mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 实现 SequenceExpression + 带标签 break/continue + EmptyStatement
This commit is contained in:
@@ -125,6 +125,17 @@ pub(crate) enum Label {
|
|||||||
CatchBody(u32),
|
CatchBody(u32),
|
||||||
FinallyBody(u32),
|
FinallyBody(u32),
|
||||||
TryEnd(u32),
|
TryEnd(u32),
|
||||||
|
LabeledEnd(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A labeled-statement scope active during emission. `break label` targets
|
||||||
|
/// `break_label`; `continue label` targets `continue_label` (only set when the
|
||||||
|
/// labeled statement directly wraps an iteration statement).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct LabelScope {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) break_label: Label,
|
||||||
|
pub(crate) continue_label: Option<Label>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct CompileCtx {
|
pub(crate) struct CompileCtx {
|
||||||
@@ -139,6 +150,10 @@ pub(crate) struct CompileCtx {
|
|||||||
pub(crate) loop_stack: Vec<(Label, Label)>,
|
pub(crate) loop_stack: Vec<(Label, Label)>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) switch_stack: Vec<Label>,
|
pub(crate) switch_stack: Vec<Label>,
|
||||||
|
/// Active labeled-statement scopes (resolves `break label` / `continue label`).
|
||||||
|
pub(crate) label_scopes: Vec<LabelScope>,
|
||||||
|
/// Label names awaiting binding to the next emitted loop's continue target.
|
||||||
|
pub(crate) pending_loop_labels: Vec<String>,
|
||||||
pub(crate) label_counter: u32,
|
pub(crate) label_counter: u32,
|
||||||
pub(crate) projected_pc: usize,
|
pub(crate) projected_pc: usize,
|
||||||
pub(crate) builtin_reg_map: Vec<(String, u8)>,
|
pub(crate) builtin_reg_map: Vec<(String, u8)>,
|
||||||
@@ -210,6 +225,8 @@ impl CompileCtx {
|
|||||||
label_map: HashMap::new(),
|
label_map: HashMap::new(),
|
||||||
loop_stack: Vec::new(),
|
loop_stack: Vec::new(),
|
||||||
switch_stack: Vec::new(),
|
switch_stack: Vec::new(),
|
||||||
|
label_scopes: Vec::new(),
|
||||||
|
pending_loop_labels: Vec::new(),
|
||||||
label_counter: 0,
|
label_counter: 0,
|
||||||
projected_pc: 0,
|
projected_pc: 0,
|
||||||
builtin_reg_map: Vec::new(),
|
builtin_reg_map: Vec::new(),
|
||||||
@@ -468,6 +485,60 @@ impl CompileCtx {
|
|||||||
self.switch_stack.last()
|
self.switch_stack.last()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn push_label_scope(
|
||||||
|
&mut self, name: &str, break_label: Label, continue_label: Option<Label>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
if self.label_scopes.iter().any(|s| s.name == name) {
|
||||||
|
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
|
||||||
|
}
|
||||||
|
self.label_scopes.push(LabelScope {
|
||||||
|
name: name.to_string(),
|
||||||
|
break_label,
|
||||||
|
continue_label,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pop_label_scope(&mut self) {
|
||||||
|
self.label_scopes.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn find_label(&self, name: &str) -> Option<&LabelScope> {
|
||||||
|
self.label_scopes.iter().rev().find(|s| s.name == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queue a label name to be bound to the continue target of the next loop
|
||||||
|
/// emitted as the labeled statement's body. Rejects duplicates in the active
|
||||||
|
/// or pending sets.
|
||||||
|
pub(crate) fn queue_loop_label(&mut self, name: &str) -> Result<(), String> {
|
||||||
|
if self.label_scopes.iter().any(|s| s.name == name) || self.pending_loop_labels.iter().any(|n| n == name) {
|
||||||
|
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
|
||||||
|
}
|
||||||
|
self.pending_loop_labels.push(name.to_string());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drain queued loop labels into active scopes bound to this loop's break and
|
||||||
|
/// continue targets. Returns how many scopes were pushed (to pop after).
|
||||||
|
pub(crate) fn take_pending_loop_labels(&mut self, break_label: Label, continue_label: Label) -> usize {
|
||||||
|
let names = std::mem::take(&mut self.pending_loop_labels);
|
||||||
|
let count = names.len();
|
||||||
|
for name in names {
|
||||||
|
self.label_scopes.push(LabelScope {
|
||||||
|
name,
|
||||||
|
break_label,
|
||||||
|
continue_label: Some(continue_label),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pop_label_scopes(&mut self, n: usize) {
|
||||||
|
for _ in 0..n {
|
||||||
|
self.label_scopes.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn is_builtin(&self, name: &str) -> bool {
|
pub(crate) fn is_builtin(&self, name: &str) -> bool {
|
||||||
self.builtin_reg_map.iter().any(|(n, _)| n == name)
|
self.builtin_reg_map.iter().any(|(n, _)| n == name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,27 @@ impl Compiler {
|
|||||||
pub(in crate::counter) fn count_continue_statement(&self, ctx: &mut CompileCtx) {
|
pub(in crate::counter) fn count_continue_statement(&self, ctx: &mut CompileCtx) {
|
||||||
ctx.projected_pc += 1; // JMP
|
ctx.projected_pc += 1; // JMP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(in crate::counter) fn count_labeled_statement(
|
||||||
|
&self, stmt: &oxide_parser::LabeledStatement<'_>, ctx: &mut CompileCtx,
|
||||||
|
) {
|
||||||
|
let body_is_loop = matches!(
|
||||||
|
stmt.body,
|
||||||
|
Statement::WhileStatement(_)
|
||||||
|
| Statement::DoWhileStatement(_)
|
||||||
|
| Statement::ForStatement(_)
|
||||||
|
| Statement::ForInStatement(_)
|
||||||
|
| Statement::ForOfStatement(_)
|
||||||
|
);
|
||||||
|
if body_is_loop {
|
||||||
|
// Loop body owns its labels; labeled break/continue reuse them.
|
||||||
|
self.count_statement(&stmt.body, ctx);
|
||||||
|
} else {
|
||||||
|
// Non-loop label: consume one id (parity with emit) and place the
|
||||||
|
// LabeledEnd target right after the body for `break label`.
|
||||||
|
let id = ctx.next_label_id();
|
||||||
|
self.count_statement(&stmt.body, ctx);
|
||||||
|
ctx.label_map.insert(Label::LabeledEnd(id), ctx.projected_pc);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ impl Compiler {
|
|||||||
Statement::ClassDeclaration(class) => self.count_class_declaration(class, ctx),
|
Statement::ClassDeclaration(class) => self.count_class_declaration(class, ctx),
|
||||||
Statement::ThrowStatement(ts) => self.count_throw_statement(ts, ctx),
|
Statement::ThrowStatement(ts) => self.count_throw_statement(ts, ctx),
|
||||||
Statement::TryStatement(ts) => self.count_try_statement(ts, ctx),
|
Statement::TryStatement(ts) => self.count_try_statement(ts, ctx),
|
||||||
|
Statement::LabeledStatement(ls) => self.count_labeled_statement(ls, ctx),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,19 @@ impl Compiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(in crate::emitter) fn emit_sequence_expression(
|
||||||
|
&self, seq: &oxide_parser::SequenceExpression, ctx: &mut CompileCtx,
|
||||||
|
) -> Result<u8, String> {
|
||||||
|
// Comma operator: evaluate each expression in order, value is the last.
|
||||||
|
// Emits exactly one expression per element (no extra instruction), so the
|
||||||
|
// projected-PC count in count_sequence_expression stays aligned.
|
||||||
|
let mut last = 0u8;
|
||||||
|
for e in &seq.expressions {
|
||||||
|
last = self.emit_expression(e, ctx)?;
|
||||||
|
}
|
||||||
|
Ok(last)
|
||||||
|
}
|
||||||
|
|
||||||
pub(in crate::emitter) fn emit_unsupported_expression(
|
pub(in crate::emitter) fn emit_unsupported_expression(
|
||||||
&self, expr: &Expression, ctx: &mut CompileCtx,
|
&self, expr: &Expression, ctx: &mut CompileCtx,
|
||||||
) -> Result<u8, String> {
|
) -> Result<u8, String> {
|
||||||
|
|||||||
@@ -967,12 +967,14 @@ impl Compiler {
|
|||||||
Statement::ForInStatement(_) => self.emit_for_in_statement(stmt, ctx),
|
Statement::ForInStatement(_) => self.emit_for_in_statement(stmt, ctx),
|
||||||
Statement::ForOfStatement(_) => self.emit_for_of_statement(stmt, ctx),
|
Statement::ForOfStatement(_) => self.emit_for_of_statement(stmt, ctx),
|
||||||
Statement::SwitchStatement(_) => self.emit_switch_statement(stmt, ctx),
|
Statement::SwitchStatement(_) => self.emit_switch_statement(stmt, ctx),
|
||||||
Statement::BreakStatement(_) => self.emit_break_statement(ctx),
|
Statement::BreakStatement(b) => self.emit_break_statement(b, ctx),
|
||||||
Statement::ContinueStatement(_) => self.emit_continue_statement(ctx),
|
Statement::ContinueStatement(c) => self.emit_continue_statement(c, ctx),
|
||||||
Statement::FunctionDeclaration(_) => self.emit_function_declaration_statement(stmt, ctx),
|
Statement::FunctionDeclaration(_) => self.emit_function_declaration_statement(stmt, ctx),
|
||||||
Statement::ClassDeclaration(_) => self.emit_class_declaration_statement(stmt, ctx),
|
Statement::ClassDeclaration(_) => self.emit_class_declaration_statement(stmt, ctx),
|
||||||
Statement::ThrowStatement(_) => self.emit_throw_statement(stmt, ctx),
|
Statement::ThrowStatement(_) => self.emit_throw_statement(stmt, ctx),
|
||||||
Statement::TryStatement(_) => self.emit_try_statement(stmt, ctx),
|
Statement::TryStatement(_) => self.emit_try_statement(stmt, ctx),
|
||||||
|
Statement::LabeledStatement(ls) => self.emit_labeled_statement(ls, ctx),
|
||||||
|
Statement::EmptyStatement(_) => Ok(None),
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1006,6 +1008,7 @@ impl Compiler {
|
|||||||
Expression::CallExpression(call) => self.emit_call_expression(call, ctx),
|
Expression::CallExpression(call) => self.emit_call_expression(call, ctx),
|
||||||
Expression::ThisExpression(_) => self.emit_this_expression(ctx),
|
Expression::ThisExpression(_) => self.emit_this_expression(ctx),
|
||||||
Expression::RegExpLiteral(lit) => self.emit_reg_exp_literal_expression(lit, ctx),
|
Expression::RegExpLiteral(lit) => self.emit_reg_exp_literal_expression(lit, ctx),
|
||||||
|
Expression::SequenceExpression(seq) => self.emit_sequence_expression(seq, ctx),
|
||||||
_ => self.emit_unsupported_expression(expr, ctx),
|
_ => self.emit_unsupported_expression(expr, ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,16 @@ impl Compiler {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::emitter) fn emit_break_statement(&self, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
pub(in crate::emitter) fn emit_break_statement(
|
||||||
let break_label = if let Some(sw_label) = ctx.current_switch() {
|
&self, stmt: &oxide_parser::BreakStatement, ctx: &mut CompileCtx,
|
||||||
|
) -> Result<Option<u8>, String> {
|
||||||
|
let break_label = if let Some(label) = &stmt.label {
|
||||||
|
let name = label.name.as_str();
|
||||||
|
let scope = ctx
|
||||||
|
.find_label(name)
|
||||||
|
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
|
||||||
|
scope.break_label
|
||||||
|
} else if let Some(sw_label) = ctx.current_switch() {
|
||||||
*sw_label
|
*sw_label
|
||||||
} else {
|
} else {
|
||||||
let (bl, _) = ctx.current_loop().ok_or("break outside switch or loop".to_string())?;
|
let (bl, _) = ctx.current_loop().ok_or("break outside switch or loop".to_string())?;
|
||||||
@@ -42,12 +50,56 @@ impl Compiler {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::emitter) fn emit_continue_statement(&self, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
pub(in crate::emitter) fn emit_continue_statement(
|
||||||
let (_, continue_label) = ctx.current_loop().ok_or("continue outside loop".to_string())?;
|
&self, stmt: &oxide_parser::ContinueStatement, ctx: &mut CompileCtx,
|
||||||
let continue_pos = ctx.resolve_label(*continue_label)?;
|
) -> Result<Option<u8>, String> {
|
||||||
|
let continue_label = if let Some(label) = &stmt.label {
|
||||||
|
let name = label.name.as_str();
|
||||||
|
let scope = ctx
|
||||||
|
.find_label(name)
|
||||||
|
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
|
||||||
|
scope.continue_label.ok_or_else(|| {
|
||||||
|
format!("SyntaxError: Illegal continue statement: '{name}' does not denote an iteration statement")
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
let (_, cl) = ctx.current_loop().ok_or("continue outside loop".to_string())?;
|
||||||
|
*cl
|
||||||
|
};
|
||||||
|
let continue_pos = ctx.resolve_label(continue_label)?;
|
||||||
let offset = (continue_pos as isize) - (ctx.bytecode.len() as isize);
|
let offset = (continue_pos as isize) - (ctx.bytecode.len() as isize);
|
||||||
let offset = ctx.checked_jump_offset(offset);
|
let offset = ctx.checked_jump_offset(offset);
|
||||||
ctx.emit(opcode::encode_jmp(offset));
|
ctx.emit(opcode::encode_jmp(offset));
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(in crate::emitter) fn emit_labeled_statement(
|
||||||
|
&self, stmt: &oxide_parser::LabeledStatement, ctx: &mut CompileCtx,
|
||||||
|
) -> Result<Option<u8>, String> {
|
||||||
|
let name = stmt.label.name.as_str();
|
||||||
|
if Self::is_iteration_statement(&stmt.body) {
|
||||||
|
// Bind the label to the loop's own continue/break targets (the loop
|
||||||
|
// drains the queue and pops the scope after its body).
|
||||||
|
ctx.queue_loop_label(name)?;
|
||||||
|
self.emit_statement(&stmt.body, ctx)?;
|
||||||
|
} else {
|
||||||
|
// Non-loop labeled statement: only `break label` is valid; it targets
|
||||||
|
// the LabeledEnd placed (by the count pass) right after the body.
|
||||||
|
let id = ctx.next_label_id();
|
||||||
|
ctx.push_label_scope(name, Label::LabeledEnd(id), None)?;
|
||||||
|
self.emit_statement(&stmt.body, ctx)?;
|
||||||
|
ctx.pop_label_scope();
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::emitter) fn is_iteration_statement(stmt: &Statement) -> bool {
|
||||||
|
matches!(
|
||||||
|
stmt,
|
||||||
|
Statement::WhileStatement(_)
|
||||||
|
| Statement::DoWhileStatement(_)
|
||||||
|
| Statement::ForStatement(_)
|
||||||
|
| Statement::ForInStatement(_)
|
||||||
|
| Statement::ForOfStatement(_)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ impl Compiler {
|
|||||||
let end_label = Label::WhileEnd(id);
|
let end_label = Label::WhileEnd(id);
|
||||||
|
|
||||||
ctx.push_loop(end_label, start_label);
|
ctx.push_loop(end_label, start_label);
|
||||||
|
let n_labeled = ctx.take_pending_loop_labels(end_label, start_label);
|
||||||
|
|
||||||
let test_reg = self.emit_expression(&wh.test, ctx)?;
|
let test_reg = self.emit_expression(&wh.test, ctx)?;
|
||||||
let end_pos = ctx.resolve_label(end_label)?;
|
let end_pos = ctx.resolve_label(end_label)?;
|
||||||
@@ -26,6 +27,7 @@ impl Compiler {
|
|||||||
let offset = ctx.checked_jump_offset(offset);
|
let offset = ctx.checked_jump_offset(offset);
|
||||||
ctx.emit(opcode::encode_jmp(offset));
|
ctx.emit(opcode::encode_jmp(offset));
|
||||||
|
|
||||||
|
ctx.pop_label_scopes(n_labeled);
|
||||||
ctx.pop_loop();
|
ctx.pop_loop();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -41,6 +43,7 @@ impl Compiler {
|
|||||||
let end_label = Label::DoWhileEnd(id);
|
let end_label = Label::DoWhileEnd(id);
|
||||||
|
|
||||||
ctx.push_loop(end_label, start_label);
|
ctx.push_loop(end_label, start_label);
|
||||||
|
let n_labeled = ctx.take_pending_loop_labels(end_label, start_label);
|
||||||
self.emit_statement(&dw.body, ctx)?;
|
self.emit_statement(&dw.body, ctx)?;
|
||||||
let test_reg = self.emit_expression(&dw.test, ctx)?;
|
let test_reg = self.emit_expression(&dw.test, ctx)?;
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ impl Compiler {
|
|||||||
let offset = ctx.checked_jump_offset(offset);
|
let offset = ctx.checked_jump_offset(offset);
|
||||||
ctx.emit(opcode::encode_jmp_if_true(test_reg, offset));
|
ctx.emit(opcode::encode_jmp_if_true(test_reg, offset));
|
||||||
|
|
||||||
|
ctx.pop_label_scopes(n_labeled);
|
||||||
ctx.pop_loop();
|
ctx.pop_loop();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -65,6 +69,7 @@ impl Compiler {
|
|||||||
let end_label = Label::ForEnd(id);
|
let end_label = Label::ForEnd(id);
|
||||||
|
|
||||||
ctx.push_loop(end_label, update_label);
|
ctx.push_loop(end_label, update_label);
|
||||||
|
let n_labeled = ctx.take_pending_loop_labels(end_label, update_label);
|
||||||
|
|
||||||
if let Some(init) = &fr.init {
|
if let Some(init) = &fr.init {
|
||||||
if let Some(expr) = init.as_expression() {
|
if let Some(expr) = init.as_expression() {
|
||||||
@@ -102,6 +107,7 @@ impl Compiler {
|
|||||||
let offset = ctx.checked_jump_offset(offset);
|
let offset = ctx.checked_jump_offset(offset);
|
||||||
ctx.emit(opcode::encode_jmp(offset));
|
ctx.emit(opcode::encode_jmp(offset));
|
||||||
|
|
||||||
|
ctx.pop_label_scopes(n_labeled);
|
||||||
ctx.pop_loop();
|
ctx.pop_loop();
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -121,6 +127,7 @@ impl Compiler {
|
|||||||
ctx.emit(opcode::encode(OpCode::FOR_IN_INIT, 0, obj_reg, 0));
|
ctx.emit(opcode::encode(OpCode::FOR_IN_INIT, 0, obj_reg, 0));
|
||||||
|
|
||||||
ctx.push_loop(end_label, start_label);
|
ctx.push_loop(end_label, start_label);
|
||||||
|
let n_labeled = ctx.take_pending_loop_labels(end_label, start_label);
|
||||||
|
|
||||||
let done_reg = ctx.alloc_reg();
|
let done_reg = ctx.alloc_reg();
|
||||||
ctx.emit(opcode::encode(OpCode::FOR_IN_DONE, done_reg, 0, 0));
|
ctx.emit(opcode::encode(OpCode::FOR_IN_DONE, done_reg, 0, 0));
|
||||||
@@ -164,6 +171,7 @@ impl Compiler {
|
|||||||
|
|
||||||
ctx.emit(opcode::encode(OpCode::FOR_IN_CLEANUP, 0, 0, 0));
|
ctx.emit(opcode::encode(OpCode::FOR_IN_CLEANUP, 0, 0, 0));
|
||||||
|
|
||||||
|
ctx.pop_label_scopes(n_labeled);
|
||||||
ctx.pop_loop();
|
ctx.pop_loop();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -181,6 +189,7 @@ impl Compiler {
|
|||||||
let iter_src_reg = self.emit_expression(&fo.right, ctx)?;
|
let iter_src_reg = self.emit_expression(&fo.right, ctx)?;
|
||||||
ctx.emit(opcode::encode(OpCode::FOR_OF_INIT, 0, iter_src_reg, 0));
|
ctx.emit(opcode::encode(OpCode::FOR_OF_INIT, 0, iter_src_reg, 0));
|
||||||
ctx.push_loop(end_label, start_label);
|
ctx.push_loop(end_label, start_label);
|
||||||
|
let n_labeled = ctx.take_pending_loop_labels(end_label, start_label);
|
||||||
|
|
||||||
let has_reg = ctx.alloc_reg();
|
let has_reg = ctx.alloc_reg();
|
||||||
ctx.emit(opcode::encode(OpCode::FOR_OF_DONE, has_reg, 0, 0));
|
ctx.emit(opcode::encode(OpCode::FOR_OF_DONE, has_reg, 0, 0));
|
||||||
@@ -217,6 +226,7 @@ impl Compiler {
|
|||||||
let offset = ctx.checked_jump_offset(offset);
|
let offset = ctx.checked_jump_offset(offset);
|
||||||
ctx.emit(opcode::encode_jmp(offset));
|
ctx.emit(opcode::encode_jmp(offset));
|
||||||
ctx.emit(opcode::encode(OpCode::FOR_OF_CLOSE, 0, 0, 0));
|
ctx.emit(opcode::encode(OpCode::FOR_OF_CLOSE, 0, 0, 0));
|
||||||
|
ctx.pop_label_scopes(n_labeled);
|
||||||
ctx.pop_loop();
|
ctx.pop_loop();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ pub(super) fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, i
|
|||||||
pin.left.name.as_str().hash(h);
|
pin.left.name.as_str().hash(h);
|
||||||
hash_expression(&pin.right, h, include_binding_names);
|
hash_expression(&pin.right, h, include_binding_names);
|
||||||
}
|
}
|
||||||
|
Expression::SequenceExpression(seq) => {
|
||||||
|
(seq.expressions.len() as u32).hash(h);
|
||||||
|
for e in &seq.expressions {
|
||||||
|
hash_expression(e, h, include_binding_names);
|
||||||
|
}
|
||||||
|
}
|
||||||
Expression::Identifier(ident) => {
|
Expression::Identifier(ident) => {
|
||||||
if include_binding_names {
|
if include_binding_names {
|
||||||
ident.name.as_str().hash(h);
|
ident.name.as_str().hash(h);
|
||||||
|
|||||||
@@ -32,8 +32,20 @@ pub(super) fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher, inc
|
|||||||
hash_statement(s, h, include_binding_names);
|
hash_statement(s, h, include_binding_names);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::BreakStatement(_) => {}
|
Statement::BreakStatement(b) => {
|
||||||
Statement::ContinueStatement(_) => {}
|
if let Some(label) = &b.label {
|
||||||
|
label.name.as_str().hash(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::ContinueStatement(c) => {
|
||||||
|
if let Some(label) = &c.label {
|
||||||
|
label.name.as_str().hash(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::LabeledStatement(ls) => {
|
||||||
|
ls.label.name.as_str().hash(h);
|
||||||
|
hash_statement(&ls.body, h, include_binding_names);
|
||||||
|
}
|
||||||
Statement::DoWhileStatement(dw) => {
|
Statement::DoWhileStatement(dw) => {
|
||||||
hash_statement(&dw.body, h, include_binding_names);
|
hash_statement(&dw.body, h, include_binding_names);
|
||||||
expression::hash_expression(&dw.test, h, include_binding_names);
|
expression::hash_expression(&dw.test, h, include_binding_names);
|
||||||
|
|||||||
@@ -297,3 +297,18 @@ fn compile_class_static_setter_emits_define_accessor() {
|
|||||||
"static class setter should emit DEFINE_ACCESSOR"
|
"static class setter should emit DEFINE_ACCESSOR"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_labeled_loop_emits_body() {
|
||||||
|
let module = compile_source("outer: while (true) { break outer; }");
|
||||||
|
assert!(!module.bytecode.is_empty(), "labeled loop should produce bytecode");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_empty_statements_are_skipped() {
|
||||||
|
let module = compile_source("; ; var x = 1;");
|
||||||
|
assert!(
|
||||||
|
module.constants.iter().any(|c| matches!(c, Constant::Int(1))),
|
||||||
|
"var after empty statements should still compile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -236,3 +236,72 @@ fn eval_large_adjacent_switches_do_not_collide_labels() {
|
|||||||
source.push_str("}switch(0){case 0:r=r+1;break;}r");
|
source.push_str("}switch(0){case 0:r=r+1;break;}r");
|
||||||
assert_eq!(eval(&source), "2");
|
assert_eq!(eval(&source), "2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_sequence_expression_returns_last() {
|
||||||
|
assert_eq!(eval("var x = 0; (x = 1, x = x + 2, x)"), "3");
|
||||||
|
assert_eq!(eval("(1, 2, 3)"), "3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_sequence_expression_in_for_clauses() {
|
||||||
|
assert_eq!(eval("for (var i = (0, 1); i < 3; i = (i + 1, i + 1)) {} i"), "3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_empty_statement_is_noop() {
|
||||||
|
assert_eq!(eval("while (false); 1"), "1");
|
||||||
|
assert_eq!(eval("; ; var x = 1; x"), "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_labeled_break_exits_loop() {
|
||||||
|
assert_eq!(eval("outer: while (true) { break outer; } 7"), "7");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_labeled_break_exits_block() {
|
||||||
|
assert_eq!(eval("label: { break label; } 5"), "5");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_labeled_continue_loop() {
|
||||||
|
assert_eq!(eval("var i = 0; outer: while (i < 3) { i = i + 1; continue outer; } i"), "3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_labeled_continue_outer_from_inner_loop() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var c = 0; outer: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (j == 1) continue outer; c = c + 1; } } c"),
|
||||||
|
"3"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_labeled_break_outer_from_inner_loop() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var c = 0; outer: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (i == 1 && j == 1) break outer; c = c + 1; } } c"),
|
||||||
|
"4"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_duplicate_label_is_compile_error() {
|
||||||
|
let r = eval("a: a: while (false) {}");
|
||||||
|
assert!(r.contains("compile error") && r.contains("already been declared"), "got: {r}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_continue_to_non_loop_label_is_compile_error() {
|
||||||
|
let r = eval("label: { continue label; }");
|
||||||
|
assert!(
|
||||||
|
r.contains("compile error") && r.contains("does not denote an iteration statement"),
|
||||||
|
"got: {r}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_break_to_undefined_label_is_compile_error() {
|
||||||
|
let r = eval("foo: while (false) {} break bar;");
|
||||||
|
assert!(r.contains("compile error") && r.contains("Undefined label"), "got: {r}");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user