task_local context over context objects

- interner impls logically separate from API in orchid-base (default host interner still in base for testing)
- error reporting, logging, and a variety of other features passed down via context in extension, not yet in host to maintain library-ish profile, should consider options
- no global spawn mechanic, the host has a spawn function but extensions only get a stash for enqueuing async work in sync callbacks which is then explicitly, manually, and with strict order popped and awaited
- still deadlocks nondeterministically for some ungodly reason
This commit is contained in:
2026-01-01 14:54:29 +00:00
parent 06debb3636
commit 32d6237dc5
92 changed files with 2507 additions and 2223 deletions

View File

@@ -3,24 +3,26 @@ pub mod parse_folder;
use std::cell::RefCell;
use std::fs::File;
use std::io::{Read, Write};
use std::mem;
use std::process::{Command, ExitCode};
use std::rc::Rc;
use async_fn_stream::try_stream;
use camino::Utf8PathBuf;
use clap::{Parser, Subcommand};
use futures::future::LocalBoxFuture;
use futures::{FutureExt, Stream, TryStreamExt, io};
use itertools::Itertools;
use orchid_base::error::Reporter;
use orchid_base::format::{FmtCtxImpl, Format, fmt, take_first};
use orchid_base::error::{try_with_reporter, with_reporter};
use orchid_base::format::{FmtCtxImpl, Format, fmt, fmt_v, take_first};
use orchid_base::interner::local_interner::local_interner;
use orchid_base::interner::{is, with_interner};
use orchid_base::location::SrcRange;
use orchid_base::logging::{LogStrategy, Logger};
use orchid_base::logging::{LogStrategy, Logger, with_logger};
use orchid_base::name::{NameLike, VPath};
use orchid_base::parse::{Import, Snippet};
use orchid_base::sym;
use orchid_base::tree::{Token, ttv_fmt};
use orchid_host::ctx::Ctx;
use orchid_host::ctx::{Ctx, JoinHandle, Spawner};
use orchid_host::execute::{ExecCtx, ExecResult};
use orchid_host::expr::ExprKind;
use orchid_host::extension::Extension;
@@ -78,84 +80,87 @@ pub enum Commands {
fn get_all_extensions<'a>(
args: &'a Args,
logger: &'a Logger,
msg_logger: &'a Logger,
ctx: &'a Ctx,
) -> impl Stream<Item = io::Result<Extension>> + 'a {
try_stream(async |mut cx| {
for ext_path in args.extension.iter() {
let exe = if cfg!(windows) { ext_path.with_extension("exe") } else { ext_path.clone() };
let init =
ext_command(Command::new(exe.as_os_str()), logger.clone(), msg_logger.clone(), ctx.clone())
.await?;
cx.emit(Extension::new(init, logger.clone(), msg_logger.clone(), ctx.clone())?).await;
let init = ext_command(Command::new(exe.as_os_str()), ctx.clone()).await?;
cx.emit(Extension::new(init, ctx.clone()).await?).await;
}
Ok(cx)
})
}
struct JoinHandleImpl(tokio::task::JoinHandle<()>);
impl JoinHandle for JoinHandleImpl {
fn abort(&self) { self.0.abort() }
fn join(self: Box<Self>) -> LocalBoxFuture<'static, ()> {
Box::pin(async { self.0.await.unwrap() })
}
}
struct SpawnerImpl;
impl Spawner for SpawnerImpl {
fn spawn_obj(&self, fut: LocalBoxFuture<'static, ()>) -> Box<dyn JoinHandle> {
Box::new(JoinHandleImpl(spawn_local(fut)))
}
}
#[tokio::main]
async fn main() -> io::Result<ExitCode> {
eprintln!("orcx launched");
let exit_code = Rc::new(RefCell::new(ExitCode::SUCCESS));
let local_set = LocalSet::new();
let exit_code1 = exit_code.clone();
let args = Args::parse();
let logger = Logger::new(if args.logs { LogStrategy::StdErr } else { LogStrategy::Discard });
let cx_logger = logger.clone();
let msg_logger =
Logger::new(if args.msg_logs { LogStrategy::StdErr } else { LogStrategy::Discard });
local_set.spawn_local(async move {
let args = Args::parse();
let ctx = &Ctx::new(Rc::new(|fut| mem::drop(spawn_local(fut))));
let i = &ctx.i;
let logger = Logger::new(if args.logs { LogStrategy::StdErr } else { LogStrategy::Discard });
let msg_logger =
Logger::new(if args.msg_logs { LogStrategy::StdErr } else { LogStrategy::Discard });
let extensions = get_all_extensions(&args, &logger, &msg_logger, ctx)
.try_collect::<Vec<Extension>>()
.await
.unwrap();
let ctx = &Ctx::new(msg_logger.clone(), SpawnerImpl);
let extensions = get_all_extensions(&args, ctx).try_collect::<Vec<Extension>>().await.unwrap();
match args.command {
Commands::Lex { file } => {
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
let mut file = File::open(file.as_std_path()).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let lexemes = lex(i.i(&buf).await, sym!(usercode; i), &systems, ctx).await.unwrap();
println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i }).await, true))
let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap();
println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true))
},
Commands::Parse { file } => {
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
let mut file = File::open(file.as_std_path()).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let lexemes = lex(i.i(&buf).await, sym!(usercode; i), &systems, ctx).await.unwrap();
let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap();
let Some(first) = lexemes.first() else {
println!("File empty!");
return;
};
let reporter = Reporter::new();
let pctx = HostParseCtxImpl {
rep: &reporter,
systems: &systems,
ctx: ctx.clone(),
src: sym!(usercode; i),
};
let pctx = HostParseCtxImpl { systems: &systems, ctx: ctx.clone(), src: sym!(usercode) };
let snip = Snippet::new(first, &lexemes);
let ptree = parse_items(&pctx, Substack::Bottom, snip).await.unwrap();
if let Some(errv) = reporter.errv() {
eprintln!("{errv}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
}
if ptree.is_empty() {
eprintln!("File empty only after parsing, but no errors were reported");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
}
for item in ptree {
println!("{}", take_first(&item.print(&FmtCtxImpl { i }).await, true))
}
match with_reporter(parse_items(&pctx, Substack::Bottom, snip)).await.unwrap() {
Err(errv) => {
eprintln!("{errv}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
},
Ok(ptree) if ptree.is_empty() => {
eprintln!("File empty only after parsing, but no errors were reported");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
},
Ok(ptree) =>
for item in ptree {
println!("{}", take_first(&item.print(&FmtCtxImpl::default()).await, true))
},
};
},
Commands::Repl => {
let mut counter = 0;
let mut imports = Vec::new();
let usercode_path = sym!(usercode; i);
let usercode_path = sym!(usercode);
let mut stdin = BufReader::new(stdin());
loop {
counter += 1;
@@ -164,26 +169,24 @@ async fn main() -> io::Result<ExitCode> {
std::io::stdout().flush().unwrap();
let mut prompt = String::new();
stdin.read_line(&mut prompt).await.unwrap();
let name = i.i(&format!("_{counter}")).await;
let path = usercode_path.suffix([name.clone()], i).await;
let name = is(&format!("_{counter}")).await;
let path = usercode_path.suffix([name.clone()]).await;
let mut lexemes =
lex(i.i(prompt.trim()).await, path.clone(), &systems, ctx).await.unwrap();
lex(is(prompt.trim()).await, path.clone(), &systems, ctx).await.unwrap();
let Some(discr) = lexemes.first() else { continue };
if args.logs {
println!("lexed: {}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i }).await, true));
println!(
"lexed: {}",
take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true)
);
}
let prefix_sr = SrcRange::zw(path.clone(), 0);
let process_lexemes = async |lexemes: &[ParsTokTree]| {
let snippet = Snippet::new(&lexemes[0], lexemes);
let reporter = Reporter::new();
let parse_ctx = HostParseCtxImpl {
ctx: ctx.clone(),
rep: &reporter,
src: path.clone(),
systems: &systems[..],
};
let parse_result = parse_item(&parse_ctx, Substack::Bottom, vec![], snippet).await;
match reporter.merge(parse_result) {
let parse_ctx =
HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] };
match try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet)).await
{
Ok(items) => Some(items),
Err(e) => {
eprintln!("{e}");
@@ -194,7 +197,7 @@ async fn main() -> io::Result<ExitCode> {
let add_imports = |items: &mut Vec<Item>, imports: &[Import]| {
items.extend(imports.iter().map(|import| Item::new(import.sr.clone(), import.clone())));
};
if discr.is_kw(i.i("import").await) {
if discr.is_kw(is("import").await) {
let Some(import_lines) = process_lexemes(&lexemes).await else { continue };
imports.extend(import_lines.into_iter().map(|it| match it.kind {
ItemKind::Import(imp) => imp,
@@ -202,8 +205,8 @@ async fn main() -> io::Result<ExitCode> {
}));
continue;
}
if !discr.is_kw(i.i("let").await) {
let prefix = [i.i("export").await, i.i("let").await, name.clone(), i.i("=").await];
if !discr.is_kw(is("let").await) {
let prefix = [is("export").await, is("let").await, name.clone(), is("=").await];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
}
let Some(mut new_lines) = process_lexemes(&lexemes).await else { continue };
@@ -216,29 +219,36 @@ async fn main() -> io::Result<ExitCode> {
add_imports(&mut new_lines, &imports);
imports.push(Import::new(input_sr.clone(), VPath::new(path.segs()), const_name.clone()));
let new_module = ParsedModule::new(true, new_lines);
let reporter = Reporter::new();
root = root.add_parsed(&new_module, path.clone(), &reporter).await;
match with_reporter(root.add_parsed(&new_module, path.clone())).await {
Ok(new) => root = new,
Err(errv) => {
eprintln!("{errv}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
}
eprintln!("parsed");
let entrypoint =
ExprKind::Const(path.suffix([const_name.clone()], i).await).at(input_sr.pos());
let mut xctx = ExecCtx::new(ctx.clone(), logger.clone(), root.clone(), entrypoint).await;
ExprKind::Const(path.suffix([const_name.clone()]).await).at(input_sr.pos());
let mut xctx = ExecCtx::new(root.clone(), entrypoint).await;
eprintln!("executed");
xctx.set_gas(Some(1000));
xctx.execute().await;
match xctx.result() {
ExecResult::Value(val) =>
println!("{const_name} = {}", take_first(&val.print(&FmtCtxImpl { i }).await, false)),
ExecResult::Value(val) => println!(
"{const_name} = {}",
take_first(&val.print(&FmtCtxImpl::default()).await, false)
),
ExecResult::Err(e) => println!("error: {e}"),
ExecResult::Gas(_) => println!("Ran out of gas!"),
}
}
},
Commands::ModTree { proj, prefix } => {
let reporter = Reporter::new();
let (mut root, _systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = proj {
let path = proj_path.into_std_path_buf();
match parse_folder(&root, path, sym!(src; i), &reporter, ctx.clone()).await {
match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await {
Ok(r) => root = r,
Err(e) => {
eprintln!("{e}");
@@ -248,7 +258,7 @@ async fn main() -> io::Result<ExitCode> {
}
}
let prefix = match prefix {
Some(pref) => VPath::parse(&pref, i).await,
Some(pref) => VPath::parse(&pref).await,
None => VPath::new([]),
};
let root_data = root.0.read().await;
@@ -265,7 +275,7 @@ async fn main() -> io::Result<ExitCode> {
}
}
for (key, mem) in &module.members {
let new_path = path.clone().name_with_suffix(key.clone()).to_sym(&root.ctx.i).await;
let new_path = path.clone().name_with_suffix(key.clone()).to_sym().await;
match mem.kind(root.ctx.clone(), &root.consts).await {
MemberKind::Module(module) => {
println!("{indent}module {key} {{");
@@ -274,20 +284,20 @@ async fn main() -> io::Result<ExitCode> {
},
MemberKind::Const => {
let value = root.consts.get(&new_path).expect("Missing const!");
println!("{indent}const {key} = {}", fmt(value, &root.ctx.i).await)
println!("{indent}const {key} = {}", fmt(value).await)
},
}
}
}
},
Commands::Exec { proj, code } => {
let reporter = Reporter::new();
let path = sym!(usercode; i);
eprintln!("exec branch");
let path = sym!(usercode);
let prefix_sr = SrcRange::zw(path.clone(), 0);
let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = proj {
let path = proj_path.into_std_path_buf();
match parse_folder(&root, path, sym!(src; i), &reporter, ctx.clone()).await {
match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await {
Ok(r) => root = r,
Err(e) => {
eprintln!("{e}");
@@ -296,22 +306,32 @@ async fn main() -> io::Result<ExitCode> {
},
}
}
let mut lexemes = lex(i.i(code.trim()).await, path.clone(), &systems, ctx).await.unwrap();
if args.logs {
println!("lexed: {}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i }).await, true));
}
let parse_ctx = HostParseCtxImpl {
ctx: ctx.clone(),
rep: &reporter,
src: path.clone(),
systems: &systems[..],
let mut lexemes = match lex(is(code.trim()).await, path.clone(), &systems, ctx).await {
Ok(lexemes) => {
if args.logs {
println!("lexed: {}", fmt_v::<ParsTokTree>(lexemes.iter()).await.join(" "));
}
lexemes
},
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
};
let prefix =
[i.i("export").await, i.i("let").await, i.i("entrypoint").await, i.i("=").await];
let parse_ctx =
HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] };
let prefix = [is("export").await, is("let").await, is("entrypoint").await, is("=").await];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
let snippet = Snippet::new(&lexemes[0], &lexemes);
let parse_res = parse_item(&parse_ctx, Substack::Bottom, vec![], snippet).await;
let entrypoint = match reporter.merge(parse_res) {
let entrypoint = match try_with_reporter(parse_item(
&parse_ctx,
Substack::Bottom,
vec![],
snippet,
))
.await
{
Ok(items) => ParsedModule::new(true, items),
Err(e) => {
eprintln!("{e}");
@@ -319,22 +339,28 @@ async fn main() -> io::Result<ExitCode> {
return;
},
};
let reporter = Reporter::new();
let root = root.add_parsed(&entrypoint, path.clone(), &reporter).await;
let expr = ExprKind::Const(sym!(usercode::entrypoint; i)).at(prefix_sr.pos());
let mut xctx = ExecCtx::new(ctx.clone(), logger.clone(), root, expr).await;
let root = match with_reporter(root.add_parsed(&entrypoint, path.clone())).await {
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
Ok(new_root) => new_root,
};
let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos());
let mut xctx = ExecCtx::new(root, expr).await;
xctx.set_gas(Some(10_000));
xctx.execute().await;
match xctx.result() {
ExecResult::Value(val) =>
println!("{}", take_first(&val.print(&FmtCtxImpl { i }).await, false)),
println!("{}", take_first(&val.print(&FmtCtxImpl::default()).await, false)),
ExecResult::Err(e) => println!("error: {e}"),
ExecResult::Gas(_) => println!("Ran out of gas!"),
}
},
}
});
local_set.await;
with_interner(local_interner(), with_logger(cx_logger, local_set)).await;
let x = *exit_code.borrow();
Ok(x)
}

View File

@@ -3,7 +3,8 @@ use std::path::{Path, PathBuf};
use futures::FutureExt;
use itertools::Itertools;
use orchid_base::error::{OrcRes, Reporter, async_io_err, os_str_to_string};
use orchid_base::error::{OrcRes, async_io_err, os_str_to_string, report};
use orchid_base::interner::is;
use orchid_base::location::SrcRange;
use orchid_base::name::Sym;
use orchid_base::parse::Snippet;
@@ -16,22 +17,16 @@ use substack::Substack;
use tokio::fs::{self, File};
use tokio::io::AsyncReadExt;
pub async fn parse_folder(
root: &Root,
path: PathBuf,
ns: Sym,
rep: &Reporter,
ctx: Ctx,
) -> OrcRes<Root> {
let parsed_module = (recur(&path, ns.clone(), rep, ctx).await?)
.expect("Project folder is a single non-orchid file");
return Ok(root.add_parsed(&parsed_module, ns, rep).await);
async fn recur(path: &Path, ns: Sym, rep: &Reporter, ctx: Ctx) -> OrcRes<Option<ParsedModule>> {
pub async fn parse_folder(root: &Root, path: PathBuf, ns: Sym, ctx: Ctx) -> OrcRes<Root> {
let parsed_module =
(recur(&path, ns.clone(), ctx).await?).expect("Project folder is a single non-orchid file");
return Ok(root.add_parsed(&parsed_module, ns).await);
async fn recur(path: &Path, ns: Sym, ctx: Ctx) -> OrcRes<Option<ParsedModule>> {
let sr = SrcRange::new(0..0, &ns);
if path.is_dir() {
let mut items = Vec::new();
let mut stream = match fs::read_dir(path).await {
Err(err) => return Err(async_io_err(err, &ctx.i, [sr]).await),
Err(err) => return Err(async_io_err(err, [sr]).await),
Ok(s) => s,
};
loop {
@@ -39,17 +34,17 @@ pub async fn parse_folder(
Ok(Some(ent)) => ent,
Ok(None) => break,
Err(err) => {
rep.report(async_io_err(err, &ctx.i, [sr.clone()]).await);
report(async_io_err(err, [sr.clone()]).await);
continue;
},
};
let os_name = entry.path().file_stem().expect("File name could not be read").to_owned();
let name = ctx.i.i(os_str_to_string(&os_name, &ctx.i, [sr.clone()]).await?).await;
let ns = ns.suffix([name.clone()], &ctx.i).await;
let name = is(os_str_to_string(&os_name, [sr.clone()]).await?).await;
let ns = ns.suffix([name.clone()]).await;
let sr = SrcRange::new(0..0, &ns);
match recur(&entry.path(), ns.clone(), rep, ctx.clone()).boxed_local().await {
match recur(&entry.path(), ns.clone(), ctx.clone()).boxed_local().await {
Err(e) => {
rep.report(e);
report(e);
continue;
},
Ok(None) => continue,
@@ -59,17 +54,17 @@ pub async fn parse_folder(
Ok(Some(ParsedModule::new(false, items)))
} else if path.extension() == Some(OsStr::new("orc")) {
let mut file = match File::open(path).await {
Err(e) => return Err(async_io_err(e, &ctx.i, [sr]).await),
Err(e) => return Err(async_io_err(e, [sr]).await),
Ok(file) => file,
};
let mut text = String::new();
if let Err(e) = file.read_to_string(&mut text).await {
return Err(async_io_err(e, &ctx.i, [sr]).await);
return Err(async_io_err(e, [sr]).await);
}
let systems =
ctx.systems.read().await.iter().filter_map(|(_, sys)| sys.upgrade()).collect_vec();
let lexemes = lex(ctx.i.i(&text).await, ns.clone(), &systems, &ctx).await?;
let hpctx = HostParseCtxImpl { ctx: ctx.clone(), rep, src: ns.clone(), systems: &systems };
let lexemes = lex(is(&text).await, ns.clone(), &systems, &ctx).await?;
let hpctx = HostParseCtxImpl { ctx: ctx.clone(), src: ns.clone(), systems: &systems };
let Some(fst) = lexemes.first() else { return Ok(Some(ParsedModule::new(false, []))) };
let items = parse_items(&hpctx, Substack::Bottom, Snippet::new(fst, &lexemes)).await?;
Ok(Some(ParsedModule::new(true, items)))