Skip to content

Commit

Permalink
feat: Add support for &rest parameters and apply as a built-in (#301
Browse files Browse the repository at this point in the history
)

* feat: Add `apply` built-in

Remove the stdlib implementation of it

* feat: Add support for `&rest` lambda parameters

* chore: Add and update tests, adapt code to latest

* chore: More tests

* chore: Minor fixes

* wip: Attempt different apply placement

* chore: Add coroutine shadowing tests

* chore: Factor out apply builtin path to new func

* chore: Explicitly mark the duplicated code block

* chore: Add couple more shadow tests

* chore: Clearer comment markers

---------

Co-authored-by: wwared <[email protected]>
  • Loading branch information
wwared and wwared authored Oct 1, 2024
1 parent f72046f commit 1f76e56
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 18 deletions.
7 changes: 0 additions & 7 deletions lib/util.lurk
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,6 @@
(revappend (cdr x) (cons (car x) y))
y)))

!(defrec apply (lambda (f args)
(if args
(if (cdr args)
(apply (f (car args)) (cdr args))
(f (car args)))
(f))))

!(def getf (lambda (plist indicator)
(letrec ((aux (lambda (plist)
(if plist
Expand Down
130 changes: 122 additions & 8 deletions src/lurk/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,13 @@ impl<F> SymbolsDigests<F> {
fn native_lurk_funcs<F: PrimeField32>(
digests: &SymbolsDigests<F>,
coroutines: &FxIndexMap<Symbol, Coroutine<F>>,
) -> [FuncE<F>; 34] {
) -> [FuncE<F>; 35] {
[
lurk_main(),
preallocate_symbols(digests),
eval(),
eval_builtin_expr(digests),
eval_apply_builtin(),
eval_coroutine_expr(digests, coroutines),
eval_opening_unop(digests),
eval_hide(),
Expand All @@ -127,7 +128,7 @@ fn native_lurk_funcs<F: PrimeField32>(
car_cdr(digests),
eval_let(),
eval_letrec(),
apply(),
apply(digests),
env_lookup(),
ingress(digests),
egress(digests),
Expand Down Expand Up @@ -197,6 +198,7 @@ pub enum EvalErr {
ApplyNonFunc,
ParamsNotList,
ParamNotSymbol,
ParamInvalidRest,
ArgsNotList,
InvalidArg,
DivByZero,
Expand Down Expand Up @@ -908,7 +910,7 @@ pub fn eval_builtin_expr<F: AbstractField>(digests: &SymbolsDigests<F>) -> FuncE
let err_tag = Tag::Err;
let invalid_form = EvalErr::InvalidForm;
match head [|name| digests.builtin_symbol_ptr(name).to_field()] {
"let", "letrec", "lambda", "cons", "strcons", "type-eq", "type-eqq" => {
"let", "letrec", "lambda", "cons", "strcons", "type-eq", "type-eqq", "apply" => {
let rest_not_cons = sub(rest_tag, cons_tag);
if rest_not_cons {
return (err_tag, invalid_form)
Expand Down Expand Up @@ -992,6 +994,10 @@ pub fn eval_builtin_expr<F: AbstractField>(digests: &SymbolsDigests<F>) -> FuncE
let t = digests.lurk_symbol_ptr("t");
return (t_tag, t)
}
"apply" => {
let (res_tag, res) = call(eval_apply_builtin, fst_tag, fst, snd_tag, snd, env);
return (res_tag, res)
}
}
}
"list" => {
Expand Down Expand Up @@ -1199,6 +1205,27 @@ pub fn eval_builtin_expr<F: AbstractField>(digests: &SymbolsDigests<F>) -> FuncE
)
}

pub fn eval_apply_builtin<F: AbstractField>() -> FuncE<F> {
func!(
partial fn eval_apply_builtin(fst_tag, fst, snd_tag, snd, env): [2] {
let (fst_tag, fst) = call(eval, fst_tag, fst, env);
match fst_tag {
Tag::Err => {
return (fst_tag, fst)
}
};
let (snd_tag, snd) = call(eval, snd_tag, snd, env);
match snd_tag {
Tag::Err => {
return (snd_tag, snd)
}
};
let (res_tag, res) = call(apply, fst_tag, fst, snd_tag, snd, env);
return (res_tag, res)
}
)
}

pub fn coerce_if_sym<F: AbstractField>() -> FuncE<F> {
func!(
fn coerce_if_sym(tag): [1] {
Expand Down Expand Up @@ -2032,7 +2059,7 @@ pub fn eval_letrec<F: AbstractField>() -> FuncE<F> {
)
}

pub fn apply<F: AbstractField>() -> FuncE<F> {
pub fn apply<F: AbstractField>(digests: &SymbolsDigests<F>) -> FuncE<F> {
func!(
partial fn apply(head_tag, head, args_tag, args, args_env): [2] {
// Constants, tags, etc
Expand Down Expand Up @@ -2073,13 +2100,97 @@ pub fn apply<F: AbstractField>() -> FuncE<F> {
return (err_tag, err)
}
Tag::Cons => {
// check if the only params left are "&rest <var>"
let (param_tag, param, rest_params_tag, rest_params) = load(params);
match param_tag {
Tag::Sym, Tag::Builtin, Tag::Coroutine => {
let rest_sym = digests.lurk_symbol_ptr("&rest");
let is_not_rest_sym = sub(param, rest_sym);
if !is_not_rest_sym {
// check whether the next param in the list is a variable
match rest_params_tag {
InternalTag::Nil => {
let err = EvalErr::ParamInvalidRest;
return (err_tag, err)
}
Tag::Cons => {
let (param_tag, param, rest_params_tag, rest_params) = load(rest_params);
match param_tag {
Tag::Sym, Tag::Builtin, Tag::Coroutine => {
// check that there are no remaining arguments after the variable
match rest_params_tag {
InternalTag::Nil => {
// evaluate all the remaining arguments and collect into a list
let (arg_tag, arg) = call(eval_list, args_tag, args, args_env);
match arg_tag {
Tag::Err => {
return (arg_tag, arg)
}
};

// and store it in the environment
let ext_env = store(param_tag, param, arg_tag, arg, func_env);
let ext_fun = store(rest_params_tag, rest_params, body_tag, body, ext_env);
let nil_tag = InternalTag::Nil;
let nil = digests.lurk_symbol_ptr("nil");
let (res_tag, res) = call(apply, fun_tag, ext_fun, nil_tag, nil, args_env);

return (res_tag, res)
}
};
let err = EvalErr::ParamInvalidRest;
return (err_tag, err)
}
};
let err = EvalErr::IllegalBindingVar;
return (err_tag, err)
}
};
let err = EvalErr::ParamsNotList;
return (err_tag, err)
}
// NOTE: the two block of codes below delimited by the comments are the *exact* same and *must* be kept in sync
// --- DUPLICATED APPLY BLOCK START ---
match args_tag {
InternalTag::Nil => {
// Undersaturated application
return (head_tag, head)
}
Tag::Cons => {
let (arg_tag, arg, rest_args_tag, rest_args) = load(args);
match param_tag {
Tag::Sym, Tag::Builtin, Tag::Coroutine => {
// evaluate the argument
let (arg_tag, arg) = call(eval, arg_tag, arg, args_env);
match arg_tag {
Tag::Err => {
return (arg_tag, arg)
}
};
// and store it in the environment
let ext_env = store(param_tag, param, arg_tag, arg, func_env);
let ext_fun = store(rest_params_tag, rest_params, body_tag, body, ext_env);
let (res_tag, res) = call(apply, fun_tag, ext_fun, rest_args_tag, rest_args, args_env);

return (res_tag, res)
}
};
let err = EvalErr::IllegalBindingVar;
return (err_tag, err)
}
};
let err = EvalErr::ArgsNotList;
return (err_tag, err)
// --- DUPLICATED APPLY BLOCK END ---
}
};
// --- DUPLICATED APPLY BLOCK START ---
match args_tag {
InternalTag::Nil => {
// Undersaturated application
return (head_tag, head)
}
Tag::Cons => {
let (param_tag, param, rest_params_tag, rest_params) = load(params);
let (arg_tag, arg, rest_args_tag, rest_args) = load(args);
match param_tag {
Tag::Sym, Tag::Builtin, Tag::Coroutine => {
Expand All @@ -2104,6 +2215,7 @@ pub fn apply<F: AbstractField>() -> FuncE<F> {
};
let err = EvalErr::ArgsNotList;
return (err_tag, err)
// --- DUPLICATED APPLY BLOCK END ---
}
};
let err = EvalErr::ParamsNotList;
Expand Down Expand Up @@ -2162,6 +2274,7 @@ mod test {
let eval_coroutine_expr = FuncChip::from_name("eval_coroutine_expr", toplevel);
let eval = FuncChip::from_name("eval", toplevel);
let eval_builtin_expr = FuncChip::from_name("eval_builtin_expr", toplevel);
let eval_apply_builtin = FuncChip::from_name("eval_apply_builtin", toplevel);
let eval_opening_unop = FuncChip::from_name("eval_opening_unop", toplevel);
let eval_hide = FuncChip::from_name("eval_hide", toplevel);
let eval_unop = FuncChip::from_name("eval_unop", toplevel);
Expand Down Expand Up @@ -2196,10 +2309,11 @@ mod test {
expected.assert_eq(&computed.to_string());
};
expect_eq(lurk_main.width(), expect!["97"]);
expect_eq(preallocate_symbols.width(), expect!["168"]);
expect_eq(preallocate_symbols.width(), expect!["176"]);
expect_eq(eval_coroutine_expr.width(), expect!["10"]);
expect_eq(eval.width(), expect!["77"]);
expect_eq(eval_builtin_expr.width(), expect!["142"]);
expect_eq(eval_builtin_expr.width(), expect!["143"]);
expect_eq(eval_apply_builtin.width(), expect!["79"]);
expect_eq(eval_opening_unop.width(), expect!["97"]);
expect_eq(eval_hide.width(), expect!["115"]);
expect_eq(eval_unop.width(), expect!["78"]);
Expand All @@ -2214,7 +2328,7 @@ mod test {
expect_eq(equal.width(), expect!["82"]);
expect_eq(equal_inner.width(), expect!["59"]);
expect_eq(car_cdr.width(), expect!["61"]);
expect_eq(apply.width(), expect!["101"]);
expect_eq(apply.width(), expect!["115"]);
expect_eq(env_lookup.width(), expect!["52"]);
expect_eq(ingress.width(), expect!["105"]);
expect_eq(egress.width(), expect!["82"]);
Expand Down
5 changes: 3 additions & 2 deletions src/lurk/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,11 @@ const BUILTIN_PACKAGE_NAME: &str = "builtin";
const META_PACKAGE_NAME: &str = "meta";
const USER_PACKAGE_NAME: &str = "lurk-user";

pub(crate) const LURK_SYMBOLS: [&str; 2] = ["nil", "t"];
pub(crate) const LURK_SYMBOLS: [&str; 3] = ["nil", "t", "&rest"];

pub(crate) const BUILTIN_SYMBOLS: [&str; 39] = [
pub(crate) const BUILTIN_SYMBOLS: [&str; 40] = [
"atom",
"apply",
"begin",
"car",
"cdr",
Expand Down
73 changes: 72 additions & 1 deletion src/lurk/tests/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,37 @@ test!(test_app2, "((lambda (x y z) y) 1 2 3)", |_| uint(2));
test!(test_app3, "((lambda (x) (lambda (y) x)) 1 2)", |_| {
uint(1)
});
test!(test_app4, "(apply (lambda (x) x) '(1))", |_| uint(1));
test!(test_app5, "(apply (lambda (x y z) y) (list 1 2 3))", |_| {
uint(2)
});
test!(
test_app6,
"(apply (lambda (x) (lambda (y) x)) '(1 2))",
|_| { uint(1) }
);
test!(test_app7, "((lambda (x &rest y) (car (cdr y))) 1)", |z| *z
.nil());
test!(test_app8, "((lambda (x &rest y) (car (cdr y))) 1 2)", |z| {
*z.nil()
});
test!(
test_app9,
"((lambda (x &rest y) (car (cdr y))) 1 2 3)",
|_| uint(3)
);
test!(
test_app10,
"((lambda (x &rest y) (car (cdr y))) 1 2 3 4)",
|_| uint(3)
);
test!(test_app_err, "(a)", |_| ZPtr::err(EvalErr::UnboundVar));
test!(test_app_err2, "((lambda () a) 2)", |_| ZPtr::err(
EvalErr::UnboundVar
));
test!(test_app_err3, "(apply (lambda (x) x) 1)", |_| ZPtr::err(
EvalErr::ArgsNotList
));

// builtins
test!(test_if, "(if 1 1 0)", |_| uint(1));
Expand Down Expand Up @@ -340,7 +367,15 @@ test!(
(if (= n 0) 0
(if (= n 1) 1
(+ (fib (- n 1)) (fib (- n 2))))))))
(fib 10))",
(fib 10))",
|_| uint(55)
);
test!(
test_sum,
"(letrec ((sum
(lambda (x &rest y)
(if y (+ x (apply sum y)) x))))
(sum 1 2 3 4 5 6 7 8 9 10))",
|_| uint(55)
);

Expand Down Expand Up @@ -449,6 +484,22 @@ test!(test_shadow3, "((lambda (cons) (+ cons 1)) 1)", |_| uint(2));
test!(test_shadow4, "(let ((cons 1)) (cons cons cons))", |z| {
z.intern_cons(uint(1), uint(1))
});
test!(
test_shadow5,
"((lambda (cons &rest car) (+ cons (car car))) 1 2 5)",
|_| uint(3)
);
test!(
test_shadow6,
"((lambda (&rest &rest) (car &rest)) 1 2 5)",
|_| uint(1)
);
test!(test_shadow7, "(let ((&rest 1)) &rest)", |_| uint(1));
test!(
test_shadow8,
"(let ((&rest (lambda (x) x))) (&rest 1))",
|_| uint(1)
);

// errors
test!(test_unbound_var, "a", |_| ZPtr::err(EvalErr::UnboundVar));
Expand Down Expand Up @@ -501,3 +552,23 @@ test!(test_shadow_err5, "(letrec ((t 1)) (+ t 1))", |_| ZPtr::err(
test!(test_shadow_err6, "((lambda (t) (+ t 1)) 1)", |_| ZPtr::err(
EvalErr::IllegalBindingVar
));
test!(test_shadow_err7, "((lambda (x &rest t) (+ x 1)) 1)", |_| {
ZPtr::err(EvalErr::IllegalBindingVar)
});
test!(
test_shadow_err8,
"((lambda (x &rest nil) (+ x 1)) 1)",
|_| ZPtr::err(EvalErr::IllegalBindingVar)
);
test!(test_rest_err1, "((lambda (x &rest) x) 1)", |_| ZPtr::err(
EvalErr::ParamInvalidRest
));
test!(test_rest_err2, "((lambda (x &rest y z) x) 1)", |_| {
ZPtr::err(EvalErr::ParamInvalidRest)
});
test!(test_rest_err3, "((lambda (&rest y z) z) 1)", |_| ZPtr::err(
EvalErr::ParamInvalidRest
));
test!(test_rest_err4, "((lambda (&rest) &rest) 1)", |_| {
ZPtr::err(EvalErr::ParamInvalidRest)
});
11 changes: 11 additions & 0 deletions src/lurk/tests/lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,14 @@ test!(
"((lambda (extern-square) (+ extern-square 1n)) 2n)",
|_| num(3)
);

test!(
test_mul_shadow4,
"((lambda (&rest mul-square) (+ (car mul-square) 1n)) 2n)",
|_| num(3)
);
test!(
test_extern_shadow4,
"((lambda (&rest extern-square) (+ (car extern-square) 1n)) 2n)",
|_| num(3)
);

0 comments on commit 1f76e56

Please sign in to comment.