Files
vectorcraft/crates/js/src/parser.rs
T
dotCooCoo ea319ebcda JavaScript: identifiers follow the Unicode categories of section 7.6, and label and parameter checks are linear
is_id_start accepts the letter categories (Lu, Ll, Lt, Lm, Lo, Nl) and
is_id_part adds Mn, Mc, Nd and Pc, read with unicode-general-category.
A superscript digit (No) inside an identifier and a mark such as U+09BE
at its start are now SyntaxErrors, and identifiers with marks outside
the old list, such as the Bengali and Tamil viramas, now parse.

Labels in scope are kept in a map, and duplicate labels in a chain and
duplicate parameters of a strict function are found with a hash set, so
long chains, many labeled statements and long parameter lists are
checked in linear time.
2026-11-07 14:09:25 -08:00

1237 lines
51 KiB
Rust

//! The parser: tokens into a [`Program`] (ECMA-262 5.1 sections 11 to 14), with the early errors
//! of section 16 and of strict mode (Annex C).
//!
//! It is a recursive-descent parser whose recursion is counted: each statement, assignment
//! expression, primary expression and function body it enters adds one to a depth, and going past
//! [`crate::Options::max_depth`] gives a `SyntaxError`. Binary operators are read with an operator
//! stack, and prefix operators, assignment chains, `else if` chains and labels with loops, so long
//! runs of them do not nest at all.
use std::collections::{HashMap, HashSet};
use crate::LineTable;
use crate::ast::{
AssignOp, Ast, BinaryOp, CatchClause, Expr, ExprId, ForInTarget, ForInit, Function, FunctionKind, LogicalOp, Name, Program, Property,
PropertyKey, PropertyValue, Stmt, StmtId, SwitchCase, UnaryOp, UpdateOp, VarDecl,
};
use crate::error::{SyntaxError, TOO_DEEP};
use crate::lexer::{Goal, Lexer};
use crate::token::{Directive, Keyword, Punct, Span, Token, TokenKind, strict_reserved};
type Result<T> = std::result::Result<T, SyntaxError>;
/// What the code being parsed allows, saved and replaced at each function body.
#[derive(Clone, Debug, Default)]
struct Context {
strict: bool,
in_function: bool,
/// The labels in scope, each with whether it labels an iteration statement (`continue` may
/// name only those). Labels in scope have distinct names.
labels: HashMap<String, bool>,
/// Iteration statements open in the current function.
loops: u32,
/// Iteration and `switch` statements open in the current function.
breakables: u32,
}
/// A binary operator, with the logical ones apart.
#[derive(Clone, Copy, Debug)]
enum BinKind {
Binary(BinaryOp),
Logical(LogicalOp),
}
/// A prefix operator.
#[derive(Clone, Copy, Debug)]
enum Prefix {
Unary(UnaryOp),
Update(UpdateOp),
}
pub(crate) struct Parser<'s> {
src: &'s [u16],
lex: Lexer<'s>,
lines: LineTable,
tok: Token,
/// Where the last token read ends.
prev_end: u32,
ast: Ast,
depth: usize,
max_depth: usize,
ctx: Context,
directives: Vec<Directive>,
/// The tokens read, kept for [`crate::tokenize`].
tokens: Option<Vec<Token>>,
/// The next statement may be a function declaration outside strict mode code: it is an `if`
/// branch or the body of a label in a statement list (ECMA-262 2015 Annex B.3.2 and B.3.4).
/// Read and cleared as each statement starts.
function_allowed: bool,
/// The next statement stands in a statement list, where a label's body may be a function
/// declaration. Read and cleared as each statement starts.
in_list: bool,
}
impl<'s> Parser<'s> {
pub(crate) fn new(src: &'s [u16], options: &crate::Options, keep_tokens: bool) -> Result<Self> {
let lex = Lexer::new(src, options.preprocessor_lines, options.max_depth);
let placeholder =
Token { kind: TokenKind::Eof, span: Span::default(), line: 1, column: 1, newline_before: false, escaped: false, legacy_octal: false };
let mut parser = Self {
src,
lex,
lines: LineTable::new(src),
tok: placeholder,
prev_end: 0,
ast: Ast::default(),
depth: 0,
max_depth: options.max_depth,
ctx: Context { strict: options.strict, ..Context::default() },
directives: Vec::new(),
tokens: keep_tokens.then(Vec::new),
function_allowed: false,
in_list: false,
};
parser.tok = parser.next_significant()?;
Ok(parser)
}
pub(crate) fn parse_program(mut self) -> Result<(Program, Vec<Token>)> {
let body = self.parse_body(false)?;
if self.tok.kind != TokenKind::Eof {
return Err(self.unexpected());
}
if let Some(tokens) = &mut self.tokens {
tokens.push(self.tok.clone());
}
let program = Program { ast: self.ast, body, strict: self.ctx.strict, directives: self.directives, lines: self.lines };
Ok((program, self.tokens.unwrap_or_default()))
}
// ---- tokens ----
/// The next token that is not a preprocessor line; preprocessor lines are kept.
fn next_significant(&mut self) -> Result<Token> {
loop {
let token = self.lex.next_token(Goal::Div)?;
if let TokenKind::Directive(directive) = &token.kind {
self.directives.push(directive.clone());
if let Some(tokens) = &mut self.tokens {
tokens.push(token);
}
continue;
}
return Ok(token);
}
}
/// Reads the current token and moves to the next.
fn bump(&mut self) -> Result<Token> {
// Kept before reading on, so a preprocessor line after the token comes after it.
if let Some(tokens) = &mut self.tokens {
tokens.push(self.tok.clone());
}
let next = self.next_significant()?;
let token = std::mem::replace(&mut self.tok, next);
self.prev_end = token.span.end;
Ok(token)
}
/// The token after the current one, without reading it. `None` when the lexer cannot read one
/// (reading it for real reports the error).
fn peek(&self) -> Option<Token> {
let mut lex = self.lex.clone();
loop {
match lex.next_token(Goal::Div) {
Ok(Token { kind: TokenKind::Directive(_), .. }) => {}
Ok(token) => return Some(token),
Err(_) => return None,
}
}
}
fn at(&self, p: Punct) -> bool {
self.tok.is_punct(p)
}
fn at_keyword(&self, k: Keyword) -> bool {
self.tok.is_keyword(k)
}
fn eat(&mut self, p: Punct) -> Result<bool> {
if self.at(p) {
self.bump()?;
return Ok(true);
}
Ok(false)
}
fn expect(&mut self, p: Punct) -> Result<Token> {
if self.at(p) {
return self.bump();
}
Err(self.error_at_token(&self.tok, format!("expected '{}' but found {}", p.as_str(), describe(&self.tok))))
}
fn expect_keyword(&mut self, k: Keyword) -> Result<Token> {
if self.at_keyword(k) {
return self.bump();
}
Err(self.error_at_token(&self.tok, format!("expected '{}' but found {}", k.as_str(), describe(&self.tok))))
}
/// Ends a statement: a `;`, or one inserted before `}`, the end of the source or a token on a
/// new line (section 7.9).
fn semicolon(&mut self) -> Result<()> {
if self.eat(Punct::Semicolon)? || self.at(Punct::RBrace) || self.tok.kind == TokenKind::Eof || self.tok.newline_before {
return Ok(());
}
Err(self.unexpected())
}
// ---- errors ----
fn error_at_offset(&self, offset: u32, message: impl Into<String>) -> SyntaxError {
let (line, column) = self.lines.line_column(offset);
SyntaxError { message: message.into(), line, column, offset }
}
fn error_at_token(&self, token: &Token, message: impl Into<String>) -> SyntaxError {
SyntaxError { message: message.into(), line: token.line, column: token.column, offset: token.span.start }
}
fn unexpected(&self) -> SyntaxError {
self.error_at_token(&self.tok, format!("unexpected {}", describe(&self.tok)))
}
/// Counts one more level of nesting; past the limit, a `SyntaxError` at the current token.
fn enter(&mut self) -> Result<()> {
if self.depth >= self.max_depth {
return Err(self.error_at_token(&self.tok, TOO_DEEP));
}
self.depth += 1;
Ok(())
}
fn leave(&mut self) {
self.depth = self.depth.saturating_sub(1);
}
fn span_from(&self, start: Span) -> Span {
Span::new(start.start, self.prev_end.max(start.start))
}
// ---- names ----
/// Checks a name used as an identifier: not a reserved word written with escapes, and not a
/// strict-mode reserved word in strict code.
fn check_identifier(&self, token: &Token, name: &str) -> Result<()> {
if token.escaped && Keyword::from_word(name).is_some() {
return Err(self.error_at_token(token, format!("keyword '{name}' must not contain escaped characters")));
}
if self.ctx.strict && strict_reserved(name) {
return Err(self.error_at_token(token, format!("'{name}' is a reserved word in strict mode")));
}
Ok(())
}
/// Reads an identifier that a declaration binds: checked as [`Self::check_identifier`] does,
/// and in strict code neither `eval` nor `arguments`.
fn binding_identifier(&mut self) -> Result<Name> {
let TokenKind::Ident(name) = &self.tok.kind else {
return Err(self.error_at_token(&self.tok, format!("expected an identifier but found {}", describe(&self.tok))));
};
let name = name.clone();
self.check_identifier(&self.tok, &name)?;
if self.ctx.strict && is_eval_or_arguments(&name) {
return Err(self.error_at_token(&self.tok, format!("'{name}' cannot be declared in strict mode")));
}
let token = self.bump()?;
Ok(Name { name, span: token.span })
}
// ---- program and function bodies ----
/// Reads source elements up to `}` (in a function body) or the end of the source, reading the
/// directive prologue at their start (section 14.1). A `"use strict"` directive makes the rest
/// strict mode code.
fn parse_body(&mut self, in_function: bool) -> Result<Vec<StmtId>> {
let mut body = Vec::new();
let mut prologue = true;
// A string with an octal escape before a `"use strict"` directive in the same prologue.
let mut octal: Option<Token> = None;
while !(self.tok.kind == TokenKind::Eof || (in_function && self.at(Punct::RBrace))) {
if prologue && matches!(self.tok.kind, TokenKind::String(_)) {
let first = self.tok.clone();
let stmt = self.parse_statement_item()?;
body.push(stmt);
if !self.is_directive(stmt, &first) {
prologue = false;
continue;
}
if self.is_use_strict(&first) {
self.ctx.strict = true;
if let Some(octal) = &octal {
return Err(self.error_at_token(octal, "octal escape sequences are not allowed in strict mode"));
}
}
if first.legacy_octal && octal.is_none() {
octal = Some(first);
}
continue;
}
prologue = false;
body.push(self.parse_statement_item()?);
}
Ok(body)
}
/// Whether `stmt`, which started at `first`, is a directive: an expression statement made of
/// that one string literal.
fn is_directive(&self, stmt: StmtId, first: &Token) -> bool {
match self.ast.stmt(stmt) {
Some(Stmt::Expr(e)) => matches!(self.ast.expr(*e), Some(Expr::String(_))) && self.ast.expr_span(*e) == first.span,
_ => false,
}
}
/// The string token is exactly `"use strict"` or `'use strict'`, without escapes or line
/// continuations.
fn is_use_strict(&self, token: &Token) -> bool {
let raw = self.src.get(token.span.start as usize..token.span.end as usize).unwrap_or_default();
raw.len() == 12 && raw.get(1..11).is_some_and(|inner| inner.iter().copied().eq("use strict".encode_utf16()))
}
// ---- statements ----
/// A source element: a function declaration or a statement.
fn parse_statement_item(&mut self) -> Result<StmtId> {
if self.at_keyword(Keyword::Function) {
self.enter()?;
let result = self.parse_function_declaration();
self.leave();
return result;
}
self.in_list = true;
self.parse_statement()
}
fn parse_statement(&mut self) -> Result<StmtId> {
self.enter()?;
let result = self.statement();
self.leave();
result
}
/// A statement where Annex B lets sloppy code put a function declaration: an `if` branch.
fn parse_branch(&mut self) -> Result<StmtId> {
self.function_allowed = !self.ctx.strict;
self.parse_statement()
}
fn statement(&mut self) -> Result<StmtId> {
let start = self.tok.span;
let function_allowed = std::mem::take(&mut self.function_allowed);
let in_list = std::mem::take(&mut self.in_list);
match &self.tok.kind {
TokenKind::Keyword(Keyword::Function) => {
if !function_allowed {
let message = if self.ctx.strict {
"in strict mode code, functions can only be declared at top level or inside a block"
} else {
"a function declaration cannot stand here; put it in a block"
};
return Err(self.error_at_token(&self.tok, message));
}
self.parse_function_declaration()
}
TokenKind::Ident(_) if self.peek().is_some_and(|t| t.is_punct(Punct::Colon)) => self.parse_labeled(in_list),
_ => self.plain_statement(start),
}
}
fn plain_statement(&mut self, start: Span) -> Result<StmtId> {
match &self.tok.kind {
TokenKind::Punct(Punct::LBrace) => {
let body = self.parse_block()?;
Ok(self.ast.push_stmt(Stmt::Block(body), self.span_from(start)))
}
TokenKind::Punct(Punct::Semicolon) => {
self.bump()?;
Ok(self.ast.push_stmt(Stmt::Empty, start))
}
TokenKind::Keyword(k) => self.keyword_statement(*k, start),
_ => {
let expr = self.parse_expression(false)?;
self.semicolon()?;
Ok(self.ast.push_stmt(Stmt::Expr(expr), self.span_from(start)))
}
}
}
fn keyword_statement(&mut self, k: Keyword, start: Span) -> Result<StmtId> {
match k {
Keyword::Var => {
self.bump()?;
let decls = self.parse_var_decls(false)?;
self.semicolon()?;
Ok(self.ast.push_stmt(Stmt::Var(decls), self.span_from(start)))
}
Keyword::If => self.parse_if(),
Keyword::Do => self.parse_do_while(start),
Keyword::While => self.parse_while(start),
Keyword::For => self.parse_for(start),
Keyword::Continue | Keyword::Break => self.parse_jump(k == Keyword::Break, start),
Keyword::Return => self.parse_return(start),
Keyword::With => self.parse_with(start),
Keyword::Switch => self.parse_switch(start),
Keyword::Throw => self.parse_throw(start),
Keyword::Try => self.parse_try(start),
Keyword::Debugger => {
self.bump()?;
self.semicolon()?;
Ok(self.ast.push_stmt(Stmt::Debugger, self.span_from(start)))
}
_ => {
let expr = self.parse_expression(false)?;
self.semicolon()?;
Ok(self.ast.push_stmt(Stmt::Expr(expr), self.span_from(start)))
}
}
}
/// `{ … }`: its statements.
fn parse_block(&mut self) -> Result<Vec<StmtId>> {
self.expect(Punct::LBrace)?;
let mut body = Vec::new();
while !self.at(Punct::RBrace) {
if self.tok.kind == TokenKind::Eof {
return Err(self.unexpected());
}
body.push(self.parse_statement_item()?);
}
self.bump()?;
Ok(body)
}
/// `var` declarations after the `var`. `no_in` leaves `in` to a `for (… in …)` around them.
fn parse_var_decls(&mut self, no_in: bool) -> Result<Vec<VarDecl>> {
let mut decls = Vec::new();
loop {
let name = self.binding_identifier()?;
let init = if self.eat(Punct::Eq)? { Some(self.parse_assignment(no_in)?) } else { None };
decls.push(VarDecl { name, init });
if !self.eat(Punct::Comma)? {
return Ok(decls);
}
}
}
/// `if (…) … else if (…) … else …`, the `else if` chain read with a loop.
fn parse_if(&mut self) -> Result<StmtId> {
let mut chain = Vec::new();
let alternate = loop {
let start = self.expect_keyword(Keyword::If)?.span;
self.expect(Punct::LParen)?;
let test = self.parse_expression(false)?;
self.expect(Punct::RParen)?;
let consequent = self.parse_branch()?;
chain.push((start, test, consequent));
if !self.at_keyword(Keyword::Else) {
break None;
}
self.bump()?;
if !self.at_keyword(Keyword::If) {
break Some(self.parse_branch()?);
}
};
let mut alternate = alternate;
let mut last = None;
for (start, test, consequent) in chain.into_iter().rev() {
let id = self.ast.push_stmt(Stmt::If { test, consequent, alternate }, self.span_from(start));
alternate = Some(id);
last = Some(id);
}
last.ok_or_else(|| self.unexpected())
}
/// The body of an iteration statement, with `break` and `continue` allowed in it.
fn parse_loop_body(&mut self) -> Result<StmtId> {
self.ctx.loops += 1;
self.ctx.breakables += 1;
let body = self.parse_statement();
self.ctx.loops -= 1;
self.ctx.breakables -= 1;
body
}
fn parse_do_while(&mut self, start: Span) -> Result<StmtId> {
self.bump()?;
let body = self.parse_loop_body()?;
self.expect_keyword(Keyword::While)?;
self.expect(Punct::LParen)?;
let test = self.parse_expression(false)?;
self.expect(Punct::RParen)?;
// A `;` after `do … while (…)` may be left out even on the same line, as ECMA-262 2015
// section 11.9.1 allows.
self.eat(Punct::Semicolon)?;
Ok(self.ast.push_stmt(Stmt::DoWhile { body, test }, self.span_from(start)))
}
fn parse_while(&mut self, start: Span) -> Result<StmtId> {
self.bump()?;
self.expect(Punct::LParen)?;
let test = self.parse_expression(false)?;
self.expect(Punct::RParen)?;
let body = self.parse_loop_body()?;
Ok(self.ast.push_stmt(Stmt::While { test, body }, self.span_from(start)))
}
fn parse_for(&mut self, start: Span) -> Result<StmtId> {
self.bump()?;
self.expect(Punct::LParen)?;
let init = if self.at(Punct::Semicolon) {
None
} else if self.at_keyword(Keyword::Var) {
self.bump()?;
let mut decls = self.parse_var_decls(true)?;
if decls.len() == 1 && self.at_keyword(Keyword::In) {
let decl = decls.pop().ok_or_else(|| self.unexpected())?;
// The variable of a for-in loop takes an initializer only outside strict mode
// code (ECMA-262 2017 Annex B.3.6).
if self.ctx.strict && decl.init.is_some() {
return Err(self.error_at_token(&self.tok, "a for-in variable cannot have an initializer in strict mode code"));
}
return self.parse_for_in(ForInTarget::Var(decl), start);
}
Some(ForInit::Var(decls))
} else {
let expr = self.parse_expression(true)?;
if self.at_keyword(Keyword::In) {
self.check_target(expr, "invalid left-hand side in for-in loop")?;
return self.parse_for_in(ForInTarget::Expr(expr), start);
}
Some(ForInit::Expr(expr))
};
self.expect(Punct::Semicolon)?;
let test = if self.at(Punct::Semicolon) { None } else { Some(self.parse_expression(false)?) };
self.expect(Punct::Semicolon)?;
let update = if self.at(Punct::RParen) { None } else { Some(self.parse_expression(false)?) };
self.expect(Punct::RParen)?;
let body = self.parse_loop_body()?;
Ok(self.ast.push_stmt(Stmt::For { init, test, update, body }, self.span_from(start)))
}
/// The rest of `for (target in object) body`, from `in`.
fn parse_for_in(&mut self, target: ForInTarget, start: Span) -> Result<StmtId> {
self.expect_keyword(Keyword::In)?;
let object = self.parse_expression(false)?;
self.expect(Punct::RParen)?;
let body = self.parse_loop_body()?;
Ok(self.ast.push_stmt(Stmt::ForIn { target, object, body }, self.span_from(start)))
}
/// `break` or `continue`, with an optional label on the same line, which must name a label
/// around it in the same function (an iteration statement's, for `continue`).
fn parse_jump(&mut self, is_break: bool, start: Span) -> Result<StmtId> {
let keyword = self.bump()?;
let label = match &self.tok.kind {
TokenKind::Ident(name) if !self.tok.newline_before => {
let name = name.clone();
self.check_identifier(&self.tok, &name)?;
let token = self.bump()?;
match self.ctx.labels.get(&name) {
None => return Err(self.error_at_token(&token, format!("undefined label '{name}'"))),
Some(&is_loop) if !is_break && !is_loop => {
return Err(self.error_at_token(&token, format!("label '{name}' does not name a loop, so continue cannot use it")));
}
Some(_) => {}
}
Some(Name { name, span: token.span })
}
_ => None,
};
if label.is_none() {
if is_break && self.ctx.breakables == 0 {
return Err(self.error_at_token(&keyword, "break outside a loop or switch"));
}
if !is_break && self.ctx.loops == 0 {
return Err(self.error_at_token(&keyword, "continue outside a loop"));
}
}
self.semicolon()?;
let stmt = if is_break { Stmt::Break(label) } else { Stmt::Continue(label) };
Ok(self.ast.push_stmt(stmt, self.span_from(start)))
}
fn parse_return(&mut self, start: Span) -> Result<StmtId> {
let keyword = self.bump()?;
if !self.ctx.in_function {
return Err(self.error_at_token(&keyword, "return outside a function"));
}
let ends = self.at(Punct::Semicolon) || self.at(Punct::RBrace) || self.tok.kind == TokenKind::Eof || self.tok.newline_before;
let arg = if ends { None } else { Some(self.parse_expression(false)?) };
self.semicolon()?;
Ok(self.ast.push_stmt(Stmt::Return(arg), self.span_from(start)))
}
fn parse_with(&mut self, start: Span) -> Result<StmtId> {
if self.ctx.strict {
return Err(self.error_at_token(&self.tok, "with statements are not allowed in strict mode"));
}
self.bump()?;
self.expect(Punct::LParen)?;
let object = self.parse_expression(false)?;
self.expect(Punct::RParen)?;
let body = self.parse_statement()?;
Ok(self.ast.push_stmt(Stmt::With { object, body }, self.span_from(start)))
}
fn parse_switch(&mut self, start: Span) -> Result<StmtId> {
self.bump()?;
self.expect(Punct::LParen)?;
let discriminant = self.parse_expression(false)?;
self.expect(Punct::RParen)?;
self.expect(Punct::LBrace)?;
self.ctx.breakables += 1;
let cases = self.parse_cases();
self.ctx.breakables -= 1;
let cases = cases?;
Ok(self.ast.push_stmt(Stmt::Switch { discriminant, cases }, self.span_from(start)))
}
/// A `switch` statement's clauses, up to and with its `}`; at most one `default`.
fn parse_cases(&mut self) -> Result<Vec<SwitchCase>> {
let mut cases = Vec::new();
let mut has_default = false;
while !self.eat(Punct::RBrace)? {
let case_start = self.tok.span;
let test = if self.at_keyword(Keyword::Case) {
self.bump()?;
Some(self.parse_expression(false)?)
} else if self.at_keyword(Keyword::Default) {
if has_default {
return Err(self.error_at_token(&self.tok, "more than one default clause in switch statement"));
}
has_default = true;
self.bump()?;
None
} else {
return Err(self.unexpected());
};
self.expect(Punct::Colon)?;
let mut body = Vec::new();
while !(self.at_keyword(Keyword::Case) || self.at_keyword(Keyword::Default) || self.at(Punct::RBrace)) {
if self.tok.kind == TokenKind::Eof {
return Err(self.unexpected());
}
body.push(self.parse_statement_item()?);
}
cases.push(SwitchCase { test, body, span: self.span_from(case_start) });
}
Ok(cases)
}
fn parse_throw(&mut self, start: Span) -> Result<StmtId> {
self.bump()?;
if self.tok.newline_before {
return Err(self.error_at_token(&self.tok, "line break after throw"));
}
let arg = self.parse_expression(false)?;
self.semicolon()?;
Ok(self.ast.push_stmt(Stmt::Throw(arg), self.span_from(start)))
}
fn parse_try(&mut self, start: Span) -> Result<StmtId> {
self.bump()?;
let block = self.parse_block()?;
let handler = if self.at_keyword(Keyword::Catch) {
self.bump()?;
self.expect(Punct::LParen)?;
let param = self.binding_identifier()?;
self.expect(Punct::RParen)?;
Some(CatchClause { param, body: self.parse_block()? })
} else {
None
};
let finalizer = if self.at_keyword(Keyword::Finally) {
self.bump()?;
Some(self.parse_block()?)
} else {
None
};
if handler.is_none() && finalizer.is_none() {
return Err(self.error_at_token(&self.tok, "missing catch or finally after try"));
}
Ok(self.ast.push_stmt(Stmt::Try { block, handler, finalizer }, self.span_from(start)))
}
/// `a: b: statement`: the labels read with a loop, each unique among the labels around it.
/// `in_list`: the labeled statement stands in a statement list, so outside strict mode code
/// its body may be a function declaration.
fn parse_labeled(&mut self, in_list: bool) -> Result<StmtId> {
let mut labels: Vec<(Name, Span)> = Vec::new();
let mut chain = HashSet::new();
while let TokenKind::Ident(name) = &self.tok.kind {
if !self.peek().is_some_and(|t| t.is_punct(Punct::Colon)) {
break;
}
let name = name.clone();
self.check_identifier(&self.tok, &name)?;
if self.ctx.labels.contains_key(&name) || !chain.insert(name.clone()) {
return Err(self.error_at_token(&self.tok, format!("duplicate label '{name}'")));
}
let token = self.bump()?;
self.bump()?;
labels.push((Name { name, span: token.span }, token.span));
}
let is_loop = self.at_keyword(Keyword::For) || self.at_keyword(Keyword::While) || self.at_keyword(Keyword::Do);
self.ctx.labels.extend(chain.into_iter().map(|name| (name, is_loop)));
self.function_allowed = in_list && !self.ctx.strict;
let body = self.parse_statement();
for (label, _) in &labels {
self.ctx.labels.remove(&label.name);
}
let mut body = body?;
for (label, label_start) in labels.into_iter().rev() {
let span = Span::new(label_start.start, self.ast.stmt_span(body).end);
body = self.ast.push_stmt(Stmt::Labeled { label, body }, span);
}
Ok(body)
}
// ---- functions ----
fn parse_function_declaration(&mut self) -> Result<StmtId> {
let start = self.expect_keyword(Keyword::Function)?.span;
let name = self.function_name()?;
let id = self.parse_function_rest(FunctionKind::Declaration, Some(name), start)?;
Ok(self.ast.push_stmt(Stmt::FunctionDecl(id), self.span_from(start)))
}
/// A function's name, checked as an identifier; the strict-mode checks wait for the body,
/// whose prologue may make the function strict.
fn function_name(&mut self) -> Result<(Name, Token)> {
let TokenKind::Ident(name) = &self.tok.kind else {
return Err(self.error_at_token(&self.tok, format!("expected a function name but found {}", describe(&self.tok))));
};
let name = name.clone();
self.check_identifier(&self.tok, &name)?;
let token = self.bump()?;
Ok((Name { name, span: token.span }, token))
}
/// The parameters and body of a function, from `(`.
fn parse_function_rest(&mut self, kind: FunctionKind, name: Option<(Name, Token)>, start: Span) -> Result<crate::ast::FunctionId> {
self.enter()?;
let result = self.function_rest(kind, name, start);
self.leave();
result
}
fn function_rest(&mut self, kind: FunctionKind, name: Option<(Name, Token)>, start: Span) -> Result<crate::ast::FunctionId> {
self.expect(Punct::LParen)?;
let mut params: Vec<(Name, Token)> = Vec::new();
if !self.at(Punct::RParen) {
loop {
let (param, token) = self.function_name_or_param()?;
params.push((param, token));
if !self.eat(Punct::Comma)? {
break;
}
}
}
let close = self.expect(Punct::RParen)?;
match kind {
FunctionKind::Getter if !params.is_empty() => return Err(self.error_at_token(&close, "a getter must not have parameters")),
FunctionKind::Setter if params.len() != 1 => return Err(self.error_at_token(&close, "a setter must have exactly one parameter")),
_ => {}
}
self.expect(Punct::LBrace)?;
let inner = Context { strict: self.ctx.strict, in_function: true, ..Context::default() };
let outer = std::mem::replace(&mut self.ctx, inner);
let body = self.parse_body(true);
let strict = self.ctx.strict;
self.ctx = outer;
let body = body?;
self.expect(Punct::RBrace)?;
if strict {
self.check_strict_function(name.as_ref(), &params)?;
}
Ok(self.ast.push_function(Function {
kind,
name: name.map(|(n, _)| n),
params: params.into_iter().map(|(n, _)| n).collect(),
body,
strict,
span: self.span_from(start),
}))
}
/// A parameter's name.
fn function_name_or_param(&mut self) -> Result<(Name, Token)> {
let TokenKind::Ident(name) = &self.tok.kind else {
return Err(self.error_at_token(&self.tok, format!("expected a parameter name but found {}", describe(&self.tok))));
};
let name = name.clone();
self.check_identifier(&self.tok, &name)?;
let token = self.bump()?;
Ok((Name { name, span: token.span }, token))
}
/// The rules strict mode code sets for a function's name and parameters (Annex C): neither
/// `eval`, `arguments` nor a strict-mode reserved word, and no parameter name twice.
fn check_strict_function(&self, name: Option<&(Name, Token)>, params: &[(Name, Token)]) -> Result<()> {
for (n, token) in name.into_iter().chain(params) {
if is_eval_or_arguments(&n.name) {
return Err(self.error_at_token(token, format!("'{}' cannot be declared in strict mode", n.name)));
}
if strict_reserved(&n.name) {
return Err(self.error_at_token(token, format!("'{}' is a reserved word in strict mode", n.name)));
}
}
let mut seen = HashSet::new();
for (p, token) in params {
if !seen.insert(p.name.as_str()) {
return Err(self.error_at_token(token, format!("duplicate parameter name '{}' is not allowed in strict mode", p.name)));
}
}
Ok(())
}
// ---- expressions ----
/// An expression: assignment expressions separated by commas. `no_in` leaves `in` to a
/// `for (… in …)` around it.
fn parse_expression(&mut self, no_in: bool) -> Result<ExprId> {
let first = self.parse_assignment(no_in)?;
if !self.at(Punct::Comma) {
return Ok(first);
}
let mut list = vec![first];
while self.eat(Punct::Comma)? {
list.push(self.parse_assignment(no_in)?);
}
let span = self.ast.expr_span(first).to(Span::new(self.prev_end, self.prev_end));
Ok(self.ast.push_expr(Expr::Sequence(list), span))
}
fn parse_assignment(&mut self, no_in: bool) -> Result<ExprId> {
self.enter()?;
let result = self.assignment(no_in);
self.leave();
result
}
/// `a = b += c`: the targets kept on a stack, so a long chain does not nest.
fn assignment(&mut self, no_in: bool) -> Result<ExprId> {
let mut targets: Vec<(ExprId, AssignOp)> = Vec::new();
loop {
let expr = self.parse_conditional(no_in)?;
let Some(op) = assign_op(&self.tok) else {
let mut value = expr;
while let Some((target, op)) = targets.pop() {
let span = self.ast.expr_span(target).to(self.ast.expr_span(value));
value = self.ast.push_expr(Expr::Assign { op, target, value }, span);
}
return Ok(value);
};
self.check_target(expr, "invalid assignment target")?;
self.bump()?;
targets.push((expr, op));
}
}
/// An assignment's, `++`'s, `--`'s or `for-in`'s target: an identifier (in strict code
/// neither `eval` nor `arguments`), a property, or outside strict mode code a call (assigning
/// to its result fails at run time, as ECMA-262 2015 Annex B and web browsers have it).
fn check_target(&self, expr: ExprId, message: &str) -> Result<()> {
let inner = self.ast.unparenthesized(expr);
match self.ast.expr(inner) {
Some(Expr::Ident(name)) => {
if self.ctx.strict && is_eval_or_arguments(name) {
return Err(self.error_at_offset(self.ast.expr_span(inner).start, format!("cannot assign to '{name}' in strict mode")));
}
Ok(())
}
Some(Expr::Member { .. } | Expr::Index { .. }) => Ok(()),
Some(Expr::Call { .. }) if !self.ctx.strict => Ok(()),
_ => Err(self.error_at_offset(self.ast.expr_span(expr).start, message)),
}
}
fn parse_conditional(&mut self, no_in: bool) -> Result<ExprId> {
let test = self.parse_binary(no_in)?;
if !self.at(Punct::Question) {
return Ok(test);
}
self.bump()?;
let consequent = self.parse_assignment(false)?;
self.expect(Punct::Colon)?;
let alternate = self.parse_assignment(no_in)?;
let span = self.ast.expr_span(test).to(self.ast.expr_span(alternate));
Ok(self.ast.push_expr(Expr::Conditional { test, consequent, alternate }, span))
}
/// Binary and logical operators, read with an operator stack by precedence (all are left
/// associative).
fn parse_binary(&mut self, no_in: bool) -> Result<ExprId> {
let mut operands = vec![self.parse_unary()?];
let mut ops: Vec<(BinKind, u8)> = Vec::new();
while let Some((op, precedence)) = binary_op(&self.tok, no_in) {
while ops.last().is_some_and(|&(_, top)| top >= precedence) {
self.reduce(&mut operands, &mut ops);
}
self.bump()?;
ops.push((op, precedence));
operands.push(self.parse_unary()?);
}
while !ops.is_empty() {
self.reduce(&mut operands, &mut ops);
}
operands.pop().ok_or_else(|| self.unexpected())
}
/// Joins the top two operands with the top operator.
fn reduce(&mut self, operands: &mut Vec<ExprId>, ops: &mut Vec<(BinKind, u8)>) {
let (Some((op, _)), Some(right), Some(left)) = (ops.pop(), operands.pop(), operands.pop()) else {
return;
};
let span = self.ast.expr_span(left).to(self.ast.expr_span(right));
let expr = match op {
BinKind::Binary(op) => Expr::Binary { op, left, right },
BinKind::Logical(op) => Expr::Logical { op, left, right },
};
operands.push(self.ast.push_expr(expr, span));
}
/// Prefix operators, read with a loop and applied innermost first.
fn parse_unary(&mut self) -> Result<ExprId> {
let mut prefixes = Vec::new();
while let Some(prefix) = prefix_op(&self.tok) {
prefixes.push((prefix, self.tok.span));
self.bump()?;
}
let mut expr = self.parse_postfix()?;
for (prefix, start) in prefixes.into_iter().rev() {
let span = start.to(self.ast.expr_span(expr));
expr = match prefix {
Prefix::Update(op) => {
self.check_target(expr, "invalid increment or decrement operand")?;
self.ast.push_expr(Expr::Update { op, prefix: true, target: expr }, span)
}
Prefix::Unary(op) => {
if op == UnaryOp::Delete && self.ctx.strict && matches!(self.ast.expr(self.ast.unparenthesized(expr)), Some(Expr::Ident(_))) {
return Err(self.error_at_offset(start.start, "delete of an unqualified identifier in strict mode"));
}
self.ast.push_expr(Expr::Unary { op, arg: expr }, span)
}
};
}
Ok(expr)
}
fn parse_postfix(&mut self) -> Result<ExprId> {
let expr = self.parse_left_hand_side()?;
let op = match &self.tok.kind {
TokenKind::Punct(Punct::PlusPlus) if !self.tok.newline_before => UpdateOp::Increment,
TokenKind::Punct(Punct::MinusMinus) if !self.tok.newline_before => UpdateOp::Decrement,
_ => return Ok(expr),
};
self.check_target(expr, "invalid increment or decrement operand")?;
let token = self.bump()?;
let span = self.ast.expr_span(expr).to(token.span);
Ok(self.ast.push_expr(Expr::Update { op, prefix: false, target: expr }, span))
}
/// Member accesses, calls and `new` (section 11.2): each `new` takes the first argument list
/// after its callee, and a `new` left without one constructs with no arguments.
fn parse_left_hand_side(&mut self) -> Result<ExprId> {
let mut news = Vec::new();
while self.at_keyword(Keyword::New) {
news.push(self.bump()?.span);
}
let mut expr = self.parse_primary()?;
loop {
match &self.tok.kind {
TokenKind::Punct(Punct::Dot) => {
self.bump()?;
let Some(name) = self.tok.identifier_name().map(str::to_string) else {
return Err(self.error_at_token(&self.tok, format!("expected a property name but found {}", describe(&self.tok))));
};
let token = self.bump()?;
let span = self.ast.expr_span(expr).to(token.span);
expr = self.ast.push_expr(Expr::Member { object: expr, property: Name { name, span: token.span } }, span);
}
TokenKind::Punct(Punct::LBracket) => {
self.bump()?;
let index = self.parse_expression(false)?;
let close = self.expect(Punct::RBracket)?;
let span = self.ast.expr_span(expr).to(close.span);
expr = self.ast.push_expr(Expr::Index { object: expr, index }, span);
}
TokenKind::Punct(Punct::LParen) => {
let args = self.parse_arguments()?;
let span = Span::new(self.ast.expr_span(expr).start, self.prev_end);
expr = match news.pop() {
Some(new) => self.ast.push_expr(Expr::New { callee: expr, args }, Span::new(new.start, self.prev_end)),
None => self.ast.push_expr(Expr::Call { callee: expr, args }, span),
};
}
_ => break,
}
}
while let Some(new) = news.pop() {
let span = Span::new(new.start, self.ast.expr_span(expr).end);
expr = self.ast.push_expr(Expr::New { callee: expr, args: Vec::new() }, span);
}
Ok(expr)
}
fn parse_arguments(&mut self) -> Result<Vec<ExprId>> {
self.expect(Punct::LParen)?;
let mut args = Vec::new();
if !self.at(Punct::RParen) {
loop {
args.push(self.parse_assignment(false)?);
if !self.eat(Punct::Comma)? {
break;
}
}
}
self.expect(Punct::RParen)?;
Ok(args)
}
fn parse_primary(&mut self) -> Result<ExprId> {
self.enter()?;
let result = self.primary();
self.leave();
result
}
fn primary(&mut self) -> Result<ExprId> {
let start = self.tok.span;
let expr = match &self.tok.kind {
TokenKind::Keyword(Keyword::This) => Expr::This,
TokenKind::Keyword(Keyword::Null) => Expr::Null,
TokenKind::Keyword(Keyword::True) => Expr::Bool(true),
TokenKind::Keyword(Keyword::False) => Expr::Bool(false),
TokenKind::Keyword(Keyword::Function) => return self.parse_function_expression(),
TokenKind::Ident(name) => {
let name = name.clone();
self.check_identifier(&self.tok, &name)?;
Expr::Ident(name)
}
TokenKind::Number(n) => {
if self.ctx.strict && self.tok.legacy_octal {
return Err(self.error_at_token(&self.tok, "octal literals are not allowed in strict mode"));
}
Expr::Number(*n)
}
TokenKind::String(s) => {
if self.ctx.strict && self.tok.legacy_octal {
return Err(self.error_at_token(&self.tok, "octal escape sequences are not allowed in strict mode"));
}
Expr::String(s.clone())
}
TokenKind::Punct(Punct::Slash | Punct::SlashEq) => {
let regexp = self.lex.rescan_regexp(&self.tok)?;
self.tok = regexp;
let TokenKind::RegExp { pattern, flags } = &self.tok.kind else {
return Err(self.unexpected());
};
Expr::RegExp { pattern: pattern.clone(), flags: flags.clone() }
}
TokenKind::Punct(Punct::LBracket) => return self.parse_array(),
TokenKind::Punct(Punct::LBrace) => return self.parse_object(),
TokenKind::Punct(Punct::LParen) => {
self.bump()?;
let inner = self.parse_expression(false)?;
self.expect(Punct::RParen)?;
return Ok(self.ast.push_expr(Expr::Paren(inner), self.span_from(start)));
}
_ => return Err(self.unexpected()),
};
let token = self.bump()?;
Ok(self.ast.push_expr(expr, token.span))
}
fn parse_function_expression(&mut self) -> Result<ExprId> {
let start = self.expect_keyword(Keyword::Function)?.span;
let name = if matches!(self.tok.kind, TokenKind::Ident(_)) { Some(self.function_name()?) } else { None };
let id = self.parse_function_rest(FunctionKind::Expression, name, start)?;
Ok(self.ast.push_expr(Expr::Function(id), self.span_from(start)))
}
/// `[a, , b]`: elements, a comma with nothing before it leaving a hole.
fn parse_array(&mut self) -> Result<ExprId> {
let start = self.expect(Punct::LBracket)?.span;
let mut elements = Vec::new();
loop {
if self.eat(Punct::RBracket)? {
break;
}
if self.eat(Punct::Comma)? {
elements.push(None);
continue;
}
elements.push(Some(self.parse_assignment(false)?));
if !self.at(Punct::RBracket) {
self.expect(Punct::Comma)?;
}
}
Ok(self.ast.push_expr(Expr::Array(elements), self.span_from(start)))
}
/// `{a: 1, "b": 2, 3: c, get d() {…}, set d(v) {…}}`. A name may repeat, a later definition
/// replacing an earlier one (ECMA-262 2015 section 12.2.6.1).
fn parse_object(&mut self) -> Result<ExprId> {
let start = self.expect(Punct::LBrace)?.span;
let mut properties = Vec::new();
while !self.eat(Punct::RBrace)? {
let property = self.parse_property()?;
properties.push(property);
if !self.at(Punct::RBrace) {
self.expect(Punct::Comma)?;
}
}
Ok(self.ast.push_expr(Expr::Object(properties), self.span_from(start)))
}
fn parse_property(&mut self) -> Result<Property> {
let start = self.tok.span;
// `get` and `set` start an accessor only when written without escapes.
let accessor = match self.tok.ident().filter(|_| !self.tok.escaped) {
Some("get") => Some(FunctionKind::Getter),
Some("set") => Some(FunctionKind::Setter),
_ => None,
};
if let Some(kind) = accessor {
let word = self.bump()?;
if property_key(&self.tok).is_some() {
let (key, _) = self.parse_property_key()?;
let function = self.parse_function_rest(kind, None, start)?;
let value = if kind == FunctionKind::Getter { PropertyValue::Get(function) } else { PropertyValue::Set(function) };
return Ok(Property { key, value, span: self.span_from(start) });
}
let key = PropertyKey::Ident(word.ident().unwrap_or_default().to_string());
return self.property_value(key, start);
}
let (key, _) = self.parse_property_key()?;
self.property_value(key, start)
}
/// `: value` after a data property's name.
fn property_value(&mut self, key: PropertyKey, start: Span) -> Result<Property> {
self.expect(Punct::Colon)?;
let value = self.parse_assignment(false)?;
Ok(Property { key, value: PropertyValue::Init(value), span: self.span_from(start) })
}
fn parse_property_key(&mut self) -> Result<(PropertyKey, Token)> {
let Some(key) = property_key(&self.tok) else {
return Err(self.error_at_token(&self.tok, format!("expected a property name but found {}", describe(&self.tok))));
};
if self.ctx.strict && self.tok.legacy_octal {
return Err(self.error_at_token(&self.tok, "octal literals and escape sequences are not allowed in strict mode"));
}
let token = self.bump()?;
Ok((key, token))
}
}
/// The key a token gives as a property name: an identifier name, a string or a number.
fn property_key(token: &Token) -> Option<PropertyKey> {
match &token.kind {
TokenKind::String(s) => Some(PropertyKey::String(s.clone())),
TokenKind::Number(n) => Some(PropertyKey::Number(*n)),
_ => token.identifier_name().map(|n| PropertyKey::Ident(n.to_string())),
}
}
fn is_eval_or_arguments(name: &str) -> bool {
name == "eval" || name == "arguments"
}
fn assign_op(token: &Token) -> Option<AssignOp> {
let TokenKind::Punct(p) = &token.kind else { return None };
Some(match p {
Punct::Eq => None,
Punct::PlusEq => Some(BinaryOp::Add),
Punct::MinusEq => Some(BinaryOp::Sub),
Punct::StarEq => Some(BinaryOp::Mul),
Punct::SlashEq => Some(BinaryOp::Div),
Punct::PercentEq => Some(BinaryOp::Mod),
Punct::ShlEq => Some(BinaryOp::Shl),
Punct::ShrEq => Some(BinaryOp::Shr),
Punct::UShrEq => Some(BinaryOp::UShr),
Punct::AmpEq => Some(BinaryOp::BitAnd),
Punct::PipeEq => Some(BinaryOp::BitOr),
Punct::CaretEq => Some(BinaryOp::BitXor),
_ => return None,
})
}
/// A binary operator and its precedence (higher binds tighter); `in` is left out under `no_in`.
fn binary_op(token: &Token, no_in: bool) -> Option<(BinKind, u8)> {
use BinKind::{Binary as B, Logical as L};
match &token.kind {
TokenKind::Keyword(Keyword::Instanceof) => Some((B(BinaryOp::Instanceof), 7)),
TokenKind::Keyword(Keyword::In) if !no_in => Some((B(BinaryOp::In), 7)),
TokenKind::Punct(p) => Some(match p {
Punct::PipePipe => (L(LogicalOp::Or), 1),
Punct::AmpAmp => (L(LogicalOp::And), 2),
Punct::Pipe => (B(BinaryOp::BitOr), 3),
Punct::Caret => (B(BinaryOp::BitXor), 4),
Punct::Amp => (B(BinaryOp::BitAnd), 5),
Punct::EqEq => (B(BinaryOp::Eq), 6),
Punct::NotEq => (B(BinaryOp::NotEq), 6),
Punct::EqEqEq => (B(BinaryOp::StrictEq), 6),
Punct::NotEqEq => (B(BinaryOp::StrictNotEq), 6),
Punct::Lt => (B(BinaryOp::Lt), 7),
Punct::Gt => (B(BinaryOp::Gt), 7),
Punct::LtEq => (B(BinaryOp::LtEq), 7),
Punct::GtEq => (B(BinaryOp::GtEq), 7),
Punct::Shl => (B(BinaryOp::Shl), 8),
Punct::Shr => (B(BinaryOp::Shr), 8),
Punct::UShr => (B(BinaryOp::UShr), 8),
Punct::Plus => (B(BinaryOp::Add), 9),
Punct::Minus => (B(BinaryOp::Sub), 9),
Punct::Star => (B(BinaryOp::Mul), 10),
Punct::Slash => (B(BinaryOp::Div), 10),
Punct::Percent => (B(BinaryOp::Mod), 10),
_ => return None,
}),
_ => None,
}
}
fn prefix_op(token: &Token) -> Option<Prefix> {
Some(match &token.kind {
TokenKind::Keyword(Keyword::Delete) => Prefix::Unary(UnaryOp::Delete),
TokenKind::Keyword(Keyword::Void) => Prefix::Unary(UnaryOp::Void),
TokenKind::Keyword(Keyword::Typeof) => Prefix::Unary(UnaryOp::Typeof),
TokenKind::Punct(Punct::Plus) => Prefix::Unary(UnaryOp::Plus),
TokenKind::Punct(Punct::Minus) => Prefix::Unary(UnaryOp::Minus),
TokenKind::Punct(Punct::Tilde) => Prefix::Unary(UnaryOp::BitNot),
TokenKind::Punct(Punct::Bang) => Prefix::Unary(UnaryOp::Not),
TokenKind::Punct(Punct::PlusPlus) => Prefix::Update(UpdateOp::Increment),
TokenKind::Punct(Punct::MinusMinus) => Prefix::Update(UpdateOp::Decrement),
_ => return None,
})
}
/// A token as an error message names it.
fn describe(token: &Token) -> String {
match &token.kind {
TokenKind::Eof => "end of input".into(),
TokenKind::Ident(name) => format!("identifier '{name}'"),
TokenKind::Keyword(k) => format!("token '{}'", k.as_str()),
TokenKind::Punct(p) => format!("token '{}'", p.as_str()),
TokenKind::Number(_) => "number".into(),
TokenKind::String(_) => "string".into(),
TokenKind::RegExp { .. } => "regular expression".into(),
TokenKind::Directive(_) => "preprocessor line".into(),
}
}