use crate::engine::api::error::Error;
use crate::engine::api::error::ErrorKind;
use crate::engine::code::bytecode::Instruction;
use crate::engine::compiler::lexer::LexicalGoal;
use crate::engine::compiler::lexer::Punctuator;
use crate::engine::compiler::lexer::TemplatePartKind;
use crate::engine::compiler::lexer::TokenKind;
use crate::engine::compiler::model::ir::IdentifierReferenceAccess;
use crate::engine::compiler::model::ir::IrConstant;
use crate::engine::compiler::model::ir::IrOp;
use crate::engine::compiler::parser::context::Parser;
use crate::engine::compiler::parser::diagnostics::source_offset;
use crate::engine::compiler::stack_guard::ParserStackFrame;
use crate::engine::value::JsString;
use crate::engine::value::PrimitiveValue as Value;
impl<'source> Parser<'source> {
pub(super) fn parse_template_literal(&mut self) -> Result<(), Error> {
let mut depth = 0_usize;
loop {
let token = *self.current();
let TokenKind::Template(part) = token.kind else {
return Err(Error::internal(
"template parser lost its continuation token",
));
};
let kind = part.kind;
let Some(cooked) = self.decode_template_cooked(&part, token.span)? else {
let initial = matches!(
kind,
TemplatePartKind::Head | TemplatePartKind::NoSubstitution
);
let invalid_span = self
.lexer
.template_escape_error(token.span.start, initial)
.map_err(crate::engine::compiler::parser::diagnostics::lex_error)?
.map(|error| error.span);
return Err(Error::syntax(
"malformed escape sequence in string literal",
invalid_span.unwrap_or(token.span),
));
};
if !cooked.is_empty() || depth == 0 {
self.emit_atom_string(cooked)?;
if depth == 0 {
if kind == TemplatePartKind::NoSubstitution {
self.advance()?;
self.anonymous_function_definition = None;
return Ok(());
}
let concat = self.add_constant(IrConstant::Primitive(Value::String(
JsString::from_static("concat"),
)))?;
self.emit_instruction(Instruction::GetField2(concat))?;
}
depth += 1;
}
if kind == TemplatePartKind::Tail {
let argument_count = depth
.checked_sub(1)
.ok_or_else(|| Error::internal("template receiver disappeared"))?;
self.emit(IrOp::TemplateCall {
argument_count,
method: true,
})?;
self.advance()?;
self.finish_template_expression();
return Ok(());
}
if !matches!(kind, TemplatePartKind::Head | TemplatePartKind::Middle) {
return Err(Error::internal("invalid template-part transition"));
}
self.advance()?;
self.parse_recursion(ParserStackFrame::Template, Self::parse_expression)?;
depth += 1;
self.expect_template_substitution_end()?;
}
}
pub(super) fn parse_tagged_template_suffix(&mut self) -> Result<bool, Error> {
if !matches!(self.current().kind, TokenKind::Template(_)) {
return Ok(false);
}
let member_method = self.promote_last_member_get_for_call()?;
let identifier_method = if member_method {
false
} else {
self.promote_tail_identifier_get(IdentifierReferenceAccess::Call)?
.is_some_and(|reference| reference.object_environment)
};
self.parse_tagged_template(member_method || identifier_method)?;
Ok(true)
}
fn parse_tagged_template(&mut self, method: bool) -> Result<(), Error> {
let call_span = self.current().span;
let constant = self.add_constant(IrConstant::TemplateObject {
cooked: Vec::new(),
raw: Vec::new(),
})?;
self.emit(IrOp::PushConstant(constant))?;
let mut argument_count = 1_usize;
loop {
let token = *self.current();
let TokenKind::Template(part) = token.kind else {
return Err(Error::internal(
"tagged template parser lost its continuation token",
));
};
let kind = part.kind;
let cooked = self.decode_template_cooked(&part, token.span)?;
let raw = self.decode_template_raw_value(&part, token.span)?;
self.append_template_site_part(constant, cooked, raw)?;
if matches!(
kind,
TemplatePartKind::NoSubstitution | TemplatePartKind::Tail
) {
self.emit_at(
IrOp::TemplateCall {
argument_count,
method,
},
source_offset(call_span)?,
)?;
self.advance()?;
self.finish_template_expression();
return Ok(());
}
if !matches!(kind, TemplatePartKind::Head | TemplatePartKind::Middle) {
return Err(Error::internal("invalid template-part transition"));
}
self.advance()?;
self.parse_recursion(ParserStackFrame::Template, Self::parse_expression)?;
argument_count = argument_count
.checked_add(1)
.ok_or_else(|| Error::new(ErrorKind::JsInternal, "stack overflow"))?;
self.expect_template_substitution_end()?;
}
}
fn append_template_site_part(
&mut self,
constant: u32,
cooked: Option<JsString>,
raw: JsString,
) -> Result<(), Error> {
let constant = usize::try_from(constant)
.map_err(|_| Error::internal("template constant index did not fit usize"))?;
let Some(IrConstant::TemplateObject {
cooked: cooked_parts,
raw: raw_parts,
}) = self.current_ir_mut().constants.get_mut(constant)
else {
return Err(Error::internal(
"template site constant lost its structural payload",
));
};
cooked_parts.push(cooked);
raw_parts.push(raw);
Ok(())
}
fn expect_template_substitution_end(&mut self) -> Result<(), Error> {
if !self.is_punctuator(Punctuator::RightBrace) {
return Err(self.syntax_here("expected '}' after template expression"));
}
self.advance_with_goal(LexicalGoal::TemplateContinuation)
}
fn finish_template_expression(&mut self) {
self.current_ir_mut().context.last_member_reference = None;
self.current_ir_mut().context.last_identifier_reference = None;
self.current_ir_mut().context.last_optional_chain = None;
self.anonymous_function_definition = None;
}
}