具有歧义的上下文无关语法 - 使用 Ply lex/yacc 生成器的小型编译器

Context Free Grammar with ambiguity - small compiler using Ply lex/yacc generator

我正在使用 Ply lex/yaac 生成器为 Python、编译器 class 编写一个小型编译器。现在我们正在进入语义分析阶段,很快就会进入代码生成阶段。但是我的语法还没有完成,因为我无法为函数调用编写规则,因为到目前为止我尝试的一切都以 shift/reduce 冲突结束,这使得规则被忽略。

有如下规则:function_call : ID function_call_args

和:

   function_call_args : expression
                      | function_call

还有:

program : function_definition program
              | class_definition program
              | function_call
              | empty

当我添加规则 expression : function_call 时,结果是模棱两可的,并产生了 shift/reduce 冲突。我希望语法中的 function_call 可以是一个表达式,然后可以编写一些代码,例如 a + b + f(2) * g(a/x) - h() .

无论我添加空作为 function_call_args 或 logical_expression 的替代,它也会变得模棱两可。或者我是否只是告诉我 return 声明 return 和 function_call 作为替代也减少了冲突。后者并不重要,如果 function_call 可以成为表达式的替代品,那么即使 return 是函数调用,也能够在函数的任何位置调用函数就足够了。

下面是词法分析器、解析器和完整语法的完整代码:

完整语法

Rule 0     S' -> program
Rule 1     program -> function_definition program
Rule 2     program -> class_definition program
Rule 3     program -> function_call
Rule 4     program -> empty
Rule 5     function_call -> ID function_call_args
Rule 6     function_call_args -> expression
Rule 7     function_call_args -> function_call
Rule 8     class_definition -> DEFINE CLASS ID LPAREN RPAREN LBRACE program RBRACE
Rule 9     function_definition -> DEFINE data_type ID LPAREN function_definition_args RPAREN block
Rule 10    function_definition_args -> var_list
Rule 11    function_definition_args -> empty
Rule 12    var_list -> var_declaration COMMA var_list
Rule 13    var_list -> var_declaration
Rule 14    var_declaration -> data_type ID
Rule 15    data_type -> VOID
Rule 16    data_type -> INT
Rule 17    data_type -> FLOAT
Rule 18    data_type -> STRING
Rule 19    data_type -> CHAR
Rule 20    block -> LBRACE statements RBRACE
Rule 21    statements -> statement statements
Rule 22    statements -> empty
Rule 23    statement -> matched_statement
Rule 24    statement -> unmatched_statement
Rule 25    matched_statement -> IF LPAREN logical_expression RPAREN matched_statement ELSE matched_statement
Rule 26    matched_statement -> WHILE LPAREN logical_expression RPAREN matched_statement
Rule 27    matched_statement -> FOR LPAREN var_declaration SEMI_COLON logical_expression SEMI_COLON attribution RPAREN matched_statement
Rule 28    matched_statement -> return_statement
Rule 29    matched_statement -> compound_statement
Rule 30    matched_statement -> simple_statement
Rule 31    matched_statement -> block
Rule 32    unmatched_statement -> IF LPAREN logical_expression RPAREN matched_statement
Rule 33    unmatched_statement -> IF LPAREN logical_expression RPAREN matched_statement ELSE unmatched_statement
Rule 34    logical_expression -> logical_term OR logical_term
Rule 35    logical_expression -> logical_term
Rule 36    logical_term -> logical_factor AND logical_factor
Rule 37    logical_term -> logical_factor
Rule 38    logical_factor -> boolean_statement
Rule 39    logical_factor -> NOT logical_factor
Rule 40    logical_factor -> LPAREN logical_expression RPAREN
Rule 41    boolean_statement -> value comparison_op value
Rule 42    boolean_statement -> value
Rule 43    value -> ID
Rule 44    value -> number
Rule 45    number -> int
Rule 46    number -> float
Rule 47    int -> INTCONST
Rule 48    float -> FLOATCONST
Rule 49    comparison_op -> LESS_THAN
Rule 50    comparison_op -> LESS_EQUAL
Rule 51    comparison_op -> GREATER_THAN
Rule 52    comparison_op -> GREATER_EQUAL
Rule 53    comparison_op -> EQUAL
Rule 54    comparison_op -> NOT_EQUAL
Rule 55    compound_statement -> var_list
Rule 56    compound_statement -> attribution
Rule 57    simple_statement -> expression
Rule 58    attribution -> var_declaration EQUALS expression
Rule 59    attribution -> var_declaration EQUALS string
Rule 60    attribution -> var_declaration EQUALS char
Rule 61    attribution -> ID EQUALS expression
Rule 62    attribution -> ID EQUALS STRINGCONST
Rule 63    attribution -> ID EQUALS CHARCONST
Rule 64    string -> STRINGCONST
Rule 65    char -> CHARCONST
Rule 66    expression -> expression PLUS term
Rule 67    expression -> expression MINUS term
Rule 68    expression -> term
Rule 69    term -> term TIMES factor
Rule 70    term -> term DIVIDE factor
Rule 71    term -> term MOD factor
Rule 72    term -> factor
Rule 73    factor -> number
Rule 74    factor -> ID
Rule 75    factor -> factor_expr
Rule 76    factor_expr -> LPAREN expression RPAREN
Rule 77    uminus_expression -> MINUS LPAREN expression RPAREN
Rule 78    empty -> <empty>
Rule 79    return_statement -> RETURN expression
Rule 80    function_definition -> DEFINE data_type ID LPAREN function_definition_args RPAREN error

词法分析器:

import ply.lex as lex
import ply.yacc as yacc


# Reserved words
reserved = {
    'if' : 'IF',
    'else' : 'ELSE',
    'while' : 'WHILE',
    'for' : 'FOR',
    'switch' : 'SWITCH',
    'case' : 'CASE',
    'class' : 'CLASS',
    'define' : 'DEFINE',
    'int' : 'INT',
    'float' : 'FLOAT',
    'char' : 'CHAR',
    'string' : 'STRING',
    'void' : 'VOID',
    'equal' : 'EQUAL',
    'and' : 'AND',
    'or' : 'OR',
    'not' : 'NOT',
    'do' : 'DO',
    'return' : 'RETURN',
}

# The tokens declaration is made here.
tokens = [

    # Literals (identifier, integer constant, float constant, string constant,
    # char const)
    # TODO add constants' Types
    'ID',
    #'NUMBER',

    # Operators +,-,*,/,%
    'PLUS',
    'MINUS',
    'TIMES',
    'DIVIDE',
    'MOD',

    # Logical Operators
    'LESS_THAN',
    'LESS_EQUAL',
    'GREATER_THAN',
    'GREATER_EQUAL',
    'NOT_EQUAL',

    # Delimeters such as (),{},[],:
    'LPAREN',
    'RPAREN',
    'LBRACE',
    'RBRACE',
    'COLON',
    'COMMA',
    'SEMI_COLON',

    #Assignment Operators
    'EQUALS',

    # Integer literal
    'INTCONST',
    # Floating literal
    'FLOATCONST',
    # String literal
    'STRINGCONST',
    # Character constant 'c' or L'c'
    'CHARCONST'
] 

tokens +=  list(reserved.values())

# Regular expression rules for simple tokens
#t_NUMBER  = r'\d+'

# Operators
t_PLUS    = r'\+'
t_MINUS   = r'-'
t_TIMES   = r'\*'
t_DIVIDE  = r'/'
t_MOD     = r'\%'

# Logical Operators
t_LESS_THAN     = r'<'
t_LESS_EQUAL    = r'<=' 
t_GREATER_THAN  = r'>'
t_GREATER_EQUAL = r'>='
t_NOT_EQUAL     = r'!='

# Delimiters
t_LPAREN     = r'\('
t_RPAREN     = r'\)'
t_COLON      = r'\:'
t_LBRACE     = r'\{'
t_RBRACE     = r'\}'
t_COMMA      = r','
t_SEMI_COLON = R';'

# Assignment
t_EQUALS  = r'='


# Integer literal
t_INTCONST = r'\d+'

# Floating literal
t_FLOATCONST = r'\d+[.]\d+'

# String literal
t_STRINGCONST = r'\"([^\\n]|(\.))*?\"'

# Character constant 'c' or L'c'
t_CHARCONST = r'\'([^\\n]|(\.))*?\''


# Rule for identificator
def t_ID(t):
    r'[a-zA-Z_][a-zA-Z0-9_]*'
    t.type = reserved.get(t.value,'ID')    # Check for reserved words
    return t

# Define a rule so we can track line numbers
def t_newline(t):
    r'\n+'
    t.lexer.lineno += len(t.value)

# Compute column.
#     input is the input text string
#     token is a token instance
def find_column(input, token):
    line_start = input.rfind('\n', 0, token.lexpos) + 1
    return (token.lexpos - line_start) + 1

# A string containing ignored characters (spaces and tabs)
t_ignore  = ' \t'

# Error handling rule
def t_error(t):
    print("Illegal character '%s'" % t.value[0])
    t.lexer.skip(1)

# One line comments Python alike
def t_comment(t):
    r"[ ]*3[^\n]*"  # 3 is '#'
    pass

# Build the lexer
#lexer = lex.lex()

lex.lex()

if __name__ == '__main__':
    lex.runmain()

解析器:

import sys
sys.path.append("..")
import ply.yacc as yacc
from lexical_analyzer import tokens
from ast import *
from semantical_analyser.semantical_analyzer import SymbolTable, Var, Scope
from semantical_analyser.stack import Stack

symbolTable = SymbolTable()
stack = Stack()
stack.push(Scope())


precedence = (
   ('nonassoc', 'LESS_THAN', 'GREATER_THAN'),  # Nonassociative operators
   ('nonassoc', 'LESS_EQUAL', 'GREATER_EQUAL'),  # Nonassociative operators
    ('left', 'PLUS', 'MINUS'),
    ('left', 'TIMES', 'DIVIDE', 'MOD'),
    ('right', 'UMINUS'),            # Unary minus operator
)

# Initial Rule
def p_program(p):
   '''program : function_definition program
              | class_definition program
              | function_call
              | empty'''
   if len(p) == 3:
      p[0] = Program(p[1], p[2])
   else:
      p[0] = p[1]
      stack.push(Scope())
   pass


#def p_function_call_expr(p):
#   '''expression : function_call'''

def p_function_call(p):
   '''function_call : ID LPAREN function_call_args RPAREN'''
   #p[0] = Node("FUNCTION_CALL", [p[1], p[2], p[3]])
   p[0] = FunctionCall(p[1], p[2])
   pass

def p_function_call_args(p):
    '''function_call_args : expression
                          | function_call'''
    #p[0] = Node("FUNCTION_CALL_ARGS", [p[1]])
    p[0] = p[1]
    pass

def p_class_definition(p):
   ''' class_definition : DEFINE CLASS ID LPAREN RPAREN LBRACE program RBRACE'''
   #p[0] = ClassDefinition("CLASS_DEFINITION", [p[1], p[2], p[3], p[4], p[5], p[6]], p[7], p[8]])
   p[0] = ClassDefinition(p[3], p[7])
   pass

# Function definition
def p_function_definition(p):
    '''function_definition : DEFINE data_type ID LPAREN function_definition_args RPAREN block'''
    #p[0] = Node("FUNCTION_DEFINITION", [p[1], p[2], p[3], p[4], p[5], p[6], p[7]])
    p[0] = FunctionDefinition(p[2], p[3], p[5], p[7])
    pass

# Arguments that goes inside a function definition
def p_function_definition_args(p):
    '''function_definition_args : var_list
                                | empty'''
    #p[0] = Node("FUNCTION_DEFINITION_ARGS", [p[1]])
    p[0] = p[1]
    pass

# list of declaration of variables or a single variable
def p_var_list(p):
    '''var_list : var_declaration COMMA var_list
                | var_declaration'''
    if len(p) >= 3:
        p[0] = VarList(p[1], p[3])
    else:
        p[0] = p[1]
    pass

# declaration of var
def p_var_declaration(p):
   'var_declaration : data_type ID'
   p[0] = VarDeclaration(p[1], p[2])
   var = Var("ID", p[2], p[1])
   symbolTable.addVar(var)
   topOfStack = stack.peek()
   topOfStack.addObj(var)
   pass

# data types for variables 
def p_data_type(p):
    '''data_type : VOID 
                 | INT 
                 | FLOAT 
                 | STRING 
                 | CHAR'''
    p[0] = DataType(p[1])
    pass

# block - a block is enclosed by braces {stmt}
def p_block(p):
   'block : LBRACE statements RBRACE'
   #p[0] = Node("BLOCK", [p[1], p[2], p[3]])
   p[0] = Block(p[2])
   #stack.push(Scope())
   pass

def p_statements(p):
   '''statements : statement statements
                 | empty'''
   if len(p) >= 3:
      p[0] = Statements(p[1], p[2])
   else:
      p[0] = Node("EMPTY", [])
   pass

# statement is either a compound statement or a simple statement
def p_statement(p):
   '''statement : matched_statement
                | unmatched_statement'''
   #p[0] = Node("STATEMENT", [p[1]])
   p[0] = p[1]
   pass

# Matched Statement - This is used to deal with If ambiguity
def p_matched_statement(p):
   ''' matched_statement : IF LPAREN logical_expression RPAREN matched_statement ELSE matched_statement 
                         | WHILE LPAREN logical_expression RPAREN matched_statement
                         | FOR LPAREN var_declaration SEMI_COLON logical_expression SEMI_COLON attribution RPAREN matched_statement 
                         | return_statement
                         | compound_statement
                         | simple_statement
                         | block'''
   if len(p) == 9:
      #p[0] = Node("FOR-CLAUSE", [p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]])
      p[0] = ForStatement(p[3], p[5], p[7], p[9])
   elif len(p) == 8:
      #p[0] = Node("IF-ELSE-CLAUSE", [p[1], p[2], p[3], p[4], p[5], p[6], p[7]])
      p[0] = IfElseStatement(p[3], p[5], p[7])
   elif len(p) == 6:
      #p[0] = Node("WHILE-CLAUSE", [p[1], p[2], p[3], p[4], p[5]])
      p[0] = WhileStatement(p[3], p[5])
   else:
      #p[0] = Node("MATCHED_STATEMENT", [p[1]])
      p[0] = p[1]
   pass

# Unmateched statement - This is used to deal with If ambiguity 
def p_unmatched_statement(p):
   ''' unmatched_statement : IF LPAREN logical_expression RPAREN matched_statement 
                           | IF LPAREN logical_expression RPAREN matched_statement ELSE unmatched_statement '''
   if len(p) == 7:
     #p[0] = Node("IF-ELSE-CLAUSE", [p[1], p[2], p[3], p[4], p[5], p[6]])
      p[0] = IfElseStatement(p[3], p[5], p[6])
   else:
     # p[0] = Node("IF-CLAUSE", [p[1], p[2], p[3], p[4], p[5]])
      p[0] = IfStatement(p[3], p[5])
      #stack.push(Scope())
      #currentScope = stack.peek()
      #newScope = Scope()
      #currentScope.addObj(newScope)
   pass

# Logical expression OR
def p_logical_expression_or(p):
   'logical_expression : logical_term OR logical_term'
   p[0] = Node("LOGICAL_EXPRESSION", [p[1], p[2]], p[3])
   pass

# Logical expression term
def p_logical_expression_term(p):
   'logical_expression : logical_term'
   p[0] = Node("LOGICAL_EXPRESSION_TERM", [p[1]])
   pass

# Logical expression AND
def p_logical_term_and(p):
   'logical_term : logical_factor AND logical_factor'
   #p[0] = Node("LOGICAL_EXPRESSION_AND", [p[1]])
   p[0] = BinaryOp(p[1], p[2], p[3])
   pass

# Logical expression factor
def p_logical_term_factor(p):
   'logical_term : logical_factor'
   #p[0] = Node("LOGICAL_EXPRESSION_FACTOR", [p[1]])
   p[0] = p[1]
   pass

# Logical expression NOT, boolean or another logical expression enclosed by parens
def p_logical_factor(p):
   '''logical_factor : boolean_statement
                     | NOT logical_factor
                     | LPAREN logical_expression RPAREN'''
   if len(p) == 4:
      #p[0] = Node("LOGICAL_EXPRESSION_FACTOR", [p[1], p[2], p[3]])
      p[0] = BinaryOp(p[1], p[2], p[3])
   elif len(p) == 3:
      #p[0] = Node("LOGICAL_EXPRESSION_FACTOR", [p[1], p[2]])
      p[0] = NotExpression(p[2])
   else:
      #p[0] = Node("LOGICAL_EXPRESSION_FACTOR", [p[1]])
      p[0] = p[1]
   pass

# boolean_statement
def p_boolean_statement(p):
   '''boolean_statement : value comparison_op value
                        | value'''
   if len(p) == 4:
      #p[0] = Node("BOOLEAN_STATEMENT", [p[1], p[3]], p[2])
      p[0] = BinaryOp(p[1], p[2], p[3])
   else:
      #p[0] = Node("BOOLEAN_STATEMENT", [p[1]])
      p[0] = p[1]
   pass


def p_value(p):
   ''' value : ID
             | number'''
   #p[0] = Node("ID_OR_NUMBER", [p[1]])
   p[0] = p[1]
   pass

def p_number(p):
   '''number : int
             | float'''
   p[0] = Number(p[1])
   pass

def p_int(p):
   'int : INTCONST'
   p[0] = Const('int', p[1])
   pass

def p_float(p):
   'float : FLOATCONST'
   p[0] = Const('float', p[1])
   pass

def p_comparison_op(p):
   '''comparison_op : LESS_THAN
                    | LESS_EQUAL
                    | GREATER_THAN
                    | GREATER_EQUAL
                    | EQUAL
                    | NOT_EQUAL'''
   #p[0] = Node("COMPARISON_OP", [p[1]])
   p[0] = p[1]
   pass

# Compound statement is a while or for or do while or switch case
def p_compound_statment(p):
   '''compound_statement : var_list
                         | attribution'''
   #p[0] = Node("COMPOUND_STATEMENT", [p[1]])
   p[0] = p[1]
   pass

# A simple statement is a declaration, or 
def p_simple_statement(p):
   '''simple_statement : expression'''
   #p[0] = Node("SIMPLE_STATEMENT", [p[1]])
   p[0] = p[1]
   pass

# Production for atrribution
def p_atrribution(p):
    '''attribution : var_declaration EQUALS expression 
                   | var_declaration EQUALS string
                   | var_declaration EQUALS char
                   | ID EQUALS expression
                   | ID EQUALS STRINGCONST
                   | ID EQUALS CHARCONST'''
    #p[0] = Node("ATTRIBUTION", [p[1], p[3]], p[2])
    #p[0] = BinaryOp(p[1], p[2], p[3])
    p[0] = Attribution(p[1], p[3])
    pass

def p_string(p):
   'string : STRINGCONST'
   p[0] = Const('string', p[1])
   pass

def p_char(p):
   'char : CHARCONST'
   p[0] = Const('char', p[1])
   pass

# Gramatical rules for expressions
def p_expression_plus(p):
    'expression : expression PLUS term'
    #p[0] = Node("binop_plus", [p[1],p[3]], p[2])
    p[0] = BinaryOp(p[1], p[2], p[3])
    pass

def p_expression_minus(p):
    'expression : expression MINUS term'
    #p[0] = Node("BINOP_MINUS", [p[1], p[3]], p[2])
    p[0] = BinaryOp(p[1], p[2], p[3])
    pass

def p_expression_term(p):
   'expression : term'
   #p[0] = Node("BINOP_TERM", [p[1]])
   p[0] = p[1]
   pass

def p_term_times(p):
   'term : term TIMES factor'
   #p[0] = Node("BINOP_TERM_TIMES", [p[1], p[3]], p[2])
   p[0] = BinaryOp(p[1], p[2], p[3])
   pass

def p_term_div(p):
   'term : term DIVIDE factor'
   #p[0] = Node("BINOP_DIVIDE", [p[1], p[3]], p[2])
   p[0] = BinaryOp(p[1], p[2], p[3])
   pass

def p_term_mod(p):
   'term : term MOD factor'
   #p[0] = Node("BINOP_MOD", [p[1], p[3]], p[2])
   p[0] = BinaryOp(p[1], p[2], p[3])
   pass

def p_term_factor(p):
   'term : factor'
   #p[0] = Node("BINOP_FACTOR", [p[1]])
   p[0] = p[1]
   pass

def p_factor_num(p):
   '''factor : number 
             | ID 
             | factor_expr'''
   #p[0] = Node("BINOP_FACTOR", [p[1]])
   p[0] = p[1]
   pass

def p_factor_expr(p):
   'factor_expr : LPAREN expression RPAREN'
   #p[0] = Node("BINOP_EXPR", [p[1], p[3]], p[2])
   p[0] = p[1]
   pass

def p_uminus_expr(p):
   'uminus_expression : MINUS LPAREN expression RPAREN %prec UMINUS'
   pass

# The empty production
def p_empty(p):
   'empty :'
   p[0] = None #Node("EMPTY", [])
   pass

# Return statement1
def p_return_statement(p):
   'return_statement : RETURN expression'
   #p[0] = Node("RETURN_STATEMENT", [p[1], p[2]])
   p[0] = ReturnStatement(p[2])
   pass


# Error rule for syntax errors
def p_error(p):
   if p:
      print("Syntax error at token", p.type)
      print("Syntax error at '%s'" % p.value)
      print("line : '%s'" % p.lineno)
      print("column: '%s'" % p.lexpos)
      #print("Syntax error in input!")
      #parser.errok()
   else:
      print("Syntax error at EOF")



def p_function_definition_error(p):
   '''function_definition : DEFINE data_type ID LPAREN function_definition_args RPAREN error'''
   print("Syntax error in function statement at '%s'" % p)
   #print('Line' + str(p.linespan(1)))

parser = yacc.yacc()
from read_file_into_buffer import readFileIntoBuffer
data = readFileIntoBuffer('test.fpl')
ast = parser.parse(data, tracking=True)
print(ast)

假设您的语言是类 C 语言,f 2 不是有效的函数调用;必须写成f(2)。但是你的语法说:

Rule 5     function_call -> ID function_call_args
Rule 6     function_call_args -> expression

这当然允许 f 2(即 ID 后跟 expression)。另一方面,该语法不允许 f() 也不允许 f(1,2).

(第三个产生式 function_call_args -> function_call 是多余的,因为您有 function_call_args -> expressionexpression -> function_call。冗余产生式会造成解析冲突,所以我将其省略。)

您需要 function_callfunction_definition 非常相似,带有显式括号和可选的 list 表达式。