Skip to content

Commit 261f70a

Browse files
committed
Allow variable args in user functions
Also include currying example in tests
1 parent 4e0858a commit 261f70a

File tree

2 files changed

+59
-2
lines changed

2 files changed

+59
-2
lines changed

lispy/lispy_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,36 @@ func TestFunctionDefinitions(t *testing.T) {
182182
}
183183
}
184184
}
185+
186+
func TestVariableArgsAndCurrying(t *testing.T) {
187+
l := InitLispy()
188+
defer CleanLispy(l)
189+
190+
cases := []struct {
191+
input string
192+
want string
193+
}{
194+
{"\\ {args body} {def (head args) (\\ (tail args) body)}",
195+
"(\\ {args body} {def (head args) (\\ (tail args) body)})"},
196+
{"def {fun} (\\ {args body} {def (head args) (\\ (tail args) body)})", "()"},
197+
{"fun {add-together x y} {+ x y}", "()"},
198+
{"add-together 1 2", "3"},
199+
{"fun {unpack f xs} {eval (join (list f) xs)}", "()"},
200+
{"fun {pack f & xs} {f xs}", "()"},
201+
{"def {uncurry} pack", "()"},
202+
{"def {curry} unpack", "()"},
203+
{"curry + {5 6 7}", "18"},
204+
{"uncurry head 5 6 7", "{5}"},
205+
{"def {add-uncurried} +", "()"},
206+
{"def {add-curried} (curry +)", "()"},
207+
{"add-curried {5 6 7}", "18"},
208+
{"add-uncurried 5 6 7", "18"},
209+
}
210+
211+
for _, c := range cases {
212+
got := l.ReadEval(c.input, false)
213+
if got.lvalString() != c.want {
214+
t.Errorf("ReadEval input: \"%s\" returned: \"%s\", actually expected: \"%s\"", c.input, got.lvalString(), c.want)
215+
}
216+
}
217+
}

lispy/lval.go

+26-2
Original file line numberDiff line numberDiff line change
@@ -320,18 +320,42 @@ func lvalCall(e *lenv, f *lval, a *lval) *lval {
320320
}
321321
// Pop the first symbol from the formal
322322
sym := f.formals.lvalPop(0)
323+
// Special case to deal with '&'
324+
if sym.sym == "&" {
325+
// Ensure '&' is followed by another symbol
326+
if f.formals.cellCount() != 1 {
327+
return lvalErr("Function format invalid. Symbol '&' was not followed by 1 symbol.)")
328+
}
329+
// Next formal should be bound to the remaining arguments
330+
nsym := f.formals.lvalPop(0)
331+
f.env.lenvPut(nsym, builtinList(e, a))
332+
break
333+
}
323334
// Pop the next argument from the list
324335
val := a.lvalPop(0)
325336
// Bind a copy into the function's environment
326337
f.env.lenvPut(sym, val)
327338
}
339+
// If '&' remains in the formal list, bind to an empty list
340+
if f.formals.cellCount() > 0 && f.formals.cells[0].sym == "&" {
341+
// Check to ensure that '&' is not passed in invalidly
342+
if f.formals.cellCount() != 2 {
343+
return lvalErr("Function forma invalid. Symbol '&' not followed by single symbol")
344+
}
345+
// Pop '&' symbol
346+
f.formals.lvalPop(0)
347+
// Pop the next symbol and create an empty list
348+
sym := f.formals.lvalPop(0)
349+
val := lvalQexpr()
350+
// Bind to the environment
351+
f.env.lenvPut(sym, val)
352+
}
328353
// If all formals have been bound, evaluate
329354
if f.formals.cellCount() == 0 {
330355
// Set environment parent to evaluation environment
331356
f.env.par = e
332357
// Evaluate and return
333-
ret := builtinEval(f.env, lvalAdd(lvalSexpr(), lvalCopy(f.body)))
334-
return ret
358+
return builtinEval(f.env, lvalAdd(lvalSexpr(), lvalCopy(f.body)))
335359
}
336360
// Otherwise, return partially evaluated function
337361
return lvalCopy(f)

0 commit comments

Comments
 (0)