Implement dynamic loads from the environment¬
This rearranges the way dynamic loads work a bit. Previously, they¬ required an object, and did a dynamic lookup in the object's property¬ map. For real dynamic loads -- of the kind Python uses, obviously,¬ but also ECMAScript -- we need to search the "environment". This change searches the environment by looking first in the lexical¬ scope in the current function. If a variable exists, we will use it.¬ If that misses, we then look in the module scope. If a variable exists¬ there, we will use it. Otherwise, if the variable is used in a non-lval position, an dynamic error will be raised ("name not declared"). If an lval, however, we will lazily allocate a slot for it. Note that Python doesn't use block scoping in the same way that most languages do. This behavior is simply achieved by Python not emitting any lexically scoped blocks other than at the function level. This doesn't perfectly achieve the scoping behavior, because we don't yet bind every name in a way that they can be dynamically discovered. The two obvious cases are class names and import names. Those will be covered in a subsequent commit. Also note that we are getting lucky here that class static/instance variables aren't accessible in Python or ECMAScript "ambiently" like they are in some languages (e.g., C#, Java); as a result, we don't need to introduce a class scope in the dynamic lookup. Some day, when we want to support such languages, we'll need to think about how to let languages control the environment probe order; for instance, perhaps the LoadDynamicExpression node can have an "environment" property.
This commit is contained in:
parent
9c1ea1f161
commit
f773000ef9
13 changed files with 185 additions and 109 deletions
|
@ -177,62 +177,64 @@ func printPackage(pkg *pack.Package, printSymbols bool, printExports bool, print
|
|||
}
|
||||
|
||||
func printModules(pkg *pack.Package, printSymbols bool, printExports bool, printIL bool, indent string) {
|
||||
pkgtok := tokens.NewPackageToken(pkg.Name)
|
||||
for _, name := range ast.StableModules(*pkg.Modules) {
|
||||
mod := (*pkg.Modules)[name]
|
||||
modtok := tokens.NewModuleToken(pkgtok, name)
|
||||
if pkg.Modules != nil {
|
||||
pkgtok := tokens.NewPackageToken(pkg.Name)
|
||||
for _, name := range ast.StableModules(*pkg.Modules) {
|
||||
mod := (*pkg.Modules)[name]
|
||||
modtok := tokens.NewModuleToken(pkgtok, name)
|
||||
|
||||
// Print the name.
|
||||
fmt.Printf("%vmodule \"%v\" {", indent, name)
|
||||
// Print the name.
|
||||
fmt.Printf("%vmodule \"%v\" {", indent, name)
|
||||
|
||||
// Now, if requested, print the tokens.
|
||||
if printSymbols || printExports {
|
||||
if mod.Imports != nil || mod.Members != nil {
|
||||
fmt.Printf("\n")
|
||||
// Now, if requested, print the tokens.
|
||||
if printSymbols || printExports {
|
||||
if mod.Imports != nil || mod.Members != nil {
|
||||
fmt.Printf("\n")
|
||||
|
||||
if mod.Imports != nil {
|
||||
// Print the imports.
|
||||
fmt.Printf("%vimports [", indent+tab)
|
||||
if mod.Imports != nil && len(*mod.Imports) > 0 {
|
||||
fmt.Printf("\n")
|
||||
for _, imp := range *mod.Imports {
|
||||
fmt.Printf("%v\"%v\"\n", indent+tab+tab, imp.Tok)
|
||||
if mod.Imports != nil {
|
||||
// Print the imports.
|
||||
fmt.Printf("%vimports [", indent+tab)
|
||||
if mod.Imports != nil && len(*mod.Imports) > 0 {
|
||||
fmt.Printf("\n")
|
||||
for _, imp := range *mod.Imports {
|
||||
fmt.Printf("%v\"%v\"\n", indent+tab+tab, imp.Tok)
|
||||
}
|
||||
fmt.Printf("%v", indent+tab)
|
||||
}
|
||||
fmt.Printf("%v", indent+tab)
|
||||
fmt.Printf("]\n")
|
||||
}
|
||||
fmt.Printf("]\n")
|
||||
}
|
||||
|
||||
exports := make(map[tokens.Token]bool)
|
||||
if mod.Exports != nil {
|
||||
// Print the exports.
|
||||
fmt.Printf("%vexports [", indent+tab)
|
||||
if mod.Exports != nil && len(*mod.Exports) > 0 {
|
||||
fmt.Printf("\n")
|
||||
for _, exp := range ast.StableModuleExports(*mod.Exports) {
|
||||
ref := (*mod.Exports)[exp].Referent.Tok
|
||||
fmt.Printf("%v\"%v\" -> \"%v\"\n", indent+tab+tab, exp, ref)
|
||||
exports[ref] = true
|
||||
exports := make(map[tokens.Token]bool)
|
||||
if mod.Exports != nil {
|
||||
// Print the exports.
|
||||
fmt.Printf("%vexports [", indent+tab)
|
||||
if mod.Exports != nil && len(*mod.Exports) > 0 {
|
||||
fmt.Printf("\n")
|
||||
for _, exp := range ast.StableModuleExports(*mod.Exports) {
|
||||
ref := (*mod.Exports)[exp].Referent.Tok
|
||||
fmt.Printf("%v\"%v\" -> \"%v\"\n", indent+tab+tab, exp, ref)
|
||||
exports[ref] = true
|
||||
}
|
||||
fmt.Printf("%v", indent+tab)
|
||||
}
|
||||
fmt.Printf("%v", indent+tab)
|
||||
fmt.Printf("]\n")
|
||||
}
|
||||
fmt.Printf("]\n")
|
||||
}
|
||||
|
||||
if mod.Members != nil {
|
||||
// Print the members.
|
||||
for _, member := range ast.StableModuleMembers(*mod.Members) {
|
||||
memtok := tokens.NewModuleMemberToken(modtok, member)
|
||||
printModuleMember(memtok, (*mod.Members)[member], printExports, exports, indent+tab)
|
||||
if mod.Members != nil {
|
||||
// Print the members.
|
||||
for _, member := range ast.StableModuleMembers(*mod.Members) {
|
||||
memtok := tokens.NewModuleMemberToken(modtok, member)
|
||||
printModuleMember(memtok, (*mod.Members)[member], printExports, exports, indent+tab)
|
||||
}
|
||||
fmt.Printf("%v", indent)
|
||||
}
|
||||
fmt.Printf("%v", indent)
|
||||
}
|
||||
} else {
|
||||
// Print a "..." so that it's clear we're omitting information, versus the module being empty.
|
||||
fmt.Printf("...")
|
||||
}
|
||||
} else {
|
||||
// Print a "..." so that it's clear we're omitting information, versus the module being empty.
|
||||
fmt.Printf("...")
|
||||
fmt.Printf("}\n")
|
||||
}
|
||||
fmt.Printf("}\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -141,8 +141,8 @@ const LoadLocationExpressionKind NodeKind = "LoadLocationExpression"
|
|||
// LoadDynamicExpression dynamically loads either a variable or a function, by name, from an object or scope.
|
||||
type LoadDynamicExpression struct {
|
||||
loadExpressionNode
|
||||
Object *Expression `json:"object"` // the object from which to load the property.
|
||||
Name Expression `json:"name"` // the dynamically evaluated name of the property to load.
|
||||
Object *Expression `json:"object,omitempty"` // the object from which to load the property.
|
||||
Name Expression `json:"name"` // the dynamically evaluated name of the property to load.
|
||||
}
|
||||
|
||||
var _ Node = (*LoadDynamicExpression)(nil)
|
||||
|
|
|
@ -8,7 +8,9 @@ import (
|
|||
"github.com/golang/glog"
|
||||
|
||||
"github.com/pulumi/coconut/pkg/compiler/ast"
|
||||
"github.com/pulumi/coconut/pkg/compiler/errors"
|
||||
"github.com/pulumi/coconut/pkg/compiler/symbols"
|
||||
"github.com/pulumi/coconut/pkg/tokens"
|
||||
"github.com/pulumi/coconut/pkg/util/contract"
|
||||
)
|
||||
|
||||
|
@ -123,7 +125,10 @@ func (b *binder) bindModuleImports(module *symbols.Module) {
|
|||
// Now bind all imports to concrete symbols: these are simple token bindings.
|
||||
if module.Node.Imports != nil {
|
||||
for _, imptok := range *module.Node.Imports {
|
||||
if imp := b.ctx.LookupModule(imptok); imp != nil {
|
||||
if !tokens.Token(imptok.Tok).HasModule() {
|
||||
b.Diag().Errorf(errors.ErrorMalformedToken.At(imptok),
|
||||
"Module", imptok.Tok, "missing module part")
|
||||
} else if imp := b.ctx.LookupModule(imptok); imp != nil {
|
||||
module.Imports = append(module.Imports, imp)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -371,16 +371,8 @@ func (a *astBinder) checkLoadLocationExpression(node *ast.LoadLocationExpression
|
|||
}
|
||||
|
||||
func (a *astBinder) checkLoadDynamicExpression(node *ast.LoadDynamicExpression) {
|
||||
// Ensure the object is non-nil.
|
||||
if node.Object == nil {
|
||||
a.b.Diag().Errorf(errors.ErrorExpectedObject.At(node))
|
||||
}
|
||||
|
||||
// Now ensure that the right hand side is either a string or a dynamic.
|
||||
name := a.b.ctx.RequireType(node.Name)
|
||||
if name != types.Dynamic && !types.CanConvert(name, types.String) {
|
||||
|
||||
}
|
||||
// Ensure that the name is either a string or a dynamic.
|
||||
a.checkExprType(node.Name, types.String)
|
||||
|
||||
// No matter the outcome, a load dynamic always produces a dynamically typed thing.
|
||||
a.b.ctx.RegisterType(node, types.Dynamic)
|
||||
|
@ -456,6 +448,9 @@ func (a *astBinder) checkInvokeFunctionExpression(node *ast.InvokeFunctionExpres
|
|||
// The resulting type of this expression is the same as the function's return type. Note that if the return is
|
||||
// nil ("void"-returning), we still register it; a nil entry is distinctly different from a missing entry.
|
||||
a.b.ctx.RegisterType(node, funty.Return)
|
||||
} else if ty == types.Dynamic {
|
||||
// It's ok to invoke a dynamically typed object; we simply might fail at runtime.
|
||||
a.b.ctx.RegisterType(node, types.Dynamic)
|
||||
} else {
|
||||
a.b.Diag().Errorf(errors.ErrorCannotInvokeNonFunction.At(node), ty)
|
||||
a.b.ctx.RegisterType(node, types.Error)
|
||||
|
|
|
@ -5,38 +5,39 @@ package errors
|
|||
// Binder errors are in the [500-600) range.
|
||||
var (
|
||||
ErrorInvalidPackageName = newError(500, "The package name must be a valid identifier")
|
||||
ErrorMalformedPackageURL = newError(501, "Package URL '%v' is malformed: %v")
|
||||
ErrorImportNotFound = newError(502, "The imported package '%v' was not found; has it been installed?%v")
|
||||
ErrorTypeNotFound = newError(503, "Type '%v' could not be found: %v")
|
||||
ErrorSymbolNotFound = newError(504, "Symbol '%v' could not be found: %v")
|
||||
ErrorSymbolAlreadyExists = newError(505, "A symbol already exists with the name '%v'")
|
||||
ErrorIncorrectExprType = newError(506, "Expression has the wrong type; expected '%v', got '%v'")
|
||||
ErrorMemberNotAccessible = newError(507, "Member '%v' is not accessible (it is %v)")
|
||||
ErrorExpectedReturnExpr = newError(508, "Expected a return expression of type '%v'")
|
||||
ErrorUnexpectedReturnExpr = newError(509, "Unexpected return expression; function has no return type (void)")
|
||||
ErrorDuplicateLabel = newError(510, "Duplicate label '%v': %v")
|
||||
ErrorUnknownJumpLabel = newError(511, "Unknown label '%v' used in the %v statement")
|
||||
ErrorCannotNewAbstractClass = newError(512, "Cannot `new` an abstract class '%v'")
|
||||
ErrorIllegalObjectLiteralType = newError(513,
|
||||
ErrorMalformedToken = newError(501, "%v token '%v' is malformed: %v")
|
||||
ErrorMalformedPackageURL = newError(502, "Package URL '%v' is malformed: %v")
|
||||
ErrorImportNotFound = newError(503, "The imported package '%v' was not found; has it been installed?%v")
|
||||
ErrorTypeNotFound = newError(504, "Type '%v' could not be found: %v")
|
||||
ErrorSymbolNotFound = newError(505, "Symbol '%v' could not be found: %v")
|
||||
ErrorSymbolAlreadyExists = newError(506, "A symbol already exists with the name '%v'")
|
||||
ErrorIncorrectExprType = newError(507, "Expression has the wrong type; expected '%v', got '%v'")
|
||||
ErrorMemberNotAccessible = newError(508, "Member '%v' is not accessible (it is %v)")
|
||||
ErrorExpectedReturnExpr = newError(509, "Expected a return expression of type '%v'")
|
||||
ErrorUnexpectedReturnExpr = newError(510, "Unexpected return expression; function has no return type (void)")
|
||||
ErrorDuplicateLabel = newError(511, "Duplicate label '%v': %v")
|
||||
ErrorUnknownJumpLabel = newError(512, "Unknown label '%v' used in the %v statement")
|
||||
ErrorCannotNewAbstractClass = newError(513, "Cannot `new` an abstract class '%v'")
|
||||
ErrorIllegalObjectLiteralType = newError(514,
|
||||
"The type '%v' may not be used as an object literal type; only records and interfaces are permitted")
|
||||
ErrorMissingRequiredProperty = newError(514, "Missing required property '%v'")
|
||||
ErrorUnaryOperatorInvalidForType = newError(515,
|
||||
ErrorMissingRequiredProperty = newError(515, "Missing required property '%v'")
|
||||
ErrorUnaryOperatorInvalidForType = newError(516,
|
||||
"The operator %v is invalid on operand type '%v'; expected '%v'")
|
||||
ErrorUnaryOperatorInvalidForOperand = newError(516, "The operator %v is invalid on operand %v; expected %v")
|
||||
ErrorUnaryOperatorMustBePrefix = newError(517, "The operator %v must be in a prefix position (not postfix)")
|
||||
ErrorBinaryOperatorInvalidForType = newError(518,
|
||||
ErrorUnaryOperatorInvalidForOperand = newError(517, "The operator %v is invalid on operand %v; expected %v")
|
||||
ErrorUnaryOperatorMustBePrefix = newError(518, "The operator %v must be in a prefix position (not postfix)")
|
||||
ErrorBinaryOperatorInvalidForType = newError(519,
|
||||
"The operator %v is invalid on %v operand type '%v'; expected '%v'")
|
||||
ErrorIllegalAssignmentLValue = newError(519, "Cannot assign to the target LHS expression (not an l-value)")
|
||||
ErrorIllegalNumericAssignmentLValue = newError(520, "Cannot perform numeric assignment %v on a non-numeric LHS")
|
||||
ErrorIllegalAssignmentTypes = newError(521, "Cannot assign a value of type '%v' to target of type '%v'")
|
||||
ErrorCannotInvokeNonFunction = newError(522, "Cannot invoke a non-function; type '%v' is not a function")
|
||||
ErrorArgumentCountMismatch = newError(523, "Function expects %v arguments; got %v instead")
|
||||
ErrorConstructorReturnType = newError(524, "Constructor '%v' has a return type '%v'; should be nil (void)")
|
||||
ErrorConstructorNotMethod = newError(525, "Constructor '%v' is not a method; got %v instead")
|
||||
ErrorExpectedObject = newError(526,
|
||||
ErrorIllegalAssignmentLValue = newError(520, "Cannot assign to the target LHS expression (not an l-value)")
|
||||
ErrorIllegalNumericAssignmentLValue = newError(521, "Cannot perform numeric assignment %v on a non-numeric LHS")
|
||||
ErrorIllegalAssignmentTypes = newError(522, "Cannot assign a value of type '%v' to target of type '%v'")
|
||||
ErrorCannotInvokeNonFunction = newError(523, "Cannot invoke a non-function; type '%v' is not a function")
|
||||
ErrorArgumentCountMismatch = newError(524, "Function expects %v arguments; got %v instead")
|
||||
ErrorConstructorReturnType = newError(525, "Constructor '%v' has a return type '%v'; should be nil (void)")
|
||||
ErrorConstructorNotMethod = newError(526, "Constructor '%v' is not a method; got %v instead")
|
||||
ErrorExpectedObject = newError(527,
|
||||
"Expected an object target for this instance member load operation")
|
||||
ErrorUnexpectedObject = newError(527,
|
||||
ErrorUnexpectedObject = newError(528,
|
||||
"Unexpected object target for this static or module load operation")
|
||||
ErrorInvalidCast = newError(528, "Illegal cast from '%v' to '%v'; this can never succeed")
|
||||
ErrorModuleAliasTargetNotFound = newError(529, "Module alias target '%v' was not found (from '%v')")
|
||||
ErrorInvalidCast = newError(529, "Illegal cast from '%v' to '%v'; this can never succeed")
|
||||
ErrorModuleAliasTargetNotFound = newError(530, "Module alias target '%v' was not found (from '%v')")
|
||||
)
|
||||
|
|
|
@ -169,6 +169,7 @@ func (node *ClassMethod) Special() bool {
|
|||
nm := node.MemberName()
|
||||
return nm == tokens.ClassInitFunction || nm == tokens.ClassConstructorFunction
|
||||
}
|
||||
func (node *ClassMethod) SpecialModInit() bool { return false }
|
||||
func (node *ClassMethod) Tree() diag.Diagable { return node.Node }
|
||||
func (node *ClassMethod) Optional() bool { return false }
|
||||
func (node *ClassMethod) Static() bool { return node.Node.Static != nil && *node.Node.Static }
|
||||
|
|
|
@ -11,6 +11,7 @@ type Function interface {
|
|||
Symbol
|
||||
Function() ast.Function
|
||||
Signature() *FunctionType
|
||||
SpecialModInit() bool // true if this is a module initializer.
|
||||
}
|
||||
|
||||
var _ Symbol = (Function)(nil)
|
||||
|
|
|
@ -171,7 +171,8 @@ func (node *ModuleMethod) Token() tokens.Token {
|
|||
),
|
||||
)
|
||||
}
|
||||
func (node *ModuleMethod) Special() bool { return node.MemberName() == tokens.ModuleInitFunction }
|
||||
func (node *ModuleMethod) Special() bool { return node.SpecialModInit() }
|
||||
func (node *ModuleMethod) SpecialModInit() bool { return node.MemberName() == tokens.ModuleInitFunction }
|
||||
func (node *ModuleMethod) Tree() diag.Diagable { return node.Node }
|
||||
func (node *ModuleMethod) MemberNode() ast.ModuleMember { return node.Node }
|
||||
func (node *ModuleMethod) MemberName() tokens.ModuleMemberName {
|
||||
|
|
|
@ -78,7 +78,7 @@ type evaluator struct {
|
|||
statics staticMap // the object values for all static variable symbols.
|
||||
protos prototypeMap // the current "prototypes" for all classes.
|
||||
stack *rt.StackFrame // a stack of frames to keep track of calls.
|
||||
locals *localScope // local variable values scoped by the lexical structure.
|
||||
locals *localScope // variable values corresponding to the current lexical scope.
|
||||
modinits modinitMap // a map of which modules have been initialized already.
|
||||
classinits classinitMap // a map of which classes have been initialized already.
|
||||
}
|
||||
|
@ -503,6 +503,18 @@ func (e *evaluator) issueUnhandledException(uw *rt.Unwind, err *diag.Diag, args
|
|||
e.Diag().Errorf(err, args...)
|
||||
}
|
||||
|
||||
// pushModuleScope establishes a new module-wide scope. It returns a function that restores the prior value.
|
||||
func (e *evaluator) pushModuleScope(m *symbols.Module) func() {
|
||||
return e.ctx.PushModule(m)
|
||||
}
|
||||
|
||||
// pushClassScope establishes a new class-wide scope. This also establishes the right module context. If the object
|
||||
// argument is non-nil, instance methods are also populated. It returns a function that restores the prior value.
|
||||
func (e *evaluator) pushClassScope(c *symbols.Class, obj *rt.Object) func() {
|
||||
contract.Assert(obj == nil || obj.Type() == c)
|
||||
return e.ctx.PushClass(c)
|
||||
}
|
||||
|
||||
// pushScope pushes a new local and context scope. The frame argument indicates whether this is an activation frame,
|
||||
// meaning that searches for local variables will not probe into parent scopes (since they are inaccessible).
|
||||
func (e *evaluator) pushScope(frame *rt.StackFrame) {
|
||||
|
@ -550,13 +562,13 @@ func (e *evaluator) evalCall(node diag.Diagable, fnc symbols.Function,
|
|||
// Ensure that we enter the right module/class context, otherwise module-sensitive binding won't work.
|
||||
switch f := fnc.(type) {
|
||||
case *symbols.ClassMethod:
|
||||
popm := e.ctx.PushModule(f.Parent.Parent)
|
||||
defer popm()
|
||||
popc := e.ctx.PushClass(f.Parent)
|
||||
defer popc()
|
||||
popModule := e.pushModuleScope(f.Parent.Parent)
|
||||
defer popModule()
|
||||
popClass := e.pushClassScope(f.Parent, this)
|
||||
defer popClass()
|
||||
case *symbols.ModuleMethod:
|
||||
popm := e.ctx.PushModule(f.Parent)
|
||||
defer popm()
|
||||
popModule := e.pushModuleScope(f.Parent)
|
||||
defer popModule()
|
||||
default:
|
||||
contract.Failf("Unrecognized function type during call: %v", reflect.TypeOf(fnc))
|
||||
}
|
||||
|
@ -1236,6 +1248,7 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
|
|||
tok := node.Name.Tok
|
||||
if tok.Simple() {
|
||||
// If there is no object and the name is simple, it refers to a local variable in the current scope.
|
||||
// For more "sophisticated" lookups, in the hierarchy of scopes, a load dynamic operation must be utilized.
|
||||
contract.Assert(this == nil)
|
||||
loc := e.ctx.Scope.Lookup(tok.Name())
|
||||
contract.Assert(loc != nil)
|
||||
|
@ -1347,19 +1360,24 @@ func (e *evaluator) evalLoadDynamic(node *ast.LoadDynamicExpression, lval bool)
|
|||
|
||||
// Evaluate the object and then the property expression.
|
||||
var this *rt.Object
|
||||
if this, uw = e.evalExpression(node.Object); uw != nil {
|
||||
return nil, uw
|
||||
var thisexpr ast.Expression
|
||||
if node.Object != nil {
|
||||
thisexpr = *node.Object
|
||||
if this, uw = e.evalExpression(thisexpr); uw != nil {
|
||||
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 nil, uw
|
||||
}
|
||||
|
||||
}
|
||||
var name *rt.Object
|
||||
if name, uw = e.evalExpression(node.Name); uw != nil {
|
||||
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 nil, uw
|
||||
}
|
||||
|
||||
// Now go ahead and search the object for a property with the given name.
|
||||
var pv *rt.Pointer
|
||||
var key tokens.Name
|
||||
|
@ -1368,8 +1386,8 @@ func (e *evaluator) evalLoadDynamic(node *ast.LoadDynamicExpression, lval bool)
|
|||
contract.Assertf(isarr, "Expected an array for numeric dynamic load index")
|
||||
ix := int(name.NumberValue())
|
||||
arrv := this.ArrayValue()
|
||||
// TODO[pulumi/coconut#70]: Although storing arrays as arrays is fine for many circumstances, there are two cases
|
||||
// particular that could cause us troubles with ECMAScript compliance. First, negative indices are fine in
|
||||
// TODO[pulumi/coconut#70]: Although storing arrays as arrays is fine for many circumstances, there are two
|
||||
// situations that could cause us troubles with ECMAScript compliance. First, negative indices are fine in
|
||||
// ECMAScript. Second, sparse arrays can be represented more efficiently as a "bag of properties" than as a
|
||||
// true array that needs to be resized (possibly growing to become enormous in memory usage).
|
||||
// TODO[pulumi/coconut#70]: We are emulating "ECMAScript-like" array accesses, where -- just like ordinary
|
||||
|
@ -1391,7 +1409,34 @@ func (e *evaluator) evalLoadDynamic(node *ast.LoadDynamicExpression, lval bool)
|
|||
} else {
|
||||
contract.Assertf(name.Type() == types.String, "Expected dynamic load name to be a string")
|
||||
key = tokens.Name(name.StringValue())
|
||||
pv = e.getObjectOrSuperProperty(this, node.Object, rt.PropertyKey(key), false, lval)
|
||||
if thisexpr == nil {
|
||||
// If there's no object, look in the current localsment.
|
||||
pkey := rt.PropertyKey(key)
|
||||
globals := e.getModuleGlobals(e.ctx.Currmodule)
|
||||
if loc := e.ctx.Scope.Lookup(key); loc != nil {
|
||||
pv = e.locals.GetValueAddr(loc, true) // create a slot, we know the declaration exists.
|
||||
} else {
|
||||
// If it didn't exist in the lexical scope, check the module's globals.
|
||||
pv = globals.GetAddr(pkey, false) // look for a global by this name, but don't allocate one.
|
||||
}
|
||||
|
||||
// Finally, if neither of those existed, and this is the target of a load, allocate a slot.
|
||||
if pv == nil && lval {
|
||||
if e.fnc.SpecialModInit() {
|
||||
pv = globals.GetAddr(pkey, true)
|
||||
} else {
|
||||
loc := symbols.NewSpecialVariableSym(key, types.Dynamic)
|
||||
pv = e.locals.GetValueAddr(loc, true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pv = e.getObjectOrSuperProperty(this, thisexpr, rt.PropertyKey(key), lval, lval)
|
||||
}
|
||||
|
||||
if pv == nil {
|
||||
// If the result is nil, then the name is not defined. Raise an error.
|
||||
return nil, e.NewNameNotDefinedException(node, key)
|
||||
}
|
||||
}
|
||||
|
||||
// If this isn't for an l-value, return the raw object. Otherwise, make sure it's not readonly, and return it.
|
||||
|
@ -1410,7 +1455,7 @@ func (e *evaluator) evalLoadDynamic(node *ast.LoadDynamicExpression, lval bool)
|
|||
return &Location{
|
||||
e: e,
|
||||
This: this,
|
||||
Name: tokens.Name(key),
|
||||
Name: key,
|
||||
Lval: lval,
|
||||
Obj: obj,
|
||||
}, nil
|
||||
|
@ -1470,11 +1515,15 @@ func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpress
|
|||
|
||||
// Ensure that this actually led to a function; this is guaranteed by the binder.
|
||||
var fnc rt.FuncStub
|
||||
switch fncobj.Type().(type) {
|
||||
switch t := fncobj.Type().(type) {
|
||||
case *symbols.FunctionType:
|
||||
fnc = fncobj.FunctionValue()
|
||||
contract.Assert(fnc.Func != nil)
|
||||
default:
|
||||
if e.ctx.RequireType(node.Function) == types.Dynamic {
|
||||
// If a dynamic expression, raise an exception rather than asserting (else the IL was malformed).
|
||||
return nil, e.NewIllegalInvokeTargetException(node.Function, t)
|
||||
}
|
||||
contract.Failf("Expected function expression to yield a function type")
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/pulumi/coconut/pkg/compiler/symbols"
|
||||
"github.com/pulumi/coconut/pkg/diag"
|
||||
"github.com/pulumi/coconut/pkg/eval/rt"
|
||||
"github.com/pulumi/coconut/pkg/tokens"
|
||||
"github.com/pulumi/coconut/pkg/util/contract"
|
||||
)
|
||||
|
||||
|
@ -21,6 +22,10 @@ func (e *evaluator) NewNullObjectException(node diag.Diagable) *rt.Unwind {
|
|||
return e.NewException(node, "Target object is null")
|
||||
}
|
||||
|
||||
func (e *evaluator) NewNameNotDefinedException(node diag.Diagable, name tokens.Name) *rt.Unwind {
|
||||
return e.NewException(node, "Name '%v' is not defined", name)
|
||||
}
|
||||
|
||||
func (e *evaluator) NewNegativeArrayLengthException(node diag.Diagable) *rt.Unwind {
|
||||
return e.NewException(node, "Invalid array size (must be >= 0)")
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ func (node *Intrinsic) Symbol() {}
|
|||
func (node *Intrinsic) Name() tokens.Name { return node.Nm }
|
||||
func (node *Intrinsic) Token() tokens.Token { return node.Tok }
|
||||
func (node *Intrinsic) Special() bool { return false }
|
||||
func (node *Intrinsic) SpecialModInit() bool { return false }
|
||||
func (node *Intrinsic) Tree() diag.Diagable { return node.Func }
|
||||
func (node *Intrinsic) Function() ast.Function { return node.Func }
|
||||
func (node *Intrinsic) Signature() *symbols.FunctionType { return node.Sig }
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/pulumi/coconut/pkg/util/contract"
|
||||
)
|
||||
|
||||
// localScope is a kind of scope that holds local variable values.
|
||||
// localScope holds variable values that correspond to a specific lexical scope.
|
||||
type localScope struct {
|
||||
Slot **localScope
|
||||
Parent *localScope // the parent to restore when popping this scope.
|
||||
|
@ -19,7 +19,7 @@ type localScope struct {
|
|||
Values valueMap // the values map contains the value for a variable so long as it exists.
|
||||
}
|
||||
|
||||
// valueMap maps local variables to their current known object value (if any).
|
||||
// valueMap maps variables to their current known object value in this scope (if any).
|
||||
type valueMap map[*symbols.LocalVariable]*rt.Pointer
|
||||
|
||||
func newLocalScope(slot **localScope, frame bool, lex *binder.Scope) *localScope {
|
||||
|
@ -59,7 +59,7 @@ func (s *localScope) GetValueAddr(sym *symbols.LocalVariable, init bool) *rt.Poi
|
|||
return s.lookupValueAddr(sym, nil, init)
|
||||
}
|
||||
|
||||
// InitValue registers a reference for a local variable, and asserts that none previously existed.
|
||||
// InitValue registers a reference for a variable, and asserts that none previously existed.
|
||||
func (s *localScope) InitValueAddr(sym *symbols.LocalVariable, ref *rt.Pointer) {
|
||||
s.lookupValueAddr(sym, ref, false)
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ func (s *localScope) InitValueAddr(sym *symbols.LocalVariable, ref *rt.Pointer)
|
|||
// lookupValueAddr is used to lookup and initialize references using a single, shared routine.
|
||||
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
|
||||
// the actual 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.
|
||||
lex := s.Lexical
|
||||
outer:
|
||||
|
|
|
@ -98,6 +98,9 @@ class Transformer:
|
|||
mod = self.transform_Module(module.name, module.py_module)
|
||||
assert module.name not in self.pkg.modules, "Module {} already exists".format(module.name)
|
||||
self.pkg.modules[module.name] = mod
|
||||
if len(modules) > 0:
|
||||
# Mark the first module supplied as the default.
|
||||
self.pkg.aliases[tokens.mod_default] = modules[0].name
|
||||
finally:
|
||||
self.ctx = oldctx
|
||||
return self.pkg
|
||||
|
@ -334,6 +337,18 @@ class Transformer:
|
|||
for namenode in node.names:
|
||||
# Python module names are dot-delimited; we need to translate into "/" delimited names.
|
||||
name = namenode.name.replace(".", tokens.name_delim)
|
||||
|
||||
# Now transform the module name into a qualified package/module token.
|
||||
# TODO: this heuristic isn't perfect; I think we should load up the target package and read its manifest
|
||||
# to figure out the precise package naming, etc. (since packages can be multi-part too).
|
||||
delimix = name.find(tokens.name_delim)
|
||||
if delimix == -1:
|
||||
# If just the package, we will use the default module.
|
||||
name = name + tokens.delim + tokens.mod_default
|
||||
else:
|
||||
# Otherwise, use the first part as the package, and the remainder as the module.
|
||||
name = name[:delimix] + tokens.delim + name[delimix+1:]
|
||||
|
||||
imports.add(ast.ModuleToken(name, loc=self.loc_from(namenode)))
|
||||
return imports
|
||||
|
||||
|
|
Loading…
Reference in a new issue