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 { type DesugaredObjectField struct {
Hide ObjectFieldHide Hide ObjectFieldHide
Name Node Name Node
Body Node Body Node
PlusSuper bool
} }
type DesugaredObjectFields []DesugaredObjectField type DesugaredObjectFields []DesugaredObjectField

View File

@ -219,7 +219,7 @@ func builtinLength(e *evaluator, xp potentialValue) (value, error) {
var num int var num int
switch x := x.(type) { switch x := x.(type) {
case valueObject: case valueObject:
num = len(objectFields(x, true)) num = len(objectFields(x, withoutHidden))
case *valueArray: case *valueArray:
num = len(x.elements) num = len(x.elements)
case *valueString: case *valueString:
@ -511,7 +511,7 @@ func builtinObjectFieldsEx(e *evaluator, objp potentialValue, includeHiddenP pot
if err != nil { if err != nil {
return nil, err return nil, err
} }
fields := objectFields(obj, !includeHidden.value) fields := objectFields(obj, withHiddenFromBool(includeHidden.value))
sort.Strings(fields) sort.Strings(fields)
elems := []potentialValue{} elems := []potentialValue{}
for _, fieldname := range fields { for _, fieldname := range fields {
@ -533,12 +533,9 @@ func builtinObjectHasEx(e *evaluator, objp potentialValue, fnamep potentialValue
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, fieldname := range objectFields(obj, !includeHidden.value) { h := withHiddenFromBool(includeHidden.value)
if fieldname == string(fname.value) { fieldp := tryObjectIndex(objectBinding(obj), string(fname.value), h)
return makeValueBoolean(true), nil return makeValueBoolean(fieldp != nil), nil
}
}
return makeValueBoolean(false), nil
} }
func builtinPow(e *evaluator, basep potentialValue, expp potentialValue) (value, error) { 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.BopPercent: "mod",
ast.BopManifestEqual: "equals", ast.BopManifestEqual: "equals",
ast.BopManifestUnequal: "notEquals", // Special case ast.BopManifestUnequal: "notEquals", // Special case
ast.BopIn: "objectHasAll",
} }
var bopBuiltins = []*BinaryBuiltin{ 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 return nil
} }
@ -307,7 +294,7 @@ func buildDesugaredObject(nodeBase ast.NodeBase, fields ast.ObjectFields) *ast.D
if field.Kind == ast.ObjectAssert { if field.Kind == ast.ObjectAssert {
newAsserts = append(newAsserts, field.Expr2) newAsserts = append(newAsserts, field.Expr2)
} else if field.Kind == ast.ObjectFieldExpr { } 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 { } else {
panic(fmt.Sprintf("INTERNAL ERROR: field should have been desugared: %s", field.Kind)) 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, Op: ast.UopNot,
Expr: buildStdCall(desugaredBop[ast.BopManifestEqual], node.Left, node.Right), Expr: buildStdCall(desugaredBop[ast.BopManifestEqual], node.Left, node.Right),
} }
} else if node.Op == ast.BopIn {
*astPtr = buildStdCall(funcname, node.Right, node.Left)
} else { } else {
*astPtr = buildStdCall(funcname, node.Left, node.Right) *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 { if _, ok := fields[fieldName]; ok {
return nil, e.Error(duplicateFieldNameErrMsg(fieldName)) 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 var asserts []unboundField
for _, assert := range ast.Asserts { for _, assert := range ast.Asserts {
@ -422,7 +426,19 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
if err != nil { if err != nil {
return nil, err 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: case *ast.Function:
return &valueFunction{ return &valueFunction{
@ -567,9 +583,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
buf.WriteString("null") buf.WriteString("null")
case valueObject: case valueObject:
// TODO(dcunnin): Run invariants (object-level assertions). fieldNames := objectFields(v, withoutHidden)
fieldNames := objectFields(v, true)
sort.Strings(fieldNames) sort.Strings(fieldNames)
err := checkAssertions(e, v) err := checkAssertions(e, v)

View File

@ -19,7 +19,9 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"runtime/pprof"
"github.com/google/go-jsonnet" "github.com/google/go-jsonnet"
) )
@ -29,6 +31,17 @@ func usage() {
} }
func main() { 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 // TODO(sbarzowski) Be consistent about error codes with C++ maybe
vm := jsonnet.MakeVM() vm := jsonnet.MakeVM()
if len(os.Args) != 2 { 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 //nothing to do here
case *ast.ImportStr: case *ast.ImportStr:
//nothing to do here //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: case *ast.SuperIndex:
if !inObject { if !inObject {
return parser.MakeStaticError("Can't use super outside of an object.", *a.Loc()) 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 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 return rv
} }
@ -78,6 +78,10 @@ func makeCallThunk(ec evalCallable, args callArguments) potentialValue {
return makeCachedThunk(&callThunk{function: ec, args: args}) 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) { func (th *callThunk) getValue(i *interpreter, trace *TraceElement) (value, error) {
evaluator := makeEvaluator(i, trace) evaluator := makeEvaluator(i, trace)
err := checkArguments(evaluator, th.args, th.function.Parameters()) err := checkArguments(evaluator, th.args, th.function.Parameters())
@ -134,7 +138,7 @@ type codeUnboundField struct {
body ast.Node 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?) // TODO(sbarzowski) better object names (perhaps include a field name too?)
return makeThunk("object_field", makeEnvironment(origBindings, sb), f.body) return makeThunk("object_field", makeEnvironment(origBindings, sb), f.body)
} }
@ -146,7 +150,7 @@ type bindingsUnboundField struct {
bindings bindingFrame 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 var upValues bindingFrame
upValues = make(bindingFrame) upValues = make(bindingFrame)
for variable, pvalue := range origBindings { for variable, pvalue := range origBindings {
@ -155,7 +159,20 @@ func (f *bindingsUnboundField) bindToObject(sb selfBinding, origBindings binding
for variable, pvalue := range f.bindings { for variable, pvalue := range f.bindings {
upValues[variable] = pvalue 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 // 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 // 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. // 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") var errNoErrorInObjectInvariants = errors.New("No error - assertions passed")
@ -377,7 +400,7 @@ func checkAssertionsHelper(e *evaluator, obj valueObject, curr valueObject, supe
return nil return nil
case *valueSimpleObject: case *valueSimpleObject:
for _, assert := range curr.asserts { 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 { if err != nil {
return err return err
} }
@ -400,7 +423,7 @@ func checkAssertions(e *evaluator, obj valueObject) error {
} }
func (o *valueSimpleObject) index(e *evaluator, field string) (value, 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 { 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. // unboundField is a field that doesn't know yet in which object it is.
type unboundField interface { 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). // 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) { 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 { 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) { func objectIndex(e *evaluator, sb selfBinding, fieldName string) (value, error) {
err := checkAssertions(e, sb.self) err := checkAssertions(e, sb.self)
if err != nil { if err != nil {
return nil, err return nil, err
} }
field, upValues, foundAt := findField(sb.self, sb.superDepth, fieldName) objp := tryObjectIndex(sb, fieldName, withHidden)
if field == nil { if objp == nil {
return nil, e.Error(fmt.Sprintf("Field does not exist: %s", fieldName)) 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} 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 type fieldHideMap map[string]ast.ObjectFieldHide
@ -544,10 +570,10 @@ func objectFieldsVisibility(obj valueObject) fieldHideMap {
return r return r
} }
func objectFields(obj valueObject, manifesting bool) []string { func objectFields(obj valueObject, h Hidden) []string {
var r []string var r []string
for fieldName, hide := range objectFieldsVisibility(obj) { for fieldName, hide := range objectFieldsVisibility(obj) {
if !manifesting || hide != ast.ObjectFieldHidden { if h == withHidden || hide != ast.ObjectFieldHidden {
r = append(r, fieldName) r = append(r, fieldName)
} }
} }