Чи існує альтернатива flex / bison, яка може бути використана у 8-розрядних вбудованих системах?


83

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

Якби я написав це для запуску на своєму Linux-боксі, я міг би використовувати flex / bison. Тепер, коли я обмежився 8-бітною платформою, я повинен робити це все вручну, чи ні?


1
Чи є конкретний чіп, який ви маєте намір використовувати? Скільки в ньому ПЗП / оперативної пам'яті?
Steve S

Оновіть за посиланням @mre. embedded.com перемістив їх URL-адреси в сміття. ( embedded.com/design/prototyping-and-development/4024523/… )
pgvoorhees

Здається, лише мови стека (вперед і Co) мають шанс на 2 КБ оперативної пам'яті, з проблисковим ядром
Jacek Cz

Відповіді:


59

Я застосував синтаксичний аналізатор для простої мови команд, націленої на ATmega328p . Цей чіп має 32k ROM і лише 2k RAM. Оперативна пам’ять, безумовно, є найважливішим обмеженням - якщо ви ще не прив’язані до певного чіпа, виберіть такий із якомога більшою кількістю оперативної пам'яті. Це значно полегшить вам життя.

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

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

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

Можливо, ви також захочете поглянути на порівняння Вікіпедії .

Зрештою, я закінчив ручним кодуванням як lexer, так і синтаксичного аналізатора.

Для синтаксичного аналізу я використав рекурсивний синтаксичний аналізатор. Я думаю, що Іра Бакстер вже виконала належну роботу з висвітлення цієї теми, і в Інтернеті є безліч навчальних посібників.

Для свого лексера я написав регулярні вирази для всіх своїх терміналів, склав схему еквівалентного автомата стану і реалізував його як одну гігантську функцію, використовуючи goto'для переходу між станами. Це було нудно, але результати працювали чудово. Окрім того, gotoце чудовий інструмент для реалізації автоматів - у всіх ваших штатах можуть бути чіткі мітки поруч із відповідним кодом, немає виклику функції або змінної стану, і це приблизно так швидко, як ви можете отримати. С справді не має кращої конструкції для побудови статичних автоматів.

Є над чим подумати: лексери насправді є лише спеціалізацією парсерів. Найбільша різниця полягає в тому, що звичайних граматик зазвичай достатньо для лексичного аналізу, тоді як більшість мов програмування мають (переважно) безконтекстні граматики. Отже, насправді ніщо не заважає вам застосувати лексер як парсер рекурсивного спуску або використовувати генератор синтаксичного аналізатора для написання лексера. Це, як правило, не так зручно, як використання більш спеціалізованого інструменту.


221

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

Якщо маркер є терміналом, напишіть код, який перевіряє вхідний потік на рядок символів, з яких складається термінал. Наприклад, для числа перевірте, чи вхідний потік містить цифри, і пересуньте курсор вхідного потоку за цифрами. Це особливо легко, якщо ви виконуєте синтаксичний аналіз буфера (для 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" вище; просто виконайте арифметику замість того, щоб будувати вузли дерев. Ось оцінювач виразів, зроблений таким чином .


2
Так, не надто складно вручну прокрутити рекурсивний парсер для простої мови. Не забувайте оптимізувати хвостові виклики, коли це можливо - простір у стеку має велике значення, коли у вас лише пара кілобайт оперативної пам'яті.
Steve S

2
Все: так, ви можете зробити оптимізацію хвостових викликів. Це не матиме значення, якщо ви не очікуєте, що вкладання у ваш проаналізований код стане дуже глибоким; для рядка коду BASIC досить важко знайти вирази набагато більше 10 паратентів, і ви завжди можете ввести кількість обмежень глибини для завантаження. Це правда, що вбудовані системи, як правило, мають менше місця в стеку, тому принаймні зверніть увагу на свій вибір тут.
Ira Baxter

2
@Mark: можливо, це буде 2012 рік, але технічний документ 1965 року, на який я посилаюся, зараз є хорошим, як і тоді, і досить непоганим, особливо якщо ви цього не знаєте.
Ira Baxter,

2
@Mark, ах, добре, дякую! Схоже, дату загадково встановили. Дякую, Господи Часу.
Ira Baxter,

2
Як я можу обробляти порожні рядки?
Данте

11

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


2

GCC може перехресно компілювати на різних платформах, але ви запускаєте flex і bison на платформі, на якій працює компілятор. Вони просто виплювають код С, який потім створює компілятор. Перевірте його, щоб побачити, наскільки великий отриманий виконуваний файл насправді. Зверніть увагу, що у них є бібліотеки часу виконання ( libfl.aтощо), які вам також доведеться перекомпілювати до своєї цілі.


Мені ще потрібно дослідити розмір цих бібліотек, і тому я поставив запитання в першу чергу. Я хочу щось конкретно орієнтоване на невеликі мікроконтролери.
Йохан

-1

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


Проблема з вашою відповіддю полягає в тому, що вона не знає обмеження 8-бітної платформи. Буде важко отримати інструментальний ланцюг, що підтримує підсилення, і таку мінімальну платформу одночасно.
Waslap

-5

Замість того, щоб заново винаходити колесо, подивіться на LUA: www.lua.org . Це мова інтерпретації, призначена для вбудовування в інше програмне забезпечення та використання в невеликих системах, таких як вбудовані системи. Вбудоване дерево синтаксичного розбору синтаксису, логіка управління, математика та підтримка змінних - не потрібно винаходити щось, що вже налагоджено та використано тисячами інших. І він є розширюваним, тобто ви можете додати його до граматики, додавши власні функції C.


2
Яким би маленьким не був Lua, я майже впевнений, що він все одно був би занадто великим.
icktoofay
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.