Fixed a very nasty deadlock

This commit is contained in:
2026-01-22 20:56:02 +01:00
parent f38193edcc
commit b9f1bb74d7
21 changed files with 506 additions and 355 deletions

View File

@@ -1,7 +1,7 @@
[alias]
xtask = "run --quiet --package xtask --"
orcx = "xtask orcx"
orcxdb = "xtask orcxdb"
orcx = "xtask orcx --"
orcxdb = "xtask orcxdb --"
[env]
CARGO_WORKSPACE_DIR = { value = "", relative = true }

View File

@@ -1,5 +1,7 @@
let user = "lorinc" + " " + "bethlenfalvy"
let user = string::concat "lorinc" (string::concat " " "bethlenfalvy")
let number = 1 + 2
let interpolated = "Hello $user $number"
let user =
let user = r[ "foo" 1, "bar" 3 ]
let ffmain = user . "foo" |> option::expect "missing value"
let main = 1

View File

@@ -13,7 +13,7 @@ use futures::{AsyncRead, AsyncWrite, FutureExt, StreamExt, stream};
use orchid_api_derive::Coding;
use orchid_api_traits::{Coding, Decode, Encode, Request, enc_vec};
use orchid_base::error::{OrcErrv, OrcRes, mk_errv, mk_errv_floating};
use orchid_base::format::{FmtCtx, FmtUnit, Format, fmt};
use orchid_base::format::{FmtCtx, FmtUnit, Format, fmt, take_first};
use orchid_base::interner::is;
use orchid_base::location::Pos;
use orchid_base::name::Sym;
@@ -320,12 +320,18 @@ impl Format for AtomFactory {
}
}
pub async fn err_not_callable() -> OrcErrv {
mk_errv_floating(is("This atom is not callable").await, "Attempted to apply value as function")
pub async fn err_not_callable(unit: &FmtUnit) -> OrcErrv {
mk_errv_floating(
is("This atom is not callable").await,
format!("Attempted to apply {} as function", take_first(unit, false)),
)
}
pub async fn err_not_command() -> OrcErrv {
mk_errv_floating(is("This atom is not a command").await, "Settled on an inactionable value")
pub async fn err_not_command(unit: &FmtUnit) -> OrcErrv {
mk_errv_floating(
is("This atom is not a command").await,
format!("Settled on {} which is an inactionable value", take_first(unit, false)),
)
}
/// Read the type ID prefix from an atom, return type information and the rest

View File

@@ -228,7 +228,7 @@ pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
fn val(&self) -> impl Future<Output = Cow<'_, Self::Data>>;
#[allow(unused_variables)]
fn call_ref(&self, arg: Expr) -> impl Future<Output = GExpr> {
async move { bot(err_not_callable().await) }
async move { bot(err_not_callable(&self.dyn_print().await).await) }
}
fn call(self, arg: Expr) -> impl Future<Output = GExpr> {
async {
@@ -239,7 +239,7 @@ pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
}
#[allow(unused_variables)]
fn command(self) -> impl Future<Output = OrcRes<Option<GExpr>>> {
async move { Err(err_not_command().await) }
async move { Err(err_not_command(&self.dyn_print().await).await) }
}
#[allow(unused_variables)]
fn free(self) -> impl Future<Output = ()> { async {} }

View File

@@ -99,11 +99,11 @@ pub trait ThinAtom:
{
#[allow(unused_variables)]
fn call(&self, arg: Expr) -> impl Future<Output = GExpr> {
async move { bot(err_not_callable().await) }
async move { bot(err_not_callable(&self.print().await).await) }
}
#[allow(unused_variables)]
fn command(&self) -> impl Future<Output = OrcRes<Option<GExpr>>> {
async move { Err(err_not_command().await) }
async move { Err(err_not_command(&self.print().await).await) }
}
#[allow(unused_variables)]
fn print(&self) -> impl Future<Output = FmtUnit> {

View File

@@ -4,12 +4,13 @@ use std::pin::Pin;
use dyn_clone::DynClone;
use never::Never;
use orchid_base::error::{OrcErrv, OrcRes, mk_errv};
use orchid_base::format::{Format, fmt};
use orchid_base::interner::is;
use orchid_base::location::Pos;
use trait_set::trait_set;
use crate::atom::{AtomicFeatures, ForeignAtom, TAtom, ToAtom};
use crate::expr::Expr;
use crate::expr::{Expr, ExprKind};
use crate::gen_expr::{GExpr, atom, bot};
pub trait TryFromExpr: Sized {
@@ -26,14 +27,17 @@ impl<T: TryFromExpr, U: TryFromExpr> TryFromExpr for (T, U) {
}
}
async fn err_not_atom(pos: Pos) -> OrcErrv {
mk_errv(is("Expected an atom").await, "This expression is not an atom", [pos])
async fn err_not_atom(pos: Pos, value: &impl Format) -> OrcErrv {
mk_errv(is("Expected an atom").await, format!("{} is not an atom", fmt(value).await), [pos])
}
impl TryFromExpr for ForeignAtom {
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
match expr.atom().await {
Err(ex) => Err(err_not_atom(ex.data().await.pos.clone()).await),
if let ExprKind::Bottom(err) = &expr.data().await.kind {
return Err(err.clone());
}
match expr.clone().atom().await {
Err(ex) => Err(err_not_atom(ex.data().await.pos.clone(), &expr).await),
Ok(f) => Ok(f),
}
}

View File

@@ -63,7 +63,7 @@ impl Root {
pub async fn add_parsed(&self, parsed: &ParsedModule, pars_prefix: Sym) -> Self {
let mut ref_this = self.0.write().await;
let this = &mut *ref_this;
let mut deferred_consts = HashMap::new();
let mut deferred_consts = Vec::new();
let consts = this.consts.clone();
let mut tfpctx = FromParsedCtx {
pars_root: parsed,
@@ -86,7 +86,7 @@ impl Root {
.expect("Merge conflict between parsed and existing module");
let new = Root(Rc::new(RwLock::new(RootData { root, consts, ctx: this.ctx.clone() })));
*this.ctx.root.write().await = new.downgrade();
for (path, (sys_id, pc_id)) in deferred_consts {
for (path, sys_id, pc_id) in deferred_consts {
let sys = this.ctx.system_inst(sys_id).await.expect("System dropped since parsing");
let api_expr = sys.client().request(api::FetchParsedConst(sys.id(), pc_id)).await.unwrap();
let expr = Expr::from_api(&api_expr, PathSetBuilder::new(), this.ctx.clone()).await;
@@ -383,7 +383,7 @@ pub struct FromParsedCtx<'a> {
root: &'a Module,
ctx: &'a Ctx,
consts: &'a MemoMap<Sym, Expr>,
deferred_consts: &'a mut HashMap<Sym, (api::SysId, api::ParsedConstId)>,
deferred_consts: &'a mut Vec<(Sym, api::SysId, api::ParsedConstId)>,
}
impl Tree for Module {
@@ -435,7 +435,7 @@ impl MemberKind {
async fn from_parsed(parsed: &ParsedMemberKind, path: Sym, ctx: &mut FromParsedCtx<'_>) -> Self {
match parsed {
ParsedMemberKind::Const(id, sys) => {
ctx.deferred_consts.insert(path, (sys.id(), *id));
ctx.deferred_consts.push((path, sys.id(), *id));
MemberKind::Const
},
ParsedMemberKind::Mod(m) =>

View File

@@ -15,7 +15,7 @@ pub async fn gen_macro_lib() -> Vec<GenMember> {
prefix("macros", [
fun(true, "resolve", async |tpl: TAtom<MacTree>| resolve(own(&tpl).await).await),
prefix("common", [
build_macro(None, ["..", "_"]).finish(),
build_macro(None, ["..", "_", "="]).finish(),
build_macro(Some(1), ["+"])
.rule(mactreev!("...$" lhs 1 macros::common::+ "...$" rhs 0), [async |[lhs, rhs]| {
call(sym_ref(sym!(std::ops::add::resolve)), [resolve(lhs).await, resolve(rhs).await])
@@ -41,7 +41,7 @@ pub async fn gen_macro_lib() -> Vec<GenMember> {
call(sym_ref(sym!(std::ops::mod::resolve)), [resolve(lhs).await, resolve(rhs).await])
}])
.finish(),
build_macro(Some(10), ["."])
build_macro(Some(3), ["."])
.rule(mactreev!("...$" lhs 1 macros::common::. "...$" rhs 0), [async |[lhs, rhs]| {
call(sym_ref(sym!(std::ops::get::resolve)), [resolve(lhs).await, resolve(rhs).await])
}])

View File

@@ -18,7 +18,7 @@ use crate::macros::macro_value::Macro;
use crate::macros::mactree_lexer::MacTreeLexer;
use crate::macros::match_macros::{MatcherAtom, gen_match_macro_lib};
use crate::macros::ph_lexer::{PhAtom, PhLexer};
use crate::macros::std_macros::gen_std_macro_lib;
use crate::macros::stdlib::gen_std_macro_lib;
use crate::macros::utils::MacroBodyArgCollector;
use crate::{MacTree, StdSystem};
@@ -55,10 +55,14 @@ impl System for MacroSystem {
sym!(macros::common::;),
sym!(macros::common::..),
sym!(macros::common::_),
sym!(macros::common::=),
sym!(macros::common::.),
sym!(std::tuple::t),
sym!(std::record::r),
sym!(pattern::match),
sym!(pattern::ref),
sym!(pattern::=>),
Sym::literal("std::fn::|>").await,
]
}
fn lexers() -> Vec<LexerObj> { vec![&MacTreeLexer, &PhLexer] }

View File

@@ -21,7 +21,6 @@ use orchid_extension::tree::{GenMember, fun, prefix};
use crate::macros::resolve::resolve;
use crate::macros::utils::{build_macro, mactree, mactreev};
use crate::std::reflection::sym_atom::SymAtom;
use crate::std::tuple::Tuple;
use crate::{HomoTpl, MacTok, MacTree, OrcOpt, Tpl, UntypedTuple, api};
#[derive(Clone, Coding)]
@@ -92,20 +91,15 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
async |[value, rules]| {
exec(async move |mut h| {
let rule_lines = h
.exec::<TAtom<Tuple>>(call(sym_ref(sym!(macros::resolve)), [
.exec::<HomoTpl<TAtom<MacTree>>>(call(sym_ref(sym!(macros::resolve)), [
mactree!(macros::common::semi_list "push" rules.clone();).to_gen().await,
]))
.await?;
let mut rule_atoms = Vec::<(TAtom<MatcherAtom>, Expr)>::new();
for line_exprh in rule_lines.iter() {
let line_mac = h
.exec::<TAtom<MacTree>>(Expr::from_handle(
ExprHandle::from_ticket(*line_exprh).await,
))
.await?;
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,
mactree!(pattern::_row "push" own(line_mac).await ;).to_gen().await,
]))
.await?;
rule_atoms.push((matcher, body));

View File

@@ -10,7 +10,7 @@ pub mod match_macros;
mod ph_lexer;
mod resolve;
mod rule;
pub mod std_macros;
pub mod stdlib;
mod utils;
use mactree::{MacTok, MacTree};

View File

@@ -0,0 +1,12 @@
use orchid_extension::tree::{GenMember, prefix};
use crate::macros::resolve::resolve;
use crate::macros::utils::{build_macro, mactree, mactreev};
pub async fn gen_functional_macro_lib() -> Vec<GenMember> {
prefix("std::fn", [build_macro(Some(4), ["|>"])
.rule(mactreev!("...$" lhs 0 "std::fn::|>" "$" fun "...$" rhs 0), [async |[lhs, fun, rhs]| {
resolve(mactree!(("push" fun ; "push" lhs ;) "pushv" rhs ;)).await
}])
.finish()])
}

View File

@@ -0,0 +1,20 @@
pub mod funnctional;
pub mod option;
pub mod record;
pub mod tuple;
use orchid_extension::tree::{GenMember, merge_trivial};
use crate::macros::stdlib::funnctional::gen_functional_macro_lib;
use crate::macros::stdlib::option::gen_option_macro_lib;
use crate::macros::stdlib::record::gen_record_macro_lib;
use crate::macros::stdlib::tuple::gen_tuple_macro_lib;
pub async fn gen_std_macro_lib() -> Vec<GenMember> {
merge_trivial([
gen_functional_macro_lib().await,
gen_option_macro_lib().await,
gen_tuple_macro_lib().await,
gen_record_macro_lib().await,
])
}

View File

@@ -0,0 +1,56 @@
use futures::StreamExt;
use orchid_base::sym;
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::tree::{GenMember, fun, prefix};
use crate::macros::match_macros::MatcherAtom;
use crate::macros::resolve::resolve;
use crate::macros::utils::{build_macro, mactree, mactreev};
use crate::{OrcOpt, Tpl};
pub async fn gen_option_macro_lib() -> Vec<GenMember> {
prefix("std::option", [
fun(false, "is_some_body", |sub: TAtom<MatcherAtom>, val: OrcOpt<Expr>| {
exec(async move |mut h| {
let Some(sub_val) = val.0 else { return Ok(OrcOpt(None)) };
sub.run_matcher(&mut h, sub_val).await
})
}),
fun(
false,
"is_none_body",
async |val: OrcOpt<Expr>| {
if val.0.is_none() { OrcOpt(Some(Tpl(()))) } else { OrcOpt(None) }
},
),
build_macro(None, ["some", "none"])
.rule(mactreev!(pattern::match_rule ( std::option::some "...$" sub_pattern 0)), [
|[sub]: [_; _]| {
exec(async move |mut h| {
let sub = h
.exec::<TAtom<MatcherAtom>>(resolve(mactree!(pattern::match_rule "push" sub;)).await)
.await?;
Ok(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 {
keys: vec![],
matcher: h.register(sym_ref(sym!(std::option::is_none_body))).await,
})
})
}])
.finish(),
])
}

View File

@@ -0,0 +1,45 @@
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::expr::Expr;
use orchid_extension::gen_expr::{call, sym_ref};
use orchid_extension::tree::{GenMember, prefix};
use crate::macros::resolve::resolve;
use crate::macros::utils::{build_macro, mactree, mactreev};
use crate::std::string::str_atom::IntStrAtom;
use crate::{HomoTpl, MacTree, Tpl};
pub async fn gen_record_macro_lib() -> Vec<GenMember> {
prefix("std::record", [build_macro(None, ["r", "_row"])
.rule(mactreev!(std::record::r[ "...$" elements 0 ]), [async |[elements]: [_; _]| {
exec(async move |mut h| {
let tup = h
.exec::<HomoTpl<TAtom<MacTree>>>(call(sym_ref(sym!(macros::resolve)), [
mactree!((macros::common::comma_list "push" elements ;)).to_gen().await,
]))
.await?;
let mut record = sym_ref(sym!(std::record::empty));
for item_exprh in tup.0 {
let Tpl((key, value)) = h
.exec::<Tpl<(TAtom<IntStrAtom>, Expr)>>(
resolve(mactree!(std::record::_row "push" own(&item_exprh).await ;)).await,
)
.await?;
record = call(sym_ref(sym!(std::record::set)), [
record.to_gen().await,
key.to_gen().await,
value.to_gen().await,
]);
}
Ok(record)
})
.await
}])
.rule(mactreev!(std::record::_row ( "$" name "...$" value 1 )), [async |[name, value]| {
Ok(Tpl((resolve(name).await, resolve(value).await)))
}])
.finish()])
}

View File

@@ -10,53 +10,11 @@ use orchid_extension::gen_expr::{GExpr, call, sym_ref};
use orchid_extension::tree::{GenMember, fun, prefix};
use crate::macros::match_macros::MatcherAtom;
use crate::macros::resolve::resolve;
use crate::macros::utils::{build_macro, mactree, mactreev};
use crate::{HomoTpl, MacTree, OrcOpt, Tpl};
use crate::{HomoTpl, MacTree, OrcOpt};
pub async fn gen_std_macro_lib() -> Vec<GenMember> {
prefix("std", [
prefix("option", [
fun(false, "is_some_body", |sub: TAtom<MatcherAtom>, val: OrcOpt<Expr>| {
exec(async move |mut h| {
let Some(sub_val) = val.0 else { return Ok(OrcOpt(None)) };
sub.run_matcher(&mut h, sub_val).await
})
}),
fun(false, "is_none_body", async |val: OrcOpt<Expr>| {
if val.0.is_none() { OrcOpt(Some(Tpl(()))) } else { OrcOpt(None) }
}),
build_macro(None, ["some", "none"])
.rule(mactreev!(pattern::match_rule ( std::option::some "...$" sub_pattern 0)), [
|[sub]: [_; _]| {
exec(async move |mut h| {
let sub = h
.exec::<TAtom<MatcherAtom>>(
resolve(mactree!(pattern::match_rule "push" sub;)).await,
)
.await?;
Ok(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 {
keys: vec![],
matcher: h.register(sym_ref(sym!(std::option::is_none_body))).await,
})
})
}])
.finish(),
]),
prefix("tuple", [
pub async fn gen_tuple_macro_lib() -> Vec<GenMember> {
prefix("std::tuple", [
build_macro(None, ["t"])
.rule(mactreev!(std::tuple::t [ "...$" elements 0 ]), [|[elements]: [_; _]| {
exec(async move |mut h| {
@@ -94,32 +52,7 @@ pub async fn gen_std_macro_lib() -> Vec<GenMember> {
])
.finish(),
fun(false, "matcher_body", tuple_matcher_body),
]),
prefix("record", [
build_macro(None, ["r"])
.rule(mactreev!(std::record::r[ "...$" elements 0 ]), [async |[elements]: [_;_]| {
exec(async move |mut h| {
let tup = h
.exec::<HomoTpl<TAtom<MacTree>>>(call(sym_ref(sym!(macros::resolve)), [
mactree!((macros::common::comma_list "push" elements ;)).to_gen().await,
]))
.await?;
let val = stream::iter(&tup.0[..])
.fold(sym_ref(sym!(std::tuple::empty)), async |head, new| {
call(sym_ref(sym!(std::tuple::cat)), [
head,
call(sym_ref(sym!(std::tuple::one)), [call(
sym_ref(sym!(macros::resolve)),
[new.clone().to_gen().await],
)]),
])
})
.await;
Ok(val)
}).await
}]).finish(),
])
])
}
fn parse_tpl(elements: MacTree, tail_matcher: Option<MacTree>) -> impl Future<Output = GExpr> {

View File

@@ -188,6 +188,15 @@ macro_rules! mactreev_impl {
$ret.push($arg);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident "pushv" $arg:expr ; $($tail:tt)*) => {
let $crate::macros::mactree::MacTok::S(_, body) = $arg.tok() else {
panic!("pushv used with non-vec value")
};
for item in body.items.iter() {
$ret.push(item.clone());
}
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident "l_" $arg:expr ; ($($body:tt)*) $($tail:tt)*) => {
$ret.push(MacTok::Lambda(
MacTok::Name($arg).at(orchid_base::location::Pos::Inherit),

View File

@@ -5,11 +5,12 @@ use std::rc::Rc;
use futures::AsyncWrite;
use futures::future::join_all;
use hashbrown::HashMap;
use orchid_api_derive::Coding;
use orchid_api_traits::{Encode, Request};
use orchid_base::interner::{IStr, es};
use orchid_base::name::Sym;
use orchid_base::sym;
use orchid_extension::atom::{Atomic, Supports};
use orchid_extension::atom::{Atomic, MethodSetBuilder, Supports};
use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant};
use orchid_extension::expr::Expr;
use orchid_extension::gen_expr::sym_ref;
@@ -18,12 +19,15 @@ use crate::api;
use crate::std::protocol::types::{GetImplMethod, GetTagIdMethod};
#[derive(Clone)]
pub struct Record(pub Rc<HashMap<IStr, Expr>>);
impl Atomic for Record {
pub struct RecordAtom(pub Rc<HashMap<IStr, Expr>>);
impl Atomic for RecordAtom {
type Data = ();
type Variant = OwnedVariant;
fn reg_reqs() -> MethodSetBuilder<Self> {
MethodSetBuilder::new().handle::<GetTagIdMethod>().handle::<GetImplMethod>()
}
}
impl OwnedAtom for Record {
impl OwnedAtom for RecordAtom {
type Refs = Vec<Expr>;
async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs {
let (keys, values) =
@@ -34,17 +38,17 @@ impl OwnedAtom for Record {
async fn deserialize(mut dctx: impl DeserializeCtx, refs: Self::Refs) -> Self {
let keys =
join_all(dctx.decode::<Vec<api::TStr>>().await.iter().map(|t| async { es(*t).await })).await;
Record(Rc::new(keys.into_iter().zip(refs).collect()))
RecordAtom(Rc::new(keys.into_iter().zip(refs).collect()))
}
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
}
impl Supports<GetTagIdMethod> for Record {
impl Supports<GetTagIdMethod> for RecordAtom {
async fn handle(&self, _: GetTagIdMethod) -> <GetTagIdMethod as Request>::Response {
Sym::literal("std::record::Record").await.to_api()
}
}
impl Supports<GetImplMethod> for Record {
impl Supports<GetImplMethod> for RecordAtom {
async fn handle(&self, req: GetImplMethod) -> <GetImplMethod as Request>::Response {
let name = Sym::from_api(req.0).await;
let val = if name == sym!(std::ops::get) {
@@ -57,3 +61,9 @@ impl Supports<GetImplMethod> for Record {
Some(val.create().await.serialize().await)
}
}
#[derive(Clone, Debug, Coding)]
pub struct CreateRecord(pub std::collections::HashMap<api::TStr, api::ExprTicket>);
impl Request for CreateRecord {
type Response = api::ExprTicket;
}

View File

@@ -7,24 +7,24 @@ use orchid_extension::expr::Expr;
use orchid_extension::tree::{GenMember, cnst, fun, prefix};
use crate::std::option::OrcOpt;
use crate::std::record::record_atom::Record;
use crate::std::record::record_atom::RecordAtom;
use crate::std::string::str_atom::IntStrAtom;
pub fn gen_record_lib() -> Vec<GenMember> {
prefix("std::record", [
cnst(true, "empty", Record(Rc::new(HashMap::new()))),
fun(true, "set", async |map: TAtom<Record>, key: IntStrAtom, val: Expr| {
cnst(true, "empty", RecordAtom(Rc::new(HashMap::new()))),
fun(true, "set", async |map: TAtom<RecordAtom>, key: IntStrAtom, val: Expr| {
let mut map = own(&map).await.0.as_ref().clone();
map.insert(key.0.clone(), val);
Record(Rc::new(map))
RecordAtom(Rc::new(map))
}),
fun(true, "get", async |map: TAtom<Record>, key: IntStrAtom| {
fun(true, "get", async |map: TAtom<RecordAtom>, key: IntStrAtom| {
OrcOpt(own(&map).await.0.get(&key.0).cloned())
}),
fun(true, "delete", async |map: TAtom<Record>, key: IntStrAtom| {
fun(true, "delete", async |map: TAtom<RecordAtom>, key: IntStrAtom| {
let mut map = own(&map).await.0.as_ref().clone();
map.remove(&key.0);
Record(Rc::new(map))
RecordAtom(Rc::new(map))
}),
])
}

View File

@@ -2,6 +2,7 @@ use std::rc::Rc;
use futures::future::join_all;
use orchid_api_derive::{Coding, Hierarchy};
use orchid_base::interner::es;
use orchid_base::name::Sym;
use orchid_base::reqnot::{Receipt, ReqHandle, ReqHandleExt};
use orchid_base::sym;
@@ -23,7 +24,7 @@ use crate::std::option::{OptAtom, gen_option_lib};
use crate::std::protocol::proto_parser::{AsProtoParser, ProtoParser};
use crate::std::protocol::type_parser::{AsTypeParser, TypeParser};
use crate::std::protocol::types::{CreateTag, Tag, Tagged, gen_protocol_lib};
use crate::std::record::record_atom::Record;
use crate::std::record::record_atom::{CreateRecord, RecordAtom};
use crate::std::record::record_lib::gen_record_lib;
use crate::std::reflection::sym_atom::{CreateSymAtom, SymAtom, gen_sym_lib};
use crate::std::string::str_lexer::StringLexer;
@@ -36,6 +37,7 @@ use crate::{Float, Int};
pub enum StdReq {
CreateTag(CreateTag),
CreateTuple(CreateTuple),
CreateRecord(CreateRecord),
CreateSymAtom(CreateSymAtom),
}
@@ -58,7 +60,7 @@ impl SystemCard for StdSystem {
Some(StrAtom::dynfo()),
Some(IntStrAtom::dynfo()),
Some(OptAtom::dynfo()),
Some(Record::dynfo()),
Some(RecordAtom::dynfo()),
Some(Tuple::dynfo()),
Some(TupleBuilder::dynfo()),
Some(Tag::dynfo()),
@@ -74,6 +76,14 @@ impl System for StdSystem {
let tk = tpl.to_expr().await.serialize().await;
xreq.reply(req, &tk).await.unwrap()
},
StdReq::CreateRecord(ref req @ CreateRecord(ref items)) => {
let values =
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;
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()
@@ -109,5 +119,7 @@ impl System for StdSystem {
gen_ops_lib(),
])
}
async fn prelude() -> Vec<Sym> { vec![sym!(std), sym!(std::tuple), sym!(std::option)] }
async fn prelude() -> Vec<Sym> {
vec![sym!(std), sym!(std::tuple), sym!(std::option), sym!(std::record)]
}
}

View File

@@ -4,10 +4,13 @@ use tokio::time::Instant;
pub mod parse_folder;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Write};
use std::pin::pin;
use std::process::{Command, ExitCode};
use std::rc::Rc;
use std::time::Duration;
use async_fn_stream::try_stream;
use camino::{Utf8Path, Utf8PathBuf};
@@ -187,253 +190,294 @@ async fn main() -> io::Result<ExitCode> {
let logger = get_logger(&args);
let logger2 = logger.clone();
unsafe { STARTUP = Some(Instant::now()) };
local_set.spawn_local(with_stash(async move {
let ctx = &Ctx::new(SpawnerImpl, logger2);
let extensions = get_all_extensions(&args, ctx).try_collect::<Vec<Extension>>().await.unwrap();
time_print(&args, "Extensions loaded");
match args.command {
Commands::Lex { file } => {
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
let mut file = File::open(file.as_std_path()).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap();
println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true))
},
Commands::Parse { file } => {
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
let mut file = File::open(file.as_std_path()).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap();
let Some(first) = lexemes.first() else {
println!("File empty!");
return;
};
let pctx = HostParseCtxImpl { systems: &systems, ctx: ctx.clone(), src: sym!(usercode) };
let snip = Snippet::new(first, &lexemes);
match with_reporter(parse_items(&pctx, Substack::Bottom, snip)).await.unwrap() {
Err(errv) => {
eprintln!("{errv}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
},
Ok(ptree) if ptree.is_empty() => {
eprintln!("File empty only after parsing, but no errors were reported");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
},
Ok(ptree) =>
for item in ptree {
println!("{}", take_first(&item.print(&FmtCtxImpl::default()).await, true))
},
};
},
Commands::Repl => {
let mut counter = 0;
let mut imports = Vec::new();
let usercode_path = sym!(usercode);
let mut stdin = BufReader::new(stdin());
loop {
counter += 1;
let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap();
print!("\\.> ");
std::io::stdout().flush().unwrap();
let mut prompt = String::new();
stdin.read_line(&mut prompt).await.unwrap();
let name = is(&format!("_{counter}")).await;
let path = usercode_path.suffix([name.clone()]).await;
let mut lexemes =
lex(is(prompt.trim()).await, path.clone(), &systems, ctx).await.unwrap();
let Some(discr) = lexemes.first() else { continue };
writeln!(
log("debug"),
"lexed: {}",
take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true)
)
.await;
let prefix_sr = SrcRange::zw(path.clone(), 0);
let process_lexemes = async |lexemes: &[ParsTokTree]| {
let snippet = Snippet::new(&lexemes[0], lexemes);
let parse_ctx =
HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] };
match try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet)).await
{
Ok(items) => Some(items),
Err(e) => {
eprintln!("{e}");
None
},
}
let ctx = Ctx::new(SpawnerImpl, logger2);
let (signal_end_main, on_end_main) = futures::channel::oneshot::channel();
let ctx1 = ctx.clone();
local_set.spawn_local(async move {
let ctx = &ctx1;
with_stash(async move {
let extensions =
get_all_extensions(&args, ctx).try_collect::<Vec<Extension>>().await.unwrap();
time_print(&args, "Extensions loaded");
match args.command {
Commands::Lex { file } => {
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
let mut file = File::open(file.as_std_path()).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap();
println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true))
},
Commands::Parse { file } => {
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
let mut file = File::open(file.as_std_path()).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap();
let Some(first) = lexemes.first() else {
println!("File empty!");
return;
};
let add_imports = |items: &mut Vec<Item>, imports: &[Import]| {
items.extend(imports.iter().map(|import| Item::new(import.sr.clone(), import.clone())));
};
if discr.is_kw(is("import").await) {
let Some(import_lines) = process_lexemes(&lexemes).await else { continue };
imports.extend(import_lines.into_iter().map(|it| match it.kind {
ItemKind::Import(imp) => imp,
_ => panic!("Expected imports from import line"),
}));
continue;
}
if !discr.is_kw(is("let").await) {
let prefix = [is("export").await, is("let").await, name.clone(), is("=").await];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
}
let Some(mut new_lines) = process_lexemes(&lexemes).await else { continue };
let const_decl = new_lines.iter().exactly_one().expect("Multiple lines from let");
let input_sr = const_decl.sr.map_range(|_| 0..0);
let const_name = match &const_decl.kind {
ItemKind::Member(ParsedMember { name: const_name, .. }) => const_name.clone(),
_ => panic!("Expected exactly one constant declaration from let"),
};
add_imports(&mut new_lines, &imports);
imports.push(Import::new(input_sr.clone(), VPath::new(path.segs()), const_name.clone()));
let new_module = ParsedModule::new(true, new_lines);
match with_reporter(root.add_parsed(&new_module, path.clone())).await {
Ok(new) => root = new,
let pctx = HostParseCtxImpl { systems: &systems, ctx: ctx.clone(), src: sym!(usercode) };
let snip = Snippet::new(first, &lexemes);
match with_reporter(parse_items(&pctx, Substack::Bottom, snip)).await.unwrap() {
Err(errv) => {
eprintln!("{errv}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
}
eprintln!("parsed");
let entrypoint =
ExprKind::Const(path.suffix([const_name.clone()]).await).at(input_sr.pos());
let mut xctx = ExecCtx::new(root.clone(), entrypoint).await;
eprintln!("executed");
xctx.set_gas(Some(1000));
xctx.execute().await;
match xctx.result() {
ExecResult::Value(val) => println!(
"{const_name} = {}",
take_first(&val.print(&FmtCtxImpl::default()).await, false)
),
ExecResult::Err(e) => println!("error: {e}"),
ExecResult::Gas(_) => println!("Ran out of gas!"),
}
}
},
Commands::ModTree { proj, prefix } => {
let (mut root, _systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = proj {
let path = proj_path.into_std_path_buf();
match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await {
Ok(r) => root = r,
Err(e) => {
eprintln!("{e}");
Ok(ptree) if ptree.is_empty() => {
eprintln!("File empty only after parsing, but no errors were reported");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
}
}
let prefix = match prefix {
Some(pref) => VPath::parse(&pref).await,
None => VPath::new([]),
};
let root_data = root.0.read().await;
print_mod(&root_data.root, prefix, &root_data).await;
async fn print_mod(module: &Module, path: VPath, root: &RootData) {
let indent = " ".repeat(path.len());
for (key, tgt) in &module.imports {
match tgt {
Ok(tgt) => println!("{indent}import {key} => {}", tgt.target),
Err(opts) => println!(
"{indent}import {key} conflicts between {}",
opts.iter().map(|i| &i.target).join(" ")
),
}
}
for (key, mem) in &module.members {
let new_path = path.clone().name_with_suffix(key.clone()).to_sym().await;
match mem.kind(root.ctx.clone(), &root.consts).await {
MemberKind::Module(module) => {
println!("{indent}module {key} {{");
print_mod(module, VPath::new(new_path.segs()), root).boxed_local().await;
println!("{indent}}}")
Ok(ptree) =>
for item in ptree {
println!("{}", take_first(&item.print(&FmtCtxImpl::default()).await, true))
},
MemberKind::Const => {
let value = root.consts.get(&new_path).expect("Missing const!");
println!("{indent}const {key} = {}", fmt(value).await)
},
}
}
}
},
Commands::Exec { proj, code } => {
let path = sym!(usercode);
let prefix_sr = SrcRange::zw(path.clone(), 0);
let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = proj {
let path = proj_path.into_std_path_buf();
match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await {
Ok(r) => root = r,
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
}
}
let mut lexemes = match lex(is(code.trim()).await, path.clone(), &systems, ctx).await {
Ok(lexemes) => {
};
},
Commands::Repl => {
let mut counter = 0;
let mut imports = Vec::new();
let usercode_path = sym!(usercode);
let mut stdin = BufReader::new(stdin());
loop {
counter += 1;
let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap();
print!("\\.> ");
std::io::stdout().flush().unwrap();
let mut prompt = String::new();
stdin.read_line(&mut prompt).await.unwrap();
let name = is(&format!("_{counter}")).await;
let path = usercode_path.suffix([name.clone()]).await;
let mut lexemes =
lex(is(prompt.trim()).await, path.clone(), &systems, ctx).await.unwrap();
let Some(discr) = lexemes.first() else { continue };
writeln!(
log("debug"),
"lexed: {}",
fmt_v::<ParsTokTree>(lexemes.iter()).await.join(" ")
take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true)
)
.await;
lexemes
},
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
};
let parse_ctx =
HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] };
let prefix = [is("export").await, is("let").await, is("entrypoint").await, is("=").await];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
let snippet = Snippet::new(&lexemes[0], &lexemes);
let entrypoint = match try_with_reporter(parse_item(
&parse_ctx,
Substack::Bottom,
vec![],
snippet,
))
.await
{
Ok(items) => ParsedModule::new(true, items),
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
};
let root = match with_reporter(root.add_parsed(&entrypoint, path.clone())).await {
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
Ok(new_root) => new_root,
};
let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos());
let mut xctx = ExecCtx::new(root, expr).await;
xctx.set_gas(Some(10_000));
xctx.execute().await;
match xctx.result() {
ExecResult::Value(val) => {
println!("{}", take_first(&val.print(&FmtCtxImpl::default()).await, false))
},
ExecResult::Err(e) => println!("error: {e}"),
ExecResult::Gas(_) => println!("Ran out of gas!"),
}
},
let prefix_sr = SrcRange::zw(path.clone(), 0);
let process_lexemes = async |lexemes: &[ParsTokTree]| {
let snippet = Snippet::new(&lexemes[0], lexemes);
let parse_ctx =
HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] };
match try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet))
.await
{
Ok(items) => Some(items),
Err(e) => {
eprintln!("{e}");
None
},
}
};
let add_imports = |items: &mut Vec<Item>, imports: &[Import]| {
items
.extend(imports.iter().map(|import| Item::new(import.sr.clone(), import.clone())));
};
if discr.is_kw(is("import").await) {
let Some(import_lines) = process_lexemes(&lexemes).await else { continue };
imports.extend(import_lines.into_iter().map(|it| match it.kind {
ItemKind::Import(imp) => imp,
_ => panic!("Expected imports from import line"),
}));
continue;
}
if !discr.is_kw(is("let").await) {
let prefix = [is("export").await, is("let").await, name.clone(), is("=").await];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
}
let Some(mut new_lines) = process_lexemes(&lexemes).await else { continue };
let const_decl = new_lines.iter().exactly_one().expect("Multiple lines from let");
let input_sr = const_decl.sr.map_range(|_| 0..0);
let const_name = match &const_decl.kind {
ItemKind::Member(ParsedMember { name: const_name, .. }) => const_name.clone(),
_ => panic!("Expected exactly one constant declaration from let"),
};
add_imports(&mut new_lines, &imports);
imports.push(Import::new(
input_sr.clone(),
VPath::new(path.segs()),
const_name.clone(),
));
let new_module = ParsedModule::new(true, new_lines);
match with_reporter(root.add_parsed(&new_module, path.clone())).await {
Ok(new) => root = new,
Err(errv) => {
eprintln!("{errv}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
}
eprintln!("parsed");
let entrypoint =
ExprKind::Const(path.suffix([const_name.clone()]).await).at(input_sr.pos());
let mut xctx = ExecCtx::new(root.clone(), entrypoint).await;
eprintln!("executed");
xctx.set_gas(Some(1000));
xctx.execute().await;
match xctx.result() {
ExecResult::Value(val) => println!(
"{const_name} = {}",
take_first(&val.print(&FmtCtxImpl::default()).await, false)
),
ExecResult::Err(e) => println!("error: {e}"),
ExecResult::Gas(_) => println!("Ran out of gas!"),
}
}
},
Commands::ModTree { proj, prefix } => {
let (mut root, _systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = proj {
let path = proj_path.into_std_path_buf();
match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await {
Ok(r) => root = r,
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
}
}
let prefix = match prefix {
Some(pref) => VPath::parse(&pref).await,
None => VPath::new([]),
};
let root_data = root.0.read().await;
print_mod(&root_data.root, prefix, &root_data).await;
async fn print_mod(module: &Module, path: VPath, root: &RootData) {
let indent = " ".repeat(path.len());
for (key, tgt) in &module.imports {
match tgt {
Ok(tgt) => println!("{indent}import {key} => {}", tgt.target),
Err(opts) => println!(
"{indent}import {key} conflicts between {}",
opts.iter().map(|i| &i.target).join(" ")
),
}
}
for (key, mem) in &module.members {
let new_path = path.clone().name_with_suffix(key.clone()).to_sym().await;
match mem.kind(root.ctx.clone(), &root.consts).await {
MemberKind::Module(module) => {
println!("{indent}module {key} {{");
print_mod(module, VPath::new(new_path.segs()), root).boxed_local().await;
println!("{indent}}}")
},
MemberKind::Const => {
let value = root.consts.get(&new_path).expect("Missing const!");
println!("{indent}const {key} = {}", fmt(value).await)
},
}
}
}
},
Commands::Exec { proj, code } => {
let path = sym!(usercode);
let prefix_sr = SrcRange::zw(path.clone(), 0);
let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = proj {
let path = proj_path.into_std_path_buf();
match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await {
Ok(r) => root = r,
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
}
}
let mut lexemes = match lex(is(code.trim()).await, path.clone(), &systems, ctx).await {
Ok(lexemes) => {
writeln!(
log("debug"),
"lexed: {}",
fmt_v::<ParsTokTree>(lexemes.iter()).await.join(" ")
)
.await;
lexemes
},
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
};
let parse_ctx =
HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] };
let prefix = [is("export").await, is("let").await, is("entrypoint").await, is("=").await];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
let snippet = Snippet::new(&lexemes[0], &lexemes);
let entrypoint = match try_with_reporter(parse_item(
&parse_ctx,
Substack::Bottom,
vec![],
snippet,
))
.await
{
Ok(items) => ParsedModule::new(true, items),
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
};
let root = match with_reporter(root.add_parsed(&entrypoint, path.clone())).await {
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
Ok(new_root) => new_root,
};
let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos());
let mut xctx = ExecCtx::new(root.clone(), expr).await;
xctx.set_gas(Some(10_000));
xctx.execute().await;
match xctx.result() {
ExecResult::Value(val) => {
println!("{}", take_first(&val.print(&FmtCtxImpl::default()).await, false))
},
ExecResult::Err(e) => println!("error: {e}"),
ExecResult::Gas(_) => println!("Ran out of gas!"),
}
},
};
})
.await;
signal_end_main.send(()).expect("cleanup should still be waiting");
});
let cleanup = async {
if on_end_main.await.is_err() {
return;
}
}));
with_interner(local_interner(), with_logger(logger, local_set)).await;
tokio::time::sleep(Duration::from_secs(2)).await;
let mut extensions = HashMap::new();
let systems = ctx.systems.read().await.values().filter_map(|v| v.upgrade()).collect_vec();
let exprs = ctx.exprs.iter().collect_vec();
for system in &systems {
extensions.insert(system.ext().name().clone(), system.ext().clone());
}
if extensions.is_empty() && systems.is_empty() && exprs.is_empty() {
return;
}
eprintln!("Shutdown is taking long. The following language constructs are still live:");
eprintln!("Extensions: {}", extensions.keys().join(", "));
for sys in &systems {
eprintln!("System: {:?} = {}", sys.id(), sys.ctor().name())
}
for (rc, expr) in &exprs {
eprintln!("{rc}x {:?} = {}", expr.id(), fmt(expr).await)
}
};
futures::future::select(
pin!(cleanup),
pin!(with_interner(local_interner(), with_logger(logger, local_set))),
)
.await;
let x = *exit_code.borrow();
Ok(x)
}