#![doc(
html_logo_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg",
html_favicon_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg"
)]
#[macro_use]
extern crate netidx_core;
#[macro_use]
extern crate combine;
#[macro_use]
extern crate serde_derive;
pub mod env;
pub mod expr;
pub mod node;
pub mod typ;
use crate::{
env::Env,
expr::{ExprId, ModPath},
node::lambda::LambdaDef,
typ::{FnType, Type},
};
use ahash::{AHashMap, AHashSet};
use anyhow::{bail, Result};
use arcstr::ArcStr;
use enumflags2::{bitflags, BitFlags};
use expr::Expr;
use futures::channel::mpsc;
use log::info;
use netidx::{
path::Path,
publisher::{Id, Val, WriteRequest},
subscriber::{self, Dval, SubId, UpdatesFlags, Value},
};
use netidx_protocols::rpc::server::{ArgSpec, RpcCall};
use netidx_value::{abstract_type::AbstractWrapper, Abstract};
use node::compiler;
use nohash::{IntMap, IntSet};
use parking_lot::{Mutex, RwLock};
use poolshark::{
global::{GPooled, Pool},
local::LPooled,
};
use std::{
any::{Any, TypeId},
cell::Cell,
collections::hash_map::{self, Entry},
fmt::Debug,
mem,
sync::{
self,
atomic::{AtomicBool, Ordering},
LazyLock,
},
time::Duration,
};
use tokio::{task, time::Instant};
use triomphe::Arc;
use uuid::Uuid;
#[derive(Debug, Clone, Copy)]
#[bitflags]
#[repr(u64)]
pub enum CFlag {
WarnUnhandled,
WarnUnused,
WarningsAreErrors,
}
#[allow(dead_code)]
static TRACE: AtomicBool = AtomicBool::new(false);
#[allow(dead_code)]
pub fn set_trace(b: bool) {
TRACE.store(b, Ordering::Relaxed)
}
#[allow(dead_code)]
pub fn with_trace<F: FnOnce() -> Result<R>, R>(
enable: bool,
spec: &Expr,
f: F,
) -> Result<R> {
let prev = trace();
set_trace(enable);
if !prev && enable {
eprintln!("trace enabled at {}, spec: {}", spec.pos, spec);
} else if prev && !enable {
eprintln!("trace disabled at {}, spec: {}", spec.pos, spec);
}
let r = match f() {
Err(e) => {
eprintln!("traced at {} failed with {e:?}", spec.pos);
Err(e)
}
r => r,
};
if prev && !enable {
eprintln!("trace reenabled")
}
set_trace(prev);
r
}
#[allow(dead_code)]
pub fn trace() -> bool {
TRACE.load(Ordering::Relaxed)
}
#[macro_export]
macro_rules! tdbg {
($e:expr) => {
if $crate::trace() {
dbg!($e)
} else {
$e
}
};
}
#[macro_export]
macro_rules! err {
($tag:expr, $err:literal) => {{
let e: Value = ($tag.clone(), ::arcstr::literal!($err)).into();
Value::Error(::triomphe::Arc::new(e))
}};
}
#[macro_export]
macro_rules! errf {
($tag:expr, $fmt:expr, $($args:expr),*) => {{
let msg: ArcStr = ::compact_str::format_compact!($fmt, $($args),*).as_str().into();
let e: Value = ($tag.clone(), msg).into();
Value::Error(::triomphe::Arc::new(e))
}};
($tag:expr, $fmt:expr) => {{
let msg: ArcStr = ::compact_str::format_compact!($fmt).as_str().into();
let e: Value = ($tag.clone(), msg).into();
Value::Error(::triomphe::Arc::new(e))
}};
}
#[macro_export]
macro_rules! defetyp {
($name:ident, $tag_name:ident, $tag:literal, $typ:expr) => {
static $tag_name: ArcStr = ::arcstr::literal!($tag);
static $name: ::std::sync::LazyLock<$crate::typ::Type> =
::std::sync::LazyLock::new(|| {
let scope = $crate::expr::ModPath::root();
$crate::expr::parser::parse_type(&format!($typ, $tag))
.expect("failed to parse type")
.scope_refs(&scope)
});
};
}
defetyp!(CAST_ERR, CAST_ERR_TAG, "InvalidCast", "Error<`{}(string)>");
atomic_id!(LambdaId);
impl From<u64> for LambdaId {
fn from(v: u64) -> Self {
LambdaId(v)
}
}
atomic_id!(BindId);
impl From<u64> for BindId {
fn from(v: u64) -> Self {
BindId(v)
}
}
impl TryFrom<Value> for BindId {
type Error = anyhow::Error;
fn try_from(value: Value) -> Result<Self> {
match value {
Value::U64(id) => Ok(BindId(id)),
v => bail!("invalid bind id {v}"),
}
}
}
pub trait UserEvent: Clone + Debug + Any {
fn clear(&mut self);
}
pub trait CustomBuiltinType: Debug + Any + Send + Sync {}
impl CustomBuiltinType for Value {}
impl CustomBuiltinType for Option<Value> {}
#[derive(Debug, Clone)]
pub struct NoUserEvent;
impl UserEvent for NoUserEvent {
fn clear(&mut self) {}
}
#[derive(Debug, Clone, Copy)]
#[bitflags]
#[repr(u64)]
pub enum PrintFlag {
DerefTVars,
ReplacePrims,
NoSource,
NoParents,
}
thread_local! {
static PRINT_FLAGS: Cell<BitFlags<PrintFlag>> = Cell::new(PrintFlag::ReplacePrims.into());
}
pub static CBATCH_POOL: LazyLock<Pool<Vec<(BindId, Box<dyn CustomBuiltinType>)>>> =
LazyLock::new(|| Pool::new(10000, 1000));
pub fn format_with_flags<G: Into<BitFlags<PrintFlag>>, R, F: FnOnce() -> R>(
flags: G,
f: F,
) -> R {
let prev = PRINT_FLAGS.replace(flags.into());
let res = f();
PRINT_FLAGS.set(prev);
res
}
#[derive(Debug)]
pub struct Event<E: UserEvent> {
pub init: bool,
pub variables: IntMap<BindId, Value>,
pub netidx: IntMap<SubId, subscriber::Event>,
pub writes: IntMap<Id, WriteRequest>,
pub rpc_calls: IntMap<BindId, RpcCall>,
pub custom: IntMap<BindId, Box<dyn CustomBuiltinType>>,
pub user: E,
}
impl<E: UserEvent> Event<E> {
pub fn new(user: E) -> Self {
Event {
init: false,
variables: IntMap::default(),
netidx: IntMap::default(),
writes: IntMap::default(),
rpc_calls: IntMap::default(),
custom: IntMap::default(),
user,
}
}
pub fn clear(&mut self) {
let Self { init, variables, netidx, rpc_calls, writes, custom, user } = self;
*init = false;
variables.clear();
netidx.clear();
rpc_calls.clear();
custom.clear();
writes.clear();
user.clear();
}
}
#[derive(Debug, Clone, Default)]
pub struct Refs {
refed: LPooled<IntSet<BindId>>,
bound: LPooled<IntSet<BindId>>,
}
pub use combine::stream::position::SourcePosition;
#[derive(Debug, Clone)]
pub struct ReferenceSite {
pub pos: SourcePosition,
pub ori: Arc<expr::Origin>,
pub name: expr::ModPath,
pub bind_id: BindId,
pub def_pos: SourcePosition,
pub def_ori: Arc<expr::Origin>,
}
#[derive(Debug, Clone)]
pub struct ModuleRefSite {
pub pos: SourcePosition,
pub ori: Arc<expr::Origin>,
pub name: expr::ModPath,
pub canonical: expr::ModPath,
pub def_ori: Option<Arc<expr::Origin>>,
}
#[derive(Debug, Clone)]
pub struct ScopeMapEntry {
pub pos: SourcePosition,
pub ori: Arc<expr::Origin>,
pub scope: Scope,
}
#[derive(Debug, Clone)]
pub struct TypeRefSite {
pub pos: SourcePosition,
pub ori: Arc<expr::Origin>,
pub name: expr::ModPath,
pub canonical_scope: expr::ModPath,
pub def_pos: SourcePosition,
pub def_ori: Arc<expr::Origin>,
}
#[derive(Debug, Clone)]
pub struct SigImplLink {
pub scope: expr::ModPath,
pub name: compact_str::CompactString,
pub sig_id: BindId,
pub impl_id: BindId,
}
#[derive(Debug, Clone)]
pub struct ModuleInternalView {
pub scope: expr::ModPath,
pub env: env::Env,
}
pub static REFERENCE_SITE_POOL: LazyLock<Pool<Vec<ReferenceSite>>> =
LazyLock::new(|| Pool::new(64, 65536));
pub static MODULE_REF_SITE_POOL: LazyLock<Pool<Vec<ModuleRefSite>>> =
LazyLock::new(|| Pool::new(64, 65536));
pub static SCOPE_MAP_ENTRY_POOL: LazyLock<Pool<Vec<ScopeMapEntry>>> =
LazyLock::new(|| Pool::new(64, 65536));
impl Refs {
pub fn clear(&mut self) {
self.refed.clear();
self.bound.clear();
}
pub fn with_external_refs(&self, mut f: impl FnMut(BindId)) {
for id in &*self.refed {
if !self.bound.contains(id) {
f(*id);
}
}
}
}
pub type Node<R, E> = Box<dyn Update<R, E>>;
#[derive(Debug)]
pub enum TypecheckPhase<'a> {
Lambda,
CallSite(&'a FnType),
}
pub type InitFn<R, E> = sync::Arc<
dyn for<'a, 'b, 'c, 'd> Fn(
&'a Scope,
&'b mut ExecCtx<R, E>,
&'c mut [Node<R, E>],
Option<&'d FnType>,
ExprId,
) -> Result<Box<dyn Apply<R, E>>>
+ Send
+ Sync
+ 'static,
>;
pub trait Apply<R: Rt, E: UserEvent>: Debug + Send + Sync + Any {
fn update(
&mut self,
ctx: &mut ExecCtx<R, E>,
from: &mut [Node<R, E>],
event: &mut Event<E>,
) -> Option<Value>;
fn delete(&mut self, _ctx: &mut ExecCtx<R, E>) {
()
}
fn typecheck(
&mut self,
_ctx: &mut ExecCtx<R, E>,
_from: &mut [Node<R, E>],
_phase: TypecheckPhase<'_>,
) -> Result<()> {
Ok(())
}
fn typ(&self) -> Arc<FnType> {
static EMPTY: LazyLock<Arc<FnType>> = LazyLock::new(|| {
Arc::new(FnType {
args: Arc::from_iter([]),
constraints: Arc::new(RwLock::new(LPooled::take())),
rtype: Type::Bottom,
throws: Type::Bottom,
vargs: None,
explicit_throws: false,
..Default::default()
})
});
Arc::clone(&*EMPTY)
}
fn refs<'a>(&self, _refs: &mut Refs) {}
fn sleep(&mut self, _ctx: &mut ExecCtx<R, E>);
}
pub trait Update<R: Rt, E: UserEvent>: Debug + Send + Sync + Any + 'static {
fn update(&mut self, ctx: &mut ExecCtx<R, E>, event: &mut Event<E>) -> Option<Value>;
fn delete(&mut self, ctx: &mut ExecCtx<R, E>);
fn typecheck(&mut self, ctx: &mut ExecCtx<R, E>) -> Result<()>;
fn typ(&self) -> &Type;
fn refs(&self, refs: &mut Refs);
fn spec(&self) -> &Expr;
fn sleep(&mut self, ctx: &mut ExecCtx<R, E>);
}
pub type BuiltInInitFn<R, E> = for<'a, 'b, 'c, 'd> fn(
&'a mut ExecCtx<R, E>,
&'a FnType,
Option<&'d FnType>,
&'b Scope,
&'c [Node<R, E>],
ExprId,
) -> Result<Box<dyn Apply<R, E>>>;
pub trait BuiltIn<R: Rt, E: UserEvent> {
const NAME: &str;
const NEEDS_CALLSITE: bool;
fn init<'a, 'b, 'c, 'd>(
ctx: &'a mut ExecCtx<R, E>,
typ: &'a FnType,
resolved_type: Option<&'d FnType>,
scope: &'b Scope,
from: &'c [Node<R, E>],
top_id: ExprId,
) -> Result<Box<dyn Apply<R, E>>>;
}
pub trait Abortable {
fn abort(&self);
}
impl Abortable for task::AbortHandle {
fn abort(&self) {
task::AbortHandle::abort(self)
}
}
pub trait Rt: Debug + Any {
type AbortHandle: Abortable;
fn clear(&mut self);
fn subscribe(&mut self, flags: UpdatesFlags, path: Path, ref_by: ExprId) -> Dval;
fn unsubscribe(&mut self, path: Path, dv: Dval, ref_by: ExprId);
fn list(&mut self, id: BindId, path: Path);
fn list_table(&mut self, id: BindId, path: Path);
fn stop_list(&mut self, id: BindId);
fn publish(&mut self, path: Path, value: Value, ref_by: ExprId) -> Result<Val>;
fn update(&mut self, id: &Val, value: Value);
fn unpublish(&mut self, id: Val, ref_by: ExprId);
fn ref_var(&mut self, id: BindId, ref_by: ExprId);
fn unref_var(&mut self, id: BindId, ref_by: ExprId);
fn set_var(&mut self, id: BindId, value: Value);
fn notify_set(&mut self, id: BindId);
fn call_rpc(&mut self, name: Path, args: Vec<(ArcStr, Value)>, id: BindId);
fn publish_rpc(
&mut self,
name: Path,
doc: Value,
spec: Vec<ArgSpec>,
id: BindId,
) -> Result<()>;
fn unpublish_rpc(&mut self, name: Path);
fn set_timer(&mut self, id: BindId, timeout: Duration);
fn spawn<F: Future<Output = (BindId, Box<dyn CustomBuiltinType>)> + Send + 'static>(
&mut self,
f: F,
) -> Self::AbortHandle;
fn spawn_var<F: Future<Output = (BindId, Value)> + Send + 'static>(
&mut self,
f: F,
) -> Self::AbortHandle;
fn watch(
&mut self,
s: mpsc::Receiver<GPooled<Vec<(BindId, Box<dyn CustomBuiltinType>)>>>,
);
fn watch_var(&mut self, s: mpsc::Receiver<GPooled<Vec<(BindId, Value)>>>);
}
#[derive(Default)]
pub struct LibState(AHashMap<TypeId, Box<dyn Any + Send + Sync>>);
impl LibState {
pub fn get_or_default<T>(&mut self) -> &mut T
where
T: Default + Any + Send + Sync,
{
self.0
.entry(TypeId::of::<T>())
.or_insert_with(|| Box::new(T::default()) as Box<dyn Any + Send + Sync>)
.downcast_mut::<T>()
.unwrap()
}
pub fn get_or_else<T, F>(&mut self, f: F) -> &mut T
where
T: Any + Send + Sync,
F: FnOnce() -> T,
{
self.0
.entry(TypeId::of::<T>())
.or_insert_with(|| Box::new(f()) as Box<dyn Any + Send + Sync>)
.downcast_mut::<T>()
.unwrap()
}
pub fn entry<'a, T>(
&'a mut self,
) -> hash_map::Entry<'a, TypeId, Box<dyn Any + Send + Sync>>
where
T: Any + Send + Sync,
{
self.0.entry(TypeId::of::<T>())
}
pub fn contains<T>(&self) -> bool
where
T: Any + Send + Sync,
{
self.0.contains_key(&TypeId::of::<T>())
}
pub fn get<T>(&mut self) -> Option<&T>
where
T: Any + Send + Sync,
{
self.0.get(&TypeId::of::<T>()).map(|t| t.downcast_ref::<T>().unwrap())
}
pub fn get_mut<T>(&mut self) -> Option<&mut T>
where
T: Any + Send + Sync,
{
self.0.get_mut(&TypeId::of::<T>()).map(|t| t.downcast_mut::<T>().unwrap())
}
pub fn set<T>(&mut self, t: T) -> Option<Box<T>>
where
T: Any + Send + Sync,
{
self.0
.insert(TypeId::of::<T>(), Box::new(t) as Box<dyn Any + Send + Sync>)
.map(|t| t.downcast::<T>().unwrap())
}
pub fn remove<T>(&mut self) -> Option<Box<T>>
where
T: Any + Send + Sync,
{
self.0.remove(&TypeId::of::<T>()).map(|t| t.downcast::<T>().unwrap())
}
}
#[derive(Default)]
pub struct AbstractTypeRegistry {
by_tid: AHashMap<TypeId, Uuid>,
by_uuid: AHashMap<Uuid, &'static str>,
}
impl AbstractTypeRegistry {
fn with<V, F: FnMut(&mut AbstractTypeRegistry) -> V>(mut f: F) -> V {
static REG: LazyLock<Mutex<AbstractTypeRegistry>> =
LazyLock::new(|| Mutex::new(AbstractTypeRegistry::default()));
let mut g = REG.lock();
f(&mut *g)
}
pub(crate) fn uuid<T: Any>(tag: &'static str) -> Uuid {
Self::with(|rg| {
*rg.by_tid.entry(TypeId::of::<T>()).or_insert_with(|| {
let id = Uuid::new_v4();
rg.by_uuid.insert(id, tag);
id
})
})
}
pub fn tag(a: &Abstract) -> Option<&'static str> {
Self::with(|rg| rg.by_uuid.get(&a.id()).map(|r| *r))
}
pub fn is_a(a: &Abstract, tag: &str) -> bool {
match Self::tag(a) {
Some(t) => t == tag,
None => false,
}
}
}
pub struct ExecCtx<R: Rt, E: UserEvent> {
lambdawrap: AbstractWrapper<LambdaDef<R, E>>,
builtins: AHashMap<&'static str, (BuiltInInitFn<R, E>, bool)>,
builtins_allowed: bool,
tags: AHashSet<ArcStr>,
pub libstate: LibState,
pub env: Env,
pub cached: IntMap<BindId, Value>,
pub rt: R,
pub lambda_defs: IntMap<LambdaId, Value>,
pub deferred_checks:
Vec<Box<dyn FnOnce(&mut ExecCtx<R, E>) -> Result<()> + Send + Sync>>,
pub references: GPooled<Vec<ReferenceSite>>,
pub module_references: GPooled<Vec<ModuleRefSite>>,
pub scope_map: GPooled<Vec<ScopeMapEntry>>,
}
impl<R: Rt, E: UserEvent> ExecCtx<R, E> {
pub fn clear(&mut self) {
self.env.clear();
self.rt.clear();
}
pub fn new(user: R) -> Result<Self> {
let id = AbstractTypeRegistry::uuid::<LambdaDef<R, E>>("lambda");
Ok(Self {
lambdawrap: Abstract::register(id)?,
env: Env::default(),
builtins: AHashMap::default(),
builtins_allowed: true,
libstate: LibState::default(),
tags: AHashSet::default(),
cached: IntMap::default(),
rt: user,
lambda_defs: IntMap::default(),
deferred_checks: Vec::new(),
references: REFERENCE_SITE_POOL.take(),
module_references: MODULE_REF_SITE_POOL.take(),
scope_map: SCOPE_MAP_ENTRY_POOL.take(),
})
}
pub fn register_builtin<T: BuiltIn<R, E>>(&mut self) -> Result<()> {
match self.builtins.entry(T::NAME) {
Entry::Vacant(e) => {
e.insert((T::init, T::NEEDS_CALLSITE));
}
Entry::Occupied(_) => bail!("builtin {} is already registered", T::NAME),
}
Ok(())
}
pub fn wrap_lambda(&mut self, def: LambdaDef<R, E>) -> Value {
let id = def.id;
let v = self.lambdawrap.wrap(def);
self.lambda_defs.insert(id, v.clone());
v
}
pub fn set_var(&mut self, id: BindId, v: Value) {
self.cached.insert(id, v.clone());
self.rt.set_var(id, v)
}
fn tag(&mut self, s: &ArcStr) -> ArcStr {
match self.tags.get(s) {
Some(s) => s.clone(),
None => {
self.tags.insert(s.clone());
s.clone()
}
}
}
pub fn with_restored<T, F: FnOnce(&mut Self) -> T>(&mut self, env: Env, f: F) -> T {
let snap = self.env.restore_lexical_env(env);
let orig = mem::replace(&mut self.env, snap);
let r = f(self);
self.env = self.env.restore_lexical_env(orig);
r
}
pub fn with_restored_mut<T, F: FnOnce(&mut Self) -> T>(
&mut self,
env: &mut Env,
f: F,
) -> T {
let snap = self.env.restore_lexical_env_mut(env);
let orig = mem::replace(&mut self.env, snap);
let r = f(self);
*env = self.env.clone();
self.env = self.env.restore_lexical_env(orig);
r
}
}
#[derive(Debug, Clone)]
pub struct Scope {
pub lexical: ModPath,
pub dynamic: ModPath,
}
impl Scope {
pub fn append<S: AsRef<str> + ?Sized>(&self, s: &S) -> Self {
Self {
lexical: ModPath(self.lexical.append(s)),
dynamic: ModPath(self.dynamic.append(s)),
}
}
pub fn root() -> Self {
Self { lexical: ModPath::root(), dynamic: ModPath::root() }
}
}
pub fn compile<R: Rt, E: UserEvent>(
ctx: &mut ExecCtx<R, E>,
flags: BitFlags<CFlag>,
scope: &Scope,
spec: Expr,
) -> Result<Node<R, E>> {
let top_id = spec.id;
let env = ctx.env.clone();
let st = Instant::now();
let mut node = match compiler::compile(ctx, flags, spec, scope, top_id) {
Ok(n) => n,
Err(e) => {
ctx.env = env;
return Err(e);
}
};
info!("compile time {:?}", st.elapsed());
let st = Instant::now();
if let Err(e) = node.typecheck(ctx) {
ctx.env = env;
return Err(e);
}
while let Some(check) = ctx.deferred_checks.pop() {
if let Err(e) = check(ctx) {
ctx.env = env;
return Err(e);
}
}
info!("typecheck time {:?}", st.elapsed());
Ok(node)
}