From 94958bfbf5f51341f6d8ce8868e50bc6631f0103 Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Tue, 15 Apr 2025 00:34:45 +0200 Subject: [PATCH] commit before easter break --- Cargo.lock | 7 + Cargo.toml | 2 +- notes/new_macro_model.md | 7 +- orchid-api-derive/Cargo.toml | 2 +- orchid-api-traits/Cargo.toml | 2 +- orchid-api/Cargo.toml | 2 +- orchid-api/src/lexer.rs | 4 +- orchid-api/src/lib.rs | 2 - orchid-api/src/macros.rs | 86 ---- orchid-api/src/parser.rs | 14 +- orchid-api/src/proto.rs | 5 +- orchid-api/src/system.rs | 4 +- orchid-api/src/tree.rs | 70 +++- orchid-base/Cargo.toml | 2 +- orchid-base/src/error.rs | 16 +- orchid-base/src/format.rs | 3 + orchid-base/src/lib.rs | 1 - orchid-base/src/macros.rs | 167 -------- orchid-base/src/name.rs | 5 +- orchid-base/src/parse.rs | 292 ++++++------- orchid-base/src/reqnot.rs | 19 +- orchid-base/src/tree.rs | 282 +++++-------- orchid-extension/Cargo.toml | 2 +- orchid-extension/src/atom.rs | 86 ++-- orchid-extension/src/atom_owned.rs | 58 ++- orchid-extension/src/atom_thin.rs | 30 +- orchid-extension/src/conv.rs | 4 +- orchid-extension/src/entrypoint.rs | 126 +++--- orchid-extension/src/expr.rs | 18 +- orchid-extension/src/func_atom.rs | 19 +- orchid-extension/src/gen_expr.rs | 34 +- orchid-extension/src/lexer.rs | 25 +- orchid-extension/src/lib.rs | 1 - orchid-extension/src/macros.rs | 103 ----- orchid-extension/src/parser.rs | 22 +- orchid-extension/src/system.rs | 13 +- orchid-extension/src/tokio.rs | 7 +- orchid-extension/src/tree.rs | 181 +++++--- orchid-host/Cargo.toml | 2 +- orchid-host/src/ctx.rs | 16 +- orchid-host/src/dealias.rs | 132 +++--- orchid-host/src/execute.rs | 37 +- orchid-host/src/expr.rs | 310 ++++++-------- orchid-host/src/expr_store.rs | 20 +- orchid-host/src/extension.rs | 14 +- orchid-host/src/lex.rs | 95 ++--- orchid-host/src/lib.rs | 3 +- orchid-host/src/macros.rs | 102 ----- orchid-host/src/parse.rs | 352 +++++++--------- orchid-host/src/parsed.rs | 383 +++++++++++++++++ orchid-host/src/system.rs | 56 ++- orchid-host/src/tree.rs | 385 ++---------------- orchid-macros/Cargo.toml | 6 + orchid-macros/src/main.rs | 3 + orchid-std/Cargo.toml | 7 +- orchid-std/src/lib.rs | 9 +- orchid-std/src/macros/macro_system.rs | 32 ++ orchid-std/src/macros/mactree.rs | 75 ++++ orchid-std/src/macros/mactree_lexer.rs | 64 +++ orchid-std/src/macros/mod.rs | 6 + .../src/macros}/rule/any_match.rs | 2 +- .../src/macros}/rule/build.rs | 5 +- .../src/macros}/rule/matcher.rs | 9 +- .../src => orchid-std/src/macros}/rule/mod.rs | 0 .../src/macros}/rule/scal_match.rs | 3 +- .../src/macros}/rule/shared.rs | 0 .../src/macros}/rule/state.rs | 4 +- .../src/macros}/rule/vec_attrs.rs | 0 .../src/macros}/rule/vec_match.rs | 2 +- orchid-std/src/std.rs | 77 ---- orchid-std/src/std/mod.rs | 4 + orchid-std/src/{ => std}/number/mod.rs | 1 + orchid-std/src/{ => std}/number/num_atom.rs | 2 - orchid-std/src/{ => std}/number/num_lexer.rs | 0 orchid-std/src/std/number/num_lib.rs | 34 ++ orchid-std/src/std/std_system.rs | 41 ++ orchid-std/src/{ => std}/string/mod.rs | 1 + orchid-std/src/{ => std}/string/str_atom.rs | 11 +- orchid-std/src/{ => std}/string/str_lexer.rs | 8 +- orchid-std/src/std/string/str_lib.rs | 15 + orchidlang/Cargo.toml | 2 +- orcx/Cargo.toml | 2 +- orcx/src/main.rs | 4 +- stdio-perftest/Cargo.toml | 2 +- xtask/Cargo.toml | 2 +- 85 files changed, 1874 insertions(+), 2189 deletions(-) delete mode 100644 orchid-api/src/macros.rs delete mode 100644 orchid-base/src/macros.rs delete mode 100644 orchid-extension/src/macros.rs delete mode 100644 orchid-host/src/macros.rs create mode 100644 orchid-host/src/parsed.rs create mode 100644 orchid-macros/Cargo.toml create mode 100644 orchid-macros/src/main.rs create mode 100644 orchid-std/src/macros/macro_system.rs create mode 100644 orchid-std/src/macros/mactree.rs create mode 100644 orchid-std/src/macros/mactree_lexer.rs create mode 100644 orchid-std/src/macros/mod.rs rename {orchid-host/src => orchid-std/src/macros}/rule/any_match.rs (95%) rename {orchid-host/src => orchid-std/src/macros}/rule/build.rs (96%) rename {orchid-host/src => orchid-std/src/macros}/rule/matcher.rs (91%) rename {orchid-host/src => orchid-std/src/macros}/rule/mod.rs (100%) rename {orchid-host/src => orchid-std/src/macros}/rule/scal_match.rs (92%) rename {orchid-host/src => orchid-std/src/macros}/rule/shared.rs (100%) rename {orchid-host/src => orchid-std/src/macros}/rule/state.rs (95%) rename {orchid-host/src => orchid-std/src/macros}/rule/vec_attrs.rs (100%) rename {orchid-host/src => orchid-std/src/macros}/rule/vec_match.rs (98%) delete mode 100644 orchid-std/src/std.rs create mode 100644 orchid-std/src/std/mod.rs rename orchid-std/src/{ => std}/number/mod.rs (68%) rename orchid-std/src/{ => std}/number/num_atom.rs (94%) rename orchid-std/src/{ => std}/number/num_lexer.rs (100%) create mode 100644 orchid-std/src/std/number/num_lib.rs create mode 100644 orchid-std/src/std/std_system.rs rename orchid-std/src/{ => std}/string/mod.rs (68%) rename orchid-std/src/{ => std}/string/str_atom.rs (92%) rename orchid-std/src/{ => std}/string/str_lexer.rs (95%) create mode 100644 orchid-std/src/std/string/str_lib.rs diff --git a/Cargo.lock b/Cargo.lock index 12261ff..1a9a8c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1186,13 +1186,19 @@ dependencies = [ "trait-set", ] +[[package]] +name = "orchid-macros" +version = "0.1.0" + [[package]] name = "orchid-std" version = "0.1.0" dependencies = [ "async-once-cell", "async-std", + "async-stream", "futures", + "hashbrown 0.15.2", "itertools", "never", "once_cell", @@ -1203,6 +1209,7 @@ dependencies = [ "orchid-extension", "ordered-float", "rust_decimal", + "test_executors", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 684ecf3..550e27d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,5 @@ members = [ "orchid-api", "orchid-api-derive", "orchid-api-traits", - "stdio-perftest", "xtask", + "stdio-perftest", "xtask", "orchid-macros", ] diff --git a/notes/new_macro_model.md b/notes/new_macro_model.md index b40ec1b..65e046e 100644 --- a/notes/new_macro_model.md +++ b/notes/new_macro_model.md @@ -42,9 +42,7 @@ Prioritised macro patterns must start and end with a vectorial placeholder. They Macros are checked from the outermost block inwards. 1. For every name token, test all named macros starting with that name - 1. Take the first rule that matches in each block - 2. If there are multiple matches across blocks, raise an ambiguity error - 3. If the tail is implicit, recurse on it + 1. If the tail is implicit, continue iterating 2. Test all prioritized macros 1. Take the first rule that matches in the highest prioritized block @@ -78,7 +76,8 @@ Recursion has to happen through the interpreter itself, so the macro system is d - atom `MacRecurState` holds the recursion state - function `resolve_recur` finds all matches on a MacTree - type: `MacRecurState -> MacTree -> MacTree` - - use all macros to find all matches in the tree + - use all relevant macros to find all matches in the tree + - since macros must contain a locally defined token, it can be assumed that at the point that a constant is evaluated and all imports in the parent module have been resolved, necessarily all relevant macro rules must have been loaded - for each match - check for recursion violations - wrap the body in iife-s corresponding to the named values in the match state diff --git a/orchid-api-derive/Cargo.toml b/orchid-api-derive/Cargo.toml index fd642c8..3442e52 100644 --- a/orchid-api-derive/Cargo.toml +++ b/orchid-api-derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "orchid-api-derive" version = "0.1.0" -edition = "2021" +edition = "2024" [lib] proc-macro = true diff --git a/orchid-api-traits/Cargo.toml b/orchid-api-traits/Cargo.toml index e5097e6..43ddd63 100644 --- a/orchid-api-traits/Cargo.toml +++ b/orchid-api-traits/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "orchid-api-traits" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/orchid-api/Cargo.toml b/orchid-api/Cargo.toml index ba53184..7802f05 100644 --- a/orchid-api/Cargo.toml +++ b/orchid-api/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "orchid-api" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/orchid-api/src/lexer.rs b/orchid-api/src/lexer.rs index f2abcb3..8a7647c 100644 --- a/orchid-api/src/lexer.rs +++ b/orchid-api/src/lexer.rs @@ -3,7 +3,7 @@ use std::ops::RangeInclusive; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; -use crate::{ExtHostReq, HostExtReq, OrcResult, ParsId, SysId, TStr, TokenTree, TreeTicket}; +use crate::{ExtHostReq, HostExtReq, OrcResult, ParsId, SysId, TStr, TokenTree}; /// - All ranges contain at least one character /// - All ranges are in increasing characeter order @@ -42,5 +42,5 @@ impl Request for SubLex { #[derive(Clone, Debug, Coding)] pub struct SubLexed { pub pos: u32, - pub ticket: TreeTicket, + pub tree: TokenTree, } diff --git a/orchid-api/src/lib.rs b/orchid-api/src/lib.rs index 4dc57a2..8041110 100644 --- a/orchid-api/src/lib.rs +++ b/orchid-api/src/lib.rs @@ -2,8 +2,6 @@ mod lexer; pub use lexer::*; mod format; pub use format::*; -mod macros; -pub use macros::*; mod atom; pub use atom::*; mod error; diff --git a/orchid-api/src/macros.rs b/orchid-api/src/macros.rs deleted file mode 100644 index f7b85bd..0000000 --- a/orchid-api/src/macros.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::collections::HashMap; -use std::num::NonZeroU64; - -use orchid_api_derive::{Coding, Hierarchy}; -use orchid_api_traits::Request; -use ordered_float::NotNan; - -use crate::{ - Atom, Comment, ExtHostReq, HostExtReq, Location, OrcResult, Paren, ParsId, SysId, TStr, TStrv, -}; - -#[derive(Clone, Copy, Debug, Coding, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct MacroTreeId(pub NonZeroU64); - -#[derive(Clone, Debug, Coding)] -pub struct MacroTree { - pub location: Location, - pub token: MacroToken, -} - -#[derive(Clone, Debug, Coding)] -pub enum MacroToken { - S(Paren, Vec), - Name(TStrv), - Slot(MacroTreeId), - Lambda(Vec, Vec), - Ph(Placeholder), - Atom(Atom), -} - -#[derive(Clone, Debug, Coding)] -pub struct MacroBlock { - pub priority: Option>, - pub rules: Vec, -} - -#[derive(Clone, Debug, Coding)] -pub struct MacroRule { - pub location: Location, - pub comments: Vec, - pub pattern: Vec, - pub id: MacroId, -} - -/// A specific macro rule with a specific pattern across invocations -#[derive(Clone, Copy, Debug, Coding, PartialEq, Eq, Hash)] -pub struct MacroId(pub NonZeroU64); - -/// After a pattern matches, this call executes the body of the macro. This -/// request returns None if an inner nested request raised an exception -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extends(HostExtReq)] -pub struct ApplyMacro { - pub sys: SysId, - pub id: MacroId, - /// Recursion token - pub run_id: ParsId, - /// Must contain exactly the keys that were specified as placeholders in the - /// pattern - pub params: HashMap>, -} -impl Request for ApplyMacro { - type Response = Option>>; -} - -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extends(ExtHostReq)] -pub struct RunMacros { - pub run_id: ParsId, - pub query: Vec, -} -impl Request for RunMacros { - type Response = Option>; -} - -#[derive(Clone, Debug, Coding)] -pub struct Placeholder { - pub name: TStr, - pub kind: PhKind, -} - -#[derive(Clone, Copy, Debug, Coding)] -pub enum PhKind { - Scalar, - Vector { priority: u8, at_least_one: bool }, -} diff --git a/orchid-api/src/parser.rs b/orchid-api/src/parser.rs index 043d189..7f0dd64 100644 --- a/orchid-api/src/parser.rs +++ b/orchid-api/src/parser.rs @@ -1,24 +1,18 @@ use std::num::NonZeroU64; -use orchid_api_derive::{Coding, Decode, Encode, Hierarchy}; +use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; -use crate::{Comment, HostExtReq, OrcResult, SysId, TokenTree}; +use crate::{Comment, HostExtReq, OrcResult, SysId, TStrv, TokenTree}; -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)] pub struct ParsId(pub NonZeroU64); -// impl orchid_api_traits::Decode for ParsId { -// async fn decode(mut read: -// std::pin::Pin<&mut R>) -> Self { -// Self(orchid_api_traits::Decode::decode(read.as_mut()).await) -// } -// } - #[derive(Clone, Debug, Coding, Hierarchy)] #[extends(HostExtReq)] pub struct ParseLine { pub sys: SysId, + pub module: TStrv, pub comments: Vec, pub exported: bool, pub line: Vec, diff --git a/orchid-api/src/proto.rs b/orchid-api/src/proto.rs index d8a893c..610c3a3 100644 --- a/orchid-api/src/proto.rs +++ b/orchid-api/src/proto.rs @@ -28,7 +28,7 @@ use async_std::io::{Read, Write}; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::{Channel, Decode, Encode, MsgSet, Request, read_exact, write_exact}; -use crate::{atom, expr, interner, lexer, logging, macros, parser, system, tree, vfs}; +use crate::{atom, expr, interner, lexer, logging, parser, system, tree, vfs}; static HOST_INTRO: &[u8] = b"Orchid host, binary API v0\n"; pub struct HostHeader { @@ -88,7 +88,7 @@ pub enum ExtHostReq { SysFwd(system::SysFwd), ExprReq(expr::ExprReq), SubLex(lexer::SubLex), - RunMacros(macros::RunMacros), + LsModule(tree::LsModule), } /// Notifications sent from the extension to the host @@ -119,7 +119,6 @@ pub enum HostExtReq { ParseLine(parser::ParseLine), GetMember(tree::GetMember), VfsReq(vfs::VfsReq), - ApplyMacro(macros::ApplyMacro), } /// Notifications sent from the host to the extension diff --git a/orchid-api/src/system.rs b/orchid-api/src/system.rs index 619f821..81ec932 100644 --- a/orchid-api/src/system.rs +++ b/orchid-api/src/system.rs @@ -52,11 +52,11 @@ pub struct NewSystem { pub depends: Vec, } impl Request for NewSystem { - type Response = SystemInst; + type Response = NewSystemResponse; } #[derive(Clone, Debug, Coding)] -pub struct SystemInst { +pub struct NewSystemResponse { /// The set of possible starting characters of tokens the lexer of this system /// can process. The lexer will notify this system if it encounters one of /// these characters.9 diff --git a/orchid-api/src/tree.rs b/orchid-api/src/tree.rs index 824ac61..3e8837f 100644 --- a/orchid-api/src/tree.rs +++ b/orchid-api/src/tree.rs @@ -1,13 +1,13 @@ +use std::collections::HashMap; use std::num::NonZeroU64; use std::ops::Range; use std::sync::Arc; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; -use ordered_float::NotNan; use crate::{ - Atom, Expression, HostExtReq, Location, MacroBlock, OrcError, Placeholder, SysId, TStr, TStrv, + ExprTicket, Expression, ExtHostReq, HostExtReq, Location, OrcError, SysId, TStr, TStrv, }; /// A token tree from a lexer recursion request. Its lifetime is the lex call, @@ -32,27 +32,24 @@ pub enum Token { LambdaHead(Vec), /// A name segment or an operator. Name(TStr), - /// An absolute name - Reference(TStrv), + /// A newly generated expression. The last place this is supposed to happen is + /// in lexers, parsers and macros should have enumerable many outputs + /// expressed as function calls. + NewExpr(Expression), + /// A pre-existing expression + Handle(ExprTicket), /// :: - NS, + NS(TStr, Box), /// Line break. BR, /// ( Round parens ), [ Square brackets ] or { Curly braces } S(Paren, Vec), - /// A new atom - Atom(Atom), - /// Anchor to insert a subtree - Slot(TreeTicket), /// A static compile-time error returned by failing lexers if - /// the rest of the source is likely still meaningful + /// the rest of the source is likely still meaningful. This is distinct from + /// NewExpr(Bottom) because it fails in dead branches too. Bottom(Vec), /// A comment Comment(Arc), - /// Placeholder - Ph(Placeholder), - /// Macro block head - Macro(Option>), } #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Coding)] @@ -75,7 +72,6 @@ pub struct Item { #[derive(Clone, Debug, Coding)] pub enum ItemKind { Member(Member), - Macro(MacroBlock), Export(TStr), Import(TStrv), } @@ -104,9 +100,53 @@ pub struct Module { pub items: Vec, } +/// Evaluate a lazy member. This call will only be issued to each system once. #[derive(Clone, Copy, Debug, Coding, Hierarchy)] #[extends(HostExtReq)] pub struct GetMember(pub SysId, pub TreeId); impl Request for GetMember { type Response = MemberKind; } + +/// This request can only be issued while the interpreter is running, so during +/// an atom call. +#[derive(Clone, Copy, Debug, Coding, Hierarchy)] +#[extends(ExtHostReq)] +pub struct LsModule(pub SysId, pub TStrv); +impl Request for LsModule { + type Response = Result; +} + +#[derive(Clone, Debug, Coding)] +pub enum LsModuleError { + InvalidPath, + IsConstant, + TreeUnavailable, +} + +#[derive(Clone, Debug, Coding)] +pub struct ModuleInfo { + /// If the name isn't a canonical name, returns the true name. + pub canonical: Option, + /// List the names defined in this module + pub members: HashMap, +} + +#[derive(Clone, Copy, Debug, Coding)] +pub struct MemberInfo { + /// true if the name is exported + pub exported: bool, + /// If it's imported, you can find the canonical name here + pub canonical: Option, + /// Whether the tree item is a constant value or a module + pub kind: MemberInfoKind, +} + +/// Indicates what kind of node a name refers to +#[derive(Clone, Copy, Debug, Coding)] +pub enum MemberInfoKind { + /// has children obtained with [crate::LsModule] + Module, + /// has a value retrievable in [crate::ExpressionKind::Const] + Constant, +} diff --git a/orchid-base/Cargo.toml b/orchid-base/Cargo.toml index 730fb16..8f97da7 100644 --- a/orchid-base/Cargo.toml +++ b/orchid-base/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "orchid-base" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/orchid-base/src/error.rs b/orchid-base/src/error.rs index 3810c7a..23a0d07 100644 --- a/orchid-base/src/error.rs +++ b/orchid-base/src/error.rs @@ -169,20 +169,16 @@ pub fn mk_errv( mk_err(description, message, posv).into() } -pub trait Reporter { - fn report(&self, e: impl Into); -} - -pub struct ReporterImpl { +pub struct Reporter { errors: RefCell>, } -impl ReporterImpl { + +impl Reporter { + pub fn report(&self, e: impl Into) { self.errors.borrow_mut().extend(e.into()) } pub fn new() -> Self { Self { errors: RefCell::new(vec![]) } } pub fn errv(self) -> Option { OrcErrv::new(self.errors.into_inner()).ok() } } -impl Reporter for ReporterImpl { - fn report(&self, e: impl Into) { self.errors.borrow_mut().extend(e.into()) } -} -impl Default for ReporterImpl { + +impl Default for Reporter { fn default() -> Self { Self::new() } } diff --git a/orchid-base/src/format.rs b/orchid-base/src/format.rs index 5cd7cf3..54a91ac 100644 --- a/orchid-base/src/format.rs +++ b/orchid-base/src/format.rs @@ -270,3 +270,6 @@ pub trait Format { impl Format for Never { async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { match *self {} } } + +/// Format with default strategy. Currently equal to [take_first_fmt] +pub async fn fmt(v: &(impl Format + ?Sized), i: &Interner) -> String { take_first_fmt(v, i).await } diff --git a/orchid-base/src/lib.rs b/orchid-base/src/lib.rs index 6fc8774..5d8a2d1 100644 --- a/orchid-base/src/lib.rs +++ b/orchid-base/src/lib.rs @@ -15,7 +15,6 @@ pub mod interner; pub mod join; pub mod location; pub mod logging; -pub mod macros; mod match_mapping; pub mod msg; pub mod name; diff --git a/orchid-base/src/macros.rs b/orchid-base/src/macros.rs deleted file mode 100644 index 73ea44f..0000000 --- a/orchid-base/src/macros.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; -use std::sync::Arc; - -use async_stream::stream; -use futures::future::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, tl_cache}; - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct MacroSlot<'a>(api::MacroTreeId, PhantomData<&'a ()>); -impl MacroSlot<'_> { - pub fn id(self) -> api::MacroTreeId { self.0 } -} - -trait_set! { - pub trait MacroAtomToApi = AsyncFnMut(&A) -> api::MacroToken; - pub trait MacroAtomFromApi<'a, A> = AsyncFnMut(&api::Atom) -> MTok<'a, A>; -} - -#[derive(Clone, Debug)] -pub struct MTree<'a, A> { - pub pos: Pos, - pub tok: Rc>, -} -impl<'a, A> MTree<'a, A> { - pub(crate) async fn from_api( - api: &api::MacroTree, - do_atom: &mut impl MacroAtomFromApi<'a, A>, - i: &Interner, - ) -> Self { - Self { - pos: Pos::from_api(&api.location, i).await, - tok: Rc::new(MTok::from_api(&api.token, i, do_atom).await), - } - } - pub(crate) async fn to_api(&self, do_atom: &mut impl MacroAtomToApi) -> api::MacroTree { - api::MacroTree { - location: self.pos.to_api(), - token: self.tok.to_api(do_atom).boxed_local().await, - } - } -} -impl Format for MTree<'_, A> { - async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { - self.tok.print(c).boxed_local().await - } -} - -#[derive(Clone, Debug)] -pub enum MTok<'a, A> { - S(Paren, Vec>), - Name(Sym), - Slot(MacroSlot<'a>), - Lambda(Vec>, Vec>), - Ph(Ph), - Atom(A), - /// Used in extensions to directly return input - Ref(Arc>), - /// Used in the matcher to skip previous macro output which can only go in - /// vectorial placeholders - Done(Rc>), -} -impl<'a, A> MTok<'a, A> { - pub(crate) async fn from_api( - api: &api::MacroToken, - i: &Interner, - do_atom: &mut impl MacroAtomFromApi<'a, A>, - ) -> Self { - match_mapping!(&api, api::MacroToken => MTok::<'a, A> { - Lambda(x => mtreev_from_api(x, i, do_atom).await, b => mtreev_from_api(b, i, do_atom).await), - Name(t => Sym::from_api(*t, i).await), - Slot(tk => MacroSlot(*tk, PhantomData)), - S(p.clone(), b => mtreev_from_api(b, i, do_atom).await), - Ph(ph => Ph::from_api(ph, i).await), - } { - api::MacroToken::Atom(a) => do_atom(a).await - }) - } - pub(crate) async fn to_api(&self, do_atom: &mut impl MacroAtomToApi) -> api::MacroToken { - async fn sink(n: &Never) -> T { match *n {} } - match_mapping!(&self, MTok => api::MacroToken { - Lambda(x => mtreev_to_api(x, do_atom).await, b => mtreev_to_api(b, do_atom).await), - Name(t.tok().to_api()), - Ph(ph.to_api()), - S(p.clone(), b => mtreev_to_api(b, do_atom).await), - Slot(tk.0.clone()), - } { - MTok::Ref(r) => r.to_api(&mut sink).boxed_local().await, - MTok::Done(t) => t.to_api(do_atom).boxed_local().await, - MTok::Atom(a) => do_atom(a).await, - }) - } - 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).boxed_local().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).boxed_local().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, - i: &Interner, - do_atom: &'b mut (impl MacroAtomFromApi<'a, A> + 'b), -) -> Vec> { - stream! { - for api in apiv { - yield MTree::from_api(api, do_atom, i).boxed_local().await - } - } - .collect() - .await -} - -pub async fn mtreev_to_api<'a: 'b, 'b, A: 'b>( - v: impl IntoIterator>, - do_atom: &mut impl MacroAtomToApi, -) -> Vec { - let mut out = Vec::new(); - for t in v { - out.push(t.to_api(do_atom).await); - } - 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/name.rs b/orchid-base/src/name.rs index afd4459..8cecd0f 100644 --- a/orchid-base/src/name.rs +++ b/orchid-base/src/name.rs @@ -2,11 +2,10 @@ use std::borrow::Borrow; use std::hash::Hash; -use std::iter::Cloned; use std::num::{NonZeroU64, NonZeroUsize}; -use std::ops::{Bound, Deref, Index, RangeBounds}; +use std::ops::{Deref, Index}; use std::path::Path; -use std::{fmt, slice, vec}; +use std::{fmt, vec}; use futures::future::{OptionFuture, join_all}; use itertools::Itertools; diff --git a/orchid-base/src/parse.rs b/orchid-base/src/parse.rs index 6f39009..0c8cf92 100644 --- a/orchid-base/src/parse.rs +++ b/orchid-base/src/parse.rs @@ -2,16 +2,30 @@ use std::fmt::{self, Display}; use std::iter; use std::ops::{Deref, Range}; +use futures::FutureExt; use futures::future::join_all; use itertools::Itertools; use crate::api; use crate::error::{OrcRes, Reporter, mk_err, mk_errv}; -use crate::format::{Format, take_first_fmt}; +use crate::format::fmt; use crate::interner::{Interner, Tok}; use crate::location::Pos; -use crate::name::{VName, VPath}; -use crate::tree::{AtomRepr, ExtraTok, Paren, TokTree, Token}; +use crate::name::VPath; +use crate::tree::{ExprRepr, ExtraTok, Paren, TokTree, Token}; + +pub trait ParseCtx { + fn i(&self) -> &Interner; + fn reporter(&self) -> &Reporter; +} +pub struct ParseCtxImpl<'a> { + pub i: &'a Interner, + pub r: &'a Reporter, +} +impl ParseCtx for ParseCtxImpl<'_> { + fn i(&self) -> &Interner { self.i } + fn reporter(&self) -> &Reporter { self.r } +} pub fn name_start(c: char) -> bool { c.is_alphabetic() || c == '_' } pub fn name_char(c: char) -> bool { name_start(c) || c.is_numeric() } @@ -21,51 +35,44 @@ 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>, - cur: &'a [TokTree<'b, A, X>], - interner: &'a Interner, +pub struct Snippet<'a, A: ExprRepr, X: ExtraTok> { + prev: &'a TokTree, + cur: &'a [TokTree], } -impl<'a, 'b, A: AtomRepr, X: ExtraTok> Snippet<'a, 'b, A, X> { - pub fn new( - prev: &'a TokTree<'b, A, X>, - cur: &'a [TokTree<'b, A, X>], - interner: &'a Interner, - ) -> Self { - Self { prev, cur, interner } - } - pub fn i(&self) -> &'a Interner { self.interner } +impl<'a, A, X> Snippet<'a, A, X> +where + A: ExprRepr, + X: ExtraTok, +{ + pub fn new(prev: &'a TokTree, cur: &'a [TokTree]) -> Self { Self { prev, cur } } 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 }; + let Self { prev, cur } = self; + let fst = Self { prev, cur: &cur[..pos as usize] }; let new_prev = if pos == 0 { self.prev } else { &self.cur[pos as usize - 1] }; - let snd = Self { prev: new_prev, cur: &self.cur[pos as usize..], interner }; + let snd = Self { prev: new_prev, cur: &self.cur[pos as usize..] }; (fst, snd) } - pub fn find_idx(self, mut f: impl FnMut(&Token<'b, A, X>) -> bool) -> Option { + pub fn find_idx(self, mut f: impl FnMut(&Token) -> bool) -> Option { self.cur.iter().position(|t| f(&t.tok)).map(|t| t as u32) } - pub fn get(self, idx: u32) -> Option<&'a TokTree<'b, A, X>> { self.cur.get(idx as usize) } + pub fn get(self, idx: u32) -> Option<&'a TokTree> { self.cur.get(idx as usize) } pub fn len(self) -> u32 { self.cur.len() as u32 } - pub fn prev(self) -> &'a TokTree<'b, A, X> { self.prev } + pub fn prev(self) -> &'a TokTree { self.prev } pub fn pos(self) -> Range { (self.cur.first().map(|f| f.range.start..self.cur.last().unwrap().range.end)) .unwrap_or(self.prev.range.clone()) } - pub fn pop_front(self) -> Option<(&'a TokTree<'b, A, X>, Self)> { + pub fn pop_front(self) -> Option<(&'a TokTree, Self)> { self.cur.first().map(|r| (r, self.split_at(1).1)) } - pub fn pop_back(self) -> Option<(Self, &'a TokTree<'b, A, X>)> { + pub fn pop_back(self) -> Option<(Self, &'a TokTree)> { self.cur.last().map(|r| (self.split_at(self.len() - 1).0, r)) } - pub fn split_once(self, f: impl FnMut(&Token<'b, A, X>) -> bool) -> Option<(Self, Self)> { + pub fn split_once(self, f: impl FnMut(&Token) -> bool) -> Option<(Self, Self)> { let idx = self.find_idx(f)?; Some((self.split_at(idx).0, self.split_at(idx + 1).1)) } - pub fn split( - mut self, - mut f: impl FnMut(&Token<'b, A, X>) -> bool, - ) -> impl Iterator { + pub fn split(mut self, mut f: impl FnMut(&Token) -> bool) -> impl Iterator { iter::from_fn(move || { if self.is_empty() { return None; @@ -77,26 +84,22 @@ impl<'a, 'b, A: AtomRepr, X: ExtraTok> Snippet<'a, 'b, A, X> { } pub fn is_empty(self) -> bool { self.len() == 0 } pub fn skip_fluff(self) -> Self { - let non_fluff_start = self.find_idx(|t| !matches!(t, Token::NS | Token::Comment(_))); + let non_fluff_start = self.find_idx(|t| !matches!(t, Token::BR | 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> { +impl Copy for Snippet<'_, A, X> {} +impl Clone for Snippet<'_, A, X> { fn clone(&self) -> Self { *self } } -impl<'b, A: AtomRepr, X: ExtraTok> Deref for Snippet<'_, 'b, A, X> { - type Target = [TokTree<'b, A, X>]; +impl Deref for Snippet<'_, A, X> { + type Target = [TokTree]; fn deref(&self) -> &Self::Target { self.cur } } /// Remove tokens that aren't meaningful in expression context, such as comments /// or line breaks -pub fn strip_fluff<'a, A: AtomRepr, X: ExtraTok>( - tt: &TokTree<'a, A, X>, -) -> Option> { +pub fn strip_fluff(tt: &TokTree) -> Option> { let tok = match &tt.tok { Token::BR => return None, Token::Comment(_) => return None, @@ -125,9 +128,10 @@ 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>> { +pub async fn line_items<'a, A: ExprRepr, X: ExtraTok>( + ctx: &impl ParseCtx, + snip: Snippet<'a, A, X>, +) -> Vec, A, X>> { let mut items = Vec::new(); let mut comments = Vec::new(); for mut line in snip.split(|t| matches!(t, Token::BR)) { @@ -143,7 +147,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().i(&**c).await, pos: Pos::Range(t.range.clone()) }, + Comment { text: ctx.i().i(&**c).await, pos: Pos::Range(t.range.clone()) }, _ => unreachable!("All are comments checked above"), } })) @@ -155,23 +159,26 @@ pub async fn line_items<'a, 'b, A: AtomRepr, X: ExtraTok>( items } -pub async fn try_pop_no_fluff<'a, 'b, A: AtomRepr, X: ExtraTok>( - snip: Snippet<'a, 'b, A, X>, -) -> ParseRes<'a, 'b, &'a TokTree<'b, A, X>, A, X> { +pub async fn try_pop_no_fluff<'a, A: ExprRepr, X: ExtraTok>( + ctx: &impl ParseCtx, + snip: Snippet<'a, A, X>, +) -> ParseRes<'a, &'a TokTree, A, X> { match snip.skip_fluff().pop_front() { Some((output, tail)) => Ok(Parsed { output, tail }), - None => - Err(mk_errv(snip.i().i("Unexpected end").await, "Pattern ends abruptly", [Pos::Range( - snip.pos(), - ) - .into()])), + None => Err(mk_errv(ctx.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<()> { +pub async fn expect_end( + ctx: &impl ParseCtx, + snip: Snippet<'_, impl ExprRepr, impl ExtraTok>, +) -> OrcRes<()> { match snip.skip_fluff().get(0) { Some(surplus) => Err(mk_errv( - snip.i().i("Extra code after end of line").await, + ctx.i().i("Extra code after end of line").await, "Code found after the end of the line", [Pos::Range(surplus.range.clone()).into()], )), @@ -179,128 +186,86 @@ pub async fn expect_end(snip: Snippet<'_, '_, impl AtomRepr, impl ExtraTok>) -> } } -pub async fn expect_tok<'a, 'b, A: AtomRepr, X: ExtraTok>( - snip: Snippet<'a, 'b, A, X>, +pub async fn expect_tok<'a, A: ExprRepr, X: ExtraTok>( + ctx: &impl ParseCtx, + snip: Snippet<'a, A, X>, tok: Tok, -) -> ParseRes<'a, 'b, (), A, X> { - let Parsed { output: head, tail } = try_pop_no_fluff(snip).await?; +) -> ParseRes<'a, (), A, X> { + let Parsed { output: head, tail } = try_pop_no_fluff(ctx, snip).await?; match &head.tok { Token::Name(n) if *n == tok => Ok(Parsed { output: (), tail }), t => Err(mk_errv( - snip.i().i("Expected specific keyword").await, - format!("Expected {tok} but found {:?}", snip.fmt(t).await), + ctx.i().i("Expected specific keyword").await, + format!("Expected {tok} but found {:?}", fmt(t, ctx.i()).await), [Pos::Range(head.range.clone()).into()], )), } } -pub struct Parsed<'a, 'b, T, A: AtomRepr, X: ExtraTok> { +pub struct Parsed<'a, T, H: ExprRepr, X: ExtraTok> { pub output: T, - pub tail: Snippet<'a, 'b, A, X>, + pub tail: Snippet<'a, H, X>, } -pub type ParseRes<'a, 'b, T, A, X> = OrcRes>; +pub type ParseRes<'a, T, H, X> = OrcRes>; -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<(Import, Pos)>, A, X> { - let ret = rec(ctx, tail).await; +pub async fn parse_multiname<'a, A: ExprRepr, X: ExtraTok>( + ctx: &impl ParseCtx, + tail: Snippet<'a, A, X>, +) -> ParseRes<'a, Vec<(Import, Pos)>, A, X> { + let Some((tt, tail)) = tail.skip_fluff().pop_front() else { + return Err(mk_errv( + ctx.i().i("Expected token").await, + "Expected a name, a parenthesized list of names, or a globstar.", + [Pos::Range(tail.pos()).into()], + )); + }; + let ret = rec(tt, ctx).await; #[allow(clippy::type_complexity)] // it's an internal function - pub async fn rec<'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().i(",").await; - let globstar = tail.i().i("*").await; - let Some((name, tail)) = tail.skip_fluff().pop_front() else { - return Err(mk_errv( - tail.i().i("Expected name").await, - "Expected a name, a list of names, or a globstar.", - [Pos::Range(tail.pos()).into()], - )); - }; - 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().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())), - (Ok(Parsed { tail, .. }), Err(e)) => { - ctx.report(e); - Ok(Parsed { output: vec![], tail }) - }, - (Ok(Parsed { tail, output }), Ok(pre)) => Ok(Parsed { - output: output.into_iter().update(|i| i.0.push(pre.clone())).collect_vec(), - tail, - }), - } - } else { - let output = match &name.tok { - Token::Name(ntok) => { - let nopt = match ntok { - n if *n == globstar => None, - n if n.starts_with(op_char) => { - return Err(mk_errv( - tail.i().i("Unescaped operator in multiname").await, - "Operators in multinames should be enclosed in []", - [Pos::Range(name.range.clone()).into()], - )); - }, - n => Some(n.clone()), - }; - vec![(vec![], nopt, Pos::Range(name.range.clone()))] - }, - Token::S(Paren::Square, b) => { - let mut ok = Vec::new(); - for tt in b.iter() { - match &tt.tok { - Token::Name(n) if n.starts_with(op_char) => - ok.push((vec![], Some(n.clone()), Pos::Range(tt.range.clone()))), - Token::BR | Token::Comment(_) => (), - _ => ctx.report(mk_err( - 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()], - )), - } + pub async fn rec( + tt: &TokTree, + ctx: &impl ParseCtx, + ) -> OrcRes>, Option>, Pos)>> { + let ttpos = Pos::Range(tt.range.clone()); + match &tt.tok { + Token::NS(ns, body) => { + if !ns.starts_with(name_start) { + ctx.reporter().report(mk_err( + ctx.i().i("Unexpected name prefix").await, + "Only names can precede ::", + [ttpos.into()], + )) + }; + let out = Box::pin(rec(body, ctx)).await?; + Ok(out.into_iter().update(|i| i.0.push(ns.clone())).collect_vec()) + }, + Token::Name(ntok) => { + let n = ntok; + let nopt = Some(n.clone()); + Ok(vec![(vec![], nopt, Pos::Range(tt.range.clone()))]) + }, + Token::S(Paren::Round, b) => { + let mut o = Vec::new(); + let mut body = Snippet::new(tt, b); + 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), } - ok - }, - Token::S(Paren::Round, b) => { - let mut ok = Vec::new(); - let body = Snippet::new(name, b, tail.interner); - for csent in body.split(|n| matches!(n, Token::Name(n) if *n == comma)) { - match Box::pin(rec(ctx, csent)).await { - Err(e) => ctx.report(e), - Ok(Parsed { output, tail }) => match tail.get(0) { - None => ok.extend(output), - Some(t) => ctx.report(mk_err( - 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()], - )), - }, - } - } - ok - }, - t => { - return Err(mk_errv( - tail.i().i("Unrecognized name end").await, - format!("Names cannot end with {:?} tokens", tail.fmt(t).await), - [Pos::Range(name.range.clone()).into()], - )); - }, - }; - Ok(Parsed { output, tail }) + body = tail; + } + Ok(o) + }, + t => { + return Err(mk_errv( + ctx.i().i("Unrecognized name end").await, + format!("Names cannot end with {:?} tokens", fmt(t, ctx.i()).await), + [ttpos.into()], + )); + }, } } - ret.map(|Parsed { output, tail }| { + ret.map(|output| { let output = (output.into_iter()) .map(|(p, name, pos)| (Import { path: VPath::new(p.into_iter().rev()), name }, pos)) .collect_vec(); @@ -327,14 +292,5 @@ mod test { use super::Snippet; - fn _covary_snip_a<'a, 'b>( - x: Snippet<'static, 'b, Never, Never>, - ) -> Snippet<'a, 'b, Never, Never> { - x - } - fn _covary_snip_b<'a, 'b>( - x: Snippet<'a, 'static, Never, Never>, - ) -> Snippet<'a, 'b, Never, Never> { - x - } + fn _covary_snip_a<'a>(x: Snippet<'static, Never, Never>) -> Snippet<'a, Never, Never> { x } } diff --git a/orchid-base/src/reqnot.rs b/orchid-base/src/reqnot.rs index 60fd53d..db27019 100644 --- a/orchid-base/src/reqnot.rs +++ b/orchid-base/src/reqnot.rs @@ -1,4 +1,4 @@ -use std::any::{Any, TypeId}; +use std::any::Any; use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; @@ -46,6 +46,9 @@ pub trait ReqHandlish { } fn defer_drop_objsafe(&self, val: Box); } +impl ReqHandlish for &'_ dyn ReqHandlish { + fn defer_drop_objsafe(&self, val: Box) { (**self).defer_drop_objsafe(val) } +} #[derive(destructure)] pub struct RequestHandle<'a, MS: MsgSet> { @@ -170,12 +173,16 @@ pub trait DynRequester { pub struct MappedRequester<'a, T: 'a>(Box LocalBoxFuture<'a, RawReply> + 'a>, Logger); impl<'a, T> MappedRequester<'a, T> { - fn new(req: U, logger: Logger) -> Self - where T: Into { + fn new U::Transfer + 'a>( + req: U, + cb: F, + logger: Logger, + ) -> Self { let req_arc = Arc::new(req); + let cb_arc = Arc::new(cb); MappedRequester( Box::new(move |t| { - Box::pin(clone!(req_arc; async move { req_arc.raw_request(t.into()).await})) + Box::pin(clone!(req_arc, cb_arc; async move { req_arc.raw_request(cb_arc(t)).await})) }), logger, ) @@ -217,10 +224,10 @@ pub trait Requester: DynRequester { &self, data: R, ) -> impl Future; - fn map<'a, U: Into>(self) -> MappedRequester<'a, U> + fn map<'a, U>(self, cb: impl Fn(U) -> Self::Transfer + 'a) -> MappedRequester<'a, U> where Self: Sized + 'a { let logger = self.logger().clone(); - MappedRequester::new(self, logger) + MappedRequester::new(self, cb, logger) } } diff --git a/orchid-base/src/tree.rs b/orchid-base/src/tree.rs index a145935..87e53a6 100644 --- a/orchid-base/src/tree.rs +++ b/orchid-base/src/tree.rs @@ -6,38 +6,61 @@ use std::ops::Range; use std::rc::Rc; use std::sync::Arc; -pub use api::PhKind; use async_stream::stream; use futures::future::join_all; use futures::{FutureExt, StreamExt}; use itertools::Itertools; use never::Never; -use ordered_float::NotNan; +use orchid_api_traits::Coding; 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::Sym; use crate::parse::Snippet; 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 = Format + Clone + fmt::Debug; - pub trait RefDoExtra = AsyncFnMut(&X, Range) -> api::TokenTree; +pub trait TokenVariant: Format + Clone + fmt::Debug { + type FromApiCtx<'a>; + type ToApiCtx<'a>; + fn from_api( + api: &ApiEquiv, + ctx: &mut Self::FromApiCtx<'_>, + pos: Pos, + i: &Interner, + ) -> impl Future; + fn into_api(self, ctx: &mut Self::ToApiCtx<'_>) -> impl Future; +} +impl TokenVariant for Never { + type FromApiCtx<'a> = (); + type ToApiCtx<'a> = (); + async fn from_api(_: &T, _: &mut Self::FromApiCtx<'_>, _: Pos, _: &Interner) -> Self { + panic!("Cannot deserialize Never") + } + async fn into_api(self, _: &mut Self::ToApiCtx<'_>) -> T { match self {} } } -pub fn recur<'a, A: AtomRepr, X: ExtraTok>( - tt: TokTree<'a, A, X>, - f: &impl Fn(TokTree<'a, A, X>, &dyn RecurCB<'a, A, X>) -> TokTree<'a, A, X>, -) -> TokTree<'a, A, X> { +trait_set! { + // TokenHandle + pub trait ExprRepr = TokenVariant; + // TokenExpr + pub trait ExtraTok = TokenVariant; +} + +trait_set! { + pub trait RecurCB = Fn(TokTree) -> TokTree; +} + +pub fn recur( + tt: TokTree, + f: &impl Fn(TokTree, &dyn RecurCB) -> TokTree, +) -> TokTree { f(tt, &|TokTree { range, tok }| { let tok = match tok { - tok @ (Token::Atom(_) | Token::BR | Token::Bottom(_) | Token::Comment(_) | Token::NS) => tok, - tok @ (Token::Name(_) | Token::Slot(_) | Token::X(_) | Token::Ph(_) | Token::Macro(_)) => tok, - tok @ Token::Reference(_) => tok, + tok @ (Token::BR | Token::Bottom(_) | Token::Comment(_) | Token::Name(_)) => tok, + tok @ (Token::Handle(_) | Token::NewExpr(_)) => tok, + Token::NS(n, b) => Token::NS(n, Box::new(recur(*b, f))), Token::LambdaHead(arg) => Token::LambdaHead(arg.into_iter().map(|tt| recur(tt, f)).collect_vec()), Token::S(p, b) => Token::S(p, b.into_iter().map(|tt| recur(tt, f)).collect_vec()), @@ -70,68 +93,48 @@ impl Display for TokHandle<'_> { } #[derive(Clone, Debug)] -pub struct TokTree<'a, A: AtomRepr, X: ExtraTok> { - pub tok: Token<'a, A, X>, +pub struct TokTree { + pub tok: Token, pub range: Range, } -impl<'b, A: AtomRepr, X: ExtraTok> TokTree<'b, A, X> { - pub async fn from_api(tt: &api::TokenTree, ctx: &mut A::Ctx, i: &Interner) -> Self { - let tok = match_mapping!(&tt.token, api::Token => Token::<'b, A, X> { - BR, NS, - Atom(a => A::from_api(a, Pos::Range(tt.range.clone()), ctx).await), +impl TokTree { + pub async fn from_api( + tt: &api::TokenTree, + hctx: &mut H::FromApiCtx<'_>, + xctx: &mut X::FromApiCtx<'_>, + i: &Interner, + ) -> Self { + let tok = match_mapping!(&tt.token, api::Token => Token:: { + BR, + NS(n => Tok::from_api(*n, i).await, + b => Box::new(Self::from_api(b, hctx, xctx, i).boxed_local().await)), Bottom(e => OrcErrv::from_api(e, i).await), - LambdaHead(arg => ttv_from_api(arg, ctx, i).await), + LambdaHead(arg => ttv_from_api(arg, hctx, xctx, i).await), Name(n => Tok::from_api(*n, i).await), - S(*par, b => ttv_from_api(b, ctx, i).await), + S(*par, b => ttv_from_api(b, hctx, xctx, i).await), Comment(c.clone()), - Slot(id => TokHandle::new(*id)), - Ph(ph => Ph::from_api(ph, i).await), - Macro(*prio), - Reference(tok => Sym::from_api(*tok, i).await) + NewExpr(expr => X::from_api(expr, xctx, Pos::Range(tt.range.clone()), i).await), + Handle(tk => H::from_api(tk, hctx, Pos::Range(tt.range.clone()), i).await) }); Self { range: tt.range.clone(), tok } } - pub async fn to_api(&self, do_extra: &mut impl RefDoExtra) -> api::TokenTree { - let token = match_mapping!(&self.tok, Token => api::Token { - Atom(a.to_api().await), - BR, - NS, - Bottom(e.to_api()), - Comment(c.clone()), - LambdaHead(arg => ttv_to_api(arg, do_extra).boxed_local().await), - Name(n.to_api()), - Slot(tt.ticket()), - S(*p, b => ttv_to_api(b, do_extra).boxed_local().await), - Ph(ph.to_api()), - Macro(*prio), - Reference(sym.to_api()), - } { - Token::X(x) => return do_extra(x, self.range.clone()).await - }); - api::TokenTree { range: self.range.clone(), token } - } - pub async fn into_api( self, - do_extra: &mut impl FnMut(X, Range) -> api::TokenTree, + hctx: &mut H::ToApiCtx<'_>, + xctx: &mut X::ToApiCtx<'_>, ) -> api::TokenTree { - let token = match self.tok { - Token::Atom(a) => api::Token::Atom(a.to_api().await), - Token::Reference(sym) => api::Token::Reference(sym.to_api()), - Token::BR => api::Token::BR, - Token::NS => api::Token::NS, - Token::Bottom(e) => api::Token::Bottom(e.to_api()), - Token::Comment(c) => api::Token::Comment(c.clone()), - Token::LambdaHead(arg) => api::Token::LambdaHead(ttv_into_api(arg, do_extra).await), - Token::Name(n) => api::Token::Name(n.to_api()), - Token::Slot(tt) => api::Token::Slot(tt.ticket()), - Token::S(p, b) => api::Token::S(p, ttv_into_api(b, do_extra).await), - Token::Ph(Ph { kind, name }) => - api::Token::Ph(api::Placeholder { name: name.to_api(), kind }), - Token::X(x) => return do_extra(x, self.range.clone()), - Token::Macro(prio) => api::Token::Macro(prio), - }; + let token = match_mapping!(self.tok, Token => api::Token { + BR, + NS(n.to_api(), b => Box::new(b.into_api(hctx, xctx).boxed_local().await)), + Bottom(e.to_api()), + Comment(c.clone()), + LambdaHead(arg => ttv_into_api(arg, hctx, xctx).await), + Name(nn.to_api()), + S(p, b => ttv_into_api(b, hctx, xctx).await), + Handle(hand.into_api(hctx).await), + NewExpr(expr.into_api(xctx).await), + }); api::TokenTree { range: self.range.clone(), token } } @@ -139,9 +142,16 @@ impl<'b, A: AtomRepr, X: ExtraTok> TokTree<'b, A, X> { pub fn as_name(&self) -> Option> { if let Token::Name(n) = &self.tok { Some(n.clone()) } else { None } } - pub fn as_s<'a>(&'a self, par: Paren, i: &'a Interner) -> Option> { - self.tok.as_s(par).map(|slc| Snippet::new(self, slc, i)) + pub fn as_s(&self, par: Paren) -> Option> { + self.tok.as_s(par).map(|slc| Snippet::new(self, slc)) } + pub fn as_lambda(&self) -> Option> { + match &self.tok { + Token::LambdaHead(arg) => Some(Snippet::new(self, arg)), + _ => None, + } + } + pub fn is_fluff(&self) -> bool { matches!(self.tok, Token::Comment(_) | Token::BR) } pub fn lambda(arg: Vec, mut body: Vec) -> Self { let arg_range = ttv_range(&arg); let s_range = arg_range.start..body.last().expect("Lambda with empty body!").range.end; @@ -149,53 +159,44 @@ impl<'b, A: AtomRepr, X: ExtraTok> TokTree<'b, A, X> { Token::S(Paren::Round, body).at(s_range) } } -impl Format for TokTree<'_, A, X> { +impl Format for TokTree { async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { self.tok.print(c).await } } -pub async fn ttv_from_api( +pub async fn ttv_from_api( tokv: impl IntoIterator>, - ctx: &mut A::Ctx, + hctx: &mut H::FromApiCtx<'_>, + xctx: &mut X::FromApiCtx<'_>, i: &Interner, -) -> Vec> { +) -> Vec> { stream! { for tok in tokv { - yield TokTree::::from_api(tok.borrow(), ctx, i).boxed_local().await + yield TokTree::::from_api(tok.borrow(), hctx, xctx, i).boxed_local().await } } .collect() .await } -pub async fn ttv_to_api<'a, A: AtomRepr, X: ExtraTok>( - tokv: impl IntoIterator>>, - do_extra: &mut impl RefDoExtra, -) -> Vec { - let mut output = Vec::new(); - for tok in tokv { - output.push(Borrow::>::borrow(&tok).to_api(do_extra).await) - } - output -} - -pub async fn ttv_into_api<'a, A: AtomRepr, X: ExtraTok>( - tokv: impl IntoIterator>, - do_extra: &mut impl FnMut(X, Range) -> api::TokenTree, +pub async fn ttv_into_api( + tokv: impl IntoIterator>, + hctx: &mut H::ToApiCtx<'_>, + xctx: &mut X::ToApiCtx<'_>, ) -> Vec { stream! { for tok in tokv { - yield tok.into_api(do_extra).await + yield tok.into_api(hctx, xctx).await } } .collect() .await } -pub fn wrap_tokv<'a, A: AtomRepr, X: ExtraTok>( - items: impl IntoIterator>, -) -> TokTree<'a, A, X> { +pub fn wrap_tokv( + items: impl IntoIterator>, +) -> TokTree { let items_v = items.into_iter().collect_vec(); match items_v.len() { 0 => panic!("A tokv with no elements is illegal"), @@ -211,70 +212,54 @@ pub use api::Paren; /// Lexer output variant #[derive(Clone, Debug)] -pub enum Token<'a, A: AtomRepr, X: ExtraTok> { +pub enum Token { /// Information about the code addressed to the human reader or dev tooling /// It has no effect on the behaviour of the program unless it's explicitly /// read via reflection Comment(Arc), /// The part of a lambda between `\` and `.` enclosing the argument. The body /// stretches to the end of the enclosing parens or the end of the const line - LambdaHead(Vec>), + LambdaHead(Vec>), /// A binding, operator, or a segment of a namespaced::name Name(Tok), - /// The namespace separator :: - NS, + /// A namespace prefix, like `my_ns::` followed by a token + NS(Tok, Box>), /// A line break BR, /// `()`, `[]`, or `{}` - S(Paren, Vec>), - /// A fully formed reference to external code emitted by a lexer plugin - Reference(Sym), - /// A value emitted by a lexer plugin - Atom(A), + S(Paren, Vec>), + /// A newly instantiated expression + NewExpr(X), + /// An existing expr from a nested lexer + Handle(H), /// A grammar error emitted by a lexer plugin if it was possible to continue /// reading. Parsers should treat it as an atom unless it prevents parsing, /// in which case both this and a relevant error should be returned. Bottom(OrcErrv), - /// An instruction from a plugin for the lexer to embed a subexpression - /// without retransmitting it. It should not appear anywhere outside lexer - /// plugin responses. - Slot(TokHandle<'a>), - /// Additional domain-specific token types - X(X), - /// A placeholder for metaprogramming, either $name, ..$name, ..$name:N, - /// ...$name, or ...$name:N - Ph(Ph), - /// `macro` or `macro(`X`)` where X is any valid floating point number - /// expression. `macro` is not a valid name in Orchid for this reason. - Macro(Option>), } -impl<'a, A: AtomRepr, X: ExtraTok> Token<'a, A, X> { - pub fn at(self, range: Range) -> TokTree<'a, A, X> { TokTree { range, tok: self } } +impl Token { + pub fn at(self, range: Range) -> TokTree { TokTree { range, tok: self } } pub fn is_kw(&self, tk: Tok) -> bool { matches!(self, Token::Name(n) if *n == tk) } - pub fn as_s(&self, par: Paren) -> Option<&[TokTree<'a, A, X>]> { + pub fn as_s(&self, par: Paren) -> Option<&[TokTree]> { match self { Self::S(p, b) if *p == par => Some(b), _ => None, } } } -impl Format for Token<'_, A, X> { +impl Format for Token { async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { match self { - 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(), + tl_cache!(Rc: Rc::new(Variants::default().bounded("\\{0b}."))) + .units([ttv_fmt(arg, c).await]), + Self::NS(n, b) => tl_cache!(Rc: Rc::new(Variants::default().bounded("{0}::{1l}"))) + .units([n.to_string().into(), b.print(c).boxed_local().await]), Self::Name(n) => format!("{n}").into(), - Self::Reference(sym) => format!("{sym}").into(), - Self::Slot(th) => format!("{th}").into(), - Self::Ph(ph) => format!("{ph}").into(), Self::S(p, b) => FmtUnit::new( match *p { Paren::Round => tl_cache!(Rc: Rc::new(Variants::default().bounded("({0b})"))), @@ -283,67 +268,22 @@ impl Format for Token<'_, A, X> { }, [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(), + Self::Handle(h) => h.print(c).await, + Self::NewExpr(ex) => ex.print(c).await, } } } -pub fn ttv_range(ttv: &[TokTree<'_, impl AtomRepr, impl ExtraTok>]) -> Range { +pub fn ttv_range<'a>(ttv: &[TokTree]) -> Range { assert!(!ttv.is_empty(), "Empty slice has no range"); ttv.first().unwrap().range.start..ttv.last().unwrap().range.end } pub async fn ttv_fmt<'a: 'b, 'b>( - ttv: impl IntoIterator>, + ttv: impl IntoIterator>, 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 ") } - -#[derive(Clone, Debug)] -pub struct Ph { - pub name: Tok, - pub kind: PhKind, -} -impl Ph { - pub async fn from_api(api: &api::Placeholder, i: &Interner) -> Self { - Self { name: Tok::from_api(api.name, i).await, kind: api.kind } - } - pub fn to_api(&self) -> api::Placeholder { - 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 { - use super::*; - - #[test] - fn test_covariance() { - fn _f<'a>(x: Token<'static, Never, Never>) -> Token<'a, Never, Never> { x } - } - - #[test] - fn fail_covariance() { - // this fails to compile - // fn _f<'a, 'b>(x: &'a mut &'static ()) -> &'a mut &'b () { x } - // this passes because it's covariant - fn _f<'a, 'b>(x: &'a fn() -> &'static ()) -> &'a fn() -> &'b () { x } - } -} diff --git a/orchid-extension/Cargo.toml b/orchid-extension/Cargo.toml index 1ae60aa..6909dbb 100644 --- a/orchid-extension/Cargo.toml +++ b/orchid-extension/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "orchid-extension" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/orchid-extension/src/atom.rs b/orchid-extension/src/atom.rs index 3062dc0..65f1d0e 100644 --- a/orchid-extension/src/atom.rs +++ b/orchid-extension/src/atom.rs @@ -1,7 +1,6 @@ use std::any::{Any, TypeId, type_name}; use std::fmt; use std::future::Future; -use std::marker::PhantomData; use std::num::NonZeroU32; use std::ops::Deref; use std::pin::Pin; @@ -22,7 +21,6 @@ use orchid_base::interner::Interner; use orchid_base::location::Pos; use orchid_base::name::Sym; use orchid_base::reqnot::Requester; -use orchid_base::tree::AtomRepr; use trait_set::trait_set; use crate::api; @@ -44,12 +42,8 @@ pub trait Atomic: 'static + Sized { type Data: Clone + Coding + Sized + 'static; /// Register handlers for IPC calls. If this atom implements [Supports], you /// should register your implementations here. If this atom doesn't - /// participate in IPC at all, use the below body. - /// ``` - /// MethodSetBuilder::new() - /// ``` - // this method isn't default-implemented to prevent bugs from forgetting to register IPC requests. - fn reg_reqs() -> MethodSetBuilder; + /// participate in IPC at all, the default implementation is fine + fn reg_reqs() -> MethodSetBuilder { MethodSetBuilder::new() } } impl AtomCard for A { type Data = ::Data; @@ -91,57 +85,43 @@ pub fn get_info( } #[derive(Clone)] -pub struct ForeignAtom<'a> { - pub(crate) expr: Option>, - pub(crate) _life: PhantomData<&'a ()>, - pub(crate) ctx: SysCtx, +pub struct ForeignAtom { + pub(crate) expr: Rc, pub(crate) atom: api::Atom, pub(crate) pos: Pos, } -impl ForeignAtom<'_> { - pub fn ex_opt(self) -> Option { - let (handle, pos) = (self.expr.as_ref()?.clone(), self.pos.clone()); - let data = ExprData { pos, kind: ExprKind::Atom(ForeignAtom { _life: PhantomData, ..self }) }; - Some(Expr::new(handle, data)) - } +impl ForeignAtom { pub fn pos(&self) -> Pos { self.pos.clone() } - pub fn ctx(&self) -> SysCtx { self.ctx.clone() } -} -impl ForeignAtom<'static> { - pub fn ex(self) -> Expr { self.ex_opt().unwrap() } + pub fn ctx(&self) -> SysCtx { self.expr.ctx.clone() } + pub fn ex(self) -> Expr { + let (handle, pos) = (self.expr.clone(), self.pos.clone()); + let data = ExprData { pos, kind: ExprKind::Atom(ForeignAtom { ..self }) }; + Expr::new(handle, data) + } pub(crate) fn new(handle: Rc, atom: api::Atom, pos: Pos) -> Self { - ForeignAtom { _life: PhantomData, atom, ctx: handle.ctx.clone(), expr: Some(handle), pos } + ForeignAtom { atom, expr: handle, pos } } pub async fn request(&self, m: M) -> Option { - let rep = (self.ctx.reqnot().request(api::Fwd( + let rep = (self.ctx().reqnot().request(api::Fwd( self.atom.clone(), - Sym::parse(M::NAME, self.ctx.i()).await.unwrap().tok().to_api(), + Sym::parse(M::NAME, self.ctx().i()).await.unwrap().tok().to_api(), enc_vec(&m).await, ))) .await?; Some(M::Response::decode(Pin::new(&mut &rep[..])).await) } } -impl fmt::Display for ForeignAtom<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}::{:?}", if self.expr.is_some() { "Clause" } else { "Tok" }, self.atom) - } +impl fmt::Display for ForeignAtom { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Atom::{:?}", self.atom) } } -impl fmt::Debug for ForeignAtom<'_> { +impl fmt::Debug for ForeignAtom { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ForeignAtom({self})") } } -impl Format for ForeignAtom<'_> { +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) + 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() } -} pub struct NotTypAtom { pub pos: Pos, @@ -235,11 +215,11 @@ impl Default for MethodSetBuilder { } #[derive(Clone)] -pub struct TypAtom<'a, A: AtomicFeatures> { - pub data: ForeignAtom<'a>, +pub struct TypAtom { + pub data: ForeignAtom, pub value: A::Data, } -impl TypAtom<'static, A> { +impl TypAtom { pub async fn downcast(expr: Rc) -> Result { match Expr::from_handle(expr).atom().await { Err(expr) => Err(NotTypAtom { @@ -252,21 +232,19 @@ impl TypAtom<'static, A> { Ok(tatom) => Ok(tatom), Err(fa) => Err(NotTypAtom { pos: fa.pos.clone(), - ctx: fa.ctx.clone(), + ctx: fa.ctx().clone(), expr: fa.ex(), typ: Box::new(A::info()), }), }, } } -} -impl TypAtom<'_, A> { pub async fn request(&self, req: M) -> M::Response where A: Supports { M::Response::decode(Pin::new( - &mut &(self.data.ctx.reqnot().request(api::Fwd( + &mut &(self.data.ctx().reqnot().request(api::Fwd( self.data.atom.clone(), - Sym::parse(M::NAME, self.data.ctx.i()).await.unwrap().tok().to_api(), + Sym::parse(M::NAME, self.data.ctx().i()).await.unwrap().tok().to_api(), enc_vec(&req).await, ))) .await @@ -275,7 +253,7 @@ impl TypAtom<'_, A> { .await } } -impl Deref for TypAtom<'_, A> { +impl Deref for TypAtom { type Target = A::Data; fn deref(&self) -> &Self::Target { &self.value } } @@ -289,8 +267,8 @@ pub trait AtomDynfo: 'static { fn tid(&self) -> TypeId; fn name(&self) -> &'static str; 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 call<'a>(&'a self, ctx: AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr>; + fn call_ref<'a>(&'a self, ctx: AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr>; fn print<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit>; fn handle_req<'a, 'b: 'a, 'c: 'a>( &'a self, @@ -304,12 +282,12 @@ pub trait AtomDynfo: 'static { &'a self, ctx: AtomCtx<'a>, write: Pin<&'b mut dyn Write>, - ) -> LocalBoxFuture<'a, Option>>; + ) -> LocalBoxFuture<'a, Option>>; fn deserialize<'a>( &'a self, ctx: SysCtx, data: &'a [u8], - refs: &'a [api::ExprTicket], + refs: &'a [Expr], ) -> LocalBoxFuture<'a, api::Atom>; fn drop<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, ()>; } @@ -319,9 +297,7 @@ trait_set! { } pub struct AtomFactory(Box); impl AtomFactory { - pub fn new( - f: impl AsyncFnOnce(SysCtx) -> api::Atom + Clone + 'static, - ) -> Self { + pub fn new(f: impl AsyncFnOnce(SysCtx) -> api::Atom + Clone + 'static) -> Self { Self(Box::new(|ctx| f(ctx).boxed_local())) } pub async fn build(self, ctx: SysCtx) -> api::Atom { (self.0)(ctx).await } diff --git a/orchid-extension/src/atom_owned.rs b/orchid-extension/src/atom_owned.rs index ae9dbd3..007445c 100644 --- a/orchid-extension/src/atom_owned.rs +++ b/orchid-extension/src/atom_owned.rs @@ -4,7 +4,6 @@ use std::future::Future; use std::num::NonZero; use std::ops::Deref; use std::pin::Pin; -use std::rc::Rc; use std::sync::atomic::AtomicU64; use async_once_cell::OnceCell; @@ -18,7 +17,7 @@ use never::Never; use orchid_api::AtomId; use orchid_api_traits::{Decode, Encode, enc_vec}; use orchid_base::error::OrcRes; -use orchid_base::format::FmtUnit; +use orchid_base::format::{FmtCtx, FmtCtxImpl, FmtUnit}; use orchid_base::name::Sym; use crate::api; @@ -26,7 +25,7 @@ use crate::atom::{ AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet, MethodSetBuilder, err_not_callable, err_not_command, get_info, }; -use crate::expr::{Expr, ExprHandle}; +use crate::expr::Expr; use crate::gen_expr::{GExpr, bot}; use crate::system::{SysCtx, SysCtxEntry}; use crate::system_ctor::CtedObj; @@ -86,17 +85,15 @@ impl AtomDynfo for OwnedAtomDynfo { Box::new(::Data::decode(Pin::new(&mut &data[..])).await) as Box }) } - fn call(&self, AtomCtx(_, id, ctx): AtomCtx, arg: api::ExprTicket) -> LocalBoxFuture<'_, GExpr> { - Box::pin(async move { take_atom(id.unwrap(), &ctx).await.dyn_call(ctx.clone(), arg).await }) + fn call(&self, AtomCtx(_, id, ctx): AtomCtx, arg: Expr) -> LocalBoxFuture<'_, GExpr> { + Box::pin(async move { take_atom(id.unwrap(), &ctx).await.dyn_call(arg).await }) } fn call_ref<'a>( &'a self, AtomCtx(_, id, ctx): AtomCtx<'a>, - arg: api::ExprTicket, + arg: Expr, ) -> LocalBoxFuture<'a, GExpr> { - Box::pin(async move { - AtomReadGuard::new(id.unwrap(), &ctx).await.dyn_call_ref(ctx.clone(), arg).await - }) + Box::pin(async move { AtomReadGuard::new(id.unwrap(), &ctx).await.dyn_call_ref(arg).await }) } fn print(&self, AtomCtx(_, id, ctx): AtomCtx<'_>) -> LocalBoxFuture<'_, FmtUnit> { Box::pin( @@ -129,24 +126,22 @@ impl AtomDynfo for OwnedAtomDynfo { &'a self, AtomCtx(_, id, ctx): AtomCtx<'a>, mut write: Pin<&'b mut dyn Write>, - ) -> LocalBoxFuture<'a, Option>> { + ) -> LocalBoxFuture<'a, Option>> { Box::pin(async move { let id = id.unwrap(); id.encode(write.as_mut()).await; - let refs = AtomReadGuard::new(id, &ctx).await.dyn_serialize(ctx.clone(), write).await; - refs.map(|v| v.into_iter().map(|t| t.handle().tk).collect_vec()) + AtomReadGuard::new(id, &ctx).await.dyn_serialize(ctx.clone(), write).await }) } fn deserialize<'a>( &'a self, ctx: SysCtx, data: &'a [u8], - refs: &'a [api::ExprTicket], + refs: &'a [Expr], ) -> LocalBoxFuture<'a, api::Atom> { Box::pin(async move { - let refs = - refs.iter().map(|tk| Expr::from_handle(Rc::new(ExprHandle::from_args(ctx.clone(), *tk)))); - let obj = T::deserialize(DeserCtxImpl(data, &ctx), T::Refs::from_iter(refs)).await; + let refs = T::Refs::from_iter(refs.iter().cloned()); + let obj = T::deserialize(DeserCtxImpl(data, &ctx), refs).await; obj._factory().build(ctx).await }) } @@ -220,12 +215,12 @@ pub trait OwnedAtom: Atomic + Any + Clone + 'static { type Refs: RefSet; fn val(&self) -> impl Future>; #[allow(unused_variables)] - fn call_ref(&self, arg: ExprHandle) -> impl Future { - async move { bot([err_not_callable(arg.ctx.i()).await]) } + fn call_ref(&self, arg: Expr) -> impl Future { + async move { bot([err_not_callable(arg.ctx().i()).await]) } } - fn call(self, arg: ExprHandle) -> impl Future { + fn call(self, arg: Expr) -> impl Future { async { - let ctx = arg.get_ctx(); + let ctx = arg.ctx(); let gcl = self.call_ref(arg).await; self.free(ctx).await; gcl @@ -238,7 +233,7 @@ 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 { + fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future { async { format!("OwnedAtom({})", type_name::()).into() } } #[allow(unused_variables)] @@ -268,9 +263,8 @@ pub trait DynOwnedAtom: 'static { fn atom_tid(&self) -> TypeId; fn as_any_ref(&self) -> &dyn Any; fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn Write>) -> LocalBoxFuture<'a, ()>; - fn dyn_call_ref(&self, ctx: SysCtx, arg: api::ExprTicket) -> LocalBoxFuture<'_, GExpr>; - fn dyn_call(self: Box, ctx: SysCtx, arg: api::ExprTicket) - -> LocalBoxFuture<'static, GExpr>; + fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr>; + fn dyn_call(self: Box, arg: Expr) -> 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<'_, FmtUnit>; @@ -286,15 +280,11 @@ impl DynOwnedAtom for T { fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn Write>) -> LocalBoxFuture<'a, ()> { async { self.val().await.as_ref().encode(buffer).await }.boxed_local() } - fn dyn_call_ref(&self, ctx: SysCtx, arg: api::ExprTicket) -> LocalBoxFuture<'_, GExpr> { - self.call_ref(ExprHandle::from_args(ctx, arg)).boxed_local() + fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr> { + self.call_ref(arg).boxed_local() } - fn dyn_call( - self: Box, - ctx: SysCtx, - arg: api::ExprTicket, - ) -> LocalBoxFuture<'static, GExpr> { - self.call(ExprHandle::from_args(ctx, arg)).boxed_local() + fn dyn_call(self: Box, arg: Expr) -> LocalBoxFuture<'static, GExpr> { + self.call(arg).boxed_local() } fn dyn_command(self: Box, ctx: SysCtx) -> LocalBoxFuture<'static, OrcRes>> { self.command(ctx).boxed_local() @@ -302,7 +292,9 @@ 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<'_, FmtUnit> { self.print(ctx).boxed_local() } + fn dyn_print(&self, ctx: SysCtx) -> LocalBoxFuture<'_, FmtUnit> { + async move { self.print(&FmtCtxImpl { i: ctx.i() }).await }.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 f68bb4e..33ab047 100644 --- a/orchid-extension/src/atom_thin.rs +++ b/orchid-extension/src/atom_thin.rs @@ -16,7 +16,7 @@ use crate::atom::{ AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet, MethodSetBuilder, err_not_callable, err_not_command, get_info, }; -use crate::expr::ExprHandle; +use crate::expr::Expr; use crate::gen_expr::{GExpr, bot}; use crate::system::SysCtx; use crate::system_ctor::CtedObj; @@ -49,23 +49,11 @@ impl AtomDynfo for ThinAtomDynfo { fn decode<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>) -> LocalBoxFuture<'a, Box> { Box::pin(async { Box::new(T::decode(Pin::new(&mut &buf[..])).await) as Box }) } - fn call<'a>( - &'a self, - AtomCtx(buf, _, ctx): AtomCtx<'a>, - arg: api::ExprTicket, - ) -> LocalBoxFuture<'a, GExpr> { - Box::pin(async move { - T::decode(Pin::new(&mut &buf[..])).await.call(ExprHandle::from_args(ctx, arg)).await - }) + fn call<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> { + Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.call(arg).await }) } - fn call_ref<'a>( - &'a self, - AtomCtx(buf, _, ctx): AtomCtx<'a>, - arg: api::ExprTicket, - ) -> LocalBoxFuture<'a, GExpr> { - Box::pin(async move { - T::decode(Pin::new(&mut &buf[..])).await.call(ExprHandle::from_args(ctx, arg)).await - }) + fn call_ref<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> { + Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.call(arg).await }) } fn handle_req<'a, 'm1: 'a, 'm2: 'a>( &'a self, @@ -89,7 +77,7 @@ impl AtomDynfo for ThinAtomDynfo { &'a self, ctx: AtomCtx<'a>, write: Pin<&'b mut dyn Write>, - ) -> LocalBoxFuture<'a, Option>> { + ) -> LocalBoxFuture<'a, Option>> { Box::pin(async { T::decode(Pin::new(&mut &ctx.0[..])).await.encode(write).await; Some(Vec::new()) @@ -99,7 +87,7 @@ impl AtomDynfo for ThinAtomDynfo { &'a self, ctx: SysCtx, data: &'a [u8], - refs: &'a [api::ExprTicket], + refs: &'a [Expr], ) -> LocalBoxFuture<'a, api::Atom> { assert!(refs.is_empty(), "Refs found when deserializing thin atom"); Box::pin(async { T::decode(Pin::new(&mut &data[..])).await._factory().build(ctx).await }) @@ -116,8 +104,8 @@ pub trait ThinAtom: AtomCard + Atomic + Coding + Send + Sync + 'static { #[allow(unused_variables)] - fn call(&self, arg: ExprHandle) -> impl Future { - async move { bot([err_not_callable(arg.ctx.i()).await]) } + fn call(&self, arg: Expr) -> impl Future { + async move { bot([err_not_callable(arg.ctx().i()).await]) } } #[allow(unused_variables)] fn command(&self, ctx: SysCtx) -> impl Future>> { diff --git a/orchid-extension/src/conv.rs b/orchid-extension/src/conv.rs index 0130e7b..7f9f48f 100644 --- a/orchid-extension/src/conv.rs +++ b/orchid-extension/src/conv.rs @@ -31,7 +31,7 @@ async fn err_type(pos: Pos, i: &Interner) -> OrcErr { mk_err(i.i("Type error").await, "The atom is a different type than expected", [pos.into()]) } -impl TryFromExpr for TypAtom<'_, A> { +impl TryFromExpr for TypAtom { async fn try_from_expr(expr: Expr) -> OrcRes { match expr.atom().await { Err(ex) => Err(err_not_atom(ex.data().await.pos.clone(), ex.ctx().i()).await.into()), @@ -51,7 +51,7 @@ impl ToExpr for GExpr { fn to_expr(self) -> GExpr { self } } impl ToExpr for Expr { - fn to_expr(self) -> GExpr { self.gen() } + fn to_expr(self) -> GExpr { self.slot() } } impl ToExpr for OrcRes { diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index 116d55e..6119e48 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -12,30 +12,29 @@ use futures::future::{LocalBoxFuture, join_all}; use futures::{FutureExt, StreamExt, stream_select}; use hashbrown::HashMap; use itertools::Itertools; -use orchid_api::{ApplyMacro, ExtMsgSet}; -use orchid_api_traits::{Decode, enc_vec}; +use orchid_api::{ExtMsgSet, IntReq}; +use orchid_api_traits::{Decode, UnderRoot, enc_vec}; use orchid_base::builtin::{ExtInit, ExtPort, Spawner}; use orchid_base::char_filter::{char_filter_match, char_filter_union, mk_char_filter}; use orchid_base::clone; use orchid_base::interner::{Interner, Tok}; use orchid_base::logging::Logger; -use orchid_base::macros::{mtreev_from_api, mtreev_to_api}; use orchid_base::name::Sym; use orchid_base::parse::{Comment, Snippet}; use orchid_base::reqnot::{ReqNot, RequestHandle, Requester}; -use orchid_base::tree::{ttv_from_api, ttv_to_api}; +use orchid_base::tree::{TokenVariant, ttv_from_api, ttv_into_api}; use substack::Substack; use trait_set::trait_set; use crate::api; use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId}; use crate::atom_owned::take_atom; +use crate::expr::{Expr, ExprHandle}; use crate::fs::VirtFS; use crate::lexer::{LexContext, err_cascade, err_not_applicable}; -use crate::macros::{Rule, RuleCtx}; use crate::system::{SysCtx, atom_by_idx}; use crate::system_ctor::{CtedObj, DynSystemCtor}; -use crate::tree::{GenTok, GenTokTree, LazyMemberFactory, TreeIntoApiCtxImpl, do_extra}; +use crate::tree::{GenItemKind, GenTok, GenTokTree, LazyMemberFactory, TreeIntoApiCtxImpl}; pub type ExtReq<'a> = RequestHandle<'a, api::ExtMsgSet>; pub type ExtReqNot = ReqNot; @@ -60,7 +59,6 @@ pub struct SystemRecord { vfses: HashMap, declfs: api::EagerVfs, lazy_members: HashMap, - rules: HashMap>, ctx: SysCtx, } @@ -197,16 +195,17 @@ pub fn extension_init( char_filter_union(&cf, &mk_char_filter(lx.char_filter().iter().cloned())) }); let lazy_mems = Mutex::new(HashMap::new()); - let rules = Mutex::new(HashMap::new()); let ctx = init_ctx(new_sys.id, cted.clone(), hand.reqnot()).await; let const_root = stream::from_iter(cted.inst().dyn_env()) - .then(|(k, v)| { - let (req, lazy_mems, rules) = (&hand, &lazy_mems, &rules); + .filter_map( + async |i| if let GenItemKind::Member(m) = i.kind { Some(m) } else { None }, + ) + .then(|mem| { + let (req, lazy_mems) = (&hand, &lazy_mems); clone!(i, ctx; async move { - let name = i.i(&k).await.to_api(); - let value = v.into_api(&mut TreeIntoApiCtxImpl { + let name = i.i(&mem.name).await.to_api(); + let value = mem.kind.into_api(&mut TreeIntoApiCtxImpl { lazy_members: &mut *lazy_mems.lock().await, - rules: &mut *rules.lock().await, sys: ctx, basepath: &[], path: Substack::Bottom, @@ -219,24 +218,23 @@ pub fn extension_init( .collect() .await; let declfs = cted.inst().dyn_vfs().to_api_rec(&mut vfses, &i).await; - let record = SystemRecord { - declfs, - vfses, - ctx, - lazy_members: lazy_mems.into_inner(), - rules: rules.into_inner(), - }; + let record = + SystemRecord { declfs, vfses, ctx, lazy_members: lazy_mems.into_inner() }; let systems = systems_weak.upgrade().expect("System constructed during shutdown"); systems.lock().await.insert(new_sys.id, record); hand - .handle(&new_sys, &api::SystemInst { lex_filter, const_root, line_types: vec![] }) + .handle(&new_sys, &api::NewSystemResponse { + lex_filter, + const_root, + line_types: vec![], + }) .await }, api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) => { let sys_ctx = get_ctx(sys_id).await; let systems = systems_weak.upgrade().expect("Member queried during shutdown"); let mut systems_g = systems.lock().await; - let SystemRecord { lazy_members, rules, .. } = + let SystemRecord { lazy_members, .. } = systems_g.get_mut(&sys_id).expect("System not found"); let (path, cb) = match lazy_members.insert(tree_id, MemberRecord::Res) { None => panic!("Tree for ID not found"), @@ -249,7 +247,6 @@ pub fn extension_init( path: Substack::Bottom, basepath: &path, lazy_members, - rules, req: &hand, }; hand.handle(&get_tree, &tree.into_api(&mut tia_ctx).await).await @@ -277,7 +274,7 @@ pub fn extension_init( api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, text, pos, id }) => { let sys_ctx = get_ctx(sys).await; let text = Tok::from_api(text, &i).await; - let ctx = LexContext { sys, id, pos, reqnot: hand.reqnot(), text: &text, i: &i }; + let ctx = LexContext { id, pos, text: &text, ctx: sys_ctx.clone() }; let trigger_char = text.chars().nth(pos as usize).unwrap(); let err_na = err_not_applicable(&i).await; let err_cascade = err_cascade(&i).await; @@ -290,8 +287,7 @@ pub fn extension_init( return hand.handle(&lex, &eopt).await; }, Ok((s, expr)) => { - let expr = - expr.to_api(&mut async |f, r| do_extra(f, r, sys_ctx.clone()).await).await; + let expr = expr.into_api(&mut (), &mut (sys_ctx, &hand)).await; let pos = (text.len() - s.len()) as u32; return hand.handle(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await; }, @@ -301,25 +297,21 @@ pub fn extension_init( hand.handle(&lex, &None).await }, api::HostExtReq::ParseLine(pline) => { - let api::ParseLine { exported, comments, sys, line } = &pline; + let api::ParseLine { module, exported, comments, sys, line } = &pline; let mut ctx = get_ctx(*sys).await; let parsers = ctx.cted().inst().dyn_parsers(); let comments = join_all(comments.iter().map(|c| Comment::from_api(c, &i))).await; - let line: Vec = ttv_from_api(line, &mut ctx, &i).await; - let snip = Snippet::new(line.first().expect("Empty line"), &line, &i); + let line: Vec = ttv_from_api(line, &mut ctx, &mut (), &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 parser = parsers.iter().find(|p| p.line_head() == **name).expect("No parser candidate"); - let o_line = match parser.parse(*exported, comments, tail) { + let module = Sym::from_api(*module, ctx.i()).await; + let o_line = match parser.parse(ctx.clone(), module, *exported, comments, tail).await + { Err(e) => Err(e.to_api()), - Ok(t) => Ok( - ttv_to_api(t, &mut async move |f, range| api::TokenTree { - range, - token: api::Token::Atom(f.clone().build(ctx.clone()).await), - }) - .await, - ), + Ok(t) => Ok(ttv_into_api(t, &mut (), &mut (ctx.clone(), &hand)).await), }; hand.handle(&pline, &o_line).await }, @@ -331,8 +323,15 @@ pub fn extension_init( match &atom_req { api::AtomReq::SerializeAtom(ser) => { let mut buf = enc_vec(&id).await; - let refs_opt = nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await; - hand.handle(ser, &refs_opt.map(|refs| (buf, refs))).await + match nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await { + None => hand.handle(ser, &None).await, + Some(refs) => { + let refs = + join_all(refs.into_iter().map(|ex| async { ex.into_api(&mut ()).await })) + .await; + hand.handle(ser, &Some((buf, refs))).await + }, + } }, api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) => hand.handle(print, &nfo.print(actx).await.to_api()).await, @@ -351,11 +350,15 @@ pub fn extension_init( hand.handle(fwded, &some.then_some(reply)).await }, api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => { - let ret = nfo.call_ref(actx, *arg).await; + // SAFETY: function calls own their argument implicitly + let expr_handle = unsafe { ExprHandle::from_args(ctx.clone(), *arg) }; + let ret = nfo.call_ref(actx, Expr::from_handle(Rc::new(expr_handle))).await; hand.handle(call, &ret.api_return(ctx.clone(), &hand).await).await }, api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => { - let ret = nfo.call(actx, *arg).await; + // SAFETY: function calls own their argument implicitly + let expr_handle = unsafe { ExprHandle::from_args(ctx.clone(), *arg) }; + let ret = nfo.call(actx, Expr::from_handle(Rc::new(expr_handle))).await; hand.handle(call, &ret.api_return(ctx.clone(), &hand).await).await }, api::AtomReq::Command(cmd @ api::Command(_)) => match nfo.command(actx).await { @@ -376,41 +379,15 @@ pub fn extension_init( let api::DeserAtom(sys, buf, refs) = &deser; let mut read = &mut &buf[..]; let ctx = get_ctx(*sys).await; + // SAFETY: deserialization implicitly grants ownership to previously owned exprs + let refs = (refs.iter()) + .map(|tk| unsafe { ExprHandle::from_args(ctx.clone(), *tk) }) + .map(|handle| Expr::from_handle(Rc::new(handle))) + .collect_vec(); let id = AtomTypeId::decode(Pin::new(&mut read)).await; let inst = ctx.cted().inst(); let nfo = atom_by_idx(inst.card(), id).expect("Deserializing atom with invalid ID"); - hand.handle(&deser, &nfo.deserialize(ctx.clone(), read, refs).await).await - }, - orchid_api::HostExtReq::ApplyMacro(am) => { - let tok = hand.will_handle_as(&am); - let ApplyMacro { id, params, run_id, sys } = am; - let sys_ctx = get_ctx(sys).await; - let mut ctx = - RuleCtx { args: ahash::HashMap::default(), run_id, sys: sys_ctx.clone() }; - for (k, v) in params { - ctx.args.insert( - Tok::from_api(k, &i).await, - mtreev_from_api(&v, &i, &mut async |_| panic!("No atom in macro prompt!")).await, - ); - } - let err_cascade = err_cascade(&i).await; - let systems = systems_weak.upgrade().expect("macro call during shutdown"); - let systems_g = systems.lock().await; - let rule = &systems_g[&sys].rules[&id]; - match (rule.apply)(ctx).await { - Err(e) => { - let new_errors = e.keep_only(|e| *e != err_cascade); - hand.handle_as(tok, &new_errors.map(|e| Err(e.to_api()))).await - }, - Ok(t) => { - clone!(sys_ctx); - let result = mtreev_to_api(&t, &mut async |a| { - api::MacroToken::Atom(a.clone().build(sys_ctx.clone()).await) - }) - .await; - hand.handle_as(tok, &Some(Ok(result))).await - }, - } + hand.handle(&deser, &nfo.deserialize(ctx.clone(), read, &refs).await).await }, } } @@ -418,7 +395,8 @@ pub fn extension_init( } }, ); - *interner_cell.borrow_mut() = Some(Interner::new_replica(rn.clone().map())); + *interner_cell.borrow_mut() = + Some(Interner::new_replica(rn.clone().map(|ir: IntReq| ir.into_root()))); spawner(Box::pin(clone!(spawner; async move { let mut streams = stream_select! { in_recv.map(Some), exit_recv.map(|_| None) }; while let Some(item) = streams.next().await { diff --git a/orchid-extension/src/expr.rs b/orchid-extension/src/expr.rs index 18c1f88..b9dd56b 100644 --- a/orchid-extension/src/expr.rs +++ b/orchid-extension/src/expr.rs @@ -20,12 +20,20 @@ pub struct ExprHandle { pub ctx: SysCtx, } impl ExprHandle { - pub(crate) fn from_args(ctx: SysCtx, tk: api::ExprTicket) -> Self { Self { ctx, tk } } + /// # Safety + /// + /// This function does not signal to take ownership of the expr. It must only + /// be called on tickets that are already implicitly owned. + pub unsafe fn from_args(ctx: SysCtx, tk: api::ExprTicket) -> Self { Self { ctx, tk } } pub fn get_ctx(&self) -> SysCtx { self.ctx.clone() } pub async fn clone(&self) -> Self { self.ctx.reqnot().notify(api::Acquire(self.ctx.sys_id(), self.tk)).await; Self { ctx: self.ctx.clone(), tk: self.tk } } + /// Drop the handle and get the ticket without a release notification. + /// Use this with messages that imply ownership transfer. This function is + /// safe because abusing it is a memory leak. + pub fn into_tk(self) -> api::ExprTicket { self.destructure().0 } } impl fmt::Debug for ExprHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -66,7 +74,7 @@ impl Expr { })) .await } - pub async fn atom(self) -> Result, Self> { + pub async fn atom(self) -> Result { match self.data().await { ExprData { kind: ExprKind::Atom(atom), .. } => Ok(atom.clone()), _ => Err(self), @@ -75,7 +83,9 @@ impl Expr { pub fn handle(&self) -> Rc { self.handle.clone() } pub fn ctx(&self) -> SysCtx { self.handle.ctx.clone() } - pub fn gen(&self) -> GExpr { GExpr { pos: Pos::SlotTarget, kind: GExprKind::Slot(self.clone()) } } + pub fn slot(&self) -> GExpr { + GExpr { pos: Pos::SlotTarget, kind: GExprKind::Slot(self.clone()) } + } } impl Format for Expr { async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { @@ -96,7 +106,7 @@ pub struct ExprData { #[derive(Clone, Debug)] pub enum ExprKind { - Atom(ForeignAtom<'static>), + Atom(ForeignAtom), Bottom(OrcErrv), Opaque, } diff --git a/orchid-extension/src/func_atom.rs b/orchid-extension/src/func_atom.rs index 6a8d885..32a69ed 100644 --- a/orchid-extension/src/func_atom.rs +++ b/orchid-extension/src/func_atom.rs @@ -13,13 +13,14 @@ use never::Never; use orchid_api_traits::Encode; use orchid_base::clone; use orchid_base::error::OrcRes; +use orchid_base::format::{FmtCtx, FmtUnit}; use orchid_base::name::Sym; use trait_set::trait_set; use crate::atom::{Atomic, MethodSetBuilder}; use crate::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; use crate::conv::ToExpr; -use crate::expr::{Expr, ExprHandle}; +use crate::expr::Expr; use crate::gen_expr::GExpr; use crate::system::{SysCtx, SysCtxEntry}; @@ -66,14 +67,13 @@ impl Fun { impl Atomic for Fun { type Data = (); type Variant = OwnedVariant; - fn reg_reqs() -> MethodSetBuilder { MethodSetBuilder::new() } } impl OwnedAtom for Fun { type Refs = Vec; async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn call_ref(&self, arg: ExprHandle) -> GExpr { + async fn call_ref(&self, arg: Expr) -> GExpr { std::io::Write::flush(&mut std::io::stderr()).unwrap(); - let new_args = self.args.iter().cloned().chain([Expr::from_handle(Rc::new(arg))]).collect_vec(); + let new_args = self.args.iter().cloned().chain([arg]).collect_vec(); if new_args.len() == self.arity.into() { (self.fun)(new_args).await.to_expr() } else { @@ -81,7 +81,7 @@ impl OwnedAtom for Fun { .to_expr() } } - async fn call(self, arg: ExprHandle) -> GExpr { self.call_ref(arg).await } + async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await } async fn serialize(&self, _: SysCtx, write: Pin<&mut (impl Write + ?Sized)>) -> Self::Refs { self.path.to_api().encode(write).await; self.args.clone() @@ -92,7 +92,7 @@ impl OwnedAtom for Fun { let (arity, fun) = sys.get_or_default::().0.lock().await.get(&path).unwrap().clone(); Self { args, arity, path, fun } } - async fn print(&self, _: SysCtx) -> orchid_base::format::FmtUnit { + async fn print<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { format!("{}:{}/{}", self.path, self.args.len(), self.arity).into() } } @@ -116,20 +116,19 @@ impl Lambda { impl Atomic for Lambda { type Data = (); type Variant = OwnedVariant; - fn reg_reqs() -> MethodSetBuilder { MethodSetBuilder::new() } } impl OwnedAtom for Lambda { type Refs = Never; async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn call_ref(&self, arg: ExprHandle) -> GExpr { - let new_args = self.args.iter().cloned().chain([Expr::from_handle(Rc::new(arg))]).collect_vec(); + async fn call_ref(&self, arg: Expr) -> GExpr { + let new_args = self.args.iter().cloned().chain([arg]).collect_vec(); if new_args.len() == self.arity.into() { (self.fun)(new_args).await.to_expr() } else { Self { args: new_args, arity: self.arity, fun: self.fun.clone() }.to_expr() } } - async fn call(self, arg: ExprHandle) -> GExpr { self.call_ref(arg).await } + async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await } } mod expr_func_derives { diff --git a/orchid-extension/src/gen_expr.rs b/orchid-extension/src/gen_expr.rs index c4c14b0..9665131 100644 --- a/orchid-extension/src/gen_expr.rs +++ b/orchid-extension/src/gen_expr.rs @@ -1,11 +1,12 @@ -use std::future::Future; +use std::rc::Rc; use futures::FutureExt; use orchid_base::error::{OrcErr, 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::reqnot::ReqHandlish; +use orchid_base::{match_mapping, tl_cache}; use crate::api; use crate::atom::{AtomFactory, ToAtom}; @@ -14,6 +15,7 @@ use crate::expr::Expr; use crate::func_atom::Lambda; use crate::system::SysCtx; +#[derive(Clone, Debug)] pub struct GExpr { pub kind: GExprKind, pub pos: Pos, @@ -34,7 +36,13 @@ impl GExpr { } } } +impl Format for GExpr { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + self.kind.print(c).await + } +} +#[derive(Clone, Debug)] pub enum GExprKind { Call(Box, Box), Lambda(u64, Box), @@ -67,6 +75,28 @@ impl GExprKind { }) } } +impl Format for GExprKind { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + match self { + GExprKind::Call(f, x) => + tl_cache!(Rc: Rc::new(Variants::default().bounded("{0} ({1})"))) + .units([f.print(c).await, x.print(c).await]), + GExprKind::Lambda(arg, body) => + tl_cache!(Rc: Rc::new(Variants::default().bounded("\\{0}.{1}"))) + .units([arg.to_string().into(), body.print(c).await]), + GExprKind::Arg(arg) => arg.to_string().into(), + GExprKind::Seq(a, b) => + tl_cache!(Rc: Rc::new(Variants::default().bounded("[{0}] {1}"))) + .units([a.print(c).await, b.print(c).await]), + GExprKind::Const(sym) => sym.to_string().into(), + GExprKind::NewAtom(atom_factory) => atom_factory.to_string().into(), + GExprKind::Slot(expr) => + tl_cache!(Rc: Rc::new(Variants::default().bounded("{{{0}}}"))) + .units([expr.print(c).await]), + GExprKind::Bottom(orc_errv) => orc_errv.to_string().into(), + } + } +} fn inherit(kind: GExprKind) -> GExpr { GExpr { pos: Pos::Inherit, kind } } diff --git a/orchid-extension/src/lexer.rs b/orchid-extension/src/lexer.rs index 82a327d..3b57967 100644 --- a/orchid-extension/src/lexer.rs +++ b/orchid-extension/src/lexer.rs @@ -6,11 +6,11 @@ use futures::future::LocalBoxFuture; use orchid_base::error::{OrcErr, OrcRes, mk_err}; use orchid_base::interner::{Interner, Tok}; use orchid_base::location::Pos; -use orchid_base::reqnot::{ReqNot, Requester}; -use orchid_base::tree::TokHandle; +use orchid_base::reqnot::Requester; use crate::api; -use crate::tree::{GenTok, GenTokTree}; +use crate::system::SysCtx; +use crate::tree::GenTokTree; pub async fn err_cascade(i: &Interner) -> OrcErr { mk_err( @@ -30,20 +30,19 @@ pub async fn err_not_applicable(i: &Interner) -> OrcErr { } pub struct LexContext<'a> { + pub ctx: SysCtx, pub text: &'a Tok, - pub sys: api::SysId, pub id: api::ParsId, pub pos: u32, - pub reqnot: ReqNot, - pub i: &'a Interner, } impl<'a> LexContext<'a> { - pub async fn recurse(&self, tail: &'a str) -> OrcRes<(&'a str, GenTokTree<'a>)> { + pub async fn recurse(&self, tail: &'a str) -> OrcRes<(&'a str, GenTokTree)> { let start = self.pos(tail); - let Some(lx) = self.reqnot.request(api::SubLex { pos: start, id: self.id }).await else { - return Err(err_cascade(self.i).await.into()); + let Some(lx) = self.ctx.reqnot().request(api::SubLex { pos: start, id: self.id }).await else { + return Err(err_cascade(self.ctx.i()).await.into()); }; - Ok((&self.text[lx.pos as usize..], GenTok::Slot(TokHandle::new(lx.ticket)).at(start..lx.pos))) + let tree = GenTokTree::from_api(&lx.tree, &mut self.ctx.clone(), &mut (), self.ctx.i()).await; + Ok((&self.text[lx.pos as usize..], tree)) } pub fn pos(&self, tail: &'a str) -> u32 { (self.text.len() - tail.len()) as u32 } @@ -58,7 +57,7 @@ pub trait Lexer: Send + Sync + Sized + Default + 'static { fn lex<'a>( tail: &'a str, ctx: &'a LexContext<'a>, - ) -> impl Future)>>; + ) -> impl Future>; } pub trait DynLexer: Send + Sync + 'static { @@ -67,7 +66,7 @@ pub trait DynLexer: Send + Sync + 'static { &self, tail: &'a str, ctx: &'a LexContext<'a>, - ) -> LocalBoxFuture<'a, OrcRes<(&'a str, GenTokTree<'a>)>>; + ) -> LocalBoxFuture<'a, OrcRes<(&'a str, GenTokTree)>>; } impl DynLexer for T { @@ -76,7 +75,7 @@ impl DynLexer for T { &self, tail: &'a str, ctx: &'a LexContext<'a>, - ) -> LocalBoxFuture<'a, OrcRes<(&'a str, GenTokTree<'a>)>> { + ) -> LocalBoxFuture<'a, OrcRes<(&'a str, GenTokTree)>> { T::lex(tail, ctx).boxed_local() } } diff --git a/orchid-extension/src/lib.rs b/orchid-extension/src/lib.rs index 650f1ca..dd7be36 100644 --- a/orchid-extension/src/lib.rs +++ b/orchid-extension/src/lib.rs @@ -10,7 +10,6 @@ pub mod fs; pub mod func_atom; pub mod gen_expr; pub mod lexer; -pub mod macros; pub mod msg; pub mod other_system; pub mod parser; diff --git a/orchid-extension/src/macros.rs b/orchid-extension/src/macros.rs deleted file mode 100644 index aa23d08..0000000 --- a/orchid-extension/src/macros.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::rc::Rc; - -use ahash::HashMap; -use futures::future::{LocalBoxFuture, join_all}; -use itertools::Itertools; -use never::Never; -use orchid_api::ExtMsgSet; -use orchid_base::error::OrcRes; -use orchid_base::interner::Tok; -use orchid_base::macros::{MTree, mtreev_from_api, mtreev_to_api}; -use orchid_base::reqnot::{ReqNot, Requester}; -use trait_set::trait_set; - -use crate::api; -use crate::atom::AtomFactory; -use crate::lexer::err_cascade; -use crate::system::SysCtx; -use crate::tree::TreeIntoApiCtx; - -pub trait Macro { - fn pattern() -> MTree<'static, Never>; - fn apply(binds: HashMap, MTree<'_, Never>>) -> MTree<'_, AtomFactory>; -} - -pub trait DynMacro { - fn pattern(&self) -> MTree<'static, Never>; - fn apply<'a>(&self, binds: HashMap, MTree<'a, Never>>) -> MTree<'a, AtomFactory>; -} - -impl DynMacro for T { - fn pattern(&self) -> MTree<'static, Never> { Self::pattern() } - fn apply<'a>(&self, binds: HashMap, MTree<'a, Never>>) -> MTree<'a, AtomFactory> { - Self::apply(binds) - } -} - -pub struct RuleCtx<'a> { - pub(crate) args: HashMap, Vec>>, - pub(crate) run_id: api::ParsId, - pub(crate) sys: SysCtx, -} -impl<'a> RuleCtx<'a> { - pub async fn recurse(&mut self, tree: &[MTree<'a, Never>]) -> OrcRes>> { - let req = api::RunMacros { - run_id: self.run_id, - query: mtreev_to_api(tree, &mut async |b| match *b {}).await, - }; - let Some(treev) = self.sys.get::>().request(req).await else { - return Err(err_cascade(self.sys.i()).await.into()); - }; - static ATOM_MSG: &str = "Returned atom from Rule recursion"; - Ok(mtreev_from_api(&treev, self.sys.i(), &mut async |_| panic!("{ATOM_MSG}")).await) - } - pub fn getv(&mut self, key: &Tok) -> Vec> { - self.args.remove(key).expect("Key not found") - } - pub fn gets(&mut self, key: &Tok) -> MTree<'a, Never> { - let v = self.getv(key); - assert!(v.len() == 1, "Not a scalar"); - v.into_iter().next().unwrap() - } - pub fn unused_arg<'b>(&mut self, keys: impl IntoIterator>) { - keys.into_iter().for_each(|k| { - self.getv(k); - }); - } -} - -trait_set! { - pub trait RuleCB = for<'a> Fn(RuleCtx<'a>) -> LocalBoxFuture<'a, OrcRes>>>; -} - -pub struct Rule { - pub(crate) comments: Vec, - pub(crate) pattern: Vec>, - pub(crate) apply: Rc, -} -impl Rule { - pub(crate) async fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::MacroRule { - api::MacroRule { - comments: join_all(self.comments.iter().map(|c| async { - api::Comment { text: ctx.sys().i().i(c).await.to_api(), location: api::Location::Inherit } - })) - .await, - location: api::Location::Inherit, - pattern: mtreev_to_api(&self.pattern, &mut async |b| match *b {}).await, - id: ctx.with_rule(Rc::new(self)), - } - } -} - -pub fn rule_cmt<'a>( - cmt: impl IntoIterator, - pattern: Vec>, - apply: impl RuleCB + 'static, -) -> Rule { - let comments = cmt.into_iter().map(|s| s.to_string()).collect_vec(); - Rule { comments, pattern, apply: Rc::new(apply) } -} - -pub fn rule(pattern: Vec>, apply: impl RuleCB + 'static) -> Rule { - rule_cmt([], pattern, apply) -} diff --git a/orchid-extension/src/parser.rs b/orchid-extension/src/parser.rs index 9230285..1e58789 100644 --- a/orchid-extension/src/parser.rs +++ b/orchid-extension/src/parser.rs @@ -1,39 +1,49 @@ +use futures::future::LocalBoxFuture; use orchid_base::error::OrcRes; +use orchid_base::name::Sym; use orchid_base::parse::{Comment, Snippet}; -use crate::atom::{AtomFactory, ForeignAtom}; +use crate::expr::Expr; +use crate::gen_expr::GExpr; +use crate::system::SysCtx; use crate::tree::GenTokTree; -pub type GenSnippet<'a> = Snippet<'a, 'a, ForeignAtom<'a>, AtomFactory>; +pub type GenSnippet<'a> = Snippet<'a, Expr, GExpr>; pub trait Parser: Send + Sync + Sized + Default + 'static { const LINE_HEAD: &'static str; fn parse( + ctx: SysCtx, + module: Sym, exported: bool, comments: Vec, line: GenSnippet<'_>, - ) -> OrcRes>>; + ) -> impl Future>> + '_; } pub trait DynParser: Send + Sync + 'static { fn line_head(&self) -> &'static str; fn parse<'a>( &self, + ctx: SysCtx, + module: Sym, exported: bool, comments: Vec, line: GenSnippet<'a>, - ) -> OrcRes>>; + ) -> LocalBoxFuture<'a, OrcRes>>; } impl DynParser for T { fn line_head(&self) -> &'static str { Self::LINE_HEAD } fn parse<'a>( &self, + ctx: SysCtx, + module: Sym, exported: bool, comments: Vec, line: GenSnippet<'a>, - ) -> OrcRes>> { - Self::parse(exported, comments, line) + ) -> LocalBoxFuture<'a, OrcRes>> { + Box::pin(async move { Self::parse(ctx, module, exported, comments, line).await }) } } diff --git a/orchid-extension/src/system.rs b/orchid-extension/src/system.rs index c9524a6..200ac7d 100644 --- a/orchid-extension/src/system.rs +++ b/orchid-extension/src/system.rs @@ -6,7 +6,6 @@ use std::pin::Pin; use std::rc::Rc; use futures::future::LocalBoxFuture; -use hashbrown::HashMap; use memo_map::MemoMap; use orchid_api::ExtMsgSet; use orchid_api_traits::{Coding, Decode}; @@ -24,7 +23,7 @@ use crate::func_atom::Fun; use crate::lexer::LexerObj; use crate::parser::ParserObj; use crate::system_ctor::{CtedObj, SystemCtor}; -use crate::tree::MemKind; +use crate::tree::GenItem; /// System as consumed by foreign code pub trait SystemCard: Default + Send + Sync + 'static { @@ -83,7 +82,7 @@ impl DynSystemCard for T { /// System as defined by author pub trait System: Send + Sync + SystemCard + 'static { - fn env() -> Vec<(String, MemKind)>; + fn env() -> Vec; fn vfs() -> DeclFs; fn lexers() -> Vec; fn parsers() -> Vec; @@ -91,7 +90,7 @@ pub trait System: Send + Sync + SystemCard + 'static { } pub trait DynSystem: Send + Sync + DynSystemCard + 'static { - fn dyn_env(&self) -> HashMap; + fn dyn_env(&self) -> Vec; fn dyn_vfs(&self) -> DeclFs; fn dyn_lexers(&self) -> Vec; fn dyn_parsers(&self) -> Vec; @@ -100,7 +99,7 @@ pub trait DynSystem: Send + Sync + DynSystemCard + 'static { } impl DynSystem for T { - fn dyn_env(&self) -> HashMap { Self::env().into_iter().collect() } + fn dyn_env(&self) -> Vec { Self::env() } fn dyn_vfs(&self) -> DeclFs { Self::vfs() } fn dyn_lexers(&self) -> Vec { Self::lexers() } fn dyn_parsers(&self) -> Vec { Self::parsers() } @@ -112,10 +111,10 @@ impl DynSystem for T { fn card(&self) -> &dyn DynSystemCard { self } } -pub async fn downcast_atom(foreign: ForeignAtom<'_>) -> Result, ForeignAtom<'_>> +pub async fn downcast_atom(foreign: ForeignAtom) -> Result, ForeignAtom> where A: AtomicFeatures { let mut data = &foreign.atom.data[..]; - let ctx = foreign.ctx.clone(); + let ctx = foreign.ctx().clone(); let value = AtomTypeId::decode(Pin::new(&mut data)).await; let own_inst = ctx.get::().inst(); let owner = if *ctx.get::() == foreign.atom.owner { diff --git a/orchid-extension/src/tokio.rs b/orchid-extension/src/tokio.rs index 4eb7428..0257dde 100644 --- a/orchid-extension/src/tokio.rs +++ b/orchid-extension/src/tokio.rs @@ -2,19 +2,16 @@ use crate::entrypoint::ExtensionData; #[cfg(feature = "tokio")] pub async fn tokio_main(data: ExtensionData) { - use std::future::Future; use std::io::Write; use std::mem; - use std::pin::{Pin, pin}; + use std::pin::Pin; use std::rc::Rc; use async_std::io; - use async_stream::stream; + use futures::StreamExt; use futures::future::LocalBoxFuture; use futures::stream::FuturesUnordered; - use futures::{StreamExt, stream, stream_select}; use orchid_api_traits::{Decode, Encode}; - use orchid_base::clone; use tokio::task::{LocalSet, spawn_local}; use crate::api; diff --git a/orchid-extension/src/tree.rs b/orchid-extension/src/tree.rs index 4818c83..bbeedbe 100644 --- a/orchid-extension/src/tree.rs +++ b/orchid-extension/src/tree.rs @@ -1,34 +1,67 @@ use std::num::NonZero; -use std::ops::Range; use std::rc::Rc; use dyn_clone::{DynClone, clone_box}; use futures::FutureExt; use futures::future::{LocalBoxFuture, join_all}; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use itertools::Itertools; -use orchid_base::interner::Tok; +use orchid_base::interner::{Interner, Tok}; +use orchid_base::location::Pos; use orchid_base::name::Sym; use orchid_base::reqnot::ReqHandlish; -use orchid_base::tree::{TokTree, Token}; -use ordered_float::NotNan; +use orchid_base::tree::{TokTree, Token, TokenVariant}; use substack::Substack; use trait_set::trait_set; use crate::api; -use crate::atom::{AtomFactory, ForeignAtom}; use crate::conv::ToExpr; use crate::entrypoint::MemberRecord; +use crate::expr::{Expr, ExprHandle}; use crate::func_atom::{ExprFunc, Fun}; use crate::gen_expr::{GExpr, arg, call, lambda, seq}; -use crate::macros::Rule; use crate::system::SysCtx; -pub type GenTokTree<'a> = TokTree<'a, ForeignAtom<'a>, AtomFactory>; -pub type GenTok<'a> = Token<'a, ForeignAtom<'a>, AtomFactory>; +pub type GenTokTree = TokTree; +pub type GenTok = Token; -pub async fn do_extra(f: &AtomFactory, r: Range, ctx: SysCtx) -> api::TokenTree { - api::TokenTree { range: r, token: api::Token::Atom(f.clone().build(ctx).await) } +impl TokenVariant for GExpr { + type FromApiCtx<'a> = (); + type ToApiCtx<'a> = (SysCtx, &'a dyn ReqHandlish); + async fn from_api( + _: &api::Expression, + _: &mut Self::FromApiCtx<'_>, + _: Pos, + _: &Interner, + ) -> Self { + panic!("Received new expression from host") + } + async fn into_api(self, (ctx, hand): &mut Self::ToApiCtx<'_>) -> api::Expression { + self.api_return(ctx.clone(), hand).await + } +} + +impl TokenVariant for Expr { + type FromApiCtx<'a> = SysCtx; + async fn from_api( + api: &api::ExprTicket, + ctx: &mut Self::FromApiCtx<'_>, + _: Pos, + _: &Interner, + ) -> Self { + // SAFETY: receiving trees from sublexers implies ownership transfer + Expr::from_handle(Rc::new(unsafe { ExprHandle::from_args(ctx.clone(), *api) })) + } + type ToApiCtx<'a> = (); + async fn into_api(self, (): &mut Self::ToApiCtx<'_>) -> api::ExprTicket { + let hand = self.handle(); + std::mem::drop(self); + let h = match Rc::try_unwrap(hand) { + Ok(h) => h, + Err(h) => h.as_ref().clone().await, + }; + h.into_tk() + } } fn with_export(mem: GenMember, public: bool) -> Vec { @@ -47,14 +80,9 @@ impl GenItem { let kind = match self.kind { GenItemKind::Export(n) => api::ItemKind::Export(ctx.sys().i().i::(&n).await.to_api()), GenItemKind::Member(mem) => api::ItemKind::Member(mem.into_api(ctx).await), - GenItemKind::Import(cn) => api::ItemKind::Import(cn.tok().to_api()), - GenItemKind::Macro(priority, gen_rules) => { - let mut rules = Vec::with_capacity(gen_rules.len()); - for rule in gen_rules { - rules.push(rule.into_api(ctx).await) - } - api::ItemKind::Macro(api::MacroBlock { priority, rules }) - }, + GenItemKind::Import(cn) => api::ItemKind::Import( + Sym::parse(&cn, ctx.sys().i()).await.expect("Import path empty string").to_api(), + ), }; let comments = join_all(self.comments.iter().map(|c| async { api::Comment { @@ -70,24 +98,23 @@ impl GenItem { pub fn cnst(public: bool, name: &str, value: impl ToExpr) -> Vec { with_export(GenMember { name: name.to_string(), kind: MemKind::Const(value.to_expr()) }, public) } +pub fn import(public: bool, path: &str) -> Vec { + let mut out = vec![GenItemKind::Import(path.to_string())]; + if public { + out.push(GenItemKind::Export(path.split("::").last().unwrap().to_string())); + } + out.into_iter().map(|kind| GenItem { comments: vec![], kind }).collect() +} pub fn module( public: bool, name: &str, - imports: impl IntoIterator, items: impl IntoIterator>, ) -> Vec { - let (name, kind) = root_mod(name, imports, items); + let (name, kind) = root_mod(name, items); with_export(GenMember { name, kind }, public) } -pub fn root_mod( - name: &str, - imports: impl IntoIterator, - items: impl IntoIterator>, -) -> (String, MemKind) { - let kind = MemKind::Mod { - imports: imports.into_iter().collect(), - items: items.into_iter().flatten().collect(), - }; +pub fn root_mod(name: &str, items: impl IntoIterator>) -> (String, MemKind) { + let kind = MemKind::Mod { items: items.into_iter().flatten().collect() }; (name.to_string(), kind) } pub fn fun(exported: bool, name: &str, xf: impl ExprFunc) -> Vec { @@ -107,12 +134,12 @@ pub fn fun(exported: bool, name: &str, xf: impl ExprFunc) -> Vec, rules: impl IntoIterator) -> Vec { - let prio = prio.map(|p| NotNan::new(p).unwrap()); - vec![GenItem { - kind: GenItemKind::Macro(prio, rules.into_iter().collect_vec()), - comments: vec![], - }] +pub fn prefix(path: &str, items: impl IntoIterator>) -> Vec { + let mut items = items.into_iter().flatten().collect_vec(); + for step in path.split("::").collect_vec().into_iter().rev() { + items = module(true, step, [items]); + } + items } pub fn comments<'a>( @@ -126,6 +153,58 @@ pub fn comments<'a>( val } +/// Trivially merge a gen tree. Behaviours were chosen to make this simple. +/// +/// - Comments on imports are discarded +/// - Comments on exports and submodules are combined +/// - Duplicate constants result in an error +/// - A combination of lazy and anything results in an error +pub fn merge_trivial(trees: impl IntoIterator>) -> Vec { + let mut imported = HashSet::::new(); + let mut exported = HashMap::>::new(); + let mut members = HashMap::)>::new(); + for item in trees.into_iter().flatten() { + match item.kind { + GenItemKind::Import(sym) => { + imported.insert(sym); + }, + GenItemKind::Export(e) => + exported.entry(e.clone()).or_insert(HashSet::new()).extend(item.comments.iter().cloned()), + GenItemKind::Member(mem) => match mem.kind { + unit @ (MemKind::Const(_) | MemKind::Lazy(_)) => { + let prev = members.insert(mem.name.clone(), (unit, item.comments.into_iter().collect())); + assert!(prev.is_none(), "Conflict in trivial tree merge on {}", mem.name); + }, + MemKind::Mod { items } => match members.entry(mem.name.clone()) { + hashbrown::hash_map::Entry::Vacant(slot) => { + slot.insert((MemKind::Mod { items }, item.comments.into_iter().collect())); + }, + hashbrown::hash_map::Entry::Occupied(mut old) => match old.get_mut() { + (MemKind::Mod { items: old_items }, old_cmts) => { + let mut swap = vec![]; + std::mem::swap(&mut swap, old_items); + *old_items = merge_trivial([swap, items]); + old_cmts.extend(item.comments); + }, + _ => panic!("Conflict in trivial merge on {}", mem.name), + }, + }, + }, + } + } + + (imported.into_iter().map(|txt| GenItem { comments: vec![], kind: GenItemKind::Import(txt) })) + .chain(exported.into_iter().map(|(k, cmtv)| GenItem { + comments: cmtv.into_iter().collect(), + kind: GenItemKind::Export(k), + })) + .chain(members.into_iter().map(|(name, (kind, cmtv))| GenItem { + comments: cmtv.into_iter().collect(), + kind: GenItemKind::Member(GenMember { name, kind }), + })) + .collect() +} + trait_set! { trait LazyMemberCallback = FnOnce(Sym, SysCtx) -> LocalBoxFuture<'static, MemKind> + DynClone @@ -144,13 +223,12 @@ impl Clone for LazyMemberFactory { pub enum GenItemKind { Member(GenMember), Export(String), - Import(Sym), - Macro(Option>, Vec), + Import(String), } pub struct GenMember { - name: String, - kind: MemKind, + pub name: String, + pub kind: MemKind, } impl GenMember { pub async fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::Member { @@ -164,7 +242,7 @@ impl GenMember { pub enum MemKind { Const(GExpr), - Mod { imports: Vec, items: Vec }, + Mod { items: Vec }, Lazy(LazyMemberFactory), } impl MemKind { @@ -172,15 +250,12 @@ impl MemKind { match self { Self::Lazy(lazy) => api::MemberKind::Lazy(ctx.with_lazy(lazy)), Self::Const(c) => api::MemberKind::Const(c.api_return(ctx.sys(), ctx.req()).await), - Self::Mod { imports, items } => { - let all_items = (imports.into_iter()) - .map(|t| GenItem { comments: vec![], kind: GenItemKind::Import(t) }) - .chain(items); - let mut items = Vec::new(); - for i in all_items { - items.push(i.into_api(ctx).boxed_local().await) + Self::Mod { items } => { + let mut api_items = Vec::new(); + for i in items { + api_items.push(i.into_api(ctx).boxed_local().await) } - api::MemberKind::Module(api::Module { items }) + api::MemberKind::Module(api::Module { items: api_items }) }, } } @@ -189,7 +264,6 @@ impl MemKind { pub trait TreeIntoApiCtx { fn sys(&self) -> SysCtx; fn with_lazy(&mut self, fac: LazyMemberFactory) -> api::TreeId; - fn with_rule(&mut self, rule: Rc) -> api::MacroId; fn push_path(&mut self, seg: Tok) -> impl TreeIntoApiCtx; fn req(&self) -> &impl ReqHandlish; } @@ -199,7 +273,6 @@ pub struct TreeIntoApiCtxImpl<'a, 'b, RH: ReqHandlish> { pub basepath: &'a [Tok], pub path: Substack<'a, Tok>, pub lazy_members: &'b mut HashMap, - pub rules: &'b mut HashMap>, pub req: &'a RH, } @@ -209,7 +282,6 @@ impl TreeIntoApiCtx for TreeIntoApiCtxImpl<'_, '_, RH> { TreeIntoApiCtxImpl { req: self.req, lazy_members: self.lazy_members, - rules: self.rules, sys: self.sys.clone(), basepath: self.basepath, path: self.path.push(seg), @@ -221,10 +293,5 @@ impl TreeIntoApiCtx for TreeIntoApiCtxImpl<'_, '_, RH> { self.lazy_members.insert(id, MemberRecord::Gen(path, fac)); id } - fn with_rule(&mut self, rule: Rc) -> orchid_api::MacroId { - let id = api::MacroId(NonZero::new((self.lazy_members.len() + 1) as u64).unwrap()); - self.rules.insert(id, rule); - id - } fn req(&self) -> &impl ReqHandlish { self.req } } diff --git a/orchid-host/Cargo.toml b/orchid-host/Cargo.toml index 2695b69..0bbd268 100644 --- a/orchid-host/Cargo.toml +++ b/orchid-host/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "orchid-host" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/orchid-host/src/ctx.rs b/orchid-host/src/ctx.rs index 3ca36aa..5816292 100644 --- a/orchid-host/src/ctx.rs +++ b/orchid-host/src/ctx.rs @@ -1,8 +1,9 @@ use std::cell::RefCell; use std::num::{NonZero, NonZeroU16}; -use std::rc::Rc; +use std::rc::{Rc, Weak}; use std::{fmt, ops}; +use async_once_cell::OnceCell; use async_std::sync::RwLock; use hashbrown::HashMap; use orchid_api::SysId; @@ -11,8 +12,9 @@ use orchid_base::interner::Interner; use crate::api; use crate::atom::WeakAtomHand; +use crate::expr_store::ExprStore; +use crate::parsed::Root; use crate::system::{System, WeakSystem}; -use crate::tree::Module; pub struct CtxData { pub i: Rc, @@ -20,7 +22,8 @@ pub struct CtxData { pub systems: RwLock>, pub system_id: RefCell, pub owned_atoms: RwLock>, - // pub root: RwLock, + pub common_exprs: ExprStore, + pub root: OnceCell>, } #[derive(Clone)] pub struct Ctx(Rc); @@ -36,7 +39,8 @@ impl Ctx { systems: RwLock::default(), system_id: RefCell::new(NonZero::new(1).unwrap()), owned_atoms: RwLock::default(), - // root: RwLock::new(Module::default()), + common_exprs: ExprStore::default(), + root: OnceCell::default(), })) } pub(crate) async fn system_inst(&self, id: api::SysId) -> Option { @@ -47,6 +51,10 @@ impl Ctx { *g = g.checked_add(1).unwrap_or(NonZeroU16::new(1).unwrap()); SysId(*g) } + pub async fn set_root(&self, root: Weak) { + assert!(self.root.get().is_none(), "Root already assigned"); + self.root.get_or_init(async { root }).await; + } } impl fmt::Debug for Ctx { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/orchid-host/src/dealias.rs b/orchid-host/src/dealias.rs index 0419745..abe5d6c 100644 --- a/orchid-host/src/dealias.rs +++ b/orchid-host/src/dealias.rs @@ -8,9 +8,10 @@ use orchid_base::format::{FmtCtxImpl, Format, take_first}; use orchid_base::interner::{Interner, Tok}; use orchid_base::location::Pos; use orchid_base::name::{NameLike, Sym, VName}; +use substack::Substack; -use crate::macros::{MacTok, MacTree}; -use crate::tree::{ItemKind, MemberKind, Module, RuleKind, WalkErrorKind}; +use crate::expr::Expr; +use crate::parsed::{ItemKind, ParsedMemberKind, ParsedModule, WalkErrorKind}; /// Errors produced by absolute_path #[derive(Clone, Debug, Hash, PartialEq, Eq)] @@ -73,28 +74,36 @@ pub fn absolute_path( .map_err(|_| AbsPathError::RootPath) } +pub struct DealiasCtx<'a> { + pub i: &'a Interner, + pub rep: &'a Reporter, + pub consts: &'a mut HashMap, +} + pub async fn resolv_glob( cwd: &[Tok], - root: &Module, + root: &ParsedModule, abs_path: &[Tok], pos: Pos, - i: &Interner, - r: &impl Reporter, + ctx: &mut DealiasCtx<'_>, ) -> Vec> { let coprefix_len = cwd.iter().zip(abs_path).take_while(|(a, b)| a == b).count(); let (co_prefix, diff_path) = abs_path.split_at(coprefix_len); - let co_parent = root.walk(false, co_prefix.iter().cloned()).await.expect("Invalid step in cwd"); - let target_module = match co_parent.walk(true, diff_path.iter().cloned()).await { + let co_parent = + root.walk(false, co_prefix.iter().cloned(), ctx.consts).await.expect("Invalid step in cwd"); + let target_module = match co_parent.walk(true, diff_path.iter().cloned(), ctx.consts).await { Ok(t) => t, Err(e) => { let path = abs_path[..=coprefix_len + e.pos].iter().join("::"); let (tk, msg) = match e.kind { WalkErrorKind::Constant => - (i.i("Invalid import path").await, format!("{path} is a constant")), - WalkErrorKind::Missing => (i.i("Invalid import path").await, format!("{path} not found")), - WalkErrorKind::Private => (i.i("Import inaccessible").await, format!("{path} is private")), + (ctx.i.i("Invalid import path").await, format!("{path} is a constant")), + WalkErrorKind::Missing => + (ctx.i.i("Invalid import path").await, format!("{path} not found")), + WalkErrorKind::Private => + (ctx.i.i("Import inaccessible").await, format!("{path} is private")), }; - r.report(mk_err(tk, msg, [pos.into()])); + (&ctx.rep).report(mk_err(tk, msg, [pos.into()])); return vec![]; }, }; @@ -104,36 +113,36 @@ pub async fn resolv_glob( /// Read import statements and convert them into aliases, rasising any import /// errors in the process pub async fn imports_to_aliases( - module: &Module, + module: &ParsedModule, cwd: &mut Vec>, - root: &Module, + root: &ParsedModule, alias_map: &mut HashMap, alias_rev_map: &mut HashMap>, - i: &Interner, - rep: &impl Reporter, + ctx: &mut DealiasCtx<'_>, ) { let mut import_locs = HashMap::>::new(); for item in &module.items { match &item.kind { ItemKind::Import(imp) => match absolute_path(cwd, &imp.path) { - Err(e) => rep.report(e.err_obj(i, item.pos.clone(), &imp.path.iter().join("::")).await), + Err(e) => + ctx.rep.report(e.err_obj(ctx.i, item.pos.clone(), &imp.path.iter().join("::")).await), Ok(abs_path) => { let names = match imp.name.as_ref() { Some(n) => Either::Right([n.clone()].into_iter()), None => Either::Left( - resolv_glob(cwd, root, &abs_path, item.pos.clone(), i, rep).await.into_iter(), + resolv_glob(cwd, root, &abs_path, item.pos.clone(), ctx).await.into_iter(), ), }; for name in names { - let mut tgt = abs_path.clone().suffix([name.clone()]).to_sym(i).await; - let src = Sym::new(cwd.iter().cloned().chain([name]), i).await.unwrap(); + let mut tgt = abs_path.clone().suffix([name.clone()]).to_sym(ctx.i).await; + let src = Sym::new(cwd.iter().cloned().chain([name]), ctx.i).await.unwrap(); import_locs.entry(src.clone()).or_insert(vec![]).push(item.pos.clone()); if let Some(tgt2) = alias_map.get(&tgt) { tgt = tgt2.clone(); } if src == tgt { - rep.report(mk_err( - i.i("Circular references").await, + ctx.rep.report(mk_err( + ctx.i.i("Circular references").await, format!("{src} circularly refers to itself"), [item.pos.clone().into()], )); @@ -142,8 +151,8 @@ pub async fn imports_to_aliases( if let Some(fst_val) = alias_map.get(&src) { let locations = (import_locs.get(&src)) .expect("The same name could only have appeared in the same module"); - rep.report(mk_err( - i.i("Conflicting imports").await, + ctx.rep.report(mk_err( + ctx.i.i("Conflicting imports").await, if fst_val == &src { format!("{src} is imported multiple times") } else { @@ -163,78 +172,35 @@ pub async fn imports_to_aliases( } }, }, - ItemKind::Member(mem) => match mem.kind().await { - MemberKind::Const(_) => (), - MemberKind::Mod(m) => { + ItemKind::Member(mem) => match mem.kind(ctx.consts).await { + ParsedMemberKind::Const => (), + ParsedMemberKind::Mod(m) => { cwd.push(mem.name()); - imports_to_aliases(m, cwd, root, alias_map, alias_rev_map, i, rep).boxed_local().await; + imports_to_aliases(m, cwd, root, alias_map, alias_rev_map, ctx).boxed_local().await; cwd.pop(); }, }, - ItemKind::Export(_) | ItemKind::Macro(..) => (), + ItemKind::Export(_) => (), } } } -pub async fn dealias(module: &mut Module, alias_map: &HashMap, i: &Interner) { +pub async fn dealias( + path: Substack<'_, Tok>, + module: &mut ParsedModule, + alias_map: &HashMap, + ctx: &mut DealiasCtx<'_>, +) { for item in &mut module.items { match &mut item.kind { ItemKind::Export(_) | ItemKind::Import(_) => (), - ItemKind::Member(mem) => match mem.kind_mut().await { - MemberKind::Const(c) => { - let Some(source) = c.source() else { continue }; - let Some(new_source) = dealias_mactreev(source, alias_map, i).await else { continue }; - c.set_source(new_source); - }, - MemberKind::Mod(m) => dealias(m, alias_map, i).boxed_local().await, + ItemKind::Member(mem) => { + let path = path.push(mem.name()); + match mem.kind_mut(ctx.consts).await { + ParsedMemberKind::Const => (), + ParsedMemberKind::Mod(m) => dealias(path, m, alias_map, ctx).boxed_local().await, + } }, - ItemKind::Macro(_, rules) => - for rule in rules.iter_mut() { - let RuleKind::Native(c) = &mut rule.kind else { continue }; - let Some(source) = c.source() else { continue }; - let Some(new_source) = dealias_mactreev(source, alias_map, i).await else { continue }; - c.set_source(new_source); - }, } } } - -async fn dealias_mactree( - mtree: &MacTree, - aliases: &HashMap, - i: &Interner, -) -> Option { - let new_tok = match &*mtree.tok { - MacTok::Atom(_) | MacTok::Ph(_) => return None, - tok @ (MacTok::Done(_) | MacTok::Ref(_) | MacTok::Slot(_)) => panic!( - "{} should not appear in retained pre-macro source", - take_first(&tok.print(&FmtCtxImpl { i }).await, true) - ), - MacTok::Name(n) => MacTok::Name(aliases.get(n).unwrap_or(n).clone()), - MacTok::Lambda(arg, body) => { - match (dealias_mactreev(arg, aliases, i).await, dealias_mactreev(body, aliases, i).await) { - (None, None) => return None, - (Some(arg), None) => MacTok::Lambda(arg, body.clone()), - (None, Some(body)) => MacTok::Lambda(arg.clone(), body), - (Some(arg), Some(body)) => MacTok::Lambda(arg, body), - } - }, - MacTok::S(p, b) => MacTok::S(*p, dealias_mactreev(b, aliases, i).await?), - }; - Some(MacTree { pos: mtree.pos.clone(), tok: Rc::new(new_tok) }) -} - -async fn dealias_mactreev( - mtreev: &[MacTree], - aliases: &HashMap, - i: &Interner, -) -> Option> { - let mut results = Vec::with_capacity(mtreev.len()); - let mut any_some = false; - for item in mtreev { - let out = dealias_mactree(item, aliases, i).boxed_local().await; - any_some |= out.is_some(); - results.push(out.unwrap_or(item.clone())); - } - any_some.then_some(results) -} diff --git a/orchid-host/src/execute.rs b/orchid-host/src/execute.rs index 381357b..ed8688b 100644 --- a/orchid-host/src/execute.rs +++ b/orchid-host/src/execute.rs @@ -3,15 +3,13 @@ use std::mem; use async_std::sync::RwLockWriteGuard; use bound::Bound; use futures::FutureExt; -use orchid_base::error::{OrcErrv, mk_errv}; +use orchid_base::error::OrcErrv; use orchid_base::format::{FmtCtxImpl, Format, take_first}; use orchid_base::location::Pos; use orchid_base::logging::Logger; -use orchid_base::name::NameLike; use crate::ctx::Ctx; -use crate::expr::{Expr, ExprKind, PathSet, Step}; -use crate::tree::{ItemKind, MemberKind, Module, Root}; +use crate::expr::{Expr, ExprKind, ExprParseCtx, PathSet, PathSetBuilder, Step}; type ExprGuard = Bound, Expr>; @@ -38,13 +36,12 @@ pub struct ExecCtx { cur_pos: Pos, did_pop: bool, logger: Logger, - root: Root, } impl ExecCtx { - pub async fn new(ctx: Ctx, logger: Logger, root: Root, init: Expr) -> Self { + pub async fn new(ctx: Ctx, logger: Logger, init: Expr) -> Self { let cur_pos = init.pos(); let cur = Bound::async_new(init, |init| init.kind().write()).await; - Self { ctx, gas: None, stack: vec![], cur, cur_pos, did_pop: false, root, logger } + Self { ctx, gas: None, stack: vec![], cur, cur_pos, did_pop: false, logger } } pub fn remaining_gas(&self) -> u64 { self.gas.expect("queried remaining_gas but no gas was set") } pub fn set_gas(&mut self, gas: Option) { self.gas = gas } @@ -92,20 +89,25 @@ impl ExecCtx { }, ExprKind::Seq(a, b) if !self.did_pop => (ExprKind::Seq(a.clone(), b), StackOp::Push(a)), ExprKind::Seq(_, b) => (ExprKind::Identity(b), StackOp::Nop), - ExprKind::Const(name) => - match self.root.get_const_value(name, self.cur_pos.clone(), self.ctx.clone()).await { + ExprKind::Const(name) => { + let root = (self.ctx.root.get().and_then(|v| v.upgrade())) + .expect("Root not assigned before execute call"); + match root.get_const_value(name, self.cur_pos.clone(), self.ctx.clone()).await { Err(e) => (ExprKind::Bottom(e), StackOp::Pop), Ok(v) => (ExprKind::Identity(v), StackOp::Nop), - }, + } + }, ExprKind::Arg => panic!("This should not appear outside function bodies"), ek @ ExprKind::Atom(_) => (ek, StackOp::Pop), ExprKind::Bottom(bot) => (ExprKind::Bottom(bot.clone()), StackOp::Unwind(bot)), ExprKind::Call(f, x) if !self.did_pop => (ExprKind::Call(f.clone(), x), StackOp::Push(f)), ExprKind::Call(f, x) => match f.try_into_owned_atom().await { Ok(atom) => { - let mut ext = atom.sys().ext().clone(); + let ext = atom.sys().ext().clone(); let x_norm = self.unpack_ident(&x).await; - let val = Expr::from_api(&atom.call(x_norm).await, &mut ext).await; + let mut parse_ctx = ExprParseCtx { ctx: self.ctx.clone(), exprs: ext.exprs().clone() }; + let val = + Expr::from_api(&atom.call(x_norm).await, PathSetBuilder::new(), &mut parse_ctx).await; (ExprKind::Identity(val.clone()), StackOp::Swap(val)) }, Err(f) => match &*f.kind().read().await { @@ -113,9 +115,16 @@ impl ExecCtx { panic!("This should not appear outside function bodies"), ExprKind::Missing => panic!("Should have been replaced"), ExprKind::Atom(a) => { - let mut ext = a.sys().ext().clone(); + let ext = a.sys().ext().clone(); let x_norm = self.unpack_ident(&x).await; - let val = Expr::from_api(&a.clone().call(x_norm).await, &mut ext).await; + let mut parse_ctx = + ExprParseCtx { ctx: ext.ctx().clone(), exprs: ext.exprs().clone() }; + let val = Expr::from_api( + &a.clone().call(x_norm).await, + PathSetBuilder::new(), + &mut parse_ctx, + ) + .await; (ExprKind::Identity(val.clone()), StackOp::Swap(val)) }, ExprKind::Bottom(exprv) => (ExprKind::Bottom(exprv.clone()), StackOp::Pop), diff --git a/orchid-host/src/expr.rs b/orchid-host/src/expr.rs index ca9d13f..3e786e6 100644 --- a/orchid-host/src/expr.rs +++ b/orchid-host/src/expr.rs @@ -8,23 +8,24 @@ use async_std::sync::RwLock; use futures::FutureExt; use hashbrown::HashSet; use itertools::Itertools; -use orchid_base::error::{OrcErrv, mk_errv}; -use orchid_base::format::{FmtCtx, FmtCtxImpl, FmtUnit, Format, Variants, take_first}; +use orchid_base::error::OrcErrv; +use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; use orchid_base::location::Pos; -use orchid_base::macros::mtreev_fmt; use orchid_base::name::Sym; -use orchid_base::tokens::Paren; +use orchid_base::tl_cache; use orchid_base::tree::{AtomRepr, indent}; -use orchid_base::{match_mapping, tl_cache}; use substack::Substack; use crate::api; use crate::atom::AtomHand; use crate::ctx::Ctx; -use crate::extension::Extension; -use crate::macros::{MacTok, MacTree}; +use crate::expr_store::ExprStore; -pub type ExprParseCtx = Extension; +#[derive(Clone)] +pub struct ExprParseCtx { + pub ctx: Ctx, + pub exprs: ExprStore, +} #[derive(Debug)] pub struct ExprData { @@ -55,13 +56,44 @@ impl Expr { .expect("this is a ref, it cannot be null"), ) } - 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"); - } - let pos = Pos::from_api(&api.location, &ctx.ctx().i).await; - let kind = RwLock::new(ExprKind::from_api(&api.kind, pos.clone(), ctx).boxed_local().await); - Self(Rc::new(ExprData { pos, kind })) + pub async fn from_api( + api: &api::Expression, + psb: PathSetBuilder<'_, u64>, + ctx: &mut ExprParseCtx, + ) -> Self { + let pos = Pos::from_api(&api.location, &ctx.ctx.i).await; + let kind = match &api.kind { + api::ExpressionKind::Arg(n) => { + assert!(psb.register_arg(&n), "Arguments must be enclosed in a matching lambda"); + ExprKind::Arg + }, + api::ExpressionKind::Bottom(bot) => + ExprKind::Bottom(OrcErrv::from_api(bot, &ctx.ctx.i).await), + api::ExpressionKind::Call(f, x) => { + let (lpsb, rpsb) = psb.split(); + ExprKind::Call( + Expr::from_api(&f, lpsb, ctx).boxed_local().await, + Expr::from_api(&x, rpsb, ctx).boxed_local().await, + ) + }, + api::ExpressionKind::Const(name) => ExprKind::Const(Sym::from_api(*name, &ctx.ctx.i).await), + api::ExpressionKind::Lambda(x, body) => { + let lbuilder = psb.lambda(&x); + let body = Expr::from_api(&body, lbuilder.stack(), ctx).boxed_local().await; + ExprKind::Lambda(lbuilder.collect(), body) + }, + api::ExpressionKind::NewAtom(a) => + ExprKind::Atom(AtomHand::from_api(a, pos.clone(), &mut ctx.ctx.clone()).await), + api::ExpressionKind::Slot(tk) => return ctx.exprs.get_expr(*tk).expect("Invalid slot"), + api::ExpressionKind::Seq(a, b) => { + let (apsb, bpsb) = psb.split(); + ExprKind::Seq( + Expr::from_api(&a, apsb, ctx).boxed_local().await, + Expr::from_api(&b, bpsb, ctx).boxed_local().await, + ) + }, + }; + Self(Rc::new(ExprData { pos, kind: RwLock::new(kind) })) } pub async fn to_api(&self) -> api::InspectedKind { use api::InspectedKind as K; @@ -107,23 +139,6 @@ pub enum ExprKind { Missing, } impl ExprKind { - pub async fn from_api(api: &api::ExpressionKind, pos: Pos, ctx: &mut ExprParseCtx) -> Self { - match_mapping!(api, api::ExpressionKind => ExprKind { - Lambda(id => PathSet::from_api(*id, api), b => Expr::from_api(b, ctx).await), - Bottom(b => OrcErrv::from_api(b, &ctx.ctx().i).await), - Call(f => Expr::from_api(f, ctx).await, x => Expr::from_api(x, ctx).await), - Const(c => Sym::from_api(*c, &ctx.ctx().i).await), - Seq(a => Expr::from_api(a, ctx).await, b => Expr::from_api(b, ctx).await), - } { - api::ExpressionKind::Arg(_) => ExprKind::Arg, - api::ExpressionKind::NewAtom(a) => ExprKind::Atom(AtomHand::from_api( - a, - pos, - &mut ctx.ctx().clone() - ).await), - api::ExpressionKind::Slot(_) => panic!("Handled in Expr"), - }) - } pub fn at(self, pos: Pos) -> Expr { Expr(Rc::new(ExprData { pos, kind: RwLock::new(self) })) } } impl Format for ExprKind { @@ -174,6 +189,93 @@ pub enum Step { Right, } +#[derive(Clone)] +pub enum PathSetFrame<'a, T: PartialEq> { + Lambda(&'a T, &'a RefCell>), + Step(Step), +} + +#[derive(Clone)] +pub struct PathSetBuilder<'a, T: PartialEq>(Substack<'a, PathSetFrame<'a, T>>); +impl<'a, T: PartialEq> PathSetBuilder<'a, T> { + pub fn new() -> Self { Self(Substack::Bottom) } + pub fn split(&'a self) -> (Self, Self) { + ( + Self(self.0.push(PathSetFrame::Step(Step::Left))), + Self(self.0.push(PathSetFrame::Step(Step::Right))), + ) + } + pub fn lambda<'b>(self, arg: &'b T) -> LambdaBuilder<'b, T> + where 'a: 'b { + LambdaBuilder { arg, path: RefCell::default(), stack: self } + } + /// Register an argument with the corresponding lambda and return true if one + /// was found. (if false is returned, the name is unbound and may refer to a + /// global) + pub fn register_arg(self, t: &T) -> bool { + let mut steps = VecDeque::new(); + for step in self.0.iter() { + match step { + PathSetFrame::Step(step) => steps.push_front(*step), + PathSetFrame::Lambda(name, _) if **name != *t => (), + PathSetFrame::Lambda(_, cell) => { + let mut ps_opt = cell.borrow_mut(); + match &mut *ps_opt { + val @ None => *val = Some(PathSet { steps: steps.into(), next: None }), + Some(val) => { + let mut swap = PathSet { steps: Vec::new(), next: None }; + mem::swap(&mut swap, val); + *val = merge(swap, &Vec::from(steps)); + }, + } + return true; + }, + }; + } + return false; + fn merge(ps: PathSet, steps: &[Step]) -> PathSet { + let diff_idx = ps.steps.iter().zip(steps).take_while(|(l, r)| l == r).count(); + if diff_idx == ps.steps.len() { + if diff_idx == steps.len() { + match ps.next { + Some(_) => panic!("New path ends where old path forks"), + None => panic!("New path same as old path"), + } + } + let Some((left, right)) = ps.next else { panic!("Old path ends where new path continues") }; + let next = match steps[diff_idx] { + Step::Left => Some((Box::new(merge(*left, &steps[diff_idx + 1..])), right)), + Step::Right => Some((left, Box::new(merge(*right, &steps[diff_idx + 1..])))), + }; + PathSet { steps: ps.steps, next } + } else { + let shared_steps = ps.steps.iter().take(diff_idx).cloned().collect(); + let main_steps = ps.steps.iter().skip(diff_idx + 1).cloned().collect(); + let new_branch = steps[diff_idx + 1..].to_vec(); + let main_side = PathSet { steps: main_steps, next: ps.next }; + let new_side = PathSet { steps: new_branch, next: None }; + let (left, right) = match steps[diff_idx] { + Step::Left => (new_side, main_side), + Step::Right => (main_side, new_side), + }; + PathSet { steps: shared_steps, next: Some((Box::new(left), Box::new(right))) } + } + } + } +} + +pub struct LambdaBuilder<'a, T: PartialEq> { + arg: &'a T, + path: RefCell>, + stack: PathSetBuilder<'a, T>, +} +impl<'a, T: PartialEq> LambdaBuilder<'a, T> { + pub fn stack(&'a self) -> PathSetBuilder<'a, T> { + PathSetBuilder(self.stack.0.push(PathSetFrame::Lambda(self.arg, &self.path))) + } + pub fn collect(self) -> Option { self.path.into_inner() } +} + #[derive(Clone, Debug)] pub struct PathSet { /// The single steps through [super::nort::Clause::Apply] @@ -186,32 +288,6 @@ impl PathSet { pub fn next(&self) -> Option<(&PathSet, &PathSet)> { self.next.as_ref().map(|(l, r)| (&**l, &**r)) } - pub fn from_api(id: u64, api: &api::ExpressionKind) -> Option { - use api::ExpressionKind as K; - struct Suffix(VecDeque, Option<(Box, Box)>); - fn seal(Suffix(steps, next): Suffix) -> PathSet { PathSet { steps: steps.into(), next } } - fn after(step: Step, mut suf: Suffix) -> Suffix { - suf.0.push_front(step); - suf - } - return from_api_inner(id, api).map(seal); - fn from_api_inner(id: u64, api: &api::ExpressionKind) -> Option { - match &api { - K::Arg(id2) => (id == *id2).then_some(Suffix(VecDeque::new(), None)), - K::Bottom(_) | K::Const(_) | K::NewAtom(_) | K::Slot(_) => None, - K::Lambda(_, b) => from_api_inner(id, &b.kind), - K::Call(l, r) | K::Seq(l, r) => { - match (from_api_inner(id, &l.kind), from_api_inner(id, &r.kind)) { - (Some(a), Some(b)) => - Some(Suffix(VecDeque::new(), Some((Box::new(seal(a)), Box::new(seal(b)))))), - (Some(l), None) => Some(after(Step::Left, l)), - (None, Some(r)) => Some(after(Step::Right, r)), - (None, None) => None, - } - }, - } - } - } } impl fmt::Display for PathSet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -239,121 +315,3 @@ pub struct WeakExpr(Weak); impl WeakExpr { pub fn upgrade(&self) -> Option { self.0.upgrade().map(Expr) } } - -#[derive(Clone)] -pub enum SrcToExprStep<'a> { - Left, - Right, - Lambda(Sym, &'a RefCell>), -} - -pub async fn mtreev_to_expr( - src: &[MacTree], - stack: Substack<'_, SrcToExprStep<'_>>, - ctx: &Ctx, -) -> ExprKind { - let Some((x, f)) = src.split_last() else { panic!("Empty expression cannot be evaluated") }; - let x_stack = if f.is_empty() { stack.clone() } else { stack.push(SrcToExprStep::Right) }; - let x_kind = match &*x.tok { - MacTok::Atom(a) => ExprKind::Atom(a.clone()), - MacTok::Name(n) => 'name: { - let mut steps = VecDeque::new(); - for step in x_stack.iter() { - match step { - SrcToExprStep::Left => steps.push_front(Step::Left), - SrcToExprStep::Right => steps.push_front(Step::Right), - SrcToExprStep::Lambda(name, _) if name != n => continue, - SrcToExprStep::Lambda(_, cell) => { - let mut ps = cell.borrow_mut(); - match &mut *ps { - val @ None => *val = Some(PathSet { steps: steps.into(), next: None }), - Some(val) => { - let mut swap = PathSet { steps: Vec::new(), next: None }; - mem::swap(&mut swap, val); - *val = merge(swap, &Vec::from(steps)); - fn merge(ps: PathSet, steps: &[Step]) -> PathSet { - let diff_idx = ps.steps.iter().zip(steps).take_while(|(l, r)| l == r).count(); - if diff_idx == ps.steps.len() { - if diff_idx == steps.len() { - match ps.next { - Some(_) => panic!("New path ends where old path forks"), - None => panic!("New path same as old path"), - } - } - let Some((left, right)) = ps.next else { - panic!("Old path ends where new path continues") - }; - let next = match steps[diff_idx] { - Step::Left => Some((Box::new(merge(*left, &steps[diff_idx + 1..])), right)), - Step::Right => Some((left, Box::new(merge(*right, &steps[diff_idx + 1..])))), - }; - PathSet { steps: ps.steps, next } - } else { - let shared_steps = ps.steps.iter().take(diff_idx).cloned().collect(); - let main_steps = ps.steps.iter().skip(diff_idx + 1).cloned().collect(); - let new_branch = steps[diff_idx + 1..].to_vec(); - let main_side = PathSet { steps: main_steps, next: ps.next }; - let new_side = PathSet { steps: new_branch, next: None }; - let (left, right) = match steps[diff_idx] { - Step::Left => (new_side, main_side), - Step::Right => (main_side, new_side), - }; - PathSet { steps: shared_steps, next: Some((Box::new(left), Box::new(right))) } - } - } - }, - } - break 'name ExprKind::Arg; - }, - } - } - ExprKind::Const(n.clone()) - }, - MacTok::Ph(_) | MacTok::Done(_) | MacTok::Ref(_) | MacTok::Slot(_) => - ExprKind::Bottom(mk_errv( - ctx.i.i("placeholder in value").await, - "Placeholders cannot appear anywhere outside macro patterns", - [x.pos.clone().into()], - )), - MacTok::S(Paren::Round, b) if b.is_empty() => - return ExprKind::Bottom(mk_errv( - ctx.i.i("Empty expression").await, - "Empty parens () are illegal", - [x.pos.clone().into()], - )), - MacTok::S(Paren::Round, b) => mtreev_to_expr(b, x_stack, ctx).boxed_local().await, - MacTok::S(..) => ExprKind::Bottom(mk_errv( - ctx.i.i("non-round parentheses after macros").await, - "[] or {} block was not consumed by macros; expressions may only contain ()", - [x.pos.clone().into()], - )), - MacTok::Lambda(_, b) if b.is_empty() => - return ExprKind::Bottom(mk_errv( - ctx.i.i("Empty lambda").await, - "Lambdas must have a body", - [x.pos.clone().into()], - )), - MacTok::Lambda(arg, b) => 'lambda_converter: { - if let [MacTree { tok, .. }] = &**arg { - if let MacTok::Name(n) = &**tok { - let path = RefCell::new(None); - let b = mtreev_to_expr(b, x_stack.push(SrcToExprStep::Lambda(n.clone(), &path)), ctx) - .boxed_local() - .await; - break 'lambda_converter ExprKind::Lambda(path.into_inner(), b.at(x.pos.clone())); - } - } - let argstr = take_first(&mtreev_fmt(arg, &FmtCtxImpl { i: &ctx.i }).await, true); - ExprKind::Bottom(mk_errv( - ctx.i.i("Malformeed lambda").await, - format!("Lambda argument should be single name, found {argstr}"), - [x.pos.clone().into()], - )) - }, - }; - if f.is_empty() { - return x_kind; - } - let f = mtreev_to_expr(f, stack.push(SrcToExprStep::Left), ctx).boxed_local().await; - ExprKind::Call(f.at(Pos::None), x_kind.at(x.pos.clone())) -} diff --git a/orchid-host/src/expr_store.rs b/orchid-host/src/expr_store.rs index 35042eb..e019dc8 100644 --- a/orchid-host/src/expr_store.rs +++ b/orchid-host/src/expr_store.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; use std::fmt; +use std::rc::Rc; use hashbrown::HashMap; use hashbrown::hash_map::Entry; @@ -8,10 +9,18 @@ use crate::api; use crate::expr::Expr; #[derive(Default)] -pub struct ExprStore(RefCell>); +pub struct ExprStoreData { + exprs: RefCell>, + parent: Option, +} +#[derive(Clone, Default)] +pub struct ExprStore(Rc); impl ExprStore { + pub fn derive(&self) -> Self { + Self(Rc::new(ExprStoreData { exprs: RefCell::default(), parent: Some(self.clone()) })) + } pub fn give_expr(&self, expr: Expr) { - match self.0.borrow_mut().entry(expr.id()) { + match self.0.exprs.borrow_mut().entry(expr.id()) { Entry::Occupied(mut oe) => oe.get_mut().0 += 1, Entry::Vacant(v) => { v.insert((1, expr)); @@ -19,16 +28,17 @@ impl ExprStore { } } pub fn take_expr(&self, ticket: api::ExprTicket) { - (self.0.borrow_mut().entry(ticket)) + (self.0.exprs.borrow_mut().entry(ticket)) .and_replace_entry_with(|_, (rc, rt)| (1 < rc).then_some((rc - 1, rt))); } pub fn get_expr(&self, ticket: api::ExprTicket) -> Option { - self.0.borrow().get(&ticket).map(|(_, expr)| expr.clone()) + (self.0.exprs.borrow().get(&ticket).map(|(_, expr)| expr.clone())) + .or_else(|| self.0.parent.as_ref()?.get_expr(ticket)) } } impl fmt::Display for ExprStore { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let r = self.0.borrow(); + let r = self.0.exprs.borrow(); let rc: u32 = r.values().map(|v| v.0).sum(); write!(f, "Store holding {rc} refs to {} exprs", r.len()) } diff --git a/orchid-host/src/extension.rs b/orchid-host/src/extension.rs index e176c1a..ec842f4 100644 --- a/orchid-host/src/extension.rs +++ b/orchid-host/src/extension.rs @@ -13,7 +13,7 @@ use futures::future::{join, join_all}; use futures::{FutureExt, StreamExt, stream, stream_select}; use hashbrown::HashMap; use itertools::Itertools; -use orchid_api::HostMsgSet; +use orchid_api::{HostMsgSet, LsModule}; use orchid_api_traits::Request; use orchid_base::builtin::ExtInit; use orchid_base::clone; @@ -45,7 +45,6 @@ pub struct ExtensionData { exprs: ExprStore, exiting_snd: Sender<()>, lex_recur: Mutex>>>, - mac_recur: Mutex>>>, } impl Drop for ExtensionData { fn drop(&mut self) { @@ -79,7 +78,7 @@ impl Extension { }))); ExtensionData { exiting_snd, - exprs: ExprStore::default(), + exprs: ctx.common_exprs.derive(), ctx: ctx.clone(), systems: (init.systems.iter().cloned()) .map(|decl| SystemCtor { decl, ext: WeakExtension(weak.clone()) }) @@ -88,7 +87,6 @@ impl Extension { init: init.clone(), next_pars: RefCell::new(NonZeroU64::new(1).unwrap()), lex_recur: Mutex::default(), - mac_recur: Mutex::default(), reqnot: ReqNot::new( msg_logger, move |sfn, _| clone!(init; Box::pin(async move { init.send(sfn).await })), @@ -169,12 +167,8 @@ impl Extension { }) .await }, - api::ExtHostReq::RunMacros(rm) => { - let (rep_in, rep_out) = channel::bounded(1); - let lex_g = this.0.mac_recur.lock().await; - let req_in = lex_g.get(&rm.run_id).expect("Sublex for nonexistent lexid"); - req_in.send(ReqPair(rm.clone(), rep_in)).await.unwrap(); - hand.handle(&rm, &rep_out.recv().await.unwrap()).await + api::ExtHostReq::LsModule(ref ls @ LsModule(ref sys, ref path)) => { + todo!() // TODO }, api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => { let atom = AtomHand::new(atom.clone(), &ctx).await; diff --git a/orchid-host/src/lex.rs b/orchid-host/src/lex.rs index 6f9c6a4..a5c6215 100644 --- a/orchid-host/src/lex.rs +++ b/orchid-host/src/lex.rs @@ -1,30 +1,25 @@ -use std::num::NonZeroU64; use std::sync::Arc; use async_std::sync::Mutex; use futures::FutureExt; -use hashbrown::HashMap; use orchid_base::error::{OrcErrv, OrcRes, mk_errv}; use orchid_base::interner::Tok; use orchid_base::location::Pos; -use orchid_base::match_mapping; -use orchid_base::name::Sym; -use orchid_base::number::{num_to_err, parse_num}; use orchid_base::parse::{name_char, name_start, op_char, unrep_space}; use orchid_base::tokens::PARENS; -use orchid_base::tree::{AtomRepr, Ph}; +use orchid_base::tree::recur; use crate::api; -use crate::atom::AtomHand; use crate::ctx::Ctx; +use crate::expr::{Expr, ExprParseCtx}; +use crate::parsed::{ParsTok, ParsTokTree}; use crate::system::System; -use crate::tree::{ParsTok, ParsTokTree}; pub struct LexCtx<'a> { pub systems: &'a [System], pub source: &'a Tok, pub tail: &'a str, - pub sub_trees: &'a mut HashMap, + pub sub_trees: &'a mut Vec, pub ctx: &'a Ctx, } impl<'a> LexCtx<'a> { @@ -50,13 +45,24 @@ impl<'a> LexCtx<'a> { } false } - pub fn add_subtree(&mut self, subtree: ParsTokTree) -> api::TreeTicket { - let next_idx = api::TreeTicket(NonZeroU64::new(self.sub_trees.len() as u64 + 1).unwrap()); - self.sub_trees.insert(next_idx, subtree); - next_idx + pub async fn ser_subtree(&mut self, subtree: ParsTokTree) -> api::TokenTree { + let mut exprs = self.ctx.common_exprs.clone(); + let foo = recur(subtree, &|tt, r| { + if let ParsTok::NewExpr(expr) = tt.tok { + return ParsTok::Handle(expr).at(tt.range); + } + r(tt) + }); + foo.into_api(&mut exprs, &mut ()).await } - pub fn rm_subtree(&mut self, ticket: api::TreeTicket) -> ParsTokTree { - self.sub_trees.remove(&ticket).unwrap() + pub async fn des_subtree(&mut self, tree: &api::TokenTree) -> ParsTokTree { + ParsTokTree::from_api( + &tree, + &mut self.ctx.common_exprs.clone(), + &mut ExprParseCtx { ctx: self.ctx.clone(), exprs: self.ctx.common_exprs.clone() }, + &self.ctx.i, + ) + .await } pub fn strip_char(&mut self, tgt: char) -> bool { if let Some(src) = self.tail.strip_prefix(tgt) { @@ -86,8 +92,12 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes { ); let tok = if ctx.strip_prefix("\r\n") || ctx.strip_prefix("\r") || ctx.strip_prefix("\n") { ParsTok::BR - } else if ctx.strip_prefix("::") { - ParsTok::NS + } else if let Some(tail) = (ctx.tail.starts_with(name_start).then_some(ctx.tail)) + .and_then(|t| t.trim_start_matches(name_char).strip_prefix("::")) + { + let name = &ctx.tail[..ctx.tail.len() - tail.len() - "::".len()]; + let body = lex_once(ctx).boxed_local().await?; + ParsTok::NS(ctx.ctx.i.i(name).await, Box::new(body)) } else if ctx.strip_prefix("--[") { let Some((cmt, tail)) = ctx.tail.split_once("]--") else { return Err(mk_errv( @@ -132,20 +142,6 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes { ctx.trim_ws(); } ParsTok::S(*paren, body) - } else if ctx.strip_prefix("macro") - && !ctx.tail.chars().next().is_some_and(|x| x.is_ascii_alphabetic()) - { - ctx.strip_prefix("macro"); - if ctx.strip_char('(') { - let pos = ctx.get_pos(); - let numstr = ctx.get_start_matches(|x| x != ')').trim(); - match parse_num(numstr) { - Ok(num) => ParsTok::Macro(Some(num.to_f64())), - Err(e) => return Err(num_to_err(e, pos, &ctx.ctx.i).await.into()), - } - } else { - ParsTok::Macro(None) - } } else { for sys in ctx.systems { let mut errors = Vec::new(); @@ -157,7 +153,7 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes { .lex(source, pos, |pos| async move { let mut ctx_g = ctx_lck.lock().await; match lex_once(&mut ctx_g.push(pos)).boxed_local().await { - Ok(t) => Some(api::SubLexed { pos: t.range.end, ticket: ctx_g.add_subtree(t) }), + Ok(t) => Some(api::SubLexed { pos: t.range.end, tree: ctx_g.ser_subtree(t).await }), Err(e) => { errors_lck.lock().await.push(e); None @@ -172,7 +168,7 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes { ), Ok(Some(lexed)) => { ctx.set_pos(lexed.pos); - return Ok(tt_to_owned(&lexed.expr, ctx).await); + return Ok(ctx.des_subtree(&lexed.expr).await); }, Ok(None) => match errors.into_iter().reduce(|a, b| a + b) { Some(errors) => return Err(errors), @@ -196,39 +192,8 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes { Ok(ParsTokTree { tok, range: start..ctx.get_pos() }) } -async fn tt_to_owned(api: &api::TokenTree, ctx: &mut LexCtx<'_>) -> ParsTokTree { - let tok = match_mapping!(&api.token, api::Token => ParsTok { - Atom(atom => - AtomHand::from_api(atom, Pos::Range(api.range.clone()), &mut ctx.ctx.clone()).await - ), - Bottom(err => OrcErrv::from_api(err, &ctx.ctx.i).await), - LambdaHead(arg => ttv_to_owned(arg, ctx).boxed_local().await), - Name(name => Tok::from_api(*name, &ctx.ctx.i).await), - Reference(tstrv => Sym::from_api(*tstrv, &ctx.ctx.i).await), - S(p.clone(), b => ttv_to_owned(b, ctx).boxed_local().await), - BR, NS, - Comment(c.clone()), - Ph(ph => Ph::from_api(ph, &ctx.ctx.i).await), - Macro(*prio), - } { - api::Token::Slot(id) => return ctx.rm_subtree(*id), - }); - ParsTokTree { range: api.range.clone(), tok } -} - -async fn ttv_to_owned<'a>( - api: impl IntoIterator, - ctx: &mut LexCtx<'_>, -) -> Vec { - let mut out = Vec::new(); - for tt in api { - out.push(tt_to_owned(tt, ctx).await) - } - out -} - pub async fn lex(text: Tok, systems: &[System], ctx: &Ctx) -> OrcRes> { - let mut sub_trees = HashMap::new(); + let mut sub_trees = Vec::new(); let mut ctx = LexCtx { source: &text, sub_trees: &mut sub_trees, tail: &text[..], systems, ctx }; let mut tokv = Vec::new(); ctx.trim(unrep_space); diff --git a/orchid-host/src/lib.rs b/orchid-host/src/lib.rs index e9db26c..eb9617a 100644 --- a/orchid-host/src/lib.rs +++ b/orchid-host/src/lib.rs @@ -8,9 +8,8 @@ pub mod expr; pub mod expr_store; pub mod extension; pub mod lex; -pub mod macros; pub mod parse; -pub mod rule; +pub mod parsed; pub mod subprocess; pub mod system; pub mod tree; diff --git a/orchid-host/src/macros.rs b/orchid-host/src/macros.rs deleted file mode 100644 index ab11de1..0000000 --- a/orchid-host/src/macros.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::rc::Rc; - -use futures::FutureExt; -use hashbrown::{HashMap, HashSet}; -use itertools::Itertools; -use orchid_base::clone; -use orchid_base::macros::{MTok, MTree, mtreev_from_api, mtreev_to_api}; -use orchid_base::name::Sym; -use trait_set::trait_set; - -use crate::api; -use crate::atom::AtomHand; -use crate::ctx::Ctx; -use crate::rule::state::MatchState; -use crate::tree::Code; - -pub type MacTok = MTok<'static, AtomHand>; -pub type MacTree = MTree<'static, AtomHand>; - -trait_set! { - trait MacroCB = Fn(Vec) -> Option>; -} - -type Slots = HashMap>; - -pub async fn macro_treev_to_api(mtree: Vec, slots: &mut Slots) -> Vec { - mtreev_to_api(&mtree, &mut |a: &AtomHand| { - let id = api::MacroTreeId((slots.len() as u64 + 1).try_into().unwrap()); - slots.insert(id, Rc::new(MacTok::Atom(a.clone()))); - async move { api::MacroToken::Slot(id) }.boxed_local() - }) - .await -} - -pub async fn macro_treev_from_api(api: Vec, ctx: Ctx) -> Vec { - mtreev_from_api(&api, &ctx.clone().i, &mut async move |atom| { - MacTok::Atom(AtomHand::new(atom.clone(), &ctx).await) - }) - .await -} - -pub fn deslot_macro(tree: &[MacTree], slots: &mut Slots) -> Option> { - return work(slots, tree); - fn work( - slots: &mut HashMap>, - tree: &[MacTree], - ) -> Option> { - let items = (tree.iter()) - .map(|t| { - Some(MacTree { - tok: match &*t.tok { - MacTok::Atom(_) | MacTok::Name(_) | MacTok::Ph(_) => return None, - MacTok::Ref(_) => panic!("Ref is an extension-local optimization"), - MacTok::Done(_) => panic!("Created and removed by matcher"), - MacTok::Slot(slot) => slots.get(&slot.id()).expect("Slot not found").clone(), - MacTok::S(paren, b) => Rc::new(MacTok::S(*paren, work(slots, b)?)), - MacTok::Lambda(a, b) => Rc::new(match (work(slots, a), work(slots, b)) { - (None, None) => return None, - (Some(a), None) => MacTok::Lambda(a, b.clone()), - (None, Some(b)) => MacTok::Lambda(a.clone(), b), - (Some(a), Some(b)) => MacTok::Lambda(a, b), - }), - }, - pos: t.pos.clone(), - }) - }) - .collect_vec(); - let any_changed = items.iter().any(Option::is_some); - any_changed.then(|| { - (items.into_iter().enumerate()) - .map(|(i, opt)| opt.unwrap_or_else(|| tree[i].clone())) - .collect_vec() - }) - } -} - -pub struct Macro { - deps: HashSet, - cases: Vec<(Matcher, Code)>, -} - -fn fill_lexicon(tgt: &MacTree, lexicon: &mut HashSet) { - match &*tgt.tok { - MTok::Name(n) => { - lexicon.insert(n.clone()); - }, - MTok::Lambda(arg, body) => { - arg.iter().for_each(|t| fill_lexicon(t, lexicon)); - body.iter().for_each(|t| fill_lexicon(t, lexicon)) - }, - MTok::S(_, body) => body.iter().for_each(|t| fill_lexicon(t, lexicon)), - _ => (), - } -} - -fn run_body(body: &Code, mut state: MatchState<'_>) -> Vec { - let inject: Vec = todo!("Call the interpreter with bindings"); - inject - .into_iter() - .map(|MTree { pos, tok }| MTree { pos, tok: Rc::new(MTok::Done(tok)) }) - .collect_vec() -} diff --git a/orchid-host/src/parse.rs b/orchid-host/src/parse.rs index b148ad1..2f9be6e 100644 --- a/orchid-host/src/parse.rs +++ b/orchid-host/src/parse.rs @@ -1,103 +1,119 @@ -use std::rc::Rc; +use std::cell::RefCell; use futures::FutureExt; use futures::future::join_all; +use hashbrown::HashMap; use itertools::Itertools; -use never::Never; -use orchid_base::error::{OrcErrv, OrcRes, Reporter, ReporterImpl, mk_err, mk_errv}; -use orchid_base::interner::Tok; +use orchid_base::error::{OrcRes, Reporter, mk_err, mk_errv}; +use orchid_base::format::fmt; +use orchid_base::interner::{Interner, Tok}; use orchid_base::location::Pos; -use orchid_base::macros::{MTok, MTree}; use orchid_base::name::Sym; use orchid_base::parse::{ - Comment, Import, Parsed, Snippet, expect_end, line_items, parse_multiname, try_pop_no_fluff, + Comment, Import, ParseCtx, Parsed, Snippet, expect_end, line_items, parse_multiname, + try_pop_no_fluff, }; use orchid_base::tree::{Paren, TokTree, Token}; use substack::Substack; -use crate::atom::AtomHand; -use crate::macros::MacTree; +use crate::ctx::Ctx; +use crate::expr::{Expr, ExprKind, PathSetBuilder}; +use crate::parsed::{Item, ItemKind, ParsTokTree, ParsedMember, ParsedMemberKind, ParsedModule}; use crate::system::System; -use crate::tree::{Code, CodeLocator, Item, ItemKind, Member, MemberKind, Module, Rule, RuleKind}; -type ParsSnippet<'a> = Snippet<'a, 'static, AtomHand, Never>; +type ParsSnippet<'a> = Snippet<'a, Expr, Expr>; -pub struct ParseCtxImpl<'a> { +pub struct HostParseCtxImpl<'a> { + pub ctx: Ctx, pub systems: &'a [System], - pub reporter: &'a ReporterImpl, + pub reporter: &'a Reporter, + pub interner: &'a Interner, + pub consts: RefCell>>, } -impl ParseCtx for ParseCtxImpl<'_> { - fn reporter(&self) -> &(impl Reporter + ?Sized) { self.reporter } +impl ParseCtx for HostParseCtxImpl<'_> { + fn reporter(&self) -> &Reporter { self.reporter } + fn i(&self) -> &Interner { self.interner } +} + +impl HostParseCtx for HostParseCtxImpl<'_> { + fn ctx(&self) -> &Ctx { &self.ctx } fn systems(&self) -> impl Iterator { self.systems.iter() } + async fn save_const(&self, path: Substack<'_, Tok>, value: Vec) { + let name = Sym::new(path.unreverse(), self.interner).await.unwrap(); + self.consts.borrow_mut().insert(name, value); + } } -pub trait ParseCtx { +pub trait HostParseCtx: ParseCtx { + fn ctx(&self) -> &Ctx; fn systems(&self) -> impl Iterator; - fn reporter(&self) -> &(impl Reporter + ?Sized); + async fn save_const(&self, path: Substack<'_, Tok>, value: Vec); } pub async fn parse_items( - ctx: &impl ParseCtx, + ctx: &impl HostParseCtx, path: Substack<'_, Tok>, items: ParsSnippet<'_>, ) -> OrcRes> { - let lines = line_items(items).await; + let lines = line_items(ctx, items).await; let line_res = join_all(lines.into_iter().map(|p| parse_item(ctx, path.clone(), p.output, p.tail))).await; Ok(line_res.into_iter().flat_map(|l| l.ok().into_iter().flatten()).collect()) } pub async fn parse_item( - ctx: &impl ParseCtx, + ctx: &impl HostParseCtx, path: Substack<'_, Tok>, comments: Vec, item: ParsSnippet<'_>, ) -> OrcRes> { match item.pop_front() { Some((TokTree { tok: Token::Name(n), .. }, postdisc)) => match n { - n if *n == item.i().i("export").await => match try_pop_no_fluff(postdisc).await? { + n if *n == ctx.i().i("export").await => match try_pop_no_fluff(ctx, 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 } => { - let Parsed { output: exports, tail } = parse_multiname(ctx.reporter(), tail).await?; + Parsed { output: TokTree { tok: Token::S(Paren::Round, body), .. }, tail } => { + expect_end(ctx, tail).await?; let mut ok = Vec::new(); - for (e, pos) in exports { - match (&e.path[..], e.name) { - ([], Some(n)) => - ok.push(Item { comments: comments.clone(), pos, kind: ItemKind::Export(n) }), - (_, Some(_)) => ctx.reporter().report(mk_err( - tail.i().i("Compound export").await, + for tt in body { + let pos = Pos::Range(tt.range.clone()); + match &tt.tok { + Token::Name(n) => + ok.push(Item { comments: comments.clone(), pos, kind: ItemKind::Export(n.clone()) }), + Token::NS(..) => ctx.reporter().report(mk_err( + ctx.i().i("Compound export").await, "Cannot export compound names (names containing the :: separator)", [pos.into()], )), - (_, None) => ctx.reporter().report(mk_err( - tail.i().i("Wildcard export").await, - "Exports cannot contain the globstar *", + t => ctx.reporter().report(mk_err( + ctx.i().i("Invalid export").await, + format!("Invalid export target {}", fmt(t, ctx.i()).await), [pos.into()], )), } } - expect_end(tail).await?; + expect_end(ctx, tail).await?; Ok(ok) }, - Parsed { output, tail } => Err(mk_errv( - tail.i().i("Malformed export").await, - "`export` can either prefix other lines or list names inside ::( ) or ::[ ]", + Parsed { output, tail: _ } => Err(mk_errv( + ctx.i().i("Malformed export").await, + "`export` can either prefix other lines or list names inside ( )", [Pos::Range(output.range.clone()).into()], )), }, - 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 { + n if *n == ctx.i().i("import").await => { + let imports = parse_import(ctx, postdisc).await?; + Ok(Vec::from_iter(imports.into_iter().map(|(t, pos)| Item { comments: comments.clone(), pos, kind: ItemKind::Import(t), - })) - }), + }))) + }, n => parse_exportable_item(ctx, path, comments, false, n.clone(), postdisc).await, }, Some(_) => Err(mk_errv( - item.i().i("Expected a line type").await, + ctx.i().i("Expected a line type").await, "All lines must begin with a keyword", [Pos::Range(item.pos()).into()], )), @@ -105,37 +121,37 @@ pub async fn parse_item( } } -pub async fn parse_import( - ctx: &impl ParseCtx, - tail: ParsSnippet<'_>, +pub async fn parse_import<'a>( + ctx: &impl HostParseCtx, + tail: ParsSnippet<'a>, ) -> OrcRes> { - let Parsed { output: imports, tail } = parse_multiname(ctx.reporter(), tail).await?; - expect_end(tail).await?; + let Parsed { output: imports, tail } = parse_multiname(ctx, tail).await?; + expect_end(ctx, tail).await?; Ok(imports) } -pub async fn parse_exportable_item( - ctx: &impl ParseCtx, +pub async fn parse_exportable_item<'a>( + ctx: &impl HostParseCtx, path: Substack<'_, Tok>, comments: Vec, exported: bool, discr: Tok, - tail: ParsSnippet<'_>, + tail: ParsSnippet<'a>, ) -> OrcRes> { - let kind = if discr == tail.i().i("mod").await { + let path_sym = Sym::new(path.unreverse(), ctx.i()).await.expect("Files should have a namespace"); + let kind = if discr == ctx.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().i("const").await { - let (name, val) = parse_const(tail, path.clone()).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)))) + ItemKind::Member(ParsedMember::new(name, path_sym, ParsedMemberKind::Mod(body))) + } else if discr == ctx.i().i("const").await { + let name = parse_const(ctx, tail, path.clone()).await?; + ItemKind::Member(ParsedMember::new(name, path_sym, ParsedMemberKind::Const)) } 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.i())).await; + let line = sys.parse(path_sym, tail.to_vec(), exported, comments).await?; + return parse_items(ctx, path, Snippet::new(tail.prev(), &line)).await; } else { let ext_lines = ctx.systems().flat_map(System::line_types).join(", "); return Err(mk_errv( - tail.i().i("Unrecognized line type").await, + ctx.i().i("Unrecognized line type").await, format!("Line types are: const, mod, macro, grammar, {ext_lines}"), [Pos::Range(tail.prev().range.clone()).into()], )); @@ -143,175 +159,115 @@ pub async fn parse_exportable_item( Ok(vec![Item { comments, pos: Pos::Range(tail.pos()), kind }]) } -pub async fn parse_module( - ctx: &impl ParseCtx, +pub async fn parse_module<'a>( + ctx: &impl HostParseCtx, path: Substack<'_, Tok>, - tail: ParsSnippet<'_>, -) -> OrcRes<(Tok, Module)> { - let (name, tail) = match try_pop_no_fluff(tail).await? { + tail: ParsSnippet<'a>, +) -> OrcRes<(Tok, ParsedModule)> { + let (name, tail) = match try_pop_no_fluff(ctx, tail).await? { Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } => (n.clone(), tail), Parsed { output, .. } => { return Err(mk_errv( - tail.i().i("Missing module name").await, - format!("A name was expected, {} was found", tail.fmt(output).await), + ctx.i().i("Missing module name").await, + format!("A name was expected, {} was found", fmt(output, ctx.i()).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.i()) else { + let Parsed { output, tail: surplus } = try_pop_no_fluff(ctx, tail).await?; + expect_end(ctx, surplus).await?; + let Some(body) = output.as_s(Paren::Round) else { return Err(mk_errv( - tail.i().i("Expected module body").await, - format!("A ( block ) was expected, {} was found", tail.fmt(output).await), + ctx.i().i("Expected module body").await, + format!("A ( block ) was expected, {} was found", fmt(output, ctx.i()).await), [Pos::Range(output.range.clone()).into()], )); }; let path = path.push(name.clone()); - Ok((name, Module::new(parse_items(ctx, path, body).await?))) + Ok((name, ParsedModule::new(parse_items(ctx, path, body).await?))) } -pub async fn parse_const( - tail: ParsSnippet<'_>, +pub async fn parse_const<'a>( + ctx: &impl HostParseCtx, + tail: ParsSnippet<'a>, path: Substack<'_, Tok>, -) -> OrcRes<(Tok, Vec)> { - let Parsed { output, tail } = try_pop_no_fluff(tail).await?; +) -> OrcRes> { + let Parsed { output, tail } = try_pop_no_fluff(ctx, tail).await?; let Some(name) = output.as_name() else { return Err(mk_errv( - tail.i().i("Missing module name").await, - format!("A name was expected, {} was found", tail.fmt(output).await), + ctx.i().i("Missing module name").await, + format!("A name was expected, {} was found", fmt(output, ctx.i()).await), [Pos::Range(output.range.clone()).into()], )); }; - let Parsed { output, tail } = try_pop_no_fluff(tail).await?; - if !output.is_kw(tail.i().i("=").await) { + let Parsed { output, tail } = try_pop_no_fluff(ctx, tail).await?; + if !output.is_kw(ctx.i().i("=").await) { return Err(mk_errv( - tail.i().i("Missing = separator").await, - format!("Expected = , found {}", tail.fmt(output).await), + ctx.i().i("Missing = separator").await, + format!("Expected = , found {}", fmt(output, ctx.i()).await), [Pos::Range(output.range.clone()).into()], )); } - try_pop_no_fluff(tail).await?; - Ok((name, parse_mtree(tail, path).await?)) + try_pop_no_fluff(ctx, tail).await?; + ctx.save_const(path, tail[..].to_vec()).await; + Ok(name) } -pub async fn parse_mtree( - mut snip: ParsSnippet<'_>, - path: Substack<'_, Tok>, -) -> OrcRes> { - let mut mtreev = Vec::new(); - while let Some((ttree, tail)) = snip.pop_front() { - snip = tail; - let (range, tok, tail) = match &ttree.tok { - Token::S(p, b) => { - let b = parse_mtree(Snippet::new(ttree, b, snip.i()), path.clone()).boxed_local().await?; - (ttree.range.clone(), MTok::S(*p, b), tail) - }, - Token::Reference(name) => (ttree.range.clone(), MTok::Name(name.clone()), tail), - Token::Name(tok) => { - let mut segments = path.unreverse(); - segments.push(tok.clone()); - let mut end = ttree.range.end; - while let Some((TokTree { tok: Token::NS, .. }, tail)) = snip.pop_front() { - let Parsed { output, tail } = try_pop_no_fluff(tail).await?; - let Some(seg) = output.as_name() else { - return Err(mk_errv( - 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()], - )); - }; - segments.push(seg); - snip = tail; - end = output.range.end; - } - (ttree.range.start..end, MTok::Name(Sym::new(segments, snip.i()).await.unwrap()), snip) - }, - Token::NS => { - return Err(mk_errv( - tail.i().i("Unexpected :: in expression").await, - ":: can only follow a name", - [Pos::Range(ttree.range.clone()).into()], - )); - }, - Token::Ph(ph) => (ttree.range.clone(), MTok::Ph(ph.clone()), tail), - Token::Macro(_) => { - return Err(mk_errv( - tail.i().i("Invalid keyword in expression").await, - "Expressions cannot use `macro` as a name.", - [Pos::Range(ttree.range.clone()).into()], - )); - }, - Token::Atom(a) => (ttree.range.clone(), MTok::Atom(a.clone()), tail), - Token::BR | Token::Comment(_) => continue, - Token::Bottom(e) => return Err(e.clone()), - Token::LambdaHead(arg) => ( - ttree.range.start..snip.pos().end, - MTok::Lambda( - parse_mtree(Snippet::new(ttree, arg, snip.i()), path.clone()).boxed_local().await?, - parse_mtree(tail, path.clone()).boxed_local().await?, - ), - Snippet::new(ttree, &[], snip.i()), - ), - Token::Slot(_) | Token::X(_) => - 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; - } - Ok(mtreev) -} - -pub async fn parse_macro( +pub async fn parse_expr( + ctx: &impl HostParseCtx, + path: Sym, + psb: PathSetBuilder<'_, Tok>, tail: ParsSnippet<'_>, - macro_i: u16, - path: Substack<'_, Tok>, -) -> OrcRes> { - let (surplus, prev, block) = match try_pop_no_fluff(tail).await? { - Parsed { tail, output: o @ TokTree { tok: Token::S(Paren::Round, b), .. } } => (tail, o, b), - Parsed { output, .. } => { - return Err(mk_errv( - tail.i().i("m").await, - "Macro blocks must either start with a block or a ..$:number", - [Pos::Range(output.range.clone()).into()], - )); - }, +) -> OrcRes { + let Some((last_idx, _)) = (tail.iter().enumerate().find(|(_, tt)| tt.as_lambda().is_some())) + .or_else(|| tail.iter().enumerate().rev().find(|(_, tt)| !tt.is_fluff())) + else { + return Err(mk_errv(ctx.i().i("Empty expression").await, "Expression ends abruptly here", [ + Pos::Range(tail.pos()).into(), + ])); }; - 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.i())).await.into_iter().enumerate() { - let Parsed { tail, output } = try_pop_no_fluff(item.tail).await?; - if !output.is_kw(tail.i().i("rule").await) { - errors.extend(mk_errv( - 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().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().i("no => in macro rule").await, - "The pattern and body of a rule must be separated by a =>", - [Pos::Range(tail.pos()).into()], - )); - continue; - }, - }; - rules.push(Rule { - comments: item.output, - pos: Pos::Range(tail.pos()), - pattern: parse_mtree(pat, path.clone()).await?, - kind: RuleKind::Native(Code::from_code( - CodeLocator::to_rule(tail.i().i(&path.unreverse()).await, macro_i, i as u16), - parse_mtree(body, path.clone()).await?, - )), - }) + let (function, value) = tail.split_at(last_idx as u32); + let pos = Pos::Range(tail.pos()); + if !function.iter().all(TokTree::is_fluff) { + let (f_psb, x_psb) = psb.split(); + let x_expr = parse_expr(ctx, path.clone(), x_psb, value).boxed_local().await?; + let f_expr = parse_expr(ctx, path, f_psb, function).boxed_local().await?; + return Ok(ExprKind::Call(f_expr, x_expr).at(pos)); + } + let Parsed { output: head, tail } = try_pop_no_fluff(ctx, value).await?; + match &head.tok { + Token::BR | Token::Comment(_) => panic!("Fluff skipped"), + Token::Bottom(b) => Ok(ExprKind::Bottom(b.clone()).at(pos.clone())), + Token::Handle(expr) => Ok(expr.clone()), + Token::NS(n, nametail) => { + let mut nametail = nametail; + let mut segments = path.iter().chain([n]).cloned().collect_vec(); + while let Token::NS(n, newtail) = &nametail.tok { + segments.push(n.clone()); + nametail = newtail; + } + let Token::Name(n) = &nametail.tok else { + return Err(mk_errv( + ctx.i().i("Loose namespace prefix in constant").await, + "Namespace prefixes in constants must be followed by names", + [pos.into()], + )); + }; + segments.push(n.clone()); + Ok(ExprKind::Const(Sym::new(segments, ctx.i()).await.unwrap()).at(pos.clone())) + }, + Token::LambdaHead(h) => { + let [TokTree { tok: Token::Name(arg), .. }] = &h[..] else { + return Err(mk_errv( + ctx.i().i("Complex lambda binding in constant").await, + "Lambda args in constants must be identified by a single name", + [pos.into()], + )); + }; + let lambda_builder = psb.lambda(arg); + let body = parse_expr(ctx, path.clone(), lambda_builder.stack(), tail).boxed_local().await?; + Ok(ExprKind::Lambda(lambda_builder.collect(), body).at(pos.clone())) + }, + _ => todo!("AAAAAA"), // TODO: todo } - if let Ok(e) = OrcErrv::new(errors) { Err(e) } else { Ok(rules) } } diff --git a/orchid-host/src/parsed.rs b/orchid-host/src/parsed.rs new file mode 100644 index 0000000..e4d4a41 --- /dev/null +++ b/orchid-host/src/parsed.rs @@ -0,0 +1,383 @@ +use std::cell::RefCell; +use std::fmt::Debug; +use std::rc::Rc; + +use async_once_cell::OnceCell; +use async_std::sync::{Mutex, RwLock}; +use async_stream::stream; +use futures::future::join_all; +use futures::{FutureExt, StreamExt}; +use hashbrown::HashMap; +use itertools::Itertools; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; +use orchid_base::interner::Tok; +use orchid_base::location::Pos; +use orchid_base::name::{NameLike, Sym}; +use orchid_base::parse::{Comment, Import}; +use orchid_base::tl_cache; +use orchid_base::tree::{TokTree, Token, TokenVariant}; + +use crate::api; +use crate::ctx::Ctx; +use crate::expr::{Expr, ExprParseCtx, PathSetBuilder}; +use crate::expr_store::ExprStore; +use crate::system::System; + +pub type ParsTokTree = TokTree; +pub type ParsTok = Token; + +impl TokenVariant for Expr { + type ToApiCtx<'a> = ExprStore; + async fn into_api(self, ctx: &mut Self::ToApiCtx<'_>) -> api::ExprTicket { + ctx.give_expr(self.clone()); + self.id() + } + type FromApiCtx<'a> = ExprStore; + async fn from_api( + api: &api::ExprTicket, + ctx: &mut Self::FromApiCtx<'_>, + _: Pos, + _: &orchid_base::interner::Interner, + ) -> Self { + let expr = ctx.get_expr(*api).expect("Dangling expr"); + ctx.take_expr(*api); + expr + } +} + +impl TokenVariant for Expr { + type FromApiCtx<'a> = ExprParseCtx; + async fn from_api( + api: &api::Expression, + ctx: &mut Self::FromApiCtx<'_>, + _: Pos, + _: &orchid_base::interner::Interner, + ) -> Self { + Expr::from_api(api, PathSetBuilder::new(), ctx).await + } + type ToApiCtx<'a> = (); + async fn into_api(self, (): &mut Self::ToApiCtx<'_>) -> api::Expression { + panic!("Failed to replace NewExpr before returning sublexer value") + } +} + +pub struct ParsedFromApiCx<'a> { + pub sys: &'a System, + pub consts: &'a mut HashMap, + pub path: Tok>>, +} +impl<'a> ParsedFromApiCx<'a> { + pub async fn push<'c>(&'c mut self, name: Tok) -> ParsedFromApiCx<'c> { + let path = self.sys.ctx().i.i(&self.path.iter().cloned().chain([name]).collect_vec()).await; + ParsedFromApiCx { path, consts: &mut *self.consts, sys: self.sys } + } +} + +#[derive(Debug)] +pub struct Item { + pub pos: Pos, + pub comments: Vec, + pub kind: ItemKind, +} + +#[derive(Debug)] +pub enum ItemKind { + Member(ParsedMember), + Export(Tok), + Import(Import), +} +impl ItemKind { + pub fn at(self, pos: Pos) -> Item { Item { comments: vec![], pos, kind: self } } +} + +impl Item { + pub async fn from_api<'a>(tree: api::Item, ctx: &mut ParsedFromApiCx<'a>) -> Self { + let kind = match tree.kind { + api::ItemKind::Member(m) => ItemKind::Member(ParsedMember::from_api(m, ctx).await), + api::ItemKind::Import(name) => ItemKind::Import(Import { + path: Sym::from_api(name, &ctx.sys.ctx().i).await.iter().collect(), + name: None, + }), + api::ItemKind::Export(e) => ItemKind::Export(Tok::from_api(e, &ctx.sys.ctx().i).await), + }; + let mut comments = Vec::new(); + for comment in tree.comments.iter() { + comments.push(Comment::from_api(comment, &ctx.sys.ctx().i).await) + } + Self { pos: Pos::from_api(&tree.location, &ctx.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::Member(mem) => match mem.kind.get() { + None => format!("lazy {}", mem.name).into(), + Some(ParsedMemberKind::Const) => + tl_cache!(Rc: Rc::new(Variants::default().bounded("const {0}"))) + .units([mem.name.rc().into()]), + Some(ParsedMemberKind::Mod(module)) => + tl_cache!(Rc: Rc::new(Variants::default().bounded("module {0} {{\n\t{1}\n}}"))) + .units([mem.name.rc().into(), module.print(c).boxed_local().await]), + }, + }; + tl_cache!(Rc: Rc::new(Variants::default().bounded("{0}\n{1}"))) + .units([comment_text.into(), item_text]) + } +} + +pub struct ParsedMember { + name: Tok, + full_name: Sym, + kind: OnceCell, + lazy: Mutex>, +} +// TODO: this one should own but not execute the lazy handle. +// Lazy handles should run +// - in the tree converter function as needed to resolve imports +// - in the tree itself when a constant is loaded +// - when a different lazy subtree references them in a wildcard import and +// forces the enumeration. +// +// do we actually need to allow wildcard imports in lazy trees? maybe a +// different kind of import is sufficient. Source code never becomes a lazy +// tree. What does? +// - Systems subtrees rarely reference each other at all. They can't use macros +// and they usually point to constants with an embedded expr. +// - Compiled libraries on the long run. The code as written may reference +// constants by indirect path. But this is actually the same as the above, +// they also wouldn't use regular imports because they are distributed as +// exprs. +// +// Everything is distributed either as source code or as exprs. Line parsers +// also operate on tokens. +// +// TODO: The trees produced by systems can be safely changed +// to the new kind of tree. This datastructure does not need to support the lazy +// handle. +impl ParsedMember { + pub fn name(&self) -> Tok { self.name.clone() } + pub async fn kind(&self, consts: &mut HashMap) -> &ParsedMemberKind { + (self.kind.get_or_init(async { + let handle = self.lazy.lock().await.take().expect("Neither known nor lazy"); + handle.run(consts).await + })) + .await + } + pub async fn kind_mut(&mut self, consts: &mut HashMap) -> &mut ParsedMemberKind { + self.kind(consts).await; + self.kind.get_mut().expect("kind() already filled the cell") + } + pub async fn from_api<'a>(api: api::Member, ctx: &'_ mut ParsedFromApiCx<'a>) -> Self { + let name = Tok::from_api(api.name, &ctx.sys.ctx().i).await; + let mut ctx: ParsedFromApiCx<'_> = (&mut *ctx).push(name.clone()).await; + let path_sym = Sym::from_tok(ctx.path.clone()).expect("We just pushed on to this"); + let kind = match api.kind { + api::MemberKind::Lazy(id) => { + let handle = LazyMemberHandle { id, sys: ctx.sys.clone(), path: path_sym.clone() }; + return handle.into_member(name.clone()).await; + }, + api::MemberKind::Const(c) => { + let mut pctx = + ExprParseCtx { ctx: ctx.sys.ctx().clone(), exprs: ctx.sys.ext().exprs().clone() }; + let expr = Expr::from_api(&c, PathSetBuilder::new(), &mut pctx).await; + ctx.consts.insert(path_sym.clone(), expr); + ParsedMemberKind::Const + }, + api::MemberKind::Module(m) => + ParsedMemberKind::Mod(ParsedModule::from_api(m, &mut ctx).await), + }; + ParsedMember { name, full_name: path_sym, kind: OnceCell::from(kind), lazy: Mutex::default() } + } + pub fn new(name: Tok, full_name: Sym, kind: ParsedMemberKind) -> Self { + ParsedMember { name, full_name, kind: OnceCell::from(kind), lazy: Mutex::default() } + } +} +impl Debug for ParsedMember { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Member") + .field("name", &self.name) + .field("kind", &self.kind) + .finish_non_exhaustive() + } +} + +#[derive(Debug)] +pub enum ParsedMemberKind { + Const, + Mod(ParsedModule), +} + +#[derive(Debug, Default)] +pub struct ParsedModule { + pub imports: Vec, + pub exports: Vec>, + pub items: Vec, +} +impl ParsedModule { + pub fn new(items: impl IntoIterator) -> Self { + let items = items.into_iter().collect_vec(); + let exports = (items.iter()) + .filter_map(|i| match &i.kind { + ItemKind::Export(e) => Some(e.clone()), + _ => None, + }) + .collect_vec(); + Self { imports: vec![], exports, items } + } + pub fn merge(&mut self, other: ParsedModule) { + let mut swap = ParsedModule::default(); + std::mem::swap(self, &mut swap); + *self = ParsedModule::new(swap.items.into_iter().chain(other.items)) + } + pub async fn from_api<'a>(m: api::Module, ctx: &mut ParsedFromApiCx<'a>) -> Self { + Self::new( + stream! { for item in m.items { yield Item::from_api(item, ctx).boxed_local().await } } + .collect::>() + .await, + ) + } + pub async fn walk<'a>( + &self, + allow_private: bool, + path: impl IntoIterator>, + consts: &mut HashMap, + ) -> Result<&ParsedModule, WalkError> { + let mut cur = self; + for (pos, step) in path.into_iter().enumerate() { + let Some(member) = (cur.items.iter()) + .filter_map(|it| if let ItemKind::Member(m) = &it.kind { Some(m) } else { None }) + .find(|m| m.name == step) + else { + return Err(WalkError { pos, kind: WalkErrorKind::Missing }); + }; + if !allow_private && !cur.exports.contains(&step) { + return Err(WalkError { pos, kind: WalkErrorKind::Private }); + } + match member.kind(consts).await { + ParsedMemberKind::Const => return Err(WalkError { pos, kind: WalkErrorKind::Constant }), + ParsedMemberKind::Mod(m) => cur = m, + } + } + Ok(cur) + } +} +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum WalkErrorKind { + Missing, + Private, + Constant, +} +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct WalkError { + pub pos: usize, + pub kind: WalkErrorKind, +} +impl Format for ParsedModule { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + let import_str = self.imports.iter().map(|i| format!("import {i}")).join("\n"); + let head_str = format!("{import_str}\nexport ::({})\n", self.exports.iter().join(", ")); + Variants::sequence(self.items.len() + 1, "\n", None).units( + [head_str.into()].into_iter().chain(join_all(self.items.iter().map(|i| i.print(c))).await), + ) + } +} + +pub struct LazyMemberHandle { + id: api::TreeId, + sys: System, + path: Sym, +} +impl LazyMemberHandle { + pub async fn run(self, consts: &mut HashMap) -> ParsedMemberKind { + match self.sys.get_tree(self.id).await { + api::MemberKind::Const(c) => { + let mut pctx = + ExprParseCtx { ctx: self.sys.ctx().clone(), exprs: self.sys.ext().exprs().clone() }; + consts.insert(self.path, Expr::from_api(&c, PathSetBuilder::new(), &mut pctx).await); + ParsedMemberKind::Const + }, + api::MemberKind::Module(m) => ParsedMemberKind::Mod( + ParsedModule::from_api(m, &mut ParsedFromApiCx { + sys: &self.sys, + consts, + path: self.path.tok(), + }) + .await, + ), + api::MemberKind::Lazy(id) => Self { id, ..self }.run(consts).boxed_local().await, + } + } + pub async fn into_member(self, name: Tok) -> ParsedMember { + ParsedMember { + name, + full_name: self.path.clone(), + kind: OnceCell::new(), + lazy: Mutex::new(Some(self)), + } + } +} + +/// TODO: +/// +/// idea, does the host need an IR here or can we figure out a way to transcribe +/// these? Should we spin off a new stage for value parsing so that ParsTokTree +/// doesn't appear in the interpreter's ingress? +pub struct Const { + pub source: Option>, +} + +/// Selects a code element +/// +/// Either the steps point to a constant and rule_loc is None, or the steps +/// point to a module and rule_loc selects a macro rule within that module +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct ConstPath { + steps: Tok>>, +} +impl ConstPath { + pub fn to_const(steps: Tok>>) -> Self { Self { steps } } +} + +#[derive(Clone)] +pub struct Root { + tree: Rc, + consts: Rc>>, +} +impl Root { + pub fn new(module: ParsedModule, consts: HashMap) -> Self { + Self { tree: Rc::new(module), consts: Rc::new(RwLock::new(consts)) } + } + pub async fn get_const_value(&self, name: Sym, pos: Pos, ctx: Ctx) -> OrcRes { + if let Some(val) = self.consts.read().await.get(&name) { + return Ok(val.clone()); + } + let (cn, mp) = name.split_last(); + let consts_mut = &mut *self.consts.write().await; + let module = self.tree.walk(true, mp.iter().cloned(), consts_mut).await.unwrap(); + let member = (module.items.iter()) + .filter_map(|it| if let ItemKind::Member(m) = &it.kind { Some(m) } else { None }) + .find(|m| m.name() == cn); + match member { + None => Err(mk_errv( + ctx.i.i("Constant does not exist").await, + format!("{name} does not refer to a constant"), + [pos.clone().into()], + )), + Some(mem) => match mem.kind(consts_mut).await { + ParsedMemberKind::Mod(_) => Err(mk_errv( + ctx.i.i("module used as constant").await, + format!("{name} is a module, not a constant"), + [pos.clone().into()], + )), + ParsedMemberKind::Const => Ok( + (consts_mut.get(&name).cloned()) + .expect("Tree says the path is correct but no value was found"), + ), + }, + } + } +} diff --git a/orchid-host/src/system.rs b/orchid-host/src/system.rs index 912e9b2..780ab3b 100644 --- a/orchid-host/src/system.rs +++ b/orchid-host/src/system.rs @@ -1,7 +1,7 @@ use std::collections::VecDeque; +use std::fmt; use std::future::Future; use std::rc::{Rc, Weak}; -use std::{fmt, mem}; use async_stream::stream; use derive_destructure::destructure; @@ -13,8 +13,9 @@ 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::interner::{Interner, Tok}; use orchid_base::location::Pos; +use orchid_base::name::Sym; use orchid_base::parse::Comment; use orchid_base::reqnot::{ReqNot, Requester}; use orchid_base::tree::ttv_from_api; @@ -23,8 +24,9 @@ use substack::{Stackframe, Substack}; use crate::api; use crate::ctx::Ctx; +use crate::expr::{Expr, ExprParseCtx}; use crate::extension::{Extension, WeakExtension}; -use crate::tree::{ItemKind, Member, Module, ParsTokTree, Root}; +use crate::parsed::{ItemKind, ParsedMember, ParsedModule, ParsTokTree, ParsedFromApiCx, Root}; #[derive(destructure)] struct SystemInstData { @@ -55,6 +57,7 @@ impl System { pub fn id(&self) -> api::SysId { self.0.id } pub fn ext(&self) -> &Extension { &self.0.ext } pub fn ctx(&self) -> &Ctx { &self.0.ctx } + pub fn i(&self) -> &Interner { &self.0.ctx.i } pub(crate) fn reqnot(&self) -> &ReqNot { self.0.ext.reqnot() } pub async fn get_tree(&self, id: api::TreeId) -> api::MemberKind { self.reqnot().request(api::GetMember(self.0.id, id)).await @@ -75,15 +78,23 @@ impl System { pub fn line_types(&self) -> impl Iterator> + '_ { self.0.line_types.iter() } pub async fn parse( &self, + module: Sym, line: Vec, exported: bool, comments: Vec, ) -> OrcRes> { - let line = - join_all(line.iter().map(|t| async { t.to_api(&mut async |n, _| match *n {}).await })).await; + let line = join_all(line.into_iter().map(|t| async { + let mut expr_store = self.0.ext.exprs().clone(); + t.into_api(&mut expr_store, &mut ()).await + })) + .await; let comments = comments.iter().map(Comment::to_api).collect_vec(); - match self.reqnot().request(api::ParseLine { exported, sys: self.id(), comments, line }).await { - Ok(parsed) => Ok(ttv_from_api(parsed, &mut self.ctx().clone(), &self.ctx().i).await), + let req = api::ParseLine { module: module.to_api(), exported, sys: self.id(), comments, line }; + match self.reqnot().request(req).await { + Ok(parsed) => { + let mut pctx = ExprParseCtx { ctx: self.ctx().clone(), exprs: self.ext().exprs().clone() }; + Ok(ttv_from_api(parsed, &mut self.ext().exprs().clone(), &mut pctx, self.i()).await) + }, Err(e) => Err(OrcErrv::from_api(&e, &self.ctx().i).await), } } @@ -122,7 +133,11 @@ impl SystemCtor { self.decl.depends.iter().map(|s| &**s) } pub fn id(&self) -> api::SysDeclId { self.decl.id } - pub async fn run<'a>(&self, depends: impl IntoIterator) -> (Module, System) { + pub async fn run<'a>( + &self, + depends: impl IntoIterator, + consts: &mut HashMap, + ) -> (ParsedModule, System) { let depends = depends.into_iter().map(|si| si.id()).collect_vec(); debug_assert_eq!(depends.len(), self.decl.depends.len(), "Wrong number of deps provided"); let ext = self.ext.upgrade().expect("SystemCtor should be freed before Extension"); @@ -139,10 +154,13 @@ impl SystemCtor { })); let const_root = clone!(data, ext; stream! { for (k, v) in sys_inst.const_root { - yield Member::from_api( + yield ParsedMember::from_api( api::Member { name: k, kind: v }, - &mut vec![Tok::from_api(k, &ext.ctx().i).await], - &data, + &mut ParsedFromApiCx { + consts, + path: ext.ctx().i.i(&[]).await, + sys: &data, + } ).await; } }) @@ -150,7 +168,7 @@ impl SystemCtor { .collect::>() .await; ext.ctx().systems.write().await.insert(id, data.downgrade()); - let root = Module::new(const_root); + let root = ParsedModule::new(const_root); (root, data) } } @@ -182,6 +200,7 @@ pub async fn init_systems( fn walk_deps<'a>( graph: &mut HashMap<&str, &'a SystemCtor>, list: &mut Vec<&'a SystemCtor>, + consts: &mut HashMap, chain: Stackframe<&str>, ) -> Result<(), SysResolvErr> { if let Some(ctor) = graph.remove(chain.item) { @@ -193,21 +212,22 @@ pub async fn init_systems( circle.extend(Substack::Frame(chain).iter().map(|s| s.to_string())); return Err(SysResolvErr::Loop(circle)); } - walk_deps(graph, list, Substack::Frame(chain).new_frame(dep))? + walk_deps(graph, list, consts, Substack::Frame(chain).new_frame(dep))? } list.push(ctor); } Ok(()) } + let mut consts = HashMap::new(); for tgt in tgts { - walk_deps(&mut to_load, &mut to_load_ordered, Substack::Bottom.new_frame(tgt))?; + walk_deps(&mut to_load, &mut to_load_ordered, &mut consts, Substack::Bottom.new_frame(tgt))?; } let mut systems = HashMap::<&str, System>::new(); - let mut root = Module::default(); + let mut root_mod = ParsedModule::default(); for ctor in to_load_ordered.iter() { - let (sys_root, sys) = ctor.run(ctor.depends().map(|n| &systems[n])).await; + let (sys_root, sys) = ctor.run(ctor.depends().map(|n| &systems[n]), &mut consts).await; systems.insert(ctor.name(), sys); - root.merge(sys_root); + root_mod.merge(sys_root); } - Ok((Root::new(root), systems.into_values().collect_vec())) + Ok((Root::new(root_mod, consts), systems.into_values().collect_vec())) } diff --git a/orchid-host/src/tree.rs b/orchid-host/src/tree.rs index cb55f67..dbf4952 100644 --- a/orchid-host/src/tree.rs +++ b/orchid-host/src/tree.rs @@ -1,374 +1,59 @@ -use std::fmt::Debug; -use std::rc::Rc; +use std::cell::RefCell; +use std::rc::{Rc, Weak}; use async_once_cell::OnceCell; -use async_std::sync::{Mutex, RwLock}; -use async_stream::stream; -use futures::future::join_all; -use futures::{FutureExt, StreamExt}; -use itertools::Itertools; -use never::Never; -use orchid_base::error::{OrcRes, mk_errv}; -use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; +use hashbrown::HashMap; use orchid_base::interner::Tok; -use orchid_base::location::Pos; -use orchid_base::macros::{mtreev_fmt, mtreev_from_api}; -use orchid_base::name::{NameLike, Sym}; -use orchid_base::parse::{Comment, Import}; -use orchid_base::tree::{AtomRepr, TokTree, Token}; -use orchid_base::{clone, tl_cache}; -use ordered_float::NotNan; -use substack::Substack; +use orchid_base::name::Sym; -use crate::api; -use crate::atom::AtomHand; -use crate::ctx::Ctx; -use crate::expr::{Expr, mtreev_to_expr}; -use crate::macros::{MacTok, MacTree}; -use crate::system::System; +use crate::expr::Expr; +use crate::parsed::{LazyMemberHandle, ParsedMemberKind, ParsedModule}; -pub type ParsTokTree = TokTree<'static, AtomHand, Never>; -pub type ParsTok = Token<'static, AtomHand, Never>; +pub struct Tree(Rc); -#[derive(Debug)] -pub struct Item { - pub pos: Pos, - pub comments: Vec, - pub kind: ItemKind, +pub struct WeakTree(Weak); + +pub struct Module { + pub members: HashMap, Rc>, } - -#[derive(Debug)] -pub enum ItemKind { - Member(Member), - Export(Tok), - Import(Import), - Macro(Option>, Vec), -} -impl ItemKind { - pub fn at(self, pos: Pos) -> Item { Item { comments: vec![], pos, kind: self } } -} - -impl Item { - pub async fn from_api(tree: api::Item, path: &mut Vec>, sys: &System) -> Self { - let kind = match tree.kind { - api::ItemKind::Member(m) => ItemKind::Member(Member::from_api(m, path, sys).await), - api::ItemKind::Import(name) => ItemKind::Import(Import { - path: Sym::from_api(name, &sys.ctx().i).await.iter().collect(), - name: None, - }), - api::ItemKind::Export(e) => ItemKind::Export(Tok::from_api(e, &sys.ctx().i).await), - api::ItemKind::Macro(macro_block) => { - let mut rules = Vec::new(); - for rule in macro_block.rules { - let mut comments = Vec::new(); - for comment in rule.comments { - comments.push(Comment::from_api(&comment, &sys.ctx().i).await); - } - let pos = Pos::from_api(&rule.location, &sys.ctx().i).await; - let pattern = mtreev_from_api(&rule.pattern, &sys.ctx().i, &mut { - clone!(pos, sys); - async move |a| { - MacTok::Atom(AtomHand::from_api(a, pos.clone(), &mut sys.ctx().clone()).await) - } - }) - .await; - rules.push(Rule { pos, pattern, kind: RuleKind::Remote(sys.clone(), rule.id), comments }); - } - ItemKind::Macro(macro_block.priority, rules) - }, - }; - let mut comments = Vec::new(); - for comment in tree.comments.iter() { - comments.push(Comment::from_api(comment, &sys.ctx().i).await) - } - 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)]), - ItemKind::Member(mem) => match mem.kind.get() { - None => format!("lazy {}", mem.name).into(), - Some(MemberKind::Const(val)) => - tl_cache!(Rc: Rc::new(Variants::default().bounded("const {0} = {1}"))) - .units([mem.name.rc().into(), val.print(c).await]), - Some(MemberKind::Mod(module)) => - tl_cache!(Rc: Rc::new(Variants::default().bounded("module {0} {{\n\t{1}\n}}"))) - .units([mem.name.rc().into(), module.print(c).boxed_local().await]), - }, - _ => panic!(), - }; - tl_cache!(Rc: Rc::new(Variants::default().bounded("{0}\n{1}"))) - .units([comment_text.into(), item_text]) +impl Module { + async fn from_parsed(parsed: &ParsedModule, root: &ParsedModule) -> Self { + let imports = } } pub struct Member { - name: Tok, - kind: OnceCell, - lazy: Mutex>, + pub public: bool, + pub root: WeakTree, + pub canonical_path: Sym, + pub lazy: RefCell)>>, + pub kind: OnceCell, } impl Member { - pub fn name(&self) -> Tok { self.name.clone() } - pub async fn kind(&self) -> &MemberKind { + pub async fn kind_mut(&mut self, consts: &mut HashMap) -> &mut MemberKind { + self.kind(consts).await; + self.kind.get_mut().expect("Thhe above line should have initialized it") + } + pub async fn kind(&self, consts: &mut HashMap) -> &MemberKind { (self.kind.get_or_init(async { - let handle = self.lazy.lock().await.take().expect("Neither known nor lazy"); - handle.run().await + let (handle, root) = + self.lazy.borrow_mut().take().expect("If kind is uninit, lazy must be Some"); + let parsed = handle.run(consts).await; + MemberKind::from_parsed(&parsed, &root).await })) .await } - pub async fn kind_mut(&mut self) -> &mut MemberKind { - self.kind().await; - self.kind.get_mut().expect("kind() already filled the cell") - } - pub async fn from_api(api: api::Member, path: &mut Vec>, sys: &System) -> Self { - path.push(Tok::from_api(api.name, &sys.ctx().i).await); - let kind = match api.kind { - api::MemberKind::Lazy(id) => { - let handle = LazyMemberHandle(id, sys.clone(), path.clone()); - return handle.into_member(path.pop().unwrap()); - }, - api::MemberKind::Const(c) => MemberKind::Const(Code::from_expr( - CodeLocator::to_const(sys.ctx().i.i(&*path).await), - Expr::from_api(&c, &mut sys.ext().clone()).await, - )), - api::MemberKind::Module(m) => MemberKind::Mod(Module::from_api(m, path, sys).await), - }; - let name = path.pop().unwrap(); - Member { name, kind: OnceCell::from(kind), lazy: Mutex::default() } - } - pub fn new(name: Tok, kind: MemberKind) -> Self { - Member { name, kind: OnceCell::from(kind), lazy: Mutex::default() } - } -} -impl Debug for Member { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Member") - .field("name", &self.name) - .field("kind", &self.kind) - .finish_non_exhaustive() - } } -#[derive(Debug)] pub enum MemberKind { - Const(Code), - Mod(Module), + Const, + Module(Module), } - -#[derive(Debug, Default)] -pub struct Module { - pub imports: Vec, - pub exports: Vec>, - pub items: Vec, -} -impl Module { - pub fn new(items: impl IntoIterator) -> Self { - let items = items.into_iter().collect_vec(); - let exports = (items.iter()) - .filter_map(|i| match &i.kind { - ItemKind::Export(e) => Some(e.clone()), - _ => None, - }) - .collect_vec(); - Self { imports: vec![], exports, items } - } - pub fn merge(&mut self, other: Module) { - let mut swap = Module::default(); - std::mem::swap(self, &mut swap); - *self = Module::new(swap.items.into_iter().chain(other.items)) - } - pub async fn from_api(m: api::Module, path: &mut Vec>, sys: &System) -> Self { - Self::new( - stream! { for item in m.items { yield Item::from_api(item, path, sys).boxed_local().await } } - .collect::>() - .await, - ) - } - pub async fn walk( - &self, - allow_private: bool, - path: impl IntoIterator>, - ) -> Result<&Module, WalkError> { - let mut cur = self; - for (pos, step) in path.into_iter().enumerate() { - let Some(member) = (cur.items.iter()) - .filter_map(|it| if let ItemKind::Member(m) = &it.kind { Some(m) } else { None }) - .find(|m| m.name == step) - else { - return Err(WalkError { pos, kind: WalkErrorKind::Missing }); - }; - if !allow_private && !cur.exports.contains(&step) { - return Err(WalkError { pos, kind: WalkErrorKind::Private }); - } - match member.kind().await { - MemberKind::Const(_) => return Err(WalkError { pos, kind: WalkErrorKind::Constant }), - MemberKind::Mod(m) => cur = m, - } - } - Ok(cur) - } -} -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub enum WalkErrorKind { - Missing, - Private, - Constant, -} -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct WalkError { - pub pos: usize, - pub kind: WalkErrorKind, -} -impl Format for Module { - async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { - let import_str = self.imports.iter().map(|i| format!("import {i}")).join("\n"); - let head_str = format!("{import_str}\nexport ::({})\n", self.exports.iter().join(", ")); - Variants::sequence(self.items.len() + 1, "\n", None).units( - [head_str.into()].into_iter().chain(join_all(self.items.iter().map(|i| i.print(c))).await), - ) - } -} - -pub struct LazyMemberHandle(api::TreeId, System, Vec>); -impl LazyMemberHandle { - pub async fn run(self) -> MemberKind { - match self.1.get_tree(self.0).await { - api::MemberKind::Const(c) => MemberKind::Const(Code { - bytecode: Expr::from_api(&c, &mut self.1.ext().clone()).await.into(), - locator: CodeLocator { steps: self.1.ctx().i.i(&self.2).await, rule_loc: None }, - source: None, - }), - api::MemberKind::Module(m) => - MemberKind::Mod(Module::from_api(m, &mut { self.2 }, &self.1).await), - api::MemberKind::Lazy(id) => Self(id, self.1, self.2).run().boxed_local().await, - } - } - pub fn into_member(self, name: Tok) -> Member { - Member { name, kind: OnceCell::new(), lazy: Mutex::new(Some(self)) } - } -} - -#[derive(Debug)] -pub struct Rule { - pub pos: Pos, - pub comments: Vec, - 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 { - Remote(System, api::MacroId), - Native(Code), -} - -#[derive(Debug)] -pub struct Code { - locator: CodeLocator, - source: Option>, - bytecode: OnceCell, -} -impl Code { - pub fn from_expr(locator: CodeLocator, expr: Expr) -> Self { - Self { locator, source: None, bytecode: expr.into() } - } - pub fn from_code(locator: CodeLocator, code: Vec) -> Self { - Self { locator, source: Some(code), bytecode: OnceCell::new() } - } - pub fn source(&self) -> Option<&Vec> { self.source.as_ref() } - pub fn set_source(&mut self, source: Vec) { - self.source = Some(source); - self.bytecode = OnceCell::new(); - } - pub async fn get_bytecode(&self, ctx: &Ctx) -> &Expr { - (self.bytecode.get_or_init(async { - let src = self.source.as_ref().expect("no bytecode or source"); - mtreev_to_expr(src, Substack::Bottom, ctx).await.at(Pos::None) - })) - .await - } -} -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 mtreev_fmt(src, c).await; - } - panic!("Code must be initialized with at least one state") - } -} - -/// Selects a code element -/// -/// Either the steps point to a constant and rule_loc is None, or the steps -/// point to a module and rule_loc selects a macro rule within that module -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct CodeLocator { - steps: Tok>>, - /// Index of a macro block in the module demarked by the steps, and a rule in - /// that macro - rule_loc: Option<(u16, u16)>, -} -impl CodeLocator { - pub fn to_const(steps: Tok>>) -> Self { Self { steps, rule_loc: None } } - pub fn to_rule(steps: Tok>>, macro_i: u16, rule_i: u16) -> Self { - Self { steps, rule_loc: Some((macro_i, rule_i)) } - } -} - -#[derive(Clone)] -pub struct Root(Rc>); -impl Root { - pub fn new(module: Module) -> Self { Self(Rc::new(RwLock::new(module))) } - pub async fn get_const_value(&self, name: impl NameLike, pos: Pos, ctx: Ctx) -> OrcRes { - let (cn, mp) = name.split_last(); - let root_lock = self.0.read().await; - let module = root_lock.walk(true, mp.iter().cloned()).await.unwrap(); - let member = (module.items.iter()) - .filter_map(|it| if let ItemKind::Member(m) = &it.kind { Some(m) } else { None }) - .find(|m| m.name() == cn); - match member { - None => Err(mk_errv( - ctx.i.i("Constant does not exist").await, - format!("{name} does not refer to a constant"), - [pos.clone().into()], - )), - Some(mem) => match mem.kind().await { - MemberKind::Mod(_) => Err(mk_errv( - ctx.i.i("module used as constant").await, - format!("{name} is a module, not a constant"), - [pos.clone().into()], - )), - MemberKind::Const(c) => Ok((c.get_bytecode(&ctx).await).clone()), - }, +impl MemberKind { + async fn from_parsed(parsed: &ParsedMemberKind, root: &ParsedModule) -> Self { + match parsed { + ParsedMemberKind::Const => MemberKind::Const, + ParsedMemberKind::Mod(m) => MemberKind::Module(Module::from_parsed(m, root).await), } } } diff --git a/orchid-macros/Cargo.toml b/orchid-macros/Cargo.toml new file mode 100644 index 0000000..8a3314d --- /dev/null +++ b/orchid-macros/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "orchid-macros" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/orchid-macros/src/main.rs b/orchid-macros/src/main.rs new file mode 100644 index 0000000..a30eb95 --- /dev/null +++ b/orchid-macros/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/orchid-std/Cargo.toml b/orchid-std/Cargo.toml index 4fb3210..94b437b 100644 --- a/orchid-std/Cargo.toml +++ b/orchid-std/Cargo.toml @@ -1,12 +1,14 @@ [package] name = "orchid-std" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] async-once-cell = "0.5.4" async-std = "1.13.0" +async-stream = "0.3.6" futures = "0.3.31" +hashbrown = "0.15.2" itertools = "0.14.0" never = "0.1.0" once_cell = "1.20.2" @@ -20,3 +22,6 @@ orchid-extension = { version = "0.1.0", path = "../orchid-extension", features = ordered-float = "5.0.0" rust_decimal = "1.36.0" tokio = { version = "1.43.0", features = ["full"] } + +[dev-dependencies] +test_executors = "0.3.2" diff --git a/orchid-std/src/lib.rs b/orchid-std/src/lib.rs index 70b8111..b90601b 100644 --- a/orchid-std/src/lib.rs +++ b/orchid-std/src/lib.rs @@ -1,7 +1,6 @@ -mod number; +mod macros; mod std; -mod string; -pub use std::StdSystem; - -pub use string::str_atom::OrcString; +pub use std::number::num_atom::{Float, HomoArray, Int, Num}; +pub use std::std_system::StdSystem; +pub use std::string::str_atom::OrcString; diff --git a/orchid-std/src/macros/macro_system.rs b/orchid-std/src/macros/macro_system.rs new file mode 100644 index 0000000..4a6cf61 --- /dev/null +++ b/orchid-std/src/macros/macro_system.rs @@ -0,0 +1,32 @@ +use never::Never; +use orchid_base::reqnot::Receipt; +use orchid_extension::atom::AtomDynfo; +use orchid_extension::entrypoint::ExtReq; +use orchid_extension::fs::DeclFs; +use orchid_extension::lexer::LexerObj; +use orchid_extension::parser::ParserObj; +use orchid_extension::system::{System, SystemCard}; +use orchid_extension::system_ctor::SystemCtor; +use orchid_extension::tree::GenItem; + +#[derive(Default)] +pub struct MacroSystem; +impl SystemCtor for MacroSystem { + type Deps = (); + type Instance = Self; + const NAME: &'static str = "macros"; + const VERSION: f64 = 0.00_01; + fn inst() -> Option { Some(Self) } +} +impl SystemCard for MacroSystem { + type Ctor = Self; + type Req = Never; + fn atoms() -> impl IntoIterator>> { [] } +} +impl System for MacroSystem { + async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { match req {} } + fn vfs() -> orchid_extension::fs::DeclFs { DeclFs::Mod(&[]) } + fn lexers() -> Vec { vec![] } + fn parsers() -> Vec { vec![] } + fn env() -> Vec { vec![] } +} diff --git a/orchid-std/src/macros/mactree.rs b/orchid-std/src/macros/mactree.rs new file mode 100644 index 0000000..987e78a --- /dev/null +++ b/orchid-std/src/macros/mactree.rs @@ -0,0 +1,75 @@ +use std::borrow::Cow; +use std::rc::Rc; + +use futures::future::join_all; +use orchid_api::Paren; +use orchid_base::error::OrcErrv; +use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; +use orchid_base::location::Pos; +use orchid_base::name::Sym; +use orchid_base::tl_cache; +use orchid_base::tree::Ph; +use orchid_extension::atom::{Atomic, MethodSetBuilder}; +use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant}; +use orchid_extension::expr::Expr; + +#[derive(Debug, Clone)] +pub struct MacTree { + pub pos: Pos, + pub tok: Rc, +} +impl MacTree {} +impl Atomic for MacTree { + type Data = (); + type Variant = OwnedVariant; +} +impl OwnedAtom for MacTree { + type Refs = (); + + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + self.tok.print(c).await + } +} + +#[derive(Debug, Clone)] +pub enum MacTok { + S(Paren, Vec), + Name(Sym), + /// Only permitted in arguments to `instantiate_tpl` + Slot, + Value(Expr), + Lambda(Vec, Vec), + Ph(Ph), +} +impl Format for MacTok { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + match self { + Self::Value(v) => v.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::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 => format!("SLOT").into(), + } + } +} + +pub async fn mtreev_fmt<'b>( + 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-std/src/macros/mactree_lexer.rs b/orchid-std/src/macros/mactree_lexer.rs new file mode 100644 index 0000000..02c95aa --- /dev/null +++ b/orchid-std/src/macros/mactree_lexer.rs @@ -0,0 +1,64 @@ +use std::ops::RangeInclusive; +use std::rc::Rc; + +use futures::FutureExt; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::location::Pos; +use orchid_base::parse::name_start; +use orchid_base::tokens::PARENS; +use orchid_extension::atom::AtomicFeatures; +use orchid_extension::gen_expr::atom; +use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; +use orchid_extension::tree::{GenTok, GenTokTree}; + +use crate::macros::mactree::{MacTok, MacTree}; + +#[derive(Default)] +pub struct MacTreeLexer; +impl Lexer for MacTreeLexer { + const CHAR_FILTER: &'static [RangeInclusive] = &['\''..='\'']; + async fn lex<'a>(tail: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree<'a>)> { + let Some(tail2) = tail.strip_prefix('\'') else { + return Err(err_not_applicable(ctx.i).await.into()); + }; + let tail3 = tail2.trim_start(); + return match mac_tree(tail3, ctx).await { + Ok((tail4, mactree)) => + Ok((tail4, GenTok::X(mactree.factory()).at(ctx.pos(tail)..ctx.pos(tail4)))), + Err(e) => Ok((tail2, GenTok::Bottom(e).at(ctx.tok_ran(1, tail2)))), + }; + async fn mac_tree<'a>(tail: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, MacTree)> { + for (lp, rp, paren) in PARENS { + let Some(mut body_tail) = tail.strip_prefix(*lp) else { continue }; + let mut items = Vec::new(); + return loop { + let tail2 = body_tail.trim(); + if let Some(tail3) = tail2.strip_prefix(*rp) { + break Ok((tail3, MacTree { + pos: Pos::Range(ctx.pos(tail)..ctx.pos(tail3)), + tok: Rc::new(MacTok::S(*paren, items)), + })); + } else if tail2.is_empty() { + return Err(mk_errv( + ctx.i.i("Unclosed block").await, + format!("Expected closing {rp}"), + [Pos::Range(ctx.tok_ran(1, tail)).into()], + )); + } + let (new_tail, new_item) = mac_tree(tail2, ctx).boxed_local().await?; + body_tail = new_tail; + items.push(new_item); + }; + } + const INTERPOL: &[&str] = &["$", "..$"]; + for pref in INTERPOL { + let Some(code) = tail.strip_prefix(pref) else { continue }; + } + return Err(mk_errv( + ctx.i.i("Expected token after '").await, + format!("Expected a token after ', found {tail:?}"), + [Pos::Range(ctx.tok_ran(1, tail)).into()], + )); + } + } +} diff --git a/orchid-std/src/macros/mod.rs b/orchid-std/src/macros/mod.rs new file mode 100644 index 0000000..1c4d9c1 --- /dev/null +++ b/orchid-std/src/macros/mod.rs @@ -0,0 +1,6 @@ +mod macro_system; +pub mod mactree; +mod mactree_lexer; +mod rule; + +use mactree::{MacTok, MacTree}; diff --git a/orchid-host/src/rule/any_match.rs b/orchid-std/src/macros/rule/any_match.rs similarity index 95% rename from orchid-host/src/rule/any_match.rs rename to orchid-std/src/macros/rule/any_match.rs index 70caf98..6a94576 100644 --- a/orchid-host/src/rule/any_match.rs +++ b/orchid-std/src/macros/rule/any_match.rs @@ -2,9 +2,9 @@ use orchid_base::name::Sym; use super::scal_match::scalv_match; use super::shared::AnyMatcher; +use super::state::MatchState; use super::vec_match::vec_match; use crate::macros::MacTree; -use crate::rule::state::MatchState; #[must_use] pub fn any_match<'a>( diff --git a/orchid-host/src/rule/build.rs b/orchid-std/src/macros/rule/build.rs similarity index 96% rename from orchid-host/src/rule/build.rs rename to orchid-std/src/macros/rule/build.rs index 917973d..d38f015 100644 --- a/orchid-host/src/rule/build.rs +++ b/orchid-std/src/macros/rule/build.rs @@ -5,8 +5,8 @@ use orchid_base::side::Side; use orchid_base::tree::Ph; use super::shared::{AnyMatcher, ScalMatcher, VecMatcher}; +use super::vec_attrs::vec_attrs; use crate::macros::{MacTok, MacTree}; -use crate::rule::vec_attrs::vec_attrs; pub type MaxVecSplit<'a> = (&'a [MacTree], (Tok, u8, bool), &'a [MacTree]); @@ -91,7 +91,6 @@ pub fn mk_vec(pattern: &[MacTree]) -> VecMatcher { #[must_use] fn mk_scalar(pattern: &MacTree) -> ScalMatcher { match &*pattern.tok { - MacTok::Atom(_) | MacTok::Done(_) => panic!("Atoms and Done aren't supported in matchers"), MacTok::Name(n) => ScalMatcher::Name(n.clone()), MacTok::Ph(Ph { name, kind }) => match kind { PhKind::Vector { .. } => { @@ -101,7 +100,7 @@ fn mk_scalar(pattern: &MacTree) -> ScalMatcher { }, 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::Ref(_) | MacTok::Slot(_) => panic!("Extension-only variants"), + MacTok::Value(_) | MacTok::Slot => panic!("Only used for templating"), } } diff --git a/orchid-host/src/rule/matcher.rs b/orchid-std/src/macros/rule/matcher.rs similarity index 91% rename from orchid-host/src/rule/matcher.rs rename to orchid-std/src/macros/rule/matcher.rs index 94e7a88..de962c1 100644 --- a/orchid-host/src/rule/matcher.rs +++ b/orchid-std/src/macros/rule/matcher.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::rc::Rc; use itertools::Itertools; use orchid_api::PhKind; @@ -8,13 +9,12 @@ use orchid_base::name::Sym; use orchid_base::tree::Ph; use super::any_match::any_match; -use super::build::mk_any; +use super::build::{mk_any, mk_vec}; use super::shared::{AnyMatcher, VecMatcher}; use super::state::{MatchState, StateEntry}; use super::vec_attrs::vec_attrs; use super::vec_match::vec_match; use crate::macros::{MacTok, MacTree}; -use crate::rule::build::mk_vec; pub fn first_is_vec(pattern: &[MacTree]) -> bool { vec_attrs(pattern.first().unwrap()).is_some() } pub fn last_is_vec(pattern: &[MacTree]) -> bool { vec_attrs(pattern.last().unwrap()).is_some() } @@ -30,8 +30,9 @@ impl NamedMatcher { match last_is_vec(pattern) { true => Self(mk_any(pattern)), false => { - let kind: PhKind = PhKind::Vector { priority: 0, at_least_one: false }; - let suffix = [MacTok::Ph(Ph { name: i.i("::after").await, kind }).at(Pos::None)]; + 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())) }, } diff --git a/orchid-host/src/rule/mod.rs b/orchid-std/src/macros/rule/mod.rs similarity index 100% rename from orchid-host/src/rule/mod.rs rename to orchid-std/src/macros/rule/mod.rs diff --git a/orchid-host/src/rule/scal_match.rs b/orchid-std/src/macros/rule/scal_match.rs similarity index 92% rename from orchid-host/src/rule/scal_match.rs rename to orchid-std/src/macros/rule/scal_match.rs index 2c6a775..ac2d6e4 100644 --- a/orchid-host/src/rule/scal_match.rs +++ b/orchid-std/src/macros/rule/scal_match.rs @@ -2,8 +2,8 @@ use orchid_base::name::Sym; use super::any_match::any_match; use super::shared::ScalMatcher; +use super::state::{MatchState, StateEntry}; use crate::macros::{MacTok, MacTree}; -use crate::rule::state::{MatchState, StateEntry}; #[must_use] pub fn scal_match<'a>( @@ -16,7 +16,6 @@ pub fn scal_match<'a>( true => MatchState::from_name(n1.clone(), expr.pos.clone()), false => MatchState::default(), }), - (ScalMatcher::Placeh { .. }, MacTok::Done(_)) => None, (ScalMatcher::Placeh { key }, _) => Some(MatchState::from_ph(key.clone(), StateEntry::Scalar(expr))), (ScalMatcher::S(c1, b_mat), MacTok::S(c2, body)) if c1 == c2 => diff --git a/orchid-host/src/rule/shared.rs b/orchid-std/src/macros/rule/shared.rs similarity index 100% rename from orchid-host/src/rule/shared.rs rename to orchid-std/src/macros/rule/shared.rs diff --git a/orchid-host/src/rule/state.rs b/orchid-std/src/macros/rule/state.rs similarity index 95% rename from orchid-host/src/rule/state.rs rename to orchid-std/src/macros/rule/state.rs index f0e66e9..7c91c9a 100644 --- a/orchid-host/src/rule/state.rs +++ b/orchid-std/src/macros/rule/state.rs @@ -40,7 +40,9 @@ impl<'a> MatchState<'a> { pub fn combine(self, s: Self) -> Self { Self { placeholders: self.placeholders.into_iter().chain(s.placeholders).collect(), - name_posv: join_maps(self.name_posv, s.name_posv, |_, l, r| l.into_iter().chain(r).collect()), + name_posv: join_maps::<_, Vec>(self.name_posv, s.name_posv, |_, l, r| { + l.into_iter().chain(r).collect() + }), } } pub fn ph_len(&self, key: &Tok) -> Option { diff --git a/orchid-host/src/rule/vec_attrs.rs b/orchid-std/src/macros/rule/vec_attrs.rs similarity index 100% rename from orchid-host/src/rule/vec_attrs.rs rename to orchid-std/src/macros/rule/vec_attrs.rs diff --git a/orchid-host/src/rule/vec_match.rs b/orchid-std/src/macros/rule/vec_match.rs similarity index 98% rename from orchid-host/src/rule/vec_match.rs rename to orchid-std/src/macros/rule/vec_match.rs index 32f9cf6..2dffd61 100644 --- a/orchid-host/src/rule/vec_match.rs +++ b/orchid-std/src/macros/rule/vec_match.rs @@ -5,8 +5,8 @@ use orchid_base::name::Sym; use super::scal_match::scalv_match; use super::shared::VecMatcher; +use super::state::{MatchState, StateEntry}; use crate::macros::MacTree; -use crate::rule::state::{MatchState, StateEntry}; #[must_use] pub fn vec_match<'a>( diff --git a/orchid-std/src/std.rs b/orchid-std/src/std.rs deleted file mode 100644 index 821e4a1..0000000 --- a/orchid-std/src/std.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::rc::Rc; - -use never::Never; -use orchid_base::number::Numeric; -use orchid_base::reqnot::Receipt; -use orchid_extension::atom::{AtomDynfo, AtomicFeatures}; -use orchid_extension::entrypoint::ExtReq; -use orchid_extension::fs::DeclFs; -use orchid_extension::system::{System, SystemCard}; -use orchid_extension::system_ctor::SystemCtor; -use orchid_extension::tree::{MemKind, comments, fun, module, root_mod}; -use ordered_float::NotNan; - -use crate::OrcString; -use crate::number::num_atom::{Float, HomoArray, Int, Num}; -use crate::number::num_lexer::NumLexer; -use crate::string::str_atom::{IntStrAtom, StrAtom}; -use crate::string::str_lexer::StringLexer; - -#[derive(Default)] -pub struct StdSystem; -impl SystemCtor for StdSystem { - type Deps = (); - type Instance = Self; - const NAME: &'static str = "orchid::std"; - const VERSION: f64 = 0.00_01; - fn inst() -> Option { Some(StdSystem) } -} -impl SystemCard for StdSystem { - type Ctor = Self; - type Req = Never; - fn atoms() -> impl IntoIterator>> { - [Some(Int::dynfo()), Some(Float::dynfo()), Some(StrAtom::dynfo()), Some(IntStrAtom::dynfo())] - } -} -impl System for StdSystem { - async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { match req {} } - fn lexers() -> Vec { vec![&StringLexer, &NumLexer] } - fn parsers() -> Vec { vec![] } - fn vfs() -> DeclFs { DeclFs::Mod(&[]) } - fn env() -> Vec<(String, MemKind)> { - vec![root_mod("std", [], [ - module(true, "string", [], [comments( - ["Concatenate two strings"], - fun(true, "concat", |left: OrcString<'static>, right: OrcString<'static>| async move { - StrAtom::new(Rc::new(left.get_string().await.to_string() + &right.get_string().await)) - }), - )]), - module(true, "number", [], [ - fun(true, "add", |a: Num, b: Num| async move { - Num(match HomoArray::new([a.0, b.0]) { - HomoArray::Int([a, b]) => Numeric::Int(a + b), - HomoArray::Float([a, b]) => Numeric::Float(a + b), - }) - }), - fun(true, "neg", |a: Num| async move { - Num(match a.0 { - Numeric::Int(i) => Numeric::Int(-i), - Numeric::Float(f) => Numeric::Float(-f), - }) - }), - fun(true, "mul", |a: Num, b: Num| async move { - Num(match HomoArray::new([a.0, b.0]) { - HomoArray::Int([a, b]) => Numeric::Int(a * b), - HomoArray::Float([a, b]) => Numeric::Float(a * b), - }) - }), - fun(true, "idiv", |a: Int, b: Int| async move { Int(a.0 / b.0) }), - fun(true, "imod", |a: Int, b: Int| async move { Int(a.0 % b.0) }), - fun(true, "fdiv", |a: Float, b: Float| async move { Float(a.0 / b.0) }), - fun(true, "fmod", |a: Float, b: Float| async move { - Float(a.0 - NotNan::new((a.0 / b.0).trunc()).unwrap() * b.0) - }), - ]), - ])] - } -} diff --git a/orchid-std/src/std/mod.rs b/orchid-std/src/std/mod.rs new file mode 100644 index 0000000..17b8ee9 --- /dev/null +++ b/orchid-std/src/std/mod.rs @@ -0,0 +1,4 @@ +pub mod number; +pub mod string; + +pub mod std_system; diff --git a/orchid-std/src/number/mod.rs b/orchid-std/src/std/number/mod.rs similarity index 68% rename from orchid-std/src/number/mod.rs rename to orchid-std/src/std/number/mod.rs index 836ef48..1177a53 100644 --- a/orchid-std/src/number/mod.rs +++ b/orchid-std/src/std/number/mod.rs @@ -1,2 +1,3 @@ pub mod num_atom; pub mod num_lexer; +pub mod num_lib; diff --git a/orchid-std/src/number/num_atom.rs b/orchid-std/src/std/number/num_atom.rs similarity index 94% rename from orchid-std/src/number/num_atom.rs rename to orchid-std/src/std/number/num_atom.rs index c64bcde..9cb9586 100644 --- a/orchid-std/src/number/num_atom.rs +++ b/orchid-std/src/std/number/num_atom.rs @@ -17,7 +17,6 @@ pub struct Int(pub i64); impl Atomic for Int { type Variant = ThinVariant; type Data = Self; - fn reg_reqs() -> MethodSetBuilder { MethodSetBuilder::new() } } impl ThinAtom for Int { async fn print(&self, _: SysCtx) -> FmtUnit { self.0.to_string().into() } @@ -33,7 +32,6 @@ pub struct Float(pub NotNan); impl Atomic for Float { type Variant = ThinVariant; type Data = Self; - fn reg_reqs() -> MethodSetBuilder { MethodSetBuilder::new() } } impl ThinAtom for Float { async fn print(&self, _: SysCtx) -> FmtUnit { self.0.to_string().into() } diff --git a/orchid-std/src/number/num_lexer.rs b/orchid-std/src/std/number/num_lexer.rs similarity index 100% rename from orchid-std/src/number/num_lexer.rs rename to orchid-std/src/std/number/num_lexer.rs diff --git a/orchid-std/src/std/number/num_lib.rs b/orchid-std/src/std/number/num_lib.rs new file mode 100644 index 0000000..cff5454 --- /dev/null +++ b/orchid-std/src/std/number/num_lib.rs @@ -0,0 +1,34 @@ +use orchid_base::number::Numeric; +use orchid_extension::tree::{GenItem, fun, prefix}; +use ordered_float::NotNan; + +use super::num_atom::{Float, HomoArray, Int, Num}; + +pub fn gen_num_lib() -> Vec { + prefix("std::number", [ + fun(true, "add", |a: Num, b: Num| async move { + Num(match HomoArray::new([a.0, b.0]) { + HomoArray::Int([a, b]) => Numeric::Int(a + b), + HomoArray::Float([a, b]) => Numeric::Float(a + b), + }) + }), + fun(true, "neg", |a: Num| async move { + Num(match a.0 { + Numeric::Int(i) => Numeric::Int(-i), + Numeric::Float(f) => Numeric::Float(-f), + }) + }), + fun(true, "mul", |a: Num, b: Num| async move { + Num(match HomoArray::new([a.0, b.0]) { + HomoArray::Int([a, b]) => Numeric::Int(a * b), + HomoArray::Float([a, b]) => Numeric::Float(a * b), + }) + }), + fun(true, "idiv", |a: Int, b: Int| async move { Int(a.0 / b.0) }), + fun(true, "imod", |a: Int, b: Int| async move { Int(a.0 % b.0) }), + fun(true, "fdiv", |a: Float, b: Float| async move { Float(a.0 / b.0) }), + fun(true, "fmod", |a: Float, b: Float| async move { + Float(a.0 - NotNan::new((a.0 / b.0).trunc()).unwrap() * b.0) + }), + ]) +} diff --git a/orchid-std/src/std/std_system.rs b/orchid-std/src/std/std_system.rs new file mode 100644 index 0000000..186bd77 --- /dev/null +++ b/orchid-std/src/std/std_system.rs @@ -0,0 +1,41 @@ +use never::Never; +use orchid_base::reqnot::Receipt; +use orchid_extension::atom::{AtomDynfo, AtomicFeatures}; +use orchid_extension::entrypoint::ExtReq; +use orchid_extension::fs::DeclFs; +use orchid_extension::lexer::LexerObj; +use orchid_extension::parser::ParserObj; +use orchid_extension::system::{System, SystemCard}; +use orchid_extension::system_ctor::SystemCtor; +use orchid_extension::tree::{GenItem, merge_trivial}; + +use super::number::num_lib::gen_num_lib; +use super::string::str_atom::{IntStrAtom, StrAtom}; +use super::string::str_lib::gen_str_lib; +use crate::std::number::num_lexer::NumLexer; +use crate::std::string::str_lexer::StringLexer; +use crate::{Float, Int}; + +#[derive(Default)] +pub struct StdSystem; +impl SystemCtor for StdSystem { + type Deps = (); + type Instance = Self; + const NAME: &'static str = "orchid::std"; + const VERSION: f64 = 0.00_01; + fn inst() -> Option { Some(Self) } +} +impl SystemCard for StdSystem { + type Ctor = Self; + type Req = Never; + fn atoms() -> impl IntoIterator>> { + [Some(Int::dynfo()), Some(Float::dynfo()), Some(StrAtom::dynfo()), Some(IntStrAtom::dynfo())] + } +} +impl System for StdSystem { + async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { match req {} } + fn lexers() -> Vec { vec![&StringLexer, &NumLexer] } + fn parsers() -> Vec { vec![] } + fn vfs() -> DeclFs { DeclFs::Mod(&[]) } + fn env() -> Vec { merge_trivial([gen_num_lib(), gen_str_lib()]) } +} diff --git a/orchid-std/src/string/mod.rs b/orchid-std/src/std/string/mod.rs similarity index 68% rename from orchid-std/src/string/mod.rs rename to orchid-std/src/std/string/mod.rs index fc7bea3..47ae88e 100644 --- a/orchid-std/src/string/mod.rs +++ b/orchid-std/src/std/string/mod.rs @@ -1,2 +1,3 @@ pub mod str_atom; pub mod str_lexer; +pub mod str_lib; diff --git a/orchid-std/src/string/str_atom.rs b/orchid-std/src/std/string/str_atom.rs similarity index 92% rename from orchid-std/src/string/str_atom.rs rename to orchid-std/src/std/string/str_atom.rs index 157392c..e2b4762 100644 --- a/orchid-std/src/string/str_atom.rs +++ b/orchid-std/src/std/string/str_atom.rs @@ -7,7 +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::format::{FmtCtx, FmtUnit}; use orchid_base::interner::Tok; use orchid_extension::atom::{AtomMethod, Atomic, MethodSetBuilder, Supports, TypAtom}; use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; @@ -49,7 +49,9 @@ impl OwnedAtom for StrAtom { async fn serialize(&self, _: SysCtx, sink: Pin<&mut (impl Write + ?Sized)>) -> Self::Refs { self.deref().encode(sink).await } - async fn print(&self, _: SysCtx) -> FmtUnit { format!("{:?}", &*self.0).into() } + async fn print<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + format!("{:?}", &*self.0).into() + } async fn deserialize(mut ctx: impl DeserializeCtx, _: Self::Refs) -> Self { Self::new(Rc::new(ctx.read::().await)) } @@ -60,7 +62,6 @@ pub struct IntStrAtom(Tok); impl Atomic for IntStrAtom { type Variant = OwnedVariant; type Data = orchid_api::TStr; - fn reg_reqs() -> MethodSetBuilder { MethodSetBuilder::new() } } impl From> for IntStrAtom { fn from(value: Tok) -> Self { Self(value) } @@ -68,7 +69,9 @@ 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) -> FmtUnit { format!("{:?}i", *self.0).into() } + async fn print<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> 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/orchid-std/src/string/str_lexer.rs b/orchid-std/src/std/string/str_lexer.rs similarity index 95% rename from orchid-std/src/string/str_lexer.rs rename to orchid-std/src/std/string/str_lexer.rs index 6899e0d..76a27d2 100644 --- a/orchid-std/src/string/str_lexer.rs +++ b/orchid-std/src/std/string/str_lexer.rs @@ -94,11 +94,9 @@ fn parse_string(str: &str) -> Result { #[derive(Default)] pub struct StringLexer; impl Lexer for StringLexer { - const CHAR_FILTER: &'static [std::ops::RangeInclusive] = &['"'..='"', '\''..='\'']; + const CHAR_FILTER: &'static [std::ops::RangeInclusive] = &['"'..='"', '`'..='`']; async fn lex<'a>(all: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree<'a>)> { - let Some((mut tail, delim)) = (all.strip_prefix('"').map(|t| (t, '"'))) - .or_else(|| all.strip_prefix('\'').map(|t| (t, '\''))) - else { + let Some(mut tail) = all.strip_prefix('"') else { return Err(err_not_applicable(ctx.i).await.into()); }; let mut ret = None; @@ -125,7 +123,7 @@ impl Lexer for StringLexer { wrap_tokv([concat_fn, prev, new]) }; loop { - if let Some(rest) = tail.strip_prefix(delim) { + if let Some(rest) = tail.strip_prefix('"') { return Ok((rest, add_frag(ret, str_to_gen(&mut cur, tail, &mut errors, ctx).await).await)); } else if let Some(rest) = tail.strip_prefix('$') { ret = Some(add_frag(ret, str_to_gen(&mut cur, tail, &mut errors, ctx).await).await); diff --git a/orchid-std/src/std/string/str_lib.rs b/orchid-std/src/std/string/str_lib.rs new file mode 100644 index 0000000..d1244ac --- /dev/null +++ b/orchid-std/src/std/string/str_lib.rs @@ -0,0 +1,15 @@ +use std::rc::Rc; + +use orchid_extension::tree::{GenItem, comments, fun, prefix}; + +use super::str_atom::StrAtom; +use crate::OrcString; + +pub fn gen_str_lib() -> Vec { + prefix("std::string", [comments( + ["Concatenate two strings"], + fun(true, "concat", |left: OrcString<'static>, right: OrcString<'static>| async move { + StrAtom::new(Rc::new(left.get_string().await.to_string() + &right.get_string().await)) + }), + )]) +} diff --git a/orchidlang/Cargo.toml b/orchidlang/Cargo.toml index 0b9caac..03c0523 100644 --- a/orchidlang/Cargo.toml +++ b/orchidlang/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "orchidlang" version = "0.3.0" -edition = "2021" +edition = "2024" license = "GPL-3.0" repository = "https://github.com/lbfalvy/orchid" description = """ diff --git a/orcx/Cargo.toml b/orcx/Cargo.toml index 979bc98..7d28610 100644 --- a/orcx/Cargo.toml +++ b/orcx/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "orcx" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/orcx/src/main.rs b/orcx/src/main.rs index a0a7288..f47f633 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -22,7 +22,7 @@ use orchid_host::execute::{ExecCtx, ExecResult}; use orchid_host::expr::mtreev_to_expr; use orchid_host::extension::Extension; use orchid_host::lex::lex; -use orchid_host::parse::{ParseCtxImpl, parse_items, parse_mtree}; +use orchid_host::parse::{HostParseCtxImpl, parse_items, parse_mtree}; use orchid_host::subprocess::ext_command; use orchid_host::system::init_systems; use substack::Substack; @@ -117,7 +117,7 @@ async fn main() -> io::Result { return; }; let reporter = ReporterImpl::new(); - let pctx = ParseCtxImpl { reporter: &reporter, systems: &systems }; + let pctx = HostParseCtxImpl { reporter: &reporter, systems: &systems }; let snip = Snippet::new(first, &lexemes, i); let ptree = parse_items(&pctx, Substack::Bottom, snip).await.unwrap(); if let Some(errv) = reporter.errv() { diff --git a/stdio-perftest/Cargo.toml b/stdio-perftest/Cargo.toml index 7ffe607..67bfa8d 100644 --- a/stdio-perftest/Cargo.toml +++ b/stdio-perftest/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "stdio-perftest" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 7a6258c..f3d9514 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "xtask" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] clap = { version = "4.5.24", features = ["derive"] }