use crate::engine::api::error::Error;
use crate::engine::api::error::ErrorKind;
use crate::engine::api::error::NativeErrorMessage;
use crate::engine::code::bytecode::Instruction;
use crate::engine::code::function::metadata::EvalKind;
use crate::engine::code::function::metadata::FunctionKind as BytecodeFunctionKind;
use crate::engine::compiler::lexer::Keyword;
use crate::engine::compiler::lexer::LexicalGoal;
use crate::engine::compiler::lexer::NumberKind;
use crate::engine::compiler::lexer::Punctuator;
use crate::engine::compiler::lexer::Span;
use crate::engine::compiler::lexer::TokenKind;
use crate::engine::compiler::model::ir::IdentifierAccess;
use crate::engine::compiler::model::ir::IrConstant;
use crate::engine::compiler::model::ir::function::FunctionKind;
use crate::engine::compiler::parser::diagnostics::IdentifierContext;
use num_traits::ToPrimitive;
use crate::engine::compiler::parser::context::Parser;
use crate::engine::compiler::parser::diagnostics::source_offset;
use crate::engine::compiler::parser::diagnostics::strict_reserved_identifier;
use crate::engine::compiler::pseudo_binding::THIS_LOCAL_NAME;
use crate::engine::value::JsString;
use crate::engine::value::PrimitiveValue as Value;
use std::borrow::Cow;
use std::rc::Rc;
impl<'source> Parser<'source> {
pub(in crate::engine::compiler) fn parse_primary(
&mut self,
import_call_allowed: bool,
) -> Result<(), Error> {
if matches!(
self.current().kind,
TokenKind::Punctuator(Punctuator::Divide | Punctuator::DivideAssign)
) {
self.relex_current_with_goal(LexicalGoal::RegExp)?;
}
let token = *self.current();
self.anonymous_function_definition = None;
match token.kind {
TokenKind::Keyword(Keyword::Null) => {
self.advance()?;
self.emit_instruction(Instruction::Null)?;
}
TokenKind::Keyword(Keyword::False) => {
self.advance()?;
self.emit_instruction(Instruction::PushFalse)?;
}
TokenKind::Keyword(Keyword::True) => {
self.advance()?;
self.emit_instruction(Instruction::PushTrue)?;
}
TokenKind::Keyword(Keyword::This) => {
self.advance()?;
if self.current_ir().derived_class_constructor
|| matches!(
self.current_ir().kind,
FunctionKind::Arrow | FunctionKind::Eval(EvalKind::Direct)
)
{
self.emit_identifier(
self.pseudo_name(THIS_LOCAL_NAME),
token.span,
IdentifierAccess::Get,
)?;
} else {
self.emit_instruction(Instruction::PushThis)?;
}
}
TokenKind::Number(number) => {
if self.current_ir().strict
&& matches!(
number.kind,
NumberKind::LegacyOctal | NumberKind::LegacyDecimal
)
{
return Err(Error::syntax(
"legacy leading-zero numeric literals are forbidden in strict mode",
token.span,
));
}
self.advance()?;
let value =
parse_number(&number).map_err(|message| Error::syntax(message, token.span))?;
self.emit_value(value)?;
}
TokenKind::String(string) => {
if self.current_ir().strict && string.has_legacy_octal_escape {
return Err(Error::syntax(
"legacy octal escapes are forbidden in strict mode",
token.span,
));
}
self.advance()?;
self.emit_atom_string(self.decode_string_literal(token.span)?)?;
}
TokenKind::Punctuator(Punctuator::LeftParen) => {
self.advance()?;
self.parse_recursion(ParserStackFrame::Parenthesized, Self::parse_expression)?;
self.expect_punctuator(Punctuator::RightParen)?;
}
TokenKind::Punctuator(Punctuator::LeftBrace) => {
self.parse_recursion(ParserStackFrame::ObjectLiteral, Self::parse_object_literal)?;
}
TokenKind::Punctuator(Punctuator::LeftBracket) => {
self.parse_recursion(ParserStackFrame::ArrayLiteral, Self::parse_array_literal)?;
}
TokenKind::Template(_) => {
self.parse_template_literal()?;
}
TokenKind::Identifier(ref identifier)
if self.is_unescaped_name(identifier, "async") && self.async_function_ahead() =>
{
self.parse_function_expression()?;
}
TokenKind::Identifier(identifier) => {
let name = self.intern_identifier(&identifier);
self.reject_forbidden_identifier_reference(self.names.name(name), token.span)?;
self.validate_identifier(
&identifier,
token.span,
self.current_ir().strict,
IdentifierContext::Reference,
)?;
self.advance()?;
let operation = self.emit_identifier(name, token.span, IdentifierAccess::Get)?;
self.current_ir_mut().context.last_identifier_reference = Some(operation);
}
TokenKind::Keyword(Keyword::Function) => {
self.parse_function_expression()?;
}
TokenKind::Keyword(Keyword::Class) => {
self.parse_class_expression()?;
}
TokenKind::Keyword(Keyword::New) => {
self.parse_new_expression()?;
}
TokenKind::Keyword(Keyword::Super) => {
self.parse_super_property(token.span)?;
}
TokenKind::Keyword(
keyword @ (Keyword::Const
| Keyword::Enum
| Keyword::Export
| Keyword::Extends
| Keyword::Var),
) => {
return Err(self.syntax_here(format!(
"unexpected token in expression: '{}'",
keyword.as_str()
)));
}
TokenKind::Keyword(Keyword::Import) => {
self.parse_import_expression(token.span, import_call_allowed)?;
}
TokenKind::Keyword(Keyword::Yield)
if matches!(
self.current_ir().execution_kind,
BytecodeFunctionKind::Generator | BytecodeFunctionKind::AsyncGenerator
) =>
{
return Err(self.syntax_here("unexpected token in expression: 'yield'"));
}
TokenKind::Keyword(keyword)
if self.current_ir().strict && strict_reserved_identifier(keyword) =>
{
return Err(self.syntax_here(format!(
"unexpected token in expression: '{}'",
keyword.as_str()
)));
}
TokenKind::Keyword(
keyword @ (Keyword::If
| Keyword::For
| Keyword::Else
| Keyword::In
| Keyword::Case
| Keyword::Default
| Keyword::Catch
| Keyword::Finally
| Keyword::Debugger),
) => {
return Err(self.syntax_here(format!(
"unexpected token in expression: '{}'",
keyword.as_str()
)));
}
TokenKind::Keyword(keyword) => {
return Err(self.syntax_here(format!(
"unexpected token in expression: '{}'",
keyword.as_str()
)));
}
TokenKind::RegExp(literal) => {
let pattern_start = token
.span
.start
.byte_offset
.checked_add(1)
.ok_or_else(|| Error::internal("regexp source range overflowed"))?;
let pattern_end = pattern_start
.checked_add(literal.pattern.len())
.ok_or_else(|| Error::internal("regexp source range overflowed"))?;
let pattern = self
.lexer
.source_range_to_js_string(pattern_start..pattern_end)?
.ok_or_else(|| Error::internal("regexp source range is invalid"))?;
let flags = JsString::try_from_utf8(literal.flags)?;
let program = oxide_regexp::compile(
&pattern.utf16_units().collect::<Vec<_>>(),
&flags.utf16_units().collect::<Vec<_>>(),
)
.map_err(|error| {
let kind = match error.kind() {
oxide_regexp::CompileErrorKind::Internal => ErrorKind::JsInternal,
oxide_regexp::CompileErrorKind::Syntax
| oxide_regexp::CompileErrorKind::TooManyCaptures
| oxide_regexp::CompileErrorKind::TooManyRegisters => ErrorKind::Syntax,
};
let message = oxide_regexp::javascript_compile_error_message(&error).to_owned();
Error::new(kind, message).with_span(token.span)
})?;
let constant = self.add_constant(IrConstant::RegExp {
pattern,
program: Rc::new(program),
})?;
self.emit_instruction_at(
Instruction::RegExp(constant),
source_offset(token.span)?,
)?;
self.advance()?;
}
TokenKind::PrivateIdentifier(identifier) => {
let mut message = NativeErrorMessage::new();
message.push_utf8("unexpected token in expression: '");
message.push_utf8(identifier.raw);
message.push_utf8("'");
return Err(
Error::from_native_message(ErrorKind::Syntax, message).with_span(token.span)
);
}
TokenKind::Punctuator(punctuator) => {
return Err(self.syntax_here(format!(
"unexpected token in expression: '{}'",
punctuator.as_str()
)));
}
TokenKind::RawAscii(0) | TokenKind::Eof => {
return Err(self.syntax_here("unexpected token in expression: ''"));
}
TokenKind::RawAscii(byte) => {
return Err(self.syntax_here(format!(
"unexpected token in expression: '{}'",
char::from(byte)
)));
}
}
Ok(())
}
pub(in crate::engine::compiler) fn reject_forbidden_identifier_reference(
&self,
name: &str,
span: Span,
) -> Result<(), Error> {
if name == "arguments" && self.current_ir().arguments_forbidden {
return Err(Error::syntax(
"'arguments' identifier is not allowed in class field initializer",
span,
));
}
Ok(())
}
pub(in crate::engine::compiler) fn parse_array_literal(&mut self) -> Result<(), Error> {
const DENSE_PREFIX_LIMIT: u32 = 32;
const MAX_STATIC_INDEX: u32 = i32::MAX as u32;
if !self.is_punctuator(Punctuator::LeftBracket) {
return Err(self.syntax_here("expecting '['"));
}
self.advance_expression_start()?;
let mut index = 0_u32;
while !self.is_punctuator(Punctuator::RightBracket) && index < DENSE_PREFIX_LIMIT {
if self.is_punctuator(Punctuator::Comma) || self.is_punctuator(Punctuator::Ellipsis) {
break;
}
self.parse_assignment_allow_in()?;
index += 1;
if self.is_punctuator(Punctuator::Comma) {
self.advance_expression_start()?;
} else if !self.is_punctuator(Punctuator::RightBracket) {
self.expect_punctuator(Punctuator::RightBracket)?;
}
}
self.emit_instruction(Instruction::ArrayFrom(u16::try_from(index).map_err(
|_| Error::internal("dense Array literal prefix does not fit u16"),
)?))?;
let mut need_length = false;
while !self.is_punctuator(Punctuator::RightBracket) && index < MAX_STATIC_INDEX {
if self.is_punctuator(Punctuator::Ellipsis) {
break;
}
need_length = true;
if !self.is_punctuator(Punctuator::Comma) {
self.parse_assignment_allow_in()?;
let key = self.add_constant(IrConstant::Primitive(Value::String(
JsString::try_from_utf8(&index.to_string())?,
)))?;
self.emit_instruction(Instruction::DefineField(key))?;
need_length = false;
}
index += 1;
if self.is_punctuator(Punctuator::Comma) {
self.advance_expression_start()?;
} else if !self.is_punctuator(Punctuator::RightBracket) {
self.expect_punctuator(Punctuator::RightBracket)?;
}
}
if self.is_punctuator(Punctuator::RightBracket) {
if need_length {
let length = self.add_constant(IrConstant::Primitive(Value::String(
JsString::from_static("length"),
)))?;
self.emit_instruction(Instruction::Dup)?;
self.emit_instruction(Instruction::PushI32(
i32::try_from(index)
.map_err(|_| Error::internal("Array literal index does not fit i32"))?,
))?;
self.emit_instruction(Instruction::PutField(length))?;
}
self.expect_punctuator(Punctuator::RightBracket)?;
self.anonymous_function_definition = None;
return Ok(());
}
self.emit_instruction(Instruction::PushI32(
i32::try_from(index)
.map_err(|_| Error::internal("Array literal index does not fit i32"))?,
))?;
while !self.is_punctuator(Punctuator::RightBracket) {
if self.is_punctuator(Punctuator::Ellipsis) {
self.advance_expression_start()?;
self.parse_assignment_allow_in()?;
self.emit_instruction(Instruction::Append)?;
} else {
need_length = true;
if !self.is_punctuator(Punctuator::Comma) {
self.parse_assignment_allow_in()?;
self.emit_instruction(Instruction::DefineArrayEl)?;
need_length = false;
}
self.emit_instruction(Instruction::Inc)?;
}
if !self.is_punctuator(Punctuator::Comma) {
break;
}
self.advance_expression_start()?;
}
if need_length {
let length = self.add_constant(IrConstant::Primitive(Value::String(
JsString::from_static("length"),
)))?;
self.emit_instruction(Instruction::Dup1)?;
self.emit_instruction(Instruction::PutField(length))?;
} else {
self.emit_instruction(Instruction::Drop)?;
}
self.expect_punctuator(Punctuator::RightBracket)?;
self.anonymous_function_definition = None;
Ok(())
}
}
use crate::engine::compiler::lexer::NumericRadix;
use crate::engine::compiler::stack_guard::ParserStackFrame;
use crate::engine::value::bigint::JsBigInt;
use num_bigint::BigUint;
pub(in crate::engine::compiler) fn parse_number(
number: &crate::engine::compiler::lexer::NumberLiteral<'_>,
) -> Result<Value, String> {
let integer = match number.kind {
NumberKind::Integer(radix) => Some(radix_digits(number.raw, radix)),
NumberKind::LegacyOctal => Some((number.raw, 8)),
_ => None,
};
if let Some((digits, radix)) = integer
&& let Some(value) = small_integer(digits, radix)
{
return Ok(Value::number(value as f64));
}
let raw = if number.raw.contains('_') {
Cow::Owned(number.raw.replace('_', ""))
} else {
Cow::Borrowed(number.raw)
};
if let NumberKind::BigInt(radix) = number.kind {
let literal = raw
.strip_suffix('n')
.ok_or_else(|| "BigInt literal is missing its suffix".to_owned())?;
let (digits, base) = match radix {
NumericRadix::Binary => (literal.get(2..).unwrap_or_default(), 2),
NumericRadix::Octal => (literal.get(2..).unwrap_or_default(), 8),
NumericRadix::Decimal => (literal, 10),
NumericRadix::Hexadecimal => (literal.get(2..).unwrap_or_default(), 16),
};
return JsBigInt::parse_radix(digits, base)
.map(Value::BigInt)
.map_err(|error| error.to_string());
}
let value = match number.kind {
NumberKind::Integer(radix) => parse_radix_literal(&raw, radix)?,
NumberKind::Float | NumberKind::LegacyDecimal => raw
.parse::<f64>()
.map_err(|_| format!("invalid numeric literal '{raw}'"))?,
NumberKind::LegacyOctal => parse_digits(&raw, 8)?,
NumberKind::BigInt(_) => unreachable!("handled above"),
};
Ok(Value::number(value))
}
pub(in crate::engine::compiler) fn parse_radix_literal(
raw: &str,
radix: NumericRadix,
) -> Result<f64, String> {
let (digits, base) = radix_digits(raw, radix);
parse_digits(digits, base)
}
fn radix_digits(raw: &str, radix: NumericRadix) -> (&str, u32) {
match radix {
NumericRadix::Binary => (raw.get(2..).unwrap_or_default(), 2),
NumericRadix::Octal => (raw.get(2..).unwrap_or_default(), 8),
NumericRadix::Decimal => (raw, 10),
NumericRadix::Hexadecimal => (raw.get(2..).unwrap_or_default(), 16),
}
}
fn small_integer(digits: &str, radix: u32) -> Option<u64> {
let mut value = 0u64;
let mut any_digit = false;
for byte in digits.bytes() {
let digit = match byte {
b'0'..=b'9' => byte - b'0',
b'a'..=b'f' => byte - b'a' + 10,
b'A'..=b'F' => byte - b'A' + 10,
b'_' => continue,
_ => return None,
};
if u32::from(digit) >= radix {
return None;
}
any_digit = true;
value = value
.checked_mul(u64::from(radix))?
.checked_add(u64::from(digit))?;
}
any_digit.then_some(value)
}
pub(in crate::engine::compiler) fn parse_digits(digits: &str, radix: u32) -> Result<f64, String> {
if digits.is_empty() {
return Err("numeric literal has no digits".to_owned());
}
let value = BigUint::parse_bytes(digits.as_bytes(), radix)
.ok_or_else(|| format!("invalid base-{radix} numeric literal"))?;
Ok(value.to_f64().unwrap_or(f64::INFINITY))
}