我可以做些什么来避免需要回溯这个语法吗?

时间:2014-02-08 19:22:12

标签: parsing grammar interpreter yacc ply

我正在尝试为编程语言实现一个解释器,最后遇到了我需要回溯的情况,但我的解析器生成器(ply,用Python编写的lex& yacc克隆)不允许这样做 这是涉及的规则:

'var_access_start : super'
'var_access_start : NAME'
'var_access_name : DOT NAME'
'var_access_idx : OPSQR expression CLSQR'
'''callargs : callargs COMMA expression
            | expression
            | '''
'var_access_metcall : DOT NAME LPAREN callargs RPAREN'
'''var_access_token : var_access_name
                    | var_access_idx
                    | var_access_metcall'''
'''var_access_tokens : var_access_tokens var_access_token
                     | var_access_token'''
'''fornew_var_access_tokens : var_access_tokens var_access_name
                            | var_access_tokens var_access_idx
                            | var_access_name
                            | var_access_idx'''
'type_varref : var_access_start fornew_var_access_tokens'
'hard_varref : var_access_start var_access_tokens'
'easy_varref : var_access_start'
'varref : easy_varref'
'varref : hard_varref'
'typereference : NAME'
'typereference : type_varref'
'''expression : new typereference LPAREN callargs RPAREN'''

'var_decl_empty : NAME'
'var_decl_value : NAME EQUALS expression'
'''var_decl : var_decl_empty
            | var_decl_value'''
'''var_decls : var_decls COMMA var_decl
             | var_decl'''
    'statement : var var_decls SEMIC'

表单

的语句出错
var x = new SomeGuy.SomeOtherGuy();

其中SomeGuy.SomeOtherGuy将是存储类型的有效变量(类型是第一类对象) - 并且该类型具有不带参数的构造函数

解析该表达式时会发生什么,解析器构造一个 var_access_start = SomeGuy var_access_metcall =。 SomeOtherGuy() 然后找到一个分号并以错误状态结束 - 我显然希望解析器回溯,并尝试构造一个表达式= new typereference(SomeGuy .SomeOtherGuy)LPAREN empty_list RPAREN然后事情会起作用,因为;可以匹配var语句的语法

但是,考虑到PLY不支持回溯,并且我在解析器生成器方面没有足够的经验来实际实现它 - 我可以对语法进行任何改变来解决这个问题吗?

我考虑过使用 - >代替 。作为“方法调用”操作符,但我宁愿不改变语言只是为了安抚解析器。 此外,我有方法作为“变量引用”的形式,所以你可以这样做 myObject.someMethod()。aChildOfTheResult [0] .doSomeOtherThing(1,2,3).helloWorld() 但如果语法可以重做以达到同样的效果,那对我来说也是有用的

谢谢!

1 个答案:

答案 0 :(得分:2)

我认为您的语言包含的内容除了您在摘录中包含的表达外。我还假设newsupervar实际上是终端。

以下只是一个粗略的概述。为了便于阅读,我使用带引号文字的bison语法,但我认为转换时不会有任何问题。

您说“类型是第一类值”但您的语法明确排除了使用方法调用来返回类型。事实上,它似乎也排除了一个方法调用返回一个函数,但这似乎很奇怪,因为它意味着方法不是一等值,即使类型是。所以我通过允许表达式来简化语法:

 new foo.returns_method_which_returns_type()()()

重新添加限制很容易,但这使得说明难以理解。

基本思想是避免强迫解析器做出过早的决定;遇到new时,只能区分来自前瞻令牌的方法调用和构造函数调用。所以我们需要确保在这一点上使用相同的减少量,这意味着当遇到开括号时,我们仍然必须保留这两种可能性。

primary: NAME
       | "super"
       ;

postfixed: primary
         | postfixed '.' NAME
         | postfixed '[' expression ']'
         | postfixed '(' call_args ')'            /* PRODUCTION 1 */
         ;

expression: postfixed
          | "new" postfixed '(' call_args ')'     /* PRODUCTION 2 */
       /* | other stuff not relevant here */
          ;

 /* Your callargs allows (,,,3). This one doesn't */
call_args : /* EMPTY */
          | expression_list
          ;
expression_list: expression
               | expression_list ',' expression
               ;

/* Another slightly simplified production */
var_decl: NAME
        | NAME '=' expression
        ;

var_decl_list: var_decl
             | var_decl_list ',' var_decl
             ;

statement: "var" var_decl_list ';'
      /* | other stuff not relevant here */
         ;

现在,看看PRODUCTION 1PRODUCTION 2,它们非常相似。 (用评论标记。)这些基本上是你寻求回溯的模糊性。但是,在这个语法中,没有问题,因为一旦遇到newPRODUCTION 2的减少只能在前瞻标记是或<时执行kbd>; ,而PRODUCTION 1只能用前瞻标记执行 [

(语法用野牛测试,只是为了确保没有冲突。)