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
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
@ -241,12 +240,11 @@ func builtinToString(e *evaluator, xp potentialValue) (value, error) {
case *valueString:
return x, nil
}
var buf bytes.Buffer
err = e.i.manifestJSON(e.trace, x, false, "", &buf)
s, err := e.i.manifestAndSerializeJSON(e.trace, x, false, "")
if err != nil {
return nil, err
}
return makeValueString(buf.String()), nil
return makeValueString(s), nil
}
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)
}
// 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.
func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool, indent string, buf *bytes.Buffer) error {
// TODO(dcunnin): All the other types...
// manifestJSON converts to standard JSON representation as in "encoding/json" package
func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, error) {
e := &evaluator{i: i, trace: trace}
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:
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("[ ]")
} else {
var prefix string
@ -543,20 +611,10 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
prefix = "["
indent2 = indent
}
for _, th := range v.elements {
// 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
}
for _, elem := range v {
buf.WriteString(prefix)
buf.WriteString(indent2)
err = i.manifestJSON(trace, elVal, multiline, indent2, buf)
if err != nil {
return err
}
serializeJSON(elem, multiline, indent2, buf)
if multiline {
prefix = ",\n"
} else {
@ -570,30 +628,22 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
buf.WriteString("]")
}
case *valueBoolean:
if v.value {
case bool:
if v {
buf.WriteString("true")
} else {
buf.WriteString("false")
}
case *valueFunction:
return makeRuntimeError("Couldn't manifest function in JSON output.", i.getCurrentStackTrace(trace))
case float64:
buf.WriteString(unparseNumber(v))
case *valueNumber:
buf.WriteString(unparseNumber(v.value))
case *valueNull:
buf.WriteString("null")
case valueObject:
fieldNames := objectFields(v, withoutHidden)
sort.Strings(fieldNames)
err := checkAssertions(e, v)
if err != nil {
return err
case map[string]interface{}:
fieldNames := make([]string, 0, len(v))
for name := range v {
fieldNames = append(fieldNames, name)
}
sort.Strings(fieldNames)
if len(fieldNames) == 0 {
buf.WriteString("{ }")
@ -608,10 +658,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
indent2 = indent
}
for _, fieldName := range fieldNames {
fieldVal, err := v.index(e, fieldName)
if err != nil {
return err
}
fieldVal := v[fieldName]
buf.WriteString(prefix)
buf.WriteString(indent2)
@ -619,11 +666,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
buf.WriteString(unparseString(fieldName))
buf.WriteString(": ")
// TODO(sbarzowski) body.Loc()
err = i.manifestJSON(trace, fieldVal, multiline, indent2, buf)
if err != nil {
return err
}
serializeJSON(fieldVal, multiline, indent2, buf)
if multiline {
prefix = ",\n"
@ -639,17 +682,26 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
buf.WriteString("}")
}
case *valueString:
buf.WriteString(unparseString(v.getString()))
case string:
buf.WriteString(unparseString(v))
default:
return makeRuntimeError(
fmt.Sprintf("Manifesting this value not implemented yet: %s", reflect.TypeOf(v)),
i.getCurrentStackTrace(trace),
)
panic(fmt.Sprintf("Unsupported value for serialization %#+v", v))
}
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,
@ -739,15 +791,6 @@ func buildInterpreter(ext vmExtMap, maxStack int, importer Importer) (*interpret
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) {
i, err := buildInterpreter(ext, maxStack, importer)
if err != nil {
@ -766,9 +809,9 @@ func evaluate(node ast.Node, ext vmExtMap, maxStack int, importer Importer) (str
manifestationTrace := &TraceElement{
loc: &manifestationLoc,
}
e := &evaluator{
i: i,
trace: manifestationTrace,
s, err := i.manifestAndSerializeJSON(manifestationTrace, result, true, "")
if err != nil {
return "", err
}
return manifest(e, result)
return s, nil
}