From 40ce26ce1e97cf46c598c7a510fa9c8a2162b5fa Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Mon, 1 Jan 2024 19:41:30 -0800 Subject: Add support for calling functions. So far this only works if the function is defined before it is called. Otherwise, it fails in confusing ways. The problem is that I currently assume identifiers are always variables, but functions are currently never variables. Functions are currently treated as special case when they are declared. I'll need to rethink that... --- demo/script.js | 7 ++++++- src/disasm.rs | 4 ++++ src/js.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ src/runtime.rs | 61 ++++++++++++++++++++++++++++++++++++----------------- 4 files changed, 112 insertions(+), 26 deletions(-) diff --git a/demo/script.js b/demo/script.js index 5f536fd..9e7e9ad 100644 --- a/demo/script.js +++ b/demo/script.js @@ -1,5 +1,5 @@ -function main() { +function lowest_price() { const offers = [{ id: 1, price: 2.01}, { id: 2, price: 2.99 }]; let min_price = null; @@ -15,3 +15,8 @@ function main() { return best_offer; } +function main() { + return lowest_price(); +} + + diff --git a/src/disasm.rs b/src/disasm.rs index 985a88a..8b02771 100644 --- a/src/disasm.rs +++ b/src/disasm.rs @@ -48,6 +48,9 @@ pub fn print_disassembly(bytecode: &ByteCode) { let function_id = iter.next_varuint(); print_op!(iter, pc, "CALL {:08X}", function_id); }, + 0x09 => { // OpType::InvokeDynamic + print_op!(iter, pc, "INVOKEDYNAMIC"); + }, 0x06 => { // OpType::InvokeGenerator let function_id = iter.next_varuint(); print_op!(iter, pc, "GEN {:08X}", function_id); @@ -57,6 +60,7 @@ pub fn print_disassembly(bytecode: &ByteCode) { }, 0x08 => { // OpType::Return print_op!(iter, pc, "RET"); + println!(); }, 0x10 => { // OpType::Push let buf = &iter.bytecode()[pc+1..]; diff --git a/src/js.rs b/src/js.rs index c0a96f8..bd7a36b 100644 --- a/src/js.rs +++ b/src/js.rs @@ -38,6 +38,13 @@ pub struct JsCompiler { max_var: Vec, bytecode: Vec, functions: Vec, + identifiers: Vec>, +} + +#[derive(Clone)] +enum IdentifierType { + Variable(IonValue), + Function(usize), } impl JsCompiler { @@ -50,6 +57,7 @@ impl JsCompiler { max_var: vec![0], bytecode: Vec::new(), functions: Vec::new(), + identifiers: Vec::new(), }; compiler.enter_block(); compiler @@ -63,6 +71,13 @@ impl JsCompiler { }; vars.insert("this".to_string(), 0); self.vars.push(vars); + + let identifiers = if let Some(identifiers) = self.identifiers.last() { + identifiers.clone() + } else { + BTreeMap::new() + }; + self.identifiers.push(identifiers); } fn exit_block(&mut self) { @@ -109,6 +124,15 @@ impl JsCompiler { self.bytecode.push(OpCode::SwapPop.into()); } + fn push_invoke(&mut self, function_id: usize) { + self.bytecode.push(OpCode::Invoke.into()); + self.push_varuint(function_id); + } + + fn push_invoke_dynamic(&mut self) { + self.bytecode.push(OpCode::InvokeDynamic.into()); + } + fn push_yield(&mut self) { self.bytecode.push(OpCode::Yield.into()); } @@ -233,7 +257,9 @@ impl JsCompiler { self.exit_block(); let name_symbol = if let Some(name) = func.name() { - self.to_symbol_id(name.sym()) + let id = self.to_symbol_id(name.sym()); + self.identifiers.last_mut().unwrap().insert(name.sym(), IdentifierType::Function(id)); + id } else { self.new_symbol_id(format!("anonFunction{}", self.functions.len())) }; @@ -255,12 +281,20 @@ impl JsCompiler { match v.binding() { Binding::Pattern(_) => todo!(), Binding::Identifier(ident) => { - let varid = self.get_or_create_variable(ident.sym()); if let Some(init) = v.init() { + if decl.is_const() { + if let Some(value) = self.get_static_expression(init) { + self.identifiers.last_mut() + .unwrap() + .insert(ident.sym(), IdentifierType::Variable(value)); + continue; + } + } self.compile_expression(init); } else { self.push_push(&IonValue::new_null()); } + let varid = self.get_or_create_variable(ident.sym()); self.push_store(varid); } } @@ -387,6 +421,13 @@ impl JsCompiler { fn get_static_expression(&mut self, expression: &Expression) -> Option { match expression { + Expression::Identifier(ident) => { + match self.identifiers.last().unwrap().get(&ident.sym()) { + Some(IdentifierType::Function(id)) => Some(IonValue::new_symbol(*id)), + Some(IdentifierType::Variable(value)) => Some(value.clone()), + None => None, + } + }, Expression::Literal(lit) => Some(self.literal_to_ion(lit)), Expression::ArrayLiteral(alit) => { let mut list = Vec::with_capacity(alit.as_ref().len()); @@ -482,7 +523,6 @@ impl JsCompiler { // I don't expect it will make sense to evaluate these statically. Expression::This => None, - Expression::Identifier(_) => None, Expression::New(_) => None, Expression::Optional(_) => None, @@ -495,8 +535,12 @@ impl JsCompiler { match expression { Expression::This => self.push_load(0), Expression::Identifier(ident) => { - let varid = self.get_or_create_variable(ident.sym()); - self.push_load(varid); + if let Some(value) = self.get_static_expression(expression) { + self.push_push(&value); + } else { + let varid = self.get_or_create_variable(ident.sym()); + self.push_load(varid); + } }, Expression::Literal(lit) => { let value = self.literal_to_ion(lit); @@ -602,7 +646,17 @@ impl JsCompiler { } }, Expression::New(_) => todo!(), - Expression::Call(_) => todo!(), + Expression::Call(call) => { + for arg in call.args() { + self.compile_expression(arg); + } + if let Some(value) = self.get_static_expression(call.function()) { + self.push_invoke(value.to_symbol_id()); + } else { + self.compile_expression(call.function()); + self.push_invoke_dynamic(); + } + }, Expression::SuperCall(_) => todo!(), Expression::ImportCall(_) => todo!(), Expression::Optional(_) => todo!(), diff --git a/src/runtime.rs b/src/runtime.rs index 97de85a..56b565e 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -37,6 +37,11 @@ pub enum OpCode { /// stack: Return = 0x08, + /// Invoke a function + /// operands: + /// stack: [arguments..., function id] -> [return value?] + InvokeDynamic = 0x09, + /// Push a typed Ion value onto the stack. /// operands: T and L /// length @@ -352,6 +357,29 @@ fn execute_frame(mut frame: StackFrame) { }); } +#[inline] +async fn invoke_function(frame: &mut StackFrame, function_id: usize) { + let function = frame.runtime.0.functions.get(&function_id) + .expect("Undefined function"); + let mut args = Vec::with_capacity(function.arguments as usize); + for _ in 0..function.arguments { + args.push(frame.stack.pop_value()); + } + let mut rx = frame.runtime.invoke(function_id, args); + let mut previous_ret = None; + while let Some(ret) = rx.recv().await { + if let Some(previous_ret) = previous_ret.take() { + let mut branch = frame.clone(); + branch.stack.push_value(&previous_ret); + execute_frame(branch); + } + previous_ret = Some(ret); + } + if let Some(ret) = previous_ret.take() { + frame.stack.push_value(&ret); + } +} + async fn execute(frame: &mut StackFrame) -> ExecutionState { if frame.eof() { return ExecutionState::Halt; @@ -393,25 +421,11 @@ async fn execute(frame: &mut StackFrame) -> ExecutionState { }, 0x05 => { // OpType::Invoke let function_id = frame.next_varuint(); - let function = frame.runtime.0.functions.get(&function_id) - .expect("Undefined function"); - let mut args = Vec::with_capacity(function.arguments as usize); - for _ in 0..function.arguments { - args.push(frame.stack.pop_value()); - } - let mut rx = frame.runtime.invoke(function_id, args); - let mut previous_ret = None; - while let Some(ret) = rx.recv().await { - if let Some(previous_ret) = previous_ret.take() { - let mut branch = frame.clone(); - branch.stack.push_value(&previous_ret); - execute_frame(branch); - } - previous_ret = Some(ret); - } - if let Some(ret) = previous_ret.take() { - frame.stack.push_value(&ret); - } + invoke_function(frame, function_id).await; + }, + 0x09 => { // OpType::InvokeDynamic + let function_id = frame.stack.pop_symbol(); + invoke_function(frame, function_id).await; }, 0x06 => { // OpType::InvokeGenerator let function_id = frame.next_varuint(); @@ -785,6 +799,15 @@ impl Stack { value } + pub fn pop_symbol(&mut self) -> usize { + let mut it = IonReader::new(self.0.iter().copied().rev(), 0); + let value = it.next_symbol_id(); + let offset = it.offset(); + self.0.truncate(self.0.len() - offset); + value + } + + #[inline] pub fn push_byte(&mut self, value: u8) { self.0.push(value); -- cgit v1.2.3