Якщо ви хочете простий спосіб кодування парсерів або у вас недостатньо місця, вам слід вручну закодувати парсер рекурсивного спуску; це, по суті, парсери 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" вище; просто виконайте арифметику замість того, щоб будувати вузли дерев. Ось оцінювач виразів, зроблений таким чином .