Switch to imports as statements

The old model for imports was to use top-level declarations on the
enclosing module itself.  This was a laudible attempt to simplify
matters, but just doesn't work.

For one, the order of initialization doesn't precisely correspond
to the imports as they appear in the source code.  This could incur
some weird module initialization problems that lead to differing
behavior between a language and its Coconut variant.

But more pressing as we work on CocoPy support, it doesn't give
us an opportunity to dynamically bind names in a correct way.  For
example, "import aws" now needs to actually translate into a variable
declaration and assignment of sorts.  Furthermore, that variable name
should be visible in the environment block in which it occurs.

This change switches imports to act like statements.  For the most
part this doesn't change much compared to the old model.  The common
pattern of declaring imports at the top of a file will translate to
the imports happening at the top of the module's initializer.  This
has the effect of initializing the transitive closure just as it
happened previously.  But it enables alternative models, like imports
inside of functions, and -- per the above -- dynamic name binding.
This commit is contained in:
joeduffy 2017-04-08 18:16:10 -07:00
parent 45a16b725f
commit e96d4018ae
43 changed files with 351 additions and 339 deletions

View file

@ -188,22 +188,9 @@ func printModules(pkg *pack.Package, printSymbols bool, printExports bool, print
// Now, if requested, print the tokens.
if printSymbols || printExports {
if mod.Imports != nil || mod.Members != nil {
if mod.Exports != 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)
}
fmt.Printf("%v", indent+tab)
}
fmt.Printf("]\n")
}
exports := make(map[tokens.Token]bool)
if mod.Exports != nil {
// Print the exports.

View file

@ -31,9 +31,8 @@ func (node *DefinitionNode) GetDescription() *string { return node.Description }
// Module contains members, including variables, functions, and/or classes.
type Module struct {
DefinitionNode
Imports *[]*ModuleToken `json:"imports,omitempty"` // the imported modules consumed by this module.
Exports *ModuleExports `json:"exports,omitempty"` // the exported symbols available for use by consuming modules.
Members *ModuleMembers `json:"members,omitempty"` // the inner members of this module, private for its own use.
Exports *ModuleExports `json:"exports,omitempty"` // the exported symbols available for use by consuming modules.
Members *ModuleMembers `json:"members,omitempty"` // the inner members of this module, private for its own use.
}
var _ Node = (*Module)(nil)

View file

@ -14,6 +14,21 @@ type StatementNode struct {
func (node *StatementNode) statement() {}
/* Imports */
// Import is mostly used to trigger the side-effect of importing another module. It is also used to make bound modules
// or module member names available in the context of the importing module or function.
type Import struct {
StatementNode
Referent *Token `json:"referent"` // the module or member token to import.
Name *Identifier `json:"identifier,omitempty"` // the name that token is bound to (if any).
}
var _ Node = (*Import)(nil)
var _ Statement = (*Import)(nil)
const ImportKind NodeKind = "Import"
/* Blocks */
type Block struct {

View file

@ -79,6 +79,11 @@ func Walk(v Visitor, node Node) {
// No children, nothing to do.
// Statements
case *Import:
Walk(v, n.Referent)
if n.Name != nil {
Walk(v, n.Name)
}
case *Block:
for _, stmt := range n.Statements {
Walk(v, stmt)

View file

@ -8,9 +8,7 @@ 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"
)
@ -112,29 +110,10 @@ func (b *binder) bindModuleExports(module *symbols.Module) {
}
func (b *binder) bindModuleDefinitions(module *symbols.Module) {
// Now we can bind module imports.
b.bindModuleImports(module)
// And finish binding the members themselves.
b.bindModuleMemberDefinitions(module)
}
// bindModuleImports binds module import tokens to their symbols. This is done as a second pass just in case there are
// inter-module dependencies.
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 !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)
}
}
}
}
// bindModuleMemberDefinitions finishes binding module members, by doing lookups sensitive to the definition pass.
func (b *binder) bindModuleMemberDefinitions(module *symbols.Module) {
glog.V(3).Infof("Binding module '%v' member defns", module.Token())

View file

@ -118,10 +118,12 @@ func (b *binder) resolveDep(dep pack.PackageURL) *symbols.ResolvedPackage {
b.Diag().Errorf(errors.ErrorCouldNotReadPackage.AtFile(loc), err)
return nil
}
pkg := b.reader.ReadPackage(doc)
// Now perform the binding and return it.
return b.resolveBindPackage(pkg, dep)
if pkg := b.reader.ReadPackage(doc); pkg != nil {
// Now perform the binding and return it.
return b.resolveBindPackage(pkg, dep)
}
contract.Assert(!b.Diag().Success())
return nil
}
}

View file

@ -136,6 +136,17 @@ func (a *astBinder) isLValue(expr ast.Expression) bool {
// Statements
func (a *astBinder) checkImport(node *ast.Import) {
imptok := node.Referent
if !tokens.Token(imptok.Tok).HasModule() {
a.b.Diag().Errorf(errors.ErrorMalformedToken.At(imptok),
"Module", imptok.Tok, "missing module part")
} else {
// Just perform a lookup to ensure the symbol exists (and error out if not).
a.b.ctx.LookupSymbol(imptok, imptok.Tok, true)
}
}
func (a *astBinder) visitBlock(node *ast.Block) {
// Entering a new block requires a fresh lexical scope.
a.b.ctx.Scope.Push(false)

View file

@ -13,7 +13,6 @@ type Module struct {
Node *ast.Module
Parent *Package
Tok tokens.Module
Imports Modules
Exports ModuleExportMap
Members ModuleMemberMap
}
@ -43,7 +42,6 @@ func NewModuleSym(node *ast.Module, parent *Package) *Module {
Node: node,
Parent: parent,
Tok: tokens.NewModuleToken(parent.Tok, tokens.ModuleName(node.Name.Ident)),
Imports: make(Modules, 0),
Exports: make(ModuleExportMap),
Members: make(ModuleMemberMap),
}

View file

@ -18,6 +18,10 @@ func decodeStatement(m mapper.Mapper, tree mapper.Object) (ast.Statement, error)
if k != nil {
kind := ast.NodeKind(*k)
switch kind {
// Imports
case ast.ImportKind:
return decodeImport(m, tree)
// Blocks
case ast.BlockKind:
return decodeBlock(m, tree)
@ -65,6 +69,14 @@ func decodeStatement(m mapper.Mapper, tree mapper.Object) (ast.Statement, error)
return nil, nil
}
func decodeImport(m mapper.Mapper, tree mapper.Object) (*ast.Import, error) {
var imp ast.Import
if err := m.Decode(tree, &imp); err != nil {
return nil, err
}
return &imp, nil
}
func decodeBlock(m mapper.Mapper, tree mapper.Object) (*ast.Block, error) {
var block ast.Block
if err := m.Decode(tree, &block); err != nil {

View file

@ -348,11 +348,6 @@ func (e *evaluator) ensureModuleInit(mod *symbols.Module) {
e.modinits[mod] = true // set true before running, in case of cycles.
if !already {
// First ensure all imported module initializers are run, in the order in which they were given.
for _, imp := range mod.Imports {
e.ensureModuleInit(imp)
}
// Populate all properties in this module, even if they will be empty for now.
var readonlines []*rt.Pointer
globals := e.getModuleGlobals(mod)
@ -665,6 +660,8 @@ func (e *evaluator) evalStatement(node ast.Statement) *rt.Unwind {
// Simply switch on the node type and dispatch to the specific function, returning the rt.Unwind info.
switch n := node.(type) {
case *ast.Import:
return e.evalImport(n)
case *ast.Block:
return e.evalBlock(n)
case *ast.LocalVariableDeclaration:
@ -701,6 +698,23 @@ func (e *evaluator) evalStatement(node ast.Statement) *rt.Unwind {
}
}
func (e *evaluator) evalImport(node *ast.Import) *rt.Unwind {
// Ensure the target module has been initialized.
contract.Assertf(node.Name == nil, "Dynamically bound import names not yet supported")
imptok := node.Referent
sym := e.ctx.LookupSymbol(imptok, imptok.Tok, true)
contract.Assert(sym != nil)
switch s := sym.(type) {
case *symbols.Module:
e.ensureModuleInit(s)
case symbols.ModuleMember:
e.ensureModuleInit(s.MemberParent())
default:
contract.Failf("Unrecognized import symbol: %v", reflect.TypeOf(sym))
}
return nil
}
func (e *evaluator) evalBlock(node *ast.Block) *rt.Unwind {
// Push a scope at the start, and pop it at afterwards; both for the symbol context and local variable values.
e.pushScope(nil)

View file

@ -19,7 +19,6 @@ export interface Definition extends Node {
// A module contains members, including variables, functions, and/or classes.
export interface Module extends Definition {
kind: ModuleKind;
imports?: ModuleToken[]; // an ordered list of import modules to initialize.
exports?: ModuleExports; // a list of exported members, keyed by name.
members?: ModuleMembers; // a list of members, keyed by their simple name.
}

View file

@ -33,6 +33,7 @@ export type NodeKind =
definitions.ClassMethodKind |
definitions.ModuleMethodKind |
statements.ImportKind |
statements.BlockKind |
statements.LocalVariableDeclarationKind |
statements.TryCatchFinallyKind |

View file

@ -2,10 +2,20 @@
import {LocalVariable} from "./definitions";
import {Expression} from "./expressions";
import {Identifier, Node} from "./nodes";
import {Identifier, Node, Token} from "./nodes";
export interface Statement extends Node {}
/** Imports **/
export interface Import extends Statement {
kind: ImportKind;
referent: Token;
name?: Identifier;
}
export const importKind = "Import";
export type ImportKind = "Import";
/** Blocks **/
export interface Block extends Statement {

View file

@ -210,7 +210,6 @@ export class Transformer {
private currentModuleMembers: ast.ModuleMembers | undefined;
private currentModuleExports: ast.ModuleExports | undefined;
private currentModuleImports: Map<string, ModuleReference>;
private currentModuleImportTokens: ast.ModuleToken[];
private currentClassToken: tokens.TypeToken | undefined;
private currentSuperClassToken: tokens.TypeToken | undefined;
private currentPackageDependencies: Set<tokens.PackageToken>;
@ -1019,7 +1018,6 @@ export class Transformer {
let priorModuleMembers: ast.ModuleMembers | undefined = this.currentModuleMembers;
let priorModuleExports: ast.ModuleExports | undefined = this.currentModuleExports;
let priorModuleImports: Map<string, ModuleReference> | undefined = this.currentModuleImports;
let priorModuleImportTokens: ast.ModuleToken[] | undefined = this.currentModuleImportTokens;
let priorTempLocalCounter: number = this.currentTempLocalCounter;
try {
// Prepare self-referential module information.
@ -1034,7 +1032,6 @@ export class Transformer {
this.currentModuleMembers = {};
this.currentModuleExports = {};
this.currentModuleImports = new Map<string, ModuleReference>();
this.currentModuleImportTokens = []; // to track the imports, in order.
this.currentTempLocalCounter = 0;
// Any top-level non-definition statements will pile up into the module initializer.
@ -1115,7 +1112,6 @@ export class Transformer {
return this.withLocation(node, <ast.Module>{
kind: ast.moduleKind,
name: ident(this.getModuleName(modtok)),
imports: this.currentModuleImportTokens,
exports: this.currentModuleExports,
members: this.currentModuleMembers,
});
@ -1126,7 +1122,6 @@ export class Transformer {
this.currentModuleMembers = priorModuleMembers;
this.currentModuleExports = priorModuleExports;
this.currentModuleImports = priorModuleImports;
this.currentModuleImportTokens = priorModuleImportTokens;
this.currentTempLocalCounter = priorTempLocalCounter;
}
}
@ -1145,8 +1140,6 @@ export class Transformer {
return [ this.transformExportAssignment(<ts.ExportAssignment>node) ];
case ts.SyntaxKind.ExportDeclaration:
return this.transformExportDeclaration(<ts.ExportDeclaration>node);
case ts.SyntaxKind.ImportDeclaration:
return [ await this.transformImportDeclaration(<ts.ImportDeclaration>node) ];
// Handle declarations; each of these results in a definition.
case ts.SyntaxKind.ClassDeclaration:
@ -1320,7 +1313,7 @@ export class Transformer {
return exports;
}
private async transformImportDeclaration(node: ts.ImportDeclaration): Promise<ModuleElement> {
private async transformImportDeclaration(node: ts.ImportDeclaration): Promise<ast.Statement> {
// An import declaration is erased in the output AST, however, we must keep track of the set of known import
// names so that we can easily look them up by name later on (e.g., in the case of reexporting whole modules).
if (node.importClause) {
@ -1329,10 +1322,6 @@ export class Transformer {
contract.assert(node.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral);
let importModule: ModuleReference =
this.resolveModuleReferenceByName((<ts.StringLiteral>node.moduleSpecifier).text);
let importModuleToken: ast.ModuleToken = this.withLocation(node.moduleSpecifier, <ast.ModuleToken>{
kind: ast.moduleTokenKind,
tok: await this.createModuleToken(importModule),
});
// Figure out what kind of import statement this is (there are many, see below).
let name: ts.Identifier | undefined;
@ -1384,8 +1373,15 @@ export class Transformer {
}
// Now keep track of the import.
this.currentModuleImportTokens.push(importModuleToken);
return <ast.Import>{
kind: ast.importKind,
referent: this.withLocation(node.moduleSpecifier, <ast.Token>{
kind: ast.tokenKind,
tok: await this.createModuleToken(importModule),
}),
};
}
return <ast.EmptyStatement>{ kind: ast.emptyStatementKind };
}
@ -1436,6 +1432,8 @@ export class Transformer {
return this.transformWhileStatement(<ts.WhileStatement>node);
// Miscellaneous statements:
case ts.SyntaxKind.ImportDeclaration:
return this.transformImportDeclaration(<ts.ImportDeclaration>node);
case ts.SyntaxKind.Block:
return this.transformBlock(<ts.Block>node);
case ts.SyntaxKind.DebuggerStatement:

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"a1": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"a": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
".main": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
".main": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"loops": {
@ -348,11 +347,11 @@
"file": "index.ts",
"start": {
"line": 9,
"column": 14
"column": 15
},
"end": {
"line": 9,
"column": 16
"column": 17
}
}
},
@ -364,7 +363,7 @@
},
"end": {
"line": 9,
"column": 16
"column": 17
}
}
},
@ -406,7 +405,7 @@
"file": "index.ts",
"start": {
"line": 9,
"column": 18
"column": 19
},
"end": {
"line": 11,
@ -844,11 +843,11 @@
"file": "index.ts",
"start": {
"line": 15,
"column": 18
"column": 19
},
"end": {
"line": 15,
"column": 19
"column": 20
}
}
},
@ -860,7 +859,7 @@
},
"end": {
"line": 15,
"column": 19
"column": 20
}
}
},
@ -946,7 +945,7 @@
"file": "index.ts",
"start": {
"line": 15,
"column": 21
"column": 22
},
"end": {
"line": 17,
@ -1461,11 +1460,11 @@
"file": "index.ts",
"start": {
"line": 29,
"column": 10
"column": 11
},
"end": {
"line": 29,
"column": 12
"column": 13
}
}
},
@ -1477,7 +1476,7 @@
},
"end": {
"line": 29,
"column": 12
"column": 13
}
}
},
@ -1519,7 +1518,7 @@
"file": "index.ts",
"start": {
"line": 29,
"column": 14
"column": 15
},
"end": {
"line": 31,
@ -1905,11 +1904,11 @@
"file": "index.ts",
"start": {
"line": 35,
"column": 14
"column": 15
},
"end": {
"line": 35,
"column": 15
"column": 16
}
}
},
@ -1921,7 +1920,7 @@
},
"end": {
"line": 35,
"column": 15
"column": 16
}
}
},
@ -2007,7 +2006,7 @@
"file": "index.ts",
"start": {
"line": 35,
"column": 17
"column": 18
},
"end": {
"line": 37,

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"C": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"au": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"modprop": {

View file

@ -10,7 +10,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"B": {

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"C": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"C": {
"kind": "Export",

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"default": {
"kind": "Export",

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "other"
},
"imports": [],
"exports": {
"D": {
"kind": "Export",
@ -354,23 +353,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [
{
"kind": "ModuleToken",
"tok": "export:other",
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 24
},
"end": {
"line": 2,
"column": 33
}
}
}
],
"exports": {
"other": {
"kind": "Export",
@ -654,6 +636,24 @@
"body": {
"kind": "Block",
"statements": [
{
"kind": "Import",
"referent": {
"kind": "Token",
"tok": "export:other",
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 24
},
"end": {
"line": 2,
"column": 33
}
}
}
},
{
"kind": "ExpressionStatement",
"expression": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"foo": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "other"
},
"imports": [],
"exports": {
"foo": {
"kind": "Export",
@ -130,38 +129,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [
{
"kind": "ModuleToken",
"tok": "modules/func_cross_call:other",
"loc": {
"file": "index.ts",
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 28
}
}
},
{
"kind": "ModuleToken",
"tok": "modules/func_cross_call:other",
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 24
},
"end": {
"line": 2,
"column": 33
}
}
}
],
"exports": {},
"members": {
".init": {
@ -173,6 +140,42 @@
"body": {
"kind": "Block",
"statements": [
{
"kind": "Import",
"referent": {
"kind": "Token",
"tok": "modules/func_cross_call:other",
"loc": {
"file": "index.ts",
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 28
}
}
}
},
{
"kind": "Import",
"referent": {
"kind": "Token",
"tok": "modules/func_cross_call:other",
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 24
},
"end": {
"line": 2,
"column": 33
}
}
}
},
{
"kind": "ExpressionStatement",
"expression": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"foo": {
"kind": "Export",

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"default": {
"kind": "Export",

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"I": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"I": {
"kind": "Export",

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"A": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "other"
},
"imports": [],
"exports": {
"C": {
"kind": "Export",
@ -354,7 +353,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"C": {
"kind": "Export",

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "other"
},
"imports": [],
"exports": {
"C": {
"kind": "Export",
@ -354,7 +353,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"C": {
"kind": "Export",

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "other"
},
"imports": [],
"exports": {
"C": {
"kind": "Export",
@ -354,7 +353,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"D": {
"kind": "Export",

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {},
"members": {
"x": {

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"x": {
"kind": "Export",

View file

@ -7,7 +7,6 @@
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"exports": {
"Point": {
"kind": "Export",

View file

@ -94,10 +94,8 @@ class Definition(Node):
class Module(Definition):
"""A module contains members, including variables, functions, and/or classes."""
def __init__(self, name, imports=None, exports=None, members=None, loc=None):
def __init__(self, name, exports=None, members=None, loc=None):
assert isinstance(name, Identifier)
assert (imports is None or
(isinstance(imports, list) and all(isinstance(node, ModuleToken) for node in imports)))
assert (exports is None or
(isinstance(exports, dict) and
all(isinstance(key, basestring) for key in exports.keys()) and
@ -107,7 +105,6 @@ class Module(Definition):
all(isinstance(key, basestring) for key in members.keys()) and
all(isinstance(value, ModuleMember) for value in members.values())))
super(Module, self).__init__("Module", name, loc=loc)
self.imports = imports
self.exports = exports
self.members = members
@ -263,6 +260,16 @@ class Statement(Node):
assert isinstance(kind, basestring)
super(Statement, self).__init__(kind, loc)
# ...Imports
class Import(Statement):
def __init__(self, referent, name=None, loc=None):
assert isinstance(referent, Token)
assert name is None or isinstance(name, Identifier)
super(Import, self).__init__("Import", loc)
self.referent = referent
self.name = name
# ...Blocks
class Block(Statement):

View file

@ -111,7 +111,6 @@ class Transformer:
assert self.ctx, "Transform passes require a context object"
members = dict()
initstmts = list()
imports = set()
modtok = self.current_module_token()
# Auto-generate the special __name__ variable and populate it in the initializer.
@ -137,8 +136,6 @@ class Transformer:
elif isinstance(stmt, py_ast.ClassDef):
clazz = self.transform_ClassDef(stmt)
members[clazz.name.ident] = clazz
elif isinstance(stmt, py_ast.Import):
imports |= self.transform_Import(stmt)
else:
# For all other statement nodes, simply accumulate them for the module initializer.
initstmt = self.transform_stmt(stmt)
@ -168,7 +165,7 @@ class Transformer:
tok = modtok + tokens.delim + name
exports[name] = ast.Export(self.ident(name), ast.Token(tok))
return ast.Module(self.ident(self.ctx.mod.name), list(imports), exports, members)
return ast.Module(self.ident(self.ctx.mod.name), exports, members)
# ...Statements
@ -201,9 +198,9 @@ class Transformer:
elif isinstance(node, py_ast.If):
stmt = self.transform_If(node)
elif isinstance(node, py_ast.Import):
assert False, "TODO: imports in non-top-level positions not yet supported"
stmt = self.transform_Import(node)
elif isinstance(node, py_ast.ImportFrom):
assert False, "TODO: imports in non-top-level positions not yet supported"
stmt = self.transform_ImportFrom(node)
elif isinstance(node, py_ast.Nonlocal):
stmt = self.transform_Nonlocal(node)
elif isinstance(node, py_ast.Pass):
@ -331,26 +328,29 @@ class Transformer:
def transform_Import(self, node):
"""Transforms an import clause into a set of AST nodes representing the imported module tokens."""
# TODO: support imports inside of non-top-level scopes.
# TODO: come up with a way to determine intra-project references.
imports = set()
imports = list()
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)
tok = 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)
delimix = tok.find(tokens.name_delim)
if delimix == -1:
# If just the package, we will use the default module.
name = name + tokens.delim + tokens.mod_default
tok = tok + 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:]
tok = tok[:delimix] + tokens.delim + tok[delimix+1:]
imports.add(ast.ModuleToken(name, loc=self.loc_from(namenode)))
return imports
toknode = ast.Token(tok, loc=self.loc_from(namenode))
imports.append(ast.Import(toknode, loc=self.loc_from(node)))
if len(imports) > 0:
return ast.MultiStatement(imports)
return imports[0]
def transform_ImportFrom(self, node):
# TODO: to support this, we will need a way of binding names back to the imported names. Furthermore, we