diff --git a/Cargo.lock b/Cargo.lock index b5f7977..197b806 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -686,6 +686,7 @@ dependencies = [ "console_error_panic_hook", "enumset", "indexmap 2.0.0", + "js-sys", "rose", "rose-interp", "serde", diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 5004798..73e3cb4 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -76,15 +76,42 @@ pub struct Function { pub body: Box<[Instr]>, } -/// Wrapper for a `Function` that knows how to resolve its `id::Function`s. -pub trait FuncNode { - fn def(&self) -> &Function; +/// Resolves `id::Function`s. +pub trait Refs<'a> { + /// See `Node`. + type Opaque; - fn get(&self, id: id::Function) -> Option + /// Resolve `id` to a function node. + fn get(&self, id: id::Function) -> Option> where Self: Sized; } +/// A node in a graph of functions. +#[derive(Clone, Debug, Copy)] +pub enum Node<'a, O, T: Refs<'a, Opaque = O>> { + /// A function with an explicit body. + Transparent { + /// To traverse the graph by resolving functions called by this one. + refs: T, + /// The signature and definition of this function. + def: &'a Function, + }, + /// A function with an opaque body. + Opaque { + /// Generic type parameters. + generics: &'a [EnumSet], + /// Types used in this function's signature. + types: &'a [Ty], + /// Parameter types. + params: &'a [id::Ty], + /// Return type. + ret: id::Ty, + /// Definition of this function; semantics may vary. + def: O, + }, +} + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug)] pub struct Instr { diff --git a/crates/frontend/Cargo.toml b/crates/frontend/Cargo.toml index b505c07..daa9075 100644 --- a/crates/frontend/Cargo.toml +++ b/crates/frontend/Cargo.toml @@ -10,6 +10,7 @@ indexmap = "2" lalrpop-util = "0.19" logos = "0.13" rose = { path = "../core" } +rose-interp = { path = "../interp" } thiserror = "1" [build-dependencies] diff --git a/crates/frontend/src/translate.rs b/crates/frontend/src/translate.rs index ed0a4a6..153922d 100644 --- a/crates/frontend/src/translate.rs +++ b/crates/frontend/src/translate.rs @@ -2,7 +2,7 @@ use crate::{ast, tokens}; use enumset::EnumSet; use indexmap::{IndexMap, IndexSet}; use rose::{self as ir, id}; -use std::{collections::HashMap, ops::Range}; +use std::{collections::HashMap, convert::Infallible, ops::Range}; #[derive(Debug, thiserror::Error)] pub enum TypeError { @@ -113,19 +113,15 @@ pub struct Module<'input> { funcs: IndexMap<&'input str, rose::Function>, } -#[derive(Clone, Copy, Debug)] -pub struct FuncRef<'input, 'a> { - m: &'a Module<'input>, - id: id::Function, -} +type Opaque = Infallible; -impl<'input, 'a> rose::FuncNode for FuncRef<'input, 'a> { - fn def(&self) -> &rose::Function { - &self.m.funcs[self.id.function()] - } +impl<'input, 'a> rose::Refs<'a> for &'a Module<'input> { + type Opaque = Opaque; - fn get(&self, id: id::Function) -> Option { - Some(Self { m: self.m, id }) + fn get(&self, id: id::Function) -> Option> { + self.funcs + .get_index(id.function()) + .map(|(_, def)| ir::Node::Transparent { refs: *self, def }) } } @@ -134,12 +130,9 @@ impl Module<'_> { self.types.get(name) } - pub fn get_func(&self, name: &str) -> Option { + pub fn get_func(&self, name: &str) -> Option> { let i = self.funcs.get_index_of(name)?; - Some(FuncRef { - m: self, - id: id::function(i), - }) + ir::Refs::get(&self, id::function(i)) } } diff --git a/crates/interp/src/lib.rs b/crates/interp/src/lib.rs index 866aa2b..a238f83 100644 --- a/crates/interp/src/lib.rs +++ b/crates/interp/src/lib.rs @@ -1,6 +1,6 @@ use indexmap::IndexSet; -use rose::{id, Binop, Expr, FuncNode, Ty, Unop}; -use std::{cell::Cell, rc::Rc}; +use rose::{id, Binop, Expr, Function, Node, Refs, Ty, Unop}; +use std::{cell::Cell, convert::Infallible, rc::Rc}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -129,24 +129,44 @@ fn resolve(typemap: &mut IndexSet, generics: &[id::Ty], types: &[id::Ty], ty id::ty(i) } -struct Interpreter<'a, F: FuncNode> { - typemap: &'a mut IndexSet, - f: &'a F, // reference instead of value because otherwise borrow checker complains in `fn block` +/// An opaque function that can be called by the interpreter. +pub trait Opaque { + fn call(&self, types: &IndexSet, generics: &[id::Ty], args: &[Val]) -> Val; +} + +impl Opaque for Infallible { + fn call(&self, _: &IndexSet, _: &[id::Ty], _: &[Val]) -> Val { + match *self {} + } +} + +/// basically, the `'a` lifetime is for the graph of functions, and the `'b` lifetime is just for +/// this particular instance of interpretation +struct Interpreter<'a, 'b, O: Opaque, T: Refs<'a, Opaque = O>> { + typemap: &'b mut IndexSet, + refs: T, + def: &'a Function, types: Vec, vars: Vec>, } -impl<'a, F: FuncNode> Interpreter<'a, F> { - fn new(typemap: &'a mut IndexSet, f: &'a F, generics: &'a [id::Ty]) -> Self { +impl<'a, 'b, O: Opaque, T: Refs<'a, Opaque = O>> Interpreter<'a, 'b, O, T> { + fn new( + typemap: &'b mut IndexSet, + refs: T, + def: &'a Function, + generics: &'b [id::Ty], + ) -> Self { let mut types = vec![]; - for ty in f.def().types.iter() { + for ty in def.types.iter() { types.push(resolve(typemap, generics, &types, ty)); } Self { typemap, - f, + refs, + def, types, - vars: vec![None; f.def().vars.len()], + vars: vec![None; def.vars.len()], } } @@ -229,10 +249,10 @@ impl<'a, F: FuncNode> Interpreter<'a, F> { Expr::Call { id, generics, args } => { let resolved: Vec = generics.iter().map(|id| self.types[id.ty()]).collect(); let vals = args.iter().map(|id| self.vars[id.var()].clone().unwrap()); - call(self.f.get(*id).unwrap(), self.typemap, &resolved, vals) + call(self.refs.get(*id).unwrap(), self.typemap, &resolved, vals) } Expr::For { arg, body, ret } => { - let n = match self.typemap[self.types[self.f.def().vars[arg.var()].ty()].ty()] { + let n = match self.typemap[self.types[self.def.vars[arg.var()].ty()].ty()] { Ty::Fin { size } => size, _ => unreachable!(), }; @@ -279,30 +299,44 @@ impl<'a, F: FuncNode> Interpreter<'a, F> { } /// Assumes `generics` and `arg` are valid. -fn call( - f: impl FuncNode, - types: &mut IndexSet, - generics: &[id::Ty], +fn call<'a, 'b, O: Opaque, T: Refs<'a, Opaque = O>>( + f: Node<'a, O, T>, + types: &'b mut IndexSet, + generics: &'b [id::Ty], args: impl Iterator, ) -> Val { - let mut interp = Interpreter::new(types, &f, generics); - for (var, arg) in f.def().params.iter().zip(args) { - interp.vars[var.var()] = Some(arg.clone()); - } - for instr in f.def().body.iter() { - interp.vars[instr.var.var()] = Some(interp.expr(&instr.expr)); + match f { + Node::Transparent { refs, def } => { + let mut interp = Interpreter::new(types, refs, def, generics); + for (var, arg) in def.params.iter().zip(args) { + interp.vars[var.var()] = Some(arg.clone()); + } + for instr in def.body.iter() { + interp.vars[instr.var.var()] = Some(interp.expr(&instr.expr)); + } + interp.vars[def.ret.var()].as_ref().unwrap().clone() + } + Node::Opaque { + generics: _, + types: _, + params: _, + ret: _, + def, + } => { + let vals: Box<[Val]> = args.collect(); + def.call(types, generics, &vals) + } } - interp.vars[f.def().ret.var()].as_ref().unwrap().clone() } #[derive(Debug, thiserror::Error)] pub enum Error {} /// Guaranteed not to panic if `f` is valid. -pub fn interp( - f: impl FuncNode, +pub fn interp<'a, O: Opaque, T: Refs<'a, Opaque = O>>( + f: Node<'a, O, T>, mut types: IndexSet, - generics: &[id::Ty], + generics: &'a [id::Ty], args: impl Iterator, ) -> Result { // TODO: check that `generics` and `arg` are valid @@ -314,21 +348,56 @@ mod tests { use super::*; use rose::{Function, Instr}; - #[derive(Clone, Copy, Debug)] + type CustomRef<'a> = &'a dyn Fn(&IndexSet, &[id::Ty], &[Val]) -> Val; + type CustomBox = Box, &[id::Ty], &[Val]) -> Val>; + + struct Custom<'a> { + f: CustomRef<'a>, + } + + impl Opaque for Custom<'_> { + fn call(&self, types: &IndexSet, generics: &[id::Ty], args: &[Val]) -> Val { + (self.f)(types, generics, args) + } + } + struct FuncInSlice<'a> { + custom: &'a [CustomBox], funcs: &'a [Function], id: id::Function, } - impl FuncNode for FuncInSlice<'_> { - fn def(&self) -> &Function { - &self.funcs[self.id.function()] + impl<'a> Refs<'a> for FuncInSlice<'a> { + type Opaque = Custom<'a>; + + fn get(&self, id: id::Function) -> Option, Self>> { + if id.function() < self.id.function() { + node(self.custom, self.funcs, id) + } else { + None + } } + } - fn get(&self, id: id::Function) -> Option { - Some(Self { - funcs: self.funcs, - id, + fn node<'a>( + custom: &'a [CustomBox], + funcs: &'a [Function], + id: id::Function, + ) -> Option, FuncInSlice<'a>>> { + let n = custom.len(); + let i = id.function(); + if i < n { + Some(Node::Opaque { + generics: &[], + types: &[], + params: &[], + ret: id::ty(0), + def: Custom { f: &custom[i] }, + }) + } else { + funcs.get(i - n).map(|def| Node::Transparent { + refs: FuncInSlice { custom, funcs, id }, + def, }) } } @@ -352,10 +421,7 @@ mod tests { .into(), }]; let answer = interp( - FuncInSlice { - funcs: &funcs, - id: id::function(0), - }, + node(&[], &funcs, id::function(0)).unwrap(), IndexSet::new(), &[], [val_f64(2.), val_f64(2.)].into_iter(), @@ -407,10 +473,7 @@ mod tests { }, ]; let answer = interp( - FuncInSlice { - funcs: &funcs, - id: id::function(1), - }, + node(&[], &funcs, id::function(1)).unwrap(), IndexSet::new(), &[], [].into_iter(), @@ -418,4 +481,35 @@ mod tests { .unwrap(); assert_eq!(answer, val_f64(1764.)); } + + #[test] + fn test_custom() { + let custom: [CustomBox; 1] = [Box::new(|_, _, args| { + Val::F64(Cell::new(args[0].f64().powf(args[1].f64()))) + })]; + let funcs = [Function { + generics: [].into(), + types: [Ty::F64].into(), + vars: [id::ty(0), id::ty(0), id::ty(0)].into(), + params: [id::var(0), id::var(1)].into(), + ret: id::var(2), + body: [Instr { + var: id::var(2), + expr: Expr::Call { + id: id::function(0), + generics: [].into(), + args: [id::var(0), id::var(1)].into(), + }, + }] + .into(), + }]; + let answer = interp( + node(&custom, &funcs, id::function(1)).unwrap(), + IndexSet::new(), + &[], + [val_f64(std::f64::consts::E), val_f64(std::f64::consts::PI)].into_iter(), + ) + .unwrap(); + assert_eq!(answer, val_f64(23.140692632779263)); + } } diff --git a/crates/validate/src/lib.rs b/crates/validate/src/lib.rs index 66580b2..56e3b17 100644 --- a/crates/validate/src/lib.rs +++ b/crates/validate/src/lib.rs @@ -1,6 +1,6 @@ use enumset::EnumSet; use indexmap::IndexMap; -use rose::{id, Binop, Constraint, Expr, FuncNode, Function, Instr, Ty, Unop}; +use rose::{id, Binop, Constraint, Expr, Function, Instr, Refs, Ty, Unop}; #[derive(Debug, Eq, thiserror::Error, PartialEq)] pub enum InstrError { @@ -284,9 +284,9 @@ enum Scope { Expired, } -struct Validator<'a, F: FuncNode> { - node: &'a F, - f: &'a Function, +struct Validator<'a, O, T: Refs<'a, Opaque = O>> { + refs: T, + def: &'a Function, constraints: IndexMap>, /// indices from `self.f.types` into `self.constraints` types: Vec, @@ -294,7 +294,7 @@ struct Validator<'a, F: FuncNode> { vars: Vec, } -impl Validator<'_, F> { +impl<'a, O, T: Refs<'a, Opaque = O>> Validator<'a, O, T> { fn ty(&self, t: id::Ty) -> &Ty { let (ty, _) = self.constraints.get_index(t.ty()).unwrap(); ty @@ -306,7 +306,7 @@ impl Validator<'_, F> { } fn var_ty_id(&self, x: id::Var) -> id::Ty { - self.types[self.f.vars[x.var()].ty()] + self.types[self.def.vars[x.var()].ty()] } fn get_ty_id(&self, x: id::Var) -> Option { @@ -539,15 +539,43 @@ impl Validator<'_, F> { check(*c == Ty::Bool && a == b && t == a, SelectType) } - Expr::Call { id, generics, args } => match self.node.get(*id) { + Expr::Call { id, generics, args } => match self.refs.get(*id) { Some(node) => { - let g = node.def(); - if generics.len() != g.generics.len() { + struct Stuff<'a> { + gens: &'a [EnumSet], + typs: &'a [Ty], + params: Box<[id::Ty]>, + ret: id::Ty, + } + let Stuff { + gens, + typs, + params, + ret, + } = match node { + rose::Node::Transparent { refs: _, def } => Stuff { + gens: &def.generics, + typs: &def.types, + params: def.params.iter().map(|x| def.vars[x.var()]).collect(), + ret: def.vars[def.ret.var()], + }, + rose::Node::Opaque { + generics, + types, + params, + ret, + def: _, + } => Stuff { + gens: generics, + typs: types, + params: params.into(), // clone just to get the types to match up + ret, + }, + }; + if generics.len() != gens.len() { return Err(CallGenericsCount); } - for (i, (expected, actual)) in - g.generics.iter().zip(generics.iter()).enumerate() - { + for (i, (expected, actual)) in gens.iter().zip(generics.iter()).enumerate() { let id = id::generic(i); match self.types.get(actual.ty()) { Some(generic) => check( @@ -558,23 +586,20 @@ impl Validator<'_, F> { } } let mut types = vec![]; - for typ in g.types.iter() { + for typ in typs.iter() { let i = self.resolve(generics, &types, typ); types.push(i); } - if args.len() != g.params.len() { + if args.len() != params.len() { return Err(CallArgsCount); } - for (i, (expected, &actual)) in g.params.iter().zip(args.iter()).enumerate() { + for (i, (expected, &actual)) in params.iter().zip(args.iter()).enumerate() { match self.get_ty_id(actual) { - Some(arg) => check( - arg == types[g.vars[expected.var()].ty()].unwrap(), - CallArg(i), - )?, + Some(arg) => check(arg == types[expected.ty()].unwrap(), CallArg(i))?, None => return Err(CallInvalidArg(i)), } } - check(t == types[g.vars[g.ret.var()].ty()].unwrap(), CallRet) + check(t == types[ret.ty()].unwrap(), CallRet) } None => Err(CallFunction), }, @@ -737,9 +762,7 @@ pub enum Error { } /// Validate `f`, assuming that all of its referenced functions are valid. -pub fn validate(f: impl FuncNode) -> Result<(), Error> { - let def = f.def(); - +pub fn validate<'a, O, T: Refs<'a, Opaque = O>>(refs: T, def: &'a Function) -> Result<(), Error> { for (i, constrs) in def.generics.iter().enumerate() { // for now we have no compatible constraints if (constrs.contains(Constraint::Index) && !constrs.contains(Constraint::Value)) @@ -846,8 +869,8 @@ pub fn validate(f: impl FuncNode) -> Result<(), Error> { } let mut validator = Validator { - node: &f, - f: def, + refs, + def, constraints, types, vars, @@ -867,32 +890,39 @@ pub fn validate(f: impl FuncNode) -> Result<(), Error> { #[cfg(test)] mod tests { use super::*; + use rose::Node; struct FuncInSlice<'a> { funcs: &'a [Function], id: id::Function, } - impl FuncNode for FuncInSlice<'_> { - fn def(&self) -> &Function { - &self.funcs[self.id.function()] - } + impl<'a> Refs<'a> for FuncInSlice<'a> { + type Opaque = (); - fn get(&self, id: id::Function) -> Option { + fn get(&self, id: id::Function) -> Option> { if id.function() < self.id.function() { - Some(Self { - funcs: self.funcs, - id, - }) + node(self.funcs, id) } else { None } } } + fn node(funcs: &[Function], id: id::Function) -> Option> { + funcs.get(id.function()).map(|def| Node::Transparent { + refs: FuncInSlice { funcs, id }, + def, + }) + } + + fn validate_in_slice(funcs: &[Function], id: id::Function) -> Result<(), Error> { + validate(FuncInSlice { funcs, id }, &funcs[id.function()]) + } + fn example_constraints(constraints: EnumSet) { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [constraints].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -904,8 +934,8 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::ImpossibleConstraints(id::generic(0)))); } @@ -931,8 +961,8 @@ mod tests { #[test] fn test_invalid_generic() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Generic { id: id::generic(0) }].into(), vars: [id::ty(0)].into(), @@ -940,14 +970,14 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidGeneric(id::ty(0)))); } fn example_kind(kind: Constraint) { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -962,8 +992,8 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidKind(id::ty(1)))); } @@ -979,8 +1009,8 @@ mod tests { #[test] fn test_scope_id() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -995,15 +1025,15 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidRef(id::ty(1)))); } #[test] fn test_ref_scope() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -1032,15 +1062,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidScope(id::ty(2)))); } #[test] fn test_inner() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -1069,15 +1099,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidInner(id::ty(2)))); } #[test] fn test_nested_ref() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -1123,15 +1153,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InnerNotValue(id::ty(5)))); } #[test] fn test_invalid_index() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::F64, @@ -1147,15 +1177,15 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidIndex(id::ty(1)))); } #[test] fn test_invalid_elem() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -1171,15 +1201,15 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidElem(id::ty(1)))); } #[test] fn test_not_index() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::F64, @@ -1194,15 +1224,15 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::NotIndex(id::ty(1)))); } #[test] fn test_elem_not_value() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::empty()].into(), types: [ Ty::Fin { size: 1 }, @@ -1218,15 +1248,15 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::ElemNotValue(id::ty(2)))); } #[test] fn test_invalid_member() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Tuple { @@ -1240,15 +1270,15 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidMember(id::ty(0), id::member(0)))); } #[test] fn test_member_not_value() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::empty()].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -1262,15 +1292,15 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::MemberNotValue(id::ty(1), id::member(0)))); } #[test] fn test_invalid_var() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [].into(), vars: [id::ty(0)].into(), @@ -1282,15 +1312,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidVar(id::var(0)))); } #[test] fn test_invalid_param() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -1302,15 +1332,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidParam(0))); } #[test] fn test_duplicate_param() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0), id::ty(0)].into(), @@ -1322,15 +1352,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::DuplicateParam(1))); } #[test] fn test_invalid_ret() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [].into(), vars: [].into(), @@ -1338,8 +1368,8 @@ mod tests { ret: id::var(0), body: [].into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, Err(Error::InvalidRet)); } @@ -1353,8 +1383,8 @@ mod tests { #[test] fn test_invalid_var() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -1366,15 +1396,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, InvalidVar)); } #[test] fn test_redeclare() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -1386,15 +1416,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, Redeclare)); } #[test] fn test_unit() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::F64].into(), vars: [id::ty(0)].into(), @@ -1406,15 +1436,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, UnitType)); } #[test] fn test_bool() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -1426,14 +1456,14 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, BoolType)); } #[test] fn test_f64() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -1445,15 +1475,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, F64Type)); } #[test] fn test_fin_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -1465,15 +1495,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, FinType)); } #[test] fn test_fin_too_big() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Fin { size: 2 }].into(), vars: [id::ty(0)].into(), @@ -1485,15 +1515,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, FinTooBig)); } #[test] fn test_array_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -1505,15 +1535,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ArrayType)); } #[test] fn test_array_index() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Unit, @@ -1533,15 +1563,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ArrayIndex)); } #[test] fn test_array_size() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Fin { size: 1 }, @@ -1560,15 +1590,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ArraySize)); } #[test] fn test_array_invalid_elem() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Fin { size: 1 }, @@ -1589,15 +1619,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ArrayInvalidElem(0))); } #[test] fn test_array_elem_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -1619,15 +1649,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ArrayElemType(0))); } #[test] fn test_tuple_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -1639,15 +1669,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, TupleType)); } #[test] fn test_tuple_size() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -1665,15 +1695,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, TupleSize)); } #[test] fn test_tuple_invalid_member() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -1693,15 +1723,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, TupleInvalidMember(id::member(0)))); } #[test] fn test_tuple_member_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -1722,15 +1752,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, TupleMemberType(id::member(0)))); } #[test] fn test_index_invalid_array() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -1752,15 +1782,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, IndexInvalidArray)); } #[test] fn test_index_invalid_index() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -1782,15 +1812,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, IndexInvalidIndex)); } #[test] fn test_index_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -1813,15 +1843,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, IndexType)); } #[test] fn test_member_invalid_tuple() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -1842,15 +1872,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, MemberInvalidTuple)); } #[test] fn test_member_not_tuple() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0), id::ty(0)].into(), @@ -1865,15 +1895,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, MemberNotTuple)); } #[test] fn test_member_invalid_member() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Tuple { members: [].into() }].into(), vars: [id::ty(0), id::ty(0)].into(), @@ -1888,15 +1918,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, MemberInvalidMember)); } #[test] fn test_member_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -1918,15 +1948,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, MemberType)); } #[test] fn test_slice_invalid_array() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -1948,15 +1978,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, SliceInvalidArray)); } #[test] fn test_slice_invalid_index() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -1978,15 +2008,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, SliceInvalidIndex)); } #[test] fn test_slice_array_not_ref() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -2008,15 +2038,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, SliceArrayNotRef)); } #[test] fn test_slice_not_ref() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index, EnumSet::empty()].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -2043,15 +2073,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, SliceNotRef)); } #[test] fn test_slice_scope() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [ Constraint::Value | Constraint::Index, EnumSet::empty(), @@ -2088,15 +2118,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, SliceScope)); } #[test] fn test_slice_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index, EnumSet::empty()].into(), types: [ Ty::Unit, @@ -2128,15 +2158,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, SliceType)); } #[test] fn test_unary_invalid_arg() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Bool].into(), vars: [id::ty(0)].into(), @@ -2151,14 +2181,14 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, UnaryInvalidArg)); } fn example_unary_type(types: Box<[Ty]>, op: Unop) { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types, vars: [id::ty(0), id::ty(1)].into(), @@ -2173,8 +2203,8 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, UnaryType)); } @@ -2200,8 +2230,8 @@ mod tests { #[test] fn test_binary_invalid_left() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Bool].into(), vars: [id::ty(0)].into(), @@ -2217,15 +2247,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, BinaryInvalidLeft)); } #[test] fn test_binary_invalid_right() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Bool].into(), vars: [id::ty(0), id::ty(0)].into(), @@ -2241,14 +2271,14 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, BinaryInvalidRight)); } fn example_binary_type(types: Box<[Ty]>, op: Binop) { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types, vars: [id::ty(0), id::ty(1), id::ty(2)].into(), @@ -2264,8 +2294,8 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, BinaryType)); } @@ -2315,8 +2345,8 @@ mod tests { } fn example_select_invalid(cond: usize, then: usize, els: usize, e: InstrError) { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Bool].into(), vars: [id::ty(0), id::ty(0)].into(), @@ -2332,8 +2362,8 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, e)); } @@ -2354,8 +2384,8 @@ mod tests { #[test] fn test_select_type_cond() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0), id::ty(0), id::ty(0)].into(), @@ -2371,15 +2401,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, SelectType)); } #[test] fn test_select_type_match() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Bool, Ty::Unit].into(), vars: [id::ty(0), id::ty(0), id::ty(1), id::ty(0)].into(), @@ -2395,15 +2425,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, SelectType)); } #[test] fn test_select_type_ret() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Bool, Ty::Unit].into(), vars: [id::ty(0), id::ty(0), id::ty(0), id::ty(1)].into(), @@ -2419,15 +2449,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, SelectType)); } #[test] fn test_call_function() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [Ty::Unit].into(), vars: [id::ty(0)].into(), @@ -2443,15 +2473,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, CallFunction)); } #[test] fn test_call_generics_count() { - let res = validate(FuncInSlice { - funcs: &[ + let res = validate_in_slice( + &[ Function { generics: [EnumSet::empty()].into(), types: [Ty::Unit].into(), @@ -2481,15 +2511,15 @@ mod tests { .into(), }, ], - id: id::function(1), - }); + id::function(1), + ); assert_eq!(res, err(0, CallGenericsCount)); } #[test] fn test_call_invalid_generic() { - let res = validate(FuncInSlice { - funcs: &[ + let res = validate_in_slice( + &[ Function { generics: [EnumSet::only(Constraint::Index)].into(), types: [Ty::Generic { id: id::generic(0) }].into(), @@ -2515,15 +2545,15 @@ mod tests { .into(), }, ], - id: id::function(1), - }); + id::function(1), + ); assert_eq!(res, err(0, CallInvalidGeneric(id::generic(0)))); } #[test] fn test_call_generic() { - let res = validate(FuncInSlice { - funcs: &[ + let res = validate_in_slice( + &[ Function { generics: [EnumSet::only(Constraint::Index)].into(), types: [Ty::Generic { id: id::generic(0) }].into(), @@ -2549,15 +2579,15 @@ mod tests { .into(), }, ], - id: id::function(1), - }); + id::function(1), + ); assert_eq!(res, err(0, CallGeneric(id::generic(0)))); } #[test] fn test_call_args_count() { - let res = validate(FuncInSlice { - funcs: &[ + let res = validate_in_slice( + &[ Function { generics: [].into(), types: [Ty::Unit].into(), @@ -2583,15 +2613,15 @@ mod tests { .into(), }, ], - id: id::function(1), - }); + id::function(1), + ); assert_eq!(res, err(0, CallArgsCount)); } #[test] fn test_call_invalid_arg() { - let res = validate(FuncInSlice { - funcs: &[ + let res = validate_in_slice( + &[ Function { generics: [].into(), types: [Ty::Unit].into(), @@ -2617,15 +2647,15 @@ mod tests { .into(), }, ], - id: id::function(1), - }); + id::function(1), + ); assert_eq!(res, err(0, CallInvalidArg(0))); } #[test] fn test_call_arg() { - let res = validate(FuncInSlice { - funcs: &[ + let res = validate_in_slice( + &[ Function { generics: [].into(), types: [Ty::Unit].into(), @@ -2651,15 +2681,15 @@ mod tests { .into(), }, ], - id: id::function(1), - }); + id::function(1), + ); assert_eq!(res, err(0, CallArg(0))); } #[test] fn test_call_ret() { - let res = validate(FuncInSlice { - funcs: &[ + let res = validate_in_slice( + &[ Function { generics: [].into(), types: [Ty::Unit].into(), @@ -2685,15 +2715,15 @@ mod tests { .into(), }, ], - id: id::function(1), - }); + id::function(1), + ); assert_eq!(res, err(0, CallRet)); } #[test] fn test_for_invalid_arg() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -2716,15 +2746,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ForInvalidArg)); } #[test] fn test_for_body() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -2751,15 +2781,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ForBody(0, Box::new(UnitType)))); } #[test] fn test_for_invalid_ret() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Generic { id: id::generic(0) }, @@ -2782,15 +2812,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ForInvalidRet)); } #[test] fn test_for_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [Constraint::Value | Constraint::Index].into(), types: [ Ty::Unit, @@ -2818,15 +2848,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ForType)); } #[test] fn test_read_invalid_var() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -2854,15 +2884,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadInvalidVar)); } #[test] fn test_read_invalid_arg() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -2890,15 +2920,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadInvalidArg)); } #[test] fn test_read_not_ref() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -2926,15 +2956,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadNotRef)); } #[test] fn test_read_not_scope() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::only(Constraint::Read)].into(), types: [ Ty::Unit, @@ -2959,15 +2989,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadNotScope)); } #[test] fn test_read_scope_kind() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -2995,15 +3025,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadScopeKind)); } #[test] fn test_read_scope_id() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3031,15 +3061,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadScopeId)); } #[test] fn test_read_inner() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3068,15 +3098,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadInner)); } #[test] fn test_read_body() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3108,15 +3138,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadBody(0, Box::new(Redeclare)))); } #[test] fn test_read_invalid_ret() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3144,15 +3174,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadInvalidRet)); } #[test] fn test_read_escape() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3180,15 +3210,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadEscape)); } #[test] fn test_read_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3217,15 +3247,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, ReadType)); } #[test] fn test_accum_invalid_shape() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3253,15 +3283,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumInvalidShape)); } #[test] fn test_accum_invalid_arg() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3289,15 +3319,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumInvalidArg)); } #[test] fn test_accum_not_ref() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3325,15 +3355,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumNotRef)); } #[test] fn test_accum_not_scope() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::only(Constraint::Accum)].into(), types: [ Ty::Unit, @@ -3358,15 +3388,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumNotScope)); } #[test] fn test_accum_scope_kind() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3394,15 +3424,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumScopeKind)); } #[test] fn test_accum_scope_id() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3430,15 +3460,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumScopeId)); } #[test] fn test_accum_inner() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3467,15 +3497,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumInner)); } #[test] fn test_accum_body() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3507,15 +3537,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumBody(0, Box::new(Redeclare)))); } #[test] fn test_accum_invalid_ret() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3543,15 +3573,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumInvalidRet)); } #[test] fn test_accum_escape() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3579,15 +3609,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumEscape)); } #[test] fn test_accum_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [].into(), types: [ Ty::Unit, @@ -3616,15 +3646,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AccumType)); } #[test] fn test_ask_invalid_var() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::only(Constraint::Read)].into(), types: [ Ty::F64, @@ -3644,15 +3674,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AskInvalidVar)); } #[test] fn test_ask_not_ref() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::only(Constraint::Read)].into(), types: [ Ty::F64, @@ -3672,15 +3702,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AskNotRef)); } #[test] fn test_ask_read() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::empty()].into(), types: [ Ty::F64, @@ -3700,15 +3730,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AskRead)); } #[test] fn test_ask_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::only(Constraint::Read)].into(), types: [ Ty::F64, @@ -3728,15 +3758,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AskType)); } #[test] fn add_invalid_accum() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::only(Constraint::Accum)].into(), types: [ Ty::Unit, @@ -3760,15 +3790,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AddInvalidAccum)); } #[test] fn add_invalid_addend() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::only(Constraint::Accum)].into(), types: [ Ty::Unit, @@ -3792,15 +3822,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AddInvalidAddend)); } #[test] fn add_not_ref() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::only(Constraint::Accum)].into(), types: [ Ty::Unit, @@ -3824,15 +3854,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AddNotRef)); } #[test] fn add_accum() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::empty()].into(), types: [ Ty::Unit, @@ -3856,15 +3886,15 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AddAccum)); } #[test] fn add_type() { - let res = validate(FuncInSlice { - funcs: &[Function { + let res = validate_in_slice( + &[Function { generics: [EnumSet::only(Constraint::Accum)].into(), types: [ Ty::Unit, @@ -3888,8 +3918,8 @@ mod tests { }] .into(), }], - id: id::function(0), - }); + id::function(0), + ); assert_eq!(res, err(0, AddType)); } } diff --git a/crates/web/Cargo.toml b/crates/web/Cargo.toml index fc52f3b..5259bf9 100644 --- a/crates/web/Cargo.toml +++ b/crates/web/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["cdylib"] console_error_panic_hook = { version = "0.1", optional = true } enumset = "1" indexmap = { version = "2", features = ["serde"] } +js-sys = "0.3" rose = { path = "../core", features = ["serde"] } rose-interp = { path = "../interp", features = ["serde"] } serde = { version = "1", features = ["derive"] } diff --git a/crates/web/src/lib.rs b/crates/web/src/lib.rs index fee11c3..d34a1b6 100644 --- a/crates/web/src/lib.rs +++ b/crates/web/src/lib.rs @@ -40,64 +40,167 @@ pub fn layouts() -> Result { ]) } -#[derive(Debug)] -struct Inner { - /// The functions this one depends on; see `rose::FuncNode`. - deps: Box<[Func]>, +/// Clone `x` into JavaScript. +fn val_to_js(x: &rose_interp::Val) -> JsValue { + match x { + rose_interp::Val::F64(x) => JsValue::from_f64(x.get()), + _ => todo!(), + } +} - /// The definition of this function. - def: rose::Function, +/// Reference to an opaque function that just points to a JavaScript function as its implementation. +struct Opaque<'a> { + f: &'a js_sys::Function, +} - /// Indices for string keys on tuple types that represent structs. - /// - /// The actual strings are stored in JavaScript. - structs: Box<[Option>]>, +impl rose_interp::Opaque for Opaque<'_> { + fn call( + &self, + _: &IndexSet, + _: &[id::Ty], + args: &[rose_interp::Val], + ) -> rose_interp::Val { + let context = &JsValue::UNDEFINED; + // we only support functions with a small number of `F64` parameters that return `F64` + rose_interp::val_f64( + match args.len() { + 0 => self.f.call0(context), + 1 => self.f.call1(context, &val_to_js(&args[0])), + 2 => self + .f + .call2(context, &val_to_js(&args[0]), &val_to_js(&args[1])), + 3 => self.f.call3( + context, + &val_to_js(&args[0]), + &val_to_js(&args[1]), + &val_to_js(&args[2]), + ), + _ => todo!(), + } + .unwrap() + .as_f64() + .unwrap(), + ) + } +} + +/// Essentially an owned version of `rose::Node`. +enum Inner { + Transparent { + deps: Box<[Func]>, + def: rose::Function, + }, + Opaque { + generics: Box<[EnumSet]>, + types: Box<[rose::Ty]>, + params: Box<[id::Ty]>, + ret: id::Ty, + def: js_sys::Function, + }, } +/// Reference to a slice of function nodes, representing dependencies of a function. +struct Refs<'a> { + deps: &'a [Func], +} + +impl<'a> rose::Refs<'a> for Refs<'a> { + type Opaque = Opaque<'a>; + + fn get(&self, id: id::Function) -> Option, Self>> { + self.deps.get(id.function()).map(|f| f.node()) + } +} + +type Stuff = (Inner, Box<[Option>]>); + /// A node in a reference-counted acyclic digraph of functions. #[wasm_bindgen] -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Func { - rc: Rc, + rc: Rc, } -impl<'a> rose::FuncNode for &'a Func { - fn def(&self) -> &rose::Function { - &self.rc.as_ref().def +#[wasm_bindgen] +impl Func { + /// Return an opaque function taking `params` `F64` parameters and returning `F64`. + #[wasm_bindgen(constructor)] + pub fn new(params: usize, def: js_sys::Function) -> Self { + Self { + rc: Rc::new(( + Inner::Opaque { + generics: [].into(), + types: [rose::Ty::F64].into(), + params: vec![id::ty(0); params].into(), + ret: id::ty(0), + def, + }, + [].into(), + )), + } } - fn get(&self, id: id::Function) -> Option { - self.rc.as_ref().deps.get(id.function()) + /// Construct a function node from the data this `Func` points to. + fn node(&self) -> rose::Node { + let (inner, _) = self.rc.as_ref(); + match inner { + Inner::Transparent { deps, def } => rose::Node::Transparent { + refs: Refs { deps }, + def, + }, + Inner::Opaque { + generics, + types, + params, + ret, + def, + } => rose::Node::Opaque { + generics, + types, + params, + ret: *ret, + def: Opaque { f: def }, + }, + } } -} -#[wasm_bindgen] -impl Func { /// Return the ID of this function's return type. #[wasm_bindgen(js_name = "retType")] pub fn ret_type(&self) -> usize { - let def = &self.rc.as_ref().def; - def.vars[def.ret.var()].ty() + let (inner, _) = self.rc.as_ref(); + match inner { + Inner::Transparent { def, .. } => def.vars[def.ret.var()].ty(), + Inner::Opaque { ret, .. } => ret.ty(), + } } /// Return the ID of the element type for the array type with ID `t`. pub fn elem(&self, t: usize) -> usize { - match self.rc.as_ref().def.types[t] { - rose::Ty::Array { index: _, elem } => elem.ty(), - _ => panic!("not an array"), + let (inner, _) = self.rc.as_ref(); + match inner { + Inner::Transparent { def, .. } => match def.types[t] { + rose::Ty::Array { index: _, elem } => elem.ty(), + _ => panic!("not an array"), + }, + Inner::Opaque { .. } => panic!(), } } /// Return the string ID for member `i` of the struct type with ID `t`. pub fn key(&self, t: usize, i: usize) -> usize { - self.rc.as_ref().structs[t].as_ref().unwrap()[i] + let (_, structs) = self.rc.as_ref(); + structs[t].as_ref().unwrap()[i] } /// Return the member type ID for member `i` of the struct type with ID `t`. pub fn mem(&self, t: usize, i: usize) -> usize { - match &self.rc.as_ref().def.types[t] { - rose::Ty::Tuple { members } => members[i].ty(), - _ => panic!("not a struct"), + let (inner, _) = self.rc.as_ref(); + match inner { + Inner::Transparent { def, .. } => match &def.types[t] { + rose::Ty::Tuple { members } => members[i].ty(), + _ => panic!("not a struct"), + }, + Inner::Opaque { .. } => panic!(), } } @@ -105,7 +208,7 @@ impl Func { /// /// The return value is Serde-converted from `rose_interp::Val`. pub fn interp(&self) -> Result { - let ret = rose_interp::interp(self, IndexSet::new(), &[], [].into_iter())?; + let ret = rose_interp::interp(self.node(), IndexSet::new(), &[], [].into_iter())?; Ok(to_js_value(&ret)?) } } @@ -277,7 +380,11 @@ pub fn pprint(f: &Func) -> Result { } let mut s = String::new(); - let def = &f.rc.as_ref().def; + let (inner, _) = f.rc.as_ref(); + let def = match inner { + Inner::Transparent { def, .. } => def, + Inner::Opaque { .. } => return Err(JsError::new("opaque function")), + }; for (i, constraints) in def.generics.iter().enumerate() { write!(&mut s, "G{i} = ")?; @@ -446,18 +553,20 @@ impl FuncBuilder { let (types, structs): (Vec<_>, Vec<_>) = self.types.into_keys().map(|ty| ty.separate()).unzip(); Func { - rc: Rc::new(Inner { - deps: self.functions.into(), - def: rose::Function { - generics: self.generics, - types: types.into(), - vars: self.vars.into_iter().map(|x| x.t).collect(), - params, - ret: id::var(out), - body: code.into(), + rc: Rc::new(( + Inner::Transparent { + deps: self.functions.into(), + def: rose::Function { + generics: self.generics, + types: types.into(), + vars: self.vars.into_iter().map(|x| x.t).collect(), + params, + ret: id::var(out), + body: code.into(), + }, }, - structs: structs.into(), - }), + structs.into(), + )), } } @@ -873,12 +982,19 @@ impl FuncBuilder { /// The returned `Vec` is always nonempty, since its last element is the return type; all the /// other elements are the parameter types. pub fn ingest(&mut self, f: &Func, strings: &[usize], generics: &[usize]) -> Vec { + let (inner, structs) = f.rc.as_ref(); + let def = match inner { + Inner::Transparent { def, .. } => def, + Inner::Opaque { params, .. } => { + // we currently only allow opaque functions of few `F64` parameters returning `F64` + let t = self.ty_f64(); + return vec![t; params.len() + 1]; + } + }; let mut types = vec![]; - let inner = f.rc.as_ref(); - let def = &inner.def; // push a corresponding type onto our own `types` for each type in the callee for (t, ty) in def.types.iter().enumerate() { - types.push(self.resolve(generics, strings, &inner.structs, &types, t, ty)); + types.push(self.resolve(generics, strings, structs, &types, t, ty)); } let mut sig: Vec<_> = def diff --git a/packages/core/src/impl.ts b/packages/core/src/impl.ts index e2558d3..b908526 100644 --- a/packages/core/src/impl.ts +++ b/packages/core/src/impl.ts @@ -403,7 +403,7 @@ type ValueParams = { [K in keyof T]: ToValue; }; -/** Constructs an abstract function with the given `types` for parameters. */ +/** Construct an abstract function by abstractly interpreting `f` once. */ export const fn = ( params: P, ret: R, @@ -454,6 +454,23 @@ export const fn = ( return g; }; +/** Construct an opaque function whose implementation runs `f`. */ +export const custom = ( + params: P, + ret: R, + f: (...args: ToJs>) => ToJs>, +): Fn & ((...args: ValueParams

) => ToSymbolic) => { + // TODO: support more complicated signatures for opaque functions + const func = new wasm.Func(params.length, f); + const g: any = (...args: SymbolicParams

): Real => + // TODO: support generics + call(g, new Uint32Array(), args as unknown[]) as Real; + funcs.register(g, func); + g[inner] = func; + g[strings] = []; // TODO: support tuples in opaque functions + return g; +}; + /** A concrete value. */ type Js = null | boolean | number | Js[] | { [K: string]: Js }; diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts index ce8bb7a..40e1924 100644 --- a/packages/core/src/index.test.ts +++ b/packages/core/src/index.test.ts @@ -5,6 +5,7 @@ import { Real, Vec, add, + custom, div, fn, interp, @@ -291,4 +292,18 @@ describe("valid", () => { const g = interp(fn([], Vec(n, n), () => f([3, 5]))); expect(g()).toEqual([0, 1]); }); + + test("custom unary function", () => { + const log = custom([Real], Real, Math.log); + const f = fn([], Real, () => log(Math.PI)); + const g = interp(f); + expect(g()).toBe(1.1447298858494002); + }); + + test("custom binary function", () => { + const pow = custom([Real, Real], Real, Math.pow); + const f = fn([], Real, () => pow(Math.E, Math.PI)); + const g = interp(f); + expect(g()).toBe(23.140692632779263); + }); }); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4a7280c..f08eab1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,6 +6,7 @@ export { abs, add, and, + custom, div, eq, fn,