如何区分类JS语言中的无引号文本与属性访问?
正在为供应商提供的、存在语法缺陷的类JS语言构建解析器,无法修改其语法。语句简化后有三种基本形式:
- 属性赋值/比较:
obj.prop1 = obj.prop2 - 带引号文本(单/双引号均可):
obj.prop1 = "quoted text" obj.prop1 = 'quoted text' - 无引号文本(可包含多词、空格、标点,但不含圆点):
obj.prop1 = text obj.prop1 = multiple words and punctuation $1,000?#-, etc
前两种场景易处理,但第三种无引号文本与第一种属性访问极易混淆,且该模式会在子表达式中重复(如数组过滤写法 obj.arrayProperty[property = value])。尝试过语义谓词、词法分析器模式、组合方案及两次解析,均未找到可行解。
以下是精简后的词法分析器和解析器代码:
Lexer 代码
lexer grammar CondensedLexer; AssignmentOrComparisonOperator: ('<' | '<=' | '=' | '!=' | '>' | '>=') -> pushMode(RHS); AdditiveOperator: '+' | '-'; Identifier : IdentifierStartCharacter IdentifierPartCharacter* ; IntegerLiteral: Sign? DecimalDigitCharacter DecimalDigitCharacter*; Dot: '.'; fragment DecimalDigit: '0'..'9'; fragment Sign: '+' | '-' ; fragment LetterCharacter // Category Letter, all subcategories; category Number, subcategory letter. : [\p{L}\p{Nl}] ; fragment DecimalDigitCharacter // Category Number, subcategory decimal digit. : [\p{Nd}] ; fragment IdentifierStartCharacter : LetterCharacter | UnderscoreCharacter ; fragment IdentifierPartCharacter : LetterCharacter | DecimalDigitCharacter ; fragment UnderscoreCharacter : '_' // underscore | '\\u005' [fF] // Unicode escape sequence for underscore ; /* * these rules are important because they allow for insignificant white space to the left of the operator, * while retaining white space to the right of the operator. I'm sure there are better ways to do this, * and I'm happy to see them, but that is not my focus at the moment */ IWS: WS -> skip; // Insignificant white space WS: [ \t\r\n]; // significant white space NEWLINE: '\r'? '\n'; // RHS = right hand side - this mode allows for unquoted strings, // single quoted strings, quoted strings, and, member accesses mode RHS; fragment DoubleQuote: '"'; fragment SingleQuote: '\''; StringLiteral : QuotedString | UnquotedString; QuotedString : DoubleQuotedString | SingleQuotedString ; fragment DoubleQuotedString: DoubleQuote RegularStringLiteralCharacter* DoubleQuote; fragment SingleQuotedString: SingleQuote RegularStringLiteralCharacter* SingleQuote; fragment RegularStringLiteralCharacter : SingleRegularStringLiteralCharacter | SimpleEscapeSequence ; fragment SingleRegularStringLiteralCharacter // anything but ", \, and NewLineCharacter : ~["\\\u000D\u000A\u0085\u2028\u2029] ; fragment SimpleEscapeSequence : '\\\'' | '\\"' | '\\\\' | '\\0' | '\\a' | '\\b' | '\\f' | '\\n' | '\\r' | '\\t' | '\\v' ; //Word: [A-Za-z_] [a-zA-Z0-9!@#$%^&*()[\]\\/ \t,|{}<>?`~]+; UnquotedString: ~('.')+? EOF;
Parser 代码
parser grammar CondensedParser; options { tokenVocab=CondensedLexer; } statement : assignmentOrComparison EOF; assignmentOrComparison : memberAccess IWS* AssignmentOrComparisonOperator IWS* restOfLine; memberAccess : memberAccess Dot Identifier | Identifier | IntegerLiteral // e.g. member.0 ; restOfLine : memberAccess | StringLiteral ;
1. 调整词法优先级,优先识别属性访问结构
当前词法规则中,UnquotedString会优先匹配非点字符,导致带点的属性访问被拆分为多个token。修改RHS模式规则,让属性访问结构的优先级高于无引号文本:
修改后的Lexer RHS模式片段:
mode RHS; // 优先匹配带点的属性访问结构 MemberAccessRHS : (Identifier | IntegerLiteral) (Dot (Identifier | IntegerLiteral))+ ; // 带引号字符串规则保留 QuotedString : DoubleQuotedString | SingleQuotedString ; // 无引号文本:排除属性访问结构,不含圆点,匹配到行尾 UnquotedString : ~[.\r\n] (~[\r\n])* ; // 复用原有片段规则...
同时移除原StringLiteral规则,将MemberAccessRHS、QuotedString、UnquotedString作为独立token处理。
2. 解析器结合语义谓词处理歧义
对于单个标识符(既可能是属性访问,也可能是无引号文本),在解析器中通过语义谓词补充判断逻辑:
修改后的Parser规则:
restOfLine : { // 判定为属性访问的条件: // 1. 当前是MemberAccessRHS token // 2. 当前是Identifier且后续紧跟Dot (_input.LT(1).getType() == CondensedLexer.MemberAccessRHS) || (_input.LT(1).getType() == CondensedLexer.Identifier && _input.LT(2) != null && _input.LT(2).getType() == CondensedLexer.Dot) }? memberAccess | QuotedString | UnquotedString ;
3. 适配子表达式(数组过滤场景)
针对obj.arrayProperty[property = value]这类嵌套结构,在Lexer中添加子表达式模式:
主模式新增规则:
LBracket: '[' -> pushMode(SubExpr);
新增SubExpr模式:
mode SubExpr; AssignmentOrComparisonOperatorSub: ('<' | '<=' | '=' | '!=' | '>' | '>=') -> pushMode(RHS); DotSub: '.'; IdentifierSub: IdentifierStartCharacter IdentifierPartCharacter*; IntegerLiteralSub: Sign? DecimalDigitCharacter DecimalDigitCharacter*; RBracket: ']' -> popMode; IWSSub: WS -> skip; WSSub: [ \t\r\n]; // 复用主模式的片段规则
解析器中扩展memberAccess规则:
memberAccess : memberAccess Dot Identifier | memberAccess LBracket assignmentOrComparison RBracket // 处理数组过滤逻辑 | Identifier | IntegerLiteral ;
4. 歧义场景的兜底处理
如果单个标识符(如text)同时符合属性访问和无引号文本的规则,语法层面无法完全区分,需交由后续语义分析阶段处理:
- 解析器产出包含两种可能性的AST节点
- 外部逻辑根据上下文(如是否存在对应属性、语句所处的场景)判定最终类型
如果能提前获取所有合法属性的列表,也可以在解析器中集成符号表,通过语义谓词实时检查标识符是否为合法属性,实现解析阶段的精准区分。
内容的提问来源于stack exchange,提问作者Chris Bristol

