Есть ли альтернатива для flex / bison, которая может использоваться в 8-битных встроенных системах?

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

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

6 ответов


я реализовал парсер для простого командного языка, предназначенного для схема ATmega328P. Этот чип имеет 32k ROM и только 2K RAM. ОЗУ, безусловно, является более важным ограничением - если вы еще не привязаны к определенному чипу, выберите один с как можно большим количеством ОЗУ. Это сделает вашу жизнь намного проще.

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

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

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

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

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

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

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

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


если вы хотите простой способ кодирования парсеров, или вы плотно на пространстве, вы должны передать код рекурсивного спуска парсера; это по существу 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 битных слов было то, что вы могли получить.


если вы не хотите кодировать это вручную, вы можете автоматизировать его с помощью метакомпилера (мета II), что производит по существу то же самое. Это умопомрачительная техническая забава и действительно занимает всю работу, даже для больших грамматик.

августа 2014 года:

Я получаю много запросов на "как построить AST с помощью парсера". Подробнее об этом, который по существу разрабатывает этот ответ, см. Мой другой ответ SO https://stackoverflow.com/a/25106688/120163

июля 2015 года:

есть много людей, которые хотят написать простой оценщик выражений. Вы можете сделать это, делая те же самые вещи, которые предлагает ссылка "AST builder" выше; просто сделайте арифметику вместо построения узлов дерева. Вот!--54-->оценщик выражений, выполненный таким образом.


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


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


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


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