Есть ли альтернатива flex / bison, которую можно использовать в 8-битных встроенных системах? - PullRequest
78 голосов
/ 11 февраля 2010

Я пишу небольшой интерпретатор для простого BASIC-подобного языка в качестве упражнения на микроконтроллере AVR в C с использованием цепочки инструментов avr-gcc. Однако мне интересно, есть ли какие-нибудь инструменты с открытым исходным кодом, которые могли бы помочь мне написать лексер и парсер.

Если бы я написал это для запуска на моем Linux-компьютере, я мог бы использовать flex / bison. Теперь, когда я ограничился 8-битной платформой, я должен делать все вручную или нет?

Ответы [ 6 ]

194 голосов
/ 25 февраля 2010

Если вы хотите простой способ кодирования синтаксических анализаторов, или у вас мало места, вы должны вручную написать парсер рекурсивного спуска; по сути это LL (1) парсеры. Это особенно эффективно для языков, которые так же «просты», как и Basic. (Я сделал несколько из них еще в 70-х!). Хорошей новостью является то, что они не содержат библиотечного кода; только то, что ты пишешь.

Их довольно легко кодировать, если у вас уже есть грамматика. Во-первых, вы должны избавиться от левых рекурсивных правил (например, X = X Y). Это обычно довольно легко сделать, поэтому я оставляю это как упражнение. (Вы не должны делать это для правил формирования списка; см. обсуждение ниже).

Тогда, если у вас есть правило БНФ в форме:

 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 builder»; просто делайте арифметику вместо построения узлов дерева. Вот вычислитель выражений, сделанный таким образом .

53 голосов
/ 26 февраля 2010

Я реализовал парсер для простого командного языка, предназначенного для ATmega328p . Этот чип имеет 32 КБ ПЗУ и только 2 КБ ОЗУ. RAM, безусловно, является более важным ограничением - если вы еще не привязаны к конкретному чипу, выберите тот, у которого как можно больше памяти. Это сделает вашу жизнь намного проще.

Сначала я подумал об использовании flex / bison. Я отказался от этого варианта по двум основным причинам:

  • По умолчанию Flex & Bison зависят от некоторых стандартных библиотечных функций (особенно для ввода-вывода), которые недоступны или не работают одинаково в avr-libc. Я почти уверен, что есть поддерживаемые обходные пути, но это дополнительные усилия, которые вам необходимо принять во внимание.
  • AVR имеет Гарвардская архитектура . C не предназначен для учета этого, поэтому даже постоянные переменные загружаются в RAM по умолчанию . Вы должны использовать специальные макросы / функции для хранения и доступа к данным в flash и EEPROM . Flex & Bison создает несколько относительно больших справочных таблиц, и они довольно быстро израсходуют вашу оперативную память. Если я не ошибаюсь (что вполне возможно), вам придется отредактировать источник вывода, чтобы воспользоваться специальными интерфейсами Flash и EEPROM.

После отказа от Flex & Bison я отправился на поиски других инструментов генератора. Вот некоторые из них, которые я рассмотрел:

Возможно, вы захотите взглянуть на сравнение Википедии .

В конце концов, я закончил ручным кодированием лексера и парсера.

Для разбора я использовал парсер рекурсивного спуска. Я думаю, Ира Бакстер уже выполнила адекватную работу по освещению этой темы, и в Интернете есть множество учебных пособий.

Для моего лексера я написал регулярные выражения для всех своих терминалов, нарисовал эквивалентный конечный автомат и реализовал его как одну гигантскую функцию, используя goto для перехода между состояниями. Это было утомительно, но результаты работали отлично. Кроме того, goto является отличным инструментом для реализации конечных автоматов - все ваши состояния могут иметь четкие метки рядом с соответствующим кодом, нет вызова функции или служебной переменной состояния, и это примерно так быстро, как вы можете получить. C действительно не имеет лучшей конструкции для построения статических автоматов.

О чем подумать: лексеры - это на самом деле просто специализация парсеров. Самым большим отличием является то, что регулярных грамматик обычно достаточно для лексического анализа, тогда как большинство языков программирования имеют (в основном) контекстно-свободные грамматики. Так что на самом деле ничто не мешает вам реализовать лексер как парсер рекурсивного спуска или использовать генератор парсера для написания лексера. Обычно это не так удобно, как использование более специализированного инструмента.

11 голосов
/ 11 февраля 2010

Вы можете использовать flex / bison в Linux с его собственным gcc для генерации кода, который вы затем будете кросс-компилировать с вашим AVR gcc для встроенной цели.

2 голосов
/ 11 февраля 2010

GCC может выполнять кросс-компиляцию для различных платформ, но вы запускаете flex и bison на платформе, на которой вы работаете, компилятором. Они просто выкладывают C-код, который затем создает компилятор. Протестируйте его, чтобы увидеть, насколько велик конечный исполняемый файл. Обратите внимание, что у них есть библиотеки времени выполнения (libfl.a и т. Д.), Которые вам также придется кросс-компилировать для вашей цели.

0 голосов
/ 24 июня 2015

Попробуйте Boost :: Spirit. Это библиотека только для заголовков, в которую вы можете зайти и создать очень быстрый и чистый парсер полностью на C ++. Перегруженные операторы в C ++ используются вместо специального файла грамматики.

0 голосов
/ 08 августа 2012

Вместо того, чтобы заново изобретать колесо, взгляните на LUA: www.lua.org . Это интерпретирующий язык, предназначенный для встраивания в другое программное обеспечение и используемый в небольших системах, таких как встроенные системы. Встроенное процедурное дерево синтаксического синтаксического анализа, логика управления, математика и поддержка переменных - не нужно заново изобретать то, что тысячи других уже отладили и использовали И это расширяемое, то есть вы можете добавить к грамматике, добавив свои собственные функции языка Си.

...