diff --git a/.zed/settings.json b/.zed/settings.json index 3ecbd3f..95696cd 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -1,7 +1,8 @@ { "languages": { "Rust": { - "language_servers": ["rust-analyzer", "..."] + "language_servers": ["rust-analyzer", "..."], + "formatter": "language_server" } }, "wrap_guides": [100], @@ -11,8 +12,11 @@ "path": "C:\\Users\\z004yk5r\\.cargo\\bin\\rust-analyzer.exe" }, "initialization_options": { - "checkOnSave": { + "check": { "command": "clippy" + }, + "rustfmt": { + "extraArgs": ["+nightly"] } } } diff --git a/Cargo.lock b/Cargo.lock index c2e9a3c..6b651b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,9 +477,9 @@ checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -515,6 +515,7 @@ dependencies = [ name = "orchid-api-traits" version = "0.1.0" dependencies = [ + "itertools", "never", "ordered-float", ] @@ -529,6 +530,7 @@ dependencies = [ "itertools", "lazy_static", "never", + "num-traits", "orchid-api", "orchid-api-derive", "orchid-api-traits", @@ -571,12 +573,14 @@ dependencies = [ "itertools", "lazy_static", "never", + "num-traits", "orchid-api", "orchid-api-traits", "orchid-base", "ordered-float", "paste", "substack", + "trait-set", ] [[package]] @@ -925,9 +929,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "substack" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffccc3d80f0a489de67aa74ff31ab852abb973e1c6dacf3704889e00ca544e7f" +checksum = "26ce98c74d8476dd7b8515495625bc1bd4449b50f4926ac030964976e035ed53" [[package]] name = "syn" diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index 4f078ae..19abd51 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -1,35 +1,2 @@ -const main := println "Hello World!" exit_status::success - -macro ( - rule match ...$expr { ...$body } => '( - fn::pass (...$expr) \match::value. ...$( - fn::pass (quote::split body ';) \cases. - fn::pass (list::map cases \x. ( - fn::pass (quote::split_once x '=>) \pair. - tuple::destr pair 2 \req. \handler. - fn::pass (macro::run '(match::request (...$key))) \match_res. - quote::match '(match::response $decoder (...$bindings)) match_res \match_res_match. - fn::pass (option::expect match_res_match "Invalid pattern ${key}") \res_parts. - fn::pass (map::get_unwrap res_parts "decoder") \decoder. - fn::pass (map::get_unwrap res_parts "bindings") \bindings. - fn::pass (quote::to_list bindings) \binding_names. - fn::pass (list::rfold handler \tail. \name. '( \ $name . $tail )) \success. - '( $decoder $success ) - )) \case_fns. - list::append case_fns '( panic "No cases match" ) - ) - ) -) - ---[ - Conceptually, all matches are compared. - 1. Within a macro, the top rule wins - 2. If two winning matches are not within the same macro, - the one that matches the outermost, first token wins, - including tokens that are immediately captured, such that a rule starting with .. is - beaten by the same rule parenthesized and any rule starting with a scalar is beaten - by the same rule prefixed with a vectorial - 3. If two winning matches start with the same token, an ambiguity error is raised - - -]-- +const user := "dave" +const main := println "Hello $user!" exit_status::success diff --git a/lex-hello.ps1 b/lex-hello.ps1 index ebc72c3..941a68f 100644 Binary files a/lex-hello.ps1 and b/lex-hello.ps1 differ diff --git a/notes/new_macro_model.md b/notes/new_macro_model.md new file mode 100644 index 0000000..0144ca2 --- /dev/null +++ b/notes/new_macro_model.md @@ -0,0 +1,45 @@ +# Code sample for the new macro system + +``` +macro ( + rule match ...$expr { ...$body } => \recurse. '( + fn::pass (...$expr) \match::value. ...$( + fn::pass (quote::split body ';) \cases. + fn::pass (list::map cases \x. ( + fn::pass (quote::split_once x '=>) \pair. + tuple::destr pair 2 \req. \handler. + fn::pass (recurse '(match::request (...$key))) \match_res. + quote::match '(match::response $decoder (...$bindings)) match_res \match_res_match. + fn::pass (option::expect match_res_match "Invalid pattern ${key}") \res_parts. + fn::pass (map::get_unwrap res_parts "decoder") \decoder. + fn::pass (map::get_unwrap res_parts "bindings") \bindings. + fn::pass (quote::to_list bindings) \binding_names. + fn::pass (list::rfold handler \tail. \name. '( \ $name . $tail )) \success. + '( $decoder $success ) + )) \case_fns. + list::append case_fns '( panic "No cases match" ) + ) + ) +) + +--[ + Macros are run from the top down. + For every token + 1. If it's a name token, test all macros starting with that name + 2. If none match and this is the first token in a list, test all macros starting with vectorials + Test all in a set of macros + 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 single match is in the recursion stack, raise a recursion error + 4. Add the matching rule to the recursion stack, then execute the body. +]-- + +--[ + 1. Macro patterns are held in the host, they don't contain atoms, and atoms are never considered equal, so the matcher doesn't have to call an extension. + 2. The body of macros may be defined in Rust. If it isn't, the entire interpreter will recurse on the macro to calculate the output. +]-- + +--[ + 1. if the rule body uses the same macro, fail with the rule + 2. if the rule explicitly recursively invokes the same macro, fail with the first match +]-- \ No newline at end of file diff --git a/orchid-api-traits/Cargo.toml b/orchid-api-traits/Cargo.toml index 33daea9..7862deb 100644 --- a/orchid-api-traits/Cargo.toml +++ b/orchid-api-traits/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +itertools = "0.13.0" never = "0.1.0" ordered-float = "4.2" diff --git a/orchid-api-traits/src/api_conv.rs b/orchid-api-traits/src/api_conv.rs new file mode 100644 index 0000000..3f842e2 --- /dev/null +++ b/orchid-api-traits/src/api_conv.rs @@ -0,0 +1,26 @@ +pub trait ApiEquiv { + type Api; +} + +pub trait ToApi: Sized + ApiEquiv { + type Ctx; + fn to_api(&self, ctx: &mut Self::Ctx) -> Self::Api; + fn into_api(self, ctx: &mut Self::Ctx) -> Self::Api { self.to_api(ctx) } +} + +pub trait FromApi: ApiEquiv { + type Ctx; + fn from_api(api: &Self::Api, ctx: &mut Self::Ctx) -> Self; +} + +/// This is the weakest kind of conversion possible; +/// By holding a reference to the source type, you can provide a reference to the target type. +/// Unlike Into, the target type may hold references into the source, +/// but unlike AsRef, it doesn't have to be fully contained in the source. +/// The resulting object is stackbound so its utility is very limited. +pub trait ProjectionMut { + fn with_built(&mut self, cb: impl FnOnce(&mut T) -> R) -> R; +} +impl ProjectionMut for T { + fn with_built(&mut self, cb: impl FnOnce(&mut T) -> R) -> R { cb(self) } +} diff --git a/orchid-api-traits/src/helpers.rs b/orchid-api-traits/src/helpers.rs index f4a4369..d9d0c81 100644 --- a/orchid-api-traits/src/helpers.rs +++ b/orchid-api-traits/src/helpers.rs @@ -1,5 +1,7 @@ use std::io::{Read, Write}; +use itertools::{Chunk, Itertools}; + use crate::Encode; pub fn encode_enum(write: &mut W, id: u8, f: impl FnOnce(&mut W)) { @@ -11,10 +13,20 @@ pub fn write_exact(write: &mut W, bytes: &'static [u8]) { write.write_all(bytes).expect("Failed to write exact bytes") } +pub fn print_bytes(b: &[u8]) -> String { + (b.iter().map(|b| format!("{b:02x}"))) + .chunks(4) + .into_iter() + .map(|mut c: Chunk<_>| c.join(" ")) + .join(" ") +} + pub fn read_exact(read: &mut R, bytes: &'static [u8]) { let mut data = vec![0u8; bytes.len()]; read.read_exact(&mut data).expect("Failed to read bytes"); - assert_eq!(&data, bytes, "Wrong bytes") + if data != bytes { + panic!("Wrong bytes!\nExpected: {}\nFound: {}", print_bytes(bytes), print_bytes(&data)); + } } pub fn enc_vec(enc: &impl Encode) -> Vec { diff --git a/orchid-api-traits/src/lib.rs b/orchid-api-traits/src/lib.rs index e07a0e7..be86c9f 100644 --- a/orchid-api-traits/src/lib.rs +++ b/orchid-api-traits/src/lib.rs @@ -2,8 +2,10 @@ mod coding; mod helpers; mod hierarchy; mod relations; +mod api_conv; -pub use coding::{Coding, Decode, Encode}; -pub use helpers::{enc_vec, encode_enum, read_exact, write_exact}; -pub use hierarchy::{Extends, InHierarchy, TLBool, TLFalse, TLTrue, UnderRoot}; -pub use relations::{Channel, MsgSet, Request}; +pub use coding::*; +pub use helpers::*; +pub use hierarchy::*; +pub use relations::*; +pub use api_conv::*; diff --git a/orchid-api-traits/src/relations.rs b/orchid-api-traits/src/relations.rs index 1a9eff5..5d8f090 100644 --- a/orchid-api-traits/src/relations.rs +++ b/orchid-api-traits/src/relations.rs @@ -3,7 +3,11 @@ use crate::helpers::enc_vec; pub trait Request: Coding + Sized + Send + 'static { type Response: Coding + Send + 'static; - fn respond(&self, rep: Self::Response) -> Vec { enc_vec(&rep) } +} + +pub fn respond(_: &R, rep: R::Response) -> Vec { enc_vec(&rep) } +pub fn respond_with(r: &R, f: impl FnOnce(&R) -> R::Response) -> Vec { + respond(r, f(r)) } pub trait Channel: 'static { diff --git a/orchid-api/src/atom.rs b/orchid-api/src/atom.rs index 03d4c37..2f875d8 100644 --- a/orchid-api/src/atom.rs +++ b/orchid-api/src/atom.rs @@ -3,10 +3,7 @@ use std::num::NonZeroU64; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; -use crate::error::OrcResult; -use crate::expr::{Expr, ExprTicket}; -use crate::proto::{ExtHostReq, HostExtNotif, HostExtReq}; -use crate::system::SysId; +use crate::{ExprTicket, Expression, ExtHostReq, HostExtNotif, HostExtReq, OrcResult, SysId, TStrv}; pub type AtomData = Vec; @@ -53,7 +50,7 @@ pub struct Atom { #[extends(AtomReq, HostExtReq)] pub struct CallRef(pub Atom, pub ExprTicket); impl Request for CallRef { - type Response = Expr; + type Response = Expression; } /// Attempt to apply an atom as a function, consuming the atom and enabling the @@ -63,7 +60,7 @@ impl Request for CallRef { #[extends(AtomReq, HostExtReq)] pub struct FinalCall(pub Atom, pub ExprTicket); impl Request for FinalCall { - type Response = Expr; + type Response = Expression; } #[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] @@ -80,34 +77,24 @@ impl Request for DeserAtom { type Response = Atom; } -/// Determine whether two atoms are identical for the purposes of macro -/// application. If a given atom is never generated by macros or this relation -/// is difficult to define, the module can return false -#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] -#[extends(AtomReq, HostExtReq)] -pub struct AtomSame(pub Atom, pub Atom); -impl Request for AtomSame { - type Response = bool; -} - /// A request blindly routed to the system that provides an atom. #[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] #[extends(AtomReq, HostExtReq)] -pub struct Fwded(pub Atom, pub Vec); +pub struct Fwded(pub Atom, pub TStrv, pub Vec); impl Request for Fwded { - type Response = Vec; + type Response = Option>; } #[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] #[extends(ExtHostReq)] -pub struct Fwd(pub Atom, pub Vec); +pub struct Fwd(pub Atom, pub TStrv, pub Vec); impl Request for Fwd { - type Response = Vec; + type Response = Option>; } #[derive(Clone, Debug, Coding)] pub enum NextStep { - Continue(Expr), + Continue(Expression), Halt, } #[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] @@ -138,7 +125,6 @@ impl Request for AtomPrint { pub enum AtomReq { CallRef(CallRef), FinalCall(FinalCall), - AtomSame(AtomSame), Fwded(Fwded), Command(Command), AtomPrint(AtomPrint), @@ -149,8 +135,7 @@ impl AtomReq { /// subclass have at least one atom argument. pub fn get_atom(&self) -> &Atom { match self { - Self::AtomSame(AtomSame(a, ..)) - | Self::CallRef(CallRef(a, ..)) + Self::CallRef(CallRef(a, ..)) | Self::Command(Command(a)) | Self::FinalCall(FinalCall(a, ..)) | Self::Fwded(Fwded(a, ..)) diff --git a/orchid-api/src/error.rs b/orchid-api/src/error.rs index d9326f1..5a11105 100644 --- a/orchid-api/src/error.rs +++ b/orchid-api/src/error.rs @@ -3,8 +3,7 @@ use std::sync::Arc; use orchid_api_derive::Coding; -use crate::interner::TStr; -use crate::location::Location; +use crate::{Location, TStr}; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)] pub struct ErrId(pub NonZeroU16); diff --git a/orchid-api/src/expr.rs b/orchid-api/src/expr.rs index b6ae1dc..986ee22 100644 --- a/orchid-api/src/expr.rs +++ b/orchid-api/src/expr.rs @@ -3,12 +3,7 @@ use std::num::NonZeroU64; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; -use crate::atom::Atom; -use crate::error::OrcError; -use crate::interner::TStrv; -use crate::location::Location; -use crate::proto::{ExtHostNotif, ExtHostReq}; -use crate::system::SysId; +use crate::{Atom, ExtHostNotif, ExtHostReq, Location, OrcError, SysId, TStrv}; /// An arbitrary ID associated with an expression on the host side. Incoming /// tickets always come with some lifetime guarantee, which can be extended with @@ -57,11 +52,11 @@ pub struct Move { /// [crate::atom::Call] or [crate::atom::CallRef], or a constant in the /// [crate::tree::Tree]. #[derive(Clone, Debug, Coding)] -pub enum Clause { +pub enum ExpressionKind { /// Apply the lhs as a function to the rhs - Call(Box, Box), + Call(Box, Box), /// Lambda function. The number operates as an argument name - Lambda(u64, Box), + Lambda(u64, Box), /// Binds the argument passed to the lambda with the same ID in the same /// template Arg(u64), @@ -70,16 +65,12 @@ pub enum Clause { Slot(ExprTicket), /// The lhs must be fully processed before the rhs can be processed. /// Equivalent to Haskell's function of the same name - Seq(Box, Box), + Seq(Box, Box), /// Insert a new atom in the tree. When the clause is used in the const tree, /// the atom must be trivial. This is always a newly constructed atom, if you /// want to reference an existing atom, use the corresponding [ExprTicket]. /// Because the atom is newly constructed, it also must belong to this system. NewAtom(Atom), - /// An atom, specifically an atom that already exists. This form is only ever - /// returned from [Inspect], and it's borrowed from the expression being - /// inspected. - Atom(ExprTicket, Atom), /// A reference to a constant Const(TStrv), /// A static runtime error. @@ -87,22 +78,35 @@ pub enum Clause { } #[derive(Clone, Debug, Coding)] -pub struct Expr { - pub clause: Clause, +pub struct Expression { + pub kind: ExpressionKind, pub location: Location, } #[derive(Clone, Debug, Coding)] -pub struct Details { - pub expr: Expr, +pub enum InspectedKind { + Atom(Atom), + Bottom(Vec), + Opaque, +} + +#[derive(Clone, Debug, Coding)] +pub struct Inspected { + pub kind: InspectedKind, + pub location: Location, pub refcount: u32, } +/// Obtain information about an expression. Used to act upon arguments by +/// resolving shallowly and operating on the atom, but also usable for +/// reflection #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] #[extends(ExprReq, ExtHostReq)] -pub struct Inspect(pub ExprTicket); +pub struct Inspect { + pub target: ExprTicket, +} impl Request for Inspect { - type Response = Details; + type Response = Inspected; } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] diff --git a/orchid-api/src/interner.rs b/orchid-api/src/interner.rs index 280e4fd..21ad833 100644 --- a/orchid-api/src/interner.rs +++ b/orchid-api/src/interner.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; -use crate::proto::{ExtHostReq, HostExtReq}; +use crate::{ExtHostReq, HostExtReq}; /// Intern requests sent by the replica to the master. These requests are /// repeatable. diff --git a/orchid-api/src/lexer.rs b/orchid-api/src/lexer.rs new file mode 100644 index 0000000..b8f1f82 --- /dev/null +++ b/orchid-api/src/lexer.rs @@ -0,0 +1,46 @@ +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}; + +/// - All ranges contain at least one character +/// - All ranges are in increasing characeter order +/// - There are excluded characters between each pair of neighboring ranges +#[derive(Clone, Debug, Coding)] +pub struct CharFilter(pub Vec>); + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(HostExtReq)] +pub struct LexExpr { + pub sys: SysId, + pub id: ParsId, + pub text: TStr, + pub pos: u32, +} +impl Request for LexExpr { + type Response = Option>; +} + +#[derive(Clone, Debug, Coding)] +pub struct LexedExpr { + pub pos: u32, + pub expr: TokenTree, +} + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(ExtHostReq)] +pub struct SubLex { + pub id: ParsId, + pub pos: u32, +} +impl Request for SubLex { + type Response = Option; +} + +#[derive(Clone, Debug, Coding)] +pub struct SubLexed { + pub pos: u32, + pub ticket: TreeTicket, +} diff --git a/orchid-api/src/lib.rs b/orchid-api/src/lib.rs index cc77710..9588d3f 100644 --- a/orchid-api/src/lib.rs +++ b/orchid-api/src/lib.rs @@ -1,35 +1,26 @@ +mod lexer; +pub use lexer::*; +mod macros; +pub use macros::*; mod atom; -pub use atom::{ - Atom, AtomData, AtomDrop, AtomId, AtomPrint, AtomReq, AtomSame, CallRef, Command, DeserAtom, - FinalCall, Fwd, Fwded, LocalAtom, NextStep, SerializeAtom, -}; +pub use atom::*; mod error; -pub use error::{ErrId, ErrLocation, OrcError, OrcResult}; +pub use error::*; mod expr; -pub use expr::{ - Acquire, Clause, Details, Expr, ExprNotif, ExprReq, ExprTicket, Inspect, Move, Release, -}; +pub use expr::*; mod interner; -pub use interner::{ - ExternStr, ExternStrv, IntReq, InternStr, InternStrv, Retained, Sweep, TStr, TStrv, -}; +pub use interner::*; mod location; -pub use location::{CodeGenInfo, Location, SourceRange}; +pub use location::*; mod logging; -pub use logging::{Log, LogStrategy}; +pub use logging::*; mod parser; -pub use parser::{CharFilter, LexExpr, LexedExpr, ParsId, ParseLine, ParserReq, SubLex, SubLexed}; +pub use parser::*; mod proto; -pub use proto::{ - ExtHostChannel, ExtHostNotif, ExtHostReq, ExtMsgSet, ExtensionHeader, HostExtChannel, - HostExtNotif, HostExtReq, HostHeader, HostMsgSet, Ping, -}; +pub use proto::*; mod system; -pub use system::{NewSystem, SysDeclId, SysId, SysReq, SystemDecl, SystemDrop, SystemInst}; +pub use system::*; mod tree; -pub use tree::{ - CompName, GetMember, Item, ItemKind, Member, MemberKind, Module, Paren, Token, TokenTree, TreeId, - TreeTicket, -}; +pub use tree::*; mod vfs; -pub use vfs::{EagerVfs, GetVfs, Loaded, VfsId, VfsRead, VfsReq}; +pub use vfs::*; diff --git a/orchid-api/src/location.rs b/orchid-api/src/location.rs index ed46d49..b7f7762 100644 --- a/orchid-api/src/location.rs +++ b/orchid-api/src/location.rs @@ -2,13 +2,17 @@ use std::ops::Range; use orchid_api_derive::Coding; -use crate::interner::TStrv; +use crate::{TStr, TStrv}; #[derive(Clone, Debug, Coding)] pub enum Location { + /// Location inaccessible. Locations are always debugging aids and never + /// mandatory. None, + /// Associated with a slot when wrapped in an expression. + SlotTarget, /// Used in functions to denote the generated code that carries on the - /// location of the call. Not allowed in the const tree. + /// location of the call. Inherit, Gen(CodeGenInfo), /// Range and file @@ -26,5 +30,5 @@ pub struct SourceRange { #[derive(Clone, Debug, Coding)] pub struct CodeGenInfo { pub generator: TStrv, - pub details: String, + pub details: TStr, } diff --git a/orchid-api/src/logging.rs b/orchid-api/src/logging.rs index 377d55c..e5b9e63 100644 --- a/orchid-api/src/logging.rs +++ b/orchid-api/src/logging.rs @@ -1,6 +1,6 @@ use orchid_api_derive::{Coding, Hierarchy}; -use crate::proto::ExtHostNotif; +use crate::ExtHostNotif; #[derive(Clone, Debug, Coding)] pub enum LogStrategy { diff --git a/orchid-api/src/macros.rs b/orchid-api/src/macros.rs new file mode 100644 index 0000000..7ee9977 --- /dev/null +++ b/orchid-api/src/macros.rs @@ -0,0 +1,82 @@ +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::{Comment, ExtHostReq, HostExtReq, Location, OrcResult, Paren, ParsId, SysId, TStr, TStrv}; + +#[derive(Clone, Debug, Coding)] +pub struct MacroTreeId(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), +} + +#[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 07e6bb8..d875114 100644 --- a/orchid-api/src/parser.rs +++ b/orchid-api/src/parser.rs @@ -1,70 +1,19 @@ use std::num::NonZeroU64; -use std::ops::RangeInclusive; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; -use crate::error::OrcResult; -use crate::interner::TStr; -use crate::proto::{ExtHostReq, HostExtReq}; -use crate::system::SysId; -use crate::tree::{TokenTree, TreeTicket}; +use crate::{Comment, HostExtReq, OrcResult, SysId, TokenTree}; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)] pub struct ParsId(pub NonZeroU64); -/// - All ranges contain at least one character -/// - All ranges are in increasing characeter order -/// - There are excluded characters between each pair of neighboring ranges -#[derive(Clone, Debug, Coding)] -pub struct CharFilter(pub Vec>); - #[derive(Clone, Debug, Coding, Hierarchy)] #[extends(HostExtReq)] -#[extendable] -pub enum ParserReq { - LexExpr(LexExpr), - ParseLine(ParseLine), -} - -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extends(ParserReq, HostExtReq)] -pub struct LexExpr { - pub sys: SysId, - pub id: ParsId, - pub text: TStr, - pub pos: u32, -} -impl Request for LexExpr { - type Response = Option>; -} - -#[derive(Clone, Debug, Coding)] -pub struct LexedExpr { - pub pos: u32, - pub expr: TokenTree, -} - -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extends(ExtHostReq)] -pub struct SubLex { - pub id: ParsId, - pub pos: u32, -} -impl Request for SubLex { - type Response = Option; -} - -#[derive(Clone, Debug, Coding)] -pub struct SubLexed { - pub pos: u32, - pub ticket: TreeTicket, -} - -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extends(ParserReq, HostExtReq)] pub struct ParseLine { pub sys: SysId, + pub comments: Vec, + pub exported: bool, pub line: Vec, } impl Request for ParseLine { diff --git a/orchid-api/src/proto.rs b/orchid-api/src/proto.rs index 824dfc5..8a44427 100644 --- a/orchid-api/src/proto.rs +++ b/orchid-api/src/proto.rs @@ -27,17 +27,16 @@ use std::io::{Read, Write}; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::{read_exact, write_exact, Channel, Decode, Encode, MsgSet, Request}; -use crate::logging::{self, LogStrategy}; -use crate::{atom, expr, interner, parser, system, tree, vfs}; +use crate::{atom, expr, interner, lexer, logging, macros, parser, system, tree, vfs}; static HOST_INTRO: &[u8] = b"Orchid host, binary API v0\n"; pub struct HostHeader { - pub log_strategy: LogStrategy, + pub log_strategy: logging::LogStrategy, } impl Decode for HostHeader { fn decode(read: &mut R) -> Self { read_exact(read, HOST_INTRO); - Self { log_strategy: LogStrategy::decode(read) } + Self { log_strategy: logging::LogStrategy::decode(read) } } } impl Encode for HostHeader { @@ -79,8 +78,10 @@ pub enum ExtHostReq { Ping(Ping), IntReq(interner::IntReq), Fwd(atom::Fwd), + SysFwd(system::SysFwd), ExprReq(expr::ExprReq), - SubLex(parser::SubLex), + SubLex(lexer::SubLex), + RunMacros(macros::RunMacros), } /// Notifications sent from the extension to the host @@ -107,9 +108,11 @@ pub enum HostExtReq { Sweep(interner::Sweep), AtomReq(atom::AtomReq), DeserAtom(atom::DeserAtom), - ParserReq(parser::ParserReq), + LexExpr(lexer::LexExpr), + ParseLine(parser::ParseLine), GetMember(tree::GetMember), VfsReq(vfs::VfsReq), + ApplyMacro(macros::ApplyMacro), } /// Notifications sent from the host to the extension @@ -147,13 +150,12 @@ impl MsgSet for HostMsgSet { mod tests { use orchid_api_traits::enc_vec; use ordered_float::NotNan; - use system::{SysDeclId, SystemDecl}; use super::*; #[test] fn host_header_enc() { - let hh = HostHeader { log_strategy: LogStrategy::File("SomeFile".to_string()) }; + let hh = HostHeader { log_strategy: logging::LogStrategy::File("SomeFile".to_string()) }; let mut enc = &enc_vec(&hh)[..]; eprintln!("Encoded to {enc:?}"); HostHeader::decode(&mut enc); @@ -164,8 +166,8 @@ mod tests { fn ext_header_enc() { let eh = ExtensionHeader { name: "my_extension".to_string(), - systems: vec![SystemDecl { - id: SysDeclId(1.try_into().unwrap()), + systems: vec![system::SystemDecl { + id: system::SysDeclId(1.try_into().unwrap()), name: "misc".to_string(), depends: vec!["std".to_string()], priority: NotNan::new(1f64).unwrap(), diff --git a/orchid-api/src/system.rs b/orchid-api/src/system.rs index 1875db2..fc0558e 100644 --- a/orchid-api/src/system.rs +++ b/orchid-api/src/system.rs @@ -5,10 +5,7 @@ use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use ordered_float::NotNan; -use crate::interner::TStr; -use crate::parser::CharFilter; -use crate::proto::{HostExtNotif, HostExtReq}; -use crate::tree::MemberKind; +use crate::{CharFilter, ExtHostReq, HostExtNotif, HostExtReq, MemberKind, TStr}; /// ID of a system type #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)] @@ -72,9 +69,24 @@ pub struct SystemInst { #[extends(HostExtNotif)] pub struct SystemDrop(pub SysId); +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(SysReq, HostExtReq)] +pub struct SysFwded(pub SysId, pub Vec); +impl Request for SysFwded { + type Response = Vec; +} + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(ExtHostReq)] +pub struct SysFwd(pub SysId, pub Vec); +impl Request for SysFwd { + type Response = Vec; +} + #[derive(Clone, Debug, Coding, Hierarchy)] #[extends(HostExtReq)] #[extendable] pub enum SysReq { NewSystem(NewSystem), + SysFwded(SysFwded), } diff --git a/orchid-api/src/tree.rs b/orchid-api/src/tree.rs index 978ce32..bf372c7 100644 --- a/orchid-api/src/tree.rs +++ b/orchid-api/src/tree.rs @@ -4,13 +4,11 @@ use std::sync::Arc; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; +use ordered_float::NotNan; -use crate::error::OrcError; -use crate::interner::{TStr, TStrv}; -use crate::location::Location; -use crate::proto::HostExtReq; -use crate::system::SysId; -use crate::{Atom, Expr}; +use crate::{ + Atom, Expression, HostExtReq, Location, MacroBlock, OrcError, Placeholder, SysId, TStr, TStrv, +}; /// A token tree from a lexer recursion request. Its lifetime is the lex call, /// the lexer can include it in its output or discard it by implication. @@ -31,7 +29,7 @@ pub struct TokenTree { #[derive(Clone, Debug, Coding)] pub enum Token { /// Lambda function head, from the opening \ until the beginning of the body. - Lambda(Vec), + LambdaHead(Vec), /// A name segment or an operator. Name(TStr), /// :: @@ -49,9 +47,13 @@ pub enum Token { Bottom(Vec), /// A comment Comment(Arc), + /// Placeholder + Ph(Placeholder), + /// Macro block head + Macro(Option>), } -#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Coding)] pub enum Paren { Round, Square, @@ -64,45 +66,42 @@ pub struct TreeId(pub NonZeroU64); #[derive(Clone, Debug, Coding)] pub struct Item { pub location: Location, - pub comments: Vec<(Arc, Location)>, + pub comments: Vec, pub kind: ItemKind, } #[derive(Clone, Debug, Coding)] pub enum ItemKind { Member(Member), - Raw(Vec), + Macro(MacroBlock), Export(TStr), - Import(CompName), + Import(TStrv), +} + +#[derive(Clone, Debug, Coding)] +pub struct Comment { + pub text: TStr, + pub location: Location, } #[derive(Clone, Debug, Coding)] pub struct Member { pub name: TStr, - pub exported: bool, pub kind: MemberKind, } #[derive(Clone, Debug, Coding)] pub enum MemberKind { - Const(Expr), + Const(Expression), Module(Module), Lazy(TreeId), } #[derive(Clone, Debug, Coding)] pub struct Module { - pub imports: Vec, pub items: Vec, } -#[derive(Clone, Debug, Coding)] -pub struct CompName { - pub path: Vec, - pub name: Option, - pub location: Location, -} - #[derive(Clone, Copy, Debug, Coding, Hierarchy)] #[extends(HostExtReq)] pub struct GetMember(pub SysId, pub TreeId); diff --git a/orchid-base/Cargo.toml b/orchid-base/Cargo.toml index caad402..a2c53bb 100644 --- a/orchid-base/Cargo.toml +++ b/orchid-base/Cargo.toml @@ -12,6 +12,7 @@ hashbrown = "0.14.3" itertools = "0.13.0" lazy_static = "1.4.0" never = "0.1.0" +num-traits = "0.2.19" orchid-api = { version = "0.1.0", path = "../orchid-api" } orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" } orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" } diff --git a/orchid-base/src/api_utils.rs b/orchid-base/src/api_utils.rs deleted file mode 100644 index 8b13789..0000000 --- a/orchid-base/src/api_utils.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/orchid-base/src/error.rs b/orchid-base/src/error.rs index 1061c5d..3c80148 100644 --- a/orchid-base/src/error.rs +++ b/orchid-base/src/error.rs @@ -1,3 +1,5 @@ +use std::fmt; +use std::ops::Add; use std::sync::Arc; use itertools::Itertools; @@ -65,16 +67,86 @@ impl PartialEq for OrcErr { impl From for Vec { fn from(value: OrcErr) -> Self { vec![value] } } - -pub fn errv_to_apiv<'a>(errv: impl IntoIterator) -> Vec { - errv.into_iter().map(OrcErr::to_api).collect_vec() +impl fmt::Display for OrcErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let pstr = self.positions.iter().map(|p| format!("{p:?}")).join("; "); + write!(f, "{}: {} @ {}", self.description, self.message, pstr) + } } -pub fn errv_from_apiv<'a>(err: impl IntoIterator) -> Vec { - err.into_iter().map(OrcErr::from_api).collect() +#[derive(Clone, Debug)] +pub struct EmptyErrv; +impl fmt::Display for EmptyErrv { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OrcErrv must not be empty") + } } -pub type OrcRes = Result>; +#[derive(Clone, Debug)] +pub struct OrcErrv(Vec); +impl OrcErrv { + pub fn new(errors: impl IntoIterator) -> Result { + let v = errors.into_iter().collect_vec(); + if v.is_empty() { Err(EmptyErrv) } else { Ok(Self(v)) } + } + #[must_use] + pub fn to_api(&self) -> Vec { self.0.iter().map(OrcErr::to_api).collect_vec() } + #[must_use] + pub fn from_api<'a>(apiv: impl IntoIterator) -> Self { + let v = apiv.into_iter().map(OrcErr::from_api).collect_vec(); + assert!(!v.is_empty(), "Error condition with 0 errors"); + Self(v) + } + #[must_use] + pub fn extended(mut self, errors: impl IntoIterator) -> Self + where Self: Extend { + self.extend(errors); + self + } + #[must_use] + pub fn len(&self) -> usize { self.0.len() } + #[must_use] + pub fn is_empty(&self) -> bool { self.len() == 0 } + #[must_use] + pub fn any(&self, f: impl FnMut(&OrcErr) -> bool) -> bool { self.0.iter().any(f) } + #[must_use] + pub fn keep_only(self, f: impl FnMut(&OrcErr) -> bool) -> Option { + let v = self.0.into_iter().filter(f).collect_vec(); + if v.is_empty() { None } else { Some(Self(v)) } + } + #[must_use] + pub fn one(&self) -> Option<&OrcErr> { (self.0.len() == 1).then(|| &self.0[9]) } + pub fn pos_iter(&self) -> impl Iterator + '_ { + self.0.iter().flat_map(|e| e.positions.iter().cloned()) + } +} +impl From for OrcErrv { + fn from(value: OrcErr) -> Self { Self(vec![value]) } +} +impl Add for OrcErrv { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { Self(self.0.into_iter().chain(rhs.0).collect_vec()) } +} +impl Extend for OrcErrv { + fn extend>(&mut self, iter: T) { self.0.extend(iter) } +} +impl Extend for OrcErrv { + fn extend>(&mut self, iter: T) { + self.0.extend(iter.into_iter().flatten()) + } +} +impl IntoIterator for OrcErrv { + type IntoIter = std::vec::IntoIter; + type Item = OrcErr; + fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } +} +impl fmt::Display for OrcErrv { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.iter().join("\n")) + } +} + +pub type OrcRes = Result; pub fn mk_err( description: Tok, @@ -88,6 +160,14 @@ pub fn mk_err( } } -pub trait Reporter { - fn report(&self, e: OrcErr); +pub fn mk_errv( + description: Tok, + message: impl AsRef, + posv: impl IntoIterator, +) -> OrcErrv { + mk_err(description, message, posv).into() +} + +pub trait Reporter { + fn report(&self, e: impl Into); } diff --git a/orchid-base/src/interner.rs b/orchid-base/src/interner.rs index d99bb12..72bd927 100644 --- a/orchid-base/src/interner.rs +++ b/orchid-base/src/interner.rs @@ -3,13 +3,14 @@ use std::hash::BuildHasher as _; use std::num::NonZeroU64; use std::ops::{Deref, DerefMut}; use std::sync::{atomic, Arc, Mutex, MutexGuard}; -use std::{fmt, hash}; +use std::{fmt, hash, mem}; use hashbrown::{HashMap, HashSet}; use itertools::Itertools as _; use orchid_api_traits::{Decode, Encode, Request}; use crate::api; +use orchid_api_traits::{ApiEquiv, FromApi, ToApi}; use crate::reqnot::{DynRequester, Requester}; /// Clippy crashes while verifying `Tok: Sized` without this and I cba to create @@ -62,7 +63,7 @@ impl Decode for Tok { fn decode(read: &mut R) -> Self { intern(&T::decode(read)) } } -pub trait Interned: Eq + hash::Hash + Clone + Internable { +pub trait Interned: Eq + hash::Hash + Clone + fmt::Debug + Internable { type Marker: InternMarker + Sized; fn intern( self: Arc, @@ -71,7 +72,7 @@ pub trait Interned: Eq + hash::Hash + Clone + Internable { fn bimap(interner: &mut TypedInterners) -> &mut Bimap; } -pub trait Internable { +pub trait Internable: fmt::Debug { type Interned: Interned; fn get_owned(&self) -> Arc; } @@ -96,7 +97,6 @@ impl Interned for String { } fn bimap(interners: &mut TypedInterners) -> &mut Bimap { &mut interners.strings } } - impl InternMarker for api::TStr { type Interned = String; fn resolve( @@ -108,17 +108,27 @@ impl InternMarker for api::TStr { fn get_id(self) -> NonZeroU64 { self.0 } fn from_id(id: NonZeroU64) -> Self { Self(id) } } - impl Internable for str { type Interned = String; fn get_owned(&self) -> Arc { Arc::new(self.to_string()) } } - impl Internable for String { type Interned = String; fn get_owned(&self) -> Arc { Arc::new(self.to_string()) } } +impl ApiEquiv for Tok { + type Api = api::TStr; +} +impl ToApi for Tok { + type Ctx = (); + fn to_api(&self, _: &mut Self::Ctx) -> Self::Api { self.marker() } +} +impl FromApi for Tok { + type Ctx = (); + fn from_api(api: &Self::Api, _: &mut Self::Ctx) -> Self { deintern(*api) } +} + impl Interned for Vec> { type Marker = api::TStrv; fn intern( @@ -129,7 +139,6 @@ impl Interned for Vec> { } fn bimap(interners: &mut TypedInterners) -> &mut Bimap { &mut interners.vecs } } - impl InternMarker for api::TStrv { type Interned = Vec>; fn resolve( @@ -143,30 +152,37 @@ impl InternMarker for api::TStrv { fn get_id(self) -> NonZeroU64 { self.0 } fn from_id(id: NonZeroU64) -> Self { Self(id) } } - impl Internable for [Tok] { type Interned = Vec>; fn get_owned(&self) -> Arc { Arc::new(self.to_vec()) } } - impl Internable for Vec> { type Interned = Vec>; fn get_owned(&self) -> Arc { Arc::new(self.to_vec()) } } - impl Internable for Vec { type Interned = Vec>; fn get_owned(&self) -> Arc { Arc::new(self.iter().map(|ts| deintern(*ts)).collect()) } } - impl Internable for [api::TStr] { type Interned = Vec>; fn get_owned(&self) -> Arc { Arc::new(self.iter().map(|ts| deintern(*ts)).collect()) } } +impl ApiEquiv for Tok>> { + type Api = api::TStrv; +} +impl ToApi for Tok>> { + type Ctx = (); + fn to_api(&self, _: &mut Self::Ctx) -> Self::Api { self.marker() } +} +impl FromApi for Tok>> { + type Ctx = (); + fn from_api(api: &Self::Api, _: &mut Self::Ctx) -> Self { deintern(*api) } +} /// The number of references held to any token by the interner. const BASE_RC: usize = 3; @@ -262,8 +278,10 @@ pub fn init_replica(req: impl DynRequester + 'static) { } pub fn intern(t: &(impl Internable + ?Sized)) -> Tok { - let mut g = interner(); let data = t.get_owned(); + let mut g = interner(); + let job = format!("{t:?} in {}", if g.master.is_some() { "replica" } else { "master" }); + eprintln!("Interning {job}"); let typed = T::bimap(&mut g.interners); if let Some(tok) = typed.by_value(&data) { return tok; @@ -275,6 +293,8 @@ pub fn intern(t: &(impl Internable + ?Sized)) -> Tok< }; let tok = Tok::new(data, marker); T::bimap(&mut g.interners).insert(tok.clone()); + mem::drop(g); + eprintln!("Interned {job}"); tok } diff --git a/orchid-base/src/lib.rs b/orchid-base/src/lib.rs index 230f5b7..84c8cd5 100644 --- a/orchid-base/src/lib.rs +++ b/orchid-base/src/lib.rs @@ -5,8 +5,6 @@ pub mod clone; pub mod combine; pub mod event; pub mod msg; -// pub mod gen; -pub mod api_utils; pub mod box_cow; pub mod char_filter; pub mod error; @@ -18,7 +16,10 @@ pub mod logging; pub mod name; pub mod number; pub mod parse; +pub mod pure_seq; pub mod reqnot; pub mod sequence; pub mod tokens; pub mod tree; +pub mod macros; +mod match_mapping; diff --git a/orchid-base/src/location.rs b/orchid-base/src/location.rs index f6f9b4f..35cd0a2 100644 --- a/orchid-base/src/location.rs +++ b/orchid-base/src/location.rs @@ -1,15 +1,16 @@ //! Structures that show where code or semantic elements came from +use crate::match_mapping; use std::fmt; use std::hash::Hash; use std::ops::Range; -use std::sync::Arc; use trait_set::trait_set; -use crate::interner::{deintern, Tok}; +use orchid_api_traits::{ApiEquiv, FromApi, ToApi}; +use crate::interner::{deintern, intern, Tok}; use crate::name::Sym; -use crate::{api, sym}; +use crate::{api, intern, sym}; trait_set! { pub trait GetSrc = FnMut(&Sym) -> Tok; @@ -18,6 +19,7 @@ trait_set! { #[derive(Debug, Clone)] pub enum Pos { None, + SlotTarget, /// Used in functions to denote the generated code that carries on the /// location of the call. Not allowed in the const tree. Inherit, @@ -28,24 +30,6 @@ pub enum Pos { Range(Range), } impl Pos { - pub fn to_api(&self) -> api::Location { - match self { - Self::Inherit => api::Location::Inherit, - Self::None => api::Location::None, - Self::Range(r) => api::Location::Range(r.clone()), - Self::Gen(cgi) => api::Location::Gen(cgi.to_api()), - Self::SourceRange(sr) => api::Location::SourceRange(sr.to_api()), - } - } - pub fn from_api(loc: &api::Location) -> Self { - match loc { - api::Location::Inherit => Self::Inherit, - api::Location::None => Self::None, - api::Location::Range(r) => Self::Range(r.clone()), - api::Location::Gen(cgi) => CodeGenInfo::from_api(cgi).location(), - api::Location::SourceRange(sr) => SourceRange::from_api(sr).location(), - } - } pub fn pretty_print(&self, get_src: &mut impl GetSrc) -> String { match self { Self::Gen(g) => g.to_string(), @@ -55,6 +39,32 @@ impl Pos { } } } +impl ApiEquiv for Pos { + type Api = api::Location; +} +impl FromApi for Pos { + type Ctx = (); + fn from_api(api: &Self::Api, ctx: &mut Self::Ctx) -> Self { + match_mapping!(api, api::Location => Pos { + None, Inherit, SlotTarget, + Range(r.clone()), + Gen(cgi => CodeGenInfo::from_api(cgi, &mut ())), + SourceRange(sr => CodeGenInfo::from_api(sr, &mut ())) + }) + } +} + +impl ToApi for Pos { + type Ctx = (); + fn to_api(&self, ctx: &mut Self::Ctx) -> Self::Api { + match_mapping!(self, Pos => Self::Api { + None, Inherit, SlotTarget, + Range(r.clone()), + Gen(cgi.to_api(ctx)), + SourceRange(sr.to_api(ctx)), + }) + } +} /// Exact source code location. Includes where the code was loaded from, what /// the original source code was, and a byte range. @@ -67,12 +77,6 @@ impl SourceRange { pub fn new(range: &Range, path: &Sym) -> Self { Self { range: range.clone(), path: path.clone() } } - pub fn to_api(&self) -> api::SourceRange { - api::SourceRange { path: self.path.tok().marker(), range: self.range.clone() } - } - pub fn from_api(sr: &api::SourceRange) -> Self { - Self { path: Sym::from_tok(deintern(sr.path)).unwrap(), range: sr.range.clone() } - } /// Create a dud [SourceRange] for testing. Its value is unspecified and /// volatile. pub fn mock() -> Self { Self { range: 0..1, path: sym!(test) } } @@ -85,7 +89,7 @@ impl SourceRange { /// 0-based index of last byte + 1 pub fn end(&self) -> u32 { self.range.end } /// Syntactic location - pub fn location(&self) -> Pos { Pos::SourceRange(self.clone()) } + pub fn pos(&self) -> Pos { Pos::SourceRange(self.clone()) } /// Transform the numeric byte range pub fn map_range(&self, map: impl FnOnce(Range) -> Range) -> Self { Self { range: map(self.range()), path: self.path() } @@ -99,6 +103,24 @@ impl SourceRange { (false, _) => format!("{sl}:{sc}..{el}:{ec}"), } } + pub fn zw(path: Sym, pos: u32) -> Self { + Self { path, range: pos..pos } + } +} +impl ApiEquiv for SourceRange { + type Api = api::SourceRange; +} +impl FromApi for SourceRange { + type Ctx = (); + fn from_api(api: &Self::Api, ctx: &mut Self::Ctx) -> Self { + Self { path: Sym::from_api(&api.path, ctx), range: api.range.clone() } + } +} +impl ToApi for SourceRange { + type Ctx = (); + fn to_api(&self, ctx: &mut Self::Ctx) -> Self::Api { + api::SourceRange { path: self.path.to_api(ctx), range: self.range.clone() } + } } /// Information about a code generator attached to the generated code @@ -107,26 +129,17 @@ pub struct CodeGenInfo { /// formatted like a Rust namespace pub generator: Sym, /// Unformatted user message with relevant circumstances and parameters - pub details: Arc, + pub details: Tok, } impl CodeGenInfo { /// A codegen marker with no user message and parameters - pub fn no_details(generator: Sym) -> Self { Self { generator, details: Arc::new(String::new()) } } + pub fn no_details(generator: Sym) -> Self { Self { generator, details: intern!(str: "") } } /// A codegen marker with a user message or parameters pub fn details(generator: Sym, details: impl AsRef) -> Self { - Self { generator, details: Arc::new(details.as_ref().to_string()) } + Self { generator, details: intern(details.as_ref()) } } /// Syntactic location - pub fn location(&self) -> Pos { Pos::Gen(self.clone()) } - pub fn to_api(&self) -> api::CodeGenInfo { - api::CodeGenInfo { generator: self.generator.tok().marker(), details: self.details.to_string() } - } - pub fn from_api(cgi: &api::CodeGenInfo) -> Self { - Self { - generator: Sym::from_tok(deintern(cgi.generator)).unwrap(), - details: Arc::new(cgi.details.clone()), - } - } + pub fn pos(&self) -> Pos { Pos::Gen(self.clone()) } } impl fmt::Debug for CodeGenInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CodeGenInfo({self})") } @@ -137,6 +150,24 @@ impl fmt::Display for CodeGenInfo { if !self.details.is_empty() { write!(f, ", details: {}", self.details) } else { write!(f, ".") } } } +impl ApiEquiv for CodeGenInfo { + type Api = api::CodeGenInfo; +} +impl FromApi for CodeGenInfo { + type Ctx = (); + fn from_api(api: &Self::Api, ctx: &mut Self::Ctx) -> Self { + Self { + generator: Sym::from_api(&api.generator, ctx), + details: Tok::from_api(&api.details, ctx), + } + } +} +impl ToApi for CodeGenInfo { + type Ctx = (); + fn to_api(&self, ctx: &mut Self::Ctx) -> Self::Api { + api::CodeGenInfo { generator: self.generator.to_api(ctx), details: self.details.to_api(ctx) } + } +} #[must_use] fn pos2lc(s: &str, i: u32) -> (u32, u32) { diff --git a/orchid-base/src/logging.rs b/orchid-base/src/logging.rs index 2cfc512..16dd0e0 100644 --- a/orchid-base/src/logging.rs +++ b/orchid-base/src/logging.rs @@ -14,7 +14,7 @@ impl Logger { pub fn log(&self, msg: impl AsRef) { writeln!(self, "{}", msg.as_ref()) } pub fn strat(&self) -> api::LogStrategy { self.0.clone() } pub fn log_buf(&self, event: impl AsRef, buf: &[u8]) { - if !std::env::var("ORCHID_LOG_BUFFERS").unwrap().is_empty() { + if std::env::var("ORCHID_LOG_BUFFERS").is_ok_and(|v| !v.is_empty()) { writeln!(self, "{}: [{}]", event.as_ref(), buf.iter().map(|b| format!("{b:02x}")).join(" ")) } } diff --git a/orchid-base/src/macros.rs b/orchid-base/src/macros.rs new file mode 100644 index 0000000..44c3e6f --- /dev/null +++ b/orchid-base/src/macros.rs @@ -0,0 +1,61 @@ +use itertools::Itertools; + +use crate::{name::Sym, tree::{AtomTok, Paren, Ph, TokTree}}; +use std::marker::PhantomData; + +use crate::{api, location::Pos}; + +#[derive(Clone, Debug)] +pub struct MacroSlot<'a>(api::MacroTreeId, PhantomData<&'a ()>); + +#[derive(Clone, Debug)] +pub struct MTree<'a, A: AtomTok> { + pub pos: Pos, + pub tok: MTok<'a, A> +} + +#[derive(Clone, Debug)] +pub enum MTok<'a, A> { + S(Paren, Vec>), + Name(Sym), + Slot(MacroSlot<'a>), + Lambda(Vec>, Vec>), + Ph(Ph), + Atom(A) +} +impl<'a, A> MTree<'a, A> { + pub(crate) fn from_api(api: &api::MacroTree) -> Self { + use api::MacroToken as MTK; + let tok = match &api.token { + MTK::Lambda(x, b) => MTok::Lambda(mtreev_from_api(x), mtreev_from_api(b)), + MTK::Name(t) => MTok::Name(Sym::deintern(*t)), + MTK::Slot(tk) => MTok::Slot(MacroSlot(tk.clone(), PhantomData)), + MTK::S(p, b) => MTok::S(p.clone(), mtreev_from_api(b)), + MTK::Ph(ph) => MTok::Ph(Ph::from_api(ph)), + }; + Self { pos: Pos::from_api(&api.location), tok } + } + pub(crate) fn to_api(&self) -> api::MacroTree { + use api::MacroToken as MTK; + let token = match &self.tok { + MTok::Lambda(x, b) => MTK::Lambda(mtreev_to_api(x), mtreev_to_api(b)), + MTok::Name(t) => MTK::Name(t.tok().marker()), + MTok::Ph(ph) => MTK::Ph(ph.to_api()), + MTok::S(p, b) => MTK::S(p.clone(), mtreev_to_api(b)), + MTok::Slot(tk) => MTK::Slot(tk.0.clone()), + }; + api::MacroTree { location: self.pos.to_api(), token } + } +} + +pub fn mtreev_from_api<'a, 'b, A>( + api: impl IntoIterator +) -> Vec> { + api.into_iter().map(MTree::from_api).collect_vec() +} + +pub fn mtreev_to_api<'a: 'b, 'b, A: 'b>( + v: impl IntoIterator> +) -> Vec { + v.into_iter().map(MTree::to_api).collect_vec() +} \ No newline at end of file diff --git a/orchid-base/src/match_mapping.rs b/orchid-base/src/match_mapping.rs new file mode 100644 index 0000000..21a73cd --- /dev/null +++ b/orchid-base/src/match_mapping.rs @@ -0,0 +1,95 @@ +/// A shorthand for mapping over enums with identical structure. Used for converting between +/// owned enums and the corresponding API enums that only differ in the type of their +/// fields. +/// +/// The basic form is +/// ```ignore +/// match_mapping!(self, ThisType => OtherType { +/// EmptyVariant, +/// TupleVariant(foo => intern(foo), bar.clone()), +/// StructVariant{ a.to_api(), b => b.} +/// }) +/// ``` +#[macro_export] +macro_rules! match_mapping { + ($input:expr, $src:ty => $tgt:ty { + $($branches:tt)* + }) => { + match_mapping!(@BRANCH_MUNCH (($input) ($src) ($tgt)) () $($branches)* ,) + }; + (@BRANCHES_DONE ( ($input:expr) ($src:ty) ($tgt:ty) ) + $( ( $variant:ident $($pat:tt)*) )* + ) => { + { + use $src as Foo; + match $input { + $( + match_mapping!(@PAT (Foo :: $variant) $($pat)*) => + match_mapping!(@VAL (< $tgt >:: $variant) $($pat)*), + )* + } + } + }; + (@BRANCH_MUNCH $ext:tt ( $($branches:tt)* ) $(,)?) => { + match_mapping!(@BRANCHES_DONE $ext $($branches)* ) + }; + (@BRANCH_MUNCH $ext:tt ( $($branches:tt)* ) $variant:ident , $($tail:tt)*) => { + match_mapping!(@BRANCH_MUNCH $ext ( $($branches)* ($variant) ) $($tail)*) + }; + (@BRANCH_MUNCH $ext:tt ( $($branches:tt)* ) $variant:ident $pat:tt , $($tail:tt)*) => { + match_mapping!(@BRANCH_MUNCH $ext + ( $($branches)* ($variant $pat) ) + $($tail)*) + }; + (@PAT ($($prefix:tt)*) ( $($fields:tt)* )) => { + $($prefix)* ( match_mapping!(@PAT_MUNCH () $($fields)*) ) + }; + (@PAT ($($prefix:tt)*) { $($fields:tt)* }) => { + $($prefix)* { match_mapping!(@PAT_MUNCH () $($fields)*) } + }; + (@PAT ($($path:tt)*)) => { $($path)* }; + (@PAT_MUNCH ($($names:ident)*) $name:ident => $value:expr) => { $($names ,)* $name }; + (@PAT_MUNCH ($($names:ident)*) $name:ident => $value:expr , $($tail:tt)*) => { + match_mapping!(@PAT_MUNCH ($($names)* $name) $($tail)*) + }; + (@PAT_MUNCH ($($names:ident)*) $name:ident . $($tail:tt)*) => { + match_mapping!(@PAT_DOT_MUNCH ($($names)* $name) $($tail)*) + }; + (@PAT_MUNCH ($($names:ident)*)) => { $($names),* }; + (@PAT_DOT_MUNCH $names:tt , $($tail:tt)*) => { + match_mapping!(@PAT_MUNCH $names $($tail)*) + }; + (@PAT_DOT_MUNCH $names:tt $_:tt $($tail:tt)*) => { + match_mapping!(@PAT_DOT_MUNCH $names $($tail)*) + }; + (@PAT_DOT_MUNCH ($($names:tt)*)) => { $($names),* }; + (@VAL ($($prefix:tt)*)) => { $($prefix)* }; + (@VAL ($($prefix:tt)*) ( $($fields:tt)* )) => { + $($prefix)* ( match_mapping!(@VAL_MUNCH () () $($fields)* ) ) + }; + (@VAL ($($prefix:tt)*) { $($fields:tt)* }) => { + $($prefix)* { match_mapping!(@VAL_MUNCH {} () $($fields)* ) } + }; + (@VAL_MUNCH () ($($prefix:tt)*) $name:ident => $value:expr) => { $($prefix)* $value }; + (@VAL_MUNCH () ($($prefix:tt)*) $name:ident => $value:expr , $($tail:tt)*) => { + match_mapping!(@VAL_MUNCH () ($($prefix)* $value, ) $($tail)*) + }; + (@VAL_MUNCH {} ($($prefix:tt)*) $name:ident => $value:expr) => { $($prefix)* $name: $value }; + (@VAL_MUNCH {} ($($prefix:tt)*) $name:ident => $value:expr , $($tail:tt)*) => { + match_mapping!(@VAL_MUNCH {} ($($prefix)* $name: $value, ) $($tail)*) + }; + (@VAL_MUNCH () ($($prefix:tt)*) $name:ident . $member:tt $($tail:tt)*) => { + match_mapping!(@VAL_DOT_MUNCH () ($($prefix)* $name . $member ) $($tail)*) + }; + (@VAL_MUNCH {} ($($prefix:tt)*) $name:ident . $member:tt $($tail:tt)*) => { + match_mapping!(@VAL_DOT_MUNCH {} ($($prefix)* $name: $name . $member) $($tail)*) + }; + (@VAL_DOT_MUNCH $ptyp:tt ($($prefix:tt)*) , $($tail:tt)*) => { + match_mapping!(@VAL_MUNCH $ptyp ($($prefix)* ,) $($tail)*) + }; + (@VAL_DOT_MUNCH $ptyp:tt ($($prefix:tt)*) $tt:tt $($tail:tt)*) => { + match_mapping!(@VAL_DOT_MUNCH $ptyp ($($prefix)* $tt) $($tail)*) + }; + (@VAL_DOT_MUNCH $ptyp:tt ($($prefix:tt)*)) => { $($prefix)* }; + (@VAL_MUNCH $_ptyp:tt ($($prefix:tt)*)) => { $($prefix)* }; +} \ No newline at end of file diff --git a/orchid-base/src/name.rs b/orchid-base/src/name.rs index a5a9561..f2c7a5e 100644 --- a/orchid-base/src/name.rs +++ b/orchid-base/src/name.rs @@ -9,10 +9,10 @@ use std::path::Path; use std::{fmt, slice, vec}; use itertools::Itertools; -use orchid_api::TStrv; use trait_set::trait_set; use crate::api; +use crate::api_conv::{ApiEquiv, FromApi, ToApi}; use crate::interner::{deintern, intern, InternMarker, Tok}; trait_set! { @@ -357,7 +357,7 @@ impl Sym { pub fn id(&self) -> NonZeroU64 { self.0.marker().get_id() } /// Extern the sym for editing pub fn to_vname(&self) -> VName { VName(self[..].to_vec()) } - pub fn deintern(marker: TStrv) -> Sym { + pub fn deintern(marker: api::TStrv) -> Sym { Self::from_tok(deintern(marker)).expect("Empty sequence found for serialized Sym") } } @@ -386,6 +386,17 @@ impl Deref for Sym { type Target = PathSlice; fn deref(&self) -> &Self::Target { self.borrow() } } +impl ApiEquiv for Sym { + type Api = api::TStrv; +} +impl ToApi for Sym { + fn to_api(&self, ctx: &mut C) -> Self::Api { self.tok().to_api(ctx) } +} +impl FromApi for Sym { + fn from_api(api: &Self::Api, ctx: &mut C) -> Self { + Self::from_tok(Tok::from_api(api, ctx)).expect("Empty sequence found for serialized Sym") + } +} /// An abstraction over tokenized vs non-tokenized names so that they can be /// handled together in datastructures. The names can never be empty diff --git a/orchid-base/src/number.rs b/orchid-base/src/number.rs index 14e1bba..6626036 100644 --- a/orchid-base/src/number.rs +++ b/orchid-base/src/number.rs @@ -3,6 +3,7 @@ use std::ops::Range; use ordered_float::NotNan; use rust_decimal::Decimal; +use num_traits::ToPrimitive; use crate::error::{mk_err, OrcErr}; use crate::intern; @@ -21,6 +22,16 @@ pub enum Numeric { impl Numeric { pub fn decimal(num: i64, scale: u32) -> Self { Self::Decimal(Decimal::new(num, scale)) } pub fn float(value: f64) -> Self { Self::Float(NotNan::new(value).unwrap()) } + pub fn to_f64(self) -> NotNan { + match self { + Self::Float(f) => f, + Self::Decimal(d) => { + let f = d.to_f64().expect("This is apparently always possible"); + NotNan::new(f).expect("decimal was nan") + }, + Self::Uint(i) => NotNan::new(i as f64).expect("int cannot be NaN"), + } + } } /// Rasons why [parse_num] might fail. See [NumError]. diff --git a/orchid-base/src/parse.rs b/orchid-base/src/parse.rs index c6c14f3..1d6eff3 100644 --- a/orchid-base/src/parse.rs +++ b/orchid-base/src/parse.rs @@ -1,14 +1,13 @@ +use std::iter; use std::ops::{Deref, Range}; -use std::sync::Arc; -use std::{fmt, iter}; use itertools::Itertools; -use crate::error::{mk_err, OrcRes, Reporter}; -use crate::interner::{deintern, Tok}; +use crate::error::{mk_err, mk_errv, OrcRes, Reporter}; +use crate::interner::{deintern, intern, Tok}; use crate::location::Pos; use crate::name::VPath; -use crate::tree::{AtomInTok, Paren, TokTree, Token}; +use crate::tree::{AtomTok, ExtraTok, Paren, TokTree, Token}; use crate::{api, intern}; pub fn name_start(c: char) -> bool { c.is_alphabetic() || c == '_' } @@ -17,11 +16,11 @@ pub fn op_char(c: char) -> bool { !name_char(c) && !c.is_whitespace() && !"()[]{ pub fn unrep_space(c: char) -> bool { c.is_whitespace() && !"\r\n".contains(c) } #[derive(Debug)] -pub struct Snippet<'a, 'b, A: AtomInTok, X> { +pub struct Snippet<'a, 'b, A: AtomTok, X: ExtraTok> { prev: &'a TokTree<'b, A, X>, cur: &'a [TokTree<'b, A, X>], } -impl<'a, 'b, A: AtomInTok, X> Snippet<'a, 'b, A, X> { +impl<'a, 'b, A: AtomTok, X: ExtraTok> Snippet<'a, 'b, A, X> { pub fn new(prev: &'a TokTree<'b, A, X>, cur: &'a [TokTree<'b, A, X>]) -> Self { Self { prev, cur } } @@ -44,6 +43,9 @@ impl<'a, 'b, A: AtomInTok, X> Snippet<'a, 'b, A, X> { pub fn pop_front(self) -> Option<(&'a TokTree<'b, A, X>, Self)> { self.cur.first().map(|r| (r, self.split_at(1).1)) } + pub fn pop_back(self) -> Option<(Self, &'a TokTree<'b, A, X>)> { + 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)> { let idx = self.find_idx(f)?; Some((self.split_at(idx).0, self.split_at(idx + 1).1)) @@ -65,25 +67,25 @@ impl<'a, 'b, A: AtomInTok, X> Snippet<'a, 'b, A, X> { self.split_at(non_fluff_start.unwrap_or(self.len())).1 } } -impl<'a, 'b, A: AtomInTok, X> Copy for Snippet<'a, 'b, A, X> {} -impl<'a, 'b, A: AtomInTok, X> Clone for Snippet<'a, 'b, A, X> { +impl<'a, 'b, A: AtomTok, X: ExtraTok> Copy for Snippet<'a, 'b, A, X> {} +impl<'a, 'b, A: AtomTok, X: ExtraTok> Clone for Snippet<'a, 'b, A, X> { fn clone(&self) -> Self { *self } } -impl<'a, 'b, A: AtomInTok, X> Deref for Snippet<'a, 'b, A, X> { +impl<'a, 'b, A: AtomTok, X: ExtraTok> Deref for Snippet<'a, 'b, A, X> { type Target = [TokTree<'b, A, X>]; 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: AtomInTok, X: Clone>( +pub fn strip_fluff<'a, A: AtomTok, X: ExtraTok>( tt: &TokTree<'a, A, X>, ) -> Option> { let tok = match &tt.tok { Token::BR => return None, Token::Comment(_) => return None, Token::LambdaHead(arg) => Token::LambdaHead(arg.iter().filter_map(strip_fluff).collect()), - Token::S(p, b) => Token::S(p.clone(), b.iter().filter_map(strip_fluff).collect()), + Token::S(p, b) => Token::S(*p, b.iter().filter_map(strip_fluff).collect()), t => t.clone(), }; Some(TokTree { tok, range: tt.range.clone() }) @@ -91,13 +93,21 @@ pub fn strip_fluff<'a, A: AtomInTok, X: Clone>( #[derive(Clone, Debug)] pub struct Comment { - pub text: Arc, + pub text: Tok, pub pos: Pos, } +impl Comment { + pub fn from_api(api: &api::Comment) -> Self { + Self { pos: Pos::from_api(&api.location), text: deintern(api.text) } + } + pub fn to_api(&self) -> api::Comment { + api::Comment { location: self.pos.to_api(), text: self.text.marker() } + } +} -pub fn line_items<'a, 'b, A: AtomInTok, X>( +pub fn line_items<'a, 'b, A: AtomTok, X: ExtraTok>( snip: Snippet<'a, 'b, A, X>, -) -> Vec<(Vec, Snippet<'a, 'b, 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)) { @@ -109,72 +119,79 @@ pub fn line_items<'a, 'b, A: AtomInTok, X>( match line.find_idx(|t| !matches!(t, Token::Comment(_))) { None => comments.extend(line.cur), Some(i) => { - let (cmts, line) = line.split_at(i); + let (cmts, tail) = line.split_at(i); let comments = Vec::from_iter(comments.drain(..).chain(cmts.cur).map(|t| match &t.tok { - Token::Comment(c) => Comment { text: c.clone(), pos: Pos::Range(t.range.clone()) }, + Token::Comment(c) => Comment { text: intern(&**c), pos: Pos::Range(t.range.clone()) }, _ => unreachable!("All are comments checked above"), })); - items.push((comments, line)); + items.push(Parsed { output: comments, tail }); }, } } items } -pub fn try_pop_no_fluff<'a, 'b, A: AtomInTok, X>( +pub fn try_pop_no_fluff<'a, 'b, A: AtomTok, X: ExtraTok>( snip: Snippet<'a, 'b, A, X>, -) -> OrcRes<(&'a TokTree<'b, A, X>, Snippet<'a, 'b, A, X>)> { - snip.skip_fluff().pop_front().ok_or_else(|| { - vec![mk_err(intern!(str: "Unexpected end"), "Pattern ends abruptly", [ - Pos::Range(snip.pos()).into() - ])] +) -> ParseRes<'a, 'b, &'a TokTree<'b, A, X>, A, X> { + snip.skip_fluff().pop_front().map(|(output, tail)| Parsed { output, tail }).ok_or_else(|| { + mk_errv( + intern!(str: "Unexpected end"), + "Pattern ends abruptly", + [Pos::Range(snip.pos()).into()], + ) }) } -pub fn expect_end(snip: Snippet<'_, '_, impl AtomInTok, impl Sized>) -> OrcRes<()> { +pub fn expect_end(snip: Snippet<'_, '_, impl AtomTok, impl ExtraTok>) -> OrcRes<()> { match snip.skip_fluff().get(0) { - Some(surplus) => Err(vec![mk_err( + Some(surplus) => Err(mk_errv( intern!(str: "Extra code after end of line"), "Code found after the end of the line", [Pos::Range(surplus.range.clone()).into()], - )]), + )), None => Ok(()), } } -pub fn expect_tok<'a, 'b, A: AtomInTok, X: fmt::Display>( +pub fn expect_tok<'a, 'b, A: AtomTok, X: ExtraTok>( snip: Snippet<'a, 'b, A, X>, tok: Tok, -) -> OrcRes> { - let (head, tail) = try_pop_no_fluff(snip)?; +) -> ParseRes<'a, 'b, (), A, X> { + let Parsed { output: head, tail } = try_pop_no_fluff(snip)?; match &head.tok { - Token::Name(n) if *n == tok => Ok(tail), - t => Err(vec![mk_err( + Token::Name(n) if *n == tok => Ok(Parsed { output: (), tail }), + t => Err(mk_errv( intern!(str: "Expected specific keyword"), format!("Expected {tok} but found {t}"), [Pos::Range(head.range.clone()).into()], - )]), + )), } } -pub fn parse_multiname<'a, 'b, A: AtomInTok, X: fmt::Display>( +pub struct Parsed<'a, 'b, T, A: AtomTok, X: ExtraTok> { + pub output: T, + pub tail: Snippet<'a, 'b, A, X>, +} + +pub type ParseRes<'a, 'b, T, A, X> = OrcRes>; + +pub fn parse_multiname<'a, 'b, A: AtomTok, X: ExtraTok>( ctx: &impl Reporter, tail: Snippet<'a, 'b, A, X>, -) -> OrcRes<(Vec, Snippet<'a, 'b, A, X>)> { +) -> ParseRes<'a, 'b, Vec<(Import, Pos)>, A, X> { let ret = rec(ctx, tail); #[allow(clippy::type_complexity)] // it's an internal function - pub fn rec<'a, 'b, A: AtomInTok, X: fmt::Display>( + pub fn rec<'a, 'b, A: AtomTok, X: ExtraTok>( ctx: &impl Reporter, tail: Snippet<'a, 'b, A, X>, - ) -> OrcRes<(Vec<(Vec>, Option>, Pos)>, Snippet<'a, 'b, A, X>)> { + ) -> ParseRes<'a, 'b, Vec<(Vec>, Option>, Pos)>, A, X> { let comma = intern!(str: ","); let globstar = intern!(str: "*"); let (name, tail) = tail.skip_fluff().pop_front().ok_or_else(|| { - vec![mk_err( - intern!(str: "Expected name"), - "Expected a name, a list of names, or a globstar.", - [Pos::Range(tail.pos()).into()], - )] + mk_err(intern!(str: "Expected name"), "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 { @@ -184,25 +201,27 @@ pub fn parse_multiname<'a, 'b, A: AtomInTok, X: fmt::Display>( ])), }; match (rec(ctx, tail), n) { - (Err(ev), n) => Err(Vec::from_iter(ev.into_iter().chain(n.err()))), - (Ok((_, tail)), Err(e)) => { + (Err(ev), n) => Err(ev.extended(n.err())), + (Ok(Parsed { tail, .. }), Err(e)) => { ctx.report(e); - Ok((vec![], tail)) + Ok(Parsed { output: vec![], tail }) }, - (Ok((n, tail)), Ok(pre)) => - Ok((n.into_iter().update(|i| i.0.push(pre.clone())).collect_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 names = match &name.tok { + 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(vec![mk_err( + return Err(mk_errv( intern!(str: "Unescaped operator in multiname"), "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()))] @@ -226,9 +245,9 @@ pub fn parse_multiname<'a, 'b, A: AtomInTok, X: fmt::Display>( let body = Snippet::new(name, b); for csent in body.split(|n| matches!(n, Token::Name(n) if *n == comma)) { match rec(ctx, csent) { - Err(e) => e.into_iter().for_each(|e| ctx.report(e)), - Ok((v, surplus)) => match surplus.get(0) { - None => ok.extend(v), + Err(e) => ctx.report(e), + Ok(Parsed { output, tail }) => match tail.get(0) { + None => ok.extend(output), Some(t) => ctx.report(mk_err( intern!(str: "Unexpected token in multiname group"), "Unexpected token. Likely missing a :: or , or wanted [] instead of ()", @@ -240,40 +259,39 @@ pub fn parse_multiname<'a, 'b, A: AtomInTok, X: fmt::Display>( ok }, t => - return Err(vec![mk_err( + return Err(mk_errv( intern!(str: "Unrecognized name end"), format!("Names cannot end with {t} tokens"), [Pos::Range(name.range.clone()).into()], - )]), + )), }; - Ok((names, tail)) + Ok(Parsed { output, tail }) } } - ret.map(|(i, tail)| { - let i = Vec::from_iter((i.into_iter()).map(|(p, name, pos)| CompName { - path: VPath::new(p.into_iter().rev()), - name, - pos, - })); - (i, tail) + ret.map(|Parsed { output, tail }| { + let output = (output.into_iter()) + .map(|(p, name, pos)| (Import { path: VPath::new(p.into_iter().rev()), name }, pos)) + .collect_vec(); + Parsed { output, tail } }) } /// A compound name, possibly ending with a globstar #[derive(Debug, Clone)] -pub struct CompName { +pub struct Import { pub path: VPath, pub name: Option>, - pub pos: Pos, } -impl CompName { - pub fn from_api(i: api::CompName) -> Self { - Self { - path: VPath::new(i.path.into_iter().map(deintern)), - name: i.name.map(deintern), - pos: Pos::from_api(&i.location), - } - } +impl Import { + // pub fn from_api(i: api::CompName) -> Self { + // Self { path: VPath::new(i.path.into_iter().map(deintern)), name: i.name.map(deintern) } + // } + // pub fn to_api(&self) -> api::CompName { + // api::CompName { + // path: self.path.iter().map(|t| t.marker()).collect(), + // name: self.name.as_ref().map(|t| t.marker()), + // } + // } } #[cfg(test)] @@ -282,6 +300,14 @@ mod test { use super::Snippet; - fn _covary_snip_a<'a, 'b>(x: Snippet<'static, 'b, Never, ()>) -> Snippet<'a, 'b, Never, ()> { x } - fn _covary_snip_b<'a, 'b>(x: Snippet<'a, 'static, Never, ()>) -> Snippet<'a, 'b, Never, ()> { x } + 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 + } } diff --git a/orchid-base/src/pure_seq.rs b/orchid-base/src/pure_seq.rs new file mode 100644 index 0000000..1717c94 --- /dev/null +++ b/orchid-base/src/pure_seq.rs @@ -0,0 +1,35 @@ +//! Methods to operate on Rust vectors in a declarative manner + +use std::iter; + +/// Pure version of [Vec::push] +/// +/// Create a new vector consisting of the provided vector with the +/// element appended. See [pushed_ref] to use it with a slice +pub fn pushed>(vec: I, t: I::Item) -> C { + vec.into_iter().chain(iter::once(t)).collect() +} + +/// Pure version of [Vec::push] +/// +/// Create a new vector consisting of the provided slice with the +/// element appended. See [pushed] for the owned version +pub fn pushed_ref<'a, T: Clone + 'a, C: FromIterator>( + vec: impl IntoIterator, + t: T, +) -> C { + vec.into_iter().cloned().chain(iter::once(t)).collect() +} + +/// Push an element on the adhoc stack, pass it to the callback, then pop the +/// element out again. +pub fn with_pushed( + vec: &mut Vec, + item: T, + cb: impl for<'a> FnOnce(&'a mut Vec) -> U, +) -> (T, U) { + vec.push(item); + let out = cb(vec); + let item = vec.pop().expect("top element stolen by callback"); + (item, out) +} diff --git a/orchid-base/src/reqnot.rs b/orchid-base/src/reqnot.rs index 0751035..ddcef9a 100644 --- a/orchid-base/src/reqnot.rs +++ b/orchid-base/src/reqnot.rs @@ -1,3 +1,5 @@ +use std::any::Any; +use std::cell::RefCell; use std::marker::PhantomData; use std::ops::{BitAnd, Deref}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -5,17 +7,24 @@ use std::sync::mpsc::{sync_channel, SyncSender}; use std::sync::{Arc, Mutex}; use std::{mem, thread}; +use derive_destructure::destructure; use dyn_clone::{clone_box, DynClone}; use hashbrown::HashMap; use orchid_api_traits::{Channel, Coding, Decode, Encode, MsgSet, Request}; use trait_set::trait_set; -pub struct ReplyToken; +pub struct Receipt; +impl Receipt { + pub fn off_thread(name: String, cb: impl FnOnce() -> Self + Send + 'static) -> Self { + thread::Builder::new().name(name).spawn(cb).unwrap(); + Self + } +} trait_set! { pub trait SendFn = for<'a> FnMut(&'a [u8], ReqNot) + DynClone + Send + 'static; pub trait ReqFn = - FnMut(RequestHandle) -> ReplyToken + DynClone + Send + Sync + 'static; + FnMut(RequestHandle, ::Req) -> Receipt + DynClone + Send + Sync + 'static; pub trait NotifFn = for<'a> FnMut(::Notif, ReqNot) + DynClone + Send + Sync + 'static; } @@ -24,29 +33,39 @@ fn get_id(message: &[u8]) -> (u64, &[u8]) { (u64::from_be_bytes(message[..8].to_vec().try_into().unwrap()), &message[8..]) } -pub struct RequestHandle { - id: u64, - message: ::Req, - parent: ReqNot, +pub trait ReqHandlish { + fn defer_drop(&self, val: impl Any + 'static); +} + +#[derive(destructure)] +pub struct RequestHandle { + defer_drop: RefCell>>, fulfilled: AtomicBool, + id: u64, + parent: ReqNot, } impl RequestHandle { + fn new(parent: ReqNot, id: u64) -> Self { + Self { defer_drop: RefCell::default(), fulfilled: false.into(), parent, id } + } pub fn reqnot(&self) -> ReqNot { self.parent.clone() } - pub fn req(&self) -> &::Req { &self.message } - fn respond(&self, response: &impl Encode) -> ReplyToken { + pub fn handle(&self, _: &U, rep: &U::Response) -> Receipt { self.respond(rep) } + pub fn will_handle_as(&self, _: &U) -> ReqTypToken { ReqTypToken(PhantomData) } + pub fn handle_as(&self, _: ReqTypToken, rep: &U::Response) -> Receipt { + self.respond(rep) + } + pub fn respond(&self, response: &impl Encode) -> Receipt { assert!(!self.fulfilled.swap(true, Ordering::Relaxed), "Already responded to {}", self.id); let mut buf = (!self.id).to_be_bytes().to_vec(); response.encode(&mut buf); let mut send = clone_box(&*self.reqnot().0.lock().unwrap().send); (send)(&buf, self.parent.clone()); - ReplyToken - } - pub fn handle(&self, _: &T, rep: &T::Response) -> ReplyToken { self.respond(rep) } - pub fn will_handle_as(&self, _: &T) -> ReqTypToken { ReqTypToken(PhantomData) } - pub fn handle_as(&self, _token: ReqTypToken, rep: &T::Response) -> ReplyToken { - self.respond(rep) + Receipt } } +impl ReqHandlish for RequestHandle { + fn defer_drop(&self, val: impl Any) { self.defer_drop.borrow_mut().push(Box::new(val)) } +} impl Drop for RequestHandle { fn drop(&mut self) { let done = self.fulfilled.load(Ordering::Relaxed); @@ -56,10 +75,6 @@ impl Drop for RequestHandle { pub struct ReqTypToken(PhantomData); -pub fn respond_with(r: &R, f: impl FnOnce(&R) -> R::Response) -> Vec { - r.respond(f(r)) -} - pub struct ReqNotData { id: u64, send: Box>, @@ -104,8 +119,11 @@ impl ReqNot { let message = ::Req::decode(&mut &payload[..]); let mut req = clone_box(&*g.req); mem::drop(g); - let handle = RequestHandle { id, message, fulfilled: false.into(), parent: self.clone() }; - thread::Builder::new().name(format!("request {id}")).spawn(move || req(handle)).unwrap(); + let rn = self.clone(); + thread::Builder::new() + .name(format!("request {id}")) + .spawn(move || req(RequestHandle::new(rn, id), message)) + .unwrap(); } } @@ -208,12 +226,12 @@ mod test { let receiver = ReqNot::::new( |_, _| panic!("Should not send anything"), clone!(received; move |notif, _| *received.lock().unwrap() = Some(notif)), - |_| panic!("Not receiving a request"), + |_, _| panic!("Not receiving a request"), ); let sender = ReqNot::::new( clone!(receiver; move |d, _| receiver.receive(d.to_vec())), |_, _| panic!("Should not receive notif"), - |_| panic!("Should not receive request"), + |_, _| panic!("Should not receive request"), ); sender.notify(3); assert_eq!(*received.lock().unwrap(), Some(3)); @@ -230,7 +248,7 @@ mod test { move |d, _| receiver.lock().unwrap().as_ref().unwrap().receive(d.to_vec()) }, |_, _| panic!("Should not receive notif"), - |_| panic!("Should not receive request"), + |_, _| panic!("Should not receive request"), )); *receiver.lock().unwrap() = Some(ReqNot::new( { @@ -238,9 +256,9 @@ mod test { move |d, _| sender.receive(d.to_vec()) }, |_, _| panic!("Not receiving notifs"), - |req| { - assert_eq!(req.req(), &TestReq(5)); - req.respond(&6u8) + |hand, req| { + assert_eq!(req, TestReq(5)); + hand.respond(&6u8) }, )); let response = sender.request(TestReq(5)); diff --git a/orchid-base/src/tree.rs b/orchid-base/src/tree.rs index 8433ccd..a003433 100644 --- a/orchid-base/src/tree.rs +++ b/orchid-base/src/tree.rs @@ -1,6 +1,6 @@ use std::borrow::Borrow; use std::cell::RefCell; -use std::fmt::{self, Display, Write}; +use std::fmt::{self, Debug, Display}; use std::iter; use std::marker::PhantomData; use std::ops::Range; @@ -8,26 +8,32 @@ use std::sync::Arc; use itertools::Itertools; use never::Never; +use orchid_api::Placeholder; +use ordered_float::NotNan; use trait_set::trait_set; use crate::api; -use crate::error::OrcErr; +use crate::error::OrcErrv; use crate::interner::{deintern, Tok}; -use crate::name::{NameLike, VName}; +use crate::name::PathSlice; +use crate::parse::Snippet; use crate::tokens::PARENS; +pub use api::PhKind as PhKind; + trait_set! { - pub trait RecurCB<'a, A: AtomInTok, X> = Fn(TokTree<'a, A, X>) -> TokTree<'a, A, X>; + pub trait RecurCB<'a, A: AtomTok, X: ExtraTok> = Fn(TokTree<'a, A, X>) -> TokTree<'a, A, X>; + pub trait ExtraTok = Display + Clone + fmt::Debug; } -pub fn recur<'a, A: AtomInTok, X>( +pub fn recur<'a, A: AtomTok, 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> { 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(_)) => tok, + tok @ (Token::Name(_) | Token::Slot(_) | Token::X(_) | Token::Ph(_) | Token::Macro(_)) => tok, 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()), @@ -36,46 +42,48 @@ pub fn recur<'a, A: AtomInTok, X>( }) } -pub trait AtomInTok: Display + Clone { +pub trait AtomTok: fmt::Display + Clone + fmt::Debug { type Context: ?Sized; fn from_api(atom: &api::Atom, pos: Range, ctx: &mut Self::Context) -> Self; fn to_api(&self) -> api::Atom; } -impl AtomInTok for Never { +impl AtomTok for Never { type Context = Never; fn from_api(_: &api::Atom, _: Range, _: &mut Self::Context) -> Self { panic!() } fn to_api(&self) -> orchid_api::Atom { match *self {} } } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct TreeHandle<'a>(api::TreeTicket, PhantomData<&'a ()>); -impl TreeHandle<'static> { - pub fn new(tt: api::TreeTicket) -> Self { TreeHandle(tt, PhantomData) } +pub struct TokHandle<'a>(api::TreeTicket, PhantomData<&'a ()>); +impl TokHandle<'static> { + pub fn new(tt: api::TreeTicket) -> Self { TokHandle(tt, PhantomData) } } -impl<'a> TreeHandle<'a> { +impl<'a> TokHandle<'a> { pub fn ticket(self) -> api::TreeTicket { self.0 } } -impl<'a> Display for TreeHandle<'a> { +impl<'a> Display for TokHandle<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Handle({})", self.0.0) } } #[derive(Clone, Debug)] -pub struct TokTree<'a, A: AtomInTok, X> { +pub struct TokTree<'a, A: AtomTok, X: ExtraTok> { pub tok: Token<'a, A, X>, pub range: Range, } -impl<'a, A: AtomInTok, X> TokTree<'a, A, X> { +impl<'a, A: AtomTok, X: ExtraTok> TokTree<'a, A, X> { pub fn from_api(tt: &api::TokenTree, ctx: &mut A::Context) -> Self { let tok = match &tt.token { api::Token::Atom(a) => Token::Atom(A::from_api(a, tt.range.clone(), ctx)), api::Token::BR => Token::BR, api::Token::NS => Token::NS, - api::Token::Bottom(e) => Token::Bottom(e.iter().map(OrcErr::from_api).collect()), - api::Token::Lambda(arg) => Token::LambdaHead(ttv_from_api(arg, ctx)), + api::Token::Bottom(e) => Token::Bottom(OrcErrv::from_api(e)), + api::Token::LambdaHead(arg) => Token::LambdaHead(ttv_from_api(arg, ctx)), api::Token::Name(name) => Token::Name(deintern(*name)), - api::Token::S(par, b) => Token::S(par.clone(), ttv_from_api(b, ctx)), + api::Token::S(par, b) => Token::S(*par, ttv_from_api(b, ctx)), api::Token::Comment(c) => Token::Comment(c.clone()), - api::Token::Slot(id) => Token::Slot(TreeHandle::new(*id)), + api::Token::Slot(id) => Token::Slot(TokHandle::new(*id)), + api::Token::Ph(ph) => Token::Ph(Ph {name: deintern(ph.name), kind: ph.kind }), + api::Token::Macro(prio) => Token::Macro(*prio) }; Self { range: tt.range.clone(), tok } } @@ -88,66 +96,110 @@ impl<'a, A: AtomInTok, X> TokTree<'a, A, X> { Token::Atom(a) => api::Token::Atom(a.to_api()), Token::BR => api::Token::BR, Token::NS => api::Token::NS, - Token::Bottom(e) => api::Token::Bottom(e.iter().map(OrcErr::to_api).collect()), + Token::Bottom(e) => api::Token::Bottom(e.to_api()), Token::Comment(c) => api::Token::Comment(c.clone()), - Token::LambdaHead(arg) => - api::Token::Lambda(arg.iter().map(|t| t.to_api(do_extra)).collect_vec()), + Token::LambdaHead(arg) => api::Token::LambdaHead(ttv_to_api(arg, do_extra)), Token::Name(n) => api::Token::Name(n.marker()), Token::Slot(tt) => api::Token::Slot(tt.ticket()), - Token::S(p, b) => api::Token::S(p.clone(), b.iter().map(|t| t.to_api(do_extra)).collect()), + Token::S(p, b) => api::Token::S(*p, ttv_to_api(b, do_extra)), + Token::Ph(Ph { name, kind }) => api::Token::Ph(Placeholder { name: name.marker(), kind: *kind }), Token::X(x) => return do_extra(x, self.range.clone()), + Token::Macro(prio) => api::Token::Macro(*prio), }; api::TokenTree { range: self.range.clone(), token } } + + pub fn into_api( + self, + do_extra: &mut impl FnMut(X, Range) -> api::TokenTree, + ) -> api::TokenTree { + let token = match self.tok { + Token::Atom(a) => api::Token::Atom(a.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)), + Token::Name(n) => api::Token::Name(n.marker()), + Token::Slot(tt) => api::Token::Slot(tt.ticket()), + Token::S(p, b) => api::Token::S(p, ttv_into_api(b, do_extra)), + Token::Ph(Ph { kind, name }) => api::Token::Ph(Placeholder { name: name.marker(), kind }), + Token::X(x) => return do_extra(x, self.range.clone()), + Token::Macro(prio) => api::Token::Macro(prio), + }; + api::TokenTree { range: self.range.clone(), token } + } + + pub fn is_kw(&self, tk: Tok) -> bool { self.tok.is_kw(tk) } + pub fn as_name(&self) -> Option> { + if let Token::Name(n) = &self.tok { Some(n.clone()) } else { None } + } + pub fn as_s(&self, par: Paren) -> Option> { + self.tok.as_s(par).map(|slc| Snippet::new(self, slc)) + } + 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; + body.insert(0, Token::LambdaHead(arg).at(arg_range)); + Token::S(Paren::Round, body).at(s_range) + } } -impl<'a, A: AtomInTok + Display, X: Display> Display for TokTree<'a, A, X> { + +impl<'a, A: AtomTok, X: ExtraTok> Display for TokTree<'a, A, X> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.tok) } } -pub fn ttv_from_api( +pub fn ttv_from_api( tokv: impl IntoIterator>, ctx: &mut A::Context, ) -> Vec> { tokv.into_iter().map(|t| TokTree::::from_api(t.borrow(), ctx)).collect() } -pub fn ttv_to_api<'a, A: AtomInTok, X>( +pub fn ttv_to_api<'a, A: AtomTok, X: ExtraTok>( tokv: impl IntoIterator>>, do_extra: &mut impl FnMut(&X, Range) -> api::TokenTree, ) -> Vec { - tokv - .into_iter() - .map(|tok| { - let tt: &TokTree = tok.borrow(); - tt.to_api(do_extra) - }) - .collect_vec() + tokv.into_iter().map(|tok| Borrow::>::borrow(&tok).to_api(do_extra)).collect_vec() } -pub fn vname_tv<'a: 'b, 'b, A: AtomInTok + 'a, X: 'a>( - name: &'b VName, - ran: Range, +pub fn ttv_into_api<'a, A: AtomTok, X: ExtraTok>( + tokv: impl IntoIterator>, + do_extra: &mut impl FnMut(X, Range) -> api::TokenTree, +) -> Vec { + tokv.into_iter().map(|t| t.into_api(do_extra)).collect_vec() +} + +/// This takes a position and not a range because it assigns the range to +/// multiple leaf tokens, which is only valid if it's a zero-width range +pub fn vname_tv<'a: 'b, 'b, A: AtomTok + 'a, X: ExtraTok + 'a>( + name: &'b PathSlice, + pos: u32, ) -> impl Iterator> + 'b { - let (head, tail) = name.split_first(); - iter::once(Token::Name(head)) - .chain(tail.iter().flat_map(|t| [Token::NS, Token::Name(t)])) - .map(move |t| t.at(ran.clone())) + let (head, tail) = name.split_first().expect("Empty vname"); + iter::once(Token::Name(head.clone())) + .chain(tail.iter().flat_map(|t| [Token::NS, Token::Name(t.clone())])) + .map(move |t| t.at(pos..pos)) } -pub fn wrap_tokv<'a, A: AtomInTok + 'a, X: 'a>( - items: Vec>, - range: Range, +pub fn wrap_tokv<'a, A: AtomTok, X: ExtraTok>( + items: impl IntoIterator> ) -> TokTree<'a, A, X> { - match items.len() { - 1 => items.into_iter().next().unwrap(), - _ => Token::S(api::Paren::Round, items).at(range), + let items_v = items.into_iter().collect_vec(); + match items_v.len() { + 0 => panic!("A tokv with no elements is illegal"), + 1 => items_v.into_iter().next().unwrap(), + _ => { + let range = items_v.first().unwrap().range.start..items_v.last().unwrap().range.end; + Token::S(api::Paren::Round, items_v).at(range) + }, } } pub use api::Paren; #[derive(Clone, Debug)] -pub enum Token<'a, A: AtomInTok, X> { +pub enum Token<'a, A: AtomTok, X: ExtraTok> { Comment(Arc), LambdaHead(Vec>), Name(Tok), @@ -155,14 +207,25 @@ pub enum Token<'a, A: AtomInTok, X> { BR, S(Paren, Vec>), Atom(A), - Bottom(Vec), - Slot(TreeHandle<'a>), + Bottom(OrcErrv), + Slot(TokHandle<'a>), X(X), + Ph(Ph), + Macro(Option>), } -impl<'a, A: AtomInTok, X> Token<'a, A, X> { +impl<'a, A: AtomTok, X: ExtraTok> Token<'a, A, X> { pub fn at(self, range: Range) -> TokTree<'a, A, X> { 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>]> { + match self { + Self::S(p, b) if *p == par => Some(b), + _ => None, + } + } } -impl<'a, A: AtomInTok + Display, X: Display> Display for Token<'a, A, X> { +impl<'a, A: AtomTok, X: ExtraTok> Display for Token<'a, A, X> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static PAREN_LEVEL: RefCell = 0.into(); @@ -175,33 +238,47 @@ impl<'a, A: AtomInTok + Display, X: Display> Display for Token<'a, A, X> { r } match self { - Self::Atom(a) => f.write_str(&indent(&format!("{a}"), get_indent(), false)), + Self::Atom(a) => f.write_str(&indent(&format!("{a} "), get_indent(), false)), Self::BR => write!(f, "\n{}", " ".repeat(get_indent())), - Self::Bottom(err) => write!( - f, - "Botttom({})", - err.iter().map(|e| format!("{}: {}", e.description, e.message)).join(", ") - ), - Self::Comment(c) => write!(f, "--[{c}]--"), - Self::LambdaHead(arg) => with_indent(|| write!(f, "\\ {} .", ttv_fmt(arg))), - Self::NS => f.write_str("::"), - Self::Name(n) => f.write_str(n), - Self::Slot(th) => write!(f, "{th}"), + Self::Bottom(err) if err.len() == 1 => write!(f, "Bottom({}) ", err.one().unwrap()), + Self::Bottom(err) => { + write!(f, "Botttom(\n{}) ", indent(&err.to_string(), get_indent() + 1, true)) + }, + Self::Comment(c) => write!(f, "--[{c}]-- "), + Self::LambdaHead(arg) => with_indent(|| write!(f, "\\ {} . ", ttv_fmt(arg))), + Self::NS => f.write_str(":: "), + Self::Name(n) => write!(f, "{n} "), + Self::Slot(th) => write!(f, "{th} "), + Self::Ph(Ph { kind, name }) => match &kind { + PhKind::Scalar => write!(f, "${name}"), + PhKind::Vector { at_least_one, priority } => { + if *at_least_one { write!(f, ".")? } + write!(f, "..${name}")?; + if 0 < *priority { write!(f, "{priority}") } else { Ok(()) } + } + } Self::S(p, b) => { let (lp, rp, _) = PARENS.iter().find(|(_, _, par)| par == p).unwrap(); - f.write_char(*lp)?; + write!(f, "{lp} ")?; with_indent(|| f.write_str(&ttv_fmt(b)))?; - f.write_char(*rp) + write!(f, "{rp} ") }, - Self::X(x) => write!(f, "{x}"), + Self::X(x) => write!(f, "{x} "), + Self::Macro(None) => write!(f, "macro "), + Self::Macro(Some(prio)) => write!(f, "macro({prio})"), } } } -pub fn ttv_fmt<'a>( - ttv: impl IntoIterator>, +pub fn ttv_range(ttv: &[TokTree<'_, impl AtomTok, impl ExtraTok>]) -> Range { + assert!(!ttv.is_empty(), "Empty slice has no range"); + ttv.first().unwrap().range.start..ttv.last().unwrap().range.end +} + +pub fn ttv_fmt<'a: 'b, 'b>( + ttv: impl IntoIterator>, ) -> String { - ttv.into_iter().join(" ") + ttv.into_iter().join("") } pub fn indent(s: &str, lvl: usize, first: bool) -> String { @@ -214,13 +291,23 @@ pub fn indent(s: &str, lvl: usize, first: bool) -> String { } } +#[derive(Clone, Debug)] +pub struct Ph { + pub name: Tok, + pub kind: PhKind, +} +impl Ph { + pub fn from_api(api: &Placeholder) -> Self { Self { name: deintern(api.name), kind: api.kind } } + pub fn to_api(&self) -> Placeholder { Placeholder { name: self.name.marker(), kind: self.kind } } +} + #[cfg(test)] mod test { use super::*; #[test] fn test_covariance() { - fn _f<'a>(x: Token<'static, Never, ()>) -> Token<'a, Never, ()> { x } + fn _f<'a>(x: Token<'static, Never, Never>) -> Token<'a, Never, Never> { x } } #[test] diff --git a/orchid-extension/src/atom.rs b/orchid-extension/src/atom.rs index c3f1157..b660d8d 100644 --- a/orchid-extension/src/atom.rs +++ b/orchid-extension/src/atom.rs @@ -3,44 +3,42 @@ use std::fmt; use std::io::{Read, Write}; use std::marker::PhantomData; use std::ops::{Deref, Range}; -use std::sync::OnceLock; +use std::sync::{Arc, OnceLock}; use dyn_clone::{clone_box, DynClone}; -use never::Never; -use orchid_api::ExprTicket; -use orchid_api_traits::{enc_vec, Coding, Decode, Request}; +use orchid_api_traits::{enc_vec, Coding, Decode, Encode, Request}; use orchid_base::error::{mk_err, OrcErr, OrcRes}; use orchid_base::intern; use orchid_base::location::Pos; +use orchid_base::name::Sym; use orchid_base::reqnot::Requester; -use orchid_base::tree::AtomInTok; +use orchid_base::tree::AtomTok; use trait_set::trait_set; use crate::api; // use crate::error::{ProjectError, ProjectResult}; -use crate::expr::{ExprHandle, GenClause, GenExpr, OwnedExpr}; +use crate::expr::{Expr, ExprData, ExprHandle, ExprKind}; use crate::system::{atom_info_for, downcast_atom, DynSystemCard, SysCtx}; pub trait AtomCard: 'static + Sized { type Data: Clone + Coding + Sized; - type Req: Coding; } pub trait AtomicVariant {} pub trait Atomic: 'static + Sized { type Variant: AtomicVariant; type Data: Clone + Coding + Sized; - type Req: Coding; + fn reg_reqs() -> MethodSet; } impl AtomCard for A { type Data = ::Data; - type Req = ::Req; } pub trait AtomicFeatures: Atomic { fn factory(self) -> AtomFactory; type Info: AtomDynfo; - const INFO: &'static Self::Info; + fn info() -> Self::Info; + fn dynfo() -> Box; } pub trait ToAtom { fn to_atom_factory(self) -> AtomFactory; @@ -54,17 +52,18 @@ impl ToAtom for AtomFactory { pub trait AtomicFeaturesImpl { fn _factory(self) -> AtomFactory; type _Info: AtomDynfo; - const _INFO: &'static Self::_Info; + fn _info() -> Self::_Info; } -impl + ?Sized> AtomicFeatures for A { +impl> AtomicFeatures for A { fn factory(self) -> AtomFactory { self._factory() } type Info = >::_Info; - const INFO: &'static Self::Info = Self::_INFO; + fn info() -> Self::Info { Self::_info() } + fn dynfo() -> Box { Box::new(Self::info()) } } pub fn get_info( sys: &(impl DynSystemCard + ?Sized), -) -> (api::AtomId, &'static dyn AtomDynfo) { +) -> (api::AtomId, Box) { atom_info_for(sys, TypeId::of::()).unwrap_or_else(|| { panic!("Atom {} not associated with system {}", type_name::(), sys.name()) }) @@ -72,34 +71,47 @@ pub fn get_info( #[derive(Clone)] pub struct ForeignAtom<'a> { - pub expr: Option, - pub char_marker: PhantomData<&'a ()>, + pub expr: Option>, + pub _life: PhantomData<&'a ()>, pub ctx: SysCtx, pub atom: api::Atom, pub pos: Pos, } impl<'a> ForeignAtom<'a> { - pub fn oex_opt(self) -> Option { - self.expr.map(|handle| { - let gen_expr = GenExpr { pos: self.pos, clause: GenClause::Atom(handle.tk, self.atom) }; - OwnedExpr { handle, val: OnceLock::from(Box::new(gen_expr)) } - }) + pub fn oex_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 { handle: Some(handle), val: OnceLock::from(data) }) } } impl ForeignAtom<'static> { - pub fn oex(self) -> OwnedExpr { self.oex_opt().unwrap() } + pub fn oex(self) -> Expr { self.oex_opt().unwrap() } + pub(crate) fn new(handle: Arc, atom: api::Atom, pos: Pos) -> Self { + ForeignAtom { _life: PhantomData, atom, ctx: handle.ctx.clone(), expr: Some(handle), pos } + } + pub fn request(&self, m: M) -> Option { + let rep = self.ctx.reqnot.request(api::Fwd( + self.atom.clone(), + Sym::parse(M::NAME).unwrap().tok().marker(), + enc_vec(&m) + ))?; + Some(M::Response::decode(&mut &rep[..])) + } } impl<'a> fmt::Display for ForeignAtom<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}::{:?}", if self.expr.is_some() { "Clause" } else { "Tok" }, self.atom) } } -impl<'a> AtomInTok for ForeignAtom<'a> { +impl<'a> fmt::Debug for ForeignAtom<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ForeignAtom({self})") } +} +impl<'a> AtomTok for ForeignAtom<'a> { type Context = SysCtx; fn from_api(atom: &api::Atom, pos: Range, ctx: &mut Self::Context) -> Self { Self { atom: atom.clone(), - char_marker: PhantomData, + _life: PhantomData, ctx: ctx.clone(), expr: None, pos: Pos::Range(pos), @@ -108,7 +120,7 @@ impl<'a> AtomInTok for ForeignAtom<'a> { fn to_api(&self) -> orchid_api::Atom { self.atom.clone() } } -pub struct NotTypAtom(pub Pos, pub OwnedExpr, pub &'static dyn AtomDynfo); +pub struct NotTypAtom(pub Pos, pub Expr, pub Box); impl NotTypAtom { pub fn mk_err(&self) -> OrcErr { mk_err( @@ -119,26 +131,86 @@ impl NotTypAtom { } } +pub trait AtomMethod: Request { + const NAME: &str; +} +pub trait Supports: AtomCard { + fn handle(&self, ctx: SysCtx, req: M) -> ::Response; +} + +trait_set! { + trait AtomReqCb = Fn(&A, SysCtx, &mut dyn Read, &mut dyn Write) + Send + Sync +} + +pub struct AtomReqHandler { + key: Sym, + cb: Box>, +} + +pub struct MethodSet { + handlers: Vec>, +} +impl MethodSet { + pub fn new() -> Self { Self{ handlers: vec![] } } + + pub fn handle(mut self) -> Self where A: Supports { + self.handlers.push(AtomReqHandler { + key: Sym::parse(M::NAME).expect("AtomMethod::NAME cannoot be empty"), + cb: Box::new(move | + a: &A, + ctx: SysCtx, + req: &mut dyn Read, + rep: &mut dyn Write + | { + Supports::::handle(a, ctx, M::decode(req)).encode(rep); + }) + }); + self + } + + pub(crate) fn dispatch( + &self, atom: &A, ctx: SysCtx, key: Sym, req: &mut dyn Read, rep: &mut dyn Write + ) -> bool { + match self.handlers.iter().find(|h| h.key == key) { + None => false, + Some(handler) => { + (handler.cb)(atom, ctx, req, rep); + true + }, + } + } +} + +impl Default for MethodSet { + fn default() -> Self { + Self::new() + } +} + #[derive(Clone)] pub struct TypAtom<'a, A: AtomicFeatures> { pub data: ForeignAtom<'a>, pub value: A::Data, } impl TypAtom<'static, A> { - pub fn downcast(expr: ExprHandle) -> Result { - match OwnedExpr::new(expr).foreign_atom() { - Err(oe) => Err(NotTypAtom(oe.get_data().pos.clone(), oe, A::INFO)), + pub fn downcast(expr: Arc) -> Result { + match Expr::new(expr).foreign_atom() { + Err(oe) => Err(NotTypAtom(oe.get_data().pos.clone(), oe, Box::new(A::info()))), Ok(atm) => match downcast_atom::(atm) { - Err(fa) => Err(NotTypAtom(fa.pos.clone(), fa.oex(), A::INFO)), + Err(fa) => Err(NotTypAtom(fa.pos.clone(), fa.oex(), Box::new(A::info()))), Ok(tatom) => Ok(tatom), }, } } } impl<'a, A: AtomicFeatures> TypAtom<'a, A> { - pub fn request + Request>(&self, req: R) -> R::Response { - R::Response::decode( - &mut &self.data.ctx.reqnot.request(api::Fwd(self.data.atom.clone(), enc_vec(&req)))[..], + pub fn request(&self, req: M) -> M::Response where A: Supports { + M::Response::decode( + &mut &self.data.ctx.reqnot.request(api::Fwd( + self.data.atom.clone(), + Sym::parse(M::NAME).unwrap().tok().marker(), + enc_vec(&req) + )).unwrap()[..] ) } } @@ -153,14 +225,13 @@ pub trait AtomDynfo: Send + Sync + 'static { fn tid(&self) -> TypeId; fn name(&self) -> &'static str; fn decode(&self, ctx: AtomCtx<'_>) -> Box; - fn call(&self, ctx: AtomCtx<'_>, arg: api::ExprTicket) -> GenExpr; - fn call_ref(&self, ctx: AtomCtx<'_>, arg: api::ExprTicket) -> GenExpr; - fn same(&self, ctx: AtomCtx<'_>, other: &api::Atom) -> bool; + fn call(&self, ctx: AtomCtx<'_>, arg: api::ExprTicket) -> Expr; + fn call_ref(&self, ctx: AtomCtx<'_>, arg: api::ExprTicket) -> Expr; fn print(&self, ctx: AtomCtx<'_>) -> String; - fn handle_req(&self, ctx: AtomCtx<'_>, req: &mut dyn Read, rep: &mut dyn Write); - fn command(&self, ctx: AtomCtx<'_>) -> OrcRes>; - fn serialize(&self, ctx: AtomCtx<'_>, write: &mut dyn Write) -> Vec; - fn deserialize(&self, ctx: SysCtx, data: &[u8], refs: &[ExprTicket]) -> api::Atom; + fn handle_req(&self, ctx: AtomCtx<'_>, key: Sym, req: &mut dyn Read, rep: &mut dyn Write) -> bool; + fn command(&self, ctx: AtomCtx<'_>) -> OrcRes>; + fn serialize(&self, ctx: AtomCtx<'_>, write: &mut dyn Write) -> Vec; + fn deserialize(&self, ctx: SysCtx, data: &[u8], refs: &[api::ExprTicket]) -> api::Atom; fn drop(&self, ctx: AtomCtx<'_>); } @@ -177,6 +248,12 @@ impl AtomFactory { impl Clone for AtomFactory { fn clone(&self) -> Self { AtomFactory(clone_box(&*self.0)) } } +impl fmt::Debug for AtomFactory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AtomFactory") } +} +impl fmt::Display for AtomFactory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AtomFactory") } +} pub fn err_not_callable() -> OrcErr { mk_err(intern!(str: "This atom is not callable"), "Attempted to apply value as function", []) @@ -185,26 +262,3 @@ pub fn err_not_callable() -> OrcErr { pub fn err_not_command() -> OrcErr { mk_err(intern!(str: "This atom is not a command"), "Settled on an inactionable value", []) } - -pub trait ReqPck: Sized { - type W: Write + ?Sized; - fn unpack<'a>(self) -> (T::Req, &'a mut Self::W, SysCtx) - where Self: 'a; - fn never(self) - where T: AtomCard { - } -} - -pub(crate) struct RequestPack<'a, T: AtomCard + ?Sized, W: Write + ?Sized> { - pub req: T::Req, - pub write: &'a mut W, - pub sys: SysCtx, -} - -impl<'a, T: AtomCard + ?Sized, W: Write + ?Sized> ReqPck for RequestPack<'a, T, W> { - type W = W; - fn unpack<'b>(self) -> (::Req, &'b mut Self::W, SysCtx) - where 'a: 'b { - (self.req, self.write, self.sys) - } -} diff --git a/orchid-extension/src/atom_owned.rs b/orchid-extension/src/atom_owned.rs index dd42534..460fd22 100644 --- a/orchid-extension/src/atom_owned.rs +++ b/orchid-extension/src/atom_owned.rs @@ -1,19 +1,19 @@ use std::any::{type_name, Any, TypeId}; use std::borrow::Cow; use std::io::{Read, Write}; -use std::marker::PhantomData; +use std::sync::Arc; use itertools::Itertools; use orchid_api_traits::{enc_vec, Decode, Encode}; use orchid_base::error::OrcRes; use orchid_base::id_store::{IdRecord, IdStore}; +use orchid_base::name::Sym; use crate::api; use crate::atom::{ - err_not_callable, err_not_command, get_info, AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, - AtomicFeaturesImpl, AtomicVariant, ReqPck, RequestPack, + err_not_callable, err_not_command, get_info, AtomCard, AtomCtx, AtomDynfo, AtomFactory, MethodSet, Atomic, AtomicFeaturesImpl, AtomicVariant, }; -use crate::expr::{bot, ExprHandle, GenExpr}; +use crate::expr::{bot, Expr, ExprHandle}; use crate::system::SysCtx; pub struct OwnedVariant; @@ -28,15 +28,17 @@ impl> AtomicFeaturesImpl Self::_Info { + OwnedAtomDynfo(A::reg_reqs()) + } type _Info = OwnedAtomDynfo; - const _INFO: &'static Self::_Info = &OwnedAtomDynfo(PhantomData); } fn with_atom(id: api::AtomId, f: impl FnOnce(IdRecord<'_, Box>) -> U) -> U { f(OBJ_STORE.get(id.0).unwrap_or_else(|| panic!("Received invalid atom ID: {}", id.0))) } -pub struct OwnedAtomDynfo(PhantomData); +pub struct OwnedAtomDynfo(MethodSet); impl AtomDynfo for OwnedAtomDynfo { fn print(&self, AtomCtx(_, id, ctx): AtomCtx<'_>) -> String { with_atom(id.unwrap(), |a| a.dyn_print(ctx)) @@ -46,19 +48,18 @@ impl AtomDynfo for OwnedAtomDynfo { fn decode(&self, AtomCtx(data, ..): AtomCtx) -> Box { Box::new(::Data::decode(&mut &data[..])) } - fn call(&self, AtomCtx(_, id, ctx): AtomCtx, arg: api::ExprTicket) -> GenExpr { + fn call(&self, AtomCtx(_, id, ctx): AtomCtx, arg: api::ExprTicket) -> Expr { with_atom(id.unwrap(), |a| a.remove().dyn_call(ctx, arg)) } - fn call_ref(&self, AtomCtx(_, id, ctx): AtomCtx, arg: api::ExprTicket) -> GenExpr { + fn call_ref(&self, AtomCtx(_, id, ctx): AtomCtx, arg: api::ExprTicket) -> Expr { with_atom(id.unwrap(), |a| a.dyn_call_ref(ctx, arg)) } - fn same(&self, AtomCtx(_, id, ctx): AtomCtx, a2: &api::Atom) -> bool { - with_atom(id.unwrap(), |a1| with_atom(a2.drop.unwrap(), |a2| a1.dyn_same(ctx, &**a2))) + fn handle_req(&self, AtomCtx(_, id, ctx): AtomCtx, key: Sym, req: &mut dyn Read, rep: &mut dyn Write) -> bool { + with_atom(id.unwrap(), |a| { + self.0.dispatch(a.as_any_ref().downcast_ref().unwrap(), ctx, key, req, rep) + }) } - fn handle_req(&self, AtomCtx(_, id, ctx): AtomCtx, req: &mut dyn Read, rep: &mut dyn Write) { - with_atom(id.unwrap(), |a| a.dyn_handle_req(ctx, req, rep)) - } - fn command(&self, AtomCtx(_, id, ctx): AtomCtx<'_>) -> OrcRes> { + fn command(&self, AtomCtx(_, id, ctx): AtomCtx<'_>) -> OrcRes> { with_atom(id.unwrap(), |a| a.remove().dyn_command(ctx)) } fn drop(&self, AtomCtx(_, id, ctx): AtomCtx) { @@ -71,10 +72,13 @@ impl AtomDynfo for OwnedAtomDynfo { ) -> Vec { let id = id.unwrap(); id.encode(write); - with_atom(id, |a| a.dyn_serialize(ctx, write)).into_iter().map(|t| t.into_tk()).collect_vec() + with_atom(id, |a| a.dyn_serialize(ctx, write)) + .into_iter() + .map(|t| t.handle.unwrap().tk) + .collect_vec() } fn deserialize(&self, ctx: SysCtx, data: &[u8], refs: &[api::ExprTicket]) -> orchid_api::Atom { - let refs = refs.iter().map(|tk| ExprHandle::from_args(ctx.clone(), *tk)); + let refs = refs.iter().map(|tk| Expr::new(Arc::new(ExprHandle::from_args(ctx.clone(), *tk)))); let obj = T::deserialize(DeserCtxImpl(data, &ctx), T::Refs::from_iter(refs)); obj._factory().build(ctx) } @@ -100,27 +104,25 @@ impl<'a> DeserializeCtx for DeserCtxImpl<'a> { } pub trait RefSet { - fn from_iter + ExactSizeIterator>(refs: I) -> Self; - fn to_vec(self) -> Vec; + fn from_iter + ExactSizeIterator>(refs: I) -> Self; + fn to_vec(self) -> Vec; } impl RefSet for () { - fn to_vec(self) -> Vec { Vec::new() } - fn from_iter + ExactSizeIterator>(refs: I) -> Self { + fn to_vec(self) -> Vec { Vec::new() } + fn from_iter + ExactSizeIterator>(refs: I) -> Self { assert_eq!(refs.len(), 0, "Expected no refs") } } -impl RefSet for Vec { - fn from_iter + ExactSizeIterator>(refs: I) -> Self { - refs.collect_vec() - } - fn to_vec(self) -> Vec { self } +impl RefSet for Vec { + fn from_iter + ExactSizeIterator>(refs: I) -> Self { refs.collect_vec() } + fn to_vec(self) -> Vec { self } } -impl RefSet for [ExprHandle; N] { - fn to_vec(self) -> Vec { self.into_iter().collect_vec() } - fn from_iter + ExactSizeIterator>(refs: I) -> Self { +impl RefSet for [Expr; N] { + fn to_vec(self) -> Vec { self.into_iter().collect_vec() } + fn from_iter + ExactSizeIterator>(refs: I) -> Self { assert_eq!(refs.len(), N, "Wrong number of refs provided"); refs.collect_vec().try_into().unwrap_or_else(|_: Vec<_>| unreachable!()) } @@ -131,22 +133,15 @@ pub trait OwnedAtom: Atomic + Send + Sync + Any + Clone type Refs: RefSet; fn val(&self) -> Cow<'_, Self::Data>; #[allow(unused_variables)] - fn call_ref(&self, arg: ExprHandle) -> GenExpr { bot(err_not_callable()) } - fn call(self, arg: ExprHandle) -> GenExpr { + fn call_ref(&self, arg: ExprHandle) -> Expr { bot([err_not_callable()]) } + fn call(self, arg: ExprHandle) -> Expr { let ctx = arg.get_ctx(); let gcl = self.call_ref(arg); self.free(ctx); gcl } #[allow(unused_variables)] - fn same(&self, ctx: SysCtx, other: &Self) -> bool { - let tname = type_name::(); - writeln!(ctx.logger, "Override OwnedAtom::same for {tname} if it can appear in macro input"); - false - } - fn handle_req(&self, pck: impl ReqPck); - #[allow(unused_variables)] - fn command(self, ctx: SysCtx) -> OrcRes> { Err(vec![err_not_command()]) } + fn command(self, ctx: SysCtx) -> OrcRes> { Err(err_not_command().into()) } #[allow(unused_variables)] fn free(self, ctx: SysCtx) {} #[allow(unused_variables)] @@ -159,41 +154,27 @@ pub trait DynOwnedAtom: Send + Sync + 'static { fn atom_tid(&self) -> TypeId; fn as_any_ref(&self) -> &dyn Any; fn encode(&self, buffer: &mut dyn Write); - fn dyn_call_ref(&self, ctx: SysCtx, arg: api::ExprTicket) -> GenExpr; - fn dyn_call(self: Box, ctx: SysCtx, arg: api::ExprTicket) -> GenExpr; - fn dyn_same(&self, ctx: SysCtx, other: &dyn DynOwnedAtom) -> bool; - fn dyn_handle_req(&self, ctx: SysCtx, req: &mut dyn Read, rep: &mut dyn Write); - fn dyn_command(self: Box, ctx: SysCtx) -> OrcRes>; + fn dyn_call_ref(&self, ctx: SysCtx, arg: api::ExprTicket) -> Expr; + fn dyn_call(self: Box, ctx: SysCtx, arg: api::ExprTicket) -> Expr; + fn dyn_command(self: Box, ctx: SysCtx) -> OrcRes>; fn dyn_free(self: Box, ctx: SysCtx); fn dyn_print(&self, ctx: SysCtx) -> String; - fn dyn_serialize(&self, ctx: SysCtx, sink: &mut dyn Write) -> Vec; + fn dyn_serialize(&self, ctx: SysCtx, sink: &mut dyn Write) -> Vec; } impl DynOwnedAtom for T { fn atom_tid(&self) -> TypeId { TypeId::of::() } fn as_any_ref(&self) -> &dyn Any { self } fn encode(&self, buffer: &mut dyn Write) { self.val().as_ref().encode(buffer) } - fn dyn_call_ref(&self, ctx: SysCtx, arg: api::ExprTicket) -> GenExpr { + fn dyn_call_ref(&self, ctx: SysCtx, arg: api::ExprTicket) -> Expr { self.call_ref(ExprHandle::from_args(ctx, arg)) } - fn dyn_call(self: Box, ctx: SysCtx, arg: api::ExprTicket) -> GenExpr { + fn dyn_call(self: Box, ctx: SysCtx, arg: api::ExprTicket) -> Expr { self.call(ExprHandle::from_args(ctx, arg)) } - fn dyn_same(&self, ctx: SysCtx, other: &dyn DynOwnedAtom) -> bool { - if TypeId::of::() != other.as_any_ref().type_id() { - return false; - } - let other_self = other.as_any_ref().downcast_ref().expect("The type_ids are the same"); - self.same(ctx, other_self) - } - fn dyn_handle_req(&self, sys: SysCtx, req: &mut dyn Read, write: &mut dyn Write) { - let pack = - RequestPack:: { req: ::Req::decode(req), write, sys }; - self.handle_req(pack) - } - fn dyn_command(self: Box, ctx: SysCtx) -> OrcRes> { self.command(ctx) } + fn dyn_command(self: Box, ctx: SysCtx) -> OrcRes> { self.command(ctx) } fn dyn_free(self: Box, ctx: SysCtx) { self.free(ctx) } fn dyn_print(&self, ctx: SysCtx) -> String { self.print(ctx) } - fn dyn_serialize(&self, ctx: SysCtx, sink: &mut dyn Write) -> Vec { + fn dyn_serialize(&self, ctx: SysCtx, sink: &mut dyn Write) -> Vec { self.serialize(ctx, sink).to_vec() } } diff --git a/orchid-extension/src/atom_thin.rs b/orchid-extension/src/atom_thin.rs index d0c6e77..711d7aa 100644 --- a/orchid-extension/src/atom_thin.rs +++ b/orchid-extension/src/atom_thin.rs @@ -1,17 +1,15 @@ use std::any::{type_name, Any, TypeId}; use std::io::Write; -use std::marker::PhantomData; -use orchid_api::ExprTicket; -use orchid_api_traits::{enc_vec, Coding, Decode}; +use orchid_api_traits::{enc_vec, Coding}; use orchid_base::error::OrcRes; +use orchid_base::name::Sym; use crate::api; use crate::atom::{ - err_not_callable, err_not_command, get_info, AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, - AtomicFeaturesImpl, AtomicVariant, ReqPck, RequestPack, + err_not_callable, err_not_command, get_info, AtomCard, AtomCtx, AtomDynfo, AtomFactory, MethodSet, Atomic, AtomicFeaturesImpl, AtomicVariant }; -use crate::expr::{bot, ExprHandle, GenExpr}; +use crate::expr::{bot, Expr, ExprHandle}; use crate::system::SysCtx; pub struct ThinVariant; @@ -25,11 +23,13 @@ impl> AtomicFeaturesImpl Self::_Info { + ThinAtomDynfo(Self::reg_reqs()) + } type _Info = ThinAtomDynfo; - const _INFO: &'static Self::_Info = &ThinAtomDynfo(PhantomData); } -pub struct ThinAtomDynfo(PhantomData); +pub struct ThinAtomDynfo(MethodSet); impl AtomDynfo for ThinAtomDynfo { fn print(&self, AtomCtx(buf, _, ctx): AtomCtx<'_>) -> String { T::decode(&mut &buf[..]).print(ctx) @@ -37,32 +37,29 @@ impl AtomDynfo for ThinAtomDynfo { fn tid(&self) -> TypeId { TypeId::of::() } fn name(&self) -> &'static str { type_name::() } fn decode(&self, AtomCtx(buf, ..): AtomCtx) -> Box { Box::new(T::decode(&mut &buf[..])) } - fn call(&self, AtomCtx(buf, _, ctx): AtomCtx, arg: api::ExprTicket) -> GenExpr { + fn call(&self, AtomCtx(buf, _, ctx): AtomCtx, arg: api::ExprTicket) -> Expr { T::decode(&mut &buf[..]).call(ExprHandle::from_args(ctx, arg)) } - fn call_ref(&self, AtomCtx(buf, _, ctx): AtomCtx, arg: api::ExprTicket) -> GenExpr { + fn call_ref(&self, AtomCtx(buf, _, ctx): AtomCtx, arg: api::ExprTicket) -> Expr { T::decode(&mut &buf[..]).call(ExprHandle::from_args(ctx, arg)) } fn handle_req( &self, AtomCtx(buf, _, sys): AtomCtx, + key: Sym, req: &mut dyn std::io::Read, - write: &mut dyn Write, - ) { - let pack = RequestPack:: { req: Decode::decode(req), write, sys }; - T::decode(&mut &buf[..]).handle_req(pack) + rep: &mut dyn Write, + ) -> bool { + self.0.dispatch(&T::decode(&mut &buf[..]), sys, key, req, rep) } - fn same(&self, AtomCtx(buf, _, ctx): AtomCtx, a2: &api::Atom) -> bool { - T::decode(&mut &buf[..]).same(ctx, &T::decode(&mut &a2.data[8..])) - } - fn command(&self, AtomCtx(buf, _, ctx): AtomCtx<'_>) -> OrcRes> { + fn command(&self, AtomCtx(buf, _, ctx): AtomCtx<'_>) -> OrcRes> { T::decode(&mut &buf[..]).command(ctx) } - fn serialize(&self, AtomCtx(buf, ..): AtomCtx<'_>, write: &mut dyn Write) -> Vec { - T::decode(&mut &buf[..]).encode(write); + fn serialize(&self, actx: AtomCtx<'_>, write: &mut dyn Write) -> Vec { + T::decode(&mut &actx.0[..]).encode(write); Vec::new() } - fn deserialize(&self, ctx: SysCtx, data: &[u8], refs: &[ExprTicket]) -> api::Atom { + fn deserialize(&self, ctx: SysCtx, data: &[u8], refs: &[api::ExprTicket]) -> api::Atom { assert!(refs.is_empty(), "Refs found when deserializing thin atom"); T::decode(&mut &data[..])._factory().build(ctx) } @@ -76,16 +73,9 @@ pub trait ThinAtom: AtomCard + Atomic + Coding + Send + Sync + 'static { #[allow(unused_variables)] - fn call(&self, arg: ExprHandle) -> GenExpr { bot(err_not_callable()) } + fn call(&self, arg: ExprHandle) -> Expr { bot([err_not_callable()]) } #[allow(unused_variables)] - fn same(&self, ctx: SysCtx, other: &Self) -> bool { - let tname = type_name::(); - writeln!(ctx.logger, "Override ThinAtom::same for {tname} if it can appear in macro input"); - false - } - fn handle_req(&self, pck: impl ReqPck); - #[allow(unused_variables)] - fn command(&self, ctx: SysCtx) -> OrcRes> { Err(vec![err_not_command()]) } + fn command(&self, ctx: SysCtx) -> OrcRes> { Err(err_not_command().into()) } #[allow(unused_variables)] fn print(&self, ctx: SysCtx) -> String { format!("ThinAtom({})", type_name::()) } } diff --git a/orchid-extension/src/conv.rs b/orchid-extension/src/conv.rs index 06e5a8a..4f49111 100644 --- a/orchid-extension/src/conv.rs +++ b/orchid-extension/src/conv.rs @@ -3,19 +3,19 @@ use orchid_base::intern; use orchid_base::location::Pos; use crate::atom::{AtomicFeatures, ToAtom, TypAtom}; -use crate::expr::{atom, botv, ExprHandle, GenExpr, OwnedExpr}; +use crate::expr::{atom, bot, Expr}; use crate::system::downcast_atom; pub trait TryFromExpr: Sized { - fn try_from_expr(expr: ExprHandle) -> OrcRes; + fn try_from_expr(expr: Expr) -> OrcRes; } -impl TryFromExpr for OwnedExpr { - fn try_from_expr(expr: ExprHandle) -> OrcRes { Ok(OwnedExpr::new(expr)) } +impl TryFromExpr for Expr { + fn try_from_expr(expr: Expr) -> OrcRes { Ok(expr) } } impl TryFromExpr for (T, U) { - fn try_from_expr(expr: ExprHandle) -> OrcRes { + fn try_from_expr(expr: Expr) -> OrcRes { Ok((T::try_from_expr(expr.clone())?, U::try_from_expr(expr)?)) } } @@ -29,31 +29,30 @@ fn err_type(pos: Pos) -> OrcErr { } impl<'a, A: AtomicFeatures> TryFromExpr for TypAtom<'a, A> { - fn try_from_expr(expr: ExprHandle) -> OrcRes { - OwnedExpr::new(expr) - .foreign_atom() - .map_err(|ex| vec![err_not_atom(ex.pos.clone())]) - .and_then(|f| downcast_atom(f).map_err(|f| vec![err_type(f.pos)])) + fn try_from_expr(expr: Expr) -> OrcRes { + (expr.foreign_atom()) + .map_err(|ex| err_not_atom(ex.pos.clone()).into()) + .and_then(|f| downcast_atom(f).map_err(|f| err_type(f.pos).into())) } } pub trait ToExpr { - fn to_expr(self) -> GenExpr; + fn to_expr(self) -> Expr; } -impl ToExpr for GenExpr { - fn to_expr(self) -> GenExpr { self } +impl ToExpr for Expr { + fn to_expr(self) -> Expr { self } } impl ToExpr for OrcRes { - fn to_expr(self) -> GenExpr { + fn to_expr(self) -> Expr { match self { - Err(e) => botv(e), + Err(e) => bot(e), Ok(t) => t.to_expr(), } } } impl ToExpr for A { - fn to_expr(self) -> GenExpr { atom(self) } + fn to_expr(self) -> Expr { atom(self) } } diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index d4250be..9a98a7c 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -6,16 +6,16 @@ use std::{mem, process, thread}; use hashbrown::HashMap; use itertools::Itertools; -use orchid_api::DeserAtom; +use orchid_api::ExtMsgSet; use orchid_api_traits::{enc_vec, Decode, Encode}; use orchid_base::char_filter::{char_filter_match, char_filter_union, mk_char_filter}; use orchid_base::clone; -use orchid_base::error::errv_to_apiv; use orchid_base::interner::{deintern, init_replica, sweep_replica}; use orchid_base::logging::Logger; +use orchid_base::macros::{mtreev_from_api, mtreev_to_api}; use orchid_base::name::{PathSlice, Sym}; -use orchid_base::parse::Snippet; -use orchid_base::reqnot::{ReqNot, Requester}; +use orchid_base::parse::{Comment, Snippet}; +use orchid_base::reqnot::{ReqHandlish, ReqNot, RequestHandle, Requester}; use orchid_base::tree::{ttv_from_api, ttv_to_api}; use substack::Substack; @@ -23,12 +23,16 @@ use crate::api; use crate::atom::{AtomCtx, AtomDynfo}; use crate::atom_owned::OBJ_STORE; use crate::fs::VirtFS; -use crate::lexer::{err_cascade, err_lexer_na, LexContext}; +use crate::lexer::{err_cascade, err_not_applicable, LexContext}; +use crate::macros::{apply_rule, RuleCtx}; use crate::msg::{recv_parent_msg, send_parent_msg}; use crate::system::{atom_by_idx, SysCtx}; use crate::system_ctor::{CtedObj, DynSystemCtor}; use crate::tree::{do_extra, GenTok, GenTokTree, LazyMemberFactory, TIACtxImpl}; +pub type ExtReq = RequestHandle; +pub type ExtReqNot = ReqNot; + pub struct ExtensionData { pub name: &'static str, pub systems: &'static [&'static dyn DynSystemCtor], @@ -56,7 +60,7 @@ pub fn with_atom_record( get_sys_ctx: &impl Fn(api::SysId, ReqNot) -> SysCtx, reqnot: ReqNot, atom: &api::Atom, - cb: impl FnOnce(&'static dyn AtomDynfo, SysCtx, api::AtomId, &[u8]) -> T, + cb: impl FnOnce(Box, SysCtx, api::AtomId, &[u8]) -> T, ) -> T { let mut data = &atom.data[..]; let ctx = get_sys_ctx(atom.owner, reqnot); @@ -107,12 +111,12 @@ fn extension_main_logic(data: ExtensionData) { api::HostExtNotif::AtomDrop(api::AtomDrop(sys_id, atom)) => OBJ_STORE.get(atom.0).unwrap().remove().dyn_free(mk_ctx(sys_id, reqnot)), }), - clone!(systems, logger; move |req| match req.req() { - api::HostExtReq::Ping(ping@api::Ping) => req.handle(ping, &()), - api::HostExtReq::Sweep(sweep@api::Sweep) => req.handle(sweep, &sweep_replica()), + clone!(systems, logger; move |hand, req| match req { + api::HostExtReq::Ping(ping@api::Ping) => hand.handle(&ping, &()), + api::HostExtReq::Sweep(sweep@api::Sweep) => hand.handle(&sweep, &sweep_replica()), api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => { let i = decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system).unwrap().0; - let cted = data.systems[i].new_system(new_sys); + let cted = data.systems[i].new_system(&new_sys); let mut vfses = HashMap::new(); let lex_filter = cted.inst().dyn_lexers().iter().fold(api::CharFilter(vec![]), |cf, lx| { let lxcf = mk_char_filter(lx.char_filter().iter().cloned()); @@ -123,7 +127,7 @@ fn extension_main_logic(data: ExtensionData) { cted: cted.clone(), id: new_sys.id, logger: logger.clone(), - reqnot: req.reqnot() + reqnot: hand.reqnot() }; let mut tia_ctx = TIACtxImpl{ lazy: &mut lazy_mems, @@ -140,7 +144,7 @@ fn extension_main_logic(data: ExtensionData) { cted, lazy_members: lazy_mems }); - req.handle(new_sys, &api::SystemInst { + hand.handle(&new_sys, &api::SystemInst { lex_filter, const_root, line_types: vec![] @@ -148,16 +152,16 @@ fn extension_main_logic(data: ExtensionData) { } api::HostExtReq::GetMember(get_tree@api::GetMember(sys_id, tree_id)) => { let mut systems_g = systems.lock().unwrap(); - let sys = systems_g.get_mut(sys_id).expect("System not found"); + let sys = systems_g.get_mut(&sys_id).expect("System not found"); let lazy = &mut sys.lazy_members; - let (path, cb) = match lazy.insert(*tree_id, MemberRecord::Res) { + let (path, cb) = match lazy.insert(tree_id, MemberRecord::Res) { None => panic!("Tree for ID not found"), Some(MemberRecord::Res) => panic!("This tree has already been transmitted"), Some(MemberRecord::Gen(path, cb)) => (path, cb), }; let tree = cb.build(path.clone()); - req.handle(get_tree, &tree.into_api(&mut TIACtxImpl{ - sys: SysCtx::new(*sys_id, &sys.cted, &logger, req.reqnot()), + hand.handle(&get_tree, &tree.into_api(&mut TIACtxImpl{ + sys: SysCtx::new(sys_id, &sys.cted, &logger, hand.reqnot()), path: Substack::Bottom, basepath: &path, lazy, @@ -165,100 +169,124 @@ fn extension_main_logic(data: ExtensionData) { } api::HostExtReq::VfsReq(api::VfsReq::GetVfs(get_vfs@api::GetVfs(sys_id))) => { let systems_g = systems.lock().unwrap(); - req.handle(get_vfs, &systems_g[sys_id].declfs) + hand.handle(&get_vfs, &systems_g[&sys_id].declfs) + } + api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => { + let api::SysFwded(sys_id, payload) = fwd; + let ctx = mk_ctx(sys_id, hand.reqnot()); + let sys = ctx.cted.inst(); + sys.dyn_request(hand, payload) } api::HostExtReq::VfsReq(api::VfsReq::VfsRead(vfs_read)) => { - let api::VfsRead(sys_id, vfs_id, path) = vfs_read; + let api::VfsRead(sys_id, vfs_id, path) = &vfs_read; let systems_g = systems.lock().unwrap(); let path = path.iter().map(|t| deintern(*t)).collect_vec(); - req.handle(vfs_read, &systems_g[sys_id].vfses[vfs_id].load(PathSlice::new(&path))) + hand.handle(&vfs_read, &systems_g[sys_id].vfses[vfs_id].load(PathSlice::new(&path))) } - api::HostExtReq::ParserReq(api::ParserReq::LexExpr(lex)) => { - let api::LexExpr{ sys, text, pos, id } = *lex; + api::HostExtReq::LexExpr(lex @ api::LexExpr{ sys, text, pos, id }) => { let systems_g = systems.lock().unwrap(); let lexers = systems_g[&sys].cted.inst().dyn_lexers(); mem::drop(systems_g); let text = deintern(text); - let ctx = LexContext { sys, id, pos, reqnot: req.reqnot(), text: &text }; + let ctx = LexContext { sys, id, pos, reqnot: hand.reqnot(), text: &text }; let trigger_char = text.chars().nth(pos as usize).unwrap(); for lx in lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char)) { match lx.lex(&text[pos as usize..], &ctx) { - Err(e) if e.iter().any(|e| *e == err_lexer_na()) => continue, + Err(e) if e.any(|e| *e == err_not_applicable()) => continue, Err(e) => { - let errv = errv_to_apiv(e.iter().filter(|e| **e == err_cascade())); - return req.handle(lex, &if errv.is_empty() { None } else { Some(Err(errv))}) + let eopt = e.keep_only(|e| *e != err_cascade()).map(|e| Err(e.to_api())); + return hand.handle(&lex, &eopt) }, Ok((s, expr)) => { - let ctx = mk_ctx(sys, req.reqnot()); + let ctx = mk_ctx(sys, hand.reqnot()); let expr = expr.to_api(&mut |f, r| do_extra(f, r, ctx.clone())); let pos = (text.len() - s.len()) as u32; - return req.handle(lex, &Some(Ok(api::LexedExpr{ pos, expr }))) + return hand.handle(&lex, &Some(Ok(api::LexedExpr{ pos, expr }))) } } } writeln!(logger, "Got notified about n/a character '{trigger_char}'"); - req.handle(lex, &None) + hand.handle(&lex, &None) }, - api::HostExtReq::ParserReq(api::ParserReq::ParseLine(pline@api::ParseLine{ sys, line })) => { - let mut ctx = mk_ctx(*sys, req.reqnot()); + api::HostExtReq::ParseLine(pline) => { + let api::ParseLine{ exported, comments, sys, line } = &pline; + let mut ctx = mk_ctx(*sys, hand.reqnot()); let parsers = ctx.cted.inst().dyn_parsers(); + let comments = comments.iter().map(Comment::from_api).collect(); let line: Vec = ttv_from_api(line, &mut ctx); 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(tail) { - Err(e) => Err(errv_to_apiv(e.iter())), + let o_line = match parser.parse(*exported, comments, tail) { + Err(e) => Err(e.to_api()), Ok(t) => Ok(ttv_to_api(t, &mut |f, range| { api::TokenTree{ range, token: api::Token::Atom(f.clone().build(ctx.clone())) } })), }; - req.handle(pline, &o_line) + hand.handle(&pline, &o_line) } api::HostExtReq::AtomReq(atom_req) => { let atom = atom_req.get_atom(); - with_atom_record(&mk_ctx, req.reqnot(), atom, |nfo, ctx, id, buf| { + with_atom_record(&mk_ctx, hand.reqnot(), atom, |nfo, ctx, id, buf| { let actx = AtomCtx(buf, atom.drop, ctx.clone()); - match atom_req { + match &atom_req { api::AtomReq::SerializeAtom(ser) => { let mut buf = enc_vec(&id); let refs = nfo.serialize(actx, &mut buf); - req.handle(ser, &(buf, refs)) + hand.handle(ser, &(buf, refs)) } - api::AtomReq::AtomPrint(print@api::AtomPrint(_)) => req.handle(print, &nfo.print(actx)), - api::AtomReq::AtomSame(same@api::AtomSame(_, r)) => { - // different systems or different type tags - if atom.owner != r.owner || buf != &r.data[..8] { - return req.handle(same, &false) - } - req.handle(same, &nfo.same(actx, r)) - }, - api::AtomReq::Fwded(fwded@api::Fwded(_, payload)) => { + api::AtomReq::AtomPrint(print@api::AtomPrint(_)) => + hand.handle(print, &nfo.print(actx)), + api::AtomReq::Fwded(fwded) => { + let api::Fwded(_, key, payload) = &fwded; let mut reply = Vec::new(); - nfo.handle_req(actx, &mut &payload[..], &mut reply); - req.handle(fwded, &reply) + let some = nfo.handle_req(actx, Sym::deintern(*key), &mut &payload[..], &mut reply); + hand.handle(fwded, &some.then_some(reply)) } - api::AtomReq::CallRef(call@api::CallRef(_, arg)) - => req.handle(call, &nfo.call_ref(actx, *arg).to_api(ctx.clone())), - api::AtomReq::FinalCall(call@api::FinalCall(_, arg)) - => req.handle(call, &nfo.call(actx, *arg).to_api(ctx.clone())), - api::AtomReq::Command(cmd@api::Command(_)) => req.handle(cmd, &match nfo.command(actx) { - Err(e) => Err(errv_to_apiv(e.iter())), - Ok(opt) => Ok(match opt { - Some(cont) => api::NextStep::Continue(cont.into_api(ctx.clone())), - None => api::NextStep::Halt, + api::AtomReq::CallRef(call@api::CallRef(_, arg)) => { + let ret = nfo.call_ref(actx, *arg); + hand.handle(call, &ret.api_return(ctx.clone(), &mut |h| hand.defer_drop(h))) + }, + api::AtomReq::FinalCall(call@api::FinalCall(_, arg)) => { + let ret = nfo.call(actx, *arg); + hand.handle(call, &ret.api_return(ctx.clone(), &mut |h| hand.defer_drop(h))) + } + api::AtomReq::Command(cmd@api::Command(_)) => { + hand.handle(cmd, &match nfo.command(actx) { + Err(e) => Err(e.to_api()), + Ok(opt) => Ok(match opt { + None => api::NextStep::Halt, + Some(cont) => api::NextStep::Continue( + cont.api_return(ctx.clone(), &mut |h| hand.defer_drop(h)) + ), + }) }) - }) + } } }) }, - api::HostExtReq::DeserAtom(deser@DeserAtom(sys, buf, refs)) => { + api::HostExtReq::DeserAtom(deser) => { + let api::DeserAtom(sys, buf, refs) = &deser; let mut read = &mut &buf[..]; - let ctx = mk_ctx(*sys, req.reqnot()); + let ctx = mk_ctx(*sys, hand.reqnot()); let id = api::AtomId::decode(&mut read); let inst = ctx.cted.inst(); let nfo = atom_by_idx(inst.card(), id).expect("Deserializing atom with invalid ID"); - req.handle(deser, &nfo.deserialize(ctx.clone(), read, refs)) + hand.handle(&deser, &nfo.deserialize(ctx.clone(), read, refs)) + }, + orchid_api::HostExtReq::ApplyMacro(am) => { + let tok = hand.will_handle_as(&am); + let sys_ctx = mk_ctx(am.sys, hand.reqnot()); + let ctx = RuleCtx { + args: am.params.into_iter().map(|(k, v)| (deintern(k), mtreev_from_api(&v))).collect(), + run_id: am.run_id, + sys: sys_ctx.clone(), + }; + hand.handle_as(tok, &match apply_rule(am.id, ctx) { + Err(e) => e.keep_only(|e| *e != err_cascade()).map(|e| Err(e.to_api())), + Ok(t) => Some(Ok(mtreev_to_api(&t))), + }) } }), ); diff --git a/orchid-extension/src/expr.rs b/orchid-extension/src/expr.rs index 535711a..d386216 100644 --- a/orchid-extension/src/expr.rs +++ b/orchid-extension/src/expr.rs @@ -1,10 +1,11 @@ -use std::marker::PhantomData; +use std::fmt; use std::ops::Deref; -use std::sync::OnceLock; +use std::sync::{Arc, OnceLock}; use derive_destructure::destructure; -use orchid_base::error::{errv_from_apiv, errv_to_apiv, OrcErr}; -use orchid_base::interner::{deintern, Tok}; +use orchid_api::InspectedKind; +use orchid_base::error::{OrcErr, OrcErrv}; +use orchid_base::interner::Tok; use orchid_base::location::Pos; use orchid_base::reqnot::Requester; @@ -19,12 +20,13 @@ pub struct ExprHandle { } impl ExprHandle { pub(crate) fn from_args(ctx: SysCtx, tk: api::ExprTicket) -> Self { Self { ctx, tk } } - pub(crate) fn into_tk(self) -> api::ExprTicket { - let (tk, ..) = self.destructure(); - tk - } pub fn get_ctx(&self) -> SysCtx { self.ctx.clone() } } +impl fmt::Debug for ExprHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ExprHandle({})", self.tk.0) + } +} impl Clone for ExprHandle { fn clone(&self) -> Self { self.ctx.reqnot.notify(api::Acquire(self.ctx.id, self.tk)); @@ -35,144 +37,128 @@ impl Drop for ExprHandle { fn drop(&mut self) { self.ctx.reqnot.notify(api::Release(self.ctx.id, self.tk)) } } -#[derive(Clone, destructure)] -pub struct OwnedExpr { - pub handle: ExprHandle, - pub val: OnceLock>, +#[derive(Clone, Debug, destructure)] +pub struct Expr { + pub handle: Option>, + pub val: OnceLock, } -impl OwnedExpr { - pub fn new(handle: ExprHandle) -> Self { Self { handle, val: OnceLock::new() } } - pub fn get_data(&self) -> &GenExpr { +impl Expr { + pub fn new(hand: Arc) -> Self { Self { handle: Some(hand), val: OnceLock::new() } } + pub fn from_data(val: ExprData) -> Self { Self { handle: None, val: OnceLock::from(val) } } + pub fn get_data(&self) -> &ExprData { self.val.get_or_init(|| { - Box::new(GenExpr::from_api( - self.handle.ctx.reqnot.request(api::Inspect(self.handle.tk)).expr, - &self.handle.ctx, - )) + let handle = self.handle.as_ref().expect("Either the value or the handle must be set"); + let details = handle.ctx.reqnot.request(api::Inspect { target: handle.tk }); + let pos = Pos::from_api(&details.location); + let kind = match details.kind { + InspectedKind::Atom(a) => ExprKind::Atom(ForeignAtom::new(handle.clone(), a, pos.clone())), + InspectedKind::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(&b)), + InspectedKind::Opaque => ExprKind::Opaque, + }; + ExprData { pos, kind } }) } pub fn foreign_atom(self) -> Result, Self> { - if let GenExpr { clause: GenClause::Atom(_, atom), pos: position } = self.get_data() { - let (atom, position) = (atom.clone(), position.clone()); - return Ok(ForeignAtom { - ctx: self.handle.ctx.clone(), - expr: Some(self.handle), - char_marker: PhantomData, - pos: position, - atom, - }); + match (self.get_data(), &self.handle) { + (ExprData { kind: ExprKind::Atom(atom), .. }, Some(_)) => Ok(atom.clone()), + _ => Err(self), } - Err(self) } + pub fn api_return( + self, + ctx: SysCtx, + do_slot: &mut impl FnMut(Arc), + ) -> api::Expression { + if let Some(h) = self.handle { + do_slot(h.clone()); + api::Expression { location: api::Location::SlotTarget, kind: api::ExpressionKind::Slot(h.tk) } + } else { + self.val.into_inner().expect("Either value or handle must be set").api_return(ctx, do_slot) + } + } + pub fn handle(&self) -> Option> { self.handle.clone() } } -impl Deref for OwnedExpr { - type Target = GenExpr; +impl Deref for Expr { + type Target = ExprData; fn deref(&self) -> &Self::Target { self.get_data() } } -#[derive(Clone)] -pub struct GenExpr { +#[derive(Clone, Debug)] +pub struct ExprData { pub pos: Pos, - pub clause: GenClause, + pub kind: ExprKind, } -impl GenExpr { - pub fn to_api(&self, ctx: SysCtx) -> api::Expr { - api::Expr { location: self.pos.to_api(), clause: self.clause.to_api(ctx) } - } - pub fn into_api(self, ctx: SysCtx) -> api::Expr { - api::Expr { location: self.pos.to_api(), clause: self.clause.into_api(ctx) } - } - pub fn from_api(api: api::Expr, ctx: &SysCtx) -> Self { - Self { pos: Pos::from_api(&api.location), clause: GenClause::from_api(api.clause, ctx) } +impl ExprData { + pub fn api_return( + self, + ctx: SysCtx, + do_slot: &mut impl FnMut(Arc), + ) -> api::Expression { + api::Expression { location: self.pos.to_api(), kind: self.kind.api_return(ctx, do_slot) } } } -#[derive(Clone)] -pub enum GenClause { - Call(Box, Box), - Lambda(u64, Box), +#[derive(Clone, Debug)] +pub enum ExprKind { + Call(Box, Box), + Lambda(u64, Box), Arg(u64), - Slot(OwnedExpr), - Seq(Box, Box), + Seq(Box, Box), Const(Tok>>), NewAtom(AtomFactory), - Atom(api::ExprTicket, api::Atom), - Bottom(Vec), + Atom(ForeignAtom<'static>), + Bottom(OrcErrv), + Opaque, } -impl GenClause { - pub fn to_api(&self, ctx: SysCtx) -> api::Clause { +impl ExprKind { + pub fn api_return( + self, + ctx: SysCtx, + do_slot: &mut impl FnMut(Arc), + ) -> api::ExpressionKind { + use api::ExpressionKind as K; match self { Self::Call(f, x) => - api::Clause::Call(Box::new(f.to_api(ctx.clone())), Box::new(x.to_api(ctx))), - Self::Seq(a, b) => api::Clause::Seq(Box::new(a.to_api(ctx.clone())), Box::new(b.to_api(ctx))), - Self::Lambda(arg, body) => api::Clause::Lambda(*arg, Box::new(body.to_api(ctx))), - Self::Arg(arg) => api::Clause::Arg(*arg), - Self::Const(name) => api::Clause::Const(name.marker()), - Self::Bottom(err) => api::Clause::Bottom(errv_to_apiv(err)), - Self::NewAtom(fac) => api::Clause::NewAtom(fac.clone().build(ctx)), - Self::Atom(tk, atom) => api::Clause::Atom(*tk, atom.clone()), - Self::Slot(_) => panic!("Slot is forbidden in const tree"), - } - } - pub fn into_api(self, ctx: SysCtx) -> api::Clause { - match self { - Self::Call(f, x) => - api::Clause::Call(Box::new(f.into_api(ctx.clone())), Box::new(x.into_api(ctx))), + K::Call(Box::new(f.api_return(ctx.clone(), do_slot)), Box::new(x.api_return(ctx, do_slot))), Self::Seq(a, b) => - api::Clause::Seq(Box::new(a.into_api(ctx.clone())), Box::new(b.into_api(ctx))), - Self::Lambda(arg, body) => api::Clause::Lambda(arg, Box::new(body.into_api(ctx))), - Self::Arg(arg) => api::Clause::Arg(arg), - Self::Slot(extk) => api::Clause::Slot(extk.handle.into_tk()), - Self::Const(name) => api::Clause::Const(name.marker()), - Self::Bottom(err) => api::Clause::Bottom(errv_to_apiv(err.iter())), - Self::NewAtom(fac) => api::Clause::NewAtom(fac.clone().build(ctx)), - Self::Atom(tk, atom) => api::Clause::Atom(tk, atom), - } - } - pub fn from_api(api: api::Clause, ctx: &SysCtx) -> Self { - match api { - api::Clause::Arg(id) => Self::Arg(id), - api::Clause::Lambda(arg, body) => Self::Lambda(arg, Box::new(GenExpr::from_api(*body, ctx))), - api::Clause::NewAtom(_) => panic!("Clause::NewAtom should never be received, only sent"), - api::Clause::Bottom(s) => Self::Bottom(errv_from_apiv(&s)), - api::Clause::Call(f, x) => - Self::Call(Box::new(GenExpr::from_api(*f, ctx)), Box::new(GenExpr::from_api(*x, ctx))), - api::Clause::Seq(a, b) => - Self::Seq(Box::new(GenExpr::from_api(*a, ctx)), Box::new(GenExpr::from_api(*b, ctx))), - api::Clause::Const(name) => Self::Const(deintern(name)), - api::Clause::Slot(exi) => Self::Slot(OwnedExpr::new(ExprHandle::from_args(ctx.clone(), exi))), - api::Clause::Atom(tk, atom) => Self::Atom(tk, atom), + K::Seq(Box::new(a.api_return(ctx.clone(), do_slot)), Box::new(b.api_return(ctx, do_slot))), + Self::Lambda(arg, body) => K::Lambda(arg, Box::new(body.api_return(ctx, do_slot))), + Self::Arg(arg) => K::Arg(arg), + Self::Const(name) => K::Const(name.marker()), + Self::Bottom(err) => K::Bottom(err.to_api()), + Self::NewAtom(fac) => K::NewAtom(fac.clone().build(ctx)), + kind @ (Self::Atom(_) | Self::Opaque) => panic!("{kind:?} should have a token"), } } } -fn inherit(clause: GenClause) -> GenExpr { GenExpr { pos: Pos::Inherit, clause } } +fn inherit(kind: ExprKind) -> Expr { Expr::from_data(ExprData { pos: Pos::Inherit, kind }) } -pub fn sym_ref(path: Tok>>) -> GenExpr { inherit(GenClause::Const(path)) } -pub fn atom(atom: A) -> GenExpr { inherit(GenClause::NewAtom(atom.to_atom_factory())) } +pub fn sym_ref(path: Tok>>) -> Expr { inherit(ExprKind::Const(path)) } +pub fn atom(atom: A) -> Expr { inherit(ExprKind::NewAtom(atom.to_atom_factory())) } -pub fn seq(ops: impl IntoIterator) -> GenExpr { - fn recur(mut ops: impl Iterator) -> Option { +pub fn seq(ops: impl IntoIterator) -> Expr { + fn recur(mut ops: impl Iterator) -> Option { let op = ops.next()?; Some(match recur(ops) { None => op, - Some(rec) => inherit(GenClause::Seq(Box::new(op), Box::new(rec))), + Some(rec) => inherit(ExprKind::Seq(Box::new(op), Box::new(rec))), }) } recur(ops.into_iter()).expect("Empty list provided to seq!") } -pub fn slot(extk: OwnedExpr) -> GenClause { GenClause::Slot(extk) } +pub fn arg(n: u64) -> ExprKind { ExprKind::Arg(n) } -pub fn arg(n: u64) -> GenClause { GenClause::Arg(n) } - -pub fn lambda(n: u64, b: impl IntoIterator) -> GenExpr { - inherit(GenClause::Lambda(n, Box::new(call(b)))) +pub fn lambda(n: u64, b: impl IntoIterator) -> Expr { + inherit(ExprKind::Lambda(n, Box::new(call(b)))) } -pub fn call(v: impl IntoIterator) -> GenExpr { +pub fn call(v: impl IntoIterator) -> Expr { v.into_iter() - .reduce(|f, x| inherit(GenClause::Call(Box::new(f), Box::new(x)))) + .reduce(|f, x| inherit(ExprKind::Call(Box::new(f), Box::new(x)))) .expect("Empty call expression") } -pub fn bot(e: OrcErr) -> GenExpr { botv(vec![e]) } -pub fn botv(ev: Vec) -> GenExpr { inherit(GenClause::Bottom(ev)) } +pub fn bot(ev: impl IntoIterator) -> Expr { + inherit(ExprKind::Bottom(OrcErrv::new(ev).unwrap())) +} diff --git a/orchid-extension/src/func_atom.rs b/orchid-extension/src/func_atom.rs index 6cde7ec..ccda4b9 100644 --- a/orchid-extension/src/func_atom.rs +++ b/orchid-extension/src/func_atom.rs @@ -5,26 +5,25 @@ use std::sync::{Arc, Mutex}; use itertools::Itertools; use lazy_static::lazy_static; -use never::Never; use orchid_api_traits::Encode; use orchid_base::error::OrcRes; use orchid_base::interner::Tok; use orchid_base::name::Sym; use trait_set::trait_set; -use crate::atom::{Atomic, ReqPck}; +use crate::atom::{MethodSet, Atomic}; use crate::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; use crate::conv::ToExpr; -use crate::expr::{ExprHandle, GenExpr}; +use crate::expr::{Expr, ExprHandle}; use crate::system::SysCtx; trait_set! { - trait FunCB = Fn(Vec) -> OrcRes + Send + Sync + 'static; + trait FunCB = Fn(Vec) -> OrcRes + Send + Sync + 'static; } pub trait ExprFunc: Clone + Send + Sync + 'static { const ARITY: u8; - fn apply(&self, v: Vec) -> OrcRes; + fn apply(&self, v: Vec) -> OrcRes; } lazy_static! { @@ -34,7 +33,7 @@ lazy_static! { #[derive(Clone)] pub(crate) struct Fun { path: Sym, - args: Vec, + args: Vec, arity: u8, fun: Arc, } @@ -53,14 +52,14 @@ impl Fun { } impl Atomic for Fun { type Data = (); - type Req = Never; type Variant = OwnedVariant; + fn reg_reqs() -> MethodSet { MethodSet::new() } } impl OwnedAtom for Fun { - type Refs = Vec; + type Refs = Vec; fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - fn call_ref(&self, arg: ExprHandle) -> GenExpr { - let new_args = self.args.iter().cloned().chain([arg]).collect_vec(); + fn call_ref(&self, arg: ExprHandle) -> Expr { + let new_args = self.args.iter().cloned().chain([Expr::new(Arc::new(arg))]).collect_vec(); if new_args.len() == self.arity.into() { (self.fun)(new_args).to_expr() } else { @@ -68,8 +67,7 @@ impl OwnedAtom for Fun { .to_expr() } } - fn call(self, arg: ExprHandle) -> GenExpr { self.call_ref(arg) } - fn handle_req(&self, pck: impl ReqPck) { pck.never() } + fn call(self, arg: ExprHandle) -> Expr { self.call_ref(arg) } fn serialize(&self, _: SysCtx, sink: &mut (impl io::Write + ?Sized)) -> Self::Refs { self.path.encode(sink); self.args.clone() @@ -86,7 +84,7 @@ mod expr_func_derives { use super::ExprFunc; use crate::conv::{ToExpr, TryFromExpr}; - use crate::func_atom::{ExprHandle, GenExpr}; + use crate::func_atom::Expr; macro_rules! expr_func_derive { ($arity: tt, $($t:ident),*) => { @@ -97,7 +95,7 @@ mod expr_func_derives { Func: Fn($($t,)*) -> Out + Clone + Send + Sync + 'static > ExprFunc<($($t,)*), Out> for Func { const ARITY: u8 = $arity; - fn apply(&self, v: Vec) -> OrcRes { + fn apply(&self, v: Vec) -> OrcRes { assert_eq!(v.len(), Self::ARITY.into(), "Arity mismatch"); let [$([< $t:lower >],)*] = v.try_into().unwrap_or_else(|_| panic!("Checked above")); Ok(self($($t::try_from_expr([< $t:lower >])?,)*).to_expr()) diff --git a/orchid-extension/src/lexer.rs b/orchid-extension/src/lexer.rs index 36ec2bf..053b8f0 100644 --- a/orchid-extension/src/lexer.rs +++ b/orchid-extension/src/lexer.rs @@ -5,22 +5,22 @@ use orchid_base::intern; use orchid_base::interner::Tok; use orchid_base::location::Pos; use orchid_base::reqnot::{ReqNot, Requester}; -use orchid_base::tree::TreeHandle; +use orchid_base::tree::TokHandle; use crate::api; use crate::tree::{GenTok, GenTokTree}; pub fn err_cascade() -> OrcErr { mk_err( - intern!(str: "An error cascading from a recursive sublexer"), + intern!(str: "An error cascading from a recursive call"), "This error should not surface. If you are seeing it, something is wrong", [Pos::None.into()], ) } -pub fn err_lexer_na() -> OrcErr { +pub fn err_not_applicable() -> OrcErr { mk_err( - intern!(str: "Pseudo-error to communicate that the lexer doesn't apply"), + intern!(str: "Pseudo-error to communicate that the current branch in a dispatch doesn't apply"), &*err_cascade().message, [Pos::None.into()], ) @@ -38,7 +38,7 @@ impl<'a> LexContext<'a> { let start = self.pos(tail); let lx = self.reqnot.request(api::SubLex { pos: start, id: self.id }).ok_or_else(err_cascade)?; - Ok((&self.text[lx.pos as usize..], GenTok::Slot(TreeHandle::new(lx.ticket)).at(start..lx.pos))) + Ok((&self.text[lx.pos as usize..], GenTok::Slot(TokHandle::new(lx.ticket)).at(start..lx.pos))) } pub fn pos(&self, tail: &'a str) -> u32 { (self.text.len() - tail.len()) as u32 } diff --git a/orchid-extension/src/lib.rs b/orchid-extension/src/lib.rs index a85b862..dbf6a97 100644 --- a/orchid-extension/src/lib.rs +++ b/orchid-extension/src/lib.rs @@ -16,3 +16,5 @@ pub mod parser; pub mod system; pub mod system_ctor; pub mod tree; +pub mod macros; +pub mod api_conv; diff --git a/orchid-extension/src/macros.rs b/orchid-extension/src/macros.rs new file mode 100644 index 0000000..5c219a3 --- /dev/null +++ b/orchid-extension/src/macros.rs @@ -0,0 +1,89 @@ +use ahash::HashMap; +use lazy_static::lazy_static; +use orchid_base::{error::OrcRes, interner::{intern, Tok}, location::Pos, macros::{mtreev_from_api, mtreev_to_api, MTree}, parse::Comment, reqnot::Requester}; +use trait_set::trait_set; +use crate::{api, lexer::err_cascade, system::SysCtx}; +use std::{num::NonZero, sync::RwLock}; + +pub trait Macro { + fn pattern() -> MTree<'static>; + fn apply(binds: HashMap, MTree<'_>>) -> MTree<'_>; +} + +pub trait DynMacro { + fn pattern(&self) -> MTree<'static>; + fn apply<'a>(&self, binds: HashMap, MTree<'a>>) -> MTree<'a>; +} + +impl DynMacro for T { + fn pattern(&self) -> MTree<'static> { Self::pattern() } + fn apply<'a>(&self, binds: HashMap, MTree<'a>>) -> MTree<'a> { 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 fn recurse(&mut self, tree: &[MTree<'a>]) -> OrcRes>> { + let req = api::RunMacros{ run_id: self.run_id, query: mtreev_to_api(tree) }; + Ok(mtreev_from_api(&self.sys.reqnot.request(req).ok_or_else(err_cascade)?)) + } + 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> { + 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>) -> OrcRes>> + Send + Sync; +} + +lazy_static!{ + static ref RULES: RwLock>> = RwLock::default(); +} + +pub struct Rule { + pub(crate) comments: Vec, + pub(crate) pattern: Vec>, + pub(crate) id: api::MacroId, +} +impl Rule { + pub(crate) fn to_api(&self) -> api::MacroRule { + api::MacroRule { + comments: self.comments.iter().map(|c| c.to_api()).collect(), + location: api::Location::Inherit, + pattern: mtreev_to_api(&self.pattern), + id: self.id, + } + } +} + +pub fn rule_cmt<'a>( + cmt: impl IntoIterator, + pattern: Vec>, + apply: impl RuleCB + 'static +) -> Rule { + let mut rules = RULES.write().unwrap(); + let id = api::MacroId(NonZero::new(rules.len() as u64 + 1).unwrap()); + rules.insert(id, Box::new(apply)); + let comments = cmt.into_iter().map(|s| Comment { pos: Pos::Inherit, text: intern(s) }).collect(); + Rule { comments, pattern, id } +} + +pub fn rule(pattern: Vec>, apply: impl RuleCB + 'static) -> Rule { + rule_cmt([], pattern, apply) +} + +pub(crate) fn apply_rule(id: api::MacroId, ctx: RuleCtx<'static>) -> OrcRes>> { + let rules = RULES.read().unwrap(); + rules[&id](ctx) +} \ No newline at end of file diff --git a/orchid-extension/src/parser.rs b/orchid-extension/src/parser.rs index 51fcf68..3c80456 100644 --- a/orchid-extension/src/parser.rs +++ b/orchid-extension/src/parser.rs @@ -1,5 +1,5 @@ use orchid_base::error::OrcRes; -use orchid_base::parse::Snippet; +use orchid_base::parse::{Comment, Snippet}; use crate::atom::{AtomFactory, ForeignAtom}; use crate::tree::GenTokTree; @@ -8,17 +8,33 @@ pub type GenSnippet<'a> = Snippet<'a, 'a, ForeignAtom<'a>, AtomFactory>; pub trait Parser: Send + Sync + Sized + Default + 'static { const LINE_HEAD: &'static str; - fn parse(line: GenSnippet<'_>) -> OrcRes>>; + fn parse( + exported: bool, + comments: Vec, + line: GenSnippet<'_>, + ) -> OrcRes>>; } pub trait DynParser: Send + Sync + 'static { fn line_head(&self) -> &'static str; - fn parse<'a>(&self, line: GenSnippet<'a>) -> OrcRes>>; + fn parse<'a>( + &self, + exported: bool, + comments: Vec, + line: GenSnippet<'a>, + ) -> OrcRes>>; } impl DynParser for T { fn line_head(&self) -> &'static str { Self::LINE_HEAD } - fn parse<'a>(&self, line: GenSnippet<'a>) -> OrcRes>> { Self::parse(line) } + fn parse<'a>( + &self, + exported: bool, + comments: Vec, + line: GenSnippet<'a>, + ) -> OrcRes>> { + Self::parse(exported, comments, line) + } } pub type ParserObj = &'static dyn DynParser; diff --git a/orchid-extension/src/system.rs b/orchid-extension/src/system.rs index c6d4e89..36cd4a0 100644 --- a/orchid-extension/src/system.rs +++ b/orchid-extension/src/system.rs @@ -3,96 +3,103 @@ use std::num::NonZero; use std::sync::Arc; use hashbrown::HashMap; -use orchid_api::AtomId; -use orchid_api_traits::Decode; +use orchid_api_traits::{Coding, Decode}; +use orchid_base::boxed_iter::BoxedIter; use orchid_base::interner::Tok; use orchid_base::logging::Logger; -use orchid_base::reqnot::ReqNot; +use orchid_base::reqnot::{Receipt, ReqNot}; use crate::api; use crate::atom::{get_info, AtomCtx, AtomDynfo, AtomicFeatures, ForeignAtom, TypAtom}; +use crate::entrypoint::ExtReq; use crate::fs::DeclFs; // use crate::fun::Fun; use crate::lexer::LexerObj; use crate::parser::ParserObj; use crate::system_ctor::{CtedObj, SystemCtor}; -use crate::tree::GenMemberKind; +use crate::tree::MemKind; /// System as consumed by foreign code pub trait SystemCard: Default + Send + Sync + 'static { type Ctor: SystemCtor; - const ATOM_DEFS: &'static [Option<&'static dyn AtomDynfo>]; + type Req: Coding; + fn atoms() -> impl IntoIterator>>; } pub trait DynSystemCard: Send + Sync + 'static { fn name(&self) -> &'static str; /// Atoms explicitly defined by the system card. Do not rely on this for /// querying atoms as it doesn't include the general atom types - fn atoms(&self) -> &'static [Option<&'static dyn AtomDynfo>]; + fn atoms(&self) -> BoxedIter>>; } /// Atoms supported by this package which may appear in all extensions. /// The indices of these are bitwise negated, such that the MSB of an atom index /// marks whether it belongs to this package (0) or the importer (1) -fn general_atoms() -> &'static [Option<&'static dyn AtomDynfo>] { - &[/*Some(Fun::INFO)*/] +fn general_atoms() -> impl Iterator>> { + [/*Some(Fun::INFO)*/].into_iter() } pub fn atom_info_for( sys: &(impl DynSystemCard + ?Sized), tid: TypeId, -) -> Option<(api::AtomId, &'static dyn AtomDynfo)> { - (sys.atoms().iter().enumerate().map(|(i, o)| (NonZero::new(i as u64 + 1).unwrap(), o))) - .chain(general_atoms().iter().enumerate().map(|(i, o)| (NonZero::new(!(i as u64)).unwrap(), o))) - .filter_map(|(i, o)| o.as_ref().map(|a| (api::AtomId(i), *a))) +) -> Option<(api::AtomId, Box)> { + (sys.atoms().enumerate().map(|(i, o)| (NonZero::new(i as u64 + 1).unwrap(), o))) + .chain(general_atoms().enumerate().map(|(i, o)| (NonZero::new(!(i as u64)).unwrap(), o))) + .filter_map(|(i, o)| o.map(|a| (api::AtomId(i), a))) .find(|ent| ent.1.tid() == tid) } pub fn atom_by_idx( sys: &(impl DynSystemCard + ?Sized), tid: api::AtomId, -) -> Option<&'static dyn AtomDynfo> { +) -> Option> { if (u64::from(tid.0) >> (u64::BITS - 1)) & 1 == 1 { - general_atoms()[!u64::from(tid.0) as usize] + general_atoms().nth(!u64::from(tid.0) as usize).unwrap() } else { - sys.atoms()[u64::from(tid.0) as usize - 1] + sys.atoms().nth(u64::from(tid.0) as usize - 1).unwrap() } } pub fn resolv_atom( sys: &(impl DynSystemCard + ?Sized), atom: &api::Atom, -) -> &'static dyn AtomDynfo { +) -> Box { let tid = api::AtomId::decode(&mut &atom.data[..8]); atom_by_idx(sys, tid).expect("Value of nonexistent type found") } impl DynSystemCard for T { fn name(&self) -> &'static str { T::Ctor::NAME } - fn atoms(&self) -> &'static [Option<&'static dyn AtomDynfo>] { Self::ATOM_DEFS } + fn atoms(&self) -> BoxedIter>> { Box::new(Self::atoms().into_iter()) } } /// System as defined by author pub trait System: Send + Sync + SystemCard + 'static { - fn env() -> Vec<(Tok, GenMemberKind)>; + fn env() -> Vec<(Tok, MemKind)>; fn vfs() -> DeclFs; fn lexers() -> Vec; fn parsers() -> Vec; + fn request(hand: ExtReq, req: Self::Req) -> Receipt; } pub trait DynSystem: Send + Sync + DynSystemCard + 'static { - fn dyn_env(&self) -> HashMap, GenMemberKind>; + fn dyn_env(&self) -> HashMap, MemKind>; fn dyn_vfs(&self) -> DeclFs; fn dyn_lexers(&self) -> Vec; fn dyn_parsers(&self) -> Vec; + fn dyn_request(&self, hand: ExtReq, req: Vec) -> Receipt; fn card(&self) -> &dyn DynSystemCard; } impl DynSystem for T { - fn dyn_env(&self) -> HashMap, GenMemberKind> { Self::env().into_iter().collect() } + fn dyn_env(&self) -> HashMap, MemKind> { Self::env().into_iter().collect() } fn dyn_vfs(&self) -> DeclFs { Self::vfs() } fn dyn_lexers(&self) -> Vec { Self::lexers() } fn dyn_parsers(&self) -> Vec { Self::parsers() } + fn dyn_request(&self, hand: ExtReq, req: Vec) -> Receipt { + Self::request(hand, ::Req::decode(&mut &req[..])) + } fn card(&self) -> &dyn DynSystemCard { self } } @@ -101,7 +108,7 @@ pub fn downcast_atom(foreign: ForeignAtom) -> Result(sys.get_card())) - .filter(|(pos, _)| AtomId::decode(&mut data) == *pos); + .filter(|(pos, _)| api::AtomId::decode(&mut data) == *pos); match info_ent { None => Err(foreign), Some((_, info)) => { diff --git a/orchid-extension/src/tree.rs b/orchid-extension/src/tree.rs index c9ff3bd..9b571b5 100644 --- a/orchid-extension/src/tree.rs +++ b/orchid-extension/src/tree.rs @@ -1,6 +1,5 @@ use std::num::NonZero; use std::ops::Range; -use std::sync::Arc; use dyn_clone::{clone_box, DynClone}; use hashbrown::HashMap; @@ -8,7 +7,9 @@ use itertools::Itertools; use orchid_base::interner::{intern, Tok}; use orchid_base::location::Pos; use orchid_base::name::Sym; +use orchid_base::parse::Comment; use orchid_base::tree::{TokTree, Token}; +use ordered_float::NotNan; use substack::Substack; use trait_set::trait_set; @@ -16,8 +17,9 @@ use crate::api; use crate::atom::{AtomFactory, ForeignAtom}; use crate::conv::ToExpr; use crate::entrypoint::MemberRecord; -use crate::expr::GenExpr; +use crate::expr::Expr; use crate::func_atom::{ExprFunc, Fun}; +use crate::macros::Rule; use crate::system::SysCtx; pub type GenTokTree<'a> = TokTree<'a, ForeignAtom<'a>, AtomFactory>; @@ -27,68 +29,85 @@ pub fn do_extra(f: &AtomFactory, r: Range, ctx: SysCtx) -> api::TokenTree { api::TokenTree { range: r, token: api::Token::Atom(f.clone().build(ctx)) } } +fn with_export(mem: GenMember, public: bool) -> Vec { + (public.then(|| GenItemKind::Export(mem.name.clone()).at(Pos::Inherit)).into_iter()) + .chain([GenItemKind::Member(mem).at(Pos::Inherit)]) + .collect() +} + pub struct GenItem { - pub item: GenItemKind, - pub comments: Vec<(String, Pos)>, + pub kind: GenItemKind, + pub comments: Vec, pub pos: Pos, } impl GenItem { pub fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::Item { - let kind = match self.item { - GenItemKind::Raw(item) => api::ItemKind::Raw(Vec::from_iter( - item.into_iter().map(|t| t.to_api(&mut |f, r| do_extra(f, r, ctx.sys()))), - )), + let kind = match self.kind { + GenItemKind::Export(n) => api::ItemKind::Export(n.marker()), GenItemKind::Member(mem) => api::ItemKind::Member(mem.into_api(ctx)), + GenItemKind::Import(cn) => api::ItemKind::Import(cn.tok().marker()), + GenItemKind::Macro(prio, rules) => api::ItemKind::Macro(api::MacroBlock { + priority: prio, + rules: rules.into_iter().map(|r| r.to_api() ).collect_vec(), + }) }; - let comments = self.comments.into_iter().map(|(s, p)| (Arc::new(s), p.to_api())).collect_vec(); + let comments = self.comments.into_iter().map(|c| c.to_api()).collect_vec(); api::Item { location: self.pos.to_api(), comments, kind } } } -pub fn cnst(public: bool, name: &str, value: impl ToExpr) -> GenItem { - let kind = GenMemberKind::Const(value.to_expr()); - GenItemKind::Member(GenMember { exported: public, name: intern(name), kind }).at(Pos::Inherit) +pub fn cnst(public: bool, name: &str, value: impl ToExpr) -> Vec { + with_export(GenMember { name: intern(name), kind: MemKind::Const(value.to_expr()) }, public) } pub fn module( public: bool, name: &str, imports: impl IntoIterator, - items: impl IntoIterator, -) -> GenItem { + items: impl IntoIterator>, +) -> Vec { let (name, kind) = root_mod(name, imports, items); - GenItemKind::Member(GenMember { exported: public, name, kind }).at(Pos::Inherit) + with_export(GenMember { name, kind }, public) } pub fn root_mod( name: &str, imports: impl IntoIterator, - items: impl IntoIterator, -) -> (Tok, GenMemberKind) { - let kind = GenMemberKind::Mod { + items: impl IntoIterator>, +) -> (Tok, MemKind) { + let kind = MemKind::Mod { imports: imports.into_iter().collect(), - items: items.into_iter().collect(), + items: items.into_iter().flatten().collect(), }; (intern(name), kind) } -pub fn fun(exported: bool, name: &str, xf: impl ExprFunc) -> GenItem { - let fac = LazyMemberFactory::new(move |sym| GenMemberKind::Const(Fun::new(sym, xf).to_expr())); - let mem = GenMember { exported, name: intern(name), kind: GenMemberKind::Lazy(fac) }; - GenItemKind::Member(mem).at(Pos::Inherit) +pub fn fun(exported: bool, name: &str, xf: impl ExprFunc) -> Vec { + let fac = LazyMemberFactory::new(move |sym| MemKind::Const(Fun::new(sym, xf).to_expr())); + with_export(GenMember { name: intern(name), kind: MemKind::Lazy(fac) }, exported) +} +pub fn macro_block(prio: Option, rules: impl IntoIterator) -> Vec { + let prio = prio.map(|p| NotNan::new(p).unwrap()); + vec![GenItemKind::Macro(prio, rules.into_iter().collect_vec()).gen()] } -pub fn comments<'a>(cmts: impl IntoIterator, mut val: GenItem) -> GenItem { - val.comments.extend(cmts.into_iter().map(|c| (c.to_string(), Pos::Inherit))); +pub fn comments<'a>( + cmts: impl IntoIterator + Clone, + mut val: Vec, +) -> Vec { + for v in val.iter_mut() { + v.comments + .extend(cmts.clone().into_iter().map(|c| Comment { text: intern(c), pos: Pos::Inherit })); + } val } trait_set! { - trait LazyMemberCallback = FnOnce(Sym) -> GenMemberKind + Send + Sync + DynClone + trait LazyMemberCallback = FnOnce(Sym) -> MemKind + Send + Sync + DynClone } pub struct LazyMemberFactory(Box); impl LazyMemberFactory { - pub fn new(cb: impl FnOnce(Sym) -> GenMemberKind + Send + Sync + Clone + 'static) -> Self { + pub fn new(cb: impl FnOnce(Sym) -> MemKind + Send + Sync + Clone + 'static) -> Self { Self(Box::new(cb)) } - pub fn build(self, path: Sym) -> GenMemberKind { (self.0)(path) } + pub fn build(self, path: Sym) -> MemKind { (self.0)(path) } } impl Clone for LazyMemberFactory { fn clone(&self) -> Self { Self(clone_box(&*self.0)) } @@ -96,42 +115,48 @@ impl Clone for LazyMemberFactory { pub enum GenItemKind { Member(GenMember), - Raw(Vec>), + Export(Tok), + Import(Sym), + Macro(Option>, Vec), } impl GenItemKind { - pub fn at(self, position: Pos) -> GenItem { - GenItem { item: self, comments: vec![], pos: position } + pub fn at(self, pos: Pos) -> GenItem { GenItem { kind: self, comments: vec![], pos } } + pub fn gen(self) -> GenItem { GenItem { kind: self, comments: vec![], pos: Pos::Inherit } } + pub fn gen_equiv(self, comments: Vec) -> GenItem { + GenItem { kind: self, comments, pos: Pos::Inherit } } } pub struct GenMember { - exported: bool, name: Tok, - kind: GenMemberKind, + kind: MemKind, } impl GenMember { pub fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::Member { api::Member { name: self.name.marker(), - exported: self.exported, kind: self.kind.into_api(&mut ctx.push_path(self.name)), } } } -pub enum GenMemberKind { - Const(GenExpr), +pub enum MemKind { + Const(Expr), Mod { imports: Vec, items: Vec }, Lazy(LazyMemberFactory), } -impl GenMemberKind { +impl MemKind { pub fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::MemberKind { match self { Self::Lazy(lazy) => api::MemberKind::Lazy(ctx.with_lazy(lazy)), - Self::Const(c) => api::MemberKind::Const(c.into_api(ctx.sys())), + Self::Const(c) => + api::MemberKind::Const(c.api_return(ctx.sys(), &mut |_| panic!("Slot found in const tree"))), Self::Mod { imports, items } => api::MemberKind::Module(api::Module { - imports: imports.into_iter().map(|t| t.tok().marker()).collect(), - items: items.into_iter().map(|i| i.into_api(ctx)).collect_vec(), + items: (imports.into_iter()) + .map(|t| GenItemKind::Import(t).gen()) + .chain(items) + .map(|i| i.into_api(ctx)) + .collect_vec(), }), } } diff --git a/orchid-host/Cargo.toml b/orchid-host/Cargo.toml index 78a63ac..ef993cd 100644 --- a/orchid-host/Cargo.toml +++ b/orchid-host/Cargo.toml @@ -11,9 +11,11 @@ hashbrown = "0.14.5" itertools = "0.13.0" lazy_static = "1.4.0" never = "0.1.0" +num-traits = "0.2.19" orchid-api = { version = "0.1.0", path = "../orchid-api" } orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" } orchid-base = { version = "0.1.0", path = "../orchid-base" } ordered-float = "4.2.0" paste = "1.0.15" -substack = "1.1.0" +substack = "1.1.1" +trait-set = "0.3.0" diff --git a/orchid-host/src/expr.rs b/orchid-host/src/expr.rs index 5763fca..0e978af 100644 --- a/orchid-host/src/expr.rs +++ b/orchid-host/src/expr.rs @@ -1,24 +1,34 @@ +use std::collections::VecDeque; use std::num::NonZeroU64; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use hashbrown::HashMap; use lazy_static::lazy_static; +use orchid_base::error::OrcErrv; +use orchid_base::interner::deintern; +use orchid_base::location::Pos; +use orchid_base::name::Sym; +use orchid_base::tree::AtomTok; use crate::api; -use crate::extension::{AtomHand, System}; +use crate::extension::AtomHand; + +pub type ExprParseCtx = (); #[derive(Clone, Debug)] -pub struct RtExpr { +pub struct Expr { is_canonical: Arc, - data: Arc<()>, + pos: Pos, + kind: Arc>, } -impl RtExpr { +impl Expr { + pub fn pos(&self) -> Pos { self.pos.clone() } pub fn as_atom(&self) -> Option { todo!() } pub fn strong_count(&self) -> usize { todo!() } pub fn id(&self) -> api::ExprTicket { api::ExprTicket( - NonZeroU64::new(self.data.as_ref() as *const () as usize as u64) + NonZeroU64::new(self.kind.as_ref() as *const RwLock<_> as usize as u64) .expect("this is a ref, it cannot be null"), ) } @@ -31,14 +41,29 @@ impl RtExpr { pub fn resolve(tk: api::ExprTicket) -> Option { KNOWN_EXPRS.read().unwrap().get(&tk).cloned() } - pub fn from_api(api: api::Expr, sys: &System) -> Self { - Self { data: Arc::default(), is_canonical: Arc::default() } + pub fn from_api(api: api::Expression, ctx: &mut ExprParseCtx) -> Self { + if let api::ExpressionKind::Slot(tk) = &api.kind { + return Self::resolve(*tk).expect("Invalid slot"); + } + Self { + kind: Arc::new(RwLock::new(ExprKind::from_api(api.kind, ctx))), + is_canonical: Arc::default(), + pos: Pos::from_api(&api.location), + } + } + pub fn to_api(&self) -> api::InspectedKind { + use api::InspectedKind as K; + match &*self.kind.read().unwrap() { + ExprKind::Atom(a) => K::Atom(a.to_api()), + ExprKind::Bottom(b) => K::Bottom(b.to_api()), + _ => K::Opaque, + } } } -impl Drop for RtExpr { +impl Drop for Expr { fn drop(&mut self) { // If the only two references left are this and known, remove from known - if Arc::strong_count(&self.data) == 2 && self.is_canonical.load(Ordering::Relaxed) { + if Arc::strong_count(&self.kind) == 2 && self.is_canonical.load(Ordering::Relaxed) { // if known is poisoned, a leak is preferable to a panicking destructor if let Ok(mut w) = KNOWN_EXPRS.write() { w.remove(&self.id()); @@ -48,5 +73,67 @@ impl Drop for RtExpr { } lazy_static! { - static ref KNOWN_EXPRS: RwLock> = RwLock::default(); + static ref KNOWN_EXPRS: RwLock> = RwLock::default(); +} + +#[derive(Clone, Debug)] +pub enum ExprKind { + Seq(Expr, Expr), + Call(Expr, Expr), + Atom(AtomHand), + Argument, + Lambda(Option, Expr), + Bottom(OrcErrv), + Const(Sym), +} +impl ExprKind { + pub fn from_api(api: api::ExpressionKind, ctx: &mut ExprParseCtx) -> Self { + use api::ExpressionKind as K; + match api { + K::Slot(_) => panic!("Handled in Expr"), + K::Lambda(id, b) => ExprKind::Lambda(PathSet::from_api(id, &b), Expr::from_api(*b, ctx)), + K::Arg(_) => ExprKind::Argument, + K::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(&b)), + K::Call(f, x) => ExprKind::Call(Expr::from_api(*f, ctx), Expr::from_api(*x, ctx)), + K::Const(c) => ExprKind::Const(Sym::from_tok(deintern(c)).unwrap()), + K::NewAtom(a) => ExprKind::Atom(AtomHand::from_api(a)), + K::Seq(a, b) => ExprKind::Seq(Expr::from_api(*a, ctx), Expr::from_api(*b, ctx)), + } + } +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum Step { + Left, + Right, +} + +#[derive(Clone, Debug)] +pub struct PathSet { + /// The single steps through [super::nort::Clause::Apply] + pub steps: VecDeque, + /// if Some, it splits at a [super::nort::Clause::Apply]. If None, it ends in + /// a [super::nort::Clause::LambdaArg] + pub next: Option<(Box, Box)>, +} +impl PathSet { + pub fn after(mut self, step: Step) -> Self { + self.steps.push_front(step); + self + } + pub fn from_api(id: u64, b: &api::Expression) -> Option { + use api::ExpressionKind as K; + match &b.kind { + K::Arg(id2) => (id == *id2).then(|| Self { steps: VecDeque::new(), next: None }), + K::Bottom(_) | K::Const(_) | K::NewAtom(_) | K::Slot(_) => None, + K::Lambda(_, b) => Self::from_api(id, b), + K::Call(l, r) | K::Seq(l, r) => match (Self::from_api(id, l), Self::from_api(id, r)) { + (Some(a), Some(b)) => + Some(Self { steps: VecDeque::new(), next: Some((Box::new(a), Box::new(b))) }), + (Some(l), None) => Some(l.after(Step::Left)), + (None, Some(r)) => Some(r.after(Step::Right)), + (None, None) => None, + }, + } + } } diff --git a/orchid-host/src/extension.rs b/orchid-host/src/extension.rs index dfa841c..343fc8f 100644 --- a/orchid-host/src/extension.rs +++ b/orchid-host/src/extension.rs @@ -11,19 +11,23 @@ use hashbrown::hash_map::Entry; use hashbrown::HashMap; use itertools::Itertools; use lazy_static::lazy_static; -use orchid_api_traits::{enc_vec, Decode, Request}; +use orchid_api::TStrv; +use orchid_api_traits::Request; use orchid_base::char_filter::char_filter_match; -use orchid_base::error::{errv_from_apiv, mk_err, OrcRes}; +use orchid_base::error::{OrcErrv, OrcRes}; use orchid_base::interner::{deintern, intern, Tok}; use orchid_base::logging::Logger; +use orchid_base::macros::{mtreev_from_api, mtreev_to_api}; +use orchid_base::parse::Comment; use orchid_base::reqnot::{ReqNot, Requester as _}; -use orchid_base::tree::{ttv_from_api, AtomInTok}; -use orchid_base::{clone, intern}; +use orchid_base::tree::{ttv_from_api, AtomTok}; +use orchid_base::clone; use ordered_float::NotNan; use substack::{Stackframe, Substack}; use crate::api; -use crate::expr::RtExpr; +use crate::expr::Expr; +use crate::macros::macro_recur; use crate::tree::{Member, ParsTokTree}; #[derive(Debug, destructure)] @@ -76,7 +80,7 @@ impl AtomHand { Self::create_new(atom) } } - pub fn call(self, arg: RtExpr) -> api::Expr { + pub fn call(self, arg: Expr) -> api::Expression { let owner_sys = self.0.owner.clone(); let reqnot = owner_sys.reqnot(); let ticket = owner_sys.give_expr(arg.canonicalize(), || arg); @@ -85,20 +89,13 @@ impl AtomHand { Err(hand) => reqnot.request(api::CallRef(hand.api_ref(), ticket)), } } - pub fn same(&self, other: &AtomHand) -> bool { - let owner = self.0.owner.id(); - if other.0.owner.id() != owner { - return false; - } - self.0.owner.reqnot().request(api::AtomSame(self.0.api_ref(), other.0.api_ref())) - } - pub fn req(&self, req: Vec) -> Vec { - self.0.owner.reqnot().request(api::Fwded(self.0.api_ref(), req)) + pub fn req(&self, key: TStrv, req: Vec) -> Option> { + self.0.owner.reqnot().request(api::Fwded(self.0.api_ref(), key, req)) } pub fn api_ref(&self) -> api::Atom { self.0.api_ref() } pub fn print(&self) -> String { self.0.owner.reqnot().request(api::AtomPrint(self.0.api_ref())) } } -impl AtomInTok for AtomHand { +impl AtomTok for AtomHand { type Context = (); fn from_api(atom: &orchid_api::Atom, _: Range, (): &mut Self::Context) -> Self { Self::from_api(atom.clone()) @@ -118,6 +115,7 @@ impl fmt::Display for AtomHand { pub trait ExtensionPort: Send + Sync { fn send(&self, msg: &[u8]); fn receive(&self) -> Option>; + fn header(&self) -> &api::ExtensionHeader; } /// Data held about an Extension. This is refcounted within [Extension]. It's @@ -139,7 +137,7 @@ impl Drop for ExtensionData { fn acq_expr(sys: api::SysId, extk: api::ExprTicket) { (System::resolve(sys).expect("Expr acq'd by invalid system")) - .give_expr(extk, || RtExpr::resolve(extk).expect("Invalid expr acq'd")); + .give_expr(extk, || Expr::resolve(extk).expect("Invalid expr acq'd")); } fn rel_expr(sys: api::SysId, extk: api::ExprTicket) { @@ -154,10 +152,11 @@ fn rel_expr(sys: api::SysId, extk: api::ExprTicket) { pub struct Extension(Arc); impl Extension { pub fn new_process(port: Arc, logger: Logger) -> io::Result { - port.send(&enc_vec(&api::HostHeader { log_strategy: logger.strat() })); - let header_reply = port.receive().expect("Extension exited immediately"); - let eh = api::ExtensionHeader::decode(&mut &header_reply[..]); + let eh = port.header(); let ret = Arc::new_cyclic(|weak: &Weak| ExtensionData { + systems: (eh.systems.iter().cloned()) + .map(|decl| SystemCtor { decl, ext: weak.clone() }) + .collect(), logger, port: port.clone(), reqnot: ReqNot::new( @@ -175,46 +174,43 @@ impl Extension { }, api::ExtHostNotif::Log(api::Log(str)) => weak.upgrade().unwrap().logger.log(str), }), - |req| match req.req() { - api::ExtHostReq::Ping(ping) => req.handle(ping, &()), + |hand, req| match req { + api::ExtHostReq::Ping(ping) => hand.handle(&ping, &()), api::ExtHostReq::IntReq(intreq) => match intreq { - api::IntReq::InternStr(s) => req.handle(s, &intern(&**s.0).marker()), - api::IntReq::InternStrv(v) => req.handle(v, &intern(&*v.0).marker()), - api::IntReq::ExternStr(si) => req.handle(si, &deintern(si.0).arc()), + api::IntReq::InternStr(s) => hand.handle(&s, &intern(&**s.0).marker()), + api::IntReq::InternStrv(v) => hand.handle(&v, &intern(&*v.0).marker()), + api::IntReq::ExternStr(si) => hand.handle(&si, &deintern(si.0).arc()), api::IntReq::ExternStrv(vi) => - req.handle(vi, &Arc::new(deintern(vi.0).iter().map(|t| t.marker()).collect_vec())), + hand.handle(&vi, &Arc::new(deintern(vi.0).iter().map(|t| t.marker()).collect_vec())), }, - api::ExtHostReq::Fwd(fw @ api::Fwd(atom, _body)) => { + api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => { let sys = System::resolve(atom.owner).unwrap(); - req.handle(fw, &sys.reqnot().request(api::Fwded(fw.0.clone(), fw.1.clone()))) + hand.handle(fw, &sys.reqnot().request(api::Fwded(fw.0.clone(), *key, body.clone()))) + }, + api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => { + let sys = System::resolve(id).unwrap(); + hand.handle(fw, &sys.request(body.clone())) }, api::ExtHostReq::SubLex(sl) => { let (rep_in, rep_out) = sync_channel(0); let lex_g = LEX_RECUR.lock().unwrap(); let req_in = lex_g.get(&sl.id).expect("Sublex for nonexistent lexid"); req_in.send(ReqPair(sl.clone(), rep_in)).unwrap(); - req.handle(sl, &rep_out.recv().unwrap()) + hand.handle(&sl, &rep_out.recv().unwrap()) }, - api::ExtHostReq::ExprReq(api::ExprReq::Inspect(ins @ api::Inspect(tk))) => { - let expr = RtExpr::resolve(*tk); - req.handle(ins, &api::Details { - refcount: 1, - expr: api::Expr { - location: api::Location::None, - clause: api::Clause::Bottom(vec![ - mk_err( - intern!(str: "Unsupported"), - "Inspecting clauses is unsupported at the moment", - [], - ) - .to_api(), - ]), - }, + api::ExtHostReq::ExprReq(api::ExprReq::Inspect(ins @ api::Inspect { target })) => { + let expr = Expr::resolve(target).expect("Invalid ticket"); + hand.handle(&ins, &api::Inspected { + refcount: expr.strong_count() as u32, + location: expr.pos().to_api(), + kind: expr.to_api(), }) }, + api::ExtHostReq::RunMacros(ref rm @ api::RunMacros{ ref run_id, ref query }) => { + hand.handle(rm, ¯o_recur(*run_id, mtreev_from_api(query)).map(|x| mtreev_to_api(&x))) + } }, ), - systems: eh.systems.into_iter().map(|decl| SystemCtor { decl, ext: weak.clone() }).collect(), }); let weak = Arc::downgrade(&ret); thread::Builder::new() @@ -263,7 +259,11 @@ impl SystemCtor { id, })); let root = (sys_inst.const_root.into_iter()) - .map(|(k, v)| Member::from_api(api::Member { exported: true, name: k, kind: v }, &data)) + .map(|(k, v)| Member::from_api( + api::Member { name: k, kind: v }, + Substack::Bottom.push(deintern(k)), + &data + )) .collect_vec(); data.0.const_root.set(root).unwrap(); inst_g.insert(id, data.clone()); @@ -281,7 +281,7 @@ pub struct ReqPair(R, pub SyncSender); #[derive(destructure)] pub struct SystemInstData { - exprs: RwLock>, + exprs: RwLock>, ext: Extension, decl_id: api::SysDeclId, lex_filter: api::CharFilter, @@ -303,11 +303,7 @@ impl System { pub fn id(&self) -> api::SysId { self.id } fn resolve(id: api::SysId) -> Option { SYSTEM_INSTS.read().unwrap().get(&id).cloned() } fn reqnot(&self) -> &ReqNot { &self.0.ext.0.reqnot } - fn give_expr( - &self, - ticket: api::ExprTicket, - get_expr: impl FnOnce() -> RtExpr, - ) -> api::ExprTicket { + fn give_expr(&self, ticket: api::ExprTicket, get_expr: impl FnOnce() -> Expr) -> api::ExprTicket { match self.0.exprs.write().unwrap().entry(ticket) { Entry::Occupied(mut oe) => { oe.get_mut().0.fetch_add(1, Ordering::Relaxed); @@ -356,12 +352,22 @@ impl System { pub fn line_types(&self) -> impl Iterator> + '_ { self.line_types.iter().cloned() } - pub fn parse(&self, line: Vec) -> OrcRes> { + pub fn parse( + &self, + line: Vec, + exported: bool, + comments: Vec, + ) -> OrcRes> { let line = line.iter().map(|t| t.to_api(&mut |n, _| match *n {})).collect_vec(); - let parsed = (self.reqnot().request(api::ParseLine { sys: self.id(), line })) - .map_err(|e| errv_from_apiv(e.iter()))?; + let comments = comments.iter().map(Comment::to_api).collect_vec(); + let parsed = + (self.reqnot().request(api::ParseLine { exported, sys: self.id(), comments, line })) + .map_err(|e| OrcErrv::from_api(&e))?; Ok(ttv_from_api(parsed, &mut ())) } + pub fn request(&self, req: Vec) -> Vec { + self.reqnot().request(api::SysFwded(self.id(), req)) + } } impl fmt::Debug for System { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/orchid-host/src/lex.rs b/orchid-host/src/lex.rs index c5ad67b..b36df50 100644 --- a/orchid-host/src/lex.rs +++ b/orchid-host/src/lex.rs @@ -2,12 +2,14 @@ use std::num::NonZeroU64; use std::sync::Arc; use hashbrown::HashMap; -use orchid_base::error::{mk_err, OrcErr, OrcRes}; +use orchid_base::error::{mk_errv, OrcErrv, OrcRes}; use orchid_base::intern; use orchid_base::interner::{deintern, intern, Tok}; use orchid_base::location::Pos; +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::Ph; use crate::api; use crate::extension::{AtomHand, System}; @@ -81,11 +83,9 @@ pub fn lex_once(ctx: &mut LexCtx) -> OrcRes { ParsTok::NS } else if ctx.strip_prefix("--[") { let (cmt, tail) = ctx.tail.split_once("]--").ok_or_else(|| { - vec![mk_err( - intern!(str: "Unterminated block comment"), - "This block comment has no ending ]--", - [Pos::Range(start..start + 3).into()], - )] + mk_errv(intern!(str: "Unterminated block comment"), "This block comment has no ending ]--", [ + Pos::Range(start..start + 3).into(), + ]) })?; ctx.set_tail(tail); ParsTok::Comment(Arc::new(cmt.to_string())) @@ -98,11 +98,11 @@ pub fn lex_once(ctx: &mut LexCtx) -> OrcRes { ctx.trim_ws(); while !ctx.strip_char('.') { if ctx.tail.is_empty() { - return Err(vec![mk_err( + return Err(mk_errv( intern!(str: "Unclosed lambda"), "Lambdae started with \\ should separate arguments from body with .", [Pos::Range(start..start + 1).into()], - )]); + )); } arg.push(lex_once(ctx)?); ctx.trim_ws(); @@ -113,33 +113,46 @@ pub fn lex_once(ctx: &mut LexCtx) -> OrcRes { ctx.trim_ws(); while !ctx.strip_char(*rp) { if ctx.tail.is_empty() { - return Err(vec![mk_err( + return Err(mk_errv( intern!(str: "unclosed paren"), format!("this {lp} has no matching {rp}"), [Pos::Range(start..start + 1).into()], - )]); + )); } body.push(lex_once(ctx)?); ctx.trim_ws(); } ParsTok::S(paren.clone(), 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(); + let num = parse_num(numstr).map_err(|e| num_to_err(e, pos))?; + ParsTok::Macro(Some(num.to_f64())) + } else { + ParsTok::Macro(None) + } } else { for sys in ctx.systems { let mut errors = Vec::new(); if ctx.tail.starts_with(|c| sys.can_lex(c)) { - let lexed = sys.lex(ctx.source.clone(), ctx.get_pos(), |pos| { - let mut sub_ctx = ctx.push(pos); - let ott = - lex_once(&mut sub_ctx).inspect_err(|e| errors.extend(e.iter().cloned())).ok()?; - Some(api::SubLexed { pos: sub_ctx.get_pos(), ticket: sub_ctx.add_subtree(ott) }) - }); - match lexed { - Ok(None) if errors.is_empty() => continue, - Ok(None) => return Err(errors), - Err(e) => return Err(e.into_iter().map(|e| OrcErr::from_api(&e)).collect()), - Ok(Some(lexed)) => { - ctx.set_pos(lexed.pos); - return Ok(tt_to_owned(&lexed.expr, ctx)); + let lx = + sys.lex(ctx.source.clone(), ctx.get_pos(), |pos| match lex_once(&mut ctx.push(pos)) { + Ok(t) => Some(api::SubLexed { pos, ticket: ctx.add_subtree(t) }), + Err(e) => { + errors.push(e); + None + }, + }); + match lx { + Err(e) => return Err(errors.into_iter().fold(OrcErrv::from_api(&e), |a, b| a + b)), + Ok(Some(lexed)) => return Ok(tt_to_owned(&lexed.expr, &mut ctx.push(lexed.pos))), + Ok(None) => match errors.into_iter().reduce(|a, b| a + b) { + Some(errors) => return Err(errors), + None => continue, }, } } @@ -149,11 +162,11 @@ pub fn lex_once(ctx: &mut LexCtx) -> OrcRes { } else if ctx.tail.starts_with(op_char) { ParsTok::Name(intern(ctx.get_start_matches(op_char))) } else { - return Err(vec![mk_err( + return Err(mk_errv( intern!(str: "Unrecognized character"), "The following syntax is meaningless.", [Pos::Range(start..start + 1).into()], - )]); + )); } }; Ok(ParsTokTree { tok, range: start..ctx.get_pos() }) @@ -162,19 +175,28 @@ pub fn lex_once(ctx: &mut LexCtx) -> OrcRes { fn tt_to_owned(api: &api::TokenTree, ctx: &mut LexCtx<'_>) -> ParsTokTree { let tok = match &api.token { api::Token::Atom(atom) => ParsTok::Atom(AtomHand::from_api(atom.clone())), - api::Token::Bottom(err) => ParsTok::Bottom(err.iter().map(OrcErr::from_api).collect()), - api::Token::Lambda(arg) => - ParsTok::LambdaHead(arg.iter().map(|t| tt_to_owned(t, ctx)).collect()), + api::Token::Bottom(err) => ParsTok::Bottom(OrcErrv::from_api(err)), + api::Token::LambdaHead(arg) => ParsTok::LambdaHead(ttv_to_owned(arg, ctx)), + api::Token::Lambda(arg, b) => ParsTok::Lambda(ttv_to_owned(arg, ctx), ttv_to_owned(b, ctx)), api::Token::Name(name) => ParsTok::Name(deintern(*name)), api::Token::S(p, b) => ParsTok::S(p.clone(), b.iter().map(|t| tt_to_owned(t, ctx)).collect()), api::Token::Slot(id) => return ctx.rm_subtree(*id), api::Token::BR => ParsTok::BR, api::Token::NS => ParsTok::NS, api::Token::Comment(c) => ParsTok::Comment(c.clone()), + api::Token::Ph(ph) => ParsTok::Ph(Ph::from_api(ph)), + api::Token::Macro(prio) => ParsTok::Macro(*prio) }; ParsTokTree { range: api.range.clone(), tok } } +fn ttv_to_owned<'a>( + api: impl IntoIterator, + ctx: &mut LexCtx<'_> +) -> Vec { + api.into_iter().map(|t| tt_to_owned(t, ctx)).collect() +} + pub fn lex(text: Tok, systems: &[System]) -> OrcRes> { let mut sub_trees = HashMap::new(); let mut ctx = LexCtx { source: &text, sub_trees: &mut sub_trees, tail: &text[..], systems }; diff --git a/orchid-host/src/lib.rs b/orchid-host/src/lib.rs index 46192c9..bb77322 100644 --- a/orchid-host/src/lib.rs +++ b/orchid-host/src/lib.rs @@ -7,3 +7,4 @@ pub mod lex; pub mod parse; pub mod subprocess; pub mod tree; +pub mod macros; diff --git a/orchid-host/src/macros.rs b/orchid-host/src/macros.rs new file mode 100644 index 0000000..9339a94 --- /dev/null +++ b/orchid-host/src/macros.rs @@ -0,0 +1,20 @@ +use std::sync::RwLock; + +use hashbrown::HashMap; +use lazy_static::lazy_static; +use orchid_base::macros::MTree; +use trait_set::trait_set; +use crate::api::ParsId; + +trait_set!{ + trait MacroCB = Fn(Vec) -> Option> + Send + Sync; +} + +lazy_static!{ + static ref RECURSION: RwLock>> = RwLock::default(); +} + +pub fn macro_recur(run_id: ParsId, input: Vec) -> Option> { + (RECURSION.read().unwrap()[&run_id])(input) +} + diff --git a/orchid-host/src/parse.rs b/orchid-host/src/parse.rs index 69764ca..6765f53 100644 --- a/orchid-host/src/parse.rs +++ b/orchid-host/src/parse.rs @@ -2,18 +2,21 @@ use std::{iter, thread}; use itertools::Itertools; use never::Never; -use orchid_base::error::{mk_err, OrcErr, OrcRes, Reporter}; +use orchid_base::error::{mk_err, mk_errv, OrcErrv, OrcRes, Reporter}; use orchid_base::intern; use orchid_base::interner::Tok; use orchid_base::location::Pos; +use orchid_base::macros::{MTok, MTree}; +use orchid_base::name::Sym; use orchid_base::parse::{ - expect_end, line_items, parse_multiname, strip_fluff, try_pop_no_fluff, Comment, CompName, - Snippet, + expect_end, line_items, parse_multiname, strip_fluff, try_pop_no_fluff, Comment, Import, + Parsed, Snippet, }; use orchid_base::tree::{Paren, TokTree, Token}; +use substack::Substack; use crate::extension::{AtomHand, System}; -use crate::tree::{Item, ItemKind, Member, MemberKind, Module, ParsTokTree}; +use crate::tree::{Code, CodeLocator, Item, ItemKind, Member, MemberKind, Module, ParsTokTree, Rule, RuleKind}; type ParsSnippet<'a> = Snippet<'a, 'static, AtomHand, Never>; @@ -22,15 +25,20 @@ pub trait ParseCtx: Send + Sync { fn reporter(&self) -> &impl Reporter; } -pub fn parse_items(ctx: &impl ParseCtx, items: ParsSnippet) -> OrcRes> { +pub fn parse_items( + ctx: &impl ParseCtx, + path: Substack>, + items: ParsSnippet +) -> OrcRes> { let lines = line_items(items); let mut ok = iter::from_fn(|| None).take(lines.len()).collect_vec(); thread::scope(|s| { let mut threads = Vec::new(); - for (slot, (cmts, item)) in ok.iter_mut().zip(lines.into_iter()) { + for (slot, Parsed { output: cmts, tail }) in ok.iter_mut().zip(lines.into_iter()) { + let path = &path; threads.push(s.spawn(move || { - *slot = Some(parse_item(ctx, cmts, item)?); - Ok::<(), Vec>(()) + *slot = Some(parse_item(ctx, path.clone(), cmts, tail)?); + Ok::<(), OrcErrv>(()) })) } for t in threads { @@ -42,136 +50,239 @@ pub fn parse_items(ctx: &impl ParseCtx, items: ParsSnippet) -> OrcRes> pub fn parse_item( ctx: &impl ParseCtx, + path: Substack>, comments: Vec, item: ParsSnippet, ) -> OrcRes> { match item.pop_front() { Some((TokTree { tok: Token::Name(n), .. }, postdisc)) => match n { n if *n == intern!(str: "export") => match try_pop_no_fluff(postdisc)? { - (TokTree { tok: Token::Name(n), .. }, postdisc) => - parse_item_2(ctx, comments, true, n.clone(), postdisc), - (TokTree { tok: Token::NS, .. }, postdisc) => { - let (exports, surplus) = parse_multiname(ctx.reporter(), postdisc)?; + Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } => + parse_exportable_item(ctx, path, comments, true, n.clone(), tail), + Parsed { output: TokTree { tok: Token::NS, .. }, tail } => { + let Parsed { output: exports, tail } = parse_multiname(ctx.reporter(), tail)?; let mut ok = Vec::new(); - exports.into_iter().for_each(|e| match (&e.path.as_slice(), e.name) { - ([], Some(n)) => ok.push(Item { - comments: comments.clone(), - pos: e.pos.clone(), - kind: ItemKind::Export(n), - }), + exports.into_iter().for_each(|(e, pos)| match (&e.path.as_slice(), e.name) { + ([], Some(n)) => + ok.push(Item { comments: comments.clone(), pos, kind: ItemKind::Export(n) }), (_, Some(_)) => ctx.reporter().report(mk_err( intern!(str: "Compound export"), "Cannot export compound names (names containing the :: separator)", - [e.pos.into()], + [pos.into()], )), (_, None) => ctx.reporter().report(mk_err( intern!(str: "Wildcard export"), "Exports cannot contain the globstar *", - [e.pos.into()], + [pos.into()], )), }); - expect_end(surplus)?; + expect_end(tail)?; Ok(ok) }, - (bogus, _) => Err(vec![mk_err( + Parsed { output, .. } => Err(mk_errv( intern!(str: "Malformed export"), "`export` can either prefix other lines or list names inside ::( ) or ::[ ]", - [Pos::Range(bogus.range.clone()).into()], - )]), + [Pos::Range(output.range.clone()).into()], + )), }, n if *n == intern!(str: "import") => parse_import(ctx, postdisc).map(|v| { - Vec::from_iter(v.into_iter().map(|t| Item { + Vec::from_iter(v.into_iter().map(|(t, pos)| Item { comments: comments.clone(), - pos: Pos::Range(postdisc.pos()), + pos, kind: ItemKind::Import(t), })) }), - n => parse_item_2(ctx, comments, false, n.clone(), postdisc), + n => parse_exportable_item(ctx, path, comments, false, n.clone(), postdisc), }, - Some(_) => Err(vec![mk_err( - intern!(str: "Expected a line type"), - "All lines must begin with a keyword", - [Pos::Range(item.pos()).into()], - )]), + Some(_) => + Err(mk_errv(intern!(str: "Expected a line type"), "All lines must begin with a keyword", [ + Pos::Range(item.pos()).into(), + ])), None => unreachable!("These lines are filtered and aggregated in earlier stages"), } } -pub fn parse_import(ctx: &impl ParseCtx, tail: ParsSnippet) -> OrcRes> { - let (imports, surplus) = parse_multiname(ctx.reporter(), tail)?; - expect_end(surplus)?; +pub fn parse_import(ctx: &impl ParseCtx, tail: ParsSnippet) -> OrcRes> { + let Parsed { output: imports, tail } = parse_multiname(ctx.reporter(), tail)?; + expect_end(tail)?; Ok(imports) } -pub fn parse_item_2( +pub fn parse_exportable_item( ctx: &impl ParseCtx, + path: Substack>, comments: Vec, exported: bool, discr: Tok, tail: ParsSnippet, ) -> OrcRes> { let kind = if discr == intern!(str: "mod") { - let (name, body) = parse_module(ctx, tail)?; - ItemKind::Member(Member::new(exported, name, MemberKind::Mod(body))) + let (name, body) = parse_module(ctx, path, tail)?; + ItemKind::Member(Member::new(name, MemberKind::Mod(body))) } else if discr == intern!(str: "const") { let (name, val) = parse_const(tail)?; - ItemKind::Member(Member::new(exported, name, MemberKind::Const(val))) + let locator = CodeLocator::to_const(path.push(name.clone()).unreverse()); + ItemKind::Member(Member::new(name, MemberKind::Const(Code::from_code(locator, val)))) } else if let Some(sys) = ctx.systems().find(|s| s.can_parse(discr.clone())) { - let line = sys.parse(tail.to_vec())?; - return parse_items(ctx, Snippet::new(tail.prev(), &line)); + let line = sys.parse(tail.to_vec(), exported, comments)?; + return parse_items(ctx, path, Snippet::new(tail.prev(), &line)); } else { let ext_lines = ctx.systems().flat_map(System::line_types).join(", "); - return Err(vec![mk_err( + return Err(mk_errv( intern!(str: "Unrecognized line type"), format!("Line types are: const, mod, macro, grammar, {ext_lines}"), [Pos::Range(tail.prev().range.clone()).into()], - )]); + )); }; Ok(vec![Item { comments, pos: Pos::Range(tail.pos()), kind }]) } -pub fn parse_module(ctx: &impl ParseCtx, tail: ParsSnippet) -> OrcRes<(Tok, Module)> { +pub fn parse_module( + ctx: &impl ParseCtx, + path: Substack>, + tail: ParsSnippet +) -> OrcRes<(Tok, Module)> { let (name, tail) = match try_pop_no_fluff(tail)? { - (TokTree { tok: Token::Name(n), .. }, tail) => (n.clone(), tail), - (tt, _) => - return Err(vec![mk_err( + Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } => (n.clone(), tail), + Parsed { output, .. } => + return Err(mk_errv( intern!(str: "Missing module name"), - format!("A name was expected, {tt} was found"), - [Pos::Range(tt.range.clone()).into()], - )]), + format!("A name was expected, {output} was found"), + [Pos::Range(output.range.clone()).into()], + )), }; - let (body, surplus) = match try_pop_no_fluff(tail)? { - (TokTree { tok: Token::S(Paren::Round, b), .. }, tail) => (b, tail), - (tt, _) => - return Err(vec![mk_err( - intern!(str: "Expected module body"), - format!("A ( block ) was expected, {tt} was found"), - [Pos::Range(tt.range.clone()).into()], - )]), - }; - let items = parse_items(ctx, ParsSnippet::new(surplus.prev(), body))?; - Ok((name, Module { imports: vec![], items })) + let Parsed { output, tail: surplus } = try_pop_no_fluff(tail)?; + expect_end(surplus)?; + let body = output.as_s(Paren::Round).ok_or_else(|| mk_errv( + intern!(str: "Expected module body"), + format!("A ( block ) was expected, {output} was found"), + [Pos::Range(output.range.clone()).into()], + ))?; + let path = path.push(name.clone()); + Ok((name, Module::new(parse_items(ctx, path, body)?))) } pub fn parse_const(tail: ParsSnippet) -> OrcRes<(Tok, Vec)> { - let (name, tail) = match try_pop_no_fluff(tail)? { - (TokTree { tok: Token::Name(n), .. }, tail) => (n.clone(), tail), - (tt, _) => - return Err(vec![mk_err( - intern!(str: "Missing module name"), - format!("A name was expected, {tt} was found"), - [Pos::Range(tt.range.clone()).into()], - )]), - }; - let tail = match try_pop_no_fluff(tail)? { - (TokTree { tok: Token::Name(n), .. }, tail) if *n == intern!(str: ":=") => tail, - (tt, _) => - return Err(vec![mk_err( - intern!(str: "Missing walrus := separator"), - format!("Expected operator := , found {tt}"), - [Pos::Range(tt.range.clone()).into()], - )]), - }; + let Parsed { output, tail } = try_pop_no_fluff(tail)?; + let name = output.as_name().ok_or_else(|| mk_errv( + intern!(str: "Missing module name"), + format!("A name was expected, {output} was found"), + [Pos::Range(output.range.clone()).into()], + ))?; + let Parsed { output, tail } = try_pop_no_fluff(tail)?; + if !output.is_kw(intern!(str: "=")) { + return Err(mk_errv( + intern!(str: "Missing walrus := separator"), + format!("Expected operator := , found {output}"), + [Pos::Range(output.range.clone()).into()], + )) + } try_pop_no_fluff(tail)?; Ok((name, tail.iter().flat_map(strip_fluff).collect_vec())) } + +pub fn parse_mtree<'a>( + mut snip: ParsSnippet<'a> +) -> OrcRes>> { + let mut mtreev = Vec::new(); + while let Some((ttree, tail)) = snip.pop_front() { + let (range, tok, tail) = match &ttree.tok { + Token::S(p, b) => ( + ttree.range.clone(), + MTok::S(*p, parse_mtree(Snippet::new(ttree, b))?), + tail, + ), + Token::Name(tok) => { + let mut segments = vec![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)?; + segments.push(output.as_name().ok_or_else(|| mk_errv( + intern!(str: "Namespaced name interrupted"), + "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()] + ))?); + snip = tail; + end = output.range.end; + } + (ttree.range.start..end, MTok::Name(Sym::new(segments).unwrap()), snip) + }, + Token::NS => return Err(mk_errv( + intern!(str: "Unexpected :: in macro pattern"), + ":: can only follow a name outside export statements", + [Pos::Range(ttree.range.clone()).into()] + )), + Token::Ph(ph) => (ttree.range.clone(), MTok::Ph(ph.clone()), tail), + Token::Atom(_) | Token::Macro(_) => return Err(mk_errv( + intern!(str: "Unsupported token in macro patterns"), + format!("Macro patterns can only contain names, braces, and lambda, not {ttree}."), + [Pos::Range(ttree.range.clone()).into()] + )), + Token::BR | Token::Comment(_) => continue, + Token::Bottom(e) => return Err(e.clone()), + Token::Lambda(arg, body) => { + let tok = MTok::Lambda( + parse_mtree(Snippet::new(&ttree, &arg))?, + parse_mtree(Snippet::new(&ttree, &body))?, + ); + (ttree.range.clone(), tok, tail) + }, + Token::LambdaHead(arg) => ( + ttree.range.start..snip.pos().end, + MTok::Lambda(parse_mtree(Snippet::new(&ttree, &arg))?, parse_mtree(tail)?), + Snippet::new(ttree, &[]), + ), + Token::Slot(_) | Token::X(_) => panic!("Did not expect {} in parsed token tree", &ttree.tok), + }; + mtreev.push(MTree { pos: Pos::Range(range.clone()), tok }); + snip = tail; + } + Ok(mtreev) +} + +pub fn parse_macro(tail: ParsSnippet, macro_i: u16, path: Substack>) -> OrcRes> { + let (surplus, prev, block) = match try_pop_no_fluff(tail)? { + Parsed { tail, output: o@TokTree { tok: Token::S(Paren::Round, b), .. } } => (tail, o, b), + Parsed { output, .. } => return Err(mk_errv( + intern!(str: "m"), + format!("Macro blocks must either start with a block or a ..$:number"), + [Pos::Range(output.range.clone()).into()] + )), + }; + expect_end(surplus)?; + let mut errors = Vec::new(); + let mut rules = Vec::new(); + for (i, item) in line_items(Snippet::new(prev, &block)).into_iter().enumerate() { + let Parsed { tail, output } = try_pop_no_fluff(item.tail)?; + if !output.is_kw(intern!(str: "rule")) { + errors.extend(mk_errv( + intern!(str: "non-rule in macro"), + format!("Expected `rule`, got {output}"), + [Pos::Range(output.range.clone()).into()] + )); + continue + }; + let (pat, body) = match tail.split_once(|t| t.is_kw(intern!(str: "=>"))) { + Some((a, b)) => (a, b), + None => { + errors.extend(mk_errv( + intern!(str: "no => in macro rule"), + "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)?, + kind: RuleKind::Native(Code::from_code( + CodeLocator::to_rule(path.unreverse(), macro_i, i as u16), + body.to_vec(), + )) + }) + } + if let Ok(e) = OrcErrv::new(errors) { Err(e) } else { Ok(rules) } +} diff --git a/orchid-host/src/subprocess.rs b/orchid-host/src/subprocess.rs index 2e6cf0d..167f5a3 100644 --- a/orchid-host/src/subprocess.rs +++ b/orchid-host/src/subprocess.rs @@ -1,17 +1,21 @@ -use std::io::{self, BufRead as _}; +use std::io::{self, BufRead as _, Write}; use std::path::PathBuf; use std::sync::Mutex; use std::{process, thread}; +use orchid_api::ExtensionHeader; +use orchid_api_traits::{Decode, Encode}; use orchid_base::logging::Logger; use orchid_base::msg::{recv_msg, send_msg}; +use crate::api; use crate::extension::ExtensionPort; pub struct Subprocess { child: Mutex, stdin: Mutex, stdout: Mutex, + header: ExtensionHeader, } impl Subprocess { pub fn new(mut cmd: process::Command, logger: Logger) -> io::Result { @@ -22,8 +26,11 @@ impl Subprocess { .stdout(process::Stdio::piped()) .stderr(process::Stdio::piped()) .spawn()?; - let stdin = child.stdin.take().unwrap(); - let stdout = child.stdout.take().unwrap(); + let mut stdin = child.stdin.take().unwrap(); + api::HostHeader { log_strategy: logger.strat() }.encode(&mut stdin); + stdin.flush()?; + let mut stdout = child.stdout.take().unwrap(); + let header = ExtensionHeader::decode(&mut stdout); let child_stderr = child.stderr.take().unwrap(); thread::Builder::new().name(format!("stderr-fwd:{prog}")).spawn(move || { let mut reader = io::BufReader::new(child_stderr); @@ -35,14 +42,25 @@ impl Subprocess { logger.log(buf); } })?; - Ok(Self { child: Mutex::new(child), stdin: Mutex::new(stdin), stdout: Mutex::new(stdout) }) + Ok(Self { + child: Mutex::new(child), + stdin: Mutex::new(stdin), + stdout: Mutex::new(stdout), + header, + }) } } impl Drop for Subprocess { fn drop(&mut self) { self.child.lock().unwrap().wait().expect("Extension exited with error"); } } impl ExtensionPort for Subprocess { - fn send(&self, msg: &[u8]) { send_msg(&mut *self.stdin.lock().unwrap(), msg).unwrap() } + fn header(&self) -> &orchid_api::ExtensionHeader { &self.header } + fn send(&self, msg: &[u8]) { + if msg.starts_with(&[0, 0, 0, 0x1c]) { + panic!("Received unnecessary prefix"); + } + send_msg(&mut *self.stdin.lock().unwrap(), msg).unwrap() + } fn receive(&self) -> Option> { match recv_msg(&mut *self.stdout.lock().unwrap()) { Ok(msg) => Some(msg), diff --git a/orchid-host/src/tree.rs b/orchid-host/src/tree.rs index 1438c03..d0dfbad 100644 --- a/orchid-host/src/tree.rs +++ b/orchid-host/src/tree.rs @@ -1,16 +1,20 @@ +use std::fmt::Debug; use std::sync::{Mutex, OnceLock}; use itertools::Itertools; use never::Never; use orchid_base::error::OrcRes; -use orchid_base::interner::{deintern, Tok}; +use orchid_base::interner::{deintern, intern, Tok}; use orchid_base::location::Pos; +use orchid_base::macros::{mtreev_from_api, MTree}; use orchid_base::name::Sym; -use orchid_base::parse::{Comment, CompName}; -use orchid_base::tree::{ttv_from_api, TokTree, Token}; +use orchid_base::parse::{Comment, Import}; +use orchid_base::tree::{TokTree, Token}; +use ordered_float::NotNan; +use substack::{with_iter_stack, Substack}; use crate::api; -use crate::expr::RtExpr; +use crate::expr::Expr; use crate::extension::{AtomHand, System}; pub type ParsTokTree = TokTree<'static, AtomHand, Never>; @@ -25,81 +29,165 @@ pub struct Item { #[derive(Debug)] pub enum ItemKind { - Raw(Vec), Member(Member), Export(Tok), - Import(CompName), + Import(Import), + Macro(Option>, Vec) } impl Item { - pub fn from_api(tree: api::Item, sys: &System) -> Self { + pub fn from_api<'a>( + tree: api::Item, + path: Substack>, + sys: &System + ) -> Self { let kind = match tree.kind { - api::ItemKind::Raw(tokv) => ItemKind::Raw(ttv_from_api(tokv, &mut ())), - api::ItemKind::Member(m) => ItemKind::Member(Member::from_api(m, sys)), - api::ItemKind::Import(i) => ItemKind::Import(CompName::from_api(i)), + api::ItemKind::Member(m) => ItemKind::Member(Member::from_api(m, path, sys)), + api::ItemKind::Import(i) => + ItemKind::Import(Import{ path: Sym::deintern(i).iter().collect(), name: None }), api::ItemKind::Export(e) => ItemKind::Export(deintern(e)), + api::ItemKind::Macro(api::MacroBlock { priority, rules }) => ItemKind::Macro(priority, { + Vec::from_iter(rules.into_iter().map(|api| Rule { + pos: Pos::from_api(&api.location), + pattern: mtreev_from_api(&api.pattern), + kind: RuleKind::Remote(sys.clone(), api.id), + comments: api.comments.iter().map(Comment::from_api).collect_vec() + })) + }) }; - let comments = tree - .comments - .into_iter() - .map(|(text, l)| Comment { text, pos: Pos::from_api(&l) }) - .collect_vec(); + let comments = tree.comments.iter().map(Comment::from_api).collect_vec(); Self { pos: Pos::from_api(&tree.location), comments, kind } } } #[derive(Debug)] pub struct Member { - pub exported: bool, pub name: Tok, pub kind: OnceLock, pub lazy: Mutex>, } impl Member { - pub fn from_api(api::Member { exported: public, name, kind }: api::Member, sys: &System) -> Self { - let (kind, lazy) = match kind { - api::MemberKind::Const(c) => - (OnceLock::from(MemberKind::PreCnst(RtExpr::from_api(c, sys))), None), - api::MemberKind::Module(m) => - (OnceLock::from(MemberKind::Mod(Module::from_api(m, sys))), None), - api::MemberKind::Lazy(id) => (OnceLock::new(), Some(LazyMemberHandle(id, sys.clone()))), + pub fn from_api<'a>( + api: api::Member, + path: Substack>, + sys: &System, + ) -> Self { + let name = deintern(api.name); + let full_path = path.push(name.clone()); + let kind = match api.kind { + api::MemberKind::Lazy(id) => + return LazyMemberHandle(id, sys.clone(), intern(&full_path.unreverse())).to_member(name), + api::MemberKind::Const(c) => MemberKind::Const(Code::from_expr( + CodeLocator::to_const(full_path.unreverse()), + Expr::from_api(c, &mut ()) + )), + api::MemberKind::Module(m) => MemberKind::Mod(Module::from_api(m, full_path, sys)), }; - Member { exported: public, name: deintern(name), kind, lazy: Mutex::new(lazy) } + Member { name, kind: OnceLock::from(kind), lazy: Mutex::default() } } - pub fn new(public: bool, name: Tok, kind: MemberKind) -> Self { - Member { exported: public, name, kind: OnceLock::from(kind), lazy: Mutex::default() } + pub fn new(name: Tok, kind: MemberKind) -> Self { + Member { name, kind: OnceLock::from(kind), lazy: Mutex::default() } } } #[derive(Debug)] pub enum MemberKind { - Const(Vec), - PreCnst(RtExpr), + Const(Code), Mod(Module), } #[derive(Debug)] pub struct Module { pub imports: Vec, + pub exports: Vec>, pub items: Vec, } impl Module { - pub fn from_api(m: api::Module, sys: &System) -> Self { - Self { - imports: m.imports.into_iter().map(|m| Sym::from_tok(deintern(m)).unwrap()).collect_vec(), - items: m.items.into_iter().map(|i| Item::from_api(i, sys)).collect_vec(), + 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 from_api(m: api::Module, path: Substack>, sys: &System) -> Self { + let mut output = Vec::new(); + for item in m.items.into_iter() { + let next = Item::from_api(item, path.clone(), sys); + output.push(next); } + Self::new(output) } } #[derive(Debug)] -pub struct LazyMemberHandle(api::TreeId, System); +pub struct LazyMemberHandle(api::TreeId, System, Tok>>); impl LazyMemberHandle { pub fn run(self) -> OrcRes { match self.1.get_tree(self.0) { - api::MemberKind::Const(c) => Ok(MemberKind::PreCnst(RtExpr::from_api(c, &self.1))), - api::MemberKind::Module(m) => Ok(MemberKind::Mod(Module::from_api(m, &self.1))), - api::MemberKind::Lazy(id) => Self(id, self.1).run(), + api::MemberKind::Const(c) => Ok(MemberKind::Const(Code { + bytecode: Expr::from_api(c, &mut ()).into(), + locator: CodeLocator { steps: self.2, rule_loc: None }, + source: None, + })), + api::MemberKind::Module(m) => with_iter_stack(self.2.iter().cloned(), |path| { + Ok(MemberKind::Mod(Module::from_api(m, path, &self.1))) + }), + api::MemberKind::Lazy(id) => Self(id, self.1, self.2).run(), } } + pub fn to_member(self, name: Tok) -> Member { + Member { name, kind: OnceLock::new(), lazy: Mutex::new(Some(self)) } + } } + +#[derive(Debug)] +pub struct Rule { + pub pos: Pos, + pub comments: Vec, + pub pattern: Vec>, + pub kind: RuleKind, +} + +#[derive(Debug)] +pub enum RuleKind { + Remote(System, api::MacroId), + Native(Code), +} + +#[derive(Debug)] +pub struct Code { + locator: CodeLocator, + source: Option>, + bytecode: OnceLock, +} +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: OnceLock::new() } + } +} + +/// 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(path: impl IntoIterator>) -> Self { + Self { steps: intern(&path.into_iter().collect_vec()), rule_loc: None } + } + pub fn to_rule(path: impl IntoIterator>, macro_i: u16, rule_i: u16) -> Self { + Self { steps: intern(&path.into_iter().collect_vec()), rule_loc: Some((macro_i, rule_i)) } + } +} \ No newline at end of file diff --git a/orchid-std/src/number/num_atom.rs b/orchid-std/src/number/num_atom.rs index 082b314..997e0b2 100644 --- a/orchid-std/src/number/num_atom.rs +++ b/orchid-std/src/number/num_atom.rs @@ -1,10 +1,9 @@ -use never::Never; use orchid_api_derive::Coding; use orchid_base::error::OrcRes; -use orchid_extension::atom::{AtomFactory, Atomic, AtomicFeatures, ReqPck, ToAtom, TypAtom}; +use orchid_extension::atom::{AtomFactory, MethodSet, Atomic, AtomicFeatures, ToAtom, TypAtom}; use orchid_extension::atom_thin::{ThinAtom, ThinVariant}; use orchid_extension::conv::TryFromExpr; -use orchid_extension::expr::ExprHandle; +use orchid_extension::expr::Expr; use ordered_float::NotNan; #[derive(Clone, Debug, Coding)] @@ -12,13 +11,13 @@ pub struct Int(pub i64); impl Atomic for Int { type Variant = ThinVariant; type Data = Self; - type Req = Never; -} -impl ThinAtom for Int { - fn handle_req(&self, pck: impl ReqPck) { pck.never() } + fn reg_reqs() -> MethodSet { + MethodSet::new() + } } +impl ThinAtom for Int {} impl TryFromExpr for Int { - fn try_from_expr(expr: ExprHandle) -> OrcRes { + fn try_from_expr(expr: Expr) -> OrcRes { TypAtom::::try_from_expr(expr).map(|t| t.value) } } @@ -28,13 +27,11 @@ pub struct Float(pub NotNan); impl Atomic for Float { type Variant = ThinVariant; type Data = Self; - type Req = Never; -} -impl ThinAtom for Float { - fn handle_req(&self, pck: impl ReqPck) { pck.never() } + fn reg_reqs() -> MethodSet { MethodSet::new() } } +impl ThinAtom for Float {} impl TryFromExpr for Float { - fn try_from_expr(expr: ExprHandle) -> OrcRes { + fn try_from_expr(expr: Expr) -> OrcRes { TypAtom::::try_from_expr(expr).map(|t| t.value) } } @@ -44,10 +41,10 @@ pub enum Numeric { Float(NotNan), } impl TryFromExpr for Numeric { - fn try_from_expr(expr: ExprHandle) -> OrcRes { - Int::try_from_expr(expr.clone()).map(|t| Numeric::Int(t.0)).or_else(|e| { - Float::try_from_expr(expr).map(|t| Numeric::Float(t.0)).map_err(|e2| [e, e2].concat()) - }) + fn try_from_expr(expr: Expr) -> OrcRes { + Int::try_from_expr(expr.clone()) + .map(|t| Numeric::Int(t.0)) + .or_else(|e| Float::try_from_expr(expr).map(|t| Numeric::Float(t.0)).map_err(|e2| e + e2)) } } impl ToAtom for Numeric { diff --git a/orchid-std/src/number/num_lexer.rs b/orchid-std/src/number/num_lexer.rs index 8aa9586..2c0f4d4 100644 --- a/orchid-std/src/number/num_lexer.rs +++ b/orchid-std/src/number/num_lexer.rs @@ -20,7 +20,7 @@ impl Lexer for NumLexer { Ok(Numeric::Float(f)) => Float(f).factory(), Ok(Numeric::Uint(uint)) => Int(uint.try_into().unwrap()).factory(), Ok(Numeric::Decimal(dec)) => Float(NotNan::new(dec.try_into().unwrap()).unwrap()).factory(), - Err(e) => return Err(vec![num_to_err(e, ctx.pos(all))]), + Err(e) => return Err(num_to_err(e, ctx.pos(all)).into()), }; Ok((tail, GenTok::X(fac).at(ctx.pos(all)..ctx.pos(tail)))) } diff --git a/orchid-std/src/std.rs b/orchid-std/src/std.rs index 97325b2..2a6bd1c 100644 --- a/orchid-std/src/std.rs +++ b/orchid-std/src/std.rs @@ -1,11 +1,13 @@ use std::sync::Arc; +use never::Never; use orchid_base::interner::Tok; 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::{comments, fun, module, root_mod, GenMemberKind}; +use orchid_extension::tree::{comments, fun, module, root_mod, MemKind}; use crate::number::num_atom::{Float, Int}; use crate::string::str_atom::{IntStrAtom, StrAtom}; @@ -23,14 +25,17 @@ impl SystemCtor for StdSystem { } impl SystemCard for StdSystem { type Ctor = Self; - const ATOM_DEFS: &'static [Option<&'static dyn AtomDynfo>] = - &[Some(Int::INFO), Some(Float::INFO), Some(StrAtom::INFO), Some(IntStrAtom::INFO)]; + type Req = Never; + fn atoms() -> impl IntoIterator>> { + [Some(Int::dynfo()), Some(Float::dynfo()), Some(StrAtom::dynfo()), Some(IntStrAtom::dynfo())] + } } impl System for StdSystem { + fn request(_: ExtReq, req: Self::Req) -> orchid_base::reqnot::Receipt { match req {} } fn lexers() -> Vec { vec![&StringLexer] } fn parsers() -> Vec { vec![] } fn vfs() -> DeclFs { DeclFs::Mod(&[]) } - fn env() -> Vec<(Tok, GenMemberKind)> { + fn env() -> Vec<(Tok, MemKind)> { vec![root_mod("std", [], [module(true, "string", [], [comments( ["Concatenate two strings"], fun(true, "concat", |left: OrcString, right: OrcString| { diff --git a/orchid-std/src/string/str_atom.rs b/orchid-std/src/string/str_atom.rs index 1e7350c..fd3a6b6 100644 --- a/orchid-std/src/string/str_atom.rs +++ b/orchid-std/src/string/str_atom.rs @@ -1,52 +1,53 @@ use std::borrow::Cow; use std::io; -use std::num::NonZeroU64; +use std::ops::Deref; use std::sync::Arc; -use never::Never; use orchid_api_derive::Coding; use orchid_api_traits::{Encode, Request}; -use orchid_base::error::{mk_err, OrcRes}; -use orchid_base::id_store::IdStore; +use orchid_base::error::{mk_errv, OrcRes}; use orchid_base::intern; use orchid_base::interner::{deintern, intern, Tok}; -use orchid_extension::atom::{Atomic, ReqPck, TypAtom}; +use orchid_extension::atom::{AtomMethod, Atomic, MethodSet, Supports, TypAtom}; use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; use orchid_extension::conv::TryFromExpr; -use orchid_extension::expr::ExprHandle; +use orchid_extension::expr::Expr; use orchid_extension::system::SysCtx; -pub static STR_REPO: IdStore> = IdStore::new(); - #[derive(Copy, Clone, Coding)] pub struct StringGetVal; impl Request for StringGetVal { - type Response = String; + type Response = Arc; +} +impl AtomMethod for StringGetVal { + const NAME: &str = "std::string_get_val"; +} +impl Supports for StrAtom { + fn handle(&self, _: SysCtx, _: StringGetVal) -> ::Response { + self.0.clone() + } } -pub struct StrAtom(NonZeroU64); +#[derive(Clone)] +pub struct StrAtom(Arc); impl Atomic for StrAtom { type Variant = OwnedVariant; - type Data = NonZeroU64; - type Req = StringGetVal; + type Data = (); + fn reg_reqs() -> MethodSet { MethodSet::new().handle::() } } impl StrAtom { - pub fn new(str: Arc) -> Self { Self(STR_REPO.add(str).id()) } + pub fn new(str: Arc) -> Self { Self(str) } + pub fn value(&self) -> Arc { self.0.clone() } } -impl Clone for StrAtom { - fn clone(&self) -> Self { Self::new(self.local_value()) } -} -impl StrAtom { - fn try_local_value(&self) -> Option> { STR_REPO.get(self.0).map(|r| r.clone()) } - fn local_value(&self) -> Arc { self.try_local_value().expect("no string found for ID") } +impl Deref for StrAtom { + type Target = str; + fn deref(&self) -> &Self::Target { &self.0 } } impl OwnedAtom for StrAtom { type Refs = (); - fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.0) } - fn same(&self, _: SysCtx, other: &Self) -> bool { self.local_value() == other.local_value() } - fn handle_req(&self, pck: impl ReqPck) { self.local_value().encode(pck.unpack().1) } + fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } fn serialize(&self, _: SysCtx, sink: &mut (impl io::Write + ?Sized)) -> Self::Refs { - self.local_value().encode(sink) + self.deref().encode(sink) } fn deserialize(mut ctx: impl DeserializeCtx, _: Self::Refs) -> Self { Self::new(Arc::new(ctx.read::())) @@ -58,7 +59,7 @@ pub struct IntStrAtom(Tok); impl Atomic for IntStrAtom { type Variant = OwnedVariant; type Data = orchid_api::TStr; - type Req = Never; + fn reg_reqs() -> MethodSet { MethodSet::new() } } impl From> for IntStrAtom { fn from(value: Tok) -> Self { Self(value) } @@ -66,8 +67,7 @@ impl From> for IntStrAtom { impl OwnedAtom for IntStrAtom { type Refs = (); fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.0.marker()) } - fn handle_req(&self, pck: impl ReqPck) { pck.never() } - fn print(&self, _ctx: SysCtx) -> String { format!("{:?}i", self.0.as_str()) } + fn print(&self, _ctx: SysCtx) -> String { format!("{:?}i", *self.0) } fn serialize(&self, _: SysCtx, write: &mut (impl io::Write + ?Sized)) { self.0.encode(write) } fn deserialize(ctx: impl DeserializeCtx, _: ()) -> Self { Self(intern(&ctx.decode::())) } } @@ -81,22 +81,19 @@ impl<'a> OrcString<'a> { pub fn get_string(&self) -> Arc { match &self { Self::Int(tok) => deintern(tok.value).arc(), - Self::Val(atom) => match STR_REPO.get(**atom) { - Some(rec) => rec.clone(), - None => Arc::new(atom.request(StringGetVal)), - }, + Self::Val(atom) => atom.request(StringGetVal), } } } impl TryFromExpr for OrcString<'static> { - fn try_from_expr(expr: ExprHandle) -> OrcRes> { - if let Ok(v) = TypAtom::::downcast(expr.clone()) { + fn try_from_expr(expr: Expr) -> OrcRes> { + if let Ok(v) = TypAtom::::try_from_expr(expr.clone()) { return Ok(OrcString::Val(v)); } - match TypAtom::::downcast(expr) { + match TypAtom::::try_from_expr(expr) { Ok(t) => Ok(OrcString::Int(t)), - Err(e) => Err(vec![mk_err(intern!(str: "A string was expected"), "", [e.0.clone().into()])]), + Err(e) => Err(mk_errv(intern!(str: "A string was expected"), "", e.pos_iter())), } } } diff --git a/orchid-std/src/string/str_lexer.rs b/orchid-std/src/string/str_lexer.rs index 29d26ff..8be0874 100644 --- a/orchid-std/src/string/str_lexer.rs +++ b/orchid-std/src/string/str_lexer.rs @@ -1,11 +1,11 @@ use itertools::Itertools; -use orchid_base::error::{mk_err, OrcErr, OrcRes}; +use orchid_base::error::{mk_err, mk_errv, OrcErr, OrcRes}; use orchid_base::interner::intern; use orchid_base::location::Pos; use orchid_base::tree::{vname_tv, wrap_tokv}; use orchid_base::{intern, vname}; use orchid_extension::atom::AtomicFeatures; -use orchid_extension::lexer::{err_lexer_na, LexContext, Lexer}; +use orchid_extension::lexer::{err_not_applicable, LexContext, Lexer}; use orchid_extension::tree::{GenTok, GenTokTree}; use super::str_atom::IntStrAtom; @@ -96,33 +96,31 @@ pub struct StringLexer; impl Lexer for StringLexer { const CHAR_FILTER: &'static [std::ops::RangeInclusive] = &['"'..='"']; fn lex<'a>(all: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree<'a>)> { - let mut tail = all.strip_prefix('"').ok_or_else(err_lexer_na)?; - let mut parts = Vec::>::new(); + let mut tail = all.strip_prefix('"').ok_or_else(err_not_applicable)?; + let mut ret = GenTok::X(IntStrAtom::from(intern!(str: "")).factory()).at(ctx.tok_ran(0, all)); let mut cur = String::new(); let mut errors = vec![]; - let commit_str = - |str: &mut String, tail: &str, err: &mut Vec, parts: &mut Vec>| { - let str_val = parse_string(str) - .inspect_err(|e| err.push(e.clone().into_proj(ctx.pos(tail) - str.len() as u32))) - .unwrap_or_default(); - let tok = GenTok::X(IntStrAtom::from(intern(&*str_val)).factory()); - parts.push(tok.at(ctx.tok_ran(str.len() as u32, tail))); - *str = String::new(); - }; + let str_to_gen = |str: &mut String, tail: &str, err: &mut Vec| { + let str_val = parse_string(&str.split_off(0)) + .inspect_err(|e| err.push(e.clone().into_proj(ctx.pos(tail) - str.len() as u32))) + .unwrap_or_default(); + GenTok::X(IntStrAtom::from(intern(&*str_val)).factory()) + .at(ctx.tok_ran(str.len() as u32, tail)) + }; + let add_frag = |prev: GenTokTree<'a>, new: GenTokTree<'a>| { + let range = prev.range.start..new.range.end; + wrap_tokv(vname_tv(&vname!(std::string::concat), new.range.end).chain([prev, new]), range) + }; loop { if let Some(rest) = tail.strip_prefix('"') { - commit_str(&mut cur, tail, &mut errors, &mut parts); - return Ok((rest, wrap_tokv(parts, ctx.pos(all)..ctx.pos(rest)))); + return Ok((rest, add_frag(ret, str_to_gen(&mut cur, tail, &mut errors)))); } else if let Some(rest) = tail.strip_prefix('$') { - commit_str(&mut cur, tail, &mut errors, &mut parts); - parts.push(GenTok::Name(intern!(str: "++")).at(ctx.tok_ran(1, rest))); - parts.extend(vname_tv(&vname!(std::string::convert), ctx.tok_ran(1, rest))); + ret = add_frag(ret, str_to_gen(&mut cur, tail, &mut errors)); let (new_tail, tree) = ctx.recurse(rest)?; tail = new_tail; - parts.push(tree); + ret = add_frag(ret, tree); } else if tail.starts_with('\\') { - // parse_string will deal with it, we just have to make sure we skip the next - // char + // parse_string will deal with it, we just have to skip the next char tail = &tail[2..]; } else { let mut ch = tail.chars(); @@ -131,12 +129,9 @@ impl Lexer for StringLexer { tail = ch.as_str(); } else { let range = ctx.pos(all)..ctx.pos(""); - commit_str(&mut cur, tail, &mut errors, &mut parts); - return Err(vec![mk_err( - intern!(str: "No string end"), - "String never terminated with \"", - [Pos::Range(range.clone()).into()], - )]); + return Err(mk_errv(intern!(str: "No string end"), "String never terminated with \"", [ + Pos::Range(range.clone()).into(), + ])); } } } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1493bcb..568b558 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -27,7 +27,7 @@ fn main() -> io::Result { let args = Args::parse(); match args.command { Commands::CheckApiRefs => walk_wsp(&mut |_| Ok(true), &mut |file| { - if file.path().extension() == Some(OsStr::new("rs")) || file.file_name() == "lib.rs" { + if file.path().extension() == Some(OsStr::new("rs")) && file.file_name() != "lib.rs" { let mut contents = String::new(); File::open(file.path())?.read_to_string(&mut contents)?; for (l, line) in contents.lines().enumerate() {