Я пытаюсь написать грамматику ANTLR для формата PHP serialize(), и все работает нормально, кроме строк. Проблема в том, что формат сериализованных строк:
s:6:"length";
С точки зрения регулярных выражений, такое правило, как s:(\d+):".{\1}";, описывало бы этот формат, если бы в подсчете "количества совпадений" были разрешены только обратные ссылки (но это не так).
Но я не могу найти способ выразить это ни для грамматики лексера, ни для синтаксического анализатора: вся идея состоит в том, чтобы количество прочитанных символов зависело от обратной ссылки, описывающей количество прочитанных символов, как в константах Фортрана Холлерита (т.е. 6HLength), не на разделителе строк.
Этот пример из грамматики ANTLR для Fortran, кажется, указывает путь, но Я не понимаю, как. Обратите внимание, что мой целевой язык - Python, в то время как большая часть документации и примеров предназначена для Java:
// numeral literal
ICON {int counter=0;} :
/* other alternatives */
// hollerith
'h' ({counter>0}? NOTNL {counter--;})* {counter==0}?
{
$setType(HOLLERITH);
String str = $getText;
str = str.replaceFirst("([0-9])+h", "");
$setText(str);
}
/* more alternatives */
;
Ответ 1 · рейтинг 5 · Bart Kiers
Поскольку ввод типа s:3:"a"b"; действителен, вы не можете определить токен String в своем лексере, если только первая и последняя двойные кавычки всегда не являются началом и концом вашей строки. Но я думаю, что это не так.
Итак, вам понадобится такое правило лексера:
SString
: 's:' Int ':"' ( . )* '";'
;
Другими словами: сопоставьте s:, затем значение integer, за которым следует :", затем один или несколько символов, которые могут быть любыми, заканчиваясь на ";. Но вам нужно сказать лексеру прекратить потребление, когда значение Int не будет достигнуто. Вы можете сделать это, смешав простой код в своей грамматике. Вы можете внедрить простой код, заключив его в { и }. Итак, сначала преобразуйте значение токена Int в целочисленную переменную с именем chars:
SString
: 's:' Int {chars = int($Int.text)} ':"' ( . )* '";'
;
Теперь вставьте некоторый код в цикл ( . )*, чтобы остановить его потребление, как только chars будет считаться до нуля:
SString
: 's:' Int {chars = int($Int.text)} ':"' ( {if chars == 0: break} . {chars = chars-1} )* '";'
;
и это все.
Небольшая демонстрационная грамматика:
grammar Test;
options {
language=Python;
}
parse
: (SString {print 'parsed: [\%s]' \% $SString.text})+ EOF
;
SString
: 's:' Int {chars = int($Int.text)} ':"' ( {if chars == 0: break} . {chars = chars-1} )* '";'
;
Int
: '0'..'9'+
;
(обратите внимание, что вам нужно избежать % внутри вашей грамматики!)
И тестовый скрипт:
import antlr3
from TestLexer import TestLexer
from TestParser import TestParser
input = 's:6:"length";s:1:""";s:0:"";s:3:"end";'
char_stream = antlr3.ANTLRStringStream(input)
lexer = TestLexer(char_stream)
tokens = antlr3.CommonTokenStream(lexer)
parser = TestParser(tokens)
parser.parse()
который производит следующий вывод:
parsed: [s:6:"length";]
parsed: [s:1:""";]
parsed: [s:0:"";]
parsed: [s:3:"end";]