Skip to content

Commit

Permalink
implement labels
Browse files Browse the repository at this point in the history
  • Loading branch information
y21 committed Jun 10, 2024
1 parent 316fd1f commit 19576d9
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 16 deletions.
1 change: 1 addition & 0 deletions crates/dash_compiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ dash_lexer = { path = "../dash_lexer", optional = true }
dash_optimizer = { path = "../dash_optimizer" }
dash_log = { path = "../dash_log" }
tracing = "0.1.37"
rustc-hash = "1.1.0"
6 changes: 5 additions & 1 deletion crates/dash_compiler/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ use std::ops::{Deref, DerefMut};

use dash_middle::compiler::instruction as inst;
use dash_middle::compiler::instruction::Instruction;
use dash_middle::interner::Symbol;

use crate::jump_container::JumpContainer;
use crate::{jump_container, FunctionCompiler};

#[derive(PartialOrd, Ord, Hash, Eq, PartialEq, Debug, Clone, Copy)]
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
pub enum Label {
IfEnd,
/// A branch of an if statement
Expand Down Expand Up @@ -39,6 +40,9 @@ pub enum Label {
TryEnd,
InitParamWithDefaultValue,
FinishParamDefaultValueInit,
UserDefinedEnd {
sym: Symbol,
},
}

pub struct InstructionBuilder<'cx, 'interner> {
Expand Down
10 changes: 5 additions & 5 deletions crates/dash_compiler/src/jump_container.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use std::collections::BTreeMap;
use rustc_hash::FxHashMap;

use crate::builder::Label;

/// A container that is responsible for storing and resolving jumps to labels
#[derive(Debug)]
pub struct JumpContainer {
jumps: BTreeMap<Label, Vec<usize>>,
labels: BTreeMap<Label, usize>,
jumps: FxHashMap<Label, Vec<usize>>,
labels: FxHashMap<Label, usize>,
}

impl JumpContainer {
pub fn new() -> Self {
Self {
jumps: BTreeMap::new(),
labels: BTreeMap::new(),
jumps: FxHashMap::default(),
labels: FxHashMap::default(),
}
}
}
Expand Down
18 changes: 16 additions & 2 deletions crates/dash_compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,10 +500,11 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {
StatementKind::Export(e) => self.visit_export_statement(span, e),
StatementKind::Class(c) => self.visit_class_declaration(span, c),
StatementKind::Continue => self.visit_continue(span),
StatementKind::Break => self.visit_break(span),
StatementKind::Break(sym) => self.visit_break(span, sym),
StatementKind::Debugger => self.visit_debugger(span),
StatementKind::Empty => self.visit_empty_statement(),
StatementKind::Switch(s) => self.visit_switch_statement(span, s),
StatementKind::Labelled(label, stmt) => self.visit_labelled(span, label, stmt),
}
}

Expand Down Expand Up @@ -2059,13 +2060,18 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {
Ok(())
}

fn visit_break(&mut self, span: Span) -> Result<(), Error> {
fn visit_break(&mut self, span: Span, sym: Option<Symbol>) -> Result<(), Error> {
let mut ib = InstructionBuilder::new(self);

if ib.current_function().enclosing_finally().is_some() {
unimplementedc!(span, "`break` in a try-finally block");
}

if let Some(sym) = sym {
ib.build_jmp(Label::UserDefinedEnd { sym }, false);
return Ok(());
}

let breakable = *ib
.current_function_mut()
.breakables
Expand Down Expand Up @@ -2314,6 +2320,14 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {

Ok(())
}

fn visit_labelled(&mut self, _: Span, label: Symbol, stmt: Box<Statement>) -> Result<(), Error> {
let mut ib = InstructionBuilder::new(self);
ib.accept(*stmt)?;
ib.current_function_mut()
.add_global_label(Label::UserDefinedEnd { sym: label });
Ok(())
}
}

/// "Naive" switch lowering:
Expand Down
9 changes: 8 additions & 1 deletion crates/dash_middle/src/parser/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,17 @@ pub enum StatementKind {
Continue,
/// Break loop statement
#[display(fmt = "break;")]
Break,
Break(Option<Symbol>),
/// Debugger statement
#[display(fmt = "debugger;")]
Debugger,
/// A labelled statement:
///
/// foo: { break foo; }
///
/// is represented as Labelled(foo, Expr(Block(Break(foo))))
#[display(fmt = "{_0}: {_1}")]
Labelled(Symbol, Box<Statement>),
/// An empty statement
///
/// This is impossible to occur in JavaScript code, however a statement may be folded to an empty statement
Expand Down
8 changes: 6 additions & 2 deletions crates/dash_middle/src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pub trait Visitor<V> {
fn visit_empty_statement(&mut self) -> V;

/// Visits a break statement
fn visit_break(&mut self, span: Span) -> V;
fn visit_break(&mut self, span: Span, sym: Option<Symbol>) -> V;

/// Visits a continue statement
fn visit_continue(&mut self, span: Span) -> V;
Expand All @@ -137,6 +137,9 @@ pub trait Visitor<V> {

/// Visits a switch statement
fn visit_switch_statement(&mut self, span: Span, s: SwitchStatement) -> V;

/// Visits a labelled statement.
fn visit_labelled(&mut self, span: Span, label: Symbol, stmt: Box<Statement>) -> V;
}

pub fn accept_default<T, V: Visitor<T>>(this: &mut V, Statement { kind, span }: Statement) -> T {
Expand All @@ -158,10 +161,11 @@ pub fn accept_default<T, V: Visitor<T>>(this: &mut V, Statement { kind, span }:
StatementKind::Export(e) => this.visit_export_statement(span, e),
StatementKind::Class(c) => this.visit_class_declaration(span, c),
StatementKind::Continue => this.visit_continue(span),
StatementKind::Break => this.visit_break(span),
StatementKind::Break(sym) => this.visit_break(span, sym),
StatementKind::Debugger => this.visit_debugger(span),
StatementKind::Empty => this.visit_empty_statement(),
StatementKind::Switch(s) => this.visit_switch_statement(span, s),
StatementKind::Labelled(l, s) => this.visit_labelled(span, l, s),
}
}

Expand Down
5 changes: 3 additions & 2 deletions crates/dash_optimizer/src/consteval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ impl<'b, 'interner> ConstFunctionEvalCtx<'b, 'interner> {
StatementKind::Class(stmt) => self.visit_class_statement(stmt, func_id),
StatementKind::Switch(stmt) => self.visit_switch_statement(stmt, func_id),
StatementKind::Continue => {}
StatementKind::Break => {}
StatementKind::Break(_) => {}
StatementKind::Debugger => {}
StatementKind::Empty => {}
StatementKind::Labelled(_, stmt) => self.visit_statement(stmt, func_id),
};

if !stmt_has_side_effects(statement) {
Expand Down Expand Up @@ -494,7 +495,7 @@ impl<'b, 'interner> ConstFunctionEvalCtx<'b, 'interner> {
fn stmt_has_side_effects(stmt: &Statement) -> bool {
match &stmt.kind {
StatementKind::Block(BlockStatement(block)) => block.iter().any(stmt_has_side_effects),
StatementKind::Break => true,
StatementKind::Break(_) => true,
StatementKind::Class(Class { .. }) => true, // TODO: can possibly be SE-free
StatementKind::Empty => false,
StatementKind::Expression(expr) => expr_has_side_effects(expr),
Expand Down
3 changes: 2 additions & 1 deletion crates/dash_optimizer/src/type_infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ impl TypeInferCtx {
StatementKind::Class(stmt) => self.visit_class_statement(stmt, func_id),
StatementKind::Switch(stmt) => self.visit_switch_statement(stmt, func_id),
StatementKind::Continue => {}
StatementKind::Break => {}
StatementKind::Break(_) => {}
StatementKind::Debugger => {}
StatementKind::Empty => {}
StatementKind::Labelled(_, s) => self.visit_statement(s, func_id),
}
}

Expand Down
15 changes: 13 additions & 2 deletions crates/dash_parser/src/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,21 @@ impl<'a, 'interner> Parser<'a, 'interner> {
TokenType::Class => self.parse_class().map(StatementKind::Class),
TokenType::Switch => self.parse_switch().map(StatementKind::Switch),
TokenType::Continue => Some(StatementKind::Continue),
TokenType::Break => Some(StatementKind::Break),
TokenType::Break => {
let ident = self.expect_identifier(false);
Some(StatementKind::Break(ident))
}
TokenType::Debugger => Some(StatementKind::Debugger),
TokenType::Semicolon => Some(StatementKind::Empty),
_ => {
other => 'other: {
if let TokenType::Identifier(label) = other {
if self.expect_token_type_and_skip(&[TokenType::Colon], false) {
// `foo: <statement that can be broken out of>`
let stmt = self.parse_statement()?;
break 'other Some(StatementKind::Labelled(label, Box::new(stmt)));
}
}

// We've skipped the current character because of the statement cases that skip the current token
// So we go back, as the skipped token belongs to this expression
self.advance_back();
Expand Down
20 changes: 20 additions & 0 deletions crates/dash_vm/src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,3 +566,23 @@ simple_test!(
"(function() { return\n5 })()",
Value::undefined()
);

simple_test!(
labels,
r"
let order = [];
let i = 0;
a: while (true) {
b: while(true) {
order.push(`i${i}`);
if (i >= 5) break b;
i++;
}
order.push(`o${i}`);
if (i == 10) break a;
i++;
}
assert(order == 'i0,i1,i2,i3,i4,i5,o5,i6,o6,i7,o7,i8,o8,i9,o9,i10,o10');
",
Value::undefined()
);

0 comments on commit 19576d9

Please sign in to comment.