Use symbol factories

This introduces symbol factory methods to make creating them
less error prone.  In particular, we hadn't been wiring up parents
properly (since they came in after the initial symbol shape).
Now with the factory methods, we'll be reforced to visit creation
sites whenever adding new required elements to symbol types.
This commit is contained in:
joeduffy 2017-01-20 13:02:51 -08:00
parent 4babba157f
commit 3f8a317724
6 changed files with 122 additions and 67 deletions

View file

@ -8,7 +8,7 @@ import (
"github.com/marapongo/mu/pkg/util/contract"
)
func (b *binder) bindClass(node *ast.Class) *symbols.Class {
func (b *binder) bindClass(node *ast.Class, parent *symbols.Module) *symbols.Class {
// Bind base type tokens to actual symbols.
var extends *symbols.Type
if node.Extends != nil {
@ -24,48 +24,45 @@ func (b *binder) bindClass(node *ast.Class) *symbols.Class {
}
}
// Now create a class symbol. This is required as a parent for the members.
class := symbols.NewClassSym(node, parent, extends, implements)
// Next, bind each member at the symbolic level; in particular, we do not yet bind bodies of methods.
members := make(symbols.ClassMemberMap)
if node.Members != nil {
for memtok, member := range *node.Members {
members[memtok] = b.bindClassMember(member)
class.Members[memtok] = b.bindClassMember(member, class)
}
}
// TODO: add these to the the symbol table for binding bodies.
// TODO: bind the bodies.
return &symbols.Class{
Node: node,
Extends: extends,
Implements: implements,
Members: members,
}
return class
}
func (b *binder) bindClassMember(node ast.ClassMember) symbols.ClassMember {
func (b *binder) bindClassMember(node ast.ClassMember, parent *symbols.Class) symbols.ClassMember {
switch n := node.(type) {
case *ast.ClassProperty:
return b.bindClassProperty(n)
return b.bindClassProperty(n, parent)
case *ast.ClassMethod:
return b.bindClassMethod(n)
return b.bindClassMethod(n, parent)
default:
contract.Failf("Unrecognized class member kind: %v", node.GetKind())
return nil
}
}
func (b *binder) bindClassProperty(node *ast.ClassProperty) *symbols.ClassProperty {
func (b *binder) bindClassProperty(node *ast.ClassProperty, parent *symbols.Class) *symbols.ClassProperty {
// Look up this node's type and inject it into the type table.
b.registerVariableType(node)
return &symbols.ClassProperty{Node: node}
return symbols.NewClassPropertySym(node, parent)
}
func (b *binder) bindClassMethod(node *ast.ClassMethod) *symbols.ClassMethod {
func (b *binder) bindClassMethod(node *ast.ClassMethod, parent *symbols.Class) *symbols.ClassMethod {
// Make a function type out of this method and inject it into the type table.
b.registerFunctionType(node)
// Note that we don't actually bind the body of this method yet. Until we have gone ahead and injected *all*
// top-level symbols into the type table, we would potentially encounter missing intra-module symbols.
return &symbols.ClassMethod{Node: node}
return symbols.NewClassMethodSym(node, parent)
}

View file

@ -3,81 +3,79 @@
package binder
import (
"github.com/golang/glog"
"github.com/marapongo/mu/pkg/compiler/ast"
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/util/contract"
)
func (b *binder) bindModule(node *ast.Module) *symbols.Module {
func (b *binder) bindModule(node *ast.Module, parent *symbols.Package) *symbols.Module {
glog.V(3).Infof("Binding package %v module %v", parent.Name, node.Name)
// First create a module symbol with empty members, so we can use it as a parent below.
module := symbols.NewModuleSym(node, parent)
// First bind all imports to concrete symbols. These will be used to perform initialization later on.
var imports symbols.Modules
if node.Imports != nil {
for _, imptok := range *node.Imports {
if imp := b.scope.LookupModule(imptok); imp != nil {
imports = append(imports, imp)
module.Imports = append(module.Imports, imp)
}
}
// TODO: add the imports to the symbol table.
}
// TODO: add the imports to the symbol table.
// Next, bind each member at the symbolic level; in particular, we do not yet bind bodies of methods.
members := make(symbols.ModuleMemberMap)
if node.Members != nil {
for memtok, member := range *node.Members {
members[memtok] = b.bindModuleMember(member)
module.Members[memtok] = b.bindModuleMember(member, module)
}
}
// TODO: add these to the the symbol table for binding bodies.
// TODO: bind the bodies.
return &symbols.Module{
Node: node,
Imports: imports,
Members: members,
}
return module
}
func (b *binder) bindModuleMember(node ast.ModuleMember) symbols.ModuleMember {
func (b *binder) bindModuleMember(node ast.ModuleMember, parent *symbols.Module) symbols.ModuleMember {
switch n := node.(type) {
case *ast.Class:
return b.bindClass(n)
return b.bindClass(n, parent)
case *ast.Export:
return b.bindExport(n)
return b.bindExport(n, parent)
case *ast.ModuleProperty:
return b.bindModuleProperty(n)
return b.bindModuleProperty(n, parent)
case *ast.ModuleMethod:
return b.bindModuleMethod(n)
return b.bindModuleMethod(n, parent)
default:
contract.Failf("Unrecognized module member kind: %v", node.GetKind())
return nil
}
}
func (b *binder) bindExport(node *ast.Export) *symbols.Export {
func (b *binder) bindExport(node *ast.Export, parent *symbols.Module) *symbols.Export {
// To bind an export, simply look up the referent symbol and associate this name with it.
refsym := b.scope.Lookup(node.Referent)
if refsym == nil {
// TODO: issue a verification error; name not found! Also sub in a "bad" symbol.
contract.Failf("Export name not found: %v", node.Referent)
}
return &symbols.Export{
Node: node,
Referent: refsym,
}
return symbols.NewExportSym(node, parent, refsym)
}
func (b *binder) bindModuleProperty(node *ast.ModuleProperty) *symbols.ModuleProperty {
func (b *binder) bindModuleProperty(node *ast.ModuleProperty, parent *symbols.Module) *symbols.ModuleProperty {
// Look up this node's type and inject it into the type table.
b.registerVariableType(node)
return &symbols.ModuleProperty{Node: node}
return symbols.NewModulePropertySym(node, parent)
}
func (b *binder) bindModuleMethod(node *ast.ModuleMethod) *symbols.ModuleMethod {
func (b *binder) bindModuleMethod(node *ast.ModuleMethod, parent *symbols.Module) *symbols.ModuleMethod {
// Make a function type out of this method and inject it into the type table.
b.registerFunctionType(node)
// Note that we don't actually bind the body of this method yet. Until we have gone ahead and injected *all*
// top-level symbols into the type table, we would potentially encounter missing intra-module symbols.
return &symbols.ModuleMethod{Node: node}
return symbols.NewModuleMethodSym(node, parent)
}

View file

@ -16,30 +16,26 @@ import (
// BindPackages takes a package AST, resolves all dependencies and tokens inside of it, and returns a fully bound
// package symbol that can be used for semantic operations (like interpretation and evaluation).
func (b *binder) BindPackage(pkg *pack.Package) *symbols.Package {
// Create a symbol with empty dependencies and modules; this allows child symbols to parent to it.
pkgsym := symbols.NewPackageSym(pkg)
// Resolve all package dependencies.
deps := b.resolvePackageDeps(pkg)
b.resolvePackageDeps(pkgsym)
// TODO: add these packages and their modules into the symbol table.
// Now bind all of the package's modules (if any).
modules := b.bindPackageModules(pkg)
b.bindPackageModules(pkgsym)
// Finally, create a new symbol and return it.
// TODO: when to inject this into the symbol table?
return &symbols.Package{
Node: pkg,
Dependencies: deps,
Modules: modules,
}
return pkgsym
}
// resolvePackageDeps resolves all package dependencies, ensuring that all symbols are available to us. This recurses
// into dependency dependencies, and so on, until we have reached a fixed point.
func (b *binder) resolvePackageDeps(pkg *pack.Package) symbols.PackageMap {
func (b *binder) resolvePackageDeps(pkg *symbols.Package) {
contract.Require(pkg != nil, "pkg")
deps := make(symbols.PackageMap)
if pkg.Dependencies != nil {
for _, depurl := range *pkg.Dependencies {
if pkg.Node.Dependencies != nil {
for _, depurl := range *pkg.Node.Dependencies {
// The dependency is a URL. Transform it into a name used for symbol resolution.
dep, err := depurl.Parse()
if err != nil {
@ -47,16 +43,14 @@ func (b *binder) resolvePackageDeps(pkg *pack.Package) symbols.PackageMap {
} else {
glog.V(3).Infof("Resolving package %v dependency name=%v, url=%v", pkg.Name, dep.Name, dep.URL())
if depsym := b.resolveDep(dep); depsym != nil {
deps[dep.Name] = depsym
pkg.Dependencies[dep.Name] = depsym
}
}
}
}
return deps
}
var cyclicTombstone = &symbols.Package{}
var cyclicTombstone = symbols.NewPackageSym(nil)
// resolveDep actually performs the package resolution process, populating the compiler symbol tables.
func (b *binder) resolveDep(dep pack.PackageURL) *symbols.Package {
@ -107,16 +101,12 @@ func (b *binder) resolveDep(dep pack.PackageURL) *symbols.Package {
return nil
}
// bindPackageModules recursively binds all modules in the given package, returning a module map.
func (b *binder) bindPackageModules(pkg *pack.Package) symbols.ModuleMap {
// bindPackageModules recursively binds all modules and stores them in the given package.
func (b *binder) bindPackageModules(pkg *symbols.Package) {
contract.Require(pkg != nil, "pkg")
modules := make(symbols.ModuleMap)
if pkg.Modules != nil {
for modtok, mod := range *pkg.Modules {
glog.V(3).Infof("Binding package %v module %v", pkg.Name, mod.Name)
modsym := b.bindModule(mod)
modules[modtok] = modsym
if pkg.Node.Modules != nil {
for modtok, mod := range *pkg.Node.Modules {
pkg.Modules[modtok] = b.bindModule(mod, pkg)
}
}
return modules
}

View file

@ -35,6 +35,16 @@ func (node *Class) Token() tokens.Token {
}
func (node *Class) Tree() diag.Diagable { return node.Node }
// NewClassSym returns a new Class symbol with the given node, parent, extends, and implements, and empty members.
func NewClassSym(node *ast.Class, parent *Module, extends *Type, implements *Types) *Class {
return &Class{
Node: node,
Parent: parent,
Extends: extends,
Implements: implements,
}
}
// ClassMember is a marker interface for things that can be module members.
type ClassMember interface {
Symbol
@ -66,6 +76,14 @@ func (node *ClassProperty) Token() tokens.Token {
}
func (node *ClassProperty) Tree() diag.Diagable { return node.Node }
// NewClassPropertySym returns a new ClassProperty symbol with the given node and parent.
func NewClassPropertySym(node *ast.ClassProperty, parent *Class) *ClassProperty {
return &ClassProperty{
Node: node,
Parent: parent,
}
}
// ClassMethod is a fully bound module method symbol.
type ClassMethod struct {
Node *ast.ClassMethod
@ -87,3 +105,11 @@ func (node *ClassMethod) Token() tokens.Token {
)
}
func (node *ClassMethod) Tree() diag.Diagable { return node.Node }
// NewClassMethodSym returns a new ClassMethod symbol with the given node and parent.
func NewClassMethodSym(node *ast.ClassMethod, parent *Class) *ClassMethod {
return &ClassMethod{
Node: node,
Parent: parent,
}
}

View file

@ -30,6 +30,16 @@ func (node *Module) Token() tokens.Token {
}
func (node *Module) Tree() diag.Diagable { return node.Node }
// NewModuleSym returns a new Module symbol with the given node and parent, and empty imports and members.
func NewModuleSym(node *ast.Module, parent *Package) *Module {
return &Module{
Node: node,
Parent: parent,
Imports: make(Modules, 0),
Members: make(ModuleMemberMap),
}
}
// Modules is an array of module pointers.
type Modules []*Module
@ -65,6 +75,15 @@ func (node *Export) Token() tokens.Token {
}
func (node *Export) Tree() diag.Diagable { return node.Node }
// NewExportSym returns a new Export symbol with the given node, parent, and referent symbol.
func NewExportSym(node *ast.Export, parent *Module, referent Symbol) *Export {
return &Export{
Node: node,
Parent: parent,
Referent: referent,
}
}
// ModuleProperty is a fully bound module property symbol.
type ModuleProperty struct {
Node *ast.ModuleProperty
@ -87,6 +106,14 @@ func (node *ModuleProperty) Token() tokens.Token {
}
func (node *ModuleProperty) Tree() diag.Diagable { return node.Node }
// NewModulePropertySym returns a new ModuleProperty symbol with the given node and parent.
func NewModulePropertySym(node *ast.ModuleProperty, parent *Module) *ModuleProperty {
return &ModuleProperty{
Node: node,
Parent: parent,
}
}
// ModuleMethod is a fully bound module method symbol.
type ModuleMethod struct {
Node *ast.ModuleMethod
@ -108,3 +135,11 @@ func (node *ModuleMethod) Token() tokens.Token {
)
}
func (node *ModuleMethod) Tree() diag.Diagable { return node.Node }
// NewModuleMethodSym returns a new ModuleMethod symbol with the given node and parent.
func NewModuleMethodSym(node *ast.ModuleMethod, parent *Module) *ModuleMethod {
return &ModuleMethod{
Node: node,
Parent: parent,
}
}

View file

@ -22,6 +22,15 @@ func (node *Package) Name() tokens.Name { return tokens.Name(node.Node.Name) }
func (node *Package) Token() tokens.Token { return tokens.Token(node.Node.Name) }
func (node *Package) Tree() diag.Diagable { return node.Node }
// NewPackageSym returns a new Package symbol with the given node.
func NewPackageSym(node *pack.Package) *Package {
return &Package{
Node: node,
Dependencies: make(PackageMap),
Modules: make(ModuleMap),
}
}
// PackageMap is a map from package token to the associated symbols.
type PackageMap map[tokens.PackageName]*Package