.call(arguments, 0).join("\n");
return evaluate(parse(program), env);
}
Использование Array.prototype.slice.call
– уловка для превращения объекта, похожего на массив, такого как аргументы, в настоящий массив, чтобы мы могли применить к нему join
. Она принимает все аргументы, переданные в run
, и считает, что все они – строчки программы.
run("do(define(total, 0),",
" define(count, 1),",
" while(<(count, 11),",
" do(define(total, +(total, count)),",
" define(count, +(count, 1)))),",
" print(total))");
// → 55
Эту программу мы видели уже несколько раз – она подсчитывает сумму чисел от 1 до 10 на языке Egg. Она уродливее эквивалентной программы на JavaScript, но не так уж и плоха для языка, заданного менее чем 150 строчками кода.
Функции
Язык программирования без функций – плохой язык.
К счастью, несложно добавить конструкцию fun
, которая расценивает последний аргумент как тело функции, а все предыдущие – имена аргументов функции.
specialForms["fun"] = function(args, env) {
if (!args.length)
throw new SyntaxError("Функции нужно тело");
function name(expr) {
if (expr.type != "word")
throw new SyntaxError("Имена аргументов должны быть типа word");
return expr.name;
}
var argNames = args.slice(0, args.length - 1).map(name);
var body = args[args.length - 1];
return function() {
if (arguments.length != argNames.length)
throw new TypeError("Неверное количество аргументов");
var localEnv = Object.create(env);
for (var i = 0; i < arguments.length; i++)
localEnv[argNames[i]] = arguments[i];
return evaluate(body, localEnv);
};
};
У функций в Egg своё локальное окружение, как и в JavaScript. Мы используем Object.create
для создания нового объекта, имеющего доступ к переменным во внешнем окружении (своего прототипа), но он также может содержать новые переменные, не меняя внешней области видимости.
Функция, созданная формой fun
, создаёт своё локальное окружение и добавляет к нему переменные-аргументы. Затем она интерпретирует тело в этом окружении и возвращает результат.
run("do(define(plusOne, fun(a, +(a, 1))),",
" print(plusOne(10)))");
// → 11
run("do(define(pow, fun(base, exp,",
" if(==(exp, 0),",
" 1,",
" *(base, pow(base, -(exp, 1)))))),",
" print(pow(2, 10)))");
// → 1024
Компиляция
Мы с вами построили интерпретатор. Во время интерпретации он работает с представлением программы, созданным парсером.
Компиляция – добавление ещё одного шага между парсером и запуском программы, которая превращает в программу в нечто, что можно выполнять более эффективно, путём проделывания большинства работы заранее. К примеру, в хорошо организованных языках при каждом использовании переменной очевидно, к какой переменной обращаются, даже без запуска программы. Это можно использовать, чтобы не искать переменную по имени каждый раз, когда к ней обращаются, а напрямую вызывать её из какой-то заранее определённой области памяти.
По традиции компиляция также превращает программу в машинный код – сырой формат, пригодный для исполнения процессором. Но каждый процесс превращения программы в другой вид, по сути, является компиляцией.
Можно было бы создать другой интерпретатор Egg, который сначала превращает программу в программу на языке JavaScript, использует new Function
для вызова компилятора JavaScript и возвращает результат. При правильной реализации Egg выполнялся бы очень быстро при относительно простой реализации.
Если вам это интересно, и вы хотите потратить на это время, я поощряю вас попробовать сделать такой компилятор в качестве упражнения.
Мошенничество
Когда мы определяли if
и while
, вы могли заметить, что они представляли собой простые обёртки вокруг if
и while
в JavaScript. Значения в Egg – также обычные значения JavaScript.
Сравнивая реализацию Egg, построенную на JavaScript, с объёмом работы, необходимой для создания языка программирования непосредственно на машинном языке, то разница становится огромной. Тем не менее, этот пример, надеюсь, даёт вам представление о работе языков программирования.
И когда вам надо что-то сделать, смошенничать будет более эффективно, нежели делать всё с нуля самому. И хотя игрушечный язык ничем не лучше JavaScript, в некоторых ситуациях написание своего языка помогает быстрее сделать работу.