【问题标题】:Parse string antlr解析字符串 antlr
【发布时间】:2018-11-27 17:18:13
【问题描述】:

我将字符串作为解析器规则而不是词法分析器,因为字符串可能包含带有表达式的转义符,例如 "The variable is \(variable)"

string
 : '"' character* '"'
 ;

character
 : escapeSequence
 | .
 ;

escapeSequence
 : '\(' expression ')'
 ;

IDENTIFIER
 : [a-zA-Z][a-zA-Z0-9]*
 ;

WHITESPACE
 : [ \r\t,] -> skip
 ;

这不起作用,因为. 匹配任何标记而不是任何字符,因此将匹配许多标识符并且空白将被完全忽略。

如何解析可以包含表达式的字符串?

查看 Swift 和 Javascript 的解析器,这两种语言都支持这样的东西,我不知道它们是如何工作的。据我所知,他们只是输出一个字符串,例如“我的带有(变量)的字符串”,而实际上无法将变量解析为它自己的东西。

【问题讨论】:

  • 查看词法分析器模式。
  • @sepp2k 我不确定他们会如何提供帮助,如果字符串中有一个字符串并且其中有一个括号,除非我遗漏了什么,否则需要将整个解析器写入标记器
  • 我根本不会使用词法分析器或解析器来进行字符串插值。将字符串解析为一个实体,然后在后处理步骤中对插值进行搜索和替换。 IMO 这比发明复杂的插值语法规则要好得多。 旁注: 在解析器中解析字符串将不起作用,因为您将允许引号和内容之间存在空格,而这些空格不会出现在输出中。
  • @MikeLischke 假设在\() 之间允许任意表达式,事情就没有那么简单了。您要么必须在 \( 之后再次调用解析器,要么在后处理中重复 很多 解析器的工作。

标签: antlr antlr4


【解决方案1】:

这个问题可以使用词法模式来解决,方法是将一种模式用于字符串的内部,而将一种(或多种)模式用于外部。在外面看到" 会切换到内部模式,看到\(" 会切换回外面。唯一复杂的部分是在外面看到):有时它应该切换回内部(因为它对应于\(),有时它不应该(当它对应于一个普通的( )。

实现这一点的最基本方法是这样的:

词法分析器:

lexer grammar StringLexer;

IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]* ;
DQUOTE: '"' -> pushMode(IN_STRING);
LPAR: '(' -> pushMode(DEFAULT_MODE);
RPAR: ')' -> popMode;

mode IN_STRING;

TEXT: ~[\\"]+ ;

BACKSLASH_PAREN: '\\(' -> pushMode(DEFAULT_MODE);

ESCAPE_SEQUENCE: '\\' . ;

DQUOTE_IN_STRING: '"' -> type(DQUOTE), popMode;

解析器:

parser grammar StringParser;

options {
    tokenVocab = 'StringLexer';
}

start: exp EOF ;

exp : '(' exp ')'
    | IDENTIFIER
    | DQUOTE stringContents* DQUOTE
    ;

stringContents : TEXT
               | ESCAPE_SEQUENCE
               | '\\(' exp ')'
               ;

这里我们每次看到(\( 时都会推送默认模式,每次看到) 时都会弹出模式。这样,只有在堆栈顶部的模式是字符串模式时,它才会回到字符串内部,只有在自上一个 \( 之后没有任何未关闭的 ( 时才会出现这种情况。

这种方法有效,但缺点是不匹配的) 将导致空堆栈异常而不是正常的语法错误,因为我们在空堆栈上调用popMode


为避免这种情况,我们可以添加一个成员来跟踪我们在括号内的嵌套深度,并且在嵌套级别为 0 时(即如果堆栈为空)不会弹出堆栈:

@members {
    int nesting = 0;
}

LPAR: '(' {
    nesting++;
    pushMode(DEFAULT_MODE);
};
RPAR: ')' {
    if (nesting > 0) {
        nesting--;
        popMode();
    }
};

mode IN_STRING;

BACKSLASH_PAREN: '\\(' {
    nesting++;
    pushMode(DEFAULT_MODE);
};

(我省略的部分与上一版本相同)。

这对不匹配的)s 有效并产生正常的语法错误。但是,它包含操作,因此不再与语言无关,如果您打算使用多种语言的语法,这只是一个问题(并且取决于语言,您甚至可能很幸运,代码可能在所有语言中都有效)您的目标语言)。


如果您想避免操作,最后一种选择是使用三种模式:一种用于任何字符串之外的代码,一种用于字符串内部,另一种用于\() 内部。第三个将与外部几乎相同,除了它会在看到括号时推送和弹出模式,而外部不会。为了使两种模式产生相同类型的令牌,第三种模式中的规则都会调用type()。这将如下所示:

lexer grammar StringLexer;

IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]* ;
DQUOTE: '"' -> pushMode(IN_STRING);
LPAR: '(';
RPAR: ')';

mode IN_STRING;

TEXT: ~[\\"]+ ;

BACKSLASH_PAREN: '\\(' -> pushMode(EMBEDDED);

ESCAPE_SEQUENCE: '\\' . ;

DQUOTE_IN_STRING: '"' -> type(DQUOTE), popMode;

mode EMBEDDED;

E_IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]* -> type(IDENTIFIER);
E_DQUOTE: '"' -> pushMode(IN_STRING), type(DQUOTE);
E_LPAR: '(' -> type(LPAR), pushMode(EMBEDDED);
E_RPAR: ')' -> type(RPAR), popMode;

请注意,我们现在不能再在解析器语法中使用字符串文字,因为当使用相同的字符串文字定义多个词法分析器规则时,不能使用字符串文字。所以现在我们必须在解析器中使用LPAR 而不是'(' 等等(出于同样的原因,我们已经不得不为DQUOTE 这样做了)。

由于这个版本涉及大量重复(尤其是随着标记数量的增加)并且阻止在解析器语法中使用字符串文字,所以我通常更喜欢带有动作的版本。


还可以在on GitHub 找到所有三个替代方案的完整代码。

【讨论】:

  • 看起来这可能有效,如果在字符串内部推送默认模式,也可以跳过嵌入的单独模式
  • @pfg 问题是输入是否类似于)。然后) 的规则将弹出空堆栈。当然,该代码无论如何都是无效的,但通常您需要正确的语法错误而不是空堆栈异常。我个人更喜欢带有动作的版本,因为它避免了第三种模式,但也避免了例外。
猜你喜欢
  • 2011-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多