use crate::engine::api::error::Error;
use crate::engine::code::bytecode::Instruction;
use crate::engine::compiler::lexer::Punctuator;
use crate::engine::compiler::lexer::Span;
use crate::engine::compiler::lexer::TokenKind;
use crate::engine::compiler::model::ir::IrConstant;
use crate::engine::compiler::model::ir::IrOp;
use crate::engine::compiler::model::ir::PrivateFieldAccess;
use crate::engine::compiler::model::ir::SpannedIrOp;
use crate::engine::compiler::parser::builder::FunctionBuilder;
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::PrimitiveValue as Value;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct OptionalChainShortCircuit {
fallback: usize,
receiver_padding: usize,
jump: usize,
}
#[derive(Debug, Default)]
pub(super) struct PendingOptionalChain {
short_circuits: Vec<OptionalChainShortCircuit>,
}
#[derive(Clone, Debug)]
pub(super) struct FinalizedOptionalChain {
short_circuits: Vec<OptionalChainShortCircuit>,
pub(super) terminal_member_get: Option<usize>,
}
impl<'source> Parser<'source> {
pub(super) fn emit_optional_chain_test(
&mut self,
chain: &mut PendingOptionalChain,
drop_count: usize,
) -> Result<(), Error> {
let continuation_depth = self.current_ir().context.stack_depth;
if drop_count == 0 || continuation_depth < drop_count {
return Err(Error::internal(
"optional chain test has an invalid Reference depth",
));
}
self.emit_instruction(Instruction::Dup)?;
self.emit_instruction(Instruction::IsUndefinedOrNull)?;
let continue_jump = self.emit_instruction(Instruction::IfFalse(u32::MAX))?;
for _ in 0..drop_count {
self.emit_instruction(Instruction::Drop)?;
}
let fallback = self.emit_instruction(Instruction::Undefined)?;
let receiver_padding = self.emit_instruction(Instruction::Nop)?;
let short_circuit = self.emit_instruction(Instruction::Goto(u32::MAX))?;
self.patch_jump(continue_jump, self.current_ir().ops.len())?;
self.current_ir_mut().context.stack_depth = continuation_depth;
chain.short_circuits.push(OptionalChainShortCircuit {
fallback,
receiver_padding,
jump: short_circuit,
});
Ok(())
}
pub(super) fn parse_optional_member_suffix(&mut self, member_span: Span) -> Result<(), Error> {
if self.is_punctuator(Punctuator::LeftBracket) {
self.advance_expression_start()?;
self.parse_recursion(ParserStackFrame::MemberAccess, Self::parse_expression)?;
self.expect_punctuator(Punctuator::RightBracket)?;
let operation =
self.emit_instruction_at(Instruction::GetArrayEl, source_offset(member_span)?)?;
self.current_ir_mut().context.last_member_reference = Some(operation);
self.anonymous_function_definition = None;
return Ok(());
}
let token = *self.current();
let name = match token.kind {
TokenKind::PrivateIdentifier(identifier) => {
let name = self.intern_private_identifier(&identifier);
self.advance()?;
let operation =
self.emit_private_field_get(name, token.span, source_offset(member_span)?)?;
self.current_ir_mut().context.last_member_reference = Some(operation);
self.anonymous_function_definition = None;
return Ok(());
}
TokenKind::Identifier(identifier) => self.intern_identifier(&identifier),
TokenKind::Keyword(keyword) => self.intern_name(keyword.as_str()),
_ => return Err(self.syntax_here("expecting field name")),
};
self.advance()?;
let value = self.names.js_string(name)?;
let key = self.add_constant(IrConstant::Primitive(Value::String(value)))?;
let operation =
self.emit_instruction_at(Instruction::GetField(key), source_offset(member_span)?)?;
self.current_ir_mut().context.last_member_reference = Some(operation);
self.anonymous_function_definition = None;
Ok(())
}
pub(super) fn finish_optional_chain(
&mut self,
chain: PendingOptionalChain,
) -> Result<(), Error> {
if chain.short_circuits.is_empty() {
return Err(Error::internal(
"optional chain finished without a nullish edge",
));
}
let (terminal_member_get, terminal_private_get) = {
let function = self.current_ir();
let terminal_member_get =
if function.context.last_member_reference == function.ops.len().checked_sub(1) {
function.context.last_member_reference
} else {
None
};
let terminal_private_get = terminal_member_get.is_some()
&& matches!(
function.ops.last(),
Some(SpannedIrOp {
op: IrOp::PrivateField {
access: PrivateFieldAccess::Get,
..
},
..
})
);
(terminal_member_get, terminal_private_get)
};
let end = self.current_ir().ops.len();
for short_circuit in &chain.short_circuits {
self.patch_jump(short_circuit.jump, end)?;
}
let function = self.current_ir_mut();
if terminal_private_get {
function.context.last_member_reference = None;
}
function.context.last_optional_chain = Some(FinalizedOptionalChain {
short_circuits: chain.short_circuits,
terminal_member_get: (!terminal_private_get)
.then_some(terminal_member_get)
.flatten(),
});
Ok(())
}
pub(super) fn rewrite_optional_chain_delete_fallback(
&mut self,
chain: FinalizedOptionalChain,
) -> Result<(), Error> {
let end = self.current_ir().ops.len();
for short_circuit in chain.short_circuits {
let Some(SpannedIrOp {
op: IrOp::Bytecode(fallback),
..
}) = self.current_ir_mut().ops.get_mut(short_circuit.fallback)
else {
return Err(Error::internal(
"optional chain delete fallback disappeared",
));
};
if !matches!(fallback, Instruction::Undefined) {
return Err(Error::internal(
"optional chain delete fallback was already rewritten",
));
}
*fallback = Instruction::PushTrue;
self.patch_jump(short_circuit.jump, end)?;
}
Ok(())
}
}
pub(super) fn pad_grouped_method_receiver(
function: &mut FunctionBuilder,
terminal_get: Option<usize>,
) -> Result<(), Error> {
if function
.context
.last_optional_chain
.as_ref()
.is_none_or(|chain| chain.terminal_member_get != terminal_get)
{
return Ok(());
}
let chain = function
.context
.last_optional_chain
.take()
.ok_or_else(|| Error::internal("optional chain marker disappeared"))?;
for short_circuit in chain.short_circuits {
let Some(SpannedIrOp {
op: IrOp::Bytecode(padding),
..
}) = function.ops.get_mut(short_circuit.receiver_padding)
else {
return Err(Error::internal(
"optional chain receiver padding disappeared",
));
};
if !matches!(padding, Instruction::Nop) {
return Err(Error::internal(
"optional chain receiver padding was already consumed",
));
}
*padding = Instruction::Undefined;
}
Ok(())
}