Если вам нужен простой способ кодирования синтаксических анализаторов или у вас мало места, вам следует вручную написать синтаксический анализатор с рекурсивным спуском; по сути, это LL (1) парсеры. Это особенно эффективно для таких «простых» языков, как Basic. (Я сделал несколько таких еще в 70-х!). Хорошая новость в том, что они не содержат библиотечного кода; просто то, что пишешь.
Их довольно легко закодировать, если у вас уже есть грамматика. Во-первых, вам нужно избавиться от леворекурсивных правил (например, X = X Y). Обычно это довольно легко сделать, поэтому я оставлю это как упражнение. (Для правил формирования списков этого делать не обязательно; см. Обсуждение ниже).
Тогда, если у вас есть правило BNF в форме:
X = A B C ;
создать подпрограмму для каждого элемента в правиле (X, A, B, C), которая возвращает логическое значение «Я видел соответствующую синтаксическую конструкцию». Для X код:
subroutine X()
if ~(A()) return false;
if ~(B()) { error(); return false; }
if ~(C()) { error(); return false; }
// insert semantic action here: generate code, do the work, ....
return true;
end X;
Аналогично для A, B, C.
Если токен является терминалом, напишите код, который проверяет входной поток на наличие строки символов, составляющей терминал. Например, для числа проверьте, что входной поток содержит цифры, и продвиньте курсор входного потока мимо цифр. Это особенно просто, если вы анализируете буфер (для BASIC вы, как правило, получаете по одной строке за раз), просто продвигая или не продвигая указатель сканирования буфера. Этот код, по сути, является лексической частью анализатора.
Если ваше правило BNF рекурсивное ... не волнуйтесь. Просто закодируйте рекурсивный вызов. Это обрабатывает такие правила грамматики, как:
T = '(' T ')' ;
Это можно закодировать как:
subroutine T()
if ~(left_paren()) return false;
if ~(T()) { error(); return false; }
if ~(right_paren()) { error(); return false; }
// insert semantic action here: generate code, do the work, ....
return true;
end T;
Если у вас есть правило BNF с альтернативой:
P = Q | R ;
затем введите P с альтернативными вариантами:
subroutine P()
if ~(Q())
{if ~(R()) return false;
return true;
}
return true;
end P;
Иногда встречаются правила формирования списков. Они имеют тенденцию быть леворекурсивными, и этот случай легко обрабатывается. Основная идея состоит в том, чтобы использовать итерацию, а не рекурсию, и это позволяет избежать бесконечной рекурсии, которую вы могли бы получить, делая это «очевидным» способом. Пример:
L = A | L A ;
Вы можете закодировать это, используя итерацию, как:
subroutine L()
if ~(A()) then return false;
while (A()) do { /* loop */ }
return true;
end L;
Таким образом можно закодировать несколько сотен грамматических правил за день или два. Есть еще детали, которые нужно заполнить, но основ здесь должно быть более чем достаточно.
Если вам действительно не хватает места, вы можете создать виртуальную машину, реализующую эти идеи. Это то, что я делал еще в 70-х, когда можно было получить 8K 16-битных слов.
Если вы не хотите кодировать это вручную, вы можете автоматизировать его с помощью метакомпилятора (Meta II), который производит по сути то же самое. Это умопомрачительное техническое развлечение, которое действительно избавляет от всей работы, даже если речь идет о сложных грамматиках.
Август 2014:
Я получаю много запросов, «как построить AST с помощью парсера». Подробнее об этом, который по сути развивает этот ответ, см. Мой другой ответ SO https://stackoverflow.com/a/25106688/120163 < / а>
Июль 2015:
Многие хотят написать простой оценщик выражений. Вы можете сделать это, выполнив те же действия, которые предлагает ссылка "Построитель AST" выше; просто выполняйте арифметические операции вместо построения узлов дерева. Вот вычислитель выражений, выполненный таким образом.
person
Ira Baxter
schedule
25.02.2010