diff --git a/eval/closure.go b/eval/closure.go index d3763eb1..ec3c700d 100644 --- a/eval/closure.go +++ b/eval/closure.go @@ -7,20 +7,25 @@ import ( var ErrArityMismatch = errors.New("arity mismatch") +var unnamedRestArg = ":" + // Closure is a closure. type Closure struct { ArgNames []string + // The name for the rest argument. If empty, the function has fixed arity. + // If equal to unnamedRestArg, the rest argument is unnamed but can be + // accessed via $args. + RestArg string Op Op Captured map[string]Variable - Variadic bool } func (*Closure) Kind() string { return "fn" } -func newClosure(a []string, op Op, e map[string]Variable, v bool) *Closure { - return &Closure{a, op, e, v} +func newClosure(a []string, r string, op Op, e map[string]Variable) *Closure { + return &Closure{a, r, op, e} } func (c *Closure) Repr(int) string { @@ -29,10 +34,15 @@ func (c *Closure) Repr(int) string { // Call calls a closure. func (c *Closure) Call(ec *EvalCtx, args []Value) { - // TODO Support optional/rest argument // TODO Support keyword arguments - if !c.Variadic && len(args) != len(c.ArgNames) { - throw(ErrArityMismatch) + if c.RestArg != "" { + if len(c.ArgNames) > len(args) { + throw(ErrArityMismatch) + } + } else { + if len(c.ArgNames) != len(args) { + throw(ErrArityMismatch) + } } // This evalCtx is dedicated to the current form, so we modify it in place. @@ -46,10 +56,11 @@ func (c *Closure) Call(ec *EvalCtx, args []Value) { } // Make local namespace and pass arguments. ec.local = make(map[string]Variable) - if !c.Variadic { - for i, name := range c.ArgNames { - ec.local[name] = NewPtrVariable(args[i]) - } + for i, name := range c.ArgNames { + ec.local[name] = NewPtrVariable(args[i]) + } + if c.RestArg != "" && c.RestArg != unnamedRestArg { + ec.local[c.RestArg] = NewPtrVariable(NewList(args[len(c.ArgNames):]...)) } ec.local["args"] = NewPtrVariable(List{&args}) ec.local["kwargs"] = NewPtrVariable(Map{&map[Value]Value{}}) diff --git a/eval/compileValuesOp.go b/eval/compileValuesOp.go index 1c1c09fc..f8844351 100644 --- a/eval/compileValuesOp.go +++ b/eval/compileValuesOp.go @@ -346,26 +346,44 @@ func captureOutput(ec *EvalCtx, op Op) []Value { func (cp *compiler) lambda(n *parse.Primary) ValuesOpFunc { // Collect argument names var argNames []string - var variadic bool - if n.List != nil { + var restArg string + if n.List == nil { + // { chunk } + restArg = unnamedRestArg + } else { // [argument list]{ chunk } argNames = make([]string, len(n.List.Compounds)) for i, arg := range n.List.Compounds { - name := mustString(cp, arg, "expect string") - argNames[i] = name + qname := mustString(cp, arg, "expect string") + splice, ns, name := parseVariable(qname) + if ns != "" { + cp.errorpf(arg.Begin(), arg.End(), "must be unqualified") + } + if name == "" { + cp.errorpf(arg.Begin(), arg.End(), "argument name must not be empty") + } + if splice { + if i != len(n.List.Compounds)-1 { + cp.errorpf(arg.Begin(), arg.End(), "only the last argument may have @") + } + restArg = name + argNames = argNames[:i] + } else { + argNames[i] = name + } } - } else { - // { chunk } - variadic = true } - // XXX The fiddlings with cp.capture is likely wrong. + // XXX The fiddlings with cp.capture is error-prone. thisScope := cp.pushScope() - thisScope["args"] = true - thisScope["kwargs"] = true for _, argName := range argNames { thisScope[argName] = true } + if restArg != "" { + thisScope[restArg] = true + } + thisScope["args"] = true + thisScope["kwargs"] = true op := cp.chunkOp(n.Chunk) capture := cp.capture cp.capture = scope{} @@ -380,7 +398,7 @@ func (cp *compiler) lambda(n *parse.Primary) ValuesOpFunc { for name := range capture { evCapture[name] = ec.ResolveVar("", name) } - return []Value{newClosure(argNames, op, evCapture, variadic)} + return []Value{newClosure(argNames, restArg, op, evCapture)} } } diff --git a/eval/eval_test.go b/eval/eval_test.go index e9ebc706..5021a734 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -150,11 +150,21 @@ var evalTests = []struct { {inc2,put2}=(f); $put2; $inc2; $put2`, strs("0", "1", "0", "1"), nomore}, - // fn and return. + // fn. {"fn f [x]{ put x=$x'.' }; f lorem; f ipsum", strs("x=lorem.", "x=ipsum."), nomore}, + // return. {"fn f []{ put a; return; put b }; f", strs("a"), nomore}, + // rest args and $args. + {"[x @xs]{ put $x $xs $args } a b c", + []Value{String("a"), + NewList(String("b"), String("c")), + NewList(String("a"), String("b"), String("c"))}, nomore}, + // $args. + {"{ put $args } lorem ipsum", + []Value{NewList(String("lorem"), String("ipsum"))}, nomore}, + // Namespaces // Pseudo-namespaces local: and up: {"x=lorem; []{local:x=ipsum; put $up:x $local:x}",