Игрушечный ЯП — Cockroach
Всем привет.
В школьном и более продвинутом курсе информатики есть учебный язык — Кукарача. Довольно удачный, для обучения детей программированию. Простой, понятный, визуальные результаты с первой строчки.
Авторы курса сделали только exe-шник под Windows. Когда младший сын начал требовать «Папа научи программировать» принял волевое решение — сделаю свою имплементацию. И сделал.
Че это вообще?
Есть прямоугольное поле. Жучок и буквочки. Нужно писать программы, что бы жучок правильным образом подвигал буковки.
Поддерживаются простые команды — ВВЕРХ
, ВНИЗ
, ВПРАВО
, ВЛЕВО
и их группировка с помощью {}
.
После каждого действия известен результат — какую букву толкнул жучок.
Есть циклы — ПОВТОРИ x
, ПОКА y
и условие ЕСЛИ z ТО ... ИНАЧЕ ...
И даже процедуры — ЭТО proc_name ... КОНЕЦ
Поэтому, в процессе обучения ребенок учится довольно сложных концепциям включая рекурсию и процедурное программирование.
Поле
Поле работает совсем очевидно — массивчик char’ов и специальный символ для жучка. Процедуры, которые передвигают жучка изменяя состояние. Запоминаем последнюю букву, которую толкнули. Осталось добавить загрузку/выгрузку в строку примерно такого вида:
А_А__
_1_1_
_____
____~
а также колбек на изменение, что бы отрисовывать и симпатичный компонент готов.
Как сделать интерпретатор?
Оказалось, что сделать интерпретатор не так уж и сложно. Надо понимать что такое синтаксис и грамматика, почитать немного примеров и воспользоваться готовыми либами.
Я взял antlr — т.к мой основной язык java и там этот генератор парсеров на слуху. В итоге получилась такая грамматика
грамматика
grammar Cockroach;
@header {
package ru.nizhikov.cockroach.antlr;
}
prog
: exprs EOF
;
exprs
: expr+
;
expr
: statement
| repeat
| while
| if
| proc
| id
| LINE_COMMENT
;
statement
: UP
| DOWN
| LEFT
| RIGHT
| STAY
| group
;
repeat
: REPEAT NUM expr
;
while
: WHILE condition expr
;
group
: OPEN_BRACKET exprs CLOSE_BRACKET
;
if
: IF condition THEN statement (ELSE statement)?
;
proc
: THIS id exprs END
;
condition
: NOT? id
| NOT? EMPTY
| NOT? NUMBER
;
id
: ID
;
LINE_COMMENT : '//' ~[\n\r]* -> skip;
UP: 'ВВЕРХ';
DOWN: 'ВНИЗ';
LEFT: 'ВЛЕВО';
RIGHT: 'ВПРАВО';
STAY: 'СТОЯТЬ';
NOT: 'НЕ';
EMPTY: 'ПУСТО';
NUMBER: 'ЦИФРА';
REPEAT: 'ПОВТОРИ';
WHILE: 'ПОКА';
CHAR: 'БУКВА';
OPEN_BRACKET: '{';
CLOSE_BRACKET: '}';
IF: 'ЕСЛИ';
THEN: 'ТО';
ELSE: 'ИНАЧЕ';
THIS: 'ЭТО';
END: 'КОНЕЦ';
ID: LETTER (LETTER | DIGIT)*;
LETTER: [a-zA-Zа-яА-Я];
NUM: DIGIT+;
DIGIT: [0-9];
SPACE: [ \r\n\t]+ -> skip;
Грамматика задает правила для парсера что бы из текста сформировать синтаксическое дерево. Пример на картинке.
простите мне было лень делать дерево для сабжевого ЯП
Интерпретатору всего-лишь надо обойти это дерево верным образом и выполнить необходимые действия. Действия следуют из их смысла:
Определение процедуры — запоминаем имя процедуры в специальной мапке. Ключ — название, значение — поддерево команд.
Вызов процедуры (токен ID) — ищем определение процедуры и вызываем соответствующее поддерево команд.
Цикл
ПОВТОРИ x
— выполняем поддеревоx
раз.Цикл
ПОКА у
— проверяем условиеy
и выполняем поддерево пока оно выполняется.ЕСЛИ z ТО ... ИНАЧЕ ...
— проверяем условиеz
и выполняем то или иное поддерево.Обычные команды — изменяем состояние поля.
Интерфейс
Последний раз я делал UI еще во времена когда jquery и extjs были модными :), поэтому погуглив как сейчас делается интерфейс слегка ох^Wудивился обилию возможностей. В итоге собрал из туториала который первым заработал рабочее one page application и запилил с помощью того что знаю — bootstrap и jquery.
Очень хотелось сделать подсветку синтаксиса, а нагугленный компонент поддерживал другой генератор парсеров. Нет проблем — записать грамматику немного другим синтаксисом проще простого.
Несмотря на то, что в CodeMirror довольно подробная документация разобраться в API оказалось не так просто. Возможно, я отвык от уровня документирования js компонент. Но, в итоге, подсветка синтаксиса и текущей команды во время дебага и запуска работает.
Сохранение файликов сделал через localStorage — удобненько.
Сложности
Первая реализация интерпретатора написана на java и работает через консоль. Пошаговое выполнение (отладку) легко сделать через ожидание ввода с консоли.
А вот в javascript простой возможности останавить выполнение в рандомном месте нет. Поэтому, пришлось заморочиться и сделать, что бы интерпретация приложения работала на promise’ах. Команда кукарачи выполняется при выполнении Promise.resolve
.
Публикация
Вкратце — github прекрасен)
Оказалось, что в github есть бесплатный, автоматизированный, удобный функционал, что бы опубликовать one page application. Называется github pages. Обалденно удобно. Собираешь свое приложение, указываешь папочку, жмешь кнопку и вуаля — приложение готово и работает.
Ну вот и все pet project готов и вроде как работает. Ребенок два раза позанимался программированием и получил массу удовольствия. Я тоже доволен и пописал интересный код.
Иходники — https://github.com/nizhikov/cockroach
А еще у меня есть канал с выступлениями и ссылками на интересные пейперы из мира разработки СУБД.