Separate manifestation from serialization to string

This is necessary for example for native functions
(which take json as arguments).

Standard "encoding/json" representation is used, but I have
mixed feeling about it. Not sure if treating json values as interface{}
is the right trade-off in our case.
This commit is contained in:
Stanisław Barzowski 2017-09-25 15:06:32 -04:00 committed by Dave Cunningham
parent 8628b9b4f9
commit e7249a1131
2 changed files with 110 additions and 69 deletions

View File

@ -17,7 +17,6 @@ limitations under the License.
package jsonnet package jsonnet
import ( import (
"bytes"
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@ -241,12 +240,11 @@ func builtinToString(e *evaluator, xp potentialValue) (value, error) {
case *valueString: case *valueString:
return x, nil return x, nil
} }
var buf bytes.Buffer s, err := e.i.manifestAndSerializeJSON(e.trace, x, false, "")
err = e.i.manifestJSON(e.trace, x, false, "", &buf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return makeValueString(buf.String()), nil return makeValueString(s), nil
} }
func builtinMakeArray(e *evaluator, szp potentialValue, funcp potentialValue) (value, error) { func builtinMakeArray(e *evaluator, szp potentialValue, funcp potentialValue) (value, error) {

View File

@ -523,15 +523,83 @@ func unparseNumber(v float64) string {
return fmt.Sprintf("%.17g", v) return fmt.Sprintf("%.17g", v)
} }
// TODO(sbarzowski) Perhaps it should be a builtin? // manifestJSON converts to standard JSON representation as in "encoding/json" package
// TODO(sbarzowski) Perhaps we should separate recursive evaluation from serialization? func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, error) {
// Strictly evaluating something may be useful by itself.
func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool, indent string, buf *bytes.Buffer) error {
// TODO(dcunnin): All the other types...
e := &evaluator{i: i, trace: trace} e := &evaluator{i: i, trace: trace}
switch v := v.(type) { switch v := v.(type) {
case *valueBoolean:
return v.value, nil
case *valueFunction:
return nil, makeRuntimeError("Couldn't manifest function in JSON output.", i.getCurrentStackTrace(trace))
case *valueNumber:
return v.value, nil
case *valueString:
return v.getString(), nil
case *valueNull:
return nil, nil
case *valueArray: case *valueArray:
if len(v.elements) == 0 { result := make([]interface{}, 0, len(v.elements))
for _, th := range v.elements {
elVal, err := th.getValue(i, trace) // TODO(sbarzowski) perhaps manifestJSON should just take potentialValue
if err != nil {
return nil, err
}
elem, err := i.manifestJSON(trace, elVal)
if err != nil {
return nil, err
}
result = append(result, elem)
}
return result, nil
case valueObject:
fieldNames := objectFields(v, withoutHidden)
sort.Strings(fieldNames)
err := checkAssertions(e, v)
if err != nil {
return nil, err
}
result := make(map[string]interface{})
for _, fieldName := range fieldNames {
fieldVal, err := v.index(e, fieldName)
if err != nil {
return nil, err
}
field, err := i.manifestJSON(trace, fieldVal)
if err != nil {
return nil, err
}
result[fieldName] = field
}
return result, nil
default:
return nil, makeRuntimeError(
fmt.Sprintf("Manifesting this value not implemented yet: %s", reflect.TypeOf(v)),
i.getCurrentStackTrace(trace),
)
}
}
func serializeJSON(v interface{}, multiline bool, indent string, buf *bytes.Buffer) {
switch v := v.(type) {
case nil:
buf.WriteString("null")
case []interface{}:
if len(v) == 0 {
buf.WriteString("[ ]") buf.WriteString("[ ]")
} else { } else {
var prefix string var prefix string
@ -543,20 +611,10 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
prefix = "[" prefix = "["
indent2 = indent indent2 = indent
} }
for _, th := range v.elements { for _, elem := range v {
// if th.body != nil {
// tloc = th.body.Loc()
// }
elVal, err := th.getValue(i, trace) // TODO(sbarzowski) perhaps manifestJSON should just take potentialValue
if err != nil {
return err
}
buf.WriteString(prefix) buf.WriteString(prefix)
buf.WriteString(indent2) buf.WriteString(indent2)
err = i.manifestJSON(trace, elVal, multiline, indent2, buf) serializeJSON(elem, multiline, indent2, buf)
if err != nil {
return err
}
if multiline { if multiline {
prefix = ",\n" prefix = ",\n"
} else { } else {
@ -570,30 +628,22 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
buf.WriteString("]") buf.WriteString("]")
} }
case *valueBoolean: case bool:
if v.value { if v {
buf.WriteString("true") buf.WriteString("true")
} else { } else {
buf.WriteString("false") buf.WriteString("false")
} }
case *valueFunction: case float64:
return makeRuntimeError("Couldn't manifest function in JSON output.", i.getCurrentStackTrace(trace)) buf.WriteString(unparseNumber(v))
case *valueNumber: case map[string]interface{}:
buf.WriteString(unparseNumber(v.value)) fieldNames := make([]string, 0, len(v))
for name := range v {
case *valueNull: fieldNames = append(fieldNames, name)
buf.WriteString("null")
case valueObject:
fieldNames := objectFields(v, withoutHidden)
sort.Strings(fieldNames)
err := checkAssertions(e, v)
if err != nil {
return err
} }
sort.Strings(fieldNames)
if len(fieldNames) == 0 { if len(fieldNames) == 0 {
buf.WriteString("{ }") buf.WriteString("{ }")
@ -608,10 +658,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
indent2 = indent indent2 = indent
} }
for _, fieldName := range fieldNames { for _, fieldName := range fieldNames {
fieldVal, err := v.index(e, fieldName) fieldVal := v[fieldName]
if err != nil {
return err
}
buf.WriteString(prefix) buf.WriteString(prefix)
buf.WriteString(indent2) buf.WriteString(indent2)
@ -619,11 +666,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
buf.WriteString(unparseString(fieldName)) buf.WriteString(unparseString(fieldName))
buf.WriteString(": ") buf.WriteString(": ")
// TODO(sbarzowski) body.Loc() serializeJSON(fieldVal, multiline, indent2, buf)
err = i.manifestJSON(trace, fieldVal, multiline, indent2, buf)
if err != nil {
return err
}
if multiline { if multiline {
prefix = ",\n" prefix = ",\n"
@ -639,17 +682,26 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
buf.WriteString("}") buf.WriteString("}")
} }
case *valueString: case string:
buf.WriteString(unparseString(v.getString())) buf.WriteString(unparseString(v))
default: default:
return makeRuntimeError( panic(fmt.Sprintf("Unsupported value for serialization %#+v", v))
fmt.Sprintf("Manifesting this value not implemented yet: %s", reflect.TypeOf(v)),
i.getCurrentStackTrace(trace),
)
} }
return nil }
// TODO(sbarzowski) Perhaps it should be a builtin?
// TODO(sbarzowski) Perhaps we should separate recursive evaluation from serialization?
// Strictly evaluating something may be useful by itself.
// For example may help with error reporting from custom serialization functions.
func (i *interpreter) manifestAndSerializeJSON(trace *TraceElement, v value, multiline bool, indent string) (string, error) {
var buf bytes.Buffer
manifested, err := i.manifestJSON(trace, v)
if err != nil {
return "", err
}
serializeJSON(manifested, multiline, indent, &buf)
return buf.String(), nil
} }
func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, newContext *TraceContext, func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, newContext *TraceContext,
@ -739,15 +791,6 @@ func buildInterpreter(ext vmExtMap, maxStack int, importer Importer) (*interpret
return &i, nil return &i, nil
} }
func manifest(e *evaluator, v value) (string, error) {
var buffer bytes.Buffer
err := e.i.manifestJSON(e.trace, v, true, "", &buffer)
if err != nil {
return "", err
}
return buffer.String(), nil
}
func evaluate(node ast.Node, ext vmExtMap, maxStack int, importer Importer) (string, error) { func evaluate(node ast.Node, ext vmExtMap, maxStack int, importer Importer) (string, error) {
i, err := buildInterpreter(ext, maxStack, importer) i, err := buildInterpreter(ext, maxStack, importer)
if err != nil { if err != nil {
@ -766,9 +809,9 @@ func evaluate(node ast.Node, ext vmExtMap, maxStack int, importer Importer) (str
manifestationTrace := &TraceElement{ manifestationTrace := &TraceElement{
loc: &manifestationLoc, loc: &manifestationLoc,
} }
e := &evaluator{ s, err := i.manifestAndSerializeJSON(manifestationTrace, result, true, "")
i: i, if err != nil {
trace: manifestationTrace, return "", err
} }
return manifest(e, result) return s, nil
} }