diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index ca98626..4f61872 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -18,6 +18,8 @@ jobs: - run: cargo run -- -S examples/sum.cirru - run: cargo run -- -S examples/assert.cirru - run: cargo run -- -S examples/nested.cirru + - run: cargo run -- -S examples/named.cirru + - run: cargo run -- --emit-binary target/a.calx examples/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 239b0b6..22c729d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly components: clippy override: true @@ -28,6 +28,13 @@ jobs: - run: cargo run -- -S examples/sum.cirru - run: cargo run -- -S examples/assert.cirru - run: cargo run -- -S examples/nested.cirru + - run: cargo run -- -S examples/named.cirru + - run: cargo run -- --emit-binary target/a.calx examples/named.cirru && cargo run -- --eval-binary target/a.calx + + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features - uses: actions-rs/clippy-check@v1 with: diff --git a/Cargo.toml b/Cargo.toml index e2cb880..8bf22b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "calx_vm" -version = "0.1.3" +version = "0.1.4" authors = ["jiyinyiyong "] edition = "2018" license = "MIT" diff --git a/README.md b/README.md index 1ffd9db..e4882cb 100644 --- a/README.md +++ b/README.md @@ -80,57 +80,60 @@ Highly inspired by: For binary op, top value puts on right. -| Code | Usage | Note | -| -------------------- | -------------------------------------------------------- | ----------------- | -| `local.set $idx` | set value at `$idx` | | -| `local.tee $idx` | set value at `$idx`, and also load it | | -| `local.get $idx` | get value at `$idx` load on stack | | -| `local.new` | increase size of array of locals | | -| `global.set $idx` | set global value at `$idx` | | -| `global.get $idx` | get global value at `$idx` | | -| `local.new` | increase size of array of globals | | -| `const $v` | push value `$v` on stack | | -| `dup` | duplicate top value on stack | | -| `drop` | drop top value from stack | | -| `i.add` | add two i64 numbers on stack into one | | -| `i.mul` | multiply two i64 numbers on stack into one | | -| `i.div` | divide two i64 numbers on stack into one | | -| `i.rem` | calculate reminder two i64 numbers on stack into one | | -| `i.neg` | negate i64 numbers on top of stack | | -| `i.shr $bits` | call SHR `$bits` bits on i64 numbers on top of stack | | -| `i.shl $bits` | call SHL `$bits` bits on i64 numbers on top of stack | | -| `i.eq` | detects if two i64 numbers on stack equal | | -| `i.ne` | detects if two i64 numbers on stack not equal | | -| `i.lt` | litter than, compares two i64 numbers on stack | | -| `i.gt` | greater than, compares two i64 numbers on stack | | -| `i.le` | litter/equal than, compares two i64 numbers on stack | | -| `i.ge` | greater/equal than, compares two i64 numbers on stack | | -| `add` | add two f64 numbers on stack into one | | -| `mul` | multiply two f64 numbers on stack into one | | -| `div` | divide two f64 numbers on stack into one | | -| `neg` | negate f64 numbers on top of stack | | -| `list.new` | | TODO | -| `list.get` | | TODO | -| `list.set` | | TODO | -| `link.new` | | TODO | -| `and` | | TODO | -| `or` | | TODO | -| `br $n` | branch `$n` level of block, 0 means end of current block | | -| `br-if $n` | like `br $n` but detects top value on stack first | Internal | -| (JMP `$l`) | jump to line `$l` | Internal | +Calx Binary Edition `0.1`: + +| Code | Usage | Note | +| -------------------- | -------------------------------------------------------- | -------------------------------------- | +| `local.set $idx` | pop from stack, set value at `$idx` | | +| `local.tee $idx` | set value at `$idx`, and also load it | | +| `local.get $idx` | get value at `$idx` load on stack | | +| `local.new $name` | increase size of array of locals | name is optional, defaults to `$` | +| `global.set $idx` | set global value at `$idx` | | +| `global.get $idx` | get global value at `$idx` | | +| `global.new` | increase size of array of globals | | +| `const $v` | push value `$v` on stack | | +| `dup` | duplicate top value on stack | | +| `drop` | drop top value from stack | | +| `i.add` | add two i64 numbers on stack into one | | +| `i.mul` | multiply two i64 numbers on stack into one | | +| `i.div` | divide two i64 numbers on stack into one | | +| `i.rem` | calculate reminder two i64 numbers on stack into one | | +| `i.neg` | negate i64 numbers on top of stack | | +| `i.shr $bits` | call SHR `$bits` bits on i64 numbers on top of stack | | +| `i.shl $bits` | call SHL `$bits` bits on i64 numbers on top of stack | | +| `i.eq` | detects if two i64 numbers on stack equal | | +| `i.ne` | detects if two i64 numbers on stack not equal | | +| `i.lt` | litter than, compares two i64 numbers on stack | | +| `i.gt` | greater than, compares two i64 numbers on stack | | +| `i.le` | litter/equal than, compares two i64 numbers on stack | | +| `i.ge` | greater/equal than, compares two i64 numbers on stack | | +| `add` | add two f64 numbers on stack into one | | +| `mul` | multiply two f64 numbers on stack into one | | +| `div` | divide two f64 numbers on stack into one | | +| `neg` | negate f64 numbers on top of stack | | +| `list.new` | | TODO | +| `list.get` | | TODO | +| `list.set` | | TODO | +| `link.new` | | TODO | +| `and` | | TODO | +| `or` | | TODO | +| `not` | | TODO | +| `br $n` | branch `$n` level of block, 0 means end of current block | | +| `br-if $n` | like `br $n` but detects top value on stack first | Internal | +| (JMP `$l`) | jump to line `$l` | Internal | | (JMP_IF `$l`) | conditional jump to `$l` | -| `block $types $body` | declare a block | | -| `loop $types $body` | declare a loop block | | -| (BlockEnd) | internal mark for ending a block | Internal | -| `echo` | pop value from stack and print | | -| `call $f` | call function `$f` | | -| `call-import $f` | call imported function `$f` | | -| `unreachable` | throw unreachable panic | | -| `nop` | No op | | -| `quit $code` | quit program and return exit code `$code` | | -| `return` | | TODO | -| `fn $types $body` | | Global definition | -| `assert` | `quit(1)` if not `true` | for testing | +| `block $types $body` | declare a block | | +| `loop $types $body` | declare a loop block | | +| (BlockEnd) | internal mark for ending a block | Internal | +| `echo` | pop value from stack and print | | +| `call $f` | call function `$f` | | +| `call-import $f` | call imported function `$f` | | +| `unreachable` | throw unreachable panic | | +| `nop` | No op | | +| `quit $code` | quit program and return exit code `$code` | | +| `return` | | TODO | +| `fn $types $body` | | Global definition | +| `assert` | `quit(1)` if not `true` | for testing | For `$types`, it can be `($t1 $t2 -> $t3 $t4)`, where supported types are: diff --git a/examples/named.cirru b/examples/named.cirru new file mode 100644 index 0000000..2787a24 --- /dev/null +++ b/examples/named.cirru @@ -0,0 +1,18 @@ + +fn f-add (($a i64) ($b i64) -> i64) + local.new $c + const 100 + local.set $c + i.add + i.add + local.get $a + local.get $b + local.get $c + dup + echo + +fn main () + const 1 + const 2 + call f-add + drop diff --git a/examples/sum.cirru b/examples/sum.cirru index 048d6e3..36f8629 100644 --- a/examples/sum.cirru +++ b/examples/sum.cirru @@ -14,7 +14,7 @@ fn blocks (-> i64) , (const "|demo of string") (echo) const 0 -fn sum () +fn sum (-> i64) local.new block (-> i64) const 0 @@ -48,6 +48,7 @@ fn sum () const "|check sum" echo local.get 0 + dup echo fn echos (-> i64) @@ -64,5 +65,6 @@ fn echos (-> i64) return -fn main () +fn main (-> i64) call sum + return diff --git a/src/bin/calx.rs b/src/bin/calx.rs index c7a78bb..d73244d 100644 --- a/src/bin/calx.rs +++ b/src/bin/calx.rs @@ -5,7 +5,7 @@ use std::time::Instant; use cirru_parser::{parse, Cirru}; use clap::{Arg, Command}; -use calx_vm::{parse_function, Calx, CalxError, CalxFunc, CalxImportsDict, CalxVM}; +use calx_vm::{log_calx_value, parse_function, Calx, CalxBinaryProgram, CalxFunc, CalxImportsDict, CalxVM, CALX_BINARY_EDITION}; fn main() -> Result<(), String> { let matches = Command::new("Calx VM") @@ -55,9 +55,18 @@ fn main() -> Result<(), String> { if eval_binary { let code = fs::read(source).expect("read binar from source file"); - fns = bincode::decode_from_slice(&code, bincode::config::standard()) + let program: CalxBinaryProgram = bincode::decode_from_slice(&code, bincode::config::standard()) .expect("decode functions from binary") .0; + if program.edition == CALX_BINARY_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 + )); + } } else { let contents = fs::read_to_string(source).expect("Cirru file for instructions"); let xs = parse(&contents).expect("Some Cirru content"); @@ -74,7 +83,11 @@ fn main() -> Result<(), String> { if emit_binary { let mut slice = [0u8; 10000]; - let length = match bincode::encode_into_slice(&fns, &mut slice, bincode::config::standard()) { + let program = CalxBinaryProgram { + edition: CALX_BINARY_EDITION.to_string(), + fns, + }; + let length = match bincode::encode_into_slice(&program, &mut slice, bincode::config::standard()) { Ok(l) => { println!("encoded binary length: {}", l); l @@ -117,11 +130,11 @@ fn main() -> Result<(), String> { } } - match vm.run() { - Ok(()) => { + match vm.run(vec![Calx::I64(1)]) { + Ok(ret) => { let elapsed = now.elapsed(); - println!("Took {:.3?}: {:?}", elapsed, vm.stack); + println!("Took {:.3?}: {:?}", elapsed, ret); Ok(()) } Err(e) => { @@ -131,8 +144,3 @@ fn main() -> Result<(), String> { } } } - -fn log_calx_value(xs: Vec) -> Result { - println!("log: {:?}", xs); - Ok(Calx::Nil) -} diff --git a/src/lib.rs b/src/lib.rs index 497e2c9..573d31f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,9 @@ mod parser; mod primes; +mod util; mod vm; pub use parser::{extract_nested, parse_function}; -pub use primes::{Calx, CalxError, CalxFrame, CalxFunc}; +pub use primes::{Calx, CalxBinaryProgram, CalxError, CalxFrame, CalxFunc, CALX_BINARY_EDITION}; +pub use util::log_calx_value; pub use vm::{CalxImportsDict, CalxVM}; diff --git a/src/parser.rs b/src/parser.rs index 23ae0ec..0bf26f6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,9 @@ -/*! Parser Cirru into Calx instructions +/*! Parse Cirru into Calx instructions * */ +mod locals; + use lazy_static::lazy_static; use regex::Regex; @@ -9,6 +11,8 @@ use cirru_parser::Cirru; use crate::primes::{Calx, CalxFunc, CalxInstr, CalxType}; +use self::locals::LocalsCollector; + /// parses /// ```cirru /// fn (i64 f64) @@ -36,15 +40,17 @@ pub fn parse_function(nodes: &[Cirru]) -> Result { return Err(String::from("invalid name")); }; - let (params_types, ret_types) = parse_types(&nodes[2])?; - 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)?; + let mut ptr_base: usize = 0; for (idx, line) in nodes.iter().enumerate() { if idx >= 3 { for expanded in extract_nested(line)? { // println!("expanded {}", expanded); - let instrs = parse_instr(ptr_base, &expanded)?; + let instrs = parse_instr(ptr_base, &expanded, &mut locals_collector)?; for instr in instrs { ptr_base += 1; @@ -58,11 +64,12 @@ pub fn parse_function(nodes: &[Cirru]) -> Result { name: name.to_string(), params_types, ret_types, + local_names: locals_collector.locals, instrs: body, }) } -pub fn parse_instr(ptr_base: usize, node: &Cirru) -> 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) => { @@ -78,36 +85,21 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru) -> Result, Stri if xs.len() != 2 { return Err(format!("local.get expected a position, {:?}", xs)); } - let idx: usize = match &xs[1] { - Cirru::Leaf(s) => parse_usize(s)?, - Cirru::List(_) => { - return Err(format!("expected token, got {}", xs[1])); - } - }; + let idx: usize = parse_local_idx(&xs[1], collector)?; Ok(vec![CalxInstr::LocalGet(idx)]) } "local.set" => { if xs.len() != 2 { return Err(format!("local.set expected a position, {:?}", xs)); } - let idx: usize = match &xs[1] { - Cirru::Leaf(s) => parse_usize(s)?, - Cirru::List(_) => { - return Err(format!("expected token, got {}", xs[1])); - } - }; + let idx: usize = parse_local_idx(&xs[1], collector)?; Ok(vec![CalxInstr::LocalSet(idx)]) } "local.tee" => { if xs.len() != 2 { return Err(format!("list.tee expected a position, {:?}", xs)); } - let idx: usize = match &xs[1] { - Cirru::Leaf(s) => parse_usize(s)?, - Cirru::List(_) => { - return Err(format!("expected token, got {}", xs[1])); - } - }; + let idx: usize = parse_local_idx(&xs[1], collector)?; Ok(vec![CalxInstr::LocalSet(idx)]) } "local.new" => Ok(vec![CalxInstr::LocalNew]), @@ -198,8 +190,8 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru) -> Result, Stri }; Ok(vec![CalxInstr::Br(idx)]) } - "block" => parse_block(ptr_base, xs, false), - "loop" => parse_block(ptr_base, xs, true), + "block" => parse_block(ptr_base, xs, false, collector), + "loop" => parse_block(ptr_base, xs, true, collector), "echo" => Ok(vec![CalxInstr::Echo]), "call" => { if xs.len() != 2 { @@ -267,6 +259,22 @@ lazy_static! { 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() { + Some(c) => { + if c == '$' { + Ok(collector.track(s)) + } else { + parse_usize(s) + } + } + None => Err(String::from("invalid empty name")), + }, + Cirru::List(_) => Err(format!("expected token, got {}", x)), + } +} + pub fn parse_value(s: &str) -> Result { match s { "nil" => Ok(Calx::Nil), @@ -301,13 +309,13 @@ pub fn parse_usize(s: &str) -> Result { } } -pub fn parse_block(ptr_base: usize, xs: &[Cirru], looped: bool) -> 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 (params_types, ret_types) = parse_types(&xs[1])?; + 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)?; + let instrs = parse_instr(p, line, collector)?; for y in instrs { p += 1; chunk.push(y); @@ -333,7 +341,8 @@ pub fn parse_block(ptr_base: usize, xs: &[Cirru], looped: bool) -> Result Result<(Vec, Vec), String> { +/// 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 { Cirru::Leaf(_) => Err(format!("expect expression for types, got {}", xs)), Cirru::List(ys) => { @@ -342,54 +351,69 @@ pub fn parse_types(xs: &Cirru) -> Result<(Vec, Vec), String> let mut ret_mode = false; for x in ys { - if let Cirru::Leaf(t) = x { - match &**t { - "->" => { + match x { + Cirru::Leaf(t) => { + if &**t == "->" { ret_mode = true; - } - "nil" => { - if ret_mode { - returns.push(CalxType::Nil); - } else { - params.push(CalxType::Nil); - } - } - "bool" => { - if ret_mode { - returns.push(CalxType::Bool); - } else { - params.push(CalxType::Bool); - } - } - "i64" => { + } else { + let ty = parse_type_name(t)?; if ret_mode { - returns.push(CalxType::I64); + returns.push(ty); } else { - params.push(CalxType::I64); + // track names in collector, if NOT named, use a string of index + let name = format!("${}", params.len()); + collector.track(&name); + params.push(ty); } } - "f64" => { - if ret_mode { - returns.push(CalxType::F64); - } else { - params.push(CalxType::F64); - } + } + + Cirru::List(zs) => { + if ret_mode { + return Err(format!("invalid syntax, return values should not have names, got {:?}", x)); } - "list" => { - if ret_mode { - returns.push(CalxType::List); - } else { - params.push(CalxType::List); - } + if zs.len() != 2 { + return Err(format!("invalid syntax, expected name and type, got {:?}", x)); } - "link" => { - if ret_mode { - returns.push(CalxType::Link); - } else { - params.push(CalxType::Link); - } + let name_str = match &zs[0] { + Cirru::Leaf(s) => s.to_owned(), + Cirru::List(_) => return Err(format!("invalid syntax, expected name, got {:?}", x)), + }; + let ty = match &zs[1] { + Cirru::Leaf(s) => parse_type_name(s)?, + Cirru::List(_) => return Err(format!("invalid syntax, expected type, got {:?}", x)), + }; + collector.track(&name_str); + params.push(ty); + } + } + } + + Ok((params, returns)) + } + } +} + +/// does not need names in block +pub fn parse_block_types(xs: &Cirru) -> Result<(Vec, Vec), String> { + match xs { + Cirru::Leaf(_) => Err(format!("expect expression for types, got {}", xs)), + Cirru::List(ys) => { + let mut params: Vec = vec![]; + let mut returns: Vec = vec![]; + let mut ret_mode = false; + + for x in ys { + if let Cirru::Leaf(t) = x { + if &**t == "->" { + ret_mode = true; + } else { + let ty = parse_type_name(t)?; + if ret_mode { + returns.push(ty); + } else { + params.push(ty); } - a => return Err(format!("Unknown type: {}", a)), } } } @@ -399,6 +423,18 @@ pub fn parse_types(xs: &Cirru) -> Result<(Vec, Vec), String> } } +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/parser/locals.rs b/src/parser/locals.rs new file mode 100644 index 0000000..a140b63 --- /dev/null +++ b/src/parser/locals.rs @@ -0,0 +1,19 @@ +/// a struct for gathering names of locals and use index +pub struct LocalsCollector { + pub locals: Vec, +} + +impl LocalsCollector { + pub fn new() -> Self { + LocalsCollector { locals: vec![] } + } + pub fn track(&mut self, name: &str) -> usize { + match self.locals.iter().position(|n| n == name) { + Some(i) => i, + None => { + self.locals.push(name.to_string()); + self.locals.len() - 1 + } + } + } +} diff --git a/src/primes.rs b/src/primes.rs index 3189885..2d48b9c 100644 --- a/src/primes.rs +++ b/src/primes.rs @@ -1,9 +1,6 @@ /*! - * Calx is a simplied VM from Calcit, but not lower level enough. - * Data in Calx is mutable, and has basic types and structures, such as Lists. - * Calx uses a mixed form of prefix and postix instructions. - * - * (I'm not equiped enough for building a bytecode VM yet...) + * 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}; @@ -22,12 +19,28 @@ pub enum Calx { // Link(Box, Box, Box), } -#[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] +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, } @@ -64,6 +77,7 @@ pub struct CalxFunc { pub params_types: Vec, pub ret_types: Vec, pub instrs: Vec, + pub local_names: Vec, } impl fmt::Display for CalxFunc { @@ -77,6 +91,13 @@ impl fmt::Display for CalxFunc { 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)?; } @@ -133,6 +154,7 @@ pub enum CalxInstr { // bool operations And, Or, + Not, // control stuctures Br(usize), BrIf(usize), @@ -187,7 +209,7 @@ impl CalxError { } } -#[derive(Debug, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] pub struct BlockData { pub looped: bool, pub ret_types: Vec, @@ -234,3 +256,16 @@ impl fmt::Display for CalxFrame { 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 new file mode 100644 index 0000000..b5c3fcb --- /dev/null +++ b/src/util.rs @@ -0,0 +1,6 @@ +use crate::primes::{Calx, CalxError}; + +pub fn log_calx_value(xs: Vec) -> Result { + println!("log: {:?}", xs); + Ok(Calx::Nil) +} diff --git a/src/vm.rs b/src/vm.rs index 96c48ef..d2d0afa 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,4 +1,4 @@ -use crate::primes::{BlockData, Calx, CalxError, CalxFrame, CalxFunc, CalxInstr}; +use crate::primes::{BlockData, Calx, CalxError, CalxFrame, CalxFunc, CalxInstr, CalxType}; use std::collections::hash_map::HashMap; use std::fmt; use std::ops::Rem; @@ -42,7 +42,10 @@ impl CalxVM { } } - pub fn run(&mut self) -> Result<(), CalxError> { + pub fn run(&mut self, args: Vec) -> Result { + // assign function parameters + self.top_frame.locals = args; + self.stack.clear(); loop { // println!("Stack {:?}", self.stack); // println!("-- op {} {:?}", self.stack.len(), instr); @@ -51,7 +54,7 @@ impl CalxVM { // println!("status {:?} {}", self.stack, self.top_frame); self.check_func_return()?; if self.frames.is_empty() { - return Ok(()); + return Ok(self.stack.pop().unwrap_or(Calx::Nil)); } else { // let prev_frame = self.top_frame; self.top_frame = self.frames.pop().unwrap(); @@ -115,7 +118,7 @@ impl CalxVM { to, initial_stack_size: self.stack.len() - params_types.len(), }); - println!("TODO check params type: {:?}", params_types); + self.check_stack_for_block(¶ms_types)?; } CalxInstr::BlockEnd(looped) => { if looped { @@ -160,7 +163,10 @@ impl CalxVM { CalxInstr::Return => { self.check_func_return()?; if self.frames.is_empty() { - return Ok(()); + return match self.stack.pop() { + Some(x) => Ok(x), + None => Err(self.gen_err("return without value".to_owned())), + }; } else { // let prev_frame = self.top_frame; self.top_frame = self.frames.pop().unwrap(); @@ -364,6 +370,9 @@ impl CalxVM { CalxInstr::Or => { // TODO } + CalxInstr::Not => { + // TODO + } CalxInstr::Call(f_name) => { match find_func(&self.funcs, &f_name) { Some(f) => { @@ -382,9 +391,6 @@ impl CalxVM { ret_types: f.ret_types, }; - // TODO check params type - println!("TODO check args: {:?}", f.params_types); - // start in new frame continue; } @@ -570,6 +576,20 @@ impl CalxVM { Ok(()) } + /// checks is given parameters on stack top + fn check_stack_for_block(&self, params: &[CalxType]) -> Result<(), CalxError> { + if self.stack.len() < params.len() { + return Err(self.gen_err(format!("stack size does not match given params: {:?} {:?}", self.stack, params))); + } + for (idx, t) in params.iter().enumerate() { + if self.stack[self.stack.len() - params.len() - 1 + idx].typed_as(t.to_owned()) { + continue; + } + return Err(self.gen_err(format!("stack type does not match given params: {:?} {:?}", self.stack, params))); + } + Ok(()) + } + #[inline(always)] fn check_func_return(&mut self) -> Result<(), CalxError> { if self.stack.len() != self.top_frame.initial_stack_size + self.top_frame.ret_types.len() { @@ -681,6 +701,7 @@ pub fn instr_stack_arity(op: &CalxInstr) -> (usize, usize) { // bool operations CalxInstr::And => (2, 1), CalxInstr::Or => (2, 1), + CalxInstr::Not => (1, 1), // control stuctures CalxInstr::Br(_) => (0, 0), CalxInstr::BrIf(_) => (1, 0),