Що це означає – написати мову? Це означає, що вам потрібно написати програму, яка буде інтерпретувати або компілювати мову програмування (нову мову). Але для написання цієї програми вам доведеться використовувати яку-небудь з існуючих мов (базову мову). Також можна написати цю програму на чистому машинному коді. Переклад статті «Write you a programming language».
Інтерпретатор або компілятор?
Для початку потрібно вибрати, що ви хочете написати: інтерпретатор або компілятор (або обидва). Яку роль вони відіграють?
Комп’ютер (машина) – це інтерпретатор машинного коду (а це теж мова).
Компілятор переводить вихідний код на іншу мову, наприклад:
- на високорівнева мова програмування (транспіляція),
- на машинний код для віртуальної машини (байткод), наприклад JVM,
- на низькорівневу мову, наприклад мову асемблера, яка потім буде переведена (іншою програмою) на машинний код.
Інтерпретатор власне виконує інструкції, наприклад JavaScript, Lisp, Machine, JVM, gnuplot, calculator і т. д.
Цікаво, що ви можете написати для своєї мови програмування як інтерпретатор, так і компілятор. Приклади – Lisp (CommonLisp), Scheme (Chez Scheme).
Також можна написати інтерпретатор або компілятор для мови програмування на цій же мові. Для цього вам доведеться спочатку написати перший інтерпретатор або компілятор на іншій мові, а потім, в нових версіях, ви зможете користуватися вже новою мовою (розкрутка).
Найкращі ноутбуки для програмування в 2021 році
Керівництва по темі створення інтерпретаторів і компіляторів можна розбити на категорії відповідно до «базових» та «нових» мов, наприклад:
- Make a Lisp – реалізація Lisp на 82 мовах
- Write Yourself a Scheme in 48 Hours – реалізація Scheme на Haskell
- Write you a Haskell – підмножина Haskell 2010 реалізоване на Haskell.
Якщо мова реалізована як компілятор, вона переводить вихідну мову на мову призначення. Вхідна мова – те ж саме, що і «нова», але мова призначення – не те саме, що «базова» мова. Керівництва можна розбити по категоріям відповідно до вихідної мови та мови призначення. наприклад:
- Implementing a JIT Compiled Language with Haskell and LLVM. Компілятор, реалізований на Haskell, переводить мову Kaleidoscope на LLVM IR (intermediate representation, проміжне представлення коду).
- How to implement a PL. Компілятор, реалізований на JS, переводить мову «λanguage» на JS.
- the-super-tiny-compiler. Компілятор, реалізований на JS, переводить маленьке підмножина Lisp на C-подібний синтаксис.
Управління пам’яттю
Наступне, що потрібно визначити, це як ваша мова програмування буде управляти пам’яттю:
- взагалі не буде (як, наприклад, яка-небудь декларативна мова)
- вам це байдуже. Виділяємо пам’ять і дозволяємо операційній системі очищати її після виходу з програми.
- статичне управління пам’яттю, як у C
- окремий випадок – Rust borrow checker
- окремий випадок – Zig
- прибирання сміття, як у Lisp, JavaScript і т. п.
- окремий випадок – посилальні можливості Pony
- можливо, якась суміш статичного типу і збірки сміття?
Система типів
Далі треба розібратися, як в вашій мові закінчиться справа з типами:
- відсутність типізації (коли у вас є тільки один тип), приклад – lambda calculus або calculator;
- динамічна типізація, приклад – Lisp;
- статична типізація, приклад – Haskell;
- структурна типізація, приклад – TypeScript.
Можна вибрати і дещо складніше. Наприклад, типізацію Мартіна-Льофа (ML), залежні типи (Idris), лінійні типи і т.п.
Парадигми
Все, вказане вище, стосувалося всіх мов програмування. На наступному етапі ви можете вибрати інші особливості (одну або більше), які визначать парадигму вашої мови.
Наприклад, ви допускаєте використання функцій першого класу, строгі обчислення, динамічні типи, макроси – і отримуєте Lisp.
- Якщо ви використовуєте гігієнічні макроси і продовження, ви отримуєте Scheme (приблизно).
- Використовуючи незмінні типи даних, ви отримуєте Clojure (теж приблизно).
Або ви можете дозволити використання функцій першого класу, ледачі обчислення, статичні типи – і отримати на виході ML.
Додаткові особливості
Поверх усього цього ви можете додати додаткові особливості, наприклад:
- систему модулів
- оптимізацію хвостової рекурсії
- зіставлення зі зразком, як в функціональних мовах програмування, і т. п.
Генератори парсерів
Одним з факторів успіху MAL є те, що йому не потрібен генератор парсеру: парсер Lisp відносно легко імплементувати. Він реалізований більш ніж на 80 мовах. Те ж стосується lis.py – у нього є ще простіший токенізатор.
Більшість посібників, не пов’язаних з Lisp, передбачають необхідність якогось специфічного генератора парсерів.
Список туторіалів
Тут зібрана колекція посібників зі створення мов програмування. Вони знаходяться в репозиторії на GitHub, так що ви цілком можете стати контрибутором.