rust lalrpop 词法歧义:括号内的非贪婪匹配

rust lalrpop lexing ambiguity: non-greedy matching inside of brackets

我正在尝试解析具有此 BNF 的 SGF 格式:

  Collection = GameTree { GameTree }
  GameTree   = "(" Sequence { GameTree } ")"
  Sequence   = Node { Node }
  Node       = ";" { Property }
  Property   = PropIdent PropValue { PropValue }
  PropIdent  = UcLetter { UcLetter }
  PropValue  = "[" CValueType "]"
  CValueType = (ValueType | Compose)
  ValueType  = (None | Number | Real | Double | Color | SimpleText |
        Text | Point  | Move | Stone)

我不是要使用 lalrpop 将 ValueType 解析为不同的类型。我只想要属性的原始文本内容。

我的 Property 规则有问题。具体来说,我在测试文件中有这一行

;AB[dp];AB[pp]HA[6]

这是两个节点。第一个节点有一个 Property,第二个有两个。括号中的内容必须是 .* 因为任何东西都可以放在那里。任意自由文本是某些属性的有效值。

使用 lalrpop

PropValue = r"\[" <r".*"> r"\]";

作为规则失败,因为它匹配 pp]HA[6 当然它只需要匹配 pp.

合理的,(因为我不知道这是如何实现的),

PropValue = r"\[" <r".*?"> r"\]";

也失败了,错误信息非常好:

/mnt/c/Users/mason_000/wsl/dev/rust/seraph/gosgf/src/parse_sgf.lalrpop:18:5: 18:10 error: "non-greedy" repetitions (`*?` or `+?`) are not supported in regular expressions

但现在我陷入了困境,因为我需要在这里进行非贪婪匹配。

我可以做的一件事是匹配不是右括号的所有内容。我不确定这是否是解决这种特殊类型歧义的预期方法(第一次使用 lalr 解析器)。我也不确定 ;HA[My free text ]]]] 是否是一个包含 My free text ]]] 内容的有效文件。但是,如果它 一个有效文件,则此解决方法将不起作用。

而且,这似乎没有用:

PropValue = r"\[" <r"[^\]]"> r"\]";

解析失败,我无法准确破译位置。

        thread 'core::sgf_replays::game189_has_6_handicap' panicked at 'called `Result::unwrap()` on an `Err` value: UnrecognizedToken { token: Some((7, Token(0, "9"), 8)), expected: ["r#\"\\]\"#"] }', libcore/result.rs:945:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
             at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::print
             at libstd/sys_common/backtrace.rs:71
             at libstd/sys_common/backtrace.rs:59
   2: std::panicking::default_hook::{{closure}}
             at libstd/panicking.rs:380
   3: std::panicking::default_hook
             at libstd/panicking.rs:390
   4: std::panicking::rust_panic_with_hook
             at libstd/panicking.rs:576
   5: std::panicking::begin_panic
             at libstd/panicking.rs:537
   6: std::panicking::begin_panic_fmt
             at libstd/panicking.rs:521
   7: rust_begin_unwind
             at libstd/panicking.rs:497
   8: core::panicking::panic_fmt
             at libcore/panicking.rs:71
   9: core::result::unwrap_failed
             at /checkout/src/libcore/macros.rs:23
  10: <core::result::Result<T, E>>::unwrap
             at /checkout/src/libcore/result.rs:782
  11: seraph::core::sgf_replays::game189_has_6_handicap
             at src/core/mod.rs:612
  12: <F as alloc::boxed::FnBox<A>>::call_box
             at libtest/lib.rs:1453
             at /checkout/src/libcore/ops/function.rs:223
             at /checkout/src/liballoc/boxed.rs:788
  13: __rust_maybe_catch_panic
             at libpanic_unwind/lib.rs:102

为了完整起见,这里是 .lalrpop

use std::str::FromStr;
use gosgf::*;
use std::collections::HashMap;
grammar;

pub Collection: GoCollection = <GameTree*>;

match {
    r"\(",
     r"\)",
     r";" ,
     r"\[",
     r"\]",
    r"[A-Z]+",
}
else {
    r"[^\]]",
}
GameTree: GameTree = {
    r"\(" <sequence: Sequence> <children: GameTree*> r"\)" => {

        let komi = f64::from_str(sequence[0].properties.get("KM").unwrap_or(&"0.0".to_owned())).unwrap();
        let size = usize::from_str(sequence[0].properties.get("SZ").unwrap_or(&"19".to_owned())).unwrap();

        let handicap;
        {
            let mut handistr = String::from("0");
            for node in &sequence {
                if let Some(ha) = node.properties.get("HA") {
                    handistr = ha.to_string();
                    break;
                }
            }

            handicap =  usize::from_str(&handistr).unwrap();
        }

        GameTree {
            komi,
            size,
            handicap,
            sequence,
            children,
        }
    }
};

Sequence = <Node+>;

Node: Node  = {
    r";" <pairs: Property+> => {
        let mut properties : HashMap<String, String> = HashMap::new();
        for (k, v) in pairs {
            properties.insert(k, v);
        }
        Node { properties }
    } 
};

Property: (String, String) = {
    <k: PropIdent> <v: PropValue> => (k.to_string(), v.to_string())
};
PropIdent = <r"[A-Z]+">;
PropValue = r"\[" <r".*"> r"\]";

这是我想出的一个答案。我放弃了多个单独的终端,并使用 [^\]] 技巧将它们全部组合成一个大终端。

Property: (String, String) = {
    <r"[A-Z]+\[[^\]]*\]"> => {
        lazy_static! {
            static ref RE : regex::Regex = regex::Regex::new(r"([A-Z]+)\[([^\]]*)\]").unwrap();
        }

        let cap = RE.captures(<>).unwrap();

        let k = &cap[1];
        let v = &cap[2];
        (k.to_string(), v.to_string())
    }
};

还不打算接受这个答案,因为我不确定这是给定可用工具的最优雅的解决方案,而且我还想知道是否可以创建一个解析

的规则
`;HA[My Text]]QB[[Nested]]`

作为两个属性 ("HA", "My Text]")("QB", "[Nested]") 作为结果,或者如果这种表达式无法用 lalr(1) 解析器解析。

编辑:尽管 Stefan 指出有效的 SGF 4 文件将转义其右括号,但我只是想知道这是否可能。我开始怀疑它是否是,因为一旦不看 属性 到下一件事,就永远无法知道括号是文本的一部分还是 属性 的结尾。