mirror of
https://github.com/voltrevo/ValueScript.git
synced 2026-04-18 03:00:27 -04:00
Compile try-catch
This commit is contained in:
@@ -37,6 +37,11 @@ pub struct LoopLabels {
|
||||
pub break_: Label,
|
||||
}
|
||||
|
||||
pub struct CatchSetting {
|
||||
pub label: Label,
|
||||
pub reg: Register,
|
||||
}
|
||||
|
||||
pub struct FunctionCompiler {
|
||||
pub current: Function,
|
||||
pub definitions: Vec<Definition>,
|
||||
@@ -46,6 +51,11 @@ pub struct FunctionCompiler {
|
||||
pub label_allocator: NameAllocator,
|
||||
pub queue: Queue<QueuedFunction>,
|
||||
pub loop_labels: Vec<LoopLabels>,
|
||||
pub catch_settings: Vec<CatchSetting>,
|
||||
pub end_label: Option<Label>,
|
||||
pub is_returning_register: Option<Register>,
|
||||
pub finally_labels: Vec<Label>,
|
||||
|
||||
pub diagnostics: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
@@ -68,6 +78,10 @@ impl FunctionCompiler {
|
||||
label_allocator: NameAllocator::default(),
|
||||
queue: Queue::new(),
|
||||
loop_labels: vec![],
|
||||
catch_settings: vec![],
|
||||
end_label: None,
|
||||
is_returning_register: None,
|
||||
finally_labels: vec![],
|
||||
diagnostics: vec![],
|
||||
};
|
||||
}
|
||||
@@ -125,6 +139,14 @@ impl FunctionCompiler {
|
||||
return Register::Named(self.reg_allocator.allocate_numbered(&prefix.to_string()));
|
||||
}
|
||||
|
||||
pub fn allocate_numbered_reg_fresh(&mut self, prefix: &str) -> Register {
|
||||
return Register::Named(
|
||||
self
|
||||
.reg_allocator
|
||||
.allocate_numbered_fresh(&prefix.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn release_reg(&mut self, reg: &Register) {
|
||||
match reg {
|
||||
Register::Named(name) => {
|
||||
@@ -256,6 +278,16 @@ impl FunctionCompiler {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(end_label) = self.end_label.as_ref() {
|
||||
self
|
||||
.current
|
||||
.body
|
||||
.push(InstructionOrLabel::Label(end_label.clone()));
|
||||
|
||||
self.end_label = None;
|
||||
self.is_returning_register = None;
|
||||
}
|
||||
|
||||
self.definitions.push(Definition {
|
||||
pointer: definition_pointer,
|
||||
content: DefinitionContent::Function(std::mem::take(&mut self.current)),
|
||||
@@ -452,7 +484,7 @@ impl FunctionCompiler {
|
||||
}
|
||||
Switch(switch) => self.todo(switch.span, "Switch statement"),
|
||||
Throw(_) => {}
|
||||
Try(try_) => self.todo(try_.span, "Try statement"),
|
||||
Try(_) => {}
|
||||
While(while_) => {
|
||||
self.populate_fn_scope_statement(&while_.body, scope);
|
||||
}
|
||||
@@ -717,26 +749,7 @@ impl FunctionCompiler {
|
||||
use swc_ecma_ast::Stmt::*;
|
||||
|
||||
match statement {
|
||||
Block(block) => {
|
||||
let block_scope = scope.nest();
|
||||
self.populate_block_scope(block, &block_scope);
|
||||
|
||||
for stmt in &block.stmts {
|
||||
self.statement(stmt, false, &block_scope);
|
||||
}
|
||||
|
||||
for mapping in block_scope.rc.borrow().name_map.values() {
|
||||
match mapping {
|
||||
MappedName::Register(reg) => {
|
||||
self.release_reg(reg);
|
||||
}
|
||||
MappedName::Definition(_)
|
||||
| MappedName::QueuedFunction(_)
|
||||
| MappedName::Builtin(_)
|
||||
| MappedName::Constant(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Block(block) => self.block_statement(block, scope),
|
||||
Empty(_) => {}
|
||||
Debugger(debugger) => self.todo(debugger.span, "Debugger statement"),
|
||||
With(with) => {
|
||||
@@ -747,21 +760,34 @@ impl FunctionCompiler {
|
||||
});
|
||||
}
|
||||
|
||||
Return(ret_stmt) => match &ret_stmt.arg {
|
||||
None => {
|
||||
// TODO: Skip if fn_last
|
||||
self.push(Instruction::End);
|
||||
Return(ret_stmt) => {
|
||||
match &ret_stmt.arg {
|
||||
None => {}
|
||||
Some(expr) => {
|
||||
let mut expression_compiler = ExpressionCompiler { fnc: self, scope };
|
||||
|
||||
expression_compiler.compile(expr, Some(Register::Return));
|
||||
}
|
||||
}
|
||||
Some(expr) => {
|
||||
let mut expression_compiler = ExpressionCompiler { fnc: self, scope };
|
||||
|
||||
expression_compiler.compile(expr, Some(Register::Return));
|
||||
if !fn_last {
|
||||
if let Some(finally_label) = self.finally_labels.last().cloned() {
|
||||
let is_returning = match self.is_returning_register.clone() {
|
||||
Some(is_returning) => is_returning.clone(),
|
||||
None => {
|
||||
let is_returning = self.allocate_reg(&"_is_returning".to_string());
|
||||
self.is_returning_register = Some(is_returning.clone());
|
||||
is_returning
|
||||
}
|
||||
};
|
||||
|
||||
if !fn_last {
|
||||
self.push(Instruction::Mov(Value::Bool(true), is_returning.clone()));
|
||||
self.push(Instruction::Jmp(finally_label.ref_()));
|
||||
} else {
|
||||
self.push(Instruction::End);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Labeled(labeled) => self.todo(labeled.span, "Labeled statement"),
|
||||
|
||||
@@ -819,7 +845,9 @@ impl FunctionCompiler {
|
||||
|
||||
self.push(instr);
|
||||
}
|
||||
Try(try_) => self.todo(try_.span, "Try statement"),
|
||||
Try(try_) => {
|
||||
self.try_(try_, scope);
|
||||
}
|
||||
While(while_) => {
|
||||
self.while_(while_, scope);
|
||||
}
|
||||
@@ -842,6 +870,27 @@ impl FunctionCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
fn block_statement(&mut self, block: &swc_ecma_ast::BlockStmt, scope: &Scope) {
|
||||
let block_scope = scope.nest();
|
||||
self.populate_block_scope(block, &block_scope);
|
||||
|
||||
for stmt in &block.stmts {
|
||||
self.statement(stmt, false, &block_scope);
|
||||
}
|
||||
|
||||
for mapping in block_scope.rc.borrow().name_map.values() {
|
||||
match mapping {
|
||||
MappedName::Register(reg) => {
|
||||
self.release_reg(reg);
|
||||
}
|
||||
MappedName::Definition(_)
|
||||
| MappedName::QueuedFunction(_)
|
||||
| MappedName::Builtin(_)
|
||||
| MappedName::Constant(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn if_(&mut self, if_: &swc_ecma_ast::IfStmt, scope: &Scope) {
|
||||
let mut expression_compiler = ExpressionCompiler { fnc: self, scope };
|
||||
|
||||
@@ -890,6 +939,157 @@ impl FunctionCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_(&mut self, try_: &swc_ecma_ast::TryStmt, scope: &Scope) {
|
||||
let (catch_label, after_catch_label) = match try_.handler {
|
||||
Some(_) => (
|
||||
Some(Label {
|
||||
name: self.label_allocator.allocate_numbered(&"catch".to_string()),
|
||||
}),
|
||||
Some(Label {
|
||||
name: self
|
||||
.label_allocator
|
||||
.allocate_numbered(&"after_catch".to_string()),
|
||||
}),
|
||||
),
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
let finally_label = match &try_.finalizer {
|
||||
Some(_) => Some(Label {
|
||||
name: self
|
||||
.label_allocator
|
||||
.allocate_numbered(&"finally".to_string()),
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let mut finally_error_reg: Option<Register> = None;
|
||||
|
||||
if let Some(label) = &finally_label {
|
||||
// We use a fresh register here because if we don't put anything in it, it's meaningful. It
|
||||
// tells finally not to re-throw.
|
||||
let reg = self.allocate_numbered_reg_fresh("_finally_error");
|
||||
self.finally_labels.push(label.clone());
|
||||
|
||||
finally_error_reg = Some(reg.clone());
|
||||
|
||||
self.catch_settings.push(CatchSetting {
|
||||
label: label.clone(),
|
||||
reg,
|
||||
});
|
||||
}
|
||||
|
||||
let mut catch_error_reg: Option<Register> = None;
|
||||
|
||||
if let Some(label) = &catch_label {
|
||||
let reg = match try_
|
||||
.handler
|
||||
.as_ref()
|
||||
.expect("catch label without handler")
|
||||
.param
|
||||
{
|
||||
Some(_) => self.allocate_numbered_reg("_error"),
|
||||
None => Register::Ignore,
|
||||
};
|
||||
|
||||
catch_error_reg = Some(reg.clone());
|
||||
|
||||
self.catch_settings.push(CatchSetting {
|
||||
label: label.clone(),
|
||||
reg,
|
||||
});
|
||||
}
|
||||
|
||||
self.apply_catch_setting();
|
||||
self.block_statement(&try_.block, scope);
|
||||
self.pop_catch_setting(); // TODO: Avoid redundant set_catch to our own finally
|
||||
|
||||
if let Some(label) = &after_catch_label {
|
||||
self.push(Instruction::Jmp(label.ref_()));
|
||||
}
|
||||
|
||||
if let Some(catch_clause) = &try_.handler {
|
||||
self.label(catch_label.unwrap());
|
||||
self.apply_catch_setting(); // TODO: Avoid redundant unset_catch
|
||||
let catch_scope = scope.nest();
|
||||
|
||||
if let Some(param) = &catch_clause.param {
|
||||
self.populate_scope_pat(param, &catch_scope);
|
||||
|
||||
let mut ec = ExpressionCompiler {
|
||||
fnc: self,
|
||||
scope: &catch_scope,
|
||||
};
|
||||
|
||||
let pattern_reg = ec.fnc.get_pattern_register(¶m, &catch_scope);
|
||||
|
||||
// TODO: Set up this register through set_catch instead of copying into it
|
||||
ec.fnc.push(Instruction::Mov(
|
||||
Value::Register(catch_error_reg.unwrap()),
|
||||
pattern_reg.clone(),
|
||||
));
|
||||
|
||||
ec.pat(¶m, &pattern_reg, false, &catch_scope);
|
||||
}
|
||||
|
||||
self.block_statement(&catch_clause.body, &catch_scope);
|
||||
|
||||
if let Some(_) = finally_label {
|
||||
self.pop_catch_setting();
|
||||
}
|
||||
|
||||
self.label(after_catch_label.unwrap());
|
||||
|
||||
// TODO: Shouldn't we be releasing registers from the scope when we don't need it anymore?
|
||||
}
|
||||
|
||||
if let Some(finally_clause) = &try_.finalizer {
|
||||
self.label(finally_label.unwrap());
|
||||
self.finally_labels.pop();
|
||||
self.apply_catch_setting();
|
||||
self.block_statement(&finally_clause, scope);
|
||||
|
||||
if let Some(is_returning) = &self.is_returning_register {
|
||||
let end_label = match &self.end_label {
|
||||
Some(end_label) => end_label.clone(),
|
||||
None => {
|
||||
let end_label = Label {
|
||||
name: self.label_allocator.allocate(&"end".to_string()),
|
||||
};
|
||||
|
||||
self.end_label = Some(end_label.clone());
|
||||
end_label
|
||||
}
|
||||
};
|
||||
|
||||
self.push(Instruction::JmpIf(
|
||||
Value::Register(is_returning.clone()),
|
||||
end_label.ref_(),
|
||||
));
|
||||
}
|
||||
|
||||
self.push(Instruction::Throw(Value::Register(
|
||||
finally_error_reg.unwrap(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_catch_setting(&mut self) {
|
||||
if let Some(catch_setting) = self.catch_settings.last() {
|
||||
self.push(Instruction::SetCatch(
|
||||
catch_setting.label.ref_(),
|
||||
catch_setting.reg.clone(),
|
||||
));
|
||||
} else {
|
||||
self.push(Instruction::UnsetCatch);
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_catch_setting(&mut self) {
|
||||
self.catch_settings.pop().expect("no catch setting to pop");
|
||||
self.apply_catch_setting();
|
||||
}
|
||||
|
||||
fn while_(self: &mut Self, while_: &swc_ecma_ast::WhileStmt, scope: &Scope) {
|
||||
let start_label = Label {
|
||||
name: self.label_allocator.allocate_numbered(&"while".to_string()),
|
||||
|
||||
Reference in New Issue
Block a user