From eeaff6a8bd76e2dbb30ae35e741a83de505b5173 Mon Sep 17 00:00:00 2001 From: tiye Date: Thu, 18 Jan 2024 01:54:44 +0800 Subject: [PATCH 01/11] move inspect formatter to method --- README.md | 2 +- src/vm.rs | 48 +++++++++++++++++++++++++++++-------------- tests/parser_tests.rs | 16 ++++++--------- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 57bcd2f..5697e83 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ Calx Binary Edition `0.1`: | `return` | | TODO | | `fn $types $body` | | Global definition | | `assert` | `quit(1)` if not `true` | for testing | -| `inspect | println inspection information | | +| `inspect` | println inspection information | | For `$types`, it can be `($t1 $t2 -> $t3 $t4)`, where supported types are: diff --git a/src/vm.rs b/src/vm.rs index fbc544e..9f7acf0 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -14,6 +14,8 @@ pub struct CalxVM { pub frames: Vec, pub top_frame: CalxFrame, pub imports: CalxImportsDict, + /// extra status to tracking runnnig finished + pub finished: bool, } impl std::fmt::Debug for CalxVM { @@ -41,9 +43,39 @@ impl CalxVM { frames: vec![], top_frame: main_frame, imports, + finished: false, } } + pub fn inspect_display(&self, indent_size: u8) -> String { + let mut output = String::new(); + let indent = "\n".to_owned() + &" ".repeat(indent_size as usize); + fmt::write( + &mut output, + format_args!( + "{indent}Internal frames: {:?}", + self.frames.iter().map(|x| x.name.to_owned()).collect::>() + ), + ) + .expect("inspect display"); + + fmt::write(&mut output, format_args!("{indent}Top frame: {}", self.top_frame.name)).expect("inspect display"); + fmt::write(&mut output, format_args!("{indent}Locals: {:?}", self.top_frame.locals)).expect("inspect display"); + fmt::write(&mut output, format_args!("{indent}Blocks: {:?}", self.top_frame.blocks_track)).expect("inspect display"); + fmt::write(&mut output, format_args!("{indent}Stack({}): {:?}", self.stack.len(), self.stack)).expect("inspect display"); + fmt::write( + &mut output, + format_args!( + "{indent}Sizes: {} + {}", + self.top_frame.initial_stack_size, + self.top_frame.ret_types.len() + ), + ) + .expect("inspect display"); + fmt::write(&mut output, format_args!("{indent}Pointer: {}", self.top_frame.pointer)).expect("inspect display"); + output + } + pub fn run(&mut self, args: Vec) -> Result { // assign function parameters self.top_frame.locals = args; @@ -500,21 +532,7 @@ impl CalxVM { } } CalxInstr::Inspect => { - println!("[ ----------------"); - println!( - " Internal frames: {:?}", - self.frames.iter().map(|x| x.name.to_owned()).collect::>() - ); - println!(" Top frame: {}", self.top_frame.name); - println!(" Locals: {:?}", self.top_frame.locals); - println!(" Blocks: {:?}", self.top_frame.blocks_track); - println!(" Stack({}): {:?}", self.stack.len(), self.stack); - println!( - " Sizes: {} + {}", - self.top_frame.initial_stack_size, - self.top_frame.ret_types.len() - ); - println!(" Pointer: {}", self.top_frame.pointer); + println!("[ ----------------{}", self.inspect_display(2)); println!(" -------------- ]"); } } diff --git a/tests/parser_tests.rs b/tests/parser_tests.rs index d0dc3c1..b80ffec 100644 --- a/tests/parser_tests.rs +++ b/tests/parser_tests.rs @@ -25,18 +25,14 @@ fn test_extracting() -> Result<(), String> { assert_eq!( Cirru::List(extract_nested(&Cirru::List(vec![ - Cirru::leaf("a"), - Cirru::leaf("b"), - Cirru::List(vec![ - Cirru::leaf("c"), - Cirru::leaf("d"), - Cirru::List(vec![Cirru::leaf("e"), Cirru::leaf("f"),]) - ]) + "a".into(), + "b".into(), + Cirru::List(vec![Cirru::leaf("c"), Cirru::leaf("d"), Cirru::List(vec!["e".into(), "f".into(),])]) ]))?), Cirru::List(vec!( - Cirru::List(vec![Cirru::leaf("e"), Cirru::leaf("f")]), - Cirru::List(vec![Cirru::leaf("c"), Cirru::leaf("d")]), - Cirru::List(vec![Cirru::leaf("a"), Cirru::leaf("b")]) + Cirru::List(vec!["e".into(), "f".into()]), + Cirru::List(vec!["c".into(), "d".into()]), + Cirru::List(vec!["a".into(), "b".into()]) )) ); From 028379c76414e87d905ee0cfd7f94eed021f62be Mon Sep 17 00:00:00 2001 From: tiye Date: Thu, 18 Jan 2024 02:07:24 +0800 Subject: [PATCH 02/11] split out step function --- Cargo.toml | 4 +- src/vm.rs | 804 +++++++++++++++++++++++++++-------------------------- 2 files changed, 417 insertions(+), 391 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ad855e8..7ddaad3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,10 @@ exclude = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cirru_parser = "0.1.25" +cirru_parser = "0.1.26" regex = "1.10.2" lazy_static = "1.4.0" -clap = { version = "4.4.13", features = ["derive"] } +clap = { version = "4.4.18", features = ["derive"] } bincode = "2.0.0-rc.3" [profile.release] diff --git a/src/vm.rs b/src/vm.rs index 9f7acf0..b749ae8 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -16,6 +16,7 @@ pub struct CalxVM { pub imports: CalxImportsDict, /// extra status to tracking runnnig finished pub finished: bool, + pub return_value: Calx, } impl std::fmt::Debug for CalxVM { @@ -43,10 +44,16 @@ impl CalxVM { frames: vec![], top_frame: main_frame, imports, + return_value: Calx::Nil, finished: false, } } + pub fn make_return(&mut self, v: Calx) { + self.return_value = v; + self.finished = true; + } + pub fn inspect_display(&self, indent_size: u8) -> String { let mut output = String::new(); let indent = "\n".to_owned() + &" ".repeat(indent_size as usize); @@ -84,32 +91,63 @@ impl CalxVM { // println!("Stack {:?}", self.stack); // println!("-- op {} {:?}", self.stack.len(), instr); - if self.top_frame.pointer >= self.top_frame.instrs.len() { - // println!("status {:?} {}", self.stack, self.top_frame); - self.check_func_return()?; - if self.frames.is_empty() { - return Ok(self.stack.pop().unwrap_or(Calx::Nil)); - } else { - // let prev_frame = self.top_frame; - self.top_frame = self.frames.pop().unwrap(); - } - self.top_frame.pointer += 1; + if self.finished { + return Ok(self.return_value.to_owned()); + } + + let quick_continue = self.step()?; + if quick_continue { continue; } - let instrs = self.top_frame.instrs.to_owned(); - match &instrs[self.top_frame.pointer] { - CalxInstr::Jmp(line) => { + + self.top_frame.pointer += 1; + } + } + + /// run one step, return true if continuing + pub fn step(&mut self) -> Result { + if self.top_frame.pointer >= self.top_frame.instrs.len() { + // println!("status {:?} {}", self.stack, self.top_frame); + self.check_func_return()?; + if self.frames.is_empty() { + let v = self.stack.pop().unwrap_or(Calx::Nil); + self.make_return(v); + return Ok(false); + } else { + // let prev_frame = self.top_frame; + self.top_frame = self.frames.pop().unwrap(); + } + self.top_frame.pointer += 1; + return Ok(true); + } + let instrs = self.top_frame.instrs.to_owned(); + match &instrs[self.top_frame.pointer] { + CalxInstr::Jmp(line) => { + self.top_frame.pointer = line.to_owned(); + return Ok(true); // point reset, goto next loop + } + CalxInstr::JmpIf(line) => { + let v = self.stack_pop()?; + if v == Calx::Bool(true) || v == Calx::I64(1) { self.top_frame.pointer = line.to_owned(); - continue; // point reset, goto next loop + return Ok(true); // point reset, goto next loop } - CalxInstr::JmpIf(line) => { - let v = self.stack_pop()?; - if v == Calx::Bool(true) || v == Calx::I64(1) { - self.top_frame.pointer = line.to_owned(); - continue; // point reset, goto next loop - } + } + CalxInstr::Br(size) => { + self.shrink_blocks_by(*size)?; + + let last_idx = self.top_frame.blocks_track.len() - 1; + if self.top_frame.blocks_track[last_idx].looped { + self.top_frame.pointer = self.top_frame.blocks_track[last_idx].from; + } else { + self.top_frame.pointer = self.top_frame.blocks_track[last_idx].to; } - CalxInstr::Br(size) => { + + return Ok(true); // point reset, goto next loop + } + CalxInstr::BrIf(size) => { + let last_idx = self.stack.len() - 1; + if self.stack[last_idx] == Calx::Bool(true) || self.stack[last_idx] == Calx::I64(1) { self.shrink_blocks_by(*size)?; let last_idx = self.top_frame.blocks_track.len() - 1; @@ -119,426 +157,414 @@ impl CalxVM { self.top_frame.pointer = self.top_frame.blocks_track[last_idx].to; } - continue; // point reset, goto next loop - } - CalxInstr::BrIf(size) => { - let last_idx = self.stack.len() - 1; - if self.stack[last_idx] == Calx::Bool(true) || self.stack[last_idx] == Calx::I64(1) { - self.shrink_blocks_by(*size)?; - - let last_idx = self.top_frame.blocks_track.len() - 1; - if self.top_frame.blocks_track[last_idx].looped { - self.top_frame.pointer = self.top_frame.blocks_track[last_idx].from; - } else { - self.top_frame.pointer = self.top_frame.blocks_track[last_idx].to; - } - - continue; // point reset, goto next loop - } + return Ok(true); // point reset, goto next loop } - CalxInstr::Block { - looped, - from, - to, - params_types, - ret_types, - } => { - if self.stack.len() < params_types.len() { - return Err(self.gen_err(format!("no enough data on stack {:?} for {:?}", self.stack, params_types))); - } - self.top_frame.blocks_track.push(BlockData { - looped: looped.to_owned(), - params_types: params_types.to_owned(), - ret_types: ret_types.to_owned(), - from: from.to_owned(), - to: to.to_owned(), - initial_stack_size: self.stack.len() - params_types.len(), - }); - self.check_stack_for_block(params_types)?; - } - CalxInstr::BlockEnd(looped) => { - if *looped { - return Err(self.gen_err(String::from("loop end expected to be branched"))); - } - let last_block = self.top_frame.blocks_track.pop().unwrap(); - if self.stack.len() != last_block.initial_stack_size + last_block.ret_types.len() { - return Err(self.gen_err(format!( - "block-end {:?} expected initial size {} plus {:?}, got stack size {}, in\n {}", - last_block, - last_block.initial_stack_size, - last_block.ret_types, - self.stack.len(), - self.top_frame - ))); - } + } + CalxInstr::Block { + looped, + from, + to, + params_types, + ret_types, + } => { + if self.stack.len() < params_types.len() { + return Err(self.gen_err(format!("no enough data on stack {:?} for {:?}", self.stack, params_types))); + } + self.top_frame.blocks_track.push(BlockData { + looped: looped.to_owned(), + params_types: params_types.to_owned(), + ret_types: ret_types.to_owned(), + from: from.to_owned(), + to: to.to_owned(), + initial_stack_size: self.stack.len() - params_types.len(), + }); + self.check_stack_for_block(params_types)?; + } + CalxInstr::BlockEnd(looped) => { + if *looped { + return Err(self.gen_err(String::from("loop end expected to be branched"))); + } + let last_block = self.top_frame.blocks_track.pop().unwrap(); + if self.stack.len() != last_block.initial_stack_size + last_block.ret_types.len() { + return Err(self.gen_err(format!( + "block-end {:?} expected initial size {} plus {:?}, got stack size {}, in\n {}", + last_block, + last_block.initial_stack_size, + last_block.ret_types, + self.stack.len(), + self.top_frame + ))); } - CalxInstr::LocalSet(idx) => { - let v = self.stack_pop()?; - if *idx >= self.top_frame.locals.len() { - return Err(self.gen_err(format!("out of bound in local.set {} for {:?}", idx, self.top_frame.locals))); - } else { - self.top_frame.locals[*idx] = v - } + } + CalxInstr::LocalSet(idx) => { + let v = self.stack_pop()?; + if *idx >= self.top_frame.locals.len() { + return Err(self.gen_err(format!("out of bound in local.set {} for {:?}", idx, self.top_frame.locals))); + } else { + self.top_frame.locals[*idx] = v } - CalxInstr::LocalTee(idx) => { - let v = self.stack_pop()?; - if *idx >= self.top_frame.locals.len() { - return Err(self.gen_err(format!("out of bound in local.tee {}", idx))); - } else { - self.top_frame.locals[*idx] = v.to_owned() - } - self.stack_push(v); + } + CalxInstr::LocalTee(idx) => { + let v = self.stack_pop()?; + if *idx >= self.top_frame.locals.len() { + return Err(self.gen_err(format!("out of bound in local.tee {}", idx))); + } else { + self.top_frame.locals[*idx] = v.to_owned() } - CalxInstr::LocalGet(idx) => { - if *idx < self.top_frame.locals.len() { - self.stack_push(self.top_frame.locals[*idx].to_owned()) - } else { - return Err(self.gen_err(format!("invalid index for local.get {}", idx))); - } + self.stack_push(v); + } + CalxInstr::LocalGet(idx) => { + if *idx < self.top_frame.locals.len() { + self.stack_push(self.top_frame.locals[*idx].to_owned()) + } else { + return Err(self.gen_err(format!("invalid index for local.get {}", idx))); } - CalxInstr::Return => { - // return values are moved to a temp space - let mut ret_stack: Vec = vec![]; + } + CalxInstr::Return => { + // return values are moved to a temp space + let mut ret_stack: Vec = vec![]; - let ret_size = self.top_frame.ret_types.len(); - for _ in 0..ret_size { - let v = self.stack_pop()?; - ret_stack.insert(0, v); - } + let ret_size = self.top_frame.ret_types.len(); + for _ in 0..ret_size { + let v = self.stack_pop()?; + ret_stack.insert(0, v); + } - self.check_func_return()?; + self.check_func_return()?; - if self.frames.is_empty() { - // top frame return, just return value - return match ret_stack.first() { - Some(x) => Ok(x.to_owned()), - None => Err(self.gen_err("return without value".to_owned())), - }; - } else { - // let prev_frame = self.top_frame; - self.top_frame = self.frames.pop().unwrap(); - // push return values back - for v in ret_stack { - self.stack_push(v); + if self.frames.is_empty() { + // top frame return, just return value + return match ret_stack.first() { + Some(x) => { + self.make_return(x.to_owned()); + Ok(false) } + None => Err(self.gen_err("return without value".to_owned())), + }; + } else { + // let prev_frame = self.top_frame; + self.top_frame = self.frames.pop().unwrap(); + // push return values back + for v in ret_stack { + self.stack_push(v); } } - CalxInstr::LocalNew => self.top_frame.locals.push(Calx::Nil), - CalxInstr::GlobalSet(idx) => { - let v = self.stack_pop()?; - if self.globals.len() >= *idx { - return Err(self.gen_err(format!("out of bound in global.set {}", idx))); - } else { - self.globals[*idx] = v - } + } + CalxInstr::LocalNew => self.top_frame.locals.push(Calx::Nil), + CalxInstr::GlobalSet(idx) => { + let v = self.stack_pop()?; + if self.globals.len() >= *idx { + return Err(self.gen_err(format!("out of bound in global.set {}", idx))); + } else { + self.globals[*idx] = v } - CalxInstr::GlobalGet(idx) => { - if *idx < self.globals.len() { - self.stack_push(self.globals[*idx].to_owned()) - } else { - return Err(self.gen_err(format!("invalid index for global.get {}", idx))); - } + } + CalxInstr::GlobalGet(idx) => { + if *idx < self.globals.len() { + self.stack_push(self.globals[*idx].to_owned()) + } else { + return Err(self.gen_err(format!("invalid index for global.get {}", idx))); } - CalxInstr::GlobalNew => self.globals.push(Calx::Nil), - CalxInstr::Const(v) => self.stack_push(v.to_owned()), - CalxInstr::Dup => { - self.stack_push(self.stack[self.stack.len() - 1].to_owned()); - } - CalxInstr::Drop => { - let _ = self.stack_pop()?; - } - CalxInstr::IntAdd => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; - match (&(self.stack[last_idx]), &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 + n2), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to add, {:?} {:?}", self.stack[last_idx], v2))), - } + } + CalxInstr::GlobalNew => self.globals.push(Calx::Nil), + CalxInstr::Const(v) => self.stack_push(v.to_owned()), + CalxInstr::Dup => { + self.stack_push(self.stack[self.stack.len() - 1].to_owned()); + } + CalxInstr::Drop => { + let _ = self.stack_pop()?; + } + CalxInstr::IntAdd => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; + match (&(self.stack[last_idx]), &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 + n2), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to add, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::IntMul => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 * n2), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to multiply, {:?} {:?}", self.stack[last_idx], v2))), - } + } + CalxInstr::IntMul => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; + match (&self.stack[last_idx], &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 * n2), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to multiply, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::IntDiv => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 / n2), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to divide, {:?} {:?}", self.stack[last_idx], v2))), - } + } + CalxInstr::IntDiv => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; + match (&self.stack[last_idx], &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 / n2), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to divide, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::IntRem => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64((*n1).rem(n2)), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to add, {:?} {:?}", self.stack[last_idx], v2))), - } + } + CalxInstr::IntRem => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; + match (&self.stack[last_idx], &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64((*n1).rem(n2)), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to add, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::IntNeg => { - let last_idx = self.stack.len() - 1; - if let Calx::I64(n) = self.stack[last_idx] { - self.stack[last_idx] = Calx::I64(-n) - } else { - return Err(self.gen_err(format!("expected int, got {}", self.stack[last_idx]))); - } + } + CalxInstr::IntNeg => { + let last_idx = self.stack.len() - 1; + if let Calx::I64(n) = self.stack[last_idx] { + self.stack[last_idx] = Calx::I64(-n) + } else { + return Err(self.gen_err(format!("expected int, got {}", self.stack[last_idx]))); } - CalxInstr::IntShr => { - let bits = self.stack_pop()?; - let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &bits) { - (Calx::I64(n), Calx::I64(b)) => self.stack[last_idx] = Calx::I64(n.checked_shr(*b as u32).unwrap()), - (_, _) => return Err(self.gen_err(format!("invalid number for SHR, {:?} {:?}", self.stack[last_idx], bits))), - } + } + CalxInstr::IntShr => { + let bits = self.stack_pop()?; + let last_idx = self.stack.len() - 1; + match (&self.stack[last_idx], &bits) { + (Calx::I64(n), Calx::I64(b)) => self.stack[last_idx] = Calx::I64(n.checked_shr(*b as u32).unwrap()), + (_, _) => return Err(self.gen_err(format!("invalid number for SHR, {:?} {:?}", self.stack[last_idx], bits))), } - CalxInstr::IntShl => { - let bits = self.stack_pop()?; - let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &bits) { - (Calx::I64(n), Calx::I64(b)) => self.stack[last_idx] = Calx::I64(n.checked_shl(*b as u32).unwrap()), - (_, _) => return Err(self.gen_err(format!("invalid number for SHL, {:?} {:?}", self.stack[last_idx], bits))), - } + } + CalxInstr::IntShl => { + let bits = self.stack_pop()?; + let last_idx = self.stack.len() - 1; + match (&self.stack[last_idx], &bits) { + (Calx::I64(n), Calx::I64(b)) => self.stack[last_idx] = Calx::I64(n.checked_shl(*b as u32).unwrap()), + (_, _) => return Err(self.gen_err(format!("invalid number for SHL, {:?} {:?}", self.stack[last_idx], bits))), } - CalxInstr::IntEq => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; + } + CalxInstr::IntEq => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 == n2), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to eq compare, {:?} {:?}", self.stack[last_idx], v2))), - } + match (&self.stack[last_idx], &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 == n2), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to eq compare, {:?} {:?}", self.stack[last_idx], v2))), } + } - CalxInstr::IntNe => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 != n2), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to ne compare, {:?} {:?}", self.stack[last_idx], v2))), - } + CalxInstr::IntNe => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; + match (&self.stack[last_idx], &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 != n2), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to ne compare, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::IntLt => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 < n2), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to le compare, {:?} {:?}", self.stack[last_idx], v2))), - } + } + CalxInstr::IntLt => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; + match (&self.stack[last_idx], &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 < n2), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to le compare, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::IntLe => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 <= n2), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to le compare, {:?} {:?}", self.stack[last_idx], v2))), - } + } + CalxInstr::IntLe => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; + match (&self.stack[last_idx], &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 <= n2), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to le compare, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::IntGt => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; + } + CalxInstr::IntGt => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 > n2), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to gt compare, {:?} {:?}", self.stack[last_idx], v2))), - } + match (&self.stack[last_idx], &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 > n2), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to gt compare, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::IntGe => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; + } + CalxInstr::IntGe => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 >= n2), - (_, _) => return Err(self.gen_err(format!("expected 2 integers to ge compare, {:?} {:?}", self.stack[last_idx], v2))), - } + match (&self.stack[last_idx], &v2) { + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::Bool(n1 >= n2), + (_, _) => return Err(self.gen_err(format!("expected 2 integers to ge compare, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::Add => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; + } + CalxInstr::Add => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::F64(n1), Calx::F64(n2)) => self.stack[last_idx] = Calx::F64(n1 + n2), - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 + n2), - (_, _) => return Err(self.gen_err(format!("expected 2 numbers to +, {:?} {:?}", self.stack[last_idx], v2))), - } + match (&self.stack[last_idx], &v2) { + (Calx::F64(n1), Calx::F64(n2)) => self.stack[last_idx] = Calx::F64(n1 + n2), + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 + n2), + (_, _) => return Err(self.gen_err(format!("expected 2 numbers to +, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::Mul => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; + } + CalxInstr::Mul => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::F64(n1), Calx::F64(n2)) => self.stack[last_idx] = Calx::F64(n1 * n2), - (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 * n2), - (_, _) => return Err(self.gen_err(format!("expected 2 numbers to multiply, {:?} {:?}", self.stack[last_idx], v2))), - } + match (&self.stack[last_idx], &v2) { + (Calx::F64(n1), Calx::F64(n2)) => self.stack[last_idx] = Calx::F64(n1 * n2), + (Calx::I64(n1), Calx::I64(n2)) => self.stack[last_idx] = Calx::I64(n1 * n2), + (_, _) => return Err(self.gen_err(format!("expected 2 numbers to multiply, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::Div => { - // reversed order - let v2 = self.stack_pop()?; - let last_idx = self.stack.len() - 1; + } + CalxInstr::Div => { + // reversed order + let v2 = self.stack_pop()?; + let last_idx = self.stack.len() - 1; - match (&self.stack[last_idx], &v2) { - (Calx::F64(n1), Calx::F64(n2)) => self.stack[last_idx] = Calx::F64(n1 / n2), - (_, _) => return Err(self.gen_err(format!("expected 2 numbers to divide, {:?} {:?}", self.stack[last_idx], v2))), - } - } - CalxInstr::Neg => { - let last_idx = self.stack.len() - 1; - if let Calx::F64(n) = self.stack[last_idx] { - self.stack[last_idx] = Calx::F64(-n) - } else { - return Err(self.gen_err(format!("expected float, got {}", self.stack[last_idx]))); - } - } - CalxInstr::NewList => { - // TODO - } - CalxInstr::ListGet => { - // TODO - } - CalxInstr::ListSet => { - // TODO + match (&self.stack[last_idx], &v2) { + (Calx::F64(n1), Calx::F64(n2)) => self.stack[last_idx] = Calx::F64(n1 / n2), + (_, _) => return Err(self.gen_err(format!("expected 2 numbers to divide, {:?} {:?}", self.stack[last_idx], v2))), } - CalxInstr::NewLink => { - // TODO - } - CalxInstr::And => { - // TODO - } - CalxInstr::Or => { - // TODO - } - CalxInstr::Not => { - // TODO + } + CalxInstr::Neg => { + let last_idx = self.stack.len() - 1; + if let Calx::F64(n) = self.stack[last_idx] { + self.stack[last_idx] = Calx::F64(-n) + } else { + return Err(self.gen_err(format!("expected float, got {}", self.stack[last_idx]))); } - CalxInstr::Call(f_name) => { - // println!("frame size: {}", self.frames.len()); - match find_func(&self.funcs, f_name) { - Some(f) => { - let instrs = f.instrs.to_owned(); - let ret_types = f.ret_types.to_owned(); - let f_name = f.name.to_owned(); - let mut locals: Vec = vec![]; - for _ in 0..f.params_types.len() { - let v = self.stack_pop()?; - locals.insert(0, v); - } - self.frames.push(self.top_frame.to_owned()); - self.top_frame = CalxFrame { - name: f_name, - blocks_track: vec![], - initial_stack_size: self.stack.len(), - locals, - pointer: 0, - instrs, - ret_types, - }; - - // start in new frame - continue; + } + CalxInstr::NewList => { + // TODO + } + CalxInstr::ListGet => { + // TODO + } + CalxInstr::ListSet => { + // TODO + } + CalxInstr::NewLink => { + // TODO + } + CalxInstr::And => { + // TODO + } + CalxInstr::Or => { + // TODO + } + CalxInstr::Not => { + // TODO + } + CalxInstr::Call(f_name) => { + // println!("frame size: {}", self.frames.len()); + match find_func(&self.funcs, f_name) { + Some(f) => { + let instrs = f.instrs.to_owned(); + let ret_types = f.ret_types.to_owned(); + let f_name = f.name.to_owned(); + let mut locals: Vec = vec![]; + for _ in 0..f.params_types.len() { + let v = self.stack_pop()?; + locals.insert(0, v); } - None => return Err(self.gen_err(format!("cannot find function named: {}", f_name))), + self.frames.push(self.top_frame.to_owned()); + self.top_frame = CalxFrame { + name: f_name, + blocks_track: vec![], + initial_stack_size: self.stack.len(), + locals, + pointer: 0, + instrs, + ret_types, + }; + + // start in new frame + return Ok(true); } + None => return Err(self.gen_err(format!("cannot find function named: {}", f_name))), } - CalxInstr::ReturnCall(f_name) => { - // println!("frame size: {}", self.frames.len()); - match find_func(&self.funcs, f_name) { - Some(f) => { - // println!("examine stack: {:?}", self.stack); - let instrs = f.instrs.to_owned(); - let ret_types = f.ret_types.to_owned(); - let f_name = f.name.to_owned(); - let mut locals: Vec = vec![]; - for _ in 0..f.params_types.len() { - let v = self.stack_pop()?; - locals.insert(0, v); - } - let prev_frame = &self.top_frame; - if prev_frame.initial_stack_size != self.stack.len() { - return Err(self.gen_err(format!( - "expected constant initial stack size: {}, got: {}", - prev_frame.initial_stack_size, - self.stack.len() - ))); - } - self.top_frame = CalxFrame { - name: f_name, - blocks_track: vec![], - initial_stack_size: self.stack.len(), - locals, - pointer: 0, - instrs, - ret_types, - }; - - // start in new frame - continue; + } + CalxInstr::ReturnCall(f_name) => { + // println!("frame size: {}", self.frames.len()); + match find_func(&self.funcs, f_name) { + Some(f) => { + // println!("examine stack: {:?}", self.stack); + let instrs = f.instrs.to_owned(); + let ret_types = f.ret_types.to_owned(); + let f_name = f.name.to_owned(); + let mut locals: Vec = vec![]; + for _ in 0..f.params_types.len() { + let v = self.stack_pop()?; + locals.insert(0, v); } - None => return Err(self.gen_err(format!("cannot find function named: {}", f_name))), - } - } - CalxInstr::CallImport(f_name) => match self.imports.to_owned().get(f_name) { - None => return Err(self.gen_err(format!("missing imported function {}", f_name))), - Some((f, size)) => { - if self.stack.len() < *size { + let prev_frame = &self.top_frame; + if prev_frame.initial_stack_size != self.stack.len() { return Err(self.gen_err(format!( - "imported function {} expected {} arguemtns, found {} on stack", - f_name, - size, + "expected constant initial stack size: {}, got: {}", + prev_frame.initial_stack_size, self.stack.len() ))); } - let mut args: Vec = vec![]; - for _ in 0..*size { - let item = self.stack_pop()?; - args.insert(0, item); - } - let v = f(args.to_owned())?; - self.stack_push(v); + self.top_frame = CalxFrame { + name: f_name, + blocks_track: vec![], + initial_stack_size: self.stack.len(), + locals, + pointer: 0, + instrs, + ret_types, + }; + + // start in new frame + return Ok(true); } - }, - CalxInstr::Unreachable => { - unreachable!("Unexpected from op") - } - CalxInstr::Nop => { - // Noop - } - CalxInstr::Quit(code) => std::process::exit(*code as i32), - CalxInstr::Echo => { - let v = self.stack_pop()?; - println!("{}", v); + None => return Err(self.gen_err(format!("cannot find function named: {}", f_name))), } - CalxInstr::Assert(message) => { - let v = self.stack_pop()?; - if v == Calx::Bool(true) || v == Calx::I64(1) { - // Ok - } else { - return Err(self.gen_err(format!("Failed assertion: {}", message))); + } + CalxInstr::CallImport(f_name) => match self.imports.to_owned().get(f_name) { + None => return Err(self.gen_err(format!("missing imported function {}", f_name))), + Some((f, size)) => { + if self.stack.len() < *size { + return Err(self.gen_err(format!( + "imported function {} expected {} arguemtns, found {} on stack", + f_name, + size, + self.stack.len() + ))); } + let mut args: Vec = vec![]; + for _ in 0..*size { + let item = self.stack_pop()?; + args.insert(0, item); + } + let v = f(args.to_owned())?; + self.stack_push(v); } - CalxInstr::Inspect => { - println!("[ ----------------{}", self.inspect_display(2)); - println!(" -------------- ]"); + }, + CalxInstr::Unreachable => { + unreachable!("Unexpected from op") + } + CalxInstr::Nop => { + // Noop + } + CalxInstr::Quit(code) => std::process::exit(*code as i32), + CalxInstr::Echo => { + let v = self.stack_pop()?; + println!("{}", v); + } + CalxInstr::Assert(message) => { + let v = self.stack_pop()?; + if v == Calx::Bool(true) || v == Calx::I64(1) { + // Ok + } else { + return Err(self.gen_err(format!("Failed assertion: {}", message))); } } - - self.top_frame.pointer += 1; + CalxInstr::Inspect => { + println!("[ ----------------{}", self.inspect_display(2)); + println!(" -------------- ]"); + } } + + Ok(false) } pub fn preprocess(&mut self, verbose: bool) -> Result<(), String> { From 3ca489952f62f0b739376cde21411b6461c94131 Mon Sep 17 00:00:00 2001 From: tiye Date: Sun, 21 Jan 2024 16:38:09 +0800 Subject: [PATCH 03/11] split data into multiple files --- Cargo.toml | 4 + src/bin/{calx.rs => cli.rs} | 19 +- src/calx.rs | 80 +++++++++ src/lib.rs | 6 +- src/parser.rs | 4 +- src/primes.rs | 350 ------------------------------------ src/util.rs | 2 +- src/vm.rs | 150 ++++++++++------ src/vm/block_data.rs | 20 +++ src/vm/frame.rs | 48 +++++ src/vm/func.rs | 42 +++++ src/vm/instr.rs | 165 +++++++++++++++++ 12 files changed, 480 insertions(+), 410 deletions(-) rename src/bin/{calx.rs => cli.rs} (85%) create mode 100644 src/calx.rs delete mode 100644 src/primes.rs create mode 100644 src/vm/block_data.rs create mode 100644 src/vm/frame.rs create mode 100644 src/vm/func.rs create mode 100644 src/vm/instr.rs diff --git a/Cargo.toml b/Cargo.toml index 7ddaad3..4b86398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,10 @@ repository = "https://github.com/calcit-lang/calx-vm.rs" readme = "README.md" exclude = [] +[[bin]] +name = "calx" +path = "src/bin/cli.rs" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/src/bin/calx.rs b/src/bin/cli.rs similarity index 85% rename from src/bin/calx.rs rename to src/bin/cli.rs index f571e67..f2d6f74 100644 --- a/src/bin/calx.rs +++ b/src/bin/cli.rs @@ -5,7 +5,18 @@ use std::time::Instant; use cirru_parser::{parse, Cirru}; use clap::{arg, Parser}; -use calx_vm::{log_calx_value, parse_function, Calx, CalxBinaryProgram, CalxFunc, CalxImportsDict, CalxVM, CALX_BINARY_EDITION}; +use calx_vm::{log_calx_value, parse_function, Calx, CalxFunc, CalxImportsDict, CalxVM, CALX_INSTR_EDITION}; + +use bincode::{Decode, Encode}; + +/// binary format for saving calx program +/// TODO this is not a valid file format that requires magic code +#[derive(Debug, Clone, PartialEq, PartialOrd, Encode, Decode)] +pub struct CalxBinaryProgram { + /// updates as instructions update + pub edition: String, + pub fns: Vec, +} /// Simple program to greet a person #[derive(Parser, Debug)] @@ -44,13 +55,13 @@ fn main() -> Result<(), String> { let program: CalxBinaryProgram = bincode::decode_from_slice(&code, bincode::config::standard()) .expect("decode functions from binary") .0; - if program.edition == CALX_BINARY_EDITION { + if program.edition == CALX_INSTR_EDITION { println!("Calx Edition: {}", program.edition); fns = program.fns; } else { return Err(format!( "Runner uses binary edition {}, binary encoded in {}", - CALX_BINARY_EDITION, program.edition + CALX_INSTR_EDITION, program.edition )); } } else { @@ -69,7 +80,7 @@ fn main() -> Result<(), String> { if emit_binary.is_some() { let program = CalxBinaryProgram { - edition: CALX_BINARY_EDITION.to_string(), + edition: CALX_INSTR_EDITION.to_string(), fns, }; let buf = bincode::encode_to_vec(program, bincode::config::standard()).map_err(|e| e.to_string())?; diff --git a/src/calx.rs b/src/calx.rs new file mode 100644 index 0000000..4d254bc --- /dev/null +++ b/src/calx.rs @@ -0,0 +1,80 @@ +use core::fmt; + +use bincode::{Decode, Encode}; + +/// Simplied from Calcit, but trying to be basic and mutable +#[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] +pub enum Calx { + Nil, + Bool(bool), + I64(i64), + F64(f64), + Str(String), + List(Vec), + // to simultate linked structures + // Link(Box, Box, Box), +} + +impl Calx { + // for runtime type checking + pub fn typed_as(&self, t: CalxType) -> bool { + match self { + Calx::Nil => t == CalxType::Nil, + Calx::Bool(_) => t == CalxType::Bool, + Calx::I64(_) => t == CalxType::I64, + Calx::F64(_) => t == CalxType::F64, + Calx::Str(_) => t == CalxType::Str, + Calx::List(_) => t == CalxType::List, + // Calx::Link(_, _, _) => t == CalxType::Link, + } + } + + pub fn truthy(&self) -> bool { + match self { + Calx::Nil => false, + Calx::Bool(b) => *b, + Calx::I64(n) => *n != 0, + Calx::F64(n) => *n != 0.0, + Calx::Str(_) => false, + Calx::List(_) => false, + // Calx::Link(_, _, _) => true, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Decode, Encode)] +pub enum CalxType { + Nil, + Bool, + I64, + F64, + Str, + List, + Link, +} + +impl fmt::Display for Calx { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Calx::Nil => f.write_str("nil"), + Calx::Bool(b) => f.write_str(&b.to_string()), + Calx::I64(n) => f.write_str(&n.to_string()), + Calx::F64(n) => f.write_str(&n.to_string()), + Calx::Str(s) => f.write_str(s), + Calx::List(xs) => { + f.write_str("(")?; + let mut at_head = true; + for x in xs { + if at_head { + at_head = false + } else { + f.write_str(" ")?; + } + x.fmt(f)?; + } + f.write_str(")")?; + Ok(()) + } // Calx::Link(..) => f.write_str("TODO LINK"), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 573d31f..0ac6173 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ +mod calx; mod parser; -mod primes; mod util; mod vm; +pub use calx::{Calx, CalxType}; pub use parser::{extract_nested, parse_function}; -pub use primes::{Calx, CalxBinaryProgram, CalxError, CalxFrame, CalxFunc, CALX_BINARY_EDITION}; pub use util::log_calx_value; -pub use vm::{CalxImportsDict, CalxVM}; +pub use vm::{func::CalxFunc, instr::CALX_INSTR_EDITION, CalxImportsDict, CalxVM}; diff --git a/src/parser.rs b/src/parser.rs index 6aa16c1..a21a7fd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -11,7 +11,9 @@ use regex::Regex; use cirru_parser::Cirru; -use crate::primes::{Calx, CalxFunc, CalxInstr, CalxType}; +use crate::calx::{Calx, CalxType}; +use crate::vm::func::CalxFunc; +use crate::vm::instr::CalxInstr; use self::locals::LocalsCollector; diff --git a/src/primes.rs b/src/primes.rs deleted file mode 100644 index 984c419..0000000 --- a/src/primes.rs +++ /dev/null @@ -1,350 +0,0 @@ -/*! - * Calx is a simplied VM, with dynamic data types, and WASM-inspired control flows. - * It is a toy project, but trying to speed up calculations for Calcit. - */ - -use bincode::{Decode, Encode}; -use std::{fmt, rc::Rc}; - -/// Simplied from Calcit, but trying to be basic and mutable -#[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] -pub enum Calx { - Nil, - Bool(bool), - I64(i64), - F64(f64), - Str(String), - List(Vec), - // to simultate linked structures - // Link(Box, Box, Box), -} - -impl Calx { - // for runtime type checking - pub fn typed_as(&self, t: CalxType) -> bool { - match self { - Calx::Nil => t == CalxType::Nil, - Calx::Bool(_) => t == CalxType::Bool, - Calx::I64(_) => t == CalxType::I64, - Calx::F64(_) => t == CalxType::F64, - Calx::Str(_) => t == CalxType::Str, - Calx::List(_) => t == CalxType::List, - // Calx::Link(_, _, _) => t == CalxType::Link, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Decode, Encode)] -pub enum CalxType { - Nil, - Bool, - I64, - F64, - Str, - List, - Link, -} - -impl fmt::Display for Calx { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Calx::Nil => f.write_str("nil"), - Calx::Bool(b) => f.write_str(&b.to_string()), - Calx::I64(n) => f.write_str(&n.to_string()), - Calx::F64(n) => f.write_str(&n.to_string()), - Calx::Str(s) => f.write_str(s), - Calx::List(xs) => { - f.write_str("(")?; - let mut at_head = true; - for x in xs { - if at_head { - at_head = false - } else { - f.write_str(" ")?; - } - x.fmt(f)?; - } - f.write_str(")")?; - Ok(()) - } // Calx::Link(..) => f.write_str("TODO LINK"), - } - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd, Encode, Decode)] -pub struct CalxFunc { - pub name: Rc, - pub params_types: Rc>, - pub ret_types: Rc>, - pub instrs: Rc>, - pub local_names: Rc>, -} - -impl fmt::Display for CalxFunc { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "CalxFunc {} (", self.name)?; - for p in &*self.params_types { - write!(f, "{:?} ", p)?; - } - f.write_str("-> ")?; - for p in &*self.ret_types { - write!(f, "{:?} ", p)?; - } - f.write_str(")")?; - if !self.local_names.is_empty() { - f.write_str("\n local_names:")?; - for (idx, l) in self.local_names.iter().enumerate() { - write!(f, " {}_{}", idx, l)?; - } - f.write_str(" .")?; - } - for (idx, instr) in self.instrs.iter().enumerate() { - write!(f, "\n {:02} {:?}", idx, instr)?; - } - f.write_str("\n")?; - Ok(()) - } -} - -/// learning from WASM but for dynamic data -#[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] -pub enum CalxInstr { - // Param, // load variable from parameter - LocalSet(usize), - LocalTee(usize), // set and also load to stack - LocalGet(usize), - LocalNew, - GlobalSet(usize), - GlobalGet(usize), - GlobalNew, - Const(Calx), - Dup, - Drop, - // number operations - IntAdd, - IntMul, - IntDiv, - IntRem, - IntNeg, - IntShr, - IntShl, - /// equal - IntEq, - /// not equal - IntNe, - /// littler than - IntLt, - /// littler than, or equal - IntLe, - /// greater than - IntGt, - /// greater than, or equal - IntGe, - Add, - Mul, - Div, - Neg, - // string operations - // list operations - NewList, - ListGet, - ListSet, - // Link - NewLink, - // bool operations - And, - Or, - Not, - // control stuctures - Br(usize), - BrIf(usize), - Jmp(usize), // internal - JmpIf(usize), // internal - Block { - params_types: Rc>, - ret_types: Rc>, - /// bool to indicate loop - looped: bool, - from: usize, - to: usize, - }, - BlockEnd(bool), - /// pop and println current value - Echo, - /// TODO use function name at first, during running, index can be faster - Call(String), - /// for tail recursion - ReturnCall(String), - CallImport(String), - Unreachable, - Nop, - Quit(usize), // quit and return value - Return, - /// TODO might also be a foreign function instead - Assert(String), - /// inspecting stack - Inspect, -} - -impl CalxInstr { - /// notice that some of the instrs are special and need to handle manually - pub fn stack_arity(&self) -> (usize, usize) { - match self { - CalxInstr::LocalSet(_) => (1, 0), - CalxInstr::LocalTee(_) => (1, 1), // TODO need check - CalxInstr::LocalGet(_) => (0, 1), - CalxInstr::LocalNew => (0, 0), - CalxInstr::GlobalSet(_) => (1, 0), - CalxInstr::GlobalGet(_) => (0, 1), - CalxInstr::GlobalNew => (0, 0), - CalxInstr::Const(_) => (0, 1), - CalxInstr::Dup => (1, 2), - CalxInstr::Drop => (1, 0), - CalxInstr::IntAdd => (2, 1), - CalxInstr::IntMul => (2, 1), - CalxInstr::IntDiv => (2, 1), - CalxInstr::IntRem => (2, 1), - CalxInstr::IntNeg => (1, 1), - CalxInstr::IntShr => (2, 1), - CalxInstr::IntShl => (2, 1), - CalxInstr::IntEq => (2, 1), - CalxInstr::IntNe => (2, 1), - CalxInstr::IntLt => (2, 1), - CalxInstr::IntLe => (2, 1), - CalxInstr::IntGt => (2, 1), - CalxInstr::IntGe => (2, 1), - CalxInstr::Add => (2, 1), - CalxInstr::Mul => (2, 1), - CalxInstr::Div => (2, 1), - CalxInstr::Neg => (1, 1), - // string operations - // list operations - CalxInstr::NewList => (0, 1), - CalxInstr::ListGet => (2, 1), - CalxInstr::ListSet => (3, 0), - // Link - CalxInstr::NewLink => (0, 1), - // bool operations - CalxInstr::And => (2, 1), - CalxInstr::Or => (2, 1), - CalxInstr::Not => (1, 1), - // control stuctures - CalxInstr::Br(_) => (0, 0), - CalxInstr::BrIf(_) => (1, 0), - CalxInstr::Jmp(_) => (0, 0), - CalxInstr::JmpIf(_) => (1, 0), - CalxInstr::Block { .. } => (0, 0), - CalxInstr::BlockEnd(_) => (0, 0), - CalxInstr::Echo => (1, 0), - CalxInstr::Call(_) => (0, 0), // TODO - CalxInstr::ReturnCall(_) => (0, 0), // TODO - CalxInstr::CallImport(_) => (0, 0), // import - CalxInstr::Unreachable => (0, 0), // TODO - CalxInstr::Nop => (0, 0), - CalxInstr::Quit(_) => (0, 0), - CalxInstr::Return => (0, 0), // TODO - CalxInstr::Assert(_) => (1, 0), - // debug - CalxInstr::Inspect => (0, 0), - } - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct CalxError { - pub message: String, - pub stack: Vec, - pub top_frame: CalxFrame, - pub blocks: Vec, - pub globals: Vec, -} - -impl fmt::Display for CalxError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}\n{:?}\n{}", self.message, self.stack, self.top_frame) - } -} - -impl CalxError { - pub fn new_raw(s: String) -> Self { - CalxError { - message: s, - stack: vec![], - top_frame: CalxFrame::default(), - blocks: vec![], - globals: vec![], - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] -pub struct BlockData { - pub looped: bool, - pub params_types: Rc>, - pub ret_types: Rc>, - pub from: usize, - pub to: usize, - pub initial_stack_size: usize, -} - -impl BlockData { - // size of stack after block finished or breaked - pub fn expected_finish_size(&self) -> usize { - self.initial_stack_size - self.params_types.len() + self.ret_types.len() - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct CalxFrame { - pub name: Rc, - pub locals: Vec, // params + added locals - /** store return values */ - pub instrs: Rc>, - pub pointer: usize, - pub initial_stack_size: usize, - pub blocks_track: Vec, - pub ret_types: Rc>, -} - -impl Default for CalxFrame { - fn default() -> Self { - CalxFrame { - name: String::from("").into(), - locals: vec![], - instrs: Rc::new(vec![]), - pointer: 0, - initial_stack_size: 0, - blocks_track: vec![], - ret_types: Rc::new(vec![]), - } - } -} - -impl fmt::Display for CalxFrame { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("CalxFrame ")?; - write!(f, "_{} (", self.initial_stack_size)?; - for p in &*self.ret_types { - write!(f, "{:?} ", p)?; - } - write!(f, ") @{}", self.pointer)?; - for (idx, instr) in self.instrs.iter().enumerate() { - write!(f, "\n {:02} {:?}", idx, instr)?; - } - f.write_str("\n")?; - Ok(()) - } -} - -/// binary format for saving calx program -/// TODO this is not a valid file format that requires magic code -#[derive(Debug, Clone, PartialEq, PartialOrd, Encode, Decode)] -pub struct CalxBinaryProgram { - /// updates as instructions update - pub edition: String, - pub fns: Vec, -} - -/// TODO not sure whether bincode remains compatible after new instruction added -/// use string for some semantics -pub const CALX_BINARY_EDITION: &str = "0.1"; diff --git a/src/util.rs b/src/util.rs index b5c3fcb..091679c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,4 @@ -use crate::primes::{Calx, CalxError}; +use crate::{calx::Calx, vm::CalxError}; pub fn log_calx_value(xs: Vec) -> Result { println!("log: {:?}", xs); diff --git a/src/vm.rs b/src/vm.rs index b749ae8..9791009 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,9 +1,20 @@ -use crate::primes::{BlockData, Calx, CalxError, CalxFrame, CalxFunc, CalxInstr, CalxType}; +mod block_data; +pub mod frame; +pub mod func; +pub mod instr; + use std::collections::hash_map::HashMap; use std::ops::Rem; use std::rc::Rc; use std::{fmt, vec}; +use crate::calx::{Calx, CalxType}; + +use self::block_data::BlockData; +use self::frame::CalxFrame; +use self::func::CalxFunc; +use self::instr::CalxInstr; + pub type CalxImportsDict = HashMap) -> Result, usize)>; #[derive(Clone)] @@ -121,19 +132,22 @@ impl CalxVM { return Ok(true); } let instrs = self.top_frame.instrs.to_owned(); + + use instr::CalxInstr::*; + match &instrs[self.top_frame.pointer] { - CalxInstr::Jmp(line) => { + Jmp(line) => { self.top_frame.pointer = line.to_owned(); return Ok(true); // point reset, goto next loop } - CalxInstr::JmpIf(line) => { + JmpIf(line) => { let v = self.stack_pop()?; if v == Calx::Bool(true) || v == Calx::I64(1) { self.top_frame.pointer = line.to_owned(); return Ok(true); // point reset, goto next loop } } - CalxInstr::Br(size) => { + Br(size) => { self.shrink_blocks_by(*size)?; let last_idx = self.top_frame.blocks_track.len() - 1; @@ -145,7 +159,7 @@ impl CalxVM { return Ok(true); // point reset, goto next loop } - CalxInstr::BrIf(size) => { + BrIf(size) => { let last_idx = self.stack.len() - 1; if self.stack[last_idx] == Calx::Bool(true) || self.stack[last_idx] == Calx::I64(1) { self.shrink_blocks_by(*size)?; @@ -160,7 +174,7 @@ impl CalxVM { return Ok(true); // point reset, goto next loop } } - CalxInstr::Block { + Block { looped, from, to, @@ -180,7 +194,7 @@ impl CalxVM { }); self.check_stack_for_block(params_types)?; } - CalxInstr::BlockEnd(looped) => { + BlockEnd(looped) => { if *looped { return Err(self.gen_err(String::from("loop end expected to be branched"))); } @@ -196,7 +210,7 @@ impl CalxVM { ))); } } - CalxInstr::LocalSet(idx) => { + LocalSet(idx) => { let v = self.stack_pop()?; if *idx >= self.top_frame.locals.len() { return Err(self.gen_err(format!("out of bound in local.set {} for {:?}", idx, self.top_frame.locals))); @@ -204,7 +218,7 @@ impl CalxVM { self.top_frame.locals[*idx] = v } } - CalxInstr::LocalTee(idx) => { + LocalTee(idx) => { let v = self.stack_pop()?; if *idx >= self.top_frame.locals.len() { return Err(self.gen_err(format!("out of bound in local.tee {}", idx))); @@ -213,14 +227,14 @@ impl CalxVM { } self.stack_push(v); } - CalxInstr::LocalGet(idx) => { + LocalGet(idx) => { if *idx < self.top_frame.locals.len() { self.stack_push(self.top_frame.locals[*idx].to_owned()) } else { return Err(self.gen_err(format!("invalid index for local.get {}", idx))); } } - CalxInstr::Return => { + Return => { // return values are moved to a temp space let mut ret_stack: Vec = vec![]; @@ -250,8 +264,8 @@ impl CalxVM { } } } - CalxInstr::LocalNew => self.top_frame.locals.push(Calx::Nil), - CalxInstr::GlobalSet(idx) => { + LocalNew => self.top_frame.locals.push(Calx::Nil), + GlobalSet(idx) => { let v = self.stack_pop()?; if self.globals.len() >= *idx { return Err(self.gen_err(format!("out of bound in global.set {}", idx))); @@ -259,22 +273,22 @@ impl CalxVM { self.globals[*idx] = v } } - CalxInstr::GlobalGet(idx) => { + GlobalGet(idx) => { if *idx < self.globals.len() { self.stack_push(self.globals[*idx].to_owned()) } else { return Err(self.gen_err(format!("invalid index for global.get {}", idx))); } } - CalxInstr::GlobalNew => self.globals.push(Calx::Nil), - CalxInstr::Const(v) => self.stack_push(v.to_owned()), - CalxInstr::Dup => { + GlobalNew => self.globals.push(Calx::Nil), + Const(v) => self.stack_push(v.to_owned()), + Dup => { self.stack_push(self.stack[self.stack.len() - 1].to_owned()); } - CalxInstr::Drop => { + Drop => { let _ = self.stack_pop()?; } - CalxInstr::IntAdd => { + IntAdd => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -283,7 +297,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 integers to add, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::IntMul => { + IntMul => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -292,7 +306,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 integers to multiply, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::IntDiv => { + IntDiv => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -301,7 +315,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 integers to divide, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::IntRem => { + IntRem => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -310,7 +324,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 integers to add, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::IntNeg => { + IntNeg => { let last_idx = self.stack.len() - 1; if let Calx::I64(n) = self.stack[last_idx] { self.stack[last_idx] = Calx::I64(-n) @@ -318,7 +332,7 @@ impl CalxVM { return Err(self.gen_err(format!("expected int, got {}", self.stack[last_idx]))); } } - CalxInstr::IntShr => { + IntShr => { let bits = self.stack_pop()?; let last_idx = self.stack.len() - 1; match (&self.stack[last_idx], &bits) { @@ -326,7 +340,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("invalid number for SHR, {:?} {:?}", self.stack[last_idx], bits))), } } - CalxInstr::IntShl => { + IntShl => { let bits = self.stack_pop()?; let last_idx = self.stack.len() - 1; match (&self.stack[last_idx], &bits) { @@ -334,7 +348,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("invalid number for SHL, {:?} {:?}", self.stack[last_idx], bits))), } } - CalxInstr::IntEq => { + IntEq => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -345,7 +359,7 @@ impl CalxVM { } } - CalxInstr::IntNe => { + IntNe => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -354,7 +368,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 integers to ne compare, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::IntLt => { + IntLt => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -363,7 +377,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 integers to le compare, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::IntLe => { + IntLe => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -372,7 +386,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 integers to le compare, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::IntGt => { + IntGt => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -382,7 +396,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 integers to gt compare, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::IntGe => { + IntGe => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -392,7 +406,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 integers to ge compare, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::Add => { + Add => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -403,7 +417,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 numbers to +, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::Mul => { + Mul => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -414,7 +428,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 numbers to multiply, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::Div => { + Div => { // reversed order let v2 = self.stack_pop()?; let last_idx = self.stack.len() - 1; @@ -424,7 +438,7 @@ impl CalxVM { (_, _) => return Err(self.gen_err(format!("expected 2 numbers to divide, {:?} {:?}", self.stack[last_idx], v2))), } } - CalxInstr::Neg => { + Neg => { let last_idx = self.stack.len() - 1; if let Calx::F64(n) = self.stack[last_idx] { self.stack[last_idx] = Calx::F64(-n) @@ -432,28 +446,28 @@ impl CalxVM { return Err(self.gen_err(format!("expected float, got {}", self.stack[last_idx]))); } } - CalxInstr::NewList => { + NewList => { // TODO } - CalxInstr::ListGet => { + ListGet => { // TODO } - CalxInstr::ListSet => { + ListSet => { // TODO } - CalxInstr::NewLink => { + NewLink => { // TODO } - CalxInstr::And => { + And => { // TODO } - CalxInstr::Or => { + Or => { // TODO } - CalxInstr::Not => { + Not => { // TODO } - CalxInstr::Call(f_name) => { + Call(f_name) => { // println!("frame size: {}", self.frames.len()); match find_func(&self.funcs, f_name) { Some(f) => { @@ -482,7 +496,7 @@ impl CalxVM { None => return Err(self.gen_err(format!("cannot find function named: {}", f_name))), } } - CalxInstr::ReturnCall(f_name) => { + ReturnCall(f_name) => { // println!("frame size: {}", self.frames.len()); match find_func(&self.funcs, f_name) { Some(f) => { @@ -519,7 +533,7 @@ impl CalxVM { None => return Err(self.gen_err(format!("cannot find function named: {}", f_name))), } } - CalxInstr::CallImport(f_name) => match self.imports.to_owned().get(f_name) { + CallImport(f_name) => match self.imports.to_owned().get(f_name) { None => return Err(self.gen_err(format!("missing imported function {}", f_name))), Some((f, size)) => { if self.stack.len() < *size { @@ -539,18 +553,18 @@ impl CalxVM { self.stack_push(v); } }, - CalxInstr::Unreachable => { + Unreachable => { unreachable!("Unexpected from op") } - CalxInstr::Nop => { + Nop => { // Noop } - CalxInstr::Quit(code) => std::process::exit(*code as i32), - CalxInstr::Echo => { + Quit(code) => std::process::exit(*code as i32), + Echo => { let v = self.stack_pop()?; println!("{}", v); } - CalxInstr::Assert(message) => { + Assert(message) => { let v = self.stack_pop()?; if v == Calx::Bool(true) || v == Calx::I64(1) { // Ok @@ -558,10 +572,17 @@ impl CalxVM { return Err(self.gen_err(format!("Failed assertion: {}", message))); } } - CalxInstr::Inspect => { + Inspect => { println!("[ ----------------{}", self.inspect_display(2)); println!(" -------------- ]"); } + If { ret_types, .. } => { + // TODO + self.check_stack_for_block(ret_types)?; + } + EndIf => { + unreachable!("End if is internal instruction during preprocessing") + } } Ok(false) @@ -816,3 +837,30 @@ impl CalxVM { pub fn find_func<'a>(funcs: &'a [CalxFunc], name: &str) -> Option<&'a CalxFunc> { funcs.iter().find(|x| *x.name == name) } + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct CalxError { + pub message: String, + pub stack: Vec, + pub top_frame: CalxFrame, + pub blocks: Vec, + pub globals: Vec, +} + +impl fmt::Display for CalxError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}\n{:?}\n{}", self.message, self.stack, self.top_frame) + } +} + +impl CalxError { + pub fn new_raw(s: String) -> Self { + CalxError { + message: s, + stack: vec![], + top_frame: CalxFrame::default(), + blocks: vec![], + globals: vec![], + } + } +} diff --git a/src/vm/block_data.rs b/src/vm/block_data.rs new file mode 100644 index 0000000..40f1ef9 --- /dev/null +++ b/src/vm/block_data.rs @@ -0,0 +1,20 @@ +use std::rc::Rc; + +use crate::calx::CalxType; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] +pub struct BlockData { + pub looped: bool, + pub params_types: Rc>, + pub ret_types: Rc>, + pub from: usize, + pub to: usize, + pub initial_stack_size: usize, +} + +impl BlockData { + // size of stack after block finished or breaked + pub fn expected_finish_size(&self) -> usize { + self.initial_stack_size - self.params_types.len() + self.ret_types.len() + } +} diff --git a/src/vm/frame.rs b/src/vm/frame.rs new file mode 100644 index 0000000..6ed8f4f --- /dev/null +++ b/src/vm/frame.rs @@ -0,0 +1,48 @@ +use core::fmt; +use std::rc::Rc; + +use crate::calx::{Calx, CalxType}; + +use super::{block_data::BlockData, instr::CalxInstr}; + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct CalxFrame { + pub name: Rc, + pub locals: Vec, // params + added locals + /** store return values */ + pub instrs: Rc>, + pub pointer: usize, + pub initial_stack_size: usize, + pub blocks_track: Vec, + pub ret_types: Rc>, +} + +impl Default for CalxFrame { + fn default() -> Self { + CalxFrame { + name: String::from("").into(), + locals: vec![], + instrs: Rc::new(vec![]), + pointer: 0, + initial_stack_size: 0, + blocks_track: vec![], + ret_types: Rc::new(vec![]), + } + } +} + +impl fmt::Display for CalxFrame { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("CalxFrame ")?; + write!(f, "_{} (", self.initial_stack_size)?; + for p in &*self.ret_types { + write!(f, "{:?} ", p)?; + } + write!(f, ") @{}", self.pointer)?; + for (idx, instr) in self.instrs.iter().enumerate() { + write!(f, "\n {:02} {:?}", idx, instr)?; + } + f.write_str("\n")?; + Ok(()) + } +} diff --git a/src/vm/func.rs b/src/vm/func.rs new file mode 100644 index 0000000..6dd230a --- /dev/null +++ b/src/vm/func.rs @@ -0,0 +1,42 @@ +use bincode::{Decode, Encode}; +use core::fmt; +use std::rc::Rc; + +use crate::calx::CalxType; + +use super::instr::CalxInstr; + +#[derive(Debug, Clone, PartialEq, PartialOrd, Encode, Decode)] +pub struct CalxFunc { + pub name: Rc, + pub params_types: Rc>, + pub ret_types: Rc>, + pub instrs: Rc>, + pub local_names: Rc>, +} + +impl fmt::Display for CalxFunc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CalxFunc {} (", self.name)?; + for p in &*self.params_types { + write!(f, "{:?} ", p)?; + } + f.write_str("-> ")?; + for p in &*self.ret_types { + write!(f, "{:?} ", p)?; + } + f.write_str(")")?; + if !self.local_names.is_empty() { + f.write_str("\n local_names:")?; + for (idx, l) in self.local_names.iter().enumerate() { + write!(f, " {}_{}", idx, l)?; + } + f.write_str(" .")?; + } + for (idx, instr) in self.instrs.iter().enumerate() { + write!(f, "\n {:02} {:?}", idx, instr)?; + } + f.write_str("\n")?; + Ok(()) + } +} diff --git a/src/vm/instr.rs b/src/vm/instr.rs new file mode 100644 index 0000000..590d2d1 --- /dev/null +++ b/src/vm/instr.rs @@ -0,0 +1,165 @@ +use std::rc::Rc; + +use bincode::{Decode, Encode}; + +use crate::calx::{Calx, CalxType}; + +/// learning from WASM but for dynamic data +#[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] +pub enum CalxInstr { + // Param, // load variable from parameter + LocalSet(usize), + LocalTee(usize), // set and also load to stack + LocalGet(usize), + LocalNew, + GlobalSet(usize), + GlobalGet(usize), + GlobalNew, + Const(Calx), + Dup, + Drop, + // number operations + IntAdd, + IntMul, + IntDiv, + IntRem, + IntNeg, + IntShr, + IntShl, + /// equal + IntEq, + /// not equal + IntNe, + /// littler than + IntLt, + /// littler than, or equal + IntLe, + /// greater than + IntGt, + /// greater than, or equal + IntGe, + Add, + Mul, + Div, + Neg, + // string operations + // list operations + NewList, + ListGet, + ListSet, + // Link + NewLink, + // bool operations + And, + Or, + Not, + // control stuctures + Br(usize), + BrIf(usize), + Jmp(usize), // internal + JmpIf(usize), // internal + Block { + params_types: Rc>, + ret_types: Rc>, + /// bool to indicate loop + looped: bool, + from: usize, + to: usize, + }, + BlockEnd(bool), + /// pop and println current value + Echo, + /// TODO use function name at first, during running, index can be faster + Call(String), + /// for tail recursion + ReturnCall(String), + CallImport(String), + Unreachable, + Nop, + Quit(usize), // quit and return value + Return, + /// TODO might also be a foreign function instead + Assert(String), + /// inspecting stack + Inspect, + /// if takes 1 value from stack, returns values as ret_types + If { + ret_types: Rc>, + then_to: usize, + else_to: usize, + to: usize, + }, + EndIf, +} + +impl CalxInstr { + /// notice that some of the instrs are special and need to handle manually + pub fn stack_arity(&self) -> (usize, usize) { + match self { + CalxInstr::LocalSet(_) => (1, 0), + CalxInstr::LocalTee(_) => (1, 1), // TODO need check + CalxInstr::LocalGet(_) => (0, 1), + CalxInstr::LocalNew => (0, 0), + CalxInstr::GlobalSet(_) => (1, 0), + CalxInstr::GlobalGet(_) => (0, 1), + CalxInstr::GlobalNew => (0, 0), + CalxInstr::Const(_) => (0, 1), + CalxInstr::Dup => (1, 2), + CalxInstr::Drop => (1, 0), + CalxInstr::IntAdd => (2, 1), + CalxInstr::IntMul => (2, 1), + CalxInstr::IntDiv => (2, 1), + CalxInstr::IntRem => (2, 1), + CalxInstr::IntNeg => (1, 1), + CalxInstr::IntShr => (2, 1), + CalxInstr::IntShl => (2, 1), + CalxInstr::IntEq => (2, 1), + CalxInstr::IntNe => (2, 1), + CalxInstr::IntLt => (2, 1), + CalxInstr::IntLe => (2, 1), + CalxInstr::IntGt => (2, 1), + CalxInstr::IntGe => (2, 1), + CalxInstr::Add => (2, 1), + CalxInstr::Mul => (2, 1), + CalxInstr::Div => (2, 1), + CalxInstr::Neg => (1, 1), + // string operations + // list operations + CalxInstr::NewList => (0, 1), + CalxInstr::ListGet => (2, 1), + CalxInstr::ListSet => (3, 0), + // Link + CalxInstr::NewLink => (0, 1), + // bool operations + CalxInstr::And => (2, 1), + CalxInstr::Or => (2, 1), + CalxInstr::Not => (1, 1), + // control stuctures + CalxInstr::Br(_) => (0, 0), + CalxInstr::BrIf(_) => (1, 0), + CalxInstr::Jmp(_) => (0, 0), + CalxInstr::JmpIf(_) => (1, 0), + CalxInstr::Block { + params_types, ret_types, .. + } => (params_types.len(), ret_types.len()), + CalxInstr::BlockEnd(_) => (0, 0), + CalxInstr::Echo => (1, 0), + CalxInstr::Call(_) => (0, 0), // TODO + CalxInstr::ReturnCall(_) => (0, 0), // TODO + CalxInstr::CallImport(_) => (0, 0), // import + CalxInstr::Unreachable => (0, 0), // TODO + CalxInstr::Nop => (0, 0), + CalxInstr::Quit(_) => (0, 0), + CalxInstr::Return => (1, 0), // TODO + CalxInstr::Assert(_) => (1, 0), + // debug + CalxInstr::Inspect => (0, 0), + CalxInstr::If { ret_types, .. } => (1, ret_types.len()), + CalxInstr::EndIf => (0, 0), + } + } +} + +/// TODO not sure whether bincode remains compatible after new instruction added +/// use string for some semantics +pub const CALX_INSTR_EDITION: &str = "0.2"; From be87de33a3aa9c6784bc8fb097871f7a948a7f73 Mon Sep 17 00:00:00 2001 From: tiye Date: Sun, 21 Jan 2024 18:16:01 +0800 Subject: [PATCH 04/11] add mid-layer called syntax --- src/lib.rs | 1 + src/parser.rs | 111 ++++++++++++++++++++++++------------------------ src/syntax.rs | 93 ++++++++++++++++++++++++++++++++++++++++ src/vm.rs | 47 ++++++++++++-------- src/vm/func.rs | 5 ++- src/vm/instr.rs | 72 ++++++++++++++++++++++++++++++- 6 files changed, 253 insertions(+), 76 deletions(-) create mode 100644 src/syntax.rs diff --git a/src/lib.rs b/src/lib.rs index 0ac6173..482f5a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod calx; mod parser; +mod syntax; mod util; mod vm; diff --git a/src/parser.rs b/src/parser.rs index a21a7fd..82eed0a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -12,8 +12,8 @@ use regex::Regex; use cirru_parser::Cirru; use crate::calx::{Calx, CalxType}; +use crate::syntax::CalxSyntax; use crate::vm::func::CalxFunc; -use crate::vm::instr::CalxInstr; use self::locals::LocalsCollector; @@ -44,7 +44,7 @@ pub fn parse_function(nodes: &[Cirru]) -> Result { return Err(String::from("invalid name")); }; - let mut body: Vec = vec![]; + let mut body: Vec = vec![]; let mut locals_collector: LocalsCollector = LocalsCollector::new(); let (params_types, ret_types) = parse_fn_types(&nodes[2], &mut locals_collector)?; @@ -54,9 +54,9 @@ pub fn parse_function(nodes: &[Cirru]) -> Result { if idx >= 3 { for expanded in extract_nested(line)? { // println!("expanded {}", expanded); - let instrs = parse_instr(ptr_base, &expanded, &mut locals_collector)?; + let syntax = parse_instr(ptr_base, &expanded, &mut locals_collector)?; - for instr in instrs { + for instr in syntax { ptr_base += 1; body.push(instr); } @@ -69,11 +69,12 @@ pub fn parse_function(nodes: &[Cirru]) -> Result { params_types: params_types.into(), ret_types: Rc::new(ret_types), local_names: Rc::new(locals_collector.locals), - instrs: Rc::new(body), + syntax: Rc::new(body), + instrs: None, }) } -pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollector) -> Result, String> { +pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollector) -> Result, String> { match node { Cirru::Leaf(_) => Err(format!("expected expr of instruction, {}", node)), Cirru::List(xs) => { @@ -90,23 +91,23 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto return Err(format!("local.get expected a position, {:?}", xs)); } let idx: usize = parse_local_idx(&xs[1], collector)?; - Ok(vec![CalxInstr::LocalGet(idx)]) + Ok(vec![CalxSyntax::LocalGet(idx)]) } "local.set" => { if xs.len() != 2 { return Err(format!("local.set expected a position, {:?}", xs)); } let idx: usize = parse_local_idx(&xs[1], collector)?; - Ok(vec![CalxInstr::LocalSet(idx)]) + Ok(vec![CalxSyntax::LocalSet(idx)]) } "local.tee" => { if xs.len() != 2 { return Err(format!("list.tee expected a position, {:?}", xs)); } let idx: usize = parse_local_idx(&xs[1], collector)?; - Ok(vec![CalxInstr::LocalSet(idx)]) + Ok(vec![CalxSyntax::LocalSet(idx)]) } - "local.new" => Ok(vec![CalxInstr::LocalNew]), + "local.new" => Ok(vec![CalxSyntax::LocalNew]), "global.get" => { if xs.len() != 2 { return Err(format!("global.get expected a position, {:?}", xs)); @@ -117,7 +118,7 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto return Err(format!("expected token, got {}", xs[1])); } }; - Ok(vec![CalxInstr::GlobalGet(idx)]) + Ok(vec![CalxSyntax::GlobalGet(idx)]) } "global.set" => { if xs.len() != 2 { @@ -129,9 +130,9 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto return Err(format!("expected token, got {}", xs[1])); } }; - Ok(vec![CalxInstr::GlobalSet(idx)]) + Ok(vec![CalxSyntax::GlobalSet(idx)]) } - "global.new" => Ok(vec![CalxInstr::GlobalNew]), + "global.new" => Ok(vec![CalxSyntax::GlobalNew]), "const" => { if xs.len() != 2 { return Err(format!("const takes exactly 1 argument, got {:?}", xs)); @@ -139,37 +140,37 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto match &xs[1] { Cirru::Leaf(s) => { let p1 = parse_value(s)?; - Ok(vec![CalxInstr::Const(p1)]) + Ok(vec![CalxSyntax::Const(p1)]) } Cirru::List(a) => Err(format!("`const` not supporting list here: {:?}", a)), } } - "dup" => Ok(vec![CalxInstr::Dup]), - "drop" => Ok(vec![CalxInstr::Drop]), - "i.add" => Ok(vec![CalxInstr::IntAdd]), - "i.mul" => Ok(vec![CalxInstr::IntMul]), - "i.div" => Ok(vec![CalxInstr::IntDiv]), - "i.neg" => Ok(vec![CalxInstr::IntNeg]), - "i.rem" => Ok(vec![CalxInstr::IntRem]), - "i.shr" => Ok(vec![CalxInstr::IntShr]), - "i.shl" => Ok(vec![CalxInstr::IntShl]), - "i.eq" => Ok(vec![CalxInstr::IntEq]), - "i.ne" => Ok(vec![CalxInstr::IntNe]), - "i.lt" => Ok(vec![CalxInstr::IntLt]), - "i.le" => Ok(vec![CalxInstr::IntLe]), - "i.gt" => Ok(vec![CalxInstr::IntGt]), - "i.ge" => Ok(vec![CalxInstr::IntGe]), - "add" => Ok(vec![CalxInstr::Add]), - "mul" => Ok(vec![CalxInstr::Mul]), - "div" => Ok(vec![CalxInstr::Div]), - "neg" => Ok(vec![CalxInstr::Neg]), - "new-list" => Ok(vec![CalxInstr::NewList]), - "list.get" => Ok(vec![CalxInstr::ListGet]), - "list.set" => Ok(vec![CalxInstr::ListSet]), - "new-link" => Ok(vec![CalxInstr::NewLink]), + "dup" => Ok(vec![CalxSyntax::Dup]), + "drop" => Ok(vec![CalxSyntax::Drop]), + "i.add" => Ok(vec![CalxSyntax::IntAdd]), + "i.mul" => Ok(vec![CalxSyntax::IntMul]), + "i.div" => Ok(vec![CalxSyntax::IntDiv]), + "i.neg" => Ok(vec![CalxSyntax::IntNeg]), + "i.rem" => Ok(vec![CalxSyntax::IntRem]), + "i.shr" => Ok(vec![CalxSyntax::IntShr]), + "i.shl" => Ok(vec![CalxSyntax::IntShl]), + "i.eq" => Ok(vec![CalxSyntax::IntEq]), + "i.ne" => Ok(vec![CalxSyntax::IntNe]), + "i.lt" => Ok(vec![CalxSyntax::IntLt]), + "i.le" => Ok(vec![CalxSyntax::IntLe]), + "i.gt" => Ok(vec![CalxSyntax::IntGt]), + "i.ge" => Ok(vec![CalxSyntax::IntGe]), + "add" => Ok(vec![CalxSyntax::Add]), + "mul" => Ok(vec![CalxSyntax::Mul]), + "div" => Ok(vec![CalxSyntax::Div]), + "neg" => Ok(vec![CalxSyntax::Neg]), + "new-list" => Ok(vec![CalxSyntax::NewList]), + "list.get" => Ok(vec![CalxSyntax::ListGet]), + "list.set" => Ok(vec![CalxSyntax::ListSet]), + "new-link" => Ok(vec![CalxSyntax::NewLink]), // TODO - "and" => Ok(vec![CalxInstr::And]), - "or" => Ok(vec![CalxInstr::Or]), + "and" => Ok(vec![CalxSyntax::And]), + "or" => Ok(vec![CalxSyntax::Or]), "br-if" => { if xs.len() != 2 { return Err(format!("br-if expected a position, {:?}", xs)); @@ -180,7 +181,7 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto return Err(format!("expected token, got {}", xs[1])); } }; - Ok(vec![CalxInstr::BrIf(idx)]) + Ok(vec![CalxSyntax::BrIf(idx)]) } "br" => { if xs.len() != 2 { @@ -192,11 +193,11 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto return Err(format!("expected token, got {}", xs[1])); } }; - Ok(vec![CalxInstr::Br(idx)]) + Ok(vec![CalxSyntax::Br(idx)]) } "block" => parse_block(ptr_base, xs, false, collector), "loop" => parse_block(ptr_base, xs, true, collector), - "echo" => Ok(vec![CalxInstr::Echo]), + "echo" => Ok(vec![CalxSyntax::Echo]), "call" => { if xs.len() != 2 { return Err(format!("call expected function name, {:?}", xs)); @@ -206,7 +207,7 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto Cirru::List(_) => return Err(format!("expected a name, got {:?}", xs[1])), }; - Ok(vec![CalxInstr::Call((*name).to_owned())]) + Ok(vec![CalxSyntax::Call((*name).to_owned())]) } "return-call" => { if xs.len() != 2 { @@ -217,7 +218,7 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto Cirru::List(_) => return Err(format!("expected a name, got {:?}", xs[1])), }; - Ok(vec![CalxInstr::ReturnCall((*name).to_owned())]) + Ok(vec![CalxSyntax::ReturnCall((*name).to_owned())]) } "call-import" => { if xs.len() != 2 { @@ -228,10 +229,10 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto Cirru::List(_) => return Err(format!("expected a name, got {:?}", xs[1])), }; - Ok(vec![CalxInstr::CallImport((*name).to_owned())]) + Ok(vec![CalxSyntax::CallImport((*name).to_owned())]) } - "unreachable" => Ok(vec![CalxInstr::Unreachable]), - "nop" => Ok(vec![CalxInstr::Nop]), + "unreachable" => Ok(vec![CalxSyntax::Unreachable]), + "nop" => Ok(vec![CalxSyntax::Nop]), ";;" => { // commenOk Ok(vec![]) @@ -246,9 +247,9 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto return Err(format!("expected token, got {}", xs[1])); } }; - Ok(vec![CalxInstr::Quit(idx)]) + Ok(vec![CalxSyntax::Quit(idx)]) } - "return" => Ok(vec![CalxInstr::Return]), + "return" => Ok(vec![CalxSyntax::Return]), "assert" => { if xs.len() != 2 { @@ -259,9 +260,9 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto Cirru::List(_) => return Err(format!("assert expected a message, got {:?}", xs[1])), }; - Ok(vec![CalxInstr::Assert((*message).to_owned())]) + Ok(vec![CalxSyntax::Assert((*message).to_owned())]) } - "inspect" => Ok(vec![CalxInstr::Inspect]), + "inspect" => Ok(vec![CalxSyntax::Inspect]), _ => Err(format!("unknown instruction: {}", name)), }, } @@ -325,9 +326,9 @@ pub fn parse_usize(s: &str) -> Result { } } -pub fn parse_block(ptr_base: usize, xs: &[Cirru], looped: bool, collector: &mut LocalsCollector) -> Result, String> { +pub fn parse_block(ptr_base: usize, xs: &[Cirru], looped: bool, collector: &mut LocalsCollector) -> Result, String> { let mut p = ptr_base + 1; - let mut chunk: Vec = vec![]; + let mut chunk: Vec = vec![]; let (params_types, ret_types) = parse_block_types(&xs[1])?; for (idx, line) in xs.iter().enumerate() { if idx > 1 { @@ -338,7 +339,7 @@ pub fn parse_block(ptr_base: usize, xs: &[Cirru], looped: bool, collector: &mut } } } - chunk.push(CalxInstr::BlockEnd(looped)); + chunk.push(CalxSyntax::BlockEnd(looped)); if looped && !ret_types.is_empty() { println!("return types for loop actuall not checked: {:?}", ret_types); @@ -346,7 +347,7 @@ pub fn parse_block(ptr_base: usize, xs: &[Cirru], looped: bool, collector: &mut chunk.insert( 0, - CalxInstr::Block { + CalxSyntax::Block { looped, from: ptr_base + 1, to: p, diff --git a/src/syntax.rs b/src/syntax.rs new file mode 100644 index 0000000..7739493 --- /dev/null +++ b/src/syntax.rs @@ -0,0 +1,93 @@ +use std::rc::Rc; + +use bincode::{Decode, Encode}; + +use crate::{Calx, CalxType}; + +/// learning from WASM but for dynamic data +#[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] +pub enum CalxSyntax { + // Param, // load variable from parameter + LocalSet(usize), + LocalTee(usize), // set and also load to stack + LocalGet(usize), + LocalNew, + GlobalSet(usize), + GlobalGet(usize), + GlobalNew, + Const(Calx), + Dup, + Drop, + // number operations + IntAdd, + IntMul, + IntDiv, + IntRem, + IntNeg, + IntShr, + IntShl, + /// equal + IntEq, + /// not equal + IntNe, + /// littler than + IntLt, + /// littler than, or equal + IntLe, + /// greater than + IntGt, + /// greater than, or equal + IntGe, + Add, + Mul, + Div, + Neg, + // string operations + // list operations + NewList, + ListGet, + ListSet, + // Link + NewLink, + // bool operations + And, + Or, + Not, + // control stuctures + Br(usize), + BrIf(usize), + Jmp(usize), // internal + JmpIf(usize), // internal + Block { + params_types: Rc>, + ret_types: Rc>, + /// bool to indicate loop + looped: bool, + from: usize, + to: usize, + }, + BlockEnd(bool), + /// pop and println current value + Echo, + /// TODO use function name at first, during running, index can be faster + Call(String), + /// for tail recursion + ReturnCall(String), + CallImport(String), + Unreachable, + Nop, + Quit(usize), // quit and return value + Return, + /// TODO might also be a foreign function instead + Assert(String), + /// inspecting stack + Inspect, + /// if takes 1 value from stack, returns values as ret_types + If { + ret_types: Rc>, + then_to: usize, + else_to: usize, + to: usize, + }, + EndIf, +} diff --git a/src/vm.rs b/src/vm.rs index 9791009..eb3a1ce 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -9,6 +9,7 @@ use std::rc::Rc; use std::{fmt, vec}; use crate::calx::{Calx, CalxType}; +use crate::syntax::CalxSyntax; use self::block_data::BlockData; use self::frame::CalxFrame; @@ -43,7 +44,10 @@ impl CalxVM { name: main_func.name.to_owned(), initial_stack_size: 0, blocks_track: vec![], - instrs: main_func.instrs.clone(), + instrs: match main_func.instrs { + Some(ref x) => x.to_owned(), + None => panic!("main function must have instrs"), + }, pointer: 0, locals: vec![], ret_types: main_func.ret_types.clone(), @@ -486,7 +490,10 @@ impl CalxVM { initial_stack_size: self.stack.len(), locals, pointer: 0, - instrs, + instrs: match instrs { + Some(x) => x.to_owned(), + None => panic!("function must have instrs"), + }, ret_types, }; @@ -523,7 +530,10 @@ impl CalxVM { initial_stack_size: self.stack.len(), locals, pointer: 0, - instrs, + instrs: match instrs { + Some(x) => x.to_owned(), + None => panic!("function must have instrs"), + }, ret_types, }; @@ -605,13 +615,13 @@ impl CalxVM { ); } - for j in 0..self.funcs[i].instrs.len() { + for j in 0..self.funcs[i].syntax.len() { if verbose { - println!("{} * {:?}", stack_size, self.funcs[i].instrs[j].to_owned()); + println!("{} * {:?}", stack_size, self.funcs[i].syntax[j].to_owned()); } - let instrs = &self.funcs[i].instrs; - match &instrs[j] { - CalxInstr::Block { + let syntax = &self.funcs[i].syntax; + match &syntax[j] { + CalxSyntax::Block { looped, params_types, ret_types, @@ -631,7 +641,7 @@ impl CalxVM { }); ops.push(CalxInstr::Nop); } - CalxInstr::Br(size) => { + CalxSyntax::Br(size) => { if *size > blocks_track.len() { return Err(format!("br {} too large", size)); } @@ -649,7 +659,7 @@ impl CalxVM { ops.push(CalxInstr::Jmp(target_block.to)) } } - CalxInstr::BrIf(size) => { + CalxSyntax::BrIf(size) => { if blocks_track.is_empty() { return Err(format!("cannot branch with no blocks, {}", size)); } @@ -671,7 +681,7 @@ impl CalxVM { return Err(format!("brIf({size}) expected size {expected_size}, got {stack_size}")); } } - CalxInstr::BlockEnd(looped) => { + CalxSyntax::BlockEnd(looped) => { // println!("checking: {:?}", blocks_track); if blocks_track.is_empty() { return Err(format!("invalid block end, {:?}", blocks_track)); @@ -690,7 +700,7 @@ impl CalxVM { ops.push(CalxInstr::Nop) } - CalxInstr::Call(f_name) => match find_func(&self.funcs, f_name) { + CalxSyntax::Call(f_name) => match find_func(&self.funcs, f_name) { Some(f) => { if stack_size < f.params_types.len() { return Err(format!("insufficient size to call: {} {:?}", stack_size, f.params_types)); @@ -700,7 +710,7 @@ impl CalxVM { } None => return Err(format!("cannot find function named: {}", f_name)), }, - CalxInstr::ReturnCall(f_name) => match find_func(&self.funcs, f_name) { + CalxSyntax::ReturnCall(f_name) => match find_func(&self.funcs, f_name) { Some(f) => { if stack_size < f.params_types.len() { return Err(format!("insufficient size to call: {} {:?}", stack_size, f.params_types)); @@ -710,7 +720,7 @@ impl CalxVM { } None => return Err(format!("cannot find function named: {}", f_name)), }, - CalxInstr::CallImport(f_name) => match &self.imports.get(f_name) { + CalxSyntax::CallImport(f_name) => match &self.imports.get(f_name) { Some((_f, size)) => { if stack_size < *size { return Err(format!("insufficient size to call import: {} {:?}", stack_size, size)); @@ -720,7 +730,7 @@ impl CalxVM { } None => return Err(format!("missing imported function {}", f_name)), }, - CalxInstr::Return => { + CalxSyntax::Return => { let ret_size = self.funcs[i].ret_types.len(); stack_size -= ret_size; if stack_size != 0 { @@ -732,8 +742,9 @@ impl CalxVM { ops.push(CalxInstr::Return); } a => { + let instr: CalxInstr = a.try_into()?; // checks - let (params_size, ret_size) = a.stack_arity(); + let (params_size, ret_size) = instr.stack_arity(); if stack_size < params_size { return Err(format!("insufficient stack {} to call {:?} of {}", stack_size, a, params_size)); } @@ -742,7 +753,7 @@ impl CalxVM { // " sizes: {:?} {} {} -> {}", // a, params_size, ret_size, stack_size // ); - ops.push(a.to_owned()); + ops.push(instr.to_owned()); } } } @@ -753,7 +764,7 @@ impl CalxVM { )); } - self.funcs[i].instrs = Rc::new(ops); + self.funcs[i].instrs = Some(Rc::new(ops)); } Ok(()) } diff --git a/src/vm/func.rs b/src/vm/func.rs index 6dd230a..19cb38c 100644 --- a/src/vm/func.rs +++ b/src/vm/func.rs @@ -2,7 +2,7 @@ use bincode::{Decode, Encode}; use core::fmt; use std::rc::Rc; -use crate::calx::CalxType; +use crate::{calx::CalxType, syntax::CalxSyntax}; use super::instr::CalxInstr; @@ -11,7 +11,8 @@ pub struct CalxFunc { pub name: Rc, pub params_types: Rc>, pub ret_types: Rc>, - pub instrs: Rc>, + pub syntax: Rc>, + pub instrs: Option>>, pub local_names: Rc>, } diff --git a/src/vm/instr.rs b/src/vm/instr.rs index 590d2d1..01cf22f 100644 --- a/src/vm/instr.rs +++ b/src/vm/instr.rs @@ -2,7 +2,10 @@ use std::rc::Rc; use bincode::{Decode, Encode}; -use crate::calx::{Calx, CalxType}; +use crate::{ + calx::{Calx, CalxType}, + syntax::CalxSyntax, +}; /// learning from WASM but for dynamic data #[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] @@ -92,6 +95,73 @@ pub enum CalxInstr { EndIf, } +impl TryFrom<&CalxSyntax> for CalxInstr { + type Error = String; + + fn try_from(syntax: &CalxSyntax) -> Result { + match syntax { + CalxSyntax::LocalSet(a) => Ok(Self::LocalSet(a.to_owned())), + CalxSyntax::LocalTee(a) => Ok(Self::LocalTee(a.to_owned())), + CalxSyntax::LocalGet(a) => Ok(Self::LocalGet(a.to_owned())), + CalxSyntax::LocalNew => Ok(Self::LocalNew), + CalxSyntax::GlobalSet(a) => Ok(Self::GlobalSet(a.to_owned())), + CalxSyntax::GlobalGet(a) => Ok(Self::GlobalGet(a.to_owned())), + CalxSyntax::GlobalNew => Ok(Self::GlobalNew), + CalxSyntax::Const(a) => Ok(Self::Const(a.to_owned())), + CalxSyntax::Dup => Ok(Self::Dup), + CalxSyntax::Drop => Ok(Self::Drop), + CalxSyntax::IntAdd => Ok(Self::IntAdd), + CalxSyntax::IntMul => Ok(Self::IntMul), + CalxSyntax::IntDiv => Ok(Self::IntDiv), + CalxSyntax::IntRem => Ok(Self::IntRem), + CalxSyntax::IntNeg => Ok(Self::IntNeg), + CalxSyntax::IntShr => Ok(Self::IntShr), + CalxSyntax::IntShl => Ok(Self::IntShl), + CalxSyntax::IntEq => Ok(Self::IntEq), + CalxSyntax::IntNe => Ok(Self::IntNe), + CalxSyntax::IntLt => Ok(Self::IntLt), + CalxSyntax::IntLe => Ok(Self::IntLe), + CalxSyntax::IntGt => Ok(Self::IntGt), + CalxSyntax::IntGe => Ok(Self::IntGe), + CalxSyntax::Add => Ok(Self::Add), + CalxSyntax::Mul => Ok(Self::Mul), + CalxSyntax::Div => Ok(Self::Div), + CalxSyntax::Neg => Ok(Self::Neg), + // string operations + // list operations + CalxSyntax::NewList => Ok(Self::NewList), + CalxSyntax::ListGet => Ok(Self::ListGet), + CalxSyntax::ListSet => Ok(Self::ListSet), + // Link + CalxSyntax::NewLink => Ok(Self::NewLink), + // bool operations + CalxSyntax::And => Ok(Self::And), + CalxSyntax::Or => Ok(Self::Or), + CalxSyntax::Not => Ok(Self::Not), + // control stuctures + CalxSyntax::Br(a) => Ok(Self::Br(a.to_owned())), + CalxSyntax::BrIf(a) => Ok(Self::BrIf(a.to_owned())), + CalxSyntax::Jmp(a) => Ok(Self::Jmp(a.to_owned())), + CalxSyntax::JmpIf(a) => Ok(Self::JmpIf(a.to_owned())), + CalxSyntax::Block { .. } => Err("Block should be handled manually".to_string()), + CalxSyntax::BlockEnd(a) => Ok(Self::BlockEnd(a.to_owned())), + CalxSyntax::Echo => Ok(Self::Echo), + CalxSyntax::Call(a) => Ok(Self::Call(a.to_owned())), + CalxSyntax::ReturnCall(a) => Ok(Self::ReturnCall(a.to_owned())), + CalxSyntax::CallImport(a) => Ok(Self::CallImport(a.to_owned())), + CalxSyntax::Unreachable => Ok(Self::Unreachable), + CalxSyntax::Nop => Ok(Self::Nop), + CalxSyntax::Quit(a) => Ok(Self::Quit(a.to_owned())), + CalxSyntax::Return => Ok(Self::Return), + CalxSyntax::Assert(a) => Ok(Self::Assert(a.to_owned())), + // debug + CalxSyntax::Inspect => Ok(Self::Inspect), + CalxSyntax::If { .. } => Err("If should be handled manually".to_string()), + CalxSyntax::EndIf => Ok(Self::EndIf), + } + } +} + impl CalxInstr { /// notice that some of the instrs are special and need to handle manually pub fn stack_arity(&self) -> (usize, usize) { From c4fb99ace4f9a65453beece8e3313158015fc29d Mon Sep 17 00:00:00 2001 From: tiye Date: Sun, 21 Jan 2024 18:46:02 +0800 Subject: [PATCH 05/11] fix instructions of initial frame --- src/bin/cli.rs | 14 +++++--------- src/vm.rs | 19 +++++++++++++++---- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/bin/cli.rs b/src/bin/cli.rs index f2d6f74..c1533ee 100644 --- a/src/bin/cli.rs +++ b/src/bin/cli.rs @@ -27,8 +27,6 @@ pub struct CalxBinaryProgram { struct Args { #[arg(short, long, value_name = "SHOW_CODE")] show_code: bool, - #[arg(short, long, value_name = "DISABLE_PRE")] - disable_pre: bool, #[arg(short, long, value_name = "EMIT_BINARY")] emit_binary: Option, #[arg(short, long, value_name = "VERBOSE")] @@ -44,7 +42,6 @@ fn main() -> Result<(), String> { let source = args.source; let show_code = args.show_code; - let disable_pre = args.disable_pre; let emit_binary = args.emit_binary; let eval_binary = args.eval_binary; @@ -104,12 +101,11 @@ fn main() -> Result<(), String> { // } let now = Instant::now(); - if !disable_pre { - println!("[calx] start preprocessing"); - vm.preprocess(args.verbose)?; - } else { - println!("Preprocess disabled.") - } + + println!("[calx] start preprocessing"); + vm.preprocess(args.verbose)?; + + vm.setup_top_frame()?; if show_code { for func in &vm.funcs { diff --git a/src/vm.rs b/src/vm.rs index eb3a1ce..289b8ad 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -44,10 +44,8 @@ impl CalxVM { name: main_func.name.to_owned(), initial_stack_size: 0, blocks_track: vec![], - instrs: match main_func.instrs { - Some(ref x) => x.to_owned(), - None => panic!("main function must have instrs"), - }, + // use empty instrs, will be replaced by preprocess + instrs: Rc::new(vec![]), pointer: 0, locals: vec![], ret_types: main_func.ret_types.clone(), @@ -64,6 +62,18 @@ impl CalxVM { } } + pub fn setup_top_frame(&mut self) -> Result<(), String> { + self.top_frame.instrs = match find_func(&self.funcs, "main") { + Some(f) => match f.instrs.to_owned() { + Some(x) => x, + None => return Err("main function must have instrs".to_owned()), + }, + None => return Err("main function is required".to_owned()), + }; + + Ok(()) + } + pub fn make_return(&mut self, v: Calx) { self.return_value = v; self.finished = true; @@ -766,6 +776,7 @@ impl CalxVM { self.funcs[i].instrs = Some(Rc::new(ops)); } + Ok(()) } From f889a64fc750becb28c0bb9e8135ef3b7d8f8f15 Mon Sep 17 00:00:00 2001 From: tiye Date: Sun, 21 Jan 2024 19:26:09 +0800 Subject: [PATCH 06/11] moving some code to FromStr --- src/calx.rs | 56 +++++++++++++++++++++++++++++++++--------- src/calx/types.rs | 30 +++++++++++++++++++++++ src/parser.rs | 62 ++++++----------------------------------------- src/syntax.rs | 2 -- src/vm.rs | 27 +++++++++++++++------ src/vm/instr.rs | 10 +++++--- 6 files changed, 106 insertions(+), 81 deletions(-) create mode 100644 src/calx/types.rs diff --git a/src/calx.rs b/src/calx.rs index 4d254bc..eeab805 100644 --- a/src/calx.rs +++ b/src/calx.rs @@ -1,6 +1,12 @@ -use core::fmt; +mod types; use bincode::{Decode, Encode}; +use core::fmt; +use lazy_static::lazy_static; +use regex::Regex; +use std::str::FromStr; + +pub use types::CalxType; /// Simplied from Calcit, but trying to be basic and mutable #[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] @@ -15,6 +21,37 @@ pub enum Calx { // Link(Box, Box, Box), } +impl FromStr for Calx { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "nil" => Ok(Calx::Nil), + "true" => Ok(Calx::Bool(true)), + "false" => Ok(Calx::Bool(false)), + "" => Err(String::from("unknown empty string")), + _ => { + let s0 = s.chars().next().unwrap(); + if s0 == '|' || s0 == ':' { + Ok(Calx::Str(s[1..s.len()].to_owned())) + } else if FLOAT_PATTERN.is_match(s) { + match s.parse::() { + Ok(u) => Ok(Calx::F64(u)), + Err(e) => Err(format!("failed to parse: {}", e)), + } + } else if INT_PATTERN.is_match(s) { + match s.parse::() { + Ok(u) => Ok(Calx::I64(u)), + Err(e) => Err(format!("failed to parse: {}", e)), + } + } else { + Err(format!("unknown value: {}", s)) + } + } + } + } +} + impl Calx { // for runtime type checking pub fn typed_as(&self, t: CalxType) -> bool { @@ -42,17 +79,6 @@ impl Calx { } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Decode, Encode)] -pub enum CalxType { - Nil, - Bool, - I64, - F64, - Str, - List, - Link, -} - impl fmt::Display for Calx { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -78,3 +104,9 @@ impl fmt::Display for Calx { } } } + +lazy_static! { + static ref FLOAT_PATTERN: Regex = Regex::new("^-?\\d+\\.(\\d+)?$").unwrap(); + static ref INT_PATTERN: Regex = Regex::new("^-?\\d+$").unwrap(); + static ref USIZE_PATTERN: Regex = Regex::new("^\\d+$").unwrap(); +} diff --git a/src/calx/types.rs b/src/calx/types.rs new file mode 100644 index 0000000..d0638e3 --- /dev/null +++ b/src/calx/types.rs @@ -0,0 +1,30 @@ +use bincode::{Decode, Encode}; +use std::str::FromStr; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Decode, Encode)] +pub enum CalxType { + Nil, + Bool, + I64, + F64, + Str, + List, + Link, +} + +impl FromStr for CalxType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "nil" => Ok(CalxType::Nil), + "bool" => Ok(CalxType::Bool), + "i64" => Ok(CalxType::I64), + "f64" => Ok(CalxType::F64), + "str" => Ok(CalxType::Str), + "list" => Ok(CalxType::List), + "link" => Ok(CalxType::Link), + _ => Err(format!("unknown type: {}", s)), + } + } +} diff --git a/src/parser.rs b/src/parser.rs index 82eed0a..b510922 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,12 +6,9 @@ mod locals; use std::rc::Rc; -use lazy_static::lazy_static; -use regex::Regex; - use cirru_parser::Cirru; -use crate::calx::{Calx, CalxType}; +use crate::calx::CalxType; use crate::syntax::CalxSyntax; use crate::vm::func::CalxFunc; @@ -81,11 +78,11 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto if xs.is_empty() { return Err(String::from("empty expr")); } - let i0 = xs[0].to_owned(); + let i0 = &xs[0]; match i0 { Cirru::List(_) => Err(format!("expected instruction name in a string, got {}", i0)), - Cirru::Leaf(name) => match &*name { + Cirru::Leaf(name) => match &**name { "local.get" => { if xs.len() != 2 { return Err(format!("local.get expected a position, {:?}", xs)); @@ -139,7 +136,7 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto } match &xs[1] { Cirru::Leaf(s) => { - let p1 = parse_value(s)?; + let p1 = s.parse()?; Ok(vec![CalxSyntax::Const(p1)]) } Cirru::List(a) => Err(format!("`const` not supporting list here: {:?}", a)), @@ -270,12 +267,6 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto } } -lazy_static! { - static ref FLOAT_PATTERN: Regex = Regex::new("^-?\\d+\\.(\\d+)?$").unwrap(); - static ref INT_PATTERN: Regex = Regex::new("^-?\\d+$").unwrap(); - static ref USIZE_PATTERN: Regex = Regex::new("^\\d+$").unwrap(); -} - fn parse_local_idx(x: &Cirru, collector: &mut LocalsCollector) -> Result { match x { Cirru::Leaf(s) => match s.chars().next() { @@ -292,33 +283,6 @@ fn parse_local_idx(x: &Cirru, collector: &mut LocalsCollector) -> Result Result { - match s { - "nil" => Ok(Calx::Nil), - "true" => Ok(Calx::Bool(true)), - "false" => Ok(Calx::Bool(false)), - "" => Err(String::from("unknown empty string")), - _ => { - let s0 = s.chars().next().unwrap(); - if s0 == '|' || s0 == ':' { - Ok(Calx::Str(s[1..s.len()].to_owned())) - } else if FLOAT_PATTERN.is_match(s) { - match s.parse::() { - Ok(u) => Ok(Calx::F64(u)), - Err(e) => Err(format!("failed to parse: {}", e)), - } - } else if INT_PATTERN.is_match(s) { - match s.parse::() { - Ok(u) => Ok(Calx::I64(u)), - Err(e) => Err(format!("failed to parse: {}", e)), - } - } else { - Err(format!("unknown value: {}", s)) - } - } - } -} - pub fn parse_usize(s: &str) -> Result { match s.parse::() { Ok(u) => Ok(u), @@ -373,7 +337,7 @@ pub fn parse_fn_types(xs: &Cirru, collector: &mut LocalsCollector) -> Result<(Ve if &**t == "->" { ret_mode = true; } else { - let ty = parse_type_name(t)?; + let ty = t.parse()?; if ret_mode { returns.push(ty); } else { @@ -397,7 +361,7 @@ pub fn parse_fn_types(xs: &Cirru, collector: &mut LocalsCollector) -> Result<(Ve Cirru::List(_) => return Err(format!("invalid syntax, expected name, got {:?}", x)), }; let ty = match &zs[1] { - Cirru::Leaf(s) => parse_type_name(s)?, + Cirru::Leaf(s) => s.parse()?, Cirru::List(_) => return Err(format!("invalid syntax, expected type, got {:?}", x)), }; collector.track(&name_str); @@ -425,7 +389,7 @@ pub fn parse_block_types(xs: &Cirru) -> Result<(Vec, Vec), S if &**t == "->" { ret_mode = true; } else { - let ty = parse_type_name(t)?; + let ty = t.parse()?; if ret_mode { returns.push(ty); } else { @@ -440,18 +404,6 @@ pub fn parse_block_types(xs: &Cirru) -> Result<(Vec, Vec), S } } -fn parse_type_name(x: &str) -> Result { - match x { - "nil" => Ok(CalxType::Nil), - "bool" => Ok(CalxType::Bool), - "i64" => Ok(CalxType::I64), - "f64" => Ok(CalxType::F64), - "list" => Ok(CalxType::List), - "link" => Ok(CalxType::Link), - a => Err(format!("Unknown type: {}", a)), - } -} - /// rather stupid function to extract nested calls before current call /// TODO better have some tests pub fn extract_nested(xs: &Cirru) -> Result, String> { diff --git a/src/syntax.rs b/src/syntax.rs index 7739493..be55e77 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -56,8 +56,6 @@ pub enum CalxSyntax { // control stuctures Br(usize), BrIf(usize), - Jmp(usize), // internal - JmpIf(usize), // internal Block { params_types: Rc>, ret_types: Rc>, diff --git a/src/vm.rs b/src/vm.rs index 289b8ad..4e5a757 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -154,6 +154,10 @@ impl CalxVM { self.top_frame.pointer = line.to_owned(); return Ok(true); // point reset, goto next loop } + JmpOffset(l) => { + self.top_frame.pointer = (self.top_frame.pointer as i32 + l) as usize; + return Ok(true); // point reset, goto next loop + } JmpIf(line) => { let v = self.stack_pop()?; if v == Calx::Bool(true) || v == Calx::I64(1) { @@ -161,6 +165,13 @@ impl CalxVM { return Ok(true); // point reset, goto next loop } } + JmpOffsetIf(l) => { + let v = self.stack_pop()?; + if v == Calx::Bool(true) || v == Calx::I64(1) { + self.top_frame.pointer = (self.top_frame.pointer as i32 + l) as usize; + return Ok(true); // point reset, goto next loop + } + } Br(size) => { self.shrink_blocks_by(*size)?; @@ -461,25 +472,25 @@ impl CalxVM { } } NewList => { - // TODO + todo!() } ListGet => { - // TODO + todo!() } ListSet => { - // TODO + todo!() } NewLink => { - // TODO + todo!() } And => { - // TODO + todo!() } Or => { - // TODO + todo!() } Not => { - // TODO + todo!() } Call(f_name) => { // println!("frame size: {}", self.frames.len()); @@ -502,7 +513,7 @@ impl CalxVM { pointer: 0, instrs: match instrs { Some(x) => x.to_owned(), - None => panic!("function must have instrs"), + None => unreachable!("function must have instrs"), }, ret_types, }; diff --git a/src/vm/instr.rs b/src/vm/instr.rs index 01cf22f..81d2cfb 100644 --- a/src/vm/instr.rs +++ b/src/vm/instr.rs @@ -59,8 +59,10 @@ pub enum CalxInstr { // control stuctures Br(usize), BrIf(usize), - Jmp(usize), // internal - JmpIf(usize), // internal + Jmp(usize), // internal + JmpOffset(i32), // internal + JmpIf(usize), // internal + JmpOffsetIf(i32), // internal Block { params_types: Rc>, ret_types: Rc>, @@ -141,8 +143,6 @@ impl TryFrom<&CalxSyntax> for CalxInstr { // control stuctures CalxSyntax::Br(a) => Ok(Self::Br(a.to_owned())), CalxSyntax::BrIf(a) => Ok(Self::BrIf(a.to_owned())), - CalxSyntax::Jmp(a) => Ok(Self::Jmp(a.to_owned())), - CalxSyntax::JmpIf(a) => Ok(Self::JmpIf(a.to_owned())), CalxSyntax::Block { .. } => Err("Block should be handled manually".to_string()), CalxSyntax::BlockEnd(a) => Ok(Self::BlockEnd(a.to_owned())), CalxSyntax::Echo => Ok(Self::Echo), @@ -208,7 +208,9 @@ impl CalxInstr { CalxInstr::Br(_) => (0, 0), CalxInstr::BrIf(_) => (1, 0), CalxInstr::Jmp(_) => (0, 0), + CalxInstr::JmpOffset(_) => (0, 0), CalxInstr::JmpIf(_) => (1, 0), + CalxInstr::JmpOffsetIf(_) => (1, 0), CalxInstr::Block { params_types, ret_types, .. } => (params_types.len(), ret_types.len()), From 6db0ed10a4309db04e5576bd776c56879f2535a3 Mon Sep 17 00:00:00 2001 From: tiye Date: Mon, 22 Jan 2024 00:08:52 +0800 Subject: [PATCH 07/11] prepare syntax for if instruction --- demos/if.cirru | 11 +++++ src/parser.rs | 113 +++++++++++++++++++++++++++++++++++++----------- src/syntax.rs | 5 ++- src/vm.rs | 21 +++++---- src/vm/func.rs | 11 ++++- src/vm/instr.rs | 1 + 6 files changed, 124 insertions(+), 38 deletions(-) create mode 100644 demos/if.cirru diff --git a/demos/if.cirru b/demos/if.cirru new file mode 100644 index 0000000..498920f --- /dev/null +++ b/demos/if.cirru @@ -0,0 +1,11 @@ + + +fn main () + const 1 + if (->) + do + const 11 + echo + do + const 20 + echo diff --git a/src/parser.rs b/src/parser.rs index b510922..fde6d94 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -22,17 +22,11 @@ use self::locals::LocalsCollector; /// ``` pub fn parse_function(nodes: &[Cirru]) -> Result { if nodes.len() <= 3 { - return Err(String::from("Not a function")); + return Err(String::from("function expects at least 3 lines")); } - if let Cirru::Leaf(x) = nodes[0].to_owned() { - if &*x == "fn" { - // ok - } else { - return Err(String::from("invalid")); - } - } else { - return Err(String::from("invalid")); + if !leaf_is(&nodes[0], "fn") && !leaf_is(&nodes[0], "defn") { + return Err(String::from("Not a function")); } let name: Box = if let Cirru::Leaf(x) = nodes[1].to_owned() { @@ -260,7 +254,8 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto Ok(vec![CalxSyntax::Assert((*message).to_owned())]) } "inspect" => Ok(vec![CalxSyntax::Inspect]), - _ => Err(format!("unknown instruction: {}", name)), + "if" => parse_if(ptr_base, xs, collector), + _ => Err(format!("unknown instruction: {} in {:?}", name, xs)), }, } } @@ -296,10 +291,13 @@ pub fn parse_block(ptr_base: usize, xs: &[Cirru], looped: bool, collector: &mut let (params_types, ret_types) = parse_block_types(&xs[1])?; for (idx, line) in xs.iter().enumerate() { if idx > 1 { - let instrs = parse_instr(p, line, collector)?; - for y in instrs { - p += 1; - chunk.push(y); + let lines = extract_nested(line)?; + for expanded in &lines { + let instrs = parse_instr(p, expanded, collector)?; + for y in instrs { + p += 1; + chunk.push(y); + } } } } @@ -322,6 +320,72 @@ pub fn parse_block(ptr_base: usize, xs: &[Cirru], looped: bool, collector: &mut Ok(chunk) } +pub fn parse_if(ptr_base: usize, xs: &[Cirru], collector: &mut LocalsCollector) -> Result, String> { + if xs.len() != 4 && xs.len() != 3 { + return Err(format!("if expected 2 or 3 arguments, got {:?}", xs)); + } + let types = parse_block_types(&xs[1])?; + let ret_types = types.1.clone(); + let then_syntax = parse_do(&xs[2], collector)?; + let else_syntax = if xs.len() == 4 { parse_do(&xs[3], collector)? } else { vec![] }; + + let mut p = ptr_base + 1; // leave a place for if instruction + let mut chunk: Vec = vec![]; + for instr in then_syntax { + p += 1; + chunk.push(instr); + } + p += 1; + let else_at = p; + chunk.push(CalxSyntax::EndIf); + for instr in else_syntax { + p += 1; + chunk.push(instr); + } + + p += 1; + chunk.push(CalxSyntax::EndIf); + + let to = p; + + chunk.insert( + 0, + CalxSyntax::If { + ret_types: Rc::new(ret_types), + else_at, + to, + }, + ); + + Ok(chunk) +} + +pub fn parse_do(xs: &Cirru, collector: &mut LocalsCollector) -> Result, String> { + match xs { + Cirru::Leaf(_) => Err(format!("expect expression for types, got {}", xs)), + Cirru::List(ys) => { + let x0 = &ys[0]; + if !leaf_is(x0, "do") { + return Err(format!("expected do, got {}", x0)); + } + + let mut chunk: Vec = vec![]; + for (idx, x) in ys.iter().enumerate() { + if idx > 0 { + let lines = extract_nested(x)?; + for expanded in &lines { + let instrs = parse_instr(idx, expanded, collector)?; + for y in instrs { + chunk.push(y); + } + } + } + } + Ok(chunk) + } + } +} + /// parameters might be named, need to check, by default use integers pub fn parse_fn_types(xs: &Cirru, collector: &mut LocalsCollector) -> Result<(Vec, Vec), String> { match xs { @@ -413,17 +477,7 @@ pub fn extract_nested(xs: &Cirru) -> Result, String> { None => Err(String::from("unexpected empty expression")), Some(Cirru::List(zs)) => Err(format!("unexpected nested instruction name: {:?}", zs)), Some(Cirru::Leaf(zs)) => match &**zs { - "block" | "loop" => { - let mut chunk: Vec = vec![Cirru::Leaf(zs.to_owned())]; - for (idx, y) in ys.iter().enumerate() { - if idx > 0 { - for e in extract_nested(y)? { - chunk.push(e); - } - } - } - Ok(vec![Cirru::List(chunk)]) - } + "block" | "loop" | "if" | "do" => Ok(vec![xs.to_owned()]), _ => { let mut pre: Vec = vec![]; let mut chunk: Vec = vec![Cirru::Leaf(zs.to_owned())]; @@ -446,3 +500,12 @@ pub fn extract_nested(xs: &Cirru) -> Result, String> { }, } } + +pub fn leaf_is(x: &Cirru, name: &str) -> bool { + if let Cirru::Leaf(y) = x { + if &**y == name { + return true; + } + } + false +} diff --git a/src/syntax.rs b/src/syntax.rs index be55e77..ac333e4 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -65,6 +65,8 @@ pub enum CalxSyntax { to: usize, }, BlockEnd(bool), + /// just a list of instructions nested + Do(Vec), /// pop and println current value Echo, /// TODO use function name at first, during running, index can be faster @@ -83,8 +85,7 @@ pub enum CalxSyntax { /// if takes 1 value from stack, returns values as ret_types If { ret_types: Rc>, - then_to: usize, - else_to: usize, + else_at: usize, to: usize, }, EndIf, diff --git a/src/vm.rs b/src/vm.rs index 4e5a757..373ee1b 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -39,7 +39,7 @@ impl std::fmt::Debug for CalxVM { impl CalxVM { pub fn new(fns: Vec, globals: Vec, imports: CalxImportsDict) -> Self { - let main_func = find_func(&fns, "main").expect("main function is required"); + let main_func = fns.iter().find(|x| *x.name == "main").expect("main function is required"); let main_frame = CalxFrame { name: main_func.name.to_owned(), initial_stack_size: 0, @@ -63,7 +63,7 @@ impl CalxVM { } pub fn setup_top_frame(&mut self) -> Result<(), String> { - self.top_frame.instrs = match find_func(&self.funcs, "main") { + self.top_frame.instrs = match self.find_func("main") { Some(f) => match f.instrs.to_owned() { Some(x) => x, None => return Err("main function must have instrs".to_owned()), @@ -494,7 +494,7 @@ impl CalxVM { } Call(f_name) => { // println!("frame size: {}", self.frames.len()); - match find_func(&self.funcs, f_name) { + match self.find_func(f_name) { Some(f) => { let instrs = f.instrs.to_owned(); let ret_types = f.ret_types.to_owned(); @@ -526,7 +526,7 @@ impl CalxVM { } ReturnCall(f_name) => { // println!("frame size: {}", self.frames.len()); - match find_func(&self.funcs, f_name) { + match self.find_func(f_name) { Some(f) => { // println!("examine stack: {:?}", self.stack); let instrs = f.instrs.to_owned(); @@ -721,7 +721,7 @@ impl CalxVM { ops.push(CalxInstr::Nop) } - CalxSyntax::Call(f_name) => match find_func(&self.funcs, f_name) { + CalxSyntax::Call(f_name) => match self.find_func(f_name) { Some(f) => { if stack_size < f.params_types.len() { return Err(format!("insufficient size to call: {} {:?}", stack_size, f.params_types)); @@ -731,7 +731,7 @@ impl CalxVM { } None => return Err(format!("cannot find function named: {}", f_name)), }, - CalxSyntax::ReturnCall(f_name) => match find_func(&self.funcs, f_name) { + CalxSyntax::ReturnCall(f_name) => match self.find_func(f_name) { Some(f) => { if stack_size < f.params_types.len() { return Err(format!("insufficient size to call: {} {:?}", stack_size, f.params_types)); @@ -762,6 +762,9 @@ impl CalxVM { } ops.push(CalxInstr::Return); } + CalxSyntax::If { ret_types, else_at, to } => { + todo!() + } a => { let instr: CalxInstr = a.try_into()?; // checks @@ -865,10 +868,10 @@ impl CalxVM { globals: self.globals.to_owned(), } } -} -pub fn find_func<'a>(funcs: &'a [CalxFunc], name: &str) -> Option<&'a CalxFunc> { - funcs.iter().find(|x| *x.name == name) + fn find_func(&self, name: &str) -> Option<&CalxFunc> { + self.funcs.iter().find(|x| *x.name == name) + } } #[derive(Debug, Clone, PartialEq, PartialOrd)] diff --git a/src/vm/func.rs b/src/vm/func.rs index 19cb38c..57618cd 100644 --- a/src/vm/func.rs +++ b/src/vm/func.rs @@ -34,8 +34,15 @@ impl fmt::Display for CalxFunc { } f.write_str(" .")?; } - for (idx, instr) in self.instrs.iter().enumerate() { - write!(f, "\n {:02} {:?}", idx, instr)?; + match &self.instrs { + Some(instrs) => { + for (idx, instr) in instrs.iter().enumerate() { + write!(f, "\n {:02} {:?}", idx, instr)?; + } + } + None => { + write!(f, "\n ")?; + } } f.write_str("\n")?; Ok(()) diff --git a/src/vm/instr.rs b/src/vm/instr.rs index 81d2cfb..7384458 100644 --- a/src/vm/instr.rs +++ b/src/vm/instr.rs @@ -158,6 +158,7 @@ impl TryFrom<&CalxSyntax> for CalxInstr { CalxSyntax::Inspect => Ok(Self::Inspect), CalxSyntax::If { .. } => Err("If should be handled manually".to_string()), CalxSyntax::EndIf => Ok(Self::EndIf), + CalxSyntax::Do(_) => Err("do should be handled manually".to_string()), } } } From 22058bd80102da61d26ea17ec9cbcc0876972034 Mon Sep 17 00:00:00 2001 From: tiye Date: Mon, 22 Jan 2024 00:25:29 +0800 Subject: [PATCH 08/11] rename runtime behaviors of BlockData --- src/vm.rs | 90 ------------------------------------------------- src/vm/frame.rs | 4 +-- src/vm/instr.rs | 23 ++----------- 3 files changed, 4 insertions(+), 113 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 373ee1b..cd48b95 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -43,7 +43,6 @@ impl CalxVM { let main_frame = CalxFrame { name: main_func.name.to_owned(), initial_stack_size: 0, - blocks_track: vec![], // use empty instrs, will be replaced by preprocess instrs: Rc::new(vec![]), pointer: 0, @@ -93,7 +92,6 @@ impl CalxVM { fmt::write(&mut output, format_args!("{indent}Top frame: {}", self.top_frame.name)).expect("inspect display"); fmt::write(&mut output, format_args!("{indent}Locals: {:?}", self.top_frame.locals)).expect("inspect display"); - fmt::write(&mut output, format_args!("{indent}Blocks: {:?}", self.top_frame.blocks_track)).expect("inspect display"); fmt::write(&mut output, format_args!("{indent}Stack({}): {:?}", self.stack.len(), self.stack)).expect("inspect display"); fmt::write( &mut output, @@ -172,69 +170,6 @@ impl CalxVM { return Ok(true); // point reset, goto next loop } } - Br(size) => { - self.shrink_blocks_by(*size)?; - - let last_idx = self.top_frame.blocks_track.len() - 1; - if self.top_frame.blocks_track[last_idx].looped { - self.top_frame.pointer = self.top_frame.blocks_track[last_idx].from; - } else { - self.top_frame.pointer = self.top_frame.blocks_track[last_idx].to; - } - - return Ok(true); // point reset, goto next loop - } - BrIf(size) => { - let last_idx = self.stack.len() - 1; - if self.stack[last_idx] == Calx::Bool(true) || self.stack[last_idx] == Calx::I64(1) { - self.shrink_blocks_by(*size)?; - - let last_idx = self.top_frame.blocks_track.len() - 1; - if self.top_frame.blocks_track[last_idx].looped { - self.top_frame.pointer = self.top_frame.blocks_track[last_idx].from; - } else { - self.top_frame.pointer = self.top_frame.blocks_track[last_idx].to; - } - - return Ok(true); // point reset, goto next loop - } - } - Block { - looped, - from, - to, - params_types, - ret_types, - } => { - if self.stack.len() < params_types.len() { - return Err(self.gen_err(format!("no enough data on stack {:?} for {:?}", self.stack, params_types))); - } - self.top_frame.blocks_track.push(BlockData { - looped: looped.to_owned(), - params_types: params_types.to_owned(), - ret_types: ret_types.to_owned(), - from: from.to_owned(), - to: to.to_owned(), - initial_stack_size: self.stack.len() - params_types.len(), - }); - self.check_stack_for_block(params_types)?; - } - BlockEnd(looped) => { - if *looped { - return Err(self.gen_err(String::from("loop end expected to be branched"))); - } - let last_block = self.top_frame.blocks_track.pop().unwrap(); - if self.stack.len() != last_block.initial_stack_size + last_block.ret_types.len() { - return Err(self.gen_err(format!( - "block-end {:?} expected initial size {} plus {:?}, got stack size {}, in\n {}", - last_block, - last_block.initial_stack_size, - last_block.ret_types, - self.stack.len(), - self.top_frame - ))); - } - } LocalSet(idx) => { let v = self.stack_pop()?; if *idx >= self.top_frame.locals.len() { @@ -507,7 +442,6 @@ impl CalxVM { self.frames.push(self.top_frame.to_owned()); self.top_frame = CalxFrame { name: f_name, - blocks_track: vec![], initial_stack_size: self.stack.len(), locals, pointer: 0, @@ -547,7 +481,6 @@ impl CalxVM { } self.top_frame = CalxFrame { name: f_name, - blocks_track: vec![], initial_stack_size: self.stack.len(), locals, pointer: 0, @@ -839,30 +772,9 @@ impl CalxVM { self.stack.push(x) } - /// assumed that the size already checked - #[inline(always)] - fn shrink_blocks_by(&mut self, size: usize) -> Result<(), CalxError> { - if self.top_frame.blocks_track.len() <= size { - return Err(self.gen_err(format!( - "stack size {} eq/smaller than br size {}", - self.top_frame.blocks_track.len(), - size - ))); - } - - let mut i = size; - while i > 0 { - self.top_frame.blocks_track.pop(); - i -= 1; - } - - Ok(()) - } - fn gen_err(&self, s: String) -> CalxError { CalxError { message: s, - blocks: self.top_frame.blocks_track.to_owned(), top_frame: self.top_frame.to_owned(), stack: self.stack.to_owned(), globals: self.globals.to_owned(), @@ -879,7 +791,6 @@ pub struct CalxError { pub message: String, pub stack: Vec, pub top_frame: CalxFrame, - pub blocks: Vec, pub globals: Vec, } @@ -895,7 +806,6 @@ impl CalxError { message: s, stack: vec![], top_frame: CalxFrame::default(), - blocks: vec![], globals: vec![], } } diff --git a/src/vm/frame.rs b/src/vm/frame.rs index 6ed8f4f..2c474ca 100644 --- a/src/vm/frame.rs +++ b/src/vm/frame.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use crate::calx::{Calx, CalxType}; -use super::{block_data::BlockData, instr::CalxInstr}; +use super::instr::CalxInstr; #[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct CalxFrame { @@ -13,7 +13,6 @@ pub struct CalxFrame { pub instrs: Rc>, pub pointer: usize, pub initial_stack_size: usize, - pub blocks_track: Vec, pub ret_types: Rc>, } @@ -25,7 +24,6 @@ impl Default for CalxFrame { instrs: Rc::new(vec![]), pointer: 0, initial_stack_size: 0, - blocks_track: vec![], ret_types: Rc::new(vec![]), } } diff --git a/src/vm/instr.rs b/src/vm/instr.rs index 7384458..66d633c 100644 --- a/src/vm/instr.rs +++ b/src/vm/instr.rs @@ -57,21 +57,10 @@ pub enum CalxInstr { Or, Not, // control stuctures - Br(usize), - BrIf(usize), Jmp(usize), // internal JmpOffset(i32), // internal JmpIf(usize), // internal JmpOffsetIf(i32), // internal - Block { - params_types: Rc>, - ret_types: Rc>, - /// bool to indicate loop - looped: bool, - from: usize, - to: usize, - }, - BlockEnd(bool), /// pop and println current value Echo, /// TODO use function name at first, during running, index can be faster @@ -141,10 +130,10 @@ impl TryFrom<&CalxSyntax> for CalxInstr { CalxSyntax::Or => Ok(Self::Or), CalxSyntax::Not => Ok(Self::Not), // control stuctures - CalxSyntax::Br(a) => Ok(Self::Br(a.to_owned())), - CalxSyntax::BrIf(a) => Ok(Self::BrIf(a.to_owned())), + CalxSyntax::Br(_) => Err("Br should be handled manually".to_string()), + CalxSyntax::BrIf(_) => Err("BrIf should be handled manually".to_owned()), CalxSyntax::Block { .. } => Err("Block should be handled manually".to_string()), - CalxSyntax::BlockEnd(a) => Ok(Self::BlockEnd(a.to_owned())), + CalxSyntax::BlockEnd(a) => Err(format!("BlockEnd should be handled manually: {}", a)), CalxSyntax::Echo => Ok(Self::Echo), CalxSyntax::Call(a) => Ok(Self::Call(a.to_owned())), CalxSyntax::ReturnCall(a) => Ok(Self::ReturnCall(a.to_owned())), @@ -206,16 +195,10 @@ impl CalxInstr { CalxInstr::Or => (2, 1), CalxInstr::Not => (1, 1), // control stuctures - CalxInstr::Br(_) => (0, 0), - CalxInstr::BrIf(_) => (1, 0), CalxInstr::Jmp(_) => (0, 0), CalxInstr::JmpOffset(_) => (0, 0), CalxInstr::JmpIf(_) => (1, 0), CalxInstr::JmpOffsetIf(_) => (1, 0), - CalxInstr::Block { - params_types, ret_types, .. - } => (params_types.len(), ret_types.len()), - CalxInstr::BlockEnd(_) => (0, 0), CalxInstr::Echo => (1, 0), CalxInstr::Call(_) => (0, 0), // TODO CalxInstr::ReturnCall(_) => (0, 0), // TODO From 0bf4f1cf416c4487e397361df3d31e4905777691 Mon Sep 17 00:00:00 2001 From: tiye Date: Mon, 22 Jan 2024 01:13:55 +0800 Subject: [PATCH 09/11] refactor BlockData to track "if" info --- .github/workflows/publish.yaml | 3 +- .github/workflows/test.yaml | 3 +- src/parser.rs | 10 ++- src/syntax.rs | 3 +- src/vm.rs | 104 +++++++++++++++++------- src/vm/block_data.rs | 141 +++++++++++++++++++++++++++++++-- src/vm/instr.rs | 3 +- 7 files changed, 222 insertions(+), 45 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index b812f2c..bd8a2a3 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -21,7 +21,8 @@ jobs: - run: cargo run -- -s demos/assert.cirru - run: cargo run -- -s demos/nested.cirru - run: cargo run -- -s demos/named.cirru - - run: cargo run -- demos/recur.cirru + - run: cargo run -- -s demos/recur.cirru + - run: cargo run -- -s demos/fibonacci.cirru - run: cargo run -- --emit-binary target/a.calx demos/named.cirru && cargo run -- --eval-binary target/a.calx # - run: cargo test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 79802d8..6dcf334 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -29,7 +29,8 @@ jobs: - run: cargo run -- -s demos/assert.cirru - run: cargo run -- -s demos/nested.cirru - run: cargo run -- -s demos/named.cirru - - run: cargo run -- demos/recur.cirru + - run: cargo run -- -s demos/recur.cirru + - run: cargo run -- -s demos/fibonacci.cirru - run: cargo run -- --emit-binary target/a.calx demos/named.cirru && cargo run -- --eval-binary target/a.calx - uses: giraffate/clippy-action@v1 diff --git a/src/parser.rs b/src/parser.rs index fde6d94..f773b21 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -331,20 +331,22 @@ pub fn parse_if(ptr_base: usize, xs: &[Cirru], collector: &mut LocalsCollector) let mut p = ptr_base + 1; // leave a place for if instruction let mut chunk: Vec = vec![]; - for instr in then_syntax { + + // put else branch first, and use jmp to target then branch + for instr in else_syntax { p += 1; chunk.push(instr); } p += 1; let else_at = p; - chunk.push(CalxSyntax::EndIf); - for instr in else_syntax { + chunk.push(CalxSyntax::ElseEnd); + for instr in then_syntax { p += 1; chunk.push(instr); } p += 1; - chunk.push(CalxSyntax::EndIf); + chunk.push(CalxSyntax::ThenEnd); let to = p; diff --git a/src/syntax.rs b/src/syntax.rs index ac333e4..68d551d 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -88,5 +88,6 @@ pub enum CalxSyntax { else_at: usize, to: usize, }, - EndIf, + ThenEnd, + ElseEnd, } diff --git a/src/vm.rs b/src/vm.rs index cd48b95..630fd0f 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -10,6 +10,7 @@ use std::{fmt, vec}; use crate::calx::{Calx, CalxType}; use crate::syntax::CalxSyntax; +use crate::vm::block_data::BlockStack; use self::block_data::BlockData; use self::frame::CalxFrame; @@ -556,7 +557,7 @@ impl CalxVM { for i in 0..self.funcs.len() { let mut stack_size = 0; let mut ops: Vec = vec![]; - let mut blocks_track: Vec = vec![]; + let mut blocks_track = BlockStack::new(); let f = &self.funcs[i]; @@ -585,14 +586,22 @@ impl CalxVM { if stack_size < params_types.len() { return Err(format!("insufficient params {} for block: {:?}", stack_size, params_types)); } - blocks_track.push(BlockData { - looped: looped.to_owned(), - params_types: params_types.to_owned(), - ret_types: ret_types.to_owned(), - from: from.to_owned(), - to: to.to_owned(), - initial_stack_size: stack_size, - }); + if *looped { + blocks_track.push(BlockData::Loop { + params_types: params_types.to_owned(), + ret_types: ret_types.to_owned(), + from: from.to_owned(), + to: to.to_owned(), + initial_stack_size: stack_size, + }); + } else { + blocks_track.push(BlockData::Block { + params_types: params_types.to_owned(), + ret_types: ret_types.to_owned(), + to: to.to_owned(), + initial_stack_size: stack_size, + }); + } ops.push(CalxInstr::Nop); } CalxSyntax::Br(size) => { @@ -600,17 +609,16 @@ impl CalxVM { return Err(format!("br {} too large", size)); } - let target_block = blocks_track[blocks_track.len() - size - 1].to_owned(); + let target_block = blocks_track.peek_block_level(*size)?; let expected_size = target_block.expected_finish_size(); if stack_size != expected_size { return Err(format!("br({size}) expected size {expected_size}, got {stack_size}")); } - if target_block.looped { - // not checking - ops.push(CalxInstr::Jmp(target_block.from)) - } else { - ops.push(CalxInstr::Jmp(target_block.to)) + match target_block { + BlockData::Loop { from, .. } => ops.push(CalxInstr::Jmp(from.to_owned())), + BlockData::Block { to, .. } => ops.push(CalxInstr::Jmp(to.to_owned())), + _ => unreachable!("br target must be block or loop"), } } CalxSyntax::BrIf(size) => { @@ -621,12 +629,12 @@ impl CalxVM { return Err(format!("br {} too large", size)); } - let target_block = blocks_track[blocks_track.len() - size - 1].to_owned(); - if target_block.looped { - // not checking - ops.push(CalxInstr::JmpIf(target_block.from)) - } else { - ops.push(CalxInstr::JmpIf(target_block.to)) + let target_block = blocks_track.peek_block_level(*size)?; + + match target_block { + BlockData::Loop { from, .. } => ops.push(CalxInstr::JmpIf(from.to_owned())), + BlockData::Block { to, .. } => ops.push(CalxInstr::JmpIf(to.to_owned())), + _ => unreachable!("br target must be block or loop"), } stack_size -= 1; @@ -641,15 +649,11 @@ impl CalxVM { return Err(format!("invalid block end, {:?}", blocks_track)); } - let prev_block = blocks_track.pop().unwrap(); + let prev_block = blocks_track.pop_block()?; if *looped { // nothing, branched during runtime - } else if stack_size != prev_block.initial_stack_size + prev_block.ret_types.len() - prev_block.params_types.len() { - let block_kind = if prev_block.looped { "loop" } else { "block" }; - return Err(format!( - "stack size is {stack_size}, initial size is {}, return types is {:?} at {block_kind} end", - prev_block.initial_stack_size, prev_block.ret_types - )); + } else if stack_size != prev_block.expected_finish_size() { + return Err(format!("size mismatch for block end: {} {:?}", stack_size, prev_block)); } ops.push(CalxInstr::Nop) @@ -696,7 +700,49 @@ impl CalxVM { ops.push(CalxInstr::Return); } CalxSyntax::If { ret_types, else_at, to } => { - todo!() + if stack_size < 1 { + return Err(format!("insufficient stack {} to branch", stack_size)); + } + stack_size -= 1; + + blocks_track.push(BlockData::If { + ret_types: ret_types.to_owned(), + else_to: else_at.to_owned(), + to: to.to_owned(), + initial_stack_size: stack_size, + }); + + ops.push(CalxInstr::JmpIf(else_at.to_owned())); + } + CalxSyntax::ElseEnd => { + if blocks_track.is_empty() { + return Err(format!("invalid else end, {:?}", blocks_track)); + } + + let prev_block = blocks_track.peek_if()?; + if stack_size != prev_block.expected_finish_size() { + return Err(format!("size mismatch for else-end: {} {:?}", stack_size, prev_block)); + } + + match prev_block { + BlockData::If { to, .. } => ops.push(CalxInstr::Jmp(to.to_owned())), + _ => unreachable!("end inside if"), + } + } + CalxSyntax::ThenEnd => { + if blocks_track.is_empty() { + return Err(format!("invalid else end, {:?}", blocks_track)); + } + + let prev_block = blocks_track.pop_if()?; + if stack_size != prev_block.expected_finish_size() { + return Err(format!("size mismatch for then-end: {} {:?}", stack_size, prev_block)); + } + + match prev_block { + BlockData::If { to, .. } => ops.push(CalxInstr::Jmp(to.to_owned())), + _ => unreachable!("end inside if"), + } } a => { let instr: CalxInstr = a.try_into()?; diff --git a/src/vm/block_data.rs b/src/vm/block_data.rs index 40f1ef9..55e46a4 100644 --- a/src/vm/block_data.rs +++ b/src/vm/block_data.rs @@ -3,18 +3,143 @@ use std::rc::Rc; use crate::calx::CalxType; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] -pub struct BlockData { - pub looped: bool, - pub params_types: Rc>, - pub ret_types: Rc>, - pub from: usize, - pub to: usize, - pub initial_stack_size: usize, +pub enum BlockData { + Block { + params_types: Rc>, + ret_types: Rc>, + to: usize, + initial_stack_size: usize, + }, + Loop { + params_types: Rc>, + ret_types: Rc>, + from: usize, + to: usize, + initial_stack_size: usize, + }, + If { + ret_types: Rc>, + else_to: usize, + to: usize, + initial_stack_size: usize, + }, } impl BlockData { // size of stack after block finished or breaked pub fn expected_finish_size(&self) -> usize { - self.initial_stack_size - self.params_types.len() + self.ret_types.len() + match self { + BlockData::Block { + initial_stack_size, + params_types, + ret_types, + .. + } => *initial_stack_size - params_types.len() + ret_types.len(), + BlockData::Loop { + initial_stack_size, + params_types, + ret_types, + .. + } => *initial_stack_size - params_types.len() + ret_types.len(), + BlockData::If { + initial_stack_size, + ret_types, + .. + } => *initial_stack_size - 1 + ret_types.len(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] +pub struct BlockStack { + pub stack: Vec, +} + +impl BlockStack { + pub fn new() -> Self { + BlockStack { stack: vec![] } + } + + pub fn push(&mut self, block: BlockData) { + self.stack.push(block); + } + + pub fn is_empty(&self) -> bool { + self.stack.is_empty() + } + + pub fn len(&self) -> usize { + self.stack.len() + } + + /// expected a `If` result, return error otherwise + pub fn pop_if(&mut self) -> Result { + match self.stack.pop() { + Some(a @ BlockData::If { .. }) => Ok(a), + None => Err("BlockStack::pop_if: stack is empty".to_string()), + block => Err(format!("BlockStack::pop_if: expected If, got {:?}", block)), + } + } + + pub fn peek_if(&self) -> Result<&BlockData, String> { + match self.stack.last() { + Some(a @ BlockData::If { .. }) => Ok(a), + None => Err("BlockStack::peek_if: stack is empty".to_string()), + block => Err(format!("BlockStack::peek_if: expected If, got {:?}", block)), + } + } + + /// pops `block` or `loop`, if `if` block occurs, just remove, return error is empty + pub fn pop_block(&mut self) -> Result { + loop { + let b = self.stack.pop(); + match b { + Some(v @ BlockData::Block { .. }) => return Ok(v), + Some(v @ BlockData::Loop { .. }) => return Ok(v), + Some(BlockData::If { .. }) => continue, + None => return Err("BlockStack::pop_block: stack is empty".to_string()), + } + } + } + + pub fn peek_block(&self) -> Result<&BlockData, String> { + loop { + let b = self.stack.last(); + match b { + Some(v @ BlockData::Block { .. }) => return Ok(v), + Some(v @ BlockData::Loop { .. }) => return Ok(v), + Some(BlockData::If { .. }) => continue, + None => return Err("BlockStack::peek_block: stack is empty".to_string()), + } + } + } + + pub fn peek_block_level(&self, level: usize) -> Result<&BlockData, String> { + if level == 0 { + return self.peek_block(); + } + + let mut count = 0; + loop { + let b = self.stack.get(self.stack.len() - 1 - count); + match b { + Some(v @ BlockData::Block { .. }) => { + if count == level { + return Ok(v); + } else { + count += 1; + } + } + Some(v @ BlockData::Loop { .. }) => { + if count == level { + return Ok(v); + } else { + count += 1; + } + } + Some(BlockData::If { .. }) => continue, + None => return Err("BlockStack::peek_block: stack is empty".to_string()), + } + } } } diff --git a/src/vm/instr.rs b/src/vm/instr.rs index 66d633c..6539eec 100644 --- a/src/vm/instr.rs +++ b/src/vm/instr.rs @@ -146,7 +146,8 @@ impl TryFrom<&CalxSyntax> for CalxInstr { // debug CalxSyntax::Inspect => Ok(Self::Inspect), CalxSyntax::If { .. } => Err("If should be handled manually".to_string()), - CalxSyntax::EndIf => Ok(Self::EndIf), + CalxSyntax::ThenEnd => Err("ThenEnd should be handled manually".to_string()), + CalxSyntax::ElseEnd => Err("ElseEnd should be handled manually".to_string()), CalxSyntax::Do(_) => Err("do should be handled manually".to_string()), } } From 65f69b64c543587a6a6d53bbdc3bcf48d22a16a9 Mon Sep 17 00:00:00 2001 From: tiye Date: Mon, 22 Jan 2024 01:33:57 +0800 Subject: [PATCH 10/11] release "if" demo as 0.2.0-a1 --- .github/workflows/publish.yaml | 3 +++ .github/workflows/test.yaml | 4 +++- Cargo.toml | 2 +- README.md | 2 ++ demos/if.cirru | 10 +++++++++- src/vm.rs | 3 ++- tests/parser_tests.rs | 32 -------------------------------- 7 files changed, 20 insertions(+), 36 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index bd8a2a3..ecf5ff9 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -16,6 +16,8 @@ jobs: toolchain: nightly components: clippy + - run: cargo test + - run: cargo run -- -s demos/hello.cirru - run: cargo run -- -s demos/sum.cirru - run: cargo run -- -s demos/assert.cirru @@ -23,6 +25,7 @@ jobs: - run: cargo run -- -s demos/named.cirru - run: cargo run -- -s demos/recur.cirru - run: cargo run -- -s demos/fibonacci.cirru + - run: cargo run -- -s demos/if.cirru - run: cargo run -- --emit-binary target/a.calx demos/named.cirru && cargo run -- --eval-binary target/a.calx # - run: cargo test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6dcf334..920f44e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,6 +24,8 @@ jobs: toolchain: nightly components: clippy + - run: cargo test + - run: cargo run -- -s demos/hello.cirru - run: cargo run -- -s demos/sum.cirru - run: cargo run -- -s demos/assert.cirru @@ -31,10 +33,10 @@ jobs: - run: cargo run -- -s demos/named.cirru - run: cargo run -- -s demos/recur.cirru - run: cargo run -- -s demos/fibonacci.cirru + - run: cargo run -- -s demos/if.cirru - run: cargo run -- --emit-binary target/a.calx demos/named.cirru && cargo run -- --eval-binary target/a.calx - uses: giraffate/clippy-action@v1 with: reporter: 'github-pr-review' github_token: ${{ secrets.GITHUB_TOKEN }} - diff --git a/Cargo.toml b/Cargo.toml index 4b86398..5f57547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "calx_vm" -version = "0.1.6" +version = "0.2.0-a1" authors = ["jiyinyiyong "] edition = "2021" license = "MIT" diff --git a/README.md b/README.md index 5697e83..6b402bc 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ fn main () ### Instructions +> _TODO_ update to `0.2.x`... + Highly inspired by: - WASM https://github.com/WebAssembly/design/blob/main/Semantics.md diff --git a/demos/if.cirru b/demos/if.cirru index 498920f..7707fe2 100644 --- a/demos/if.cirru +++ b/demos/if.cirru @@ -1,7 +1,13 @@ - fn main () const 1 + call demo + const 0 + call demo + +fn demo (($a i64) ->) + local.get $a + if (->) do const 11 @@ -9,3 +15,5 @@ fn main () do const 20 echo + const 3 + echo diff --git a/src/vm.rs b/src/vm.rs index 630fd0f..daea4b0 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -703,7 +703,6 @@ impl CalxVM { if stack_size < 1 { return Err(format!("insufficient stack {} to branch", stack_size)); } - stack_size -= 1; blocks_track.push(BlockData::If { ret_types: ret_types.to_owned(), @@ -712,6 +711,7 @@ impl CalxVM { initial_stack_size: stack_size, }); + stack_size -= 1; ops.push(CalxInstr::JmpIf(else_at.to_owned())); } CalxSyntax::ElseEnd => { @@ -720,6 +720,7 @@ impl CalxVM { } let prev_block = blocks_track.peek_if()?; + if stack_size != prev_block.expected_finish_size() { return Err(format!("size mismatch for else-end: {} {:?}", stack_size, prev_block)); } diff --git a/tests/parser_tests.rs b/tests/parser_tests.rs index b80ffec..8556510 100644 --- a/tests/parser_tests.rs +++ b/tests/parser_tests.rs @@ -36,37 +36,5 @@ fn test_extracting() -> Result<(), String> { )) ); - assert_eq!( - Cirru::List(extract_nested(&Cirru::List(vec![ - Cirru::leaf("block"), - Cirru::List(vec![ - Cirru::leaf("c"), - Cirru::leaf("d"), - Cirru::List(vec![Cirru::leaf("e"), Cirru::leaf("f"),]) - ]) - ]))?), - Cirru::List(vec!(Cirru::List(vec![ - Cirru::leaf("block"), - Cirru::List(vec![Cirru::leaf("e"), Cirru::leaf("f")]), - Cirru::List(vec![Cirru::leaf("c"), Cirru::leaf("d")]) - ]),)) - ); - - assert_eq!( - Cirru::List(extract_nested(&Cirru::List(vec![ - Cirru::leaf("loop"), - Cirru::List(vec![ - Cirru::leaf("c"), - Cirru::leaf("d"), - Cirru::List(vec![Cirru::leaf("e"), Cirru::leaf("f"),]) - ]) - ]))?), - Cirru::List(vec!(Cirru::List(vec![ - Cirru::leaf("loop"), - Cirru::List(vec![Cirru::leaf("e"), Cirru::leaf("f")]), - Cirru::List(vec![Cirru::leaf("c"), Cirru::leaf("d")]) - ]),)) - ); - Ok(()) } From 9f7769c2bacbf18c1dd067c9ba28f6a0583e2b25 Mon Sep 17 00:00:00 2001 From: tiye Date: Mon, 22 Jan 2024 01:46:58 +0800 Subject: [PATCH 11/11] add a fibonacci demo using "if" --- .github/workflows/publish.yaml | 1 + .github/workflows/test.yaml | 1 + demos/fibo-if.cirru | 25 +++++++++++++++++++++++++ src/vm.rs | 1 + 4 files changed, 28 insertions(+) create mode 100644 demos/fibo-if.cirru diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index ecf5ff9..97a11d6 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -26,6 +26,7 @@ jobs: - run: cargo run -- -s demos/recur.cirru - run: cargo run -- -s demos/fibonacci.cirru - run: cargo run -- -s demos/if.cirru + - run: cargo run -- -s demos/fibo-if.cirru - run: cargo run -- --emit-binary target/a.calx demos/named.cirru && cargo run -- --eval-binary target/a.calx # - run: cargo test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 920f44e..cde036a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,6 +34,7 @@ jobs: - run: cargo run -- -s demos/recur.cirru - run: cargo run -- -s demos/fibonacci.cirru - run: cargo run -- -s demos/if.cirru + - run: cargo run -- -s demos/fibo-if.cirru - run: cargo run -- --emit-binary target/a.calx demos/named.cirru && cargo run -- --eval-binary target/a.calx - uses: giraffate/clippy-action@v1 diff --git a/demos/fibo-if.cirru b/demos/fibo-if.cirru new file mode 100644 index 0000000..d977482 --- /dev/null +++ b/demos/fibo-if.cirru @@ -0,0 +1,25 @@ + +fn main () + call fibo + const 34 + echo + +fn fibo (($x i64) -> i64) + local.get $x + const 3 + i.lt + if (->) + do + const 1 + return + do + local.get $x + const -1 + i.add + call fibo + local.get $x + const -2 + i.add + call fibo + i.add + return \ No newline at end of file diff --git a/src/vm.rs b/src/vm.rs index daea4b0..6d06a2e 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -129,6 +129,7 @@ impl CalxVM { } /// run one step, return true if continuing + #[inline(always)] pub fn step(&mut self) -> Result { if self.top_frame.pointer >= self.top_frame.instrs.len() { // println!("status {:?} {}", self.stack, self.top_frame);