Простейший парсер на Javascript

В этом разделе описывается язык грамматики nearley, на котором вы можете описать грамматики для анализа nearley. Грамматики обычно хранятся в файлах .ne. Затем вы можете использовать nearleyc для компиляции .ne грамматики для модулей JavaScript.

Вы можете найти много примеров грамматик nearley в Интернете, а также некоторые из них в каталоге examples/ репозитория Github.

Запас слов

Терминал — это одна постоянная строка или токен. Например, ключевое слово «if» — это терминал. Нетерминал описывает набор возможных строк. Например, все операторы “if” могут быть описаны одним нетерминалом, значение которого зависит от условия и тела оператора if.

Правило (или производственное правило) — это определение нетерминала. Например,

ifStatement -> «если» условие «, то» оператор «endif»

Это правило, в соответствии с которым анализируется нетерминальный оператор if, ifStatement. Это зависит от условия и оператора нетерминалов. Нетерминал может быть описан несколькими правилами. Например, мы можем добавить второе правило ifStatement -> «если» условие «, то» оператор «else» оператор «endif» для поддержки предложений “else”.

По умолчанию nearley пытается проанализировать первый нетерминал, определенный в грамматике. В следующей грамматике nearley попытается проанализировать входной текст как выражение.

expression -> number «+» number
expression -> number «-» number
expression -> number «*» number
expression -> number «/» number
number -> [0-9]:+

Вы можете использовать символ канала | для разделения альтернативных правил для нетерминала. В приведенном ниже примере выражение имеет четыре различных правила.

expression ->
number «+» number
| number «-» number
| number «*» number
| number «/» number

Ключевое слово null обозначает правило эпсилона, которое ничему не соответствует. Следующий нетерминал соответствует нулю или нескольким коровам подряд, например cowcowcow:

a ->null | a «корова»

Постпроцессоры

По умолчанию nearley обертывает все, что соответствует правилу, в массив. Например, когда правило -> «tick» «tock» соответствует строке «ticktock», оно создает “дерево разбора” [«tick», «tock»]. Однако в большинстве случаев вам необходимо каким-то образом обработать эти данные: например, вы можете отфильтровать пробелы или преобразовать результаты в пользовательский объект JavaScript.

Для этой цели каждое правило может иметь постпроцессор: функцию JavaScript, которая преобразует массив и возвращает “обработанную” версию результата. Постпроцессоры завернуты в {% %}s:

expression -> number «+» number {%
function(data) {
return {
operator: «sum»,
leftOperand: data[0],
rightOperand: data[2] // data[1] is «+»
};
}
%}

Приведенное выше правило разберет строку 5+10 на { оператор: «sum», leftOperand: 5, rightOperand: 10 }.

Постпроцессором может быть любая функция с функцией подписи(data, location, reject). Здесь,
data: Массив-это массив, содержащий результаты анализа каждой части правила. Обратите внимание, что это все еще массив, даже если правило имеет только одну часть! Вы можете использовать встроенный постпроцессор {% id %} для преобразования массива из одного элемента в сам элемент.

Для пользователей функции стрелки удобным шаблоном является разложение массива данных в аргументе функции стрелки:

expression ->
number «+» number {% ([fst, _, snd]) =>fst + snd %}
| number «-» number {% ([fst, _, snd]) =>fst — snd %}
| number «*» number {% ([fst, _, snd]) =>fst * snd %}
| number «/» number {% ([fst, _, snd]) =>fst / snd %}

location: число — это индекс (основанный на нуле), с которого начинается соответствие правилу. Это можно использовать для отображения местоположения выражения в сообщении об ошибке.

Примечание: Многие токенизаторы предоставляют информацию о строке, столбце и смещении в объекте токена. Если вы используете токенизатор, то лучше использовать эту информацию, чем переменную, предоставленную nearley, которая только сообщит вам, что она увидела n-й токен, а не n-й символ в строке.

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

Отклонение используется в некоторых крайних случаях. Например, предположим, что вы хотите, чтобы последовательности букв соответствовали переменным, за исключением ключевого слова if. В этом случае ваше правило может быть:

variable -> [a-z]:+ {%
function(d,l, reject) {
if (d[0] == ‘if’) {
return reject;
} else {
return {‘name’: d[0]};
}
}
%}

Предупреждение: Грамматики, использующие reject, не являются контекстно-свободными и часто гораздо медленнее анализируются. Поэтому мы рекомендуем вам не использовать reject без крайней необходимости. Обычно вместо этого вы можете использовать токенизатор.

nearley предоставляет один встроенный постпроцессор:

id возвращает первый элемент массива данных. Это полезно для извлечения содержимого одноэлементного массива: foo ->bar {% id %}

Больше синтаксиса: советы и рекомендации

Комментарии

Комментарии помечаются знаком»#’. Все от# до конца строки игнорируется:

выражение -> число «+» число # сумма двух чисел

Кодировки

Вы можете использовать допустимые кодировки регулярных выражений в правиле (если вы не используете токенизатор):

not_a_letter -> [^a-zA-Z]

. — символ может использоваться для представления любого символа.

Строковые литералы без учета регистра

Вы можете создать строковые литералы без учета регистра, добавив i после строкового литерала:

cow -> «корова»i # соответствует корове, КОРОВЕ и так далее.

Обратите внимание, что если вы используете лексер, ваш лексер должен использовать флаг i в своих регулярных выражениях. То есть, если вы используете лексер, вы не должны использовать суффикс i в nearley.

EBNF

nearley поддерживает операторы *,? и + из EBNF, как показано в примере:

batman -> «na»:* «batman» # nananana…nanabatman

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

banana -> «ba» («na» {% id %} | «NA» {% id %}):+

Макросы

Макросы позволяют создавать полиморфные правила:

# Matches «‘Hello?’ ‘Hello?’ ‘Hello?'»
matchThree[X] -> $X » » $X » » $X
inQuotes[X] -> «‘» $X «‘»

main ->matchThree[inQuotes[«Hello?»]]

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

# Matches «Cows oink.» and «Cows moo!»
sentence[ANIMAL, PUNCTUATION] ->animalGoes[(«moo» | «oink» | «baa»)] $PUNCTUATION
animalGoes[SOUND] -> $ANIMAL » » $SOUND # uses $ANIMAL from its caller

main ->sentence[«Cows», («.» | «!»)]

Макросы расширяются во время компиляции и вставляются в места, где они используются. Это не “настоящие” правила. Поэтому макросы не могут быть рекурсивными (nearleyc войдет в бесконечный цикл, пытаясь расширить макроцикл). Они также должны быть определены до их использования.

Дополнительные JS

Для более сложных постпроцессоров или любой другой функциональности, которая вам может понадобиться, вы можете включить фрагменты кода JavaScript между производственными правилами, окружив его @{% … %}:

@{%
constcowSays = require(«./cow.js»);
%}

cow -> «moo» {% ([moo]) =>cowSays(moo) %}

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

Импорт других грамматик

Вы можете включить содержимое других файлов грамматики:

@include «../misc/primitives.ne» # путь к компилируемому файлу
sum ->number «+» number # использует «number» из включенного файла

Есть некоторые общие нетерминалы, такие как “целое число” и “строка в двойных кавычках”, которые поставляются с nearley, чтобы помочь вам эффективно создавать прототипы грамматик. Вы можете включить их с помощью директивы @builtin:

@builtin «number.ne»
main ->int:+

Обратите внимание, что мы имеем в виду “эффективный” в том смысле, что вы можете настроить их очень быстро. Встроенные функции неэффективны в том смысле, что они замедляют работу вашего парсера. Для “реального” проекта вы хотели бы переключиться на лексер и реализовать эти примитивы самостоятельно!

Более подробную информацию см. в каталоге builtin/ на Github. Обратите внимание, что в том числе файл импортирует все нетерминалы, определенные в нем, а также любые JS, макросы и параметры конфигурации, определенные там.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *