Существует ли альтернатива для гибкого провода/бизона, который применим в 8-разрядных встроенных системах?

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

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

80
задан Lundin 10 September 2015 в 13:01
поделиться

4 ответа

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

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

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

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

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

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

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

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

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

56
ответ дан 24 November 2019 в 09:45
поделиться

. относится к классу. < span class = «one »/> можно выбрать с помощью .one .

# относится к идентификатору. < span id = «one »/> можно выбрать с помощью # one .

Вы должны использовать классы, когда может быть больше одного из данного элемента, и идентификаторы, когда вы знаете, что будет только один. # navigation-bar будет использовать идентификатор, так как в макете будет только одна панель навигации, но .navigation-link будет использовать имя класса, поскольку у вас будет несколько ссылок навигации. (Лучше было бы использовать # navigation-bar a: link , чтобы получить навигационные ссылки, но вы получите мою точку зрения.)

-121--1416154-

Точка . - селектор класса , хеш/фунт/октоторп # выбирает по идентификатору :

<style>
    .foo { ... }
    #bar { ... }
</style>
...
<p class='foo'>Foo</p>
<p id='bar' class='baz'>Bar</p>

идентификаторы должны быть уникальными по всему документу, классы не должны быть. Это основная разница. Есть некоторые вещи, которые следует отметить в отношении сценариев, но они, как правило, не представляют особого интереса при создании стилей.

Кроме того, элемент может принадлежать нескольким классам:

<p class="foo bar baz">

, и, как видно выше, классы и идентификаторы не являются взаимоисключающими.

-121--1416155-

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

11
ответ дан 24 November 2019 в 09:45
поделиться

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

2
ответ дан 24 November 2019 в 09:45
поделиться

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

Если токен является терминалом, напишите код, который проверяет входной поток на наличие строки символов, составляющей терминал. { {1}} Например, для числа проверьте, что входной поток содержит цифры, и переместите курсор входного потока мимо цифр. Это особенно просто, если вы выполняете синтаксический анализ буфера (для BASIC вы, как правило, получаете по одной строке за раз) , просто перемещая или не перемещая указатель сканирования буфера. {{1} } Этот код, по сути, является лексической частью анализатора.

Если ваше правило 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;

You таким образом может закодировать несколько сотен грамматических правил за день или два. Нужно заполнить больше деталей, но основ здесь должно быть более чем достаточно.

Если у вас действительно мало места, вы можете создать виртуальную машину, которая реализует эти идеи. Это то, что я делал еще в 70-х, когда можно было получить 8K 16-битных слов.


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

Август 2014 г .:

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

Июль 2015 г .:

Есть много людей, которые хотят написать простой оценщик выражений. Вы можете сделать это, выполнив те же действия, которые предлагает ссылка «Построитель AST» выше; просто выполняйте арифметические операции вместо построения узлов дерева. Вот вычислитель выражений, выполненный таким образом .

203
ответ дан 24 November 2019 в 09:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: