我正在尝试使用ply编写解析器,其中\n
在语法上有时很重要,有时必须忽略。
更准确地说,是我想解析的语言中,有一些与定义相对应的行必须以\n
结尾以指示定义的结尾。在所有其他情况下,\n
必须忽略,它仅用于计数输入文件中的行。
例如:
def1
def2
def3
def4
将是有效的,但:
def1
def2 def3
def 4
不会,因为每个定义都必须以\n
结尾
我想要的东西与我们可以在其中编写的Python有点类似:
def a(b):
if b==0:
return (b+1)
或
def a(b):
if b==0:
return (b+1)
但是
def a(b): if b==0: return (b+1)
不允许。
\n
是指示语句结束所必需的,但是对代码没有影响。
我不知道要用ply重现这种行为。 如果我这样定义令牌:
def t_NEWLINE(self,t):
r'\n+'
t.lexer.lineno += len(t.value)
return t
除非语法明确允许将此标记插入几乎所有位置,否则不允许\n
。
我考虑过上下文语法,但我的情况只有一个上下文。我只是希望能够同时使用\n
作为令牌是某些规则,否则将被忽略。
有什么办法吗?
答案 0 :(得分:1)
由于Ply为您提供了图灵完备的编程语言(Python)的强大功能,因此肯定会有一种方法。但是,如果不了解问题的具体细节,就不可能提供很多解决方案。
对Python本身的词法分析确实需要一个更复杂的策略,该策略包括一个小型状态机(基本上是为了消除括号内的换行符,而在括号中则忽略了换行符)。请注意,即使是简单的Python语句也必须以换行符或分号终止,因此终止符肯定在语法中。典型的Python词法分析器会忽略注释和空白行。我可以提供一个示例,但是我不知道它在这里是否有意义,因为您的语言显然仅“与Python中的语言有点类似”。
因此,我在这里努力研究一个用例,该用例适合您的问题中非常广泛的描述,并且在Ply中相对容易解决。我接受它可能与您的用例完全无关,但是它可能会为将来有不同但相似要求的读者服务。
碰到一种语言,其中的语句不需要任何形式的终止实际上是很少见的,尽管这当然不是不可能的。例如,一种典型的语言包括
a = b
,而不是let a = b
),将是不明确的。 ({a(b)
可以是一个函数调用(一个语句)或两个连续的表达式语句;对于具有上述特征的大多数语言,可以构造类似的示例。
不过,语言设计可以克服所有这些问题。这种设计最简单的方法是要求所有语句,甚至函数调用和赋值,都以关键字开头。大概使用这种语言,定义语句也以关键字开头,并且坚持围绕定义换行的唯一原因是美观。 (但是,美观是有原因的。美观而不是解析限制会导致问题中的Python单行定义是非法的。)
然后,假设我们有一门语言,其定义语句以其符号为DEF
的关键字开始,并以符号END
结束(否则,我们将不知道定义的结尾)。并且我们还将假设赋值语句以关键字LET
开头,不需要显式终止。 (当然会有其他语句类型,但是它们将遵循与LET
相同的模式。)无论出于何种原因,我们都想确保DEF
始终是一行上的第一个标记,而{ END
始终是最后一个标记,尽管我们对LET a = b LET c = 3
感到满意,但它可以确保一个定义不会与任何其他语句水平并存。
执行此操作的一种方法是忽略换行符,除了在DEF
之前或在END
之后的换行符。然后,我们将编写一个语法,其中包括:
lines : #empty
| lines line NEWLINE
line : #empty
| line simple_stmt
| definition
definition : DEF prototype lines END
simple_stmt : LET lhs '=' rhs
请注意,以上语法要求程序为空或以NEWLINE结尾。
现在,要过滤掉不重要的NEWLINE
标记,我们可以在Ply生成的词法分析器周围使用包装器类。包装器的构造函数将词法分析器作为参数,并通过删除认为不重要的NEWLINE标记来过滤该词法分析器的输出流。我们还通过在必要时构造NEWLINE令牌来确保输入以NEWLINE结尾(除非它为空)。 (这实际上不是问题的一部分,但是它简化了语法。)
# Used to fabricate a token object.
from types import SimpleNamespace
class LexerWrapper(object):
def __init__(self, lexer):
"""Create a new wrapper given the lexer which is being wrapped"""
self.lexer = lexer
# None, or tuple containing queued token.
# Using a tuple allows None (eof) to be queued.
self.pending = None
# Previous token or None
self.previous = None
def token(self):
"""Return the next token, or None if end of input has been reached"""
# If there's a pending token, send it
if self.pending is not None:
t = self.pending[0]
self.pending = None
return t
# Get the next (useful) token
while True
t = self.lexer.token()
# Make sure that we send a NEWLINE before EOF
if t is None:
t, self.previous = self.previous, None
self.pending = (None,)
if t is not None and t.type != 'NEWLINE':
# Manufacture a NEWLINE token if necessary
t = SimpleNamespace( type='NEWLINE'
, value='\n'
, lineno=self.lexer.lineno
)
return t
elif t.type == 'NEWLINE':
if self.previous is None or self.previous.type == 'NEWLINE':
# Get another token
continue
if self.previous.type == 'END':
# Use this NEWLINE if it follows END
self.previous = None
else:
# Get another token
self.previous = t
continue
else:
self.previous = t
return t