graphix-compiler 0.9.0

A dataflow language for UIs and network programming, compiler
Documentation
use crate::{
    compiler::compile,
    deref_typ,
    env::Env,
    expr::{self, Expr, ExprId, ModPath},
    format_with_flags,
    typ::{Type, TypeRef},
    wrap, BindId, CFlag, Event, ExecCtx, Node, PrintFlag, Refs, Rt, Scope, Update,
    UserEvent,
};
use anyhow::{anyhow, bail, Result};
use arcstr::{literal, ArcStr};
use compact_str::format_compact;
use enumflags2::BitFlags;
use netidx_value::{Typ, Value};
use poolshark::local::LPooled;
use std::{collections::hash_map::Entry, sync::LazyLock};
use triomphe::Arc;

pub(super) static ECHAIN: LazyLock<ModPath> =
    LazyLock::new(|| ModPath::from(["ErrChain"]));

fn typ_echain(param: Type) -> Type {
    Type::Ref (TypeRef {
        scope: ModPath::root(),
        name: ECHAIN.clone(),
        params: Arc::from_iter([param]),
     ..Default::default()})
}

pub(super) fn wrap_error(env: &Env, spec: &Expr, e: Value) -> Value {
    static ERRCHAIN: LazyLock<Type> = LazyLock::new(|| typ_echain(Type::empty_tvar()));
    let pos: Value =
        [(literal!("column"), spec.pos.column), (literal!("line"), spec.pos.line)].into();
    if ERRCHAIN.is_a(env, &e) {
        let error = e.clone().cast_to::<[(ArcStr, Value); 4]>().unwrap();
        let error = error[1].1.clone();
        [
            (literal!("cause"), e.clone()),
            (literal!("error"), error),
            (literal!("ori"), spec.ori.to_value()),
            (literal!("pos"), pos),
        ]
        .into()
    } else {
        [
            (literal!("cause"), Value::Null),
            (literal!("error"), e.clone()),
            (literal!("ori"), spec.ori.to_value()),
            (literal!("pos"), pos),
        ]
        .into()
    }
}

#[derive(Debug)]
pub(crate) struct TryCatch<R: Rt, E: UserEvent> {
    spec: Expr,
    typ: Type,
    nodes: LPooled<Vec<Node<R, E>>>,
    handler: Node<R, E>,
}

impl<R: Rt, E: UserEvent> TryCatch<R, E> {
    pub(crate) fn new(
        ctx: &mut ExecCtx<R, E>,
        flags: BitFlags<CFlag>,
        spec: Expr,
        scope: &Scope,
        top_id: ExprId,
        tc: &Arc<expr::TryCatchExpr>,
    ) -> Result<Node<R, E>> {
        let inner_name = format_compact!("tc{}", BindId::new().inner());
        let inner_scope = scope.append(inner_name.as_str());
        let catch_name = format_compact!("ca{}", BindId::new().inner());
        let catch_scope = scope.append(catch_name.as_str());
        let typ = Type::empty_tvar();
        match &typ {
            Type::TVar(tv) => {
                let mut tv = tv.write();
                tv.frozen = true;
                *tv.typ.write() = Some(Type::Bottom)
            }
            _ => unreachable!(),
        }
        let id = ctx
            .env
            .bind_variable(&catch_scope.lexical, &tc.bind, typ, spec.pos, spec.ori.clone())
            .id;
        let handler = compile(ctx, flags, (*tc.handler).clone(), &catch_scope, top_id)?;
        ctx.env.catch.insert_cow(inner_scope.dynamic.clone(), id);
        let nodes = tc
            .exprs
            .iter()
            .map(|e| compile(ctx, flags, e.clone(), &inner_scope, top_id))
            .collect::<Result<LPooled<Vec<_>>>>()?;
        let typ =
            nodes.last().ok_or_else(|| anyhow!("empty try catch block"))?.typ().clone();
        Ok(Box::new(Self { spec, typ, nodes, handler }))
    }
}

impl<R: Rt, E: UserEvent> Update<R, E> for TryCatch<R, E> {
    fn update(&mut self, ctx: &mut ExecCtx<R, E>, event: &mut Event<E>) -> Option<Value> {
        let res = self.nodes.iter_mut().fold(None, |_, n| n.update(ctx, event));
        let _ = self.handler.update(ctx, event);
        res
    }

    fn delete(&mut self, ctx: &mut ExecCtx<R, E>) {
        for n in self.nodes.iter_mut() {
            n.delete(ctx);
        }
        self.handler.delete(ctx);
    }

    fn sleep(&mut self, ctx: &mut ExecCtx<R, E>) {
        for n in self.nodes.iter_mut() {
            n.sleep(ctx)
        }
        self.handler.sleep(ctx);
    }

    fn typecheck(&mut self, ctx: &mut ExecCtx<R, E>) -> Result<()> {
        for n in self.nodes.iter_mut() {
            wrap!(n, n.typecheck(ctx))?
        }
        wrap!(self.handler, self.handler.typecheck(ctx))
    }

    fn spec(&self) -> &Expr {
        &self.spec
    }

    fn typ(&self) -> &Type {
        &self.typ
    }

    fn refs(&self, refs: &mut Refs) {
        for n in self.nodes.iter() {
            n.refs(refs);
        }
        self.handler.refs(refs);
    }
}

#[derive(Debug)]
pub(crate) struct Qop<R: Rt, E: UserEvent> {
    spec: Expr,
    typ: Type,
    id: Option<BindId>,
    n: Node<R, E>,
}

impl<R: Rt, E: UserEvent> Qop<R, E> {
    pub(crate) fn compile(
        ctx: &mut ExecCtx<R, E>,
        flags: BitFlags<CFlag>,
        spec: Expr,
        scope: &Scope,
        top_id: ExprId,
        e: &Expr,
    ) -> Result<Node<R, E>> {
        let n = compile(ctx, flags, e.clone(), scope, top_id)?;
        let id = match ctx.env.lookup_catch(&scope.dynamic).ok() {
            None => {
                if flags.contains(CFlag::WarnUnhandled | CFlag::WarningsAreErrors) {
                    bail!(
                        "ERROR: {} at {} error raised by ? will not be caught",
                        spec.ori,
                        spec.pos
                    )
                }
                if flags.contains(CFlag::WarnUnhandled) {
                    eprintln!(
                        "WARNING: {} at {} error raised by ? will not be caught",
                        spec.ori, spec.pos
                    );
                }
                None
            }
            o => o,
        };
        let typ = Type::empty_tvar();
        Ok(Box::new(Self { spec, typ, id, n }))
    }
}

impl<R: Rt, E: UserEvent> Update<R, E> for Qop<R, E> {
    fn update(&mut self, ctx: &mut ExecCtx<R, E>, event: &mut Event<E>) -> Option<Value> {
        match self.n.update(ctx, event) {
            None => None,
            Some(Value::Error(e)) => match self.id {
                Some(id) => {
                    let e = wrap_error(&ctx.env, &self.spec, (*e).clone());
                    let v = Value::Error(Arc::new(e));
                    match event.variables.entry(id) {
                        Entry::Vacant(e) => {
                            e.insert(v);
                        }
                        Entry::Occupied(_) => ctx.set_var(id, v),
                    }
                    None
                }
                None => {
                    log::error!(
                        "unhandled error in {} at {} {e}",
                        self.spec.ori,
                        self.spec.pos
                    );
                    eprintln!(
                        "unhandled error in {} at {} {e}",
                        self.spec.ori, self.spec.pos
                    );
                    None
                }
            },
            Some(v) => Some(v),
        }
    }

    fn typ(&self) -> &Type {
        &self.typ
    }

    fn spec(&self) -> &Expr {
        &self.spec
    }

    fn refs(&self, refs: &mut Refs) {
        self.n.refs(refs)
    }

    fn delete(&mut self, ctx: &mut ExecCtx<R, E>) {
        self.n.delete(ctx)
    }

    fn sleep(&mut self, ctx: &mut ExecCtx<R, E>) {
        self.n.sleep(ctx);
    }

    fn typecheck(&mut self, ctx: &mut ExecCtx<R, E>) -> Result<()> {
        fn fix_echain_typ<R: Rt, E: UserEvent>(
            ctx: &ExecCtx<R, E>,
            etyp: &Type,
        ) -> Result<Type> {
            deref_typ!("error", ctx, etyp,
                Some(Type::Primitive(p)) => {
                    if !p.contains(Typ::Error) {
                        bail!("expected error not {}", Type::Primitive(*p))
                    }
                    if *p == BitFlags::from(Typ::Error) {
                        Ok(Type::Error(Arc::new(typ_echain(Type::Any))))
                    } else {
                        let mut p = *p;
                        p.remove(Typ::Error);
                        Ok(Type::Set(Arc::from_iter([
                            Type::Error(Arc::new(typ_echain(Type::Any))),
                            Type::Primitive(p)
                        ])))
                    }
                },
                Some(Type::Error(et)) => et.with_deref(|et| match et {
                    None => bail!("type must be known"),
                    Some(Type::Ref (TypeRef { scope, name, .. }))
                        if scope == &ModPath::root() && name == &*ECHAIN =>
                    {
                        Ok(etyp.clone())
                    }
                    Some(et) => {
                        Ok(Type::Error(Arc::new(typ_echain(et.clone()))))
                    }
                }),
                Some(Type::Set(elts)) => {
                    let mut res = elts
                        .iter()
                        .map(|et| fix_echain_typ(ctx, et))
                        .collect::<Result<LPooled<Vec<Type>>>>()?;
                    Ok(Type::Set(Arc::from_iter(res.drain(..))))
                }
            )
        }
        wrap!(self.n, self.n.typecheck(ctx))?;
        let err = Type::Error(Arc::new(Type::empty_tvar()));
        if !self.n.typ().contains_with_flags(BitFlags::empty(), &ctx.env, &err)? {
            format_with_flags(PrintFlag::DerefTVars, || {
                bail!("cannot use the ? operator on non error type {}", self.n.typ())
            })?
        }
        let err = Type::Primitive(Typ::Error.into());
        let rtyp = self.n.typ().diff(&ctx.env, &err)?;
        wrap!(self, self.typ.check_contains(&ctx.env, &rtyp))?;
        if let Some(id) = self.id {
            let etyp = self.n.typ().diff(&ctx.env, &rtyp)?;
            let etyp = wrap!(self, fix_echain_typ(&ctx, &etyp))?;
            let bind = ctx.env.by_id.get(&id).ok_or_else(|| anyhow!("BUG: catch"))?;
            match &bind.typ {
                Type::TVar(tv) => {
                    let tv = tv.read();
                    let mut typ = tv.typ.write();
                    match &mut *typ {
                        None => *typ = Some(etyp.clone()),
                        Some(t) => *typ = Some(t.union(&ctx.env, &etyp)?),
                    }
                }
                _ => unreachable!(),
            }
        }
        Ok(())
    }
}

#[derive(Debug)]
pub(crate) struct OrNever<R: Rt, E: UserEvent> {
    spec: Expr,
    typ: Type,
    n: Node<R, E>,
}

impl<R: Rt, E: UserEvent> OrNever<R, E> {
    pub(crate) fn compile(
        ctx: &mut ExecCtx<R, E>,
        flags: BitFlags<CFlag>,
        spec: Expr,
        scope: &Scope,
        top_id: ExprId,
        e: &Expr,
    ) -> Result<Node<R, E>> {
        let n = compile(ctx, flags, e.clone(), scope, top_id)?;
        let typ = Type::empty_tvar();
        Ok(Box::new(Self { spec, typ, n }))
    }
}

impl<R: Rt, E: UserEvent> Update<R, E> for OrNever<R, E> {
    fn update(&mut self, ctx: &mut ExecCtx<R, E>, event: &mut Event<E>) -> Option<Value> {
        match self.n.update(ctx, event) {
            None => None,
            Some(Value::Error(e)) => {
                log::warn!("ignored error in {} at {} {e}", self.spec.ori, self.spec.pos);
                None
            }
            Some(v) => Some(v),
        }
    }

    fn typ(&self) -> &Type {
        &self.typ
    }

    fn spec(&self) -> &Expr {
        &self.spec
    }

    fn refs(&self, refs: &mut Refs) {
        self.n.refs(refs)
    }

    fn delete(&mut self, ctx: &mut ExecCtx<R, E>) {
        self.n.delete(ctx)
    }

    fn sleep(&mut self, ctx: &mut ExecCtx<R, E>) {
        self.n.sleep(ctx);
    }

    fn typecheck(&mut self, ctx: &mut ExecCtx<R, E>) -> Result<()> {
        wrap!(self.n, self.n.typecheck(ctx))?;
        let err = Type::Error(Arc::new(Type::empty_tvar()));
        if !self.n.typ().contains_with_flags(BitFlags::empty(), &ctx.env, &err)? {
            format_with_flags(PrintFlag::DerefTVars, || {
                bail!("cannot use the $ operator on non error type {}", self.n.typ())
            })?
        }
        let err = Type::Primitive(Typ::Error.into());
        let rtyp = self.n.typ().diff(&ctx.env, &err)?;
        wrap!(self, self.typ.check_contains(&ctx.env, &rtyp))?;
        Ok(())
    }
}