Не подобається ООП Робіть свою мову програмування, Дмитро Hестерук

Блог про програмування — C#, F#, C++, архітектура та багато іншого

Чи не подобається ООП? Робіть свою мову програмування!

В інтернеті нині модно говорити що «ООП це шлак», і багато хто мріє зробити свою власну мову програмування, але чогось бояться. А насправді, якщо з розумом підійти, тут все просто. Серйозно! Я знаю, ви хочете мені заперечити, що мовляв…

Писати свій компілятор у native code/IL/bytecode надто складно — а й не треба! Ви розумієте, що існуючі компілятори на кшталт С чи Java відточувалися роками, сотнями людей? Чому б не скористатися цим багатством, компілюючи вашу мову в той же С/C++, і потім отримуючи від С/C++ компілятора всі оптимізації і плюшки?

Мене не прет ідея возитися з кастомними форматами файлів для лексингу та парсингу, це депресивно - а і не треба!! Просто візьміть фреймворк, який підтримує парсеробудування у коді.

У мене зараз весь код на Java/C#/C++ написаний, як я зроблю interop? — так дуже просто, адже з підходом транскомпіляції, ви можете транслювати свою мову в будь-який з перерахованих вище, генеруючи і потім споживаючи будь-який інтерфейс , причому обидві сторони.

Мова я зроблю, а як щодо підтримки мови в моїй улюбленій IDE? — о, ну це вже найвищий політ. Для початку, постарайтеся зробити короткий маленький язик, для якого інструменти не критичні. А потім можна навчитися і підтримувати тули (або писати свої).

Щоб показати наскільки це все просто, ось невеликий приклад: уявімо, що ви хочете розширити визначення С-подібної глобальної функції, додавши такі фічі:

Визначити свій набір «коротких» змінних типів, на зразок i32 або f64 на заміну int і double .

Передавати аргументи уформаті x,y : i32 , тобто перевикористовуючи визначення типу для кількох одночасно.

Додавати в тіло функції визначення змінних на кшталт x = 5 так щоб, за умови, що x не ім'я параметра, це перетворювалося на повноцінну декларацію змінної, а інакше просто присвоювалося значення.

Для початку такий фічесет підійде? Я знаю що мало, але я тут і не намагаюся цілу мову зробити. Ось як це виглядатиме:

Структури для мови

У всіх мовах є механізми побудови парсерів. Я візьму C++ і Boost.Spirit, наприклад, але взагалі мова тут особливого значення немає. Для початку давайте зробимо нові типи на зразок f32 замість float:

Тепер визначаємо функцію:

У будь-якої функції є ім'я, вона бере скільки там параметрів (ну, декларацій параметрів, але стислість сестра таланту), і у неї є тіло, яке в нашому випадку складатиметься виключно з присвоєння значень змінним (не дуже практично, I know) .

Оскільки ми вміємо визначати аргументи стилі x,y:i32 , тобто. кілька з одним типом, що структур parameter ми визначимо ось так:

Ну і нарешті присвоєння значень змінним можна зробити так:

Вище показаний г~код для визначення типу, думаю ви розумієте, що згодом це можна пофіксувати.

Все структури готові, можна будувати парсер. (Насправді є ще етап їхньої адаптації через Boost.Fusion, але це implementation-specific деталь, якщо щось гляньте в сорці.)

Парсер для нашої мови написати настільки легко, що я просто наведу весь код повністю, а потім ми його обговоримо, ок?

Для початку, ось ці qi::rule просто говорять парсерові як те, що він розпарсує лягає на структури що ми визначили раніше. Наприклад, ось хочеться розпарсувати присвоєння на кшталт x = 3, що це? Цеідентифікатор (тобто 1 і більше alphanumeric символів), потім = , потім ще раз набір символів і в кінці ; .

Саме Boost.Sprit, на відміну регулярок, «один і більше» записується як до типу символу, тобто. +alnum. Тобто означає «один і більше», * — «скільки завгодно», і так далі. Ось і виходить, що привласнення ми розпарсили, а оскільки наш qi::rule мепить його на assignment_statement, поля цієї структури будуть присвоєні автоматично. Це геніально, чи як?

Те саме і з іншими частинами мови. Хочеш розпарити кілька змінних через кому і запхати їх у вектор? Пишемо +alnum % ',' де оператор % - це як сказати *(+alnum >> ',') , тільки коротше. Що також зручно.

Так от, парсер у нас готовий, можна парсити. На Spirit це робиться ось так:

…де render() – це функція яка обходить те що ми напарсили і генерує з цього найчистіший, готовий до компіляції С (ніхто не заважає вам виводити відразу в різних мов).

Pretty print

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

Потім виводимо параметри, не забуваючи, що у нас може бути кілька імен для одного і того ж типу аргументу:

А потім, акуратно, привласнення. Не забуваємо, що тип потрібно прописувати тільки якщо змінна не фігурує десь у параметрах функції:

Ось, власне, і все. Тут у принципі можна продати «повноцінний» Visitor, якщо хочеться.

Висновок

Як ви зрозуміли, тут залишився крок компіляції отриманого коду — думаю всім таким чином очевидно як це робити, це залежить від мови, яку ви нагенерували. Взагалі я борюся за «портативний» C/C++,але вирішувати зрештою вам.

Якщо бажаєте сорці проекту, вони тут. Мій приклад на С++, але ви можете реалізувати свою мову будь-чим. Мораль у тому що створити зараз свою кросс-компілювану мову легко, тому замість того, щоб нити про ОВП і воювати з вітряками, простіше сісти і запиляти щось своє. Так що сідайте і пишіть спеку вашого чудо-юдо мови. Успіхів!