highlight

2020年7月14日火曜日

Sprache: 位置情報を得るには

テキストをパースする処理を実装する際、入力元の何行目とか、そういった位置情報を取得したい場合というのがある。 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 件のコメント:

コメントを投稿

スパムフィルタが機能しないようなので、コメント不可にしました。

注: コメントを投稿できるのは、このブログのメンバーだけです。