Я пытаюсь создать интерпретатор Pascal с помощью ANTLR, и в настоящее время у меня возникают проблемы с обработкой циклов при обходе дерева AST. Например, цикл for анализируется как:
parametricLoop
: FOR IDENTIFIER ASSIGN start = integerExpression TO end = integerExpression DO
statement
-> ^( PARAMETRIC_LOOP IDENTIFIER $start $end statement )
;
(вариант с DOWNTO игнорируется). Каким образом я могу заставить ходока повторять выполнение цикла столько раз, сколько необходимо? Я знаю, что для этого я должен использовать input.Mark() и input.Rewind(). Но где именно их нужно поставить? Мой текущий неправильный вариант выглядит так (целевой язык C#):
parametricLoop
:
^(
PARAMETRIC_LOOP
IDENTIFIER
start = integerExpression
{
Variable parameter = Members.variable($IDENTIFIER.text);
parameter.value = $start.result;
}
end = integerExpression
{
int end_value = $end.result;
if ((int)parameter.value > end_value) goto EndLoop;
parametric_loop_start = input.Mark();
}
statement
{
parameter.value = (int)parameter.value + 1;
if ((int)parameter.value <= end_value)
input.Rewind(parametric_loop_start);
)
{
EndLoop: ;
}
;
(надеюсь все понятно). Условие повторения следует проверять перед первым выполнением оператора. Я пытался поиграть с размещением Mark и Rewind в разных блоках кода, включая @init и @after, и даже помещал завершающий goto в начало цикла, но каждый цикл либо повторялся один раз, либо выдавал исключения, такие как Unexpected token, например ':= ' (задание). Я понятия не имею, как заставить это работать правильно, и не могу найти ни одного рабочего примера. Может ли кто-нибудь предложить решение этой проблемы?
Ответ 1 · рейтинг 1 · Aasmund Eldhuset
Я не использовал ANTLR, но мне кажется, что вы пытаетесь выполнить программу во время ее парсинга, но парсеры на самом деле не для этого предназначены (простые арифметические выражения могут выполняться во время парсинга, но как у вас обнаружены, петли проблематичны). Я настоятельно рекомендую вам использовать синтаксический анализ только для создания AST. Таким образом, код синтаксического анализатора для parametricLoop должен создавать только узел дерева, представляющий цикл, с дочерними узлами, представляющими переменные, условия и тело. После этого в отдельном обычном классе C# (которому вы предоставляете AST, сгенерированный синтаксическим анализатором), вы выполняете код, обходя дерево каким-либо образом, а затем у вас есть полная свобода прыгать туда и обратно между узлами по порядку. для имитации выполнения цикла.
Ответ 2 · рейтинг 1 · Farid Derradji
Я работаю с ANTLR 3.4 и нашел решение, которое работает с классом CommonTreeNodeStream.
По сути, я отделил новые экземпляры моего анализатора дерева, который, в свою очередь, проанализировал все поддеревья. Мой пример кода определяет цикл while:
tree grammar Interpreter;
...
@members
{
...
private Interpreter (CommonTree node, Map<String, Integer> symbolTable)
{
this (new CommonTreeNodeStream (node));
...
}
...
}
...
stmt : ...
| ^(WHILE c=. s1=.) // ^(WHILE cond stmt)
{
for (;;)
{
Interpreter condition = new Interpreter (c, this.symbolTable);
boolean result = condition.cond ();
if (! result)
break;
Interpreter statement = new Interpreter (s1, this.symbolTable);
statement.stmt ();
}
}
...
cond returns [boolean result]
: ^(LT e1=expr e2=expr) {$result = ($e1.value < $e2.value);}
| ...
Ответ 3 · Community
Только что решил аналогичную проблему, несколько моментов:
Кажется, вам нужно использовать BufferedTreeNodeStream вместо CommonTreeNodeStream, CommonTreeNodeStream у меня никогда не работает (долго пытался выяснить)
Использование seek кажется мне более понятным
Вот мой код для команды списка, уверен, что ваш можно легко изменить на этот стиль:
list returns [Object r]
: ^(LIST ID
{int e_index = input.Index;}
exp=.
{int s_index = input.Index;}
statements=.
)
{
int next = input.Index;
input.Seek(e_index);
object list = expression();
foreach(object o in (IEnumerable<object>)list)
{
model[$ID.Text] = o;
input.Seek(s_index);
$r += optional_block().ToString();
}
input.Seek(next);
}