You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何区分类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: '&quot;';
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 &quot;, \, and NewLineCharacter
    : ~["\\\u000D\u000A\u0085\u2028\u2029]
    ;

fragment SimpleEscapeSequence
    : '\\\'' | '\\&quot;' | '\\\\' | '\\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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 08:59:53