diff --git a/Cargo.lock b/Cargo.lock index cc78c6d..0c8e6aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.3" @@ -137,16 +126,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chumsky" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d" -dependencies = [ - "hashbrown 0.12.3", - "stacker", -] - [[package]] name = "clap" version = "4.3.4" @@ -204,12 +183,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "cpufeatures" version = "0.2.7" @@ -238,19 +211,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", -] - [[package]] name = "digest" version = "0.10.7" @@ -261,16 +221,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "duplicate" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" -dependencies = [ - "heck", - "proc-macro-error", -] - [[package]] name = "dyn-clone" version = "1.0.11" @@ -320,17 +270,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "globset" version = "0.4.10" @@ -344,22 +283,13 @@ dependencies = [ "regex", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.6", -] - [[package]] name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ - "ahash 0.8.3", + "ahash", "allocator-api2", ] @@ -451,36 +381,32 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "orchidlang" version = "0.2.2" dependencies = [ - "chumsky", "clap", - "derive_more", - "duplicate", "dyn-clone", - "hashbrown 0.14.0", + "hashbrown", "itertools", "ordered-float", "paste", "polling", "rust-embed", "take_mut", - "thiserror", "trait-set", "unicode-segmentation", ] [[package]] name = "ordered-float" -version = "3.7.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213" +checksum = "e3a540f3e3b3d7929c884e46d093d344e4e5bdeed54d08bf007df50c93cc85d5" dependencies = [ "num-traits", ] @@ -511,30 +437,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.56" @@ -544,15 +446,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - [[package]] name = "quote" version = "1.0.26" @@ -614,15 +507,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.37.19" @@ -659,12 +543,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "semver" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" - [[package]] name = "serde" version = "1.0.160" @@ -682,19 +560,6 @@ dependencies = [ "digest", ] -[[package]] -name = "stacker" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "winapi", -] - [[package]] name = "strsim" version = "0.10.0" @@ -729,26 +594,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" -[[package]] -name = "thiserror" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", -] - [[package]] name = "tracing" version = "0.1.37" @@ -817,12 +662,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 3313db4..ad36a76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,18 +22,14 @@ doc = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -thiserror = "1.0" -chumsky = "0.9" hashbrown = "0.14" -ordered-float = "3.7" +ordered-float = "4.1" itertools = "0.11" dyn-clone = "1.0" clap = { version = "4.3", features = ["derive"] } trait-set = "0.3" paste = "1.0" rust-embed = { version = "8.0", features = ["include-exclude"] } -duplicate = "1.0.0" take_mut = "0.2.2" unicode-segmentation = "1.10.1" polling = "3.0.0" -derive_more = "0.99.17" diff --git a/README.md b/README.md index 16ebade..e50cfec 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,7 @@ An experimental lazy, pure functional programming language designed to be embedd ## Usage -TODO - -I need to write a few articles explaining individual fragments of the language, and accurately document everything. Writing tutorials at this stage is not really worth it. +The standalone interpreter can be built as the binary target from this package. The language tutorial and standard library documentation is at [www.lbfalvy.com/orchid-reference](https://www.lbfalvy.com/orchid-reference/). Embedder guide and Rust API documentation are coming soon. ## Design @@ -35,4 +33,4 @@ Orchids and mangrove trees form complex ecosystems; The flowers persuade the tre ## Contribution -All contributions are welcome. For the time being, use the issue tracker to discuss ideas. \ No newline at end of file +All contributions are welcome. For the time being, use the issue tracker to discuss ideas. diff --git a/ROADMAP.md b/ROADMAP.md index 5e1f585..a589198 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,12 +2,6 @@ This document is a wishlist, its items aren't ordered in any way other than inli # Language -## Operator declarations -A dedicated (exportable) line type for declaring operators. Still just names, only you can write them next to other things without whitespace - -- ops may not contain c-ident-safe characters -- clusters of operator characters are broken up with a greedy algorithm - ## Typeclasses Elixir-style protocols probably, only with n-ary dispatch which I saw in SICP-js @@ -37,11 +31,6 @@ Error tokens with rules to lift them out. Kinda depends on preservation of locat ## Async Join allows to run code when a tuple of pending events all resolve on the event poller -## New: FS -Exposes tree operations to Orchid -Uses existing IO to open and read files -Uses the event bus to read directories in batches without blocking other Orchid code - ## New: Network Event-driven I/O with single-fire events and resubscription to relay backpressure to the OS. Initially TCP diff --git a/examples/calculator/main.orc b/examples/calculator/main.orc index 541c7f1..e3f983e 100644 --- a/examples/calculator/main.orc +++ b/examples/calculator/main.orc @@ -1,4 +1,4 @@ -import std::(proc::*, to_float, to_string, panic, str::char_at) +import std::(to_float, to_string, panic, string::char_at) export const main := do{ cps print "left operand: "; @@ -6,7 +6,6 @@ export const main := do{ let a = to_float data; cps print "operator: "; cps op = readln; - let op = char_at op 0; cps println ("you selected \"" ++ op ++ "\""); cps print "right operand: "; cps data = readln; diff --git a/examples/file-browser/main.orc b/examples/file-browser/main.orc index de32968..b8c5283 100644 --- a/examples/file-browser/main.orc +++ b/examples/file-browser/main.orc @@ -1,18 +1,17 @@ import system::(io, directfs, async) -import std::proc::* import std::(to_string, to_uint, inspect) -const folder_view := \path.\next. do{ +const folder_view := \path. \next. do{ cps println $ "Contents of " ++ directfs::os_print path; cps entries = async::block_on $ directfs::read_dir path; cps list::enumerate entries - |> list::map (pass \id. pass \name.\is_dir. + |> list::map (pass \id. pass \name. \is_dir. println $ to_string id ++ ": " ++ directfs::os_print name ++ if is_dir then "/" else "" ) |> list::chain; cps print "select an entry, or .. to move up: "; cps choice = readln; - if (choice == "..\n") then do { + if (choice == "..") then do { let parent_path = directfs::pop_path path |> option::unwrap |> tuple::pick 0 2; diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index 3abdd18..50d1518 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -1,5 +1,7 @@ +import std::exit_status + const main := ( println "Hello, world!" - "success" + exit_status::success ) -- main := "Hello, World!\n" diff --git a/examples/list-processing/main.orc b/examples/list-processing/main.orc index 63c1bdd..bdaf7aa 100644 --- a/examples/list-processing/main.orc +++ b/examples/list-processing/main.orc @@ -1,4 +1,4 @@ -import std::(proc::*, to_string) +import std::to_string export const main := do{ let foo = list::new[1, 2, 3, 4, 5, 6]; @@ -6,7 +6,7 @@ export const main := do{ let sum = bar |> list::skip 2 |> list::take 3 - |> list::reduce (\a.\b. a + b) + |> list::reduce (\a. \b. a + b) |> option::unwrap; cps println $ to_string sum; 0 diff --git a/examples/maps/main.orc b/examples/maps/main.orc index e63be17..3f8ab35 100644 --- a/examples/maps/main.orc +++ b/examples/maps/main.orc @@ -1,4 +1,4 @@ -import std::(proc::*, to_string) +import std::to_string export const main := do{ let foo = map::new[ diff --git a/src/bin/orcx.rs b/src/bin/orcx.rs index ad71eb3..358f348 100644 --- a/src/bin/orcx.rs +++ b/src/bin/orcx.rs @@ -3,13 +3,13 @@ mod cli; use std::fs::File; use std::io::BufReader; use std::path::PathBuf; -use std::process; +use std::process::ExitCode; use clap::Parser; use itertools::Itertools; use orchidlang::facade::{Environment, PreMacro}; use orchidlang::systems::asynch::AsynchSystem; -use orchidlang::systems::stl::StlConfig; +use orchidlang::systems::stl::{ExitStatus, StlConfig}; use orchidlang::systems::{directfs, io, scheduler}; use orchidlang::{ast, interpreted, interpreter, Interner, Sym, VName}; @@ -80,7 +80,7 @@ fn print_for_debug(e: &ast::Expr) { } /// A little utility to step through the resolution of a macro set -pub fn macro_debug(premacro: PreMacro, sym: Sym) { +pub fn macro_debug(premacro: PreMacro, sym: Sym) -> ExitCode { let (mut code, location) = (premacro.consts.get(&sym)) .unwrap_or_else(|| { panic!( @@ -111,7 +111,7 @@ pub fn macro_debug(premacro: PreMacro, sym: Sym) { }, "p" | "print" => print_for_debug(&code), "d" | "dump" => print!("Rules: {}", premacro.repo), - "q" | "quit" => return, + "q" | "quit" => return ExitCode::SUCCESS, "h" | "help" => print!( "Available commands: \t, n, next\t\ttake a step @@ -128,7 +128,7 @@ pub fn macro_debug(premacro: PreMacro, sym: Sym) { } } -pub fn main() { +pub fn main() -> ExitCode { let args = Args::parse(); args.chk_proj().unwrap_or_else(|e| panic!("{e}")); let dir = PathBuf::try_from(args.dir).unwrap(); @@ -150,7 +150,7 @@ pub fn main() { let premacro = env.load_dir(&dir, &main).unwrap(); if args.dump_repo { println!("Parsed rules: {}", premacro.repo); - return; + return ExitCode::SUCCESS; } if !args.macro_debug.is_empty() { let sym = i.i(&to_vname(&args.macro_debug, &i)); @@ -160,15 +160,15 @@ pub fn main() { proc.validate_refs().unwrap(); let main = interpreted::Clause::Constant(i.i(&main)).wrap(); let ret = proc.run(main, None).unwrap(); - let interpreter::Return { gas, state, inert } = ret; + let interpreter::Return { state, inert, .. } = ret; drop(proc); - if inert { - println!("Settled at {}", state.expr().clause); - if let Some(g) = gas { - println!("Remaining gas: {g}") - } - } else if gas == Some(0) { - eprintln!("Ran out of gas!"); - process::exit(-1); + assert!(inert, "Gas is not used, only inert data should be yielded"); + match state.clone().downcast::() { + Ok(ExitStatus::Success) => ExitCode::SUCCESS, + Ok(ExitStatus::Failure) => ExitCode::FAILURE, + Err(_) => { + println!("{}", state.expr().clause); + ExitCode::SUCCESS + }, } } diff --git a/src/systems/assertion_error.rs b/src/error/assertion_error.rs similarity index 90% rename from src/systems/assertion_error.rs rename to src/error/assertion_error.rs index 7a50ef0..0fcc98e 100644 --- a/src/systems/assertion_error.rs +++ b/src/error/assertion_error.rs @@ -19,12 +19,12 @@ impl AssertionError { location: Location, message: &'static str, ) -> Result> { - return Err(Self::ext(location, message)); + Err(Self::ext(location, message)) } /// Construct and upcast to [ExternError] pub fn ext(location: Location, message: &'static str) -> Rc { - return Self { location, message }.into_extern(); + Self { location, message }.into_extern() } } diff --git a/src/error/import_all.rs b/src/error/import_all.rs index 7f4b59c..3730860 100644 --- a/src/error/import_all.rs +++ b/src/error/import_all.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::sync::Arc; use itertools::Itertools; @@ -10,9 +10,9 @@ use crate::VName; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ImportAll { /// The file containing the offending import - pub offender_file: Rc, + pub offender_file: Arc, /// The module containing the offending import - pub offender_mod: Rc, + pub offender_mod: Arc, } impl ProjectError for ImportAll { fn description(&self) -> &str { "a top-level glob import was used" } diff --git a/src/error/mod.rs b/src/error/mod.rs index f98c3b5..07d472b 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -8,6 +8,8 @@ mod project_error; mod too_many_supers; mod unexpected_directory; mod visibility_mismatch; +mod assertion_error; +mod runtime_error; pub use conflicting_roles::ConflictingRoles; pub use import_all::ImportAll; @@ -18,3 +20,5 @@ pub use project_error::{ErrorPosition, ProjectError, ProjectResult}; pub use too_many_supers::TooManySupers; pub use unexpected_directory::UnexpectedDirectory; pub use visibility_mismatch::VisibilityMismatch; +pub use assertion_error::AssertionError; +pub use runtime_error::RuntimeError; diff --git a/src/error/not_exported.rs b/src/error/not_exported.rs index 8545f04..09d12e2 100644 --- a/src/error/not_exported.rs +++ b/src/error/not_exported.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::sync::Arc; use super::{ErrorPosition, ProjectError}; use crate::representations::location::Location; @@ -25,14 +25,14 @@ impl ProjectError for NotExported { Box::new( [ ErrorPosition { - location: Location::File(Rc::new(self.file.clone())), + location: Location::File(Arc::new(self.file.clone())), message: Some(format!( "{} isn't exported", Interner::extern_all(&self.subpath).join("::") )), }, ErrorPosition { - location: Location::File(Rc::new(self.referrer_file.clone())), + location: Location::File(Arc::new(self.referrer_file.clone())), message: Some(format!( "{} cannot see this symbol", Interner::extern_all(&self.referrer_subpath).join("::") diff --git a/src/error/not_found.rs b/src/error/not_found.rs deleted file mode 100644 index 26eabf6..0000000 --- a/src/error/not_found.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::rc::Rc; - -use super::ProjectError; -use crate::representations::project::ProjectModule; -#[allow(unused)] // For doc -use crate::tree::Module; -use crate::tree::WalkError; -use crate::{Interner, Location, NameLike, Tok, VName}; - -/// Error produced when an import refers to a nonexistent module -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct NotFound { - /// The module that imported the invalid path - pub source: Option, - /// The file not containing the expected path - pub file: VName, - /// The invalid import path - pub subpath: VName, -} -impl NotFound { - /// Produce this error from the parameters of [Module]`::walk_ref` and a - /// [WalkError] - /// - /// # Panics - /// - /// - if `path` is shorter than the `pos` of the error - /// - if a walk up to but not including `pos` fails - /// - /// Basically, if `e` was not produced by the `walk*` methods called on - /// `path`. - #[must_use] - pub fn from_walk_error( - source: &[Tok], - prefix: &[Tok], - path: &[Tok], - orig: &ProjectModule, - e: WalkError, - ) -> Self { - let last_mod = - orig.walk_ref(&path[..e.pos], false).expect("error occured on next step"); - let mut whole_path = prefix.iter().chain(path.iter()).cloned(); - if let Some(file) = &last_mod.extra.file { - Self { - source: Some(source.to_vec()), - file: whole_path.by_ref().take(file.len()).collect(), - subpath: whole_path.collect(), - } - } else { - Self { - source: Some(source.to_vec()), - file: whole_path.collect(), - subpath: Vec::new(), - } - } - } -} -impl ProjectError for NotFound { - fn description(&self) -> &str { - "an import refers to a nonexistent module" - } - fn message(&self) -> String { - format!( - "module {} in {} was not found", - Interner::extern_all(&self.subpath).join("::"), - Interner::extern_all(&self.file).join("/"), - ) - } - fn one_position(&self) -> crate::Location { - Location::File(Rc::new(Interner::extern_all(&self.file))) - } -} diff --git a/src/systems/runtime_error.rs b/src/error/runtime_error.rs similarity index 87% rename from src/systems/runtime_error.rs rename to src/error/runtime_error.rs index 98a0a3b..7c7b9a2 100644 --- a/src/systems/runtime_error.rs +++ b/src/error/runtime_error.rs @@ -17,12 +17,12 @@ impl RuntimeError { message: String, operation: &'static str, ) -> Result> { - return Err(Self { message, operation }.into_extern()); + Err(Self { message, operation }.into_extern()) } /// Construct and upcast to [ExternError] pub fn ext(message: String, operation: &'static str) -> Rc { - return Self { message, operation }.into_extern(); + Self { message, operation }.into_extern() } } diff --git a/src/error/unexpected_directory.rs b/src/error/unexpected_directory.rs index 1365aef..636c121 100644 --- a/src/error/unexpected_directory.rs +++ b/src/error/unexpected_directory.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::sync::Arc; use super::ProjectError; use crate::{Interner, Location, VName}; @@ -16,7 +16,7 @@ impl ProjectError for UnexpectedDirectory { to a directory" } fn one_position(&self) -> crate::Location { - Location::File(Rc::new(self.path.clone())) + Location::File(Arc::new(self.path.clone())) } fn message(&self) -> String { diff --git a/src/error/visibility_mismatch.rs b/src/error/visibility_mismatch.rs index f9de177..f99c201 100644 --- a/src/error/visibility_mismatch.rs +++ b/src/error/visibility_mismatch.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::sync::Arc; use super::project_error::ProjectError; use crate::representations::location::Location; @@ -23,6 +23,6 @@ impl ProjectError for VisibilityMismatch { ) } fn one_position(&self) -> Location { - Location::File(Rc::new(self.file.clone())) + Location::File(Arc::new(self.file.clone())) } } diff --git a/src/facade/environment.rs b/src/facade/environment.rs index b7665c5..759e98d 100644 --- a/src/facade/environment.rs +++ b/src/facade/environment.rs @@ -42,6 +42,8 @@ impl<'a> Environment<'a> { let system_tree = from_const_tree(sys.constants.clone(), &sys.vname(i)); tree = ProjectTree(never::unwrap_always(tree.0.overlay(system_tree.0))); } + let mut lexer_plugins = vec![]; + let mut line_parsers = vec![]; let mut prelude = vec![]; for sys in systems.iter() { if !sys.code.is_empty() { @@ -50,9 +52,13 @@ impl<'a> Environment<'a> { &|k| sys.load_file(k), &tree, &prelude, + &lexer_plugins, + &line_parsers, i, )?; } + lexer_plugins.extend(sys.lexer_plugin.as_deref().iter()); + line_parsers.extend(sys.line_parser.as_deref().iter()); prelude.extend_from_slice(&sys.prelude); } Ok(CompiledEnv { prelude, tree, systems }) @@ -67,11 +73,19 @@ impl<'a> Environment<'a> { let i = self.i; let CompiledEnv { prelude, systems, tree } = self.compile()?; let file_cache = file_loader::mk_dir_cache(dir.to_path_buf()); + let lexer_plugins = (systems.iter()) + .filter_map(|s| s.lexer_plugin.as_deref()) + .collect::>(); + let line_parsers = (systems.iter()) + .filter_map(|s| s.line_parser.as_deref()) + .collect::>(); let vname_tree = parse_layer( iter::once(target), &|path| file_cache.find(path), &tree, &prelude, + &lexer_plugins, + &line_parsers, i, )?; let tree = vname_to_sym_tree(vname_tree, i); diff --git a/src/facade/pre_macro.rs b/src/facade/pre_macro.rs index 2134218..d34a120 100644 --- a/src/facade/pre_macro.rs +++ b/src/facade/pre_macro.rs @@ -1,5 +1,5 @@ use std::iter; -use std::rc::Rc; +use std::sync::Arc; use hashbrown::HashMap; @@ -50,7 +50,7 @@ impl<'a> PreMacro<'a> { .unwrap_or_else(|_| panic!("path sourced from symbol names")); (origin.extra.file.as_ref()).cloned() }) - .map(|p| Location::File(Rc::new(p))) + .map(|p| Location::File(Arc::new(p))) .unwrap_or(Location::Unknown); (name, (expr, location)) }) diff --git a/src/facade/system.rs b/src/facade/system.rs index d50f96c..0e38882 100644 --- a/src/facade/system.rs +++ b/src/facade/system.rs @@ -2,6 +2,7 @@ use hashbrown::HashMap; use crate::error::{ErrorPosition, ProjectError}; use crate::interpreter::HandlerTable; +use crate::parse::{LexerPlugin, LineParser}; use crate::pipeline::file_loader::{IOResult, Loaded}; use crate::sourcefile::FileEntry; use crate::utils::boxed_iter::box_empty; @@ -23,6 +24,13 @@ pub struct System<'a> { pub prelude: Vec, /// Handlers for actions defined in this system pub handlers: HandlerTable<'a>, + /// Custom lexer for the source code representation atomic data. + /// These take priority over builtin lexers so the syntax they + /// match should be unambiguous + pub lexer_plugin: Option>, + /// Parser that processes custom line types into their representation in the + /// module tree + pub line_parser: Option>, } impl<'a> System<'a> { /// Intern the name of the system so that it can be used as an Orchid diff --git a/src/foreign/atom.rs b/src/foreign/atom.rs index aa00f40..0e7fddc 100644 --- a/src/foreign/atom.rs +++ b/src/foreign/atom.rs @@ -1,13 +1,16 @@ use std::any::Any; use std::fmt::Debug; +use std::rc::Rc; use dyn_clone::DynClone; -use crate::interpreted::ExprInst; +use super::ExternError; +use crate::ddispatch::request; +use crate::error::AssertionError; +use crate::interpreted::{ExprInst, TryFromExprInst}; use crate::interpreter::{Context, RuntimeError}; use crate::representations::interpreted::Clause; use crate::utils::ddispatch::Responder; -use crate::Primitive; /// Information returned by [Atomic::run]. This mirrors /// [crate::interpreter::Return] but with a clause instead of an Expr. @@ -24,8 +27,18 @@ pub struct AtomicReturn { /// Returned by [Atomic::run] pub type AtomicResult = Result; +/// Trait for things that are _definitely_ equal. +pub trait StrictEq { + /// must return true if the objects were produced via the exact same sequence + /// of transformations, including any relevant context data. Must return false + /// if the objects are of different type, or if their type is [PartialEq] + /// and [PartialEq::eq] returns false. + fn strict_eq(&self, other: &dyn Any) -> bool; +} + /// Functionality the interpreter needs to handle a value -pub trait Atomic: Any + Debug + DynClone + Responder +pub trait Atomic: + Any + Debug + DynClone + StrictEq + Responder + Send where Self: 'static, { @@ -54,7 +67,7 @@ where where Self: Sized, { - Clause::P(Primitive::Atom(Atom(Box::new(self)))) + Clause::Atom(Atom(Box::new(self))) } /// Wrap the atom in a new expression instance to be placed in a tree @@ -73,7 +86,7 @@ where /// inert at which point the [Atom] will validate and process the argument, /// returning a different [Atom] intended for processing by external code, a new /// [ExternFn] to capture an additional argument, or an Orchid expression -/// to pass control back to the interpreter.btop +/// to pass control back to the interpreter. pub struct Atom(pub Box); impl Atom { /// Wrap an [Atomic] in a type-erased box @@ -84,23 +97,26 @@ impl Atom { /// Get the contained data #[must_use] pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic } - /// Attempt to downcast contained data to a specific type - pub fn try_cast(self) -> Result { + /// Test the type of the contained data without downcasting + #[must_use] + pub fn is(&self) -> bool { self.data().as_any_ref().is::() } + /// Downcast contained data, panic if it isn't the specified type + #[must_use] + pub fn downcast(self) -> T { + *self.0.as_any().downcast().expect("Type mismatch on Atom::cast") + } + /// Normalize the contained data + pub fn run(self, ctx: Context) -> AtomicResult { self.0.run(ctx) } + /// Request a delegate from the encapsulated data + pub fn request(&self) -> Option { request(self.0.as_ref()) } + /// Downcast the atom to a concrete atomic type, or return the original atom + /// if it is not the specified type + pub fn try_downcast(self) -> Result { match self.0.as_any_ref().is::() { true => Ok(*self.0.as_any().downcast().expect("checked just above")), false => Err(self), } } - /// Test the type of the contained data without downcasting - #[must_use] - pub fn is(&self) -> bool { self.data().as_any_ref().is::() } - /// Downcast contained data, panic if it isn't the specified type - #[must_use] - pub fn cast(self) -> T { - *self.0.as_any().downcast().expect("Type mismatch on Atom::cast") - } - /// Normalize the contained data - pub fn run(self, ctx: Context) -> AtomicResult { self.0.run(ctx) } } impl Clone for Atom { @@ -109,6 +125,16 @@ impl Clone for Atom { impl Debug for Atom { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "##ATOM[{:?}]##", self.data()) + write!(f, "{:?}", self.data()) + } +} + +impl TryFromExprInst for Atom { + fn from_exi(exi: ExprInst) -> Result> { + let loc = exi.location(); + match exi.expr_val().clause { + Clause::Atom(a) => Ok(a), + _ => AssertionError::fail(loc, "atom"), + } } } diff --git a/src/foreign/cps_box.rs b/src/foreign/cps_box.rs index b553ba7..a0b97d1 100644 --- a/src/foreign/cps_box.rs +++ b/src/foreign/cps_box.rs @@ -7,12 +7,12 @@ use trait_set::trait_set; use super::{Atomic, ExternFn, InertAtomic, XfnResult}; use crate::interpreted::{Clause, ExprInst}; use crate::interpreter::{Context, HandlerRes}; -use crate::utils::pure_push::pushed_ref; +use crate::utils::pure_seq::pushed_ref; use crate::ConstTree; trait_set! { /// A "well behaved" type that can be used as payload in a CPS box - pub trait CPSPayload = Clone + Debug + 'static; + pub trait CPSPayload = Clone + Debug + Send + 'static; /// A function to handle a CPS box with a specific payload pub trait CPSHandler = FnMut(&T, &ExprInst) -> HandlerRes; } diff --git a/src/foreign/extern_fn.rs b/src/foreign/extern_fn.rs index e1622b2..e4d629d 100644 --- a/src/foreign/extern_fn.rs +++ b/src/foreign/extern_fn.rs @@ -3,13 +3,12 @@ use std::fmt::{Debug, Display}; use std::hash::Hash; use std::rc::Rc; -use dyn_clone::DynClone; +use dyn_clone::{DynClone, clone_box}; use super::XfnResult; use crate::interpreted::ExprInst; use crate::interpreter::Context; use crate::representations::interpreted::Clause; -use crate::Primitive; /// Errors produced by external code pub trait ExternError: Display { @@ -34,7 +33,7 @@ impl Error for dyn ExternError {} /// Represents an externally defined function from the perspective of /// the executor. Since Orchid lacks basic numerical operations, /// these are also external functions. -pub trait ExternFn: DynClone { +pub trait ExternFn: DynClone + Send { /// Display name of the function #[must_use] fn name(&self) -> &str; @@ -50,7 +49,7 @@ pub trait ExternFn: DynClone { where Self: Sized + 'static, { - Clause::P(Primitive::ExternFn(Box::new(self))) + Clause::ExternFn(ExFn(Box::new(self))) } } @@ -68,3 +67,21 @@ impl Debug for dyn ExternFn { write!(f, "##EXTERN[{}]##", self.name()) } } + +/// Represents a black box function that can be applied to a [Clause] to produce +/// a new [Clause], typically an [Atom] representing external work, a new [ExFn] +/// to take additional arguments, or an Orchid tree to return control to the +/// interpreter +#[derive(Debug)] +pub struct ExFn(pub Box); +impl ExFn { + /// Combine the function with an argument to produce a new clause + pub fn apply(self, arg: ExprInst, ctx: Context) -> XfnResult { + self.0.apply(arg, ctx) + } +} +impl Clone for ExFn { + fn clone(&self) -> Self { + Self(clone_box(self.0.as_ref())) + } +} diff --git a/src/foreign/fn_bridge.rs b/src/foreign/fn_bridge.rs index c80fc63..b1ef65d 100644 --- a/src/foreign/fn_bridge.rs +++ b/src/foreign/fn_bridge.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::rc::Rc; +use super::atom::StrictEq; use super::{ Atomic, AtomicResult, AtomicReturn, ExternError, ExternFn, XfnResult, }; @@ -9,7 +10,7 @@ use crate::ddispatch::Responder; use crate::interpreted::{Clause, ExprInst, TryFromExprInst}; use crate::interpreter::{run, Context, Return}; use crate::systems::codegen::{opt, res}; -use crate::{Literal, OrcString}; +use crate::OrcString; /// A trait for things that are infallibly convertible to [Clause]. These types /// can be returned by callbacks passed to the [super::xfn_1ary] family of @@ -17,6 +18,8 @@ use crate::{Literal, OrcString}; pub trait ToClause: Clone { /// Convert the type to a [Clause]. fn to_clause(self) -> Clause; + /// Convert to an expression instance via [ToClause]. + fn to_exi(self) -> ExprInst { self.to_clause().wrap() } } impl ToClause for T { @@ -28,14 +31,8 @@ impl ToClause for Clause { impl ToClause for ExprInst { fn to_clause(self) -> Clause { self.expr_val().clause } } -impl ToClause for Literal { - fn to_clause(self) -> Clause { self.into() } -} -impl ToClause for u64 { - fn to_clause(self) -> Clause { Literal::Uint(self).into() } -} impl ToClause for String { - fn to_clause(self) -> Clause { OrcString::from(self).cls() } + fn to_clause(self) -> Clause { OrcString::from(self).atom_cls() } } impl ToClause for Option { fn to_clause(self) -> Clause { opt(self.map(|t| t.to_clause().wrap())) } @@ -59,31 +56,28 @@ pub struct Param { _t: PhantomData, _u: PhantomData, } +unsafe impl Send for Param {} impl Param { /// Wrap a new function in a parametric struct pub fn new(f: F) -> Self where F: FnOnce(T) -> Result>, { - Self { data: f, _t: PhantomData::default(), _u: PhantomData::default() } + Self { data: f, _t: PhantomData, _u: PhantomData } } /// Take out the function pub fn get(self) -> F { self.data } } impl Clone for Param { fn clone(&self) -> Self { - Self { - data: self.data.clone(), - _t: PhantomData::default(), - _u: PhantomData::default(), - } + Self { data: self.data.clone(), _t: PhantomData, _u: PhantomData } } } impl< T: 'static + TryFromExprInst, U: 'static + ToClause, - F: 'static + Clone + FnOnce(T) -> Result>, + F: 'static + Clone + Send + FnOnce(T) -> Result>, > ToClause for Param { fn to_clause(self) -> Clause { self.xfn_cls() } @@ -93,6 +87,11 @@ struct FnMiddleStage { argument: ExprInst, f: Param, } +impl StrictEq for FnMiddleStage { + fn strict_eq(&self, _other: &dyn std::any::Any) -> bool { + unimplemented!("This should never be able to appear in a pattern") + } +} impl Clone for FnMiddleStage { fn clone(&self) -> Self { @@ -110,7 +109,7 @@ impl Responder for FnMiddleStage {} impl< T: 'static + TryFromExprInst, U: 'static + ToClause, - F: 'static + Clone + FnOnce(T) -> Result>, + F: 'static + Clone + FnOnce(T) -> Result> + Send, > Atomic for FnMiddleStage { fn as_any(self: Box) -> Box { self } @@ -128,7 +127,7 @@ impl< impl< T: 'static + TryFromExprInst, U: 'static + ToClause, - F: 'static + Clone + FnOnce(T) -> Result>, + F: 'static + Clone + Send + FnOnce(T) -> Result>, > ExternFn for Param { fn name(&self) -> &str { "anonymous Rust function" } @@ -155,16 +154,16 @@ pub mod constructors { " Orchid function. See also Constraints summarized:\n\n" "- the callback must live as long as `'static`\n" "- All arguments must implement [TryFromExprInst]\n" - "- all but the last argument must implement [Clone]\n" + "- all but the last argument must implement [Clone] and [Send]\n" "- the return type must implement [ToClause].\n\n" ] #[doc = "Other arities: " $( "[xfn_" $alt "ary], " )+ ] pub fn [< xfn_ $number ary >] < - $( $t : TryFromExprInst + Clone + 'static, )* + $( $t : TryFromExprInst + Clone + Send + 'static, )* TLast: TryFromExprInst + 'static, - TReturn: ToClause + 'static, + TReturn: ToClause + Send + 'static, TFunction: FnOnce( $( $t , )* TLast ) - -> Result> + Clone + 'static + -> Result> + Clone + Send + 'static >(function: TFunction) -> impl ExternFn { xfn_variant!(@BODY_LOOP function ( $( ( $t [< $t:lower >] ) )* ) diff --git a/src/foreign/inert.rs b/src/foreign/inert.rs index b1b677f..4e03b8a 100644 --- a/src/foreign/inert.rs +++ b/src/foreign/inert.rs @@ -2,22 +2,25 @@ use std::any::Any; use std::fmt::Debug; use std::rc::Rc; +use ordered_float::NotNan; + +use super::atom::StrictEq; use super::{AtomicResult, AtomicReturn, ExternError}; +use crate::error::AssertionError; #[allow(unused)] // for doc // use crate::define_fn; use crate::foreign::Atomic; use crate::interpreted::{Clause, Expr, ExprInst, TryFromExprInst}; use crate::interpreter::Context; -use crate::systems::AssertionError; +use crate::systems::stl::Numeric; use crate::utils::ddispatch::{Request, Responder}; -use crate::Primitive; /// A proxy trait that implements [Atomic] for blobs of data in Rust code that /// cannot be processed and always report inert. Since these are expected to be /// parameters of functions defined with [define_fn] it also automatically /// implements [TryFromExprInst] so that a conversion doesn't have to be /// provided in argument lists. -pub trait InertAtomic: Debug + Clone + 'static { +pub trait InertAtomic: Debug + Clone + Send + 'static { /// Typename to be shown in the error when a conversion from [ExprInst] fails #[must_use] fn type_str() -> &'static str; @@ -25,9 +28,29 @@ pub trait InertAtomic: Debug + Clone + 'static { /// you need it, but behaves exactly as the default implementation. #[allow(unused_mut, unused_variables)] // definition should show likely usage fn respond(&self, mut request: Request) {} + /// Equality comparison used by the pattern matcher. Since the pattern matcher + /// only works with parsed code, you only need to implement this if your type + /// is directly parseable. + /// + /// If your type implements [PartialEq], this can simply be implemented as + /// ```ignore + /// fn strict_eq(&self, other: &Self) -> bool { self == other } + /// ``` + fn strict_eq(&self, _: &Self) -> bool { false } +} +impl StrictEq for T { + fn strict_eq(&self, other: &dyn Any) -> bool { + other.downcast_ref().map_or(false, |other| self.strict_eq(other)) + } } impl Responder for T { - fn respond(&self, request: Request) { self.respond(request) } + fn respond(&self, mut request: Request) { + if request.can_serve::() { + request.serve(self.clone()) + } else { + self.respond(request) + } + } } impl Atomic for T { fn as_any(self: Box) -> Box { self } @@ -42,7 +65,7 @@ impl TryFromExprInst for T { fn from_exi(exi: ExprInst) -> Result> { let Expr { clause, location } = exi.expr_val(); match clause { - Clause::P(Primitive::Atom(a)) => match a.0.as_any().downcast() { + Clause::Atom(a) => match a.0.as_any().downcast() { Ok(t) => Ok(*t), Err(_) => AssertionError::fail(location, Self::type_str()), }, @@ -50,3 +73,24 @@ impl TryFromExprInst for T { } } } + +impl InertAtomic for bool { + fn type_str() -> &'static str { "bool" } + fn strict_eq(&self, other: &Self) -> bool { self == other } +} + +impl InertAtomic for usize { + fn type_str() -> &'static str { "usize" } + fn strict_eq(&self, other: &Self) -> bool { self == other } + fn respond(&self, mut request: Request) { + request.serve(Numeric::Uint(*self)) + } +} + +impl InertAtomic for NotNan { + fn type_str() -> &'static str { "NotNan" } + fn strict_eq(&self, other: &Self) -> bool { self == other } + fn respond(&self, mut request: Request) { + request.serve(Numeric::Float(*self)) + } +} diff --git a/src/foreign/mod.rs b/src/foreign/mod.rs index 2cb8ecd..c0cf495 100644 --- a/src/foreign/mod.rs +++ b/src/foreign/mod.rs @@ -10,8 +10,8 @@ mod inert; use std::rc::Rc; -pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn}; -pub use extern_fn::{ExternError, ExternFn}; +pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn, StrictEq}; +pub use extern_fn::{ExternError, ExternFn, ExFn}; pub use fn_bridge::constructors::{ xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, xfn_5ary, xfn_6ary, xfn_7ary, xfn_8ary, xfn_9ary, diff --git a/src/interner/monotype.rs b/src/interner/monotype.rs index 6ae83bd..28d390a 100644 --- a/src/interner/monotype.rs +++ b/src/interner/monotype.rs @@ -1,7 +1,6 @@ use std::borrow::Borrow; -use std::cell::RefCell; use std::hash::{BuildHasher, Hash}; -use std::rc::Rc; +use std::sync::{RwLock, Arc}; use hashbrown::HashMap; @@ -11,32 +10,32 @@ use super::token::Tok; /// Lasso but much simpler, in part because not much can be known about the /// type. pub struct TypedInterner { - tokens: RefCell, Tok>>, + tokens: RwLock, Tok>>, } impl TypedInterner { /// Create a fresh interner instance #[must_use] - pub fn new() -> Rc { - Rc::new(Self { tokens: RefCell::new(HashMap::new()) }) + pub fn new() -> Arc { + Arc::new(Self { tokens: RwLock::new(HashMap::new()) }) } /// Intern an object, returning a token #[must_use] pub fn i>( - self: &Rc, + self: &Arc, q: &Q, ) -> Tok where T: Borrow, { - let mut tokens = self.tokens.borrow_mut(); + let mut tokens = self.tokens.write().unwrap(); let hash = compute_hash(tokens.hasher(), q); let raw_entry = tokens .raw_entry_mut() .from_hash(hash, |k| >::borrow(k) == q); let kv = raw_entry.or_insert_with(|| { - let keyrc = Rc::new(q.to_owned()); - let token = Tok::::new(keyrc.clone(), Rc::downgrade(self)); + let keyrc = Arc::new(q.to_owned()); + let token = Tok::::new(keyrc.clone(), Arc::downgrade(self)); (keyrc, token) }); kv.1.clone() diff --git a/src/interner/multitype.rs b/src/interner/multitype.rs index 5771e8e..38d7073 100644 --- a/src/interner/multitype.rs +++ b/src/interner/multitype.rs @@ -2,7 +2,7 @@ use std::any::{Any, TypeId}; use std::borrow::Borrow; use std::cell::{RefCell, RefMut}; use std::hash::Hash; -use std::rc::Rc; +use std::sync::Arc; use hashbrown::HashMap; @@ -13,7 +13,7 @@ use super::token::Tok; /// that implements [ToOwned]. Objects of the same type are stored together in a /// [TypedInterner]. pub struct Interner { - interners: RefCell>>, + interners: RefCell>>, } impl Interner { /// Create a new interner @@ -24,7 +24,7 @@ impl Interner { #[must_use] pub fn i(&self, q: &Q) -> Tok where - Q::Owned: 'static + Eq + Hash + Clone + Borrow, + Q::Owned: 'static + Eq + Hash + Clone + Borrow + Send + Sync, { let mut interners = self.interners.borrow_mut(); let interner = get_interner(&mut interners); @@ -44,9 +44,9 @@ impl Default for Interner { /// Get or create an interner for a given type. #[must_use] -fn get_interner( - interners: &mut RefMut>>, -) -> Rc> { +fn get_interner( + interners: &mut RefMut>>, +) -> Arc> { let boxed = interners .raw_entry_mut() .from_key(&TypeId::of::()) diff --git a/src/interner/token.rs b/src/interner/token.rs index b4b09bd..70b6374 100644 --- a/src/interner/token.rs +++ b/src/interner/token.rs @@ -3,7 +3,7 @@ use std::fmt::{Debug, Display}; use std::hash::Hash; use std::num::NonZeroUsize; use std::ops::Deref; -use std::rc::{Rc, Weak}; +use std::sync::{Arc, Weak}; use super::TypedInterner; @@ -13,13 +13,13 @@ use super::TypedInterner; /// currently not enforced. #[derive(Clone)] pub struct Tok { - data: Rc, + data: Arc, interner: Weak>, } impl Tok { /// Create a new token. Used exclusively by the interner #[must_use] - pub(crate) fn new(data: Rc, interner: Weak>) -> Self { + pub(crate) fn new(data: Arc, interner: Weak>) -> Self { Self { data, interner } } /// Take the ID number out of a token diff --git a/src/interpreter/apply.rs b/src/interpreter/apply.rs index 340b442..c593b39 100644 --- a/src/interpreter/apply.rs +++ b/src/interpreter/apply.rs @@ -3,7 +3,7 @@ use super::error::RuntimeError; use super::Return; use crate::foreign::AtomicReturn; use crate::representations::interpreted::{Clause, ExprInst}; -use crate::representations::{PathSet, Primitive}; +use crate::representations::PathSet; use crate::utils::never::{unwrap_always, Always}; use crate::utils::Side; @@ -77,9 +77,8 @@ pub fn apply( ) -> Result { let (state, (gas, inert)) = f.try_update(|clause, loc| match clause { // apply an ExternFn or an internal function - Clause::P(Primitive::ExternFn(f)) => { - let clause = - f.apply(x, ctx.clone()).map_err(|e| RuntimeError::Extern(e))?; + Clause::ExternFn(f) => { + let clause = f.apply(x, ctx.clone()).map_err(RuntimeError::Extern)?; Ok((clause, (ctx.gas.map(|g| g - 1), false))) }, Clause::Lambda { args, body } => Ok(if let Some(args) = args { @@ -97,7 +96,7 @@ pub fn apply( } else { Err(RuntimeError::MissingSymbol(name.clone(), loc)) }, - Clause::P(Primitive::Atom(atom)) => { + Clause::Atom(atom) => { // take a step in expanding atom let AtomicReturn { clause, gas, inert } = atom.run(ctx.clone())?; Ok((Clause::Apply { f: clause.wrap(), x }, (gas, inert))) diff --git a/src/interpreter/handler.rs b/src/interpreter/handler.rs index 47e5b20..38b6c01 100644 --- a/src/interpreter/handler.rs +++ b/src/interpreter/handler.rs @@ -8,7 +8,6 @@ use super::{run, Context, Return, RuntimeError}; use crate::foreign::{Atom, Atomic, ExternError}; use crate::interpreted::{Clause, Expr, ExprInst}; use crate::utils::take_with_output; -use crate::Primitive; trait_set! { trait Handler = FnMut(Box) -> HandlerRes; @@ -71,9 +70,9 @@ pub fn run_handler( loop { let mut ret = run(expr, ctx.clone())?; let quit = take_with_output(&mut ret.state, |exi| match exi.expr_val() { - Expr { clause: Clause::P(Primitive::Atom(a)), .. } => { + Expr { clause: Clause::Atom(a), .. } => { match handlers.dispatch(a.0) { - Err(b) => (Clause::P(Primitive::Atom(Atom(b))).wrap(), Ok(true)), + Err(b) => (Clause::Atom(Atom(b)).wrap(), Ok(true)), Ok(e) => match e { Ok(expr) => (expr, Ok(false)), Err(e) => (Clause::Bottom.wrap(), Err(e)), diff --git a/src/interpreter/run.rs b/src/interpreter/run.rs index 02d66ed..3463744 100644 --- a/src/interpreter/run.rs +++ b/src/interpreter/run.rs @@ -3,7 +3,6 @@ use super::context::{Context, Return}; use super::error::RuntimeError; use crate::foreign::AtomicReturn; use crate::representations::interpreted::{Clause, ExprInst}; -use crate::representations::Primitive; /// Normalize an expression using beta reduction with memoization pub fn run(expr: ExprInst, mut ctx: Context) -> Result { @@ -19,7 +18,7 @@ pub fn run(expr: ExprInst, mut ctx: Context) -> Result { ctx.gas = res.gas; cls = res.state.expr().clause.clone(); }, - Clause::P(Primitive::Atom(data)) => { + Clause::Atom(data) => { let AtomicReturn { clause, gas, inert } = data.run(ctx.clone())?; if inert { return Ok((clause, (gas, true))); diff --git a/src/lib.rs b/src/lib.rs index c06e498..062966b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,8 +29,8 @@ pub use representations::project::{ collect_consts, collect_rules, vname_to_sym_tree, ProjectTree, }; pub use representations::{ - ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal, - Location, NameLike, OrcString, PathSet, Primitive, Sym, VName, + ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Location, + NameLike, OrcString, PathSet, Sym, VName, }; pub use utils::substack::Substack; pub use utils::{ddispatch, take_with_output, thread_pool, IdMap, Side}; diff --git a/src/parse/comment.rs b/src/parse/comment.rs deleted file mode 100644 index b28d2e5..0000000 --- a/src/parse/comment.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub use chumsky::prelude::*; -pub use chumsky::{self, Parser}; - -use super::decls::SimpleParser; - -/// Parses Lua-style comments -#[must_use] -pub fn comment_parser() -> impl SimpleParser { - choice(( - just("--[").ignore_then(take_until(just("]--").ignored())), - just("--").ignore_then(take_until(just("\n").rewind().ignored().or(end()))), - )) - .map(|(vc, ())| vc) - .collect() - .labelled("comment") -} diff --git a/src/parse/context.rs b/src/parse/context.rs index 7e08c78..3e30267 100644 --- a/src/parse/context.rs +++ b/src/parse/context.rs @@ -1,19 +1,62 @@ -use std::rc::Rc; +use std::ops::Range; +use std::sync::Arc; +use super::stream::Stream; +use crate::error::ProjectResult; +use crate::foreign::Atom; use crate::interner::Interner; -use crate::{Tok, VName}; +use crate::sourcefile::FileEntryKind; +use crate::{Location, VName}; /// Trait enclosing all context features /// /// Hiding type parameters in associated types allows for simpler /// parser definitions -pub trait Context: Clone { +pub trait Context { #[must_use] - fn ops(&self) -> &[Tok]; - #[must_use] - fn file(&self) -> Rc; + fn file(&self) -> Arc; #[must_use] fn interner(&self) -> &Interner; + #[must_use] + fn source(&self) -> Arc; + fn lexers(&self) -> &[&dyn LexerPlugin]; + fn line_parsers(&self) -> &[&dyn LineParser]; + #[must_use] + fn pos(&self, tail: &str) -> usize { self.source().len() - tail.len() } + #[must_use] + fn location(&self, len: usize, tail: &str) -> Location { + match self.pos(tail).checked_sub(len) { + Some(start) => self.range_loc(start..self.pos(tail)), + None => { + let tl = tail.len(); + panic!("len={len} greater than tail.len()={tl}; tail={tail:?}") + }, + } + } + #[must_use] + fn range_loc(&self, range: Range) -> Location { + Location::Range { file: self.file(), range, source: self.source() } + } +} + +pub type LexerPluginOut<'a> = Option>; +pub type LineParserOut = Option>>; +pub trait LexerPlugin: + for<'a> Fn(&'a str, &dyn Context) -> LexerPluginOut<'a> + Sync + Send +{ +} +impl LexerPlugin for F where + F: for<'a> Fn(&'a str, &dyn Context) -> LexerPluginOut<'a> + Sync + Send +{ +} + +pub trait LineParser: + Fn(Stream<'_>, &dyn Context) -> LineParserOut + Sync + Send +{ +} +impl LineParser for F where + F: Fn(Stream<'_>, &dyn Context) -> LineParserOut + Sync + Send +{ } /// Struct implementing context @@ -21,29 +64,55 @@ pub trait Context: Clone { /// Hiding type parameters in associated types allows for simpler /// parser definitions pub struct ParsingContext<'a> { - pub ops: &'a [Tok], pub interner: &'a Interner, - pub file: Rc, + pub file: Arc, + pub source: Arc, + pub lexers: &'a [&'a dyn LexerPlugin], + pub line_parsers: &'a [&'a dyn LineParser], } impl<'a> ParsingContext<'a> { pub fn new( - ops: &'a [Tok], interner: &'a Interner, - file: Rc, + file: Arc, + source: Arc, + lexers: &'a [&'a dyn LexerPlugin], + line_parsers: &'a [&'a dyn LineParser], ) -> Self { - Self { ops, interner, file } + Self { interner, file, source, lexers, line_parsers } } } impl<'a> Clone for ParsingContext<'a> { fn clone(&self) -> Self { - Self { ops: self.ops, interner: self.interner, file: self.file.clone() } + Self { + interner: self.interner, + file: self.file.clone(), + source: self.source.clone(), + lexers: self.lexers, + line_parsers: self.line_parsers, + } } } impl Context for ParsingContext<'_> { fn interner(&self) -> &Interner { self.interner } - fn file(&self) -> Rc { self.file.clone() } - fn ops(&self) -> &[Tok] { self.ops } + fn file(&self) -> Arc { self.file.clone() } + fn source(&self) -> Arc { self.source.clone() } + fn lexers(&self) -> &[&dyn LexerPlugin] { self.lexers } + fn line_parsers(&self) -> &[&dyn LineParser] { self.line_parsers } +} + +pub struct MockContext<'a>(pub &'a Interner); +impl<'a> Context for MockContext<'a> { + // these are doing something + fn interner(&self) -> &Interner { self.0 } + fn pos(&self, tail: &str) -> usize { usize::MAX / 2 - tail.len() } + // these are expendable + fn file(&self) -> Arc { Arc::new(Vec::new()) } + fn lexers(&self) -> &[&dyn LexerPlugin] { &[] } + fn line_parsers(&self) -> &[&dyn LineParser] { &[] } + fn location(&self, _: usize, _: &str) -> Location { Location::Unknown } + fn range_loc(&self, _: Range) -> Location { Location::Unknown } + fn source(&self) -> Arc { Arc::new(String::new()) } } diff --git a/src/parse/decls.rs b/src/parse/decls.rs deleted file mode 100644 index ce351da..0000000 --- a/src/parse/decls.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::hash::Hash; - -use chumsky::prelude::Simple; -use chumsky::{BoxedParser, Parser}; -use trait_set::trait_set; - -trait_set! { - /// Wrapper around [Parser] with [Simple] error to avoid repeating the input - pub trait SimpleParser = - Parser>; -} -/// Boxed version of [SimpleParser] -pub type BoxedSimpleParser<'a, I, O> = BoxedParser<'a, I, O, Simple>; diff --git a/src/parse/errors.rs b/src/parse/errors.rs index c6c8b05..563435a 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -1,12 +1,10 @@ use std::rc::Rc; -use chumsky::prelude::Simple; use itertools::Itertools; use super::{Entry, Lexeme}; -use crate::error::{ErrorPosition, ProjectError}; -use crate::utils::BoxedIter; -use crate::{Location, Tok, VName}; +use crate::error::ProjectError; +use crate::{Location, Tok}; #[derive(Debug)] pub struct LineNeedsPrefix { @@ -14,10 +12,10 @@ pub struct LineNeedsPrefix { } impl ProjectError for LineNeedsPrefix { fn description(&self) -> &str { "This linetype requires a prefix" } + fn one_position(&self) -> Location { self.entry.location() } fn message(&self) -> String { format!("{} cannot appear at the beginning of a line", self.entry) } - fn one_position(&self) -> Location { self.entry.location() } } #[derive(Debug)] @@ -27,14 +25,12 @@ pub struct UnexpectedEOL { } impl ProjectError for UnexpectedEOL { fn description(&self) -> &str { "The line ended abruptly" } - + fn one_position(&self) -> Location { self.entry.location() } fn message(&self) -> String { "The line ends unexpectedly here. In Orchid, all line breaks outside \ parentheses start a new declaration" .to_string() } - - fn one_position(&self) -> Location { self.entry.location() } } pub struct ExpectedEOL { @@ -58,10 +54,8 @@ impl ExpectedName { } } impl ProjectError for ExpectedName { - fn description(&self) -> &str { - "A name was expected here, but something else was found" - } - + fn description(&self) -> &str { "A name was expected" } + fn one_position(&self) -> Location { self.entry.location() } fn message(&self) -> String { if self.entry.is_keyword() { format!( @@ -72,8 +66,6 @@ impl ProjectError for ExpectedName { format!("Expected a name, found {}", self.entry) } } - - fn one_position(&self) -> Location { self.entry.location() } } #[derive()] @@ -84,18 +76,15 @@ pub struct Expected { } impl Expected { pub fn expect(l: Lexeme, e: &Entry) -> Result<(), Rc> { - if e.lexeme != l { - return Err( - Self { expected: vec![l], or_name: false, found: e.clone() }.rc(), - ); + if e.lexeme.strict_eq(&l) { + return Ok(()); } - Ok(()) + Err(Self { expected: vec![l], or_name: false, found: e.clone() }.rc()) } } impl ProjectError for Expected { - fn description(&self) -> &str { - "A concrete token was expected but something else was found" - } + fn description(&self) -> &str { "A concrete token was expected" } + fn one_position(&self) -> Location { self.found.location() } fn message(&self) -> String { let list = match &self.expected[..] { &[] => return "Unsatisfiable expectation".to_string(), @@ -108,21 +97,15 @@ impl ProjectError for Expected { let or_name = if self.or_name { " or a name" } else { "" }; format!("Expected {list}{or_name} but found {}", self.found) } - - fn one_position(&self) -> Location { self.found.location() } } pub struct ReservedToken { pub entry: Entry, } impl ProjectError for ReservedToken { - fn description(&self) -> &str { - "A token reserved for future use was found in the code" - } - - fn message(&self) -> String { format!("{} is a reserved token", self.entry) } - + fn description(&self) -> &str { "Syntax reserved for future use" } fn one_position(&self) -> Location { self.entry.location() } + fn message(&self) -> String { format!("{} is a reserved token", self.entry) } } pub struct BadTokenInRegion { @@ -130,15 +113,11 @@ pub struct BadTokenInRegion { pub region: &'static str, } impl ProjectError for BadTokenInRegion { - fn description(&self) -> &str { - "A token was found in a region where it should not appear" - } - + fn description(&self) -> &str { "An unexpected token was found" } + fn one_position(&self) -> Location { self.entry.location() } fn message(&self) -> String { format!("{} cannot appear in {}", self.entry, self.region) } - - fn one_position(&self) -> Location { self.entry.location() } } pub struct NotFound { @@ -146,70 +125,90 @@ pub struct NotFound { pub location: Location, } impl ProjectError for NotFound { - fn description(&self) -> &str { - "A specific lexeme was expected but not found in the given range" - } - - fn message(&self) -> String { format!("{} was expected", self.expected) } - + fn description(&self) -> &str { "A specific lexeme was expected" } fn one_position(&self) -> Location { self.location.clone() } + fn message(&self) -> String { format!("{} was expected", self.expected) } } -pub struct LeadingNS { - pub location: Location, -} +pub struct LeadingNS(pub Location); impl ProjectError for LeadingNS { fn description(&self) -> &str { ":: can only follow a name token" } - fn one_position(&self) -> Location { self.location.clone() } + fn one_position(&self) -> Location { self.0.clone() } } -pub struct MisalignedParen { - pub entry: Entry, -} +pub struct MisalignedParen(pub Entry); impl ProjectError for MisalignedParen { - fn description(&self) -> &str { - "Parentheses (), [] and {} must always pair up" - } - fn message(&self) -> String { format!("This {} has no pair", self.entry) } - fn one_position(&self) -> Location { self.entry.location() } + fn description(&self) -> &str { "(), [] and {} must always pair up" } + fn one_position(&self) -> Location { self.0.location() } + fn message(&self) -> String { format!("This {} has no pair", self.0) } } -pub struct NamespacedExport { - pub location: Location, -} +pub struct NamespacedExport(pub Location); impl ProjectError for NamespacedExport { - fn description(&self) -> &str { - "Exports can only refer to unnamespaced names in the local namespace" - } - fn one_position(&self) -> Location { self.location.clone() } + fn description(&self) -> &str { "Only local names may be exported" } + fn one_position(&self) -> Location { self.0.clone() } } -pub struct GlobExport { - pub location: Location, -} +pub struct GlobExport(pub Location); impl ProjectError for GlobExport { - fn description(&self) -> &str { - "Exports can only refer to concrete names, globstars are not allowed" - } - fn one_position(&self) -> Location { self.location.clone() } + fn description(&self) -> &str { "Globstars are not allowed in exports" } + fn one_position(&self) -> Location { self.0.clone() } } -pub struct LexError { - pub errors: Vec>, - pub source: Rc, - pub file: VName, +pub struct NoStringEnd(pub Location); +impl ProjectError for NoStringEnd { + fn description(&self) -> &str { "A string literal was not closed with `\"`" } + fn one_position(&self) -> Location { self.0.clone() } } -impl ProjectError for LexError { - fn description(&self) -> &str { "An error occured during tokenization" } - fn positions(&self) -> BoxedIter { - let file = self.file.clone(); - Box::new(self.errors.iter().map(move |s| ErrorPosition { - location: Location::Range { - file: Rc::new(file.clone()), - range: s.span(), - source: self.source.clone(), - }, - message: Some(format!("{}", s)), - })) + +pub struct NoCommentEnd(pub Location); +impl ProjectError for NoCommentEnd { + fn description(&self) -> &str { "a comment was not closed with `]--`" } + fn one_position(&self) -> Location { self.0.clone() } +} + +pub struct FloatPlacehPrio(pub Location); +impl ProjectError for FloatPlacehPrio { + fn description(&self) -> &str { + "a placeholder priority has a decimal point or a negative exponent" } + fn one_position(&self) -> Location { self.0.clone() } +} + +pub struct NaNLiteral(pub Location); +impl ProjectError for NaNLiteral { + fn description(&self) -> &str { "float literal decoded to NaN" } + fn one_position(&self) -> Location { self.0.clone() } +} + +pub struct LiteralOverflow(pub Location); +impl ProjectError for LiteralOverflow { + fn description(&self) -> &str { "number literal described number greater than usize::MAX" } + fn one_position(&self) -> Location { self.0.clone() } +} + +pub struct ExpectedDigit(pub Location); +impl ProjectError for ExpectedDigit { + fn description(&self) -> &str { "expected a digit" } + fn one_position(&self) -> Location { self.0.clone() } +} + +pub struct NotHex(pub Location); +impl ProjectError for NotHex { + fn description(&self) -> &str { "Expected a hex digit" } + fn one_position(&self) -> Location { self.0.clone() } +} + +pub struct BadCodePoint(pub Location); +impl ProjectError for BadCodePoint { + fn description(&self) -> &str { + "\\uXXXX escape sequence does not describe valid code point" + } + fn one_position(&self) -> Location { self.0.clone() } +} + +pub struct BadEscapeSequence(pub Location); +impl ProjectError for BadEscapeSequence { + fn description(&self) -> &str { "Unrecognized escape sequence" } + fn one_position(&self) -> Location { self.0.clone() } } diff --git a/src/parse/facade.rs b/src/parse/facade.rs index fb9f3c1..481e0b8 100644 --- a/src/parse/facade.rs +++ b/src/parse/facade.rs @@ -1,31 +1,17 @@ -use std::rc::Rc; - -use chumsky::Parser; - use super::context::Context; -use super::errors::LexError; -use super::lexer; +use super::lexer::lex; use super::sourcefile::parse_module_body; use super::stream::Stream; use crate::error::{ParseErrorWithTokens, ProjectError, ProjectResult}; use crate::representations::sourcefile::FileEntry; -pub fn parse2(data: &str, ctx: impl Context) -> ProjectResult> { - let source = Rc::new(data.to_string()); - let lexie = lexer(ctx.clone(), source.clone()); - let tokens = (lexie.parse(data)).map_err(|errors| { - LexError { - errors, - file: ctx.file().as_ref().clone(), - source: source.clone(), - } - .rc() - })?; +pub fn parse2(ctx: impl Context) -> ProjectResult> { + let tokens = lex(vec![], ctx.source().as_str(), &ctx).expect("debug"); if tokens.is_empty() { Ok(Vec::new()) } else { - parse_module_body(Stream::from_slice(&tokens), ctx).map_err(|error| { - ParseErrorWithTokens { error, full_source: data.to_string(), tokens }.rc() + parse_module_body(Stream::from_slice(&tokens), &ctx).map_err(|error| { + ParseErrorWithTokens { error, full_source: ctx.source().to_string(), tokens }.rc() }) } } diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs index ee1a5a6..25e6856 100644 --- a/src/parse/lexer.rs +++ b/src/parse/lexer.rs @@ -1,24 +1,26 @@ -use std::fmt::{self, Display}; +use std::fmt::Display; use std::ops::Range; -use std::rc::Rc; +use std::sync::Arc; -use chumsky::prelude::*; -use chumsky::text::keyword; -use chumsky::Parser; use itertools::Itertools; use ordered_float::NotNan; +use super::LexerPlugin; use super::context::Context; -use super::decls::SimpleParser; -use super::number::print_nat16; -use super::{comment, name, number, placeholder, string}; +use super::errors::{FloatPlacehPrio, NoCommentEnd}; +use super::numeric::{parse_num, print_nat16, numstart}; use crate::ast::{PHClass, Placeholder}; +use crate::error::{ProjectResult, ProjectError}; +use crate::foreign::Atom; use crate::interner::Tok; -use crate::parse::operators::operators_parser; -use crate::representations::Literal; -use crate::{Interner, Location, VName}; +use crate::parse::numeric::{numchar, lex_numeric}; +use crate::parse::string::lex_string; +use crate::systems::stl::Numeric; +use crate::utils::pure_seq::next; +use crate::utils::unwrap_or; +use crate::{Location, VName}; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug)] pub struct Entry { pub lexeme: Lexeme, pub location: Location, @@ -32,14 +34,15 @@ impl Entry { #[must_use] pub fn is_keyword(&self) -> bool { - matches!( - self.lexeme, - Lexeme::Const - | Lexeme::Export - | Lexeme::Import - | Lexeme::Macro - | Lexeme::Module - ) + false + // matches!( + // self.lexeme, + // Lexeme::Const + // | Lexeme::Export + // | Lexeme::Import + // | Lexeme::Macro + // | Lexeme::Module + // ) } #[must_use] @@ -51,9 +54,13 @@ impl Entry { } #[must_use] - pub fn file(&self) -> Rc { + pub fn file(&self) -> Arc { self.location.file().expect("An Entry can only have a range location") } + + fn new(location: Location, lexeme: Lexeme) -> Self { + Self { lexeme, location } + } } impl Display for Entry { @@ -66,9 +73,9 @@ impl AsRef for Entry { fn as_ref(&self) -> &Location { &self.location } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug)] pub enum Lexeme { - Literal(Literal), + Atom(Atom), Name(Tok), Arrow(NotNan), /// Walrus operator (formerly shorthand macro) @@ -86,20 +93,19 @@ pub enum Lexeme { At, // Dot, Type, // type operator - Comment(Rc), - Export, - Import, - Module, - Macro, - Const, - Operators(Rc), + Comment(Arc), + // Export, + // Import, + // Module, + // Macro, + // Const, Placeh(Placeholder), } impl Display for Lexeme { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Literal(l) => write!(f, "{:?}", l), + Self::Atom(a) => write!(f, "{a:?}"), Self::Name(token) => write!(f, "{}", **token), Self::Walrus => write!(f, ":="), Self::Arrow(prio) => write!(f, "={}=>", print_nat16(*prio)), @@ -116,14 +122,11 @@ impl Display for Lexeme { Self::At => write!(f, "@"), Self::Type => write!(f, ":"), Self::Comment(text) => write!(f, "--[{}]--", text), - Self::Export => write!(f, "export"), - Self::Import => write!(f, "import"), - Self::Module => write!(f, "module"), - Self::Const => write!(f, "const"), - Self::Macro => write!(f, "macro"), - Self::Operators(ops) => { - write!(f, "operators[{}]", Interner::extern_all(ops).join(" ")) - }, + // Self::Export => write!(f, "export"), + // Self::Import => write!(f, "import"), + // Self::Module => write!(f, "module"), + // Self::Const => write!(f, "const"), + // Self::Macro => write!(f, "macro"), Self::Placeh(Placeholder { name, class }) => match *class { PHClass::Scalar => write!(f, "${}", **name), PHClass::Vec { nonzero, prio } => { @@ -147,97 +150,192 @@ impl Lexeme { ) } - #[must_use] - pub fn parser>( - self, - ) -> impl Parser + Clone { - filter(move |ent: &Entry| ent.lexeme == self) + pub fn strict_eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Arrow(f1), Self::Arrow(f2)) => f1 == f2, + (Self::At, Self::At) | (Self::BR, Self::BR) => true, + (Self::BS, Self::BS) /*| (Self::Const, Self::Const)*/ => true, + // (Self::Export, Self::Export) | (Self::Import, Self::Import) => true, + // (Self::Macro, Self::Macro) | (Self::Module, Self::Module) => true, + (Self::NS, Self::NS) | (Self::Type, Self::Type) => true, + (Self::Walrus, Self::Walrus) => true, + (Self::Atom(a1), Self::Atom(a2)) => a1.0.strict_eq(&a2.0), + (Self::Comment(c1), Self::Comment(c2)) => c1 == c2, + (Self::LP(p1), Self::LP(p2)) | (Self::RP(p1), Self::RP(p2)) => p1 == p2, + (Self::Name(n1), Self::Name(n2)) => n1 == n2, + (Self::Placeh(ph1), Self::Placeh(ph2)) => ph1 == ph2, + (_, _) => false, + } } } -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct LexedText(pub Vec); +#[allow(unused)] +pub fn format(lexed: &[Entry]) -> String { lexed.iter().join(" ") } -impl Display for LexedText { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.iter().join(" ")) +pub fn namechar(c: char) -> bool { c.is_alphanumeric() | (c == '_') } +pub fn namestart(c: char) -> bool { c.is_alphabetic() | (c == '_') } +pub fn opchar(c: char) -> bool { + !namestart(c) && !numstart(c) && !c.is_whitespace() && !"()[]{},".contains(c) +} + +pub fn split_filter( + s: &str, + mut pred: impl FnMut(char) -> bool, +) -> (&str, &str) { + s.find(|c| !pred(c)).map_or((s, ""), |i| s.split_at(i)) +} + +fn lit_table() -> impl IntoIterator { + [ + ("\\", Lexeme::BS), + ("@", Lexeme::At), + ("(", Lexeme::LP('(')), + ("[", Lexeme::LP('[')), + ("{", Lexeme::LP('{')), + (")", Lexeme::RP('(')), + ("]", Lexeme::RP('[')), + ("}", Lexeme::RP('{')), + ("\n", Lexeme::BR), + (":=", Lexeme::Walrus), + ("::", Lexeme::NS), + (":", Lexeme::Type), + ] +} + +static BUILTIN_ATOMS: &[&dyn LexerPlugin] = &[&lex_string, &lex_numeric]; + +pub fn lex( + mut tokens: Vec, + mut data: &str, + ctx: &impl Context, +) -> ProjectResult> { + let mut prev_len = data.len() + 1; + 'tail:loop { + if prev_len == data.len() { + panic!("got stuck at {data:?}, parsed {:?}", tokens.last().unwrap()); + } + prev_len = data.len(); + data = data.trim_start_matches(|c: char| c.is_whitespace() && c != '\n'); + let (head, _) = match next(data.chars()) { + Some((h, t)) => (h, t.as_str()), + None => return Ok(tokens), + }; + for lexer in ctx.lexers().iter().chain(BUILTIN_ATOMS.iter()) { + if let Some(res) = lexer(data, ctx) { + let (atom, tail) = res?; + if tail.len() == data.len() { + panic!("lexer plugin consumed 0 characters") + } + let loc = ctx.location(data.len() - tail.len(), tail); + tokens.push(Entry::new(loc, Lexeme::Atom(atom))); + data = tail; + continue 'tail; + } + } + for (prefix, lexeme) in lit_table() { + if let Some(tail) = data.strip_prefix(prefix) { + tokens.push(Entry::new(ctx.location(prefix.len(), tail), lexeme.clone())); + data = tail; + continue 'tail; + } + } + + if let Some(tail) = data.strip_prefix(',') { + let lexeme = Lexeme::Name(ctx.interner().i(",")); + tokens.push(Entry::new(ctx.location(1, tail), lexeme)); + data = tail; + continue 'tail; + } + if let Some(tail) = data.strip_prefix("--[") { + let (note, tail) = (tail.split_once("]--")) + .ok_or_else(|| NoCommentEnd(ctx.location(tail.len(), "")).rc())?; + let lexeme = Lexeme::Comment(Arc::new(note.to_string())); + let location = ctx.location(note.len() + 3, tail); + tokens.push(Entry::new(location, lexeme)); + data = tail; + continue 'tail; + } + if let Some(tail) = data.strip_prefix("--") { + let (note, tail) = split_filter(tail, |c| c != '\n'); + let lexeme = Lexeme::Comment(Arc::new(note.to_string())); + let location = ctx.location(note.len(), tail); + tokens.push(Entry::new(location, lexeme)); + data = tail; + continue 'tail; + } + if let Some(tail) = data.strip_prefix('=') { + if tail.chars().next().map_or(false, numstart) { + let (num, post_num) = split_filter(tail, numchar); + if let Some(tail) = post_num.strip_prefix("=>") { + let lexeme = Lexeme::Arrow(parse_num(num).map_err(|e| e.into_proj(num.len(), post_num, ctx))?.as_float()); + let location = ctx.location(num.len() + 3, tail); + tokens.push(Entry::new(location, lexeme)); + data = tail; + continue 'tail; + } + } + } + // todo: parse placeholders, don't forget vectorials! + if let Some(tail) = data.strip_prefix('$') { + let (name, tail) = split_filter(tail, namechar); + if !name.is_empty() { + let name = ctx.interner().i(name); + let location = ctx.location(name.len() + 1, tail); + let lexeme = Lexeme::Placeh(Placeholder { name, class: PHClass::Scalar }); + tokens.push(Entry::new(location, lexeme)); + data = tail; + continue 'tail; + } + } + if let Some(vec) = data.strip_prefix("..") { + let (nonzero, tail) = + vec.strip_prefix('.').map_or((false, vec), |t| (true, t)); + if let Some(tail) = tail.strip_prefix('$') { + let (name, tail) = split_filter(tail, namechar); + if !name.is_empty() { + let (prio, priolen, tail) = tail + .strip_prefix(':') + .map(|tail| split_filter(tail, numchar)) + .filter(|(num, _)| !num.is_empty()) + .map(|(num_str, tail)| { + parse_num(num_str) + .map_err(|e| e.into_proj(num_str.len(), tail, ctx)) + .and_then(|num| { + Ok(unwrap_or!(num => Numeric::Uint; { + return Err(FloatPlacehPrio(ctx.location(num_str.len(), tail)).rc()) + })) + }) + .map(|p| (p, num_str.len() + 1, tail)) + }) + .unwrap_or(Ok((0, 0, tail)))?; + let byte_len = if nonzero { 4 } else { 3 } + priolen + name.len(); + let name = ctx.interner().i(name); + let class = PHClass::Vec { nonzero, prio }; + let lexeme = Lexeme::Placeh(Placeholder { name, class }); + tokens.push(Entry::new(ctx.location(byte_len, tail), lexeme)); + data = tail; + continue 'tail; + } + } + } + if namestart(head) { + let (name, tail) = split_filter(data, namechar); + if !name.is_empty() { + let lexeme = Lexeme::Name(ctx.interner().i(name)); + tokens.push(Entry::new(ctx.location(name.len(), tail), lexeme)); + data = tail; + continue 'tail; + } + } + if opchar(head) { + let (name, tail) = split_filter(data, opchar); + if !name.is_empty() { + let lexeme = Lexeme::Name(ctx.interner().i(name)); + tokens.push(Entry::new(ctx.location(name.len(), tail), lexeme)); + data = tail; + continue 'tail; + } + } + unreachable!(r#"opchar is pretty much defined as "not namechar" "#) } } - -#[must_use] -fn paren_parser(lp: char, rp: char) -> impl SimpleParser { - just(lp).to(Lexeme::LP(lp)).or(just(rp).to(Lexeme::RP(lp))) -} - -#[must_use] -pub fn literal_parser<'a>( - ctx: impl Context + 'a, -) -> impl SimpleParser + 'a { - choice(( - // all ints are valid floats so it takes precedence - number::int_parser().map(Literal::Uint), - number::float_parser().map(Literal::Num), - string::str_parser() - .map(move |s| Literal::Str(ctx.interner().i(&s).into())), - )) -} - -pub static BASE_OPS: &[&str] = &[",", ".", "..", "...", "*"]; - -#[must_use] -pub fn lexer<'a>( - ctx: impl Context + 'a, - source: Rc, -) -> impl SimpleParser> + 'a { - let all_ops = ctx - .ops() - .iter() - .map(|op| op.as_ref()) - .chain(BASE_OPS.iter().cloned()) - .map(str::to_string) - .collect::>(); - choice(( - keyword("export").to(Lexeme::Export), - keyword("module").to(Lexeme::Module), - keyword("import").to(Lexeme::Import), - keyword("macro").to(Lexeme::Macro), - keyword("const").to(Lexeme::Const), - operators_parser({ - let ctx = ctx.clone(); - move |s| ctx.interner().i(&s) - }) - .map(|v| Lexeme::Operators(Rc::new(v))), - paren_parser('(', ')'), - paren_parser('[', ']'), - paren_parser('{', '}'), - just(":=").to(Lexeme::Walrus), - just("=") - .ignore_then(number::float_parser()) - .then_ignore(just("=>")) - .map(Lexeme::rule), - comment::comment_parser().map(|s| Lexeme::Comment(Rc::new(s))), - placeholder::placeholder_parser(ctx.clone()).map(Lexeme::Placeh), - just("::").to(Lexeme::NS), - just('\\').to(Lexeme::BS), - just('@').to(Lexeme::At), - just(':').to(Lexeme::Type), - just('\n').to(Lexeme::BR), - // just('.').to(Lexeme::Dot), - literal_parser(ctx.clone()).map(Lexeme::Literal), - name::name_parser(&all_ops).map({ - let ctx = ctx.clone(); - move |n| Lexeme::Name(ctx.interner().i(&n)) - }), - )) - .map_with_span(move |lexeme, range| Entry { - lexeme, - location: Location::Range { - range, - file: ctx.file(), - source: source.clone(), - }, - }) - .padded_by(one_of(" \t").repeated()) - .repeated() - .then_ignore(end()) -} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 4f97d9d..530ba41 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,19 +1,18 @@ -mod comment; mod context; -mod decls; mod errors; mod facade; mod lexer; mod multiname; -mod name; -mod number; -mod operators; -mod placeholder; +mod numeric; mod sourcefile; mod stream; mod string; -pub use context::ParsingContext; +pub use context::{ParsingContext, Context, LexerPlugin, LineParser}; pub use facade::parse2; -pub use lexer::{lexer, Entry, Lexeme}; -pub use number::{float_parser, int_parser, print_nat16}; +pub use lexer::{namechar, namestart, opchar, split_filter, Entry, Lexeme}; +pub use numeric::{ + lex_numeric, numchar, numstart, parse_num, print_nat16, NumError, + NumErrorKind, +}; +pub use string::{lex_string, parse_string, StringError, StringErrorKind}; diff --git a/src/parse/multiname.rs b/src/parse/multiname.rs index 765506d..d35d3d3 100644 --- a/src/parse/multiname.rs +++ b/src/parse/multiname.rs @@ -41,12 +41,12 @@ impl Subresult { } } -fn parse_multiname_branch( - cursor: Stream<'_>, - ctx: impl Context, -) -> ProjectResult<(BoxedIter, Stream<'_>)> { +fn parse_multiname_branch<'a>( + cursor: Stream<'a>, + ctx: &impl Context, +) -> ProjectResult<(BoxedIter<'a, Subresult>, Stream<'a>)> { let comma = ctx.interner().i(","); - let (subnames, cursor) = parse_multiname_rec(cursor, ctx.clone())?; + let (subnames, cursor) = parse_multiname_rec(cursor, ctx)?; let (delim, cursor) = cursor.trim().pop()?; match &delim.lexeme { Lexeme::Name(n) if n == &comma => { @@ -65,10 +65,10 @@ fn parse_multiname_branch( } } -fn parse_multiname_rec( - curosr: Stream<'_>, - ctx: impl Context, -) -> ProjectResult<(BoxedIter, Stream<'_>)> { +fn parse_multiname_rec<'a>( + curosr: Stream<'a>, + ctx: &impl Context, +) -> ProjectResult<(BoxedIter<'a, Subresult>, Stream<'a>)> { let star = ctx.interner().i("*"); let comma = ctx.interner().i(","); let (head, mut cursor) = curosr.trim().pop()?; @@ -103,7 +103,7 @@ fn parse_multiname_rec( Ok((box_once(Subresult::new_glob(head.location())), cursor)), Lexeme::Name(n) if ![comma, star].contains(n) => { let cursor = cursor.trim(); - if cursor.get(0).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) { + if cursor.get(0).map_or(false, |e| e.lexeme.strict_eq(&Lexeme::NS)) { let cursor = cursor.step()?; let (out, cursor) = parse_multiname_rec(cursor, ctx)?; let out = Box::new(out.map(|sr| sr.push_front(n.clone()))); @@ -123,10 +123,10 @@ fn parse_multiname_rec( } } -pub fn parse_multiname( - cursor: Stream<'_>, - ctx: impl Context, -) -> ProjectResult<(Vec, Stream<'_>)> { +pub fn parse_multiname<'a>( + cursor: Stream<'a>, + ctx: &impl Context, +) -> ProjectResult<(Vec, Stream<'a>)> { let (output, cont) = parse_multiname_rec(cursor, ctx)?; Ok((output.map(|sr| sr.finalize()).collect(), cont)) } diff --git a/src/parse/name.rs b/src/parse/name.rs deleted file mode 100644 index 14ff57f..0000000 --- a/src/parse/name.rs +++ /dev/null @@ -1,71 +0,0 @@ -use chumsky::prelude::*; -use chumsky::{self, Parser}; - -use super::decls::{BoxedSimpleParser, SimpleParser}; - -/// Matches any one of the passed operators, preferring longer ones -fn op_parser<'a>( - ops: &[impl AsRef + Clone], -) -> BoxedSimpleParser<'a, char, String> { - let mut sorted_ops: Vec = - ops.iter().map(|t| t.as_ref().to_string()).collect(); - sorted_ops.sort_by_key(|op| -(op.len() as i64)); - sorted_ops - .into_iter() - .map(|op| just(op).boxed()) - .reduce(|a, b| a.or(b).boxed()) - .unwrap_or_else(|| { - empty().map(|()| panic!("Empty isn't meant to match")).boxed() - }) - .labelled("operator") - .boxed() -} - -/// Characters that cannot be parsed as part of an operator -/// -/// The initial operator list overrides this. -pub static NOT_NAME_CHAR: &[char] = &[ - ':', // used for namespacing and type annotations - '\\', '@', // parametric expression starters - '"', // parsed as primitive and therefore would never match - '(', ')', '[', ']', '{', '}', // must be strictly balanced - '.', // Argument-body separator in parametrics - ',', // Import separator -]; - -/// Matches anything that's allowed as an operator -/// -/// FIXME: `@name` without a dot should be parsed correctly for overrides. -/// Could be an operator but then parametrics should take precedence, -/// which might break stuff. investigate. -/// -/// TODO: `.` could possibly be parsed as an operator in some contexts. -/// This operator is very common in maths so it's worth a try. -/// Investigate. -#[must_use] -pub fn anyop_parser<'a>() -> impl SimpleParser + 'a { - filter(move |c| { - !NOT_NAME_CHAR.contains(c) - && !c.is_whitespace() - && !c.is_alphanumeric() - && c != &'_' - }) - .repeated() - .at_least(1) - .collect() - .labelled("anyop") -} - -/// Parse an operator or name. Failing both, parse everything up to -/// the next whitespace or blacklisted character as a new operator. -#[must_use] -pub fn name_parser<'a>( - ops: &[impl AsRef + Clone], -) -> impl SimpleParser + 'a { - choice(( - op_parser(ops), // First try to parse a known operator - text::ident().labelled("plain text"), // Failing that, parse plain text - anyop_parser(), // Finally parse everything until tne next forbidden char - )) - .labelled("name") -} diff --git a/src/parse/number.rs b/src/parse/number.rs deleted file mode 100644 index 117a5dc..0000000 --- a/src/parse/number.rs +++ /dev/null @@ -1,140 +0,0 @@ -use chumsky::prelude::*; -use chumsky::{self, Parser}; -use ordered_float::NotNan; - -use super::decls::SimpleParser; - -fn assert_not_digit(base: u32, c: char) { - if base > (10 + (c as u32 - 'a' as u32)) { - panic!("The character '{}' is a digit in base ({})", c, base) - } -} - -/// Parse an arbitrarily grouped sequence of digits starting with an underscore. -/// -/// TODO: this should use separated_by and parse the leading group too -#[must_use] -fn separated_digits_parser(base: u32) -> impl SimpleParser { - just('_') - .ignore_then(text::digits(base)) - .repeated() - .map(|sv| sv.iter().flat_map(|s| s.chars()).collect()) -} - -/// parse a grouped uint -/// -/// Not to be confused with [int_parser] which does a lot more -#[must_use] -fn uint_parser(base: u32) -> impl SimpleParser { - text::int(base).then(separated_digits_parser(base)).map( - move |(s1, s2): (String, String)| { - u64::from_str_radix(&(s1 + &s2), base).unwrap() - }, - ) -} - -/// parse exponent notation, or return 0 as the default exponent. -/// The exponent is always in decimal. -#[must_use] -fn pow_parser() -> impl SimpleParser { - choice(( - just('p').ignore_then(text::int(10)).map(|s: String| s.parse().unwrap()), - just("p-") - .ignore_then(text::int(10)) - .map(|s: String| -s.parse::().unwrap()), - )) - .or_else(|_| Ok(0)) -} - -/// returns a mapper that converts a mantissa and an exponent into an uint -/// -/// TODO it panics if it finds a negative exponent -fn nat2u(base: u64) -> impl Fn((u64, i32)) -> u64 { - move |(val, exp)| { - if exp == 0 { - val - } else { - val * base.checked_pow(exp.try_into().unwrap()).unwrap() - } - } -} - -/// returns a mapper that converts a mantissa and an exponent into a float -fn nat2f(base: u64) -> impl Fn((NotNan, i32)) -> NotNan { - move |(val, exp)| { - if exp == 0 { - val - } else { - val * (base as f64).powf(exp.try_into().unwrap()) - } - } -} - -/// parse an uint from exponential notation (panics if 'p' is a digit in base) -#[must_use] -fn pow_uint_parser(base: u32) -> impl SimpleParser { - assert_not_digit(base, 'p'); - uint_parser(base).then(pow_parser()).map(nat2u(base.into())) -} - -/// parse an uint from a base determined by its prefix or lack thereof -/// -/// Not to be confused with [uint_parser] which is a component of it. -#[must_use] -pub fn int_parser() -> impl SimpleParser { - choice(( - just("0b").ignore_then(pow_uint_parser(2)), - just("0x").ignore_then(pow_uint_parser(16)), - just('0').ignore_then(pow_uint_parser(8)), - pow_uint_parser(10), // Dec has no prefix - )) -} - -/// parse a float from dot notation -#[must_use] -fn dotted_parser(base: u32) -> impl SimpleParser> { - uint_parser(base) - .then( - just('.') - .ignore_then(text::digits(base).then(separated_digits_parser(base))) - .map(move |(frac1, frac2)| { - let frac = frac1 + &frac2; - let frac_num = u64::from_str_radix(&frac, base).unwrap() as f64; - let dexp = base.pow(frac.len().try_into().unwrap()); - frac_num / dexp as f64 - }) - .or_not() - .map(|o| o.unwrap_or_default()), - ) - .try_map(|(wh, f), s| { - NotNan::new(wh as f64 + f) - .map_err(|_| Simple::custom(s, "Float literal evaluates to NaN")) - }) -} - -/// parse a float from dotted and optionally also exponential notation -#[must_use] -fn pow_float_parser(base: u32) -> impl SimpleParser> { - assert_not_digit(base, 'p'); - dotted_parser(base).then(pow_parser()).map(nat2f(base.into())) -} - -/// parse a float with dotted and optionally exponential notation from a base -/// determined by its prefix -#[must_use] -pub fn float_parser() -> impl SimpleParser> { - choice(( - just("0b").ignore_then(pow_float_parser(2)), - just("0x").ignore_then(pow_float_parser(16)), - just('0').ignore_then(pow_float_parser(8)), - pow_float_parser(10), - )) - .labelled("float") -} - -#[must_use] -pub fn print_nat16(num: NotNan) -> String { - let exp = num.log(16.0).floor(); - let man = num / 16_f64.powf(exp); - format!("{man}p{exp:.0}") -} diff --git a/src/parse/numeric.rs b/src/parse/numeric.rs new file mode 100644 index 0000000..cef05b8 --- /dev/null +++ b/src/parse/numeric.rs @@ -0,0 +1,148 @@ +use std::num::IntErrorKind; +use std::ops::Range; +use std::rc::Rc; + +use ordered_float::NotNan; + +use super::context::Context; +use super::errors::NaNLiteral; +use super::lexer::split_filter; +use crate::error::{ProjectError, ProjectResult}; +use crate::foreign::Atom; +use crate::systems::stl::Numeric; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum NumErrorKind { + NaN, + Overflow, + InvalidDigit, +} +impl NumErrorKind { + fn from_int(kind: &IntErrorKind) -> Self { + match kind { + IntErrorKind::InvalidDigit => Self::InvalidDigit, + IntErrorKind::NegOverflow | IntErrorKind::PosOverflow => Self::Overflow, + _ => panic!("Impossible error condition"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NumError { + pub range: Range, + pub kind: NumErrorKind, +} + +impl NumError { + pub fn into_proj( + self, + len: usize, + tail: &str, + ctx: &(impl Context + ?Sized), + ) -> Rc { + let start = ctx.source().len() - tail.len() - len + self.range.start; + let location = ctx.range_loc(start..start + self.range.len()); + match self.kind { + NumErrorKind::NaN => NaNLiteral(location).rc(), + _ => panic!(), + // NumErrorKind::Int(iek) => IntError(location, iek).rc(), + } + } +} + +pub fn parse_num(string: &str) -> Result { + let overflow_err = + NumError { range: 0..string.len(), kind: NumErrorKind::Overflow }; + let (radix, noprefix, pos) = + (string.strip_prefix("0x").map(|s| (16u8, s, 2))) + .or_else(|| string.strip_prefix("0b").map(|s| (2u8, s, 2))) + .or_else(|| string.strip_prefix("0o").map(|s| (8u8, s, 2))) + .unwrap_or((10u8, string, 0)); + // identity + let (base, exponent) = match noprefix.split_once('p') { + Some((b, e)) => { + let (s, d, len) = e.strip_prefix('-').map_or((1, e, 0), |ue| (-1, ue, 1)); + (b, s * int_parse(d, radix, pos + b.len() + 1 + len)? as i32) + }, + None => (noprefix, 0), + }; + match base.split_once('.') { + None => { + let base_usize = int_parse(base, radix, pos)?; + if let Ok(pos_exp) = u32::try_from(exponent) { + if let Some(radical) = usize::from(radix).checked_pow(pos_exp) { + let number = base_usize.checked_mul(radical).ok_or(overflow_err)?; + return Ok(Numeric::Uint(number)); + } + } + let f = (base_usize as f64) * (radix as f64).powi(exponent); + let err = NumError { range: 0..string.len(), kind: NumErrorKind::NaN }; + Ok(Numeric::Float(NotNan::new(f).map_err(|_| err)?)) + }, + Some((whole, part)) => { + let whole_n = int_parse(whole, radix, pos)? as f64; + let part_n = int_parse(part, radix, pos + whole.len() + 1)? as f64; + let real_val = whole_n + (part_n / radix.pow(part.len() as u32) as f64); + let f = real_val * (radix as f64).powi(exponent); + Ok(Numeric::Float(NotNan::new(f).expect("None of the inputs are NaN"))) + }, + } +} + +fn int_parse(s: &str, radix: u8, start: usize) -> Result { + let s = s.chars().filter(|c| *c != '_').collect::(); + let range = start..(start + s.len()); + usize::from_str_radix(&s, radix as u32) + .map_err(|e| NumError { range, kind: NumErrorKind::from_int(e.kind()) }) +} + +pub fn numchar(c: char) -> bool { c.is_alphanumeric() | "._-".contains(c) } +pub fn numstart(c: char) -> bool { c.is_ascii_digit() } + +pub fn lex_numeric<'a>( + data: &'a str, + ctx: &dyn Context, +) -> Option> { + data.chars().next().filter(|c| numstart(*c)).map(|_| { + let (num_str, tail) = split_filter(data, numchar); + match parse_num(num_str) { + Ok(Numeric::Float(f)) => Ok((Atom::new(f), tail)), + Ok(Numeric::Uint(i)) => Ok((Atom::new(i), tail)), + Err(e) => Err(e.into_proj(num_str.len(), tail, ctx)), + } + }) +} + +#[cfg(test)] +mod test { + use crate::parse::numeric::parse_num; + use crate::systems::stl::Numeric; + + #[test] + fn just_ints() { + let test = |s, n| assert_eq!(parse_num(s), Ok(Numeric::Uint(n))); + test("12345", 12345); + test("0xcafebabe", 0xcafebabe); + test("0o751", 0o751); + test("0b111000111", 0b111000111); + } + + #[test] + fn decimals() { + let test = |s, n| assert_eq!(parse_num(s).map(|n| n.as_f64()), Ok(n)); + test("3.1417", 3.1417); + test("3.1417", 3_f64 + 1417_f64 / 10000_f64); + test("0xf.cafe", 0xf as f64 + 0xcafe as f64 / 0x10000 as f64); + test("34p3", 34000f64); + test("0x2p3", (0x2 * 0x1000) as f64); + test("1.5p3", 1500f64); + test("0x2.5p3", (0x25 * 0x100) as f64); + } +} + +#[must_use] +pub fn print_nat16(num: NotNan) -> String { + let exp = num.log(16.0).floor(); + let man = num / 16_f64.powf(exp); + format!("{man}p{exp:.0}") +} diff --git a/src/parse/operators.rs b/src/parse/operators.rs deleted file mode 100644 index a140b06..0000000 --- a/src/parse/operators.rs +++ /dev/null @@ -1,32 +0,0 @@ -use chumsky::prelude::*; - -use super::decls::SimpleParser; - -#[must_use] -pub fn operators_parser( - f: impl Fn(String) -> T, -) -> impl SimpleParser> { - filter(|c: &char| c != &']' && !c.is_whitespace()) - .repeated() - .at_least(1) - .collect() - .map(f) - .separated_by(text::whitespace()) - .allow_leading() - .allow_trailing() - .at_least(1) - .delimited_by(just("operators["), just(']')) -} - -#[cfg(test)] -mod test { - use chumsky::Parser; - - use super::operators_parser; - - #[test] - fn operators_scratchpad() { - let parsely = operators_parser(|s| s); - println!("{:?}", parsely.parse("operators[$ |> =>]")) - } -} diff --git a/src/parse/placeholder.rs b/src/parse/placeholder.rs deleted file mode 100644 index 947a995..0000000 --- a/src/parse/placeholder.rs +++ /dev/null @@ -1,31 +0,0 @@ -use chumsky::prelude::*; -use chumsky::Parser; - -use super::context::Context; -use super::decls::SimpleParser; -use super::number::int_parser; -use crate::ast::{PHClass, Placeholder}; - -#[must_use] -pub fn placeholder_parser( - ctx: impl Context, -) -> impl SimpleParser { - choice(( - just("...").to(Some(true)), - just("..").to(Some(false)), - empty().to(None), - )) - .then(just("$").ignore_then(text::ident())) - .then(just(":").ignore_then(int_parser()).or_not()) - .try_map(move |((vec_nonzero, name), vec_prio), span| { - let name = ctx.interner().i(&name); - if let Some(nonzero) = vec_nonzero { - let prio = vec_prio.unwrap_or_default(); - Ok(Placeholder { name, class: PHClass::Vec { nonzero, prio } }) - } else if vec_prio.is_some() { - Err(Simple::custom(span, "Scalar placeholders have no priority")) - } else { - Ok(Placeholder { name, class: PHClass::Scalar }) - } - }) -} diff --git a/src/parse/sourcefile.rs b/src/parse/sourcefile.rs index d5a7cc3..069e14b 100644 --- a/src/parse/sourcefile.rs +++ b/src/parse/sourcefile.rs @@ -18,7 +18,6 @@ use crate::representations::location::Location; use crate::representations::sourcefile::{FileEntry, MemberKind, ModuleBlock}; use crate::representations::VName; use crate::sourcefile::{FileEntryKind, Import, Member}; -use crate::Primitive; pub fn split_lines(module: Stream<'_>) -> impl Iterator> { let mut source = module.data.iter().enumerate(); @@ -52,36 +51,44 @@ pub fn split_lines(module: Stream<'_>) -> impl Iterator> { pub fn parse_module_body( cursor: Stream<'_>, - ctx: impl Context, + ctx: &impl Context, ) -> ProjectResult> { split_lines(cursor) .map(Stream::trim) .filter(|l| !l.data.is_empty()) .map(|l| { - Ok(FileEntry { - locations: vec![l.location()], - kind: parse_line(l, ctx.clone())?, + parse_line(l, ctx).map(move |kinds| { + kinds + .into_iter() + .map(move |kind| FileEntry { locations: vec![l.location()], kind }) }) }) + .flatten_ok() .collect() } pub fn parse_line( cursor: Stream<'_>, - ctx: impl Context, -) -> ProjectResult { - match cursor.get(0)?.lexeme { + ctx: &impl Context, +) -> ProjectResult> { + for line_parser in ctx.line_parsers() { + if let Some(result) = line_parser(cursor, ctx) { + return result; + } + } + match &cursor.get(0)?.lexeme { Lexeme::BR | Lexeme::Comment(_) => parse_line(cursor.step()?, ctx), - Lexeme::Export => parse_export_line(cursor.step()?, ctx), - Lexeme::Const | Lexeme::Macro | Lexeme::Module | Lexeme::Operators(_) => - Ok(FileEntryKind::Member(Member { + Lexeme::Name(n) if **n == "export" => + parse_export_line(cursor.step()?, ctx).map(|k| vec![k]), + Lexeme::Name(n) if ["const", "macro", "module"].contains(&n.as_str()) => + Ok(vec![FileEntryKind::Member(Member { kind: parse_member(cursor, ctx)?, exported: false, - })), - Lexeme::Import => { + })]), + Lexeme::Name(n) if **n == "import" => { let (imports, cont) = parse_multiname(cursor.step()?, ctx)?; cont.expect_empty()?; - Ok(FileEntryKind::Import(imports)) + Ok(vec![FileEntryKind::Import(imports)]) }, _ => { let err = BadTokenInRegion { @@ -95,23 +102,23 @@ pub fn parse_line( pub fn parse_export_line( cursor: Stream<'_>, - ctx: impl Context, + ctx: &impl Context, ) -> ProjectResult { let cursor = cursor.trim(); - match cursor.get(0)?.lexeme { + match &cursor.get(0)?.lexeme { Lexeme::NS => { let (names, cont) = parse_multiname(cursor.step()?, ctx)?; cont.expect_empty()?; let names = (names.into_iter()) .map(|Import { name, path, location }| match (name, &path[..]) { (Some(n), []) => Ok((n, location)), - (None, _) => Err(GlobExport { location }.rc()), - _ => Err(NamespacedExport { location }.rc()), + (None, _) => Err(GlobExport(location).rc()), + _ => Err(NamespacedExport(location).rc()), }) .collect::, _>>()?; Ok(FileEntryKind::Export(names)) }, - Lexeme::Const | Lexeme::Macro | Lexeme::Module | Lexeme::Operators(_) => + Lexeme::Name(n) if ["const", "macro", "module"].contains(&n.as_str()) => Ok(FileEntryKind::Member(Member { kind: parse_member(cursor, ctx)?, exported: true, @@ -128,26 +135,22 @@ pub fn parse_export_line( fn parse_member( cursor: Stream<'_>, - ctx: impl Context, + ctx: &impl Context, ) -> ProjectResult { let (typemark, cursor) = cursor.trim().pop()?; match &typemark.lexeme { - Lexeme::Const => { + Lexeme::Name(n) if **n == "const" => { let constant = parse_const(cursor, ctx)?; Ok(MemberKind::Constant(constant)) }, - Lexeme::Macro => { + Lexeme::Name(n) if **n == "macro" => { let rule = parse_rule(cursor, ctx)?; Ok(MemberKind::Rule(rule)) }, - Lexeme::Module => { + Lexeme::Name(n) if **n == "module" => { let module = parse_module(cursor, ctx)?; Ok(MemberKind::Module(module)) }, - Lexeme::Operators(ops) => { - cursor.trim().expect_empty()?; - Ok(MemberKind::Operators(ops[..].to_vec())) - }, _ => { let err = BadTokenInRegion { entry: typemark.clone(), region: "member type" }; @@ -158,20 +161,20 @@ fn parse_member( fn parse_rule( cursor: Stream<'_>, - ctx: impl Context, + ctx: &impl Context, ) -> ProjectResult> { let (pattern, prio, template) = cursor.find_map("arrow", |a| match a { Lexeme::Arrow(p) => Some(*p), _ => None, })?; - let (pattern, _) = parse_exprv(pattern, None, ctx.clone())?; + let (pattern, _) = parse_exprv(pattern, None, ctx)?; let (template, _) = parse_exprv(template, None, ctx)?; Ok(Rule { pattern, prio, template }) } fn parse_const( cursor: Stream<'_>, - ctx: impl Context, + ctx: &impl Context, ) -> ProjectResult { let (name_ent, cursor) = cursor.trim().pop()?; let name = ExpectedName::expect(name_ent)?; @@ -183,7 +186,7 @@ fn parse_const( fn parse_module( cursor: Stream<'_>, - ctx: impl Context, + ctx: &impl Context, ) -> ProjectResult { let (name_ent, cursor) = cursor.trim().pop()?; let name = ExpectedName::expect(name_ent)?; @@ -195,11 +198,11 @@ fn parse_module( Ok(ModuleBlock { name, body }) } -fn parse_exprv( - mut cursor: Stream<'_>, +fn parse_exprv<'a>( + mut cursor: Stream<'a>, paren: Option, - ctx: impl Context, -) -> ProjectResult<(Vec>, Stream<'_>)> { + ctx: &impl Context, +) -> ProjectResult<(Vec>, Stream<'a>)> { let mut output = Vec::new(); cursor = cursor.trim(); while let Ok(current) = cursor.get(0) { @@ -207,11 +210,9 @@ fn parse_exprv( Lexeme::BR | Lexeme::Comment(_) => unreachable!("Fillers skipped"), Lexeme::At | Lexeme::Type => return Err(ReservedToken { entry: current.clone() }.rc()), - Lexeme::Literal(l) => { - output.push(Expr { - value: Clause::P(Primitive::Literal(l.clone())), - location: current.location(), - }); + Lexeme::Atom(a) => { + let value = Clause::Atom(a.clone()); + output.push(Expr { value, location: current.location() }); cursor = cursor.step()?; }, Lexeme::Placeh(ph) => { @@ -223,25 +224,23 @@ fn parse_exprv( }, Lexeme::Name(n) => { let location = cursor.location(); - let mut fullname = vec![n.clone()]; - while cursor.get(1).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) { + let mut fullname: VName = vec![n.clone()]; + while cursor.get(1).map_or(false, |e| e.lexeme.strict_eq(&Lexeme::NS)) { fullname.push(ExpectedName::expect(cursor.get(2)?)?); cursor = cursor.step()?.step()?; } output.push(Expr { value: Clause::Name(fullname), location }); cursor = cursor.step()?; }, - Lexeme::NS => - return Err(LeadingNS { location: current.location() }.rc()), + Lexeme::NS => return Err(LeadingNS(current.location()).rc()), Lexeme::RP(c) => return if Some(*c) == paren { Ok((output, cursor.step()?)) } else { - Err(MisalignedParen { entry: cursor.get(0)?.clone() }.rc()) + Err(MisalignedParen(cursor.get(0)?.clone()).rc()) }, Lexeme::LP(c) => { - let (result, leftover) = - parse_exprv(cursor.step()?, Some(*c), ctx.clone())?; + let (result, leftover) = parse_exprv(cursor.step()?, Some(*c), ctx)?; output.push(Expr { value: Clause::S(*c, Rc::new(result)), location: cursor.get(0)?.location().to(leftover.fallback.location()), @@ -250,9 +249,9 @@ fn parse_exprv( }, Lexeme::BS => { let dot = ctx.interner().i("."); - let (arg, body) = - cursor.step()?.find("A '.'", |l| l == &Lexeme::Name(dot.clone()))?; - let (arg, _) = parse_exprv(arg, None, ctx.clone())?; + let (arg, body) = (cursor.step())? + .find("A '.'", |l| l.strict_eq(&Lexeme::Name(dot.clone())))?; + let (arg, _) = parse_exprv(arg, None, ctx)?; let (body, leftover) = parse_exprv(body, paren, ctx)?; output.push(Expr { location: cursor.location(), @@ -278,7 +277,7 @@ fn vec_to_single( v: Vec>, ) -> ProjectResult> { match v.len() { - 0 => return Err(UnexpectedEOL { entry: fallback.clone() }.rc()), + 0 => Err(UnexpectedEOL { entry: fallback.clone() }.rc()), 1 => Ok(v.into_iter().exactly_one().unwrap()), _ => Ok(Expr { location: expr_slice_location(&v), diff --git a/src/parse/string.rs b/src/parse/string.rs index 371b22c..63175c0 100644 --- a/src/parse/string.rs +++ b/src/parse/string.rs @@ -1,50 +1,117 @@ -use chumsky::prelude::*; -use chumsky::{self, Parser}; +use itertools::Itertools; -use super::decls::SimpleParser; +use super::context::Context; +use super::errors::{BadCodePoint, BadEscapeSequence, NoStringEnd, NotHex}; +use crate::error::{ProjectError, ProjectResult}; +use crate::foreign::Atom; +use crate::OrcString; -/// Parses a text character that is not the specified delimiter -#[must_use] -fn text_parser(delim: char) -> impl SimpleParser { - // Copied directly from Chumsky's JSON example. - let escape = just('\\').ignore_then( - just('\\') - .or(just('/')) - .or(just('"')) - .or(just('b').to('\x08')) - .or(just('f').to('\x0C')) - .or(just('n').to('\n')) - .or(just('r').to('\r')) - .or(just('t').to('\t')) - .or( - just('u').ignore_then( - filter(|c: &char| c.is_ascii_hexdigit()) - .repeated() - .exactly(4) - .collect::() - .validate(|digits, span, emit| { - char::from_u32(u32::from_str_radix(&digits, 16).unwrap()) - .unwrap_or_else(|| { - emit(Simple::custom(span, "invalid unicode character")); - '\u{FFFD}' // unicode replacement character - }) - }), - ), - ), - ); - filter(move |&c| c != '\\' && c != delim).or(escape) +pub enum StringErrorKind { + NotHex, + BadCodePoint, + BadEscSeq, } -/// Parse a string between double quotes -#[must_use] -pub fn str_parser() -> impl SimpleParser { - just('"') - .ignore_then( - text_parser('"').map(Some) - .or(just("\\\n").then(just(' ').or(just('\t')).repeated()).map(|_| None)) // Newlines preceded by backslashes are ignored along with all following indentation. - .repeated(), - ) - .then_ignore(just('"')) - .flatten() - .collect() +pub struct StringError { + pos: usize, + kind: StringErrorKind, +} + +pub fn parse_string(str: &str) -> Result { + let mut target = String::new(); + let mut iter = str.char_indices(); + while let Some((_, c)) = iter.next() { + if c != '\\' { + target.push(c); + continue; + } + let (mut pos, code) = iter.next().expect("lexer would have continued"); + let next = match code { + c @ ('\\' | '/' | '"') => c, + 'b' => '\x08', + 'f' => '\x0f', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + '\n' => 'skipws: loop { + match iter.next() { + None => return Ok(target), + Some((_, c)) => + if !c.is_whitespace() { + break 'skipws c; + }, + } + }, + 'u' => { + let acc = ((0..4).rev()) + .map(|radical| { + let (j, c) = (iter.next()) + .ok_or(StringError { pos, kind: StringErrorKind::NotHex })?; + pos = j; + let b = + u32::from_str_radix(&String::from(c), 16).map_err(|_| { + StringError { pos, kind: StringErrorKind::NotHex } + })?; + Ok(16u32.pow(radical) + b) + }) + .fold_ok(0, u32::wrapping_add)?; + char::from_u32(acc) + .ok_or(StringError { pos, kind: StringErrorKind::BadCodePoint })? + }, + _ => return Err(StringError { pos, kind: StringErrorKind::BadEscSeq }), + }; + target.push(next); + } + Ok(target) +} + +pub fn lex_string<'a>( + data: &'a str, + ctx: &dyn Context, +) -> Option> { + data.strip_prefix('"').map(|data| { + let mut leftover = data; + return loop { + let (inside, outside) = (leftover.split_once('"')) + .ok_or_else(|| NoStringEnd(ctx.location(data.len(), "")).rc())?; + let backslashes = inside.chars().rev().take_while(|c| *c == '\\').count(); + if backslashes % 2 == 0 { + // cut form tail to recoup what string_content doesn't have + let (string_data, tail) = data.split_at(data.len() - outside.len() - 1); + let tail = &tail[1..]; // push the tail past the end quote + let string = parse_string(string_data).map_err(|e| { + let start = ctx.pos(data) + e.pos; + let location = ctx.range_loc(start..start + 1); + match e.kind { + StringErrorKind::NotHex => NotHex(location).rc(), + StringErrorKind::BadCodePoint => BadCodePoint(location).rc(), + StringErrorKind::BadEscSeq => BadEscapeSequence(location).rc(), + } + })?; + let tok = ctx.interner().i(&string); + break Ok((Atom::new(OrcString::from(tok)), tail)); + } else { + leftover = outside; + } + }; + }) +} +// TODO: rewrite the tree building pipeline step to load files + +#[cfg(test)] +mod test { + use super::lex_string; + use crate::parse::context::MockContext; + use crate::{Interner, OrcString}; + + #[test] + fn plain_string() { + let source = r#""hello world!" - says the programmer"#; + let i = Interner::new(); + let (data, tail) = lex_string(source, &MockContext(&i)) + .expect("the snippet starts with a quote") + .expect("it contains a valid string"); + assert_eq!(data.try_downcast::().unwrap().as_str(), "hello world!"); + assert_eq!(tail, " - says the programmer"); + } } diff --git a/src/pipeline/dealias/resolve_aliases.rs b/src/pipeline/dealias/resolve_aliases.rs index f150741..15dffbd 100644 --- a/src/pipeline/dealias/resolve_aliases.rs +++ b/src/pipeline/dealias/resolve_aliases.rs @@ -8,7 +8,7 @@ use crate::representations::project::{ ItemKind, ProjectExt, ProjectItem, ProjectMod, }; use crate::tree::{ModEntry, ModMember, Module}; -use crate::utils::pure_push::pushed; +use crate::utils::pure_seq::pushed; use crate::{Interner, ProjectTree, Tok, VName}; #[must_use] @@ -74,7 +74,6 @@ fn resolve_aliases_rec( ModMember::Sub(module) => ModMember::Sub(resolve_aliases_rec(root, module, updated, false)), ModMember::Item(item) => ModMember::Item(ProjectItem { - is_op: item.is_op, kind: match &item.kind { ItemKind::Const(value) => ItemKind::Const(process_expr(value)), other => other.clone(), diff --git a/src/pipeline/file_loader.rs b/src/pipeline/file_loader.rs index 8221b23..9e7e857 100644 --- a/src/pipeline/file_loader.rs +++ b/src/pipeline/file_loader.rs @@ -1,6 +1,6 @@ //! Source loader callback definition and builtin implementations use std::path::{Path, PathBuf}; -use std::rc::Rc; +use std::sync::Arc; use std::{fs, io}; use hashbrown::{HashMap, HashSet}; @@ -25,7 +25,7 @@ impl ProjectError for FileLoadingError { "Neither a file nor a directory could be read from the requested path" } fn one_position(&self) -> crate::Location { - Location::File(Rc::new(self.path.clone())) + Location::File(Arc::new(self.path.clone())) } fn message(&self) -> String { format!("File: {}\nDirectory: {}", self.file, self.dir) @@ -37,10 +37,10 @@ impl ProjectError for FileLoadingError { #[derive(Clone, PartialEq, Eq, Hash)] pub enum Loaded { /// Conceptually equivalent to a sourcefile - Code(Rc), + Code(Arc), /// Conceptually equivalent to the list of *.orc files in a folder, without /// the extension - Collection(Rc>), + Collection(Arc>), } impl Loaded { /// Is the loaded item source code (not a collection)? @@ -56,7 +56,7 @@ pub fn load_file(root: &Path, path: &[Tok]) -> IOResult { let full_path = path.iter().fold(root.to_owned(), |p, t| p.join(t.as_str())); let file_path = full_path.with_extension("orc"); let file_error = match fs::read_to_string(file_path) { - Ok(string) => return Ok(Loaded::Code(Rc::new(string))), + Ok(string) => return Ok(Loaded::Code(Arc::new(string))), Err(err) => err, }; let dir = match fs::read_dir(&full_path) { @@ -83,7 +83,7 @@ pub fn load_file(root: &Path, path: &[Tok]) -> IOResult { }) }) .collect(); - Ok(Loaded::Collection(Rc::new(names))) + Ok(Loaded::Collection(Arc::new(names))) } /// Generates a cached file loader for a directory @@ -102,7 +102,7 @@ pub fn load_embed(path: &str, ext: &str) -> IOResult { if let Some(file) = T::get(&file_path) { let s = String::from_utf8(file.data.to_vec()).expect("Embed must be valid UTF-8"); - Ok(Loaded::Code(Rc::new(s))) + Ok(Loaded::Code(Arc::new(s))) } else { let entries = T::iter() .map(|c| c.to_string()) @@ -121,7 +121,7 @@ pub fn load_embed(path: &str, ext: &str) -> IOResult { }) }) .collect::>(); - Ok(Loaded::Collection(Rc::new(entries))) + Ok(Loaded::Collection(Arc::new(entries))) } } @@ -165,9 +165,9 @@ pub fn embed_to_map( } } (files.into_iter()) - .map(|(k, s)| (k, Loaded::Code(Rc::new(s)))) + .map(|(k, s)| (k, Loaded::Code(Arc::new(s)))) .chain((dirs.into_iter()).map(|(k, entv)| { - (k, Loaded::Collection(Rc::new(entv.into_iter().collect()))) + (k, Loaded::Collection(Arc::new(entv.into_iter().collect()))) })) .collect() } diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index bca79a4..c31393b 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -5,5 +5,6 @@ mod import_abs_path; mod parse_layer; mod project_tree; mod source_loader; +// mod tree_loader; pub use parse_layer::parse_layer; diff --git a/src/pipeline/parse_layer.rs b/src/pipeline/parse_layer.rs index ffea7bb..81e4a8d 100644 --- a/src/pipeline/parse_layer.rs +++ b/src/pipeline/parse_layer.rs @@ -3,6 +3,7 @@ use super::file_loader::IOResult; use super::{project_tree, source_loader}; use crate::error::ProjectResult; use crate::interner::{Interner, Tok}; +use crate::parse::{LexerPlugin, LineParser}; use crate::representations::sourcefile::FileEntry; use crate::representations::VName; use crate::utils::never; @@ -21,10 +22,14 @@ pub fn parse_layer<'a>( loader: &impl Fn(&[Tok]) -> IOResult, environment: &'a ProjectTree, prelude: &[FileEntry], + lexer_plugins: &[&dyn LexerPlugin], + line_parsers: &[&dyn LineParser], i: &Interner, ) -> ProjectResult> { + let sl_ctx = + source_loader::Context { prelude, i, lexer_plugins, line_parsers }; let (preparsed, source) = - source_loader::load_source(targets, prelude, i, loader, &|path| { + source_loader::load_source(targets, sl_ctx, loader, &|path| { environment.0.walk_ref(&[], path, false).is_ok() })?; let tree = diff --git a/src/pipeline/project_tree/build_tree.rs b/src/pipeline/project_tree/build_tree.rs index 96360fb..3f3e9ed 100644 --- a/src/pipeline/project_tree/build_tree.rs +++ b/src/pipeline/project_tree/build_tree.rs @@ -13,7 +13,7 @@ use crate::sourcefile::{ }; use crate::tree::{ModEntry, ModMember, Module}; use crate::utils::get_or::get_or_default; -use crate::utils::pure_push::pushed_ref; +use crate::utils::pure_seq::pushed_ref; use crate::{Tok, VName}; #[must_use = "A submodule may not be integrated into the tree"] @@ -28,7 +28,7 @@ pub struct TreeReport { pub fn build_tree( path: &VName, source: Vec, - Module { entries, extra }: PreMod, + Module { entries, .. }: PreMod, imports: ImpMod, prelude: &[FileEntry], ) -> ProjectResult { @@ -56,20 +56,11 @@ pub fn build_tree( MemberKind::Constant(Constant { name, value }) => { consts.insert(name, value /* .prefix(path, &|_| false) */); }, - MemberKind::Operators(_) => (), MemberKind::Rule(rule) => rule_fragments.push(rule), }, } } - let mod_details = extra.details().expect("Directories handled elsewhere"); - let rules = (mod_details.patterns.iter()) - .zip(rule_fragments.into_iter()) - .map(|(p, Rule { prio, template: t, .. })| { - // let p = p.iter().map(|e| e.prefix(path, &|_| false)).collect(); - // let t = t.into_iter().map(|e| e.prefix(path, &|_| false)).collect(); - Rule { pattern: p.clone(), prio, template: t } - }) - .collect(); + let rules = rule_fragments; let (pre_subs, pre_items) = (entries.into_iter()) .partition_map::, HashMap<_, _>, _, _, _>( |(k, ModEntry { exported, member })| match member { @@ -98,7 +89,7 @@ pub fn build_tree( Ok((k, ModEntry { exported, member })) }) .chain((pre_items.into_iter()).map( - |(k, (exported, PreItem { has_value, is_op, location }))| { + |(k, (exported, PreItem { has_value, location }))| { let item = match imports_from.get(&k) { Some(_) if has_value => { // Local value cannot be assigned to imported key @@ -112,12 +103,10 @@ pub fn build_tree( }, None => { let k = consts.remove(&k).map_or(ItemKind::None, ItemKind::Const); - ProjectItem { is_op, kind: k } - }, - Some(report) => ProjectItem { - is_op: is_op | report.is_op, - kind: ItemKind::Alias(report.source.clone()), + ProjectItem { kind: k } }, + Some(report) => + ProjectItem { kind: ItemKind::Alias(report.source.clone()) }, }; Ok((k, ModEntry { exported, member: ModMember::Item(item) })) }, @@ -129,7 +118,6 @@ pub fn build_tree( exported: false, member: ModMember::Item(ProjectItem { kind: ItemKind::Alias(from.source.clone()), - is_op: from.is_op, }), }) }); diff --git a/src/pipeline/project_tree/import_tree.rs b/src/pipeline/project_tree/import_tree.rs index 518de9a..5223fc4 100644 --- a/src/pipeline/project_tree/import_tree.rs +++ b/src/pipeline/project_tree/import_tree.rs @@ -11,7 +11,7 @@ use crate::representations::project::ImpReport; use crate::sourcefile::{absolute_path, Import}; use crate::tree::{ErrKind, ModEntry, ModMember, Module, WalkError}; use crate::utils::boxed_iter::{box_chain, box_once}; -use crate::utils::pure_push::pushed_ref; +use crate::utils::pure_seq::pushed_ref; use crate::utils::{unwrap_or, BoxedIter}; use crate::{Interner, ProjectTree, Tok, VName}; @@ -65,17 +65,13 @@ pub fn assert_visible_overlay<'a>( }) } -pub fn process_donor_module<'a, TItem: Clone>( - module: &'a Module, +pub fn process_donor_module( + module: &Module, abs_path: Rc, - is_op: impl Fn(&TItem) -> bool + 'a, -) -> impl Iterator, VName, bool)> + 'a { - (module.entries.iter()).filter(|(_, ent)| ent.exported).map( - move |(n, ent)| { - let is_op = ent.item().map_or(false, &is_op); - (n.clone(), pushed_ref(abs_path.as_ref(), n.clone()), is_op) - }, - ) +) -> impl Iterator, VName)> + '_ { + (module.entries.iter()) + .filter(|(_, ent)| ent.exported) + .map(move |(n, _)| (n.clone(), pushed_ref(abs_path.as_ref(), n.clone()))) } pub fn import_tree( @@ -99,14 +95,7 @@ pub fn import_tree( // println!("Old root: {:#?}", &prev_root.0); panic!("{}", e.at(location)) })?; - let is_op = (root.0.walk1_ref(&[], &abs_path, false)) - .map(|(ent, _)| ent.item().map_or(false, |i| i.is_op)) - .or_else(|e| if e.kind == ErrKind::Missing { - (prev_root.0.walk1_ref(&[], &abs_path, false)) - .map(|(ent, _)| ent.item().map_or(false, |i| i.is_op)) - } else {Err(e)}) - .map_err(|e| e.at(location))?; - box_once((name.clone(), abs_path, is_op)) + box_once((name.clone(), abs_path)) } else { let rc_path = Rc::new(abs_path); // wildcard imports are validated @@ -116,8 +105,7 @@ pub fn import_tree( let new_imports = match (root.0).walk_ref(&[], &rc_path, false) { Err(e) if e.kind == ErrKind::Missing => Err(e), Err(e) => return Err(e.at(location)), - Ok(module) - => Ok(process_donor_module(module, rc_path.clone(), |i| i.is_op)) + Ok(module) => Ok(process_donor_module(module, rc_path.clone())) }; let old_m = match (prev_root.0).walk_ref(&[], &rc_path, false) { Err(e) if e.kind != ErrKind::Missing => return Err(e.at(location)), @@ -134,7 +122,7 @@ pub fn import_tree( }, Ok(old_m) => old_m, }; - let it1 = process_donor_module(old_m, rc_path.clone(), |i| i.is_op); + let it1 = process_donor_module(old_m, rc_path.clone()); match new_imports { Err(_) => Box::new(it1), Ok(it2) => box_chain!(it1, it2) @@ -144,10 +132,10 @@ pub fn import_tree( // leaf sets flattened to leaves .flatten_ok() // translated to entries - .map_ok(|(name, source, is_op)| { + .map_ok(|(name, source)| { (name, ModEntry { exported: false, // this is irrelevant but needed - member: ModMember::Item(ImpReport { source, is_op }), + member: ModMember::Item(ImpReport { source }), }) }) .chain( diff --git a/src/pipeline/project_tree/rebuild_tree.rs b/src/pipeline/project_tree/rebuild_tree.rs index 0412c77..3e405a8 100644 --- a/src/pipeline/project_tree/rebuild_tree.rs +++ b/src/pipeline/project_tree/rebuild_tree.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use hashbrown::HashMap; use itertools::Itertools; @@ -7,15 +5,14 @@ use super::build_tree::{build_tree, TreeReport}; use super::import_tree::{import_tree, ImpMod}; use crate::error::ProjectResult; use crate::pipeline::source_loader::{ - LoadedSourceTable, PreExtra, PreItem, PreMod, Preparsed, + LoadedSourceTable, PreExtra, PreMod, Preparsed, }; -use crate::representations::project::{ImpReport, ProjectExt, ProjectMod}; +use crate::representations::project::{ProjectExt, ProjectMod}; use crate::sourcefile::FileEntry; use crate::tree::{ModEntry, ModMember, Module}; -use crate::utils::never::{always, unwrap_always}; -use crate::utils::pure_push::pushed_ref; +use crate::utils::pure_seq::pushed_ref; use crate::utils::unwrap_or; -use crate::{parse, Interner, ProjectTree, Tok, VName}; +use crate::{Interner, ProjectTree, Tok, VName}; pub fn rebuild_file( path: Vec>, @@ -23,35 +20,12 @@ pub fn rebuild_file( imports: ImpMod, source: &LoadedSourceTable, prelude: &[FileEntry], - i: &Interner, ) -> ProjectResult> { let file = match &pre.extra { PreExtra::Dir => panic!("Dir should not hand this node off"), PreExtra::Submod(_) => panic!("should not have received this"), PreExtra::File(f) => f, }; - let mut ops = Vec::new(); - unwrap_always(imports.search_all((), &mut |_, module, ()| { - ops.extend( - (module.entries.iter()) - .filter(|(_, ent)| { - matches!(ent.member, ModMember::Item(ImpReport { is_op: true, .. })) - }) - .map(|(name, _)| name.clone()), - ); - always(()) - })); - unwrap_always(pre.search_all((), &mut |_, module, ()| { - ops.extend( - (module.entries.iter()) - .filter(|(_, ent)| { - matches!(ent.member, ModMember::Item(PreItem { is_op: true, .. })) - }) - .map(|(name, _)| name.clone()), - ); - always(()) - })); - let ctx = parse::ParsingContext::new(&ops, i, Rc::new(path.clone())); let src = source.get(&file.name).unwrap_or_else(|| { panic!( "{} should have been preparsed already. Preparsed files are {}", @@ -62,13 +36,11 @@ pub fn rebuild_file( .join(", ") ) }); - let entries = parse::parse2(&src.text, ctx)?; - let TreeReport { entries: items, rules, imports_from } = + let entries = src.entries.clone(); + let TreeReport { entries, rules, imports_from } = build_tree(&path, entries, pre, imports, prelude)?; - Ok(Module { - entries: items, - extra: ProjectExt { file: Some(path.clone()), path, imports_from, rules }, - }) + let file = Some(path.clone()); + Ok(Module { entries, extra: ProjectExt { file, path, imports_from, rules } }) } pub fn rebuild_dir( @@ -77,15 +49,14 @@ pub fn rebuild_dir( mut imports: ImpMod, source: &LoadedSourceTable, prelude: &[FileEntry], - i: &Interner, ) -> ProjectResult> { match pre.extra { PreExtra::Dir => (), PreExtra::File(_) => - return rebuild_file(path, pre, imports, source, prelude, i), + return rebuild_file(path, pre, imports, source, prelude), PreExtra::Submod(_) => panic!("Dirs contain dirs and files"), } - let items = (pre.entries.into_iter()) + let entries = (pre.entries.into_iter()) .map(|(name, entry)| { match imports.entries.remove(&name).map(|e| e.member) { Some(ModMember::Sub(impmod)) => (name, entry, impmod), @@ -97,12 +68,8 @@ pub fn rebuild_dir( let pre = unwrap_or!(member => ModMember::Sub; panic!("Dirs can only contain submodules") ); - Ok((name, ModEntry { - exported, - member: ModMember::Sub(rebuild_dir( - path, pre, impmod, source, prelude, i, - )?), - })) + let module = rebuild_dir(path, pre, impmod, source, prelude)?; + Ok((name, ModEntry { exported, member: ModMember::Sub(module) })) }) .collect::, _>>()?; Ok(Module { @@ -112,7 +79,7 @@ pub fn rebuild_dir( rules: Vec::new(), file: None, }, - entries: items, + entries, }) } @@ -126,6 +93,6 @@ pub fn rebuild_tree( ) -> ProjectResult> { let imports = import_tree(Vec::new(), &preparsed.0, &preparsed, prev_root, i)?; - rebuild_dir(Vec::new(), preparsed.0, imports, source, prelude, i) + rebuild_dir(Vec::new(), preparsed.0, imports, source, prelude) .map(ProjectTree) } diff --git a/src/pipeline/source_loader/load_source.rs b/src/pipeline/source_loader/load_source.rs index 66becbb..4e68c40 100644 --- a/src/pipeline/source_loader/load_source.rs +++ b/src/pipeline/source_loader/load_source.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use hashbrown::HashMap; use super::loaded_source::{LoadedSource, LoadedSourceTable}; @@ -7,24 +9,32 @@ use crate::error::{ NoTargets, ProjectError, ProjectResult, UnexpectedDirectory, }; use crate::interner::{Interner, Tok}; +use crate::parse::{self, LexerPlugin, LineParser, ParsingContext}; use crate::pipeline::file_loader::{IOResult, Loaded}; use crate::pipeline::import_abs_path::import_abs_path; use crate::representations::sourcefile::FileEntry; use crate::tree::Module; -use crate::utils::pure_push::pushed_ref; +use crate::utils::pure_seq::pushed_ref; use crate::utils::{split_max_prefix, unwrap_or}; use crate::Location; +#[derive(Clone, Copy)] +pub struct Context<'a> { + pub prelude: &'a [FileEntry], + pub i: &'a Interner, + pub lexer_plugins: &'a [&'a dyn LexerPlugin], + pub line_parsers: &'a [&'a dyn LineParser], +} + /// Load the source at the given path or all within if it's a collection, /// and all sources imported from these. fn load_abs_path_rec( abs_path: &[Tok], mut all: Preparsed, source: &mut LoadedSourceTable, - prelude: &[FileEntry], - i: &Interner, get_source: &impl Fn(&[Tok]) -> IOResult, is_injected_module: &impl Fn(&[Tok]) -> bool, + ctx @ Context { i, lexer_plugins, line_parsers, prelude }: Context, ) -> ProjectResult { // # Termination // @@ -47,8 +57,15 @@ fn load_abs_path_rec( let text = unwrap_or!(get_source(filename)? => Loaded::Code; { return Err(UnexpectedDirectory { path: filename.to_vec() }.rc()) }); - source.insert(filename.to_vec(), LoadedSource { text: text.clone() }); - let preparsed = preparse(filename.to_vec(), text.as_str(), prelude, i)?; + let entries = parse::parse2(ParsingContext::new( + i, + Arc::new(filename.to_vec()), + text, + lexer_plugins, + line_parsers, + ))?; + let preparsed = preparse(filename.to_vec(), entries.clone(), prelude)?; + source.insert(filename.to_vec(), LoadedSource { entries }); // recurse on all imported modules // will be taken and returned by the closure. None iff an error is thrown all = preparsed.0.search_all(all, &mut |modpath, @@ -73,10 +90,9 @@ fn load_abs_path_rec( &abs_pathv, all, source, - prelude, - i, get_source, is_injected_module, + ctx, )?; } Ok(all) @@ -105,10 +121,9 @@ fn load_abs_path_rec( &abs_subpath, all, source, - prelude, - i, get_source, is_injected_module, + ctx, )?; } Ok(all) @@ -123,8 +138,7 @@ fn load_abs_path_rec( /// injected data (the ProjectTree doesn't make a distinction between the two) pub fn load_source<'a>( targets: impl Iterator]>, - prelude: &[FileEntry], - i: &Interner, + ctx: Context, get_source: &impl Fn(&[Tok]) -> IOResult, is_injected_module: &impl Fn(&[Tok]) -> bool, ) -> ProjectResult<(Preparsed, LoadedSourceTable)> { @@ -138,10 +152,9 @@ pub fn load_source<'a>( target, all, &mut table, - prelude, - i, get_source, is_injected_module, + ctx, )?; } if any_target { Ok((all, table)) } else { Err(NoTargets.rc()) } diff --git a/src/pipeline/source_loader/loaded_source.rs b/src/pipeline/source_loader/loaded_source.rs index 7bfe542..6109b60 100644 --- a/src/pipeline/source_loader/loaded_source.rs +++ b/src/pipeline/source_loader/loaded_source.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; -use std::rc::Rc; use crate::representations::VName; +use crate::sourcefile::FileEntry; #[derive(Debug)] pub struct LoadedSource { - pub text: Rc, + pub entries: Vec } pub type LoadedSourceTable = HashMap; diff --git a/src/pipeline/source_loader/mod.rs b/src/pipeline/source_loader/mod.rs index b7c7c1b..8aab3bc 100644 --- a/src/pipeline/source_loader/mod.rs +++ b/src/pipeline/source_loader/mod.rs @@ -20,6 +20,6 @@ mod loaded_source; mod preparse; mod types; -pub use load_source::load_source; +pub use load_source::{load_source, Context}; pub use loaded_source::{LoadedSource, LoadedSourceTable}; pub use types::{PreExtra, PreFileExt, PreItem, PreMod, PreSubExt, Preparsed}; diff --git a/src/pipeline/source_loader/preparse.rs b/src/pipeline/source_loader/preparse.rs index ce66612..0c7a1d3 100644 --- a/src/pipeline/source_loader/preparse.rs +++ b/src/pipeline/source_loader/preparse.rs @@ -1,26 +1,21 @@ -use std::rc::Rc; - use hashbrown::HashMap; use itertools::Itertools; use super::types::{PreFileExt, PreItem, PreSubExt}; use super::{PreExtra, Preparsed}; -use crate::ast::{Clause, Constant, Expr}; +use crate::ast::{Clause, Constant}; use crate::error::{ ConflictingRoles, ProjectError, ProjectResult, VisibilityMismatch, }; -use crate::interner::Interner; -use crate::parse::{self, ParsingContext}; use crate::representations::sourcefile::{FileEntry, MemberKind}; use crate::representations::tree::{ModEntry, ModMember, Module}; use crate::sourcefile::{FileEntryKind, Import, Member, ModuleBlock}; use crate::utils::get_or::{get_or_default, get_or_make}; -use crate::utils::pure_push::pushed; +use crate::utils::pure_seq::pushed; use crate::{Location, Tok, VName}; struct FileReport { entries: HashMap, ModEntry>, - patterns: Vec>>, imports: Vec, } @@ -32,12 +27,11 @@ fn to_module( prelude: &[FileEntry], ) -> ProjectResult { let mut imports = Vec::new(); - let mut patterns = Vec::new(); let mut items = HashMap::, (bool, PreItem)>::new(); let mut to_export = HashMap::, Vec>::new(); let mut submods = HashMap::, (bool, Vec, Vec)>::new(); - let entries = prelude.iter().cloned().chain(src.into_iter()); + let entries = prelude.iter().cloned().chain(src); for FileEntry { kind, locations } in entries { match kind { FileEntryKind::Import(imp) => imports.extend(imp.into_iter()), @@ -72,17 +66,7 @@ fn to_module( submods.insert(name.clone(), (exported, locations, body.clone())); } }, - MemberKind::Operators(ops) => - for op in ops { - let (prev_exported, it) = get_or_default(&mut items, &op); - if let Some(loc) = locations.get(0) { - it.location = it.location.clone().or(loc.clone()) - } - *prev_exported |= exported; - it.is_op = true; - }, - MemberKind::Rule(r) => { - patterns.push(r.pattern.clone()); + MemberKind::Rule(r) => if exported { for ex in r.pattern { ex.search_all(&mut |ex| { @@ -95,8 +79,7 @@ fn to_module( None::<()> }); } - } - }, + }, }, _ => (), } @@ -111,11 +94,11 @@ fn to_module( .try_insert(subname.clone(), ModEntry { member: ModMember::Sub({ name.push(subname); - let FileReport { imports, entries: items, patterns } = + let FileReport { imports, entries: items } = to_module(file, name.clone(), body, prelude)?; Module { entries: items, - extra: PreExtra::Submod(PreSubExt { imports, patterns }), + extra: PreExtra::Submod(PreSubExt { imports }), } }), exported, @@ -125,7 +108,6 @@ fn to_module( for (item, locations) in to_export { get_or_make(&mut entries, &item, || ModEntry { member: ModMember::Item(PreItem { - is_op: false, has_value: false, location: locations[0].clone(), }), @@ -133,26 +115,22 @@ fn to_module( }) .exported = true } - Ok(FileReport { entries, imports, patterns }) + Ok(FileReport { entries, imports }) } /// Preparse the module. At this stage, only the imports and /// names defined by the module can be parsed pub fn preparse( file: VName, - source: &str, + entries: Vec, prelude: &[FileEntry], - i: &Interner, ) -> ProjectResult { - // Parse with no operators - let ctx = ParsingContext::new(&[], i, Rc::new(file.clone())); - let entries = parse::parse2(source, ctx)?; - let FileReport { entries, imports, patterns } = + let FileReport { entries, imports } = to_module(&file, file.clone(), entries, prelude)?; let mut module = Module { entries, extra: PreExtra::File(PreFileExt { - details: PreSubExt { patterns, imports }, + details: PreSubExt { imports }, name: file.clone(), }), }; diff --git a/src/pipeline/source_loader/types.rs b/src/pipeline/source_loader/types.rs index b57cc05..3e9a717 100644 --- a/src/pipeline/source_loader/types.rs +++ b/src/pipeline/source_loader/types.rs @@ -1,7 +1,6 @@ use std::fmt::Display; use std::ops::Add; -use crate::ast::Expr; use crate::error::ProjectResult; use crate::sourcefile::Import; use crate::tree::Module; @@ -9,34 +8,27 @@ use crate::{Interner, Location, VName}; #[derive(Debug, Clone)] pub struct PreItem { - pub is_op: bool, pub has_value: bool, pub location: Location, } impl Display for PreItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { has_value, is_op, location } = self; - let description = match (is_op, has_value) { - (true, true) => "operator with value", - (true, false) => "operator", - (false, true) => "value", - (false, false) => "keyword", - }; + let Self { has_value, location } = self; + let description = if *has_value { "value" } else { "keyword" }; write!(f, "{description} {location}") } } impl Default for PreItem { fn default() -> Self { - PreItem { is_op: false, has_value: false, location: Location::Unknown } + PreItem { has_value: false, location: Location::Unknown } } } #[derive(Debug, Clone)] pub struct PreSubExt { pub imports: Vec, - pub patterns: Vec>>, } #[derive(Debug, Clone)] diff --git a/src/representations/ast.rs b/src/representations/ast.rs index f22c212..2cccc46 100644 --- a/src/representations/ast.rs +++ b/src/representations/ast.rs @@ -15,13 +15,13 @@ use ordered_float::NotNan; use super::interpreted; use super::location::Location; use super::namelike::{NameLike, VName}; -use super::primitive::Primitive; +use crate::foreign::{Atom, ExFn}; use crate::interner::Tok; use crate::parse::print_nat16; use crate::utils::rc_tools::map_rc; /// A [Clause] with associated metadata -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Expr { /// The actual value pub value: Clause, @@ -100,7 +100,7 @@ pub enum PHClass { /// If true, must match at least one clause nonzero: bool, /// Greediness in the allocation of tokens - prio: u64, + prio: usize, }, /// Matches exactly one token, lambda or parenthesized group Scalar, @@ -129,10 +129,12 @@ impl Display for Placeholder { } /// An S-expression as read from a source file -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub enum Clause { - /// A primitive - P(Primitive), + /// An opaque function, eg. an effectful function employing CPS + ExternFn(ExFn), + /// An opaque non-callable value, eg. a file handle + Atom(Atom), /// A c-style name or an operator, eg. `+`, `i`, `foo::bar` Name(N), /// A parenthesized expression @@ -214,7 +216,7 @@ impl Clause { #[must_use] pub fn map_names(&self, pred: &impl Fn(&N) -> Option) -> Option { match self { - Clause::P(_) | Clause::Placeh(_) => None, + Clause::Atom(_) | Clause::ExternFn(_) | Clause::Placeh(_) => None, Clause::Name(name) => pred(name).map(Clause::Name), Clause::S(c, body) => { let mut any_some = false; @@ -262,7 +264,8 @@ impl Clause { match self { Self::Name(n) => Clause::Name(pred(n)), Self::Placeh(p) => Clause::Placeh(p), - Self::P(p) => Clause::P(p), + Self::Atom(a) => Clause::Atom(a), + Self::ExternFn(f) => Clause::ExternFn(f), Self::Lambda(n, b) => Clause::Lambda( map_rc(n, |n| n.into_iter().map(|e| e.transform_names(pred)).collect()), map_rc(b, |b| b.into_iter().map(|e| e.transform_names(pred)).collect()), @@ -282,7 +285,8 @@ impl Clause { match self { Clause::Lambda(arg, body) => arg.iter().chain(body.iter()).find_map(|expr| expr.search_all(f)), - Clause::Name(_) | Clause::P(_) | Clause::Placeh(_) => None, + Clause::Name(_) | Clause::Atom(_) => None, + Clause::ExternFn(_) | Clause::Placeh(_) => None, Clause::S(_, body) => body.iter().find_map(|expr| expr.search_all(f)), } } @@ -295,7 +299,8 @@ impl Clause { match self { Clause::Lambda(arg, body) => search_all_slcs(arg, f).or_else(|| search_all_slcs(body, f)), - Clause::Name(_) | Clause::P(_) | Clause::Placeh(_) => None, + Clause::Name(_) | Clause::Atom(_) => None, + Clause::ExternFn(_) | Clause::Placeh(_) => None, Clause::S(_, body) => search_all_slcs(body, f), } } @@ -325,7 +330,8 @@ impl Clause { impl Display for Clause { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::P(p) => write!(f, "{:?}", p), + Self::ExternFn(fun) => write!(f, "{fun:?}"), + Self::Atom(a) => write!(f, "{a:?}"), Self::Name(name) => write!(f, "{}", name.to_strv().join("::")), Self::S(del, items) => { let body = items.iter().join(" "); @@ -348,7 +354,7 @@ impl Display for Clause { } /// A substitution rule as read from the source -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Rule { /// Tree fragment in the source code that activates this rule pub pattern: Vec>, @@ -407,7 +413,7 @@ impl Display for Rule { } /// A named constant -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Constant { /// Used to reference the constant pub name: Tok, diff --git a/src/representations/ast_to_postmacro.rs b/src/representations/ast_to_postmacro.rs index 99fd2a4..576ff11 100644 --- a/src/representations/ast_to_postmacro.rs +++ b/src/representations/ast_to_postmacro.rs @@ -107,7 +107,8 @@ fn expr_rec<'a>( Ok(postmacro::Expr { value: expr.value, location: location.clone() }) } else { let value = match value { - ast::Clause::P(p) => postmacro::Clause::P(p.clone()), + ast::Clause::Atom(a) => postmacro::Clause::Atom(a.clone()), + ast::Clause::ExternFn(fun) => postmacro::Clause::ExternFn(fun.clone()), ast::Clause::Lambda(arg, b) => { let name = match &arg[..] { [ast::Expr { value: ast::Clause::Name(name), .. }] => name, diff --git a/src/representations/const_tree.rs b/src/representations/const_tree.rs index 1f3e61a..ce79a6f 100644 --- a/src/representations/const_tree.rs +++ b/src/representations/const_tree.rs @@ -4,12 +4,12 @@ use hashbrown::HashMap; use super::project::{ItemKind, ProjectItem}; use crate::ast::{Clause, Expr}; -use crate::foreign::{Atom, Atomic, ExternFn}; +use crate::foreign::{Atom, Atomic, ExFn, ExternFn}; use crate::interner::Tok; use crate::representations::location::Location; use crate::representations::project::{ProjectExt, ProjectMod, ProjectTree}; use crate::representations::tree::{ModEntry, ModMember, Module}; -use crate::representations::{Primitive, VName}; +use crate::representations::VName; use crate::utils::substack::Substack; /// A lightweight module tree that can be built declaratively by hand to @@ -25,21 +25,18 @@ pub enum ConstTree { impl ConstTree { /// Describe a [Primitive] #[must_use] - pub fn primitive(primitive: Primitive) -> Self { - Self::Const(Expr { - location: Location::Unknown, - value: Clause::P(primitive), - }) + pub fn clause(value: Clause) -> Self { + Self::Const(Expr { location: Location::Unknown, value }) } /// Describe an [ExternFn] #[must_use] pub fn xfn(xfn: impl ExternFn + 'static) -> Self { - Self::primitive(Primitive::ExternFn(Box::new(xfn))) + Self::clause(Clause::ExternFn(ExFn(Box::new(xfn)))) } /// Describe an [Atomic] #[must_use] pub fn atom(atom: impl Atomic + 'static) -> Self { - Self::primitive(Primitive::Atom(Atom(Box::new(atom)))) + Self::clause(Clause::Atom(Atom(Box::new(atom)))) } /// Describe a module #[must_use] @@ -85,7 +82,7 @@ impl Add for ConstTree { product.insert(key, i1); } } - product.extend(t2.into_iter()); + product.extend(t2); Self::Tree(product) } else { panic!("cannot combine tree and value fields") @@ -104,10 +101,8 @@ fn from_const_tree_rec( items.insert(name.clone(), ModEntry { exported: true, member: match item { - ConstTree::Const(c) => ModMember::Item(ProjectItem { - kind: ItemKind::Const(c), - is_op: false, - }), + ConstTree::Const(c) => + ModMember::Item(ProjectItem { kind: ItemKind::Const(c) }), ConstTree::Tree(t) => ModMember::Sub(from_const_tree_rec(path.push(name), t, file)), }, diff --git a/src/representations/interpreted.rs b/src/representations/interpreted.rs index bebe36f..8e636c6 100644 --- a/src/representations/interpreted.rs +++ b/src/representations/interpreted.rs @@ -2,20 +2,18 @@ //! //! This code may be generated to minimize the number of states external //! functions have to define -use std::cell::RefCell; use std::fmt::{Debug, Display}; use std::ops::{Deref, DerefMut}; use std::rc::Rc; +use std::sync::{Arc, TryLockError, Mutex}; #[allow(unused)] // for doc use super::ast; use super::location::Location; use super::path_set::PathSet; -use super::primitive::Primitive; -use super::Literal; #[allow(unused)] // for doc use crate::foreign::Atomic; -use crate::foreign::ExternError; +use crate::foreign::{Atom, ExFn, ExternError}; use crate::utils::ddispatch::request; use crate::utils::take_with_output; use crate::Sym; @@ -40,10 +38,11 @@ impl Debug for Expr { impl Display for Expr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.location { - Location::Unknown => write!(f, "{}", self.clause), - loc => write!(f, "{}:({})", loc, self.clause), - } + write!(f, "{}", self.clause) + // match &self.location { + // Location::Unknown => write!(f, "{}", self.clause), + // loc => write!(f, "{}:({})", loc, self.clause), + // } } } @@ -64,20 +63,20 @@ impl TryFromExprInst for ExprInst { /// A wrapper around expressions to handle their multiple occurences in /// the tree together #[derive(Clone)] -pub struct ExprInst(pub Rc>); +pub struct ExprInst(pub Arc>); impl ExprInst { /// Wrap an [Expr] in a shared container so that normalizatoin steps are /// applied to all references #[must_use] - pub fn new(expr: Expr) -> Self { Self(Rc::new(RefCell::new(expr))) } + pub fn new(expr: Expr) -> Self { Self(Arc::new(Mutex::new(expr))) } /// Take the [Expr] out of this container if it's the last reference to it, or /// clone it out. #[must_use] pub fn expr_val(self) -> Expr { - Rc::try_unwrap(self.0) - .map(|c| c.into_inner()) - .unwrap_or_else(|rc| rc.as_ref().borrow().deref().clone()) + Arc::try_unwrap(self.0) + .map(|c| c.into_inner().unwrap()) + .unwrap_or_else(|arc| arc.lock().unwrap().clone()) } /// Read-only access to the shared expression instance @@ -87,7 +86,7 @@ impl ExprInst { /// if the expression is already borrowed in read-write mode #[must_use] pub fn expr(&self) -> impl Deref + '_ { - self.0.as_ref().borrow() + self.0.lock().unwrap() } /// Read-Write access to the shared expression instance @@ -97,7 +96,7 @@ impl ExprInst { /// if the expression is already borrowed #[must_use] pub fn expr_mut(&self) -> impl DerefMut + '_ { - self.0.as_ref().borrow_mut() + self.0.lock().unwrap() } /// Call a normalization function on the expression. The expr is @@ -137,26 +136,6 @@ impl ExprInst { predicate(&self.expr().clause) } - /// Call the predicate on the value inside this expression if it is a - /// primitive - pub fn get_literal(self) -> Result<(Literal, Location), Self> { - Rc::try_unwrap(self.0).map_or_else( - |rc| { - if let Expr { clause: Clause::P(Primitive::Literal(li)), location } = - rc.as_ref().borrow().deref() - { - return Ok((li.clone(), location.clone())); - } - Err(Self(rc)) - }, - |cell| match cell.into_inner() { - Expr { clause: Clause::P(Primitive::Literal(li)), location } => - Ok((li, location)), - expr => Err(Self::new(expr)), - }, - ) - } - /// Visit all expressions in the tree. The search can be exited early by /// returning [Some] /// @@ -174,7 +153,8 @@ impl ExprInst { Clause::Lambda { body, .. } => body.search_all(predicate), Clause::Constant(_) | Clause::LambdaArg - | Clause::P(_) + | Clause::Atom(_) + | Clause::ExternFn(_) | Clause::Bottom => None, }) } @@ -195,7 +175,7 @@ impl ExprInst { #[must_use = "your request might not have succeeded"] pub fn request(&self) -> Option { match &self.expr().clause { - Clause::P(Primitive::Atom(a)) => request(&*a.0), + Clause::Atom(a) => request(&*a.0), _ => None, } } @@ -203,18 +183,20 @@ impl ExprInst { impl Debug for ExprInst { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0.try_borrow() { + match self.0.try_lock() { Ok(expr) => write!(f, "{expr:?}"), - Err(_) => write!(f, ""), + Err(TryLockError::Poisoned(_)) => write!(f, ""), + Err(TryLockError::WouldBlock) => write!(f, ""), } } } impl Display for ExprInst { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0.try_borrow() { + match self.0.try_lock() { Ok(expr) => write!(f, "{expr}"), - Err(_) => write!(f, ""), + Err(TryLockError::Poisoned(_)) => write!(f, ""), + Err(TryLockError::WouldBlock) => write!(f, ""), } } } @@ -224,8 +206,10 @@ impl Display for ExprInst { pub enum Clause { /// An expression that causes an error Bottom, - /// An unintrospectable unit - P(Primitive), + /// An opaque function, eg. an effectful function employing CPS + ExternFn(ExFn), + /// An opaque non-callable value, eg. a file handle + Atom(Atom), /// A function application Apply { /// Function to be applied @@ -252,7 +236,7 @@ impl Clause { /// copied or moved clauses as it does not have debug information and /// does not share a normalization cache list with them. pub fn wrap(self) -> ExprInst { - ExprInst(Rc::new(RefCell::new(Expr { + ExprInst(Arc::new(Mutex::new(Expr { location: Location::Unknown, clause: self, }))) @@ -284,7 +268,8 @@ impl Clause { impl Display for Clause { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Clause::P(p) => write!(f, "{p:?}"), + Clause::ExternFn(fun) => write!(f, "{fun:?}"), + Clause::Atom(a) => write!(f, "{a:?}"), Clause::Bottom => write!(f, "bottom"), Clause::LambdaArg => write!(f, "arg"), Clause::Apply { f: fun, x } => write!(f, "({fun} {x})"), @@ -296,11 +281,3 @@ impl Display for Clause { } } } - -impl> From for Clause { - fn from(value: T) -> Self { Self::P(Primitive::Literal(value.into())) } -} - -impl> From for ExprInst { - fn from(value: T) -> Self { value.into().wrap() } -} diff --git a/src/representations/literal.rs b/src/representations/literal.rs deleted file mode 100644 index ae17fec..0000000 --- a/src/representations/literal.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::fmt::Debug; - -use ordered_float::NotNan; - -use super::OrcString; - -/// Exact values read from the AST which have a shared meaning recognized by all -/// external functions -#[derive(Clone, PartialEq, Eq, Hash)] -pub enum Literal { - /// Any floating point number except `NaN` - Num(NotNan), - /// An unsigned integer; a size, index or pointer - Uint(u64), - /// A utf-8 character sequence - Str(OrcString), -} - -impl Debug for Literal { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Num(arg0) => write!(f, "{:?}", arg0), - Self::Uint(arg0) => write!(f, "{:?}", arg0), - Self::Str(arg0) => write!(f, "{:?}", arg0), - } - } -} - -impl From> for Literal { - fn from(value: NotNan) -> Self { Self::Num(value) } -} -impl From for Literal { - fn from(value: u64) -> Self { Self::Uint(value) } -} -impl From for Literal { - fn from(value: String) -> Self { Self::Str(value.into()) } -} diff --git a/src/representations/location.rs b/src/representations/location.rs index c239b58..e54797f 100644 --- a/src/representations/location.rs +++ b/src/representations/location.rs @@ -1,6 +1,6 @@ use std::fmt::{Debug, Display}; use std::ops::Range; -use std::rc::Rc; +use std::sync::Arc; use itertools::Itertools; @@ -13,15 +13,15 @@ pub enum Location { /// Location information lost or code generated on the fly Unknown, /// Only the file is known - File(Rc), + File(Arc), /// Character slice of the code Range { /// Argument to the file loading callback that produced this code - file: Rc, + file: Arc, /// Index of the unicode code points associated with the code range: Range, /// The full source code as received by the parser - source: Rc, + source: Arc, }, } @@ -38,7 +38,7 @@ impl Location { /// File, if known #[must_use] - pub fn file(&self) -> Option> { + pub fn file(&self) -> Option> { if let Self::File(file) | Self::Range { file, .. } = self { Some(file.clone()) } else { @@ -48,7 +48,7 @@ impl Location { /// Associated source code, if known #[must_use] - pub fn source(&self) -> Option> { + pub fn source(&self) -> Option> { if let Self::Range { source, .. } = self { Some(source.clone()) } else { diff --git a/src/representations/mod.rs b/src/representations/mod.rs index 3b57986..bb357d3 100644 --- a/src/representations/mod.rs +++ b/src/representations/mod.rs @@ -3,22 +3,18 @@ pub mod ast_to_interpreted; pub mod ast_to_postmacro; mod const_tree; pub mod interpreted; -pub mod literal; pub mod location; mod namelike; pub mod path_set; pub mod postmacro; pub mod postmacro_to_interpreted; -pub mod primitive; pub mod project; pub mod sourcefile; mod string; pub mod tree; pub use const_tree::{from_const_tree, ConstTree}; -pub use literal::Literal; pub use location::Location; pub use namelike::{NameLike, Sym, VName}; pub use path_set::PathSet; -pub use primitive::Primitive; pub use string::OrcString; diff --git a/src/representations/path_set.rs b/src/representations/path_set.rs index 086ac85..01b3f59 100644 --- a/src/representations/path_set.rs +++ b/src/representations/path_set.rs @@ -1,8 +1,8 @@ use std::fmt::Debug; use std::ops::Add; -use std::rc::Rc; +use std::sync::Arc; -use crate::utils::rc_tools::rc_to_owned; +use crate::utils::rc_tools::arc_to_owned; use crate::utils::Side; /// A branching path selecting some placeholders (but at least one) in a Lambda @@ -10,9 +10,9 @@ use crate::utils::Side; #[derive(Clone, PartialEq, Eq, Hash)] pub struct PathSet { /// The definite steps - pub steps: Rc>, + pub steps: Arc>, /// if Some, it splits. If None, it ends. - pub next: Option<(Rc, Rc)>, + pub next: Option<(Arc, Arc)>, } impl PathSet { @@ -22,20 +22,20 @@ impl PathSet { left: Self, right: Self, ) -> Self { - let steps = Rc::new(steps.into_iter().collect()); - Self { steps, next: Some((Rc::new(left), Rc::new(right))) } + let steps = Arc::new(steps.into_iter().collect()); + Self { steps, next: Some((Arc::new(left), Arc::new(right))) } } /// Create a path set for one target pub fn end(steps: impl IntoIterator) -> Self { - Self { steps: Rc::new(steps.into_iter().collect()), next: None } + Self { steps: Arc::new(steps.into_iter().collect()), next: None } } /// Create a path set points to a slot that is a direct /// child of the given lambda with no applications. In essence, this means /// that this argument will be picked as the value of the expression after an /// arbitrary amount of subsequent discarded parameters. - pub fn pick() -> Self { Self { steps: Rc::new(vec![]), next: None } } + pub fn pick() -> Self { Self { steps: Arc::new(vec![]), next: None } } } impl Debug for PathSet { @@ -57,7 +57,10 @@ impl Debug for PathSet { impl Add for PathSet { type Output = Self; fn add(self, rhs: Self) -> Self::Output { - Self { steps: Rc::new(vec![]), next: Some((Rc::new(self), Rc::new(rhs))) } + Self { + steps: Arc::new(vec![]), + next: Some((Arc::new(self), Arc::new(rhs))), + } } } @@ -65,9 +68,9 @@ impl Add for PathSet { type Output = Self; fn add(self, rhs: Side) -> Self::Output { let PathSet { steps, next } = self; - let mut new_steps = rc_to_owned(steps); + let mut new_steps = arc_to_owned(steps); new_steps.insert(0, rhs); - Self { steps: Rc::new(new_steps), next } + Self { steps: Arc::new(new_steps), next } } } @@ -78,9 +81,9 @@ mod tests { #[test] fn test_combine() -> Result<(), &'static str> { let ps1 = - PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Left]) }; + PathSet { next: None, steps: Arc::new(vec![Side::Left, Side::Left]) }; let ps2 = - PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Right]) }; + PathSet { next: None, steps: Arc::new(vec![Side::Left, Side::Right]) }; let sum = ps1.clone() + ps2.clone(); assert_eq!(sum.steps.as_ref(), &[]); let nexts = sum.next.ok_or("nexts not set")?; @@ -92,16 +95,16 @@ mod tests { fn extend_scaffold() -> PathSet { PathSet { next: Some(( - Rc::new(PathSet { + Arc::new(PathSet { next: None, - steps: Rc::new(vec![Side::Left, Side::Left]), + steps: Arc::new(vec![Side::Left, Side::Left]), }), - Rc::new(PathSet { + Arc::new(PathSet { next: None, - steps: Rc::new(vec![Side::Left, Side::Right]), + steps: Arc::new(vec![Side::Left, Side::Right]), }), )), - steps: Rc::new(vec![Side::Left, Side::Right, Side::Left]), + steps: Arc::new(vec![Side::Left, Side::Right, Side::Left]), } } diff --git a/src/representations/postmacro.rs b/src/representations/postmacro.rs index 498aa2d..b8e5588 100644 --- a/src/representations/postmacro.rs +++ b/src/representations/postmacro.rs @@ -2,7 +2,7 @@ use std::fmt::{Debug, Write}; use std::rc::Rc; use super::location::Location; -use super::primitive::Primitive; +use crate::foreign::{ExFn, Atom}; use crate::utils::string_from_charset; use crate::Sym; @@ -42,7 +42,10 @@ pub enum Clause { Lambda(Rc), Constant(Sym), LambdaArg(usize), - P(Primitive), + /// An opaque function, eg. an effectful function employing CPS + ExternFn(ExFn), + /// An opaque non-callable value, eg. a file handle + Atom(Atom), } const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz"; @@ -75,7 +78,8 @@ impl Clause { Wrap(wl, wr): Wrap, ) -> std::fmt::Result { match self { - Self::P(p) => write!(f, "{p:?}"), + Self::Atom(a) => write!(f, "{a:?}"), + Self::ExternFn(fun) => write!(f, "{fun:?}"), Self::Lambda(body) => parametric_fmt(f, depth, "\\", body, wr), Self::LambdaArg(skip) => { let lambda_depth = (depth - skip - 1).try_into().unwrap(); diff --git a/src/representations/postmacro_to_interpreted.rs b/src/representations/postmacro_to_interpreted.rs index e76bd51..b3bb6f9 100644 --- a/src/representations/postmacro_to_interpreted.rs +++ b/src/representations/postmacro_to_interpreted.rs @@ -1,5 +1,4 @@ -use std::cell::RefCell; -use std::rc::Rc; +use std::sync::{Arc, Mutex}; use super::path_set::PathSet; use super::{interpreted, postmacro}; @@ -18,12 +17,12 @@ fn collect_paths_cls_rec( depth: usize, ) -> Option { match cls { - postmacro::Clause::P(_) | postmacro::Clause::Constant(_) => None, + postmacro::Clause::Atom(_) | postmacro::Clause::ExternFn(_) => None, + postmacro::Clause::Constant(_) => None, postmacro::Clause::LambdaArg(h) => - if *h != depth { - None - } else { - Some(PathSet { next: None, steps: Rc::new(vec![]) }) + match *h != depth { + true => None, + false => Some(PathSet::pick()) }, postmacro::Clause::Lambda(b) => collect_paths_expr_rec(b, depth + 1), postmacro::Clause::Apply(f, x) => { @@ -43,7 +42,8 @@ pub fn clause(cls: &postmacro::Clause) -> interpreted::Clause { match cls { postmacro::Clause::Constant(name) => interpreted::Clause::Constant(name.clone()), - postmacro::Clause::P(p) => interpreted::Clause::P(p.clone()), + postmacro::Clause::Atom(a) => interpreted::Clause::Atom(a.clone()), + postmacro::Clause::ExternFn(fun) => interpreted::Clause::ExternFn(fun.clone()), postmacro::Clause::Apply(f, x) => interpreted::Clause::Apply { f: expr(f.as_ref()), x: expr(x.as_ref()) }, postmacro::Clause::Lambda(body) => interpreted::Clause::Lambda { @@ -55,7 +55,7 @@ pub fn clause(cls: &postmacro::Clause) -> interpreted::Clause { } pub fn expr(expr: &postmacro::Expr) -> interpreted::ExprInst { - interpreted::ExprInst(Rc::new(RefCell::new(interpreted::Expr { + interpreted::ExprInst(Arc::new(Mutex::new(interpreted::Expr { location: expr.location.clone(), clause: clause(&expr.value), }))) diff --git a/src/representations/primitive.rs b/src/representations/primitive.rs deleted file mode 100644 index e25db35..0000000 --- a/src/representations/primitive.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::fmt::Debug; - -use super::Literal; -use crate::foreign::{Atom, ExternFn}; - -/// A value the interpreter can't inspect -pub enum Primitive { - /// A literal value, eg. `1`, `"hello"` - Literal(Literal), - /// An opaque function, eg. an effectful function employing CPS - ExternFn(Box), - /// An opaque non-callable value, eg. a file handle - Atom(Atom), -} - -impl PartialEq for Primitive { - fn eq(&self, other: &Self) -> bool { - if let (Self::Literal(l1), Self::Literal(l2)) = (self, other) { - l1 == l2 - } else { - false - } - } -} - -impl Clone for Primitive { - fn clone(&self) -> Self { - match self { - Primitive::Literal(l) => Primitive::Literal(l.clone()), - Primitive::Atom(a) => Primitive::Atom(a.clone()), - Primitive::ExternFn(ef) => - Primitive::ExternFn(dyn_clone::clone_box(ef.as_ref())), - } - } -} - -impl Debug for Primitive { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Atom(a) => write!(f, "{a:?}"), - Self::ExternFn(ef) => write!(f, "{ef:?}"), - Self::Literal(l) => write!(f, "{l:?}"), - } - } -} diff --git a/src/representations/project.rs b/src/representations/project.rs index b245c8a..49ca356 100644 --- a/src/representations/project.rs +++ b/src/representations/project.rs @@ -31,26 +31,15 @@ impl Default for ItemKind { #[derive(Debug, Clone, Default)] pub struct ProjectItem { pub kind: ItemKind, - pub is_op: bool, } impl Display for ProjectItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.kind { - ItemKind::None => match self.is_op { - true => write!(f, "operator"), - false => write!(f, "keyword"), - }, - ItemKind::Const(c) => match self.is_op { - true => write!(f, "operator with value {c}"), - false => write!(f, "constant {c}"), - }, + ItemKind::None => write!(f, "keyword"), + ItemKind::Const(c) => write!(f, "constant {c}"), ItemKind::Alias(alias) => { - let origin = Interner::extern_all(alias).join("::"); - match self.is_op { - true => write!(f, "operator alias to {origin}"), - false => write!(f, "alias to {origin}"), - } + write!(f, "alias to {}", Interner::extern_all(alias).join("::")) }, } } @@ -61,9 +50,6 @@ impl Display for ProjectItem { pub struct ImpReport { /// Absolute path of the module the symbol is imported from pub source: N, - /// Whether this symbol should be treated as an operator for the purpose of - /// parsing - pub is_op: bool, } /// Additional data about a loaded module beyond the list of constants and @@ -94,8 +80,8 @@ impl Add for ProjectExt { Interner::extern_all(&self.path).join("::") ) } - self.imports_from.extend(imports_from.into_iter()); - self.rules.extend(rules.into_iter()); + self.imports_from.extend(imports_from); + self.rules.extend(rules); if file.is_some() { self.file = file } @@ -190,7 +176,6 @@ fn vname_to_sym_tree_rec( ModMember::Sub(module) => ModMember::Sub(vname_to_sym_tree_rec(module, i)), ModMember::Item(ex) => ModMember::Item(ProjectItem { - is_op: ex.is_op, kind: match ex.kind { ItemKind::None => ItemKind::None, ItemKind::Alias(n) => ItemKind::Alias(n), @@ -204,7 +189,7 @@ fn vname_to_sym_tree_rec( extra: ProjectExt { path: tree.extra.path, imports_from: (tree.extra.imports_from.into_iter()) - .map(|(k, v)| (k, ImpReport { is_op: v.is_op, source: i.i(&v.source) })) + .map(|(k, v)| (k, ImpReport { source: i.i(&v.source) })) .collect(), rules: (tree.extra.rules.into_iter()) .map(|Rule { pattern, prio, template }| Rule { diff --git a/src/representations/sourcefile.rs b/src/representations/sourcefile.rs index 33954b6..7c0ef39 100644 --- a/src/representations/sourcefile.rs +++ b/src/representations/sourcefile.rs @@ -8,7 +8,7 @@ use super::namelike::VName; use crate::ast::{Constant, Rule}; use crate::error::{ProjectError, ProjectResult, TooManySupers}; use crate::interner::{Interner, Tok}; -use crate::utils::pure_push::pushed; +use crate::utils::pure_seq::pushed; use crate::utils::{unwrap_or, BoxedIter}; use crate::Location; @@ -78,16 +78,11 @@ pub enum MemberKind { Constant(Constant), /// A prefixed set of other entries Module(ModuleBlock), - /// Operator declarations - Operators(Vec>), } impl Display for MemberKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Operators(opv) => { - write!(f, "operators[{}]", opv.iter().map(|t| &**t).join(" ")) - }, Self::Constant(c) => c.fmt(f), Self::Module(m) => m.fmt(f), Self::Rule(r) => r.fmt(f), diff --git a/src/representations/string.rs b/src/representations/string.rs index 77c0282..1027b4f 100644 --- a/src/representations/string.rs +++ b/src/representations/string.rs @@ -1,10 +1,10 @@ use std::fmt::Debug; use std::hash::Hash; use std::ops::Deref; -use std::rc::Rc; +use std::sync::Arc; -use crate::interpreted::{Clause, ExprInst}; -use crate::{Literal, Primitive, Tok}; +use crate::foreign::InertAtomic; +use crate::{Interner, Tok}; /// An Orchid string which may or may not be interned #[derive(Clone, Eq)] @@ -12,7 +12,7 @@ pub enum OrcString { /// An interned string. Equality-conpared by reference. Interned(Tok), /// An uninterned bare string. Equality-compared by character - Runtime(Rc), + Runtime(Arc), } impl Debug for OrcString { @@ -25,23 +25,21 @@ impl Debug for OrcString { } impl OrcString { + /// Intern the contained string + pub fn intern(&mut self, i: &Interner) { + if let Self::Runtime(t) = self { + *self = Self::Interned(i.i(t.as_str())) + } + } /// Clone out the plain Rust [String] #[must_use] pub fn get_string(self) -> String { match self { Self::Interned(s) => s.as_str().to_owned(), Self::Runtime(rc) => - Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()), + Arc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()), } } - - /// Wrap in a [Clause] for returning from extern functions - pub fn cls(self) -> Clause { - Clause::P(Primitive::Literal(Literal::Str(self))) - } - - /// Wrap in an [ExprInst] for embedding in runtime-generated code - pub fn exi(self) -> ExprInst { self.cls().wrap() } } impl Deref for OrcString { @@ -62,7 +60,7 @@ impl Hash for OrcString { } impl From for OrcString { - fn from(value: String) -> Self { Self::Runtime(Rc::new(value)) } + fn from(value: String) -> Self { Self::Runtime(Arc::new(value)) } } impl From> for OrcString { @@ -77,3 +75,8 @@ impl PartialEq for OrcString { } } } + +impl InertAtomic for OrcString { + fn type_str() -> &'static str { "OrcString" } + fn strict_eq(&self, other: &Self) -> bool { self == other } +} diff --git a/src/representations/tree.rs b/src/representations/tree.rs index 9eef4d4..83906a2 100644 --- a/src/representations/tree.rs +++ b/src/representations/tree.rs @@ -174,7 +174,7 @@ impl Module { (_, right) => new_items.insert(key, right), }; } - new_items.extend(self.entries.into_iter()); + new_items.extend(self.entries); Ok(Module { entries: new_items, extra: (self.extra + extra)? }) } } diff --git a/src/rule/matcher_vectree/build.rs b/src/rule/matcher_vectree/build.rs index 8ab8bed..258833c 100644 --- a/src/rule/matcher_vectree/build.rs +++ b/src/rule/matcher_vectree/build.rs @@ -8,7 +8,7 @@ use crate::rule::vec_attrs::vec_attrs; use crate::utils::Side; pub type MaxVecSplit<'a> = - (&'a [RuleExpr], (Tok, u64, bool), &'a [RuleExpr]); + (&'a [RuleExpr], (Tok, usize, bool), &'a [RuleExpr]); /// Derive the details of the central vectorial and the two sides from a /// slice of Expr's @@ -107,7 +107,8 @@ fn mk_vec(pattern: &[RuleExpr]) -> VecMatcher { #[must_use] fn mk_scalar(pattern: &RuleExpr) -> ScalMatcher { match &pattern.value { - Clause::P(p) => ScalMatcher::P(p.clone()), + Clause::Atom(a) => ScalMatcher::Atom(a.clone()), + Clause::ExternFn(_) => panic!("Cannot match on ExternFn"), Clause::Name(n) => ScalMatcher::Name(n.clone()), Clause::Placeh(Placeholder { name, class }) => { debug_assert!( diff --git a/src/rule/matcher_vectree/scal_match.rs b/src/rule/matcher_vectree/scal_match.rs index 1e3dc65..7260ccc 100644 --- a/src/rule/matcher_vectree/scal_match.rs +++ b/src/rule/matcher_vectree/scal_match.rs @@ -10,7 +10,8 @@ pub fn scal_match<'a>( expr: &'a RuleExpr, ) -> Option> { match (matcher, &expr.value) { - (ScalMatcher::P(p1), Clause::P(p2)) if p1 == p2 => Some(State::new()), + (ScalMatcher::Atom(a1), Clause::Atom(a2)) if a1.0.strict_eq(&a2.0) => + Some(State::new()), (ScalMatcher::Name(n1), Clause::Name(n2)) if n1 == n2 => Some(State::new()), (ScalMatcher::Placeh(key), _) => Some(State::from([(key.clone(), StateEntry::Scalar(expr))])), diff --git a/src/rule/matcher_vectree/shared.rs b/src/rule/matcher_vectree/shared.rs index d913a72..b5a230c 100644 --- a/src/rule/matcher_vectree/shared.rs +++ b/src/rule/matcher_vectree/shared.rs @@ -5,15 +5,15 @@ use itertools::Itertools; use super::any_match::any_match; use super::build::mk_any; +use crate::foreign::Atom; use crate::interner::Tok; -use crate::representations::Primitive; use crate::rule::matcher::{Matcher, RuleExpr}; use crate::rule::state::State; use crate::utils::Side; use crate::{Sym, VName}; pub enum ScalMatcher { - P(Primitive), + Atom(Atom), Name(Sym), S(char, Box), Lambda(Box, Box), @@ -68,7 +68,7 @@ impl Matcher for AnyMatcher { impl Display for ScalMatcher { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::P(p) => write!(f, "{:?}", p), + Self::Atom(a) => write!(f, "{a:?}"), Self::Placeh(n) => write!(f, "${n}"), Self::Name(n) => write!(f, "{}", n.extern_vec().join("::")), Self::S(c, body) => { diff --git a/src/rule/matcher_vectree/vec_match.rs b/src/rule/matcher_vectree/vec_match.rs index 0d113ca..178d1b0 100644 --- a/src/rule/matcher_vectree/vec_match.rs +++ b/src/rule/matcher_vectree/vec_match.rs @@ -55,7 +55,7 @@ pub fn vec_match<'a>( // Valid combinations of locations for the separators let mut pos_pairs = lposv .into_iter() - .cartesian_product(rposv.into_iter()) + .cartesian_product(rposv) .filter(|((lpos, _), (rpos, _))| lpos + left_sep.len() <= *rpos) .map(|((lpos, mut lstate), (rpos, rstate))| { lstate.extend(rstate); diff --git a/src/rule/prepare_rule.rs b/src/rule/prepare_rule.rs index 5c93aeb..ea88df1 100644 --- a/src/rule/prepare_rule.rs +++ b/src/rule/prepare_rule.rs @@ -30,11 +30,11 @@ fn pad(mut rule: Rule, i: &Interner) -> Rule { let prefix_v = if prefix_explicit { empty } else { prefix }; let suffix_v = if suffix_explicit { empty } else { suffix }; rule.pattern = (prefix_v.iter().cloned()) - .chain(rule.pattern.into_iter()) + .chain(rule.pattern) .chain(suffix_v.iter().cloned()) .collect(); rule.template = (prefix_v.iter().cloned()) - .chain(rule.template.into_iter()) + .chain(rule.template) .chain(suffix_v.iter().cloned()) .collect(); rule @@ -60,7 +60,8 @@ fn check_rec_expr( in_template: bool, ) -> Result<(), RuleError> { match &expr.value { - Clause::Name(_) | Clause::P(_) => Ok(()), + Clause::Name(_) | Clause::Atom(_) => Ok(()), + Clause::ExternFn(_) => Err(RuleError::ExternFn), Clause::Placeh(Placeholder { name, class }) => { let typ = (*class).into(); // in a template, the type must be known and identical diff --git a/src/rule/rule_error.rs b/src/rule/rule_error.rs index 8adc06a..8e48ca2 100644 --- a/src/rule/rule_error.rs +++ b/src/rule/rule_error.rs @@ -5,6 +5,8 @@ use hashbrown::HashSet; use crate::ast::{self, search_all_slcs, PHClass, Placeholder, Rule}; use crate::error::{ErrorPosition, ProjectError}; +#[allow(unused)] // for doc +use crate::foreign::ExternFn; use crate::interner::Tok; use crate::utils::BoxedIter; use crate::{Location, Sym}; @@ -20,16 +22,20 @@ pub enum RuleError { Multiple(Tok), /// Two vectorial placeholders are next to each other VecNeighbors(Tok, Tok), + /// Found an [ExternFn] in the pattern. This is a really unlikely mistake + /// caused only by rogue systems. + ExternFn, } impl RuleError { /// Convert into a unified error trait object shared by all Orchid errors #[must_use] pub fn to_project_error(self, rule: &Rule) -> Rc { match self { - RuleError::Missing(name) => Missing::new(rule, name).rc(), - RuleError::Multiple(name) => Multiple::new(rule, name).rc(), - RuleError::ArityMismatch(name) => ArityMismatch::new(rule, name).rc(), - RuleError::VecNeighbors(n1, n2) => VecNeighbors::new(rule, n1, n2).rc(), + Self::Missing(name) => Missing::new(rule, name).rc(), + Self::Multiple(name) => Multiple::new(rule, name).rc(), + Self::ArityMismatch(name) => ArityMismatch::new(rule, name).rc(), + Self::VecNeighbors(n1, n2) => VecNeighbors::new(rule, n1, n2).rc(), + Self::ExternFn => ExternFnInPattern(rule.clone()).rc(), } } } @@ -37,6 +43,7 @@ impl RuleError { impl Display for RuleError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::ExternFn => write!(f, "Found an ExternFn in the pattern"), Self::Missing(key) => write!(f, "Key {key} not in match pattern"), Self::ArityMismatch(key) => { write!(f, "Key {key} used inconsistently with and without ellipsis") @@ -44,10 +51,8 @@ impl Display for RuleError { Self::Multiple(key) => { write!(f, "Key {key} appears multiple times in match pattern") }, - Self::VecNeighbors(left, right) => write!( - f, - "Keys {left} and {right} are two vectorials right next to each other" - ), + Self::VecNeighbors(left, right) => + write!(f, "vectorials {left} and {right} are next to each other"), } } } @@ -232,3 +237,15 @@ impl ProjectError for VecNeighbors { ) } } + +/// Not referencing by location because it's most likely unknown +#[derive(Debug)] +pub struct ExternFnInPattern(ast::Rule); +impl ProjectError for ExternFnInPattern { + fn description(&self) -> &str { + "Found an ExternFn in a pattern. Unlikely error caused by a system" + } + fn message(&self) -> String { + format!("Found ExternFn in pattern {}", self.0) + } +} diff --git a/src/rule/state.rs b/src/rule/state.rs index 0c02cfc..0338ef1 100644 --- a/src/rule/state.rs +++ b/src/rule/state.rs @@ -7,7 +7,7 @@ use crate::ast::{Clause, Expr, PHClass, Placeholder}; use crate::interner::Tok; use crate::utils::unwrap_or; -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug)] pub enum StateEntry<'a> { Vec(&'a [RuleExpr]), Scalar(&'a RuleExpr), @@ -27,7 +27,8 @@ pub fn apply_exprv(template: &[RuleExpr], state: &State) -> Vec { pub fn apply_expr(template: &RuleExpr, state: &State) -> Vec { let Expr { location, value } = template; match value { - Clause::P(_) | Clause::Name(_) => vec![template.clone()], + Clause::Atom(_) | Clause::Name(_) | Clause::ExternFn(_) => + vec![template.clone()], Clause::S(c, body) => vec![Expr { location: location.clone(), value: Clause::S(*c, Rc::new(apply_exprv(body.as_slice(), state))), diff --git a/src/rule/update_first_seq.rs b/src/rule/update_first_seq.rs index 16aebd1..7480ad6 100644 --- a/src/rule/update_first_seq.rs +++ b/src/rule/update_first_seq.rs @@ -35,7 +35,8 @@ pub fn clause>) -> Option>>>( pred: &mut F, ) -> Option> { match c { - Clause::P(_) | Clause::Placeh { .. } | Clause::Name { .. } => None, + Clause::Atom(_) | Clause::Placeh { .. } | Clause::Name { .. } => None, + Clause::ExternFn(_) => None, Clause::Lambda(arg, body) => if let Some(arg) = exprv(arg.clone(), pred) { Some(Clause::Lambda(arg, body.clone())) diff --git a/src/rule/vec_attrs.rs b/src/rule/vec_attrs.rs index 1425449..ccf4e77 100644 --- a/src/rule/vec_attrs.rs +++ b/src/rule/vec_attrs.rs @@ -5,7 +5,7 @@ use crate::interner::Tok; /// Returns the name, priority and nonzero of the expression if it is /// a vectorial placeholder #[must_use] -pub fn vec_attrs(expr: &RuleExpr) -> Option<(Tok, u64, bool)> { +pub fn vec_attrs(expr: &RuleExpr) -> Option<(Tok, usize, bool)> { match expr.value.clone() { Clause::Placeh(Placeholder { class: PHClass::Vec { prio, nonzero }, diff --git a/src/systems/asynch/async.orc b/src/systems/asynch/async.orc index 52d9f54..4479c22 100644 --- a/src/systems/asynch/async.orc +++ b/src/systems/asynch/async.orc @@ -1,6 +1,6 @@ import std::panic -export const block_on := \action.\cont. ( +export const block_on := \action. \cont. ( action cont (\e.panic "unwrapped asynch call") \c.yield diff --git a/src/systems/asynch/system.rs b/src/systems/asynch/system.rs index fa70175..6893227 100644 --- a/src/systems/asynch/system.rs +++ b/src/systems/asynch/system.rs @@ -4,6 +4,7 @@ use std::collections::VecDeque; use std::fmt::{Debug, Display}; use std::rc::Rc; use std::sync::mpsc::Sender; +use std::sync::{Arc, Mutex}; use std::time::Duration; use hashbrown::HashMap; @@ -17,23 +18,30 @@ use crate::interpreted::{Clause, ExprInst}; use crate::interpreter::HandlerTable; use crate::pipeline::file_loader::embed_to_map; use crate::systems::codegen::call; -use crate::systems::stl::Boolean; use crate::utils::poller::{PollEvent, Poller}; use crate::utils::unwrap_or; use crate::{ConstTree, Interner}; #[derive(Debug, Clone)] struct Timer { - recurring: Boolean, + recurring: bool, delay: NotNan, } -pub fn set_timer(recurring: Boolean, delay: NotNan) -> XfnResult { +pub fn set_timer(recurring: bool, delay: NotNan) -> XfnResult { Ok(init_cps(2, Timer { recurring, delay })) } #[derive(Clone)] -struct CancelTimer(Rc); +struct CancelTimer(Arc>); +impl CancelTimer { + pub fn new(f: impl Fn() + Send + 'static) -> Self { + Self(Arc::new(Mutex::new(f))) + } + pub fn cancel(&self) { + self.0.lock().unwrap()() + } +} impl Debug for CancelTimer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "opaque cancel operation") @@ -134,17 +142,16 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> { let mut polly = polly.borrow_mut(); let (timeout, action, cont) = t.unpack2(); let duration = Duration::from_secs_f64(*timeout.delay); - let cancel_timer = if timeout.recurring.0 { - CancelTimer(Rc::new(polly.set_interval(duration, action))) - } else { - CancelTimer(Rc::new(polly.set_timeout(duration, action))) + let cancel_timer = match timeout.recurring { + true => CancelTimer::new(polly.set_interval(duration, action)), + false => CancelTimer::new(polly.set_timeout(duration, action)), }; Ok(call(cont, [init_cps(1, cancel_timer).wrap()]).wrap()) } }); handler_table.register(move |t: Box>| { let (command, cont) = t.unpack1(); - command.0.as_ref()(); + command.cancel(); Ok(cont) }); handler_table.register({ @@ -165,7 +172,7 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> { PollEvent::Event(ev) => { let handler = (handlers.get_mut(&ev.as_ref().type_id())) .unwrap_or_else(|| { - panic!("Unhandled messgae type: {:?}", ev.type_id()) + panic!("Unhandled messgae type: {:?}", (*ev).type_id()) }); let events = handler(ev); // we got new microtasks @@ -181,6 +188,8 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> { }); System { name: vec!["system".to_string(), "asynch".to_string()], + lexer_plugin: None, + line_parser: None, constants: ConstTree::namespace( [i.i("system"), i.i("async")], ConstTree::tree([ diff --git a/src/systems/cast_exprinst.rs b/src/systems/cast_exprinst.rs deleted file mode 100644 index 716b014..0000000 --- a/src/systems/cast_exprinst.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! Utility functions that operate on literals. Because of the parallel locked -//! nature of [ExprInst], returning a reference to [Literal] is not possible. -use std::rc::Rc; - -use ordered_float::NotNan; - -use super::assertion_error::AssertionError; -use crate::foreign::{Atom, ExternError}; -use crate::interpreted::{Clause, Expr, TryFromExprInst}; -use crate::representations::interpreted::ExprInst; -use crate::representations::{Literal, OrcString}; -use crate::{Location, Primitive}; - -/// [ExprInst::get_literal] except the error is mapped to an [ExternError] -pub fn get_literal( - exi: ExprInst, -) -> Result<(Literal, Location), Rc> { - (exi.get_literal()).map_err(|exi| { - eprintln!("failed to get literal from {:?}", exi.expr().clause); - AssertionError::ext(exi.location(), "literal") - }) -} - -// ######## Automatically ######## - -impl TryFromExprInst for Literal { - fn from_exi(exi: ExprInst) -> Result> { - get_literal(exi).map(|(l, _)| l) - } -} - -impl TryFromExprInst for OrcString { - fn from_exi(exi: ExprInst) -> Result> { - match get_literal(exi)? { - (Literal::Str(s), _) => Ok(s), - (_, location) => AssertionError::fail(location, "string"), - } - } -} - -impl TryFromExprInst for u64 { - fn from_exi(exi: ExprInst) -> Result> { - match get_literal(exi)? { - (Literal::Uint(u), _) => Ok(u), - (_, location) => AssertionError::fail(location, "uint"), - } - } -} - -impl TryFromExprInst for NotNan { - fn from_exi(exi: ExprInst) -> Result> { - match get_literal(exi)? { - (Literal::Num(n), _) => Ok(n), - (_, location) => AssertionError::fail(location, "float"), - } - } -} - -impl TryFromExprInst for Atom { - fn from_exi(exi: ExprInst) -> Result> { - let Expr { clause, location } = exi.expr_val(); - match clause { - Clause::P(Primitive::Atom(a)) => Ok(a), - _ => AssertionError::fail(location, "atom"), - } - } -} diff --git a/src/systems/codegen.rs b/src/systems/codegen.rs index 9ab9e7a..cfc6502 100644 --- a/src/systems/codegen.rs +++ b/src/systems/codegen.rs @@ -39,11 +39,11 @@ pub fn tuple(data: impl IntoIterator) -> Clause { #[cfg(test)] mod test { - use crate::systems::codegen::tuple; + use crate::{systems::codegen::tuple, foreign::Atomic}; #[test] fn tuple_printer() { - println!("Binary tuple: {}", tuple([0.into(), 1.into()])) + println!("Binary tuple: {}", tuple([0usize.atom_exi(), 1usize.atom_exi()])) } } diff --git a/src/systems/directfs/commands.rs b/src/systems/directfs/commands.rs index af2f63b..387d5f0 100644 --- a/src/systems/directfs/commands.rs +++ b/src/systems/directfs/commands.rs @@ -8,24 +8,27 @@ use itertools::Itertools; use super::osstring::os_string_lib; use crate::ddispatch::Responder; +use crate::error::RuntimeError; use crate::facade::{IntoSystem, System}; use crate::foreign::cps_box::{init_cps, CPSBox}; use crate::foreign::{ - xfn_1ary, xfn_2ary, Atomic, AtomicReturn, InertAtomic, XfnResult, + xfn_1ary, xfn_2ary, Atomic, AtomicReturn, InertAtomic, StrictEq, XfnResult, }; use crate::interpreted::{Clause, ExprInst}; use crate::interpreter::HandlerTable; use crate::systems::codegen::{call, list, opt, tuple}; use crate::systems::io::{wrap_io_error, Source}; use crate::systems::scheduler::{SeqScheduler, SharedHandle}; -use crate::systems::stl::Boolean; -use crate::systems::RuntimeError; use crate::utils::unwrap_or; use crate::ConstTree; #[derive(Debug, Clone)] pub struct CurrentDir; impl Responder for CurrentDir {} +impl StrictEq for CurrentDir { + // never appears in macros + fn strict_eq(&self, _: &dyn std::any::Any) -> bool { false } +} impl Atomic for CurrentDir { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn std::any::Any { self } @@ -95,7 +98,7 @@ fn read_dir(sched: &SeqScheduler, cmd: CPSBox) -> ExprInst { Err(e) => vec![call(fail, [wrap_io_error(e)]).wrap()], Ok(os_namev) => { let converted = (os_namev.into_iter()) - .map(|(n, d)| Ok(tuple([n.atom_exi(), Boolean(d).atom_exi()]).wrap())) + .map(|(n, d)| Ok(tuple([n.atom_exi(), d.atom_exi()]).wrap())) .collect::, Clause>>(); match converted { Err(e) => vec![call(fail, [e.wrap()]).wrap()], @@ -115,7 +118,7 @@ pub fn write_file(sched: &SeqScheduler, cmd: CPSBox) -> ExprInst { |file, _| match file { Err(e) => vec![call(fail, [wrap_io_error(e)]).wrap()], Ok(f) => { - let handle = SharedHandle::wrap(Box::new(f) as Box); + let handle = SharedHandle::wrap(Box::new(f) as Box); vec![call(succ, [handle.atom_exi()]).wrap()] }, }, @@ -180,6 +183,8 @@ impl IntoSystem<'static> for DirectFS { name: ["system", "directfs"].into_iter().map_into().collect(), code: HashMap::new(), prelude: Vec::new(), + lexer_plugin: None, + line_parser: None, constants: ConstTree::namespace( [i.i("system"), i.i("directfs")], ConstTree::tree([ diff --git a/src/systems/io/bindings.rs b/src/systems/io/bindings.rs index 2c56562..19f36d9 100644 --- a/src/systems/io/bindings.rs +++ b/src/systems/io/bindings.rs @@ -1,13 +1,13 @@ use super::flow::IOCmdHandlePack; use super::instances::{BRead, ReadCmd, SRead, Sink, Source, WriteCmd}; +use crate::error::RuntimeError; use crate::foreign::cps_box::init_cps; -use crate::foreign::{xfn_1ary, xfn_2ary, Atom, Atomic, XfnResult}; +use crate::foreign::{xfn_1ary, xfn_2ary, Atomic, XfnResult, Atom}; use crate::interpreted::Clause; use crate::representations::OrcString; use crate::systems::scheduler::SharedHandle; use crate::systems::stl::Binary; -use crate::systems::RuntimeError; -use crate::{ast, ConstTree, Interner, Primitive}; +use crate::{ConstTree, Interner, ast}; type WriteHandle = SharedHandle; type ReadHandle = SharedHandle; @@ -21,11 +21,11 @@ pub fn read_line(handle: ReadHandle) -> XfnResult { pub fn read_bin(handle: ReadHandle) -> XfnResult { Ok(init_cps(3, IOCmdHandlePack { handle, cmd: ReadCmd::RBytes(BRead::All) })) } -pub fn read_bytes(handle: ReadHandle, n: u64) -> XfnResult { - let cmd = ReadCmd::RBytes(BRead::N(n.try_into().unwrap())); +pub fn read_bytes(handle: ReadHandle, n: usize) -> XfnResult { + let cmd = ReadCmd::RBytes(BRead::N(n)); Ok(init_cps(3, IOCmdHandlePack { cmd, handle })) } -pub fn read_until(handle: ReadHandle, pattern: u64) -> XfnResult { +pub fn read_until(handle: ReadHandle, pattern: usize) -> XfnResult { let delim = pattern.try_into().map_err(|_| { let msg = "greater than 255".to_string(); RuntimeError::ext(msg, "converting number to byte") @@ -63,8 +63,7 @@ pub fn io_bindings<'a>( std_streams .into_iter() .map(|(n, at)| { - let expr = ast::Clause::P(Primitive::Atom(Atom(at))).into_expr(); - (i.i(n), ConstTree::Const(expr)) + (i.i(n), ConstTree::clause(ast::Clause::Atom(Atom(at)))) }) .collect(), ), diff --git a/src/systems/io/instances.rs b/src/systems/io/instances.rs index caed569..a55af99 100644 --- a/src/systems/io/instances.rs +++ b/src/systems/io/instances.rs @@ -7,7 +7,7 @@ use crate::interpreted::ExprInst; use crate::systems::codegen::call; use crate::systems::scheduler::{Canceller, SharedHandle}; use crate::systems::stl::Binary; -use crate::Literal; +use crate::OrcString; /// Any type that we can read controlled amounts of data from pub type Source = BufReader>; @@ -63,10 +63,14 @@ impl IOCmd for ReadCmd { Self::RStr(sread) => { let mut buf = String::new(); let sresult = match &sread { - SRead::All => stream.read_to_string(&mut buf), - SRead::Line => stream.read_line(&mut buf), + SRead::All => stream.read_to_string(&mut buf).map(|_| ()), + SRead::Line => stream.read_line(&mut buf).map(|_| { + if buf.ends_with('\n') { + buf.pop(); + } + }), }; - ReadResult::RStr(sread, sresult.map(|_| buf)) + ReadResult::RStr(sread, sresult.map(|()| buf)) }, } } @@ -88,14 +92,14 @@ impl ReadResult { vec![call(succ, [arg]).wrap()] }, ReadResult::RStr(_, Ok(text)) => { - vec![call(succ, [Literal::Str(text.into()).into()]).wrap()] + vec![call(succ, [OrcString::from(text).atom_exi()]).wrap()] }, } } } /// Function to convert [io::Error] to Orchid data -pub fn wrap_io_error(_e: io::Error) -> ExprInst { Literal::Uint(0u64).into() } +pub fn wrap_io_error(_e: io::Error) -> ExprInst { 0usize.atom_exi() } /// Writing command (string or binary) #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/src/systems/io/io.orc b/src/systems/io/io.orc index ddc9ba1..7795d97 100644 --- a/src/systems/io/io.orc +++ b/src/systems/io/io.orc @@ -2,7 +2,7 @@ import std::panic import system::io import system::async::yield -export const print := \text.\ok. ( +export const print := \text. \ok. ( io::write_str io::stdout text (io::flush io::stdout ok @@ -13,7 +13,7 @@ export const print := \text.\ok. ( \_. yield ) -export const println := \line.\ok. ( +export const println := \line. \ok. ( print (line ++ "\n") ok ) diff --git a/src/systems/io/service.rs b/src/systems/io/service.rs index 5b7e5cb..a79eb8f 100644 --- a/src/systems/io/service.rs +++ b/src/systems/io/service.rs @@ -113,6 +113,8 @@ impl<'a, ST: IntoIterator> IntoSystem<'static> name: None, }]), }], + lexer_plugin: None, + line_parser: None, } } } diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 139e75f..e2f27c1 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -1,13 +1,7 @@ //! Constants exposed to usercode by the interpreter -mod assertion_error; pub mod asynch; -pub mod cast_exprinst; pub mod codegen; pub mod directfs; pub mod io; -mod runtime_error; pub mod scheduler; pub mod stl; - -pub use assertion_error::AssertionError; -pub use runtime_error::RuntimeError; diff --git a/src/systems/scheduler/busy.rs b/src/systems/scheduler/busy.rs index 2fcf493..f5dc1d0 100644 --- a/src/systems/scheduler/busy.rs +++ b/src/systems/scheduler/busy.rs @@ -7,14 +7,17 @@ use crate::interpreted::ExprInst; pub type SyncResult = (T, Box); pub type SyncOperation = Box SyncResult + Send>; -pub type SyncOpResultHandler = - Box, Canceller) -> (T, Vec)>; +pub type SyncOpResultHandler = Box< + dyn FnOnce(T, Box, Canceller) -> (T, Vec) + + + Send, +>; struct SyncQueueItem { cancelled: Canceller, operation: SyncOperation, handler: SyncOpResultHandler, - early_cancel: Box (T, Vec)>, + early_cancel: Box (T, Vec) + Send>, } pub enum NextItemReportKind { @@ -36,11 +39,14 @@ pub struct NextItemReport { pub struct BusyState { handler: SyncOpResultHandler, queue: VecDeque>, - seal: Option Vec>>, + seal: Option Vec + Send>>, } impl BusyState { pub fn new( - handler: impl FnOnce(T, U, Canceller) -> (T, Vec) + 'static, + handler: impl FnOnce(T, U, Canceller) -> (T, Vec) + + + Send + + 'static, ) -> Self { BusyState { handler: Box::new(|t, payload, cancel| { @@ -59,8 +65,8 @@ impl BusyState { pub fn enqueue( &mut self, operation: impl FnOnce(T, Canceller) -> (T, U) + Send + 'static, - handler: impl FnOnce(T, U, Canceller) -> (T, Vec) + 'static, - early_cancel: impl FnOnce(T) -> (T, Vec) + 'static, + handler: impl FnOnce(T, U, Canceller) -> (T, Vec) + Send + 'static, + early_cancel: impl FnOnce(T) -> (T, Vec) + Send + 'static, ) -> Option { if self.seal.is_some() { return None; @@ -81,7 +87,7 @@ impl BusyState { Some(cancelled) } - pub fn seal(&mut self, recipient: impl FnOnce(T) -> Vec + 'static) { + pub fn seal(&mut self, recipient: impl FnOnce(T) -> Vec + Send + 'static) { assert!(self.seal.is_none(), "Already sealed"); self.seal = Some(Box::new(recipient)) } @@ -101,7 +107,7 @@ impl BusyState { if candidate.cancelled.is_cancelled() { let ret = (candidate.early_cancel)(instance); instance = ret.0; - events.extend(ret.1.into_iter()); + events.extend(ret.1); } else { break candidate; } diff --git a/src/systems/scheduler/system.rs b/src/systems/scheduler/system.rs index 0e071d2..b346792 100644 --- a/src/systems/scheduler/system.rs +++ b/src/systems/scheduler/system.rs @@ -2,6 +2,7 @@ use std::any::{type_name, Any}; use std::cell::RefCell; use std::fmt::Debug; use std::rc::Rc; +use std::sync::{Arc, Mutex}; use hashbrown::HashMap; use itertools::Itertools; @@ -9,14 +10,13 @@ use trait_set::trait_set; use super::busy::{BusyState, NextItemReportKind}; use super::Canceller; +use crate::error::AssertionError; use crate::facade::{IntoSystem, System}; use crate::foreign::cps_box::{init_cps, CPSBox}; use crate::foreign::{xfn_1ary, InertAtomic, XfnResult}; use crate::interpreted::{Clause, ExprInst}; use crate::interpreter::HandlerTable; use crate::systems::asynch::{AsynchSystem, MessagePort}; -use crate::systems::stl::Boolean; -use crate::systems::AssertionError; use crate::utils::ddispatch::Request; use crate::utils::thread_pool::ThreadPool; use crate::utils::{take_with_output, unwrap_or, IdMap}; @@ -47,17 +47,17 @@ pub enum SharedState { /// A shared handle for a resource of type `T` that can be used with a /// [SeqScheduler] to execute mutating operations one by one in worker threads. -pub struct SharedHandle(Rc>>); +pub struct SharedHandle(Arc>>); impl SharedHandle { /// Wrap a value to be accessible to a [SeqScheduler]. pub fn wrap(t: T) -> Self { - Self(Rc::new(RefCell::new(SharedResource::Free(t)))) + Self(Arc::new(Mutex::new(SharedResource::Free(t)))) } /// Check the state of the handle pub fn state(&self) -> SharedState { - match &*self.0.as_ref().borrow() { + match &*self.0.lock().unwrap() { SharedResource::Busy(b) if b.is_sealed() => SharedState::Sealed, SharedResource::Busy(_) => SharedState::Busy, SharedResource::Free(_) => SharedState::Free, @@ -70,7 +70,7 @@ impl SharedHandle { /// sense as eg. an optimization. You can return the value after processing /// via [SyncHandle::untake]. pub fn take(&self) -> Option { - take_with_output(&mut *self.0.as_ref().borrow_mut(), |state| match state { + take_with_output(&mut *self.0.lock().unwrap(), |state| match state { SharedResource::Free(t) => (SharedResource::Taken, Some(t)), _ => (state, None), }) @@ -80,10 +80,13 @@ impl SharedHandle { /// is to return values synchronously after they have been removed with /// [SyncHandle::untake]. pub fn untake(&self, value: T) -> Result<(), T> { - take_with_output(&mut *self.0.as_ref().borrow_mut(), |state| match state { - SharedResource::Taken => (SharedResource::Free(value), Ok(())), - _ => (state, Err(value)), - }) + take_with_output( + &mut *self.0.lock().unwrap(), + |state| match state { + SharedResource::Taken => (SharedResource::Free(value), Ok(())), + _ => (state, Err(value)), + }, + ) } } impl Clone for SharedHandle { @@ -97,12 +100,12 @@ impl Debug for SharedHandle { .finish() } } -impl InertAtomic for SharedHandle { +impl InertAtomic for SharedHandle { fn type_str() -> &'static str { "a SharedHandle" } fn respond(&self, mut request: Request) { request.serve_with(|| { let this = self.clone(); - TakeCmd(Rc::new(move |sch| { + TakeCmd(Arc::new(move |sch| { let _ = sch.seal(this.clone(), |_| Vec::new()); })) }) @@ -110,7 +113,7 @@ impl InertAtomic for SharedHandle { } #[derive(Clone)] -pub struct TakeCmd(pub Rc); +pub struct TakeCmd(pub Arc); impl Debug for TakeCmd { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "A command to drop a shared resource") @@ -134,8 +137,8 @@ pub fn take_and_drop(x: ExprInst) -> XfnResult { } } -pub fn is_taken_error(x: ExprInst) -> XfnResult { - Ok(Boolean(x.downcast::().is_ok())) +pub fn is_taken_error(x: ExprInst) -> XfnResult { + Ok(x.downcast::().is_ok()) } trait_set! { @@ -195,11 +198,11 @@ impl SeqScheduler { pub fn schedule( &self, handle: SharedHandle, - operation: impl FnOnce(T, Canceller) -> (T, U) + Send + 'static, - handler: impl FnOnce(T, U, Canceller) -> (T, Vec) + 'static, - early_cancel: impl FnOnce(T) -> (T, Vec) + 'static, + operation: impl FnOnce(T, Canceller) -> (T, U) + Sync + Send + 'static, + handler: impl FnOnce(T, U, Canceller) -> (T, Vec) + Sync + Send + 'static, + early_cancel: impl FnOnce(T) -> (T, Vec) + Sync + Send + 'static, ) -> Result { - take_with_output(&mut *handle.0.as_ref().borrow_mut(), { + take_with_output(&mut *handle.0.lock().unwrap(), { let handle = handle.clone(); |state| { match state { @@ -246,10 +249,10 @@ impl SeqScheduler { pub fn seal( &self, handle: SharedHandle, - seal: impl FnOnce(T) -> Vec + 'static, + seal: impl FnOnce(T) -> Vec + Sync + Send + 'static, ) -> Result, SealedOrTaken> { take_with_output( - &mut *handle.0.as_ref().borrow_mut(), + &mut *handle.0.lock().unwrap(), |state| match state { SharedResource::Busy(mut b) if !b.is_sealed() => { b.seal(seal); @@ -281,7 +284,7 @@ impl SeqScheduler { let (t, u): (T, U) = *data.downcast().expect("This is associated by ID"); let handle2 = handle.clone(); - take_with_output(&mut *handle.0.as_ref().borrow_mut(), |state| { + take_with_output(&mut *handle.0.lock().unwrap(), |state| { let busy = unwrap_or! { state => SharedResource::Busy; panic!("Handle with outstanding invocation must be busy") }; @@ -329,6 +332,8 @@ impl IntoSystem<'static> for SeqScheduler { prelude: Vec::new(), code: HashMap::new(), handlers, + lexer_plugin: None, + line_parser: None, constants: ConstTree::namespace( [i.i("system"), i.i("scheduler")], ConstTree::tree([ diff --git a/src/systems/stl/bin.rs b/src/systems/stl/binary.rs similarity index 65% rename from src/systems/stl/bin.rs rename to src/systems/stl/binary.rs index 03decd5..f424417 100644 --- a/src/systems/stl/bin.rs +++ b/src/systems/stl/binary.rs @@ -3,15 +3,16 @@ use std::sync::Arc; use itertools::Itertools; -use super::Boolean; +use crate::error::RuntimeError; use crate::foreign::{ xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, Atomic, InertAtomic, XfnResult, }; use crate::interpreted::Clause; use crate::systems::codegen::{opt, tuple}; -use crate::systems::RuntimeError; use crate::utils::{iter_find, unwrap_or}; -use crate::{ConstTree, Interner, Literal}; +use crate::{ConstTree, Interner}; + +const INT_BYTES: usize = usize::BITS as usize / 8; /// A block of binary data #[derive(Clone, Hash, PartialEq, Eq)] @@ -43,93 +44,86 @@ pub fn concatenate(a: Binary, b: Binary) -> XfnResult { } /// Extract a subsection of the binary data -pub fn slice(s: Binary, i: u64, len: u64) -> XfnResult { - if i + len < s.0.len() as u64 { +pub fn slice(s: Binary, i: usize, len: usize) -> XfnResult { + if i + len < s.0.len() { RuntimeError::fail( "Byte index out of bounds".to_string(), "indexing binary", )? } - let data = s.0[i as usize..i as usize + len as usize].to_vec(); - Ok(Binary(Arc::new(data))) + Ok(Binary(Arc::new(s.0[i..i + len].to_vec()))) } /// Return the index where the first argument first contains the second, if any pub fn find(haystack: Binary, needle: Binary) -> XfnResult { let found = iter_find(haystack.0.iter(), needle.0.iter()); - Ok(opt(found.map(|x| Literal::Uint(x as u64).into()))) + Ok(opt(found.map(usize::atom_exi))) } /// Split binary data block into two smaller blocks -pub fn split(bin: Binary, i: u64) -> XfnResult { - if bin.0.len() < i as usize { +pub fn split(bin: Binary, i: usize) -> XfnResult { + if bin.0.len() < i { RuntimeError::fail( "Byte index out of bounds".to_string(), "splitting binary", )? } - let (asl, bsl) = bin.0.split_at(i as usize); - Ok(tuple([ - Binary(Arc::new(asl.to_vec())).atom_cls().into(), - Binary(Arc::new(bsl.to_vec())).atom_cls().into(), - ])) + let (asl, bsl) = bin.0.split_at(i); + Ok(tuple([asl, bsl].map(|s| Binary(Arc::new(s.to_vec())).atom_exi()))) } /// Read a number from a binary blob pub fn get_num( buf: Binary, - loc: u64, - size: u64, - is_le: Boolean, -) -> XfnResult { - if buf.0.len() < (loc + size) as usize { + loc: usize, + size: usize, + is_le: bool, +) -> XfnResult { + if buf.0.len() < (loc + size) { RuntimeError::fail( "section out of range".to_string(), "reading number from binary data", )? } - if 8 < size { + if INT_BYTES < size { RuntimeError::fail( - "more than 8 bytes provided".to_string(), + "more than std::bin::int_bytes bytes provided".to_string(), "reading number from binary data", )? } - let mut data = [0u8; 8]; - let section = &buf.0[loc as usize..(loc + size) as usize]; - let num = if is_le.0 { - data[0..size as usize].copy_from_slice(section); - u64::from_le_bytes(data) + let mut data = [0u8; INT_BYTES]; + let section = &buf.0[loc..(loc + size)]; + let num = if is_le { + data[0..size].copy_from_slice(section); + usize::from_le_bytes(data) } else { - data[8 - size as usize..].copy_from_slice(section); - u64::from_be_bytes(data) + data[INT_BYTES - size..].copy_from_slice(section); + usize::from_be_bytes(data) }; - Ok(Literal::Uint(num)) + Ok(num) } /// Convert a number into a blob -pub fn from_num(size: u64, is_le: Boolean, data: u64) -> XfnResult { - if size > 8 { +pub fn from_num(size: usize, is_le: bool, data: usize) -> XfnResult { + if INT_BYTES < size { RuntimeError::fail( - "more than 8 bytes requested".to_string(), + "more than std::bin::int_bytes bytes requested".to_string(), "converting number to binary", )? } - let bytes = if is_le.0 { - data.to_le_bytes()[0..size as usize].to_vec() - } else { - data.to_be_bytes()[8 - size as usize..].to_vec() + let bytes = match is_le { + true => data.to_le_bytes()[0..size].to_vec(), + false => data.to_be_bytes()[8 - size..].to_vec(), }; Ok(Binary(Arc::new(bytes))) } /// Detect the number of bytes in the blob -pub fn size(b: Binary) -> XfnResult { - Ok(Literal::Uint(b.0.len() as u64)) -} +pub fn size(b: Binary) -> XfnResult { Ok(b.0.len()) } pub fn bin(i: &Interner) -> ConstTree { ConstTree::tree([( - i.i("bin"), + i.i("binary"), ConstTree::tree([ (i.i("concat"), ConstTree::xfn(xfn_2ary(concatenate))), (i.i("slice"), ConstTree::xfn(xfn_3ary(slice))), @@ -138,6 +132,7 @@ pub fn bin(i: &Interner) -> ConstTree { (i.i("get_num"), ConstTree::xfn(xfn_4ary(get_num))), (i.i("from_num"), ConstTree::xfn(xfn_3ary(from_num))), (i.i("size"), ConstTree::xfn(xfn_1ary(size))), + (i.i("int_bytes"), ConstTree::atom(INT_BYTES)), ]), )]) } diff --git a/src/systems/stl/bool.orc b/src/systems/stl/bool.orc index 03a2b1d..54390d0 100644 --- a/src/systems/stl/bool.orc +++ b/src/systems/stl/bool.orc @@ -1,8 +1,10 @@ -export operators[ != == ] +export ::(!=, ==) export const not := \bool. if bool then false else true macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b)) macro ...$a == ...$b =0x3p36=> (equals (...$a) (...$b)) +export macro ...$a and ...$b =0x4p36=> (ifthenelse (...$a) (...$b) false) +export macro ...$a or ...$b =0x4p36=> (ifthenelse (...$a) true (...$b)) export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> ( ifthenelse (...$cond) (...$true) (...$false) ) diff --git a/src/systems/stl/bool.rs b/src/systems/stl/bool.rs index d287146..508fc85 100644 --- a/src/systems/stl/bool.rs +++ b/src/systems/stl/bool.rs @@ -1,26 +1,17 @@ -use crate::foreign::{xfn_1ary, xfn_2ary, InertAtomic, XfnResult}; +use crate::foreign::{xfn_1ary, xfn_2ary, XfnResult, Atom}; use crate::interner::Interner; use crate::representations::interpreted::Clause; -use crate::systems::AssertionError; -use crate::{ConstTree, Literal, Location}; +use crate::error::AssertionError; +use crate::{ConstTree, Location, OrcString}; -/// Booleans exposed to Orchid -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Boolean(pub bool); -impl InertAtomic for Boolean { - fn type_str() -> &'static str { "a boolean" } -} - -impl From for Boolean { - fn from(value: bool) -> Self { Self(value) } -} +use super::Numeric; /// Takes a boolean and two branches, runs the first if the bool is true, the /// second if it's false. // Even though it's a ternary function, IfThenElse is implemented as an unary // foreign function, as the rest of the logic can be defined in Orchid. -pub fn if_then_else(b: Boolean) -> XfnResult { - Ok(match b.0 { +pub fn if_then_else(b: bool) -> XfnResult { + Ok(match b { true => Clause::pick(Clause::constfn(Clause::LambdaArg)), false => Clause::constfn(Clause::pick(Clause::LambdaArg)), }) @@ -29,16 +20,25 @@ pub fn if_then_else(b: Boolean) -> XfnResult { /// Compares the inner values if /// /// - both are string, +/// - both are bool, /// - both are either uint or num -pub fn equals(a: Literal, b: Literal) -> XfnResult { - Ok(Boolean::from(match (a, b) { - (Literal::Str(s1), Literal::Str(s2)) => s1 == s2, - (Literal::Num(n1), Literal::Num(n2)) => n1 == n2, - (Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2, - (Literal::Num(n1), Literal::Uint(u1)) => *n1 == (u1 as f64), - (Literal::Uint(u1), Literal::Num(n1)) => *n1 == (u1 as f64), - (..) => AssertionError::fail(Location::Unknown, "the expected type")?, - })) +pub fn equals(a: Atom, b: Atom) -> XfnResult { + let (a, b) = match (a.try_downcast::(), b.try_downcast::()) { + (Ok(a), Ok(b)) => return Ok(a == b), + (Err(a), Err(b)) => (a, b), + _ => return Ok(false), + }; + match (a.request::(), b.request::()) { + (Some(a), Some(b)) => return Ok(a.as_float() == b.as_float()), + (None, None) => (), + _ => return Ok(false), + }; + match (a.try_downcast::(), b.try_downcast::()) { + (Ok(a), Ok(b)) => return Ok(a == b), + (Err(_), Err(_)) => (), + _ => return Ok(false), + }; + AssertionError::fail(Location::Unknown, "the expected type") } pub fn bool(i: &Interner) -> ConstTree { @@ -47,8 +47,8 @@ pub fn bool(i: &Interner) -> ConstTree { ConstTree::tree([ (i.i("ifthenelse"), ConstTree::xfn(xfn_1ary(if_then_else))), (i.i("equals"), ConstTree::xfn(xfn_2ary(equals))), - (i.i("true"), ConstTree::atom(Boolean(true))), - (i.i("false"), ConstTree::atom(Boolean(false))), + (i.i("true"), ConstTree::atom(true)), + (i.i("false"), ConstTree::atom(false)), ]), )]) } diff --git a/src/systems/stl/conv.rs b/src/systems/stl/conv.rs index c206a44..ad15e1b 100644 --- a/src/systems/stl/conv.rs +++ b/src/systems/stl/conv.rs @@ -1,48 +1,45 @@ -use chumsky::Parser; use ordered_float::NotNan; -use super::ArithmeticError; -use crate::foreign::{xfn_1ary, ExternError, XfnResult}; +use super::Numeric; +use crate::error::AssertionError; +use crate::foreign::{xfn_1ary, Atom, XfnResult}; use crate::interner::Interner; -use crate::parse::{float_parser, int_parser}; -use crate::systems::AssertionError; -use crate::{ConstTree, Literal, Location}; +use crate::parse::parse_num; +use crate::{ConstTree, Location, OrcString}; + +fn to_numeric(a: Atom) -> XfnResult { + if let Some(n) = a.request::() { + return Ok(n); + } + if let Some(s) = a.request::() { + return parse_num(s.as_str()) + .map_err(|_| AssertionError::ext(Location::Unknown, "number syntax")); + } + AssertionError::fail(Location::Unknown, "string or number") +} /// parse a number. Accepts the same syntax Orchid does. -pub fn to_float(l: Literal) -> XfnResult { - match l { - Literal::Str(s) => float_parser() - .parse(s.as_str()) - .map(Literal::Num) - .map_err(|_| AssertionError::ext(Location::Unknown, "float syntax")), - n @ Literal::Num(_) => Ok(n), - Literal::Uint(i) => NotNan::new(i as f64) - .map(Literal::Num) - .map_err(|_| ArithmeticError::NaN.into_extern()), - } +pub fn to_float(a: Atom) -> XfnResult> { + to_numeric(a).map(|n| n.as_float()) } /// Parse an unsigned integer. Accepts the same formats Orchid does. If the /// input is a number, floors it. -pub fn to_uint(l: Literal) -> XfnResult { - match l { - Literal::Str(s) => int_parser() - .parse(s.as_str()) - .map(Literal::Uint) - .map_err(|_| AssertionError::ext(Location::Unknown, "int syntax")), - Literal::Num(n) => Ok(Literal::Uint(n.floor() as u64)), - i @ Literal::Uint(_) => Ok(i), - } +pub fn to_uint(a: Atom) -> XfnResult { + to_numeric(a).map(|n| match n { + Numeric::Float(f) => f.floor() as usize, + Numeric::Uint(i) => i, + }) } /// Convert a literal to a string using Rust's conversions for floats, chars and /// uints respectively -pub fn to_string(l: Literal) -> XfnResult { - Ok(match l { - Literal::Uint(i) => Literal::Str(i.to_string().into()), - Literal::Num(n) => Literal::Str(n.to_string().into()), - s @ Literal::Str(_) => s, - }) +pub fn to_string(a: Atom) -> XfnResult { + a.try_downcast::() + .or_else(|e| e.try_downcast::().map(|i| i.to_string().into())) + .or_else(|e| e.try_downcast::>().map(|i| i.to_string().into())) + .or_else(|e| e.try_downcast::().map(|i| i.to_string().into())) + .map_err(|_| AssertionError::ext(Location::Unknown, "string or number")) } pub fn conv(i: &Interner) -> ConstTree { diff --git a/src/systems/stl/exit_status.rs b/src/systems/stl/exit_status.rs new file mode 100644 index 0000000..6e88c20 --- /dev/null +++ b/src/systems/stl/exit_status.rs @@ -0,0 +1,27 @@ +use crate::foreign::{xfn_1ary, InertAtomic}; +use crate::{ConstTree, Interner}; + +/// An Orchid equivalent to Rust's binary exit status model +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ExitStatus { + /// unix exit code 0 + Success, + /// unix exit code 1 + Failure, +} + +impl InertAtomic for ExitStatus { + fn type_str() -> &'static str { "ExitStatus" } +} + +pub fn exit_status(i: &Interner) -> ConstTree { + let is_success = |es: ExitStatus| Ok(es == ExitStatus::Success); + ConstTree::namespace( + [i.i("exit_status")], + ConstTree::tree([ + (i.i("success"), ConstTree::atom(ExitStatus::Success)), + (i.i("failure"), ConstTree::atom(ExitStatus::Failure)), + (i.i("is_success"), ConstTree::xfn(xfn_1ary(is_success))), + ]), + ) +} diff --git a/src/systems/stl/fn.orc b/src/systems/stl/functional.orc similarity index 88% rename from src/systems/stl/fn.orc rename to src/systems/stl/functional.orc index 5daefc8..4ff9ef3 100644 --- a/src/systems/stl/fn.orc +++ b/src/systems/stl/functional.orc @@ -6,19 +6,19 @@ export const identity := \x.x Apply the function to the given value. Can be used to assign a concrete value in a cps assignment statement. ]-- -export const pass := \val.\cont. cont val +export const pass := \val. \cont. cont val --[ Apply the function to the given pair of values. Mainly useful to assign a concrete pair of values in a cps multi-assignment statement ]-- -export const pass2 := \a.\b.\cont. cont a b +export const pass2 := \a. \b. \cont. cont a b --[ A function that returns the given value for any input. Also useful as a "break" statement in a "do" block. ]-- export const return := \a. \b.a -export operators[$ |> =>] +export ::($, |>, =>) macro ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix) macro ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix diff --git a/src/systems/stl/known.orc b/src/systems/stl/known.orc index a63e78f..7e4b7d2 100644 --- a/src/systems/stl/known.orc +++ b/src/systems/stl/known.orc @@ -1,3 +1 @@ -export operators[ , ] - -export const foo := \a.a +export ::[,] diff --git a/src/systems/stl/list.orc b/src/systems/stl/list.orc index f6ed9e5..9e75d59 100644 --- a/src/systems/stl/list.orc +++ b/src/systems/stl/list.orc @@ -1,13 +1,14 @@ -import super::(option, fn::*, proc::*, loop::*, bool::*, known::*, num::*, tuple::*) +import super::option +import super::(functional::*, procedural::*, loop::*, bool::*, known::*, number::*, tuple::*) -const pair := \a.\b. \f. f a b +const pair := \a. \b. \f. f a b -- Constructors -export const cons := \hd.\tl. option::some t[hd, tl] +export const cons := \hd. \tl. option::some t[hd, tl] export const end := option::none -export const pop := \list.\default.\f. do{ +export const pop := \list. \default. \f. do{ cps tuple = list default; cps head, tail = tuple; f head tail @@ -19,7 +20,7 @@ export const pop := \list.\default.\f. do{ Fold each element into an accumulator using an `acc -> el -> acc`. This evaluates the entire list, and is always tail recursive. ]-- -export const fold := \list.\acc.\f. ( +export const fold := \list. \acc. \f. ( loop_over (list, acc) { cps head, list = pop list acc; let acc = f acc head; @@ -30,9 +31,9 @@ export const fold := \list.\acc.\f. ( Fold each element into an accumulator in reverse order. This evaulates the entire list, and is never tail recursive. ]-- -export const rfold := \list.\acc.\f. ( +export const rfold := \list. \acc. \f. ( recursive r (list) - pop list acc \head.\tail. + pop list acc \head. \tail. f (r tail) head ) @@ -40,7 +41,7 @@ export const rfold := \list.\acc.\f. ( Fold each element into a shared element with an `el -> el -> el`. This evaluates the entire list, and is never tail recursive. ]-- -export const reduce := \list.\f. do{ +export const reduce := \list. \f. do{ cps head, list = pop list option::none; option::some $ fold list head f } @@ -49,8 +50,8 @@ export const reduce := \list.\f. do{ Return a new list that contains only the elements from the input list for which the function returns true. This operation is lazy. ]-- -export const filter := \list.\f. ( - pop list end \head.\tail. +export const filter := \list. \f. ( + pop list end \head. \tail. if (f head) then cons head (filter tail f) else filter tail f @@ -59,9 +60,9 @@ export const filter := \list.\f. ( --[ Transform each element of the list with an `el -> any`. ]-- -export const map := \list.\f. ( +export const map := \list. \f. ( recursive r (list) - pop list end \head.\tail. + pop list end \head. \tail. cons (f head) (r tail) ) @@ -69,7 +70,7 @@ export const map := \list.\f. ( Skip `n` elements from the list and return the tail If `n` is not an integer, this returns `end`. ]-- -export const skip := \foo.\n. ( +export const skip := \foo. \n. ( loop_over (foo, n) { cps _head, foo = if n == 0 then return foo @@ -82,11 +83,11 @@ export const skip := \foo.\n. ( Return `n` elements from the list and discard the rest. This operation is lazy. ]-- -export const take := \list.\n. ( +export const take := \list. \n. ( recursive r (list, n) if n == 0 then end - else pop list end \head.\tail. + else pop list end \head. \tail. cons head $ r tail $ n - 1 ) @@ -94,7 +95,7 @@ export const take := \list.\n. ( Return the `n`th element from the list. This operation is tail recursive. ]-- -export const get := \list.\n. ( +export const get := \list. \n. ( loop_over (list, n) { cps head, list = pop list option::none; cps if n == 0 @@ -109,7 +110,7 @@ export const get := \list.\n. ( ]-- export const enumerate := \list. ( recursive r (list, n = 0) - pop list end \head.\tail. + pop list end \head. \tail. cons t[n, head] $ r tail $ n + 1 ) @@ -118,7 +119,7 @@ export const enumerate := \list. ( element on the return value of the next element with the tail passed to it. The continuation is passed to the very last argument. ]-- -export const chain := \list.\cont. loop_over (list) { +export const chain := \list. \cont. loop_over (list) { cps head, list = pop list cont; cps head; } diff --git a/src/systems/stl/loop.orc b/src/systems/stl/loop.orc index f7b7e9b..d69e2b5 100644 --- a/src/systems/stl/loop.orc +++ b/src/systems/stl/loop.orc @@ -1,4 +1,6 @@ -import super::proc::(;, do, =) +import super::procedural::* +import super::bool::* +import super::functional::(return, identity) import super::known::* --[ @@ -14,7 +16,7 @@ export const Y := \f.(\x.f (x x))(\x.f (x x)) non-tail recursion by using cps statements, but it's more ergonomic than [Y] and more flexible than [std::list::fold]. - To break out of the loop, use [std::fn::const] in a cps statement + To break out of the loop, use [std::fn::return] in a cps statement ]-- export macro loop_over (..$binds) { ...$body @@ -35,6 +37,16 @@ macro parse_binds (...$item) =0x1p250=> ( () ) +-- while loop +export macro statement ( + while ..$condition (..$binds) { + ...$body + } +) $next =0x5p129=> loop_over (..$binds) { + cps if (..$condition) then identity else return $next; + ...$body; +} + -- parse_bind converts items to pairs macro parse_bind ($name) =0x1p250=> ($name bind_no_value) macro parse_bind ($name = ...$value) =0x1p250=> ($name (...$value)) diff --git a/src/systems/stl/map.orc b/src/systems/stl/map.orc index 4570865..fa4e105 100644 --- a/src/systems/stl/map.orc +++ b/src/systems/stl/map.orc @@ -1,4 +1,4 @@ -import super::(bool::*, fn::*, known::*, list, option, loop::*, proc::*) +import super::(bool::*, functional::*, known::*, list, option, loop::*, procedural::*) import std::panic -- utilities for using lists as pairs @@ -17,7 +17,7 @@ export const snd := \l. ( -- constructors export const empty := list::end -export const add := \m.\k.\v. ( +export const add := \m. \k. \v. ( list::cons list::new[k, v] m @@ -26,7 +26,7 @@ export const add := \m.\k.\v. ( -- queries -- return the last occurrence of a key if exists -export const get := \m.\key. ( +export const get := \m. \key. ( loop_over (m) { cps record, m = list::pop m option::none; cps if fst record == key @@ -38,20 +38,20 @@ export const get := \m.\key. ( -- commands -- remove one occurrence of a key -export const del := \m.\k. ( +export const del := \m. \k. ( recursive r (m) - list::pop m list::end \head.\tail. + list::pop m list::end \head. \tail. if fst head == k then tail else list::cons head $ r tail ) -- remove all occurrences of a key -export const delall := \m.\k. ( +export const delall := \m. \k. ( list::filter m \record. fst record != k ) -- replace at most one occurrence of a key -export const set := \m.\k.\v. ( +export const set := \m. \k. \v. ( m |> del k |> add k v @@ -60,7 +60,7 @@ export const set := \m.\k.\v. ( -- ensure that there's only one instance of each key in the map export const normalize := \m. ( recursive r (m, normal=empty) - list::pop m normal \head.\tail. + list::pop m normal \head. \tail. r tail $ set normal (fst head) (snd head) ) diff --git a/src/systems/stl/mod.rs b/src/systems/stl/mod.rs index 4e8554c..b1b3b6f 100644 --- a/src/systems/stl/mod.rs +++ b/src/systems/stl/mod.rs @@ -1,18 +1,18 @@ //! Basic types and their functions, frequently used tools with no environmental //! dependencies. mod arithmetic_error; -mod bin; +mod binary; mod bool; mod conv; mod inspect; -mod num; +mod number; mod panic; mod state; mod stl_system; -mod str; +mod string; +mod exit_status; pub use arithmetic_error::ArithmeticError; -pub use bin::Binary; -pub use num::Numeric; +pub use binary::Binary; +pub use number::Numeric; pub use stl_system::StlConfig; - -pub use self::bool::Boolean; +pub use exit_status::ExitStatus; diff --git a/src/systems/stl/num.orc b/src/systems/stl/num.orc deleted file mode 100644 index e08df41..0000000 --- a/src/systems/stl/num.orc +++ /dev/null @@ -1,7 +0,0 @@ -export operators[ + - * % / ] - -macro ...$a + ...$b =0x2p36=> (add (...$a) (...$b)) -macro ...$a:1 - ...$b =0x2p36=> (subtract (...$a) (...$b)) -macro ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b)) -macro ...$a:1 % ...$b =0x1p36=> (remainder (...$a) (...$b)) -macro ...$a:1 / ...$b =0x1p36=> (divide (...$a) (...$b)) diff --git a/src/systems/stl/number.orc b/src/systems/stl/number.orc new file mode 100644 index 0000000..0db56e4 --- /dev/null +++ b/src/systems/stl/number.orc @@ -0,0 +1,16 @@ +import super::bool::* + +export ::(+, -, [*], %, /, <, >, <=, >=) + +const less_than_or_equal := \a. \b. a < b or a == b + +macro ...$a + ...$b =0x2p36=> (add (...$a) (...$b)) +macro ...$a:1 - ...$b =0x2p36=> (subtract (...$a) (...$b)) +macro ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b)) +macro ...$a:1 % ...$b =0x1p36=> (remainder (...$a) (...$b)) +macro ...$a:1 / ...$b =0x1p36=> (divide (...$a) (...$b)) +macro ...$a:1 < ...$b =0x3p36=> (less_than (...$a) (...$b)) +macro ...$a:1 > ...$b =0x3p36=> ((...$b) < (...$a)) +macro ...$a:1 <= ...$b =0x3p36=> (less_than_or_equal (...$a) (...$b)) +macro ...$a:1 >= ...$b =0x3p36=> ((...$b) <= (...$a)) + diff --git a/src/systems/stl/num.rs b/src/systems/stl/number.rs similarity index 59% rename from src/systems/stl/num.rs rename to src/systems/stl/number.rs index bbac9b8..66290af 100644 --- a/src/systems/stl/num.rs +++ b/src/systems/stl/number.rs @@ -3,13 +3,11 @@ use std::rc::Rc; use ordered_float::NotNan; use super::ArithmeticError; -use crate::foreign::{xfn_2ary, ExternError, ToClause, XfnResult}; +use crate::error::AssertionError; +use crate::foreign::{xfn_2ary, ExternError, ToClause, XfnResult, Atomic}; use crate::interpreted::TryFromExprInst; use crate::representations::interpreted::{Clause, ExprInst}; -use crate::representations::{Literal, Primitive}; -use crate::systems::cast_exprinst::get_literal; -use crate::systems::AssertionError; -use crate::{ConstTree, Interner}; +use crate::{ConstTree, Interner, Location}; // region: Numeric, type to handle floats and uints together @@ -17,24 +15,34 @@ use crate::{ConstTree, Interner}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Numeric { /// A nonnegative integer such as a size, index or count - Uint(u64), + Uint(usize), /// A float other than NaN. Orchid has no silent errors - Num(NotNan), + Float(NotNan), } impl Numeric { - fn as_f64(&self) -> f64 { + /// Return the enclosed float, or cast the enclosed int to a float + pub fn as_f64(&self) -> f64 { match self { - Numeric::Num(n) => **n, + Numeric::Float(n) => **n, Numeric::Uint(i) => *i as f64, } } + /// Returns the enclosed [NotNan], or casts and wraps the enclosed int + pub fn as_float(&self) -> NotNan { + match self { + Numeric::Float(n) => *n, + Numeric::Uint(i) => + NotNan::new(*i as f64).expect("ints cannot cast to NaN"), + } + } + /// Wrap a f64 in a Numeric - fn num(value: f64) -> Result> { + pub fn new(value: f64) -> Result> { if value.is_finite() { NotNan::new(value) - .map(Self::Num) + .map(Self::Float) .map_err(|_| ArithmeticError::NaN.into_extern()) } else { Err(ArithmeticError::Infinity.into_extern()) @@ -43,20 +51,17 @@ impl Numeric { } impl TryFromExprInst for Numeric { fn from_exi(exi: ExprInst) -> Result> { - match get_literal(exi)? { - (Literal::Uint(i), _) => Ok(Numeric::Uint(i)), - (Literal::Num(n), _) => Ok(Numeric::Num(n)), - (_, location) => AssertionError::fail(location, "an integer or number")?, - } + (exi.request()) + .ok_or_else(|| AssertionError::ext(Location::Unknown, "a numeric value")) } } impl ToClause for Numeric { fn to_clause(self) -> Clause { - Clause::P(Primitive::Literal(match self { - Numeric::Uint(i) => Literal::Uint(i), - Numeric::Num(n) => Literal::Num(n), - })) + match self { + Numeric::Uint(i) => i.atom_cls(), + Numeric::Float(n) => n.atom_cls(), + } } } @@ -70,19 +75,19 @@ pub fn add(a: Numeric, b: Numeric) -> XfnResult { .checked_add(b) .map(Numeric::Uint) .ok_or_else(|| ArithmeticError::Overflow.into_extern()), - (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a + b)), - (Numeric::Num(a), Numeric::Uint(b)) - | (Numeric::Uint(b), Numeric::Num(a)) => Numeric::num(*a + b as f64), + (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a + b)), + (Numeric::Float(a), Numeric::Uint(b)) + | (Numeric::Uint(b), Numeric::Float(a)) => Numeric::new(*a + b as f64), } } /// Subtract a number from another. Always returns Number. pub fn subtract(a: Numeric, b: Numeric) -> XfnResult { match (a, b) { - (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::num(a as f64 - b as f64), - (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a - b)), - (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a - b as f64), - (Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 - *b), + (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::new(a as f64 - b as f64), + (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a - b)), + (Numeric::Float(a), Numeric::Uint(b)) => Numeric::new(*a - b as f64), + (Numeric::Uint(a), Numeric::Float(b)) => Numeric::new(a as f64 - *b), } } @@ -94,9 +99,9 @@ pub fn multiply(a: Numeric, b: Numeric) -> XfnResult { .checked_mul(b) .map(Numeric::Uint) .ok_or_else(|| ArithmeticError::Overflow.into_extern()), - (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a * b)), - (Numeric::Uint(a), Numeric::Num(b)) - | (Numeric::Num(b), Numeric::Uint(a)) => Numeric::num(a as f64 * *b), + (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a * b)), + (Numeric::Uint(a), Numeric::Float(b)) + | (Numeric::Float(b), Numeric::Uint(a)) => Numeric::new(a as f64 * *b), } } @@ -107,7 +112,7 @@ pub fn divide(a: Numeric, b: Numeric) -> XfnResult { if b == 0.0 { return Err(ArithmeticError::DivByZero.into_extern()); } - Numeric::num(a / b) + Numeric::new(a / b) } /// Take the remainder of two numbers. If they're both uint, the output is @@ -118,23 +123,31 @@ pub fn remainder(a: Numeric, b: Numeric) -> XfnResult { .checked_rem(b) .map(Numeric::Uint) .ok_or_else(|| ArithmeticError::DivByZero.into_extern()), - (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a % b)), - (Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 % *b), - (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a % b as f64), + (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a % b)), + (Numeric::Uint(a), Numeric::Float(b)) => Numeric::new(a as f64 % *b), + (Numeric::Float(a), Numeric::Uint(b)) => Numeric::new(*a % b as f64), } } +pub fn less_than(a: Numeric, b: Numeric) -> XfnResult { + Ok(match (a, b) { + (Numeric::Uint(a), Numeric::Uint(b)) => a < b, + (a, b) => a.as_f64() < b.as_f64(), + }) +} + // endregion pub fn num(i: &Interner) -> ConstTree { ConstTree::tree([( - i.i("num"), + i.i("number"), ConstTree::tree([ (i.i("add"), ConstTree::xfn(xfn_2ary(add))), (i.i("subtract"), ConstTree::xfn(xfn_2ary(subtract))), (i.i("multiply"), ConstTree::xfn(xfn_2ary(multiply))), (i.i("divide"), ConstTree::xfn(xfn_2ary(divide))), (i.i("remainder"), ConstTree::xfn(xfn_2ary(remainder))), + (i.i("less_than"), ConstTree::xfn(xfn_2ary(less_than))), ]), )]) } diff --git a/src/systems/stl/option.orc b/src/systems/stl/option.orc index ab070eb..942dcb7 100644 --- a/src/systems/stl/option.orc +++ b/src/systems/stl/option.orc @@ -1,9 +1,9 @@ import std::panic -export const some := \v. \d.\f. f v -export const none := \d.\f. d +export const some := \v. \d. \f. f v +export const none := \d. \f. d -export const map := \option.\f. option none f +export const map := \option. \f. option none f export const flatten := \option. option none \opt. opt -export const flatmap := \option.\f. option none \opt. map opt f +export const flatmap := \option. \f. option none \opt. map opt f export const unwrap := \option. option (panic "value expected") \x.x diff --git a/src/systems/stl/prelude.orc b/src/systems/stl/prelude.orc index 3f3d507..3b81387 100644 --- a/src/systems/stl/prelude.orc +++ b/src/systems/stl/prelude.orc @@ -1,11 +1,13 @@ -import std::num::* -export ::[+ - * / %] -import std::str::* +import std::number::* +export ::[+ - * / % < > <= >=] +import std::string::* export ::[++] import std::bool::* -export ::([==], if, then, else, true, false) -import std::fn::* +export ::([== !=], if, then, else, true, false, and, or, not) +import std::functional::* export ::([$ |> =>], identity, pass, pass2, return) +import std::procedural::* +export ::(do, let, cps, [; =]) import std::tuple::* export ::(t) import std::tuple @@ -14,7 +16,7 @@ import std::map import std::option export ::(tuple, list, map, option) import std::loop::* -export ::(loop_over, recursive) +export ::(loop_over, recursive, while) import std::known::* export ::[,] diff --git a/src/systems/stl/proc.orc b/src/systems/stl/procedural.orc similarity index 74% rename from src/systems/stl/proc.orc rename to src/systems/stl/procedural.orc index 0c6ff4c..56454d3 100644 --- a/src/systems/stl/proc.orc +++ b/src/systems/stl/procedural.orc @@ -1,6 +1,4 @@ -import super::fn::=> - -export operators[ ; = ] +import super::functional::=> -- remove duplicate ;-s export macro do { @@ -8,12 +6,13 @@ export macro do { } =0x3p130=> do { ...$statement ; ...$rest } +-- modular operation block that returns a value export macro do { ...$statement ; ...$rest:1 } =0x2p130=> statement (...$statement) (do { ...$rest }) export macro do { ...$return } =0x1p130=> (...$return) - -export ::do +-- modular operation block that returns a CPS function +export macro do cps { ...$body } =0x1p130=> \cont. do { ...$body ; cont } export macro statement (let $name = ...$value) (...$next) =0x1p230=> ( ( \$name. ...$next) (...$value) diff --git a/src/systems/stl/result.orc b/src/systems/stl/result.orc index 4b26ed0..72eea1b 100644 --- a/src/systems/stl/result.orc +++ b/src/systems/stl/result.orc @@ -1,10 +1,10 @@ import std::panic -export const ok := \v. \fe.\fv. fv v -export const err := \e. \fe.\fv. fe e +export const ok := \v. \fe. \fv. fv v +export const err := \e. \fe. \fv. fe e -export const map := \result.\fv. result err fv -export const map_err := \result.\fe. result fe ok +export const map := \result. \fv. result err fv +export const map_err := \result. \fe. result fe ok export const flatten := \result. result err \res. res -export const and_then := \result.\f. result err \v. f v +export const and_then := \result. \f. result err \v. f v export const unwrap := \result. result (\e. panic "value expected") \v.v diff --git a/src/systems/stl/state.rs b/src/systems/stl/state.rs index bbe5c52..7539b03 100644 --- a/src/systems/stl/state.rs +++ b/src/systems/stl/state.rs @@ -1,6 +1,5 @@ -use std::cell::RefCell; use std::ops::Deref; -use std::rc::Rc; +use std::sync::{Arc, RwLock}; use crate::foreign::cps_box::{const_cps, init_cps, CPSBox}; use crate::foreign::{xfn_1ary, Atomic, InertAtomic, XfnResult}; @@ -10,9 +9,9 @@ use crate::systems::codegen::call; use crate::{ConstTree, Interner}; #[derive(Debug, Clone)] -pub struct State(Rc>); +pub struct State(Arc>); impl InertAtomic for State { - fn type_str() -> &'static str { "a stateful container" } + fn type_str() -> &'static str { "State" } } #[derive(Debug, Clone)] @@ -30,21 +29,21 @@ fn set_state(s: State) -> XfnResult { Ok(init_cps(2, SetStateCmd(s))) } fn new_state_handler(cmd: CPSBox) -> Result { let (_, default, handler) = cmd.unpack2(); - let state = State(Rc::new(RefCell::new(default))); + let state = State(Arc::new(RwLock::new(default))); Ok(call(handler, [state.atom_exi()]).wrap()) } fn set_state_handler(cmd: CPSBox) -> Result { let (SetStateCmd(state), value, handler) = cmd.unpack2(); - *state.0.as_ref().borrow_mut() = value; + *state.0.as_ref().write().unwrap() = value; Ok(handler) } fn get_state_handler(cmd: CPSBox) -> Result { let (GetStateCmd(state), handler) = cmd.unpack1(); - let val = match Rc::try_unwrap(state.0) { - Ok(cell) => cell.into_inner(), - Err(rc) => rc.as_ref().borrow().deref().clone(), + let val = match Arc::try_unwrap(state.0) { + Ok(lock) => lock.into_inner().unwrap(), + Err(arc) => arc.as_ref().read().unwrap().deref().clone(), }; Ok(call(handler, [val]).wrap()) } diff --git a/src/systems/stl/stl_system.rs b/src/systems/stl/stl_system.rs index fad381d..fefa1e6 100644 --- a/src/systems/stl/stl_system.rs +++ b/src/systems/stl/stl_system.rs @@ -1,15 +1,17 @@ #![allow(non_upper_case_globals)] + use hashbrown::HashMap; use rust_embed::RustEmbed; -use super::bin::bin; +use super::binary::bin; use super::bool::bool; use super::conv::conv; +use super::exit_status::exit_status; use super::inspect::inspect; -use super::num::num; +use super::number::num; use super::panic::panic; use super::state::{state_handlers, state_lib}; -use super::str::str; +use super::string::str; use crate::facade::{IntoSystem, System}; use crate::interner::Interner; use crate::pipeline::file_loader::embed_to_map; @@ -32,10 +34,16 @@ struct StlEmbed; impl IntoSystem<'static> for StlConfig { fn into_system(self, i: &Interner) -> System<'static> { - let pure_fns = - conv(i) + bool(i) + str(i) + num(i) + bin(i) + panic(i) + state_lib(i); + let pure_tree = bin(i) + + bool(i) + + conv(i) + + exit_status(i) + + num(i) + + panic(i) + + state_lib(i) + + str(i); let mk_impure_fns = || inspect(i); - let fns = if self.impure { pure_fns + mk_impure_fns() } else { pure_fns }; + let fns = if self.impure { pure_tree + mk_impure_fns() } else { pure_tree }; System { name: vec!["std".to_string()], constants: HashMap::from([(i.i("std"), fns)]), @@ -49,6 +57,8 @@ impl IntoSystem<'static> for StlConfig { }]), }], handlers: state_handlers(), + lexer_plugin: None, + line_parser: None, } } } diff --git a/src/systems/stl/str.orc b/src/systems/stl/string.orc similarity index 63% rename from src/systems/stl/str.orc rename to src/systems/stl/string.orc index 1868b84..7d244a9 100644 --- a/src/systems/stl/str.orc +++ b/src/systems/stl/string.orc @@ -1,10 +1,8 @@ -import super::(proc::*, bool::*, panic) - -export operators[++] +import super::(procedural::*, bool::*, panic) export macro ...$a ++ ...$b =0x4p36=> (concat (...$a) (...$b)) -export const char_at := \s.\i. do{ +export const char_at := \s. \i. do{ let slc = slice s i 1; if len slc == 1 then slc diff --git a/src/systems/stl/str.rs b/src/systems/stl/string.rs similarity index 67% rename from src/systems/stl/str.rs rename to src/systems/stl/string.rs index 4da708b..da225a4 100644 --- a/src/systems/stl/str.rs +++ b/src/systems/stl/string.rs @@ -1,31 +1,31 @@ use unicode_segmentation::UnicodeSegmentation; -use crate::foreign::{xfn_1ary, xfn_2ary, xfn_3ary, XfnResult}; +use crate::error::RuntimeError; +use crate::foreign::{ + xfn_1ary, xfn_2ary, xfn_3ary, Atomic, ToClause, XfnResult, +}; use crate::interner::Interner; use crate::interpreted::Clause; use crate::representations::OrcString; use crate::systems::codegen::{opt, tuple}; -use crate::systems::RuntimeError; use crate::utils::iter_find; -use crate::{ConstTree, Literal}; +use crate::ConstTree; -pub fn len(s: OrcString) -> XfnResult { - Ok(s.graphemes(true).count() as u64) -} +pub fn len(s: OrcString) -> XfnResult { Ok(s.graphemes(true).count()) } -pub fn size(s: OrcString) -> XfnResult { Ok(s.as_bytes().len() as u64) } +pub fn size(s: OrcString) -> XfnResult { Ok(s.as_bytes().len()) } /// Append a string to another pub fn concatenate(a: OrcString, b: OrcString) -> XfnResult { Ok(a.get_string() + b.as_str()) } -pub fn slice(s: OrcString, i: u64, len: u64) -> XfnResult { +pub fn slice(s: OrcString, i: usize, len: usize) -> XfnResult { let graphs = s.as_str().graphemes(true); if i == 0 { - return Ok(graphs.take(len as usize).collect::()); + return Ok(graphs.take(len).collect::()); } - let mut prefix = graphs.skip(i as usize - 1); + let mut prefix = graphs.skip(i - 1); if prefix.next().is_none() { return Err(RuntimeError::ext( "Character index out of bounds".to_string(), @@ -33,7 +33,7 @@ pub fn slice(s: OrcString, i: u64, len: u64) -> XfnResult { )); } let mut count = 0; - let ret = (prefix.take(len as usize)) + let ret = (prefix.take(len)) .map(|x| { count += 1; x @@ -52,19 +52,19 @@ pub fn slice(s: OrcString, i: u64, len: u64) -> XfnResult { pub fn find(haystack: OrcString, needle: OrcString) -> XfnResult { let haystack_graphs = haystack.as_str().graphemes(true); let found = iter_find(haystack_graphs, needle.as_str().graphemes(true)); - Ok(opt(found.map(|x| Literal::Uint(x as u64).into()))) + Ok(opt(found.map(|x| x.atom_exi()))) } -pub fn split(s: OrcString, i: u64) -> XfnResult { +pub fn split(s: OrcString, i: usize) -> XfnResult { let mut graphs = s.as_str().graphemes(true); - let a = graphs.by_ref().take(i as usize).collect::(); + let a = graphs.by_ref().take(i).collect::(); let b = graphs.collect::(); - Ok(tuple([a.into(), b.into()])) + Ok(tuple([a.to_exi(), b.to_exi()])) } pub fn str(i: &Interner) -> ConstTree { ConstTree::tree([( - i.i("str"), + i.i("string"), ConstTree::tree([ (i.i("concat"), ConstTree::xfn(xfn_2ary(concatenate))), (i.i("slice"), ConstTree::xfn(xfn_3ary(slice))), diff --git a/src/systems/stl/tuple.orc b/src/systems/stl/tuple.orc index 0a01f24..3dff411 100644 --- a/src/systems/stl/tuple.orc +++ b/src/systems/stl/tuple.orc @@ -1,11 +1,11 @@ -import super::(known::*, bool::*, num::*) +import super::(known::*, bool::*, number::*) -const discard_args := \n.\value. ( +const discard_args := \n. \value. ( if n == 0 then value else \_. discard_args (n - 1) value ) -export const pick := \tuple. \i.\n. tuple ( +export const pick := \tuple. \i. \n. tuple ( discard_args i \val. discard_args (n - 1 - i) val ) diff --git a/src/utils/ddispatch.rs b/src/utils/ddispatch.rs index a171a3d..3049e8a 100644 --- a/src/utils/ddispatch.rs +++ b/src/utils/ddispatch.rs @@ -5,16 +5,23 @@ use std::any::Any; /// A request for a value of an unknown type pub struct Request<'a>(&'a mut dyn Any); impl<'a> Request<'a> { - /// Checks whether a value of the given type would serve the request - pub fn can_serve(&self) -> bool { self.0.is::>() } + /// Checks if a value of the given type would serve the request, and the + /// request had not yet been served + pub fn can_serve(&self) -> bool { + self.0.downcast_ref::>().map_or(false, Option::is_none) + } /// Serve a value if it's the correct type - pub fn serve(&mut self, value: T) { self.serve_with(|| value) } + pub fn serve(&mut self, value: T) { + self.serve_with::(|| value) + } /// Invoke the callback to serve the request only if the return type matches pub fn serve_with(&mut self, provider: impl FnOnce() -> T) { - if let Some(slot) = self.0.downcast_mut() { - *slot = provider(); + if let Some(slot) = self.0.downcast_mut::>() { + if slot.is_none() { + *slot = Some(provider()); + } } } } diff --git a/src/utils/delete_cell.rs b/src/utils/delete_cell.rs index 18fc2e9..759c4c7 100644 --- a/src/utils/delete_cell.rs +++ b/src/utils/delete_cell.rs @@ -1,17 +1,16 @@ -use std::cell::RefCell; -use std::rc::Rc; +use std::sync::{Arc, Mutex}; -pub struct DeleteCell(pub Rc>>); +pub struct DeleteCell(pub Arc>>); impl DeleteCell { - pub fn new(t: T) -> Self { Self(Rc::new(RefCell::new(Some(t)))) } + pub fn new(t: T) -> Self { Self(Arc::new(Mutex::new(Some(t)))) } - pub fn take(&self) -> Option { self.0.borrow_mut().take() } + pub fn take(&self) -> Option { self.0.lock().unwrap().take() } pub fn clone_out(&self) -> Option where T: Clone, { - self.0.borrow().clone() + self.0.lock().unwrap().clone() } } impl Clone for DeleteCell { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 17fa168..4b52625 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -6,7 +6,7 @@ mod id_map; mod iter_find; pub mod never; pub mod poller; -pub mod pure_push; +pub mod pure_seq; pub mod rc_tools; mod replace_first; mod side; diff --git a/src/utils/poller.rs b/src/utils/poller.rs index a8c713a..7e350b1 100644 --- a/src/utils/poller.rs +++ b/src/utils/poller.rs @@ -55,7 +55,7 @@ pub struct Poller { receiver: Receiver, } -impl Poller { +impl Poller { pub fn new() -> (Sender, Self) { let (sender, receiver) = channel(); let this = Self { receiver, timers: BinaryHeap::new() }; @@ -79,7 +79,7 @@ impl Poller { &mut self, period: Duration, data: TRec, - ) -> impl Fn() + Clone { + ) -> impl Fn() + Send + Clone { let data_cell = DeleteCell::new(data); self.timers.push(Timer { expires: Instant::now() + period, diff --git a/src/utils/pure_push.rs b/src/utils/pure_seq.rs similarity index 69% rename from src/utils/pure_push.rs rename to src/utils/pure_seq.rs index 267c1c4..490a201 100644 --- a/src/utils/pure_push.rs +++ b/src/utils/pure_seq.rs @@ -18,3 +18,11 @@ pub fn pushed_ref<'a, T: Clone + 'a>( ) -> Vec { vec.into_iter().cloned().chain(iter::once(t)).collect() } + +/// Pure version of [Iterator::next] +/// +/// Remove an item from the iterator. If successful, returns the item and the +/// iterator. If the iterator is empty it is consumed. +pub fn next(mut i: I) -> Option<(I::Item, I)> { + i.next().map(|item| (item, i)) +} diff --git a/src/utils/rc_tools.rs b/src/utils/rc_tools.rs index db031cd..8d397be 100644 --- a/src/utils/rc_tools.rs +++ b/src/utils/rc_tools.rs @@ -1,9 +1,13 @@ -use std::rc::Rc; +use std::{rc::Rc, sync::Arc}; pub fn rc_to_owned(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| rc.as_ref().clone()) } +pub fn arc_to_owned(rc: Arc) -> T { + Arc::try_unwrap(rc).unwrap_or_else(|rc| rc.as_ref().clone()) +} + pub fn map_rc(rc: Rc, pred: impl FnOnce(T) -> U) -> Rc { Rc::new(pred(rc_to_owned(rc))) } diff --git a/src/utils/substack.rs b/src/utils/substack.rs index 2e21399..f4b75d4 100644 --- a/src/utils/substack.rs +++ b/src/utils/substack.rs @@ -98,7 +98,7 @@ impl<'a, T> SubstackIterator<'a, T> { impl<'a, T> Copy for SubstackIterator<'a, T> {} impl<'a, T> Clone for SubstackIterator<'a, T> { - fn clone(&self) -> Self { Self { curr: self.curr } } + fn clone(&self) -> Self { *self } } impl<'a, T> Iterator for SubstackIterator<'a, T> {