diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 183fe21..f953f24 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,5 +1,4 @@ name: Rust - on: push: branches: ["master"] @@ -9,14 +8,50 @@ env: CARGO_TERM_COLOR: always jobs: - build: + test: + name: Test runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Install the Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Rust Cache Action + uses: Swatinem/rust-cache@v2 + - name: Run tests + run: cargo test + + fmt: + name: Rustfmt + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Build - run: cargo build --all --verbose --release - - name: Run tests - run: cargo test --all --verbose --release - - name: Lint + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Enforce formatting + run: cargo fmt --check + + lint: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Linting run: ./run_linter.sh + + coverage: + name: Code coverage + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - name: Generate code coverage + run: cargo install cargo-tarpaulin && cargo tarpaulin --verbose --workspace diff --git a/Cargo.lock b/Cargo.lock index efc8e9d..e84ad51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] @@ -34,24 +34,23 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" [[package]] name = "anstyle-parse" @@ -73,9 +72,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys", @@ -89,9 +88,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bumpalo" @@ -113,9 +112,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -125,7 +127,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chimpanzee" -version = "0.1.0" +version = "0.2.1" dependencies = [ "byteorder", "chrono", @@ -140,9 +142,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "f56b4c72906975ca04becb8a30e102dfecddd0c06181e3e95ddc444be28881f8" dependencies = [ "android-tzdata", "iana-time-zone", @@ -150,7 +152,7 @@ dependencies = [ "num-traits", "time", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] @@ -182,18 +184,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.11" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" +checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.3.11" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" +checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" dependencies = [ "anstream", "anstyle", @@ -203,9 +205,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" dependencies = [ "heck", "proc-macro2", @@ -215,9 +217,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "colorchoice" @@ -318,9 +320,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", @@ -421,21 +423,21 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f478948fd84d9f8e86967bf432640e46adfb5a4bd4f14ef7e864ab38220534ae" [[package]] name = "memoffset" @@ -518,18 +520,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -558,9 +560,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" dependencies = [ "aho-corasick", "memchr", @@ -570,9 +572,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", @@ -581,15 +583,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rustix" -version = "0.38.3" +version = "0.38.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" dependencies = [ "bitflags", "errno", @@ -627,18 +629,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.169" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd51c3db8f9500d531e6c12dd0fd4ad13d133e9117f5aebac3cdbb8b6d9824b0" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.169" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27738cfea0d944ab72c3ed01f3d5f23ec4322af8a1431e40ce630e4c01ea74fd" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", @@ -647,9 +649,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -683,9 +685,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.24" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ccaf716a23c35ff908f91c971a86a9a71af5998c1d8f10e828d9f55f68ac00" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -715,9 +717,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "utf8parse" @@ -856,9 +858,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -871,42 +873,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml index e42ff14..d5563f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chimpanzee" -version = "0.1.0" +version = "0.2.1" edition = "2021" authors = ["Yago Iglesias "] license = "MIT OR Apache-2.0" diff --git a/benches/formatter_bench.rs b/benches/formatter_bench.rs index 5292aee..ac7c79a 100644 --- a/benches/formatter_bench.rs +++ b/benches/formatter_bench.rs @@ -1,7 +1,6 @@ use chimpanzee::formatter::Formatter; use criterion::{black_box, criterion_group, criterion_main, Criterion}; - pub fn long_formatter_benchmark(c: &mut Criterion) { let input = r#" let fibonacci_it = fn (x) { diff --git a/run_linter.sh b/run_linter.sh index a424720..0835c31 100755 --- a/run_linter.sh +++ b/run_linter.sh @@ -5,16 +5,10 @@ ALLOWED_LINTS=("must_use_candidate" "missing-errors-doc" "cast_possible_truncati format_lints(){ for lint in "${ALLOWED_LINTS[@]}"; do - printf '%s' "-A" + printf '%s' "-A" printf " %s " "clippy::$lint" done } -if [ -z "$1" ]; then - cargo clippy --all -- -W "clippy::pedantic" $(format_lints) -else - module=$1 - shift - cargo clippy -p "$module" "$@" -- -W "clippy::pedantic" $(format_lints) -fi +cargo clippy "$@" -- -W "clippy::pedantic" -D warnings $(format_lints) diff --git a/src/bin/monkey.rs b/src/bin/monkey.rs index 042422f..1590868 100644 --- a/src/bin/monkey.rs +++ b/src/bin/monkey.rs @@ -1,5 +1,5 @@ -use clap::Parser; use chimpanzee::repl::ReplCli; +use clap::Parser; use std::error::Error; fn main() -> Result<(), Box> { diff --git a/src/bin/monkeyfmt.rs b/src/bin/monkeyfmt.rs index d0c1f53..1b65029 100644 --- a/src/bin/monkeyfmt.rs +++ b/src/bin/monkeyfmt.rs @@ -1,5 +1,5 @@ -use clap::Parser; use chimpanzee::formatter::cli::FormatterCli; +use clap::Parser; fn main() -> Result<(), Box> { let args = FormatterCli::parse(); diff --git a/src/compiler/compiler_tests.rs b/src/compiler/compiler_tests.rs index 902776c..129c262 100644 --- a/src/compiler/compiler_tests.rs +++ b/src/compiler/compiler_tests.rs @@ -2,71 +2,14 @@ #[cfg(test)] pub mod tests { - use std::rc::Rc; - use crate::{ compiler::{ - code::{Instructions, Opcode}, - test_utils::check_instructions, - Compiler, - }, - object::{ - test_utils::check_constants, - {CompiledFunction, Object}, + code::Opcode, + test_utils::{flatten_instructions, run_compiler, CompilerTestCase}, }, - parser::parse, + object::Object, }; - struct CompilerTestCase { - input: String, - expected_constants: Vec, - expected_instructions: Instructions, - } - - fn flatten_instructions(instructions: Vec) -> Instructions { - let mut res = Instructions::default(); - for instruction in instructions { - res.append(instruction); - } - res - } - - fn flatten_u8_instructions(instructions: Vec) -> Vec { - let mut res = vec![]; - for instruction in instructions { - res.append(&mut instruction.data.clone()); - } - res - } - - fn run_compiler(tests: Vec) { - for test in tests { - println!("Testing input: {}", test.input); - let program = parse(&test.input); - - let mut compiler = Compiler::new(); - - match compiler.compile(program) { - Ok(_) => { - let bytecode = compiler.bytecode(); - println!( - "want {}, got {}", - test.expected_instructions, bytecode.instructions - ); - check_instructions(&bytecode.instructions, &test.expected_instructions); - check_constants( - &bytecode.constants, - &test - .expected_constants - .iter() - .map(|x| Rc::new(x.clone())) - .collect(), - ); - } - Err(err) => panic!("compiler error: {err}"), - } - } - } #[test] fn test_integer_arithemtic() { let tests = vec![ @@ -571,602 +514,6 @@ pub mod tests { run_compiler(tests); } - #[test] - fn test_funtions() { - let tests = vec![ - CompilerTestCase { - input: "fn() { return 5 + 10; }".to_string(), - expected_constants: vec![ - Object::INTEGER(5), - Object::INTEGER(10), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![0]), - Opcode::Constant.make(vec![1]), - Opcode::Add.make(vec![]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 0, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![2, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: "fn() { 5 + 10; }".to_string(), - expected_constants: vec![ - Object::INTEGER(5), - Object::INTEGER(10), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![0]), - Opcode::Constant.make(vec![1]), - Opcode::Add.make(vec![]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 0, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![2, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: "fn() { 1; 2 }".to_string(), - expected_constants: vec![ - Object::INTEGER(1), - Object::INTEGER(2), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![0]), - Opcode::Pop.make(vec![]), - Opcode::Constant.make(vec![1]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 0, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![2, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: "fn() { 24 }()".to_string(), - expected_constants: vec![ - Object::INTEGER(24), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![0]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 0, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![1, 0]), - Opcode::Call.make(vec![0]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: "let noArg = fn() { 24 }; noArg();".to_string(), - expected_constants: vec![ - Object::INTEGER(24), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![0]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 0, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![1, 0]), - Opcode::SetGlobal.make(vec![0]), - Opcode::GetGlobal.make(vec![0]), - Opcode::Call.make(vec![0]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: "let oneArg = fn(a) {}; oneArg(24);".to_string(), - expected_constants: vec![ - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![Opcode::Return.make(vec![0])]), - num_locals: 1, - num_parameters: 1, - }), - Object::INTEGER(24), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![0, 0]), - Opcode::SetGlobal.make(vec![0]), - Opcode::GetGlobal.make(vec![0]), - Opcode::Constant.make(vec![1]), - Opcode::Call.make(vec![1]), - Opcode::Pop.make(vec![0]), - ]), - }, - CompilerTestCase { - input: "let manyArg = fn(a, b, c) { }; manyArg(24, 25, 26);".to_string(), - expected_constants: vec![ - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![Opcode::Return.make(vec![0])]), - num_locals: 3, - num_parameters: 3, - }), - Object::INTEGER(24), - Object::INTEGER(25), - Object::INTEGER(26), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![0, 0]), - Opcode::SetGlobal.make(vec![0]), - Opcode::GetGlobal.make(vec![0]), - Opcode::Constant.make(vec![1]), - Opcode::Constant.make(vec![2]), - Opcode::Constant.make(vec![3]), - Opcode::Call.make(vec![3]), - Opcode::Pop.make(vec![0]), - ]), - }, - CompilerTestCase { - input: "let oneArg = fn(a) { a; }; oneArg(24);".to_string(), - expected_constants: vec![ - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::GetLocal.make(vec![0]), - Opcode::ReturnValue.make(vec![0]), - ]), - num_locals: 1, - num_parameters: 1, - }), - Object::INTEGER(24), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![0, 0]), - Opcode::SetGlobal.make(vec![0]), - Opcode::GetGlobal.make(vec![0]), - Opcode::Constant.make(vec![1]), - Opcode::Call.make(vec![1]), - Opcode::Pop.make(vec![0]), - ]), - }, - CompilerTestCase { - input: "let manyArg = fn(a, b, c) { a; b; c; }; manyArg(24, 25, 26);".to_string(), - expected_constants: vec![ - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::GetLocal.make(vec![0]), - Opcode::Pop.make(vec![0]), - Opcode::GetLocal.make(vec![1]), - Opcode::Pop.make(vec![0]), - Opcode::GetLocal.make(vec![2]), - Opcode::ReturnValue.make(vec![0]), - ]), - num_locals: 3, - num_parameters: 3, - }), - Object::INTEGER(24), - Object::INTEGER(25), - Object::INTEGER(26), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![0, 0]), - Opcode::SetGlobal.make(vec![0]), - Opcode::GetGlobal.make(vec![0]), - Opcode::Constant.make(vec![1]), - Opcode::Constant.make(vec![2]), - Opcode::Constant.make(vec![3]), - Opcode::Call.make(vec![3]), - Opcode::Pop.make(vec![0]), - ]), - }, - ]; - - run_compiler(tests); - } - - #[test] - fn test_function_with_no_return_value() { - let tests = vec![CompilerTestCase { - input: "fn() { }".to_string(), - expected_constants: vec![Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![Opcode::Return.make(vec![])]), - num_locals: 0, - num_parameters: 0, - })], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![0, 0]), - Opcode::Pop.make(vec![]), - ]), - }]; - - run_compiler(tests); - } - - #[test] - fn test_let_statements_scope() { - let tests = vec![ - CompilerTestCase { - input: r#" - let num = 55; - fn() { num }"# - .to_string(), - expected_constants: vec![ - Object::INTEGER(55), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::GetGlobal.make(vec![0]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 0, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Constant.make(vec![0]), - Opcode::SetGlobal.make(vec![0]), - Opcode::Closure.make(vec![1, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: r#" - fn() { - let num = 55; - num - }"# - .to_string(), - expected_constants: vec![ - Object::INTEGER(55), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![0]), - Opcode::SetLocal.make(vec![0]), - Opcode::GetLocal.make(vec![0]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![1, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: r#" - fn() { - let a = 55; - let b = 77; - a + b - }"# - .to_string(), - expected_constants: vec![ - Object::INTEGER(55), - Object::INTEGER(77), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![0]), - Opcode::SetLocal.make(vec![0]), - Opcode::Constant.make(vec![1]), - Opcode::SetLocal.make(vec![1]), - Opcode::GetLocal.make(vec![0]), - Opcode::GetLocal.make(vec![1]), - Opcode::Add.make(vec![]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 2, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![2, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - ]; - - run_compiler(tests); - } - - #[test] - fn test_builtins() { - let tests = vec![ - CompilerTestCase { - input: "len([]); push([], 1);".to_string(), - expected_constants: vec![Object::INTEGER(1)], - expected_instructions: flatten_instructions(vec![ - Opcode::GetBuiltin.make(vec![0]), - Opcode::Array.make(vec![0]), - Opcode::Call.make(vec![1]), - Opcode::Pop.make(vec![]), - Opcode::GetBuiltin.make(vec![4]), - Opcode::Array.make(vec![0]), - Opcode::Constant.make(vec![0]), - Opcode::Call.make(vec![2]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: "fn() { len([]); }".to_string(), - expected_constants: vec![Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::GetBuiltin.make(vec![0]), - Opcode::Array.make(vec![0]), - Opcode::Call.make(vec![1]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 0, - num_parameters: 0, - })], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![0, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - ]; - - run_compiler(tests); - } - - #[test] - - fn test_closures() { - let tests = vec![ - CompilerTestCase { - input: r#" - fn(a){ - fn(b){ - a + b - } - }"# - .to_string(), - expected_constants: vec![ - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::GetFree.make(vec![0]), - Opcode::GetLocal.make(vec![0]), - Opcode::Add.make(vec![]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 1, - }), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::GetLocal.make(vec![0]), - Opcode::Closure.make(vec![0, 1]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 1, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![1, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: r#" - fn(a) { - fn(b) { - fn(c) { - a + b + c - } - } - };"# - .to_string(), - - expected_constants: vec![ - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::GetFree.make(vec![0]), - Opcode::GetFree.make(vec![1]), - Opcode::Add.make(vec![]), - Opcode::GetLocal.make(vec![0]), - Opcode::Add.make(vec![]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 1, - }), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::GetFree.make(vec![0]), - Opcode::GetLocal.make(vec![0]), - Opcode::Closure.make(vec![0, 2]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 1, - }), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::GetLocal.make(vec![0]), - Opcode::Closure.make(vec![1, 1]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 1, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![2, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: r#" - let global = 55; - fn() { - let a = 66; - fn() { - let b = 77; - fn() { - let c = 88; - global + a + b + c; - } - } - } - "# - .to_string(), - expected_constants: vec![ - Object::INTEGER(55), - Object::INTEGER(66), - Object::INTEGER(77), - Object::INTEGER(88), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![3]), - Opcode::SetLocal.make(vec![0]), - Opcode::GetGlobal.make(vec![0]), - Opcode::GetFree.make(vec![0]), - Opcode::Add.make(vec![]), - Opcode::GetFree.make(vec![1]), - Opcode::Add.make(vec![]), - Opcode::GetLocal.make(vec![0]), - Opcode::Add.make(vec![]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 0, - }), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![2]), - Opcode::SetLocal.make(vec![0]), - Opcode::GetFree.make(vec![0]), - Opcode::GetLocal.make(vec![0]), - Opcode::Closure.make(vec![4, 2]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 0, - }), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Constant.make(vec![1]), - Opcode::SetLocal.make(vec![0]), - Opcode::GetLocal.make(vec![0]), - Opcode::Closure.make(vec![5, 1]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Constant.make(vec![0]), - Opcode::SetGlobal.make(vec![0]), - Opcode::Closure.make(vec![6, 0]), - Opcode::Pop.make(vec![]), - ]), - }, - ]; - - run_compiler(tests); - } - - #[test] - fn test_recursive_functions() { - let tests = vec![ - CompilerTestCase { - input: r#" - let countDown = fn(x){ - countDown(x - 1); - }; - countDown(1);"# - .to_string(), - expected_constants: vec![ - Object::INTEGER(1), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::CurrentClosure.make(vec![]), - Opcode::GetLocal.make(vec![0]), - Opcode::Constant.make(vec![0]), - Opcode::Sub.make(vec![]), - Opcode::Call.make(vec![1]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 1, - }), - Object::INTEGER(1), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![1, 0]), - Opcode::SetGlobal.make(vec![0]), - Opcode::GetGlobal.make(vec![0]), - Opcode::Constant.make(vec![2]), - Opcode::Call.make(vec![1]), - Opcode::Pop.make(vec![]), - ]), - }, - CompilerTestCase { - input: r#" - let wrapper = fn() { - let countDown = fn(x) { - countDown(x - 1); - }; - countDown(1); - }; - wrapper(); - "# - .to_string(), - expected_constants: vec![ - Object::INTEGER(1), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::CurrentClosure.make(vec![]), - Opcode::GetLocal.make(vec![0]), - Opcode::Constant.make(vec![0]), - Opcode::Sub.make(vec![]), - Opcode::Call.make(vec![1]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 1, - }), - Object::INTEGER(1), - Object::COMPILEDFUNCTION(CompiledFunction { - instructions: flatten_u8_instructions(vec![ - Opcode::Closure.make(vec![1, 0]), - Opcode::SetLocal.make(vec![0]), - Opcode::GetLocal.make(vec![0]), - Opcode::Constant.make(vec![2]), - Opcode::Call.make(vec![1]), - Opcode::ReturnValue.make(vec![]), - ]), - num_locals: 1, - num_parameters: 0, - }), - ], - expected_instructions: flatten_instructions(vec![ - Opcode::Closure.make(vec![3, 0]), - Opcode::SetGlobal.make(vec![0]), - Opcode::GetGlobal.make(vec![0]), - Opcode::Call.make(vec![0]), - Opcode::Pop.make(vec![]), - ]), - }, - ]; - - run_compiler(tests); - } - #[test] fn test_shadowing_with_itself() { let tests = vec![CompilerTestCase { @@ -1188,159 +535,4 @@ pub mod tests { run_compiler(tests); } - - #[test] - fn test_while_statements() { - let tests = vec![CompilerTestCase { - input: r#" - while (true){ - puts("yes"); - } - "# - .to_string(), - expected_constants: vec![Object::STRING("yes".to_string())], - expected_instructions: flatten_instructions(vec![ - Opcode::True.make(vec![]), // 000 - Opcode::JumpNotTruthy.make(vec![15]), // 001 - Opcode::GetBuiltin.make(vec![5]), // 004 - Opcode::Constant.make(vec![0]), // 006 - Opcode::Call.make(vec![1]), // 009 - Opcode::Pop.make(vec![]), // 011 - Opcode::Jump.make(vec![0]), // 012 - // 015 - ]), - }]; - - run_compiler(tests); - } - - #[test] - fn test_break_in_while() { - let tests = vec![CompilerTestCase { - input: r#" - while (true){ - break; - } - "# - .to_string(), - expected_constants: vec![], - expected_instructions: flatten_instructions(vec![ - Opcode::True.make(vec![]), // 000 - Opcode::JumpNotTruthy.make(vec![10]), // 001 - Opcode::Jump.make(vec![10]), // 004 - Opcode::Jump.make(vec![0]), // 007 - // 010 - ]), - }]; - - run_compiler(tests); - } - - #[test] - fn test_nested_breaks_in_while() { - let tests = vec![CompilerTestCase { - input: r#" - while (true){ - while (true){ - break; - } - break; - } - "# - .to_string(), - expected_constants: vec![], - expected_instructions: flatten_instructions(vec![ - Opcode::True.make(vec![]), // 000 - Opcode::JumpNotTruthy.make(vec![20]), // 001 - Opcode::True.make(vec![]), // 004 - Opcode::JumpNotTruthy.make(vec![14]), // 005 - Opcode::Jump.make(vec![14]), // 008 - Opcode::Jump.make(vec![4]), // 011 - Opcode::Jump.make(vec![20]), // 014 - Opcode::Jump.make(vec![0]), // 017 - // 020 - ]), - }]; - - run_compiler(tests); - } - #[test] - fn test_continue_in_while() { - let tests = vec![CompilerTestCase { - input: r#" - while (true){ - continue; - } - "# - .to_string(), - expected_constants: vec![], - expected_instructions: flatten_instructions(vec![ - Opcode::True.make(vec![]), // 000 - Opcode::JumpNotTruthy.make(vec![10]), // 001 - Opcode::Jump.make(vec![0]), // 004 - Opcode::Jump.make(vec![0]), // 007 - // 010 - ]), - }]; - - run_compiler(tests); - } - - #[test] - fn test_nested_continue_in_while() { - let tests = vec![CompilerTestCase { - input: r#" - while (true){ - while (true){ - continue; - } - continue; - } - "# - .to_string(), - expected_constants: vec![], - expected_instructions: flatten_instructions(vec![ - Opcode::True.make(vec![]), // 000 - Opcode::JumpNotTruthy.make(vec![20]), // 001 - Opcode::True.make(vec![]), // 004 - Opcode::JumpNotTruthy.make(vec![14]), // 005 - Opcode::Jump.make(vec![4]), // 008 - Opcode::Jump.make(vec![4]), // 011 - Opcode::Jump.make(vec![0]), // 014 - Opcode::Jump.make(vec![0]), // 017 - // 020 - ]), - }]; - - run_compiler(tests); - } - - #[test] - fn test_continue_and_break_in_while() { - let tests = vec![CompilerTestCase { - input: r#" - while (true){ - while (true){ - continue; - } - break; - } - "# - .to_string(), - expected_constants: vec![], - expected_instructions: flatten_instructions(vec![ - Opcode::True.make(vec![]), // 000 - Opcode::JumpNotTruthy.make(vec![20]), // 001 - Opcode::True.make(vec![]), // 004 - Opcode::JumpNotTruthy.make(vec![14]), // 005 - Opcode::Jump.make(vec![4]), // 008 - Opcode::Jump.make(vec![4]), // 011 - Opcode::Jump.make(vec![20]), // 014 - Opcode::Jump.make(vec![0]), // 017 - // 020 - ]), - }]; - - run_compiler(tests); - } } diff --git a/src/compiler/function_tests.rs b/src/compiler/function_tests.rs new file mode 100644 index 0000000..fbdf807 --- /dev/null +++ b/src/compiler/function_tests.rs @@ -0,0 +1,609 @@ +#[allow(clippy::too_many_lines)] +#[cfg(test)] +pub mod tests { + + use crate::{ + compiler::{ + code::Opcode, + test_utils::{ + flatten_instructions, flatten_u8_instructions, run_compiler, CompilerTestCase, + }, + }, + object::{CompiledFunction, Object}, + }; + + #[test] + fn test_funtions() { + let tests = vec![ + CompilerTestCase { + input: "fn() { return 5 + 10; }".to_string(), + expected_constants: vec![ + Object::INTEGER(5), + Object::INTEGER(10), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::Constant.make(vec![1]), + Opcode::Add.make(vec![]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 0, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![2, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: "fn() { 5 + 10; }".to_string(), + expected_constants: vec![ + Object::INTEGER(5), + Object::INTEGER(10), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::Constant.make(vec![1]), + Opcode::Add.make(vec![]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 0, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![2, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: "fn() { 1; 2 }".to_string(), + expected_constants: vec![ + Object::INTEGER(1), + Object::INTEGER(2), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::Pop.make(vec![]), + Opcode::Constant.make(vec![1]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 0, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![2, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: "fn() { 24 }()".to_string(), + expected_constants: vec![ + Object::INTEGER(24), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 0, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![1, 0]), + Opcode::Call.make(vec![0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: "let noArg = fn() { 24 }; noArg();".to_string(), + expected_constants: vec![ + Object::INTEGER(24), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 0, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![1, 0]), + Opcode::SetGlobal.make(vec![0]), + Opcode::GetGlobal.make(vec![0]), + Opcode::Call.make(vec![0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: "let oneArg = fn(a) {}; oneArg(24);".to_string(), + expected_constants: vec![ + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![Opcode::Return.make(vec![0])]), + num_locals: 1, + num_parameters: 1, + }), + Object::INTEGER(24), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![0, 0]), + Opcode::SetGlobal.make(vec![0]), + Opcode::GetGlobal.make(vec![0]), + Opcode::Constant.make(vec![1]), + Opcode::Call.make(vec![1]), + Opcode::Pop.make(vec![0]), + ]), + }, + CompilerTestCase { + input: "let manyArg = fn(a, b, c) { }; manyArg(24, 25, 26);".to_string(), + expected_constants: vec![ + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![Opcode::Return.make(vec![0])]), + num_locals: 3, + num_parameters: 3, + }), + Object::INTEGER(24), + Object::INTEGER(25), + Object::INTEGER(26), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![0, 0]), + Opcode::SetGlobal.make(vec![0]), + Opcode::GetGlobal.make(vec![0]), + Opcode::Constant.make(vec![1]), + Opcode::Constant.make(vec![2]), + Opcode::Constant.make(vec![3]), + Opcode::Call.make(vec![3]), + Opcode::Pop.make(vec![0]), + ]), + }, + CompilerTestCase { + input: "let oneArg = fn(a) { a; }; oneArg(24);".to_string(), + expected_constants: vec![ + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::GetLocal.make(vec![0]), + Opcode::ReturnValue.make(vec![0]), + ]), + num_locals: 1, + num_parameters: 1, + }), + Object::INTEGER(24), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![0, 0]), + Opcode::SetGlobal.make(vec![0]), + Opcode::GetGlobal.make(vec![0]), + Opcode::Constant.make(vec![1]), + Opcode::Call.make(vec![1]), + Opcode::Pop.make(vec![0]), + ]), + }, + CompilerTestCase { + input: "let manyArg = fn(a, b, c) { a; b; c; }; manyArg(24, 25, 26);".to_string(), + expected_constants: vec![ + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::GetLocal.make(vec![0]), + Opcode::Pop.make(vec![0]), + Opcode::GetLocal.make(vec![1]), + Opcode::Pop.make(vec![0]), + Opcode::GetLocal.make(vec![2]), + Opcode::ReturnValue.make(vec![0]), + ]), + num_locals: 3, + num_parameters: 3, + }), + Object::INTEGER(24), + Object::INTEGER(25), + Object::INTEGER(26), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![0, 0]), + Opcode::SetGlobal.make(vec![0]), + Opcode::GetGlobal.make(vec![0]), + Opcode::Constant.make(vec![1]), + Opcode::Constant.make(vec![2]), + Opcode::Constant.make(vec![3]), + Opcode::Call.make(vec![3]), + Opcode::Pop.make(vec![0]), + ]), + }, + ]; + + run_compiler(tests); + } + + #[test] + fn test_function_with_no_return_value() { + let tests = vec![CompilerTestCase { + input: "fn() { }".to_string(), + expected_constants: vec![Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![Opcode::Return.make(vec![])]), + num_locals: 0, + num_parameters: 0, + })], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![0, 0]), + Opcode::Pop.make(vec![]), + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_let_statements_scope() { + let tests = vec![ + CompilerTestCase { + input: r#" + let num = 55; + fn() { num }"# + .to_string(), + expected_constants: vec![ + Object::INTEGER(55), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::GetGlobal.make(vec![0]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 0, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::SetGlobal.make(vec![0]), + Opcode::Closure.make(vec![1, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: r#" + fn() { + let num = 55; + num + }"# + .to_string(), + expected_constants: vec![ + Object::INTEGER(55), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::SetLocal.make(vec![0]), + Opcode::GetLocal.make(vec![0]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![1, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: r#" + fn() { + let a = 55; + let b = 77; + a + b + }"# + .to_string(), + expected_constants: vec![ + Object::INTEGER(55), + Object::INTEGER(77), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::SetLocal.make(vec![0]), + Opcode::Constant.make(vec![1]), + Opcode::SetLocal.make(vec![1]), + Opcode::GetLocal.make(vec![0]), + Opcode::GetLocal.make(vec![1]), + Opcode::Add.make(vec![]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 2, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![2, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + ]; + + run_compiler(tests); + } + #[test] + fn test_builtins() { + let tests = vec![ + CompilerTestCase { + input: "len([]); push([], 1);".to_string(), + expected_constants: vec![Object::INTEGER(1)], + expected_instructions: flatten_instructions(vec![ + Opcode::GetBuiltin.make(vec![0]), + Opcode::Array.make(vec![0]), + Opcode::Call.make(vec![1]), + Opcode::Pop.make(vec![]), + Opcode::GetBuiltin.make(vec![4]), + Opcode::Array.make(vec![0]), + Opcode::Constant.make(vec![0]), + Opcode::Call.make(vec![2]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: "fn() { len([]); }".to_string(), + expected_constants: vec![Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::GetBuiltin.make(vec![0]), + Opcode::Array.make(vec![0]), + Opcode::Call.make(vec![1]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 0, + num_parameters: 0, + })], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![0, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + ]; + + run_compiler(tests); + } + + #[test] + + fn test_closures() { + let tests = vec![ + CompilerTestCase { + input: r#" + fn(a){ + fn(b){ + a + b + } + }"# + .to_string(), + expected_constants: vec![ + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::GetFree.make(vec![0]), + Opcode::GetLocal.make(vec![0]), + Opcode::Add.make(vec![]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 1, + }), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::GetLocal.make(vec![0]), + Opcode::Closure.make(vec![0, 1]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 1, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![1, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: r#" + fn(a) { + fn(b) { + fn(c) { + a + b + c + } + } + };"# + .to_string(), + + expected_constants: vec![ + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::GetFree.make(vec![0]), + Opcode::GetFree.make(vec![1]), + Opcode::Add.make(vec![]), + Opcode::GetLocal.make(vec![0]), + Opcode::Add.make(vec![]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 1, + }), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::GetFree.make(vec![0]), + Opcode::GetLocal.make(vec![0]), + Opcode::Closure.make(vec![0, 2]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 1, + }), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::GetLocal.make(vec![0]), + Opcode::Closure.make(vec![1, 1]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 1, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![2, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: r#" + let global = 55; + fn() { + let a = 66; + fn() { + let b = 77; + fn() { + let c = 88; + global + a + b + c; + } + } + } + "# + .to_string(), + expected_constants: vec![ + Object::INTEGER(55), + Object::INTEGER(66), + Object::INTEGER(77), + Object::INTEGER(88), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![3]), + Opcode::SetLocal.make(vec![0]), + Opcode::GetGlobal.make(vec![0]), + Opcode::GetFree.make(vec![0]), + Opcode::Add.make(vec![]), + Opcode::GetFree.make(vec![1]), + Opcode::Add.make(vec![]), + Opcode::GetLocal.make(vec![0]), + Opcode::Add.make(vec![]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 0, + }), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![2]), + Opcode::SetLocal.make(vec![0]), + Opcode::GetFree.make(vec![0]), + Opcode::GetLocal.make(vec![0]), + Opcode::Closure.make(vec![4, 2]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 0, + }), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Constant.make(vec![1]), + Opcode::SetLocal.make(vec![0]), + Opcode::GetLocal.make(vec![0]), + Opcode::Closure.make(vec![5, 1]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::SetGlobal.make(vec![0]), + Opcode::Closure.make(vec![6, 0]), + Opcode::Pop.make(vec![]), + ]), + }, + ]; + + run_compiler(tests); + } + + #[test] + fn test_recursive_functions() { + let tests = vec![ + CompilerTestCase { + input: r#" + let countDown = fn(x){ + countDown(x - 1); + }; + countDown(1);"# + .to_string(), + expected_constants: vec![ + Object::INTEGER(1), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::CurrentClosure.make(vec![]), + Opcode::GetLocal.make(vec![0]), + Opcode::Constant.make(vec![0]), + Opcode::Sub.make(vec![]), + Opcode::Call.make(vec![1]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 1, + }), + Object::INTEGER(1), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![1, 0]), + Opcode::SetGlobal.make(vec![0]), + Opcode::GetGlobal.make(vec![0]), + Opcode::Constant.make(vec![2]), + Opcode::Call.make(vec![1]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: r#" + let wrapper = fn() { + let countDown = fn(x) { + countDown(x - 1); + }; + countDown(1); + }; + wrapper(); + "# + .to_string(), + expected_constants: vec![ + Object::INTEGER(1), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::CurrentClosure.make(vec![]), + Opcode::GetLocal.make(vec![0]), + Opcode::Constant.make(vec![0]), + Opcode::Sub.make(vec![]), + Opcode::Call.make(vec![1]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 1, + }), + Object::INTEGER(1), + Object::COMPILEDFUNCTION(CompiledFunction { + instructions: flatten_u8_instructions(vec![ + Opcode::Closure.make(vec![1, 0]), + Opcode::SetLocal.make(vec![0]), + Opcode::GetLocal.make(vec![0]), + Opcode::Constant.make(vec![2]), + Opcode::Call.make(vec![1]), + Opcode::ReturnValue.make(vec![]), + ]), + num_locals: 1, + num_parameters: 0, + }), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Closure.make(vec![3, 0]), + Opcode::SetGlobal.make(vec![0]), + Opcode::GetGlobal.make(vec![0]), + Opcode::Call.make(vec![0]), + Opcode::Pop.make(vec![]), + ]), + }, + ]; + + run_compiler(tests); + } +} diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 367d57a..d5698b0 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,7 +1,9 @@ pub mod code; mod compiler_tests; +mod function_tests; pub mod symbol_table; mod test_utils; +mod while_tests; use std::{cell::RefCell, rc::Rc}; diff --git a/src/compiler/test_utils.rs b/src/compiler/test_utils.rs index d0c56bd..0db4539 100644 --- a/src/compiler/test_utils.rs +++ b/src/compiler/test_utils.rs @@ -1,7 +1,13 @@ -use crate::compiler::code::Instructions; +use std::rc::Rc; + +use crate::{ + compiler::{code::Instructions, Compiler}, + object::{test_utils::check_constants, Object}, + parser::parse, +}; #[allow(dead_code)] -pub fn check_instructions(instructions: &Instructions, expected: &Instructions) { +pub(crate) fn check_instructions(instructions: &Instructions, expected: &Instructions) { assert_eq!( instructions.data.len(), expected.data.len(), @@ -12,3 +18,58 @@ pub fn check_instructions(instructions: &Instructions, expected: &Instructions) "wrong instructions. want={expected:?}, got={instructions:?}" ); } + +#[allow(dead_code)] +pub(crate) struct CompilerTestCase { + pub(crate) input: String, + pub(crate) expected_constants: Vec, + pub(crate) expected_instructions: Instructions, +} + +#[allow(dead_code)] +pub(crate) fn flatten_instructions(instructions: Vec) -> Instructions { + let mut res = Instructions::default(); + for instruction in instructions { + res.append(instruction); + } + res +} + +#[allow(dead_code)] +pub(crate) fn flatten_u8_instructions(instructions: Vec) -> Vec { + let mut res = vec![]; + for instruction in instructions { + res.append(&mut instruction.data.clone()); + } + res +} + +#[allow(dead_code)] +pub(crate) fn run_compiler(tests: Vec) { + for test in tests { + println!("Testing input: {}", test.input); + let program = parse(&test.input); + + let mut compiler = Compiler::new(); + + match compiler.compile(program) { + Ok(_) => { + let bytecode = compiler.bytecode(); + println!( + "want {}, got {}", + test.expected_instructions, bytecode.instructions + ); + check_instructions(&bytecode.instructions, &test.expected_instructions); + check_constants( + &bytecode.constants, + &test + .expected_constants + .iter() + .map(|x| Rc::new(x.clone())) + .collect(), + ); + } + Err(err) => panic!("compiler error: {err}"), + } + } +} diff --git a/src/compiler/while_tests.rs b/src/compiler/while_tests.rs new file mode 100644 index 0000000..4837af3 --- /dev/null +++ b/src/compiler/while_tests.rs @@ -0,0 +1,167 @@ +#[allow(clippy::too_many_lines)] +#[cfg(test)] +pub mod tests { + + use crate::{ + compiler::{ + code::Opcode, + test_utils::{flatten_instructions, run_compiler, CompilerTestCase}, + }, + object::Object, + }; + + #[test] + fn test_while_statements() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + puts("yes"); + } + "# + .to_string(), + expected_constants: vec![Object::STRING("yes".to_string())], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![15]), // 001 + Opcode::GetBuiltin.make(vec![5]), // 004 + Opcode::Constant.make(vec![0]), // 006 + Opcode::Call.make(vec![1]), // 009 + Opcode::Pop.make(vec![]), // 011 + Opcode::Jump.make(vec![0]), // 012 + // 015 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_break_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + break; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![10]), // 001 + Opcode::Jump.make(vec![10]), // 004 + Opcode::Jump.make(vec![0]), // 007 + // 010 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_nested_breaks_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + while (true){ + break; + } + break; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![20]), // 001 + Opcode::True.make(vec![]), // 004 + Opcode::JumpNotTruthy.make(vec![14]), // 005 + Opcode::Jump.make(vec![14]), // 008 + Opcode::Jump.make(vec![4]), // 011 + Opcode::Jump.make(vec![20]), // 014 + Opcode::Jump.make(vec![0]), // 017 + // 020 + ]), + }]; + + run_compiler(tests); + } + #[test] + fn test_continue_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + continue; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![10]), // 001 + Opcode::Jump.make(vec![0]), // 004 + Opcode::Jump.make(vec![0]), // 007 + // 010 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_nested_continue_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + while (true){ + continue; + } + continue; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![20]), // 001 + Opcode::True.make(vec![]), // 004 + Opcode::JumpNotTruthy.make(vec![14]), // 005 + Opcode::Jump.make(vec![4]), // 008 + Opcode::Jump.make(vec![4]), // 011 + Opcode::Jump.make(vec![0]), // 014 + Opcode::Jump.make(vec![0]), // 017 + // 020 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_continue_and_break_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + while (true){ + continue; + } + break; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![20]), // 001 + Opcode::True.make(vec![]), // 004 + Opcode::JumpNotTruthy.make(vec![14]), // 005 + Opcode::Jump.make(vec![4]), // 008 + Opcode::Jump.make(vec![4]), // 011 + Opcode::Jump.make(vec![20]), // 014 + Opcode::Jump.make(vec![0]), // 017 + // 020 + ]), + }]; + + run_compiler(tests); + } +} diff --git a/src/interpreter/evaluator.rs b/src/interpreter/evaluator.rs index 530f340..99c8f96 100644 --- a/src/interpreter/evaluator.rs +++ b/src/interpreter/evaluator.rs @@ -126,7 +126,9 @@ impl Evaluator { } Expression::FunctionCall(x) => { let function = self.eval_expression(*x.function); - if Self::is_error(&function) {} + if Self::is_error(&function) { + return function; + } let args = self.eval_expressions(x.arguments); if args.len() == 1 && Self::is_error(&args[0]) { return args[0].clone(); diff --git a/src/interpreter/evaluator_tests.rs b/src/interpreter/evaluator_tests.rs index b9f5b27..444035f 100644 --- a/src/interpreter/evaluator_tests.rs +++ b/src/interpreter/evaluator_tests.rs @@ -520,7 +520,7 @@ mod tests { fn test_integer_object(object: Object, expected: i64) { match object { Object::INTEGER(x) => assert_eq!(x, expected), - x => panic!("The object is not an integer, it is {:#?}", x), + x => panic!("The object is not an integer, it is {x:#?}"), } } diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 358c325..7218e12 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -50,7 +50,7 @@ pub enum Token { Return, While, Break, - Continue + Continue, } impl Display for Token { @@ -91,7 +91,7 @@ impl Display for Token { Token::Return => write!(f, "return"), Token::While => write!(f, "while"), Token::Break => write!(f, "break"), - Token::Continue => write!(f, "continue") + Token::Continue => write!(f, "continue"), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0d781fb..00c6ff5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -62,9 +62,9 @@ impl Parser { Token::Let => self.parse_let_statement().map(Statement::Let), Token::Return => self.parse_return_statement().map(Statement::Return), Token::While => self.parse_while_statement().map(Statement::While), - Token::Break | Token::Continue => self - .parse_loop_statement() - .map(Statement::LoopStatements), + Token::Break | Token::Continue => { + self.parse_loop_statement().map(Statement::LoopStatements) + } _ => self.parse_expression_statement().map(Statement::Expression), } } diff --git a/src/parser/parser_tests.rs b/src/parser/parser_tests.rs index 8485c65..21a480f 100644 --- a/src/parser/parser_tests.rs +++ b/src/parser/parser_tests.rs @@ -5,8 +5,8 @@ mod tests { lexer::{token::Token, Lexer}, parser::{ ast::{ - BlockStatement, Conditional, LoopStatement, Expression, FunctionCall, Identifier, - InfixOperator, LetStatement, Primitive, Program, ReturnStatement, Statement, + BlockStatement, Conditional, Expression, FunctionCall, Identifier, InfixOperator, + LetStatement, LoopStatement, Primitive, Program, ReturnStatement, Statement, WhileStatement, }, Parser, diff --git a/src/vm/function_tests.rs b/src/vm/function_tests.rs new file mode 100644 index 0000000..fc1d169 --- /dev/null +++ b/src/vm/function_tests.rs @@ -0,0 +1,527 @@ +#[allow(clippy::too_many_lines)] +#[cfg(test)] +mod tests { + use crate::{ + compiler::Compiler, + object::Object, + parser::parse, + vm::{ + test_utils::{run_vm_tests, VmTestCase}, + VM, + }, + }; + + #[test] + fn test_calling_functions_without_arguments() { + let tests = vec![ + VmTestCase { + input: r#" + let fivePlusTen = fn() { 5 + 10; }; + fivePlusTen();"# + .to_string(), + expected: Object::INTEGER(15), + }, + VmTestCase { + input: r#" + let one = fn() { 1; }; + let two = fn() { 2; }; + one() + two()"# + .to_string(), + expected: Object::INTEGER(3), + }, + VmTestCase { + input: r#" + let a = fn() { 1 }; + let b = fn() { a() + 1 }; + let c = fn() { b() + 1 }; + c();"# + .to_string(), + expected: Object::INTEGER(3), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_functions_with_return_statements() { + let tests = vec![ + VmTestCase { + input: r#" + let earlyExit = fn() { return 99; 100; }; + earlyExit();"# + .to_string(), + expected: Object::INTEGER(99), + }, + VmTestCase { + input: r#" + let earlyExit = fn() { return 99; return 100; }; + earlyExit();"# + .to_string(), + expected: Object::INTEGER(99), + }, + ]; + run_vm_tests(tests); + } + + #[test] + fn test_functions_without_return_value() { + let tests = vec![ + VmTestCase { + input: r#" + let noReturn = fn() { }; + noReturn();"# + .to_string(), + expected: Object::NULL, + }, + VmTestCase { + input: r#" + let noReturn = fn() { }; + let noReturnTwo = fn() { noReturn(); }; + noReturn(); + noReturnTwo();"# + .to_string(), + expected: Object::NULL, + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_first_class_functions() { + let tests = vec![ + VmTestCase { + input: r#" + let returnsOne = fn() { 1; }; + let returnsOneReturner = fn() { returnsOne; }; + returnsOneReturner()();"# + .to_string(), + expected: Object::INTEGER(1), + }, + VmTestCase { + input: r#" + let returnsOneReturner = fn() { + let returnsOne = fn() { 1; }; + returnsOne; + }; + returnsOneReturner()();"# + .to_string(), + expected: Object::INTEGER(1), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_calling_function_with_bindings() { + let tests = vec![ + VmTestCase { + input: r#" + let one = fn() { let one = 1; one }; + one();"# + .to_string(), + expected: Object::INTEGER(1), + }, + VmTestCase { + input: r#" + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + oneAndTwo();"# + .to_string(), + expected: Object::INTEGER(3), + }, + VmTestCase { + input: r#" + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + let threeAndFour = fn() { let three = 3; let four = 4; three + four; }; + oneAndTwo() + threeAndFour();"# + .to_string(), + expected: Object::INTEGER(10), + }, + VmTestCase { + input: r#" + let firstFoobar = fn() { let foobar = 50; foobar; }; + let secondFoobar = fn() { let foobar = 100; foobar; }; + firstFoobar() + secondFoobar();"# + .to_string(), + expected: Object::INTEGER(150), + }, + VmTestCase { + input: r#" + let globalSeed = 50; + let minusOne = fn() { + let num = 1; + globalSeed - num; + } + let minusTwo = fn() { + let num = 2; + globalSeed - num; + } + minusOne() + minusTwo();"# + .to_string(), + expected: Object::INTEGER(97), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_calling_functions_with_arguments_and_bindings() { + let tests = vec![ + VmTestCase { + input: r#" + let identity = fn(a) { a; }; + identity(4);"# + .to_string(), + expected: Object::INTEGER(4), + }, + VmTestCase { + input: r#" + let sum = fn(a, b) { a + b; }; + sum(1, 2);"# + .to_string(), + expected: Object::INTEGER(3), + }, + VmTestCase { + input: r#" + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2);"# + .to_string(), + expected: Object::INTEGER(3), + }, + VmTestCase { + input: r#" + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2) + sum(3, 4);"# + .to_string(), + expected: Object::INTEGER(10), + }, + VmTestCase { + input: r#" + let sum = fn(a, b) { + let c = a + b; + c; + }; + let outer = fn() { + sum(1, 2) + sum(3, 4); + }; + outer();"# + .to_string(), + expected: Object::INTEGER(10), + }, + VmTestCase { + input: r#" + let globalNum = 10; + let sum = fn(a, b) { + let c = a + b; + c + globalNum; + }; + let outer = fn() { + sum(1, 2) + sum(3, 4) + globalNum; + }; + outer() + globalNum;"# + .to_string(), + expected: Object::INTEGER(50), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_calling_functions_with_wrong_arguments() { + let tests = vec![ + VmTestCase { + input: r#" + fn() { 1; }(1);"# + .to_string(), + expected: Object::ERROR("Wrong number of arguments: want=0, got=1".to_string()), + }, + VmTestCase { + input: r#" + fn(a) { a; }();"# + .to_string(), + expected: Object::ERROR("Wrong number of arguments: want=1, got=0".to_string()), + }, + VmTestCase { + input: r#" + fn(a, b) { a + b; }(1);"# + .to_string(), + expected: Object::ERROR("Wrong number of arguments: want=2, got=1".to_string()), + }, + ]; + + for test in tests { + println!("Running test: {}", test.input); + let program = parse(&test.input); + let mut compiler = Compiler::new(); + compiler.compile(program).unwrap(); + let bytecode = compiler.bytecode(); + + let mut vm = VM::new(bytecode); + match vm.run() { + Ok(_) => { + panic!("Expected error, but got no error"); + } + Err(e) => match test.expected { + Object::ERROR(msg) => { + assert_eq!(e, msg); + } + _ => { + unreachable!("Poorly written test, the expected value should be an error"); + } + }, + } + } + } + + #[test] + fn test_builtin_functions() { + let tests = vec![ + VmTestCase { + input: r#"len("")"#.to_string(), + expected: Object::INTEGER(0), + }, + VmTestCase { + input: r#"len("four")"#.to_string(), + expected: Object::INTEGER(4), + }, + VmTestCase { + input: r#"len("hello world")"#.to_string(), + expected: Object::INTEGER(11), + }, + VmTestCase { + input: r#"len(1)"#.to_string(), + expected: Object::ERROR("argument to `len` not supported, got INTEGER".to_string()), + }, + VmTestCase { + input: r#"len("one", "two")"#.to_string(), + expected: Object::ERROR("wrong number of arguments. got=2, want=1".to_string()), + }, + VmTestCase { + input: r#"len([1, 2, 3])"#.to_string(), + expected: Object::INTEGER(3), + }, + VmTestCase { + input: r#"len([])"#.to_string(), + expected: Object::INTEGER(0), + }, + VmTestCase { + input: r#"len([1, 2, 3], [4, 5, 6])"#.to_string(), + expected: Object::ERROR("wrong number of arguments. got=2, want=1".to_string()), + }, + VmTestCase { + input: r#"first([1, 2, 3])"#.to_string(), + expected: Object::INTEGER(1), + }, + VmTestCase { + input: r#"first([])"#.to_string(), + expected: Object::NULL, + }, + VmTestCase { + input: r#"first(1)"#.to_string(), + expected: Object::ERROR( + "argument to `first` not supported, must be ARRAY, got INTEGER".to_string(), + ), + }, + VmTestCase { + input: r#"last([1, 2, 3])"#.to_string(), + expected: Object::INTEGER(3), + }, + VmTestCase { + input: r#"last([])"#.to_string(), + expected: Object::NULL, + }, + VmTestCase { + input: r#"last(1)"#.to_string(), + expected: Object::ERROR( + "argument to `last` not supported, must be ARRAY, got INTEGER".to_string(), + ), + }, + VmTestCase { + input: r#"rest([1, 2, 3])"#.to_string(), + expected: Object::ARRAY(vec![Object::INTEGER(2), Object::INTEGER(3)]), + }, + VmTestCase { + input: r#"rest([])"#.to_string(), + expected: Object::NULL, + }, + VmTestCase { + input: r#"push([], 1)"#.to_string(), + expected: Object::ARRAY(vec![Object::INTEGER(1)]), + }, + VmTestCase { + input: r#"push(1, 1)"#.to_string(), + expected: Object::ERROR( + "argument to `push` not supported, must be ARRAY, got INTEGER".to_string(), + ), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_closures() { + let tests = vec![ + VmTestCase { + input: r#" + let newClosure = fn(a) { + fn() { a; }; + }; + let closure = newClosure(99); + closure();"# + .to_string(), + expected: Object::INTEGER(99), + }, + VmTestCase { + input: r#" + let newAdder = fn(a, b) { + fn(c) { a + b + c }; + }; + let adder = newAdder(1, 2); + adder(8);"# + .to_string(), + expected: Object::INTEGER(11), + }, + VmTestCase { + input: r#" + let newAdder = fn(a, b) { + let c = a + b; + fn(d) { c + d }; + }; + let adder = newAdder(1, 2); + adder(8);"# + .to_string(), + expected: Object::INTEGER(11), + }, + VmTestCase { + input: r#" + let newAdderOuter = fn(a, b) { + let c = a + b; + fn(d) { + let e = d + c; + fn(f) { e + f; }; + }; + }; + let newAdderInner = newAdderOuter(1, 2) + let adder = newAdderInner(3); + adder(8);"# + .to_string(), + expected: Object::INTEGER(14), + }, + VmTestCase { + input: r#" + let a = 1; + let newAdderOuter = fn(b) { + fn(c) { + fn(d) { a + b + c + d }; + }; + }; + let newAdderInner = newAdderOuter(2) + let adder = newAdderInner(3); + adder(8);"# + .to_string(), + expected: Object::INTEGER(14), + }, + VmTestCase { + input: r#" + let newClosure = fn(a, b) { + let one = fn() { a; }; + let two = fn() { b; }; + fn() { one() + two(); }; + }; + let closure = newClosure(9, 90); + closure();"# + .to_string(), + expected: Object::INTEGER(99), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_recursive_functions() { + let tests = vec![ + VmTestCase { + input: r#" + let countDown = fn(x) { + if (x == 0) { + return 0; + } else { + countDown(x - 1); + } + }; + countDown(1);"# + .to_string(), + expected: Object::INTEGER(0), + }, + VmTestCase { + input: r#" + let countDown = fn(x) { + if (x == 0) { + return 0; + } else { + countDown(x - 1); + } + }; + let wrapper = fn() { + countDown(1); + }; + wrapper();"# + .to_string(), + expected: Object::INTEGER(0), + }, + VmTestCase { + input: r#" + let wrapper = fn() { + let countDown = fn(x) { + if (x == 0) { + return 0; + } else { + countDown(x - 1); + } + }; + countDown(1); + }; + wrapper();"# + .to_string(), + expected: Object::INTEGER(0), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_recursive_fibonacci() { + let tests = vec![VmTestCase { + input: r#" + let fibonacci = fn(x) { + if (x == 0) { + return 0; + } else { + if (x == 1) { + return 1; + } else { + fibonacci(x - 1) + fibonacci(x - 2); + } + } + }; + fibonacci(15);"# + .to_string(), + expected: Object::INTEGER(610), + }]; + + run_vm_tests(tests); + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 35455c4..57d954c 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,4 +1,8 @@ +mod function_tests; +mod shadowing_tests; +mod test_utils; mod vm_tests; +mod while_statemets_tests; use crate::{ compiler::{ @@ -493,7 +497,7 @@ impl VM { Object::COMPILEDFUNCTION(func) => { let mut closure = Closure::new(func); - for obj in self.stack[self.sp - num_free..self.sp].iter() { + for obj in &self.stack[self.sp - num_free..self.sp] { closure.add_free_variable(obj.as_ref().clone()); } diff --git a/src/vm/shadowing_tests.rs b/src/vm/shadowing_tests.rs new file mode 100644 index 0000000..35a29ee --- /dev/null +++ b/src/vm/shadowing_tests.rs @@ -0,0 +1,296 @@ +#[allow(clippy::too_many_lines)] +#[cfg(test)] +mod tests { + + use crate::{ + object::Object, + vm::test_utils::{run_vm_tests, VmTestCase}, + }; + + #[test] + fn test_use_same_varaible_multiple_times() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 1; + let b = a; + let c = a + b + 1; + c"# + .to_string(), + expected: Object::INTEGER(3), + }, + VmTestCase { + input: r#" + let a = 1; + let b = a; + let c = a + b + 1; + let a = 2; + let b = a; + let c = a + b + 1; + c"# + .to_string(), + expected: Object::INTEGER(5), + }, + VmTestCase { + input: r#" + let a = "hello"; + let b = "world"; + let c = a + b; + let d = a + b + c; + d"# + .to_string(), + expected: Object::STRING("helloworldhelloworld".to_string()), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_array_multiple_ussage() { + let tests = vec![ + VmTestCase { + input: r#" + let array = [1,2,3]; + let value = array[1] + array[2]; + value"# + .to_string(), + expected: Object::INTEGER(5), + }, + VmTestCase { + input: r#" + let array = [1,2,3]; + let array = push(array, 4); + array"# + .to_string(), + expected: Object::ARRAY(vec![ + Object::INTEGER(1), + Object::INTEGER(2), + Object::INTEGER(3), + Object::INTEGER(4), + ]), + }, + ]; + run_vm_tests(tests); + } + + #[test] + fn test_shadowing_same_type() { + let tests = vec![ + VmTestCase { + input: r#" + let x = 4; + let x = 5; + x"# + .to_string(), + expected: Object::INTEGER(5), + }, + VmTestCase { + input: r#" + let x = [1,2,3]; + let x = [4,5,6]; + x"# + .to_string(), + expected: Object::ARRAY(vec![ + Object::INTEGER(4), + Object::INTEGER(5), + Object::INTEGER(6), + ]), + }, + VmTestCase { + input: r#" + let x = fn() { 1 }; + let x = fn() { 2 }; + x()"# + .to_string(), + expected: Object::INTEGER(2), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_shadowing_with_new_type() { + let tests = vec![ + VmTestCase { + input: r#" + let x = 4; + let x = "string"; + x"# + .to_string(), + expected: Object::STRING("string".to_string()), + }, + VmTestCase { + input: r#" + let x = "string"; + let x = fn() { 1 }; + x()"# + .to_string(), + expected: Object::INTEGER(1), + }, + VmTestCase { + input: r#" + let x = fn() { 1 }; + let x = 5, + x"# + .to_string(), + expected: Object::INTEGER(5), + }, + VmTestCase { + input: r#" + let x = 5; + let x = [1,2,3]; + x"# + .to_string(), + expected: Object::ARRAY(vec![ + Object::INTEGER(1), + Object::INTEGER(2), + Object::INTEGER(3), + ]), + }, + VmTestCase { + input: r#" + let x = [1,2,3]; + let x = 5; + x"# + .to_string(), + expected: Object::INTEGER(5), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_shadowing_using_previous_value() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 1; + let b = a * a + 2 + b"# + .to_string(), + expected: Object::INTEGER(3), + }, + VmTestCase { + input: r#" + let a = 1; + let a = a + 1; + a"# + .to_string(), + expected: Object::INTEGER(2), + }, + VmTestCase { + input: r#" + let a = fn() { + let a = 1; + a + }; + a() + "# + .to_string(), + expected: Object::INTEGER(1), + }, + VmTestCase { + input: r#" + let f = fn(a){ + let a = 1; + let a = a + 1; + a + }; + f(1) + "# + .to_string(), + expected: Object::INTEGER(2), + }, + VmTestCase { + input: r#" + let f = fn(){ + let a = 1; + let h = fn(){ + let a = 2; + a + }; + h() + a + }; + f() + "# + .to_string(), + expected: Object::INTEGER(3), + }, + // Addition of a global variable a with 10 as its value + VmTestCase { + input: r#" + let a = 10; + let a = fn() { + let a = 1; + a + }; + a() + "# + .to_string(), + expected: Object::INTEGER(1), + }, + VmTestCase { + input: r#" + let a = 10; + let f = fn(a){ + let a = 1; + a + }; + f(1) + a + "# + .to_string(), + expected: Object::INTEGER(11), + }, + VmTestCase { + input: r#" + let a = 10; + let f = fn(a){ + let a = 1; + let a = a + 1; + a + }; + f(1) + a + "# + .to_string(), + expected: Object::INTEGER(12), + }, + VmTestCase { + input: r#" + let a = 10; + let f = fn(){ + let h = fn(){ + let a = 2; + a + }; + h() + }; + f() + a + "# + .to_string(), + expected: Object::INTEGER(12), + }, + VmTestCase { + input: r#" + let a = 10; + let f = fn(){ + let a = 1; + let a = a + 1; + let h = fn(){ + let a = 2; + a + }; + h() + a + }; + f() + a + "# + .to_string(), + expected: Object::INTEGER(14), + }, + ]; + + run_vm_tests(tests); + } +} diff --git a/src/vm/test_utils.rs b/src/vm/test_utils.rs new file mode 100644 index 0000000..b6d82af --- /dev/null +++ b/src/vm/test_utils.rs @@ -0,0 +1,39 @@ +use crate::{ + compiler::{code::Instructions, Compiler}, + object::{test_utils::check_constants, Object}, + parser::parse, + vm::VM, +}; + +#[allow(dead_code)] +pub(crate) struct VmTestCase { + pub(crate) input: String, + pub(crate) expected: Object, +} + +#[allow(dead_code)] +pub(crate) fn run_vm_tests(tests: Vec) { + for test in tests { + println!("Running test: {}", test.input); + let program = parse(&test.input); + let mut compiler = Compiler::new(); + compiler.compile(program).unwrap(); + let bytecode = compiler.bytecode(); + + for (i, constant) in bytecode.constants.iter().enumerate() { + match constant { + Object::COMPILEDFUNCTION(cf) => { + println!("Compiled function:"); + let instructions = Instructions::new(cf.instructions.clone()); + println!("{instructions}"); + } + _ => println!("{i}: {constant}"), + } + } + + let mut vm = VM::new(bytecode); + vm.run().unwrap(); + let got = vm.last_popped_stack_element().unwrap(); + check_constants(&vec![test.expected], &vec![got]); + } +} diff --git a/src/vm/vm_tests.rs b/src/vm/vm_tests.rs index cefc1b0..48ea4b8 100644 --- a/src/vm/vm_tests.rs +++ b/src/vm/vm_tests.rs @@ -1,49 +1,13 @@ #[allow(clippy::too_many_lines)] #[cfg(test)] mod tests { - use std::collections::HashMap; use crate::{ - compiler::{code::Instructions, Compiler}, - object::{test_utils::check_constants, Object}, - parser::parse, - vm::VM, + object::Object, + vm::test_utils::{run_vm_tests, VmTestCase}, }; - struct VmTestCase { - input: String, - expected: Object, - } - - fn run_vm_tests(tests: Vec) { - for test in tests { - println!("Running test: {}", test.input); - let program = parse(&test.input); - let mut compiler = Compiler::new(); - compiler.compile(program).unwrap(); - let bytecode = compiler.bytecode(); - - for (i, constant) in bytecode.constants.iter().enumerate() { - match constant { - Object::COMPILEDFUNCTION(cf) => { - println!("Compiled function:"); - let instructions = Instructions::new(cf.instructions.clone()); - println!("{}", instructions); - } - _ => { - println!("{}: {}", i, constant); - } - } - } - - let mut vm = VM::new(bytecode); - vm.run().unwrap(); - let got = vm.last_popped_stack_element().unwrap(); - check_constants(&vec![test.expected], &vec![got]); - } - } - #[test] fn test_integer_arithmetic() { let tests = vec![ @@ -429,1100 +393,4 @@ mod tests { run_vm_tests(tests); } - - #[test] - fn test_calling_functions_without_arguments() { - let tests = vec![ - VmTestCase { - input: r#" - let fivePlusTen = fn() { 5 + 10; }; - fivePlusTen();"# - .to_string(), - expected: Object::INTEGER(15), - }, - VmTestCase { - input: r#" - let one = fn() { 1; }; - let two = fn() { 2; }; - one() + two()"# - .to_string(), - expected: Object::INTEGER(3), - }, - VmTestCase { - input: r#" - let a = fn() { 1 }; - let b = fn() { a() + 1 }; - let c = fn() { b() + 1 }; - c();"# - .to_string(), - expected: Object::INTEGER(3), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_functions_with_return_statements() { - let tests = vec![ - VmTestCase { - input: r#" - let earlyExit = fn() { return 99; 100; }; - earlyExit();"# - .to_string(), - expected: Object::INTEGER(99), - }, - VmTestCase { - input: r#" - let earlyExit = fn() { return 99; return 100; }; - earlyExit();"# - .to_string(), - expected: Object::INTEGER(99), - }, - ]; - run_vm_tests(tests); - } - - #[test] - fn test_functions_without_return_value() { - let tests = vec![ - VmTestCase { - input: r#" - let noReturn = fn() { }; - noReturn();"# - .to_string(), - expected: Object::NULL, - }, - VmTestCase { - input: r#" - let noReturn = fn() { }; - let noReturnTwo = fn() { noReturn(); }; - noReturn(); - noReturnTwo();"# - .to_string(), - expected: Object::NULL, - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_first_class_functions() { - let tests = vec![ - VmTestCase { - input: r#" - let returnsOne = fn() { 1; }; - let returnsOneReturner = fn() { returnsOne; }; - returnsOneReturner()();"# - .to_string(), - expected: Object::INTEGER(1), - }, - VmTestCase { - input: r#" - let returnsOneReturner = fn() { - let returnsOne = fn() { 1; }; - returnsOne; - }; - returnsOneReturner()();"# - .to_string(), - expected: Object::INTEGER(1), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_calling_function_with_bindings() { - let tests = vec![ - VmTestCase { - input: r#" - let one = fn() { let one = 1; one }; - one();"# - .to_string(), - expected: Object::INTEGER(1), - }, - VmTestCase { - input: r#" - let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; - oneAndTwo();"# - .to_string(), - expected: Object::INTEGER(3), - }, - VmTestCase { - input: r#" - let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; - let threeAndFour = fn() { let three = 3; let four = 4; three + four; }; - oneAndTwo() + threeAndFour();"# - .to_string(), - expected: Object::INTEGER(10), - }, - VmTestCase { - input: r#" - let firstFoobar = fn() { let foobar = 50; foobar; }; - let secondFoobar = fn() { let foobar = 100; foobar; }; - firstFoobar() + secondFoobar();"# - .to_string(), - expected: Object::INTEGER(150), - }, - VmTestCase { - input: r#" - let globalSeed = 50; - let minusOne = fn() { - let num = 1; - globalSeed - num; - } - let minusTwo = fn() { - let num = 2; - globalSeed - num; - } - minusOne() + minusTwo();"# - .to_string(), - expected: Object::INTEGER(97), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_calling_functions_with_arguments_and_bindings() { - let tests = vec![ - VmTestCase { - input: r#" - let identity = fn(a) { a; }; - identity(4);"# - .to_string(), - expected: Object::INTEGER(4), - }, - VmTestCase { - input: r#" - let sum = fn(a, b) { a + b; }; - sum(1, 2);"# - .to_string(), - expected: Object::INTEGER(3), - }, - VmTestCase { - input: r#" - let sum = fn(a, b) { - let c = a + b; - c; - }; - sum(1, 2);"# - .to_string(), - expected: Object::INTEGER(3), - }, - VmTestCase { - input: r#" - let sum = fn(a, b) { - let c = a + b; - c; - }; - sum(1, 2) + sum(3, 4);"# - .to_string(), - expected: Object::INTEGER(10), - }, - VmTestCase { - input: r#" - let sum = fn(a, b) { - let c = a + b; - c; - }; - let outer = fn() { - sum(1, 2) + sum(3, 4); - }; - outer();"# - .to_string(), - expected: Object::INTEGER(10), - }, - VmTestCase { - input: r#" - let globalNum = 10; - let sum = fn(a, b) { - let c = a + b; - c + globalNum; - }; - let outer = fn() { - sum(1, 2) + sum(3, 4) + globalNum; - }; - outer() + globalNum;"# - .to_string(), - expected: Object::INTEGER(50), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_calling_functions_with_wrong_arguments() { - let tests = vec![ - VmTestCase { - input: r#" - fn() { 1; }(1);"# - .to_string(), - expected: Object::ERROR("Wrong number of arguments: want=0, got=1".to_string()), - }, - VmTestCase { - input: r#" - fn(a) { a; }();"# - .to_string(), - expected: Object::ERROR("Wrong number of arguments: want=1, got=0".to_string()), - }, - VmTestCase { - input: r#" - fn(a, b) { a + b; }(1);"# - .to_string(), - expected: Object::ERROR("Wrong number of arguments: want=2, got=1".to_string()), - }, - ]; - - for test in tests { - println!("Running test: {}", test.input); - let program = parse(&test.input); - let mut compiler = Compiler::new(); - compiler.compile(program).unwrap(); - let bytecode = compiler.bytecode(); - - let mut vm = VM::new(bytecode); - match vm.run() { - Ok(_) => { - panic!("Expected error, but got no error"); - } - Err(e) => match test.expected { - Object::ERROR(msg) => { - assert_eq!(e, msg); - } - _ => { - unreachable!("Poorly written test, the expected value should be an error"); - } - }, - } - } - } - - #[test] - fn test_builtin_functions() { - let tests = vec![ - VmTestCase { - input: r#"len("")"#.to_string(), - expected: Object::INTEGER(0), - }, - VmTestCase { - input: r#"len("four")"#.to_string(), - expected: Object::INTEGER(4), - }, - VmTestCase { - input: r#"len("hello world")"#.to_string(), - expected: Object::INTEGER(11), - }, - VmTestCase { - input: r#"len(1)"#.to_string(), - expected: Object::ERROR("argument to `len` not supported, got INTEGER".to_string()), - }, - VmTestCase { - input: r#"len("one", "two")"#.to_string(), - expected: Object::ERROR("wrong number of arguments. got=2, want=1".to_string()), - }, - VmTestCase { - input: r#"len([1, 2, 3])"#.to_string(), - expected: Object::INTEGER(3), - }, - VmTestCase { - input: r#"len([])"#.to_string(), - expected: Object::INTEGER(0), - }, - VmTestCase { - input: r#"len([1, 2, 3], [4, 5, 6])"#.to_string(), - expected: Object::ERROR("wrong number of arguments. got=2, want=1".to_string()), - }, - VmTestCase { - input: r#"first([1, 2, 3])"#.to_string(), - expected: Object::INTEGER(1), - }, - VmTestCase { - input: r#"first([])"#.to_string(), - expected: Object::NULL, - }, - VmTestCase { - input: r#"first(1)"#.to_string(), - expected: Object::ERROR( - "argument to `first` not supported, must be ARRAY, got INTEGER".to_string(), - ), - }, - VmTestCase { - input: r#"last([1, 2, 3])"#.to_string(), - expected: Object::INTEGER(3), - }, - VmTestCase { - input: r#"last([])"#.to_string(), - expected: Object::NULL, - }, - VmTestCase { - input: r#"last(1)"#.to_string(), - expected: Object::ERROR( - "argument to `last` not supported, must be ARRAY, got INTEGER".to_string(), - ), - }, - VmTestCase { - input: r#"rest([1, 2, 3])"#.to_string(), - expected: Object::ARRAY(vec![Object::INTEGER(2), Object::INTEGER(3)]), - }, - VmTestCase { - input: r#"rest([])"#.to_string(), - expected: Object::NULL, - }, - VmTestCase { - input: r#"push([], 1)"#.to_string(), - expected: Object::ARRAY(vec![Object::INTEGER(1)]), - }, - VmTestCase { - input: r#"push(1, 1)"#.to_string(), - expected: Object::ERROR( - "argument to `push` not supported, must be ARRAY, got INTEGER".to_string(), - ), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_closures() { - let tests = vec![ - VmTestCase { - input: r#" - let newClosure = fn(a) { - fn() { a; }; - }; - let closure = newClosure(99); - closure();"# - .to_string(), - expected: Object::INTEGER(99), - }, - VmTestCase { - input: r#" - let newAdder = fn(a, b) { - fn(c) { a + b + c }; - }; - let adder = newAdder(1, 2); - adder(8);"# - .to_string(), - expected: Object::INTEGER(11), - }, - VmTestCase { - input: r#" - let newAdder = fn(a, b) { - let c = a + b; - fn(d) { c + d }; - }; - let adder = newAdder(1, 2); - adder(8);"# - .to_string(), - expected: Object::INTEGER(11), - }, - VmTestCase { - input: r#" - let newAdderOuter = fn(a, b) { - let c = a + b; - fn(d) { - let e = d + c; - fn(f) { e + f; }; - }; - }; - let newAdderInner = newAdderOuter(1, 2) - let adder = newAdderInner(3); - adder(8);"# - .to_string(), - expected: Object::INTEGER(14), - }, - VmTestCase { - input: r#" - let a = 1; - let newAdderOuter = fn(b) { - fn(c) { - fn(d) { a + b + c + d }; - }; - }; - let newAdderInner = newAdderOuter(2) - let adder = newAdderInner(3); - adder(8);"# - .to_string(), - expected: Object::INTEGER(14), - }, - VmTestCase { - input: r#" - let newClosure = fn(a, b) { - let one = fn() { a; }; - let two = fn() { b; }; - fn() { one() + two(); }; - }; - let closure = newClosure(9, 90); - closure();"# - .to_string(), - expected: Object::INTEGER(99), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_recursive_functions() { - let tests = vec![ - VmTestCase { - input: r#" - let countDown = fn(x) { - if (x == 0) { - return 0; - } else { - countDown(x - 1); - } - }; - countDown(1);"# - .to_string(), - expected: Object::INTEGER(0), - }, - VmTestCase { - input: r#" - let countDown = fn(x) { - if (x == 0) { - return 0; - } else { - countDown(x - 1); - } - }; - let wrapper = fn() { - countDown(1); - }; - wrapper();"# - .to_string(), - expected: Object::INTEGER(0), - }, - VmTestCase { - input: r#" - let wrapper = fn() { - let countDown = fn(x) { - if (x == 0) { - return 0; - } else { - countDown(x - 1); - } - }; - countDown(1); - }; - wrapper();"# - .to_string(), - expected: Object::INTEGER(0), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_recursive_fibonacci() { - let tests = vec![VmTestCase { - input: r#" - let fibonacci = fn(x) { - if (x == 0) { - return 0; - } else { - if (x == 1) { - return 1; - } else { - fibonacci(x - 1) + fibonacci(x - 2); - } - } - }; - fibonacci(15);"# - .to_string(), - expected: Object::INTEGER(610), - }]; - - run_vm_tests(tests); - } - #[test] - fn test_use_same_varaible_multiple_times() { - let tests = vec![ - VmTestCase { - input: r#" - let a = 1; - let b = a; - let c = a + b + 1; - c"# - .to_string(), - expected: Object::INTEGER(3), - }, - VmTestCase { - input: r#" - let a = 1; - let b = a; - let c = a + b + 1; - let a = 2; - let b = a; - let c = a + b + 1; - c"# - .to_string(), - expected: Object::INTEGER(5), - }, - VmTestCase { - input: r#" - let a = "hello"; - let b = "world"; - let c = a + b; - let d = a + b + c; - d"# - .to_string(), - expected: Object::STRING("helloworldhelloworld".to_string()), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_array_multiple_ussage() { - let tests = vec![ - VmTestCase { - input: r#" - let array = [1,2,3]; - let value = array[1] + array[2]; - value"# - .to_string(), - expected: Object::INTEGER(5), - }, - VmTestCase { - input: r#" - let array = [1,2,3]; - let array = push(array, 4); - array"# - .to_string(), - expected: Object::ARRAY(vec![ - Object::INTEGER(1), - Object::INTEGER(2), - Object::INTEGER(3), - Object::INTEGER(4), - ]), - }, - ]; - run_vm_tests(tests); - } - - #[test] - fn test_shadowing_same_type() { - let tests = vec![ - VmTestCase { - input: r#" - let x = 4; - let x = 5; - x"# - .to_string(), - expected: Object::INTEGER(5), - }, - VmTestCase { - input: r#" - let x = [1,2,3]; - let x = [4,5,6]; - x"# - .to_string(), - expected: Object::ARRAY(vec![ - Object::INTEGER(4), - Object::INTEGER(5), - Object::INTEGER(6), - ]), - }, - VmTestCase { - input: r#" - let x = fn() { 1 }; - let x = fn() { 2 }; - x()"# - .to_string(), - expected: Object::INTEGER(2), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_shadowing_with_new_type() { - let tests = vec![ - VmTestCase { - input: r#" - let x = 4; - let x = "string"; - x"# - .to_string(), - expected: Object::STRING("string".to_string()), - }, - VmTestCase { - input: r#" - let x = "string"; - let x = fn() { 1 }; - x()"# - .to_string(), - expected: Object::INTEGER(1), - }, - VmTestCase { - input: r#" - let x = fn() { 1 }; - let x = 5, - x"# - .to_string(), - expected: Object::INTEGER(5), - }, - VmTestCase { - input: r#" - let x = 5; - let x = [1,2,3]; - x"# - .to_string(), - expected: Object::ARRAY(vec![ - Object::INTEGER(1), - Object::INTEGER(2), - Object::INTEGER(3), - ]), - }, - VmTestCase { - input: r#" - let x = [1,2,3]; - let x = 5; - x"# - .to_string(), - expected: Object::INTEGER(5), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_shadowing_using_previous_value() { - let tests = vec![ - VmTestCase { - input: r#" - let a = 1; - let b = a * a + 2 - b"# - .to_string(), - expected: Object::INTEGER(3), - }, - VmTestCase { - input: r#" - let a = 1; - let a = a + 1; - a"# - .to_string(), - expected: Object::INTEGER(2), - }, - VmTestCase { - input: r#" - let a = fn() { - let a = 1; - a - }; - a() - "# - .to_string(), - expected: Object::INTEGER(1), - }, - VmTestCase { - input: r#" - let f = fn(a){ - let a = 1; - let a = a + 1; - a - }; - f(1) - "# - .to_string(), - expected: Object::INTEGER(2), - }, - VmTestCase { - input: r#" - let f = fn(){ - let a = 1; - let h = fn(){ - let a = 2; - a - }; - h() + a - }; - f() - "# - .to_string(), - expected: Object::INTEGER(3), - }, - // Addition of a global variable a with 10 as its value - VmTestCase { - input: r#" - let a = 10; - let a = fn() { - let a = 1; - a - }; - a() - "# - .to_string(), - expected: Object::INTEGER(1), - }, - VmTestCase { - input: r#" - let a = 10; - let f = fn(a){ - let a = 1; - a - }; - f(1) + a - "# - .to_string(), - expected: Object::INTEGER(11), - }, - VmTestCase { - input: r#" - let a = 10; - let f = fn(a){ - let a = 1; - let a = a + 1; - a - }; - f(1) + a - "# - .to_string(), - expected: Object::INTEGER(12), - }, - VmTestCase { - input: r#" - let a = 10; - let f = fn(){ - let h = fn(){ - let a = 2; - a - }; - h() - }; - f() + a - "# - .to_string(), - expected: Object::INTEGER(12), - }, - VmTestCase { - input: r#" - let a = 10; - let f = fn(){ - let a = 1; - let a = a + 1; - let h = fn(){ - let a = 2; - a - }; - h() + a - }; - f() + a - "# - .to_string(), - expected: Object::INTEGER(14), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_while_statements_without_break_or_continue() { - let tests = vec![ - VmTestCase { - input: r#" - let a = 1; - while (a < 100){ - let a = a + 1; - } - a - "# - .to_string(), - expected: Object::INTEGER(100), - }, - VmTestCase { - input: r#" - let a = 1; - while (a < 0){ - let a = 100; - } - a - "# - .to_string(), - expected: Object::INTEGER(1), - }, - VmTestCase { - input: r#" - let a = 1; - while(false){ - let a = 100; - } - a - "# - .to_string(), - expected: Object::INTEGER(1), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_while_clean_up() { - // This tests makes sure that a while statement clears the stack correctly (which is - // different from the conditional behavior) - let tests = vec![VmTestCase { - input: r#" - let a = 0; - while (a < 10000){ - let a = a + 1; - puts(a); - } - a - "# - .to_string(), - expected: Object::INTEGER(10000), - }]; - - run_vm_tests(tests); - } - - #[test] - fn test_break_from_while() { - let tests = vec![ - VmTestCase { - input: r#" - let a = 0; - while (a < 10) { - if (a == 5) { - break; - } - let a = a + 1; - }; - a"# - .to_string(), - expected: Object::INTEGER(5), - }, - VmTestCase { - input: r#" - let a = 0; - let c = 0; - while (a < 10) { - let b = 0; - while (b < 10) { - if (b == 5) { - break; - } - let b = b + 1; - let c = c + 1; - } - let a = a + 1; - }; - c"# - .to_string(), - expected: Object::INTEGER(50), - }, - VmTestCase { - input: r#" - let a = 0; - let c = 0; - while (a < 10) { - if (a == 5) { - break; - } - - let b = 0; - while (b < 10) { - if (b == 5) { - break; - } - let b = b + 1; - let c = c + 1; - } - let a = a + 1; - }; - c"# - .to_string(), - expected: Object::INTEGER(25), - }, - // The next tests will take care of the possible interference between the break and a function - VmTestCase { - input: r#" - let f = fn (a) { - let c = 0; - while (a < 10) { - if (a == 5) { - break; - } - - let b = 0; - while (b < 10) { - if (b == 5) { - break; - } - let b = b + 1; - let c = c + 1; - } - let a = a + 1; - } - c - }; - f(0)"# - .to_string(), - expected: Object::INTEGER(25), - }, - VmTestCase { - input: r#" - let a = 0; - let c = 0; - while (a < 10) { - if (a == 5) { - break; - } - - let f = fn () { - let c = 0; - let b = 0; - while (b < 10) { - if (b == 5) { - break; - } - let b = b + 1; - let c = c + 1; - } - c - } - - let a = a + 1; - let c = c + f(); - }; - c"# - .to_string(), - expected: Object::INTEGER(25), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_continue_from_while() { - let tests = vec![ - VmTestCase { - input: r#" - let a = 0; - let c = 0; - while (a < 10) { - let a = a + 1; - if (a == 5) { - let c = c + 2; - continue; - } - let c = c + 1; - }; - c"# - .to_string(), - expected: Object::INTEGER(11), - }, - VmTestCase { - input: r#" - let a = 0; - let c = 0; - while (a < 10) { - let b = 0; - while (b < 10) { - let b = b + 1; - if (b == 5) { - let c = c + 3; - continue; - } - let c = c + 1; - } - let a = a + 1; - }; - c"# - .to_string(), - expected: Object::INTEGER(120), - }, - // The next tests will take care of the possible interference between the continue and a function - VmTestCase { - input: r#" - let f = fn (a) { - let c = 0; - while (a < 10) { - let b = 0; - while (b < 10) { - let b = b + 1; - if (b == 5) { - let c = c + 3; - continue; - } - let c = c + 1; - } - let a = a + 1; - } - c - }; - f(0)"# - .to_string(), - expected: Object::INTEGER(120), - }, - VmTestCase { - input: r#" - let a = 0; - let c = 0; - while (a < 10) { - let f = fn () { - let c = 0; - let b = 0; - while (b < 10) { - let b = b + 1; - if (b == 5) { - let c = c + 3; - continue; - } - let c = c + 1; - } - c - } - - let a = a + 1; - let c = c + f(); - }; - c"# - .to_string(), - expected: Object::INTEGER(120), - }, - ]; - - run_vm_tests(tests); - } - - #[test] - fn test_continue_and_break_in_while() { - let tests = vec![VmTestCase { - input: r#" - let a = 0; - let c = 0; - while (a < 10) { - let a = a + 1; - if (a == 5) { - let c = c + 3; - continue; - } - if (a == 7) { - break; - } - let c = c + 1; - } - c"# - .to_string(), - expected: Object::INTEGER(8), - }]; - run_vm_tests(tests); - } } diff --git a/src/vm/while_statemets_tests.rs b/src/vm/while_statemets_tests.rs new file mode 100644 index 0000000..852438f --- /dev/null +++ b/src/vm/while_statemets_tests.rs @@ -0,0 +1,305 @@ +#[allow(clippy::too_many_lines)] +#[cfg(test)] +mod tests { + + use crate::{ + object::Object, + vm::test_utils::{run_vm_tests, VmTestCase}, + }; + + #[test] + fn test_while_statements_without_break_or_continue() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 1; + while (a < 100){ + let a = a + 1; + } + a + "# + .to_string(), + expected: Object::INTEGER(100), + }, + VmTestCase { + input: r#" + let a = 1; + while (a < 0){ + let a = 100; + } + a + "# + .to_string(), + expected: Object::INTEGER(1), + }, + VmTestCase { + input: r#" + let a = 1; + while(false){ + let a = 100; + } + a + "# + .to_string(), + expected: Object::INTEGER(1), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_while_clean_up() { + // This tests makes sure that a while statement clears the stack correctly (which is + // different from the conditional behavior) + let tests = vec![VmTestCase { + input: r#" + let a = 0; + while (a < 10000){ + let a = a + 1; + puts(a); + } + a + "# + .to_string(), + expected: Object::INTEGER(10000), + }]; + + run_vm_tests(tests); + } + + #[test] + fn test_break_from_while() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 0; + while (a < 10) { + if (a == 5) { + break; + } + let a = a + 1; + }; + a"# + .to_string(), + expected: Object::INTEGER(5), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + let a = a + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(50), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + if (a == 5) { + break; + } + + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + let a = a + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(25), + }, + // The next tests will take care of the possible interference between the break and a function + VmTestCase { + input: r#" + let f = fn (a) { + let c = 0; + while (a < 10) { + if (a == 5) { + break; + } + + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + let a = a + 1; + } + c + }; + f(0)"# + .to_string(), + expected: Object::INTEGER(25), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + if (a == 5) { + break; + } + + let f = fn () { + let c = 0; + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + c + } + + let a = a + 1; + let c = c + f(); + }; + c"# + .to_string(), + expected: Object::INTEGER(25), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_continue_from_while() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let a = a + 1; + if (a == 5) { + let c = c + 2; + continue; + } + let c = c + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(11), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let b = 0; + while (b < 10) { + let b = b + 1; + if (b == 5) { + let c = c + 3; + continue; + } + let c = c + 1; + } + let a = a + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(120), + }, + // The next tests will take care of the possible interference between the continue and a function + VmTestCase { + input: r#" + let f = fn (a) { + let c = 0; + while (a < 10) { + let b = 0; + while (b < 10) { + let b = b + 1; + if (b == 5) { + let c = c + 3; + continue; + } + let c = c + 1; + } + let a = a + 1; + } + c + }; + f(0)"# + .to_string(), + expected: Object::INTEGER(120), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let f = fn () { + let c = 0; + let b = 0; + while (b < 10) { + let b = b + 1; + if (b == 5) { + let c = c + 3; + continue; + } + let c = c + 1; + } + c + } + + let a = a + 1; + let c = c + f(); + }; + c"# + .to_string(), + expected: Object::INTEGER(120), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_continue_and_break_in_while() { + let tests = vec![VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let a = a + 1; + if (a == 5) { + let c = c + 3; + continue; + } + if (a == 7) { + break; + } + let c = c + 1; + } + c"# + .to_string(), + expected: Object::INTEGER(8), + }]; + run_vm_tests(tests); + } +} diff --git a/tests/formatting_integrity.rs b/tests/formatting_integrity.rs index 8f639cd..ef4b582 100644 --- a/tests/formatting_integrity.rs +++ b/tests/formatting_integrity.rs @@ -9,11 +9,11 @@ fn run_test(input: &str) { let formatted_input = Formatter::format(input); - println!("{}", formatted_input); + println!("{formatted_input}"); let formatted_evaluation = run_input(formatted_input.as_str()); - println!("{}", formatted_evaluation); + println!("{formatted_evaluation}"); assert_eq!(input_evaluation, formatted_evaluation); }