Skip to content

Commit 6d6c293

Browse files
gotwarlostsbarzowski
authored andcommitted
allow initialization of external and TLA code variables from an AST node.
This allows configuration of a VM with an external code variable that has been pre-parsed into an AST node. This allows a caller to pre-parse a large object into a node and re-use that for multiple VMs. The cost of converting the object to an ast.Node is incurred only once by the caller as opposed to having this eagerly incurred (whether or not the object is used) before the eval.
1 parent 8a3fcc0 commit 6d6c293

File tree

4 files changed

+109
-17
lines changed

4 files changed

+109
-17
lines changed

imports.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ func (cache *importCache) importString(importedFrom, importedPath string, i *int
139139
return makeValueString(data.String()), nil
140140
}
141141

142+
func nodeToPV(i *interpreter, filename string, node ast.Node) *cachedThunk {
143+
env := makeInitialEnv(filename, i.baseStd)
144+
return &cachedThunk{
145+
env: &env,
146+
body: node,
147+
content: nil,
148+
}
149+
}
150+
142151
func codeToPV(i *interpreter, filename string, code string) *cachedThunk {
143152
node, err := program.SnippetToAST(ast.DiagnosticFileName(filename), "", code)
144153
if err != nil {
@@ -149,12 +158,7 @@ func codeToPV(i *interpreter, filename string, code string) *cachedThunk {
149158
// The same thinking applies to external variables.
150159
return &cachedThunk{err: err}
151160
}
152-
env := makeInitialEnv(filename, i.baseStd)
153-
return &cachedThunk{
154-
env: &env,
155-
body: node,
156-
content: nil,
157-
}
161+
return nodeToPV(i, filename, node)
158162
}
159163

160164
// ImportCode imports code from a path.

interpreter.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,9 +1194,13 @@ func evaluateStd(i *interpreter) (value, error) {
11941194
func prepareExtVars(i *interpreter, ext vmExtMap, kind string) map[string]*cachedThunk {
11951195
result := make(map[string]*cachedThunk)
11961196
for name, content := range ext {
1197-
if content.isCode {
1198-
result[name] = codeToPV(i, "<"+kind+":"+name+">", content.value)
1199-
} else {
1197+
diagnosticFile := "<" + kind + ":" + name + ">"
1198+
switch content.kind {
1199+
case extKindCode:
1200+
result[name] = codeToPV(i, diagnosticFile, content.value)
1201+
case extKindNode:
1202+
result[name] = nodeToPV(i, diagnosticFile, content.node)
1203+
default:
12001204
result[name] = readyThunk(makeValueString(content.value))
12011205
}
12021206
}

jsonnet_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package jsonnet
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"reflect"
67
"strings"
78
"testing"
@@ -265,3 +266,64 @@ func TestTLAReset(t *testing.T) {
265266
t.Errorf("unexpected error %v", err)
266267
}
267268
}
269+
270+
func assertVarOutput(t *testing.T, jsonStr string) {
271+
var data struct {
272+
Var string `json:"var"`
273+
Code string `json:"code"`
274+
Node string `json:"node"`
275+
}
276+
err := json.Unmarshal([]byte(jsonStr), &data)
277+
if err != nil {
278+
t.Fatalf("unexpected error: %v", err)
279+
}
280+
if data.Var != "var" {
281+
t.Errorf("var attribute not correct, want '%s' got '%s'", "var", data.Var)
282+
}
283+
if data.Code != "code" {
284+
t.Errorf("code attribute not correct, want '%s' got '%s'", "code", data.Code)
285+
}
286+
if data.Node != "node" {
287+
t.Errorf("node attribute not correct, want '%s' got '%s'", "node", data.Node)
288+
}
289+
}
290+
291+
func TestExtTypes(t *testing.T) {
292+
node, err := SnippetToAST("var.jsonnet", `{ node: 'node' }`)
293+
if err != nil {
294+
t.Fatalf("Unexpected error: %v", err)
295+
}
296+
vm := MakeVM()
297+
vm.ExtVar("var", "var")
298+
vm.ExtCode("code", `{ code: 'code'}`)
299+
vm.ExtNode("node", node)
300+
301+
jsonStr, err := vm.EvaluateAnonymousSnippet(
302+
"caller.jsonnet",
303+
`{ var: std.extVar('var') } + std.extVar('code') + std.extVar('node')`,
304+
)
305+
if err != nil {
306+
t.Fatalf("Unexpected error: %v", err)
307+
}
308+
assertVarOutput(t, jsonStr)
309+
}
310+
311+
func TestTLATypes(t *testing.T) {
312+
node, err := SnippetToAST("var.jsonnet", `{ node: 'node' }`)
313+
if err != nil {
314+
t.Fatalf("Unexpected error: %v", err)
315+
}
316+
vm := MakeVM()
317+
vm.TLAVar("var", "var")
318+
vm.TLACode("code", `{ code: 'code'}`)
319+
vm.TLANode("node", node)
320+
321+
jsonStr, err := vm.EvaluateAnonymousSnippet(
322+
"caller.jsonnet",
323+
`function (var, code, node) { var: var } + code + node`,
324+
)
325+
if err != nil {
326+
t.Fatalf("Unexpected error: %v", err)
327+
}
328+
assertVarOutput(t, jsonStr)
329+
}

vm.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,23 @@ type VM struct {
4646
importCache *importCache
4747
}
4848

49+
// extKind indicates the kind of external variable that is being initialized for the VM
50+
type extKind int
51+
52+
const (
53+
extKindVar extKind = iota // a simple string
54+
extKindCode // a code snippet represented as a string
55+
extKindNode // an ast.Node that is passed in
56+
)
57+
4958
// External variable or top level argument provided before execution
5059
type vmExt struct {
51-
// jsonnet code to evaluate or string to pass
60+
// the kind of external variable that is specified.
61+
kind extKind
62+
// jsonnet code to evaluate (kind=extKindCode) or string to pass (kind=extKindVar)
5263
value string
53-
// isCode determines whether it should be evaluated as jsonnet code or
54-
// treated as string.
55-
isCode bool
64+
// the specified node for kind=extKindNode
65+
node ast.Node
5666
}
5767

5868
type vmExtMap map[string]vmExt
@@ -85,13 +95,19 @@ func (vm *VM) flushValueCache() {
8595

8696
// ExtVar binds a Jsonnet external var to the given value.
8797
func (vm *VM) ExtVar(key string, val string) {
88-
vm.ext[key] = vmExt{value: val, isCode: false}
98+
vm.ext[key] = vmExt{value: val, kind: extKindVar}
8999
vm.flushValueCache()
90100
}
91101

92102
// ExtCode binds a Jsonnet external code var to the given code.
93103
func (vm *VM) ExtCode(key string, val string) {
94-
vm.ext[key] = vmExt{value: val, isCode: true}
104+
vm.ext[key] = vmExt{value: val, kind: extKindCode}
105+
vm.flushValueCache()
106+
}
107+
108+
// ExtNode binds a Jsonnet external code var to the given AST node.
109+
func (vm *VM) ExtNode(key string, node ast.Node) {
110+
vm.ext[key] = vmExt{node: node, kind: extKindNode}
95111
vm.flushValueCache()
96112
}
97113

@@ -103,15 +119,21 @@ func (vm *VM) ExtReset() {
103119

104120
// TLAVar binds a Jsonnet top level argument to the given value.
105121
func (vm *VM) TLAVar(key string, val string) {
106-
vm.tla[key] = vmExt{value: val, isCode: false}
122+
vm.tla[key] = vmExt{value: val, kind: extKindVar}
107123
// Setting a TLA does not require flushing the cache.
108124
// Only the results of evaluation of imported files are cached
109125
// and the TLAs do not affect these unlike extVars.
110126
}
111127

112128
// TLACode binds a Jsonnet top level argument to the given code.
113129
func (vm *VM) TLACode(key string, val string) {
114-
vm.tla[key] = vmExt{value: val, isCode: true}
130+
vm.tla[key] = vmExt{value: val, kind: extKindCode}
131+
// Setting a TLA does not require flushing the cache - see above.
132+
}
133+
134+
// TLANode binds a Jsonnet top level argument to the given AST node.
135+
func (vm *VM) TLANode(key string, node ast.Node) {
136+
vm.tla[key] = vmExt{node: node, kind: extKindNode}
115137
// Setting a TLA does not require flushing the cache - see above.
116138
}
117139

0 commit comments

Comments
 (0)