Как новичок, когда я изучал ANTLR4 из The Definitive ANTLR 4 Reference, я попытался запустить мою модифицированную версию упражнения из главы 7:
/**
* to parse properties file
* this example demonstrates using embedded actions in code
*/
grammar PropFile;
@header {
import java.util.Properties;
}
@members {
Properties props = new Properties();
}
file
:
{
System.out.println("Loading file...");
}
prop+
{
System.out.println("finished:\n"+props);
}
;
prop
: ID '=' STRING NEWLINE
{
props.setProperty($ID.getText(),$STRING.getText());//add one property
}
;
ID : [a-zA-Z]+ ;
STRING :(~[\r\n])+; //if use STRING : '"' .*? '"' everything is fine
NEWLINE : '\r'?'\n' ;
Поскольку свойства Java - это только пара ключей, я использую STRING
для сопоставления eveything, кроме NEWLINE
(я не хочу, чтобы он просто поддерживал строки в двойных кавычках). При выполнении следующего предложения я получил:
D:\Antlr\Ex\PropFile\Prop1>grun PropFile prop -tokens
driver=mysql
^Z
[@0,0:11='driver=mysql',<3>,1:0]
[@1,12:13='\r\n',<4>,1:12]
[@2,14:13='<EOF>',<-1>,2:14]
line 1:0 mismatched input 'driver=mysql' expecting ID
Когда я использую STRING: '"'.*? '"'
, Это работает.
Я хотел бы знать, где я был неправ, чтобы избежать подобных ошибок в будущем.
Пожалуйста, дайте мне предложение, спасибо!
Поскольку оба ID и STRING могут соответствовать входному тексту, начинающемуся с "driver", lexer будет выбирать максимально возможное совпадение, даже если правило идентификатора будет первым.
Итак, у вас есть несколько вариантов. Самое непосредственное - устранить двусмысленность между ID и STRING (как это работает ваша альтернатива), требуя, чтобы строка начиналась с знака равенства.
file : prop+ EOF ;
prop : ID STRING NEWLINE ;
ID : [a-zA-Z]+ ;
STRING : '=' (~[\r\n])+;
NEWLINE : '\r'?'\n' ;
Затем вы можете использовать действие, чтобы обрезать знак равенства из текста токена.
В качестве альтернативы вы можете использовать предикат для устранения неоднозначности правил.
file : prop+ EOF ;
prop : ID '=' STRING NEWLINE ;
ID : [a-zA-Z]+ ;
STRING : { isValue() }? (~[\r\n])+;
NEWLINE : '\r'?'\n' ;
где метод isValue смотрит назад на поток символов, чтобы убедиться, что он соответствует знаку равенства. Что-то вроде:
@members {
public boolean isValue() {
int offset = _tokenStartCharIndex;
for (int idx = offset-1; idx >=0; idx--) {
String s = _input.getText(Interval.of(idx, idx));
if (Character.isWhitespace(s.charAt(0))) {
continue;
} else if (s.charAt(0) == '=') {
return true;
} else {
break;
}
}
return false;
}
}