我正在尝试使用柠檬编写一个简单的解析器,用于类似javascript的语言。我无法解决冲突错误,我怀疑这是一个无法解决的问题。
冲突在以下语法之间:
Content-Encoding: gzip
和
{x = 10;}
第一个是包含赋值语句的语句块,第二个是定义对象的表达式语句。
解析它们的语法会导致冲突。最小代码如下:
{x:10};
输出文件显示以下内容:
rMod ::= rStmt.
rStmt ::= rStmtList RCURLY. {leaveScope();}
rStmtList ::= rStmtList rStmt.
rStmtList ::= LCURLY. {enterScope();}
rStmt ::= rExpr SEMI.
rExpr ::= rObj.
rObj ::= LCURLY rObjItemList RCURLY.
rObjItemList ::= rObjItemList COMMA rObjItem.
rObjItemList ::= rObjItem.
rObjItem ::= ID COLON rExpr.
rExpr ::= ID.
rExpr ::= NUM.
我将非常感激地接受任何有关如何解决此问题的建议。感谢。
答案 0 :(得分:1)
问题的核心是你想在启动语句块的大括号之后执行enterScope()
。但是,如果括号后跟两个标记VAR
和:
,则它会启动一个对象字面值,而不是一个块。所以不知道是否在没有双令牌前瞻的情况下执行enterScope
动作是不可能的,而且柠檬不会产生LR(2)语法。在这种程度上,你是正确的,问题是无法解决的。但当然有解决方案。
从任何角度来看,最糟糕的解决方案(可读性,复杂性,可验证性)可能是使用通常的LR(2)→LR(1)转换创建LR(1)语法,这将允许您调用{{1在明确表示已输入范围的位置处的操作。这意味着将减少延迟一个令牌。这反过来意味着将enterScope();
划分为两个不相交的非终端:那些expr
可以以expr
开头,而那些不可以。{1}}。对于那些可以以VAR
开头的expr
,您还需要提供一种机制,它基本上允许您将VAR
和VAR
的其余部分粘合在一起;在表达式的情况下,这特别难看(但仍然可能)。目标是能够写:
expr
在这种特殊情况下,另一种丑陋但可能不那么丑陋的替代方法是识别变量名称后跟冒号作为单个词法标记(block(A) ::= blockPrefix(B) RCURLY . { closeScope(); A = B;}
blockPrefix(A) ::= lcurlyOpen exprNotStartingVAR(E) . { A = E; }
blockPrefix(A) ::= lcurlyVAR(V) restOfExprStartingVar(R) . { A = makeExpr(V, R); }
blockPrefix(A) ::= blockPrefix(B) SEMI expr(E) . { A = appendExpr(B, E); }
lcurlyOpen ::= LCURLY . { openScope(); }
lcurlyVAR(A) ::= LCURLY VAR(V) . { openScope(); A = V; }
)。虽然这使词法分析器变得复杂(特别是因为你需要识别变量名和冒号之间出现空格甚至注释的结构),但它使语法更加简单。通过该更改,没有冲突,因为对象文字必须以VAR_COLON
开头,而expr只能以VAR_COLON
(或其他无关的标记)开头。
更简单的解决方案是不要尝试创建范围 inherited attribute。如果我们综合考虑范围,那么问题或多或少会消失:
VAR
该语法没有冲突,但它可能从根本上改变了处理范围的方式,因为它需要在块完成时将变量名称作为范围,而不是在解析进行时以内联方式进行。
但是,我认为对于大多数语言(包括Javascript)来说,在块结束时进行作用域实际上更方便,或者甚至作为AST之后的解析后步行。与C不同,Javascript允许在第一次提及后声明局部变量。甚至可以在声明之前使用本地函数。 (这与Python略有不同,其中函数声明是可执行的赋值,但是作用域规则是相似的。)
作为另一个例子,C ++允许类成员在类的声明中的任何地方声明,即使该成员已在另一个类成员函数中被提及。
还有很多其他的例子。这些范围规则通常允许程序员通过允许样式选项(例如将成员变量定义放在C ++中的类定义的末尾)而在C中是不可能的。