Bind packages and modules

This change implements a significant amount of the top-level package
and module binding logic, including module and class members.  It also
begins whittling away at the legacy binder logic (which I expect will
disappear entirely in the next checkin).

The scope abstraction has been rewritten in terms of the new tokens
and symbols layers.  Each scope has a symbol table that associates
names with bound symbols, which can be used during lookup.  This
accomplishes lexical scoping of the symbol names, by pushing and
popping at the appropriate times.  I envision all name resolution to
happen during this single binding pass so that we needn't reconstruct
lexical scoping more than once.

Note that we need to do two passes at the top-level, however.  We
must first bind module-level member names to their symbols *before*
we bind any method bodies, otherwise legal intra-module references
might turn up empty-handed during this binding pass.

There is also a type table that associates types with ast.Nodes.
This is how we avoid needing a complete shadow tree of nodes, and/or
avoid needing to mutate the nodes in place.  Every node with a type
gets an entry in the type table.  For example, variable declarations,
expressions, and so on, each get an entry.  This ensures that we can
access type symbols throughout the subsequent passes without needing
to reconstruct scopes or emulating lexical scoping (as described above).

This is a work in progress, so there are a number of important TODOs
in there associated with symbol table management and body binding.
This commit is contained in:
joeduffy 2017-01-19 13:37:47 -08:00
parent 5aff453cc1
commit 35fddddb78
15 changed files with 520 additions and 166 deletions

View file

@ -223,7 +223,7 @@ func printExport(name tokens.Token, export *ast.Export, indent string) {
if export.Access != nil {
mods = append(mods, string(*export.Access))
}
fmt.Printf("%vexport \"%v\"%v %v\n", indent, name, modString(mods), export.Token)
fmt.Printf("%vexport \"%v\"%v %v\n", indent, name, modString(mods), export.Referent)
}
func printClass(name tokens.Token, class *ast.Class, exportOnly bool, indent string) {

View file

@ -64,7 +64,7 @@ type ModuleMembers map[tokens.Token]ModuleMember
// Export re-exports a Definition from another Module, possibly with a different name.
type Export struct {
moduleMemberNode
Token tokens.Token `json:"token"`
Referent tokens.Token `json:"referent"`
}
var _ Node = (*Export)(nil)

View file

@ -3,13 +3,13 @@
package binder
import (
"github.com/marapongo/mu/pkg/compiler/ast"
"github.com/marapongo/mu/pkg/compiler/core"
"github.com/marapongo/mu/pkg/compiler/legacy/ast"
"github.com/marapongo/mu/pkg/compiler/metadata"
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/diag"
"github.com/marapongo/mu/pkg/pack"
"github.com/marapongo/mu/pkg/tokens"
"github.com/marapongo/mu/pkg/util/contract"
"github.com/marapongo/mu/pkg/workspace"
)
@ -20,21 +20,30 @@ type Binder interface {
// 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).
BindPackage(pkg *pack.Package) *symbols.Package
// PrepareStack prepares the AST for binding. It returns a list of all unresolved dependency references. These
// must be bound and supplied to the BindStack function as the deps argument.
PrepareStack(stack *ast.Stack) []tokens.Ref
// BindStack takes an AST, and its set of dependencies, and binds all names inside, mutating it in place. It
// returns a full list of all dependency Stacks that this Stack depends upon (which must then be bound).
BindStack(stack *ast.Stack, deprefs ast.DependencyRefs) []*ast.Stack
// ValidateStack runs last, after all transitive dependencies have been bound, to perform last minute validation.
ValidateStack(stack *ast.Stack)
}
// TypeMap maps AST nodes to their corresponding type. The semantics of this differ based on the kind of node. For
// example, an ast.Expression's type is the type of its evaluation; an ast.LocalVariable's type is the bound type of its
// value. And so on. This is used during binding, type checking, and evaluation, to perform type-sensitive operations.
// This avoids needing to recreate scopes and/or storing type information on every single node in the AST.
type TypeMap map[ast.Node]symbols.Type
// New allocates a fresh binder object with the given workspace, context, and metadata reader.
func New(w workspace.W, ctx *core.Context, reader metadata.Reader) Binder {
// Create a new binder and a new scope with an empty symbol table.
b := &binder{ctx: ctx, w: w}
b := &binder{
w: w,
ctx: ctx,
reader: reader,
types: make(TypeMap),
}
// Create a global scope and populate it with all of the predefined type names.
b.PushScope()
for _, prim := range symbols.Primitives {
b.scope.MustRegister(prim)
}
return b
}
@ -42,9 +51,74 @@ type binder struct {
w workspace.W // a workspace in which this compilation is happening.
ctx *core.Context // a context shared across all phases of compilation.
reader metadata.Reader // a metadata reader (in case we encounter package references).
scope *scope // the current (mutable) scope.
scope *Scope // the current (mutable) scope.
types TypeMap // the typechecked types for expressions (see TypeMap's comments above).
}
func (b *binder) Diag() diag.Sink {
return b.ctx.Diag
}
// PushScope creates a new scope with an empty symbol table parented to the existing one.
func (b *binder) PushScope() {
b.scope = NewScope(b.ctx, b.scope)
}
// PopScope replaces the current scope with its parent.
func (b *binder) PopScope() {
contract.Assertf(b.scope != nil, "Unexpected empty binding scope during PopScope")
b.scope = b.scope.parent
}
// registerFunctionType understands how to turn any function node into a type, and adds it to the type table. This
// works for any kind of function-like AST node: module property, class property, or lambda.
func (b *binder) registerFunctionType(node ast.Function) {
// Make a function type and inject it into the type table.
var params *[]symbols.Type
np := node.GetParameters()
if np != nil {
*params = make([]symbols.Type, len(*np))
for i, param := range *np {
var ptysym symbols.Type
// If there was an explicit type, look it up.
if param.Type != nil {
ptysym = b.scope.LookupType(*param.Type)
}
// If either the parameter's type was unknown, or the lookup failed (leaving an error), use the any type.
if ptysym == nil {
ptysym = symbols.AnyType
}
(*params)[i] = ptysym
}
}
var ret *symbols.Type
nr := node.GetReturnType()
if nr != nil {
*ret = b.scope.LookupType(*nr)
}
b.types[node] = symbols.NewFunctionType(params, ret)
}
// registerVariableType understands how to turn any variable node into a type, and adds it to the type table. This
// works for any kind of variable-like AST node: module property, class property, parameter, or local variable.
func (b *binder) registerVariableType(node ast.Variable) {
var tysym symbols.Type
// If there is an explicit node type, use it.
nt := node.GetType()
if nt != nil {
tysym = b.scope.LookupType(*nt)
}
// Otherwise, either there was no type, or the lookup failed (leaving behind an error); use the any type.
if tysym == nil {
tysym = symbols.AnyType
}
b.types[node] = tysym
}

View file

@ -0,0 +1,71 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package binder
import (
"github.com/marapongo/mu/pkg/compiler/ast"
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/util/contract"
)
func (b *binder) bindClass(node *ast.Class) *symbols.Class {
// Bind base type tokens to actual symbols.
var extends *symbols.Type
if node.Extends != nil {
*extends = b.scope.LookupType(*node.Extends)
}
var implements *symbols.Types
if node.Implements != nil {
*implements = make(symbols.Types, 0, len(*node.Implements))
for _, impltok := range *node.Implements {
if impl := b.scope.LookupType(impltok); impl != nil {
*implements = append(*implements, impl)
}
}
}
// 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)
}
}
// 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,
}
}
func (b *binder) bindClassMember(node ast.ClassMember) symbols.ClassMember {
switch n := node.(type) {
case *ast.ClassProperty:
return b.bindClassProperty(n)
case *ast.ClassMethod:
return b.bindClassMethod(n)
default:
contract.Failf("Unrecognized class member kind: %v", node.GetKind())
return nil
}
}
func (b *binder) bindClassProperty(node *ast.ClassProperty) *symbols.ClassProperty {
// Look up this node's type and inject it into the type table.
b.registerVariableType(node)
return &symbols.ClassProperty{Node: node}
}
func (b *binder) bindClassMethod(node *ast.ClassMethod) *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}
}

View file

@ -77,37 +77,37 @@ func (b *binder) ValidateStack(stack *ast.Stack) {
// LookupService binds a name to a Service type.
func (b *binder) LookupService(nm tokens.Name) (*ast.Service, bool) {
contract.Assertf(b.scope != nil, "Unexpected empty binding scope during LookupService")
return b.scope.LookupService(nm)
return nil, false
}
// LookupStack binds a name to a Stack type.
func (b *binder) LookupStack(nm tokens.Name) (*ast.Stack, bool) {
contract.Assertf(b.scope != nil, "Unexpected empty binding scope during LookupStack")
return b.scope.LookupStack(nm)
return nil, false
}
// LookupUninstStack binds a name to a UninstStack type.
func (b *binder) LookupUninstStack(nm tokens.Name) (*ast.UninstStack, bool) {
contract.Assertf(b.scope != nil, "Unexpected empty binding scope during LookupUninstStack")
return b.scope.LookupUninstStack(nm)
return nil, false
}
// LookupSchema binds a name to a Schema type.
func (b *binder) LookupSchema(nm tokens.Name) (*ast.Schema, bool) {
contract.Assertf(b.scope != nil, "Unexpected empty binding scope during LookupSchema")
return b.scope.LookupSchema(nm)
return nil, false
}
// LookupSymbol binds a name to any kind of Symbol.
func (b *binder) LookupSymbol(nm tokens.Name) (*legacy.Symbol, bool) {
contract.Assertf(b.scope != nil, "Unexpected empty binding scope during LookupSymbol")
return b.scope.LookupSymbol(nm)
return nil, false
}
// RegisterSymbol registers a symbol with the given name; if it already exists, the function returns false.
func (b *binder) RegisterSymbol(sym *legacy.Symbol) bool {
contract.Assertf(b.scope != nil, "Unexpected empty binding scope during RegisterSymbol")
return b.scope.RegisterSymbol(sym)
return false
}
type binderPreparePhase struct {

View file

@ -0,0 +1,83 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package binder
import (
"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 {
// 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)
}
}
}
// 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)
}
}
// TODO: add these to the the symbol table for binding bodies.
// TODO: bind the bodies.
return &symbols.Module{
Node: node,
Imports: imports,
Members: members,
}
}
func (b *binder) bindModuleMember(node ast.ModuleMember) symbols.ModuleMember {
switch n := node.(type) {
case *ast.Class:
return b.bindClass(n)
case *ast.Export:
return b.bindExport(n)
case *ast.ModuleProperty:
return b.bindModuleProperty(n)
case *ast.ModuleMethod:
return b.bindModuleMethod(n)
default:
contract.Failf("Unrecognized module member kind: %v", node.GetKind())
return nil
}
}
func (b *binder) bindExport(node *ast.Export) *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,
}
}
func (b *binder) bindModuleProperty(node *ast.ModuleProperty) *symbols.ModuleProperty {
// Look up this node's type and inject it into the type table.
b.registerVariableType(node)
return &symbols.ModuleProperty{Node: node}
}
func (b *binder) bindModuleMethod(node *ast.ModuleMethod) *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}
}

View file

@ -17,21 +17,35 @@ 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 {
b.resolvePackageDeps(pkg)
// TODO: create a package symbol.
return nil
// Resolve all package dependencies.
deps := b.resolvePackageDeps(pkg)
// 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)
// 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,
}
}
// 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) {
func (b *binder) resolvePackageDeps(pkg *pack.Package) symbols.PackageMap {
contract.Require(pkg != nil, "pkg")
deps := make(symbols.PackageMap)
if pkg.Dependencies != nil {
for _, dep := range *pkg.Dependencies {
glog.V(3).Infof("Resolving package %v dependency %v", pkg.Name, dep)
b.resolveDep(dep)
depsym := b.resolveDep(dep)
deps[dep] = depsym
}
}
return deps
}
var cyclicTombstone = &symbols.Package{}
@ -40,13 +54,13 @@ var cyclicTombstone = &symbols.Package{}
func (b *binder) resolveDep(dep tokens.Package) *symbols.Package {
// First, see if we've already loaded this package. If yes, reuse it.
// TODO: ensure versions match.
if pkg, exists := b.ctx.Pkgs[dep]; exists {
if pkgsym, exists := b.ctx.Pkgs[dep]; exists {
// Check for cycles. If one exists, do not process this dependency any further.
if pkg == cyclicTombstone {
if pkgsym == cyclicTombstone {
// TODO: report the full transitive loop to help debug cycles.
b.Diag().Errorf(errors.ErrorImportCycle, dep)
}
return pkg
return pkgsym
}
// There are many places a dependency could come from. Consult the workspace for a list of those paths. It will
@ -83,3 +97,17 @@ func (b *binder) resolveDep(dep tokens.Package) *symbols.Package {
b.Diag().Errorf(errors.ErrorPackageNotFound, ref)
return nil
}
// bindPackageModules recursively binds all modules in the given package, returning a module map.
func (b *binder) bindPackageModules(pkg *pack.Package) symbols.ModuleMap {
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
}
}
return modules
}

View file

@ -3,88 +3,89 @@
package binder
import (
"github.com/marapongo/mu/pkg/compiler/legacy"
"github.com/marapongo/mu/pkg/compiler/legacy/ast"
"github.com/marapongo/mu/pkg/compiler/core"
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/tokens"
"github.com/marapongo/mu/pkg/util/contract"
)
// PushScope creates a new scope with an empty symbol table parented to the existing one.
func (b *binder) PushScope() {
b.scope = &scope{parent: b.scope, symtbl: make(map[tokens.Name]*legacy.Symbol)}
// Scope enables lookups and symbols to obey traditional language scoping rules.
type Scope struct {
ctx *core.Context
parent *Scope
symtbl SymbolTable
}
// PopScope replaces the current scope with its parent.
func (b *binder) PopScope() {
contract.Assertf(b.scope != nil, "Unexpected empty binding scope during pop")
b.scope = b.scope.parent
}
// SymbolTable is a mapping from symbol token to an actual symbol object representing it.
type SymbolTable map[tokens.Token]symbols.Symbol
// scope enables lookups and symbols to obey traditional language scoping rules.
type scope struct {
parent *scope
symtbl map[tokens.Name]*legacy.Symbol
}
// LookupService binds a name to a Service type.
func (s *scope) LookupService(nm tokens.Name) (*ast.Service, bool) {
sym, exists := s.LookupSymbol(nm)
if exists && sym.Kind == legacy.SymKindService {
return sym.Real.(*ast.Service), true
// NewScope allocates and returns a fresh scope with the optional parent scope attached to it.
func NewScope(ctx *core.Context, parent *Scope) *Scope {
return &Scope{
ctx: ctx,
parent: parent,
symtbl: make(SymbolTable),
}
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
return nil, false
}
// LookupStack binds a name to a Stack type.
func (s *scope) LookupStack(nm tokens.Name) (*ast.Stack, bool) {
sym, exists := s.LookupSymbol(nm)
if exists && sym.Kind == legacy.SymKindStack {
return sym.Real.(*ast.Stack), true
}
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
return nil, false
}
// LookupUninstStack binds a name to a UninstStack type.
func (s *scope) LookupUninstStack(nm tokens.Name) (*ast.UninstStack, bool) {
sym, exists := s.LookupSymbol(nm)
if exists && sym.Kind == legacy.SymKindUninstStack {
return sym.Real.(*ast.UninstStack), true
}
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
return nil, false
}
// LookupSchema binds a name to a Schema type.
func (s *scope) LookupSchema(nm tokens.Name) (*ast.Schema, bool) {
sym, exists := s.LookupSymbol(nm)
if exists && sym.Kind == legacy.SymKindSchema {
return sym.Real.(*ast.Schema), true
}
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
return nil, false
}
// LookupSymbol binds a name to any kind of Symbol.
func (s *scope) LookupSymbol(nm tokens.Name) (*legacy.Symbol, bool) {
// Lookup finds a symbol registered underneath the given token, issuing an error and returning nil if not found.
func (s *Scope) Lookup(tok tokens.Token) symbols.Symbol {
for s != nil {
if sym, exists := s.symtbl[nm]; exists {
return sym, true
if sym, exists := s.symtbl[tok]; exists {
contract.Assert(sym != nil)
return sym
}
// If not in this scope, keep looking at the ancestral chain, if one exists.
s = s.parent
}
return nil, false
// TODO: issue an error about a missing symbol.
return nil
}
// RegisterSymbol registers a symbol with the given name; if it already exists, the function returns false.
func (s *scope) RegisterSymbol(sym *legacy.Symbol) bool {
nm := sym.Name
if _, exists := s.symtbl[nm]; exists {
// LookupModule finds a module symbol registered underneath the given token, issuing an error and returning nil if
// missing. If the symbol exists, but is of the wrong type (i.e., not a *symbols.Module), then an error is issued.
func (s *Scope) LookupModule(tok tokens.Module) *symbols.Module {
if sym := s.Lookup(tokens.Token(tok)); sym != nil {
if typsym, ok := sym.(*symbols.Module); ok {
return typsym
}
// TODO: issue an error about an incorrect symbol type.
} else {
// TODO: issue an error about a missing symbol.
}
return nil
}
// LookupType finds a type symbol registered underneath the given token, issuing an error and returning nil if missing.
// If the symbol exists, but is of the wrong type (i.e., not a symbols.Type), then an error is issued.
func (s *Scope) LookupType(tok tokens.Type) symbols.Type {
if sym := s.Lookup(tokens.Token(tok)); sym != nil {
if typsym, ok := sym.(symbols.Type); ok {
return typsym
}
// TODO: issue an error about an incorrect symbol type.
} else {
// TODO: issue an error about a missing symbol.
}
return nil
}
// Register registers a symbol with a given name; if it already exists, the function returns false.
func (s *Scope) Register(sym symbols.Symbol) bool {
tok := sym.Token()
if _, exists := s.symtbl[tok]; exists {
// TODO: this won't catch "shadowing" for parent scopes; do we care about this?
return false
}
s.symtbl[nm] = sym
s.symtbl[tok] = sym
return true
}
// MustRegister registers a symbol with a given name; if it already exists, the function fail-fasts.
func (s *Scope) MustRegister(sym symbols.Symbol) {
ok := s.Register(sym)
contract.Assertf(ok, "Expected symbol %v to be unique; entry already found in this scope", sym.Token())
}

View file

@ -10,19 +10,21 @@ import (
// Class is a fully bound class symbol.
type Class struct {
Tree *ast.Class
Members map[tokens.Module]ClassMember
Node *ast.Class
Extends *Type
Implements *Types
Members ClassMemberMap
}
var _ Symbol = (*Class)(nil)
var _ Type = (*Class)(nil)
var _ ModuleMember = (*Class)(nil)
func (node *Class) symbol() {}
func (node *Class) typesym() {}
func (node *Class) moduleMember() {}
func (node *Class) GetName() tokens.Token { return node.Tree.Name.Ident }
func (node *Class) GetTree() diag.Diagable { return node.Tree }
func (node *Class) symbol() {}
func (node *Class) typesym() {}
func (node *Class) moduleMember() {}
func (node *Class) Token() tokens.Token { return node.Node.Name.Ident }
func (node *Class) Tree() diag.Diagable { return node.Node }
// ClassMember is a marker interface for things that can be module members.
type ClassMember interface {
@ -30,28 +32,31 @@ type ClassMember interface {
classMember()
}
// ClassMemberMap is a map from a class member's token to its associated symbol.
type ClassMemberMap map[tokens.Token]ClassMember
// ClassProperty is a fully bound module property symbol.
type ClassProperty struct {
Tree *ast.ClassProperty
Node *ast.ClassProperty
}
var _ Symbol = (*ClassProperty)(nil)
var _ ClassMember = (*ClassProperty)(nil)
func (node *ClassProperty) symbol() {}
func (node *ClassProperty) classMember() {}
func (node *ClassProperty) GetName() tokens.Token { return node.Tree.Name.Ident }
func (node *ClassProperty) GetTree() diag.Diagable { return node.Tree }
func (node *ClassProperty) symbol() {}
func (node *ClassProperty) classMember() {}
func (node *ClassProperty) Token() tokens.Token { return node.Node.Name.Ident }
func (node *ClassProperty) Tree() diag.Diagable { return node.Node }
// ClassMethod is a fully bound module method symbol.
type ClassMethod struct {
Tree *ast.ClassMethod
Node *ast.ClassMethod
}
var _ Symbol = (*ClassMethod)(nil)
var _ ClassMember = (*ClassMethod)(nil)
func (node *ClassMethod) symbol() {}
func (node *ClassMethod) classMember() {}
func (node *ClassMethod) GetName() tokens.Token { return node.Tree.Name.Ident }
func (node *ClassMethod) GetTree() diag.Diagable { return node.Tree }
func (node *ClassMethod) symbol() {}
func (node *ClassMethod) classMember() {}
func (node *ClassMethod) Token() tokens.Token { return node.Node.Name.Ident }
func (node *ClassMethod) Tree() diag.Diagable { return node.Node }

View file

@ -10,15 +10,19 @@ import (
// Module is a fully bound module symbol.
type Module struct {
Tree *ast.Module
Members map[tokens.Module]ModuleMember
Node *ast.Module
Imports Modules
Members ModuleMemberMap
}
var _ Symbol = (*Module)(nil)
func (node *Module) symbol() {}
func (node *Module) GetName() tokens.Token { return node.Tree.Name.Ident }
func (node *Module) GetTree() diag.Diagable { return node.Tree }
func (node *Module) symbol() {}
func (node *Module) Token() tokens.Token { return node.Node.Name.Ident }
func (node *Module) Tree() diag.Diagable { return node.Node }
// Modules is an array of module pointers.
type Modules []*Module
// ModuleMember is a marker interface for things that can be module members.
type ModuleMember interface {
@ -26,28 +30,45 @@ type ModuleMember interface {
moduleMember()
}
// ModuleMembers is a map from a module member's token to its associated symbol.
type ModuleMemberMap map[tokens.Token]ModuleMember
// Export is a fully bound module property symbol that associates a name with some other symbol.
type Export struct {
Node *ast.Export
Referent Symbol // the symbol that this export actually refers to.
}
var _ Symbol = (*Export)(nil)
var _ ModuleMember = (*Export)(nil)
func (node *Export) symbol() {}
func (node *Export) moduleMember() {}
func (node *Export) Token() tokens.Token { return node.Node.Name.Ident }
func (node *Export) Tree() diag.Diagable { return node.Node }
// ModuleProperty is a fully bound module property symbol.
type ModuleProperty struct {
Tree *ast.ModuleProperty
Node *ast.ModuleProperty
}
var _ Symbol = (*ModuleProperty)(nil)
var _ ModuleMember = (*ModuleProperty)(nil)
func (node *ModuleProperty) symbol() {}
func (node *ModuleProperty) moduleMember() {}
func (node *ModuleProperty) GetName() tokens.Token { return node.Tree.Name.Ident }
func (node *ModuleProperty) GetTree() diag.Diagable { return node.Tree }
func (node *ModuleProperty) symbol() {}
func (node *ModuleProperty) moduleMember() {}
func (node *ModuleProperty) Token() tokens.Token { return node.Node.Name.Ident }
func (node *ModuleProperty) Tree() diag.Diagable { return node.Node }
// ModuleMethod is a fully bound module method symbol.
type ModuleMethod struct {
Tree *ast.ModuleMethod
Node *ast.ModuleMethod
}
var _ Symbol = (*ModuleMethod)(nil)
var _ ModuleMember = (*ModuleMethod)(nil)
func (node *ModuleMethod) symbol() {}
func (node *ModuleMethod) moduleMember() {}
func (node *ModuleMethod) GetName() tokens.Token { return node.Tree.Name.Ident }
func (node *ModuleMethod) GetTree() diag.Diagable { return node.Tree }
func (node *ModuleMethod) symbol() {}
func (node *ModuleMethod) moduleMember() {}
func (node *ModuleMethod) Token() tokens.Token { return node.Node.Name.Ident }
func (node *ModuleMethod) Tree() diag.Diagable { return node.Node }

View file

@ -3,12 +3,26 @@
package symbols
import (
"github.com/marapongo/mu/pkg/diag"
"github.com/marapongo/mu/pkg/pack"
"github.com/marapongo/mu/pkg/tokens"
)
// Package is a fully bound package symbol.
type Package struct {
Symbol
Dependencies map[tokens.Package]*Package
Modules map[tokens.Module]*Module
Node *pack.Package
Dependencies PackageMap
Modules ModuleMap
}
var _ Symbol = (*Package)(nil)
func (node *Package) symbol() {}
func (node *Package) Token() tokens.Token { return tokens.Token(node.Node.Name) }
func (node *Package) Tree() diag.Diagable { return node.Node }
// PackageMap is a map from package token to the associated symbols.
type PackageMap map[tokens.Package]*Package
// ModuleMap is a map from module token to the associated symbols.
type ModuleMap map[tokens.Module]*Module

View file

@ -10,6 +10,6 @@ import (
// Symbol is the base interface for all MuIL symbol types.
type Symbol interface {
symbol()
GetName() tokens.Token // the unique name of this symbol.
GetTree() diag.Diagable // the diagnosable tree associated with this symbol.
Token() tokens.Token // the unique name token for this symbol.
Tree() diag.Diagable // the diagnosable tree associated with this symbol.
}

View file

@ -15,6 +15,9 @@ type Type interface {
typesym()
}
// Types is a list of type symbols.
type Types []Type
// primitive is an internal representation of a primitive type symbol (any, bool, number, string).
type primitive struct {
Name tokens.Token
@ -23,10 +26,10 @@ type primitive struct {
var _ Symbol = (*primitive)(nil)
var _ Type = (*primitive)(nil)
func (node *primitive) symbol() {}
func (node *primitive) typesym() {}
func (node *primitive) GetName() tokens.Token { return node.Name }
func (node *primitive) GetTree() diag.Diagable { return nil }
func (node *primitive) symbol() {}
func (node *primitive) typesym() {}
func (node *primitive) Token() tokens.Token { return node.Name }
func (node *primitive) Tree() diag.Diagable { return nil }
// All of the primitive types.
var (
@ -46,23 +49,51 @@ var Primitives = map[tokens.Token]Type{
// Declare some type decorator strings used for parsing and producing array/map types.
const (
TypeDecorsArray = "%v" + TypeDecorsArraySuffix
TypeDecorsArraySuffix = "[]"
TypeDecorsMap = TypeDecorsMapPrefix + "%v" + TypeDecorsMapSeparator + "%v"
TypeDecorsMapPrefix = "map["
TypeDecorsMapSeparator = "]"
TypeDecorsArray = "%v" + TypeDecorsArraySuffix
TypeDecorsArraySuffix = "[]"
TypeDecorsMap = TypeDecorsMapPrefix + "%v" + TypeDecorsMapSeparator + "%v"
TypeDecorsMapPrefix = "map["
TypeDecorsMapSeparator = "]"
TypeDecorsFunction = TypeDecorsFunctionPrefix + "%v" + TypeDecorsFunctionSeparator + "%v"
TypeDecorsFunctionPrefix = "("
TypeDecorsFunctionParamSep = ","
TypeDecorsFunctionSeparator = ")"
)
// ArrayName creates a new array name from an element type.
func ArrayName(elem Type) tokens.Token {
// TODO: consider caching this to avoid creating needless strings.
return tokens.Token(fmt.Sprintf(TypeDecorsArray, elem.GetName()))
return tokens.Token(fmt.Sprintf(TypeDecorsArray, elem.Token()))
}
// MapName creates a new array name from an element type.
func MapName(key Type, elem Type) tokens.Token {
// TODO: consider caching this to avoid creating needless strings.
return tokens.Token(fmt.Sprintf(TypeDecorsMap, key.GetName(), elem.GetName()))
return tokens.Token(fmt.Sprintf(TypeDecorsMap, key.Token(), elem.Token()))
}
// FunctionName creates a new array name from an element type.
func FunctionName(params *[]Type, ret *Type) tokens.Token {
// TODO: consider caching this to avoid creating needless strings.
// Stringify the parameters (if any).
sparams := ""
if params != nil {
for i, param := range *params {
if i > 0 {
sparams += TypeDecorsFunctionParamSep
}
sparams += string(param.Token())
}
}
// Stringify the return type (if any).
sret := ""
if ret != nil {
sret = string((*ret).Token())
}
return tokens.Token(fmt.Sprintf(TypeDecorsFunction, sparams, sret))
}
// ArrayType is an array whose elements are of some other type.
@ -73,10 +104,14 @@ type ArrayType struct {
var _ Symbol = (*ArrayType)(nil)
var _ Type = (*ArrayType)(nil)
func (node *ArrayType) symbol() {}
func (node *ArrayType) typesym() {}
func (node *ArrayType) GetName() tokens.Token { return ArrayName(node.Element) }
func (node *ArrayType) GetTree() diag.Diagable { return nil }
func (node *ArrayType) symbol() {}
func (node *ArrayType) typesym() {}
func (node *ArrayType) Token() tokens.Token { return ArrayName(node.Element) }
func (node *ArrayType) Tree() diag.Diagable { return nil }
func NewArrayType(elem Type) *ArrayType {
return &ArrayType{elem}
}
// KeyType is an array whose keys and elements are of some other types.
type MapType struct {
@ -87,7 +122,29 @@ type MapType struct {
var _ Symbol = (*MapType)(nil)
var _ Type = (*MapType)(nil)
func (node *MapType) symbol() {}
func (node *MapType) typesym() {}
func (node *MapType) GetName() tokens.Token { return MapName(node.Key, node.Element) }
func (node *MapType) GetTree() diag.Diagable { return nil }
func (node *MapType) symbol() {}
func (node *MapType) typesym() {}
func (node *MapType) Token() tokens.Token { return MapName(node.Key, node.Element) }
func (node *MapType) Tree() diag.Diagable { return nil }
func NewMapType(key Type, elem Type) *MapType {
return &MapType{key, elem}
}
// FunctionType is an invocable type, representing a signature with optional parameters and a return type.
type FunctionType struct {
Parameters *[]Type
Return *Type
}
var _ Symbol = (*FunctionType)(nil)
var _ Type = (*FunctionType)(nil)
func (node *FunctionType) symbol() {}
func (node *FunctionType) typesym() {}
func (node *FunctionType) Token() tokens.Token { return FunctionName(node.Parameters, node.Return) }
func (node *FunctionType) Tree() diag.Diagable { return nil }
func NewFunctionType(params *[]Type, ret *Type) *FunctionType {
return &FunctionType{params, ret}
}

View file

@ -34,8 +34,8 @@ export type ModuleMembers = { [token: string /*symbols.Token*/]: ModuleMember };
// An export definition re-exports a definition from another module, possibly with a different name.
export interface Export extends ModuleMember {
kind: ExportKind;
token: symbols.Token;
kind: ExportKind;
referent: symbols.Token;
}
export const exportKind = "Export";
export type ExportKind = "Export";

View file

@ -612,10 +612,10 @@ export class Transformer {
// elements exported from the current module.
let propertyName: ast.Identifier = this.transformIdentifier(exportClause.propertyName);
exports.push(<ast.Export>{
kind: ast.exportKind,
name: name,
access: symbols.publicAccessibility,
token: this.createModuleMemberToken(sourceModule, propertyName.ident),
kind: ast.exportKind,
name: name,
access: symbols.publicAccessibility,
referent: this.createModuleMemberToken(sourceModule, propertyName.ident),
});
}
else {
@ -623,10 +623,10 @@ export class Transformer {
// from within the same module, just look up the definition and change its accessibility to public.
if (sourceModule) {
exports.push(<ast.Export>{
kind: ast.exportKind,
name: name,
access: symbols.publicAccessibility,
token: this.createModuleMemberToken(sourceModule, name.ident),
kind: ast.exportKind,
name: name,
access: symbols.publicAccessibility,
referent: this.createModuleMemberToken(sourceModule, name.ident),
});
}
else {
@ -646,10 +646,10 @@ export class Transformer {
let otherModule: ModuleReference | undefined = this.currentModuleImports!.get(name.ident);
contract.assert(!!otherModule, "Expected either a member or import match for export name");
exports.push(<ast.Export>{
kind: ast.exportKind,
name: name,
access: symbols.publicAccessibility,
token: this.createModuleToken(otherModule!),
kind: ast.exportKind,
name: name,
access: symbols.publicAccessibility,
referent: this.createModuleToken(otherModule!),
});
}
}
@ -665,13 +665,13 @@ export class Transformer {
contract.assert(!!sourceModule);
for (let name of this.resolveModuleExportNames(sourceModule!)) {
exports.push(<ast.Export>{
kind: ast.exportKind,
name: <ast.Identifier>{
kind: ast.exportKind,
name: <ast.Identifier>{
kind: ast.identifierKind,
ident: this.extractMemberToken(name),
},
access: symbols.publicAccessibility,
token: name,
access: symbols.publicAccessibility,
referent: name,
});
}
}