use crate::engine::api::error::Error;
use crate::engine::code::bytecode::{Instruction, IteratorCallKind};
use crate::engine::code::function::metadata::FunctionKind as BytecodeFunctionKind;
use crate::engine::compiler::lexer::Keyword;
use crate::engine::compiler::lexer::Punctuator;
use crate::engine::compiler::lexer::TokenKind;
use crate::engine::compiler::model::ir::{IrOp, SpannedIrOp};
use crate::engine::compiler::parser::context::Parser;
use crate::engine::compiler::parser::diagnostics::source_offset;
use crate::engine::compiler::relocation::insert_hoist_fragment;
use crate::engine::value::{JsString, PrimitiveValue as Value};
impl<'source> Parser<'source> {
pub(super) fn insert_generator_initial_yield(&mut self) -> Result<(), Error> {
let function = self.current_ir();
if !matches!(
function.execution_kind,
BytecodeFunctionKind::Generator | BytecodeFunctionKind::AsyncGenerator
) || function.context.in_function_body
|| function.context.stack_depth != 0
{
return Err(Error::internal(
"generator initial yield was inserted in an invalid phase",
));
}
if function
.ops
.iter()
.any(|operation| matches!(operation.op, IrOp::Bytecode(Instruction::InitialYield)))
{
return Err(Error::internal(
"generator contains more than one initial yield",
));
}
let body_scope = function.body_scope;
let body_entry = function
.ops
.iter()
.position(
|operation| matches!(operation.op, IrOp::EnterScope(scope) if scope == body_scope),
)
.ok_or_else(|| Error::internal("generator body has no scope entry"))?;
insert_hoist_fragment(
self.current_ir_mut(),
body_entry,
vec![SpannedIrOp {
op: IrOp::Bytecode(Instruction::InitialYield),
pc_site: None,
}],
)
}
pub(super) fn parse_yield_expression(&mut self) -> Result<(), Error> {
let yield_span = self.current().span;
if !matches!(self.current().kind, TokenKind::Keyword(Keyword::Yield)) {
return Err(Error::internal("yield parser did not start at yield"));
}
if !matches!(
self.current_ir().execution_kind,
BytecodeFunctionKind::Generator | BytecodeFunctionKind::AsyncGenerator
) {
return Err(self.syntax_here("unexpected 'yield' keyword"));
}
if !self.current_ir().context.in_function_body {
return Err(self.syntax_here("yield in default expression"));
}
self.advance()?;
let line_terminator = self.current().line_terminator_before;
let delegated = !line_terminator && self.is_punctuator(Punctuator::Multiply);
if delegated {
self.advance_expression_start()?;
self.parse_assignment()?;
} else if line_terminator || Self::yield_has_no_operand(&self.current().kind) {
self.emit_instruction(Instruction::Undefined)?;
} else {
self.parse_assignment()?;
}
self.anonymous_function_definition = None;
if delegated {
let asynchronous =
self.current_ir().execution_kind == BytecodeFunctionKind::AsyncGenerator;
self.lower_yield_star(yield_span, asynchronous)?;
self.anonymous_function_definition = None;
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;
return Ok(());
}
if self.current_ir().execution_kind == BytecodeFunctionKind::AsyncGenerator {
self.emit_instruction_at(Instruction::Await, source_offset(yield_span)?)?;
}
self.emit_instruction_at(Instruction::Yield, source_offset(yield_span)?)?;
let next = self.emit_instruction(Instruction::IfFalse(u32::MAX))?;
let resumed_depth = self.current_ir().context.stack_depth;
self.emit_return_completion(yield_span, true)?;
let next_target = self.current_ir().ops.len();
self.patch_jump(next, next_target)?;
self.current_ir_mut().context.stack_depth = resumed_depth;
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;
Ok(())
}
fn lower_yield_star(
&mut self,
yield_span: super::Span,
asynchronous: bool,
) -> Result<(), Error> {
let base_depth = self
.current_ir()
.context
.stack_depth
.checked_sub(1)
.ok_or_else(|| Error::internal("yield* has no delegate operand"))?;
let yield_offset = source_offset(yield_span)?;
let done = self.add_constant(super::IrConstant::Primitive(Value::String(
JsString::from_static("done"),
)))?;
let value = self.add_constant(super::IrConstant::Primitive(Value::String(
JsString::from_static("value"),
)))?;
self.emit_instruction(if asynchronous {
Instruction::AsyncIteratorStart
} else {
Instruction::IteratorStart
})?;
self.emit_instruction(Instruction::Undefined)?;
let loop_target = self.current_ir().ops.len();
self.emit_instruction(Instruction::IteratorNext)?;
if asynchronous {
self.emit_instruction_at(Instruction::Await, yield_offset)?;
}
self.emit_instruction(Instruction::IteratorCheckObject)?;
self.emit_instruction(Instruction::GetField2(done))?;
let initial_done = self.emit_instruction(Instruction::IfTrue(u32::MAX))?;
let yield_target = self.current_ir().ops.len();
if asynchronous {
self.emit_instruction(Instruction::GetField(value))?;
self.emit_instruction_at(Instruction::AsyncYieldStar, yield_offset)?;
} else {
self.emit_instruction_at(Instruction::YieldStar, yield_offset)?;
}
self.emit_instruction(Instruction::Dup)?;
let return_resume = self.emit_instruction(Instruction::IfTrue(u32::MAX))?;
self.emit_instruction(Instruction::Drop)?;
let next_loop = self.emit_instruction(Instruction::Goto(u32::MAX))?;
self.patch_jump(next_loop, loop_target)?;
self.current_ir_mut().context.stack_depth = base_depth + 5;
let return_target = self.current_ir().ops.len();
self.patch_jump(return_resume, return_target)?;
self.emit_instruction(Instruction::PushI32(2))?;
self.emit_instruction(Instruction::StrictEq)?;
let throw_resume = self.emit_instruction(Instruction::IfTrue(u32::MAX))?;
if asynchronous {
self.emit_instruction_at(Instruction::Await, yield_offset)?;
}
self.emit_instruction(Instruction::IteratorCall(IteratorCallKind::ReturnWithValue))?;
let missing_return = self.emit_instruction(Instruction::IfTrue(u32::MAX))?;
if asynchronous {
self.emit_instruction_at(Instruction::Await, yield_offset)?;
}
self.emit_instruction(Instruction::IteratorCheckObject)?;
self.emit_instruction(Instruction::GetField2(done))?;
let return_not_done = self.emit_instruction(Instruction::IfFalse(u32::MAX))?;
self.patch_jump(return_not_done, yield_target)?;
self.emit_instruction(Instruction::GetField(value))?;
let return_value = self.current_ir().ops.len();
self.patch_jump(missing_return, return_value)?;
for _ in 0..3 {
self.emit_instruction(Instruction::Nip)?;
}
self.emit_return_completion(yield_span, true)?;
self.current_ir_mut().context.stack_depth = base_depth + 4;
let throw_target = self.current_ir().ops.len();
self.patch_jump(throw_resume, throw_target)?;
self.emit_instruction(Instruction::IteratorCall(IteratorCallKind::ThrowWithValue))?;
let missing_throw = self.emit_instruction(Instruction::IfTrue(u32::MAX))?;
if asynchronous {
self.emit_instruction_at(Instruction::Await, yield_offset)?;
}
self.emit_instruction(Instruction::IteratorCheckObject)?;
self.emit_instruction(Instruction::GetField2(done))?;
let throw_not_done = self.emit_instruction(Instruction::IfFalse(u32::MAX))?;
self.patch_jump(throw_not_done, yield_target)?;
let throw_done = self.emit_instruction(Instruction::Goto(u32::MAX))?;
let missing_throw_target = self.current_ir().ops.len();
self.patch_jump(missing_throw, missing_throw_target)?;
self.emit_instruction(Instruction::IteratorCall(
IteratorCallKind::ReturnWithoutValue,
))?;
let close_missing = self.emit_instruction(Instruction::IfTrue(u32::MAX))?;
if asynchronous {
self.emit_instruction_at(Instruction::Await, yield_offset)?;
}
let iterator_throw = self.current_ir().ops.len();
self.patch_jump(close_missing, iterator_throw)?;
self.emit_instruction(Instruction::ThrowIteratorMissingThrow)?;
let end_target = self.current_ir().ops.len();
self.patch_jump(initial_done, end_target)?;
self.patch_jump(throw_done, end_target)?;
self.current_ir_mut().context.stack_depth = base_depth + 4;
self.emit_instruction(Instruction::GetField(value))?;
for _ in 0..3 {
self.emit_instruction(Instruction::Nip)?;
}
Ok(())
}
fn yield_has_no_operand(kind: &TokenKind<'_>) -> bool {
matches!(
kind,
TokenKind::Punctuator(
Punctuator::Semicolon
| Punctuator::RightParen
| Punctuator::RightBracket
| Punctuator::RightBrace
| Punctuator::Comma
| Punctuator::Colon
)
)
}
}