Выбрать главу

Вот и всё, что нужно для интерпретации 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;

};

Ещё одна основная часть языка – do, выполняющий все аргументы сверху вниз. Его значение – это значение, выдаваемое последним аргументом.

specialForms["do"] = function(args, env) {

  var value = false;

  args.forEach(function(arg) {

    value = evaluate(arg, env);

  });

  return value;

};

Чтобы создавать переменные и давать им значения, мы создаём форму define. Она ожидает word в качестве первого аргумента, и выражение, производящее значение, которое надо присвоить этому слову в качестве второго. define, как и всё, является выражением, поэтому оно должно возвращать значение. Пусть оно возвращает присвоенное значение (прямо как оператор = в JavaScript).

specialForms["define"] = function(args, env) {

  if (args.length != 2 || args[0].type != "word")

    throw new SyntaxError("Bad use of define");

  var value = evaluate(args[1], env);

  env[args[0].name] = value;

  return value;

};

Окружение

Окружение, принимаемое интерпретатором — это объект со свойствами, чьи имена соответствуют именам переменных, а значения – значениям этих переменных. Давайте определим объект окружения, представляющий глобальную область видимости.

Для использования конструкции if мы должны создать булевские значения. Так как их всего два, особый синтаксис для них не нужен. Мы просто делаем две переменные со значениями true и false.

var topEnv = Object.create(null);

topEnv["true"] = true;

topEnv["false"] = false;

Теперь мы можем вычислить простое выражение, меняющее булевское значение на обратное.

var prog = parse("if(true, false, true)");

console.log(evaluate(prog, topEnv)); // → false

Для поддержки простых арифметических операторов и сравнения мы добавим несколько функций в окружение. Для упрощения кода мы будем использовать new Function для создания набора функций-операторов в цикле, а не определять их все по отдельности.

["+", "-", "*", "/", "==", "<", ">"].forEach(function(op) {

  topEnv[op] = new Function("a, b", "return a " + op + " b;");

});

Также пригодится способ вывода значений, так что мы обернём console.log в функцию и назовём её print.

topEnv["print"] = function(value) {

  console.log(value);

  return value;

};

Это даёт нам достаточно элементарных инструментов для написания простых программ. Следующая функция run даёт удобный способ записи и запуска. Она создаёт свежее окружение, парсит и разбирает строчки, которые мы ей передаём, так, как будто они являются одной программой.

function run() {

  var env = Object.create(topEnv);

  var program = Array.prototype.slice