September-october commit

- manual parser
- stl refinements
- all language constructs are now Send
This commit is contained in:
2023-10-11 18:27:50 +01:00
parent 56679dcc01
commit 86e520e8b8
127 changed files with 1666 additions and 1872 deletions

173
Cargo.lock generated
View File

@@ -2,17 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "ahash" name = "ahash"
version = "0.8.3" version = "0.8.3"
@@ -137,16 +126,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 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]] [[package]]
name = "clap" name = "clap"
version = "4.3.4" version = "4.3.4"
@@ -204,12 +183,6 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.7" version = "0.2.7"
@@ -238,19 +211,6 @@ dependencies = [
"typenum", "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]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -261,16 +221,6 @@ dependencies = [
"crypto-common", "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]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.11" version = "1.0.11"
@@ -320,17 +270,6 @@ dependencies = [
"version_check", "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]] [[package]]
name = "globset" name = "globset"
version = "0.4.10" version = "0.4.10"
@@ -344,22 +283,13 @@ dependencies = [
"regex", "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]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.0" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
dependencies = [ dependencies = [
"ahash 0.8.3", "ahash",
"allocator-api2", "allocator-api2",
] ]
@@ -451,36 +381,32 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.17.1" version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]] [[package]]
name = "orchidlang" name = "orchidlang"
version = "0.2.2" version = "0.2.2"
dependencies = [ dependencies = [
"chumsky",
"clap", "clap",
"derive_more",
"duplicate",
"dyn-clone", "dyn-clone",
"hashbrown 0.14.0", "hashbrown",
"itertools", "itertools",
"ordered-float", "ordered-float",
"paste", "paste",
"polling", "polling",
"rust-embed", "rust-embed",
"take_mut", "take_mut",
"thiserror",
"trait-set", "trait-set",
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "3.7.0" version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213" checksum = "e3a540f3e3b3d7929c884e46d093d344e4e5bdeed54d08bf007df50c93cc85d5"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@@ -511,30 +437,6 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.56" version = "1.0.56"
@@ -544,15 +446,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "psm"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.26" version = "1.0.26"
@@ -614,15 +507,6 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.37.19" version = "0.37.19"
@@ -659,12 +543,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "semver"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.160" version = "1.0.160"
@@ -682,19 +560,6 @@ dependencies = [
"digest", "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]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@@ -729,26 +594,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" 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]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.37" version = "0.1.37"
@@ -817,12 +662,6 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@@ -22,18 +22,14 @@ doc = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
thiserror = "1.0"
chumsky = "0.9"
hashbrown = "0.14" hashbrown = "0.14"
ordered-float = "3.7" ordered-float = "4.1"
itertools = "0.11" itertools = "0.11"
dyn-clone = "1.0" dyn-clone = "1.0"
clap = { version = "4.3", features = ["derive"] } clap = { version = "4.3", features = ["derive"] }
trait-set = "0.3" trait-set = "0.3"
paste = "1.0" paste = "1.0"
rust-embed = { version = "8.0", features = ["include-exclude"] } rust-embed = { version = "8.0", features = ["include-exclude"] }
duplicate = "1.0.0"
take_mut = "0.2.2" take_mut = "0.2.2"
unicode-segmentation = "1.10.1" unicode-segmentation = "1.10.1"
polling = "3.0.0" polling = "3.0.0"
derive_more = "0.99.17"

View File

@@ -7,9 +7,7 @@ An experimental lazy, pure functional programming language designed to be embedd
## Usage ## Usage
TODO 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.
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.
## Design ## Design

View File

@@ -2,12 +2,6 @@ This document is a wishlist, its items aren't ordered in any way other than inli
# Language # 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 ## Typeclasses
Elixir-style protocols probably, only with n-ary dispatch which I saw in SICP-js 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 ## Async
Join allows to run code when a tuple of pending events all resolve on the event poller 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 ## New: Network
Event-driven I/O with single-fire events and resubscription to relay backpressure to the OS. Initially TCP Event-driven I/O with single-fire events and resubscription to relay backpressure to the OS. Initially TCP

View File

@@ -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{ export const main := do{
cps print "left operand: "; cps print "left operand: ";
@@ -6,7 +6,6 @@ export const main := do{
let a = to_float data; let a = to_float data;
cps print "operator: "; cps print "operator: ";
cps op = readln; cps op = readln;
let op = char_at op 0;
cps println ("you selected \"" ++ op ++ "\""); cps println ("you selected \"" ++ op ++ "\"");
cps print "right operand: "; cps print "right operand: ";
cps data = readln; cps data = readln;

View File

@@ -1,5 +1,4 @@
import system::(io, directfs, async) import system::(io, directfs, async)
import std::proc::*
import std::(to_string, to_uint, inspect) import std::(to_string, to_uint, inspect)
const folder_view := \path. \next. do{ const folder_view := \path. \next. do{
@@ -12,7 +11,7 @@ const folder_view := \path.\next. do{
|> list::chain; |> list::chain;
cps print "select an entry, or .. to move up: "; cps print "select an entry, or .. to move up: ";
cps choice = readln; cps choice = readln;
if (choice == "..\n") then do { if (choice == "..") then do {
let parent_path = directfs::pop_path path let parent_path = directfs::pop_path path
|> option::unwrap |> option::unwrap
|> tuple::pick 0 2; |> tuple::pick 0 2;

View File

@@ -1,5 +1,7 @@
import std::exit_status
const main := ( const main := (
println "Hello, world!" println "Hello, world!"
"success" exit_status::success
) )
-- main := "Hello, World!\n" -- main := "Hello, World!\n"

View File

@@ -1,4 +1,4 @@
import std::(proc::*, to_string) import std::to_string
export const main := do{ export const main := do{
let foo = list::new[1, 2, 3, 4, 5, 6]; let foo = list::new[1, 2, 3, 4, 5, 6];

View File

@@ -1,4 +1,4 @@
import std::(proc::*, to_string) import std::to_string
export const main := do{ export const main := do{
let foo = map::new[ let foo = map::new[

View File

@@ -3,13 +3,13 @@ mod cli;
use std::fs::File; use std::fs::File;
use std::io::BufReader; use std::io::BufReader;
use std::path::PathBuf; use std::path::PathBuf;
use std::process; use std::process::ExitCode;
use clap::Parser; use clap::Parser;
use itertools::Itertools; use itertools::Itertools;
use orchidlang::facade::{Environment, PreMacro}; use orchidlang::facade::{Environment, PreMacro};
use orchidlang::systems::asynch::AsynchSystem; use orchidlang::systems::asynch::AsynchSystem;
use orchidlang::systems::stl::StlConfig; use orchidlang::systems::stl::{ExitStatus, StlConfig};
use orchidlang::systems::{directfs, io, scheduler}; use orchidlang::systems::{directfs, io, scheduler};
use orchidlang::{ast, interpreted, interpreter, Interner, Sym, VName}; use orchidlang::{ast, interpreted, interpreter, Interner, Sym, VName};
@@ -80,7 +80,7 @@ fn print_for_debug(e: &ast::Expr<Sym>) {
} }
/// A little utility to step through the resolution of a macro set /// 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)) let (mut code, location) = (premacro.consts.get(&sym))
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
@@ -111,7 +111,7 @@ pub fn macro_debug(premacro: PreMacro, sym: Sym) {
}, },
"p" | "print" => print_for_debug(&code), "p" | "print" => print_for_debug(&code),
"d" | "dump" => print!("Rules: {}", premacro.repo), "d" | "dump" => print!("Rules: {}", premacro.repo),
"q" | "quit" => return, "q" | "quit" => return ExitCode::SUCCESS,
"h" | "help" => print!( "h" | "help" => print!(
"Available commands: "Available commands:
\t<blank>, n, next\t\ttake a step \t<blank>, 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(); let args = Args::parse();
args.chk_proj().unwrap_or_else(|e| panic!("{e}")); args.chk_proj().unwrap_or_else(|e| panic!("{e}"));
let dir = PathBuf::try_from(args.dir).unwrap(); let dir = PathBuf::try_from(args.dir).unwrap();
@@ -150,7 +150,7 @@ pub fn main() {
let premacro = env.load_dir(&dir, &main).unwrap(); let premacro = env.load_dir(&dir, &main).unwrap();
if args.dump_repo { if args.dump_repo {
println!("Parsed rules: {}", premacro.repo); println!("Parsed rules: {}", premacro.repo);
return; return ExitCode::SUCCESS;
} }
if !args.macro_debug.is_empty() { if !args.macro_debug.is_empty() {
let sym = i.i(&to_vname(&args.macro_debug, &i)); let sym = i.i(&to_vname(&args.macro_debug, &i));
@@ -160,15 +160,15 @@ pub fn main() {
proc.validate_refs().unwrap(); proc.validate_refs().unwrap();
let main = interpreted::Clause::Constant(i.i(&main)).wrap(); let main = interpreted::Clause::Constant(i.i(&main)).wrap();
let ret = proc.run(main, None).unwrap(); let ret = proc.run(main, None).unwrap();
let interpreter::Return { gas, state, inert } = ret; let interpreter::Return { state, inert, .. } = ret;
drop(proc); drop(proc);
if inert { assert!(inert, "Gas is not used, only inert data should be yielded");
println!("Settled at {}", state.expr().clause); match state.clone().downcast::<ExitStatus>() {
if let Some(g) = gas { Ok(ExitStatus::Success) => ExitCode::SUCCESS,
println!("Remaining gas: {g}") Ok(ExitStatus::Failure) => ExitCode::FAILURE,
} Err(_) => {
} else if gas == Some(0) { println!("{}", state.expr().clause);
eprintln!("Ran out of gas!"); ExitCode::SUCCESS
process::exit(-1); },
} }
} }

View File

@@ -19,12 +19,12 @@ impl AssertionError {
location: Location, location: Location,
message: &'static str, message: &'static str,
) -> Result<T, Rc<dyn ExternError>> { ) -> Result<T, Rc<dyn ExternError>> {
return Err(Self::ext(location, message)); Err(Self::ext(location, message))
} }
/// Construct and upcast to [ExternError] /// Construct and upcast to [ExternError]
pub fn ext(location: Location, message: &'static str) -> Rc<dyn ExternError> { pub fn ext(location: Location, message: &'static str) -> Rc<dyn ExternError> {
return Self { location, message }.into_extern(); Self { location, message }.into_extern()
} }
} }

View File

@@ -1,4 +1,4 @@
use std::rc::Rc; use std::sync::Arc;
use itertools::Itertools; use itertools::Itertools;
@@ -10,9 +10,9 @@ use crate::VName;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ImportAll { pub struct ImportAll {
/// The file containing the offending import /// The file containing the offending import
pub offender_file: Rc<VName>, pub offender_file: Arc<VName>,
/// The module containing the offending import /// The module containing the offending import
pub offender_mod: Rc<VName>, pub offender_mod: Arc<VName>,
} }
impl ProjectError for ImportAll { impl ProjectError for ImportAll {
fn description(&self) -> &str { "a top-level glob import was used" } fn description(&self) -> &str { "a top-level glob import was used" }

View File

@@ -8,6 +8,8 @@ mod project_error;
mod too_many_supers; mod too_many_supers;
mod unexpected_directory; mod unexpected_directory;
mod visibility_mismatch; mod visibility_mismatch;
mod assertion_error;
mod runtime_error;
pub use conflicting_roles::ConflictingRoles; pub use conflicting_roles::ConflictingRoles;
pub use import_all::ImportAll; pub use import_all::ImportAll;
@@ -18,3 +20,5 @@ pub use project_error::{ErrorPosition, ProjectError, ProjectResult};
pub use too_many_supers::TooManySupers; pub use too_many_supers::TooManySupers;
pub use unexpected_directory::UnexpectedDirectory; pub use unexpected_directory::UnexpectedDirectory;
pub use visibility_mismatch::VisibilityMismatch; pub use visibility_mismatch::VisibilityMismatch;
pub use assertion_error::AssertionError;
pub use runtime_error::RuntimeError;

View File

@@ -1,4 +1,4 @@
use std::rc::Rc; use std::sync::Arc;
use super::{ErrorPosition, ProjectError}; use super::{ErrorPosition, ProjectError};
use crate::representations::location::Location; use crate::representations::location::Location;
@@ -25,14 +25,14 @@ impl ProjectError for NotExported {
Box::new( Box::new(
[ [
ErrorPosition { ErrorPosition {
location: Location::File(Rc::new(self.file.clone())), location: Location::File(Arc::new(self.file.clone())),
message: Some(format!( message: Some(format!(
"{} isn't exported", "{} isn't exported",
Interner::extern_all(&self.subpath).join("::") Interner::extern_all(&self.subpath).join("::")
)), )),
}, },
ErrorPosition { ErrorPosition {
location: Location::File(Rc::new(self.referrer_file.clone())), location: Location::File(Arc::new(self.referrer_file.clone())),
message: Some(format!( message: Some(format!(
"{} cannot see this symbol", "{} cannot see this symbol",
Interner::extern_all(&self.referrer_subpath).join("::") Interner::extern_all(&self.referrer_subpath).join("::")

View File

@@ -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<VName>,
/// 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<String>],
prefix: &[Tok<String>],
path: &[Tok<String>],
orig: &ProjectModule<impl NameLike>,
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)))
}
}

View File

@@ -17,12 +17,12 @@ impl RuntimeError {
message: String, message: String,
operation: &'static str, operation: &'static str,
) -> Result<T, Rc<dyn ExternError>> { ) -> Result<T, Rc<dyn ExternError>> {
return Err(Self { message, operation }.into_extern()); Err(Self { message, operation }.into_extern())
} }
/// Construct and upcast to [ExternError] /// Construct and upcast to [ExternError]
pub fn ext(message: String, operation: &'static str) -> Rc<dyn ExternError> { pub fn ext(message: String, operation: &'static str) -> Rc<dyn ExternError> {
return Self { message, operation }.into_extern(); Self { message, operation }.into_extern()
} }
} }

View File

@@ -1,4 +1,4 @@
use std::rc::Rc; use std::sync::Arc;
use super::ProjectError; use super::ProjectError;
use crate::{Interner, Location, VName}; use crate::{Interner, Location, VName};
@@ -16,7 +16,7 @@ impl ProjectError for UnexpectedDirectory {
to a directory" to a directory"
} }
fn one_position(&self) -> crate::Location { fn one_position(&self) -> crate::Location {
Location::File(Rc::new(self.path.clone())) Location::File(Arc::new(self.path.clone()))
} }
fn message(&self) -> String { fn message(&self) -> String {

View File

@@ -1,4 +1,4 @@
use std::rc::Rc; use std::sync::Arc;
use super::project_error::ProjectError; use super::project_error::ProjectError;
use crate::representations::location::Location; use crate::representations::location::Location;
@@ -23,6 +23,6 @@ impl ProjectError for VisibilityMismatch {
) )
} }
fn one_position(&self) -> Location { fn one_position(&self) -> Location {
Location::File(Rc::new(self.file.clone())) Location::File(Arc::new(self.file.clone()))
} }
} }

View File

@@ -42,6 +42,8 @@ impl<'a> Environment<'a> {
let system_tree = from_const_tree(sys.constants.clone(), &sys.vname(i)); let system_tree = from_const_tree(sys.constants.clone(), &sys.vname(i));
tree = ProjectTree(never::unwrap_always(tree.0.overlay(system_tree.0))); 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![]; let mut prelude = vec![];
for sys in systems.iter() { for sys in systems.iter() {
if !sys.code.is_empty() { if !sys.code.is_empty() {
@@ -50,9 +52,13 @@ impl<'a> Environment<'a> {
&|k| sys.load_file(k), &|k| sys.load_file(k),
&tree, &tree,
&prelude, &prelude,
&lexer_plugins,
&line_parsers,
i, 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); prelude.extend_from_slice(&sys.prelude);
} }
Ok(CompiledEnv { prelude, tree, systems }) Ok(CompiledEnv { prelude, tree, systems })
@@ -67,11 +73,19 @@ impl<'a> Environment<'a> {
let i = self.i; let i = self.i;
let CompiledEnv { prelude, systems, tree } = self.compile()?; let CompiledEnv { prelude, systems, tree } = self.compile()?;
let file_cache = file_loader::mk_dir_cache(dir.to_path_buf()); 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::<Vec<_>>();
let line_parsers = (systems.iter())
.filter_map(|s| s.line_parser.as_deref())
.collect::<Vec<_>>();
let vname_tree = parse_layer( let vname_tree = parse_layer(
iter::once(target), iter::once(target),
&|path| file_cache.find(path), &|path| file_cache.find(path),
&tree, &tree,
&prelude, &prelude,
&lexer_plugins,
&line_parsers,
i, i,
)?; )?;
let tree = vname_to_sym_tree(vname_tree, i); let tree = vname_to_sym_tree(vname_tree, i);

View File

@@ -1,5 +1,5 @@
use std::iter; use std::iter;
use std::rc::Rc; use std::sync::Arc;
use hashbrown::HashMap; use hashbrown::HashMap;
@@ -50,7 +50,7 @@ impl<'a> PreMacro<'a> {
.unwrap_or_else(|_| panic!("path sourced from symbol names")); .unwrap_or_else(|_| panic!("path sourced from symbol names"));
(origin.extra.file.as_ref()).cloned() (origin.extra.file.as_ref()).cloned()
}) })
.map(|p| Location::File(Rc::new(p))) .map(|p| Location::File(Arc::new(p)))
.unwrap_or(Location::Unknown); .unwrap_or(Location::Unknown);
(name, (expr, location)) (name, (expr, location))
}) })

View File

@@ -2,6 +2,7 @@ use hashbrown::HashMap;
use crate::error::{ErrorPosition, ProjectError}; use crate::error::{ErrorPosition, ProjectError};
use crate::interpreter::HandlerTable; use crate::interpreter::HandlerTable;
use crate::parse::{LexerPlugin, LineParser};
use crate::pipeline::file_loader::{IOResult, Loaded}; use crate::pipeline::file_loader::{IOResult, Loaded};
use crate::sourcefile::FileEntry; use crate::sourcefile::FileEntry;
use crate::utils::boxed_iter::box_empty; use crate::utils::boxed_iter::box_empty;
@@ -23,6 +24,13 @@ pub struct System<'a> {
pub prelude: Vec<FileEntry>, pub prelude: Vec<FileEntry>,
/// Handlers for actions defined in this system /// Handlers for actions defined in this system
pub handlers: HandlerTable<'a>, 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<Box<dyn LexerPlugin>>,
/// Parser that processes custom line types into their representation in the
/// module tree
pub line_parser: Option<Box<dyn LineParser>>,
} }
impl<'a> System<'a> { impl<'a> System<'a> {
/// Intern the name of the system so that it can be used as an Orchid /// Intern the name of the system so that it can be used as an Orchid

View File

@@ -1,13 +1,16 @@
use std::any::Any; use std::any::Any;
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc;
use dyn_clone::DynClone; 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::interpreter::{Context, RuntimeError};
use crate::representations::interpreted::Clause; use crate::representations::interpreted::Clause;
use crate::utils::ddispatch::Responder; use crate::utils::ddispatch::Responder;
use crate::Primitive;
/// Information returned by [Atomic::run]. This mirrors /// Information returned by [Atomic::run]. This mirrors
/// [crate::interpreter::Return] but with a clause instead of an Expr. /// [crate::interpreter::Return] but with a clause instead of an Expr.
@@ -24,8 +27,18 @@ pub struct AtomicReturn {
/// Returned by [Atomic::run] /// Returned by [Atomic::run]
pub type AtomicResult = Result<AtomicReturn, RuntimeError>; pub type AtomicResult = Result<AtomicReturn, RuntimeError>;
/// 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 /// 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 where
Self: 'static, Self: 'static,
{ {
@@ -54,7 +67,7 @@ where
where where
Self: Sized, 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 /// 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, /// inert at which point the [Atom] will validate and process the argument,
/// returning a different [Atom] intended for processing by external code, a new /// returning a different [Atom] intended for processing by external code, a new
/// [ExternFn] to capture an additional argument, or an Orchid expression /// [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<dyn Atomic>); pub struct Atom(pub Box<dyn Atomic>);
impl Atom { impl Atom {
/// Wrap an [Atomic] in a type-erased box /// Wrap an [Atomic] in a type-erased box
@@ -84,23 +97,26 @@ impl Atom {
/// Get the contained data /// Get the contained data
#[must_use] #[must_use]
pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic } pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic }
/// Attempt to downcast contained data to a specific type /// Test the type of the contained data without downcasting
pub fn try_cast<T: Atomic>(self) -> Result<T, Self> { #[must_use]
pub fn is<T: Atomic>(&self) -> bool { self.data().as_any_ref().is::<T>() }
/// Downcast contained data, panic if it isn't the specified type
#[must_use]
pub fn downcast<T: Atomic>(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<T: 'static>(&self) -> Option<T> { 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<T: Atomic>(self) -> Result<T, Self> {
match self.0.as_any_ref().is::<T>() { match self.0.as_any_ref().is::<T>() {
true => Ok(*self.0.as_any().downcast().expect("checked just above")), true => Ok(*self.0.as_any().downcast().expect("checked just above")),
false => Err(self), false => Err(self),
} }
} }
/// Test the type of the contained data without downcasting
#[must_use]
pub fn is<T: 'static>(&self) -> bool { self.data().as_any_ref().is::<T>() }
/// Downcast contained data, panic if it isn't the specified type
#[must_use]
pub fn cast<T: 'static>(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 { impl Clone for Atom {
@@ -109,6 +125,16 @@ impl Clone for Atom {
impl Debug for Atom { impl Debug for Atom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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<Self, Rc<dyn ExternError>> {
let loc = exi.location();
match exi.expr_val().clause {
Clause::Atom(a) => Ok(a),
_ => AssertionError::fail(loc, "atom"),
}
} }
} }

View File

@@ -7,12 +7,12 @@ use trait_set::trait_set;
use super::{Atomic, ExternFn, InertAtomic, XfnResult}; use super::{Atomic, ExternFn, InertAtomic, XfnResult};
use crate::interpreted::{Clause, ExprInst}; use crate::interpreted::{Clause, ExprInst};
use crate::interpreter::{Context, HandlerRes}; use crate::interpreter::{Context, HandlerRes};
use crate::utils::pure_push::pushed_ref; use crate::utils::pure_seq::pushed_ref;
use crate::ConstTree; use crate::ConstTree;
trait_set! { trait_set! {
/// A "well behaved" type that can be used as payload in a CPS box /// 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 /// A function to handle a CPS box with a specific payload
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &ExprInst) -> HandlerRes; pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &ExprInst) -> HandlerRes;
} }

View File

@@ -3,13 +3,12 @@ use std::fmt::{Debug, Display};
use std::hash::Hash; use std::hash::Hash;
use std::rc::Rc; use std::rc::Rc;
use dyn_clone::DynClone; use dyn_clone::{DynClone, clone_box};
use super::XfnResult; use super::XfnResult;
use crate::interpreted::ExprInst; use crate::interpreted::ExprInst;
use crate::interpreter::Context; use crate::interpreter::Context;
use crate::representations::interpreted::Clause; use crate::representations::interpreted::Clause;
use crate::Primitive;
/// Errors produced by external code /// Errors produced by external code
pub trait ExternError: Display { pub trait ExternError: Display {
@@ -34,7 +33,7 @@ impl Error for dyn ExternError {}
/// Represents an externally defined function from the perspective of /// Represents an externally defined function from the perspective of
/// the executor. Since Orchid lacks basic numerical operations, /// the executor. Since Orchid lacks basic numerical operations,
/// these are also external functions. /// these are also external functions.
pub trait ExternFn: DynClone { pub trait ExternFn: DynClone + Send {
/// Display name of the function /// Display name of the function
#[must_use] #[must_use]
fn name(&self) -> &str; fn name(&self) -> &str;
@@ -50,7 +49,7 @@ pub trait ExternFn: DynClone {
where where
Self: Sized + 'static, 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()) 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<dyn ExternFn + 'static>);
impl ExFn {
/// Combine the function with an argument to produce a new clause
pub fn apply(self, arg: ExprInst, ctx: Context) -> XfnResult<Clause> {
self.0.apply(arg, ctx)
}
}
impl Clone for ExFn {
fn clone(&self) -> Self {
Self(clone_box(self.0.as_ref()))
}
}

View File

@@ -2,6 +2,7 @@ use std::fmt::Debug;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use super::atom::StrictEq;
use super::{ use super::{
Atomic, AtomicResult, AtomicReturn, ExternError, ExternFn, XfnResult, Atomic, AtomicResult, AtomicReturn, ExternError, ExternFn, XfnResult,
}; };
@@ -9,7 +10,7 @@ use crate::ddispatch::Responder;
use crate::interpreted::{Clause, ExprInst, TryFromExprInst}; use crate::interpreted::{Clause, ExprInst, TryFromExprInst};
use crate::interpreter::{run, Context, Return}; use crate::interpreter::{run, Context, Return};
use crate::systems::codegen::{opt, res}; 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 /// 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 /// 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 { pub trait ToClause: Clone {
/// Convert the type to a [Clause]. /// Convert the type to a [Clause].
fn to_clause(self) -> Clause; fn to_clause(self) -> Clause;
/// Convert to an expression instance via [ToClause].
fn to_exi(self) -> ExprInst { self.to_clause().wrap() }
} }
impl<T: Atomic + Clone> ToClause for T { impl<T: Atomic + Clone> ToClause for T {
@@ -28,14 +31,8 @@ impl ToClause for Clause {
impl ToClause for ExprInst { impl ToClause for ExprInst {
fn to_clause(self) -> Clause { self.expr_val().clause } 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 { impl ToClause for String {
fn to_clause(self) -> Clause { OrcString::from(self).cls() } fn to_clause(self) -> Clause { OrcString::from(self).atom_cls() }
} }
impl<T: ToClause> ToClause for Option<T> { impl<T: ToClause> ToClause for Option<T> {
fn to_clause(self) -> Clause { opt(self.map(|t| t.to_clause().wrap())) } fn to_clause(self) -> Clause { opt(self.map(|t| t.to_clause().wrap())) }
@@ -59,31 +56,28 @@ pub struct Param<T, U, F> {
_t: PhantomData<T>, _t: PhantomData<T>,
_u: PhantomData<U>, _u: PhantomData<U>,
} }
unsafe impl<T, U, F: Send> Send for Param<T, U, F> {}
impl<T, U, F> Param<T, U, F> { impl<T, U, F> Param<T, U, F> {
/// Wrap a new function in a parametric struct /// Wrap a new function in a parametric struct
pub fn new(f: F) -> Self pub fn new(f: F) -> Self
where where
F: FnOnce(T) -> Result<U, Rc<dyn ExternError>>, F: FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
{ {
Self { data: f, _t: PhantomData::default(), _u: PhantomData::default() } Self { data: f, _t: PhantomData, _u: PhantomData }
} }
/// Take out the function /// Take out the function
pub fn get(self) -> F { self.data } pub fn get(self) -> F { self.data }
} }
impl<T, U, F: Clone> Clone for Param<T, U, F> { impl<T, U, F: Clone> Clone for Param<T, U, F> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self { data: self.data.clone(), _t: PhantomData, _u: PhantomData }
data: self.data.clone(),
_t: PhantomData::default(),
_u: PhantomData::default(),
}
} }
} }
impl< impl<
T: 'static + TryFromExprInst, T: 'static + TryFromExprInst,
U: 'static + ToClause, U: 'static + ToClause,
F: 'static + Clone + FnOnce(T) -> Result<U, Rc<dyn ExternError>>, F: 'static + Clone + Send + FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
> ToClause for Param<T, U, F> > ToClause for Param<T, U, F>
{ {
fn to_clause(self) -> Clause { self.xfn_cls() } fn to_clause(self) -> Clause { self.xfn_cls() }
@@ -93,6 +87,11 @@ struct FnMiddleStage<T, U, F> {
argument: ExprInst, argument: ExprInst,
f: Param<T, U, F>, f: Param<T, U, F>,
} }
impl<T, U, F> StrictEq for FnMiddleStage<T, U, F> {
fn strict_eq(&self, _other: &dyn std::any::Any) -> bool {
unimplemented!("This should never be able to appear in a pattern")
}
}
impl<T, U, F: Clone> Clone for FnMiddleStage<T, U, F> { impl<T, U, F: Clone> Clone for FnMiddleStage<T, U, F> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@@ -110,7 +109,7 @@ impl<T, U, F> Responder for FnMiddleStage<T, U, F> {}
impl< impl<
T: 'static + TryFromExprInst, T: 'static + TryFromExprInst,
U: 'static + ToClause, U: 'static + ToClause,
F: 'static + Clone + FnOnce(T) -> Result<U, Rc<dyn ExternError>>, F: 'static + Clone + FnOnce(T) -> Result<U, Rc<dyn ExternError>> + Send,
> Atomic for FnMiddleStage<T, U, F> > Atomic for FnMiddleStage<T, U, F>
{ {
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self } fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
@@ -128,7 +127,7 @@ impl<
impl< impl<
T: 'static + TryFromExprInst, T: 'static + TryFromExprInst,
U: 'static + ToClause, U: 'static + ToClause,
F: 'static + Clone + FnOnce(T) -> Result<U, Rc<dyn ExternError>>, F: 'static + Clone + Send + FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
> ExternFn for Param<T, U, F> > ExternFn for Param<T, U, F>
{ {
fn name(&self) -> &str { "anonymous Rust function" } fn name(&self) -> &str { "anonymous Rust function" }
@@ -155,16 +154,16 @@ pub mod constructors {
" Orchid function. See also Constraints summarized:\n\n" " Orchid function. See also Constraints summarized:\n\n"
"- the callback must live as long as `'static`\n" "- the callback must live as long as `'static`\n"
"- All arguments must implement [TryFromExprInst]\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" "- the return type must implement [ToClause].\n\n"
] ]
#[doc = "Other arities: " $( "[xfn_" $alt "ary], " )+ ] #[doc = "Other arities: " $( "[xfn_" $alt "ary], " )+ ]
pub fn [< xfn_ $number ary >] < pub fn [< xfn_ $number ary >] <
$( $t : TryFromExprInst + Clone + 'static, )* $( $t : TryFromExprInst + Clone + Send + 'static, )*
TLast: TryFromExprInst + 'static, TLast: TryFromExprInst + 'static,
TReturn: ToClause + 'static, TReturn: ToClause + Send + 'static,
TFunction: FnOnce( $( $t , )* TLast ) TFunction: FnOnce( $( $t , )* TLast )
-> Result<TReturn, Rc<dyn ExternError>> + Clone + 'static -> Result<TReturn, Rc<dyn ExternError>> + Clone + Send + 'static
>(function: TFunction) -> impl ExternFn { >(function: TFunction) -> impl ExternFn {
xfn_variant!(@BODY_LOOP function xfn_variant!(@BODY_LOOP function
( $( ( $t [< $t:lower >] ) )* ) ( $( ( $t [< $t:lower >] ) )* )

View File

@@ -2,22 +2,25 @@ use std::any::Any;
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc; use std::rc::Rc;
use ordered_float::NotNan;
use super::atom::StrictEq;
use super::{AtomicResult, AtomicReturn, ExternError}; use super::{AtomicResult, AtomicReturn, ExternError};
use crate::error::AssertionError;
#[allow(unused)] // for doc #[allow(unused)] // for doc
// use crate::define_fn; // use crate::define_fn;
use crate::foreign::Atomic; use crate::foreign::Atomic;
use crate::interpreted::{Clause, Expr, ExprInst, TryFromExprInst}; use crate::interpreted::{Clause, Expr, ExprInst, TryFromExprInst};
use crate::interpreter::Context; use crate::interpreter::Context;
use crate::systems::AssertionError; use crate::systems::stl::Numeric;
use crate::utils::ddispatch::{Request, Responder}; use crate::utils::ddispatch::{Request, Responder};
use crate::Primitive;
/// A proxy trait that implements [Atomic] for blobs of data in Rust code that /// 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 /// cannot be processed and always report inert. Since these are expected to be
/// parameters of functions defined with [define_fn] it also automatically /// parameters of functions defined with [define_fn] it also automatically
/// implements [TryFromExprInst] so that a conversion doesn't have to be /// implements [TryFromExprInst] so that a conversion doesn't have to be
/// provided in argument lists. /// 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 /// Typename to be shown in the error when a conversion from [ExprInst] fails
#[must_use] #[must_use]
fn type_str() -> &'static str; 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. /// you need it, but behaves exactly as the default implementation.
#[allow(unused_mut, unused_variables)] // definition should show likely usage #[allow(unused_mut, unused_variables)] // definition should show likely usage
fn respond(&self, mut request: Request) {} 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<T: InertAtomic> StrictEq for T {
fn strict_eq(&self, other: &dyn Any) -> bool {
other.downcast_ref().map_or(false, |other| self.strict_eq(other))
}
} }
impl<T: InertAtomic> Responder for T { impl<T: InertAtomic> Responder for T {
fn respond(&self, request: Request) { self.respond(request) } fn respond(&self, mut request: Request) {
if request.can_serve::<T>() {
request.serve(self.clone())
} else {
self.respond(request)
}
}
} }
impl<T: InertAtomic> Atomic for T { impl<T: InertAtomic> Atomic for T {
fn as_any(self: Box<Self>) -> Box<dyn Any> { self } fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
@@ -42,7 +65,7 @@ impl<T: InertAtomic> TryFromExprInst for T {
fn from_exi(exi: ExprInst) -> Result<Self, Rc<dyn ExternError>> { fn from_exi(exi: ExprInst) -> Result<Self, Rc<dyn ExternError>> {
let Expr { clause, location } = exi.expr_val(); let Expr { clause, location } = exi.expr_val();
match clause { 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), Ok(t) => Ok(*t),
Err(_) => AssertionError::fail(location, Self::type_str()), Err(_) => AssertionError::fail(location, Self::type_str()),
}, },
@@ -50,3 +73,24 @@ impl<T: InertAtomic> 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<f64> {
fn type_str() -> &'static str { "NotNan<f64>" }
fn strict_eq(&self, other: &Self) -> bool { self == other }
fn respond(&self, mut request: Request) {
request.serve(Numeric::Float(*self))
}
}

View File

@@ -10,8 +10,8 @@ mod inert;
use std::rc::Rc; use std::rc::Rc;
pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn}; pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn, StrictEq};
pub use extern_fn::{ExternError, ExternFn}; pub use extern_fn::{ExternError, ExternFn, ExFn};
pub use fn_bridge::constructors::{ pub use fn_bridge::constructors::{
xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, xfn_5ary, xfn_6ary, xfn_7ary, xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, xfn_5ary, xfn_6ary, xfn_7ary,
xfn_8ary, xfn_9ary, xfn_8ary, xfn_9ary,

View File

@@ -1,7 +1,6 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::cell::RefCell;
use std::hash::{BuildHasher, Hash}; use std::hash::{BuildHasher, Hash};
use std::rc::Rc; use std::sync::{RwLock, Arc};
use hashbrown::HashMap; 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 /// Lasso but much simpler, in part because not much can be known about the
/// type. /// type.
pub struct TypedInterner<T: 'static + Eq + Hash + Clone> { pub struct TypedInterner<T: 'static + Eq + Hash + Clone> {
tokens: RefCell<HashMap<Rc<T>, Tok<T>>>, tokens: RwLock<HashMap<Arc<T>, Tok<T>>>,
} }
impl<T: Eq + Hash + Clone> TypedInterner<T> { impl<T: Eq + Hash + Clone> TypedInterner<T> {
/// Create a fresh interner instance /// Create a fresh interner instance
#[must_use] #[must_use]
pub fn new() -> Rc<Self> { pub fn new() -> Arc<Self> {
Rc::new(Self { tokens: RefCell::new(HashMap::new()) }) Arc::new(Self { tokens: RwLock::new(HashMap::new()) })
} }
/// Intern an object, returning a token /// Intern an object, returning a token
#[must_use] #[must_use]
pub fn i<Q: ?Sized + Eq + Hash + ToOwned<Owned = T>>( pub fn i<Q: ?Sized + Eq + Hash + ToOwned<Owned = T>>(
self: &Rc<Self>, self: &Arc<Self>,
q: &Q, q: &Q,
) -> Tok<T> ) -> Tok<T>
where where
T: Borrow<Q>, T: Borrow<Q>,
{ {
let mut tokens = self.tokens.borrow_mut(); let mut tokens = self.tokens.write().unwrap();
let hash = compute_hash(tokens.hasher(), q); let hash = compute_hash(tokens.hasher(), q);
let raw_entry = tokens let raw_entry = tokens
.raw_entry_mut() .raw_entry_mut()
.from_hash(hash, |k| <T as Borrow<Q>>::borrow(k) == q); .from_hash(hash, |k| <T as Borrow<Q>>::borrow(k) == q);
let kv = raw_entry.or_insert_with(|| { let kv = raw_entry.or_insert_with(|| {
let keyrc = Rc::new(q.to_owned()); let keyrc = Arc::new(q.to_owned());
let token = Tok::<T>::new(keyrc.clone(), Rc::downgrade(self)); let token = Tok::<T>::new(keyrc.clone(), Arc::downgrade(self));
(keyrc, token) (keyrc, token)
}); });
kv.1.clone() kv.1.clone()

View File

@@ -2,7 +2,7 @@ use std::any::{Any, TypeId};
use std::borrow::Borrow; use std::borrow::Borrow;
use std::cell::{RefCell, RefMut}; use std::cell::{RefCell, RefMut};
use std::hash::Hash; use std::hash::Hash;
use std::rc::Rc; use std::sync::Arc;
use hashbrown::HashMap; use hashbrown::HashMap;
@@ -13,7 +13,7 @@ use super::token::Tok;
/// that implements [ToOwned]. Objects of the same type are stored together in a /// that implements [ToOwned]. Objects of the same type are stored together in a
/// [TypedInterner]. /// [TypedInterner].
pub struct Interner { pub struct Interner {
interners: RefCell<HashMap<TypeId, Rc<dyn Any>>>, interners: RefCell<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
} }
impl Interner { impl Interner {
/// Create a new interner /// Create a new interner
@@ -24,7 +24,7 @@ impl Interner {
#[must_use] #[must_use]
pub fn i<Q: ?Sized + Eq + Hash + ToOwned>(&self, q: &Q) -> Tok<Q::Owned> pub fn i<Q: ?Sized + Eq + Hash + ToOwned>(&self, q: &Q) -> Tok<Q::Owned>
where where
Q::Owned: 'static + Eq + Hash + Clone + Borrow<Q>, Q::Owned: 'static + Eq + Hash + Clone + Borrow<Q> + Send + Sync,
{ {
let mut interners = self.interners.borrow_mut(); let mut interners = self.interners.borrow_mut();
let interner = get_interner(&mut interners); let interner = get_interner(&mut interners);
@@ -44,9 +44,9 @@ impl Default for Interner {
/// Get or create an interner for a given type. /// Get or create an interner for a given type.
#[must_use] #[must_use]
fn get_interner<T: 'static + Eq + Hash + Clone>( fn get_interner<T: 'static + Eq + Hash + Clone + Send + Sync>(
interners: &mut RefMut<HashMap<TypeId, Rc<dyn Any>>>, interners: &mut RefMut<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
) -> Rc<TypedInterner<T>> { ) -> Arc<TypedInterner<T>> {
let boxed = interners let boxed = interners
.raw_entry_mut() .raw_entry_mut()
.from_key(&TypeId::of::<T>()) .from_key(&TypeId::of::<T>())

View File

@@ -3,7 +3,7 @@ use std::fmt::{Debug, Display};
use std::hash::Hash; use std::hash::Hash;
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::ops::Deref; use std::ops::Deref;
use std::rc::{Rc, Weak}; use std::sync::{Arc, Weak};
use super::TypedInterner; use super::TypedInterner;
@@ -13,13 +13,13 @@ use super::TypedInterner;
/// currently not enforced. /// currently not enforced.
#[derive(Clone)] #[derive(Clone)]
pub struct Tok<T: Eq + Hash + Clone + 'static> { pub struct Tok<T: Eq + Hash + Clone + 'static> {
data: Rc<T>, data: Arc<T>,
interner: Weak<TypedInterner<T>>, interner: Weak<TypedInterner<T>>,
} }
impl<T: Eq + Hash + Clone + 'static> Tok<T> { impl<T: Eq + Hash + Clone + 'static> Tok<T> {
/// Create a new token. Used exclusively by the interner /// Create a new token. Used exclusively by the interner
#[must_use] #[must_use]
pub(crate) fn new(data: Rc<T>, interner: Weak<TypedInterner<T>>) -> Self { pub(crate) fn new(data: Arc<T>, interner: Weak<TypedInterner<T>>) -> Self {
Self { data, interner } Self { data, interner }
} }
/// Take the ID number out of a token /// Take the ID number out of a token

View File

@@ -3,7 +3,7 @@ use super::error::RuntimeError;
use super::Return; use super::Return;
use crate::foreign::AtomicReturn; use crate::foreign::AtomicReturn;
use crate::representations::interpreted::{Clause, ExprInst}; 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::never::{unwrap_always, Always};
use crate::utils::Side; use crate::utils::Side;
@@ -77,9 +77,8 @@ pub fn apply(
) -> Result<Return, RuntimeError> { ) -> Result<Return, RuntimeError> {
let (state, (gas, inert)) = f.try_update(|clause, loc| match clause { let (state, (gas, inert)) = f.try_update(|clause, loc| match clause {
// apply an ExternFn or an internal function // apply an ExternFn or an internal function
Clause::P(Primitive::ExternFn(f)) => { Clause::ExternFn(f) => {
let clause = let clause = f.apply(x, ctx.clone()).map_err(RuntimeError::Extern)?;
f.apply(x, ctx.clone()).map_err(|e| RuntimeError::Extern(e))?;
Ok((clause, (ctx.gas.map(|g| g - 1), false))) Ok((clause, (ctx.gas.map(|g| g - 1), false)))
}, },
Clause::Lambda { args, body } => Ok(if let Some(args) = args { Clause::Lambda { args, body } => Ok(if let Some(args) = args {
@@ -97,7 +96,7 @@ pub fn apply(
} else { } else {
Err(RuntimeError::MissingSymbol(name.clone(), loc)) Err(RuntimeError::MissingSymbol(name.clone(), loc))
}, },
Clause::P(Primitive::Atom(atom)) => { Clause::Atom(atom) => {
// take a step in expanding atom // take a step in expanding atom
let AtomicReturn { clause, gas, inert } = atom.run(ctx.clone())?; let AtomicReturn { clause, gas, inert } = atom.run(ctx.clone())?;
Ok((Clause::Apply { f: clause.wrap(), x }, (gas, inert))) Ok((Clause::Apply { f: clause.wrap(), x }, (gas, inert)))

View File

@@ -8,7 +8,6 @@ use super::{run, Context, Return, RuntimeError};
use crate::foreign::{Atom, Atomic, ExternError}; use crate::foreign::{Atom, Atomic, ExternError};
use crate::interpreted::{Clause, Expr, ExprInst}; use crate::interpreted::{Clause, Expr, ExprInst};
use crate::utils::take_with_output; use crate::utils::take_with_output;
use crate::Primitive;
trait_set! { trait_set! {
trait Handler = FnMut(Box<dyn Any>) -> HandlerRes; trait Handler = FnMut(Box<dyn Any>) -> HandlerRes;
@@ -71,9 +70,9 @@ pub fn run_handler(
loop { loop {
let mut ret = run(expr, ctx.clone())?; let mut ret = run(expr, ctx.clone())?;
let quit = take_with_output(&mut ret.state, |exi| match exi.expr_val() { 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) { 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(e) => match e {
Ok(expr) => (expr, Ok(false)), Ok(expr) => (expr, Ok(false)),
Err(e) => (Clause::Bottom.wrap(), Err(e)), Err(e) => (Clause::Bottom.wrap(), Err(e)),

View File

@@ -3,7 +3,6 @@ use super::context::{Context, Return};
use super::error::RuntimeError; use super::error::RuntimeError;
use crate::foreign::AtomicReturn; use crate::foreign::AtomicReturn;
use crate::representations::interpreted::{Clause, ExprInst}; use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::Primitive;
/// Normalize an expression using beta reduction with memoization /// Normalize an expression using beta reduction with memoization
pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> { pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
@@ -19,7 +18,7 @@ pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
ctx.gas = res.gas; ctx.gas = res.gas;
cls = res.state.expr().clause.clone(); cls = res.state.expr().clause.clone();
}, },
Clause::P(Primitive::Atom(data)) => { Clause::Atom(data) => {
let AtomicReturn { clause, gas, inert } = data.run(ctx.clone())?; let AtomicReturn { clause, gas, inert } = data.run(ctx.clone())?;
if inert { if inert {
return Ok((clause, (gas, true))); return Ok((clause, (gas, true)));

View File

@@ -29,8 +29,8 @@ pub use representations::project::{
collect_consts, collect_rules, vname_to_sym_tree, ProjectTree, collect_consts, collect_rules, vname_to_sym_tree, ProjectTree,
}; };
pub use representations::{ pub use representations::{
ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal, ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Location,
Location, NameLike, OrcString, PathSet, Primitive, Sym, VName, NameLike, OrcString, PathSet, Sym, VName,
}; };
pub use utils::substack::Substack; pub use utils::substack::Substack;
pub use utils::{ddispatch, take_with_output, thread_pool, IdMap, Side}; pub use utils::{ddispatch, take_with_output, thread_pool, IdMap, Side};

View File

@@ -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<char, String> {
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")
}

View File

@@ -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::interner::Interner;
use crate::{Tok, VName}; use crate::sourcefile::FileEntryKind;
use crate::{Location, VName};
/// Trait enclosing all context features /// Trait enclosing all context features
/// ///
/// Hiding type parameters in associated types allows for simpler /// Hiding type parameters in associated types allows for simpler
/// parser definitions /// parser definitions
pub trait Context: Clone { pub trait Context {
#[must_use] #[must_use]
fn ops(&self) -> &[Tok<String>]; fn file(&self) -> Arc<VName>;
#[must_use]
fn file(&self) -> Rc<VName>;
#[must_use] #[must_use]
fn interner(&self) -> &Interner; fn interner(&self) -> &Interner;
#[must_use]
fn source(&self) -> Arc<String>;
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<usize>) -> Location {
Location::Range { file: self.file(), range, source: self.source() }
}
}
pub type LexerPluginOut<'a> = Option<ProjectResult<(Atom, &'a str)>>;
pub type LineParserOut = Option<ProjectResult<Vec<FileEntryKind>>>;
pub trait LexerPlugin:
for<'a> Fn(&'a str, &dyn Context) -> LexerPluginOut<'a> + Sync + Send
{
}
impl<F> 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<F> LineParser for F where
F: Fn(Stream<'_>, &dyn Context) -> LineParserOut + Sync + Send
{
} }
/// Struct implementing context /// Struct implementing context
@@ -21,29 +64,55 @@ pub trait Context: Clone {
/// Hiding type parameters in associated types allows for simpler /// Hiding type parameters in associated types allows for simpler
/// parser definitions /// parser definitions
pub struct ParsingContext<'a> { pub struct ParsingContext<'a> {
pub ops: &'a [Tok<String>],
pub interner: &'a Interner, pub interner: &'a Interner,
pub file: Rc<VName>, pub file: Arc<VName>,
pub source: Arc<String>,
pub lexers: &'a [&'a dyn LexerPlugin],
pub line_parsers: &'a [&'a dyn LineParser],
} }
impl<'a> ParsingContext<'a> { impl<'a> ParsingContext<'a> {
pub fn new( pub fn new(
ops: &'a [Tok<String>],
interner: &'a Interner, interner: &'a Interner,
file: Rc<VName>, file: Arc<VName>,
source: Arc<String>,
lexers: &'a [&'a dyn LexerPlugin],
line_parsers: &'a [&'a dyn LineParser],
) -> Self { ) -> Self {
Self { ops, interner, file } Self { interner, file, source, lexers, line_parsers }
} }
} }
impl<'a> Clone for ParsingContext<'a> { impl<'a> Clone for ParsingContext<'a> {
fn clone(&self) -> Self { 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<'_> { impl Context for ParsingContext<'_> {
fn interner(&self) -> &Interner { self.interner } fn interner(&self) -> &Interner { self.interner }
fn file(&self) -> Rc<VName> { self.file.clone() } fn file(&self) -> Arc<VName> { self.file.clone() }
fn ops(&self) -> &[Tok<String>] { self.ops } fn source(&self) -> Arc<String> { 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<VName> { 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<usize>) -> Location { Location::Unknown }
fn source(&self) -> Arc<String> { Arc::new(String::new()) }
} }

View File

@@ -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<I: Eq + Hash + Clone, O> =
Parser<I, O, Error = Simple<I>>;
}
/// Boxed version of [SimpleParser]
pub type BoxedSimpleParser<'a, I, O> = BoxedParser<'a, I, O, Simple<I>>;

View File

@@ -1,12 +1,10 @@
use std::rc::Rc; use std::rc::Rc;
use chumsky::prelude::Simple;
use itertools::Itertools; use itertools::Itertools;
use super::{Entry, Lexeme}; use super::{Entry, Lexeme};
use crate::error::{ErrorPosition, ProjectError}; use crate::error::ProjectError;
use crate::utils::BoxedIter; use crate::{Location, Tok};
use crate::{Location, Tok, VName};
#[derive(Debug)] #[derive(Debug)]
pub struct LineNeedsPrefix { pub struct LineNeedsPrefix {
@@ -14,10 +12,10 @@ pub struct LineNeedsPrefix {
} }
impl ProjectError for LineNeedsPrefix { impl ProjectError for LineNeedsPrefix {
fn description(&self) -> &str { "This linetype requires a prefix" } fn description(&self) -> &str { "This linetype requires a prefix" }
fn one_position(&self) -> Location { self.entry.location() }
fn message(&self) -> String { fn message(&self) -> String {
format!("{} cannot appear at the beginning of a line", self.entry) format!("{} cannot appear at the beginning of a line", self.entry)
} }
fn one_position(&self) -> Location { self.entry.location() }
} }
#[derive(Debug)] #[derive(Debug)]
@@ -27,14 +25,12 @@ pub struct UnexpectedEOL {
} }
impl ProjectError for UnexpectedEOL { impl ProjectError for UnexpectedEOL {
fn description(&self) -> &str { "The line ended abruptly" } fn description(&self) -> &str { "The line ended abruptly" }
fn one_position(&self) -> Location { self.entry.location() }
fn message(&self) -> String { fn message(&self) -> String {
"The line ends unexpectedly here. In Orchid, all line breaks outside \ "The line ends unexpectedly here. In Orchid, all line breaks outside \
parentheses start a new declaration" parentheses start a new declaration"
.to_string() .to_string()
} }
fn one_position(&self) -> Location { self.entry.location() }
} }
pub struct ExpectedEOL { pub struct ExpectedEOL {
@@ -58,10 +54,8 @@ impl ExpectedName {
} }
} }
impl ProjectError for ExpectedName { impl ProjectError for ExpectedName {
fn description(&self) -> &str { fn description(&self) -> &str { "A name was expected" }
"A name was expected here, but something else was found" fn one_position(&self) -> Location { self.entry.location() }
}
fn message(&self) -> String { fn message(&self) -> String {
if self.entry.is_keyword() { if self.entry.is_keyword() {
format!( format!(
@@ -72,8 +66,6 @@ impl ProjectError for ExpectedName {
format!("Expected a name, found {}", self.entry) format!("Expected a name, found {}", self.entry)
} }
} }
fn one_position(&self) -> Location { self.entry.location() }
} }
#[derive()] #[derive()]
@@ -84,18 +76,15 @@ pub struct Expected {
} }
impl Expected { impl Expected {
pub fn expect(l: Lexeme, e: &Entry) -> Result<(), Rc<dyn ProjectError>> { pub fn expect(l: Lexeme, e: &Entry) -> Result<(), Rc<dyn ProjectError>> {
if e.lexeme != l { if e.lexeme.strict_eq(&l) {
return Err( return Ok(());
Self { expected: vec![l], or_name: false, found: e.clone() }.rc(),
);
} }
Ok(()) Err(Self { expected: vec![l], or_name: false, found: e.clone() }.rc())
} }
} }
impl ProjectError for Expected { impl ProjectError for Expected {
fn description(&self) -> &str { fn description(&self) -> &str { "A concrete token was expected" }
"A concrete token was expected but something else was found" fn one_position(&self) -> Location { self.found.location() }
}
fn message(&self) -> String { fn message(&self) -> String {
let list = match &self.expected[..] { let list = match &self.expected[..] {
&[] => return "Unsatisfiable expectation".to_string(), &[] => return "Unsatisfiable expectation".to_string(),
@@ -108,21 +97,15 @@ impl ProjectError for Expected {
let or_name = if self.or_name { " or a name" } else { "" }; let or_name = if self.or_name { " or a name" } else { "" };
format!("Expected {list}{or_name} but found {}", self.found) format!("Expected {list}{or_name} but found {}", self.found)
} }
fn one_position(&self) -> Location { self.found.location() }
} }
pub struct ReservedToken { pub struct ReservedToken {
pub entry: Entry, pub entry: Entry,
} }
impl ProjectError for ReservedToken { impl ProjectError for ReservedToken {
fn description(&self) -> &str { fn description(&self) -> &str { "Syntax reserved for future use" }
"A token reserved for future use was found in the code"
}
fn message(&self) -> String { format!("{} is a reserved token", self.entry) }
fn one_position(&self) -> Location { self.entry.location() } fn one_position(&self) -> Location { self.entry.location() }
fn message(&self) -> String { format!("{} is a reserved token", self.entry) }
} }
pub struct BadTokenInRegion { pub struct BadTokenInRegion {
@@ -130,15 +113,11 @@ pub struct BadTokenInRegion {
pub region: &'static str, pub region: &'static str,
} }
impl ProjectError for BadTokenInRegion { impl ProjectError for BadTokenInRegion {
fn description(&self) -> &str { fn description(&self) -> &str { "An unexpected token was found" }
"A token was found in a region where it should not appear" fn one_position(&self) -> Location { self.entry.location() }
}
fn message(&self) -> String { fn message(&self) -> String {
format!("{} cannot appear in {}", self.entry, self.region) format!("{} cannot appear in {}", self.entry, self.region)
} }
fn one_position(&self) -> Location { self.entry.location() }
} }
pub struct NotFound { pub struct NotFound {
@@ -146,70 +125,90 @@ pub struct NotFound {
pub location: Location, pub location: Location,
} }
impl ProjectError for NotFound { impl ProjectError for NotFound {
fn description(&self) -> &str { fn description(&self) -> &str { "A specific lexeme was expected" }
"A specific lexeme was expected but not found in the given range"
}
fn message(&self) -> String { format!("{} was expected", self.expected) }
fn one_position(&self) -> Location { self.location.clone() } fn one_position(&self) -> Location { self.location.clone() }
fn message(&self) -> String { format!("{} was expected", self.expected) }
} }
pub struct LeadingNS { pub struct LeadingNS(pub Location);
pub location: Location,
}
impl ProjectError for LeadingNS { impl ProjectError for LeadingNS {
fn description(&self) -> &str { ":: can only follow a name token" } 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 struct MisalignedParen(pub Entry);
pub entry: Entry,
}
impl ProjectError for MisalignedParen { impl ProjectError for MisalignedParen {
fn description(&self) -> &str { fn description(&self) -> &str { "(), [] and {} must always pair up" }
"Parentheses (), [] and {} must always pair up" fn one_position(&self) -> Location { self.0.location() }
} fn message(&self) -> String { format!("This {} has no pair", self.0) }
fn message(&self) -> String { format!("This {} has no pair", self.entry) }
fn one_position(&self) -> Location { self.entry.location() }
} }
pub struct NamespacedExport { pub struct NamespacedExport(pub Location);
pub location: Location,
}
impl ProjectError for NamespacedExport { impl ProjectError for NamespacedExport {
fn description(&self) -> &str { fn description(&self) -> &str { "Only local names may be exported" }
"Exports can only refer to unnamespaced names in the local namespace" fn one_position(&self) -> Location { self.0.clone() }
}
fn one_position(&self) -> Location { self.location.clone() }
} }
pub struct GlobExport { pub struct GlobExport(pub Location);
pub location: Location,
}
impl ProjectError for GlobExport { impl ProjectError for GlobExport {
fn description(&self) -> &str { fn description(&self) -> &str { "Globstars are not allowed in exports" }
"Exports can only refer to concrete names, globstars are not allowed" fn one_position(&self) -> Location { self.0.clone() }
}
fn one_position(&self) -> Location { self.location.clone() }
} }
pub struct LexError { pub struct NoStringEnd(pub Location);
pub errors: Vec<Simple<char>>, impl ProjectError for NoStringEnd {
pub source: Rc<String>, fn description(&self) -> &str { "A string literal was not closed with `\"`" }
pub file: VName, fn one_position(&self) -> Location { self.0.clone() }
} }
impl ProjectError for LexError {
fn description(&self) -> &str { "An error occured during tokenization" } pub struct NoCommentEnd(pub Location);
fn positions(&self) -> BoxedIter<ErrorPosition> { impl ProjectError for NoCommentEnd {
let file = self.file.clone(); fn description(&self) -> &str { "a comment was not closed with `]--`" }
Box::new(self.errors.iter().map(move |s| ErrorPosition { fn one_position(&self) -> Location { self.0.clone() }
location: Location::Range {
file: Rc::new(file.clone()),
range: s.span(),
source: self.source.clone(),
},
message: Some(format!("{}", s)),
}))
} }
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() }
} }

View File

@@ -1,31 +1,17 @@
use std::rc::Rc;
use chumsky::Parser;
use super::context::Context; use super::context::Context;
use super::errors::LexError; use super::lexer::lex;
use super::lexer;
use super::sourcefile::parse_module_body; use super::sourcefile::parse_module_body;
use super::stream::Stream; use super::stream::Stream;
use crate::error::{ParseErrorWithTokens, ProjectError, ProjectResult}; use crate::error::{ParseErrorWithTokens, ProjectError, ProjectResult};
use crate::representations::sourcefile::FileEntry; use crate::representations::sourcefile::FileEntry;
pub fn parse2(data: &str, ctx: impl Context) -> ProjectResult<Vec<FileEntry>> { pub fn parse2(ctx: impl Context) -> ProjectResult<Vec<FileEntry>> {
let source = Rc::new(data.to_string()); let tokens = lex(vec![], ctx.source().as_str(), &ctx).expect("debug");
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()
})?;
if tokens.is_empty() { if tokens.is_empty() {
Ok(Vec::new()) Ok(Vec::new())
} else { } else {
parse_module_body(Stream::from_slice(&tokens), ctx).map_err(|error| { parse_module_body(Stream::from_slice(&tokens), &ctx).map_err(|error| {
ParseErrorWithTokens { error, full_source: data.to_string(), tokens }.rc() ParseErrorWithTokens { error, full_source: ctx.source().to_string(), tokens }.rc()
}) })
} }
} }

View File

@@ -1,24 +1,26 @@
use std::fmt::{self, Display}; use std::fmt::Display;
use std::ops::Range; 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 itertools::Itertools;
use ordered_float::NotNan; use ordered_float::NotNan;
use super::LexerPlugin;
use super::context::Context; use super::context::Context;
use super::decls::SimpleParser; use super::errors::{FloatPlacehPrio, NoCommentEnd};
use super::number::print_nat16; use super::numeric::{parse_num, print_nat16, numstart};
use super::{comment, name, number, placeholder, string};
use crate::ast::{PHClass, Placeholder}; use crate::ast::{PHClass, Placeholder};
use crate::error::{ProjectResult, ProjectError};
use crate::foreign::Atom;
use crate::interner::Tok; use crate::interner::Tok;
use crate::parse::operators::operators_parser; use crate::parse::numeric::{numchar, lex_numeric};
use crate::representations::Literal; use crate::parse::string::lex_string;
use crate::{Interner, Location, VName}; 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 struct Entry {
pub lexeme: Lexeme, pub lexeme: Lexeme,
pub location: Location, pub location: Location,
@@ -32,14 +34,15 @@ impl Entry {
#[must_use] #[must_use]
pub fn is_keyword(&self) -> bool { pub fn is_keyword(&self) -> bool {
matches!( false
self.lexeme, // matches!(
Lexeme::Const // self.lexeme,
| Lexeme::Export // Lexeme::Const
| Lexeme::Import // | Lexeme::Export
| Lexeme::Macro // | Lexeme::Import
| Lexeme::Module // | Lexeme::Macro
) // | Lexeme::Module
// )
} }
#[must_use] #[must_use]
@@ -51,9 +54,13 @@ impl Entry {
} }
#[must_use] #[must_use]
pub fn file(&self) -> Rc<VName> { pub fn file(&self) -> Arc<VName> {
self.location.file().expect("An Entry can only have a range location") 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 { impl Display for Entry {
@@ -66,9 +73,9 @@ impl AsRef<Location> for Entry {
fn as_ref(&self) -> &Location { &self.location } fn as_ref(&self) -> &Location { &self.location }
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug)]
pub enum Lexeme { pub enum Lexeme {
Literal(Literal), Atom(Atom),
Name(Tok<String>), Name(Tok<String>),
Arrow(NotNan<f64>), Arrow(NotNan<f64>),
/// Walrus operator (formerly shorthand macro) /// Walrus operator (formerly shorthand macro)
@@ -86,20 +93,19 @@ pub enum Lexeme {
At, At,
// Dot, // Dot,
Type, // type operator Type, // type operator
Comment(Rc<String>), Comment(Arc<String>),
Export, // Export,
Import, // Import,
Module, // Module,
Macro, // Macro,
Const, // Const,
Operators(Rc<VName>),
Placeh(Placeholder), Placeh(Placeholder),
} }
impl Display for Lexeme { impl Display for Lexeme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Literal(l) => write!(f, "{:?}", l), Self::Atom(a) => write!(f, "{a:?}"),
Self::Name(token) => write!(f, "{}", **token), Self::Name(token) => write!(f, "{}", **token),
Self::Walrus => write!(f, ":="), Self::Walrus => write!(f, ":="),
Self::Arrow(prio) => write!(f, "={}=>", print_nat16(*prio)), Self::Arrow(prio) => write!(f, "={}=>", print_nat16(*prio)),
@@ -116,14 +122,11 @@ impl Display for Lexeme {
Self::At => write!(f, "@"), Self::At => write!(f, "@"),
Self::Type => write!(f, ":"), Self::Type => write!(f, ":"),
Self::Comment(text) => write!(f, "--[{}]--", text), Self::Comment(text) => write!(f, "--[{}]--", text),
Self::Export => write!(f, "export"), // Self::Export => write!(f, "export"),
Self::Import => write!(f, "import"), // Self::Import => write!(f, "import"),
Self::Module => write!(f, "module"), // Self::Module => write!(f, "module"),
Self::Const => write!(f, "const"), // Self::Const => write!(f, "const"),
Self::Macro => write!(f, "macro"), // Self::Macro => write!(f, "macro"),
Self::Operators(ops) => {
write!(f, "operators[{}]", Interner::extern_all(ops).join(" "))
},
Self::Placeh(Placeholder { name, class }) => match *class { Self::Placeh(Placeholder { name, class }) => match *class {
PHClass::Scalar => write!(f, "${}", **name), PHClass::Scalar => write!(f, "${}", **name),
PHClass::Vec { nonzero, prio } => { PHClass::Vec { nonzero, prio } => {
@@ -147,97 +150,192 @@ impl Lexeme {
) )
} }
#[must_use] pub fn strict_eq(&self, other: &Self) -> bool {
pub fn parser<E: chumsky::Error<Entry>>( match (self, other) {
self, (Self::Arrow(f1), Self::Arrow(f2)) => f1 == f2,
) -> impl Parser<Entry, Entry, Error = E> + Clone { (Self::At, Self::At) | (Self::BR, Self::BR) => true,
filter(move |ent: &Entry| ent.lexeme == self) (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)] #[allow(unused)]
pub struct LexedText(pub Vec<Entry>); pub fn format(lexed: &[Entry]) -> String { lexed.iter().join(" ") }
impl Display for LexedText { pub fn namechar(c: char) -> bool { c.is_alphanumeric() | (c == '_') }
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { pub fn namestart(c: char) -> bool { c.is_alphabetic() | (c == '_') }
write!(f, "{}", self.0.iter().join(" ")) 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<Item = (&'static str, Lexeme)> {
[
("\\", 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<Entry>,
mut data: &str,
ctx: &impl Context,
) -> ProjectResult<Vec<Entry>> {
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;
} }
} }
#[must_use] if let Some(tail) = data.strip_prefix(',') {
fn paren_parser(lp: char, rp: char) -> impl SimpleParser<char, Lexeme> { let lexeme = Lexeme::Name(ctx.interner().i(","));
just(lp).to(Lexeme::LP(lp)).or(just(rp).to(Lexeme::RP(lp))) tokens.push(Entry::new(ctx.location(1, tail), lexeme));
data = tail;
continue 'tail;
} }
if let Some(tail) = data.strip_prefix("--[") {
#[must_use] let (note, tail) = (tail.split_once("]--"))
pub fn literal_parser<'a>( .ok_or_else(|| NoCommentEnd(ctx.location(tail.len(), "")).rc())?;
ctx: impl Context + 'a, let lexeme = Lexeme::Comment(Arc::new(note.to_string()));
) -> impl SimpleParser<char, Literal> + 'a { let location = ctx.location(note.len() + 3, tail);
choice(( tokens.push(Entry::new(location, lexeme));
// all ints are valid floats so it takes precedence data = tail;
number::int_parser().map(Literal::Uint), continue 'tail;
number::float_parser().map(Literal::Num),
string::str_parser()
.map(move |s| Literal::Str(ctx.interner().i(&s).into())),
))
} }
if let Some(tail) = data.strip_prefix("--") {
pub static BASE_OPS: &[&str] = &[",", ".", "..", "...", "*"]; let (note, tail) = split_filter(tail, |c| c != '\n');
let lexeme = Lexeme::Comment(Arc::new(note.to_string()));
#[must_use] let location = ctx.location(note.len(), tail);
pub fn lexer<'a>( tokens.push(Entry::new(location, lexeme));
ctx: impl Context + 'a, data = tail;
source: Rc<String>, continue 'tail;
) -> impl SimpleParser<char, Vec<Entry>> + 'a { }
let all_ops = ctx if let Some(tail) = data.strip_prefix('=') {
.ops() if tail.chars().next().map_or(false, numstart) {
.iter() let (num, post_num) = split_filter(tail, numchar);
.map(|op| op.as_ref()) if let Some(tail) = post_num.strip_prefix("=>") {
.chain(BASE_OPS.iter().cloned()) let lexeme = Lexeme::Arrow(parse_num(num).map_err(|e| e.into_proj(num.len(), post_num, ctx))?.as_float());
.map(str::to_string) let location = ctx.location(num.len() + 3, tail);
.collect::<Vec<_>>(); tokens.push(Entry::new(location, lexeme));
choice(( data = tail;
keyword("export").to(Lexeme::Export), continue 'tail;
keyword("module").to(Lexeme::Module), }
keyword("import").to(Lexeme::Import), }
keyword("macro").to(Lexeme::Macro), }
keyword("const").to(Lexeme::Const), // todo: parse placeholders, don't forget vectorials!
operators_parser({ if let Some(tail) = data.strip_prefix('$') {
let ctx = ctx.clone(); let (name, tail) = split_filter(tail, namechar);
move |s| ctx.interner().i(&s) 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(|v| Lexeme::Operators(Rc::new(v))), .map(|p| (p, num_str.len() + 1, tail))
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()) .unwrap_or(Ok((0, 0, tail)))?;
.repeated() let byte_len = if nonzero { 4 } else { 3 } + priolen + name.len();
.then_ignore(end()) 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" "#)
}
} }

View File

@@ -1,19 +1,18 @@
mod comment;
mod context; mod context;
mod decls;
mod errors; mod errors;
mod facade; mod facade;
mod lexer; mod lexer;
mod multiname; mod multiname;
mod name; mod numeric;
mod number;
mod operators;
mod placeholder;
mod sourcefile; mod sourcefile;
mod stream; mod stream;
mod string; mod string;
pub use context::ParsingContext; pub use context::{ParsingContext, Context, LexerPlugin, LineParser};
pub use facade::parse2; pub use facade::parse2;
pub use lexer::{lexer, Entry, Lexeme}; pub use lexer::{namechar, namestart, opchar, split_filter, Entry, Lexeme};
pub use number::{float_parser, int_parser, print_nat16}; pub use numeric::{
lex_numeric, numchar, numstart, parse_num, print_nat16, NumError,
NumErrorKind,
};
pub use string::{lex_string, parse_string, StringError, StringErrorKind};

View File

@@ -41,12 +41,12 @@ impl Subresult {
} }
} }
fn parse_multiname_branch( fn parse_multiname_branch<'a>(
cursor: Stream<'_>, cursor: Stream<'a>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<(BoxedIter<Subresult>, Stream<'_>)> { ) -> ProjectResult<(BoxedIter<'a, Subresult>, Stream<'a>)> {
let comma = ctx.interner().i(","); 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()?; let (delim, cursor) = cursor.trim().pop()?;
match &delim.lexeme { match &delim.lexeme {
Lexeme::Name(n) if n == &comma => { Lexeme::Name(n) if n == &comma => {
@@ -65,10 +65,10 @@ fn parse_multiname_branch(
} }
} }
fn parse_multiname_rec( fn parse_multiname_rec<'a>(
curosr: Stream<'_>, curosr: Stream<'a>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<(BoxedIter<Subresult>, Stream<'_>)> { ) -> ProjectResult<(BoxedIter<'a, Subresult>, Stream<'a>)> {
let star = ctx.interner().i("*"); let star = ctx.interner().i("*");
let comma = ctx.interner().i(","); let comma = ctx.interner().i(",");
let (head, mut cursor) = curosr.trim().pop()?; let (head, mut cursor) = curosr.trim().pop()?;
@@ -103,7 +103,7 @@ fn parse_multiname_rec(
Ok((box_once(Subresult::new_glob(head.location())), cursor)), Ok((box_once(Subresult::new_glob(head.location())), cursor)),
Lexeme::Name(n) if ![comma, star].contains(n) => { Lexeme::Name(n) if ![comma, star].contains(n) => {
let cursor = cursor.trim(); 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 cursor = cursor.step()?;
let (out, cursor) = parse_multiname_rec(cursor, ctx)?; let (out, cursor) = parse_multiname_rec(cursor, ctx)?;
let out = Box::new(out.map(|sr| sr.push_front(n.clone()))); let out = Box::new(out.map(|sr| sr.push_front(n.clone())));
@@ -123,10 +123,10 @@ fn parse_multiname_rec(
} }
} }
pub fn parse_multiname( pub fn parse_multiname<'a>(
cursor: Stream<'_>, cursor: Stream<'a>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<(Vec<Import>, Stream<'_>)> { ) -> ProjectResult<(Vec<Import>, Stream<'a>)> {
let (output, cont) = parse_multiname_rec(cursor, ctx)?; let (output, cont) = parse_multiname_rec(cursor, ctx)?;
Ok((output.map(|sr| sr.finalize()).collect(), cont)) Ok((output.map(|sr| sr.finalize()).collect(), cont))
} }

View File

@@ -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<str> + Clone],
) -> BoxedSimpleParser<'a, char, String> {
let mut sorted_ops: Vec<String> =
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<char, String> + '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<str> + Clone],
) -> impl SimpleParser<char, String> + '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")
}

View File

@@ -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<char, String> {
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<char, u64> {
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<char, i32> {
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::<i32>().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<f64>, i32)) -> NotNan<f64> {
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<char, u64> {
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<char, u64> {
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<char, NotNan<f64>> {
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<char, NotNan<f64>> {
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<char, NotNan<f64>> {
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<f64>) -> String {
let exp = num.log(16.0).floor();
let man = num / 16_f64.powf(exp);
format!("{man}p{exp:.0}")
}

148
src/parse/numeric.rs Normal file
View File

@@ -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<usize>,
pub kind: NumErrorKind,
}
impl NumError {
pub fn into_proj(
self,
len: usize,
tail: &str,
ctx: &(impl Context + ?Sized),
) -> Rc<dyn ProjectError> {
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<Numeric, NumError> {
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<usize, NumError> {
let s = s.chars().filter(|c| *c != '_').collect::<String>();
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<ProjectResult<(Atom, &'a str)>> {
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<f64>) -> String {
let exp = num.log(16.0).floor();
let man = num / 16_f64.powf(exp);
format!("{man}p{exp:.0}")
}

View File

@@ -1,32 +0,0 @@
use chumsky::prelude::*;
use super::decls::SimpleParser;
#[must_use]
pub fn operators_parser<T>(
f: impl Fn(String) -> T,
) -> impl SimpleParser<char, Vec<T>> {
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[$ |> =>]"))
}
}

View File

@@ -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<char, Placeholder> {
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 })
}
})
}

View File

@@ -18,7 +18,6 @@ use crate::representations::location::Location;
use crate::representations::sourcefile::{FileEntry, MemberKind, ModuleBlock}; use crate::representations::sourcefile::{FileEntry, MemberKind, ModuleBlock};
use crate::representations::VName; use crate::representations::VName;
use crate::sourcefile::{FileEntryKind, Import, Member}; use crate::sourcefile::{FileEntryKind, Import, Member};
use crate::Primitive;
pub fn split_lines(module: Stream<'_>) -> impl Iterator<Item = Stream<'_>> { pub fn split_lines(module: Stream<'_>) -> impl Iterator<Item = Stream<'_>> {
let mut source = module.data.iter().enumerate(); let mut source = module.data.iter().enumerate();
@@ -52,36 +51,44 @@ pub fn split_lines(module: Stream<'_>) -> impl Iterator<Item = Stream<'_>> {
pub fn parse_module_body( pub fn parse_module_body(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<Vec<FileEntry>> { ) -> ProjectResult<Vec<FileEntry>> {
split_lines(cursor) split_lines(cursor)
.map(Stream::trim) .map(Stream::trim)
.filter(|l| !l.data.is_empty()) .filter(|l| !l.data.is_empty())
.map(|l| { .map(|l| {
Ok(FileEntry { parse_line(l, ctx).map(move |kinds| {
locations: vec![l.location()], kinds
kind: parse_line(l, ctx.clone())?, .into_iter()
.map(move |kind| FileEntry { locations: vec![l.location()], kind })
}) })
}) })
.flatten_ok()
.collect() .collect()
} }
pub fn parse_line( pub fn parse_line(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<FileEntryKind> { ) -> ProjectResult<Vec<FileEntryKind>> {
match cursor.get(0)?.lexeme { 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::BR | Lexeme::Comment(_) => parse_line(cursor.step()?, ctx),
Lexeme::Export => parse_export_line(cursor.step()?, ctx), Lexeme::Name(n) if **n == "export" =>
Lexeme::Const | Lexeme::Macro | Lexeme::Module | Lexeme::Operators(_) => parse_export_line(cursor.step()?, ctx).map(|k| vec![k]),
Ok(FileEntryKind::Member(Member { Lexeme::Name(n) if ["const", "macro", "module"].contains(&n.as_str()) =>
Ok(vec![FileEntryKind::Member(Member {
kind: parse_member(cursor, ctx)?, kind: parse_member(cursor, ctx)?,
exported: false, exported: false,
})), })]),
Lexeme::Import => { Lexeme::Name(n) if **n == "import" => {
let (imports, cont) = parse_multiname(cursor.step()?, ctx)?; let (imports, cont) = parse_multiname(cursor.step()?, ctx)?;
cont.expect_empty()?; cont.expect_empty()?;
Ok(FileEntryKind::Import(imports)) Ok(vec![FileEntryKind::Import(imports)])
}, },
_ => { _ => {
let err = BadTokenInRegion { let err = BadTokenInRegion {
@@ -95,23 +102,23 @@ pub fn parse_line(
pub fn parse_export_line( pub fn parse_export_line(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<FileEntryKind> { ) -> ProjectResult<FileEntryKind> {
let cursor = cursor.trim(); let cursor = cursor.trim();
match cursor.get(0)?.lexeme { match &cursor.get(0)?.lexeme {
Lexeme::NS => { Lexeme::NS => {
let (names, cont) = parse_multiname(cursor.step()?, ctx)?; let (names, cont) = parse_multiname(cursor.step()?, ctx)?;
cont.expect_empty()?; cont.expect_empty()?;
let names = (names.into_iter()) let names = (names.into_iter())
.map(|Import { name, path, location }| match (name, &path[..]) { .map(|Import { name, path, location }| match (name, &path[..]) {
(Some(n), []) => Ok((n, location)), (Some(n), []) => Ok((n, location)),
(None, _) => Err(GlobExport { location }.rc()), (None, _) => Err(GlobExport(location).rc()),
_ => Err(NamespacedExport { location }.rc()), _ => Err(NamespacedExport(location).rc()),
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Ok(FileEntryKind::Export(names)) 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 { Ok(FileEntryKind::Member(Member {
kind: parse_member(cursor, ctx)?, kind: parse_member(cursor, ctx)?,
exported: true, exported: true,
@@ -128,26 +135,22 @@ pub fn parse_export_line(
fn parse_member( fn parse_member(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<MemberKind> { ) -> ProjectResult<MemberKind> {
let (typemark, cursor) = cursor.trim().pop()?; let (typemark, cursor) = cursor.trim().pop()?;
match &typemark.lexeme { match &typemark.lexeme {
Lexeme::Const => { Lexeme::Name(n) if **n == "const" => {
let constant = parse_const(cursor, ctx)?; let constant = parse_const(cursor, ctx)?;
Ok(MemberKind::Constant(constant)) Ok(MemberKind::Constant(constant))
}, },
Lexeme::Macro => { Lexeme::Name(n) if **n == "macro" => {
let rule = parse_rule(cursor, ctx)?; let rule = parse_rule(cursor, ctx)?;
Ok(MemberKind::Rule(rule)) Ok(MemberKind::Rule(rule))
}, },
Lexeme::Module => { Lexeme::Name(n) if **n == "module" => {
let module = parse_module(cursor, ctx)?; let module = parse_module(cursor, ctx)?;
Ok(MemberKind::Module(module)) Ok(MemberKind::Module(module))
}, },
Lexeme::Operators(ops) => {
cursor.trim().expect_empty()?;
Ok(MemberKind::Operators(ops[..].to_vec()))
},
_ => { _ => {
let err = let err =
BadTokenInRegion { entry: typemark.clone(), region: "member type" }; BadTokenInRegion { entry: typemark.clone(), region: "member type" };
@@ -158,20 +161,20 @@ fn parse_member(
fn parse_rule( fn parse_rule(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<Rule<VName>> { ) -> ProjectResult<Rule<VName>> {
let (pattern, prio, template) = cursor.find_map("arrow", |a| match a { let (pattern, prio, template) = cursor.find_map("arrow", |a| match a {
Lexeme::Arrow(p) => Some(*p), Lexeme::Arrow(p) => Some(*p),
_ => None, _ => None,
})?; })?;
let (pattern, _) = parse_exprv(pattern, None, ctx.clone())?; let (pattern, _) = parse_exprv(pattern, None, ctx)?;
let (template, _) = parse_exprv(template, None, ctx)?; let (template, _) = parse_exprv(template, None, ctx)?;
Ok(Rule { pattern, prio, template }) Ok(Rule { pattern, prio, template })
} }
fn parse_const( fn parse_const(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<Constant> { ) -> ProjectResult<Constant> {
let (name_ent, cursor) = cursor.trim().pop()?; let (name_ent, cursor) = cursor.trim().pop()?;
let name = ExpectedName::expect(name_ent)?; let name = ExpectedName::expect(name_ent)?;
@@ -183,7 +186,7 @@ fn parse_const(
fn parse_module( fn parse_module(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<ModuleBlock> { ) -> ProjectResult<ModuleBlock> {
let (name_ent, cursor) = cursor.trim().pop()?; let (name_ent, cursor) = cursor.trim().pop()?;
let name = ExpectedName::expect(name_ent)?; let name = ExpectedName::expect(name_ent)?;
@@ -195,11 +198,11 @@ fn parse_module(
Ok(ModuleBlock { name, body }) Ok(ModuleBlock { name, body })
} }
fn parse_exprv( fn parse_exprv<'a>(
mut cursor: Stream<'_>, mut cursor: Stream<'a>,
paren: Option<char>, paren: Option<char>,
ctx: impl Context, ctx: &impl Context,
) -> ProjectResult<(Vec<Expr<VName>>, Stream<'_>)> { ) -> ProjectResult<(Vec<Expr<VName>>, Stream<'a>)> {
let mut output = Vec::new(); let mut output = Vec::new();
cursor = cursor.trim(); cursor = cursor.trim();
while let Ok(current) = cursor.get(0) { while let Ok(current) = cursor.get(0) {
@@ -207,11 +210,9 @@ fn parse_exprv(
Lexeme::BR | Lexeme::Comment(_) => unreachable!("Fillers skipped"), Lexeme::BR | Lexeme::Comment(_) => unreachable!("Fillers skipped"),
Lexeme::At | Lexeme::Type => Lexeme::At | Lexeme::Type =>
return Err(ReservedToken { entry: current.clone() }.rc()), return Err(ReservedToken { entry: current.clone() }.rc()),
Lexeme::Literal(l) => { Lexeme::Atom(a) => {
output.push(Expr { let value = Clause::Atom(a.clone());
value: Clause::P(Primitive::Literal(l.clone())), output.push(Expr { value, location: current.location() });
location: current.location(),
});
cursor = cursor.step()?; cursor = cursor.step()?;
}, },
Lexeme::Placeh(ph) => { Lexeme::Placeh(ph) => {
@@ -223,25 +224,23 @@ fn parse_exprv(
}, },
Lexeme::Name(n) => { Lexeme::Name(n) => {
let location = cursor.location(); let location = cursor.location();
let mut fullname = vec![n.clone()]; let mut fullname: VName = vec![n.clone()];
while cursor.get(1).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) { while cursor.get(1).map_or(false, |e| e.lexeme.strict_eq(&Lexeme::NS)) {
fullname.push(ExpectedName::expect(cursor.get(2)?)?); fullname.push(ExpectedName::expect(cursor.get(2)?)?);
cursor = cursor.step()?.step()?; cursor = cursor.step()?.step()?;
} }
output.push(Expr { value: Clause::Name(fullname), location }); output.push(Expr { value: Clause::Name(fullname), location });
cursor = cursor.step()?; cursor = cursor.step()?;
}, },
Lexeme::NS => Lexeme::NS => return Err(LeadingNS(current.location()).rc()),
return Err(LeadingNS { location: current.location() }.rc()),
Lexeme::RP(c) => Lexeme::RP(c) =>
return if Some(*c) == paren { return if Some(*c) == paren {
Ok((output, cursor.step()?)) Ok((output, cursor.step()?))
} else { } else {
Err(MisalignedParen { entry: cursor.get(0)?.clone() }.rc()) Err(MisalignedParen(cursor.get(0)?.clone()).rc())
}, },
Lexeme::LP(c) => { Lexeme::LP(c) => {
let (result, leftover) = let (result, leftover) = parse_exprv(cursor.step()?, Some(*c), ctx)?;
parse_exprv(cursor.step()?, Some(*c), ctx.clone())?;
output.push(Expr { output.push(Expr {
value: Clause::S(*c, Rc::new(result)), value: Clause::S(*c, Rc::new(result)),
location: cursor.get(0)?.location().to(leftover.fallback.location()), location: cursor.get(0)?.location().to(leftover.fallback.location()),
@@ -250,9 +249,9 @@ fn parse_exprv(
}, },
Lexeme::BS => { Lexeme::BS => {
let dot = ctx.interner().i("."); let dot = ctx.interner().i(".");
let (arg, body) = let (arg, body) = (cursor.step())?
cursor.step()?.find("A '.'", |l| l == &Lexeme::Name(dot.clone()))?; .find("A '.'", |l| l.strict_eq(&Lexeme::Name(dot.clone())))?;
let (arg, _) = parse_exprv(arg, None, ctx.clone())?; let (arg, _) = parse_exprv(arg, None, ctx)?;
let (body, leftover) = parse_exprv(body, paren, ctx)?; let (body, leftover) = parse_exprv(body, paren, ctx)?;
output.push(Expr { output.push(Expr {
location: cursor.location(), location: cursor.location(),
@@ -278,7 +277,7 @@ fn vec_to_single(
v: Vec<Expr<VName>>, v: Vec<Expr<VName>>,
) -> ProjectResult<Expr<VName>> { ) -> ProjectResult<Expr<VName>> {
match v.len() { 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()), 1 => Ok(v.into_iter().exactly_one().unwrap()),
_ => Ok(Expr { _ => Ok(Expr {
location: expr_slice_location(&v), location: expr_slice_location(&v),

View File

@@ -1,50 +1,117 @@
use chumsky::prelude::*; use itertools::Itertools;
use chumsky::{self, Parser};
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 pub enum StringErrorKind {
#[must_use] NotHex,
fn text_parser(delim: char) -> impl SimpleParser<char, char> { BadCodePoint,
// Copied directly from Chumsky's JSON example. BadEscSeq,
let escape = just('\\').ignore_then( }
just('\\')
.or(just('/')) pub struct StringError {
.or(just('"')) pos: usize,
.or(just('b').to('\x08')) kind: StringErrorKind,
.or(just('f').to('\x0C')) }
.or(just('n').to('\n'))
.or(just('r').to('\r')) pub fn parse_string(str: &str) -> Result<String, StringError> {
.or(just('t').to('\t')) let mut target = String::new();
.or( let mut iter = str.char_indices();
just('u').ignore_then( while let Some((_, c)) = iter.next() {
filter(|c: &char| c.is_ascii_hexdigit()) if c != '\\' {
.repeated() target.push(c);
.exactly(4) continue;
.collect::<String>() }
.validate(|digits, span, emit| { let (mut pos, code) = iter.next().expect("lexer would have continued");
char::from_u32(u32::from_str_radix(&digits, 16).unwrap()) let next = match code {
.unwrap_or_else(|| { c @ ('\\' | '/' | '"') => c,
emit(Simple::custom(span, "invalid unicode character")); 'b' => '\x08',
'\u{FFFD}' // unicode replacement character '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 })?
); },
filter(move |&c| c != '\\' && c != delim).or(escape) _ => return Err(StringError { pos, kind: StringErrorKind::BadEscSeq }),
};
target.push(next);
}
Ok(target)
} }
/// Parse a string between double quotes pub fn lex_string<'a>(
#[must_use] data: &'a str,
pub fn str_parser() -> impl SimpleParser<char, String> { ctx: &dyn Context,
just('"') ) -> Option<ProjectResult<(Atom, &'a str)>> {
.ignore_then( data.strip_prefix('"').map(|data| {
text_parser('"').map(Some) let mut leftover = data;
.or(just("\\\n").then(just(' ').or(just('\t')).repeated()).map(|_| None)) // Newlines preceded by backslashes are ignored along with all following indentation. return loop {
.repeated(), let (inside, outside) = (leftover.split_once('"'))
) .ok_or_else(|| NoStringEnd(ctx.location(data.len(), "")).rc())?;
.then_ignore(just('"')) let backslashes = inside.chars().rev().take_while(|c| *c == '\\').count();
.flatten() if backslashes % 2 == 0 {
.collect() // 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::<OrcString>().unwrap().as_str(), "hello world!");
assert_eq!(tail, " - says the programmer");
}
} }

View File

@@ -8,7 +8,7 @@ use crate::representations::project::{
ItemKind, ProjectExt, ProjectItem, ProjectMod, ItemKind, ProjectExt, ProjectItem, ProjectMod,
}; };
use crate::tree::{ModEntry, ModMember, Module}; use crate::tree::{ModEntry, ModMember, Module};
use crate::utils::pure_push::pushed; use crate::utils::pure_seq::pushed;
use crate::{Interner, ProjectTree, Tok, VName}; use crate::{Interner, ProjectTree, Tok, VName};
#[must_use] #[must_use]
@@ -74,7 +74,6 @@ fn resolve_aliases_rec(
ModMember::Sub(module) => ModMember::Sub(module) =>
ModMember::Sub(resolve_aliases_rec(root, module, updated, false)), ModMember::Sub(resolve_aliases_rec(root, module, updated, false)),
ModMember::Item(item) => ModMember::Item(ProjectItem { ModMember::Item(item) => ModMember::Item(ProjectItem {
is_op: item.is_op,
kind: match &item.kind { kind: match &item.kind {
ItemKind::Const(value) => ItemKind::Const(process_expr(value)), ItemKind::Const(value) => ItemKind::Const(process_expr(value)),
other => other.clone(), other => other.clone(),

View File

@@ -1,6 +1,6 @@
//! Source loader callback definition and builtin implementations //! Source loader callback definition and builtin implementations
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc; use std::sync::Arc;
use std::{fs, io}; use std::{fs, io};
use hashbrown::{HashMap, HashSet}; 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" "Neither a file nor a directory could be read from the requested path"
} }
fn one_position(&self) -> crate::Location { fn one_position(&self) -> crate::Location {
Location::File(Rc::new(self.path.clone())) Location::File(Arc::new(self.path.clone()))
} }
fn message(&self) -> String { fn message(&self) -> String {
format!("File: {}\nDirectory: {}", self.file, self.dir) format!("File: {}\nDirectory: {}", self.file, self.dir)
@@ -37,10 +37,10 @@ impl ProjectError for FileLoadingError {
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub enum Loaded { pub enum Loaded {
/// Conceptually equivalent to a sourcefile /// Conceptually equivalent to a sourcefile
Code(Rc<String>), Code(Arc<String>),
/// Conceptually equivalent to the list of *.orc files in a folder, without /// Conceptually equivalent to the list of *.orc files in a folder, without
/// the extension /// the extension
Collection(Rc<Vec<String>>), Collection(Arc<Vec<String>>),
} }
impl Loaded { impl Loaded {
/// Is the loaded item source code (not a collection)? /// Is the loaded item source code (not a collection)?
@@ -56,7 +56,7 @@ pub fn load_file(root: &Path, path: &[Tok<String>]) -> IOResult {
let full_path = path.iter().fold(root.to_owned(), |p, t| p.join(t.as_str())); 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_path = full_path.with_extension("orc");
let file_error = match fs::read_to_string(file_path) { 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, Err(err) => err,
}; };
let dir = match fs::read_dir(&full_path) { let dir = match fs::read_dir(&full_path) {
@@ -83,7 +83,7 @@ pub fn load_file(root: &Path, path: &[Tok<String>]) -> IOResult {
}) })
}) })
.collect(); .collect();
Ok(Loaded::Collection(Rc::new(names))) Ok(Loaded::Collection(Arc::new(names)))
} }
/// Generates a cached file loader for a directory /// Generates a cached file loader for a directory
@@ -102,7 +102,7 @@ pub fn load_embed<T: 'static + RustEmbed>(path: &str, ext: &str) -> IOResult {
if let Some(file) = T::get(&file_path) { if let Some(file) = T::get(&file_path) {
let s = let s =
String::from_utf8(file.data.to_vec()).expect("Embed must be valid UTF-8"); 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 { } else {
let entries = T::iter() let entries = T::iter()
.map(|c| c.to_string()) .map(|c| c.to_string())
@@ -121,7 +121,7 @@ pub fn load_embed<T: 'static + RustEmbed>(path: &str, ext: &str) -> IOResult {
}) })
}) })
.collect::<Vec<String>>(); .collect::<Vec<String>>();
Ok(Loaded::Collection(Rc::new(entries))) Ok(Loaded::Collection(Arc::new(entries)))
} }
} }
@@ -165,9 +165,9 @@ pub fn embed_to_map<T: 'static + RustEmbed>(
} }
} }
(files.into_iter()) (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)| { .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() .collect()
} }

View File

@@ -5,5 +5,6 @@ mod import_abs_path;
mod parse_layer; mod parse_layer;
mod project_tree; mod project_tree;
mod source_loader; mod source_loader;
// mod tree_loader;
pub use parse_layer::parse_layer; pub use parse_layer::parse_layer;

View File

@@ -3,6 +3,7 @@ use super::file_loader::IOResult;
use super::{project_tree, source_loader}; use super::{project_tree, source_loader};
use crate::error::ProjectResult; use crate::error::ProjectResult;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::parse::{LexerPlugin, LineParser};
use crate::representations::sourcefile::FileEntry; use crate::representations::sourcefile::FileEntry;
use crate::representations::VName; use crate::representations::VName;
use crate::utils::never; use crate::utils::never;
@@ -21,10 +22,14 @@ pub fn parse_layer<'a>(
loader: &impl Fn(&[Tok<String>]) -> IOResult, loader: &impl Fn(&[Tok<String>]) -> IOResult,
environment: &'a ProjectTree<VName>, environment: &'a ProjectTree<VName>,
prelude: &[FileEntry], prelude: &[FileEntry],
lexer_plugins: &[&dyn LexerPlugin],
line_parsers: &[&dyn LineParser],
i: &Interner, i: &Interner,
) -> ProjectResult<ProjectTree<VName>> { ) -> ProjectResult<ProjectTree<VName>> {
let sl_ctx =
source_loader::Context { prelude, i, lexer_plugins, line_parsers };
let (preparsed, source) = 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() environment.0.walk_ref(&[], path, false).is_ok()
})?; })?;
let tree = let tree =

View File

@@ -13,7 +13,7 @@ use crate::sourcefile::{
}; };
use crate::tree::{ModEntry, ModMember, Module}; use crate::tree::{ModEntry, ModMember, Module};
use crate::utils::get_or::get_or_default; 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}; use crate::{Tok, VName};
#[must_use = "A submodule may not be integrated into the tree"] #[must_use = "A submodule may not be integrated into the tree"]
@@ -28,7 +28,7 @@ pub struct TreeReport {
pub fn build_tree( pub fn build_tree(
path: &VName, path: &VName,
source: Vec<FileEntry>, source: Vec<FileEntry>,
Module { entries, extra }: PreMod, Module { entries, .. }: PreMod,
imports: ImpMod, imports: ImpMod,
prelude: &[FileEntry], prelude: &[FileEntry],
) -> ProjectResult<TreeReport> { ) -> ProjectResult<TreeReport> {
@@ -56,20 +56,11 @@ pub fn build_tree(
MemberKind::Constant(Constant { name, value }) => { MemberKind::Constant(Constant { name, value }) => {
consts.insert(name, value /* .prefix(path, &|_| false) */); consts.insert(name, value /* .prefix(path, &|_| false) */);
}, },
MemberKind::Operators(_) => (),
MemberKind::Rule(rule) => rule_fragments.push(rule), MemberKind::Rule(rule) => rule_fragments.push(rule),
}, },
} }
} }
let mod_details = extra.details().expect("Directories handled elsewhere"); let rules = rule_fragments;
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 (pre_subs, pre_items) = (entries.into_iter()) let (pre_subs, pre_items) = (entries.into_iter())
.partition_map::<HashMap<_, _>, HashMap<_, _>, _, _, _>( .partition_map::<HashMap<_, _>, HashMap<_, _>, _, _, _>(
|(k, ModEntry { exported, member })| match member { |(k, ModEntry { exported, member })| match member {
@@ -98,7 +89,7 @@ pub fn build_tree(
Ok((k, ModEntry { exported, member })) Ok((k, ModEntry { exported, member }))
}) })
.chain((pre_items.into_iter()).map( .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) { let item = match imports_from.get(&k) {
Some(_) if has_value => { Some(_) if has_value => {
// Local value cannot be assigned to imported key // Local value cannot be assigned to imported key
@@ -112,12 +103,10 @@ pub fn build_tree(
}, },
None => { None => {
let k = consts.remove(&k).map_or(ItemKind::None, ItemKind::Const); let k = consts.remove(&k).map_or(ItemKind::None, ItemKind::Const);
ProjectItem { is_op, kind: k } ProjectItem { kind: k }
},
Some(report) => ProjectItem {
is_op: is_op | report.is_op,
kind: ItemKind::Alias(report.source.clone()),
}, },
Some(report) =>
ProjectItem { kind: ItemKind::Alias(report.source.clone()) },
}; };
Ok((k, ModEntry { exported, member: ModMember::Item(item) })) Ok((k, ModEntry { exported, member: ModMember::Item(item) }))
}, },
@@ -129,7 +118,6 @@ pub fn build_tree(
exported: false, exported: false,
member: ModMember::Item(ProjectItem { member: ModMember::Item(ProjectItem {
kind: ItemKind::Alias(from.source.clone()), kind: ItemKind::Alias(from.source.clone()),
is_op: from.is_op,
}), }),
}) })
}); });

View File

@@ -11,7 +11,7 @@ use crate::representations::project::ImpReport;
use crate::sourcefile::{absolute_path, Import}; use crate::sourcefile::{absolute_path, Import};
use crate::tree::{ErrKind, ModEntry, ModMember, Module, WalkError}; use crate::tree::{ErrKind, ModEntry, ModMember, Module, WalkError};
use crate::utils::boxed_iter::{box_chain, box_once}; 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::utils::{unwrap_or, BoxedIter};
use crate::{Interner, ProjectTree, Tok, VName}; use crate::{Interner, ProjectTree, Tok, VName};
@@ -65,17 +65,13 @@ pub fn assert_visible_overlay<'a>(
}) })
} }
pub fn process_donor_module<'a, TItem: Clone>( pub fn process_donor_module<TItem: Clone>(
module: &'a Module<TItem, impl Clone>, module: &Module<TItem, impl Clone>,
abs_path: Rc<VName>, abs_path: Rc<VName>,
is_op: impl Fn(&TItem) -> bool + 'a, ) -> impl Iterator<Item = (Tok<String>, VName)> + '_ {
) -> impl Iterator<Item = (Tok<String>, VName, bool)> + 'a { (module.entries.iter())
(module.entries.iter()).filter(|(_, ent)| ent.exported).map( .filter(|(_, ent)| ent.exported)
move |(n, ent)| { .map(move |(n, _)| (n.clone(), pushed_ref(abs_path.as_ref(), n.clone())))
let is_op = ent.item().map_or(false, &is_op);
(n.clone(), pushed_ref(abs_path.as_ref(), n.clone()), is_op)
},
)
} }
pub fn import_tree( pub fn import_tree(
@@ -99,14 +95,7 @@ pub fn import_tree(
// println!("Old root: {:#?}", &prev_root.0); // println!("Old root: {:#?}", &prev_root.0);
panic!("{}", e.at(location)) panic!("{}", e.at(location))
})?; })?;
let is_op = (root.0.walk1_ref(&[], &abs_path, false)) box_once((name.clone(), abs_path))
.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))
} else { } else {
let rc_path = Rc::new(abs_path); let rc_path = Rc::new(abs_path);
// wildcard imports are validated // wildcard imports are validated
@@ -116,8 +105,7 @@ pub fn import_tree(
let new_imports = match (root.0).walk_ref(&[], &rc_path, false) { let new_imports = match (root.0).walk_ref(&[], &rc_path, false) {
Err(e) if e.kind == ErrKind::Missing => Err(e), Err(e) if e.kind == ErrKind::Missing => Err(e),
Err(e) => return Err(e.at(location)), Err(e) => return Err(e.at(location)),
Ok(module) Ok(module) => Ok(process_donor_module(module, rc_path.clone()))
=> Ok(process_donor_module(module, rc_path.clone(), |i| i.is_op))
}; };
let old_m = match (prev_root.0).walk_ref(&[], &rc_path, false) { let old_m = match (prev_root.0).walk_ref(&[], &rc_path, false) {
Err(e) if e.kind != ErrKind::Missing => return Err(e.at(location)), 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, 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 { match new_imports {
Err(_) => Box::new(it1), Err(_) => Box::new(it1),
Ok(it2) => box_chain!(it1, it2) Ok(it2) => box_chain!(it1, it2)
@@ -144,10 +132,10 @@ pub fn import_tree(
// leaf sets flattened to leaves // leaf sets flattened to leaves
.flatten_ok() .flatten_ok()
// translated to entries // translated to entries
.map_ok(|(name, source, is_op)| { .map_ok(|(name, source)| {
(name, ModEntry { (name, ModEntry {
exported: false, // this is irrelevant but needed exported: false, // this is irrelevant but needed
member: ModMember::Item(ImpReport { source, is_op }), member: ModMember::Item(ImpReport { source }),
}) })
}) })
.chain( .chain(

View File

@@ -1,5 +1,3 @@
use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use itertools::Itertools;
@@ -7,15 +5,14 @@ use super::build_tree::{build_tree, TreeReport};
use super::import_tree::{import_tree, ImpMod}; use super::import_tree::{import_tree, ImpMod};
use crate::error::ProjectResult; use crate::error::ProjectResult;
use crate::pipeline::source_loader::{ 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::sourcefile::FileEntry;
use crate::tree::{ModEntry, ModMember, Module}; use crate::tree::{ModEntry, ModMember, Module};
use crate::utils::never::{always, unwrap_always}; use crate::utils::pure_seq::pushed_ref;
use crate::utils::pure_push::pushed_ref;
use crate::utils::unwrap_or; use crate::utils::unwrap_or;
use crate::{parse, Interner, ProjectTree, Tok, VName}; use crate::{Interner, ProjectTree, Tok, VName};
pub fn rebuild_file( pub fn rebuild_file(
path: Vec<Tok<String>>, path: Vec<Tok<String>>,
@@ -23,35 +20,12 @@ pub fn rebuild_file(
imports: ImpMod, imports: ImpMod,
source: &LoadedSourceTable, source: &LoadedSourceTable,
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner,
) -> ProjectResult<ProjectMod<VName>> { ) -> ProjectResult<ProjectMod<VName>> {
let file = match &pre.extra { let file = match &pre.extra {
PreExtra::Dir => panic!("Dir should not hand this node off"), PreExtra::Dir => panic!("Dir should not hand this node off"),
PreExtra::Submod(_) => panic!("should not have received this"), PreExtra::Submod(_) => panic!("should not have received this"),
PreExtra::File(f) => f, 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(|| { let src = source.get(&file.name).unwrap_or_else(|| {
panic!( panic!(
"{} should have been preparsed already. Preparsed files are {}", "{} should have been preparsed already. Preparsed files are {}",
@@ -62,13 +36,11 @@ pub fn rebuild_file(
.join(", ") .join(", ")
) )
}); });
let entries = parse::parse2(&src.text, ctx)?; let entries = src.entries.clone();
let TreeReport { entries: items, rules, imports_from } = let TreeReport { entries, rules, imports_from } =
build_tree(&path, entries, pre, imports, prelude)?; build_tree(&path, entries, pre, imports, prelude)?;
Ok(Module { let file = Some(path.clone());
entries: items, Ok(Module { entries, extra: ProjectExt { file, path, imports_from, rules } })
extra: ProjectExt { file: Some(path.clone()), path, imports_from, rules },
})
} }
pub fn rebuild_dir( pub fn rebuild_dir(
@@ -77,15 +49,14 @@ pub fn rebuild_dir(
mut imports: ImpMod, mut imports: ImpMod,
source: &LoadedSourceTable, source: &LoadedSourceTable,
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner,
) -> ProjectResult<ProjectMod<VName>> { ) -> ProjectResult<ProjectMod<VName>> {
match pre.extra { match pre.extra {
PreExtra::Dir => (), PreExtra::Dir => (),
PreExtra::File(_) => 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"), PreExtra::Submod(_) => panic!("Dirs contain dirs and files"),
} }
let items = (pre.entries.into_iter()) let entries = (pre.entries.into_iter())
.map(|(name, entry)| { .map(|(name, entry)| {
match imports.entries.remove(&name).map(|e| e.member) { match imports.entries.remove(&name).map(|e| e.member) {
Some(ModMember::Sub(impmod)) => (name, entry, impmod), Some(ModMember::Sub(impmod)) => (name, entry, impmod),
@@ -97,12 +68,8 @@ pub fn rebuild_dir(
let pre = unwrap_or!(member => ModMember::Sub; let pre = unwrap_or!(member => ModMember::Sub;
panic!("Dirs can only contain submodules") panic!("Dirs can only contain submodules")
); );
Ok((name, ModEntry { let module = rebuild_dir(path, pre, impmod, source, prelude)?;
exported, Ok((name, ModEntry { exported, member: ModMember::Sub(module) }))
member: ModMember::Sub(rebuild_dir(
path, pre, impmod, source, prelude, i,
)?),
}))
}) })
.collect::<Result<HashMap<_, _>, _>>()?; .collect::<Result<HashMap<_, _>, _>>()?;
Ok(Module { Ok(Module {
@@ -112,7 +79,7 @@ pub fn rebuild_dir(
rules: Vec::new(), rules: Vec::new(),
file: None, file: None,
}, },
entries: items, entries,
}) })
} }
@@ -126,6 +93,6 @@ pub fn rebuild_tree(
) -> ProjectResult<ProjectTree<VName>> { ) -> ProjectResult<ProjectTree<VName>> {
let imports = let imports =
import_tree(Vec::new(), &preparsed.0, &preparsed, prev_root, i)?; 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) .map(ProjectTree)
} }

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use hashbrown::HashMap; use hashbrown::HashMap;
use super::loaded_source::{LoadedSource, LoadedSourceTable}; use super::loaded_source::{LoadedSource, LoadedSourceTable};
@@ -7,24 +9,32 @@ use crate::error::{
NoTargets, ProjectError, ProjectResult, UnexpectedDirectory, NoTargets, ProjectError, ProjectResult, UnexpectedDirectory,
}; };
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::parse::{self, LexerPlugin, LineParser, ParsingContext};
use crate::pipeline::file_loader::{IOResult, Loaded}; use crate::pipeline::file_loader::{IOResult, Loaded};
use crate::pipeline::import_abs_path::import_abs_path; use crate::pipeline::import_abs_path::import_abs_path;
use crate::representations::sourcefile::FileEntry; use crate::representations::sourcefile::FileEntry;
use crate::tree::Module; 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::utils::{split_max_prefix, unwrap_or};
use crate::Location; 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, /// Load the source at the given path or all within if it's a collection,
/// and all sources imported from these. /// and all sources imported from these.
fn load_abs_path_rec( fn load_abs_path_rec(
abs_path: &[Tok<String>], abs_path: &[Tok<String>],
mut all: Preparsed, mut all: Preparsed,
source: &mut LoadedSourceTable, source: &mut LoadedSourceTable,
prelude: &[FileEntry],
i: &Interner,
get_source: &impl Fn(&[Tok<String>]) -> IOResult, get_source: &impl Fn(&[Tok<String>]) -> IOResult,
is_injected_module: &impl Fn(&[Tok<String>]) -> bool, is_injected_module: &impl Fn(&[Tok<String>]) -> bool,
ctx @ Context { i, lexer_plugins, line_parsers, prelude }: Context,
) -> ProjectResult<Preparsed> { ) -> ProjectResult<Preparsed> {
// # Termination // # Termination
// //
@@ -47,8 +57,15 @@ fn load_abs_path_rec(
let text = unwrap_or!(get_source(filename)? => Loaded::Code; { let text = unwrap_or!(get_source(filename)? => Loaded::Code; {
return Err(UnexpectedDirectory { path: filename.to_vec() }.rc()) return Err(UnexpectedDirectory { path: filename.to_vec() }.rc())
}); });
source.insert(filename.to_vec(), LoadedSource { text: text.clone() }); let entries = parse::parse2(ParsingContext::new(
let preparsed = preparse(filename.to_vec(), text.as_str(), prelude, i)?; 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 // recurse on all imported modules
// will be taken and returned by the closure. None iff an error is thrown // will be taken and returned by the closure. None iff an error is thrown
all = preparsed.0.search_all(all, &mut |modpath, all = preparsed.0.search_all(all, &mut |modpath,
@@ -73,10 +90,9 @@ fn load_abs_path_rec(
&abs_pathv, &abs_pathv,
all, all,
source, source,
prelude,
i,
get_source, get_source,
is_injected_module, is_injected_module,
ctx,
)?; )?;
} }
Ok(all) Ok(all)
@@ -105,10 +121,9 @@ fn load_abs_path_rec(
&abs_subpath, &abs_subpath,
all, all,
source, source,
prelude,
i,
get_source, get_source,
is_injected_module, is_injected_module,
ctx,
)?; )?;
} }
Ok(all) Ok(all)
@@ -123,8 +138,7 @@ fn load_abs_path_rec(
/// injected data (the ProjectTree doesn't make a distinction between the two) /// injected data (the ProjectTree doesn't make a distinction between the two)
pub fn load_source<'a>( pub fn load_source<'a>(
targets: impl Iterator<Item = &'a [Tok<String>]>, targets: impl Iterator<Item = &'a [Tok<String>]>,
prelude: &[FileEntry], ctx: Context,
i: &Interner,
get_source: &impl Fn(&[Tok<String>]) -> IOResult, get_source: &impl Fn(&[Tok<String>]) -> IOResult,
is_injected_module: &impl Fn(&[Tok<String>]) -> bool, is_injected_module: &impl Fn(&[Tok<String>]) -> bool,
) -> ProjectResult<(Preparsed, LoadedSourceTable)> { ) -> ProjectResult<(Preparsed, LoadedSourceTable)> {
@@ -138,10 +152,9 @@ pub fn load_source<'a>(
target, target,
all, all,
&mut table, &mut table,
prelude,
i,
get_source, get_source,
is_injected_module, is_injected_module,
ctx,
)?; )?;
} }
if any_target { Ok((all, table)) } else { Err(NoTargets.rc()) } if any_target { Ok((all, table)) } else { Err(NoTargets.rc()) }

View File

@@ -1,11 +1,11 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc;
use crate::representations::VName; use crate::representations::VName;
use crate::sourcefile::FileEntry;
#[derive(Debug)] #[derive(Debug)]
pub struct LoadedSource { pub struct LoadedSource {
pub text: Rc<String>, pub entries: Vec<FileEntry>
} }
pub type LoadedSourceTable = HashMap<VName, LoadedSource>; pub type LoadedSourceTable = HashMap<VName, LoadedSource>;

View File

@@ -20,6 +20,6 @@ mod loaded_source;
mod preparse; mod preparse;
mod types; mod types;
pub use load_source::load_source; pub use load_source::{load_source, Context};
pub use loaded_source::{LoadedSource, LoadedSourceTable}; pub use loaded_source::{LoadedSource, LoadedSourceTable};
pub use types::{PreExtra, PreFileExt, PreItem, PreMod, PreSubExt, Preparsed}; pub use types::{PreExtra, PreFileExt, PreItem, PreMod, PreSubExt, Preparsed};

View File

@@ -1,26 +1,21 @@
use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use itertools::Itertools;
use super::types::{PreFileExt, PreItem, PreSubExt}; use super::types::{PreFileExt, PreItem, PreSubExt};
use super::{PreExtra, Preparsed}; use super::{PreExtra, Preparsed};
use crate::ast::{Clause, Constant, Expr}; use crate::ast::{Clause, Constant};
use crate::error::{ use crate::error::{
ConflictingRoles, ProjectError, ProjectResult, VisibilityMismatch, ConflictingRoles, ProjectError, ProjectResult, VisibilityMismatch,
}; };
use crate::interner::Interner;
use crate::parse::{self, ParsingContext};
use crate::representations::sourcefile::{FileEntry, MemberKind}; use crate::representations::sourcefile::{FileEntry, MemberKind};
use crate::representations::tree::{ModEntry, ModMember, Module}; use crate::representations::tree::{ModEntry, ModMember, Module};
use crate::sourcefile::{FileEntryKind, Import, Member, ModuleBlock}; use crate::sourcefile::{FileEntryKind, Import, Member, ModuleBlock};
use crate::utils::get_or::{get_or_default, get_or_make}; 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}; use crate::{Location, Tok, VName};
struct FileReport { struct FileReport {
entries: HashMap<Tok<String>, ModEntry<PreItem, PreExtra>>, entries: HashMap<Tok<String>, ModEntry<PreItem, PreExtra>>,
patterns: Vec<Vec<Expr<VName>>>,
imports: Vec<Import>, imports: Vec<Import>,
} }
@@ -32,12 +27,11 @@ fn to_module(
prelude: &[FileEntry], prelude: &[FileEntry],
) -> ProjectResult<FileReport> { ) -> ProjectResult<FileReport> {
let mut imports = Vec::new(); let mut imports = Vec::new();
let mut patterns = Vec::new();
let mut items = HashMap::<Tok<String>, (bool, PreItem)>::new(); let mut items = HashMap::<Tok<String>, (bool, PreItem)>::new();
let mut to_export = HashMap::<Tok<String>, Vec<Location>>::new(); let mut to_export = HashMap::<Tok<String>, Vec<Location>>::new();
let mut submods = let mut submods =
HashMap::<Tok<String>, (bool, Vec<Location>, Vec<FileEntry>)>::new(); HashMap::<Tok<String>, (bool, Vec<Location>, Vec<FileEntry>)>::new();
let entries = prelude.iter().cloned().chain(src.into_iter()); let entries = prelude.iter().cloned().chain(src);
for FileEntry { kind, locations } in entries { for FileEntry { kind, locations } in entries {
match kind { match kind {
FileEntryKind::Import(imp) => imports.extend(imp.into_iter()), FileEntryKind::Import(imp) => imports.extend(imp.into_iter()),
@@ -72,17 +66,7 @@ fn to_module(
submods.insert(name.clone(), (exported, locations, body.clone())); submods.insert(name.clone(), (exported, locations, body.clone()));
} }
}, },
MemberKind::Operators(ops) => MemberKind::Rule(r) =>
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());
if exported { if exported {
for ex in r.pattern { for ex in r.pattern {
ex.search_all(&mut |ex| { ex.search_all(&mut |ex| {
@@ -95,7 +79,6 @@ fn to_module(
None::<()> None::<()>
}); });
} }
}
}, },
}, },
_ => (), _ => (),
@@ -111,11 +94,11 @@ fn to_module(
.try_insert(subname.clone(), ModEntry { .try_insert(subname.clone(), ModEntry {
member: ModMember::Sub({ member: ModMember::Sub({
name.push(subname); name.push(subname);
let FileReport { imports, entries: items, patterns } = let FileReport { imports, entries: items } =
to_module(file, name.clone(), body, prelude)?; to_module(file, name.clone(), body, prelude)?;
Module { Module {
entries: items, entries: items,
extra: PreExtra::Submod(PreSubExt { imports, patterns }), extra: PreExtra::Submod(PreSubExt { imports }),
} }
}), }),
exported, exported,
@@ -125,7 +108,6 @@ fn to_module(
for (item, locations) in to_export { for (item, locations) in to_export {
get_or_make(&mut entries, &item, || ModEntry { get_or_make(&mut entries, &item, || ModEntry {
member: ModMember::Item(PreItem { member: ModMember::Item(PreItem {
is_op: false,
has_value: false, has_value: false,
location: locations[0].clone(), location: locations[0].clone(),
}), }),
@@ -133,26 +115,22 @@ fn to_module(
}) })
.exported = true .exported = true
} }
Ok(FileReport { entries, imports, patterns }) Ok(FileReport { entries, imports })
} }
/// Preparse the module. At this stage, only the imports and /// Preparse the module. At this stage, only the imports and
/// names defined by the module can be parsed /// names defined by the module can be parsed
pub fn preparse( pub fn preparse(
file: VName, file: VName,
source: &str, entries: Vec<FileEntry>,
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner,
) -> ProjectResult<Preparsed> { ) -> ProjectResult<Preparsed> {
// Parse with no operators let FileReport { entries, imports } =
let ctx = ParsingContext::new(&[], i, Rc::new(file.clone()));
let entries = parse::parse2(source, ctx)?;
let FileReport { entries, imports, patterns } =
to_module(&file, file.clone(), entries, prelude)?; to_module(&file, file.clone(), entries, prelude)?;
let mut module = Module { let mut module = Module {
entries, entries,
extra: PreExtra::File(PreFileExt { extra: PreExtra::File(PreFileExt {
details: PreSubExt { patterns, imports }, details: PreSubExt { imports },
name: file.clone(), name: file.clone(),
}), }),
}; };

View File

@@ -1,7 +1,6 @@
use std::fmt::Display; use std::fmt::Display;
use std::ops::Add; use std::ops::Add;
use crate::ast::Expr;
use crate::error::ProjectResult; use crate::error::ProjectResult;
use crate::sourcefile::Import; use crate::sourcefile::Import;
use crate::tree::Module; use crate::tree::Module;
@@ -9,34 +8,27 @@ use crate::{Interner, Location, VName};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PreItem { pub struct PreItem {
pub is_op: bool,
pub has_value: bool, pub has_value: bool,
pub location: Location, pub location: Location,
} }
impl Display for PreItem { impl Display for PreItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { has_value, is_op, location } = self; let Self { has_value, location } = self;
let description = match (is_op, has_value) { let description = if *has_value { "value" } else { "keyword" };
(true, true) => "operator with value",
(true, false) => "operator",
(false, true) => "value",
(false, false) => "keyword",
};
write!(f, "{description} {location}") write!(f, "{description} {location}")
} }
} }
impl Default for PreItem { impl Default for PreItem {
fn default() -> Self { fn default() -> Self {
PreItem { is_op: false, has_value: false, location: Location::Unknown } PreItem { has_value: false, location: Location::Unknown }
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PreSubExt { pub struct PreSubExt {
pub imports: Vec<Import>, pub imports: Vec<Import>,
pub patterns: Vec<Vec<Expr<VName>>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@@ -15,13 +15,13 @@ use ordered_float::NotNan;
use super::interpreted; use super::interpreted;
use super::location::Location; use super::location::Location;
use super::namelike::{NameLike, VName}; use super::namelike::{NameLike, VName};
use super::primitive::Primitive; use crate::foreign::{Atom, ExFn};
use crate::interner::Tok; use crate::interner::Tok;
use crate::parse::print_nat16; use crate::parse::print_nat16;
use crate::utils::rc_tools::map_rc; use crate::utils::rc_tools::map_rc;
/// A [Clause] with associated metadata /// A [Clause] with associated metadata
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug)]
pub struct Expr<N: NameLike> { pub struct Expr<N: NameLike> {
/// The actual value /// The actual value
pub value: Clause<N>, pub value: Clause<N>,
@@ -100,7 +100,7 @@ pub enum PHClass {
/// If true, must match at least one clause /// If true, must match at least one clause
nonzero: bool, nonzero: bool,
/// Greediness in the allocation of tokens /// Greediness in the allocation of tokens
prio: u64, prio: usize,
}, },
/// Matches exactly one token, lambda or parenthesized group /// Matches exactly one token, lambda or parenthesized group
Scalar, Scalar,
@@ -129,10 +129,12 @@ impl Display for Placeholder {
} }
/// An S-expression as read from a source file /// An S-expression as read from a source file
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub enum Clause<N: NameLike> { pub enum Clause<N: NameLike> {
/// A primitive /// An opaque function, eg. an effectful function employing CPS
P(Primitive), ExternFn(ExFn),
/// An opaque non-callable value, eg. a file handle
Atom(Atom),
/// A c-style name or an operator, eg. `+`, `i`, `foo::bar` /// A c-style name or an operator, eg. `+`, `i`, `foo::bar`
Name(N), Name(N),
/// A parenthesized expression /// A parenthesized expression
@@ -214,7 +216,7 @@ impl<N: NameLike> Clause<N> {
#[must_use] #[must_use]
pub fn map_names(&self, pred: &impl Fn(&N) -> Option<N>) -> Option<Self> { pub fn map_names(&self, pred: &impl Fn(&N) -> Option<N>) -> Option<Self> {
match self { match self {
Clause::P(_) | Clause::Placeh(_) => None, Clause::Atom(_) | Clause::ExternFn(_) | Clause::Placeh(_) => None,
Clause::Name(name) => pred(name).map(Clause::Name), Clause::Name(name) => pred(name).map(Clause::Name),
Clause::S(c, body) => { Clause::S(c, body) => {
let mut any_some = false; let mut any_some = false;
@@ -262,7 +264,8 @@ impl<N: NameLike> Clause<N> {
match self { match self {
Self::Name(n) => Clause::Name(pred(n)), Self::Name(n) => Clause::Name(pred(n)),
Self::Placeh(p) => Clause::Placeh(p), 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( Self::Lambda(n, b) => Clause::Lambda(
map_rc(n, |n| n.into_iter().map(|e| e.transform_names(pred)).collect()), 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()), map_rc(b, |b| b.into_iter().map(|e| e.transform_names(pred)).collect()),
@@ -282,7 +285,8 @@ impl<N: NameLike> Clause<N> {
match self { match self {
Clause::Lambda(arg, body) => Clause::Lambda(arg, body) =>
arg.iter().chain(body.iter()).find_map(|expr| expr.search_all(f)), 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)), Clause::S(_, body) => body.iter().find_map(|expr| expr.search_all(f)),
} }
} }
@@ -295,7 +299,8 @@ impl<N: NameLike> Clause<N> {
match self { match self {
Clause::Lambda(arg, body) => Clause::Lambda(arg, body) =>
search_all_slcs(arg, f).or_else(|| search_all_slcs(body, f)), 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), Clause::S(_, body) => search_all_slcs(body, f),
} }
} }
@@ -325,7 +330,8 @@ impl Clause<VName> {
impl<N: NameLike> Display for Clause<N> { impl<N: NameLike> Display for Clause<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { 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::Name(name) => write!(f, "{}", name.to_strv().join("::")),
Self::S(del, items) => { Self::S(del, items) => {
let body = items.iter().join(" "); let body = items.iter().join(" ");
@@ -348,7 +354,7 @@ impl<N: NameLike> Display for Clause<N> {
} }
/// A substitution rule as read from the source /// A substitution rule as read from the source
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone)]
pub struct Rule<N: NameLike> { pub struct Rule<N: NameLike> {
/// Tree fragment in the source code that activates this rule /// Tree fragment in the source code that activates this rule
pub pattern: Vec<Expr<N>>, pub pattern: Vec<Expr<N>>,
@@ -407,7 +413,7 @@ impl<N: NameLike> Display for Rule<N> {
} }
/// A named constant /// A named constant
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone)]
pub struct Constant { pub struct Constant {
/// Used to reference the constant /// Used to reference the constant
pub name: Tok<String>, pub name: Tok<String>,

View File

@@ -107,7 +107,8 @@ fn expr_rec<'a>(
Ok(postmacro::Expr { value: expr.value, location: location.clone() }) Ok(postmacro::Expr { value: expr.value, location: location.clone() })
} else { } else {
let value = match value { 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) => { ast::Clause::Lambda(arg, b) => {
let name = match &arg[..] { let name = match &arg[..] {
[ast::Expr { value: ast::Clause::Name(name), .. }] => name, [ast::Expr { value: ast::Clause::Name(name), .. }] => name,

View File

@@ -4,12 +4,12 @@ use hashbrown::HashMap;
use super::project::{ItemKind, ProjectItem}; use super::project::{ItemKind, ProjectItem};
use crate::ast::{Clause, Expr}; use crate::ast::{Clause, Expr};
use crate::foreign::{Atom, Atomic, ExternFn}; use crate::foreign::{Atom, Atomic, ExFn, ExternFn};
use crate::interner::Tok; use crate::interner::Tok;
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::representations::project::{ProjectExt, ProjectMod, ProjectTree}; use crate::representations::project::{ProjectExt, ProjectMod, ProjectTree};
use crate::representations::tree::{ModEntry, ModMember, Module}; use crate::representations::tree::{ModEntry, ModMember, Module};
use crate::representations::{Primitive, VName}; use crate::representations::VName;
use crate::utils::substack::Substack; use crate::utils::substack::Substack;
/// A lightweight module tree that can be built declaratively by hand to /// A lightweight module tree that can be built declaratively by hand to
@@ -25,21 +25,18 @@ pub enum ConstTree {
impl ConstTree { impl ConstTree {
/// Describe a [Primitive] /// Describe a [Primitive]
#[must_use] #[must_use]
pub fn primitive(primitive: Primitive) -> Self { pub fn clause(value: Clause<VName>) -> Self {
Self::Const(Expr { Self::Const(Expr { location: Location::Unknown, value })
location: Location::Unknown,
value: Clause::P(primitive),
})
} }
/// Describe an [ExternFn] /// Describe an [ExternFn]
#[must_use] #[must_use]
pub fn xfn(xfn: impl ExternFn + 'static) -> Self { 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] /// Describe an [Atomic]
#[must_use] #[must_use]
pub fn atom(atom: impl Atomic + 'static) -> Self { 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 /// Describe a module
#[must_use] #[must_use]
@@ -85,7 +82,7 @@ impl Add for ConstTree {
product.insert(key, i1); product.insert(key, i1);
} }
} }
product.extend(t2.into_iter()); product.extend(t2);
Self::Tree(product) Self::Tree(product)
} else { } else {
panic!("cannot combine tree and value fields") panic!("cannot combine tree and value fields")
@@ -104,10 +101,8 @@ fn from_const_tree_rec(
items.insert(name.clone(), ModEntry { items.insert(name.clone(), ModEntry {
exported: true, exported: true,
member: match item { member: match item {
ConstTree::Const(c) => ModMember::Item(ProjectItem { ConstTree::Const(c) =>
kind: ItemKind::Const(c), ModMember::Item(ProjectItem { kind: ItemKind::Const(c) }),
is_op: false,
}),
ConstTree::Tree(t) => ConstTree::Tree(t) =>
ModMember::Sub(from_const_tree_rec(path.push(name), t, file)), ModMember::Sub(from_const_tree_rec(path.push(name), t, file)),
}, },

View File

@@ -2,20 +2,18 @@
//! //!
//! This code may be generated to minimize the number of states external //! This code may be generated to minimize the number of states external
//! functions have to define //! functions have to define
use std::cell::RefCell;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Arc, TryLockError, Mutex};
#[allow(unused)] // for doc #[allow(unused)] // for doc
use super::ast; use super::ast;
use super::location::Location; use super::location::Location;
use super::path_set::PathSet; use super::path_set::PathSet;
use super::primitive::Primitive;
use super::Literal;
#[allow(unused)] // for doc #[allow(unused)] // for doc
use crate::foreign::Atomic; use crate::foreign::Atomic;
use crate::foreign::ExternError; use crate::foreign::{Atom, ExFn, ExternError};
use crate::utils::ddispatch::request; use crate::utils::ddispatch::request;
use crate::utils::take_with_output; use crate::utils::take_with_output;
use crate::Sym; use crate::Sym;
@@ -40,10 +38,11 @@ impl Debug for Expr {
impl Display for Expr { impl Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.location { write!(f, "{}", self.clause)
Location::Unknown => write!(f, "{}", self.clause), // match &self.location {
loc => write!(f, "{}:({})", loc, self.clause), // 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 /// A wrapper around expressions to handle their multiple occurences in
/// the tree together /// the tree together
#[derive(Clone)] #[derive(Clone)]
pub struct ExprInst(pub Rc<RefCell<Expr>>); pub struct ExprInst(pub Arc<Mutex<Expr>>);
impl ExprInst { impl ExprInst {
/// Wrap an [Expr] in a shared container so that normalizatoin steps are /// Wrap an [Expr] in a shared container so that normalizatoin steps are
/// applied to all references /// applied to all references
#[must_use] #[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 /// Take the [Expr] out of this container if it's the last reference to it, or
/// clone it out. /// clone it out.
#[must_use] #[must_use]
pub fn expr_val(self) -> Expr { pub fn expr_val(self) -> Expr {
Rc::try_unwrap(self.0) Arc::try_unwrap(self.0)
.map(|c| c.into_inner()) .map(|c| c.into_inner().unwrap())
.unwrap_or_else(|rc| rc.as_ref().borrow().deref().clone()) .unwrap_or_else(|arc| arc.lock().unwrap().clone())
} }
/// Read-only access to the shared expression instance /// Read-only access to the shared expression instance
@@ -87,7 +86,7 @@ impl ExprInst {
/// if the expression is already borrowed in read-write mode /// if the expression is already borrowed in read-write mode
#[must_use] #[must_use]
pub fn expr(&self) -> impl Deref<Target = Expr> + '_ { pub fn expr(&self) -> impl Deref<Target = Expr> + '_ {
self.0.as_ref().borrow() self.0.lock().unwrap()
} }
/// Read-Write access to the shared expression instance /// Read-Write access to the shared expression instance
@@ -97,7 +96,7 @@ impl ExprInst {
/// if the expression is already borrowed /// if the expression is already borrowed
#[must_use] #[must_use]
pub fn expr_mut(&self) -> impl DerefMut<Target = Expr> + '_ { pub fn expr_mut(&self) -> impl DerefMut<Target = Expr> + '_ {
self.0.as_ref().borrow_mut() self.0.lock().unwrap()
} }
/// Call a normalization function on the expression. The expr is /// Call a normalization function on the expression. The expr is
@@ -137,26 +136,6 @@ impl ExprInst {
predicate(&self.expr().clause) 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 /// Visit all expressions in the tree. The search can be exited early by
/// returning [Some] /// returning [Some]
/// ///
@@ -174,7 +153,8 @@ impl ExprInst {
Clause::Lambda { body, .. } => body.search_all(predicate), Clause::Lambda { body, .. } => body.search_all(predicate),
Clause::Constant(_) Clause::Constant(_)
| Clause::LambdaArg | Clause::LambdaArg
| Clause::P(_) | Clause::Atom(_)
| Clause::ExternFn(_)
| Clause::Bottom => None, | Clause::Bottom => None,
}) })
} }
@@ -195,7 +175,7 @@ impl ExprInst {
#[must_use = "your request might not have succeeded"] #[must_use = "your request might not have succeeded"]
pub fn request<T: 'static>(&self) -> Option<T> { pub fn request<T: 'static>(&self) -> Option<T> {
match &self.expr().clause { match &self.expr().clause {
Clause::P(Primitive::Atom(a)) => request(&*a.0), Clause::Atom(a) => request(&*a.0),
_ => None, _ => None,
} }
} }
@@ -203,18 +183,20 @@ impl ExprInst {
impl Debug for ExprInst { impl Debug for ExprInst {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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:?}"), Ok(expr) => write!(f, "{expr:?}"),
Err(_) => write!(f, "<borrowed>"), Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
} }
} }
} }
impl Display for ExprInst { impl Display for ExprInst {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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}"), Ok(expr) => write!(f, "{expr}"),
Err(_) => write!(f, "<borrowed>"), Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
} }
} }
} }
@@ -224,8 +206,10 @@ impl Display for ExprInst {
pub enum Clause { pub enum Clause {
/// An expression that causes an error /// An expression that causes an error
Bottom, Bottom,
/// An unintrospectable unit /// An opaque function, eg. an effectful function employing CPS
P(Primitive), ExternFn(ExFn),
/// An opaque non-callable value, eg. a file handle
Atom(Atom),
/// A function application /// A function application
Apply { Apply {
/// Function to be applied /// Function to be applied
@@ -252,7 +236,7 @@ impl Clause {
/// copied or moved clauses as it does not have debug information and /// copied or moved clauses as it does not have debug information and
/// does not share a normalization cache list with them. /// does not share a normalization cache list with them.
pub fn wrap(self) -> ExprInst { pub fn wrap(self) -> ExprInst {
ExprInst(Rc::new(RefCell::new(Expr { ExprInst(Arc::new(Mutex::new(Expr {
location: Location::Unknown, location: Location::Unknown,
clause: self, clause: self,
}))) })))
@@ -284,7 +268,8 @@ impl Clause {
impl Display for Clause { impl Display for Clause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { 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::Bottom => write!(f, "bottom"),
Clause::LambdaArg => write!(f, "arg"), Clause::LambdaArg => write!(f, "arg"),
Clause::Apply { f: fun, x } => write!(f, "({fun} {x})"), Clause::Apply { f: fun, x } => write!(f, "({fun} {x})"),
@@ -296,11 +281,3 @@ impl Display for Clause {
} }
} }
} }
impl<T: Into<Literal>> From<T> for Clause {
fn from(value: T) -> Self { Self::P(Primitive::Literal(value.into())) }
}
impl<T: Into<Clause>> From<T> for ExprInst {
fn from(value: T) -> Self { value.into().wrap() }
}

View File

@@ -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<f64>),
/// 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<NotNan<f64>> for Literal {
fn from(value: NotNan<f64>) -> Self { Self::Num(value) }
}
impl From<u64> for Literal {
fn from(value: u64) -> Self { Self::Uint(value) }
}
impl From<String> for Literal {
fn from(value: String) -> Self { Self::Str(value.into()) }
}

View File

@@ -1,6 +1,6 @@
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::sync::Arc;
use itertools::Itertools; use itertools::Itertools;
@@ -13,15 +13,15 @@ pub enum Location {
/// Location information lost or code generated on the fly /// Location information lost or code generated on the fly
Unknown, Unknown,
/// Only the file is known /// Only the file is known
File(Rc<VName>), File(Arc<VName>),
/// Character slice of the code /// Character slice of the code
Range { Range {
/// Argument to the file loading callback that produced this code /// Argument to the file loading callback that produced this code
file: Rc<VName>, file: Arc<VName>,
/// Index of the unicode code points associated with the code /// Index of the unicode code points associated with the code
range: Range<usize>, range: Range<usize>,
/// The full source code as received by the parser /// The full source code as received by the parser
source: Rc<String>, source: Arc<String>,
}, },
} }
@@ -38,7 +38,7 @@ impl Location {
/// File, if known /// File, if known
#[must_use] #[must_use]
pub fn file(&self) -> Option<Rc<VName>> { pub fn file(&self) -> Option<Arc<VName>> {
if let Self::File(file) | Self::Range { file, .. } = self { if let Self::File(file) | Self::Range { file, .. } = self {
Some(file.clone()) Some(file.clone())
} else { } else {
@@ -48,7 +48,7 @@ impl Location {
/// Associated source code, if known /// Associated source code, if known
#[must_use] #[must_use]
pub fn source(&self) -> Option<Rc<String>> { pub fn source(&self) -> Option<Arc<String>> {
if let Self::Range { source, .. } = self { if let Self::Range { source, .. } = self {
Some(source.clone()) Some(source.clone())
} else { } else {

View File

@@ -3,22 +3,18 @@ pub mod ast_to_interpreted;
pub mod ast_to_postmacro; pub mod ast_to_postmacro;
mod const_tree; mod const_tree;
pub mod interpreted; pub mod interpreted;
pub mod literal;
pub mod location; pub mod location;
mod namelike; mod namelike;
pub mod path_set; pub mod path_set;
pub mod postmacro; pub mod postmacro;
pub mod postmacro_to_interpreted; pub mod postmacro_to_interpreted;
pub mod primitive;
pub mod project; pub mod project;
pub mod sourcefile; pub mod sourcefile;
mod string; mod string;
pub mod tree; pub mod tree;
pub use const_tree::{from_const_tree, ConstTree}; pub use const_tree::{from_const_tree, ConstTree};
pub use literal::Literal;
pub use location::Location; pub use location::Location;
pub use namelike::{NameLike, Sym, VName}; pub use namelike::{NameLike, Sym, VName};
pub use path_set::PathSet; pub use path_set::PathSet;
pub use primitive::Primitive;
pub use string::OrcString; pub use string::OrcString;

View File

@@ -1,8 +1,8 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::Add; 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; use crate::utils::Side;
/// A branching path selecting some placeholders (but at least one) in a Lambda /// 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)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct PathSet { pub struct PathSet {
/// The definite steps /// The definite steps
pub steps: Rc<Vec<Side>>, pub steps: Arc<Vec<Side>>,
/// if Some, it splits. If None, it ends. /// if Some, it splits. If None, it ends.
pub next: Option<(Rc<PathSet>, Rc<PathSet>)>, pub next: Option<(Arc<PathSet>, Arc<PathSet>)>,
} }
impl PathSet { impl PathSet {
@@ -22,20 +22,20 @@ impl PathSet {
left: Self, left: Self,
right: Self, right: Self,
) -> Self { ) -> Self {
let steps = Rc::new(steps.into_iter().collect()); let steps = Arc::new(steps.into_iter().collect());
Self { steps, next: Some((Rc::new(left), Rc::new(right))) } Self { steps, next: Some((Arc::new(left), Arc::new(right))) }
} }
/// Create a path set for one target /// Create a path set for one target
pub fn end(steps: impl IntoIterator<Item = Side>) -> Self { pub fn end(steps: impl IntoIterator<Item = Side>) -> 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 /// Create a path set points to a slot that is a direct
/// child of the given lambda with no applications. In essence, this means /// 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 /// that this argument will be picked as the value of the expression after an
/// arbitrary amount of subsequent discarded parameters. /// 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 { impl Debug for PathSet {
@@ -57,7 +57,10 @@ impl Debug for PathSet {
impl Add for PathSet { impl Add for PathSet {
type Output = Self; type Output = Self;
fn add(self, rhs: Self) -> Self::Output { 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<Side> for PathSet {
type Output = Self; type Output = Self;
fn add(self, rhs: Side) -> Self::Output { fn add(self, rhs: Side) -> Self::Output {
let PathSet { steps, next } = self; 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); 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] #[test]
fn test_combine() -> Result<(), &'static str> { fn test_combine() -> Result<(), &'static str> {
let ps1 = 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 = 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(); let sum = ps1.clone() + ps2.clone();
assert_eq!(sum.steps.as_ref(), &[]); assert_eq!(sum.steps.as_ref(), &[]);
let nexts = sum.next.ok_or("nexts not set")?; let nexts = sum.next.ok_or("nexts not set")?;
@@ -92,16 +95,16 @@ mod tests {
fn extend_scaffold() -> PathSet { fn extend_scaffold() -> PathSet {
PathSet { PathSet {
next: Some(( next: Some((
Rc::new(PathSet { Arc::new(PathSet {
next: None, 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, 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]),
} }
} }

View File

@@ -2,7 +2,7 @@ use std::fmt::{Debug, Write};
use std::rc::Rc; use std::rc::Rc;
use super::location::Location; use super::location::Location;
use super::primitive::Primitive; use crate::foreign::{ExFn, Atom};
use crate::utils::string_from_charset; use crate::utils::string_from_charset;
use crate::Sym; use crate::Sym;
@@ -42,7 +42,10 @@ pub enum Clause {
Lambda(Rc<Expr>), Lambda(Rc<Expr>),
Constant(Sym), Constant(Sym),
LambdaArg(usize), 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"; const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz";
@@ -75,7 +78,8 @@ impl Clause {
Wrap(wl, wr): Wrap, Wrap(wl, wr): Wrap,
) -> std::fmt::Result { ) -> std::fmt::Result {
match self { 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::Lambda(body) => parametric_fmt(f, depth, "\\", body, wr),
Self::LambdaArg(skip) => { Self::LambdaArg(skip) => {
let lambda_depth = (depth - skip - 1).try_into().unwrap(); let lambda_depth = (depth - skip - 1).try_into().unwrap();

View File

@@ -1,5 +1,4 @@
use std::cell::RefCell; use std::sync::{Arc, Mutex};
use std::rc::Rc;
use super::path_set::PathSet; use super::path_set::PathSet;
use super::{interpreted, postmacro}; use super::{interpreted, postmacro};
@@ -18,12 +17,12 @@ fn collect_paths_cls_rec(
depth: usize, depth: usize,
) -> Option<PathSet> { ) -> Option<PathSet> {
match cls { match cls {
postmacro::Clause::P(_) | postmacro::Clause::Constant(_) => None, postmacro::Clause::Atom(_) | postmacro::Clause::ExternFn(_) => None,
postmacro::Clause::Constant(_) => None,
postmacro::Clause::LambdaArg(h) => postmacro::Clause::LambdaArg(h) =>
if *h != depth { match *h != depth {
None true => None,
} else { false => Some(PathSet::pick())
Some(PathSet { next: None, steps: Rc::new(vec![]) })
}, },
postmacro::Clause::Lambda(b) => collect_paths_expr_rec(b, depth + 1), postmacro::Clause::Lambda(b) => collect_paths_expr_rec(b, depth + 1),
postmacro::Clause::Apply(f, x) => { postmacro::Clause::Apply(f, x) => {
@@ -43,7 +42,8 @@ pub fn clause(cls: &postmacro::Clause) -> interpreted::Clause {
match cls { match cls {
postmacro::Clause::Constant(name) => postmacro::Clause::Constant(name) =>
interpreted::Clause::Constant(name.clone()), 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) => postmacro::Clause::Apply(f, x) =>
interpreted::Clause::Apply { f: expr(f.as_ref()), x: expr(x.as_ref()) }, interpreted::Clause::Apply { f: expr(f.as_ref()), x: expr(x.as_ref()) },
postmacro::Clause::Lambda(body) => interpreted::Clause::Lambda { 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 { 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(), location: expr.location.clone(),
clause: clause(&expr.value), clause: clause(&expr.value),
}))) })))

View File

@@ -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<dyn ExternFn>),
/// 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:?}"),
}
}
}

View File

@@ -31,26 +31,15 @@ impl<N: NameLike> Default for ItemKind<N> {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ProjectItem<N: NameLike> { pub struct ProjectItem<N: NameLike> {
pub kind: ItemKind<N>, pub kind: ItemKind<N>,
pub is_op: bool,
} }
impl<N: NameLike> Display for ProjectItem<N> { impl<N: NameLike> Display for ProjectItem<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind { match &self.kind {
ItemKind::None => match self.is_op { ItemKind::None => write!(f, "keyword"),
true => write!(f, "operator"), ItemKind::Const(c) => write!(f, "constant {c}"),
false => write!(f, "keyword"),
},
ItemKind::Const(c) => match self.is_op {
true => write!(f, "operator with value {c}"),
false => write!(f, "constant {c}"),
},
ItemKind::Alias(alias) => { ItemKind::Alias(alias) => {
let origin = Interner::extern_all(alias).join("::"); write!(f, "alias to {}", Interner::extern_all(alias).join("::"))
match self.is_op {
true => write!(f, "operator alias to {origin}"),
false => write!(f, "alias to {origin}"),
}
}, },
} }
} }
@@ -61,9 +50,6 @@ impl<N: NameLike> Display for ProjectItem<N> {
pub struct ImpReport<N: NameLike> { pub struct ImpReport<N: NameLike> {
/// Absolute path of the module the symbol is imported from /// Absolute path of the module the symbol is imported from
pub source: N, 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 /// Additional data about a loaded module beyond the list of constants and
@@ -94,8 +80,8 @@ impl<N: NameLike> Add for ProjectExt<N> {
Interner::extern_all(&self.path).join("::") Interner::extern_all(&self.path).join("::")
) )
} }
self.imports_from.extend(imports_from.into_iter()); self.imports_from.extend(imports_from);
self.rules.extend(rules.into_iter()); self.rules.extend(rules);
if file.is_some() { if file.is_some() {
self.file = file self.file = file
} }
@@ -190,7 +176,6 @@ fn vname_to_sym_tree_rec(
ModMember::Sub(module) => ModMember::Sub(module) =>
ModMember::Sub(vname_to_sym_tree_rec(module, i)), ModMember::Sub(vname_to_sym_tree_rec(module, i)),
ModMember::Item(ex) => ModMember::Item(ProjectItem { ModMember::Item(ex) => ModMember::Item(ProjectItem {
is_op: ex.is_op,
kind: match ex.kind { kind: match ex.kind {
ItemKind::None => ItemKind::None, ItemKind::None => ItemKind::None,
ItemKind::Alias(n) => ItemKind::Alias(n), ItemKind::Alias(n) => ItemKind::Alias(n),
@@ -204,7 +189,7 @@ fn vname_to_sym_tree_rec(
extra: ProjectExt { extra: ProjectExt {
path: tree.extra.path, path: tree.extra.path,
imports_from: (tree.extra.imports_from.into_iter()) 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(), .collect(),
rules: (tree.extra.rules.into_iter()) rules: (tree.extra.rules.into_iter())
.map(|Rule { pattern, prio, template }| Rule { .map(|Rule { pattern, prio, template }| Rule {

View File

@@ -8,7 +8,7 @@ use super::namelike::VName;
use crate::ast::{Constant, Rule}; use crate::ast::{Constant, Rule};
use crate::error::{ProjectError, ProjectResult, TooManySupers}; use crate::error::{ProjectError, ProjectResult, TooManySupers};
use crate::interner::{Interner, Tok}; 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::utils::{unwrap_or, BoxedIter};
use crate::Location; use crate::Location;
@@ -78,16 +78,11 @@ pub enum MemberKind {
Constant(Constant), Constant(Constant),
/// A prefixed set of other entries /// A prefixed set of other entries
Module(ModuleBlock), Module(ModuleBlock),
/// Operator declarations
Operators(Vec<Tok<String>>),
} }
impl Display for MemberKind { impl Display for MemberKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Operators(opv) => {
write!(f, "operators[{}]", opv.iter().map(|t| &**t).join(" "))
},
Self::Constant(c) => c.fmt(f), Self::Constant(c) => c.fmt(f),
Self::Module(m) => m.fmt(f), Self::Module(m) => m.fmt(f),
Self::Rule(r) => r.fmt(f), Self::Rule(r) => r.fmt(f),

View File

@@ -1,10 +1,10 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::sync::Arc;
use crate::interpreted::{Clause, ExprInst}; use crate::foreign::InertAtomic;
use crate::{Literal, Primitive, Tok}; use crate::{Interner, Tok};
/// An Orchid string which may or may not be interned /// An Orchid string which may or may not be interned
#[derive(Clone, Eq)] #[derive(Clone, Eq)]
@@ -12,7 +12,7 @@ pub enum OrcString {
/// An interned string. Equality-conpared by reference. /// An interned string. Equality-conpared by reference.
Interned(Tok<String>), Interned(Tok<String>),
/// An uninterned bare string. Equality-compared by character /// An uninterned bare string. Equality-compared by character
Runtime(Rc<String>), Runtime(Arc<String>),
} }
impl Debug for OrcString { impl Debug for OrcString {
@@ -25,23 +25,21 @@ impl Debug for OrcString {
} }
impl 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] /// Clone out the plain Rust [String]
#[must_use] #[must_use]
pub fn get_string(self) -> String { pub fn get_string(self) -> String {
match self { match self {
Self::Interned(s) => s.as_str().to_owned(), Self::Interned(s) => s.as_str().to_owned(),
Self::Runtime(rc) => 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 { impl Deref for OrcString {
@@ -62,7 +60,7 @@ impl Hash for OrcString {
} }
impl From<String> for OrcString { impl From<String> for OrcString {
fn from(value: String) -> Self { Self::Runtime(Rc::new(value)) } fn from(value: String) -> Self { Self::Runtime(Arc::new(value)) }
} }
impl From<Tok<String>> for OrcString { impl From<Tok<String>> 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 }
}

View File

@@ -174,7 +174,7 @@ impl<TItem: Clone, TExt: Clone> Module<TItem, TExt> {
(_, right) => new_items.insert(key, right), (_, 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)? }) Ok(Module { entries: new_items, extra: (self.extra + extra)? })
} }
} }

View File

@@ -8,7 +8,7 @@ use crate::rule::vec_attrs::vec_attrs;
use crate::utils::Side; use crate::utils::Side;
pub type MaxVecSplit<'a> = pub type MaxVecSplit<'a> =
(&'a [RuleExpr], (Tok<String>, u64, bool), &'a [RuleExpr]); (&'a [RuleExpr], (Tok<String>, usize, bool), &'a [RuleExpr]);
/// Derive the details of the central vectorial and the two sides from a /// Derive the details of the central vectorial and the two sides from a
/// slice of Expr's /// slice of Expr's
@@ -107,7 +107,8 @@ fn mk_vec(pattern: &[RuleExpr]) -> VecMatcher {
#[must_use] #[must_use]
fn mk_scalar(pattern: &RuleExpr) -> ScalMatcher { fn mk_scalar(pattern: &RuleExpr) -> ScalMatcher {
match &pattern.value { 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::Name(n) => ScalMatcher::Name(n.clone()),
Clause::Placeh(Placeholder { name, class }) => { Clause::Placeh(Placeholder { name, class }) => {
debug_assert!( debug_assert!(

View File

@@ -10,7 +10,8 @@ pub fn scal_match<'a>(
expr: &'a RuleExpr, expr: &'a RuleExpr,
) -> Option<State<'a>> { ) -> Option<State<'a>> {
match (matcher, &expr.value) { 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::Name(n1), Clause::Name(n2)) if n1 == n2 => Some(State::new()),
(ScalMatcher::Placeh(key), _) => (ScalMatcher::Placeh(key), _) =>
Some(State::from([(key.clone(), StateEntry::Scalar(expr))])), Some(State::from([(key.clone(), StateEntry::Scalar(expr))])),

View File

@@ -5,15 +5,15 @@ use itertools::Itertools;
use super::any_match::any_match; use super::any_match::any_match;
use super::build::mk_any; use super::build::mk_any;
use crate::foreign::Atom;
use crate::interner::Tok; use crate::interner::Tok;
use crate::representations::Primitive;
use crate::rule::matcher::{Matcher, RuleExpr}; use crate::rule::matcher::{Matcher, RuleExpr};
use crate::rule::state::State; use crate::rule::state::State;
use crate::utils::Side; use crate::utils::Side;
use crate::{Sym, VName}; use crate::{Sym, VName};
pub enum ScalMatcher { pub enum ScalMatcher {
P(Primitive), Atom(Atom),
Name(Sym), Name(Sym),
S(char, Box<AnyMatcher>), S(char, Box<AnyMatcher>),
Lambda(Box<AnyMatcher>, Box<AnyMatcher>), Lambda(Box<AnyMatcher>, Box<AnyMatcher>),
@@ -68,7 +68,7 @@ impl Matcher for AnyMatcher {
impl Display for ScalMatcher { impl Display for ScalMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::P(p) => write!(f, "{:?}", p), Self::Atom(a) => write!(f, "{a:?}"),
Self::Placeh(n) => write!(f, "${n}"), Self::Placeh(n) => write!(f, "${n}"),
Self::Name(n) => write!(f, "{}", n.extern_vec().join("::")), Self::Name(n) => write!(f, "{}", n.extern_vec().join("::")),
Self::S(c, body) => { Self::S(c, body) => {

View File

@@ -55,7 +55,7 @@ pub fn vec_match<'a>(
// Valid combinations of locations for the separators // Valid combinations of locations for the separators
let mut pos_pairs = lposv let mut pos_pairs = lposv
.into_iter() .into_iter()
.cartesian_product(rposv.into_iter()) .cartesian_product(rposv)
.filter(|((lpos, _), (rpos, _))| lpos + left_sep.len() <= *rpos) .filter(|((lpos, _), (rpos, _))| lpos + left_sep.len() <= *rpos)
.map(|((lpos, mut lstate), (rpos, rstate))| { .map(|((lpos, mut lstate), (rpos, rstate))| {
lstate.extend(rstate); lstate.extend(rstate);

View File

@@ -30,11 +30,11 @@ fn pad(mut rule: Rule<Sym>, i: &Interner) -> Rule<Sym> {
let prefix_v = if prefix_explicit { empty } else { prefix }; let prefix_v = if prefix_explicit { empty } else { prefix };
let suffix_v = if suffix_explicit { empty } else { suffix }; let suffix_v = if suffix_explicit { empty } else { suffix };
rule.pattern = (prefix_v.iter().cloned()) rule.pattern = (prefix_v.iter().cloned())
.chain(rule.pattern.into_iter()) .chain(rule.pattern)
.chain(suffix_v.iter().cloned()) .chain(suffix_v.iter().cloned())
.collect(); .collect();
rule.template = (prefix_v.iter().cloned()) rule.template = (prefix_v.iter().cloned())
.chain(rule.template.into_iter()) .chain(rule.template)
.chain(suffix_v.iter().cloned()) .chain(suffix_v.iter().cloned())
.collect(); .collect();
rule rule
@@ -60,7 +60,8 @@ fn check_rec_expr(
in_template: bool, in_template: bool,
) -> Result<(), RuleError> { ) -> Result<(), RuleError> {
match &expr.value { match &expr.value {
Clause::Name(_) | Clause::P(_) => Ok(()), Clause::Name(_) | Clause::Atom(_) => Ok(()),
Clause::ExternFn(_) => Err(RuleError::ExternFn),
Clause::Placeh(Placeholder { name, class }) => { Clause::Placeh(Placeholder { name, class }) => {
let typ = (*class).into(); let typ = (*class).into();
// in a template, the type must be known and identical // in a template, the type must be known and identical

View File

@@ -5,6 +5,8 @@ use hashbrown::HashSet;
use crate::ast::{self, search_all_slcs, PHClass, Placeholder, Rule}; use crate::ast::{self, search_all_slcs, PHClass, Placeholder, Rule};
use crate::error::{ErrorPosition, ProjectError}; use crate::error::{ErrorPosition, ProjectError};
#[allow(unused)] // for doc
use crate::foreign::ExternFn;
use crate::interner::Tok; use crate::interner::Tok;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::{Location, Sym}; use crate::{Location, Sym};
@@ -20,16 +22,20 @@ pub enum RuleError {
Multiple(Tok<String>), Multiple(Tok<String>),
/// Two vectorial placeholders are next to each other /// Two vectorial placeholders are next to each other
VecNeighbors(Tok<String>, Tok<String>), VecNeighbors(Tok<String>, Tok<String>),
/// Found an [ExternFn] in the pattern. This is a really unlikely mistake
/// caused only by rogue systems.
ExternFn,
} }
impl RuleError { impl RuleError {
/// Convert into a unified error trait object shared by all Orchid errors /// Convert into a unified error trait object shared by all Orchid errors
#[must_use] #[must_use]
pub fn to_project_error(self, rule: &Rule<Sym>) -> Rc<dyn ProjectError> { pub fn to_project_error(self, rule: &Rule<Sym>) -> Rc<dyn ProjectError> {
match self { match self {
RuleError::Missing(name) => Missing::new(rule, name).rc(), Self::Missing(name) => Missing::new(rule, name).rc(),
RuleError::Multiple(name) => Multiple::new(rule, name).rc(), Self::Multiple(name) => Multiple::new(rule, name).rc(),
RuleError::ArityMismatch(name) => ArityMismatch::new(rule, name).rc(), Self::ArityMismatch(name) => ArityMismatch::new(rule, name).rc(),
RuleError::VecNeighbors(n1, n2) => VecNeighbors::new(rule, n1, n2).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 { impl Display for RuleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::ExternFn => write!(f, "Found an ExternFn in the pattern"),
Self::Missing(key) => write!(f, "Key {key} not in match pattern"), Self::Missing(key) => write!(f, "Key {key} not in match pattern"),
Self::ArityMismatch(key) => { Self::ArityMismatch(key) => {
write!(f, "Key {key} used inconsistently with and without ellipsis") write!(f, "Key {key} used inconsistently with and without ellipsis")
@@ -44,10 +51,8 @@ impl Display for RuleError {
Self::Multiple(key) => { Self::Multiple(key) => {
write!(f, "Key {key} appears multiple times in match pattern") write!(f, "Key {key} appears multiple times in match pattern")
}, },
Self::VecNeighbors(left, right) => write!( Self::VecNeighbors(left, right) =>
f, write!(f, "vectorials {left} and {right} are next to each other"),
"Keys {left} and {right} are two vectorials right 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<Sym>);
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)
}
}

View File

@@ -7,7 +7,7 @@ use crate::ast::{Clause, Expr, PHClass, Placeholder};
use crate::interner::Tok; use crate::interner::Tok;
use crate::utils::unwrap_or; use crate::utils::unwrap_or;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug)]
pub enum StateEntry<'a> { pub enum StateEntry<'a> {
Vec(&'a [RuleExpr]), Vec(&'a [RuleExpr]),
Scalar(&'a RuleExpr), Scalar(&'a RuleExpr),
@@ -27,7 +27,8 @@ pub fn apply_exprv(template: &[RuleExpr], state: &State) -> Vec<RuleExpr> {
pub fn apply_expr(template: &RuleExpr, state: &State) -> Vec<RuleExpr> { pub fn apply_expr(template: &RuleExpr, state: &State) -> Vec<RuleExpr> {
let Expr { location, value } = template; let Expr { location, value } = template;
match value { match value {
Clause::P(_) | Clause::Name(_) => vec![template.clone()], Clause::Atom(_) | Clause::Name(_) | Clause::ExternFn(_) =>
vec![template.clone()],
Clause::S(c, body) => vec![Expr { Clause::S(c, body) => vec![Expr {
location: location.clone(), location: location.clone(),
value: Clause::S(*c, Rc::new(apply_exprv(body.as_slice(), state))), value: Clause::S(*c, Rc::new(apply_exprv(body.as_slice(), state))),

View File

@@ -35,7 +35,8 @@ pub fn clause<F: FnMut(Rc<Vec<RuleExpr>>) -> Option<Rc<Vec<RuleExpr>>>>(
pred: &mut F, pred: &mut F,
) -> Option<Clause<Sym>> { ) -> Option<Clause<Sym>> {
match c { match c {
Clause::P(_) | Clause::Placeh { .. } | Clause::Name { .. } => None, Clause::Atom(_) | Clause::Placeh { .. } | Clause::Name { .. } => None,
Clause::ExternFn(_) => None,
Clause::Lambda(arg, body) => Clause::Lambda(arg, body) =>
if let Some(arg) = exprv(arg.clone(), pred) { if let Some(arg) = exprv(arg.clone(), pred) {
Some(Clause::Lambda(arg, body.clone())) Some(Clause::Lambda(arg, body.clone()))

View File

@@ -5,7 +5,7 @@ use crate::interner::Tok;
/// Returns the name, priority and nonzero of the expression if it is /// Returns the name, priority and nonzero of the expression if it is
/// a vectorial placeholder /// a vectorial placeholder
#[must_use] #[must_use]
pub fn vec_attrs(expr: &RuleExpr) -> Option<(Tok<String>, u64, bool)> { pub fn vec_attrs(expr: &RuleExpr) -> Option<(Tok<String>, usize, bool)> {
match expr.value.clone() { match expr.value.clone() {
Clause::Placeh(Placeholder { Clause::Placeh(Placeholder {
class: PHClass::Vec { prio, nonzero }, class: PHClass::Vec { prio, nonzero },

View File

@@ -4,6 +4,7 @@ use std::collections::VecDeque;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::rc::Rc; use std::rc::Rc;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use hashbrown::HashMap; use hashbrown::HashMap;
@@ -17,23 +18,30 @@ use crate::interpreted::{Clause, ExprInst};
use crate::interpreter::HandlerTable; use crate::interpreter::HandlerTable;
use crate::pipeline::file_loader::embed_to_map; use crate::pipeline::file_loader::embed_to_map;
use crate::systems::codegen::call; use crate::systems::codegen::call;
use crate::systems::stl::Boolean;
use crate::utils::poller::{PollEvent, Poller}; use crate::utils::poller::{PollEvent, Poller};
use crate::utils::unwrap_or; use crate::utils::unwrap_or;
use crate::{ConstTree, Interner}; use crate::{ConstTree, Interner};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Timer { struct Timer {
recurring: Boolean, recurring: bool,
delay: NotNan<f64>, delay: NotNan<f64>,
} }
pub fn set_timer(recurring: Boolean, delay: NotNan<f64>) -> XfnResult<Clause> { pub fn set_timer(recurring: bool, delay: NotNan<f64>) -> XfnResult<Clause> {
Ok(init_cps(2, Timer { recurring, delay })) Ok(init_cps(2, Timer { recurring, delay }))
} }
#[derive(Clone)] #[derive(Clone)]
struct CancelTimer(Rc<dyn Fn()>); struct CancelTimer(Arc<Mutex<dyn Fn() + Send>>);
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 { impl Debug for CancelTimer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "opaque cancel operation") write!(f, "opaque cancel operation")
@@ -134,17 +142,16 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> {
let mut polly = polly.borrow_mut(); let mut polly = polly.borrow_mut();
let (timeout, action, cont) = t.unpack2(); let (timeout, action, cont) = t.unpack2();
let duration = Duration::from_secs_f64(*timeout.delay); let duration = Duration::from_secs_f64(*timeout.delay);
let cancel_timer = if timeout.recurring.0 { let cancel_timer = match timeout.recurring {
CancelTimer(Rc::new(polly.set_interval(duration, action))) true => CancelTimer::new(polly.set_interval(duration, action)),
} else { false => CancelTimer::new(polly.set_timeout(duration, action)),
CancelTimer(Rc::new(polly.set_timeout(duration, action)))
}; };
Ok(call(cont, [init_cps(1, cancel_timer).wrap()]).wrap()) Ok(call(cont, [init_cps(1, cancel_timer).wrap()]).wrap())
} }
}); });
handler_table.register(move |t: Box<CPSBox<CancelTimer>>| { handler_table.register(move |t: Box<CPSBox<CancelTimer>>| {
let (command, cont) = t.unpack1(); let (command, cont) = t.unpack1();
command.0.as_ref()(); command.cancel();
Ok(cont) Ok(cont)
}); });
handler_table.register({ handler_table.register({
@@ -165,7 +172,7 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> {
PollEvent::Event(ev) => { PollEvent::Event(ev) => {
let handler = (handlers.get_mut(&ev.as_ref().type_id())) let handler = (handlers.get_mut(&ev.as_ref().type_id()))
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!("Unhandled messgae type: {:?}", ev.type_id()) panic!("Unhandled messgae type: {:?}", (*ev).type_id())
}); });
let events = handler(ev); let events = handler(ev);
// we got new microtasks // we got new microtasks
@@ -181,6 +188,8 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> {
}); });
System { System {
name: vec!["system".to_string(), "asynch".to_string()], name: vec!["system".to_string(), "asynch".to_string()],
lexer_plugin: None,
line_parser: None,
constants: ConstTree::namespace( constants: ConstTree::namespace(
[i.i("system"), i.i("async")], [i.i("system"), i.i("async")],
ConstTree::tree([ ConstTree::tree([

View File

@@ -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<dyn ExternError>> {
(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<Self, Rc<dyn ExternError>> {
get_literal(exi).map(|(l, _)| l)
}
}
impl TryFromExprInst for OrcString {
fn from_exi(exi: ExprInst) -> Result<Self, Rc<dyn ExternError>> {
match get_literal(exi)? {
(Literal::Str(s), _) => Ok(s),
(_, location) => AssertionError::fail(location, "string"),
}
}
}
impl TryFromExprInst for u64 {
fn from_exi(exi: ExprInst) -> Result<Self, Rc<dyn ExternError>> {
match get_literal(exi)? {
(Literal::Uint(u), _) => Ok(u),
(_, location) => AssertionError::fail(location, "uint"),
}
}
}
impl TryFromExprInst for NotNan<f64> {
fn from_exi(exi: ExprInst) -> Result<Self, Rc<dyn ExternError>> {
match get_literal(exi)? {
(Literal::Num(n), _) => Ok(n),
(_, location) => AssertionError::fail(location, "float"),
}
}
}
impl TryFromExprInst for Atom {
fn from_exi(exi: ExprInst) -> Result<Self, Rc<dyn ExternError>> {
let Expr { clause, location } = exi.expr_val();
match clause {
Clause::P(Primitive::Atom(a)) => Ok(a),
_ => AssertionError::fail(location, "atom"),
}
}
}

View File

@@ -39,11 +39,11 @@ pub fn tuple(data: impl IntoIterator<Item = ExprInst>) -> Clause {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::systems::codegen::tuple; use crate::{systems::codegen::tuple, foreign::Atomic};
#[test] #[test]
fn tuple_printer() { fn tuple_printer() {
println!("Binary tuple: {}", tuple([0.into(), 1.into()])) println!("Binary tuple: {}", tuple([0usize.atom_exi(), 1usize.atom_exi()]))
} }
} }

View File

@@ -8,24 +8,27 @@ use itertools::Itertools;
use super::osstring::os_string_lib; use super::osstring::os_string_lib;
use crate::ddispatch::Responder; use crate::ddispatch::Responder;
use crate::error::RuntimeError;
use crate::facade::{IntoSystem, System}; use crate::facade::{IntoSystem, System};
use crate::foreign::cps_box::{init_cps, CPSBox}; use crate::foreign::cps_box::{init_cps, CPSBox};
use crate::foreign::{ 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::interpreted::{Clause, ExprInst};
use crate::interpreter::HandlerTable; use crate::interpreter::HandlerTable;
use crate::systems::codegen::{call, list, opt, tuple}; use crate::systems::codegen::{call, list, opt, tuple};
use crate::systems::io::{wrap_io_error, Source}; use crate::systems::io::{wrap_io_error, Source};
use crate::systems::scheduler::{SeqScheduler, SharedHandle}; use crate::systems::scheduler::{SeqScheduler, SharedHandle};
use crate::systems::stl::Boolean;
use crate::systems::RuntimeError;
use crate::utils::unwrap_or; use crate::utils::unwrap_or;
use crate::ConstTree; use crate::ConstTree;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CurrentDir; pub struct CurrentDir;
impl Responder for 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 { impl Atomic for CurrentDir {
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self } fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
fn as_any_ref(&self) -> &dyn std::any::Any { self } fn as_any_ref(&self) -> &dyn std::any::Any { self }
@@ -95,7 +98,7 @@ fn read_dir(sched: &SeqScheduler, cmd: CPSBox<ReadDirCmd>) -> ExprInst {
Err(e) => vec![call(fail, [wrap_io_error(e)]).wrap()], Err(e) => vec![call(fail, [wrap_io_error(e)]).wrap()],
Ok(os_namev) => { Ok(os_namev) => {
let converted = (os_namev.into_iter()) 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::<Result<Vec<_>, Clause>>(); .collect::<Result<Vec<_>, Clause>>();
match converted { match converted {
Err(e) => vec![call(fail, [e.wrap()]).wrap()], Err(e) => vec![call(fail, [e.wrap()]).wrap()],
@@ -115,7 +118,7 @@ pub fn write_file(sched: &SeqScheduler, cmd: CPSBox<WriteFile>) -> ExprInst {
|file, _| match file { |file, _| match file {
Err(e) => vec![call(fail, [wrap_io_error(e)]).wrap()], Err(e) => vec![call(fail, [wrap_io_error(e)]).wrap()],
Ok(f) => { Ok(f) => {
let handle = SharedHandle::wrap(Box::new(f) as Box<dyn Write>); let handle = SharedHandle::wrap(Box::new(f) as Box<dyn Write + Send>);
vec![call(succ, [handle.atom_exi()]).wrap()] vec![call(succ, [handle.atom_exi()]).wrap()]
}, },
}, },
@@ -180,6 +183,8 @@ impl IntoSystem<'static> for DirectFS {
name: ["system", "directfs"].into_iter().map_into().collect(), name: ["system", "directfs"].into_iter().map_into().collect(),
code: HashMap::new(), code: HashMap::new(),
prelude: Vec::new(), prelude: Vec::new(),
lexer_plugin: None,
line_parser: None,
constants: ConstTree::namespace( constants: ConstTree::namespace(
[i.i("system"), i.i("directfs")], [i.i("system"), i.i("directfs")],
ConstTree::tree([ ConstTree::tree([

View File

@@ -1,13 +1,13 @@
use super::flow::IOCmdHandlePack; use super::flow::IOCmdHandlePack;
use super::instances::{BRead, ReadCmd, SRead, Sink, Source, WriteCmd}; use super::instances::{BRead, ReadCmd, SRead, Sink, Source, WriteCmd};
use crate::error::RuntimeError;
use crate::foreign::cps_box::init_cps; 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::interpreted::Clause;
use crate::representations::OrcString; use crate::representations::OrcString;
use crate::systems::scheduler::SharedHandle; use crate::systems::scheduler::SharedHandle;
use crate::systems::stl::Binary; use crate::systems::stl::Binary;
use crate::systems::RuntimeError; use crate::{ConstTree, Interner, ast};
use crate::{ast, ConstTree, Interner, Primitive};
type WriteHandle = SharedHandle<Sink>; type WriteHandle = SharedHandle<Sink>;
type ReadHandle = SharedHandle<Source>; type ReadHandle = SharedHandle<Source>;
@@ -21,11 +21,11 @@ pub fn read_line(handle: ReadHandle) -> XfnResult<Clause> {
pub fn read_bin(handle: ReadHandle) -> XfnResult<Clause> { pub fn read_bin(handle: ReadHandle) -> XfnResult<Clause> {
Ok(init_cps(3, IOCmdHandlePack { handle, cmd: ReadCmd::RBytes(BRead::All) })) Ok(init_cps(3, IOCmdHandlePack { handle, cmd: ReadCmd::RBytes(BRead::All) }))
} }
pub fn read_bytes(handle: ReadHandle, n: u64) -> XfnResult<Clause> { pub fn read_bytes(handle: ReadHandle, n: usize) -> XfnResult<Clause> {
let cmd = ReadCmd::RBytes(BRead::N(n.try_into().unwrap())); let cmd = ReadCmd::RBytes(BRead::N(n));
Ok(init_cps(3, IOCmdHandlePack { cmd, handle })) Ok(init_cps(3, IOCmdHandlePack { cmd, handle }))
} }
pub fn read_until(handle: ReadHandle, pattern: u64) -> XfnResult<Clause> { pub fn read_until(handle: ReadHandle, pattern: usize) -> XfnResult<Clause> {
let delim = pattern.try_into().map_err(|_| { let delim = pattern.try_into().map_err(|_| {
let msg = "greater than 255".to_string(); let msg = "greater than 255".to_string();
RuntimeError::ext(msg, "converting number to byte") RuntimeError::ext(msg, "converting number to byte")
@@ -63,8 +63,7 @@ pub fn io_bindings<'a>(
std_streams std_streams
.into_iter() .into_iter()
.map(|(n, at)| { .map(|(n, at)| {
let expr = ast::Clause::P(Primitive::Atom(Atom(at))).into_expr(); (i.i(n), ConstTree::clause(ast::Clause::Atom(Atom(at))))
(i.i(n), ConstTree::Const(expr))
}) })
.collect(), .collect(),
), ),

View File

@@ -7,7 +7,7 @@ use crate::interpreted::ExprInst;
use crate::systems::codegen::call; use crate::systems::codegen::call;
use crate::systems::scheduler::{Canceller, SharedHandle}; use crate::systems::scheduler::{Canceller, SharedHandle};
use crate::systems::stl::Binary; use crate::systems::stl::Binary;
use crate::Literal; use crate::OrcString;
/// Any type that we can read controlled amounts of data from /// Any type that we can read controlled amounts of data from
pub type Source = BufReader<Box<dyn Read + Send>>; pub type Source = BufReader<Box<dyn Read + Send>>;
@@ -63,10 +63,14 @@ impl IOCmd for ReadCmd {
Self::RStr(sread) => { Self::RStr(sread) => {
let mut buf = String::new(); let mut buf = String::new();
let sresult = match &sread { let sresult = match &sread {
SRead::All => stream.read_to_string(&mut buf), SRead::All => stream.read_to_string(&mut buf).map(|_| ()),
SRead::Line => stream.read_line(&mut buf), 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()] vec![call(succ, [arg]).wrap()]
}, },
ReadResult::RStr(_, Ok(text)) => { 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 /// 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) /// Writing command (string or binary)
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]

View File

@@ -113,6 +113,8 @@ impl<'a, ST: IntoIterator<Item = (&'a str, Stream)>> IntoSystem<'static>
name: None, name: None,
}]), }]),
}], }],
lexer_plugin: None,
line_parser: None,
} }
} }
} }

View File

@@ -1,13 +1,7 @@
//! Constants exposed to usercode by the interpreter //! Constants exposed to usercode by the interpreter
mod assertion_error;
pub mod asynch; pub mod asynch;
pub mod cast_exprinst;
pub mod codegen; pub mod codegen;
pub mod directfs; pub mod directfs;
pub mod io; pub mod io;
mod runtime_error;
pub mod scheduler; pub mod scheduler;
pub mod stl; pub mod stl;
pub use assertion_error::AssertionError;
pub use runtime_error::RuntimeError;

View File

@@ -7,14 +7,17 @@ use crate::interpreted::ExprInst;
pub type SyncResult<T> = (T, Box<dyn Any + Send>); pub type SyncResult<T> = (T, Box<dyn Any + Send>);
pub type SyncOperation<T> = pub type SyncOperation<T> =
Box<dyn FnOnce(T, Canceller) -> SyncResult<T> + Send>; Box<dyn FnOnce(T, Canceller) -> SyncResult<T> + Send>;
pub type SyncOpResultHandler<T> = pub type SyncOpResultHandler<T> = Box<
Box<dyn FnOnce(T, Box<dyn Any + Send>, Canceller) -> (T, Vec<ExprInst>)>; dyn FnOnce(T, Box<dyn Any + Send>, Canceller) -> (T, Vec<ExprInst>)
+ Send,
>;
struct SyncQueueItem<T> { struct SyncQueueItem<T> {
cancelled: Canceller, cancelled: Canceller,
operation: SyncOperation<T>, operation: SyncOperation<T>,
handler: SyncOpResultHandler<T>, handler: SyncOpResultHandler<T>,
early_cancel: Box<dyn FnOnce(T) -> (T, Vec<ExprInst>)>, early_cancel: Box<dyn FnOnce(T) -> (T, Vec<ExprInst>) + Send>,
} }
pub enum NextItemReportKind<T> { pub enum NextItemReportKind<T> {
@@ -36,11 +39,14 @@ pub struct NextItemReport<T> {
pub struct BusyState<T> { pub struct BusyState<T> {
handler: SyncOpResultHandler<T>, handler: SyncOpResultHandler<T>,
queue: VecDeque<SyncQueueItem<T>>, queue: VecDeque<SyncQueueItem<T>>,
seal: Option<Box<dyn FnOnce(T) -> Vec<ExprInst>>>, seal: Option<Box<dyn FnOnce(T) -> Vec<ExprInst> + Send>>,
} }
impl<T> BusyState<T> { impl<T> BusyState<T> {
pub fn new<U: 'static + Send>( pub fn new<U: 'static + Send>(
handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>) + 'static, handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>)
+ Send
+ 'static,
) -> Self { ) -> Self {
BusyState { BusyState {
handler: Box::new(|t, payload, cancel| { handler: Box::new(|t, payload, cancel| {
@@ -59,8 +65,8 @@ impl<T> BusyState<T> {
pub fn enqueue<U: 'static + Send>( pub fn enqueue<U: 'static + Send>(
&mut self, &mut self,
operation: impl FnOnce(T, Canceller) -> (T, U) + Send + 'static, operation: impl FnOnce(T, Canceller) -> (T, U) + Send + 'static,
handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>) + 'static, handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>) + Send + 'static,
early_cancel: impl FnOnce(T) -> (T, Vec<ExprInst>) + 'static, early_cancel: impl FnOnce(T) -> (T, Vec<ExprInst>) + Send + 'static,
) -> Option<Canceller> { ) -> Option<Canceller> {
if self.seal.is_some() { if self.seal.is_some() {
return None; return None;
@@ -81,7 +87,7 @@ impl<T> BusyState<T> {
Some(cancelled) Some(cancelled)
} }
pub fn seal(&mut self, recipient: impl FnOnce(T) -> Vec<ExprInst> + 'static) { pub fn seal(&mut self, recipient: impl FnOnce(T) -> Vec<ExprInst> + Send + 'static) {
assert!(self.seal.is_none(), "Already sealed"); assert!(self.seal.is_none(), "Already sealed");
self.seal = Some(Box::new(recipient)) self.seal = Some(Box::new(recipient))
} }
@@ -101,7 +107,7 @@ impl<T> BusyState<T> {
if candidate.cancelled.is_cancelled() { if candidate.cancelled.is_cancelled() {
let ret = (candidate.early_cancel)(instance); let ret = (candidate.early_cancel)(instance);
instance = ret.0; instance = ret.0;
events.extend(ret.1.into_iter()); events.extend(ret.1);
} else { } else {
break candidate; break candidate;
} }

View File

@@ -2,6 +2,7 @@ use std::any::{type_name, Any};
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Arc, Mutex};
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use itertools::Itertools;
@@ -9,14 +10,13 @@ use trait_set::trait_set;
use super::busy::{BusyState, NextItemReportKind}; use super::busy::{BusyState, NextItemReportKind};
use super::Canceller; use super::Canceller;
use crate::error::AssertionError;
use crate::facade::{IntoSystem, System}; use crate::facade::{IntoSystem, System};
use crate::foreign::cps_box::{init_cps, CPSBox}; use crate::foreign::cps_box::{init_cps, CPSBox};
use crate::foreign::{xfn_1ary, InertAtomic, XfnResult}; use crate::foreign::{xfn_1ary, InertAtomic, XfnResult};
use crate::interpreted::{Clause, ExprInst}; use crate::interpreted::{Clause, ExprInst};
use crate::interpreter::HandlerTable; use crate::interpreter::HandlerTable;
use crate::systems::asynch::{AsynchSystem, MessagePort}; use crate::systems::asynch::{AsynchSystem, MessagePort};
use crate::systems::stl::Boolean;
use crate::systems::AssertionError;
use crate::utils::ddispatch::Request; use crate::utils::ddispatch::Request;
use crate::utils::thread_pool::ThreadPool; use crate::utils::thread_pool::ThreadPool;
use crate::utils::{take_with_output, unwrap_or, IdMap}; 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 /// 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. /// [SeqScheduler] to execute mutating operations one by one in worker threads.
pub struct SharedHandle<T>(Rc<RefCell<SharedResource<T>>>); pub struct SharedHandle<T>(Arc<Mutex<SharedResource<T>>>);
impl<T> SharedHandle<T> { impl<T> SharedHandle<T> {
/// Wrap a value to be accessible to a [SeqScheduler]. /// Wrap a value to be accessible to a [SeqScheduler].
pub fn wrap(t: T) -> Self { 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 /// Check the state of the handle
pub fn state(&self) -> SharedState { 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(b) if b.is_sealed() => SharedState::Sealed,
SharedResource::Busy(_) => SharedState::Busy, SharedResource::Busy(_) => SharedState::Busy,
SharedResource::Free(_) => SharedState::Free, SharedResource::Free(_) => SharedState::Free,
@@ -70,7 +70,7 @@ impl<T> SharedHandle<T> {
/// sense as eg. an optimization. You can return the value after processing /// sense as eg. an optimization. You can return the value after processing
/// via [SyncHandle::untake]. /// via [SyncHandle::untake].
pub fn take(&self) -> Option<T> { pub fn take(&self) -> Option<T> {
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)), SharedResource::Free(t) => (SharedResource::Taken, Some(t)),
_ => (state, None), _ => (state, None),
}) })
@@ -80,10 +80,13 @@ impl<T> SharedHandle<T> {
/// is to return values synchronously after they have been removed with /// is to return values synchronously after they have been removed with
/// [SyncHandle::untake]. /// [SyncHandle::untake].
pub fn untake(&self, value: T) -> Result<(), T> { pub fn untake(&self, value: T) -> Result<(), T> {
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::Taken => (SharedResource::Free(value), Ok(())), SharedResource::Taken => (SharedResource::Free(value), Ok(())),
_ => (state, Err(value)), _ => (state, Err(value)),
}) },
)
} }
} }
impl<T> Clone for SharedHandle<T> { impl<T> Clone for SharedHandle<T> {
@@ -97,12 +100,12 @@ impl<T> Debug for SharedHandle<T> {
.finish() .finish()
} }
} }
impl<T: 'static> InertAtomic for SharedHandle<T> { impl<T: Send + 'static> InertAtomic for SharedHandle<T> {
fn type_str() -> &'static str { "a SharedHandle" } fn type_str() -> &'static str { "a SharedHandle" }
fn respond(&self, mut request: Request) { fn respond(&self, mut request: Request) {
request.serve_with(|| { request.serve_with(|| {
let this = self.clone(); let this = self.clone();
TakeCmd(Rc::new(move |sch| { TakeCmd(Arc::new(move |sch| {
let _ = sch.seal(this.clone(), |_| Vec::new()); let _ = sch.seal(this.clone(), |_| Vec::new());
})) }))
}) })
@@ -110,7 +113,7 @@ impl<T: 'static> InertAtomic for SharedHandle<T> {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct TakeCmd(pub Rc<dyn Fn(SeqScheduler)>); pub struct TakeCmd(pub Arc<dyn Fn(SeqScheduler) + Send + Sync>);
impl Debug for TakeCmd { impl Debug for TakeCmd {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "A command to drop a shared resource") write!(f, "A command to drop a shared resource")
@@ -134,8 +137,8 @@ pub fn take_and_drop(x: ExprInst) -> XfnResult<Clause> {
} }
} }
pub fn is_taken_error(x: ExprInst) -> XfnResult<Boolean> { pub fn is_taken_error(x: ExprInst) -> XfnResult<bool> {
Ok(Boolean(x.downcast::<SealedOrTaken>().is_ok())) Ok(x.downcast::<SealedOrTaken>().is_ok())
} }
trait_set! { trait_set! {
@@ -195,11 +198,11 @@ impl SeqScheduler {
pub fn schedule<T: Send + 'static, U: Send + 'static>( pub fn schedule<T: Send + 'static, U: Send + 'static>(
&self, &self,
handle: SharedHandle<T>, handle: SharedHandle<T>,
operation: impl FnOnce(T, Canceller) -> (T, U) + Send + 'static, operation: impl FnOnce(T, Canceller) -> (T, U) + Sync + Send + 'static,
handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>) + 'static, handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>) + Sync + Send + 'static,
early_cancel: impl FnOnce(T) -> (T, Vec<ExprInst>) + 'static, early_cancel: impl FnOnce(T) -> (T, Vec<ExprInst>) + Sync + Send + 'static,
) -> Result<Canceller, SealedOrTaken> { ) -> Result<Canceller, SealedOrTaken> {
take_with_output(&mut *handle.0.as_ref().borrow_mut(), { take_with_output(&mut *handle.0.lock().unwrap(), {
let handle = handle.clone(); let handle = handle.clone();
|state| { |state| {
match state { match state {
@@ -246,10 +249,10 @@ impl SeqScheduler {
pub fn seal<T>( pub fn seal<T>(
&self, &self,
handle: SharedHandle<T>, handle: SharedHandle<T>,
seal: impl FnOnce(T) -> Vec<ExprInst> + 'static, seal: impl FnOnce(T) -> Vec<ExprInst> + Sync + Send + 'static,
) -> Result<Vec<ExprInst>, SealedOrTaken> { ) -> Result<Vec<ExprInst>, SealedOrTaken> {
take_with_output( take_with_output(
&mut *handle.0.as_ref().borrow_mut(), &mut *handle.0.lock().unwrap(),
|state| match state { |state| match state {
SharedResource::Busy(mut b) if !b.is_sealed() => { SharedResource::Busy(mut b) if !b.is_sealed() => {
b.seal(seal); b.seal(seal);
@@ -281,7 +284,7 @@ impl SeqScheduler {
let (t, u): (T, U) = let (t, u): (T, U) =
*data.downcast().expect("This is associated by ID"); *data.downcast().expect("This is associated by ID");
let handle2 = handle.clone(); 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; let busy = unwrap_or! { state => SharedResource::Busy;
panic!("Handle with outstanding invocation must be busy") panic!("Handle with outstanding invocation must be busy")
}; };
@@ -329,6 +332,8 @@ impl IntoSystem<'static> for SeqScheduler {
prelude: Vec::new(), prelude: Vec::new(),
code: HashMap::new(), code: HashMap::new(),
handlers, handlers,
lexer_plugin: None,
line_parser: None,
constants: ConstTree::namespace( constants: ConstTree::namespace(
[i.i("system"), i.i("scheduler")], [i.i("system"), i.i("scheduler")],
ConstTree::tree([ ConstTree::tree([

View File

@@ -3,15 +3,16 @@ use std::sync::Arc;
use itertools::Itertools; use itertools::Itertools;
use super::Boolean; use crate::error::RuntimeError;
use crate::foreign::{ use crate::foreign::{
xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, Atomic, InertAtomic, XfnResult, xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, Atomic, InertAtomic, XfnResult,
}; };
use crate::interpreted::Clause; use crate::interpreted::Clause;
use crate::systems::codegen::{opt, tuple}; use crate::systems::codegen::{opt, tuple};
use crate::systems::RuntimeError;
use crate::utils::{iter_find, unwrap_or}; 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 /// A block of binary data
#[derive(Clone, Hash, PartialEq, Eq)] #[derive(Clone, Hash, PartialEq, Eq)]
@@ -43,93 +44,86 @@ pub fn concatenate(a: Binary, b: Binary) -> XfnResult<Binary> {
} }
/// Extract a subsection of the binary data /// Extract a subsection of the binary data
pub fn slice(s: Binary, i: u64, len: u64) -> XfnResult<Binary> { pub fn slice(s: Binary, i: usize, len: usize) -> XfnResult<Binary> {
if i + len < s.0.len() as u64 { if i + len < s.0.len() {
RuntimeError::fail( RuntimeError::fail(
"Byte index out of bounds".to_string(), "Byte index out of bounds".to_string(),
"indexing binary", "indexing binary",
)? )?
} }
let data = s.0[i as usize..i as usize + len as usize].to_vec(); Ok(Binary(Arc::new(s.0[i..i + len].to_vec())))
Ok(Binary(Arc::new(data)))
} }
/// Return the index where the first argument first contains the second, if any /// Return the index where the first argument first contains the second, if any
pub fn find(haystack: Binary, needle: Binary) -> XfnResult<Clause> { pub fn find(haystack: Binary, needle: Binary) -> XfnResult<Clause> {
let found = iter_find(haystack.0.iter(), needle.0.iter()); 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 /// Split binary data block into two smaller blocks
pub fn split(bin: Binary, i: u64) -> XfnResult<Clause> { pub fn split(bin: Binary, i: usize) -> XfnResult<Clause> {
if bin.0.len() < i as usize { if bin.0.len() < i {
RuntimeError::fail( RuntimeError::fail(
"Byte index out of bounds".to_string(), "Byte index out of bounds".to_string(),
"splitting binary", "splitting binary",
)? )?
} }
let (asl, bsl) = bin.0.split_at(i as usize); let (asl, bsl) = bin.0.split_at(i);
Ok(tuple([ Ok(tuple([asl, bsl].map(|s| Binary(Arc::new(s.to_vec())).atom_exi())))
Binary(Arc::new(asl.to_vec())).atom_cls().into(),
Binary(Arc::new(bsl.to_vec())).atom_cls().into(),
]))
} }
/// Read a number from a binary blob /// Read a number from a binary blob
pub fn get_num( pub fn get_num(
buf: Binary, buf: Binary,
loc: u64, loc: usize,
size: u64, size: usize,
is_le: Boolean, is_le: bool,
) -> XfnResult<Literal> { ) -> XfnResult<usize> {
if buf.0.len() < (loc + size) as usize { if buf.0.len() < (loc + size) {
RuntimeError::fail( RuntimeError::fail(
"section out of range".to_string(), "section out of range".to_string(),
"reading number from binary data", "reading number from binary data",
)? )?
} }
if 8 < size { if INT_BYTES < size {
RuntimeError::fail( RuntimeError::fail(
"more than 8 bytes provided".to_string(), "more than std::bin::int_bytes bytes provided".to_string(),
"reading number from binary data", "reading number from binary data",
)? )?
} }
let mut data = [0u8; 8]; let mut data = [0u8; INT_BYTES];
let section = &buf.0[loc as usize..(loc + size) as usize]; let section = &buf.0[loc..(loc + size)];
let num = if is_le.0 { let num = if is_le {
data[0..size as usize].copy_from_slice(section); data[0..size].copy_from_slice(section);
u64::from_le_bytes(data) usize::from_le_bytes(data)
} else { } else {
data[8 - size as usize..].copy_from_slice(section); data[INT_BYTES - size..].copy_from_slice(section);
u64::from_be_bytes(data) usize::from_be_bytes(data)
}; };
Ok(Literal::Uint(num)) Ok(num)
} }
/// Convert a number into a blob /// Convert a number into a blob
pub fn from_num(size: u64, is_le: Boolean, data: u64) -> XfnResult<Binary> { pub fn from_num(size: usize, is_le: bool, data: usize) -> XfnResult<Binary> {
if size > 8 { if INT_BYTES < size {
RuntimeError::fail( RuntimeError::fail(
"more than 8 bytes requested".to_string(), "more than std::bin::int_bytes bytes requested".to_string(),
"converting number to binary", "converting number to binary",
)? )?
} }
let bytes = if is_le.0 { let bytes = match is_le {
data.to_le_bytes()[0..size as usize].to_vec() true => data.to_le_bytes()[0..size].to_vec(),
} else { false => data.to_be_bytes()[8 - size..].to_vec(),
data.to_be_bytes()[8 - size as usize..].to_vec()
}; };
Ok(Binary(Arc::new(bytes))) Ok(Binary(Arc::new(bytes)))
} }
/// Detect the number of bytes in the blob /// Detect the number of bytes in the blob
pub fn size(b: Binary) -> XfnResult<Literal> { pub fn size(b: Binary) -> XfnResult<usize> { Ok(b.0.len()) }
Ok(Literal::Uint(b.0.len() as u64))
}
pub fn bin(i: &Interner) -> ConstTree { pub fn bin(i: &Interner) -> ConstTree {
ConstTree::tree([( ConstTree::tree([(
i.i("bin"), i.i("binary"),
ConstTree::tree([ ConstTree::tree([
(i.i("concat"), ConstTree::xfn(xfn_2ary(concatenate))), (i.i("concat"), ConstTree::xfn(xfn_2ary(concatenate))),
(i.i("slice"), ConstTree::xfn(xfn_3ary(slice))), (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("get_num"), ConstTree::xfn(xfn_4ary(get_num))),
(i.i("from_num"), ConstTree::xfn(xfn_3ary(from_num))), (i.i("from_num"), ConstTree::xfn(xfn_3ary(from_num))),
(i.i("size"), ConstTree::xfn(xfn_1ary(size))), (i.i("size"), ConstTree::xfn(xfn_1ary(size))),
(i.i("int_bytes"), ConstTree::atom(INT_BYTES)),
]), ]),
)]) )])
} }

View File

@@ -1,8 +1,10 @@
export operators[ != == ] export ::(!=, ==)
export const not := \bool. if bool then false else true export const not := \bool. if bool then false else true
macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b)) macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b))
macro ...$a == ...$b =0x3p36=> (equals (...$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=> ( export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> (
ifthenelse (...$cond) (...$true) (...$false) ifthenelse (...$cond) (...$true) (...$false)
) )

View File

@@ -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::interner::Interner;
use crate::representations::interpreted::Clause; use crate::representations::interpreted::Clause;
use crate::systems::AssertionError; use crate::error::AssertionError;
use crate::{ConstTree, Literal, Location}; use crate::{ConstTree, Location, OrcString};
/// Booleans exposed to Orchid use super::Numeric;
#[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<bool> for Boolean {
fn from(value: bool) -> Self { Self(value) }
}
/// Takes a boolean and two branches, runs the first if the bool is true, the /// Takes a boolean and two branches, runs the first if the bool is true, the
/// second if it's false. /// second if it's false.
// Even though it's a ternary function, IfThenElse is implemented as an unary // 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. // foreign function, as the rest of the logic can be defined in Orchid.
pub fn if_then_else(b: Boolean) -> XfnResult<Clause> { pub fn if_then_else(b: bool) -> XfnResult<Clause> {
Ok(match b.0 { Ok(match b {
true => Clause::pick(Clause::constfn(Clause::LambdaArg)), true => Clause::pick(Clause::constfn(Clause::LambdaArg)),
false => Clause::constfn(Clause::pick(Clause::LambdaArg)), false => Clause::constfn(Clause::pick(Clause::LambdaArg)),
}) })
@@ -29,16 +20,25 @@ pub fn if_then_else(b: Boolean) -> XfnResult<Clause> {
/// Compares the inner values if /// Compares the inner values if
/// ///
/// - both are string, /// - both are string,
/// - both are bool,
/// - both are either uint or num /// - both are either uint or num
pub fn equals(a: Literal, b: Literal) -> XfnResult<Boolean> { pub fn equals(a: Atom, b: Atom) -> XfnResult<bool> {
Ok(Boolean::from(match (a, b) { let (a, b) = match (a.try_downcast::<OrcString>(), b.try_downcast::<OrcString>()) {
(Literal::Str(s1), Literal::Str(s2)) => s1 == s2, (Ok(a), Ok(b)) => return Ok(a == b),
(Literal::Num(n1), Literal::Num(n2)) => n1 == n2, (Err(a), Err(b)) => (a, b),
(Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2, _ => return Ok(false),
(Literal::Num(n1), Literal::Uint(u1)) => *n1 == (u1 as f64), };
(Literal::Uint(u1), Literal::Num(n1)) => *n1 == (u1 as f64), match (a.request::<Numeric>(), b.request::<Numeric>()) {
(..) => AssertionError::fail(Location::Unknown, "the expected type")?, (Some(a), Some(b)) => return Ok(a.as_float() == b.as_float()),
})) (None, None) => (),
_ => return Ok(false),
};
match (a.try_downcast::<bool>(), b.try_downcast::<bool>()) {
(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 { pub fn bool(i: &Interner) -> ConstTree {
@@ -47,8 +47,8 @@ pub fn bool(i: &Interner) -> ConstTree {
ConstTree::tree([ ConstTree::tree([
(i.i("ifthenelse"), ConstTree::xfn(xfn_1ary(if_then_else))), (i.i("ifthenelse"), ConstTree::xfn(xfn_1ary(if_then_else))),
(i.i("equals"), ConstTree::xfn(xfn_2ary(equals))), (i.i("equals"), ConstTree::xfn(xfn_2ary(equals))),
(i.i("true"), ConstTree::atom(Boolean(true))), (i.i("true"), ConstTree::atom(true)),
(i.i("false"), ConstTree::atom(Boolean(false))), (i.i("false"), ConstTree::atom(false)),
]), ]),
)]) )])
} }

View File

@@ -1,48 +1,45 @@
use chumsky::Parser;
use ordered_float::NotNan; use ordered_float::NotNan;
use super::ArithmeticError; use super::Numeric;
use crate::foreign::{xfn_1ary, ExternError, XfnResult}; use crate::error::AssertionError;
use crate::foreign::{xfn_1ary, Atom, XfnResult};
use crate::interner::Interner; use crate::interner::Interner;
use crate::parse::{float_parser, int_parser}; use crate::parse::parse_num;
use crate::systems::AssertionError; use crate::{ConstTree, Location, OrcString};
use crate::{ConstTree, Literal, Location};
fn to_numeric(a: Atom) -> XfnResult<Numeric> {
if let Some(n) = a.request::<Numeric>() {
return Ok(n);
}
if let Some(s) = a.request::<OrcString>() {
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. /// parse a number. Accepts the same syntax Orchid does.
pub fn to_float(l: Literal) -> XfnResult<Literal> { pub fn to_float(a: Atom) -> XfnResult<NotNan<f64>> {
match l { to_numeric(a).map(|n| n.as_float())
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()),
}
} }
/// Parse an unsigned integer. Accepts the same formats Orchid does. If the /// Parse an unsigned integer. Accepts the same formats Orchid does. If the
/// input is a number, floors it. /// input is a number, floors it.
pub fn to_uint(l: Literal) -> XfnResult<Literal> { pub fn to_uint(a: Atom) -> XfnResult<usize> {
match l { to_numeric(a).map(|n| match n {
Literal::Str(s) => int_parser() Numeric::Float(f) => f.floor() as usize,
.parse(s.as_str()) Numeric::Uint(i) => i,
.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),
}
} }
/// Convert a literal to a string using Rust's conversions for floats, chars and /// Convert a literal to a string using Rust's conversions for floats, chars and
/// uints respectively /// uints respectively
pub fn to_string(l: Literal) -> XfnResult<Literal> { pub fn to_string(a: Atom) -> XfnResult<OrcString> {
Ok(match l { a.try_downcast::<OrcString>()
Literal::Uint(i) => Literal::Str(i.to_string().into()), .or_else(|e| e.try_downcast::<usize>().map(|i| i.to_string().into()))
Literal::Num(n) => Literal::Str(n.to_string().into()), .or_else(|e| e.try_downcast::<NotNan<f64>>().map(|i| i.to_string().into()))
s @ Literal::Str(_) => s, .or_else(|e| e.try_downcast::<bool>().map(|i| i.to_string().into()))
}) .map_err(|_| AssertionError::ext(Location::Unknown, "string or number"))
} }
pub fn conv(i: &Interner) -> ConstTree { pub fn conv(i: &Interner) -> ConstTree {

View File

@@ -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))),
]),
)
}

Some files were not shown because too many files have changed in this diff Show More