forked from Orchid/orchid
Almost Alpha
Massive improvements across the board. One day I'll adopt incremental commits.
This commit is contained in:
@@ -1,21 +1,28 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, iter};
|
||||
//! The main structure of the façade, collects systems and exposes various
|
||||
//! operations over the whole set.
|
||||
|
||||
use intern_all::{i, Tok};
|
||||
use substack::Substack;
|
||||
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 crate::error::ProjectResult;
|
||||
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, CodeLocation};
|
||||
use crate::name::{Sym, VPath};
|
||||
use crate::pipeline::load_solution::{load_solution, SolutionContext};
|
||||
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::utils::unwrap_or::unwrap_or;
|
||||
use crate::virt_fs::{DeclTree, DirNode, VirtFS};
|
||||
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]
|
||||
@@ -28,9 +35,7 @@ impl<'a> Loader<'a> {
|
||||
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()
|
||||
}
|
||||
pub fn systems(&self) -> impl Iterator<Item = &System<'a>> { self.systems.iter() }
|
||||
|
||||
/// Register a new system in the environment
|
||||
#[must_use]
|
||||
@@ -50,21 +55,22 @@ impl<'a> Loader<'a> {
|
||||
/// 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())
|
||||
})
|
||||
.try_fold(ConstTree::tree::<&str>([]), |acc, sys| acc.combine(sys.constants.clone()))
|
||||
.expect("Conflicting const trees")
|
||||
}
|
||||
|
||||
pub fn handlers(self) -> HandlerTable<'a> {
|
||||
(self.systems.into_iter())
|
||||
.fold(HandlerTable::new(), |t, sys| t.combine(sys.handlers))
|
||||
/// 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 solution_ctx(&self) -> ProjectResult<SolutionContext> {
|
||||
Ok(SolutionContext {
|
||||
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)
|
||||
}),
|
||||
@@ -72,47 +78,113 @@ impl<'a> Loader<'a> {
|
||||
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_tree(&self, dir: PathBuf) -> DeclTree {
|
||||
pub fn make_dir_fs(&self, dir: PathBuf) -> DeclTree {
|
||||
let dir_node = DirNode::new(dir, ".orc").rc();
|
||||
let base = DeclTree::tree([("tree", DeclTree::leaf(dir_node))]);
|
||||
(self.systems().try_fold(base, |acc, sub| acc.combine(sub.code.clone())))
|
||||
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,
|
||||
target: Sym,
|
||||
) -> ProjectResult<ProjectTree> {
|
||||
let ctx = self.solution_ctx()?;
|
||||
let tgt_loc =
|
||||
CodeLocation::Gen(CodeGenInfo::no_details("facade::entrypoint"));
|
||||
let root = self.make_dir_tree(dir.clone());
|
||||
let targets = iter::once((target, tgt_loc));
|
||||
let constants = self.constants().unwrap_mod();
|
||||
load_solution(ctx, targets, &constants, &root)
|
||||
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) -> ProjectResult<ProjectTree> {
|
||||
let ctx = self.solution_ctx()?;
|
||||
let tgt_loc =
|
||||
CodeLocation::Gen(CodeGenInfo::no_details("facade::entrypoint"));
|
||||
let mut orc_files: Vec<VPath> = Vec::new();
|
||||
find_all_orc_files(&dir, &mut orc_files, Substack::Bottom);
|
||||
let root = self.make_dir_tree(dir.clone());
|
||||
let constants = self.constants().unwrap_mod();
|
||||
let targets = (orc_files.into_iter())
|
||||
.map(|p| (p.as_suffix_of(i("tree")).to_sym(), tgt_loc.clone()));
|
||||
load_solution(ctx, targets, &constants, &root)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,24 +192,12 @@ impl<'a> Default for Loader<'a> {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
fn find_all_orc_files(
|
||||
path: &Path,
|
||||
paths: &mut Vec<VPath>,
|
||||
stack: Substack<'_, Tok<String>>,
|
||||
) {
|
||||
assert!(path.exists(), "find_all_orc_files encountered missing path");
|
||||
if path.is_symlink() {
|
||||
let path = unwrap_or!(fs::read_link(path).ok(); return);
|
||||
find_all_orc_files(&path, paths, stack)
|
||||
} else if path.is_file() {
|
||||
if path.extension().and_then(|t| t.to_str()) == Some("orc") {
|
||||
paths.push(VPath(stack.unreverse()))
|
||||
}
|
||||
} else if path.is_dir() {
|
||||
let entries = unwrap_or!(path.read_dir().ok(); return);
|
||||
for entry in entries.filter_map(Result::ok) {
|
||||
let name = unwrap_or!(entry.file_name().into_string().ok(); return);
|
||||
find_all_orc_files(&entry.path(), paths, stack.push(i(&name)))
|
||||
}
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user