Такой язык не обязан напоминать обычный ЯП. Если бы JavaScript не содержал регулярных выражений, вы могли бы написать свои парсер и интерпретатор для такого суб-языка.
Или представьте, что вы строите гигантского робота-динозавра и вам нужно запрограммировать его поведение. JavaScript – не самый эффективный способ сделать это. Можно вместо этого выбрать язык примерно такого свойства:
behavior walk
perform when
destination ahead
actions
move left-foot
move right-foot
behavior attack
perform when
Godzilla in-view
actions
fire laser-eyes
launch arm-rockets
Обычно это называют языком для выбранной области (domain-specific language) – язык, специально предназначенный для работы в узком направлении. Такой язык может быть более выразительным, чем язык общего назначения, потому что он разработан для выражения именно тех вещей, которые надо выразить в этой области – и больше ничего.
Упражнения
Массивы
Добавьте поддержку массивов в Egg. Для этого добавьте три функции в основную область видимости: array(...)
для создания массива, содержащего значения аргументов, length(array)
для возврата длины массива и element(array, n)
для возврата n-ного элемента.
// Добавьте кода
topEnv["array"] = "...";
topEnv["length"] = "...";
topEnv["element"] = "...";
run("do(define(sum, fun(array,",
" do(define(i, 0),",
" define(sum, 0),",
" while(<(i, length(array)),",
" do(define(sum, +(sum, element(array, i))),",
" define(i, +(i, 1)))),",
" sum))),",
" print(sum(array(1, 2, 3))))");
// → 6
Замыкания
Способ определения fun
позволяет функциям в Egg замыкаться вокруг окружения, и использовать локальные переменные в теле функции, которые видны во время определения, точно как в функциях JavaScript.
Следующая программа иллюстрирует это: функция f
возвращает функцию, добавляющую её аргумент к аргументу f
, то есть, ей нужен доступ к локальной области видимости внутри f
для использования переменной a
.
run("do(define(f, fun(a, fun(b, +(a, b)))),",
" print(f(4)(5)))");
// → 9
Объясните, используя определение формы fun
, какой механизм позволяет этой конструкции работать.
Комментарии
Хорошо было бы иметь комментарии в Egg. К примеру, мы могли бы игнорировать оставшуюся часть строки, встречая символ \#
– так, как это происходит с //
в JavaScript.
Большие изменения в парсере делать не придётся. Мы просто поменяем skipSpace
, чтобы она пропускала комментарии, будто они являются пробелами – и во всех местах, где вызывается skipSpace
, комментарии тоже будут пропущены. Внесите это изменение.
// Поменяйте старую функцию
function skipSpace(string) {
var first = string.search(/\S/);
if (first == -1) return "";
return string.slice(first);
}
console.log(parse("# hello\nx"));
// → {type: "word", name: "x"}
console.log(parse("a # one\n # two\n()"));
// → {type: "apply",
// operator: {type: "word", name: "a"},
// args: []}
Чиним область видимости
Сейчас мы можем присвоить переменной значение только через define
. Эта конструкция работает как при присвоении старым переменным, так и при создании новых.
Эта неоднозначность приводит к проблемам. Если вы пытаетесь присвоить новое значение нелокальной переменной, вместо этого вы определяете локальную с таким же именем. (Некоторые языки так и делают, но мне это всегда казалось дурацким способом работы с областью видимости).
Добавьте форму set
, схожую с define
, которая присваивает переменной новое значение, обновляя переменную во внешней области видимости, если она не задана в локальной. Если переменная вообще не задана, швыряйте ReferenceError
(ещё один стандартный тип ошибки).
Техника представления областей видимости в виде простых объектов, до сего момента бывшая удобной, теперь будет вам мешать. Вам может понадобиться функция Object.getPrototypeOf
, возвращающая прототип объекта. Также помните, что область видимости не наследуется от Object.prototype
, поэтому если вам надо вызвать на них hasOwnProperty
, придётся использовать такую неуклюжую конструкцию: