Pass through JSON

This commit is contained in:
Dave Cunningham 2016-04-03 14:10:35 -04:00 committed by Dave Cunningham
parent c3e4c805f2
commit 09c7c8c7c0
8 changed files with 879 additions and 66 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
*~
.*.swp

View File

@ -12,6 +12,9 @@
This is a port of [jsonnet](http://jsonnet.org/) to go. It is very much a work in progress.
This implementation is largely based on the the [jsonnet C++ implementation](https://github.com/google/jsonnet).
The precise revision is
https://github.com/google/jsonnet/tree/27ddf2c2f7041c09316cf7c9ef13af9588fdd671 but when we reach
feature parity with that revision, we will chase up all the recent changes on the C++ side.
## Implementation Notes

17
ast.go
View File

@ -32,6 +32,7 @@ type identifiers []identifier
type astNode interface {
Loc() *LocationRange
FreeVariables() identifiers
}
type astNodes []astNode
@ -39,12 +40,17 @@ type astNodes []astNode
type astNodeBase struct {
loc LocationRange
freeVariables identifiers
}
func (n *astNodeBase) Loc() *LocationRange {
return &n.loc
}
func (n *astNodeBase) FreeVariables() identifiers {
return n.freeVariables
}
// ---------------------------------------------------------------------------
// +gen stringer
@ -386,7 +392,15 @@ type astObjectField struct {
expr2, expr3 astNode // In scope of the object (can see self).
}
// TODO(jbeda): Add constructor helpers here
// TODO(jbeda): Add the remaining constructor helpers here
func astObjectFieldLocal(methodSugar bool, id *identifier, ids identifiers, trailingComma bool, body astNode) astObjectField {
return astObjectField{astObjectLocal, astObjectFieldVisible, false, methodSugar, nil, id, ids, trailingComma, body, nil}
}
func astObjectFieldLocalNoMethod(id *identifier, body astNode) astObjectField {
return astObjectField{astObjectLocal, astObjectFieldVisible, false, false, nil, id, identifiers{}, false, body, nil}
}
type astObjectFields []astObjectField
@ -504,7 +518,6 @@ type astUnary struct {
type astVar struct {
astNodeBase
id identifier
original identifier
}
// ---------------------------------------------------------------------------

View File

@ -16,15 +16,387 @@ limitations under the License.
package jsonnet
func desugar(ast astNode, objLevel int) (astNode, error) {
return ast, nil
import (
"bytes"
"encoding/hex"
"fmt"
"reflect"
"unicode/utf8"
)
func makeStr(s string) *astLiteralString {
return &astLiteralString{astNodeBase{loc: LocationRange{}}, s, astStringDouble, ""}
}
func desugarFile(ast astNode) (astNode, error) {
ast, err := desugar(ast, 0)
func stringUnescape(loc *LocationRange, s string) (string, error) {
var buf bytes.Buffer
// read one rune at a time
for i := 0; i < len(s); {
r, w := utf8.DecodeRuneInString(s[i:])
i += w
switch r {
case '\\':
if i >= len(s) {
return "", makeStaticError("Truncated escape sequence in string literal.", *loc)
}
r2, w := utf8.DecodeRuneInString(s[i:])
i += w
switch r2 {
case '"':
buf.WriteRune('"')
case '\\':
buf.WriteRune('\\')
case '/':
buf.WriteRune('/') // This one is odd, maybe a mistake.
case 'b':
buf.WriteRune('\b')
case 'f':
buf.WriteRune('\f')
case 'n':
buf.WriteRune('\n')
case 'r':
buf.WriteRune('\r')
case 't':
buf.WriteRune('\t')
case 'u':
if i+4 > len(s) {
return "", makeStaticError("Truncated unicode escape sequence in string literal.", *loc)
}
codeBytes, err := hex.DecodeString(s[0:4])
if err != nil {
return nil, err
return "", makeStaticError(fmt.Sprintf("Unicode escape sequence was malformed: %s", s[0:4]), *loc)
}
code := int(codeBytes[0])*256 + int(codeBytes[1])
buf.WriteRune(rune(code))
i += 4
default:
return "", makeStaticError(fmt.Sprintf("Unknown escape sequence in string literal: \\%c", r2), *loc)
}
default:
buf.WriteRune(r)
}
}
return buf.String(), nil
}
func desugarFields(location LocationRange, fields *astObjectFields, objLevel int) error {
// Desugar children
for _, field := range *fields {
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
// TODO(dcunnin): this
for _, field := range *fields {
if field.kind != astObjectAssert {
continue
}
/*
AST *msg = field.expr3
field.expr3 = nil
if (msg == nil) {
auto msg_str = U"Object assertion failed."
msg = alloc->make<LiteralString>(field.expr2->location, msg_str,
LiteralString::DOUBLE, "")
}
// if expr2 then true else error msg
field.expr2 = alloc->make<Conditional>(
ast->location,
field.expr2,
alloc->make<LiteralBoolean>(E, true),
alloc->make<Error>(msg->location, msg))
*/
}
// Remove methods
// TODO(dcunnin): this
for _, field := range *fields {
if !field.methodSugar {
continue
}
/*
field.expr2 = alloc->make<Function>(
field.expr2->location, field.ids, false, field.expr2)
field.methodSugar = false
field.ids.clear()
*/
}
// Remove object-level locals
newFields := []astObjectField{}
var binds astLocalBinds
for _, local := range *fields {
if local.kind != astObjectLocal {
continue
}
binds = append(binds, astLocalBind{variable: *local.id, body: local.expr2})
}
for _, field := range *fields {
if field.kind == astObjectLocal {
continue
}
if len(binds) > 0 {
field.expr2 = &astLocal{astNodeBase{loc: *field.expr2.Loc()}, binds, field.expr2}
}
newFields = append(newFields, field)
}
*fields = newFields
// Change all to FIELD_EXPR
for i := range *fields {
field := &(*fields)[i]
switch field.kind {
case astObjectAssert:
// Nothing to do.
case astObjectFieldID:
field.expr1 = makeStr(string(*field.id))
field.kind = astObjectFieldExpr
case astObjectFieldExpr:
// Nothing to do.
case astObjectFieldStr:
// Just set the flag.
field.kind = astObjectFieldExpr
case astObjectLocal:
return fmt.Errorf("INTERNAL ERROR: Locals should be removed by now.")
}
}
// Remove +:
// TODO(dcunnin): this
for _, field := range *fields {
if !field.superSugar {
continue
}
/*
AST *super_f = alloc->make<SuperIndex>(field.expr1->location, field.expr1, nil)
field.expr2 = alloc->make<Binary>(ast->location, super_f, BOP_PLUS, field.expr2)
field.superSugar = false
*/
}
return nil
}
func desugar(astPtr *astNode, objLevel int) error {
ast := *astPtr
// TODO(dcunnin): Remove all uses of unimplErr.
unimplErr := makeStaticError(fmt.Sprintf("Desugarer does not yet implement ast: %s", reflect.TypeOf(ast)), *ast.Loc())
var err error // Make all recursive calls of the form err = desugar, rather than some being err := desugar
switch ast := ast.(type) {
case *astApply:
err = desugar(&ast.target, objLevel)
if err != nil {
return err
}
for i := range ast.arguments {
err = desugar(&ast.arguments[i], objLevel)
if err != nil {
return err
}
}
case *astApplyBrace:
err = desugar(&ast.left, objLevel)
if err != nil {
return err
}
err = desugar(&ast.right, objLevel)
if err != nil {
return err
}
*astPtr = &astBinary{
astNodeBase: ast.astNodeBase,
left: ast.left,
op: bopPlus,
right: ast.right,
}
case *astArray:
for i := range ast.elements {
err = desugar(&ast.elements[i], objLevel)
if err != nil {
return err
}
}
case *astArrayComp:
return unimplErr
case *astAssert:
return unimplErr
case *astBinary:
err = desugar(&ast.left, objLevel)
if err != nil {
return err
}
err = desugar(&ast.right, objLevel)
if err != nil {
return err
}
// TODO(dcunnin): Need to handle bopPercent, bopManifestUnequal, bopManifestEqual
case *astBuiltin:
// Nothing to do.
case *astConditional:
err = desugar(&ast.cond, objLevel)
if err != nil {
return err
}
err = desugar(&ast.branchTrue, objLevel)
if err != nil {
return err
}
if ast.branchFalse != nil {
ast.branchFalse = &astLiteralNull{}
}
case *astDollar:
if objLevel == 0 {
return makeStaticError("No top-level object found.", *ast.Loc())
}
*astPtr = &astVar{astNodeBase: ast.astNodeBase, id: identifier("$")}
case *astError:
err = desugar(&ast.expr, objLevel)
if err != nil {
return err
}
case *astFunction:
err = desugar(&ast.body, objLevel)
if err != nil {
return err
}
case *astImport:
// Nothing to do.
case *astImportStr:
// Nothing to do.
case *astIndex:
return unimplErr
case *astLocal:
for _, bind := range ast.binds {
err = desugar(&bind.body, objLevel)
if err != nil {
return err
}
}
err = desugar(&ast.body, objLevel)
if err != nil {
return err
}
// TODO(dcunnin): Desugar local functions
case *astLiteralBoolean:
// Nothing to do.
case *astLiteralNull:
// Nothing to do.
case *astLiteralNumber:
// Nothing to do.
case *astLiteralString:
unescaped, err := stringUnescape(ast.Loc(), ast.value)
if err != nil {
return err
}
ast.value = unescaped
ast.kind = astStringDouble
ast.blockIndent = ""
case *astObject:
// Hidden variable to allow $ binding.
if objLevel == 0 {
dollar := identifier("$")
ast.fields = append(ast.fields, astObjectFieldLocalNoMethod(&dollar, &astSelf{}))
}
err = desugarFields(*ast.Loc(), &ast.fields, objLevel)
if err != nil {
return err
}
var newFields astDesugaredObjectFields
var newAsserts astNodes
for _, field := range ast.fields {
if field.kind == astObjectAssert {
newAsserts = append(newAsserts, field.expr2)
} else if field.kind == astObjectFieldExpr {
newFields = append(newFields, astDesugaredObjectField{field.hide, field.expr1, field.expr2})
} else {
return fmt.Errorf("INTERNAL ERROR: field should have been desugared: %s", field.kind)
}
}
*astPtr = &astDesugaredObject{ast.astNodeBase, newAsserts, newFields}
case *astDesugaredObject:
return unimplErr
case *astObjectComp:
return unimplErr
case *astObjectComprehensionSimple:
return unimplErr
case *astSelf:
// Nothing to do.
case *astSuperIndex:
return unimplErr
case *astUnary:
err = desugar(&ast.expr, objLevel)
if err != nil {
return err
}
case *astVar:
// Nothing to do.
default:
return makeStaticError(fmt.Sprintf("Desugarer does not recognize ast: %s", reflect.TypeOf(ast)), *ast.Loc())
}
return nil
}
func desugarFile(ast *astNode) error {
err := desugar(ast, 0)
if err != nil {
return err
}
// TODO(dcunnin): wrap in std local
return ast, nil
return nil
}

View File

@ -20,6 +20,8 @@ import (
"bytes"
"fmt"
"math"
"reflect"
"sort"
)
// Misc top-level stuff
@ -37,8 +39,15 @@ type RuntimeError struct {
Msg string
}
func makeRuntimeError(msg string) RuntimeError {
func makeRuntimeError(loc *LocationRange, msg string) RuntimeError {
// TODO(dcunnin): Build proper stacktrace.
return RuntimeError{
StackTrace: []TraceFrame{
{
Loc: *loc,
Name: "name",
},
},
Msg: msg,
}
}
@ -50,7 +59,7 @@ func (err RuntimeError) Error() string {
// Values and state
type bindingFrame map[*identifier]thunk
type bindingFrame map[identifier]*thunk
type value interface {
}
@ -88,43 +97,63 @@ func makeValueNull() *valueNull {
}
type thunk struct {
Content value // nil if not filled
Name *identifier
UpValues bindingFrame
Self value
Offset int
Body astNode
content value // nil if not filled
name identifier
upValues bindingFrame
self value
offset int
body astNode
}
func makeThunk(name *identifier, self *value, offset int, body astNode) *thunk {
func makeThunk(name identifier, self value, offset int, body astNode) *thunk {
return &thunk{
Name: name,
Self: self,
Offset: offset,
Body: body,
name: name,
self: self,
offset: offset,
body: body,
}
}
func (t *thunk) fill(v value) {
t.Content = v
t.Self = nil
t.UpValues = make(bindingFrame) // clear the map
t.content = v
t.self = nil
t.upValues = make(bindingFrame) // clear the map
}
func (t *thunk) filled() bool {
return t.content != nil
}
type valueArray struct {
Elements []thunk
elements []*thunk
}
func makeValueArray(elements []thunk) *valueArray {
func makeValueArray(elements []*thunk) *valueArray {
return &valueArray{
Elements: elements,
elements: elements,
}
}
// TODO(dcunnin): SimpleObject
// TODO(dcunnin): ExtendedObject
// TODO(dcunnin): ComprehensionObject
// TODO(dcunnin): Closure
type valueClosure struct {
upValues bindingFrame
}
type valueSimpleObjectField struct {
hide astObjectFieldHide
body astNode
}
type valueSimpleObjectFieldMap map[string]valueSimpleObjectField
type valueSimpleObject struct {
upValues bindingFrame
fields valueSimpleObjectFieldMap
asserts []astNode
}
// TODO(dcunnin): extendedObject
// TODO(dcunnin): comprehensionObject
// TODO(dcunnin): closure
// The stack
@ -135,19 +164,106 @@ type TraceFrame struct {
}
type callFrame struct {
isCall bool
ast astNode
location LocationRange
tailCall bool
thunks []*thunk
context value
self value
offset int
bindings bindingFrame
}
type callStack struct {
Calls int
Limit int
Stack []callFrame
calls int
limit int
stack []*callFrame
}
func (s *callStack) top() *callFrame {
return s.stack[len(s.stack)-1]
}
func (s *callStack) pop() {
if s.top().isCall {
s.calls--
}
s.stack = s.stack[:len(s.stack)-1]
}
/** If there is a tailstrict annotated frame followed by some locals, pop them all. */
func (s *callStack) tailCallTrimStack() {
for i := len(s.stack) - 1; i >= 0; i-- {
if s.stack[i].isCall {
// If thunks > 0 that means we are still executing args (tailstrict).
if !s.stack[i].tailCall || len(s.stack[i].thunks) > 0 {
return
}
// Remove all stack frames including this one.
s.stack = s.stack[:i]
s.calls--
return
}
}
}
func (s *callStack) newCall(loc *LocationRange, context value, self value, offset int, upValues bindingFrame) error {
s.tailCallTrimStack()
if s.calls >= s.limit {
return makeRuntimeError(loc, "Max stack frames exceeded.")
}
s.stack = append(s.stack, &callFrame{
isCall: true,
location: *loc,
context: context,
self: self,
offset: offset,
bindings: upValues,
tailCall: false,
})
s.calls++
return nil
}
func (s *callStack) newLocal(vars bindingFrame) {
s.stack = append(s.stack, &callFrame{
bindings: vars,
})
}
// getSelfBinding resolves the self construct
func (s *callStack) getSelfBinding() (value, int) {
for i := len(s.stack) - 1; i >= 0; i-- {
if s.stack[i].isCall {
return s.stack[i].self, s.stack[i].offset
}
}
// Should never get here if the stack is well-formed.
return nil, 0
}
// lookUpVar finds for the closest variable in scope that matches the given name.
func (s *callStack) lookUpVar(id identifier) *thunk {
for i := len(s.stack) - 1; i >= 0; i-- {
bind := s.stack[i].bindings[id]
if bind != nil {
return bind
}
if s.stack[i].isCall {
// Nothing beyond the captured environment of the thunk / closure.
break
}
}
return nil
}
func makeCallStack(limit int) callStack {
return callStack{
Calls: 0,
Limit: limit,
calls: 0,
limit: limit,
}
}
@ -156,35 +272,216 @@ func makeCallStack(limit int) callStack {
// TODO(dcunnin): Add multi output.
type interpreter struct {
Stack callStack
ExternalVars vmExtMap
stack callStack
idArrayElement identifier
idInvariant identifier
externalVars vmExtMap
}
func (i *interpreter) execute(a astNode) (value, error) {
func (i *interpreter) capture(freeVars identifiers) bindingFrame {
var env bindingFrame
for _, fv := range freeVars {
env[fv] = i.stack.lookUpVar(fv)
}
return env
}
type fieldHideMap map[string]astObjectFieldHide
func (i *interpreter) objectFieldsAux(obj value) fieldHideMap {
r := make(fieldHideMap)
switch obj := obj.(type) {
// case *valueExtendedObject:
// TODO(dcunnin): this
case *valueSimpleObject:
for fieldName, field := range obj.fields {
r[fieldName] = field.hide
}
// case *valueComprehensionObject:
// TODO(dcunnin): this
}
return r
}
func (i *interpreter) objectFields(obj value, manifesting bool) []string {
var r []string
for fieldName, hide := range i.objectFieldsAux(obj) {
if !manifesting || hide != astObjectFieldHidden {
r = append(r, fieldName)
}
}
return r
}
func (i *interpreter) findObject(f string, curr value, startFrom int, counter *int) value {
switch curr := curr.(type) {
// case *valueExtendedObject:
// TODO(dcunnin): this
case *valueSimpleObject:
if *counter >= startFrom {
if _, ok := curr.fields[f]; ok {
return curr
}
}
*counter++
// case *valueComprehensionObject:
/*
if *counter >= startFrom {
// TODO(dcunnin): this
}
*counter++
*/
}
return nil
}
func (i *interpreter) objectIndex(loc *LocationRange, obj value, f string, offset int) (astNode, error) {
var foundAt int
self := obj
found := i.findObject(f, obj, offset, &foundAt)
if found == nil {
return nil, makeRuntimeError(loc, fmt.Sprintf("Field does not exist: %s", f))
}
switch found := found.(type) {
case *valueSimpleObject:
field := found.fields[f]
i.stack.newCall(loc, found, self, foundAt, found.upValues)
return field.body, nil
// case *valueComprehensionObject:
/*
// TODO(dcunnin): this
*/
default:
return nil, fmt.Errorf("Internal error: findObject returned unrecognized type: %s", reflect.TypeOf(found))
}
}
func (i *interpreter) evaluate(a astNode) (value, error) {
// TODO(dcunnin): All the other cases...
switch ast := a.(type) {
case *astArray:
self, offset := i.stack.getSelfBinding()
var elements []*thunk
for _, el := range ast.elements {
elThunk := makeThunk(i.idArrayElement, self, offset, el)
elThunk.upValues = i.capture(el.FreeVariables())
elements = append(elements, elThunk)
}
return &valueArray{elements}, nil
case *astBinary:
// TODO(dcunnin): Assume it's + on numbers for now
leftVal, err := i.execute(ast.left)
leftVal, err := i.evaluate(ast.left)
if err != nil {
return nil, err
}
leftNum := leftVal.(*valueNumber).value
rightVal, err := i.execute(ast.right)
rightVal, err := i.evaluate(ast.right)
if err != nil {
return nil, err
}
rightNum := rightVal.(*valueNumber).value
return makeValueNumber(leftNum + rightNum), nil
case *astLiteralNull:
return makeValueNull(), nil
case *astDesugaredObject:
// Evaluate all the field names. Check for null, dups, etc.
fields := make(valueSimpleObjectFieldMap)
for _, field := range ast.fields {
fieldNameValue, err := i.evaluate(field.name)
if err != nil {
return nil, err
}
var fieldName string
switch fieldNameValue := fieldNameValue.(type) {
case *valueString:
fieldName = fieldNameValue.value
case *valueNull:
// Omitted field.
continue
default:
return nil, makeRuntimeError(ast.Loc(), "Field name was not a string.")
}
if err != nil {
return nil, err
}
if _, ok := fields[fieldName]; ok {
return nil, makeRuntimeError(ast.Loc(), fmt.Sprintf("Duplicate field name: \"%s\"", fieldName))
}
fields[fieldName] = valueSimpleObjectField{field.hide, field.body}
}
upValues := i.capture(ast.FreeVariables())
return &valueSimpleObject{upValues, fields, ast.asserts}, nil
case *astLiteralBoolean:
return makeValueBoolean(ast.value), nil
case *astLiteralNull:
return makeValueNull(), nil
case *astLiteralNumber:
return makeValueNumber(ast.value), nil
default:
return nil, makeRuntimeError("Executing this AST type not implemented yet.")
case *astLiteralString:
return makeValueString(ast.value), nil
case *astLocal:
vars := make(bindingFrame)
self, offset := i.stack.getSelfBinding()
for _, bind := range ast.binds {
th := makeThunk(bind.variable, self, offset, bind.body)
vars[bind.variable] = th
}
for _, bind := range ast.binds {
th := vars[bind.variable]
th.upValues = i.capture(bind.body.FreeVariables())
}
i.stack.newLocal(vars)
// Add new stack frame, with new thunk for this variable
// execute body WRT stack frame.
return i.evaluate(ast.body)
default:
return nil, makeRuntimeError(ast.Loc(), fmt.Sprintf("Executing this AST type not implemented yet: %v", reflect.TypeOf(a)))
}
}
// unparseString Wraps in "" and escapes stuff to make the string JSON-compliant and human-readable.
func unparseString(v string) string {
var buf bytes.Buffer
buf.WriteString("\"")
for _, c := range v {
switch c {
case '"':
buf.WriteString("\n")
case '\\':
buf.WriteString("\\\\")
case '\b':
buf.WriteString("\\b")
case '\f':
buf.WriteString("\\f")
case '\n':
buf.WriteString("\\n")
case '\r':
buf.WriteString("\\r")
case '\t':
buf.WriteString("\\t")
case 0:
buf.WriteString("\\u0000")
default:
if c < 0x20 || (c >= 0x7f && c <= 0x9f) {
buf.WriteString(fmt.Sprintf("\\u%04x", int(c)))
} else {
buf.WriteRune(c)
}
}
}
buf.WriteString("\"")
return buf.String()
}
func unparseNumber(v float64) string {
@ -198,36 +495,157 @@ func unparseNumber(v float64) string {
return fmt.Sprintf("%.17g", v)
}
func (i *interpreter) manifestJSON(v value, multiline bool, indent string, buf *bytes.Buffer) error {
func (i *interpreter) manifestJSON(loc *LocationRange, v value, multiline bool, indent string, buf *bytes.Buffer) error {
// TODO(dcunnin): All the other types...
switch v := v.(type) {
case *valueArray:
if len(v.elements) == 0 {
buf.WriteString("[ ]")
} else {
var prefix string
var indent2 string
if multiline {
prefix = "[\n"
indent2 = indent + " "
} else {
prefix = "["
indent2 = indent
}
for _, th := range v.elements {
tloc := loc
if th.body != nil {
tloc = th.body.Loc()
}
var elVal value
if th.filled() {
i.stack.newCall(loc, th, nil, 0, make(bindingFrame))
elVal = th.content
} else {
i.stack.newCall(loc, th, th.self, th.offset, th.upValues)
var err error
elVal, err = i.evaluate(th.body)
if err != nil {
return err
}
}
buf.WriteString(prefix)
buf.WriteString(indent2)
err := i.manifestJSON(tloc, elVal, multiline, indent2, buf)
if err != nil {
return err
}
i.stack.pop()
if multiline {
prefix = ",\n"
} else {
prefix = ", "
}
}
if multiline {
buf.WriteString("\n")
}
buf.WriteString(indent)
buf.WriteString("]")
}
case *valueBoolean:
if v.value {
buf.WriteString("true")
} else {
buf.WriteString("false")
}
case *valueNull:
buf.WriteString("null")
case *valueClosure:
return makeRuntimeError(loc, "Couldn't manifest function in JSON output.")
case *valueNumber:
buf.WriteString(unparseNumber(v.value))
case *valueNull:
buf.WriteString("null")
// TODO(dcunnin): Other types representing objects will be handled by the same code here.
case *valueSimpleObject:
// TODO(dcunnin): Run invariants (object-level assertions).
fieldNames := i.objectFields(v, true)
sort.Strings(fieldNames)
if len(fieldNames) == 0 {
buf.WriteString("{ }")
} else {
var prefix string
var indent2 string
if multiline {
prefix = "{\n"
indent2 = indent + " "
} else {
prefix = "{"
indent2 = indent
}
for _, fieldName := range fieldNames {
body, err := i.objectIndex(loc, v, fieldName, 0)
if err != nil {
return err
}
fieldVal, err := i.evaluate(body)
if err != nil {
return err
}
buf.WriteString(prefix)
buf.WriteString(indent2)
buf.WriteString("\"")
buf.WriteString(fieldName)
buf.WriteString("\"")
buf.WriteString(": ")
err = i.manifestJSON(body.Loc(), fieldVal, multiline, indent2, buf)
if err != nil {
return err
}
if multiline {
prefix = ",\n"
} else {
prefix = ", "
}
}
if multiline {
buf.WriteString("\n")
}
buf.WriteString(indent)
buf.WriteString("}")
}
case *valueString:
buf.WriteString(unparseString(v.value))
default:
return makeRuntimeError("Manifesting this value not implemented yet.")
return makeRuntimeError(loc, fmt.Sprintf("Manifesting this value not implemented yet: %s", reflect.TypeOf(v)))
}
return nil
}
func execute(ast astNode, ext vmExtMap, maxStack int) (string, error) {
func evaluate(ast astNode, ext vmExtMap, maxStack int) (string, error) {
i := interpreter{
Stack: makeCallStack(maxStack),
ExternalVars: ext,
stack: makeCallStack(maxStack),
idArrayElement: identifier("array_element"),
idInvariant: identifier("object_assert"),
externalVars: ext,
}
result, err := i.execute(ast)
result, err := i.evaluate(ast)
if err != nil {
return "", err
}
var buffer bytes.Buffer
err = i.manifestJSON(result, true, "", &buffer)
loc := makeLocationRangeMessage("During manifestation")
err = i.manifestJSON(&loc, result, true, "", &buffer)
if err != nil {
return "", err
}

View File

@ -58,8 +58,11 @@ func (vm *VM) EvaluateSnippet(filename string, snippet string) (string, error) {
if err != nil {
return "", err
}
ast, err = desugarFile(ast)
output, err := execute(ast, vm.ext, vm.MaxStack)
err = desugarFile(&ast)
if err != nil {
return "", err
}
output, err := evaluate(ast, vm.ext, vm.MaxStack)
if err != nil {
return "", err
}

View File

@ -36,6 +36,10 @@ var mainTests = []mainTest{
{"simple_arith1", "3 + 3", "6", ""},
{"simple_arith2", "3 + 3 + 3", "9", ""},
{"simple_arith3", "(3 + 3) + (3 + 3)", "12", ""},
{"empty_array", "[]", "[ ]", ""},
{"array", "[1, 2, 1 + 2]", "[\n 1,\n 2,\n 3\n]", ""},
{"empty_object", "{}", "{ }", ""},
{"object", `{"x": 1+1}`, "{\n \"x\": 2\n}", ""},
}
func TestMain(t *testing.T) {

View File

@ -682,7 +682,6 @@ func (p *parser) parseTerminal() (astNode, error) {
return &astVar{
astNodeBase: astNodeBase{loc: tok.loc},
id: identifier(tok.data),
original: identifier(tok.data),
}, nil
case tokenSelf:
return &astSelf{
@ -751,7 +750,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
return nil, err
}
return &astAssert{
astNodeBase: astNodeBase{locFromTokenAST(begin, rest)},
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, rest)},
cond: cond,
message: msg,
rest: rest,
@ -764,7 +763,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
return nil, err
}
return &astError{
astNodeBase: astNodeBase{locFromTokenAST(begin, expr)},
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, expr)},
expr: expr,
}, nil
@ -793,7 +792,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
lr = locFromTokenAST(begin, branchFalse)
}
return &astConditional{
astNodeBase: astNodeBase{lr},
astNodeBase: astNodeBase{loc: lr},
cond: cond,
branchTrue: branchTrue,
branchFalse: branchFalse,
@ -812,7 +811,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
return nil, err
}
return &astFunction{
astNodeBase: astNodeBase{locFromTokenAST(begin, body)},
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, body)},
parameters: params,
trailingComma: gotComma,
body: body,
@ -828,7 +827,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
}
if lit, ok := body.(*astLiteralString); ok {
return &astImport{
astNodeBase: astNodeBase{locFromTokenAST(begin, body)},
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, body)},
file: lit.value,
}, nil
}
@ -842,7 +841,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
}
if lit, ok := body.(*astLiteralString); ok {
return &astImportStr{
astNodeBase: astNodeBase{locFromTokenAST(begin, body)},
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, body)},
file: lit.value,
}, nil
}
@ -869,7 +868,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
return nil, err
}
return &astLocal{
astNodeBase: astNodeBase{locFromTokenAST(begin, body)},
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, body)},
binds: binds,
body: body,
}, nil
@ -888,7 +887,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
return nil, err
}
return &astUnary{
astNodeBase: astNodeBase{locFromTokenAST(op, expr)},
astNodeBase: astNodeBase{loc: locFromTokenAST(op, expr)},
op: uop,
expr: expr,
}, nil