From 09c7c8c7c0d65dc11b6069fe58d63f3a4bf6ebfd Mon Sep 17 00:00:00 2001 From: Dave Cunningham Date: Sun, 3 Apr 2016 14:10:35 -0400 Subject: [PATCH] Pass through JSON --- .gitignore | 1 + README.md | 3 + ast.go | 21 +- desugarer.go | 384 ++++++++++++++++++++++++++++++++++++- interpreter.go | 508 ++++++++++++++++++++++++++++++++++++++++++++----- main.go | 7 +- main_test.go | 4 + parser.go | 17 +- 8 files changed, 879 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index b25c15b..4c5f88a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *~ +.*.swp diff --git a/README.md b/README.md index 80e0ab1..4ac6358 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/ast.go b/ast.go index 1c9ecc2..79bc96f 100644 --- a/ast.go +++ b/ast.go @@ -32,19 +32,25 @@ type identifiers []identifier type astNode interface { Loc() *LocationRange + FreeVariables() identifiers } type astNodes []astNode // --------------------------------------------------------------------------- type astNodeBase struct { - loc LocationRange + 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 @@ -503,8 +517,7 @@ type astUnary struct { // astVar represents variables. type astVar struct { astNodeBase - id identifier - original identifier + id identifier } // --------------------------------------------------------------------------- diff --git a/desugarer.go b/desugarer.go index 3b51a5c..8674777 100644 --- a/desugarer.go +++ b/desugarer.go @@ -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 "", 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(field.expr2->location, msg_str, + LiteralString::DOUBLE, "") + } + + // if expr2 then true else error msg + field.expr2 = alloc->make( + ast->location, + field.expr2, + alloc->make(E, true), + alloc->make(msg->location, msg)) + */ + } + + // Remove methods + // TODO(dcunnin): this + for _, field := range *fields { + if !field.methodSugar { + continue + } + /* + field.expr2 = alloc->make( + 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(field.expr1->location, field.expr1, nil) + field.expr2 = alloc->make(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 nil, err + return err } // TODO(dcunnin): wrap in std local - return ast, nil + return nil } diff --git a/interpreter.go b/interpreter.go index 55454a2..72b1290 100644 --- a/interpreter.go +++ b/interpreter.go @@ -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,37 +272,218 @@ 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 + + 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("Executing this AST type not implemented yet.") + 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 { if v == math.Floor(v) { return fmt.Sprintf("%.0f", v) @@ -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 } diff --git a/main.go b/main.go index fe72bf3..629c045 100644 --- a/main.go +++ b/main.go @@ -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 } diff --git a/main_test.go b/main_test.go index 781dc98..074e9a2 100644 --- a/main_test.go +++ b/main_test.go @@ -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) { diff --git a/parser.go b/parser.go index 0453387..7a7ed12 100644 --- a/parser.go +++ b/parser.go @@ -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