Implement graph generation

This change lowers the information collected about resource allocations
and dependencies into the MuGL graph representation.

As part of this, we've moved pkg/compiler/eval out into its own top-level
package, pkg/eval, and split up its innards into a smaller sub-package,
pkg/eval/rt, that contains the Object and Pointer abstractions.  This
permits the graph generation logic to use it without introducing cycles.
This commit is contained in:
joeduffy 2017-02-01 08:08:21 -08:00
parent 24ea62bc78
commit 5594d23f84
14 changed files with 340 additions and 205 deletions

View file

@ -11,9 +11,9 @@ import (
"github.com/marapongo/mu/pkg/compiler/binder"
"github.com/marapongo/mu/pkg/compiler/core"
"github.com/marapongo/mu/pkg/compiler/errors"
"github.com/marapongo/mu/pkg/compiler/eval"
"github.com/marapongo/mu/pkg/compiler/metadata"
"github.com/marapongo/mu/pkg/diag"
"github.com/marapongo/mu/pkg/eval"
"github.com/marapongo/mu/pkg/graph"
"github.com/marapongo/mu/pkg/graph/graphgen"
"github.com/marapongo/mu/pkg/pack"

View file

@ -1,104 +0,0 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package eval
import (
"fmt"
"github.com/golang/glog"
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/compiler/types"
"github.com/marapongo/mu/pkg/util/contract"
)
// Allocator is a factory for creating objects.
type Allocator struct {
hooks InterpreterHooks // an optional set of allocation lifetime callback hooks.
}
// NewAllocator allocates a fresh allocator instance.
func NewAllocator(hooks InterpreterHooks) *Allocator {
return &Allocator{hooks: hooks}
}
// newObject is used internally for all allocator-driven object allocation, so that hooks can be run appropriately.
func (a *Allocator) newObject(t symbols.Type, v interface{}, props Properties) *Object {
o := NewObject(t, nil, props)
if a.hooks != nil {
a.hooks.OnNewObject(o)
}
return o
}
// New creates a new empty object of the given type.
func (a *Allocator) New(t symbols.Type) *Object {
return a.newObject(t, nil, nil)
}
// NewPrimitive creates a new primitive object with the given primitive type.
func (a *Allocator) NewPrimitive(t symbols.Type, v interface{}) *Object {
if glog.V(9) {
glog.V(9).Infof("New primitive object: t=%v; v=%v", t, v)
}
return a.newObject(t, v, nil)
}
// NewBool creates a new primitive number object.
func (a *Allocator) NewBool(v bool) *Object {
return a.NewPrimitive(types.Bool, v)
}
// NewNumber creates a new primitive number object.
func (a *Allocator) NewNumber(v float64) *Object {
return a.NewPrimitive(types.Number, v)
}
// NewString creates a new primitive number object.
func (a *Allocator) NewString(v string) *Object {
return a.NewPrimitive(types.String, v)
}
// NewFunction creates a new function object that can be invoked, with the given symbol.
func (a *Allocator) NewFunction(fnc symbols.Function, this *Object) *Object {
stub := funcStub{Func: fnc, This: this}
return a.newObject(fnc.FuncType(), stub, nil)
}
// funcStub is a stub that captures a symbol plus an optional instance 'this' object.
type funcStub struct {
Func symbols.Function
This *Object
}
// NewPointerObject allocates a new pointer-like object that wraps the given reference.
func (a *Allocator) NewPointer(t symbols.Type, ptr *Pointer) *Object {
contract.Require(ptr != nil, "ptr")
ptrt := symbols.NewPointerType(t)
return a.NewPrimitive(ptrt, ptr)
}
// NewError creates a new exception with the given message.
func (a *Allocator) NewError(message string, args ...interface{}) *Object {
// TODO: capture a stack trace.
return a.NewPrimitive(types.Error, fmt.Sprintf(message, args...))
}
// NewConstant returns a new object with the right type and value, based on some constant data.
func (a *Allocator) NewConstant(v interface{}) *Object {
if v == nil {
return a.NewPrimitive(types.Null, nil)
}
switch data := v.(type) {
case bool:
return a.NewPrimitive(types.Bool, data)
case string:
return a.NewPrimitive(types.String, data)
case float64:
return a.NewPrimitive(types.Number, data)
default:
// TODO: we could support more here (essentially, anything that is JSON serializable).
contract.Failf("Unrecognized constant data literal: %v", data)
return nil
}
}

View file

@ -122,6 +122,7 @@ func (node *ClassProperty) Token() tokens.Token {
func (node *ClassProperty) Tree() diag.Diagable { return node.Node }
func (node *ClassProperty) classMember() {}
func (node *ClassProperty) Optional() bool { return node.Node.Optional != nil && *node.Node.Optional }
func (node *ClassProperty) Readonly() bool { return node.Node.Readonly != nil && *node.Node.Readonly }
func (node *ClassProperty) Static() bool { return node.Node.Static != nil && *node.Node.Static }
func (node *ClassProperty) Default() *interface{} { return node.Node.Default }
func (node *ClassProperty) Type() Type { return node.Ty }

View file

@ -124,6 +124,7 @@ func (node *ModuleProperty) Token() tokens.Token {
func (node *ModuleProperty) Tree() diag.Diagable { return node.Node }
func (node *ModuleProperty) moduleMember() {}
func (node *ModuleProperty) MemberNode() ast.ModuleMember { return node.Node }
func (node *ModuleProperty) Readonly() bool { return node.Node.Readonly != nil && *node.Node.Readonly }
func (node *ModuleProperty) Type() Type { return node.Ty }
func (node *ModuleProperty) VarNode() ast.Variable { return node.Node }
func (node *ModuleProperty) String() string { return string(node.Name()) }

View file

@ -31,6 +31,7 @@ func (node *LocalVariable) symbol() {}
func (node *LocalVariable) Name() tokens.Name { return node.Nm }
func (node *LocalVariable) Token() tokens.Token { return tokens.Token(node.Name()) }
func (node *LocalVariable) Tree() diag.Diagable { return node.Node }
func (node *LocalVariable) Readonly() bool { return node.Node.Readonly != nil && *node.Node.Readonly }
func (node *LocalVariable) Type() Type { return node.Ty }
func (node *LocalVariable) VarNode() ast.Variable { return node.Node }
func (node *LocalVariable) String() string { return string(node.Name()) }

89
pkg/eval/alloc.go Normal file
View file

@ -0,0 +1,89 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package eval
import (
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/compiler/types"
"github.com/marapongo/mu/pkg/eval/rt"
)
// Allocator is a factory for creating objects.
type Allocator struct {
hooks InterpreterHooks // an optional set of allocation lifetime callback hooks.
}
// NewAllocator allocates a fresh allocator instance.
func NewAllocator(hooks InterpreterHooks) *Allocator {
return &Allocator{hooks: hooks}
}
// onNewObject is invoked for each allocation and emits an appropriate event.
func (a *Allocator) onNewObject(o *rt.Object) {
if a.hooks != nil {
a.hooks.OnNewObject(o)
}
}
// New creates a new empty object of the given type.
func (a *Allocator) New(t symbols.Type) *rt.Object {
obj := rt.NewObject(t, nil, nil)
a.onNewObject(obj)
return obj
}
// NewPrimitive creates a new primitive object with the given primitive type.
func (a *Allocator) NewPrimitive(t symbols.Type, v interface{}) *rt.Object {
obj := rt.NewObject(t, v, nil)
a.onNewObject(obj)
return obj
}
// NewBool creates a new primitive number object.
func (a *Allocator) NewBool(v bool) *rt.Object {
obj := rt.NewPrimitiveObject(types.Bool, v)
a.onNewObject(obj)
return obj
}
// NewNumber creates a new primitive number object.
func (a *Allocator) NewNumber(v float64) *rt.Object {
obj := rt.NewPrimitiveObject(types.Number, v)
a.onNewObject(obj)
return obj
}
// NewString creates a new primitive number object.
func (a *Allocator) NewString(v string) *rt.Object {
obj := rt.NewPrimitiveObject(types.String, v)
a.onNewObject(obj)
return obj
}
// NewFunction creates a new function object that can be invoked, with the given symbol.
func (a *Allocator) NewFunction(fnc symbols.Function, this *rt.Object) *rt.Object {
obj := rt.NewFunctionObject(fnc, this)
a.onNewObject(obj)
return obj
}
// NewPointerObject allocates a new pointer-like object that wraps the given reference.
func (a *Allocator) NewPointer(t symbols.Type, ptr *rt.Pointer) *rt.Object {
obj := rt.NewPointerObject(t, ptr)
a.onNewObject(obj)
return obj
}
// NewError creates a new exception with the given message.
func (a *Allocator) NewError(message string, args ...interface{}) *rt.Object {
obj := rt.NewErrorObject(message, args...)
a.onNewObject(obj)
return obj
}
// NewConstant returns a new object with the right type and value, based on some constant data.
func (a *Allocator) NewConstant(v interface{}) *rt.Object {
obj := rt.NewConstantObject(v)
a.onNewObject(obj)
return obj
}

View file

@ -15,6 +15,7 @@ import (
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/compiler/types"
"github.com/marapongo/mu/pkg/diag"
"github.com/marapongo/mu/pkg/eval/rt"
"github.com/marapongo/mu/pkg/graph"
"github.com/marapongo/mu/pkg/tokens"
"github.com/marapongo/mu/pkg/util/contract"
@ -31,19 +32,19 @@ type Interpreter interface {
// EvaluateModule performs evaluation on the given module's entrypoint function.
EvaluateModule(mod *symbols.Module, args core.Args) graph.Graph
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
EvaluateFunction(fnc symbols.Function, this *Object, args core.Args) graph.Graph
EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args) graph.Graph
}
// InterpreterHooks is a set of callbacks that can be used to hook into interesting interpreter events.
type InterpreterHooks interface {
OnNewObject(o *Object) // invoked whenever an object is created.
OnAssignProperty(o *Object, prop string, old, nw *Object) // invoked whenever a property is (re)assigned.
OnEnterPackage(pkg *symbols.Package) // invoked whenever we enter a new package.
OnLeavePackage(pkg *symbols.Package) // invoked whenever we leave a package.
OnEnterModule(mod *symbols.Module) // invoked whenever we enter a new module.
OnLeaveModule(mod *symbols.Module) // invoked whenever we leave a module.
OnEnterFunction(fnc symbols.Function) // invoked whenever we enter a new function.
OnLeaveFunction(fnc symbols.Function) // invoked whenever we leave a function.
OnNewObject(o *rt.Object) // invoked whenever an object is created.
OnAssignProperty(o *rt.Object, prop string, old, nw *rt.Object) // invoked whenever a property is (re)assigned.
OnEnterPackage(pkg *symbols.Package) // invoked whenever we enter a new package.
OnLeavePackage(pkg *symbols.Package) // invoked whenever we leave a package.
OnEnterModule(mod *symbols.Module) // invoked whenever we enter a new module.
OnLeaveModule(mod *symbols.Module) // invoked whenever we leave a module.
OnEnterFunction(fnc symbols.Function) // invoked whenever we enter a new function.
OnLeaveFunction(fnc symbols.Function) // invoked whenever we leave a function.
}
// New creates an interpreter that can be used to evaluate MuPackages.
@ -72,7 +73,7 @@ type evaluator struct {
classinits classinitMap // a map of which classes have been initialized already.
}
type globalMap map[symbols.Variable]*Pointer
type globalMap map[symbols.Variable]*rt.Pointer
type modinitMap map[*symbols.Module]bool
type classinitMap map[*symbols.Class]bool
@ -134,7 +135,7 @@ func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) graph.Gr
}
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *Object, args core.Args) graph.Graph {
func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args) graph.Graph {
glog.Infof("Evaluating function '%v'", fnc.Token())
if e.hooks != nil {
e.hooks.OnEnterFunction(fnc)
@ -158,8 +159,8 @@ func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *Object, args co
contract.Failf("Unrecognized function evaluation type: %v", reflect.TypeOf(f))
}
// First, validate any arguments, and turn them into real runtime *Objects.
var argos []*Object
// First, validate any arguments, and turn them into real runtime *rt.Objects.
var argos []*rt.Object
params := fnc.FuncNode().GetParameters()
if params == nil {
if len(args) != 0 {
@ -312,7 +313,7 @@ func (e *evaluator) popScope() {
// Functions
func (e *evaluator) evalCall(fnc symbols.Function, this *Object, args ...*Object) (*Object, *Unwind) {
func (e *evaluator) evalCall(fnc symbols.Function, this *rt.Object, args ...*rt.Object) (*rt.Object, *Unwind) {
glog.V(7).Infof("Evaluating call to fnc %v; this=%v args=%v", fnc, this != nil, len(args))
// Save the prior func, set the new one, and restore upon exit.
@ -337,10 +338,10 @@ func (e *evaluator) evalCall(fnc symbols.Function, this *Object, args ...*Object
contract.Assertf(!f.Static(), "Static methods don't have 'this' arguments, but we got a non-nil one")
contract.Assertf(types.CanConvert(this.Type(), f.Parent), "'this' argument was of the wrong type")
e.ctx.Scope.Register(f.Parent.This)
e.locals.InitValueAddr(f.Parent.This, &Pointer{obj: this, readonly: true})
e.locals.InitValueAddr(f.Parent.This, rt.NewPointer(this, true))
if f.Parent.Super != nil {
e.ctx.Scope.Register(f.Parent.Super)
e.locals.InitValueAddr(f.Parent.Super, &Pointer{obj: this, readonly: true})
e.locals.InitValueAddr(f.Parent.Super, rt.NewPointer(this, true))
}
default:
contract.Failf("Only class methods should have 'this' arguments, but we got a non-nil one")
@ -538,7 +539,7 @@ func (e *evaluator) evalLabeledStatement(node *ast.LabeledStatement) *Unwind {
}
func (e *evaluator) evalReturnStatement(node *ast.ReturnStatement) *Unwind {
var ret *Object
var ret *rt.Object
if node.Expression != nil {
var uw *Unwind
if ret, uw = e.evalExpression(*node.Expression); uw != nil {
@ -605,7 +606,7 @@ func (e *evaluator) evalExpressionStatement(node *ast.ExpressionStatement) *Unwi
// Expressions
func (e *evaluator) evalExpression(node ast.Expression) (*Object, *Unwind) {
func (e *evaluator) evalExpression(node ast.Expression) (*rt.Object, *Unwind) {
// Simply switch on the node type and dispatch to the specific function, returning the object and Unwind info.
switch n := node.(type) {
case *ast.NullLiteral:
@ -653,7 +654,7 @@ func (e *evaluator) evalExpression(node ast.Expression) (*Object, *Unwind) {
// evalLValueExpression evaluates an expression for use as an l-value; in particular, this loads the target as a
// pointer/reference object, rather than as an ordinary value, so that it can be used in an assignment. This is only
// valid on the subset of AST nodes that are legal l-values (very few of them, it turns out).
func (e *evaluator) evalLValueExpression(node ast.Expression) (*Object, *Unwind) {
func (e *evaluator) evalLValueExpression(node ast.Expression) (*rt.Object, *Unwind) {
switch n := node.(type) {
case *ast.LoadLocationExpression:
return e.evalLoadLocationExpressionFor(n, true)
@ -666,29 +667,29 @@ func (e *evaluator) evalLValueExpression(node ast.Expression) (*Object, *Unwind)
}
}
func (e *evaluator) evalNullLiteral(node *ast.NullLiteral) (*Object, *Unwind) {
func (e *evaluator) evalNullLiteral(node *ast.NullLiteral) (*rt.Object, *Unwind) {
return e.alloc.NewPrimitive(types.Null, nil), nil
}
func (e *evaluator) evalBoolLiteral(node *ast.BoolLiteral) (*Object, *Unwind) {
func (e *evaluator) evalBoolLiteral(node *ast.BoolLiteral) (*rt.Object, *Unwind) {
return e.alloc.NewPrimitive(types.Bool, node.Value), nil
}
func (e *evaluator) evalNumberLiteral(node *ast.NumberLiteral) (*Object, *Unwind) {
func (e *evaluator) evalNumberLiteral(node *ast.NumberLiteral) (*rt.Object, *Unwind) {
return e.alloc.NewPrimitive(types.Number, node.Value), nil
}
func (e *evaluator) evalStringLiteral(node *ast.StringLiteral) (*Object, *Unwind) {
func (e *evaluator) evalStringLiteral(node *ast.StringLiteral) (*rt.Object, *Unwind) {
return e.alloc.NewPrimitive(types.String, node.Value), nil
}
func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*Object, *Unwind) {
func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*rt.Object, *Unwind) {
// Fetch this expression type and assert that it's an array.
ty := e.ctx.RequireType(node).(*symbols.ArrayType)
// Now create the array data.
var sz *int
var arr []Value
var arr []rt.Value
// If there's a node size, ensure it's a number, and initialize the array.
if node.Size != nil {
@ -702,7 +703,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*Object, *Unwind)
// If the size is less than zero, raise a new error.
return nil, NewThrowUnwind(e.alloc.NewError("Invalid array size (must be >= 0)"))
}
arr = make([]Value, sz)
arr = make([]rt.Value, sz)
}
// If there are elements, place them into the array. This has two behaviors:
@ -711,7 +712,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*Object, *Unwind)
if node.Elements != nil {
if sz == nil {
// Right-size the array.
arr = make([]Value, 0, len(*node.Elements))
arr = make([]rt.Value, 0, len(*node.Elements))
} else if len(*node.Elements) > *sz {
// The element count exceeds the size; raise an error.
return nil, NewThrowUnwind(
@ -736,7 +737,7 @@ func (e *evaluator) evalArrayLiteral(node *ast.ArrayLiteral) (*Object, *Unwind)
return e.alloc.NewPrimitive(ty, arr), nil
}
func (e *evaluator) evalObjectLiteral(node *ast.ObjectLiteral) (*Object, *Unwind) {
func (e *evaluator) evalObjectLiteral(node *ast.ObjectLiteral) (*rt.Object, *Unwind) {
obj := e.alloc.New(e.ctx.Types[node])
if node.Properties != nil {
@ -754,13 +755,13 @@ func (e *evaluator) evalObjectLiteral(node *ast.ObjectLiteral) (*Object, *Unwind
return obj, nil
}
func (e *evaluator) evalLoadLocationExpression(node *ast.LoadLocationExpression) (*Object, *Unwind) {
func (e *evaluator) evalLoadLocationExpression(node *ast.LoadLocationExpression) (*rt.Object, *Unwind) {
return e.evalLoadLocationExpressionFor(node, false)
}
func (e *evaluator) evalLoadLocationExpressionFor(node *ast.LoadLocationExpression, lval bool) (*Object, *Unwind) {
func (e *evaluator) evalLoadLocationExpressionFor(node *ast.LoadLocationExpression, lval bool) (*rt.Object, *Unwind) {
// If there's a 'this', evaluate it.
var this *Object
var this *rt.Object
if node.Object != nil {
var uw *Unwind
if this, uw = e.evalExpression(*node.Object); uw != nil {
@ -769,7 +770,7 @@ func (e *evaluator) evalLoadLocationExpressionFor(node *ast.LoadLocationExpressi
}
// Create a pointer to the target location.
var pv *Pointer
var pv *rt.Pointer
var ty symbols.Type
tok := node.Name.Tok
if this == nil && tok.Simple() {
@ -790,17 +791,14 @@ func (e *evaluator) evalLoadLocationExpressionFor(node *ast.LoadLocationExpressi
// Create a new readonly ref slot, pointing to the method, that will abandon if overwritten.
contract.Assert(this != nil)
// TODO[marapongo/mu#56]: consider permitting "dynamic" method overwriting.
pv = &Pointer{
obj: e.alloc.NewFunction(s, this),
readonly: true,
}
pv = rt.NewPointer(e.alloc.NewFunction(s, this), true)
ty = s.Type()
case *symbols.ModuleProperty:
// Search the globals table and, if not present, allocate a new property.
contract.Assert(this == nil)
ref, has := e.globals[s]
if !has {
ref = &Pointer{}
ref = rt.NewPointer(nil, s.Readonly())
e.globals[s] = ref
}
pv = ref
@ -809,10 +807,7 @@ func (e *evaluator) evalLoadLocationExpressionFor(node *ast.LoadLocationExpressi
// Create a new readonly ref slot, pointing to the method, that will abandon if overwritten.
contract.Assert(this == nil)
// TODO[marapongo/mu#56]: consider permitting "dynamic" method overwriting.
pv = &Pointer{
obj: e.alloc.NewFunction(s, nil),
readonly: true,
}
pv = rt.NewPointer(e.alloc.NewFunction(s, nil), true)
ty = s.Type
default:
contract.Failf("Unexpected symbol token kind during load expression: %v", tok)
@ -830,18 +825,18 @@ func (e *evaluator) evalLoadLocationExpressionFor(node *ast.LoadLocationExpressi
return pv.Obj(), nil
}
func (e *evaluator) evalLoadDynamicExpression(node *ast.LoadDynamicExpression) (*Object, *Unwind) {
func (e *evaluator) evalLoadDynamicExpression(node *ast.LoadDynamicExpression) (*rt.Object, *Unwind) {
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node))
return nil, nil
}
func (e *evaluator) evalNewExpression(node *ast.NewExpression) (*Object, *Unwind) {
func (e *evaluator) evalNewExpression(node *ast.NewExpression) (*rt.Object, *Unwind) {
// TODO: create a new object and invoke its constructor.
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node))
return nil, nil
}
func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpression) (*Object, *Unwind) {
func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpression) (*rt.Object, *Unwind) {
// Evaluate the function that we are meant to invoke.
fncobj, uw := e.evalExpression(node.Function)
if uw != nil {
@ -849,7 +844,7 @@ func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpress
}
// Ensure that this actually led to a function; this is guaranteed by the binder.
var fnc funcStub
var fnc rt.FuncStub
switch fncobj.Type().(type) {
case *symbols.FunctionType:
fnc = fncobj.FunctionValue()
@ -859,7 +854,7 @@ func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpress
}
// Now evaluate the arguments to the function, in order.
var args []*Object
var args []*rt.Object
if node.Arguments != nil {
for _, arg := range *node.Arguments {
argobj, uw := e.evalExpression(arg)
@ -874,21 +869,21 @@ func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpress
return e.evalCall(fnc.Func, fnc.This, args...)
}
func (e *evaluator) evalLambdaExpression(node *ast.LambdaExpression) (*Object, *Unwind) {
func (e *evaluator) evalLambdaExpression(node *ast.LambdaExpression) (*rt.Object, *Unwind) {
// TODO: create the lambda object that can be invoked at runtime.
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node))
return nil, nil
}
func (e *evaluator) evalUnaryOperatorExpression(node *ast.UnaryOperatorExpression) (*Object, *Unwind) {
func (e *evaluator) evalUnaryOperatorExpression(node *ast.UnaryOperatorExpression) (*rt.Object, *Unwind) {
return e.evalUnaryOperatorExpressionFor(node, false)
}
func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpression, lval bool) (*Object, *Unwind) {
func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpression, lval bool) (*rt.Object, *Unwind) {
contract.Assertf(!lval || node.Operator == ast.OpDereference, "Only dereference unary ops support l-values")
// Evaluate the operand and prepare to use it.
var opand *Object
var opand *rt.Object
if node.Operator == ast.OpAddressof ||
node.Operator == ast.OpPlusPlus || node.Operator == ast.OpMinusMinus {
// These operators require an l-value; so we bind the expression a bit differently.
@ -960,9 +955,9 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres
}
}
func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpression) (*Object, *Unwind) {
func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpression) (*rt.Object, *Unwind) {
// Evaluate the operands and prepare to use them. First left, then right.
var lhs *Object
var lhs *rt.Object
if isBinaryAssignmentOperator(node.Operator) {
var uw *Unwind
if lhs, uw = e.evalLValueExpression(node.Left); uw != nil {
@ -1153,7 +1148,7 @@ func isBinaryAssignmentOperator(op ast.BinaryOperator) bool {
}
}
func (e *evaluator) evalBinaryOperatorEquals(lhs *Object, rhs *Object) bool {
func (e *evaluator) evalBinaryOperatorEquals(lhs *rt.Object, rhs *rt.Object) bool {
if lhs == rhs {
return true
}
@ -1172,22 +1167,22 @@ func (e *evaluator) evalBinaryOperatorEquals(lhs *Object, rhs *Object) bool {
return false
}
func (e *evaluator) evalCastExpression(node *ast.CastExpression) (*Object, *Unwind) {
func (e *evaluator) evalCastExpression(node *ast.CastExpression) (*rt.Object, *Unwind) {
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node))
return nil, nil
}
func (e *evaluator) evalIsInstExpression(node *ast.IsInstExpression) (*Object, *Unwind) {
func (e *evaluator) evalIsInstExpression(node *ast.IsInstExpression) (*rt.Object, *Unwind) {
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node))
return nil, nil
}
func (e *evaluator) evalTypeOfExpression(node *ast.TypeOfExpression) (*Object, *Unwind) {
func (e *evaluator) evalTypeOfExpression(node *ast.TypeOfExpression) (*rt.Object, *Unwind) {
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node))
return nil, nil
}
func (e *evaluator) evalConditionalExpression(node *ast.ConditionalExpression) (*Object, *Unwind) {
func (e *evaluator) evalConditionalExpression(node *ast.ConditionalExpression) (*rt.Object, *Unwind) {
// Evaluate the branches explicitly based on the result of the condition node.
cond, uw := e.evalExpression(node.Condition)
if uw != nil {
@ -1199,9 +1194,9 @@ func (e *evaluator) evalConditionalExpression(node *ast.ConditionalExpression) (
return e.evalExpression(node.Alternate)
}
func (e *evaluator) evalSequenceExpression(node *ast.SequenceExpression) (*Object, *Unwind) {
func (e *evaluator) evalSequenceExpression(node *ast.SequenceExpression) (*rt.Object, *Unwind) {
// Simply walk through the sequence and return the last object.
var obj *Object
var obj *rt.Object
contract.Assert(len(node.Expressions) > 0)
for _, expr := range node.Expressions {
var uw *Unwind

View file

@ -1,6 +1,6 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package eval
package rt
import (
"fmt"
@ -62,9 +62,9 @@ func (o *Object) StringValue() string {
}
// FunctionValue asserts that the target is a reference and returns its value.
func (o *Object) FunctionValue() funcStub {
func (o *Object) FunctionValue() FuncStub {
contract.Assertf(o.value != nil, "Expected Function object to carry a Value; got nil")
r, ok := o.value.(funcStub)
r, ok := o.value.(FuncStub)
contract.Assertf(ok, "Expected Function object's Value to be a Function")
return r
}
@ -80,12 +80,35 @@ func (o *Object) PointerValue() *Pointer {
// GetPropertyAddr returns the reference to an object's property, lazily initializing if 'init' is true, or
// returning nil otherwise.
func (o *Object) GetPropertyAddr(nm tokens.Name, init bool) *Pointer {
ref, has := o.properties[nm]
if !has {
ref = &Pointer{}
o.properties[nm] = ref
ptr, hasprop := o.properties[nm]
if !hasprop {
// Look up the property definition (if any) in the members list, to seed a default value.
var obj *Object
var readonly bool
if class, isclass := o.t.(*symbols.Class); isclass {
if member, hasmember := class.Members[tokens.ClassMemberName(nm)]; hasmember {
switch m := member.(type) {
case *symbols.ClassProperty:
if m.Default() != nil {
obj = NewConstantObject(*m.Default())
}
readonly = m.Readonly()
case *symbols.ClassMethod:
if m.Static() {
obj = NewFunctionObject(m, nil)
} else {
obj = NewFunctionObject(m, o)
}
readonly = true // TODO[marapongo/mu#56]: consider permitting JS-style overwriting of methods.
default:
contract.Failf("Unexpected member type: %v", member)
}
}
}
ptr = NewPointer(obj, readonly)
o.properties[nm] = ptr
}
return ref
return ptr
}
// String can be used to print the contents of an object; it tries to be smart about the display.
@ -134,3 +157,67 @@ func (o *Object) String() string {
return "obj{type=" + o.t.Token().String() + ",props={" + p + "}}"
}
}
// NewPrimitiveObject creates a new primitive object with the given primitive type.
func NewPrimitiveObject(t symbols.Type, v interface{}) *Object {
return NewObject(t, v, nil)
}
// NewBoolObject creates a new primitive number object.
func NewBoolObject(v bool) *Object {
return NewPrimitiveObject(types.Bool, v)
}
// NewNumber creates a new primitive number object.
func NewNumber(v float64) *Object {
return NewPrimitiveObject(types.Number, v)
}
// NewString creates a new primitive number object.
func NewString(v string) *Object {
return NewPrimitiveObject(types.String, v)
}
// NewFunctionObject creates a new function object that can be invoked, with the given symbol.
func NewFunctionObject(fnc symbols.Function, this *Object) *Object {
stub := FuncStub{Func: fnc, This: this}
return NewObject(fnc.FuncType(), stub, nil)
}
// NewPointerObject allocates a new pointer-like object that wraps the given reference.
func NewPointerObject(t symbols.Type, ptr *Pointer) *Object {
contract.Require(ptr != nil, "ptr")
ptrt := symbols.NewPointerType(t)
return NewPrimitiveObject(ptrt, ptr)
}
// NewErrorObject creates a new exception with the given message.
func NewErrorObject(message string, args ...interface{}) *Object {
// TODO: capture a stack trace.
return NewPrimitiveObject(types.Error, fmt.Sprintf(message, args...))
}
// NewConstantObject returns a new object with the right type and value, based on some constant data.
func NewConstantObject(v interface{}) *Object {
if v == nil {
return NewPrimitiveObject(types.Null, nil)
}
switch data := v.(type) {
case bool:
return NewPrimitiveObject(types.Bool, data)
case string:
return NewPrimitiveObject(types.String, data)
case float64:
return NewPrimitiveObject(types.Number, data)
default:
// TODO: we could support more here (essentially, anything that is JSON serializable).
contract.Failf("Unrecognized constant data literal: %v", data)
return nil
}
}
// FuncStub is a stub that captures a symbol plus an optional instance 'this' object.
type FuncStub struct {
Func symbols.Function
This *Object
}

View file

@ -1,6 +1,6 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package eval
package rt
import (
"fmt"
@ -16,6 +16,10 @@ type Pointer struct {
var _ fmt.Stringer = (*Pointer)(nil)
func NewPointer(obj *Object, readonly bool) *Pointer {
return &Pointer{obj: obj, readonly: readonly}
}
func (ptr *Pointer) Readonly() bool { return ptr.readonly }
func (ptr *Pointer) Obj() *Object { return ptr.obj }

View file

@ -6,6 +6,7 @@ import (
"github.com/marapongo/mu/pkg/compiler/binder"
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/compiler/types"
"github.com/marapongo/mu/pkg/eval/rt"
"github.com/marapongo/mu/pkg/util/contract"
)
@ -19,7 +20,7 @@ type localScope struct {
}
// valueMap maps local variables to their current known object value (if any).
type valueMap map[*symbols.LocalVariable]*Pointer
type valueMap map[*symbols.LocalVariable]*rt.Pointer
func newLocalScope(slot **localScope, frame bool, lex *binder.Scope) *localScope {
s := &localScope{
@ -45,7 +46,7 @@ func (s *localScope) Pop() {
}
// GetValue returns the object value for the given symbol.
func (s *localScope) GetValue(sym *symbols.LocalVariable) *Object {
func (s *localScope) GetValue(sym *symbols.LocalVariable) *rt.Object {
if ref := s.GetValueAddr(sym, false); ref != nil {
return ref.Obj()
}
@ -54,17 +55,17 @@ func (s *localScope) GetValue(sym *symbols.LocalVariable) *Object {
// GetValueAddr returns a reference to the object for the given symbol. If init is true, and the value doesn't
// exist, a new slot will be allocated. Otherwise, the return value is nil.
func (s *localScope) GetValueAddr(sym *symbols.LocalVariable, init bool) *Pointer {
func (s *localScope) GetValueAddr(sym *symbols.LocalVariable, init bool) *rt.Pointer {
return s.lookupValueAddr(sym, nil, init)
}
// InitValue registers a reference for a local variable, and asserts that none previously existed.
func (s *localScope) InitValueAddr(sym *symbols.LocalVariable, ref *Pointer) {
func (s *localScope) InitValueAddr(sym *symbols.LocalVariable, ref *rt.Pointer) {
s.lookupValueAddr(sym, ref, false)
}
// lookupValueAddr is used to lookup and initialize references using a single, shared routine.
func (s *localScope) lookupValueAddr(sym *symbols.LocalVariable, place *Pointer, init bool) *Pointer {
func (s *localScope) lookupValueAddr(sym *symbols.LocalVariable, place *rt.Pointer, init bool) *rt.Pointer {
// To get a value's reference, we must first find the position in the shadowed frames, so that its lifetime equals
// the actual local variable symbol's lifetime. This ensures that once that frame is popped, so too is any value
// associated with it; and similarly, that its value won't be popped until the frame containing the variable is.
@ -94,7 +95,7 @@ outer:
return place
}
if init {
ref := &Pointer{}
ref := rt.NewPointer(nil, sym.Readonly())
s.Values[sym] = ref
return ref
}
@ -102,7 +103,7 @@ outer:
}
// SetValue overwrites the current value, or adds a new entry, for the given symbol.
func (s *localScope) SetValue(sym *symbols.LocalVariable, obj *Object) {
func (s *localScope) SetValue(sym *symbols.LocalVariable, obj *rt.Object) {
contract.Assert(obj == nil || types.CanConvert(obj.Type(), sym.Type()))
ptr := s.GetValueAddr(sym, true)
ptr.Set(obj)

View file

@ -3,6 +3,7 @@
package eval
import (
"github.com/marapongo/mu/pkg/eval/rt"
"github.com/marapongo/mu/pkg/tokens"
"github.com/marapongo/mu/pkg/util/contract"
)
@ -11,8 +12,8 @@ import (
type Unwind struct {
kind unwindKind // the kind of the unwind.
label *tokens.Name // a label being sought (valid only on break/continue).
returned *Object // an object being returned (valid only on return).
thrown *Object // an exception object being thrown (valid only on throw).
returned *rt.Object // an object being returned (valid only on return).
thrown *rt.Object // an exception object being thrown (valid only on throw).
}
// unwindKind is the kind of unwind being performed.
@ -27,8 +28,8 @@ const (
func NewBreakUnwind(label *tokens.Name) *Unwind { return &Unwind{kind: breakUnwind, label: label} }
func NewContinueUnwind(label *tokens.Name) *Unwind { return &Unwind{kind: continueUnwind, label: label} }
func NewReturnUnwind(ret *Object) *Unwind { return &Unwind{kind: returnUnwind, returned: ret} }
func NewThrowUnwind(thrown *Object) *Unwind { return &Unwind{kind: throwUnwind, thrown: thrown} }
func NewReturnUnwind(ret *rt.Object) *Unwind { return &Unwind{kind: returnUnwind, returned: ret} }
func NewThrowUnwind(thrown *rt.Object) *Unwind { return &Unwind{kind: throwUnwind, thrown: thrown} }
func (uw *Unwind) Break() bool { return uw.kind == breakUnwind }
func (uw *Unwind) Continue() bool { return uw.kind == continueUnwind }
@ -40,12 +41,12 @@ func (uw *Unwind) Label() *tokens.Name {
return uw.label
}
func (uw *Unwind) Returned() *Object {
func (uw *Unwind) Returned() *rt.Object {
contract.Assert(uw.Return())
return uw.returned
}
func (uw *Unwind) Thrown() *Object {
func (uw *Unwind) Thrown() *rt.Object {
contract.Assert(uw.Throw())
return uw.thrown
}

View file

@ -5,10 +5,11 @@ package graphgen
import (
"github.com/marapongo/mu/pkg/compiler/core"
"github.com/marapongo/mu/pkg/compiler/eval"
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/compiler/types"
"github.com/marapongo/mu/pkg/compiler/types/predef"
"github.com/marapongo/mu/pkg/eval"
"github.com/marapongo/mu/pkg/eval/rt"
"github.com/marapongo/mu/pkg/graph"
"github.com/marapongo/mu/pkg/util/contract"
)
@ -33,20 +34,43 @@ type generator struct {
}
// objectSet is a set of object pointers; each entry has a ref-count to track how many occurrences it contains.
type objectSet map[*eval.Object]int
type objectSet map[*rt.Object]int
// dependsSet is a map of object pointers to the objectSet containing the set of objects each such object depends upon.
type dependsSet map[*eval.Object]objectSet
type dependsSet map[*rt.Object]objectSet
var _ Generator = (*generator)(nil)
// Graph takes the information recorded thus far and produces a new MuGL graph from it.
func (g *generator) Graph() graph.Graph {
return nil
// First create vertices for all objects.
verts := make(map[*rt.Object]*objectVertex)
for o := range g.res {
verts[o] = newObjectVertex(o)
}
// Now create edges connecting all vertices along dependency lines.
// TODO: detect and issue errors about cycles.
for o, deps := range g.res {
for dep := range deps {
verts[o].AddEdge(verts[dep])
}
}
// Finally, find all vertices that do not have any incoming edges, and consider them roots.
var roots []graph.Vertex
for _, vert := range verts {
if len(vert.Ins()) == 0 {
roots = append(roots, vert)
}
}
contract.Assert(len(roots) > 0) // should have been detected with the DAG algorithm above.
return newObjectGraph(roots)
}
// OnNewObject is called whenever a new object has been allocated.
func (g *generator) OnNewObject(o *eval.Object) {
func (g *generator) OnNewObject(o *rt.Object) {
contract.Assert(o != nil)
// We only care about subclasses of the mu.Resource type; all others are "just" data/computation.
if types.HasBaseName(o.Type(), predef.MuResourceClass) {
@ -59,7 +83,7 @@ func (g *generator) OnNewObject(o *eval.Object) {
}
// OnAssignProperty is called whenever a property has been (re)assigned; it receives both the old and new values.
func (g *generator) OnAssignProperty(o *eval.Object, prop string, old *eval.Object, nw *eval.Object) {
func (g *generator) OnAssignProperty(o *rt.Object, prop string, old *rt.Object, nw *rt.Object) {
contract.Assert(o != nil)
// If the target of the assignment is a resource, we need to track dependencies.
@ -72,12 +96,13 @@ func (g *generator) OnAssignProperty(o *eval.Object, prop string, old *eval.Obje
c, has := deps[old]
contract.Assertf(has, "Expected old resource property to exist in dependency map")
contract.Assertf(c > 0, "Expected old resource property ref-count to be > 0 in dependency map")
deps[old]--
deps[old] = c - 1
}
// If the new object is a resource, add a ref-count (or a whole new entry if needed).
if nw != nil && types.HasBaseName(nw.Type(), predef.MuResourceClass) {
if c, has := deps[nw]; has {
contract.Assertf(c >= 0, "Expected old resource property ref-count to be >= 0 in dependency map")
deps[nw] = c + 1
} else {
deps[nw] = 1

View file

@ -0,0 +1,42 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package graphgen
import (
"github.com/marapongo/mu/pkg/eval/rt"
"github.com/marapongo/mu/pkg/graph"
)
type objectGraph struct {
roots []graph.Vertex
}
var _ graph.Graph = (*objectGraph)(nil)
func newObjectGraph(roots []graph.Vertex) *objectGraph {
return &objectGraph{roots: roots}
}
func (v *objectGraph) Roots() []graph.Vertex { return v.roots }
type objectVertex struct {
obj *rt.Object // this vertex's object.
ins []graph.Vertex // edges connecting from other vertices into this vertex.
outs []graph.Vertex // edges connecting this vertex to other vertices.
}
var _ graph.Vertex = (*objectVertex)(nil)
func newObjectVertex(obj *rt.Object) *objectVertex {
return &objectVertex{obj: obj}
}
func (v *objectVertex) Obj() *rt.Object { return v.obj }
func (v *objectVertex) Ins() []graph.Vertex { return v.ins }
func (v *objectVertex) Outs() []graph.Vertex { return v.outs }
// AddEdge creates an edge connecting the receiver vertex to the argument vertex.
func (v *objectVertex) AddEdge(to *objectVertex) {
v.outs = append(v.outs, to) // outgoing from this vertex to the other.
to.ins = append(to.ins, v) // incoming from this vertex to the other.
}

View file

@ -3,20 +3,12 @@
package graph
import (
"github.com/marapongo/mu/pkg/tokens"
"github.com/marapongo/mu/pkg/eval/rt"
)
// Vertex is a single vertex within an overall MuGL graph.
type Vertex interface {
Type() tokens.Type // the type of the node.
Properties() map[tokens.Name]VertexProperty // a complete set of properties, known and unknown.
Edges() []Vertex // other nodes that this node depends upon.
}
// VertexProperty represents a single property associated with a node.
type VertexProperty interface {
Name() tokens.Variable // the property's name.
Type() tokens.Type // the type of this property's value.
Value() *interface{} // the value of this property, or nil if unknown.
Computed() bool // true if this property's value is unknown because it will be computed.
Obj() *rt.Object // the vertex's object.
Ins() []Vertex // incoming edges from other vertices within the graph to this vertex.
Outs() []Vertex // outgoing edges from this vertex to other vertices within the graph.
}