diff --git a/Cargo.lock b/Cargo.lock index 6092c96..505ea68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -43,9 +43,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.1" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ "bitflags", "clap_lex", @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.1.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8955d4e8cd4f28f9a01c93a050194c4d131e73ca02f6636bcddbed867014d7" +checksum = "3d6540eedc41f8a5a76cf3d8d458057dcdf817be4158a55b5f861f7a5483de75" dependencies = [ "clap", ] @@ -195,9 +195,9 @@ dependencies = [ [[package]] name = "fd-lock" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb21c69b9fea5e15dbc1049e4b77145dd0ba1c84019c488102de0dc4ea4b0a27" +checksum = "28c0190ff0bd3b28bfdd4d0cf9f92faa12880fb0b8ae2054723dd6c76a4efd42" dependencies = [ "cfg-if", "rustix", @@ -205,24 +205,79 @@ dependencies = [ ] [[package]] -name = "feint" +name = "feint-builtins" version = "0.0.0" dependencies = [ "bitflags", + "feint-code-gen", + "feint-util", + "flate2", + "indexmap", + "num-bigint", + "num-traits", + "once_cell", + "tar", +] + +[[package]] +name = "feint-cli" +version = "0.0.0" +dependencies = [ "clap", "clap_complete", - "ctrlc", "dirs", "env_logger", - "flate2", - "indexmap", + "feint-builtins", + "feint-compiler", + "feint-driver", + "rustyline", +] + +[[package]] +name = "feint-code-gen" +version = "0.0.0" + +[[package]] +name = "feint-compiler" +version = "0.0.0" +dependencies = [ + "feint-builtins", + "feint-util", "log", "num-bigint", "num-traits", "once_cell", "regex", - "rustyline", - "tar", +] + +[[package]] +name = "feint-driver" +version = "0.0.0" +dependencies = [ + "feint-builtins", + "feint-code-gen", + "feint-compiler", + "feint-util", + "feint-vm", + "once_cell", +] + +[[package]] +name = "feint-util" +version = "0.0.0" + +[[package]] +name = "feint-vm" +version = "0.0.0" +dependencies = [ + "bitflags", + "ctrlc", + "env_logger", + "feint-builtins", + "feint-util", + "indexmap", + "log", + "num-traits", ] [[package]] @@ -370,10 +425,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ + "autocfg", "bitflags", "cfg-if", "libc", @@ -520,9 +576,9 @@ dependencies = [ [[package]] name = "rustyline" -version = "10.0.0" +version = "10.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1cd5ae51d3f7bf65d7969d579d502168ef578f289452bd8ccc91de28fda20e" +checksum = "c1e83c32c3f3c33b08496e0d1df9ea8c64d39adb8eb36a1ebb1440c690697aef" dependencies = [ "bitflags", "cfg-if", @@ -532,7 +588,7 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.24.3", + "nix 0.25.1", "radix_trie", "scopeguard", "unicode-segmentation", diff --git a/Cargo.toml b/Cargo.toml index 6479de7..2e2be1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,29 @@ -[package] -name = "feint" +[workspace] +resolver = "2" +members = [ + "feint-builtins", + "feint-cli", + "feint-code-gen", + "feint-compiler", + "feint-util", + "feint-driver", + "feint-vm", +] + +[workspace.package] +edition = "2021" version = "0.0.0" -authors = ["Wyatt Baldwin "] description = "Programming language, bytecode-esque compiler, and stack-based VM" -edition = "2021" +authors = ["Wyatt Baldwin "] readme = "README.md" license-file = "LICENSE" homepage = "https://github.com/feint-lang/" repository = "https://github.com/feint-lang/feint" -[dependencies] +[workspace.dependencies] bitflags = "~1.3.2" clap = { version = "~4.1.1", features = ["env"] } +clap_complete = "~4.1.1" ctrlc = "~3.2.4" dirs = "~4.0.0" env_logger = "~0.10.0" @@ -22,11 +34,5 @@ num-bigint = "~0.4.3" num-traits = "~0.2.15" once_cell = "1.17.0" regex = "~1.7.1" -rustyline = "~10.0.0" -tar = { version = "0.4.38", default-features = false } - -[build-dependencies] -clap = { version = "~4.1.1", features = ["env"] } -clap_complete = "4.1.0" -flate2 = { version = "1.0.25", features = ["zlib"], default-features = false } +rustyline = "~10.1.1" tar = { version = "0.4.38", default-features = false } diff --git a/README.md b/README.md index 3fea10b..5184c89 100644 --- a/README.md +++ b/README.md @@ -65,22 +65,26 @@ instructions for use with [neovim]. - Everything is an object of some type - Strong typing - Lexical scoping -- Everything is an expression or acts like an expression; every +- Everything is an expression or acts like an expression--every statement returns some value - Significant whitespace (by default, but maybe consider `{...}` blocks for certain special cases like passing functions) -- No this/self on methods but this/self is required to access attributes -- Disallow arbitrary attachment of attributes (???) -- Everything is immutable by default (???) - Implicit return of last evaluated expression (like Rust); this applies to *all* blocks/scopes -- Custom types can implement operators by defining methods such as `+` - Only booleans and `nil` can be used in boolean contexts by default; custom types can implement the `!!` operator +- Everything is immutable by default (idea: use `=` for `const` and `<-` + for `var`) +- No this/self in method definitions but this/self is required to access + attributes +- Disallow arbitrary attachment of attributes +- Custom types can implement operators by defining methods such as `+` +- No inheritance (???) ## Memory Management -TODO +The current implementation uses reference counting via Rust's `Arc`, but +memory can leak if there are cycles. ## Intrinsic Types @@ -100,21 +104,27 @@ TODO ## Vars Variables are defined without the use of any keywords, like Python or -Ruby. +Ruby. Vars can be reassigned within a given scope using `=`. Vars in +outer scopes can be reassigned using `<-`. ``` -a = true -b = nil - a = true block -> print(a) # outer a -a = true +b = true block -> - a = false - print(a) # block a -print(a) # outer a + # This creates a new `b` in the block. + b = false + +# After exiting the block, `b` is `true` + +c = 1 +block -> + # This reassigns the outer `c` + c <- 2 + +# After exiting the block, `c` is `2` ``` ## Type Hints @@ -133,8 +143,7 @@ f: Str = (s: Str, count: Int) => ## Format Strings -Similar to f-strings in Python. Sometimes called $-strings since they -use a `$` to mark strings as format strings. +Similar to $-strings in F# and f-strings in Python. ``` x = 1 @@ -154,7 +163,7 @@ block_val = block -> y = 2 x + y -block_val == 4 +assert(block_val == 4) ``` ## Scopes @@ -165,6 +174,8 @@ scope. Blocks are denoted by `->` and always return (so to speak) a value, which may be `nil`. +Function blocks are denoted by `=>` to resolve a parsing ambiguity. + ## Conditionals NOTE: By default, only booleans and `nil` can be used in boolean @@ -210,11 +221,13 @@ can be written more succinctly as: ``` x = "abc" + result = match x -> "a" -> 1 "ab" -> 2 "abc" -> 3 * -> 4 + print(result) # -> 3 ``` @@ -227,19 +240,21 @@ branch and no match is found, the `match` block will return `nil`. ## Pattern Matching Pattern matching can be done using the builtin `Always` singleton `@`. -`@` always evaluates as `true` can be compared for equality with +`@` always evaluates as `true`. It can be compared for equality with _anything_ and the comparison will _always_ be `true`. ``` r = match (1, 2) -> (1, @) -> true * -> false -print(r) # -> true + +assert(r == true) r = match (1, 2) -> (@, 2) -> true * -> false -print(r) # -> true + +assert(r == true) ``` ## Loops @@ -265,20 +280,14 @@ loop i <- 1...10 -> i # Loop until condition is met -# -# TODO: There's a bug in this example since `loop cond` checks the OUTER -# `cond`. This is due to how scopes work--the inner assignment -# creates a new scope-local var rather than reassigning the outer -# `cond`. A possible fix would be to add an `outer` keyword. +# +# NOTE the use of `cond <- false` rather than `cond = false`. This is +# necessary because `cond = false` would create a new var in the loop's +# scope such that the loop would never exit (since it checks the OUTER +# `cond`). cond = true loop cond -> - cond = false - -# Another fix for the above example would be to pull the outer cond into -# the loop's scope as a local variable: -cond = true -loop cond = cond -> - cond = false + cond <- false ``` ## Jumps @@ -286,7 +295,8 @@ loop cond = cond -> - Forward jumps support the jump-to-exit pattern - Backward jumps are disallowed (so no looping via goto) - Labels can only be placed at the beginning of a line or directly - after a scope start marker + after a scope start marker (although the latter isn't particularly + useful) - Labels are fenced by colons e.g. `:my_label:` (this differentiates labels from identifiers with type hints) - Labels can't be redefined in a scope @@ -326,7 +336,8 @@ my_func(() => nil) Functions can be defined with a var args parameter in the last position, which can be accessed in the body of the function as `$args`. `$args` -is always a tuple and will be empty if there are no var args. +is always defined, is always a tuple, and will be empty if there are no +var args. ``` # $main is special; $args is argv @@ -338,10 +349,7 @@ f = (x, y, ...) => print(x, y, $args) ### Closures Closures work pretty much like Python or JavaScript. One difference is -that names defined *after* a function won't be captured. Allowing this -seems a bit wonky in the first place and also adds a bit of complexity -for no apparent benefit (or, at least, I'm not sure what the benefit of -this capability is off the top of my head.) +that names defined *after* a function won't be captured. Here's an extremely artificial example: diff --git a/aliases b/aliases index 94d5382..f9eb135 100644 --- a/aliases +++ b/aliases @@ -1,5 +1,4 @@ set debug_binary ./target/debug/feint -set release_binary ./target/release/feint # build alias b="cargo build" @@ -27,9 +26,9 @@ alias d="cargo run -- --dis --debug" alias l="RUST_LOG=feint=trace cargo run --" # run release binary -alias rr="cargo build --release && $release_binary --" -alias cr="cargo build --release && $release_binary -- --code" -alias dr="cargo build --release && $release_binary -- --dis --debug" +alias rr="cargo run --release --" +alias cr="cargo run --release -- --code" +alias dr="cargo run --release -- --dis --debug" # profile alias p="cargo flamegraph --root --" diff --git a/examples/conditionals.fi b/examples/conditionals.fi index 6d12dc2..41c893c 100644 --- a/examples/conditionals.fi +++ b/examples/conditionals.fi @@ -41,13 +41,13 @@ else -> # Pattern matching obj = (1, 2) pat = @ -assert(obj == pat, (obj, pat), true) +assert_eq(obj, pat, true) pat = (1, @) -assert(obj == pat, (obj, pat), true) +assert_eq(obj, pat, true) pat = (@, 2) -assert(obj == pat, (obj, pat), true) +assert_eq(obj, pat, true) -# This just demonstrates that if/else suite at the end of the file +# This just demonstrates that an if/else suite at the end of the file # doesn't cause an error. if false -> print_err(nil) diff --git a/examples/mandlebrot.fi b/examples/mandlebrot.fi new file mode 100644 index 0000000..b8244a8 --- /dev/null +++ b/examples/mandlebrot.fi @@ -0,0 +1,80 @@ +# Ported from https://github.com/RustPython/RustPython/blob/main/benches/benchmarks/mandelbrot.py +# (not sure if it's that's the original source). +# +# Python runs this in ~200ms on my laptop. This version runs in ~425ms +# using a release build. +import std.args +import std.system + + +die = (code, ...) => + if $args.length -> + print_err($args.join(" ")) + system.exit(code) + + +$main = (...) => + "Mandlebrot + + # Args + + - w: Float + Width + + - h: Float + Height + + - print: Bool + Flag indicating whether to print output to terminal + + " + args = args.parse("mandlebrot", $args, $main.$doc, { + "args": ( + {"name": "w", "default": 20.0, "type": Float}, + {"name": "h", "default": 10.0, "type": Float}, + ), + "flags": ( + {"name": "print", "default": true}, + ) + }) + + if args.err -> + die(1) + + if args $$ nil -> + system.exit(0) + + w = args.get("w") ?? die(1, "Expected width to be a Float") + h = args.get("h") ?? die(1, "Expected height to be a Float") + do_print = args.get("print") + + y = 0.0 + + loop y < h -> + x = 0.0 + + loop x < w -> + zr = zi = tr = ti = 0.0 + cr = 2 * x / w - 1.5 + ci = 2 * y / h - 1.0 + i = 0 + + loop i < w && tr + ti <= 4 -> + zi <- 2 * zr * zi + ci + zr <- tr - ti + cr + tr <- zr * zr + ti <- zi * zi + i += 1 + + # NOTE: Using $print instead of print to avoid function call + # overhead in this hot loop. Additionally, print + # currently always adds a newline. + if do_print -> + $print(if tr + ti <= 4 -> "*" else -> ".",) + + x += 1 + + if do_print -> + $print("\n",) + + y += 1 diff --git a/examples/reassignment.fi b/examples/reassignment.fi new file mode 100644 index 0000000..96bdfa9 --- /dev/null +++ b/examples/reassignment.fi @@ -0,0 +1,47 @@ +# Reassignment using <- is allowed only to vars defined in an OUTER +# scope, EXCLUDING globals. + +x = 1 + +# This would throw a compilation error. +# x <- 2 + +# So would this. +# block -> +# x <- 2 + + +# This is okay though. The rationale for this is that it provides a way +# to dynamically initialize a global without leaking temporaries into +# the global scope. +y = block -> + a = true + loop a -> + a <- false + +assert_is(y, false, true) + + +f = () => + # This would throw throw a compilation error because x isn't defined + # in the function's scope. + # x <- 2 + ... + + +g = (x) => + # This would throw throw a compilation error because the parameter x + # is defined in the same scope. + # x <- 2 + + block -> + x <- 2 + + cond = true + loop cond -> + # NOTE: Using = instead of <- here would cause an infinite loop. + cond <- false + + +$main = (...) => + g(1) diff --git a/examples/seq.fi b/examples/seq.fi new file mode 100644 index 0000000..c4f56e9 --- /dev/null +++ b/examples/seq.fi @@ -0,0 +1,11 @@ +tuple = ("a", "b", "c") +result = [] +tuple.each((item, i) => result.push((i, item))) +expected = [(0, "a"), (1, "b"), (2, "c")] +assert_eq(result, expected, true) + +list = ["a", "b", "c"] +result = [] +list.each((item, i) => result.push((i, item))) +expected = [(0, "a"), (1, "b"), (2, "c")] +assert_eq(result, expected, true) diff --git a/feint-builtins/Cargo.toml b/feint-builtins/Cargo.toml new file mode 100644 index 0000000..8b1c961 --- /dev/null +++ b/feint-builtins/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "feint-builtins" +edition.workspace = true +version.workspace = true +description.workspace = true +authors.workspace = true +readme.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +feint-code-gen = { path = "../feint-code-gen" } +feint-util = { path = "../feint-util" } + +bitflags.workspace = true +flate2.workspace = true +indexmap.workspace = true +num-bigint.workspace = true +num-traits.workspace = true +once_cell.workspace = true +tar = { workspace = true, default-features = false } + +[build-dependencies] +flate2 = { workspace = true, features = ["zlib"], default-features = false } +tar = { workspace = true, default-features = false } diff --git a/build.rs b/feint-builtins/build.rs similarity index 85% rename from build.rs rename to feint-builtins/build.rs index c47747c..e7991f5 100644 --- a/build.rs +++ b/feint-builtins/build.rs @@ -5,12 +5,9 @@ use std::io::Error; use std::path::Path; use std::process; -use clap_complete::{self, shells}; use flate2::{Compression, GzBuilder}; use tar::Builder as TarBuilder; -include!("src/cli.rs"); - fn main() -> Result<(), Error> { let out_dir = match env::var_os("OUT_DIR") { Some(out_dir) => out_dir, @@ -25,7 +22,6 @@ fn main() -> Result<(), Error> { let out_dir = Path::new(&out_dir); stamp(out_dir)?; - make_shell_completion_scripts(out_dir)?; make_module_archive(out_dir)?; Ok(()) @@ -39,13 +35,6 @@ fn stamp(out_dir: &Path) -> Result<(), Error> { Ok(()) } -fn make_shell_completion_scripts(out_dir: &Path) -> Result<(), Error> { - let mut cmd = build_cli(); - clap_complete::generate_to(shells::Bash, &mut cmd, "feint", out_dir)?; - clap_complete::generate_to(shells::Fish, &mut cmd, "feint", out_dir)?; - Ok(()) -} - fn make_module_archive(out_dir: &Path) -> Result<(), Error> { let archive_path = out_dir.join("modules.tgz"); let archive_file = File::create(archive_path)?; diff --git a/feint-builtins/src/lib.rs b/feint-builtins/src/lib.rs new file mode 100644 index 0000000..20d2935 --- /dev/null +++ b/feint-builtins/src/lib.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate bitflags; + +pub mod modules; +pub mod new; +pub mod types; + +mod util; + +#[cfg(test)] +mod tests; diff --git a/feint-builtins/src/modules/mod.rs b/feint-builtins/src/modules/mod.rs new file mode 100644 index 0000000..ae8b25f --- /dev/null +++ b/feint-builtins/src/modules/mod.rs @@ -0,0 +1,80 @@ +use ::std::borrow::Cow; +use ::std::collections::HashMap; +use ::std::io::Read; +use ::std::path::Path; +use ::std::sync::{Arc, RwLock}; + +use flate2::read::GzDecoder; +use once_cell::sync::Lazy; +use tar::Archive as TarArchive; + +use feint_code_gen::{obj_ref, obj_ref_t}; + +use crate::types::map::Map; +use crate::types::{ObjectRef, ObjectTrait}; + +pub mod std; +pub use self::std::STD; + +/// This mirrors `system.modules`. It provides a way to access +/// modules in Rust code (e.g., in the VM). +pub static MODULES: Lazy = Lazy::new(|| obj_ref!(Map::default())); + +/// Add module to `system.modules`. +pub fn add_module(name: &str, module: ObjectRef) { + let modules = MODULES.write().unwrap(); + let modules = modules.down_to_map().unwrap(); + modules.insert(name, module); +} + +/// Get module from `system.modules`. +/// +/// XXX: Panics if the module doesn't exist (since that shouldn't be +/// possible). +pub fn get_module(name: &str) -> ObjectRef { + let modules = MODULES.read().unwrap(); + let modules = modules.down_to_map().unwrap(); + if let Some(module) = modules.get(name) { + module.clone() + } else { + panic!("Module not registered: {name}"); + } +} + +/// Get module from `system.modules`. +/// +/// XXX: This will return `None` if the module doesn't exist. Generally, +/// this should only be used during bootstrap. In most cases, +/// `get_module` should be used instead. +pub fn maybe_get_module(name: &str) -> Option { + let modules = MODULES.read().unwrap(); + let modules = modules.down_to_map().unwrap(); + modules.get(name) +} + +/// At build time, a compressed archive is created containing the +/// std .fi module files (see `build.rs`). +/// +/// At runtime, the module file data is read out and stored in a map +/// (lazily). When a std module is imported, the file data is read from +/// this map rather than reading from disk. +/// +/// The utility of this is that we don't need an install process that +/// copies the std module files into some location on the file system +/// based on the location of the current executable or anything like +/// that. +pub static STD_FI_MODULES: Lazy>> = Lazy::new(|| { + let archive_bytes: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/modules.tgz")); + let decoder = GzDecoder::new(archive_bytes); + let mut archive = TarArchive::new(decoder); + let mut modules = HashMap::new(); + for entry in archive.entries().unwrap() { + let mut entry = entry.unwrap(); + let path: Cow<'_, Path> = entry.path().unwrap(); + let path = path.to_str().unwrap().to_owned(); + let mut result = Vec::new(); + entry.read_to_end(&mut result).unwrap(); + modules.insert(path, result); + } + modules +}); diff --git a/src/modules/std/args.fi b/feint-builtins/src/modules/std/args.fi similarity index 78% rename from src/modules/std/args.fi rename to feint-builtins/src/modules/std/args.fi index 25458db..413f039 100644 --- a/src/modules/std/args.fi +++ b/feint-builtins/src/modules/std/args.fi @@ -18,9 +18,14 @@ parse: Map = ( # Returns - A Map containing args or errors. + A `Map` containing args or errors, unless help is requested, in + which case the program's usage will be printed and `nil` will be + returned. " + if argv.iter.err -> + return argv.iter + add_help = spec.get("add_help") ?? true arg_specs = spec.get("args") @@ -111,20 +116,43 @@ parse: Map = ( it = flag_specs.iter() loop (spec = it.next()) $! nil -> name = spec.get("name") + default = spec.get("default") ?? false val = match flags.get(name) -> true -> true false -> false - nil -> false + nil -> default result.add(name, val) if flags.get("help") -> print($"usage: {program_name}\n") print(description) + + if arg_specs $! nil -> + print("\n# Args\n") + it = arg_specs.iter() + loop (spec = it.next()) $! nil -> + print(spec.get("name")) + + if option_specs $! nil -> + print("\n# Options\n") + it = option_specs.iter() + loop (spec = it.next()) $! nil -> + print(spec.get("name")) + + if flag_specs $! nil -> + print("\n# Flags\n") + it = flag_specs.iter() + loop (spec = it.next()) != nil -> + print(spec.get("name")) + + nil else if errors.is_empty -> result else -> ess = if errors.length == 1 -> "" else -> "s" - print_err($"{errors.length} error{ess} encountered while parsing args:\n") + print_err( + $"{program_name}: {errors.length} error{ess} encountered while parsing args\n" + ) errors.each((name, err) => print_err($"{name}: {err.message}")) Err.new(ErrType.arg, "") diff --git a/feint-builtins/src/modules/std/list.fi b/feint-builtins/src/modules/std/list.fi new file mode 100644 index 0000000..26284f0 --- /dev/null +++ b/feint-builtins/src/modules/std/list.fi @@ -0,0 +1,82 @@ +# Method: List.each() +each = (fn: Func) => + "Apply function to each List item. + + # Args + + - fn: Func + A function that will be passed each item in turn and the index of + the item. + + # Returns + + If all calls to `fn` are successful, `nil` is returned. If a call is + unsuccessful, the `Err` from that call is returned. + + " + meth = List.each + + result = check_arg_type(meth, "this", this, (List,)) + if result.err -> + return result + + result = check_arg_type(meth, "fn", fn, (Func, IntrinsicFunc)) + if result.err -> + return result + + n_args = match fn.$arity -> + 0 -> return Err.new(ErrType.arg, $"fn of {meth} should take at least 1 arg") + 1 -> 1 + * -> 2 + + if this.is_empty -> + return nil + + i = 0 + loop i < this.length -> + result = if n_args == 1 -> fn(this.i) else -> fn(this.i, i) + if result.err -> + return result + i += 1 + + +# Method: List.map() +map: List = (fn: Func) => + "Apply function to each List item and collect results. + + # Args + + - fn: Func + A function that will be passed each item in turn and the index of + the item. + + # Returns + + A list containing the result of each call, which could be an `Err`. + + " + meth = List.map + + result = check_arg_type(meth, "this", this, (List,)) + if result.err -> + return result + + result = check_arg_type(meth, "fn", fn, (Func, IntrinsicFunc)) + if result.err -> + return result + + results = [] + + if this.is_empty -> + return results + + n_args = match fn.$arity -> + 0 -> return Err.new(ErrType.arg, $"fn of {meth} should take at least 1 arg") + 1 -> 1 + * -> 2 + + i = 0 + loop i < this.length -> + result = if n_args == 1 -> fn(this.i) else -> fn(this.i, i) + result.push(result) + i += 1 diff --git a/feint-builtins/src/modules/std/map.fi b/feint-builtins/src/modules/std/map.fi new file mode 100644 index 0000000..00d664e --- /dev/null +++ b/feint-builtins/src/modules/std/map.fi @@ -0,0 +1,56 @@ +# Method: Map.each() +each = (fn: Func) => + "Apply function to each Map item. + + # Args + + - fn: Func + A function that will be passed the key and value of each entry in + turn. An index is also passed. + + # Returns + + If all calls to `fn` are successful, `nil` is returned. If a call is + unsuccessful, the `Err` from that call is returned. + + ``` + → map = {'a': 'a', 'b': 'b'} + {'a' => 'a', 'b' => 'b'} + → fn = (k, v, i) => print($'{i + 1}. {k} = {v}') + function fn/2 @ + → map.each(fn) + 1. a = a + 2. b = b + ``` + + " + meth = Map.each + + result = check_arg_type(meth, "this", this, (Map,)) + if result.err -> + return result + + result = check_arg_type(meth, "fn", fn, (Func, IntrinsicFunc)) + if result.err -> + return result + + if this.is_empty -> + return nil + + n_args = match fn.$arity -> + 0 -> return Err.new(ErrType.arg, $"fn of {meth} should take at least 1 arg") + 1 -> 1 + 2 -> 2 + * -> 3 + + i = 0 + it = this.iter() + loop (entry = it.next()) $! nil -> + key = entry.0 + result = match n_args -> + 1 -> fn(key) + 2 -> fn(key, entry.1) + 3 -> fn(key, entry.1, i) + if result.err -> + return result + i += 1 diff --git a/src/modules/std/mod.rs b/feint-builtins/src/modules/std/mod.rs similarity index 53% rename from src/modules/std/mod.rs rename to feint-builtins/src/modules/std/mod.rs index a882de4..c8ef84e 100644 --- a/src/modules/std/mod.rs +++ b/feint-builtins/src/modules/std/mod.rs @@ -1,5 +1,3 @@ pub use self::std::STD; -pub use proc::PROC; -mod proc; mod std; diff --git a/feint-builtins/src/modules/std/std.fi b/feint-builtins/src/modules/std/std.fi new file mode 100644 index 0000000..e4e94ec --- /dev/null +++ b/feint-builtins/src/modules/std/std.fi @@ -0,0 +1,201 @@ +"std module (builtins)" + + +print = (...) => + "Print representation of zero or more objects to stdout. + + The objects will be converted to their string representations, + joined together with spaces, and printed out with a trailing + newline. + + # Args + + - objects?: Any[] + + " + # $print args: + # + # - object: Any + # A single object to print + # + # - stderr: Bool + # Print to stderr instead of stdout + # + # - nl: Bool + # Print a newline (which will cause a flush) + # + # - repr: Bool + # Print object repr rather than string (use Debug impl) + # + # - no_nil: Bool + # Don't print object if it's nil (only applicable when printing + # to stdout) + # + # NOTE: Though the syntax below looks call-like, $print is NOT a + # function. It's compiled to a PRINT instruction, and its + # operand MUST be a tuple (i.e., a trailing comma MUST be + # included when none of the flags are specified). + i = 0 + loop i < $args.length -> + $print ($args.i, false) + $print (" ", false) + i += 1 + $print ("\n", false) + + +print_err = (...) => + "Print representation of zero or more objects to stderr. + + The objects will be converted to their string representations, + joined together with spaces, and printed out with a trailing + newline. + + # Args + + - objects?: Any[] + + " + i = 0 + loop i < $args.length -> + $print ($args.i, true) + $print (" ", true) + i += 1 + $print ("\n", true) + + +type: Type = (obj: Any) => + "Get the type of an object." + obj.$type + + +id = (obj: Any) => + "Get the ID of an object." + print(obj.$id) + + +help = (obj: Any) => + "Print the docstring for an object." + doc = obj.$doc + + result = if doc.err -> + match doc.err -> + ErrType.attr_not_found -> $"Object doesn't have a docstring: {obj}" + * -> $"{doc.err}" + else -> + match doc -> + nil -> $"Object has a nil docstring: {obj}" + "" -> $"Object has an empty docstring: {obj}" + * -> obj.$doc + + print(result) + + +assert: Bool | Err = (condition: Bool, ...) => + "Check condition and return error if false. + + # Args + + - condition + - message?: Any + - halt?: Bool = false + + # Returns + + true: if the assertion succeeded + Err: if the assertion failed and `halt` is unset + + > NOTE: If `halt` is set, the program will exit immediately with an + > error code. + + " + if condition -> + true + else -> + msg = $args.get(0) + + err = match msg -> + nil -> Err.new(ErrType.assertion, "") + * -> Err.new(ErrType.assertion, Str.new(msg)) + + if $args.get(1) -> + print_err(err) + $halt 1 + + err + + +assert_eq: Bool | Err = (a: Any, b: Any, ...) => + "Check if items are equal and return error if not. + + # Args + + - a: Any + - b: Any + - halt?: Bool = false + + # Returns + + true: if the items are equal + Err: if the items are not equal and `halt` is unset + + > NOTE: If `halt` is set, the program will exit immediately with an + > error code. + + " + if a == b -> + true + else -> + err = Err.new(ErrType.assertion, $"{a} is not equal to {b}") + + if $args.get(0) -> + print_err(err) + $halt 1 + + err + + +assert_is: Bool | Err = (a: Any, b: Any, ...) => + "Check if items are the same object and return error if not. + + # Args + + - a: Any + - b: Any + - halt?: Bool = false + + # Returns + + true: if the items are the same object + Err: if the items are not the same and `halt` is unset + + > NOTE: If `halt` is set, the program will exit immediately with an + > error code. + + " + if a $$ b -> + true + else -> + err = Err.new(ErrType.assertion, $"{a} is not {b}") + + if $args.get(0) -> + print_err(err) + $halt 1 + + err + + +# NOTE: This is used for manual type-checking of function args, which +# isn't great. +check_arg_type = (func: Func, name: Str, arg: Any, expected_types: Tuple) => + i = 0 + loop i < expected_types.length -> + if arg.$type $$ expected_types.i -> + return nil + i += 1 + + err = Err.new( + ErrType.arg, + $"{name} of {func} expected type {expected_types.0.$name}; got {arg.$type.$name}", + ) + $debug err + err diff --git a/src/modules/std/std.rs b/feint-builtins/src/modules/std/std.rs similarity index 76% rename from src/modules/std/std.rs rename to feint-builtins/src/modules/std/std.rs index 0b52a28..70017af 100644 --- a/src/modules/std/std.rs +++ b/feint-builtins/src/modules/std/std.rs @@ -3,11 +3,12 @@ use std::sync::{Arc, RwLock}; use once_cell::sync::Lazy; -use crate::types::{self, gen, new}; -use crate::vm::RuntimeErr; +use feint_code_gen::{obj_ref_t, use_arg, use_arg_str}; -pub static STD: Lazy = Lazy::new(|| { - new::intrinsic_module( +use crate::{new, types}; + +pub static STD: Lazy = Lazy::new(|| { + new::module( "std", "", "std module (builtins)", @@ -39,19 +40,20 @@ pub static STD: Lazy = Lazy::new(|| { None, &["module", "name"], "Make a new custom type - + # Args - + - module: Module - name: Str - + ", - |_, args, _| { - let module = args[0].clone(); - let name_arg = gen::use_arg!(args, 1); - let name = gen::use_arg_str!(new_type, name, name_arg); + |_, args| { + let module_arg = use_arg!(args, 0); + let module = use_arg_str!(new_type, module_name, module_arg); + let name_arg = use_arg!(args, 1); + let name = use_arg_str!(new_type, name, name_arg); let class = new::custom_type(module, name); - Ok(class) + class }, ), ), diff --git a/src/modules/std/system.fi b/feint-builtins/src/modules/std/system.fi similarity index 100% rename from src/modules/std/system.fi rename to feint-builtins/src/modules/std/system.fi diff --git a/src/modules/std/test.fi b/feint-builtins/src/modules/std/test.fi similarity index 100% rename from src/modules/std/test.fi rename to feint-builtins/src/modules/std/test.fi diff --git a/feint-builtins/src/modules/std/tuple.fi b/feint-builtins/src/modules/std/tuple.fi new file mode 100644 index 0000000..9b52945 --- /dev/null +++ b/feint-builtins/src/modules/std/tuple.fi @@ -0,0 +1,82 @@ +# Method: Tuple.each() +each = (fn: Func) => + "Apply function to each Tuple item. + + # Args + + - fn: Func + A function that will be passed each item in turn and the index of + the item. + + # Returns + + If all calls to `fn` are successful, `nil` is returned. If a call is + unsuccessful, the `Err` from that call is returned. + + " + meth = Tuple.each + + result = check_arg_type(meth, "this", this, (Tuple,)) + if result.err -> + return result + + result = check_arg_type(meth, "fn", fn, (Func, IntrinsicFunc)) + if result.err -> + return result + + n_args = match fn.$arity -> + 0 -> return Err.new(ErrType.arg, $"fn of {meth} should take at least 1 arg") + 1 -> 1 + * -> 2 + + if this.is_empty -> + return nil + + i = 0 + loop i < this.length -> + result = if n_args == 1 -> fn(this.i) else -> fn(this.i, i) + if result.err -> + return result + i += 1 + + +# Method: Tuple.map() +map: List = (fn: Func) => + "Apply function to each Tuple item and collect results. + + # Args + + - fn: Func + A function that will be passed each item in turn and the index of + the item. + + # Returns + + A list containing the result of each call, which could be an `Err`. + + " + meth = Tuple.map + + result = check_arg_type(meth, "this", this, (Tuple,)) + if result.err -> + return result + + result = check_arg_type(meth, "fn", fn, (Func, IntrinsicFunc)) + if result.err -> + return result + + results = [] + + if this.is_empty -> + return results + + n_args = match fn.$arity -> + 0 -> return Err.new(ErrType.arg, $"fn of {meth} should take at least 1 arg") + 1 -> 1 + * -> 2 + + i = 0 + loop i < this.length -> + result = if n_args == 1 -> fn(this.i) else -> fn(this.i, i) + result.push(result) + i += 1 diff --git a/src/types/new.rs b/feint-builtins/src/new.rs similarity index 63% rename from src/types/new.rs rename to feint-builtins/src/new.rs index 024baee..89c4805 100644 --- a/src/types/new.rs +++ b/feint-builtins/src/new.rs @@ -9,65 +9,111 @@ use num_traits::{FromPrimitive, Num, Signed, ToPrimitive}; use indexmap::IndexMap; use once_cell::sync::Lazy; -use crate::util::format_doc; -use crate::vm::{globals, Code, RuntimeErr}; - -use super::base::{ObjectRef, ObjectTrait}; -use super::gen::{obj_ref, obj_ref_t, use_arg}; -use super::result::Params; - -use super::bound_func::BoundFunc; -use super::cell::Cell; -use super::closure::Closure; -use super::custom::{CustomObj, CustomType}; -use super::err::ErrObj; -use super::err_type::ErrKind; -use super::file::File; -use super::float::Float; -use super::func::Func; -use super::int::Int; -use super::intrinsic_func::{IntrinsicFn, IntrinsicFunc}; -use super::iterator::FIIterator; -use super::list::List; -use super::map::Map; -use super::module::Module; -use super::ns::Namespace; -use super::prop::Prop; -use super::str::Str; -use super::tuple::Tuple; +use feint_code_gen::{obj_ref, obj_ref_t}; +use feint_util::string::format_doc; + +use crate::types::{ObjectRef, ObjectTrait, Params}; + +use crate::types::always::Always; +use crate::types::bool::Bool; +use crate::types::bound_func::BoundFunc; +use crate::types::cell::Cell; +use crate::types::class::Type; +use crate::types::closure::Closure; +use crate::types::code::Code; +use crate::types::custom::CustomObj; +use crate::types::err::ErrObj; +use crate::types::err_type::ErrKind; +use crate::types::file::File; +use crate::types::float::Float; +use crate::types::func::Func; +use crate::types::int::Int; +use crate::types::intrinsic_func::{IntrinsicFn, IntrinsicFunc}; +use crate::types::iterator::FIIterator; +use crate::types::list::List; +use crate::types::map::Map; +use crate::types::module::Module; +use crate::types::nil::Nil; +use crate::types::ns::Namespace; +use crate::types::prop::Prop; +use crate::types::str::Str; +use crate::types::tuple::Tuple; // Global singletons --------------------------------------------------- +static NIL: Lazy = Lazy::new(|| obj_ref!(Nil::new())); +static TRUE: Lazy = Lazy::new(|| obj_ref!(Bool::new(true))); +static FALSE: Lazy = Lazy::new(|| obj_ref!(Bool::new(false))); +static ALWAYS: Lazy = Lazy::new(|| obj_ref!(Always::new())); + +static EMPTY_STR: Lazy = + Lazy::new(|| obj_ref!(Str::new("".to_owned()))); + +static NEWLINE: Lazy = + Lazy::new(|| obj_ref!(Str::new("\n".to_owned()))); + +static EMPTY_TUPLE: Lazy = + Lazy::new(|| obj_ref!(Tuple::new(vec![]))); + +static SHARED_INT_MAX: usize = 256; +static SHARED_INT_MAX_BIGINT: Lazy = Lazy::new(|| BigInt::from(SHARED_INT_MAX)); +static SHARED_INTS: Lazy> = Lazy::new(|| { + (0..=SHARED_INT_MAX).map(|i| obj_ref!(Int::new(BigInt::from(i)))).collect() +}); + #[inline] pub fn nil() -> ObjectRef { - globals::NIL.clone() + NIL.clone() } #[inline] pub fn bool(val: bool) -> ObjectRef { if val { - globals::TRUE.clone() + TRUE.clone() } else { - globals::FALSE.clone() + FALSE.clone() } } +#[inline] +pub fn always() -> ObjectRef { + ALWAYS.clone() +} + #[inline] pub fn empty_str() -> ObjectRef { - globals::EMPTY_STR.clone() + EMPTY_STR.clone() +} + +#[inline] +pub fn newline() -> ObjectRef { + NEWLINE.clone() } #[inline] pub fn empty_tuple() -> ObjectRef { - globals::EMPTY_TUPLE.clone() + EMPTY_TUPLE.clone() } -// Intrinsic type constructors --------------------------------- +// Modules ------------------------------------------------------------- -pub fn bound_func(func: ObjectRef, this: ObjectRef) -> ObjectRef { - obj_ref!(BoundFunc::new(func, this)) +pub fn module( + name: &str, + path: &str, + doc: &str, + entries: &[(&str, ObjectRef)], +) -> obj_ref_t!(Module) { + obj_ref!(Module::with_entries( + entries, + name.to_owned(), + path.to_owned(), + Code::default(), + Some(doc.to_owned()) + )) } +// Function------------------------------------------------------------- + pub fn intrinsic_func( module_name: &str, name: &str, @@ -88,19 +134,21 @@ pub fn intrinsic_func( )) } -pub fn intrinsic_module( - name: &str, - path: &str, - doc: &str, - entries: &[(&str, ObjectRef)], -) -> obj_ref_t!(Module) { - obj_ref!(Module::with_entries( - entries, - name.to_owned(), - path.to_owned(), - Code::default(), - Some(doc.to_owned()) - )) +pub fn func>( + module_name: S, + func_name: S, + params: Params, + code: Code, +) -> ObjectRef { + obj_ref!(Func::new(module_name.into(), func_name.into(), params, code)) +} + +pub fn bound_func(func: ObjectRef, this: ObjectRef) -> ObjectRef { + obj_ref!(BoundFunc::new(func, this)) +} + +pub fn closure(func: ObjectRef, captured: ObjectRef) -> ObjectRef { + obj_ref!(Closure::new(func, captured)) } pub fn cell() -> ObjectRef { @@ -111,8 +159,8 @@ pub fn cell_with_value(value: ObjectRef) -> ObjectRef { obj_ref!(Cell::with_value(value)) } -pub fn closure(func: ObjectRef, captured: ObjectRef) -> ObjectRef { - obj_ref!(Closure::new(func, captured)) +pub fn argv_tuple(argv: &[String]) -> ObjectRef { + obj_ref!(Tuple::new(argv.iter().map(str).collect())) } // Errors -------------------------------------------------------------- @@ -153,6 +201,10 @@ pub fn index_out_of_bounds_err(index: usize, obj: ObjectRef) -> ObjectRef { err(ErrKind::IndexOutOfBounds, index.to_string(), obj) } +pub fn not_callable_err(obj: ObjectRef) -> ObjectRef { + err(ErrKind::NotCallable, format!("{}", obj.read().unwrap()), obj) +} + pub fn string_err>(msg: S, obj: ObjectRef) -> ObjectRef { err(ErrKind::String, msg, obj) } @@ -179,26 +231,20 @@ pub fn float(value: f64) -> ObjectRef { obj_ref!(Float::new(value)) } -pub fn float_from_string>(value: S) -> ObjectRef { - let value = value.into(); - let value = value.parse::().unwrap(); - float(value) -} - -pub fn func>( - module_name: S, - func_name: S, - params: Params, - code: Code, -) -> ObjectRef { - obj_ref!(Func::new(module_name.into(), func_name.into(), params, code)) +pub fn float_from_string>(val: S) -> ObjectRef { + let val = val.into(); + if let Ok(val) = val.parse::() { + float(val) + } else { + type_err("Could not convert string to Float", str(val)) + } } pub fn int>(value: I) -> ObjectRef { let value = value.into(); - if value.is_positive() && &value <= Lazy::force(&globals::SHARED_INT_MAX_BIGINT) { + if value.is_positive() && &value <= Lazy::force(&SHARED_INT_MAX_BIGINT) { let index = value.to_usize().unwrap(); - globals::SHARED_INTS[index].clone() + SHARED_INTS[index].clone() } else { obj_ref!(Int::new(value)) } @@ -239,9 +285,9 @@ pub fn prop(getter: ObjectRef) -> ObjectRef { pub fn str>(val: S) -> ObjectRef { let val = val.into(); if val.is_empty() { - globals::EMPTY_STR.clone() + EMPTY_STR.clone() } else if val == "\n" { - globals::NEWLINE.clone() + NEWLINE.clone() } else { obj_ref!(Str::new(val)) } @@ -249,20 +295,16 @@ pub fn str>(val: S) -> ObjectRef { pub fn tuple(items: Vec) -> ObjectRef { if items.is_empty() { - globals::EMPTY_TUPLE.clone() + EMPTY_TUPLE.clone() } else { obj_ref!(Tuple::new(items)) } } -pub fn argv_tuple(argv: &[String]) -> ObjectRef { - obj_ref!(Tuple::new(argv.iter().map(str).collect())) -} - // Custom type constructor --------------------------------------------- -pub fn custom_type(module: ObjectRef, name: &str) -> ObjectRef { - let class_ref = obj_ref!(CustomType::new(module.clone(), name.to_owned())); +pub fn custom_type(module_name: &str, name: &str) -> ObjectRef { + let class_ref = obj_ref!(Type::new(module_name, name)); { let mut class = class_ref.write().unwrap(); @@ -270,7 +312,7 @@ pub fn custom_type(module: ObjectRef, name: &str) -> ObjectRef { ns.insert( "new", intrinsic_func( - module.read().unwrap().down_to_mod().unwrap().name(), + module_name, name, Some(class_ref.clone()), &["attrs"], @@ -278,37 +320,20 @@ pub fn custom_type(module: ObjectRef, name: &str) -> ObjectRef { # Args - - class: TypeRef + - - type_obj: ObjectRef - attributes: Map ", - |this, args, _| { - let attrs_arg = use_arg!(args, 0); + |this_ref, args| { + let type_obj = this_ref.read().unwrap(); + let attrs_arg = args.get(0).unwrap(); + let attrs_arg = attrs_arg.read().unwrap(); let attrs = attrs_arg.down_to_map().unwrap(); - let mut ns = Namespace::default(); - ns.extend_from_map(attrs); - - // XXX: Cloning the inner object is wonky and breaks - // identity testing. - let type_obj = this.read().unwrap(); - let type_obj = if type_obj.is_type_object() { - // Called via custom type. - let type_obj = type_obj.down_to_custom_type().unwrap(); - obj_ref!(type_obj.clone()) - } else { - // Called via custom instance. - // XXX: This branch isn't reachable because the - // VM will panic due to the identity test - // issue noted above. - let type_obj = type_obj.type_obj(); - let type_obj = type_obj.read().unwrap(); - let type_obj = type_obj.down_to_custom_type().unwrap(); - obj_ref!(type_obj.clone()) - }; - - let instance = CustomObj::new(type_obj, ns); - Ok(obj_ref!(instance)) + obj_ref!(CustomObj::new( + type_obj.class(), + Namespace::from_map(attrs) + )) }, ), ); diff --git a/feint-builtins/src/tests/mod.rs b/feint-builtins/src/tests/mod.rs new file mode 100644 index 0000000..6f8b3de --- /dev/null +++ b/feint-builtins/src/tests/mod.rs @@ -0,0 +1 @@ +mod types; diff --git a/src/tests/types.rs b/feint-builtins/src/tests/types.rs similarity index 86% rename from src/tests/types.rs rename to feint-builtins/src/tests/types.rs index fc2129e..aec0296 100644 --- a/src/tests/types.rs +++ b/feint-builtins/src/tests/types.rs @@ -1,4 +1,5 @@ -use crate::types::{new, ObjectRef}; +use crate::new; +use crate::types::{ObjectRef, ObjectTrait}; fn check_ok>(obj: ObjectRef, msg: S) { assert!(!obj.read().unwrap().is_err(), "{}", msg.into()) @@ -105,17 +106,12 @@ mod list { } mod custom { + use crate::modules::add_module; use indexmap::IndexMap; - use crate::vm::{RuntimeObjResult, VM}; - use super::*; - fn instance( - type_obj: ObjectRef, - attrs: &[(&str, ObjectRef)], - vm: &mut VM, - ) -> RuntimeObjResult { + fn instance(type_obj: ObjectRef, attrs: &[(&str, ObjectRef)]) -> ObjectRef { let attrs = new::map(IndexMap::from_iter( attrs.into_iter().map(|(n, v)| (n.to_string(), v.clone())), )); @@ -123,22 +119,22 @@ mod custom { let new = new.read().unwrap(); let new = new.down_to_intrinsic_func().unwrap(); let new = new.func(); - new(type_obj.clone(), vec![attrs], vm) + new(type_obj.clone(), vec![attrs]) } #[test] fn test_custom() { - let vm = &mut VM::default(); - - let mod1 = new::intrinsic_module("test1", "", "test module 1", &[]); - let t1 = new::custom_type(mod1, "Custom1"); - let t1_obj1 = instance(t1.clone(), &[("value", new::nil())], vm).unwrap(); - let t1_obj2 = instance(t1.clone(), &[("value", new::nil())], vm).unwrap(); - let t1_obj3 = instance(t1.clone(), &[("value", new::nil())], vm).unwrap(); - - let mod2 = new::intrinsic_module("test2", "", "test module 2", &[]); - let t2 = new::custom_type(mod2, "Custom2"); - let t2_obj1 = instance(t2.clone(), &[], vm).unwrap(); + let mod1 = new::module("test1", "", "test module 1", &[]); + add_module("test1", mod1); + let t1 = new::custom_type("test1", "Custom1"); + let t1_obj1 = instance(t1.clone(), &[("value", new::nil())]); + let t1_obj2 = instance(t1.clone(), &[("value", new::nil())]); + let t1_obj3 = instance(t1.clone(), &[("value", new::nil())]); + + let mod2 = new::module("test2", "", "test module 2", &[]); + add_module("test2", mod2); + let t2 = new::custom_type("test2", "Custom2"); + let t2_obj1 = instance(t2.clone(), &[]); check_attr(t1.clone(), "$id"); check_attr(t1.clone(), "$type"); diff --git a/src/types/always.rs b/feint-builtins/src/types/always.rs similarity index 52% rename from src/types/always.rs rename to feint-builtins/src/types/always.rs index de96924..01b22db 100644 --- a/src/types/always.rs +++ b/feint-builtins/src/types/always.rs @@ -1,48 +1,38 @@ +//! Builtin `Always` type. use std::any::Any; use std::fmt; use std::sync::{Arc, RwLock}; use once_cell::sync::Lazy; -use super::gen; -use super::new; +use feint_code_gen::*; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use super::base::{ObjectTrait, TypeRef}; +use super::class::Type; use super::ns::Namespace; -// AlwaysType Type ----------------------------------------------------- - -gen::type_and_impls!(AlwaysType, Always); - -pub static ALWAYS_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(AlwaysType::new())); - -// Always Object ------------------------------------------------------- +std_type!(ALWAYS_TYPE, Always); pub struct Always { ns: Namespace, } -gen::standard_object_impls!(Always); +standard_object_impls!(Always); impl Always { - #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { ns: Namespace::default() } } } impl ObjectTrait for Always { - gen::object_trait_header!(ALWAYS_TYPE); + object_trait_header!(ALWAYS_TYPE); fn is_equal(&self, _rhs: &dyn ObjectTrait) -> bool { true } } -// Display ------------------------------------------------------------- - impl fmt::Display for Always { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "@") diff --git a/src/types/base.rs b/feint-builtins/src/types/base.rs similarity index 55% rename from src/types/base.rs rename to feint-builtins/src/types/base.rs index f05dcb1..fb90fc2 100644 --- a/src/types/base.rs +++ b/feint-builtins/src/types/base.rs @@ -6,64 +6,39 @@ use std::sync::{Arc, RwLock}; use num_bigint::BigInt; use num_traits::ToPrimitive; -use crate::dis::Disassembler; -use crate::modules::std::STD; -use crate::types::FuncTrait; -use crate::vm::{RuntimeBoolResult, RuntimeErr, RuntimeObjResult}; +use feint_code_gen::*; -use super::gen; -use super::new; -use super::ns::Namespace; - -use super::always::{Always, AlwaysType}; -use super::bool::{Bool, BoolType}; -use super::bound_func::{BoundFunc, BoundFuncType}; -use super::cell::{Cell, CellType}; -use super::class::{Type, TypeType}; -use super::closure::{Closure, ClosureType}; -use super::custom::{CustomObj, CustomType}; -use super::err::{ErrObj, ErrType}; -use super::err_type::{ErrTypeObj, ErrTypeType}; -use super::file::{File, FileType}; -use super::float::{Float, FloatType}; -use super::func::{Func, FuncType}; -use super::int::{Int, IntType}; -use super::intrinsic_func::{IntrinsicFunc, IntrinsicFuncType}; -use super::iterator::{FIIterator, IteratorType}; -use super::list::{List, ListType}; -use super::map::{Map, MapType}; -use super::module::{Module, ModuleType}; -use super::nil::{Nil, NilType}; -use super::prop::{Prop, PropType}; -use super::str::{Str, StrType}; -use super::tuple::{Tuple, TupleType}; - -pub type TypeRef = gen::obj_ref_t!(dyn TypeTrait); -pub type ObjectRef = gen::obj_ref_t!(dyn ObjectTrait); - -// Type Trait ---------------------------------------------------------- - -/// Types in the system are backed by an implementation of `TypeTrait`. -/// Each type implementation will be instantiated exactly once (i.e., -/// types are singletons). Example: `IntType`. -pub trait TypeTrait { - fn name(&self) -> &str; - fn full_name(&self) -> &str; - fn ns(&self) -> &Namespace; - - fn module(&self) -> ObjectRef { - STD.clone() - } +use crate::modules::get_module; +use crate::new; - fn id(&self) -> usize { - let p = self as *const Self; - p as *const () as usize - } +use super::func_trait::FuncTrait; +use super::ns::Namespace; - fn is(&self, other: &dyn TypeTrait) -> bool { - self.id() == other.id() - } -} +use super::always::Always; +use super::bool::Bool; +use super::bound_func::BoundFunc; +use super::cell::Cell; +use super::class::Type; +use super::closure::Closure; +use super::custom::CustomObj; +use super::err::ErrObj; +use super::err_type::ErrTypeObj; +use super::file::File; +use super::float::Float; +use super::func::Func; +use super::int::Int; +use super::intrinsic_func::IntrinsicFunc; +use super::iterator::FIIterator; +use super::list::List; +use super::map::Map; +use super::module::Module; +use super::nil::Nil; +use super::prop::Prop; +use super::str::Str; +use super::tuple::Tuple; + +pub type TypeRef = obj_ref_t!(Type); +pub type ObjectRef = obj_ref_t!(dyn ObjectTrait); // Object Trait -------------------------------------------------------- @@ -108,28 +83,18 @@ macro_rules! make_value_extractor { /// Create associated unary op function. macro_rules! make_unary_op { - ( $meth:ident, $op:literal, $result:ty ) => { - fn $meth(&self) -> $result { - Err(RuntimeErr::type_err(format!( - "Unary operator {} ({}) not implemented for {}", - $op, - stringify!($meth), - self.type_obj().read().unwrap() - ))) + ( $meth:ident, $op:literal, $ty:ty ) => { + fn $meth(&self) -> Option<$ty> { + None } }; } /// Create associated binary op function. macro_rules! make_bin_op { - ( $func:ident, $op:literal, $result:ty ) => { - fn $func(&self, _rhs: &dyn ObjectTrait) -> $result { - Err(RuntimeErr::type_err(format!( - "Binary operator {} ({}) not implemented for {}", - $op, - stringify!($func), - self.type_obj().read().unwrap() - ))) + ( $func:ident, $op:literal, $ty:ty ) => { + fn $func(&self, _rhs: &dyn ObjectTrait) -> Option<$ty> { + None } }; } @@ -139,22 +104,12 @@ macro_rules! make_bin_op { pub trait ObjectTrait { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; - - /// Get an instance's type as a type. This is needed to retrieve - /// type level attributes. fn class(&self) -> TypeRef; - /// Get an instance's type as an object. This is needed so the type - /// can be used in object contexts. - fn type_obj(&self) -> ObjectRef; - /// Each object has a namespace that holds its attributes. fn ns(&self) -> &Namespace; fn ns_mut(&mut self) -> &mut Namespace; - /// Cast object to type, if possible. - fn as_type(&self) -> Option<&dyn TypeTrait>; - fn id(&self) -> usize { let p = self as *const Self; p as *const () as usize @@ -165,11 +120,14 @@ pub trait ObjectTrait { new::int(self.id()) } - /// XXX: This resolves to `std` unless overridden. fn module(&self) -> ObjectRef { - let class = self.class(); - let class = class.read().unwrap(); - class.module().clone() + if let Some(t) = self.down_to_type() { + get_module(t.module_name()) + } else { + let class = self.class(); + let class = class.read().unwrap(); + class.module() + } } // Attributes (accessed by name) ----------------------------------- @@ -186,6 +144,10 @@ pub trait ObjectTrait { /// TODO: There's probably a more elegant way to do this, but it /// might require a bit of re-architecting. fn get_attr(&self, name: &str, this: ObjectRef) -> ObjectRef { + self.base_get_attr(name, this) + } + + fn base_get_attr(&self, name: &str, this: ObjectRef) -> ObjectRef { // Special attributes that *cannot* be overridden -------------- if name == "$id" { return self.id_obj(); @@ -196,7 +158,7 @@ pub trait ObjectTrait { } if name == "$type" { - return self.type_obj(); + return self.class(); } if name == "$names" { @@ -213,38 +175,39 @@ pub trait ObjectTrait { return new::tuple(items); } - if name == "$dis" { - // User functions, bound functions wrapping user functions, - // and closures wrapping user functions can be disassembled. - if let Some(f) = self.down_to_func() { - let mut dis = Disassembler::new(); - dis.disassemble(f.code()); - } else if let Some(b) = self.down_to_bound_func() { - let f = b.func(); - let f = f.read().unwrap(); - if let Some(f) = f.down_to_func() { - let mut dis = Disassembler::new(); - dis.disassemble(f.code()); - } else { - eprintln!("Cannot disassemble bound func: {}", b); - } - } else if let Some(c) = self.down_to_closure() { - let f = c.func(); - let f = f.read().unwrap(); - if let Some(f) = f.down_to_func() { - let mut dis = Disassembler::new(); - dis.disassemble(f.code()); - } else { - eprintln!("Cannot disassemble closure: {}", c); - } - } else if let Some(m) = self.down_to_mod() { - let mut dis = Disassembler::new(); - dis.disassemble(m.code()); - } else { - eprintln!("Cannot disassemble object: {}", &*this.read().unwrap()); - } - return new::nil(); - } + // TODO: Convert to builtin function + // if name == "$dis" { + // // User functions, bound functions wrapping user functions, + // // and closures wrapping user functions can be disassembled. + // if let Some(f) = self.down_to_func() { + // let mut dis = Disassembler::new(); + // dis.disassemble(f.code()); + // } else if let Some(b) = self.down_to_bound_func() { + // let f = b.func(); + // let f = f.read().unwrap(); + // if let Some(f) = f.down_to_func() { + // let mut dis = Disassembler::new(); + // dis.disassemble(f.code()); + // } else { + // eprintln!("Cannot disassemble bound func: {}", b); + // } + // } else if let Some(c) = self.down_to_closure() { + // let f = c.func(); + // let f = f.read().unwrap(); + // if let Some(f) = f.down_to_func() { + // let mut dis = Disassembler::new(); + // dis.disassemble(f.code()); + // } else { + // eprintln!("Cannot disassemble closure: {}", c); + // } + // } else if let Some(m) = self.down_to_mod() { + // let mut dis = Disassembler::new(); + // dis.disassemble(m.code()); + // } else { + // eprintln!("Cannot disassemble object: {}", &*this.read().unwrap()); + // } + // return new::nil(); + // } // Instance attributes ----------------------------------------- // @@ -254,7 +217,7 @@ pub trait ObjectTrait { return obj; } - if let Some(obj) = self.type_obj().read().unwrap().ns().get(name) { + if let Some(obj) = self.class().read().unwrap().ns().get(name) { return obj; } @@ -306,7 +269,7 @@ pub trait ObjectTrait { }; } - self.attr_not_found(name, this) + new::attr_not_found_err(name, this) } /// Set attribute. @@ -324,11 +287,7 @@ pub trait ObjectTrait { this: ObjectRef, ) -> ObjectRef { // TODO: The default should be a "does not support" attr access - self.attr_not_found(name, this) - } - - fn attr_not_found(&self, name: &str, obj: ObjectRef) -> ObjectRef { - new::attr_not_found_err(name, obj) + new::attr_not_found_err(name, this) } // Items (accessed by index) --------------------------------------- @@ -354,28 +313,6 @@ pub trait ObjectTrait { // Type checkers --------------------------------------------------- - make_type_checker!(is_type_type, TypeType); - make_type_checker!(is_always_type, AlwaysType); - make_type_checker!(is_bool_type, BoolType); - make_type_checker!(is_bound_func_type, BoundFuncType); - make_type_checker!(is_intrinsic_func_type, IntrinsicFuncType); - make_type_checker!(is_cell_type, CellType); - make_type_checker!(is_closure_type, ClosureType); - make_type_checker!(is_err_type, ErrType); - make_type_checker!(is_err_type_type, ErrTypeType); - make_type_checker!(is_file_type, FileType); - make_type_checker!(is_float_type, FloatType); - make_type_checker!(is_func_type, FuncType); - make_type_checker!(is_int_type, IntType); - make_type_checker!(is_iterator_type, IteratorType); - make_type_checker!(is_list_type, ListType); - make_type_checker!(is_map_type, MapType); - make_type_checker!(is_mod_type, ModuleType); - make_type_checker!(is_nil_type, NilType); - make_type_checker!(is_prop_type, PropType); - make_type_checker!(is_str_type, StrType); - make_type_checker!(is_tuple_type, TupleType); - make_type_checker!(is_type, Type); make_type_checker!(is_always, Always); make_type_checker!(is_bool, Bool); @@ -398,11 +335,6 @@ pub trait ObjectTrait { make_type_checker!(is_str, Str); make_type_checker!(is_tuple, Tuple); - /// Is this object a type object? - fn is_type_object(&self) -> bool { - self.type_obj().read().unwrap().is_type_type() - } - fn is_immutable(&self) -> bool { !(self.is_cell() || self.is_file() || self.is_list() || self.is_map()) } @@ -415,29 +347,6 @@ pub trait ObjectTrait { // // These downcast object refs to their concrete types. - make_down_to!(down_to_type_type, TypeType); - make_down_to!(down_to_always_type, AlwaysType); - make_down_to!(down_to_bool_type, BoolType); - make_down_to!(down_to_bound_func_type, BoundFuncType); - make_down_to!(down_to_intrinsic_func_type, IntrinsicFuncType); - make_down_to!(down_to_cell_type, CellType); - make_down_to!(down_to_closure_type, ClosureType); - make_down_to!(down_to_custom_type, CustomType); - make_down_to!(down_to_err_type, ErrType); - make_down_to!(down_to_err_type_type, ErrTypeType); - make_down_to!(down_to_file_type, FileType); - make_down_to!(down_to_float_type, FloatType); - make_down_to!(down_to_func_type, FuncType); - make_down_to!(down_to_list_type, ListType); - make_down_to!(down_to_int_type, IntType); - make_down_to!(down_to_iterator_type, IteratorType); - make_down_to!(down_to_map_type, MapType); - make_down_to!(down_to_mod_type, ModuleType); - make_down_to!(down_to_nil_type, NilType); - make_down_to!(down_to_prop_type, PropType); - make_down_to!(down_to_str_type, StrType); - make_down_to!(down_to_tuple_type, TupleType); - make_down_to!(down_to_type, Type); make_down_to!(down_to_always, Always); make_down_to!(down_to_bool, Bool); @@ -507,14 +416,14 @@ pub trait ObjectTrait { // Unary operations ------------------------------------------------ - make_unary_op!(negate, "-", RuntimeObjResult); - make_unary_op!(bool_val, "!!", RuntimeBoolResult); + make_unary_op!(negate, "-", ObjectRef); + make_unary_op!(bool_val, "!!", bool); - fn not(&self) -> RuntimeBoolResult { + fn not(&self) -> Option { match self.bool_val() { - Ok(true) => Ok(false), - Ok(false) => Ok(true), - err => err, + Some(true) => Some(false), + Some(false) => Some(true), + None => None, } } @@ -530,55 +439,33 @@ pub trait ObjectTrait { if self.is(rhs) { return true; } - let t = self.type_obj(); + let t = self.class(); let t = t.read().unwrap(); - let u = rhs.type_obj(); + let u = rhs.class(); let u = u.read().unwrap(); t.is(&*u) && self.is_equal(rhs) } fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { - self.is(rhs) || rhs.is_always() + self.is(rhs) || self.is_always() || rhs.is_always() } - make_bin_op!(and, "&&", RuntimeBoolResult); - make_bin_op!(or, "||", RuntimeBoolResult); - make_bin_op!(less_than, "<", RuntimeBoolResult); - make_bin_op!(greater_than, ">", RuntimeBoolResult); - - make_bin_op!(pow, "^", RuntimeObjResult); - make_bin_op!(modulo, "%", RuntimeObjResult); - make_bin_op!(mul, "*", RuntimeObjResult); - make_bin_op!(div, "/", RuntimeObjResult); - make_bin_op!(floor_div, "//", RuntimeObjResult); - make_bin_op!(add, "+", RuntimeObjResult); - make_bin_op!(sub, "-", RuntimeObjResult); - - // Call ------------------------------------------------------------ - - fn not_callable(&self) -> RuntimeErr { - RuntimeErr::not_callable(self.class().read().unwrap().full_name()) - } + make_bin_op!(and, "&&", bool); + make_bin_op!(or, "||", bool); + make_bin_op!(less_than, "<", bool); + make_bin_op!(greater_than, ">", bool); + + make_bin_op!(pow, "^", ObjectRef); + make_bin_op!(modulo, "%", ObjectRef); + make_bin_op!(mul, "*", ObjectRef); + make_bin_op!(div, "/", ObjectRef); + make_bin_op!(floor_div, "//", ObjectRef); + make_bin_op!(add, "+", ObjectRef); + make_bin_op!(sub, "-", ObjectRef); } // Display ------------------------------------------------------------- -macro_rules! write_type_instance { - ( $f:ident, $t:ident, $($A:ty),+ ) => { $( - if let Some(t) = $t.as_any().downcast_ref::<$A>() { - return write!($f, "", t.full_name()); - } - )+ }; -} - -macro_rules! debug_type_instance { - ( $f:ident, $t:ident, $($A:ty),+ ) => { $( - if let Some(t) = $t.as_any().downcast_ref::<$A>() { - return write!($f, "", t.full_name(), ObjectTrait::id(t)); - } - )+ }; -} - macro_rules! write_instance { ( $f:ident, $i:ident, $($A:ty),+ ) => { $( if let Some(i) = $i.as_any().downcast_ref::<$A>() { @@ -595,46 +482,8 @@ macro_rules! debug_instance { )+ }; } -impl fmt::Display for dyn TypeTrait { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "", self.full_name()) - } -} - -impl fmt::Debug for dyn TypeTrait { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "", self.full_name(), self.id()) - } -} - impl fmt::Display for dyn ObjectTrait { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write_type_instance!( - f, - self, - TypeType, - AlwaysType, - BoolType, - BoundFuncType, - IntrinsicFuncType, - CellType, - ClosureType, - CustomType, - ErrType, - ErrTypeType, - FileType, - FloatType, - FuncType, - IntType, - IteratorType, - ListType, - MapType, - ModuleType, - NilType, - PropType, - StrType, - TupleType - ); write_instance!( f, self, @@ -667,32 +516,6 @@ impl fmt::Display for dyn ObjectTrait { impl fmt::Debug for dyn ObjectTrait { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - debug_type_instance!( - f, - self, - TypeType, - AlwaysType, - BoolType, - BoundFuncType, - IntrinsicFuncType, - CellType, - ClosureType, - CustomType, - ErrType, - ErrTypeType, - FileType, - FloatType, - FuncType, - IntType, - IteratorType, - ListType, - MapType, - ModuleType, - NilType, - PropType, - StrType, - TupleType - ); debug_instance!( f, self, diff --git a/feint-builtins/src/types/bool.rs b/feint-builtins/src/types/bool.rs new file mode 100644 index 0000000..a6666ac --- /dev/null +++ b/feint-builtins/src/types/bool.rs @@ -0,0 +1,81 @@ +//! Builtin `Bool` type. +use std::any::Any; +use std::fmt; +use std::sync::{Arc, RwLock}; + +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use super::base::{ObjectTrait, TypeRef}; +use super::class::Type; +use super::ns::Namespace; + +std_type!(BOOL_TYPE, Bool); + +pub struct Bool { + ns: Namespace, + value: bool, +} + +standard_object_impls!(Bool); + +impl Bool { + pub fn new(value: bool) -> Self { + Self { ns: Namespace::default(), value } + } + + pub fn value(&self) -> &bool { + &self.value + } +} + +impl ObjectTrait for Bool { + object_trait_header!(BOOL_TYPE); + + // Unary operations ----------------------------------------------- + + fn bool_val(&self) -> Option { + Some(*self.value()) + } + + // Binary operations ----------------------------------------------- + + fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { + if self.is(rhs) || rhs.is_always() { + true + } else if let Some(rhs) = rhs.down_to_bool() { + self.value() == rhs.value() + } else { + false + } + } + + fn and(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_bool() { + Some(*self.value() && *rhs.value()) + } else { + None + } + } + + fn or(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_bool() { + Some(*self.value() || *rhs.value()) + } else { + None + } + } +} + +impl fmt::Display for Bool { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.value) + } +} + +impl fmt::Debug for Bool { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} diff --git a/src/types/bound_func.rs b/feint-builtins/src/types/bound_func.rs similarity index 51% rename from src/types/bound_func.rs rename to feint-builtins/src/types/bound_func.rs index 3c8bed4..50d6a53 100644 --- a/src/types/bound_func.rs +++ b/feint-builtins/src/types/bound_func.rs @@ -1,26 +1,21 @@ +//! Builtin `BoundFunc` type. use std::any::Any; use std::fmt; use std::sync::{Arc, RwLock}; use once_cell::sync::Lazy; -use super::gen; -use super::new; +use feint_code_gen::*; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; use super::func_trait::FuncTrait; use super::ns::Namespace; -use super::result::Params; - -// Bound Function Type ------------------------------------------------- - -gen::type_and_impls!(BoundFuncType, BoundFunc); - -pub static BOUND_FUNC_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(BoundFuncType::new())); +use super::Params; -// BoundFunc Object ---------------------------------------------------------- +std_type!(BOUND_FUNC_TYPE, BoundFunc); pub struct BoundFunc { ns: Namespace, @@ -31,38 +26,47 @@ pub struct BoundFunc { params: Params, } -gen::standard_object_impls!(BoundFunc); +standard_object_impls!(BoundFunc); impl BoundFunc { - pub fn new(func: ObjectRef, this: ObjectRef) -> Self { - let f = func.read().unwrap(); - - let (module_name, name, params, doc) = - if let Some(f) = f.down_to_intrinsic_func() { - (f.module_name(), f.name(), f.params(), f.get_doc()) - } else if let Some(f) = f.down_to_func() { - (f.module_name(), f.name(), f.params(), f.get_doc()) - } else if let Some(f) = f.down_to_closure() { - (f.module_name(), f.name(), f.params(), f.get_doc()) - } else { - panic!("Unexpected bound func type: {f}") - }; - - let module_name = module_name.to_owned(); - let name = name.to_owned(); - let params = params.clone(); - - drop(f); + pub fn new(func_ref: ObjectRef, this: ObjectRef) -> Self { + let (module_name, name, doc, params, params_tuple, arity, has_var_args) = { + let func_guard = func_ref.read().unwrap(); + + let func: &dyn FuncTrait = + if let Some(func) = func_guard.down_to_intrinsic_func() { + func + } else if let Some(func) = func_guard.down_to_func() { + func + } else if let Some(func) = func_guard.down_to_closure() { + func + } else { + panic!("Unexpected bound func type: {func_guard}") + }; + + ( + func.module_name().to_owned(), + func.name().to_owned(), + func.get_doc(), + func.params().clone(), + func.get_params(), + func.arity(), + func.has_var_args(), + ) + }; Self { ns: Namespace::with_entries(&[ ("$module_name", new::str(&module_name)), ("$full_name", new::str(format!("{module_name}.{name}"))), ("$name", new::str(&name)), + ("$params", params_tuple), ("$doc", doc), + ("$arity", new::int(arity)), + ("$has_var_args", new::bool(has_var_args)), ]), module_name, - func, + func: func_ref, this, name, params, @@ -101,28 +105,26 @@ impl FuncTrait for BoundFunc { } impl ObjectTrait for BoundFunc { - gen::object_trait_header!(BOUND_FUNC_TYPE); + object_trait_header!(BOUND_FUNC_TYPE); fn module(&self) -> ObjectRef { self.func().read().unwrap().module() } } -// Display ------------------------------------------------------------- - impl fmt::Display for BoundFunc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} *BOUND* to {:?}", - self.func.read().unwrap(), - &*self.this.read().unwrap() - ) + write!(f, "{}", self.func.read().unwrap()) } } impl fmt::Debug for BoundFunc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") + write!( + f, + "{:?} *BOUND* to {:?}", + &*self.func.read().unwrap(), + &*self.this.read().unwrap() + ) } } diff --git a/src/types/cell.rs b/feint-builtins/src/types/cell.rs similarity index 56% rename from src/types/cell.rs rename to feint-builtins/src/types/cell.rs index 04f2639..d576648 100644 --- a/src/types/cell.rs +++ b/feint-builtins/src/types/cell.rs @@ -1,36 +1,28 @@ +//! Builtin `Cell` type. use std::any::Any; use std::fmt; use std::sync::{Arc, RwLock}; use once_cell::sync::Lazy; -use crate::vm::RuntimeBoolResult; +use feint_code_gen::*; -use super::gen; -use super::new; +use crate::new; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; use super::ns::Namespace; -// Cell Type ----------------------------------------------------------- - -gen::type_and_impls!(CellType, Cell); - -pub static CELL_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(CellType::new())); - -// Cell Object --------------------------------------------------------- +std_type!(CELL_TYPE, Cell); pub struct Cell { ns: Namespace, value: ObjectRef, } -gen::standard_object_impls!(Cell); +standard_object_impls!(Cell); impl Cell { - #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { ns: Namespace::default(), value: new::nil() } } @@ -51,15 +43,13 @@ impl Cell { } impl ObjectTrait for Cell { - gen::object_trait_header!(CELL_TYPE); + object_trait_header!(CELL_TYPE); - fn bool_val(&self) -> RuntimeBoolResult { - Ok(false) + fn bool_val(&self) -> Option { + Some(false) } } -// Display ------------------------------------------------------------- - impl fmt::Display for Cell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "&({:?})", &*self.value.read().unwrap()) diff --git a/feint-builtins/src/types/class.rs b/feint-builtins/src/types/class.rs new file mode 100644 index 0000000..4d311fa --- /dev/null +++ b/feint-builtins/src/types/class.rs @@ -0,0 +1,73 @@ +//! Builtin `Type` type--the base of the type hierarchy. +//! +//! "Class" and "type" are synonymous and used interchangeably. Lower +//! case "class" is used instead of "type" because the latter is a Rust +//! keyword. +use std::any::Any; +use std::fmt; +use std::sync::{Arc, RwLock}; + +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use crate::new; + +use super::base::{ObjectTrait, TypeRef}; +use super::ns::Namespace; + +std_type!(TYPE_TYPE, Type); + +pub struct Type { + module_name: String, + name: String, + full_name: String, + ns: Namespace, +} + +standard_object_impls!(Type); + +impl Type { + pub fn new(module_name: &str, name: &str) -> Self { + let full_name = format!("{module_name}.{name}"); + let full_name_str = new::str(&full_name); + Self { + module_name: module_name.to_owned(), + name: name.to_owned(), + full_name, + ns: Namespace::with_entries(&[ + ("$module_name", new::str(module_name)), + ("$name", new::str(name)), + ("$full_name", full_name_str), + ]), + } + } + + pub fn module_name(&self) -> &String { + &self.module_name + } + + pub fn name(&self) -> &String { + &self.name + } + + pub fn full_name(&self) -> &String { + &self.full_name + } +} + +impl ObjectTrait for Type { + object_trait_header!(TYPE_TYPE); +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "", self.name) + } +} + +impl fmt::Debug for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "", self.full_name(), self.id()) + } +} diff --git a/src/types/closure.rs b/feint-builtins/src/types/closure.rs similarity index 72% rename from src/types/closure.rs rename to feint-builtins/src/types/closure.rs index 7c561f2..ced8376 100644 --- a/src/types/closure.rs +++ b/feint-builtins/src/types/closure.rs @@ -1,26 +1,21 @@ +//! Builtin `Closure` type. use std::any::Any; use std::fmt; use std::sync::{Arc, RwLock}; use once_cell::sync::Lazy; -use super::gen; -use super::new; -use super::result::Params; +use feint_code_gen::*; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; use super::func_trait::FuncTrait; use super::ns::Namespace; +use super::Params; -// Closure Type -------------------------------------------------------- - -gen::type_and_impls!(ClosureType, Closure); - -pub static CLOSURE_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(ClosureType::new())); - -// Closure Object ------------------------------------------------------ +std_type!(CLOSURE_TYPE, Closure); pub struct Closure { ns: Namespace, @@ -32,14 +27,19 @@ pub struct Closure { captured: ObjectRef, } -gen::standard_object_impls!(Closure); +standard_object_impls!(Closure); impl Closure { pub fn new(func_ref: ObjectRef, captured: ObjectRef) -> Self { let func = func_ref.read().unwrap(); let func = func.down_to_func().unwrap(); Self { - ns: Namespace::with_entries(&[("$doc", func.get_doc())]), + ns: Namespace::with_entries(&[ + ("$params", func.get_params()), + ("$doc", func.get_doc()), + ("$arity", new::int(func.arity())), + ("$has_var_args", new::bool(func.has_var_args())), + ]), module_name: func.module_name().to_owned(), name: func.name().to_owned(), params: func.params().clone(), @@ -87,11 +87,9 @@ impl FuncTrait for Closure { } impl ObjectTrait for Closure { - gen::object_trait_header!(CLOSURE_TYPE); + object_trait_header!(CLOSURE_TYPE); } -// Display ------------------------------------------------------------- - impl fmt::Display for Closure { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[closure] {}", self.func.read().unwrap()) @@ -100,6 +98,6 @@ impl fmt::Display for Closure { impl fmt::Debug for Closure { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") + write!(f, "[closure] {:?}", &*self.func.read().unwrap()) } } diff --git a/feint-builtins/src/types/code.rs b/feint-builtins/src/types/code.rs new file mode 100644 index 0000000..a086674 --- /dev/null +++ b/feint-builtins/src/types/code.rs @@ -0,0 +1,406 @@ +use std::ops::Index; +use std::slice::Iter; + +use feint_util::op::{BinaryOperator, CompareOperator, InplaceOperator, UnaryOperator}; +use feint_util::source::Location; +use feint_util::string::format_doc; + +use crate::new; +use crate::types::{FuncTrait, ObjectRef}; + +type FreeVarEntry = ( + usize, // address + String, // name + Location, // source start + Location, // source end +); + +/// Code for a module or function. +#[derive(Debug)] +pub struct Code { + chunk: Vec, + constants: Vec, + // Vars defined outside of this unit of code. + free_vars: Vec, +} + +impl Default for Code { + fn default() -> Self { + Code::new(vec![], vec![], vec![]) + } +} + +impl Index for Code { + type Output = Inst; + + fn index(&self, index: usize) -> &Self::Output { + &self.chunk[index] + } +} + +impl PartialEq for Code { + fn eq(&self, other: &Self) -> bool { + if self.chunk != other.chunk { + return false; + } + if self.constants.len() != other.constants.len() { + return false; + } + if self.free_vars != other.free_vars { + return false; + } + for (c, d) in self.constants.iter().zip(other.constants.iter()) { + let c = c.read().unwrap(); + let d = d.read().unwrap(); + if !c.is_equal(&*d) { + return false; + } + } + true + } +} + +impl Code { + pub fn new( + chunk: Vec, + constants: Vec, + free_vars: Vec, + ) -> Self { + Self { chunk, constants, free_vars } + } + + /// Initialize code object with a list of instructions, also known + /// as a chunk. + pub fn with_chunk(chunk: Vec) -> Self { + Self::new(chunk, vec![], vec![]) + } + + /// Extend this `Code` object with another `Code` object: + /// + /// - Extend instructions, adjusting constant indexes + /// - Extend constants + /// - Free vars are ignored for now since this is mainly intended + /// for extending modules (where there are no free vars) and not + /// functions + /// + /// IMPORTANT: ALL instructions that hold a const index MUST be + /// updated here. + pub fn extend(&mut self, mut code: Self) { + use Inst::LoadConst; + let mut replacements = vec![]; + let const_offset = self.constants.len(); + for (addr, inst) in code.iter_chunk().enumerate() { + if let LoadConst(index) = inst { + replacements.push((addr, LoadConst(const_offset + index))); + } + } + for (addr, inst) in replacements { + code.replace_inst(addr, inst); + } + self.chunk.extend(code.chunk); + self.constants.extend(code.constants); + } + + /// Get docstring for code unit, if there is one. + pub fn get_doc(&self) -> ObjectRef { + if let Some(Inst::LoadConst(0)) = self.chunk.get(1) { + if let Some(obj_ref) = self.get_const(0) { + let obj = obj_ref.read().unwrap(); + if let Some(doc) = obj.get_str_val() { + return new::str(format_doc(doc)); + } + } + } + new::nil() + } + + // Instructions ---------------------------------------------------- + + pub fn len_chunk(&self) -> usize { + self.chunk.len() + } + + pub fn iter_chunk(&self) -> Iter<'_, Inst> { + self.chunk.iter() + } + + pub fn push_inst(&mut self, inst: Inst) { + self.chunk.push(inst) + } + + pub fn pop_inst(&mut self) -> Option { + self.chunk.pop() + } + + pub fn insert_inst(&mut self, index: usize, inst: Inst) { + self.chunk.insert(index, inst); + } + + pub fn replace_inst(&mut self, index: usize, inst: Inst) { + self.chunk[index] = inst; + } + + /// Explicit return statements need to jump to the end of the + /// function so that the function can be cleanly exited. + pub fn fix_up_explicit_returns(&mut self) { + let return_addr = self.len_chunk(); + for addr in 0..return_addr { + let inst = &self.chunk[addr]; + if let Inst::ReturnPlaceholder(inst_addr, depth) = inst { + let rel_addr = return_addr - inst_addr; + self.replace_inst(*inst_addr, Inst::Jump(rel_addr, true, depth - 1)); + } + } + } + + // Constants ------------------------------------------------------- + + pub fn add_const(&mut self, val_ref: ObjectRef) -> usize { + let val_guard = val_ref.read().unwrap(); + let val = &*val_guard; + + // XXX: Functions are immutable and comparable, but it feels + // potentially unsafe to treat them as such here. + let is_comparable = val.is_immutable() && !val.is_func(); + + for (index, other_ref) in self.iter_constants().enumerate() { + let other = other_ref.read().unwrap(); + let other_is_comparable = other.is_immutable() && !other.is_func(); + if is_comparable && other_is_comparable && other.is_type_equal(val) { + return index; + } + } + + let index = self.constants.len(); + drop(val_guard); + self.constants.push(val_ref); + index + } + + pub fn get_const(&self, index: usize) -> Option<&ObjectRef> { + self.constants.get(index) + } + + pub fn iter_constants(&self) -> Iter<'_, ObjectRef> { + self.constants.iter() + } + + pub fn get_main(&self) -> Option { + let maybe_index = self.constants.iter().position(|obj_ref| { + let obj = obj_ref.read().unwrap(); + if let Some(func) = obj.down_to_func() { + func.name() == "$main" + } else { + false + } + }); + maybe_index.map(|index| self.constants[index].clone()) + } + + // Vars ------------------------------------------------------------ + + pub fn free_vars(&self) -> &Vec { + &self.free_vars + } + + /// Add a free var, a reference to a var defined in an enclosing + /// scope. This also adds a placeholder instruction for the free + /// var that will replaced in the compiler's name resolution stage. + pub fn add_free_var>( + &mut self, + name: S, + start: Location, + end: Location, + ) { + let addr = self.len_chunk(); + let name = name.into(); + self.free_vars.push((addr, name.clone(), start, end)); + self.push_inst(Inst::FreeVarPlaceholder(addr, name)); + } +} + +/// NOTE: When adding or removing instructions, the PartialEq impl +/// below must also be updated. +#[derive(Debug)] +pub enum Inst { + NoOp, + + // Pop TOS and discard it. + Pop, + + ScopeStart, + ScopeEnd, + + StatementStart(Location, Location), + + // Other constants are local to a given code unit. + LoadConst(usize), + + DeclareVar(String), + + // Args: name, offset + // + // `offset` is the number of scopes above the current scope, where + // the var is defined. 0 means the current scope, 1 means the parent + // scope, and so on. + AssignVar(String, usize), + + // Args: name, offset + // + // `offset` is the number of scopes above the current scope to start + // the search. 0 means the current scope, 1 means the parent scope, + // and so on. + LoadVar(String, usize), + + // Load module global + LoadGlobal(String), + + // Load builtin + LoadBuiltin(String), + + // These are analogous to AssignVar and LoadVar. Assignment wraps + // the value in a cell so that it can be shared. Loading unwraps the + // value. + AssignCell(String), + LoadCell(String), + + // Load captured value to TOS (a special case of LoadCell). + LoadCaptured(String), + + // Jumps ----------------------------------------------------------- + // + // For all jump instructions, the first arg is the target address + // relative to the jump address. The second arg is a flag to + // indicate a forward or reverse jump. The third arg is the scope + // exit count. + // + // Relative addresses allow instructions to be inserted BEFORE any + // forward jumps or AFTER any backward jumps within a code segment. + // Mainly this is to allow instructions to be inserted at the + // beginning of functions. + + // Jump unconditionally. + Jump(usize, bool, usize), + + // Jump unconditionally and push nil onto stack. + JumpPushNil(usize, bool, usize), + + // If top of stack is true, jump to address. Otherwise, continue. + JumpIf(usize, bool, usize), + + // If top of stack is false, jump to address. Otherwise, continue. + JumpIfNot(usize, bool, usize), + + // If top of stack is NOT nil, jump to address. Otherwise, continue. + JumpIfNotNil(usize, bool, usize), + + UnaryOp(UnaryOperator), + BinaryOp(BinaryOperator), + CompareOp(CompareOperator), + InplaceOp(InplaceOperator), + + // Call function with N values from top of stack. The args are + // ordered such that the 1st arg is at TOS and other args are below + // it. + Call(usize), + + // RETURN is a jump target at the end of a function. Its only + // purpose is to serve as a jump target for explicit returns. + Return, + + // These make compound objects from the top N items on the stack. + MakeString(usize), + MakeTuple(usize), + MakeList(usize), + MakeMap(usize), + + // Capture set for function--a list of names for the function to + // capture. If empty, a regular function will be created. + CaptureSet(Vec), + + // Make function or closure depending on capture set. MAKE_FUNC + // expects the following entries at TOS: + // + // TOS capture_set: Map (added by CAPTURE_SET) + // func: Func (added by LOAD_CONST) + MakeFunc, + + LoadModule(String), + + Halt(u8), + HaltTop, + + // Placeholders ---------------------------------------------------- + // + // Placeholders are inserted during compilation and later updated. + // All placeholders must be replaced or a runtime error will be + // thrown. + Placeholder(usize, Box, String), // address, instruction, error message + FreeVarPlaceholder(usize, String), // address, var name + BreakPlaceholder(usize, usize), // jump address, scope depth + ContinuePlaceholder(usize, usize), // jump address, scope depth + + // NOTE: This is used for explicit return statements. It will be + // replaced with a jump to a RETURN target. + ReturnPlaceholder(usize, usize), // jump address, scope depth + + // Miscellaneous --------------------------------------------------- + + // Pop TOS and print it to stdout or stderr. Behavior is controlled + // by passing in flags. Pass `PrintFlags::default()` for the default + // behavior, which is to print to stdout with no newline. + Print(PrintFlags), + + DisplayStack(String), +} + +bitflags! { + #[derive(Default)] + pub struct PrintFlags: u32 { + const STDERR = 0b00000001; // print to stderr instead of stdout + const NL = 0b00000010; // print a trailing newline + const REPR = 0b00000100; // print repr using fmt::Debug + const NO_NIL = 0b00001000; // don't print obj if it's nil (if !STDERR) + } +} + +impl PartialEq for Inst { + fn eq(&self, other: &Self) -> bool { + use Inst::*; + + match (self, other) { + (NoOp, NoOp) => true, + (Pop, Pop) => true, + (ScopeStart, ScopeStart) => true, + (ScopeEnd, ScopeEnd) => true, + (StatementStart(..), StatementStart(..)) => true, + (LoadConst(a), LoadConst(b)) => a == b, + (DeclareVar(a), DeclareVar(b)) => a == b, + (AssignVar(a, i), AssignVar(b, j)) => (a, i) == (b, j), + (LoadVar(a, i), LoadVar(b, j)) => (a, i) == (b, j), + (AssignCell(a), AssignCell(b)) => a == b, + (LoadCell(a), LoadCell(b)) => a == b, + (LoadCaptured(a), LoadCaptured(b)) => a == b, + (Jump(a, b, c), Jump(d, e, f)) => (a, b, c) == (d, e, f), + (JumpPushNil(a, b, c), JumpPushNil(d, e, f)) => (a, b, c) == (d, e, f), + (JumpIfNot(a, b, c), JumpIfNot(d, e, f)) => (a, b, c) == (d, e, f), + (UnaryOp(a), UnaryOp(b)) => a == b, + (BinaryOp(a), BinaryOp(b)) => a == b, + (CompareOp(a), CompareOp(b)) => a == b, + (InplaceOp(a), InplaceOp(b)) => a == b, + (Call(a), Call(b)) => a == b, + (Return, Return) => true, + (MakeString(a), MakeString(b)) => a == b, + (MakeTuple(a), MakeTuple(b)) => a == b, + (MakeList(a), MakeList(b)) => a == b, + (MakeMap(a), MakeMap(b)) => a == b, + (CaptureSet(a), CaptureSet(b)) => a == b, + (MakeFunc, MakeFunc) => true, + (LoadModule(a), LoadModule(b)) => a == b, + (Halt(a), Halt(b)) => a == b, + (HaltTop, HaltTop) => true, + (Print(a), Print(b)) => a == b, + _ => false, + } + } +} diff --git a/feint-builtins/src/types/custom.rs b/feint-builtins/src/types/custom.rs new file mode 100644 index 0000000..28f4631 --- /dev/null +++ b/feint-builtins/src/types/custom.rs @@ -0,0 +1,75 @@ +//! Builtin `CustomType` type. +use std::any::Any; +use std::fmt; + +use feint_code_gen::*; + +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::ns::Namespace; + +pub struct CustomObj { + class: TypeRef, + ns: Namespace, +} + +standard_object_impls!(CustomObj); + +impl CustomObj { + pub fn new(class: TypeRef, attrs: Namespace) -> Self { + Self { class, ns: attrs } + } +} + +impl ObjectTrait for CustomObj { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn class(&self) -> TypeRef { + self.class.clone() + } + + fn ns(&self) -> &Namespace { + &self.ns + } + + fn ns_mut(&mut self) -> &mut Namespace { + &mut self.ns + } + + fn set_attr( + &mut self, + name: &str, + value: ObjectRef, + _this: ObjectRef, + ) -> ObjectRef { + self.ns.set(name, value); + new::nil() + } + + fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { + self.is(rhs) || rhs.is_always() || self.ns.is_equal(rhs.ns()) + } +} + +impl fmt::Display for CustomObj { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let class = self.class(); + let class = class.read().unwrap(); + write!(f, "<{} object>", class.name()) + } +} + +impl fmt::Debug for CustomObj { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let class = self.class(); + let class = class.read().unwrap(); + write!(f, "<{} object @ {}>", class.full_name(), self.id()) + } +} diff --git a/feint-builtins/src/types/err.rs b/feint-builtins/src/types/err.rs new file mode 100644 index 0000000..67d0dad --- /dev/null +++ b/feint-builtins/src/types/err.rs @@ -0,0 +1,179 @@ +//! Builtin `Error` type. +//! +//! The error type represents _recoverable_ runtime errors that can be +//! checked in user code using this pattern: +//! +//! result = assert(false) +//! if result.err -> +//! # Handle `result` as an error +//! print(result) +//! +//! _All_ objects respond to `err`, which returns either an `Err` object +//! or `nil`. `Err` objects evaluate as `false` in a boolean context: +//! +//! if !assert(false) -> +//! print("false is not true") +use std::any::Any; +use std::fmt; +use std::sync::{Arc, RwLock}; + +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use crate::new; +use crate::util::check_args; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; +use super::err_type::ErrKind; +use super::ns::Namespace; + +pub static ERR_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "Err")); + + { + type_ref.write().unwrap().ns_mut().extend(&[ + // Class Methods ----------------------------------------------- + meth!("new", type_ref, &["type", "msg"], "", |_, args| { + let name = "Err.new()"; + + let result = check_args(name, &args, false, 2, Some(2)); + if let Err(err) = result { + return err; + } + + let type_arg = use_arg!(args, 0); + let msg_arg = use_arg!(args, 1); + + let err_type = if let Some(err_type) = type_arg.down_to_err_type_obj() { + err_type + } else { + let arg_err_msg = format!("{name} expected type to be an ErrType"); + // NOTE: This is problematic because user code won't be + // able to tell if the arg error was the result of + // creating an arg err explicitly or the result of + // an internal error. Note that this applies to + // *any* user-constructible error. + // + // TODO: Figure out a solution for this, perhaps an err + // type that is *not* user-constructible or a + // nested err type? + return new::arg_err(arg_err_msg, new::nil()); + }; + + let kind = err_type.kind().clone(); + + let msg = if let Some(msg) = msg_arg.get_str_val() { + msg + } else { + let arg_err_msg = format!("{name} expected message to be a Str"); + return new::arg_err(arg_err_msg, new::nil()); + }; + + new::err(kind, msg, new::nil()) + }), + // Instance Attributes ----------------------------------------- + prop!("type", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_err().unwrap(); + this.kind.get_obj().unwrap() + }), + prop!("message", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_err().unwrap(); + new::str(&this.message) + }), + ]); + } + + type_ref +}); + +// NOTE: This is named `ErrObj` instead of `Err` to avoid conflict with +// Rust's `Err`. +pub struct ErrObj { + ns: Namespace, + pub kind: ErrKind, + pub message: String, + pub obj: ObjectRef, + bool_val: bool, + responds_to_bool: bool, +} + +standard_object_impls!(ErrObj); + +impl ErrObj { + pub fn new(kind: ErrKind, message: String, obj: ObjectRef) -> Self { + let bool_val = kind != ErrKind::Ok; + Self { + ns: Namespace::default(), + kind, + message, + obj, + bool_val, + responds_to_bool: false, + } + } + + pub fn with_responds_to_bool( + kind: ErrKind, + message: String, + obj: ObjectRef, + ) -> Self { + let mut instance = Self::new(kind, message, obj); + instance.responds_to_bool = true; + instance + } + + pub fn retrieve_bool_val(&self) -> bool { + self.bool_val + } +} + +impl ObjectTrait for ErrObj { + object_trait_header!(ERR_TYPE); + + fn bool_val(&self) -> Option { + if self.responds_to_bool { + Some(self.bool_val) + } else { + // Err(RuntimeErr::type_err(concat!( + // "An Err object cannot be evaluated directly as a ", + // "Bool. You must access it via the `.err` attribute of ", + // "the result object.", + // ))) + None + } + } + + fn and(&self, rhs: &dyn ObjectTrait) -> Option { + let lhs = self.bool_val()?; + let rhs = rhs.bool_val()?; + Some(lhs && rhs) + } + + fn or(&self, rhs: &dyn ObjectTrait) -> Option { + let lhs = self.bool_val()?; + let rhs = rhs.bool_val()?; + Some(lhs && rhs) + } +} + +impl fmt::Display for ErrObj { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let kind = &self.kind; + let msg = &self.message; + if self.message.is_empty() { + write!(f, "{} [{}]", kind, kind.name()) + } else { + write!(f, "[{}] {}: {}", kind.name(), kind, msg) + } + } +} + +impl fmt::Debug for ErrObj { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} diff --git a/src/types/err_type.rs b/feint-builtins/src/types/err_type.rs similarity index 70% rename from src/types/err_type.rs rename to feint-builtins/src/types/err_type.rs index 40fdd3d..86bef92 100644 --- a/src/types/err_type.rs +++ b/feint-builtins/src/types/err_type.rs @@ -1,4 +1,4 @@ -//! Error Types +//! Builtin error types //! //! Builtin type used to tag builtin `Err` instances. use std::any::Any; @@ -7,11 +7,12 @@ use std::sync::{Arc, RwLock}; use once_cell::sync::Lazy; -use super::gen; -use super::new; +use feint_code_gen::*; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; use super::ns::Namespace; #[derive(Clone, Debug, PartialEq)] @@ -23,6 +24,7 @@ pub enum ErrKind { FileNotFound, FileUnreadable, IndexOutOfBounds, + NotCallable, String, Type, Ok, @@ -38,6 +40,7 @@ static ERR_KINDS: Lazy> = Lazy::new(|| { FileNotFound, FileUnreadable, IndexOutOfBounds, + NotCallable, String, Type, Ok, @@ -55,6 +58,7 @@ impl ErrKind { FileNotFound => "file_not_found", FileUnreadable => "file_unreadable", IndexOutOfBounds => "index_out_of_bounds", + NotCallable => "not_callable", String => "string", Type => "type", Ok => "ok", @@ -63,43 +67,41 @@ impl ErrKind { pub fn get_obj(&self) -> Option { let err_type_type = ERR_TYPE_TYPE.read().unwrap(); - err_type_type.ns.get(self.name()) + err_type_type.ns().get(self.name()) } } -// ErrType Type -------------------------------------------------------- +pub static ERR_TYPE_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "ErrType")); -gen::type_and_impls!(ErrTypeType, ErrType); + { + let mut type_obj = type_ref.write().unwrap(); + let ns = type_obj.ns_mut(); -pub static ERR_TYPE_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(ErrTypeType::new()); - let mut type_obj = type_ref.write().unwrap(); + // Types as class attributes ----------------------------------- + for kind in ERR_KINDS.iter() { + ns.insert(kind.name(), obj_ref!(ErrTypeObj::new(kind.clone()))); + } - // Types as class attributes - for kind in ERR_KINDS.iter() { - type_obj.add_attr(kind.name(), gen::obj_ref!(ErrTypeObj::new(kind.clone()))); + ns.extend(&[ + // Instance Attributes ------------------------------------- + prop!("name", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.as_any().downcast_ref::().unwrap(); + new::str(this.name()) + }), + ]); } - type_obj.add_attrs(&[ - // Instance Attributes ----------------------------------------- - gen::prop!("name", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.as_any().downcast_ref::().unwrap(); - Ok(new::str(this.name())) - }), - ]); - - type_ref.clone() + type_ref }); -// ErrType Object ------------------------------------------------------ - pub struct ErrTypeObj { ns: Namespace, kind: ErrKind, } -gen::standard_object_impls!(ErrTypeObj); +standard_object_impls!(ErrTypeObj); impl ErrTypeObj { pub fn new(kind: ErrKind) -> Self { @@ -116,7 +118,7 @@ impl ErrTypeObj { } impl ObjectTrait for ErrTypeObj { - gen::object_trait_header!(ERR_TYPE_TYPE); + object_trait_header!(ERR_TYPE_TYPE); fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { if self.is(rhs) || rhs.is_always() { @@ -129,8 +131,6 @@ impl ObjectTrait for ErrTypeObj { } } -// Display ------------------------------------------------------------- - impl fmt::Display for ErrKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use ErrKind::*; @@ -142,6 +142,7 @@ impl fmt::Display for ErrKind { FileNotFound => "File not found", FileUnreadable => "File could not be read", IndexOutOfBounds => "Index out of bounds", + NotCallable => "Not callable", String => "String error", Type => "Type error", Ok => "OK (not an error)", diff --git a/src/types/file.rs b/feint-builtins/src/types/file.rs similarity index 55% rename from src/types/file.rs rename to feint-builtins/src/types/file.rs index e9f7a45..1acb792 100644 --- a/src/types/file.rs +++ b/feint-builtins/src/types/file.rs @@ -1,3 +1,4 @@ +//! Builtin `File` type. use std::any::Any; use std::fmt; use std::fs; @@ -7,57 +8,51 @@ use std::sync::{Arc, RwLock}; use once_cell::sync::{Lazy, OnceCell}; -use crate::vm::{RuntimeBoolResult, RuntimeErr}; +use feint_code_gen::*; -use super::gen; -use super::new; +use crate::new; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; use super::ns::Namespace; -// File Type ------------------------------------------------------------ - -gen::type_and_impls!(FileType, File); - -pub static FILE_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(FileType::new()); - let mut type_obj = type_ref.write().unwrap(); - - type_obj.add_attrs(&[ - // Class Methods - gen::meth!("new", type_ref, &["file_name"], "", |_, args, _| { - let arg = gen::use_arg!(args, 0); - if let Some(file_name) = arg.get_str_val() { - let path = Path::new(file_name); - Ok(if path.is_file() { - new::file(file_name) +pub static FILE_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "File")); + + { + type_ref.write().unwrap().ns_mut().extend(&[ + // Class Methods ------------------------------------------- + meth!("new", type_ref, &["file_name"], "", |_, args| { + let arg = use_arg!(args, 0); + if let Some(file_name) = arg.get_str_val() { + let path = Path::new(file_name); + if path.is_file() { + new::file(file_name) + } else { + new::file_not_found_err(file_name, new::nil()) + } } else { - new::file_not_found_err(file_name, new::nil()) - }) - } else { - let message = format!("File.new(file_name) expected string; got {arg}"); - Ok(new::arg_err(message, new::nil())) - } - }), - // Instance Attributes - gen::prop!("text", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_file().unwrap(); - Ok(this.text()) - }), - gen::prop!("lines", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = &mut this.down_to_file().unwrap(); - Ok(this.lines()) - }), - ]); - - type_ref.clone() + let message = + format!("File.new(file_name) expected string; got {arg}"); + new::arg_err(message, new::nil()) + } + }), + // Instance Attributes ------------------------------------- + prop!("text", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_file().unwrap(); + this.text() + }), + prop!("lines", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = &mut this.down_to_file().unwrap(); + this.lines() + }), + ]); + } + type_ref }); -// File Object ---------------------------------------------------------- - pub struct File { ns: Namespace, file_name: String, @@ -66,7 +61,7 @@ pub struct File { lines: OnceCell, } -gen::standard_object_impls!(File); +standard_object_impls!(File); impl File { pub fn new(file_name: String) -> Self { @@ -116,15 +111,13 @@ impl File { } impl ObjectTrait for File { - gen::object_trait_header!(FILE_TYPE); + object_trait_header!(FILE_TYPE); - fn bool_val(&self) -> RuntimeBoolResult { - Ok(false) + fn bool_val(&self) -> Option { + Some(false) } } -// Display ------------------------------------------------------------- - impl fmt::Display for File { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "", &self.path.display()) diff --git a/feint-builtins/src/types/float.rs b/feint-builtins/src/types/float.rs new file mode 100644 index 0000000..69cfabf --- /dev/null +++ b/feint-builtins/src/types/float.rs @@ -0,0 +1,156 @@ +//! Builtin `Float` type. +use std::any::Any; +use std::fmt; +use std::sync::{Arc, RwLock}; + +use num_traits::ToPrimitive; +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; +use super::ns::Namespace; +use super::util::{eq_int_float, float_gt_int, float_lt_int}; + +pub static FLOAT_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "Float")); + + { + type_ref.write().unwrap().ns_mut().extend(&[ + // Class Methods ----------------------------------------------- + meth!("new", type_ref, &["value"], "", |this, args| { + let arg = use_arg!(args, 0); + let float = if let Some(val) = arg.get_float_val() { + new::float(*val) + } else if let Some(val) = arg.get_int_val() { + new::float(val.to_f64().unwrap()) + } else if let Some(val) = arg.get_str_val() { + new::float_from_string(val) + } else { + let msg = + format!("Float.new() expected string or float; got {arg}"); + new::type_err(msg, this) + }; + float + }), + ]); + } + + type_ref +}); + +macro_rules! make_op { + ( $meth:ident, $op:tt, $message:literal, $trunc:literal ) => { + fn $meth(&self, rhs: &dyn ObjectTrait) -> Option { + let value = if let Some(rhs) = rhs.down_to_float() { + *rhs.value() + } else if let Some(rhs) = rhs.down_to_int() { + rhs.value().to_f64().unwrap() + } else { + return None + }; + let mut value = &self.value $op value; + if $trunc { + value = value.trunc(); + } + let value = new::float(value); + Some(value) + } + }; +} + +pub struct Float { + ns: Namespace, + value: f64, +} + +standard_object_impls!(Float); + +impl Float { + pub fn new(value: f64) -> Self { + Self { ns: Namespace::default(), value } + } + + pub fn value(&self) -> &f64 { + &self.value + } +} + +impl ObjectTrait for Float { + object_trait_header!(FLOAT_TYPE); + + fn negate(&self) -> Option { + Some(new::float(-*self.value())) + } + + fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { + if self.is(rhs) || rhs.is_always() { + true + } else if let Some(rhs) = rhs.down_to_float() { + self.value() == rhs.value() + } else if let Some(rhs) = rhs.down_to_int() { + eq_int_float(rhs, self) + } else { + false + } + } + + fn less_than(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_float() { + Some(self.value() < rhs.value()) + } else if let Some(rhs) = rhs.down_to_int() { + Some(float_lt_int(self, rhs)) + } else { + None + } + } + + fn greater_than(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_float() { + Some(self.value() > rhs.value()) + } else if let Some(rhs) = rhs.down_to_int() { + Some(float_gt_int(self, rhs)) + } else { + return None; + } + } + + fn pow(&self, rhs: &dyn ObjectTrait) -> Option { + let exp = if let Some(rhs) = rhs.down_to_float() { + *rhs.value() + } else if let Some(rhs) = rhs.down_to_int() { + rhs.value().to_f64().unwrap() + } else { + return None; + }; + let value = self.value().powf(exp); + let value = new::float(value); + Some(value) + } + + make_op!(modulo, %, "Could not divide {} with Float", false); + make_op!(mul, *, "Could not multiply {} with Float", false); + make_op!(div, /, "Could not divide {} into Float", false); + make_op!(floor_div, /, "Could not divide {} into Float", true); // truncates + make_op!(add, +, "Could not add {} to Float", false); + make_op!(sub, -, "Could not subtract {} from Float", false); +} + +impl fmt::Display for Float { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.value().fract() == 0.0 { + write!(f, "{}.0", self.value) + } else { + write!(f, "{}", self.value) + } + } +} + +impl fmt::Debug for Float { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} diff --git a/src/types/func.rs b/feint-builtins/src/types/func.rs similarity index 74% rename from src/types/func.rs rename to feint-builtins/src/types/func.rs index 79c30ec..d5d6e91 100644 --- a/src/types/func.rs +++ b/feint-builtins/src/types/func.rs @@ -1,29 +1,23 @@ +//! Builtin `Func` type. use std::any::Any; use std::fmt; use std::sync::{Arc, RwLock}; use once_cell::sync::{Lazy, OnceCell}; -use crate::modules::get_module; -use crate::vm::Code; +use feint_code_gen::*; -use super::gen; -use super::new; -use super::result::Params; +use crate::modules::get_module; +use crate::new; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; +use super::code::Code; use super::func_trait::FuncTrait; use super::ns::Namespace; +use super::Params; -// Function Type ------------------------------------------------------- - -gen::type_and_impls!(FuncType, Func); - -pub static FUNC_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(FuncType::new())); - -// Func Object ---------------------------------------------------------- +std_type!(FUNC_TYPE, FuncType); pub struct Func { ns: Namespace, @@ -34,16 +28,19 @@ pub struct Func { code: Code, } -gen::standard_object_impls!(Func); +standard_object_impls!(Func); impl Func { pub fn new(module_name: String, name: String, params: Params, code: Code) -> Self { - Self { + let params_tuple = new::tuple(params.iter().map(new::str).collect()); + + let mut instance = Self { ns: Namespace::with_entries(&[ // Instance Attributes ("$module_name", new::str(&module_name)), ("$full_name", new::str(format!("{module_name}.{name}"))), ("$name", new::str(&name)), + ("$params", params_tuple), ("$doc", code.get_doc()), ]), module_name, @@ -51,7 +48,14 @@ impl Func { name, params, code, - } + }; + + let arity = (&instance as &dyn FuncTrait).arity(); + let has_var_args = (&instance as &dyn FuncTrait).has_var_args(); + instance.ns_mut().insert("$arity", new::int(arity)); + instance.ns_mut().insert("$has_var_args", new::bool(has_var_args)); + + instance } pub fn arg_names(&self) -> Vec<&str> { @@ -94,7 +98,7 @@ impl FuncTrait for Func { } impl ObjectTrait for Func { - gen::object_trait_header!(FUNC_TYPE); + object_trait_header!(FUNC_TYPE); fn module(&self) -> ObjectRef { self.module.get_or_init(|| get_module(&self.module_name)).clone() @@ -111,16 +115,14 @@ impl ObjectTrait for Func { } } -// Display ------------------------------------------------------------- - impl fmt::Display for Func { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", FuncTrait::format_string(self, Some(self.id()))) + write!(f, "{}", FuncTrait::format_string(self, None)) } } impl fmt::Debug for Func { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") + write!(f, "{}", FuncTrait::format_string(self, Some(self.id()))) } } diff --git a/src/types/func_trait.rs b/feint-builtins/src/types/func_trait.rs similarity index 82% rename from src/types/func_trait.rs rename to feint-builtins/src/types/func_trait.rs index b619973..5119ada 100644 --- a/src/types/func_trait.rs +++ b/feint-builtins/src/types/func_trait.rs @@ -1,8 +1,8 @@ use std::fmt; -use crate::types::{Namespace, ObjectRef}; - -use super::result::Params; +use super::base::ObjectRef; +use super::ns::Namespace; +use super::Params; // Function Trait ------------------------------------------------------ @@ -13,8 +13,18 @@ pub trait FuncTrait { fn name(&self) -> &String; fn params(&self) -> &Params; + fn get_params(&self) -> ObjectRef { + self.ns() + .get("$params") + .expect("Function type should have a $params attribute") + .clone() + } + fn get_doc(&self) -> ObjectRef { - self.ns().get("$doc").unwrap().clone() + self.ns() + .get("$doc") + .expect("Function type should have a $doc attribute") + .clone() } /// Returns the required number of args. @@ -34,8 +44,7 @@ pub trait FuncTrait { } /// If the function has var args, this returns the index of the var - /// args in the args list (which is also equal to the required - /// number of args). + /// args in the args list. fn var_args_index(&self) -> Option { let params = self.params(); if let Some(name) = params.last() { diff --git a/feint-builtins/src/types/int.rs b/feint-builtins/src/types/int.rs new file mode 100644 index 0000000..adad366 --- /dev/null +++ b/feint-builtins/src/types/int.rs @@ -0,0 +1,187 @@ +//! Builtin `Int` type. +use std::any::Any; +use std::fmt; +use std::sync::{Arc, RwLock}; + +use num_bigint::BigInt; +use num_traits::{FromPrimitive, ToPrimitive}; +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; +use super::ns::Namespace; +use super::util::{eq_int_float, int_gt_float, int_lt_float}; + +pub static INT_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "Int")); + + { + type_ref.write().unwrap().ns_mut().extend(&[ + // Class Methods ------------------------------------------- + meth!("new", type_ref, &["value"], "", |this, args| { + let arg = use_arg!(args, 0); + let int = if let Some(val) = arg.get_int_val() { + new::int(val.clone()) + } else if let Some(val) = arg.get_float_val() { + new::int(BigInt::from_f64(*val).unwrap()) + } else if let Some(val) = arg.get_str_val() { + new::int_from_string(val) + } else { + let msg = format!("Int.new() expected number or string; got {arg}"); + new::type_err(msg, this) + }; + int + }), + ]); + } + + type_ref +}); + +macro_rules! make_op { + ( $meth:ident, $op:tt ) => { + fn $meth(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_int() { + // XXX: Return Int + let value = self.value() $op rhs.value(); + Some(new::int(value)) + } else if let Some(rhs) = rhs.down_to_float() { + // XXX: Return Float + let value = self.value().to_f64().unwrap() $op rhs.value(); + Some(new::float(value)) + } else { + None + } + } + }; +} + +pub struct Int { + ns: Namespace, + value: BigInt, +} + +standard_object_impls!(Int); + +impl Int { + pub fn new(value: BigInt) -> Self { + Self { ns: Namespace::default(), value } + } + + pub fn value(&self) -> &BigInt { + &self.value + } + + // Cast both LHS and RHS to f64 and divide them + fn div_f64(&self, rhs: &dyn ObjectTrait) -> Option { + let lhs_val = self.value().to_f64().unwrap(); + let rhs_val = if let Some(rhs) = rhs.down_to_int() { + rhs.value().to_f64().unwrap() + } else if let Some(rhs) = rhs.down_to_float() { + *rhs.value() + } else { + return None; + }; + Some(lhs_val / rhs_val) + } +} + +impl ObjectTrait for Int { + object_trait_header!(INT_TYPE); + + fn negate(&self) -> Option { + Some(new::int(-self.value.clone())) + } + + fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { + if self.is(rhs) || rhs.is_always() { + true + } else if let Some(rhs) = rhs.down_to_int() { + self.value() == rhs.value() + } else if let Some(rhs) = rhs.down_to_float() { + eq_int_float(self, rhs) + } else { + false + } + } + + fn less_than(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_int() { + Some(self.value() < rhs.value()) + } else if let Some(rhs) = rhs.down_to_float() { + Some(int_lt_float(self, rhs)) + } else { + None + } + } + + fn greater_than(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_int() { + Some(self.value() > rhs.value()) + } else if let Some(rhs) = rhs.down_to_float() { + Some(int_gt_float(self, rhs)) + } else { + None + } + } + + fn pow(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_int() { + // XXX: Return Int + let base = self.value(); + let exp = rhs.value().to_u32().unwrap(); + let value = base.pow(exp); + let value = new::int(value); + Some(value) + } else if let Some(rhs) = rhs.down_to_float() { + // XXX: Return Float + let base = self.value().to_f64().unwrap(); + let exp = *rhs.value(); + let value = base.powf(exp); + let value = new::float(value); + Some(value) + } else { + None + } + } + + make_op!(modulo, %); + make_op!(mul, *); + make_op!(add, +); + make_op!(sub, -); + + // Int division *always* returns a Float + fn div(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(value) = self.div_f64(rhs) { + Some(new::float(value)) + } else { + None + } + } + + // Int *floor* division *always* returns an Int + fn floor_div(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(value) = self.div_f64(rhs) { + let value = BigInt::from_f64(value).unwrap(); + Some(new::int(value)) + } else { + None + } + } +} + +impl fmt::Display for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.value) + } +} + +impl fmt::Debug for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} diff --git a/src/types/intrinsic_func.rs b/feint-builtins/src/types/intrinsic_func.rs similarity index 69% rename from src/types/intrinsic_func.rs rename to feint-builtins/src/types/intrinsic_func.rs index 09d7932..b0b84ae 100644 --- a/src/types/intrinsic_func.rs +++ b/feint-builtins/src/types/intrinsic_func.rs @@ -1,4 +1,6 @@ -//! Intrinsic (implemented in Rust) function type. +//! Builtin `IntrinsicFunc` type. +//! +//! Used to implement builtin functions in Rust. use std::any::Any; use std::fmt; use std::sync::{Arc, RwLock}; @@ -6,27 +8,19 @@ use std::sync::{Arc, RwLock}; use once_cell::sync::{Lazy, OnceCell}; use crate::modules::get_module; -use crate::vm::VM; +use feint_code_gen::*; -use super::gen; -use super::new; -use super::result::{Args, CallResult, Params}; +use crate::new; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; use super::func_trait::FuncTrait; use super::ns::Namespace; +use super::{Args, CallResult, Params}; -pub type IntrinsicFn = fn(ObjectRef, Args, &mut VM) -> CallResult; +pub type IntrinsicFn = fn(ObjectRef, Args) -> CallResult; -// Intrinsic Function Type --------------------------------------------- - -gen::type_and_impls!(IntrinsicFuncType, IntrinsicFunc); - -pub static INTRINSIC_FUNC_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(IntrinsicFuncType::new())); - -// IntrinsicFunc Object ------------------------------------------------ +std_type!(INTRINSIC_FUNC_TYPE, IntrinsicFuncType); pub struct IntrinsicFunc { ns: Namespace, @@ -38,7 +32,7 @@ pub struct IntrinsicFunc { func: IntrinsicFn, } -gen::standard_object_impls!(IntrinsicFunc); +standard_object_impls!(IntrinsicFunc); impl IntrinsicFunc { pub fn new( @@ -49,12 +43,15 @@ impl IntrinsicFunc { doc: ObjectRef, func: IntrinsicFn, ) -> Self { - Self { + let params_tuple = new::tuple(params.iter().map(new::str).collect()); + + let mut instance = Self { ns: Namespace::with_entries(&[ // Instance Attributes ("$module_name", new::str(module_name.as_str())), ("$full_name", new::str(format!("{module_name}.{name}"))), ("$name", new::str(name.as_str())), + ("$params", params_tuple), ("$doc", doc), ]), module_name, @@ -63,7 +60,14 @@ impl IntrinsicFunc { this_type, params, func, - } + }; + + let arity = (&instance as &dyn FuncTrait).arity(); + let has_var_args = (&instance as &dyn FuncTrait).has_var_args(); + instance.ns_mut().insert("$arity", new::int(arity)); + instance.ns_mut().insert("$has_var_args", new::bool(has_var_args)); + + instance } pub fn this_type(&self) -> Option { @@ -98,7 +102,7 @@ impl FuncTrait for IntrinsicFunc { } impl ObjectTrait for IntrinsicFunc { - gen::object_trait_header!(INTRINSIC_FUNC_TYPE); + object_trait_header!(INTRINSIC_FUNC_TYPE); fn module(&self) -> ObjectRef { self.module.get_or_init(|| get_module(&self.module_name)).clone() @@ -109,12 +113,12 @@ impl ObjectTrait for IntrinsicFunc { impl fmt::Display for IntrinsicFunc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", FuncTrait::format_string(self, Some(self.id()))) + write!(f, "{}", FuncTrait::format_string(self, None)) } } impl fmt::Debug for IntrinsicFunc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") + write!(f, "{}", FuncTrait::format_string(self, Some(self.id()))) } } diff --git a/feint-builtins/src/types/iterator.rs b/feint-builtins/src/types/iterator.rs new file mode 100644 index 0000000..177872b --- /dev/null +++ b/feint-builtins/src/types/iterator.rs @@ -0,0 +1,90 @@ +//! Builtin `Iterator` type. +use std::any::Any; +use std::fmt; +use std::sync::{Arc, RwLock}; + +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; +use super::ns::Namespace; + +pub static ITERATOR_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "Iterator")); + + { + type_ref.write().unwrap().ns_mut().extend(&[ + // Instance Methods ---------------------------------------- + meth!("next", type_ref, &[], "", |this, _| { + let mut this = this.write().unwrap(); + let this = this.down_to_iterator_mut().unwrap(); + this.next() + }), + meth!("peek", type_ref, &[], "", |this, _| { + let this = this.write().unwrap(); + let this = this.down_to_iterator().unwrap(); + this.peek() + }), + ]); + } + + type_ref +}); + +pub struct FIIterator { + ns: Namespace, + wrapped: Vec, + current: usize, +} + +standard_object_impls!(FIIterator); + +impl FIIterator { + pub fn new(wrapped: Vec) -> Self { + Self { ns: Namespace::default(), wrapped, current: 0 } + } + + fn next(&mut self) -> ObjectRef { + let obj = self.get_or_nil(self.current); + if self.current < self.len() { + self.current += 1; + } + obj + } + + fn peek(&self) -> ObjectRef { + self.get_or_nil(self.current) + } + + fn len(&self) -> usize { + self.wrapped.len() + } + + fn get_or_nil(&self, index: usize) -> ObjectRef { + if index >= self.len() { + new::nil() + } else { + self.wrapped[index].clone() + } + } +} + +impl ObjectTrait for FIIterator { + object_trait_header!(ITERATOR_TYPE); +} + +impl fmt::Display for FIIterator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") + } +} + +impl fmt::Debug for FIIterator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} diff --git a/feint-builtins/src/types/list.rs b/feint-builtins/src/types/list.rs new file mode 100644 index 0000000..0ab5440 --- /dev/null +++ b/feint-builtins/src/types/list.rs @@ -0,0 +1,237 @@ +//! Builtin `List` type. +use std::any::Any; +use std::fmt; +use std::sync::{Arc, RwLock}; + +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; +use super::ns::Namespace; +use super::seq; + +pub static LIST_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "List")); + + { + type_ref.write().unwrap().ns_mut().extend(&[ + // Instance Attributes ------------------------------------- + prop!("length", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_list().unwrap(); + new::int(this.len()) + }), + prop!("is_empty", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_list().unwrap(); + new::bool(this.len() == 0) + }), + prop!("sum", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_list().unwrap(); + let items = &this.items.read().unwrap(); + seq::sum(items) + }), + // Instance Methods ---------------------------------------- + meth!( + "extend", + type_ref, + &["items"], + "Push items and return this.", + |this, args| { + let return_val = this.clone(); + let this = this.read().unwrap(); + let this = this.down_to_list().unwrap(); + if let Some(err) = this.extend(args[0].clone()) { + return err; + } + return_val + } + ), + meth!("get", type_ref, &["index"], "", |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_list().unwrap(); + let index = use_arg_usize!(get, index, args, 0); + let result = match this.get(index) { + Some(obj) => obj, + None => new::nil(), + }; + result + }), + meth!("has", type_ref, &["member"], "", |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_list().unwrap(); + let items = &this.items.read().unwrap(); + seq::has(items, &args) + }), + meth!("iter", type_ref, &[], "", |this_ref, _| { + let this = this_ref.read().unwrap(); + let this = this.down_to_list().unwrap(); + let items = this.items.read().unwrap(); + new::iterator(items.clone()) + }), + meth!("join", type_ref, &["sep"], "", |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_list().unwrap(); + let items = &this.items.read().unwrap(); + seq::join(items, &args) + }), + meth!("pop", type_ref, &[], "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_list().unwrap(); + match this.pop() { + Some(obj) => obj, + None => new::nil(), + } + }), + meth!( + "push", + type_ref, + &["item"], + "Push item and return it.", + |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_list().unwrap(); + let arg = args[0].clone(); + this.push(arg.clone()); + arg + } + ), + ]); + } + + type_ref +}); + +std_type!(NIL_TYPE, NilType); + +pub struct List { + ns: Namespace, + items: RwLock>, +} + +standard_object_impls!(List); + +impl List { + pub fn new(items: Vec) -> Self { + Self { ns: Namespace::default(), items: RwLock::new(items) } + } + + fn len(&self) -> usize { + let items = self.items.read().unwrap(); + items.len() + } + + fn push(&self, item: ObjectRef) { + let items = &mut self.items.write().unwrap(); + items.push(item); + } + + fn extend(&self, obj_ref: ObjectRef) -> Option { + let obj = obj_ref.read().unwrap(); + let items = &mut self.items.write().unwrap(); + if let Some(list) = obj.down_to_list() { + let new_items = list.items.read().unwrap(); + for item in new_items.iter() { + items.push(item.clone()); + } + } else if let Some(tuple) = obj.down_to_tuple() { + for item in tuple.iter() { + items.push(item.clone()); + } + } else { + // TODO: Do type checking at a higher level + let msg = format!( + "List.extend() expected List or Tuple; got {}", + obj.class().read().unwrap() + ); + return Some(new::type_err(msg, obj_ref.clone())); + } + None + } + + fn pop(&self) -> Option { + let items = &mut self.items.write().unwrap(); + if let Some(item) = items.pop() { + Some(item.clone()) + } else { + None + } + } + + fn get(&self, index: usize) -> Option { + let items = self.items.read().unwrap(); + if let Some(item) = items.get(index) { + Some(item.clone()) + } else { + None + } + } +} + +impl ObjectTrait for List { + object_trait_header!(LIST_TYPE); + + fn get_item(&self, index: usize, this: ObjectRef) -> ObjectRef { + if let Some(item) = self.get(index) { + item.clone() + } else { + self.index_out_of_bounds(index, this) + } + } + + fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { + if self.is(rhs) || rhs.is_always() { + return true; + } + if let Some(rhs) = rhs.down_to_list() { + if self.len() != rhs.len() { + return false; + } + let items = self.items.read().unwrap(); + let rhs_items = rhs.items.read().unwrap(); + for (a, b) in items.iter().zip(rhs_items.iter()) { + let a = a.read().unwrap(); + let b = b.read().unwrap(); + if !a.is_equal(&*b) { + return false; + } + } + true + } else { + false + } + } +} + +// Display ------------------------------------------------------------- + +impl fmt::Display for List { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let this_id = self.id(); + let items = self.items.read().unwrap(); + let items: Vec = items + .iter() + .map(|item| { + let item = item.read().unwrap(); + if item.id() == this_id { + "[...]".to_owned() + } else { + format!("{:?}", &*item) + } + }) + .collect(); + let items_str = items.join(", "); + write!(f, "[{items_str}]") + } +} + +impl fmt::Debug for List { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} diff --git a/feint-builtins/src/types/map.rs b/feint-builtins/src/types/map.rs new file mode 100644 index 0000000..9421ffe --- /dev/null +++ b/feint-builtins/src/types/map.rs @@ -0,0 +1,217 @@ +use std::any::Any; +use std::fmt; +use std::sync::{Arc, RwLock}; + +use indexmap::IndexMap; +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; +use super::ns::Namespace; + +pub static MAP_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "Map")); + + { + type_ref.write().unwrap().ns_mut().extend(&[ + // Instance Attributes ------------------------------------- + prop!("length", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_map().unwrap(); + new::int(this.len()) + }), + prop!("is_empty", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_map().unwrap(); + new::bool(this.is_empty()) + }), + // Instance Methods ---------------------------------------- + meth!( + "add", + type_ref, + &["key", "val"], + "Add entry to Map. + + # Args + + - key: Str + - value: Any + + ", + |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_map().unwrap(); + let arg = use_arg!(args, 0); + let key = use_arg_str!(get, key, arg); + let val = args[1].clone(); + this.insert(key, val); + new::nil() + } + ), + meth!( + "get", + type_ref, + &["key"], + "Get value for key from Map. + + # Args + + - key: Key + + # Returns + + - Any: If key is present + - nil: If key is not present + + > NOTE: There's no way to distinguish between a key that isn't present + > versus a key that has `nil` as its value. To avoid ambiguity, don't + > store `nil` values. + + ", + |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_map().unwrap(); + let arg = use_arg!(args, 0); + let key = use_arg_str!(get, key, arg); + match this.get(key) { + Some(obj) => obj, + None => new::nil(), + } + } + ), + meth!("has", type_ref, &["member"], "", |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_map().unwrap(); + let arg = use_arg!(args, 0); + let key = use_arg_str!(get, key, arg); + let result = this.contains_key(key); + new::bool(result) + }), + meth!("iter", type_ref, &[], "", |this_ref, _| { + let this = this_ref.read().unwrap(); + let this = this.down_to_map().unwrap(); + let mut items = vec![]; + for (name, val) in this.entries.read().unwrap().iter() { + items.push(new::tuple(vec![new::str(name), val.clone()])) + } + new::iterator(items) + }), + ]); + } + + type_ref +}); + +// Map ---------------------------------------------------------- + +pub struct Map { + ns: Namespace, + entries: RwLock>, +} + +standard_object_impls!(Map); + +impl Default for Map { + fn default() -> Self { + Self { ns: Namespace::default(), entries: RwLock::new(IndexMap::default()) } + } +} + +impl Map { + pub fn new(entries: IndexMap) -> Self { + Self { ns: Namespace::default(), entries: RwLock::new(entries) } + } + + pub fn len(&self) -> usize { + let entries = self.entries.read().unwrap(); + entries.len() + } + + pub fn is_empty(&self) -> bool { + let entries = self.entries.read().unwrap(); + entries.is_empty() + } + + pub fn insert>(&self, key: S, val: ObjectRef) { + let entries = &mut self.entries.write().unwrap(); + entries.insert(key.into(), val); + } + + pub fn get(&self, name: &str) -> Option { + let entries = self.entries.read().unwrap(); + if let Some(val) = entries.get(name) { + Some(val.clone()) + } else { + None + } + } + + pub fn contains_key(&self, key: &str) -> bool { + let entries = self.entries.read().unwrap(); + entries.contains_key(key) + } + + pub fn entries(&self) -> &RwLock> { + &self.entries + } +} + +impl ObjectTrait for Map { + object_trait_header!(MAP_TYPE); + + fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { + if self.is(rhs) || rhs.is_always() { + return true; + } + if let Some(rhs) = rhs.down_to_map() { + if self.len() != rhs.len() { + return false; + } + let entries = self.entries.read().unwrap(); + let rhs_entries = rhs.entries.read().unwrap(); + entries.iter().all(|(name, a_ref)| { + if let Some(b_ref) = rhs_entries.get(name) { + let a = a_ref.read().unwrap(); + let b = b_ref.read().unwrap(); + a.is_equal(&*b) + } else { + false + } + }) + } else { + false + } + } +} + +// Display ------------------------------------------------------------- + +impl fmt::Display for Map { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let this_id = self.id(); + let entries = self.entries.read().unwrap(); + let entries: Vec = entries + .iter() + .map(|(name, val)| { + let val = val.read().unwrap(); + if val.id() == this_id { + "{...}".to_owned() + } else { + format!("{name:?} => {:?}", &*val) + } + }) + .collect(); + let string = entries.join(", "); + write!(f, "{{{string}}}") + } +} + +impl fmt::Debug for Map { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} diff --git a/feint-builtins/src/types/mod.rs b/feint-builtins/src/types/mod.rs new file mode 100644 index 0000000..a049dc8 --- /dev/null +++ b/feint-builtins/src/types/mod.rs @@ -0,0 +1,45 @@ +pub use base::{ObjectRef, ObjectTrait, TypeRef}; +pub use func::Func; +pub use func_trait::FuncTrait; +pub use intrinsic_func::IntrinsicFunc; +pub use map::Map; +pub use module::Module; + +pub type ThisOpt = Option; +pub type Params = Vec; +pub type Args = Vec; +pub type CallResult = ObjectRef; + +mod base; +mod func_trait; + +// Namespace (not a type) +pub(crate) mod ns; + +pub mod code; + +// Intrinsic Types +pub mod always; +pub mod bool; +pub mod bound_func; +pub mod cell; +pub mod class; +pub mod closure; +pub mod custom; +pub mod err; +pub mod err_type; +pub mod file; +pub mod float; +pub mod func; +pub mod int; +pub mod intrinsic_func; +pub mod iterator; +pub mod list; +pub mod map; +pub mod module; +pub mod nil; +pub mod prop; +pub mod seq; +pub mod str; +pub mod tuple; +pub mod util; diff --git a/src/types/module.rs b/feint-builtins/src/types/module.rs similarity index 66% rename from src/types/module.rs rename to feint-builtins/src/types/module.rs index 31bfdaa..6b4f5c2 100644 --- a/src/types/module.rs +++ b/feint-builtins/src/types/module.rs @@ -4,31 +4,28 @@ use std::sync::{Arc, RwLock}; use once_cell::sync::Lazy; +use feint_code_gen::*; + use crate::util::check_args; -use crate::vm::{Code, RuntimeErr}; -use super::gen; -use super::new; +use super::base::{ObjectRef, ObjectTrait, TypeRef}; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use super::class::Type; +use super::code::Code; use super::map::Map; use super::ns::Namespace; +use crate::new; -// Module Type --------------------------------------------------------- - -gen::type_and_impls!(ModuleType, Module); +pub static MODULE_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "Module")); -pub static MODULE_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(ModuleType::new()); - let mut type_obj = type_ref.write().unwrap(); + { + type_ref.write().unwrap().ns_mut().extend(&[meth!( + "new", + type_ref, + &["name", "path", "doc", "attrs"], + "Create a new Module - type_obj.add_attrs(&[gen::meth!( - "new", - type_ref, - &["name", "path", "doc", "attrs"], - "Create a new Module - # Args - name: Str @@ -39,37 +36,38 @@ pub static MODULE_TYPE: Lazy = Lazy::new(|| { # Returns Module", - |_, args, _| { - if let Err(err) = check_args("new", &args, false, 4, Some(4)) { - return Ok(err); - }; - - let name_arg = gen::use_arg!(args, 0); - let path_arg = gen::use_arg!(args, 1); - let doc_arg = gen::use_arg!(args, 2); - let attrs_arg = gen::use_arg!(args, 3); - - let name = gen::use_arg_str!(new, name, name_arg); - let path = gen::use_arg_str!(new, path, path_arg); - let doc = gen::use_arg_str!(new, doc, doc_arg); - let attrs = gen::use_arg_map!(new, attrs, attrs_arg); - - let module = Module::with_map_entries( - attrs, - name.to_owned(), - path.to_owned(), - Code::default(), - Some(doc.to_owned()), - ); - - Ok(gen::obj_ref!(module)) - } - )]); - - type_ref.clone() + |_, args| { + if let Err(err) = check_args("new", &args, false, 4, Some(4)) { + return err; + }; + + let name_arg = use_arg!(args, 0); + let path_arg = use_arg!(args, 1); + let doc_arg = use_arg!(args, 2); + let attrs_arg = use_arg!(args, 3); + + let name = use_arg_str!(new, name, name_arg); + let path = use_arg_str!(new, path, path_arg); + let doc = use_arg_str!(new, doc, doc_arg); + let attrs = use_arg_map!(new, attrs, attrs_arg); + + let module = Module::with_map_entries( + attrs, + name.to_owned(), + path.to_owned(), + Code::default(), + Some(doc.to_owned()), + ); + + obj_ref!(module) + } + )]); + } + + type_ref }); -// Module Object ------------------------------------------------------- +// Module ------------------------------------------------------- pub struct Module { ns: Namespace, @@ -78,7 +76,7 @@ pub struct Module { code: Code, } -gen::standard_object_impls!(Module); +standard_object_impls!(Module); impl Module { /// NOTE: The `$doc` attribute should only be passed for intrinsic @@ -164,7 +162,7 @@ impl Module { } impl ObjectTrait for Module { - gen::object_trait_header!(MODULE_TYPE); + object_trait_header!(MODULE_TYPE); } // Display ------------------------------------------------------------- diff --git a/feint-builtins/src/types/nil.rs b/feint-builtins/src/types/nil.rs new file mode 100644 index 0000000..8b23e4b --- /dev/null +++ b/feint-builtins/src/types/nil.rs @@ -0,0 +1,47 @@ +//! Builtin `Nil` type. +use std::any::Any; +use std::fmt; +use std::sync::{Arc, RwLock}; + +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use super::base::{ObjectTrait, TypeRef}; +use super::class::Type; +use super::ns::Namespace; + +std_type!(NIL_TYPE, NilType); + +pub struct Nil { + ns: Namespace, +} + +standard_object_impls!(Nil); + +impl Nil { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { ns: Namespace::default() } + } +} + +impl ObjectTrait for Nil { + object_trait_header!(NIL_TYPE); + + fn bool_val(&self) -> Option { + Some(false) + } +} + +impl fmt::Display for Nil { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "nil") + } +} + +impl fmt::Debug for Nil { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} diff --git a/src/types/ns.rs b/feint-builtins/src/types/ns.rs similarity index 87% rename from src/types/ns.rs rename to feint-builtins/src/types/ns.rs index b0cb98d..d5a197f 100644 --- a/src/types/ns.rs +++ b/feint-builtins/src/types/ns.rs @@ -36,6 +36,12 @@ impl Namespace { ns } + pub fn from_map(map: &Map) -> Self { + let mut ns = Self::default(); + ns.extend_from_map(map); + ns + } + pub fn clear(&mut self) { self.objects.clear() } @@ -62,6 +68,20 @@ impl Namespace { self.objects.insert(name.into(), obj); } + pub fn get_or_insert ObjectRef>( + &mut self, + name: &str, + mut make_obj: F, + ) -> ObjectRef { + if let Some(obj) = self.get(name) { + obj.clone() + } else { + let obj = make_obj(); + self.insert(name, obj.clone()); + obj + } + } + /// Set an object's value. This will only succeed if the object /// already exists in the namespace. pub fn set(&mut self, name: &str, obj: ObjectRef) -> bool { diff --git a/src/types/prop.rs b/feint-builtins/src/types/prop.rs similarity index 58% rename from src/types/prop.rs rename to feint-builtins/src/types/prop.rs index 5f9309e..c018098 100644 --- a/src/types/prop.rs +++ b/feint-builtins/src/types/prop.rs @@ -1,3 +1,5 @@ +//! Builtiln `Prop` type. +//! //! The `Prop` type wraps a function that is called to compute the value //! of an attribute. use std::any::Any; @@ -6,28 +8,20 @@ use std::sync::{Arc, RwLock}; use once_cell::sync::Lazy; -use super::gen; -use super::new; +use feint_code_gen::*; -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; use super::ns::Namespace; -// Prop Type ----------------------------------------------------------- - -gen::type_and_impls!(PropType, Prop); - -pub static PROP_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(PropType::new())); - -// Prop Object --------------------------------------------------------- +std_type!(PROP_TYPE, NilType); pub struct Prop { ns: Namespace, getter: ObjectRef, } -gen::standard_object_impls!(Prop); +standard_object_impls!(Prop); impl Prop { pub fn new(getter: ObjectRef) -> Self { @@ -40,11 +34,9 @@ impl Prop { } impl ObjectTrait for Prop { - gen::object_trait_header!(PROP_TYPE); + object_trait_header!(PROP_TYPE); } -// Display ------------------------------------------------------------- - impl fmt::Display for Prop { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "", self.getter.read().unwrap()) diff --git a/feint-builtins/src/types/seq.rs b/feint-builtins/src/types/seq.rs new file mode 100644 index 0000000..f33a710 --- /dev/null +++ b/feint-builtins/src/types/seq.rs @@ -0,0 +1,65 @@ +//! Common sequence operations + +use num_bigint::BigInt; + +use feint_code_gen::{use_arg, use_arg_str}; + +use crate::new; + +use super::base::ObjectRef; +use super::Args; + +pub fn has(items: &[ObjectRef], args: &Args) -> ObjectRef { + if items.is_empty() { + return new::bool(false); + } + let member = use_arg!(args, 0); + for item in items.iter() { + if member.is_equal(&*item.read().unwrap()) { + return new::bool(true); + } + } + new::bool(false) +} + +pub fn join(items: &[ObjectRef], args: &Args) -> ObjectRef { + if items.is_empty() { + return new::empty_str(); + } + + let n_items = items.len(); + let last_i = n_items - 1; + let arg = use_arg!(args, 0); + let sep = use_arg_str!(join, sep, arg); + + // XXX: Guessing at average word length + let capacity = n_items * 5 + ((last_i) * sep.len()); + let mut string = String::with_capacity(capacity); + + for (i, item) in items.iter().enumerate() { + let item = item.read().unwrap(); + let str = item.to_string(); + string.push_str(&str); + if i != last_i { + string.push_str(sep); + } + } + + new::str(string) +} + +pub fn sum(items: &[ObjectRef]) -> ObjectRef { + let mut sum = new::int(BigInt::from(0)); + for item in items.iter() { + sum = { + let a = sum.read().unwrap(); + let b = item.read().unwrap(); + if let Some(new_sum) = (*a).add(&*b) { + new_sum + } else { + return new::type_err("Could not add object to sum", item.clone()); + } + } + } + sum +} diff --git a/feint-builtins/src/types/str.rs b/feint-builtins/src/types/str.rs new file mode 100644 index 0000000..4754b23 --- /dev/null +++ b/feint-builtins/src/types/str.rs @@ -0,0 +1,203 @@ +use std::any::Any; +use std::fmt; +use std::str::EscapeDefault; +use std::sync::{Arc, RwLock}; + +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; +use super::ns::Namespace; + +pub static STR_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "Str")); + + { + type_ref.write().unwrap().ns_mut().extend(&[ + // Class Methods ----------------------------------------------- + meth!("new", type_ref, &["value"], "", |_, args| { + let arg = use_arg!(args, 0); + if arg.is_str() { + args[0].clone() + } else { + new::str(arg.to_string()) + } + }), + // Instance Attributes ------------------------------------- + prop!("length", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let value = this.get_str_val().unwrap(); + new::int(value.len()) + }), + // Instance Methods ---------------------------------------- + meth!("starts_with", type_ref, &["prefix"], "", |this, args| { + let this = this.read().unwrap(); + let value = this.get_str_val().unwrap(); + let arg = use_arg!(args, 0); + let prefix = use_arg_str!(starts_with, prefix, arg); + new::bool(value.starts_with(prefix)) + }), + meth!("ends_with", type_ref, &["suffix"], "", |this, args| { + let this = this.read().unwrap(); + let value = this.get_str_val().unwrap(); + let arg = use_arg!(args, 0); + let suffix = use_arg_str!(ends_with, suffix, arg); + new::bool(value.ends_with(suffix)) + }), + meth!("upper", type_ref, &[], "", |this, _| { + let this = this.read().unwrap(); + let value = this.get_str_val().unwrap(); + new::str(value.to_uppercase()) + }), + meth!("lower", type_ref, &[], "", |this, _| { + let this = this.read().unwrap(); + let value = this.get_str_val().unwrap(); + new::str(value.to_lowercase()) + }), + // meth!( + // "render", + // type_ref, + // &["context"], + // "Render string as template + // + // Templates may contain `{{ name }}` vars which will be replaced with the + // values provided in the context map. + // + // # Args + // + // - context: Map A map containing values to be rendered into the + // template. + // + // ", + // |this, args| { + // let context = args[0].clone(); + // let result = render_template(this.clone(), context)?; + // Ok(result) + // } + // ), + meth!("repeat", type_ref, &["count"], "", |this, args| { + let this = this.read().unwrap(); + let value = this.get_str_val().unwrap(); + let count = use_arg_usize!(get, index, args, 0); + new::str(value.repeat(count)) + }), + meth!("replace", type_ref, &["old", "new"], "", |this, args| { + let this = this.read().unwrap(); + let value = this.get_str_val().unwrap(); + let arg1 = use_arg!(args, 0); + let arg2 = use_arg!(args, 1); + let old = use_arg_str!(replace, old, arg1); + let new = use_arg_str!(replace, new, arg2); + let result = value.replace(old, new); + new::str(result) + }), + meth!("remove_prefix", type_ref, &["prefix"], "", |this_ref, args| { + let this = this_ref.read().unwrap(); + let val = this.get_str_val().unwrap(); + let arg = use_arg!(args, 0); + let prefix = use_arg_str!(starts_with, prefix, arg); + if let Some(new_val) = val.strip_prefix(prefix) { + new::str(new_val) + } else { + drop(this); + this_ref + } + }), + ]); + } + + type_ref +}); + +// Str ---------------------------------------------------------- + +pub struct Str { + ns: Namespace, + value: String, +} + +standard_object_impls!(Str); + +impl Str { + pub fn new(value: String) -> Self { + Self { + ns: Namespace::with_entries(&[ + // Instance Attributes + // ("length", new::int(value.len())), + ]), + value, + } + } + + pub fn value(&self) -> &str { + self.value.as_str() + } + + /// XXX: Make this correspond to how FeInt escapes strings and + /// improve FeInt's handling of escapes, particular unicode. + fn escape(&self) -> EscapeDefault<'_> { + self.value.escape_default() + } +} + +impl ObjectTrait for Str { + object_trait_header!(STR_TYPE); + + fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { + if self.is(rhs) || rhs.is_always() { + true + } else if let Some(rhs) = rhs.down_to_str() { + self.is(rhs) || self.value() == rhs.value() + } else { + false + } + } + + fn add(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_str() { + let a = self.value(); + let b = rhs.value(); + let mut value = String::with_capacity(a.len() + b.len()); + value.push_str(a); + value.push_str(b); + let value = new::str(value); + Some(value) + } else { + None + } + } + + fn less_than(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_str() { + Some(self.value() < rhs.value()) + } else { + None + } + } + + fn greater_than(&self, rhs: &dyn ObjectTrait) -> Option { + if let Some(rhs) = rhs.down_to_str() { + Some(self.value() > rhs.value()) + } else { + None + } + } +} + +// Display ------------------------------------------------------------- + +impl fmt::Display for Str { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.value) + } +} + +impl fmt::Debug for Str { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\"{}\"", self.escape()) + } +} diff --git a/feint-builtins/src/types/tuple.rs b/feint-builtins/src/types/tuple.rs new file mode 100644 index 0000000..1e41d62 --- /dev/null +++ b/feint-builtins/src/types/tuple.rs @@ -0,0 +1,148 @@ +use std::any::Any; +use std::fmt; +use std::slice::Iter; +use std::sync::{Arc, RwLock}; + +use once_cell::sync::Lazy; + +use feint_code_gen::*; + +use crate::new; + +use super::base::{ObjectRef, ObjectTrait, TypeRef}; +use super::class::Type; +use super::ns::Namespace; +use super::seq; + +pub static TUPLE_TYPE: Lazy = Lazy::new(|| { + let type_ref = obj_ref!(Type::new("std", "Tuple")); + + { + type_ref.write().unwrap().ns_mut().extend(&[ + // Instance Attributes ------------------------------------- + prop!("length", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_tuple().unwrap(); + new::int(this.len()) + }), + prop!("is_empty", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_tuple().unwrap(); + new::bool(this.len() == 0) + }), + prop!("sum", type_ref, "", |this, _| { + let this = this.read().unwrap(); + let this = this.down_to_tuple().unwrap(); + seq::sum(&this.items) + }), + // Instance Methods ---------------------------------------- + meth!("get", type_ref, &["index"], "", |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_tuple().unwrap(); + let index = use_arg_usize!(get, index, args, 0); + match this.get(index) { + Some(obj) => obj, + None => new::nil(), + } + }), + meth!("has", type_ref, &["member"], "", |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_tuple().unwrap(); + seq::has(&this.items, &args) + }), + meth!("iter", type_ref, &[], "", |this_ref, _| { + let this = this_ref.read().unwrap(); + let this = this.down_to_tuple().unwrap(); + new::iterator(this.items.clone()) + }), + meth!("join", type_ref, &["sep"], "", |this, args| { + let this = this.read().unwrap(); + let this = this.down_to_tuple().unwrap(); + seq::join(&this.items, &args) + }), + ]); + } + + type_ref +}); + +// Tuple -------------------------------------------------------- + +pub struct Tuple { + ns: Namespace, + items: Vec, +} + +standard_object_impls!(Tuple); + +impl Tuple { + pub fn new(items: Vec) -> Self { + Self { ns: Namespace::default(), items } + } + + pub(crate) fn iter(&self) -> Iter<'_, ObjectRef> { + self.items.iter() + } + + pub(crate) fn len(&self) -> usize { + self.items.len() + } + + fn get(&self, index: usize) -> Option { + if let Some(item) = self.items.get(index) { + Some(item.clone()) + } else { + None + } + } +} + +impl ObjectTrait for Tuple { + object_trait_header!(TUPLE_TYPE); + + fn get_item(&self, index: usize, this: ObjectRef) -> ObjectRef { + if let Some(item) = self.items.get(index) { + item.clone() + } else { + self.index_out_of_bounds(index, this) + } + } + + fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { + if self.is(rhs) || rhs.is_always() { + return true; + } + if let Some(rhs) = rhs.down_to_tuple() { + if self.len() != rhs.len() { + return false; + } + for (a, b) in self.iter().zip(rhs.iter()) { + let a = a.read().unwrap(); + let b = b.read().unwrap(); + if !a.is_equal(&*b) { + return false; + } + } + true + } else { + false + } + } +} + +impl fmt::Display for Tuple { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let num_items = self.len(); + let items: Vec = + self.iter().map(|item| format!("{:?}", &*item.read().unwrap())).collect(); + let items_str = items.join(", "); + let trailing_comma = if num_items == 1 { "," } else { "" }; + write!(f, "({items_str}{trailing_comma})") + } +} + +impl fmt::Debug for Tuple { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} diff --git a/feint-builtins/src/types/util.rs b/feint-builtins/src/types/util.rs new file mode 100644 index 0000000..5bdd633 --- /dev/null +++ b/feint-builtins/src/types/util.rs @@ -0,0 +1,41 @@ +use num_bigint::BigInt; +use num_traits::{FromPrimitive, ToPrimitive}; + +use super::float::Float; +use super::int::Int; + +/// Compare Int and Float for equality. +pub fn eq_int_float(int: &Int, float: &Float) -> bool { + let float_val = float.value(); + if float_val.fract() == 0.0 { + let int_val = int.value(); + let float_as_int = BigInt::from_f64(*float_val).unwrap(); + *int_val == float_as_int + } else { + false + } +} + +/// Check Float < Int +pub fn float_lt_int(lhs_float: &Float, rhs_int: &Int) -> bool { + let rhs_as_float = rhs_int.value().to_f64().unwrap(); + *lhs_float.value() < rhs_as_float +} + +/// Check Int < Float +pub fn int_lt_float(lhs_int: &Int, rhs_float: &Float) -> bool { + let lhs_as_float = lhs_int.value().to_f64().unwrap(); + lhs_as_float < *rhs_float.value() +} + +/// Check Float > Int +pub fn float_gt_int(lhs_float: &Float, rhs_int: &Int) -> bool { + let rhs_as_float = rhs_int.value().to_f64().unwrap(); + *lhs_float.value() > rhs_as_float +} + +/// Check Int > Float +pub fn int_gt_float(lhs_int: &Int, rhs_float: &Float) -> bool { + let lhs_as_float = lhs_int.value().to_f64().unwrap(); + lhs_as_float > *rhs_float.value() +} diff --git a/src/util/call.rs b/feint-builtins/src/util.rs similarity index 97% rename from src/util/call.rs rename to feint-builtins/src/util.rs index c55bef6..964088b 100644 --- a/src/util/call.rs +++ b/feint-builtins/src/util.rs @@ -1,4 +1,5 @@ -use crate::types::{new, Args, ObjectRef}; +use crate::new; +use crate::types::{Args, ObjectRef}; /// Check args and return info. /// diff --git a/feint-cli/Cargo.toml b/feint-cli/Cargo.toml new file mode 100644 index 0000000..fc52682 --- /dev/null +++ b/feint-cli/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "feint-cli" +edition.workspace = true +version.workspace = true +description.workspace = true +authors.workspace = true +readme.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +feint-builtins = { path = "../feint-builtins" } +feint-compiler = { path = "../feint-compiler" } +feint-driver = { path = "../feint-driver" } + +clap.workspace = true +clap_complete.workspace = true +dirs.workspace = true +env_logger.workspace = true +rustyline.workspace = true + +[build-dependencies] +clap.workspace = true +clap_complete.workspace = true + +[[bin]] +name = "feint" +path = "src/main.rs" diff --git a/feint-cli/build.rs b/feint-cli/build.rs new file mode 100644 index 0000000..8ba6b0f --- /dev/null +++ b/feint-cli/build.rs @@ -0,0 +1,43 @@ +use std::env; +use std::fs::File; +use std::io::Error; +use std::path::Path; +use std::process; + +use clap_complete::{self, shells}; + +include!("src/cli.rs"); + +fn main() -> Result<(), Error> { + let out_dir = match env::var_os("OUT_DIR") { + Some(out_dir) => out_dir, + None => { + eprintln!("OUT_DIR env var not set"); + eprintln!("Cannot proceed"); + eprintln!("Aborting"); + process::exit(1); + } + }; + + let out_dir = Path::new(&out_dir); + + stamp(out_dir)?; + make_shell_completion_scripts(out_dir)?; + + Ok(()) +} + +/// Adds a stamp file to the build output directory so that the latest +/// build can be found by other tools. +fn stamp(out_dir: &Path) -> Result<(), Error> { + let stamp_path = Path::new(out_dir).join("feint.stamp"); + File::create(stamp_path)?; + Ok(()) +} + +fn make_shell_completion_scripts(out_dir: &Path) -> Result<(), Error> { + let mut cmd = build_cli(); + clap_complete::generate_to(shells::Bash, &mut cmd, "feint", out_dir)?; + clap_complete::generate_to(shells::Fish, &mut cmd, "feint", out_dir)?; + Ok(()) +} diff --git a/src/cli.rs b/feint-cli/src/cli.rs similarity index 100% rename from src/cli.rs rename to feint-cli/src/cli.rs diff --git a/src/main.rs b/feint-cli/src/main.rs similarity index 76% rename from src/main.rs rename to feint-cli/src/main.rs index 6211c30..0684216 100644 --- a/src/main.rs +++ b/feint-cli/src/main.rs @@ -1,3 +1,9 @@ +mod cli; +mod repl; + +#[cfg(test)] +mod tests; + use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; @@ -5,11 +11,9 @@ use std::process::ExitCode; use clap::{parser::ValueSource, ArgMatches}; -use feint::cli; -use feint::exe::Executor; -use feint::repl::Repl; -use feint::result::ExeResult; -use feint::vm::{CallDepth, VMState, DEFAULT_MAX_CALL_DEPTH}; +use feint_driver::{Driver, DriverErrKind, DriverResult}; + +use repl::Repl; /// Interpret a file if one is specified. Otherwise, run the REPL. fn main() -> ExitCode { @@ -21,7 +25,13 @@ fn main() -> ExitCode { let debug = *matches.get_one::("debug").unwrap(); let max_call_depth = match matches.value_source("max_call_depth") { - Some(ValueSource::DefaultValue) => DEFAULT_MAX_CALL_DEPTH, + Some(ValueSource::DefaultValue) => { + if cfg!(debug_assertions) { + 256 + } else { + 1024 + } + } _ => max_call_depth, }; @@ -38,7 +48,7 @@ fn main() -> ExitCode { } /// Subcommand: run -fn handle_run(matches: &ArgMatches, max_call_depth: CallDepth, debug: bool) -> u8 { +fn handle_run(matches: &ArgMatches, max_call_depth: usize, debug: bool) -> u8 { let file_name = matches.get_one::("FILE_NAME"); let code = matches.get_one::("code"); let dis = *matches.get_one::("dis").unwrap(); @@ -63,44 +73,44 @@ fn handle_run(matches: &ArgMatches, max_call_depth: CallDepth, debug: bool) -> u // error. let incremental = !(code.is_some() || file_name.is_some()); - let mut exe = Executor::new(max_call_depth, argv, incremental, dis, debug); + let mut driver = Driver::new(max_call_depth, argv, incremental, dis, debug); - if let Err(err) = exe.bootstrap() { - return handle_exe_result(Err(err)); + if let Err(err) = driver.bootstrap() { + return handle_driver_result(Err(err)); } - let exe_result = if let Some(code) = code { - exe.execute_text(code) + let result = if let Some(code) = code { + driver.execute_text(code) } else if let Some(file_name) = file_name { if file_name == "-" { - exe.execute_stdin() + driver.execute_stdin() } else if let Some(path) = get_script_file_path(file_name) { - exe.execute_file(path.as_path()) + driver.execute_file(path.as_path()) } else { - exe.execute_module_as_script(file_name) + driver.execute_module_as_script(file_name) } } else { let history_path = create_repl_history_file(&save_repl_history, history_path); - exe.install_sigint_handler(); - let mut repl = Repl::new(history_path, exe); + driver.install_sigint_handler(); + let mut repl = Repl::new(history_path, driver); repl.run() }; - handle_exe_result(exe_result) + handle_driver_result(result) } /// Subcommand: test -fn handle_test(matches: &ArgMatches, max_call_depth: CallDepth, debug: bool) -> u8 { +fn handle_test(matches: &ArgMatches, max_call_depth: usize, debug: bool) -> u8 { let argv: Vec = matches .get_many::("argv") .unwrap_or_default() .map(|v| v.to_string()) .collect(); - let mut exe = Executor::new(max_call_depth, argv, false, false, debug); - if let Err(err) = exe.bootstrap() { - return handle_exe_result(Err(err)); + let mut driver = Driver::new(max_call_depth, argv, false, false, debug); + if let Err(err) = driver.bootstrap() { + return handle_driver_result(Err(err)); } - handle_exe_result(exe.execute_module_as_script("std.test")) + handle_driver_result(driver.execute_module_as_script("std.test")) } // Utilities ----------------------------------------------------------- @@ -184,25 +194,24 @@ fn create_repl_history_file(cond: &bool, path: Option<&String>) -> Option u8 { - match exe_result { - Ok(vm_state) => match vm_state { - VMState::Running => { - eprintln!("VM should be idle or halted, not running"); - 255 - } - VMState::Idle(_) => 0, - VMState::Halted(0) => 0, - VMState::Halted(code) => code, - }, - Err(err) => { - if let Some(exit_code) = err.exit_code() { - exit_code - } else { - 255 +fn handle_driver_result(result: DriverResult) -> u8 { + result.unwrap_or_else(|err| { + if let Some(code) = err.exit_code() { + code + } else { + if matches!( + &err.kind, + DriverErrKind::Bootstrap(_) + | DriverErrKind::CouldNotReadSourceFile(_) + | DriverErrKind::ModuleDirNotFound(_) + | DriverErrKind::ModuleNotFound(_) + | DriverErrKind::ReplErr(_) + ) { + eprintln!("{err}"); } + 255 } - } + }) } /// Get path for str, expanding leading ~ to user home directory. The diff --git a/src/repl.rs b/feint-cli/src/repl.rs similarity index 59% rename from src/repl.rs rename to feint-cli/src/repl.rs index 6222503..54fbf8f 100644 --- a/src/repl.rs +++ b/feint-cli/src/repl.rs @@ -4,40 +4,38 @@ use std::path::PathBuf; use rustyline::config::Configurer; use rustyline::error::ReadlineError; -use crate::compiler::CompErrKind; -use crate::dis; -use crate::exe::Executor; -use crate::parser::ParseErrKind; -use crate::result::{ExeErr, ExeErrKind, ExeResult}; -use crate::scanner::ScanErrKind; -use crate::types::{new, ObjectRef, ObjectTrait}; -use crate::vm::VMState; +use feint_builtins::new; +use feint_builtins::types::{ObjectRef, ObjectTrait}; +use feint_compiler::{CompErrKind, ParseErrKind, ScanErrKind}; +use feint_driver::result::{DriverErr, DriverErrKind, DriverOptResult, DriverResult}; +use feint_driver::Driver; pub struct Repl { module: ObjectRef, reader: rustyline::Editor<()>, history_path: Option, - executor: Executor, + driver: Driver, } impl Repl { - pub fn new(history_path: Option, executor: Executor) -> Self { - let module = new::intrinsic_module("$repl", "$repl", "FeInt REPL module", &[]); + pub fn new(history_path: Option, mut driver: Driver) -> Self { + let module = new::module("$repl", "", "FeInt REPL module", &[]); + driver.add_module("$repl", module.clone()); + let mut reader = - rustyline::Editor::<()>::new().expect("Could initialize readline"); + rustyline::Editor::<()>::new().expect("Could not initialize readline"); reader.set_indent_size(4); reader.set_tab_stop(4); - Repl { module, reader, history_path, executor } + + Repl { module, reader, history_path, driver } } - pub fn run(&mut self) -> ExeResult { + pub fn run(&mut self) -> DriverResult { println!("Welcome to the FeInt REPL (read/eval/print loop)"); println!("Type a line of code, then hit Enter to evaluate it"); self.load_history(); println!("Type .exit or .quit to exit"); - self.executor.add_module("$repl", self.module.clone()); - let result = loop { match self.read_line("→ ", true) { Ok(None) => { @@ -46,8 +44,11 @@ impl Repl { Ok(Some(input)) => { // Evaluate the input. If eval returns a result of // any kind (ok or err), shut down the REPL. - if let Some(result) = self.eval(input.as_str(), true) { - break result; + let result = self.eval(input.as_str(), true); + match result { + Ok(Some(code)) => break Ok(code), + Ok(None) => (), + Err(err) => break Err(err), } } // User hit Ctrl-C @@ -55,17 +56,16 @@ impl Repl { println!("Use Ctrl-D or .exit to exit"); } // User hit Ctrl-D - Err(ReadlineError::Eof) => { - break Ok(VMState::Halted(0)); - } + Err(ReadlineError::Eof) => break Ok(0), // Unexpected error encountered while attempting to read // a line. Err(err) => { - let msg = format!("Could not read line: {}", err); - break Err(ExeErr::new(ExeErrKind::ReplErr(msg))); + let msg = format!("Could not read line: {err}"); + break Err(DriverErr::new(DriverErrKind::ReplErr(msg))); } } }; + result } @@ -85,69 +85,70 @@ impl Repl { } /// Evaluate text. Returns `None` to indicate to the main loop to - /// continue reading and evaluating input. Returns an `ExeResult` to + /// continue reading and evaluating input. Returns `Some(u8)` to /// indicate to the main loop to exit. - pub fn eval(&mut self, text: &str, continue_on_err: bool) -> Option { + pub fn eval(&mut self, text: &str, continue_on_err: bool) -> DriverOptResult { self.add_history_entry(text); if matches!(text, ".exit" | ".quit") { - return Some(Ok(VMState::Halted(0))); + return Ok(Some(0)); } else if self.handle_command(text) { - return None; + return Ok(None); } - let result = self.executor.execute_repl(text, self.module.clone()); - - match result { - Ok(vm_state) => { - return match vm_state { - VMState::Running => None, - VMState::Idle(_) => None, - // Halted: - state => Some(Ok(state)), - }; - } - Err(err) => { - // If the special Exit err is returned, exit. - if let Some(code) = err.exit_code() { - return Some(Ok(VMState::Halted(code))); - } - - // If there's an error executing the current input, try - // to add more lines *if* the error can potentially be - // recovered from by adding more input. - if !(continue_on_err && self.continue_on_err(&err)) { - return None; - } + // If there's an error executing the current input, try to add + // more lines *if* the error can potentially be recovered from + // by adding more input. + self.driver.execute_repl(text, self.module.clone()).or_else(|err| { + if let Some(code) = err.exit_code() { + return Ok(Some(code)); } - } - // Add input until 2 successive blank lines are entered. - let mut input = text.to_owned(); - let mut blank_line_count = 0; - loop { - let read_line_result = self.read_line("+ ", false); - if let Ok(None) = read_line_result { - unreachable!(); - } else if let Ok(Some(new_input)) = read_line_result { - input.push('\n'); - if new_input.is_empty() { - if blank_line_count > 0 { - break self.eval(input.as_str(), false); - } - blank_line_count += 1; - } else { - input.push_str(new_input.as_str()); - if blank_line_count > 0 { - break self.eval(input.as_str(), false); + if continue_on_err && self.continue_on_err(&err) { + // Add input until 2 successive blank lines are entered. + let mut input = text.to_owned(); + let mut blank_line_count = 0; + loop { + match self.read_line("+ ", false) { + Ok(None) => unreachable!( + "read_line should never return None in this context" + ), + Ok(Some(new_input)) => { + input.push('\n'); + if new_input.is_empty() { + if blank_line_count > 0 { + break self.eval(input.as_str(), false); + } + blank_line_count += 1; + } else { + input.push_str(new_input.as_str()); + if blank_line_count > 0 { + break self.eval(input.as_str(), false); + } + blank_line_count = 0; + } + } + Err(err) => { + break Err(DriverErr::new(DriverErrKind::ReplErr( + format!("{err}"), + ))); + } } - blank_line_count = 0; } } else { - let msg = format!("{}", read_line_result.unwrap_err()); - break Some(Err(ExeErr::new(ExeErrKind::ReplErr(msg)))); + if matches!( + &err.kind, + DriverErrKind::Bootstrap(_) + | DriverErrKind::CouldNotReadSourceFile(_) + | DriverErrKind::ModuleDirNotFound(_) + | DriverErrKind::ModuleNotFound(_) + | DriverErrKind::ReplErr(_) + ) { + eprintln!("{err}"); + } + Ok(None) } - } + }) } fn handle_command(&mut self, text: &str) -> bool { @@ -185,13 +186,10 @@ impl Repl { } } ".dis" => { - let module = self.module.read().unwrap(); - let module = module.down_to_mod().unwrap(); - let mut disassembler = dis::Disassembler::new(); - disassembler.disassemble(module.code()); + self.driver.disassemble_module(self.module.clone()); } ".stack" => { - self.executor.display_stack(); + self.driver.display_stack(); } ".emacs" => { self.reader.set_edit_mode(rustyline::config::EditMode::Emacs); @@ -204,8 +202,8 @@ impl Repl { true } - fn continue_on_err(&self, err: &ExeErr) -> bool { - if let ExeErrKind::ScanErr(kind) = &err.kind { + fn continue_on_err(&self, err: &DriverErr) -> bool { + if let DriverErrKind::ScanErr(kind) = &err.kind { use ScanErrKind::*; return matches!( kind, @@ -214,10 +212,10 @@ impl Repl { | UnmatchedOpeningBracket(_) | UnterminatedStr(_) ); - } else if let ExeErrKind::ParseErr(kind) = &err.kind { + } else if let DriverErrKind::ParseErr(kind) = &err.kind { use ParseErrKind::*; return matches!(kind, ExpectedBlock(_)); - } else if let ExeErrKind::CompErr(kind) = &err.kind { + } else if let DriverErrKind::CompErr(kind) = &err.kind { use CompErrKind::*; return matches!(kind, LabelNotFoundInScope(..)); } @@ -230,7 +228,7 @@ impl Repl { println!("REPL history will be saved to {}", path.to_string_lossy()); match self.reader.load_history(path.as_path()) { Ok(_) => (), - Err(err) => eprintln!("Could not load REPL history: {}", err), + Err(err) => eprintln!("Could not load REPL history: {err}"), } } None => (), @@ -244,7 +242,7 @@ impl Repl { match self.reader.save_history(path.as_path()) { Ok(_) => (), Err(err) => { - eprintln!("WARNING: Could not save REPL history: {}", err) + eprintln!("WARNING: Could not save REPL history: {err}") } } } diff --git a/feint-cli/src/tests/mod.rs b/feint-cli/src/tests/mod.rs new file mode 100644 index 0000000..dae8daa --- /dev/null +++ b/feint-cli/src/tests/mod.rs @@ -0,0 +1 @@ +mod repl; diff --git a/src/tests/repl.rs b/feint-cli/src/tests/repl.rs similarity index 66% rename from src/tests/repl.rs rename to feint-cli/src/tests/repl.rs index 83556a9..ba056e1 100644 --- a/src/tests/repl.rs +++ b/feint-cli/src/tests/repl.rs @@ -1,4 +1,5 @@ -use crate::exe::Executor; +use feint_driver::Driver; + use crate::repl::Repl; #[test] @@ -36,14 +37,14 @@ fn eval_if_with_no_block() { // Utilities ----------------------------------------------------------- fn eval(input: &str) { - let mut exe = Executor::new(16, vec![], false, false, false); - if let Err(err) = exe.bootstrap() { + let mut driver = Driver::new(16, vec![], false, false, false); + if let Err(err) = driver.bootstrap() { panic!("{err}"); } - let mut repl = Repl::new(None, exe); + let mut repl = Repl::new(None, driver); match repl.eval(input, false) { - Some(Ok(_)) => assert!(false), - Some(Err(_)) => assert!(false), - None => assert!(true), // eval returns None on valid input + Ok(Some(_)) => assert!(false), + Ok(None) => assert!(true), // eval returns None on valid input + Err(_) => assert!(false), } } diff --git a/feint-code-gen/Cargo.toml b/feint-code-gen/Cargo.toml new file mode 100644 index 0000000..0253dc0 --- /dev/null +++ b/feint-code-gen/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "feint-code-gen" +edition.workspace = true +version.workspace = true +description.workspace = true +authors.workspace = true +readme.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true diff --git a/src/types/gen.rs b/feint-code-gen/src/lib.rs similarity index 57% rename from src/types/gen.rs rename to feint-code-gen/src/lib.rs index 0154533..a7d29a6 100644 --- a/src/types/gen.rs +++ b/feint-code-gen/src/lib.rs @@ -1,116 +1,29 @@ +#[macro_export] macro_rules! obj_ref_t { ( $ty:ty ) => { Arc> }; } +#[macro_export] macro_rules! obj_ref { ( $obj:expr ) => { Arc::new(RwLock::new($obj)) }; } -pub(crate) use obj_ref; -pub(crate) use obj_ref_t; - -// Types --------------------------------------------------------------- - -/// Generate an intrinsic type definition. This includes the type's -/// struct and impl as well as the TypeTrait, Send, and Sync impls. -/// -/// Args: -/// -/// $type_name: ident -/// The type name. E.g., `NilType` -/// -/// $name: ident -/// The type's object name. E.g., `Nil` -macro_rules! type_and_impls { - ( $type_name:ident, $name:ident ) => { - pub struct $type_name { - ns: Namespace, - } - - unsafe impl Send for $type_name {} - unsafe impl Sync for $type_name {} - - impl $type_name { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - let name = new::str(stringify!($name)); - let full_name = new::str(concat!("std.", stringify!($name))); - Self { - ns: Namespace::with_entries(&[ - ("$name", name), - ("$full_name", full_name), - ]), - } - } - - pub fn with_attrs(attrs: &[(&str, ObjectRef)]) -> Self { - let mut type_obj = Self::new(); - type_obj.ns.extend(attrs); - type_obj - } - - pub fn add_attr(&mut self, name: &str, val: ObjectRef) { - self.ns.insert(name, val); - } - - pub fn add_attrs(&mut self, attrs: &[(&str, ObjectRef)]) { - self.ns.extend(attrs); - } - } - - impl TypeTrait for $type_name { - fn name(&self) -> &str { - stringify!($name) - } - - fn full_name(&self) -> &str { - concat!("std.", stringify!($name)) - } - - fn ns(&self) -> &Namespace { - &self.ns - } - } - - impl ObjectTrait for $type_name { - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn class(&self) -> TypeRef { - TYPE_TYPE.clone() - } - - fn type_obj(&self) -> ObjectRef { - TYPE_TYPE.clone() - } - - fn ns(&self) -> &Namespace { - &self.ns - } - - fn ns_mut(&mut self) -> &mut Namespace { - &mut self.ns - } - - fn as_type(&self) -> Option<&dyn TypeTrait> { - Some(self) - } - } +#[macro_export] +macro_rules! std_type { + ( $name:ident, $class_name:ident ) => { + pub static $name: Lazy = + Lazy::new(|| obj_ref!(Type::new("std", stringify!($class_name)))); }; } /// Generate standard obj impls. +#[macro_export] macro_rules! standard_object_impls { - ( $name:ident ) => { + ( $name:ident ) => { unsafe impl Send for $name {} unsafe impl Sync for $name {} }; @@ -123,6 +36,7 @@ macro_rules! standard_object_impls { /// /// $class: ident /// The singleton type instance. E.g. `NIL_TYPE`. +#[macro_export] macro_rules! object_trait_header { ( $class:ident ) => { fn as_any(&self) -> &dyn Any { @@ -137,10 +51,6 @@ macro_rules! object_trait_header { $class.clone() } - fn type_obj(&self) -> ObjectRef { - $class.clone() - } - fn ns(&self) -> &Namespace { &self.ns } @@ -148,17 +58,9 @@ macro_rules! object_trait_header { fn ns_mut(&mut self) -> &mut Namespace { &mut self.ns } - - fn as_type(&self) -> Option<&dyn TypeTrait> { - None - } }; } -pub(crate) use object_trait_header; -pub(crate) use standard_object_impls; -pub(crate) use type_and_impls; - // Methods ------------------------------------------------------------- /// Make a class or instance method for an intrinsic type. @@ -189,6 +91,7 @@ pub(crate) use type_and_impls; /// Returns a 2-tuple containing the method name and the intrinsic /// function object itself. This makes it easy to add the method to the /// type's namespace by calling `ns.add_obj(meth!(...))`. +#[macro_export] macro_rules! meth { ( $name:literal, $this_type:expr, $params:expr, $doc:literal, $func:expr ) => { ( @@ -207,6 +110,7 @@ macro_rules! meth { /// This is similar to `meth!` but it creates a property instead of a /// method and has no `$params` arg. +#[macro_export] macro_rules! prop { ( $name:literal, $this_type:expr, $doc:literal, $func:expr ) => { ( @@ -229,13 +133,15 @@ macro_rules! prop { /// /// $args: Args /// $index: usize +#[macro_export] macro_rules! use_arg { ( $args:ident, $index:literal ) => {{ if $index < $args.len() { $args[$index].read().unwrap() } else { - // NOTE: This should never happen from user code. - return Err(RuntimeErr::index_out_of_bounds("Arg", $index)); + let msg = + format!("{}() didn't receive enough args", stringify!($func_name)); + return new::arg_err(msg, new::nil()); } }}; } @@ -243,6 +149,7 @@ macro_rules! use_arg { // The use_arg_ macros convert the supplied arg to a value of the // given type if possible or return an Err if not. +#[macro_export] macro_rules! use_arg_str { ( $func_name:ident, $arg_name:ident, $arg:ident ) => {{ if let Some(val) = $arg.get_str_val() { @@ -253,11 +160,12 @@ macro_rules! use_arg_str { stringify!($func_name), stringify!($arg_name) ); - return Ok(new::arg_err(msg, new::nil())); + return new::arg_err(msg, new::nil()); } }}; } +#[macro_export] macro_rules! use_arg_map { ( $func_name:ident, $arg_name:ident, $arg:ident ) => {{ if let Some(val) = $arg.get_map_val() { @@ -268,11 +176,12 @@ macro_rules! use_arg_map { stringify!($func_name), stringify!($arg_name) ); - return Ok(new::arg_err(msg, new::nil())); + return new::arg_err(msg, new::nil()); } }}; } +#[macro_export] macro_rules! use_arg_usize { ( $func_name:ident, $arg_name:ident, $args:ident, $index:literal ) => {{ if $index < $args.len() { @@ -285,20 +194,12 @@ macro_rules! use_arg_usize { stringify!($func_name), stringify!($arg_name) ); - return Ok(new::arg_err(msg, new::nil())); + return new::arg_err(msg, new::nil()); } } else { - // NOTE: This should never happen from user code. let msg = format!("{}() didn't receive enough args", stringify!($func_name)); - return Err(RuntimeErr::index_out_of_bounds(msg, $index)); + return new::arg_err(msg, new::nil()); } }}; } - -pub(crate) use meth; -pub(crate) use prop; -pub(crate) use use_arg; -pub(crate) use use_arg_map; -pub(crate) use use_arg_str; -pub(crate) use use_arg_usize; diff --git a/feint-compiler/Cargo.toml b/feint-compiler/Cargo.toml new file mode 100644 index 0000000..36a82aa --- /dev/null +++ b/feint-compiler/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "feint-compiler" +edition.workspace = true +version.workspace = true +description.workspace = true +authors.workspace = true +readme.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +feint-builtins = { path = "../feint-builtins" } +feint-util = { path = "../feint-util" } + +log.workspace = true +num-bigint.workspace = true +num-traits.workspace = true +once_cell.workspace = true +regex.workspace = true diff --git a/src/ast/ast.rs b/feint-compiler/src/ast/ast.rs similarity index 93% rename from src/ast/ast.rs rename to feint-compiler/src/ast/ast.rs index 499b1d1..5aec528 100644 --- a/src/ast/ast.rs +++ b/feint-compiler/src/ast/ast.rs @@ -2,13 +2,14 @@ use std::fmt; use num_bigint::BigInt; -use crate::op::{ +use feint_builtins::types::Params; +use feint_util::op::{ BinaryOperator, CompareOperator, InplaceOperator, ShortCircuitCompareOperator, UnaryOperator, }; +use feint_util::source::Location; + use crate::scanner::Token; -use crate::source::Location; -use crate::types::Params; /// Module - A self-contained list of statements. #[derive(PartialEq)] @@ -24,11 +25,8 @@ impl Module { impl fmt::Debug for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let items: Vec = self - .statements - .iter() - .map(|statement| format!("{:?}", statement)) - .collect(); + let items: Vec = + self.statements.iter().map(|statement| format!("{statement:?}")).collect(); write!(f, "{}", items.join(" ; ")) } } @@ -51,6 +49,7 @@ pub enum StatementKind { Return(Expr), Halt(Expr), Print(Expr), + Debug(Expr), Expr(Expr), } @@ -96,6 +95,10 @@ impl Statement { Self::new(StatementKind::Print(expr), start, end) } + pub fn new_debug(expr: Expr, start: Location, end: Location) -> Self { + Self::new(StatementKind::Debug(expr), start, end) + } + pub fn new_expr(expr: Expr, start: Location, end: Location) -> Self { Self::new(StatementKind::Expr(expr), start, end) } @@ -134,7 +137,8 @@ impl fmt::Debug for StatementKind { Self::Return(expr) => write!(f, "return {expr:?}"), Self::Halt(expr) => write!(f, "$halt {expr:?}"), Self::Print(expr) => write!(f, "$print {expr:?}"), - Self::Expr(expr) => write!(f, "{:?}", expr), + Self::Debug(expr) => write!(f, "$debug {expr:?}"), + Self::Expr(expr) => write!(f, "{expr:?}"), } } } @@ -162,6 +166,7 @@ pub enum ExprKind { Call(Call), DeclarationAndAssignment(Box, Box), Assignment(Box, Box), + Reassignment(Box, Box), UnaryOp(UnaryOperator, Box), BinaryOp(Box, BinaryOperator, Box), CompareOp(Box, CompareOperator, Box), @@ -280,6 +285,18 @@ impl Expr { end, ) } + pub fn new_reassignment( + lhs_expr: Expr, + value_expr: Expr, + start: Location, + end: Location, + ) -> Self { + Self::new( + ExprKind::Reassignment(Box::new(lhs_expr), Box::new(value_expr)), + start, + end, + ) + } pub fn new_func( params: Params, @@ -305,7 +322,7 @@ impl Expr { start: Location, end: Location, ) -> Self { - let kind = if let Ok(op) = UnaryOperator::from_token(op_token) { + let kind = if let Ok(op) = UnaryOperator::from_token(op_token.as_str()) { ExprKind::UnaryOp(op, Box::new(a)) } else { panic!("Unknown unary operator: {op_token}"); @@ -321,13 +338,15 @@ impl Expr { end: Location, ) -> Self { use ExprKind::{BinaryOp, CompareOp, InplaceOp, ShortCircuitCompareOp}; - let kind = if let Ok(op) = BinaryOperator::from_token(op_token) { + let kind = if let Ok(op) = BinaryOperator::from_token(op_token.as_str()) { BinaryOp(Box::new(a), op, Box::new(b)) - } else if let Ok(op) = CompareOperator::from_token(op_token) { + } else if let Ok(op) = CompareOperator::from_token(op_token.as_str()) { CompareOp(Box::new(a), op, Box::new(b)) - } else if let Ok(op) = ShortCircuitCompareOperator::from_token(op_token) { + } else if let Ok(op) = + ShortCircuitCompareOperator::from_token(op_token.as_str()) + { ShortCircuitCompareOp(Box::new(a), op, Box::new(b)) - } else if let Ok(op) = InplaceOperator::from_token(op_token) { + } else if let Ok(op) = InplaceOperator::from_token(op_token.as_str()) { InplaceOp(Box::new(a), op, Box::new(b)) } else { panic!("Unknown binary operator: {op_token}"); @@ -446,6 +465,7 @@ impl fmt::Debug for ExprKind { write!(f, "{ident:?} = {expr:?}") } Self::Assignment(ident, expr) => write!(f, "{ident:?} = {expr:?}"), + Self::Reassignment(ident, expr) => write!(f, "{ident:?} <- {expr:?}"), Self::Block(block) => write!(f, "block {block:?}"), Self::Conditional(branches, default) => { write!(f, "{branches:?} {default:?}") @@ -478,11 +498,8 @@ impl StatementBlock { impl fmt::Debug for StatementBlock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let items: Vec = self - .statements - .iter() - .map(|statement| format!("{:?}", statement)) - .collect(); + let items: Vec = + self.statements.iter().map(|statement| format!("{statement:?}")).collect(); write!(f, "-> {}", items.join(" ; ")) } } @@ -660,6 +677,6 @@ impl fmt::Debug for IdentKind { Self::SpecialIdent(name) => name, Self::TypeIdent(name) => name, }; - write!(f, "{}", name) + write!(f, "{name}") } } diff --git a/feint-compiler/src/ast/mod.rs b/feint-compiler/src/ast/mod.rs new file mode 100644 index 0000000..7ae2518 --- /dev/null +++ b/feint-compiler/src/ast/mod.rs @@ -0,0 +1,3 @@ +pub mod ast; +pub use ast::*; +pub mod visitors; diff --git a/src/ast/visitors.rs b/feint-compiler/src/ast/visitors.rs similarity index 100% rename from src/ast/visitors.rs rename to feint-compiler/src/ast/visitors.rs diff --git a/src/compiler/compiler.rs b/feint-compiler/src/compiler/compiler.rs similarity index 93% rename from src/compiler/compiler.rs rename to feint-compiler/src/compiler/compiler.rs index 3959381..cc891a7 100644 --- a/src/compiler/compiler.rs +++ b/feint-compiler/src/compiler/compiler.rs @@ -1,11 +1,13 @@ //! Compiler. use std::collections::HashSet; +use feint_builtins::modules::STD; +use feint_builtins::new; +use feint_builtins::types::code::{Code, Inst}; +use feint_builtins::types::Module; +use feint_util::stack::Stack; + use crate::ast; -use crate::modules::std::STD; -use crate::types::{new, Module}; -use crate::util::Stack; -use crate::vm::{Code, Inst}; use super::result::{CompErr, CompResult, VisitResult}; use super::visitor::CompilerVisitor; @@ -30,22 +32,21 @@ pub struct Compiler { // are known to exist but aren't available to the compiler (e.g., in // the REPL). global_names: HashSet, + debug: bool, } -impl Default for Compiler { - fn default() -> Self { +impl Compiler { + pub fn new(global_names: HashSet, debug: bool) -> Self { + Self { visitor_stack: Stack::new(), global_names, debug } + } + + pub fn for_module(debug: bool) -> Self { let mut global_names = HashSet::default(); global_names.insert("$full_name".to_owned()); global_names.insert("$name".to_owned()); global_names.insert("$path".to_owned()); global_names.insert("$doc".to_owned()); - Self::new(global_names) - } -} - -impl Compiler { - pub fn new(global_names: HashSet) -> Self { - Self { visitor_stack: Stack::new(), global_names } + Self::new(global_names, debug) } /// Compile AST module node to module object. @@ -65,8 +66,11 @@ impl Compiler { module_name: &str, module: ast::Module, ) -> Result { - let mut visitor = - CompilerVisitor::for_module(module_name, self.global_names.clone()); + let mut visitor = CompilerVisitor::for_module( + module_name, + self.global_names.clone(), + self.debug, + ); visitor.visit_module(module)?; self.global_names = self .global_names @@ -114,7 +118,7 @@ impl Compiler { let params = node.params.clone(); let mut visitor = - CompilerVisitor::for_func(func_name, self.global_names.clone()); + CompilerVisitor::for_func(func_name, self.global_names.clone(), self.debug); visitor.visit_func(node)?; // Unresolved names are assumed to be globals or builtins. @@ -165,7 +169,7 @@ impl Compiler { Inst::ScopeEnd => { break; } - Inst::AssignVar(n) if n == name => { + Inst::AssignVar(n, 0) if n == name => { info.cell_var_assignments.push(addr); } Inst::LoadVar(n, 0) if n == name => { diff --git a/feint-compiler/src/compiler/mod.rs b/feint-compiler/src/compiler/mod.rs new file mode 100644 index 0000000..f686aa9 --- /dev/null +++ b/feint-compiler/src/compiler/mod.rs @@ -0,0 +1,7 @@ +pub use compiler::Compiler; +pub use result::{CompErr, CompErrKind}; + +mod compiler; +mod result; +mod scope; +mod visitor; diff --git a/src/compiler/result.rs b/feint-compiler/src/compiler/result.rs similarity index 91% rename from src/compiler/result.rs rename to feint-compiler/src/compiler/result.rs index d5f7fb3..a10bfbc 100644 --- a/src/compiler/result.rs +++ b/feint-compiler/src/compiler/result.rs @@ -1,5 +1,5 @@ -use crate::source::Location; -use crate::types::Module; +use feint_builtins::types::Module; +use feint_util::source::Location; pub type CompResult = Result; pub type VisitResult = Result<(), CompErr>; @@ -61,6 +61,10 @@ impl CompErr { Self::new(CompErrKind::CannotReassignSpecialIdent(name, start, end)) } + pub fn reassignment(msg: String, start: Location, end: Location) -> Self { + Self::new(CompErrKind::Reassignment(msg, start, end)) + } + pub fn main_must_be_func(start: Location, end: Location) -> Self { Self::new(CompErrKind::MainMustBeFunc(start, end)) } @@ -91,6 +95,7 @@ impl CompErr { ExpectedIdent(start, end) => (start, end), CannotAssignSpecialIdent(_, start, end) => (start, end), CannotReassignSpecialIdent(_, start, end) => (start, end), + Reassignment(_, start, end) => (start, end), MainMustBeFunc(start, end) => (start, end), GlobalNotFound(_, start, end) => (start, end), VarArgsMustBeLast(start, end) => (start, end), @@ -110,6 +115,7 @@ pub enum CompErrKind { ExpectedIdent(Location, Location), CannotAssignSpecialIdent(String, Location, Location), CannotReassignSpecialIdent(String, Location, Location), + Reassignment(String, Location, Location), MainMustBeFunc(Location, Location), GlobalNotFound(String, Location, Location), VarArgsMustBeLast(Location, Location), diff --git a/src/compiler/scope.rs b/feint-compiler/src/compiler/scope.rs similarity index 99% rename from src/compiler/scope.rs rename to feint-compiler/src/compiler/scope.rs index 2d643df..12875eb 100644 --- a/src/compiler/scope.rs +++ b/feint-compiler/src/compiler/scope.rs @@ -285,7 +285,7 @@ impl Scope { if let Some(pos) = self.jumps.iter().position(|(n, _)| n == name) { &self.jumps[pos].1 } else { - panic!("Jump does not exist in scope: {}", name) + panic!("Jump does not exist in scope: {name}") } } }; diff --git a/src/compiler/visitor.rs b/feint-compiler/src/compiler/visitor.rs similarity index 91% rename from src/compiler/visitor.rs rename to feint-compiler/src/compiler/visitor.rs index b7ea1f1..1d5bc81 100644 --- a/src/compiler/visitor.rs +++ b/feint-compiler/src/compiler/visitor.rs @@ -3,15 +3,17 @@ use std::collections::HashSet; use std::fmt; use std::fmt::Formatter; -use crate::ast; -use crate::modules::std::STD; -use crate::op::{ +use feint_builtins::modules::STD; +use feint_builtins::new; +use feint_builtins::types::code::{Code, Inst, PrintFlags}; +use feint_builtins::types::ObjectRef; +use feint_util::op::{ BinaryOperator, CompareOperator, InplaceOperator, ShortCircuitCompareOperator, UnaryOperator, }; -use crate::source::Location; -use crate::types::{new, ObjectRef}; -use crate::vm::{globals, Code, Inst, PrintFlags}; +use feint_util::source::Location; + +use crate::ast; use super::result::{CompErr, VisitResult}; use super::scope::{Scope, ScopeKind, ScopeTree}; @@ -29,8 +31,9 @@ type FuncNode = ( /// construct a `Module` or `Func` from this `Code` object. pub struct CompilerVisitor { initial_scope_kind: ScopeKind, - global_names: HashSet, name: String, + global_names: HashSet, + debug: bool, pub(crate) code: Code, pub(crate) scope_tree: ScopeTree, pub(crate) scope_depth: usize, @@ -42,12 +45,14 @@ impl CompilerVisitor { initial_scope_kind: ScopeKind, name: &str, global_names: HashSet, + debug: bool, ) -> Self { assert!(matches!(initial_scope_kind, ScopeKind::Module | ScopeKind::Func)); Self { initial_scope_kind, name: name.to_owned(), global_names, + debug, code: Code::default(), scope_tree: ScopeTree::new(initial_scope_kind), scope_depth: 0, @@ -55,12 +60,20 @@ impl CompilerVisitor { } } - pub(crate) fn for_module(name: &str, global_names: HashSet) -> Self { - Self::new(ScopeKind::Module, name, global_names) + pub(crate) fn for_module( + name: &str, + global_names: HashSet, + debug: bool, + ) -> Self { + Self::new(ScopeKind::Module, name, global_names, debug) } - pub(crate) fn for_func(name: &str, global_names: HashSet) -> Self { - Self::new(ScopeKind::Func, name, global_names) + pub(crate) fn for_func( + name: &str, + global_names: HashSet, + debug: bool, + ) -> Self { + Self::new(ScopeKind::Func, name, global_names, debug) } // Entry Point Visitors -------------------------------------------- @@ -189,6 +202,7 @@ impl CompilerVisitor { Kind::Return(expr) => self.visit_return(expr)?, Kind::Halt(expr) => self.visit_halt(expr)?, Kind::Print(expr) => self.visit_print(expr)?, + Kind::Debug(expr) => self.visit_debug(expr)?, Kind::Expr(expr) => self.visit_expr(expr, None)?, } Ok(()) @@ -210,7 +224,7 @@ impl CompilerVisitor { self.scope_tree.add_var(self.len(), &var_name, true); self.push(Inst::DeclareVar(var_name.clone())); self.push(Inst::LoadModule(name.clone())); - self.push(Inst::AssignVar(var_name.clone())); + self.push(Inst::AssignVar(var_name.clone(), 0)); } else { let var_name = name .split('.') @@ -219,7 +233,7 @@ impl CompilerVisitor { self.scope_tree.add_var(self.len(), var_name, true); self.push(Inst::DeclareVar(var_name.to_owned())); self.push(Inst::LoadModule(name.clone())); - self.push(Inst::AssignVar(var_name.to_owned())); + self.push(Inst::AssignVar(var_name.to_owned(), 0)); } Ok(()) } @@ -238,7 +252,7 @@ impl CompilerVisitor { let obj = args[0].clone(); // XXX: This is kinda gnarly. if n_args > 1 && args[1].is_true() { - flags.insert(PrintFlags::ERR); + flags.insert(PrintFlags::STDERR); } if n_args > 2 && args[2].is_true() { flags.insert(PrintFlags::NL); @@ -256,12 +270,19 @@ impl CompilerVisitor { } } let msg = concat!( - "$print expects to receive at least 1 arg and no more ", - "than 5 args: (object, ...flags)" + "$print expects to receive a Tuple with at least 1 arg ", + "and no more than 5 args: (object, ...flags)" ); Err(CompErr::print(msg, expr.start, expr.end)) } + fn visit_debug(&mut self, expr: ast::Expr) -> VisitResult { + if self.debug || cfg!(debug_assertions) { + self.visit_print(expr)?; + } + Ok(()) + } + fn visit_return(&mut self, expr: ast::Expr) -> VisitResult { self.visit_expr(expr, None)?; self.push(Inst::ReturnPlaceholder(self.len(), self.scope_depth)); @@ -293,6 +314,9 @@ impl CompilerVisitor { Kind::Assignment(lhs_expr, value_expr) => { self.visit_assignment(*lhs_expr, *value_expr)? } + Kind::Reassignment(lhs_expr, value_expr) => { + self.visit_reassignment(*lhs_expr, *value_expr)? + } Kind::Block(block) => self.visit_block(block)?, Kind::Conditional(branches, default) => { self.visit_conditional(branches, default)? @@ -358,11 +382,7 @@ impl CompilerVisitor { Kind::Always => self.push_always(), Kind::Ellipsis => self.push_nil(), Kind::Int(value) => { - if let Some(index) = globals::shared_int_index(&value) { - self.push_global_const(index) - } else { - self.add_const(new::int(value)); - } + self.add_const(new::int(value)); } Kind::Float(value) => { self.add_const(new::float(value)); @@ -707,13 +727,45 @@ impl CompilerVisitor { } self.visit_expr(value_expr, Some(name.clone()))?; self.scope_tree.mark_assigned(self.scope_tree.pointer(), name.as_str()); - self.push(Inst::AssignVar(name)); + self.push(Inst::AssignVar(name, 0)); Ok(()) } else { Err(CompErr::expected_ident(lhs_expr.start, lhs_expr.end)) } } + fn visit_reassignment( + &mut self, + lhs_expr: ast::Expr, + value_expr: ast::Expr, + ) -> VisitResult { + if let Some(name) = lhs_expr.ident_name() { + if let Some(var) = self.scope_tree.find_var(&name, None) { + let depth = var.depth; + let offset = self.scope_depth - depth; + if self.is_module() && depth == 0 { + let msg = format!( + "Cannot reassign {name} using <- in global scope. Use = instead." + ); + Err(CompErr::reassignment(msg, lhs_expr.start, lhs_expr.end)) + } else if self.is_func() && offset == 0 { + let msg = format!( + "Cannot reassign var {name} using <- in same scope. Use = instead." + ); + Err(CompErr::reassignment(msg, lhs_expr.start, lhs_expr.end)) + } else { + self.visit_expr(value_expr, Some(name.clone()))?; + self.push(Inst::AssignVar(name, offset)); + Ok(()) + } + } else { + Err(CompErr::name_not_found(name, lhs_expr.start, lhs_expr.end)) + } + } else { + Err(CompErr::expected_ident(lhs_expr.start, lhs_expr.end)) + } + } + fn visit_compare_op( &mut self, expr_a: ast::Expr, @@ -852,35 +904,31 @@ impl CompilerVisitor { // Global constants ------------------------------------------------ fn push_nil(&mut self) { - self.push(Inst::LoadNil); + self.add_const(new::nil()); } fn push_true(&mut self) { - self.push(Inst::LoadTrue); + self.add_const(new::bool(true)); } fn push_false(&mut self) { - self.push(Inst::LoadFalse); + self.add_const(new::bool(false)); } fn push_always(&mut self) { - self.push(Inst::LoadAlways); + self.add_const(new::always()); } fn push_empty_str(&mut self) { - self.push(Inst::LoadEmptyStr); + self.add_const(new::empty_str()); } fn push_newline(&mut self) { - self.push(Inst::LoadNewline); + self.add_const(new::newline()); } fn push_empty_tuple(&mut self) { - self.push(Inst::LoadEmptyTuple); - } - - fn push_global_const(&mut self, index: usize) { - self.push(Inst::LoadGlobalConst(index)); + self.add_const(new::empty_tuple()); } // Code unit constants --------------------------------------------- diff --git a/src/format.rs b/feint-compiler/src/format.rs similarity index 89% rename from src/format.rs rename to feint-compiler/src/format.rs index a36548d..93205d0 100644 --- a/src/format.rs +++ b/feint-compiler/src/format.rs @@ -1,7 +1,8 @@ +use feint_builtins::new; +use feint_builtins::types::ObjectRef; +use feint_util::source::source_from_text; + use crate::scanner::{ScanTokensResult, Scanner, Token, TokenWithLocation as TWL}; -use crate::source::source_from_text; -use crate::types::{new, ObjectRef}; -use crate::vm::RuntimeObjResult; #[derive(Clone, Debug, PartialEq)] pub enum FormatStrToken { @@ -110,10 +111,7 @@ pub fn scan_format_string( Ok(tokens) } -pub fn render_template( - template_ref: ObjectRef, - context_ref: ObjectRef, -) -> RuntimeObjResult { +pub fn render_template(template_ref: ObjectRef, context_ref: ObjectRef) -> ObjectRef { use Token::{EndOfStatement, Ident}; let template = template_ref.read().unwrap(); @@ -123,10 +121,7 @@ pub fn render_template( let context = if let Some(context) = context.down_to_map() { context } else { - return Ok(new::string_err( - "Expected context to be a map", - template_ref.clone(), - )); + return new::string_err("Expected context to be a map", template_ref.clone()); }; let scan_result = scan_format_string(template.as_str(), Some(("{{", "}}"))); @@ -135,7 +130,7 @@ pub fn render_template( Ok(tokens) => tokens, Err(err) => { let msg = format!("Could not parse template: {err:?}"); - return Ok(new::string_err(msg, template_ref.clone())); + return new::string_err(msg, template_ref.clone()); } }; @@ -154,7 +149,7 @@ pub fn render_template( output.push_str(val.as_str()); } else { let msg = format!("Name not found in context: {name}"); - return Ok(new::string_err(msg, template_ref.clone())); + return new::string_err(msg, template_ref.clone()); } } _ => { @@ -163,11 +158,11 @@ pub fn render_template( let msg = format!( "Template is contains an invalid expression: {tokens:?}" ); - return Ok(new::string_err(msg, template_ref.clone())); + return new::string_err(msg, template_ref.clone()); } }, } } - Ok(new::str(output)) + new::str(output) } diff --git a/feint-compiler/src/lib.rs b/feint-compiler/src/lib.rs new file mode 100644 index 0000000..c331f94 --- /dev/null +++ b/feint-compiler/src/lib.rs @@ -0,0 +1,12 @@ +pub use compiler::{CompErr, CompErrKind, Compiler}; +pub use parser::{ParseErr, ParseErrKind, Parser}; +pub use scanner::{ScanErr, ScanErrKind, Scanner, Token, TokenWithLocation}; + +pub mod ast; +pub mod compiler; +pub mod format; +pub mod parser; +pub mod scanner; + +#[cfg(test)] +mod tests; diff --git a/src/parser/mod.rs b/feint-compiler/src/parser/mod.rs similarity index 53% rename from src/parser/mod.rs rename to feint-compiler/src/parser/mod.rs index 4ae6de2..fc96e1f 100644 --- a/src/parser/mod.rs +++ b/feint-compiler/src/parser/mod.rs @@ -1,5 +1,5 @@ -pub(crate) use parser::Parser; -pub(crate) use result::{ParseErr, ParseErrKind}; +pub use parser::Parser; +pub use result::{ParseErr, ParseErrKind}; mod parser; mod precedence; diff --git a/src/parser/parser.rs b/feint-compiler/src/parser/parser.rs similarity index 97% rename from src/parser/parser.rs rename to feint-compiler/src/parser/parser.rs index 29493fa..43c7477 100644 --- a/src/parser/parser.rs +++ b/feint-compiler/src/parser/parser.rs @@ -2,11 +2,12 @@ use std::collections::VecDeque; use std::iter::{Iterator, Peekable}; +use feint_util::source::Location; + use crate::ast; use crate::format::FormatStrToken; use crate::parser::result::StatementResult; use crate::scanner::{ScanErr, ScanTokenResult, Token, TokenWithLocation}; -use crate::source::Location; use super::precedence::{ get_binary_precedence, get_unary_precedence, is_right_associative, @@ -83,7 +84,8 @@ impl> Parser { log::trace!("BEGIN STATEMENT level {level}"); self.statement_level += 1; use Token::{ - Break, Continue, EndOfStatement, Halt, Import, Jump, Label, Print, Return, + Break, Continue, Debug, EndOfStatement, Halt, Import, Jump, Label, Print, + Return, }; let token = self.expect_next_token()?; let start = token.start; @@ -94,6 +96,7 @@ impl> Parser { Jump => self.jump(start)?, Label(name) => self.label(name, start)?, Return => self.return_(start)?, + Debug => self.debug(start)?, Halt => self.halt(start)?, Print => self.print(start)?, _ => { @@ -173,6 +176,22 @@ impl> Parser { Ok(ast::Statement::new_print(expr, start, end)) } + /// Handle `$debug`. + fn debug(&mut self, start: Location) -> StatementResult { + let expr = self.expr(0)?; + let end = expr.end; + let args = ast::Expr::new_tuple( + vec![ + expr, + ast::Expr::new_true(start, end), + ast::Expr::new_true(start, end), + ], + start, + end, + ); + Ok(ast::Statement::new_debug(args, start, end)) + } + /// Handle `import`. fn import(&mut self, start: Location) -> StatementResult { if self.func_level != 0 { @@ -647,6 +666,12 @@ impl> Parser { ast::Expr::new_assignment(lhs, value, start, end) } } + Token::Feed => { + log::trace!("BINOP: reassignment"); + let value = self.expr(infix_prec)?; + let end = value.end; + ast::Expr::new_reassignment(lhs, value, start, end) + } // Call Token::LParen => { log::trace!("BINOP: call {lhs:?}"); diff --git a/src/parser/precedence.rs b/feint-compiler/src/parser/precedence.rs similarity index 96% rename from src/parser/precedence.rs rename to feint-compiler/src/parser/precedence.rs index 2d72ebb..0852e90 100644 --- a/src/parser/precedence.rs +++ b/feint-compiler/src/parser/precedence.rs @@ -13,7 +13,7 @@ pub fn get_binary_precedence(token: &Token) -> u8 { /// Return true if the token represents a right-associate operator. pub fn is_right_associative(token: &Token) -> bool { // Exponentiation and assignment - matches!(token, Token::Caret | Token::Equal) + matches!(token, Token::Caret | Token::Equal | Token::Feed) } #[rustfmt::skip] @@ -30,6 +30,7 @@ pub fn get_operator_precedence(token: &Token) -> (u8, u8) { use Token::*; match token { Equal // a = b + | Feed // a <- b | MulEqual // a *= b | DivEqual // a /= b | PlusEqual // a -= b diff --git a/src/parser/result.rs b/feint-compiler/src/parser/result.rs similarity index 98% rename from src/parser/result.rs rename to feint-compiler/src/parser/result.rs index 4192a50..a083d0d 100644 --- a/src/parser/result.rs +++ b/feint-compiler/src/parser/result.rs @@ -1,6 +1,7 @@ +use feint_util::source::Location; + use crate::ast; use crate::scanner::{ScanErr, Token, TokenWithLocation}; -use crate::source::Location; pub type BoolResult = Result; pub type ParseResult = Result; diff --git a/src/scanner/keywords.rs b/feint-compiler/src/scanner/keywords.rs similarity index 96% rename from src/scanner/keywords.rs rename to feint-compiler/src/scanner/keywords.rs index 6b1cabe..5dc1995 100644 --- a/src/scanner/keywords.rs +++ b/feint-compiler/src/scanner/keywords.rs @@ -26,6 +26,7 @@ pub static KEYWORDS: Lazy> = Lazy::new(|| { ("return", Return), ("$halt", Halt), ("$print", Print), + ("$debug", Debug), ] .iter() .cloned() diff --git a/src/scanner/mod.rs b/feint-compiler/src/scanner/mod.rs similarity index 100% rename from src/scanner/mod.rs rename to feint-compiler/src/scanner/mod.rs diff --git a/src/scanner/result.rs b/feint-compiler/src/scanner/result.rs similarity index 98% rename from src/scanner/result.rs rename to feint-compiler/src/scanner/result.rs index eb07766..ed63f31 100644 --- a/src/scanner/result.rs +++ b/feint-compiler/src/scanner/result.rs @@ -2,8 +2,9 @@ use std::num::ParseFloatError; use num_bigint::ParseBigIntError; +use feint_util::source::Location; + use crate::format::FormatStrErr; -use crate::source::Location; use super::{Token, TokenWithLocation}; diff --git a/src/scanner/scanner.rs b/feint-compiler/src/scanner/scanner.rs similarity index 99% rename from src/scanner/scanner.rs rename to feint-compiler/src/scanner/scanner.rs index 27fb6bf..30ab1b8 100644 --- a/src/scanner/scanner.rs +++ b/feint-compiler/src/scanner/scanner.rs @@ -6,10 +6,11 @@ use num_traits::Num; use once_cell::sync::Lazy; use regex::Regex; +use feint_util::source::{Location, Source}; +use feint_util::stack::Stack; + use crate::format::scan_format_string; use crate::scanner::result::AddTokenResult; -use crate::source::{Location, Source}; -use crate::util::Stack; use super::keywords::KEYWORDS; use super::result::ScanErrKind as ErrKind; @@ -150,7 +151,7 @@ impl<'a, T: BufRead> Scanner<'a, T> { Some(('<', Some('='), _)) => { self.consume_char_and_return_token(LessThanOrEqual) } - Some(('<', Some('-'), _)) => self.consume_char_and_return_token(LoopFeed), + Some(('<', Some('-'), _)) => self.consume_char_and_return_token(Feed), Some(('<', _, _)) => LessThan, Some(('>', Some('='), _)) => { self.consume_char_and_return_token(GreaterThanOrEqual) @@ -787,7 +788,7 @@ impl<'a, T: BufRead> Scanner<'a, T> { Some('o') | Some('O') => 8, Some('x') | Some('X') => 16, Some(t) if t.is_ascii_alphabetic() => { - panic!("Unsupported numeric type: {}", t); + panic!("Unsupported numeric type: {t}"); } _ => 10, } @@ -893,7 +894,7 @@ impl<'a, T: BufRead> Scanner<'a, T> { '\'' => string.push('\''), // This also seems to be a standard. - '\"' => string.push('\"'), + '"' => string.push('"'), // Any other escaped char resolves to the // original *escaped* version of itself. diff --git a/src/scanner/token.rs b/feint-compiler/src/scanner/token.rs similarity index 97% rename from src/scanner/token.rs rename to feint-compiler/src/scanner/token.rs index b1112fa..3d37770 100644 --- a/src/scanner/token.rs +++ b/feint-compiler/src/scanner/token.rs @@ -2,8 +2,9 @@ use std::fmt; use num_bigint::BigInt; +use feint_util::source::Location; + use crate::format::FormatStrToken; -use crate::source::Location; #[derive(Clone, Debug, PartialEq)] pub enum Token { @@ -19,7 +20,7 @@ pub enum Token { Colon, // : DotDot, // .. Ellipsis, // ... - LoopFeed, // <- + Feed, // <- // Fundamental types ----------------------------------------------- At, // @ (used to represent the singleton Always) @@ -88,8 +89,9 @@ pub enum Token { Return, // return Jump, // jump label Label(String), // :label: - Halt, - Print, + Halt, // $halt + Print, // $print + Debug, // $debug // Import/export --------------------------------------------------- Import, // import @@ -126,7 +128,7 @@ impl Token { Self::Colon => ":", Self::DotDot => "..", Self::Ellipsis => "...", - Self::LoopFeed => "<-", + Self::Feed => "<-", // Fundamental types ----------------------------------------------- Self::Int(_) => "Int", @@ -153,7 +155,7 @@ impl Token { Self::Comma => ",", Self::DollarDollar => "$$", - Self::DollarNot => "$", + Self::DollarNot => "$!", Self::EqualEqualEqual => "===", Self::NotEqualEqual => "!==", Self::EqualEqual => "==", @@ -196,6 +198,7 @@ impl Token { Self::Label(_name) => "label", Self::Halt => "$halt", Self::Print => "$print", + Self::Debug => "$debug", // Import/export --------------------------------------------------- Self::Import => "import", diff --git a/src/tests/ast.rs b/feint-compiler/src/tests/ast.rs similarity index 97% rename from src/tests/ast.rs rename to feint-compiler/src/tests/ast.rs index fd93c71..d12ec5d 100644 --- a/src/tests/ast.rs +++ b/feint-compiler/src/tests/ast.rs @@ -1,8 +1,9 @@ use num_bigint::BigInt; +use feint_util::source::Location; + use crate::ast::*; use crate::scanner::Token; -use crate::source::Location; #[test] #[rustfmt::skip] diff --git a/src/tests/format.rs b/feint-compiler/src/tests/format.rs similarity index 99% rename from src/tests/format.rs rename to feint-compiler/src/tests/format.rs index 3473ffa..d42e8ce 100644 --- a/src/tests/format.rs +++ b/feint-compiler/src/tests/format.rs @@ -1,10 +1,11 @@ use num_bigint::BigInt; +use feint_util::source::Location; + use crate::format::FormatStrErr::*; use crate::format::FormatStrToken::*; use crate::format::*; use crate::scanner::{Token, TokenWithLocation}; -use crate::source::Location; fn scan_ok( string: &str, diff --git a/feint-compiler/src/tests/mod.rs b/feint-compiler/src/tests/mod.rs new file mode 100644 index 0000000..2ec9e04 --- /dev/null +++ b/feint-compiler/src/tests/mod.rs @@ -0,0 +1,4 @@ +mod ast; +mod format; +mod parser; +mod scanner; diff --git a/src/tests/parser.rs b/feint-compiler/src/tests/parser.rs similarity index 98% rename from src/tests/parser.rs rename to feint-compiler/src/tests/parser.rs index c679628..7a26c53 100644 --- a/src/tests/parser.rs +++ b/feint-compiler/src/tests/parser.rs @@ -1,9 +1,10 @@ use num_bigint::BigInt; +use feint_util::op::BinaryOperator; +use feint_util::source::{source_from_text, Location}; + use crate::ast; -use crate::op::BinaryOperator; use crate::scanner::Scanner; -use crate::source::{source_from_text, Location}; use crate::parser::*; diff --git a/src/tests/scanner.rs b/feint-compiler/src/tests/scanner.rs similarity index 99% rename from src/tests/scanner.rs rename to feint-compiler/src/tests/scanner.rs index 000fe07..5093e8c 100644 --- a/src/tests/scanner.rs +++ b/feint-compiler/src/tests/scanner.rs @@ -1,6 +1,8 @@ +use std::convert::From; + use num_bigint::BigInt; -use crate::source::{source_from_text, Location}; +use feint_util::source::{source_from_text, Location}; use crate::scanner::*; diff --git a/feint-driver/Cargo.toml b/feint-driver/Cargo.toml new file mode 100644 index 0000000..64aae9e --- /dev/null +++ b/feint-driver/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "feint-driver" +edition.workspace = true +version.workspace = true +description.workspace = true +authors.workspace = true +readme.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +feint-builtins = { path = "../feint-builtins" } +feint-code-gen = { path = "../feint-code-gen" } +feint-compiler = { path = "../feint-compiler" } +feint-util = { path = "../feint-util" } +feint-vm = { path = "../feint-vm" } + +once_cell.workspace = true diff --git a/src/exe.rs b/feint-driver/src/driver.rs similarity index 82% rename from src/exe.rs rename to feint-driver/src/driver.rs index 5efc944..25e7371 100644 --- a/src/exe.rs +++ b/feint-driver/src/driver.rs @@ -1,62 +1,36 @@ //! Front end for executing code from a source on a VM. -use std::borrow::Cow; -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::fs::canonicalize; -use std::io::{BufRead, Read}; +use std::io::BufRead; use std::path::Path; use std::sync::{Arc, RwLock}; -use flate2::read::GzDecoder; -use once_cell::sync::Lazy; -use tar::Archive as TarArchive; - -use crate::compiler::{CompErr, CompErrKind, Compiler}; -use crate::modules::std::{self as stdlib, STD}; -use crate::modules::{add_module, maybe_get_module, MODULES}; -use crate::parser::{ParseErr, ParseErrKind, Parser}; -use crate::result::ExeErrKind::ModuleNotFound; -use crate::result::{ExeErr, ExeErrKind, ExeResult}; -use crate::scanner::{ScanErr, ScanErrKind, Scanner, Token, TokenWithLocation}; -use crate::source::{ +use feint_builtins::modules::{ + add_module, maybe_get_module, MODULES, STD, STD_FI_MODULES, +}; +use feint_builtins::new; +use feint_builtins::types::{ + self, + code::{Inst, PrintFlags}, + Module, ObjectRef, ObjectTrait, +}; +use feint_code_gen::obj_ref; +use feint_compiler::{ + ast, CompErr, CompErrKind, Compiler, ParseErr, ParseErrKind, Parser, ScanErr, + ScanErrKind, Scanner, Token, TokenWithLocation, +}; +use feint_util::source::{ source_from_bytes, source_from_file, source_from_stdin, source_from_text, Location, Source, }; -use crate::types::gen::obj_ref; -use crate::types::{new, Module, ObjectRef, ObjectTrait}; -use crate::vm::{ - CallDepth, Inst, PrintFlags, ModuleExecutionContext, RuntimeErr, RuntimeErrKind, - VMExeResult, VMState, VM, +use feint_vm::{ + CallDepth, Disassembler, ModuleExecutionContext, RuntimeErr, RuntimeErrKind, + RuntimeResult, VMState, VM, }; -use crate::{ast, dis}; - -/// At build time, a compressed archive is created containing the -/// std .fi module files (see `build.rs`). -/// -/// At runtime, the module file data is read out and stored in a map -/// (lazily). When a std module is imported, the file data is read from -/// this map rather than reading from disk. -/// -/// The utility of this is that we don't need an install process that -/// copies the std module files into some location on the file system -/// based on the location of the current executable or anything like -/// that. -static STD_FI_MODULES: Lazy>> = Lazy::new(|| { - let archive_bytes: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/modules.tgz")); - let decoder = GzDecoder::new(archive_bytes); - let mut archive = TarArchive::new(decoder); - let mut modules = HashMap::new(); - for entry in archive.entries().unwrap() { - let mut entry = entry.unwrap(); - let path: Cow<'_, Path> = entry.path().unwrap(); - let path = path.to_str().unwrap().to_owned(); - let mut result = Vec::new(); - entry.read_to_end(&mut result).unwrap(); - modules.insert(path, result); - } - modules -}); -pub struct Executor { +use super::result::{DriverErr, DriverErrKind, DriverOptResult, DriverResult}; + +pub struct Driver { vm: VM, argv: Vec, incremental: bool, @@ -66,7 +40,7 @@ pub struct Executor { imports: VecDeque, } -impl Executor { +impl Driver { pub fn new( max_call_depth: CallDepth, argv: Vec, @@ -103,7 +77,7 @@ impl Executor { // Bootstrap ------------------------------------------------------- /// Bootstrap and return error on failure. - pub fn bootstrap(&mut self) -> Result<(), ExeErr> { + pub fn bootstrap(&mut self) -> Result<(), DriverErr> { // Add the `std` module with builtins first because any other // module may rely on it, including `system`. self.extend_intrinsic_module(STD.clone(), "std")?; @@ -123,7 +97,9 @@ impl Executor { system.ns_mut().insert("argv", new::argv_tuple(&self.argv)); } - self.add_module("std.proc", stdlib::PROC.clone()); + self.extend_intrinsic_type(types::list::LIST_TYPE.clone(), "std.list")?; + self.extend_intrinsic_type(types::map::MAP_TYPE.clone(), "std.map")?; + self.extend_intrinsic_type(types::tuple::TUPLE_TYPE.clone(), "std.tuple")?; Ok(()) } @@ -134,7 +110,7 @@ impl Executor { &mut self, base_module: ObjectRef, name: &str, - ) -> Result<(), ExeErr> { + ) -> Result<(), DriverErr> { let fi_module = self.load_module(name)?; let fi_module = fi_module.read().unwrap(); let fi_module = fi_module.down_to_mod().unwrap(); @@ -145,6 +121,23 @@ impl Executor { Ok(()) } + /// Extend intrinsic type with global objects from corresponding + /// FeInt module. + fn extend_intrinsic_type( + &mut self, + type_ref: ObjectRef, + module_name: &str, + ) -> Result<(), DriverErr> { + let mut ty = type_ref.write().unwrap(); + let fi_module = self.get_or_add_module(module_name)?; + let fi_module = fi_module.read().unwrap(); + let fi_module = fi_module.down_to_mod().unwrap(); + for (name, val) in fi_module.iter_globals() { + ty.ns_mut().insert(name, val.clone()); + } + Ok(()) + } + // Execute --------------------------------------------------------- /// Execute text entered in REPL. REPL execution is different from @@ -152,7 +145,7 @@ impl Executor { /// compiled all at once and executed as a script. In the REPL, code /// is compiled incrementally as it's entered, which makes it /// somewhat more complex to deal with. - pub fn execute_repl(&mut self, text: &str, module: ObjectRef) -> ExeResult { + pub fn execute_repl(&mut self, text: &str, module: ObjectRef) -> DriverOptResult { self.current_file_name = "".to_owned(); // XXX: Nested scopes are necessary to avoid deadlocks. @@ -167,30 +160,27 @@ impl Executor { let source = &mut source_from_text(text); let ast_module = self.parse_source(source)?; - let mut compiler = Compiler::new(global_names); + let mut compiler = Compiler::new(global_names, self.debug); let comp_result = compiler.compile_module_to_code("$repl", ast_module); let mut code = comp_result.map_err(|err| { self.handle_comp_err(&err, source); - ExeErr::new(ExeErrKind::CompErr(err.kind)) + DriverErr::new(DriverErrKind::CompErr(err.kind)) })?; // Assign TOS to _, print it, then pop it to clear the stack let last_inst = code.pop_inst(); if let Some(Inst::Pop) = last_inst { - let print_flags = PrintFlags::ERR - | PrintFlags::NL - | PrintFlags::REPR - | PrintFlags::NO_NIL; + let print_flags = PrintFlags::NL | PrintFlags::REPR | PrintFlags::NO_NIL; code.push_inst(Inst::DeclareVar("_".to_owned())); - code.push_inst(Inst::AssignVar("_".to_owned())); + code.push_inst(Inst::AssignVar("_".to_owned(), 0)); code.push_inst(Inst::Print(print_flags)); } else { let last_inst = match last_inst { Some(inst) => format!("{inst:?}"), None => "[EMPTY CHUNK]".to_owned(), }; - panic!("Expected module chunk to end with POP; got {}", last_inst); + panic!("Expected module chunk to end with POP; got {last_inst}"); } { @@ -199,7 +189,7 @@ impl Executor { module.code_mut().extend(code); } - let vm_state = { + { let module = module.read().unwrap(); let module = module.down_to_mod().unwrap(); self.execute_module(module, start, source, false)? @@ -213,11 +203,14 @@ impl Executor { } } - Ok(vm_state) + match &self.vm.state { + VMState::Running | VMState::Idle(_) => Ok(None), + VMState::Halted(code) => Ok(Some(*code)), + } } /// Execute source from file as script. - pub fn execute_file(&mut self, file_path: &Path) -> ExeResult { + pub fn execute_file(&mut self, file_path: &Path) -> DriverResult { match source_from_file(file_path) { Ok(mut source) => { self.set_current_file_name(file_path); @@ -225,20 +218,20 @@ impl Executor { } Err(err) => { let message = format!("{}: {err}", file_path.display()); - Err(ExeErr::new(ExeErrKind::CouldNotReadSourceFile(message))) + Err(DriverErr::new(DriverErrKind::CouldNotReadSourceFile(message))) } } } /// Execute stdin as script. - pub fn execute_stdin(&mut self) -> ExeResult { + pub fn execute_stdin(&mut self) -> DriverResult { self.current_file_name = "".to_owned(); let mut source = source_from_stdin(); self.execute_script_from_source(&mut source) } /// Execute text as script. - pub fn execute_text(&mut self, text: &str) -> ExeResult { + pub fn execute_text(&mut self, text: &str) -> DriverResult { self.current_file_name = "".to_owned(); let mut source = source_from_text(text); self.execute_script_from_source(&mut source) @@ -250,7 +243,7 @@ impl Executor { fn execute_script_from_source( &mut self, source: &mut Source, - ) -> ExeResult { + ) -> DriverResult { let module = self.compile_module("$main", source)?; let module_ref = obj_ref!(module); self.add_module("$main", module_ref.clone()); @@ -259,7 +252,7 @@ impl Executor { self.execute_module(module, 0, source, true) } - pub fn execute_module_as_script(&mut self, name: &str) -> ExeResult { + pub fn execute_module_as_script(&mut self, name: &str) -> DriverResult { let module = self.get_or_add_module(name)?; let module = module.read().unwrap(); let module = module.down_to_mod().unwrap(); @@ -276,14 +269,14 @@ impl Executor { start: usize, source: &mut Source, is_main: bool, - ) -> ExeResult { + ) -> DriverResult { if self.dis && is_main { - let mut disassembler = dis::Disassembler::new(); + let mut disassembler = Disassembler::new(); disassembler.disassemble(module.code()); if self.debug { self.display_stack(); } - return Ok(VMState::Halted(0)); + return Ok(0); } self.load_imported_modules()?; @@ -313,11 +306,19 @@ impl Executor { self.display_vm_state(&result); } - match result { - Ok(()) => Ok(self.vm.state.clone()), - Err(err) => { + result + .map(|_| match &self.vm.state { + VMState::Running => { + eprintln!("VM should be idle or halted, not running"); + 255 + } + VMState::Idle(_) => 0, + VMState::Halted(0) => 0, + VMState::Halted(code) => *code, + }) + .map_err(|err| { if let RuntimeErrKind::Exit(_) = err.kind { - Err(ExeErr::new(ExeErrKind::RuntimeErr(err.kind))) + DriverErr::new(DriverErrKind::RuntimeErr(err.kind)) } else { let start = self.vm.loc().0; let line = source @@ -325,10 +326,9 @@ impl Executor { .unwrap_or(""); self.print_err_line(start.line, line); self.handle_runtime_err(&err); - Err(ExeErr::new(ExeErrKind::RuntimeErr(err.kind))) + DriverErr::new(DriverErrKind::RuntimeErr(err.kind)) } - } - } + }) } // Parsing --------------------------------------------------------- @@ -337,7 +337,7 @@ impl Executor { fn parse_source( &mut self, source: &mut Source, - ) -> Result { + ) -> Result { let scanner = Scanner::new(source); let mut parser = Parser::new(scanner); match parser.parse() { @@ -348,10 +348,10 @@ impl Executor { Err(err) => { if let ParseErrKind::ScanErr(scan_err) = err.kind { self.handle_scan_err(&scan_err, source); - Err(ExeErr::new(ExeErrKind::ScanErr(scan_err.kind))) + Err(DriverErr::new(DriverErrKind::ScanErr(scan_err.kind))) } else { self.handle_parse_err(&err, source); - Err(ExeErr::new(ExeErrKind::ParseErr(err.kind))) + Err(DriverErr::new(DriverErrKind::ParseErr(err.kind))) } } } @@ -364,14 +364,14 @@ impl Executor { &mut self, name: &str, source: &mut Source, - ) -> Result { + ) -> Result { let ast_module = self.parse_source(source)?; - let mut compiler = Compiler::default(); + let mut compiler = Compiler::for_module(self.debug); let module = compiler .compile_module(name, self.current_file_name.as_str(), ast_module) .map_err(|err| { self.handle_comp_err(&err, source); - ExeErr::new(ExeErrKind::CompErr(err.kind)) + DriverErr::new(DriverErrKind::CompErr(err.kind)) })?; Ok(module) } @@ -382,7 +382,7 @@ impl Executor { /// /// XXX: This will load the module regardless of whether it has /// already been loaded. - fn load_module(&mut self, name: &str) -> Result { + fn load_module(&mut self, name: &str) -> Result { // TODO: Handle non-std modules if let Some(file_data) = STD_FI_MODULES.get(name) { self.set_current_file_name(Path::new(&format!("<{name}>"))); @@ -394,7 +394,7 @@ impl Executor { } Ok(obj_ref!(module)) } else { - Err(ExeErr::new(ModuleNotFound(name.to_owned()))) + Err(DriverErr::new(DriverErrKind::ModuleNotFound(name.to_owned()))) } } @@ -404,17 +404,17 @@ impl Executor { } /// Get module from `MODULES` (the `system.modules` mirror). - fn get_module(&mut self, name: &str) -> Result { + fn get_module(&mut self, name: &str) -> Result { if let Some(module) = maybe_get_module(name) { Ok(module) } else { - Err(ExeErr::new(ModuleNotFound(name.to_owned()))) + Err(DriverErr::new(DriverErrKind::ModuleNotFound(name.to_owned()))) } } /// Get module or load it from file system and add it to both /// `MODULES` and `system.modules`. - fn get_or_add_module(&mut self, name: &str) -> Result { + fn get_or_add_module(&mut self, name: &str) -> Result { if let Ok(module) = self.get_module(name) { Ok(module) } else { @@ -436,13 +436,22 @@ impl Executor { } /// Load modules imported by the current module. - fn load_imported_modules(&mut self) -> Result<(), ExeErr> { + fn load_imported_modules(&mut self) -> Result<(), DriverErr> { while let Some(name) = self.imports.pop_front() { self.get_or_add_module(&name)?; } Ok(()) } + // Disassembly ----------------------------------------------------- + + pub fn disassemble_module(&self, module: ObjectRef) { + let module = module.read().unwrap(); + let module = module.down_to_mod().unwrap(); + let mut disassembler = Disassembler::new(); + disassembler.disassemble(module.code()); + } + // Error Handling -------------------------------------------------- fn print_err_line(&self, line_no: usize, line: &str) { @@ -485,7 +494,7 @@ impl Executor { let col = loc.col; let mut message = match &err.kind { UnexpectedChar(c) => { - format!("Syntax error: Unexpected character at column {}: '{}'", col, c) + format!("Syntax error: Unexpected character at column {col}: '{c}'") } UnmatchedOpeningBracket(_) => { format!("Unmatched open bracket at {loc}") @@ -508,7 +517,7 @@ impl Executor { format!("Syntax error: Invalid label: {msg}") } FormatStrErr(err) => { - use crate::format::FormatStrErr::*; + use feint_compiler::format::FormatStrErr::*; match err { EmptyExpr(pos) => { loc = Location::new(loc.line, loc.col + pos + 2); @@ -592,7 +601,7 @@ impl Executor { "Parse error: extra match arm found after default match arm".to_string() } SyntaxErr(loc) => format!("Syntax error at {loc}"), - kind => format!("Unhandled parse error: {:?}", kind), + kind => format!("Unhandled parse error: {kind:?}"), }; if self.debug { message = format!("PARSE ERROR: {message}"); @@ -626,6 +635,9 @@ impl Executor { CannotReassignSpecialIdent(name, ..) => { format!("cannot reassign special name: {name}") } + Reassignment(msg, ..) => { + msg.to_string() + } MainMustBeFunc(..) => { "$main must be a function".to_owned() } @@ -663,7 +675,7 @@ impl Executor { NameErr(message) => format!("Name error: {message}"), TypeErr(message) => format!("Type error: {message}"), NotCallable(type_name) => format!("Object is not callable: {type_name}"), - kind => format!("Unhandled runtime error: {}", kind), + kind => format!("Unhandled runtime error: {kind}"), }; if self.debug { message = format!("RUNTIME ERROR: {message}"); @@ -673,13 +685,13 @@ impl Executor { // Miscellaneous --------------------------------------------------- - pub(crate) fn display_stack(&self) { + pub fn display_stack(&self) { eprintln!("{:=<79}", "STACK "); self.vm.display_stack(); } - fn display_vm_state(&self, result: &VMExeResult) { + fn display_vm_state(&self, result: &RuntimeResult) { eprintln!("\n{:=<79}", "VM STATE "); - eprintln!("{:?}", result); + eprintln!("{result:?}"); } } diff --git a/feint-driver/src/lib.rs b/feint-driver/src/lib.rs new file mode 100644 index 0000000..a3d481f --- /dev/null +++ b/feint-driver/src/lib.rs @@ -0,0 +1,8 @@ +pub mod driver; +pub mod result; + +pub use driver::Driver; +pub use result::{DriverErr, DriverErrKind, DriverResult}; + +#[cfg(test)] +mod tests; diff --git a/src/result.rs b/feint-driver/src/result.rs similarity index 68% rename from src/result.rs rename to feint-driver/src/result.rs index 1369f3e..6bd36d3 100644 --- a/src/result.rs +++ b/feint-driver/src/result.rs @@ -1,27 +1,28 @@ use core::fmt; use std::fmt::Formatter; -use crate::compiler::CompErrKind; -use crate::parser::ParseErrKind; -use crate::scanner::ScanErrKind; -use crate::vm::{RuntimeErrKind, VMState}; +use feint_compiler::{CompErrKind, ParseErrKind, ScanErrKind}; +use feint_vm::RuntimeErrKind; -/// Result type used by top level program executor. -pub type ExeResult = Result; +pub type CallDepth = usize; + +/// Result type used by top level program driver. +pub type DriverResult = Result; +pub type DriverOptResult = Result, DriverErr>; #[derive(Debug)] -pub struct ExeErr { - pub kind: ExeErrKind, +pub struct DriverErr { + pub kind: DriverErrKind, } -impl ExeErr { - pub fn new(kind: ExeErrKind) -> Self { +impl DriverErr { + pub fn new(kind: DriverErrKind) -> Self { Self { kind } } /// Return exit code if this error wraps a runtime exit error. pub fn exit_code(&self) -> Option { - if let ExeErrKind::RuntimeErr(RuntimeErrKind::Exit(code)) = self.kind { + if let DriverErrKind::RuntimeErr(RuntimeErrKind::Exit(code)) = self.kind { Some(code) } else { None @@ -30,29 +31,35 @@ impl ExeErr { } #[derive(Debug)] -pub enum ExeErrKind { +pub enum DriverErrKind { + // These errors are NOT handled by the driver. They should be + // handled by the user of the driver (e.g., in main, REPL). Bootstrap(String), + CouldNotReadSourceFile(String), ModuleDirNotFound(String), ModuleNotFound(String), - CouldNotReadSourceFile(String), + ReplErr(String), + // These errors ARE handled by the driver. ScanErr(ScanErrKind), ParseErr(ParseErrKind), CompErr(CompErrKind), RuntimeErr(RuntimeErrKind), - ReplErr(String), } -impl fmt::Display for ExeErr { +impl fmt::Display for DriverErr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.kind) } } -impl fmt::Display for ExeErrKind { +impl fmt::Display for DriverErrKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - use ExeErrKind::*; + use DriverErrKind::*; let msg = match self { Bootstrap(msg) => format!("Bootstrap process failed: {msg}"), + CouldNotReadSourceFile(file_name) => { + format!("Could not read source file: {file_name}") + } ModuleDirNotFound(path) => format!( concat!( "Module directory not found: {}\n", @@ -63,14 +70,11 @@ impl fmt::Display for ExeErrKind { ModuleNotFound(name) => { format!("Module not found: {name}") } - CouldNotReadSourceFile(file_name) => { - format!("Could not read source file: {file_name}") - } + ReplErr(msg) => format!("REPL error: {msg}"), ScanErr(kind) => format!("Scan error: {kind:?}"), ParseErr(kind) => format!("Parse error: {kind:?}"), CompErr(kind) => format!("Compilation error: {kind:?}"), RuntimeErr(kind) => format!("Runtime error: {kind:?}"), - ReplErr(msg) => format!("REPL error: {msg}"), }; write!(f, "{msg}") } diff --git a/feint-driver/src/tests/driver.rs b/feint-driver/src/tests/driver.rs new file mode 100644 index 0000000..9a8cea2 --- /dev/null +++ b/feint-driver/src/tests/driver.rs @@ -0,0 +1,21 @@ +use feint_vm::RuntimeErrKind; + +use crate::driver::Driver; +use crate::result::{DriverErrKind, DriverResult}; + +fn execute(source: &str) -> DriverResult { + let mut driver = Driver::new(16, vec![], false, false, false); + driver.bootstrap()?; + driver.execute_text(source) +} + +#[test] +fn test_too_much_recursion() { + let result = execute("f = () => f()\nf()"); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(matches!( + err.kind, + DriverErrKind::RuntimeErr(RuntimeErrKind::RecursionDepthExceeded(_)) + )); +} diff --git a/feint-driver/src/tests/mod.rs b/feint-driver/src/tests/mod.rs new file mode 100644 index 0000000..75c97aa --- /dev/null +++ b/feint-driver/src/tests/mod.rs @@ -0,0 +1,2 @@ +mod driver; +mod run; diff --git a/src/tests/run.rs b/feint-driver/src/tests/run.rs similarity index 90% rename from src/tests/run.rs rename to feint-driver/src/tests/run.rs index d4e1e99..f2fa7b4 100644 --- a/src/tests/run.rs +++ b/feint-driver/src/tests/run.rs @@ -1,17 +1,17 @@ -use crate::exe::Executor; -use crate::result::ExeResult; +use crate::driver::Driver; +use crate::result::DriverResult; -fn run_text(text: &str) -> ExeResult { - let mut exe = Executor::new(16, vec![], false, false, false); - exe.bootstrap()?; - exe.execute_text(text) +fn run_text(text: &str) -> DriverResult { + let mut driver = Driver::new(16, vec![], false, false, false); + driver.bootstrap()?; + driver.execute_text(text) } -fn assert_result_is_ok(result: ExeResult) { +fn assert_result_is_ok(result: DriverResult) { assert!(result.is_ok(), "{:?}", result.err()); } -fn assert_result_is_err(result: ExeResult) { +fn assert_result_is_err(result: DriverResult) { assert!(result.is_err(), "{:?}", result); } diff --git a/feint-util/Cargo.toml b/feint-util/Cargo.toml new file mode 100644 index 0000000..e572b66 --- /dev/null +++ b/feint-util/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "feint-util" +edition.workspace = true +version.workspace = true +description.workspace = true +authors.workspace = true +readme.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true diff --git a/feint-util/src/lib.rs b/feint-util/src/lib.rs new file mode 100644 index 0000000..9cb6624 --- /dev/null +++ b/feint-util/src/lib.rs @@ -0,0 +1,11 @@ +//! # FeInt +//! +//! FeInt is a stack-based bytecode interpreter. + +pub mod op; +pub mod source; +pub mod stack; +pub mod string; + +#[cfg(test)] +mod tests; diff --git a/src/op.rs b/feint-util/src/op.rs similarity index 73% rename from src/op.rs rename to feint-util/src/op.rs index 497ed81..5830d4b 100644 --- a/src/op.rs +++ b/feint-util/src/op.rs @@ -3,8 +3,6 @@ //! easier to handle different types of operations in the VM. use std::fmt; -use crate::scanner::Token; - /// Unary operators. #[derive(Clone, Eq, PartialEq)] pub enum UnaryOperator { @@ -15,12 +13,12 @@ pub enum UnaryOperator { } impl UnaryOperator { - pub fn from_token(token: &Token) -> Result { + pub fn from_token(token: &str) -> Result { let op = match token { - Token::Plus => Self::Plus, - Token::Minus => Self::Negate, - Token::Bang => Self::Not, - Token::BangBang => Self::AsBool, + "+" => Self::Plus, + "-" => Self::Negate, + "!" => Self::Not, + "!!" => Self::AsBool, _ => return Err(format!("Unknown unary operator: {token}")), }; Ok(op) @@ -59,16 +57,16 @@ pub enum BinaryOperator { } impl BinaryOperator { - pub fn from_token(token: &Token) -> Result { + pub fn from_token(token: &str) -> Result { let op = match token { - Token::Caret => Self::Pow, - Token::Star => Self::Mul, - Token::Slash => Self::Div, - Token::DoubleSlash => Self::FloorDiv, - Token::Percent => Self::Mod, - Token::Plus => Self::Add, - Token::Minus => Self::Sub, - Token::Dot => Self::Dot, + "^" => Self::Pow, + "*" => Self::Mul, + "/" => Self::Div, + "//" => Self::FloorDiv, + "%" => Self::Mod, + "+" => Self::Add, + "-" => Self::Sub, + "." => Self::Dot, _ => return Err(format!("Unknown binary operator: {token}")), }; Ok(op) @@ -113,18 +111,18 @@ pub enum CompareOperator { } impl CompareOperator { - pub fn from_token(token: &Token) -> Result { + pub fn from_token(token: &str) -> Result { let op = match token { - Token::DollarDollar => Self::Is, - Token::DollarNot => Self::IsNot, - Token::EqualEqualEqual => Self::IsTypeEqual, - Token::NotEqualEqual => Self::IsNotTypeEqual, - Token::EqualEqual => Self::IsEqual, - Token::NotEqual => Self::NotEqual, - Token::LessThan => Self::LessThan, - Token::LessThanOrEqual => Self::LessThanOrEqual, - Token::GreaterThan => Self::GreaterThan, - Token::GreaterThanOrEqual => Self::GreaterThanOrEqual, + "$$" => Self::Is, + "$!" => Self::IsNot, + "===" => Self::IsTypeEqual, + "!==" => Self::IsNotTypeEqual, + "==" => Self::IsEqual, + "!=" => Self::NotEqual, + "<" => Self::LessThan, + "<=" => Self::LessThanOrEqual, + ">" => Self::GreaterThan, + ">=" => Self::GreaterThanOrEqual, _ => return Err(format!("Unknown comparison operator: {token}")), }; Ok(op) @@ -134,7 +132,7 @@ impl CompareOperator { impl fmt::Display for CompareOperator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let string = match self { - Self::Is => "$", + Self::Is => "$$", Self::IsNot => "$!", Self::IsTypeEqual => "===", Self::IsNotTypeEqual => "!==", @@ -164,11 +162,11 @@ pub enum ShortCircuitCompareOperator { } impl ShortCircuitCompareOperator { - pub fn from_token(token: &Token) -> Result { + pub fn from_token(token: &str) -> Result { let op = match token { - Token::And => Self::And, - Token::Or => Self::Or, - Token::NilOr => Self::NilOr, + "&&" => Self::And, + "||" => Self::Or, + "??" => Self::NilOr, _ => { return Err(format!( "Unknown short-circuiting comparison operator: {token}" @@ -206,12 +204,12 @@ pub enum InplaceOperator { } impl InplaceOperator { - pub fn from_token(token: &Token) -> Result { + pub fn from_token(token: &str) -> Result { let op = match token { - Token::MulEqual => Self::Mul, - Token::DivEqual => Self::Div, - Token::PlusEqual => Self::Add, - Token::MinusEqual => Self::Sub, + "*=" => Self::Mul, + "/=" => Self::Div, + "+=" => Self::Add, + "-=" => Self::Sub, _ => return Err(format!("Unknown inplace operator: {token}")), }; Ok(op) diff --git a/src/source.rs b/feint-util/src/source.rs similarity index 95% rename from src/source.rs rename to feint-util/src/source.rs index f0fd770..a7e3486 100644 --- a/src/source.rs +++ b/feint-util/src/source.rs @@ -1,6 +1,6 @@ use std::collections::VecDeque; use std::fs::File; -use std::io::{BufRead, BufReader, Cursor, Take}; +use std::io::{BufRead, BufReader, Cursor}; use std::path::Path; use std::{fmt, io}; @@ -64,7 +64,7 @@ pub fn source_from_stdin() -> Source> { /// - Tracks current line and column. /// - Panics when lines are too long. pub struct Source { - stream: Take, + stream: T, /// String buffer the source reader reads lines into. buffer: String, /// The queue of characters for the current line. @@ -79,9 +79,9 @@ pub struct Source { } impl Source { - pub fn new(source: T) -> Self { + pub fn new(stream: T) -> Self { let mut source = Source { - stream: source.take(MAX_LINE_LENGTH + 1), + stream, buffer: String::with_capacity(INITIAL_CAPACITY), queue: VecDeque::with_capacity(INITIAL_CAPACITY), line_no: 0, @@ -119,7 +119,7 @@ impl Source { } Ok(n) => { if n > MAX_LINE_LENGTH_USIZE { - panic!("Line is too long (> {})", MAX_LINE_LENGTH); + panic!("Line is too long (> {MAX_LINE_LENGTH})"); } self.line_no += 1; self.col = 0; @@ -141,7 +141,7 @@ impl Source { } } Err(err) => { - panic!("Could not read line from source: {}", err); + panic!("Could not read line from source: {err}"); } }; } diff --git a/src/util/stack.rs b/feint-util/src/stack.rs similarity index 98% rename from src/util/stack.rs rename to feint-util/src/stack.rs index b6930cc..fe44713 100644 --- a/src/util/stack.rs +++ b/feint-util/src/stack.rs @@ -85,7 +85,7 @@ impl Stack { impl fmt::Display for Stack { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for index in self.iter() { - write!(f, "{}", index)?; + write!(f, "{index}")?; } write!(f, "") } diff --git a/src/util/string.rs b/feint-util/src/string.rs similarity index 100% rename from src/util/string.rs rename to feint-util/src/string.rs diff --git a/feint-util/src/tests/mod.rs b/feint-util/src/tests/mod.rs new file mode 100644 index 0000000..e19fd3d --- /dev/null +++ b/feint-util/src/tests/mod.rs @@ -0,0 +1 @@ +mod stack; diff --git a/src/tests/util.rs b/feint-util/src/tests/stack.rs similarity index 98% rename from src/tests/util.rs rename to feint-util/src/tests/stack.rs index 676b61d..46f99d4 100644 --- a/src/tests/util.rs +++ b/feint-util/src/tests/stack.rs @@ -1,4 +1,4 @@ -use crate::util::Stack; +use crate::stack::Stack; #[test] fn new_stack_is_empty() { diff --git a/feint-vm/Cargo.toml b/feint-vm/Cargo.toml new file mode 100644 index 0000000..943e986 --- /dev/null +++ b/feint-vm/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "feint-vm" +edition.workspace = true +version.workspace = true +description.workspace = true +authors.workspace = true +readme.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +feint-builtins = { path = "../feint-builtins" } +feint-util = { path = "../feint-util" } + +bitflags.workspace = true +ctrlc.workspace = true +env_logger.workspace = true +indexmap.workspace = true +log.workspace = true +num-traits.workspace = true diff --git a/src/vm/context.rs b/feint-vm/src/context.rs similarity index 94% rename from src/vm/context.rs rename to feint-vm/src/context.rs index 1e1a181..817ae41 100644 --- a/src/vm/context.rs +++ b/feint-vm/src/context.rs @@ -1,9 +1,11 @@ //! VM runtime context. use indexmap::IndexMap; -use crate::modules::std::STD; -use crate::types::{new, ObjectRef, ObjectTrait}; -use crate::vm::RuntimeObjResult; +use feint_builtins::modules::STD; +use feint_builtins::new; +use feint_builtins::types::{ObjectRef, ObjectTrait}; + +use crate::RuntimeObjResult; use super::result::{RuntimeErr, RuntimeResult}; @@ -43,7 +45,7 @@ impl ModuleExecutionContext { } #[inline] - pub(crate) fn globals(&self) -> &Namespace { + pub fn globals(&self) -> &Namespace { &self.ns_stack[0] } @@ -109,11 +111,13 @@ impl ModuleExecutionContext { &mut self, name: &str, obj: ObjectRef, + offset: usize, ) -> Result { - let ns = self.current_mut(); + let depth = self.current_depth() - offset; + let ns = self.ns_stack.get_mut(depth).unwrap(); if ns.contains_key(name) { ns.insert(name.to_owned(), obj); - Ok(self.ns_stack.len() - 1) + Ok(depth) } else { let message = format!("Name not defined in current scope: {name}"); Err(RuntimeErr::name_err(message)) @@ -127,7 +131,7 @@ impl ModuleExecutionContext { obj: ObjectRef, ) -> Result { self.declare_var(name); - self.assign_var(name, obj) + self.assign_var(name, obj, 0) } /// Assign value to var--reach into the scope at depth and set the diff --git a/src/dis.rs b/feint-vm/src/dis.rs similarity index 79% rename from src/dis.rs rename to feint-vm/src/dis.rs index c1153d4..6c8865a 100644 --- a/src/dis.rs +++ b/feint-vm/src/dis.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::vm::{globals, Code, Inst}; +use feint_builtins::types::code::{Code, Inst}; pub struct Disassembler { curr_line_no: usize, @@ -37,7 +37,7 @@ impl Disassembler { if let Some(func) = obj.down_to_func() { println!(); let heading = format!("{func:?} "); - println!("{:=<79}", heading); + println!("{heading:=<79}"); self.disassemble(func.code()); } } @@ -54,25 +54,6 @@ impl Disassembler { match inst { NoOp => self.align("NOOP", "ø"), Pop => self.align("POP", ""), - LoadGlobalConst(index) => { - let op_code = "LOAD_GLOBAL_CONST"; - let index = *index; - if let Some(obj) = globals::get_global_constant(index) { - self.align(op_code, format!("{index} ({})", obj.read().unwrap())) - } else { - self.align( - op_code, - format!("{index} ([global constant does not exist])"), - ) - } - } - LoadNil => self.align("LOAD_NIL", "nil"), - LoadTrue => self.align("LOAD_TRUE", "true"), - LoadFalse => self.align("LOAD_FALSE", "false"), - LoadAlways => self.align("LOAD_ALWAYS", "@"), - LoadEmptyStr => self.align("LOAD_EMPTY_STR", "\"\""), - LoadNewline => self.align("LOAD_NEWLINE", "\"\\n\""), - LoadEmptyTuple => self.align("LOAD_EMPTY_TUPLE", "()"), ScopeStart => self.align("SCOPE_START", ""), ScopeEnd => self.align("SCOPE_END", ""), StatementStart(start, _) => { @@ -80,17 +61,21 @@ impl Disassembler { self.curr_line_no = start.line; self.align("STATEMENT_START", "") } - LoadConst(index) => { - let constant = match code.get_const(*index) { - Ok(obj) => obj.read().unwrap().to_string(), - Err(err) => err.to_string(), - }; - self.align("LOAD_CONST", format!("{index} ({constant:?})")) - } + LoadConst(index) => match code.get_const(*index) { + Some(obj) => { + let obj_str = obj.read().unwrap().to_string(); + self.align("LOAD_CONST", format!("{index} ({obj_str:?})")) + } + None => self.align("LOAD_CONST", ""), + }, DeclareVar(name) => self.align("DECLARE_VAR", name), - AssignVar(name) => self.align("ASSIGN_VAR", name), + AssignVar(name, offset) => { + let offset_prefix = if *offset == 0 { "" } else { "-" }; + self.align("ASSIGN_VAR", format!("{name} @ {offset_prefix}{offset}")) + } LoadVar(name, offset) => { - self.align("LOAD_VAR", format!("{name} @ -{offset}")) + let offset_prefix = if *offset == 0 { "" } else { "-" }; + self.align("LOAD_VAR", format!("{name} @ {offset_prefix}{offset}")) } LoadGlobal(name) => self.align("LOAD_GLOBAL", name), LoadBuiltin(name) => self.align("LOAD_BUILTIN", name), diff --git a/feint-vm/src/lib.rs b/feint-vm/src/lib.rs new file mode 100644 index 0000000..6ad32f7 --- /dev/null +++ b/feint-vm/src/lib.rs @@ -0,0 +1,15 @@ +pub use context::ModuleExecutionContext; +pub use dis::Disassembler; +pub use result::{ + CallDepth, RuntimeBoolResult, RuntimeErr, RuntimeErrKind, RuntimeObjResult, + RuntimeResult, VMState, +}; +pub use vm::VM; + +pub mod context; +pub mod dis; +pub mod result; +pub mod vm; + +#[cfg(test)] +mod tests; diff --git a/src/vm/result.rs b/feint-vm/src/result.rs similarity index 75% rename from src/vm/result.rs rename to feint-vm/src/result.rs index a0a1765..bcecfe8 100644 --- a/src/vm/result.rs +++ b/feint-vm/src/result.rs @@ -1,12 +1,9 @@ use std::fmt; use std::fmt::Formatter; -use crate::compiler::CompErr; -use crate::parser::ParseErr; -use crate::types::ObjectRef; +use feint_builtins::types::ObjectRef; pub type CallDepth = usize; -pub type VMExeResult = Result<(), RuntimeErr>; pub type RuntimeResult = Result<(), RuntimeErr>; pub type RuntimeObjResult = Result; pub type RuntimeBoolResult = Result; @@ -34,8 +31,6 @@ pub enum ValueStackKind { ReturnVal(ObjectRef), } -// Runtime errors ------------------------------------------------------ - #[derive(Clone, Debug)] pub struct RuntimeErr { pub kind: RuntimeErrKind, @@ -46,18 +41,6 @@ impl RuntimeErr { Self { kind } } - pub fn config_name_not_known>(name: S) -> Self { - Self::new(RuntimeErrKind::ConfigNameNotKnown(name.into())) - } - - pub fn config_value_not_set>(name: S) -> Self { - Self::new(RuntimeErrKind::ConfigValueNotSet(name.into())) - } - - pub fn config_value_is_not_valid>(name: S, msg: S) -> Self { - Self::new(RuntimeErrKind::ConfigValueIsNotValid(name.into(), msg.into())) - } - pub fn exit(code: u8) -> Self { Self::new(RuntimeErrKind::Exit(code)) } @@ -131,9 +114,6 @@ impl fmt::Display for RuntimeErr { #[derive(Clone, Debug)] pub enum RuntimeErrKind { - ConfigNameNotKnown(String), - ConfigValueNotSet(String), - ConfigValueIsNotValid(String, String), Exit(u8), AssertionFailed(String), EmptyStack, @@ -145,8 +125,6 @@ pub enum RuntimeErrKind { ConstantNotFound(usize), CapturedVarNotFound(String), ExpectedVar(String), - ParseErr(ParseErr), - CompErr(CompErr), UnhandledInstruction(String), TypeErr(String), NameErr(String), @@ -158,17 +136,6 @@ pub enum RuntimeErrKind { impl fmt::Display for RuntimeErrKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - use RuntimeErrKind::*; - let str = match self { - ConfigNameNotKnown(name) => { - format!("Unknown FeInt config name: {name}") - } - ConfigValueNotSet(name) => format!("FeInt config value not set: {name}"), - ConfigValueIsNotValid(name, val) => { - format!("FeInt config value not valid for {name}: {val}") - } - _ => format!("{self:?}"), - }; - write!(f, "{str}") + write!(f, "{self:?}") } } diff --git a/src/tests/vm.rs b/feint-vm/src/tests.rs similarity index 77% rename from src/tests/vm.rs rename to feint-vm/src/tests.rs index 7b5135d..328420f 100644 --- a/src/tests/vm.rs +++ b/feint-vm/src/tests.rs @@ -1,6 +1,11 @@ -use crate::op::BinaryOperator; -use crate::types::{new, Module}; -use crate::vm::*; +use feint_builtins::new; +use feint_builtins::types::{ + code::{Code, Inst}, + Module, +}; +use feint_util::op::BinaryOperator; + +use crate::*; #[test] fn execute_simple_program() { diff --git a/src/vm/vm.rs b/feint-vm/src/vm.rs similarity index 84% rename from src/vm/vm.rs rename to feint-vm/src/vm.rs index def7e30..d4dfb3e 100644 --- a/src/vm/vm.rs +++ b/feint-vm/src/vm.rs @@ -9,30 +9,25 @@ use std::sync::{ }; use ctrlc; +use feint_builtins::modules::get_module; use indexmap::IndexMap; use num_traits::ToPrimitive; -use crate::modules::get_module; -use crate::op::{BinaryOperator, CompareOperator, InplaceOperator, UnaryOperator}; -use crate::source::Location; -use crate::types::{ - new, Args, Func, FuncTrait, IntrinsicFunc, Module, ObjectRef, ThisOpt, +use feint_builtins::new; +use feint_builtins::types::code::{Code, Inst, PrintFlags}; +use feint_builtins::types::{ + Args, Func, FuncTrait, IntrinsicFunc, Module, ObjectRef, ObjectTrait, ThisOpt, }; -use crate::util::Stack; +use feint_util::op::{BinaryOperator, CompareOperator, InplaceOperator, UnaryOperator}; +use feint_util::source::Location; +use feint_util::stack::Stack; -use super::code::Code; use super::context::ModuleExecutionContext; -use super::globals; -use super::inst::{Inst, PrintFlags}; use super::result::{ CallDepth, PeekObjResult, PeekResult, PopNObjResult, PopNResult, PopObjResult, - PopResult, RuntimeErr, RuntimeObjResult, RuntimeResult, VMExeResult, VMState, - ValueStackKind, + PopResult, RuntimeErr, RuntimeObjResult, RuntimeResult, VMState, ValueStackKind, }; -pub const DEFAULT_MAX_CALL_DEPTH: CallDepth = - if cfg!(debug_assertions) { 256 } else { 1024 }; - struct CallFrame { stack_pointer: usize, this_opt: ThisOpt, @@ -61,9 +56,8 @@ impl CallFrame { } pub struct VM { - pub(crate) ctx: ModuleExecutionContext, - pub(crate) state: VMState, - global_constants: Vec, + pub ctx: ModuleExecutionContext, + pub state: VMState, // The scope stack contains pointers into the value stack. When a // scope is entered, the current, pre-scope stack position is // recorded. When a scope is exited, its corresponding pointer is @@ -90,7 +84,8 @@ unsafe impl Sync for VM {} impl Default for VM { fn default() -> Self { - VM::new(ModuleExecutionContext::default(), DEFAULT_MAX_CALL_DEPTH) + let call_depth = if cfg!(debug_assertions) { 256 } else { 1024 }; + VM::new(ModuleExecutionContext::default(), call_depth) } } @@ -99,7 +94,6 @@ impl VM { VM { ctx, state: VMState::Idle(None), - global_constants: globals::get_global_constants(), scope_stack: Stack::with_capacity(max_call_depth), value_stack: Stack::with_capacity(max_call_depth * 8), call_stack: Stack::with_capacity(max_call_depth), @@ -110,13 +104,13 @@ impl VM { } } - pub fn execute_module(&mut self, module: &Module, start: usize) -> VMExeResult { + pub fn execute_module(&mut self, module: &Module, start: usize) -> RuntimeResult { self.reset(); self.execute_code(module, module.code(), start) } - pub fn execute_func(&mut self, func: &Func, start: usize) -> VMExeResult { - let module = func.module(); + pub fn execute_func(&mut self, func: &Func, start: usize) -> RuntimeResult { + let module = (func as &dyn FuncTrait).module(); let module = module.read().unwrap(); let module = module.down_to_mod().unwrap(); self.execute_code(module, func.code(), start) @@ -136,7 +130,7 @@ impl VM { module: &Module, code: &Code, mut ip: usize, - ) -> VMExeResult { + ) -> RuntimeResult { use Inst::*; self.set_running(); @@ -163,32 +157,6 @@ impl VM { Pop => { self.pop()?; } - // Well-known global constants - LoadNil => { - self.push_global_const(globals::NIL_INDEX)?; - } - LoadTrue => { - self.push_global_const(globals::TRUE_INDEX)?; - } - LoadFalse => { - self.push_global_const(globals::FALSE_INDEX)?; - } - LoadAlways => { - self.push_global_const(globals::ALWAYS_INDEX)?; - } - LoadEmptyStr => { - self.push_global_const(globals::EMPTY_STR_INDEX)?; - } - LoadNewline => { - self.push_global_const(globals::NEWLINE_INDEX)?; - } - LoadEmptyTuple => { - self.push_global_const(globals::EMPTY_TUPLE_INDEX)?; - } - // Other global constants (shared ints) - LoadGlobalConst(index) => { - self.push_global_const(*index)?; - } // Scopes ScopeStart => { self.enter_scope(); @@ -200,8 +168,8 @@ impl VM { self.loc = (*start, *end); } LoadConst(index) => { - let obj = code.get_const(*index)?.clone(); - self.push(ValueStackKind::Constant(obj, *index)); + let obj = code.get_const(*index).unwrap(); + self.push(ValueStackKind::Constant(obj.clone(), *index)); } // Modules LoadModule(name) => { @@ -214,9 +182,9 @@ impl VM { self.ctx.declare_var(name.as_str()); } } - AssignVar(name) => { + AssignVar(name, offset) => { let obj = self.pop_obj()?; - let depth = self.ctx.assign_var(name, obj)?; + let depth = self.ctx.assign_var(name, obj, *offset)?; self.push_var(depth, name.clone())?; } LoadVar(name, offset) => { @@ -276,12 +244,12 @@ impl VM { let depth = if let Some(cell) = var.down_to_cell_mut() { // Wrap TOS in existing cell. cell.set_value(value.clone()); - self.ctx.assign_var(name, var_ref.clone())? + self.ctx.assign_var(name, var_ref.clone(), 0)? } else { // Create new cell to wrap TOS in. assert!(var.is_nil()); let cell_ref = new::cell_with_value(value.clone()); - self.ctx.assign_var(name, cell_ref)? + self.ctx.assign_var(name, cell_ref, 0)? }; // Push cell *value* to TOS. self.push(ValueStackKind::CellVar(value, depth, name.to_owned())); @@ -326,7 +294,7 @@ impl VM { } } JumpPushNil(addr, forward, scope_exit_count) => { - self.push_global_const(0)?; + self.push_temp(new::nil()); self.exit_scopes(*scope_exit_count); if *forward { jump_ip = Some(ip + *addr); @@ -335,35 +303,48 @@ impl VM { } } JumpIf(addr, forward, scope_exit_count) => { - self.exit_scopes(*scope_exit_count); let obj = self.peek_obj()?; let obj = obj.read().unwrap(); - if obj.bool_val()? { - if *forward { - jump_ip = Some(ip + *addr); - } else { - jump_ip = Some(ip - *addr); + if let Some(should_jump) = obj.bool_val() { + if should_jump { + self.exit_scopes(*scope_exit_count); + if *forward { + jump_ip = Some(ip + *addr); + } else { + jump_ip = Some(ip - *addr); + } } + } else { + // XXX: Should not be a hard error? + return Err(RuntimeErr::type_err(format!( + "Cannot evaluate as bool: {obj}" + ))); } } JumpIfNot(addr, forward, scope_exit_count) => { - self.exit_scopes(*scope_exit_count); let obj = self.peek_obj()?; let obj = obj.read().unwrap(); - if !obj.bool_val()? { - if *forward { - jump_ip = Some(ip + *addr); - } else { - jump_ip = Some(ip - *addr); + if let Some(should_not_jump) = obj.bool_val() { + if !should_not_jump { + self.exit_scopes(*scope_exit_count); + if *forward { + jump_ip = Some(ip + *addr); + } else { + jump_ip = Some(ip - *addr); + } } + } else { + // XXX: Should not be a hard error? + return Err(RuntimeErr::type_err(format!( + "Cannot evaluate as bool: {obj}" + ))); } } JumpIfNotNil(addr, forward, scope_exit_count) => { - self.exit_scopes(*scope_exit_count); let obj = self.peek_obj()?; let obj = obj.read().unwrap(); - let nil = &self.global_constants[globals::NIL_INDEX]; - if !obj.is(&*nil.read().unwrap()) { + if !obj.is_nil() { + self.exit_scopes(*scope_exit_count); if *forward { jump_ip = Some(ip + *addr); } else { @@ -481,8 +462,11 @@ impl VM { if let AssignCell(_) = &code[ip + 1] { let closure_cell = new::cell_with_value(func_ref.clone()); - self.ctx - .assign_var(func.name(), closure_cell.clone())?; + self.ctx.assign_var( + func.name(), + closure_cell.clone(), + 0, + )?; capture_set .insert(func.name().to_owned(), closure_cell); } @@ -593,13 +577,13 @@ impl VM { self.state = VMState::Idle(obj); } - fn halt(&mut self, exit_code: u8) -> VMExeResult { + fn halt(&mut self, exit_code: u8) -> RuntimeResult { self.reset(); self.state = VMState::Halted(exit_code); Err(RuntimeErr::exit(exit_code)) } - pub fn halt_top(&mut self) -> VMExeResult { + pub fn halt_top(&mut self) -> RuntimeResult { let obj = self.pop_obj()?; let obj = obj.read().unwrap(); let return_code = match obj.get_int_val() { @@ -626,12 +610,31 @@ impl VM { let a = a_ref.read().unwrap(); let result = match op { Plus => { - drop(a); - a_ref // no-op + // no-op + self.push_temp(a_ref.clone()); + return Ok(()); + } + Negate => { + if let Some(result) = a.negate() { + result + } else { + new::type_err(format!("- not defined for {a}"), a_ref.clone()) + } + } + AsBool => { + if let Some(result) = a.bool_val() { + new::bool(result) + } else { + new::type_err(format!("!! not defined for {a}"), a_ref.clone()) + } + } + Not => { + if let Some(result) = a.not() { + new::bool(result) + } else { + new::type_err(format!("! not defined for {a}"), a_ref.clone()) + } } - Negate => a.negate()?, - AsBool => new::bool(a.bool_val()?), - Not => new::bool(a.not()?), }; self.push_temp(result); Ok(()) @@ -648,13 +651,13 @@ impl VM { let b = b_ref.read().unwrap(); let b = &*b; let result = match op { - Pow => a.pow(b)?, - Mul => a.mul(b)?, - Div => a.div(b)?, - FloorDiv => a.floor_div(b)?, - Mod => a.modulo(b)?, - Add => a.add(b)?, - Sub => a.sub(b)?, + Pow => a.pow(b), + Mul => a.mul(b), + Div => a.div(b), + FloorDiv => a.floor_div(b), + Mod => a.modulo(b), + Add => a.add(b), + Sub => a.sub(b), Dot => { let obj_ref = if let Some(name) = b.get_str_val() { let mut result = a.get_attr(name, a_ref.clone()); @@ -688,33 +691,45 @@ impl VM { }; let obj = obj_ref.read().unwrap(); - if obj.is_intrinsic_func() || obj.is_func() || obj.is_closure() { - // If `b` in `a.b` is a function, bind `b` to `a`. + let result = + if obj.is_intrinsic_func() || obj.is_func() || obj.is_closure() { + // If `b` in `a.b` is a function, bind `b` to `a`. - // TODO: Check whether `a` is a type or an instance. + // TODO: Check whether `a` is a type or an instance. - new::bound_func(obj_ref.clone(), a_ref.clone()) - } else if let Some(prop) = obj.down_to_prop() { - // If `b` in `a.b` is a property, bind `b`'s getter - // to `a` then call the bound getter. + new::bound_func(obj_ref.clone(), a_ref.clone()) + } else if let Some(prop) = obj.down_to_prop() { + // If `b` in `a.b` is a property, bind `b`'s getter + // to `a` then call the bound getter. - // TODO: Check whether `a` is a type or an instance - // and return the property itself when `a` is - // a type. + // TODO: Check whether `a` is a type or an instance + // and return the property itself when `a` is + // a type. - let func = new::bound_func(prop.getter(), a_ref.clone()); - if a.is_type_object() { - func + let func = new::bound_func(prop.getter(), a_ref.clone()); + if a.is_type() { + func + } else { + return self.call(func, vec![]); + } } else { - return self.call(func, vec![]); - } - } else { - drop(obj); - obj_ref - } + obj_ref.clone() + }; + + self.push_temp(result); + return Ok(()); } }; - self.push_temp(result); + + if let Some(result) = result { + self.push_temp(result); + } else { + self.push_temp(new::type_err( + format!("Operation not defined for {b}"), + a_ref.clone(), + )); + } + Ok(()) } @@ -735,10 +750,10 @@ impl VM { IsNotTypeEqual => !a.is_type_equal(b), IsEqual => a.is_equal(b), NotEqual => !a.is_equal(b), - LessThan => a.less_than(b)?, - LessThanOrEqual => a.less_than(b)? || a.is_equal(b), - GreaterThan => a.greater_than(b)?, - GreaterThanOrEqual => a.greater_than(b)? || a.is_equal(b), + LessThan => a.less_than(b).unwrap_or(false), + LessThanOrEqual => a.less_than(b).unwrap_or(false) || a.is_equal(b), + GreaterThan => a.greater_than(b).unwrap_or(false), + GreaterThanOrEqual => a.greater_than(b).unwrap_or(false) || a.is_equal(b), }; self.push_temp(new::bool(result)); Ok(()) @@ -755,11 +770,22 @@ impl VM { let b = b_ref.read().unwrap(); let b = &*b; let result = match op { - InplaceOperator::Mul => a.mul(b)?, - InplaceOperator::Div => a.div(b)?, - InplaceOperator::Add => a.add(b)?, - InplaceOperator::Sub => a.sub(b)?, + InplaceOperator::Mul => a.mul(b), + InplaceOperator::Div => a.div(b), + InplaceOperator::Add => a.add(b), + InplaceOperator::Sub => a.sub(b), }; + + let result = if let Some(result) = result { + result + } else { + self.push_temp(new::type_err( + format!("Operation not defined for {b}"), + a_ref.clone(), + )); + return Ok(()); + }; + if let ValueStackKind::Var(_, depth, name) = a_kind { self.ctx.assign_var_at_depth(depth, name.as_str(), result.clone())?; self.push_temp(result); @@ -770,7 +796,7 @@ impl VM { cell.set_value(result.clone()); self.push_temp(result); } else { - return Err(RuntimeErr::expected_var(format!("Binary op: {}", op))); + return Err(RuntimeErr::expected_var(format!("Binary op: {op}"))); } Ok(()) } @@ -778,27 +804,42 @@ impl VM { fn handle_print(&mut self, flags: &PrintFlags) -> RuntimeResult { if let Ok(obj) = self.pop_obj() { let obj = obj.read().unwrap(); - if flags.contains(PrintFlags::NO_NIL) && obj.is_nil() { - // do nothing - } else if flags.contains(PrintFlags::ERR) { - if flags.contains(PrintFlags::REPR) { - eprint!("{:?}", &*obj); + + let print_to_stdout = !flags.contains(PrintFlags::STDERR); + let no_nil = flags.contains(PrintFlags::NO_NIL); + + if print_to_stdout && no_nil && obj.is_nil() { + return Ok(()); + } + + let is_nl = obj.get_str_val() == Some("\n"); + let print_nl = flags.contains(PrintFlags::NL) && !is_nl; + let print_repr = flags.contains(PrintFlags::REPR); + + if print_to_stdout { + if print_repr { + print!("{:?}", &*obj); + } else if is_nl { + println!(); } else { - eprint!("{obj}"); + print!("{obj}"); } - if flags.contains(PrintFlags::NL) { - eprintln!(); + if print_nl { + println!(); } } else { - if flags.contains(PrintFlags::REPR) { - print!("{:?}", &*obj); + if print_repr { + eprint!("{:?}", &*obj); + } else if is_nl { + eprintln!(); } else { - print!("{obj}"); + eprint!("{obj}"); } - if flags.contains(PrintFlags::NL) { - println!(); + if print_nl { + eprintln!(); } } + Ok(()) } else { Err(RuntimeErr::empty_stack()) @@ -896,7 +937,7 @@ impl VM { let expected_type = &*expected_type.read().unwrap(); let this = bound_func.this(); let this = this.read().unwrap(); - let this_type = this.type_obj(); + let this_type = this.class(); let this_type = this_type.read().unwrap(); // class method || instance method // XXX: Not sure this is the best way to distinguish @@ -920,10 +961,12 @@ impl VM { ); self.call_closure(func_ref.clone(), this_opt, args) } else { - Err(func_obj.not_callable()) + self.push_temp(new::not_callable_err(callable_ref.clone())); + Ok(()) } } else { - Err(callable.not_callable()) + self.push_temp(new::not_callable_err(callable_ref.clone())); + Ok(()) } } @@ -935,18 +978,10 @@ impl VM { ) -> RuntimeResult { let args = self.check_call_args(func, &this_opt, args)?; self.push_call_frame(this_opt.clone(), None)?; - let result = (func.func())(self.find_this(), args, self); - match result { - Ok(return_val) => { - self.push_return_val(return_val); - self.pop_call_frame()?; - Ok(()) - } - Err(err) => { - self.reset(); - Err(err) - } - } + let return_val = (func.func())(self.find_this(), args); + self.push_return_val(return_val); + self.pop_call_frame()?; + Ok(()) } pub fn call_func( @@ -1034,7 +1069,9 @@ impl VM { || "".to_owned(), |this_ref| { let this_obj = this_ref.read().unwrap(); - format!("{}.", this_obj.class().read().unwrap().full_name()) + // TODO: + // format!("{}.", this_obj.class().read().unwrap().full_name()) + format!("{this_obj}") } ), name @@ -1088,15 +1125,6 @@ impl VM { self.value_stack.push(kind); } - fn push_global_const(&mut self, index: usize) -> RuntimeResult { - if let Some(obj) = self.global_constants.get(index) { - self.push(ValueStackKind::GlobalConstant(obj.clone(), index)); - Ok(()) - } else { - Err(RuntimeErr::constant_not_found(index)) - } - } - fn push_var(&mut self, depth: usize, name: String) -> RuntimeResult { let obj_ref = self.ctx.get_var_at_depth(depth, name.as_str())?; // XXX: This is a workaround for function args being created diff --git a/scripts/type.template b/scripts/type.template index 8be9038..305acda 100644 --- a/scripts/type.template +++ b/scripts/type.template @@ -10,12 +10,12 @@ use super::gen; use super::new; use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; + use super::ns::Namespace; // {{ type_name }} Type {{ type_rule }} -gen::type_and_impls!({{ type_name }}, {{ obj_name }}); + pub static {{ singleton_type_name }}: Lazy = Lazy::new(|| gen::obj_ref!({{ type_name }}::new())); diff --git a/src/ast/mod.rs b/src/ast/mod.rs deleted file mode 100644 index a0b76b9..0000000 --- a/src/ast/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub(crate) use ast::*; - -pub(crate) mod visitors; - -mod ast; diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs deleted file mode 100644 index 496cb26..0000000 --- a/src/compiler/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub(crate) use compiler::Compiler; -pub(crate) use result::{CompErr, CompErrKind}; - -mod compiler; -mod result; -mod scope; -mod visitor; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index cd64332..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! # FeInt -//! -//! FeInt is a stack-based bytecode interpreter. -#[macro_use] -extern crate bitflags; - -pub mod cli; -pub mod dis; -pub mod exe; -pub mod op; -pub mod repl; -pub mod result; -pub mod source; -pub mod vm; - -mod ast; -mod compiler; -mod format; -mod modules; -mod parser; -mod scanner; -mod types; -mod util; - -#[cfg(test)] -mod tests; diff --git a/src/modules/mod.rs b/src/modules/mod.rs deleted file mode 100644 index 07068bf..0000000 --- a/src/modules/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -use ::std::sync::{Arc, RwLock}; -use once_cell::sync::Lazy; - -use crate::types::gen::{obj_ref, obj_ref_t}; -use crate::types::{Map, ObjectRef, ObjectTrait}; - -pub mod std; - -/// This mirrors `system.modules`. It provides a way to access -/// modules in Rust code (e.g., in the VM). -pub static MODULES: Lazy = Lazy::new(|| obj_ref!(Map::default())); - -/// Add module to `std.system.modules`. -pub fn add_module(name: &str, module: ObjectRef) { - let modules = MODULES.write().unwrap(); - let modules = modules.down_to_map().unwrap(); - modules.insert(name, module); -} - -/// Get module from `system.modules`. -/// -/// XXX: Panics if the module doesn't exist (since that shouldn't be -/// possible). -pub fn get_module(name: &str) -> ObjectRef { - let modules = MODULES.read().unwrap(); - let modules = modules.down_to_map().unwrap(); - if let Some(module) = modules.get(name) { - module.clone() - } else { - panic!("Module not registered: {name}"); - } -} - -/// Get module from `system.modules`. -/// -/// XXX: This will return `None` if the module doesn't exist. Generally, -/// this should only be used during bootstrap. In most cases, -/// `get_module` should be used instead. -pub fn maybe_get_module(name: &str) -> Option { - let modules = MODULES.read().unwrap(); - let modules = modules.down_to_map().unwrap(); - modules.get(name) -} diff --git a/src/modules/std/proc.rs b/src/modules/std/proc.rs deleted file mode 100644 index 6db6506..0000000 --- a/src/modules/std/proc.rs +++ /dev/null @@ -1,18 +0,0 @@ -//use std::process::Command; -use std::sync::{Arc, RwLock}; - -use once_cell::sync::Lazy; - -use crate::types::gen::obj_ref_t; -use crate::types::{new, Module}; - -pub static PROC: Lazy = Lazy::new(|| { - new::intrinsic_module( - "std.proc", - "", - "Proc module", - &[ - // TODO: - ], - ) -}); diff --git a/src/modules/std/std.fi b/src/modules/std/std.fi deleted file mode 100644 index 71c5c9d..0000000 --- a/src/modules/std/std.fi +++ /dev/null @@ -1,94 +0,0 @@ -"std module (builtins)" - - -print = (...) => - "Print representation of zero or more objects to stdout. - - # Args - - - objects?: Any[] - - " - i = 0 - loop i < $args.length -> - $print ($args.i, false) - $print (" ", false) - i += 1 - $print ("", false, true) - - -print_err = (...) => - "Print representation of zero or more objects to stderr. - - # Args - - - objects?: Any[] - - " - i = 0 - loop i < $args.length -> - $print ($args.i, true) - $print (" ", true) - i += 1 - $print ("", true, true) - - -type: Type = (obj: Any) => - "Get the type of an object." - print(obj.$type) - - -id = (obj: Any) => - "Get the ID of an object." - print(obj.$id) - - -help = (obj: Any) => - "Print the docstring for an object." - doc = obj.$doc - - result = if doc.err -> - match doc.err -> - ErrType.attr_not_found -> $"Object doesn't have a docstring: {obj}" - * -> $"{doc.err}" - else -> - match doc -> - nil -> $"Object has a nil docstring: {obj}" - "" -> $"Object has an empty docstring: {obj}" - * -> obj.$doc - - print(result) - - -assert: Bool | Err = (condition: Bool, ...) => - "Check condition and return error if false. - - # Args - - - condition - - message?: Any - - halt?: Bool = false - - # Returns - - true: if the assertion succeeded - Err: if the assertion failed and `halt` is unset - - > NOTE: If `halt` is set, the program will exit immediately with an - > error code. - - " - if condition -> - true - else -> - msg = $args.get(0) - - err = match msg -> - nil -> Err.new(ErrType.assertion, "") - * -> Err.new(ErrType.assertion, Str.new(msg)) - - if $args.get(1) -> - print_err(err) - $halt 1 - - err diff --git a/src/tests/compiler.rs b/src/tests/compiler.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/tests/compiler.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/tests/exe.rs b/src/tests/exe.rs deleted file mode 100644 index c81f555..0000000 --- a/src/tests/exe.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::exe::Executor; -use crate::result::{ExeErrKind, ExeResult}; -use crate::vm::RuntimeErrKind; - -fn execute(source: &str) -> ExeResult { - let mut exe = Executor::new(16, vec![], false, false, false); - exe.bootstrap()?; - exe.execute_text(source) -} - -#[test] -fn test_too_much_recursion() { - let result = execute("f = () => f()\nf()"); - assert!(result.is_err()); - let err = result.unwrap_err(); - assert!(matches!( - err.kind, - ExeErrKind::RuntimeErr(RuntimeErrKind::RecursionDepthExceeded(_)) - )); -} diff --git a/src/tests/mod.rs b/src/tests/mod.rs deleted file mode 100644 index 20cfdf1..0000000 --- a/src/tests/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod ast; -mod compiler; -mod exe; -mod format; -mod parser; -mod repl; -mod run; -mod scanner; -mod types; -mod util; -mod vm; diff --git a/src/types/bool.rs b/src/types/bool.rs deleted file mode 100644 index b7eeb3c..0000000 --- a/src/types/bool.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use once_cell::sync::Lazy; - -use crate::vm::{RuntimeBoolResult, RuntimeErr}; - -use super::gen; -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; - -// Bool Type ----------------------------------------------------------- - -gen::type_and_impls!(BoolType, Bool); - -pub static BOOL_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(BoolType::new())); - -// Bool Object --------------------------------------------------------- - -pub struct Bool { - ns: Namespace, - value: bool, -} - -gen::standard_object_impls!(Bool); - -impl Bool { - pub fn new(value: bool) -> Self { - Self { ns: Namespace::default(), value } - } - - pub fn value(&self) -> &bool { - &self.value - } -} - -impl ObjectTrait for Bool { - gen::object_trait_header!(BOOL_TYPE); - - // Unary operations ----------------------------------------------- - - fn bool_val(&self) -> RuntimeBoolResult { - Ok(*self.value()) - } - - // Binary operations ----------------------------------------------- - - fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { - if self.is(rhs) || rhs.is_always() { - true - } else if let Some(rhs) = rhs.down_to_bool() { - self.value() == rhs.value() - } else { - false - } - } - - fn and(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - if let Some(rhs) = rhs.down_to_bool() { - Ok(*self.value() && *rhs.value()) - } else { - Err(RuntimeErr::type_err(format!( - "{} && {} not implemented", - self.class().read().unwrap(), - rhs.class().read().unwrap(), - ))) - } - } - - fn or(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - if let Some(rhs) = rhs.down_to_bool() { - Ok(*self.value() || *rhs.value()) - } else { - Err(RuntimeErr::type_err(format!( - "{} || {} not implemented", - self.class().read().unwrap(), - rhs.class().read().unwrap(), - ))) - } - } -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for Bool { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.value) - } -} - -impl fmt::Debug for Bool { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/class.rs b/src/types/class.rs deleted file mode 100644 index f26a1ec..0000000 --- a/src/types/class.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! "Class" and "type" are used interchangeably and mean exactly the -//! same thing. Lower case "class" is used instead of "type" because the -//! latter is a Rust keyword. -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use once_cell::sync::Lazy; - -use super::gen; -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::ns::Namespace; - -// Type Type ----------------------------------------------------------- - -gen::type_and_impls!(TypeType, Type); - -pub static TYPE_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(TypeType::new())); - -// Type Object --------------------------------------------------------- - -pub struct Type { - ns: Namespace, -} - -gen::standard_object_impls!(Type); - -impl Type { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self { ns: Namespace::default() } - } -} - -impl ObjectTrait for Type { - gen::object_trait_header!(TYPE_TYPE); -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for Type { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} @ {}", self.type_obj().read().unwrap(), self.id()) - } -} - -impl fmt::Debug for Type { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/custom.rs b/src/types/custom.rs deleted file mode 100644 index 2a8795d..0000000 --- a/src/types/custom.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use super::gen; -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; - -// Custom Type --------------------------------------------------------- - -/// TODO: This shouldn't need to be cloneable -#[derive(Clone)] -pub struct CustomType { - ns: Namespace, - module: ObjectRef, - name: String, - full_name: String, -} - -impl CustomType { - pub fn new(module_ref: ObjectRef, name: String) -> Self { - let module = module_ref.read().unwrap(); - let module = module.down_to_mod().unwrap(); - let full_name = format!("{}.{name}", module.name()); - let type_ref = Self { - ns: Namespace::with_entries(&[ - // Class Attributes - ("$module_name", new::str(module.name())), - ("$full_name", new::str(&full_name)), - ("$name", new::str(&name)), - ]), - module: module_ref.clone(), - name, - full_name, - }; - type_ref - } -} - -gen::standard_object_impls!(CustomType); - -impl TypeTrait for CustomType { - fn name(&self) -> &str { - self.name.as_str() - } - - fn full_name(&self) -> &str { - self.full_name.as_str() - } - - fn ns(&self) -> &Namespace { - &self.ns - } - - fn module(&self) -> ObjectRef { - self.module.clone() - } -} - -/// NOTE: This is customized so the module is correct. -impl ObjectTrait for CustomType { - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn class(&self) -> TypeRef { - TYPE_TYPE.clone() - } - - fn type_obj(&self) -> ObjectRef { - TYPE_TYPE.clone() - } - - fn ns(&self) -> &Namespace { - &self.ns - } - - fn ns_mut(&mut self) -> &mut Namespace { - &mut self.ns - } - - fn as_type(&self) -> Option<&dyn TypeTrait> { - Some(self) - } - - fn module(&self) -> ObjectRef { - self.module.clone() - } -} - -// Custom Object ------------------------------------------------------- - -pub struct CustomObj { - type_obj: gen::obj_ref_t!(CustomType), - ns: Namespace, -} - -gen::standard_object_impls!(CustomObj); - -impl CustomObj { - pub fn new(type_obj: gen::obj_ref_t!(CustomType), attrs: Namespace) -> Self { - Self { type_obj, ns: attrs } - } -} - -impl ObjectTrait for CustomObj { - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn class(&self) -> TypeRef { - self.type_obj.clone() - } - - fn type_obj(&self) -> ObjectRef { - self.type_obj.clone() - } - - fn ns(&self) -> &Namespace { - &self.ns - } - - fn ns_mut(&mut self) -> &mut Namespace { - &mut self.ns - } - - fn as_type(&self) -> Option<&dyn TypeTrait> { - None - } - - fn set_attr( - &mut self, - name: &str, - value: ObjectRef, - _this: ObjectRef, - ) -> ObjectRef { - self.ns.set(name, value); - new::nil() - } - - fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { - self.is(rhs) || rhs.is_always() || self.ns.is_equal(rhs.ns()) - } -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for CustomObj { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let class = self.class(); - let class = class.read().unwrap(); - write!(f, "<{} object @ {}>", class.full_name(), self.id()) - } -} - -impl fmt::Debug for CustomObj { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/err.rs b/src/types/err.rs deleted file mode 100644 index 6c384d6..0000000 --- a/src/types/err.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! # Error Type -//! -//! The error type represents _recoverable_ runtime errors that can be -//! checked in user code using this pattern: -//! -//! result = assert(false) -//! if result.err -> -//! # Handle `result` as an error -//! print(result) -//! -//! _All_ objects respond to `err`, which returns either an `Err` object -//! or `nil`. `Err` objects evaluate as `false` in a boolean context: -//! -//! if !assert(false) -> -//! print("false is not true") -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use once_cell::sync::Lazy; - -use crate::util::check_args; -use crate::vm::{RuntimeBoolResult, RuntimeErr}; - -use super::gen; -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::err_type::ErrKind; -use super::ns::Namespace; - -// Err Type ------------------------------------------------------------ - -gen::type_and_impls!(ErrType, Err); - -pub static ERR_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(ErrType::new()); - let mut type_obj = type_ref.write().unwrap(); - - type_obj.add_attrs(&[ - // Class Methods ----------------------------------------------- - gen::meth!("new", type_ref, &["type", "msg"], "", |_, args, _| { - let name = "Err.new()"; - - let result = check_args(name, &args, false, 2, Some(2)); - if let Err(err) = result { - return Ok(err); - } - - let type_arg = gen::use_arg!(args, 0); - let msg_arg = gen::use_arg!(args, 1); - - let err_type = if let Some(err_type) = type_arg.down_to_err_type_obj() { - err_type - } else { - let arg_err_msg = format!("{name} expected type to be an ErrType"); - // NOTE: This is problematic because user code won't be - // able to tell if the arg error was the result of - // creating an arg err explicitly or the result of - // an internal error. Note that this applies to - // *any* user-constructible error. - // - // TODO: Figure out a solution for this, perhaps an err - // type that is *not* user-constructible or a - // nested err type? - return Ok(new::arg_err(arg_err_msg, new::nil())); - }; - - let kind = err_type.kind().clone(); - - let msg = if let Some(msg) = msg_arg.get_str_val() { - msg - } else { - let arg_err_msg = format!("{name} expected message to be a Str"); - return Ok(new::arg_err(arg_err_msg, new::nil())); - }; - - Ok(new::err(kind, msg, new::nil())) - }), - // Instance Attributes ----------------------------------------- - gen::prop!("type", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_err().unwrap(); - Ok(this.kind.get_obj().unwrap()) - }), - gen::prop!("message", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_err().unwrap(); - Ok(new::str(&this.message)) - }), - ]); - - type_ref.clone() -}); - -// Error Object -------------------------------------------------------- - -// NOTE: This is named `ErrObj` instead of `Err` to avoid conflict with -// Rust's `Err`. -pub struct ErrObj { - ns: Namespace, - pub kind: ErrKind, - pub message: String, - pub obj: ObjectRef, - bool_val: bool, - responds_to_bool: bool, -} - -gen::standard_object_impls!(ErrObj); - -impl ErrObj { - pub fn new(kind: ErrKind, message: String, obj: ObjectRef) -> Self { - let bool_val = kind != ErrKind::Ok; - Self { - ns: Namespace::default(), - kind, - message, - obj, - bool_val, - responds_to_bool: false, - } - } - - pub fn with_responds_to_bool( - kind: ErrKind, - message: String, - obj: ObjectRef, - ) -> Self { - let mut instance = Self::new(kind, message, obj); - instance.responds_to_bool = true; - instance - } - - pub fn retrieve_bool_val(&self) -> bool { - self.bool_val - } -} - -impl ObjectTrait for ErrObj { - gen::object_trait_header!(ERR_TYPE); - - fn bool_val(&self) -> RuntimeBoolResult { - if self.responds_to_bool { - Ok(self.bool_val) - } else { - Err(RuntimeErr::type_err(concat!( - "An Err object cannot be evaluated directly as a ", - "Bool. You must access it via the `.err` attribute of ", - "the result object.", - ))) - } - } - - fn and(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - let lhs = self.bool_val()?; - let rhs = rhs.bool_val()?; - Ok(lhs && rhs) - } - - fn or(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - let lhs = self.bool_val()?; - let rhs = rhs.bool_val()?; - Ok(lhs || rhs) - } -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for ErrObj { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let kind = &self.kind; - let msg = &self.message; - if self.message.is_empty() { - write!(f, "{} [{}]", kind, kind.name()) - } else { - write!(f, "[{}] {}: {}", kind.name(), kind, msg) - } - } -} - -impl fmt::Debug for ErrObj { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/float.rs b/src/types/float.rs deleted file mode 100644 index 8905a92..0000000 --- a/src/types/float.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use num_traits::ToPrimitive; -use once_cell::sync::Lazy; - -use crate::vm::{RuntimeBoolResult, RuntimeErr, RuntimeObjResult}; - -use super::gen; - -use super::new; -use super::util::{eq_int_float, gt_int_float, lt_int_float}; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; - -// Float Type ---------------------------------------------------------- - -gen::type_and_impls!(FloatType, Float); - -pub static FLOAT_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(FloatType::new()); - let mut type_obj = type_ref.write().unwrap(); - - type_obj.add_attrs(&[ - // Class Methods ----------------------------------------------- - gen::meth!("new", type_ref, &["value"], "", |_, args, _| { - let arg = gen::use_arg!(args, 0); - let float = if let Some(val) = arg.get_float_val() { - new::float(*val) - } else if let Some(val) = arg.get_int_val() { - new::float(val.to_f64().unwrap()) - } else if let Some(val) = arg.get_str_val() { - new::float_from_string(val) - } else { - let message = format!("Float new expected string or float; got {arg}"); - return Err(RuntimeErr::type_err(message)); - }; - Ok(float) - }), - ]); - - type_ref.clone() -}); - -// Float Object -------------------------------------------------------- - -macro_rules! make_op { - ( $meth:ident, $op:tt, $message:literal, $trunc:literal ) => { - fn $meth(&self, rhs: &dyn ObjectTrait) -> RuntimeObjResult { - let value = if let Some(rhs) = rhs.down_to_float() { - *rhs.value() - } else if let Some(rhs) = rhs.down_to_int() { - rhs.value().to_f64().unwrap() - } else { - return Err(RuntimeErr::type_err(format!($message, rhs.class().read().unwrap()))); - }; - let mut value = &self.value $op value; - if $trunc { - value = value.trunc(); - } - let value = new::float(value); - Ok(value) - } - }; -} - -pub struct Float { - ns: Namespace, - value: f64, -} - -gen::standard_object_impls!(Float); - -impl Float { - pub fn new(value: f64) -> Self { - Self { ns: Namespace::default(), value } - } - - pub fn value(&self) -> &f64 { - &self.value - } -} - -impl ObjectTrait for Float { - gen::object_trait_header!(FLOAT_TYPE); - - fn negate(&self) -> RuntimeObjResult { - Ok(new::float(-*self.value())) - } - - fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { - if self.is(rhs) || rhs.is_always() { - true - } else if let Some(rhs) = rhs.down_to_float() { - self.value() == rhs.value() - } else if let Some(rhs) = rhs.down_to_int() { - eq_int_float(rhs, self) - } else { - false - } - } - - fn less_than(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - if let Some(rhs) = rhs.down_to_float() { - Ok(self.value() < rhs.value()) - } else if let Some(rhs) = rhs.down_to_int() { - Ok(lt_int_float(rhs, self)) - } else { - Err(RuntimeErr::type_err(format!( - "Could not compare {} to {}: <", - self.class().read().unwrap(), - rhs.class().read().unwrap() - ))) - } - } - - fn greater_than(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - if let Some(rhs) = rhs.down_to_float() { - Ok(self.value() > rhs.value()) - } else if let Some(rhs) = rhs.down_to_int() { - Ok(gt_int_float(rhs, self)) - } else { - Err(RuntimeErr::type_err(format!( - "Could not compare {} to {}: >", - self.class().read().unwrap(), - rhs.class().read().unwrap() - ))) - } - } - - fn pow(&self, rhs: &dyn ObjectTrait) -> RuntimeObjResult { - let exp = if let Some(rhs) = rhs.down_to_float() { - *rhs.value() - } else if let Some(rhs) = rhs.down_to_int() { - rhs.value().to_f64().unwrap() - } else { - return Err(RuntimeErr::type_err(format!( - "Could not raise {} by {}", - self.class().read().unwrap(), - rhs.class().read().unwrap() - ))); - }; - let value = self.value().powf(exp); - let value = new::float(value); - Ok(value) - } - - make_op!(modulo, %, "Could not divide {} with Float", false); - make_op!(mul, *, "Could not multiply {} with Float", false); - make_op!(div, /, "Could not divide {} into Float", false); - make_op!(floor_div, /, "Could not divide {} into Float", true); // truncates - make_op!(add, +, "Could not add {} to Float", false); - make_op!(sub, -, "Could not subtract {} from Float", false); -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for Float { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.value().fract() == 0.0 { - write!(f, "{}.0", self.value) - } else { - write!(f, "{}", self.value) - } - } -} - -impl fmt::Debug for Float { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/int.rs b/src/types/int.rs deleted file mode 100644 index fb66030..0000000 --- a/src/types/int.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use num_bigint::BigInt; -use num_traits::{FromPrimitive, ToPrimitive}; - -use once_cell::sync::Lazy; - -use crate::vm::{RuntimeBoolResult, RuntimeErr, RuntimeObjResult}; - -use super::gen; - -use super::new; -use super::util::{eq_int_float, gt_int_float, lt_int_float}; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; - -// Int Type ------------------------------------------------------------ - -static DOC: &str = " -Intrinsic Int type -"; - -gen::type_and_impls!(IntType, Int); - -pub static INT_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(IntType::new()); - let mut type_obj = type_ref.write().unwrap(); - - type_obj.add_attrs(&[ - ("$doc", new::str(DOC)), - // Class Methods ----------------------------------------------- - gen::meth!("new", type_ref, &["value"], "", |_, args, _| { - let arg = gen::use_arg!(args, 0); - let int = if let Some(val) = arg.get_int_val() { - new::int(val.clone()) - } else if let Some(val) = arg.get_float_val() { - new::int(BigInt::from_f64(*val).unwrap()) - } else if let Some(val) = arg.get_str_val() { - new::int_from_string(val) - } else { - let message = format!("Int.new() expected number or string; got {arg}"); - return Err(RuntimeErr::type_err(message)); - }; - Ok(int) - }), - ]); - - type_ref.clone() -}); - -// Int Object ---------------------------------------------------------- - -macro_rules! make_op { - ( $meth:ident, $op:tt, $message:literal ) => { - fn $meth(&self, rhs: &dyn ObjectTrait) -> RuntimeObjResult { - if let Some(rhs) = rhs.down_to_int() { - // XXX: Return Int - let value = self.value() $op rhs.value(); - let value = new::int(value); - Ok(value) - } else if let Some(rhs) = rhs.down_to_float() { - // XXX: Return Float - let value = self.value().to_f64().unwrap() $op rhs.value(); - let value = new::float(value); - Ok(value) - } else { - Err(RuntimeErr::type_err(format!($message, rhs.class().read().unwrap()))) - } - } - }; -} - -pub struct Int { - ns: Namespace, - value: BigInt, -} - -gen::standard_object_impls!(Int); - -impl Int { - pub fn new(value: BigInt) -> Self { - Self { ns: Namespace::default(), value } - } - - pub fn value(&self) -> &BigInt { - &self.value - } - - // Cast both LHS and RHS to f64 and divide them - fn div_f64(&self, rhs: &dyn ObjectTrait) -> Result { - let lhs_val = self.value().to_f64().unwrap(); - let rhs_val = if let Some(rhs) = rhs.down_to_int() { - rhs.value().to_f64().unwrap() - } else if let Some(rhs) = rhs.down_to_float() { - *rhs.value() - } else { - return Err(RuntimeErr::type_err(format!( - "Could not divide {} into Int", - rhs.class().read().unwrap() - ))); - }; - Ok(lhs_val / rhs_val) - } -} - -impl ObjectTrait for Int { - gen::object_trait_header!(INT_TYPE); - - fn negate(&self) -> RuntimeObjResult { - Ok(new::int(-self.value.clone())) - } - - fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { - if self.is(rhs) || rhs.is_always() { - true - } else if let Some(rhs) = rhs.down_to_int() { - self.value() == rhs.value() - } else if let Some(rhs) = rhs.down_to_float() { - eq_int_float(self, rhs) - } else { - false - } - } - - fn less_than(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - if let Some(rhs) = rhs.down_to_int() { - Ok(self.value() < rhs.value()) - } else if let Some(rhs) = rhs.down_to_float() { - Ok(lt_int_float(self, rhs)) - } else { - Err(RuntimeErr::type_err(format!( - "Could not compare {} to {}: >", - self.class().read().unwrap(), - rhs.class().read().unwrap() - ))) - } - } - - fn greater_than(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - if let Some(rhs) = rhs.down_to_int() { - Ok(self.value() > rhs.value()) - } else if let Some(rhs) = rhs.down_to_float() { - Ok(gt_int_float(self, rhs)) - } else { - Err(RuntimeErr::type_err(format!( - "Could not compare {} to {}: >", - self.class().read().unwrap(), - rhs.class().read().unwrap() - ))) - } - } - - fn pow(&self, rhs: &dyn ObjectTrait) -> RuntimeObjResult { - if let Some(rhs) = rhs.down_to_int() { - // XXX: Return Int - let base = self.value(); - let exp = rhs.value().to_u32().unwrap(); - let value = base.pow(exp); - let value = new::int(value); - Ok(value) - } else if let Some(rhs) = rhs.down_to_float() { - // XXX: Return Float - let base = self.value().to_f64().unwrap(); - let exp = *rhs.value(); - let value = base.powf(exp); - let value = new::float(value); - Ok(value) - } else { - Err(RuntimeErr::type_err(format!( - "Could not raise {} by {}", - self.class().read().unwrap(), - rhs.class().read().unwrap() - ))) - } - } - - make_op!(modulo, %, "Could not divide {} with Int"); - make_op!(mul, *, "Could not multiply {} with Int"); - make_op!(add, +, "Could not add {} to Int"); - make_op!(sub, -, "Could not subtract {} from Int"); - - // Int division *always* returns a Float - fn div(&self, rhs: &dyn ObjectTrait) -> RuntimeObjResult { - let value = self.div_f64(rhs)?; - let value = new::float(value); - Ok(value) - } - - // Int *floor* division *always* returns an Int - fn floor_div(&self, rhs: &dyn ObjectTrait) -> RuntimeObjResult { - let value = self.div_f64(rhs)?; - let value = BigInt::from_f64(value).unwrap(); - let value = new::int(value); - Ok(value) - } -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for Int { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.value) - } -} - -impl fmt::Debug for Int { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/iterator.rs b/src/types/iterator.rs deleted file mode 100644 index dde5707..0000000 --- a/src/types/iterator.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use once_cell::sync::Lazy; - -use super::gen; -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; - -// IteratorType Type --------------------------------------------------- - -gen::type_and_impls!(IteratorType, Iterator); - -pub static ITERATOR_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(IteratorType::new()); - let mut type_obj = type_ref.write().unwrap(); - - type_obj.add_attrs(&[ - // Instance Methods -------------------------------------------- - gen::meth!("next", type_ref, &[], "", |this, _, _| { - let mut this = this.write().unwrap(); - let this = this.down_to_iterator_mut().unwrap(); - Ok(this.next()) - }), - gen::meth!("peek", type_ref, &[], "", |this, _, _| { - let this = this.write().unwrap(); - let this = this.down_to_iterator().unwrap(); - Ok(this.peek()) - }), - ]); - - type_ref.clone() -}); - -// Iterator Object ----------------------------------------------------- - -pub struct FIIterator { - ns: Namespace, - wrapped: Vec, - current: usize, -} - -gen::standard_object_impls!(FIIterator); - -impl FIIterator { - pub fn new(wrapped: Vec) -> Self { - Self { ns: Namespace::default(), wrapped, current: 0 } - } - - fn next(&mut self) -> ObjectRef { - let obj = self.get_or_nil(self.current); - if self.current < self.len() { - self.current += 1; - } - obj - } - - fn peek(&self) -> ObjectRef { - self.get_or_nil(self.current) - } - - fn len(&self) -> usize { - self.wrapped.len() - } - - fn get_or_nil(&self, index: usize) -> ObjectRef { - if index >= self.len() { - new::nil() - } else { - self.wrapped[index].clone() - } - } -} - -impl ObjectTrait for FIIterator { - gen::object_trait_header!(ITERATOR_TYPE); -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for FIIterator { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "") - } -} - -impl fmt::Debug for FIIterator { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/list.rs b/src/types/list.rs deleted file mode 100644 index b98ac94..0000000 --- a/src/types/list.rs +++ /dev/null @@ -1,261 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use once_cell::sync::Lazy; - -use crate::vm::{RuntimeErr, RuntimeResult}; - -use super::gen; - -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; -use super::seq; - -// List Type ----------------------------------------------------------- - -gen::type_and_impls!(ListType, List); - -pub static LIST_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(ListType::new()); - let mut type_obj = type_ref.write().unwrap(); - - type_obj.add_attrs(&[ - // Instance Attributes ----------------------------------------- - gen::prop!("length", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_list().unwrap(); - Ok(new::int(this.len())) - }), - gen::prop!("is_empty", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_list().unwrap(); - Ok(new::bool(this.len() == 0)) - }), - gen::prop!("sum", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_list().unwrap(); - let items = &this.items.read().unwrap(); - seq::sum(items) - }), - // Instance Methods -------------------------------------------- - gen::meth!( - "each", - type_ref, - &["each_fn"], - "Apply function to each List item. - - # Args - - - func: Func - - A function that will be passed each item in turn and, optionally, the - index of the item. - - ", - |this_obj, args, vm| { - let this = this_obj.read().unwrap(); - let this = this.down_to_list().unwrap(); - let items = &this.items.read().unwrap(); - seq::each(&this_obj, items, &args, vm) - } - ), - gen::meth!( - "extend", - type_ref, - &["items"], - "Push items and return this.", - |this, args, _| { - let return_val = this.clone(); - let this = this.read().unwrap(); - let this = this.down_to_list().unwrap(); - this.extend(args[0].clone())?; - Ok(return_val) - } - ), - gen::meth!("get", type_ref, &["index"], "", |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_list().unwrap(); - let index = gen::use_arg_usize!(get, index, args, 0); - let result = match this.get(index) { - Some(obj) => obj, - None => new::nil(), - }; - Ok(result) - }), - gen::meth!("has", type_ref, &["member"], "", |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_list().unwrap(); - let items = &this.items.read().unwrap(); - seq::has(items, &args) - }), - gen::meth!("join", type_ref, &["sep"], "", |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_list().unwrap(); - let items = &this.items.read().unwrap(); - seq::join(items, &args) - }), - gen::meth!("map", type_ref, &["map_fn"], "", |this_obj, args, vm| { - let this = this_obj.read().unwrap(); - let this = this.down_to_list().unwrap(); - let items = &this.items.read().unwrap(); - seq::map(&this_obj, items, &args, vm) - }), - gen::meth!("pop", type_ref, &[], "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_list().unwrap(); - let result = match this.pop() { - Some(obj) => obj, - None => new::nil(), - }; - Ok(result) - }), - gen::meth!( - "push", - type_ref, - &["item"], - "Push item and return it.", - |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_list().unwrap(); - let arg = args[0].clone(); - this.push(arg.clone()); - Ok(arg) - } - ), - ]); - - type_ref.clone() -}); - -// List Object --------------------------------------------------------- - -pub struct List { - ns: Namespace, - items: RwLock>, -} - -gen::standard_object_impls!(List); - -impl List { - pub fn new(items: Vec) -> Self { - Self { ns: Namespace::default(), items: RwLock::new(items) } - } - - pub fn len(&self) -> usize { - let items = self.items.read().unwrap(); - items.len() - } - - pub fn push(&self, item: ObjectRef) { - let items = &mut self.items.write().unwrap(); - items.push(item); - } - - pub fn extend(&self, obj: ObjectRef) -> RuntimeResult { - let obj = obj.read().unwrap(); - let items = &mut self.items.write().unwrap(); - if let Some(list) = obj.down_to_list() { - let new_items = list.items.read().unwrap(); - for item in new_items.iter() { - items.push(item.clone()); - } - } else if let Some(tuple) = obj.down_to_tuple() { - for item in tuple.iter() { - items.push(item.clone()); - } - } else { - // TODO: Do type checking at a higher level - let msg = format!( - "List.extend() expected List or Tuple; got {}", - obj.class().read().unwrap() - ); - return Err(RuntimeErr::type_err(msg)); - } - Ok(()) - } - - pub fn pop(&self) -> Option { - let items = &mut self.items.write().unwrap(); - if let Some(item) = items.pop() { - Some(item.clone()) - } else { - None - } - } - - pub fn get(&self, index: usize) -> Option { - let items = self.items.read().unwrap(); - if let Some(item) = items.get(index) { - Some(item.clone()) - } else { - None - } - } -} - -impl ObjectTrait for List { - gen::object_trait_header!(LIST_TYPE); - - fn get_item(&self, index: usize, this: ObjectRef) -> ObjectRef { - if let Some(item) = self.get(index) { - item.clone() - } else { - self.index_out_of_bounds(index, this) - } - } - - fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { - if self.is(rhs) || rhs.is_always() { - return true; - } - if let Some(rhs) = rhs.down_to_list() { - if self.len() != rhs.len() { - return false; - } - let items = self.items.read().unwrap(); - let rhs_items = rhs.items.read().unwrap(); - for (a, b) in items.iter().zip(rhs_items.iter()) { - let a = a.read().unwrap(); - let b = b.read().unwrap(); - if !a.is_equal(&*b) { - return false; - } - } - true - } else { - false - } - } -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for List { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let this_id = self.id(); - let items = self.items.read().unwrap(); - let items: Vec = items - .iter() - .map(|item| { - let item = item.read().unwrap(); - if item.id() == this_id { - "[...]".to_owned() - } else { - format!("{:?}", &*item) - } - }) - .collect(); - let items_str = items.join(", "); - write!(f, "[{}]", items_str) - } -} - -impl fmt::Debug for List { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/map.rs b/src/types/map.rs deleted file mode 100644 index 69a8572..0000000 --- a/src/types/map.rs +++ /dev/null @@ -1,275 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use indexmap::IndexMap; -use once_cell::sync::Lazy; - -use crate::vm::RuntimeErr; - -use super::gen; -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; - -// Map Type ------------------------------------------------------------ - -gen::type_and_impls!(MapType, Map); - -pub static MAP_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(MapType::new()); - let mut type_obj = type_ref.write().unwrap(); - - type_obj.add_attrs(&[ - // Instance Attributes ----------------------------------------- - gen::prop!("length", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_map().unwrap(); - Ok(new::int(this.len())) - }), - gen::prop!("is_empty", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_map().unwrap(); - Ok(new::bool(this.is_empty())) - }), - // Instance Methods -------------------------------------------- - gen::meth!( - "add", - type_ref, - &["key", "val"], - "Add entry to Map. - - # Args - - - key: Str - - value: Any - - ", - |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_map().unwrap(); - let arg = gen::use_arg!(args, 0); - let key = gen::use_arg_str!(get, key, arg); - let val = args[1].clone(); - this.insert(key, val); - Ok(new::nil()) - } - ), - gen::meth!( - "each", - type_ref, - &["each_fn"], - "Apply function to each Map entry. - - # Args - - - func: Func - - A function that will be passed the key and value of each entry in - turn. - - ``` - → map = {'a': 'a', 'b': 'b'} - {'a' => 'a', 'b' => 'b'} - → fn = (k, v) => print($'{k} = {v}') - function fn/2 @ - → map.each(fn) - a = a - b = b - ``` - - ", - |this_obj, args, vm| { - let this = this_obj.read().unwrap(); - let this = this.down_to_map().unwrap(); - let entries = &this.entries.read().unwrap(); - - if entries.is_empty() { - return Ok(new::nil()); - } - - let each_fn = &args[0]; - let n_args = if let Some(f) = each_fn.read().unwrap().as_func() { - if f.has_var_args() { - 3 - } else { - f.arity() - } - } else { - return Ok(new::arg_err( - "each/1 expects a function", - this_obj.clone(), - )); - }; - - for (i, (key, val)) in entries.iter().enumerate() { - let each = each_fn.clone(); - let key = new::str(key); - if n_args == 1 { - vm.call(each, vec![key])?; - } else if n_args == 2 { - vm.call(each, vec![key, val.clone()])?; - } else { - vm.call(each, vec![key, val.clone(), new::int(i)])?; - } - } - - Ok(new::nil()) - } - ), - gen::meth!( - "get", - type_ref, - &["key"], - "Get value for key from Map. - - # Args - - - key: Key - - # Returns - - - Any: If key is present - - nil: If key is not present - - > NOTE: There's no way to distinguish between a key that isn't present - > versus a key that has `nil` as its value. To avoid ambiguity, don't - > store `nil` values. - - ", - |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_map().unwrap(); - let arg = gen::use_arg!(args, 0); - let key = gen::use_arg_str!(get, key, arg); - let result = match this.get(key) { - Some(obj) => obj, - None => new::nil(), - }; - Ok(result) - } - ), - gen::meth!("has", type_ref, &["member"], "", |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_map().unwrap(); - let arg = gen::use_arg!(args, 0); - let key = gen::use_arg_str!(get, key, arg); - let result = this.contains_key(key); - Ok(new::bool(result)) - }), - ]); - - type_ref.clone() -}); - -// Map Object ---------------------------------------------------------- - -pub struct Map { - ns: Namespace, - entries: RwLock>, -} - -gen::standard_object_impls!(Map); - -impl Default for Map { - fn default() -> Self { - Self { ns: Namespace::default(), entries: RwLock::new(IndexMap::default()) } - } -} - -impl Map { - pub fn new(entries: IndexMap) -> Self { - Self { ns: Namespace::default(), entries: RwLock::new(entries) } - } - - pub fn len(&self) -> usize { - let entries = self.entries.read().unwrap(); - entries.len() - } - - pub fn is_empty(&self) -> bool { - let entries = self.entries.read().unwrap(); - entries.is_empty() - } - - pub fn insert>(&self, key: S, val: ObjectRef) { - let entries = &mut self.entries.write().unwrap(); - entries.insert(key.into(), val); - } - - pub fn get(&self, name: &str) -> Option { - let entries = self.entries.read().unwrap(); - if let Some(val) = entries.get(name) { - Some(val.clone()) - } else { - None - } - } - - pub fn contains_key(&self, key: &str) -> bool { - let entries = self.entries.read().unwrap(); - entries.contains_key(key) - } - - pub fn entries(&self) -> &RwLock> { - &self.entries - } -} - -impl ObjectTrait for Map { - gen::object_trait_header!(MAP_TYPE); - - fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { - if self.is(rhs) || rhs.is_always() { - return true; - } - if let Some(rhs) = rhs.down_to_map() { - if self.len() != rhs.len() { - return false; - } - let entries = self.entries.read().unwrap(); - let rhs_entries = rhs.entries.read().unwrap(); - entries.iter().all(|(name, a_ref)| { - if let Some(b_ref) = rhs_entries.get(name) { - let a = a_ref.read().unwrap(); - let b = b_ref.read().unwrap(); - a.is_equal(&*b) - } else { - false - } - }) - } else { - false - } - } -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for Map { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let this_id = self.id(); - let entries = self.entries.read().unwrap(); - let entries: Vec = entries - .iter() - .map(|(name, val)| { - let val = val.read().unwrap(); - if val.id() == this_id { - "{...}".to_owned() - } else { - format!("{name:?} => {:?}", &*val) - } - }) - .collect(); - let string = entries.join(", "); - write!(f, "{{{string}}}") - } -} - -impl fmt::Debug for Map { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index fdd7578..0000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Objects -pub(crate) use base::{ObjectRef, ObjectTrait}; -pub(crate) use map::Map; - -// Namespacing -pub(crate) use module::Module; -pub(crate) use ns::Namespace; - -// Functions -pub(crate) use func::Func; -pub(crate) use func_trait::FuncTrait; -pub(crate) use intrinsic_func::IntrinsicFunc; - -pub(crate) mod new; -pub(crate) use result::{Args, Params, ThisOpt}; - -mod base; -mod func_trait; - -// Namespace (not a type) -pub(crate) mod ns; - -// Intrinsic Types -pub(crate) mod always; -pub(crate) mod bool; -pub(crate) mod bound_func; -pub(crate) mod cell; -pub(crate) mod class; -pub(crate) mod closure; -pub(crate) mod custom; -pub(crate) mod err; -pub(crate) mod err_type; -pub(crate) mod file; -pub(crate) mod float; -pub(crate) mod func; -pub(crate) mod gen; -pub(crate) mod int; -pub(crate) mod intrinsic_func; -pub(crate) mod iterator; -pub(crate) mod list; -pub(crate) mod map; -pub(crate) mod module; -pub(crate) mod nil; -pub(crate) mod prop; -pub(crate) mod result; -pub(crate) mod seq; -pub(crate) mod str; -pub(crate) mod tuple; -pub(crate) mod util; diff --git a/src/types/nil.rs b/src/types/nil.rs deleted file mode 100644 index 032c00d..0000000 --- a/src/types/nil.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use once_cell::sync::Lazy; - -use crate::vm::RuntimeBoolResult; - -use super::gen; -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; - -// Nil Type ------------------------------------------------------------ - -gen::type_and_impls!(NilType, Nil); - -pub static NIL_TYPE: Lazy = - Lazy::new(|| gen::obj_ref!(NilType::new())); - -// Nil Object ---------------------------------------------------------- - -pub struct Nil { - ns: Namespace, -} - -gen::standard_object_impls!(Nil); - -impl Nil { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self { ns: Namespace::default() } - } -} - -impl ObjectTrait for Nil { - gen::object_trait_header!(NIL_TYPE); - - fn bool_val(&self) -> RuntimeBoolResult { - Ok(false) - } -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for Nil { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "nil") - } -} - -impl fmt::Debug for Nil { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/result.rs b/src/types/result.rs deleted file mode 100644 index 0da4e9c..0000000 --- a/src/types/result.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::vm::RuntimeErr; - -use super::base::ObjectRef; - -// TODO: Move call-related types elsewhere -pub type ThisOpt = Option; -pub type Params = Vec; -pub type Args = Vec; -pub type CallResult = Result; diff --git a/src/types/seq.rs b/src/types/seq.rs deleted file mode 100644 index 89163a9..0000000 --- a/src/types/seq.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Common sequence operations - -use num_bigint::BigInt; - -use crate::vm::{RuntimeErr, RuntimeObjResult, VM}; - -use super::gen::{use_arg, use_arg_str}; -use super::new; - -use super::base::ObjectRef; -use super::result::Args; - -pub fn each( - this: &ObjectRef, - items: &[ObjectRef], - args: &Args, - vm: &mut VM, -) -> RuntimeObjResult { - if items.is_empty() { - return Ok(new::nil()); - } - - let each_fn = &args[0]; - let f = each_fn.read().unwrap(); - let n_args = if let Some(f) = f.as_func() { - if f.has_var_args() { - 2 - } else { - f.arity() - } - } else { - return Ok(new::arg_err("each/1 expects a function", this.clone())); - }; - - for (i, item) in items.iter().enumerate() { - let each = each_fn.clone(); - let item = item.clone(); - if n_args == 1 { - vm.call(each, vec![item])?; - } else { - vm.call(each, vec![item, new::int(i)])?; - } - } - - Ok(new::nil()) -} - -pub fn has(items: &[ObjectRef], args: &Args) -> RuntimeObjResult { - if items.is_empty() { - return Ok(new::bool(false)); - } - let member = use_arg!(args, 0); - for item in items.iter() { - if member.is_equal(&*item.read().unwrap()) { - return Ok(new::bool(true)); - } - } - Ok(new::bool(false)) -} - -pub fn join(items: &[ObjectRef], args: &Args) -> RuntimeObjResult { - if items.is_empty() { - return Ok(new::empty_str()); - } - - let n_items = items.len(); - let last_i = n_items - 1; - let arg = use_arg!(args, 0); - let sep = use_arg_str!(join, sep, arg); - - // XXX: Guessing at average word length - let capacity = n_items * 5 + ((last_i) * sep.len()); - let mut string = String::with_capacity(capacity); - - for (i, item) in items.iter().enumerate() { - let item = item.read().unwrap(); - let str = item.to_string(); - string.push_str(&str); - if i != last_i { - string.push_str(sep); - } - } - - Ok(new::str(string)) -} - -pub fn map( - this: &ObjectRef, - items: &[ObjectRef], - args: &Args, - vm: &mut VM, -) -> RuntimeObjResult { - if items.is_empty() { - return Ok(new::empty_tuple()); - } - - let map_fn = &args[0]; - let f = map_fn.read().unwrap(); - let n_args = if let Some(f) = f.as_func() { - if f.has_var_args() { - 2 - } else { - f.arity() - } - } else { - return Ok(new::arg_err("map/1 expects a function", this.clone())); - }; - - let mut results = vec![]; - for (i, item) in items.iter().enumerate() { - let map = map_fn.clone(); - let item = item.clone(); - if n_args == 1 { - vm.call(map, vec![item])?; - } else { - vm.call(map, vec![item, new::int(i)])?; - } - results.push(vm.pop_obj()?); - } - - Ok(new::tuple(results)) -} - -pub fn sum(items: &[ObjectRef]) -> RuntimeObjResult { - let mut sum = new::int(BigInt::from(0)); - for item in items.iter() { - let a = sum.read().unwrap(); - let b = item.read().unwrap(); - let new_sum = (*a).add(&*b)?; - drop(a); - sum = new_sum; - } - Ok(sum) -} diff --git a/src/types/str.rs b/src/types/str.rs deleted file mode 100644 index f0fcc20..0000000 --- a/src/types/str.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::sync::{Arc, RwLock}; - -use once_cell::sync::Lazy; - -use crate::format::render_template; -use crate::vm::{RuntimeBoolResult, RuntimeErr, RuntimeObjResult}; - -use super::gen::{self, use_arg, use_arg_str, use_arg_usize}; -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; - -// Str Type ------------------------------------------------------------ - -gen::type_and_impls!(StrType, Str); - -pub static STR_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(StrType::new()); - let mut type_obj = type_ref.write().unwrap(); - - type_obj.add_attrs(&[ - // Class Methods ----------------------------------------------- - gen::meth!("new", type_ref, &["value"], "", |_, args, _| { - let arg = use_arg!(args, 0); - Ok(if arg.is_str() { args[0].clone() } else { new::str(arg.to_string()) }) - }), - // Instance Attributes ----------------------------------------- - gen::prop!("length", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let value = this.get_str_val().unwrap(); - Ok(new::int(value.len())) - }), - // Instance Methods -------------------------------------------- - gen::meth!("starts_with", type_ref, &["prefix"], "", |this, args, _| { - let this = this.read().unwrap(); - let value = this.get_str_val().unwrap(); - let arg = use_arg!(args, 0); - let prefix = use_arg_str!(starts_with, prefix, arg); - Ok(new::bool(value.starts_with(prefix))) - }), - gen::meth!("ends_with", type_ref, &["suffix"], "", |this, args, _| { - let this = this.read().unwrap(); - let value = this.get_str_val().unwrap(); - let arg = use_arg!(args, 0); - let suffix = use_arg_str!(ends_with, suffix, arg); - Ok(new::bool(value.ends_with(suffix))) - }), - gen::meth!("upper", type_ref, &[], "", |this, _, _| { - let this = this.read().unwrap(); - let value = this.get_str_val().unwrap(); - Ok(new::str(value.to_uppercase())) - }), - gen::meth!("lower", type_ref, &[], "", |this, _, _| { - let this = this.read().unwrap(); - let value = this.get_str_val().unwrap(); - Ok(new::str(value.to_lowercase())) - }), - gen::meth!( - "render", - type_ref, - &["context"], - "Render string as template - - Templates may contain `{{ name }}` vars which will be replaced with the - values provided in the context map. - - # Args - - - context: Map A map containing values to be rendered into the - template. - - ", - |this, args, _| { - let context = args[0].clone(); - let result = render_template(this.clone(), context)?; - Ok(result) - } - ), - gen::meth!("repeat", type_ref, &["count"], "", |this, args, _| { - let this = this.read().unwrap(); - let value = this.get_str_val().unwrap(); - let count = use_arg_usize!(get, index, args, 0); - Ok(new::str(value.repeat(count))) - }), - gen::meth!("replace", type_ref, &["old", "new"], "", |this, args, _| { - let this = this.read().unwrap(); - let value = this.get_str_val().unwrap(); - let arg1 = use_arg!(args, 0); - let arg2 = use_arg!(args, 1); - let old = use_arg_str!(replace, old, arg1); - let new = use_arg_str!(replace, new, arg2); - let result = value.replace(old, new); - Ok(new::str(result)) - }), - gen::meth!("remove_prefix", type_ref, &["prefix"], "", |this_ref, args, _| { - let this = this_ref.read().unwrap(); - let val = this.get_str_val().unwrap(); - let arg = use_arg!(args, 0); - let prefix = use_arg_str!(starts_with, prefix, arg); - Ok(if let Some(new_val) = val.strip_prefix(prefix) { - new::str(new_val) - } else { - drop(this); - this_ref - }) - }), - ]); - - type_ref.clone() -}); - -// Str Object ---------------------------------------------------------- - -pub struct Str { - ns: Namespace, - value: String, -} - -gen::standard_object_impls!(Str); - -impl Str { - pub fn new(value: String) -> Self { - Self { - ns: Namespace::with_entries(&[ - // Instance Attributes - ("len", new::int(value.len())), - ]), - value, - } - } - - pub fn value(&self) -> &str { - self.value.as_str() - } -} - -impl ObjectTrait for Str { - gen::object_trait_header!(STR_TYPE); - - fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { - if self.is(rhs) || rhs.is_always() { - true - } else if let Some(rhs) = rhs.down_to_str() { - self.is(rhs) || self.value() == rhs.value() - } else { - false - } - } - - fn add(&self, rhs: &dyn ObjectTrait) -> RuntimeObjResult { - if let Some(rhs) = rhs.down_to_str() { - let a = self.value(); - let b = rhs.value(); - let mut value = String::with_capacity(a.len() + b.len()); - value.push_str(a); - value.push_str(b); - let value = new::str(value); - Ok(value) - } else { - Err(RuntimeErr::type_err(format!( - "Cannot concatenate {} to {}", - self.class().read().unwrap(), - rhs.class().read().unwrap(), - ))) - } - } - - fn less_than(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - if let Some(rhs) = rhs.down_to_str() { - Ok(self.value() < rhs.value()) - } else { - Err(RuntimeErr::type_err(format!( - "Cannot compare {} to {}: <", - self.class().read().unwrap(), - rhs.class().read().unwrap(), - ))) - } - } - - fn greater_than(&self, rhs: &dyn ObjectTrait) -> RuntimeBoolResult { - if let Some(rhs) = rhs.down_to_str() { - Ok(self.value() > rhs.value()) - } else { - Err(RuntimeErr::type_err(format!( - "Cannot compare {} to {}: >", - self.class().read().unwrap(), - rhs.class().read().unwrap(), - ))) - } - } -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for Str { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.value) - } -} - -impl fmt::Debug for Str { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "\"{}\"", self.value) - } -} diff --git a/src/types/tuple.rs b/src/types/tuple.rs deleted file mode 100644 index 844db46..0000000 --- a/src/types/tuple.rs +++ /dev/null @@ -1,180 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::slice::Iter; -use std::sync::{Arc, RwLock}; - -use once_cell::sync::Lazy; - -use crate::vm::RuntimeErr; - -use super::gen; -use super::new; - -use super::base::{ObjectRef, ObjectTrait, TypeRef, TypeTrait}; -use super::class::TYPE_TYPE; -use super::ns::Namespace; -use super::seq; - -// Tuple Type ---------------------------------------------------------- - -gen::type_and_impls!(TupleType, Tuple); - -pub static TUPLE_TYPE: Lazy = Lazy::new(|| { - let type_ref = gen::obj_ref!(TupleType::new()); - let mut type_obj = type_ref.write().unwrap(); - - type_obj.add_attrs(&[ - // Instance Attributes ----------------------------------------- - gen::prop!("length", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_tuple().unwrap(); - Ok(new::int(this.len())) - }), - gen::prop!("is_empty", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_tuple().unwrap(); - Ok(new::bool(this.len() == 0)) - }), - gen::prop!("sum", type_ref, "", |this, _, _| { - let this = this.read().unwrap(); - let this = this.down_to_tuple().unwrap(); - seq::sum(&this.items) - }), - // Instance Methods -------------------------------------------- - gen::meth!( - "each", - type_ref, - &["each_fn"], - "Apply function to each Tuple item. - - # Args - - - func: Func - - A function that will be passed each item in turn and, optionally, the - index of the item. - - ", - |this_obj, args, vm| { - let this = this_obj.read().unwrap(); - let this = this.down_to_tuple().unwrap(); - seq::each(&this_obj, &this.items, &args, vm) - } - ), - gen::meth!("get", type_ref, &["index"], "", |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_tuple().unwrap(); - let index = gen::use_arg_usize!(get, index, args, 0); - let result = match this.get(index) { - Some(obj) => obj, - None => new::nil(), - }; - Ok(result) - }), - gen::meth!("has", type_ref, &["member"], "", |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_tuple().unwrap(); - seq::has(&this.items, &args) - }), - gen::meth!("iter", type_ref, &[], "", |this_ref, _, _| { - let this = this_ref.read().unwrap(); - let this = this.down_to_tuple().unwrap(); - Ok(new::iterator(this.items.clone())) - }), - gen::meth!("join", type_ref, &["sep"], "", |this, args, _| { - let this = this.read().unwrap(); - let this = this.down_to_tuple().unwrap(); - seq::join(&this.items, &args) - }), - gen::meth!("map", type_ref, &["map_fn"], "", |this_obj, args, vm| { - let this = this_obj.read().unwrap(); - let this = this.down_to_tuple().unwrap(); - seq::map(&this_obj, &this.items, &args, vm) - }), - ]); - - type_ref.clone() -}); - -// Tuple Object -------------------------------------------------------- - -pub struct Tuple { - ns: Namespace, - items: Vec, -} - -gen::standard_object_impls!(Tuple); - -impl Tuple { - pub fn new(items: Vec) -> Self { - Self { ns: Namespace::default(), items } - } - - pub fn iter(&self) -> Iter<'_, ObjectRef> { - self.items.iter() - } - - pub fn len(&self) -> usize { - self.items.len() - } - - pub fn get(&self, index: usize) -> Option { - if let Some(item) = self.items.get(index) { - Some(item.clone()) - } else { - None - } - } -} - -impl ObjectTrait for Tuple { - gen::object_trait_header!(TUPLE_TYPE); - - fn get_item(&self, index: usize, this: ObjectRef) -> ObjectRef { - if let Some(item) = self.items.get(index) { - item.clone() - } else { - self.index_out_of_bounds(index, this) - } - } - - fn is_equal(&self, rhs: &dyn ObjectTrait) -> bool { - if self.is(rhs) || rhs.is_always() { - return true; - } - if let Some(rhs) = rhs.down_to_tuple() { - if self.len() != rhs.len() { - return false; - } - for (a, b) in self.iter().zip(rhs.iter()) { - let a = a.read().unwrap(); - let b = b.read().unwrap(); - if !a.is_equal(&*b) { - return false; - } - } - true - } else { - false - } - } -} - -// Display ------------------------------------------------------------- - -impl fmt::Display for Tuple { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let num_items = self.len(); - let items: Vec = - self.iter().map(|item| format!("{:?}", &*item.read().unwrap())).collect(); - let items_str = items.join(", "); - let trailing_comma = if num_items == 1 { "," } else { "" }; - write!(f, "({}{})", items_str, trailing_comma) - } -} - -impl fmt::Debug for Tuple { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} diff --git a/src/types/util.rs b/src/types/util.rs deleted file mode 100644 index b74a06e..0000000 --- a/src/types/util.rs +++ /dev/null @@ -1,29 +0,0 @@ -use num_bigint::BigInt; -use num_traits::{FromPrimitive, ToPrimitive}; - -use super::float::Float; -use super::int::Int; - -/// Compare Int and Float for equality. -pub fn eq_int_float(int: &Int, float: &Float) -> bool { - let float_val = float.value(); - if float_val.fract() == 0.0 { - let int_val = int.value(); - let float_as_int = BigInt::from_f64(*float_val).unwrap(); - *int_val == float_as_int - } else { - false - } -} - -/// Compare Int and Float for less than. -pub fn lt_int_float(int: &Int, float: &Float) -> bool { - let int_as_float = int.value().to_f64().unwrap(); - int_as_float < *float.value() -} - -/// Compare Int and Float for greater than. -pub fn gt_int_float(int: &Int, float: &Float) -> bool { - let int_as_float = int.value().to_f64().unwrap(); - int_as_float > *float.value() -} diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index 4163d95..0000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub(crate) use call::check_args; -pub(crate) use stack::Stack; -pub(crate) use string::format_doc; - -mod call; -mod stack; -mod string; diff --git a/src/vm/code.rs b/src/vm/code.rs deleted file mode 100644 index 764adb3..0000000 --- a/src/vm/code.rs +++ /dev/null @@ -1,224 +0,0 @@ -use std::ops::Index; -use std::slice::Iter; - -use crate::source::Location; -use crate::types::{new, FuncTrait, ObjectRef}; -use crate::util::format_doc; - -use super::inst::Inst; -use super::result::RuntimeErr; - -type FreeVarEntry = ( - usize, // address - String, // name - Location, // source start - Location, // source end -); - -/// Code for a module or function. -#[derive(Debug)] -pub struct Code { - chunk: Vec, - constants: Vec, - // Vars defined outside of this unit of code. - free_vars: Vec, -} - -impl Default for Code { - fn default() -> Self { - Code::new(vec![], vec![], vec![]) - } -} - -impl Index for Code { - type Output = Inst; - - fn index(&self, index: usize) -> &Self::Output { - &self.chunk[index] - } -} - -impl PartialEq for Code { - fn eq(&self, other: &Self) -> bool { - if self.chunk != other.chunk { - return false; - } - if self.constants.len() != other.constants.len() { - return false; - } - if self.free_vars != other.free_vars { - return false; - } - for (c, d) in self.constants.iter().zip(other.constants.iter()) { - let c = c.read().unwrap(); - let d = d.read().unwrap(); - if !c.is_equal(&*d) { - return false; - } - } - true - } -} - -impl Code { - pub fn new( - chunk: Vec, - constants: Vec, - free_vars: Vec, - ) -> Self { - Self { chunk, constants, free_vars } - } - - /// Initialize code object with a list of instructions, also known - /// as a chunk. - pub fn with_chunk(chunk: Vec) -> Self { - Self::new(chunk, vec![], vec![]) - } - - /// Extend this `Code` object with another `Code` object: - /// - /// - Extend instructions, adjusting constant indexes - /// - Extend constants - /// - Free vars are ignored for now since this is mainly intended - /// for extending modules (where there are no free vars) and not - /// functions - /// - /// IMPORTANT: ALL instructions that hold a const index MUST be - /// updated here. - pub fn extend(&mut self, mut code: Self) { - use Inst::LoadConst; - let mut replacements = vec![]; - let const_offset = self.constants.len(); - for (addr, inst) in code.iter_chunk().enumerate() { - if let LoadConst(index) = inst { - replacements.push((addr, LoadConst(const_offset + index))); - } - } - for (addr, inst) in replacements { - code.replace_inst(addr, inst); - } - self.chunk.extend(code.chunk); - self.constants.extend(code.constants); - } - - /// Get docstring for code unit, if there is one. - pub fn get_doc(&self) -> ObjectRef { - if let Some(Inst::LoadConst(0)) = self.chunk.get(1) { - if let Ok(obj_ref) = self.get_const(0) { - let obj = obj_ref.read().unwrap(); - if let Some(doc) = obj.get_str_val() { - return new::str(format_doc(doc)); - } - } - } - new::nil() - } - - // Instructions ---------------------------------------------------- - - pub fn len_chunk(&self) -> usize { - self.chunk.len() - } - - pub fn iter_chunk(&self) -> Iter<'_, Inst> { - self.chunk.iter() - } - - pub fn push_inst(&mut self, inst: Inst) { - self.chunk.push(inst) - } - - pub fn pop_inst(&mut self) -> Option { - self.chunk.pop() - } - - pub fn insert_inst(&mut self, index: usize, inst: Inst) { - self.chunk.insert(index, inst); - } - - pub fn replace_inst(&mut self, index: usize, inst: Inst) { - self.chunk[index] = inst; - } - - /// Explicit return statements need to jump to the end of the - /// function so that the function can be cleanly exited. - pub fn fix_up_explicit_returns(&mut self) { - let return_addr = self.len_chunk(); - for addr in 0..return_addr { - let inst = &self.chunk[addr]; - if let Inst::ReturnPlaceholder(inst_addr, depth) = inst { - let rel_addr = return_addr - inst_addr; - self.replace_inst(*inst_addr, Inst::Jump(rel_addr, true, depth - 1)); - } - } - } - - // Constants ------------------------------------------------------- - - pub fn add_const(&mut self, val_ref: ObjectRef) -> usize { - let val_guard = val_ref.read().unwrap(); - let val = &*val_guard; - - // XXX: Functions are immutable and comparable, but it feels - // potentially unsafe to treat them as such here. - let is_comparable = val.is_immutable() && !val.is_func(); - - for (index, other_ref) in self.iter_constants().enumerate() { - let other = other_ref.read().unwrap(); - let other_is_comparable = other.is_immutable() && !other.is_func(); - if is_comparable && other_is_comparable && other.is_equal(val) { - return index; - } - } - - let index = self.constants.len(); - drop(val_guard); - self.constants.push(val_ref); - index - } - - pub fn get_const(&self, index: usize) -> Result<&ObjectRef, RuntimeErr> { - if let Some(obj) = self.constants.get(index) { - Ok(obj) - } else { - Err(RuntimeErr::constant_not_found(index)) - } - } - - pub fn iter_constants(&self) -> Iter<'_, ObjectRef> { - self.constants.iter() - } - - pub fn get_main(&self) -> Option { - let maybe_index = self.constants.iter().position(|obj_ref| { - let obj = obj_ref.read().unwrap(); - if let Some(func) = obj.down_to_func() { - func.name() == "$main" - } else { - false - } - }); - maybe_index.map(|index| self.constants[index].clone()) - } - - // Vars ------------------------------------------------------------ - - pub fn free_vars(&self) -> &Vec { - &self.free_vars - } - - /// Add a free var, a reference to a var defined in an enclosing - /// scope. This also adds a placeholder instruction for the free - /// var that will replaced in the compiler's name resolution stage. - pub fn add_free_var>( - &mut self, - name: S, - start: Location, - end: Location, - ) { - let addr = self.len_chunk(); - let name = name.into(); - self.free_vars.push((addr, name.clone(), start, end)); - self.push_inst(Inst::FreeVarPlaceholder(addr, name)); - } -} diff --git a/src/vm/globals.rs b/src/vm/globals.rs deleted file mode 100644 index 403c13d..0000000 --- a/src/vm/globals.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! This module defines global constants that are shared across all -//! code units. These can (and should) be loaded by index in the VM -//! using dedicated instructions (`LOAD_NIL`, `LOAD_GLOBAL_CONST`, etc). - -use std::sync::{Arc, RwLock}; - -use num_bigint::BigInt; -use num_traits::{Signed, ToPrimitive, Zero}; - -use once_cell::sync::Lazy; - -use crate::types::gen::{obj_ref, obj_ref_t}; -use crate::types::ObjectRef; - -use crate::types::always::Always; -use crate::types::bool::Bool; -use crate::types::int::Int; -use crate::types::nil::Nil; -use crate::types::str::Str; -use crate::types::tuple::Tuple; - -pub static NIL: Lazy = Lazy::new(|| obj_ref!(Nil::new())); -pub static TRUE: Lazy = Lazy::new(|| obj_ref!(Bool::new(true))); -pub static FALSE: Lazy = Lazy::new(|| obj_ref!(Bool::new(false))); -pub static ALWAYS: Lazy = Lazy::new(|| obj_ref!(Always::new())); - -pub static EMPTY_STR: Lazy = - Lazy::new(|| obj_ref!(Str::new("".to_owned()))); - -pub static NEWLINE: Lazy = - Lazy::new(|| obj_ref!(Str::new("\n".to_owned()))); - -pub static EMPTY_TUPLE: Lazy = - Lazy::new(|| obj_ref!(Tuple::new(vec![]))); - -pub static SHARED_INT_MAX: usize = 256; -pub static SHARED_INT_MAX_BIGINT: Lazy = - Lazy::new(|| BigInt::from(SHARED_INT_MAX)); -pub static SHARED_INTS: Lazy> = Lazy::new(|| { - (0..=SHARED_INT_MAX).map(|i| obj_ref!(Int::new(BigInt::from(i)))).collect() -}); - -pub const NIL_INDEX: usize = 0; -pub const TRUE_INDEX: usize = 1; -pub const FALSE_INDEX: usize = 2; -pub const ALWAYS_INDEX: usize = 3; -pub const EMPTY_STR_INDEX: usize = 4; -pub const NEWLINE_INDEX: usize = 5; -pub const EMPTY_TUPLE_INDEX: usize = 6; -pub const SHARED_INT_INDEX: usize = 7; - -/// Get the global constants. -/// -/// NOTE: This is only intended to be called _once_. -pub fn get_global_constants() -> Vec { - let mut global_constants: Vec = vec![ - NIL.clone(), - TRUE.clone(), - FALSE.clone(), - ALWAYS.clone(), - EMPTY_STR.clone(), - NEWLINE.clone(), - EMPTY_TUPLE.clone(), - ]; - for int in SHARED_INTS.iter() { - global_constants.push(int.clone()); - } - global_constants -} - -/// Get the global constant index for the `int` if it's in the shared -/// int range. -pub fn shared_int_index(int: &BigInt) -> Option { - if int.is_zero() { - Some(SHARED_INT_INDEX) - } else if int.is_positive() && int <= Lazy::force(&SHARED_INT_MAX_BIGINT) { - Some(int.to_usize().unwrap() + SHARED_INT_INDEX) - } else { - None - } -} - -/// Get the global constant at `index`. -/// -/// NOTE: This is only intended for use in testing. -pub(crate) fn get_global_constant(index: usize) -> Option { - let global_constants = get_global_constants(); - global_constants.get(index).cloned() -} diff --git a/src/vm/inst.rs b/src/vm/inst.rs deleted file mode 100644 index 7dac9c6..0000000 --- a/src/vm/inst.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::op::{BinaryOperator, CompareOperator, InplaceOperator, UnaryOperator}; -use crate::source::Location; - -/// NOTE: When adding or removing instructions, the PartialEq impl -/// below must also be updated. -#[derive(Debug)] -pub enum Inst { - NoOp, - - // Pop TOS and discard it. - Pop, - - // Global constants are shared globally by all code units. - LoadGlobalConst(usize), - - // Special global constants with a known index. - LoadNil, // 0 - LoadTrue, // 1 - LoadFalse, // 2 - LoadAlways, // 3 - LoadEmptyStr, // 4 - LoadNewline, // 5 - LoadEmptyTuple, // 6 - - ScopeStart, - ScopeEnd, - - StatementStart(Location, Location), - - // Other constants are local to a given code unit. - LoadConst(usize), - - DeclareVar(String), - AssignVar(String), - - // Args: name, offset - // - // `offset` is the number of scopes above the current scope to start - // the search. 0 means the current scope, 1 means the parent scope, - // and so on. - LoadVar(String, usize), - - // Load module global - LoadGlobal(String), - - // Load builtin - LoadBuiltin(String), - - // These are analogous to AssignVar and LoadVar. Assignment wraps - // the value in a cell so that it can be shared. Loading unwraps the - // value. - AssignCell(String), - LoadCell(String), - - // Load captured value to TOS (a special case of LoadCell). - LoadCaptured(String), - - // Jumps ----------------------------------------------------------- - // - // For all jump instructions, the first arg is the target address - // relative to the jump address. The second arg is a flag to - // indicate a forward or reverse jump. The third arg is the scope - // exit count. - // - // Relative addresses allow instructions to be inserted BEFORE any - // forward jumps or AFTER any backward jumps within a code segment. - // Mainly this is to allow instructions to be inserted at the - // beginning of functions. - - // Jump unconditionally. - Jump(usize, bool, usize), - - // Jump unconditionally and push nil onto stack. - JumpPushNil(usize, bool, usize), - - // If top of stack is true, jump to address. Otherwise, continue. - JumpIf(usize, bool, usize), - - // If top of stack is false, jump to address. Otherwise, continue. - JumpIfNot(usize, bool, usize), - - // If top of stack is NOT nil, jump to address. Otherwise, continue. - JumpIfNotNil(usize, bool, usize), - - UnaryOp(UnaryOperator), - BinaryOp(BinaryOperator), - CompareOp(CompareOperator), - InplaceOp(InplaceOperator), - - // Call function with N values from top of stack. The args are - // ordered such that the 1st arg is at TOS and other args are below - // it. - Call(usize), - - // RETURN is a jump target at the end of a function. Its only - // purpose is to serve as a jump target for explicit returns. - Return, - - // These make compound objects from the top N items on the stack. - MakeString(usize), - MakeTuple(usize), - MakeList(usize), - MakeMap(usize), - - // Capture set for function--a list of names for the function to - // capture. If empty, a regular function will be created. - CaptureSet(Vec), - - // Make function or closure depending on capture set. MAKE_FUNC - // expects the following entries at TOS: - // - // TOS capture_set: Map (added by CAPTURE_SET) - // func: Func (added by LOAD_CONST) - MakeFunc, - - LoadModule(String), - - Halt(u8), - HaltTop, - - // Placeholders ---------------------------------------------------- - // - // Placeholders are inserted during compilation and later updated. - // All placeholders must be replaced or a runtime error will be - // thrown. - Placeholder(usize, Box, String), // address, instruction, error message - FreeVarPlaceholder(usize, String), // address, var name - BreakPlaceholder(usize, usize), // jump address, scope depth - ContinuePlaceholder(usize, usize), // jump address, scope depth - - // NOTE: This is used for explicit return statements. It will be - // replaced with a jump to a RETURN target. - ReturnPlaceholder(usize, usize), // jump address, scope depth - - // Miscellaneous --------------------------------------------------- - - // Pop TOS and print it to stdout or stderr. Behavior is controlled - // by passing in flags. Pass `PrintFlags::default()` for the default - // behavior, which is to print to stdout with no newline. - Print(PrintFlags), - - DisplayStack(String), -} - -bitflags! { - #[derive(Default)] - pub struct PrintFlags: u32 { - const ERR = 0b00000001; // print to stderr - const NL = 0b00000010; // print a trailing newline. - const REPR = 0b00000100; // print repr using fmt::Debug - const NO_NIL = 0b00001000; // don't print obj if it's nil - } -} - -impl PartialEq for Inst { - fn eq(&self, other: &Self) -> bool { - use Inst::*; - - match (self, other) { - (NoOp, NoOp) => true, - (Pop, Pop) => true, - (LoadGlobalConst(a), LoadGlobalConst(b)) => a == b, - (LoadNil, LoadNil) => true, - (LoadTrue, LoadTrue) => true, - (LoadFalse, LoadFalse) => true, - (LoadAlways, LoadAlways) => true, - (LoadEmptyStr, LoadEmptyStr) => true, - (LoadEmptyTuple, LoadEmptyTuple) => true, - (ScopeStart, ScopeStart) => true, - (ScopeEnd, ScopeEnd) => true, - (StatementStart(..), StatementStart(..)) => true, - (LoadConst(a), LoadConst(b)) => a == b, - (DeclareVar(a), DeclareVar(b)) => a == b, - (AssignVar(a), AssignVar(b)) => a == b, - (LoadVar(a, i), LoadVar(b, j)) => (a, i) == (b, j), - (AssignCell(a), AssignCell(b)) => a == b, - (LoadCell(a), LoadCell(b)) => a == b, - (LoadCaptured(a), LoadCaptured(b)) => a == b, - (Jump(a, b, c), Jump(d, e, f)) => (a, b, c) == (d, e, f), - (JumpPushNil(a, b, c), JumpPushNil(d, e, f)) => (a, b, c) == (d, e, f), - (JumpIfNot(a, b, c), JumpIfNot(d, e, f)) => (a, b, c) == (d, e, f), - (UnaryOp(a), UnaryOp(b)) => a == b, - (BinaryOp(a), BinaryOp(b)) => a == b, - (CompareOp(a), CompareOp(b)) => a == b, - (InplaceOp(a), InplaceOp(b)) => a == b, - (Call(a), Call(b)) => a == b, - (Return, Return) => true, - (MakeString(a), MakeString(b)) => a == b, - (MakeTuple(a), MakeTuple(b)) => a == b, - (MakeList(a), MakeList(b)) => a == b, - (MakeMap(a), MakeMap(b)) => a == b, - (CaptureSet(a), CaptureSet(b)) => a == b, - (MakeFunc, MakeFunc) => true, - (LoadModule(a), LoadModule(b)) => a == b, - (Halt(a), Halt(b)) => a == b, - (HaltTop, HaltTop) => true, - (Print(a), Print(b)) => a == b, - _ => false, - } - } -} diff --git a/src/vm/mod.rs b/src/vm/mod.rs deleted file mode 100644 index 52f5e99..0000000 --- a/src/vm/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -pub use result::VMState; -pub use result::{CallDepth, RuntimeErr}; -pub use vm::{DEFAULT_MAX_CALL_DEPTH, VM}; - -pub(crate) use code::Code; -pub(crate) use context::ModuleExecutionContext; -pub(crate) use inst::Inst; -pub(crate) use inst::PrintFlags; -pub(crate) use result::{ - RuntimeBoolResult, RuntimeErrKind, RuntimeObjResult, RuntimeResult, VMExeResult, -}; - -pub(crate) mod globals; - -mod code; -mod context; -mod inst; -mod result; -mod vm;