use std::ffi::OsStr; use std::path::{Path, PathBuf}; use futures::FutureExt; use itertools::Itertools; use orchid_base::error::{OrcRes, Reporter, async_io_err, mk_err, os_str_to_string}; use orchid_base::location::SrcRange; use orchid_base::name::Sym; use orchid_base::parse::Snippet; use orchid_host::ctx::Ctx; use orchid_host::lex::lex; use orchid_host::parse::{HostParseCtxImpl, parse_items}; use orchid_host::parsed::ParsedModule; use orchid_host::tree::Root; 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 { 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> { let sr = SrcRange::new(0..0, &ns); if path.is_dir() { let Some(name_os) = path.file_name() else { return Err(mk_err( ctx.i.i("Could not read directory name").await, format!("Path {} ends in ..", path.to_string_lossy()), [sr], )); }; let name = ctx.i.i(os_str_to_string(name_os, &ctx.i, [sr]).await?).await; let ns = ns.suffix([name.clone()], &ctx.i).await; let sr = SrcRange::new(0..0, &ns); 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), Ok(s) => s, }; loop { let entry = match stream.next_entry().await { Ok(Some(ent)) => ent, Ok(None) => break, Err(err) => { rep.report(async_io_err(err, &ctx.i, [sr.clone()]).await); continue; }, }; match recur(&entry.path(), ns.clone(), rep, ctx.clone()).boxed_local().await { Err(e) => { rep.report(e); continue; }, Ok(None) => continue, Ok(Some(module)) => items.push(module.default_item(name.clone(), sr.clone())), } } Ok(Some(ParsedModule::new(false, items))) } else if path.extension() == Some(OsStr::new("orc")) { let name_os = path.file_stem().expect("If there is an extension, there must be a stem"); let name = ctx.i.i(os_str_to_string(name_os, &ctx.i, [sr]).await?).await; let ns = ns.suffix([name], &ctx.i).await; let sr = SrcRange::new(0..0, &ns); let mut file = match File::open(path).await { Err(e) => return Err(async_io_err(e, &ctx.i, [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); } 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 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(false, items))) } else { Ok(None) } } }