feat: 实现 SequenceExpression + 带标签 break/continue + EmptyStatement

This commit is contained in:
2026-06-23 23:00:20 +08:00
parent 5de79b4253
commit 3ea2353194
11 changed files with 284 additions and 9 deletions
+71
View File
@@ -125,6 +125,17 @@ pub(crate) enum Label {
CatchBody(u32),
FinallyBody(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 {
@@ -139,6 +150,10 @@ pub(crate) struct CompileCtx {
pub(crate) loop_stack: Vec<(Label, Label)>,
#[allow(dead_code)]
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) projected_pc: usize,
pub(crate) builtin_reg_map: Vec<(String, u8)>,
@@ -210,6 +225,8 @@ impl CompileCtx {
label_map: HashMap::new(),
loop_stack: Vec::new(),
switch_stack: Vec::new(),
label_scopes: Vec::new(),
pending_loop_labels: Vec::new(),
label_counter: 0,
projected_pc: 0,
builtin_reg_map: Vec::new(),
@@ -468,6 +485,60 @@ impl CompileCtx {
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 {
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) {
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::ThrowStatement(ts) => self.count_throw_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(
&self, expr: &Expression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
+5 -2
View File
@@ -967,12 +967,14 @@ impl Compiler {
Statement::ForInStatement(_) => self.emit_for_in_statement(stmt, ctx),
Statement::ForOfStatement(_) => self.emit_for_of_statement(stmt, ctx),
Statement::SwitchStatement(_) => self.emit_switch_statement(stmt, ctx),
Statement::BreakStatement(_) => self.emit_break_statement(ctx),
Statement::ContinueStatement(_) => self.emit_continue_statement(ctx),
Statement::BreakStatement(b) => self.emit_break_statement(b, ctx),
Statement::ContinueStatement(c) => self.emit_continue_statement(c, ctx),
Statement::FunctionDeclaration(_) => self.emit_function_declaration_statement(stmt, ctx),
Statement::ClassDeclaration(_) => self.emit_class_declaration_statement(stmt, ctx),
Statement::ThrowStatement(_) => self.emit_throw_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),
}
}
@@ -1006,6 +1008,7 @@ impl Compiler {
Expression::CallExpression(call) => self.emit_call_expression(call, ctx),
Expression::ThisExpression(_) => self.emit_this_expression(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),
}
}
@@ -28,8 +28,16 @@ impl Compiler {
Ok(None)
}
pub(in crate::emitter) fn emit_break_statement(&self, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let break_label = if let Some(sw_label) = ctx.current_switch() {
pub(in crate::emitter) fn emit_break_statement(
&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
} else {
let (bl, _) = ctx.current_loop().ok_or("break outside switch or loop".to_string())?;
@@ -42,12 +50,56 @@ impl Compiler {
Ok(None)
}
pub(in crate::emitter) fn emit_continue_statement(&self, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let (_, continue_label) = ctx.current_loop().ok_or("continue outside loop".to_string())?;
let continue_pos = ctx.resolve_label(*continue_label)?;
pub(in crate::emitter) fn emit_continue_statement(
&self, stmt: &oxide_parser::ContinueStatement, ctx: &mut CompileCtx,
) -> 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 = ctx.checked_jump_offset(offset);
ctx.emit(opcode::encode_jmp(offset));
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);
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 end_pos = ctx.resolve_label(end_label)?;
@@ -26,6 +27,7 @@ impl Compiler {
let offset = ctx.checked_jump_offset(offset);
ctx.emit(opcode::encode_jmp(offset));
ctx.pop_label_scopes(n_labeled);
ctx.pop_loop();
Ok(None)
}
@@ -41,6 +43,7 @@ impl Compiler {
let end_label = Label::DoWhileEnd(id);
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)?;
let test_reg = self.emit_expression(&dw.test, ctx)?;
@@ -49,6 +52,7 @@ impl Compiler {
let offset = ctx.checked_jump_offset(offset);
ctx.emit(opcode::encode_jmp_if_true(test_reg, offset));
ctx.pop_label_scopes(n_labeled);
ctx.pop_loop();
Ok(None)
}
@@ -65,6 +69,7 @@ impl Compiler {
let end_label = Label::ForEnd(id);
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(expr) = init.as_expression() {
@@ -102,6 +107,7 @@ impl Compiler {
let offset = ctx.checked_jump_offset(offset);
ctx.emit(opcode::encode_jmp(offset));
ctx.pop_label_scopes(n_labeled);
ctx.pop_loop();
Ok(None)
@@ -121,6 +127,7 @@ impl Compiler {
ctx.emit(opcode::encode(OpCode::FOR_IN_INIT, 0, obj_reg, 0));
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();
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.pop_label_scopes(n_labeled);
ctx.pop_loop();
Ok(None)
}
@@ -181,6 +189,7 @@ impl Compiler {
let iter_src_reg = self.emit_expression(&fo.right, ctx)?;
ctx.emit(opcode::encode(OpCode::FOR_OF_INIT, 0, iter_src_reg, 0));
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();
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);
ctx.emit(opcode::encode_jmp(offset));
ctx.emit(opcode::encode(OpCode::FOR_OF_CLOSE, 0, 0, 0));
ctx.pop_label_scopes(n_labeled);
ctx.pop_loop();
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);
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) => {
if include_binding_names {
ident.name.as_str().hash(h);
+14 -2
View File
@@ -32,8 +32,20 @@ pub(super) fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher, inc
hash_statement(s, h, include_binding_names);
}
}
Statement::BreakStatement(_) => {}
Statement::ContinueStatement(_) => {}
Statement::BreakStatement(b) => {
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) => {
hash_statement(&dw.body, 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"
);
}
#[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"
);
}