Правило ANTLR для использования фиксированного количества символов

Я пытаюсь написать грамматику 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 */
    ;

person FGM    schedule 24.10.2010    source источник


Ответы (1)


Поскольку ввод типа 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";]
person Bart Kiers    schedule 25.10.2010