Introduce lexical scoping for variable values
This commit is contained in:
parent
cb8af34c93
commit
240a08b73e
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
35
pkg/compiler/eval/scope.go
Normal file
35
pkg/compiler/eval/scope.go
Normal 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
|
Loading…
Reference in a new issue