Support of +:, in, in super

Also profiling
This commit is contained in:
Stanisław Barzowski 2017-08-30 16:41:15 -04:00 committed by Dave Cunningham
parent 93bcf64454
commit 0c86c9e109
49 changed files with 184 additions and 48 deletions

View File

@ -453,9 +453,10 @@ type Object struct {
// ---------------------------------------------------------------------------
type DesugaredObjectField struct {
Hide ObjectFieldHide
Name Node
Body Node
Hide ObjectFieldHide
Name Node
Body Node
PlusSuper bool
}
type DesugaredObjectFields []DesugaredObjectField

View File

@ -219,7 +219,7 @@ func builtinLength(e *evaluator, xp potentialValue) (value, error) {
var num int
switch x := x.(type) {
case valueObject:
num = len(objectFields(x, true))
num = len(objectFields(x, withoutHidden))
case *valueArray:
num = len(x.elements)
case *valueString:
@ -511,7 +511,7 @@ func builtinObjectFieldsEx(e *evaluator, objp potentialValue, includeHiddenP pot
if err != nil {
return nil, err
}
fields := objectFields(obj, !includeHidden.value)
fields := objectFields(obj, withHiddenFromBool(includeHidden.value))
sort.Strings(fields)
elems := []potentialValue{}
for _, fieldname := range fields {
@ -533,12 +533,9 @@ func builtinObjectHasEx(e *evaluator, objp potentialValue, fnamep potentialValue
if err != nil {
return nil, err
}
for _, fieldname := range objectFields(obj, !includeHidden.value) {
if fieldname == string(fname.value) {
return makeValueBoolean(true), nil
}
}
return makeValueBoolean(false), nil
h := withHiddenFromBool(includeHidden.value)
fieldp := tryObjectIndex(objectBinding(obj), string(fname.value), h)
return makeValueBoolean(fieldp != nil), nil
}
func builtinPow(e *evaluator, basep potentialValue, expp potentialValue) (value, error) {
@ -657,6 +654,7 @@ var desugaredBop = map[ast.BinaryOp]ast.Identifier{
ast.BopPercent: "mod",
ast.BopManifestEqual: "equals",
ast.BopManifestUnequal: "notEquals", // Special case
ast.BopIn: "objectHasAll",
}
var bopBuiltins = []*BinaryBuiltin{

BIN
compat/libgojsonnet.so Normal file

Binary file not shown.

View File

@ -191,19 +191,6 @@ func desugarFields(location ast.LocationRange, fields *ast.ObjectFields, objLeve
}
}
// 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<ast.Binary>(ast->location, super_f, BOP_PLUS, field.expr2)
field.superSugar = false
*/
}
return nil
}
@ -307,7 +294,7 @@ func buildDesugaredObject(nodeBase ast.NodeBase, fields ast.ObjectFields) *ast.D
if field.Kind == ast.ObjectAssert {
newAsserts = append(newAsserts, field.Expr2)
} else if field.Kind == ast.ObjectFieldExpr {
newFields = append(newFields, ast.DesugaredObjectField{field.Hide, field.Expr1, field.Expr2})
newFields = append(newFields, ast.DesugaredObjectField{field.Hide, field.Expr1, field.Expr2, field.SuperSugar})
} else {
panic(fmt.Sprintf("INTERNAL ERROR: field should have been desugared: %s", field.Kind))
}
@ -400,6 +387,8 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
Op: ast.UopNot,
Expr: buildStdCall(desugaredBop[ast.BopManifestEqual], node.Left, node.Right),
}
} else if node.Op == ast.BopIn {
*astPtr = buildStdCall(funcname, node.Right, node.Left)
} else {
*astPtr = buildStdCall(funcname, node.Left, node.Right)
}

View File

@ -324,7 +324,11 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
if _, ok := fields[fieldName]; ok {
return nil, e.Error(duplicateFieldNameErrMsg(fieldName))
}
fields[fieldName] = valueSimpleObjectField{field.Hide, &codeUnboundField{field.Body}}
var f unboundField = &codeUnboundField{field.Body}
if field.PlusSuper {
f = &PlusSuperUnboundField{f}
}
fields[fieldName] = valueSimpleObjectField{field.Hide, f}
}
var asserts []unboundField
for _, assert := range ast.Asserts {
@ -422,7 +426,19 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
if err != nil {
return nil, err
}
return superIndex(e, i.stack.getSelfBinding(), indexStr.getString())
return objectIndex(e, i.stack.getSelfBinding().super(), indexStr.getString())
case *ast.InSuper:
index, err := e.evalInCurrentContext(ast.Index)
if err != nil {
return nil, err
}
indexStr, err := e.getString(index)
if err != nil {
return nil, err
}
field := tryObjectIndex(i.stack.getSelfBinding().super(), indexStr.getString(), withHidden)
return makeValueBoolean(field != nil), nil
case *ast.Function:
return &valueFunction{
@ -567,9 +583,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
buf.WriteString("null")
case valueObject:
// TODO(dcunnin): Run invariants (object-level assertions).
fieldNames := objectFields(v, true)
fieldNames := objectFields(v, withoutHidden)
sort.Strings(fieldNames)
err := checkAssertions(e, v)

View File

@ -19,7 +19,9 @@ package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"runtime/pprof"
"github.com/google/go-jsonnet"
)
@ -29,6 +31,17 @@ func usage() {
}
func main() {
// https://blog.golang.org/profiling-go-programs
var cpuprofile = os.Getenv("JSONNET_CPU_PROFILE")
if cpuprofile != "" {
f, err := os.Create(cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
// TODO(sbarzowski) Be consistent about error codes with C++ maybe
vm := jsonnet.MakeVM()
if len(os.Args) != 2 {

View File

@ -76,6 +76,11 @@ func analyzeVisit(a ast.Node, inObject bool, vars ast.IdentifierSet) error {
//nothing to do here
case *ast.ImportStr:
//nothing to do here
case *ast.InSuper:
if !inObject {
return parser.MakeStaticError("Can't use super outside of an object.", *a.Loc())
}
visitNext(a.Index, inObject, vars, s)
case *ast.SuperIndex:
if !inObject {
return parser.MakeStaticError("Can't use super outside of an object.", *a.Loc())

1
testdata/in.golden vendored Normal file
View File

@ -0,0 +1 @@
true

1
testdata/in.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
"x" in { "x": 42 }

1
testdata/in2.golden vendored Normal file
View File

@ -0,0 +1 @@
false

1
testdata/in2.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
"x" in { }

1
testdata/in3.golden vendored Normal file
View File

@ -0,0 +1 @@
true

1
testdata/in3.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
"x" in { x: 42, y: 42 }

1
testdata/in4.golden vendored Normal file
View File

@ -0,0 +1 @@
false

1
testdata/in4.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
"x" in { assert false }

3
testdata/insuper.golden vendored Normal file
View File

@ -0,0 +1,3 @@
{
"x": 42
}

1
testdata/insuper.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ x: 42 } { assert "x" in super }

1
testdata/insuper2.golden vendored Normal file
View File

@ -0,0 +1 @@
testdata/insuper2:1:11-13 Expected token OPERATOR but got (in, "in")

1
testdata/insuper2.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ } { "x" in super }

3
testdata/insuper3.golden vendored Normal file
View File

@ -0,0 +1,3 @@
{
"false": false
}

1
testdata/insuper3.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ "false": "x" in super }

1
testdata/insuper4.golden vendored Normal file
View File

@ -0,0 +1 @@
testdata/insuper4:1:2-13 Can't use super outside of an object.

1
testdata/insuper4.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
"x" in super

6
testdata/insuper5.golden vendored Normal file
View File

@ -0,0 +1,6 @@
{
"x": 42,
"y": {
"false": false
}
}

1
testdata/insuper5.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ x: 42 } { y: { "false": "x" in super } }

1
testdata/insuper6.golden vendored Normal file
View File

@ -0,0 +1 @@
testdata/insuper6:1:10-20 Unknown variable: undeclared

1
testdata/insuper6.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ assert undeclared in super }

1
testdata/insuper7.golden vendored Normal file
View File

@ -0,0 +1 @@
"object"

1
testdata/insuper7.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.type({ assert false } { x: "x" in super })

6
testdata/supersugar.golden vendored Normal file
View File

@ -0,0 +1,6 @@
{
"x": {
"a": 1,
"b": 2
}
}

1
testdata/supersugar.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ x: {a: 1} } { x +: {b: 2} }

3
testdata/supersugar2.golden vendored Normal file
View File

@ -0,0 +1,3 @@
{
"x": 1
}

1
testdata/supersugar2.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ x :+ 1 }

3
testdata/supersugar3.golden vendored Normal file
View File

@ -0,0 +1,3 @@
{
"x": true
}

1
testdata/supersugar3.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ assert self.x } { x +: true }

4
testdata/supersugar4.golden vendored Normal file
View File

@ -0,0 +1,4 @@
{
"x": true,
"y": 43
}

1
testdata/supersugar4.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ assert self.x, y: 42 } { x: true, y +: 1 }

1
testdata/supersugar5.golden vendored Normal file
View File

@ -0,0 +1 @@
42

1
testdata/supersugar5.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
({ } { x +: function(x) x }).x(42)

3
testdata/supersugar6.golden vendored Normal file
View File

@ -0,0 +1,3 @@
{
"x": "hello, world"
}

1
testdata/supersugar6.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ x: "hello, " } { x +: "world" }

10
testdata/supersugar7.golden vendored Normal file
View File

@ -0,0 +1,10 @@
{
"x": [
1,
2,
3,
4,
5,
6
]
}

1
testdata/supersugar7.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ x: [1, 2, 3] } { x +: [4, 5, 6] }

1
testdata/supersugar8.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: Object assertion failed.

1
testdata/supersugar8.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
{ assert self.x } { x +: false }

1
testdata/supersugar9.golden vendored Normal file
View File

@ -0,0 +1 @@
"object"

2
testdata/supersugar9.jsonnet vendored Normal file
View File

@ -0,0 +1,2 @@
local infloop() = infloop();
std.type({ assert infloop, x: 1 } { x +: 1 })

View File

@ -34,7 +34,7 @@ func (rv *readyValue) getValue(i *interpreter, t *TraceElement) (value, error) {
return rv.content, nil
}
func (rv *readyValue) bindToObject(sb selfBinding, origBinding bindingFrame) potentialValue {
func (rv *readyValue) bindToObject(sb selfBinding, origBinding bindingFrame, fieldName string) potentialValue {
return rv
}
@ -78,6 +78,10 @@ func makeCallThunk(ec evalCallable, args callArguments) potentialValue {
return makeCachedThunk(&callThunk{function: ec, args: args})
}
func call(ec evalCallable, arguments ...potentialValue) potentialValue {
return makeCallThunk(ec, args(arguments...))
}
func (th *callThunk) getValue(i *interpreter, trace *TraceElement) (value, error) {
evaluator := makeEvaluator(i, trace)
err := checkArguments(evaluator, th.args, th.function.Parameters())
@ -134,7 +138,7 @@ type codeUnboundField struct {
body ast.Node
}
func (f *codeUnboundField) bindToObject(sb selfBinding, origBindings bindingFrame) potentialValue {
func (f *codeUnboundField) bindToObject(sb selfBinding, origBindings bindingFrame, fieldName string) potentialValue {
// TODO(sbarzowski) better object names (perhaps include a field name too?)
return makeThunk("object_field", makeEnvironment(origBindings, sb), f.body)
}
@ -146,7 +150,7 @@ type bindingsUnboundField struct {
bindings bindingFrame
}
func (f *bindingsUnboundField) bindToObject(sb selfBinding, origBindings bindingFrame) potentialValue {
func (f *bindingsUnboundField) bindToObject(sb selfBinding, origBindings bindingFrame, fieldName string) potentialValue {
var upValues bindingFrame
upValues = make(bindingFrame)
for variable, pvalue := range origBindings {
@ -155,7 +159,20 @@ func (f *bindingsUnboundField) bindToObject(sb selfBinding, origBindings binding
for variable, pvalue := range f.bindings {
upValues[variable] = pvalue
}
return f.inner.bindToObject(sb, upValues)
return f.inner.bindToObject(sb, upValues, fieldName)
}
type PlusSuperUnboundField struct {
inner unboundField
}
func (f *PlusSuperUnboundField) bindToObject(sb selfBinding, origBinding bindingFrame, fieldName string) potentialValue {
left := tryObjectIndex(sb.super(), fieldName, withHidden)
right := f.inner.bindToObject(sb, origBinding, fieldName)
if left != nil {
return call(bopBuiltins[ast.BopPlus], left, right)
}
return right
}
// evalCallables

View File

@ -311,6 +311,29 @@ func makeUnboundSelfBinding() selfBinding {
}
}
func objectBinding(obj valueObject) selfBinding {
return selfBinding{self: obj, superDepth: 0}
}
func (sb selfBinding) super() selfBinding {
return selfBinding{self: sb.self, superDepth: sb.superDepth + 1}
}
type Hidden int
const (
withHidden Hidden = iota
withoutHidden
)
func withHiddenFromBool(with bool) Hidden {
if with {
return withHidden
} else {
return withoutHidden
}
}
// Hack - we need to distinguish not-checked-yet and no error situations
// so we have a special value for no error and nil means that we don't know yet.
var errNoErrorInObjectInvariants = errors.New("No error - assertions passed")
@ -377,7 +400,7 @@ func checkAssertionsHelper(e *evaluator, obj valueObject, curr valueObject, supe
return nil
case *valueSimpleObject:
for _, assert := range curr.asserts {
_, err := e.evaluate(assert.bindToObject(selfBinding{self: obj, superDepth: superDepth}, curr.upValues))
_, err := e.evaluate(assert.bindToObject(selfBinding{self: obj, superDepth: superDepth}, curr.upValues, ""))
if err != nil {
return err
}
@ -400,7 +423,7 @@ func checkAssertions(e *evaluator, obj valueObject) error {
}
func (o *valueSimpleObject) index(e *evaluator, field string) (value, error) {
return objectIndex(e, selfBinding{self: o, superDepth: 0}, field)
return objectIndex(e, objectBinding(o), field)
}
func (*valueSimpleObject) inheritanceSize() int {
@ -426,7 +449,7 @@ type valueSimpleObjectField struct {
// unboundField is a field that doesn't know yet in which object it is.
type unboundField interface {
bindToObject(sb selfBinding, origBinding bindingFrame) potentialValue
bindToObject(sb selfBinding, origBinding bindingFrame, fieldName string) potentialValue
}
// valueExtendedObject represents an object created through inheritence (left + right).
@ -455,7 +478,7 @@ type valueExtendedObject struct {
}
func (o *valueExtendedObject) index(e *evaluator, field string) (value, error) {
return objectIndex(e, selfBinding{self: o, superDepth: 0}, field)
return objectIndex(e, objectBinding(o), field)
}
func (o *valueExtendedObject) inheritanceSize() int {
@ -498,23 +521,26 @@ func findField(curr value, minSuperDepth int, f string) (*valueSimpleObjectField
}
}
func superIndex(e *evaluator, currentSB selfBinding, field string) (value, error) {
superSB := selfBinding{self: currentSB.self, superDepth: currentSB.superDepth + 1}
return objectIndex(e, superSB, field)
}
func objectIndex(e *evaluator, sb selfBinding, fieldName string) (value, error) {
err := checkAssertions(e, sb.self)
if err != nil {
return nil, err
}
field, upValues, foundAt := findField(sb.self, sb.superDepth, fieldName)
if field == nil {
objp := tryObjectIndex(sb, fieldName, withHidden)
if objp == nil {
return nil, e.Error(fmt.Sprintf("Field does not exist: %s", fieldName))
}
return e.evaluate(objp)
}
func tryObjectIndex(sb selfBinding, fieldName string, h Hidden) potentialValue {
field, upValues, foundAt := findField(sb.self, sb.superDepth, fieldName)
if field == nil || (h == withoutHidden && field.hide == ast.ObjectFieldHidden) {
return nil
}
fieldSelfBinding := selfBinding{self: sb.self, superDepth: foundAt}
return e.evaluate(field.field.bindToObject(fieldSelfBinding, upValues))
return field.field.bindToObject(fieldSelfBinding, upValues, fieldName)
}
type fieldHideMap map[string]ast.ObjectFieldHide
@ -544,10 +570,10 @@ func objectFieldsVisibility(obj valueObject) fieldHideMap {
return r
}
func objectFields(obj valueObject, manifesting bool) []string {
func objectFields(obj valueObject, h Hidden) []string {
var r []string
for fieldName, hide := range objectFieldsVisibility(obj) {
if !manifesting || hide != ast.ObjectFieldHidden {
if h == withHidden || hide != ast.ObjectFieldHidden {
r = append(r, fieldName)
}
}