diff --git a/Cargo.lock b/Cargo.lock index fd3edd6..1a51e5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1241,6 +1241,7 @@ dependencies = [ "orchid-extension", "ordered-float", "rust_decimal", + "substack", "test_executors", "tokio", ] diff --git a/orchid-api/src/parser.rs b/orchid-api/src/parser.rs index f12e0ff..dae3c0b 100644 --- a/orchid-api/src/parser.rs +++ b/orchid-api/src/parser.rs @@ -59,7 +59,7 @@ pub struct ParsedMember { #[derive(Clone, Debug, Coding)] pub enum ParsedMemberKind { Constant(ParsedConstId), - Module(Vec), + Module { lines: Vec, use_prelude: bool }, } /// Obtain the value of a parsed constant. This is guaranteed to be called after diff --git a/orchid-api/src/system.rs b/orchid-api/src/system.rs index 81ec932..c4019a7 100644 --- a/orchid-api/src/system.rs +++ b/orchid-api/src/system.rs @@ -5,7 +5,7 @@ use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use ordered_float::NotNan; -use crate::{CharFilter, ExtHostReq, HostExtNotif, HostExtReq, MemberKind, TStr}; +use crate::{CharFilter, ExtHostReq, HostExtNotif, HostExtReq, MemberKind, TStr, TStrv}; /// ID of a system type #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)] @@ -63,6 +63,7 @@ pub struct NewSystemResponse { pub lex_filter: CharFilter, pub line_types: Vec, pub const_root: HashMap, + pub prelude: Vec, } #[derive(Clone, Debug, Coding, Hierarchy)] diff --git a/orchid-api/src/tree.rs b/orchid-api/src/tree.rs index 8ba099a..ad53241 100644 --- a/orchid-api/src/tree.rs +++ b/orchid-api/src/tree.rs @@ -27,7 +27,7 @@ pub struct TokenTree { #[derive(Clone, Debug, Coding)] pub enum Token { /// Lambda function head, from the opening \ until the beginning of the body. - LambdaHead(Vec), + LambdaHead(Box), /// A name segment or an operator. Name(TStr), /// A newly generated expression. The last place this is supposed to happen is diff --git a/orchid-base/src/clone.rs b/orchid-base/src/clone.rs index 7aa9202..f460684 100644 --- a/orchid-base/src/clone.rs +++ b/orchid-base/src/clone.rs @@ -1,8 +1,8 @@ #[macro_export] macro_rules! clone { - ($($n:ident),+; $body:expr) => ( + ($($n:ident $($mut:ident)?),+; $body:expr) => ( { - $( let $n = $n.clone(); )+ + $( let $($mut)? $n = $n.clone(); )+ $body } ); diff --git a/orchid-base/src/location.rs b/orchid-base/src/location.rs index 862d929..57469c7 100644 --- a/orchid-base/src/location.rs +++ b/orchid-base/src/location.rs @@ -15,7 +15,7 @@ trait_set! { pub trait GetSrc = FnMut(&Sym) -> Tok; } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Pos { None, SlotTarget, diff --git a/orchid-base/src/name.rs b/orchid-base/src/name.rs index a447be7..df893a6 100644 --- a/orchid-base/src/name.rs +++ b/orchid-base/src/name.rs @@ -272,34 +272,34 @@ pub trait NameLike: /// Convert into held slice fn as_slice(&self) -> &[Tok] { Borrow::<[Tok]>::borrow(self) } /// Get iterator over tokens - fn iter(&self) -> impl NameIter + '_ { self.as_slice().iter().cloned() } + fn segs(&self) -> impl NameIter + '_ { self.as_slice().iter().cloned() } /// Get iterator over string segments fn str_iter(&self) -> impl Iterator + '_ { self.as_slice().iter().map(|t| t.as_str()) } /// Fully resolve the name for printing #[must_use] - fn to_strv(&self) -> Vec { self.iter().map(|s| s.to_string()).collect() } + fn to_strv(&self) -> Vec { self.segs().map(|s| s.to_string()).collect() } /// Format the name as an approximate filename - fn as_src_path(&self) -> String { format!("{}.orc", self.iter().join("/")) } + fn as_src_path(&self) -> String { format!("{}.orc", self.segs().join("/")) } /// Return the number of segments in the name - fn len(&self) -> NonZeroUsize { - NonZeroUsize::try_from(self.iter().count()).expect("NameLike never empty") + fn len_nz(&self) -> NonZeroUsize { + NonZeroUsize::try_from(self.segs().count()).expect("NameLike never empty") } /// Like slice's `split_first` except we know that it always returns Some - fn split_first(&self) -> (Tok, &[Tok]) { + fn split_first_seg(&self) -> (Tok, &[Tok]) { let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty"); (foot.clone(), torso) } /// Like slice's `split_last` except we know that it always returns Some - fn split_last(&self) -> (Tok, &[Tok]) { + fn split_last_seg(&self) -> (Tok, &[Tok]) { let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty"); (foot.clone(), torso) } /// Get the first element - fn first(&self) -> Tok { self.split_first().0 } + fn first_seg(&self) -> Tok { self.split_first_seg().0 } /// Get the last element - fn last(&self) -> Tok { self.split_last().0 } + fn last_seg(&self) -> Tok { self.split_last_seg().0 } } impl NameLike for Sym {} diff --git a/orchid-base/src/parse.rs b/orchid-base/src/parse.rs index 3644cf6..a09018d 100644 --- a/orchid-base/src/parse.rs +++ b/orchid-base/src/parse.rs @@ -7,12 +7,12 @@ use futures::future::join_all; use itertools::Itertools; use crate::api; -use crate::error::{OrcRes, Reporter, mk_err, mk_errv}; -use crate::format::fmt; +use crate::error::{OrcErrv, OrcRes, Reporter, mk_err, mk_errv}; +use crate::format::{FmtCtx, FmtUnit, Format, fmt}; use crate::interner::{Interner, Tok}; use crate::location::SrcRange; use crate::name::{Sym, VName, VPath}; -use crate::tree::{ExprRepr, ExtraTok, Paren, TokTree, Token, ttv_range}; +use crate::tree::{ExprRepr, ExtraTok, Paren, TokTree, Token, ttv_fmt, ttv_range}; pub trait ParseCtx { #[must_use] @@ -95,18 +95,10 @@ impl Deref for Snippet<'_, A, X> { type Target = [TokTree]; fn deref(&self) -> &Self::Target { self.cur } } - -/// Remove tokens that aren't meaningful in expression context, such as comments -/// or line breaks -pub fn strip_fluff(tt: &TokTree) -> Option> { - let tok = match &tt.tok { - Token::BR => return None, - Token::Comment(_) => return None, - Token::LambdaHead(arg) => Token::LambdaHead(arg.iter().filter_map(strip_fluff).collect()), - Token::S(p, b) => Token::S(*p, b.iter().filter_map(strip_fluff).collect()), - t => t.clone(), - }; - Some(TokTree { tok, sr: tt.sr.clone() }) +impl Format for Snippet<'_, A, X> { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + ttv_fmt(&**self, c).await + } } #[derive(Clone, Debug)] @@ -208,6 +200,15 @@ pub async fn expect_tok<'a, A: ExprRepr, X: ExtraTok>( } } +pub async fn token_errv( + ctx: &impl ParseCtx, + tok: &TokTree, + description: &'static str, + message: impl FnOnce(&str) -> String, +) -> OrcErrv { + mk_errv(ctx.i().i(description).await, message(&fmt(tok, ctx.i()).await), [tok.sr.pos()]) +} + pub struct Parsed<'a, T, H: ExprRepr, X: ExtraTok> { pub output: T, pub tail: Snippet<'a, H, X>, diff --git a/orchid-base/src/tree.rs b/orchid-base/src/tree.rs index f082af3..b3c4a1e 100644 --- a/orchid-base/src/tree.rs +++ b/orchid-base/src/tree.rs @@ -62,8 +62,7 @@ pub fn recur( tok @ (Token::BR | Token::Bottom(_) | Token::Comment(_) | Token::Name(_)) => tok, tok @ (Token::Handle(_) | Token::NewExpr(_)) => tok, Token::NS(n, b) => Token::NS(n, Box::new(recur(*b, f))), - Token::LambdaHead(arg) => - Token::LambdaHead(arg.into_iter().map(|tt| recur(tt, f)).collect_vec()), + Token::LambdaHead(arg) => Token::LambdaHead(Box::new(recur(*arg, f))), Token::S(p, b) => Token::S(p, b.into_iter().map(|tt| recur(tt, f)).collect_vec()), }; TokTree { sr: range, tok } @@ -117,7 +116,7 @@ impl TokTree { NS(n => Tok::from_api(*n, i).await, b => Box::new(Self::from_api(b, hctx, xctx, src, i).boxed_local().await)), Bottom(e => OrcErrv::from_api(e, i).await), - LambdaHead(arg => ttv_from_api(arg, hctx, xctx, src, i).await), + LambdaHead(arg => Box::new(Self::from_api(arg, hctx, xctx, src, i).boxed_local().await)), Name(n => Tok::from_api(*n, i).await), S(*par, b => ttv_from_api(b, hctx, xctx, src, i).await), Comment(c.clone()), @@ -137,7 +136,7 @@ impl TokTree { NS(n.to_api(), b => Box::new(b.into_api(hctx, xctx).boxed_local().await)), Bottom(e.to_api()), Comment(c.clone()), - LambdaHead(arg => ttv_into_api(arg, hctx, xctx).boxed_local().await), + LambdaHead(arg => Box::new(arg.into_api(hctx, xctx).boxed_local().await)), Name(nn.to_api()), S(p, b => ttv_into_api(b, hctx, xctx).boxed_local().await), Handle(hand.into_api(hctx).await), @@ -153,18 +152,18 @@ impl TokTree { pub fn as_s(&self, par: Paren) -> Option> { self.tok.as_s(par).map(|slc| Snippet::new(self, slc)) } - pub fn as_lambda(&self) -> Option> { + pub fn as_lambda(&self) -> Option<&Self> { match &self.tok { - Token::LambdaHead(arg) => Some(Snippet::new(self, arg)), + Token::LambdaHead(arg) => Some(&**arg), _ => None, } } pub fn is_fluff(&self) -> bool { matches!(self.tok, Token::Comment(_) | Token::BR) } - pub fn lambda(arg: Vec, mut body: Vec) -> Self { - let arg_range = ttv_range(&arg).expect("Lambda with empty arg!"); + pub fn lambda(arg: Self, mut body: Vec) -> Self { + let arg_range = arg.sr(); let mut s_range = arg_range.clone(); s_range.range.end = body.last().expect("Lambda with empty body!").sr.range.end; - body.insert(0, Token::LambdaHead(arg).at(arg_range)); + body.insert(0, Token::LambdaHead(Box::new(arg)).at(arg_range)); Token::S(Paren::Round, body).at(s_range) } pub fn sr(&self) -> SrcRange { self.sr.clone() } @@ -230,7 +229,7 @@ pub enum Token { Comment(Rc), /// The part of a lambda between `\` and `.` enclosing the argument. The body /// stretches to the end of the enclosing parens or the end of the const line - LambdaHead(Vec>), + LambdaHead(Box>), /// A binding, operator, or a segment of a namespaced::name Name(Tok), /// A namespace prefix, like `my_ns::` followed by a token @@ -267,7 +266,7 @@ impl Format for Token { Self::Comment(c) => format!("--[{c}]--").into(), Self::LambdaHead(arg) => tl_cache!(Rc: Rc::new(Variants::default().bounded("\\{0b}."))) - .units([ttv_fmt(arg, c).await]), + .units([arg.print(c).boxed_local().await]), Self::NS(n, b) => tl_cache!(Rc: Rc::new(Variants::default().bounded("{0}::{1l}"))) .units([n.to_string().into(), b.print(c).boxed_local().await]), Self::Name(n) => format!("{n}").into(), diff --git a/orchid-extension/src/atom_owned.rs b/orchid-extension/src/atom_owned.rs index 399b2e8..7b82024 100644 --- a/orchid-extension/src/atom_owned.rs +++ b/orchid-extension/src/atom_owned.rs @@ -23,7 +23,7 @@ use orchid_base::name::Sym; use crate::api; use crate::atom::{ AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet, - MethodSetBuilder, err_not_callable, err_not_command, get_info, + MethodSetBuilder, TypAtom, err_not_callable, err_not_command, get_info, }; use crate::expr::Expr; use crate::gen_expr::{GExpr, bot}; @@ -68,6 +68,7 @@ impl Deref for AtomReadGuard<'_> { fn deref(&self) -> &Self::Target { &**self.guard.get(&self.id).unwrap() } } +/// Remove an atom from the store pub(crate) async fn take_atom(id: api::AtomId, ctx: &SysCtx) -> Box { let mut g = ctx.get_or_default::().objects.write().await; g.remove(&id).unwrap_or_else(|| panic!("Received invalid atom ID: {}", id.0)) @@ -313,3 +314,11 @@ struct ObjStore { objects: RwLock>>, } impl SysCtxEntry for ObjStore {} + +pub async fn get_own_instance(typ: TypAtom) -> A { + let ctx = typ.data.ctx(); + let g = ctx.get_or_default::().objects.read().await; + let dyn_atom = (g.get(&typ.data.atom.drop.expect("Owned atoms always have a drop ID"))) + .expect("Atom ID invalid; atom type probably not owned by this crate"); + dyn_atom.as_any_ref().downcast_ref().cloned().expect("The ID should imply a type as well") +} diff --git a/orchid-extension/src/conv.rs b/orchid-extension/src/conv.rs index 7f9f48f..ecd9878 100644 --- a/orchid-extension/src/conv.rs +++ b/orchid-extension/src/conv.rs @@ -1,5 +1,6 @@ use std::future::Future; +use never::Never; use orchid_base::error::{OrcErr, OrcRes, mk_err}; use orchid_base::interner::Interner; use orchid_base::location::Pos; @@ -7,7 +8,7 @@ use orchid_base::location::Pos; use crate::atom::{AtomicFeatures, ToAtom, TypAtom}; use crate::expr::Expr; use crate::gen_expr::{GExpr, atom, bot}; -use crate::system::downcast_atom; +use crate::system::{SysCtx, downcast_atom}; pub trait TryFromExpr: Sized { fn try_from_expr(expr: Expr) -> impl Future>; @@ -43,6 +44,10 @@ impl TryFromExpr for TypAtom { } } +impl TryFromExpr for SysCtx { + async fn try_from_expr(expr: Expr) -> OrcRes { Ok(expr.ctx()) } +} + pub trait ToExpr { fn to_expr(self) -> GExpr; } @@ -66,3 +71,7 @@ impl ToExpr for OrcRes { impl ToExpr for A { fn to_expr(self) -> GExpr { atom(self) } } + +impl ToExpr for Never { + fn to_expr(self) -> GExpr { match self {} } +} diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index d30ea0c..b59b16c 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -17,6 +17,7 @@ use orchid_api_traits::{Decode, UnderRoot, enc_vec}; use orchid_base::builtin::{ExtInit, ExtPort, Spawner}; use orchid_base::char_filter::{char_filter_match, char_filter_union, mk_char_filter}; use orchid_base::clone; +use orchid_base::error::Reporter; use orchid_base::interner::{Interner, Tok}; use orchid_base::logging::Logger; use orchid_base::name::Sym; @@ -189,10 +190,13 @@ pub fn extension_init( }) .collect() .await; + let prelude = + cted.inst().dyn_prelude(&i).await.iter().map(|sym| sym.to_api()).collect(); let record = SystemRecord { ctx, lazy_members: lazy_mems.into_inner() }; let systems = systems_weak.upgrade().expect("System constructed during shutdown"); systems.lock().await.insert(new_sys.id, record); - let response = api::NewSystemResponse { lex_filter, const_root, line_types: vec![] }; + let response = + api::NewSystemResponse { lex_filter, const_root, line_types: vec![], prelude }; hand.handle(&new_sys, &response).await }, api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) => { @@ -262,8 +266,10 @@ pub fn extension_init( let parser = parsers.iter().find(|p| p.line_head() == **name).expect("No parser candidate"); let module = Sym::from_api(*module, ctx.i()).await; - let pctx = ParsCtx::new(ctx.clone(), module); - let o_line = match parser.parse(pctx, *exported, comments, tail).await { + let reporter = Reporter::new(); + let pctx = ParsCtx::new(ctx.clone(), module, &reporter); + let parse_res = parser.parse(pctx, *exported, comments, tail).await; + let o_line = match reporter.merge(parse_res) { Err(e) => Err(e.to_api()), Ok(t) => Ok(linev_into_api(t, ctx.clone(), &hand).await), }; diff --git a/orchid-extension/src/parser.rs b/orchid-extension/src/parser.rs index 21cd2b7..5fb9280 100644 --- a/orchid-extension/src/parser.rs +++ b/orchid-extension/src/parser.rs @@ -4,12 +4,12 @@ use futures::FutureExt; use futures::future::{LocalBoxFuture, join_all}; use itertools::Itertools; use orchid_api::ResolveNames; -use orchid_base::error::OrcRes; +use orchid_base::error::{OrcRes, Reporter}; use orchid_base::id_store::IdStore; -use orchid_base::interner::Tok; +use orchid_base::interner::{Interner, Tok}; use orchid_base::location::SrcRange; use orchid_base::name::Sym; -use orchid_base::parse::{Comment, Snippet}; +use orchid_base::parse::{Comment, ParseCtx, Snippet}; use orchid_base::reqnot::{ReqHandlish, Requester}; use orchid_base::tree::ttv_into_api; @@ -61,12 +61,19 @@ pub struct ParsCtx<'a> { _parse: PhantomData<&'a mut ()>, ctx: SysCtx, module: Sym, + reporter: &'a Reporter, } -impl ParsCtx<'_> { - pub(crate) fn new(ctx: SysCtx, module: Sym) -> Self { Self { _parse: PhantomData, ctx, module } } +impl<'a> ParsCtx<'a> { + pub(crate) fn new(ctx: SysCtx, module: Sym, reporter: &'a Reporter) -> Self { + Self { _parse: PhantomData, ctx, module, reporter } + } pub fn ctx(&self) -> &SysCtx { &self.ctx } pub fn module(&self) -> Sym { self.module.clone() } } +impl ParseCtx for ParsCtx<'_> { + fn i(&self) -> &Interner { self.ctx.i() } + fn reporter(&self) -> &Reporter { self.reporter } +} type BoxConstCallback = Box LocalBoxFuture<'static, GExpr>>; @@ -94,8 +101,10 @@ impl ParsedLine { ParsedMemKind::Const(cb) => api::ParsedMemberKind::Constant(api::ParsedConstId( ctx.get_or_default::().consts.add(cb).id(), )), - ParsedMemKind::Mod(plv) => - api::ParsedMemberKind::Module(linev_into_api(plv, ctx, hand).boxed_local().await), + ParsedMemKind::Mod { lines, use_prelude } => api::ParsedMemberKind::Module { + lines: linev_into_api(lines, ctx, hand).boxed_local().await, + use_prelude, + }, }, }), ParsedLineKind::Rec(tv) => @@ -119,20 +128,26 @@ pub enum ParsedLineKind { } pub struct ParsedMem { - name: Tok, - exported: bool, - kind: ParsedMemKind, + pub name: Tok, + pub exported: bool, + pub kind: ParsedMemKind, } pub enum ParsedMemKind { Const(BoxConstCallback), - Mod(Vec), + Mod { lines: Vec, use_prelude: bool }, } impl ParsedMemKind { pub fn cnst GExpr + 'static>(f: F) -> Self { Self::Const(Box::new(|ctx| Box::pin(f(ctx)))) } + pub fn module(lines: impl IntoIterator) -> Self { + Self::Mod { lines: lines.into_iter().collect(), use_prelude: true } + } + pub fn clean_module(lines: impl IntoIterator) -> Self { + Self::Mod { lines: lines.into_iter().collect(), use_prelude: false } + } } /* TODO: how the macro runner uses the multi-stage loader diff --git a/orchid-extension/src/system.rs b/orchid-extension/src/system.rs index 8845a2e..8cb44cf 100644 --- a/orchid-extension/src/system.rs +++ b/orchid-extension/src/system.rs @@ -13,6 +13,7 @@ use orchid_base::boxed_iter::BoxedIter; use orchid_base::builtin::Spawner; use orchid_base::interner::Interner; use orchid_base::logging::Logger; +use orchid_base::name::Sym; use orchid_base::reqnot::{Receipt, ReqNot}; use crate::api; @@ -81,6 +82,7 @@ impl DynSystemCard for T { /// System as defined by author pub trait System: Send + Sync + SystemCard + 'static { + fn prelude(i: &Interner) -> impl Future>; fn env() -> Vec; fn lexers() -> Vec; fn parsers() -> Vec; @@ -88,6 +90,7 @@ pub trait System: Send + Sync + SystemCard + 'static { } pub trait DynSystem: Send + Sync + DynSystemCard + 'static { + fn dyn_prelude<'a>(&'a self, i: &'a Interner) -> LocalBoxFuture<'a, Vec>; fn dyn_env(&self) -> Vec; fn dyn_lexers(&self) -> Vec; fn dyn_parsers(&self) -> Vec; @@ -96,6 +99,9 @@ pub trait DynSystem: Send + Sync + DynSystemCard + 'static { } impl DynSystem for T { + fn dyn_prelude<'a>(&'a self, i: &'a Interner) -> LocalBoxFuture<'a, Vec> { + Box::pin(Self::prelude(i)) + } fn dyn_env(&self) -> Vec { Self::env() } fn dyn_lexers(&self) -> Vec { Self::lexers() } fn dyn_parsers(&self) -> Vec { Self::parsers() } @@ -129,22 +135,6 @@ where A: AtomicFeatures { Ok(TypAtom { value, data: foreign }) } -// #[derive(Clone)] -// pub struct SysCtx { -// pub reqnot: ReqNot, -// pub spawner: Spawner, -// pub id: api::SysId, -// pub cted: CtedObj, -// pub logger: Logger, -// pub obj_store: ObjStore, -// pub i: Rc, -// } -// impl fmt::Debug for SysCtx { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// write!(f, "SysCtx({:?})", self.id) -// } -// } - #[derive(Clone)] pub struct SysCtx(Rc>>); impl SysCtx { diff --git a/orchid-host/src/extension.rs b/orchid-host/src/extension.rs index 6f725ff..86a50ac 100644 --- a/orchid-host/src/extension.rs +++ b/orchid-host/src/extension.rs @@ -293,6 +293,7 @@ impl Extension { pub fn downgrade(&self) -> WeakExtension { WeakExtension(Rc::downgrade(&self.0)) } } +#[derive(Clone)] pub struct WeakExtension(Weak); impl WeakExtension { #[must_use] diff --git a/orchid-host/src/lex.rs b/orchid-host/src/lex.rs index 3f7f4af..4ab1a0a 100644 --- a/orchid-host/src/lex.rs +++ b/orchid-host/src/lex.rs @@ -125,21 +125,12 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes { let end = tail.find(['\n', '\r']).map_or(tail.len(), |n| n - 1); ctx.push_pos(end as u32); ParsTok::Comment(Rc::new(tail[2..end].to_string())) - } else if ctx.strip_char('\\') { - let mut arg = Vec::new(); + } else if let Some(tail) = ctx.tail.strip_prefix('\\').filter(|t| t.starts_with(name_start)) { + // fanciness like \$placeh in templates is resolved in the macro engine. + ctx.set_tail(tail); + let arg = lex_once(ctx).boxed_local().await?; ctx.trim_ws(); - while !ctx.strip_char('.') { - if ctx.tail.is_empty() { - return Err(mk_errv( - ctx.ctx.i.i("Unclosed lambda").await, - "Lambdae started with \\ should separate arguments from body with .", - [SrcRange::new(start..start + 1, ctx.path)], - )); - } - arg.push(lex_once(ctx).boxed_local().await?); - ctx.trim_ws(); - } - ParsTok::LambdaHead(arg) + ParsTok::LambdaHead(Box::new(arg)) } else if let Some((lp, rp, paren)) = PARENS.iter().find(|(lp, ..)| ctx.strip_char(*lp)) { let mut body = Vec::new(); ctx.trim_ws(); diff --git a/orchid-host/src/parse.rs b/orchid-host/src/parse.rs index 3fb4de3..e00196a 100644 --- a/orchid-host/src/parse.rs +++ b/orchid-host/src/parse.rs @@ -1,10 +1,9 @@ -use futures::FutureExt; use futures::future::join_all; use itertools::Itertools; use orchid_base::error::{OrcRes, Reporter, mk_errv}; use orchid_base::format::fmt; use orchid_base::interner::{Interner, Tok}; -use orchid_base::name::{Sym, VPath}; +use orchid_base::name::Sym; use orchid_base::parse::{ Comment, Import, ParseCtx, Parsed, Snippet, expect_end, line_items, parse_multiname, try_pop_no_fluff, @@ -13,7 +12,7 @@ use orchid_base::tree::{Paren, TokTree, Token}; use substack::Substack; use crate::ctx::Ctx; -use crate::expr::{Expr, ExprKind, PathSetBuilder}; +use crate::expr::Expr; use crate::parsed::{Item, ItemKind, ParsedMember, ParsedMemberKind, ParsedModule}; use crate::system::System; @@ -110,9 +109,6 @@ pub async fn parse_exportable_item<'a>( let kind = if discr == ctx.i().i("mod").await { let (name, body) = parse_module(ctx, path, tail).await?; ItemKind::Member(ParsedMember { name, exported, kind: ParsedMemberKind::Mod(body) }) - } else if discr == ctx.i().i("const").await { - let (name, expr) = parse_const(ctx, tail, path.clone()).await?; - ItemKind::Member(ParsedMember { name, exported, kind: ParsedMemberKind::ParsedConst(expr) }) } else if let Some(sys) = ctx.systems().find(|s| s.can_parse(discr.clone())) { return sys .parse(path, tail.to_vec(), exported, comments, &mut async |stack, lines| { @@ -156,107 +152,5 @@ pub async fn parse_module<'a>( )); }; let path = path.push(name.clone()); - Ok((name, ParsedModule::new(parse_items(ctx, path, body).await?))) -} - -pub async fn parse_const<'a>( - ctx: &impl HostParseCtx, - tail: ParsSnippet<'a>, - path: Substack<'_, Tok>, -) -> OrcRes<(Tok, Expr)> { - let Parsed { output, tail } = try_pop_no_fluff(ctx, tail).await?; - let Some(name) = output.as_name() else { - return Err(mk_errv( - ctx.i().i("Missing module name").await, - format!("A name was expected, {} was found", fmt(output, ctx.i()).await), - [output.sr()], - )); - }; - let Parsed { output, tail } = try_pop_no_fluff(ctx, tail).await?; - if !output.is_kw(ctx.i().i("=").await) { - return Err(mk_errv( - ctx.i().i("Missing = separator").await, - format!("Expected = , found {}", fmt(output, ctx.i()).await), - [output.sr()], - )); - } - try_pop_no_fluff(ctx, tail).await?; - // ctx.save_const(path, tail[..].to_vec()).await; - let final_path = - VPath::new(path.unreverse()).name_with_suffix(name.clone()).to_sym(ctx.i()).await; - let val = parse_expr(ctx, final_path, PathSetBuilder::new(), tail).await?; - Ok((name, val)) -} - -pub async fn parse_expr( - ctx: &impl HostParseCtx, - path: Sym, - psb: PathSetBuilder<'_, Tok>, - tail: ParsSnippet<'_>, -) -> OrcRes { - let Some((last_idx, _)) = (tail.iter().enumerate().find(|(_, tt)| tt.as_lambda().is_some())) - .or_else(|| tail.iter().enumerate().rev().find(|(_, tt)| !tt.is_fluff())) - else { - return Err(mk_errv(ctx.i().i("Empty expression").await, "Expression ends abruptly here", [ - tail.sr(), - ])); - }; - let (function, value) = tail.split_at(last_idx as u32); - let pos = tail.sr().pos(); - if !function.iter().all(TokTree::is_fluff) { - let (f_psb, x_psb) = psb.split(); - let x_expr = parse_expr(ctx, path.clone(), x_psb, value).boxed_local().await?; - let f_expr = parse_expr(ctx, path, f_psb, function).boxed_local().await?; - return Ok(ExprKind::Call(f_expr, x_expr).at(pos)); - } - let Parsed { output: head, tail } = try_pop_no_fluff(ctx, value).await?; - match &head.tok { - Token::BR | Token::Comment(_) => panic!("Fluff skipped"), - Token::Bottom(b) => Ok(ExprKind::Bottom(b.clone()).at(pos.clone())), - Token::Handle(expr) => Ok(expr.clone()), - Token::NS(n, nametail) => { - let mut nametail = nametail; - let mut segments = vec![n.clone()]; - while let Token::NS(n, newtail) = &nametail.tok { - segments.push(n.clone()); - nametail = newtail; - } - let Token::Name(n) = &nametail.tok else { - return Err(mk_errv( - ctx.i().i("Loose namespace prefix in constant").await, - "Namespace prefixes in constants must be followed by names", - [pos], - )); - }; - segments.push(n.clone()); - Ok(ExprKind::Const(Sym::new(segments, ctx.i()).await.unwrap()).at(pos.clone())) - }, - Token::LambdaHead(h) => { - let [TokTree { tok: Token::Name(arg), .. }] = &h[..] else { - return Err(mk_errv( - ctx.i().i("Complex lambda binding in constant").await, - "Lambda args in constants must be identified by a single name", - [pos], - )); - }; - let lambda_builder = psb.lambda(arg); - let body = parse_expr(ctx, path.clone(), lambda_builder.stack(), tail).boxed_local().await?; - Ok(ExprKind::Lambda(lambda_builder.collect(), body).at(pos.clone())) - }, - Token::S(Paren::Round, body) => - parse_expr(ctx, path, psb, Snippet::new(head, body)).boxed_local().await, - Token::S(..) => - return Err(mk_errv( - ctx.i().i("Constants may only contain (), not [] or {}").await, - "It seems like you are trying to call a macro. Consider a 'let' line", - [pos], - )), - Token::Name(n) => - if psb.register_arg(n) { - Ok(ExprKind::Arg.at(pos)) - } else { - Ok(ExprKind::Const(Sym::new([n.clone()], ctx.i()).await.unwrap()).at(pos)) - }, - Token::NewExpr(ex) => Ok(ex.clone()), - } + Ok((name, ParsedModule::new(true, parse_items(ctx, path, body).await?))) } diff --git a/orchid-host/src/parsed.rs b/orchid-host/src/parsed.rs index 9ccf4c4..c2bdc7c 100644 --- a/orchid-host/src/parsed.rs +++ b/orchid-host/src/parsed.rs @@ -1,8 +1,8 @@ -use std::fmt::Debug; +use std::fmt::{self, Debug}; use std::rc::Rc; use futures::FutureExt; -use futures::future::join_all; +use futures::future::{LocalBoxFuture, join_all}; use hashbrown::HashSet; use itertools::Itertools; use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; @@ -26,6 +26,11 @@ pub struct Item { pub comments: Vec, pub kind: ItemKind, } +impl Item { + pub fn new(sr: SrcRange, kind: impl Into) -> Self { + Self { sr, comments: Vec::new(), kind: kind.into() } + } +} #[derive(Debug)] pub enum ItemKind { @@ -36,6 +41,12 @@ impl ItemKind { #[must_use] pub fn at(self, sr: SrcRange) -> Item { Item { comments: vec![], sr, kind: self } } } +impl From for ItemKind { + fn from(value: ParsedMember) -> Self { Self::Member(value) } +} +impl From for ItemKind { + fn from(value: Import) -> Self { Self::Import(value) } +} impl Format for Item { async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { @@ -43,9 +54,6 @@ impl Format for Item { let item_text = match &self.kind { ItemKind::Import(i) => format!("import {i}").into(), ItemKind::Member(mem) => match &mem.kind { - ParsedMemberKind::ParsedConst(expr) => - tl_cache!(Rc: Rc::new(Variants::default().bounded("const {0} = {1l}"))) - .units([mem.name.rc().into(), expr.print(c).await]), ParsedMemberKind::DeferredConst(_, sys) => tl_cache!(Rc: Rc::new(Variants::default().bounded("const {0} via {1}"))) .units([mem.name.rc().into(), sys.print(c).await]), @@ -67,6 +75,9 @@ pub struct ParsedMember { impl ParsedMember { #[must_use] pub fn name(&self) -> Tok { self.name.clone() } + pub fn new(exported: bool, name: Tok, kind: impl Into) -> Self { + Self { exported, name, kind: kind.into() } + } } impl Debug for ParsedMember { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -77,36 +88,53 @@ impl Debug for ParsedMember { } } +pub(crate) type ParsedExprCallback = + Rc Fn(&'a [Tok]) -> LocalBoxFuture<'a, Expr>>; + +pub struct ParsedExpr { + pub(crate) debug: String, + pub(crate) callback: ParsedExprCallback, +} +impl ParsedExpr { + pub async fn run(self, imported_names: &[Tok]) -> Expr { + (self.callback)(imported_names).await + } +} +impl fmt::Debug for ParsedExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.debug) } +} + #[derive(Debug)] pub enum ParsedMemberKind { DeferredConst(api::ParsedConstId, System), - ParsedConst(Expr), Mod(ParsedModule), } - -// TODO: cannot determine alias origin at this stage; parsed tree is never -// walkable! +impl From for ParsedMemberKind { + fn from(value: ParsedModule) -> Self { Self::Mod(value) } +} #[derive(Debug, Default)] pub struct ParsedModule { pub exports: Vec>, pub items: Vec, + pub use_prelude: bool, } impl ParsedModule { #[must_use] - pub fn new(items: impl IntoIterator) -> Self { + pub fn new(use_prelude: bool, items: impl IntoIterator) -> Self { let items = items.into_iter().collect_vec(); let exports = (items.iter()) .filter_map(|i| if let ItemKind::Member(m) = &i.kind { Some(m) } else { None }) .filter(|m| m.exported) .map(|m| m.name.clone()) .collect_vec(); - Self { exports, items } + Self { exports, items, use_prelude } } pub fn merge(&mut self, other: ParsedModule) { let mut swap = ParsedModule::default(); std::mem::swap(self, &mut swap); - *self = ParsedModule::new(swap.items.into_iter().chain(other.items)) + assert_eq!(self.use_prelude, other.use_prelude, "merging modules that disagree on prelude"); + *self = ParsedModule::new(self.use_prelude, swap.items.into_iter().chain(other.items)) } #[must_use] pub fn get_imports(&self) -> impl IntoIterator { @@ -134,8 +162,7 @@ impl Tree for ParsedModule { .find(|m| m.name == key) { match &member.kind { - ParsedMemberKind::DeferredConst(..) | ParsedMemberKind::ParsedConst(_) => - return ChildResult::Err(ChildErrorKind::Constant), + ParsedMemberKind::DeferredConst(..) => return ChildResult::Err(ChildErrorKind::Constant), ParsedMemberKind::Mod(m) => return ChildResult::Ok(m), } } diff --git a/orchid-host/src/system.rs b/orchid-host/src/system.rs index 26ec2c9..e9bb9a2 100644 --- a/orchid-host/src/system.rs +++ b/orchid-host/src/system.rs @@ -39,6 +39,7 @@ struct SystemInstData { lex_filter: api::CharFilter, id: api::SysId, line_types: Vec>, + prelude: Vec, pub(crate) const_paths: MemoMap, } impl Drop for SystemInstData { @@ -69,6 +70,11 @@ impl System { #[must_use] pub fn deps(&self) -> &[System] { &self.0.deps } #[must_use] + pub fn ctor(&self) -> SystemCtor { + (self.0.ext.system_ctors().find(|c| c.decl.id == self.0.decl_id).cloned()) + .expect("Ctor was used to create ext") + } + #[must_use] pub(crate) fn reqnot(&self) -> &ReqNot { self.0.ext.reqnot() } #[must_use] pub async fn get_tree(&self, id: api::TreeId) -> api::MemberKind { @@ -78,6 +84,8 @@ impl System { pub fn has_lexer(&self) -> bool { !self.0.lex_filter.0.is_empty() } #[must_use] pub fn can_lex(&self, c: char) -> bool { char_filter_match(&self.0.lex_filter, c) } + #[must_use] + pub fn prelude(&self) -> Vec { self.0.prelude.clone() } /// Have this system lex a part of the source. It is assumed that /// [Self::can_lex] was called and returned true. pub async fn lex>>( @@ -147,10 +155,10 @@ impl System { }; let name = ctx.i.ex(name).await; let mkind = match kind { - api::ParsedMemberKind::Module(items) => { + api::ParsedMemberKind::Module { lines, use_prelude } => { let items = - conv(items, module.push(name.clone()), callback, ctx).boxed_local().await?; - ParsedMemberKind::Mod(ParsedModule::new(items)) + conv(lines, module.push(name.clone()), callback, ctx).boxed_local().await?; + ParsedMemberKind::Mod(ParsedModule::new(use_prelude, items)) }, api::ParsedMemberKind::Constant(cid) => ParsedMemberKind::DeferredConst(cid, ctx.sys.clone()), @@ -199,7 +207,7 @@ impl System { let orig = self.0.const_paths.get(&orig).expect("origin for find_names invalid").clone(); let ctx = self.0.ctx.clone(); async move |rel| { - let cwd = orig.split_last().1; + let cwd = orig.split_last_seg().1; let abs = absolute_path(cwd, rel, &ctx.i).await.ok()?; let root_data = &mut *root.0.write().await; let walk_ctx = &mut (ctx.clone(), &mut root_data.consts); @@ -221,6 +229,7 @@ impl WeakSystem { pub fn upgrade(&self) -> Option { self.0.upgrade().map(System) } } +#[derive(Clone)] pub struct SystemCtor { pub(crate) decl: api::SystemDecl, pub(crate) ext: WeakExtension, @@ -228,6 +237,10 @@ pub struct SystemCtor { impl SystemCtor { #[must_use] pub fn name(&self) -> &str { &self.decl.name } + pub async fn name_tok(&self) -> Sym { + (Sym::parse(&self.decl.name, &self.ext.upgrade().expect("ext dropped early").ctx().i).await) + .expect("System cannot have empty name") + } #[must_use] pub fn priority(&self) -> NotNan { self.decl.priority } #[must_use] @@ -252,6 +265,7 @@ impl SystemCtor { line_types: join_all(sys_inst.line_types.iter().map(|m| Tok::from_api(*m, &ext.ctx().i))) .await, id, + prelude: join_all(sys_inst.prelude.iter().map(|tok| Sym::from_api(*tok, &ext.ctx().i))).await, const_paths: MemoMap::new(), })); let api_module_root = api::Module { diff --git a/orchid-host/src/tree.rs b/orchid-host/src/tree.rs index 30e9cf5..e9b72fe 100644 --- a/orchid-host/src/tree.rs +++ b/orchid-host/src/tree.rs @@ -9,12 +9,11 @@ use futures::{FutureExt, StreamExt, stream}; use hashbrown::HashMap; use hashbrown::hash_map::Entry; use itertools::Itertools; -use orchid_api::FetchParsedConst; use orchid_base::clone; use orchid_base::error::{OrcRes, Reporter, mk_err, mk_errv}; use orchid_base::interner::Tok; -use orchid_base::location::{Pos, SrcRange}; -use orchid_base::name::{Sym, VPath}; +use orchid_base::location::{CodeGenInfo, Pos}; +use orchid_base::name::{NameLike, Sym, VPath}; use orchid_base::reqnot::Requester; use crate::api; @@ -60,11 +59,12 @@ impl Root { let mut ref_this = self.0.write().await; let this = &mut *ref_this; let mut deferred_consts = HashMap::new(); + let mut consts = this.consts.clone(); let mut tfpctx = FromParsedCtx { pars_root: parsed, deferred_consts: &mut deferred_consts, + consts: &mut consts, pars_prefix: pars_prefix.clone(), - consts: &mut this.consts, root: &this.root, ctx: &this.ctx, rep, @@ -78,14 +78,13 @@ impl Root { )]); module = Module { imports: HashMap::new(), members } } - let mut consts = this.consts.clone(); let root = (this.root.merge(&module, this.ctx.clone(), &mut consts).await) .expect("Merge conflict between parsed and existing module"); let new = Root(Rc::new(RwLock::new(RootData { root, consts, ctx: this.ctx.clone() }))); *this.ctx.root.write().await = new.downgrade(); for (path, (sys_id, pc_id)) in deferred_consts { let sys = this.ctx.system_inst(sys_id).await.expect("System dropped since parsing"); - let api_expr = sys.reqnot().request(FetchParsedConst { id: pc_id, sys: sys.id() }).await; + let api_expr = sys.reqnot().request(api::FetchParsedConst { id: pc_id, sys: sys.id() }).await; let mut xp_ctx = ExprParseCtx { ctx: &this.ctx, exprs: sys.ext().exprs() }; let expr = Expr::from_api(&api_expr, PathSetBuilder::new(), &mut xp_ctx).await; new.0.write().await.consts.insert(path, expr); @@ -154,7 +153,7 @@ impl<'a> TreeFromApiCtx<'a> { #[derive(Clone, Debug, PartialEq, Eq)] pub struct ResolvedImport { target: Sym, - sr: SrcRange, + pos: Pos, } #[derive(Clone, Default)] @@ -234,6 +233,22 @@ impl Module { } } let mut imports = HashMap::new(); + if parsed.use_prelude { + let systems = ctx.ctx.systems.read().await; + for sys in systems.values().flat_map(|weak| weak.upgrade()) { + for prelude_item in sys.prelude() { + imports.insert( + prelude_item.last_seg(), + Ok(ResolvedImport { + target: prelude_item, + pos: CodeGenInfo::new_details(sys.ctor().name_tok().await, "In prelude", &ctx.ctx.i) + .await + .pos(), + }), + ); + } + } + } let conflicting_imports_msg = ctx.ctx.i.i("Conflicting imports").await; for (key, values) in imports_by_name { if values.len() == 1 { @@ -243,8 +258,10 @@ impl Module { match abs_path_res { Err(e) => ctx.rep.report(e.err_obj(&ctx.ctx.i, sr.pos(), &import.to_string()).await), Ok(abs_path) => { - imports - .insert(key, Ok(ResolvedImport { target: abs_path.to_sym(&ctx.ctx.i).await, sr })); + imports.insert( + key, + Ok(ResolvedImport { target: abs_path.to_sym(&ctx.ctx.i).await, pos: sr.pos() }), + ); }, } } else { @@ -263,7 +280,10 @@ impl Module { let values = stream::iter(values) .then(|(n, sr)| { clone!(key; async move { - ResolvedImport { target: n.to_vname().suffix([key.clone()]).to_sym(i).await, sr } + ResolvedImport { + target: n.to_vname().suffix([key.clone()]).to_sym(i).await, + pos: sr.pos(), + } }) }) .collect::>() @@ -278,7 +298,7 @@ impl Module { ctx.rep.report(mk_err( self_referential_msg.clone(), format!("import {} points to itself or a path within itself", &import.target), - [import.sr.pos().into()], + [import.pos.clone().into()], )); } } @@ -363,9 +383,9 @@ pub struct FromParsedCtx<'a> { pars_prefix: Sym, pars_root: &'a ParsedModule, root: &'a Module, - consts: &'a mut HashMap, rep: &'a Reporter, ctx: &'a Ctx, + consts: &'a mut HashMap, deferred_consts: &'a mut HashMap, } @@ -417,10 +437,6 @@ impl MemberKind { #[must_use] async fn from_parsed(parsed: &ParsedMemberKind, path: Sym, ctx: &mut FromParsedCtx<'_>) -> Self { match parsed { - ParsedMemberKind::ParsedConst(expr) => { - ctx.consts.insert(path, expr.clone()); - MemberKind::Const - }, ParsedMemberKind::DeferredConst(id, sys) => { ctx.deferred_consts.insert(path, (sys.id(), *id)); MemberKind::Const diff --git a/orchid-std/Cargo.toml b/orchid-std/Cargo.toml index 94b437b..96acc4b 100644 --- a/orchid-std/Cargo.toml +++ b/orchid-std/Cargo.toml @@ -21,6 +21,7 @@ orchid-extension = { version = "0.1.0", path = "../orchid-extension", features = ] } ordered-float = "5.0.0" rust_decimal = "1.36.0" +substack = "1.1.1" tokio = { version = "1.43.0", features = ["full"] } [dev-dependencies] diff --git a/orchid-std/src/lib.rs b/orchid-std/src/lib.rs index d054043..b90601b 100644 --- a/orchid-std/src/lib.rs +++ b/orchid-std/src/lib.rs @@ -1,4 +1,4 @@ -// mod macros; +mod macros; mod std; pub use std::number::num_atom::{Float, HomoArray, Int, Num}; diff --git a/orchid-std/src/macros/instantiate_tpl.rs b/orchid-std/src/macros/instantiate_tpl.rs new file mode 100644 index 0000000..20eb346 --- /dev/null +++ b/orchid-std/src/macros/instantiate_tpl.rs @@ -0,0 +1,94 @@ +use std::borrow::Cow; +use std::pin::Pin; +use std::rc::Rc; + +use futures::AsyncWrite; +use itertools::Itertools; +use never::Never; +use orchid_extension::atom::{Atomic, TypAtom}; +use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant, get_own_instance}; +use orchid_extension::conv::{ToExpr, TryFromExpr}; +use orchid_extension::expr::Expr; +use orchid_extension::gen_expr::GExpr; +use orchid_extension::system::SysCtx; + +use crate::macros::mactree::{MacTok, MacTree}; + +#[derive(Clone)] +pub struct InstantiateTplCall { + pub(crate) tpl: MacTree, + pub(crate) argc: usize, + pub(crate) argv: Vec, +} +impl Atomic for InstantiateTplCall { + type Variant = OwnedVariant; + type Data = (); +} +impl OwnedAtom for InstantiateTplCall { + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } + /// TODO: get serialization done for mactree + type Refs = Vec; + async fn serialize( + &self, + ctx: SysCtx, + write: Pin<&mut (impl AsyncWrite + ?Sized)>, + ) -> Self::Refs { + todo!() + } + async fn deserialize(ctx: impl DeserializeCtx, refs: Self::Refs) -> Self { todo!() } + // Technically must be supported but shouldn't actually ever be called + async fn call_ref(&self, arg: Expr) -> GExpr { + eprintln!( + "Copying partially applied instantiate_tpl call. This is an internal value.\ + \nIt should be fully consumed within generated code." + ); + self.clone().call(arg).await + } + async fn call(mut self, arg: Expr) -> GExpr { + let arg = match TypAtom::try_from_expr(arg).await { + Err(e) => return Err::(e).to_expr(), + Ok(t) => get_own_instance(t).await, + }; + self.argv.push(arg); + if self.argv.len() < self.argc { + return self.to_expr(); + } + instantiate_tpl(&self.tpl, &mut self.argv.into_iter(), &mut false).to_expr() + } +} +fn instantiate_tpl( + tpl: &MacTree, + argv: &mut impl Iterator, + changed: &mut bool, +) -> MacTree { + let tok = match &*tpl.tok { + MacTok::Slot => { + *changed = true; + return argv.next().expect("Not enough arguments to fill all slots!"); + }, + MacTok::Lambda(arg, body) => MacTok::Lambda( + ro(changed, |changed| instantiate_tpl(arg, argv, changed)), + instantiate_tpl_v(body, argv, changed), + ), + MacTok::Name(_) | MacTok::Value(_) => return tpl.clone(), + MacTok::Ph(_) => panic!("instantiate_tpl received a placeholder"), + MacTok::S(p, body) => MacTok::S(*p, instantiate_tpl_v(body, argv, changed)), + }; + if *changed { MacTree { pos: tpl.pos.clone(), tok: Rc::new(tok) } } else { tpl.clone() } +} +fn instantiate_tpl_v( + tpl: &[MacTree], + argv: &mut impl Iterator, + changed: &mut bool, +) -> Vec { + tpl.iter().map(|tree| ro(changed, |changed| instantiate_tpl(tree, argv, changed))).collect_vec() +} + +/// reverse "or". Inside, the flag is always false, but raising it will raise +/// the outside flag too. +fn ro(flag: &mut bool, cb: impl FnOnce(&mut bool) -> T) -> T { + let mut new_flag = false; + let val = cb(&mut new_flag); + *flag |= new_flag; + val +} diff --git a/orchid-std/src/macros/let_line.rs b/orchid-std/src/macros/let_line.rs new file mode 100644 index 0000000..593dee7 --- /dev/null +++ b/orchid-std/src/macros/let_line.rs @@ -0,0 +1,59 @@ +use futures::future::LocalBoxFuture; +use itertools::Itertools; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::interner::Tok; +use orchid_base::name::Sym; +use orchid_base::parse::{Comment, ParseCtx, Parsed, expect_tok, token_errv, try_pop_no_fluff}; +use orchid_extension::gen_expr::GExpr; +use orchid_extension::parser::{ + ConstCtx, GenSnippet, ParsCtx, ParsedLine, ParsedLineKind, ParsedMem, ParsedMemKind, Parser, +}; +use substack::Substack; + +type ExprGenerator = + Box FnOnce(ConstCtx, Substack<'a, Sym>) -> LocalBoxFuture<'a, GExpr>>; + +#[derive(Default)] +pub struct LetLine; +impl Parser for LetLine { + const LINE_HEAD: &'static str = "let"; + async fn parse<'a>( + ctx: ParsCtx<'a>, + exported: bool, + comments: Vec, + line: GenSnippet<'a>, + ) -> OrcRes> { + let Parsed { output: name_tok, tail } = try_pop_no_fluff(&ctx, line).await?; + let Some(name) = name_tok.as_name() else { + let err = token_errv(&ctx, name_tok, "Constant must have a name", |t| { + format!("Expected a name but found {t}") + }); + return Err(err.await); + }; + let Parsed { tail, .. } = expect_tok(&ctx, tail, ctx.i().i("=").await).await?; + fn do_tokv(line: GenSnippet<'_>) -> ExprGenerator { + let first: ExprGenerator = if let Some((idx, arg)) = + line.iter().enumerate().find_map(|(i, x)| Some((i, x.as_lambda()?))) + { + Box::new(move |ctx, stack| Box::pin(async move { + let name = ctx.names([]) + })) + } else { + + }; + todo!() + } + let expr_generator = do_tokv(tail); + Ok(vec![ParsedLine { + comments, + sr: line.sr(), + kind: ParsedLineKind::Mem(ParsedMem { + exported, + name, + kind: ParsedMemKind::cnst(async |ctx| expr_generator(ctx, Substack::Bottom).await), + }), + }]) + } +} + +fn update_names(tree: MacTree) \ No newline at end of file diff --git a/orchid-std/src/macros/macro_lib.rs b/orchid-std/src/macros/macro_lib.rs new file mode 100644 index 0000000..fd2763a --- /dev/null +++ b/orchid-std/src/macros/macro_lib.rs @@ -0,0 +1,20 @@ +use orchid_extension::atom::TypAtom; +use orchid_extension::atom_owned::get_own_instance; +use orchid_extension::tree::{GenMember, comments, fun, prefix}; + +use crate::Int; +use crate::macros::instantiate_tpl::InstantiateTplCall; +use crate::macros::mactree::MacTree; + +pub fn gen_macro_lib() -> Vec { + prefix("macros", [comments( + ["This is an internal function, you can't obtain a value of its argument type.", "hidden"], + fun(true, "instantiate_tpl", |tpl: TypAtom, right: Int| async move { + InstantiateTplCall { + tpl: get_own_instance(tpl).await, + argc: right.0.try_into().unwrap(), + argv: Vec::new(), + } + }), + )]) +} diff --git a/orchid-std/src/macros/macro_system.rs b/orchid-std/src/macros/macro_system.rs index ca3ab15..62c2570 100644 --- a/orchid-std/src/macros/macro_system.rs +++ b/orchid-std/src/macros/macro_system.rs @@ -1,14 +1,17 @@ use never::Never; +use orchid_base::interner::Interner; +use orchid_base::name::Sym; use orchid_base::reqnot::Receipt; use orchid_extension::atom::AtomDynfo; use orchid_extension::entrypoint::ExtReq; -use orchid_extension::fs::DeclFs; use orchid_extension::lexer::LexerObj; use orchid_extension::parser::ParserObj; use orchid_extension::system::{System, SystemCard}; use orchid_extension::system_ctor::SystemCtor; use orchid_extension::tree::GenMember; +use crate::macros::let_line::LetLine; +use crate::macros::macro_lib::gen_macro_lib; use crate::macros::mactree_lexer::MacTreeLexer; #[derive(Default)] @@ -27,8 +30,8 @@ impl SystemCard for MacroSystem { } impl System for MacroSystem { async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { match req {} } - fn vfs() -> orchid_extension::fs::DeclFs { DeclFs::Mod(&[]) } + async fn prelude(_: &Interner) -> Vec { vec![] } fn lexers() -> Vec { vec![&MacTreeLexer] } - fn parsers() -> Vec { vec![] } - fn env() -> Vec { vec![] } + fn parsers() -> Vec { vec![&LetLine] } + fn env() -> Vec { gen_macro_lib() } } diff --git a/orchid-std/src/macros/mactree.rs b/orchid-std/src/macros/mactree.rs index 70d3d3e..bc72bbc 100644 --- a/orchid-std/src/macros/mactree.rs +++ b/orchid-std/src/macros/mactree.rs @@ -39,7 +39,9 @@ pub enum MacTok { /// Only permitted in arguments to `instantiate_tpl` Slot, Value(Expr), - Lambda(Vec, Vec), + Lambda(MacTree, Vec), + /// Only permitted in "pattern" values produced by macro blocks, which are + /// never accessed as variables by usercode Ph(Ph), } impl Format for MacTok { @@ -50,7 +52,7 @@ impl Format for MacTok { tl_cache!(Rc: Rc::new(Variants::default() .unbounded("\\{0b}.{1l}") .bounded("(\\{0b}.{1b})"))), - [mtreev_fmt(arg, c).await, mtreev_fmt(b, c).await], + [arg.print(c).await, mtreev_fmt(b, c).await], ), Self::Name(n) => format!("{n}").into(), Self::Ph(ph) => format!("{ph}").into(), @@ -96,3 +98,41 @@ pub enum PhKind { Scalar, Vector { at_least_one: bool, priority: u8 }, } + +pub fn map_mactree( + tpl: &MacTree, + map: &mut impl FnMut(MacTree) -> Option, + argv: &mut impl Iterator, + changed: &mut bool, +) -> MacTree { + let tok = match &*tpl.tok { + MacTok::Slot => { + *changed = true; + return argv.next().expect("Not enough arguments to fill all slots!"); + }, + MacTok::Lambda(arg, body) => MacTok::Lambda( + ro(changed, |changed| instantiate_tpl(arg, argv, changed)), + instantiate_tpl_v(body, argv, changed), + ), + MacTok::Name(_) | MacTok::Value(_) => return tpl.clone(), + MacTok::Ph(_) => panic!("instantiate_tpl received a placeholder"), + MacTok::S(p, body) => MacTok::S(*p, instantiate_tpl_v(body, argv, changed)), + }; + if *changed { MacTree { pos: tpl.pos.clone(), tok: Rc::new(tok) } } else { tpl.clone() } +} +pub fn map_mactree_v( + tpl: &[MacTree], + argv: &mut impl Iterator, + changed: &mut bool, +) -> Vec { + tpl.iter().map(|tree| ro(changed, |changed| instantiate_tpl(tree, argv, changed))).collect_vec() +} + +/// reverse "or". Inside, the flag is always false, but raising it will raise +/// the outside flag too. +fn ro(flag: &mut bool, cb: impl FnOnce(&mut bool) -> T) -> T { + let mut new_flag = false; + let val = cb(&mut new_flag); + *flag |= new_flag; + val +} diff --git a/orchid-std/src/macros/mod.rs b/orchid-std/src/macros/mod.rs index 1c4d9c1..971e94f 100644 --- a/orchid-std/src/macros/mod.rs +++ b/orchid-std/src/macros/mod.rs @@ -1,3 +1,6 @@ +mod instantiate_tpl; +mod let_line; +mod macro_lib; mod macro_system; pub mod mactree; mod mactree_lexer; diff --git a/orchid-std/src/std/std_system.rs b/orchid-std/src/std/std_system.rs index 4a7c5b8..c3ad7f2 100644 --- a/orchid-std/src/std/std_system.rs +++ b/orchid-std/src/std/std_system.rs @@ -1,5 +1,8 @@ use never::Never; +use orchid_base::interner::Interner; +use orchid_base::name::Sym; use orchid_base::reqnot::Receipt; +use orchid_base::sym; use orchid_extension::atom::{AtomDynfo, AtomicFeatures}; use orchid_extension::entrypoint::ExtReq; use orchid_extension::lexer::LexerObj; @@ -36,4 +39,5 @@ impl System for StdSystem { fn lexers() -> Vec { vec![&StringLexer, &NumLexer] } fn parsers() -> Vec { vec![] } fn env() -> Vec { merge_trivial([gen_num_lib(), gen_str_lib()]) } + async fn prelude(i: &Interner) -> Vec { vec![sym!(std; i).await] } } diff --git a/orcx/src/main.rs b/orcx/src/main.rs index 93a315f..383f772 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -25,8 +25,10 @@ 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}; @@ -194,6 +196,7 @@ async fn main() -> io::Result { }, 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()); @@ -206,25 +209,18 @@ async fn main() -> io::Result { }, } } - let lexemes = - lex(i.i(code.trim()).await, sym!(usercode; i).await, &systems, ctx).await.unwrap(); + 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 path = sym!(usercode; i).await; 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; + 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) => { @@ -233,6 +229,12 @@ async fn main() -> io::Result { 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; diff --git a/orcx/src/parse_folder.rs b/orcx/src/parse_folder.rs index 5bb2530..e020ab7 100644 --- a/orcx/src/parse_folder.rs +++ b/orcx/src/parse_folder.rs @@ -63,7 +63,7 @@ pub async fn parse_folder( Ok(Some(module)) => items.push(module.default_item(name.clone(), sr.clone())), } } - Ok(Some(ParsedModule::new(items))) + 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; @@ -81,9 +81,9 @@ pub async fn parse_folder( 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([]))) }; + 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(items))) + Ok(Some(ParsedModule::new(false, items))) } else { Ok(None) }