Есть такая игра Haven and Hearth. Все в ней хорошо, затянула сразу. Но в ней приходиться делать очень много рутинных операций изо дня в день и меня, как программиста, это категорически не устраивало. Решил я облегчить нелегкий труд игрока, написав движок скриптов для клиента.
Игровой клиент написан на Java. Погуглив — нашел несколько решений: Lua, Groovy и другие. Изначально планировал сделать самый самый простой язык на подобие ассемблера и поэтому что-то серьезное даже не планировалось. Ни Luа ни Groovy меня не устроили в силу того, что пришлось бы изучать довольно много чтобы написать хоть чтото рабочее. Да и Lua насколько я знаю под Java существует только очень старый заброшеный порт, который давно не обновлялся.
Начал читать теорию разработки компиляторов — и сразу меня эта тема затянула. Скажу сразу что по сложности это куда выше чем было бы использовать Lua или Groovy. Но на тот момент мне стало жутко интересно именно создание своего ЯП с нуля, а конечный результат был уже не так важен, собственно именно поэтому я и решил написать свое.
Итак, обработка скрипта (парсинг) делится на несколько этапов:
1. Лексический анализ. анализируем текст скрипта на совпадение с лексемами, на выходе отдаем Асбтрактное синтаксическое дерево
2. Синтаксический анализ. тут все куда сложнее — нужно уже обрабатывать языковые конструкции.
3. Создание Символьной таблицы.
4. Выполнение скрипта.
Реализовать все это вручную — довольно сложная задача, поэтому начал поиск инструментов облегчающих задачу. И наткнулся в сети на ANTLR. Это надстройка над тем или иным языком программирования для разработки своего парсера. Поддерживает кучу языков: C, C++, C#, Java, Ruby и многие другие.
И тут появилась абсолютная свобода выбора синтаксиса своего скрипт движка, и вообще можно было все реализовать «чисто» под себя, без каких бы то ни было ограничений.
Выбор пал на классический Си ввиду простоы и распространенности синтаксиса среди других ЯП. Был взят пример реализации CMinus. И на его основе был написан свой парсер.
Основная проблема всплыла уже на этапе синтаксического разбора. Т.к. документация по ANTLR была устаревшая и запустить на выполнение скрипт не получалось даже из примеров. Пришлось лезть в недры исходников и разбиратся что да как.
Вот исходный код Лексического анализатора ссылка на pastebin. Он разбирает текст на лексемы и строит абстрактное синтаксическое дерево, больше ничего не делает.
На первый взгляд выглядит очень жутко. На самом деле я провел целую неделю только на понимание как же писать свои грамматики, читая чужой код и примеры в сети. И только после этого чтото начало укладываться в голове.
Парсер функций. Его задача вычленить функции из Абстрактного Синтаксического Дерева (далее АСД) и записать их в таблицу функции для дальнейшго вызова. Таким образом у нас функции можно вызывать из любого места скрипта, независимо от того где она объявлена — до или после вызова.
Синтаксический анализатор. Его задача фактическое выполнение кода путем вызова заранее описаной в скрипте функции void main(). Внутри него описаны правила дял разбора нод АСД, фактически — это прямые вставки кода в тело сгенерированного парсера.
Далее была написана прослойка между движком скриптов и самим клиентом H&H, что и определило реальный набор рабочих функций для взаимодействия с клиентом.
Весь исходный код клиента выложен тут.
Были написаны несколько рабочих скриптов облегчающих жизнь, которые разошлись по рукам…
Как ни странно изначально не верилось в то что все это будет работать без глюков и багов =), а оно заработало! Да еще как!
Все изыскания автора не претендуют на научную точность и верность. Все это личные изыскания в ходе которых, признаюсь я не сильно углублялся в недры теории, а изучал только необходимое для реализации затеи. Также я понимаю что получилось решение сильно уступающее другим скрипт движкам Lua и другим… Но! получен бесецнный опыт в разработке интерпретатора, а это и было целью (разобратся досконально как же оно все работает изнутри).
За бортом осталась реализация массивов и работа со строками, но на самом деле это уже вопрос техники. Самое сложное было — написать ядро.
Спасибо за внимание!
Игровой клиент написан на Java. Погуглив — нашел несколько решений: Lua, Groovy и другие. Изначально планировал сделать самый самый простой язык на подобие ассемблера и поэтому что-то серьезное даже не планировалось. Ни Luа ни Groovy меня не устроили в силу того, что пришлось бы изучать довольно много чтобы написать хоть чтото рабочее. Да и Lua насколько я знаю под Java существует только очень старый заброшеный порт, который давно не обновлялся.
Начал читать теорию разработки компиляторов — и сразу меня эта тема затянула. Скажу сразу что по сложности это куда выше чем было бы использовать Lua или Groovy. Но на тот момент мне стало жутко интересно именно создание своего ЯП с нуля, а конечный результат был уже не так важен, собственно именно поэтому я и решил написать свое.
Итак, обработка скрипта (парсинг) делится на несколько этапов:
1. Лексический анализ. анализируем текст скрипта на совпадение с лексемами, на выходе отдаем Асбтрактное синтаксическое дерево
2. Синтаксический анализ. тут все куда сложнее — нужно уже обрабатывать языковые конструкции.
3. Создание Символьной таблицы.
4. Выполнение скрипта.
Реализовать все это вручную — довольно сложная задача, поэтому начал поиск инструментов облегчающих задачу. И наткнулся в сети на ANTLR. Это надстройка над тем или иным языком программирования для разработки своего парсера. Поддерживает кучу языков: C, C++, C#, Java, Ruby и многие другие.
И тут появилась абсолютная свобода выбора синтаксиса своего скрипт движка, и вообще можно было все реализовать «чисто» под себя, без каких бы то ни было ограничений.
Выбор пал на классический Си ввиду простоы и распространенности синтаксиса среди других ЯП. Был взят пример реализации CMinus. И на его основе был написан свой парсер.
Основная проблема всплыла уже на этапе синтаксического разбора. Т.к. документация по ANTLR была устаревшая и запустить на выполнение скрипт не получалось даже из примеров. Пришлось лезть в недры исходников и разбиратся что да как.
Вот исходный код Лексического анализатора ссылка на pastebin. Он разбирает текст на лексемы и строит абстрактное синтаксическое дерево, больше ничего не делает.
На первый взгляд выглядит очень жутко. На самом деле я провел целую неделю только на понимание как же писать свои грамматики, читая чужой код и примеры в сети. И только после этого чтото начало укладываться в голове.
Парсер функций. Его задача вычленить функции из Абстрактного Синтаксического Дерева (далее АСД) и записать их в таблицу функции для дальнейшго вызова. Таким образом у нас функции можно вызывать из любого места скрипта, независимо от того где она объявлена — до или после вызова.
Синтаксический анализатор. Его задача фактическое выполнение кода путем вызова заранее описаной в скрипте функции void main(). Внутри него описаны правила дял разбора нод АСД, фактически — это прямые вставки кода в тело сгенерированного парсера.
Далее была написана прослойка между движком скриптов и самим клиентом H&H, что и определило реальный набор рабочих функций для взаимодействия с клиентом.
Весь исходный код клиента выложен тут.
Заключение
Были написаны несколько рабочих скриптов облегчающих жизнь, которые разошлись по рукам…
Как ни странно изначально не верилось в то что все это будет работать без глюков и багов =), а оно заработало! Да еще как!
Все изыскания автора не претендуют на научную точность и верность. Все это личные изыскания в ходе которых, признаюсь я не сильно углублялся в недры теории, а изучал только необходимое для реализации затеи. Также я понимаю что получилось решение сильно уступающее другим скрипт движкам Lua и другим… Но! получен бесецнный опыт в разработке интерпретатора, а это и было целью (разобратся досконально как же оно все работает изнутри).
За бортом осталась реализация массивов и работа со строками, но на самом деле это уже вопрос техники. Самое сложное было — написать ядро.
Спасибо за внимание!