> if (skipSpace(result.rest).length > 0)
> throw new SyntaxError("Неожиданный текст после программы");
> return result.expr;
>}
>console.log(parse("+(a, 10)"));
>// → {type: "apply",
>// operator: {type: "word", name: "+"},
>// 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
.
Рекурсивная структура интерпретатора напоминает парсер. Оба отражают структуру языка. Можно было бы интегрировать парсер в интерпретатор и интерпретировать во время разбора, но их разделение делает программу более читаемой.