From ebdc6440113159a389fd7bb6cc4b56148eb4d27e Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Thu, 14 Feb 2019 11:46:05 +0100 Subject: [PATCH] fix static scope handling --- dynaml/defined.go | 12 +++++-- dynaml/expression.go | 2 +- dynaml/fake_binding_helper_test.go | 2 +- dynaml/lambda.go | 50 ++++++++++++++++++++++-------- flow/cascade_test.go | 45 +++++++++++++++++++++++++++ flow/environment.go | 44 ++++++++++++++++---------- flow/environment_test.go | 12 +++---- flow/flow.go | 2 +- flow/flow_test.go | 1 + yaml/node.go | 2 +- 10 files changed, 129 insertions(+), 43 deletions(-) diff --git a/dynaml/defined.go b/dynaml/defined.go index f6070fd..1482a30 100644 --- a/dynaml/defined.go +++ b/dynaml/defined.go @@ -22,15 +22,21 @@ func (e CallExpr) valid(binding Binding) (interface{}, EvaluationInfo, bool) { } func (e CallExpr) defined(binding Binding) (interface{}, EvaluationInfo, bool) { + info := DefaultInfo() pushed := make([]Expression, len(e.Arguments)) ok := true resolved := true copy(pushed, e.Arguments) for i, _ := range pushed { - _, _, ok = ResolveExpressionOrPushEvaluation(&pushed[i], &resolved, nil, binding, true) - if resolved && !ok { - return false, DefaultInfo(), true + _, info, ok = ResolveExpressionOrPushEvaluation(&pushed[i], &resolved, nil, binding, true) + if resolved { + if !ok { + return false, DefaultInfo(), true + } + if info.Undefined { + return false, DefaultInfo(), true + } } } if !resolved { diff --git a/dynaml/expression.go b/dynaml/expression.go index d5e0bdd..3244adc 100644 --- a/dynaml/expression.go +++ b/dynaml/expression.go @@ -16,7 +16,7 @@ type SourceProvider interface { type Binding interface { SourceProvider - GetLocalBinding() map[string]yaml.Node + GetStaticBinding() map[string]yaml.Node GetRootBinding() map[string]yaml.Node FindFromRoot([]string) (yaml.Node, bool) diff --git a/dynaml/fake_binding_helper_test.go b/dynaml/fake_binding_helper_test.go index df0c596..cf9721c 100644 --- a/dynaml/fake_binding_helper_test.go +++ b/dynaml/fake_binding_helper_test.go @@ -58,7 +58,7 @@ func (c FakeBinding) WithSource(source string) Binding { return c } -func (c FakeBinding) GetLocalBinding() map[string]yaml.Node { +func (c FakeBinding) GetStaticBinding() map[string]yaml.Node { return map[string]yaml.Node{} } diff --git a/dynaml/lambda.go b/dynaml/lambda.go index a68bce5..9ee246b 100644 --- a/dynaml/lambda.go +++ b/dynaml/lambda.go @@ -21,11 +21,18 @@ type LambdaExpr struct { E Expression } +func keys(m map[string]yaml.Node) []string { + s := []string{} + for k := range m { + s = append(s, k) + } + return s +} + func (e LambdaExpr) Evaluate(binding Binding, locally bool) (interface{}, EvaluationInfo, bool) { info := DefaultInfo() debug.Debug("LAMBDA VALUE with resolver %+v\n", binding) - - return LambdaValue{e, binding.GetLocalBinding(), staticScope(binding)}, info, true + return LambdaValue{e, binding.GetStaticBinding(), staticScope(binding)}, info, true } func (e LambdaExpr) String() string { @@ -69,7 +76,7 @@ func (e LambdaRefExpr) Evaluate(binding Binding, locally bool) (interface{}, Eva debug.Debug("no lambda expression: %T\n", expr) return info.Error("'%s' is no lambda expression", v) } - lambda = LambdaValue{lexpr, binding.GetLocalBinding(), staticScope(binding)} + lambda = LambdaValue{lexpr, binding.GetStaticBinding(), staticScope(binding)} default: return info.Error("lambda reference must resolve to lambda value or string") @@ -84,7 +91,7 @@ func (e LambdaRefExpr) String() string { type LambdaValue struct { lambda LambdaExpr - local map[string]yaml.Node + static map[string]yaml.Node resolver Binding } @@ -105,18 +112,35 @@ func (e LambdaValue) EquivalentTo(val interface{}) bool { return ok && reflect.DeepEqual(e.lambda, o.lambda) } -func (e LambdaValue) String() string { - binding := "" - if len(e.local) > 0 { - binding = "{" +func short(val interface{}, all bool) string { + switch v := val.(type) { + case []yaml.Node: + s := "[" + sep := "" + for _, e := range v { + s = fmt.Sprintf("%s%s%s", s, sep, short(e.Value(), all)) + sep = ", " + } + return s + "]" + case map[string]yaml.Node: + s := "{" sep := "" - for n, v := range e.local { - if n != "_" { - binding += fmt.Sprintf("%s%s: %v", sep, n, v.Value()) + for k, e := range v { + if all || k != "_" { + s = fmt.Sprintf("%s%s%s: %s", s, sep, k, short(e.Value(), all)) sep = ", " } } - binding += "}" + return s + "}" + default: + return fmt.Sprintf("%v", v) + } +} + +func (e LambdaValue) String() string { + binding := "" + if len(e.static) > 0 { + binding = short(e.static, false) } return fmt.Sprintf("%s%s", binding, e.lambda) } @@ -133,7 +157,7 @@ func (e LambdaValue) Evaluate(args []interface{}, binding Binding, locally bool) return false, nil, info, false } inp := map[string]yaml.Node{} - for n, v := range e.local { + for n, v := range e.static { inp[n] = v } debug.Debug("LAMBDA CALL: inherit local %+v\n", inp) diff --git a/flow/cascade_test.go b/flow/cascade_test.go index 10ff373..a457f9b 100644 --- a/flow/cascade_test.go +++ b/flow/cascade_test.go @@ -500,6 +500,51 @@ values: 3 Expect(template).To(CascadeAs(resolved, source, stub)) }) }) + + Context("lambda scopes", func() { + Context("in lambdas", func() { + It("are passed to nested lambdas", func() { + source := parseYAML(` +--- +func: (( |x|->($a=100) |y|->($b=101) |z|->[x,y,z,a,b] )) + +values: (( .func(1)(2)(3) )) + +`) + resolved := parseYAML(` +--- +values: + - 1 + - 2 + - 3 + - 100 + - 101 +`) + Expect(template).To(CascadeAs(resolved, source)) + }) + }) + Context("in templates", func() { + It("are passed to nested lambdas", func() { + source := parseYAML(` +--- +template: + <<: (( &template )) + func: (( |y|->{$x=x, $y=y} )) +func: (( |x|->*template )) +inst: (( .func("instx") )) + +values: (( .inst.func("insty") )) +`) + resolved := parseYAML(` +--- +values: + x: instx + "y": insty +`) + Expect(template).To(CascadeAs(resolved, source)) + }) + }) + }) }) Describe("using local nodes", func() { diff --git a/flow/environment.go b/flow/environment.go index 13f7f60..633e88e 100644 --- a/flow/environment.go +++ b/flow/environment.go @@ -12,14 +12,19 @@ import ( ) type Scope struct { - local map[string]yaml.Node - path []string - next *Scope - root *Scope + local map[string]yaml.Node + static map[string]yaml.Node + path []string + next *Scope + root *Scope +} + +func newFakeScope(outer *Scope, path []string, local map[string]yaml.Node) *Scope { + return newScope(outer, path, local, nil) } -func newScope(outer *Scope, path []string, local map[string]yaml.Node) *Scope { - scope := &Scope{local, path, outer, nil} +func newScope(outer *Scope, path []string, local, static map[string]yaml.Node) *Scope { + scope := &Scope{local, static, path, outer, nil} if outer == nil { scope.root = scope } else { @@ -38,8 +43,8 @@ type DefaultEnvironment struct { currentSourceName string - local map[string]yaml.Node - outer dynaml.Binding + static map[string]yaml.Node + outer dynaml.Binding active bool } @@ -55,10 +60,10 @@ func keys(s map[string]yaml.Node) string { } func (e DefaultEnvironment) String() string { - result := fmt.Sprintf("ENV: %s local: %s", strings.Join(e.path, ", "), keys(e.local)) + result := fmt.Sprintf("SCOPES: <%s> static: %s", strings.Join(e.path, ", "), keys(e.static)) s := e.scope for s != nil { - result = result + "\n " + keys(s.local) + result = result + "\n <" + strings.Join(s.path, ", ") + ">" + keys(s.local) + keys(s.static) s = s.next } return result @@ -101,8 +106,8 @@ func (e DefaultEnvironment) GetScope() *Scope { return e.scope } -func (e DefaultEnvironment) GetLocalBinding() map[string]yaml.Node { - return e.local +func (e DefaultEnvironment) GetStaticBinding() map[string]yaml.Node { + return e.static } func (e DefaultEnvironment) FindFromRoot(path []string) (yaml.Node, bool) { @@ -146,14 +151,20 @@ func (e DefaultEnvironment) WithSource(source string) dynaml.Binding { } func (e DefaultEnvironment) WithScope(step map[string]yaml.Node) dynaml.Binding { - e.scope = newScope(e.scope, e.path, step) - e.local = map[string]yaml.Node{} + e.scope = newScope(e.scope, e.path, step, e.static) return e } func (e DefaultEnvironment) WithLocalScope(step map[string]yaml.Node) dynaml.Binding { - e.scope = newScope(e.scope, nil, step) - e.local = step + static := map[string]yaml.Node{} + for k, v := range e.static { + static[k] = v + } + for k, v := range step { + static[k] = v + } + e.static = static + e.scope = newScope(e.scope, nil, step, static) return e } @@ -166,7 +177,6 @@ func (e DefaultEnvironment) WithPath(step string) dynaml.Binding { copy(newPath, e.stubPath) e.stubPath = append(newPath, step) - e.local = map[string]yaml.Node{} return e } diff --git a/flow/environment_test.go b/flow/environment_test.go index a386a92..8db7b9a 100644 --- a/flow/environment_test.go +++ b/flow/environment_test.go @@ -16,7 +16,7 @@ foo: `) environment := DefaultEnvironment{ - scope: newScope(nil, nil, tree.Value().(map[string]yaml.Node)), + scope: newFakeScope(nil, nil, tree.Value().(map[string]yaml.Node)), } Context("when the first step is found", func() { @@ -37,7 +37,7 @@ foos: `) environment := DefaultEnvironment{ - scope: newScope(nil, nil, tree.Value().(map[string]yaml.Node)), + scope: newFakeScope(nil, nil, tree.Value().(map[string]yaml.Node)), } It("treats the name as the key", func() { @@ -66,7 +66,7 @@ foo: `) environment := DefaultEnvironment{ - scope: newScope(newScope(newScope(nil, nil, + scope: newFakeScope(newFakeScope(newFakeScope(nil, nil, tree.Value().(map[string]yaml.Node)), nil, tree.Value().(map[string]yaml.Node)["foo"].Value().(map[string]yaml.Node)), nil, tree.Value().(map[string]yaml.Node)["foo"].Value().(map[string]yaml.Node)["bar"].Value().(map[string]yaml.Node)), @@ -92,7 +92,7 @@ foo: `) environment := DefaultEnvironment{ - scope: newScope(newScope(newScope(nil, nil, + scope: newFakeScope(newFakeScope(newFakeScope(nil, nil, tree.Value().(map[string]yaml.Node)), nil, tree.Value().(map[string]yaml.Node)["foo"].Value().(map[string]yaml.Node)), nil, tree.Value().(map[string]yaml.Node)["foo"].Value().(map[string]yaml.Node)["bar"].Value().(map[string]yaml.Node)), @@ -122,7 +122,7 @@ foo: `) environment := DefaultEnvironment{ - scope: newScope(nil, nil, tree.Value().(map[string]yaml.Node)), + scope: newFakeScope(nil, nil, tree.Value().(map[string]yaml.Node)), } It("returns the node found by the path from the root", func() { @@ -141,7 +141,7 @@ foo: `) environment := DefaultEnvironment{ - scope: newScope(nil, nil, tree.Value().(map[string]yaml.Node)), + scope: newFakeScope(nil, nil, tree.Value().(map[string]yaml.Node)), } It("accepts [x] for following through lists", func() { diff --git a/flow/flow.go b/flow/flow.go index 48753b5..ed3e7a8 100644 --- a/flow/flow.go +++ b/flow/flow.go @@ -243,7 +243,7 @@ func flowMap(root yaml.Node, env dynaml.Binding) yaml.Node { debug.Debug("found temporary declaration\n") } if flags.Local() { - debug.Debug("found local declaration\n") + debug.Debug("found static declaration\n") } } if ok && m.Has(dynaml.TEMPLATE) { diff --git a/flow/flow_test.go b/flow/flow_test.go index 5693673..1973958 100644 --- a/flow/flow_test.go +++ b/flow/flow_test.go @@ -6374,6 +6374,7 @@ result: }) }) }) + Describe("scoped expressions", func() { Context("in normal expressions", func() { It("accepts empty scopes", func() { diff --git a/yaml/node.go b/yaml/node.go index 08fbda8..77aae25 100644 --- a/yaml/node.go +++ b/yaml/node.go @@ -414,7 +414,7 @@ func EmbeddedDynaml(root Node) *string { if !ok { return nil } - rootString=strings.Replace(rootString,"\n"," ", -1) + rootString = strings.Replace(rootString, "\n", " ", -1) sub := embeddedDynaml.FindStringSubmatch(rootString) if sub == nil { return nil