Files
orchid/src/facade/loader.rs
Lawrence Bethlenfalvy ed0d64d52e Almost Alpha
Massive improvements across the board.

One day I'll adopt incremental commits.
2024-02-24 00:31:00 +00:00

204 lines
7.4 KiB
Rust

//! The main structure of the façade, collects systems and exposes various
//! operations over the whole set.
use std::borrow::Borrow;
use std::path::PathBuf;
use intern_all::i;
use super::macro_runner::MacroRunner;
use super::merge_trees::merge_trees;
use super::process::Process;
use super::system::{IntoSystem, System};
use super::unbound_ref::validate_refs;
use crate::error::Reporter;
use crate::gen::tree::ConstTree;
use crate::interpreter::context::RunEnv;
use crate::interpreter::handler::HandlerTable;
use crate::location::{CodeGenInfo, CodeOrigin};
use crate::name::{PathSlice, Sym, VPath};
use crate::pipeline::load_project::{load_project, ProjectContext};
use crate::pipeline::project::ProjectTree;
use crate::sym;
use crate::utils::combine::Combine;
use crate::utils::sequence::Sequence;
use crate::virt_fs::{DeclTree, DirNode, Loaded, VirtFS};
/// A compiled environment ready to load user code. It stores the list of
/// systems and combines with usercode to produce a [Process]
pub struct Loader<'a> {
systems: Vec<System<'a>>,
}
impl<'a> Loader<'a> {
/// Initialize a new environment
#[must_use]
pub fn new() -> Self { Self { systems: Vec::new() } }
/// Retrieve the list of systems
pub fn systems(&self) -> impl Iterator<Item = &System<'a>> { self.systems.iter() }
/// Register a new system in the environment
#[must_use]
pub fn add_system<'b: 'a>(mut self, is: impl IntoSystem<'b> + 'b) -> Self {
self.systems.push(Box::new(is).into_system());
self
}
/// Extract the systems from the environment
pub fn into_systems(self) -> Vec<System<'a>> { self.systems }
/// Initialize an environment with a prepared list of systems
pub fn from_systems(sys: impl IntoIterator<Item = System<'a>>) -> Self {
Self { systems: sys.into_iter().collect() }
}
/// Combine the `constants` fields of all systems
pub fn constants(&self) -> ConstTree {
(self.systems())
.try_fold(ConstTree::tree::<&str>([]), |acc, sys| acc.combine(sys.constants.clone()))
.expect("Conflicting const trees")
}
/// Extract the command handlers from the systems, consuming the loader in the
/// process. This has to consume the systems because handler tables aren't
/// Copy. It also establishes the practice that environments live on the
/// stack.
pub fn handlers(&self) -> HandlerTable<'_> {
(self.systems.iter()).fold(HandlerTable::new(), |t, sys| t.link(&sys.handlers))
}
/// Compile the environment from the set of systems and return it directly.
/// See [#load_dir]
pub fn project_ctx<'b>(&self, reporter: &'b Reporter) -> ProjectContext<'_, 'b> {
ProjectContext {
lexer_plugins: Sequence::new(|| {
self.systems().flat_map(|sys| &sys.lexer_plugins).map(|b| &**b)
}),
line_parsers: Sequence::new(|| {
self.systems().flat_map(|sys| &sys.line_parsers).map(|b| &**b)
}),
preludes: Sequence::new(|| self.systems().flat_map(|sys| &sys.prelude)),
reporter,
}
}
/// Combine source code from all systems with the specified directory into a
/// common [VirtFS]
pub fn make_dir_fs(&self, dir: PathBuf) -> DeclTree {
let dir_node = DirNode::new(dir, ".orc").rc();
DeclTree::tree([("tree", DeclTree::leaf(dir_node))])
}
/// All system trees merged into one
pub fn system_fs(&self) -> DeclTree {
(self.systems().try_fold(DeclTree::empty(), |acc, sub| acc.combine(sub.code.clone())))
.expect("Conflicting system trees")
}
/// A wrapper around [load_project] that only takes the arguments that aren't
/// fully specified by systems
pub fn load_project_main(
&self,
entrypoints: impl IntoIterator<Item = Sym>,
root: DeclTree,
reporter: &Reporter,
) -> ProjectTree {
let tgt_loc = CodeOrigin::Gen(CodeGenInfo::no_details(sym!(facade::entrypoint)));
let constants = self.constants().unwrap_mod();
let targets = entrypoints.into_iter().map(|s| (s, tgt_loc.clone()));
let root = self.system_fs().combine(root).expect("System trees conflict with root");
load_project(&self.project_ctx(reporter), targets, &constants, &root)
}
/// A wrapper around [load_project] that only takes the arguments that aren't
/// fully specified by systems
pub fn load_project(&self, root: DeclTree, reporter: &Reporter) -> ProjectTree {
let mut orc_files: Vec<VPath> = Vec::new();
find_all_orc_files([].borrow(), &mut orc_files, &root);
let entrypoints = (orc_files.into_iter()).map(|p| p.name_with_suffix(i!(str: "tree")).to_sym());
let tgt_loc = CodeOrigin::Gen(CodeGenInfo::no_details(sym!(facade::entrypoint)));
let constants = self.constants().unwrap_mod();
let targets = entrypoints.into_iter().map(|s| (s, tgt_loc.clone()));
let root = self.system_fs().combine(root).expect("System trees conflict with root");
load_project(&self.project_ctx(reporter), targets, &constants, &root)
}
/// Load a directory from the local file system as an Orchid project.
/// File loading proceeds along import statements and ignores all files
/// not reachable from the specified file.
pub fn load_main(
&self,
dir: PathBuf,
targets: impl IntoIterator<Item = Sym>,
reporter: &Reporter,
) -> ProjectTree {
self.load_project_main(targets, self.make_dir_fs(dir), reporter)
}
/// Load every orchid file in a directory
pub fn load_dir(&self, dir: PathBuf, reporter: &Reporter) -> ProjectTree {
self.load_project(self.make_dir_fs(dir), reporter)
}
/// Build a process by calling other utilities in [crate::facade]. A sort of
/// facade over the facade. If you need a custom file system, consider
/// combining this with [Loader::load_project]. For usage with
/// [Loader::load_main] and [Loader::load_dir] we offer the shorthands
/// [Loader::proc_main] and [Loader::proc_dir].
pub fn proc(
&'a self,
tree: ProjectTree,
check_refs: bool,
macro_limit: Option<usize>,
reporter: &Reporter,
) -> Process<'a> {
let mr = MacroRunner::new(&tree, macro_limit, reporter);
let pm_tree = mr.run_macros(tree, reporter);
let consts = merge_trees(pm_tree.all_consts(), self.systems(), reporter);
if check_refs {
validate_refs(consts.keys().cloned().collect(), reporter, &mut |sym, location| {
(consts.get(&sym).map(|nc| nc.value.clone()))
.ok_or_else(|| RunEnv::sym_not_found(sym, location))
});
}
Process::new(consts, self.handlers())
}
/// Load a project and process everything
pub fn proc_dir(
&'a self,
dir: PathBuf,
check_refs: bool,
macro_limit: Option<usize>,
reporter: &Reporter,
) -> Process<'a> {
self.proc(self.load_dir(dir.to_owned(), reporter), check_refs, macro_limit, reporter)
}
/// Load a project and process everything to load specific symbols
pub fn proc_main(
&'a self,
dir: PathBuf,
targets: impl IntoIterator<Item = Sym>,
check_refs: bool,
macro_limit: Option<usize>,
reporter: &Reporter,
) -> Process<'a> {
self.proc(self.load_main(dir.to_owned(), targets, reporter), check_refs, macro_limit, reporter)
}
}
impl<'a> Default for Loader<'a> {
fn default() -> Self { Self::new() }
}
fn find_all_orc_files(path: &PathSlice, paths: &mut Vec<VPath>, vfs: &impl VirtFS) {
match vfs.read(path) {
Err(_) => (),
Ok(Loaded::Code(_)) => paths.push(path.to_vpath()),
Ok(Loaded::Collection(items)) => items
.iter()
.for_each(|suffix| find_all_orc_files(&path.to_vpath().suffix([suffix.clone()]), paths, vfs)),
}
}