mirror of
https://github.com/google/go-jsonnet.git
synced 2025-08-07 14:57:24 +02:00
Pass through JSON
This commit is contained in:
parent
c3e4c805f2
commit
09c7c8c7c0
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
*~
|
||||
.*.swp
|
||||
|
@ -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
17
ast.go
@ -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
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
384
desugarer.go
384
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 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
|
||||
}
|
||||
|
510
interpreter.go
510
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,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
|
||||
}
|
||||
|
7
main.go
7
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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
17
parser.go
17
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
|
||||
|
Loading…
Reference in New Issue
Block a user