use std::borrow::Cow;
use crate::engine::api::error::Error;
use crate::engine::code::function::metadata::EvalKind;
use crate::engine::compiler::lexer::Identifier;
use crate::engine::compiler::lexer::Keyword;
use crate::engine::compiler::lexer::LexContext;
use crate::engine::compiler::lexer::LexicalGoal;
use crate::engine::compiler::lexer::Punctuator;
use crate::engine::compiler::lexer::Span;
use crate::engine::compiler::lexer::TemplatePart;
use crate::engine::compiler::lexer::TemplatePartKind;
use crate::engine::compiler::lexer::Token;
use crate::engine::compiler::lexer::TokenKind;
use crate::engine::compiler::lexer::quickjs_simple_lookahead_is_of;
use crate::engine::compiler::model::ir::function::FunctionKind;
use crate::engine::compiler::names::NameId;
use crate::engine::compiler::parser::context::ForHeadDelimiter;
use crate::engine::compiler::parser::context::ForIterationKind;
use crate::engine::compiler::parser::context::Parser;
use crate::engine::compiler::parser::diagnostics::lex_error;
use crate::engine::compiler::pseudo_binding::NEW_TARGET_LOCAL_NAME;
use crate::engine::value::JsString;
use crate::engine::compiler::stack_guard::ParserStackFrame;
impl<'source> Parser<'source> {
pub(in crate::engine::compiler) fn expect_punctuator(
&mut self,
punctuator: Punctuator,
) -> Result<(), Error> {
if self.consume_punctuator(punctuator)? {
Ok(())
} else {
Err(self.syntax_here(format!("expecting '{}'", punctuator.as_str())))
}
}
pub(in crate::engine::compiler) fn consume_punctuator(
&mut self,
punctuator: Punctuator,
) -> Result<bool, Error> {
if self.is_punctuator(punctuator) {
self.advance()?;
Ok(true)
} else {
Ok(false)
}
}
pub(in crate::engine::compiler) fn is_punctuator(&self, punctuator: Punctuator) -> bool {
matches!(self.current().kind, TokenKind::Punctuator(current) if current == punctuator)
}
pub(in crate::engine::compiler) fn identifier_text(
&self,
identifier: &Identifier<'source>,
) -> Cow<'source, str> {
let raw = identifier.raw.strip_prefix('#').unwrap_or(identifier.raw);
if !identifier.has_escape {
return Cow::Borrowed(raw);
}
Cow::Owned(self.lexer.decode_identifier_text(identifier.raw))
}
pub(in crate::engine::compiler) fn intern_identifier(
&mut self,
identifier: &Identifier<'source>,
) -> NameId {
let raw = identifier.raw.strip_prefix('#').unwrap_or(identifier.raw);
if !identifier.has_escape {
return self.names.intern(raw);
}
let decoded = self.lexer.decode_identifier_text(identifier.raw);
self.names.intern(&decoded)
}
pub(in crate::engine::compiler) fn intern_identifier_string(
&mut self,
identifier: &Identifier<'source>,
) -> Result<JsString, Error> {
let name = self.intern_identifier(identifier);
Ok(self.names.js_string(name)?)
}
pub(in crate::engine::compiler) fn intern_private_identifier(
&mut self,
identifier: &Identifier<'source>,
) -> NameId {
if !identifier.has_escape {
return self.names.intern(identifier.raw);
}
let mut binding = String::with_capacity(identifier.raw.len().saturating_add(1));
binding.push('#');
binding.push_str(&self.lexer.decode_identifier_text(identifier.raw));
self.names.intern(&binding)
}
pub(in crate::engine::compiler) fn intern_name(&mut self, name: &str) -> NameId {
self.names.intern(name)
}
pub(in crate::engine::compiler) fn pseudo_name(&self, name: &str) -> NameId {
self.names
.lookup(name)
.expect("synthetic binding name must be pre-interned")
}
pub(in crate::engine::compiler) fn identical_name(
&self,
identifier: &Identifier<'source>,
expected: &str,
) -> bool {
self.identifier_text(identifier) == expected
}
pub(in crate::engine::compiler) fn is_unescaped_name(
&self,
identifier: &Identifier<'source>,
expected: &str,
) -> bool {
!identifier.has_escape && identifier.raw == expected
}
pub(in crate::engine::compiler) fn decode_string_literal(
&self,
span: Span,
) -> Result<JsString, Error> {
let body = span.start.byte_offset + 1..span.end.byte_offset - 1;
if !self.lexer.source().as_bytes()[body.clone()].contains(&b'\\') {
return self
.lexer
.source_range_to_js_string(body)?
.ok_or_else(|| Error::internal("invalid string literal source range"));
}
let value = self
.lexer
.decode_string_literal(span.start)
.map_err(lex_error)?;
Ok(JsString::try_from_utf16(value.utf16)?)
}
pub(in crate::engine::compiler) fn decode_template_raw_value(
&self,
part: &TemplatePart<'source>,
span: Span,
) -> Result<JsString, Error> {
let value = self
.lexer
.decode_template_raw_value(span.start, template_part_is_initial(part.kind))
.map_err(lex_error)?;
Ok(JsString::try_from_utf16(value.utf16)?)
}
pub(in crate::engine::compiler) fn decode_template_cooked(
&self,
part: &TemplatePart<'source>,
span: Span,
) -> Result<Option<JsString>, Error> {
if part.invalid_escape {
return Ok(None);
}
let value = self
.lexer
.decode_template_cooked_value(span.start, template_part_is_initial(part.kind))
.map_err(lex_error)?;
Ok(Some(JsString::try_from_utf16(value.utf16)?))
}
pub(in crate::engine::compiler) fn is_for_of_keyword(&self) -> bool {
matches!(
&self.current().kind,
TokenKind::Identifier(identifier) if self.is_unescaped_name(identifier, "of")
)
}
pub(in crate::engine::compiler) fn next_token_is_for_of_keyword(&self) -> bool {
self.lexer
.source()
.get(self.current().span.end.byte_offset..)
.is_some_and(quickjs_simple_lookahead_is_of)
}
pub(in crate::engine::compiler) fn for_iteration_kind_ahead(&self) -> Option<ForIterationKind> {
if let Some((start, context, hint)) = self.lookahead.borrow().iteration_hint
&& start == self.current().span.start.byte_offset
&& context == self.lexer.context()
{
return hint;
}
let mut lexer = self.lexer.clone();
lexer.seek(self.current().span.start);
let mut delimiters = self.delimiter_stack();
let mut goal = LexicalGoal::Div;
let mut regexp_allowed = true;
loop {
let requested_goal = goal;
goal = LexicalGoal::Div;
let Ok(mut token) = self.probe_token(&mut lexer, requested_goal) else {
return None;
};
if requested_goal == LexicalGoal::Div
&& regexp_allowed
&& matches!(
token.kind,
TokenKind::Punctuator(Punctuator::Divide | Punctuator::DivideAssign)
)
{
lexer.seek(token.span.start);
let Ok(regexp) = self.probe_token(&mut lexer, LexicalGoal::RegExp) else {
return None;
};
token = regexp;
}
if delimiters.is_empty() {
match &token.kind {
TokenKind::Keyword(Keyword::In) => return Some(ForIterationKind::In),
TokenKind::Identifier(identifier)
if self.is_unescaped_name(identifier, "of") =>
{
return Some(ForIterationKind::Of);
}
TokenKind::Punctuator(Punctuator::RightParen) | TokenKind::Eof => return None,
_ => {}
}
}
match &token.kind {
TokenKind::Punctuator(Punctuator::LeftParen) => {
delimiters.push(ForHeadDelimiter::Parenthesis);
}
TokenKind::Punctuator(Punctuator::LeftBracket) => {
delimiters.push(ForHeadDelimiter::Bracket);
}
TokenKind::Punctuator(Punctuator::LeftBrace) => {
delimiters.push(ForHeadDelimiter::Brace);
}
TokenKind::Punctuator(Punctuator::RightParen) => {
if delimiters.pop() != Some(ForHeadDelimiter::Parenthesis) {
return None;
}
}
TokenKind::Punctuator(Punctuator::RightBracket) => {
if delimiters.pop() != Some(ForHeadDelimiter::Bracket) {
return None;
}
}
TokenKind::Punctuator(Punctuator::RightBrace) => {
if delimiters.last() == Some(&ForHeadDelimiter::Template) {
goal = LexicalGoal::TemplateContinuation;
regexp_allowed = true;
continue;
}
if delimiters.pop() != Some(ForHeadDelimiter::Brace) {
return None;
}
}
TokenKind::Template(part) => match part.kind {
TemplatePartKind::Head => delimiters.push(ForHeadDelimiter::Template),
TemplatePartKind::Middle => {
if delimiters.last() != Some(&ForHeadDelimiter::Template) {
return None;
}
}
TemplatePartKind::Tail => {
if delimiters.pop() != Some(ForHeadDelimiter::Template) {
return None;
}
}
TemplatePartKind::NoSubstitution => {}
},
TokenKind::Eof => return None,
_ => {}
}
regexp_allowed = for_head_regexp_allowed_after(&token.kind);
}
}
pub(in crate::engine::compiler) fn for_head_has_top_level_semicolon(&self) -> bool {
if !self.is_punctuator(Punctuator::LeftParen) {
return false;
}
let mut lexer = self.lexer.clone();
lexer.seek(self.current().span.start);
let mut delimiters = self.delimiter_stack();
let mut goal = LexicalGoal::Div;
let mut regexp_allowed = true;
let mut has_semicolon = false;
let mut inner_start = None;
let mut iteration_hint = None;
let has_semicolon = (|| {
loop {
let requested_goal = goal;
goal = LexicalGoal::Div;
let Ok(mut token) = self.probe_token(&mut lexer, requested_goal) else {
return has_semicolon;
};
if requested_goal == LexicalGoal::Div
&& regexp_allowed
&& matches!(
token.kind,
TokenKind::Punctuator(Punctuator::Divide | Punctuator::DivideAssign)
)
{
lexer.seek(token.span.start);
let Ok(regexp) = self.probe_token(&mut lexer, LexicalGoal::RegExp) else {
return has_semicolon;
};
token = regexp;
}
if delimiters.len() == 1 {
inner_start.get_or_insert(token.span.start.byte_offset);
if iteration_hint.is_none() {
iteration_hint = match &token.kind {
TokenKind::Keyword(Keyword::In) => Some(ForIterationKind::In),
TokenKind::Identifier(identifier)
if self.is_unescaped_name(identifier, "of") =>
{
Some(ForIterationKind::Of)
}
_ => None,
};
}
}
match &token.kind {
TokenKind::Punctuator(Punctuator::LeftParen) => {
if delimiters.len() >= 255 {
return has_semicolon;
}
delimiters.push(ForHeadDelimiter::Parenthesis);
}
TokenKind::Punctuator(Punctuator::LeftBracket) => {
if delimiters.len() >= 255 {
return has_semicolon;
}
delimiters.push(ForHeadDelimiter::Bracket);
}
TokenKind::Punctuator(Punctuator::LeftBrace) => {
if delimiters.len() >= 255 {
return has_semicolon;
}
delimiters.push(ForHeadDelimiter::Brace);
}
TokenKind::Punctuator(Punctuator::RightParen) => {
if delimiters.pop() != Some(ForHeadDelimiter::Parenthesis) {
return has_semicolon;
}
if delimiters.is_empty() {
return has_semicolon;
}
}
TokenKind::Punctuator(Punctuator::RightBracket) => {
if delimiters.pop() != Some(ForHeadDelimiter::Bracket) {
return has_semicolon;
}
}
TokenKind::Punctuator(Punctuator::RightBrace) => {
if delimiters.last() == Some(&ForHeadDelimiter::Template) {
goal = LexicalGoal::TemplateContinuation;
regexp_allowed = true;
continue;
}
if delimiters.pop() != Some(ForHeadDelimiter::Brace) {
return has_semicolon;
}
}
TokenKind::Punctuator(Punctuator::Semicolon) if delimiters.len() == 1 => {
has_semicolon = true;
}
TokenKind::Template(part) => match part.kind {
TemplatePartKind::Head => {
if delimiters.len() >= 255 {
return has_semicolon;
}
delimiters.push(ForHeadDelimiter::Template);
}
TemplatePartKind::Middle => {
if delimiters.last() != Some(&ForHeadDelimiter::Template) {
return has_semicolon;
}
}
TemplatePartKind::Tail => {
if delimiters.pop() != Some(ForHeadDelimiter::Template) {
return has_semicolon;
}
}
TemplatePartKind::NoSubstitution => {}
},
TokenKind::Eof => return has_semicolon,
_ => {}
}
regexp_allowed = for_head_regexp_allowed_after(&token.kind);
}
})();
if let Some(start) = inner_start {
self.lookahead.borrow_mut().iteration_hint =
iteration_hint.map(|hint| (start, self.lexer.context(), Some(hint)));
}
has_semicolon
}
pub(in crate::engine::compiler) fn lexical_declaration_ahead(
&self,
allow_line_terminated_other: bool,
) -> Result<bool, Error> {
if matches!(
self.current().kind,
TokenKind::Keyword(Keyword::Let | Keyword::Const)
) {
return Ok(true);
}
let TokenKind::Identifier(identifier) = &self.current().kind else {
return Ok(false);
};
if !self.is_unescaped_name(identifier, "let") {
return Ok(false);
}
let mut lexer = self.lexer.clone();
lexer.seek(self.current().span.start);
self.probe_token(&mut lexer, LexicalGoal::Div)
.map_err(lex_error)?;
let next = self
.probe_token(&mut lexer, LexicalGoal::Div)
.map_err(lex_error)?;
let other_declaration_start = matches!(
&next.kind,
TokenKind::Punctuator(Punctuator::LeftBrace)
| TokenKind::Identifier(Identifier {
escaped_reserved_word: false,
..
})
| TokenKind::Keyword(Keyword::Let | Keyword::Yield | Keyword::Await)
);
Ok(
matches!(&next.kind, TokenKind::Punctuator(Punctuator::LeftBracket))
|| (other_declaration_start
&& (!next.line_terminator_before || allow_line_terminated_other)),
)
}
pub(in crate::engine::compiler) fn label_ahead(&self) -> Option<String> {
let TokenKind::Identifier(identifier) = &self.current().kind else {
return None;
};
if identifier.escaped_reserved_word {
return None;
}
let mut lexer = self.lexer.clone();
lexer.seek(self.current().span.end);
let Ok(next) = self.probe_token(&mut lexer, LexicalGoal::Div) else {
return None;
};
matches!(next.kind, TokenKind::Punctuator(Punctuator::Colon))
.then(|| self.identifier_text(identifier).into_owned())
}
pub(in crate::engine::compiler) fn restricted_function_declaration_ahead(
&self,
annex_b_function_allowed: bool,
) -> Result<bool, Error> {
let generator = matches!(self.current().kind, TokenKind::Keyword(Keyword::Function));
let async_function = self.async_function_ahead();
if (!generator || !annex_b_function_allowed) && !async_function {
return Ok(false);
}
let mut lexer = self.lexer.clone();
lexer.seek(self.current().span.end);
let next = self
.probe_token(&mut lexer, LexicalGoal::Div)
.map_err(lex_error)?;
if generator {
Ok(matches!(
next.kind,
TokenKind::Punctuator(Punctuator::Multiply)
))
} else {
Ok(async_function)
}
}
pub(in crate::engine::compiler) fn async_function_ahead(&self) -> bool {
let TokenKind::Identifier(identifier) = &self.current().kind else {
return false;
};
if !self.is_unescaped_name(identifier, "async") {
return false;
}
let mut lexer = self.lexer.clone();
lexer.seek(self.current().span.end);
let Ok(function) = self.probe_token(&mut lexer, LexicalGoal::Div) else {
return false;
};
!function.line_terminator_before
&& matches!(function.kind, TokenKind::Keyword(Keyword::Function))
}
pub(in crate::engine::compiler) fn at_eof(&self) -> bool {
matches!(self.current().kind, TokenKind::Eof)
}
pub(in crate::engine::compiler) fn current_new_target_allowed(&self) -> bool {
let mut function_id = self.current_function;
loop {
let function = &self.functions[function_id];
match function.kind {
FunctionKind::Ordinary | FunctionKind::Method => return true,
FunctionKind::Script
| FunctionKind::Module
| FunctionKind::Eval(EvalKind::Indirect) => return false,
FunctionKind::Eval(EvalKind::Direct) => {
return self
.names
.lookup(NEW_TARGET_LOCAL_NAME)
.is_some_and(|name| {
function
.binding_from_scope(function.ir.var_scope, name)
.is_some()
});
}
FunctionKind::Eval(EvalKind::None) => return false,
FunctionKind::Arrow => {
let Some(parent) = function.parent else {
return false;
};
function_id = parent.function;
}
}
}
}
pub(in crate::engine::compiler) fn current(&self) -> &Token<'source> {
&self.token
}
pub(in crate::engine::compiler) fn advance(&mut self) -> Result<(), Error> {
self.advance_with_goal(LexicalGoal::Div)
}
pub(in crate::engine::compiler) fn advance_expression_start(&mut self) -> Result<(), Error> {
self.advance()?;
if matches!(
self.current().kind,
TokenKind::Punctuator(Punctuator::Divide | Punctuator::DivideAssign)
) {
self.relex_current_with_goal(LexicalGoal::RegExp)?;
}
Ok(())
}
#[inline]
pub(in crate::engine::compiler) fn advance_with_goal(
&mut self,
goal: LexicalGoal,
) -> Result<(), Error> {
if !self.at_eof() {
if self.stack_guard.check_physical().is_err() {
return Err(self.syntax_here("stack overflow"));
}
let previous_end = self.token.span.end.byte_offset;
self.scan_next_token(goal)?;
self.previous_end = Some(previous_end);
}
Ok(())
}
#[inline]
fn scan_next_token(&mut self, goal: LexicalGoal) -> Result<(), Error> {
let start = self.lexer.current_position().byte_offset;
let context = self.lexer.context();
if !self.take_lookahead(start, goal, context) {
self.lexer
.next_token_into(goal, &mut self.token)
.map_err(lex_error)?;
}
self.token_context = (context, goal);
Ok(())
}
pub(in crate::engine::compiler) fn relex_current_with_goal(
&mut self,
goal: LexicalGoal,
) -> Result<(), Error> {
if self.token_context == (self.lexer.context(), goal) {
return Ok(());
}
let position = self.current().span.start;
let line_terminator_before = self.current().line_terminator_before;
self.lookahead_invalidate_from(position.byte_offset);
self.lexer.seek(position);
self.scan_next_token(goal)?;
self.token.line_terminator_before = line_terminator_before;
Ok(())
}
pub(in crate::engine::compiler) fn relex_current_with_strict(
&mut self,
strict: bool,
) -> Result<(), Error> {
let mut context = self.lexer.context();
context.strict = strict;
self.relex_current_with_context(context)
}
pub(in crate::engine::compiler) fn relex_current_with_context(
&mut self,
context: LexContext,
) -> Result<(), Error> {
self.lexer.set_context(context);
if self.token_context == (context, LexicalGoal::Div) {
return Ok(());
}
let position = self.current().span.start;
let line_terminator_before = self.current().line_terminator_before;
self.lookahead_invalidate_from(position.byte_offset);
self.lexer.seek(position);
self.scan_next_token(LexicalGoal::Div)?;
self.token.line_terminator_before = line_terminator_before;
Ok(())
}
pub(in crate::engine::compiler) fn set_future_lex_context(&mut self, context: LexContext) {
let position = self.current().span.end;
self.lookahead_invalidate_from(position.byte_offset);
self.lexer.seek(position);
self.lexer.set_context(context);
}
pub(in crate::engine::compiler) fn directive_prologue_has_use_strict(
&self,
inherited_strict: bool,
) -> Result<bool, Error> {
if !matches!(self.current().kind, TokenKind::String(_)) {
return Ok(false);
}
let position = self.current().span.start;
let mut lexer = self.lexer.clone();
lexer.seek(position);
let mut context = lexer.context();
context.strict = inherited_strict;
lexer.set_context(context);
let mut token = self
.probe_token(&mut lexer, LexicalGoal::Div)
.map_err(lex_error)?;
let mut found_strict = false;
loop {
let candidate = match &token.kind {
TokenKind::String(literal) => {
!literal.has_escape && literal.raw[1..literal.raw.len() - 1] == *"use strict"
}
_ => return Ok(found_strict),
};
let next = self
.probe_token(&mut lexer, LexicalGoal::Div)
.map_err(lex_error)?;
let consumed = match &next.kind {
TokenKind::Punctuator(Punctuator::Semicolon) => 2,
TokenKind::Punctuator(Punctuator::RightBrace) | TokenKind::Eof => 1,
_ if next.line_terminator_before && quickjs_directive_asi_token(&next.kind) => 1,
_ => return Ok(found_strict),
};
if candidate {
found_strict = true;
}
token = if consumed == 1 {
next
} else {
self.probe_token(&mut lexer, LexicalGoal::Div)
.map_err(lex_error)?
};
if candidate {
let mut context = lexer.context();
context.strict = true;
lexer.set_context(context);
}
}
}
pub(in crate::engine::compiler) fn syntax_here(&self, message: impl Into<String>) -> Error {
Error::syntax(message, self.current().span)
}
pub(in crate::engine::compiler) fn parse_recursion<T>(
&mut self,
frame: ParserStackFrame,
parse: impl FnOnce(&mut Self) -> Result<T, Error>,
) -> Result<T, Error> {
let weight = self.enter_recursion(frame)?;
let result = parse(self);
if result.is_ok() {
self.leave_recursion(weight);
}
result
}
pub(in crate::engine::compiler) fn enter_recursion(
&mut self,
frame: ParserStackFrame,
) -> Result<u64, Error> {
self.stack_guard
.enter(frame)
.map_err(|_| self.syntax_here("stack overflow"))
}
pub(in crate::engine::compiler) fn leave_recursion(&mut self, weight: u64) {
self.stack_guard.leave(weight);
}
pub(in crate::engine::compiler) fn unsupported_here(
&self,
message: impl Into<String>,
) -> Error {
Error::unsupported(message, self.current().span)
}
}
fn template_part_is_initial(kind: TemplatePartKind) -> bool {
matches!(
kind,
TemplatePartKind::NoSubstitution | TemplatePartKind::Head
)
}
pub(in crate::engine::compiler) fn quickjs_directive_asi_token(kind: &TokenKind<'_>) -> bool {
matches!(
kind,
TokenKind::Number(_)
| TokenKind::String(_)
| TokenKind::Template(_)
| TokenKind::Identifier(_)
| TokenKind::RegExp(_)
| TokenKind::Punctuator(Punctuator::Decrement | Punctuator::Increment)
| TokenKind::Keyword(
Keyword::Null
| Keyword::False
| Keyword::True
| Keyword::If
| Keyword::Return
| Keyword::Var
| Keyword::This
| Keyword::Delete
| Keyword::Typeof
| Keyword::New
| Keyword::Do
| Keyword::While
| Keyword::For
| Keyword::Switch
| Keyword::Throw
| Keyword::Try
| Keyword::Function
| Keyword::Debugger
| Keyword::With
| Keyword::Class
| Keyword::Const
| Keyword::Enum
| Keyword::Export
| Keyword::Import
| Keyword::Super
| Keyword::Interface
| Keyword::Let
| Keyword::Package
| Keyword::Private
| Keyword::Protected
| Keyword::Public
| Keyword::Static
)
)
}
pub(in crate::engine::compiler) fn for_head_regexp_allowed_after(kind: &TokenKind<'_>) -> bool {
if matches!(
kind,
TokenKind::Identifier(identifier)
if !identifier.has_escape && matches!(identifier.raw, "of" | "yield")
) {
return true;
}
!matches!(
kind,
TokenKind::Number(_)
| TokenKind::String(_)
| TokenKind::RegExp(_)
| TokenKind::Identifier(_)
| TokenKind::Keyword(Keyword::Null | Keyword::False | Keyword::True | Keyword::This)
| TokenKind::Punctuator(
Punctuator::RightParen
| Punctuator::RightBracket
| Punctuator::RightBrace
| Punctuator::Increment
| Punctuator::Decrement
)
)
}