Как стать автором
Обновить

Как я за месяц написал интерпретируемый язык программирования на Python

Уровень сложностиПростой
Время на прочтение7 мин
Количество просмотров19K
Всего голосов 32: ↑26 и ↓6+29
Комментарии44

Комментарии 44

Поздравляю с изобретением велосипеда :-)

Вообще-то есть LEX и YACC (или аналоги их - FLEX и BISON) Там достаточно описать грамматику языка чтобы получить исходный код интерпретатора на С/С++ Дальше уже можно играться с компиляцией в байт-код (например, для ускорения выполнения программы).

Когда-то давно занимался этим - нужно было написать скриптовый язык для работы со специфической платой расширения для компа (фактически там были ЦАП/АЦП - что-то к ней цеплялось, нужна была возможность писать скрипты для работы с тем что к ней прицепили). Делал интерпретатор скрипта (для тестов) и компилятор скрипта в байткод + интерпретатор байткода (когда скрипт уже протестирован).

Получился вполне себе типизированный процедурный язык с вистом и профурсетками переменными, условиями, циклами + набором встроенных специфических функций для работа с платой расширения.

Так вот делал как раз на С++ + FLEX + BISON. Даже работало :-)

Добрый день! Я рассматривал использование Flex и Bison. Основная цель проекта - самому написать Лексер и Парсер и приблизительно понять, как это работает изнутри. Также возникли проблемы с изучением Bison. Когда я начинал, я не понимал как описывать грамматику.

Потому что и FLex и Bison, опять же - это очень старые технологии. Причем ориентированные на один целевой язык. Посмотрите на ANTLR, для начала. Он вам построит парсер на питоне, если вам так удобнее.

В принципе, написать самому что-то типа LL(1) на языке высокого уровня вообще не сложно, это будет примерно столько же кода, что вы тут показали. Все из чего состоит генератор парсеров - это по сути, снова парсер метаязыка описания грамматики (EBNF обычно), а дальше некоторый объем возни с таблицами символов конкретной грамматики (для LL(1), опять же - это например построение транзитивных замыканий множеств символов, с которых может начинаться нетерминал). Ничего принципиально сложного там нет. Главное начать с хорошей книжки по теории.

Спасибо за совет! Я рассмотрю использование LL(1)

К своим, если есть интерес, очень рекомендую после такого упражнения попробовать ANTLR4 или какие ещё генераторы умеют в Python, просто для сравнения.

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

Но в свое время когда надо было свой "язык" в "прод", то в бой пошел ANTLR )

Я сразу отказался использовать такие библиотеки, как PLY и RPLY, поэтому мой проект написан на чистом Python. 

То что вы PLY не взяли - наверное даже правильно, потому что по описанию это порт LEXX/YACC, а это ну очень старый подход к построению интерпретаторов. Можно и что-то получше найти взамен.

А вот то что вы про грамматику своего языка ничего не написали, а сразу код стали приводить - это зря. По-хорошему, если уж вы делаете язык - опишите грамматику. А потом уже из нее можно было бы построить (автоматически) таблицы для разбора одним из проверенных методов, типа LR(n) или даже самым простым LL(1). А для лексера напрашивается конечный автомат.

В вашем же коде, не порывшись достаточно глубоко, не понять, что за метод вы применили. И кстати, именно поэтому у вас парсер сложный, в том числе для вас. В случае же например LL(1), парсер это строк 20 кода, примерно. Остальное таблицы, которые автоматически строятся из грамматики. В этом случае вам зачастую вообще не нужно код вашего парсера понимать, потому что он строится автоматически, а вы для него дописываете функции, выполняемые например при распознавании в тексте какой-то части грамматики.

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

LEX/YACC это не то чтобы старый подход. Это потоковые (насколько помню) интерпретаторы. Они позволяют (в том числе) строить и командные процессоры, а не только интерпретаторы скриптовых языков.

Но описание грамматики нужно, да.

LEX/YACC это не то чтобы старый подход. 

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

Ну то есть, я бы их не стал советовать, но с другой стороны - чтобы воспользоваться одним из таких инструментов, все-таки придется понять, что такое грамматика, и какие они бывают, и попытаться ее написать. А это позже непременно пригодится.

Так что может быть и стоило бы их взять - но желательно понимать, что есть и другие подходы, более современные.

Ну я этой темы не касался очень давно - то что делал, делал лет 15 назад. И тогда какого-то серьезного выбора не было.

То что оно громоздко и медленно было - да. Но тогда решил это байткодом т.к. там набор скриптов писался один раз (т.е. постоянно их править не было нужды, иногда что-то поправить, иногда новый написать). А вот гонялись они постоянно. И скомпилированные в байткод скрипты работали достаточно шустро т.к. разбора уже не требовали (там вообще был отдельный раннер для них).

ANTLR это вообще-то на минутку, 1992 год. Так что выбор был.

Возможно, но тогда (2005-й год или раньше даже) что-то найти было сложнее, особенно когда не знаешь точно что ищешь :-)

Так или иначе, поставленная задача тогда была решена в установленные сроки и с потребным качеством. С тех пор не приходилось заниматься подобными вещами.

то что делал, делал лет 15 назад.

OCaml, ocamllex/ocamlyacc — они хотя бы типы не стирают.

LEX/YACC это не то чтобы старый подход.

Если под этим подходом вы имеете ввиду генераторы парсеров, то, конечно, не старый. А если конкретно пару Lex/Yacc — то это из времён, когда динозавры бегали по болотам. Мало того, что вместо них flex/bison, так и те стирают типы, передавая в основную программу.

Из современного antlr4, BNFC, ocamllex/menhir.

antlr4

Едва ли его можно назвать современным, но с учетом отсутствия альтернатив сгодится.

ну очень старый подход к построению интерпретаторов. Можно и что-то получше найти взамен

Подходы кагбэ не портятся со временем. Вы предлагаете LL вместо LR. Но LL уступает LR в некоторых аспектах.

Я не предлагаю LL и даже LR. Вам показалось. Я предлагаю взять любой инструмент, который умеет генерировать код по любой типовой грамматике. Как правило это будет LR, кстати.

Направление верное взяли. Фишка ручного разбора в том (а не готового генератора типа PLY/FLEX/YACC), что можно выдавать человекопонятные сообщения об ошибках (забытая скобка, или перменная) и за один проход находить не только первую ошибку, а несколько, например в разных функциях вашего языка.

Каждая такая статья кидает камень в четырнадцатилетнего меня, который по видеокурсам пытался свой первый сайт на php сделать (получилось очень плохо).

получилось

Каждая такая статья кидает камень в четырнадцатилетнего меня, который по видеокурсам пытался свой первый сайт на php сделать (получилось очень плохо).

Пфуй, у нас в 14 лет учителя говорили, что не надо заниматься всякой низкоинтеллектуальной чепухой вроде програзма! :-)

В ваши 14 были видеокурсы, php и веб.

Главное, что получилось и был получен опыт. Когда я пробовал писать на PHP, то он у меня даже не запустился :)

Я никогда не интересовался созданием языков программирования, но регулярно натыкался на утверждения что в Rust макросы настолько разнообразные, что на них можно делать новые и новые языки.

По факту, макросы дают вполне уверенный доступ к компилятору. А дальше дело лишь за чёрной магией и упорством.

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

Ох, любят же конечно люди копипасту. Чем вас словари-то обидели что вы их не использовали?

Парсер, по моему мнению, является сложнейшей частью языка.

А для меня парсер - одна из простейших частей языка. Говорю как программист, работающий над компилятором языка Kotlin.

Ну так ты сравнил божественный Котлин и чё-попало Питон) точнейшая типизация делает парсинг сильно проще. Байт-код ART выглядит отлично, он намного понятнее даже того же Ассемблера (который вообще "обёртка над байт-кодом") - именно такое сравнение мне пришло на ум...

Ну так то Котлин сложнее из-за поддержки хотя бы того же замыкания, которое убил в Питоне данный ТС.

Хотя если честно я не очень понял в чём смысл этой работы, в то время как основная её фишка "многопоточный цикл" работает неправильно xD. Во избежание неадекватных минусов поясню для тех кто не допёр: посмотрите на output цикла от 0 до 4 в три потока, который выдаёт значение 0 четвертым. Подсказка: потоки работают последовательно, зачем ноль в конце?

Коммент про парсинг говорю как энтузиаст, изнедавна ковыряющий SMALI (продукты жизнедеятельности Котлина)

Потоки работают паралельно, на то они и потоки.
Ну а если дело в том, что 3 потока при обработке 4 выдали 0 в конце, ну так то же ничего сверестественного. Планируем на выполнение 4 задачи, 0 забирает первый поток и тут у него отбирают управление и передают второму, дальше дела, дела, потом управление возвращают первому, после всех остальных. Бинго )

Может быть такое поведение для Питона и норма, но для нормальных ЯП механизм "чё попало" не выглядит уместным. Пускай другие потоки начинают обрабатывать числа, но почему тогда две итерации в некоторых случаях быстрее чем одна, если сложность итераций одинаковая? А если чисел было бы больше, ноль тоже последним бы был?

Если ты думаешь что ТС использует плохую функцию print (хотя очевидно она работает как очередь), пусть попробует создать массив по размеру итераций и положить по индексу каждой итерации значение таймштампа. Тогда и узнает точное время выполнения каждой итерации

Для любых норма, паралельное исполнение, на то и паралельное исполнение.

А для меня парсер - одна из простейших частей языка.

Это немножечко напоминает анекдот про то, как мальчики в классе мерялись писюнами. :-)

Очень хорошо. Можно только код рефакторить с применением подхода Don't Repeat Yourself (DRY). А именно, использовать новый оператор Match вместо elif'ов и try-except блоков, и словари.

Ничего больше усложнять не стоит, чисто лишь код в порядок привести.

Спасибо за совет! Использовать словари действительно хорошая идея.

Очень хорошо!

Одно замечание — вы смешиваете понятия «языка» и «интерпретатора». Язык — это соглашения, это не программа, он не может работать быстро или медленно. :-) Но это сейчас не важно.

Вы сделали tree-walking interpreter. А дальше вы можете воспользоваться техниками из Crafting interpreters, чтобы сделать компилятор в байт-код, и интерпретировать уже байт-код. См https://craftinginterpreters.com/

См. также https://github.com/true-grue/Compiler-Development — «Что читать о разработке компиляторов». И можно задавать народу вопросы в телеге «Compiler Development».

Успехов!

Спасибо! Я уже думал, сделать язык компилируемым. Сейчас только изучаю теорию.

Нет, интерпретатор был написан благодаря статьям на Хабре.

А у меня в мои 15 лет не найдется проекта, который я был бы готов показать людям.

Не торопитесь, можно до 25 (50-ти) изучать базу и штуки интересные очень узкому кругу людей.
А потом на основе этого сделать продукт всей своей жизни )

Отлично! Интерпретатор интерпретируемый интерпретатором!

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории