Implement rest arg for function.

This fixes #83.
This commit is contained in:
Qi Xiao 2016-02-26 01:54:27 +01:00
parent 9f4a6413c1
commit 00290a6f2b
3 changed files with 61 additions and 22 deletions

View File

@ -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{}})

View File

@ -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)}
}
}

View File

@ -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}",