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_std::io::stdin; use async_std::path::PathBuf; use async_stream::try_stream; use camino::Utf8PathBuf; use clap::{Parser, Subcommand}; use futures::{Stream, TryStreamExt, io}; use orchid_base::error::Reporter; use orchid_base::format::{FmtCtxImpl, Format, take_first}; use orchid_base::logging::{LogStrategy, Logger}; use orchid_base::parse::Snippet; use orchid_base::sym; use orchid_base::tree::ttv_fmt; use orchid_host::ctx::Ctx; use orchid_host::execute::{ExecCtx, ExecResult}; use orchid_host::expr::PathSetBuilder; use orchid_host::extension::Extension; use orchid_host::lex::lex; use orchid_host::parse::{HostParseCtxImpl, parse_expr, parse_items}; use orchid_host::parsed::{Item, ItemKind, ParsedMember, ParsedMemberKind, ParsedModule}; use orchid_host::subprocess::ext_command; use orchid_host::system::init_systems; use orchid_host::tree::Root; use substack::Substack; use tokio::task::{LocalSet, spawn_local}; use crate::parse_folder::parse_folder; #[derive(Parser, Debug)] #[command(version, about, long_about)] pub struct Args { #[arg(short, long, env = "ORCHID_EXTENSIONS", value_delimiter = ';')] extension: Vec, #[arg(short, long, env = "ORCHID_DEFAULT_SYSTEMS", value_delimiter = ';')] system: Vec, #[arg(short, long)] logs: bool, #[arg(short, long)] msg_logs: bool, #[command(subcommand)] command: Commands, } #[derive(Subcommand, Debug)] pub enum Commands { Lex { #[arg(short, long)] file: Utf8PathBuf, }, Parse { #[arg(short, long)] file: Utf8PathBuf, }, Repl, Execute { #[arg(long)] proj: Option, #[arg()] code: String, }, } fn get_all_extensions<'a>( args: &'a Args, logger: &'a Logger, msg_logger: &'a Logger, ctx: &'a Ctx, ) -> impl Stream> + 'a { try_stream! { 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 .unwrap(); let ext = Extension::new(init, logger.clone(), msg_logger.clone(), ctx.clone())?; yield ext } } } #[tokio::main] async fn main() -> io::Result { let exit_code = Rc::new(RefCell::new(ExitCode::SUCCESS)); let local_set = LocalSet::new(); let exit_code1 = exit_code.clone(); 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::>() .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).await, &systems, ctx).await.unwrap(); println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i }).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).await, &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).await, }; 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)) } }, Commands::Repl => loop { let (root, systems) = init_systems(&args.system, &extensions).await.unwrap(); print!("\\.> "); std::io::stdout().flush().unwrap(); let mut prompt = String::new(); stdin().read_line(&mut prompt).await.unwrap(); eprintln!("lexing"); let lexemes = lex(i.i(prompt.trim()).await, sym!(usercode; i).await, &systems, ctx).await.unwrap(); eprintln!("lexed"); if args.logs { println!("lexed: {}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i }).await, true)); } let path = sym!(usercode; i).await; let reporter = Reporter::new(); let parse_ctx = HostParseCtxImpl { ctx: ctx.clone(), rep: &reporter, src: path.clone(), systems: &systems[..], }; let parse_res = parse_expr( &parse_ctx, path.clone(), PathSetBuilder::new(), Snippet::new(&lexemes[0], &lexemes), ) .await; eprintln!("parsed"); let expr = match reporter.merge(parse_res) { Ok(expr) => expr, Err(e) => { eprintln!("{e}"); continue; }, }; let mut xctx = ExecCtx::new(ctx.clone(), logger.clone(), root.clone(), expr).await; eprintln!("executed"); xctx.set_gas(Some(1000)); xctx.execute().await; match xctx.result() { ExecResult::Value(val) => println!("{}", take_first(&val.print(&FmtCtxImpl { i }).await, false)), ExecResult::Err(e) => println!("error: {e}"), ExecResult::Gas(_) => println!("Ran out of gas!"), } }, Commands::Execute { proj, code } => { let reporter = Reporter::new(); let path = sym!(usercode::entrypoint; i).await; let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap(); if let Some(proj_path) = proj { let path = PathBuf::from(proj_path.into_std_path_buf()); match parse_folder(&root, path, sym!(src; i).await, &reporter, ctx.clone()).await { Ok(r) => root = r, Err(e) => { eprintln!("{e}"); *exit_code1.borrow_mut() = ExitCode::FAILURE; return; }, } } let lexemes = lex(i.i(code.trim()).await, path.clone(), &systems, ctx).await.unwrap(); let snippet = Snippet::new(&lexemes[0], &lexemes); 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 parse_res = parse_expr(&parse_ctx, path.clone(), PathSetBuilder::new(), snippet).await; let expr = match reporter.merge(parse_res) { Ok(expr) => expr, Err(e) => { eprintln!("{e}"); *exit_code1.borrow_mut() = ExitCode::FAILURE; return; }, }; let parsed_root = ParsedModule::new(true, [Item::new( snippet.sr(), ParsedMember::new(true, i.i("entrypoint").await, expr.clone()), )]); let reporter = Reporter::new(); let root = root.add_parsed(&parsed_root, sym!(usercode; i).await, &reporter).await; let mut xctx = ExecCtx::new(ctx.clone(), logger.clone(), root, expr).await; xctx.set_gas(Some(1000)); xctx.execute().await; match xctx.result() { ExecResult::Value(val) => println!("{}", take_first(&val.print(&FmtCtxImpl { i }).await, false)), ExecResult::Err(e) => println!("error: {e}"), ExecResult::Gas(_) => println!("Ran out of gas!"), } }, } }); local_set.await; let x = *exit_code.borrow(); Ok(x) }