Introduce lexical scoping for variable values

This commit is contained in:
joeduffy 2017-01-25 13:14:51 -08:00
parent cb8af34c93
commit 240a08b73e
3 changed files with 81 additions and 22 deletions

View file

@ -11,24 +11,24 @@ import (
"github.com/marapongo/mu/pkg/util/contract" "github.com/marapongo/mu/pkg/util/contract"
) )
// Scope enables lookups and symbols to obey traditional language scoping rules. // Scope facilitates storing information that obeys lexically nested scoping rules.
type Scope struct { type Scope struct {
ctx *core.Context // the shared context object for errors, etc. ctx *core.Context // the shared context object for errors, etc.
slot **Scope // the slot rooting the tree of current scopes for pushing/popping. slot **Scope // the slot rooting the tree of current scopes for pushing/popping.
parent *Scope // the parent scope to restore upon pop, or nil if this is the top. parent *Scope // the parent scope to restore upon pop, or nil if this is the top.
variables Variables // the current scope's variables map (name to variable symbol). locals LocalMap // the current scope's locals map (name to local symbol).
} }
// Variables is a mapping from identifier to an actual local variable symbol object representing it. // LocalMap maps the name of locals to their corresponding symbols, for the few places we need name-based lookup.
type Variables map[tokens.Name]*symbols.LocalVariable type LocalMap map[tokens.Name]*symbols.LocalVariable
// NewScope allocates and returns a fresh scope using the given slot, populating it. // NewScope allocates and returns a fresh scope using the given slot, populating it.
func NewScope(ctx *core.Context, slot **Scope) *Scope { func NewScope(ctx *core.Context, slot **Scope) *Scope {
scope := &Scope{ scope := &Scope{
ctx: ctx, ctx: ctx,
slot: slot, slot: slot,
parent: *slot, parent: *slot,
variables: make(Variables), locals: make(LocalMap),
} }
*slot = scope *slot = scope
return scope return scope
@ -41,13 +41,14 @@ func (s *Scope) Push() *Scope {
// Pop restores the prior scope into the underlying slot, tossing away the current symbol table. // Pop restores the prior scope into the underlying slot, tossing away the current symbol table.
func (s *Scope) Pop() { func (s *Scope) Pop() {
contract.Assert(*s.slot == s)
*s.slot = s.parent *s.slot = s.parent
} }
// Lookup finds a variable underneath the given name, issuing an error and returning nil if not found. // Lookup finds a variable underneath the given name, issuing an error and returning nil if not found.
func (s *Scope) Lookup(nm tokens.Name) *symbols.LocalVariable { func (s *Scope) Lookup(nm tokens.Name) *symbols.LocalVariable {
for s != nil { for s != nil {
if sym, exists := s.variables[nm]; exists { if sym, exists := s.locals[nm]; exists {
contract.Assert(sym != nil) contract.Assert(sym != nil)
return sym return sym
} }
@ -63,12 +64,12 @@ func (s *Scope) Lookup(nm tokens.Name) *symbols.LocalVariable {
// Register registers a local variable with a given name; if it already exists, the function returns false. // Register registers a local variable with a given name; if it already exists, the function returns false.
func (s *Scope) Register(sym *symbols.LocalVariable) bool { func (s *Scope) Register(sym *symbols.LocalVariable) bool {
nm := sym.Name() nm := sym.Name()
if _, exists := s.variables[nm]; exists { if _, exists := s.locals[nm]; exists {
// TODO: this won't catch "shadowing" for parent scopes; do we care about this? // TODO: this won't catch "shadowing" for parent scopes; do we care about this?
return false return false
} }
s.variables[nm] = sym s.locals[nm] = sym
return true return true
} }

View file

@ -27,15 +27,23 @@ type Interpreter interface {
// New creates an interpreter that can be used to evaluate MuPackages. // New creates an interpreter that can be used to evaluate MuPackages.
func New(ctx *binder.Context) Interpreter { func New(ctx *binder.Context) Interpreter {
return &evaluator{ctx: ctx} e := &evaluator{
ctx: ctx,
globals: make(globalMap),
}
initLocalScope(&e.locals)
return e
} }
type evaluator struct { type evaluator struct {
fnc *symbols.ModuleMethod // the function under evaluation. fnc *symbols.ModuleMethod // the function under evaluation.
ctx *binder.Context // the binding context with type and symbol information. ctx *binder.Context // the binding context with type and symbol information.
variables map[symbols.Variable]*Object // the object values for variable symbols. TODO: make this scope-based. globals globalMap // the object values for global variable symbols.
locals *localScope // local variable values scoped by the lexical structure.
} }
type globalMap map[symbols.Variable]*Object
var _ Interpreter = (*evaluator)(nil) var _ Interpreter = (*evaluator)(nil)
func (e *evaluator) Ctx() *binder.Context { return e.ctx } func (e *evaluator) Ctx() *binder.Context { return e.ctx }
@ -86,18 +94,33 @@ func (e *evaluator) evalStatement(node ast.Statement) *unwind {
} }
func (e *evaluator) evalBlock(node *ast.Block) *unwind { func (e *evaluator) evalBlock(node *ast.Block) *unwind {
// TODO: erect a variable scope (and tear it down afterwards). // Push a scope at the start, and pop it at afterwards; both for the symbol context and local variable values.
e.ctx.Scope.Push()
e.locals.Push()
defer func() {
e.locals.Pop()
e.ctx.Scope.Pop()
}()
for _, stmt := range node.Statements {
if uw := e.evalStatement(stmt); uw != nil {
return uw
}
}
return nil return nil
} }
func (e *evaluator) evalLocalVariableDeclaration(node *ast.LocalVariableDeclaration) *unwind { func (e *evaluator) evalLocalVariableDeclaration(node *ast.LocalVariableDeclaration) *unwind {
// Populate the variable in the scope.
sym := e.ctx.RequireVariable(node.Local).(*symbols.LocalVariable)
e.ctx.Scope.Register(sym)
// If there is a default value, set it now. // If there is a default value, set it now.
if node.Local.Default != nil { if node.Local.Default != nil {
obj := NewConstantObject(*node.Local.Default) obj := NewConstantObject(*node.Local.Default)
sym := e.ctx.RequireVariable(node.Local)
contract.Assert(sym != nil)
contract.Assert(obj.Type == sym.Type()) contract.Assert(obj.Type == sym.Type())
e.variables[sym] = obj e.locals.Values[sym] = obj
} }
return nil return nil

View file

@ -0,0 +1,35 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package eval
import (
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/util/contract"
)
// localScope is a kind of scope that holds local variable values.
type localScope struct {
Slot **localScope
Parent *localScope
Values ValueMap
}
func initLocalScope(slot **localScope) *localScope {
return &localScope{
Slot: slot,
Parent: *slot,
Values: make(ValueMap),
}
}
func (s *localScope) Push() *localScope {
return initLocalScope(s.Slot)
}
func (s *localScope) Pop() {
contract.Assert(*s.Slot == s)
*s.Slot = s.Parent
}
// ValueMap maps local variables to their current known object value (if any).
type ValueMap map[*symbols.LocalVariable]*Object