STL rework

- fixed lots of bugs
- overlay libraries work correctly and reliably
- the STL is an overlay library
- examples updated
This commit is contained in:
2023-06-17 21:12:23 +01:00
parent 5bb8a12fc2
commit aebbf51228
91 changed files with 1444 additions and 1395 deletions

225
Cargo.lock generated
View File

@@ -24,6 +24,15 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.3.2" version = "0.3.2"
@@ -85,6 +94,25 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bstr"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5"
dependencies = [
"memchr",
"serde",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.79" version = "1.0.79"
@@ -109,9 +137,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.2.7" version = "4.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -120,9 +148,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.2.7" version = "4.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -133,9 +161,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.2.0" version = "4.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@@ -145,9 +173,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.4.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
@@ -155,6 +183,35 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "cpufeatures"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.11" version = "1.0.11"
@@ -188,6 +245,22 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.8" version = "0.2.8"
@@ -199,6 +272,19 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "globset"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@@ -273,6 +359,21 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.15" version = "0.2.15"
@@ -299,15 +400,16 @@ dependencies = [
"itertools", "itertools",
"ordered-float", "ordered-float",
"paste", "paste",
"rust-embed",
"thiserror", "thiserror",
"trait-set", "trait-set",
] ]
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "3.6.0" version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a384337e997e6860ffbaa83708b2ef329fd8c54cb67a5f64d421e0f943254f" checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@@ -345,6 +447,58 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "regex"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "rust-embed"
version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 1.0.109",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "7.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731"
dependencies = [
"globset",
"sha2",
"walkdir",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.37.19" version = "0.37.19"
@@ -359,6 +513,32 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
[[package]]
name = "sha2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "stacker" name = "stacker"
version = "0.1.15" version = "0.1.15"
@@ -431,6 +611,12 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.8" version = "1.0.8"
@@ -449,6 +635,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@@ -471,6 +667,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View File

@@ -23,11 +23,12 @@ doc = false
[dependencies] [dependencies]
thiserror = "1.0" thiserror = "1.0"
chumsky = "0.9.2" chumsky = "0.9"
hashbrown = "0.13.2" hashbrown = "0.13"
ordered-float = "3.0" ordered-float = "3.7"
itertools = "0.10" itertools = "0.10"
dyn-clone = "1.0.11" dyn-clone = "1.0"
clap = { version = "4.2.4", features = ["derive"] } clap = { version = "4.3", features = ["derive"] }
trait-set = "0.3.0" trait-set = "0.3"
paste = "1.0.12" paste = "1.0"
rust-embed = { version = "6.6", features = ["include-exclude"] }

View File

@@ -1,16 +1,15 @@
import std::(parse_float, to_string) import std::(proc::*, to_float, to_string, io::(readline, print))
import std::(readline, print)
export main := do{ export main := do{
cps print "left operand: "; cps print "left operand: ";
cps data = readline; cps data = readline;
let a = parse_float data; let a = to_float data;
cps print "operator: "; cps print "operator: ";
cps op = readline; cps op = readline;
cps print ("you selected \"" ++ op ++ "\"\n"); cps print ("you selected \"" ++ op ++ "\"\n");
cps print "right operand: "; cps print "right operand: ";
cps data = readline; cps data = readline;
let b = parse_float data; let b = to_float data;
let result = ( let result = (
if op == "+" then a + b if op == "+" then a + b
else if op == "-" then a - b else if op == "-" then a - b

View File

@@ -1,3 +1,3 @@
import std::print import std::io::print
main := print "Hello, world!\n" "goodbye" main := print "Hello, world!\n" "goodbye"

View File

@@ -1,41 +0,0 @@
import option
import super::fn::*
pair := \a.\b. \f. f a b
-- Constructors
export cons := \hd.\tl. option::some (pair hd tl)
export end := option::none
export pop := \list.\default.\f.list default \cons.cons f
-- Operators
export reduce := \list.\acc.\f. (
loop r on (list acc) with
pop list acc \head.\tail. r tail (f acc head)
)
export map := \list.\f. (
loop r on (list) with
pop list end \head.\tail. cons (f head) (r tail)
)
export skip := \list.\n. (
loop r on (list n) with
if n == 0 then list
else pop list end \head.\tail. r tail (n - 1)
)
export take := \list.\n. (
loop r on (list n) with
if n == 0 then end
else pop list end \head.\tail. cons head $ r tail $ n - 1
)
new[...$item, ...$rest:1] =0x2p84=> (cons (...$item) new[...$rest])
new[...$end] =0x1p84=> (cons (...$end) end)
new[] =0x1p84=> end
export ::(new)

View File

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

View File

@@ -1,6 +0,0 @@
export some := \v. \d.\f. f v
export none := \d.\f. d
export map := \option.\f. option none f
export flatten := \option. option none \opt. opt
export flatmap := \option.\f. option none \opt. map opt f

View File

@@ -1,15 +0,0 @@
export Y := \f.(\x.f (x x))(\x.f (x x))
export loop $r on (..$parameters) with ...$tail =0x5p129=> Y (\$r.
bind_names (..$parameters) (...$tail)
) ..$parameters
-- bind each of the names in the first argument as a parameter for the second argument
bind_names ($name ..$rest) $payload =0x1p250=> \$name. bind_names (..$rest) $payload
bind_names () (...$payload) =0x1p250=> ...$payload
export ...$prefix $ ...$suffix:1 =0x1p34=> ...$prefix (...$suffix)
export ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix
export (...$argv) => ...$body =0x2p129=> (bind_names (...$argv) (...$body))
$name => ...$body =0x1p129=> (\$name. ...$body)

View File

@@ -1,8 +1,4 @@
import list import std::(proc::*, io::print, to_string)
import map
import option
import fn::*
import std::(print, to_string)
export main := do{ export main := do{
let foo = map::new[ let foo = map::new[

View File

@@ -36,35 +36,13 @@
}, },
"extensions": { "extensions": {
"recommendations": [ "recommendations": [
"tomoki1207.pdf",
"james-yu.latex-workshop",
"bungcip.better-toml", "bungcip.better-toml",
"maptz.regionfolder", "maptz.regionfolder",
"serayuzgur.crates", "serayuzgur.crates",
"tamasfe.even-better-toml", "tamasfe.even-better-toml",
"haskell.haskell",
"justusadam.language-haskell",
"yzhang.markdown-all-in-one", "yzhang.markdown-all-in-one",
"goessner.mdmath", "gruntfuggly.todo-tree",
"gruntfuggly.todo-tree" "vadimcn.vscode-lldb"
] ]
}, },
"launch": {
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Cargo launch",
"cwd": "${workspaceFolder:orchid}",
"program": "${workspaceFolder}/target/debug/orchid",
"cargo": {
"args": [
"run",
]
},
"args": []
}
]
}
} }

View File

@@ -12,7 +12,6 @@ newline_style = "Unix"
normalize_comments = true normalize_comments = true
wrap_comments = true wrap_comments = true
overflow_delimited_expr = true overflow_delimited_expr = true
single_line_if_else_max_width = 50
use_small_heuristics = "Max" use_small_heuristics = "Max"
# literals # literals

2
src/bin/cli/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
mod prompt;
pub use prompt::cmd_prompt;

11
src/bin/cli/prompt.rs Normal file
View File

@@ -0,0 +1,11 @@
use std::io::{self, Error, Write};
pub fn cmd_prompt(prompt: &str) -> Result<(String, Vec<String>), Error> {
print!("{}", prompt);
io::stdout().flush()?;
let mut cmdln = String::new();
io::stdin().read_line(&mut cmdln)?;
let mut segments = cmdln.split(' ');
let cmd = if let Some(cmd) = segments.next() { cmd } else { "" };
Ok((cmd.to_string(), segments.map(str::to_string).collect()))
}

View File

@@ -1,170 +1,166 @@
use std::borrow::Borrow; mod cli;
use std::fs::File; use std::fs::File;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc; use std::process;
use clap::Parser; use clap::Parser;
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use itertools::Itertools;
use orchidlang::interner::{InternedDisplay, Interner, Sym}; use orchidlang::interner::{InternedDisplay, Interner, Sym};
use orchidlang::pipeline::file_loader::{mk_cache, Loaded}; use orchidlang::{ast, ast_to_interpreted, interpreter, pipeline, rule, stl};
use orchidlang::pipeline::{
collect_consts, collect_rules, from_const_tree, parse_layer, ProjectTree, use crate::cli::cmd_prompt;
};
use orchidlang::rule::Repo;
use orchidlang::sourcefile::{FileEntry, Import};
use orchidlang::{ast_to_interpreted, interpreter, stl};
/// Orchid interpreter /// Orchid interpreter
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
struct Args { struct Args {
/// Folder containing main.orc /// Folder containing main.orc or the manually specified entry module
#[arg(short, long, default_value = ".")] #[arg(short, long, default_value = ".")]
pub project: String, pub dir: String,
/// Entrypoint for the interpreter
#[arg(short, long, default_value = "main::main")]
pub main: String,
/// Maximum number of steps taken by the macro executor
#[arg(long, default_value_t = 10_000)]
pub macro_limit: usize,
/// Print the parsed ruleset and exit
#[arg(long)]
pub dump_repo: bool,
/// Step through the macro execution process in the specified symbol
#[arg(long, default_value = "")]
pub macro_debug: String,
} }
impl Args { impl Args {
/// Validate the project directory and the
pub fn chk_dir_main(&self) -> Result<(), String> {
let dir_path = PathBuf::from(&self.dir);
if !dir_path.is_dir() {
return Err(format!("{} is not a directory", dir_path.display()));
}
let segs = self.main.split("::").collect::<Vec<_>>();
if segs.len() < 2 {
return Err("Entry point too short".to_string());
}
let (pathsegs, _) = segs.split_at(segs.len() - 1);
let mut possible_files = pathsegs.iter().scan(dir_path, |path, seg| {
path.push(seg);
Some(path.with_extension("orc"))
});
if possible_files.all(|p| File::open(p).is_err()) {
return Err(format!(
"{} not found in {}",
pathsegs.join("::"),
PathBuf::from(&self.dir).display()
));
}
Ok(())
}
pub fn chk_proj(&self) -> Result<(), String> { pub fn chk_proj(&self) -> Result<(), String> {
let mut path = PathBuf::from(&self.project); self.chk_dir_main()
path.push(PathBuf::from("main.orc")); }
if File::open(&path).is_ok() { }
Ok(())
} else { /// Load and parse all source related to the symbol `target` or all symbols
Err(format!("{} not found", path.display())) /// in the namespace `target` in the context of the STL. All sourcefiles must
/// reside within `dir`.
fn load_dir(dir: &Path, target: Sym, i: &Interner) -> pipeline::ProjectTree {
let file_cache = pipeline::file_loader::mk_dir_cache(dir.to_path_buf(), i);
let library = stl::mk_stl(i, stl::StlOptions::default());
pipeline::parse_layer(
&[target],
&|path| file_cache.find(&path),
&library,
&stl::mk_prelude(i),
i,
)
.expect("Failed to load source code")
}
pub fn to_sym(data: &str, i: &Interner) -> Sym {
i.i(&data.split("::").map(|s| i.i(s)).collect::<Vec<_>>()[..])
}
/// A little utility to step through the resolution of a macro set
pub fn macro_debug(repo: rule::Repo, mut code: ast::Expr, i: &Interner) {
let mut idx = 0;
println!("Macro debugger working on {}", code.bundle(i));
loop {
let (cmd, _) = cmd_prompt("cmd> ").unwrap();
match cmd.trim() {
"" | "n" | "next" =>
if let Some(c) = repo.step(&code) {
idx += 1;
code = c;
println!("Step {idx}: {}", code.bundle(i));
},
"p" | "print" => println!("Step {idx}: {}", code.bundle(i)),
"d" | "dump" => println!("Rules: {}", repo.bundle(i)),
"q" | "quit" => return,
"h" | "help" => println!(
"Available commands:
\t<blank>, n, next\t\ttake a step
\tp, print\t\tprint the current state
\tq, quit\t\texit
\th, help\t\tprint this text"
),
_ => {
println!("unrecognized command \"{}\", try \"help\"", cmd);
continue;
},
} }
} }
} }
fn main() { pub fn main() {
let args = Args::parse(); let args = Args::parse();
args.chk_proj().unwrap_or_else(|e| panic!("{e}")); args.chk_proj().unwrap_or_else(|e| panic!("{e}"));
run_dir(PathBuf::try_from(args.project).unwrap().borrow()); let dir = PathBuf::try_from(args.dir).unwrap();
}
static PRELUDE_TXT: &str = r#"
import std::(
add, subtract, multiply, remainder, divide,
equals, ifthenelse,
concatenate
)
export ...$a + ...$b =0x2p36=> (add (...$a) (...$b))
export ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b))
export ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b))
export ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b))
export ...$a / ...$b:1 =0x1p36=> (divide (...$a) (...$b))
export ...$a == ...$b =0x3p36=> (equals (...$a) (...$b))
export ...$a ++ ...$b =0x4p36=> (concatenate (...$a) (...$b))
export do { ...$statement ; ...$rest:1 } =0x2p130=> statement (...$statement) do { ...$rest }
export do { ...$return } =0x1p130=> ...$return
export statement (let $name = ...$value) ...$next =0x1p230=> (
(\$name. ...$next) (...$value)
)
export statement (cps $name = ...$operation) ...$next =0x2p230=> (
(...$operation) \$name. ...$next
)
export statement (cps ...$operation) ...$next =0x1p230=> (
(...$operation) (...$next)
)
export if ...$cond then ...$true else ...$false:1 =0x1p84=> (
ifthenelse (...$cond) (...$true) (...$false)
)
export ::(,)
"#;
fn prelude_path(i: &Interner) -> Sym {
i.i(&[i.i("prelude")][..])
}
fn mainmod_path(i: &Interner) -> Sym {
i.i(&[i.i("main")][..])
}
fn entrypoint(i: &Interner) -> Sym {
i.i(&[i.i("main"), i.i("main")][..])
}
fn load_environment(i: &Interner) -> ProjectTree {
let env = from_const_tree(
HashMap::from([(i.i("std"), stl::mk_stl(i))]),
&[i.i("std")],
i,
);
let loader = |path: Sym| {
if path == prelude_path(i) {
Ok(Loaded::Code(Rc::new(PRELUDE_TXT.to_string())))
} else {
panic!(
"Prelude pointed to non-std path {}",
i.extern_vec(path).join("::")
)
}
};
parse_layer(&[prelude_path(i)], &loader, &env, &[], i).expect("prelude error")
}
fn load_dir(i: &Interner, dir: &Path) -> ProjectTree {
let environment = load_environment(i);
let file_cache = mk_cache(dir.to_path_buf(), i);
let loader = |path| file_cache.find(&path);
let prelude =
[FileEntry::Import(vec![Import { path: prelude_path(i), name: None }])];
parse_layer(&[mainmod_path(i)], &loader, &environment, &prelude, i)
.expect("Failed to load source code")
}
pub fn run_dir(dir: &Path) {
let i = Interner::new(); let i = Interner::new();
let project = load_dir(&i, dir); let main = to_sym(&args.main, &i);
let rules = collect_rules(&project); let project = load_dir(&dir, main, &i);
let consts = collect_consts(&project, &i); let rules = pipeline::collect_rules(&project);
println!("Initializing rule repository with {} rules", rules.len()); let consts = pipeline::collect_consts(&project, &i);
let repo = Repo::new(rules, &i).unwrap_or_else(|(rule, error)| { let repo = rule::Repo::new(rules, &i).unwrap_or_else(|(rule, error)| {
panic!( panic!(
"Rule error: {} "Rule error: {}
Offending rule: {}", Offending rule: {}",
error.bundle(&i), error.bundle(&i),
rule.bundle(&i) rule.bundle(&i)
) )
}); });
println!("Repo dump: {}", repo.bundle(&i)); if args.dump_repo {
println!("Parsed rules: {}", repo.bundle(&i));
return;
} else if !args.macro_debug.is_empty() {
let name = to_sym(&args.macro_debug, &i);
let code = consts
.get(&name)
.unwrap_or_else(|| panic!("Constant {} not found", args.macro_debug));
return macro_debug(repo, code.clone(), &i);
}
let mut exec_table = HashMap::new(); let mut exec_table = HashMap::new();
for (name, source) in consts.iter() { for (name, source) in consts.iter() {
// let nval = entrypoint(&i); let name = &nval; let source = &consts[name];
let mut tree = source.clone();
let displayname = i.extern_vec(*name).join("::"); let displayname = i.extern_vec(*name).join("::");
let macro_timeout = 100; let (unmatched, steps_left) = repo.long_step(source, args.macro_limit + 1);
println!("Executing macros in {displayname}...",); assert!(steps_left > 0, "Macro execution in {displayname} did not halt");
let mut idx = 0; let runtree = ast_to_interpreted(&unmatched).unwrap_or_else(|e| {
let unmatched = loop { panic!("Postmacro conversion error in {displayname}: {e}")
if idx == macro_timeout { });
panic!("Macro execution in {displayname} didn't halt")
}
match repo.step(&tree) {
None => break tree,
Some(phase) => {
println!("Step {idx}/{macro_timeout}: {}", phase.bundle(&i));
tree = phase;
},
}
idx += 1;
};
let runtree = ast_to_interpreted(&unmatched)
.unwrap_or_else(|e| panic!("Postmacro conversion error: {e}"));
exec_table.insert(*name, runtree); exec_table.insert(*name, runtree);
} }
println!("macro execution complete");
let ctx = let ctx =
interpreter::Context { symbols: &exec_table, interner: &i, gas: None }; interpreter::Context { symbols: &exec_table, interner: &i, gas: None };
let entrypoint = exec_table.get(&entrypoint(&i)).unwrap_or_else(|| { let entrypoint = exec_table.get(&main).unwrap_or_else(|| {
let main = args.main;
let symbols =
exec_table.keys().map(|t| i.extern_vec(*t).join("::")).join(", ");
panic!( panic!(
"entrypoint not found, known keys are: {}", "Entrypoint not found!
exec_table Entrypoint was {main}
.keys() known keys are {symbols}"
.map(|t| i.r(*t).iter().map(|t| i.r(*t)).join("::"))
.join(", ")
) )
}); });
let io_handler = orchidlang::stl::handleIO; let io_handler = orchidlang::stl::handleIO;
@@ -173,12 +169,11 @@ pub fn run_dir(dir: &Path) {
ret.unwrap_or_else(|e| panic!("Runtime error: {}", e)); ret.unwrap_or_else(|e| panic!("Runtime error: {}", e));
if inert { if inert {
println!("Settled at {}", state.expr().clause.bundle(&i)); println!("Settled at {}", state.expr().clause.bundle(&i));
println!( if let Some(g) = gas {
"Remaining gas: {}", println!("Remaining gas: {g}")
gas.map(|g| g.to_string()).unwrap_or(String::from("")) }
); } else if gas == Some(0) {
} eprintln!("Ran out of gas!");
if gas == Some(0) { process::exit(-1);
println!("Ran out of gas!")
} }
} }

View File

@@ -6,14 +6,21 @@ use std::fmt::Debug;
#[allow(unused)] // for the doc comments #[allow(unused)] // for the doc comments
use dyn_clone::DynClone; use dyn_clone::DynClone;
#[allow(unused)] // for the doc comments
use crate::define_fn;
#[allow(unused)] // for the doc comments #[allow(unused)] // for the doc comments
use crate::foreign::{Atomic, ExternFn}; use crate::foreign::{Atomic, ExternFn};
#[allow(unused)] // for the doc comments #[allow(unused)] // for the doc comments
use crate::write_fn_step;
#[allow(unused)] // for the doc comments
use crate::Primitive; use crate::Primitive;
/// A macro that generates implementations of [Atomic] to simplify the /// A macro that generates implementations of [Atomic] to simplify the
/// development of external bindings for Orchid. /// development of external bindings for Orchid.
/// ///
/// Most use cases are fulfilled by [define_fn], pathological cases can combine
/// [write_fn_step] with manual [Atomic] implementations.
///
/// The macro depends on implementations of [`AsRef<Clause>`] and /// The macro depends on implementations of [`AsRef<Clause>`] and
/// [`From<(&Self, Clause)>`] for extracting the clause to be processed and then /// [`From<(&Self, Clause)>`] for extracting the clause to be processed and then
/// reconstructing the [Atomic]. Naturally, supertraits of [Atomic] are also /// reconstructing the [Atomic]. Naturally, supertraits of [Atomic] are also
@@ -32,34 +39,35 @@ use crate::Primitive;
/// ///
/// _definition of the `add` function in the STL_ /// _definition of the `add` function in the STL_
/// ``` /// ```
/// use orchidlang::{Literal};
/// use orchidlang::interpreted::ExprInst; /// use orchidlang::interpreted::ExprInst;
/// use orchidlang::stl::Numeric; /// use orchidlang::stl::litconv::with_lit;
/// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl}; /// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl};
/// ///
/// /// Convert a literal to a string using Rust's conversions for floats, chars and
/// /// uints respectively
/// #[derive(Clone)] /// #[derive(Clone)]
/// pub struct Add2; /// struct ToString;
/// externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1 { x }));
/// ///
/// #[derive(Debug, Clone)] /// externfn_impl!{
/// pub struct Add1 { /// ToString, |_: &Self, expr_inst: ExprInst|{
/// x: ExprInst, /// Ok(InternalToString {
/// expr_inst
/// })
/// }
/// } /// }
/// atomic_redirect!(Add1, x); /// #[derive(std::fmt::Debug,Clone)]
/// atomic_impl!(Add1); /// struct InternalToString {
/// externfn_impl!(Add1, |this: &Self, x: ExprInst| { /// expr_inst: ExprInst,
/// let a: Numeric = this.x.clone().try_into()?;
/// Ok(Add0 { a, x })
/// });
///
/// #[derive(Debug, Clone)]
/// pub struct Add0 {
/// a: Numeric,
/// x: ExprInst,
/// } /// }
/// atomic_redirect!(Add0, x); /// atomic_redirect!(InternalToString, expr_inst);
/// atomic_impl!(Add0, |Self { a, x }: &Self, _| { /// atomic_impl!(InternalToString, |Self { expr_inst }: &Self, _|{
/// let b: Numeric = x.clone().try_into()?; /// with_lit(expr_inst, |l| Ok(match l {
/// Ok((*a + b).into()) /// Literal::Char(c) => c.to_string(),
/// Literal::Uint(i) => i.to_string(),
/// Literal::Num(n) => n.to_string(),
/// Literal::Str(s) => s.clone(),
/// })).map(|s| Literal::Str(s).into())
/// }); /// });
/// ``` /// ```
#[macro_export] #[macro_export]
@@ -92,7 +100,11 @@ macro_rules! atomic_impl {
// branch off or wrap up // branch off or wrap up
let clause = if inert { let clause = if inert {
let closure = $next_phase; let closure = $next_phase;
match closure(&next_self, ctx) { let res: Result<
$crate::interpreted::Clause,
std::rc::Rc<dyn $crate::foreign::ExternError>,
> = closure(&next_self, ctx);
match res {
Ok(r) => r, Ok(r) => r,
Err(e) => return Err($crate::interpreter::RuntimeError::Extern(e)), Err(e) => return Err($crate::interpreter::RuntimeError::Extern(e)),
} }

View File

@@ -26,6 +26,15 @@ use crate::write_fn_step;
/// defined in the first step and returns a [Result] of the success type or /// defined in the first step and returns a [Result] of the success type or
/// `Rc<dyn ExternError>`. /// `Rc<dyn ExternError>`.
/// ///
/// To avoid typing the same expression a lot, the conversion is optional.
/// If it is omitted, the field is initialized with a [TryInto::try_into] call
/// from `&ExprInst` to the target type. In this case, the error is
/// short-circuited using `?` so conversions through `FromResidual` are allowed.
/// The optional syntax starts with `as`.
///
/// If all conversions are omitted, the alias definition (`expr=$ident in`) has
/// no effect and is therefore optional.
///
/// Finally, the body of the function is provided as an expression which can /// Finally, the body of the function is provided as an expression which can
/// reference all of the arguments by their names, each bound to a ref of the /// reference all of the arguments by their names, each bound to a ref of the
/// specified type. /// specified type.
@@ -45,13 +54,59 @@ use crate::write_fn_step;
/// } /// }
/// } /// }
/// ``` /// ```
///
/// A simpler format is also offered for unary functions:
///
/// ```
/// use orchidlang::stl::litconv::with_lit;
/// use orchidlang::{define_fn, Literal};
///
/// define_fn! {
/// /// Convert a literal to a string using Rust's conversions for floats,
/// /// chars and uints respectively
/// ToString = |x| with_lit(x, |l| Ok(match l {
/// Literal::Char(c) => c.to_string(),
/// Literal::Uint(i) => i.to_string(),
/// Literal::Num(n) => n.to_string(),
/// Literal::Str(s) => s.clone(),
/// })).map(|s| Literal::Str(s).into())
/// }
/// ```
#[macro_export] #[macro_export]
macro_rules! define_fn { macro_rules! define_fn {
// Unary function entry
($( #[ $attr:meta ] )* $qual:vis $name:ident = $body:expr) => {paste::paste!{
$crate::write_fn_step!(
$( #[ $attr ] )* $qual $name
>
[< Internal $name >]
);
$crate::write_fn_step!(
[< Internal $name >]
{}
out = expr => Ok(expr);
{
let lambda = $body;
lambda(out)
}
);
}};
// xname is optional only if every conversion is implicit
($( #[ $attr:meta ] )* $qual:vis $name:ident {
$( $arg:ident: $typ:ty ),+
} => $body:expr) => {
$crate::define_fn!{expr=expr in
$( #[ $attr ] )* $qual $name {
$( $arg: $typ ),*
} => $body
}
};
// multi-parameter function entry
(expr=$xname:ident in (expr=$xname:ident in
$( #[ $attr:meta ] )* $( #[ $attr:meta ] )*
$qual:vis $name:ident { $qual:vis $name:ident {
$arg0:ident: $typ0:ty as $parse0:expr $arg0:ident: $typ0:ty $( as $parse0:expr )?
$(, $arg:ident: $typ:ty as $parse:expr )* $(, $arg:ident: $typ:ty $( as $parse:expr )? )*
} => $body:expr } => $body:expr
) => {paste::paste!{ ) => {paste::paste!{
// Generate initial state // Generate initial state
@@ -64,8 +119,10 @@ macro_rules! define_fn {
$crate::define_fn!(@MIDDLE $xname [< Internal $name >] ($body) $crate::define_fn!(@MIDDLE $xname [< Internal $name >] ($body)
() ()
( (
($arg0: $typ0 as $parse0) ( $arg0: $typ0 $( as $parse0)? )
$( ($arg: $typ as $parse) )* $(
( $arg: $typ $( as $parse)? )
)*
) )
); );
}}; }};
@@ -80,10 +137,10 @@ macro_rules! define_fn {
// later fields // later fields
( (
// field that should be processed by this step // field that should be processed by this step
( $arg0:ident: $typ0:ty as $parse0:expr ) ( $arg0:ident: $typ0:ty $( as $parse0:expr )? )
// ensure that we have a next stage // ensure that we have a next stage
$( $(
( $arg:ident: $typ:ty as $parse:expr ) ( $arg:ident: $typ:ty $( as $parse:expr )? )
)+ )+
) )
) => {paste::paste!{ ) => {paste::paste!{
@@ -93,7 +150,7 @@ macro_rules! define_fn {
$( $arg_prev:ident : $typ_prev:ty ),* $( $arg_prev:ident : $typ_prev:ty ),*
} }
[< $name $arg0:upper >] [< $name $arg0:upper >]
where $arg0:$typ0 = $xname => $parse0; where $arg0:$typ0 $( = $xname => $parse0 )? ;
); );
$crate::define_fn!(@MIDDLE $xname [< $name $arg0:upper >] ($body) $crate::define_fn!(@MIDDLE $xname [< $name $arg0:upper >] ($body)
( (
@@ -101,7 +158,9 @@ macro_rules! define_fn {
($arg0: $typ0) ($arg0: $typ0)
) )
( (
$( ($arg: $typ as $parse) )+ $(
( $arg: $typ $( as $parse)? )
)+
) )
); );
}}; }};
@@ -113,7 +172,7 @@ macro_rules! define_fn {
) )
// the last one is initialized before the body runs // the last one is initialized before the body runs
( (
($arg0:ident: $typ0:ty as $parse0:expr) ($arg0:ident: $typ0:ty $( as $parse0:expr )? )
) )
) => { ) => {
$crate::write_fn_step!( $crate::write_fn_step!(
@@ -121,7 +180,7 @@ macro_rules! define_fn {
{ {
$( $arg_prev: $typ_prev ),* $( $arg_prev: $typ_prev ),*
} }
$arg0:$typ0 = $xname => $parse0; $arg0:$typ0 $( = $xname => $parse0 )? ;
$body $body
); );
}; };

View File

@@ -1,13 +1,17 @@
#[allow(unused)] // for doc #[allow(unused)] // for doc
use crate::define_fn; use crate::define_fn;
#[allow(unused)] // for doc #[allow(unused)] // for doc
use crate::foreign::Atomic;
#[allow(unused)] // for doc
use crate::foreign::ExternFn; use crate::foreign::ExternFn;
#[allow(unused)] // for doc #[allow(unused)] // for doc
use crate::interpreted::ExprInst; use crate::interpreted::ExprInst;
/// Write one step in the state machine representing a simple n-ary non-variadic /// Write one step in the state machine representing a simple n-ary non-variadic
/// Orchid function. There are no known use cases for it that aren't expressed /// Orchid function. Most use cases are better covered by [define_fn] which
/// better with [define_fn] which generates calls to this macro. /// generates calls to this macro. This macro can be used in combination with
/// manual [Atomic] implementations to define a function that only behaves like
/// a simple n-ary non-variadic function with respect to some of its arguments.
/// ///
/// There are three ways to call this macro for the initial state, internal /// There are three ways to call this macro for the initial state, internal
/// state, and exit state. All of them are demonstrated in one example and /// state, and exit state. All of them are demonstrated in one example and
@@ -25,14 +29,14 @@ use crate::interpreted::ExprInst;
/// // Middle state /// // Middle state
/// write_fn_step!( /// write_fn_step!(
/// CharAt1 {} /// CharAt1 {}
/// CharAt0 where s = x => with_str(x, |s| Ok(s.clone())); /// CharAt0 where s: String = x => with_str(x, |s| Ok(s.clone()));
/// ); /// );
/// // Exit state /// // Exit state
/// write_fn_step!( /// write_fn_step!(
/// CharAt0 { s: String } /// CharAt0 { s: String }
/// i = x => with_uint(x, Ok); /// i = x => with_uint(x, Ok);
/// { /// {
/// if let Some(c) = s.chars().nth(i as usize) { /// if let Some(c) = s.chars().nth(*i as usize) {
/// Ok(Clause::P(Primitive::Literal(Literal::Char(c)))) /// Ok(Clause::P(Primitive::Literal(Literal::Char(c))))
/// } else { /// } else {
/// RuntimeError::fail( /// RuntimeError::fail(
@@ -52,7 +56,7 @@ use crate::interpreted::ExprInst;
/// struct definition. A field called `expr_inst` of type [ExprInst] is added /// struct definition. A field called `expr_inst` of type [ExprInst] is added
/// implicitly, so the first middle state has an empty field list. The next /// implicitly, so the first middle state has an empty field list. The next
/// state is also provided, alongside the name and conversion of the next /// state is also provided, alongside the name and conversion of the next
/// parameter from a [&ExprInst] under the provided alias to a /// parameter from a `&ExprInst` under the provided alias to a
/// `Result<_, Rc<dyn ExternError>>`. The success type is inferred from the /// `Result<_, Rc<dyn ExternError>>`. The success type is inferred from the
/// type of the field at the place of its actual definition. This conversion is /// type of the field at the place of its actual definition. This conversion is
/// done in the implementation of [ExternFn] which also places the new /// done in the implementation of [ExternFn] which also places the new
@@ -67,6 +71,12 @@ use crate::interpreted::ExprInst;
/// argument names bound. The arguments here are all references to their actual /// argument names bound. The arguments here are all references to their actual
/// types except for the last one which is converted from [ExprInst] immediately /// types except for the last one which is converted from [ExprInst] immediately
/// before the body is evaluated. /// before the body is evaluated.
///
/// To avoid typing the same parsing process a lot, the conversion is optional.
/// If it is omitted, the field is initialized with a [TryInto::try_into] call
/// from `&ExprInst` to the target type. In this case, the error is
/// short-circuited using `?` so conversions through `FromResidual` are allowed.
/// The optional syntax starts with the `=` sign and ends before the semicolon.
#[macro_export] #[macro_export]
macro_rules! write_fn_step { macro_rules! write_fn_step {
// write entry stage // write entry stage
@@ -87,7 +97,7 @@ macro_rules! write_fn_step {
$( $arg:ident : $typ:ty ),* $( $arg:ident : $typ:ty ),*
} }
$next:ident where $next:ident where
$added:ident $( : $added_typ:ty )? = $xname:ident => $extract:expr ; $added:ident $( : $added_typ:ty )? $( = $xname:ident => $extract:expr )? ;
) => { ) => {
$( #[ $attr ] )* $( #[ $attr ] )*
#[derive(std::fmt::Debug, Clone)] #[derive(std::fmt::Debug, Clone)]
@@ -100,8 +110,8 @@ macro_rules! write_fn_step {
$crate::externfn_impl!( $crate::externfn_impl!(
$name, $name,
|this: &Self, expr_inst: $crate::interpreted::ExprInst| { |this: &Self, expr_inst: $crate::interpreted::ExprInst| {
let $xname = &this.expr_inst; let $added $( :$added_typ )? =
let $added $( :$added_typ )? = $extract?; $crate::write_fn_step!(@CONV &this.expr_inst $(, $xname $extract )?);
Ok($next{ Ok($next{
$( $arg: this.$arg.clone(), )* $( $arg: this.$arg.clone(), )*
$added, expr_inst $added, expr_inst
@@ -114,23 +124,37 @@ macro_rules! write_fn_step {
$( #[ $attr:meta ] )* $quant:vis $name:ident { $( #[ $attr:meta ] )* $quant:vis $name:ident {
$( $arg:ident: $typ:ty ),* $( $arg:ident: $typ:ty ),*
} }
$added:ident $(: $added_typ:ty )? = $xname:ident => $extract:expr ; $added:ident $(: $added_typ:ty )? $( = $xname:ident => $extract:expr )? ;
$process:expr $process:expr
) => { ) => {
$( #[ $attr ] )* $( #[ $attr ] )*
#[derive(std::fmt::Debug, Clone)] #[derive(std::fmt::Debug, Clone)]
$quant struct $name { $quant struct $name {
$( $arg: $typ, )+ $( $arg: $typ, )*
expr_inst: $crate::interpreted::ExprInst, expr_inst: $crate::interpreted::ExprInst,
} }
$crate::atomic_redirect!($name, expr_inst); $crate::atomic_redirect!($name, expr_inst);
$crate::atomic_impl!( $crate::atomic_impl!(
$name, $name,
|Self{ $($arg, )* expr_inst }: &Self, _| { |Self{ $($arg, )* expr_inst }: &Self, _| {
let $xname = expr_inst; let added $(: $added_typ )? =
let $added $(: $added_typ )? = $extract?; $crate::write_fn_step!(@CONV expr_inst $(, $xname $extract )?);
let $added = &added;
$process $process
} }
); );
}; };
// Write conversion expression for an ExprInst
(@CONV $locxname:expr, $xname:ident $extract:expr) => {
{
let $xname = $locxname;
match $extract {
Err(e) => return Err(e),
Ok(r) => r,
}
}
};
(@CONV $locxname:expr) => {
($locxname).try_into()?
};
} }

View File

@@ -52,7 +52,7 @@ impl<T: Eq + Hash + Clone> TypedInterner<T> {
pub fn r(&self, t: Tok<T>) -> &T { pub fn r(&self, t: Tok<T>) -> &T {
let values = self.values.borrow(); let values = self.values.borrow();
let key = t.into_usize() - 1; let key = t.into_usize() - 1;
values[key].0 values[key].0.borrow()
} }
/// Intern a static reference without allocating the data on the heap /// Intern a static reference without allocating the data on the heap
@@ -91,7 +91,7 @@ impl<T: Eq + Hash + Clone> Drop for TypedInterner<T> {
if !owned { if !owned {
continue; continue;
} }
unsafe { Box::from_raw((item as *const T).cast_mut()) }; let _ = unsafe { Box::from_raw((item as *const T).cast_mut()) };
} }
} }
} }

View File

@@ -18,6 +18,7 @@ pub mod rule;
pub mod stl; pub mod stl;
mod utils; mod utils;
pub use interner::Sym;
pub use representations::ast_to_interpreted::ast_to_interpreted; pub use representations::ast_to_interpreted::ast_to_interpreted;
pub use representations::{ pub use representations::{
ast, interpreted, sourcefile, tree, Literal, Location, PathSet, Primitive, ast, interpreted, sourcefile, tree, Literal, Location, PathSet, Primitive,

View File

@@ -83,11 +83,7 @@ pub fn import_parser<'a>(
Some(Import { Some(Import {
path: ctx.interner().i(&path), path: ctx.interner().i(&path),
name: { name: {
if name == ctx.interner().i("*") { if name == ctx.interner().i("*") { None } else { Some(name) }
None
} else {
Some(name)
}
}, },
}) })
}) })

View File

@@ -115,11 +115,7 @@ impl InternedDisplay for Lexeme {
Self::PH(Placeholder { name, class }) => match *class { Self::PH(Placeholder { name, class }) => match *class {
PHClass::Scalar => write!(f, "${}", i.r(*name)), PHClass::Scalar => write!(f, "${}", i.r(*name)),
PHClass::Vec { nonzero, prio } => { PHClass::Vec { nonzero, prio } => {
if nonzero { if nonzero { write!(f, "...") } else { write!(f, "..") }?;
write!(f, "...")
} else {
write!(f, "..")
}?;
write!(f, "${}", i.r(*name))?; write!(f, "${}", i.r(*name))?;
if prio != 0 { if prio != 0 {
write!(f, ":{}", prio)?; write!(f, ":{}", prio)?;

View File

@@ -1,8 +1,11 @@
//! File system implementation of the source loader callback //! Source loader callback definition and builtin implementations
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc; use std::rc::Rc;
use std::{fs, io}; use std::{fs, io};
use chumsky::text::Character;
use rust_embed::RustEmbed;
use crate::interner::{Interner, Sym}; use crate::interner::{Interner, Sym};
use crate::pipeline::error::{ use crate::pipeline::error::{
ErrorPosition, ProjectError, UnexpectedDirectory, ErrorPosition, ProjectError, UnexpectedDirectory,
@@ -86,13 +89,52 @@ pub fn load_file(root: &Path, path: &[impl AsRef<str>]) -> IOResult {
} }
/// Generates a cached file loader for a directory /// Generates a cached file loader for a directory
pub fn mk_cache(root: PathBuf, i: &Interner) -> Cache<Sym, IOResult> { pub fn mk_dir_cache(root: PathBuf, i: &Interner) -> Cache<Sym, IOResult> {
Cache::new(move |token: Sym, _this| -> IOResult { Cache::new(move |token: Sym, _this| -> IOResult {
let path = i.r(token).iter().map(|t| i.r(*t).as_str()).collect::<Vec<_>>(); let path = i.r(token).iter().map(|t| i.r(*t).as_str()).collect::<Vec<_>>();
load_file(&root, &path) load_file(&root, &path)
}) })
} }
/// Load a file from the specified path from an embed table
pub fn load_embed<T: 'static + RustEmbed>(path: &str, ext: &str) -> IOResult {
let file_path = path.to_string() + ext;
if let Some(file) = T::get(&file_path) {
let s = file.data.iter().map(|c| c.to_char()).collect::<String>();
Ok(Loaded::Code(Rc::new(s)))
} else {
let entries = T::iter()
.map(|c| c.to_string())
.filter_map(|path: String| {
let item_prefix = path.to_string() + "/";
path.strip_prefix(&item_prefix).map(|subpath| {
let item_name = subpath
.split_inclusive('/')
.next()
.expect("Exact match excluded earlier");
item_name
.strip_suffix('/') // subdirectory
.or_else(|| item_name.strip_suffix(ext)) // file
.expect("embed should be filtered to extension")
.to_string()
})
})
.collect::<Vec<String>>();
Ok(Loaded::Collection(Rc::new(entries)))
}
}
/// Generates a cached file loader for a [RustEmbed]
pub fn mk_embed_cache<'a, T: 'static + RustEmbed>(
ext: &'a str,
i: &'a Interner,
) -> Cache<'a, Sym, IOResult> {
Cache::new(move |token: Sym, _this| -> IOResult {
let path = i.extern_vec(token).join("/");
load_embed::<T>(&path, ext)
})
}
/// Loads the string contents of a file at the given location. /// Loads the string contents of a file at the given location.
/// If the path points to a directory, raises an error. /// If the path points to a directory, raises an error.
pub fn load_text( pub fn load_text(

View File

@@ -3,14 +3,14 @@ use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use super::alias_map::AliasMap; use super::alias_map::AliasMap;
use super::decls::InjectedAsFn; use super::decls::{InjectedAsFn, UpdatedFn};
use crate::ast::{Expr, Rule}; use crate::ast::{Expr, Rule};
use crate::interner::{Interner, Sym, Tok}; use crate::interner::{Interner, Sym, Tok};
use crate::pipeline::{ProjectExt, ProjectModule}; use crate::pipeline::{ProjectExt, ProjectModule};
use crate::representations::tree::{ModEntry, ModMember}; use crate::representations::tree::{ModEntry, ModMember};
use crate::utils::Substack; use crate::utils::Substack;
fn resolve( fn resolve_rec(
token: Sym, token: Sym,
alias_map: &AliasMap, alias_map: &AliasMap,
i: &Interner, i: &Interner,
@@ -18,7 +18,7 @@ fn resolve(
if let Some(alias) = alias_map.resolve(token) { if let Some(alias) = alias_map.resolve(token) {
Some(i.r(alias).clone()) Some(i.r(alias).clone())
} else if let Some((foot, body)) = i.r(token).split_last() { } else if let Some((foot, body)) = i.r(token).split_last() {
let mut new_beginning = resolve(i.i(body), alias_map, i)?; let mut new_beginning = resolve_rec(i.i(body), alias_map, i)?;
new_beginning.push(*foot); new_beginning.push(*foot);
Some(new_beginning) Some(new_beginning)
} else { } else {
@@ -26,6 +26,18 @@ fn resolve(
} }
} }
fn resolve(
token: Sym,
alias_map: &AliasMap,
injected_as: &impl InjectedAsFn,
i: &Interner,
) -> Option<Sym> {
injected_as(&i.r(token)[..]).or_else(|| {
let next_v = resolve_rec(token, alias_map, i)?;
Some(injected_as(&next_v).unwrap_or_else(|| i.i(&next_v)))
})
}
fn process_expr( fn process_expr(
expr: &Expr, expr: &Expr,
alias_map: &AliasMap, alias_map: &AliasMap,
@@ -33,16 +45,7 @@ fn process_expr(
i: &Interner, i: &Interner,
) -> Expr { ) -> Expr {
expr expr
.map_names(&|n| { .map_names(&|n| resolve(n, alias_map, injected_as, i))
injected_as(&i.r(n)[..]).or_else(|| {
let next_v = resolve(n, alias_map, i)?;
// println!("Resolved alias {} to {}",
// i.extern_vec(n).join("::"),
// i.extern_all(&next_v).join("::")
// );
Some(injected_as(&next_v).unwrap_or_else(|| i.i(&next_v)))
})
})
.unwrap_or_else(|| expr.clone()) .unwrap_or_else(|| expr.clone())
} }
@@ -54,10 +57,9 @@ fn apply_aliases_rec(
alias_map: &AliasMap, alias_map: &AliasMap,
i: &Interner, i: &Interner,
injected_as: &impl InjectedAsFn, injected_as: &impl InjectedAsFn,
updated: &impl UpdatedFn,
) -> ProjectModule { ) -> ProjectModule {
let items = module let items = (module.items.iter())
.items
.iter()
.map(|(name, ent)| { .map(|(name, ent)| {
let ModEntry { exported, member } = ent; let ModEntry { exported, member } = ent;
let member = match member { let member = match member {
@@ -65,9 +67,7 @@ fn apply_aliases_rec(
ModMember::Item(process_expr(expr, alias_map, injected_as, i)), ModMember::Item(process_expr(expr, alias_map, injected_as, i)),
ModMember::Sub(module) => { ModMember::Sub(module) => {
let subpath = path.push(*name); let subpath = path.push(*name);
let is_ignored = let new_mod = if !updated(&subpath.iter().rev_vec_clone()) {
injected_as(&subpath.iter().rev_vec_clone()).is_some();
let new_mod = if is_ignored {
module.clone() module.clone()
} else { } else {
let module = module.as_ref(); let module = module.as_ref();
@@ -77,6 +77,7 @@ fn apply_aliases_rec(
alias_map, alias_map,
i, i,
injected_as, injected_as,
updated,
)) ))
}; };
ModMember::Sub(new_mod) ModMember::Sub(new_mod)
@@ -85,23 +86,18 @@ fn apply_aliases_rec(
(*name, ModEntry { exported: *exported, member }) (*name, ModEntry { exported: *exported, member })
}) })
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
let rules = module let rules = (module.extra.rules.iter())
.extra
.rules
.iter()
.map(|rule| { .map(|rule| {
let Rule { pattern, prio, template } = rule; let Rule { pattern, prio, template } = rule;
Rule { Rule {
prio: *prio, prio: *prio,
pattern: Rc::new( pattern: Rc::new(
pattern (pattern.iter())
.iter()
.map(|expr| process_expr(expr, alias_map, injected_as, i)) .map(|expr| process_expr(expr, alias_map, injected_as, i))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
), ),
template: Rc::new( template: Rc::new(
template (template.iter())
.iter()
.map(|expr| process_expr(expr, alias_map, injected_as, i)) .map(|expr| process_expr(expr, alias_map, injected_as, i))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
), ),
@@ -113,7 +109,11 @@ fn apply_aliases_rec(
imports: module.imports.clone(), imports: module.imports.clone(),
extra: ProjectExt { extra: ProjectExt {
rules, rules,
exports: module.extra.exports.clone(), exports: (module.extra.exports.iter())
.map(|(k, v)| {
(*k, resolve(*v, alias_map, injected_as, i).unwrap_or(*v))
})
.collect(),
file: module.extra.file.clone(), file: module.extra.file.clone(),
imports_from: module.extra.imports_from.clone(), imports_from: module.extra.imports_from.clone(),
}, },
@@ -125,6 +125,14 @@ pub fn apply_aliases(
alias_map: &AliasMap, alias_map: &AliasMap,
i: &Interner, i: &Interner,
injected_as: &impl InjectedAsFn, injected_as: &impl InjectedAsFn,
updated: &impl UpdatedFn,
) -> ProjectModule { ) -> ProjectModule {
apply_aliases_rec(Substack::Bottom, module, alias_map, i, injected_as) apply_aliases_rec(
Substack::Bottom,
module,
alias_map,
i,
injected_as,
updated,
)
} }

View File

@@ -1,12 +1,13 @@
use core::panic;
use std::rc::Rc; use std::rc::Rc;
use super::alias_map::AliasMap; use super::alias_map::AliasMap;
use super::decls::InjectedAsFn; use super::decls::UpdatedFn;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::pipeline::error::{NotExported, ProjectError}; use crate::pipeline::error::{NotExported, ProjectError};
use crate::pipeline::project_tree::{split_path, ProjectModule, ProjectTree}; use crate::pipeline::project_tree::{split_path, ProjectModule, ProjectTree};
use crate::representations::tree::{ModMember, WalkErrorKind}; use crate::representations::tree::{ModMember, WalkErrorKind};
use crate::utils::{pushed, Substack}; use crate::utils::{pushed, unwrap_or, Substack};
/// Assert that a module identified by a path can see a given symbol /// Assert that a module identified by a path can see a given symbol
fn assert_visible( fn assert_visible(
@@ -15,36 +16,40 @@ fn assert_visible(
project: &ProjectTree, project: &ProjectTree,
i: &Interner, i: &Interner,
) -> Result<(), Rc<dyn ProjectError>> { ) -> Result<(), Rc<dyn ProjectError>> {
let (tgt_item, tgt_path) = if let Some(s) = target.split_last() { let (tgt_item, tgt_path) = unwrap_or!(target.split_last(); return Ok(()));
s
} else {
return Ok(());
};
let shared_len = let shared_len =
source.iter().zip(tgt_path.iter()).take_while(|(a, b)| a == b).count(); source.iter().zip(tgt_path.iter()).take_while(|(a, b)| a == b).count();
let shared_root = let vis_ignored_len = usize::min(tgt_path.len(), shared_len + 1);
project.0.walk(&tgt_path[..shared_len], false).expect("checked in parsing"); let private_root =
let direct_parent = (project.0).walk(&tgt_path[..vis_ignored_len], false).unwrap_or_else(|e| {
shared_root.walk(&tgt_path[shared_len..], true).map_err(|e| { let path_slc = &tgt_path[..vis_ignored_len];
match e.kind { let bad_path = i.extern_all(path_slc).join("::");
WalkErrorKind::Missing => panic!("checked in parsing"), eprintln!(
WalkErrorKind::Private => { "Error while walking {bad_path}; {:?} on step {}",
let full_path = &tgt_path[..shared_len + e.pos]; e.kind, e.pos
let (file, sub) = split_path(full_path, project); );
let (ref_file, ref_sub) = split_path(source, project); eprintln!("looking from {}", i.extern_all(source).join("::"));
NotExported { panic!("")
file: i.extern_all(file), });
subpath: i.extern_all(sub), let direct_parent = private_root
referrer_file: i.extern_all(ref_file), .walk(&tgt_path[vis_ignored_len..], true)
referrer_subpath: i.extern_all(ref_sub), .map_err(|e| match e.kind {
} WalkErrorKind::Missing => panic!("checked in parsing"),
.rc() WalkErrorKind::Private => {
}, let full_path = &tgt_path[..shared_len + e.pos];
} let (file, sub) = split_path(full_path, project);
let (ref_file, ref_sub) = split_path(source, project);
NotExported {
file: i.extern_all(file),
subpath: i.extern_all(sub),
referrer_file: i.extern_all(ref_file),
referrer_subpath: i.extern_all(ref_sub),
}
.rc()
},
})?; })?;
let tgt_item_exported = direct_parent.extra.exports.contains_key(tgt_item); let tgt_item_exported = direct_parent.extra.exports.contains_key(tgt_item);
let target_prefixes_source = let target_prefixes_source = shared_len == tgt_path.len();
shared_len == tgt_path.len() && source.get(shared_len) == Some(tgt_item);
if !tgt_item_exported && !target_prefixes_source { if !tgt_item_exported && !target_prefixes_source {
let (file, sub) = split_path(target, project); let (file, sub) = split_path(target, project);
let (ref_file, ref_sub) = split_path(source, project); let (ref_file, ref_sub) = split_path(source, project);
@@ -69,20 +74,30 @@ fn collect_aliases_rec(
project: &ProjectTree, project: &ProjectTree,
alias_map: &mut AliasMap, alias_map: &mut AliasMap,
i: &Interner, i: &Interner,
injected_as: &impl InjectedAsFn, updated: &impl UpdatedFn,
) -> Result<(), Rc<dyn ProjectError>> { ) -> Result<(), Rc<dyn ProjectError>> {
// Assume injected module has been alias-resolved // Assume injected module has been alias-resolved
let mod_path_v = path.iter().rev_vec_clone(); let mod_path_v = path.iter().rev_vec_clone();
if injected_as(&mod_path_v).is_some() { if !updated(&mod_path_v) {
return Ok(()); return Ok(());
}; };
for (&name, &target_mod) in module.extra.imports_from.iter() { for (&name, &target_mod_name) in module.extra.imports_from.iter() {
let target_mod_v = i.r(target_mod); let target_mod_v = i.r(target_mod_name);
let target_sym_v = pushed(target_mod_v, name); let target_sym_v = pushed(target_mod_v, name);
assert_visible(&mod_path_v, &target_sym_v, project, i)?; assert_visible(&mod_path_v, &target_sym_v, project, i)?;
let sym_path_v = pushed(&mod_path_v, name); let sym_path_v = pushed(&mod_path_v, name);
let sym_path = i.i(&sym_path_v); let sym_path = i.i(&sym_path_v);
let target_sym = i.i(&target_sym_v); let target_mod = (project.0.walk(target_mod_v, false))
.expect("checked above in assert_visible");
let target_sym =
*target_mod.extra.exports.get(&name).unwrap_or_else(|| {
panic!(
"error in {}, {} has no member {}",
i.extern_all(&mod_path_v).join("::"),
i.extern_all(target_mod_v).join("::"),
i.r(name)
)
});
alias_map.link(sym_path, target_sym); alias_map.link(sym_path, target_sym);
} }
for (&name, entry) in module.items.iter() { for (&name, entry) in module.items.iter() {
@@ -97,7 +112,7 @@ fn collect_aliases_rec(
project, project,
alias_map, alias_map,
i, i,
injected_as, updated,
)? )?
} }
Ok(()) Ok(())
@@ -109,14 +124,7 @@ pub fn collect_aliases(
project: &ProjectTree, project: &ProjectTree,
alias_map: &mut AliasMap, alias_map: &mut AliasMap,
i: &Interner, i: &Interner,
injected_as: &impl InjectedAsFn, updated: &impl UpdatedFn,
) -> Result<(), Rc<dyn ProjectError>> { ) -> Result<(), Rc<dyn ProjectError>> {
collect_aliases_rec( collect_aliases_rec(Substack::Bottom, module, project, alias_map, i, updated)
Substack::Bottom,
module,
project,
alias_map,
i,
injected_as,
)
} }

View File

@@ -4,4 +4,5 @@ use crate::interner::{Sym, Tok};
trait_set! { trait_set! {
pub trait InjectedAsFn = Fn(&[Tok<String>]) -> Option<Sym>; pub trait InjectedAsFn = Fn(&[Tok<String>]) -> Option<Sym>;
pub trait UpdatedFn = Fn(&[Tok<String>]) -> bool;
} }

View File

@@ -1,11 +1,9 @@
use std::rc::Rc; use std::rc::Rc;
use itertools::Itertools;
use super::alias_map::AliasMap; use super::alias_map::AliasMap;
use super::apply_aliases::apply_aliases; use super::apply_aliases::apply_aliases;
use super::collect_aliases::collect_aliases; use super::collect_aliases::collect_aliases;
use super::decls::InjectedAsFn; use super::decls::{InjectedAsFn, UpdatedFn};
use crate::interner::Interner; use crate::interner::Interner;
use crate::pipeline::error::ProjectError; use crate::pipeline::error::ProjectError;
use crate::pipeline::project_tree::ProjectTree; use crate::pipeline::project_tree::ProjectTree;
@@ -16,21 +14,11 @@ pub fn resolve_imports(
project: ProjectTree, project: ProjectTree,
i: &Interner, i: &Interner,
injected_as: &impl InjectedAsFn, injected_as: &impl InjectedAsFn,
updated: &impl UpdatedFn,
) -> Result<ProjectTree, Rc<dyn ProjectError>> { ) -> Result<ProjectTree, Rc<dyn ProjectError>> {
let mut map = AliasMap::new(); let mut map = AliasMap::new();
collect_aliases(project.0.as_ref(), &project, &mut map, i, injected_as)?; collect_aliases(project.0.as_ref(), &project, &mut map, i, updated)?;
println!( let new_mod =
"Aliases: {{{:?}}}", apply_aliases(project.0.as_ref(), &map, i, injected_as, updated);
map
.targets
.iter()
.map(|(kt, vt)| format!(
"{} => {}",
i.extern_vec(*kt).join("::"),
i.extern_vec(*vt).join("::")
))
.join(", ")
);
let new_mod = apply_aliases(project.0.as_ref(), &map, i, injected_as);
Ok(ProjectTree(Rc::new(new_mod))) Ok(ProjectTree(Rc::new(new_mod)))
} }

View File

@@ -6,7 +6,6 @@ mod import_resolution;
mod parse_layer; mod parse_layer;
mod project_tree; mod project_tree;
mod source_loader; mod source_loader;
mod split_name;
pub use parse_layer::parse_layer; pub use parse_layer::parse_layer;
pub use project_tree::{ pub use project_tree::{

View File

@@ -34,13 +34,16 @@ pub fn parse_layer(
}; };
let source = let source =
source_loader::load_source(targets, prelude, i, loader, &|path| { source_loader::load_source(targets, prelude, i, loader, &|path| {
injected_as(path).is_some() environment.0.walk(&i.r(path)[..], false).is_ok()
})?; })?;
let tree = project_tree::build_tree(source, i, prelude, &injected_names)?; let tree = project_tree::build_tree(source, i, prelude, &injected_names)?;
let sum = ProjectTree(Rc::new( let sum = ProjectTree(Rc::new(
environment.0.as_ref().clone() + tree.0.as_ref().clone(), environment.0.as_ref().clone().overlay(tree.0.as_ref().clone()),
)); ));
let resolvd = import_resolution::resolve_imports(sum, i, &injected_as)?; let resolvd =
import_resolution::resolve_imports(sum, i, &injected_as, &|path| {
tree.0.walk(path, false).is_ok()
})?;
// Addition among modules favours the left hand side. // Addition among modules favours the left hand side.
Ok(resolvd) Ok(resolvd)
} }

View File

@@ -57,11 +57,7 @@ fn source_to_module(
let imports = data let imports = data
.iter() .iter()
.filter_map(|ent| { .filter_map(|ent| {
if let FileEntry::Import(impv) = ent { if let FileEntry::Import(impv) = ent { Some(impv.iter()) } else { None }
Some(impv.iter())
} else {
None
}
}) })
.flatten() .flatten()
.cloned() .cloned()
@@ -182,6 +178,10 @@ fn files_to_module(
i: &Interner, i: &Interner,
) -> Rc<Module<Expr, ProjectExt>> { ) -> Rc<Module<Expr, ProjectExt>> {
let lvl = path.len(); let lvl = path.len();
debug_assert!(
files.iter().map(|f| f.path.len()).max().unwrap() >= lvl,
"path is longer than any of the considered file paths"
);
let path_v = path.iter().rev_vec_clone(); let path_v = path.iter().rev_vec_clone();
if files.len() == 1 && files[0].path.len() == lvl { if files.len() == 1 && files[0].path.len() == lvl {
return source_to_module( return source_to_module(
@@ -227,6 +227,7 @@ pub fn build_tree(
prelude: &[FileEntry], prelude: &[FileEntry],
injected: &impl InjectedOperatorsFn, injected: &impl InjectedOperatorsFn,
) -> Result<ProjectTree, Rc<dyn ProjectError>> { ) -> Result<ProjectTree, Rc<dyn ProjectError>> {
assert!(!files.is_empty(), "A tree requires at least one module");
let ops_cache = collect_ops::mk_cache(&files, i, injected); let ops_cache = collect_ops::mk_cache(&files, i, injected);
let mut entries = files let mut entries = files
.iter() .iter()

View File

@@ -1,16 +1,13 @@
use std::println;
use std::rc::Rc; use std::rc::Rc;
use hashbrown::HashSet; use hashbrown::HashSet;
use itertools::Itertools;
use trait_set::trait_set; use trait_set::trait_set;
use crate::interner::{Interner, Sym, Tok}; use crate::interner::{Interner, Sym, Tok};
use crate::pipeline::error::{ModuleNotFound, ProjectError}; use crate::pipeline::error::{ModuleNotFound, ProjectError};
use crate::pipeline::source_loader::LoadedSourceTable; use crate::pipeline::source_loader::LoadedSourceTable;
use crate::pipeline::split_name::split_name;
use crate::representations::tree::WalkErrorKind; use crate::representations::tree::WalkErrorKind;
use crate::utils::Cache; use crate::utils::{split_max_prefix, unwrap_or, Cache};
pub type OpsResult = Result<Rc<HashSet<Tok<String>>>, Rc<dyn ProjectError>>; pub type OpsResult = Result<Rc<HashSet<Tok<String>>>, Rc<dyn ProjectError>>;
pub type ExportedOpsCache<'a> = Cache<'a, Sym, OpsResult>; pub type ExportedOpsCache<'a> = Cache<'a, Sym, OpsResult>;
@@ -33,34 +30,24 @@ pub fn collect_exported_ops(
i: &Interner, i: &Interner,
injected: &impl InjectedOperatorsFn, injected: &impl InjectedOperatorsFn,
) -> OpsResult { ) -> OpsResult {
if let Some(ops) = injected(path) { let injected = injected(path).unwrap_or_else(|| Rc::new(HashSet::new()));
if path == i.i(&[i.i("prelude")][..]) {
println!("%%% Prelude exported ops %%%");
println!("{}", ops.iter().map(|t| i.r(*t)).join(", "));
}
return Ok(ops);
}
let is_file = |n: &[Tok<String>]| loaded.contains_key(&i.i(n)); let is_file = |n: &[Tok<String>]| loaded.contains_key(&i.i(n));
let path_s = &i.r(path)[..]; let path_s = &i.r(path)[..];
let name_split = split_name(path_s, &is_file); let name_split = split_max_prefix(path_s, &is_file);
let (fpath_v, subpath_v) = if let Some(f) = name_split { let (fpath_v, subpath_v) = unwrap_or!(name_split; return Ok(Rc::new(
f (loaded.keys())
} else { .copied()
return Ok(Rc::new( .filter_map(|modname| {
loaded let modname_s = i.r(modname);
.keys() if path_s.len() == coprefix(path_s.iter(), modname_s.iter()) {
.copied() Some(modname_s[path_s.len()])
.filter_map(|modname| { } else {
let modname_s = i.r(modname); None
if path_s.len() == coprefix(path_s.iter(), modname_s.iter()) { }
Some(modname_s[path_s.len()]) })
} else { .chain(injected.iter().copied())
None .collect::<HashSet<_>>(),
} )));
})
.collect::<HashSet<_>>(),
));
};
let fpath = i.i(fpath_v); let fpath = i.i(fpath_v);
let preparsed = &loaded[&fpath].preparsed; let preparsed = &loaded[&fpath].preparsed;
let module = preparsed.0.walk(subpath_v, false).map_err(|walk_err| { let module = preparsed.0.walk(subpath_v, false).map_err(|walk_err| {
@@ -70,8 +57,7 @@ pub fn collect_exported_ops(
}, },
WalkErrorKind::Missing => ModuleNotFound { WalkErrorKind::Missing => ModuleNotFound {
file: i.extern_vec(fpath), file: i.extern_vec(fpath),
subpath: subpath_v subpath: (subpath_v.iter())
.iter()
.take(walk_err.pos) .take(walk_err.pos)
.map(|t| i.r(*t)) .map(|t| i.r(*t))
.cloned() .cloned()
@@ -80,12 +66,11 @@ pub fn collect_exported_ops(
.rc(), .rc(),
} }
})?; })?;
let out: HashSet<_> = let out = (module.items.iter())
module.items.iter().filter(|(_, v)| v.exported).map(|(k, _)| *k).collect(); .filter(|(_, v)| v.exported)
if path == i.i(&[i.i("prelude")][..]) { .map(|(k, _)| *k)
println!("%%% Prelude exported ops %%%"); .chain(injected.iter().copied())
println!("{}", out.iter().map(|t| i.r(*t)).join(", ")); .collect::<HashSet<_>>();
}
Ok(Rc::new(out)) Ok(Rc::new(out))
} }

View File

@@ -1,7 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use hashbrown::HashSet; use hashbrown::HashSet;
use itertools::Itertools;
use super::exported_ops::{ExportedOpsCache, OpsResult}; use super::exported_ops::{ExportedOpsCache, OpsResult};
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
@@ -34,13 +33,11 @@ pub fn collect_ops_for(
) -> OpsResult { ) -> OpsResult {
let tree = &loaded[&i.i(file)].preparsed.0; let tree = &loaded[&i.i(file)].preparsed.0;
let mut ret = HashSet::new(); let mut ret = HashSet::new();
println!("collecting ops for {}", i.extern_all(file).join("::"));
tree_all_ops(tree.as_ref(), &mut ret); tree_all_ops(tree.as_ref(), &mut ret);
tree.visit_all_imports(&mut |modpath, _module, import| { tree.visit_all_imports(&mut |modpath, _module, import| {
if let Some(n) = import.name { if let Some(n) = import.name {
ret.insert(n); ret.insert(n);
} else { } else {
println!("\tglob import from {}", i.extern_vec(import.path).join("::"));
let path = import_abs_path(file, modpath, &i.r(import.path)[..], i) let path = import_abs_path(file, modpath, &i.r(import.path)[..], i)
.expect("This error should have been caught during loading"); .expect("This error should have been caught during loading");
ret.extend(ops_cache.find(&i.i(&path))?.iter().copied()); ret.extend(ops_cache.find(&i.i(&path))?.iter().copied());
@@ -48,9 +45,5 @@ pub fn collect_ops_for(
Ok::<_, Rc<dyn ProjectError>>(()) Ok::<_, Rc<dyn ProjectError>>(())
})?; })?;
ret.drain_filter(|t| !is_op(i.r(*t))); ret.drain_filter(|t| !is_op(i.r(*t)));
if file == &[i.i("map")][..] {
println!(" %%% ops in map %%% ");
println!("{}", ret.iter().map(|t| i.r(*t)).join(", "))
}
Ok(Rc::new(ret)) Ok(Rc::new(ret))
} }

View File

@@ -41,6 +41,7 @@ impl Add for ProjectExt {
pub type ProjectModule = Module<Expr, ProjectExt>; pub type ProjectModule = Module<Expr, ProjectExt>;
/// Module corresponding to the root of a project /// Module corresponding to the root of a project
#[derive(Debug, Clone)]
pub struct ProjectTree(pub Rc<ProjectModule>); pub struct ProjectTree(pub Rc<ProjectModule>);
fn collect_rules_rec(bag: &mut Vec<Rule>, module: &ProjectModule) { fn collect_rules_rec(bag: &mut Vec<Rule>, module: &ProjectModule) {

View File

@@ -3,12 +3,12 @@ use std::rc::Rc;
use super::loaded_source::{LoadedSource, LoadedSourceTable}; use super::loaded_source::{LoadedSource, LoadedSourceTable};
use super::preparse::preparse; use super::preparse::preparse;
use crate::interner::{Interner, Sym, Tok}; use crate::interner::{Interner, Sym};
use crate::pipeline::error::ProjectError; use crate::pipeline::error::ProjectError;
use crate::pipeline::file_loader::{load_text, IOResult, Loaded}; use crate::pipeline::file_loader::{load_text, IOResult, Loaded};
use crate::pipeline::import_abs_path::import_abs_path; use crate::pipeline::import_abs_path::import_abs_path;
use crate::pipeline::split_name::split_name;
use crate::representations::sourcefile::FileEntry; use crate::representations::sourcefile::FileEntry;
use crate::utils::split_max_prefix;
/// Load the source at the given path or all within if it's a collection, /// Load the source at the given path or all within if it's a collection,
/// and all sources imported from these. /// and all sources imported from these.
@@ -18,31 +18,67 @@ fn load_abs_path_rec(
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner, i: &Interner,
get_source: &impl Fn(Sym) -> IOResult, get_source: &impl Fn(Sym) -> IOResult,
is_injected: &impl Fn(&[Tok<String>]) -> bool, is_injected_module: &impl Fn(Sym) -> bool,
) -> Result<(), Rc<dyn ProjectError>> { ) -> Result<(), Rc<dyn ProjectError>> {
let abs_pathv = i.r(abs_path); // # Termination
// short-circuit if this import is defined externally or already known //
if is_injected(abs_pathv) | table.contains_key(&abs_path) { // Every recursion of this function either
// - adds one of the files in the source directory to `table` or
// - recursively traverses a directory tree
// therefore eventually the function exits, assuming that the directory tree
// contains no cycles.
// Termination: exit if entry already visited
if table.contains_key(&abs_path) {
return Ok(()); return Ok(());
} }
// try splitting the path to file, swallowing any IO errors // try splitting the path to file, swallowing any IO errors
let is_file = |p| (get_source)(p).map(|l| l.is_code()).unwrap_or(false); let is_file = |sym| get_source(sym).map(|l| l.is_code()).unwrap_or(false);
let name_split = split_name(abs_pathv, &|p| is_file(i.i(p))); let name_split = split_max_prefix(&i.r(abs_path)[..], &|p| is_file(i.i(p)));
let filename = if let Some((f, _)) = name_split { if let Some((filename, _)) = name_split {
f // if the filename is valid, load, preparse and record this file
let text = load_text(i.i(filename), &get_source, i)?;
let preparsed = preparse(
filename.iter().map(|t| i.r(*t)).cloned().collect(),
text.as_str(),
prelude,
i,
)?;
table.insert(i.i(filename), LoadedSource {
text,
preparsed: preparsed.clone(),
});
// recurse on all imported modules
preparsed.0.visit_all_imports(&mut |modpath, _module, import| {
let abs_pathv =
import_abs_path(filename, modpath, &import.nonglob_path(i), i)?;
// recurse on imported module
load_abs_path_rec(
i.i(&abs_pathv),
table,
prelude,
i,
get_source,
is_injected_module,
)
})
} else { } else {
// If the path could not be split to file, load it as directory // If the path is not within a file, load it as directory
let coll = if let Loaded::Collection(c) = (get_source)(abs_path)? { let coll = match get_source(abs_path) {
c Ok(Loaded::Collection(coll)) => coll,
} Ok(Loaded::Code(_)) =>
// ^^ raise any IO error that was previously swallowed unreachable!("split_name returned None but the path is a file"),
else { Err(e) => {
panic!("split_name returned None but the path is a file") let parent = i.r(abs_path).split_last().expect("import path nonzero").1;
// exit without error if it was injected, or raise any IO error that was
// previously swallowed
return if is_injected_module(i.i(parent)) { Ok(()) } else { Err(e) };
},
}; };
// recurse on all files and folders within // recurse on all files and folders within
for item in coll.iter() { for item in coll.iter() {
let abs_subpath = abs_pathv let abs_subpath = (i.r(abs_path).iter())
.iter()
.copied() .copied()
.chain(iter::once(i.i(item))) .chain(iter::once(i.i(item)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -52,48 +88,36 @@ fn load_abs_path_rec(
prelude, prelude,
i, i,
get_source, get_source,
is_injected, is_injected_module,
)? )?
} }
return Ok(()); Ok(())
}; }
// otherwise load, preparse and record this file
let text = load_text(i.i(filename), &get_source, i)?;
let preparsed = preparse(
filename.iter().map(|t| i.r(*t)).cloned().collect(),
text.as_str(),
prelude,
i,
)?;
table.insert(abs_path, LoadedSource { text, preparsed: preparsed.clone() });
// recurse on all imported modules
preparsed.0.visit_all_imports(&mut |modpath, _module, import| {
let abs_pathv =
import_abs_path(filename, modpath, &import.nonglob_path(i), i)?;
// recurse on imported module
load_abs_path_rec(
i.i(&abs_pathv),
table,
prelude,
i,
get_source,
is_injected,
)
})
} }
/// Load and preparse all files reachable from the load targets via /// Load and preparse all files reachable from the load targets via
/// imports that aren't injected. /// imports that aren't injected.
///
/// is_injected_module must return false for injected symbols, but may return
/// true for parents of injected modules that are not directly part of the
/// injected data (the ProjectTree doesn't make a distinction between the two)
pub fn load_source( pub fn load_source(
targets: &[Sym], targets: &[Sym],
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner, i: &Interner,
get_source: &impl Fn(Sym) -> IOResult, get_source: &impl Fn(Sym) -> IOResult,
is_injected: &impl Fn(&[Tok<String>]) -> bool, is_injected_module: &impl Fn(Sym) -> bool,
) -> Result<LoadedSourceTable, Rc<dyn ProjectError>> { ) -> Result<LoadedSourceTable, Rc<dyn ProjectError>> {
let mut table = LoadedSourceTable::new(); let mut table = LoadedSourceTable::new();
for target in targets { for target in targets {
load_abs_path_rec(*target, &mut table, prelude, i, get_source, is_injected)? load_abs_path_rec(
*target,
&mut table,
prelude,
i,
get_source,
is_injected_module,
)?
} }
Ok(table) Ok(table)
} }

View File

@@ -1,16 +0,0 @@
use crate::interner::Tok;
#[allow(clippy::type_complexity)]
// FIXME couldn't find a good factoring
pub fn split_name<'a>(
path: &'a [Tok<String>],
is_valid: &impl Fn(&[Tok<String>]) -> bool,
) -> Option<(&'a [Tok<String>], &'a [Tok<String>])> {
for split in (0..=path.len()).rev() {
let (filename, subpath) = path.split_at(split);
if is_valid(filename) {
return Some((filename, subpath));
}
}
None
}

View File

@@ -179,11 +179,8 @@ impl Clause {
match self { match self {
Clause::Lambda(arg, body) => { Clause::Lambda(arg, body) => {
arg.visit_names(binds, cb); arg.visit_names(binds, cb);
let new_binds = if let Clause::Name(n) = arg.value { let new_binds =
binds.push(n) if let Clause::Name(n) = arg.value { binds.push(n) } else { binds };
} else {
binds
};
for x in body.iter() { for x in body.iter() {
x.visit_names(new_binds, cb) x.visit_names(new_binds, cb)
} }
@@ -216,11 +213,7 @@ impl Clause {
val.unwrap_or_else(|| e.clone()) val.unwrap_or_else(|| e.clone())
}) })
.collect(); .collect();
if any_some { if any_some { Some(Clause::S(*c, Rc::new(new_body))) } else { None }
Some(Clause::S(*c, Rc::new(new_body)))
} else {
None
}
}, },
Clause::Lambda(arg, body) => { Clause::Lambda(arg, body) => {
let new_arg = arg.map_names(pred); let new_arg = arg.map_names(pred);

View File

@@ -219,3 +219,9 @@ impl<T: Into<Literal>> From<T> for Clause {
Self::P(Primitive::Literal(value.into())) Self::P(Primitive::Literal(value.into()))
} }
} }
impl<T: Into<Clause>> From<T> for ExprInst {
fn from(value: T) -> Self {
value.into().wrap()
}
}

View File

@@ -1,4 +1,6 @@
//! Building blocks of a source file //! Building blocks of a source file
use std::iter;
use itertools::{Either, Itertools}; use itertools::{Either, Itertools};
use crate::ast::{Constant, Rule}; use crate::ast::{Constant, Rule};
@@ -157,7 +159,9 @@ pub fn absolute_path(
if tail.is_empty() { if tail.is_empty() {
Ok(new_abs.to_vec()) Ok(new_abs.to_vec())
} else { } else {
absolute_path(new_abs, tail, i) let new_rel =
iter::once(i.i("self")).chain(tail.iter().copied()).collect::<Vec<_>>();
absolute_path(new_abs, &new_rel, i)
} }
} else if *head == i.i("self") { } else if *head == i.i("self") {
Ok(abs_location.iter().chain(tail.iter()).copied().collect()) Ok(abs_location.iter().chain(tail.iter()).copied().collect())

View File

@@ -107,23 +107,23 @@ impl<TItem: Clone, TExt: Clone> Module<TItem, TExt> {
) -> Result<(), E> { ) -> Result<(), E> {
self.visit_all_imports_rec(Substack::Bottom, callback) self.visit_all_imports_rec(Substack::Bottom, callback)
} }
}
impl<TItem: Clone, TExt: Clone + Add<Output = TExt>> Add /// Combine two module trees; wherever they conflict, the overlay is
for Module<TItem, TExt> /// preferred.
{ pub fn overlay(mut self, overlay: Self) -> Self
type Output = Self; where
TExt: Add<TExt, Output = TExt>,
fn add(mut self, rhs: Self) -> Self::Output { {
let Module { extra, imports, items } = rhs; let Module { extra, imports, items } = overlay;
let mut new_items = HashMap::new();
for (key, right) in items { for (key, right) in items {
// if both contain a submodule // if both contain a submodule
if let Some(left) = self.items.remove(&key) { if let Some(left) = self.items.remove(&key) {
if let ModMember::Sub(rsub) = &right.member { if let ModMember::Sub(rsub) = &right.member {
if let ModMember::Sub(lsub) = &left.member { if let ModMember::Sub(lsub) = &left.member {
// merge them with rhs exportedness // merge them with rhs exportedness
let new_mod = lsub.as_ref().clone() + rsub.as_ref().clone(); let new_mod = lsub.as_ref().clone().overlay(rsub.as_ref().clone());
self.items.insert(key, ModEntry { new_items.insert(key, ModEntry {
exported: right.exported, exported: right.exported,
member: ModMember::Sub(Rc::new(new_mod)), member: ModMember::Sub(Rc::new(new_mod)),
}); });
@@ -132,10 +132,14 @@ impl<TItem: Clone, TExt: Clone + Add<Output = TExt>> Add
} }
} }
// otherwise right shadows left // otherwise right shadows left
self.items.insert(key, right); new_items.insert(key, right);
} }
new_items.extend(self.items.into_iter());
self.imports.extend(imports.into_iter()); self.imports.extend(imports.into_iter());
self.extra = self.extra + extra; Module {
self items: new_items,
imports: self.imports,
extra: self.extra + extra,
}
} }
} }

View File

@@ -56,11 +56,8 @@ fn mk_vec(pattern: &[Expr]) -> VecMatcher {
pattern.last().map(vec_attrs).is_some(), pattern.last().map(vec_attrs).is_some(),
"pattern must end with a vectorial" "pattern must end with a vectorial"
); );
let (left, (key, prio, nonzero), right) = split_at_max_vec(pattern) let (left, (key, _, nonzero), right) = split_at_max_vec(pattern)
.expect("pattern must have vectorial placeholders at least at either end"); .expect("pattern must have vectorial placeholders at least at either end");
if prio >= 1 {
println!("Nondefault priority {} found", prio)
}
let r_sep_size = scal_cnt(right.iter()); let r_sep_size = scal_cnt(right.iter());
let (r_sep, r_side) = right.split_at(r_sep_size); let (r_sep, r_side) = right.split_at(r_sep_size);
let l_sep_size = scal_cnt(left.iter().rev()); let l_sep_size = scal_cnt(left.iter().rev());

View File

@@ -114,29 +114,25 @@ impl<M: Matcher> Repository<M> {
/// Attempt to run each rule in priority order `limit` times. Returns /// Attempt to run each rule in priority order `limit` times. Returns
/// the final tree and the number of iterations left to the limit. /// the final tree and the number of iterations left to the limit.
#[allow(unused)] #[allow(unused)]
pub fn long_step( pub fn long_step(&self, code: &Expr, mut limit: usize) -> (Expr, usize) {
&self,
code: &Expr,
mut limit: usize,
) -> Result<(Expr, usize), RuleError> {
if limit == 0 { if limit == 0 {
return Ok((code.clone(), 0)); return (code.clone(), 0);
} }
if let Some(mut processed) = self.step(code) { if let Some(mut processed) = self.step(code) {
limit -= 1; limit -= 1;
if limit == 0 { if limit == 0 {
return Ok((processed, 0)); return (processed, 0);
} }
while let Some(out) = self.step(&processed) { while let Some(out) = self.step(&processed) {
limit -= 1; limit -= 1;
if limit == 0 { if limit == 0 {
return Ok((out, 0)); return (out, 0);
} }
processed = out; processed = out;
} }
Ok((processed, limit)) (processed, limit)
} else { } else {
Ok((code.clone(), limit)) (code.clone(), limit)
} }
} }
} }

View File

@@ -0,0 +1,28 @@
use std::fmt::Display;
use crate::foreign::ExternError;
/// Various errors produced by arithmetic operations
pub enum ArithmeticError {
/// Integer overflow
Overflow,
/// Float overflow
Infinity,
/// Division or modulo by zero
DivByZero,
/// Other, unexpected operation produced NaN
NaN,
}
impl Display for ArithmeticError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NaN => write!(f, "Operation resulted in NaN"),
Self::Overflow => write!(f, "Integer overflow"),
Self::Infinity => write!(f, "Operation resulted in Infinity"),
Self::DivByZero => write!(f, "A division by zero was attempted"),
}
}
}
impl ExternError for ArithmeticError {}

4
src/stl/bool.orc Normal file
View File

@@ -0,0 +1,4 @@
export ...$a == ...$b =0x3p36=> (equals (...$a) (...$b))
export if ...$cond then ...$true else ...$false:1 =0x1p84=> (
ifthenelse (...$cond) (...$true) (...$false)
)

92
src/stl/bool.rs Normal file
View File

@@ -0,0 +1,92 @@
use std::rc::Rc;
use crate::foreign::Atom;
use crate::interner::Interner;
use crate::pipeline::ConstTree;
use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::Primitive;
use crate::stl::litconv::with_lit;
use crate::stl::AssertionError;
use crate::{atomic_inert, define_fn, Literal, PathSet};
/// Booleans exposed to Orchid
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Boolean(pub bool);
atomic_inert!(Boolean);
impl From<bool> for Boolean {
fn from(value: bool) -> Self {
Self(value)
}
}
impl TryFrom<&ExprInst> for Boolean {
type Error = ();
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
let expr = value.expr();
if let Clause::P(Primitive::Atom(Atom(a))) = &expr.clause {
if let Some(b) = a.as_any().downcast_ref::<Boolean>() {
return Ok(*b);
}
}
Err(())
}
}
define_fn! {expr=x in
/// Compares the inner values if
///
/// - both values are char,
/// - both are string,
/// - both are either uint or num
Equals {
a: Literal as with_lit(x, |l| Ok(l.clone())),
b: Literal as with_lit(x, |l| Ok(l.clone()))
} => Ok(Boolean::from(match (a, b) {
(Literal::Char(c1), Literal::Char(c2)) => c1 == c2,
(Literal::Num(n1), Literal::Num(n2)) => n1 == n2,
(Literal::Str(s1), Literal::Str(s2)) => s1 == s2,
(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(
b.clone().into(),
"the expected type"
)?,
}).to_atom_cls())
}
// 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.
define_fn! {
/// Takes a boolean and two branches, runs the first if the bool is true, the
/// second if it's false.
IfThenElse = |x: &ExprInst| x.try_into()
.map_err(|_| AssertionError::ext(x.clone(), "a boolean"))
.map(|b: Boolean| if b.0 {Clause::Lambda {
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
body: Clause::Lambda {
args: None,
body: Clause::LambdaArg.wrap()
}.wrap(),
}} else {Clause::Lambda {
args: None,
body: Clause::Lambda {
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
body: Clause::LambdaArg.wrap(),
}.wrap(),
}})
}
pub fn bool(i: &Interner) -> ConstTree {
ConstTree::tree([(
i.i("bool"),
ConstTree::tree([
(i.i("ifthenelse"), ConstTree::xfn(IfThenElse)),
(i.i("equals"), ConstTree::xfn(Equals)),
(i.i("true"), ConstTree::atom(Boolean(true))),
(i.i("false"), ConstTree::atom(Boolean(false))),
]),
)])
}

View File

@@ -1,29 +0,0 @@
use crate::atomic_inert;
use crate::foreign::Atom;
use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::Primitive;
/// Booleans exposed to Orchid
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Boolean(pub bool);
atomic_inert!(Boolean);
impl From<bool> for Boolean {
fn from(value: bool) -> Self {
Self(value)
}
}
impl TryFrom<ExprInst> for Boolean {
type Error = ();
fn try_from(value: ExprInst) -> Result<Self, Self::Error> {
let expr = value.expr();
if let Clause::P(Primitive::Atom(Atom(a))) = &expr.clause {
if let Some(b) = a.as_any().downcast_ref::<Boolean>() {
return Ok(*b);
}
}
Err(())
}
}

View File

@@ -1,54 +0,0 @@
use std::fmt::Debug;
use super::super::assertion_error::AssertionError;
use super::super::litconv::with_lit;
use super::boolean::Boolean;
use crate::representations::interpreted::ExprInst;
use crate::representations::Literal;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// Compares the inner values if
///
/// - both values are char,
/// - both are string,
/// - both are either uint or num
///
/// Next state: [Equals1]
#[derive(Clone)]
pub struct Equals2;
externfn_impl!(Equals2, |_: &Self, x: ExprInst| Ok(Equals1 { x }));
/// Prev state: [Equals2]; Next state: [Equals0]
#[derive(Debug, Clone)]
pub struct Equals1 {
x: ExprInst,
}
atomic_redirect!(Equals1, x);
atomic_impl!(Equals1);
externfn_impl!(Equals1, |this: &Self, x: ExprInst| {
with_lit(&this.x, |l| Ok(Equals0 { a: l.clone(), x }))
});
/// Prev state: [Equals1]
#[derive(Debug, Clone)]
pub struct Equals0 {
a: Literal,
x: ExprInst,
}
atomic_redirect!(Equals0, x);
atomic_impl!(Equals0, |Self { a, x }: &Self, _| {
let eqls = with_lit(x, |l| {
Ok(match (a, l) {
(Literal::Char(c1), Literal::Char(c2)) => c1 == c2,
(Literal::Num(n1), Literal::Num(n2)) => n1 == n2,
(Literal::Str(s1), Literal::Str(s2)) => s1 == s2,
(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(x.clone(), "the expected type")?,
})
})?;
Ok(Boolean::from(eqls).to_atom_cls())
});

View File

@@ -1,46 +0,0 @@
use std::fmt::Debug;
use std::rc::Rc;
use super::super::assertion_error::AssertionError;
use super::Boolean;
use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::PathSet;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// Takes a boolean and two branches, runs the first if the bool is true, the
/// second if it's false.
///
/// Next state: [IfThenElse0]
#[derive(Clone)]
pub struct IfThenElse1;
externfn_impl!(IfThenElse1, |_: &Self, x: ExprInst| Ok(IfThenElse0 { x }));
/// Prev state: [IfThenElse1]
#[derive(Debug, Clone)]
pub struct IfThenElse0 {
x: ExprInst,
}
atomic_redirect!(IfThenElse0, x);
atomic_impl!(IfThenElse0, |this: &Self, _| {
let Boolean(b) = this
.x
.clone()
.try_into()
.map_err(|_| AssertionError::ext(this.x.clone(), "a boolean"))?;
Ok(if b {
Clause::Lambda {
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
body: Clause::Lambda { args: None, body: Clause::LambdaArg.wrap() }
.wrap(),
}
} else {
Clause::Lambda {
args: None,
body: Clause::Lambda {
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
body: Clause::LambdaArg.wrap(),
}
.wrap(),
}
})
});

View File

@@ -1,16 +0,0 @@
mod boolean;
mod equals;
mod ifthenelse;
pub use boolean::Boolean;
use crate::interner::Interner;
use crate::pipeline::ConstTree;
pub fn bool(i: &Interner) -> ConstTree {
ConstTree::tree([
(i.i("ifthenelse"), ConstTree::xfn(ifthenelse::IfThenElse1)),
(i.i("equals"), ConstTree::xfn(equals::Equals2)),
(i.i("true"), ConstTree::atom(Boolean(true))),
(i.i("false"), ConstTree::atom(Boolean(false))),
])
}

67
src/stl/conv.rs Normal file
View File

@@ -0,0 +1,67 @@
use chumsky::Parser;
use ordered_float::NotNan;
use super::litconv::with_lit;
use super::{ArithmeticError, AssertionError};
use crate::foreign::ExternError;
use crate::interner::Interner;
use crate::parse::{float_parser, int_parser};
use crate::pipeline::ConstTree;
use crate::{define_fn, Literal};
define_fn! {
/// parse a number. Accepts the same syntax Orchid does.
ToFloat = |x| with_lit(x, |l| match l {
Literal::Str(s) => float_parser()
.parse(s.as_str())
.map_err(|_| AssertionError::ext(
x.clone(),
"cannot be parsed into a float"
)),
Literal::Num(n) => Ok(*n),
Literal::Uint(i) => NotNan::new(*i as f64)
.map_err(|_| ArithmeticError::NaN.into_extern()),
Literal::Char(char) => char
.to_digit(10)
.map(|i| NotNan::new(i as f64).expect("u32 to f64 yielded NaN"))
.ok_or_else(|| AssertionError::ext(x.clone(), "is not a decimal digit")),
}).map(|nn| Literal::Num(nn).into())
}
define_fn! {
/// Parse an unsigned integer. Accepts the same formats Orchid does. If the
/// input is a number, floors it.
ToUint = |x| with_lit(x, |l| match l {
Literal::Str(s) => int_parser()
.parse(s.as_str())
.map_err(|_| AssertionError::ext(
x.clone(),
"cannot be parsed into an unsigned int",
)),
Literal::Num(n) => Ok(n.floor() as u64),
Literal::Uint(i) => Ok(*i),
Literal::Char(char) => char
.to_digit(10)
.map(u64::from)
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit")),
}).map(|u| Literal::Uint(u).into())
}
define_fn! {
/// Convert a literal to a string using Rust's conversions for floats, chars and
/// uints respectively
ToString = |x| with_lit(x, |l| Ok(match l {
Literal::Char(c) => c.to_string(),
Literal::Uint(i) => i.to_string(),
Literal::Num(n) => n.to_string(),
Literal::Str(s) => s.clone(),
})).map(|s| Literal::Str(s).into())
}
pub fn conv(i: &Interner) -> ConstTree {
ConstTree::tree([
(i.i("to_float"), ConstTree::xfn(ToFloat)),
(i.i("to_uint"), ConstTree::xfn(ToUint)),
(i.i("to_string"), ConstTree::xfn(ToString)),
])
}

View File

@@ -1,14 +0,0 @@
use crate::interner::Interner;
use crate::pipeline::ConstTree;
mod parse_float;
mod parse_uint;
mod to_string;
pub fn conv(i: &Interner) -> ConstTree {
ConstTree::tree([
(i.i("parse_float"), ConstTree::xfn(parse_float::ParseFloat1)),
(i.i("parse_uint"), ConstTree::xfn(parse_uint::ParseUint1)),
(i.i("to_string"), ConstTree::xfn(to_string::ToString1)),
])
}

View File

@@ -1,43 +0,0 @@
use std::fmt::Debug;
use chumsky::Parser;
use super::super::assertion_error::AssertionError;
use super::super::litconv::with_lit;
use crate::parse::float_parser;
use crate::representations::interpreted::ExprInst;
use crate::representations::Literal;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// parse a number. Accepts the same syntax Orchid does
///
/// Next state: [ParseFloat0]
#[derive(Clone)]
pub struct ParseFloat1;
externfn_impl!(ParseFloat1, |_: &Self, x: ExprInst| Ok(ParseFloat0 { x }));
/// Prev state: [ParseFloat1]
#[derive(Debug, Clone)]
pub struct ParseFloat0 {
x: ExprInst,
}
atomic_redirect!(ParseFloat0, x);
atomic_impl!(ParseFloat0, |Self { x }: &Self, _| {
let number = with_lit(x, |l| {
Ok(match l {
Literal::Str(s) => {
let parser = float_parser();
parser.parse(s.as_str()).map_err(|_| {
AssertionError::ext(x.clone(), "cannot be parsed into a float")
})?
},
Literal::Num(n) => *n,
Literal::Uint(i) => (*i as u32).into(),
Literal::Char(char) => char
.to_digit(10)
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))?
.into(),
})
})?;
Ok(number.into())
});

View File

@@ -1,47 +0,0 @@
use std::fmt::Debug;
use chumsky::Parser;
use super::super::assertion_error::AssertionError;
use super::super::litconv::with_lit;
use crate::parse::int_parser;
use crate::representations::interpreted::ExprInst;
use crate::representations::Literal;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// Parse an unsigned integer. Accepts the same formats Orchid does. If the
/// input is a number, floors it.
///
/// Next state: [ParseUint0]
#[derive(Clone)]
pub struct ParseUint1;
externfn_impl!(ParseUint1, |_: &Self, x: ExprInst| Ok(ParseUint0 { x }));
/// Prev state: [ParseUint1]
#[derive(Debug, Clone)]
pub struct ParseUint0 {
x: ExprInst,
}
atomic_redirect!(ParseUint0, x);
atomic_impl!(ParseUint0, |Self { x }: &Self, _| {
let uint = with_lit(x, |l| {
Ok(match l {
Literal::Str(s) => {
let parser = int_parser();
parser.parse(s.as_str()).map_err(|_| {
AssertionError::ext(
x.clone(),
"cannot be parsed into an unsigned int",
)
})?
},
Literal::Num(n) => n.floor() as u64,
Literal::Uint(i) => *i,
Literal::Char(char) => char
.to_digit(10)
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))?
.into(),
})
})?;
Ok(uint.into())
});

View File

@@ -1,32 +0,0 @@
use std::fmt::Debug;
use super::super::litconv::with_lit;
use crate::representations::interpreted::ExprInst;
use crate::representations::Literal;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// Convert a literal to a string using Rust's conversions for floats, chars and
/// uints respectively
///
/// Next state: [ToString0]
#[derive(Clone)]
pub struct ToString1;
externfn_impl!(ToString1, |_: &Self, x: ExprInst| Ok(ToString0 { x }));
/// Prev state: [ToString1]
#[derive(Debug, Clone)]
pub struct ToString0 {
x: ExprInst,
}
atomic_redirect!(ToString0, x);
atomic_impl!(ToString0, |Self { x }: &Self, _| {
let string = with_lit(x, |l| {
Ok(match l {
Literal::Char(c) => c.to_string(),
Literal::Uint(i) => i.to_string(),
Literal::Num(n) => n.to_string(),
Literal::Str(s) => s.clone(),
})
})?;
Ok(string.into())
});

View File

@@ -1,32 +0,0 @@
use std::fmt::Debug;
use crate::foreign::{Atomic, AtomicReturn};
use crate::interner::InternedDisplay;
use crate::interpreter::Context;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_defaults, externfn_impl};
/// Print and return whatever expression is in the argument without normalizing
/// it.
///
/// Next state: [Debug1]
#[derive(Clone)]
pub struct Debug2;
externfn_impl!(Debug2, |_: &Self, x: ExprInst| Ok(Debug1 { x }));
/// Prev state: [Debug2]
#[derive(Debug, Clone)]
pub struct Debug1 {
x: ExprInst,
}
impl Atomic for Debug1 {
atomic_defaults!();
fn run(&self, ctx: Context) -> crate::foreign::AtomicResult {
println!("{}", self.x.bundle(ctx.interner));
Ok(AtomicReturn {
clause: self.x.expr().clause.clone(),
gas: ctx.gas.map(|g| g - 1),
inert: false,
})
}
}

View File

@@ -1,19 +0,0 @@
use crate::interner::Interner;
use crate::pipeline::ConstTree;
mod debug;
mod io;
mod panic;
mod print;
mod readline;
pub use io::{handle, IO};
pub fn cpsio(i: &Interner) -> ConstTree {
ConstTree::tree([
(i.i("print"), ConstTree::xfn(print::Print2)),
(i.i("readline"), ConstTree::xfn(readline::Readln2)),
(i.i("debug"), ConstTree::xfn(debug::Debug2)),
(i.i("panic"), ConstTree::xfn(panic::Panic1)),
])
}

View File

@@ -1,35 +0,0 @@
use std::fmt::Display;
use super::super::litconv::with_str;
use crate::foreign::ExternError;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// Takes a message, returns an [ExternError] unconditionally.
///
/// Next state: [Panic0]
#[derive(Clone)]
pub struct Panic1;
externfn_impl!(Panic1, |_: &Self, x: ExprInst| Ok(Panic0 { x }));
/// Prev state: [Panic1]
#[derive(Debug, Clone)]
pub struct Panic0 {
x: ExprInst,
}
atomic_redirect!(Panic0, x);
atomic_impl!(Panic0, |Self { x }: &Self, _| {
with_str(x, |s| Err(OrchidPanic(s.clone()).into_extern()))
});
/// An unrecoverable error in Orchid land. Of course, because Orchid is lazy, it
/// only applies to the expressions that use the one that generated it.
pub struct OrchidPanic(String);
impl Display for OrchidPanic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Orchid code panicked: {}", self.0)
}
}
impl ExternError for OrchidPanic {}

View File

@@ -1,40 +0,0 @@
use std::fmt::Debug;
use super::super::litconv::with_str;
use super::io::IO;
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
use crate::interpreter::Context;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_defaults, atomic_impl, atomic_redirect, externfn_impl};
/// Wrap a string and the continuation into an [IO] event to be evaluated by the
/// embedder.
///
/// Next state: [Print1]
#[derive(Clone)]
pub struct Print2;
externfn_impl!(Print2, |_: &Self, x: ExprInst| Ok(Print1 { x }));
/// Prev state: [Print2]; Next state: [Print0]
#[derive(Debug, Clone)]
pub struct Print1 {
x: ExprInst,
}
atomic_redirect!(Print1, x);
atomic_impl!(Print1);
externfn_impl!(Print1, |this: &Self, x: ExprInst| {
with_str(&this.x, |s| Ok(Print0 { s: s.clone(), x }))
});
/// Prev state: [Print1]
#[derive(Debug, Clone)]
pub struct Print0 {
s: String,
x: ExprInst,
}
impl Atomic for Print0 {
atomic_defaults!();
fn run(&self, ctx: Context) -> AtomicResult {
Ok(AtomicReturn::from_data(IO::Print(self.s.clone(), self.x.clone()), ctx))
}
}

View File

@@ -1,27 +0,0 @@
use std::fmt::Debug;
use super::io::IO;
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
use crate::interpreter::Context;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_defaults, externfn_impl};
/// Create an [IO] event that reads a line form standard input and calls the
/// continuation with it.
///
/// Next state: [Readln1]
#[derive(Clone)]
pub struct Readln2;
externfn_impl!(Readln2, |_: &Self, x: ExprInst| Ok(Readln1 { x }));
/// Prev state: [Readln2]
#[derive(Debug, Clone)]
pub struct Readln1 {
x: ExprInst,
}
impl Atomic for Readln1 {
atomic_defaults!();
fn run(&self, ctx: Context) -> AtomicResult {
Ok(AtomicReturn::from_data(IO::Readline(self.x.clone()), ctx))
}
}

View File

@@ -8,7 +8,7 @@ export loop $r on (..$parameters) with ...$tail =0x5p129=> Y (\$r.
bind_names ($name ..$rest) $payload =0x1p250=> \$name. bind_names (..$rest) $payload bind_names ($name ..$rest) $payload =0x1p250=> \$name. bind_names (..$rest) $payload
bind_names () (...$payload) =0x1p250=> ...$payload bind_names () (...$payload) =0x1p250=> ...$payload
export ...$prefix $ ...$suffix:1 =0x1p39=> ...$prefix (...$suffix) export ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix)
export ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix export ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix
export (...$argv) => ...$body =0x2p129=> (bind_names (...$argv) (...$body)) export (...$argv) => ...$body =0x2p129=> (bind_names (...$argv) (...$body))

29
src/stl/io/inspect.rs Normal file
View File

@@ -0,0 +1,29 @@
use std::fmt::Debug;
use crate::foreign::{Atomic, AtomicReturn};
use crate::interner::InternedDisplay;
use crate::interpreter::Context;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_defaults, write_fn_step};
write_fn_step! {
/// Print and return whatever expression is in the argument without
/// normalizing it.
pub Inspect > Inspect1
}
#[derive(Debug, Clone)]
struct Inspect1 {
expr_inst: ExprInst,
}
impl Atomic for Inspect1 {
atomic_defaults!();
fn run(&self, ctx: Context) -> crate::foreign::AtomicResult {
println!("{}", self.expr_inst.bundle(ctx.interner));
Ok(AtomicReturn {
clause: self.expr_inst.expr().clause.clone(),
gas: ctx.gas.map(|g| g - 1),
inert: false,
})
}
}

30
src/stl/io/mod.rs Normal file
View File

@@ -0,0 +1,30 @@
use crate::interner::Interner;
use crate::pipeline::ConstTree;
mod command;
mod inspect;
mod panic;
mod print;
mod readline;
pub use command::{handle, IO};
pub fn io(i: &Interner, allow_impure: bool) -> ConstTree {
let pure = ConstTree::tree([(
i.i("io"),
ConstTree::tree([
(i.i("print"), ConstTree::xfn(print::Print)),
(i.i("readline"), ConstTree::xfn(readline::ReadLn)),
(i.i("panic"), ConstTree::xfn(panic::Panic)),
]),
)]);
if !allow_impure {
pure
} else {
pure
+ ConstTree::tree([(
i.i("io"),
ConstTree::tree([(i.i("debug"), ConstTree::xfn(inspect::Inspect))]),
)])
}
}

21
src/stl/io/panic.rs Normal file
View File

@@ -0,0 +1,21 @@
use std::fmt::Display;
use super::super::litconv::with_str;
use crate::define_fn;
use crate::foreign::ExternError;
define_fn! {
/// Takes a message, returns an [ExternError] unconditionally.
pub Panic = |x| with_str(x, |s| Err(OrchidPanic(s.clone()).into_extern()))
}
/// An unrecoverable error in Orchid land. Because Orchid is lazy, this only
/// invalidates expressions that reference the one that generated it.
pub struct OrchidPanic(String);
impl Display for OrchidPanic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Orchid code panicked: {}", self.0)
}
}
impl ExternError for OrchidPanic {}

31
src/stl/io/print.rs Normal file
View File

@@ -0,0 +1,31 @@
use std::fmt::Debug;
use super::super::litconv::with_str;
use super::command::IO;
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
use crate::interpreter::Context;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_defaults, write_fn_step};
write_fn_step! {
/// Wrap a string and the continuation into an [IO] event to be evaluated by
/// the embedder.
pub Print > Print1
}
write_fn_step! {
Print1 {}
Print0 where message = x => with_str(x, |s| Ok(s.clone()));
}
#[derive(Debug, Clone)]
struct Print0 {
message: String,
expr_inst: ExprInst,
}
impl Atomic for Print0 {
atomic_defaults!();
fn run(&self, ctx: Context) -> AtomicResult {
let command = IO::Print(self.message.clone(), self.expr_inst.clone());
Ok(AtomicReturn::from_data(command, ctx))
}
}

25
src/stl/io/readline.rs Normal file
View File

@@ -0,0 +1,25 @@
use std::fmt::Debug;
use super::command::IO;
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
use crate::interpreter::Context;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_defaults, write_fn_step};
write_fn_step! {
/// Create an [IO] event that reads a line form standard input and calls the
/// continuation with it.
pub ReadLn > ReadLn1
}
#[derive(Debug, Clone)]
struct ReadLn1 {
expr_inst: ExprInst,
}
impl Atomic for ReadLn1 {
atomic_defaults!();
fn run(&self, ctx: Context) -> AtomicResult {
let command = IO::Readline(self.expr_inst.clone());
Ok(AtomicReturn::from_data(command, ctx))
}
}

1
src/stl/known.orc Normal file
View File

@@ -0,0 +1 @@
export ::(,)

View File

@@ -1,5 +1,4 @@
import option import super::(option, fn::*, bool::*, known::*, num::*,)
import super::fn::*
pair := \a.\b. \f. f a b pair := \a.\b. \f. f a b

View File

@@ -1,8 +1,5 @@
import list import super::(bool::*, fn::*, known::*, list, option, proc::*)
import option import std::io::panic
import fn::*
import std::to_string
import std::debug
-- utilities for using lists as pairs -- utilities for using lists as pairs

View File

@@ -1,13 +1,68 @@
use hashbrown::HashMap;
use rust_embed::RustEmbed;
use super::bool::bool; use super::bool::bool;
use super::conv::conv; use super::conv::conv;
use super::cpsio::cpsio; use super::io::io;
use super::num::num; use super::num::num;
use super::str::str; use super::str::str;
use crate::interner::Interner; use crate::interner::{Interner, Sym};
use crate::pipeline::ConstTree; use crate::pipeline::file_loader::mk_embed_cache;
use crate::pipeline::{from_const_tree, parse_layer, ProjectTree};
use crate::sourcefile::{FileEntry, Import};
/// Feature flags for the STL.
#[derive(Default)]
pub struct StlOptions {
/// Whether impure functions (such as io::debug) are allowed. An embedder
/// would typically disable this flag
pub impure: bool,
}
#[derive(RustEmbed)]
#[folder = "src/stl"]
#[prefix = "std/"]
#[include = "*.orc"]
struct StlEmbed;
// TODO: fix all orc modules to not rely on prelude
/// Build the standard library used by the interpreter by combining the other /// Build the standard library used by the interpreter by combining the other
/// libraries /// libraries
pub fn mk_stl(i: &Interner) -> ConstTree { pub fn mk_stl(i: &Interner, options: StlOptions) -> ProjectTree {
cpsio(i) + conv(i) + bool(i) + str(i) + num(i) let const_tree = from_const_tree(
HashMap::from([(
i.i("std"),
io(i, options.impure) + conv(i) + bool(i) + str(i) + num(i),
)]),
&[i.i("std")],
i,
);
let ld_cache = mk_embed_cache::<StlEmbed>(".orc", i);
parse_layer(
&StlEmbed::iter()
.map(|path| -> Sym {
let segtoks = path
.strip_suffix(".orc")
.expect("the embed is filtered for suffix")
.split('/')
.map(|segment| i.i(segment))
.collect::<Vec<_>>();
i.i(&segtoks[..])
})
.collect::<Vec<_>>()[..],
&|p| ld_cache.find(&p),
&const_tree,
&[],
i,
)
.expect("Parse error in STL")
}
/// Generate prelude lines to be injected to every module compiled with the STL
pub fn mk_prelude(i: &Interner) -> Vec<FileEntry> {
vec![FileEntry::Import(vec![Import {
path: i.i(&[i.i("std"), i.i("prelude")][..]),
name: None,
}])]
} }

View File

@@ -1,17 +1,19 @@
//! Constants exposed to usercode by the interpreter //! Constants exposed to usercode by the interpreter
mod arithmetic_error;
mod assertion_error; mod assertion_error;
mod bool; mod bool;
mod conv; mod conv;
mod cpsio; mod io;
pub mod litconv; pub mod litconv;
mod mk_stl; mod mk_stl;
mod num; mod num;
mod runtime_error; mod runtime_error;
mod str; mod str;
pub use arithmetic_error::ArithmeticError;
pub use assertion_error::AssertionError; pub use assertion_error::AssertionError;
pub use cpsio::{handle as handleIO, IO}; pub use io::{handle as handleIO, IO};
pub use mk_stl::mk_stl; pub use mk_stl::{mk_prelude, mk_stl, StlOptions};
pub use runtime_error::RuntimeError; pub use runtime_error::RuntimeError;
pub use self::bool::Boolean; pub use self::bool::Boolean;

5
src/stl/num.orc Normal file
View File

@@ -0,0 +1,5 @@
export ...$a + ...$b =0x2p36=> (add (...$a) (...$b))
export ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b))
export ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b))
export ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b))
export ...$a / ...$b:1 =0x1p36=> (divide (...$a) (...$b))

149
src/stl/num.rs Normal file
View File

@@ -0,0 +1,149 @@
use std::rc::Rc;
use ordered_float::NotNan;
use super::litconv::with_lit;
use super::{ArithmeticError, AssertionError};
use crate::define_fn;
use crate::foreign::ExternError;
use crate::interner::Interner;
use crate::pipeline::ConstTree;
use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::{Literal, Primitive};
// region: Numeric, type to handle floats and uints together
/// A number, either floating point or unsigned int, visible to Orchid.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Numeric {
/// A nonnegative integer such as a size, index or count
Uint(u64),
/// A float other than NaN. Orchid has no silent errors
Num(NotNan<f64>),
}
impl Numeric {
fn as_f64(&self) -> f64 {
match self {
Numeric::Num(n) => **n,
Numeric::Uint(i) => *i as f64,
}
}
/// Wrap a f64 in a Numeric
fn num(value: f64) -> Result<Self, Rc<dyn ExternError>> {
if value.is_finite() {
NotNan::new(value)
.map(Self::Num)
.map_err(|_| ArithmeticError::NaN.into_extern())
} else {
Err(ArithmeticError::Infinity.into_extern())
}
}
}
impl TryFrom<&ExprInst> for Numeric {
type Error = Rc<dyn ExternError>;
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
with_lit(value, |l| match l {
Literal::Uint(i) => Ok(Numeric::Uint(*i)),
Literal::Num(n) => Ok(Numeric::Num(*n)),
_ => AssertionError::fail(value.clone(), "an integer or number")?,
})
}
}
impl From<Numeric> for Clause {
fn from(value: Numeric) -> Self {
Clause::P(Primitive::Literal(match value {
Numeric::Uint(i) => Literal::Uint(i),
Numeric::Num(n) => Literal::Num(n),
}))
}
}
// endregion
// region: operations
define_fn! {
/// Add two numbers. If they're both uint, the output is uint. If either is
/// number, the output is number.
Add { a: Numeric, b: Numeric } => match (a, b) {
(Numeric::Uint(a), Numeric::Uint(b)) => {
a.checked_add(*b)
.map(Numeric::Uint)
.ok_or_else(|| ArithmeticError::Overflow.into_extern())
}
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a + b)),
(Numeric::Num(a), Numeric::Uint(b)) | (Numeric::Uint(b), Numeric::Num(a))
=> Numeric::num(a.into_inner() + *b as f64),
}.map(Numeric::into)
}
define_fn! {
/// Subtract a number from another. Always returns Number.
Subtract { a: Numeric, b: Numeric } => match (a, b) {
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::num(*a as f64 - *b as f64),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a - b)),
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(**a - *b as f64),
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(*a as f64 - **b),
}.map(Numeric::into)
}
define_fn! {
/// Multiply two numbers. If they're both uint, the output is uint. If either
/// is number, the output is number.
Multiply { a: Numeric, b: Numeric } => match (a, b) {
(Numeric::Uint(a), Numeric::Uint(b)) => {
a.checked_mul(*b)
.map(Numeric::Uint)
.ok_or_else(|| ArithmeticError::Overflow.into_extern())
}
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a * b)),
(Numeric::Uint(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Uint(a))
=> Numeric::num(*a as f64 * **b),
}.map(Numeric::into)
}
define_fn! {
/// Divide a number by another. Always returns Number.
Divide { a: Numeric, b: Numeric } => {
let a: f64 = a.as_f64();
let b: f64 = b.as_f64();
if b == 0.0 {
return Err(ArithmeticError::DivByZero.into_extern())
}
Numeric::num(a / b).map(Numeric::into)
}
}
define_fn! {
/// Take the remainder of two numbers. If they're both uint, the output is
/// uint. If either is number, the output is number.
Remainder { a: Numeric, b: Numeric } => match (a, b) {
(Numeric::Uint(a), Numeric::Uint(b)) => {
a.checked_rem(*b)
.map(Numeric::Uint)
.ok_or_else(|| ArithmeticError::DivByZero.into_extern())
}
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a % b)),
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(*a as f64 % **b),
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(**a % *b as f64),
}.map(Numeric::into)
}
// endregion
pub fn num(i: &Interner) -> ConstTree {
ConstTree::tree([(
i.i("num"),
ConstTree::tree([
(i.i("add"), ConstTree::xfn(Add)),
(i.i("subtract"), ConstTree::xfn(Subtract)),
(i.i("multiply"), ConstTree::xfn(Multiply)),
(i.i("divide"), ConstTree::xfn(Divide)),
(i.i("remainder"), ConstTree::xfn(Remainder)),
]),
)])
}

View File

@@ -1,16 +0,0 @@
mod numeric;
pub mod operators;
pub use numeric::Numeric;
use crate::interner::Interner;
use crate::pipeline::ConstTree;
pub fn num(i: &Interner) -> ConstTree {
ConstTree::tree([
(i.i("add"), ConstTree::xfn(operators::add::Add2)),
(i.i("subtract"), ConstTree::xfn(operators::subtract::Subtract2)),
(i.i("multiply"), ConstTree::xfn(operators::multiply::Multiply2)),
(i.i("divide"), ConstTree::xfn(operators::divide::Divide2)),
(i.i("remainder"), ConstTree::xfn(operators::remainder::Remainder2)),
])
}

View File

@@ -1,135 +0,0 @@
use std::ops::{Add, Div, Mul, Rem, Sub};
use std::rc::Rc;
use ordered_float::NotNan;
use super::super::assertion_error::AssertionError;
use super::super::litconv::with_lit;
use crate::foreign::ExternError;
use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::{Literal, Primitive};
/// A number, either floating point or unsigned int, visible to Orchid.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Numeric {
/// A nonnegative integer such as a size, index or count
Uint(u64),
/// A float other than NaN. Orchid has no silent errors
Num(NotNan<f64>),
}
impl Numeric {
/// Wrap a f64 in a Numeric
///
/// # Panics
///
/// if the value is NaN or Infinity.try_into()
fn num<T: Into<f64>>(value: T) -> Self {
let f = value.into();
assert!(f.is_finite(), "unrepresentable number");
NotNan::try_from(f).map(Self::Num).expect("not a number")
}
}
impl Add for Numeric {
type Output = Numeric;
fn add(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a + b),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a + b),
(Numeric::Uint(a), Numeric::Num(b))
| (Numeric::Num(b), Numeric::Uint(a)) =>
Numeric::num::<f64>(a as f64 + *b),
}
}
}
impl Sub for Numeric {
type Output = Numeric;
fn sub(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Numeric::Uint(a), Numeric::Uint(b)) if b <= a => Numeric::Uint(a - b),
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::num(a as f64 - b as f64),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a - b),
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 - *b),
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a - b as f64),
}
}
}
impl Mul for Numeric {
type Output = Numeric;
fn mul(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a * b),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a * b),
(Numeric::Uint(a), Numeric::Num(b))
| (Numeric::Num(b), Numeric::Uint(a)) =>
Numeric::Num(NotNan::new(a as f64).unwrap() * b),
}
}
}
impl Div for Numeric {
type Output = Numeric;
fn div(self, rhs: Self) -> Self::Output {
let a: f64 = self.into();
let b: f64 = rhs.into();
Numeric::num(a / b)
}
}
impl Rem for Numeric {
type Output = Numeric;
fn rem(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a % b),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a % b),
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 % *b),
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a % b as f64),
}
}
}
impl TryFrom<ExprInst> for Numeric {
type Error = Rc<dyn ExternError>;
fn try_from(value: ExprInst) -> Result<Self, Self::Error> {
with_lit(&value.clone(), |l| match l {
Literal::Uint(i) => Ok(Numeric::Uint(*i)),
Literal::Num(n) => Ok(Numeric::Num(*n)),
_ => AssertionError::fail(value, "an integer or number")?,
})
}
}
impl From<Numeric> for Clause {
fn from(value: Numeric) -> Self {
Clause::P(Primitive::Literal(match value {
Numeric::Uint(i) => Literal::Uint(i),
Numeric::Num(n) => Literal::Num(n),
}))
}
}
impl From<Numeric> for String {
fn from(value: Numeric) -> Self {
match value {
Numeric::Uint(i) => i.to_string(),
Numeric::Num(n) => n.to_string(),
}
}
}
impl From<Numeric> for f64 {
fn from(val: Numeric) -> Self {
match val {
Numeric::Num(n) => *n,
Numeric::Uint(i) => i as f64,
}
}
}

View File

@@ -1,31 +0,0 @@
use std::fmt::Debug;
use super::super::Numeric;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
#[derive(Clone)]
pub struct Add2;
externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1 { x }));
#[derive(Debug, Clone)]
pub struct Add1 {
x: ExprInst,
}
atomic_redirect!(Add1, x);
atomic_impl!(Add1);
externfn_impl!(Add1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Add0 { a, x })
});
#[derive(Debug, Clone)]
pub struct Add0 {
a: Numeric,
x: ExprInst,
}
atomic_redirect!(Add0, x);
atomic_impl!(Add0, |Self { a, x }: &Self, _| {
let b: Numeric = x.clone().try_into()?;
Ok((*a + b).into())
});

View File

@@ -1,37 +0,0 @@
use std::fmt::Debug;
use super::super::Numeric;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// Divides two numbers
///
/// Next state: [Divide1]
#[derive(Clone)]
pub struct Divide2;
externfn_impl!(Divide2, |_: &Self, x: ExprInst| Ok(Divide1 { x }));
/// Prev state: [Divide2]; Next state: [Divide0]
#[derive(Debug, Clone)]
pub struct Divide1 {
x: ExprInst,
}
atomic_redirect!(Divide1, x);
atomic_impl!(Divide1);
externfn_impl!(Divide1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Divide0 { a, x })
});
/// Prev state: [Divide1]
#[derive(Debug, Clone)]
pub struct Divide0 {
a: Numeric,
x: ExprInst,
}
atomic_redirect!(Divide0, x);
atomic_impl!(Divide0, |Self { a, x }: &Self, _| {
let b: Numeric = x.clone().try_into()?;
Ok((*a / b).into())
});

View File

@@ -1,5 +0,0 @@
pub mod add;
pub mod divide;
pub mod multiply;
pub mod remainder;
pub mod subtract;

View File

@@ -1,36 +0,0 @@
use std::fmt::Debug;
use super::super::Numeric;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// Multiplies two numbers
///
/// Next state: [Multiply1]
#[derive(Clone)]
pub struct Multiply2;
externfn_impl!(Multiply2, |_: &Self, x: ExprInst| Ok(Multiply1 { x }));
/// Prev state: [Multiply2]; Next state: [Multiply0]
#[derive(Debug, Clone)]
pub struct Multiply1 {
x: ExprInst,
}
atomic_redirect!(Multiply1, x);
atomic_impl!(Multiply1);
externfn_impl!(Multiply1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Multiply0 { a, x })
});
/// Prev state: [Multiply1]
#[derive(Debug, Clone)]
pub struct Multiply0 {
a: Numeric,
x: ExprInst,
}
atomic_redirect!(Multiply0, x);
atomic_impl!(Multiply0, |Self { a, x }: &Self, _| {
let b: Numeric = x.clone().try_into()?;
Ok((*a * b).into())
});

View File

@@ -1,36 +0,0 @@
use std::fmt::Debug;
use super::super::Numeric;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// Takes the modulus of two numbers.
///
/// Next state: [Remainder1]
#[derive(Clone)]
pub struct Remainder2;
externfn_impl!(Remainder2, |_: &Self, x: ExprInst| Ok(Remainder1 { x }));
/// Prev state: [Remainder2]; Next state: [Remainder0]
#[derive(Debug, Clone)]
pub struct Remainder1 {
x: ExprInst,
}
atomic_redirect!(Remainder1, x);
atomic_impl!(Remainder1);
externfn_impl!(Remainder1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Remainder0 { a, x })
});
/// Prev state: [Remainder1]
#[derive(Debug, Clone)]
pub struct Remainder0 {
a: Numeric,
x: ExprInst,
}
atomic_redirect!(Remainder0, x);
atomic_impl!(Remainder0, |Self { a, x }: &Self, _| {
let b: Numeric = x.clone().try_into()?;
Ok((*a % b).into())
});

View File

@@ -1,36 +0,0 @@
use std::fmt::Debug;
use super::super::Numeric;
use crate::representations::interpreted::ExprInst;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
/// Subtracts two numbers
///
/// Next state: [Subtract1]
#[derive(Clone)]
pub struct Subtract2;
externfn_impl!(Subtract2, |_: &Self, x: ExprInst| Ok(Subtract1 { x }));
/// Prev state: [Subtract2]; Next state: [Subtract0]
#[derive(Debug, Clone)]
pub struct Subtract1 {
x: ExprInst,
}
atomic_redirect!(Subtract1, x);
atomic_impl!(Subtract1);
externfn_impl!(Subtract1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Subtract0 { a, x })
});
/// Prev state: [Subtract1]
#[derive(Debug, Clone)]
pub struct Subtract0 {
a: Numeric,
x: ExprInst,
}
atomic_redirect!(Subtract0, x);
atomic_impl!(Subtract0, |Self { a, x }: &Self, _| {
let b: Numeric = x.clone().try_into()?;
Ok((*a - b).into())
});

View File

@@ -1,4 +1,4 @@
import std::panic import std::io::panic
export some := \v. \d.\f. f v export some := \v. \d.\f. f v
export none := \d.\f. d export none := \d.\f. d

15
src/stl/prelude.orc Normal file
View File

@@ -0,0 +1,15 @@
import std::num::*
export ::(+, -, *, /, %)
import std::str::*
export ::(++)
import std::bool::*
export ::(==, if, then, else)
import std::fn::*
export ::(loop, on, with, $, |>, =>)
import std::list
import std::map
import std::option
export ::(list, map, option)
import std::known::*
export ::(,)

12
src/stl/proc.orc Normal file
View File

@@ -0,0 +1,12 @@
export do { ...$statement ; ...$rest:1 } =0x2p130=> statement (...$statement) do { ...$rest }
export do { ...$return } =0x1p130=> ...$return
export statement (let $name = ...$value) ...$next =0x1p230=> (
(\$name. ...$next) (...$value)
)
export statement (cps $name = ...$operation) ...$next =0x2p230=> (
(...$operation) \$name. ...$next
)
export statement (cps ...$operation) ...$next =0x1p230=> (
(...$operation) (...$next)
)

1
src/stl/str.orc Normal file
View File

@@ -0,0 +1 @@
export ...$a ++ ...$b =0x4p36=> (concatenate (...$a) (...$b))

39
src/stl/str.rs Normal file
View File

@@ -0,0 +1,39 @@
use super::litconv::{with_str, with_uint};
use super::RuntimeError;
use crate::interner::Interner;
use crate::pipeline::ConstTree;
use crate::{define_fn, Literal};
define_fn! {expr=x in
/// Append a string to another
pub Concatenate {
a: String as with_str(x, |s| Ok(s.clone())),
b: String as with_str(x, |s| Ok(s.clone()))
} => Ok(Literal::Str(a.to_owned() + b).into())
}
define_fn! {expr=x in
pub CharAt {
s: String as with_str(x, |s| Ok(s.clone())),
i: u64 as with_uint(x, Ok)
} => {
if let Some(c) = s.chars().nth(*i as usize) {
Ok(Literal::Char(c).into())
} else {
RuntimeError::fail(
"Character index out of bounds".to_string(),
"indexing string",
)?
}
}
}
pub fn str(i: &Interner) -> ConstTree {
ConstTree::tree([(
i.i("str"),
ConstTree::tree([
(i.i("concatenate"), ConstTree::xfn(Concatenate)),
(i.i("char_at"), ConstTree::xfn(CharAt)),
]),
)])
}

View File

@@ -1,24 +0,0 @@
use super::super::litconv::{with_str, with_uint};
use super::super::runtime_error::RuntimeError;
use crate::interpreted::Clause;
use crate::{write_fn_step, Literal, Primitive};
write_fn_step!(pub CharAt2 > CharAt1);
write_fn_step!(
CharAt1 {}
CharAt0 where s = x => with_str(x, |s| Ok(s.clone())) ;
);
write_fn_step!(
CharAt0 { s: String }
i = x => with_uint(x, Ok);
{
if let Some(c) = s.chars().nth(i as usize) {
Ok(Clause::P(Primitive::Literal(Literal::Char(c))))
} else {
RuntimeError::fail(
"Character index out of bounds".to_string(),
"indexing string",
)?
}
}
);

View File

@@ -1,14 +0,0 @@
use super::super::litconv::with_str;
use crate::define_fn;
use crate::representations::interpreted::Clause;
use crate::representations::{Literal, Primitive};
define_fn! {expr=x in
/// Append a string to another
pub Concatenate {
a: String as with_str(x, |s| Ok(s.clone())),
b: String as with_str(x, |s| Ok(s.clone()))
} => {
Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + &b))))
}
}

View File

@@ -1,12 +0,0 @@
mod char_at;
mod concatenate;
use crate::interner::Interner;
use crate::pipeline::ConstTree;
pub fn str(i: &Interner) -> ConstTree {
ConstTree::tree([(
i.i("concatenate"),
ConstTree::xfn(concatenate::Concatenate),
)])
}

View File

@@ -3,6 +3,7 @@ mod print_nname;
mod pushed; mod pushed;
mod replace_first; mod replace_first;
mod side; mod side;
mod split_max_prefix;
mod string_from_charset; mod string_from_charset;
mod substack; mod substack;
mod unwrap_or; mod unwrap_or;
@@ -12,6 +13,7 @@ pub use print_nname::sym2string;
pub use pushed::pushed; pub use pushed::pushed;
pub use replace_first::replace_first; pub use replace_first::replace_first;
pub use side::Side; pub use side::Side;
pub use split_max_prefix::split_max_prefix;
pub use substack::{Stackframe, Substack, SubstackIterator}; pub use substack::{Stackframe, Substack, SubstackIterator};
pub(crate) use unwrap_or::unwrap_or; pub(crate) use unwrap_or::unwrap_or;
pub mod iter; pub mod iter;

View File

@@ -0,0 +1,14 @@
/// Split off the longest prefix accepted by the validator
#[allow(clippy::type_complexity)] // FIXME couldn't find a good factoring
pub fn split_max_prefix<'a, T>(
path: &'a [T],
is_valid: &impl Fn(&[T]) -> bool,
) -> Option<(&'a [T], &'a [T])> {
for split in (0..=path.len()).rev() {
let (filename, subpath) = path.split_at(split);
if is_valid(filename) {
return Some((filename, subpath));
}
}
None
}

View File

@@ -41,17 +41,13 @@ impl<'a, T> Substack<'a, T> {
} }
/// Create a new frame on top of this substack /// Create a new frame on top of this substack
pub fn new_frame(&'a self, item: T) -> Stackframe<'a, T> { pub fn new_frame(&'a self, item: T) -> Stackframe<'a, T> {
Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len) } Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len + 1) }
} }
/// obtain the previous stackframe if one exists /// obtain the previous stackframe if one exists
/// TODO: this should return a [Substack] /// TODO: this should return a [Substack]
pub fn pop(&'a self, count: usize) -> Option<&'a Stackframe<'a, T>> { pub fn pop(&'a self, count: usize) -> Option<&'a Stackframe<'a, T>> {
if let Self::Frame(p) = self { if let Self::Frame(p) = self {
if count == 0 { if count == 0 { Some(p) } else { p.prev.pop(count - 1) }
Some(p)
} else {
p.prev.pop(count - 1)
}
} else { } else {
None None
} }

View File

@@ -1,9 +1,13 @@
/// A macro version of [Option::unwrap_or_else] which supports flow /// A macro version of [Option::unwrap_or_else] which supports flow
/// control statements such as `return` and `break` in the "else" branch. /// control statements such as `return` and `break` in the "else" branch.
/// It also supports unwrapping concrete variants of other enums
macro_rules! unwrap_or { macro_rules! unwrap_or {
($m:expr; $fail:expr) => {{ ($m:expr; $fail:expr) => {{
if let Some(res) = ($m) { res } else { $fail } if let Some(res) = ($m) { res } else { $fail }
}}; }};
($m:expr => $pattern:path; $fail:expr) => {{
if let $pattern(res) = ($m) { res } else { $fail }
}};
} }
pub(crate) use unwrap_or; pub(crate) use unwrap_or;