66问题
77----------
88你想根据一组语法规则解析文本并执行命令,或者构造一个代表输入的抽象语法树。
9- 语法非常简单,所以你可以自己写这个解析器 ,而不是使用一些框架。
9+ 如果语法非常简单,你可以自己写这个解析器 ,而不是使用一些框架。
1010
1111|
1212
4444 在EBNF中,被包含在{...}*中的规则是可选的。*代表0次或多次重复(跟正则表达式中意义是一样的)。
4545
4646现在,如果你对BNF的工作机制还不是很明白的话,就把它当做是一组左右符号可相互替换的规则。
47- 一般来讲,解析的原理就是你通过利用BNF完成多个替换和扩展以匹配输入文本和语法规则 。
47+ 一般来讲,解析的原理就是你利用BNF完成多个替换和扩展以匹配输入文本和语法规则 。
4848为了演示,假设你正在解析形如3 + 4 * 5的表达式。
4949这个表达式先要通过使用2.18节中介绍的技术分解为一组令牌流。
5050结果可能是像下列这样的令牌序列:
5353
5454 NUM + NUM * NUM
5555
56- From there, parsing involves trying to match the grammar to input tokens by making
57- substitutions:
5856 在此基础上, 解析动作会试着去通过替换操作匹配语法到输入令牌:
5957
6058.. code-block :: python
@@ -224,7 +222,7 @@ substitutions:
224222如果你在找寻关于语法,解析算法等相关的背景知识的话,你应该去看一下编译器书籍。
225223很显然,关于这方面的内容太多,不可能在这里全部展开。
226224
227- 尽管如此,编写一个迭代下降解析器的整体思路是比较简单的 。
225+ 尽管如此,编写一个递归下降解析器的整体思路是比较简单的 。
228226开始的时候,你先获得所有的语法规则,然后将其转换为一个函数或者方法。
229227因此如果你的语法类似这样:
230228
@@ -250,9 +248,171 @@ substitutions:
250248 def factor (self ):
251249 ...
252250
253- The task of each method is simple—it must walk from left to right over each part of the
254- grammar rule, consuming tokens in the process. In a sense, the goal of the method is
255- to either consume the rule or generate a syntax error if it gets stuck. To do this, the
256- following implementation techniques are applied:
251+ 每个方法要完成的任务很简单 - 它必须从左至右遍历语法规则的每一部分,处理每个令牌。
252+ 从某种意义上讲,方法的目的就是要么处理完语法规则,要么产生一个语法错误。
253+ 为了这样做,需采用下面的这些实现方法:
254+
255+ - 如果规则中的下个符号是另外一个语法规则的名字(比如term或factor),就简单的调用同名的方法即可。
256+ 这就是该算法中"下降"的由来 - 控制下降到另一个语法规则中去。
257+ 有时候规则会调用已经执行的方法(比如,在factor ::= '('expr ')'中对expr的调用)。
258+ 这就是算法中"递归"的由来。
259+ - 如果规则中下一个符号是个特殊符号(比如(),你得查找下一个令牌并确认是一个精确匹配)。
260+ 如果不匹配,就产生一个语法错误。这一节中的_expect()方法就是用来做这一步的。
261+ - 如果规则中下一个符号为一些可能的选择项(比如 + 或 -),
262+ 你必须对每一种可能情况检查下一个令牌,只有当它匹配一个的时候才能继续。
263+ 这也是本节示例中_accept()方法的目的。
264+ 它相当于_expect()方法的弱化版本,因为如果一个匹配找到了它会继续,
265+ 但是如果没找到,它不会产生错误而是回滚(允许后续的检查继续进行)。
266+ - 对于有重复部分的规则(比如在规则表达式 ::= term { ('+'|'-') term }*中),
267+ 重复动作通过一个while循环来实现。
268+ 循环主体会收集或处理所有的重复元素直到没有其他元素可以找到。
269+ - 一旦整个语法规则处理完成,每个方法会返回某种结果给调用者。
270+ 这就是在解析过程中值是怎样累加的原理。
271+ 比如,在表达式求值程序中,返回值代表表达式解析后的部分结果。
272+ 最后所有值会在最顶层的语法规则方法中合并起来。
273+
274+ 尽管向你演示的是一个简单的例子,递归下降解析器可以用来实现非常复杂的解析。
275+ 比如,Python语言本身就是通过一个递归下降解析器去解释的。
276+ 如果你对此感兴趣,你可以通过查看Python源码文件Grammar/Grammar来研究下底层语法机制。
277+ 看完你会发现,通过手动方式去实现一个解析器其实会有很多的局限和不足之处。
278+
279+ 其中一个局限就是它们不能被用于包含任何左递归的语法规则中。比如,加入你需要翻译下面这样一个规则:
280+
281+ .. code-block :: python
282+
283+ items ::= items ' ,' item
284+ | item
285+
286+ 为了这样做,你可能会像下面这样使用items()方法:
287+
288+ .. code-block :: python
289+
290+ def items (self ):
291+ itemsval = self .items()
292+ if itemsval and self ._accept(' ,' ):
293+ itemsval.append(self .item())
294+ else :
295+ itemsval = [ self .item() ]
296+
297+ 唯一的问题是这个方法根本不能工作,事实上,它会产生一个无限递归错误。
298+
299+ 关于语法规则本身你可能也会碰到一些棘手的问题。
300+ 比如,你可能想知道下面这个简单扼语法是否表述得当:
301+
302+ .. code-block :: python
303+
304+ expr ::= factor { (' +' | ' -' | ' *' | ' /' ) factor }*
305+
306+ factor ::= ' (' expression ' )'
307+ | NUM
308+
309+ 这个语法看上去没啥问题,但是它却不能察觉到标准四则运算中的运算符优先级。
310+ 比如,表达式"3 + 4 * 5"会得到35而不是期望的23.
311+ 分开使用"expr"和"term"规则可以让它正确的工作。
312+
313+ 对于复杂的语法,你最好是选择某个解析工具比如PyParsing或者是PLY。
314+ 下面是使用PLY来重写表达式求值程序的代码:
315+
316+ .. code-block :: python
317+
318+ from ply.lex import lex
319+ from ply.yacc import yacc
320+
321+ # Token list
322+ tokens = [ ' NUM' , ' PLUS' , ' MINUS' , ' TIMES' , ' DIVIDE' , ' LPAREN' , ' RPAREN' ]
323+ # Ignored characters
324+ t_ignore = ' \t\n '
325+ # Token specifications (as regexs)
326+ t_PLUS = r ' \+ '
327+ t_MINUS = r ' -'
328+ t_TIMES = r ' \* '
329+ t_DIVIDE = r ' /'
330+ t_LPAREN = r ' \( '
331+ t_RPAREN = r ' \) '
332+
333+ # Token processing functions
334+ def t_NUM (t ):
335+ r ' \d+'
336+ t.value = int (t.value)
337+ return t
338+
339+ # Error handler
340+ def t_error (t ):
341+ print (' Bad character: {!r } ' .format(t.value[0 ]))
342+ t.skip(1 )
343+
344+ # Build the lexer
345+ lexer = lex()
346+
347+ # Grammar rules and handler functions
348+ def p_expr (p ):
349+ '''
350+ expr : expr PLUS term
351+ | expr MINUS term
352+ '''
353+ if p[2 ] == ' +' :
354+ p[0 ] = p[1 ] + p[3 ]
355+ elif p[2 ] == ' -' :
356+ p[0 ] = p[1 ] - p[3 ]
357+
358+
359+ def p_expr_term (p ):
360+ '''
361+ expr : term
362+ '''
363+ p[0 ] = p[1 ]
364+
365+
366+ def p_term (p ):
367+ '''
368+ term : term TIMES factor
369+ | term DIVIDE factor
370+ '''
371+ if p[2 ] == ' *' :
372+ p[0 ] = p[1 ] * p[3 ]
373+ elif p[2 ] == ' /' :
374+ p[0 ] = p[1 ] / p[3 ]
375+
376+ def p_term_factor (p ):
377+ '''
378+ term : factor
379+ '''
380+ p[0 ] = p[1 ]
381+
382+ def p_factor (p ):
383+ '''
384+ factor : NUM
385+ '''
386+ p[0 ] = p[1 ]
387+
388+ def p_factor_group (p ):
389+ '''
390+ factor : LPAREN expr RPAREN
391+ '''
392+ p[0 ] = p[2 ]
393+
394+ def p_error (p ):
395+ print (' Syntax error' )
396+
397+ parser = yacc()
398+
399+ 这个程序中,所有代码都在一个比较高的层次。你只需要为令牌写正则表达式和规则匹配时的高阶处理函数即可。
400+ 而实际的运行解析器,接受令牌等等底层动作已经被库函数实现了。
401+
402+ 下面是一个怎样使用得到的解析对象的例子:
403+
404+ .. code-block :: python
405+
406+ >> > parser.parse(' 2' )
407+ 2
408+ >> > parser.parse(' 2+3' )
409+ 5
410+ >> > parser.parse(' 2+(3+4)*5' )
411+ 37
412+ >> >
413+
414+ 如果你想在你的编程过程中来点挑战和刺激,编写解析器和编译器是个不错的选择。
415+ 再次,一本编译器的书籍会包含很多底层的理论知识。不过很多好的资源也可以在网上找到。
416+ Python自己的ast模块也值得去看一下。
417+
257418
258- *
0 commit comments