bit of cleanup, few steps towards commands demo

This commit is contained in:
2026-04-18 15:13:27 +00:00
parent 6eed6b9831
commit 60c96964d9
12 changed files with 228 additions and 538 deletions

View File

@@ -13,11 +13,15 @@ clap = { version = "4.5.54", features = ["derive", "env", "cargo"] }
ctrlc = "3.5.1"
futures = "0.3.31"
itertools = "0.14.0"
never = "0.1.0"
orchid-api = { version = "0.1.0", path = "../orchid-api" }
orchid-base = { version = "0.1.0", path = "../orchid-base" }
orchid-extension = { version = "0.1.0", path = "../orchid-extension" }
orchid-host = { version = "0.1.0", path = "../orchid-host", features = [
"tokio",
"orchid-extension",
] }
stacker = "0.1.23"
substack = "1.1.1"
tokio = { version = "1.49.0", features = ["full"] }
tokio-util = { version = "0.7.18", features = ["compat"] }

View File

@@ -1,6 +1,12 @@
use orchid_base::Logger;
use never::Never;
use orchid_base::{IStr, Logger, NameLike, Receipt, ReqHandle, Sym};
use orchid_extension::{self as ox, OrcReader, OrcWriter};
use orchid_host::cmd_system::{CmdEvent, CmdRunner};
use orchid_host::dylib::ext_dylib;
use orchid_host::inline::ext_inline;
use orchid_host::tree::Root;
use tokio::time::Instant;
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
pub mod parse_folder;
mod print_mod;
mod repl;
@@ -22,7 +28,7 @@ use futures::{FutureExt, Stream, TryStreamExt, io};
use itertools::Itertools;
use orchid_base::local_interner::local_interner;
use orchid_base::{
FmtCtxImpl, Format, Snippet, SrcRange, Token, VPath, fmt, fmt_v, is, log, sym, take_first,
FmtCtxImpl, Snippet, SrcRange, Token, VPath, fmt, fmt_v, is, log, sym, take_first,
try_with_reporter, ttv_fmt, with_interner, with_logger, with_reporter, with_stash,
};
use orchid_host::ctx::{Ctx, JoinHandle, Spawner};
@@ -34,7 +40,7 @@ use orchid_host::logger::LoggerImpl;
use orchid_host::parse::{HostParseCtxImpl, parse_item, parse_items};
use orchid_host::parsed::{ParsTokTree, ParsedModule};
use orchid_host::subprocess::ext_command;
use orchid_host::system::init_systems;
use orchid_host::system::{System, init_systems};
use substack::Substack;
use tokio::task::{LocalSet, spawn_local};
@@ -265,6 +271,90 @@ impl Spawner for SpawnerImpl {
}
}
#[derive(Debug, Default)]
struct StdIoSystem;
impl ox::SystemCard for StdIoSystem {
type Ctor = Self;
type Req = Never;
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn ox::AtomOps>>> { [] }
}
impl ox::SystemCtor for StdIoSystem {
const NAME: &'static str = "orcx::stdio";
const VERSION: f64 = 0.0;
type Card = Self;
type Deps = ();
type Instance = Self;
fn inst(&self, _: <Self::Deps as orchid_extension::DepDef>::Sat) -> Self::Instance { Self }
}
impl ox::System for StdIoSystem {
type Ctor = Self;
async fn request<'a>(
&self,
_: Box<dyn ReqHandle<'a> + 'a>,
req: ox::ReqForSystem<Self>,
) -> Receipt<'a> {
match req {}
}
async fn env(&self) -> Vec<ox::tree::GenMember> {
// TODO: this is impractical, try dialogue interface eg. prompt, choice, println
ox::tree::module(true, "stdio", [
ox::tree::fun(true, "get_stdin", async |cb: ox::Expr| {
ox::cmd(async move || {
Some(ox::gen_expr::call(cb.clone(), OrcReader(tokio::io::stdin().compat())))
})
}),
ox::tree::fun(true, "get_stdout", async |cb: ox::Expr| {
ox::cmd(async move || {
Some(ox::gen_expr::call(cb.clone(), OrcWriter(tokio::io::stdout().compat_write())))
})
}),
ox::tree::fun(true, "get_stderr", async |cb: ox::Expr| {
ox::cmd(async move || {
Some(ox::gen_expr::call(cb.clone(), OrcWriter(tokio::io::stderr().compat_write())))
})
}),
])
}
}
async fn load_proj_if_set(root: &mut Root, args: &Args, ctx: &Ctx) -> Result<(), String> {
if let Some(proj_path) = &args.proj {
let path = proj_path.clone().into_std_path_buf();
*root = try_with_reporter(parse_folder(root, path, sym!(src), ctx.clone()))
.await
.map_err(|e| e.to_string())?
}
Ok(())
}
async fn add_const_at(
root: &mut Root,
ctx: &Ctx,
systems: &[System],
path: Sym,
value: IStr,
) -> Result<(), String> {
let (name, parent) = path.split_last_seg();
let path = Sym::new(parent.iter().cloned())
.await
.map_err(|_| format!("Const path must have two segments, found {path}"))?;
let prefix_sr = SrcRange::zw(path.clone(), 0);
let mut lexemes =
lex(value.clone(), path.clone(), systems, ctx).await.map_err(|e| e.to_string())?;
writeln!(log("debug"), "lexed: {}", fmt_v::<ParsTokTree>(lexemes.iter()).await.join(" ")).await;
let parse_ctx = HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems };
let prefix = [is("export").await, is("let").await, name, is("=").await];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
let snippet = Snippet::new(&lexemes[0], &lexemes);
let items = try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet))
.await
.map_err(|e| e.to_string())?;
let entrypoint = ParsedModule::new(true, items);
*root =
with_reporter(root.add_parsed(&entrypoint, path.clone())).await.map_err(|e| e.to_string())?;
Ok(())
}
fn main() -> io::Result<ExitCode> {
// Use a 10MB stack for single-threaded, unoptimized operation
stacker::grow(10 * 1024 * 1024, || {
@@ -283,10 +373,10 @@ fn main() -> io::Result<ExitCode> {
local_set.spawn_local(async move {
let ctx = &ctx1;
let res = with_stash(async move {
let extensions =
let mut extensions =
get_all_extensions(&args, ctx).try_collect::<Vec<Extension>>().await.unwrap();
time_print(&args, "Extensions loaded");
match args.command {
match &args.command {
Commands::Lex { file, line } => {
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
let mut buf = String::new();
@@ -323,73 +413,64 @@ fn main() -> io::Result<ExitCode> {
);
}
for item in ptree {
println!("{}", take_first(&item.print(&FmtCtxImpl::default()).await, true))
println!("{}", fmt(&item).await)
}
},
Commands::Repl => repl(&args, &extensions, ctx.clone()).await?,
Commands::ModTree { prefix } => {
let (mut root, _systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = args.proj {
let path = proj_path.into_std_path_buf();
root = try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone()))
.await
.map_err(|e| e.to_string())?
}
load_proj_if_set(&mut root, &args, ctx).await?;
let prefix = match prefix {
Some(pref) => VPath::parse(&pref).await,
Some(pref) => VPath::parse(pref).await,
None => VPath::new([]),
};
let root_data = root.0.read().await;
print_mod::print_mod(&root_data.root, prefix, &root_data).await;
},
Commands::Eval { code } => {
let path = sym!(usercode);
let prefix_sr = SrcRange::zw(path.clone(), 0);
let path = sym!(usercode::entrypoint);
let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = args.proj {
let path = proj_path.into_std_path_buf();
root = try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone()))
.await
.map_err(|e| e.to_string())?;
}
let mut lexemes = lex(is(code.trim()).await, path.clone(), &systems, ctx)
.await
.map_err(|e| e.to_string())?;
writeln!(
log("debug"),
"lexed: {}",
fmt_v::<ParsTokTree>(lexemes.iter()).await.join(" ")
)
.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 items =
try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet))
.await
.map_err(|e| e.to_string())?;
let entrypoint = ParsedModule::new(true, items);
let root = with_reporter(root.add_parsed(&entrypoint, path.clone()))
.await
.map_err(|e| e.to_string())?;
let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos());
load_proj_if_set(&mut root, &args, ctx).await?;
add_const_at(&mut root, ctx, &systems[..], path.clone(), is(code).await).await?;
let expr = ExprKind::Const(path.clone()).at(SrcRange::zw(path.clone(), 0).pos());
let mut xctx = ExecCtx::new(root.clone(), expr).await;
if !args.no_gas {
xctx.set_gas(Some(args.gas));
}
match xctx.execute().await {
ExecResult::Value(val, _) => {
println!("{}", take_first(&val.print(&FmtCtxImpl::default()).await, false))
},
ExecResult::Value(val, _) => println!("{}", fmt(&val).await),
ExecResult::Err(e, _) => println!("error: {e}"),
ExecResult::Gas(_) => println!("Ran out of gas!"),
ExecResult::Gas(_) => println!("Exceeded gas limit of {}", args.gas),
}
},
Commands::Exec { main: _ } => {
todo!("Integration of the command system is in-dev")
Commands::Exec { main } => {
let path = sym!(usercode::entrypoint);
let (mut root, mut systems) = init_systems(&args.system, &extensions).await.unwrap();
let io_ext_builder = ox::ExtensionBuilder::new("orcx::stdio").system(StdIoSystem);
let io_ext_init = ext_inline(io_ext_builder, ctx.clone()).await;
let io_ext =
Extension::new(io_ext_init, ctx.clone()).await.map_err(|e| e.to_string())?;
let io_ctor = (io_ext.system_ctors().find(|ctor| ctor.name() == "orchid::cmd"))
.expect("Missing command system ctor");
let (io_root, io_system) = io_ctor.run(vec![]).await;
root = root.merge(&io_root).await.expect("Failed to merge stdio root into tree");
systems.push(io_system);
extensions.push(io_ext);
load_proj_if_set(&mut root, &args, ctx).await?;
add_const_at(&mut root, ctx, &systems[..], path.clone(), is(main).await).await?;
let expr = ExprKind::Const(path.clone()).at(SrcRange::zw(path.clone(), 0).pos());
let mut crun = CmdRunner::new(root, ctx.clone(), [expr]).await;
if !args.no_gas {
crun.set_gas(args.gas);
}
match crun.execute().await {
CmdEvent::Exit | CmdEvent::Settled => (),
CmdEvent::Err(e) => println!("error: {e}"),
CmdEvent::Gas => println!("Exceeded gas limit of {}", args.gas),
CmdEvent::NonCommand(val) => {
println!("Non-command value: {}", fmt(&val).await)
},
}
},
};
Ok(())