Спецификация
Спецификация языка ECMAScript® (живой стандарт) описывает всё, что касается JavaScript, чтобы каждый мог реализовать свой движок.
Для нашего парсера важны главы:
- Глава 5: условные обозначения
- Глава 11: исходный текст ECMAScript
- Глава 12: лексическая грамматика ECMAScript
- Главы 13–16: выражения, операторы, функции, классы, скрипты и модули
- Приложение B: дополнительные возможности для веб-браузеров
- Приложение C: строгий режим ECMAScript
Навигация по тексту спецификации:
- По кликабельным элементам можно получить постоянную ссылку — якорь в URL, например
#sec-identifiers - При наведении бывают подсказки; вкладка «References» показывает все ссылки
Условные обозначения
Читать нужно раздел 5.1.5 Grammar Notation.
На что обратить внимание:
Рекурсия
Так в грамматике задают списки.
ArgumentList :
AssignmentExpression
ArgumentList , AssignmentExpressionозначает
a, b = 1, c = 2
^_____________^ ArgumentList
^__________^ ArgumentList, AssignmentExpression,
^___^ AssignmentExpressionНеобязательность
Суффикс _opt_ — необязательный фрагмент. Например,
VariableDeclaration :
BindingIdentifier Initializer_optозначает
var binding_identifier;
var binding_identifier = Initializer;
______________ Initializer_optПараметры
[Return] и [In] — параметры грамматики.
Например,
ScriptBody :
StatementList[~Yield, ~Await, ~Return]значит, что на верхнем уровне скрипта yield, await и return запрещены, тогда как
ModuleItem :
ImportDeclaration
ExportDeclaration
StatementListItem[~Yield, +Await, ~Return]допускает top-level await.
Исходный текст
Раздел 11.2 Types of Source Code говорит о сильном различии между кодом скрипта и кодом модуля, а также о режиме use strict, который запрещает старые особенности поведения JS.
Код скрипта по умолчанию не строгий; use strict в начале файла делает скрипт строгим. В HTML: <script src="javascript.js"></script>.
Код модуля всегда строгий. В HTML: <script type="module" src="main.mjs"></script>.
Лексическая грамматика ECMAScript
Более развёрнуто — в блоге V8 Understanding the ECMAScript spec.
Automatic Semicolon Insertion
Здесь перечислены правила, когда точку с запятой можно опустить. Вся логика сводится к следующему:
pub fn asi(&mut self) -> Result<()> {
if self.eat(Kind::Semicolon) || self.can_insert_semicolon() {
return Ok(());
}
let range = self.prev_node_end..self.cur_token().start;
Err(SyntaxError::AutoSemicolonInsertion(range.into()))
}
pub const fn can_insert_semicolon(&self) -> bool {
self.cur_token().is_on_new_line || matches!(self.cur_kind(), Kind::RCurly | Kind::Eof)
}asi нужно явно вызывать там, где положено, например в конце оператора:
fn parse_debugger_statement(&mut self) -> Result<Statement<'a>> {
let node = self.start_node();
self.expect(Kind::Debugger)?;
self.asi()?;
self.ast.debugger_statement(self.finish_node(node))
}INFO
Этот раздел про ASI написан с прицелом на парсер: явно указано, что текст разбирается слева направо, то есть почти невозможно реализовать парсер «иначе». Автор jsparagus высказался об этом здесь.
The specification for this feature is both very-high-level and weirdly procedural («When, as the source text is parsed from left to right, a token is encountered...», as if the specification is telling a story about a browser. As far as I know, this is the only place in the spec where anything is assumed or implied about the internal implementation details of parsing.) But it would be hard to specify ASI any other way.
Выражения, операторы, функции, классы, скрипты и модули
Синтаксическую грамматику понимать нужно время, затем можно переносить её в код парсера.