Категории
Самые читаемые
onlinekniga.com » Компьютеры и Интернет » Программирование » Выразительный JavaScript - Марейн Хавербеке

Выразительный JavaScript - Марейн Хавербеке

Читать онлайн Выразительный JavaScript - Марейн Хавербеке

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 36 37 38 39 40 41 42 43 44 ... 79
Перейти на страницу:

Для поддержки простых арифметических операторов и сравнения мы добавим несколько функций в окружение. Для упрощения кода мы будем использовать 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

    .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, в некоторых ситуациях написание своего языка помогает быстрее сделать работу.

Такой язык не обязан напоминать обычный ЯП. Если бы JavaScript не содержал регулярных выражений, вы могли бы написать свои парсер и интерпретатор для такого суб-языка.

Или представьте, что вы строите гигантского робота-динозавра и вам нужно запрограммировать его поведение. JavaScript – не самый эффективный способ сделать это. Можно вместо этого выбрать язык примерно такого свойства:

behavior walk

  perform when

    destination ahead

  actions

    move left-foot

    move right-foot

behavior attack

  perform when

    Godzilla in-view

  actions

    fire laser-eyes

    launch arm-rockets

Обычно это называют языком для выбранной области (domain-specific language) – язык, специально предназначенный для работы в узком направлении. Такой язык может быть более выразительным, чем язык общего назначения, потому что он разработан для выражения именно тех вещей, которые надо выразить в этой области – и больше ничего.

Упражнения

Массивы

Добавьте поддержку массивов в Egg. Для этого добавьте три функции в основную область видимости: array(...) для создания массива, содержащего значения аргументов, length(array) для возврата длины массива и element(array, n) для возврата n-ного элемента.

// Добавьте кода

topEnv["array"] = "...";

topEnv["length"] = "...";

topEnv["element"] = "...";

run("do(define(sum, fun(array,",

    "     do(define(i, 0),",

    "        define(sum, 0),",

    "        while(<(i, length(array)),",

    "          do(define(sum, +(sum, element(array, i))),",

    "             define(i, +(i, 1)))),",

    "        sum))),",

    "   print(sum(array(1, 2, 3))))");

// → 6

Замыкания

Способ определения fun позволяет функциям в Egg замыкаться вокруг окружения, и использовать локальные переменные в теле функции, которые видны во время определения, точно как в функциях JavaScript.

Следующая программа иллюстрирует это: функция f возвращает функцию, добавляющую её аргумент к аргументу f, то есть, ей нужен доступ к локальной области видимости внутри f для использования переменной a.

run("do(define(f, fun(a, fun(b, +(a, b)))),",

    "   print(f(4)(5)))");

// → 9

Объясните, используя определение формы fun, какой механизм позволяет этой конструкции работать.

Комментарии

Хорошо было бы иметь комментарии в Egg. К примеру, мы могли бы игнорировать оставшуюся часть строки, встречая символ # – так, как это происходит с // в JavaScript.

Большие изменения в парсере делать не придётся. Мы просто поменяем skipSpace, чтобы она пропускала комментарии, будто они являются пробелами – и во всех местах, где вызывается skipSpace, комментарии тоже будут пропущены. Внесите это изменение.

// Поменяйте старую функцию

function skipSpace(string) {

  var first = string.search(/S/);

  if (first == -1) return "";

  return string.slice(first);

}

console.log(parse("# hellonx"));

1 ... 36 37 38 39 40 41 42 43 44 ... 79
Перейти на страницу:
На этой странице вы можете бесплатно читать книгу Выразительный JavaScript - Марейн Хавербеке.
Комментарии