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 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).
|
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
|
## Implementation Notes
|
||||||
|
|
||||||
|
21
ast.go
21
ast.go
@ -32,19 +32,25 @@ type identifiers []identifier
|
|||||||
|
|
||||||
type astNode interface {
|
type astNode interface {
|
||||||
Loc() *LocationRange
|
Loc() *LocationRange
|
||||||
|
FreeVariables() identifiers
|
||||||
}
|
}
|
||||||
type astNodes []astNode
|
type astNodes []astNode
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
type astNodeBase struct {
|
type astNodeBase struct {
|
||||||
loc LocationRange
|
loc LocationRange
|
||||||
|
freeVariables identifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *astNodeBase) Loc() *LocationRange {
|
func (n *astNodeBase) Loc() *LocationRange {
|
||||||
return &n.loc
|
return &n.loc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *astNodeBase) FreeVariables() identifiers {
|
||||||
|
return n.freeVariables
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// +gen stringer
|
// +gen stringer
|
||||||
@ -386,7 +392,15 @@ type astObjectField struct {
|
|||||||
expr2, expr3 astNode // In scope of the object (can see self).
|
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
|
type astObjectFields []astObjectField
|
||||||
|
|
||||||
@ -503,8 +517,7 @@ type astUnary struct {
|
|||||||
// astVar represents variables.
|
// astVar represents variables.
|
||||||
type astVar struct {
|
type astVar struct {
|
||||||
astNodeBase
|
astNodeBase
|
||||||
id identifier
|
id identifier
|
||||||
original identifier
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
384
desugarer.go
384
desugarer.go
@ -16,15 +16,387 @@ limitations under the License.
|
|||||||
|
|
||||||
package jsonnet
|
package jsonnet
|
||||||
|
|
||||||
func desugar(ast astNode, objLevel int) (astNode, error) {
|
import (
|
||||||
return ast, nil
|
"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) {
|
func stringUnescape(loc *LocationRange, s string) (string, error) {
|
||||||
ast, err := desugar(ast, 0)
|
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<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 {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
// TODO(dcunnin): wrap in std local
|
// TODO(dcunnin): wrap in std local
|
||||||
return ast, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
508
interpreter.go
508
interpreter.go
@ -20,6 +20,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Misc top-level stuff
|
// Misc top-level stuff
|
||||||
@ -37,8 +39,15 @@ type RuntimeError struct {
|
|||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRuntimeError(msg string) RuntimeError {
|
func makeRuntimeError(loc *LocationRange, msg string) RuntimeError {
|
||||||
|
// TODO(dcunnin): Build proper stacktrace.
|
||||||
return RuntimeError{
|
return RuntimeError{
|
||||||
|
StackTrace: []TraceFrame{
|
||||||
|
{
|
||||||
|
Loc: *loc,
|
||||||
|
Name: "name",
|
||||||
|
},
|
||||||
|
},
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +59,7 @@ func (err RuntimeError) Error() string {
|
|||||||
|
|
||||||
// Values and state
|
// Values and state
|
||||||
|
|
||||||
type bindingFrame map[*identifier]thunk
|
type bindingFrame map[identifier]*thunk
|
||||||
|
|
||||||
type value interface {
|
type value interface {
|
||||||
}
|
}
|
||||||
@ -88,43 +97,63 @@ func makeValueNull() *valueNull {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type thunk struct {
|
type thunk struct {
|
||||||
Content value // nil if not filled
|
content value // nil if not filled
|
||||||
Name *identifier
|
name identifier
|
||||||
UpValues bindingFrame
|
upValues bindingFrame
|
||||||
Self value
|
self value
|
||||||
Offset int
|
offset int
|
||||||
Body astNode
|
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{
|
return &thunk{
|
||||||
Name: name,
|
name: name,
|
||||||
Self: self,
|
self: self,
|
||||||
Offset: offset,
|
offset: offset,
|
||||||
Body: body,
|
body: body,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *thunk) fill(v value) {
|
func (t *thunk) fill(v value) {
|
||||||
t.Content = v
|
t.content = v
|
||||||
t.Self = nil
|
t.self = nil
|
||||||
t.UpValues = make(bindingFrame) // clear the map
|
t.upValues = make(bindingFrame) // clear the map
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *thunk) filled() bool {
|
||||||
|
return t.content != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type valueArray struct {
|
type valueArray struct {
|
||||||
Elements []thunk
|
elements []*thunk
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeValueArray(elements []thunk) *valueArray {
|
func makeValueArray(elements []*thunk) *valueArray {
|
||||||
return &valueArray{
|
return &valueArray{
|
||||||
Elements: elements,
|
elements: elements,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(dcunnin): SimpleObject
|
type valueClosure struct {
|
||||||
// TODO(dcunnin): ExtendedObject
|
upValues bindingFrame
|
||||||
// TODO(dcunnin): ComprehensionObject
|
}
|
||||||
// TODO(dcunnin): Closure
|
|
||||||
|
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
|
// The stack
|
||||||
|
|
||||||
@ -135,19 +164,106 @@ type TraceFrame struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type callFrame struct {
|
type callFrame struct {
|
||||||
|
isCall bool
|
||||||
|
ast astNode
|
||||||
|
location LocationRange
|
||||||
|
tailCall bool
|
||||||
|
thunks []*thunk
|
||||||
|
context value
|
||||||
|
self value
|
||||||
|
offset int
|
||||||
bindings bindingFrame
|
bindings bindingFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
type callStack struct {
|
type callStack struct {
|
||||||
Calls int
|
calls int
|
||||||
Limit int
|
limit int
|
||||||
Stack []callFrame
|
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 {
|
func makeCallStack(limit int) callStack {
|
||||||
return callStack{
|
return callStack{
|
||||||
Calls: 0,
|
calls: 0,
|
||||||
Limit: limit,
|
limit: limit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,37 +272,218 @@ func makeCallStack(limit int) callStack {
|
|||||||
// TODO(dcunnin): Add multi output.
|
// TODO(dcunnin): Add multi output.
|
||||||
|
|
||||||
type interpreter struct {
|
type interpreter struct {
|
||||||
Stack callStack
|
stack callStack
|
||||||
ExternalVars vmExtMap
|
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...
|
// TODO(dcunnin): All the other cases...
|
||||||
switch ast := a.(type) {
|
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:
|
case *astBinary:
|
||||||
// TODO(dcunnin): Assume it's + on numbers for now
|
// TODO(dcunnin): Assume it's + on numbers for now
|
||||||
leftVal, err := i.execute(ast.left)
|
leftVal, err := i.evaluate(ast.left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
leftNum := leftVal.(*valueNumber).value
|
leftNum := leftVal.(*valueNumber).value
|
||||||
rightVal, err := i.execute(ast.right)
|
rightVal, err := i.evaluate(ast.right)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rightNum := rightVal.(*valueNumber).value
|
rightNum := rightVal.(*valueNumber).value
|
||||||
return makeValueNumber(leftNum + rightNum), nil
|
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:
|
case *astLiteralBoolean:
|
||||||
return makeValueBoolean(ast.value), nil
|
return makeValueBoolean(ast.value), nil
|
||||||
|
|
||||||
|
case *astLiteralNull:
|
||||||
|
return makeValueNull(), nil
|
||||||
|
|
||||||
case *astLiteralNumber:
|
case *astLiteralNumber:
|
||||||
return makeValueNumber(ast.value), nil
|
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:
|
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 {
|
func unparseNumber(v float64) string {
|
||||||
if v == math.Floor(v) {
|
if v == math.Floor(v) {
|
||||||
return fmt.Sprintf("%.0f", v)
|
return fmt.Sprintf("%.0f", v)
|
||||||
@ -198,36 +495,157 @@ func unparseNumber(v float64) string {
|
|||||||
return fmt.Sprintf("%.17g", v)
|
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...
|
// TODO(dcunnin): All the other types...
|
||||||
switch v := v.(type) {
|
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:
|
case *valueBoolean:
|
||||||
if v.value {
|
if v.value {
|
||||||
buf.WriteString("true")
|
buf.WriteString("true")
|
||||||
} else {
|
} else {
|
||||||
buf.WriteString("false")
|
buf.WriteString("false")
|
||||||
}
|
}
|
||||||
case *valueNull:
|
|
||||||
buf.WriteString("null")
|
case *valueClosure:
|
||||||
|
return makeRuntimeError(loc, "Couldn't manifest function in JSON output.")
|
||||||
|
|
||||||
case *valueNumber:
|
case *valueNumber:
|
||||||
buf.WriteString(unparseNumber(v.value))
|
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:
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func execute(ast astNode, ext vmExtMap, maxStack int) (string, error) {
|
func evaluate(ast astNode, ext vmExtMap, maxStack int) (string, error) {
|
||||||
i := interpreter{
|
i := interpreter{
|
||||||
Stack: makeCallStack(maxStack),
|
stack: makeCallStack(maxStack),
|
||||||
ExternalVars: ext,
|
idArrayElement: identifier("array_element"),
|
||||||
|
idInvariant: identifier("object_assert"),
|
||||||
|
externalVars: ext,
|
||||||
}
|
}
|
||||||
result, err := i.execute(ast)
|
result, err := i.evaluate(ast)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
err = i.manifestJSON(result, true, "", &buffer)
|
loc := makeLocationRangeMessage("During manifestation")
|
||||||
|
err = i.manifestJSON(&loc, result, true, "", &buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
ast, err = desugarFile(ast)
|
err = desugarFile(&ast)
|
||||||
output, err := execute(ast, vm.ext, vm.MaxStack)
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
output, err := evaluate(ast, vm.ext, vm.MaxStack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,10 @@ var mainTests = []mainTest{
|
|||||||
{"simple_arith1", "3 + 3", "6", ""},
|
{"simple_arith1", "3 + 3", "6", ""},
|
||||||
{"simple_arith2", "3 + 3 + 3", "9", ""},
|
{"simple_arith2", "3 + 3 + 3", "9", ""},
|
||||||
{"simple_arith3", "(3 + 3) + (3 + 3)", "12", ""},
|
{"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) {
|
func TestMain(t *testing.T) {
|
||||||
|
17
parser.go
17
parser.go
@ -682,7 +682,6 @@ func (p *parser) parseTerminal() (astNode, error) {
|
|||||||
return &astVar{
|
return &astVar{
|
||||||
astNodeBase: astNodeBase{loc: tok.loc},
|
astNodeBase: astNodeBase{loc: tok.loc},
|
||||||
id: identifier(tok.data),
|
id: identifier(tok.data),
|
||||||
original: identifier(tok.data),
|
|
||||||
}, nil
|
}, nil
|
||||||
case tokenSelf:
|
case tokenSelf:
|
||||||
return &astSelf{
|
return &astSelf{
|
||||||
@ -751,7 +750,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &astAssert{
|
return &astAssert{
|
||||||
astNodeBase: astNodeBase{locFromTokenAST(begin, rest)},
|
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, rest)},
|
||||||
cond: cond,
|
cond: cond,
|
||||||
message: msg,
|
message: msg,
|
||||||
rest: rest,
|
rest: rest,
|
||||||
@ -764,7 +763,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &astError{
|
return &astError{
|
||||||
astNodeBase: astNodeBase{locFromTokenAST(begin, expr)},
|
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, expr)},
|
||||||
expr: expr,
|
expr: expr,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
@ -793,7 +792,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
|
|||||||
lr = locFromTokenAST(begin, branchFalse)
|
lr = locFromTokenAST(begin, branchFalse)
|
||||||
}
|
}
|
||||||
return &astConditional{
|
return &astConditional{
|
||||||
astNodeBase: astNodeBase{lr},
|
astNodeBase: astNodeBase{loc: lr},
|
||||||
cond: cond,
|
cond: cond,
|
||||||
branchTrue: branchTrue,
|
branchTrue: branchTrue,
|
||||||
branchFalse: branchFalse,
|
branchFalse: branchFalse,
|
||||||
@ -812,7 +811,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &astFunction{
|
return &astFunction{
|
||||||
astNodeBase: astNodeBase{locFromTokenAST(begin, body)},
|
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, body)},
|
||||||
parameters: params,
|
parameters: params,
|
||||||
trailingComma: gotComma,
|
trailingComma: gotComma,
|
||||||
body: body,
|
body: body,
|
||||||
@ -828,7 +827,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
|
|||||||
}
|
}
|
||||||
if lit, ok := body.(*astLiteralString); ok {
|
if lit, ok := body.(*astLiteralString); ok {
|
||||||
return &astImport{
|
return &astImport{
|
||||||
astNodeBase: astNodeBase{locFromTokenAST(begin, body)},
|
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, body)},
|
||||||
file: lit.value,
|
file: lit.value,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -842,7 +841,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
|
|||||||
}
|
}
|
||||||
if lit, ok := body.(*astLiteralString); ok {
|
if lit, ok := body.(*astLiteralString); ok {
|
||||||
return &astImportStr{
|
return &astImportStr{
|
||||||
astNodeBase: astNodeBase{locFromTokenAST(begin, body)},
|
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, body)},
|
||||||
file: lit.value,
|
file: lit.value,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -869,7 +868,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &astLocal{
|
return &astLocal{
|
||||||
astNodeBase: astNodeBase{locFromTokenAST(begin, body)},
|
astNodeBase: astNodeBase{loc: locFromTokenAST(begin, body)},
|
||||||
binds: binds,
|
binds: binds,
|
||||||
body: body,
|
body: body,
|
||||||
}, nil
|
}, nil
|
||||||
@ -888,7 +887,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &astUnary{
|
return &astUnary{
|
||||||
astNodeBase: astNodeBase{locFromTokenAST(op, expr)},
|
astNodeBase: astNodeBase{loc: locFromTokenAST(op, expr)},
|
||||||
op: uop,
|
op: uop,
|
||||||
expr: expr,
|
expr: expr,
|
||||||
}, nil
|
}, nil
|
||||||
|
Loading…
Reference in New Issue
Block a user