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

问题描述 投票:0回答:1

[我正在尝试使用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是指示语句结束所必需的,但对代码没有任何影响。

而且我不知道要用层板重现这种行为。如果我像这样定义令牌:

def t_NEWLINE(self,t):
    r'\n+'
    t.lexer.lineno += len(t.value)
    return t

除非语法明确允许将此标记插入几乎所有位置,否则将不允许\n

我考虑过上下文语法,但是我的情况只有一个上下文。我只是想能够同时使用\n作为令牌是某些规则,否则将被忽略。

有什么办法吗?

python yacc lex ply
1个回答
0
投票

由于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
© www.soinside.com 2019 - 2024. All rights reserved.