Ply Lex Yacc:在某些规则中将\ n视为令牌,否则将其忽略

时间:2019-12-30 10:07:42

标签: python yacc lex ply

我正在尝试使用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作为令牌是某些规则,否则将被忽略。

有什么办法吗?

1 个答案:

答案 0 :(得分:1)

由于Ply为您提供了图灵完备的编程语言(Python)的强大功能,因此肯定会有一种方法。但是,如果不了解问题的具体细节,就不可能提供很多解决方案。

对Python本身的词法分析确实需要一个更复杂的策略,该策略包括一个小型状态机(基本上是为了消除括号内的换行符,而在括号中则忽略了换行符)。请注意,即使是简单的Python语句也必须以换行符或分号终止,因此终止符肯定在语法中。典型的Python词法分析器会忽略注释和空白行。我可以提供一个示例,但是我不知道它在这里是否有意义,因为您的语言显然仅“与Python中的语言有点类似”。

因此,我在这里努力研究一个用例,该用例适合您的问题中非常广泛的描述,并且在Ply中相对容易解决。我接受它可能与您的用例完全无关,但是它可能会为将来有不同但相似要求的读者服务。

碰到一种语言,其中的语句不需要任何形式的终止实际上是很少见的,尽管这当然不是不可能的。例如,一种典型的语言包括

  1. 以表达式(或表达式语句,例如函数调用)结尾的语句,
  2. 以表达式开头的语句,再次包括表达式语句,但也包括无关键字赋值(a = b,而不是let a = b),
  3. 表示分组和函数调用参数的双用途括号。
除非语句具有确定的终止符,否则

将是不明确的。 ({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