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

Для упрощения парсинга строки в Egg не будут поддерживать обратных слэшей и подобных вещей. Строка – просто последовательность символов, не являющихся двойными кавычками, заключённая в двойные кавычки. Число – последовательность цифр. Имена переменных могут состоять из любых символов, не являющихся пробелами и не имеющих специального значения в синтаксисе.

Приложения записываются так же, как в JavaScript — при помощи скобок после выражения и с любым количеством аргументов в скобках, разделённых запятыми.

do(define(x, 10),

   if(>(x, 5)),

      print("много"),

      print("мало"))

Однородность языка означает, что то, что в JavaScript является операторами (например, >), применяется так же, как и остальные функции. Так как в синтаксисе нет концепции блоков, нам нужна конструкция do для обозначения нескольких вещей, выполняемых последовательно.

Структура данных, описывающая программу, будет состоять из объектов выражений, у каждого из которых будет свойство type, отражающее тип этого выражения и другие свойства, описывающие содержимое.

Выражения типа “value” представляют строки или числа. Их свойство value содержит строку или число, которое они представляют. Выражения типа “word” используются для идентификаторов (имён). У таких объектов есть свойство name, содержащее имя идентификатора в виде строки. И наконец, выражения “apply” представляют приложения. У них есть свойство operator, ссылающееся на применяемое выражение, и свойство args с массивом аргументов.

Часть >(x, 5) будет представлена так:

{

  type: "apply",

  operator: {type: "word", name: ">"},

  args: [

    {type: "word", name: "x"},

    {type: "value", value: 5}

  ]

}

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

Структура синтаксического дерева

Сравните это с парсером, написанным нами для файла настроек в главе 9, у которого была простая структура: он делил ввод на строки и обрабатывал их одну за другой. Там было всего несколько форм, которые разрешено принимать строке.

Здесь нам нужен другой подход. Выражения не разделяются на строчки, и их структура рекурсивна. Выражения-приложения содержат другие выражения. К счастью, эта задача элегантно решается применением рекурсивной функции, отражающей рекурсивность языка.

Мы определяем функцию parseExpression, принимающую строку на вход и возвращающую объект, содержащий структуру данных для выражения с начала строки, вместе с частью строки, оставшейся после парсинга. При разборе подвыражений (таких, как аргумент приложения), эта функция снова вызывается, возвращая выражение аргумента вместе с оставшимся текстом. Тот текст может, в свою очередь, содержать ещё аргументы, или же быть закрывающей скобкой, завершающей список аргументов.

Первая часть парсера:

function parseExpression(program) {

  program = skipSpace(program);

  var match, expr;

  if (match = /^"([^"]*)"/.exec(program))

    expr = {type: "value", value: match[1]};

  else if (match = /^\d+\b/.exec(program))

    expr = {type: "value", value: Number(match[0])};

  else if (match = /^[^\s(),"]+/.exec(program))

    expr = {type: "word", name: match[0]};

  else

    throw new SyntaxError("Неожиданный синтаксис: " + program);

  return parseApply(expr, program.slice(match[0].length));

}

function skipSpace(string) {

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

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

  return string.slice(first);

}

Поскольку Egg разрешает любое количество пробелов в элементах, нам надо постоянно вырезать пробелы с начала строки. С этим справляется skipSpace.

Пропустив начальные пробелы, parseExpression использует три регулярки для распознавания трёх простых (атомарных) элементов, поддерживаемых языком: строк, чисел и слов. Парсер создаёт разные структуры для разных типов. Если ввод не подходит ни под одну из форм, это не является допустимым выражением, и он выбрасывает ошибку. SyntaxError – стандартный объект для ошибок, который создаётся при попытке запуска некорректной программы JavaScript.