在Pest/Rust中使用PUSH和POP匹配嵌套块的问题
解决LaTeX嵌套环境块的解析问题
我在解析LaTeX的一个小型定义子集时,搞不定嵌套的\begin{}和\end{}对的识别。比如下面这段LaTeX代码:
\begin{document} Some text. \begin{quote} A quote. \end{quote} \end{document}
我预期的解析树应该是嵌套的环境结构:
- env - - expression - - env - - - expression
但实际解析出来,内部的\begin{quote}和\end{quote}被当成了普通命令,不是环境块:
- env - - expression - - command - - expression - - command
我的PEG语法规则如下:
// Top level rule is `document`. document = { SOI ~ (section? ~ newline_char)* ~ section? ~ EOI } section = { env_stmt | cmd_stmt | expression } // Expression grammar expression = { ( cmd_stmt | literal )* } literal = @{ char+ } char = @{ ASCII_ALPHANUMERIC | punctuation } punctuation = { "," | "." | ";" | "(" | ")" | "[" | "]" | "|" | "<" | ">" | ":" } // Control Statement Grammar cmd_stmt = { ctrl_character ~ name ~ cmd_stmt_opt? ~ "{" ~ expression ~ "}" } cmd_stmt_opt = { "[" ~ name ~ "]" } name = @{ ASCII_ALPHA+ } COMMENT = _{ "%" ~ (!newline_char ~ ANY)* ~ newline_char } WHITESPACE = _{ " " } newline_char = _{"\n"} ctrl_character = _{ "\\" } // Environment Grammar env_stmt = { env_begin ~ env_content ~ env_end } env_content = { (section? ~ newline_char)* } env_begin = @{ ctrl_character ~ "begin" ~ "{" ~ PUSH(name) ~ "}" } env_end = @{ ctrl_character ~ "end" ~ "{" ~ PEEK ~ "}" }
我猜是PUSH/PEEK的问题,但去掉它们后,只有内部块能被解析成环境,外部块反而变成了控制语句。
问题出在哪
- 普通命令的规则太宽:
cmd_stmt里的name允许任意字母组合,\begin和\end会被误判成普通命令,而不是环境的专属开头/结尾。 - 栈的维护有问题:
env_end只检查栈顶的环境名(PEEK),但没把它从栈里弹出来(POP),嵌套环境时栈的状态乱掉,后续匹配自然出错。
修正后的语法
// Top level rule is `document`. document = { SOI ~ (section? ~ newline_char)* ~ section? ~ EOI } section = { env_stmt | cmd_stmt | expression } // Expression grammar expression = { ( cmd_stmt | literal )* } literal = @{ char+ } char = @{ ASCII_ALPHANUMERIC | punctuation } punctuation = { "," | "." | ";" | "(" | ")" | "[" | "]" | "|" | "<" | ">" | ":" } // Control Statement Grammar // 禁止begin/end作为普通命令的名称 cmd_stmt = { ctrl_character ~ (name & !("begin" | "end")) ~ cmd_stmt_opt? ~ "{" ~ expression ~ "}" } cmd_stmt_opt = { "[" ~ name ~ "]" } name = @{ ASCII_ALPHA+ } COMMENT = _{ "%" ~ (!newline_char ~ ANY)* ~ newline_char } WHITESPACE = _{ " " } newline_char = _{"\n"} ctrl_character = _{ "\\" } // Environment Grammar env_stmt = { env_begin ~ env_content ~ env_end } env_content = { (section? ~ newline_char)* } // 把环境名压入栈 env_begin = @{ ctrl_character ~ "begin" ~ "{" ~ PUSH(name) ~ "}" } // 匹配栈顶环境名并弹出 env_end = @{ ctrl_character ~ "end" ~ "{" ~ POP ~ "}" }
修正说明
- 限制普通命令的名称:在
cmd_stmt里加了& !("begin" | "end"),确保begin和end不会被当作普通命令的名字,这样\begin{...}和\end{...}只会被环境的规则匹配。 - 完善栈操作:把
env_end里的PEEK改成POP,匹配栈顶环境名后立刻弹出,保证嵌套环境的栈状态正确:- 解析
\begin{document}时,把document压栈; - 解析内部
\begin{quote}时,把quote压栈; - 解析
\end{quote}时,弹出栈顶的quote,栈顶回到document; - 最后解析
\end{document}时,弹出document,栈清空。
- 解析
这样就能正确识别嵌套的环境块,生成预期的解析树了。
内容的提问来源于stack exchange,提问作者pierre
相关产品推荐
相关产品推荐

