PEG:ifステートメントの文法の何が問題になっていますか?

Aug 23 2020

rust-pegを使用してOCamlのような言語を実装していますが、パーサーにバグがあります。if文の文法を定義しましたが、機能しません。

テストケースの入力はとして解析されると思いApply(Apply(Apply(Apply(f, then), 2) else), 4)ます。私は意味"then"ないキーワードのIdentとして解析されます。

このapply-expression文法を修正する方法がわかりません。あなたはなにか考えはありますか?

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Expression {
    Number(i64),
    If {
        cond: Box<Expression>,
        conseq: Box<Expression>,
        alt: Box<Expression>,
    },
    Ident(String),
    Apply(Box<Expression>, Box<Expression>),
}

use peg::parser;
use toplevel::expression;
use Expression::*;

parser! {
pub grammar toplevel() for str {

    rule _() = [' ' | '\n']*

    pub rule expression() -> Expression
        = expr()

    rule expr() -> Expression
        = if_expr()
        / apply_expr()

    rule if_expr() -> Expression
        = "if" _ cond:expr() _ "then" _ conseq:expr() _ "else" _ alt:expr() {
            Expression::If {
                cond: Box::new(cond),
                conseq: Box::new(conseq),
                alt: Box::new(alt)
            }
        }

    rule apply_expr() -> Expression
        = e1:atom() _ e2:atom() { Apply(Box::new(e1), Box::new(e2)) }
        / atom()

    rule atom() -> Expression
        = number()
        / id:ident() { Ident(id) }

    rule number() -> Expression
        = n:$(['0'..='9']+) { Expression::Number(n.parse().unwrap()) } rule ident() -> String = id:$(['a'..='z' | 'A'..='Z']['a'..='z' | 'A'..='Z' | '0'..='9']*) { id.to_string() }
}}

fn main() {
    assert_eq!(expression("1"), Ok(Number(1)));
    assert_eq!(
        expression("myFunc 10"),
        Ok(Apply(
            Box::new(Ident("myFunc".to_string())),
            Box::new(Number(10))
        ))
    );

    // failed
    assert_eq!(
        expression("if f then 2 else 3"),
        Ok(If {
            cond: Box::new(Ident("f".to_string())),
            conseq: Box::new(Number(2)),
            alt: Box::new(Number(3))
        })
    );
}
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `Err(ParseError { location: LineCol { line: 1, column: 11, offset: 10 }, expected: ExpectedSet { expected: {"\"then\"", "\' \' | \'\\n\'"} } })`,
 right: `Ok(If { cond: Ident("f"), conseq: Number(2), alt: Number(3) })`', src/main.rs:64:5

回答

2 orlp Aug 26 2020 at 16:22

PEGは順序付けられた選択肢を使用します。つまり、R = A / Bあるルールを記述したときにR、ある位置でA正常に解析された場合、その選択によって後で問題が発生したとしても、そのルール試行されません。これは文脈自由文法との主な違いであり、見過ごされがちです。BA

特に、あなたが書いたときにapply = atom atom / atom、それは行の二つの原子を解析することが可能です場合、それはなります決して、それは後で意味がない残りの部分を意味していても、1つだけ1を解析しようとしません。

これを、thenelseが文法の完全に優れた識別子であるという事実と組み合わせると、発生する問題が発生します。