Formatter introduced

This commit is contained in:
2025-02-07 00:47:34 +01:00
parent b94d8a64cb
commit 40c5eaf3d5
23 changed files with 608 additions and 218 deletions

View File

@@ -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<AtomData>);

View File

@@ -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<Self> {
// 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<ExprTicket>,
) -> 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<Variants>: 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<Variants>: Rc::new(Variants::default()
.unbounded("\\.{0l}")
.bounded("(\\.{0b})")))
.units([body.print(c).await]),
ExprKind::Lambda(Some(path), body) => tl_cache!(Rc<Variants>: 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<Variants>: 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<ExprData>);
impl WeakExpr {

View File

@@ -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
},
}
})
}

View File

@@ -59,7 +59,7 @@ pub async fn parse_item(
) -> OrcRes<Vec<Item>> {
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<String>,
tail: ParsSnippet<'_>,
) -> OrcRes<Vec<Item>> {
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<String>, Vec<Pars
let Parsed { output, tail } = try_pop_no_fluff(tail).await?;
let Some(name) = output.as_name() else {
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 } = try_pop_no_fluff(tail).await?;
if !output.is_kw(tail.i("=").await) {
if !output.is_kw(tail.i().i("=").await) {
return Err(mk_errv(
tail.i("Missing walrus := separator").await,
format!("Expected operator := , found {}", output.print().await),
tail.i().i("Missing = separator").await,
format!("Expected = , found {}", tail.fmt(output).await),
[Pos::Range(output.range.clone()).into()],
));
}
@@ -200,7 +201,7 @@ pub async fn parse_mtree(mut snip: ParsSnippet<'_>) -> OrcRes<Vec<MacTree>> {
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<Vec<MacTree>> {
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<Vec<MacTree>> {
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<Vec<MacTree>> {
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<Vec<MacTree>> {
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(),
)),
})

View File

@@ -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<SystemInstData>);

View File

@@ -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<Variants>: 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<Variants>: Rc::new(Variants::default().bounded("{0}\n{1}")))
.units([comment_text.into(), item_text])
}
}
pub struct Member {
pub name: Tok<String>,
@@ -172,6 +191,24 @@ pub struct Rule {
pub pattern: Vec<MacTree>,
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<Variants>: 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<Variants>: 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
///