如果我在antlr4词法分析器中有一个ONELINE_STRING片段规则,用于在一行上标识一个简单的引用字符串,那么如何在词法分析器中创建一个更通用的STRING规则来连接相邻的ONELINE_STRING(即,仅分开)通过空格和/或评论),只要它们各自从不同的行开始?
即,
"foo" "bar"
将被解析为两个STRING令牌," foo"然后是" bar"
,同时:
"foo"
"bar"
将被视为一个STRING令牌:" foobar"
为了澄清:我的想法是,虽然我通常希望解析器能够将相邻字符串识别为单独的,并且解析器忽略空白和注释,但我想使用如果最后一个非空格的想法一行上的子标记是一个字符串,并且下一行中不是所有空格的第一个子标记也是一个字符串,那么单独的字符串应该连接成一个长字符串,作为指定可能非常长的字符串的方法无需将整个事情放在一条线上。如果我想要连接所有相邻的字符串子标记,这是非常简单的,因为它们在C中...但是出于我的目的,我只想在字符串子标记在不同的行上开始时发生连接。对于可能使用字符串的解析器中的任何规则,此连接应该是不可见的。这就是为什么我认为将规则放在词法分析器而不是解析器中可能会更好,但我并不完全反对在解析器中执行此操作,以及所有可能引用STRING的解析规则只要需要字符串,token就会引用解析器字符串规则。
的Sample1:
"desc" "this sample will parse as two strings.
Sample3(注意,'输出'是该语言中的关键字):
output "this is a very long line that I've explicitly made so that it does not "
"easily fit on just one line, so it gets split up into separate ones for "
"ease of reading, but the parser should see it all as one long string. "
"This example will parse as if the output command had been followed by "
"only a single string, even though it is composed of multiple string "
"fragments, all of which should be invisible to the parser.%n";
这两个示例都应该被解析器接受为有效。前者是声明的一个例子,而后者是语言中命令性声明的一个例子。
附录:
我原本以为这需要在词法分析器中完成,因为虽然解析器应该忽略换行符,就像所有其他空格一样,多行字符串实际上对我没想到的换行符的存在很敏感解析器可以感知到它。
但是,我一直认为可以将ONELINE_STRING作为词法分析器规则,并且有一个通用的字符串'解析器规则检测相邻的ONELINE_STRINGS,使用字符串之间的谓词来检测下一个ONELINE_STRING令牌是否在与前一个不同的行上开始,如果是,它应该无形地连接它们,以便它的文本与具有的字符串无法区分已在一行中全部指定。然而,我不确定如何实施这一点的后勤工作。
好的,我有。
我需要在解析器中使用字符串识别器,正如你们有人建议的那样。诀窍是在词法分析器中使用词法分析器模式。
所以在Lexer文件中我有这个:
BEGIN_STRING : '"' -> pushMode(StringMode);
mode StringMode;
END_STRING: '"'-> popMode;
STRING_LITERAL_TEXT : ~[\r\n%"];
STRING_LITERAL_ESCAPE_QUOTE : '%"' { setText("\""); };
STRING_LITERAL_ESCAPE_PERCENT: '%%' { setText("%"); };
STRING_LITERAL_ESCAPE_NEWLINE : '%n'{ setText("\n"); };
UNTERMINATED_STRING: { _input.LA(1) == '\n' || _input.LA(1) == '\r' || _input.LA(1) == EOF}? -> popMode;
在解析器文件中我有:
string returns [String text] locals [int line] : a=stringLiteral { $line = $a.line; $text=$a.text;}
({_input.LT(1)!=null && _input.LT(1).getLine()>$line}?
a=stringLiteral { $line = $a.line; $text+=$a.text; })*
;
stringLiteral returns [int line, String text]: BEGIN_STRING {$text = "";}
(a=(STRING_LITERAL_TEXT
| STRING_LITERAL_ESCAPE_NEWLINE
| STRING_LITERAL_ESCAPE_QUOTE
| STRING_LITERAL_ESCAPE_PERCENT
) {$text+=$a.text;} )*
stringEnd { $line = $BEGIN_STRING.line; }
;
stringEnd: END_STRING #string_finish
| UNTERMINATED_STRING #string_hang
;
字符串规则因此连接相邻的字符串文字,只要它们位于不同的行上即可。 stringEnd规则需要一个事件处理程序,用于何时未正确终止字符串文字,以便解析器可以报告语法错误,但字符串将被视为已正确关闭。
答案 0 :(得分:1)
最简单的方法是在解析器中执行此操作。我认为没有必要在词法分析器中完成这一点。
multiString : singleString +;
singleString : ONELINE_STRING;
ONELINE_STRING: ...; // no fragment!
WS : ... -> skip;
Comment : ... -> skip;
答案 1 :(得分:1)
如前所述,(IMO)更好的方法是在解析器中处理这个问题。但这是一种在词法分析器中处理它的方法:
STRING
: SINGLE_STRING ( LINE_CONTINUATION SINGLE_STRING )*
;
HIDDEN
: ( SPACE | LINE_BREAK | COMMENT ) -> channel(HIDDEN)
;
fragment SINGLE_STRING
: '"' ~'"'* '"'
;
fragment LINE_CONTINUATION
: ( SPACE | COMMENT )* LINE_BREAK ( SPACE | COMMENT )*
;
fragment SPACE
: [ \t]
;
fragment LINE_BREAK
: [\r\n]
| '\r\n'
;
fragment COMMENT
: '//' ~[\r\n]+
;
对输入进行标记:
"a" "b" "c" "d" "e" "f"
会创建以下5个令牌:
"a"
"b"
"c"\n"d"
"e"
"f"
但是,如果令牌包含注释:
"c" // comment "d"
然后您需要在稍后阶段自己从令牌中删除此"// comment"
。词法分析器将无法将此子字符串放在不同的通道上,或skip
。