Most files suffered major changes

- Less ambiguous syntax
- Better parser (Chumsky only does tokenization now)
- Tidy(|ier) error handling
- Facade for simplified embedding
- External code grouped in (fairly) self-contained Systems
- Dynamic action dispatch
- Many STL additions
This commit is contained in:
2023-08-17 20:47:08 +01:00
parent 751a02a1ec
commit 3fdabc29da
139 changed files with 4269 additions and 1783 deletions

93
Cargo.lock generated
View File

@@ -33,6 +33,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.3.2" version = "0.3.2"
@@ -183,6 +189,21 @@ 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 = "concurrent-queue"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.7" version = "0.2.7"
@@ -192,6 +213,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@@ -202,6 +232,19 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn 1.0.109",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -306,11 +349,12 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.13.2" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
dependencies = [ dependencies = [
"ahash 0.8.3", "ahash 0.8.3",
"allocator-api2",
] ]
[[package]] [[package]]
@@ -350,9 +394,9 @@ dependencies = [
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [ dependencies = [
"either", "either",
] ]
@@ -405,12 +449,14 @@ version = "0.2.2"
dependencies = [ dependencies = [
"chumsky", "chumsky",
"clap", "clap",
"derive_more",
"duplicate", "duplicate",
"dyn-clone", "dyn-clone",
"hashbrown 0.13.2", "hashbrown 0.14.0",
"itertools", "itertools",
"ordered-float", "ordered-float",
"paste", "paste",
"polling",
"rust-embed", "rust-embed",
"take_mut", "take_mut",
"thiserror", "thiserror",
@@ -433,6 +479,28 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "pin-project-lite"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
[[package]]
name = "polling"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
"bitflags",
"cfg-if",
"concurrent-queue",
"libc",
"log",
"pin-project-lite",
"windows-sys",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@@ -536,6 +604,15 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.37.19" version = "0.37.19"
@@ -559,6 +636,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "semver"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.160" version = "1.0.160"

View File

@@ -16,7 +16,7 @@ path = "src/lib.rs"
[[bin]] [[bin]]
name = "orcx" name = "orcx"
path = "src/bin/main.rs" path = "src/bin/orcx.rs"
doc = false doc = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -24,9 +24,9 @@ doc = false
[dependencies] [dependencies]
thiserror = "1.0" thiserror = "1.0"
chumsky = "0.9" chumsky = "0.9"
hashbrown = "0.13" hashbrown = "0.14"
ordered-float = "3.7" ordered-float = "3.7"
itertools = "0.10" itertools = "0.11"
dyn-clone = "1.0" dyn-clone = "1.0"
clap = { version = "4.3", features = ["derive"] } clap = { version = "4.3", features = ["derive"] }
trait-set = "0.3" trait-set = "0.3"
@@ -35,3 +35,5 @@ rust-embed = { version = "6.6", features = ["include-exclude"] }
duplicate = "1.0.0" duplicate = "1.0.0"
take_mut = "0.2.2" take_mut = "0.2.2"
unicode-segmentation = "1.10.1" unicode-segmentation = "1.10.1"
polling = "2.8.0"
derive_more = "0.99.17"

View File

@@ -1,5 +1,49 @@
# IO This document is a wishlist, its items aren't ordered in any way other than inline notes about dependency relations
All IO is event-based via callbacks. # Language
Driven streams such as stdin expose single-fire events for the results of functions such as "read until terminator" or "read N bytes".
Network IO exposes repeated events such as "connect", "message", etc. ## Operator declarations
A dedicated (exportable) line type for declaring operators. Still just names, only you can write them next to other things without whitespace
- ops may not contain c-ident-safe characters
- clusters of operator characters are broken up with a greedy algorithm
## Typeclasses
Elixir-style protocols probably, only with n-ary dispatch which I saw in SICP-js
# Rules
## Placeholder constraints
Simultaneously match a pattern to a subexpression and give it a name to copy it over
- Copy unique 1->1 names over by default to preserve their location info
# STL
## Command short-circuiting
Functions for each command type which destructure it and pass it to an Orchid callback
## Runtime error handling
result? multipath cps utils? Not sure yet.
## Pattern matching
This was the main trick in Orchid, still want to do it, still need to polish the language first
## Macro error handling
Error tokens with rules to lift them out. Kinda depends on preservation of location info in rules to be really useful
# Systems
## Async
Join allows to run code when a tuple of pending events all resolve on the event poller
## New: FS
Exposes tree operations to Orchid
Uses existing IO to open and read files
Uses the event bus to read directories in batches without blocking other Orchid code
## New: Network
Event-driven I/O with single-fire events and resubscription to relay backpressure to the OS. Initially TCP
## New: Marshall
Serialization of Orchid data, including code, given customizable sets of serializable foreign items. Alternatively, code reflection so that all this can go in the STL

View File

@@ -1,14 +1,15 @@
import std::(proc::*, to_float, to_string, io::(readline, print)) import std::(proc::*, to_float, to_string, panic, str::char_at)
export main := do{ export const main := do{
cps print "left operand: "; cps print "left operand: ";
cps data = readline; cps data = readln;
let a = to_float data; let a = to_float data;
cps print "operator: "; cps print "operator: ";
cps op = readline; cps op = readln;
cps print ("you selected \"" ++ op ++ "\"\n"); let op = char_at op 0;
cps println ("you selected \"" ++ op ++ "\"");
cps print "right operand: "; cps print "right operand: ";
cps data = readline; cps data = readln;
let b = to_float data; let b = to_float data;
let result = ( let result = (
if op == "+" then a + b if op == "+" then a + b
@@ -17,6 +18,6 @@ export main := do{
else if op == "/" then a / b else if op == "/" then a / b
else (panic "Unsupported operation") else (panic "Unsupported operation")
); );
cps print ("Result: " ++ to_string result ++ "\n"); cps println ("Result: " ++ to_string result);
0 0
} }

View File

@@ -1,3 +1,5 @@
import std::io::print const main := (
println "Hello, world!"
main := print "Hello, world!\n" "goodbye" "success"
)
-- main := "Hello, World!\n"

View File

@@ -1,6 +1,6 @@
import std::(proc::*, io::print, to_string) import std::(proc::*, to_string)
export main := do{ export const main := do{
let foo = list::new[1, 2, 3, 4, 5, 6]; let foo = list::new[1, 2, 3, 4, 5, 6];
let bar = list::map foo n => n * 2; let bar = list::map foo n => n * 2;
let sum = bar let sum = bar
@@ -8,6 +8,6 @@ export main := do{
|> list::take 3 |> list::take 3
|> list::reduce (\a.\b. a + b) |> list::reduce (\a.\b. a + b)
|> option::unwrap; |> option::unwrap;
cps print $ to_string sum ++ "\n"; cps println $ to_string sum;
0 0
} }

View File

@@ -1,6 +1,6 @@
import std::(proc::*, io::print, to_string) import std::(proc::*, to_string)
export main := do{ export const main := do{
let foo = map::new[ let foo = map::new[
"foo" = 1, "foo" = 1,
"bar" = 2, "bar" = 2,
@@ -9,6 +9,6 @@ export main := do{
]; ];
let num = map::get foo "bar" let num = map::get foo "bar"
|> option::unwrap; |> option::unwrap;
cps print (to_string num ++ "\n"); cps println $ to_string num;
0 0
} }

View File

@@ -36,7 +36,6 @@
}, },
"extensions": { "extensions": {
"recommendations": [ "recommendations": [
"bungcip.better-toml",
"maptz.regionfolder", "maptz.regionfolder",
"serayuzgur.crates", "serayuzgur.crates",
"tamasfe.even-better-toml", "tamasfe.even-better-toml",

View File

@@ -1,183 +0,0 @@
mod cli;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::{iter, process};
use clap::Parser;
use hashbrown::HashMap;
use itertools::Itertools;
use orchidlang::interner::InternedDisplay;
use orchidlang::{
ast, ast_to_interpreted, collect_consts, collect_rules, interpreter,
pipeline, rule, stl, vname_to_sym_tree, Interner, ProjectTree, Stok, Sym,
VName,
};
use crate::cli::cmd_prompt;
/// Orchid interpreter
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Folder containing main.orc or the manually specified entry module
#[arg(short, long, default_value = ".")]
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 {
/// 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> {
self.chk_dir_main()
}
}
/// Load and parse all source related to the symbol `target` or all symbols
/// in the namespace `target` in the context of the STL. All sourcefiles must
/// reside within `dir`.
fn load_dir(dir: &Path, target: &[Stok], i: &Interner) -> ProjectTree<VName> {
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(
iter::once(target),
&|path| file_cache.find(path),
&library,
&stl::mk_prelude(i),
i,
)
.expect("Failed to load source code")
}
pub fn to_vname(data: &str, i: &Interner) -> VName {
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<Sym>, 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;
},
}
}
}
pub fn main() {
let args = Args::parse();
args.chk_proj().unwrap_or_else(|e| panic!("{e}"));
let dir = PathBuf::try_from(args.dir).unwrap();
let i = Interner::new();
let main = to_vname(&args.main, &i);
let project = vname_to_sym_tree(load_dir(&dir, &main, &i), &i);
let rules = collect_rules(&project);
let consts = collect_consts(&project, &i);
let repo = rule::Repo::new(rules, &i).unwrap_or_else(|(rule, error)| {
panic!(
"Rule error: {}
Offending rule: {}",
error.bundle(&i),
rule.bundle(&i)
)
});
if args.dump_repo {
println!("Parsed rules: {}", repo.bundle(&i));
return;
} else if !args.macro_debug.is_empty() {
let name = i.i(&to_vname(&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();
for (name, source) in consts.iter() {
let displayname = i.extern_vec(*name).join("::");
let (unmatched, steps_left) = repo.long_step(source, args.macro_limit + 1);
assert!(steps_left > 0, "Macro execution in {displayname} did not halt");
let runtree = ast_to_interpreted(&unmatched).unwrap_or_else(|e| {
panic!("Postmacro conversion error in {displayname}: {e}")
});
exec_table.insert(*name, runtree);
}
let ctx =
interpreter::Context { symbols: &exec_table, interner: &i, gas: None };
let entrypoint = exec_table.get(&i.i(&main)).unwrap_or_else(|| {
let main = args.main;
let symbols =
exec_table.keys().map(|t| i.extern_vec(*t).join("::")).join(", ");
panic!(
"Entrypoint not found!
Entrypoint was {main}
known keys are {symbols}"
)
});
let io_handler = orchidlang::stl::handleIO;
let ret = interpreter::run_handler(entrypoint.clone(), io_handler, ctx);
let interpreter::Return { gas, state, inert } =
ret.unwrap_or_else(|e| panic!("Runtime error: {}", e));
if inert {
println!("Settled at {}", state.expr().clause.bundle(&i));
if let Some(g) = gas {
println!("Remaining gas: {g}")
}
} else if gas == Some(0) {
eprintln!("Ran out of gas!");
process::exit(-1);
}
}

172
src/bin/orcx.rs Normal file
View File

@@ -0,0 +1,172 @@
mod cli;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use std::process;
use clap::Parser;
use itertools::Itertools;
use orchidlang::facade::{Environment, PreMacro};
use orchidlang::interner::InternedDisplay;
use orchidlang::systems::stl::StlConfig;
use orchidlang::systems::{io_system, AsynchConfig, IOStream};
use orchidlang::{ast, interpreted, interpreter, Interner, Sym, VName};
use crate::cli::cmd_prompt;
/// Orchid interpreter
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Folder containing main.orc or the manually specified entry module
#[arg(short, long, default_value = ".")]
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 {
/// 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> {
self.chk_dir_main()
}
}
pub fn to_vname(data: &str, i: &Interner) -> VName {
data.split("::").map(|s| i.i(s)).collect::<Vec<_>>()
}
fn print_for_debug(e: &ast::Expr<Sym>, i: &Interner) {
print!(
"code: {}\nglossary: {}",
e.bundle(i),
(e.value.collect_names().into_iter())
.map(|t| i.extern_vec(t).join("::"))
.join(", ")
)
}
/// A little utility to step through the resolution of a macro set
pub fn macro_debug(premacro: PreMacro, sym: Sym, i: &Interner) {
let (mut code, location) = (premacro.consts.get(&sym))
.unwrap_or_else(|| {
panic!(
"Symbol {} not found\nvalid symbols: \n\t{}\n",
i.extern_vec(sym).join("::"),
(premacro.consts.keys())
.map(|t| i.extern_vec(*t).join("::"))
.join("\n\t")
)
})
.clone();
println!(
"Debugging macros in {} defined at {}.
Initial state: ",
i.extern_vec(sym).join("::"),
location
);
print_for_debug(&code, i);
let mut steps = premacro.step(sym).enumerate();
loop {
let (cmd, _) = cmd_prompt("\ncmd> ").unwrap();
match cmd.trim() {
"" | "n" | "next" =>
if let Some((idx, c)) = steps.next() {
code = c;
print!("Step {idx}: ");
print_for_debug(&code, i);
} else {
print!("Halted")
},
"p" | "print" => print_for_debug(&code, i),
"d" | "dump" => print!("Rules: {}", premacro.repo.bundle(i)),
"q" | "quit" => return,
"h" | "help" => print!(
"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"
),
_ => {
print!("unrecognized command \"{}\", try \"help\"", cmd);
continue;
},
}
}
}
pub fn main() {
let args = Args::parse();
args.chk_proj().unwrap_or_else(|e| panic!("{e}"));
let dir = PathBuf::try_from(args.dir).unwrap();
let i = Interner::new();
let main = to_vname(&args.main, &i);
let mut asynch = AsynchConfig::new();
let io = io_system(&mut asynch, None, None, [
("stdin", IOStream::Source(BufReader::new(Box::new(std::io::stdin())))),
("stdout", IOStream::Sink(Box::new(std::io::stdout()))),
("stderr", IOStream::Sink(Box::new(std::io::stderr()))),
]);
let env = Environment::new(&i)
.add_system(StlConfig { impure: true })
.add_system(asynch)
.add_system(io);
let premacro = i.unwrap(env.load_dir(&dir, &main));
if args.dump_repo {
println!("Parsed rules: {}", premacro.repo.bundle(&i));
return;
}
if !args.macro_debug.is_empty() {
let sym = i.i(&to_vname(&args.macro_debug, &i));
return macro_debug(premacro, sym, &i);
}
let mut proc = i.unwrap(premacro.build_process(Some(args.macro_limit)));
let main = interpreted::Clause::Constant(i.i(&main)).wrap();
let ret = i.unwrap(proc.run(main, None));
let interpreter::Return { gas, state, inert } = ret;
drop(proc);
if inert {
println!("Settled at {}", state.expr().clause.bundle(&i));
if let Some(g) = gas {
println!("Remaining gas: {g}")
}
} else if gas == Some(0) {
eprintln!("Ran out of gas!");
process::exit(-1);
}
}

View File

@@ -4,27 +4,31 @@ use super::{ErrorPosition, ProjectError};
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::utils::iter::box_once; use crate::utils::iter::box_once;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::{Interner, VName};
/// Error produced for the statement `import *` /// Error produced for the statement `import *`
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ImportAll { pub struct ImportAll {
/// The file containing the offending import /// The file containing the offending import
pub offender_file: Vec<String>, pub offender_file: Rc<Vec<String>>,
/// The module containing the offending import /// The module containing the offending import
pub offender_mod: Vec<String>, pub offender_mod: Rc<VName>,
} }
impl ProjectError for ImportAll { impl ProjectError for ImportAll {
fn description(&self) -> &str { fn description(&self) -> &str {
"a top-level glob import was used" "a top-level glob import was used"
} }
fn message(&self) -> String { fn message(&self, i: &Interner) -> String {
format!("{} imports *", self.offender_mod.join("::")) format!("{} imports *", i.extern_all(&self.offender_mod).join("::"))
} }
fn positions(&self) -> BoxedIter<ErrorPosition> { fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { box_once(ErrorPosition {
location: Location::File(Rc::new(self.offender_file.clone())), location: Location::File(self.offender_file.clone()),
message: Some(format!("{} imports *", self.offender_mod.join("::"))), message: Some(format!(
"{} imports *",
i.extern_all(&self.offender_mod).join("::")
)),
}) })
} }
} }

View File

@@ -1,18 +1,20 @@
//! Various errors the pipeline can produce //! Various errors the pipeline can produce
mod import_all; mod import_all;
mod no_targets;
mod not_exported; mod not_exported;
mod not_found; mod not_found;
mod parse_error_with_path; mod parse_error_with_tokens;
mod project_error; mod project_error;
mod too_many_supers; mod too_many_supers;
mod unexpected_directory; mod unexpected_directory;
mod visibility_mismatch; mod visibility_mismatch;
pub use import_all::ImportAll; pub use import_all::ImportAll;
pub use no_targets::NoTargets;
pub use not_exported::NotExported; pub use not_exported::NotExported;
pub use not_found::NotFound; pub use not_found::NotFound;
pub use parse_error_with_path::ParseErrorWithPath; pub use parse_error_with_tokens::ParseErrorWithTokens;
pub use project_error::{ErrorPosition, ProjectError}; pub use project_error::{ErrorPosition, ProjectError, ProjectResult};
pub use too_many_supers::TooManySupers; pub use too_many_supers::TooManySupers;
pub use unexpected_directory::UnexpectedDirectory; pub use unexpected_directory::UnexpectedDirectory;
pub use visibility_mismatch::VisibilityMismatch; pub use visibility_mismatch::VisibilityMismatch;

23
src/error/no_targets.rs Normal file
View File

@@ -0,0 +1,23 @@
use super::{ErrorPosition, ProjectError};
#[allow(unused)] // for doc
use crate::parse_layer;
use crate::utils::iter::box_empty;
use crate::utils::BoxedIter;
use crate::Interner;
/// Error produced when [parse_layer] is called without targets. This function
/// produces an error instead of returning a straightforward empty tree because
/// the edge case of no targets is often an error and should generally be
/// handled explicitly
#[derive(Debug)]
pub struct NoTargets;
impl ProjectError for NoTargets {
fn description(&self) -> &str {
"No targets were specified for layer parsing"
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_empty()
}
}

View File

@@ -3,35 +3,39 @@ use std::rc::Rc;
use super::{ErrorPosition, ProjectError}; use super::{ErrorPosition, ProjectError};
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::{Interner, VName};
/// An import refers to a symbol which exists but is not exported. /// An import refers to a symbol which exists but is not exported.
#[derive(Debug)] #[derive(Debug)]
pub struct NotExported { pub struct NotExported {
/// The containing file - files are always exported /// The containing file - files are always exported
pub file: Vec<String>, pub file: VName,
/// The path leading to the unexported module /// The path leading to the unexported module
pub subpath: Vec<String>, pub subpath: VName,
/// The offending file /// The offending file
pub referrer_file: Vec<String>, pub referrer_file: VName,
/// The module containing the offending import /// The module containing the offending import
pub referrer_subpath: Vec<String>, pub referrer_subpath: VName,
} }
impl ProjectError for NotExported { impl ProjectError for NotExported {
fn description(&self) -> &str { fn description(&self) -> &str {
"An import refers to a symbol that exists but isn't exported" "An import refers to a symbol that exists but isn't exported"
} }
fn positions(&self) -> BoxedIter<ErrorPosition> { fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
Box::new( Box::new(
[ [
ErrorPosition { ErrorPosition {
location: Location::File(Rc::new(self.file.clone())), location: Location::File(Rc::new(i.extern_all(&self.file))),
message: Some(format!("{} isn't exported", self.subpath.join("::"))), message: Some(format!(
"{} isn't exported",
i.extern_all(&self.subpath).join("::")
)),
}, },
ErrorPosition { ErrorPosition {
location: Location::File(Rc::new(self.referrer_file.clone())), location: Location::File(Rc::new(i.extern_all(&self.referrer_file))),
message: Some(format!( message: Some(format!(
"{} cannot see this symbol", "{} cannot see this symbol",
self.referrer_subpath.join("::") i.extern_all(&self.referrer_subpath).join("::")
)), )),
}, },
] ]

View File

@@ -5,15 +5,17 @@ use crate::tree::Module;
use crate::tree::WalkError; use crate::tree::WalkError;
use crate::utils::iter::box_once; use crate::utils::iter::box_once;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::{Interner, NameLike, Tok}; use crate::{Interner, NameLike, Tok, VName};
/// Error produced when an import refers to a nonexistent module /// Error produced when an import refers to a nonexistent module
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct NotFound { pub struct NotFound {
/// The file containing the invalid import /// The module that imported the invalid path
pub file: Vec<String>, pub source: Option<VName>,
/// The file not containing the expected path
pub file: VName,
/// The invalid import path /// The invalid import path
pub subpath: Vec<String>, pub subpath: VName,
} }
impl NotFound { impl NotFound {
/// Produce this error from the parameters of [Module]`::walk_ref` and a /// Produce this error from the parameters of [Module]`::walk_ref` and a
@@ -27,23 +29,27 @@ impl NotFound {
/// Basically, if `e` was not produced by the `walk*` methods called on /// Basically, if `e` was not produced by the `walk*` methods called on
/// `path`. /// `path`.
pub fn from_walk_error( pub fn from_walk_error(
source: &[Tok<String>],
prefix: &[Tok<String>], prefix: &[Tok<String>],
path: &[Tok<String>], path: &[Tok<String>],
orig: &ProjectModule<impl NameLike>, orig: &ProjectModule<impl NameLike>,
e: WalkError, e: WalkError,
i: &Interner,
) -> Self { ) -> Self {
let last_mod = let last_mod =
orig.walk_ref(&path[..e.pos], false).expect("error occured on next step"); orig.walk_ref(&path[..e.pos], false).expect("error occured on next step");
let mut whole_path = let mut whole_path = prefix.iter().chain(path.iter()).copied();
prefix.iter().chain(path.iter()).map(|t| i.r(*t)).cloned();
if let Some(file) = &last_mod.extra.file { if let Some(file) = &last_mod.extra.file {
Self { Self {
source: Some(source.to_vec()),
file: whole_path.by_ref().take(file.len()).collect(), file: whole_path.by_ref().take(file.len()).collect(),
subpath: whole_path.collect(), subpath: whole_path.collect(),
} }
} else { } else {
Self { file: whole_path.collect(), subpath: Vec::new() } Self {
source: Some(source.to_vec()),
file: whole_path.collect(),
subpath: Vec::new(),
}
} }
} }
} }
@@ -51,14 +57,14 @@ impl ProjectError for NotFound {
fn description(&self) -> &str { fn description(&self) -> &str {
"an import refers to a nonexistent module" "an import refers to a nonexistent module"
} }
fn message(&self) -> String { fn message(&self, i: &Interner) -> String {
format!( format!(
"module {} in {} was not found", "module {} in {} was not found",
self.subpath.join("::"), i.extern_all(&self.subpath).join("::"),
self.file.join("/"), i.extern_all(&self.file).join("/"),
) )
} }
fn positions(&self) -> BoxedIter<ErrorPosition> { fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition::just_file(self.file.clone())) box_once(ErrorPosition::just_file(i.extern_all(&self.file)))
} }
} }

View File

@@ -0,0 +1,34 @@
use std::rc::Rc;
use itertools::Itertools;
use super::{ErrorPosition, ProjectError};
use crate::interner::InternedDisplay;
use crate::parse::Entry;
use crate::utils::BoxedIter;
use crate::Interner;
/// Produced by stages that parse text when it fails.
pub struct ParseErrorWithTokens {
/// The complete source of the faulty file
pub full_source: String,
/// Tokens, if the error did not occur during tokenization
pub tokens: Vec<Entry>,
/// The parse error produced by Chumsky
pub error: Rc<dyn ProjectError>,
}
impl ProjectError for ParseErrorWithTokens {
fn description(&self) -> &str {
self.error.description()
}
fn message(&self, i: &Interner) -> String {
format!(
"Failed to parse code: {}\nTokenized source for context:\n{}",
self.error.message(i),
self.tokens.iter().map(|t| t.to_string_i(i)).join(" "),
)
}
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
self.error.positions(i)
}
}

View File

@@ -1,8 +1,9 @@
use std::fmt::{Debug, Display};
use std::rc::Rc; use std::rc::Rc;
use crate::interner::InternedDisplay;
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::Interner;
/// A point of interest in resolving the error, such as the point where /// A point of interest in resolving the error, such as the point where
/// processing got stuck, a command that is likely to be incorrect /// processing got stuck, a command that is likely to be incorrect
@@ -22,15 +23,15 @@ impl ErrorPosition {
/// Errors addressed to the developer which are to be resolved with /// Errors addressed to the developer which are to be resolved with
/// code changes /// code changes
pub trait ProjectError: Debug { pub trait ProjectError {
/// A general description of this type of error /// A general description of this type of error
fn description(&self) -> &str; fn description(&self) -> &str;
/// A formatted message that includes specific parameters /// A formatted message that includes specific parameters
fn message(&self) -> String { fn message(&self, _i: &Interner) -> String {
String::new() self.description().to_string()
} }
/// Code positions relevant to this error /// Code positions relevant to this error
fn positions(&self) -> BoxedIter<ErrorPosition>; fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition>;
/// Convert the error into an `Rc<dyn ProjectError>` to be able to /// Convert the error into an `Rc<dyn ProjectError>` to be able to
/// handle various errors together /// handle various errors together
fn rc(self) -> Rc<dyn ProjectError> fn rc(self) -> Rc<dyn ProjectError>
@@ -41,14 +42,18 @@ pub trait ProjectError: Debug {
} }
} }
impl Display for dyn ProjectError { impl InternedDisplay for dyn ProjectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt_i(
&self,
f: &mut std::fmt::Formatter<'_>,
i: &Interner,
) -> std::fmt::Result {
let description = self.description(); let description = self.description();
let message = self.message(); let message = self.message(i);
let positions = self.positions(); let positions = self.positions(i);
write!(f, "Problem with the project: {description}; {message}")?; writeln!(f, "Project error: {description}\n{message}")?;
for ErrorPosition { location, message } in positions { for ErrorPosition { location, message } in positions {
write!( writeln!(
f, f,
"@{location}: {}", "@{location}: {}",
message.unwrap_or("location of interest".to_string()) message.unwrap_or("location of interest".to_string())
@@ -57,3 +62,7 @@ impl Display for dyn ProjectError {
Ok(()) Ok(())
} }
} }
/// Alias for a result with an error of [Rc] of [ProjectError] trait object.
/// This is the type of result most commonly returned by pre-run operations.
pub type ProjectResult<T> = Result<T, Rc<dyn ProjectError>>;

View File

@@ -4,38 +4,39 @@ use super::{ErrorPosition, ProjectError};
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::utils::iter::box_once; use crate::utils::iter::box_once;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::{Interner, VName};
/// Error produced when an import path starts with more `super` segments /// Error produced when an import path starts with more `super` segments
/// than the current module's absolute path /// than the current module's absolute path
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TooManySupers { pub struct TooManySupers {
/// The offending import path /// The offending import path
pub path: Vec<String>, pub path: VName,
/// The file containing the offending import /// The file containing the offending import
pub offender_file: Vec<String>, pub offender_file: VName,
/// The module containing the offending import /// The module containing the offending import
pub offender_mod: Vec<String>, pub offender_mod: VName,
} }
impl ProjectError for TooManySupers { impl ProjectError for TooManySupers {
fn description(&self) -> &str { fn description(&self) -> &str {
"an import path starts with more `super` segments than the current \ "an import path starts with more `super` segments than the current \
module's absolute path" module's absolute path"
} }
fn message(&self) -> String { fn message(&self, i: &Interner) -> String {
format!( format!(
"path {} in {} contains too many `super` steps.", "path {} in {} contains too many `super` steps.",
self.path.join("::"), i.extern_all(&self.path).join("::"),
self.offender_mod.join("::") i.extern_all(&self.offender_mod).join("::")
) )
} }
fn positions(&self) -> BoxedIter<ErrorPosition> { fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { box_once(ErrorPosition {
location: Location::File(Rc::new(self.offender_file.clone())), location: Location::File(Rc::new(i.extern_all(&self.offender_file))),
message: Some(format!( message: Some(format!(
"path {} in {} contains too many `super` steps.", "path {} in {} contains too many `super` steps.",
self.path.join("::"), i.extern_all(&self.path).join("::"),
self.offender_mod.join("::") i.extern_all(&self.offender_mod).join("::")
)), )),
}) })
} }

View File

@@ -1,26 +1,27 @@
use super::{ErrorPosition, ProjectError}; use super::{ErrorPosition, ProjectError};
use crate::utils::iter::box_once; use crate::utils::iter::box_once;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::{Interner, VName};
/// Produced when a stage that deals specifically with code encounters /// Produced when a stage that deals specifically with code encounters
/// a path that refers to a directory /// a path that refers to a directory
#[derive(Debug)] #[derive(Debug)]
pub struct UnexpectedDirectory { pub struct UnexpectedDirectory {
/// Path to the offending collection /// Path to the offending collection
pub path: Vec<String>, pub path: VName,
} }
impl ProjectError for UnexpectedDirectory { impl ProjectError for UnexpectedDirectory {
fn description(&self) -> &str { fn description(&self) -> &str {
"A stage that deals specifically with code encountered a path that refers \ "A stage that deals specifically with code encountered a path that refers \
to a directory" to a directory"
} }
fn positions(&self) -> BoxedIter<ErrorPosition> { fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition::just_file(self.path.clone())) box_once(ErrorPosition::just_file(i.extern_all(&self.path)))
} }
fn message(&self) -> String { fn message(&self, i: &Interner) -> String {
format!( format!(
"{} was expected to be a file but a directory was found", "{} was expected to be a file but a directory was found",
self.path.join("/") i.extern_all(&self.path).join("/")
) )
} }
} }

View File

@@ -4,12 +4,13 @@ use super::project_error::{ErrorPosition, ProjectError};
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::utils::iter::box_once; use crate::utils::iter::box_once;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::{Interner, VName};
/// Multiple occurences of the same namespace with different visibility /// Multiple occurences of the same namespace with different visibility
#[derive(Debug)] #[derive(Debug)]
pub struct VisibilityMismatch { pub struct VisibilityMismatch {
/// The namespace with ambiguous visibility /// The namespace with ambiguous visibility
pub namespace: Vec<String>, pub namespace: VName,
/// The file containing the namespace /// The file containing the namespace
pub file: Rc<Vec<String>>, pub file: Rc<Vec<String>>,
} }
@@ -17,12 +18,12 @@ impl ProjectError for VisibilityMismatch {
fn description(&self) -> &str { fn description(&self) -> &str {
"Some occurences of a namespace are exported but others are not" "Some occurences of a namespace are exported but others are not"
} }
fn positions(&self) -> BoxedIter<ErrorPosition> { fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { box_once(ErrorPosition {
location: Location::File(self.file.clone()), location: Location::File(self.file.clone()),
message: Some(format!( message: Some(format!(
"{} is opened multiple times with different visibilities", "{} is opened multiple times with different visibilities",
self.namespace.join("::") i.extern_all(&self.namespace).join("::")
)), )),
}) })
} }

91
src/facade/environment.rs Normal file
View File

@@ -0,0 +1,91 @@
use std::iter;
use std::path::Path;
use hashbrown::HashMap;
use super::system::{IntoSystem, System};
use super::PreMacro;
use crate::error::ProjectResult;
use crate::pipeline::file_loader;
use crate::sourcefile::FileEntry;
use crate::{
from_const_tree, parse_layer, vname_to_sym_tree, Interner, ProjectTree, Stok,
VName,
};
/// A compiled environment ready to load user code. It stores the list of
/// systems and combines with usercode to produce a [Process]
pub struct Environment<'a> {
/// [Interner] pseudo-global
pub i: &'a Interner,
systems: Vec<System<'a>>,
}
impl<'a> Environment<'a> {
/// Initialize a new environment
pub fn new(i: &'a Interner) -> Self {
Self { i, systems: Vec::new() }
}
/// Register a new system in the environment
pub fn add_system<'b: 'a>(mut self, is: impl IntoSystem<'b> + 'b) -> Self {
self.systems.push(Box::new(is).into_system(self.i));
self
}
/// Compile the environment from the set of systems and return it directly.
/// See [#load_dir]
pub fn compile(self) -> ProjectResult<CompiledEnv<'a>> {
let Self { i, systems, .. } = self;
let mut tree = from_const_tree(HashMap::new(), &[i.i("none")]);
for sys in systems.iter() {
let system_tree = from_const_tree(sys.constants.clone(), &sys.vname(i));
tree = ProjectTree(tree.0.overlay(system_tree.0));
}
let mut prelude = vec![];
for sys in systems.iter() {
if !sys.code.is_empty() {
tree = parse_layer(
sys.code.keys().map(|sym| &sym[..]),
&|k| sys.load_file(k),
&tree,
&prelude,
i,
)?;
}
prelude.extend_from_slice(&sys.prelude);
}
Ok(CompiledEnv { prelude, tree, systems })
}
/// Load a directory from the local file system as an Orchid project.
pub fn load_dir(
self,
dir: &Path,
target: &[Stok],
) -> ProjectResult<PreMacro<'a>> {
let i = self.i;
let CompiledEnv { prelude, systems, tree } = self.compile()?;
let file_cache = file_loader::mk_dir_cache(dir.to_path_buf(), i);
let vname_tree = parse_layer(
iter::once(target),
&|path| file_cache.find(path),
&tree,
&prelude,
i,
)?;
let tree = vname_to_sym_tree(vname_tree, i);
PreMacro::new(tree, systems, i)
}
}
/// Compiled environment waiting for usercode. An intermediate step between
/// [Environment] and [Process]
pub struct CompiledEnv<'a> {
/// Namespace tree for pre-defined symbols with symbols at the leaves and
/// rules defined on the nodes
pub tree: ProjectTree<VName>,
/// Lines prepended to each usercode file
pub prelude: Vec<FileEntry>,
/// List of systems to source handlers for the interpreter
pub systems: Vec<System<'a>>,
}

12
src/facade/mod.rs Normal file
View File

@@ -0,0 +1,12 @@
//! A simplified set of commands each grouping a large subset of the operations
//! exposed by Orchid to make writing embeddings faster in the typical case.
mod environment;
mod pre_macro;
mod process;
mod system;
pub use environment::{CompiledEnv, Environment};
pub use pre_macro::{MacroTimeout, PreMacro};
pub use process::Process;
pub use system::{IntoSystem, MissingSystemCode, System};

134
src/facade/pre_macro.rs Normal file
View File

@@ -0,0 +1,134 @@
use std::iter;
use std::rc::Rc;
use hashbrown::HashMap;
use super::{Process, System};
use crate::error::{ErrorPosition, ProjectError, ProjectResult};
use crate::interpreter::HandlerTable;
use crate::rule::Repo;
use crate::utils::iter::box_once;
use crate::utils::BoxedIter;
use crate::{
ast, ast_to_interpreted, collect_consts, collect_rules, rule, Interner,
Location, ProjectTree, Sym,
};
/// Everything needed for macro execution, and constructing the process
pub struct PreMacro<'a> {
/// Optimized catalog of substitution rules
pub repo: Repo,
/// Runtime code containing macro invocations
pub consts: HashMap<Sym, (ast::Expr<Sym>, Location)>,
/// Libraries and plug-ins
pub systems: Vec<System<'a>>,
/// [Interner] pseudo-global
pub i: &'a Interner,
}
impl<'a> PreMacro<'a> {
/// Build a [PreMacro] from a source tree and system list
pub fn new(
tree: ProjectTree<Sym>,
systems: Vec<System<'a>>,
i: &'a Interner,
) -> ProjectResult<Self> {
let consts = collect_consts(&tree, i);
let rules = collect_rules(&tree);
let repo = match rule::Repo::new(rules, i) {
Ok(r) => r,
Err((rule, error)) => {
return Err(error.to_project_error(&rule));
},
};
Ok(Self {
repo,
consts: (consts.into_iter())
.map(|(name, expr)| {
let location = (i.r(name).split_last())
.and_then(|(_, path)| {
let origin = (tree.0.walk_ref(path, false))
.expect("path sourced from symbol names");
origin.extra.file.as_ref().map(|path| i.extern_all(&path[..]))
})
.map(|p| Location::File(Rc::new(p)))
.unwrap_or(Location::Unknown);
(name, (expr, location))
})
.collect(),
i,
systems,
})
}
/// Run all macros to termination or the optional timeout. If a timeout does
/// not occur, returns a process which can execute Orchid code
pub fn build_process(
self,
timeout: Option<usize>,
) -> ProjectResult<Process<'a>> {
let Self { i, systems, repo, consts } = self;
let mut symbols = HashMap::new();
for (name, (source, source_location)) in consts.iter() {
let unmatched = if let Some(limit) = timeout {
let (unmatched, steps_left) = repo.long_step(source, limit + 1);
if steps_left == 0 {
return Err(
MacroTimeout {
location: source_location.clone(),
symbol: *name,
limit,
}
.rc(),
);
} else {
unmatched
}
} else {
repo.pass(source).unwrap_or_else(|| source.clone())
};
let runtree = ast_to_interpreted(&unmatched).map_err(|e| e.rc())?;
symbols.insert(*name, runtree);
}
Ok(Process {
symbols,
i,
handlers: (systems.into_iter())
.fold(HandlerTable::new(), |tbl, sys| tbl.combine(sys.handlers)),
})
}
/// Obtain an iterator that steps through the preprocessing of a constant
/// for debugging macros
pub fn step(&self, sym: Sym) -> impl Iterator<Item = ast::Expr<Sym>> + '_ {
let mut target = self.consts.get(&sym).expect("Target not found").0.clone();
iter::from_fn(move || {
target = self.repo.step(&target)?;
Some(target.clone())
})
}
}
/// Error raised when a macro runs too long
#[derive(Debug)]
pub struct MacroTimeout {
location: Location,
symbol: Sym,
limit: usize,
}
impl ProjectError for MacroTimeout {
fn description(&self) -> &str {
"Macro execution has not halted"
}
fn message(&self, i: &Interner) -> String {
format!(
"Macro execution during the processing of {} took more than {} steps",
i.extern_vec(self.symbol).join("::"),
self.limit
)
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.location.clone(), message: None })
}
}

90
src/facade/process.rs Normal file
View File

@@ -0,0 +1,90 @@
use hashbrown::HashMap;
use crate::error::{ErrorPosition, ProjectError, ProjectResult};
use crate::interpreted::{self, ExprInst};
#[allow(unused)] // for doc
use crate::interpreter;
use crate::interpreter::{
run_handler, Context, HandlerTable, Return, RuntimeError,
};
use crate::utils::iter::box_once;
use crate::utils::BoxedIter;
use crate::{Interner, Location, Sym};
/// This struct ties the state of systems to loaded code, and allows to call
/// Orchid-defined functions
pub struct Process<'a> {
pub(crate) symbols: HashMap<Sym, ExprInst>,
pub(crate) handlers: HandlerTable<'a>,
pub(crate) i: &'a Interner,
}
impl<'a> Process<'a> {
/// Execute the given command in this process. If gas is specified, at most as
/// many steps will be executed and then the partial result returned.
///
/// This is useful to catch infinite loops or ensure that a tenant program
/// yields
pub fn run(
&mut self,
prompt: ExprInst,
gas: Option<usize>,
) -> Result<Return, RuntimeError> {
let ctx = Context { gas, interner: self.i, symbols: &self.symbols };
run_handler(prompt, &mut self.handlers, ctx)
}
/// Find all unbound constant names in a symbol. This is often useful to
/// identify dynamic loading targets.
pub fn unbound_refs(&self, key: Sym) -> Vec<(Sym, Location)> {
let mut errors = Vec::new();
let sym = self.symbols.get(&key).expect("symbol must exist");
sym.search_all(&mut |s: &ExprInst| {
let expr = s.expr();
if let interpreted::Clause::Constant(sym) = expr.clause {
if !self.symbols.contains_key(&sym) {
errors.push((sym, expr.location.clone()))
}
}
None::<()>
});
errors
}
/// Assert that, unless [interpreted::Clause::Constant]s are created
/// procedurally, a [interpreter::RuntimeError::MissingSymbol] cannot be
/// produced
pub fn validate_refs(&self) -> ProjectResult<()> {
for key in self.symbols.keys() {
if let Some((symbol, location)) = self.unbound_refs(*key).pop() {
return Err(MissingSymbol { location, referrer: *key, symbol }.rc());
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct MissingSymbol {
referrer: Sym,
location: Location,
symbol: Sym,
}
impl ProjectError for MissingSymbol {
fn description(&self) -> &str {
"A name not referring to a known symbol was found in the source after \
macro execution. This can either mean that a symbol name was mistyped, or \
that macro execution didn't correctly halt."
}
fn message(&self, i: &Interner) -> String {
format!(
"The symbol {} referenced in {} does not exist",
i.extern_vec(self.symbol).join("::"),
i.extern_vec(self.referrer).join("::")
)
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.location.clone(), message: None })
}
}

72
src/facade/system.rs Normal file
View File

@@ -0,0 +1,72 @@
use hashbrown::HashMap;
use crate::error::{ErrorPosition, ProjectError};
use crate::interpreter::HandlerTable;
use crate::pipeline::file_loader::{IOResult, Loaded};
use crate::sourcefile::FileEntry;
use crate::utils::iter::box_empty;
use crate::utils::BoxedIter;
use crate::{ConstTree, Interner, Tok, VName};
/// A description of every point where an external library can hook into Orchid.
/// Intuitively, this can be thought of as a plugin
pub struct System<'a> {
/// An identifier for the system used eg. in error reporting.
pub name: Vec<String>,
/// External functions and other constant values defined in AST form
pub constants: HashMap<Tok<String>, ConstTree>,
/// Orchid libraries defined by this system
pub code: HashMap<Vec<Tok<String>>, Loaded>,
/// Prelude lines to be added to **subsequent** systems and usercode to
/// expose the functionality of this system. The prelude is not added during
/// the loading of this system
pub prelude: Vec<FileEntry>,
/// Handlers for actions defined in this system
pub handlers: HandlerTable<'a>,
}
impl<'a> System<'a> {
/// Intern the name of the system so that it can be used as an Orchid
/// namespace
pub fn vname(&self, i: &Interner) -> Vec<Tok<String>> {
self.name.iter().map(|s| i.i(s)).collect::<Vec<_>>()
}
/// Load a file from the system
pub fn load_file(&self, path: &[Tok<String>]) -> IOResult {
(self.code.get(path)).cloned().ok_or_else(|| {
let err =
MissingSystemCode { path: path.to_vec(), system: self.name.clone() };
err.rc()
})
}
}
/// An error raised when a system fails to load a path. This usually means that
/// another system the current one depends on did not get loaded
#[derive(Debug)]
pub struct MissingSystemCode {
path: VName,
system: Vec<String>,
}
impl ProjectError for MissingSystemCode {
fn description(&self) -> &str {
"A system tried to import a path that doesn't exist"
}
fn message(&self, i: &Interner) -> String {
format!(
"Path {} is not defined by {} or any system before it",
i.extern_all(&self.path).join("::"),
self.system.join("::")
)
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_empty()
}
}
/// Trait for objects that can be converted into a [System] in the presence
/// of an [Interner].
pub trait IntoSystem<'a>: 'a {
/// Convert this object into a system using an interner
fn into_system(self, i: &Interner) -> System<'a>;
}

View File

@@ -1,19 +1,12 @@
//! Interaction with foreign code
//!
//! Structures and traits used in the exposure of external functions and values
//! to Orchid code
use std::any::Any; use std::any::Any;
use std::error::Error; use std::fmt::Debug;
use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::rc::Rc;
use dyn_clone::DynClone; use dyn_clone::DynClone;
use crate::interpreted::ExprInst;
use crate::interpreter::{Context, RuntimeError}; use crate::interpreter::{Context, RuntimeError};
pub use crate::representations::interpreted::Clause; use crate::representations::interpreted::Clause;
use crate::representations::interpreted::ExprInst; use crate::Primitive;
use crate::representations::Primitive;
/// Information returned by [Atomic::run]. This mirrors /// Information returned by [Atomic::run]. This mirrors
/// [crate::interpreter::Return] but with a clause instead of an Expr. /// [crate::interpreter::Return] but with a clause instead of an Expr.
@@ -29,73 +22,12 @@ pub struct AtomicReturn {
impl AtomicReturn { impl AtomicReturn {
/// Wrap an inert atomic for delivery to the supervisor /// Wrap an inert atomic for delivery to the supervisor
pub fn from_data<D: Atomic>(d: D, c: Context) -> Self { pub fn from_data<D: Atomic>(d: D, c: Context) -> Self {
AtomicReturn { clause: d.to_atom_cls(), gas: c.gas, inert: false } AtomicReturn { clause: d.atom_cls(), gas: c.gas, inert: false }
} }
} }
/// A type-erased error in external code
pub type RcError = Rc<dyn ExternError>;
/// Returned by [Atomic::run] /// Returned by [Atomic::run]
pub type AtomicResult = Result<AtomicReturn, RuntimeError>; pub type AtomicResult = Result<AtomicReturn, RuntimeError>;
/// Returned by [ExternFn::apply]
pub type XfnResult = Result<Clause, RcError>;
/// Errors produced by external code
pub trait ExternError: Display {
/// Convert into trait object
fn into_extern(self) -> Rc<dyn ExternError>
where
Self: 'static + Sized,
{
Rc::new(self)
}
}
impl Debug for dyn ExternError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self}")
}
}
impl Error for dyn ExternError {}
/// Represents an externally defined function from the perspective of
/// the executor. Since Orchid lacks basic numerical operations,
/// these are also external functions.
pub trait ExternFn: DynClone {
/// Display name of the function
fn name(&self) -> &str;
/// Combine the function with an argument to produce a new clause
fn apply(&self, arg: ExprInst, ctx: Context) -> XfnResult;
/// Hash the name to get a somewhat unique hash.
fn hash(&self, mut state: &mut dyn std::hash::Hasher) {
self.name().hash(&mut state)
}
/// Wrap this function in a clause to be placed in an [AtomicResult].
fn to_xfn_cls(self) -> Clause
where
Self: Sized + 'static,
{
Clause::P(Primitive::ExternFn(Box::new(self)))
}
}
impl Eq for dyn ExternFn {}
impl PartialEq for dyn ExternFn {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
}
}
impl Hash for dyn ExternFn {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name().hash(state)
}
}
impl Debug for dyn ExternFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "##EXTERN[{}]##", self.name())
}
}
/// Functionality the interpreter needs to handle a value /// Functionality the interpreter needs to handle a value
pub trait Atomic: Any + Debug + DynClone pub trait Atomic: Any + Debug + DynClone
@@ -106,17 +38,27 @@ where
/// during introspection by other external code. There is no other way to /// during introspection by other external code. There is no other way to
/// interact with values of unknown types at the moment. /// interact with values of unknown types at the moment.
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
/// Attempt to normalize this value. If it wraps a value, this should report /// Attempt to normalize this value. If it wraps a value, this should report
/// inert. If it wraps a computation, it should execute one logical step of /// inert. If it wraps a computation, it should execute one logical step of
/// the computation and return a structure representing the ntext. /// the computation and return a structure representing the ntext.
fn run(&self, ctx: Context) -> AtomicResult; fn run(&self, ctx: Context) -> AtomicResult;
/// Wrap the atom in a clause to be placed in an [AtomicResult]. /// Wrap the atom in a clause to be placed in an [AtomicResult].
fn to_atom_cls(self) -> Clause fn atom_cls(self) -> Clause
where where
Self: Sized, Self: Sized,
{ {
Clause::P(Primitive::Atom(Atom(Box::new(self)))) Clause::P(Primitive::Atom(Atom(Box::new(self))))
} }
/// Wrap the atom in a new expression instance to be placed in a tree
fn atom_exi(self) -> ExprInst
where
Self: Sized,
{
self.atom_cls().wrap()
}
} }
/// Represents a black box unit of code with its own normalization steps. /// Represents a black box unit of code with its own normalization steps.

122
src/foreign/cps_box.rs Normal file
View File

@@ -0,0 +1,122 @@
//! Automated wrappers to make working with CPS commands easier.
use std::fmt::Debug;
use std::iter;
use trait_set::trait_set;
use super::{Atomic, AtomicResult, AtomicReturn, ExternFn, XfnResult};
use crate::interpreted::{Clause, ExprInst};
use crate::interpreter::{Context, HandlerRes};
use crate::{atomic_defaults, ConstTree};
trait_set! {
/// A "well behaved" type that can be used as payload in a CPS box
pub trait CPSPayload = Clone + Debug + 'static;
/// A function to handle a CPS box with a specific payload
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &ExprInst) -> HandlerRes;
}
/// The pre-argument version of CPSBox
#[derive(Debug, Clone)]
struct CPSFn<T: CPSPayload> {
pub argc: usize,
pub continuations: Vec<ExprInst>,
pub payload: T,
}
impl<T: CPSPayload> CPSFn<T> {
fn new(argc: usize, payload: T) -> Self {
debug_assert!(
argc > 0,
"Null-ary CPS functions are invalid, use an Atom instead"
);
Self { argc, continuations: Vec::new(), payload }
}
}
impl<T: CPSPayload> ExternFn for CPSFn<T> {
fn name(&self) -> &str {
"CPS function without argument"
}
fn apply(&self, arg: ExprInst, _ctx: Context) -> XfnResult {
let payload = self.payload.clone();
let continuations = (self.continuations.iter())
.cloned()
.chain(iter::once(arg))
.collect::<Vec<_>>();
if self.argc == 1 {
Ok(CPSBox { payload, continuations }.atom_cls())
} else {
Ok(CPSFn { argc: self.argc - 1, payload, continuations }.xfn_cls())
}
}
}
/// An inert Orchid Atom value encapsulating a payload and a continuation point
#[derive(Debug, Clone)]
pub struct CPSBox<T: CPSPayload> {
/// Details about the command
pub payload: T,
/// Possible continuations, in the order they were provided
pub continuations: Vec<ExprInst>,
}
impl<T: CPSPayload> CPSBox<T> {
/// Assert that the command was instantiated with the correct number of
/// possible continuations. This is decided by the native bindings, not user
/// code, therefore this error may be uncovered by usercode but can never be
/// produced at will.
pub fn assert_count(&self, expect: usize) {
let real = self.continuations.len();
debug_assert!(
real == expect,
"Tried to read {expect} argument(s) but {real} were provided for {:?}",
self.payload
)
}
/// Unpack the wrapped command and the continuation
pub fn unpack1(&self) -> (&T, &ExprInst) {
self.assert_count(1);
(&self.payload, &self.continuations[0])
}
/// Unpack the wrapped command and 2 continuations (usually an async and a
/// sync)
pub fn unpack2(&self) -> (&T, &ExprInst, &ExprInst) {
self.assert_count(2);
(&self.payload, &self.continuations[0], &self.continuations[1])
}
/// Unpack the wrapped command and 3 continuations (usually an async success,
/// an async fail and a sync)
pub fn unpack3(&self) -> (&T, &ExprInst, &ExprInst, &ExprInst) {
self.assert_count(3);
(
&self.payload,
&self.continuations[0],
&self.continuations[1],
&self.continuations[2],
)
}
}
impl<T: CPSPayload> Atomic for CPSBox<T> {
atomic_defaults!();
fn run(&self, ctx: Context) -> AtomicResult {
Ok(AtomicReturn {
clause: self.clone().atom_cls(),
gas: ctx.gas,
inert: true,
})
}
}
/// Like [init_cps] but wrapped in a [ConstTree] for init-time usage
pub fn mk_const<T: CPSPayload>(argc: usize, payload: T) -> ConstTree {
ConstTree::xfn(CPSFn::new(argc, payload))
}
/// Construct a CPS function which takes an argument and then acts inert
/// so that command executors can receive it.
///
/// This function is meant to be used in an external function defined with
/// [crate::define_fn]. For usage in a [ConstTree], see [mk_const]
pub fn init_cps<T: CPSPayload>(argc: usize, payload: T) -> Clause {
CPSFn::new(argc, payload).xfn_cls()
}

71
src/foreign/extern_fn.rs Normal file
View File

@@ -0,0 +1,71 @@
use std::error::Error;
use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::rc::Rc;
use dyn_clone::DynClone;
use crate::interpreted::ExprInst;
use crate::interpreter::Context;
use crate::representations::interpreted::Clause;
use crate::Primitive;
/// Returned by [ExternFn::apply]
pub type XfnResult = Result<Clause, Rc<dyn ExternError>>;
/// Errors produced by external code
pub trait ExternError: Display {
/// Convert into trait object
fn into_extern(self) -> Rc<dyn ExternError>
where
Self: 'static + Sized,
{
Rc::new(self)
}
}
impl Debug for dyn ExternError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self}")
}
}
impl Error for dyn ExternError {}
/// Represents an externally defined function from the perspective of
/// the executor. Since Orchid lacks basic numerical operations,
/// these are also external functions.
pub trait ExternFn: DynClone {
/// Display name of the function
fn name(&self) -> &str;
/// Combine the function with an argument to produce a new clause
fn apply(&self, arg: ExprInst, ctx: Context) -> XfnResult;
/// Hash the name to get a somewhat unique hash.
fn hash(&self, mut state: &mut dyn std::hash::Hasher) {
self.name().hash(&mut state)
}
/// Wrap this function in a clause to be placed in an [AtomicResult].
fn xfn_cls(self) -> Clause
where
Self: Sized + 'static,
{
Clause::P(Primitive::ExternFn(Box::new(self)))
}
}
impl Eq for dyn ExternFn {}
impl PartialEq for dyn ExternFn {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
}
}
impl Hash for dyn ExternFn {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name().hash(state)
}
}
impl Debug for dyn ExternFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "##EXTERN[{}]##", self.name())
}
}

17
src/foreign/mod.rs Normal file
View File

@@ -0,0 +1,17 @@
//! Interaction with foreign code
//!
//! Structures and traits used in the exposure of external functions and values
//! to Orchid code
mod atom;
pub mod cps_box;
mod extern_fn;
use std::rc::Rc;
pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn};
pub use extern_fn::{ExternError, ExternFn, XfnResult};
pub use crate::representations::interpreted::Clause;
/// A type-erased error in external code
pub type RcError = Rc<dyn ExternError>;

View File

@@ -74,7 +74,7 @@ macro_rules! atomic_impl {
($typ:ident) => { ($typ:ident) => {
$crate::atomic_impl! {$typ, |this: &Self, _: $crate::interpreter::Context| { $crate::atomic_impl! {$typ, |this: &Self, _: $crate::interpreter::Context| {
use $crate::foreign::ExternFn; use $crate::foreign::ExternFn;
Ok(this.clone().to_xfn_cls()) Ok(this.clone().xfn_cls())
}} }}
}; };
($typ:ident, $next_phase:expr) => { ($typ:ident, $next_phase:expr) => {
@@ -108,7 +108,7 @@ macro_rules! atomic_impl {
Err(e) => return Err($crate::interpreter::RuntimeError::Extern(e)), Err(e) => return Err($crate::interpreter::RuntimeError::Extern(e)),
} }
} else { } else {
next_self.to_atom_cls() next_self.atom_cls()
}; };
// package and return // package and return
Ok($crate::foreign::AtomicReturn { clause, gas, inert: false }) Ok($crate::foreign::AtomicReturn { clause, gas, inert: false })

View File

@@ -14,7 +14,7 @@ use crate::foreign::Atomic;
/// on [Any], [Debug] and [DynClone]. /// on [Any], [Debug] and [DynClone].
#[macro_export] #[macro_export]
macro_rules! atomic_inert { macro_rules! atomic_inert {
($typ:ident) => { ($typ:ident, $typename:expr) => {
impl $crate::foreign::Atomic for $typ { impl $crate::foreign::Atomic for $typ {
$crate::atomic_defaults! {} $crate::atomic_defaults! {}
@@ -23,11 +23,25 @@ macro_rules! atomic_inert {
ctx: $crate::interpreter::Context, ctx: $crate::interpreter::Context,
) -> $crate::foreign::AtomicResult { ) -> $crate::foreign::AtomicResult {
Ok($crate::foreign::AtomicReturn { Ok($crate::foreign::AtomicReturn {
clause: self.clone().to_atom_cls(), clause: self.clone().atom_cls(),
gas: ctx.gas, gas: ctx.gas,
inert: true, inert: true,
}) })
} }
} }
impl TryFrom<&ExprInst> for $typ {
type Error = std::rc::Rc<dyn $crate::foreign::ExternError>;
fn try_from(
value: &$crate::interpreted::ExprInst,
) -> Result<Self, Self::Error> {
$crate::systems::cast_exprinst::with_atom(
value,
$typename,
|a: &$typ| Ok(a.clone()),
)
}
}
}; };
} }

View File

@@ -74,7 +74,8 @@ use crate::write_fn_step;
#[macro_export] #[macro_export]
macro_rules! define_fn { macro_rules! define_fn {
// Unary function entry // Unary function entry
($( #[ $attr:meta ] )* $qual:vis $name:ident = $body:expr) => {paste::paste!{ ($( #[ $attr:meta ] )* $qual:vis $name:ident = |$x:ident| $body:expr) => {
paste::paste!{
$crate::write_fn_step!( $crate::write_fn_step!(
$( #[ $attr ] )* $qual $name $( #[ $attr ] )* $qual $name
> >
@@ -85,14 +86,15 @@ macro_rules! define_fn {
{} {}
out = expr => Ok(expr); out = expr => Ok(expr);
{ {
let lambda = $body; let lambda = |$x: &$crate::interpreted::ExprInst| $body;
lambda(out) lambda(out)
} }
); );
}}; }
};
// xname is optional only if every conversion is implicit // xname is optional only if every conversion is implicit
($( #[ $attr:meta ] )* $qual:vis $name:ident { ($( #[ $attr:meta ] )* $qual:vis $name:ident {
$( $arg:ident: $typ:ty ),+ $( $arg:ident: $typ:ty ),+ $(,)?
} => $body:expr) => { } => $body:expr) => {
$crate::define_fn!{expr=expr in $crate::define_fn!{expr=expr in
$( #[ $attr ] )* $qual $name { $( #[ $attr ] )* $qual $name {
@@ -105,7 +107,7 @@ macro_rules! define_fn {
$( #[ $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

View File

@@ -8,6 +8,7 @@ use hashbrown::HashMap;
use super::monotype::TypedInterner; use super::monotype::TypedInterner;
use super::token::Tok; use super::token::Tok;
use super::InternedDisplay;
/// A collection of interners based on their type. Allows to intern any object /// A collection of interners based on their type. Allows to intern any object
/// that implements [ToOwned]. Objects of the same type are stored together in a /// that implements [ToOwned]. Objects of the same type are stored together in a
@@ -59,6 +60,29 @@ impl Interner {
) -> Vec<T> { ) -> Vec<T> {
s.iter().map(|t| self.r(*t)).cloned().collect() s.iter().map(|t| self.r(*t)).cloned().collect()
} }
/// A variant of `unwrap` using [InternedDisplay] to circumvent `unwrap`'s
/// dependencyon [Debug]. For clarity, [expect] should be preferred.
pub fn unwrap<T, E: InternedDisplay>(&self, result: Result<T, E>) -> T {
result.unwrap_or_else(|e| {
println!("Unwrapped Error: {}", e.bundle(self));
panic!("Unwrapped an error");
})
}
/// A variant of `expect` using [InternedDisplay] to circumvent `expect`'s
/// depeendency on [Debug].
pub fn expect<T, E: InternedDisplay>(
&self,
result: Result<T, E>,
msg: &str,
) -> T {
result.unwrap_or_else(|e| {
println!("Expectation failed: {msg}");
println!("Error: {}", e.bundle(self));
panic!("Expected an error");
})
}
} }
impl Default for Interner { impl Default for Interner {

View File

@@ -1,5 +1,6 @@
use core::fmt::Formatter; use core::fmt::{self, Display, Formatter};
use std::fmt::Display; use core::ops::Deref;
use std::rc::Rc;
use crate::interner::Interner; use crate::interner::Interner;
@@ -29,16 +30,13 @@ pub trait InternedDisplay {
} }
} }
impl<T> InternedDisplay for T // Special loophole for Rc<dyn ProjectError>
impl<T: ?Sized> InternedDisplay for Rc<T>
where where
T: Display, T: InternedDisplay,
{ {
fn fmt_i( fn fmt_i(&self, f: &mut Formatter<'_>, i: &Interner) -> fmt::Result {
&self, self.deref().fmt_i(f, i)
f: &mut std::fmt::Formatter<'_>,
_i: &Interner,
) -> std::fmt::Result {
<Self as Display>::fmt(self, f)
} }
} }

View File

@@ -15,7 +15,7 @@ fn map_at<E>(
mapper: &mut impl FnMut(&Clause) -> Result<Clause, E>, mapper: &mut impl FnMut(&Clause) -> Result<Clause, E>,
) -> Result<ExprInst, E> { ) -> Result<ExprInst, E> {
source source
.try_update(|value| { .try_update(|value, _loc| {
// Pass right through lambdas // Pass right through lambdas
if let Clause::Lambda { args, body } = value { if let Clause::Lambda { args, body } = value {
return Ok(( return Ok((
@@ -87,7 +87,7 @@ pub fn apply(
x: ExprInst, x: ExprInst,
ctx: Context, ctx: Context,
) -> Result<Return, RuntimeError> { ) -> Result<Return, RuntimeError> {
let (state, (gas, inert)) = f.try_update(|clause| match clause { let (state, (gas, inert)) = f.try_update(|clause, loc| match clause {
// apply an ExternFn or an internal function // apply an ExternFn or an internal function
Clause::P(Primitive::ExternFn(f)) => { Clause::P(Primitive::ExternFn(f)) => {
let clause = let clause =
@@ -104,16 +104,11 @@ pub fn apply(
} else { } else {
(body.expr().clause.clone(), (ctx.gas, false)) (body.expr().clause.clone(), (ctx.gas, false))
}), }),
Clause::Constant(name) => { Clause::Constant(name) =>
let symval = if let Some(sym) = ctx.symbols.get(name) { if let Some(sym) = ctx.symbols.get(name) {
sym.clone() Ok((Clause::Apply { f: sym.clone(), x }, (ctx.gas, false)))
} else { } else {
panic!( Err(RuntimeError::MissingSymbol(*name, loc.clone()))
"missing symbol for function {}",
ctx.interner.extern_vec(*name).join("::")
)
};
Ok((Clause::Apply { f: symval, x }, (ctx.gas, false)))
}, },
Clause::P(Primitive::Atom(atom)) => { Clause::P(Primitive::Atom(atom)) => {
// take a step in expanding atom // take a step in expanding atom

View File

@@ -1,8 +1,9 @@
use std::fmt::Display;
use std::rc::Rc; use std::rc::Rc;
use crate::foreign::ExternError; use crate::foreign::ExternError;
use crate::interner::InternedDisplay;
use crate::representations::interpreted::ExprInst; use crate::representations::interpreted::ExprInst;
use crate::{Location, Sym};
/// Problems in the process of execution /// Problems in the process of execution
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -11,6 +12,8 @@ pub enum RuntimeError {
Extern(Rc<dyn ExternError>), Extern(Rc<dyn ExternError>),
/// Primitive applied as function /// Primitive applied as function
NonFunctionApplication(ExprInst), NonFunctionApplication(ExprInst),
/// Symbol not in context
MissingSymbol(Sym, Location),
} }
impl From<Rc<dyn ExternError>> for RuntimeError { impl From<Rc<dyn ExternError>> for RuntimeError {
@@ -19,12 +22,23 @@ impl From<Rc<dyn ExternError>> for RuntimeError {
} }
} }
impl Display for RuntimeError { impl InternedDisplay for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt_i(
&self,
f: &mut std::fmt::Formatter<'_>,
i: &crate::Interner,
) -> std::fmt::Result {
match self { match self {
Self::Extern(e) => write!(f, "Error in external function: {e}"), Self::Extern(e) => write!(f, "Error in external function: {e}"),
Self::NonFunctionApplication(loc) => { Self::NonFunctionApplication(expr) => {
write!(f, "Primitive applied as function at {loc:?}") write!(f, "Primitive applied as function at {}", expr.expr().location)
},
Self::MissingSymbol(sym, loc) => {
write!(
f,
"{}, called at {loc} is not loaded",
i.extern_vec(*sym).join("::")
)
}, },
} }
} }

View File

@@ -0,0 +1,84 @@
use std::any::{Any, TypeId};
use std::mem;
use std::rc::Rc;
use hashbrown::HashMap;
use trait_set::trait_set;
use super::{run, Context, Return, RuntimeError};
use crate::foreign::ExternError;
use crate::interpreted::{Clause, ExprInst};
use crate::utils::unwrap_or;
use crate::Primitive;
trait_set! {
trait Handler = for<'b> FnMut(&'b dyn Any) -> HandlerRes;
}
/// A table of command handlers
#[derive(Default)]
pub struct HandlerTable<'a> {
handlers: HashMap<TypeId, Box<dyn Handler + 'a>>,
}
impl<'a> HandlerTable<'a> {
/// Create a new [HandlerTable]
pub fn new() -> Self {
Self { handlers: HashMap::new() }
}
/// Add a handler function to interpret a type of atom and decide what happens
/// next. This function can be impure.
pub fn register<T: 'static>(
&mut self,
mut f: impl for<'b> FnMut(&'b T) -> HandlerRes + 'a,
) {
let cb = move |a: &dyn Any| f(a.downcast_ref().expect("found by TypeId"));
let prev = self.handlers.insert(TypeId::of::<T>(), Box::new(cb));
assert!(prev.is_none(), "A handler for this type is already registered");
}
/// Find and execute the corresponding handler for this type
pub fn dispatch(&mut self, arg: &dyn Any) -> Option<HandlerRes> {
self.handlers.get_mut(&arg.type_id()).map(|f| f(arg))
}
/// Combine two non-overlapping handler sets
pub fn combine(mut self, other: Self) -> Self {
for (key, value) in other.handlers {
let prev = self.handlers.insert(key, value);
assert!(prev.is_none(), "Duplicate handlers")
}
self
}
}
/// Various possible outcomes of a [Handler] execution. Ok returns control to
/// the interpreter. The meaning of Err is decided by the value in it.
pub type HandlerRes = Result<ExprInst, Rc<dyn ExternError>>;
/// [run] orchid code, executing any commands it returns using the specified
/// [Handler]s.
pub fn run_handler(
mut expr: ExprInst,
handlers: &mut HandlerTable,
mut ctx: Context,
) -> Result<Return, RuntimeError> {
loop {
let ret = run(expr.clone(), ctx.clone())?;
if ret.gas == Some(0) {
return Ok(ret);
}
let state_ex = ret.state.expr();
let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause {
a
} else {
mem::drop(state_ex);
return Ok(ret);
};
expr = unwrap_or!(handlers.dispatch(a.0.as_any()); {
mem::drop(state_ex);
return Ok(ret)
})?;
ctx.gas = ret.gas;
}
}

View File

@@ -2,8 +2,10 @@
mod apply; mod apply;
mod context; mod context;
mod error; mod error;
mod handler;
mod run; mod run;
pub use context::{Context, Return}; pub use context::{Context, Return};
pub use error::RuntimeError; pub use error::RuntimeError;
pub use run::{run, run_handler, Handler, HandlerErr, HandlerParm, HandlerRes}; pub use handler::{run_handler, HandlerRes, HandlerTable};
pub use run::run;

View File

@@ -1,17 +1,14 @@
use std::mem;
use std::rc::Rc;
use super::apply::apply; use super::apply::apply;
use super::context::{Context, Return}; use super::context::{Context, Return};
use super::error::RuntimeError; use super::error::RuntimeError;
use crate::foreign::{Atom, Atomic, AtomicReturn, ExternError}; use crate::foreign::AtomicReturn;
use crate::representations::interpreted::{Clause, ExprInst}; use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::Primitive; use crate::representations::Primitive;
/// Normalize an expression using beta reduction with memoization /// Normalize an expression using beta reduction with memoization
pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> { pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
let (state, (gas, inert)) = let (state, (gas, inert)) =
expr.try_normalize(|cls| -> Result<(Clause, _), RuntimeError> { expr.try_normalize(|cls, loc| -> Result<(Clause, _), RuntimeError> {
let mut i = cls.clone(); let mut i = cls.clone();
while ctx.gas.map(|g| g > 0).unwrap_or(true) { while ctx.gas.map(|g| g > 0).unwrap_or(true) {
match &i { match &i {
@@ -33,7 +30,8 @@ pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
i = clause.clone(); i = clause.clone();
}, },
Clause::Constant(c) => { Clause::Constant(c) => {
let symval = ctx.symbols.get(c).expect("missing symbol for value"); let symval = (ctx.symbols.get(c))
.ok_or_else(|| RuntimeError::MissingSymbol(*c, loc.clone()))?;
ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup
i = symval.expr().clause.clone(); i = symval.expr().clause.clone();
}, },
@@ -46,111 +44,3 @@ pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
})?; })?;
Ok(Return { state, gas, inert }) Ok(Return { state, gas, inert })
} }
/// Opaque inert data that may encode a command to a [Handler]
pub type HandlerParm = Box<dyn Atomic>;
/// Reasons why a [Handler] could not interpret a command. Convertible from
/// either variant
pub enum HandlerErr {
/// The command was addressed to us but its execution resulted in an error
Extern(Rc<dyn ExternError>),
/// This handler is not applicable, either because the [HandlerParm] is not a
/// command or because it's meant for some other handler
NA(HandlerParm),
}
impl From<Rc<dyn ExternError>> for HandlerErr {
fn from(value: Rc<dyn ExternError>) -> Self {
Self::Extern(value)
}
}
impl<T> From<T> for HandlerErr
where
T: ExternError + 'static,
{
fn from(value: T) -> Self {
Self::Extern(value.into_extern())
}
}
impl From<HandlerParm> for HandlerErr {
fn from(value: HandlerParm) -> Self {
Self::NA(value)
}
}
/// Various possible outcomes of a [Handler] execution. Ok returns control to
/// the interpreter. The meaning of Err is decided by the value in it.
pub type HandlerRes = Result<ExprInst, HandlerErr>;
/// A trait for things that may be able to handle commands returned by Orchid
/// code. This trait is implemented for `FnMut(HandlerParm) -> HandlerRes` and
/// `(Handler, Handler)`, users are not supposed to implement it themselves.
///
/// A handler receives an arbitrary inert [Atomic] and uses [Atomic::as_any]
/// then downcast_ref of [std::any::Any] to obtain a known type. If this fails,
/// it returns the box in [HandlerErr::NA] which will be passed to the next
/// handler.
pub trait Handler {
/// Attempt to resolve a command with this handler.
fn resolve(&mut self, data: HandlerParm) -> HandlerRes;
/// If this handler isn't applicable, try the other one.
fn or<T: Handler>(self, t: T) -> (Self, T)
where
Self: Sized,
{
(self, t)
}
}
impl<F> Handler for F
where
F: FnMut(HandlerParm) -> HandlerRes,
{
fn resolve(&mut self, data: HandlerParm) -> HandlerRes {
self(data)
}
}
impl<T: Handler, U: Handler> Handler for (T, U) {
fn resolve(&mut self, data: HandlerParm) -> HandlerRes {
match self.0.resolve(data) {
Err(HandlerErr::NA(data)) => self.1.resolve(data),
x => x,
}
}
}
/// [run] orchid code, executing any commands it returns using the specified
/// [Handler]s.
pub fn run_handler(
mut expr: ExprInst,
mut handler: impl Handler,
mut ctx: Context,
) -> Result<Return, RuntimeError> {
loop {
let ret = run(expr.clone(), ctx.clone())?;
if ret.gas == Some(0) {
return Ok(ret);
}
let state_ex = ret.state.expr();
let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause {
a
} else {
mem::drop(state_ex);
return Ok(ret);
};
let boxed = a.clone().0;
expr = match handler.resolve(boxed) {
Ok(expr) => expr,
Err(HandlerErr::Extern(ext)) => Err(ext)?,
Err(HandlerErr::NA(atomic)) =>
return Ok(Return {
gas: ret.gas,
inert: ret.inert,
state: Clause::P(Primitive::Atom(Atom(atomic))).wrap(),
}),
};
ctx.gas = ret.gas;
}
}

View File

@@ -1,4 +1,4 @@
#![deny(missing_docs)] #![warn(missing_docs)]
#![doc( #![doc(
html_logo_url = "https://raw.githubusercontent.com/lbfalvy/orchid/master/icon.svg" html_logo_url = "https://raw.githubusercontent.com/lbfalvy/orchid/master/icon.svg"
)] )]
@@ -7,6 +7,8 @@
)] )]
//! Orchid is a lazy, pure scripting language to be embedded in Rust //! Orchid is a lazy, pure scripting language to be embedded in Rust
//! applications. Check out the repo for examples and other links. //! applications. Check out the repo for examples and other links.
pub mod error;
pub mod facade;
pub mod foreign; pub mod foreign;
mod foreign_macros; mod foreign_macros;
pub mod interner; pub mod interner;
@@ -15,7 +17,7 @@ mod parse;
pub mod pipeline; pub mod pipeline;
mod representations; mod representations;
pub mod rule; pub mod rule;
pub mod stl; pub mod systems;
mod utils; mod utils;
pub use interner::{Interner, Tok}; pub use interner::{Interner, Tok};
@@ -32,4 +34,4 @@ pub use representations::{
ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal, ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal,
Location, PathSet, Primitive, Location, PathSet, Primitive,
}; };
pub use utils::{Side, Substack}; pub use utils::{Side, Substack, ThreadPool};

View File

@@ -1,7 +1,6 @@
use std::hash::Hash; use std::hash::Hash;
use chumsky::prelude::Simple; use chumsky::prelude::Simple;
use chumsky::recursive::Recursive;
use chumsky::{BoxedParser, Parser}; use chumsky::{BoxedParser, Parser};
use trait_set::trait_set; use trait_set::trait_set;
@@ -12,6 +11,3 @@ trait_set! {
} }
/// Boxed version of [SimpleParser] /// Boxed version of [SimpleParser]
pub type BoxedSimpleParser<'a, I, O> = BoxedParser<'a, I, O, Simple<I>>; pub type BoxedSimpleParser<'a, I, O> = BoxedParser<'a, I, O, Simple<I>>;
/// [Recursive] specialization of [SimpleParser] to parameterize calls to
/// [chumsky::recursive::recursive]
pub type SimpleRecursive<'a, I, O> = Recursive<'a, I, O, Simple<I>>;

View File

@@ -1,51 +0,0 @@
/// Produces filter_mapping functions for enum types:
/// ```rs
/// enum_parser!(Foo::Bar | "Some error!")
/// // Foo::Bar(T) into T
/// enum_parser!(Foo::Bar)
/// // same as above but with the default error "Expected Foo::Bar"
/// enum_parser!(Foo >> Quz; Bar, Baz)
/// // Foo::Bar(T) into Quz::Bar(T)
/// // Foo::Baz(U) into Quz::Baz(U)
/// ```
macro_rules! enum_filter {
($p:path | $m:tt) => {
{
|l| {
if let $p(x) = l { Ok(x) }
else { Err($m) }
}
}
};
($p:path >> $q:path; $i:ident | $m:tt) => {
{
use $p as srcpath;
use $q as tgtpath;
let base = enum_filter!(srcpath::$i | $m);
move |l| base(l).map(tgtpath::$i)
}
};
($p:path >> $q:path; $i:ident) => {
enum_filter!($p >> $q; $i | {concat!("Expected ", stringify!($i))})
};
($p:path >> $q:path; $($i:ident),+ | $m:tt) => {
{
use $p as srcpath;
use $q as tgtpath;
|l| match l {
$( srcpath::$i(x) => Ok(tgtpath::$i(x)), )+
_ => Err($m)
}
}
};
($p:path >> $q:path; $($i:ident),+) => {
enum_filter!($p >> $q; $($i),+ | {
concat!("Expected one of ", $(stringify!($i), " "),+)
})
};
($p:path) => {
enum_filter!($p | {concat!("Expected ", stringify!($p))})
};
}
pub(crate) use enum_filter;

241
src/parse/errors.rs Normal file
View File

@@ -0,0 +1,241 @@
use std::rc::Rc;
use chumsky::prelude::Simple;
use itertools::Itertools;
use super::{Entry, Lexeme};
use crate::error::{ErrorPosition, ProjectError};
use crate::interner::InternedDisplay;
use crate::utils::iter::box_once;
use crate::utils::BoxedIter;
use crate::{Interner, Location, Tok};
#[derive(Debug)]
pub struct LineNeedsPrefix {
pub entry: Entry,
}
impl ProjectError for LineNeedsPrefix {
fn description(&self) -> &str {
"This linetype requires a prefix"
}
fn message(&self, i: &Interner) -> String {
format!("{} cannot appear at the beginning of a line", self.entry.bundle(i))
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { message: None, location: self.entry.location() })
}
}
#[derive(Debug)]
pub struct UnexpectedEOL {
/// Last entry before EOL
pub entry: Entry,
}
impl ProjectError for UnexpectedEOL {
fn description(&self) -> &str {
"The line ended abruptly"
}
fn message(&self, _i: &Interner) -> String {
"The line ends unexpectedly here. In Orchid, all line breaks outside \
parentheses start a new declaration"
.to_string()
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { message: None, location: self.entry.location() })
}
}
pub struct ExpectedEOL {
pub location: Location,
}
impl ProjectError for ExpectedEOL {
fn description(&self) -> &str {
"Expected the end of the line"
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.location.clone(), message: None })
}
}
#[derive(Debug)]
pub struct ExpectedName {
pub entry: Entry,
}
impl ExpectedName {
pub fn expect(entry: &Entry) -> Result<Tok<String>, Rc<dyn ProjectError>> {
match entry.lexeme {
Lexeme::Name(n) => Ok(n),
_ => Err(Self { entry: entry.clone() }.rc()),
}
}
}
impl ProjectError for ExpectedName {
fn description(&self) -> &str {
"A name was expected here, but something else was found"
}
fn message(&self, i: &Interner) -> String {
if self.entry.is_keyword() {
format!(
"{} is a restricted keyword and cannot be used as a name",
self.entry.bundle(i)
)
} else {
format!("Expected a name, found {}", self.entry.bundle(i))
}
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.entry.location(), message: None })
}
}
#[derive()]
pub struct Expected {
pub expected: Vec<Lexeme>,
pub or_name: bool,
pub found: Entry,
}
impl Expected {
pub fn expect(l: Lexeme, e: &Entry) -> Result<(), Rc<dyn ProjectError>> {
if e.lexeme != l {
return Err(
Self { expected: vec![l], or_name: false, found: e.clone() }.rc(),
);
}
Ok(())
}
}
impl ProjectError for Expected {
fn description(&self) -> &str {
"A concrete token was expected but something else was found"
}
fn message(&self, i: &Interner) -> String {
let list = match &self.expected[..] {
&[] => return "Unsatisfiable expectation".to_string(),
[only] => only.to_string_i(i),
[a, b] => format!("either {} or {}", a.bundle(i), b.bundle(i)),
[variants @ .., last] => format!(
"any of {} or {}",
variants.iter().map(|l| l.to_string_i(i)).join(", "),
last.bundle(i)
),
};
let or_name = if self.or_name { " or a name" } else { "" };
format!("Expected {}{} but found {}", list, or_name, self.found.bundle(i))
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.found.location(), message: None })
}
}
pub struct ReservedToken {
pub entry: Entry,
}
impl ProjectError for ReservedToken {
fn description(&self) -> &str {
"A token reserved for future use was found in the code"
}
fn message(&self, i: &Interner) -> String {
format!("{} is a reserved token", self.entry.bundle(i))
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.entry.location(), message: None })
}
}
pub struct BadTokenInRegion {
pub entry: Entry,
pub region: &'static str,
}
impl ProjectError for BadTokenInRegion {
fn description(&self) -> &str {
"A token was found in a region where it should not appear"
}
fn message(&self, i: &Interner) -> String {
format!("{} cannot appear in {}", self.entry.bundle(i), self.region)
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.entry.location(), message: None })
}
}
pub struct NotFound {
pub expected: &'static str,
pub location: Location,
}
impl ProjectError for NotFound {
fn description(&self) -> &str {
"A specific lexeme was expected but not found in the given range"
}
fn message(&self, _i: &Interner) -> String {
format!("{} was expected", self.expected)
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.location.clone(), message: None })
}
}
pub struct LeadingNS {
pub location: Location,
}
impl ProjectError for LeadingNS {
fn description(&self) -> &str {
":: can only follow a name token"
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.location.clone(), message: None })
}
}
pub struct MisalignedParen {
pub entry: Entry,
}
impl ProjectError for MisalignedParen {
fn description(&self) -> &str {
"Parentheses (), [] and {} must always pair up"
}
fn message(&self, i: &Interner) -> String {
format!("This {} has no pair", self.entry.bundle(i))
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.entry.location(), message: None })
}
}
pub struct NamespacedExport {
pub location: Location,
}
impl ProjectError for NamespacedExport {
fn description(&self) -> &str {
"Exports can only refer to unnamespaced names in the local namespace"
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.location.clone(), message: None })
}
}
pub struct LexError {
pub errors: Vec<Simple<char>>,
pub file: Rc<Vec<String>>,
}
impl ProjectError for LexError {
fn description(&self) -> &str {
"An error occured during tokenization"
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
let file = self.file.clone();
Box::new(self.errors.iter().map(move |s| ErrorPosition {
location: Location::Range { file: file.clone(), range: s.span() },
message: Some(format!("{}", s)),
}))
}
}

View File

@@ -1,107 +0,0 @@
use std::ops::Range;
use std::rc::Rc;
use chumsky::prelude::*;
use chumsky::{self, Parser};
use super::context::Context;
use super::decls::SimpleParser;
use super::enum_filter::enum_filter;
use super::lexer::{filter_map_lex, Entry, Lexeme};
use crate::representations::ast::{Clause, Expr};
use crate::representations::location::Location;
use crate::representations::{Primitive, VName};
/// Parses any number of expr wrapped in (), [] or {}
fn sexpr_parser(
expr: impl SimpleParser<Entry, Expr<VName>> + Clone,
) -> impl SimpleParser<Entry, (Clause<VName>, Range<usize>)> + Clone {
let body = expr.repeated();
choice((
Lexeme::LP('(').parser().then(body.clone()).then(Lexeme::RP('(').parser()),
Lexeme::LP('[').parser().then(body.clone()).then(Lexeme::RP('[').parser()),
Lexeme::LP('{').parser().then(body).then(Lexeme::RP('{').parser()),
))
.map(|((lp, body), rp)| {
let Entry { lexeme, range: Range { start, .. } } = lp;
let end = rp.range.end;
let char = if let Lexeme::LP(c) = lexeme {
c
} else {
unreachable!("The parser only matches Lexeme::LP")
};
(Clause::S(char, Rc::new(body)), start..end)
})
.labelled("S-expression")
}
/// Parses `\name.body` or `\name:type.body` where name is any valid name
/// and type and body are both expressions. Comments are allowed
/// and ignored everywhere in between the tokens
fn lambda_parser<'a>(
expr: impl SimpleParser<Entry, Expr<VName>> + Clone + 'a,
ctx: impl Context + 'a,
) -> impl SimpleParser<Entry, (Clause<VName>, Range<usize>)> + Clone + 'a {
Lexeme::BS
.parser()
.ignore_then(expr.clone())
.then_ignore(Lexeme::Name(ctx.interner().i(".")).parser())
.then(expr.repeated().at_least(1))
.map_with_span(move |(arg, body), span| {
(Clause::Lambda(Rc::new(arg), Rc::new(body)), span)
})
.labelled("Lambda")
}
/// Parses a sequence of names separated by :: <br/>
/// Comments and line breaks are allowed and ignored in between
pub fn ns_name_parser<'a>()
-> impl SimpleParser<Entry, (VName, Range<usize>)> + Clone + 'a {
filter_map_lex(enum_filter!(Lexeme::Name))
.separated_by(Lexeme::NS.parser())
.at_least(1)
.map(move |elements| {
let start = elements.first().expect("can never be empty").1.start;
let end = elements.last().expect("can never be empty").1.end;
let tokens = (elements.iter().map(|(t, _)| *t)).collect::<Vec<_>>();
(tokens, start..end)
})
.labelled("Namespaced name")
}
pub fn namelike_parser<'a>()
-> impl SimpleParser<Entry, (Clause<VName>, Range<usize>)> + Clone + 'a {
choice((
filter_map_lex(enum_filter!(Lexeme::PH))
.map(|(ph, range)| (Clause::Placeh(ph), range)),
ns_name_parser().map(|(token, range)| (Clause::Name(token), range)),
))
}
pub fn clause_parser<'a>(
expr: impl SimpleParser<Entry, Expr<VName>> + Clone + 'a,
ctx: impl Context + 'a,
) -> impl SimpleParser<Entry, (Clause<VName>, Range<usize>)> + Clone + 'a {
choice((
filter_map_lex(enum_filter!(Lexeme >> Primitive; Literal))
.map(|(p, s)| (Clause::P(p), s))
.labelled("Literal"),
sexpr_parser(expr.clone()),
lambda_parser(expr, ctx),
namelike_parser(),
))
.labelled("Clause")
}
/// Parse an expression
pub fn xpr_parser<'a>(
ctx: impl Context + 'a,
) -> impl SimpleParser<Entry, Expr<VName>> + 'a {
recursive(move |expr| {
clause_parser(expr, ctx.clone()).map(move |(value, range)| Expr {
value,
location: Location::Range { file: ctx.file(), range },
})
})
.labelled("Expression")
}

View File

@@ -1,59 +1,22 @@
use std::fmt::Debug;
use chumsky::prelude::*;
use chumsky::Parser; use chumsky::Parser;
use thiserror::Error;
use super::context::Context; use super::context::Context;
use super::{lexer, line_parser, Entry}; use super::errors::LexError;
use crate::parse::sourcefile::split_lines; use super::lexer;
use super::sourcefile::parse_module_body;
use super::stream::Stream;
use crate::error::{ParseErrorWithTokens, ProjectError, ProjectResult};
use crate::representations::sourcefile::FileEntry; use crate::representations::sourcefile::FileEntry;
#[derive(Error, Debug, Clone)] pub fn parse2(data: &str, ctx: impl Context) -> ProjectResult<Vec<FileEntry>> {
pub enum ParseError {
#[error("Could not tokenize {0:?}")]
Lex(Vec<Simple<char>>),
#[error(
"Could not parse {:?} on line {}",
.0.first().unwrap().1.span(),
.0.first().unwrap().0
)]
Ast(Vec<(usize, Simple<Entry>)>),
}
/// Parse a string of code into a collection of module elements;
/// imports, exports, comments, declarations, etc.
///
/// Notice that because the lexer splits operators based on the provided
/// list, the output will only be correct if operator list already
/// contains all operators defined or imported by this module.
pub fn parse(
data: &str,
ctx: impl Context,
) -> Result<Vec<FileEntry>, ParseError> {
// TODO: wrap `i`, `ops` and `prefix` in a parsing context
let lexie = lexer(ctx.clone()); let lexie = lexer(ctx.clone());
let token_batchv = lexie.parse(data).map_err(ParseError::Lex)?; let tokens = (lexie.parse(data))
let parsr = line_parser(ctx).then_ignore(end()); .map_err(|errors| LexError { errors, file: ctx.file() }.rc())?;
let (parsed_lines, errors_per_line) = split_lines(&token_batchv) if tokens.is_empty() {
.enumerate() Ok(Vec::new())
.map(|(i, entv)| {
(i, entv.iter().filter(|e| !e.is_filler()).cloned().collect::<Vec<_>>())
})
.filter(|(_, l)| !l.is_empty())
.map(|(i, l)| (i, parsr.parse(l)))
.map(|(i, res)| match res {
Ok(r) => (Some(r), (i, vec![])),
Err(e) => (None, (i, e)),
})
.unzip::<_, _, Vec<_>, Vec<_>>();
let total_err = errors_per_line
.into_iter()
.flat_map(|(i, v)| v.into_iter().map(move |e| (i, e)))
.collect::<Vec<_>>();
if !total_err.is_empty() {
Err(ParseError::Ast(total_err))
} else { } else {
Ok(parsed_lines.into_iter().map(Option::unwrap).collect()) parse_module_body(Stream::from_slice(&tokens), ctx).map_err(|error| {
ParseErrorWithTokens { error, full_source: data.to_string(), tokens }.rc()
})
} }
} }

View File

@@ -1,93 +0,0 @@
use chumsky::prelude::*;
use chumsky::Parser;
use itertools::Itertools;
use super::context::Context;
use super::decls::{SimpleParser, SimpleRecursive};
use super::enum_filter::enum_filter;
use super::lexer::{filter_map_lex, Lexeme};
use super::Entry;
use crate::interner::Tok;
use crate::representations::sourcefile::Import;
use crate::utils::iter::{
box_chain, box_flatten, box_once, into_boxed_iter, BoxedIterIter,
};
/// initialize an iterator of iterator with a single element.
fn init_table(name: Tok<String>) -> BoxedIterIter<'static, Tok<String>> {
box_once(box_once(name))
}
/// Parse an import command
/// Syntax is same as Rust's `use` except the verb is import, no trailing
/// semi and the delimiters are plain parentheses. Namespaces should
/// preferably contain crossplatform filename-legal characters but the
/// symbols are explicitly allowed to go wild.
/// There's a blacklist in [crate::parse::name::NOT_NAME_CHAR]
pub fn import_parser<'a>(
ctx: impl Context + 'a,
) -> impl SimpleParser<Entry, Vec<Import>> + 'a {
// TODO: this algorithm isn't cache friendly and copies a lot
recursive({
let ctx = ctx.clone();
move |expr: SimpleRecursive<Entry, BoxedIterIter<Tok<String>>>| {
filter_map_lex(enum_filter!(Lexeme::Name))
.map(|(t, _)| t)
.separated_by(Lexeme::NS.parser())
.then(
Lexeme::NS
.parser()
.ignore_then(choice((
expr
.clone()
.separated_by(Lexeme::Name(ctx.interner().i(",")).parser())
.delimited_by(
Lexeme::LP('(').parser(),
Lexeme::RP('(').parser(),
)
.map(|v| box_flatten(v.into_iter()))
.labelled("import group"),
// Each expr returns a list of imports, flatten into common list
Lexeme::Name(ctx.interner().i("*"))
.parser()
.map(move |_| init_table(ctx.interner().i("*")))
.labelled("wildcard import"), // Just a *, wrapped
filter_map_lex(enum_filter!(Lexeme::Name))
.map(|(t, _)| init_table(t))
.labelled("import terminal"), // Just a name, wrapped
)))
.or_not(),
)
.map(
|(name, opt_post): (
Vec<Tok<String>>,
Option<BoxedIterIter<Tok<String>>>,
)|
-> BoxedIterIter<Tok<String>> {
if let Some(post) = opt_post {
Box::new(
post.map(move |el| box_chain!(name.clone().into_iter(), el)),
)
} else {
box_once(into_boxed_iter(name))
}
},
)
}
})
.map(move |paths| {
paths
.filter_map(|namespaces| {
let mut path = namespaces.collect_vec();
let name = path.pop()?;
Some(Import {
path: ctx.interner().i(&path),
name: {
if name == ctx.interner().i("*") { None } else { Some(name) }
},
})
})
.collect()
})
.labelled("import")
}

View File

@@ -1,27 +1,53 @@
use std::fmt; use std::fmt;
use std::ops::Range; use std::ops::Range;
use std::rc::Rc;
use chumsky::prelude::*; use chumsky::prelude::*;
use chumsky::text::keyword; use chumsky::text::keyword;
use chumsky::{Parser, Span}; use chumsky::Parser;
use ordered_float::NotNan; use ordered_float::NotNan;
use super::context::Context; use super::context::Context;
use super::decls::SimpleParser; use super::decls::SimpleParser;
use super::number::print_nat16;
use super::{comment, name, number, placeholder, string}; use super::{comment, name, number, placeholder, string};
use crate::ast::{PHClass, Placeholder}; use crate::ast::{PHClass, Placeholder};
use crate::interner::{InternedDisplay, Interner, Tok}; use crate::interner::{InternedDisplay, Interner, Tok};
use crate::representations::Literal; use crate::representations::Literal;
use crate::Location;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Entry { pub struct Entry {
pub lexeme: Lexeme, pub lexeme: Lexeme,
pub range: Range<usize>, pub location: Location,
} }
impl Entry { impl Entry {
/// Checks if the lexeme is a comment or line break
pub fn is_filler(&self) -> bool { pub fn is_filler(&self) -> bool {
matches!(self.lexeme, Lexeme::Comment(_)) matches!(self.lexeme, Lexeme::Comment(_) | Lexeme::BR)
|| matches!(self.lexeme, Lexeme::BR) }
pub fn is_keyword(&self) -> bool {
matches!(
self.lexeme,
Lexeme::Const
| Lexeme::Export
| Lexeme::Import
| Lexeme::Macro
| Lexeme::Module
)
}
pub fn location(&self) -> Location {
self.location.clone()
}
pub fn range(&self) -> Range<usize> {
self.location.range().expect("An Entry can only have a known location")
}
pub fn file(&self) -> Rc<Vec<String>> {
self.location.file().expect("An Entry can only have a range location")
} }
} }
@@ -35,27 +61,33 @@ impl InternedDisplay for Entry {
} }
} }
impl From<Entry> for (Lexeme, Range<usize>) { // impl From<Entry> for (Lexeme, Range<usize>) {
fn from(ent: Entry) -> Self { // fn from(ent: Entry) -> Self {
(ent.lexeme, ent.range) // (ent.lexeme.clone(), ent.range())
} // }
} // }
impl Span for Entry { // impl Span for Entry {
type Context = Lexeme; // type Context = (Lexeme, Rc<Vec<String>>);
type Offset = usize; // type Offset = usize;
fn context(&self) -> Self::Context { // fn context(&self) -> Self::Context {
self.lexeme.clone() // (self.lexeme.clone(), self.file())
} // }
fn start(&self) -> Self::Offset { // fn start(&self) -> Self::Offset {
self.range.start() // self.range().start()
} // }
fn end(&self) -> Self::Offset { // fn end(&self) -> Self::Offset {
self.range.end() // self.range().end()
} // }
fn new(context: Self::Context, range: Range<Self::Offset>) -> Self { // fn new((lexeme, file): Self::Context, range: Range<Self::Offset>) -> Self {
Self { lexeme: context, range } // Self { lexeme, location: Location::Range { file, range } }
// }
// }
impl AsRef<Location> for Entry {
fn as_ref(&self) -> &Location {
&self.location
} }
} }
@@ -63,9 +95,9 @@ impl Span for Entry {
pub enum Lexeme { pub enum Lexeme {
Literal(Literal), Literal(Literal),
Name(Tok<String>), Name(Tok<String>),
Rule(NotNan<f64>), Arrow(NotNan<f64>),
/// Walrus operator (formerly shorthand macro) /// Walrus operator (formerly shorthand macro)
Const, Walrus,
/// Line break /// Line break
BR, BR,
/// Namespace separator /// Namespace separator
@@ -77,12 +109,15 @@ pub enum Lexeme {
/// Backslash /// Backslash
BS, BS,
At, At,
Dot,
Type, // type operator Type, // type operator
Comment(String), Comment(String),
Export, Export,
Import, Import,
Namespace, Module,
PH(Placeholder), Macro,
Const,
Placeh(Placeholder),
} }
impl InternedDisplay for Lexeme { impl InternedDisplay for Lexeme {
@@ -94,8 +129,8 @@ impl InternedDisplay for Lexeme {
match self { match self {
Self::Literal(l) => write!(f, "{:?}", l), Self::Literal(l) => write!(f, "{:?}", l),
Self::Name(token) => write!(f, "{}", i.r(*token)), Self::Name(token) => write!(f, "{}", i.r(*token)),
Self::Const => write!(f, ":="), Self::Walrus => write!(f, ":="),
Self::Rule(prio) => write!(f, "={}=>", prio), Self::Arrow(prio) => write!(f, "={}=>", print_nat16(*prio)),
Self::NS => write!(f, "::"), Self::NS => write!(f, "::"),
Self::LP(l) => write!(f, "{}", l), Self::LP(l) => write!(f, "{}", l),
Self::RP(l) => match l { Self::RP(l) => match l {
@@ -107,12 +142,15 @@ impl InternedDisplay for Lexeme {
Self::BR => writeln!(f), Self::BR => writeln!(f),
Self::BS => write!(f, "\\"), Self::BS => write!(f, "\\"),
Self::At => write!(f, "@"), Self::At => write!(f, "@"),
Self::Dot => write!(f, "."),
Self::Type => write!(f, ":"), Self::Type => write!(f, ":"),
Self::Comment(text) => write!(f, "--[{}]--", text), Self::Comment(text) => write!(f, "--[{}]--", text),
Self::Export => write!(f, "export"), Self::Export => write!(f, "export"),
Self::Import => write!(f, "import"), Self::Import => write!(f, "import"),
Self::Namespace => write!(f, "namespace"), Self::Module => write!(f, "module"),
Self::PH(Placeholder { name, class }) => match *class { Self::Const => write!(f, "const"),
Self::Macro => write!(f, "macro"),
Self::Placeh(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 { write!(f, "...") } else { write!(f, "..") }?; if nonzero { write!(f, "...") } else { write!(f, "..") }?;
@@ -129,7 +167,9 @@ impl InternedDisplay for Lexeme {
impl Lexeme { impl Lexeme {
pub fn rule(prio: impl Into<f64>) -> Self { pub fn rule(prio: impl Into<f64>) -> Self {
Lexeme::Rule(NotNan::new(prio.into()).expect("Rule priority cannot be NaN")) Lexeme::Arrow(
NotNan::new(prio.into()).expect("Rule priority cannot be NaN"),
)
} }
pub fn parser<E: chumsky::Error<Entry>>( pub fn parser<E: chumsky::Error<Entry>>(
@@ -179,38 +219,37 @@ pub fn lexer<'a>(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
choice(( choice((
keyword("export").to(Lexeme::Export), keyword("export").to(Lexeme::Export),
keyword("module").to(Lexeme::Namespace), keyword("module").to(Lexeme::Module),
keyword("import").to(Lexeme::Import), keyword("import").to(Lexeme::Import),
keyword("macro").to(Lexeme::Macro),
keyword("const").to(Lexeme::Const),
paren_parser('(', ')'), paren_parser('(', ')'),
paren_parser('[', ']'), paren_parser('[', ']'),
paren_parser('{', '}'), paren_parser('{', '}'),
just(":=").to(Lexeme::Const), just(":=").to(Lexeme::Walrus),
just("=") just("=")
.ignore_then(number::float_parser()) .ignore_then(number::float_parser())
.then_ignore(just("=>")) .then_ignore(just("=>"))
.map(Lexeme::rule), .map(Lexeme::rule),
comment::comment_parser().map(Lexeme::Comment), comment::comment_parser().map(Lexeme::Comment),
placeholder::placeholder_parser(ctx.clone()).map(Lexeme::Placeh),
just("::").to(Lexeme::NS), just("::").to(Lexeme::NS),
just('\\').to(Lexeme::BS), just('\\').to(Lexeme::BS),
just('@').to(Lexeme::At), just('@').to(Lexeme::At),
just(':').to(Lexeme::Type), just(':').to(Lexeme::Type),
just('\n').to(Lexeme::BR), just('\n').to(Lexeme::BR),
placeholder::placeholder_parser(ctx.clone()).map(Lexeme::PH), just('.').to(Lexeme::Dot),
literal_parser().map(Lexeme::Literal), literal_parser().map(Lexeme::Literal),
name::name_parser(&all_ops) name::name_parser(&all_ops).map({
.map(move |n| Lexeme::Name(ctx.interner().i(&n))), let ctx = ctx.clone();
move |n| Lexeme::Name(ctx.interner().i(&n))
}),
)) ))
.map_with_span(|lexeme, range| Entry { lexeme, range }) .map_with_span(move |lexeme, range| Entry {
lexeme,
location: Location::Range { range, file: ctx.file() },
})
.padded_by(one_of(" \t").repeated()) .padded_by(one_of(" \t").repeated())
.repeated() .repeated()
.then_ignore(end()) .then_ignore(end())
} }
pub fn filter_map_lex<'a, O, M: ToString>(
f: impl Fn(Lexeme) -> Result<O, M> + Clone + 'a,
) -> impl SimpleParser<Entry, (O, Range<usize>)> + Clone + 'a {
filter_map(move |s: Range<usize>, e: Entry| {
let out = f(e.lexeme).map_err(|msg| Simple::custom(s.clone(), msg))?;
Ok((out, s))
})
}

View File

@@ -1,20 +1,19 @@
mod comment; mod comment;
mod context; mod context;
mod decls; mod decls;
mod enum_filter; mod errors;
mod expression;
mod facade; mod facade;
mod import;
mod lexer; mod lexer;
mod multiname;
mod name; mod name;
mod number; mod number;
mod placeholder; mod placeholder;
mod sourcefile; mod sourcefile;
mod stream;
mod string; mod string;
pub use context::ParsingContext; pub use context::ParsingContext;
pub use facade::{parse, ParseError}; pub use facade::parse2;
pub use lexer::{lexer, Entry, Lexeme}; pub use lexer::{lexer, Entry, Lexeme};
pub use name::is_op; pub use name::is_op;
pub use number::{float_parser, int_parser}; pub use number::{float_parser, int_parser};
pub use sourcefile::line_parser;

88
src/parse/multiname.rs Normal file
View File

@@ -0,0 +1,88 @@
use std::collections::VecDeque;
use std::iter;
use super::context::Context;
use super::errors::{Expected, ExpectedName};
use super::stream::Stream;
use super::Lexeme;
use crate::error::{ProjectError, ProjectResult};
use crate::utils::iter::{box_chain, box_once, BoxedIterIter};
use crate::Tok;
fn parse_multiname_branch(
cursor: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<(BoxedIterIter<Tok<String>>, Stream<'_>)> {
let comma = ctx.interner().i(",");
let (subnames, cursor) = parse_multiname_rec(cursor, ctx.clone())?;
let (delim, cursor) = cursor.trim().pop()?;
match delim.lexeme {
Lexeme::Name(n) if n == comma => {
let (tail, cont) = parse_multiname_branch(cursor, ctx)?;
Ok((box_chain!(subnames, tail), cont))
},
Lexeme::RP('(') => Ok((subnames, cursor)),
_ => Err(
Expected {
expected: vec![Lexeme::Name(comma), Lexeme::RP('(')],
or_name: false,
found: delim.clone(),
}
.rc(),
),
}
}
pub fn parse_multiname_rec(
curosr: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<(BoxedIterIter<Tok<String>>, Stream<'_>)> {
let comma = ctx.interner().i(",");
let (head, cursor) = curosr.trim().pop()?;
match &head.lexeme {
Lexeme::LP('(') => parse_multiname_branch(cursor, ctx),
Lexeme::LP('[') => {
let (op_ent, cursor) = cursor.trim().pop()?;
let op = ExpectedName::expect(op_ent)?;
let (rp_ent, cursor) = cursor.trim().pop()?;
Expected::expect(Lexeme::RP('['), rp_ent)?;
Ok((box_once(box_once(op)), cursor))
},
Lexeme::Name(n) if *n != comma => {
let cursor = cursor.trim();
if cursor.get(0).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) {
let cursor = cursor.step()?;
let (out, cursor) = parse_multiname_rec(cursor, ctx)?;
let out = Box::new(out.map(|i| box_chain!(i, iter::once(*n))));
Ok((out, cursor))
} else {
Ok((box_once(box_once(*n)), cursor))
}
},
_ => Err(
Expected {
expected: vec![Lexeme::LP('(')],
or_name: true,
found: head.clone(),
}
.rc(),
),
}
}
pub fn parse_multiname(
cursor: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<(Vec<Vec<Tok<String>>>, Stream<'_>)> {
let (output, cont) = parse_multiname_rec(cursor, ctx)?;
let output = output
.map(|it| {
let mut deque = VecDeque::with_capacity(it.size_hint().0);
for item in it {
deque.push_front(item)
}
deque.into()
})
.collect();
Ok((output, cont))
}

View File

@@ -123,3 +123,9 @@ pub fn float_parser() -> impl SimpleParser<char, NotNan<f64>> {
)) ))
.labelled("float") .labelled("float")
} }
pub fn print_nat16(num: NotNan<f64>) -> String {
let exp = num.log(16.0).floor();
let man = num / 16_f64.powf(exp);
format!("{man}p{exp:.0}")
}

View File

@@ -1,138 +1,28 @@
use std::iter; use std::iter;
use std::rc::Rc; use std::rc::Rc;
use chumsky::prelude::*;
use chumsky::Parser;
use itertools::Itertools; use itertools::Itertools;
use super::context::Context; use super::context::Context;
use super::decls::{SimpleParser, SimpleRecursive}; use super::errors::{
use super::enum_filter::enum_filter; BadTokenInRegion, Expected, ExpectedName, LeadingNS, MisalignedParen,
use super::expression::xpr_parser; NamespacedExport, ReservedToken, UnexpectedEOL,
use super::import::import_parser; };
use super::lexer::{filter_map_lex, Lexeme}; use super::lexer::Lexeme;
use super::multiname::parse_multiname;
use super::stream::Stream;
use super::Entry; use super::Entry;
use crate::ast::{Clause, Constant, Expr, Rule}; use crate::ast::{Clause, Constant, Expr, Rule};
use crate::error::{ProjectError, ProjectResult};
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::representations::sourcefile::{FileEntry, Member, Namespace}; use crate::representations::sourcefile::{FileEntry, Member, ModuleBlock};
use crate::representations::VName; use crate::representations::VName;
use crate::sourcefile::Import;
use crate::Primitive;
fn rule_parser<'a>( pub fn split_lines(module: Stream<'_>) -> impl Iterator<Item = Stream<'_>> {
ctx: impl Context + 'a, let mut source = module.data.iter().enumerate();
) -> impl SimpleParser<Entry, Rule<VName>> + 'a { let mut fallback = module.fallback;
xpr_parser(ctx.clone())
.repeated()
.at_least(1)
.then(filter_map_lex(enum_filter!(Lexeme::Rule)))
.then(xpr_parser(ctx).repeated().at_least(1))
.map(|((p, (prio, _)), t)| Rule { pattern: p, prio, template: t })
.labelled("Rule")
}
fn const_parser<'a>(
ctx: impl Context + 'a,
) -> impl SimpleParser<Entry, Constant> + 'a {
filter_map_lex(enum_filter!(Lexeme::Name))
.then_ignore(Lexeme::Const.parser())
.then(xpr_parser(ctx.clone()).repeated().at_least(1))
.map(move |((name, _), value)| Constant {
name,
value: if let Ok(ex) = value.iter().exactly_one() {
ex.clone()
} else {
let start = value
.first()
.expect("value cannot be empty")
.location
.range()
.expect("all locations in parsed source are known")
.start;
let end = value
.last()
.expect("asserted right above")
.location
.range()
.expect("all locations in parsed source are known")
.end;
Expr {
location: Location::Range { file: ctx.file(), range: start..end },
value: Clause::S('(', Rc::new(value)),
}
},
})
}
pub fn collect_errors<T, E: chumsky::Error<T>>(e: Vec<E>) -> E {
e.into_iter()
.reduce(chumsky::Error::merge)
.expect("Error list must be non_enmpty")
}
fn namespace_parser<'a>(
line: impl SimpleParser<Entry, FileEntry> + 'a,
) -> impl SimpleParser<Entry, Namespace> + 'a {
Lexeme::Namespace
.parser()
.ignore_then(filter_map_lex(enum_filter!(Lexeme::Name)))
.then(
any()
.repeated()
.delimited_by(Lexeme::LP('(').parser(), Lexeme::RP('(').parser())
.try_map(move |body, _| {
split_lines(&body)
.map(|l| line.parse(l))
.collect::<Result<Vec<_>, _>>()
.map_err(collect_errors)
}),
)
.map(move |((name, _), body)| Namespace { name, body })
}
fn member_parser<'a>(
line: impl SimpleParser<Entry, FileEntry> + 'a,
ctx: impl Context + 'a,
) -> impl SimpleParser<Entry, Member> + 'a {
choice((
namespace_parser(line).map(Member::Namespace),
rule_parser(ctx.clone()).map(Member::Rule),
const_parser(ctx).map(Member::Constant),
))
}
pub fn line_parser<'a>(
ctx: impl Context + 'a,
) -> impl SimpleParser<Entry, FileEntry> + 'a {
recursive(|line: SimpleRecursive<Entry, FileEntry>| {
choice((
// In case the usercode wants to parse doc
filter_map_lex(enum_filter!(Lexeme >> FileEntry; Comment))
.map(|(ent, _)| ent),
// plain old imports
Lexeme::Import
.parser()
.ignore_then(import_parser(ctx.clone()).map(FileEntry::Import)),
Lexeme::Export.parser().ignore_then(choice((
// token collection
Lexeme::NS
.parser()
.ignore_then(
filter_map_lex(enum_filter!(Lexeme::Name))
.map(|(e, _)| e)
.separated_by(Lexeme::Name(ctx.interner().i(",")).parser())
.delimited_by(Lexeme::LP('(').parser(), Lexeme::RP('(').parser()),
)
.map(FileEntry::Export),
// public declaration
member_parser(line.clone(), ctx.clone()).map(FileEntry::Exported),
))),
// This could match almost anything so it has to go last
member_parser(line, ctx).map(FileEntry::Internal),
))
})
}
pub fn split_lines(data: &[Entry]) -> impl Iterator<Item = &[Entry]> {
let mut source = data.iter().enumerate();
let mut last_slice = 0; let mut last_slice = 0;
let mut finished = false; let mut finished = false;
iter::from_fn(move || { iter::from_fn(move || {
@@ -144,7 +34,9 @@ pub fn split_lines(data: &[Entry]) -> impl Iterator<Item = &[Entry]> {
Lexeme::BR if paren_count == 0 => { Lexeme::BR if paren_count == 0 => {
let begin = last_slice; let begin = last_slice;
last_slice = i + 1; last_slice = i + 1;
return Some(&data[begin..i]); let cur_prev = fallback;
fallback = &module.data[i];
return Some(Stream::new(cur_prev, &module.data[begin..i]));
}, },
_ => (), _ => (),
} }
@@ -152,9 +44,242 @@ pub fn split_lines(data: &[Entry]) -> impl Iterator<Item = &[Entry]> {
// Include last line even without trailing newline // Include last line even without trailing newline
if !finished { if !finished {
finished = true; finished = true;
return Some(&data[last_slice..]); return Some(Stream::new(fallback, &module.data[last_slice..]));
} }
None None
}) })
.filter(|s| !s.is_empty()) }
pub fn parse_module_body(
cursor: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<Vec<FileEntry>> {
split_lines(cursor)
.map(Stream::trim)
.filter(|l| !l.data.is_empty())
.map(|l| parse_line(l, ctx.clone()))
.collect()
}
pub fn parse_line(
cursor: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<FileEntry> {
match cursor.get(0)?.lexeme {
Lexeme::BR | Lexeme::Comment(_) => parse_line(cursor.step()?, ctx),
Lexeme::Export => parse_export_line(cursor.step()?, ctx),
Lexeme::Const | Lexeme::Macro | Lexeme::Module =>
Ok(FileEntry::Internal(parse_member(cursor, ctx)?)),
Lexeme::Import => {
let globstar = ctx.interner().i("*");
let (names, cont) = parse_multiname(cursor.step()?, ctx.clone())?;
cont.expect_empty()?;
let imports = (names.into_iter())
.map(|mut nsname| {
let name = nsname.pop().expect("multinames cannot be zero-length");
Import {
path: ctx.interner().i(&nsname),
name: if name == globstar { None } else { Some(name) },
}
})
.collect();
Ok(FileEntry::Import(imports))
},
_ => {
let err = BadTokenInRegion {
entry: cursor.get(0)?.clone(),
region: "start of line",
};
Err(err.rc())
},
}
}
pub fn parse_export_line(
cursor: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<FileEntry> {
let cursor = cursor.trim();
match cursor.get(0)?.lexeme {
Lexeme::NS => {
let (names, cont) = parse_multiname(cursor.step()?, ctx)?;
cont.expect_empty()?;
let names = (names.into_iter())
.map(|i| if i.len() == 1 { Some(i[0]) } else { None })
.collect::<Option<Vec<_>>>()
.ok_or_else(|| NamespacedExport { location: cursor.location() }.rc())?;
Ok(FileEntry::Export(names))
},
Lexeme::Const | Lexeme::Macro | Lexeme::Module =>
Ok(FileEntry::Exported(parse_member(cursor, ctx)?)),
_ => {
let err = BadTokenInRegion {
entry: cursor.get(0)?.clone(),
region: "exported line",
};
Err(err.rc())
},
}
}
fn parse_member(
cursor: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<Member> {
let (typemark, cursor) = cursor.trim().pop()?;
match typemark.lexeme {
Lexeme::Const => {
let constant = parse_const(cursor, ctx)?;
Ok(Member::Constant(constant))
},
Lexeme::Macro => {
let rule = parse_rule(cursor, ctx)?;
Ok(Member::Rule(rule))
},
Lexeme::Module => {
let module = parse_module(cursor, ctx)?;
Ok(Member::Module(module))
},
_ => {
let err =
BadTokenInRegion { entry: typemark.clone(), region: "member type" };
Err(err.rc())
},
}
}
fn parse_rule(
cursor: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<Rule<VName>> {
let (pattern, prio, template) = cursor.find_map("arrow", |a| match a {
Lexeme::Arrow(p) => Some(*p),
_ => None,
})?;
let (pattern, _) = parse_exprv(pattern, None, ctx.clone())?;
let (template, _) = parse_exprv(template, None, ctx)?;
Ok(Rule { pattern, prio, template })
}
fn parse_const(
cursor: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<Constant> {
let (name_ent, cursor) = cursor.trim().pop()?;
let name = ExpectedName::expect(name_ent)?;
let (walrus_ent, cursor) = cursor.trim().pop()?;
Expected::expect(Lexeme::Walrus, walrus_ent)?;
let (body, _) = parse_exprv(cursor, None, ctx)?;
Ok(Constant { name, value: vec_to_single(walrus_ent, body)? })
}
fn parse_module(
cursor: Stream<'_>,
ctx: impl Context,
) -> ProjectResult<ModuleBlock> {
let (name_ent, cursor) = cursor.trim().pop()?;
let name = ExpectedName::expect(name_ent)?;
let (lp_ent, cursor) = cursor.trim().pop()?;
Expected::expect(Lexeme::LP('('), lp_ent)?;
let (last, cursor) = cursor.pop_back()?;
Expected::expect(Lexeme::RP('('), last)?;
let body = parse_module_body(cursor, ctx)?;
Ok(ModuleBlock { name, body })
}
fn parse_exprv(
mut cursor: Stream<'_>,
paren: Option<char>,
ctx: impl Context,
) -> ProjectResult<(Vec<Expr<VName>>, Stream<'_>)> {
let mut output = Vec::new();
cursor = cursor.trim();
while let Ok(current) = cursor.get(0) {
match &current.lexeme {
Lexeme::BR | Lexeme::Comment(_) => unreachable!("Fillers skipped"),
Lexeme::At | Lexeme::Type =>
return Err(ReservedToken { entry: current.clone() }.rc()),
Lexeme::Literal(l) => {
output.push(Expr {
value: Clause::P(Primitive::Literal(l.clone())),
location: current.location(),
});
cursor = cursor.step()?;
},
Lexeme::Placeh(ph) => {
output.push(Expr {
value: Clause::Placeh(*ph),
location: current.location(),
});
cursor = cursor.step()?;
},
Lexeme::Name(n) => {
let location = cursor.location();
let mut fullname = vec![*n];
while cursor.get(1).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) {
fullname.push(ExpectedName::expect(cursor.get(2)?)?);
cursor = cursor.step()?.step()?;
}
output.push(Expr { value: Clause::Name(fullname), location });
cursor = cursor.step()?;
},
Lexeme::NS =>
return Err(LeadingNS { location: current.location() }.rc()),
Lexeme::RP(c) =>
return if Some(*c) == paren {
Ok((output, cursor.step()?))
} else {
Err(MisalignedParen { entry: cursor.get(0)?.clone() }.rc())
},
Lexeme::LP(c) => {
let (result, leftover) =
parse_exprv(cursor.step()?, Some(*c), ctx.clone())?;
output.push(Expr {
value: Clause::S(*c, Rc::new(result)),
location: cursor.get(0)?.location().to(leftover.fallback.location()),
});
cursor = leftover;
},
Lexeme::BS => {
let (arg, body) =
cursor.step()?.find("A '.'", |l| l == &Lexeme::Dot)?;
let (arg, _) = parse_exprv(arg, None, ctx.clone())?;
let (body, leftover) = parse_exprv(body, paren, ctx)?;
output.push(Expr {
location: cursor.location(),
value: Clause::Lambda(Rc::new(arg), Rc::new(body)),
});
return Ok((output, leftover));
},
_ => {
let err = BadTokenInRegion {
entry: cursor.get(0)?.clone(),
region: "expression",
};
return Err(err.rc());
},
}
cursor = cursor.trim();
}
Ok((output, Stream::new(cursor.fallback, &[])))
}
fn vec_to_single(
fallback: &Entry,
v: Vec<Expr<VName>>,
) -> ProjectResult<Expr<VName>> {
match v.len() {
0 => return Err(UnexpectedEOL { entry: fallback.clone() }.rc()),
1 => Ok(v.into_iter().exactly_one().unwrap()),
_ => Ok(Expr {
location: expr_slice_location(&v),
value: Clause::S('(', Rc::new(v)),
}),
}
}
pub fn expr_slice_location(v: &[impl AsRef<Location>]) -> Location {
v.first()
.map(|l| l.as_ref().clone().to(v.last().unwrap().as_ref().clone()))
.unwrap_or(Location::Unknown)
} }

119
src/parse/stream.rs Normal file
View File

@@ -0,0 +1,119 @@
use super::errors::{ExpectedEOL, NotFound, UnexpectedEOL};
use super::{Entry, Lexeme};
use crate::error::{ProjectError, ProjectResult};
use crate::Location;
/// Represents a slice which may or may not contain items, and a fallback entry
/// used for error reporting whenever the errant stream is empty.
#[derive(Clone, Copy)]
pub struct Stream<'a> {
pub fallback: &'a Entry,
pub data: &'a [Entry],
}
impl<'a> Stream<'a> {
pub fn new(fallback: &'a Entry, data: &'a [Entry]) -> Self {
Self { fallback, data }
}
pub fn trim(self) -> Self {
let Self { data, fallback } = self;
let front = data.iter().take_while(|e| e.is_filler()).count();
let (_, right) = data.split_at(front);
let back = right.iter().rev().take_while(|e| e.is_filler()).count();
let (data, _) = right.split_at(right.len() - back);
Self { fallback, data }
}
pub fn step(self) -> ProjectResult<Self> {
let (fallback, data) = (self.data.split_first())
.ok_or_else(|| UnexpectedEOL { entry: self.fallback.clone() }.rc())?;
Ok(Stream { data, fallback })
}
pub fn pop(self) -> ProjectResult<(&'a Entry, Stream<'a>)> {
Ok((self.get(0)?, self.step()?))
}
/// Retrieve an index from a slice or raise an [UnexpectedEOL].
pub fn get(self, idx: usize) -> ProjectResult<&'a Entry> {
self.data.get(idx).ok_or_else(|| {
let entry = self.data.last().unwrap_or(self.fallback).clone();
UnexpectedEOL { entry }.rc()
})
}
pub fn location(self) -> Location {
self.data.first().map_or_else(
|| self.fallback.location(),
|f| f.location().to(self.data.last().unwrap().location()),
)
}
pub fn find_map<T>(
self,
expected: &'static str,
mut f: impl FnMut(&'a Lexeme) -> Option<T>,
) -> ProjectResult<(Self, T, Self)> {
let Self { data, fallback } = self;
let (dot_idx, output) = skip_parenthesized(data.iter())
.find_map(|(i, e)| f(&e.lexeme).map(|t| (i, t)))
.ok_or_else(|| NotFound { expected, location: self.location() }.rc())?;
let (left, not_left) = data.split_at(dot_idx);
let (middle_ent, right) = not_left.split_first().unwrap();
Ok((Self::new(fallback, left), output, Self::new(middle_ent, right)))
}
pub fn find(
self,
expected: &'static str,
mut f: impl FnMut(&Lexeme) -> bool,
) -> ProjectResult<(Self, Self)> {
let (left, _, right) =
self.find_map(expected, |l| if f(l) { Some(()) } else { None })?;
Ok((left, right))
}
pub fn pop_back(self) -> ProjectResult<(&'a Entry, Self)> {
let Self { data, fallback } = self;
let (last, data) = (data.split_last())
.ok_or_else(|| UnexpectedEOL { entry: fallback.clone() }.rc())?;
Ok((last, Self { fallback, data }))
}
/// # Panics
///
/// If the slice is empty
pub fn from_slice(data: &'a [Entry]) -> Self {
let fallback =
(data.first()).expect("Empty slice cannot be converted into a parseable");
Self { data, fallback }
}
pub fn expect_empty(self) -> ProjectResult<()> {
if let Some(x) = self.data.first() {
Err(ExpectedEOL { location: x.location() }.rc())
} else {
Ok(())
}
}
}
// impl<'a> From<(&'a Entry, &'a [Entry])> for Stream<'a> {
// fn from((fallback, data): (&'a Entry, &'a [Entry])) -> Self {
// Self::new(fallback, data)
// }
// }
pub fn skip_parenthesized<'a>(
it: impl Iterator<Item = &'a Entry>,
) -> impl Iterator<Item = (usize, &'a Entry)> {
let mut paren_lvl = 1;
it.enumerate().filter(move |(_, e)| {
match e.lexeme {
Lexeme::LP(_) => paren_lvl += 1,
Lexeme::RP(_) => paren_lvl -= 1,
_ => (),
}
paren_lvl <= 1
})
}

View File

@@ -1,45 +0,0 @@
use std::rc::Rc;
use super::{ErrorPosition, ProjectError};
use crate::parse::ParseError;
use crate::representations::location::Location;
use crate::utils::BoxedIter;
/// Produced by stages that parse text when it fails.
#[derive(Debug)]
pub struct ParseErrorWithPath {
/// The complete source of the faulty file
pub full_source: String,
/// The path to the faulty file
pub path: Vec<String>,
/// The parse error produced by Chumsky
pub error: ParseError,
}
impl ProjectError for ParseErrorWithPath {
fn description(&self) -> &str {
"Failed to parse code"
}
fn positions(&self) -> BoxedIter<ErrorPosition> {
match &self.error {
ParseError::Lex(lex) => Box::new(lex.iter().map(|s| ErrorPosition {
location: Location::Range {
file: Rc::new(self.path.clone()),
range: s.span(),
},
message: Some(s.to_string()),
})),
ParseError::Ast(ast) => Box::new(ast.iter().map(|(_i, s)| {
ErrorPosition {
location: s
.found()
.map(|e| Location::Range {
file: Rc::new(self.path.clone()),
range: e.range.clone(),
})
.unwrap_or_else(|| Location::File(Rc::new(self.path.clone()))),
message: Some(s.label().unwrap_or("Parse error").to_string()),
}
})),
}
}
}

View File

@@ -3,11 +3,13 @@ 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 hashbrown::{HashMap, HashSet};
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use crate::error::{ErrorPosition, ProjectError, ProjectResult};
#[allow(unused)] // for doc
use crate::facade::System;
use crate::interner::Interner; use crate::interner::Interner;
use crate::pipeline::error::{ErrorPosition, ProjectError};
use crate::utils::iter::box_once; use crate::utils::iter::box_once;
use crate::utils::{BoxedIter, Cache}; use crate::utils::{BoxedIter, Cache};
use crate::{Stok, VName}; use crate::{Stok, VName};
@@ -23,10 +25,10 @@ impl ProjectError for FileLoadingError {
fn description(&self) -> &str { fn description(&self) -> &str {
"Neither a file nor a directory could be read from the requested path" "Neither a file nor a directory could be read from the requested path"
} }
fn positions(&self) -> BoxedIter<ErrorPosition> { fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition::just_file(self.path.clone())) box_once(ErrorPosition::just_file(self.path.clone()))
} }
fn message(&self) -> String { fn message(&self, _i: &Interner) -> String {
format!("File: {}\nDirectory: {}", self.file, self.dir) format!("File: {}\nDirectory: {}", self.file, self.dir)
} }
} }
@@ -49,7 +51,7 @@ impl Loaded {
} }
/// Returned by any source loading callback /// Returned by any source loading callback
pub type IOResult = Result<Loaded, Rc<dyn ProjectError>>; pub type IOResult = ProjectResult<Loaded>;
/// Load a file from a path expressed in Rust strings, but relative to /// Load a file from a path expressed in Rust strings, but relative to
/// a root expressed as an OS Path. /// a root expressed as an OS Path.
@@ -103,7 +105,8 @@ pub fn mk_dir_cache(root: PathBuf, i: &Interner) -> Cache<VName, IOResult> {
pub fn load_embed<T: 'static + RustEmbed>(path: &str, ext: &str) -> IOResult { pub fn load_embed<T: 'static + RustEmbed>(path: &str, ext: &str) -> IOResult {
let file_path = path.to_string() + ext; let file_path = path.to_string() + ext;
if let Some(file) = T::get(&file_path) { if let Some(file) = T::get(&file_path) {
let s = file.data.iter().map(|c| c.to_char()).collect::<String>(); let s =
String::from_utf8(file.data.to_vec()).expect("Embed must be valid UTF-8");
Ok(Loaded::Code(Rc::new(s))) Ok(Loaded::Code(Rc::new(s)))
} else { } else {
let entries = T::iter() let entries = T::iter()
@@ -137,3 +140,38 @@ pub fn mk_embed_cache<'a, T: 'static + RustEmbed>(
load_embed::<T>(&path, ext) load_embed::<T>(&path, ext)
}) })
} }
/// Load all files from an embed and convert them into a map usable in a
/// [System]
pub fn embed_to_map<T: 'static + RustEmbed>(
suffix: &str,
i: &Interner,
) -> HashMap<Vec<Stok>, Loaded> {
let mut files = HashMap::new();
let mut dirs = HashMap::new();
for path in T::iter() {
let vpath = path
.strip_suffix(suffix)
.expect("the embed must be filtered for suffix")
.split('/')
.map(|s| s.to_string())
.collect::<Vec<_>>();
let tokvpath = vpath.iter().map(|segment| i.i(segment)).collect::<Vec<_>>();
let data = T::get(&path).expect("path from iterator").data;
let text =
String::from_utf8(data.to_vec()).expect("code embeds must be utf-8");
files.insert(tokvpath.clone(), text);
for (lvl, subname) in vpath.iter().enumerate() {
let dirname = tokvpath.split_at(lvl).0;
let (_, entries) = (dirs.raw_entry_mut().from_key(dirname))
.or_insert_with(|| (dirname.to_vec(), HashSet::new()));
entries.get_or_insert_with(subname, Clone::clone);
}
}
(files.into_iter())
.map(|(k, s)| (k, Loaded::Code(Rc::new(s))))
.chain((dirs.into_iter()).map(|(k, entv)| {
(k, Loaded::Collection(Rc::new(entv.into_iter().collect())))
}))
.collect()
}

View File

@@ -1,6 +1,4 @@
use std::rc::Rc; use crate::error::{ProjectError, ProjectResult, TooManySupers};
use super::error::{ProjectError, TooManySupers};
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::representations::sourcefile::absolute_path; use crate::representations::sourcefile::absolute_path;
use crate::utils::Substack; use crate::utils::Substack;
@@ -10,7 +8,7 @@ pub fn import_abs_path(
mod_stack: Substack<Tok<String>>, mod_stack: Substack<Tok<String>>,
import_path: &[Tok<String>], import_path: &[Tok<String>],
i: &Interner, i: &Interner,
) -> Result<Vec<Tok<String>>, Rc<dyn ProjectError>> { ) -> ProjectResult<Vec<Tok<String>>> {
// path of module within file // path of module within file
let mod_pathv = mod_stack.iter().rev_vec_clone(); let mod_pathv = mod_stack.iter().rev_vec_clone();
// path of module within compilation // path of module within compilation
@@ -23,9 +21,9 @@ pub fn import_abs_path(
// preload-target path within compilation // preload-target path within compilation
absolute_path(&abs_pathv, import_path, i).map_err(|_| { absolute_path(&abs_pathv, import_path, i).map_err(|_| {
TooManySupers { TooManySupers {
path: import_path.iter().map(|t| i.r(*t)).cloned().collect(), path: import_path.to_vec(),
offender_file: src_path.iter().map(|t| i.r(*t)).cloned().collect(), offender_file: src_path.to_vec(),
offender_mod: mod_pathv.iter().map(|t| i.r(*t)).cloned().collect(), offender_mod: mod_pathv,
} }
.rc() .rc()
}) })

View File

@@ -1,9 +1,7 @@
use std::rc::Rc;
use super::alias_map::AliasMap; use super::alias_map::AliasMap;
use super::decls::UpdatedFn; use super::decls::UpdatedFn;
use crate::interner::{Interner, Tok}; use crate::error::{NotExported, NotFound, ProjectError, ProjectResult};
use crate::pipeline::error::{NotExported, NotFound, ProjectError}; use crate::interner::Tok;
use crate::pipeline::project_tree::split_path; use crate::pipeline::project_tree::split_path;
use crate::representations::project::{ProjectModule, ProjectTree}; use crate::representations::project::{ProjectModule, ProjectTree};
use crate::representations::tree::{ModMember, WalkErrorKind}; use crate::representations::tree::{ModMember, WalkErrorKind};
@@ -15,8 +13,7 @@ fn assert_visible(
source: &[Tok<String>], // must point to a file or submodule source: &[Tok<String>], // must point to a file or submodule
target: &[Tok<String>], // may point to a symbol or module of any kind target: &[Tok<String>], // may point to a symbol or module of any kind
project: &ProjectTree<VName>, project: &ProjectTree<VName>,
i: &Interner, ) -> ProjectResult<()> {
) -> Result<(), Rc<dyn ProjectError>> {
let (tgt_item, tgt_path) = unwrap_or!(target.split_last(); return Ok(())); let (tgt_item, tgt_path) = unwrap_or!(target.split_last(); 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();
@@ -27,11 +24,11 @@ fn assert_visible(
WalkErrorKind::Private => WalkErrorKind::Private =>
unreachable!("visibility is not being checked here"), unreachable!("visibility is not being checked here"),
WalkErrorKind::Missing => NotFound::from_walk_error( WalkErrorKind::Missing => NotFound::from_walk_error(
source,
&[], &[],
&tgt_path[..vis_ignored_len], &tgt_path[..vis_ignored_len],
&project.0, &project.0,
e, e,
i,
) )
.rc(), .rc(),
})?; })?;
@@ -39,37 +36,51 @@ fn assert_visible(
.walk_ref(&tgt_path[vis_ignored_len..], true) .walk_ref(&tgt_path[vis_ignored_len..], true)
.map_err(|e| match e.kind { .map_err(|e| match e.kind {
WalkErrorKind::Missing => NotFound::from_walk_error( WalkErrorKind::Missing => NotFound::from_walk_error(
source,
&tgt_path[..vis_ignored_len], &tgt_path[..vis_ignored_len],
&tgt_path[vis_ignored_len..], &tgt_path[vis_ignored_len..],
&project.0, &project.0,
e, e,
i,
) )
.rc(), .rc(),
WalkErrorKind::Private => { WalkErrorKind::Private => {
let full_path = &tgt_path[..shared_len + e.pos]; let full_path = &tgt_path[..shared_len + e.pos];
let (file, sub) = split_path(full_path, project); // These errors are encountered during error reporting but they're more
let (ref_file, ref_sub) = split_path(source, project); // fundamental / higher prio than the error to be raised and would
// emerge nonetheless so they take over and the original error is
// swallowed
match split_path(full_path, project) {
Err(e) =>
NotFound::from_walk_error(source, &[], full_path, &project.0, e)
.rc(),
Ok((file, sub)) => {
let (ref_file, ref_sub) = split_path(source, project)
.expect("Source path assumed to be valid");
NotExported { NotExported {
file: i.extern_all(file), file: file.to_vec(),
subpath: i.extern_all(sub), subpath: sub.to_vec(),
referrer_file: i.extern_all(ref_file), referrer_file: ref_file.to_vec(),
referrer_subpath: i.extern_all(ref_sub), referrer_subpath: ref_sub.to_vec(),
} }
.rc() .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 = shared_len == tgt_path.len(); let target_prefixes_source = shared_len == tgt_path.len();
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).map_err(|e| {
let (ref_file, ref_sub) = split_path(source, project); NotFound::from_walk_error(source, &[], target, &project.0, e).rc()
})?;
let (ref_file, ref_sub) = split_path(source, project)
.expect("The source path is assumed to be valid");
Err( Err(
NotExported { NotExported {
file: i.extern_all(file), file: file.to_vec(),
subpath: i.extern_all(sub), subpath: sub.to_vec(),
referrer_file: i.extern_all(ref_file), referrer_file: ref_file.to_vec(),
referrer_subpath: i.extern_all(ref_sub), referrer_subpath: ref_sub.to_vec(),
} }
.rc(), .rc(),
) )
@@ -84,9 +95,8 @@ fn collect_aliases_rec(
module: &ProjectModule<VName>, module: &ProjectModule<VName>,
project: &ProjectTree<VName>, project: &ProjectTree<VName>,
alias_map: &mut AliasMap, alias_map: &mut AliasMap,
i: &Interner,
updated: &impl UpdatedFn, updated: &impl UpdatedFn,
) -> Result<(), Rc<dyn ProjectError>> { ) -> ProjectResult<()> {
// 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 !updated(&mod_path_v) { if !updated(&mod_path_v) {
@@ -94,20 +104,18 @@ fn collect_aliases_rec(
}; };
for (&name, target_mod_name) in module.extra.imports_from.iter() { for (&name, target_mod_name) in module.extra.imports_from.iter() {
let target_sym_v = pushed(target_mod_name, name); let target_sym_v = pushed(target_mod_name, name);
assert_visible(&mod_path_v, &target_sym_v, project, i)?; assert_visible(&mod_path_v, &target_sym_v, project)?;
let sym_path_v = pushed(&mod_path_v, name); let sym_path_v = pushed(&mod_path_v, name);
let target_mod = (project.0.walk_ref(target_mod_name, false)) let target_mod = (project.0.walk_ref(target_mod_name, false))
.expect("checked above in assert_visible"); .expect("checked above in assert_visible");
let target_sym = target_mod let target_sym = (target_mod.extra.exports.get(&name))
.extra
.exports
.get(&name)
.ok_or_else(|| { .ok_or_else(|| {
let file_len = let file_len =
target_mod.extra.file.as_ref().unwrap_or(target_mod_name).len(); target_mod.extra.file.as_ref().unwrap_or(target_mod_name).len();
NotFound { NotFound {
file: i.extern_all(&target_mod_name[..file_len]), source: Some(mod_path_v.clone()),
subpath: i.extern_all(&target_sym_v[file_len..]), file: target_mod_name[..file_len].to_vec(),
subpath: target_sym_v[file_len..].to_vec(),
} }
.rc() .rc()
})? })?
@@ -121,7 +129,6 @@ fn collect_aliases_rec(
submodule, submodule,
project, project,
alias_map, alias_map,
i,
updated, updated,
)? )?
} }
@@ -133,8 +140,7 @@ pub fn collect_aliases(
module: &ProjectModule<VName>, module: &ProjectModule<VName>,
project: &ProjectTree<VName>, project: &ProjectTree<VName>,
alias_map: &mut AliasMap, alias_map: &mut AliasMap,
i: &Interner,
updated: &impl UpdatedFn, updated: &impl UpdatedFn,
) -> Result<(), Rc<dyn ProjectError>> { ) -> ProjectResult<()> {
collect_aliases_rec(Substack::Bottom, module, project, alias_map, i, updated) collect_aliases_rec(Substack::Bottom, module, project, alias_map, updated)
} }

View File

@@ -1,11 +1,8 @@
use std::rc::Rc;
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, UpdatedFn}; use super::decls::{InjectedAsFn, UpdatedFn};
use crate::interner::Interner; use crate::error::ProjectResult;
use crate::pipeline::error::ProjectError;
use crate::representations::project::ProjectTree; use crate::representations::project::ProjectTree;
use crate::representations::VName; use crate::representations::VName;
@@ -13,12 +10,11 @@ use crate::representations::VName;
/// replace these aliases with the original names throughout the tree /// replace these aliases with the original names throughout the tree
pub fn resolve_imports( pub fn resolve_imports(
project: ProjectTree<VName>, project: ProjectTree<VName>,
i: &Interner,
injected_as: &impl InjectedAsFn, injected_as: &impl InjectedAsFn,
updated: &impl UpdatedFn, updated: &impl UpdatedFn,
) -> Result<ProjectTree<VName>, Rc<dyn ProjectError>> { ) -> ProjectResult<ProjectTree<VName>> {
let mut map = AliasMap::new(); let mut map = AliasMap::new();
collect_aliases(&project.0, &project, &mut map, i, updated)?; collect_aliases(&project.0, &project, &mut map, updated)?;
let new_mod = apply_aliases(&project.0, &map, injected_as, updated); let new_mod = apply_aliases(&project.0, &map, injected_as, updated);
Ok(ProjectTree(new_mod)) Ok(ProjectTree(new_mod))
} }

View File

@@ -1,5 +1,4 @@
//! Loading Orchid modules from source //! Loading Orchid modules from source
pub mod error;
pub mod file_loader; pub mod file_loader;
mod import_abs_path; mod import_abs_path;
mod import_resolution; mod import_resolution;

View File

@@ -1,8 +1,8 @@
use std::rc::Rc; use std::rc::Rc;
use super::error::ProjectError;
use super::file_loader::IOResult; use super::file_loader::IOResult;
use super::{import_resolution, project_tree, source_loader}; use super::{import_resolution, project_tree, source_loader};
use crate::error::ProjectResult;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::representations::sourcefile::FileEntry; use crate::representations::sourcefile::FileEntry;
use crate::representations::VName; use crate::representations::VName;
@@ -22,7 +22,7 @@ pub fn parse_layer<'a>(
environment: &'a ProjectTree<VName>, environment: &'a ProjectTree<VName>,
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner, i: &Interner,
) -> Result<ProjectTree<VName>, Rc<dyn ProjectError>> { ) -> ProjectResult<ProjectTree<VName>> {
// A path is injected if it is walkable in the injected tree // A path is injected if it is walkable in the injected tree
let injected_as = |path: &[Tok<String>]| { let injected_as = |path: &[Tok<String>]| {
let (item, modpath) = path.split_last()?; let (item, modpath) = path.split_last()?;
@@ -40,7 +40,7 @@ pub fn parse_layer<'a>(
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(environment.0.clone().overlay(tree.0.clone())); let sum = ProjectTree(environment.0.clone().overlay(tree.0.clone()));
let resolvd = let resolvd =
import_resolution::resolve_imports(sum, i, &injected_as, &|path| { import_resolution::resolve_imports(sum, &injected_as, &|path| {
tree.0.walk_ref(path, false).is_ok() tree.0.walk_ref(path, false).is_ok()
})?; })?;
// Addition among modules favours the left hand side. // Addition among modules favours the left hand side.

View File

@@ -1,5 +1,5 @@
use crate::interner::Tok; use crate::interner::Tok;
use crate::representations::sourcefile::{FileEntry, Member, Namespace}; use crate::representations::sourcefile::{FileEntry, Member, ModuleBlock};
fn member_rec( fn member_rec(
// object // object
@@ -9,9 +9,9 @@ fn member_rec(
prelude: &[FileEntry], prelude: &[FileEntry],
) -> Member { ) -> Member {
match member { match member {
Member::Namespace(Namespace { name, body }) => { Member::Module(ModuleBlock { name, body }) => {
let new_body = entv_rec(body, path, prelude); let new_body = entv_rec(body, path, prelude);
Member::Namespace(Namespace { name, body: new_body }) Member::Module(ModuleBlock { name, body: new_body })
}, },
any => any, any => any,
} }

View File

@@ -1,19 +1,18 @@
use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use itertools::Itertools;
use super::collect_ops; use super::collect_ops;
use super::collect_ops::InjectedOperatorsFn; use super::collect_ops::InjectedOperatorsFn;
use super::parse_file::parse_file; use super::parse_file::parse_file;
use crate::ast::{Constant, Expr}; use crate::ast::{Clause, Constant, Expr};
use crate::error::{ProjectError, ProjectResult, TooManySupers};
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::pipeline::error::{ProjectError, TooManySupers};
use crate::pipeline::source_loader::{LoadedSource, LoadedSourceTable}; use crate::pipeline::source_loader::{LoadedSource, LoadedSourceTable};
use crate::representations::project::{ProjectExt, ProjectTree}; use crate::representations::project::{ProjectExt, ProjectTree};
use crate::representations::sourcefile::{absolute_path, FileEntry, Member}; use crate::representations::sourcefile::{absolute_path, FileEntry, Member};
use crate::representations::tree::{ModEntry, ModMember, Module}; use crate::representations::tree::{ModEntry, ModMember, Module};
use crate::representations::{NameLike, VName}; use crate::representations::{NameLike, VName};
use crate::tree::{WalkError, WalkErrorKind};
use crate::utils::iter::{box_empty, box_once}; use crate::utils::iter::{box_empty, box_once};
use crate::utils::{pushed, unwrap_or, Substack}; use crate::utils::{pushed, unwrap_or, Substack};
@@ -26,25 +25,27 @@ struct ParsedSource<'a> {
/// Split a path into file- and subpath in knowledge /// Split a path into file- and subpath in knowledge
/// ///
/// # Panics /// # Errors
/// ///
/// if the path is invalid /// if the path is invalid
#[allow(clippy::type_complexity)] // bit too sensitive here IMO
pub fn split_path<'a>( pub fn split_path<'a>(
path: &'a [Tok<String>], path: &'a [Tok<String>],
proj: &'a ProjectTree<impl NameLike>, proj: &'a ProjectTree<impl NameLike>,
) -> (&'a [Tok<String>], &'a [Tok<String>]) { ) -> Result<(&'a [Tok<String>], &'a [Tok<String>]), WalkError> {
let (end, body) = unwrap_or!(path.split_last(); { let (end, body) = unwrap_or!(path.split_last(); {
return (&[], &[]) return Ok((&[], &[]))
}); });
let mut module = (proj.0.walk_ref(body, false)) let mut module = (proj.0.walk_ref(body, false))?;
.expect("invalid path can't have been split above"); let entry = (module.items.get(end))
if let ModMember::Sub(m) = &module.items[end].member { .ok_or(WalkError { pos: path.len() - 1, kind: WalkErrorKind::Missing })?;
if let ModMember::Sub(m) = &entry.member {
module = m; module = m;
} }
let file = let file =
module.extra.file.as_ref().map(|s| &path[..s.len()]).unwrap_or(path); module.extra.file.as_ref().map(|s| &path[..s.len()]).unwrap_or(path);
let subpath = &path[file.len()..]; let subpath = &path[file.len()..];
(file, subpath) Ok((file, subpath))
} }
/// Convert normalized, prefixed source into a module /// Convert normalized, prefixed source into a module
@@ -63,7 +64,7 @@ fn source_to_module(
// context // context
i: &Interner, i: &Interner,
filepath_len: usize, filepath_len: usize,
) -> Result<Module<Expr<VName>, ProjectExt<VName>>, Rc<dyn ProjectError>> { ) -> ProjectResult<Module<Expr<VName>, ProjectExt<VName>>> {
let path_v = path.iter().rev_vec_clone(); let path_v = path.iter().rev_vec_clone();
let imports = (data.iter()) let imports = (data.iter())
.filter_map(|ent| { .filter_map(|ent| {
@@ -73,16 +74,16 @@ fn source_to_module(
.cloned() .cloned()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let imports_from = (imports.iter()) let imports_from = (imports.iter())
.map(|imp| -> Result<_, Rc<dyn ProjectError>> { .map(|imp| -> ProjectResult<_> {
let mut imp_path_v = i.r(imp.path).clone(); let mut imp_path_v = i.r(imp.path).clone();
imp_path_v.push(imp.name.expect("glob imports had just been resolved")); imp_path_v.push(imp.name.expect("glob imports had just been resolved"));
let mut abs_path = absolute_path(&path_v, &imp_path_v, i) let mut abs_path = absolute_path(&path_v, &imp_path_v, i)
.expect("should have failed in preparsing"); .expect("should have failed in preparsing");
let name = abs_path.pop().ok_or_else(|| { let name = abs_path.pop().ok_or_else(|| {
TooManySupers { TooManySupers {
offender_file: i.extern_all(&path_v[..filepath_len]), offender_file: path_v[..filepath_len].to_vec(),
offender_mod: i.extern_all(&path_v[filepath_len..]), offender_mod: path_v[filepath_len..].to_vec(),
path: i.extern_all(&imp_path_v), path: imp_path_v,
} }
.rc() .rc()
})?; })?;
@@ -96,15 +97,18 @@ fn source_to_module(
FileEntry::Export(names) => Box::new(names.iter().copied().map(mk_ent)), FileEntry::Export(names) => Box::new(names.iter().copied().map(mk_ent)),
FileEntry::Exported(mem) => match mem { FileEntry::Exported(mem) => match mem {
Member::Constant(constant) => box_once(mk_ent(constant.name)), Member::Constant(constant) => box_once(mk_ent(constant.name)),
Member::Namespace(ns) => box_once(mk_ent(ns.name)), Member::Module(ns) => box_once(mk_ent(ns.name)),
Member::Rule(rule) => { Member::Rule(rule) => {
let mut names = Vec::new(); let mut names = Vec::new();
for e in rule.pattern.iter() { for e in rule.pattern.iter() {
e.visit_names(Substack::Bottom, &mut |n| { e.search_all(&mut |e| {
if let Clause::Name(n) = &e.value {
if let Some([name]) = n.strip_prefix(&path_v[..]) { if let Some([name]) = n.strip_prefix(&path_v[..]) {
names.push((*name, n.clone())) names.push((*name, n.clone()))
} }
}) }
None::<()>
});
} }
Box::new(names.into_iter()) Box::new(names.into_iter())
}, },
@@ -124,7 +128,7 @@ fn source_to_module(
let items = (data.into_iter()) let items = (data.into_iter())
.filter_map(|ent| { .filter_map(|ent| {
let member_to_item = |exported, member| match member { let member_to_item = |exported, member| match member {
Member::Namespace(ns) => { Member::Module(ns) => {
let new_prep = unwrap_or!( let new_prep = unwrap_or!(
&preparsed.items[&ns.name].member => ModMember::Sub; &preparsed.items[&ns.name].member => ModMember::Sub;
panic!("Preparsed should include entries for all submodules") panic!("Preparsed should include entries for all submodules")
@@ -171,7 +175,7 @@ fn files_to_module(
path: Substack<Tok<String>>, path: Substack<Tok<String>>,
files: Vec<ParsedSource>, files: Vec<ParsedSource>,
i: &Interner, i: &Interner,
) -> Result<Module<Expr<VName>, ProjectExt<VName>>, Rc<dyn ProjectError>> { ) -> ProjectResult<Module<Expr<VName>, ProjectExt<VName>>> {
let lvl = path.len(); let lvl = path.len();
debug_assert!( debug_assert!(
files.iter().map(|f| f.path.len()).max().unwrap() >= lvl, files.iter().map(|f| f.path.len()).max().unwrap() >= lvl,
@@ -190,7 +194,7 @@ fn files_to_module(
let items = (files.into_iter()) let items = (files.into_iter())
.group_by(|f| f.path[lvl]) .group_by(|f| f.path[lvl])
.into_iter() .into_iter()
.map(|(namespace, files)| -> Result<_, Rc<dyn ProjectError>> { .map(|(namespace, files)| -> ProjectResult<_> {
let subpath = path.push(namespace); let subpath = path.push(namespace);
let files_v = files.collect::<Vec<_>>(); let files_v = files.collect::<Vec<_>>();
let module = files_to_module(subpath, files_v, i)?; let module = files_to_module(subpath, files_v, i)?;
@@ -217,7 +221,7 @@ pub fn build_tree(
i: &Interner, i: &Interner,
prelude: &[FileEntry], prelude: &[FileEntry],
injected: &impl InjectedOperatorsFn, injected: &impl InjectedOperatorsFn,
) -> Result<ProjectTree<VName>, Rc<dyn ProjectError>> { ) -> ProjectResult<ProjectTree<VName>> {
assert!(!files.is_empty(), "A tree requires at least one module"); 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
@@ -225,7 +229,7 @@ pub fn build_tree(
.map(|(path, loaded)| { .map(|(path, loaded)| {
Ok((path, loaded, parse_file(path, &files, &ops_cache, i, prelude)?)) Ok((path, loaded, parse_file(path, &files, &ops_cache, i, prelude)?))
}) })
.collect::<Result<Vec<_>, Rc<dyn ProjectError>>>()?; .collect::<ProjectResult<Vec<_>>>()?;
// sort by similarity, then longest-first // sort by similarity, then longest-first
entries.sort_unstable_by(|a, b| a.0.cmp(b.0).reverse()); entries.sort_unstable_by(|a, b| a.0.cmp(b.0).reverse());
let files = entries let files = entries

View File

@@ -3,14 +3,14 @@ use std::rc::Rc;
use hashbrown::HashSet; use hashbrown::HashSet;
use trait_set::trait_set; use trait_set::trait_set;
use crate::error::{NotFound, ProjectError, ProjectResult};
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::pipeline::error::{NotFound, ProjectError};
use crate::pipeline::source_loader::LoadedSourceTable; use crate::pipeline::source_loader::LoadedSourceTable;
use crate::representations::tree::WalkErrorKind; use crate::representations::tree::WalkErrorKind;
use crate::utils::{split_max_prefix, unwrap_or, Cache}; use crate::utils::{split_max_prefix, unwrap_or, Cache};
use crate::Sym; use crate::Sym;
pub type OpsResult = Result<Rc<HashSet<Tok<String>>>, Rc<dyn ProjectError>>; pub type OpsResult = ProjectResult<Rc<HashSet<Tok<String>>>>;
pub type ExportedOpsCache<'a> = Cache<'a, Sym, OpsResult>; pub type ExportedOpsCache<'a> = Cache<'a, Sym, OpsResult>;
trait_set! { trait_set! {
@@ -54,12 +54,9 @@ pub fn collect_exported_ops(
unreachable!("visibility is not being checked here") unreachable!("visibility is not being checked here")
}, },
WalkErrorKind::Missing => NotFound { WalkErrorKind::Missing => NotFound {
file: i.extern_all(fpath), source: None,
subpath: (subpath.iter()) file: fpath.to_vec(),
.take(walk_err.pos) subpath: subpath[..walk_err.pos].to_vec(),
.map(|t| i.r(*t))
.cloned()
.collect(),
} }
.rc(), .rc(),
}, },

View File

@@ -3,9 +3,9 @@ use std::rc::Rc;
use hashbrown::HashSet; use hashbrown::HashSet;
use super::exported_ops::{ExportedOpsCache, OpsResult}; use super::exported_ops::{ExportedOpsCache, OpsResult};
use crate::error::ProjectResult;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::parse::is_op; use crate::parse::is_op;
use crate::pipeline::error::ProjectError;
use crate::pipeline::import_abs_path::import_abs_path; use crate::pipeline::import_abs_path::import_abs_path;
use crate::pipeline::source_loader::LoadedSourceTable; use crate::pipeline::source_loader::LoadedSourceTable;
use crate::representations::tree::{ModMember, Module}; use crate::representations::tree::{ModMember, Module};
@@ -39,16 +39,17 @@ pub fn collect_ops_for(
let tree = &loaded[file].preparsed.0; let tree = &loaded[file].preparsed.0;
let mut ret = HashSet::new(); let mut ret = HashSet::new();
tree_all_ops(tree, &mut ret); tree_all_ops(tree, &mut ret);
tree.visit_all_imports(&mut |modpath, _module, import| { tree.visit_all_imports(&mut |modpath, _m, import| -> ProjectResult<()> {
if let Some(n) = import.name { if let Some(n) = import.name {
ret.insert(n); ret.insert(n);
} else { } else {
let path = import_abs_path(file, modpath, &i.r(import.path)[..], i) let path = i.expect(
.expect("This error should have been caught during loading"); import_abs_path(file, modpath, &i.r(import.path)[..], i),
"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());
} }
Ok::<_, Rc<dyn ProjectError>>(()) Ok(())
})?; })?;
ret.drain_filter(|t| !is_op(i.r(*t))); Ok(Rc::new(ret.into_iter().filter(|t| is_op(i.r(*t))).collect()))
Ok(Rc::new(ret))
} }

View File

@@ -2,7 +2,7 @@ use super::collect_ops::ExportedOpsCache;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::pipeline::import_abs_path::import_abs_path; use crate::pipeline::import_abs_path::import_abs_path;
use crate::representations::sourcefile::{ use crate::representations::sourcefile::{
FileEntry, Import, Member, Namespace, FileEntry, Import, Member, ModuleBlock,
}; };
use crate::representations::tree::{ModMember, Module}; use crate::representations::tree::{ModMember, Module};
use crate::utils::iter::box_once; use crate::utils::iter::box_once;
@@ -20,14 +20,14 @@ fn member_rec(
i: &Interner, i: &Interner,
) -> Member { ) -> Member {
match member { match member {
Member::Namespace(Namespace { name, body }) => { Member::Module(ModuleBlock { name, body }) => {
let subprep = unwrap_or!( let subprep = unwrap_or!(
&preparsed.items[&name].member => ModMember::Sub; &preparsed.items[&name].member => ModMember::Sub;
unreachable!("This name must point to a namespace") unreachable!("This name must point to a namespace")
); );
let new_body = let new_body =
entv_rec(mod_stack.push(name), subprep, body, path, ops_cache, i); entv_rec(mod_stack.push(name), subprep, body, path, ops_cache, i);
Member::Namespace(Namespace { name, body: new_body }) Member::Module(ModuleBlock { name, body: new_body })
}, },
any => any, any => any,
} }
@@ -58,10 +58,14 @@ fn entv_rec(
.into_iter() .into_iter()
.flat_map(|import| { .flat_map(|import| {
if let Import { name: None, path } = import { if let Import { name: None, path } = import {
let p = import_abs_path(mod_path, mod_stack, &i.r(path)[..], i) let p = i.expect(
.expect("Should have emerged in preparsing"); import_abs_path(mod_path, mod_stack, &i.r(path)[..], i),
let names = (ops_cache.find(&i.i(&p))) "Should have emerged in preparsing",
.expect("Should have emerged in second parsing"); );
let names = i.expect(
ops_cache.find(&i.i(&p)),
"Should have emerged in second parsing",
);
let imports = (names.iter()) let imports = (names.iter())
.map(move |&n| Import { name: Some(n), path }) .map(move |&n| Import { name: Some(n), path })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@@ -4,9 +4,9 @@ use super::add_prelude::add_prelude;
use super::collect_ops::{collect_ops_for, ExportedOpsCache}; use super::collect_ops::{collect_ops_for, ExportedOpsCache};
use super::normalize_imports::normalize_imports; use super::normalize_imports::normalize_imports;
use super::prefix::prefix; use super::prefix::prefix;
use crate::error::ProjectResult;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::parse; use crate::parse;
use crate::pipeline::error::ProjectError;
use crate::pipeline::source_loader::LoadedSourceTable; use crate::pipeline::source_loader::LoadedSourceTable;
use crate::representations::sourcefile::{normalize_namespaces, FileEntry}; use crate::representations::sourcefile::{normalize_namespaces, FileEntry};
@@ -24,7 +24,7 @@ pub fn parse_file(
ops_cache: &ExportedOpsCache, ops_cache: &ExportedOpsCache,
i: &Interner, i: &Interner,
prelude: &[FileEntry], prelude: &[FileEntry],
) -> Result<Vec<FileEntry>, Rc<dyn ProjectError>> { ) -> ProjectResult<Vec<FileEntry>> {
let ld = &loaded[path]; let ld = &loaded[path];
// let ops_cache = collect_ops::mk_cache(loaded, i); // let ops_cache = collect_ops::mk_cache(loaded, i);
let ops = collect_ops_for(path, loaded, ops_cache, i)?; let ops = collect_ops_for(path, loaded, ops_cache, i)?;
@@ -34,8 +34,10 @@ pub fn parse_file(
ops: &ops_vec, ops: &ops_vec,
file: Rc::new(i.extern_all(path)), file: Rc::new(i.extern_all(path)),
}; };
let entries = parse::parse(ld.text.as_str(), ctx) let entries = i.expect(
.expect("This error should have been caught during loading"); parse::parse2(ld.text.as_str(), ctx),
"This error should have been caught during loading",
);
let with_prelude = add_prelude(entries, path, prelude); let with_prelude = add_prelude(entries, path, prelude);
let impnormalized = let impnormalized =
normalize_imports(&ld.preparsed.0, with_prelude, path, ops_cache, i); normalize_imports(&ld.preparsed.0, with_prelude, path, ops_cache, i);

View File

@@ -1,7 +1,7 @@
use super::collect_ops::ExportedOpsCache; use super::collect_ops::ExportedOpsCache;
use crate::ast::{Constant, Rule}; use crate::ast::{Constant, Rule};
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::representations::sourcefile::{FileEntry, Member, Namespace}; use crate::representations::sourcefile::{FileEntry, Member, ModuleBlock};
use crate::utils::Substack; use crate::utils::Substack;
fn member_rec( fn member_rec(
@@ -19,9 +19,9 @@ fn member_rec(
.chain(mod_stack.iter().rev_vec_clone().into_iter()) .chain(mod_stack.iter().rev_vec_clone().into_iter())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match data { match data {
Member::Namespace(Namespace { name, body }) => { Member::Module(ModuleBlock { name, body }) => {
let new_body = entv_rec(mod_stack.push(name), body, path, ops_cache, i); let new_body = entv_rec(mod_stack.push(name), body, path, ops_cache, i);
Member::Namespace(Namespace { name, body: new_body }) Member::Module(ModuleBlock { name, body: new_body })
}, },
Member::Constant(constant) => Member::Constant(Constant { Member::Constant(constant) => Member::Constant(Constant {
name: constant.name, name: constant.name,

View File

@@ -1,10 +1,11 @@
use std::iter; use std::iter;
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::error::{
NoTargets, ProjectError, ProjectResult, UnexpectedDirectory,
};
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::pipeline::error::{ProjectError, UnexpectedDirectory};
use crate::pipeline::file_loader::{IOResult, Loaded}; use crate::pipeline::file_loader::{IOResult, Loaded};
use crate::pipeline::import_abs_path::import_abs_path; use crate::pipeline::import_abs_path::import_abs_path;
use crate::representations::sourcefile::FileEntry; use crate::representations::sourcefile::FileEntry;
@@ -19,7 +20,7 @@ fn load_abs_path_rec(
i: &Interner, i: &Interner,
get_source: &impl Fn(&[Tok<String>]) -> IOResult, get_source: &impl Fn(&[Tok<String>]) -> IOResult,
is_injected_module: &impl Fn(&[Tok<String>]) -> bool, is_injected_module: &impl Fn(&[Tok<String>]) -> bool,
) -> Result<(), Rc<dyn ProjectError>> { ) -> ProjectResult<()> {
// # Termination // # Termination
// //
// Every recursion of this function either // Every recursion of this function either
@@ -40,7 +41,7 @@ fn load_abs_path_rec(
if let Some((filename, _)) = name_split { if let Some((filename, _)) = name_split {
// if the filename is valid, load, preparse and record this file // if the filename is valid, load, preparse and record this file
let text = unwrap_or!(get_source(filename)? => Loaded::Code; { let text = unwrap_or!(get_source(filename)? => Loaded::Code; {
return Err(UnexpectedDirectory { path: i.extern_all(filename) }.rc()) return Err(UnexpectedDirectory { path: filename.to_vec() }.rc())
}); });
let preparsed = preparse( let preparsed = preparse(
filename.iter().map(|t| i.r(*t)).cloned().collect(), filename.iter().map(|t| i.r(*t)).cloned().collect(),
@@ -56,6 +57,9 @@ fn load_abs_path_rec(
preparsed.0.visit_all_imports(&mut |modpath, _module, import| { preparsed.0.visit_all_imports(&mut |modpath, _module, import| {
let abs_pathv = let abs_pathv =
import_abs_path(filename, modpath, &import.nonglob_path(i), i)?; import_abs_path(filename, modpath, &import.nonglob_path(i), i)?;
if abs_path.starts_with(&abs_pathv) {
return Ok(());
}
// recurse on imported module // recurse on imported module
load_abs_path_rec( load_abs_path_rec(
&abs_pathv, &abs_pathv,
@@ -111,9 +115,11 @@ pub fn load_source<'a>(
i: &Interner, i: &Interner,
get_source: &impl Fn(&[Tok<String>]) -> IOResult, get_source: &impl Fn(&[Tok<String>]) -> IOResult,
is_injected_module: &impl Fn(&[Tok<String>]) -> bool, is_injected_module: &impl Fn(&[Tok<String>]) -> bool,
) -> Result<LoadedSourceTable, Rc<dyn ProjectError>> { ) -> ProjectResult<LoadedSourceTable> {
let mut table = LoadedSourceTable::new(); let mut table = LoadedSourceTable::new();
let mut any_target = false;
for target in targets { for target in targets {
any_target |= true;
load_abs_path_rec( load_abs_path_rec(
target, target,
&mut table, &mut table,
@@ -123,5 +129,5 @@ pub fn load_source<'a>(
is_injected_module, is_injected_module,
)? )?
} }
Ok(table) if any_target { Ok(table) } else { Err(NoTargets.rc()) }
} }

View File

@@ -4,11 +4,9 @@ use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use crate::ast::Constant; use crate::ast::Constant;
use crate::error::{ProjectError, ProjectResult, VisibilityMismatch};
use crate::interner::Interner; use crate::interner::Interner;
use crate::parse::{self, ParsingContext}; use crate::parse::{self, ParsingContext};
use crate::pipeline::error::{
ParseErrorWithPath, ProjectError, VisibilityMismatch,
};
use crate::representations::sourcefile::{ use crate::representations::sourcefile::{
imports, normalize_namespaces, FileEntry, Member, imports, normalize_namespaces, FileEntry, Member,
}; };
@@ -38,12 +36,12 @@ fn to_module(src: &[FileEntry], prelude: &[FileEntry]) -> Module<(), ()> {
let imports = imports(all_src()).cloned().collect::<Vec<_>>(); let imports = imports(all_src()).cloned().collect::<Vec<_>>();
let mut items = all_src() let mut items = all_src()
.filter_map(|ent| match ent { .filter_map(|ent| match ent {
FileEntry::Internal(Member::Namespace(ns)) => { FileEntry::Internal(Member::Module(ns)) => {
let member = ModMember::Sub(to_module(&ns.body, prelude)); let member = ModMember::Sub(to_module(&ns.body, prelude));
let entry = ModEntry { exported: false, member }; let entry = ModEntry { exported: false, member };
Some((ns.name, entry)) Some((ns.name, entry))
}, },
FileEntry::Exported(Member::Namespace(ns)) => { FileEntry::Exported(Member::Module(ns)) => {
let member = ModMember::Sub(to_module(&ns.body, prelude)); let member = ModMember::Sub(to_module(&ns.body, prelude));
let entry = ModEntry { exported: true, member }; let entry = ModEntry { exported: true, member };
Some((ns.name, entry)) Some((ns.name, entry))
@@ -55,8 +53,8 @@ fn to_module(src: &[FileEntry], prelude: &[FileEntry]) -> Module<(), ()> {
match file_entry { match file_entry {
FileEntry::Comment(_) FileEntry::Comment(_)
| FileEntry::Import(_) | FileEntry::Import(_)
| FileEntry::Internal(Member::Namespace(_)) | FileEntry::Internal(Member::Module(_))
| FileEntry::Exported(Member::Namespace(_)) => (), | FileEntry::Exported(Member::Module(_)) => (),
FileEntry::Export(tokv) => FileEntry::Export(tokv) =>
for tok in tokv { for tok in tokv {
add_export(&mut items, *tok) add_export(&mut items, *tok)
@@ -89,24 +87,13 @@ pub fn preparse(
source: &str, source: &str,
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner, i: &Interner,
) -> Result<Preparsed, Rc<dyn ProjectError>> { ) -> ProjectResult<Preparsed> {
// Parse with no operators // Parse with no operators
let ctx = ParsingContext::<&str>::new(&[], i, Rc::new(file.clone())); let ctx = ParsingContext::<&str>::new(&[], i, Rc::new(file.clone()));
let entries = parse::parse(source, ctx).map_err(|error| { let entries = parse::parse2(source, ctx)?;
ParseErrorWithPath {
full_source: source.to_string(),
error,
path: file.clone(),
}
.rc()
})?;
let normalized = normalize_namespaces(Box::new(entries.into_iter())) let normalized = normalize_namespaces(Box::new(entries.into_iter()))
.map_err(|ns| { .map_err(|namespace| {
VisibilityMismatch { VisibilityMismatch { namespace, file: Rc::new(file.clone()) }.rc()
namespace: ns.into_iter().map(|t| i.r(t)).cloned().collect(),
file: Rc::new(file.clone()),
}
.rc()
})?; })?;
Ok(Preparsed(to_module(&normalized, prelude))) Ok(Preparsed(to_module(&normalized, prelude)))
} }

View File

@@ -6,14 +6,17 @@
use std::hash::Hash; use std::hash::Hash;
use std::rc::Rc; use std::rc::Rc;
use hashbrown::HashSet;
use itertools::Itertools; use itertools::Itertools;
use ordered_float::NotNan; use ordered_float::NotNan;
#[allow(unused)] // for doc
use super::interpreted;
use super::location::Location; use super::location::Location;
use super::namelike::{NameLike, VName}; use super::namelike::{NameLike, VName};
use super::primitive::Primitive; use super::primitive::Primitive;
use crate::interner::{InternedDisplay, Interner, Tok}; use crate::interner::{InternedDisplay, Interner, Tok};
use crate::utils::{map_rc, Substack}; use crate::utils::map_rc;
/// A [Clause] with associated metadata /// A [Clause] with associated metadata
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@@ -25,17 +28,6 @@ pub struct Expr<N: NameLike> {
} }
impl<N: NameLike> Expr<N> { impl<N: NameLike> Expr<N> {
/// Obtain the contained clause
pub fn into_clause(self) -> Clause<N> {
self.value
}
/// Call the function on every name in this expression
pub fn visit_names(&self, binds: Substack<&N>, cb: &mut impl FnMut(&N)) {
let Expr { value, .. } = self;
value.visit_names(binds, cb);
}
/// Process all names with the given mapper. /// Process all names with the given mapper.
/// Return a new object if anything was processed /// Return a new object if anything was processed
pub fn map_names(&self, pred: &impl Fn(&N) -> Option<N>) -> Option<Self> { pub fn map_names(&self, pred: &impl Fn(&N) -> Option<N>) -> Option<Self> {
@@ -49,6 +41,32 @@ impl<N: NameLike> Expr<N> {
pub fn transform_names<O: NameLike>(self, pred: &impl Fn(N) -> O) -> Expr<O> { pub fn transform_names<O: NameLike>(self, pred: &impl Fn(N) -> O) -> Expr<O> {
Expr { value: self.value.transform_names(pred), location: self.location } Expr { value: self.value.transform_names(pred), location: self.location }
} }
/// Visit all expressions in the tree. The search can be exited early by
/// returning [Some]
///
/// See also [interpreted::ExprInst::search_all]
pub fn search_all<T>(
&self,
f: &mut impl FnMut(&Self) -> Option<T>,
) -> Option<T> {
f(self).or_else(|| self.value.search_all(f))
}
}
impl<N: NameLike> AsRef<Location> for Expr<N> {
fn as_ref(&self) -> &Location {
&self.location
}
}
/// Visit all expression sequences including this sequence itself. Otherwise
/// works exactly like [Expr::search_all_slcs]
pub fn search_all_slcs<N: NameLike, T>(
this: &[Expr<N>],
f: &mut impl FnMut(&[Expr<N>]) -> Option<T>,
) -> Option<T> {
f(this).or_else(|| this.iter().find_map(|expr| expr.value.search_all_slcs(f)))
} }
impl Expr<VName> { impl Expr<VName> {
@@ -130,7 +148,7 @@ pub enum Clause<N: NameLike> {
/// eg. `(print out "hello")`, `[1, 2, 3]`, `{Some(t) => t}` /// eg. `(print out "hello")`, `[1, 2, 3]`, `{Some(t) => t}`
S(char, Rc<Vec<Expr<N>>>), S(char, Rc<Vec<Expr<N>>>),
/// A function expression, eg. `\x. x + 1` /// A function expression, eg. `\x. x + 1`
Lambda(Rc<Expr<N>>, Rc<Vec<Expr<N>>>), Lambda(Rc<Vec<Expr<N>>>, Rc<Vec<Expr<N>>>),
/// A placeholder for macros, eg. `$name`, `...$body`, `...$lhs:1` /// A placeholder for macros, eg. `$name`, `...$body`, `...$lhs:1`
Placeh(Placeholder), Placeh(Placeholder),
} }
@@ -162,7 +180,7 @@ impl<N: NameLike> Clause<N> {
if exprs.is_empty() { if exprs.is_empty() {
None None
} else if exprs.len() == 1 { } else if exprs.len() == 1 {
Some(exprs[0].clone().into_clause()) Some(exprs[0].value.clone())
} else { } else {
Some(Self::S('(', Rc::new(exprs.to_vec()))) Some(Self::S('(', Rc::new(exprs.to_vec())))
} }
@@ -176,30 +194,22 @@ impl<N: NameLike> Clause<N> {
} }
} }
/// Recursively iterate through all "names" in an expression. /// Collect all names that appear in this expression.
/// It also finds a lot of things that aren't names, such as all /// NOTICE: this isn't the total set of unbound names, it's mostly useful to
/// bound parameters. Generally speaking, this is not a very /// make weak statements for optimization.
/// sophisticated search. pub fn collect_names(&self) -> HashSet<N> {
pub fn visit_names(&self, binds: Substack<&N>, cb: &mut impl FnMut(&N)) { if let Self::Name(n) = self {
match self { return HashSet::from([n.clone()]);
Clause::Lambda(arg, body) => {
arg.visit_names(binds, cb);
let new_binds =
if let Clause::Name(n) = &arg.value { binds.push(n) } else { binds };
for x in body.iter() {
x.visit_names(new_binds, cb)
} }
}, let mut glossary = HashSet::new();
Clause::S(_, body) => let result = self.search_all(&mut |e| {
for x in body.iter() { if let Clause::Name(n) = &e.value {
x.visit_names(binds, cb) glossary.insert(n.clone());
},
Clause::Name(name) =>
if binds.iter().all(|x| x != &name) {
cb(name)
},
_ => (),
} }
None::<()>
});
assert!(result.is_none(), "Callback never returns Some, wtf???");
glossary
} }
/// Process all names with the given mapper. /// Process all names with the given mapper.
@@ -221,10 +231,15 @@ impl<N: NameLike> Clause<N> {
if any_some { Some(Clause::S(*c, Rc::new(new_body))) } else { None } if any_some { 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 mut any_some = false;
let mut any_some = new_arg.is_some(); let new_arg = (arg.iter())
let new_body = body .map(|e| {
.iter() let val = e.map_names(pred);
any_some |= val.is_some();
val.unwrap_or_else(|| e.clone())
})
.collect();
let new_body = (body.iter())
.map(|e| { .map(|e| {
let val = e.map_names(pred); let val = e.map_names(pred);
any_some |= val.is_some(); any_some |= val.is_some();
@@ -232,10 +247,7 @@ impl<N: NameLike> Clause<N> {
}) })
.collect(); .collect();
if any_some { if any_some {
Some(Clause::Lambda( Some(Clause::Lambda(Rc::new(new_arg), Rc::new(new_body)))
new_arg.map(Rc::new).unwrap_or_else(|| arg.clone()),
Rc::new(new_body),
))
} else { } else {
None None
} }
@@ -253,7 +265,7 @@ impl<N: NameLike> Clause<N> {
Self::Placeh(p) => Clause::Placeh(p), Self::Placeh(p) => Clause::Placeh(p),
Self::P(p) => Clause::P(p), Self::P(p) => Clause::P(p),
Self::Lambda(n, b) => Clause::Lambda( Self::Lambda(n, b) => Clause::Lambda(
map_rc(n, |n| n.transform_names(pred)), map_rc(n, |n| n.into_iter().map(|e| e.transform_names(pred)).collect()),
map_rc(b, |b| b.into_iter().map(|e| e.transform_names(pred)).collect()), map_rc(b, |b| b.into_iter().map(|e| e.transform_names(pred)).collect()),
), ),
Self::S(c, b) => Clause::S( Self::S(c, b) => Clause::S(
@@ -262,6 +274,32 @@ impl<N: NameLike> Clause<N> {
), ),
} }
} }
/// Pair of [Expr::search_all]
pub fn search_all<T>(
&self,
f: &mut impl FnMut(&Expr<N>) -> Option<T>,
) -> Option<T> {
match self {
Clause::Lambda(arg, body) =>
arg.iter().chain(body.iter()).find_map(|expr| expr.search_all(f)),
Clause::Name(_) | Clause::P(_) | Clause::Placeh(_) => None,
Clause::S(_, body) => body.iter().find_map(|expr| expr.search_all(f)),
}
}
/// Pair of [Expr::search_all_slcs]
pub fn search_all_slcs<T>(
&self,
f: &mut impl FnMut(&[Expr<N>]) -> Option<T>,
) -> Option<T> {
match self {
Clause::Lambda(arg, body) =>
search_all_slcs(arg, f).or_else(|| search_all_slcs(body, f)),
Clause::Name(_) | Clause::P(_) | Clause::Placeh(_) => None,
Clause::S(_, body) => search_all_slcs(body, f),
}
}
} }
impl Clause<VName> { impl Clause<VName> {
@@ -319,7 +357,7 @@ impl<N: NameLike> InternedDisplay for Clause<N> {
}, },
Self::Lambda(arg, body) => { Self::Lambda(arg, body) => {
f.write_str("\\")?; f.write_str("\\")?;
arg.fmt_i(f, i)?; fmt_expr_seq(&mut arg.iter(), f, i)?;
f.write_str(".")?; f.write_str(".")?;
fmt_expr_seq(&mut body.iter(), f, i) fmt_expr_seq(&mut body.iter(), f, i)
}, },
@@ -360,10 +398,13 @@ impl Rule<VName> {
pub fn collect_single_names(&self) -> Vec<Tok<String>> { pub fn collect_single_names(&self) -> Vec<Tok<String>> {
let mut names = Vec::new(); let mut names = Vec::new();
for e in self.pattern.iter() { for e in self.pattern.iter() {
e.visit_names(Substack::Bottom, &mut |ns_name| { e.search_all(&mut |e| {
if let Clause::Name(ns_name) = &e.value {
if ns_name.len() == 1 { if ns_name.len() == 1 {
names.push(ns_name[0]) names.push(ns_name[0])
} }
}
None::<()>
}); });
} }
names names

View File

@@ -1,13 +1,14 @@
use std::fmt::Display;
use std::rc::Rc; use std::rc::Rc;
use super::location::Location; use super::location::Location;
use super::{ast, postmacro}; use super::{ast, postmacro};
use crate::utils::Substack; use crate::error::{ErrorPosition, ProjectError};
use crate::Sym; use crate::utils::iter::box_once;
use crate::utils::{BoxedIter, Substack};
use crate::{Interner, Sym};
#[derive(Clone)] #[derive(Debug, Clone)]
pub enum Error { pub enum ErrorKind {
/// `()` as a clause is meaningless in lambda calculus /// `()` as a clause is meaningless in lambda calculus
EmptyS, EmptyS,
/// Only `(...)` may be converted to typed lambdas. `[...]` and `{...}` /// Only `(...)` may be converted to typed lambdas. `[...]` and `{...}`
@@ -16,28 +17,43 @@ pub enum Error {
/// Placeholders shouldn't even occur in the code during macro /// Placeholders shouldn't even occur in the code during macro
/// execution. Something is clearly terribly wrong /// execution. Something is clearly terribly wrong
Placeholder, Placeholder,
/// Arguments can only be [ast::Clause::Name] /// Arguments can only be a single [ast::Clause::Name]
InvalidArg, InvalidArg,
} }
impl Display for Error { #[derive(Debug, Clone)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { pub struct Error {
match self { pub location: Location,
Error::EmptyS => { pub kind: ErrorKind,
write!(f, "`()` as a clause is meaningless in lambda calculus") }
}, impl Error {
Error::BadGroup(_) => write!( pub fn new(kind: ErrorKind, location: &Location) -> Self {
f, Self { location: location.clone(), kind }
"Only `(...)` may be converted to typed lambdas. `[...]` and \
`{{...}}` left in the code are signs of incomplete macro execution"
),
Error::Placeholder => write!(
f,
"Placeholders shouldn't even appear in the code during macro \
execution, this is likely a compiler bug"
),
Error::InvalidArg => write!(f, "Arguments can only be Name nodes"),
} }
}
impl ProjectError for Error {
fn description(&self) -> &str {
match self.kind {
ErrorKind::BadGroup(_) =>
"Only `(...)` may be converted to typed lambdas. `[...]` and `{{...}}` \
left in the code are signs of incomplete macro execution",
ErrorKind::EmptyS => "`()` as a clause is meaningless in lambda calculus",
ErrorKind::InvalidArg => "Argument names can only be Name nodes",
ErrorKind::Placeholder =>
"Placeholders shouldn't even appear in the code during macro \
execution,this is likely a compiler bug",
}
}
fn message(&self, _i: &Interner) -> String {
match self.kind {
ErrorKind::BadGroup(char) => format!("{} block found in the code", char),
_ => self.description().to_string(),
}
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.location.clone(), message: None })
} }
} }
@@ -66,14 +82,16 @@ impl<'a> Context<'a> {
/// Process an expression sequence /// Process an expression sequence
fn exprv_rec<'a>( fn exprv_rec<'a>(
location: &'a Location,
v: &'a [ast::Expr<Sym>], v: &'a [ast::Expr<Sym>],
ctx: Context<'a>, ctx: Context<'a>,
) -> Result<postmacro::Expr, Error> { ) -> Result<postmacro::Expr, Error> {
let (last, rest) = v.split_last().ok_or(Error::EmptyS)?; let (last, rest) =
(v.split_last()).ok_or_else(|| Error::new(ErrorKind::EmptyS, location))?;
if rest.is_empty() { if rest.is_empty() {
return expr_rec(&v[0], ctx); return expr_rec(&v[0], ctx);
} }
let f = exprv_rec(rest, ctx)?; let f = exprv_rec(location, rest, ctx)?;
let x = expr_rec(last, ctx)?; let x = expr_rec(last, ctx)?;
let value = postmacro::Clause::Apply(Rc::new(f), Rc::new(x)); let value = postmacro::Clause::Apply(Rc::new(f), Rc::new(x));
Ok(postmacro::Expr { value, location: Location::Unknown }) Ok(postmacro::Expr { value, location: Location::Unknown })
@@ -86,52 +104,44 @@ fn expr_rec<'a>(
) -> Result<postmacro::Expr, Error> { ) -> Result<postmacro::Expr, Error> {
if let ast::Clause::S(paren, body) = value { if let ast::Clause::S(paren, body) = value {
if *paren != '(' { if *paren != '(' {
return Err(Error::BadGroup(*paren)); return Err(Error::new(ErrorKind::BadGroup(*paren), location));
} }
let expr = exprv_rec(body.as_ref(), ctx)?; let expr = exprv_rec(location, body.as_ref(), ctx)?;
Ok(postmacro::Expr { value: expr.value, location: location.clone() }) Ok(postmacro::Expr { value: expr.value, location: location.clone() })
} else { } else {
let value = clause_rec(value, ctx)?; let value = match value {
Ok(postmacro::Expr { value, location: location.clone() }) ast::Clause::P(p) => postmacro::Clause::P(p.clone()),
} ast::Clause::Lambda(arg, b) => {
} let name = match &arg[..] {
[ast::Expr { value: ast::Clause::Name(name), .. }] => name,
/// Process a clause [ast::Expr { value: ast::Clause::Placeh { .. }, .. }] =>
fn clause_rec<'a>( return Err(Error::new(ErrorKind::Placeholder, location)),
cls: &'a ast::Clause<Sym>, _ => return Err(Error::new(ErrorKind::InvalidArg, location)),
ctx: Context<'a>,
) -> Result<postmacro::Clause, Error> {
match cls {
ast::Clause::P(p) => Ok(postmacro::Clause::P(p.clone())),
ast::Clause::Lambda(expr, b) => {
let name = match expr.value {
ast::Clause::Name(name) => name,
ast::Clause::Placeh { .. } => return Err(Error::Placeholder),
_ => return Err(Error::InvalidArg),
}; };
let body_ctx = ctx.w_name(name); let body_ctx = ctx.w_name(*name);
let body = exprv_rec(b.as_ref(), body_ctx)?; let body = exprv_rec(location, b.as_ref(), body_ctx)?;
Ok(postmacro::Clause::Lambda(Rc::new(body))) postmacro::Clause::Lambda(Rc::new(body))
}, },
ast::Clause::Name(name) => { ast::Clause::Name(name) => {
let lvl_opt = ctx let lvl_opt = (ctx.names.iter())
.names
.iter()
.enumerate() .enumerate()
.find(|(_, n)| *n == name) .find(|(_, n)| *n == name)
.map(|(lvl, _)| lvl); .map(|(lvl, _)| lvl);
Ok(match lvl_opt { match lvl_opt {
Some(lvl) => postmacro::Clause::LambdaArg(lvl), Some(lvl) => postmacro::Clause::LambdaArg(lvl),
None => postmacro::Clause::Constant(*name), None => postmacro::Clause::Constant(*name),
}) }
}, },
ast::Clause::S(paren, entries) => { ast::Clause::S(paren, entries) => {
if *paren != '(' { if *paren != '(' {
return Err(Error::BadGroup(*paren)); return Err(Error::new(ErrorKind::BadGroup(*paren), location));
} }
let expr = exprv_rec(entries.as_ref(), ctx)?; let expr = exprv_rec(location, entries.as_ref(), ctx)?;
Ok(expr.value) expr.value
}, },
ast::Clause::Placeh { .. } => Err(Error::Placeholder), ast::Clause::Placeh { .. } =>
return Err(Error::new(ErrorKind::Placeholder, location)),
};
Ok(postmacro::Expr { value, location: location.clone() })
} }
} }

View File

@@ -14,6 +14,7 @@ use crate::utils::{pushed, Substack};
/// A lightweight module tree that can be built declaratively by hand to /// A lightweight module tree that can be built declaratively by hand to
/// describe libraries of external functions in Rust. It implements [Add] for /// describe libraries of external functions in Rust. It implements [Add] for
/// added convenience /// added convenience
#[derive(Clone, Debug)]
pub enum ConstTree { pub enum ConstTree {
/// A function or constant /// A function or constant
Const(Expr<VName>), Const(Expr<VName>),
@@ -40,6 +41,29 @@ impl ConstTree {
pub fn tree(arr: impl IntoIterator<Item = (Tok<String>, Self)>) -> Self { pub fn tree(arr: impl IntoIterator<Item = (Tok<String>, Self)>) -> Self {
Self::Tree(arr.into_iter().collect()) Self::Tree(arr.into_iter().collect())
} }
/// Namespace the tree with the list of names
pub fn namespace(
pref: impl IntoIterator<Item = Tok<String>>,
data: Self,
) -> Self {
let mut iter = pref.into_iter();
if let Some(ns) = iter.next() {
Self::tree([(ns, Self::namespace(iter, data))])
} else {
data
}
}
/// Unwrap the table of subtrees from a tree
///
/// # Panics
///
/// If this is a leaf node aka. constant and not a namespace
pub fn unwrap_tree(self) -> HashMap<Tok<String>, Self> {
match self {
Self::Tree(map) => map,
_ => panic!("Attempted to unwrap leaf as tree"),
}
}
} }
impl Add for ConstTree { impl Add for ConstTree {
type Output = ConstTree; type Output = ConstTree;

View File

@@ -7,6 +7,8 @@ use std::fmt::Debug;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::rc::Rc; use std::rc::Rc;
#[allow(unused)] // for doc
use super::ast;
use super::location::Location; use super::location::Location;
use super::path_set::PathSet; use super::path_set::PathSet;
use super::primitive::Primitive; use super::primitive::Primitive;
@@ -83,9 +85,11 @@ impl ExprInst {
/// across the tree. /// across the tree.
pub fn try_normalize<T, E>( pub fn try_normalize<T, E>(
&self, &self,
mapper: impl FnOnce(&Clause) -> Result<(Clause, T), E>, mapper: impl FnOnce(&Clause, &Location) -> Result<(Clause, T), E>,
) -> Result<(Self, T), E> { ) -> Result<(Self, T), E> {
let (new_clause, extra) = mapper(&self.expr().clause)?; let expr = self.expr();
let (new_clause, extra) = mapper(&expr.clause, &expr.location)?;
drop(expr);
self.expr_mut().clause = new_clause; self.expr_mut().clause = new_clause;
Ok((self.clone(), extra)) Ok((self.clone(), extra))
} }
@@ -95,10 +99,10 @@ impl ExprInst {
/// the original but is normalized independently. /// the original but is normalized independently.
pub fn try_update<T, E>( pub fn try_update<T, E>(
&self, &self,
mapper: impl FnOnce(&Clause) -> Result<(Clause, T), E>, mapper: impl FnOnce(&Clause, &Location) -> Result<(Clause, T), E>,
) -> Result<(Self, T), E> { ) -> Result<(Self, T), E> {
let expr = self.expr(); let expr = self.expr();
let (clause, extra) = mapper(&expr.clause)?; let (clause, extra) = mapper(&expr.clause, &expr.location)?;
let new_expr = Expr { clause, location: expr.location.clone() }; let new_expr = Expr { clause, location: expr.location.clone() };
Ok((Self(Rc::new(RefCell::new(new_expr))), extra)) Ok((Self(Rc::new(RefCell::new(new_expr))), extra))
} }
@@ -123,6 +127,25 @@ impl ExprInst {
Err(NotALiteral) Err(NotALiteral)
} }
} }
/// Visit all expressions in the tree. The search can be exited early by
/// returning [Some]
///
/// See also [ast::Expr::search_all]
pub fn search_all<T>(
&self,
predicate: &mut impl FnMut(&Self) -> Option<T>,
) -> Option<T> {
if let Some(t) = predicate(self) {
return Some(t);
}
self.inspect(|c| match c {
Clause::Apply { f, x } =>
f.search_all(predicate).or_else(|| x.search_all(predicate)),
Clause::Lambda { body, .. } => body.search_all(predicate),
Clause::Constant(_) | Clause::LambdaArg | Clause::P(_) => None,
})
}
} }
impl Debug for ExprInst { impl Debug for ExprInst {

View File

@@ -39,6 +39,36 @@ impl Location {
None None
} }
} }
/// If the two locations are ranges in the same file, connect them.
/// Otherwise choose the more accurate, preferring lhs if equal.
pub fn to(self, other: Self) -> Self {
match self {
Location::Unknown => other,
Location::File(f) => match other {
Location::Range { .. } => other,
_ => Location::File(f),
},
Location::Range { file: f1, range: r1 } => Location::Range {
range: match other {
Location::Range { file: f2, range: r2 } if f1 == f2 =>
r1.start..r2.end,
_ => r1,
},
file: f1,
},
}
}
/// Choose one of the two locations, preferring better accuracy, or lhs if
/// equal
pub fn or(self, alt: Self) -> Self {
match (&self, &alt) {
(Self::Unknown, _) => alt,
(Self::File(_), Self::Range { .. }) => alt,
_ => self,
}
}
} }
impl Display for Location { impl Display for Location {

View File

@@ -1,3 +1,5 @@
use std::hash::Hash;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
/// A mutable representation of a namespaced identifier. /// A mutable representation of a namespaced identifier.
@@ -16,7 +18,7 @@ pub type Sym = Tok<Vec<Tok<String>>>;
/// An abstraction over tokenized vs non-tokenized names so that they can be /// An abstraction over tokenized vs non-tokenized names so that they can be
/// handled together in datastructures /// handled together in datastructures
pub trait NameLike: 'static + Clone + Eq { pub trait NameLike: 'static + Clone + Eq + Hash {
/// Fully resolve the name for printing /// Fully resolve the name for printing
fn to_strv(&self, i: &Interner) -> Vec<String>; fn to_strv(&self, i: &Interner) -> Vec<String>;
} }

View File

@@ -40,7 +40,7 @@ impl Import {
/// A namespace block /// A namespace block
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Namespace { pub struct ModuleBlock {
/// Name prefixed to all names in the block /// Name prefixed to all names in the block
pub name: Tok<String>, pub name: Tok<String>,
/// Prefixed entries /// Prefixed entries
@@ -56,7 +56,7 @@ pub enum Member {
/// A constant (or function) associated with a name /// A constant (or function) associated with a name
Constant(Constant), Constant(Constant),
/// A prefixed set of other entries /// A prefixed set of other entries
Namespace(Namespace), Module(ModuleBlock),
} }
/// Anything we might encounter in a file /// Anything we might encounter in a file
@@ -94,8 +94,8 @@ pub fn normalize_namespaces(
) -> Result<Vec<FileEntry>, Vec<Tok<String>>> { ) -> Result<Vec<FileEntry>, Vec<Tok<String>>> {
let (mut namespaces, mut rest) = src let (mut namespaces, mut rest) = src
.partition_map::<Vec<_>, Vec<_>, _, _, _>(|ent| match ent { .partition_map::<Vec<_>, Vec<_>, _, _, _>(|ent| match ent {
FileEntry::Exported(Member::Namespace(ns)) => Either::Left((true, ns)), FileEntry::Exported(Member::Module(ns)) => Either::Left((true, ns)),
FileEntry::Internal(Member::Namespace(ns)) => Either::Left((false, ns)), FileEntry::Internal(Member::Module(ns)) => Either::Left((false, ns)),
other => Either::Right(other), other => Either::Right(other),
}); });
// Combine namespace blocks with the same name // Combine namespace blocks with the same name
@@ -123,7 +123,7 @@ pub fn normalize_namespaces(
e.push(name); e.push(name);
e e
})?; })?;
let member = Member::Namespace(Namespace { name, body }); let member = Member::Module(ModuleBlock { name, body });
match (any_exported, any_internal) { match (any_exported, any_internal) {
(true, true) => Err(vec![name]), (true, true) => Err(vec![name]),
(true, false) => Ok(FileEntry::Exported(member)), (true, false) => Ok(FileEntry::Exported(member)),

View File

@@ -112,7 +112,7 @@ fn mk_scalar(pattern: &RuleExpr) -> ScalMatcher {
}, },
Clause::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body))), Clause::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body))),
Clause::Lambda(arg, body) => Clause::Lambda(arg, body) =>
ScalMatcher::Lambda(Box::new(mk_scalar(arg)), Box::new(mk_any(body))), ScalMatcher::Lambda(Box::new(mk_any(arg)), Box::new(mk_any(body))),
} }
} }

View File

@@ -16,7 +16,7 @@ pub fn scal_match<'a>(
(ScalMatcher::S(c1, b_mat), Clause::S(c2, body)) if c1 == c2 => (ScalMatcher::S(c1, b_mat), Clause::S(c2, body)) if c1 == c2 =>
any_match(b_mat, &body[..]), any_match(b_mat, &body[..]),
(ScalMatcher::Lambda(arg_mat, b_mat), Clause::Lambda(arg, body)) => { (ScalMatcher::Lambda(arg_mat, b_mat), Clause::Lambda(arg, body)) => {
let mut state = scal_match(arg_mat, arg)?; let mut state = any_match(arg_mat, arg)?;
state.extend(any_match(b_mat, body)?); state.extend(any_match(b_mat, body)?);
Some(state) Some(state)
}, },

View File

@@ -14,7 +14,7 @@ pub enum ScalMatcher {
P(Primitive), P(Primitive),
Name(Sym), Name(Sym),
S(char, Box<AnyMatcher>), S(char, Box<AnyMatcher>),
Lambda(Box<ScalMatcher>, Box<AnyMatcher>), Lambda(Box<AnyMatcher>, Box<AnyMatcher>),
Placeh(Tok<String>), Placeh(Tok<String>),
} }

View File

@@ -68,7 +68,7 @@ fn check_rec_expr(
if !in_template { if !in_template {
Err(RuleError::Multiple(*name)) Err(RuleError::Multiple(*name))
} else if known != typ { } else if known != typ {
Err(RuleError::TypeMismatch(*name)) Err(RuleError::ArityMismatch(*name))
} else { } else {
Ok(()) Ok(())
} }
@@ -79,7 +79,7 @@ fn check_rec_expr(
} }
}, },
Clause::Lambda(arg, body) => { Clause::Lambda(arg, body) => {
check_rec_expr(arg.as_ref(), types, in_template)?; check_rec_exprv(arg, types, in_template)?;
check_rec_exprv(body, types, in_template) check_rec_exprv(body, types, in_template)
}, },
Clause::S(_, body) => check_rec_exprv(body, types, in_template), Clause::S(_, body) => check_rec_exprv(body, types, in_template),

View File

@@ -3,6 +3,7 @@ use std::format;
use std::rc::Rc; use std::rc::Rc;
use hashbrown::HashSet; use hashbrown::HashSet;
use itertools::Itertools;
use ordered_float::NotNan; use ordered_float::NotNan;
use super::matcher::{Matcher, RuleExpr}; use super::matcher::{Matcher, RuleExpr};
@@ -11,7 +12,6 @@ use super::state::apply_exprv;
use super::{update_first_seq, RuleError, VectreeMatcher}; use super::{update_first_seq, RuleError, VectreeMatcher};
use crate::ast::Rule; use crate::ast::Rule;
use crate::interner::{InternedDisplay, Interner}; use crate::interner::{InternedDisplay, Interner};
use crate::utils::Substack;
use crate::Sym; use crate::Sym;
#[derive(Debug)] #[derive(Debug)]
@@ -60,9 +60,7 @@ impl<M: Matcher> Repository<M> {
let rule = prepare_rule(r.clone(), i).map_err(|e| (r, e))?; let rule = prepare_rule(r.clone(), i).map_err(|e| (r, e))?;
let mut glossary = HashSet::new(); let mut glossary = HashSet::new();
for e in rule.pattern.iter() { for e in rule.pattern.iter() {
e.visit_names(Substack::Bottom, &mut |op| { glossary.extend(e.value.collect_names().into_iter());
glossary.insert(*op);
})
} }
let matcher = M::new(Rc::new(rule.pattern.clone())); let matcher = M::new(Rc::new(rule.pattern.clone()));
let prep = CachedRule { let prep = CachedRule {
@@ -78,10 +76,7 @@ impl<M: Matcher> Repository<M> {
/// Attempt to run each rule in priority order once /// Attempt to run each rule in priority order once
pub fn step(&self, code: &RuleExpr) -> Option<RuleExpr> { pub fn step(&self, code: &RuleExpr) -> Option<RuleExpr> {
let mut glossary = HashSet::new(); let glossary = code.value.collect_names();
code.visit_names(Substack::Bottom, &mut |op| {
glossary.insert(*op);
});
for (rule, deps, _) in self.cache.iter() { for (rule, deps, _) in self.cache.iter() {
if !deps.is_subset(&glossary) { if !deps.is_subset(&glossary) {
continue; continue;
@@ -164,8 +159,13 @@ impl<M: InternedDisplay + Matcher> InternedDisplay for Repository<M> {
i: &Interner, i: &Interner,
) -> std::fmt::Result { ) -> std::fmt::Result {
writeln!(f, "Repository[")?; writeln!(f, "Repository[")?;
for (item, _, p) in self.cache.iter() { for (item, deps, p) in self.cache.iter() {
write!(f, "\t{}", fmt_hex(f64::from(*p)))?; write!(
f,
" priority: {}\tdependencies: [{}]\n ",
fmt_hex(f64::from(*p)),
deps.iter().map(|t| i.extern_vec(*t).join("::")).join(", ")
)?;
item.fmt_i(f, i)?; item.fmt_i(f, i)?;
writeln!(f)?; writeln!(f)?;
} }

View File

@@ -1,6 +1,13 @@
use std::fmt; use std::fmt;
use std::rc::Rc;
use hashbrown::HashSet;
use crate::ast::{self, search_all_slcs, PHClass, Placeholder, Rule};
use crate::error::{ErrorPosition, ProjectError};
use crate::interner::{InternedDisplay, Interner, Tok}; use crate::interner::{InternedDisplay, Interner, Tok};
use crate::utils::BoxedIter;
use crate::{Location, Sym};
/// Various reasons why a substitution rule may be invalid /// Various reasons why a substitution rule may be invalid
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -8,12 +15,23 @@ pub enum RuleError {
/// A key is present in the template but not the pattern /// A key is present in the template but not the pattern
Missing(Tok<String>), Missing(Tok<String>),
/// A key uses a different arity in the template and in the pattern /// A key uses a different arity in the template and in the pattern
TypeMismatch(Tok<String>), ArityMismatch(Tok<String>),
/// Multiple occurences of a placeholder in a pattern /// Multiple occurences of a placeholder in a pattern
Multiple(Tok<String>), Multiple(Tok<String>),
/// Two vectorial placeholders are next to each other /// Two vectorial placeholders are next to each other
VecNeighbors(Tok<String>, Tok<String>), VecNeighbors(Tok<String>, Tok<String>),
} }
impl RuleError {
/// Convert into a unified error trait object shared by all Orchid errors
pub fn to_project_error(self, rule: &Rule<Sym>) -> Rc<dyn ProjectError> {
match self {
RuleError::Missing(name) => Missing::new(rule, name).rc(),
RuleError::Multiple(name) => Multiple::new(rule, name).rc(),
RuleError::ArityMismatch(name) => ArityMismatch::new(rule, name).rc(),
RuleError::VecNeighbors(n1, n2) => VecNeighbors::new(rule, n1, n2).rc(),
}
}
}
impl InternedDisplay for RuleError { impl InternedDisplay for RuleError {
fn fmt_i(&self, f: &mut fmt::Formatter<'_>, i: &Interner) -> fmt::Result { fn fmt_i(&self, f: &mut fmt::Formatter<'_>, i: &Interner) -> fmt::Result {
@@ -21,7 +39,7 @@ impl InternedDisplay for RuleError {
Self::Missing(key) => { Self::Missing(key) => {
write!(f, "Key {:?} not in match pattern", i.r(key)) write!(f, "Key {:?} not in match pattern", i.r(key))
}, },
Self::TypeMismatch(key) => write!( Self::ArityMismatch(key) => write!(
f, f,
"Key {:?} used inconsistently with and without ellipsis", "Key {:?} used inconsistently with and without ellipsis",
i.r(key) i.r(key)
@@ -38,3 +56,181 @@ impl InternedDisplay for RuleError {
} }
} }
} }
/// A key is present in the template but not the pattern of a rule
#[derive(Debug)]
pub struct Missing {
locations: HashSet<Location>,
name: Tok<String>,
}
impl Missing {
pub fn new(rule: &ast::Rule<Sym>, name: Tok<String>) -> Self {
let mut locations = HashSet::new();
for expr in rule.template.iter() {
expr.search_all(&mut |e| {
if let ast::Clause::Placeh(ph) = &e.value {
if ph.name == name {
locations.insert(e.location.clone());
}
}
None::<()>
});
}
Self { locations, name }
}
}
impl ProjectError for Missing {
fn description(&self) -> &str {
"A key appears in the template but not the pattern of a rule"
}
fn message(&self, i: &Interner) -> String {
format!(
"The key {} appears in the template but not the pattern of this rule",
i.r(self.name)
)
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
Box::new(
(self.locations.iter())
.cloned()
.map(|location| ErrorPosition { location, message: None }),
)
}
}
/// A key is present multiple times in the pattern of a rule
#[derive(Debug)]
pub struct Multiple {
locations: HashSet<Location>,
name: Tok<String>,
}
impl Multiple {
pub fn new(rule: &ast::Rule<Sym>, name: Tok<String>) -> Self {
let mut locations = HashSet::new();
for expr in rule.template.iter() {
expr.search_all(&mut |e| {
if let ast::Clause::Placeh(ph) = &e.value {
if ph.name == name {
locations.insert(e.location.clone());
}
}
None::<()>
});
}
Self { locations, name }
}
}
impl ProjectError for Multiple {
fn description(&self) -> &str {
"A key appears multiple times in the pattern of a rule"
}
fn message(&self, i: &Interner) -> String {
format!("The key {} appears multiple times in this pattern", i.r(self.name))
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
Box::new(
(self.locations.iter())
.cloned()
.map(|location| ErrorPosition { location, message: None }),
)
}
}
/// A key is present multiple times in the pattern of a rule
#[derive(Debug)]
pub struct ArityMismatch {
locations: HashSet<(Location, ast::PHClass)>,
name: Tok<String>,
}
impl ArityMismatch {
pub fn new(rule: &ast::Rule<Sym>, name: Tok<String>) -> Self {
let mut locations = HashSet::new();
for expr in rule.template.iter() {
expr.search_all(&mut |e| {
if let ast::Clause::Placeh(ph) = &e.value {
if ph.name == name {
locations.insert((e.location.clone(), ph.class));
}
}
None::<()>
});
}
Self { locations, name }
}
}
impl ProjectError for ArityMismatch {
fn description(&self) -> &str {
"A key appears with different arities in a rule"
}
fn message(&self, i: &Interner) -> String {
format!(
"The key {} appears multiple times with different arities in this rule",
i.r(self.name)
)
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
Box::new((self.locations.iter()).cloned().map(|(location, class)| {
ErrorPosition {
location,
message: Some(
"This instance represents ".to_string()
+ match class {
ast::PHClass::Scalar => "one clause",
ast::PHClass::Vec { nonzero: true, .. } => "one or more clauses",
ast::PHClass::Vec { nonzero: false, .. } =>
"any number of clauses",
},
),
}
}))
}
}
/// Two vectorial placeholders appear next to each other
#[derive(Debug)]
pub struct VecNeighbors {
locations: HashSet<Location>,
n1: Tok<String>,
n2: Tok<String>,
}
impl VecNeighbors {
pub fn new(rule: &ast::Rule<Sym>, n1: Tok<String>, n2: Tok<String>) -> Self {
let mut locations = HashSet::new();
search_all_slcs(&rule.template[..], &mut |ev| {
for pair in ev.windows(2) {
let (a, b) = (&pair[0], &pair[1]);
let a_vec = matches!(a.value, ast::Clause::Placeh(
Placeholder{ class: PHClass::Vec { .. }, name }
) if name == n1);
let b_vec = matches!(b.value, ast::Clause::Placeh(
Placeholder{ class: PHClass::Vec { .. }, name }
) if name == n2);
if a_vec && b_vec {
locations.insert(a.location.clone());
locations.insert(b.location.clone());
}
}
None::<()>
});
Self { locations, n1, n2 }
}
}
impl ProjectError for VecNeighbors {
fn description(&self) -> &str {
"Two vectorial placeholders appear next to each other"
}
fn message(&self, i: &Interner) -> String {
format!(
"The keys {} and {} appear next to each other with a vectorial arity",
i.r(self.n1),
i.r(self.n2)
)
}
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
Box::new(
(self.locations.iter())
.cloned()
.map(|location| ErrorPosition { location, message: None }),
)
}
}

View File

@@ -1,7 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools;
use super::matcher::RuleExpr; use super::matcher::RuleExpr;
use crate::ast::{Clause, Expr, PHClass, Placeholder}; use crate::ast::{Clause, Expr, PHClass, Placeholder};
@@ -44,12 +43,7 @@ pub fn apply_expr(template: &RuleExpr, state: &State) -> Vec<RuleExpr> {
Clause::Lambda(arg, body) => vec![Expr { Clause::Lambda(arg, body) => vec![Expr {
location: location.clone(), location: location.clone(),
value: Clause::Lambda( value: Clause::Lambda(
Rc::new( Rc::new(apply_exprv(arg, state)),
apply_expr(arg.as_ref(), state)
.into_iter()
.exactly_one()
.expect("Lambda arguments can only ever be scalar"),
),
Rc::new(apply_exprv(&body[..], state)), Rc::new(apply_exprv(&body[..], state)),
), ),
}], }],

View File

@@ -34,8 +34,8 @@ pub fn clause<F: FnMut(Rc<Vec<RuleExpr>>) -> Option<Rc<Vec<RuleExpr>>>>(
match c { match c {
Clause::P(_) | Clause::Placeh { .. } | Clause::Name { .. } => None, Clause::P(_) | Clause::Placeh { .. } | Clause::Name { .. } => None,
Clause::Lambda(arg, body) => Clause::Lambda(arg, body) =>
if let Some(arg) = expr(arg.as_ref(), pred) { if let Some(arg) = exprv(arg.clone(), pred) {
Some(Clause::Lambda(Rc::new(arg), body.clone())) Some(Clause::Lambda(arg, body.clone()))
} else { } else {
exprv(body.clone(), pred).map(|body| Clause::Lambda(arg.clone(), body)) exprv(body.clone(), pred).map(|body| Clause::Lambda(arg.clone(), body))
}, },

View File

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

View File

@@ -1,26 +0,0 @@
import super::known::*
--[ Do nothing. Especially useful as a passive cps operation ]--
export identity := \x.x
--[
Apply the function to the given value. Can be used to assign a
concrete value in a cps assignment statement.
]--
export pass := \val.\cont. cont val
--[
Apply the function to the given pair of values. Mainly useful to assign
a concrete pair of values in a cps multi-assignment statement
]--
export pass2 := \a.\b.\cont. cont a b
--[
A function that returns the given value for any input. Also useful as a
"break" statement in a "do" block.
]--
export const := \a. \b.a
export ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix)
export ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix
export ($name) => ...$body =0x2p129=> (\$name. ...$body)
export ($name, ...$argv) => ...$body =0x2p129=> (\$name. (...$argv) => ...$body)
$name => ...$body =0x1p129=> (\$name. ...$body)

View File

@@ -1,45 +0,0 @@
use std::io::{self, Write};
use super::super::runtime_error::RuntimeError;
use crate::atomic_inert;
use crate::interpreter::{HandlerParm, HandlerRes};
use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::{Literal, Primitive};
use crate::utils::unwrap_or;
/// An IO command to be handled by the host application.
#[derive(Clone, Debug)]
pub enum IO {
/// Print a string to standard output and resume execution
Print(String, ExprInst),
/// Read a line from standard input and pass it to the calback
Readline(ExprInst),
}
atomic_inert!(IO);
/// Default xommand handler for IO actions
pub fn handle(effect: HandlerParm) -> HandlerRes {
// Downcast command
let io: &IO = unwrap_or!(effect.as_any().downcast_ref(); Err(effect)?);
// Interpret and execute
Ok(match io {
IO::Print(str, cont) => {
print!("{}", str);
io::stdout()
.flush()
.map_err(|e| RuntimeError::ext(e.to_string(), "writing to stdout"))?;
cont.clone()
},
IO::Readline(cont) => {
let mut buf = String::new();
io::stdin()
.read_line(&mut buf)
.map_err(|e| RuntimeError::ext(e.to_string(), "reading from stdin"))?;
buf.pop();
let x = Clause::P(Primitive::Literal(Literal::Str(buf))).wrap();
Clause::Apply { f: cont.clone(), x }.wrap()
},
})
}

View File

@@ -1,30 +0,0 @@
use crate::interner::Interner;
use crate::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))]),
)])
}
}

View File

@@ -1,31 +0,0 @@
use std::fmt::Debug;
use super::super::inspect::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))
}
}

View File

@@ -1,25 +0,0 @@
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))
}
}

View File

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

View File

@@ -1,70 +0,0 @@
use hashbrown::HashMap;
use rust_embed::RustEmbed;
use super::bin::bin;
use super::bool::bool;
use super::conv::conv;
use super::io::io;
use super::num::num;
use super::str::str;
use crate::interner::Interner;
use crate::pipeline::file_loader::mk_embed_cache;
use crate::pipeline::parse_layer;
use crate::representations::VName;
use crate::sourcefile::{FileEntry, Import};
use crate::{from_const_tree, ProjectTree};
/// 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
/// libraries
pub fn mk_stl(i: &Interner, options: StlOptions) -> ProjectTree<VName> {
let const_tree = from_const_tree(
HashMap::from([(
i.i("std"),
io(i, options.impure) + conv(i) + bool(i) + str(i) + num(i) + bin(i),
)]),
&[i.i("std")],
);
let ld_cache = mk_embed_cache::<StlEmbed>(".orc", i);
let targets = StlEmbed::iter()
.map(|path| {
path
.strip_suffix(".orc")
.expect("the embed is filtered for suffix")
.split('/')
.map(|segment| i.i(segment))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
parse_layer(
targets.iter().map(|v| &v[..]),
&|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,22 +0,0 @@
//! Constants exposed to usercode by the interpreter
mod arithmetic_error;
mod assertion_error;
mod bool;
mod conv;
mod io;
pub mod inspect;
mod mk_stl;
mod num;
mod runtime_error;
mod str;
pub mod codegen;
mod bin;
pub use arithmetic_error::ArithmeticError;
pub use assertion_error::AssertionError;
pub use io::{handle as handleIO, IO};
pub use mk_stl::{mk_prelude, mk_stl, StlOptions};
pub use runtime_error::RuntimeError;
pub use self::bool::Boolean;
pub use self::num::Numeric;

View File

@@ -1,5 +0,0 @@
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))

View File

@@ -1,9 +0,0 @@
import std::io::panic
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
export unwrap := \option. option (panic "value expected") \x.x

View File

@@ -1,16 +0,0 @@
import super::fn::=>
-- remove duplicate ;-s
export do { ...$statement ; ; ...$rest:1 } =0x3p130=> do { ...$statement ; ...$rest }
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 ...$names = ...$operation:1) ...$next =0x2p230=> (
(...$operation) ( (...$names) => ...$next )
)
export statement (cps ...$operation) ...$next =0x1p230=> (
(...$operation) (...$next)
)

View File

@@ -1,10 +0,0 @@
import super::(proc::*, bool::*, io::panic)
export ...$a ++ ...$b =0x4p36=> (concat (...$a) (...$b))
export char_at := \s.\i. do{
let slc = slice s i 1;
if len slc == 1
then slc
else panic "Character index out of bounds"
}

View File

@@ -0,0 +1,5 @@
mod system;
mod types;
pub use system::{AsynchConfig, InfiniteBlock};
pub use types::{Asynch, MessagePort};

View File

@@ -0,0 +1,183 @@
use std::any::{type_name, Any, TypeId};
use std::cell::RefCell;
use std::fmt::{Debug, Display};
use std::rc::Rc;
use std::sync::mpsc::Sender;
use std::time::Duration;
use hashbrown::HashMap;
use ordered_float::NotNan;
use super::types::MessagePort;
use super::Asynch;
use crate::facade::{IntoSystem, System};
use crate::foreign::cps_box::{init_cps, CPSBox};
use crate::foreign::ExternError;
use crate::interpreted::ExprInst;
use crate::interpreter::HandlerTable;
use crate::systems::codegen::call;
use crate::systems::stl::Boolean;
use crate::utils::{unwrap_or, PollEvent, Poller};
use crate::{atomic_inert, define_fn, ConstTree, Interner};
#[derive(Debug, Clone)]
struct Timer {
recurring: Boolean,
duration: NotNan<f64>,
}
define_fn! {expr=x in
SetTimer {
recurring: Boolean,
duration: NotNan<f64>
} => Ok(init_cps(2, Timer{
recurring: *recurring,
duration: *duration
}))
}
#[derive(Clone)]
struct CancelTimer(Rc<dyn Fn()>);
impl Debug for CancelTimer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "opaque cancel operation")
}
}
#[derive(Clone, Debug)]
struct Yield;
atomic_inert!(Yield, "a yield command");
/// Error indicating a yield command when all event producers and timers had
/// exited
pub struct InfiniteBlock;
impl ExternError for InfiniteBlock {}
impl Display for InfiniteBlock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
static MSG: &str = "User code yielded, but there are no timers or event \
producers to wake it up in the future";
write!(f, "{}", MSG)
}
}
impl MessagePort for Sender<Box<dyn Any + Send>> {
fn send<T: Send + 'static>(&mut self, message: T) {
let _ = Self::send(self, Box::new(message));
}
}
impl<F> MessagePort for F
where
F: FnMut(Box<dyn Any + Send>) + Send + Clone + 'static,
{
fn send<T: Send + 'static>(&mut self, message: T) {
self(Box::new(message))
}
}
type AnyHandler<'a> = Box<dyn FnMut(Box<dyn Any>) -> Option<ExprInst> + 'a>;
/// Datastructures the asynch system will eventually be constructed from
pub struct AsynchConfig<'a> {
poller: Poller<Box<dyn Any + Send>, ExprInst, ExprInst>,
sender: Sender<Box<dyn Any + Send>>,
handlers: HashMap<TypeId, AnyHandler<'a>>,
}
impl<'a> AsynchConfig<'a> {
/// Create a new async event loop that allows registering handlers and taking
/// references to the port before it's converted into a [System]
pub fn new() -> Self {
let (sender, poller) = Poller::new();
Self { poller, sender, handlers: HashMap::new() }
}
}
impl<'a> Asynch for AsynchConfig<'a> {
type Port = Sender<Box<dyn Any + Send>>;
fn register<T: 'static>(
&mut self,
mut f: impl FnMut(Box<T>) -> Option<ExprInst> + 'a,
) {
let cb = move |a: Box<dyn Any>| f(a.downcast().expect("keyed by TypeId"));
let prev = self.handlers.insert(TypeId::of::<T>(), Box::new(cb));
assert!(
prev.is_none(),
"Duplicate handlers for async event {}",
type_name::<T>()
)
}
fn get_port(&self) -> Self::Port {
self.sender.clone()
}
}
impl<'a> Default for AsynchConfig<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> IntoSystem<'a> for AsynchConfig<'a> {
fn into_system(self, i: &Interner) -> System<'a> {
let Self { mut handlers, poller, .. } = self;
let mut handler_table = HandlerTable::new();
let polly = Rc::new(RefCell::new(poller));
handler_table.register({
let polly = polly.clone();
move |t: &CPSBox<Timer>| {
let mut polly = polly.borrow_mut();
let (timeout, action, cont) = t.unpack2();
let duration = Duration::from_secs_f64(*timeout.duration);
let cancel_timer = if timeout.recurring.0 {
CancelTimer(Rc::new(polly.set_interval(duration, action.clone())))
} else {
CancelTimer(Rc::new(polly.set_timeout(duration, action.clone())))
};
Ok(call(cont.clone(), [init_cps(1, cancel_timer).wrap()]).wrap())
}
});
handler_table.register(move |t: &CPSBox<CancelTimer>| {
let (command, cont) = t.unpack1();
command.0.as_ref()();
Ok(cont.clone())
});
handler_table.register({
let polly = polly.clone();
move |_: &Yield| {
let mut polly = polly.borrow_mut();
loop {
let next = unwrap_or!(polly.run();
return Err(InfiniteBlock.into_extern())
);
match next {
PollEvent::Once(expr) => return Ok(expr),
PollEvent::Recurring(expr) => return Ok(expr),
PollEvent::Event(ev) => {
let handler = (handlers.get_mut(&ev.as_ref().type_id()))
.unwrap_or_else(|| {
panic!("Unhandled messgae type: {:?}", ev.type_id())
});
if let Some(expr) = handler(ev) {
return Ok(expr);
}
},
}
}
}
});
System {
name: vec!["system".to_string(), "asynch".to_string()],
constants: ConstTree::namespace(
[i.i("system"), i.i("async")],
ConstTree::tree([
(i.i("set_timer"), ConstTree::xfn(SetTimer)),
(i.i("yield"), ConstTree::atom(Yield)),
]),
)
.unwrap_tree(),
code: HashMap::new(),
prelude: Vec::new(),
handlers: handler_table,
}
}
}

View File

@@ -0,0 +1,30 @@
use crate::interpreted::ExprInst;
/// A thread-safe handle that can be used to send events of any type
pub trait MessagePort: Send + Clone + 'static {
/// Send an event. Any type is accepted, handlers are dispatched by type ID
fn send<T: Send + 'static>(&mut self, message: T);
}
pub trait Asynch {
/// A thread-safe handle that can be used to push events into the dispatcher
type Port: MessagePort;
/// Register a function that will be called synchronously when an event of the
/// accepted type is dispatched. Only one handler may be specified for each
/// event type. The handler may choose to process the event autonomously, or
/// return an Orchid thunk for the interpreter to execute.
///
/// # Panics
///
/// When the function is called with an argument type it was previously called
/// with
fn register<T: 'static>(
&mut self,
f: impl FnMut(Box<T>) -> Option<ExprInst> + 'static,
);
/// Return a handle that can be passed to worker threads and used to push
/// events onto the dispatcher
fn get_port(&self) -> Self::Port;
}

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