Open8

nomでREPLに使えるパーサを作りたい

todeskingtodesking

入力が完全ならそのパース結果を、途中でエラーになったらパースエラーを、続きがあると判断したら追加入力を求めたい。

REPL> 123
=> 123: Int
REPL> 12a
=> Parse Error
REPL> 1 +
    | 2
=> 1 + 2 = 3

todeskingtodesking

nom::character::streaming あたりにストリーミング用のコンビネーターがあるのでそれを使えばよいが、多少の注意が必要そうだ。
nom::character::streaming::alpha1(アルファベット1文字以上の連続)で "abc" をパースするとIncompleteエラーとなる(後続の入力に"d"等のアルファベットが来る可能性があるため)。

todeskingtodesking

パースしたい文法によるが、大部分のケースでは入力を工夫することで対処できそうだ。入力が"abc"だった場合、"abc\n"をパーサに渡せばよい。

todeskingtodesking

完全に解決するにはInputTakeAtPosition traitを適切に実装する必要がありそうだ(split_at_position*split_at_position*_completeに委譲する)

todeskingtodesking

これがおそらく不可能。

struct Complete<T: InputTakeAtPosition>(T);
impl <T: InputTakeAtPosition> InputTakeAtPosition for Complete<T> {
    fn split_at_position1<P, E: ParseError<Self>>(
        &self,
        predicate: P,
        e: ErrorKind
    ) -> IResult<Self, Self, E> {
        // 第二型引数は E1 :ParseError<T> である必要がある。
        // この型はE: ParseError<Complete<T>> とは互換性がないため、
        // 何らかの手段で変換してやる必要があるが、汎用的な手段はなさそう
        match self.0.split_at_position<P, E=E1>(predicate, e) {
            Err(e) => {
                // ???
            }
        }
    }
    // ...
}
todeskingtodesking

InputTakeAtPositionの実装内でエラーを正しくハンドリングする方法がわからん、面倒なのでこのような解決法になりそう:

pub trait ParsingPolicy {
    fn alpha1<T, E: nom::error::ParseError<T>>(input: T) -> nom::IResult<T, T, E>
        where T: nom::InputTakeAtPosition,
              T::Item: nom::AsChar;
}

pub struct ParseComplete;
impl ParsingPolicy for ParseComplete {
    fn alpha1<T, E: nom::error::ParseError<T>>(input: T) -> nom::IResult<T, T, E>
        where T: nom::InputTakeAtPosition,
              T::Item: nom::AsChar {
        nom::character::complete::alpha1(input)
    }
}


pub struct ParseStream;
impl ParsingPolicy for ParseStream {
    fn alpha1<T, E: nom::error::ParseError<T>>(input: T) -> nom::IResult<T, T, E>
        where T: nom::InputTakeAtPosition,
              T::Item: nom::AsChar {
        nom::character::streaming::alpha1(input)
    }
}



pub fn parse<P: ParsingPolicy, I>(src: I) -> IResult<I, I>
where I: nom::InputTakeAtPosition,
      <I as nom::InputTakeAtPosition>::Item: nom::AsChar,
{
    P::alpha1(src)
}
todeskingtodesking

この方針も必要な関数を全部traitに定義する必要があって面倒…… 全部シグネチャが同じというわけではないのでマクロ一発でどうにかするのも難しい

todeskingtodesking

これでどうにかなりそう(本当にこんなことをする必要が……?)

parsing_policy.rs
use nom::error::ParseError;
use nom::AsChar;
use nom::Compare;
use nom::IResult;
use nom::InputLength;
use nom::InputTake;
use nom::InputTakeAtPosition;
use std::clone::Clone;

macro_rules! parsing_policy_methods {
    ($mode:ident) => {
        parsing_policy_methods!(@impl $mode bytes tag(tag)
            <T, Input, Error: ParseError<Input>>(
                tag: T
            ) -> impl Fn(Input) -> IResult<Input, Input, Error>
            where
                Input: InputTake + InputLength + Compare<T>,
                T: InputLength + Clone
        );
        parsing_policy_methods!(@impl $mode character alpha1(input)
            <T, E: ParseError<T>>(input: T) -> IResult<T, T, E>
            where
                T: InputTakeAtPosition,
                <T as InputTakeAtPosition>::Item: AsChar,
        );
    };
    (@impl trait $ns:ident $name:ident($($args:ident),*) $($sig:tt)*) => {
        fn $name $($sig)*;
    };
    (@impl complete $ns:ident $name:ident ( $($args:ident),*) $($sig:tt)* ) => {
        fn $name $($sig)* {
            nom::$ns::complete::$name($($args),*)
        }
    };
    (@impl streaming $ns:ident $name:ident ( $($args:ident),*) $($sig:tt)* ) => {
        fn $name $($sig)* {
            nom::$ns::streaming::$name($($args),*)
        }
    };
}

pub trait ParsingPolicy {
    parsing_policy_methods!(trait);
}
pub struct ParsingPolicyComplete;
pub struct ParsingPolicyStreaming;
impl ParsingPolicy for ParsingPolicyComplete {
    parsing_policy_methods!(complete);
}
impl ParsingPolicy for ParsingPolicyStreaming {
    parsing_policy_methods!(streaming);
}