diff --git a/ast/ast.go b/ast/ast.go index 862bdeb..2a19dfb 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -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 diff --git a/builtins.go b/builtins.go index eed46fa..f0ddb87 100644 --- a/builtins.go +++ b/builtins.go @@ -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{ diff --git a/compat/libgojsonnet.so b/compat/libgojsonnet.so new file mode 100644 index 0000000..228f632 Binary files /dev/null and b/compat/libgojsonnet.so differ diff --git a/desugarer.go b/desugarer.go index fb7fecd..b4b8752 100644 --- a/desugarer.go +++ b/desugarer.go @@ -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(field.expr1->location, field.expr1, nil) - field.expr2 = alloc->make(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) } diff --git a/interpreter.go b/interpreter.go index 5b53d6a..048a53c 100644 --- a/interpreter.go +++ b/interpreter.go @@ -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) diff --git a/jsonnet/cmd.go b/jsonnet/cmd.go index f2f458c..b513904 100644 --- a/jsonnet/cmd.go +++ b/jsonnet/cmd.go @@ -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 { diff --git a/static_analyzer.go b/static_analyzer.go index bc295f3..56ab9f8 100644 --- a/static_analyzer.go +++ b/static_analyzer.go @@ -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()) diff --git a/testdata/in.golden b/testdata/in.golden new file mode 100644 index 0000000..27ba77d --- /dev/null +++ b/testdata/in.golden @@ -0,0 +1 @@ +true diff --git a/testdata/in.jsonnet b/testdata/in.jsonnet new file mode 100644 index 0000000..3172c98 --- /dev/null +++ b/testdata/in.jsonnet @@ -0,0 +1 @@ +"x" in { "x": 42 } diff --git a/testdata/in2.golden b/testdata/in2.golden new file mode 100644 index 0000000..c508d53 --- /dev/null +++ b/testdata/in2.golden @@ -0,0 +1 @@ +false diff --git a/testdata/in2.jsonnet b/testdata/in2.jsonnet new file mode 100644 index 0000000..d01e6d2 --- /dev/null +++ b/testdata/in2.jsonnet @@ -0,0 +1 @@ +"x" in { } diff --git a/testdata/in3.golden b/testdata/in3.golden new file mode 100644 index 0000000..27ba77d --- /dev/null +++ b/testdata/in3.golden @@ -0,0 +1 @@ +true diff --git a/testdata/in3.jsonnet b/testdata/in3.jsonnet new file mode 100644 index 0000000..eb47d9c --- /dev/null +++ b/testdata/in3.jsonnet @@ -0,0 +1 @@ +"x" in { x: 42, y: 42 } diff --git a/testdata/in4.golden b/testdata/in4.golden new file mode 100644 index 0000000..c508d53 --- /dev/null +++ b/testdata/in4.golden @@ -0,0 +1 @@ +false diff --git a/testdata/in4.jsonnet b/testdata/in4.jsonnet new file mode 100644 index 0000000..2cbc923 --- /dev/null +++ b/testdata/in4.jsonnet @@ -0,0 +1 @@ +"x" in { assert false } diff --git a/testdata/insuper.golden b/testdata/insuper.golden new file mode 100644 index 0000000..81626ce --- /dev/null +++ b/testdata/insuper.golden @@ -0,0 +1,3 @@ +{ + "x": 42 +} diff --git a/testdata/insuper.jsonnet b/testdata/insuper.jsonnet new file mode 100644 index 0000000..cc7074b --- /dev/null +++ b/testdata/insuper.jsonnet @@ -0,0 +1 @@ +{ x: 42 } { assert "x" in super } diff --git a/testdata/insuper2.golden b/testdata/insuper2.golden new file mode 100644 index 0000000..f5947d8 --- /dev/null +++ b/testdata/insuper2.golden @@ -0,0 +1 @@ +testdata/insuper2:1:11-13 Expected token OPERATOR but got (in, "in") diff --git a/testdata/insuper2.jsonnet b/testdata/insuper2.jsonnet new file mode 100644 index 0000000..14b5581 --- /dev/null +++ b/testdata/insuper2.jsonnet @@ -0,0 +1 @@ +{ } { "x" in super } diff --git a/testdata/insuper3.golden b/testdata/insuper3.golden new file mode 100644 index 0000000..6bd9dbc --- /dev/null +++ b/testdata/insuper3.golden @@ -0,0 +1,3 @@ +{ + "false": false +} diff --git a/testdata/insuper3.jsonnet b/testdata/insuper3.jsonnet new file mode 100644 index 0000000..a3020c9 --- /dev/null +++ b/testdata/insuper3.jsonnet @@ -0,0 +1 @@ +{ "false": "x" in super } diff --git a/testdata/insuper4.golden b/testdata/insuper4.golden new file mode 100644 index 0000000..d644ada --- /dev/null +++ b/testdata/insuper4.golden @@ -0,0 +1 @@ +testdata/insuper4:1:2-13 Can't use super outside of an object. diff --git a/testdata/insuper4.jsonnet b/testdata/insuper4.jsonnet new file mode 100644 index 0000000..377694d --- /dev/null +++ b/testdata/insuper4.jsonnet @@ -0,0 +1 @@ +"x" in super diff --git a/testdata/insuper5.golden b/testdata/insuper5.golden new file mode 100644 index 0000000..e9a2e21 --- /dev/null +++ b/testdata/insuper5.golden @@ -0,0 +1,6 @@ +{ + "x": 42, + "y": { + "false": false + } +} diff --git a/testdata/insuper5.jsonnet b/testdata/insuper5.jsonnet new file mode 100644 index 0000000..506899b --- /dev/null +++ b/testdata/insuper5.jsonnet @@ -0,0 +1 @@ +{ x: 42 } { y: { "false": "x" in super } } diff --git a/testdata/insuper6.golden b/testdata/insuper6.golden new file mode 100644 index 0000000..8e21f48 --- /dev/null +++ b/testdata/insuper6.golden @@ -0,0 +1 @@ +testdata/insuper6:1:10-20 Unknown variable: undeclared diff --git a/testdata/insuper6.jsonnet b/testdata/insuper6.jsonnet new file mode 100644 index 0000000..f2c98d8 --- /dev/null +++ b/testdata/insuper6.jsonnet @@ -0,0 +1 @@ +{ assert undeclared in super } diff --git a/testdata/insuper7.golden b/testdata/insuper7.golden new file mode 100644 index 0000000..b5ffc7c --- /dev/null +++ b/testdata/insuper7.golden @@ -0,0 +1 @@ +"object" diff --git a/testdata/insuper7.jsonnet b/testdata/insuper7.jsonnet new file mode 100644 index 0000000..2468d28 --- /dev/null +++ b/testdata/insuper7.jsonnet @@ -0,0 +1 @@ +std.type({ assert false } { x: "x" in super }) diff --git a/testdata/supersugar.golden b/testdata/supersugar.golden new file mode 100644 index 0000000..c88acd4 --- /dev/null +++ b/testdata/supersugar.golden @@ -0,0 +1,6 @@ +{ + "x": { + "a": 1, + "b": 2 + } +} diff --git a/testdata/supersugar.jsonnet b/testdata/supersugar.jsonnet new file mode 100644 index 0000000..7140d7f --- /dev/null +++ b/testdata/supersugar.jsonnet @@ -0,0 +1 @@ +{ x: {a: 1} } { x +: {b: 2} } diff --git a/testdata/supersugar2.golden b/testdata/supersugar2.golden new file mode 100644 index 0000000..bb0e46e --- /dev/null +++ b/testdata/supersugar2.golden @@ -0,0 +1,3 @@ +{ + "x": 1 +} diff --git a/testdata/supersugar2.jsonnet b/testdata/supersugar2.jsonnet new file mode 100644 index 0000000..22d2009 --- /dev/null +++ b/testdata/supersugar2.jsonnet @@ -0,0 +1 @@ +{ x :+ 1 } diff --git a/testdata/supersugar3.golden b/testdata/supersugar3.golden new file mode 100644 index 0000000..c522f1b --- /dev/null +++ b/testdata/supersugar3.golden @@ -0,0 +1,3 @@ +{ + "x": true +} diff --git a/testdata/supersugar3.jsonnet b/testdata/supersugar3.jsonnet new file mode 100644 index 0000000..66a47f5 --- /dev/null +++ b/testdata/supersugar3.jsonnet @@ -0,0 +1 @@ +{ assert self.x } { x +: true } diff --git a/testdata/supersugar4.golden b/testdata/supersugar4.golden new file mode 100644 index 0000000..54dc1b9 --- /dev/null +++ b/testdata/supersugar4.golden @@ -0,0 +1,4 @@ +{ + "x": true, + "y": 43 +} diff --git a/testdata/supersugar4.jsonnet b/testdata/supersugar4.jsonnet new file mode 100644 index 0000000..fc93667 --- /dev/null +++ b/testdata/supersugar4.jsonnet @@ -0,0 +1 @@ +{ assert self.x, y: 42 } { x: true, y +: 1 } diff --git a/testdata/supersugar5.golden b/testdata/supersugar5.golden new file mode 100644 index 0000000..d81cc07 --- /dev/null +++ b/testdata/supersugar5.golden @@ -0,0 +1 @@ +42 diff --git a/testdata/supersugar5.jsonnet b/testdata/supersugar5.jsonnet new file mode 100644 index 0000000..d64f7d9 --- /dev/null +++ b/testdata/supersugar5.jsonnet @@ -0,0 +1 @@ +({ } { x +: function(x) x }).x(42) diff --git a/testdata/supersugar6.golden b/testdata/supersugar6.golden new file mode 100644 index 0000000..69e90cc --- /dev/null +++ b/testdata/supersugar6.golden @@ -0,0 +1,3 @@ +{ + "x": "hello, world" +} diff --git a/testdata/supersugar6.jsonnet b/testdata/supersugar6.jsonnet new file mode 100644 index 0000000..2baa56d --- /dev/null +++ b/testdata/supersugar6.jsonnet @@ -0,0 +1 @@ +{ x: "hello, " } { x +: "world" } diff --git a/testdata/supersugar7.golden b/testdata/supersugar7.golden new file mode 100644 index 0000000..5ccb7ab --- /dev/null +++ b/testdata/supersugar7.golden @@ -0,0 +1,10 @@ +{ + "x": [ + 1, + 2, + 3, + 4, + 5, + 6 + ] +} diff --git a/testdata/supersugar7.jsonnet b/testdata/supersugar7.jsonnet new file mode 100644 index 0000000..e086fc0 --- /dev/null +++ b/testdata/supersugar7.jsonnet @@ -0,0 +1 @@ +{ x: [1, 2, 3] } { x +: [4, 5, 6] } diff --git a/testdata/supersugar8.golden b/testdata/supersugar8.golden new file mode 100644 index 0000000..a866347 --- /dev/null +++ b/testdata/supersugar8.golden @@ -0,0 +1 @@ +RUNTIME ERROR: Object assertion failed. diff --git a/testdata/supersugar8.jsonnet b/testdata/supersugar8.jsonnet new file mode 100644 index 0000000..5aaf485 --- /dev/null +++ b/testdata/supersugar8.jsonnet @@ -0,0 +1 @@ +{ assert self.x } { x +: false } diff --git a/testdata/supersugar9.golden b/testdata/supersugar9.golden new file mode 100644 index 0000000..b5ffc7c --- /dev/null +++ b/testdata/supersugar9.golden @@ -0,0 +1 @@ +"object" diff --git a/testdata/supersugar9.jsonnet b/testdata/supersugar9.jsonnet new file mode 100644 index 0000000..bd9fc6a --- /dev/null +++ b/testdata/supersugar9.jsonnet @@ -0,0 +1,2 @@ +local infloop() = infloop(); +std.type({ assert infloop, x: 1 } { x +: 1 }) diff --git a/thunks.go b/thunks.go index b62b183..b20734d 100644 --- a/thunks.go +++ b/thunks.go @@ -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 diff --git a/value.go b/value.go index 2a2a7ec..b139b83 100644 --- a/value.go +++ b/value.go @@ -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) } }