解析 "simple" 语法
Parsing a "simple" grammar
提前抱歉;我敢肯定,对于习惯于使用解析器和语法的人来说,这个问题看起来几乎是愚蠢的,但这些对我来说是陌生的话题,这是我尝试轻轻地进入需要它们的实际案例。
我想为以下 "language" 编写解析器,其中包含单个 "special structure",如下所示:
\command[ options ]{ contents }
内容可以是任何内容,包括嵌套命令,并且可以包含转义括号或反斜杠 \{ \} \
。我意识到 "anything" 不是特定的,但理想情况下,如果可能的话,它们应该通过匹配括号(不包括转义括号)来确定。
选项应该是以逗号分隔的赋值表达式列表,例如 name = value
,但值可以是包含 =
或 ,
字符的带引号的字符串。最后前面的 name
和 command
应该验证正则表达式 \w[\w\d\._-+*]*
-- 也就是说,第一个字符应该是字母,其余字符应该是字母、数字或其中之一. _ - + *
.
用正则表达式写这个似乎过于复杂(例如,因为值可能包含引号字符 , =
,否则会分隔赋值或 name/value 对)。所以我认为这里最合适的工具是语法,但尽管阅读浅薄,我只是不确定如何编写它(BNF、PEG 等?)、使用哪种类型的解析器(LR、递归体面等?) ,以及我如何在实际程序中使用解析输出。
我更喜欢 Python 的答案,它解释了标签,但当然,如果 necessary/better 合适,我会非常满意工具组合。
注意: 这与 LaTeX 无关。我当然意识到它们的相似性,但 LaTeX 比以前的语言复杂得多,例如字符代码会根据上下文而变化。我只是要求一个实际的例子(我认为)对于 SO 来说足够简单,但在我的日常工作中已经对我有用了。
首先用您喜欢的任何符号更正式地表达您的语法。例如,根据您的描述,EBNF 将是这样的:
program := element+
element := command | literal
literal := (not '\')+
command := '\'identifier options? '{' program '}'
options := option | options ',' option
option := identifier '=' value
value := number | string
string := '"' (escape | not '\' or '"')* '"'
escape : = '\' char
然后将其提供给解析器生成器(pyParsing、pyYACC、ANTLR)或手动编写解析器。在后一种情况下,自上而下是最简单的选择:从语法的顶部开始并将每个规则转换为一个函数,该函数将 return 一个解析的 AST 节点并使用输入或 return 什么都不做或扔。示例:
def program():
elements = []
while next_sym():
elements.append(element())
return {'type': 'program', 'children': elements}
def element():
return command() or literal()
def command():
if next_sym() == '\':
get_sym()
...parse command here
return {'type': 'command', 'children': ...}
return None
其中 next_sym
return 是输入的下一个符号(或 EOF 上的 None
)并且 get_sym
消耗该符号并推进输入缓冲区。
提前抱歉;我敢肯定,对于习惯于使用解析器和语法的人来说,这个问题看起来几乎是愚蠢的,但这些对我来说是陌生的话题,这是我尝试轻轻地进入需要它们的实际案例。
我想为以下 "language" 编写解析器,其中包含单个 "special structure",如下所示:
\command[ options ]{ contents }
内容可以是任何内容,包括嵌套命令,并且可以包含转义括号或反斜杠 \{ \} \
。我意识到 "anything" 不是特定的,但理想情况下,如果可能的话,它们应该通过匹配括号(不包括转义括号)来确定。
选项应该是以逗号分隔的赋值表达式列表,例如 name = value
,但值可以是包含 =
或 ,
字符的带引号的字符串。最后前面的 name
和 command
应该验证正则表达式 \w[\w\d\._-+*]*
-- 也就是说,第一个字符应该是字母,其余字符应该是字母、数字或其中之一. _ - + *
.
用正则表达式写这个似乎过于复杂(例如,因为值可能包含引号字符 , =
,否则会分隔赋值或 name/value 对)。所以我认为这里最合适的工具是语法,但尽管阅读浅薄,我只是不确定如何编写它(BNF、PEG 等?)、使用哪种类型的解析器(LR、递归体面等?) ,以及我如何在实际程序中使用解析输出。
我更喜欢 Python 的答案,它解释了标签,但当然,如果 necessary/better 合适,我会非常满意工具组合。
注意: 这与 LaTeX 无关。我当然意识到它们的相似性,但 LaTeX 比以前的语言复杂得多,例如字符代码会根据上下文而变化。我只是要求一个实际的例子(我认为)对于 SO 来说足够简单,但在我的日常工作中已经对我有用了。
首先用您喜欢的任何符号更正式地表达您的语法。例如,根据您的描述,EBNF 将是这样的:
program := element+
element := command | literal
literal := (not '\')+
command := '\'identifier options? '{' program '}'
options := option | options ',' option
option := identifier '=' value
value := number | string
string := '"' (escape | not '\' or '"')* '"'
escape : = '\' char
然后将其提供给解析器生成器(pyParsing、pyYACC、ANTLR)或手动编写解析器。在后一种情况下,自上而下是最简单的选择:从语法的顶部开始并将每个规则转换为一个函数,该函数将 return 一个解析的 AST 节点并使用输入或 return 什么都不做或扔。示例:
def program():
elements = []
while next_sym():
elements.append(element())
return {'type': 'program', 'children': elements}
def element():
return command() or literal()
def command():
if next_sym() == '\':
get_sym()
...parse command here
return {'type': 'command', 'children': ...}
return None
其中 next_sym
return 是输入的下一个符号(或 EOF 上的 None
)并且 get_sym
消耗该符号并推进输入缓冲区。