From 534f08b45c1b5832c94fb5b4dd9cb60131e7100c Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Tue, 27 Jan 2026 20:53:45 +0100 Subject: [PATCH] Significantly extended stdlib --- Cargo.lock | 8 + examples/hello-world/main.orc | 4 +- orchid-extension/src/conv.rs | 8 +- orchid-extension/src/coroutine_exec.rs | 6 +- orchid-extension/src/entrypoint.rs | 5 +- orchid-extension/src/func_atom.rs | 24 ++- orchid-extension/src/gen_expr.rs | 3 +- orchid-extension/src/system.rs | 5 +- orchid-extension/src/tree.rs | 4 +- orchid-host/Cargo.toml | 2 + orchid-std/Cargo.toml | 1 + orchid-std/src/macros/instantiate_tpl.rs | 6 +- orchid-std/src/macros/let_line.rs | 4 +- orchid-std/src/macros/macro_lib.rs | 11 +- orchid-std/src/macros/macro_line.rs | 6 +- orchid-std/src/macros/mactree_lexer.rs | 6 +- orchid-std/src/macros/match_macros.rs | 36 ++-- orchid-std/src/macros/ph_lexer.rs | 3 +- orchid-std/src/macros/resolve.rs | 6 +- orchid-std/src/macros/stdlib/option.rs | 10 +- orchid-std/src/macros/stdlib/record.rs | 8 +- orchid-std/src/macros/stdlib/tuple.rs | 28 +-- orchid-std/src/macros/utils.rs | 77 ++++---- orchid-std/src/std/binary/binary_atom.rs | 25 +++ orchid-std/src/std/binary/binary_lib.rs | 146 +++++++++++++++ orchid-std/src/std/binary/mod.rs | 2 + orchid-std/src/std/boolean.rs | 47 +++++ orchid-std/src/std/mod.rs | 2 + orchid-std/src/std/number/num_atom.rs | 49 +++-- orchid-std/src/std/number/num_lexer.rs | 12 +- orchid-std/src/std/ops/subscript_lexer.rs | 4 +- orchid-std/src/std/option.rs | 6 +- orchid-std/src/std/protocol/proto_parser.rs | 4 +- orchid-std/src/std/protocol/type_parser.rs | 4 +- orchid-std/src/std/protocol/types.rs | 10 +- orchid-std/src/std/record/record_lib.rs | 8 +- orchid-std/src/std/reflection/sym_atom.rs | 9 +- orchid-std/src/std/std_system.rs | 24 ++- orchid-std/src/std/string/str_lexer.rs | 7 +- orchid-std/src/std/string/str_lib.rs | 190 ++++++++++++++++---- orchid-std/src/std/tuple.rs | 24 +-- orcx/src/main.rs | 2 +- 42 files changed, 635 insertions(+), 211 deletions(-) create mode 100644 orchid-std/src/std/binary/binary_atom.rs create mode 100644 orchid-std/src/std/binary/binary_lib.rs create mode 100644 orchid-std/src/std/binary/mod.rs create mode 100644 orchid-std/src/std/boolean.rs diff --git a/Cargo.lock b/Cargo.lock index 7d35fff..70025d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -948,6 +948,7 @@ dependencies = [ "orchid-api", "orchid-api-traits", "orchid-base", + "orchid-extension", "ordered-float", "pastey", "substack", @@ -981,6 +982,7 @@ dependencies = [ "substack", "test_executors", "tokio", + "unicode-segmentation", ] [[package]] @@ -1708,6 +1710,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-xid" version = "0.1.0" diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index afc4bc4..90c5fc1 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -1,2 +1,4 @@ let user = r[ "foo" 1, "bar" t[3, 4] ] -let main = user.bar.1 +let _main = user.bar.1 + +let main = "foo" + string::slice "hello" 1 3 + "bar" diff --git a/orchid-extension/src/conv.rs b/orchid-extension/src/conv.rs index 649f613..32879d9 100644 --- a/orchid-extension/src/conv.rs +++ b/orchid-extension/src/conv.rs @@ -9,9 +9,9 @@ use orchid_base::interner::is; use orchid_base::location::Pos; use trait_set::trait_set; -use crate::atom::{AtomicFeatures, ForeignAtom, TAtom, ToAtom}; +use crate::atom::{AtomicFeatures, ForeignAtom, TAtom}; use crate::expr::{Expr, ExprKind}; -use crate::gen_expr::{GExpr, atom, bot}; +use crate::gen_expr::{GExpr, bot}; pub trait TryFromExpr: Sized { fn try_from_expr(expr: Expr) -> impl Future>; @@ -111,10 +111,6 @@ impl ToExpr for OrcRes { } } -impl ToExpr for A { - async fn to_gen(self) -> GExpr { atom(self) } -} - impl ToExpr for Never { async fn to_gen(self) -> GExpr { match self {} } } diff --git a/orchid-extension/src/coroutine_exec.rs b/orchid-extension/src/coroutine_exec.rs index 92eb12f..d85cd0b 100644 --- a/orchid-extension/src/coroutine_exec.rs +++ b/orchid-extension/src/coroutine_exec.rs @@ -13,7 +13,7 @@ use crate::atom::Atomic; use crate::atom_owned::{OwnedAtom, OwnedVariant}; use crate::conv::{ToExpr, TryFromExpr}; use crate::expr::Expr; -use crate::gen_expr::{GExpr, arg, call, lambda, seq}; +use crate::gen_expr::{GExpr, arg, call, lambda, new_atom, seq}; enum Command { Execute(GExpr, Sender), @@ -34,11 +34,11 @@ impl BuilderCoroutine { None => panic!("Before the stream ends, we should have gotten a Halt"), Some(Command::Halt(expr)) => expr, Some(Command::Execute(expr, reply)) => call( - lambda(0, [seq([arg(0)], call(Replier { reply, builder: self }.to_gen().await, [arg(0)]))]), + lambda(0, [seq([arg(0)], call(new_atom(Replier { reply, builder: self }), [arg(0)]))]), [expr], ), Some(Command::Register(expr, reply)) => - call(Replier { reply, builder: self }.to_gen().await, [expr]), + call(new_atom(Replier { reply, builder: self }), [expr]), } } } diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index beec6c6..85fc316 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -261,7 +261,8 @@ impl ExtensionBuilder { let text = es(text).await; let src = Sym::from_api(src).await; let expr_store = BorrowedExprStore::new(); - let trigger_char = text.chars().nth(pos as usize).unwrap(); + let tail = &text[pos as usize..]; + let trigger_char = tail.chars().next().unwrap(); let ekey_na = ekey_not_applicable().await; let ekey_cascade = ekey_cascade().await; let lexers = cted().inst().dyn_lexers(); @@ -269,7 +270,7 @@ impl ExtensionBuilder { lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char)) { let ctx = LexContext::new(&expr_store, &text, id, pos, src.clone()); - match try_with_reporter(lx.lex(&text[pos as usize..], &ctx)).await { + match try_with_reporter(lx.lex(tail, &ctx)).await { Err(e) if e.any(|e| *e == ekey_na) => continue, Err(e) => { let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api())); diff --git a/orchid-extension/src/func_atom.rs b/orchid-extension/src/func_atom.rs index e504cda..9484382 100644 --- a/orchid-extension/src/func_atom.rs +++ b/orchid-extension/src/func_atom.rs @@ -6,7 +6,7 @@ use std::future::Future; use std::pin::Pin; use std::rc::Rc; -use futures::future::LocalBoxFuture; +use futures::future::{LocalBoxFuture, join_all}; use futures::{AsyncWrite, FutureExt}; use itertools::Itertools; use never::Never; @@ -14,6 +14,7 @@ use orchid_api_traits::Encode; use orchid_base::clone; use orchid_base::error::OrcRes; use orchid_base::format::{FmtCtx, FmtUnit}; +use orchid_base::location::Pos; use orchid_base::name::Sym; use task_local::task_local; use trait_set::trait_set; @@ -24,7 +25,7 @@ use crate::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; use crate::conv::ToExpr; use crate::coroutine_exec::{ExecHandle, exec}; use crate::expr::Expr; -use crate::gen_expr::GExpr; +use crate::gen_expr::{GExpr, new_atom}; use crate::system::sys_id; trait_set! { @@ -44,6 +45,16 @@ pub fn get_arg(idx: usize) -> Expr { .expect("get_arg called outside ExprFunc") } +pub fn get_argc() -> usize { + ARGV.try_with(|argv| argv.len()).expect("get_arg called outside ExprFunc") +} + +pub async fn get_arg_posv(idxes: impl IntoIterator) -> impl Iterator { + let args = (ARGV.try_with(|argv| idxes.into_iter().map(|i| &argv[i]).cloned().collect_vec())) + .expect("get_arg_posv called outside ExprFunc"); + join_all(args.iter().map(|expr| expr.pos())).await.into_iter() +} + pub trait ExprFunc: Clone + 'static { fn argtyps() -> &'static [TypeId]; fn apply<'a>(&self, hand: ExecHandle<'a>, v: Vec) -> impl Future>; @@ -123,7 +134,7 @@ impl OwnedAtom for Fun { if new_args.len() == self.record.argtyps.len() { (self.record.fun)(new_args).await.to_gen().await } else { - Self { args: new_args, record: self.record.clone(), path: self.path.clone() }.to_gen().await + new_atom(Self { args: new_args, record: self.record.clone(), path: self.path.clone() }) } } async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await } @@ -169,7 +180,7 @@ impl OwnedAtom for Lambda { if new_args.len() == self.record.argtyps.len() { (self.record.fun)(new_args).await.to_gen().await } else { - Self { args: new_args, record: self.record.clone() }.to_gen().await + new_atom(Self { args: new_args, record: self.record.clone() }) } } async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await } @@ -181,7 +192,7 @@ mod expr_func_derives { use orchid_base::error::OrcRes; - use super::ExprFunc; + use super::{ARGV, ExprFunc}; use crate::conv::{ToExpr, TryFromExpr}; use crate::func_atom::{ExecHandle, Expr}; use crate::gen_expr::GExpr; @@ -200,8 +211,9 @@ mod expr_func_derives { } async fn apply<'a>(&self, _: ExecHandle<'a>, v: Vec) -> OrcRes { assert_eq!(v.len(), Self::argtyps().len(), "Arity mismatch"); + let argv = v.clone(); let [$([< $t:lower >],)*] = v.try_into().unwrap_or_else(|_| panic!("Checked above")); - Ok(self($($t::try_from_expr([< $t:lower >]).await?,)*).await.to_gen().await) + Ok(ARGV.scope(argv, self($($t::try_from_expr([< $t:lower >]).await?,)*)).await.to_gen().await) } } } diff --git a/orchid-extension/src/gen_expr.rs b/orchid-extension/src/gen_expr.rs index afed46d..345a682 100644 --- a/orchid-extension/src/gen_expr.rs +++ b/orchid-extension/src/gen_expr.rs @@ -106,7 +106,8 @@ impl Format for GExprKind { fn inherit(kind: GExprKind) -> GExpr { GExpr { pos: Pos::Inherit, kind } } pub fn sym_ref(path: Sym) -> GExpr { inherit(GExprKind::Const(path)) } -pub fn atom(atom: A) -> GExpr { inherit(GExprKind::NewAtom(atom.to_atom_factory())) } +/// Creates an expression from a new atom that we own. +pub fn new_atom(atom: A) -> GExpr { inherit(GExprKind::NewAtom(atom.to_atom_factory())) } pub fn seq(deps: impl IntoIterator, val: GExpr) -> GExpr { fn recur(mut ops: impl Iterator) -> Option { diff --git a/orchid-extension/src/system.rs b/orchid-extension/src/system.rs index 1ae6e73..94f05a6 100644 --- a/orchid-extension/src/system.rs +++ b/orchid-extension/src/system.rs @@ -155,7 +155,10 @@ where A: AtomicFeatures { Ok(TAtom { value: *value, untyped: foreign }) } -pub async fn dep_req>(req: Req) -> Req::Response { +/// Make a global request to a system that supports this request type. The +/// target system must either be the system in which this function is called, or +/// one of its direct dependencies. +pub async fn sys_req>(req: Req) -> Req::Response { let mut msg = Vec::new(); req.into().encode_vec(&mut msg); let cted = cted(); diff --git a/orchid-extension/src/tree.rs b/orchid-extension/src/tree.rs index 5f33605..48eeafe 100644 --- a/orchid-extension/src/tree.rs +++ b/orchid-extension/src/tree.rs @@ -20,7 +20,7 @@ use crate::api; use crate::conv::ToExpr; use crate::expr::{BorrowedExprStore, Expr, ExprHandle}; use crate::func_atom::{ExprFunc, Fun}; -use crate::gen_expr::{GExpr, sym_ref}; +use crate::gen_expr::{GExpr, new_atom, sym_ref}; pub type GenTokTree = TokTree; pub type GenTok = Token; @@ -75,7 +75,7 @@ pub fn root_mod(name: &str, mems: impl IntoIterator>) -> ( } pub fn fun(public: bool, name: &str, xf: impl ExprFunc) -> Vec { let fac = - LazyMemberFactory::new(async move |sym| MemKind::Const(Fun::new(sym, xf).await.to_gen().await)); + LazyMemberFactory::new(async move |sym| MemKind::Const(new_atom(Fun::new(sym, xf).await))); vec![GenMember { name: name.to_string(), kind: MemKind::Lazy(fac), public, comments: vec![] }] } pub fn prefix(path: &str, items: impl IntoIterator>) -> Vec { diff --git a/orchid-host/Cargo.toml b/orchid-host/Cargo.toml index 57e990c..e6a72a7 100644 --- a/orchid-host/Cargo.toml +++ b/orchid-host/Cargo.toml @@ -22,6 +22,7 @@ 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" } +orchid-extension = { version = "0.1.0", path = "../orchid-extension", optional = true } ordered-float = "5.1.0" pastey = "0.2.1" substack = "1.1.1" @@ -33,3 +34,4 @@ unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" } [features] tokio = ["dep:tokio", "dep:tokio-util", "dep:libloading"] +orchid-extension = ["dep:orchid-extension"] diff --git a/orchid-std/Cargo.toml b/orchid-std/Cargo.toml index fe47b1f..2be48d3 100644 --- a/orchid-std/Cargo.toml +++ b/orchid-std/Cargo.toml @@ -33,6 +33,7 @@ rust_decimal = "1.39.0" subslice-offset = "0.1.1" substack = "1.1.1" tokio = { version = "1.49.0", features = ["full"] } +unicode-segmentation = "1.12.0" [dev-dependencies] test_executors = "0.4.1" diff --git a/orchid-std/src/macros/instantiate_tpl.rs b/orchid-std/src/macros/instantiate_tpl.rs index 5554c27..0acf61a 100644 --- a/orchid-std/src/macros/instantiate_tpl.rs +++ b/orchid-std/src/macros/instantiate_tpl.rs @@ -7,7 +7,7 @@ use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::exec; use orchid_extension::expr::Expr; -use orchid_extension::gen_expr::GExpr; +use orchid_extension::gen_expr::{GExpr, new_atom}; use crate::macros::mactree::{MacTok, MacTree}; @@ -41,7 +41,7 @@ impl OwnedAtom for InstantiateTplCall { Ok(t) => self.argv.push(own(&t).await), }; if self.argv.len() < self.argc { - return self.to_gen().await; + return new_atom(self); } let mut args = self.argv.into_iter(); let ret = self.tpl.map(&mut false, &mut |mt| match mt.tok() { @@ -49,7 +49,7 @@ impl OwnedAtom for InstantiateTplCall { _ => None, }); assert!(args.next().is_none(), "Too many arguments for all slots"); - ret.to_gen().await + new_atom(ret) }) .await .to_gen() diff --git a/orchid-std/src/macros/let_line.rs b/orchid-std/src/macros/let_line.rs index 1894aa9..3bb896f 100644 --- a/orchid-std/src/macros/let_line.rs +++ b/orchid-std/src/macros/let_line.rs @@ -11,7 +11,7 @@ use orchid_base::sym; use orchid_base::tree::Paren; use orchid_extension::atom::TAtom; use orchid_extension::conv::TryFromExpr; -use orchid_extension::gen_expr::{atom, call, sym_ref}; +use orchid_extension::gen_expr::{call, new_atom, sym_ref}; use orchid_extension::parser::{ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser}; use crate::macros::mactree::{MacTok, MacTree, MacTreeSeq}; @@ -40,7 +40,7 @@ impl Parser for LetLine { Ok(vec![ParsedLine::cnst(&line.sr(), &comments, exported, name, async move |ctx| { let macro_input = MacTok::S(Paren::Round, with_reporter(dealias_mac_v(&aliased, &ctx)).await?).at(sr.pos()); - Ok(call(sym_ref(sym!(macros::resolve)), [atom(macro_input)])) + Ok(call(sym_ref(sym!(macros::resolve)), [new_atom(macro_input)])) })]) } } diff --git a/orchid-std/src/macros/macro_lib.rs b/orchid-std/src/macros/macro_lib.rs index a215941..adfa2d3 100644 --- a/orchid-std/src/macros/macro_lib.rs +++ b/orchid-std/src/macros/macro_lib.rs @@ -1,9 +1,8 @@ use orchid_base::sym; use orchid_extension::atom::TAtom; use orchid_extension::atom_owned::own; -use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::exec; -use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::gen_expr::{call, new_atom, sym_ref}; use orchid_extension::tree::{GenMember, fun, prefix}; use crate::macros::mactree::MacTree; @@ -53,14 +52,14 @@ pub async fn gen_macro_lib() -> Vec { exec(async |mut h| { let recur = resolve(mactree!(macros::common::comma_list "push" tail ;)).await; let mut tail = h.exec::>>(recur).await?; - tail.0.insert(0, h.exec(head).await?); + tail.0.insert(0, h.exec(new_atom(head)).await?); Ok(tail) }) .await }], ) .rule(mactreev!(macros::common::comma_list ( "...$" final_tail 0 )), [async |[tail]| { - HomoTpl(vec![tail.to_gen().await]) + HomoTpl(vec![new_atom(tail)]) }]) .rule(mactreev!(macros::common::comma_list()), [async |[]| UntypedTuple(Vec::new())]) .finish(), @@ -71,14 +70,14 @@ pub async fn gen_macro_lib() -> Vec { exec(async |mut h| { let recur = resolve(mactree!(macros::common::semi_list "push" tail ;)).await; let mut tail = h.exec::>>(recur).await?; - tail.0.insert(0, h.exec(head).await?); + tail.0.insert(0, h.exec(new_atom(head)).await?); Ok(tail) }) .await }], ) .rule(mactreev!(macros::common::semi_list ( "...$" final_tail 0 )), [async |[tail]| { - HomoTpl(vec![tail.to_gen().await]) + HomoTpl(vec![new_atom(tail)]) }]) .rule(mactreev!(macros::common::semi_list()), [async |[]| UntypedTuple(Vec::new())]) .finish(), diff --git a/orchid-std/src/macros/macro_line.rs b/orchid-std/src/macros/macro_line.rs index bda4eff..b299756 100644 --- a/orchid-std/src/macros/macro_line.rs +++ b/orchid-std/src/macros/macro_line.rs @@ -13,7 +13,7 @@ use orchid_base::tree::{Paren, Token}; use orchid_base::{clone, sym}; use orchid_extension::atom::TAtom; use orchid_extension::conv::{ToExpr, TryFromExpr}; -use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::gen_expr::{call, new_atom, sym_ref}; use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; use crate::macros::let_line::{dealias_mac_v, parse_tokv}; @@ -133,7 +133,7 @@ impl Parser for MacroLine { let macro_input = MacTok::S(Paren::Round, with_reporter(dealias_mac_v(&body_mactree, &ctx)).await?) .at(body_sr.pos()); - Ok(call(sym_ref(sym!(macros::resolve)), [macro_input.to_gen().await])) + Ok(call(sym_ref(sym!(macros::resolve)), [new_atom(macro_input)])) })) } let mac_cell = Rc::new(OnceCell::new()); @@ -165,7 +165,7 @@ impl Parser for MacroLine { rules, }))) }; - mac_cell.get_or_init(mac_future).await.clone().to_gen().await + mac_cell.get_or_init(mac_future).await.clone().map(new_atom).to_gen().await })) } Ok(lines) diff --git a/orchid-std/src/macros/mactree_lexer.rs b/orchid-std/src/macros/mactree_lexer.rs index 06fb000..66092d1 100644 --- a/orchid-std/src/macros/mactree_lexer.rs +++ b/orchid-std/src/macros/mactree_lexer.rs @@ -6,6 +6,7 @@ use orchid_base::error::{OrcRes, mk_errv}; use orchid_base::interner::is; use orchid_base::tokens::PARENS; use orchid_base::tree::Paren; +use orchid_extension::gen_expr::new_atom; use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; use orchid_extension::parser::p_tree2gen; use orchid_extension::tree::{GenTok, GenTokTree, x_tok}; @@ -28,11 +29,12 @@ impl Lexer for MacTreeLexer { Ok((tail4, mactree)) => { let range = lctx.pos_tt(tail, tail4); let tok = match &args[..] { - [] => x_tok(mactree).await, + [] => x_tok(new_atom(mactree)).await, _ => { let instantiate_tpl_call = InstantiateTplCall { argc: args.len(), argv: vec![], tpl: mactree }; - let call = chain!([x_tok(instantiate_tpl_call).await.at(range.clone())], args); + let call = + chain!([x_tok(new_atom(instantiate_tpl_call)).await.at(range.clone())], args); GenTok::S(Paren::Round, call.collect()) }, }; diff --git a/orchid-std/src/macros/match_macros.rs b/orchid-std/src/macros/match_macros.rs index ca9fa00..172ffef 100644 --- a/orchid-std/src/macros/match_macros.rs +++ b/orchid-std/src/macros/match_macros.rs @@ -15,7 +15,7 @@ use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::{ExecHandle, exec}; use orchid_extension::expr::{Expr, ExprHandle}; -use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, sym_ref}; +use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, new_atom, sym_ref}; use orchid_extension::tree::{GenMember, fun, prefix}; use crate::macros::resolve::resolve; @@ -82,25 +82,27 @@ pub async fn gen_match_macro_lib() -> Vec { .await }, ), - fun(true, "matcher", async |names: HomoTpl>, matcher: Expr| MatcherAtom { - keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0).await)).await, - matcher, + fun(true, "matcher", async |names: HomoTpl>, matcher: Expr| { + new_atom(MatcherAtom { + keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0).await)).await, + matcher, + }) }), build_macro(None, ["match", "match_rule", "_row", "=>"]) .rule(mactreev!("pattern::match" "...$" value 0 { "..$" rules 0 }), [ async |[value, rules]| { exec(async move |mut h| { let rule_lines = h - .exec::>>(call(sym_ref(sym!(macros::resolve)), [ - mactree!(macros::common::semi_list "push" rules.clone();).to_gen().await, - ])) + .exec::>>(call(sym_ref(sym!(macros::resolve)), [new_atom( + mactree!(macros::common::semi_list "push" rules.clone();), + )])) .await?; let mut rule_atoms = Vec::<(TAtom, Expr)>::new(); for line_mac in rule_lines.0.iter() { let Tpl((matcher, body)) = h - .exec(call(sym_ref(sym!(macros::resolve)), [ - mactree!(pattern::_row "push" own(line_mac).await ;).to_gen().await, - ])) + .exec(call(sym_ref(sym!(macros::resolve)), [new_atom( + mactree!(pattern::_row "push" own(line_mac).await ;), + )])) .await?; rule_atoms.push((matcher, body)); } @@ -128,18 +130,18 @@ pub async fn gen_match_macro_lib() -> Vec { resolve(mactree!(pattern::match_rule "push" pattern; )).await }]) .rule(mactreev!(pattern::match_rule ( macros::common::_ )), [async |[]| { - Ok(MatcherAtom { + Ok(new_atom(MatcherAtom { keys: Vec::new(), matcher: lambda(0, [OrcOpt(Some(Tpl(()))).to_gen().await]).create().await, - }) + })) }]) .rule(mactreev!(pattern::_row ( "...$" pattern 0 pattern::=> "...$" value 1 )), [ async |[pattern, mut value]| { exec(async move |mut h| -> OrcRes, GExpr)>> { let Ok(pat) = h - .exec::>(call(sym_ref(sym!(macros::resolve)), [ - mactree!(pattern::match_rule "push" pattern.clone();).to_gen().await, - ])) + .exec::>(call(sym_ref(sym!(macros::resolve)), [new_atom( + mactree!(pattern::match_rule "push" pattern.clone();), + )])) .await else { return Err(mk_errv( @@ -171,10 +173,10 @@ pub async fn gen_match_macro_lib() -> Vec { [name.pos()], )); }; - Ok(MatcherAtom { + Ok(new_atom(MatcherAtom { keys: vec![name.clone()], matcher: sym_ref(sym!(pattern::ref_body)).to_expr().await, - }) + })) }]) .finish(), ]) diff --git a/orchid-std/src/macros/ph_lexer.rs b/orchid-std/src/macros/ph_lexer.rs index d48b49c..350718c 100644 --- a/orchid-std/src/macros/ph_lexer.rs +++ b/orchid-std/src/macros/ph_lexer.rs @@ -5,6 +5,7 @@ use orchid_base::interner::{es, is}; use orchid_base::parse::{name_char, name_start}; use orchid_extension::atom::Atomic; use orchid_extension::atom_thin::{ThinAtom, ThinVariant}; +use orchid_extension::gen_expr::new_atom; use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; use orchid_extension::tree::{GenTokTree, x_tok}; @@ -72,6 +73,6 @@ impl Lexer for PhLexer { } }; let ph_atom = PhAtom(is(name).await.to_api(), phkind); - Ok((tail, x_tok(ph_atom).await.at(ctx.pos_tt(line, tail)))) + Ok((tail, x_tok(new_atom(ph_atom)).await.at(ctx.pos_tt(line, tail)))) } } diff --git a/orchid-std/src/macros/resolve.rs b/orchid-std/src/macros/resolve.rs index 07a5d84..35f36d9 100644 --- a/orchid-std/src/macros/resolve.rs +++ b/orchid-std/src/macros/resolve.rs @@ -15,7 +15,7 @@ use orchid_extension::atom::TAtom; use orchid_extension::atom_owned::own; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::{ExecHandle, exec}; -use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, sym_ref}; +use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, new_atom, sym_ref}; use orchid_extension::reflection::{ReflMemKind, refl}; use subslice_offset::SubsliceOffset; use substack::Substack; @@ -283,9 +283,9 @@ async fn mk_body_call(mac: &Macro, rule: &Rule, state: &MatchState<'_>, pos: Pos let mut call_args = vec![]; for name in rule.ph_names.iter() { call_args.push(match state.get(name).expect("Missing state entry for placeholder") { - StateEntry::Scalar(scal) => (**scal).clone().to_gen().await, + StateEntry::Scalar(scal) => new_atom((**scal).clone()), StateEntry::Vec(vec) => - MacTok::S(Paren::Round, MacTreeSeq::new(vec.iter().cloned())).at(Pos::None).to_gen().await, + new_atom(MacTok::S(Paren::Round, MacTreeSeq::new(vec.iter().cloned())).at(Pos::None)), }); } call(sym_ref(mac.0.module.suffix([rule.body.clone()]).await), call_args).at(pos.clone()) diff --git a/orchid-std/src/macros/stdlib/option.rs b/orchid-std/src/macros/stdlib/option.rs index b95274f..677a399 100644 --- a/orchid-std/src/macros/stdlib/option.rs +++ b/orchid-std/src/macros/stdlib/option.rs @@ -4,7 +4,7 @@ use orchid_extension::atom::TAtom; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::exec; use orchid_extension::expr::Expr; -use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::gen_expr::{call, new_atom, sym_ref}; use orchid_extension::tree::{GenMember, fun, prefix}; use crate::macros::match_macros::MatcherAtom; @@ -34,21 +34,21 @@ pub async fn gen_option_macro_lib() -> Vec { let sub = h .exec::>(resolve(mactree!(pattern::match_rule "push" sub;)).await) .await?; - Ok(MatcherAtom { + Ok(new_atom(MatcherAtom { keys: sub.keys().collect().await, matcher: h .register(call(sym_ref(sym!(std::option::is_some_body)), [sub.to_gen().await])) .await, - }) + })) }) }, ]) .rule(mactreev!(pattern::match_rule(std::option::none)), [|[]: [_; _]| { exec(async |mut h| { - Ok(MatcherAtom { + Ok(new_atom(MatcherAtom { keys: vec![], matcher: h.register(sym_ref(sym!(std::option::is_none_body))).await, - }) + })) }) }]) .finish(), diff --git a/orchid-std/src/macros/stdlib/record.rs b/orchid-std/src/macros/stdlib/record.rs index 2400dd0..6e4ce71 100644 --- a/orchid-std/src/macros/stdlib/record.rs +++ b/orchid-std/src/macros/stdlib/record.rs @@ -4,7 +4,7 @@ use orchid_extension::atom_owned::own; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::exec; use orchid_extension::expr::Expr; -use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::gen_expr::{call, new_atom, sym_ref}; use orchid_extension::tree::{GenMember, prefix}; use crate::macros::resolve::resolve; @@ -17,9 +17,9 @@ pub async fn gen_record_macro_lib() -> Vec { .rule(mactreev!(std::record::r[ "...$" elements 0 ]), [async |[elements]: [_; _]| { exec(async move |mut h| { let tup = h - .exec::>>(call(sym_ref(sym!(macros::resolve)), [ - mactree!((macros::common::comma_list "push" elements ;)).to_gen().await, - ])) + .exec::>>(call(sym_ref(sym!(macros::resolve)), [new_atom( + mactree!((macros::common::comma_list "push" elements ;)), + )])) .await?; let mut record = sym_ref(sym!(std::record::empty)); for item_exprh in tup.0 { diff --git a/orchid-std/src/macros/stdlib/tuple.rs b/orchid-std/src/macros/stdlib/tuple.rs index a994c27..c8e5c7b 100644 --- a/orchid-std/src/macros/stdlib/tuple.rs +++ b/orchid-std/src/macros/stdlib/tuple.rs @@ -6,7 +6,7 @@ use orchid_extension::atom_owned::own; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::exec; use orchid_extension::expr::Expr; -use orchid_extension::gen_expr::{GExpr, call, sym_ref}; +use orchid_extension::gen_expr::{GExpr, call, new_atom, sym_ref}; use orchid_extension::tree::{GenMember, fun, prefix}; use crate::macros::match_macros::MatcherAtom; @@ -20,7 +20,7 @@ pub async fn gen_tuple_macro_lib() -> Vec { exec(async move |mut h| { let tup = h .exec::>>(call(sym_ref(sym!(macros::resolve)), [ - mactree!((macros::common::comma_list "push" elements ;)).to_gen().await, + new_atom(mactree!((macros::common::comma_list "push" elements ;))), ])) .await?; let val = stream::iter(&tup.0[..]) @@ -56,32 +56,32 @@ pub async fn gen_tuple_macro_lib() -> Vec { } fn parse_tpl(elements: MacTree, tail_matcher: Option) -> impl Future { - exec(async move |mut h| -> OrcRes { + exec(async move |mut h| -> OrcRes { let tup = h - .exec::>>(call(sym_ref(sym!(macros::resolve)), [ - mactree!((macros::common::comma_list "push" elements ;)).to_gen().await, - ])) + .exec::>>(call(sym_ref(sym!(macros::resolve)), [new_atom( + mactree!((macros::common::comma_list "push" elements ;)), + )])) .await?; let mut subs = Vec::with_capacity(tup.0.len()); for mac_a in &tup.0[..] { let mac = own(mac_a).await; let sub = h - .exec::>(call(sym_ref(sym!(macros::resolve)), [ - mactree!(pattern::match_rule ("push" mac ;)).to_gen().await, - ])) + .exec::>(call(sym_ref(sym!(macros::resolve)), [new_atom( + mactree!(pattern::match_rule ("push" mac ;)), + )])) .await?; subs.push(sub); } let tail_matcher = match tail_matcher { Some(mac) => Some( - h.exec::>(call(sym_ref(sym!(macros::resolve)), [ - mactree!(pattern::match_rule "push" mac ;).to_gen().await, - ])) + h.exec::>(call(sym_ref(sym!(macros::resolve)), [new_atom( + mactree!(pattern::match_rule "push" mac ;), + )])) .await?, ), None => None, }; - Ok(MatcherAtom { + Ok(new_atom(MatcherAtom { keys: stream::iter(&subs[..]) .flat_map(|t| t.keys()) .chain(stream::iter(&tail_matcher).flat_map(|mat| mat.keys())) @@ -93,7 +93,7 @@ fn parse_tpl(elements: MacTree, tail_matcher: Option) -> impl Future cnst(true, name, MacroBodyArgCollector { - argc: N, - args: Vec::new(), - cb: Rc::new(move |argv| { - let arr = argv.into_iter().collect_array::().expect("argc should enforce the length"); - let body = body.clone(); - Box::pin(async move { body(arr).await.to_gen().await }) + 1.. => cnst( + true, + name, + new_atom(MacroBodyArgCollector { + argc: N, + args: Vec::new(), + cb: Rc::new(move |argv| { + let arr = + argv.into_iter().collect_array::().expect("argc should enforce the length"); + let body = body.clone(); + Box::pin(async move { body(arr).await.to_gen().await }) + }), }), - }), + ), }); self.patterns.push(pat); self @@ -105,35 +110,31 @@ impl MacroBuilder { let main_const = lazy(true, &format!("__macro__{name}"), async move |path| { let module = (Sym::new(path.split_last_seg().1.iter().cloned()).await) .expect("Default macro in global root"); - MemKind::Const( - Macro(Rc::new(MacroData { - canonical_name: module.suffix([is(name).await]).await, - module, - prio, - rules: stream(async |mut h| { - for (counter, pattern) in patterns.into_iter().enumerate() { - let mut placeholders = Vec::new(); - pattern.map(&mut false, &mut |tt| { - if let MacTok::Ph(ph) = &*tt.tok { - placeholders.push(ph.name.clone()) - } - None - }); - h.emit(Rule { - matcher: Matcher::new(pattern.clone()).await.unwrap(), - pattern, - ph_names: placeholders, - body: is(&format!("({name})::{counter}")).await, - }) - .await; - } - }) - .collect() - .await, - })) - .to_gen() + MemKind::Const(new_atom(Macro(Rc::new(MacroData { + canonical_name: module.suffix([is(name).await]).await, + module, + prio, + rules: stream(async |mut h| { + for (counter, pattern) in patterns.into_iter().enumerate() { + let mut placeholders = Vec::new(); + pattern.map(&mut false, &mut |tt| { + if let MacTok::Ph(ph) = &*tt.tok { + placeholders.push(ph.name.clone()) + } + None + }); + h.emit(Rule { + matcher: Matcher::new(pattern.clone()).await.unwrap(), + pattern, + ph_names: placeholders, + body: is(&format!("({name})::{counter}")).await, + }) + .await; + } + }) + .collect() .await, - ) + })))) }); let kw_consts = own_kws[1..].iter().flat_map(|kw| { lazy(true, &format!("__macro__{kw}"), async move |path| { diff --git a/orchid-std/src/std/binary/binary_atom.rs b/orchid-std/src/std/binary/binary_atom.rs new file mode 100644 index 0000000..d97d677 --- /dev/null +++ b/orchid-std/src/std/binary/binary_atom.rs @@ -0,0 +1,25 @@ +use std::borrow::Cow; +use std::pin::Pin; +use std::rc::Rc; + +use futures::AsyncWrite; +use orchid_api_traits::Encode; +use orchid_extension::atom::Atomic; +use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; + +#[derive(Clone)] +pub struct BlobAtom(pub(crate) Rc>); +impl Atomic for BlobAtom { + type Variant = OwnedVariant; + type Data = (); +} +impl OwnedAtom for BlobAtom { + type Refs = (); + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } + async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs { + self.0.encode(write).await.unwrap() + } + async fn deserialize(mut dctx: impl DeserializeCtx, _: Self::Refs) -> Self { + Self(dctx.read::>>().await) + } +} diff --git a/orchid-std/src/std/binary/binary_lib.rs b/orchid-std/src/std/binary/binary_lib.rs new file mode 100644 index 0000000..d38a03b --- /dev/null +++ b/orchid-std/src/std/binary/binary_lib.rs @@ -0,0 +1,146 @@ +use std::rc::Rc; + +use orchid_base::error::{OrcErrv, mk_errv}; +use orchid_base::interner::is; +use orchid_extension::atom::TAtom; +use orchid_extension::atom_owned::own; +use orchid_extension::func_atom::get_arg_posv; +use orchid_extension::gen_expr::new_atom; +use orchid_extension::tree::{GenMember, comments, fun, prefix}; + +use crate::std::binary::binary_atom::BlobAtom; +use crate::std::boolean::Bool; +use crate::{Int, OrcOpt, Tpl}; + +async fn bounds_error( + expected: String, + blob: &BlobAtom, + args: impl IntoIterator, +) -> OrcErrv { + mk_errv( + is("Index out of bounds").await, + format!("Selected {expected} from blob of len {}", blob.0.len()), + get_arg_posv(args).await, + ) +} + +pub fn gen_binary_lib() -> Vec { + prefix("std", [comments( + ["A Blob is a sequence of bytes stored and processed efficiently."], + prefix("binary", [ + comments( + ["Appends a binary blob to another", "|type: Blob -> Blob -> Blob|"], + fun(true, "concat", async |a: TAtom, b: TAtom| { + new_atom(BlobAtom(Rc::new( + own(&a).await.0.iter().chain(&own(&b).await.0[..]).copied().collect(), + ))) + }), + ), + comments( + [ + "Copies out a subsection of the binary into a new blob \ + specified by starting point and length", + "|type: Blob -> Int -> Int -> Blob|", + ], + fun(true, "slice", async |a: TAtom, Int(start): Int, Int(len): Int| { + let blob = own(&a).await; + if start + len > blob.0.len() as i64 { + return Err(bounds_error(format!("{start}+{len}"), &blob, 0..3).await); + } + let sub = blob.0[start as usize..(start + len) as usize].to_vec(); + Ok(new_atom(BlobAtom(Rc::new(sub)))) + }), + ), + comments( + [ + "Return the index where the second binary appears as a subsection of the first", + "|type: Blob -> Blob -> std::option Int|", + ], + fun(true, "find", async |haystack: TAtom, needle: TAtom| { + let haystack_vec = own(&haystack).await; + let needle_vec = own(&needle).await; + for i in 0..haystack_vec.0.len() - needle_vec.0.len() { + if haystack_vec.0[i..].starts_with(&needle_vec.0) { + return OrcOpt(Some(Int(i as i64))); + } + } + OrcOpt(None) + }), + ), + comments( + [ + "Splits the binary into two halves at the given byte index", + "|type: Blob -> Int -> std::tuple Blob Blob|", + ], + fun(true, "split", async |a: TAtom, i: Int| { + let v = own(&a).await; + if v.0.len() < i.0 as usize { + return Err(bounds_error(i.0.to_string(), &v, 1..2).await); + } + let (l, r) = v.0.split_at(i.0 as usize); + Ok(Tpl(( + new_atom(BlobAtom(Rc::new(l.to_vec()))), + new_atom(BlobAtom(Rc::new(r.to_vec()))), + ))) + }), + ), + comments( + [ + "Takes a binary, a starting point, a length no greater than 8, and a boolean flag \ + which is true if the number is little endian. Reads a usize from \ + the specified location in the binary.", + "|type: Blob -> Int -> Int -> Bool -> Int|", + ], + fun( + true, + "get_int", + async |bin: TAtom, Int(start): Int, Int(len): Int, Bool(le): Bool| { + let vec = own(&bin).await; + if start + len > vec.0.len() as i64 { + return Err(bounds_error(format!("{start}+{len}"), &vec, 1..3).await); + } + if 8 < len { + return Err(mk_errv( + is("Too many bytes for int conversion").await, + format!("At most 8 bytes fit into an Int, requested {len}"), + get_arg_posv(3..4).await, + )); + } + let slice = &vec.0[start as usize..(start + len) as usize]; + let mut data = [0u8; 8]; + Ok(Int(if le { + data[..len as usize].copy_from_slice(slice); + i64::from_le_bytes(data) + } else { + data[(8 - len as usize)..].copy_from_slice(slice); + i64::from_be_bytes(data) + })) + }, + ), + ), + comments( + [ + "Takes a length no greater than int_bytes, a little endian flag and a number to encode. \ + Turns the least significant bytes of the given int into a binary.", + "|type: Int -> Bool -> Int -> Blob|", + ], + fun(true, "from_num", async |len: Int, le: Bool, val: Int| { + if 8 < len.0 { + return Err(mk_errv( + is("Too many bytes for int conversion").await, + format!("Ints are 8 bytes, attempted to write {} byte buffer", len.0), + get_arg_posv(0..1).await, + )); + } + let data = if le.0 { val.0.to_le_bytes() } else { val.0.to_be_bytes() }; + let data = if le.0 { &data[..len.0 as usize] } else { &data[(8 - len.0 as usize)..] }; + Ok(new_atom(BlobAtom(Rc::new(data.to_vec())))) + }), + ), + comments( + ["Returns the number of bytes in a binary", "|type: Blob -> Int|"], + fun(true, "size", async |blob: TAtom| Int(own(&blob).await.0.len() as i64)), + ), + ]), + )]) +} diff --git a/orchid-std/src/std/binary/mod.rs b/orchid-std/src/std/binary/mod.rs new file mode 100644 index 0000000..2983006 --- /dev/null +++ b/orchid-std/src/std/binary/mod.rs @@ -0,0 +1,2 @@ +pub mod binary_atom; +pub mod binary_lib; diff --git a/orchid-std/src/std/boolean.rs b/orchid-std/src/std/boolean.rs new file mode 100644 index 0000000..8e1899a --- /dev/null +++ b/orchid-std/src/std/boolean.rs @@ -0,0 +1,47 @@ +use orchid_api_derive::Coding; +use orchid_base::error::OrcRes; +use orchid_base::format::FmtUnit; +use orchid_base::sym; +use orchid_extension::atom::{Atomic, TAtom}; +use orchid_extension::atom_thin::{ThinAtom, ThinVariant}; +use orchid_extension::conv::{ToExpr, TryFromExpr}; +use orchid_extension::expr::Expr; +use orchid_extension::gen_expr::{GExpr, sym_ref}; +use orchid_extension::tree::{GenMember, cnst, comments, fun, prefix}; + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding)] +pub struct Bool(pub bool); +impl Atomic for Bool { + type Variant = ThinVariant; + type Data = Self; +} +impl ThinAtom for Bool { + async fn print(&self) -> FmtUnit { self.0.to_string().into() } +} +impl TryFromExpr for Bool { + async fn try_from_expr(expr: Expr) -> OrcRes { + match TAtom::::downcast(expr.handle()).await { + Err(e) => Err(e.mk_err().await), + Ok(atom) => Ok(atom.value), + } + } +} +impl ToExpr for Bool { + async fn to_gen(self) -> GExpr { + sym_ref(if self.0 { sym!(std::true) } else { sym!(std::false) }) + } +} + +pub fn gen_bool_lib() -> Vec { + prefix("std", [ + comments( + [ + "Returns the second argument if the bool is true, the third argument otherwise", + "|type: Bool -> T -> T -> T|", + ], + fun(true, "ifthenelse", async |Bool(b): Bool, t: Expr, f: Expr| if b { t } else { f }), + ), + cnst(true, "true", Bool(true)), + cnst(true, "false", Bool(false)), + ]) +} diff --git a/orchid-std/src/std/mod.rs b/orchid-std/src/std/mod.rs index 2134f37..2e1f16a 100644 --- a/orchid-std/src/std/mod.rs +++ b/orchid-std/src/std/mod.rs @@ -1,3 +1,5 @@ +pub mod binary; +pub mod boolean; pub mod number; pub mod ops; pub mod option; diff --git a/orchid-std/src/std/number/num_atom.rs b/orchid-std/src/std/number/num_atom.rs index 9804e74..dabf810 100644 --- a/orchid-std/src/std/number/num_atom.rs +++ b/orchid-std/src/std/number/num_atom.rs @@ -1,24 +1,32 @@ -use orchid_api_derive::Coding; +use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use orchid_base::error::OrcRes; use orchid_base::format::FmtUnit; use orchid_base::name::Sym; use orchid_base::number::Numeric; use orchid_base::sym; -use orchid_extension::atom::{ - AtomFactory, Atomic, AtomicFeatures, MethodSetBuilder, Supports, TAtom, ToAtom, -}; +use orchid_extension::atom::{Atomic, MethodSetBuilder, Supports, TAtom}; use orchid_extension::atom_thin::{ThinAtom, ThinVariant}; -use orchid_extension::conv::TryFromExpr; +use orchid_extension::conv::{ToExpr, TryFromExpr}; use orchid_extension::expr::Expr; use orchid_extension::gen_expr::sym_ref; +use orchid_extension::system::sys_req; use ordered_float::NotNan; use rust_decimal::prelude::Zero; use crate::std::protocol::types::{GetImplMethod, GetTagIdMethod}; +use crate::std::std_system::StdReq; use crate::std::string::to_string::ToStringMethod; +use crate::{StdSystem, api}; -#[derive(Clone, Debug, Coding)] +#[derive(Debug, Clone, Copy, Coding, Hash, PartialEq, Eq, Hierarchy)] +#[extends(StdReq)] +pub struct CreateInt(pub Int); +impl Request for CreateInt { + type Response = api::ExprTicket; +} + +#[derive(Clone, Copy, Debug, Coding, Hash, PartialEq, Eq)] pub struct Int(pub i64); impl Atomic for Int { type Variant = ThinVariant; @@ -38,6 +46,11 @@ impl TryFromExpr for Int { TAtom::::try_from_expr(expr).await.map(|t| t.value) } } +impl ToExpr for Int { + async fn to_gen(self) -> orchid_extension::gen_expr::GExpr { + Expr::deserialize(sys_req::(CreateInt(self)).await).await.to_gen().await + } +} impl Supports for Int { async fn handle(&self, _: GetTagIdMethod) -> ::Response { sym!(std::number::Int).to_api() @@ -68,7 +81,14 @@ impl Supports for Int { } } -#[derive(Clone, Debug, Coding)] +#[derive(Clone, Copy, Debug, Coding, Hash, PartialEq, Eq, Hierarchy)] +#[extends(StdReq)] +pub struct CreateFloat(pub Float); +impl Request for CreateFloat { + type Response = api::ExprTicket; +} + +#[derive(Clone, Copy, Debug, Coding, Hash, PartialEq, Eq)] pub struct Float(pub NotNan); impl Atomic for Float { type Variant = ThinVariant; @@ -88,6 +108,11 @@ impl TryFromExpr for Float { Ok(Self(Num::try_from_expr(expr).await?.0.to_f64())) } } +impl ToExpr for Float { + async fn to_gen(self) -> orchid_extension::gen_expr::GExpr { + Expr::deserialize(sys_req::(CreateFloat(self)).await).await.to_gen().await + } +} impl Supports for Float { async fn handle(&self, _: GetTagIdMethod) -> ::Response { sym!(std::number::Float).to_api() @@ -131,12 +156,14 @@ impl TryFromExpr for Num { } } } -impl ToAtom for Num { - fn to_atom_factory(self) -> AtomFactory { +impl ToExpr for Num { + async fn to_gen(self) -> orchid_extension::gen_expr::GExpr { match self.0 { - Numeric::Float(f) => Float(f).factory(), - Numeric::Int(i) => Int(i).factory(), + Numeric::Float(f) => Float(f).to_expr().await, + Numeric::Int(i) => Int(i).to_expr().await, } + .to_gen() + .await } } diff --git a/orchid-std/src/std/number/num_lexer.rs b/orchid-std/src/std/number/num_lexer.rs index f41ffa4..2740a44 100644 --- a/orchid-std/src/std/number/num_lexer.rs +++ b/orchid-std/src/std/number/num_lexer.rs @@ -2,7 +2,7 @@ use std::ops::RangeInclusive; use orchid_base::error::OrcRes; use orchid_base::number::{num_to_errv, parse_num}; -use orchid_extension::atom::ToAtom; +use orchid_extension::conv::ToExpr; use orchid_extension::lexer::{LexContext, Lexer}; use orchid_extension::tree::{GenTokTree, x_tok}; @@ -15,10 +15,10 @@ impl Lexer for NumLexer { async fn lex<'a>(all: &'a str, lxcx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> { let ends_at = all.find(|c: char| !c.is_ascii_hexdigit() && !"xX._pP".contains(c)); let (chars, tail) = all.split_at(ends_at.unwrap_or(all.len())); - let fac = match parse_num(chars) { - Ok(numeric) => Num(numeric).to_atom_factory(), - Err(e) => return Err(num_to_errv(e, lxcx.pos(all), lxcx.src()).await), - }; - Ok((tail, x_tok(fac).await.at(lxcx.pos_lt(chars.len(), tail)))) + match parse_num(chars) { + Ok(numeric) => + Ok((tail, x_tok(Num(numeric).to_gen().await).await.at(lxcx.pos_lt(chars.len(), tail)))), + Err(e) => Err(num_to_errv(e, lxcx.pos(all), lxcx.src()).await), + } } } diff --git a/orchid-std/src/std/ops/subscript_lexer.rs b/orchid-std/src/std/ops/subscript_lexer.rs index f1dca23..4335fca 100644 --- a/orchid-std/src/std/ops/subscript_lexer.rs +++ b/orchid-std/src/std/ops/subscript_lexer.rs @@ -1,7 +1,7 @@ use orchid_base::error::OrcRes; use orchid_base::interner::is; use orchid_base::parse::{name_char, name_start}; -use orchid_extension::conv::ToExpr; +use orchid_extension::gen_expr::new_atom; use orchid_extension::lexer::{LexContext, LexedData, Lexer, err_not_applicable}; use orchid_extension::tree::GenTok; @@ -22,7 +22,7 @@ impl Lexer for SubscriptLexer { let new_tail = &tail[name_len + 1..]; Ok((new_tail, [ GenTok::Name(is(".").await).at(lctx.pos_lt(1, &tail[1..])), - GenTok::NewExpr(IntStrAtom(is(&tail[1..name_len + 1]).await).to_gen().await) + GenTok::NewExpr(new_atom(IntStrAtom(is(&tail[1..name_len + 1]).await))) .at(lctx.pos_tt(&tail[1..], new_tail)), ])) } diff --git a/orchid-std/src/std/option.rs b/orchid-std/src/std/option.rs index 8893fb4..d0bd2a0 100644 --- a/orchid-std/src/std/option.rs +++ b/orchid-std/src/std/option.rs @@ -10,7 +10,7 @@ use orchid_extension::atom::{Atomic, ForeignAtom, TAtom}; use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; use orchid_extension::conv::{ToExpr, TryFromExpr}; use orchid_extension::expr::{Expr, ExprHandle}; -use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::gen_expr::{call, new_atom, sym_ref}; use orchid_extension::tree::{GenMember, cnst, fun, prefix}; use crate::{OrcString, api}; @@ -59,8 +59,8 @@ impl ToExpr for OrcOpt { pub fn gen_option_lib() -> Vec { prefix("std::option", [ - cnst(true, "none", OptAtom(None)), - fun(true, "some", async |ex: Expr| OptAtom(Some(ex))), + cnst(true, "none", new_atom(OptAtom(None))), + fun(true, "some", async |ex: Expr| new_atom(OptAtom(Some(ex)))), fun(true, "expect", async |opt: ForeignAtom, msg: OrcString| { match OrcOpt::try_from_expr(opt.clone().ex()).await? { OrcOpt(Some(ex)) => Ok::(ex), diff --git a/orchid-std/src/std/protocol/proto_parser.rs b/orchid-std/src/std/protocol/proto_parser.rs index 0340f6d..08abd1e 100644 --- a/orchid-std/src/std/protocol/proto_parser.rs +++ b/orchid-std/src/std/protocol/proto_parser.rs @@ -7,7 +7,7 @@ use orchid_base::parse::{Comment, Parsed, expect_end, try_pop_no_fluff}; use orchid_base::sym; use orchid_base::tree::Token; use orchid_extension::coroutine_exec::exec; -use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::gen_expr::{call, new_atom, sym_ref}; use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; use crate::std::protocol::parse_impls::parse_impls; @@ -44,7 +44,7 @@ impl Parser for AsProtoParser { for (k, v) in impls { new_impls.insert(k.clone(), h.register(sym_ref(id.suffix([v]).await)).await); } - Tag { id, impls: Rc::new(new_impls) } + new_atom(Tag { id, impls: Rc::new(new_impls) }) }) .await })); diff --git a/orchid-std/src/std/protocol/type_parser.rs b/orchid-std/src/std/protocol/type_parser.rs index 7a96de4..be0774d 100644 --- a/orchid-std/src/std/protocol/type_parser.rs +++ b/orchid-std/src/std/protocol/type_parser.rs @@ -7,7 +7,7 @@ use orchid_base::parse::{Comment, Parsed, expect_end, try_pop_no_fluff}; use orchid_base::sym; use orchid_base::tree::Token; use orchid_extension::coroutine_exec::exec; -use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::gen_expr::{call, new_atom, sym_ref}; use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; use crate::std::protocol::parse_impls::parse_impls; @@ -44,7 +44,7 @@ impl Parser for AsTypeParser { for (k, v) in impls { new_impls.insert(k.clone(), h.register(sym_ref(id.suffix([v]).await)).await); } - Tag { id, impls: Rc::new(new_impls) } + new_atom(Tag { id, impls: Rc::new(new_impls) }) }) .await })); diff --git a/orchid-std/src/std/protocol/types.rs b/orchid-std/src/std/protocol/types.rs index 28466ec..a81a768 100644 --- a/orchid-std/src/std/protocol/types.rs +++ b/orchid-std/src/std/protocol/types.rs @@ -18,8 +18,8 @@ use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; use orchid_extension::conv::{ClonableToExprDyn, ToExpr}; use orchid_extension::coroutine_exec::exec; use orchid_extension::expr::{Expr, ExprHandle}; -use orchid_extension::gen_expr::{GExpr, call, sym_ref}; -use orchid_extension::system::dep_req; +use orchid_extension::gen_expr::{GExpr, call, new_atom, sym_ref}; +use orchid_extension::system::sys_req; use orchid_extension::tree::{GenMember, MemKind, cnst, fun, lazy, prefix}; use crate::std::std_system::StdReq; @@ -135,7 +135,9 @@ pub fn gen_protocol_lib() -> Vec { fun(false, "resolve", async |tag: ForeignAtom, value: ForeignAtom| { Ok(call(get_impl(value.clone(), tag).await?.to_gen().await, [value.to_gen().await])) }), - fun(false, "wrap", async |tag: TAtom, value: Expr| Tagged { tag: own(&tag).await, value }), + fun(false, "wrap", async |tag: TAtom, value: Expr| { + new_atom(Tagged { tag: own(&tag).await, value }) + }), fun(false, "unwrap", async |tag: TAtom, value: TAtom| { let own_tag = own(&tag).await; let own_val = own(&value).await; @@ -178,7 +180,7 @@ impl<'a> TagBuilder<'a> { self } pub async fn finish(self) -> TAtom { - let tk = dep_req::(CreateTag { + let tk = sys_req::(CreateTag { name: Sym::parse(&self.name).await.unwrap().to_api(), impls: join_all(self.impls.into_iter().map(|(s, fut)| async move { ( diff --git a/orchid-std/src/std/record/record_lib.rs b/orchid-std/src/std/record/record_lib.rs index a7ef8a3..72a34e8 100644 --- a/orchid-std/src/std/record/record_lib.rs +++ b/orchid-std/src/std/record/record_lib.rs @@ -7,7 +7,7 @@ use orchid_base::interner::is; use orchid_extension::atom::TAtom; use orchid_extension::atom_owned::own; use orchid_extension::expr::Expr; -use orchid_extension::gen_expr::arg; +use orchid_extension::gen_expr::{arg, new_atom}; use orchid_extension::tree::{GenMember, cnst, fun, prefix}; use crate::std::record::record_atom::RecordAtom; @@ -15,11 +15,11 @@ use crate::std::string::str_atom::IntStrAtom; pub fn gen_record_lib() -> Vec { prefix("std::record", [ - cnst(true, "empty", RecordAtom(Rc::new(HashMap::new()))), + cnst(true, "empty", new_atom(RecordAtom(Rc::new(HashMap::new())))), fun(true, "set", async |map: TAtom, key: IntStrAtom, val: Expr| { let mut map = own(&map).await.0.as_ref().clone(); map.insert(key.0.clone(), val); - RecordAtom(Rc::new(map)) + new_atom(RecordAtom(Rc::new(map))) }), fun(true, "get", async |map: TAtom, key: IntStrAtom| { let record = own(&map).await; @@ -35,7 +35,7 @@ pub fn gen_record_lib() -> Vec { fun(true, "delete", async |map: TAtom, key: IntStrAtom| { let mut map = own(&map).await.0.as_ref().clone(); map.remove(&key.0); - RecordAtom(Rc::new(map)) + new_atom(RecordAtom(Rc::new(map))) }), ]) } diff --git a/orchid-std/src/std/reflection/sym_atom.rs b/orchid-std/src/std/reflection/sym_atom.rs index ee31e00..7351fa8 100644 --- a/orchid-std/src/std/reflection/sym_atom.rs +++ b/orchid-std/src/std/reflection/sym_atom.rs @@ -8,7 +8,8 @@ use orchid_base::name::{NameLike, Sym}; use orchid_extension::atom::{Atomic, Supports, TAtom}; use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; use orchid_extension::expr::{Expr, ExprHandle}; -use orchid_extension::system::dep_req; +use orchid_extension::gen_expr::new_atom; +use orchid_extension::system::sys_req; use orchid_extension::tree::{GenMember, fun, prefix}; use crate::std::std_system::StdReq; @@ -43,7 +44,7 @@ impl Request for CreateSymAtom { pub async fn sym_expr(sym: Sym) -> Expr { Expr::from_handle(ExprHandle::deserialize( - dep_req::(CreateSymAtom(sym.to_api())).await, + sys_req::(CreateSymAtom(sym.to_api())).await, )) } @@ -51,7 +52,7 @@ pub async fn gen_sym_lib() -> Vec { prefix("std::refl::sym", [ fun(true, "from_str", async move |str: TAtom| { match Sym::parse(&es(*str).await).await { - Ok(sym) => Ok(SymAtom(sym)), + Ok(sym) => Ok(new_atom(SymAtom(sym))), Err(_) => Err(mk_errv( is("Cannot parse sym from empty string").await, "Empty string passed to std::refl::sym::from_str", @@ -60,7 +61,7 @@ pub async fn gen_sym_lib() -> Vec { } }), fun(true, "to_tpl", async move |sym: TAtom| { - HomoTpl(own(&sym).await.0.segs().map(IntStrAtom).collect()) + HomoTpl(own(&sym).await.0.segs().map(|seg| new_atom(IntStrAtom(seg))).collect()) }), ]) } diff --git a/orchid-std/src/std/std_system.rs b/orchid-std/src/std/std_system.rs index f4fe1de..8958261 100644 --- a/orchid-std/src/std/std_system.rs +++ b/orchid-std/src/std/std_system.rs @@ -9,6 +9,7 @@ use orchid_base::sym; use orchid_extension::atom::{AtomDynfo, AtomicFeatures}; use orchid_extension::conv::ToExpr; use orchid_extension::expr::Expr; +use orchid_extension::gen_expr::new_atom; use orchid_extension::lexer::LexerObj; use orchid_extension::parser::ParserObj; use orchid_extension::system::{System, SystemCard}; @@ -18,6 +19,10 @@ use orchid_extension::tree::{GenMember, merge_trivial}; use super::number::num_lib::gen_num_lib; use super::string::str_atom::{IntStrAtom, StrAtom}; use super::string::str_lib::gen_str_lib; +use crate::std::binary::binary_atom::BlobAtom; +use crate::std::binary::binary_lib::gen_binary_lib; +use crate::std::boolean::gen_bool_lib; +use crate::std::number::num_atom::{CreateFloat, CreateInt}; use crate::std::number::num_lexer::NumLexer; use crate::std::ops::gen_ops_lib; use crate::std::ops::subscript_lexer::SubscriptLexer; @@ -36,6 +41,8 @@ use crate::{Float, Int}; #[extendable] #[allow(clippy::enum_variant_names, reason = "For the time being there are only ctor calls")] pub enum StdReq { + CreateInt(CreateInt), + CreateFloat(CreateFloat), CreateTag(CreateTag), CreateTuple(CreateTuple), CreateRecord(CreateRecord), @@ -56,6 +63,7 @@ impl SystemCard for StdSystem { type Req = StdReq; fn atoms() -> impl IntoIterator>> { [ + Some(BlobAtom::dynfo()), Some(Int::dynfo()), Some(Float::dynfo()), Some(StrAtom::dynfo()), @@ -72,9 +80,13 @@ impl SystemCard for StdSystem { impl System for StdSystem { async fn request<'a>(xreq: Box + 'a>, req: Self::Req) -> Receipt<'a> { match req { + StdReq::CreateInt(ref req @ CreateInt(int)) => + xreq.reply(req, &new_atom(int).to_expr().await.serialize().await).await.unwrap(), + StdReq::CreateFloat(ref req @ CreateFloat(float)) => + xreq.reply(req, &new_atom(float).to_expr().await.serialize().await).await.unwrap(), StdReq::CreateTuple(ref req @ CreateTuple(ref items)) => { let tpl = Tuple(Rc::new(join_all(items.iter().copied().map(Expr::deserialize)).await)); - let tk = tpl.to_expr().await.serialize().await; + let tk = new_atom(tpl).to_expr().await.serialize().await; xreq.reply(req, &tk).await.unwrap() }, StdReq::CreateRecord(ref req @ CreateRecord(ref items)) => { @@ -82,12 +94,12 @@ impl System for StdSystem { join_all(items.iter().map(async |(k, v)| (es(*k).await, Expr::deserialize(*v).await))) .await; let rec = RecordAtom(Rc::new(values.into_iter().collect())); - let tk = rec.to_expr().await.serialize().await; + let tk = new_atom(rec).to_expr().await.serialize().await; xreq.reply(req, &tk).await.unwrap() }, StdReq::CreateSymAtom(ref req @ CreateSymAtom(sym_tok)) => { let sym_atom = SymAtom(Sym::from_api(sym_tok).await); - xreq.reply(req, &sym_atom.to_expr().await.serialize().await).await.unwrap() + xreq.reply(req, &new_atom(sym_atom).to_expr().await.serialize().await).await.unwrap() }, StdReq::CreateTag(ref req @ CreateTag { name, ref impls }) => { let tag_atom = Tag { @@ -102,7 +114,7 @@ impl System for StdSystem { .collect(), ), }; - xreq.reply(req, &tag_atom.to_expr().await.serialize().await).await.unwrap() + xreq.reply(req, &new_atom(tag_atom).to_expr().await.serialize().await).await.unwrap() }, } } @@ -110,6 +122,7 @@ impl System for StdSystem { fn parsers() -> Vec { vec![&AsTypeParser, &TypeParser, &AsProtoParser, &ProtoParser] } async fn env() -> Vec { merge_trivial([ + gen_bool_lib(), gen_num_lib(), gen_str_lib(), gen_option_lib(), @@ -118,9 +131,10 @@ impl System for StdSystem { gen_protocol_lib(), gen_sym_lib().await, gen_ops_lib(), + gen_binary_lib(), ]) } async fn prelude() -> Vec { - vec![sym!(std), sym!(std::tuple), sym!(std::option), sym!(std::record)] + vec![sym!(std), sym!(std::tuple), sym!(std::option), sym!(std::record), sym!(std::string)] } } diff --git a/orchid-std/src/std/string/str_lexer.rs b/orchid-std/src/std/string/str_lexer.rs index b229a0c..2ce2569 100644 --- a/orchid-std/src/std/string/str_lexer.rs +++ b/orchid-std/src/std/string/str_lexer.rs @@ -5,7 +5,7 @@ use orchid_base::location::SrcRange; use orchid_base::name::Sym; use orchid_base::sym; use orchid_base::tree::{Paren, wrap_tokv}; -use orchid_extension::gen_expr::sym_ref; +use orchid_extension::gen_expr::{new_atom, sym_ref}; use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; use orchid_extension::parser::p_tree2gen; use orchid_extension::tree::{GenTok, GenTokTree, ref_tok, x_tok}; @@ -99,6 +99,7 @@ impl Lexer for StringLexer { const CHAR_FILTER: &'static [std::ops::RangeInclusive] = &['"'..='"', '`'..='`']; async fn lex<'a>(all: &'a str, lctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> { let Some(mut tail) = all.strip_prefix('"') else { + eprintln!("Unrecognized start char {:?}", all.chars().next().unwrap()); return Err(err_not_applicable().await); }; let mut ret = None; @@ -115,8 +116,8 @@ impl Lexer for StringLexer { err.extend(e.clone().into_proj(ctx.src(), ctx.pos(tail) - str.len() as u32).await); } let str_val = str_val_res.unwrap_or_default(); - x_tok(IntStrAtom::from(is(&str_val).await)).await.at(ctx.pos_lt(str.len() as u32, tail)) - as GenTokTree + let atom = new_atom(IntStrAtom::from(is(&str_val).await)); + x_tok(atom).await.at(ctx.pos_lt(str.len() as u32, tail)) as GenTokTree } let add_frag = |prev: Option, new: GenTokTree| async { let Some(prev) = prev else { return new }; diff --git a/orchid-std/src/std/string/str_lib.rs b/orchid-std/src/std/string/str_lib.rs index f1f5b58..dc3f7dd 100644 --- a/orchid-std/src/std/string/str_lib.rs +++ b/orchid-std/src/std/string/str_lib.rs @@ -1,50 +1,174 @@ use std::rc::Rc; +use orchid_base::error::mk_errv; use orchid_base::format::fmt; +use orchid_base::interner::is; use orchid_base::sym; use orchid_extension::atom::ForeignAtom; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::exec; use orchid_extension::expr::Expr; -use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::func_atom::get_arg; +use orchid_extension::gen_expr::{call, new_atom, sym_ref}; use orchid_extension::tree::{GenMember, comments, fun, prefix}; +use unicode_segmentation::UnicodeSegmentation; use super::str_atom::StrAtom; -use crate::OrcString; use crate::std::protocol::types::{get_impl, proto}; use crate::std::string::to_string::ToStringMethod; +use crate::{Int, OrcOpt, OrcString, Tpl}; pub fn gen_str_lib() -> Vec { - prefix("std::string", [ - comments( - ["Concatenate two strings"], - fun(true, "concat", async |left: OrcString, right: OrcString| { - StrAtom::new(Rc::new(left.get_string().await.to_string() + &right.get_string().await)) - }), - ), - comments( - ["Converts a value to string. This function is used in interpolation. \ - It supports the std::string::to_string protocol in Orchid, \ - the std::string::to_string request in Rust, \ - and expression debug printing as a fallback (print_atom for Atomic implementors in Rust).\n\n\ - This function is infallible."], - fun(true, "to_str", async |input: Expr| { - exec(async move |mut h| { - if let Ok(atom) = h.exec::(input.clone()).await { - if let Some(str) = atom.request(ToStringMethod).await { - return StrAtom::new(Rc::new(str)).to_gen().await; - } - let proto_ref = sym_ref(sym!(std::string::to_string::__protocol_tag__)); - let proto = h.exec(proto_ref).await.expect("This protocol is defined in this system"); - if let Ok(cb) = get_impl(atom.clone(), proto).await { - return call(cb.to_gen().await, [atom.to_gen().await]).to_gen().await; - } + prefix("std", [comments( + ["There are two string types, IntStr and Str. Literals are always IntStr, which are quick to \ + equality-compare but may leak, so you can't generally create them at runtime.\n\n\ + All functions here operate on Unicode graphemes. This essentially means that letters with \ + added diacritics and Mandarin multi-codepoint characters are treated as a single character."], + prefix("string", [ + comments( + ["Concatenate two strings", "|type: Str -> Str -> Str|"], + fun(true, "concat", async |left: OrcString, right: OrcString| { + new_atom(StrAtom::new(Rc::new( + left.get_string().await.to_string() + &right.get_string().await, + ))) + }), + ), + comments( + [ + "Find the size of a string in bytes. Strings are stored in UTF-8. \ + This should be used to determine the computational resource utilization of strings. \ + It should not be used to determine whether to truncate text.", + "|type: Str -> Int|", + ], + fun(true, "size", async |s: OrcString| Int(s.get_string().await.len().try_into().unwrap())), + ), + comments( + [ + "Find the number of characters in a string. This can be used for example to \ + truncate text. It should not be used to limit the size of messages for security purposes.", + "|type: Str -> Int|", + ], + fun(true, "len", async |s: OrcString| { + Int(s.get_string().await.graphemes(true).count().try_into().unwrap()) + }), + ), + comments( + [ + "Takes a string, a start and a length in graphemes. \ + Slices out the specified subsection of the string.", + "|type: Str -> Int -> Int -> Str|", + ], + fun(true, "slice", async |s: OrcString, Int(start): Int, Int(len): Int| { + let str = s.get_string().await; + if len <= 0 { + return Ok(new_atom(StrAtom::new(Rc::default()))); } - return StrAtom::new(Rc::new(fmt(&input).await)).to_gen().await; - }) - .await - }), - ), - proto(true, "to_string").finish(), - ]) + let mut substr_iter = str.graphemes(true).skip(start.try_into().unwrap()); + let new_str: String = + substr_iter.by_ref().take(usize::try_from(len).unwrap() - 1).collect(); + let Some(s) = substr_iter.next() else { + let str_len = str.graphemes(true).count(); + return Err(mk_errv( + is("Index out of bounds").await, + format!("Tried to select grapheme {start}+{len} from string that only has {str_len}"), + [get_arg(0).pos().await, get_arg(1).pos().await, get_arg(2).pos().await], + )); + }; + Ok(new_atom(StrAtom::new(Rc::new(new_str + s)))) + }), + ), + comments( + [ + "If the first string contains the second then returns the index.", + "|type: Str -> Str -> std::option Int|", + ], + fun(true, "find", async |haystack: OrcString, needle: OrcString| { + let haystack_str = haystack.get_string().await; + let needle_str = needle.get_string().await; + let mut haystack_graphs = haystack_str.graphemes(true); + let mut index = 0; + loop { + let mut needle_graphs = needle_str.graphemes(true); + // check that all chars are equal + if haystack_graphs.clone().zip(needle_graphs.by_ref()).all(|(l, r)| l == r) { + // if we exhausted the haystack but not the needle, we can't succeed + if needle_graphs.next().is_some() { + break; + } + return OrcOpt(Some(Int(index))); + } + if haystack_graphs.next().is_none() { + break; + } + index += 1; + } + OrcOpt(None) + }), + ), + comments( + [ + "Splits the string into two substrings at the nth grapheme.", + "|type: Str -> Int -> std::tuple Str Str|", + ], + fun(true, "split", async |s: OrcString, i: Int| { + let str = s.get_string().await; + let Some((i, _)) = str.grapheme_indices(true).nth(i.0.try_into().unwrap()) else { + let len = str.graphemes(true).count(); + return Err(mk_errv( + is("Index out of bounds").await, + format!("Tried to split string at {}, it only has {} graphemes", i.0, len), + [get_arg(0).pos().await, get_arg(1).pos().await], + )); + }; + let (left, right) = str.split_at(i); + Ok(Tpl(( + new_atom(StrAtom::new(Rc::new(left.to_string()))), + new_atom(StrAtom::new(Rc::new(right.to_string()))), + ))) + }), + ), + comments( + ["Returns the nth grapheme.", "|type: Str -> Int -> Str|"], + fun(true, "char_at", async |s: OrcString, i: Int| { + let str = s.get_string().await; + let Some(s) = str.graphemes(true).nth(i.0.try_into().unwrap()) else { + let len = str.graphemes(true).count(); + return Err(mk_errv( + is("Index out of bounds").await, + format!("Tried to read grapheme {} from string, it only has {}", i.0, len), + [get_arg(0).pos().await, get_arg(1).pos().await], + )); + }; + Ok(new_atom(StrAtom::new(Rc::new(s.to_string())))) + }), + ), + comments( + [ + "Converts a value to string. This function is used in interpolation. \ + It supports the std::string::to_string protocol in Orchid, \ + the std::string::to_string request in Rust, \ + and expression debug printing as a fallback (print_atom for Atomic implementors in Rust).\n\n\ + This function is infallible.", + "|type: any -> Str|", + ], + fun(true, "to_str", async |input: Expr| { + exec(async move |mut h| { + if let Ok(atom) = h.exec::(input.clone()).await { + if let Some(str) = atom.request(ToStringMethod).await { + return new_atom(StrAtom::new(Rc::new(str))); + } + let proto_ref = sym_ref(sym!(std::string::to_string::__protocol_tag__)); + let proto = h.exec(proto_ref).await.expect("This protocol is defined in this system"); + if let Ok(cb) = get_impl(atom.clone(), proto).await { + return call(cb.to_gen().await, [atom.to_gen().await]).to_gen().await; + } + } + return new_atom(StrAtom::new(Rc::new(fmt(&input).await))); + }) + .await + }), + ), + proto(true, "to_string").finish(), + ]), + )]) } diff --git a/orchid-std/src/std/tuple.rs b/orchid-std/src/std/tuple.rs index 2517b46..59164fe 100644 --- a/orchid-std/src/std/tuple.rs +++ b/orchid-std/src/std/tuple.rs @@ -17,8 +17,8 @@ use orchid_extension::atom::{Atomic, MethodSetBuilder, Supports, TAtom}; use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant, own}; use orchid_extension::conv::{ToExpr, TryFromExpr}; use orchid_extension::expr::{Expr, ExprHandle}; -use orchid_extension::gen_expr::{GExpr, sym_ref}; -use orchid_extension::system::dep_req; +use orchid_extension::gen_expr::{GExpr, new_atom, sym_ref}; +use orchid_extension::system::sys_req; use orchid_extension::tree::{GenMember, cnst, fun, prefix}; use crate::std::protocol::types::{GetImplMethod, GetTagIdMethod}; @@ -93,22 +93,22 @@ impl OwnedAtom for TupleBuilder { async fn call(mut self, arg: Expr) -> GExpr { self.items.push(arg); if self.arity.get() == self.items.len().try_into().expect("counting up from 0") { - Tuple(Rc::new(self.items)).to_gen().await + new_atom(Tuple(Rc::new(self.items))) } else { - self.to_gen().await + new_atom(self) } } } pub fn gen_tuple_lib() -> Vec { prefix("std::tuple", [ - cnst(true, "empty", Tuple(Rc::new(Vec::new()))), - fun(true, "one", async |item: Expr| Tuple(Rc::new(vec![item]))), + cnst(true, "empty", new_atom(Tuple(Rc::new(Vec::new())))), + fun(true, "one", async |item: Expr| new_atom(Tuple(Rc::new(vec![item])))), fun(true, "new", async |arity: TAtom| { if let Ok(arity) = u32::try_from(arity.value.0).and_then(|v| v.try_into()) { - TupleBuilder { arity, items: Vec::new() }.to_gen().await + new_atom(TupleBuilder { arity, items: Vec::new() }) } else { - Tuple(Rc::new(Vec::new())).to_gen().await + new_atom(Tuple(Rc::new(Vec::new()))) } }), fun(true, "get", async |tup: TAtom, idx: TAtom| { @@ -128,7 +128,7 @@ pub fn gen_tuple_lib() -> Vec { let mut new_vec = own(&tup).await.0.to_vec(); if let Some(slot) = new_vec.get_mut(idx) { *slot = val; - return Ok(Tuple(Rc::new(new_vec))); + return Ok(new_atom(Tuple(Rc::new(new_vec)))); } } return Err(mk_errv( @@ -141,7 +141,9 @@ pub fn gen_tuple_lib() -> Vec { Int(tup.len().try_into().expect("Tuple was created with an Int length")) }), fun(true, "cat", async |left: TAtom, right: TAtom| { - Tuple(Rc::new(own(&left).await.0.iter().chain(own(&right).await.0.iter()).cloned().collect())) + new_atom(Tuple(Rc::new( + own(&left).await.0.iter().chain(own(&right).await.0.iter()).cloned().collect(), + ))) }), ]) } @@ -159,7 +161,7 @@ impl TryFromExpr for UntypedTuple { impl ToExpr for UntypedTuple { async fn to_gen(self) -> GExpr { let exprs = join_all(self.0.into_iter().map(async |expr| expr.serialize().await)).await; - Expr::deserialize(dep_req::(CreateTuple(exprs)).await).await.to_gen().await + Expr::deserialize(sys_req::(CreateTuple(exprs)).await).await.to_gen().await } } diff --git a/orcx/src/main.rs b/orcx/src/main.rs index 285ab65..0e58fe2 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -64,7 +64,7 @@ pub struct Args { #[derive(Subcommand, Debug)] pub enum Commands { Lex { - #[arg(short, long)] + #[arg()] file: Utf8PathBuf, }, Parse {