Support config maps
This change adds support for configuration maps. This is a new feature that permits initialization code to come from markup, after compilation, but before evaluation. There is nothing special with this code as it could have been authored by a user. But it offers a convenient way to specialize configuration settings per target husk, without needing to write code to specialize each of those husks (which is needlessly complex). For example, let's say we want to have two husks, one in AWS's us-west-1 region, and the other in us-east-2. From the same source package, we can just create two husks, let's say "prod-west" and "prod-east": prod-west.json: { "husk": "prod-west", "config": { "aws:config:region": "us-west-1" } } prod-east.json: { "husk": "prod-east", "config": { "aws:config:region": "us-east-2" } } Now when we evaluate these packages, they will automatically poke the right configuration variables in the AWS package *before* actually evaluating the CocoJS package contents. As a result, the static variable "region" in the "aws:config" package will have the desired value. This is obviously fairly general purpose, but will allow us to experiment with different schemes and patterns. Also, I need to whip up support for secrets, but that is a task for another day (perhaps tomorrow).
This commit is contained in:
parent
2c6616831f
commit
d91b04d8f4
|
@ -32,7 +32,7 @@ func newEvalCmd() *cobra.Command {
|
|||
"a path to a blueprint elsewhere can be provided as the [blueprint] argument.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Perform the compilation and, if non-nil is returned, output the graph.
|
||||
if result := compile(cmd, args); result != nil {
|
||||
if result := compile(cmd, args, nil); result != nil {
|
||||
// Serialize that evaluation graph so that it's suitable for printing/serializing.
|
||||
if dotOutput {
|
||||
// Convert the output to a DOT file.
|
||||
|
|
14
cmd/husk.go
14
cmd/husk.go
|
@ -103,7 +103,7 @@ func create(husk tokens.QName) {
|
|||
|
||||
// compile just uses the standard logic to parse arguments, options, and to locate/compile a package. It returns the
|
||||
// CocoGL graph that is produced, or nil if an error occurred (in which case, we would expect non-0 errors).
|
||||
func compile(cmd *cobra.Command, args []string) *compileResult {
|
||||
func compile(cmd *cobra.Command, args []string, config *resource.ConfigMap) *compileResult {
|
||||
// If there's a --, we need to separate out the command args from the stack args.
|
||||
flags := cmd.Flags()
|
||||
dashdash := flags.ArgsLenAtDash()
|
||||
|
@ -117,6 +117,12 @@ func compile(cmd *cobra.Command, args []string) *compileResult {
|
|||
opts := core.DefaultOptions()
|
||||
opts.Args = dashdashArgsToMap(packArgs)
|
||||
|
||||
// Create the preexec hook if the config map is non-nil.
|
||||
var preexec compiler.Preexec
|
||||
if config != nil {
|
||||
preexec = config.ApplyConfig
|
||||
}
|
||||
|
||||
// In the case of an argument, load that specific package and new up a compiler based on its base path.
|
||||
// Otherwise, use the default workspace and package logic (which consults the current working directory).
|
||||
var comp compiler.Compiler
|
||||
|
@ -130,7 +136,7 @@ func compile(cmd *cobra.Command, args []string) *compileResult {
|
|||
sink().Errorf(errors.ErrorCantCreateCompiler, err)
|
||||
return nil
|
||||
}
|
||||
pkg, heap = comp.Compile()
|
||||
pkg, heap = comp.Compile(preexec)
|
||||
} else {
|
||||
fn := args[0]
|
||||
if pkgmeta := cmdutil.ReadPackageFromArg(fn); pkgmeta != nil {
|
||||
|
@ -144,7 +150,7 @@ func compile(cmd *cobra.Command, args []string) *compileResult {
|
|||
sink().Errorf(errors.ErrorCantReadPackage, fn, err)
|
||||
return nil
|
||||
}
|
||||
pkg, heap = comp.CompilePackage(pkgmeta)
|
||||
pkg, heap = comp.CompilePackage(pkgmeta, preexec)
|
||||
}
|
||||
}
|
||||
return &compileResult{comp, pkg, heap}
|
||||
|
@ -163,7 +169,7 @@ func plan(cmd *cobra.Command, info *huskCmdInfo, delete bool) *planResult {
|
|||
var result *compileResult
|
||||
if !delete {
|
||||
// First, compile; if that yields errors or an empty heap, exit early.
|
||||
if result = compile(cmd, info.args); result == nil || result.Heap == nil {
|
||||
if result = compile(cmd, info.args, info.dep.Config); result == nil || result.Heap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/pulumi/coconut/pkg/compiler/errors"
|
||||
"github.com/pulumi/coconut/pkg/compiler/symbols"
|
||||
"github.com/pulumi/coconut/pkg/compiler/types"
|
||||
"github.com/pulumi/coconut/pkg/diag"
|
||||
"github.com/pulumi/coconut/pkg/tokens"
|
||||
"github.com/pulumi/coconut/pkg/util/contract"
|
||||
)
|
||||
|
@ -101,17 +102,17 @@ func (ctx *Context) LookupClassMember(t symbols.Type, clm tokens.ClassMemberName
|
|||
|
||||
// LookupSymbol performs a complex lookup for a complex token; if require is true, failed lookups will issue an
|
||||
// error; and in any case, the AST node is used as the context for errors (lookup, accessibility, or otherwise).
|
||||
func (ctx *Context) LookupSymbol(node ast.Node, tok tokens.Token, require bool) symbols.Symbol {
|
||||
func (ctx *Context) LookupSymbol(node diag.Diagable, tok tokens.Token, require bool) symbols.Symbol {
|
||||
return ctx.lookupSymbolCore(node, tok, require, true)
|
||||
}
|
||||
|
||||
// LookupShallowSymbol is just like LookupSymbol, except that it will not resolve exports fully. As a result, the
|
||||
// returned symbol may not be bound to the actual underlying class member, and may return a symbols.Export instead.
|
||||
func (ctx *Context) LookupShallowSymbol(node ast.Node, tok tokens.Token, require bool) symbols.Symbol {
|
||||
func (ctx *Context) LookupShallowSymbol(node diag.Diagable, tok tokens.Token, require bool) symbols.Symbol {
|
||||
return ctx.lookupSymbolCore(node, tok, require, false)
|
||||
}
|
||||
|
||||
func (ctx *Context) lookupSymbolCore(node ast.Node, tok tokens.Token,
|
||||
func (ctx *Context) lookupSymbolCore(node diag.Diagable, tok tokens.Token,
|
||||
require bool, resolveExports bool) symbols.Symbol {
|
||||
contract.Require(node != nil, "Node")
|
||||
|
||||
|
@ -242,7 +243,7 @@ func (ctx *Context) LookupPackageSymbol(name tokens.PackageName) *symbols.Packag
|
|||
}
|
||||
|
||||
// LookupTypeToken binds a type token to its symbol, creating elements if needed. The node context is used for errors.
|
||||
func (ctx *Context) LookupTypeToken(node ast.Node, tok tokens.Type, require bool) symbols.Type {
|
||||
func (ctx *Context) LookupTypeToken(node diag.Diagable, tok tokens.Type, require bool) symbols.Type {
|
||||
contract.Require(node != nil, "node")
|
||||
|
||||
var ty symbols.Type // the type, if any.
|
||||
|
@ -305,7 +306,7 @@ func (ctx *Context) LookupFunctionType(node ast.Function) *symbols.FunctionType
|
|||
}
|
||||
|
||||
// lookupBasicType handles decorated types (pointers, arrays, maps, functions) and primitives.
|
||||
func (ctx *Context) lookupBasicType(node ast.Node, tok tokens.Type, require bool) symbols.Type {
|
||||
func (ctx *Context) lookupBasicType(node diag.Diagable, tok tokens.Type, require bool) symbols.Type {
|
||||
contract.Require(node != nil, "node")
|
||||
contract.Requiref(tok.Primitive() || tok.Decorated(), "tok", "Primitive() || Decorated()")
|
||||
|
||||
|
@ -358,7 +359,7 @@ func (ctx *Context) lookupBasicType(node ast.Node, tok tokens.Type, require bool
|
|||
return types.Error
|
||||
}
|
||||
|
||||
func (ctx *Context) checkClassVisibility(node ast.Node, class symbols.Type, member symbols.ClassMember) {
|
||||
func (ctx *Context) checkClassVisibility(node diag.Diagable, class symbols.Type, member symbols.ClassMember) {
|
||||
acc := member.MemberNode().GetAccess()
|
||||
if acc == nil {
|
||||
a := tokens.PrivateAccessibility // private is the default.
|
||||
|
|
|
@ -29,11 +29,11 @@ type Compiler interface {
|
|||
Workspace() workspace.W // the workspace that this compielr is using.
|
||||
|
||||
// Compile detects a package from the workspace and compiles it into a graph.
|
||||
Compile() (*symbols.Package, *heapstate.Heap)
|
||||
Compile(preexec Preexec) (*symbols.Package, *heapstate.Heap)
|
||||
// CompilePath compiles a package given its path into its graph form.
|
||||
CompilePath(path string) (*symbols.Package, *heapstate.Heap)
|
||||
CompilePath(path string, preexec Preexec) (*symbols.Package, *heapstate.Heap)
|
||||
// CompilePackage compiles a given package object into its associated graph form.
|
||||
CompilePackage(pkg *pack.Package) (*symbols.Package, *heapstate.Heap)
|
||||
CompilePackage(pkg *pack.Package, preexec Preexec) (*symbols.Package, *heapstate.Heap)
|
||||
// Verify detects a package from the workspace and validates its CocoIL contents.
|
||||
Verify() bool
|
||||
// VerifyPath verifies a package given its path, validating that its CocoIL contents are correct.
|
||||
|
@ -42,6 +42,9 @@ type Compiler interface {
|
|||
VerifyPackage(pkg *pack.Package) bool
|
||||
}
|
||||
|
||||
// Preexec can be used to hook compilation after binding, but before evaluation, for any pre-evaluation steps.
|
||||
type Preexec func(*binder.Context, *symbols.Package, eval.Interpreter)
|
||||
|
||||
// compiler is the canonical implementation of the Coconut compiler.
|
||||
type compiler struct {
|
||||
w workspace.W
|
||||
|
@ -105,23 +108,23 @@ func (c *compiler) Diag() diag.Sink { return c.ctx.Diag }
|
|||
func (c *compiler) Workspace() workspace.W { return c.w }
|
||||
|
||||
// Compile attempts to detect the package from the current working directory and, provided that succeeds, compiles it.
|
||||
func (c *compiler) Compile() (*symbols.Package, *heapstate.Heap) {
|
||||
func (c *compiler) Compile(preexec Preexec) (*symbols.Package, *heapstate.Heap) {
|
||||
if path := c.detectPackage(); path != "" {
|
||||
return c.CompilePath(path)
|
||||
return c.CompilePath(path, preexec)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CompilePath loads a package at the given path and compiles it into a graph.
|
||||
func (c *compiler) CompilePath(path string) (*symbols.Package, *heapstate.Heap) {
|
||||
func (c *compiler) CompilePath(path string, preexec Preexec) (*symbols.Package, *heapstate.Heap) {
|
||||
if pkg := c.readPackage(path); pkg != nil {
|
||||
return c.CompilePackage(pkg)
|
||||
return c.CompilePackage(pkg, preexec)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CompilePackage compiles the given package into a graph.
|
||||
func (c *compiler) CompilePackage(pkg *pack.Package) (*symbols.Package, *heapstate.Heap) {
|
||||
func (c *compiler) CompilePackage(pkg *pack.Package, preexec Preexec) (*symbols.Package, *heapstate.Heap) {
|
||||
contract.Requiref(pkg != nil, "pkg", "!= nil")
|
||||
glog.Infof("Compiling package '%v' (w=%v)", pkg.Name, c.w.Root())
|
||||
if glog.V(2) {
|
||||
|
@ -157,8 +160,16 @@ func (c *compiler) CompilePackage(pkg *pack.Package) (*symbols.Package, *heapsta
|
|||
// Now, create the machinery we need to generate a graph.
|
||||
gg := heapstate.New(c.ctx)
|
||||
|
||||
// Now, evaluate.
|
||||
// Create a fresh evaluator; if there are pre-exec hooks, run them now.
|
||||
e := eval.New(b.Ctx(), gg)
|
||||
if preexec != nil {
|
||||
preexec(b.Ctx(), pkgsym, e)
|
||||
}
|
||||
if !c.Diag().Success() {
|
||||
return nil, nil // the preexec functions raised errors; quit.
|
||||
}
|
||||
|
||||
// Go ahead and perform the evaluation.
|
||||
e.EvaluatePackage(pkgsym, c.ctx.Opts.Args)
|
||||
if !c.Diag().Success() {
|
||||
return pkgsym, nil
|
||||
|
|
|
@ -12,4 +12,6 @@ var (
|
|||
ErrorCantReadDeployment = newError(2005, "Could not read deployment file '%v': %v")
|
||||
ErrorDuplicateMonikerNames = newError(2006, "Duplicate objects with the same name: %v")
|
||||
ErrorInvalidHuskName = newError(2007, "Invalid husk '%v'; could not be found in the workspace")
|
||||
ErrorIllegalConfigToken = newError(2008,
|
||||
"Configs may only target module properties and class static properties; %v is neither")
|
||||
)
|
||||
|
|
297
pkg/eval/eval.go
297
pkg/eval/eval.go
|
@ -34,6 +34,12 @@ type Interpreter interface {
|
|||
EvaluateModule(mod *symbols.Module, args core.Args)
|
||||
// EvaluateFunction performs an evaluation of the given function, using the provided arguments.
|
||||
EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args)
|
||||
|
||||
// LoadLocation loads a location by symbol; lval controls whether it is an l-value or just a value.
|
||||
LoadLocation(tree diag.Diagable, sym symbols.Symbol, this *rt.Object, lval bool) *Location
|
||||
|
||||
// NewConstantObject creates a new constant object in the context of this interpreter.
|
||||
NewConstantObject(tree diag.Diagable, v interface{}) *rt.Object
|
||||
}
|
||||
|
||||
// InterpreterHooks is a set of callbacks that can be used to hook into interesting interpreter events.
|
||||
|
@ -470,6 +476,11 @@ func (e *evaluator) newObject(tree diag.Diagable, t symbols.Type) *rt.Object {
|
|||
return e.alloc.New(tree, t, nil, proto)
|
||||
}
|
||||
|
||||
// NewConstantObject is a public function that allocates an object with the given state.
|
||||
func (e *evaluator) NewConstantObject(tree diag.Diagable, v interface{}) *rt.Object {
|
||||
return e.alloc.NewConstant(tree, v)
|
||||
}
|
||||
|
||||
// issueUnhandledException issues an unhandled exception error using the given diagnostic and unwind information.
|
||||
func (e *evaluator) issueUnhandledException(uw *rt.Unwind, err *diag.Diag, args ...interface{}) {
|
||||
contract.Assert(uw.Throw())
|
||||
|
@ -979,7 +990,7 @@ func (e *evaluator) evalExpression(node ast.Expression) (*rt.Object, *rt.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) (location, *rt.Unwind) {
|
||||
func (e *evaluator) evalLValueExpression(node ast.Expression) (*Location, *rt.Unwind) {
|
||||
switch n := node.(type) {
|
||||
case *ast.LoadLocationExpression:
|
||||
return e.evalLoadLocation(n, true)
|
||||
|
@ -988,10 +999,10 @@ func (e *evaluator) evalLValueExpression(node ast.Expression) (location, *rt.Unw
|
|||
case *ast.UnaryOperatorExpression:
|
||||
contract.Assert(n.Operator == ast.OpDereference)
|
||||
obj, uw := e.evalUnaryOperatorExpressionFor(n, true)
|
||||
return location{Obj: obj}, uw
|
||||
return &Location{e: e, Obj: obj}, uw
|
||||
default:
|
||||
contract.Failf("Unrecognized l-value expression type: %v", node.GetKind())
|
||||
return location{}, nil
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1114,18 +1125,6 @@ func (e *evaluator) evalObjectLiteral(node *ast.ObjectLiteral) (*rt.Object, *rt.
|
|||
return obj, nil
|
||||
}
|
||||
|
||||
func (e *evaluator) evalLoadLocationExpression(node *ast.LoadLocationExpression) (*rt.Object, *rt.Unwind) {
|
||||
loc, uw := e.evalLoadLocation(node, false)
|
||||
return loc.Obj, uw
|
||||
}
|
||||
|
||||
type location struct {
|
||||
This *rt.Object // the target object, if any.
|
||||
Name tokens.Name // the simple name of the variable.
|
||||
Lval bool // whether the result is an lval.
|
||||
Obj *rt.Object // the resulting object (pointer if lval, object otherwise).
|
||||
}
|
||||
|
||||
// getObjectOrSuperProperty loads a property pointer from an object using the given property key. It understands how
|
||||
// to determine whether this is a `super` load, and bind it, and will adjust the resulting pointer accordingly.
|
||||
func (e *evaluator) getObjectOrSuperProperty(
|
||||
|
@ -1160,9 +1159,61 @@ func (e *evaluator) getObjectOrSuperProperty(
|
|||
return obj.GetPropertyAddr(k, init, forWrite)
|
||||
}
|
||||
|
||||
func (e *evaluator) evalLoadLocationExpression(node *ast.LoadLocationExpression) (*rt.Object, *rt.Unwind) {
|
||||
loc, uw := e.evalLoadLocation(node, false)
|
||||
return loc.Obj, uw
|
||||
}
|
||||
|
||||
func (e *evaluator) newLocation(node diag.Diagable, sym symbols.Symbol,
|
||||
pv *rt.Pointer, ty symbols.Type, this *rt.Object, lval bool) *Location {
|
||||
// If this is an l-value, return a pointer to the object; otherwise, return the raw object.
|
||||
var obj *rt.Object
|
||||
if lval {
|
||||
obj = e.alloc.NewPointer(node, ty, pv)
|
||||
} else {
|
||||
obj = pv.Obj()
|
||||
}
|
||||
|
||||
if glog.V(9) {
|
||||
glog.V(9).Infof("Loaded location of type '%v' from symbol '%v': lval=%v current=%v",
|
||||
ty.Token(), sym.Token(), lval, pv.Obj())
|
||||
}
|
||||
|
||||
return &Location{
|
||||
e: e,
|
||||
This: this,
|
||||
Name: sym.Name(),
|
||||
Lval: lval,
|
||||
Obj: obj,
|
||||
}
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
e *evaluator // the evaluator that produced this location.
|
||||
This *rt.Object // the target object, if any.
|
||||
Name tokens.Name // the simple name of the variable.
|
||||
Lval bool // whether the result is an lval.
|
||||
Obj *rt.Object // the resulting object (pointer if lval, object otherwise).
|
||||
}
|
||||
|
||||
func (loc *Location) Assign(node diag.Diagable, val *rt.Object) {
|
||||
// Perform the assignment, but make sure to invoke the property assignment hook if necessary.
|
||||
ptr := loc.Obj.PointerValue()
|
||||
|
||||
// If the pointer is readonly, however, we will disallow the assignment.
|
||||
if ptr.Readonly() {
|
||||
loc.e.Diag().Errorf(errors.ErrorIllegalReadonlyLValue.At(node))
|
||||
} else {
|
||||
if loc.e.hooks != nil {
|
||||
loc.e.hooks.OnVariableAssign(node, loc.This, loc.Name, ptr.Obj(), val)
|
||||
}
|
||||
ptr.Set(val)
|
||||
}
|
||||
}
|
||||
|
||||
// evalLoadLocation evaluates and loads information about the target. It takes an lval bool which
|
||||
// determines whether the resulting location object is an lval (pointer) or regular object.
|
||||
func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool) (location, *rt.Unwind) {
|
||||
func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool) (*Location, *rt.Unwind) {
|
||||
// If there's a target object, evaluate it.
|
||||
var this *rt.Object
|
||||
var thisexpr ast.Expression
|
||||
|
@ -1170,7 +1221,7 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
|
|||
thisexpr = *node.Object
|
||||
var uw *rt.Unwind
|
||||
if this, uw = e.evalExpression(thisexpr); uw != nil {
|
||||
return location{}, uw
|
||||
return nil, uw
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1189,87 +1240,88 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
|
|||
sym = loc
|
||||
} else {
|
||||
sym = e.ctx.LookupSymbol(node.Name, tok, false)
|
||||
contract.Assert(sym != nil) // don't issue errors; we shouldn't ever get here if verification failed.
|
||||
sym = MaybeIntrinsic(node, sym) // replace this with an intrinsic if it's recognized as one.
|
||||
|
||||
// If the symbol is an export, keep chasing down the referents until we hit a real symbol.
|
||||
for {
|
||||
export, isexport := sym.(*symbols.Export)
|
||||
if !isexport {
|
||||
break
|
||||
}
|
||||
// Simply chase the referent symbol until we bottom out on something useful.
|
||||
contract.Assertf(export.Referent != sym, "Unexpected self-referential export token")
|
||||
sym = export.Referent
|
||||
contract.Assertf(sym != nil, "Expected export '%v' to resolve to a token", export.Node.Referent.Tok)
|
||||
var uw *rt.Unwind
|
||||
pv, ty, uw = e.evalLoadSymbolLocation(node, sym, this, thisexpr, lval)
|
||||
if uw != nil {
|
||||
return nil, uw
|
||||
}
|
||||
}
|
||||
|
||||
// Look up the symbol property in the right place. Note that because this is a static load, we intentionally
|
||||
// do not perform any lazily initialization of missing property slots; they must exist. But we still need to
|
||||
// load from the object in case one of the properties was overwritten. The sole exception is for `dynamic`.
|
||||
switch s := sym.(type) {
|
||||
case symbols.ClassMember:
|
||||
// Consult either the statics map or the object's property based on the kind of symbol. Note that we do
|
||||
// this even for class functions so that in case they are replaced or overridden in derived types, we get
|
||||
// the expected "virtual" dispatch behavior. The one special case is constructors, where we intentionally
|
||||
// return a statically resolved symbol (since they aren't stored as properties and to support `super`).
|
||||
k := rt.PropertyKey(sym.Name())
|
||||
if s.Static() {
|
||||
contract.Assert(this == nil)
|
||||
class := s.MemberParent()
|
||||
e.ensureClassInit(class) // ensure the class is initialized.
|
||||
statics := e.getClassStatics(class)
|
||||
pv = statics.GetAddr(k, false)
|
||||
} else {
|
||||
contract.Assert(this != nil)
|
||||
if uw := e.checkThis(node, this); uw != nil {
|
||||
return location{}, uw
|
||||
}
|
||||
dynload := this.Type() == types.Dynamic
|
||||
pv = e.getObjectOrSuperProperty(this, thisexpr, k, dynload, lval)
|
||||
}
|
||||
contract.Assertf(pv != nil, "Missing class member '%v' (static=%v)", s.Token(), s.Static())
|
||||
ty = s.Type()
|
||||
contract.Assert(ty != nil)
|
||||
case symbols.ModuleMemberProperty:
|
||||
module := s.MemberParent()
|
||||
return e.newLocation(node, sym, pv, ty, this, lval), nil
|
||||
}
|
||||
|
||||
// Ensure this module has been initialized.
|
||||
e.ensureModuleInit(module)
|
||||
func (e *evaluator) evalLoadSymbolLocation(node diag.Diagable, sym symbols.Symbol,
|
||||
this *rt.Object, thisexpr ast.Expression, lval bool) (*rt.Pointer, symbols.Type, *rt.Unwind) {
|
||||
contract.Assert(sym != nil) // don't issue errors; we shouldn't ever get here if verification failed.
|
||||
sym = MaybeIntrinsic(node, sym) // replace this with an intrinsic if it's recognized as one.
|
||||
|
||||
// Search the globals table for this module's members.
|
||||
// If the symbol is an export, keep chasing down the referents until we hit a real symbol.
|
||||
for {
|
||||
export, isexport := sym.(*symbols.Export)
|
||||
if !isexport {
|
||||
break
|
||||
}
|
||||
// Simply chase the referent symbol until we bottom out on something useful.
|
||||
contract.Assertf(export.Referent != sym, "Unexpected self-referential export token")
|
||||
sym = export.Referent
|
||||
contract.Assertf(sym != nil, "Expected export '%v' to resolve to a token", export.Node.Referent.Tok)
|
||||
}
|
||||
|
||||
// Look up the symbol property in the right place. Note that because this is a static load, we intentionally
|
||||
// do not perform any lazily initialization of missing property slots; they must exist. But we still need to
|
||||
// load from the object in case one of the properties was overwritten. The sole exception is for `dynamic`.
|
||||
var pv *rt.Pointer
|
||||
var ty symbols.Type
|
||||
switch s := sym.(type) {
|
||||
case symbols.ClassMember:
|
||||
// Consult either the statics map or the object's property based on the kind of symbol. Note that we do
|
||||
// this even for class functions so that in case they are replaced or overridden in derived types, we get
|
||||
// the expected "virtual" dispatch behavior. The one special case is constructors, where we intentionally
|
||||
// return a statically resolved symbol (since they aren't stored as properties and to support `super`).
|
||||
k := rt.PropertyKey(sym.Name())
|
||||
if s.Static() {
|
||||
contract.Assert(this == nil)
|
||||
k := rt.PropertyKey(s.Name())
|
||||
globals := e.getModuleGlobals(module)
|
||||
pv = globals.GetAddr(k, false)
|
||||
contract.Assertf(pv != nil, "Missing module member '%v'", s.Token())
|
||||
ty = s.MemberType()
|
||||
contract.Assert(ty != nil)
|
||||
default:
|
||||
contract.Failf("Unexpected symbol token '%v' kind during load expression: %v",
|
||||
tok, reflect.TypeOf(sym))
|
||||
class := s.MemberParent()
|
||||
e.ensureClassInit(class) // ensure the class is initialized.
|
||||
statics := e.getClassStatics(class)
|
||||
pv = statics.GetAddr(k, false)
|
||||
} else {
|
||||
contract.Assert(this != nil)
|
||||
if uw := e.checkThis(node, this); uw != nil {
|
||||
return nil, nil, uw
|
||||
}
|
||||
dynload := this.Type() == types.Dynamic
|
||||
pv = e.getObjectOrSuperProperty(this, thisexpr, k, dynload, lval)
|
||||
}
|
||||
contract.Assertf(pv != nil, "Missing class member '%v' (static=%v)", s.Token(), s.Static())
|
||||
ty = s.Type()
|
||||
contract.Assert(ty != nil)
|
||||
case symbols.ModuleMemberProperty:
|
||||
module := s.MemberParent()
|
||||
|
||||
// Ensure this module has been initialized.
|
||||
e.ensureModuleInit(module)
|
||||
|
||||
// Search the globals table for this module's members.
|
||||
contract.Assert(this == nil)
|
||||
k := rt.PropertyKey(s.Name())
|
||||
globals := e.getModuleGlobals(module)
|
||||
pv = globals.GetAddr(k, false)
|
||||
contract.Assertf(pv != nil, "Missing module member '%v'", s.Token())
|
||||
ty = s.MemberType()
|
||||
contract.Assert(ty != nil)
|
||||
default:
|
||||
contract.Failf("Unexpected symbol token '%v' kind during load expression: %v",
|
||||
sym.Token(), reflect.TypeOf(sym))
|
||||
}
|
||||
|
||||
// If this is an l-value, return a pointer to the object; otherwise, return the raw object.
|
||||
var obj *rt.Object
|
||||
if lval {
|
||||
obj = e.alloc.NewPointer(node, ty, pv)
|
||||
} else {
|
||||
obj = pv.Obj()
|
||||
}
|
||||
return pv, ty, nil
|
||||
}
|
||||
|
||||
if glog.V(9) {
|
||||
glog.V(9).Infof("Loaded location of type '%v' from symbol '%v': lval=%v current=%v",
|
||||
ty.Token(), sym.Token(), lval, pv.Obj())
|
||||
}
|
||||
|
||||
return location{
|
||||
This: this,
|
||||
Name: sym.Name(),
|
||||
Lval: lval,
|
||||
Obj: obj,
|
||||
}, nil
|
||||
func (e *evaluator) LoadLocation(tree diag.Diagable, sym symbols.Symbol, this *rt.Object, lval bool) *Location {
|
||||
pv, ty, uw := e.evalLoadSymbolLocation(tree, sym, this, nil, lval)
|
||||
contract.Assertf(uw == nil, "Unexpected unwind; possible nil 'this' for instance method")
|
||||
return e.newLocation(tree, sym, pv, ty, this, lval)
|
||||
}
|
||||
|
||||
// checkThis checks a this object, raising a runtime error if it is the runtime null value.
|
||||
|
@ -1286,22 +1338,22 @@ func (e *evaluator) evalLoadDynamicExpression(node *ast.LoadDynamicExpression) (
|
|||
return loc.Obj, uw
|
||||
}
|
||||
|
||||
func (e *evaluator) evalLoadDynamic(node *ast.LoadDynamicExpression, lval bool) (location, *rt.Unwind) {
|
||||
func (e *evaluator) evalLoadDynamic(node *ast.LoadDynamicExpression, lval bool) (*Location, *rt.Unwind) {
|
||||
var uw *rt.Unwind
|
||||
|
||||
// Evaluate the object and then the property expression.
|
||||
var this *rt.Object
|
||||
if this, uw = e.evalExpression(node.Object); uw != nil {
|
||||
return location{}, uw
|
||||
return nil, uw
|
||||
}
|
||||
var name *rt.Object
|
||||
if name, uw = e.evalExpression(node.Name); uw != nil {
|
||||
return location{}, uw
|
||||
return nil, uw
|
||||
}
|
||||
|
||||
// Check that the object isn't null; if it is, raise an exception.
|
||||
if uw = e.checkThis(node, this); uw != nil {
|
||||
return location{}, uw
|
||||
return nil, uw
|
||||
}
|
||||
|
||||
// Now go ahead and search the object for a property with the given name.
|
||||
|
@ -1351,7 +1403,8 @@ func (e *evaluator) evalLoadDynamic(node *ast.LoadDynamicExpression, lval bool)
|
|||
}
|
||||
contract.Assert(obj != nil)
|
||||
|
||||
return location{
|
||||
return &Location{
|
||||
e: e,
|
||||
This: this,
|
||||
Name: tokens.Name(key),
|
||||
Lval: lval,
|
||||
|
@ -1452,7 +1505,7 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres
|
|||
|
||||
// Evaluate the operand and prepare to use it.
|
||||
var opand *rt.Object
|
||||
var opandloc *location
|
||||
var opandloc *Location
|
||||
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.
|
||||
|
@ -1461,7 +1514,7 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres
|
|||
return nil, uw
|
||||
}
|
||||
opand = loc.Obj
|
||||
opandloc = &loc
|
||||
opandloc = loc
|
||||
} else {
|
||||
// Otherwise, we just need to evaluate the operand as usual.
|
||||
var uw *rt.Unwind
|
||||
|
@ -1502,7 +1555,7 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres
|
|||
old := ptr.Obj()
|
||||
val := old.NumberValue()
|
||||
new := e.alloc.NewNumber(node, val+1)
|
||||
e.evalAssign(node.Operand, *opandloc, new)
|
||||
opandloc.Assign(node.Operand, new)
|
||||
if node.Postfix {
|
||||
return old, nil
|
||||
}
|
||||
|
@ -1513,7 +1566,7 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres
|
|||
old := ptr.Obj()
|
||||
val := old.NumberValue()
|
||||
new := e.alloc.NewNumber(node, val-1)
|
||||
e.evalAssign(node.Operand, *opandloc, new)
|
||||
opandloc.Assign(node.Operand, new)
|
||||
if node.Postfix {
|
||||
return old, nil
|
||||
}
|
||||
|
@ -1527,14 +1580,13 @@ func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpres
|
|||
func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpression) (*rt.Object, *rt.Unwind) {
|
||||
// Evaluate the operands and prepare to use them. First left, then right.
|
||||
var lhs *rt.Object
|
||||
var lhsloc *location
|
||||
var lhsloc *Location
|
||||
if isBinaryAssignmentOperator(node.Operator) {
|
||||
loc, uw := e.evalLValueExpression(node.Left)
|
||||
if uw != nil {
|
||||
var uw *rt.Unwind
|
||||
if lhsloc, uw = e.evalLValueExpression(node.Left); uw != nil {
|
||||
return nil, uw
|
||||
}
|
||||
lhs = loc.Obj
|
||||
lhsloc = &loc
|
||||
lhs = lhsloc.Obj
|
||||
} else {
|
||||
var uw *rt.Unwind
|
||||
if lhs, uw = e.evalExpression(node.Left); uw != nil {
|
||||
|
@ -1613,7 +1665,7 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress
|
|||
// Assignment operators
|
||||
case ast.OpAssign:
|
||||
// The target is an l-value; just overwrite its value, and yield the new value as the result.
|
||||
e.evalAssign(node.Left, *lhsloc, rhs)
|
||||
lhsloc.Assign(node.Left, rhs)
|
||||
return rhs, nil
|
||||
case ast.OpAssignSum:
|
||||
var val *rt.Object
|
||||
|
@ -1625,67 +1677,67 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress
|
|||
// Otherwise, the target is a numeric l-value; just += to it, and yield the new value as the result.
|
||||
val = e.alloc.NewNumber(node, ptr.Obj().NumberValue()+rhs.NumberValue())
|
||||
}
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignDifference:
|
||||
// The target is a numeric l-value; just -= rhs to it, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, ptr.Obj().NumberValue()-rhs.NumberValue())
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignProduct:
|
||||
// The target is a numeric l-value; just *= rhs to it, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, ptr.Obj().NumberValue()*rhs.NumberValue())
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignQuotient:
|
||||
// The target is a numeric l-value; just /= rhs to it, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, ptr.Obj().NumberValue()/rhs.NumberValue())
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignRemainder:
|
||||
// The target is a numeric l-value; just %= rhs to it, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())%int64(rhs.NumberValue())))
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignExponentiation:
|
||||
// The target is a numeric l-value; just raise to rhs as a power, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, math.Pow(ptr.Obj().NumberValue(), rhs.NumberValue()))
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignBitwiseShiftLeft:
|
||||
// The target is a numeric l-value; just <<= rhs to it, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())<<uint(rhs.NumberValue())))
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignBitwiseShiftRight:
|
||||
// The target is a numeric l-value; just >>= rhs to it, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())>>uint(rhs.NumberValue())))
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignBitwiseAnd:
|
||||
// The target is a numeric l-value; just &= rhs to it, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())&int64(rhs.NumberValue())))
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignBitwiseOr:
|
||||
// The target is a numeric l-value; just |= rhs to it, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())|int64(rhs.NumberValue())))
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
case ast.OpAssignBitwiseXor:
|
||||
// The target is a numeric l-value; just ^= rhs to it, and yield the new value as the result.
|
||||
ptr := lhs.PointerValue()
|
||||
val := e.alloc.NewNumber(node, float64(int64(ptr.Obj().NumberValue())^int64(rhs.NumberValue())))
|
||||
e.evalAssign(node.Left, *lhsloc, val)
|
||||
lhsloc.Assign(node.Left, val)
|
||||
return val, nil
|
||||
|
||||
// Relational operators
|
||||
|
@ -1725,21 +1777,6 @@ func isBinaryAssignmentOperator(op ast.BinaryOperator) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evalAssign(node ast.Node, loc location, val *rt.Object) {
|
||||
// Perform the assignment, but make sure to invoke the property assignment hook if necessary.
|
||||
ptr := loc.Obj.PointerValue()
|
||||
|
||||
// If the pointer is readonly, however, we will disallow the assignment.
|
||||
if ptr.Readonly() {
|
||||
e.Diag().Errorf(errors.ErrorIllegalReadonlyLValue.At(node))
|
||||
} else {
|
||||
if e.hooks != nil {
|
||||
e.hooks.OnVariableAssign(node, loc.This, loc.Name, ptr.Obj(), val)
|
||||
}
|
||||
ptr.Set(val)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evalBinaryOperatorEquals(lhs *rt.Object, rhs *rt.Object) bool {
|
||||
if lhs == rhs {
|
||||
return true
|
||||
|
|
|
@ -28,8 +28,8 @@ func init() {
|
|||
|
||||
// Intrinsic is a special intrinsic function whose behavior is implemented by the runtime.
|
||||
type Intrinsic struct {
|
||||
Node ast.Node // the contextual node representing the place where this intrinsic got created.
|
||||
Func ast.Function // the underlying function's node (before mapping to an intrinsic).
|
||||
Node diag.Diagable // the contextual node representing the place where this intrinsic got created.
|
||||
Func ast.Function // the underlying function's node (before mapping to an intrinsic).
|
||||
Nm tokens.Name
|
||||
Tok tokens.Token
|
||||
Sig *symbols.FunctionType
|
||||
|
@ -54,10 +54,10 @@ func (node *Intrinsic) Invoke(e *evaluator, this *rt.Object, args []*rt.Object)
|
|||
}
|
||||
|
||||
// newIntrinsic returns a new intrinsic function symbol with the given information.
|
||||
func NewIntrinsic(node ast.Node, fnc ast.Function, tok tokens.Token, nm tokens.Name,
|
||||
func NewIntrinsic(tree diag.Diagable, fnc ast.Function, tok tokens.Token, nm tokens.Name,
|
||||
sig *symbols.FunctionType, invoker Invoker) *Intrinsic {
|
||||
return &Intrinsic{
|
||||
Node: node,
|
||||
Node: tree,
|
||||
Func: fnc,
|
||||
Nm: nm,
|
||||
Tok: tok,
|
||||
|
@ -68,7 +68,7 @@ func NewIntrinsic(node ast.Node, fnc ast.Function, tok tokens.Token, nm tokens.N
|
|||
|
||||
// MaybeIntrinsic checks whether the given symbol is an intrinsic and, if so, swaps it out with the actual runtime
|
||||
// implementation of that intrinsic. If the symbol is not an intrinsic, the original symbol is simply returned.
|
||||
func MaybeIntrinsic(node ast.Node, sym symbols.Symbol) symbols.Symbol {
|
||||
func MaybeIntrinsic(tree diag.Diagable, sym symbols.Symbol) symbols.Symbol {
|
||||
switch s := sym.(type) {
|
||||
case *Intrinsic:
|
||||
// Already an intrinsic; do not swap it out.
|
||||
|
@ -77,7 +77,7 @@ func MaybeIntrinsic(node ast.Node, sym symbols.Symbol) symbols.Symbol {
|
|||
// cache these symbols because of the need to associate the AST node with the resulting symbol.
|
||||
tok := s.Token()
|
||||
if invoker, isintrinsic := Intrinsics[tok]; isintrinsic {
|
||||
sym = NewIntrinsic(node, s.Function(), tok, tok.Name(), s.Signature(), invoker)
|
||||
sym = NewIntrinsic(tree, s.Function(), tok, tok.Name(), s.Signature(), invoker)
|
||||
}
|
||||
}
|
||||
return sym
|
||||
|
|
76
pkg/resource/config.go
Normal file
76
pkg/resource/config.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2016 Pulumi, Inc. All rights reserved.
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/pulumi/coconut/pkg/compiler/binder"
|
||||
"github.com/pulumi/coconut/pkg/compiler/errors"
|
||||
"github.com/pulumi/coconut/pkg/compiler/symbols"
|
||||
"github.com/pulumi/coconut/pkg/diag"
|
||||
"github.com/pulumi/coconut/pkg/eval"
|
||||
"github.com/pulumi/coconut/pkg/tokens"
|
||||
)
|
||||
|
||||
// ConfigMap contains a mapping from variable token to the value to poke into that variable.
|
||||
type ConfigMap map[tokens.ModuleMember]interface{}
|
||||
|
||||
// ApplyConfig applies the configuration map to an existing interpreter context. The map is simply a map of tokens --
|
||||
// which must be globally settable variables (module properties or static class properties) -- to serializable constant
|
||||
// values. The routine simply walks these tokens in sorted order, and assigns the constant objects. Note that, because
|
||||
// we are accessing module and class members, this routine will also trigger the relevant initialization routines.
|
||||
func (cfg *ConfigMap) ApplyConfig(ctx *binder.Context, pkg *symbols.Package, e eval.Interpreter) {
|
||||
if cfg != nil {
|
||||
// For each config entry, bind the token to its symbol, and then attempt to assign to it.
|
||||
for _, tok := range stableConfigKeys(*cfg) {
|
||||
// Bind to the symbol; if it returns nil, this means an error has resulted, and we can skip it.
|
||||
var tree diag.Diagable = nil // there is no source info for this; eventually we may assign one.
|
||||
if sym := ctx.LookupSymbol(tree, tokens.Token(tok), true); sym != nil {
|
||||
switch s := sym.(type) {
|
||||
case *symbols.ModuleProperty:
|
||||
// ok
|
||||
case *symbols.ClassProperty:
|
||||
// class properties are ok, so long as they are static.
|
||||
if !s.Static() {
|
||||
ctx.Diag.Errorf(errors.ErrorIllegalConfigToken, tok)
|
||||
return
|
||||
}
|
||||
default:
|
||||
ctx.Diag.Errorf(errors.ErrorIllegalConfigToken, tok)
|
||||
return
|
||||
}
|
||||
|
||||
// Load up the location as an l-value; because we don't support instance properties, this is nil.
|
||||
if loc := e.LoadLocation(tree, sym, nil, true); loc != nil {
|
||||
// Allocate a new constant for the value we are about to assign, and assign it to the location.
|
||||
v := (*cfg)[tok]
|
||||
loc.Assign(tree, e.NewConstantObject(nil, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stableConfigKeys(config ConfigMap) []tokens.ModuleMember {
|
||||
sorted := make(configKeys, 0, len(config))
|
||||
for key := range config {
|
||||
sorted = append(sorted, key)
|
||||
}
|
||||
sort.Sort(sorted)
|
||||
return sorted
|
||||
}
|
||||
|
||||
type configKeys []tokens.ModuleMember
|
||||
|
||||
func (s configKeys) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s configKeys) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s configKeys) Less(i, j int) bool {
|
||||
return s[i] < s[j]
|
||||
}
|
|
@ -16,6 +16,7 @@ import (
|
|||
// Deployment is a serialized deployment target plus a record of the latest deployment.
|
||||
type Deployment struct {
|
||||
Husk tokens.QName `json:"husk"` // the target environment name.
|
||||
Config *ConfigMap `json:"config,omitempty"` // optional configuration key/values.
|
||||
Latest *DeploymentRecord `json:"latest,omitempty"` // the latest/current deployment record.
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue