Optional eval (#92)

* Optional arguments
This commit is contained in:
Stanisław Barzowski 2017-10-10 00:06:14 -04:00 committed by Dave Cunningham
parent f0f70419f8
commit ba0f236b14
72 changed files with 432 additions and 89 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ coverage.out
/tests_path.source
/jsonnet/jsonnet
*.so
*.prof

View File

@ -322,8 +322,8 @@ type NamedParameter struct {
}
type Parameters struct {
Positional Identifiers
Named []NamedParameter
Required Identifiers
Optional []NamedParameter
}
// ---------------------------------------------------------------------------

View File

@ -224,7 +224,7 @@ func builtinLength(e *evaluator, xp potentialValue) (value, error) {
case *valueString:
num = x.length()
case *valueFunction:
num = len(x.parameters())
num = len(x.parameters().required)
default:
return nil, e.typeErrorGeneral(x)
}
@ -614,13 +614,12 @@ func getBuiltinEvaluator(e *evaluator, name ast.Identifier) *evaluator {
}
func (b *UnaryBuiltin) EvalCall(args callArguments, e *evaluator) (value, error) {
// TODO check args
return b.function(getBuiltinEvaluator(e, b.name), args.positional[0])
flatArgs := flattenArgs(args, b.Parameters())
return b.function(getBuiltinEvaluator(e, b.name), flatArgs[0])
}
func (b *UnaryBuiltin) Parameters() ast.Identifiers {
return b.parameters
func (b *UnaryBuiltin) Parameters() Parameters {
return Parameters{required: b.parameters}
}
type BinaryBuiltin struct {
@ -629,13 +628,38 @@ type BinaryBuiltin struct {
parameters ast.Identifiers
}
func (b *BinaryBuiltin) EvalCall(args callArguments, e *evaluator) (value, error) {
// TODO check args
return b.function(getBuiltinEvaluator(e, b.name), args.positional[0], args.positional[1])
// flattenArgs transforms all arguments to a simple array of positional arguments.
// It's needed, because it's possible to use named arguments for required parameters.
// For example both `toString("x")` and `toString(a="x")` are allowed.
// It assumes that we have already checked for duplicates.
func flattenArgs(args callArguments, params Parameters) []potentialValue {
if len(args.named) == 0 {
return args.positional
}
if len(params.optional) != 0 {
panic("Can't normalize arguments if optional parameters are present")
}
needed := make(map[ast.Identifier]int)
for i := len(args.positional); i < len(params.required); i++ {
needed[params.required[i]] = i
}
func (b *BinaryBuiltin) Parameters() ast.Identifiers {
return b.parameters
flatArgs := make([]potentialValue, len(params.required))
copy(flatArgs, args.positional)
for _, arg := range args.named {
flatArgs[needed[arg.name]] = arg.pv
}
return flatArgs
}
func (b *BinaryBuiltin) EvalCall(args callArguments, e *evaluator) (value, error) {
flatArgs := flattenArgs(args, b.Parameters())
return b.function(getBuiltinEvaluator(e, b.name), flatArgs[0], flatArgs[1])
}
func (b *BinaryBuiltin) Parameters() Parameters {
return Parameters{required: b.parameters}
}
type TernaryBuiltin struct {
@ -645,12 +669,12 @@ type TernaryBuiltin struct {
}
func (b *TernaryBuiltin) EvalCall(args callArguments, e *evaluator) (value, error) {
// TODO check args
return b.function(getBuiltinEvaluator(e, b.name), args.positional[0], args.positional[1], args.positional[2])
flatArgs := flattenArgs(args, b.Parameters())
return b.function(getBuiltinEvaluator(e, b.name), flatArgs[0], flatArgs[1], flatArgs[2])
}
func (b *TernaryBuiltin) Parameters() ast.Identifiers {
return b.parameters
func (b *TernaryBuiltin) Parameters() Parameters {
return Parameters{required: b.parameters}
}
var desugaredBop = map[ast.BinaryOp]ast.Identifier{
@ -698,7 +722,7 @@ var uopBuiltins = []*UnaryBuiltin{
var funcBuiltins = map[string]evalCallable{
"extVar": &UnaryBuiltin{name: "extVar", function: builtinExtVar, parameters: ast.Identifiers{"x"}},
"length": &UnaryBuiltin{name: "length", function: builtinLength, parameters: ast.Identifiers{"x"}},
"toString": &UnaryBuiltin{name: "toString", function: builtinToString, parameters: ast.Identifiers{"x"}},
"toString": &UnaryBuiltin{name: "toString", function: builtinToString, parameters: ast.Identifiers{"a"}},
"makeArray": &BinaryBuiltin{name: "makeArray", function: builtinMakeArray, parameters: ast.Identifiers{"sz", "func"}},
"flatMap": &BinaryBuiltin{name: "flatMap", function: builtinFlatMap, parameters: ast.Identifiers{"func", "arr"}},
"filter": &BinaryBuiltin{name: "filter", function: builtinFilter, parameters: ast.Identifiers{"func", "arr"}},

View File

@ -86,28 +86,6 @@ func stringUnescape(loc *ast.LocationRange, s string) (string, error) {
}
func desugarFields(location ast.LocationRange, fields *ast.ObjectFields, objLevel int) error {
// Desugar children
for i := range *fields {
field := &((*fields)[i])
if field.Expr1 != nil {
err := desugar(&field.Expr1, objLevel)
if err != nil {
return err
}
}
err := desugar(&field.Expr2, objLevel+1)
if err != nil {
return err
}
if field.Expr3 != nil {
err := desugar(&field.Expr3, objLevel+1)
if err != nil {
return err
}
}
}
// Simplify asserts
for i := range *fields {
field := &(*fields)[i]
@ -187,7 +165,7 @@ func desugarFields(location ast.LocationRange, fields *ast.ObjectFields, objLeve
func simpleLambda(body ast.Node, paramName ast.Identifier) ast.Node {
return &ast.Function{
Body: body,
Parameters: ast.Parameters{Positional: ast.Identifiers{paramName}},
Parameters: ast.Parameters{Required: ast.Identifiers{paramName}},
}
}
@ -233,8 +211,6 @@ func desugarObjectComp(comp *ast.ObjectComp, objLevel int) (ast.Node, error) {
comp.Fields = append(comp.Fields, ast.ObjectFieldLocalNoMethod(&dollar, &ast.Self{}))
}
// TODO(sbarzowski) find a consistent convention to prevent desugaring the same thing twice
// here we deeply desugar fields and it will happen again
err := desugarFields(*comp.Loc(), &comp.Fields, objLevel+1)
if err != nil {
return nil, err
@ -383,6 +359,7 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
Expr: buildStdCall(desugaredBop[ast.BopManifestEqual], node.Left, node.Right),
}
} else if node.Op == ast.BopIn {
// reversed order of arguments
*astPtr = buildStdCall(funcname, node.Right, node.Left)
} else {
*astPtr = buildStdCall(funcname, node.Left, node.Right)
@ -429,6 +406,13 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
}
case *ast.Function:
for i := range node.Parameters.Optional {
param := &node.Parameters.Optional[i]
err = desugar(&param.DefaultArg, objLevel)
if err != nil {
return
}
}
err = desugar(&node.Body, objLevel)
if err != nil {
return
@ -476,7 +460,10 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
node.Step = &ast.LiteralNull{}
}
*astPtr = buildStdCall("slice", node.Target, node.BeginIndex, node.EndIndex, node.Step)
desugar(astPtr, objLevel)
err = desugar(astPtr, objLevel)
if err != nil {
return
}
case *ast.Local:
for i := range node.Binds {
@ -529,9 +516,32 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
}
*astPtr = buildDesugaredObject(node.NodeBase, node.Fields)
err = desugar(astPtr, objLevel)
if err != nil {
return
}
case *ast.DesugaredObject:
return nil
for i := range node.Fields {
field := &((node.Fields)[i])
if field.Name != nil {
err := desugar(&field.Name, objLevel)
if err != nil {
return err
}
}
err := desugar(&field.Body, objLevel+1)
if err != nil {
return err
}
}
for i := range node.Asserts {
assert := &((node.Asserts)[i])
err := desugar(assert, objLevel+1)
if err != nil {
return err
}
}
case *ast.ObjectComp:
comp, err := desugarObjectComp(node, objLevel)

View File

@ -464,11 +464,16 @@ func (i *interpreter) evaluate(a ast.Node) (value, error) {
arguments := callArguments{
positional: make([]potentialValue, len(ast.Arguments.Positional)),
named: make([]namedCallArgument, len(ast.Arguments.Named)),
}
for i, arg := range ast.Arguments.Positional {
arguments.positional[i] = makeThunk(argEnv, arg)
}
for i, arg := range ast.Arguments.Named {
arguments.named[i] = namedCallArgument{name: arg.Name, pv: makeThunk(argEnv, arg.Arg)}
}
return e.evaluate(function.call(arguments))
default:

View File

@ -23,7 +23,6 @@ import (
"path/filepath"
"strings"
"testing"
"unicode/utf8"
"github.com/sergi/go-diff/diffmatchpatch"
)
@ -40,25 +39,6 @@ type mainTest struct {
golden string
}
func removeExcessiveWhitespace(s string) string {
var buf bytes.Buffer
separated := true
for i, w := 0, 0; i < len(s); i += w {
runeValue, width := utf8.DecodeRuneInString(s[i:])
if runeValue == '\n' || runeValue == ' ' {
if !separated {
buf.WriteString(" ")
separated = true
}
} else {
buf.WriteRune(runeValue)
separated = false
}
w = width
}
return buf.String()
}
func setExtVars(vm *VM) {
// TODO(sbarzowski) extract, so that it's possible to define extvars per-test
// Check that it doesn't get evaluated.

View File

@ -0,0 +1 @@
(function(a=[1, b[1]], b=[a[0], 2]) [a, b])()

View File

@ -313,9 +313,9 @@ func addContext(node ast.Node, context *string, bind string) {
case *ast.Function:
funContext := functionContext(bind)
addContext(node.Body, funContext, anonymous)
for i := range node.Parameters.Named {
for i := range node.Parameters.Optional {
// Default arguments have the same context as the function body.
addContext(node.Parameters.Named[i].DefaultArg, funContext, anonymous)
addContext(node.Parameters.Optional[i].DefaultArg, funContext, anonymous)
}
case *ast.Object:
// TODO(sbarzowski) include fieldname, maybe even chains

View File

@ -200,10 +200,10 @@ func (p *parser) parseParameters(elementKind string) (*ast.Parameters, bool, err
if !ok {
return nil, false, MakeStaticError(fmt.Sprintf("Expected simple identifier but got a complex expression."), *arg.Loc())
}
params.Positional = append(params.Positional, *id)
params.Required = append(params.Required, *id)
}
for _, arg := range args.Named {
params.Named = append(params.Named, ast.NamedParameter{Name: arg.Name, DefaultArg: arg.Arg})
params.Optional = append(params.Optional, ast.NamedParameter{Name: arg.Name, DefaultArg: arg.Arg})
}
return &params, trailingComma, nil
}

View File

@ -46,6 +46,9 @@ func analyzeVisit(a ast.Node, inObject bool, vars ast.IdentifierSet) error {
for _, arg := range a.Arguments.Positional {
visitNext(arg, inObject, vars, s)
}
for _, arg := range a.Arguments.Named {
visitNext(arg.Arg, inObject, vars, s)
}
case *ast.Array:
for _, elem := range a.Elements {
visitNext(elem, inObject, vars, s)
@ -60,18 +63,24 @@ func analyzeVisit(a ast.Node, inObject bool, vars ast.IdentifierSet) error {
case *ast.Error:
visitNext(a.Expr, inObject, vars, s)
case *ast.Function:
// TODO(sbarzowski) check duplicate function parameters
// or maybe somewhere else as it doesn't require any context
newVars := vars.Clone()
for _, param := range a.Parameters.Positional {
for _, param := range a.Parameters.Required {
newVars.Add(param)
}
for _, param := range a.Parameters.Optional {
newVars.Add(param.Name)
}
for _, param := range a.Parameters.Optional {
visitNext(param.DefaultArg, inObject, newVars, s)
}
visitNext(a.Body, inObject, newVars, s)
// Parameters are free inside the body, but not visible here or outside
for _, param := range a.Parameters.Positional {
for _, param := range a.Parameters.Required {
s.freeVars.Remove(param)
}
// TODO(sbarzowski) when we have default values of params check them
for _, param := range a.Parameters.Optional {
s.freeVars.Remove(param.Name)
}
case *ast.Import:
//nothing to do here
case *ast.ImportStr:

1
std.thisFile.jsonnet Normal file
View File

@ -0,0 +1 @@
std.thisFile

View File

@ -1,4 +1,4 @@
RUNTIME ERROR: function expected 1 argument(s), but got 0
RUNTIME ERROR: Missing argument: x
-------------------------------------------------
testdata/bad_function_call:1:1-18 $

View File

@ -1,4 +1,4 @@
RUNTIME ERROR: function expected 1 argument(s), but got 2
RUNTIME ERROR: Function expected 1 positional argument(s), but got 2
-------------------------------------------------
testdata/bad_function_call2:1:1-22 $

View File

@ -1,4 +1,4 @@
RUNTIME ERROR: function expected 1 argument(s), but got 2
RUNTIME ERROR: Function expected 1 positional argument(s), but got 2
-------------------------------------------------
testdata/bad_function_call_and_error:1:1-38 $

1
testdata/optional_args.golden vendored Normal file
View File

@ -0,0 +1 @@
42

1
testdata/optional_args.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
local foo(x=42) = x; foo()

1
testdata/optional_args10.golden vendored Normal file
View File

@ -0,0 +1 @@
2

1
testdata/optional_args10.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(x, y) x)(y=1, x=2)

10
testdata/optional_args11.golden vendored Normal file
View File

@ -0,0 +1,10 @@
RUNTIME ERROR: Argument x already provided
-------------------------------------------------
testdata/optional_args11:1:1-30 $
(function(x, y) 42)(42, x=42)
-------------------------------------------------
During evaluation

1
testdata/optional_args11.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(x, y) 42)(42, x=42)

1
testdata/optional_args12.golden vendored Normal file
View File

@ -0,0 +1 @@
42

1
testdata/optional_args12.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(x=42, y=42) 42)(y=42)

10
testdata/optional_args13.golden vendored Normal file
View File

@ -0,0 +1,10 @@
RUNTIME ERROR: Missing argument: y
-------------------------------------------------
testdata/optional_args13:1:1-26 $
(function(x, y) 42)(x=42)
-------------------------------------------------
During evaluation

1
testdata/optional_args13.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(x, y) 42)(x=42)

1
testdata/optional_args14.golden vendored Normal file
View File

@ -0,0 +1 @@
42

1
testdata/optional_args14.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(a=b, b=a) 42)()

1
testdata/optional_args15.golden vendored Normal file
View File

@ -0,0 +1 @@
42

1
testdata/optional_args15.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ g: 42, f(a=self.g): a }.f()

1
testdata/optional_args16.golden vendored Normal file
View File

@ -0,0 +1 @@
42

1
testdata/optional_args16.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(x=17) x)(42)

1
testdata/optional_args17.golden vendored Normal file
View File

@ -0,0 +1 @@
3

1
testdata/optional_args17.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(x, y=42) x + y)(y=1, x=2)

6
testdata/optional_args18.golden vendored Normal file
View File

@ -0,0 +1,6 @@
[
42,
43,
44,
45
]

1
testdata/optional_args18.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(a1=1, a2=a1+1, a3=a2+1, a4=a3+1) [a1, a2, a3, a4])(a1=42)

6
testdata/optional_args19.golden vendored Normal file
View File

@ -0,0 +1,6 @@
[
45,
44,
43,
42
]

1
testdata/optional_args19.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(a1=a2+1, a2=a3+1, a3=a4+1, a4=1) [a1, a2, a3, a4])(a4=42)

5
testdata/optional_args2.golden vendored Normal file
View File

@ -0,0 +1,5 @@
{
"x": 2,
"y": 4,
"z": 2
}

4
testdata/optional_args2.jsonnet vendored Normal file
View File

@ -0,0 +1,4 @@
local x = 1;
local foo(x=2, y=3, z=x) = {x: x, y: y, z: z};
local x = 4;
foo(y=x)

18
testdata/optional_args20.golden vendored Normal file
View File

@ -0,0 +1,18 @@
[
[
42,
49
],
[
43,
48
],
[
44,
47
],
[
45,
46
]
]

1
testdata/optional_args20.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(x, a1=[x, a2[1] + 1], a2=[a1[0] + 1, a3[1] + 1], a3=[a2[0] + 1, a4[1] + 1], a4=[a3[0] + 1, a4[0] + 1]) [a1, a2, a3, a4])(x=42)

1
testdata/optional_args21.golden vendored Normal file
View File

@ -0,0 +1 @@
2

2
testdata/optional_args21.jsonnet vendored Normal file
View File

@ -0,0 +1,2 @@
local foo(x=2, z=x) = x;
foo()

10
testdata/optional_args22.golden vendored Normal file
View File

@ -0,0 +1,10 @@
[
[
42,
42
],
[
42,
17
]
]

2
testdata/optional_args22.jsonnet vendored Normal file
View File

@ -0,0 +1,2 @@
local foo(x) = local bar(y=x) = [x, y]; bar;
[foo(42)(), foo(42)(17)]

5
testdata/optional_args3.golden vendored Normal file
View File

@ -0,0 +1,5 @@
{
"x": 2,
"y": 1,
"z": 2
}

3
testdata/optional_args3.jsonnet vendored Normal file
View File

@ -0,0 +1,3 @@
local x = 1;
local foo(x=2, y=3, z=x) = {x: x, y: y, z: z};
foo(y=x)

5
testdata/optional_args4.golden vendored Normal file
View File

@ -0,0 +1,5 @@
{
"x": 5,
"y": 4,
"z": 5
}

4
testdata/optional_args4.jsonnet vendored Normal file
View File

@ -0,0 +1,4 @@
local x = 1;
local foo(x=2, y=3, z=x) = {x: x, y: y, z: z};
local x = 4;
foo(x=5, y=x)

1
testdata/optional_args5.golden vendored Normal file
View File

@ -0,0 +1 @@
42

2
testdata/optional_args5.jsonnet vendored Normal file
View File

@ -0,0 +1,2 @@
local foo(x, y=x) = y;
foo(42)

1
testdata/optional_args6.golden vendored Normal file
View File

@ -0,0 +1 @@
42

2
testdata/optional_args6.jsonnet vendored Normal file
View File

@ -0,0 +1,2 @@
local foo(x, y=x) = y;
foo(x=42)

1
testdata/optional_args7.golden vendored Normal file
View File

@ -0,0 +1 @@
42

2
testdata/optional_args7.jsonnet vendored Normal file
View File

@ -0,0 +1,2 @@
local foo(x) = x;
foo(x=42)

10
testdata/optional_args8.golden vendored Normal file
View File

@ -0,0 +1,10 @@
RUNTIME ERROR: Function has no parameter y
-------------------------------------------------
testdata/optional_args8:2:1-10 $
foo(y=17)
-------------------------------------------------
During evaluation

2
testdata/optional_args8.jsonnet vendored Normal file
View File

@ -0,0 +1,2 @@
local foo(x=42) = x;
foo(y=17)

10
testdata/optional_args9.golden vendored Normal file
View File

@ -0,0 +1,10 @@
RUNTIME ERROR: Argument x already provided
-------------------------------------------------
testdata/optional_args9:1:1-26 $
(function(x) x)(42, x=42)
-------------------------------------------------
During evaluation

1
testdata/optional_args9.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(x) x)(42, x=42)

7
testdata/std.makeArrayNamed.golden vendored Normal file
View File

@ -0,0 +1,7 @@
[
0,
1,
2,
3,
4
]

1
testdata/std.makeArrayNamed.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.makeArray(sz=5, func=function(i) i)

7
testdata/std.makeArrayNamed2.golden vendored Normal file
View File

@ -0,0 +1,7 @@
[
0,
1,
2,
3,
4
]

1
testdata/std.makeArrayNamed2.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.makeArray(func=function(i) i, sz=5)

10
testdata/std.makeArrayNamed3.golden vendored Normal file
View File

@ -0,0 +1,10 @@
RUNTIME ERROR: Function has no parameter blahblah
-------------------------------------------------
testdata/std.makeArrayNamed3:1:1-54 $
std.makeArray(blahblah=5, blahblahblah=function(i) i)
-------------------------------------------------
During evaluation

1
testdata/std.makeArrayNamed3.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.makeArray(blahblah=5, blahblahblah=function(i) i)

5
testdata/std.makeArrayNamed4.golden vendored Normal file
View File

@ -0,0 +1,5 @@
[
0,
1,
2
]

1
testdata/std.makeArrayNamed4.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.makeArray(3, func=function(i) i)

1
testdata/std.toString8.golden vendored Normal file
View File

@ -0,0 +1 @@
"42"

1
testdata/std.toString8.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.toString(a=42)

10
testdata/too_many_arguments.golden vendored Normal file
View File

@ -0,0 +1,10 @@
RUNTIME ERROR: Function expected 3 positional argument(s), but got 4
-------------------------------------------------
testdata/too_many_arguments:1:1-35 $
(function(x, y, z) 42)(1, 2, 3, 4)
-------------------------------------------------
During evaluation

1
testdata/too_many_arguments.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
(function(x, y, z) 42)(1, 2, 3, 4)

View File

@ -96,6 +96,18 @@ func (th *callThunk) getValue(i *interpreter, trace *TraceElement) (value, error
return th.function.EvalCall(th.args, evaluator)
}
// deferredThunk allows deferring creation of evaluable until it's actually needed.
// It's useful for self-recursive structures.
type deferredThunk func() evaluable
func (th deferredThunk) getValue(i *interpreter, trace *TraceElement) (value, error) {
return th().getValue(i, trace)
}
func makeDeferredThunk(th deferredThunk) potentialValue {
return makeCachedThunk(th)
}
// cachedThunk is a wrapper that caches the value of a potentialValue after
// the first evaluation.
// Note: All potentialValues are required to provide the same value every time,
@ -190,28 +202,81 @@ type closure struct {
// arguments should be added to it, before executing it
env environment
function *ast.Function
params Parameters
}
func (closure *closure) EvalCall(arguments callArguments, e *evaluator) (value, error) {
argThunks := make(bindingFrame)
parameters := closure.Parameters()
for i, arg := range arguments.positional {
argThunks[closure.function.Parameters.Positional[i]] = arg
var name ast.Identifier
if i < len(parameters.required) {
name = parameters.required[i]
} else {
name = parameters.optional[i-len(parameters.required)].name
}
argThunks[name] = arg
}
calledEnvironment := makeEnvironment(
for _, arg := range arguments.named {
argThunks[arg.name] = arg.pv
}
var calledEnvironment environment
for i := range parameters.optional {
param := &parameters.optional[i]
if _, exists := argThunks[param.name]; !exists {
argThunks[param.name] = makeDeferredThunk(func() evaluable {
// Default arguments are evaluated in the same environment as function body
return param.defaultArg.inEnv(&calledEnvironment)
})
}
}
calledEnvironment = makeEnvironment(
addBindings(closure.env.upValues, argThunks),
closure.env.sb,
)
return e.evalInCleanEnv(&calledEnvironment, closure.function.Body)
}
func (closure *closure) Parameters() ast.Identifiers {
return closure.function.Parameters.Positional
func (closure *closure) Parameters() Parameters {
return closure.params
}
func prepareClosureParameters(parameters ast.Parameters, env environment) Parameters {
optionalParameters := make([]namedParameter, 0, len(parameters.Optional))
for _, named := range parameters.Optional {
optionalParameters = append(optionalParameters, namedParameter{
name: named.Name,
defaultArg: &defaultArgument{
body: named.DefaultArg,
},
})
}
return Parameters{
required: parameters.Required,
optional: optionalParameters,
}
}
func makeClosure(env environment, function *ast.Function) *closure {
return &closure{
env: env,
function: function,
params: prepareClosureParameters(function.Parameters, env),
}
}
// partialPotentialValue
// -------------------------------------
type defaultArgument struct {
body ast.Node
}
func (da *defaultArgument) inEnv(env *environment) potentialValue {
return makeThunk(*env, da.body)
}

View File

@ -235,24 +235,64 @@ type valueFunction struct {
// TODO(sbarzowski) better name?
type evalCallable interface {
EvalCall(args callArguments, e *evaluator) (value, error)
Parameters() ast.Identifiers
Parameters() Parameters
}
type partialPotentialValue interface {
inEnv(env *environment) potentialValue
}
func (f *valueFunction) call(args callArguments) potentialValue {
return makeCallThunk(f.ec, args)
}
func (f *valueFunction) parameters() ast.Identifiers {
func (f *valueFunction) parameters() Parameters {
return f.ec.Parameters()
}
func checkArguments(e *evaluator, args callArguments, params ast.Identifiers) error {
// TODO(sbarzowski) this will get much more complicated with named params
func checkArguments(e *evaluator, args callArguments, params Parameters) error {
received := make(map[ast.Identifier]bool)
accepted := make(map[ast.Identifier]bool)
numPassed := len(args.positional)
numExpected := len(params)
if numPassed != numExpected {
return e.Error(fmt.Sprintf("function expected %v argument(s), but got %v", numExpected, numPassed))
numExpected := len(params.required) + len(params.optional)
if numPassed > numExpected {
return e.Error(fmt.Sprintf("Function expected %v positional argument(s), but got %v", numExpected, numPassed))
}
for _, param := range params.required {
accepted[param] = true
}
for _, param := range params.optional {
accepted[param.name] = true
}
for i := range args.positional {
if i < len(params.required) {
received[params.required[i]] = true
} else {
received[params.optional[i-len(params.required)].name] = true
}
}
for _, arg := range args.named {
if _, present := received[arg.name]; present {
return e.Error(fmt.Sprintf("Argument %v already provided", arg.name))
}
if _, present := accepted[arg.name]; !present {
return e.Error(fmt.Sprintf("Function has no parameter %v", arg.name))
}
received[arg.name] = true
}
for _, param := range params.required {
if _, present := received[param]; !present {
return e.Error(fmt.Sprintf("Missing argument: %v", param))
}
}
return nil
}
@ -260,9 +300,28 @@ func (f *valueFunction) typename() string {
return "function"
}
type Parameters struct {
required ast.Identifiers
optional []namedParameter
}
type namedParameter struct {
name ast.Identifier
defaultArg potentialValueInEnv
}
type potentialValueInEnv interface {
inEnv(env *environment) potentialValue
}
type callArguments struct {
positional []potentialValue
// TODO named arguments
named []namedCallArgument
}
type namedCallArgument struct {
name ast.Identifier
pv potentialValue
}
func args(xs ...potentialValue) callArguments {