forked from Orchid/orchid
September-october commit
- manual parser - stl refinements - all language constructs are now Send
This commit is contained in:
173
Cargo.lock
generated
173
Cargo.lock
generated
@@ -2,17 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.3"
|
||||
@@ -137,16 +126,6 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d"
|
||||
dependencies = [
|
||||
"hashbrown 0.12.3",
|
||||
"stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.4"
|
||||
@@ -204,12 +183,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.7"
|
||||
@@ -238,19 +211,6 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -261,16 +221,6 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "duplicate"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.11"
|
||||
@@ -320,17 +270,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.10"
|
||||
@@ -344,22 +283,13 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
@@ -451,36 +381,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "orchidlang"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"chumsky",
|
||||
"clap",
|
||||
"derive_more",
|
||||
"duplicate",
|
||||
"dyn-clone",
|
||||
"hashbrown 0.14.0",
|
||||
"hashbrown",
|
||||
"itertools",
|
||||
"ordered-float",
|
||||
"paste",
|
||||
"polling",
|
||||
"rust-embed",
|
||||
"take_mut",
|
||||
"thiserror",
|
||||
"trait-set",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "3.7.0"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213"
|
||||
checksum = "e3a540f3e3b3d7929c884e46d093d344e4e5bdeed54d08bf007df50c93cc85d5"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
@@ -511,30 +437,6 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
@@ -544,15 +446,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
@@ -614,15 +507,6 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.19"
|
||||
@@ -659,12 +543,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
@@ -682,19 +560,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@@ -729,26 +594,6 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
@@ -817,12 +662,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
||||
@@ -22,18 +22,14 @@ doc = false
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0"
|
||||
chumsky = "0.9"
|
||||
hashbrown = "0.14"
|
||||
ordered-float = "3.7"
|
||||
ordered-float = "4.1"
|
||||
itertools = "0.11"
|
||||
dyn-clone = "1.0"
|
||||
clap = { version = "4.3", features = ["derive"] }
|
||||
trait-set = "0.3"
|
||||
paste = "1.0"
|
||||
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
||||
duplicate = "1.0.0"
|
||||
take_mut = "0.2.2"
|
||||
unicode-segmentation = "1.10.1"
|
||||
polling = "3.0.0"
|
||||
derive_more = "0.99.17"
|
||||
|
||||
@@ -7,9 +7,7 @@ An experimental lazy, pure functional programming language designed to be embedd
|
||||
|
||||
## Usage
|
||||
|
||||
TODO
|
||||
|
||||
I need to write a few articles explaining individual fragments of the language, and accurately document everything. Writing tutorials at this stage is not really worth it.
|
||||
The standalone interpreter can be built as the binary target from this package. The language tutorial and standard library documentation is at [www.lbfalvy.com/orchid-reference](https://www.lbfalvy.com/orchid-reference/). Embedder guide and Rust API documentation are coming soon.
|
||||
|
||||
## Design
|
||||
|
||||
|
||||
11
ROADMAP.md
11
ROADMAP.md
@@ -2,12 +2,6 @@ This document is a wishlist, its items aren't ordered in any way other than inli
|
||||
|
||||
# Language
|
||||
|
||||
## Operator declarations
|
||||
A dedicated (exportable) line type for declaring operators. Still just names, only you can write them next to other things without whitespace
|
||||
|
||||
- ops may not contain c-ident-safe characters
|
||||
- clusters of operator characters are broken up with a greedy algorithm
|
||||
|
||||
## Typeclasses
|
||||
Elixir-style protocols probably, only with n-ary dispatch which I saw in SICP-js
|
||||
|
||||
@@ -37,11 +31,6 @@ Error tokens with rules to lift them out. Kinda depends on preservation of locat
|
||||
## Async
|
||||
Join allows to run code when a tuple of pending events all resolve on the event poller
|
||||
|
||||
## New: FS
|
||||
Exposes tree operations to Orchid
|
||||
Uses existing IO to open and read files
|
||||
Uses the event bus to read directories in batches without blocking other Orchid code
|
||||
|
||||
## New: Network
|
||||
Event-driven I/O with single-fire events and resubscription to relay backpressure to the OS. Initially TCP
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import std::(proc::*, to_float, to_string, panic, str::char_at)
|
||||
import std::(to_float, to_string, panic, string::char_at)
|
||||
|
||||
export const main := do{
|
||||
cps print "left operand: ";
|
||||
@@ -6,7 +6,6 @@ export const main := do{
|
||||
let a = to_float data;
|
||||
cps print "operator: ";
|
||||
cps op = readln;
|
||||
let op = char_at op 0;
|
||||
cps println ("you selected \"" ++ op ++ "\"");
|
||||
cps print "right operand: ";
|
||||
cps data = readln;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import system::(io, directfs, async)
|
||||
import std::proc::*
|
||||
import std::(to_string, to_uint, inspect)
|
||||
|
||||
const folder_view := \path. \next. do{
|
||||
@@ -12,7 +11,7 @@ const folder_view := \path.\next. do{
|
||||
|> list::chain;
|
||||
cps print "select an entry, or .. to move up: ";
|
||||
cps choice = readln;
|
||||
if (choice == "..\n") then do {
|
||||
if (choice == "..") then do {
|
||||
let parent_path = directfs::pop_path path
|
||||
|> option::unwrap
|
||||
|> tuple::pick 0 2;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import std::exit_status
|
||||
|
||||
const main := (
|
||||
println "Hello, world!"
|
||||
"success"
|
||||
exit_status::success
|
||||
)
|
||||
-- main := "Hello, World!\n"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import std::(proc::*, to_string)
|
||||
import std::to_string
|
||||
|
||||
export const main := do{
|
||||
let foo = list::new[1, 2, 3, 4, 5, 6];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import std::(proc::*, to_string)
|
||||
import std::to_string
|
||||
|
||||
export const main := do{
|
||||
let foo = map::new[
|
||||
|
||||
@@ -3,13 +3,13 @@ mod cli;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use clap::Parser;
|
||||
use itertools::Itertools;
|
||||
use orchidlang::facade::{Environment, PreMacro};
|
||||
use orchidlang::systems::asynch::AsynchSystem;
|
||||
use orchidlang::systems::stl::StlConfig;
|
||||
use orchidlang::systems::stl::{ExitStatus, StlConfig};
|
||||
use orchidlang::systems::{directfs, io, scheduler};
|
||||
use orchidlang::{ast, interpreted, interpreter, Interner, Sym, VName};
|
||||
|
||||
@@ -80,7 +80,7 @@ fn print_for_debug(e: &ast::Expr<Sym>) {
|
||||
}
|
||||
|
||||
/// A little utility to step through the resolution of a macro set
|
||||
pub fn macro_debug(premacro: PreMacro, sym: Sym) {
|
||||
pub fn macro_debug(premacro: PreMacro, sym: Sym) -> ExitCode {
|
||||
let (mut code, location) = (premacro.consts.get(&sym))
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
@@ -111,7 +111,7 @@ pub fn macro_debug(premacro: PreMacro, sym: Sym) {
|
||||
},
|
||||
"p" | "print" => print_for_debug(&code),
|
||||
"d" | "dump" => print!("Rules: {}", premacro.repo),
|
||||
"q" | "quit" => return,
|
||||
"q" | "quit" => return ExitCode::SUCCESS,
|
||||
"h" | "help" => print!(
|
||||
"Available commands:
|
||||
\t<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();
|
||||
args.chk_proj().unwrap_or_else(|e| panic!("{e}"));
|
||||
let dir = PathBuf::try_from(args.dir).unwrap();
|
||||
@@ -150,7 +150,7 @@ pub fn main() {
|
||||
let premacro = env.load_dir(&dir, &main).unwrap();
|
||||
if args.dump_repo {
|
||||
println!("Parsed rules: {}", premacro.repo);
|
||||
return;
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
if !args.macro_debug.is_empty() {
|
||||
let sym = i.i(&to_vname(&args.macro_debug, &i));
|
||||
@@ -160,15 +160,15 @@ pub fn main() {
|
||||
proc.validate_refs().unwrap();
|
||||
let main = interpreted::Clause::Constant(i.i(&main)).wrap();
|
||||
let ret = proc.run(main, None).unwrap();
|
||||
let interpreter::Return { gas, state, inert } = ret;
|
||||
let interpreter::Return { state, inert, .. } = ret;
|
||||
drop(proc);
|
||||
if inert {
|
||||
println!("Settled at {}", state.expr().clause);
|
||||
if let Some(g) = gas {
|
||||
println!("Remaining gas: {g}")
|
||||
}
|
||||
} else if gas == Some(0) {
|
||||
eprintln!("Ran out of gas!");
|
||||
process::exit(-1);
|
||||
assert!(inert, "Gas is not used, only inert data should be yielded");
|
||||
match state.clone().downcast::<ExitStatus>() {
|
||||
Ok(ExitStatus::Success) => ExitCode::SUCCESS,
|
||||
Ok(ExitStatus::Failure) => ExitCode::FAILURE,
|
||||
Err(_) => {
|
||||
println!("{}", state.expr().clause);
|
||||
ExitCode::SUCCESS
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@ impl AssertionError {
|
||||
location: Location,
|
||||
message: &'static str,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
return Err(Self::ext(location, message));
|
||||
Err(Self::ext(location, message))
|
||||
}
|
||||
|
||||
/// Construct and upcast to [ExternError]
|
||||
pub fn ext(location: Location, message: &'static str) -> Rc<dyn ExternError> {
|
||||
return Self { location, message }.into_extern();
|
||||
Self { location, message }.into_extern()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
@@ -10,9 +10,9 @@ use crate::VName;
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ImportAll {
|
||||
/// The file containing the offending import
|
||||
pub offender_file: Rc<VName>,
|
||||
pub offender_file: Arc<VName>,
|
||||
/// The module containing the offending import
|
||||
pub offender_mod: Rc<VName>,
|
||||
pub offender_mod: Arc<VName>,
|
||||
}
|
||||
impl ProjectError for ImportAll {
|
||||
fn description(&self) -> &str { "a top-level glob import was used" }
|
||||
|
||||
@@ -8,6 +8,8 @@ mod project_error;
|
||||
mod too_many_supers;
|
||||
mod unexpected_directory;
|
||||
mod visibility_mismatch;
|
||||
mod assertion_error;
|
||||
mod runtime_error;
|
||||
|
||||
pub use conflicting_roles::ConflictingRoles;
|
||||
pub use import_all::ImportAll;
|
||||
@@ -18,3 +20,5 @@ pub use project_error::{ErrorPosition, ProjectError, ProjectResult};
|
||||
pub use too_many_supers::TooManySupers;
|
||||
pub use unexpected_directory::UnexpectedDirectory;
|
||||
pub use visibility_mismatch::VisibilityMismatch;
|
||||
pub use assertion_error::AssertionError;
|
||||
pub use runtime_error::RuntimeError;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
use crate::representations::location::Location;
|
||||
@@ -25,14 +25,14 @@ impl ProjectError for NotExported {
|
||||
Box::new(
|
||||
[
|
||||
ErrorPosition {
|
||||
location: Location::File(Rc::new(self.file.clone())),
|
||||
location: Location::File(Arc::new(self.file.clone())),
|
||||
message: Some(format!(
|
||||
"{} isn't exported",
|
||||
Interner::extern_all(&self.subpath).join("::")
|
||||
)),
|
||||
},
|
||||
ErrorPosition {
|
||||
location: Location::File(Rc::new(self.referrer_file.clone())),
|
||||
location: Location::File(Arc::new(self.referrer_file.clone())),
|
||||
message: Some(format!(
|
||||
"{} cannot see this symbol",
|
||||
Interner::extern_all(&self.referrer_subpath).join("::")
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,12 @@ impl RuntimeError {
|
||||
message: String,
|
||||
operation: &'static str,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
return Err(Self { message, operation }.into_extern());
|
||||
Err(Self { message, operation }.into_extern())
|
||||
}
|
||||
|
||||
/// Construct and upcast to [ExternError]
|
||||
pub fn ext(message: String, operation: &'static str) -> Rc<dyn ExternError> {
|
||||
return Self { message, operation }.into_extern();
|
||||
Self { message, operation }.into_extern()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::ProjectError;
|
||||
use crate::{Interner, Location, VName};
|
||||
@@ -16,7 +16,7 @@ impl ProjectError for UnexpectedDirectory {
|
||||
to a directory"
|
||||
}
|
||||
fn one_position(&self) -> crate::Location {
|
||||
Location::File(Rc::new(self.path.clone()))
|
||||
Location::File(Arc::new(self.path.clone()))
|
||||
}
|
||||
|
||||
fn message(&self) -> String {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::project_error::ProjectError;
|
||||
use crate::representations::location::Location;
|
||||
@@ -23,6 +23,6 @@ impl ProjectError for VisibilityMismatch {
|
||||
)
|
||||
}
|
||||
fn one_position(&self) -> Location {
|
||||
Location::File(Rc::new(self.file.clone()))
|
||||
Location::File(Arc::new(self.file.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ impl<'a> Environment<'a> {
|
||||
let system_tree = from_const_tree(sys.constants.clone(), &sys.vname(i));
|
||||
tree = ProjectTree(never::unwrap_always(tree.0.overlay(system_tree.0)));
|
||||
}
|
||||
let mut lexer_plugins = vec![];
|
||||
let mut line_parsers = vec![];
|
||||
let mut prelude = vec![];
|
||||
for sys in systems.iter() {
|
||||
if !sys.code.is_empty() {
|
||||
@@ -50,9 +52,13 @@ impl<'a> Environment<'a> {
|
||||
&|k| sys.load_file(k),
|
||||
&tree,
|
||||
&prelude,
|
||||
&lexer_plugins,
|
||||
&line_parsers,
|
||||
i,
|
||||
)?;
|
||||
}
|
||||
lexer_plugins.extend(sys.lexer_plugin.as_deref().iter());
|
||||
line_parsers.extend(sys.line_parser.as_deref().iter());
|
||||
prelude.extend_from_slice(&sys.prelude);
|
||||
}
|
||||
Ok(CompiledEnv { prelude, tree, systems })
|
||||
@@ -67,11 +73,19 @@ impl<'a> Environment<'a> {
|
||||
let i = self.i;
|
||||
let CompiledEnv { prelude, systems, tree } = self.compile()?;
|
||||
let file_cache = file_loader::mk_dir_cache(dir.to_path_buf());
|
||||
let lexer_plugins = (systems.iter())
|
||||
.filter_map(|s| s.lexer_plugin.as_deref())
|
||||
.collect::<Vec<_>>();
|
||||
let line_parsers = (systems.iter())
|
||||
.filter_map(|s| s.line_parser.as_deref())
|
||||
.collect::<Vec<_>>();
|
||||
let vname_tree = parse_layer(
|
||||
iter::once(target),
|
||||
&|path| file_cache.find(path),
|
||||
&tree,
|
||||
&prelude,
|
||||
&lexer_plugins,
|
||||
&line_parsers,
|
||||
i,
|
||||
)?;
|
||||
let tree = vname_to_sym_tree(vname_tree, i);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::iter;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
@@ -50,7 +50,7 @@ impl<'a> PreMacro<'a> {
|
||||
.unwrap_or_else(|_| panic!("path sourced from symbol names"));
|
||||
(origin.extra.file.as_ref()).cloned()
|
||||
})
|
||||
.map(|p| Location::File(Rc::new(p)))
|
||||
.map(|p| Location::File(Arc::new(p)))
|
||||
.unwrap_or(Location::Unknown);
|
||||
(name, (expr, location))
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ use hashbrown::HashMap;
|
||||
|
||||
use crate::error::{ErrorPosition, ProjectError};
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::parse::{LexerPlugin, LineParser};
|
||||
use crate::pipeline::file_loader::{IOResult, Loaded};
|
||||
use crate::sourcefile::FileEntry;
|
||||
use crate::utils::boxed_iter::box_empty;
|
||||
@@ -23,6 +24,13 @@ pub struct System<'a> {
|
||||
pub prelude: Vec<FileEntry>,
|
||||
/// Handlers for actions defined in this system
|
||||
pub handlers: HandlerTable<'a>,
|
||||
/// Custom lexer for the source code representation atomic data.
|
||||
/// These take priority over builtin lexers so the syntax they
|
||||
/// match should be unambiguous
|
||||
pub lexer_plugin: Option<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> {
|
||||
/// Intern the name of the system so that it can be used as an Orchid
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
use crate::interpreted::ExprInst;
|
||||
use super::ExternError;
|
||||
use crate::ddispatch::request;
|
||||
use crate::error::AssertionError;
|
||||
use crate::interpreted::{ExprInst, TryFromExprInst};
|
||||
use crate::interpreter::{Context, RuntimeError};
|
||||
use crate::representations::interpreted::Clause;
|
||||
use crate::utils::ddispatch::Responder;
|
||||
use crate::Primitive;
|
||||
|
||||
/// Information returned by [Atomic::run]. This mirrors
|
||||
/// [crate::interpreter::Return] but with a clause instead of an Expr.
|
||||
@@ -24,8 +27,18 @@ pub struct AtomicReturn {
|
||||
/// Returned by [Atomic::run]
|
||||
pub type AtomicResult = Result<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
|
||||
pub trait Atomic: Any + Debug + DynClone + Responder
|
||||
pub trait Atomic:
|
||||
Any + Debug + DynClone + StrictEq + Responder + Send
|
||||
where
|
||||
Self: 'static,
|
||||
{
|
||||
@@ -54,7 +67,7 @@ where
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Clause::P(Primitive::Atom(Atom(Box::new(self))))
|
||||
Clause::Atom(Atom(Box::new(self)))
|
||||
}
|
||||
|
||||
/// Wrap the atom in a new expression instance to be placed in a tree
|
||||
@@ -73,7 +86,7 @@ where
|
||||
/// inert at which point the [Atom] will validate and process the argument,
|
||||
/// returning a different [Atom] intended for processing by external code, a new
|
||||
/// [ExternFn] to capture an additional argument, or an Orchid expression
|
||||
/// to pass control back to the interpreter.btop
|
||||
/// to pass control back to the interpreter.
|
||||
pub struct Atom(pub Box<dyn Atomic>);
|
||||
impl Atom {
|
||||
/// Wrap an [Atomic] in a type-erased box
|
||||
@@ -84,23 +97,26 @@ impl Atom {
|
||||
/// Get the contained data
|
||||
#[must_use]
|
||||
pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic }
|
||||
/// Attempt to downcast contained data to a specific type
|
||||
pub fn try_cast<T: Atomic>(self) -> Result<T, Self> {
|
||||
/// Test the type of the contained data without downcasting
|
||||
#[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>() {
|
||||
true => Ok(*self.0.as_any().downcast().expect("checked just above")),
|
||||
false => Err(self),
|
||||
}
|
||||
}
|
||||
/// Test the type of the contained data without downcasting
|
||||
#[must_use]
|
||||
pub fn is<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 {
|
||||
@@ -109,6 +125,16 @@ impl Clone for Atom {
|
||||
|
||||
impl Debug for Atom {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "##ATOM[{:?}]##", self.data())
|
||||
write!(f, "{:?}", self.data())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromExprInst for Atom {
|
||||
fn from_exi(exi: ExprInst) -> Result<Self, Rc<dyn ExternError>> {
|
||||
let loc = exi.location();
|
||||
match exi.expr_val().clause {
|
||||
Clause::Atom(a) => Ok(a),
|
||||
_ => AssertionError::fail(loc, "atom"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ use trait_set::trait_set;
|
||||
use super::{Atomic, ExternFn, InertAtomic, XfnResult};
|
||||
use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::interpreter::{Context, HandlerRes};
|
||||
use crate::utils::pure_push::pushed_ref;
|
||||
use crate::utils::pure_seq::pushed_ref;
|
||||
use crate::ConstTree;
|
||||
|
||||
trait_set! {
|
||||
/// A "well behaved" type that can be used as payload in a CPS box
|
||||
pub trait CPSPayload = Clone + Debug + 'static;
|
||||
pub trait CPSPayload = Clone + Debug + Send + 'static;
|
||||
/// A function to handle a CPS box with a specific payload
|
||||
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &ExprInst) -> HandlerRes;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@ use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
use std::rc::Rc;
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
use dyn_clone::{DynClone, clone_box};
|
||||
|
||||
use super::XfnResult;
|
||||
use crate::interpreted::ExprInst;
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::Clause;
|
||||
use crate::Primitive;
|
||||
|
||||
/// Errors produced by external code
|
||||
pub trait ExternError: Display {
|
||||
@@ -34,7 +33,7 @@ impl Error for dyn ExternError {}
|
||||
/// Represents an externally defined function from the perspective of
|
||||
/// the executor. Since Orchid lacks basic numerical operations,
|
||||
/// these are also external functions.
|
||||
pub trait ExternFn: DynClone {
|
||||
pub trait ExternFn: DynClone + Send {
|
||||
/// Display name of the function
|
||||
#[must_use]
|
||||
fn name(&self) -> &str;
|
||||
@@ -50,7 +49,7 @@ pub trait ExternFn: DynClone {
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Clause::P(Primitive::ExternFn(Box::new(self)))
|
||||
Clause::ExternFn(ExFn(Box::new(self)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,3 +67,21 @@ impl Debug for dyn ExternFn {
|
||||
write!(f, "##EXTERN[{}]##", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a black box function that can be applied to a [Clause] to produce
|
||||
/// a new [Clause], typically an [Atom] representing external work, a new [ExFn]
|
||||
/// to take additional arguments, or an Orchid tree to return control to the
|
||||
/// interpreter
|
||||
#[derive(Debug)]
|
||||
pub struct ExFn(pub Box<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()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::atom::StrictEq;
|
||||
use super::{
|
||||
Atomic, AtomicResult, AtomicReturn, ExternError, ExternFn, XfnResult,
|
||||
};
|
||||
@@ -9,7 +10,7 @@ use crate::ddispatch::Responder;
|
||||
use crate::interpreted::{Clause, ExprInst, TryFromExprInst};
|
||||
use crate::interpreter::{run, Context, Return};
|
||||
use crate::systems::codegen::{opt, res};
|
||||
use crate::{Literal, OrcString};
|
||||
use crate::OrcString;
|
||||
|
||||
/// A trait for things that are infallibly convertible to [Clause]. These types
|
||||
/// can be returned by callbacks passed to the [super::xfn_1ary] family of
|
||||
@@ -17,6 +18,8 @@ use crate::{Literal, OrcString};
|
||||
pub trait ToClause: Clone {
|
||||
/// Convert the type to a [Clause].
|
||||
fn to_clause(self) -> Clause;
|
||||
/// Convert to an expression instance via [ToClause].
|
||||
fn to_exi(self) -> ExprInst { self.to_clause().wrap() }
|
||||
}
|
||||
|
||||
impl<T: Atomic + Clone> ToClause for T {
|
||||
@@ -28,14 +31,8 @@ impl ToClause for Clause {
|
||||
impl ToClause for ExprInst {
|
||||
fn to_clause(self) -> Clause { self.expr_val().clause }
|
||||
}
|
||||
impl ToClause for Literal {
|
||||
fn to_clause(self) -> Clause { self.into() }
|
||||
}
|
||||
impl ToClause for u64 {
|
||||
fn to_clause(self) -> Clause { Literal::Uint(self).into() }
|
||||
}
|
||||
impl ToClause for String {
|
||||
fn to_clause(self) -> Clause { OrcString::from(self).cls() }
|
||||
fn to_clause(self) -> Clause { OrcString::from(self).atom_cls() }
|
||||
}
|
||||
impl<T: ToClause> ToClause for Option<T> {
|
||||
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>,
|
||||
_u: PhantomData<U>,
|
||||
}
|
||||
unsafe impl<T, U, F: Send> Send for Param<T, U, F> {}
|
||||
impl<T, U, F> Param<T, U, F> {
|
||||
/// Wrap a new function in a parametric struct
|
||||
pub fn new(f: F) -> Self
|
||||
where
|
||||
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
|
||||
pub fn get(self) -> F { self.data }
|
||||
}
|
||||
impl<T, U, F: Clone> Clone for Param<T, U, F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
data: self.data.clone(),
|
||||
_t: PhantomData::default(),
|
||||
_u: PhantomData::default(),
|
||||
}
|
||||
Self { data: self.data.clone(), _t: PhantomData, _u: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
T: 'static + TryFromExprInst,
|
||||
U: 'static + ToClause,
|
||||
F: 'static + Clone + FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
|
||||
F: 'static + Clone + Send + FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
|
||||
> ToClause for Param<T, U, F>
|
||||
{
|
||||
fn to_clause(self) -> Clause { self.xfn_cls() }
|
||||
@@ -93,6 +87,11 @@ struct FnMiddleStage<T, U, F> {
|
||||
argument: ExprInst,
|
||||
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> {
|
||||
fn clone(&self) -> Self {
|
||||
@@ -110,7 +109,7 @@ impl<T, U, F> Responder for FnMiddleStage<T, U, F> {}
|
||||
impl<
|
||||
T: 'static + TryFromExprInst,
|
||||
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>
|
||||
{
|
||||
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
|
||||
@@ -128,7 +127,7 @@ impl<
|
||||
impl<
|
||||
T: 'static + TryFromExprInst,
|
||||
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>
|
||||
{
|
||||
fn name(&self) -> &str { "anonymous Rust function" }
|
||||
@@ -155,16 +154,16 @@ pub mod constructors {
|
||||
" Orchid function. See also Constraints summarized:\n\n"
|
||||
"- the callback must live as long as `'static`\n"
|
||||
"- All arguments must implement [TryFromExprInst]\n"
|
||||
"- all but the last argument must implement [Clone]\n"
|
||||
"- all but the last argument must implement [Clone] and [Send]\n"
|
||||
"- the return type must implement [ToClause].\n\n"
|
||||
]
|
||||
#[doc = "Other arities: " $( "[xfn_" $alt "ary], " )+ ]
|
||||
pub fn [< xfn_ $number ary >] <
|
||||
$( $t : TryFromExprInst + Clone + 'static, )*
|
||||
$( $t : TryFromExprInst + Clone + Send + 'static, )*
|
||||
TLast: TryFromExprInst + 'static,
|
||||
TReturn: ToClause + 'static,
|
||||
TReturn: ToClause + Send + 'static,
|
||||
TFunction: FnOnce( $( $t , )* TLast )
|
||||
-> Result<TReturn, Rc<dyn ExternError>> + Clone + 'static
|
||||
-> Result<TReturn, Rc<dyn ExternError>> + Clone + Send + 'static
|
||||
>(function: TFunction) -> impl ExternFn {
|
||||
xfn_variant!(@BODY_LOOP function
|
||||
( $( ( $t [< $t:lower >] ) )* )
|
||||
|
||||
@@ -2,22 +2,25 @@ use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::atom::StrictEq;
|
||||
use super::{AtomicResult, AtomicReturn, ExternError};
|
||||
use crate::error::AssertionError;
|
||||
#[allow(unused)] // for doc
|
||||
// use crate::define_fn;
|
||||
use crate::foreign::Atomic;
|
||||
use crate::interpreted::{Clause, Expr, ExprInst, TryFromExprInst};
|
||||
use crate::interpreter::Context;
|
||||
use crate::systems::AssertionError;
|
||||
use crate::systems::stl::Numeric;
|
||||
use crate::utils::ddispatch::{Request, Responder};
|
||||
use crate::Primitive;
|
||||
|
||||
/// A proxy trait that implements [Atomic] for blobs of data in Rust code that
|
||||
/// cannot be processed and always report inert. Since these are expected to be
|
||||
/// parameters of functions defined with [define_fn] it also automatically
|
||||
/// implements [TryFromExprInst] so that a conversion doesn't have to be
|
||||
/// provided in argument lists.
|
||||
pub trait InertAtomic: Debug + Clone + 'static {
|
||||
pub trait InertAtomic: Debug + Clone + Send + 'static {
|
||||
/// Typename to be shown in the error when a conversion from [ExprInst] fails
|
||||
#[must_use]
|
||||
fn type_str() -> &'static str;
|
||||
@@ -25,9 +28,29 @@ pub trait InertAtomic: Debug + Clone + 'static {
|
||||
/// you need it, but behaves exactly as the default implementation.
|
||||
#[allow(unused_mut, unused_variables)] // definition should show likely usage
|
||||
fn respond(&self, mut request: Request) {}
|
||||
/// Equality comparison used by the pattern matcher. Since the pattern matcher
|
||||
/// only works with parsed code, you only need to implement this if your type
|
||||
/// is directly parseable.
|
||||
///
|
||||
/// If your type implements [PartialEq], this can simply be implemented as
|
||||
/// ```ignore
|
||||
/// fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
/// ```
|
||||
fn strict_eq(&self, _: &Self) -> bool { false }
|
||||
}
|
||||
impl<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 {
|
||||
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 {
|
||||
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>> {
|
||||
let Expr { clause, location } = exi.expr_val();
|
||||
match clause {
|
||||
Clause::P(Primitive::Atom(a)) => match a.0.as_any().downcast() {
|
||||
Clause::Atom(a) => match a.0.as_any().downcast() {
|
||||
Ok(t) => Ok(*t),
|
||||
Err(_) => AssertionError::fail(location, Self::type_str()),
|
||||
},
|
||||
@@ -50,3 +73,24 @@ impl<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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ mod inert;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn};
|
||||
pub use extern_fn::{ExternError, ExternFn};
|
||||
pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn, StrictEq};
|
||||
pub use extern_fn::{ExternError, ExternFn, ExFn};
|
||||
pub use fn_bridge::constructors::{
|
||||
xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, xfn_5ary, xfn_6ary, xfn_7ary,
|
||||
xfn_8ary, xfn_9ary,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::RefCell;
|
||||
use std::hash::{BuildHasher, Hash};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{RwLock, Arc};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
@@ -11,32 +10,32 @@ use super::token::Tok;
|
||||
/// Lasso but much simpler, in part because not much can be known about the
|
||||
/// type.
|
||||
pub struct TypedInterner<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> {
|
||||
/// Create a fresh interner instance
|
||||
#[must_use]
|
||||
pub fn new() -> Rc<Self> {
|
||||
Rc::new(Self { tokens: RefCell::new(HashMap::new()) })
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self { tokens: RwLock::new(HashMap::new()) })
|
||||
}
|
||||
|
||||
/// Intern an object, returning a token
|
||||
#[must_use]
|
||||
pub fn i<Q: ?Sized + Eq + Hash + ToOwned<Owned = T>>(
|
||||
self: &Rc<Self>,
|
||||
self: &Arc<Self>,
|
||||
q: &Q,
|
||||
) -> Tok<T>
|
||||
where
|
||||
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 raw_entry = tokens
|
||||
.raw_entry_mut()
|
||||
.from_hash(hash, |k| <T as Borrow<Q>>::borrow(k) == q);
|
||||
let kv = raw_entry.or_insert_with(|| {
|
||||
let keyrc = Rc::new(q.to_owned());
|
||||
let token = Tok::<T>::new(keyrc.clone(), Rc::downgrade(self));
|
||||
let keyrc = Arc::new(q.to_owned());
|
||||
let token = Tok::<T>::new(keyrc.clone(), Arc::downgrade(self));
|
||||
(keyrc, token)
|
||||
});
|
||||
kv.1.clone()
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::any::{Any, TypeId};
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::hash::Hash;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
@@ -13,7 +13,7 @@ use super::token::Tok;
|
||||
/// that implements [ToOwned]. Objects of the same type are stored together in a
|
||||
/// [TypedInterner].
|
||||
pub struct Interner {
|
||||
interners: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
|
||||
interners: RefCell<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
|
||||
}
|
||||
impl Interner {
|
||||
/// Create a new interner
|
||||
@@ -24,7 +24,7 @@ impl Interner {
|
||||
#[must_use]
|
||||
pub fn i<Q: ?Sized + Eq + Hash + ToOwned>(&self, q: &Q) -> Tok<Q::Owned>
|
||||
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 interner = get_interner(&mut interners);
|
||||
@@ -44,9 +44,9 @@ impl Default for Interner {
|
||||
|
||||
/// Get or create an interner for a given type.
|
||||
#[must_use]
|
||||
fn get_interner<T: 'static + Eq + Hash + Clone>(
|
||||
interners: &mut RefMut<HashMap<TypeId, Rc<dyn Any>>>,
|
||||
) -> Rc<TypedInterner<T>> {
|
||||
fn get_interner<T: 'static + Eq + Hash + Clone + Send + Sync>(
|
||||
interners: &mut RefMut<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
|
||||
) -> Arc<TypedInterner<T>> {
|
||||
let boxed = interners
|
||||
.raw_entry_mut()
|
||||
.from_key(&TypeId::of::<T>())
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::ops::Deref;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use super::TypedInterner;
|
||||
|
||||
@@ -13,13 +13,13 @@ use super::TypedInterner;
|
||||
/// currently not enforced.
|
||||
#[derive(Clone)]
|
||||
pub struct Tok<T: Eq + Hash + Clone + 'static> {
|
||||
data: Rc<T>,
|
||||
data: Arc<T>,
|
||||
interner: Weak<TypedInterner<T>>,
|
||||
}
|
||||
impl<T: Eq + Hash + Clone + 'static> Tok<T> {
|
||||
/// Create a new token. Used exclusively by the interner
|
||||
#[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 }
|
||||
}
|
||||
/// Take the ID number out of a token
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::error::RuntimeError;
|
||||
use super::Return;
|
||||
use crate::foreign::AtomicReturn;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::{PathSet, Primitive};
|
||||
use crate::representations::PathSet;
|
||||
use crate::utils::never::{unwrap_always, Always};
|
||||
use crate::utils::Side;
|
||||
|
||||
@@ -77,9 +77,8 @@ pub fn apply(
|
||||
) -> Result<Return, RuntimeError> {
|
||||
let (state, (gas, inert)) = f.try_update(|clause, loc| match clause {
|
||||
// apply an ExternFn or an internal function
|
||||
Clause::P(Primitive::ExternFn(f)) => {
|
||||
let clause =
|
||||
f.apply(x, ctx.clone()).map_err(|e| RuntimeError::Extern(e))?;
|
||||
Clause::ExternFn(f) => {
|
||||
let clause = f.apply(x, ctx.clone()).map_err(RuntimeError::Extern)?;
|
||||
Ok((clause, (ctx.gas.map(|g| g - 1), false)))
|
||||
},
|
||||
Clause::Lambda { args, body } => Ok(if let Some(args) = args {
|
||||
@@ -97,7 +96,7 @@ pub fn apply(
|
||||
} else {
|
||||
Err(RuntimeError::MissingSymbol(name.clone(), loc))
|
||||
},
|
||||
Clause::P(Primitive::Atom(atom)) => {
|
||||
Clause::Atom(atom) => {
|
||||
// take a step in expanding atom
|
||||
let AtomicReturn { clause, gas, inert } = atom.run(ctx.clone())?;
|
||||
Ok((Clause::Apply { f: clause.wrap(), x }, (gas, inert)))
|
||||
|
||||
@@ -8,7 +8,6 @@ use super::{run, Context, Return, RuntimeError};
|
||||
use crate::foreign::{Atom, Atomic, ExternError};
|
||||
use crate::interpreted::{Clause, Expr, ExprInst};
|
||||
use crate::utils::take_with_output;
|
||||
use crate::Primitive;
|
||||
|
||||
trait_set! {
|
||||
trait Handler = FnMut(Box<dyn Any>) -> HandlerRes;
|
||||
@@ -71,9 +70,9 @@ pub fn run_handler(
|
||||
loop {
|
||||
let mut ret = run(expr, ctx.clone())?;
|
||||
let quit = take_with_output(&mut ret.state, |exi| match exi.expr_val() {
|
||||
Expr { clause: Clause::P(Primitive::Atom(a)), .. } => {
|
||||
Expr { clause: Clause::Atom(a), .. } => {
|
||||
match handlers.dispatch(a.0) {
|
||||
Err(b) => (Clause::P(Primitive::Atom(Atom(b))).wrap(), Ok(true)),
|
||||
Err(b) => (Clause::Atom(Atom(b)).wrap(), Ok(true)),
|
||||
Ok(e) => match e {
|
||||
Ok(expr) => (expr, Ok(false)),
|
||||
Err(e) => (Clause::Bottom.wrap(), Err(e)),
|
||||
|
||||
@@ -3,7 +3,6 @@ use super::context::{Context, Return};
|
||||
use super::error::RuntimeError;
|
||||
use crate::foreign::AtomicReturn;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::Primitive;
|
||||
|
||||
/// Normalize an expression using beta reduction with memoization
|
||||
pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
|
||||
@@ -19,7 +18,7 @@ pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
|
||||
ctx.gas = res.gas;
|
||||
cls = res.state.expr().clause.clone();
|
||||
},
|
||||
Clause::P(Primitive::Atom(data)) => {
|
||||
Clause::Atom(data) => {
|
||||
let AtomicReturn { clause, gas, inert } = data.run(ctx.clone())?;
|
||||
if inert {
|
||||
return Ok((clause, (gas, true)));
|
||||
|
||||
@@ -29,8 +29,8 @@ pub use representations::project::{
|
||||
collect_consts, collect_rules, vname_to_sym_tree, ProjectTree,
|
||||
};
|
||||
pub use representations::{
|
||||
ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal,
|
||||
Location, NameLike, OrcString, PathSet, Primitive, Sym, VName,
|
||||
ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Location,
|
||||
NameLike, OrcString, PathSet, Sym, VName,
|
||||
};
|
||||
pub use utils::substack::Substack;
|
||||
pub use utils::{ddispatch, take_with_output, thread_pool, IdMap, Side};
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -1,19 +1,62 @@
|
||||
use std::rc::Rc;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::stream::Stream;
|
||||
use crate::error::ProjectResult;
|
||||
use crate::foreign::Atom;
|
||||
use crate::interner::Interner;
|
||||
use crate::{Tok, VName};
|
||||
use crate::sourcefile::FileEntryKind;
|
||||
use crate::{Location, VName};
|
||||
|
||||
/// Trait enclosing all context features
|
||||
///
|
||||
/// Hiding type parameters in associated types allows for simpler
|
||||
/// parser definitions
|
||||
pub trait Context: Clone {
|
||||
pub trait Context {
|
||||
#[must_use]
|
||||
fn ops(&self) -> &[Tok<String>];
|
||||
#[must_use]
|
||||
fn file(&self) -> Rc<VName>;
|
||||
fn file(&self) -> Arc<VName>;
|
||||
#[must_use]
|
||||
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
|
||||
@@ -21,29 +64,55 @@ pub trait Context: Clone {
|
||||
/// Hiding type parameters in associated types allows for simpler
|
||||
/// parser definitions
|
||||
pub struct ParsingContext<'a> {
|
||||
pub ops: &'a [Tok<String>],
|
||||
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> {
|
||||
pub fn new(
|
||||
ops: &'a [Tok<String>],
|
||||
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 { ops, interner, file }
|
||||
Self { interner, file, source, lexers, line_parsers }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Clone for ParsingContext<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { ops: self.ops, interner: self.interner, file: self.file.clone() }
|
||||
Self {
|
||||
interner: self.interner,
|
||||
file: self.file.clone(),
|
||||
source: self.source.clone(),
|
||||
lexers: self.lexers,
|
||||
line_parsers: self.line_parsers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for ParsingContext<'_> {
|
||||
fn interner(&self) -> &Interner { self.interner }
|
||||
fn file(&self) -> Rc<VName> { self.file.clone() }
|
||||
fn ops(&self) -> &[Tok<String>] { self.ops }
|
||||
fn file(&self) -> Arc<VName> { self.file.clone() }
|
||||
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()) }
|
||||
}
|
||||
|
||||
@@ -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>>;
|
||||
@@ -1,12 +1,10 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use chumsky::prelude::Simple;
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::{Entry, Lexeme};
|
||||
use crate::error::{ErrorPosition, ProjectError};
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Location, Tok, VName};
|
||||
use crate::error::ProjectError;
|
||||
use crate::{Location, Tok};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LineNeedsPrefix {
|
||||
@@ -14,10 +12,10 @@ pub struct LineNeedsPrefix {
|
||||
}
|
||||
impl ProjectError for LineNeedsPrefix {
|
||||
fn description(&self) -> &str { "This linetype requires a prefix" }
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
fn message(&self) -> String {
|
||||
format!("{} cannot appear at the beginning of a line", self.entry)
|
||||
}
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -27,14 +25,12 @@ pub struct UnexpectedEOL {
|
||||
}
|
||||
impl ProjectError for UnexpectedEOL {
|
||||
fn description(&self) -> &str { "The line ended abruptly" }
|
||||
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
fn message(&self) -> String {
|
||||
"The line ends unexpectedly here. In Orchid, all line breaks outside \
|
||||
parentheses start a new declaration"
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
}
|
||||
|
||||
pub struct ExpectedEOL {
|
||||
@@ -58,10 +54,8 @@ impl ExpectedName {
|
||||
}
|
||||
}
|
||||
impl ProjectError for ExpectedName {
|
||||
fn description(&self) -> &str {
|
||||
"A name was expected here, but something else was found"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str { "A name was expected" }
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
fn message(&self) -> String {
|
||||
if self.entry.is_keyword() {
|
||||
format!(
|
||||
@@ -72,8 +66,6 @@ impl ProjectError for ExpectedName {
|
||||
format!("Expected a name, found {}", self.entry)
|
||||
}
|
||||
}
|
||||
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
}
|
||||
|
||||
#[derive()]
|
||||
@@ -84,18 +76,15 @@ pub struct Expected {
|
||||
}
|
||||
impl Expected {
|
||||
pub fn expect(l: Lexeme, e: &Entry) -> Result<(), Rc<dyn ProjectError>> {
|
||||
if e.lexeme != l {
|
||||
return Err(
|
||||
Self { expected: vec![l], or_name: false, found: e.clone() }.rc(),
|
||||
);
|
||||
if e.lexeme.strict_eq(&l) {
|
||||
return Ok(());
|
||||
}
|
||||
Ok(())
|
||||
Err(Self { expected: vec![l], or_name: false, found: e.clone() }.rc())
|
||||
}
|
||||
}
|
||||
impl ProjectError for Expected {
|
||||
fn description(&self) -> &str {
|
||||
"A concrete token was expected but something else was found"
|
||||
}
|
||||
fn description(&self) -> &str { "A concrete token was expected" }
|
||||
fn one_position(&self) -> Location { self.found.location() }
|
||||
fn message(&self) -> String {
|
||||
let list = match &self.expected[..] {
|
||||
&[] => return "Unsatisfiable expectation".to_string(),
|
||||
@@ -108,21 +97,15 @@ impl ProjectError for Expected {
|
||||
let or_name = if self.or_name { " or a name" } else { "" };
|
||||
format!("Expected {list}{or_name} but found {}", self.found)
|
||||
}
|
||||
|
||||
fn one_position(&self) -> Location { self.found.location() }
|
||||
}
|
||||
|
||||
pub struct ReservedToken {
|
||||
pub entry: Entry,
|
||||
}
|
||||
impl ProjectError for ReservedToken {
|
||||
fn description(&self) -> &str {
|
||||
"A token reserved for future use was found in the code"
|
||||
}
|
||||
|
||||
fn message(&self) -> String { format!("{} is a reserved token", self.entry) }
|
||||
|
||||
fn description(&self) -> &str { "Syntax reserved for future use" }
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
fn message(&self) -> String { format!("{} is a reserved token", self.entry) }
|
||||
}
|
||||
|
||||
pub struct BadTokenInRegion {
|
||||
@@ -130,15 +113,11 @@ pub struct BadTokenInRegion {
|
||||
pub region: &'static str,
|
||||
}
|
||||
impl ProjectError for BadTokenInRegion {
|
||||
fn description(&self) -> &str {
|
||||
"A token was found in a region where it should not appear"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str { "An unexpected token was found" }
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
fn message(&self) -> String {
|
||||
format!("{} cannot appear in {}", self.entry, self.region)
|
||||
}
|
||||
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
}
|
||||
|
||||
pub struct NotFound {
|
||||
@@ -146,70 +125,90 @@ pub struct NotFound {
|
||||
pub location: Location,
|
||||
}
|
||||
impl ProjectError for NotFound {
|
||||
fn description(&self) -> &str {
|
||||
"A specific lexeme was expected but not found in the given range"
|
||||
}
|
||||
|
||||
fn message(&self) -> String { format!("{} was expected", self.expected) }
|
||||
|
||||
fn description(&self) -> &str { "A specific lexeme was expected" }
|
||||
fn one_position(&self) -> Location { self.location.clone() }
|
||||
fn message(&self) -> String { format!("{} was expected", self.expected) }
|
||||
}
|
||||
|
||||
pub struct LeadingNS {
|
||||
pub location: Location,
|
||||
}
|
||||
pub struct LeadingNS(pub Location);
|
||||
impl ProjectError for LeadingNS {
|
||||
fn description(&self) -> &str { ":: can only follow a name token" }
|
||||
fn one_position(&self) -> Location { self.location.clone() }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct MisalignedParen {
|
||||
pub entry: Entry,
|
||||
}
|
||||
pub struct MisalignedParen(pub Entry);
|
||||
impl ProjectError for MisalignedParen {
|
||||
fn description(&self) -> &str {
|
||||
"Parentheses (), [] and {} must always pair up"
|
||||
}
|
||||
fn message(&self) -> String { format!("This {} has no pair", self.entry) }
|
||||
fn one_position(&self) -> Location { self.entry.location() }
|
||||
fn description(&self) -> &str { "(), [] and {} must always pair up" }
|
||||
fn one_position(&self) -> Location { self.0.location() }
|
||||
fn message(&self) -> String { format!("This {} has no pair", self.0) }
|
||||
}
|
||||
|
||||
pub struct NamespacedExport {
|
||||
pub location: Location,
|
||||
}
|
||||
pub struct NamespacedExport(pub Location);
|
||||
impl ProjectError for NamespacedExport {
|
||||
fn description(&self) -> &str {
|
||||
"Exports can only refer to unnamespaced names in the local namespace"
|
||||
}
|
||||
fn one_position(&self) -> Location { self.location.clone() }
|
||||
fn description(&self) -> &str { "Only local names may be exported" }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct GlobExport {
|
||||
pub location: Location,
|
||||
}
|
||||
pub struct GlobExport(pub Location);
|
||||
impl ProjectError for GlobExport {
|
||||
fn description(&self) -> &str {
|
||||
"Exports can only refer to concrete names, globstars are not allowed"
|
||||
}
|
||||
fn one_position(&self) -> Location { self.location.clone() }
|
||||
fn description(&self) -> &str { "Globstars are not allowed in exports" }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct LexError {
|
||||
pub errors: Vec<Simple<char>>,
|
||||
pub source: Rc<String>,
|
||||
pub file: VName,
|
||||
pub struct NoStringEnd(pub Location);
|
||||
impl ProjectError for NoStringEnd {
|
||||
fn description(&self) -> &str { "A string literal was not closed with `\"`" }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
impl ProjectError for LexError {
|
||||
fn description(&self) -> &str { "An error occured during tokenization" }
|
||||
fn positions(&self) -> BoxedIter<ErrorPosition> {
|
||||
let file = self.file.clone();
|
||||
Box::new(self.errors.iter().map(move |s| ErrorPosition {
|
||||
location: Location::Range {
|
||||
file: Rc::new(file.clone()),
|
||||
range: s.span(),
|
||||
source: self.source.clone(),
|
||||
},
|
||||
message: Some(format!("{}", s)),
|
||||
}))
|
||||
|
||||
pub struct NoCommentEnd(pub Location);
|
||||
impl ProjectError for NoCommentEnd {
|
||||
fn description(&self) -> &str { "a comment was not closed with `]--`" }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct FloatPlacehPrio(pub Location);
|
||||
impl ProjectError for FloatPlacehPrio {
|
||||
fn description(&self) -> &str {
|
||||
"a placeholder priority has a decimal point or a negative exponent"
|
||||
}
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct NaNLiteral(pub Location);
|
||||
impl ProjectError for NaNLiteral {
|
||||
fn description(&self) -> &str { "float literal decoded to NaN" }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct LiteralOverflow(pub Location);
|
||||
impl ProjectError for LiteralOverflow {
|
||||
fn description(&self) -> &str { "number literal described number greater than usize::MAX" }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct ExpectedDigit(pub Location);
|
||||
impl ProjectError for ExpectedDigit {
|
||||
fn description(&self) -> &str { "expected a digit" }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct NotHex(pub Location);
|
||||
impl ProjectError for NotHex {
|
||||
fn description(&self) -> &str { "Expected a hex digit" }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct BadCodePoint(pub Location);
|
||||
impl ProjectError for BadCodePoint {
|
||||
fn description(&self) -> &str {
|
||||
"\\uXXXX escape sequence does not describe valid code point"
|
||||
}
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
pub struct BadEscapeSequence(pub Location);
|
||||
impl ProjectError for BadEscapeSequence {
|
||||
fn description(&self) -> &str { "Unrecognized escape sequence" }
|
||||
fn one_position(&self) -> Location { self.0.clone() }
|
||||
}
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use chumsky::Parser;
|
||||
|
||||
use super::context::Context;
|
||||
use super::errors::LexError;
|
||||
use super::lexer;
|
||||
use super::lexer::lex;
|
||||
use super::sourcefile::parse_module_body;
|
||||
use super::stream::Stream;
|
||||
use crate::error::{ParseErrorWithTokens, ProjectError, ProjectResult};
|
||||
use crate::representations::sourcefile::FileEntry;
|
||||
|
||||
pub fn parse2(data: &str, ctx: impl Context) -> ProjectResult<Vec<FileEntry>> {
|
||||
let source = Rc::new(data.to_string());
|
||||
let lexie = lexer(ctx.clone(), source.clone());
|
||||
let tokens = (lexie.parse(data)).map_err(|errors| {
|
||||
LexError {
|
||||
errors,
|
||||
file: ctx.file().as_ref().clone(),
|
||||
source: source.clone(),
|
||||
}
|
||||
.rc()
|
||||
})?;
|
||||
pub fn parse2(ctx: impl Context) -> ProjectResult<Vec<FileEntry>> {
|
||||
let tokens = lex(vec![], ctx.source().as_str(), &ctx).expect("debug");
|
||||
if tokens.is_empty() {
|
||||
Ok(Vec::new())
|
||||
} else {
|
||||
parse_module_body(Stream::from_slice(&tokens), ctx).map_err(|error| {
|
||||
ParseErrorWithTokens { error, full_source: data.to_string(), tokens }.rc()
|
||||
parse_module_body(Stream::from_slice(&tokens), &ctx).map_err(|error| {
|
||||
ParseErrorWithTokens { error, full_source: ctx.source().to_string(), tokens }.rc()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::fmt::Display;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chumsky::prelude::*;
|
||||
use chumsky::text::keyword;
|
||||
use chumsky::Parser;
|
||||
use itertools::Itertools;
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::LexerPlugin;
|
||||
use super::context::Context;
|
||||
use super::decls::SimpleParser;
|
||||
use super::number::print_nat16;
|
||||
use super::{comment, name, number, placeholder, string};
|
||||
use super::errors::{FloatPlacehPrio, NoCommentEnd};
|
||||
use super::numeric::{parse_num, print_nat16, numstart};
|
||||
use crate::ast::{PHClass, Placeholder};
|
||||
use crate::error::{ProjectResult, ProjectError};
|
||||
use crate::foreign::Atom;
|
||||
use crate::interner::Tok;
|
||||
use crate::parse::operators::operators_parser;
|
||||
use crate::representations::Literal;
|
||||
use crate::{Interner, Location, VName};
|
||||
use crate::parse::numeric::{numchar, lex_numeric};
|
||||
use crate::parse::string::lex_string;
|
||||
use crate::systems::stl::Numeric;
|
||||
use crate::utils::pure_seq::next;
|
||||
use crate::utils::unwrap_or;
|
||||
use crate::{Location, VName};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Entry {
|
||||
pub lexeme: Lexeme,
|
||||
pub location: Location,
|
||||
@@ -32,14 +34,15 @@ impl Entry {
|
||||
|
||||
#[must_use]
|
||||
pub fn is_keyword(&self) -> bool {
|
||||
matches!(
|
||||
self.lexeme,
|
||||
Lexeme::Const
|
||||
| Lexeme::Export
|
||||
| Lexeme::Import
|
||||
| Lexeme::Macro
|
||||
| Lexeme::Module
|
||||
)
|
||||
false
|
||||
// matches!(
|
||||
// self.lexeme,
|
||||
// Lexeme::Const
|
||||
// | Lexeme::Export
|
||||
// | Lexeme::Import
|
||||
// | Lexeme::Macro
|
||||
// | Lexeme::Module
|
||||
// )
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -51,9 +54,13 @@ impl Entry {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn file(&self) -> Rc<VName> {
|
||||
pub fn file(&self) -> Arc<VName> {
|
||||
self.location.file().expect("An Entry can only have a range location")
|
||||
}
|
||||
|
||||
fn new(location: Location, lexeme: Lexeme) -> Self {
|
||||
Self { lexeme, location }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Entry {
|
||||
@@ -66,9 +73,9 @@ impl AsRef<Location> for Entry {
|
||||
fn as_ref(&self) -> &Location { &self.location }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Lexeme {
|
||||
Literal(Literal),
|
||||
Atom(Atom),
|
||||
Name(Tok<String>),
|
||||
Arrow(NotNan<f64>),
|
||||
/// Walrus operator (formerly shorthand macro)
|
||||
@@ -86,20 +93,19 @@ pub enum Lexeme {
|
||||
At,
|
||||
// Dot,
|
||||
Type, // type operator
|
||||
Comment(Rc<String>),
|
||||
Export,
|
||||
Import,
|
||||
Module,
|
||||
Macro,
|
||||
Const,
|
||||
Operators(Rc<VName>),
|
||||
Comment(Arc<String>),
|
||||
// Export,
|
||||
// Import,
|
||||
// Module,
|
||||
// Macro,
|
||||
// Const,
|
||||
Placeh(Placeholder),
|
||||
}
|
||||
|
||||
impl Display for Lexeme {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Literal(l) => write!(f, "{:?}", l),
|
||||
Self::Atom(a) => write!(f, "{a:?}"),
|
||||
Self::Name(token) => write!(f, "{}", **token),
|
||||
Self::Walrus => write!(f, ":="),
|
||||
Self::Arrow(prio) => write!(f, "={}=>", print_nat16(*prio)),
|
||||
@@ -116,14 +122,11 @@ impl Display for Lexeme {
|
||||
Self::At => write!(f, "@"),
|
||||
Self::Type => write!(f, ":"),
|
||||
Self::Comment(text) => write!(f, "--[{}]--", text),
|
||||
Self::Export => write!(f, "export"),
|
||||
Self::Import => write!(f, "import"),
|
||||
Self::Module => write!(f, "module"),
|
||||
Self::Const => write!(f, "const"),
|
||||
Self::Macro => write!(f, "macro"),
|
||||
Self::Operators(ops) => {
|
||||
write!(f, "operators[{}]", Interner::extern_all(ops).join(" "))
|
||||
},
|
||||
// Self::Export => write!(f, "export"),
|
||||
// Self::Import => write!(f, "import"),
|
||||
// Self::Module => write!(f, "module"),
|
||||
// Self::Const => write!(f, "const"),
|
||||
// Self::Macro => write!(f, "macro"),
|
||||
Self::Placeh(Placeholder { name, class }) => match *class {
|
||||
PHClass::Scalar => write!(f, "${}", **name),
|
||||
PHClass::Vec { nonzero, prio } => {
|
||||
@@ -147,97 +150,192 @@ impl Lexeme {
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn parser<E: chumsky::Error<Entry>>(
|
||||
self,
|
||||
) -> impl Parser<Entry, Entry, Error = E> + Clone {
|
||||
filter(move |ent: &Entry| ent.lexeme == self)
|
||||
pub fn strict_eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Arrow(f1), Self::Arrow(f2)) => f1 == f2,
|
||||
(Self::At, Self::At) | (Self::BR, Self::BR) => true,
|
||||
(Self::BS, Self::BS) /*| (Self::Const, Self::Const)*/ => true,
|
||||
// (Self::Export, Self::Export) | (Self::Import, Self::Import) => true,
|
||||
// (Self::Macro, Self::Macro) | (Self::Module, Self::Module) => true,
|
||||
(Self::NS, Self::NS) | (Self::Type, Self::Type) => true,
|
||||
(Self::Walrus, Self::Walrus) => true,
|
||||
(Self::Atom(a1), Self::Atom(a2)) => a1.0.strict_eq(&a2.0),
|
||||
(Self::Comment(c1), Self::Comment(c2)) => c1 == c2,
|
||||
(Self::LP(p1), Self::LP(p2)) | (Self::RP(p1), Self::RP(p2)) => p1 == p2,
|
||||
(Self::Name(n1), Self::Name(n2)) => n1 == n2,
|
||||
(Self::Placeh(ph1), Self::Placeh(ph2)) => ph1 == ph2,
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct LexedText(pub Vec<Entry>);
|
||||
#[allow(unused)]
|
||||
pub fn format(lexed: &[Entry]) -> String { lexed.iter().join(" ") }
|
||||
|
||||
impl Display for LexedText {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0.iter().join(" "))
|
||||
pub fn namechar(c: char) -> bool { c.is_alphanumeric() | (c == '_') }
|
||||
pub fn namestart(c: char) -> bool { c.is_alphabetic() | (c == '_') }
|
||||
pub fn opchar(c: char) -> bool {
|
||||
!namestart(c) && !numstart(c) && !c.is_whitespace() && !"()[]{},".contains(c)
|
||||
}
|
||||
|
||||
pub fn split_filter(
|
||||
s: &str,
|
||||
mut pred: impl FnMut(char) -> bool,
|
||||
) -> (&str, &str) {
|
||||
s.find(|c| !pred(c)).map_or((s, ""), |i| s.split_at(i))
|
||||
}
|
||||
|
||||
fn lit_table() -> impl IntoIterator<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]
|
||||
fn paren_parser(lp: char, rp: char) -> impl SimpleParser<char, Lexeme> {
|
||||
just(lp).to(Lexeme::LP(lp)).or(just(rp).to(Lexeme::RP(lp)))
|
||||
if let Some(tail) = data.strip_prefix(',') {
|
||||
let lexeme = Lexeme::Name(ctx.interner().i(","));
|
||||
tokens.push(Entry::new(ctx.location(1, tail), lexeme));
|
||||
data = tail;
|
||||
continue 'tail;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn literal_parser<'a>(
|
||||
ctx: impl Context + 'a,
|
||||
) -> impl SimpleParser<char, Literal> + 'a {
|
||||
choice((
|
||||
// all ints are valid floats so it takes precedence
|
||||
number::int_parser().map(Literal::Uint),
|
||||
number::float_parser().map(Literal::Num),
|
||||
string::str_parser()
|
||||
.map(move |s| Literal::Str(ctx.interner().i(&s).into())),
|
||||
))
|
||||
if let Some(tail) = data.strip_prefix("--[") {
|
||||
let (note, tail) = (tail.split_once("]--"))
|
||||
.ok_or_else(|| NoCommentEnd(ctx.location(tail.len(), "")).rc())?;
|
||||
let lexeme = Lexeme::Comment(Arc::new(note.to_string()));
|
||||
let location = ctx.location(note.len() + 3, tail);
|
||||
tokens.push(Entry::new(location, lexeme));
|
||||
data = tail;
|
||||
continue 'tail;
|
||||
}
|
||||
|
||||
pub static BASE_OPS: &[&str] = &[",", ".", "..", "...", "*"];
|
||||
|
||||
#[must_use]
|
||||
pub fn lexer<'a>(
|
||||
ctx: impl Context + 'a,
|
||||
source: Rc<String>,
|
||||
) -> impl SimpleParser<char, Vec<Entry>> + 'a {
|
||||
let all_ops = ctx
|
||||
.ops()
|
||||
.iter()
|
||||
.map(|op| op.as_ref())
|
||||
.chain(BASE_OPS.iter().cloned())
|
||||
.map(str::to_string)
|
||||
.collect::<Vec<_>>();
|
||||
choice((
|
||||
keyword("export").to(Lexeme::Export),
|
||||
keyword("module").to(Lexeme::Module),
|
||||
keyword("import").to(Lexeme::Import),
|
||||
keyword("macro").to(Lexeme::Macro),
|
||||
keyword("const").to(Lexeme::Const),
|
||||
operators_parser({
|
||||
let ctx = ctx.clone();
|
||||
move |s| ctx.interner().i(&s)
|
||||
if let Some(tail) = data.strip_prefix("--") {
|
||||
let (note, tail) = split_filter(tail, |c| c != '\n');
|
||||
let lexeme = Lexeme::Comment(Arc::new(note.to_string()));
|
||||
let location = ctx.location(note.len(), tail);
|
||||
tokens.push(Entry::new(location, lexeme));
|
||||
data = tail;
|
||||
continue 'tail;
|
||||
}
|
||||
if let Some(tail) = data.strip_prefix('=') {
|
||||
if tail.chars().next().map_or(false, numstart) {
|
||||
let (num, post_num) = split_filter(tail, numchar);
|
||||
if let Some(tail) = post_num.strip_prefix("=>") {
|
||||
let lexeme = Lexeme::Arrow(parse_num(num).map_err(|e| e.into_proj(num.len(), post_num, ctx))?.as_float());
|
||||
let location = ctx.location(num.len() + 3, tail);
|
||||
tokens.push(Entry::new(location, lexeme));
|
||||
data = tail;
|
||||
continue 'tail;
|
||||
}
|
||||
}
|
||||
}
|
||||
// todo: parse placeholders, don't forget vectorials!
|
||||
if let Some(tail) = data.strip_prefix('$') {
|
||||
let (name, tail) = split_filter(tail, namechar);
|
||||
if !name.is_empty() {
|
||||
let name = ctx.interner().i(name);
|
||||
let location = ctx.location(name.len() + 1, tail);
|
||||
let lexeme = Lexeme::Placeh(Placeholder { name, class: PHClass::Scalar });
|
||||
tokens.push(Entry::new(location, lexeme));
|
||||
data = tail;
|
||||
continue 'tail;
|
||||
}
|
||||
}
|
||||
if let Some(vec) = data.strip_prefix("..") {
|
||||
let (nonzero, tail) =
|
||||
vec.strip_prefix('.').map_or((false, vec), |t| (true, t));
|
||||
if let Some(tail) = tail.strip_prefix('$') {
|
||||
let (name, tail) = split_filter(tail, namechar);
|
||||
if !name.is_empty() {
|
||||
let (prio, priolen, tail) = tail
|
||||
.strip_prefix(':')
|
||||
.map(|tail| split_filter(tail, numchar))
|
||||
.filter(|(num, _)| !num.is_empty())
|
||||
.map(|(num_str, tail)| {
|
||||
parse_num(num_str)
|
||||
.map_err(|e| e.into_proj(num_str.len(), tail, ctx))
|
||||
.and_then(|num| {
|
||||
Ok(unwrap_or!(num => Numeric::Uint; {
|
||||
return Err(FloatPlacehPrio(ctx.location(num_str.len(), tail)).rc())
|
||||
}))
|
||||
})
|
||||
.map(|v| Lexeme::Operators(Rc::new(v))),
|
||||
paren_parser('(', ')'),
|
||||
paren_parser('[', ']'),
|
||||
paren_parser('{', '}'),
|
||||
just(":=").to(Lexeme::Walrus),
|
||||
just("=")
|
||||
.ignore_then(number::float_parser())
|
||||
.then_ignore(just("=>"))
|
||||
.map(Lexeme::rule),
|
||||
comment::comment_parser().map(|s| Lexeme::Comment(Rc::new(s))),
|
||||
placeholder::placeholder_parser(ctx.clone()).map(Lexeme::Placeh),
|
||||
just("::").to(Lexeme::NS),
|
||||
just('\\').to(Lexeme::BS),
|
||||
just('@').to(Lexeme::At),
|
||||
just(':').to(Lexeme::Type),
|
||||
just('\n').to(Lexeme::BR),
|
||||
// just('.').to(Lexeme::Dot),
|
||||
literal_parser(ctx.clone()).map(Lexeme::Literal),
|
||||
name::name_parser(&all_ops).map({
|
||||
let ctx = ctx.clone();
|
||||
move |n| Lexeme::Name(ctx.interner().i(&n))
|
||||
}),
|
||||
))
|
||||
.map_with_span(move |lexeme, range| Entry {
|
||||
lexeme,
|
||||
location: Location::Range {
|
||||
range,
|
||||
file: ctx.file(),
|
||||
source: source.clone(),
|
||||
},
|
||||
.map(|p| (p, num_str.len() + 1, tail))
|
||||
})
|
||||
.padded_by(one_of(" \t").repeated())
|
||||
.repeated()
|
||||
.then_ignore(end())
|
||||
.unwrap_or(Ok((0, 0, tail)))?;
|
||||
let byte_len = if nonzero { 4 } else { 3 } + priolen + name.len();
|
||||
let name = ctx.interner().i(name);
|
||||
let class = PHClass::Vec { nonzero, prio };
|
||||
let lexeme = Lexeme::Placeh(Placeholder { name, class });
|
||||
tokens.push(Entry::new(ctx.location(byte_len, tail), lexeme));
|
||||
data = tail;
|
||||
continue 'tail;
|
||||
}
|
||||
}
|
||||
}
|
||||
if namestart(head) {
|
||||
let (name, tail) = split_filter(data, namechar);
|
||||
if !name.is_empty() {
|
||||
let lexeme = Lexeme::Name(ctx.interner().i(name));
|
||||
tokens.push(Entry::new(ctx.location(name.len(), tail), lexeme));
|
||||
data = tail;
|
||||
continue 'tail;
|
||||
}
|
||||
}
|
||||
if opchar(head) {
|
||||
let (name, tail) = split_filter(data, opchar);
|
||||
if !name.is_empty() {
|
||||
let lexeme = Lexeme::Name(ctx.interner().i(name));
|
||||
tokens.push(Entry::new(ctx.location(name.len(), tail), lexeme));
|
||||
data = tail;
|
||||
continue 'tail;
|
||||
}
|
||||
}
|
||||
unreachable!(r#"opchar is pretty much defined as "not namechar" "#)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
mod comment;
|
||||
mod context;
|
||||
mod decls;
|
||||
mod errors;
|
||||
mod facade;
|
||||
mod lexer;
|
||||
mod multiname;
|
||||
mod name;
|
||||
mod number;
|
||||
mod operators;
|
||||
mod placeholder;
|
||||
mod numeric;
|
||||
mod sourcefile;
|
||||
mod stream;
|
||||
mod string;
|
||||
|
||||
pub use context::ParsingContext;
|
||||
pub use context::{ParsingContext, Context, LexerPlugin, LineParser};
|
||||
pub use facade::parse2;
|
||||
pub use lexer::{lexer, Entry, Lexeme};
|
||||
pub use number::{float_parser, int_parser, print_nat16};
|
||||
pub use lexer::{namechar, namestart, opchar, split_filter, Entry, Lexeme};
|
||||
pub use numeric::{
|
||||
lex_numeric, numchar, numstart, parse_num, print_nat16, NumError,
|
||||
NumErrorKind,
|
||||
};
|
||||
pub use string::{lex_string, parse_string, StringError, StringErrorKind};
|
||||
|
||||
@@ -41,12 +41,12 @@ impl Subresult {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_multiname_branch(
|
||||
cursor: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
) -> ProjectResult<(BoxedIter<Subresult>, Stream<'_>)> {
|
||||
fn parse_multiname_branch<'a>(
|
||||
cursor: Stream<'a>,
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<(BoxedIter<'a, Subresult>, Stream<'a>)> {
|
||||
let comma = ctx.interner().i(",");
|
||||
let (subnames, cursor) = parse_multiname_rec(cursor, ctx.clone())?;
|
||||
let (subnames, cursor) = parse_multiname_rec(cursor, ctx)?;
|
||||
let (delim, cursor) = cursor.trim().pop()?;
|
||||
match &delim.lexeme {
|
||||
Lexeme::Name(n) if n == &comma => {
|
||||
@@ -65,10 +65,10 @@ fn parse_multiname_branch(
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_multiname_rec(
|
||||
curosr: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
) -> ProjectResult<(BoxedIter<Subresult>, Stream<'_>)> {
|
||||
fn parse_multiname_rec<'a>(
|
||||
curosr: Stream<'a>,
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<(BoxedIter<'a, Subresult>, Stream<'a>)> {
|
||||
let star = ctx.interner().i("*");
|
||||
let comma = ctx.interner().i(",");
|
||||
let (head, mut cursor) = curosr.trim().pop()?;
|
||||
@@ -103,7 +103,7 @@ fn parse_multiname_rec(
|
||||
Ok((box_once(Subresult::new_glob(head.location())), cursor)),
|
||||
Lexeme::Name(n) if ![comma, star].contains(n) => {
|
||||
let cursor = cursor.trim();
|
||||
if cursor.get(0).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) {
|
||||
if cursor.get(0).map_or(false, |e| e.lexeme.strict_eq(&Lexeme::NS)) {
|
||||
let cursor = cursor.step()?;
|
||||
let (out, cursor) = parse_multiname_rec(cursor, ctx)?;
|
||||
let out = Box::new(out.map(|sr| sr.push_front(n.clone())));
|
||||
@@ -123,10 +123,10 @@ fn parse_multiname_rec(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_multiname(
|
||||
cursor: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
) -> ProjectResult<(Vec<Import>, Stream<'_>)> {
|
||||
pub fn parse_multiname<'a>(
|
||||
cursor: Stream<'a>,
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<(Vec<Import>, Stream<'a>)> {
|
||||
let (output, cont) = parse_multiname_rec(cursor, ctx)?;
|
||||
Ok((output.map(|sr| sr.finalize()).collect(), cont))
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
148
src/parse/numeric.rs
Normal 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}")
|
||||
}
|
||||
@@ -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[$ |> =>]"))
|
||||
}
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -18,7 +18,6 @@ use crate::representations::location::Location;
|
||||
use crate::representations::sourcefile::{FileEntry, MemberKind, ModuleBlock};
|
||||
use crate::representations::VName;
|
||||
use crate::sourcefile::{FileEntryKind, Import, Member};
|
||||
use crate::Primitive;
|
||||
|
||||
pub fn split_lines(module: Stream<'_>) -> impl Iterator<Item = Stream<'_>> {
|
||||
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(
|
||||
cursor: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<Vec<FileEntry>> {
|
||||
split_lines(cursor)
|
||||
.map(Stream::trim)
|
||||
.filter(|l| !l.data.is_empty())
|
||||
.map(|l| {
|
||||
Ok(FileEntry {
|
||||
locations: vec![l.location()],
|
||||
kind: parse_line(l, ctx.clone())?,
|
||||
parse_line(l, ctx).map(move |kinds| {
|
||||
kinds
|
||||
.into_iter()
|
||||
.map(move |kind| FileEntry { locations: vec![l.location()], kind })
|
||||
})
|
||||
})
|
||||
.flatten_ok()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn parse_line(
|
||||
cursor: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
) -> ProjectResult<FileEntryKind> {
|
||||
match cursor.get(0)?.lexeme {
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<Vec<FileEntryKind>> {
|
||||
for line_parser in ctx.line_parsers() {
|
||||
if let Some(result) = line_parser(cursor, ctx) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
match &cursor.get(0)?.lexeme {
|
||||
Lexeme::BR | Lexeme::Comment(_) => parse_line(cursor.step()?, ctx),
|
||||
Lexeme::Export => parse_export_line(cursor.step()?, ctx),
|
||||
Lexeme::Const | Lexeme::Macro | Lexeme::Module | Lexeme::Operators(_) =>
|
||||
Ok(FileEntryKind::Member(Member {
|
||||
Lexeme::Name(n) if **n == "export" =>
|
||||
parse_export_line(cursor.step()?, ctx).map(|k| vec![k]),
|
||||
Lexeme::Name(n) if ["const", "macro", "module"].contains(&n.as_str()) =>
|
||||
Ok(vec![FileEntryKind::Member(Member {
|
||||
kind: parse_member(cursor, ctx)?,
|
||||
exported: false,
|
||||
})),
|
||||
Lexeme::Import => {
|
||||
})]),
|
||||
Lexeme::Name(n) if **n == "import" => {
|
||||
let (imports, cont) = parse_multiname(cursor.step()?, ctx)?;
|
||||
cont.expect_empty()?;
|
||||
Ok(FileEntryKind::Import(imports))
|
||||
Ok(vec![FileEntryKind::Import(imports)])
|
||||
},
|
||||
_ => {
|
||||
let err = BadTokenInRegion {
|
||||
@@ -95,23 +102,23 @@ pub fn parse_line(
|
||||
|
||||
pub fn parse_export_line(
|
||||
cursor: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<FileEntryKind> {
|
||||
let cursor = cursor.trim();
|
||||
match cursor.get(0)?.lexeme {
|
||||
match &cursor.get(0)?.lexeme {
|
||||
Lexeme::NS => {
|
||||
let (names, cont) = parse_multiname(cursor.step()?, ctx)?;
|
||||
cont.expect_empty()?;
|
||||
let names = (names.into_iter())
|
||||
.map(|Import { name, path, location }| match (name, &path[..]) {
|
||||
(Some(n), []) => Ok((n, location)),
|
||||
(None, _) => Err(GlobExport { location }.rc()),
|
||||
_ => Err(NamespacedExport { location }.rc()),
|
||||
(None, _) => Err(GlobExport(location).rc()),
|
||||
_ => Err(NamespacedExport(location).rc()),
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(FileEntryKind::Export(names))
|
||||
},
|
||||
Lexeme::Const | Lexeme::Macro | Lexeme::Module | Lexeme::Operators(_) =>
|
||||
Lexeme::Name(n) if ["const", "macro", "module"].contains(&n.as_str()) =>
|
||||
Ok(FileEntryKind::Member(Member {
|
||||
kind: parse_member(cursor, ctx)?,
|
||||
exported: true,
|
||||
@@ -128,26 +135,22 @@ pub fn parse_export_line(
|
||||
|
||||
fn parse_member(
|
||||
cursor: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<MemberKind> {
|
||||
let (typemark, cursor) = cursor.trim().pop()?;
|
||||
match &typemark.lexeme {
|
||||
Lexeme::Const => {
|
||||
Lexeme::Name(n) if **n == "const" => {
|
||||
let constant = parse_const(cursor, ctx)?;
|
||||
Ok(MemberKind::Constant(constant))
|
||||
},
|
||||
Lexeme::Macro => {
|
||||
Lexeme::Name(n) if **n == "macro" => {
|
||||
let rule = parse_rule(cursor, ctx)?;
|
||||
Ok(MemberKind::Rule(rule))
|
||||
},
|
||||
Lexeme::Module => {
|
||||
Lexeme::Name(n) if **n == "module" => {
|
||||
let module = parse_module(cursor, ctx)?;
|
||||
Ok(MemberKind::Module(module))
|
||||
},
|
||||
Lexeme::Operators(ops) => {
|
||||
cursor.trim().expect_empty()?;
|
||||
Ok(MemberKind::Operators(ops[..].to_vec()))
|
||||
},
|
||||
_ => {
|
||||
let err =
|
||||
BadTokenInRegion { entry: typemark.clone(), region: "member type" };
|
||||
@@ -158,20 +161,20 @@ fn parse_member(
|
||||
|
||||
fn parse_rule(
|
||||
cursor: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<Rule<VName>> {
|
||||
let (pattern, prio, template) = cursor.find_map("arrow", |a| match a {
|
||||
Lexeme::Arrow(p) => Some(*p),
|
||||
_ => None,
|
||||
})?;
|
||||
let (pattern, _) = parse_exprv(pattern, None, ctx.clone())?;
|
||||
let (pattern, _) = parse_exprv(pattern, None, ctx)?;
|
||||
let (template, _) = parse_exprv(template, None, ctx)?;
|
||||
Ok(Rule { pattern, prio, template })
|
||||
}
|
||||
|
||||
fn parse_const(
|
||||
cursor: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<Constant> {
|
||||
let (name_ent, cursor) = cursor.trim().pop()?;
|
||||
let name = ExpectedName::expect(name_ent)?;
|
||||
@@ -183,7 +186,7 @@ fn parse_const(
|
||||
|
||||
fn parse_module(
|
||||
cursor: Stream<'_>,
|
||||
ctx: impl Context,
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<ModuleBlock> {
|
||||
let (name_ent, cursor) = cursor.trim().pop()?;
|
||||
let name = ExpectedName::expect(name_ent)?;
|
||||
@@ -195,11 +198,11 @@ fn parse_module(
|
||||
Ok(ModuleBlock { name, body })
|
||||
}
|
||||
|
||||
fn parse_exprv(
|
||||
mut cursor: Stream<'_>,
|
||||
fn parse_exprv<'a>(
|
||||
mut cursor: Stream<'a>,
|
||||
paren: Option<char>,
|
||||
ctx: impl Context,
|
||||
) -> ProjectResult<(Vec<Expr<VName>>, Stream<'_>)> {
|
||||
ctx: &impl Context,
|
||||
) -> ProjectResult<(Vec<Expr<VName>>, Stream<'a>)> {
|
||||
let mut output = Vec::new();
|
||||
cursor = cursor.trim();
|
||||
while let Ok(current) = cursor.get(0) {
|
||||
@@ -207,11 +210,9 @@ fn parse_exprv(
|
||||
Lexeme::BR | Lexeme::Comment(_) => unreachable!("Fillers skipped"),
|
||||
Lexeme::At | Lexeme::Type =>
|
||||
return Err(ReservedToken { entry: current.clone() }.rc()),
|
||||
Lexeme::Literal(l) => {
|
||||
output.push(Expr {
|
||||
value: Clause::P(Primitive::Literal(l.clone())),
|
||||
location: current.location(),
|
||||
});
|
||||
Lexeme::Atom(a) => {
|
||||
let value = Clause::Atom(a.clone());
|
||||
output.push(Expr { value, location: current.location() });
|
||||
cursor = cursor.step()?;
|
||||
},
|
||||
Lexeme::Placeh(ph) => {
|
||||
@@ -223,25 +224,23 @@ fn parse_exprv(
|
||||
},
|
||||
Lexeme::Name(n) => {
|
||||
let location = cursor.location();
|
||||
let mut fullname = vec![n.clone()];
|
||||
while cursor.get(1).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) {
|
||||
let mut fullname: VName = vec![n.clone()];
|
||||
while cursor.get(1).map_or(false, |e| e.lexeme.strict_eq(&Lexeme::NS)) {
|
||||
fullname.push(ExpectedName::expect(cursor.get(2)?)?);
|
||||
cursor = cursor.step()?.step()?;
|
||||
}
|
||||
output.push(Expr { value: Clause::Name(fullname), location });
|
||||
cursor = cursor.step()?;
|
||||
},
|
||||
Lexeme::NS =>
|
||||
return Err(LeadingNS { location: current.location() }.rc()),
|
||||
Lexeme::NS => return Err(LeadingNS(current.location()).rc()),
|
||||
Lexeme::RP(c) =>
|
||||
return if Some(*c) == paren {
|
||||
Ok((output, cursor.step()?))
|
||||
} else {
|
||||
Err(MisalignedParen { entry: cursor.get(0)?.clone() }.rc())
|
||||
Err(MisalignedParen(cursor.get(0)?.clone()).rc())
|
||||
},
|
||||
Lexeme::LP(c) => {
|
||||
let (result, leftover) =
|
||||
parse_exprv(cursor.step()?, Some(*c), ctx.clone())?;
|
||||
let (result, leftover) = parse_exprv(cursor.step()?, Some(*c), ctx)?;
|
||||
output.push(Expr {
|
||||
value: Clause::S(*c, Rc::new(result)),
|
||||
location: cursor.get(0)?.location().to(leftover.fallback.location()),
|
||||
@@ -250,9 +249,9 @@ fn parse_exprv(
|
||||
},
|
||||
Lexeme::BS => {
|
||||
let dot = ctx.interner().i(".");
|
||||
let (arg, body) =
|
||||
cursor.step()?.find("A '.'", |l| l == &Lexeme::Name(dot.clone()))?;
|
||||
let (arg, _) = parse_exprv(arg, None, ctx.clone())?;
|
||||
let (arg, body) = (cursor.step())?
|
||||
.find("A '.'", |l| l.strict_eq(&Lexeme::Name(dot.clone())))?;
|
||||
let (arg, _) = parse_exprv(arg, None, ctx)?;
|
||||
let (body, leftover) = parse_exprv(body, paren, ctx)?;
|
||||
output.push(Expr {
|
||||
location: cursor.location(),
|
||||
@@ -278,7 +277,7 @@ fn vec_to_single(
|
||||
v: Vec<Expr<VName>>,
|
||||
) -> ProjectResult<Expr<VName>> {
|
||||
match v.len() {
|
||||
0 => return Err(UnexpectedEOL { entry: fallback.clone() }.rc()),
|
||||
0 => Err(UnexpectedEOL { entry: fallback.clone() }.rc()),
|
||||
1 => Ok(v.into_iter().exactly_one().unwrap()),
|
||||
_ => Ok(Expr {
|
||||
location: expr_slice_location(&v),
|
||||
|
||||
@@ -1,50 +1,117 @@
|
||||
use chumsky::prelude::*;
|
||||
use chumsky::{self, Parser};
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::decls::SimpleParser;
|
||||
use super::context::Context;
|
||||
use super::errors::{BadCodePoint, BadEscapeSequence, NoStringEnd, NotHex};
|
||||
use crate::error::{ProjectError, ProjectResult};
|
||||
use crate::foreign::Atom;
|
||||
use crate::OrcString;
|
||||
|
||||
/// Parses a text character that is not the specified delimiter
|
||||
#[must_use]
|
||||
fn text_parser(delim: char) -> impl SimpleParser<char, char> {
|
||||
// Copied directly from Chumsky's JSON example.
|
||||
let escape = just('\\').ignore_then(
|
||||
just('\\')
|
||||
.or(just('/'))
|
||||
.or(just('"'))
|
||||
.or(just('b').to('\x08'))
|
||||
.or(just('f').to('\x0C'))
|
||||
.or(just('n').to('\n'))
|
||||
.or(just('r').to('\r'))
|
||||
.or(just('t').to('\t'))
|
||||
.or(
|
||||
just('u').ignore_then(
|
||||
filter(|c: &char| c.is_ascii_hexdigit())
|
||||
.repeated()
|
||||
.exactly(4)
|
||||
.collect::<String>()
|
||||
.validate(|digits, span, emit| {
|
||||
char::from_u32(u32::from_str_radix(&digits, 16).unwrap())
|
||||
.unwrap_or_else(|| {
|
||||
emit(Simple::custom(span, "invalid unicode character"));
|
||||
'\u{FFFD}' // unicode replacement character
|
||||
pub enum StringErrorKind {
|
||||
NotHex,
|
||||
BadCodePoint,
|
||||
BadEscSeq,
|
||||
}
|
||||
|
||||
pub struct StringError {
|
||||
pos: usize,
|
||||
kind: StringErrorKind,
|
||||
}
|
||||
|
||||
pub fn parse_string(str: &str) -> Result<String, StringError> {
|
||||
let mut target = String::new();
|
||||
let mut iter = str.char_indices();
|
||||
while let Some((_, c)) = iter.next() {
|
||||
if c != '\\' {
|
||||
target.push(c);
|
||||
continue;
|
||||
}
|
||||
let (mut pos, code) = iter.next().expect("lexer would have continued");
|
||||
let next = match code {
|
||||
c @ ('\\' | '/' | '"') => c,
|
||||
'b' => '\x08',
|
||||
'f' => '\x0f',
|
||||
'n' => '\n',
|
||||
'r' => '\r',
|
||||
't' => '\t',
|
||||
'\n' => 'skipws: loop {
|
||||
match iter.next() {
|
||||
None => return Ok(target),
|
||||
Some((_, c)) =>
|
||||
if !c.is_whitespace() {
|
||||
break 'skipws c;
|
||||
},
|
||||
}
|
||||
},
|
||||
'u' => {
|
||||
let acc = ((0..4).rev())
|
||||
.map(|radical| {
|
||||
let (j, c) = (iter.next())
|
||||
.ok_or(StringError { pos, kind: StringErrorKind::NotHex })?;
|
||||
pos = j;
|
||||
let b =
|
||||
u32::from_str_radix(&String::from(c), 16).map_err(|_| {
|
||||
StringError { pos, kind: StringErrorKind::NotHex }
|
||||
})?;
|
||||
Ok(16u32.pow(radical) + b)
|
||||
})
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
filter(move |&c| c != '\\' && c != delim).or(escape)
|
||||
.fold_ok(0, u32::wrapping_add)?;
|
||||
char::from_u32(acc)
|
||||
.ok_or(StringError { pos, kind: StringErrorKind::BadCodePoint })?
|
||||
},
|
||||
_ => return Err(StringError { pos, kind: StringErrorKind::BadEscSeq }),
|
||||
};
|
||||
target.push(next);
|
||||
}
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
/// Parse a string between double quotes
|
||||
#[must_use]
|
||||
pub fn str_parser() -> impl SimpleParser<char, String> {
|
||||
just('"')
|
||||
.ignore_then(
|
||||
text_parser('"').map(Some)
|
||||
.or(just("\\\n").then(just(' ').or(just('\t')).repeated()).map(|_| None)) // Newlines preceded by backslashes are ignored along with all following indentation.
|
||||
.repeated(),
|
||||
)
|
||||
.then_ignore(just('"'))
|
||||
.flatten()
|
||||
.collect()
|
||||
pub fn lex_string<'a>(
|
||||
data: &'a str,
|
||||
ctx: &dyn Context,
|
||||
) -> Option<ProjectResult<(Atom, &'a str)>> {
|
||||
data.strip_prefix('"').map(|data| {
|
||||
let mut leftover = data;
|
||||
return loop {
|
||||
let (inside, outside) = (leftover.split_once('"'))
|
||||
.ok_or_else(|| NoStringEnd(ctx.location(data.len(), "")).rc())?;
|
||||
let backslashes = inside.chars().rev().take_while(|c| *c == '\\').count();
|
||||
if backslashes % 2 == 0 {
|
||||
// cut form tail to recoup what string_content doesn't have
|
||||
let (string_data, tail) = data.split_at(data.len() - outside.len() - 1);
|
||||
let tail = &tail[1..]; // push the tail past the end quote
|
||||
let string = parse_string(string_data).map_err(|e| {
|
||||
let start = ctx.pos(data) + e.pos;
|
||||
let location = ctx.range_loc(start..start + 1);
|
||||
match e.kind {
|
||||
StringErrorKind::NotHex => NotHex(location).rc(),
|
||||
StringErrorKind::BadCodePoint => BadCodePoint(location).rc(),
|
||||
StringErrorKind::BadEscSeq => BadEscapeSequence(location).rc(),
|
||||
}
|
||||
})?;
|
||||
let tok = ctx.interner().i(&string);
|
||||
break Ok((Atom::new(OrcString::from(tok)), tail));
|
||||
} else {
|
||||
leftover = outside;
|
||||
}
|
||||
};
|
||||
})
|
||||
}
|
||||
// TODO: rewrite the tree building pipeline step to load files
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::lex_string;
|
||||
use crate::parse::context::MockContext;
|
||||
use crate::{Interner, OrcString};
|
||||
|
||||
#[test]
|
||||
fn plain_string() {
|
||||
let source = r#""hello world!" - says the programmer"#;
|
||||
let i = Interner::new();
|
||||
let (data, tail) = lex_string(source, &MockContext(&i))
|
||||
.expect("the snippet starts with a quote")
|
||||
.expect("it contains a valid string");
|
||||
assert_eq!(data.try_downcast::<OrcString>().unwrap().as_str(), "hello world!");
|
||||
assert_eq!(tail, " - says the programmer");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::representations::project::{
|
||||
ItemKind, ProjectExt, ProjectItem, ProjectMod,
|
||||
};
|
||||
use crate::tree::{ModEntry, ModMember, Module};
|
||||
use crate::utils::pure_push::pushed;
|
||||
use crate::utils::pure_seq::pushed;
|
||||
use crate::{Interner, ProjectTree, Tok, VName};
|
||||
|
||||
#[must_use]
|
||||
@@ -74,7 +74,6 @@ fn resolve_aliases_rec(
|
||||
ModMember::Sub(module) =>
|
||||
ModMember::Sub(resolve_aliases_rec(root, module, updated, false)),
|
||||
ModMember::Item(item) => ModMember::Item(ProjectItem {
|
||||
is_op: item.is_op,
|
||||
kind: match &item.kind {
|
||||
ItemKind::Const(value) => ItemKind::Const(process_expr(value)),
|
||||
other => other.clone(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Source loader callback definition and builtin implementations
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, io};
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
@@ -25,7 +25,7 @@ impl ProjectError for FileLoadingError {
|
||||
"Neither a file nor a directory could be read from the requested path"
|
||||
}
|
||||
fn one_position(&self) -> crate::Location {
|
||||
Location::File(Rc::new(self.path.clone()))
|
||||
Location::File(Arc::new(self.path.clone()))
|
||||
}
|
||||
fn message(&self) -> String {
|
||||
format!("File: {}\nDirectory: {}", self.file, self.dir)
|
||||
@@ -37,10 +37,10 @@ impl ProjectError for FileLoadingError {
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Loaded {
|
||||
/// Conceptually equivalent to a sourcefile
|
||||
Code(Rc<String>),
|
||||
Code(Arc<String>),
|
||||
/// Conceptually equivalent to the list of *.orc files in a folder, without
|
||||
/// the extension
|
||||
Collection(Rc<Vec<String>>),
|
||||
Collection(Arc<Vec<String>>),
|
||||
}
|
||||
impl Loaded {
|
||||
/// 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 file_path = full_path.with_extension("orc");
|
||||
let file_error = match fs::read_to_string(file_path) {
|
||||
Ok(string) => return Ok(Loaded::Code(Rc::new(string))),
|
||||
Ok(string) => return Ok(Loaded::Code(Arc::new(string))),
|
||||
Err(err) => err,
|
||||
};
|
||||
let dir = match fs::read_dir(&full_path) {
|
||||
@@ -83,7 +83,7 @@ pub fn load_file(root: &Path, path: &[Tok<String>]) -> IOResult {
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
Ok(Loaded::Collection(Rc::new(names)))
|
||||
Ok(Loaded::Collection(Arc::new(names)))
|
||||
}
|
||||
|
||||
/// Generates a cached file loader for a directory
|
||||
@@ -102,7 +102,7 @@ pub fn load_embed<T: 'static + RustEmbed>(path: &str, ext: &str) -> IOResult {
|
||||
if let Some(file) = T::get(&file_path) {
|
||||
let s =
|
||||
String::from_utf8(file.data.to_vec()).expect("Embed must be valid UTF-8");
|
||||
Ok(Loaded::Code(Rc::new(s)))
|
||||
Ok(Loaded::Code(Arc::new(s)))
|
||||
} else {
|
||||
let entries = T::iter()
|
||||
.map(|c| c.to_string())
|
||||
@@ -121,7 +121,7 @@ pub fn load_embed<T: 'static + RustEmbed>(path: &str, ext: &str) -> IOResult {
|
||||
})
|
||||
})
|
||||
.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())
|
||||
.map(|(k, s)| (k, Loaded::Code(Rc::new(s))))
|
||||
.map(|(k, s)| (k, Loaded::Code(Arc::new(s))))
|
||||
.chain((dirs.into_iter()).map(|(k, entv)| {
|
||||
(k, Loaded::Collection(Rc::new(entv.into_iter().collect())))
|
||||
(k, Loaded::Collection(Arc::new(entv.into_iter().collect())))
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@ mod import_abs_path;
|
||||
mod parse_layer;
|
||||
mod project_tree;
|
||||
mod source_loader;
|
||||
// mod tree_loader;
|
||||
|
||||
pub use parse_layer::parse_layer;
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::file_loader::IOResult;
|
||||
use super::{project_tree, source_loader};
|
||||
use crate::error::ProjectResult;
|
||||
use crate::interner::{Interner, Tok};
|
||||
use crate::parse::{LexerPlugin, LineParser};
|
||||
use crate::representations::sourcefile::FileEntry;
|
||||
use crate::representations::VName;
|
||||
use crate::utils::never;
|
||||
@@ -21,10 +22,14 @@ pub fn parse_layer<'a>(
|
||||
loader: &impl Fn(&[Tok<String>]) -> IOResult,
|
||||
environment: &'a ProjectTree<VName>,
|
||||
prelude: &[FileEntry],
|
||||
lexer_plugins: &[&dyn LexerPlugin],
|
||||
line_parsers: &[&dyn LineParser],
|
||||
i: &Interner,
|
||||
) -> ProjectResult<ProjectTree<VName>> {
|
||||
let sl_ctx =
|
||||
source_loader::Context { prelude, i, lexer_plugins, line_parsers };
|
||||
let (preparsed, source) =
|
||||
source_loader::load_source(targets, prelude, i, loader, &|path| {
|
||||
source_loader::load_source(targets, sl_ctx, loader, &|path| {
|
||||
environment.0.walk_ref(&[], path, false).is_ok()
|
||||
})?;
|
||||
let tree =
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::sourcefile::{
|
||||
};
|
||||
use crate::tree::{ModEntry, ModMember, Module};
|
||||
use crate::utils::get_or::get_or_default;
|
||||
use crate::utils::pure_push::pushed_ref;
|
||||
use crate::utils::pure_seq::pushed_ref;
|
||||
use crate::{Tok, VName};
|
||||
|
||||
#[must_use = "A submodule may not be integrated into the tree"]
|
||||
@@ -28,7 +28,7 @@ pub struct TreeReport {
|
||||
pub fn build_tree(
|
||||
path: &VName,
|
||||
source: Vec<FileEntry>,
|
||||
Module { entries, extra }: PreMod,
|
||||
Module { entries, .. }: PreMod,
|
||||
imports: ImpMod,
|
||||
prelude: &[FileEntry],
|
||||
) -> ProjectResult<TreeReport> {
|
||||
@@ -56,20 +56,11 @@ pub fn build_tree(
|
||||
MemberKind::Constant(Constant { name, value }) => {
|
||||
consts.insert(name, value /* .prefix(path, &|_| false) */);
|
||||
},
|
||||
MemberKind::Operators(_) => (),
|
||||
MemberKind::Rule(rule) => rule_fragments.push(rule),
|
||||
},
|
||||
}
|
||||
}
|
||||
let mod_details = extra.details().expect("Directories handled elsewhere");
|
||||
let rules = (mod_details.patterns.iter())
|
||||
.zip(rule_fragments.into_iter())
|
||||
.map(|(p, Rule { prio, template: t, .. })| {
|
||||
// let p = p.iter().map(|e| e.prefix(path, &|_| false)).collect();
|
||||
// let t = t.into_iter().map(|e| e.prefix(path, &|_| false)).collect();
|
||||
Rule { pattern: p.clone(), prio, template: t }
|
||||
})
|
||||
.collect();
|
||||
let rules = rule_fragments;
|
||||
let (pre_subs, pre_items) = (entries.into_iter())
|
||||
.partition_map::<HashMap<_, _>, HashMap<_, _>, _, _, _>(
|
||||
|(k, ModEntry { exported, member })| match member {
|
||||
@@ -98,7 +89,7 @@ pub fn build_tree(
|
||||
Ok((k, ModEntry { exported, member }))
|
||||
})
|
||||
.chain((pre_items.into_iter()).map(
|
||||
|(k, (exported, PreItem { has_value, is_op, location }))| {
|
||||
|(k, (exported, PreItem { has_value, location }))| {
|
||||
let item = match imports_from.get(&k) {
|
||||
Some(_) if has_value => {
|
||||
// Local value cannot be assigned to imported key
|
||||
@@ -112,12 +103,10 @@ pub fn build_tree(
|
||||
},
|
||||
None => {
|
||||
let k = consts.remove(&k).map_or(ItemKind::None, ItemKind::Const);
|
||||
ProjectItem { is_op, kind: k }
|
||||
},
|
||||
Some(report) => ProjectItem {
|
||||
is_op: is_op | report.is_op,
|
||||
kind: ItemKind::Alias(report.source.clone()),
|
||||
ProjectItem { kind: k }
|
||||
},
|
||||
Some(report) =>
|
||||
ProjectItem { kind: ItemKind::Alias(report.source.clone()) },
|
||||
};
|
||||
Ok((k, ModEntry { exported, member: ModMember::Item(item) }))
|
||||
},
|
||||
@@ -129,7 +118,6 @@ pub fn build_tree(
|
||||
exported: false,
|
||||
member: ModMember::Item(ProjectItem {
|
||||
kind: ItemKind::Alias(from.source.clone()),
|
||||
is_op: from.is_op,
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::representations::project::ImpReport;
|
||||
use crate::sourcefile::{absolute_path, Import};
|
||||
use crate::tree::{ErrKind, ModEntry, ModMember, Module, WalkError};
|
||||
use crate::utils::boxed_iter::{box_chain, box_once};
|
||||
use crate::utils::pure_push::pushed_ref;
|
||||
use crate::utils::pure_seq::pushed_ref;
|
||||
use crate::utils::{unwrap_or, BoxedIter};
|
||||
use crate::{Interner, ProjectTree, Tok, VName};
|
||||
|
||||
@@ -65,17 +65,13 @@ pub fn assert_visible_overlay<'a>(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_donor_module<'a, TItem: Clone>(
|
||||
module: &'a Module<TItem, impl Clone>,
|
||||
pub fn process_donor_module<TItem: Clone>(
|
||||
module: &Module<TItem, impl Clone>,
|
||||
abs_path: Rc<VName>,
|
||||
is_op: impl Fn(&TItem) -> bool + 'a,
|
||||
) -> impl Iterator<Item = (Tok<String>, VName, bool)> + 'a {
|
||||
(module.entries.iter()).filter(|(_, ent)| ent.exported).map(
|
||||
move |(n, ent)| {
|
||||
let is_op = ent.item().map_or(false, &is_op);
|
||||
(n.clone(), pushed_ref(abs_path.as_ref(), n.clone()), is_op)
|
||||
},
|
||||
)
|
||||
) -> impl Iterator<Item = (Tok<String>, VName)> + '_ {
|
||||
(module.entries.iter())
|
||||
.filter(|(_, ent)| ent.exported)
|
||||
.map(move |(n, _)| (n.clone(), pushed_ref(abs_path.as_ref(), n.clone())))
|
||||
}
|
||||
|
||||
pub fn import_tree(
|
||||
@@ -99,14 +95,7 @@ pub fn import_tree(
|
||||
// println!("Old root: {:#?}", &prev_root.0);
|
||||
panic!("{}", e.at(location))
|
||||
})?;
|
||||
let is_op = (root.0.walk1_ref(&[], &abs_path, false))
|
||||
.map(|(ent, _)| ent.item().map_or(false, |i| i.is_op))
|
||||
.or_else(|e| if e.kind == ErrKind::Missing {
|
||||
(prev_root.0.walk1_ref(&[], &abs_path, false))
|
||||
.map(|(ent, _)| ent.item().map_or(false, |i| i.is_op))
|
||||
} else {Err(e)})
|
||||
.map_err(|e| e.at(location))?;
|
||||
box_once((name.clone(), abs_path, is_op))
|
||||
box_once((name.clone(), abs_path))
|
||||
} else {
|
||||
let rc_path = Rc::new(abs_path);
|
||||
// wildcard imports are validated
|
||||
@@ -116,8 +105,7 @@ pub fn import_tree(
|
||||
let new_imports = match (root.0).walk_ref(&[], &rc_path, false) {
|
||||
Err(e) if e.kind == ErrKind::Missing => Err(e),
|
||||
Err(e) => return Err(e.at(location)),
|
||||
Ok(module)
|
||||
=> Ok(process_donor_module(module, rc_path.clone(), |i| i.is_op))
|
||||
Ok(module) => Ok(process_donor_module(module, rc_path.clone()))
|
||||
};
|
||||
let old_m = match (prev_root.0).walk_ref(&[], &rc_path, false) {
|
||||
Err(e) if e.kind != ErrKind::Missing => return Err(e.at(location)),
|
||||
@@ -134,7 +122,7 @@ pub fn import_tree(
|
||||
},
|
||||
Ok(old_m) => old_m,
|
||||
};
|
||||
let it1 = process_donor_module(old_m, rc_path.clone(), |i| i.is_op);
|
||||
let it1 = process_donor_module(old_m, rc_path.clone());
|
||||
match new_imports {
|
||||
Err(_) => Box::new(it1),
|
||||
Ok(it2) => box_chain!(it1, it2)
|
||||
@@ -144,10 +132,10 @@ pub fn import_tree(
|
||||
// leaf sets flattened to leaves
|
||||
.flatten_ok()
|
||||
// translated to entries
|
||||
.map_ok(|(name, source, is_op)| {
|
||||
.map_ok(|(name, source)| {
|
||||
(name, ModEntry {
|
||||
exported: false, // this is irrelevant but needed
|
||||
member: ModMember::Item(ImpReport { source, is_op }),
|
||||
member: ModMember::Item(ImpReport { source }),
|
||||
})
|
||||
})
|
||||
.chain(
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
|
||||
@@ -7,15 +5,14 @@ use super::build_tree::{build_tree, TreeReport};
|
||||
use super::import_tree::{import_tree, ImpMod};
|
||||
use crate::error::ProjectResult;
|
||||
use crate::pipeline::source_loader::{
|
||||
LoadedSourceTable, PreExtra, PreItem, PreMod, Preparsed,
|
||||
LoadedSourceTable, PreExtra, PreMod, Preparsed,
|
||||
};
|
||||
use crate::representations::project::{ImpReport, ProjectExt, ProjectMod};
|
||||
use crate::representations::project::{ProjectExt, ProjectMod};
|
||||
use crate::sourcefile::FileEntry;
|
||||
use crate::tree::{ModEntry, ModMember, Module};
|
||||
use crate::utils::never::{always, unwrap_always};
|
||||
use crate::utils::pure_push::pushed_ref;
|
||||
use crate::utils::pure_seq::pushed_ref;
|
||||
use crate::utils::unwrap_or;
|
||||
use crate::{parse, Interner, ProjectTree, Tok, VName};
|
||||
use crate::{Interner, ProjectTree, Tok, VName};
|
||||
|
||||
pub fn rebuild_file(
|
||||
path: Vec<Tok<String>>,
|
||||
@@ -23,35 +20,12 @@ pub fn rebuild_file(
|
||||
imports: ImpMod,
|
||||
source: &LoadedSourceTable,
|
||||
prelude: &[FileEntry],
|
||||
i: &Interner,
|
||||
) -> ProjectResult<ProjectMod<VName>> {
|
||||
let file = match &pre.extra {
|
||||
PreExtra::Dir => panic!("Dir should not hand this node off"),
|
||||
PreExtra::Submod(_) => panic!("should not have received this"),
|
||||
PreExtra::File(f) => f,
|
||||
};
|
||||
let mut ops = Vec::new();
|
||||
unwrap_always(imports.search_all((), &mut |_, module, ()| {
|
||||
ops.extend(
|
||||
(module.entries.iter())
|
||||
.filter(|(_, ent)| {
|
||||
matches!(ent.member, ModMember::Item(ImpReport { is_op: true, .. }))
|
||||
})
|
||||
.map(|(name, _)| name.clone()),
|
||||
);
|
||||
always(())
|
||||
}));
|
||||
unwrap_always(pre.search_all((), &mut |_, module, ()| {
|
||||
ops.extend(
|
||||
(module.entries.iter())
|
||||
.filter(|(_, ent)| {
|
||||
matches!(ent.member, ModMember::Item(PreItem { is_op: true, .. }))
|
||||
})
|
||||
.map(|(name, _)| name.clone()),
|
||||
);
|
||||
always(())
|
||||
}));
|
||||
let ctx = parse::ParsingContext::new(&ops, i, Rc::new(path.clone()));
|
||||
let src = source.get(&file.name).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"{} should have been preparsed already. Preparsed files are {}",
|
||||
@@ -62,13 +36,11 @@ pub fn rebuild_file(
|
||||
.join(", ")
|
||||
)
|
||||
});
|
||||
let entries = parse::parse2(&src.text, ctx)?;
|
||||
let TreeReport { entries: items, rules, imports_from } =
|
||||
let entries = src.entries.clone();
|
||||
let TreeReport { entries, rules, imports_from } =
|
||||
build_tree(&path, entries, pre, imports, prelude)?;
|
||||
Ok(Module {
|
||||
entries: items,
|
||||
extra: ProjectExt { file: Some(path.clone()), path, imports_from, rules },
|
||||
})
|
||||
let file = Some(path.clone());
|
||||
Ok(Module { entries, extra: ProjectExt { file, path, imports_from, rules } })
|
||||
}
|
||||
|
||||
pub fn rebuild_dir(
|
||||
@@ -77,15 +49,14 @@ pub fn rebuild_dir(
|
||||
mut imports: ImpMod,
|
||||
source: &LoadedSourceTable,
|
||||
prelude: &[FileEntry],
|
||||
i: &Interner,
|
||||
) -> ProjectResult<ProjectMod<VName>> {
|
||||
match pre.extra {
|
||||
PreExtra::Dir => (),
|
||||
PreExtra::File(_) =>
|
||||
return rebuild_file(path, pre, imports, source, prelude, i),
|
||||
return rebuild_file(path, pre, imports, source, prelude),
|
||||
PreExtra::Submod(_) => panic!("Dirs contain dirs and files"),
|
||||
}
|
||||
let items = (pre.entries.into_iter())
|
||||
let entries = (pre.entries.into_iter())
|
||||
.map(|(name, entry)| {
|
||||
match imports.entries.remove(&name).map(|e| e.member) {
|
||||
Some(ModMember::Sub(impmod)) => (name, entry, impmod),
|
||||
@@ -97,12 +68,8 @@ pub fn rebuild_dir(
|
||||
let pre = unwrap_or!(member => ModMember::Sub;
|
||||
panic!("Dirs can only contain submodules")
|
||||
);
|
||||
Ok((name, ModEntry {
|
||||
exported,
|
||||
member: ModMember::Sub(rebuild_dir(
|
||||
path, pre, impmod, source, prelude, i,
|
||||
)?),
|
||||
}))
|
||||
let module = rebuild_dir(path, pre, impmod, source, prelude)?;
|
||||
Ok((name, ModEntry { exported, member: ModMember::Sub(module) }))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, _>>()?;
|
||||
Ok(Module {
|
||||
@@ -112,7 +79,7 @@ pub fn rebuild_dir(
|
||||
rules: Vec::new(),
|
||||
file: None,
|
||||
},
|
||||
entries: items,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -126,6 +93,6 @@ pub fn rebuild_tree(
|
||||
) -> ProjectResult<ProjectTree<VName>> {
|
||||
let imports =
|
||||
import_tree(Vec::new(), &preparsed.0, &preparsed, prev_root, i)?;
|
||||
rebuild_dir(Vec::new(), preparsed.0, imports, source, prelude, i)
|
||||
rebuild_dir(Vec::new(), preparsed.0, imports, source, prelude)
|
||||
.map(ProjectTree)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::loaded_source::{LoadedSource, LoadedSourceTable};
|
||||
@@ -7,24 +9,32 @@ use crate::error::{
|
||||
NoTargets, ProjectError, ProjectResult, UnexpectedDirectory,
|
||||
};
|
||||
use crate::interner::{Interner, Tok};
|
||||
use crate::parse::{self, LexerPlugin, LineParser, ParsingContext};
|
||||
use crate::pipeline::file_loader::{IOResult, Loaded};
|
||||
use crate::pipeline::import_abs_path::import_abs_path;
|
||||
use crate::representations::sourcefile::FileEntry;
|
||||
use crate::tree::Module;
|
||||
use crate::utils::pure_push::pushed_ref;
|
||||
use crate::utils::pure_seq::pushed_ref;
|
||||
use crate::utils::{split_max_prefix, unwrap_or};
|
||||
use crate::Location;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Context<'a> {
|
||||
pub prelude: &'a [FileEntry],
|
||||
pub i: &'a Interner,
|
||||
pub lexer_plugins: &'a [&'a dyn LexerPlugin],
|
||||
pub line_parsers: &'a [&'a dyn LineParser],
|
||||
}
|
||||
|
||||
/// Load the source at the given path or all within if it's a collection,
|
||||
/// and all sources imported from these.
|
||||
fn load_abs_path_rec(
|
||||
abs_path: &[Tok<String>],
|
||||
mut all: Preparsed,
|
||||
source: &mut LoadedSourceTable,
|
||||
prelude: &[FileEntry],
|
||||
i: &Interner,
|
||||
get_source: &impl Fn(&[Tok<String>]) -> IOResult,
|
||||
is_injected_module: &impl Fn(&[Tok<String>]) -> bool,
|
||||
ctx @ Context { i, lexer_plugins, line_parsers, prelude }: Context,
|
||||
) -> ProjectResult<Preparsed> {
|
||||
// # Termination
|
||||
//
|
||||
@@ -47,8 +57,15 @@ fn load_abs_path_rec(
|
||||
let text = unwrap_or!(get_source(filename)? => Loaded::Code; {
|
||||
return Err(UnexpectedDirectory { path: filename.to_vec() }.rc())
|
||||
});
|
||||
source.insert(filename.to_vec(), LoadedSource { text: text.clone() });
|
||||
let preparsed = preparse(filename.to_vec(), text.as_str(), prelude, i)?;
|
||||
let entries = parse::parse2(ParsingContext::new(
|
||||
i,
|
||||
Arc::new(filename.to_vec()),
|
||||
text,
|
||||
lexer_plugins,
|
||||
line_parsers,
|
||||
))?;
|
||||
let preparsed = preparse(filename.to_vec(), entries.clone(), prelude)?;
|
||||
source.insert(filename.to_vec(), LoadedSource { entries });
|
||||
// recurse on all imported modules
|
||||
// will be taken and returned by the closure. None iff an error is thrown
|
||||
all = preparsed.0.search_all(all, &mut |modpath,
|
||||
@@ -73,10 +90,9 @@ fn load_abs_path_rec(
|
||||
&abs_pathv,
|
||||
all,
|
||||
source,
|
||||
prelude,
|
||||
i,
|
||||
get_source,
|
||||
is_injected_module,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
Ok(all)
|
||||
@@ -105,10 +121,9 @@ fn load_abs_path_rec(
|
||||
&abs_subpath,
|
||||
all,
|
||||
source,
|
||||
prelude,
|
||||
i,
|
||||
get_source,
|
||||
is_injected_module,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
Ok(all)
|
||||
@@ -123,8 +138,7 @@ fn load_abs_path_rec(
|
||||
/// injected data (the ProjectTree doesn't make a distinction between the two)
|
||||
pub fn load_source<'a>(
|
||||
targets: impl Iterator<Item = &'a [Tok<String>]>,
|
||||
prelude: &[FileEntry],
|
||||
i: &Interner,
|
||||
ctx: Context,
|
||||
get_source: &impl Fn(&[Tok<String>]) -> IOResult,
|
||||
is_injected_module: &impl Fn(&[Tok<String>]) -> bool,
|
||||
) -> ProjectResult<(Preparsed, LoadedSourceTable)> {
|
||||
@@ -138,10 +152,9 @@ pub fn load_source<'a>(
|
||||
target,
|
||||
all,
|
||||
&mut table,
|
||||
prelude,
|
||||
i,
|
||||
get_source,
|
||||
is_injected_module,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
if any_target { Ok((all, table)) } else { Err(NoTargets.rc()) }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::representations::VName;
|
||||
use crate::sourcefile::FileEntry;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoadedSource {
|
||||
pub text: Rc<String>,
|
||||
pub entries: Vec<FileEntry>
|
||||
}
|
||||
|
||||
pub type LoadedSourceTable = HashMap<VName, LoadedSource>;
|
||||
|
||||
@@ -20,6 +20,6 @@ mod loaded_source;
|
||||
mod preparse;
|
||||
mod types;
|
||||
|
||||
pub use load_source::load_source;
|
||||
pub use load_source::{load_source, Context};
|
||||
pub use loaded_source::{LoadedSource, LoadedSourceTable};
|
||||
pub use types::{PreExtra, PreFileExt, PreItem, PreMod, PreSubExt, Preparsed};
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::types::{PreFileExt, PreItem, PreSubExt};
|
||||
use super::{PreExtra, Preparsed};
|
||||
use crate::ast::{Clause, Constant, Expr};
|
||||
use crate::ast::{Clause, Constant};
|
||||
use crate::error::{
|
||||
ConflictingRoles, ProjectError, ProjectResult, VisibilityMismatch,
|
||||
};
|
||||
use crate::interner::Interner;
|
||||
use crate::parse::{self, ParsingContext};
|
||||
use crate::representations::sourcefile::{FileEntry, MemberKind};
|
||||
use crate::representations::tree::{ModEntry, ModMember, Module};
|
||||
use crate::sourcefile::{FileEntryKind, Import, Member, ModuleBlock};
|
||||
use crate::utils::get_or::{get_or_default, get_or_make};
|
||||
use crate::utils::pure_push::pushed;
|
||||
use crate::utils::pure_seq::pushed;
|
||||
use crate::{Location, Tok, VName};
|
||||
|
||||
struct FileReport {
|
||||
entries: HashMap<Tok<String>, ModEntry<PreItem, PreExtra>>,
|
||||
patterns: Vec<Vec<Expr<VName>>>,
|
||||
imports: Vec<Import>,
|
||||
}
|
||||
|
||||
@@ -32,12 +27,11 @@ fn to_module(
|
||||
prelude: &[FileEntry],
|
||||
) -> ProjectResult<FileReport> {
|
||||
let mut imports = Vec::new();
|
||||
let mut patterns = Vec::new();
|
||||
let mut items = HashMap::<Tok<String>, (bool, PreItem)>::new();
|
||||
let mut to_export = HashMap::<Tok<String>, Vec<Location>>::new();
|
||||
let mut submods =
|
||||
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 {
|
||||
match kind {
|
||||
FileEntryKind::Import(imp) => imports.extend(imp.into_iter()),
|
||||
@@ -72,17 +66,7 @@ fn to_module(
|
||||
submods.insert(name.clone(), (exported, locations, body.clone()));
|
||||
}
|
||||
},
|
||||
MemberKind::Operators(ops) =>
|
||||
for op in ops {
|
||||
let (prev_exported, it) = get_or_default(&mut items, &op);
|
||||
if let Some(loc) = locations.get(0) {
|
||||
it.location = it.location.clone().or(loc.clone())
|
||||
}
|
||||
*prev_exported |= exported;
|
||||
it.is_op = true;
|
||||
},
|
||||
MemberKind::Rule(r) => {
|
||||
patterns.push(r.pattern.clone());
|
||||
MemberKind::Rule(r) =>
|
||||
if exported {
|
||||
for ex in r.pattern {
|
||||
ex.search_all(&mut |ex| {
|
||||
@@ -95,7 +79,6 @@ fn to_module(
|
||||
None::<()>
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
_ => (),
|
||||
@@ -111,11 +94,11 @@ fn to_module(
|
||||
.try_insert(subname.clone(), ModEntry {
|
||||
member: ModMember::Sub({
|
||||
name.push(subname);
|
||||
let FileReport { imports, entries: items, patterns } =
|
||||
let FileReport { imports, entries: items } =
|
||||
to_module(file, name.clone(), body, prelude)?;
|
||||
Module {
|
||||
entries: items,
|
||||
extra: PreExtra::Submod(PreSubExt { imports, patterns }),
|
||||
extra: PreExtra::Submod(PreSubExt { imports }),
|
||||
}
|
||||
}),
|
||||
exported,
|
||||
@@ -125,7 +108,6 @@ fn to_module(
|
||||
for (item, locations) in to_export {
|
||||
get_or_make(&mut entries, &item, || ModEntry {
|
||||
member: ModMember::Item(PreItem {
|
||||
is_op: false,
|
||||
has_value: false,
|
||||
location: locations[0].clone(),
|
||||
}),
|
||||
@@ -133,26 +115,22 @@ fn to_module(
|
||||
})
|
||||
.exported = true
|
||||
}
|
||||
Ok(FileReport { entries, imports, patterns })
|
||||
Ok(FileReport { entries, imports })
|
||||
}
|
||||
|
||||
/// Preparse the module. At this stage, only the imports and
|
||||
/// names defined by the module can be parsed
|
||||
pub fn preparse(
|
||||
file: VName,
|
||||
source: &str,
|
||||
entries: Vec<FileEntry>,
|
||||
prelude: &[FileEntry],
|
||||
i: &Interner,
|
||||
) -> ProjectResult<Preparsed> {
|
||||
// Parse with no operators
|
||||
let ctx = ParsingContext::new(&[], i, Rc::new(file.clone()));
|
||||
let entries = parse::parse2(source, ctx)?;
|
||||
let FileReport { entries, imports, patterns } =
|
||||
let FileReport { entries, imports } =
|
||||
to_module(&file, file.clone(), entries, prelude)?;
|
||||
let mut module = Module {
|
||||
entries,
|
||||
extra: PreExtra::File(PreFileExt {
|
||||
details: PreSubExt { patterns, imports },
|
||||
details: PreSubExt { imports },
|
||||
name: file.clone(),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::fmt::Display;
|
||||
use std::ops::Add;
|
||||
|
||||
use crate::ast::Expr;
|
||||
use crate::error::ProjectResult;
|
||||
use crate::sourcefile::Import;
|
||||
use crate::tree::Module;
|
||||
@@ -9,34 +8,27 @@ use crate::{Interner, Location, VName};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PreItem {
|
||||
pub is_op: bool,
|
||||
pub has_value: bool,
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
impl Display for PreItem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { has_value, is_op, location } = self;
|
||||
let description = match (is_op, has_value) {
|
||||
(true, true) => "operator with value",
|
||||
(true, false) => "operator",
|
||||
(false, true) => "value",
|
||||
(false, false) => "keyword",
|
||||
};
|
||||
let Self { has_value, location } = self;
|
||||
let description = if *has_value { "value" } else { "keyword" };
|
||||
write!(f, "{description} {location}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PreItem {
|
||||
fn default() -> Self {
|
||||
PreItem { is_op: false, has_value: false, location: Location::Unknown }
|
||||
PreItem { has_value: false, location: Location::Unknown }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PreSubExt {
|
||||
pub imports: Vec<Import>,
|
||||
pub patterns: Vec<Vec<Expr<VName>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -15,13 +15,13 @@ use ordered_float::NotNan;
|
||||
use super::interpreted;
|
||||
use super::location::Location;
|
||||
use super::namelike::{NameLike, VName};
|
||||
use super::primitive::Primitive;
|
||||
use crate::foreign::{Atom, ExFn};
|
||||
use crate::interner::Tok;
|
||||
use crate::parse::print_nat16;
|
||||
use crate::utils::rc_tools::map_rc;
|
||||
|
||||
/// A [Clause] with associated metadata
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Expr<N: NameLike> {
|
||||
/// The actual value
|
||||
pub value: Clause<N>,
|
||||
@@ -100,7 +100,7 @@ pub enum PHClass {
|
||||
/// If true, must match at least one clause
|
||||
nonzero: bool,
|
||||
/// Greediness in the allocation of tokens
|
||||
prio: u64,
|
||||
prio: usize,
|
||||
},
|
||||
/// Matches exactly one token, lambda or parenthesized group
|
||||
Scalar,
|
||||
@@ -129,10 +129,12 @@ impl Display for Placeholder {
|
||||
}
|
||||
|
||||
/// An S-expression as read from a source file
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Clause<N: NameLike> {
|
||||
/// A primitive
|
||||
P(Primitive),
|
||||
/// An opaque function, eg. an effectful function employing CPS
|
||||
ExternFn(ExFn),
|
||||
/// An opaque non-callable value, eg. a file handle
|
||||
Atom(Atom),
|
||||
/// A c-style name or an operator, eg. `+`, `i`, `foo::bar`
|
||||
Name(N),
|
||||
/// A parenthesized expression
|
||||
@@ -214,7 +216,7 @@ impl<N: NameLike> Clause<N> {
|
||||
#[must_use]
|
||||
pub fn map_names(&self, pred: &impl Fn(&N) -> Option<N>) -> Option<Self> {
|
||||
match self {
|
||||
Clause::P(_) | Clause::Placeh(_) => None,
|
||||
Clause::Atom(_) | Clause::ExternFn(_) | Clause::Placeh(_) => None,
|
||||
Clause::Name(name) => pred(name).map(Clause::Name),
|
||||
Clause::S(c, body) => {
|
||||
let mut any_some = false;
|
||||
@@ -262,7 +264,8 @@ impl<N: NameLike> Clause<N> {
|
||||
match self {
|
||||
Self::Name(n) => Clause::Name(pred(n)),
|
||||
Self::Placeh(p) => Clause::Placeh(p),
|
||||
Self::P(p) => Clause::P(p),
|
||||
Self::Atom(a) => Clause::Atom(a),
|
||||
Self::ExternFn(f) => Clause::ExternFn(f),
|
||||
Self::Lambda(n, b) => Clause::Lambda(
|
||||
map_rc(n, |n| n.into_iter().map(|e| e.transform_names(pred)).collect()),
|
||||
map_rc(b, |b| b.into_iter().map(|e| e.transform_names(pred)).collect()),
|
||||
@@ -282,7 +285,8 @@ impl<N: NameLike> Clause<N> {
|
||||
match self {
|
||||
Clause::Lambda(arg, body) =>
|
||||
arg.iter().chain(body.iter()).find_map(|expr| expr.search_all(f)),
|
||||
Clause::Name(_) | Clause::P(_) | Clause::Placeh(_) => None,
|
||||
Clause::Name(_) | Clause::Atom(_) => None,
|
||||
Clause::ExternFn(_) | Clause::Placeh(_) => None,
|
||||
Clause::S(_, body) => body.iter().find_map(|expr| expr.search_all(f)),
|
||||
}
|
||||
}
|
||||
@@ -295,7 +299,8 @@ impl<N: NameLike> Clause<N> {
|
||||
match self {
|
||||
Clause::Lambda(arg, body) =>
|
||||
search_all_slcs(arg, f).or_else(|| search_all_slcs(body, f)),
|
||||
Clause::Name(_) | Clause::P(_) | Clause::Placeh(_) => None,
|
||||
Clause::Name(_) | Clause::Atom(_) => None,
|
||||
Clause::ExternFn(_) | Clause::Placeh(_) => None,
|
||||
Clause::S(_, body) => search_all_slcs(body, f),
|
||||
}
|
||||
}
|
||||
@@ -325,7 +330,8 @@ impl Clause<VName> {
|
||||
impl<N: NameLike> Display for Clause<N> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::P(p) => write!(f, "{:?}", p),
|
||||
Self::ExternFn(fun) => write!(f, "{fun:?}"),
|
||||
Self::Atom(a) => write!(f, "{a:?}"),
|
||||
Self::Name(name) => write!(f, "{}", name.to_strv().join("::")),
|
||||
Self::S(del, items) => {
|
||||
let body = items.iter().join(" ");
|
||||
@@ -348,7 +354,7 @@ impl<N: NameLike> Display for Clause<N> {
|
||||
}
|
||||
|
||||
/// A substitution rule as read from the source
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Rule<N: NameLike> {
|
||||
/// Tree fragment in the source code that activates this rule
|
||||
pub pattern: Vec<Expr<N>>,
|
||||
@@ -407,7 +413,7 @@ impl<N: NameLike> Display for Rule<N> {
|
||||
}
|
||||
|
||||
/// A named constant
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Constant {
|
||||
/// Used to reference the constant
|
||||
pub name: Tok<String>,
|
||||
|
||||
@@ -107,7 +107,8 @@ fn expr_rec<'a>(
|
||||
Ok(postmacro::Expr { value: expr.value, location: location.clone() })
|
||||
} else {
|
||||
let value = match value {
|
||||
ast::Clause::P(p) => postmacro::Clause::P(p.clone()),
|
||||
ast::Clause::Atom(a) => postmacro::Clause::Atom(a.clone()),
|
||||
ast::Clause::ExternFn(fun) => postmacro::Clause::ExternFn(fun.clone()),
|
||||
ast::Clause::Lambda(arg, b) => {
|
||||
let name = match &arg[..] {
|
||||
[ast::Expr { value: ast::Clause::Name(name), .. }] => name,
|
||||
|
||||
@@ -4,12 +4,12 @@ use hashbrown::HashMap;
|
||||
|
||||
use super::project::{ItemKind, ProjectItem};
|
||||
use crate::ast::{Clause, Expr};
|
||||
use crate::foreign::{Atom, Atomic, ExternFn};
|
||||
use crate::foreign::{Atom, Atomic, ExFn, ExternFn};
|
||||
use crate::interner::Tok;
|
||||
use crate::representations::location::Location;
|
||||
use crate::representations::project::{ProjectExt, ProjectMod, ProjectTree};
|
||||
use crate::representations::tree::{ModEntry, ModMember, Module};
|
||||
use crate::representations::{Primitive, VName};
|
||||
use crate::representations::VName;
|
||||
use crate::utils::substack::Substack;
|
||||
|
||||
/// A lightweight module tree that can be built declaratively by hand to
|
||||
@@ -25,21 +25,18 @@ pub enum ConstTree {
|
||||
impl ConstTree {
|
||||
/// Describe a [Primitive]
|
||||
#[must_use]
|
||||
pub fn primitive(primitive: Primitive) -> Self {
|
||||
Self::Const(Expr {
|
||||
location: Location::Unknown,
|
||||
value: Clause::P(primitive),
|
||||
})
|
||||
pub fn clause(value: Clause<VName>) -> Self {
|
||||
Self::Const(Expr { location: Location::Unknown, value })
|
||||
}
|
||||
/// Describe an [ExternFn]
|
||||
#[must_use]
|
||||
pub fn xfn(xfn: impl ExternFn + 'static) -> Self {
|
||||
Self::primitive(Primitive::ExternFn(Box::new(xfn)))
|
||||
Self::clause(Clause::ExternFn(ExFn(Box::new(xfn))))
|
||||
}
|
||||
/// Describe an [Atomic]
|
||||
#[must_use]
|
||||
pub fn atom(atom: impl Atomic + 'static) -> Self {
|
||||
Self::primitive(Primitive::Atom(Atom(Box::new(atom))))
|
||||
Self::clause(Clause::Atom(Atom(Box::new(atom))))
|
||||
}
|
||||
/// Describe a module
|
||||
#[must_use]
|
||||
@@ -85,7 +82,7 @@ impl Add for ConstTree {
|
||||
product.insert(key, i1);
|
||||
}
|
||||
}
|
||||
product.extend(t2.into_iter());
|
||||
product.extend(t2);
|
||||
Self::Tree(product)
|
||||
} else {
|
||||
panic!("cannot combine tree and value fields")
|
||||
@@ -104,10 +101,8 @@ fn from_const_tree_rec(
|
||||
items.insert(name.clone(), ModEntry {
|
||||
exported: true,
|
||||
member: match item {
|
||||
ConstTree::Const(c) => ModMember::Item(ProjectItem {
|
||||
kind: ItemKind::Const(c),
|
||||
is_op: false,
|
||||
}),
|
||||
ConstTree::Const(c) =>
|
||||
ModMember::Item(ProjectItem { kind: ItemKind::Const(c) }),
|
||||
ConstTree::Tree(t) =>
|
||||
ModMember::Sub(from_const_tree_rec(path.push(name), t, file)),
|
||||
},
|
||||
|
||||
@@ -2,20 +2,18 @@
|
||||
//!
|
||||
//! This code may be generated to minimize the number of states external
|
||||
//! functions have to define
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, TryLockError, Mutex};
|
||||
|
||||
#[allow(unused)] // for doc
|
||||
use super::ast;
|
||||
use super::location::Location;
|
||||
use super::path_set::PathSet;
|
||||
use super::primitive::Primitive;
|
||||
use super::Literal;
|
||||
#[allow(unused)] // for doc
|
||||
use crate::foreign::Atomic;
|
||||
use crate::foreign::ExternError;
|
||||
use crate::foreign::{Atom, ExFn, ExternError};
|
||||
use crate::utils::ddispatch::request;
|
||||
use crate::utils::take_with_output;
|
||||
use crate::Sym;
|
||||
@@ -40,10 +38,11 @@ impl Debug for Expr {
|
||||
|
||||
impl Display for Expr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.location {
|
||||
Location::Unknown => write!(f, "{}", self.clause),
|
||||
loc => write!(f, "{}:({})", loc, self.clause),
|
||||
}
|
||||
write!(f, "{}", self.clause)
|
||||
// match &self.location {
|
||||
// Location::Unknown => write!(f, "{}", self.clause),
|
||||
// loc => write!(f, "{}:({})", loc, self.clause),
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,20 +63,20 @@ impl TryFromExprInst for ExprInst {
|
||||
/// A wrapper around expressions to handle their multiple occurences in
|
||||
/// the tree together
|
||||
#[derive(Clone)]
|
||||
pub struct ExprInst(pub Rc<RefCell<Expr>>);
|
||||
pub struct ExprInst(pub Arc<Mutex<Expr>>);
|
||||
impl ExprInst {
|
||||
/// Wrap an [Expr] in a shared container so that normalizatoin steps are
|
||||
/// applied to all references
|
||||
#[must_use]
|
||||
pub fn new(expr: Expr) -> Self { Self(Rc::new(RefCell::new(expr))) }
|
||||
pub fn new(expr: Expr) -> Self { Self(Arc::new(Mutex::new(expr))) }
|
||||
|
||||
/// Take the [Expr] out of this container if it's the last reference to it, or
|
||||
/// clone it out.
|
||||
#[must_use]
|
||||
pub fn expr_val(self) -> Expr {
|
||||
Rc::try_unwrap(self.0)
|
||||
.map(|c| c.into_inner())
|
||||
.unwrap_or_else(|rc| rc.as_ref().borrow().deref().clone())
|
||||
Arc::try_unwrap(self.0)
|
||||
.map(|c| c.into_inner().unwrap())
|
||||
.unwrap_or_else(|arc| arc.lock().unwrap().clone())
|
||||
}
|
||||
|
||||
/// Read-only access to the shared expression instance
|
||||
@@ -87,7 +86,7 @@ impl ExprInst {
|
||||
/// if the expression is already borrowed in read-write mode
|
||||
#[must_use]
|
||||
pub fn expr(&self) -> impl Deref<Target = Expr> + '_ {
|
||||
self.0.as_ref().borrow()
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Read-Write access to the shared expression instance
|
||||
@@ -97,7 +96,7 @@ impl ExprInst {
|
||||
/// if the expression is already borrowed
|
||||
#[must_use]
|
||||
pub fn expr_mut(&self) -> impl DerefMut<Target = Expr> + '_ {
|
||||
self.0.as_ref().borrow_mut()
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Call a normalization function on the expression. The expr is
|
||||
@@ -137,26 +136,6 @@ impl ExprInst {
|
||||
predicate(&self.expr().clause)
|
||||
}
|
||||
|
||||
/// Call the predicate on the value inside this expression if it is a
|
||||
/// primitive
|
||||
pub fn get_literal(self) -> Result<(Literal, Location), Self> {
|
||||
Rc::try_unwrap(self.0).map_or_else(
|
||||
|rc| {
|
||||
if let Expr { clause: Clause::P(Primitive::Literal(li)), location } =
|
||||
rc.as_ref().borrow().deref()
|
||||
{
|
||||
return Ok((li.clone(), location.clone()));
|
||||
}
|
||||
Err(Self(rc))
|
||||
},
|
||||
|cell| match cell.into_inner() {
|
||||
Expr { clause: Clause::P(Primitive::Literal(li)), location } =>
|
||||
Ok((li, location)),
|
||||
expr => Err(Self::new(expr)),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Visit all expressions in the tree. The search can be exited early by
|
||||
/// returning [Some]
|
||||
///
|
||||
@@ -174,7 +153,8 @@ impl ExprInst {
|
||||
Clause::Lambda { body, .. } => body.search_all(predicate),
|
||||
Clause::Constant(_)
|
||||
| Clause::LambdaArg
|
||||
| Clause::P(_)
|
||||
| Clause::Atom(_)
|
||||
| Clause::ExternFn(_)
|
||||
| Clause::Bottom => None,
|
||||
})
|
||||
}
|
||||
@@ -195,7 +175,7 @@ impl ExprInst {
|
||||
#[must_use = "your request might not have succeeded"]
|
||||
pub fn request<T: 'static>(&self) -> Option<T> {
|
||||
match &self.expr().clause {
|
||||
Clause::P(Primitive::Atom(a)) => request(&*a.0),
|
||||
Clause::Atom(a) => request(&*a.0),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -203,18 +183,20 @@ impl ExprInst {
|
||||
|
||||
impl Debug for ExprInst {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0.try_borrow() {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr:?}"),
|
||||
Err(_) => write!(f, "<borrowed>"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ExprInst {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0.try_borrow() {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr}"),
|
||||
Err(_) => write!(f, "<borrowed>"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,8 +206,10 @@ impl Display for ExprInst {
|
||||
pub enum Clause {
|
||||
/// An expression that causes an error
|
||||
Bottom,
|
||||
/// An unintrospectable unit
|
||||
P(Primitive),
|
||||
/// An opaque function, eg. an effectful function employing CPS
|
||||
ExternFn(ExFn),
|
||||
/// An opaque non-callable value, eg. a file handle
|
||||
Atom(Atom),
|
||||
/// A function application
|
||||
Apply {
|
||||
/// Function to be applied
|
||||
@@ -252,7 +236,7 @@ impl Clause {
|
||||
/// copied or moved clauses as it does not have debug information and
|
||||
/// does not share a normalization cache list with them.
|
||||
pub fn wrap(self) -> ExprInst {
|
||||
ExprInst(Rc::new(RefCell::new(Expr {
|
||||
ExprInst(Arc::new(Mutex::new(Expr {
|
||||
location: Location::Unknown,
|
||||
clause: self,
|
||||
})))
|
||||
@@ -284,7 +268,8 @@ impl Clause {
|
||||
impl Display for Clause {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Clause::P(p) => write!(f, "{p:?}"),
|
||||
Clause::ExternFn(fun) => write!(f, "{fun:?}"),
|
||||
Clause::Atom(a) => write!(f, "{a:?}"),
|
||||
Clause::Bottom => write!(f, "bottom"),
|
||||
Clause::LambdaArg => write!(f, "arg"),
|
||||
Clause::Apply { f: fun, x } => write!(f, "({fun} {x})"),
|
||||
@@ -296,11 +281,3 @@ impl Display for Clause {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<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() }
|
||||
}
|
||||
|
||||
@@ -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()) }
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
@@ -13,15 +13,15 @@ pub enum Location {
|
||||
/// Location information lost or code generated on the fly
|
||||
Unknown,
|
||||
/// Only the file is known
|
||||
File(Rc<VName>),
|
||||
File(Arc<VName>),
|
||||
/// Character slice of the code
|
||||
Range {
|
||||
/// 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
|
||||
range: Range<usize>,
|
||||
/// The full source code as received by the parser
|
||||
source: Rc<String>,
|
||||
source: Arc<String>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ impl Location {
|
||||
|
||||
/// File, if known
|
||||
#[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 {
|
||||
Some(file.clone())
|
||||
} else {
|
||||
@@ -48,7 +48,7 @@ impl Location {
|
||||
|
||||
/// Associated source code, if known
|
||||
#[must_use]
|
||||
pub fn source(&self) -> Option<Rc<String>> {
|
||||
pub fn source(&self) -> Option<Arc<String>> {
|
||||
if let Self::Range { source, .. } = self {
|
||||
Some(source.clone())
|
||||
} else {
|
||||
|
||||
@@ -3,22 +3,18 @@ pub mod ast_to_interpreted;
|
||||
pub mod ast_to_postmacro;
|
||||
mod const_tree;
|
||||
pub mod interpreted;
|
||||
pub mod literal;
|
||||
pub mod location;
|
||||
mod namelike;
|
||||
pub mod path_set;
|
||||
pub mod postmacro;
|
||||
pub mod postmacro_to_interpreted;
|
||||
pub mod primitive;
|
||||
pub mod project;
|
||||
pub mod sourcefile;
|
||||
mod string;
|
||||
pub mod tree;
|
||||
|
||||
pub use const_tree::{from_const_tree, ConstTree};
|
||||
pub use literal::Literal;
|
||||
pub use location::Location;
|
||||
pub use namelike::{NameLike, Sym, VName};
|
||||
pub use path_set::PathSet;
|
||||
pub use primitive::Primitive;
|
||||
pub use string::OrcString;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Add;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::utils::rc_tools::rc_to_owned;
|
||||
use crate::utils::rc_tools::arc_to_owned;
|
||||
use crate::utils::Side;
|
||||
|
||||
/// A branching path selecting some placeholders (but at least one) in a Lambda
|
||||
@@ -10,9 +10,9 @@ use crate::utils::Side;
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct PathSet {
|
||||
/// The definite steps
|
||||
pub steps: Rc<Vec<Side>>,
|
||||
pub steps: Arc<Vec<Side>>,
|
||||
/// if Some, it splits. If None, it ends.
|
||||
pub next: Option<(Rc<PathSet>, Rc<PathSet>)>,
|
||||
pub next: Option<(Arc<PathSet>, Arc<PathSet>)>,
|
||||
}
|
||||
|
||||
impl PathSet {
|
||||
@@ -22,20 +22,20 @@ impl PathSet {
|
||||
left: Self,
|
||||
right: Self,
|
||||
) -> Self {
|
||||
let steps = Rc::new(steps.into_iter().collect());
|
||||
Self { steps, next: Some((Rc::new(left), Rc::new(right))) }
|
||||
let steps = Arc::new(steps.into_iter().collect());
|
||||
Self { steps, next: Some((Arc::new(left), Arc::new(right))) }
|
||||
}
|
||||
|
||||
/// Create a path set for one target
|
||||
pub fn end(steps: impl IntoIterator<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
|
||||
/// child of the given lambda with no applications. In essence, this means
|
||||
/// that this argument will be picked as the value of the expression after an
|
||||
/// arbitrary amount of subsequent discarded parameters.
|
||||
pub fn pick() -> Self { Self { steps: Rc::new(vec![]), next: None } }
|
||||
pub fn pick() -> Self { Self { steps: Arc::new(vec![]), next: None } }
|
||||
}
|
||||
|
||||
impl Debug for PathSet {
|
||||
@@ -57,7 +57,10 @@ impl Debug for PathSet {
|
||||
impl Add for PathSet {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self { steps: Rc::new(vec![]), next: Some((Rc::new(self), Rc::new(rhs))) }
|
||||
Self {
|
||||
steps: Arc::new(vec![]),
|
||||
next: Some((Arc::new(self), Arc::new(rhs))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,9 +68,9 @@ impl Add<Side> for PathSet {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Side) -> Self::Output {
|
||||
let PathSet { steps, next } = self;
|
||||
let mut new_steps = rc_to_owned(steps);
|
||||
let mut new_steps = arc_to_owned(steps);
|
||||
new_steps.insert(0, rhs);
|
||||
Self { steps: Rc::new(new_steps), next }
|
||||
Self { steps: Arc::new(new_steps), next }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,9 +81,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_combine() -> Result<(), &'static str> {
|
||||
let ps1 =
|
||||
PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Left]) };
|
||||
PathSet { next: None, steps: Arc::new(vec![Side::Left, Side::Left]) };
|
||||
let ps2 =
|
||||
PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Right]) };
|
||||
PathSet { next: None, steps: Arc::new(vec![Side::Left, Side::Right]) };
|
||||
let sum = ps1.clone() + ps2.clone();
|
||||
assert_eq!(sum.steps.as_ref(), &[]);
|
||||
let nexts = sum.next.ok_or("nexts not set")?;
|
||||
@@ -92,16 +95,16 @@ mod tests {
|
||||
fn extend_scaffold() -> PathSet {
|
||||
PathSet {
|
||||
next: Some((
|
||||
Rc::new(PathSet {
|
||||
Arc::new(PathSet {
|
||||
next: None,
|
||||
steps: Rc::new(vec![Side::Left, Side::Left]),
|
||||
steps: Arc::new(vec![Side::Left, Side::Left]),
|
||||
}),
|
||||
Rc::new(PathSet {
|
||||
Arc::new(PathSet {
|
||||
next: None,
|
||||
steps: Rc::new(vec![Side::Left, Side::Right]),
|
||||
steps: Arc::new(vec![Side::Left, Side::Right]),
|
||||
}),
|
||||
)),
|
||||
steps: Rc::new(vec![Side::Left, Side::Right, Side::Left]),
|
||||
steps: Arc::new(vec![Side::Left, Side::Right, Side::Left]),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::{Debug, Write};
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::location::Location;
|
||||
use super::primitive::Primitive;
|
||||
use crate::foreign::{ExFn, Atom};
|
||||
use crate::utils::string_from_charset;
|
||||
use crate::Sym;
|
||||
|
||||
@@ -42,7 +42,10 @@ pub enum Clause {
|
||||
Lambda(Rc<Expr>),
|
||||
Constant(Sym),
|
||||
LambdaArg(usize),
|
||||
P(Primitive),
|
||||
/// An opaque function, eg. an effectful function employing CPS
|
||||
ExternFn(ExFn),
|
||||
/// An opaque non-callable value, eg. a file handle
|
||||
Atom(Atom),
|
||||
}
|
||||
|
||||
const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz";
|
||||
@@ -75,7 +78,8 @@ impl Clause {
|
||||
Wrap(wl, wr): Wrap,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::P(p) => write!(f, "{p:?}"),
|
||||
Self::Atom(a) => write!(f, "{a:?}"),
|
||||
Self::ExternFn(fun) => write!(f, "{fun:?}"),
|
||||
Self::Lambda(body) => parametric_fmt(f, depth, "\\", body, wr),
|
||||
Self::LambdaArg(skip) => {
|
||||
let lambda_depth = (depth - skip - 1).try_into().unwrap();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::path_set::PathSet;
|
||||
use super::{interpreted, postmacro};
|
||||
@@ -18,12 +17,12 @@ fn collect_paths_cls_rec(
|
||||
depth: usize,
|
||||
) -> Option<PathSet> {
|
||||
match cls {
|
||||
postmacro::Clause::P(_) | postmacro::Clause::Constant(_) => None,
|
||||
postmacro::Clause::Atom(_) | postmacro::Clause::ExternFn(_) => None,
|
||||
postmacro::Clause::Constant(_) => None,
|
||||
postmacro::Clause::LambdaArg(h) =>
|
||||
if *h != depth {
|
||||
None
|
||||
} else {
|
||||
Some(PathSet { next: None, steps: Rc::new(vec![]) })
|
||||
match *h != depth {
|
||||
true => None,
|
||||
false => Some(PathSet::pick())
|
||||
},
|
||||
postmacro::Clause::Lambda(b) => collect_paths_expr_rec(b, depth + 1),
|
||||
postmacro::Clause::Apply(f, x) => {
|
||||
@@ -43,7 +42,8 @@ pub fn clause(cls: &postmacro::Clause) -> interpreted::Clause {
|
||||
match cls {
|
||||
postmacro::Clause::Constant(name) =>
|
||||
interpreted::Clause::Constant(name.clone()),
|
||||
postmacro::Clause::P(p) => interpreted::Clause::P(p.clone()),
|
||||
postmacro::Clause::Atom(a) => interpreted::Clause::Atom(a.clone()),
|
||||
postmacro::Clause::ExternFn(fun) => interpreted::Clause::ExternFn(fun.clone()),
|
||||
postmacro::Clause::Apply(f, x) =>
|
||||
interpreted::Clause::Apply { f: expr(f.as_ref()), x: expr(x.as_ref()) },
|
||||
postmacro::Clause::Lambda(body) => interpreted::Clause::Lambda {
|
||||
@@ -55,7 +55,7 @@ pub fn clause(cls: &postmacro::Clause) -> interpreted::Clause {
|
||||
}
|
||||
|
||||
pub fn expr(expr: &postmacro::Expr) -> interpreted::ExprInst {
|
||||
interpreted::ExprInst(Rc::new(RefCell::new(interpreted::Expr {
|
||||
interpreted::ExprInst(Arc::new(Mutex::new(interpreted::Expr {
|
||||
location: expr.location.clone(),
|
||||
clause: clause(&expr.value),
|
||||
})))
|
||||
|
||||
@@ -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:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,26 +31,15 @@ impl<N: NameLike> Default for ItemKind<N> {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProjectItem<N: NameLike> {
|
||||
pub kind: ItemKind<N>,
|
||||
pub is_op: bool,
|
||||
}
|
||||
|
||||
impl<N: NameLike> Display for ProjectItem<N> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.kind {
|
||||
ItemKind::None => match self.is_op {
|
||||
true => write!(f, "operator"),
|
||||
false => write!(f, "keyword"),
|
||||
},
|
||||
ItemKind::Const(c) => match self.is_op {
|
||||
true => write!(f, "operator with value {c}"),
|
||||
false => write!(f, "constant {c}"),
|
||||
},
|
||||
ItemKind::None => write!(f, "keyword"),
|
||||
ItemKind::Const(c) => write!(f, "constant {c}"),
|
||||
ItemKind::Alias(alias) => {
|
||||
let origin = Interner::extern_all(alias).join("::");
|
||||
match self.is_op {
|
||||
true => write!(f, "operator alias to {origin}"),
|
||||
false => write!(f, "alias to {origin}"),
|
||||
}
|
||||
write!(f, "alias to {}", Interner::extern_all(alias).join("::"))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -61,9 +50,6 @@ impl<N: NameLike> Display for ProjectItem<N> {
|
||||
pub struct ImpReport<N: NameLike> {
|
||||
/// Absolute path of the module the symbol is imported from
|
||||
pub source: N,
|
||||
/// Whether this symbol should be treated as an operator for the purpose of
|
||||
/// parsing
|
||||
pub is_op: bool,
|
||||
}
|
||||
|
||||
/// Additional data about a loaded module beyond the list of constants and
|
||||
@@ -94,8 +80,8 @@ impl<N: NameLike> Add for ProjectExt<N> {
|
||||
Interner::extern_all(&self.path).join("::")
|
||||
)
|
||||
}
|
||||
self.imports_from.extend(imports_from.into_iter());
|
||||
self.rules.extend(rules.into_iter());
|
||||
self.imports_from.extend(imports_from);
|
||||
self.rules.extend(rules);
|
||||
if file.is_some() {
|
||||
self.file = file
|
||||
}
|
||||
@@ -190,7 +176,6 @@ fn vname_to_sym_tree_rec(
|
||||
ModMember::Sub(module) =>
|
||||
ModMember::Sub(vname_to_sym_tree_rec(module, i)),
|
||||
ModMember::Item(ex) => ModMember::Item(ProjectItem {
|
||||
is_op: ex.is_op,
|
||||
kind: match ex.kind {
|
||||
ItemKind::None => ItemKind::None,
|
||||
ItemKind::Alias(n) => ItemKind::Alias(n),
|
||||
@@ -204,7 +189,7 @@ fn vname_to_sym_tree_rec(
|
||||
extra: ProjectExt {
|
||||
path: tree.extra.path,
|
||||
imports_from: (tree.extra.imports_from.into_iter())
|
||||
.map(|(k, v)| (k, ImpReport { is_op: v.is_op, source: i.i(&v.source) }))
|
||||
.map(|(k, v)| (k, ImpReport { source: i.i(&v.source) }))
|
||||
.collect(),
|
||||
rules: (tree.extra.rules.into_iter())
|
||||
.map(|Rule { pattern, prio, template }| Rule {
|
||||
|
||||
@@ -8,7 +8,7 @@ use super::namelike::VName;
|
||||
use crate::ast::{Constant, Rule};
|
||||
use crate::error::{ProjectError, ProjectResult, TooManySupers};
|
||||
use crate::interner::{Interner, Tok};
|
||||
use crate::utils::pure_push::pushed;
|
||||
use crate::utils::pure_seq::pushed;
|
||||
use crate::utils::{unwrap_or, BoxedIter};
|
||||
use crate::Location;
|
||||
|
||||
@@ -78,16 +78,11 @@ pub enum MemberKind {
|
||||
Constant(Constant),
|
||||
/// A prefixed set of other entries
|
||||
Module(ModuleBlock),
|
||||
/// Operator declarations
|
||||
Operators(Vec<Tok<String>>),
|
||||
}
|
||||
|
||||
impl Display for MemberKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Operators(opv) => {
|
||||
write!(f, "operators[{}]", opv.iter().map(|t| &**t).join(" "))
|
||||
},
|
||||
Self::Constant(c) => c.fmt(f),
|
||||
Self::Module(m) => m.fmt(f),
|
||||
Self::Rule(r) => r.fmt(f),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::{Literal, Primitive, Tok};
|
||||
use crate::foreign::InertAtomic;
|
||||
use crate::{Interner, Tok};
|
||||
|
||||
/// An Orchid string which may or may not be interned
|
||||
#[derive(Clone, Eq)]
|
||||
@@ -12,7 +12,7 @@ pub enum OrcString {
|
||||
/// An interned string. Equality-conpared by reference.
|
||||
Interned(Tok<String>),
|
||||
/// An uninterned bare string. Equality-compared by character
|
||||
Runtime(Rc<String>),
|
||||
Runtime(Arc<String>),
|
||||
}
|
||||
|
||||
impl Debug for OrcString {
|
||||
@@ -25,23 +25,21 @@ impl Debug for OrcString {
|
||||
}
|
||||
|
||||
impl OrcString {
|
||||
/// Intern the contained string
|
||||
pub fn intern(&mut self, i: &Interner) {
|
||||
if let Self::Runtime(t) = self {
|
||||
*self = Self::Interned(i.i(t.as_str()))
|
||||
}
|
||||
}
|
||||
/// Clone out the plain Rust [String]
|
||||
#[must_use]
|
||||
pub fn get_string(self) -> String {
|
||||
match self {
|
||||
Self::Interned(s) => s.as_str().to_owned(),
|
||||
Self::Runtime(rc) =>
|
||||
Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()),
|
||||
Arc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap in a [Clause] for returning from extern functions
|
||||
pub fn cls(self) -> Clause {
|
||||
Clause::P(Primitive::Literal(Literal::Str(self)))
|
||||
}
|
||||
|
||||
/// Wrap in an [ExprInst] for embedding in runtime-generated code
|
||||
pub fn exi(self) -> ExprInst { self.cls().wrap() }
|
||||
}
|
||||
|
||||
impl Deref for OrcString {
|
||||
@@ -62,7 +60,7 @@ impl Hash for OrcString {
|
||||
}
|
||||
|
||||
impl From<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 {
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ impl<TItem: Clone, TExt: Clone> Module<TItem, TExt> {
|
||||
(_, 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)? })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::rule::vec_attrs::vec_attrs;
|
||||
use crate::utils::Side;
|
||||
|
||||
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
|
||||
/// slice of Expr's
|
||||
@@ -107,7 +107,8 @@ fn mk_vec(pattern: &[RuleExpr]) -> VecMatcher {
|
||||
#[must_use]
|
||||
fn mk_scalar(pattern: &RuleExpr) -> ScalMatcher {
|
||||
match &pattern.value {
|
||||
Clause::P(p) => ScalMatcher::P(p.clone()),
|
||||
Clause::Atom(a) => ScalMatcher::Atom(a.clone()),
|
||||
Clause::ExternFn(_) => panic!("Cannot match on ExternFn"),
|
||||
Clause::Name(n) => ScalMatcher::Name(n.clone()),
|
||||
Clause::Placeh(Placeholder { name, class }) => {
|
||||
debug_assert!(
|
||||
|
||||
@@ -10,7 +10,8 @@ pub fn scal_match<'a>(
|
||||
expr: &'a RuleExpr,
|
||||
) -> Option<State<'a>> {
|
||||
match (matcher, &expr.value) {
|
||||
(ScalMatcher::P(p1), Clause::P(p2)) if p1 == p2 => Some(State::new()),
|
||||
(ScalMatcher::Atom(a1), Clause::Atom(a2)) if a1.0.strict_eq(&a2.0) =>
|
||||
Some(State::new()),
|
||||
(ScalMatcher::Name(n1), Clause::Name(n2)) if n1 == n2 => Some(State::new()),
|
||||
(ScalMatcher::Placeh(key), _) =>
|
||||
Some(State::from([(key.clone(), StateEntry::Scalar(expr))])),
|
||||
|
||||
@@ -5,15 +5,15 @@ use itertools::Itertools;
|
||||
|
||||
use super::any_match::any_match;
|
||||
use super::build::mk_any;
|
||||
use crate::foreign::Atom;
|
||||
use crate::interner::Tok;
|
||||
use crate::representations::Primitive;
|
||||
use crate::rule::matcher::{Matcher, RuleExpr};
|
||||
use crate::rule::state::State;
|
||||
use crate::utils::Side;
|
||||
use crate::{Sym, VName};
|
||||
|
||||
pub enum ScalMatcher {
|
||||
P(Primitive),
|
||||
Atom(Atom),
|
||||
Name(Sym),
|
||||
S(char, Box<AnyMatcher>),
|
||||
Lambda(Box<AnyMatcher>, Box<AnyMatcher>),
|
||||
@@ -68,7 +68,7 @@ impl Matcher for AnyMatcher {
|
||||
impl Display for ScalMatcher {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::P(p) => write!(f, "{:?}", p),
|
||||
Self::Atom(a) => write!(f, "{a:?}"),
|
||||
Self::Placeh(n) => write!(f, "${n}"),
|
||||
Self::Name(n) => write!(f, "{}", n.extern_vec().join("::")),
|
||||
Self::S(c, body) => {
|
||||
|
||||
@@ -55,7 +55,7 @@ pub fn vec_match<'a>(
|
||||
// Valid combinations of locations for the separators
|
||||
let mut pos_pairs = lposv
|
||||
.into_iter()
|
||||
.cartesian_product(rposv.into_iter())
|
||||
.cartesian_product(rposv)
|
||||
.filter(|((lpos, _), (rpos, _))| lpos + left_sep.len() <= *rpos)
|
||||
.map(|((lpos, mut lstate), (rpos, rstate))| {
|
||||
lstate.extend(rstate);
|
||||
|
||||
@@ -30,11 +30,11 @@ fn pad(mut rule: Rule<Sym>, i: &Interner) -> Rule<Sym> {
|
||||
let prefix_v = if prefix_explicit { empty } else { prefix };
|
||||
let suffix_v = if suffix_explicit { empty } else { suffix };
|
||||
rule.pattern = (prefix_v.iter().cloned())
|
||||
.chain(rule.pattern.into_iter())
|
||||
.chain(rule.pattern)
|
||||
.chain(suffix_v.iter().cloned())
|
||||
.collect();
|
||||
rule.template = (prefix_v.iter().cloned())
|
||||
.chain(rule.template.into_iter())
|
||||
.chain(rule.template)
|
||||
.chain(suffix_v.iter().cloned())
|
||||
.collect();
|
||||
rule
|
||||
@@ -60,7 +60,8 @@ fn check_rec_expr(
|
||||
in_template: bool,
|
||||
) -> Result<(), RuleError> {
|
||||
match &expr.value {
|
||||
Clause::Name(_) | Clause::P(_) => Ok(()),
|
||||
Clause::Name(_) | Clause::Atom(_) => Ok(()),
|
||||
Clause::ExternFn(_) => Err(RuleError::ExternFn),
|
||||
Clause::Placeh(Placeholder { name, class }) => {
|
||||
let typ = (*class).into();
|
||||
// in a template, the type must be known and identical
|
||||
|
||||
@@ -5,6 +5,8 @@ use hashbrown::HashSet;
|
||||
|
||||
use crate::ast::{self, search_all_slcs, PHClass, Placeholder, Rule};
|
||||
use crate::error::{ErrorPosition, ProjectError};
|
||||
#[allow(unused)] // for doc
|
||||
use crate::foreign::ExternFn;
|
||||
use crate::interner::Tok;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Location, Sym};
|
||||
@@ -20,16 +22,20 @@ pub enum RuleError {
|
||||
Multiple(Tok<String>),
|
||||
/// Two vectorial placeholders are next to each other
|
||||
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 {
|
||||
/// Convert into a unified error trait object shared by all Orchid errors
|
||||
#[must_use]
|
||||
pub fn to_project_error(self, rule: &Rule<Sym>) -> Rc<dyn ProjectError> {
|
||||
match self {
|
||||
RuleError::Missing(name) => Missing::new(rule, name).rc(),
|
||||
RuleError::Multiple(name) => Multiple::new(rule, name).rc(),
|
||||
RuleError::ArityMismatch(name) => ArityMismatch::new(rule, name).rc(),
|
||||
RuleError::VecNeighbors(n1, n2) => VecNeighbors::new(rule, n1, n2).rc(),
|
||||
Self::Missing(name) => Missing::new(rule, name).rc(),
|
||||
Self::Multiple(name) => Multiple::new(rule, name).rc(),
|
||||
Self::ArityMismatch(name) => ArityMismatch::new(rule, name).rc(),
|
||||
Self::VecNeighbors(n1, n2) => VecNeighbors::new(rule, n1, n2).rc(),
|
||||
Self::ExternFn => ExternFnInPattern(rule.clone()).rc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +43,7 @@ impl RuleError {
|
||||
impl Display for RuleError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::ExternFn => write!(f, "Found an ExternFn in the pattern"),
|
||||
Self::Missing(key) => write!(f, "Key {key} not in match pattern"),
|
||||
Self::ArityMismatch(key) => {
|
||||
write!(f, "Key {key} used inconsistently with and without ellipsis")
|
||||
@@ -44,10 +51,8 @@ impl Display for RuleError {
|
||||
Self::Multiple(key) => {
|
||||
write!(f, "Key {key} appears multiple times in match pattern")
|
||||
},
|
||||
Self::VecNeighbors(left, right) => write!(
|
||||
f,
|
||||
"Keys {left} and {right} are two vectorials right next to each other"
|
||||
),
|
||||
Self::VecNeighbors(left, right) =>
|
||||
write!(f, "vectorials {left} and {right} are next to each other"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,3 +237,15 @@ impl ProjectError for VecNeighbors {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Not referencing by location because it's most likely unknown
|
||||
#[derive(Debug)]
|
||||
pub struct ExternFnInPattern(ast::Rule<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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::ast::{Clause, Expr, PHClass, Placeholder};
|
||||
use crate::interner::Tok;
|
||||
use crate::utils::unwrap_or;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum StateEntry<'a> {
|
||||
Vec(&'a [RuleExpr]),
|
||||
Scalar(&'a RuleExpr),
|
||||
@@ -27,7 +27,8 @@ pub fn apply_exprv(template: &[RuleExpr], state: &State) -> Vec<RuleExpr> {
|
||||
pub fn apply_expr(template: &RuleExpr, state: &State) -> Vec<RuleExpr> {
|
||||
let Expr { location, value } = template;
|
||||
match value {
|
||||
Clause::P(_) | Clause::Name(_) => vec![template.clone()],
|
||||
Clause::Atom(_) | Clause::Name(_) | Clause::ExternFn(_) =>
|
||||
vec![template.clone()],
|
||||
Clause::S(c, body) => vec![Expr {
|
||||
location: location.clone(),
|
||||
value: Clause::S(*c, Rc::new(apply_exprv(body.as_slice(), state))),
|
||||
|
||||
@@ -35,7 +35,8 @@ pub fn clause<F: FnMut(Rc<Vec<RuleExpr>>) -> Option<Rc<Vec<RuleExpr>>>>(
|
||||
pred: &mut F,
|
||||
) -> Option<Clause<Sym>> {
|
||||
match c {
|
||||
Clause::P(_) | Clause::Placeh { .. } | Clause::Name { .. } => None,
|
||||
Clause::Atom(_) | Clause::Placeh { .. } | Clause::Name { .. } => None,
|
||||
Clause::ExternFn(_) => None,
|
||||
Clause::Lambda(arg, body) =>
|
||||
if let Some(arg) = exprv(arg.clone(), pred) {
|
||||
Some(Clause::Lambda(arg, body.clone()))
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::interner::Tok;
|
||||
/// Returns the name, priority and nonzero of the expression if it is
|
||||
/// a vectorial placeholder
|
||||
#[must_use]
|
||||
pub fn vec_attrs(expr: &RuleExpr) -> Option<(Tok<String>, u64, bool)> {
|
||||
pub fn vec_attrs(expr: &RuleExpr) -> Option<(Tok<String>, usize, bool)> {
|
||||
match expr.value.clone() {
|
||||
Clause::Placeh(Placeholder {
|
||||
class: PHClass::Vec { prio, nonzero },
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::collections::VecDeque;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
@@ -17,23 +18,30 @@ use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::pipeline::file_loader::embed_to_map;
|
||||
use crate::systems::codegen::call;
|
||||
use crate::systems::stl::Boolean;
|
||||
use crate::utils::poller::{PollEvent, Poller};
|
||||
use crate::utils::unwrap_or;
|
||||
use crate::{ConstTree, Interner};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Timer {
|
||||
recurring: Boolean,
|
||||
recurring: bool,
|
||||
delay: NotNan<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 }))
|
||||
}
|
||||
|
||||
#[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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "opaque cancel operation")
|
||||
@@ -134,17 +142,16 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> {
|
||||
let mut polly = polly.borrow_mut();
|
||||
let (timeout, action, cont) = t.unpack2();
|
||||
let duration = Duration::from_secs_f64(*timeout.delay);
|
||||
let cancel_timer = if timeout.recurring.0 {
|
||||
CancelTimer(Rc::new(polly.set_interval(duration, action)))
|
||||
} else {
|
||||
CancelTimer(Rc::new(polly.set_timeout(duration, action)))
|
||||
let cancel_timer = match timeout.recurring {
|
||||
true => CancelTimer::new(polly.set_interval(duration, action)),
|
||||
false => CancelTimer::new(polly.set_timeout(duration, action)),
|
||||
};
|
||||
Ok(call(cont, [init_cps(1, cancel_timer).wrap()]).wrap())
|
||||
}
|
||||
});
|
||||
handler_table.register(move |t: Box<CPSBox<CancelTimer>>| {
|
||||
let (command, cont) = t.unpack1();
|
||||
command.0.as_ref()();
|
||||
command.cancel();
|
||||
Ok(cont)
|
||||
});
|
||||
handler_table.register({
|
||||
@@ -165,7 +172,7 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> {
|
||||
PollEvent::Event(ev) => {
|
||||
let handler = (handlers.get_mut(&ev.as_ref().type_id()))
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Unhandled messgae type: {:?}", ev.type_id())
|
||||
panic!("Unhandled messgae type: {:?}", (*ev).type_id())
|
||||
});
|
||||
let events = handler(ev);
|
||||
// we got new microtasks
|
||||
@@ -181,6 +188,8 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> {
|
||||
});
|
||||
System {
|
||||
name: vec!["system".to_string(), "asynch".to_string()],
|
||||
lexer_plugin: None,
|
||||
line_parser: None,
|
||||
constants: ConstTree::namespace(
|
||||
[i.i("system"), i.i("async")],
|
||||
ConstTree::tree([
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,11 +39,11 @@ pub fn tuple(data: impl IntoIterator<Item = ExprInst>) -> Clause {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::systems::codegen::tuple;
|
||||
use crate::{systems::codegen::tuple, foreign::Atomic};
|
||||
|
||||
#[test]
|
||||
fn tuple_printer() {
|
||||
println!("Binary tuple: {}", tuple([0.into(), 1.into()]))
|
||||
println!("Binary tuple: {}", tuple([0usize.atom_exi(), 1usize.atom_exi()]))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,24 +8,27 @@ use itertools::Itertools;
|
||||
|
||||
use super::osstring::os_string_lib;
|
||||
use crate::ddispatch::Responder;
|
||||
use crate::error::RuntimeError;
|
||||
use crate::facade::{IntoSystem, System};
|
||||
use crate::foreign::cps_box::{init_cps, CPSBox};
|
||||
use crate::foreign::{
|
||||
xfn_1ary, xfn_2ary, Atomic, AtomicReturn, InertAtomic, XfnResult,
|
||||
xfn_1ary, xfn_2ary, Atomic, AtomicReturn, InertAtomic, StrictEq, XfnResult,
|
||||
};
|
||||
use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::systems::codegen::{call, list, opt, tuple};
|
||||
use crate::systems::io::{wrap_io_error, Source};
|
||||
use crate::systems::scheduler::{SeqScheduler, SharedHandle};
|
||||
use crate::systems::stl::Boolean;
|
||||
use crate::systems::RuntimeError;
|
||||
use crate::utils::unwrap_or;
|
||||
use crate::ConstTree;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CurrentDir;
|
||||
impl Responder for CurrentDir {}
|
||||
impl StrictEq for CurrentDir {
|
||||
// never appears in macros
|
||||
fn strict_eq(&self, _: &dyn std::any::Any) -> bool { false }
|
||||
}
|
||||
impl Atomic for CurrentDir {
|
||||
fn as_any(self: Box<Self>) -> Box<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()],
|
||||
Ok(os_namev) => {
|
||||
let converted = (os_namev.into_iter())
|
||||
.map(|(n, d)| Ok(tuple([n.atom_exi(), Boolean(d).atom_exi()]).wrap()))
|
||||
.map(|(n, d)| Ok(tuple([n.atom_exi(), d.atom_exi()]).wrap()))
|
||||
.collect::<Result<Vec<_>, Clause>>();
|
||||
match converted {
|
||||
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 {
|
||||
Err(e) => vec![call(fail, [wrap_io_error(e)]).wrap()],
|
||||
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()]
|
||||
},
|
||||
},
|
||||
@@ -180,6 +183,8 @@ impl IntoSystem<'static> for DirectFS {
|
||||
name: ["system", "directfs"].into_iter().map_into().collect(),
|
||||
code: HashMap::new(),
|
||||
prelude: Vec::new(),
|
||||
lexer_plugin: None,
|
||||
line_parser: None,
|
||||
constants: ConstTree::namespace(
|
||||
[i.i("system"), i.i("directfs")],
|
||||
ConstTree::tree([
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use super::flow::IOCmdHandlePack;
|
||||
use super::instances::{BRead, ReadCmd, SRead, Sink, Source, WriteCmd};
|
||||
use crate::error::RuntimeError;
|
||||
use crate::foreign::cps_box::init_cps;
|
||||
use crate::foreign::{xfn_1ary, xfn_2ary, Atom, Atomic, XfnResult};
|
||||
use crate::foreign::{xfn_1ary, xfn_2ary, Atomic, XfnResult, Atom};
|
||||
use crate::interpreted::Clause;
|
||||
use crate::representations::OrcString;
|
||||
use crate::systems::scheduler::SharedHandle;
|
||||
use crate::systems::stl::Binary;
|
||||
use crate::systems::RuntimeError;
|
||||
use crate::{ast, ConstTree, Interner, Primitive};
|
||||
use crate::{ConstTree, Interner, ast};
|
||||
|
||||
type WriteHandle = SharedHandle<Sink>;
|
||||
type ReadHandle = SharedHandle<Source>;
|
||||
@@ -21,11 +21,11 @@ pub fn read_line(handle: ReadHandle) -> XfnResult<Clause> {
|
||||
pub fn read_bin(handle: ReadHandle) -> XfnResult<Clause> {
|
||||
Ok(init_cps(3, IOCmdHandlePack { handle, cmd: ReadCmd::RBytes(BRead::All) }))
|
||||
}
|
||||
pub fn read_bytes(handle: ReadHandle, n: u64) -> XfnResult<Clause> {
|
||||
let cmd = ReadCmd::RBytes(BRead::N(n.try_into().unwrap()));
|
||||
pub fn read_bytes(handle: ReadHandle, n: usize) -> XfnResult<Clause> {
|
||||
let cmd = ReadCmd::RBytes(BRead::N(n));
|
||||
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 msg = "greater than 255".to_string();
|
||||
RuntimeError::ext(msg, "converting number to byte")
|
||||
@@ -63,8 +63,7 @@ pub fn io_bindings<'a>(
|
||||
std_streams
|
||||
.into_iter()
|
||||
.map(|(n, at)| {
|
||||
let expr = ast::Clause::P(Primitive::Atom(Atom(at))).into_expr();
|
||||
(i.i(n), ConstTree::Const(expr))
|
||||
(i.i(n), ConstTree::clause(ast::Clause::Atom(Atom(at))))
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::interpreted::ExprInst;
|
||||
use crate::systems::codegen::call;
|
||||
use crate::systems::scheduler::{Canceller, SharedHandle};
|
||||
use crate::systems::stl::Binary;
|
||||
use crate::Literal;
|
||||
use crate::OrcString;
|
||||
|
||||
/// Any type that we can read controlled amounts of data from
|
||||
pub type Source = BufReader<Box<dyn Read + Send>>;
|
||||
@@ -63,10 +63,14 @@ impl IOCmd for ReadCmd {
|
||||
Self::RStr(sread) => {
|
||||
let mut buf = String::new();
|
||||
let sresult = match &sread {
|
||||
SRead::All => stream.read_to_string(&mut buf),
|
||||
SRead::Line => stream.read_line(&mut buf),
|
||||
SRead::All => stream.read_to_string(&mut buf).map(|_| ()),
|
||||
SRead::Line => stream.read_line(&mut buf).map(|_| {
|
||||
if buf.ends_with('\n') {
|
||||
buf.pop();
|
||||
}
|
||||
}),
|
||||
};
|
||||
ReadResult::RStr(sread, sresult.map(|_| buf))
|
||||
ReadResult::RStr(sread, sresult.map(|()| buf))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -88,14 +92,14 @@ impl ReadResult {
|
||||
vec![call(succ, [arg]).wrap()]
|
||||
},
|
||||
ReadResult::RStr(_, Ok(text)) => {
|
||||
vec![call(succ, [Literal::Str(text.into()).into()]).wrap()]
|
||||
vec![call(succ, [OrcString::from(text).atom_exi()]).wrap()]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Function to convert [io::Error] to Orchid data
|
||||
pub fn wrap_io_error(_e: io::Error) -> ExprInst { Literal::Uint(0u64).into() }
|
||||
pub fn wrap_io_error(_e: io::Error) -> ExprInst { 0usize.atom_exi() }
|
||||
|
||||
/// Writing command (string or binary)
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
||||
@@ -113,6 +113,8 @@ impl<'a, ST: IntoIterator<Item = (&'a str, Stream)>> IntoSystem<'static>
|
||||
name: None,
|
||||
}]),
|
||||
}],
|
||||
lexer_plugin: None,
|
||||
line_parser: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
//! Constants exposed to usercode by the interpreter
|
||||
mod assertion_error;
|
||||
pub mod asynch;
|
||||
pub mod cast_exprinst;
|
||||
pub mod codegen;
|
||||
pub mod directfs;
|
||||
pub mod io;
|
||||
mod runtime_error;
|
||||
pub mod scheduler;
|
||||
pub mod stl;
|
||||
|
||||
pub use assertion_error::AssertionError;
|
||||
pub use runtime_error::RuntimeError;
|
||||
|
||||
@@ -7,14 +7,17 @@ use crate::interpreted::ExprInst;
|
||||
pub type SyncResult<T> = (T, Box<dyn Any + Send>);
|
||||
pub type SyncOperation<T> =
|
||||
Box<dyn FnOnce(T, Canceller) -> SyncResult<T> + Send>;
|
||||
pub type SyncOpResultHandler<T> =
|
||||
Box<dyn FnOnce(T, Box<dyn Any + Send>, Canceller) -> (T, Vec<ExprInst>)>;
|
||||
pub type SyncOpResultHandler<T> = Box<
|
||||
dyn FnOnce(T, Box<dyn Any + Send>, Canceller) -> (T, Vec<ExprInst>)
|
||||
|
||||
+ Send,
|
||||
>;
|
||||
|
||||
struct SyncQueueItem<T> {
|
||||
cancelled: Canceller,
|
||||
operation: SyncOperation<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> {
|
||||
@@ -36,11 +39,14 @@ pub struct NextItemReport<T> {
|
||||
pub struct BusyState<T> {
|
||||
handler: SyncOpResultHandler<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> {
|
||||
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 {
|
||||
BusyState {
|
||||
handler: Box::new(|t, payload, cancel| {
|
||||
@@ -59,8 +65,8 @@ impl<T> BusyState<T> {
|
||||
pub fn enqueue<U: 'static + Send>(
|
||||
&mut self,
|
||||
operation: impl FnOnce(T, Canceller) -> (T, U) + Send + 'static,
|
||||
handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>) + 'static,
|
||||
early_cancel: impl FnOnce(T) -> (T, Vec<ExprInst>) + 'static,
|
||||
handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>) + Send + 'static,
|
||||
early_cancel: impl FnOnce(T) -> (T, Vec<ExprInst>) + Send + 'static,
|
||||
) -> Option<Canceller> {
|
||||
if self.seal.is_some() {
|
||||
return None;
|
||||
@@ -81,7 +87,7 @@ impl<T> BusyState<T> {
|
||||
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");
|
||||
self.seal = Some(Box::new(recipient))
|
||||
}
|
||||
@@ -101,7 +107,7 @@ impl<T> BusyState<T> {
|
||||
if candidate.cancelled.is_cancelled() {
|
||||
let ret = (candidate.early_cancel)(instance);
|
||||
instance = ret.0;
|
||||
events.extend(ret.1.into_iter());
|
||||
events.extend(ret.1);
|
||||
} else {
|
||||
break candidate;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::any::{type_name, Any};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
@@ -9,14 +10,13 @@ use trait_set::trait_set;
|
||||
|
||||
use super::busy::{BusyState, NextItemReportKind};
|
||||
use super::Canceller;
|
||||
use crate::error::AssertionError;
|
||||
use crate::facade::{IntoSystem, System};
|
||||
use crate::foreign::cps_box::{init_cps, CPSBox};
|
||||
use crate::foreign::{xfn_1ary, InertAtomic, XfnResult};
|
||||
use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::systems::asynch::{AsynchSystem, MessagePort};
|
||||
use crate::systems::stl::Boolean;
|
||||
use crate::systems::AssertionError;
|
||||
use crate::utils::ddispatch::Request;
|
||||
use crate::utils::thread_pool::ThreadPool;
|
||||
use crate::utils::{take_with_output, unwrap_or, IdMap};
|
||||
@@ -47,17 +47,17 @@ pub enum SharedState {
|
||||
|
||||
/// A shared handle for a resource of type `T` that can be used with a
|
||||
/// [SeqScheduler] to execute mutating operations one by one in worker threads.
|
||||
pub struct SharedHandle<T>(Rc<RefCell<SharedResource<T>>>);
|
||||
pub struct SharedHandle<T>(Arc<Mutex<SharedResource<T>>>);
|
||||
|
||||
impl<T> SharedHandle<T> {
|
||||
/// Wrap a value to be accessible to a [SeqScheduler].
|
||||
pub fn wrap(t: T) -> Self {
|
||||
Self(Rc::new(RefCell::new(SharedResource::Free(t))))
|
||||
Self(Arc::new(Mutex::new(SharedResource::Free(t))))
|
||||
}
|
||||
|
||||
/// Check the state of the handle
|
||||
pub fn state(&self) -> SharedState {
|
||||
match &*self.0.as_ref().borrow() {
|
||||
match &*self.0.lock().unwrap() {
|
||||
SharedResource::Busy(b) if b.is_sealed() => SharedState::Sealed,
|
||||
SharedResource::Busy(_) => SharedState::Busy,
|
||||
SharedResource::Free(_) => SharedState::Free,
|
||||
@@ -70,7 +70,7 @@ impl<T> SharedHandle<T> {
|
||||
/// sense as eg. an optimization. You can return the value after processing
|
||||
/// via [SyncHandle::untake].
|
||||
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)),
|
||||
_ => (state, None),
|
||||
})
|
||||
@@ -80,10 +80,13 @@ impl<T> SharedHandle<T> {
|
||||
/// is to return values synchronously after they have been removed with
|
||||
/// [SyncHandle::untake].
|
||||
pub fn untake(&self, value: T) -> Result<(), T> {
|
||||
take_with_output(&mut *self.0.as_ref().borrow_mut(), |state| match state {
|
||||
take_with_output(
|
||||
&mut *self.0.lock().unwrap(),
|
||||
|state| match state {
|
||||
SharedResource::Taken => (SharedResource::Free(value), Ok(())),
|
||||
_ => (state, Err(value)),
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<T> Clone for SharedHandle<T> {
|
||||
@@ -97,12 +100,12 @@ impl<T> Debug for SharedHandle<T> {
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl<T: 'static> InertAtomic for SharedHandle<T> {
|
||||
impl<T: Send + 'static> InertAtomic for SharedHandle<T> {
|
||||
fn type_str() -> &'static str { "a SharedHandle" }
|
||||
fn respond(&self, mut request: Request) {
|
||||
request.serve_with(|| {
|
||||
let this = self.clone();
|
||||
TakeCmd(Rc::new(move |sch| {
|
||||
TakeCmd(Arc::new(move |sch| {
|
||||
let _ = sch.seal(this.clone(), |_| Vec::new());
|
||||
}))
|
||||
})
|
||||
@@ -110,7 +113,7 @@ impl<T: 'static> InertAtomic for SharedHandle<T> {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TakeCmd(pub Rc<dyn Fn(SeqScheduler)>);
|
||||
pub struct TakeCmd(pub Arc<dyn Fn(SeqScheduler) + Send + Sync>);
|
||||
impl Debug for TakeCmd {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "A command to drop a shared resource")
|
||||
@@ -134,8 +137,8 @@ pub fn take_and_drop(x: ExprInst) -> XfnResult<Clause> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_taken_error(x: ExprInst) -> XfnResult<Boolean> {
|
||||
Ok(Boolean(x.downcast::<SealedOrTaken>().is_ok()))
|
||||
pub fn is_taken_error(x: ExprInst) -> XfnResult<bool> {
|
||||
Ok(x.downcast::<SealedOrTaken>().is_ok())
|
||||
}
|
||||
|
||||
trait_set! {
|
||||
@@ -195,11 +198,11 @@ impl SeqScheduler {
|
||||
pub fn schedule<T: Send + 'static, U: Send + 'static>(
|
||||
&self,
|
||||
handle: SharedHandle<T>,
|
||||
operation: impl FnOnce(T, Canceller) -> (T, U) + Send + 'static,
|
||||
handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>) + 'static,
|
||||
early_cancel: impl FnOnce(T) -> (T, Vec<ExprInst>) + 'static,
|
||||
operation: impl FnOnce(T, Canceller) -> (T, U) + Sync + Send + 'static,
|
||||
handler: impl FnOnce(T, U, Canceller) -> (T, Vec<ExprInst>) + Sync + Send + 'static,
|
||||
early_cancel: impl FnOnce(T) -> (T, Vec<ExprInst>) + Sync + Send + 'static,
|
||||
) -> 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();
|
||||
|state| {
|
||||
match state {
|
||||
@@ -246,10 +249,10 @@ impl SeqScheduler {
|
||||
pub fn seal<T>(
|
||||
&self,
|
||||
handle: SharedHandle<T>,
|
||||
seal: impl FnOnce(T) -> Vec<ExprInst> + 'static,
|
||||
seal: impl FnOnce(T) -> Vec<ExprInst> + Sync + Send + 'static,
|
||||
) -> Result<Vec<ExprInst>, SealedOrTaken> {
|
||||
take_with_output(
|
||||
&mut *handle.0.as_ref().borrow_mut(),
|
||||
&mut *handle.0.lock().unwrap(),
|
||||
|state| match state {
|
||||
SharedResource::Busy(mut b) if !b.is_sealed() => {
|
||||
b.seal(seal);
|
||||
@@ -281,7 +284,7 @@ impl SeqScheduler {
|
||||
let (t, u): (T, U) =
|
||||
*data.downcast().expect("This is associated by ID");
|
||||
let handle2 = handle.clone();
|
||||
take_with_output(&mut *handle.0.as_ref().borrow_mut(), |state| {
|
||||
take_with_output(&mut *handle.0.lock().unwrap(), |state| {
|
||||
let busy = unwrap_or! { state => SharedResource::Busy;
|
||||
panic!("Handle with outstanding invocation must be busy")
|
||||
};
|
||||
@@ -329,6 +332,8 @@ impl IntoSystem<'static> for SeqScheduler {
|
||||
prelude: Vec::new(),
|
||||
code: HashMap::new(),
|
||||
handlers,
|
||||
lexer_plugin: None,
|
||||
line_parser: None,
|
||||
constants: ConstTree::namespace(
|
||||
[i.i("system"), i.i("scheduler")],
|
||||
ConstTree::tree([
|
||||
|
||||
@@ -3,15 +3,16 @@ use std::sync::Arc;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::Boolean;
|
||||
use crate::error::RuntimeError;
|
||||
use crate::foreign::{
|
||||
xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, Atomic, InertAtomic, XfnResult,
|
||||
};
|
||||
use crate::interpreted::Clause;
|
||||
use crate::systems::codegen::{opt, tuple};
|
||||
use crate::systems::RuntimeError;
|
||||
use crate::utils::{iter_find, unwrap_or};
|
||||
use crate::{ConstTree, Interner, Literal};
|
||||
use crate::{ConstTree, Interner};
|
||||
|
||||
const INT_BYTES: usize = usize::BITS as usize / 8;
|
||||
|
||||
/// A block of binary data
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
@@ -43,93 +44,86 @@ pub fn concatenate(a: Binary, b: Binary) -> XfnResult<Binary> {
|
||||
}
|
||||
|
||||
/// Extract a subsection of the binary data
|
||||
pub fn slice(s: Binary, i: u64, len: u64) -> XfnResult<Binary> {
|
||||
if i + len < s.0.len() as u64 {
|
||||
pub fn slice(s: Binary, i: usize, len: usize) -> XfnResult<Binary> {
|
||||
if i + len < s.0.len() {
|
||||
RuntimeError::fail(
|
||||
"Byte index out of bounds".to_string(),
|
||||
"indexing binary",
|
||||
)?
|
||||
}
|
||||
let data = s.0[i as usize..i as usize + len as usize].to_vec();
|
||||
Ok(Binary(Arc::new(data)))
|
||||
Ok(Binary(Arc::new(s.0[i..i + len].to_vec())))
|
||||
}
|
||||
|
||||
/// Return the index where the first argument first contains the second, if any
|
||||
pub fn find(haystack: Binary, needle: Binary) -> XfnResult<Clause> {
|
||||
let found = iter_find(haystack.0.iter(), needle.0.iter());
|
||||
Ok(opt(found.map(|x| Literal::Uint(x as u64).into())))
|
||||
Ok(opt(found.map(usize::atom_exi)))
|
||||
}
|
||||
|
||||
/// Split binary data block into two smaller blocks
|
||||
pub fn split(bin: Binary, i: u64) -> XfnResult<Clause> {
|
||||
if bin.0.len() < i as usize {
|
||||
pub fn split(bin: Binary, i: usize) -> XfnResult<Clause> {
|
||||
if bin.0.len() < i {
|
||||
RuntimeError::fail(
|
||||
"Byte index out of bounds".to_string(),
|
||||
"splitting binary",
|
||||
)?
|
||||
}
|
||||
let (asl, bsl) = bin.0.split_at(i as usize);
|
||||
Ok(tuple([
|
||||
Binary(Arc::new(asl.to_vec())).atom_cls().into(),
|
||||
Binary(Arc::new(bsl.to_vec())).atom_cls().into(),
|
||||
]))
|
||||
let (asl, bsl) = bin.0.split_at(i);
|
||||
Ok(tuple([asl, bsl].map(|s| Binary(Arc::new(s.to_vec())).atom_exi())))
|
||||
}
|
||||
|
||||
/// Read a number from a binary blob
|
||||
pub fn get_num(
|
||||
buf: Binary,
|
||||
loc: u64,
|
||||
size: u64,
|
||||
is_le: Boolean,
|
||||
) -> XfnResult<Literal> {
|
||||
if buf.0.len() < (loc + size) as usize {
|
||||
loc: usize,
|
||||
size: usize,
|
||||
is_le: bool,
|
||||
) -> XfnResult<usize> {
|
||||
if buf.0.len() < (loc + size) {
|
||||
RuntimeError::fail(
|
||||
"section out of range".to_string(),
|
||||
"reading number from binary data",
|
||||
)?
|
||||
}
|
||||
if 8 < size {
|
||||
if INT_BYTES < size {
|
||||
RuntimeError::fail(
|
||||
"more than 8 bytes provided".to_string(),
|
||||
"more than std::bin::int_bytes bytes provided".to_string(),
|
||||
"reading number from binary data",
|
||||
)?
|
||||
}
|
||||
let mut data = [0u8; 8];
|
||||
let section = &buf.0[loc as usize..(loc + size) as usize];
|
||||
let num = if is_le.0 {
|
||||
data[0..size as usize].copy_from_slice(section);
|
||||
u64::from_le_bytes(data)
|
||||
let mut data = [0u8; INT_BYTES];
|
||||
let section = &buf.0[loc..(loc + size)];
|
||||
let num = if is_le {
|
||||
data[0..size].copy_from_slice(section);
|
||||
usize::from_le_bytes(data)
|
||||
} else {
|
||||
data[8 - size as usize..].copy_from_slice(section);
|
||||
u64::from_be_bytes(data)
|
||||
data[INT_BYTES - size..].copy_from_slice(section);
|
||||
usize::from_be_bytes(data)
|
||||
};
|
||||
Ok(Literal::Uint(num))
|
||||
Ok(num)
|
||||
}
|
||||
|
||||
/// Convert a number into a blob
|
||||
pub fn from_num(size: u64, is_le: Boolean, data: u64) -> XfnResult<Binary> {
|
||||
if size > 8 {
|
||||
pub fn from_num(size: usize, is_le: bool, data: usize) -> XfnResult<Binary> {
|
||||
if INT_BYTES < size {
|
||||
RuntimeError::fail(
|
||||
"more than 8 bytes requested".to_string(),
|
||||
"more than std::bin::int_bytes bytes requested".to_string(),
|
||||
"converting number to binary",
|
||||
)?
|
||||
}
|
||||
let bytes = if is_le.0 {
|
||||
data.to_le_bytes()[0..size as usize].to_vec()
|
||||
} else {
|
||||
data.to_be_bytes()[8 - size as usize..].to_vec()
|
||||
let bytes = match is_le {
|
||||
true => data.to_le_bytes()[0..size].to_vec(),
|
||||
false => data.to_be_bytes()[8 - size..].to_vec(),
|
||||
};
|
||||
Ok(Binary(Arc::new(bytes)))
|
||||
}
|
||||
|
||||
/// Detect the number of bytes in the blob
|
||||
pub fn size(b: Binary) -> XfnResult<Literal> {
|
||||
Ok(Literal::Uint(b.0.len() as u64))
|
||||
}
|
||||
pub fn size(b: Binary) -> XfnResult<usize> { Ok(b.0.len()) }
|
||||
|
||||
pub fn bin(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("bin"),
|
||||
i.i("binary"),
|
||||
ConstTree::tree([
|
||||
(i.i("concat"), ConstTree::xfn(xfn_2ary(concatenate))),
|
||||
(i.i("slice"), ConstTree::xfn(xfn_3ary(slice))),
|
||||
@@ -138,6 +132,7 @@ pub fn bin(i: &Interner) -> ConstTree {
|
||||
(i.i("get_num"), ConstTree::xfn(xfn_4ary(get_num))),
|
||||
(i.i("from_num"), ConstTree::xfn(xfn_3ary(from_num))),
|
||||
(i.i("size"), ConstTree::xfn(xfn_1ary(size))),
|
||||
(i.i("int_bytes"), ConstTree::atom(INT_BYTES)),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
export operators[ != == ]
|
||||
export ::(!=, ==)
|
||||
|
||||
export const not := \bool. if bool then false else true
|
||||
macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b))
|
||||
macro ...$a == ...$b =0x3p36=> (equals (...$a) (...$b))
|
||||
export macro ...$a and ...$b =0x4p36=> (ifthenelse (...$a) (...$b) false)
|
||||
export macro ...$a or ...$b =0x4p36=> (ifthenelse (...$a) true (...$b))
|
||||
export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> (
|
||||
ifthenelse (...$cond) (...$true) (...$false)
|
||||
)
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
use crate::foreign::{xfn_1ary, xfn_2ary, InertAtomic, XfnResult};
|
||||
use crate::foreign::{xfn_1ary, xfn_2ary, XfnResult, Atom};
|
||||
use crate::interner::Interner;
|
||||
use crate::representations::interpreted::Clause;
|
||||
use crate::systems::AssertionError;
|
||||
use crate::{ConstTree, Literal, Location};
|
||||
use crate::error::AssertionError;
|
||||
use crate::{ConstTree, Location, OrcString};
|
||||
|
||||
/// Booleans exposed to Orchid
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Boolean(pub bool);
|
||||
impl InertAtomic for Boolean {
|
||||
fn type_str() -> &'static str { "a boolean" }
|
||||
}
|
||||
|
||||
impl From<bool> for Boolean {
|
||||
fn from(value: bool) -> Self { Self(value) }
|
||||
}
|
||||
use super::Numeric;
|
||||
|
||||
/// Takes a boolean and two branches, runs the first if the bool is true, the
|
||||
/// second if it's false.
|
||||
// Even though it's a ternary function, IfThenElse is implemented as an unary
|
||||
// foreign function, as the rest of the logic can be defined in Orchid.
|
||||
pub fn if_then_else(b: Boolean) -> XfnResult<Clause> {
|
||||
Ok(match b.0 {
|
||||
pub fn if_then_else(b: bool) -> XfnResult<Clause> {
|
||||
Ok(match b {
|
||||
true => Clause::pick(Clause::constfn(Clause::LambdaArg)),
|
||||
false => Clause::constfn(Clause::pick(Clause::LambdaArg)),
|
||||
})
|
||||
@@ -29,16 +20,25 @@ pub fn if_then_else(b: Boolean) -> XfnResult<Clause> {
|
||||
/// Compares the inner values if
|
||||
///
|
||||
/// - both are string,
|
||||
/// - both are bool,
|
||||
/// - both are either uint or num
|
||||
pub fn equals(a: Literal, b: Literal) -> XfnResult<Boolean> {
|
||||
Ok(Boolean::from(match (a, b) {
|
||||
(Literal::Str(s1), Literal::Str(s2)) => s1 == s2,
|
||||
(Literal::Num(n1), Literal::Num(n2)) => n1 == n2,
|
||||
(Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2,
|
||||
(Literal::Num(n1), Literal::Uint(u1)) => *n1 == (u1 as f64),
|
||||
(Literal::Uint(u1), Literal::Num(n1)) => *n1 == (u1 as f64),
|
||||
(..) => AssertionError::fail(Location::Unknown, "the expected type")?,
|
||||
}))
|
||||
pub fn equals(a: Atom, b: Atom) -> XfnResult<bool> {
|
||||
let (a, b) = match (a.try_downcast::<OrcString>(), b.try_downcast::<OrcString>()) {
|
||||
(Ok(a), Ok(b)) => return Ok(a == b),
|
||||
(Err(a), Err(b)) => (a, b),
|
||||
_ => return Ok(false),
|
||||
};
|
||||
match (a.request::<Numeric>(), b.request::<Numeric>()) {
|
||||
(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 {
|
||||
@@ -47,8 +47,8 @@ pub fn bool(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("ifthenelse"), ConstTree::xfn(xfn_1ary(if_then_else))),
|
||||
(i.i("equals"), ConstTree::xfn(xfn_2ary(equals))),
|
||||
(i.i("true"), ConstTree::atom(Boolean(true))),
|
||||
(i.i("false"), ConstTree::atom(Boolean(false))),
|
||||
(i.i("true"), ConstTree::atom(true)),
|
||||
(i.i("false"), ConstTree::atom(false)),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
|
||||
@@ -1,48 +1,45 @@
|
||||
use chumsky::Parser;
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::ArithmeticError;
|
||||
use crate::foreign::{xfn_1ary, ExternError, XfnResult};
|
||||
use super::Numeric;
|
||||
use crate::error::AssertionError;
|
||||
use crate::foreign::{xfn_1ary, Atom, XfnResult};
|
||||
use crate::interner::Interner;
|
||||
use crate::parse::{float_parser, int_parser};
|
||||
use crate::systems::AssertionError;
|
||||
use crate::{ConstTree, Literal, Location};
|
||||
use crate::parse::parse_num;
|
||||
use crate::{ConstTree, Location, OrcString};
|
||||
|
||||
fn to_numeric(a: Atom) -> XfnResult<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.
|
||||
pub fn to_float(l: Literal) -> XfnResult<Literal> {
|
||||
match l {
|
||||
Literal::Str(s) => float_parser()
|
||||
.parse(s.as_str())
|
||||
.map(Literal::Num)
|
||||
.map_err(|_| AssertionError::ext(Location::Unknown, "float syntax")),
|
||||
n @ Literal::Num(_) => Ok(n),
|
||||
Literal::Uint(i) => NotNan::new(i as f64)
|
||||
.map(Literal::Num)
|
||||
.map_err(|_| ArithmeticError::NaN.into_extern()),
|
||||
}
|
||||
pub fn to_float(a: Atom) -> XfnResult<NotNan<f64>> {
|
||||
to_numeric(a).map(|n| n.as_float())
|
||||
}
|
||||
|
||||
/// Parse an unsigned integer. Accepts the same formats Orchid does. If the
|
||||
/// input is a number, floors it.
|
||||
pub fn to_uint(l: Literal) -> XfnResult<Literal> {
|
||||
match l {
|
||||
Literal::Str(s) => int_parser()
|
||||
.parse(s.as_str())
|
||||
.map(Literal::Uint)
|
||||
.map_err(|_| AssertionError::ext(Location::Unknown, "int syntax")),
|
||||
Literal::Num(n) => Ok(Literal::Uint(n.floor() as u64)),
|
||||
i @ Literal::Uint(_) => Ok(i),
|
||||
}
|
||||
pub fn to_uint(a: Atom) -> XfnResult<usize> {
|
||||
to_numeric(a).map(|n| match n {
|
||||
Numeric::Float(f) => f.floor() as usize,
|
||||
Numeric::Uint(i) => i,
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a literal to a string using Rust's conversions for floats, chars and
|
||||
/// uints respectively
|
||||
pub fn to_string(l: Literal) -> XfnResult<Literal> {
|
||||
Ok(match l {
|
||||
Literal::Uint(i) => Literal::Str(i.to_string().into()),
|
||||
Literal::Num(n) => Literal::Str(n.to_string().into()),
|
||||
s @ Literal::Str(_) => s,
|
||||
})
|
||||
pub fn to_string(a: Atom) -> XfnResult<OrcString> {
|
||||
a.try_downcast::<OrcString>()
|
||||
.or_else(|e| e.try_downcast::<usize>().map(|i| i.to_string().into()))
|
||||
.or_else(|e| e.try_downcast::<NotNan<f64>>().map(|i| i.to_string().into()))
|
||||
.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 {
|
||||
|
||||
27
src/systems/stl/exit_status.rs
Normal file
27
src/systems/stl/exit_status.rs
Normal 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
Reference in New Issue
Block a user