diff --git a/SWAP.md b/SWAP.md index 9c5f30d..8b9dbdf 100644 --- a/SWAP.md +++ b/SWAP.md @@ -1,6 +1,8 @@ -Since the macro AST is built as a custom tokenizer inside the system, it needs access to the import set. On the other hand, import sets aren't available until after parsing. Need a way to place this order in a lexer without restricting the expression value of the lexer. +Decide whether we need patterns at runtime. Maybe macros aren't obligated to return MacTree so destructuring can be done in a safer and easier way? -The daft option of accepting import resolution queries at runtime is available but consider better options. +Double-check type and templating logic in the note, it's a bit fishy. + +Consider whether all macros need to be loaded or the const references could be used to pre-filter for a given let line. ## alternate extension mechanism diff --git a/orchid-base/src/error.rs b/orchid-base/src/error.rs index 0252ecd..36369fa 100644 --- a/orchid-base/src/error.rs +++ b/orchid-base/src/error.rs @@ -158,6 +158,40 @@ impl fmt::Display for OrcErrv { pub type OrcRes = Result; +pub fn join_ok(left: OrcRes, right: OrcRes) -> OrcRes<(T, U)> { + match (left, right) { + (Ok(t), Ok(u)) => Ok((t, u)), + (Err(e), Ok(_)) | (Ok(_), Err(e)) => Err(e), + (Err(e1), Err(e2)) => Err(e1 + e2), + } +} + +#[macro_export] +macro_rules! join_ok { + ($($names:ident $(: $tys:ty)? = $vals:expr;)*) => { + let $crate::join_ok!(@NAMES $($names $(: $tys)? = $vals;)*) + : + $crate::join_ok!(@TYPES $($names $(: $tys)? = $vals;)*) + = + $crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*)?; + }; + (@NAMES $name:ident $(: $ty:ty)? = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => { + ($name, $crate::join_ok!(@NAMES $($names $(: $tys)? = $vals;)*)) + }; + (@NAMES) => { _ }; + (@TYPES $name:ident : $ty:ty = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => { + ($ty, $crate::join_ok!(@TYPES $($names $(: $tys)? = $vals;)*)) + }; + (@TYPES $name:ident = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => { + (_, $crate::join_ok!(@TYPES $($names $(: $tys)? = $vals;)*)) + }; + (@TYPES) => { () }; + (@VALUES $name:ident $(: $ty:ty)? = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => { + $crate::error::join_ok($val, $crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*)) + }; + (@VALUES) => { Ok(()) }; +} + pub fn mk_err( description: Tok, message: impl AsRef, diff --git a/orchid-base/src/location.rs b/orchid-base/src/location.rs index 57469c7..6a6d145 100644 --- a/orchid-base/src/location.rs +++ b/orchid-base/src/location.rs @@ -108,6 +108,10 @@ impl SrcRange { pub fn to_api(&self) -> api::SourceRange { api::SourceRange { path: self.path.to_api(), range: self.range.clone() } } + pub fn to(&self, rhs: &Self) -> Self { + assert_eq!(self.path, rhs.path, "Range continues across files"); + Self { path: self.path(), range: self.start().min(rhs.start())..self.end().max(rhs.end()) } + } } impl From for ErrPos { fn from(val: SrcRange) -> Self { val.pos().into() } diff --git a/orchid-base/src/name.rs b/orchid-base/src/name.rs index df893a6..b886402 100644 --- a/orchid-base/src/name.rs +++ b/orchid-base/src/name.rs @@ -236,8 +236,8 @@ impl Sym { Self::from_tok(Tok::from_api(marker, i).await).expect("Empty sequence found for serialized Sym") } pub fn to_api(&self) -> api::TStrv { self.tok().to_api() } - pub async fn push(&self, tok: Tok, i: &Interner) -> Sym { - Self::new(self.0.iter().cloned().chain([tok]), i).await.unwrap() + pub async fn suffix(&self, tokv: impl IntoIterator>, i: &Interner) -> Sym { + Self::new(self.0.iter().cloned().chain(tokv), i).await.unwrap() } } impl fmt::Debug for Sym { diff --git a/orchid-base/src/parse.rs b/orchid-base/src/parse.rs index a09018d..092693a 100644 --- a/orchid-base/src/parse.rs +++ b/orchid-base/src/parse.rs @@ -11,14 +11,14 @@ 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::name::{NameLike, Sym, VName, VPath}; use crate::tree::{ExprRepr, ExtraTok, Paren, TokTree, Token, ttv_fmt, ttv_range}; pub trait ParseCtx { #[must_use] fn i(&self) -> &Interner; #[must_use] - fn reporter(&self) -> &Reporter; + fn rep(&self) -> &Reporter; } pub struct ParseCtxImpl<'a> { pub i: &'a Interner, @@ -26,7 +26,7 @@ pub struct ParseCtxImpl<'a> { } impl ParseCtx for ParseCtxImpl<'_> { fn i(&self) -> &Interner { self.i } - fn reporter(&self) -> &Reporter { self.r } + fn rep(&self) -> &Reporter { self.r } } pub fn name_start(c: char) -> bool { c.is_alphabetic() || c == '_' } @@ -237,7 +237,7 @@ pub async fn parse_multiname<'a, A: ExprRepr, X: ExtraTok>( match &tt.tok { Token::NS(ns, body) => { if !ns.starts_with(name_start) { - ctx.reporter().report(mk_err( + ctx.rep().report(mk_err( ctx.i().i("Unexpected name prefix").await, "Only names can precede ::", [ttpos.into()], @@ -257,7 +257,7 @@ pub async fn parse_multiname<'a, A: ExprRepr, X: ExtraTok>( while let Some((output, tail)) = body.pop_front() { match rec(output, ctx).boxed_local().await { Ok(names) => o.extend(names), - Err(e) => ctx.reporter().report(e), + Err(e) => ctx.rep().report(e), } body = tail; } @@ -296,6 +296,10 @@ impl Import { None => self.path.into_name().expect("Import cannot be empty"), } } + pub fn new(sr: SrcRange, path: VPath, name: Tok) -> Self { + Import { path, name: Some(name), sr } + } + pub fn new_glob(sr: SrcRange, path: VPath) -> Self { Import { path, name: None, sr } } } impl Display for Import { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/orchid-base/src/tree.rs b/orchid-base/src/tree.rs index b3c4a1e..85557b3 100644 --- a/orchid-base/src/tree.rs +++ b/orchid-base/src/tree.rs @@ -16,7 +16,7 @@ use crate::error::OrcErrv; use crate::format::{FmtCtx, FmtUnit, Format, Variants}; use crate::interner::{Interner, Tok}; use crate::location::{Pos, SrcRange}; -use crate::name::Sym; +use crate::name::{Sym, VName, VPath}; use crate::parse::Snippet; use crate::{api, match_mapping, tl_cache}; @@ -149,6 +149,20 @@ impl TokTree { pub fn as_name(&self) -> Option> { if let Token::Name(n) = &self.tok { Some(n.clone()) } else { None } } + pub fn as_multiname(&self) -> Result> { + let mut segs = VPath::new([]); + let mut cur = self; + loop { + match &cur.tok { + Token::Name(last) => return Ok(segs.name_with_suffix(last.clone())), + Token::NS(seg, inner) => { + segs = segs.suffix([seg.clone()]); + cur = inner; + }, + _ => return Err(cur), + } + } + } pub fn as_s(&self, par: Paren) -> Option> { self.tok.as_s(par).map(|slc| Snippet::new(self, slc)) } diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index b59b16c..36d4fc5 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -32,10 +32,10 @@ use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId}; use crate::atom_owned::take_atom; use crate::expr::{Expr, ExprHandle}; use crate::lexer::{LexContext, err_cascade, err_not_applicable}; -use crate::parser::{ParsCtx, get_const, linev_into_api}; +use crate::parser::{PTok, PTokTree, ParsCtx, get_const, linev_into_api}; use crate::system::{SysCtx, atom_by_idx}; use crate::system_ctor::{CtedObj, DynSystemCtor}; -use crate::tree::{GenTok, GenTokTree, LazyMemberFactory, TreeIntoApiCtxImpl}; +use crate::tree::{LazyMemberFactory, TreeIntoApiCtxImpl}; pub type ExtReq<'a> = RequestHandle<'a, api::ExtMsgSet>; pub type ExtReqNot = ReqNot; @@ -259,10 +259,10 @@ pub fn extension_init( let src = Sym::from_api(*src, ctx.i()).await; let comments = join_all(comments.iter().map(|c| Comment::from_api(c, src.clone(), &i))).await; - let line: Vec = ttv_from_api(line, &mut ctx, &mut (), &src, &i).await; + let line: Vec = ttv_from_api(line, &mut ctx, &mut (), &src, &i).await; let snip = Snippet::new(line.first().expect("Empty line"), &line); let (head, tail) = snip.pop_front().unwrap(); - let name = if let GenTok::Name(n) = &head.tok { n } else { panic!("No line head") }; + let name = if let PTok::Name(n) = &head.tok { n } else { panic!("No line head") }; let parser = parsers.iter().find(|p| p.line_head() == **name).expect("No parser candidate"); let module = Sym::from_api(*module, ctx.i()).await; diff --git a/orchid-extension/src/parser.rs b/orchid-extension/src/parser.rs index 24a9573..9e05429 100644 --- a/orchid-extension/src/parser.rs +++ b/orchid-extension/src/parser.rs @@ -4,6 +4,7 @@ use async_stream::stream; use futures::future::{LocalBoxFuture, join_all}; use futures::{FutureExt, Stream, StreamExt, pin_mut}; use itertools::Itertools; +use never::Never; use orchid_api::ResolveNames; use orchid_base::error::{OrcRes, Reporter}; use orchid_base::id_store::IdStore; @@ -12,7 +13,7 @@ use orchid_base::location::SrcRange; use orchid_base::name::Sym; use orchid_base::parse::{Comment, ParseCtx, Snippet}; use orchid_base::reqnot::{ReqHandlish, Requester}; -use orchid_base::tree::ttv_into_api; +use orchid_base::tree::{TokTree, Token, ttv_into_api}; use crate::api; use crate::expr::Expr; @@ -20,7 +21,9 @@ use crate::gen_expr::GExpr; use crate::system::{SysCtx, SysCtxEntry}; use crate::tree::GenTokTree; -pub type GenSnippet<'a> = Snippet<'a, Expr, GExpr>; +pub type PTok = Token; +pub type PTokTree = TokTree; +pub type PSnippet<'a> = Snippet<'a, Expr, Never>; pub trait Parser: Send + Sync + Sized + Default + 'static { const LINE_HEAD: &'static str; @@ -28,7 +31,7 @@ pub trait Parser: Send + Sync + Sized + Default + 'static { ctx: ParsCtx<'a>, exported: bool, comments: Vec, - line: GenSnippet<'a>, + line: PSnippet<'a>, ) -> impl Future>> + 'a; } @@ -39,7 +42,7 @@ pub trait DynParser: Send + Sync + 'static { ctx: ParsCtx<'a>, exported: bool, comments: Vec, - line: GenSnippet<'a>, + line: PSnippet<'a>, ) -> LocalBoxFuture<'a, OrcRes>>; } @@ -50,7 +53,7 @@ impl DynParser for T { ctx: ParsCtx<'a>, exported: bool, comments: Vec, - line: GenSnippet<'a>, + line: PSnippet<'a>, ) -> LocalBoxFuture<'a, OrcRes>> { Box::pin(async move { Self::parse(ctx, exported, comments, line).await }) } @@ -73,7 +76,7 @@ impl<'a> ParsCtx<'a> { } impl ParseCtx for ParsCtx<'_> { fn i(&self) -> &Interner { self.ctx.i() } - fn reporter(&self) -> &Reporter { self.reporter } + fn rep(&self) -> &Reporter { self.reporter } } type BoxConstCallback = Box LocalBoxFuture<'static, GExpr>>; @@ -174,26 +177,25 @@ pub struct ConstCtx { impl ConstCtx { pub fn names<'a>( &'a self, - names: impl IntoIterator + Clone + 'a, - ) -> impl Stream)> + 'a { + names: impl IntoIterator + 'a, + ) -> impl Stream> + 'a { let resolve_names = ResolveNames { constid: self.constid, sys: self.ctx.sys_id(), - names: names.clone().into_iter().map(|n| n.to_api()).collect_vec(), + names: names.into_iter().map(|n| n.to_api()).collect_vec(), }; stream! { - let new_names = self.ctx.reqnot().request(resolve_names).await; - for (name, name_opt) in names.into_iter().zip(new_names) { - yield (name.clone(), match name_opt { + for name_opt in self.ctx.reqnot().request(resolve_names).await { + yield match name_opt { None => None, Some(name) => Some(Sym::from_api(name, self.ctx.i()).await) - }) + } } } } pub async fn names_n(&self, names: [&Sym; N]) -> [Option; N] { let mut results = [const { None }; N]; - let names = self.names(names).enumerate().filter_map(|(i, n)| async move { Some((i, n.1?)) }); + let names = self.names(names).enumerate().filter_map(|(i, n)| async move { Some((i, n?)) }); pin_mut!(names); while let Some((i, name)) = names.next().await { results[i] = Some(name); diff --git a/orchid-host/src/parse.rs b/orchid-host/src/parse.rs index e00196a..6aa4ae0 100644 --- a/orchid-host/src/parse.rs +++ b/orchid-host/src/parse.rs @@ -26,7 +26,7 @@ pub struct HostParseCtxImpl<'a> { } impl ParseCtx for HostParseCtxImpl<'_> { - fn reporter(&self) -> &Reporter { self.rep } + fn rep(&self) -> &Reporter { self.rep } fn i(&self) -> &Interner { &self.ctx.i } } diff --git a/orchid-std/src/macros/instantiate_tpl.rs b/orchid-std/src/macros/instantiate_tpl.rs index 700169e..d218243 100644 --- a/orchid-std/src/macros/instantiate_tpl.rs +++ b/orchid-std/src/macros/instantiate_tpl.rs @@ -1,9 +1,7 @@ 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}; diff --git a/orchid-std/src/macros/let_line.rs b/orchid-std/src/macros/let_line.rs index 53f8a32..7caea15 100644 --- a/orchid-std/src/macros/let_line.rs +++ b/orchid-std/src/macros/let_line.rs @@ -1,20 +1,17 @@ -use futures::future::LocalBoxFuture; +use async_std::stream; +use async_stream::stream; +use futures::{FutureExt, StreamExt}; use hashbrown::HashMap; 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 orchid_base::error::OrcRes; +use orchid_base::parse::{ + Comment, ParseCtx, Parsed, Snippet, expect_tok, token_errv, try_pop_no_fluff, +}; +use orchid_extension::parser::{ + PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, ParsedLineKind, ParsedMem, ParsedMemKind, Parser, }; -use substack::Substack; -use crate::macros::mactree::{MacTok, MacTree, map_mactree, map_mactree_v}; - -type ExprGenerator = - Box FnOnce(ConstCtx, Substack<'a, Sym>) -> LocalBoxFuture<'a, GExpr>>; +use crate::macros::mactree::{MacTok, MacTree, map_mactree_v}; #[derive(Default)] pub struct LetLine; @@ -24,7 +21,7 @@ impl Parser for LetLine { ctx: ParsCtx<'a>, exported: bool, comments: Vec, - line: GenSnippet<'a>, + line: PSnippet<'a>, ) -> OrcRes> { let Parsed { output: name_tok, tail } = try_pop_no_fluff(&ctx, line).await?; let Some(name) = name_tok.as_name() else { @@ -34,19 +31,11 @@ impl Parser for LetLine { return Err(err.await); }; let Parsed { tail, .. } = expect_tok(&ctx, tail, ctx.i().i("=").await).await?; - fn parse_tokv(line: GenSnippet<'_>) -> Vec { - let first: MacTree = if let Some((idx, arg)) = - line.iter().enumerate().find_map(|(i, x)| Some((i, x.as_lambda()?))) - { - } else { - }; - todo!() - } let mut names = HashMap::new(); - let aliased = parse_tokv(tail); + let aliased = parse_tokv(tail, &ctx).await; map_mactree_v(&aliased, &mut false, &mut |tpl| { if let MacTok::Name(n) = &*tpl.tok { - names.insert(n.clone(), n); + names.insert(n.clone(), n.clone()); } None }); @@ -56,14 +45,76 @@ impl Parser for LetLine { kind: ParsedLineKind::Mem(ParsedMem { exported, name, - kind: ParsedMemKind::cnst(async |ctx| { - let mut names_str = ctx.names(names.keys()); - while let Some(()) = names_str.next().await { - names[] + kind: ParsedMemKind::cnst(async move |ctx| { + let keys = names.keys().cloned().collect_vec(); + let names_mut = &mut names; + stream! { + for await (canonical, local) in ctx.names(&keys).zip(stream::from_iter(&keys)) { + if let Some(name) = canonical { + *names_mut.get_mut(local).expect("Queried specifically the keys of this map") = name + } + } } - todo!() + .collect::<()>() + .await; + let macro_input = map_mactree_v(&aliased, &mut false, &mut |tree| match &*tree.tok { + MacTok::Name(n) => names.get(n).map(|new_n| MacTok::Name(new_n.clone()).at(tree.pos())), + _ => None, + }); + todo!("Run macros then convert this into an expr") }), }), }]) } } + +async fn parse_tokv(line: PSnippet<'_>, ctx: &ParsCtx<'_>) -> Vec { + if let Some((idx, arg)) = line.iter().enumerate().find_map(|(i, x)| Some((i, x.as_lambda()?))) { + let (head, lambda) = line.split_at(idx as u32); + let (_, body) = lambda.pop_front().unwrap(); + let body = parse_tokv(body, ctx).boxed_local().await; + let mut all = parse_tokv_no_lambdas(&head, ctx).await; + match parse_tok(arg, ctx).await { + Some(arg) => all.push(MacTok::Lambda(arg, body).at(lambda.sr().pos())), + None => ctx.rep().report( + token_errv(ctx, arg, "Lambda argument fluff", |arg| { + format!("Lambda arguments must be a valid token, found meaningless fragment {arg}") + }) + .await, + ), + }; + all + } else { + parse_tokv_no_lambdas(&line, ctx).await + } +} + +async fn parse_tokv_no_lambdas(line: &[PTokTree], ctx: &ParsCtx<'_>) -> Vec { + stream::from_iter(line).filter_map(|tt| parse_tok(tt, ctx)).collect().await +} + +async fn parse_tok(tree: &PTokTree, ctx: &ParsCtx<'_>) -> Option { + let tok = match &tree.tok { + PTok::Bottom(errv) => MacTok::Bottom(errv.clone()), + PTok::BR | PTok::Comment(_) => return None, + PTok::Name(n) => MacTok::Name(ctx.module().suffix([n.clone()], ctx.i()).await), + PTok::NS(..) => match tree.as_multiname() { + Ok(mn) => MacTok::Name(mn.to_sym(ctx.i()).await), + Err(nested) => { + ctx.rep().report( + token_errv(ctx, tree, ":: can only be followed by a name in an expression", |tok| { + format!("Expected name, found {tok}") + }) + .await, + ); + return parse_tok(nested, ctx).boxed_local().await; + }, + }, + PTok::Handle(expr) => MacTok::Value(expr.clone()), + PTok::NewExpr(never) => match *never {}, + PTok::LambdaHead(_) => panic!("Lambda-head handled in the sequence parser"), + PTok::S(p, body) => + MacTok::S(*p, parse_tokv(Snippet::new(tree, body), ctx).boxed_local().await), + }; + Some(tok.at(tree.sr().pos())) +} diff --git a/orchid-std/src/macros/mactree.rs b/orchid-std/src/macros/mactree.rs index c55fa07..04f47f9 100644 --- a/orchid-std/src/macros/mactree.rs +++ b/orchid-std/src/macros/mactree.rs @@ -2,14 +2,17 @@ use std::borrow::Cow; use std::fmt::Display; use std::rc::Rc; +use futures::FutureExt; use futures::future::join_all; use itertools::Itertools; use orchid_api::Paren; +use orchid_base::error::OrcErrv; use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; use orchid_base::interner::Tok; use orchid_base::location::Pos; use orchid_base::name::Sym; use orchid_base::tl_cache; +use orchid_base::tree::indent; use orchid_extension::atom::Atomic; use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant}; use orchid_extension::expr::Expr; @@ -20,7 +23,8 @@ pub struct MacTree { pub tok: Rc, } impl MacTree { - fn tok(&self) -> &MacTok { &*self.tok } + pub fn tok(&self) -> &MacTok { &*self.tok } + pub fn pos(&self) -> Pos { self.pos.clone() } } impl Atomic for MacTree { type Data = (); @@ -46,6 +50,12 @@ pub enum MacTok { /// Only permitted in "pattern" values produced by macro blocks, which are /// never accessed as variables by usercode Ph(Ph), + Bottom(OrcErrv), +} +impl MacTok { + pub fn at(self, pos: impl Into) -> MacTree { + MacTree { pos: pos.into(), tok: Rc::new(self) } + } } impl Format for MacTok { async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { @@ -55,7 +65,7 @@ impl Format for MacTok { tl_cache!(Rc: Rc::new(Variants::default() .unbounded("\\{0b}.{1l}") .bounded("(\\{0b}.{1b})"))), - [arg.print(c).await, mtreev_fmt(b, c).await], + [arg.print(c).boxed_local().await, mtreev_fmt(b, c).await], ), Self::Name(n) => format!("{n}").into(), Self::Ph(ph) => format!("{ph}").into(), @@ -68,6 +78,8 @@ impl Format for MacTok { [mtreev_fmt(body, c).await], ), Self::Slot => "SLOT".into(), + Self::Bottom(err) if err.len() == 1 => format!("Bottom({}) ", err.one().unwrap()).into(), + Self::Bottom(err) => format!("Botttom(\n{}) ", indent(&err.to_string())).into(), } } } @@ -117,7 +129,8 @@ pub fn map_mactree Option>( ro(changed, |changed| map_mactree(arg, changed, map)), map_mactree_v(body, changed, map), ), - MacTok::Name(_) | MacTok::Value(_) | MacTok::Slot | MacTok::Ph(_) => return src.clone(), + MacTok::Name(_) | MacTok::Value(_) | MacTok::Slot | MacTok::Ph(_) | MacTok::Bottom(_) => + return src.clone(), MacTok::S(p, body) => MacTok::S(*p, map_mactree_v(body, changed, map)), }, }; diff --git a/orchid-std/src/macros/rule/build.rs b/orchid-std/src/macros/rule/build.rs index ab73642..91737c0 100644 --- a/orchid-std/src/macros/rule/build.rs +++ b/orchid-std/src/macros/rule/build.rs @@ -1,5 +1,9 @@ +use futures::FutureExt; +use futures::future::join_all; use itertools::Itertools; -use orchid_base::interner::Tok; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::interner::{Interner, Tok}; +use orchid_base::join_ok; use orchid_base::side::Side; use super::shared::{AnyMatcher, ScalMatcher, VecMatcher}; @@ -28,24 +32,31 @@ fn scal_cnt<'a>(iter: impl Iterator) -> usize { } #[must_use] -pub fn mk_any(pattern: &[MacTree]) -> AnyMatcher { +pub async fn mk_any(pattern: &[MacTree], i: &Interner) -> OrcRes { let left_split = scal_cnt(pattern.iter()); if pattern.len() <= left_split { - return AnyMatcher::Scalar(mk_scalv(pattern)); + return Ok(AnyMatcher::Scalar(mk_scalv(pattern, i).await?)); } let (left, not_left) = pattern.split_at(left_split); let right_split = not_left.len() - scal_cnt(pattern.iter().rev()); let (mid, right) = not_left.split_at(right_split); - AnyMatcher::Vec { left: mk_scalv(left), mid: mk_vec(mid), right: mk_scalv(right) } + join_ok! { + left = mk_scalv(left, i).await; + mid = mk_vec(mid, i).await; + right = mk_scalv(right, i).await; + } + Ok(AnyMatcher::Vec { left, mid, right }) } /// Pattern MUST NOT contain vectorial placeholders #[must_use] -fn mk_scalv(pattern: &[MacTree]) -> Vec { pattern.iter().map(mk_scalar).collect() } +async fn mk_scalv(pattern: &[MacTree], i: &Interner) -> OrcRes> { + join_all(pattern.iter().map(|pat| mk_scalar(pat, i))).await.into_iter().collect() +} /// Pattern MUST start and end with a vectorial placeholder #[must_use] -pub fn mk_vec(pattern: &[MacTree]) -> VecMatcher { +pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes { debug_assert!(!pattern.is_empty(), "pattern cannot be empty"); debug_assert!(pattern.first().map(vec_attrs).is_some(), "pattern must start with a vectorial"); debug_assert!(pattern.last().map(vec_attrs).is_some(), "pattern must end with a vectorial"); @@ -57,39 +68,57 @@ pub fn mk_vec(pattern: &[MacTree]) -> VecMatcher { let (l_side, l_sep) = left.split_at(left.len() - l_sep_size); let main = VecMatcher::Placeh { key: key.clone(), nonzero }; match (left, right) { - (&[], &[]) => VecMatcher::Placeh { key, nonzero }, - (&[], _) => VecMatcher::Scan { - direction: Side::Left, - left: Box::new(main), - sep: mk_scalv(r_sep), - right: Box::new(mk_vec(r_side)), + (&[], &[]) => Ok(VecMatcher::Placeh { key, nonzero }), + (&[], _) => { + join_ok! { + sep = mk_scalv(r_sep, i).await; + right = mk_vec(r_side, i).boxed_local().await; + } + Ok(VecMatcher::Scan { + direction: Side::Left, + left: Box::new(main), + sep, + right: Box::new(right), + }) }, - (_, &[]) => VecMatcher::Scan { - direction: Side::Right, - left: Box::new(mk_vec(l_side)), - sep: mk_scalv(l_sep), - right: Box::new(main), + (_, &[]) => { + join_ok! { + left = mk_vec(l_side, i).boxed_local().await; + sep = mk_scalv(l_sep, i).await; + } + Ok(VecMatcher::Scan { + direction: Side::Right, + left: Box::new(left), + sep, + right: Box::new(main), + }) }, (..) => { let mut key_order = l_side.iter().chain(r_side.iter()).filter_map(vec_attrs).collect::>(); key_order.sort_by_key(|(_, prio, _)| -(*prio as i64)); - VecMatcher::Middle { - left: Box::new(mk_vec(l_side)), - left_sep: mk_scalv(l_sep), - mid: Box::new(main), - right_sep: mk_scalv(r_sep), - right: Box::new(mk_vec(r_side)), - key_order: key_order.into_iter().map(|(n, ..)| n).collect(), + join_ok! { + left = mk_vec(l_side, i).boxed_local().await; + left_sep = mk_scalv(l_sep, i).await; + right_sep = mk_scalv(r_sep, i).await; + right = mk_vec(r_side, i).boxed_local().await; } + Ok(VecMatcher::Middle { + left: Box::new(left), + left_sep, + mid: Box::new(main), + right_sep, + right: Box::new(right), + key_order: key_order.into_iter().map(|(n, ..)| n).collect(), + }) }, } } /// Pattern MUST NOT be a vectorial placeholder #[must_use] -fn mk_scalar(pattern: &MacTree) -> ScalMatcher { - match &*pattern.tok { +async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes { + Ok(match &*pattern.tok { MacTok::Name(n) => ScalMatcher::Name(n.clone()), MacTok::Ph(Ph { name, kind }) => match kind { PhKind::Vector { .. } => { @@ -97,10 +126,16 @@ fn mk_scalar(pattern: &MacTree) -> ScalMatcher { }, PhKind::Scalar => ScalMatcher::Placeh { key: name.clone() }, }, - MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body))), - MacTok::Lambda(arg, body) => ScalMatcher::Lambda(Box::new(mk_any(arg)), Box::new(mk_any(body))), + MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body, i).boxed_local().await?)), + MacTok::Lambda(..) => + return Err(mk_errv( + i.i("Lambda in matcher").await, + "Lambdas can't be matched for, only generated in templates", + [pattern.pos()], + )), MacTok::Value(_) | MacTok::Slot => panic!("Only used for templating"), - } + MacTok::Bottom(errv) => return Err(errv.clone()), + }) } #[cfg(test)] @@ -150,7 +185,7 @@ mod test { })) .await, ]; - let matcher = mk_any(&pattern); + let matcher = mk_any(&pattern, &i).await.expect("This matcher isn't broken"); println!("{matcher}"); }) } diff --git a/orchid-std/src/macros/rule/matcher.rs b/orchid-std/src/macros/rule/matcher.rs index 4e02904..126625c 100644 --- a/orchid-std/src/macros/rule/matcher.rs +++ b/orchid-std/src/macros/rule/matcher.rs @@ -2,6 +2,7 @@ use std::fmt; use std::rc::Rc; use itertools::Itertools; +use orchid_base::error::OrcRes; use orchid_base::interner::Interner; use orchid_base::location::Pos; use orchid_base::name::Sym; @@ -20,21 +21,21 @@ pub fn last_is_vec(pattern: &[MacTree]) -> bool { vec_attrs(pattern.last().unwra pub struct NamedMatcher(AnyMatcher); impl NamedMatcher { - pub async fn new(pattern: &[MacTree], i: &Interner) -> Self { + pub async fn new(pattern: &[MacTree], i: &Interner) -> OrcRes { assert!( matches!(pattern.first().map(|tree| &*tree.tok), Some(MacTok::Name(_))), "Named matchers must begin with a name" ); - match last_is_vec(pattern) { - true => Self(mk_any(pattern)), + Ok(Self(match last_is_vec(pattern) { + true => mk_any(pattern, i).await, false => { let kind = PhKind::Vector { priority: 0, at_least_one: false }; let tok = MacTok::Ph(Ph { name: i.i("::after").await, kind }); let suffix = [MacTree { pos: Pos::None, tok: Rc::new(tok) }]; - Self(mk_any(&pattern.iter().chain(&suffix).cloned().collect_vec())) + mk_any(&pattern.iter().chain(&suffix).cloned().collect_vec(), i).await }, - } + }?)) } /// Also returns the tail, if any, which should be matched further /// Note that due to how priod works below, the main usable information from @@ -62,12 +63,12 @@ impl fmt::Debug for NamedMatcher { pub struct PriodMatcher(VecMatcher); impl PriodMatcher { - pub fn new(pattern: &[MacTree]) -> Self { + pub async fn new(pattern: &[MacTree], i: &Interner) -> OrcRes { assert!( pattern.first().and_then(vec_attrs).is_some() && pattern.last().and_then(vec_attrs).is_some(), "Prioritized matchers must start and end with a vectorial", ); - Self(mk_vec(pattern)) + Ok(Self(mk_vec(pattern, i).await?)) } /// tokens before the offset always match the prefix pub fn apply<'a>( diff --git a/orchid-std/src/macros/rule/scal_match.rs b/orchid-std/src/macros/rule/scal_match.rs index ac2d6e4..dad5a6e 100644 --- a/orchid-std/src/macros/rule/scal_match.rs +++ b/orchid-std/src/macros/rule/scal_match.rs @@ -20,8 +20,6 @@ pub fn scal_match<'a>( Some(MatchState::from_ph(key.clone(), StateEntry::Scalar(expr))), (ScalMatcher::S(c1, b_mat), MacTok::S(c2, body)) if c1 == c2 => any_match(b_mat, &body[..], save_loc), - (ScalMatcher::Lambda(arg_mat, b_mat), MacTok::Lambda(arg, body)) => - Some(any_match(arg_mat, arg, save_loc)?.combine(any_match(b_mat, body, save_loc)?)), _ => None, } } diff --git a/orchid-std/src/macros/rule/shared.rs b/orchid-std/src/macros/rule/shared.rs index 25a29ed..32bee70 100644 --- a/orchid-std/src/macros/rule/shared.rs +++ b/orchid-std/src/macros/rule/shared.rs @@ -11,7 +11,6 @@ use orchid_base::tokens::{PARENS, Paren}; pub enum ScalMatcher { Name(Sym), S(Paren, Box), - Lambda(Box, Box), Placeh { key: Tok }, } @@ -62,7 +61,6 @@ impl fmt::Display for ScalMatcher { let (l, r, _) = PARENS.iter().find(|r| r.2 == *t).unwrap(); write!(f, "{l}{body}{r}") }, - Self::Lambda(arg, body) => write!(f, "\\{arg}.{body}"), } } } diff --git a/orcx/src/main.rs b/orcx/src/main.rs index 383f772..562b42e 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -13,22 +13,24 @@ use async_stream::try_stream; use camino::Utf8PathBuf; use clap::{Parser, Subcommand}; use futures::{Stream, TryStreamExt, io}; +use itertools::Itertools; use orchid_base::error::Reporter; use orchid_base::format::{FmtCtxImpl, Format, take_first}; +use orchid_base::location::SrcRange; use orchid_base::logging::{LogStrategy, Logger}; -use orchid_base::parse::Snippet; +use orchid_base::name::{NameLike, VPath}; +use orchid_base::parse::{Import, Snippet}; use orchid_base::sym; -use orchid_base::tree::ttv_fmt; +use orchid_base::tree::{Token, ttv_fmt}; use orchid_host::ctx::Ctx; use orchid_host::execute::{ExecCtx, ExecResult}; -use orchid_host::expr::PathSetBuilder; +use orchid_host::expr::ExprKind; 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::parse::{HostParseCtxImpl, parse_item, parse_items}; +use orchid_host::parsed::{Item, ItemKind, ParsTokTree, ParsedMember, 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}; @@ -147,56 +149,90 @@ async fn main() -> io::Result { 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}"); + Commands::Repl => { + let mut counter = 0; + let mut imports = Vec::new(); + let usercode_path = sym!(usercode; i).await; + loop { + counter += 1; + let (mut 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(); + let name = i.i(&format!("_{counter}")).await; + let path = usercode_path.suffix([name.clone()], i).await; + let mut lexemes = + lex(i.i(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)); + } + 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) { + Ok(items) => Some(items), + Err(e) => { + eprintln!("{e}"); + None + }, + } + }; + let add_imports = |items: &mut Vec, imports: &[Import]| { + items.extend(imports.iter().map(|import| Item::new(import.sr.clone(), import.clone()))); + }; + if discr.is_kw(i.i("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, + _ => panic!("Expected imports from import line"), + })); 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!"), + } + if !discr.is_kw(i.i("let").await) { + let prefix = [i.i("export").await, i.i("let").await, name.clone(), i.i("=").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 }; + let const_decl = new_lines.iter().exactly_one().expect("Multiple lines from let"); + let input_sr = const_decl.sr.map_range(|_| 0..0); + let const_name = match &const_decl.kind { + ItemKind::Member(ParsedMember { name: const_name, .. }) => const_name.clone(), + _ => panic!("Expected exactly one constant declaration from let"), + }; + 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; + 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; + 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::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 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 = PathBuf::from(proj_path.into_std_path_buf()); @@ -209,8 +245,7 @@ async fn main() -> io::Result { }, } } - let lexemes = lex(i.i(code.trim()).await, path.clone(), &systems, ctx).await.unwrap(); - let snippet = Snippet::new(&lexemes[0], &lexemes); + 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)); } @@ -220,21 +255,22 @@ async fn main() -> io::Result { 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, + let prefix = + [i.i("export").await, i.i("let").await, i.i("entrypoint").await, i.i("=").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) { + Ok(items) => ParsedModule::new(true, items), 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 root = root.add_parsed(&entrypoint, path.clone(), &reporter).await; + let expr = ExprKind::Const(sym!(usercode::entrypoint; i).await).at(prefix_sr.pos()); 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 e020ab7..5376c42 100644 --- a/orcx/src/parse_folder.rs +++ b/orcx/src/parse_folder.rs @@ -39,7 +39,7 @@ pub async fn parse_folder( )); }; let name = ctx.i.i(os_str_to_string(name_os, &ctx.i, [sr]).await?).await; - let ns = ns.push(name.clone(), &ctx.i).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 { @@ -67,7 +67,7 @@ pub async fn parse_folder( } 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.push(name, &ctx.i).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),