Для тех, кто мечтает создать свой язык программирования.

15.12.2020 12:23, автор DiEitch

Я не советую вам говорить на эту тему со знакомыми программистами, сотрудниками в офисе и случайными прохожими. В лучшем случае просто засмеют, а то и отправят к психотерапевту. Однако, большинство продвинутых программистов хотя бы раз в жизни задавало себе подобный вопрос: "А можно ли придумать что-то лучше Си, плюсов или Питона (или хотя бы такое же по значимости), стать вторым Деннисом Ритчи или третьим Гвидо Ван Россумом (или хоть четвёртым)?"

В принципе, с какой-то периодичностью появляются новые ЯП, как довольно успешные, так и однодневки. Многие ошибочно полагают, что написание языка - это чисто техническое дело (лексер, парсер и "дело в шляпе"). Однако, разбираясь в этих премудростях, я понял, что это больше философская задача, причём очень и очень непростая.

Но... немного отвлечёмся на существующие ЯП (здесь, ЯП-языки программирования, а не "Я плакалъ"). Для оценки порога вхождения в язык обычно применяют пример "Helloworld" (что, я считаю, не совсем правильно, ибо он не охватывает всё множество операторов и приёмов языка, и ни разу не характеризует язык по сложности, но зато можно быстро и просто понять: нравится язык или нет).

По традиции, всё же, давайте сравним этот пример на Си и на плюсах:

И что мы увидим? Второй пример, естественно, покажется нам сложнее: какие-то "<<","using namespace","cout", "endl". В то время, как в первом просто вызывается функция печати printf(). Чтобы начать "писать" на C++ мы должны уяснить, что обозначают эти неизвестные слова, и как их применять. Что, например, строку "Hello, world!" сдвигаем в консоль, что консоль - это серийное устройство, поэтому стоят операторы сдвига, что endl, это перевод строки, который идёт после самой строки, ну и так далее. Это грубо и есть порог вхождения в язык.

Вернёмся к философской проблеме.
Аксиома 1. Чем выше порог вхождения в язык, тем меньше программистов изначально захочет его использовать. Чем меньше аудитория, меньше известность, меньше новых пользователей. Из-за этого многие языки сразу после появления "канули в лету". И обратно: чем ниже порог вхождения в язык - тем меньше на нём в итоге можно написать (зависимость непрямая). Изначально, язык будет "набирать обороты", но в какой-то момент окажется, что он не соответствует требованиям программистов, и опять-таки популярность уйдёт, его забудут. В таком случае, разработчики обычно корректируют язык, добавляя в него различные операторы, библиотеки и усложняют (естественно, повышая порог вхождения для новичков, и главное тут - не переборщить);
Аксиома 2. Язык должен базироваться на тех знаниях, которые уже заложены в любого человека (например, школьная алгебра). А если он "ломает и коверкает" предыдущие знания и уводит в сторону, это только усложняет его изучение и формирует психологические препятствия;
Аксиома 3. Каждый оператор языка должен быть чётко закреплён за своим символом и соответствовать ему интуитивно (я тут не учитываю перегрузку). И если в вашем языке операция вычитания будет обозначаться знаком "#" в одном случае, а в другом "$" , то с вами всё понятно, кроме того, все эти символы должны легко и непринуждённо воспроизводиться средствами ПК (как думаете, откуда в Си "<=", ">=" и "!=");
Аксиома 4. Язык должен обладать чёткими и понятными синтаксическими правилами и логическими конструкциями без разночтений (сюда я бы отнёс также приоритеты операций и знаки их повышения);
Аксиома 5. Исходные тексты должны быть легко читаемыми с чётким и ясным началом и концом блоков (правила пунктуации и форматирования, яркий пример - инденты в Питоне, которые также определяют начало-конец блоков).

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

Давайте вернёмся на пару десятков лет назад. BASIC. Язык, который называли языком домохозяек, или "кухонным" языком. Так ли прост он был? Интерпретатор бейсика использовал номера строк в качестве меток, что было вполне понятно, но в больших программах приводило к эффекту "спагетти", это когда даже сам автор уже не совсем понимал, что и где происходит в программе. Cо стороны операторов IF, FOR и прочих он не сильно отличался от других языков программирования. Переменные создавались оператором LET или выражением присваивания. Однако, работа с массивами и строками была не совсем ясной и не совсем удобной, указатели отсутствовали. Процедуры отсутствовали в привычном виде и не имели явных параметров (просто находились по определённому номеру строки, который нужно было знать для вызова) и вызывались оператором GOSUB.

Пример "Helloworld" для интерпретатора BASIC:

Почему Бейсик стал успешным на тот момент? Я считаю, что ключевым стало внедрение его в домашние компьютеры (в частности, ZX-Spectrum), калькуляторы,  игровые консоли. Кроме того, свою роль сыграла и реклама: Beginner’s All-purpose Symbolic Instruction Code — универсальный код символических инструкций для начинающих, а другие языки того времени были уж очень узкоспециализированными. Интерпретатор BASIC помогал отлаживать программы в пошаговом режиме (что позволяло сразу увидеть, где "программист" напортачил) и мог работать в режиме калькулятора (например, PRINT 2^5), а также сохранять, загружать и редактировать программы (в том числе, и заветные для многих игры).

Резюмируя выше сказанное: чтобы программисту (или вообще не программисту, а домохозяйке, например) не пришлось учить язык, он должен быть интуитивно понятным и предельно логичным для любого человека, а, значит, использовать конструкции, к которым мы привыкли с детства и постоянно пользуемся в своей жизнедеятельности, например, все понимают выражение: 2+2=4. Такими выражениями языка могли бы пользоваться люди любого склада ума и любых профессий. С другой стороны - язык должен быть и достаточно развитым (и при этом всё ещё оставаться достаточно понятным), содержать переменные, массивы, указатели, должен уметь использовать ранее написанные библиотеки.

Теперь вы уже, вероятно, пришли к пониманию: какая непростая эта задача - придумать свой язык?

Давным давно, когда я ещё не был программистом, я изучал в универе Паскаль (до этого был и Бейсик, но это было давно и не считается). Когда мне пришлось переходить на Си (а мне именно пришлось - особо не хотел), я полагал, что Си - довольно сложный язык. Однако, моё мнение с тех пор сильно изменилось. Он настолько хорошо продуман, что проще языка, наверное, уже и не придумать. Ещё долгое время его не смогут (я надеюсь) полностью заменить ничем другим (особенно в аппаратуре) благодаря его "низкоуровневости", высокой скорости и лёгкой портабельности.

В дополнение к данной статье я предлагаю читателям "пощупать" простой интерпретатор Си с редактором исходного текста на контроллере ATMEGA1284P (этот контроллер AVR выбран исключительно для показательных целей и из-за сравнительно большого количества RAM по сравнению с другими подобными в DIP корпусе). Скачать архив можно здесь. Клавиши bLeft, bRight и bMenu - это  энкодер (в Протеусе не было смысла его эмулировать).

Пояснения: по умолчанию в проекте загружен HEX с примером blink. Для открытия примера printf нужно дважды нажать левой кнопкой мыши и выбрать в свойствах контроллера "Program File" другой пример. При нажатии bShift код примера подгрузится во встроенный редактор, его можно править. Для запуска предустановленного примера (и выхода из редактора) при нажатом Shift (горит светодиод D1) нажать и держать более 2х секунд клавишу bMenu.

При наборе текста длительное нажатие bMenu (без Shift) - перевод строки, короткое - вызов экранного меню. При нажатом Shift: bLeft - Space, bShift - выключить Shift. Без Shift курсор влево/вправо. Длительное нажатие bShift - Lines (номера строк), повторно - выключение Lines.  Интерпретатор имеет ограничения: пока нельзя полноценно работать с массивами (объявление: char arr[]={'p','r',...}; или char arr[]="Privet"; "не прокатит"), но объявление char arr[2]; не выдаёт ошибок, если не обращаться к переменной arr, операции типа ^= не проходят (пока не исправлял). Длительное нажатие bMenu при выборе уже символа (а не выборе подменю) в меню удваивает введеный символ/оператор (для набора ((, )), {{, }}, [[, ]], &&, ||, ==, ++, -- и прочих, исключая зарезервированные слова и знаки пунктуации).

При завершении программы (кроме бесконечных циклов) или при обнаруженных ошибках запускается просмотрщик лога длиной 20 сообщений (пока) с выводом printf и exitcode=code main().

Когда запущен viewer: длительное нажатие bShift - Lines (номера строк), повторно - выключение Lines; При нажатом Shift: bLeft - курсор вверх/вниз,bShift - выключить Shift, без него курсор влево/вправо. Редактор и просмотрщик автоматически учитывают  (перенос строки).

Проект в протеусе с проверкой декларации массива.

OSD меню:

Где: 0 - ввод цифр 0..9; a,k,u (c Shift A,K,U) - ввод букв a-j/A-J, k-t/K-T, u-z,_/U-Z,\; + - арифметика; & - логика; ; - пунктуация; () - различные скобки; if - зарезервированные слова;

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

На Хабре я как-то прочитал статью, где один товарищ писал интерпретатор CPrompt, но в комментариях его просто "задавили" по основным пунктам: 1)неприменимость, 2)моральное устаревание, 3)поддержка не всех стандартов.

Немного покритикую здесь эти мнения:

1) Применить такой интерпретатор прямо сегодня можно в ПЛК (кроме того, некоторые производители программируемых логических контроллеров уже давно начали вводить в свои изделия поддержку языка Си, совместно с языком релейных схем и функциональных диаграмм). Представьте себе ситуацию: 1.1) крайний север, АСУТП с ПЛК на FBD. В минус 50 градусов по Цельсию нужно присоединить ноутбук с системой разработки к контроллеру, сначала отладить а потом прошить (писать программу, конечно, можно и в помещении, но проверять нужно будет в "поле", а загонять оборудование в тёплый док - большие финансовые и временные потери - никто не позволит); 1.2) маленькая фирма из 2 человек (директор и бухгалтер) поставляет ПЛК на большое предприятие (завод), на этот раз заказ включает настройку и программирование, надо либо дополнительно нанять спеца по релейкам и FBD (а это, на минуточку, выйдет вам в десятки тысяч долларов), либо просто найти "сишника", которых пруд пруди, за оклад; возможны и другие похожие ситуации - стоит только немного пофантазировать;

2) Си ещё долго не устареет, рано списывать активный язык, как бы некоторым этого ни хотелось. Кроме того, Си обладает отличной портируемостью, что немаловажно для аппаратуры. Бывает так, что вчера заказчик хотел контроллер AVR, а сегодня "бабки надвое на скамейке" посоветовали ARM CORTEX-M, а завтра - это уже ARM9;

3) далеко не везде нужны лямбды, нэймспейсы, классы, тройные массивы, например, аппаратура в этом плане вообще консервативна. Да, можно написать и на 17 плюсах аппаратуру, получить "синтаксический сахар" и возрадоваться, но потерять на сериализации львиную долю производительности и поставить оборудование мощнее и на порядок дороже, а также получить вероятные утечки памяти и потом успешно бороться с ними, ну и обслуживать это всё плюсовики не станут за копейки;

4) для начинающих иногда полезно иметь в руках реально работающую вещицу, где встроенный интерпретатор Си (имеются ввиду те, кто работает с оборудованием, остальным и мобильник сгодится с интерпретатором).

PS: меня можно упрекнуть, что в этой статье речь сначала шла о написании своего ЯП, но потом были перечислены различные языки, а в итоге вообще ведётся пропаганда Си! И получается, что вроде я даже отговариваю читателя от написания своего собственного языка. Нет, и ни в коем случае. И как раз-таки всё логично: это напоминание, что, когда вы будете продвигать свой язык, то помните, - далеко впереди вас есть такие мощные и успешные конкуренты как: Си, Питон, JAVA и другие. С другой стороны - вам будет и на кого ориентироваться, и с кем сравнить. А дорогу осилит идущий.