Если вам нужен простой способ кодирования парсеров или у вас мало места, вам следует вручную написать парсер с рекурсивным спуском; по сути, это парсеры LL (1). Это особенно эффективно для таких «простых» языков, как Basic. (Я сделал несколько таких еще в 70-х!). Хорошая новость в том, что они не содержат библиотечного кода; просто то, что пишешь.
Их довольно легко закодировать, если у вас уже есть грамматика. Во-первых, вы должны избавиться от леворекурсивных правил (например, X = XY). Обычно это довольно легко сделать, поэтому я оставлю это как упражнение. (Для правил формирования списков этого делать не обязательно; см. Обсуждение ниже).
Тогда, если у вас есть правило 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" выше; просто выполняйте арифметические операции вместо построения узлов дерева. Вот такой вот оценщик выражений .