~jojo/effem

798d0fc4756120bac7c91e5f265f55d505a65211 — JoJo 3 months ago 841c407
add more binops & overload binops for all numeric types
11 files changed, 500 insertions(+), 125 deletions(-)

M src/abase.rs
M src/cache.rs
M src/check.rs
M src/diag.rs
M src/eval.rs
M src/fem.rs
M src/lex.rs
M src/main.rs
M src/name.rs
M src/parse.rs
M src/resolve.rs
M src/abase.rs => src/abase.rs +108 -21
@@ 43,10 43,24 @@ pub enum Return {

#[derive(Clone, Debug)]
pub enum Expr {
    Add(Operand, Operand),
    Mul(Operand, Operand),
    Binop(Binop, Operand, Operand),
    Call(Operand, Vec<Operand>),
    Operand(Operand),
    If(Operand, Block<Box<Expr>>, Block<Box<Expr>>),
}
#[derive(Debug, Clone, Copy)]
pub enum Binop {
    Add,
    Sub,
    Mul,
    Quot,
    Rem,
    Lt,
    Gt,
    Leq,
    Geq,
    Eq,
    Neq,
}

#[derive(Clone, Debug)]


@@ 57,8 71,9 @@ pub enum Operand {
    Global(DefId),
}

#[derive(Clone, Debug)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum Const {
    Bool(bool),
    I8(i8),
    I16(i16),
    I32(i32),


@@ 72,6 87,59 @@ pub enum Const {
    F64(f64),
}

macro_rules! impl_const_from {
    ($t:ty, $c:expr) => {
        impl From<$t> for Const {
            fn from(x: $t) -> Const {
                $c(x)
            }
        }
    };
}

impl_const_from!(bool, Const::Bool);
impl_const_from!(i8, Const::I8);
impl_const_from!(i16, Const::I16);
impl_const_from!(i32, Const::I32);
impl_const_from!(i64, Const::I64);
impl_const_from!(isize, Const::Int);
impl_const_from!(u8, Const::N8);
impl_const_from!(u16, Const::N16);
impl_const_from!(u32, Const::N32);
impl_const_from!(u64, Const::N64);
impl_const_from!(usize, Const::Nat);
impl_const_from!(f64, Const::F64);

macro_rules! impl_const_arithm_binop {
    ($op_trait:ty, $op_name:ident) => {
        impl $op_trait for Const {
            type Output = Option<Const>;
            fn $op_name(self, other: Self) -> Self::Output {
                match (self, other) {
                    (Const::I8(x), Const::I8(y)) => Some(x.$op_name(y).into()),
                    (Const::I16(x), Const::I16(y)) => Some(x.$op_name(y).into()),
                    (Const::I32(x), Const::I32(y)) => Some(x.$op_name(y).into()),
                    (Const::I64(x), Const::I64(y)) => Some(x.$op_name(y).into()),
                    (Const::Int(x), Const::Int(y)) => Some(x.$op_name(y).into()),
                    (Const::N8(x), Const::N8(y)) => Some(x.$op_name(y).into()),
                    (Const::N16(x), Const::N16(y)) => Some(x.$op_name(y).into()),
                    (Const::N32(x), Const::N32(y)) => Some(x.$op_name(y).into()),
                    (Const::N64(x), Const::N64(y)) => Some(x.$op_name(y).into()),
                    (Const::Nat(x), Const::Nat(y)) => Some(x.$op_name(y).into()),
                    (Const::F64(x), Const::F64(y)) => Some(x.$op_name(y).into()),
                    _ => None,
                }
            }
        }
    };
}

impl_const_arithm_binop!(std::ops::Add, add);
impl_const_arithm_binop!(std::ops::Sub, sub);
impl_const_arithm_binop!(std::ops::Mul, mul);
impl_const_arithm_binop!(std::ops::Div, div);
impl_const_arithm_binop!(std::ops::Rem, rem);

pub fn abase_def(cache: &mut Cache, rhs: &FExpr) -> Result<GlobDef> {
    AbaseDef::new(cache).run(rhs)
}


@@ 111,6 179,7 @@ impl<'c> AbaseDef<'c> {
    fn abase(&mut self, expr: &FExpr) -> Result<Expr> {
        use crate::fem::ExprKind::*;
        match &expr.kind {
            Bool(x) => Ok(Expr::Operand(Operand::Const(Const::Bool(*x)))),
            &Int(x) => Ok(Expr::Operand(Operand::Const(match expr.typ {
                Type::Int { width: 8, signed: true } => Const::I8(x as i8),
                Type::Int { width: 16, signed: true } => Const::I16(x as i16),


@@ 129,20 198,17 @@ impl<'c> AbaseDef<'c> {
            Fun(ps, b) => Ok(Expr::Operand(Operand::Local(self.abase_fun(ps, b)?))),
            App(f, xs) => match &f.kind {
                Var(ResName { res: Res::Prim(p) }) => match p {
                    Prim::Add | Prim::AddF => {
                        let x = self.abase(&xs[0])?;
                        let x = self.let_anon_soft(x)?;
                        let y = self.abase(&xs[1])?;
                        let y = self.let_anon_soft(y)?;
                        Ok(Expr::Add(x, y))
                    }
                    Prim::Mul | Prim::MulF => {
                        let x = self.abase(&xs[0])?;
                        let x = self.let_anon_soft(x)?;
                        let y = self.abase(&xs[1])?;
                        let y = self.let_anon_soft(y)?;
                        Ok(Expr::Mul(x, y))
                    }
                    Prim::Add => self.abase_binop(xs, Binop::Add),
                    Prim::Sub => self.abase_binop(xs, Binop::Sub),
                    Prim::Mul => self.abase_binop(xs, Binop::Mul),
                    Prim::Quot => self.abase_binop(xs, Binop::Quot),
                    Prim::Rem => self.abase_binop(xs, Binop::Rem),
                    Prim::Lt => self.abase_binop(xs, Binop::Lt),
                    Prim::Gt => self.abase_binop(xs, Binop::Gt),
                    Prim::Leq => self.abase_binop(xs, Binop::Leq),
                    Prim::Geq => self.abase_binop(xs, Binop::Geq),
                    Prim::Eq => self.abase_binop(xs, Binop::Eq),
                    Prim::Neq => self.abase_binop(xs, Binop::Neq),
                },
                _ => {
                    let f = self.abase(f)?;


@@ 166,16 232,30 @@ impl<'c> AbaseDef<'c> {
            Var(ResName { res: Res::Prim(_) }) => todo!(), // TODO: generate a closure around the op or smth
            Var(ResName { res: Res::Module(_) }) =>
                panic!("ice: found module id in expr context when abasing. Should've been caught by type checker."),
            If(pred, conseq, alt) => {
                let pred_a = self.abase(pred)?;
                let pred_a = self.let_anon_soft(pred_a)?;
                let conseq_a = self.enter_block(|self_| self_.abase(conseq).map(Box::new))?;
                let alt_a = self.enter_block(|self_| self_.abase(alt).map(Box::new))?;
                Ok(Expr::If(pred_a, conseq_a, alt_a))
            }
        }
    }

    fn abase_binop(&mut self, xs: &[FExpr], op: Binop) -> Result<Expr> {
        let x = self.abase(&xs[0])?;
        let x = self.let_anon_soft(x)?;
        let y = self.abase(&xs[1])?;
        let y = self.let_anon_soft(y)?;
        Ok(Expr::Binop(op, x, y))
    }

    // TODO: closures. capture free vars etc.
    fn abase_fun(&mut self, params: &[(PubIdent, LocalId)], body: &FExpr) -> Result<LocalId> {
        let fid = self.gen_local();

        let old_defs = std::mem::take(&mut self.defs);
        let old_vars = std::mem::take(&mut self.vars);
        let old_stms = std::mem::take(&mut self.stms);

        let params = params
            .iter()


@@ 185,18 265,25 @@ impl<'c> AbaseDef<'c> {
            })
            .collect::<Vec<_>>();
        self.vars.extend(&params);
        let term = self.abase(body)?;

        let body = self.enter_block(|self_| self_.abase(body).map(Return::Val))?;

        let new_defs = std::mem::replace(&mut self.defs, old_defs);
        self.vars = old_vars;
        let new_stms = std::mem::replace(&mut self.stms, old_stms);

        let def = FunDef { defs: new_defs, params, body: Block { stms: new_stms, term: Return::Val(term) } };
        let def = FunDef { defs: new_defs, params, body };
        self.defs.push((fid, GlobDef::FunDef(def)));

        Ok(fid)
    }

    fn enter_block<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<Block<T>> {
        let old_stms = std::mem::take(&mut self.stms);
        let term = f(self)?;
        let new_stms = std::mem::replace(&mut self.stms, old_stms);
        Ok(Block { stms: new_stms, term })
    }

    fn gen_local(&mut self) -> LocalId {
        assert!(self.n_locals < u32::MAX);
        self.n_locals += 1;

M src/cache.rs => src/cache.rs +18 -4
@@ 120,9 120,16 @@ impl Cache {
    pub fn fetch_module_local_resolved_names(&mut self, module_id: ModuleId) -> Result<&HashMap<String, Res>> {
        static PRELUDE: &[(&str, Res)] = &[
            ("+", Res::Prim(Prim::Add)),
            ("-", Res::Prim(Prim::Sub)),
            ("*", Res::Prim(Prim::Mul)),
            ("+f", Res::Prim(Prim::AddF)),
            ("*f", Res::Prim(Prim::MulF)),
            ("quot", Res::Prim(Prim::Quot)),
            ("rem", Res::Prim(Prim::Rem)),
            ("<", Res::Prim(Prim::Lt)),
            (">", Res::Prim(Prim::Gt)),
            ("<=", Res::Prim(Prim::Leq)),
            (">=", Res::Prim(Prim::Geq)),
            ("=", Res::Prim(Prim::Eq)),
            ("/=", Res::Prim(Prim::Neq)),
        ];

        if self.module_local_resolved_names.contains_key(&module_id) {


@@ 148,9 155,16 @@ impl Cache {
            }
            match sname.get(1).unwrap().as_str() {
                "+" => Ok(Res::Prim(Prim::Add)),
                "-" => Ok(Res::Prim(Prim::Sub)),
                "*" => Ok(Res::Prim(Prim::Mul)),
                "+f" => Ok(Res::Prim(Prim::AddF)),
                "*f" => Ok(Res::Prim(Prim::MulF)),
                "quot" => Ok(Res::Prim(Prim::Quot)),
                "rem" => Ok(Res::Prim(Prim::Rem)),
                "<" => Ok(Res::Prim(Prim::Lt)),
                ">" => Ok(Res::Prim(Prim::Gt)),
                "<=" => Ok(Res::Prim(Prim::Leq)),
                ">=" => Ok(Res::Prim(Prim::Geq)),
                "=" => Ok(Res::Prim(Prim::Eq)),
                "/=" => Ok(Res::Prim(Prim::Neq)),
                _ => todo!(), // Err
            }
        } else if let Some(&res) = self.resolved_names.get(&sname) {

M src/check.rs => src/check.rs +124 -34
@@ 1,7 1,7 @@
use crate::prelude::*;
use diag::TypeErr::*;
use fem::*;
use resolve::Expr as RExpr;
use resolve::{Expr as RExpr, Type as RType};
use std::collections::HashMap;

pub fn check_def(cache: &mut Cache, body: &RExpr) -> Result<Expr> {


@@ 25,6 25,7 @@ impl<'c> CheckDef<'c> {
    fn infer(&mut self, expr: &RExpr) -> Result<Expr> {
        use resolve::ExprKind as Rek;
        match &expr.kind {
            &Rek::Bool(x) => Ok(Expr { typ: Type::F64, kind: ExprKind::Bool(x) }),
            Rek::Int(x) => Ok(Expr {
                typ: if (isize::MIN as i128..=isize::MAX as i128).contains(x) {
                    Type::ISize


@@ 35,25 36,42 @@ impl<'c> CheckDef<'c> {
                },
                kind: ExprKind::Int(*x),
            }),
            Rek::F64(x) => Ok(Expr { typ: Type::F64, kind: ExprKind::F64(*x) }),
            &Rek::F64(x) => Ok(Expr { typ: Type::F64, kind: ExprKind::F64(x) }),
            Rek::Fun(ps, b) if ps.is_empty() => {
                let bi = self.infer(b)?;
                Ok(Expr { typ: Type::Fun(vec![], Box::new(bi.typ.clone())), kind: ExprKind::Fun(vec![], Box::new(bi)) })
            }
            Rek::Fun(_, _) => err(InferLambda(expr.loc)),
            Rek::App(f, args) => {
                let fi = self.infer(f)?;
                if let Type::Fun(ps, r) = &fi.typ {
                    if ps.len() == args.len() {
                        let argsc = ps.iter().zip(args).map(|(p, a)| self.check(a, p)).collect::<Result<Vec<_>>>()?;
                        Ok(Expr { typ: (**r).clone(), kind: ExprKind::App(Box::new(fi), argsc) })
            Rek::App(f, args) => match f.kind {
                Rek::Fun(_, _) | Rek::Var(ResName { res: Res::Prim(_) }) => {
                    let argsi = args.iter().map(|a| self.infer(a)).collect::<Result<Vec<_>>>()?;
                    let t_fexpected =
                        RType::Fun(argsi.iter().map(|e| e.typ.to_parsed()).collect(), Box::new(RType::Hole));
                    let fc = self.check(f, &t_fexpected)?;
                    let typ = match &fc.typ {
                        Type::Fun(_, t_ret) => (**t_ret).clone(),
                        _ => panic!("ice: checked function of application should be Type::Fun, is {:?}", fc.typ),
                    };
                    Ok(Expr { typ, kind: ExprKind::App(Box::new(fc), argsi) })
                }
                _ => {
                    let fi = self.infer(f)?;
                    if let Type::Fun(ps, r) = &fi.typ {
                        if ps.len() == args.len() {
                            let argsc = ps
                                .iter()
                                .zip(args)
                                .map(|(p, a)| self.check(a, &p.to_parsed()))
                                .collect::<Result<Vec<_>>>()?;
                            Ok(Expr { typ: (**r).clone(), kind: ExprKind::App(Box::new(fi), argsc) })
                        } else {
                            err(ArityMisApp { loc: expr.loc, params: ps.len(), args: args.len() })
                        }
                    } else {
                        err(ArityMisApp { loc: expr.loc, params: ps.len(), args: args.len() })
                        err(AppNonFun(expr.loc, fi.typ))
                    }
                } else {
                    err(AppNonFun(expr.loc, fi.typ))
                }
            }
            },
            Rek::Var(r @ ResName { res: Res::Def(id) }) => {
                let typ = self.cache.fetch_checked(*id).map(|Expr { typ, .. }| typ.clone())?;
                Ok(Expr { typ, kind: ExprKind::Var(r.clone()) })


@@ 62,58 80,121 @@ impl<'c> CheckDef<'c> {
                Some(t) => Ok(Expr { typ: t.clone(), kind: ExprKind::Var(r.clone()) }),
                None => panic!("ice: undefined local var of id {id}"),
            },
            Rek::Var(r @ ResName { res: Res::Prim(Prim::Add | Prim::Mul) }) => Ok(Expr {
                typ: Type::Fun(vec![Type::ISize, Type::ISize], Box::new(Type::ISize)),
                kind: ExprKind::Var(r.clone()),
            }),
            Rek::Var(r @ ResName { res: Res::Prim(Prim::AddF | Prim::MulF) }) => Ok(Expr {
                typ: Type::Fun(vec![Type::F64, Type::F64], Box::new(Type::F64)),
            Rek::Var(r @ ResName { res: Res::Prim(Prim::Add | Prim::Sub | Prim::Mul | Prim::Quot | Prim::Rem) }) =>
                Ok(Expr {
                    typ: Type::Fun(vec![Type::ISize, Type::ISize], Box::new(Type::ISize)),
                    kind: ExprKind::Var(r.clone()),
                }),
            Rek::Var(
                r @ ResName { res: Res::Prim(Prim::Lt | Prim::Gt | Prim::Leq | Prim::Geq | Prim::Eq | Prim::Neq) },
            ) => Ok(Expr {
                typ: Type::Fun(vec![Type::ISize, Type::ISize], Box::new(Type::Bool)),
                kind: ExprKind::Var(r.clone()),
            }),
            Rek::Var(ResName { res: Res::Module(id) }) => err(ModuleIsNotExpr(expr.loc, *id)),
            Rek::Annot(e, t) => self.check(e, t),
            Rek::If(p, c, a) => {
                let pc = self.check(p, &RType::Bool)?;
                let ci = self.infer(c)?;
                // TODO: On error, add context / note about what type was inferred for the prev branch
                let ac = self.check(a, &ci.typ.to_parsed())?;
                Ok(Expr { typ: ci.typ.clone(), kind: ExprKind::If(Box::new(pc), Box::new(ci), Box::new(ac)) })
            }
        }
    }

    fn check(&mut self, expr: &RExpr, expected: &Type) -> Result<Expr> {
    fn check(&mut self, expr: &RExpr, expected: &RType) -> Result<Expr> {
        use resolve::ExprKind as Rek;
        match &expr.kind {
            _ if matches!(expected, RType::Hole) => self.infer(expr),
            &Rek::Int(x) => {
                match expected {
                    Type::Int { width: 128, signed: true } if i128::MIN <= x && x <= i128::MAX => (),
                    Type::Int { width: 128, signed: false } if 0 <= x && x as u128 <= u128::MAX => (),
                    Type::Int { width, signed: true } if -(1 << (width - 1)) <= x && x < (1 << (width - 1)) => (),
                    Type::Int { width, signed: false } if 0 <= x && x < (1 << width) => (),
                    Type::ISize if isize::MIN as i128 <= x && x <= isize::MAX as i128 => (),
                    Type::NSize if 0 <= x && x as u128 <= usize::MAX as u128 => (),
                    _ => return err(ExpectedFound(expr.loc, expected.clone(), self.infer(expr)?.typ)),
                }
                Ok(Expr { typ: expected.clone(), kind: ExprKind::Int(x) })
                    RType::Int { width: 128, signed: true } => (),
                    RType::Int { width: 128, signed: false } if 0 <= x => (),
                    RType::Int { width, signed: true } if -(1 << (width - 1)) <= x && x < (1 << (width - 1)) => (),
                    RType::Int { width, signed: false } if 0 <= x && x < (1 << width) => (),
                    RType::ISize if isize::MIN as i128 <= x && x <= isize::MAX as i128 => (),
                    RType::NSize if 0 <= x && x as u128 <= usize::MAX as u128 => (),
                    RType::Int { .. } | RType::ISize | RType::NSize =>
                        return err(ExpectedFoundOutOfRange(expr.loc, expected.clone())),
                    _ => return err(ExpectedFound(expr.loc, expected.clone(), self.infer(expr)?.typ.to_parsed())),
                };
                Ok(Expr { typ: expected.to_fem().unwrap(), kind: ExprKind::Int(x) })
            }
            Rek::Fun(ps, b) =>
                if let Type::Fun(tps, tr) = expected {
                if let RType::Fun(tps, tr) = expected {
                    if ps.len() != tps.len() {
                        return err(ArityMisLambda { loc: expr.loc, check: tps.len(), lit: ps.len() });
                    }
                    self.scopes.push(ps.iter().zip(tps).map(|((_, id), t)| (*id, t.clone())).collect());
                    let tps = ps
                        .iter()
                        .zip(tps)
                        .map(|((ident, _), t)| t.to_fem().ok_or_else(|| InferLambdaParam(ident.clone())))
                        .collect::<std::result::Result<Vec<_>, _>>()?;
                    self.scopes.push(ps.iter().map(|(_, id)| *id).zip(tps.iter().cloned()).collect());
                    let bc = self.check(b, tr)?;
                    self.scopes.pop();
                    Ok(Expr { typ: expected.clone(), kind: ExprKind::Fun(ps.clone(), Box::new(bc)) })
                    Ok(Expr {
                        typ: Type::Fun(tps, Box::new(bc.typ.clone())),
                        kind: ExprKind::Fun(ps.clone(), Box::new(bc)),
                    })
                } else {
                    err(ExpectedFoundLambda(expr.loc, expected.clone()))
                },
            Rek::Var(r @ ResName { res: Res::Prim(prim) }) => match prim {
                Prim::Add | Prim::Sub | Prim::Mul | Prim::Quot | Prim::Rem =>
                    if let RType::Fun(tps, tr) = expected {
                        if tps.len() != 2 {
                            return err(ArityMisLambda { loc: expr.loc, check: tps.len(), lit: 2 });
                        }
                        let tx = tps.iter().find(|t| !matches!(t, RType::Hole)).unwrap_or(tr).or(&RType::ISize);
                        if tps.iter().any(|t| !subtype(tx, t)) || !subtype(tx, tr) {
                            return err(ExpectedFoundArithmBinop(expr.loc, expected.clone()));
                        }
                        let tx = tx.to_fem().unwrap();
                        Ok(Expr {
                            typ: Type::Fun(vec![tx.clone(), tx.clone()], Box::new(tx)),
                            kind: ExprKind::Var(r.clone()),
                        })
                    } else {
                        err(ExpectedFoundArithmBinop(expr.loc, expected.clone()))
                    },
                Prim::Lt | Prim::Gt | Prim::Leq | Prim::Geq | Prim::Eq | Prim::Neq =>
                    if let RType::Fun(tps, tr) = expected {
                        if tps.len() != 2 {
                            return err(ArityMisLambda { loc: expr.loc, check: tps.len(), lit: 2 });
                        }
                        let tp = tps.iter().find(|t| !matches!(t, RType::Hole)).unwrap_or(&RType::ISize);
                        if tps.iter().any(|t| !subtype(tp, t)) || !subtype(&RType::Bool, tr) {
                            return err(ExpectedFoundLogicBinop(expr.loc, expected.clone()));
                        }
                        let tp = tp.to_fem().unwrap();
                        Ok(Expr {
                            typ: Type::Fun(vec![tp.clone(), tp.clone()], Box::new(Type::Bool)),
                            kind: ExprKind::Var(r.clone()),
                        })
                    } else {
                        err(ExpectedFoundLogicBinop(expr.loc, expected.clone()))
                    },
            },
            Rek::Annot(e, t) =>
                if t == expected {
                if subtype(t, expected) {
                    self.check(e, t)
                } else {
                    err(ExpectedFoundAnnot(expr.loc, expected.clone(), t.clone()))
                },
            Rek::If(p, c, a) => {
                let p_c = self.check(p, &RType::Bool)?;
                let c_c = self.check(c, expected)?;
                let a_c = self.check(a, expected)?;
                Ok(Expr { typ: c_c.typ.clone(), kind: ExprKind::If(Box::new(p_c), Box::new(c_c), Box::new(a_c)) })
            }
            _ => {
                let expri = self.infer(expr)?;
                if &expri.typ == expected {
                if subtype(&expri.typ.to_parsed(), expected) {
                    Ok(expri)
                } else {
                    err(ExpectedFound(expr.loc, expected.clone(), expri.typ))
                    err(ExpectedFound(expr.loc, expected.clone(), expri.typ.to_parsed()))
                }
            }
        }


@@ 124,6 205,15 @@ impl<'c> CheckDef<'c> {
    }
}

fn subtype(t: &RType, u: &RType) -> bool {
    match (t, u) {
        (_, RType::Hole) => true,
        (RType::Fun(tps, tr), RType::Fun(ups, ur)) =>
            tps.iter().zip(ups).all(|(t, u)| subtype(t, u)) && subtype(tr, ur),
        _ => t == u,
    }
}

fn err<A>(c: TypeErr) -> Result<A> {
    Err(Error::Type(c))
}

M src/diag.rs => src/diag.rs +21 -5
@@ 1,4 1,5 @@
use crate::prelude::*;
use fem::Type as FType;
use resolve::Type as RType;
use std::collections::HashSet;
use std::io::*;


@@ 51,10 52,14 @@ pub enum TypeErr {
    ExpectedFound(Loc, RType, RType),
    ExpectedFoundAnnot(Loc, RType, RType),
    ExpectedFoundLambda(Loc, RType),
    ExpectedFoundArithmBinop(Loc, RType),
    ExpectedFoundLogicBinop(Loc, RType),
    ExpectedFoundOutOfRange(Loc, RType),
    ArityMisLambda { loc: Loc, check: usize, lit: usize },
    ArityMisApp { loc: Loc, params: usize, args: usize },
    InferLambda(Loc),
    AppNonFun(Loc, RType),
    InferLambdaParam(PubIdent),
    AppNonFun(Loc, FType),
    ModuleIsNotExpr(Loc, ModuleId),
}



@@ 63,11 68,15 @@ impl TypeErr {
        use TypeErr::*;
        match self {
            ExpectedFound(loc, e, f) => loc.error(cache, format!("Expected `{e}`, found `{f}`.")),
            ExpectedFoundAnnot(loc, e, f) => loc.error(cache, format!("Expected {e:?}, found {f:?} (according to annotation).")),
            ExpectedFoundLambda(loc, e) => loc.error(cache, format!("Expected {e:?}, found a function.")),
            ExpectedFoundAnnot(loc, e, f) => loc.error(cache, format!("Expected {e}, found {f} (according to annotation).")),
            ExpectedFoundLambda(loc, e) => loc.error(cache, format!("Expected {e}, found a function.")),
            ExpectedFoundArithmBinop(loc, e) => loc.error(cache, format!("Expected {e}, found an arithmetic binary operator.")),
            ExpectedFoundLogicBinop(loc, e) => loc.error(cache, format!("Expected {e}, found a logic binary operator.")),
            ExpectedFoundOutOfRange(loc, e) => loc.error(cache, format!("Expected {e}, found integer out of range for that type.")),
            ArityMisLambda { loc, check, lit } => loc.error(cache, format!("Arity mismatch in function parameters. Expectectations from the outside are that there be {check} parameters, but the function here instead has {lit}.")),
            ArityMisApp { loc, params, args } => loc.error(cache, format!("Arity mismatch in function application. Function specifies {params} parameters, but {args} arguments were given.")),
            InferLambda(loc) => loc.error(cache, "Can't infer type of lambdas with >0 parameters (yet). Please surround the lambda in a type annotation to have the type checked instead of inferred."),
            InferLambda(loc) => loc.error(cache, "Can't infer type of lambdas without any additional type information (yet). Please surround the lambda in a type annotation to have the type checked instead of inferred."),
            InferLambdaParam(ident) => ident.loc.error(cache, "Can't infer type of lambda parameter (yet). Maybe surround the lambda in a type annotation to have the type checked instead of inferred."),
            AppNonFun(loc, t) => loc.error(cache, format!("Expected a function to apply, found a {t}.")),
            ModuleIsNotExpr(loc, _mid) => loc.error(cache, "Expected an expression, but this name resolves to a module."),
        }


@@ 110,12 119,19 @@ pub struct ParseErr {

impl ParseErr {
    pub fn render(&self, cache: &mut Cache) -> String {
        use std::fmt::Write;
        self.loc.error(
            cache,
            if self.expecteds.len() == 1 {
                format!("expected {}", self.expecteds.iter().next().unwrap())
            } else {
                format!("expected one of {:?}", self.expecteds)
                let mut s = "expected one of ".to_string();
                let mut expecteds = self.expecteds.iter();
                write!(s, "{}", expecteds.next().unwrap()).unwrap();
                for x in expecteds {
                    write!(s, " | {}", x).unwrap()
                }
                s
            },
        )
    }

M src/eval.rs => src/eval.rs +32 -13
@@ 34,30 34,49 @@ impl<'c, 'd> EvalDef<'c, 'd> {
    }

    fn run_var(mut self, body: &Block<Operand>) -> Result<Operand> {
        for stm in &body.stms {
            self.eval_stm(stm)?;
        }
        self.eval_stms(&body.stms)?;
        self.eval_operand(&body.term)
    }

    fn eval(&mut self, expr: &Expr) -> Result<Operand> {
        use abase::Const::*;
        use Binop::*;
        use Operand::*;
        match expr {
            Expr::Add(x, y) => match (self.eval_operand(x)?, self.eval_operand(y)?) {
                (Operand::Const(Const::F64(x)), Operand::Const(Const::F64(y))) => Ok(Operand::Const(Const::F64(x + y))),
                (Operand::Const(Const::Int(x)), Operand::Const(Const::Int(y))) => Ok(Operand::Const(Const::Int(x + y))),
                (xe, ye) => panic!("ice: cannot add {xe:?} + {ye:?} ({x:?} + {y:?})"),
            },
            Expr::Mul(x, y) => match (self.eval_operand(x)?, self.eval_operand(y)?) {
                (Operand::Const(Const::F64(x)), Operand::Const(Const::F64(y))) => Ok(Operand::Const(Const::F64(x * y))),
                (Operand::Const(Const::Int(x)), Operand::Const(Const::Int(y))) => Ok(Operand::Const(Const::Int(x * y))),
                (xe, ye) => panic!("ice: cannot add {xe:?} * {ye:?} ({x:?} * {y:?})"),
            Expr::Binop(op, x, y) => match (self.eval_operand(x)?, self.eval_operand(y)?) {
                (Const(x), Const(y)) => Ok(Const(match op {
                    Add => (x + y).unwrap(),
                    Sub => (x - y).unwrap(),
                    Mul => (x * y).unwrap(),
                    Quot => (x / y).unwrap(),
                    Rem => (x % y).unwrap(),
                    Lt => Bool(x < y),
                    Gt => Bool(x > y),
                    Leq => Bool(x <= y),
                    Geq => Bool(x >= y),
                    Eq => Bool(x == y),
                    Neq => Bool(x != y),
                })),
                (x_e, y_e) => panic!("ice: cannot apply binary operation {op:?} to {x_e:?} and {y_e:?}"),
            },
            Expr::Call(f, xs) => match self.eval_operand(f)? {
                Operand::Global(fid) => self.cache.fetch_evaluated_at(fid, xs),
                Global(fid) => self.cache.fetch_evaluated_at(fid, xs),
                fe => panic!("ice: applying non-function {fe:?} ({f:?})"),
            },
            Expr::Operand(rand) => self.eval_operand(rand),
            Expr::If(pred, conseq, alt) => match self.eval_operand(pred)? {
                Const(Bool(true)) => self.eval_stms(&conseq.stms).and_then(|()| self.eval(&conseq.term)),
                Const(Bool(false)) => self.eval_stms(&alt.stms).and_then(|()| self.eval(&alt.term)),
                pe => panic!("ice: `if` expects const bool predicate, found {pe:?}"),
            },
        }
    }

    fn eval_stms(&mut self, stms: &[Stm]) -> Result<()> {
        for stm in stms {
            self.eval_stm(stm)?;
        }
        Ok(())
    }

    fn eval_stm(&mut self, stm: &Stm) -> Result<()> {

M src/fem.rs => src/fem.rs +32 -1
@@ 1,7 1,36 @@
//! Effem core IR

use crate::prelude::*;
pub use parse::Type;

#[derive(Debug, Clone, PartialEq)]
pub enum Type {
    Bool,
    Int { width: u8, signed: bool },
    ISize,
    NSize,
    F64,
    Fun(Vec<Type>, Box<Type>),
}

impl Type {
    pub fn to_parsed(&self) -> parse::Type {
        match *self {
            Type::Bool => parse::Type::Bool,
            Type::Int { width, signed } => parse::Type::Int { width, signed },
            Type::ISize => parse::Type::ISize,
            Type::NSize => parse::Type::NSize,
            Type::F64 => parse::Type::F64,
            Type::Fun(ref ps, ref r) =>
                parse::Type::Fun(ps.iter().map(|t| t.to_parsed()).collect(), Box::new(r.to_parsed())),
        }
    }
}

impl std::fmt::Display for Type {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.to_parsed().fmt(f)
    }
}

#[derive(Debug, Clone)]
pub struct Expr {


@@ 11,9 40,11 @@ pub struct Expr {

#[derive(Debug, Clone)]
pub enum ExprKind {
    Bool(bool),
    Int(i128),
    F64(f64),
    Fun(Vec<(PubIdent, LocalId)>, Box<Expr>),
    App(Box<Expr>, Vec<Expr>),
    Var(ResName),
    If(Box<Expr>, Box<Expr>, Box<Expr>),
}

M src/lex.rs => src/lex.rs +2 -2
@@ 63,9 63,9 @@ impl<'s> Scanner<'s> {
            Some(if let Some('.') = self.peek() {
                self.consume();
                if let Some(y) = self.maybe_term_nospace()? {
                    Token { offset: x.offset.clone(), tok: Tok::DotIn(Box::new(x), Box::new(y)) }
                    Token { offset: x.offset, tok: Tok::DotIn(Box::new(x), Box::new(y)) }
                } else {
                    Token { offset: x.offset.clone(), tok: Tok::DotPost(Box::new(x)) }
                    Token { offset: x.offset, tok: Tok::DotPost(Box::new(x)) }
                }
            } else {
                x

M src/main.rs => src/main.rs +42 -18
@@ 74,6 74,7 @@ mod test {
    use abase::*;
    use diag::Result;
    use eval::*;
    use std::assert_matches::assert_matches;

    fn run_tmp(main_src: &str) -> Result<Operand> {
        let dir = tempfile::tempdir().unwrap();


@@ 108,28 109,26 @@ mod test {

    #[test]
    fn test_main_literal() {
        assert!(matches!(run_tmp("(def main 123.456)"), Ok(Operand::Const(Const::F64(x))) if x == 123.456))
        assert_matches!(run_tmp("(def main 123.456)"), Ok(Operand::Const(Const::F64(x))) if x == 123.456)
    }

    #[test]
    fn test_main_arithm1() {
        assert!(matches!(run_tmp("(def main (+f.prim. 1.0 2.0))"), Ok(Operand::Const(Const::F64(x))) if x == 3.0))
        assert_matches!(run_tmp("(def main (+.prim. 1.0 2.0))"), Ok(Operand::Const(Const::F64(x))) if x == 3.0)
    }

    #[test]
    fn test_main_arithm2() {
        assert!(
            matches!(run_tmp("(def main (+f.prim. (*f.prim. 13.0 100.0) 37.0))"), Ok(Operand::Const(Const::F64(x))) if x == 1337.0)
        )
        assert_matches!(run_tmp("(def main (+.prim. (*.prim. 13.0 100.0) 37.0))"), Ok(Operand::Const(Const::F64(x))) if x == 1337.0)
    }

    #[test]
    fn test_main_arithm_var() {
        let src = "
            (def x 1300.0)
            (def main (+f x 37.0))
            (def main (+ x 37.0))
        ";
        assert!(matches!(run_tmp(src), Ok(Operand::Const(Const::F64(x))) if x == 1337.0))
        assert_matches!(run_tmp(src), Ok(Operand::Const(Const::F64(x))) if x == 1337.0)
    }

    #[test]


@@ 137,9 136,9 @@ mod test {
        let src = "
            (def main (double 21.0))
            (def double (of (Fun [F64] F64)
                            (fun [x] (*f x 2.0))))
                            (fun [x] (* x 2.0))))
        ";
        assert!(matches!(run_tmp(src), Ok(Operand::Const(Const::F64(x))) if x == 42.0))
        assert_matches!(run_tmp(src), Ok(Operand::Const(Const::F64(x))) if x == 42.0)
    }

    #[test]


@@ 153,21 152,21 @@ mod test {
    #[test]
    fn test_modules1() {
        let main_src = "(def main (double.double.own. 21.0))";
        let double_src = "(def double (of (Fun [F64] F64) (fun [x] (*f x 2.0))))";
        assert!(matches!(run_tmp_multi(&["own", "main"], &[
        let double_src = "(def double (of (Fun [F64] F64) (fun [x] (* x 2.0))))";
        assert_matches!(run_tmp_multi(&["own", "main"], &[
            ("main.fm", main_src),
            ("double.fm", double_src)
        ]), Ok(Operand::Const(Const::F64(x))) if x == 42.0))
        ]), Ok(Operand::Const(Const::F64(x))) if x == 42.0)
    }

    #[test]
    fn test_modules2() {
        let main_src = "(def main (double.own. 21.0))";
        let lib_src = "(def double (of (Fun [F64] F64) (fun [x] (*f x 2.0))))";
        assert!(matches!(run_tmp_multi(&["own", "main"], &[
        let lib_src = "(def double (of (Fun [F64] F64) (fun [x] (* x 2.0))))";
        assert_matches!(run_tmp_multi(&["own", "main"], &[
            ("main.fm", main_src),
            ("lib.fm", lib_src)
        ]), Ok(Operand::Const(Const::F64(x))) if x == 42.0))
        ]), Ok(Operand::Const(Const::F64(x))) if x == 42.0)
    }

    #[test]


@@ 175,7 174,7 @@ mod test {
        let src = "
            (def main (+ 40 2))
        ";
        assert!(matches!(run_tmp(src), Ok(Operand::Const(Const::Int(42)))))
        assert_matches!(run_tmp(src), Ok(Operand::Const(Const::Int(42))))
    }

    #[test]


@@ 183,7 182,7 @@ mod test {
        let src = "
            (def main (+ -40 -2))
        ";
        assert!(matches!(run_tmp(src), Ok(Operand::Const(Const::Int(-42)))))
        assert_matches!(run_tmp(src), Ok(Operand::Const(Const::Int(-42))))
    }

    #[test]


@@ 191,7 190,7 @@ mod test {
        let src = "
            (def main (of I8 42))
        ";
        assert!(matches!(run_tmp(src), Ok(Operand::Const(Const::I8(42)))))
        assert_matches!(run_tmp(src), Ok(Operand::Const(Const::I8(42))))
    }

    #[test]


@@ 201,4 200,29 @@ mod test {
        ";
        assert!(run_tmp(src).is_err())
    }

    #[test]
    fn test_if() {
        let src = "
            (def abs (of (Fun [Int] Int) (fun [x] (if (< x 0) (- 0 x) x))))
            (def main (+ (abs 100) (abs -23)))
        ";
        assert_matches!(run_tmp(src), Ok(Operand::Const(Const::Int(123))))
    }

    #[test]
    fn test_add_i8() {
        let src = "
            (def main ((of (Fun [I8 _] _) +) 100 23)))
        ";
        assert_matches!(run_tmp(src), Ok(Operand::Const(Const::I8(123))))
    }

    #[test]
    fn test_i8_out_of_range() {
        let src = "
            (def main (of I8 200)))
        ";
        assert_matches!(run_tmp(src), Err(_))
    }
}

M src/name.rs => src/name.rs +18 -4
@@ 54,9 54,16 @@ pub struct FileId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Prim {
    Add,
    Sub,
    Mul,
    AddF,
    MulF,
    Quot,
    Rem,
    Lt,
    Gt,
    Leq,
    Geq,
    Eq,
    Neq,
}

impl fmt::Display for Prim {


@@ 66,9 73,16 @@ impl fmt::Display for Prim {
            "{}",
            match self {
                Prim::Add => "+",
                Prim::Sub => "-",
                Prim::Mul => "*",
                Prim::AddF => "+f",
                Prim::MulF => "*f",
                Prim::Quot => "quot",
                Prim::Rem => "rem",
                Prim::Lt => "<",
                Prim::Gt => ">",
                Prim::Leq => "<=",
                Prim::Geq => ">=",
                Prim::Eq => "=",
                Prim::Neq => "/=",
            }
        )
    }

M src/parse.rs => src/parse.rs +96 -21
@@ 30,26 30,54 @@ pub struct Expr {

#[derive(Debug, Clone)]
pub enum ExprKind {
    Bool(bool),
    Int(i128),
    F64(f64),
    Fun(Vec<PrivIdent>, Box<Expr>),
    App(Box<Expr>, Vec<Expr>),
    Var(ParsedName),
    Annot(Box<Expr>, Type),
    If(Box<Expr>, Box<Expr>, Box<Expr>),
}

#[derive(Debug, Clone, PartialEq)]
pub enum Type {
    Bool,
    Int { width: u8, signed: bool },
    ISize,
    NSize,
    F64,
    Fun(Vec<Type>, Box<Type>),
    Hole,
}

impl Type {
    pub fn to_fem(&self) -> Option<fem::Type> {
        Some(match *self {
            Type::Bool => fem::Type::Bool,
            Type::Int { width, signed } => fem::Type::Int { width, signed },
            Type::ISize => fem::Type::ISize,
            Type::NSize => fem::Type::NSize,
            Type::F64 => fem::Type::F64,
            Type::Fun(ref ps, ref r) =>
                fem::Type::Fun(ps.iter().map(|t| t.to_fem()).collect::<Option<_>>()?, Box::new(r.to_fem()?)),
            Type::Hole => return None,
        })
    }

    pub fn or<'a>(&'a self, other: &'a Self) -> &'a Self {
        if let Type::Hole = self {
            other
        } else {
            self
        }
    }
}

impl std::fmt::Display for Type {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Type::Bool => write!(f, "Bool"),
            Type::Int { width, signed: true } => write!(f, "I{width}"),
            Type::Int { width, signed: false } => write!(f, "N{width}"),
            Type::ISize => write!(f, "Int"),


@@ 57,11 85,15 @@ impl std::fmt::Display for Type {
            Type::F64 => write!(f, "F64"),
            Type::Fun(ps, r) => {
                write!(f, "(Fun [")?;
                for p in ps {
                    write!(f, "{p}")?;
                if !ps.is_empty() {
                    write!(f, "{}", ps[0])?;
                }
                for p in &ps[1..] {
                    write!(f, " {p}")?;
                }
                write!(f, "] {r})")
            }
            Type::Hole => write!(f, "_"),
        }
    }
}


@@ 148,13 180,18 @@ fn def<'s: 't, 't>(inp: Inp<'s, 't>) -> Res<'s, 't, (PrivIdent, Expr)> {

fn expr<'s: 't, 't>(inp: Inp<'s, 't>) -> Res<'s, 't, Expr> {
    let loc = inp.context.with_offset(inp.next()?.1.offset);
    let (inp, kind) =
        alt4(map(lit_int, ExprKind::Int), map(lit_float, ExprKind::F64), map(name, ExprKind::Var), parens(pexpr))(inp)?;
    let (inp, kind) = alt5(
        map(lit_bool, ExprKind::Bool),
        map(lit_int, ExprKind::Int),
        map(lit_float, ExprKind::F64),
        map(name, ExprKind::Var),
        parens(pexpr),
    )(inp)?;
    Ok((inp, Expr { loc, kind }))
}

fn pexpr<'s: 't, 't>(inp: Inp<'s, 't>) -> Res<'s, 't, ExprKind> {
    alt3(
    alt4(
        |inp| {
            let (inp, _) = special_form("fun")(inp)?;
            let (inp, params) = brackets(rest(ident))(inp)?;


@@ 167,6 204,12 @@ fn pexpr<'s: 't, 't>(inp: Inp<'s, 't>) -> Res<'s, 't, ExprKind> {
            let (inp, (t, e)) = pair(typ, expr)(inp)?;
            Ok((inp, ExprKind::Annot(Box::new(e), t)))
        },
        |inp| {
            let (inp, _) = special_form("if")(inp)?;
            let (inp, pred) = expr(inp)?;
            let (inp, (conseq, alt)) = pair(expr, expr)(inp)?;
            Ok((inp, ExprKind::If(Box::new(pred), Box::new(conseq), Box::new(alt))))
        },
        map(pair(expr, rest(expr)), |(f, xs)| ExprKind::App(Box::new(f), xs)),
    )(inp)
}


@@ 186,7 229,7 @@ fn typ<'s: 't, 't>(inp: Inp<'s, 't>) -> Res<'s, 't, Type> {
            give(literally("N64"), Type::Int { width: 64, signed: false }),
        ),
        alt2(give(literally("Int"), Type::ISize), give(literally("Nat"), Type::NSize)),
        give(literally("F64"), Type::F64),
        alt3(give(literally("F64"), Type::F64), give(literally("Bool"), Type::Bool), give(literally("_"), Type::Hole)),
        parens(ptyp),
    )(inp)
}


@@ 257,6 300,14 @@ fn lit_int<'s: 't, 't>(inp: Inp<'s, 't>) -> Res<'s, 't, i128> {
    })
}

fn lit_bool<'s: 't, 't>(inp: Inp<'s, 't>) -> Res<'s, 't, bool> {
    inp.filter_next(|t| match t.tok {
        Tok::Ident("True") => Ok(true),
        Tok::Ident("False") => Ok(false),
        _ => Err("*boolean literal, True or False*"),
    })
}

// fn lit_int<'s: 't, 't>(inp: Inp<'s, 't>) -> Res<'s, 't, i64> {
//     inp.filter_next(|tok| match *tok {
//         Tok::Int(x) => Ok(x),


@@ 311,26 362,50 @@ fn end<'s: 't, 't>(inp: Inp<'s, 't>) -> Res<'s, 't, ()> {
fn alt2<'s: 't, 't, A>(mut f: impl Parser<'s, 't, A>, mut g: impl Parser<'s, 't, A>) -> impl Parser<'s, 't, A> {
    move |inp| {
        let dist0 = inp.dist;
        f(inp.clone()).or_else(|e1| {
            if e1.dist > dist0 {
                return Err(e1);
            }
            g(inp).map_err(|e2| {
                if e2.dist > dist0 {
                    return e2;
                }
                ParseErr { expecteds: e1.expecteds.union(&e2.expecteds).cloned().collect(), ..e2 }
            })
        })
        let e1 = match f(inp.clone()) {
            y @ Ok(_) => return y,
            Err(e) if e.dist > dist0 => return Err(e),
            Err(e) => e,
        };
        let e2 = match g(inp.clone()) {
            y @ Ok(_) => return y,
            Err(e) if e.dist > dist0 => return Err(e),
            Err(e) => e,
        };
        let mut expecteds = e1.expecteds;
        expecteds.extend(e2.expecteds);
        Err(ParseErr { dist: dist0, loc: e1.loc, expecteds })
    }
}

fn alt3<'s: 't, 't, A>(
    f: impl Parser<'s, 't, A>,
    g: impl Parser<'s, 't, A>,
    h: impl Parser<'s, 't, A>,
    mut f: impl Parser<'s, 't, A>,
    mut g: impl Parser<'s, 't, A>,
    mut h: impl Parser<'s, 't, A>,
) -> impl Parser<'s, 't, A> {
    alt2(f, alt2(g, h))
    move |inp| {
        let dist0 = inp.dist;
        let e1 = match f(inp.clone()) {
            y @ Ok(_) => return y,
            Err(e) if e.dist > dist0 => return Err(e),
            Err(e) => e,
        };
        let e2 = match g(inp.clone()) {
            y @ Ok(_) => return y,
            Err(e) if e.dist > dist0 => return Err(e),
            Err(e) => e,
        };
        let e3 = match h(inp.clone()) {
            y @ Ok(_) => return y,
            Err(e) if e.dist > dist0 => return Err(e),
            Err(e) => e,
        };
        Err(ParseErr {
            dist: dist0,
            loc: e1.loc,
            expecteds: [e1, e2, e3].into_iter().flat_map(|e| e.expecteds).collect(),
        })
    }
}

fn alt4<'s: 't, 't, A>(

M src/resolve.rs => src/resolve.rs +7 -2
@@ 18,12 18,14 @@ pub struct Expr {

#[derive(Debug, Clone)]
pub enum ExprKind {
    Bool(bool),
    Int(i128),
    F64(f64),
    Fun(Vec<(PubIdent, LocalId)>, Box<Expr>),
    App(Box<Expr>, Vec<Expr>),
    Var(ResName),
    Annot(Box<Expr>, Type),
    If(Box<Expr>, Box<Expr>, Box<Expr>),
}

pub fn resolve_def(cache: &mut Cache, parent_module: ModuleId, def_body: &parse::Expr) -> Result<Expr> {


@@ 50,8 52,9 @@ impl<'c> Resolver<'c> {
        use parse::ExprKind as Pek;
        use ExprKind as Ek;
        let kind = match &e.kind {
            Pek::Int(x) => Ek::Int(*x),
            Pek::F64(x) => Ek::F64(*x),
            &Pek::Bool(x) => Ek::Bool(x),
            &Pek::Int(x) => Ek::Int(x),
            &Pek::F64(x) => Ek::F64(x),
            Pek::Fun(params, body) => {
                let rparams =
                    params.iter().map(|p| Ok((self.pub_ident(p)?, self.gen_local_id()))).collect::<Result<Vec<_>>>()?;


@@ 71,6 74,8 @@ impl<'c> Resolver<'c> {
                Ek::Var(ResName { res: self.resolve_parsed_name(v)? })
            }
            Pek::Annot(e, t) => Ek::Annot(Box::new(self.resolve(e)?), t.clone()),
            Pek::If(p, c, a) =>
                Ek::If(Box::new(self.resolve(p)?), Box::new(self.resolve(c)?), Box::new(self.resolve(a)?)),
        };
        Ok(Expr { loc: e.loc, kind })
    }