Я пишу маленький интерпретатор для простого ОСНОВНОГО как язык как осуществление на микроконтроллере AVR в C использование avr-gcc набора инструментальных средств. Однако я задаюсь вопросом, существуют ли какие-либо инструменты с открытым исходным кодом там, которые могли бы помочь мне пишущий лексический анализатор и синтаксический анализатор.
Если я записал бы это для работы моего поля Linux, я мог бы использовать гибкий провод/бизона. Теперь, когда я ограничил меня 8-разрядной платформой, я должен сделать все это вручную, или нет?
Я реализовал синтаксический анализатор простого командного языка, предназначенный для ATmega328p . Этот чип имеет 32 КБ ПЗУ и только 2 КБ ОЗУ. ОЗУ, безусловно, является более важным ограничением - если вы еще не привязаны к конкретному чипу, выберите тот, у которого как можно больше ОЗУ. Это сделает вашу жизнь намного проще.
Сначала я подумал об использовании flex / bison. Я отказался от этой опции по двум основным причинам:
Отказавшись от Flex & Bison, я стал искать другие инструменты-генераторы. Вот некоторые из них, которые я рассмотрел:
Вы также можете взглянуть на сравнение Википедии .
В конце концов, я написал и лексер, и парсер вручную.
Для синтаксического анализа я использовал синтаксический анализатор с рекурсивным спуском.Я думаю, что Ира Бакстер уже проделала адекватную работу по освещению этой темы, и в Интернете есть множество руководств.
Для своего лексера я написал регулярные выражения для всех своих терминалов, построил диаграмму эквивалентного конечного автомата и реализовал его как одну гигантскую функцию, используя goto
для перехода между состояниями. Это было утомительно, но результат оказался отличным. Кроме того, goto
- отличный инструмент для реализации конечных автоматов - все ваши состояния могут иметь четкие метки рядом с соответствующим кодом, нет никаких дополнительных затрат на вызов функции или переменную состояния, и это примерно как как можно быстрее. У C действительно нет лучшей конструкции для создания статических конечных автоматов.
Есть над чем подумать: на самом деле лексеры - это просто специализация парсеров. Самая большая разница в том, что обычных грамматик обычно достаточно для лексического анализа, тогда как в большинстве языков программирования есть (в основном) контекстно-свободные грамматики. Так что на самом деле ничто не мешает вам реализовать лексер в качестве парсера рекурсивного спуска или использовать генератор синтаксического анализатора для написания лексера. Просто обычно это не так удобно, как использование более специализированного инструмента.
.
относится к классу. < span class = «one »/>
можно выбрать с помощью .one
.
#
относится к идентификатору. < span id = «one »/>
можно выбрать с помощью # one
.
Вы должны использовать классы, когда может быть больше одного из данного элемента, и идентификаторы, когда вы знаете, что будет только один. # navigation-bar
будет использовать идентификатор, так как в макете будет только одна панель навигации, но .navigation-link
будет использовать имя класса, поскольку у вас будет несколько ссылок навигации. (Лучше было бы использовать # navigation-bar a: link
, чтобы получить навигационные ссылки, но вы получите мою точку зрения.)
Точка .
- селектор класса , хеш/фунт/октоторп #
выбирает по идентификатору :
<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 для встроенной цели.
GCC может выполнять кросс-компиляцию для различных платформ, но вы запускаете flex и bison на той платформе, на которой вы запускаете компилятор. Они просто выплевывают C-код, который затем собирает компилятор. Протестируйте его, чтобы увидеть, насколько велик получившийся исполняемый файл. Обратите внимание, что у них есть библиотеки времени выполнения (libfl.a
и т.д.), которые вам также придется кросс-компилировать под вашу цель.
Если вам нужен простой способ кодирования синтаксических анализаторов или у вас мало места, вам следует вручную написать синтаксический анализатор с рекурсивным спуском; по сути, это парсеры 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» выше; просто выполняйте арифметические операции вместо построения узлов дерева. Вот вычислитель выражений, выполненный таким образом .