テキストをパースする処理を実装する際、入力元の何行目とか、そういった位置情報を取得したい場合というのがある。 Sprache ではどうすれば取得できるのかを記事にしておく。
言葉で説明すると、IPositionAware
という interface の SetPos()
メソッドを実装した class を定義して、位置情報を取りたい箇所で SetPos()
を呼ぶ事で、位置情報やマッチした長さが渡されるので、その情報を参照すれば良いという事になる。
まず IPositionAware
を実装する。こんな感じのクラスがあれば位置情報と値を保持できるハズ。
public class ParsingLocator<T> : IPositionAware<ParsingLocator<T>>
{
public T Value { get; }
public Position Start { get; private set; }
public int Length { get; private set; }
public ParsingLocator<T> SetPos(Position startPos, int length)
{
Start = startPos;
Length = length;
return this;
}
public ParsingLocator(T val)
{
Value = val;
}
}
次に、拡張メソッドを定義して、Parser
のメソッドに見せかける(勿論、実装者の好み次第で別の方法を採っても良い)。
Sprache で定義されている Positioned()
は、IPositionAware
を引数とする拡張メソッドで、これを呼ぶと内部で SetPos()
を呼んでくれる。
Locate()
拡張メソッドで、new ParsingLocator()
と Positioned()
呼び出しを同時に行えれば便利ではないかという目論見。
Select()
は上の ParsingLocator
を Linq 式で使える様にする為の拡張メソッド。
public static class ParsingLocatorExt
{
public static Parser<ParsingLocator<T>> Locate<T>(this Parser<T> value)
=> value.Select(x => new ParsingLocator<T>(x)).Positioned();
public static R Select<T, R>(this ParsingLocator<T> obj, Func<ParsingLocator<T>, R> func)
=> func(obj);
}
数値文字列を int
に解釈するパーサ(Num
)に組み合わせるという例を書くとこんな感じかな。なお、テストはしてない。
Parser
に対して Locate()
を呼び出すと ParsingLocator
オブジェクトが返ってきて、そこに Parser
が返した値と位置情報が入っているので、
select
句で好きなように加工して、情報を取得すればいい。ここでは単純にタプルに入れて返している。
static Parser<int> Num = Parse.Decimal.Select(x => int.Parse(x));
static Parser<(int, Position, int)> NumWithLocation =
from num in Num.Locate()
select (num.Value, num.Start, num.Length);
0 件のコメント:
コメントを投稿
スパムフィルタが機能しないようなので、コメント不可にしました。
注: コメントを投稿できるのは、このブログのメンバーだけです。