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"
)
// Scope enables lookups and symbols to obey traditional language scoping rules.
// Scope facilitates storing information that obeys lexically nested scoping rules.
type Scope struct {
ctx *core.Context // the shared context object for errors, etc.
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.
variables Variables // the current scope's variables map (name to variable symbol).
ctx *core.Context // the shared context object for errors, etc.
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.
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.
type Variables map[tokens.Name]*symbols.LocalVariable
// LocalMap maps the name of locals to their corresponding symbols, for the few places we need name-based lookup.
type LocalMap map[tokens.Name]*symbols.LocalVariable
// NewScope allocates and returns a fresh scope using the given slot, populating it.
func NewScope(ctx *core.Context, slot **Scope) *Scope {
scope := &Scope{
ctx: ctx,
slot: slot,
parent: *slot,
variables: make(Variables),
ctx: ctx,
slot: slot,
parent: *slot,
locals: make(LocalMap),
}
*slot = 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.
func (s *Scope) Pop() {
contract.Assert(*s.slot == s)
*s.slot = s.parent
}
// 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 {
for s != nil {
if sym, exists := s.variables[nm]; exists {
if sym, exists := s.locals[nm]; exists {
contract.Assert(sym != nil)
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.
func (s *Scope) Register(sym *symbols.LocalVariable) bool {
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?
return false
}
s.variables[nm] = sym
s.locals[nm] = sym
return true
}

View file

@ -27,15 +27,23 @@ type Interpreter interface {
// New creates an interpreter that can be used to evaluate MuPackages.
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 {
fnc *symbols.ModuleMethod // the function under evaluation.
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.
fnc *symbols.ModuleMethod // the function under evaluation.
ctx *binder.Context // the binding context with type and symbol information.
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)
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 {
// 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
}
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 node.Local.Default != nil {
obj := NewConstantObject(*node.Local.Default)
sym := e.ctx.RequireVariable(node.Local)
contract.Assert(sym != nil)
contract.Assert(obj.Type == sym.Type())
e.variables[sym] = obj
e.locals.Values[sym] = obj
}
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