Выразительный JavaScript (Хавербеке) - страница 118

>  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.

Рекурсивная структура интерпретатора напоминает парсер. Оба отражают структуру языка. Можно было бы интегрировать парсер в интерпретатор и интерпретировать во время разбора, но их разделение делает программу более читаемой.