当您想接受所有元素时,您可以显式声明 A 后面不跟 END 的规则,并使用 yacc 和 PLY 友好地处理模棱两可的规则这一事实。
你可以简单地有一个正常的规则:
Expression : A END
低于将发出警告的较低优先级规则(稍后会出现)
Expression : A
这样,所有的 A 都将被接受,不会出现任何语法错误,并且对于任何没有后跟 END 的 A 都会发出警告,包括流程末尾的一个。为了更容易找到违规的A,我在警告中添加了符号在流程中的位置。
编辑:
修改脚本以正确处理其他语法错误(例如AENDENDAEND),并通过将expressions : expression expressions替换为expressions : expressions expression来立即减少expressions
这是修改后的脚本(在 python 3.4 中测试,只需将 raw_input 替换为 input):
from __future__ import print_function
# Tokens
tokens = ('A', 'END')
t_A = r'A'
t_END = r'END'
t_ignore = " "
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
# Build the lexer
import ply.lex as lex
lex.lex()
# Rules
def p_statement_expr(p):
'''statement : expressions'''
print("parsed:", p[1])
def p_expressions(p):
'''expressions : expressions expression'''
p[0] = p[1] + [p[2]]
def p_expressions_err(p):
'''expressions : expressions error'''
p[0] = p[1]
def p_expressions_empty(p):
'''expressions : '''
p[0] = list()
def p_expression_pharse(p):
'''expression : A END'''
p[0] = 'A'
# add a separate rule BELOW previous one to display a warning
def p_expression_pharse_warn(p):
'''expression : A'''
print("Warning at absolute position %d (line %d)" % (p.lexpos(1), p.lineno(1)))
p[0] = 'A'
def p_error(p):
if p:
print("Syntax error at '%s'" % p.value)
else:
print("Syntax error at EOI")
import ply.yacc as yacc
yacc.yacc()
while 1:
try:
s = raw_input('query > ') # use input() on Python 3
except EOFError:
break
yacc.parse(s)
编辑:以下是避免附加规则的错误尝试:它比上述版本更复杂且效率更低。请看下面我的结论
根据评论编辑:
我理解你的观点,你不想增加语法规则。除了最后一个令牌外,它可以是容错的。如果您的最后一个令牌有误,它将不会跟随任何内容,并且永远不会被规则expression : A error 捕获。
但是这里有一个容错解析器,如果出现错误,它会保留除最后一个标记之外的所有内容:
from __future__ import print_function
# Tokens
tokens = ('A', 'END')
t_A = r'A'
t_END = r'END'
t_ignore = " "
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
# Build the lexer
import ply.lex as lex
lex.lex()
# Rules
def p_statement_expr(p):
'''statement : expressions'''
# print("parsed:", p[1])
def p_expressions(p):
'''expressions : expressions expression'''
p[0] = p[1] + [p[2]]
result.append(p[2])
def p_expressions_empty(p):
'''expressions : '''
p[0] = list()
def p_expression_pharse(p):
'''expression : A END
| A error'''
p[0] = 'A'
def p_error(p):
if p:
global lasterr
print("Syntax error at '%s' (%d)" % (p.value, p.lexpos))
else:
print("Syntax error at EOI")
import ply.yacc as yacc
yacc.yacc()
while 1:
try:
s = input('query > ') # use input() on Python 3
except EOFError:
break
result = []
yacc.parse(s)
print('Result', result)
原则是通过expressions : expressions expression而不是expressions : expression expressions进行整理,并将所有内容保存在一个全局变量中。
输入 A END A A END A A A END 会给出
Result ['A', 'A', 'A', 'A', 'A', 'A']
加上:A END A A END A A A END,它给出了
Result ['A', 'A', 'A', 'A', 'A']
(除最后一个之外的所有标记)
使用真正的 flex - bison 解决方案,可以使用在输入末尾匹配的特殊 <<EOF>> 标记,在最后一个标记之后总是有另一个标记。不幸的是,它没有在 PLY 中实现,唯一真正的解决方案是引入一个单独接受 A 令牌的规则。对于真正的解析器,它还保证您实际上正在处理正确的令牌:我使用了
def p_expression_pharse(p):
'''expression : A END'''
p[0] = 1 + p.lexpos(1)
# add a separate rule BELOW previous one to display a warning
def p_expression_pharse_warn(p):
'''expression : A'''
print("Warning at absolute position %d (line %d)" % (p.lexpos(1), p.lineno(1)))
p[0] = -1 - p.lexpos(1)
唯一标识结果字符串中的标记,我得到正确的位置。
而且……错误处理很简单……
讨论 TL/DR:
我承认我错过了上次令牌错误恢复的要点。这是因为在我在实际用例中看到的所有解析器中,错误恢复包括拒绝语法不正确的部分(因此不能直接使用)并在下一个正确的 token 组上重新同步解析器。在我所看到的所有情况中,如果可以使用部分句子,它一定不是由错误恢复机制处理,而是由语法规则处理,其中很容易描述适当的动作。
如果您只是想保留违规输入以供以后处理,我认为这不是取决于语法的操作问题,我会简单地记下违规令牌的位置,或者最多记下正确的位置 last分析标记(一个完整元素的结尾),第一个错误恢复标记的开始,并说中间的内容不正确。
但这与这里的要求有很大不同......