ECMAScript | Синтаксические и лексические грамматики

ECMAScript | Синтаксические и лексические грамматики

Контекстно-свободные грамматики (5.1.1 Context-Free Grammars)

«Контекстно-свободная грамматика» (context-free grammar) состоит из ряда «производств» (productions). Каждое производство имеет абстрактный символ, называемый «нетерминальным» (nonterminal), «в левой части» (left-hand side) и последовательность из нуля или более нетерминальных и «терминальных» (terminal) символов в правой части. Для каждой грамматики терминальные символы взяты из определенного алфавита.

«Цепное производство» (chain production) — это производство, в правой части которого есть ровно один нетерминальный символ, а также ноль или более оконечных символов.

Начиная с предложения, состоящего из одного выделенного нетерминального символа, называемого «целевым символом» (goal symbol), данная контекстно-свободная грамматика определяет «язык» (language), а именно (возможно, бесконечный) набор возможных последовательностей терминальных символов, которые могут возникнуть в результате многократной замены любого нетерминального в последовательность с правой частью производства, для которой нетерминал является левой частью.

 

Лексическая грамматика и грамматика RegExp (5.1.2 The Lexical and RegExp Grammars)

«Лексическая грамматика» (lexical grammar) для ECMAScript приведена в разделе 12. Эта грамматика имеет в качестве оконечных символов кодовые точки Unicode, которые соответствуют правилам для SourceCharacter, определенным в разделе 11.1. Она определяет набор производств, начиная с символа цели InputElementDiv, InputElementTemplateTail, или InputElementRegExp, или InputElementRegExpOrTemplateTail, которые описывают, как последовательности таких кодовых точек преобразуются в последовательность входных элементов.

Элементы ввода, отличные от пробелов и комментариев, образуют терминальные символы для синтаксической грамматики для ECMAScript и называются «токенами» (tokens) ECMAScript. Эти токены являются зарезервированными словами, идентификаторами, литералами и пунктуаторами языка ECMAScript. Более того, терминаторы строки, хотя и не считаются маркерами, также становятся частью потока элементов ввода и направляют процесс автоматической вставки точки с запятой (раздел 12.9). Простые пробелы и однострочные комментарии отбрасываются и не отображаются в потоке входных элементов синтаксической грамматики. MultiLineComment (то есть комментарий формы / *… * / независимо от того, занимает ли он более одной строки) также просто отбрасывается, если он не содержит признака конца строки; но если MultiLineComment содержит один или несколько ограничителей строки, то он заменяется одним ограничителем строки, который становится частью потока входных элементов для синтаксической грамматики.

Грамматика регулярных выражений RegExp для ECMAScript приведена в разделе 22.2.1. Эта грамматика также имеет в качестве терминальных символов кодовые точки, как определено SourceCharacter. Он определяет набор производств, начиная с «шаблона» (Pattern) символа цели, который описывает, как последовательности кодовых точек преобразуются в шаблоны регулярных выражений.

Продукция лексической грамматики и грамматики RegExp отличается наличием двух двоеточий «::» в качестве разделительных знаков препинания. Лексическая грамматика и грамматика RegExp имеют несколько общих производств.

 

Грамматика числовой строки (5.1.3 The Numeric String Grammar)

Другая грамматика используется для перевода строк в числовые значения. Эта грамматика подобна той части лексической грамматики, которая связана с числовыми литералами и имеет в качестве терминальных символов SourceCharacter. Эта грамматика появляется в разделе 7.1.4.1.

Грамматика числовых строк отличается наличием трех двоеточий «:::» в качестве знаков препинания.

 

Синтаксическая грамматика (5.1.4 The Syntactic Grammar)

Синтаксическая грамматика для ECMAScript дается в разделах с 13 по 16. В этой грамматике есть токены ECMAScript, определенные лексической грамматикой в качестве конечных символов (раздел 5.1.2). Он определяет набор производств, начиная с двух альтернативных символов цели «сценария» Script и «модуля» Module, которые описывают, как последовательности токенов образуют синтаксически правильные независимые компоненты программ ECMAScript.

Когда поток кодовых точек должен быть проанализирован как сценарий или модуль ECMAScript (Script или Module), он сначала преобразуется в поток входных элементов путем повторного применения лексической грамматики; этот поток входных элементов затем анализируется с помощью одного приложения синтаксической грамматики. Входной поток синтаксически ошибочен, если токены в потоке входных элементов не могут быть проанализированы как единственный экземпляр целевого нетерминала (Script или Module) без каких-либо оставшихся токенов.

Когда синтаксический анализ успешен, он строит «дерево синтаксического анализа» (parse tree), корневую древовидную структуру, в которой каждый узел является «узлом синтаксического анализа» (Parse Node). Каждый узел синтаксического анализа является «экземпляром» (instance) символа в грамматике; он представляет собой диапазон исходного текста, который может быть получен из этого символа. Корневой узел дерева синтаксического анализа, представляющий весь исходный текст, является экземпляром символа цели синтаксического анализа. Когда узел синтаксического анализа является экземпляром нетерминала, он также является экземпляром некоторого продукта, который имеет этот нетерминал в левой части. Более того, у него ноль или более «детей» (children), по одному для каждого символа в правой части производства: каждый ребёнок является узлом синтаксического анализа, который является экземпляром соответствующего символа.

Новые Узлы Синтаксического Анализа создаются для каждого вызова анализатора и никогда не используются повторно между синтаксическими анализами даже идентичного исходного текста. Узлы синтаксического анализа считаются «одним и тем же узлом синтаксического анализа» (same Parse Node) тогда и только тогда, когда они представляют один и тот же диапазон исходного текста, являются экземплярами одного и того же грамматического символа и являются результатом одного и того же вызова синтаксического анализатора.

Примечание 1

Многократный синтаксический анализ одной и той же строки приведет к разным узлам синтаксического анализа. Например, рассмотрите:

let str = «1 + 1;»;
eval(str);
eval(str);

Каждый вызов eval преобразует значение str в исходный текст ECMAScript и выполняет независимый синтаксический анализ, который создает собственное отдельное дерево узлов синтаксического анализа. Деревья различны, даже если каждый синтаксический анализ работает с исходным текстом, полученным из одного и того же значения String.

Примечание 2

Узлы синтаксического анализа являются артефактами спецификации, и реализации не требуют использования аналогичной структуры данных.

 

Производство синтаксической грамматики отличается наличием только одного двоеточия «:» в качестве знака препинания.

Синтаксическая грамматика, представленная в разделах с 13 по 16, не является полным отчетом о том, какие последовательности токенов принимаются в качестве правильного сценария или модуля (Script или Module) ECMAScript. Также принимаются некоторые дополнительные последовательности токенов, а именно те, которые были бы описаны грамматикой, если бы в последовательность были добавлены только точки с запятой в определенных местах (например, перед символами конца строки). Более того, определенные последовательности токенов, описываемые грамматикой, не считаются приемлемыми, если символ конца строки появляется в определенных «неудобных» местах.

В некоторых случаях, чтобы избежать двусмысленности, синтаксическая грамматика использует обобщенные конструкции, которые разрешают последовательности токенов, которые не образуют допустимый скрипт или модуль (Script или Module) ECMAScript. Например, этот метод используется для объектных литералов и шаблонов деструктуризации объектов. В таких случаях предоставляется более строгая дополнительная грамматика, которая дополнительно ограничивает допустимые последовательности токенов. Как правило, правило ранней ошибки затем определяет состояние ошибки, если «P не покрывает (covering) N», где P — это узел синтаксического анализа (экземпляр обобщенной продукции), а N — нетерминал из дополнительной грамматики. Здесь последовательность токенов, изначально совпадающих с P, снова анализируется с использованием N в качестве символа цели. (Если N принимает грамматические параметры, то им присваиваются те же значения, которые использовались при первоначальном анализе P.) Ошибка возникает, если последовательность токенов не может быть проанализирована как один экземпляр N без остатка токенов. Впоследствии алгоритмы обращаются к результату синтаксического анализа, используя фразу формы «N, которая покрывается P». Это всегда будет узел синтаксического анализа (экземпляр N, уникальный для данного P), поскольку любой сбой синтаксического анализа был бы обнаружен правилом ранней ошибки.

 

Грамматические обозначения (5.1.5 Grammar Notation)

Терминальные символы отображаются шрифтом фиксированной ширины (fixed width) как в грамматиках, так и в данной спецификации, когда текст напрямую ссылается на такой терминальный символ. Они должны появиться в сценарии точно так, как написано. Все указанные таким образом кодовые точки оконечных символов следует понимать как соответствующие кодовые точки Unicode из диапазона Basic Latin, в отличие от любых похожих на вид кодовых точек из других диапазонов Unicode. Кодовая точка в терминальном символе не может быть выражена с помощью обратной косой линии \ UnicodeEscapeSequence.

Нетерминальные символы выделены курсивом (italic). Определение нетерминала (также называемого «производство»“production”) вводится именем определяемого нетерминала, за которым следует одно или несколько двоеточий — :, ::, :::. (Число двоеточий указывает, к какой грамматике принадлежит «производство».) Одна или несколько альтернативных правых частей нетерминала следуют в следующих строках.

Например, синтаксическое определение оператора «while«:

WhileStatement :

while ( Expression ) Statement

утверждает, что нетерминальный объект WhileStatement представляет токен while, за которым следует токен ( левой круглой скобки, за которым следует выражение Expression, за которым следует токен ) правой круглой скобки, за которым следует оператор Statement. Вхождения выражения Expression и оператора Statement сами по себе нетерминалы.

 

Другой пример — синтаксическое определение «списка аргументов»:

ArgumentList :

AssignmentExpression

ArgumentList , AssignmentExpression

заявляет, что «список аргументов» ArgumentList может представлять либо одно «выражение присвоения» AssignmentExpression, либо другой «список аргументов» ArgumentList, за которым следует «запятая»(comma ,), за которой следует «выражение присвоения» AssignmentExpression. Это определение ArgumentList является рекурсивным, то есть определяется в терминах самого себя. В результате ArgumentList может содержать любое положительное количество аргументов, разделенных запятыми, где каждое выражение аргумента является «выражением присвоения» AssignmentExpression. Такие рекурсивные определения нетерминалов широко распространены.

 

Нижний индекс «opt«, который может стоять после терминала или нетерминала, указывает на необязательный символ. Альтернатива, содержащая необязательный символ, на самом деле определяет две правые стороны: одна без необязательного элемента, а другая включает его. Это означает, что:

VariableDeclaration :

BindingIdentifier Initializeropt

удобное сокращение для:

VariableDeclaration :

BindingIdentifier

BindingIdentifier Initializer

 

Ещё пример грамматического обозначения (синтаксического определения) для оператора «for«:

ForStatement :

for ( LexicalDeclaration Expressionopt ; Expressionopt ) Statement

удобное сокращение для:

ForStatement :

for ( LexicalDeclaration ; Expressionopt ) Statement

for ( LexicalDeclaration Expression ; Expressionopt ) Statement

что, в свою очередь, является сокращением для:

ForStatement :

for ( LexicalDeclaration ; ) Statement

for ( LexicalDeclaration ; Expression ) Statement

for ( LexicalDeclaration Expression ; ) Statement

for ( LexicalDeclaration Expression ; Expression ) Statement

Итак, в этом примере нетерминальный оператор for ForStatement фактически имеет четыре альтернативные правые части.

 

Производство может быть параметризовано с помощью подписанной аннотации формы «[parameters]«, которая может появляться как суффикс к нетерминальному символу, определенному производством. «Параметры» «[parameters]» могут быть либо одним именем, либо списком имен, разделенных запятыми. Параметризованное производство — это сокращение для набора производств, определяющих все комбинации имен параметров, которым предшествует подчеркивание, добавленное к параметризованному нетерминальному символу.

 

Это означает, что:

StatementList[Return] :

ReturnStatement

ExpressionStatement

удобное сокращение для:

StatementList :

ReturnStatement

ExpressionStatement

StatementList_Return :

ReturnStatement

ExpressionStatement

 

Ещё вариант обозначения

StatementList[Return, In] :

ReturnStatement

ExpressionStatement

это сокращение от:

StatementList :

ReturnStatement

ExpressionStatement

StatementList_Return :

ReturnStatement

ExpressionStatement

StatementList_In :

ReturnStatement

ExpressionStatement

StatementList_Return_In :

ReturnStatement

ExpressionStatement

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

 

Ссылки на нетерминалы в правой части продукции также могут быть параметризованы. Например:

StatementList :

ReturnStatement

ExpressionStatement[+In]

эквивалентно высказыванию:

StatementList :

ReturnStatement

ExpressionStatement_In

и:

StatementList :

ReturnStatement

ExpressionStatement[~In]

эквивалентно:

StatementList :

ReturnStatement

ExpressionStatement

 

Нетерминальная ссылка может иметь как список параметров, так и суффикс «opt«. Например в объявлении переменной:

VariableDeclaration :

BindingIdentifier Initializer [+In] opt

это сокращение от:

VariableDeclaration :

BindingIdentifier

BindingIdentifier Initializer_In

 

 

Добавление к имени параметра префикса «?» с правой стороны нетерминальная ссылка делает это значение параметра зависимым от появления имени параметра в ссылке на левый символ текущего производства.

Например:

VariableDeclaration[In] :

BindingIdentifier Initializer[?In]

это сокращение от:

VariableDeclaration :

BindingIdentifier Initializer

VariableDeclaration_In :

BindingIdentifier Initializer_In

 

Если альтернатива с правой стороны имеет префикс «[+parameter]», эта альтернатива доступна только в том случае, если именованный параметр использовался для ссылки на нетерминальный символ продукции.

Если альтернатива с правой стороны имеет префикс «[~parameter]», эта альтернатива доступна только в том случае, если именованный параметр не использовался для ссылки на нетерминальный символ продукции.

Это означает, что:

StatementList[Return] :

[+Return]ReturnStatement

ExpressionStatement

это сокращение от:

StatementList :

ExpressionStatement

StatementList_Return :

ReturnStatement

ExpressionStatement

и что:

StatementList[Return] :

[~Return]ReturnStatement

ExpressionStatement

это сокращение от:

StatementList :

ReturnStatement

ExpressionStatement

StatementList_Return :

ExpressionStatement

 

Когда слова «один из» (one of) следуют за двоеточием в определении грамматики, они означают, что каждый из конечных символов в следующей строке или строках является альтернативным определением.

Например, лексическая грамматика для ECMAScript содержит продукцию:

NonZeroDigit :: one of

1 2 3 4 5 6 7 8 9

что просто удобное сокращение для:

NonZeroDigit ::

1
2
3
4
5
6
7
8
9

 

Если фраза «[пусто]» ([empty]) появляется в правой части производства, это означает, что правая часть производства не содержит терминалов или нетерминалов.

 

Если фраза «[lookahead = seq]» появляется в правой части производства, это указывает на то, что производство может использоваться только в том случае, если последовательность маркеров seq является префиксом непосредственно следующей за входной последовательностью маркеров. Точно так же «[lookahead ∈ set]», где set — это конечный непустой набор последовательностей токенов, указывает, что производство может использоваться только в том случае, если некоторый элемент множества является префиксом непосредственно следующей последовательности токенов. Для удобства набор также может быть записан как нетерминал, и в этом случае он представляет собой набор всех последовательностей токенов, до которых этот нетерминал может расширяться. Считается редакционной ошибкой, если нетерминал может расширяться до бесконечного числа различных последовательностей токенов.

 

Эти условия могут быть отменены. «[Lookahead ≠ seq]» указывает, что содержимое производства может использоваться только в том случае, если seq не является префиксом непосредственно следующей за входной последовательностью токенов, и «[lookahead ∉ set]» указывает, что производство может использоваться, только если НЕ элемент set является префиксом непосредственно следующей за последовательностью токенов.

В качестве примера приведем определения:

DecimalDigit :: one of

0 1 2 3 4 5 6 7 8 9

DecimalDigits ::

DecimalDigit

DecimalDigits DecimalDigit

определение:

LookaheadExample ::

n [lookahead ∉ { 1, 3, 5, 7, 9 }] DecimalDigits

DecimalDigit [lookahead ∉ DecimalDigit]

соответствует либо букве n, за которой следует одна или несколько десятичных цифр, первая из которых четная, либо десятичной цифре, за которой не следует другая десятичная цифра.

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

Если фраза «[здесь нет LineTerminator]» появляется в правой части синтаксической грамматики, это указывает на то, что производство является «производством с ограничениями»(restricted production): ее нельзя использовать, если LineTerminator встречается во входном потоке в указанное положение. Например, производство:

ThrowStatement :

throw [no LineTerminator here] Expression ;

указывает, что продукция может не быть использована, если в сценарии между токеном выброса throw и выражением Expression встречается LineTerminator.

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

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

Правая часть производства может указывать, что определенные расширения не разрешены, используя фразу «но не» (“but not”) и затем указывая расширения, которые должны быть исключены.

Например, производство:

Identifier ::

IdentifierName but not ReservedWord

означает, что нетерминальный идентификатор Identifier может быть заменен любой последовательностью кодовых точек, которая может заменить IdentifierName, при условии, что та же последовательность кодовых точек не может заменить ReservedWord.

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

SourceCharacter ::

any Unicode code point

 

Информационные ссылки

ECMAScript | Условные обозначения

Стандарт ECMAScript — Раздел «5.1 Syntactic and Lexical Grammars» — https://tc39.es/ecma262/#sec-syntactic-and-lexical-grammars