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

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

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


person Johan    schedule 11.02.2010    source источник
comment
Есть ли конкретный чип, который вы собираетесь использовать? Сколько у него ПЗУ / ОЗУ?   -  person Steve S    schedule 25.02.2010
comment
Обновите ссылку на @mre. Embedded.com удалил их URL-адреса. (embedded.com/design / prototyping-and-development / 4024523 /)   -  person pgvoorhees    schedule 12.02.2016
comment
Кажется, только стековые laguages ​​(четвертый и Ко) имеют шанс на 2 КБ ОЗУ с прошитым ядром   -  person Jacek Cz    schedule 22.04.2016


Ответы (6)


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

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

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

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

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

В конце концов, я написал и лексер, и парсер вручную.

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

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

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

person Steve S    schedule 26.02.2010

Если вам нужен простой способ кодирования синтаксических анализаторов или у вас мало места, вам следует вручную написать синтаксический анализатор с рекурсивным спуском; по сути, это 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
comment
Да, это не так уж и сложно для простого языка создать рекурсивный синтаксический анализатор спуска. Не забывайте оптимизировать хвостовые вызовы, когда это возможно - пространство в стеке имеет большое значение, когда у вас всего пара килобайт оперативной памяти. - person Steve S; 25.02.2010
comment
@Ira - возможно, вы захотите добавить что-нибудь о реализации лексера / сканера с нуля (поскольку OP спрашивал о гибкости). Это не так просто и элегантно сделать вручную, как парсер, но я думаю, что это заслуживает упоминания. - person Steve S; 25.02.2010
comment
@Steve: заметил, что сканирование терминала === часть лексера. - person Ira Baxter; 25.02.2010
comment
Все: да, вы можете сделать оптимизацию хвостового вызова. Это не имеет значения, если вы не ожидаете, что вложение в анализируемый код станет действительно глубоким; для строки кода BASIC довольно сложно найти выражения, глубина которых превышает 10 паратенсов, и вы всегда можете установить счетчик предельной глубины для загрузки. Это правда, что встроенные системы обычно имеют меньше места в стеке, так что хотя бы обратите внимание на свой выбор здесь. - person Ira Baxter; 25.02.2010
comment
Ах, я пролистал слишком быстро и пропустил это. Возможно, мне придется написать собственный ответ, описывающий другой способ его реализации. Но хороший ответ! - person Steve S; 25.02.2010
comment
@IraBaxter: 2012 год;) - person mpen; 17.03.2012
comment
@Mark: и вы можете кодировать парсеры вручную, если вы настаиваете (все еще имеет смысл, если они не сложные), или вы можете получить действительно мощные генераторы парсеров. Твой выбор. Смотрите мою биографию, если хотите упасть со скалы могущественного. - person Ira Baxter; 17.03.2012
comment
@Mark: и это может быть 2012 год, но технический документ 1965 года, на который я ссылаюсь, сейчас хорош, как и тогда, и довольно хорош, особенно если вы этого не знаете. - person Ira Baxter; 17.03.2012
comment
@IraBaxter: Я не имел в виду, что ваш ответ устарел, я указывал на то, что вы допустили опечатку. Вы написали ИЗМЕНИТЬ 16 МАРТА 2011. - person mpen; 17.03.2012
comment
@Mark, ах, хорошо, спасибо! Похоже, дата загадочным образом была назначена. Спасибо, Повелитель времени. - person Ira Baxter; 18.03.2012
comment
Спасибо за ваш ответ, это именно то, что я искал, ответ, отмеченный сверху, не подходит для быстрых простых языков, другие ваши ресурсы также чрезвычайно открывают и интересны! - person Krupip; 28.09.2017
comment
Как я могу обрабатывать пустые строки? - person Dante; 02.11.2017
comment
Пустой строкой, я думаю, вы говорите, что у вас есть такое грамматическое правило, как X - ›Y | эпсилон. В этом случае вы пишете подпрограмму для X, которая вызывает Y; если он находит Y, он возвращает успех. Если он не находит Y, все равно возвращает true.. - person Ira Baxter; 03.11.2017
comment
@IraBaxter, спасибо. Есть еще одна проблема. Для грамматики T = '(' T ')' | epsilon как я могу сообщить об ошибке синтаксиса для потоков токенов, таких как (())()? Грамматика останавливается и возвращает истину, когда потребляет 2-й ) и никогда не видит (). - person Dante; 06.11.2017
comment
@Dante: Ну, это должно остановиться после 2-го). В конце концов, это то, что говорит ваше грамматическое правило: у вас могут быть вложенные круглые скобки, в которых ничего нет, и ничего больше. (()) () не является допустимой строкой в ​​соответствии с вашим правилом грамматики. Итак, либо это неверный ввод, либо у вас есть другие правила грамматики, о которых вы нам не сказали. - person Ira Baxter; 07.11.2017
comment
@Dante: ... подождите, может быть, вы жалуетесь, что он принимает часть (()), но не жалуется на дополнительную ()? Это потому, что ваше целевое правило должно иметь вид GOAL - ›NONTERMINAL ‹EOF›, где EOF - это индикатор конца ввода. Вы можете изобрести специальный токен для EOF или использовать символ ASCII ‹CR› при чтении строк. На практике EOF не является токеном, это исчерпание входной строки, и у вас есть какой-то предикат, который может это проверить. Вы изменяете свое правило GOAL, чтобы проверять EOF, как только оно распознает все остальное, и жалуется, если не видит EOF. - person Ira Baxter; 07.11.2017
comment
Этот ответ наконец заставил меня написать собственный парсер LL (1). Тем не менее, книги Crafting Interpreters, главы с 4 по 6 действительно положили мне конец . - person jchook; 27.06.2019

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

person Paul R    schedule 11.02.2010

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

person ConcernedOfTunbridgeWells    schedule 11.02.2010
comment
Мне все еще нужно исследовать размер этих библиотек, и именно поэтому я задал этот вопрос в первую очередь. Я хочу что-то специально предназначенное для небольших микроконтроллеров. - person Johan; 11.02.2010

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

person Erik Aronesty    schedule 24.06.2015
comment
Проблема с вашим ответом в том, что он не осознает ограничение 8-битной платформы. Будет сложно получить цепочку инструментов, поддерживающую ускорение, и такую ​​крохотную платформу одновременно. - person Waslap; 24.04.2017

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

person Scott Hall    schedule 07.08.2012
comment
Каким бы маленьким ни был Lua, я почти уверен, что он все равно будет слишком большим. - person icktoofay; 03.08.2013