Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions crates/zapcode-core/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ struct Compiler {
local_indices: HashMap<String, usize>,
functions: Vec<CompiledFunction>,
loop_stack: Vec<LoopInfo>,
try_depth: usize,
external_functions: HashSet<String>,
}

struct LoopInfo {
break_patches: Vec<usize>,
continue_patches: Vec<usize>,
try_depth: usize,
}

impl Compiler {
Expand All @@ -47,6 +49,7 @@ impl Compiler {
local_indices: HashMap::new(),
functions: Vec::new(),
loop_stack: Vec::new(),
try_depth: 0,
external_functions,
}
}
Expand Down Expand Up @@ -222,6 +225,7 @@ impl Compiler {
self.loop_stack.push(LoopInfo {
break_patches: Vec::new(),
continue_patches: Vec::new(),
try_depth: self.try_depth,
});

self.compile_expr(test)?;
Expand All @@ -248,6 +252,7 @@ impl Compiler {
self.loop_stack.push(LoopInfo {
break_patches: Vec::new(),
continue_patches: Vec::new(),
try_depth: self.try_depth,
});

for s in body {
Expand Down Expand Up @@ -282,6 +287,7 @@ impl Compiler {
self.loop_stack.push(LoopInfo {
break_patches: Vec::new(),
continue_patches: Vec::new(),
try_depth: self.try_depth,
});

let exit_jump = if let Some(test) = test {
Expand Down Expand Up @@ -329,6 +335,7 @@ impl Compiler {
self.loop_stack.push(LoopInfo {
break_patches: Vec::new(),
continue_patches: Vec::new(),
try_depth: self.try_depth,
});

self.emit(Instruction::Dup);
Expand Down Expand Up @@ -382,9 +389,11 @@ impl Compiler {
} => {
let setup = self.emit(Instruction::SetupTry(0, None));

self.try_depth += 1;
for s in try_body {
self.compile_statement(s)?;
}
self.try_depth -= 1;
self.emit(Instruction::EndTry);
let jump_past_catch = self.emit(Instruction::Jump(0));

Expand Down Expand Up @@ -413,16 +422,34 @@ impl Compiler {
}
}
Statement::Break { .. } => {
let idx = self.emit(Instruction::Jump(0));
if let Some(loop_info) = self.loop_stack.last_mut() {
loop_info.break_patches.push(idx);
let Some(target_try_depth) = self.loop_stack.last().map(|info| info.try_depth)
else {
return Err(ZapcodeError::CompileError(
"illegal break statement (not inside a loop)".to_string(),
));
};
for _ in target_try_depth..self.try_depth {
self.emit(Instruction::EndTry);
}
let idx = self.emit(Instruction::Jump(0));
self.loop_stack.last_mut().unwrap().break_patches.push(idx);
}
Statement::Continue { .. } => {
let idx = self.emit(Instruction::Jump(0));
if let Some(loop_info) = self.loop_stack.last_mut() {
loop_info.continue_patches.push(idx);
let Some(target_try_depth) = self.loop_stack.last().map(|info| info.try_depth)
else {
return Err(ZapcodeError::CompileError(
"illegal continue statement (not inside a loop)".to_string(),
));
};
for _ in target_try_depth..self.try_depth {
self.emit(Instruction::EndTry);
}
let idx = self.emit(Instruction::Jump(0));
self.loop_stack
.last_mut()
.unwrap()
.continue_patches
.push(idx);
}
Statement::FunctionDecl { func_index, .. } => {
self.emit(Instruction::CreateClosure(*func_index));
Expand Down
155 changes: 100 additions & 55 deletions crates/zapcode-core/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ pub struct Vm {
last_load_source: Option<ReceiverSource>,
/// Counter for assigning unique generator IDs.
next_generator_id: u64,
generator_try_handlers: HashMap<u64, Vec<SuspendedTryHandler>>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
Expand All @@ -108,6 +109,11 @@ pub(crate) struct TryInfo {
pub(crate) stack_depth: usize,
}

struct SuspendedTryHandler {
catch_ip: usize,
stack_offset: usize,
}

impl Vm {
fn new(
program: CompiledProgram,
Expand Down Expand Up @@ -135,6 +141,7 @@ impl Vm {
last_global_name: None,
last_load_source: None,
next_generator_id: 0,
generator_try_handlers: HashMap::new(),
}
}

Expand Down Expand Up @@ -181,6 +188,7 @@ impl Vm {
last_global_name: None,
last_load_source: None,
next_generator_id: 0,
generator_try_handlers: HashMap::new(),
}
}

Expand All @@ -203,6 +211,60 @@ impl Vm {
.ok_or_else(|| ZapcodeError::RuntimeError("stack underflow".to_string()))
}

fn pop_call_frame(&mut self) -> Option<CallFrame> {
let frame = self.frames.pop()?;
self.tracker.pop_frame();
let remaining_depth = self.frames.len();
self.try_stack
.retain(|try_info| try_info.frame_depth <= remaining_depth);
Some(frame)
}

fn take_try_handler(&mut self, min_frame_depth: usize) -> Option<TryInfo> {
while let Some(try_info) = self.try_stack.last() {
if try_info.frame_depth > self.frames.len() {
self.try_stack.pop();
continue;
}
if try_info.frame_depth <= min_frame_depth {
return None;
}
return self.try_stack.pop();
}
None
}

fn try_handle_error(&mut self, err: &ZapcodeError, min_frame_depth: usize) -> Result<bool> {
let Some(try_info) = self.take_try_handler(min_frame_depth) else {
return Ok(false);
};

self.continuations.retain(|continuation| {
let callback_frame_index = match continuation {
Continuation::ArrayMap {
callback_frame_index,
..
}
| Continuation::ArrayForEach {
callback_frame_index,
..
} => *callback_frame_index,
};
callback_frame_index < try_info.frame_depth
});
while self.frames.len() > try_info.frame_depth {
self.pop_call_frame();
}
self.stack.truncate(try_info.stack_depth);
self.push(Value::String(Arc::from(err.to_string())))?;
let frame = self
.frames
.last_mut()
.ok_or_else(|| ZapcodeError::RuntimeError("no active call frame".to_string()))?;
frame.ip = try_info.catch_ip;
Ok(true)
}

fn peek(&self) -> Result<&Value> {
self.stack
.last()
Expand Down Expand Up @@ -364,8 +426,7 @@ impl Vm {
return Ok(VmState::Complete(result));
} else {
// Return from function
let frame = self.frames.pop().unwrap();
self.tracker.pop_frame();
let frame = self.pop_call_frame().unwrap();
// If this was a constructor, return `this`
if let Some(this_val) = frame.this_value {
self.stack.truncate(frame.stack_base);
Expand All @@ -392,22 +453,7 @@ impl Vm {
}
}
Err(err) => {
// Try to catch the error
if let Some(try_info) = self.try_stack.pop() {
// Unwind to catch block
while self.frames.len() > try_info.frame_depth {
self.frames.pop();
self.tracker.pop_frame();
}
self.stack.truncate(try_info.stack_depth);

// Push error value
let error_val = Value::String(Arc::from(err.to_string()));
self.push(error_val)?;

// Jump to catch
self.current_frame_mut().ip = try_info.catch_ip;
} else {
if !self.try_handle_error(&err, 0)? {
return Err(err);
}
}
Expand Down Expand Up @@ -590,14 +636,12 @@ impl Vm {
// End of function without explicit return
if self.frames.len() > target_frame_depth + 1 {
// Inner function ended, pop and continue
self.frames.pop();
self.tracker.pop_frame();
self.pop_call_frame();
self.push(Value::Undefined)?;
continue;
} else {
// Our target function ended
self.frames.pop();
self.tracker.pop_frame();
self.pop_call_frame();
return Ok(Value::Undefined);
}
}
Expand All @@ -624,17 +668,7 @@ impl Vm {
}
}
Err(err) => {
// Try to catch the error within the callback
if let Some(try_info) = self.try_stack.pop() {
while self.frames.len() > try_info.frame_depth {
self.frames.pop();
self.tracker.pop_frame();
}
self.stack.truncate(try_info.stack_depth);
let error_val = Value::String(Arc::from(err.to_string()));
self.push(error_val)?;
self.current_frame_mut().ip = try_info.catch_ip;
} else {
if !self.try_handle_error(&err, target_frame_depth)? {
return Err(err);
}
}
Expand Down Expand Up @@ -1033,6 +1067,15 @@ impl Vm {
this_value: None,
receiver_source: None,
});
let frame_depth = self.frames.len();
if let Some(handlers) = self.generator_try_handlers.remove(&gen_obj.id) {
self.try_stack
.extend(handlers.into_iter().map(|handler| TryInfo {
catch_ip: handler.catch_ip,
frame_depth,
stack_depth: stack_base + handler.stack_offset,
}));
Comment thread
TheUncharted marked this conversation as resolved.
}
self.run_generator_until_yield_or_return(gen_obj)
}
}
Expand All @@ -1044,6 +1087,7 @@ impl Vm {
let gen_key = format!("__gen_{}", gen_obj.id);
if gen_obj.done {
self.globals.remove(&gen_key);
self.generator_try_handlers.remove(&gen_obj.id);
} else {
self.globals.insert(gen_key, Value::Generator(gen_obj));
}
Expand Down Expand Up @@ -1074,8 +1118,7 @@ impl Vm {
};
if frame.ip >= instructions.len() {
if self.frames.len() > target_frame_depth + 1 {
let frame = self.frames.pop().unwrap();
self.tracker.pop_frame();
let frame = self.pop_call_frame().unwrap();
if let Some(this_val) = frame.this_value {
self.stack.truncate(frame.stack_base);
self.push(this_val)?;
Expand All @@ -1084,8 +1127,7 @@ impl Vm {
}
continue;
}
let frame = self.frames.pop().unwrap();
self.tracker.pop_frame();
let frame = self.pop_call_frame().unwrap();
self.stack.truncate(frame.stack_base);
let result = self.finish_generator(gen_obj, Value::Undefined);
return Ok(result);
Expand All @@ -1094,9 +1136,24 @@ impl Vm {
if matches!(instr, Instruction::Yield) {
self.current_frame_mut().ip += 1;
let yielded_value = self.pop()?;
let frame = self.frames.pop().unwrap();
self.tracker.pop_frame();
let frame_depth = self.frames.len();
let frame_stack_base = self.current_frame().stack_base;
let try_handlers: Vec<SuspendedTryHandler> = self
.try_stack
.iter()
.filter(|try_info| try_info.frame_depth == frame_depth)
.map(|try_info| SuspendedTryHandler {
catch_ip: try_info.catch_ip,
stack_offset: try_info.stack_depth.saturating_sub(frame_stack_base),
})
.collect();
let frame = self.pop_call_frame().unwrap();
let frame_stack: Vec<Value> = self.stack.drain(frame.stack_base..).collect();
if try_handlers.is_empty() {
self.generator_try_handlers.remove(&gen_obj.id);
} else {
self.generator_try_handlers.insert(gen_obj.id, try_handlers);
}
gen_obj.suspended = Some(SuspendedFrame {
ip: frame.ip,
locals: frame.locals,
Expand All @@ -1110,14 +1167,12 @@ impl Vm {
self.current_frame_mut().ip += 1;
let return_val = self.pop().unwrap_or(Value::Undefined);
if self.frames.len() > target_frame_depth + 1 {
let frame = self.frames.pop().unwrap();
self.tracker.pop_frame();
let frame = self.pop_call_frame().unwrap();
self.stack.truncate(frame.stack_base);
self.push(return_val)?;
continue;
}
let frame = self.frames.pop().unwrap();
self.tracker.pop_frame();
let frame = self.pop_call_frame().unwrap();
self.stack.truncate(frame.stack_base);
let result = self.finish_generator(gen_obj, return_val);
return Ok(result);
Expand All @@ -1138,16 +1193,7 @@ impl Vm {
}
}
Err(err) => {
if let Some(try_info) = self.try_stack.pop() {
while self.frames.len() > try_info.frame_depth {
self.frames.pop();
self.tracker.pop_frame();
}
self.stack.truncate(try_info.stack_depth);
let error_val = Value::String(Arc::from(err.to_string()));
self.push(error_val)?;
self.current_frame_mut().ip = try_info.catch_ip;
} else {
if !self.try_handle_error(&err, target_frame_depth)? {
return Err(err);
}
}
Expand Down Expand Up @@ -1783,8 +1829,7 @@ impl Vm {
return Ok(Some(VmState::Complete(return_val)));
}

let frame = self.frames.pop().unwrap();
self.tracker.pop_frame();
let frame = self.pop_call_frame().unwrap();

// If this was a constructor frame (has this_value), return the
// updated `this` instead of the explicit return value (unless
Expand Down
Loading
Loading