forked from Orchid/orchid
in midst of refactor
This commit is contained in:
2
orchidlang/src/bin/cli/mod.rs
Normal file
2
orchidlang/src/bin/cli/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod prompt;
|
||||
pub use prompt::cmd_prompt;
|
||||
11
orchidlang/src/bin/cli/prompt.rs
Normal file
11
orchidlang/src/bin/cli/prompt.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use std::io::{self, Error, Write};
|
||||
|
||||
pub fn cmd_prompt(prompt: &str) -> Result<(String, Vec<String>), Error> {
|
||||
print!("{}", prompt);
|
||||
io::stdout().flush()?;
|
||||
let mut cmdln = String::new();
|
||||
io::stdin().read_line(&mut cmdln)?;
|
||||
let mut segments = cmdln.split(' ');
|
||||
let cmd = if let Some(cmd) = segments.next() { cmd } else { "" };
|
||||
Ok((cmd.to_string(), segments.map(str::to_string).collect()))
|
||||
}
|
||||
74
orchidlang/src/bin/features/macro_debug.rs
Normal file
74
orchidlang/src/bin/features/macro_debug.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use itertools::Itertools;
|
||||
use orchidlang::error::Reporter;
|
||||
use orchidlang::facade::macro_runner::MacroRunner;
|
||||
use orchidlang::libs::std::exit_status::OrcExitStatus;
|
||||
use orchidlang::location::{CodeGenInfo, CodeLocation};
|
||||
use orchidlang::name::Sym;
|
||||
use orchidlang::pipeline::project::{ItemKind, ProjItem, ProjectTree};
|
||||
use orchidlang::sym;
|
||||
|
||||
use crate::cli::cmd_prompt;
|
||||
|
||||
/// A little utility to step through the reproject of a macro set
|
||||
pub fn main(tree: ProjectTree, symbol: Sym) -> OrcExitStatus {
|
||||
print!("Macro debugger starting on {symbol}");
|
||||
let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orcx::macro_runner)));
|
||||
let expr_ent = match tree.0.walk1_ref(&[], &symbol[..], |_| true) {
|
||||
Ok((e, _)) => e.clone(),
|
||||
Err(e) => {
|
||||
eprintln!("{}", e.at(&location.origin()));
|
||||
return OrcExitStatus::Failure;
|
||||
},
|
||||
};
|
||||
let mut expr = match expr_ent.item() {
|
||||
Some(ProjItem { kind: ItemKind::Const(c) }) => c.clone(),
|
||||
_ => {
|
||||
eprintln!("macro-debug argument must be a constant");
|
||||
return OrcExitStatus::Failure;
|
||||
},
|
||||
};
|
||||
let reporter = Reporter::new();
|
||||
let macro_runner = MacroRunner::new(&tree, None, &reporter);
|
||||
reporter.assert_exit();
|
||||
println!("\nInitial state: {expr}");
|
||||
// print_for_debug(&code);
|
||||
let mut steps = macro_runner.step(expr.clone()).enumerate();
|
||||
loop {
|
||||
let (cmd, _) = cmd_prompt("\ncmd> ").unwrap();
|
||||
match cmd.trim() {
|
||||
"" | "n" | "next" => match steps.next() {
|
||||
None => print!("Halted"),
|
||||
Some((idx, c)) => {
|
||||
expr = c;
|
||||
print!("Step {idx}: {expr}");
|
||||
},
|
||||
},
|
||||
"p" | "print" => {
|
||||
let glossary = expr.value.collect_names();
|
||||
let gl_str = glossary.iter().join(", ");
|
||||
print!("code: {expr}\nglossary: {gl_str}")
|
||||
},
|
||||
"d" | "dump" => print!("Rules: {}", macro_runner.repo),
|
||||
"q" | "quit" => return OrcExitStatus::Success,
|
||||
"complete" => {
|
||||
match steps.last() {
|
||||
Some((idx, c)) => print!("Step {idx}: {c}"),
|
||||
None => print!("Already halted"),
|
||||
}
|
||||
return OrcExitStatus::Success;
|
||||
},
|
||||
"h" | "help" => print!(
|
||||
"Available commands:
|
||||
\t<blank>, n, next\t\ttake a step
|
||||
\tp, print\t\tprint the current state
|
||||
\td, dump\t\tprint the rule table
|
||||
\tq, quit\t\texit
|
||||
\th, help\t\tprint this text"
|
||||
),
|
||||
_ => {
|
||||
print!("unrecognized command \"{}\", try \"help\"", cmd);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
4
orchidlang/src/bin/features/mod.rs
Normal file
4
orchidlang/src/bin/features/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod macro_debug;
|
||||
pub mod print_project;
|
||||
pub mod shared;
|
||||
pub mod tests;
|
||||
55
orchidlang/src/bin/features/print_project.rs
Normal file
55
orchidlang/src/bin/features/print_project.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use itertools::Itertools;
|
||||
use orchidlang::pipeline::project::{ItemKind, ProjItem, ProjectMod};
|
||||
use orchidlang::tree::{ModEntry, ModMember};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ProjPrintOpts {
|
||||
pub width: u16,
|
||||
pub hide_locations: bool,
|
||||
}
|
||||
|
||||
fn indent(amount: u16) -> String { " ".repeat(amount.into()) }
|
||||
|
||||
pub fn print_proj_mod(module: &ProjectMod, lvl: u16, opts: ProjPrintOpts) -> String {
|
||||
let mut acc = String::new();
|
||||
let tab = indent(lvl);
|
||||
for (key, ModEntry { member, x }) in &module.entries {
|
||||
let mut line_acc = String::new();
|
||||
for c in &x.comments {
|
||||
line_acc += &format!("{tab}, --[|{}|]--\n", c);
|
||||
}
|
||||
if x.exported {
|
||||
line_acc += &format!("{tab}export ");
|
||||
} else {
|
||||
line_acc += &tab
|
||||
}
|
||||
match member {
|
||||
ModMember::Sub(module) => {
|
||||
line_acc += &format!("module {key} {{\n");
|
||||
line_acc += &print_proj_mod(module, lvl + 1, opts);
|
||||
line_acc += &format!("{tab}}}");
|
||||
},
|
||||
ModMember::Item(ProjItem { kind: ItemKind::None }) => {
|
||||
line_acc += &format!("keyword {key}");
|
||||
},
|
||||
ModMember::Item(ProjItem { kind: ItemKind::Alias(tgt) }) => {
|
||||
line_acc += &format!("alias {key} => {tgt}");
|
||||
},
|
||||
ModMember::Item(ProjItem { kind: ItemKind::Const(val) }) => {
|
||||
line_acc += &format!("const {key} := {val}");
|
||||
},
|
||||
}
|
||||
if !x.locations.is_empty() && !opts.hide_locations {
|
||||
let locs = x.locations.iter().map(|l| l.to_string()).join(", ");
|
||||
let line_len = line_acc.split('\n').last().unwrap().len();
|
||||
match usize::from(opts.width).checked_sub(locs.len() + line_len + 4) {
|
||||
Some(padding) => line_acc += &" ".repeat(padding),
|
||||
None => line_acc += &format!("\n{tab} @ "),
|
||||
}
|
||||
line_acc += &locs;
|
||||
}
|
||||
line_acc += "\n";
|
||||
acc += &line_acc
|
||||
}
|
||||
acc
|
||||
}
|
||||
64
orchidlang/src/bin/features/shared.rs
Normal file
64
orchidlang/src/bin/features/shared.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use std::io::BufReader;
|
||||
use std::thread;
|
||||
|
||||
use orchidlang::facade::loader::Loader;
|
||||
use orchidlang::libs::asynch::system::AsynchSystem;
|
||||
use orchidlang::libs::directfs::DirectFS;
|
||||
use orchidlang::libs::io::{IOService, Sink, Source, Stream};
|
||||
use orchidlang::libs::scheduler::system::SeqScheduler;
|
||||
use orchidlang::libs::std::std_system::StdConfig;
|
||||
|
||||
pub fn stdin_source() -> Source { BufReader::new(Box::new(std::io::stdin())) }
|
||||
pub fn stdout_sink() -> Sink { Box::new(std::io::stdout()) }
|
||||
pub fn stderr_sink() -> Sink { Box::new(std::io::stderr()) }
|
||||
|
||||
pub fn with_std_env<T>(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T {
|
||||
with_env(stdin_source(), stdout_sink(), stderr_sink(), cb)
|
||||
}
|
||||
|
||||
pub fn with_env<T>(
|
||||
stdin: Source,
|
||||
stdout: Sink,
|
||||
stderr: Sink,
|
||||
cb: impl for<'a> FnOnce(Loader<'a>) -> T,
|
||||
) -> T {
|
||||
let mut asynch = AsynchSystem::new();
|
||||
let scheduler = SeqScheduler::new(&mut asynch);
|
||||
let std_streams = [
|
||||
("stdin", Stream::Source(stdin)),
|
||||
("stdout", Stream::Sink(stdout)),
|
||||
("stderr", Stream::Sink(stderr)),
|
||||
];
|
||||
let env = Loader::new()
|
||||
.add_system(StdConfig { impure: true })
|
||||
.add_system(asynch)
|
||||
.add_system(scheduler.clone())
|
||||
.add_system(IOService::new(scheduler.clone(), std_streams))
|
||||
.add_system(DirectFS::new(scheduler));
|
||||
cb(env)
|
||||
}
|
||||
|
||||
pub fn worker_cnt() -> usize { thread::available_parallelism().map(usize::from).unwrap_or(1) }
|
||||
|
||||
macro_rules! unwrap_exit {
|
||||
($param:expr) => {
|
||||
match $param {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return ExitCode::FAILURE;
|
||||
},
|
||||
}
|
||||
};
|
||||
($param:expr; $error:expr) => {
|
||||
match $param {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return $error;
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use unwrap_exit;
|
||||
111
orchidlang/src/bin/features/tests.rs
Normal file
111
orchidlang/src/bin/features/tests.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::fmt;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use orchidlang::error::{ProjectError, ProjectResult, Reporter};
|
||||
use orchidlang::facade::loader::Loader;
|
||||
use orchidlang::facade::macro_runner::MacroRunner;
|
||||
use orchidlang::facade::merge_trees::NortConst;
|
||||
use orchidlang::facade::process::Process;
|
||||
use orchidlang::foreign::error::{RTError, RTErrorObj, RTResult};
|
||||
use orchidlang::foreign::inert::Inert;
|
||||
use orchidlang::interpreter::error::RunError;
|
||||
use orchidlang::interpreter::nort;
|
||||
use orchidlang::libs::io::{Sink, Source};
|
||||
use orchidlang::libs::std::exit_status::OrcExitStatus;
|
||||
use orchidlang::name::Sym;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use rayon::slice::ParallelSlice;
|
||||
|
||||
use super::shared::{with_env, worker_cnt};
|
||||
|
||||
pub fn mock_source() -> Source { BufReader::new(Box::new(&[][..])) }
|
||||
pub fn mock_sink() -> Sink { Box::<Vec<u8>>::default() }
|
||||
pub fn with_mock_env<T>(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T {
|
||||
with_env(mock_source(), mock_sink(), mock_sink(), cb)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestDidNotHalt(Sym);
|
||||
impl RTError for TestDidNotHalt {}
|
||||
impl fmt::Display for TestDidNotHalt {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Test {} did not halt", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestDidNotSucceed(Sym, nort::Expr);
|
||||
impl RTError for TestDidNotSucceed {}
|
||||
impl fmt::Display for TestDidNotSucceed {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Test {} settled on {}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_test(proc: &mut Process, name: Sym, data: NortConst) -> RTResult<()> {
|
||||
let res = proc.run(data.value, Some(10_000)).map_err(|e| match e {
|
||||
RunError::Extern(e) => e,
|
||||
RunError::Interrupted(_) => TestDidNotHalt(name.clone()).pack(),
|
||||
})?;
|
||||
match res.clone().downcast()? {
|
||||
Inert(OrcExitStatus::Success) => Ok(()),
|
||||
_ => Err(TestDidNotSucceed(name, res).pack()),
|
||||
}
|
||||
}
|
||||
pub fn run_tests(
|
||||
dir: &Path,
|
||||
macro_limit: usize,
|
||||
threads: Option<usize>,
|
||||
tests: &[(Sym, NortConst)],
|
||||
) -> ProjectResult<()> {
|
||||
with_mock_env(|env| {
|
||||
let reporter = Reporter::new();
|
||||
env.proc_dir(dir.to_owned(), true, Some(macro_limit), &reporter);
|
||||
reporter.bind()
|
||||
})?;
|
||||
let threads = threads.unwrap_or_else(worker_cnt);
|
||||
rayon::ThreadPoolBuilder::new().num_threads(threads).build_global().unwrap();
|
||||
let batch_size = tests.len().div_ceil(threads);
|
||||
let errors = (tests.par_chunks(batch_size))
|
||||
.map(|tests| {
|
||||
with_mock_env(|env| {
|
||||
let reporter = Reporter::new();
|
||||
let mut proc = env.proc_dir(dir.to_owned(), true, Some(macro_limit), &reporter);
|
||||
reporter.assert(); // checked above
|
||||
(tests.iter())
|
||||
.filter_map(|(test, constant)| {
|
||||
Some((test.clone(), run_test(&mut proc, test.clone(), constant.clone()).err()?))
|
||||
})
|
||||
.collect_vec()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<HashMap<_, _>>();
|
||||
if errors.is_empty() { Ok(()) } else { Err(TestsFailed(errors).pack()) }
|
||||
}
|
||||
|
||||
pub struct TestsFailed(HashMap<Sym, RTErrorObj>);
|
||||
impl ProjectError for TestsFailed {
|
||||
const DESCRIPTION: &'static str = "Various tests failed";
|
||||
fn message(&self) -> String {
|
||||
([format!("{} tests failed. Errors:", self.0.len())].into_iter())
|
||||
.chain(self.0.iter().map(|(k, e)| format!("In {k}, {e}")))
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tree_tests(dir: &Path, reporter: &Reporter) -> ProjectResult<Vec<(Sym, NortConst)>> {
|
||||
with_mock_env(|env| {
|
||||
let tree = env.load_dir(dir.to_owned(), reporter);
|
||||
let tree = MacroRunner::new(&tree, Some(10_000), reporter).run_macros(tree, reporter);
|
||||
(tree.all_consts().into_iter())
|
||||
.filter(|(_, rep)| rep.comments.iter().any(|s| s.trim() == "test"))
|
||||
.map(|(k, v)| Ok((k.clone(), NortConst::convert_from(v, reporter))))
|
||||
.collect::<ProjectResult<Vec<_>>>()
|
||||
})
|
||||
}
|
||||
283
orchidlang/src/bin/orcx.rs
Normal file
283
orchidlang/src/bin/orcx.rs
Normal file
@@ -0,0 +1,283 @@
|
||||
mod cli;
|
||||
mod features;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use hashbrown::HashSet;
|
||||
use itertools::Itertools;
|
||||
use never::Never;
|
||||
use orchidlang::error::Reporter;
|
||||
use orchidlang::facade::macro_runner::MacroRunner;
|
||||
use orchidlang::facade::merge_trees::{merge_trees, NortConst};
|
||||
use orchidlang::facade::process::Process;
|
||||
use orchidlang::foreign::inert::Inert;
|
||||
use orchidlang::gen::tpl;
|
||||
use orchidlang::gen::traits::Gen;
|
||||
use orchidlang::interpreter::gen_nort::nort_gen;
|
||||
use orchidlang::interpreter::nort::{self};
|
||||
use orchidlang::libs::std::exit_status::OrcExitStatus;
|
||||
use orchidlang::libs::std::string::OrcString;
|
||||
use orchidlang::location::{CodeGenInfo, CodeLocation, SourceRange};
|
||||
use orchidlang::name::Sym;
|
||||
use orchidlang::parse::context::FlatLocContext;
|
||||
use orchidlang::parse::lexer::{lex, Lexeme};
|
||||
use orchidlang::sym;
|
||||
use orchidlang::tree::{ModMemberRef, TreeTransforms};
|
||||
use orchidlang::virt_fs::{decl_file, DeclTree};
|
||||
|
||||
use crate::features::macro_debug;
|
||||
use crate::features::print_project::{print_proj_mod, ProjPrintOpts};
|
||||
use crate::features::shared::{stderr_sink, stdout_sink, unwrap_exit, with_env, with_std_env};
|
||||
use crate::features::tests::{get_tree_tests, mock_source, run_test, run_tests, with_mock_env};
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
/// Run unit tests, any constant annotated --[[ test ]]--
|
||||
Test {
|
||||
/// Specify an exact test to run
|
||||
#[arg(long)]
|
||||
only: Option<String>,
|
||||
#[arg(long, short)]
|
||||
threads: Option<usize>,
|
||||
#[arg(long)]
|
||||
system: Option<String>,
|
||||
},
|
||||
#[command(arg_required_else_help = true)]
|
||||
MacroDebug {
|
||||
#[arg(long, short)]
|
||||
symbol: String,
|
||||
},
|
||||
ListMacros,
|
||||
ProjectTree {
|
||||
#[arg(long, default_value_t = false)]
|
||||
hide_locations: bool,
|
||||
#[arg(long)]
|
||||
width: Option<u16>,
|
||||
},
|
||||
Repl,
|
||||
}
|
||||
/// Orchid interpreter
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "Orchid Executor")]
|
||||
#[command(author = "Lawrence Bethlenfalvy <lbfalvy@protonmail.com>")]
|
||||
#[command(long_about = Some("Execute Orchid projects from the file system"))]
|
||||
struct Args {
|
||||
/// Folder containing main.orc or the manually specified entry module
|
||||
#[arg(short, long, default_value = ".")]
|
||||
pub dir: String,
|
||||
/// Alternative entrypoint for the interpreter
|
||||
#[arg(short, long)]
|
||||
pub main: Option<String>,
|
||||
/// Maximum number of steps taken by the macro executor
|
||||
#[arg(long, default_value_t = 10_000)]
|
||||
pub macro_limit: usize,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
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 = match &self.main {
|
||||
Some(s) => s.split("::").collect::<Vec<_>>(),
|
||||
None => match File::open("./main.orc") {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => return Err(format!("Cannot open './main.orc'\n{e}")),
|
||||
},
|
||||
};
|
||||
if segs.len() < 2 {
|
||||
return Err("Entry point too short".to_string());
|
||||
};
|
||||
let (_, pathsegs) = segs.split_last().unwrap();
|
||||
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()) {
|
||||
let out_path = pathsegs.join("::");
|
||||
let pbuf = PathBuf::from(&self.dir);
|
||||
return Err(format!("{out_path} not found in {}", pbuf.display()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn chk_proj(&self) -> Result<(), String> { self.chk_dir_main() }
|
||||
}
|
||||
|
||||
pub fn main() -> ExitCode {
|
||||
let args = Args::parse();
|
||||
unwrap_exit!(args.chk_proj());
|
||||
let dir = PathBuf::from(args.dir);
|
||||
let main_s = args.main.as_ref().map_or("tree::main::main", |s| s);
|
||||
let main = Sym::parse(main_s).expect("--main cannot be empty");
|
||||
let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orcx::entrypoint)));
|
||||
let reporter = Reporter::new();
|
||||
|
||||
// subcommands
|
||||
#[allow(clippy::blocks_in_conditions)]
|
||||
match args.command {
|
||||
Some(Command::ListMacros) => with_mock_env(|env| {
|
||||
let tree = env.load_main(dir, [main], &reporter);
|
||||
let mr = MacroRunner::new(&tree, None, &reporter);
|
||||
println!("Parsed rules: {}", mr.repo);
|
||||
ExitCode::SUCCESS
|
||||
}),
|
||||
Some(Command::ProjectTree { hide_locations, width }) => {
|
||||
let tree = with_mock_env(|env| env.load_main(dir, [main], &reporter));
|
||||
let w = width.or_else(|| termsize::get().map(|s| s.cols)).unwrap_or(74);
|
||||
let print_opts = ProjPrintOpts { width: w, hide_locations };
|
||||
println!("Project tree: {}", print_proj_mod(&tree.0, 0, print_opts));
|
||||
ExitCode::SUCCESS
|
||||
},
|
||||
Some(Command::MacroDebug { symbol }) => with_mock_env(|env| {
|
||||
let tree = env.load_main(dir, [main], &reporter);
|
||||
let symbol = Sym::parse(&symbol).expect("macro-debug needs an argument");
|
||||
macro_debug::main(tree, symbol).code()
|
||||
}),
|
||||
Some(Command::Test { only: Some(_), threads: Some(_), .. }) => {
|
||||
eprintln!(
|
||||
"Each test case runs in a single thread.
|
||||
--only and --threads cannot both be specified"
|
||||
);
|
||||
ExitCode::FAILURE
|
||||
},
|
||||
Some(Command::Test { only: Some(_), system: Some(_), .. }) => {
|
||||
eprintln!(
|
||||
"Conflicting test filters applied. --only runs a single test by
|
||||
symbol name, while --system runs all tests in a system"
|
||||
);
|
||||
ExitCode::FAILURE
|
||||
},
|
||||
Some(Command::Test { only: None, threads, system: None }) => {
|
||||
let tree_tests = reporter.unwrap_exit(get_tree_tests(&dir, &reporter));
|
||||
unwrap_exit!(run_tests(&dir, args.macro_limit, threads, &tree_tests));
|
||||
ExitCode::SUCCESS
|
||||
},
|
||||
Some(Command::Test { only: Some(symbol), threads: None, system: None }) => {
|
||||
let symbol = Sym::parse(&symbol).expect("Test needs an argument");
|
||||
with_env(mock_source(), stdout_sink(), stderr_sink(), |env| {
|
||||
// iife in lieu of try blocks
|
||||
let tree = env.load_main(dir.clone(), [symbol.clone()], &reporter);
|
||||
let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter);
|
||||
let consts = mr.run_macros(tree, &reporter).all_consts();
|
||||
let test = consts.get(&symbol).expect("Test not found");
|
||||
let nc = NortConst::convert_from(test.clone(), &reporter);
|
||||
let mut proc = Process::new(merge_trees(consts, env.systems(), &reporter), env.handlers());
|
||||
unwrap_exit!(run_test(&mut proc, symbol.clone(), nc.clone()));
|
||||
ExitCode::SUCCESS
|
||||
})
|
||||
},
|
||||
Some(Command::Test { only: None, threads, system: Some(system) }) => {
|
||||
let subtrees = unwrap_exit!(with_mock_env(|env| {
|
||||
match env.systems().find(|s| s.name == system) {
|
||||
None => Err(format!("System {system} not found")),
|
||||
Some(sys) => {
|
||||
let mut paths = HashSet::new();
|
||||
sys.code.search_all((), |path, node, ()| {
|
||||
if matches!(node, ModMemberRef::Item(_)) {
|
||||
let name = Sym::new(path.unreverse()).expect("Empty path means global file");
|
||||
paths.insert(name);
|
||||
}
|
||||
});
|
||||
Ok(paths)
|
||||
},
|
||||
}
|
||||
}));
|
||||
let in_subtrees = |sym: Sym| subtrees.iter().any(|sub| sym[..].starts_with(&sub[..]));
|
||||
let tests = with_mock_env(|env| {
|
||||
let tree = env.load_main(dir.clone(), [main.clone()], &reporter);
|
||||
let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter);
|
||||
let src_consts = mr.run_macros(tree, &reporter).all_consts();
|
||||
let consts = merge_trees(src_consts, env.systems(), &reporter);
|
||||
(consts.into_iter())
|
||||
.filter(|(k, v)| in_subtrees(k.clone()) && v.comments.iter().any(|c| c.trim() == "test"))
|
||||
.collect_vec()
|
||||
});
|
||||
eprintln!("Running {} tests", tests.len());
|
||||
unwrap_exit!(run_tests(&dir, args.macro_limit, threads, &tests));
|
||||
eprintln!("All tests pass");
|
||||
ExitCode::SUCCESS
|
||||
},
|
||||
None => with_std_env(|env| {
|
||||
let proc = env.proc_main(dir, [main.clone()], true, Some(args.macro_limit), &reporter);
|
||||
reporter.assert_exit();
|
||||
let ret = unwrap_exit!(proc.run(nort::Clause::Constant(main).into_expr(location), None));
|
||||
drop(proc);
|
||||
match ret.clone().downcast() {
|
||||
Ok(Inert(OrcExitStatus::Success)) => ExitCode::SUCCESS,
|
||||
Ok(Inert(OrcExitStatus::Failure)) => ExitCode::FAILURE,
|
||||
Err(_) => {
|
||||
println!("{}", ret.clause);
|
||||
ExitCode::SUCCESS
|
||||
},
|
||||
}
|
||||
}),
|
||||
Some(Command::Repl) => with_std_env(|env| {
|
||||
let sctx = env.project_ctx(&reporter);
|
||||
loop {
|
||||
let reporter = Reporter::new();
|
||||
print!("orc");
|
||||
let mut src = String::new();
|
||||
let mut paren_tally = 0;
|
||||
loop {
|
||||
print!("> ");
|
||||
stdout().flush().unwrap();
|
||||
let mut buf = String::new();
|
||||
stdin().read_line(&mut buf).unwrap();
|
||||
src += &buf;
|
||||
let range = SourceRange::mock();
|
||||
let spctx = sctx.parsing(range.code());
|
||||
let pctx = FlatLocContext::new(&spctx, &range);
|
||||
let res =
|
||||
lex(Vec::new(), &buf, &pctx, |_| Ok::<_, Never>(false)).unwrap_or_else(|e| match e {});
|
||||
res.tokens.iter().for_each(|e| match &e.lexeme {
|
||||
Lexeme::LP(_) => paren_tally += 1,
|
||||
Lexeme::RP(_) => paren_tally -= 1,
|
||||
_ => (),
|
||||
});
|
||||
if 0 == paren_tally {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let tree = env.load_project_main(
|
||||
[sym!(tree::main::__repl_input__)],
|
||||
DeclTree::ns("tree::main", [decl_file(&format!("const __repl_input__ := {src}"))]),
|
||||
&reporter,
|
||||
);
|
||||
let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter);
|
||||
let proj_consts = mr.run_macros(tree, &reporter).all_consts();
|
||||
let consts = merge_trees(proj_consts, env.systems(), &reporter);
|
||||
let ctx = nort_gen(location.clone());
|
||||
let to_string_tpl = tpl::A(tpl::C("std::string::convert"), tpl::Slot);
|
||||
if let Err(err) = reporter.bind() {
|
||||
eprintln!("{err}");
|
||||
continue;
|
||||
}
|
||||
let proc = Process::new(consts, env.handlers());
|
||||
let prompt = tpl::C("tree::main::__repl_input__").template(ctx.clone(), []);
|
||||
let out = match proc.run(prompt, Some(1000)) {
|
||||
Ok(out) => out,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
if let Ok(out) = proc.run(to_string_tpl.template(ctx, [out.clone()]), Some(1000)) {
|
||||
if let Ok(s) = out.clone().downcast::<Inert<OrcString>>() {
|
||||
println!("{}", s.0.as_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
println!("{out}")
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user