From 40c5eaf3d54cf0b1fec2e832b122e9d50197ebe4 Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Fri, 7 Feb 2025 00:47:34 +0100 Subject: [PATCH] Formatter introduced --- examples/hello-world/main.orc | 4 +- orchid-api/src/atom.rs | 7 +- orchid-api/src/format.rs | 20 +++ orchid-api/src/lib.rs | 2 + orchid-base/src/format.rs | 269 ++++++++++++++++++++++++----- orchid-base/src/lib.rs | 1 + orchid-base/src/macros.rs | 49 +++++- orchid-base/src/match_mapping.rs | 7 +- orchid-base/src/parse.rs | 76 ++++---- orchid-base/src/tl_cache.rs | 9 + orchid-base/src/tree.rs | 95 +++++----- orchid-extension/src/atom.rs | 19 +- orchid-extension/src/atom_owned.rs | 11 +- orchid-extension/src/atom_thin.rs | 7 +- orchid-extension/src/entrypoint.rs | 2 +- orchid-host/src/atom.rs | 11 +- orchid-host/src/expr.rs | 70 ++++++-- orchid-host/src/extension.rs | 9 +- orchid-host/src/parse.rs | 89 +++++----- orchid-host/src/system.rs | 9 +- orchid-host/src/tree.rs | 54 +++++- orchid-std/src/string/str_atom.rs | 3 +- orcx/src/main.rs | 3 +- 23 files changed, 608 insertions(+), 218 deletions(-) create mode 100644 orchid-api/src/format.rs create mode 100644 orchid-base/src/tl_cache.rs diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index 19abd51..fa6faaf 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -1,2 +1,2 @@ -const user := "dave" -const main := println "Hello $user!" exit_status::success +const user = "dave" +const main = println "Hello $user!" exit_status::success diff --git a/orchid-api/src/atom.rs b/orchid-api/src/atom.rs index 87ccd0e..636276e 100644 --- a/orchid-api/src/atom.rs +++ b/orchid-api/src/atom.rs @@ -4,7 +4,8 @@ use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use crate::{ - ExprTicket, Expression, ExtHostReq, HostExtNotif, HostExtReq, OrcResult, SysId, TStrv, + ExprTicket, Expression, ExtHostReq, FormattingUnit, HostExtNotif, HostExtReq, OrcResult, SysId, + TStrv, }; pub type AtomData = Vec; @@ -117,14 +118,14 @@ pub struct AtomDrop(pub SysId, pub AtomId); #[extends(AtomReq, HostExtReq)] pub struct AtomPrint(pub Atom); impl Request for AtomPrint { - type Response = String; + type Response = FormattingUnit; } #[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] #[extends(ExtHostReq)] pub struct ExtAtomPrint(pub Atom); impl Request for ExtAtomPrint { - type Response = String; + type Response = FormattingUnit; } /// Requests that apply to an existing atom instance diff --git a/orchid-api/src/format.rs b/orchid-api/src/format.rs new file mode 100644 index 0000000..ab487dd --- /dev/null +++ b/orchid-api/src/format.rs @@ -0,0 +1,20 @@ +use orchid_api_derive::Coding; + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)] +pub struct FormattingUnit { + pub subs: Vec, + pub variants: Vec, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)] +pub struct FormattingVariant { + pub bounded: bool, + pub elements: Vec, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)] +pub enum FormattingElement { + Sub { slot: u32, bounded: Option }, + String(String), + Indent(Vec), +} diff --git a/orchid-api/src/lib.rs b/orchid-api/src/lib.rs index 9588d3f..4dc57a2 100644 --- a/orchid-api/src/lib.rs +++ b/orchid-api/src/lib.rs @@ -1,5 +1,7 @@ mod lexer; pub use lexer::*; +mod format; +pub use format::*; mod macros; pub use macros::*; mod atom; diff --git a/orchid-base/src/format.rs b/orchid-base/src/format.rs index af9ee0b..86e5c69 100644 --- a/orchid-base/src/format.rs +++ b/orchid-base/src/format.rs @@ -1,11 +1,17 @@ +use std::cmp::Ordering; use std::convert::Infallible; +use std::future::Future; use std::iter; use std::rc::Rc; use std::str::FromStr; use itertools::Itertools; +use never::Never; use regex::Regex; +use crate::interner::Interner; +use crate::{api, match_mapping}; + #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct FmtUnit { pub subs: Vec, @@ -15,6 +21,36 @@ impl FmtUnit { pub fn new(variants: Rc, subs: impl IntoIterator) -> Self { Self { subs: subs.into_iter().collect(), variants } } + pub fn from_api(api: &api::FormattingUnit) -> Self { + Self { + subs: api.subs.iter().map(Self::from_api).collect(), + variants: Rc::new(Variants( + (api.variants.iter().map(|var| Variant { + bounded: var.bounded, + elements: var.elements.iter().map(FmtElement::from_api).collect(), + })) + .collect(), + )), + } + } + pub fn to_api(&self) -> api::FormattingUnit { + api::FormattingUnit { + subs: self.subs.iter().map(Self::to_api).collect(), + variants: (self.variants.0.iter().map(|var| api::FormattingVariant { + bounded: var.bounded, + elements: var.elements.iter().map(FmtElement::to_api).collect(), + })) + .collect(), + } + } + pub fn sequence( + delim: &str, + seq_bnd: Option, + seq: impl IntoIterator, + ) -> Self { + let items = seq.into_iter().collect_vec(); + FmtUnit::new(Variants::sequence(items.len(), delim, seq_bnd), items) + } } impl From for FmtUnit where Variants: From @@ -24,66 +60,207 @@ where Variants: From impl FromStr for FmtUnit { type Err = Infallible; fn from_str(s: &str) -> Result { - Ok(Self { subs: vec![], variants: Rc::new(Variants::new([s])) }) + Ok(Self { subs: vec![], variants: Rc::new(Variants::default().bounded(s)) }) } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum FmtElement { - Sub(u8), + Sub { slot: u32, bounded: Option }, String(Rc), - InlineSub(u8), + Indent(Vec), +} +impl FmtElement { + pub fn str(s: &'_ str) -> Self { Self::String(Rc::new(s.to_string())) } + pub fn sub(slot: u32, bounded: Option) -> Self { Self::Sub { slot, bounded } } + pub fn bounded(i: u32) -> Self { Self::sub(i, Some(true)) } + pub fn unbounded(i: u32) -> Self { Self::sub(i, Some(false)) } + pub fn last(i: u32) -> Self { Self::sub(i, None) } + pub fn sequence(len: usize, bounded: Option) -> impl Iterator { + let len32: u32 = len.try_into().unwrap(); + (0..len32 - 1).map(FmtElement::unbounded).chain([FmtElement::sub(len32 - 1, bounded)]) + } + pub fn from_api(api: &api::FormattingElement) -> Self { + match_mapping!(api, api::FormattingElement => FmtElement { + Indent(v => v.iter().map(FmtElement::from_api).collect()), + String(s => Rc::new(s.clone())), + Sub{ *slot, *bounded }, + }) + } + pub fn to_api(&self) -> api::FormattingElement { + match_mapping!(self, FmtElement => api::FormattingElement { + Indent(v => v.iter().map(FmtElement::to_api).collect()), + String(s => s.to_string()), + Sub{ *slot, *bounded }, + }) + } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct Variants(pub Vec>); +pub struct Variant { + pub bounded: bool, + pub elements: Vec, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)] +pub struct Variants(pub Vec); impl Variants { - pub fn new<'a>(variants: impl IntoIterator) -> Self { - let re = Regex::new(r"(?\{\d+?-?\})|(\{\{)|(\}\})").unwrap(); - Self(Vec::from_iter(variants.into_iter().map(|s: &str| { - let matches = re.captures_iter(s); - let slots = matches.into_iter().filter_map(|m| m.name("tpl")).map(|tpl| { - let no_opencurly = tpl.as_str().strip_prefix("{").expect("required by regex"); - let maybe_dash = no_opencurly.strip_suffix("}").expect("required by regex"); - let (num, had_dash) = - maybe_dash.strip_suffix('-').map_or((maybe_dash, false), |s| (s, true)); - let idx = num.parse::().expect("Decimal digits required by regex"); - (tpl.range(), idx, had_dash) - }); - (iter::once(None).chain(slots.into_iter().map(Some)).chain(None).tuple_windows()) - .flat_map(|(l, r)| { - let string = match (l, &r) { - (None, Some((r, ..))) => &s[..r.start], - (Some((r1, ..)), Some((r2, ..))) => &s[r1.end..r2.start], - (Some((r, ..)), None) => &s[r.end..], - (None, None) => s, - }; - let str_item = FmtElement::String(Rc::new(string.to_string())); - match r { - None => itertools::Either::Left([str_item]), - Some((_, idx, inline)) => itertools::Either::Right([str_item, match inline { - true => FmtElement::InlineSub(idx), - false => FmtElement::Sub(idx), - }]), - } - .into_iter() - }) - .coalesce(|left, right| match (left, right) { - (FmtElement::String(left), FmtElement::String(right)) => - Ok(FmtElement::String(Rc::new(left.to_string() + right.as_str()))), - tuple => Err(tuple), - }) - .collect_vec() - }))) + fn parse_phs(s: &'_ str) -> Vec { + let re = Regex::new(r"(?\{\d+?[bl]?\})| (\{\{)|(\}\})").unwrap(); + let matches = re.captures_iter(s); + let slots = matches.into_iter().filter_map(|m| m.name("tpl")).map(|tpl| { + let no_opencurly = tpl.as_str().strip_prefix("{").expect("required by regex"); + let maybe_dash = no_opencurly.strip_suffix("}").expect("required by regex"); + // we know it's not empty + let last_char = maybe_dash.as_bytes()[maybe_dash.len() - 1] as char; + let (num, bounded) = if !last_char.is_ascii_digit() { + let bounded = match last_char { + 'b' => Some(true), + 'l' => None, + _ => panic!("Invalid modifier char"), + }; + (&maybe_dash[0..maybe_dash.len() - 1], bounded) + } else { + (maybe_dash, Some(false)) + }; + let idx = num.parse::().expect("Decimal digits required by regex"); + (tpl.range(), idx, bounded) + }); + (iter::once(None).chain(slots.into_iter().map(Some)).chain(None).tuple_windows()) + .flat_map(|(l, r)| { + let string = match (l, &r) { + (None, Some((r, ..))) => &s[..r.start], + (Some((r1, ..)), Some((r2, ..))) => &s[r1.end..r2.start], + (Some((r, ..)), None) => &s[r.end..], + (None, None) => s, + }; + let str_item = FmtElement::String(Rc::new(string.to_string())); + match r { + None => itertools::Either::Left([str_item]), + Some((_, idx, bounded)) => + itertools::Either::Right([str_item, FmtElement::Sub { slot: idx, bounded }]), + } + .into_iter() + }) + .coalesce(|left, right| match (left, right) { + (FmtElement::String(left), FmtElement::String(right)) => + Ok(FmtElement::String(Rc::new(left.to_string() + right.as_str()))), + tuple => Err(tuple), + }) + .collect_vec() + } + fn parse(s: &'_ str) -> Vec { + let mut lines = s.lines(); + let Some(mut cur) = lines.next() else { return vec![] }; + return indent_blk(&mut cur, &mut lines, 0); + fn indent_blk<'a>( + cur: &mut &'a str, + lines: &mut impl Iterator, + blk_lv: usize, + ) -> Vec { + let mut out = Vec::new(); + loop { + let line_lv = cur.chars().take_while(|c| *c == '\t').count(); + match line_lv.cmp(&blk_lv) { + Ordering::Greater => out.push(FmtElement::Indent(indent_blk(cur, lines, blk_lv + 1))), + Ordering::Equal => out.extend(Variants::parse_phs(&cur[blk_lv..])), + Ordering::Less => return out, + } + match lines.next() { + Some(line) => *cur = line, + None => return out, + } + } + } + } + fn add(&mut self, bounded: bool, s: &'_ str) { + self.0.push(Variant { bounded, elements: Self::parse(s) }) + } + // This option is available in all positions + pub fn bounded(mut self, s: &'_ str) -> Self { + self.add(true, s); + self + } + // This option is only available in positions immediately preceding the end of + // the sequence or a parenthesized subsequence. + pub fn unbounded(mut self, s: &'_ str) -> Self { + self.add(false, s); + self + } + pub fn sequence(len: usize, delim: &str, seq_bnd: Option) -> Rc { + let seq = Itertools::intersperse(FmtElement::sequence(len, seq_bnd), FmtElement::str(delim)); + Rc::new(Variants(vec![Variant { bounded: true, elements: seq.collect_vec() }])) + } + pub fn units(self: &Rc, subs: impl IntoIterator) -> FmtUnit { + FmtUnit::new(self.clone(), subs) + } +} +impl From> for Variants { + fn from(value: Rc) -> Self { + Self(vec![Variant { elements: vec![FmtElement::String(value)], bounded: true }]) } } impl From for Variants { - fn from(value: String) -> Self { Self(vec![vec![FmtElement::String(Rc::new(value))]]) } -} -impl From> for Variants { - fn from(value: Rc) -> Self { Self(vec![vec![FmtElement::String(value)]]) } + fn from(value: String) -> Self { Self::from(Rc::new(value)) } } impl FromStr for Variants { type Err = Infallible; - fn from_str(s: &str) -> Result { Ok(Self::new([s])) } + fn from_str(s: &str) -> Result { Ok(Self::default().bounded(s)) } +} + +fn indent_str(s: &str, indent: u16) -> String { + s.replace("\n", &format!("\n{}", "\t".repeat(indent.into()))) +} + +fn fill_slots<'a, 'b>( + elements: impl IntoIterator, + values: &[FmtUnit], + indent: u16, + last_bounded: bool, +) -> String { + elements + .into_iter() + .map(|el| match el { + FmtElement::String(s) => indent_str(s, indent), + FmtElement::Sub { slot, bounded } => + indent_str(&take_first(&values[*slot as usize], bounded.unwrap_or(last_bounded)), indent), + FmtElement::Indent(elements) => fill_slots(elements, values, indent + 1, last_bounded), + }) + .collect() +} + +/// The simplest possible print strategy +pub fn take_first(unit: &FmtUnit, bounded: bool) -> String { + let first = unit.variants.0.iter().find(|v| v.bounded || bounded).expect("No bounded variant!"); + fill_slots(&first.elements, &unit.subs, 0, bounded) +} + +pub async fn take_first_fmt(v: &(impl Format + ?Sized), i: &Interner) -> String { + take_first(&v.print(&FmtCtxImpl { i }).await, false) +} + +pub struct FmtCtxImpl<'a> { + pub i: &'a Interner, +} + +pub trait FmtCtx { + fn i(&self) -> &Interner; + // fn print_as(&self, p: &(impl Format + ?Sized)) -> impl Future where Self: Sized { + // async { + // // for now, always take the first option which is probably the one-line + // form let variants = p.print(self).await; + // take_first(&variants, true) + // } + // } +} +impl FmtCtx for FmtCtxImpl<'_> { + fn i(&self) -> &Interner { self.i } +} + +pub trait Format { + fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future + 'a; +} +impl Format for Never { + async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { match *self {} } } diff --git a/orchid-base/src/lib.rs b/orchid-base/src/lib.rs index 7307df1..6fc8774 100644 --- a/orchid-base/src/lib.rs +++ b/orchid-base/src/lib.rs @@ -25,5 +25,6 @@ pub mod pure_seq; pub mod reqnot; pub mod sequence; pub mod side; +mod tl_cache; pub mod tokens; pub mod tree; diff --git a/orchid-base/src/macros.rs b/orchid-base/src/macros.rs index 8d524f8..6366206 100644 --- a/orchid-base/src/macros.rs +++ b/orchid-base/src/macros.rs @@ -3,16 +3,17 @@ use std::rc::Rc; use std::sync::Arc; use async_stream::stream; -use futures::future::LocalBoxFuture; +use futures::future::{LocalBoxFuture, join_all}; use futures::{FutureExt, StreamExt}; use never::Never; use trait_set::trait_set; +use crate::format::{FmtCtx, FmtUnit, Format, Variants}; use crate::interner::Interner; use crate::location::Pos; use crate::name::Sym; use crate::tree::{Paren, Ph}; -use crate::{api, match_mapping}; +use crate::{api, match_mapping, tl_cache}; #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct MacroSlot<'a>(api::MacroTreeId, PhantomData<&'a ()>); @@ -49,6 +50,11 @@ impl<'a, A> MTree<'a, A> { } } } +impl Format for MTree<'_, A> { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + self.tok.print(c).await + } +} #[derive(Clone, Debug)] pub enum MTok<'a, A> { @@ -96,6 +102,38 @@ impl<'a, A> MTok<'a, A> { } pub fn at(self, pos: Pos) -> MTree<'a, A> { MTree { pos, tok: Rc::new(self) } } } +impl Format for MTok<'_, A> { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + match self { + Self::Atom(a) => a.print(c).await, + Self::Done(d) => + FmtUnit::new(tl_cache!(Rc: Rc::new(Variants::default().bounded("(Done){0l}"))), [ + d.print(c).await, + ]), + Self::Lambda(arg, b) => FmtUnit::new( + tl_cache!(Rc: Rc::new(Variants::default() + .unbounded("\\{0b}.{1l}") + .bounded("(\\{0b}.{1b})"))), + [mtreev_fmt(arg, c).await, mtreev_fmt(b, c).await], + ), + Self::Name(n) => format!("{n}").into(), + Self::Ph(ph) => format!("{ph}").into(), + Self::Ref(r) => + FmtUnit::new(tl_cache!(Rc: Rc::new(Variants::default().bounded("(ref){0l}"))), [ + r.print(c).await, + ]), + Self::S(p, body) => FmtUnit::new( + match *p { + Paren::Round => Rc::new(Variants::default().bounded("({0b})")), + Paren::Curly => Rc::new(Variants::default().bounded("{{0b}}")), + Paren::Square => Rc::new(Variants::default().bounded("[{0b}]")), + }, + [mtreev_fmt(body, c).await], + ), + Self::Slot(slot) => format!("{:?}", slot.0).into(), + } + } +} pub async fn mtreev_from_api<'a, 'b, A>( apiv: impl IntoIterator, @@ -121,3 +159,10 @@ pub async fn mtreev_to_api<'a: 'b, 'b, A: 'b>( } out } + +pub async fn mtreev_fmt<'a: 'b, 'b, A: 'b + Format>( + v: impl IntoIterator>, + c: &(impl FmtCtx + ?Sized), +) -> FmtUnit { + FmtUnit::sequence(" ", None, join_all(v.into_iter().map(|t| t.print(c))).await) +} diff --git a/orchid-base/src/match_mapping.rs b/orchid-base/src/match_mapping.rs index 799ab46..f9e4419 100644 --- a/orchid-base/src/match_mapping.rs +++ b/orchid-base/src/match_mapping.rs @@ -17,7 +17,8 @@ macro_rules! match_mapping { $($branches:tt)* } $({ $($extra:tt)* - })?) => { + })?) => {{ + type Helper = T; match_mapping!(@BRANCH_MUNCH (($input) ($($src)*) ($tgt) ($($($extra)*)?)) () @@ -25,7 +26,7 @@ macro_rules! match_mapping { ) // note: we're adding a comma to the input so the optional trailing comma becomes // an optional second comma which is easier to match - }; + }}; // ======== Process match branches // Can't generate branches individually so gather them into a collection and render them here (@BRANCHES_DONE ( ($input:expr) $src:tt ($tgt:ty) ($($extra:tt)*) ) @@ -35,7 +36,7 @@ macro_rules! match_mapping { match $input { $( match_mapping!(@PAT ($src $variant) $($pat)*) => - match_mapping!(@VAL (< $tgt >:: $variant) $($pat)*), + match_mapping!(@VAL (Helper::< $tgt >:: $variant) $($pat)*), )* $($extra)* } diff --git a/orchid-base/src/parse.rs b/orchid-base/src/parse.rs index 58ae81e..efadcb2 100644 --- a/orchid-base/src/parse.rs +++ b/orchid-base/src/parse.rs @@ -1,3 +1,4 @@ +use std::fmt::{self, Display}; use std::iter; use std::ops::{Deref, Range}; @@ -6,7 +7,8 @@ use itertools::Itertools; use crate::api; use crate::error::{OrcRes, Reporter, mk_err, mk_errv}; -use crate::interner::{Internable, Interned, Interner, Tok}; +use crate::format::{Format, take_first_fmt}; +use crate::interner::{Interner, Tok}; use crate::location::Pos; use crate::name::VPath; use crate::tree::{AtomRepr, ExtraTok, Paren, TokTree, Token}; @@ -16,6 +18,8 @@ pub fn name_char(c: char) -> bool { name_start(c) || c.is_numeric() } pub fn op_char(c: char) -> bool { !name_char(c) && !c.is_whitespace() && !"()[]{}\\".contains(c) } pub fn unrep_space(c: char) -> bool { c.is_whitespace() && !"\r\n".contains(c) } +/// A cheaply copiable subsection of a document that holds onto context data and +/// one token for error reporting on empty subsections. #[derive(Debug)] pub struct Snippet<'a, 'b, A: AtomRepr, X: ExtraTok> { prev: &'a TokTree<'b, A, X>, @@ -30,10 +34,7 @@ impl<'a, 'b, A: AtomRepr, X: ExtraTok> Snippet<'a, 'b, A, X> { ) -> Self { Self { prev, cur, interner } } - pub async fn i(&self, arg: &(impl Internable + ?Sized)) -> Tok { - self.interner.i(arg).await - } - pub fn interner(&self) -> &'a Interner { self.interner } + pub fn i(&self) -> &'a Interner { self.interner } pub fn split_at(self, pos: u32) -> (Self, Self) { let Self { prev, cur, interner } = self; let fst = Self { prev, cur: &cur[..pos as usize], interner }; @@ -66,7 +67,9 @@ impl<'a, 'b, A: AtomRepr, X: ExtraTok> Snippet<'a, 'b, A, X> { mut f: impl FnMut(&Token<'b, A, X>) -> bool, ) -> impl Iterator { iter::from_fn(move || { - self.is_empty().then_some(())?; + if self.is_empty() { + return None; + } let (ret, next) = self.split_once(&mut f).unwrap_or(self.split_at(self.len())); self = next; Some(ret) @@ -77,6 +80,8 @@ impl<'a, 'b, A: AtomRepr, X: ExtraTok> Snippet<'a, 'b, A, X> { let non_fluff_start = self.find_idx(|t| !matches!(t, Token::NS | Token::Comment(_))); self.split_at(non_fluff_start.unwrap_or(self.len())).1 } + /// Format the argument using the context held in this snippet + pub async fn fmt(self, v: &(impl Format + ?Sized)) -> String { take_first_fmt(v, self.i()).await } } impl Copy for Snippet<'_, '_, A, X> {} impl Clone for Snippet<'_, '_, A, X> { @@ -116,6 +121,10 @@ impl Comment { } } +impl fmt::Display for Comment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "--[{}]--", self.text) } +} + pub async fn line_items<'a, 'b, A: AtomRepr, X: ExtraTok>( snip: Snippet<'a, 'b, A, X>, ) -> Vec, A, X>> { @@ -134,7 +143,7 @@ pub async fn line_items<'a, 'b, A: AtomRepr, X: ExtraTok>( let comments = join_all(comments.drain(..).chain(cmts.cur).map(|t| async { match &t.tok { Token::Comment(c) => - Comment { text: tail.i(&**c).await, pos: Pos::Range(t.range.clone()) }, + Comment { text: tail.i().i(&**c).await, pos: Pos::Range(t.range.clone()) }, _ => unreachable!("All are comments checked above"), } })) @@ -151,17 +160,18 @@ pub async fn try_pop_no_fluff<'a, 'b, A: AtomRepr, X: ExtraTok>( ) -> ParseRes<'a, 'b, &'a TokTree<'b, A, X>, A, X> { match snip.skip_fluff().pop_front() { Some((output, tail)) => Ok(Parsed { output, tail }), - None => Err(mk_errv(snip.i("Unexpected end").await, "Pattern ends abruptly", [Pos::Range( - snip.pos(), - ) - .into()])), + None => + Err(mk_errv(snip.i().i("Unexpected end").await, "Pattern ends abruptly", [Pos::Range( + snip.pos(), + ) + .into()])), } } pub async fn expect_end(snip: Snippet<'_, '_, impl AtomRepr, impl ExtraTok>) -> OrcRes<()> { match snip.skip_fluff().get(0) { Some(surplus) => Err(mk_errv( - snip.i("Extra code after end of line").await, + snip.i().i("Extra code after end of line").await, "Code found after the end of the line", [Pos::Range(surplus.range.clone()).into()], )), @@ -177,8 +187,8 @@ pub async fn expect_tok<'a, 'b, A: AtomRepr, X: ExtraTok>( match &head.tok { Token::Name(n) if *n == tok => Ok(Parsed { output: (), tail }), t => Err(mk_errv( - snip.i("Expected specific keyword").await, - format!("Expected {tok} but found {:?}", t.print().await), + snip.i().i("Expected specific keyword").await, + format!("Expected {tok} but found {:?}", snip.fmt(t).await), [Pos::Range(head.range.clone()).into()], )), } @@ -201,11 +211,11 @@ pub async fn parse_multiname<'a, 'b, A: AtomRepr, X: ExtraTok>( ctx: &(impl Reporter + ?Sized), tail: Snippet<'a, 'b, A, X>, ) -> ParseRes<'a, 'b, Vec<(Vec>, Option>, Pos)>, A, X> { - let comma = tail.i(",").await; - let globstar = tail.i("*").await; + let comma = tail.i().i(",").await; + let globstar = tail.i().i("*").await; let Some((name, tail)) = tail.skip_fluff().pop_front() else { return Err(mk_errv( - tail.i("Expected name").await, + tail.i().i("Expected name").await, "Expected a name, a list of names, or a globstar.", [Pos::Range(tail.pos()).into()], )); @@ -213,9 +223,10 @@ pub async fn parse_multiname<'a, 'b, A: AtomRepr, X: ExtraTok>( if let Some((Token::NS, tail)) = tail.skip_fluff().pop_front().map(|(tt, s)| (&tt.tok, s)) { let n = match &name.tok { Token::Name(n) if n.starts_with(name_start) => Ok(n), - _ => Err(mk_err(tail.i("Unexpected name prefix").await, "Only names can precede ::", [ - Pos::Range(name.range.clone()).into(), - ])), + _ => + Err(mk_err(tail.i().i("Unexpected name prefix").await, "Only names can precede ::", [ + Pos::Range(name.range.clone()).into(), + ])), }; match (Box::pin(rec(ctx, tail)).await, n) { (Err(ev), n) => Err(ev.extended(n.err())), @@ -235,7 +246,7 @@ pub async fn parse_multiname<'a, 'b, A: AtomRepr, X: ExtraTok>( n if *n == globstar => None, n if n.starts_with(op_char) => { return Err(mk_errv( - tail.i("Unescaped operator in multiname").await, + tail.i().i("Unescaped operator in multiname").await, "Operators in multinames should be enclosed in []", [Pos::Range(name.range.clone()).into()], )); @@ -252,7 +263,7 @@ pub async fn parse_multiname<'a, 'b, A: AtomRepr, X: ExtraTok>( ok.push((vec![], Some(n.clone()), Pos::Range(tt.range.clone()))), Token::BR | Token::Comment(_) => (), _ => ctx.report(mk_err( - tail.i("Non-operator in escapement in multiname").await, + tail.i().i("Non-operator in escapement in multiname").await, "In multinames, [] functions as a literal name list reserved for operators", [Pos::Range(name.range.clone()).into()], )), @@ -269,7 +280,7 @@ pub async fn parse_multiname<'a, 'b, A: AtomRepr, X: ExtraTok>( Ok(Parsed { output, tail }) => match tail.get(0) { None => ok.extend(output), Some(t) => ctx.report(mk_err( - tail.i("Unexpected token in multiname group").await, + tail.i().i("Unexpected token in multiname group").await, "Unexpected token. Likely missing a :: or , or wanted [] instead of ()", [Pos::Range(t.range.clone()).into()], )), @@ -280,8 +291,8 @@ pub async fn parse_multiname<'a, 'b, A: AtomRepr, X: ExtraTok>( }, t => { return Err(mk_errv( - tail.i("Unrecognized name end").await, - format!("Names cannot end with {:?} tokens", t.print().await), + tail.i().i("Unrecognized name end").await, + format!("Names cannot end with {:?} tokens", tail.fmt(t).await), [Pos::Range(name.range.clone()).into()], )); }, @@ -303,16 +314,11 @@ pub struct Import { pub path: VPath, pub name: Option>, } -impl Import { - // pub fn from_api(i: api::CompName) -> Self { - // Self { path: VPath::new(i.path.into_iter().map(deintern)), name: - // i.name.map(deintern) } } - // pub fn to_api(&self) -> api::CompName { - // api::CompName { - // path: self.path.iter().map(|t| t.marker()).collect(), - // name: self.name.as_ref().map(|t| t.marker()), - // } - // } + +impl Display for Import { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}::{}", self.path.iter().join("::"), self.name.as_ref().map_or("*", |t| t.as_str())) + } } #[cfg(test)] diff --git a/orchid-base/src/tl_cache.rs b/orchid-base/src/tl_cache.rs new file mode 100644 index 0000000..1bf4cdb --- /dev/null +++ b/orchid-base/src/tl_cache.rs @@ -0,0 +1,9 @@ +#[macro_export] +macro_rules! tl_cache { + ($ty:ty : $expr:expr) => {{ + thread_local! { + static V: $ty = $expr; + } + V.with(|v| v.clone()) + }}; +} diff --git a/orchid-base/src/tree.rs b/orchid-base/src/tree.rs index 48f83ed..258ef01 100644 --- a/orchid-base/src/tree.rs +++ b/orchid-base/src/tree.rs @@ -1,9 +1,10 @@ use std::borrow::Borrow; use std::fmt::{self, Debug, Display}; -use std::future::{Future, ready}; +use std::future::Future; use std::iter; use std::marker::PhantomData; use std::ops::Range; +use std::rc::Rc; use std::sync::Arc; pub use api::PhKind; @@ -16,16 +17,16 @@ use ordered_float::NotNan; use trait_set::trait_set; use crate::error::OrcErrv; +use crate::format::{FmtCtx, FmtUnit, Format, Variants}; use crate::interner::{Interner, Tok}; use crate::location::Pos; use crate::name::PathSlice; use crate::parse::Snippet; -use crate::tokens::PARENS; -use crate::{api, match_mapping}; +use crate::{api, match_mapping, tl_cache}; trait_set! { pub trait RecurCB<'a, A: AtomRepr, X: ExtraTok> = Fn(TokTree<'a, A, X>) -> TokTree<'a, A, X>; - pub trait ExtraTok = Display + Clone + fmt::Debug; + pub trait ExtraTok = Format + Clone + fmt::Debug; pub trait RefDoExtra = for<'b> FnMut(&'b X, Range) -> LocalBoxFuture<'b, api::TokenTree>; } @@ -46,22 +47,15 @@ pub fn recur<'a, A: AtomRepr, X: ExtraTok>( }) } -pub trait AtomRepr: Clone { +pub trait AtomRepr: Clone + Format { type Ctx: ?Sized; fn from_api(api: &api::Atom, pos: Pos, ctx: &mut Self::Ctx) -> impl Future; fn to_api(&self) -> impl Future + '_; - fn print(&self) -> impl Future + '_; } impl AtomRepr for Never { type Ctx = Never; - #[allow(unreachable_code)] - fn from_api(_: &api::Atom, _: Pos, ctx: &mut Self::Ctx) -> impl Future { - ready(match *ctx {}) - } - #[allow(unreachable_code)] - fn to_api(&self) -> impl Future + '_ { ready(match *self {}) } - #[allow(unreachable_code)] - fn print(&self) -> impl Future + '_ { ready(match *self {}) } + async fn from_api(_: &api::Atom, _: Pos, ctx: &mut Self::Ctx) -> Self { match *ctx {} } + async fn to_api(&self) -> orchid_api::Atom { match *self {} } } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] @@ -152,7 +146,11 @@ impl<'b, A: AtomRepr, X: ExtraTok> TokTree<'b, A, X> { body.insert(0, Token::LambdaHead(arg).at(arg_range)); Token::S(Paren::Round, body).at(s_range) } - pub async fn print(&self) -> String { self.tok.print().await } +} +impl Format for TokTree<'_, A, X> { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + self.tok.print(c).await + } } pub async fn ttv_from_api( @@ -245,32 +243,34 @@ impl<'a, A: AtomRepr, X: ExtraTok> Token<'a, A, X> { _ => None, } } - pub async fn print(&self) -> String { +} +impl Format for Token<'_, A, X> { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { match self { - Self::Atom(a) => a.print().await, - Self::BR => "\n".to_string(), - Self::Bottom(err) if err.len() == 1 => format!("Bottom({}) ", err.one().unwrap()), - Self::Bottom(err) => format!("Botttom(\n{}) ", indent(&err.to_string())), - Self::Comment(c) => format!("--[{c}]-- "), - Self::LambdaHead(arg) => format!("\\ {} . ", indent(&ttv_fmt(arg).await)), - Self::NS => ":: ".to_string(), - Self::Name(n) => format!("{n} "), - Self::Slot(th) => format!("{th} "), - Self::Ph(Ph { kind, name }) => match &kind { - PhKind::Scalar => format!("${name}"), - PhKind::Vector { at_least_one, priority } => { - let prefix = if *at_least_one { "..." } else { ".." }; - let suffix = if 0 < *priority { format!(":{priority}") } else { String::new() }; - format!("{prefix}${name}{suffix}") + Self::Atom(a) => a.print(c).await, + Self::BR => "\n".to_string().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(), + Self::Comment(c) => format!("--[{c}]--").into(), + Self::LambdaHead(arg) => + FmtUnit::new(tl_cache!(Rc: Rc::new(Variants::default().bounded("\\{0b}."))), [ + ttv_fmt(arg, c).await, + ]), + Self::NS => "::".to_string().into(), + Self::Name(n) => format!("{n}").into(), + Self::Slot(th) => format!("{th}").into(), + Self::Ph(ph) => format!("{ph}").into(), + Self::S(p, b) => FmtUnit::new( + match *p { + Paren::Round => Rc::new(Variants::default().bounded("({0b})")), + Paren::Curly => Rc::new(Variants::default().bounded("{{{0b}}}")), + Paren::Square => Rc::new(Variants::default().bounded("[{0b}]")), }, - }, - Self::S(p, b) => { - let (lp, rp, _) = PARENS.iter().find(|(_, _, par)| par == p).unwrap(); - format!("{lp} {}{rp} ", indent(&ttv_fmt(b).await)) - }, - Self::X(x) => format!("{x} "), - Self::Macro(None) => "macro ".to_string(), - Self::Macro(Some(prio)) => format!("macro({prio})"), + [ttv_fmt(b, c).await], + ), + Self::X(x) => x.print(c).await, + Self::Macro(None) => "macro".to_string().into(), + Self::Macro(Some(prio)) => format!("macro({prio})").into(), } } } @@ -282,8 +282,9 @@ pub fn ttv_range(ttv: &[TokTree<'_, impl AtomRepr, impl ExtraTok>]) -> Range( ttv: impl IntoIterator>, -) -> String { - join_all(ttv.into_iter().map(|tt| tt.print())).await.join("") + c: &(impl FmtCtx + ?Sized), +) -> FmtUnit { + FmtUnit::sequence(" ", None, join_all(ttv.into_iter().map(|t| t.print(c))).await) } pub fn indent(s: &str) -> String { s.replace("\n", "\n ") } @@ -301,6 +302,18 @@ impl Ph { api::Placeholder { name: self.name.to_api(), kind: self.kind } } } +impl Display for Ph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let n = &self.name; + match self.kind { + PhKind::Scalar => write!(f, "${n}"), + PhKind::Vector { priority: 0, at_least_one: true } => write!(f, "...${}", self.name), + PhKind::Vector { priority: p, at_least_one: true } => write!(f, "...${}:{}", self.name, p), + PhKind::Vector { priority: 0, at_least_one: false } => write!(f, "..${}", self.name), + PhKind::Vector { priority: p, at_least_one: false } => write!(f, "..${}:{}", self.name, p), + } + } +} #[cfg(test)] mod test { diff --git a/orchid-extension/src/atom.rs b/orchid-extension/src/atom.rs index b82328d..f7911c1 100644 --- a/orchid-extension/src/atom.rs +++ b/orchid-extension/src/atom.rs @@ -15,6 +15,7 @@ use futures::{FutureExt, StreamExt}; use orchid_api_traits::{Coding, Decode, Encode, Request, enc_vec}; use orchid_base::clone; use orchid_base::error::{OrcErr, OrcRes, mk_err}; +use orchid_base::format::{FmtCtx, FmtUnit, Format}; use orchid_base::interner::Interner; use orchid_base::location::Pos; use orchid_base::name::Sym; @@ -117,15 +118,17 @@ impl fmt::Display for ForeignAtom<'_> { impl fmt::Debug for ForeignAtom<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ForeignAtom({self})") } } +impl Format for ForeignAtom<'_> { + async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + FmtUnit::from_api(&self.ctx.reqnot.request(api::ExtAtomPrint(self.atom.clone())).await) + } +} impl AtomRepr for ForeignAtom<'_> { type Ctx = SysCtx; async fn from_api(atom: &api::Atom, pos: Pos, ctx: &mut Self::Ctx) -> Self { Self { atom: atom.clone(), _life: PhantomData, ctx: ctx.clone(), expr: None, pos } } async fn to_api(&self) -> orchid_api::Atom { self.atom.clone() } - async fn print(&self) -> String { - self.ctx.reqnot.request(api::ExtAtomPrint(self.atom.clone())).await - } } pub struct NotTypAtom { @@ -266,6 +269,9 @@ impl Deref for TypAtom<'_, A> { } pub struct AtomCtx<'a>(pub &'a [u8], pub Option, pub SysCtx); +impl FmtCtx for AtomCtx<'_> { + fn i(&self) -> &Interner { &self.2.i } +} pub trait AtomDynfo: 'static { fn tid(&self) -> TypeId; @@ -273,7 +279,7 @@ pub trait AtomDynfo: 'static { fn decode<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, Box>; fn call<'a>(&'a self, ctx: AtomCtx<'a>, arg: api::ExprTicket) -> LocalBoxFuture<'a, GExpr>; fn call_ref<'a>(&'a self, ctx: AtomCtx<'a>, arg: api::ExprTicket) -> LocalBoxFuture<'a, GExpr>; - fn print<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, String>; + fn print<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit>; fn handle_req<'a, 'b: 'a, 'c: 'a>( &'a self, ctx: AtomCtx<'a>, @@ -317,6 +323,11 @@ impl fmt::Debug for AtomFactory { impl fmt::Display for AtomFactory { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AtomFactory") } } +impl Format for AtomFactory { + async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + "AtomFactory".to_string().into() + } +} pub async fn err_not_callable(i: &Interner) -> OrcErr { mk_err(i.i("This atom is not callable").await, "Attempted to apply value as function", []) diff --git a/orchid-extension/src/atom_owned.rs b/orchid-extension/src/atom_owned.rs index 7adf00f..50a73d6 100644 --- a/orchid-extension/src/atom_owned.rs +++ b/orchid-extension/src/atom_owned.rs @@ -13,6 +13,7 @@ use never::Never; use orchid_api_traits::{Decode, Encode, enc_vec}; use orchid_base::clone; use orchid_base::error::OrcRes; +use orchid_base::format::FmtUnit; use orchid_base::id_store::{IdRecord, IdStore}; use orchid_base::name::Sym; @@ -76,7 +77,7 @@ impl AtomDynfo for OwnedAtomDynfo { } .boxed_local() } - fn print(&self, AtomCtx(_, id, ctx): AtomCtx<'_>) -> LocalBoxFuture<'_, String> { + fn print(&self, AtomCtx(_, id, ctx): AtomCtx<'_>) -> LocalBoxFuture<'_, FmtUnit> { async move { with_atom(id.unwrap(), &ctx, |a| clone!(ctx; async move { a.dyn_print(ctx).await })).await } @@ -227,8 +228,8 @@ pub trait OwnedAtom: Atomic + Any + Clone + 'static { #[allow(unused_variables)] fn free(self, ctx: SysCtx) -> impl Future { async {} } #[allow(unused_variables)] - fn print(&self, ctx: SysCtx) -> impl Future { - async { format!("OwnedAtom({})", type_name::()) } + fn print(&self, ctx: SysCtx) -> impl Future { + async { format!("OwnedAtom({})", type_name::()).into() } } #[allow(unused_variables)] fn serialize( @@ -262,7 +263,7 @@ pub trait DynOwnedAtom: 'static { -> LocalBoxFuture<'static, GExpr>; fn dyn_command(self: Box, ctx: SysCtx) -> LocalBoxFuture<'static, OrcRes>>; fn dyn_free(self: Box, ctx: SysCtx) -> LocalBoxFuture<'static, ()>; - fn dyn_print(&self, ctx: SysCtx) -> LocalBoxFuture<'_, String>; + fn dyn_print(&self, ctx: SysCtx) -> LocalBoxFuture<'_, FmtUnit>; fn dyn_serialize<'a>( &'a self, ctx: SysCtx, @@ -291,7 +292,7 @@ impl DynOwnedAtom for T { fn dyn_free(self: Box, ctx: SysCtx) -> LocalBoxFuture<'static, ()> { self.free(ctx).boxed_local() } - fn dyn_print(&self, ctx: SysCtx) -> LocalBoxFuture<'_, String> { self.print(ctx).boxed_local() } + fn dyn_print(&self, ctx: SysCtx) -> LocalBoxFuture<'_, FmtUnit> { self.print(ctx).boxed_local() } fn dyn_serialize<'a>( &'a self, ctx: SysCtx, diff --git a/orchid-extension/src/atom_thin.rs b/orchid-extension/src/atom_thin.rs index 9fbd7ba..33f0629 100644 --- a/orchid-extension/src/atom_thin.rs +++ b/orchid-extension/src/atom_thin.rs @@ -8,6 +8,7 @@ use futures::FutureExt; use futures::future::LocalBoxFuture; use orchid_api_traits::{Coding, enc_vec}; use orchid_base::error::OrcRes; +use orchid_base::format::FmtUnit; use orchid_base::name::Sym; use crate::api; @@ -39,7 +40,7 @@ pub struct ThinAtomDynfo { ms: OnceCell>, } impl AtomDynfo for ThinAtomDynfo { - fn print<'a>(&self, AtomCtx(buf, _, ctx): AtomCtx<'a>) -> LocalBoxFuture<'a, String> { + fn print<'a>(&self, AtomCtx(buf, _, ctx): AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit> { async move { T::decode(Pin::new(&mut &buf[..])).await.print(ctx).await }.boxed_local() } fn tid(&self) -> TypeId { TypeId::of::() } @@ -123,7 +124,7 @@ pub trait ThinAtom: async move { Err(err_not_command(&ctx.i).await.into()) } } #[allow(unused_variables)] - fn print(&self, ctx: SysCtx) -> impl Future { - async { format!("ThinAtom({})", type_name::()) } + fn print(&self, ctx: SysCtx) -> impl Future { + async { format!("ThinAtom({})", type_name::()).into() } } } diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index 05eeef6..4ada81b 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -343,7 +343,7 @@ pub async fn extension_main_logic(data: ExtensionData, spawner: Spawner) { hand.handle(ser, &refs_opt.map(|refs| (buf, refs))).await }, api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) => - hand.handle(print, &nfo.print(actx).await).await, + hand.handle(print, &nfo.print(actx).await.to_api()).await, api::AtomReq::Fwded(fwded) => { let api::Fwded(_, key, payload) = &fwded; let mut reply = Vec::new(); diff --git a/orchid-host/src/atom.rs b/orchid-host/src/atom.rs index 68bb6f7..e1c6c7e 100644 --- a/orchid-host/src/atom.rs +++ b/orchid-host/src/atom.rs @@ -2,6 +2,7 @@ use std::fmt; use std::rc::{Rc, Weak}; use derive_destructure::destructure; +use orchid_base::format::{FmtCtx, FmtUnit, Format, take_first_fmt}; use orchid_base::location::Pos; use orchid_base::reqnot::Requester; use orchid_base::tree::AtomRepr; @@ -78,18 +79,20 @@ impl AtomHand { self.0.owner.reqnot().request(api::Fwded(self.0.api_ref(), key, req)).await } pub fn api_ref(&self) -> api::Atom { self.0.api_ref() } - pub async fn to_string(&self) -> String { - self.0.owner.reqnot().request(api::AtomPrint(self.0.api_ref())).await - } + pub async fn to_string(&self) -> String { take_first_fmt(self, &self.0.owner.ctx().i).await } pub fn downgrade(&self) -> WeakAtomHand { WeakAtomHand(Rc::downgrade(&self.0)) } } +impl Format for AtomHand { + async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + FmtUnit::from_api(&self.0.owner.reqnot().request(api::AtomPrint(self.0.api_ref())).await) + } +} impl AtomRepr for AtomHand { type Ctx = Ctx; async fn from_api(atom: &orchid_api::Atom, _: Pos, ctx: &mut Self::Ctx) -> Self { Self::new(atom.clone(), ctx).await } async fn to_api(&self) -> orchid_api::Atom { self.api_ref() } - async fn print(&self) -> String { self.to_string().await } } pub struct WeakAtomHand(Weak); diff --git a/orchid-host/src/expr.rs b/orchid-host/src/expr.rs index e680060..a5b0444 100644 --- a/orchid-host/src/expr.rs +++ b/orchid-host/src/expr.rs @@ -1,14 +1,19 @@ use std::collections::VecDeque; +use std::fmt; use std::num::NonZeroU64; use std::rc::{Rc, Weak}; use async_std::sync::RwLock; use futures::FutureExt; +use hashbrown::HashSet; +use itertools::Itertools; +use orchid_api::ExprTicket; use orchid_base::error::OrcErrv; +use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; use orchid_base::location::Pos; -use orchid_base::match_mapping; use orchid_base::name::Sym; -use orchid_base::tree::AtomRepr; +use orchid_base::tree::{AtomRepr, indent}; +use orchid_base::{match_mapping, tl_cache}; use crate::api; use crate::atom::AtomHand; @@ -34,15 +39,6 @@ impl Expr { .expect("this is a ref, it cannot be null"), ) } - // pub fn canonicalize(&self) -> api::ExprTicket { - // if !self.is_canonical.swap(true, Ordering::Relaxed) { - // KNOWN_EXPRS.write().unwrap().entry(self.id()).or_insert_with(|| - // self.clone()); } - // self.id() - // } - // pub fn resolve(tk: api::ExprTicket) -> Option { - // KNOWN_EXPRS.read().unwrap().get(&tk).cloned() - // } pub async fn from_api(api: &api::Expression, ctx: &mut ExprParseCtx) -> Self { if let api::ExpressionKind::Slot(tk) = &api.kind { return ctx.exprs().get_expr(*tk).expect("Invalid slot"); @@ -60,6 +56,43 @@ impl Expr { } } } +impl Format for Expr { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + return print_expr(self, c, &mut HashSet::new()).await; + async fn print_expr<'a>( + expr: &'a Expr, + c: &'a (impl FmtCtx + ?Sized + 'a), + visited: &mut HashSet, + ) -> FmtUnit { + if visited.contains(&expr.id()) { + return "CYCLIC_EXPR".to_string().into(); + } + visited.insert(expr.id()); + match &*expr.0.kind.read().await { + ExprKind::Arg => "Arg".to_string().into(), + ExprKind::Atom(a) => a.print(c).await, + ExprKind::Bottom(e) if e.len() == 1 => format!("Bottom({e})").into(), + ExprKind::Bottom(e) => format!("Bottom(\n\t{}\n)", indent(&e.to_string())).into(), + ExprKind::Call(f, x) => tl_cache!(Rc: Rc::new(Variants::default() + .unbounded("{0} {1l}") + .bounded("({0} {1b})"))) + .units([f.print(c).await, x.print(c).await]), + ExprKind::Const(c) => format!("{c}").into(), + ExprKind::Lambda(None, body) => tl_cache!(Rc: Rc::new(Variants::default() + .unbounded("\\.{0l}") + .bounded("(\\.{0b})"))) + .units([body.print(c).await]), + ExprKind::Lambda(Some(path), body) => tl_cache!(Rc: Rc::new(Variants::default() + .unbounded("\\{0b}. {1l}") + .bounded("(\\{0b}. {1b})"))) + .units([format!("{path}").into(), body.print(c).await]), + ExprKind::Seq(l, r) => + tl_cache!(Rc: Rc::new(Variants::default().bounded("[{0b}]{1l}"))) + .units([l.print(c).await, r.print(c).await]), + } + } + } +} #[derive(Clone, Debug)] pub enum ExprKind { @@ -128,6 +161,21 @@ impl PathSet { } } } +impl fmt::Display for PathSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn print_step(step: Step) -> &'static str { if step == Step::Left { "l" } else { "r" } } + let step_s = self.steps.iter().copied().map(print_step).join(""); + match &self.next { + Some((left, right)) => { + if !step_s.is_empty() { + write!(f, "{step_s}>")?; + } + write!(f, "({left}|{right})") + }, + None => write!(f, "{step_s}"), + } + } +} pub struct WeakExpr(Weak); impl WeakExpr { diff --git a/orchid-host/src/extension.rs b/orchid-host/src/extension.rs index c1725df..e491e12 100644 --- a/orchid-host/src/extension.rs +++ b/orchid-host/src/extension.rs @@ -15,10 +15,10 @@ use orchid_api::HostMsgSet; use orchid_api_traits::Request; use orchid_base::builtin::ExtInit; use orchid_base::clone; +use orchid_base::format::{FmtCtxImpl, Format}; use orchid_base::interner::Tok; use orchid_base::logging::Logger; use orchid_base::reqnot::{ReqNot, Requester as _}; -use orchid_base::tree::AtomRepr; use crate::api; use crate::atom::AtomHand; @@ -152,8 +152,11 @@ impl Extension { req_in.send(ReqPair(rm.clone(), rep_in)).await.unwrap(); hand.handle(&rm, &rep_out.recv().await.unwrap()).await }, - api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => - hand.handle(eap, &AtomHand::new(atom.clone(), &ctx).await.print().await).await, + api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => { + let atom = AtomHand::new(atom.clone(), &ctx).await; + let unit = atom.print(&FmtCtxImpl { i: &this.ctx().i }).await; + hand.handle(eap, &unit.to_api()).await + }, } }) } diff --git a/orchid-host/src/parse.rs b/orchid-host/src/parse.rs index d693c20..5a7de26 100644 --- a/orchid-host/src/parse.rs +++ b/orchid-host/src/parse.rs @@ -59,7 +59,7 @@ pub async fn parse_item( ) -> OrcRes> { match item.pop_front() { Some((TokTree { tok: Token::Name(n), .. }, postdisc)) => match n { - n if *n == item.i("export").await => match try_pop_no_fluff(postdisc).await? { + n if *n == item.i().i("export").await => match try_pop_no_fluff(postdisc).await? { Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } => parse_exportable_item(ctx, path, comments, true, n.clone(), tail).await, Parsed { output: TokTree { tok: Token::NS, .. }, tail } => { @@ -70,12 +70,12 @@ pub async fn parse_item( ([], Some(n)) => ok.push(Item { comments: comments.clone(), pos, kind: ItemKind::Export(n) }), (_, Some(_)) => ctx.reporter().report(mk_err( - tail.i("Compound export").await, + tail.i().i("Compound export").await, "Cannot export compound names (names containing the :: separator)", [pos.into()], )), (_, None) => ctx.reporter().report(mk_err( - tail.i("Wildcard export").await, + tail.i().i("Wildcard export").await, "Exports cannot contain the globstar *", [pos.into()], )), @@ -85,12 +85,12 @@ pub async fn parse_item( Ok(ok) }, Parsed { output, tail } => Err(mk_errv( - tail.i("Malformed export").await, + tail.i().i("Malformed export").await, "`export` can either prefix other lines or list names inside ::( ) or ::[ ]", [Pos::Range(output.range.clone()).into()], )), }, - n if *n == item.i("import").await => parse_import(ctx, postdisc).await.map(|v| { + n if *n == item.i().i("import").await => parse_import(ctx, postdisc).await.map(|v| { Vec::from_iter(v.into_iter().map(|(t, pos)| Item { comments: comments.clone(), pos, @@ -99,10 +99,11 @@ pub async fn parse_item( }), n => parse_exportable_item(ctx, path, comments, false, n.clone(), postdisc).await, }, - Some(_) => - Err(mk_errv(item.i("Expected a line type").await, "All lines must begin with a keyword", [ - Pos::Range(item.pos()).into(), - ])), + Some(_) => Err(mk_errv( + item.i().i("Expected a line type").await, + "All lines must begin with a keyword", + [Pos::Range(item.pos()).into()], + )), None => unreachable!("These lines are filtered and aggregated in earlier stages"), } } @@ -124,20 +125,20 @@ pub async fn parse_exportable_item( discr: Tok, tail: ParsSnippet<'_>, ) -> OrcRes> { - let kind = if discr == tail.i("mod").await { + let kind = if discr == tail.i().i("mod").await { let (name, body) = parse_module(ctx, path, tail).await?; ItemKind::Member(Member::new(name, MemberKind::Mod(body))) - } else if discr == tail.i("const").await { + } else if discr == tail.i().i("const").await { let (name, val) = parse_const(tail).await?; - let locator = CodeLocator::to_const(tail.i(&path.push(name.clone()).unreverse()).await); + let locator = CodeLocator::to_const(tail.i().i(&path.push(name.clone()).unreverse()).await); ItemKind::Member(Member::new(name, MemberKind::Const(Code::from_code(locator, val)))) } else if let Some(sys) = ctx.systems().find(|s| s.can_parse(discr.clone())) { let line = sys.parse(tail.to_vec(), exported, comments).await?; - return parse_items(ctx, path, Snippet::new(tail.prev(), &line, tail.interner())).await; + return parse_items(ctx, path, Snippet::new(tail.prev(), &line, tail.i())).await; } else { let ext_lines = ctx.systems().flat_map(System::line_types).join(", "); return Err(mk_errv( - tail.i("Unrecognized line type").await, + tail.i().i("Unrecognized line type").await, format!("Line types are: const, mod, macro, grammar, {ext_lines}"), [Pos::Range(tail.prev().range.clone()).into()], )); @@ -154,18 +155,18 @@ pub async fn parse_module( Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } => (n.clone(), tail), Parsed { output, .. } => { return Err(mk_errv( - tail.i("Missing module name").await, - format!("A name was expected, {} was found", output.print().await), + tail.i().i("Missing module name").await, + format!("A name was expected, {} was found", tail.fmt(output).await), [Pos::Range(output.range.clone()).into()], )); }, }; let Parsed { output, tail: surplus } = try_pop_no_fluff(tail).await?; expect_end(surplus).await?; - let Some(body) = output.as_s(Paren::Round, tail.interner()) else { + let Some(body) = output.as_s(Paren::Round, tail.i()) else { return Err(mk_errv( - tail.i("Expected module body").await, - format!("A ( block ) was expected, {} was found", output.print().await), + tail.i().i("Expected module body").await, + format!("A ( block ) was expected, {} was found", tail.fmt(output).await), [Pos::Range(output.range.clone()).into()], )); }; @@ -177,16 +178,16 @@ pub async fn parse_const(tail: ParsSnippet<'_>) -> OrcRes<(Tok, Vec) -> OrcRes> { let (range, tok, tail) = match &ttree.tok { Token::S(p, b) => ( ttree.range.clone(), - MTok::S(*p, parse_mtree(Snippet::new(ttree, b, snip.interner())).boxed_local().await?), + MTok::S(*p, parse_mtree(Snippet::new(ttree, b, snip.i())).boxed_local().await?), tail, ), Token::Name(tok) => { @@ -210,7 +211,7 @@ pub async fn parse_mtree(mut snip: ParsSnippet<'_>) -> OrcRes> { let Parsed { output, tail } = try_pop_no_fluff(tail).await?; let Some(seg) = output.as_name() else { return Err(mk_errv( - tail.i("Namespaced name interrupted").await, + tail.i().i("Namespaced name interrupted").await, "In expression context, :: must always be followed by a name.\n\ ::() is permitted only in import and export items", [Pos::Range(output.range.clone()).into()], @@ -220,15 +221,11 @@ pub async fn parse_mtree(mut snip: ParsSnippet<'_>) -> OrcRes> { snip = tail; end = output.range.end; } - ( - ttree.range.start..end, - MTok::Name(Sym::new(segments, snip.interner()).await.unwrap()), - snip, - ) + (ttree.range.start..end, MTok::Name(Sym::new(segments, snip.i()).await.unwrap()), snip) }, Token::NS => { return Err(mk_errv( - tail.i("Unexpected :: in macro pattern").await, + tail.i().i("Unexpected :: in macro pattern").await, ":: can only follow a name outside export statements", [Pos::Range(ttree.range.clone()).into()], )); @@ -236,10 +233,10 @@ pub async fn parse_mtree(mut snip: ParsSnippet<'_>) -> OrcRes> { Token::Ph(ph) => (ttree.range.clone(), MTok::Ph(ph.clone()), tail), Token::Atom(_) | Token::Macro(_) => { return Err(mk_errv( - tail.i("Unsupported token in macro patterns").await, + tail.i().i("Unsupported token in macro patterns").await, format!( "Macro patterns can only contain names, braces, and lambda, not {}.", - ttree.print().await + tail.fmt(ttree).await ), [Pos::Range(ttree.range.clone()).into()], )); @@ -249,13 +246,13 @@ pub async fn parse_mtree(mut snip: ParsSnippet<'_>) -> OrcRes> { Token::LambdaHead(arg) => ( ttree.range.start..snip.pos().end, MTok::Lambda( - parse_mtree(Snippet::new(ttree, arg, snip.interner())).boxed_local().await?, + parse_mtree(Snippet::new(ttree, arg, snip.i())).boxed_local().await?, parse_mtree(tail).boxed_local().await?, ), - Snippet::new(ttree, &[], snip.interner()), + Snippet::new(ttree, &[], snip.i()), ), Token::Slot(_) | Token::X(_) => - panic!("Did not expect {} in parsed token tree", &ttree.tok.print().await), + panic!("Did not expect {} in parsed token tree", tail.fmt(ttree).await), }; mtreev.push(MTree { pos: Pos::Range(range.clone()), tok: Rc::new(tok) }); snip = tail; @@ -272,7 +269,7 @@ pub async fn parse_macro( Parsed { tail, output: o @ TokTree { tok: Token::S(Paren::Round, b), .. } } => (tail, o, b), Parsed { output, .. } => { return Err(mk_errv( - tail.i("m").await, + tail.i().i("m").await, "Macro blocks must either start with a block or a ..$:number", [Pos::Range(output.range.clone()).into()], )); @@ -281,24 +278,22 @@ pub async fn parse_macro( expect_end(surplus).await?; let mut errors = Vec::new(); let mut rules = Vec::new(); - for (i, item) in - line_items(Snippet::new(prev, block, tail.interner())).await.into_iter().enumerate() - { + for (i, item) in line_items(Snippet::new(prev, block, tail.i())).await.into_iter().enumerate() { let Parsed { tail, output } = try_pop_no_fluff(item.tail).await?; - if !output.is_kw(tail.i("rule").await) { + if !output.is_kw(tail.i().i("rule").await) { errors.extend(mk_errv( - tail.i("non-rule in macro").await, - format!("Expected `rule`, got {}", output.print().await), + tail.i().i("non-rule in macro").await, + format!("Expected `rule`, got {}", tail.fmt(output).await), [Pos::Range(output.range.clone()).into()], )); continue; }; - let arrow = tail.i("=>").await; + let arrow = tail.i().i("=>").await; let (pat, body) = match tail.split_once(|t| t.is_kw(arrow.clone())) { Some((a, b)) => (a, b), None => { errors.extend(mk_errv( - tail.i("no => in macro rule").await, + tail.i().i("no => in macro rule").await, "The pattern and body of a rule must be separated by a =>", [Pos::Range(tail.pos()).into()], )); @@ -310,7 +305,7 @@ pub async fn parse_macro( pos: Pos::Range(tail.pos()), pattern: parse_mtree(pat).await?, kind: RuleKind::Native(Code::from_code( - CodeLocator::to_rule(tail.i(&path.unreverse()).await, macro_i, i as u16), + CodeLocator::to_rule(tail.i().i(&path.unreverse()).await, macro_i, i as u16), body.to_vec(), )), }) diff --git a/orchid-host/src/system.rs b/orchid-host/src/system.rs index 0bc0ba8..ec55a00 100644 --- a/orchid-host/src/system.rs +++ b/orchid-host/src/system.rs @@ -13,6 +13,7 @@ use orchid_base::async_once_cell::OnceCell; use orchid_base::char_filter::char_filter_match; use orchid_base::clone; use orchid_base::error::{OrcErrv, OrcRes}; +use orchid_base::format::{FmtCtx, FmtUnit, Format}; use orchid_base::interner::Tok; use orchid_base::parse::Comment; use orchid_base::reqnot::{ReqNot, Requester}; @@ -97,12 +98,14 @@ impl System { this.ctx.owned_atoms.write().await.remove(&drop); })) } - pub async fn print(&self) -> String { + pub fn downgrade(&self) -> WeakSystem { WeakSystem(Rc::downgrade(&self.0)) } +} +impl Format for System { + async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { let ctor = (self.0.ext.system_ctors().find(|c| c.id() == self.0.decl_id)) .expect("System instance with no associated constructor"); - format!("System({} @ {} #{})", ctor.name(), ctor.priority(), self.0.id.0) + format!("System({} @ {} #{})", ctor.name(), ctor.priority(), self.0.id.0).into() } - pub fn downgrade(&self) -> WeakSystem { WeakSystem(Rc::downgrade(&self.0)) } } pub struct WeakSystem(Weak); diff --git a/orchid-host/src/tree.rs b/orchid-host/src/tree.rs index 57c5e61..c974408 100644 --- a/orchid-host/src/tree.rs +++ b/orchid-host/src/tree.rs @@ -1,18 +1,21 @@ use std::fmt::Debug; +use std::rc::Rc; use std::sync::{Mutex, OnceLock}; use async_stream::stream; +use futures::future::join_all; use futures::{FutureExt, StreamExt}; use itertools::Itertools; use never::Never; -use orchid_base::clone; use orchid_base::error::OrcRes; +use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; use orchid_base::interner::Tok; use orchid_base::location::Pos; -use orchid_base::macros::mtreev_from_api; +use orchid_base::macros::{mtreev_fmt, mtreev_from_api}; use orchid_base::name::Sym; use orchid_base::parse::{Comment, Import}; -use orchid_base::tree::{AtomRepr, TokTree, Token}; +use orchid_base::tree::{AtomRepr, TokTree, Token, ttv_fmt}; +use orchid_base::{clone, tl_cache}; use ordered_float::NotNan; use crate::api; @@ -78,6 +81,22 @@ impl Item { Self { pos: Pos::from_api(&tree.location, &sys.ctx().i).await, comments, kind } } } +impl Format for Item { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + let comment_text = self.comments.iter().join("\n"); + let item_text = match &self.kind { + ItemKind::Import(i) => format!("import {i}").into(), + ItemKind::Export(e) => format!("export {e}").into(), + ItemKind::Macro(None, rules) => + tl_cache!(Rc: Rc::new(Variants::default().bounded("macro {{\n\t{0}\n}}"))) + .units([Variants::sequence(rules.len(), "\n", None) + .units(join_all(rules.iter().map(|r| r.print(c))).await)]), + _ => panic!(), + }; + tl_cache!(Rc: Rc::new(Variants::default().bounded("{0}\n{1}"))) + .units([comment_text.into(), item_text]) + } +} pub struct Member { pub name: Tok, @@ -172,6 +191,24 @@ pub struct Rule { pub pattern: Vec, pub kind: RuleKind, } +impl Format for Rule { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + FmtUnit::new( + tl_cache!(Rc: Rc::new(Variants::default().bounded("{0b}\n{1} => {2b}"))), + [ + self.comments.iter().join("\n").into(), + mtreev_fmt(&self.pattern, c).await, + match &self.kind { + RuleKind::Native(code) => code.print(c).await, + RuleKind::Remote(sys, id) => FmtUnit::new( + tl_cache!(Rc: Rc::new(Variants::default().bounded("{0} #{1}"))), + [sys.print(c).await, format!("{id:?}").into()], + ), + }, + ], + ) + } +} #[derive(Debug)] pub enum RuleKind { @@ -193,6 +230,17 @@ impl Code { Self { locator, source: Some(code), bytecode: OnceLock::new() } } } +impl Format for Code { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + if let Some(bc) = self.bytecode.get() { + return bc.print(c).await; + } + if let Some(src) = &self.source { + return ttv_fmt(src, c).await; + } + panic!("Code must be initialized with at least one state") + } +} /// Selects a code element /// diff --git a/orchid-std/src/string/str_atom.rs b/orchid-std/src/string/str_atom.rs index 07b0ef2..5ce375e 100644 --- a/orchid-std/src/string/str_atom.rs +++ b/orchid-std/src/string/str_atom.rs @@ -7,6 +7,7 @@ use async_std::io::Write; use orchid_api_derive::Coding; use orchid_api_traits::{Encode, Request}; use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::format::FmtUnit; use orchid_base::interner::Tok; use orchid_extension::atom::{AtomMethod, Atomic, MethodSetBuilder, Supports, TypAtom}; use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; @@ -66,7 +67,7 @@ impl From> for IntStrAtom { impl OwnedAtom for IntStrAtom { type Refs = (); async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.0.to_api()) } - async fn print(&self, _ctx: SysCtx) -> String { format!("{:?}i", *self.0) } + async fn print(&self, _ctx: SysCtx) -> FmtUnit { format!("{:?}i", *self.0).into() } async fn serialize(&self, _: SysCtx, write: Pin<&mut (impl Write + ?Sized)>) { self.0.encode(write).await } diff --git a/orcx/src/main.rs b/orcx/src/main.rs index 89581de..08e112d 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -10,6 +10,7 @@ use clap::{Parser, Subcommand}; use futures::{Stream, TryStreamExt, io}; use orchid_base::clone; use orchid_base::error::ReporterImpl; +use orchid_base::format::{FmtCtxImpl, take_first}; use orchid_base::logging::{LogStrategy, Logger}; use orchid_base::parse::Snippet; use orchid_base::tree::ttv_fmt; @@ -83,7 +84,7 @@ async fn main() -> io::Result { let mut buf = String::new(); file.read_to_string(&mut buf).unwrap(); let lexemes = lex(ctx.i.i(&buf).await, &systems, ctx).await.unwrap(); - println!("{}", ttv_fmt(&lexemes).await) + println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i: &ctx.i }).await, true)) }, Commands::Parse { file } => { let systems = init_systems(&args.system, &extensions).await.unwrap();