diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index b812f2c..97a11d6 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -16,12 +16,17 @@ 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 - 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 -- -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 79802d8..cde036a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,16 +24,20 @@ 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 - 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 -- -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 with: reporter: 'github-pr-review' github_token: ${{ secrets.GITHUB_TOKEN }} - diff --git a/Cargo.toml b/Cargo.toml index ad855e8..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" @@ -11,13 +11,17 @@ 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] -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/README.md b/README.md index 57bcd2f..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 @@ -135,7 +137,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/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/demos/if.cirru b/demos/if.cirru new file mode 100644 index 0000000..7707fe2 --- /dev/null +++ b/demos/if.cirru @@ -0,0 +1,19 @@ + +fn main () + const 1 + call demo + const 0 + call demo + +fn demo (($a i64) ->) + local.get $a + + if (->) + do + const 11 + echo + do + const 20 + echo + const 3 + echo diff --git a/src/bin/calx.rs b/src/bin/cli.rs similarity index 81% rename from src/bin/calx.rs rename to src/bin/cli.rs index f571e67..c1533ee 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)] @@ -16,8 +27,6 @@ use calx_vm::{log_calx_value, parse_function, Calx, CalxBinaryProgram, CalxFunc, 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")] @@ -33,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; @@ -44,13 +52,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 +77,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())?; @@ -93,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/calx.rs b/src/calx.rs new file mode 100644 index 0000000..eeab805 --- /dev/null +++ b/src/calx.rs @@ -0,0 +1,112 @@ +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)] +pub enum Calx { + Nil, + Bool(bool), + I64(i64), + F64(f64), + Str(String), + List(Vec), + // to simultate linked structures + // 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 { + 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, + } + } +} + +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"), + } + } +} + +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/lib.rs b/src/lib.rs index 573d31f..482f5a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,10 @@ +mod calx; mod parser; -mod primes; +mod syntax; 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..f773b21 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,12 +6,11 @@ mod locals; use std::rc::Rc; -use lazy_static::lazy_static; -use regex::Regex; - use cirru_parser::Cirru; -use crate::primes::{Calx, CalxFunc, CalxInstr, CalxType}; +use crate::calx::CalxType; +use crate::syntax::CalxSyntax; +use crate::vm::func::CalxFunc; use self::locals::LocalsCollector; @@ -23,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() { @@ -42,7 +35,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)?; @@ -52,9 +45,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); } @@ -67,44 +60,45 @@ 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) => { 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)); } 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)); @@ -115,7 +109,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 { @@ -127,47 +121,47 @@ 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)); } match &xs[1] { Cirru::Leaf(s) => { - let p1 = parse_value(s)?; - Ok(vec![CalxInstr::Const(p1)]) + let p1 = s.parse()?; + 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)); @@ -178,7 +172,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 { @@ -190,11 +184,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)); @@ -204,7 +198,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 { @@ -215,7 +209,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 { @@ -226,10 +220,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![]) @@ -244,9 +238,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 { @@ -257,22 +251,17 @@ 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]), - _ => Err(format!("unknown instruction: {}", name)), + "inspect" => Ok(vec![CalxSyntax::Inspect]), + "if" => parse_if(ptr_base, xs, collector), + _ => Err(format!("unknown instruction: {} in {:?}", name, xs)), }, } } } } -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() { @@ -289,33 +278,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), @@ -323,20 +285,23 @@ 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 { - 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); + } } } } - 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); @@ -344,7 +309,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, @@ -355,6 +320,74 @@ 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![]; + + // 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::ElseEnd); + for instr in then_syntax { + p += 1; + chunk.push(instr); + } + + p += 1; + chunk.push(CalxSyntax::ThenEnd); + + 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 { @@ -370,7 +403,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 { @@ -394,7 +427,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); @@ -422,7 +455,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 { @@ -437,18 +470,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> { @@ -458,17 +479,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())]; @@ -491,3 +502,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/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/syntax.rs b/src/syntax.rs new file mode 100644 index 0000000..68d551d --- /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), + Block { + params_types: Rc>, + ret_types: Rc>, + /// bool to indicate loop + looped: bool, + from: usize, + 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 + 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>, + else_at: usize, + to: usize, + }, + ThenEnd, + ElseEnd, +} 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 fbc544e..6d06a2e 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,9 +1,22 @@ -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 crate::syntax::CalxSyntax; +use crate::vm::block_data::BlockStack; + +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)] @@ -14,6 +27,9 @@ pub struct CalxVM { pub frames: Vec, pub top_frame: CalxFrame, pub imports: CalxImportsDict, + /// extra status to tracking runnnig finished + pub finished: bool, + pub return_value: Calx, } impl std::fmt::Debug for CalxVM { @@ -24,12 +40,12 @@ 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, - blocks_track: vec![], - instrs: main_func.instrs.clone(), + // use empty instrs, will be replaced by preprocess + instrs: Rc::new(vec![]), pointer: 0, locals: vec![], ret_types: main_func.ret_types.clone(), @@ -41,9 +57,56 @@ impl CalxVM { frames: vec![], top_frame: main_frame, imports, + return_value: Calx::Nil, + finished: false, } } + pub fn setup_top_frame(&mut self) -> Result<(), String> { + 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()), + }, + 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; + } + + 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}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; @@ -52,482 +115,450 @@ 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 = line.to_owned(); - continue; // 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; - } + self.top_frame.pointer += 1; + } + } - 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)?; + /// 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); + 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(); - 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; - } + use instr::CalxInstr::*; - continue; // point reset, goto next loop - } + match &instrs[self.top_frame.pointer] { + Jmp(line) => { + 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) { + self.top_frame.pointer = line.to_owned(); + 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 - ))); - } + } + 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 } - 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 - } + } + 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); + } + 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); + } + 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![]; + } + 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 - } + } + 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))); + } 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))); - } + } + 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))), - } + } + 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()); + } + Drop => { + let _ = self.stack_pop()?; + } + 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))), - } + } + 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))), - } + } + 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))), - } + } + 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]))); - } + } + 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))), - } + } + 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))), - } + } + 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; + } + 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))), - } + 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))), - } + } + 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))), - } + } + 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; + } + 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; + } + 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; + } + 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; + } + 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; + } + 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 + } + 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; + } + NewList => { + todo!() + } + ListGet => { + todo!() + } + ListSet => { + todo!() + } + NewLink => { + todo!() + } + And => { + todo!() + } + Or => { + todo!() + } + Not => { + todo!() + } + Call(f_name) => { + // println!("frame size: {}", self.frames.len()); + match self.find_func(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, + initial_stack_size: self.stack.len(), + locals, + pointer: 0, + instrs: match instrs { + Some(x) => x.to_owned(), + None => unreachable!("function must have 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; + } + ReturnCall(f_name) => { + // println!("frame size: {}", self.frames.len()); + match self.find_func(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, + initial_stack_size: self.stack.len(), + locals, + pointer: 0, + instrs: match instrs { + Some(x) => x.to_owned(), + None => panic!("function must have instrs"), + }, + ret_types, + }; + + // start in new frame + return Ok(true); } - }, - CalxInstr::Unreachable => { - unreachable!("Unexpected from op") + None => return Err(self.gen_err(format!("cannot find function named: {}", f_name))), } - 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))); + } + 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!("[ ----------------"); - 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!(" -------------- ]"); + }, + Unreachable => { + unreachable!("Unexpected from op") + } + Nop => { + // Noop + } + Quit(code) => std::process::exit(*code as i32), + Echo => { + let v = self.stack_pop()?; + println!("{}", v); + } + 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; + 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) } pub fn preprocess(&mut self, verbose: bool) -> Result<(), String> { 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]; @@ -540,13 +571,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, @@ -556,35 +587,42 @@ 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); } - CalxInstr::Br(size) => { + CalxSyntax::Br(size) => { if *size > blocks_track.len() { 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"), } } - CalxInstr::BrIf(size) => { + CalxSyntax::BrIf(size) => { if blocks_track.is_empty() { return Err(format!("cannot branch with no blocks, {}", size)); } @@ -592,12 +630,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; @@ -606,26 +644,22 @@ 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)); } - 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) } - CalxInstr::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)); @@ -635,7 +669,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 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)); @@ -645,7 +679,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)); @@ -655,7 +689,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 { @@ -666,9 +700,56 @@ impl CalxVM { } ops.push(CalxInstr::Return); } + CalxSyntax::If { ret_types, else_at, to } => { + if stack_size < 1 { + return Err(format!("insufficient stack {} to branch", stack_size)); + } + + 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, + }); + + stack_size -= 1; + 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()?; // 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)); } @@ -677,7 +758,7 @@ impl CalxVM { // " sizes: {:?} {} {} -> {}", // a, params_size, ret_size, stack_size // ); - ops.push(a.to_owned()); + ops.push(instr.to_owned()); } } } @@ -688,8 +769,9 @@ impl CalxVM { )); } - self.funcs[i].instrs = Rc::new(ops); + self.funcs[i].instrs = Some(Rc::new(ops)); } + Ok(()) } @@ -738,37 +820,41 @@ 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(), } } + + fn find_func(&self, name: &str) -> Option<&CalxFunc> { + self.funcs.iter().find(|x| *x.name == name) + } } -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 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(), + globals: vec![], + } + } } diff --git a/src/vm/block_data.rs b/src/vm/block_data.rs new file mode 100644 index 0000000..55e46a4 --- /dev/null +++ b/src/vm/block_data.rs @@ -0,0 +1,145 @@ +use std::rc::Rc; + +use crate::calx::CalxType; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] +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 { + 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/frame.rs b/src/vm/frame.rs new file mode 100644 index 0000000..2c474ca --- /dev/null +++ b/src/vm/frame.rs @@ -0,0 +1,46 @@ +use core::fmt; +use std::rc::Rc; + +use crate::calx::{Calx, CalxType}; + +use super::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 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, + 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..57618cd --- /dev/null +++ b/src/vm/func.rs @@ -0,0 +1,50 @@ +use bincode::{Decode, Encode}; +use core::fmt; +use std::rc::Rc; + +use crate::{calx::CalxType, syntax::CalxSyntax}; + +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 syntax: Rc>, + pub instrs: Option>>, + 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(" .")?; + } + 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 new file mode 100644 index 0000000..6539eec --- /dev/null +++ b/src/vm/instr.rs @@ -0,0 +1,222 @@ +use std::rc::Rc; + +use bincode::{Decode, Encode}; + +use crate::{ + calx::{Calx, CalxType}, + syntax::CalxSyntax, +}; + +/// 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 + Jmp(usize), // internal + JmpOffset(i32), // internal + JmpIf(usize), // internal + JmpOffsetIf(i32), // internal + /// 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 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(_) => 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) => 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())), + 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::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()), + } + } +} + +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::Jmp(_) => (0, 0), + CalxInstr::JmpOffset(_) => (0, 0), + CalxInstr::JmpIf(_) => (1, 0), + CalxInstr::JmpOffsetIf(_) => (1, 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"; diff --git a/tests/parser_tests.rs b/tests/parser_tests.rs index d0dc3c1..8556510 100644 --- a/tests/parser_tests.rs +++ b/tests/parser_tests.rs @@ -25,52 +25,16 @@ 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()]) )) ); - 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(()) }