【问题标题】:Controlling Python PLY lexer states from parser从解析器控制 Python PLY 词法分析器状态
【发布时间】:2012-03-24 02:35:49
【问题描述】:

我正在开发一个简单的 SQL 选择,例如查询解析器,我需要能够从字面上捕获可能在某些地方发生的子查询。我发现词法分析器状态是最好的解决方案,并且能够使用花括号来标记开始和结束来进行 POC。但是,子查询将由括号而不是花括号分隔,并且括号也可以出现在其他地方,所以我不能成为每个开放括号的状态。解析器很容易获得此信息,因此我希望在解析器规则中的适当位置调用 begin 和 end。然而这并没有奏效,因为词法分析器似乎一次对流进行了标记,因此标记是在 INITIAL 状态下生成的。有解决此问题的方法吗?以下是我尝试做的概要:

def p_value_subquery(p):
    """
     value : start_sub end_sub
    """
    p[0] = "( " + p[1] + " )"

def p_start_sub(p):
    """
    start_sub : OPAR
    """
    start_subquery(p.lexer)
    p[0] = p[1]

def p_end_sub(p):
    """
    end_sub : CPAR
    """
    subquery = end_subquery(p.lexer)
    p[0] = subquery

start_subquery() 和 end_subquery() 定义如下:

def start_subquery(lexer):
    lexer.code_start = lexer.lexpos        # Record the starting position
    lexer.level = 1
    lexer.begin('subquery') 

def end_subquery(lexer):
    value = lexer.lexdata[lexer.code_start:lexer.lexpos-1]
    lexer.lineno += value.count('\n')
    lexer.begin('INITIAL')
    return value

词法分析器标记只是用来检测近括号:

@lex.TOKEN(r"\(")
def t_subquery_SUBQST(t):
    lexer.level += 1

@lex.TOKEN(r"\)")
def t_subquery_SUBQEN(t):
    lexer.level -= 1

@lex.TOKEN(r".")
def t_subquery_anychar(t):
    pass

我将不胜感激。

【问题讨论】:

    标签: python yacc lexer ply


    【解决方案1】:

    这个答案可能只是部分有用,但我也建议查看 PLY 文档 (http://www.dabeaz.com/ply/ply.html) 的“6.11 嵌入式操作”部分。简而言之,可以编写在规则中间发生动作的语法规则。它看起来类似于:

    def p_somerule(p):
        '''somerule : A B possible_sub_query LBRACE sub_query RBRACE'''
    
    def p_possible_sub_query(p):
        '''possible_sub_query :'''
        ...
        # Check if the last token read was LBRACE.   If so, flip lexer state
        # Sadly, it doesn't seem that the token is easily accessible. Would have to hack it
        if last_token == 'LBRACE':
            p.lexer.begin('SUBQUERY')
    

    关于词法分析器的行为,只有一个前瞻标记被使用。因此,在任何特定的语法规则中,最多只读取了一个额外的标记。如果要翻转词法分析器状态,则需要确保它发生在解析器使用令牌之前,但在解析器要求读取下一个传入令牌之前。

    另外,如果可能的话,我会尽量避免使用 yacc() 错误处理堆栈作为解决方案。错误处理中有太多的黑魔法——越能避免越好。

    目前我的时间有点紧迫,但这似乎可以为下一版本的 PLY 进行调查。会把它放在我的待办事项清单上。

    【讨论】:

    • 感谢 dmbedded 操作的指针,它看起来很有希望。但是,在您的示例中,我们应该检查前瞻令牌而不是最后一个令牌?最后一个令牌是B,但前瞻是LBRACE,对吗?
    【解决方案2】:

    根据 PLY 作者的回复,我想出了这个更好的解决方案。我还没有弄清楚如何将子查询作为令牌返回,但其余的看起来好多了,不再需要被视为 hack。

    def start_subquery(lexer):
        lexer.code_start = lexer.lexpos        # Record the starting position
        lexer.level = 1
        lexer.begin("subquery")
    
    def end_subquery(lexer):
        lexer.begin("INITIAL")
    
    def get_subquery(lexer):
        value = lexer.lexdata[lexer.code_start:lexer.code_end-1]
        lexer.lineno += value.count('\n')
        return value
    
    @lex.TOKEN(r"\(")
    def t_subquery_OPAR(t):
        lexer.level += 1
    
    @lex.TOKEN(r"\)")
    def t_subquery_CPAR(t):
        lexer.level -= 1
        if lexer.level == 0:
            lexer.code_end = lexer.lexpos        # Record the ending position
            return t
    
    @lex.TOKEN(r".")
    def t_subquery_anychar(t):
        pass
    
    def p_value_subquery(p):
        """
        value : check_subquery_start OPAR check_subquery_end CPAR
        """
        p[0] = "( " + get_subquery(p.lexer) + " )"
    
    def p_check_subquery_start(p):
        """
        check_subquery_start : 
        """
        # Here last_token would be yacc's lookahead.
        if last_token.type == "OPAR":
            start_subquery(p.lexer)
    
    def p_check_subquery_end(p):
        """
        check_subquery_end : 
        """
        # Here last_token would be yacc's lookahead.
        if last_token.type == "CPAR":
            end_subquery(p.lexer)
    
    last_token = None
    
    def p_error(p):
        global subquery_retry_pos
        if p is None:
            print >> sys.stderr, "ERROR: unexpected end of query"
        else:
            print >> sys.stderr, "ERROR: Skipping unrecognized token", p.type, "("+ \
                    p.value+") at line:", p.lineno, "and column:", find_column(p.lexer.lexdata, p)
            # Just discard the token and tell the parser it's okay.
            yacc.errok()
    
    def get_token():
        global last_token
        last_token = lexer.token()
        return last_token
    
    def parse_query(input, debug=0):
        lexer.input(input)
        return parser.parse(input, tokenfunc=get_token, debug=0)
    

    【讨论】:

      【解决方案3】:

      由于没有人有答案,所以我很难找到解决方法,这是一个使用错误恢复和重新启动()的丑陋黑客。

      def start_subquery(lexer, pos):
          lexer.code_start = lexer.lexpos        # Record the starting position
          lexer.level = 1
          lexer.begin("subquery") 
          lexer.lexpos = pos
      
      def end_subquery(lexer):
          value = lexer.lexdata[lexer.code_start:lexer.lexpos-1]
          lexer.lineno += value.count('\n')
          lexer.begin('INITIAL')
          return value
      
      @lex.TOKEN(r"\(")
      def t_subquery_SUBQST(t):
          lexer.level += 1
      
      @lex.TOKEN(r"\)")
      def t_subquery_SUBQEN(t):
          lexer.level -= 1
          if lexer.level == 0:
              t.type = "SUBQUERY"
              t.value = end_subquery(lexer)
              return t
      
      @lex.TOKEN(r".")
      def t_subquery_anychar(t):
          pass
      
      # NOTE: Due to the nature of the ugly workaround, the CPAR gets dropped, which
      # makes it look like there is an imbalance.
      def p_value_subquery(p):
          """
           value : OPAR SUBQUERY
          """
          p[0] = "( " + p[2] + " )"
      
      subquery_retry_pos = None
      
      def p_error(p):
          global subquery_retry_pos
          if p is None:
              print >> sys.stderr, "ERROR: unexpected end of query"
          elif p.type == 'SELECT' and parser.symstack[-1].type == 'OPAR':
              lexer.input(lexer.lexdata)
              subquery_retry_pos = parser.symstack[-1].lexpos
              yacc.restart()
          else:
              print >> sys.stderr, "ERROR: Skipping unrecognized token", p.type, "("+ \
                      p.value+") at line:", p.lineno, "and column:", find_column(p.lexer.lexdata, p)
              # Just discard the token and tell the parser it's okay.
              yacc.errok()
      
      def get_token():
          global subquery_retry_pos
          token = lexer.token()
          if token and token.lexpos == subquery_retry_pos:
              start_subquery(lexer, lexer.lexpos)
              subquery_retry_pos = None
          return token
      
      def parse_query(input, debug=0):
          lexer.input(inp)
          result = parser.parse(inp, tokenfunc=get_token, debug=0)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-29
        • 1970-01-01
        • 2013-01-31
        • 1970-01-01
        • 1970-01-01
        • 2017-03-03
        相关资源
        最近更新 更多