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:
parent
5aff453cc1
commit
35fddddb78
15 changed files with 520 additions and 166 deletions
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
71
pkg/compiler/binder/class.go
Normal file
71
pkg/compiler/binder/class.go
Normal 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}
|
||||
}
|
|
@ -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 {
|
||||
|
|
83
pkg/compiler/binder/module.go
Normal file
83
pkg/compiler/binder/module.go
Normal 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}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue