Open8
nomでREPLに使えるパーサを作りたい
入力が完全ならそのパース結果を、途中でエラーになったらパースエラーを、続きがあると判断したら追加入力を求めたい。
REPL> 123
=> 123: Int
REPL> 12a
=> Parse Error
REPL> 1 +
| 2
=> 1 + 2 = 3
nom::character::streaming
あたりにストリーミング用のコンビネーターがあるのでそれを使えばよいが、多少の注意が必要そうだ。
nom::character::streaming::alpha1
(アルファベット1文字以上の連続)で "abc"
をパースするとIncomplete
エラーとなる(後続の入力に"d"
等のアルファベットが来る可能性があるため)。
パースしたい文法によるが、大部分のケースでは入力を工夫することで対処できそうだ。入力が"abc"
だった場合、"abc\n"
をパーサに渡せばよい。
完全に解決するにはInputTakeAtPosition traitを適切に実装する必要がありそうだ(split_at_position*
をsplit_at_position*_complete
に委譲する)
これがおそらく不可能。
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) => {
// ???
}
}
}
// ...
}
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)
}
この方針も必要な関数を全部traitに定義する必要があって面倒…… 全部シグネチャが同じというわけではないのでマクロ一発でどうにかするのも難しい
これでどうにかなりそう(本当にこんなことをする必要が……?)
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);
}