// args: [{type: "word", name: "a"},
// {type: "value", value: 10}]}
Работает! Она не выдаёт полезной информации при ошибке, и не хранит номера строки и столбца, с которых начинается каждое выражение, что могло бы пригодиться при разборе ошибок – но для нас и этого хватит.
Интерпретатор
А что нам делать с синтаксическим деревом программы? Запускать её! Этим занимается интерпретатор. Вы даёте ему синтаксическое дерево и объект окружения, который связывает имена со значениями, а он интерпретирует выражение, представляемое деревом, и возвращает результат.
function evaluate(expr, env) {
switch(expr.type) {
case "value":
return expr.value;
case "word":
if (expr.name in env)
return env[expr.name];
else
throw new ReferenceError("Неопределённая переменная: "
expr.name);
case "apply":
if (expr.operator.type == "word" &&
expr.operator.name in specialForms)
return specialForms[expr.operator.name](expr.args,
env);
var op = evaluate(expr.operator, env);
if (typeof op != "function")
throw new TypeError("Приложение не является функцией.");
return op.apply(null, expr.args.map(function(arg) {
return evaluate(arg, env);
}));
}
}
var specialForms = Object.create(null);
У интерпретатора есть код для каждого из типов выражений. Для литералов он возвращает их значение. Например, выражение 100
интерпретируется в число 100. У переменной мы должны проверить, определена ли она в окружении, и если да – запросить её значение.
С приложениями сложнее. Если это особая форма типа if
, мы ничего не интерпретируем, а просто передаём аргументы вместе с окружением в функцию, обрабатывающую форму. Если это простой вызов, мы интерпретируем оператор, проверяем, что это функция и вызываем его с результатом интерпретации аргументов.
Для представления значений функций Egg мы будем использовать простые значения функций JavaScript. Мы вернёмся к этому позже, когда определим специальную форму fun
.
Рекурсивная структура интерпретатора напоминает парсер. Оба отражают структуру языка. Можно было бы интегрировать парсер в интерпретатор и интерпретировать во время разбора, но их разделение делает программу более читаемой.
Вот и всё, что нужно для интерпретации Egg. Вот так просто. Но без определения нескольких специальных форм и добавления полезных значений в окружение, вы с этим языком ничего не сможете сделать.
Специальные формы
Объект specialForms
используется для определения особого синтаксиса Egg. Он сопоставляет слова с функциями, интерпретирующими эти специальные формы. Пока он пуст. Давайте добавим несколько форм.
specialForms["if"] = function(args, env) {
if (args.length != 3)
throw new SyntaxError("Неправильное количество аргументов для if");
if (evaluate(args[0], env) !== false)
return evaluate(args[1], env);
else
return evaluate(args[2], env);
};
Конструкция if
языка Egg ждёт три аргумента. Она вычисляет первый, и если результат не false
, вычисляет второй. В ином случае вычисляет третий. Этот if
больше похож на тернарный оператор ?:
. Это выражение, а не инструкция, и она выдаёт значение, а именно, результат второго или третьего выражения.
Egg отличается от JavaScript тем, как он обрабатывает условие if
. Он не будет считать ноль или пустую строку за false
.
if
представлено в виде особой формы а не обычной функции, потому что аргументы функций вычисляются перед вызовом, а if
должен интерпретировать один из двух аргументов – второй или третий, в зависимости от значения первого.
Форма для while
схожая.
specialForms["while"] = function(args, env) {
if (args.length != 2)
throw new SyntaxError("Неправильное количество аргументов для while");
while (evaluate(args[0], env) !== false)
evaluate(args[1], env);
// Поскольку undefined не задано в Egg,
// за отсутствием осмысленного результата возвращаем false
return false;
};