我将字符串作为解析器规则而不是词法分析器,因为字符串可能包含带有表达式的转义符,例如"The variable is \(variable)"
。
string
: '"' character* '"'
;
character
: escapeSequence
| .
;
escapeSequence
: '\(' expression ')'
;
IDENTIFIER
: [a-zA-Z][a-zA-Z0-9]*
;
WHITESPACE
: [ \r\t,] -> skip
;
这不起作用,因为.
匹配任何令牌而不是任何字符,因此将匹配许多标识符并且空白将被完全忽略。
如何解析可以在其中包含表达式的字符串?
看看Swift和Javascript的解析器,这两种语言都支持这样的事情,我不知道它们是如何工作的。据我所知,他们只是输出一个字符串,例如“我的字符串中包含(变量)”,而实际上并不能将变量解析为它自己的东西。
答案 0 :(得分:2)
可以通过使用词法模式来解决此问题,方法是在字符串内部使用一个模式,在字符串外部使用一个(或多个)模式。看到外部的"
会切换到内部模式,看到\(
或"
会切换回外部。唯一复杂的部分是在外部看到)
:有时应该切换回内部(因为它对应于\(
),有时则不应该(当它对应于{普通(
)。
实现此目标的最基本方法如下:
词法分析器:
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);
};
(我遗漏的部分与以前的版本相同)。
这可以工作,并且会为不匹配的)
产生正常的语法错误。但是,它包含动作,因此不再与语言无关,这仅是一个问题,如果您打算使用多种语言的语法(并且取决于语言,您甚至可能会很幸运,并且代码可能在所有语言中均有效)。您的目标语言)。
如果要避免操作,最后一种选择是采用三种模式:一种用于任何字符串之外的代码,一种用于字符串内部,而另一种用于\()
内部。第三个几乎与外部的相同,除了在看到括号时它将推动并弹出模式,而外部的则不会。为了使两种模式都产生相同类型的令牌,第三种模式中的规则将全部调用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中找到。