mirror of
https://github.com/google/go-jsonnet.git
synced 2025-09-28 17:01:02 +02:00
parent
f0f70419f8
commit
ba0f236b14
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ coverage.out
|
||||
/tests_path.source
|
||||
/jsonnet/jsonnet
|
||||
*.so
|
||||
*.prof
|
||||
|
@ -322,8 +322,8 @@ type NamedParameter struct {
|
||||
}
|
||||
|
||||
type Parameters struct {
|
||||
Positional Identifiers
|
||||
Named []NamedParameter
|
||||
Required Identifiers
|
||||
Optional []NamedParameter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
56
builtins.go
56
builtins.go
@ -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
|
||||
}
|
||||
|
||||
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) Parameters() ast.Identifiers {
|
||||
return b.parameters
|
||||
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"}},
|
||||
|
64
desugarer.go
64
desugarer.go
@ -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(¶m.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)
|
||||
|
@ -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:
|
||||
|
20
main_test.go
20
main_test.go
@ -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.
|
||||
|
1
mutually_recursive_defaults.input
Normal file
1
mutually_recursive_defaults.input
Normal file
@ -0,0 +1 @@
|
||||
(function(a=[1, b[1]], b=[a[0], 2]) [a, b])()
|
@ -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
|
||||
|
@ -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 ¶ms, trailingComma, nil
|
||||
}
|
||||
|
@ -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
1
std.thisFile.jsonnet
Normal file
@ -0,0 +1 @@
|
||||
std.thisFile
|
2
testdata/bad_function_call.golden
vendored
2
testdata/bad_function_call.golden
vendored
@ -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 $
|
||||
|
||||
|
2
testdata/bad_function_call2.golden
vendored
2
testdata/bad_function_call2.golden
vendored
@ -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 $
|
||||
|
||||
|
2
testdata/bad_function_call_and_error.golden
vendored
2
testdata/bad_function_call_and_error.golden
vendored
@ -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
1
testdata/optional_args.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
42
|
1
testdata/optional_args.jsonnet
vendored
Normal file
1
testdata/optional_args.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
local foo(x=42) = x; foo()
|
1
testdata/optional_args10.golden
vendored
Normal file
1
testdata/optional_args10.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
2
|
1
testdata/optional_args10.jsonnet
vendored
Normal file
1
testdata/optional_args10.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
(function(x, y) x)(y=1, x=2)
|
10
testdata/optional_args11.golden
vendored
Normal file
10
testdata/optional_args11.golden
vendored
Normal 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
1
testdata/optional_args11.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
(function(x, y) 42)(42, x=42)
|
1
testdata/optional_args12.golden
vendored
Normal file
1
testdata/optional_args12.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
42
|
1
testdata/optional_args12.jsonnet
vendored
Normal file
1
testdata/optional_args12.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
(function(x=42, y=42) 42)(y=42)
|
10
testdata/optional_args13.golden
vendored
Normal file
10
testdata/optional_args13.golden
vendored
Normal 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
1
testdata/optional_args13.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
(function(x, y) 42)(x=42)
|
1
testdata/optional_args14.golden
vendored
Normal file
1
testdata/optional_args14.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
42
|
1
testdata/optional_args14.jsonnet
vendored
Normal file
1
testdata/optional_args14.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
(function(a=b, b=a) 42)()
|
1
testdata/optional_args15.golden
vendored
Normal file
1
testdata/optional_args15.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
42
|
1
testdata/optional_args15.jsonnet
vendored
Normal file
1
testdata/optional_args15.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
{ g: 42, f(a=self.g): a }.f()
|
1
testdata/optional_args16.golden
vendored
Normal file
1
testdata/optional_args16.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
42
|
1
testdata/optional_args16.jsonnet
vendored
Normal file
1
testdata/optional_args16.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
(function(x=17) x)(42)
|
1
testdata/optional_args17.golden
vendored
Normal file
1
testdata/optional_args17.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
3
|
1
testdata/optional_args17.jsonnet
vendored
Normal file
1
testdata/optional_args17.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
(function(x, y=42) x + y)(y=1, x=2)
|
6
testdata/optional_args18.golden
vendored
Normal file
6
testdata/optional_args18.golden
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
[
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45
|
||||
]
|
1
testdata/optional_args18.jsonnet
vendored
Normal file
1
testdata/optional_args18.jsonnet
vendored
Normal 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
6
testdata/optional_args19.golden
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
[
|
||||
45,
|
||||
44,
|
||||
43,
|
||||
42
|
||||
]
|
1
testdata/optional_args19.jsonnet
vendored
Normal file
1
testdata/optional_args19.jsonnet
vendored
Normal 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
5
testdata/optional_args2.golden
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"x": 2,
|
||||
"y": 4,
|
||||
"z": 2
|
||||
}
|
4
testdata/optional_args2.jsonnet
vendored
Normal file
4
testdata/optional_args2.jsonnet
vendored
Normal 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
18
testdata/optional_args20.golden
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
[
|
||||
[
|
||||
42,
|
||||
49
|
||||
],
|
||||
[
|
||||
43,
|
||||
48
|
||||
],
|
||||
[
|
||||
44,
|
||||
47
|
||||
],
|
||||
[
|
||||
45,
|
||||
46
|
||||
]
|
||||
]
|
1
testdata/optional_args20.jsonnet
vendored
Normal file
1
testdata/optional_args20.jsonnet
vendored
Normal 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
1
testdata/optional_args21.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
2
|
2
testdata/optional_args21.jsonnet
vendored
Normal file
2
testdata/optional_args21.jsonnet
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
local foo(x=2, z=x) = x;
|
||||
foo()
|
10
testdata/optional_args22.golden
vendored
Normal file
10
testdata/optional_args22.golden
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
[
|
||||
[
|
||||
42,
|
||||
42
|
||||
],
|
||||
[
|
||||
42,
|
||||
17
|
||||
]
|
||||
]
|
2
testdata/optional_args22.jsonnet
vendored
Normal file
2
testdata/optional_args22.jsonnet
vendored
Normal 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
5
testdata/optional_args3.golden
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"x": 2,
|
||||
"y": 1,
|
||||
"z": 2
|
||||
}
|
3
testdata/optional_args3.jsonnet
vendored
Normal file
3
testdata/optional_args3.jsonnet
vendored
Normal 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
5
testdata/optional_args4.golden
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"x": 5,
|
||||
"y": 4,
|
||||
"z": 5
|
||||
}
|
4
testdata/optional_args4.jsonnet
vendored
Normal file
4
testdata/optional_args4.jsonnet
vendored
Normal 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
1
testdata/optional_args5.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
42
|
2
testdata/optional_args5.jsonnet
vendored
Normal file
2
testdata/optional_args5.jsonnet
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
local foo(x, y=x) = y;
|
||||
foo(42)
|
1
testdata/optional_args6.golden
vendored
Normal file
1
testdata/optional_args6.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
42
|
2
testdata/optional_args6.jsonnet
vendored
Normal file
2
testdata/optional_args6.jsonnet
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
local foo(x, y=x) = y;
|
||||
foo(x=42)
|
1
testdata/optional_args7.golden
vendored
Normal file
1
testdata/optional_args7.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
42
|
2
testdata/optional_args7.jsonnet
vendored
Normal file
2
testdata/optional_args7.jsonnet
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
local foo(x) = x;
|
||||
foo(x=42)
|
10
testdata/optional_args8.golden
vendored
Normal file
10
testdata/optional_args8.golden
vendored
Normal 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
2
testdata/optional_args8.jsonnet
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
local foo(x=42) = x;
|
||||
foo(y=17)
|
10
testdata/optional_args9.golden
vendored
Normal file
10
testdata/optional_args9.golden
vendored
Normal 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
1
testdata/optional_args9.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
(function(x) x)(42, x=42)
|
7
testdata/std.makeArrayNamed.golden
vendored
Normal file
7
testdata/std.makeArrayNamed.golden
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
]
|
1
testdata/std.makeArrayNamed.jsonnet
vendored
Normal file
1
testdata/std.makeArrayNamed.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
std.makeArray(sz=5, func=function(i) i)
|
7
testdata/std.makeArrayNamed2.golden
vendored
Normal file
7
testdata/std.makeArrayNamed2.golden
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
]
|
1
testdata/std.makeArrayNamed2.jsonnet
vendored
Normal file
1
testdata/std.makeArrayNamed2.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
std.makeArray(func=function(i) i, sz=5)
|
10
testdata/std.makeArrayNamed3.golden
vendored
Normal file
10
testdata/std.makeArrayNamed3.golden
vendored
Normal 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
1
testdata/std.makeArrayNamed3.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
std.makeArray(blahblah=5, blahblahblah=function(i) i)
|
5
testdata/std.makeArrayNamed4.golden
vendored
Normal file
5
testdata/std.makeArrayNamed4.golden
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2
|
||||
]
|
1
testdata/std.makeArrayNamed4.jsonnet
vendored
Normal file
1
testdata/std.makeArrayNamed4.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
std.makeArray(3, func=function(i) i)
|
1
testdata/std.toString8.golden
vendored
Normal file
1
testdata/std.toString8.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
"42"
|
1
testdata/std.toString8.jsonnet
vendored
Normal file
1
testdata/std.toString8.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
std.toString(a=42)
|
10
testdata/too_many_arguments.golden
vendored
Normal file
10
testdata/too_many_arguments.golden
vendored
Normal 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
1
testdata/too_many_arguments.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
(function(x, y, z) 42)(1, 2, 3, 4)
|
73
thunks.go
73
thunks.go
@ -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 := ¶meters.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)
|
||||
}
|
||||
|
75
value.go
75
value.go
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user