* feat: add support for named output variables Function output parameters are located at the start of the function frame, uninitialized, as they could be only set through return statements. Supporting named output variables requires to: - identify and allocate symbols corresponding to output names: done at ident parsing, with the funcRet() helper, - compute the location of output name in the frame: done with retRank() helper, - initialize the frame entry with the zero value of symbol type, as the symbol can be accessed and written at multiple times, and return not setting the variable (opposite to unnamed). Done with frameType() helper, which now takes into account output parameters. * refactor: simplify memory management Perform function frame analysis at pre-order, instead of post-order, to initialize memory frame, and track value types. Remove all operation involving unitialized types. Frame memory layout is now build incrementally, instead of having to perform dedicated tree walks on AST.
152 lines
4.5 KiB
Go
152 lines
4.5 KiB
Go
package interp
|
|
|
|
import (
|
|
"log"
|
|
"reflect"
|
|
"strconv"
|
|
)
|
|
|
|
// A SymKind represents the kind of symbol
|
|
type SymKind uint
|
|
|
|
// Symbol kinds for the go language
|
|
const (
|
|
Const SymKind = iota // Constant
|
|
Typ // Type
|
|
Var // Variable
|
|
Func // Function
|
|
Bin // Binary from runtime
|
|
Bltn // Builtin
|
|
)
|
|
|
|
var symKinds = [...]string{
|
|
Const: "Const",
|
|
Typ: "Typ",
|
|
Var: "Var",
|
|
Func: "Func",
|
|
Bin: "Bin",
|
|
Bltn: "Bltn",
|
|
}
|
|
|
|
func (k SymKind) String() string {
|
|
if k < SymKind(len(symKinds)) {
|
|
return symKinds[k]
|
|
}
|
|
return "SymKind(" + strconv.Itoa(int(k)) + ")"
|
|
}
|
|
|
|
// A Symbol represents an interpreter object such as type, constant, var, func, builtin or binary object
|
|
type Symbol struct {
|
|
kind SymKind
|
|
typ *Type // Type of value
|
|
node *Node // Node value if index is negative
|
|
recv *Receiver // receiver node value, if sym refers to a method
|
|
index int // index of value in frame or -1
|
|
val interface{} // default value (used for constants)
|
|
path string // package path if typ.cat is SrcPkgT or BinPkgT
|
|
builtin BuiltinGenerator // Builtin function or nil
|
|
global bool // true if symbol is defined in global space
|
|
//constant bool // true if symbol value is constant
|
|
}
|
|
|
|
// A SymMap stores symbols indexed by name
|
|
type SymMap map[string]*Symbol
|
|
|
|
// Scope type stores symbols in maps, and frame layout as array of types
|
|
// The purposes of scopes are to manage the visibility of each symbol
|
|
// and to store the memory frame layout informations (type and index in frame)
|
|
// at each level (global, package, functions)
|
|
//
|
|
// scopes are organized in a stack fashion: a first scope (universe) is created
|
|
// once at global level, and for each block (package, func, for, etc...), a new
|
|
// scope is pushed at entry, and poped at exit.
|
|
//
|
|
// Nested scopes with the same level value use the same frame: it allows to have
|
|
// eaxctly one frame per function, with a fixed position for each variable (named
|
|
// or not), no matter the inner complexity (number of nested blocks in the function)
|
|
//
|
|
// In symbols, the index value corresponds to the index in scope.types, and at
|
|
// execution to the index in frame, created exactly from the types layout.
|
|
//
|
|
type Scope struct {
|
|
anc *Scope // Ancestor upper scope
|
|
def *Node // function definition node this scope belongs to, or nil
|
|
types []reflect.Type // Frame layout, may be shared by same level scopes
|
|
level int // Frame level: number of frame indirections to access var during execution
|
|
sym SymMap // Map of symbols defined in this current scope
|
|
global bool // true if scope refers to global space (single frame for universe and package level scopes)
|
|
}
|
|
|
|
// push creates a new scope and chain it to the current one
|
|
func (s *Scope) push(indirect bool) *Scope {
|
|
scope := Scope{anc: s, level: s.level, sym: map[string]*Symbol{}}
|
|
if indirect {
|
|
scope.types = []reflect.Type{}
|
|
scope.level = s.level + 1
|
|
} else {
|
|
// propagate size, types, def and global as scopes at same level share the same frame
|
|
scope.types = s.types
|
|
scope.def = s.def
|
|
scope.global = s.global
|
|
scope.level = s.level
|
|
}
|
|
return &scope
|
|
}
|
|
|
|
func (s *Scope) pushBloc() *Scope { return s.push(false) }
|
|
func (s *Scope) pushFunc() *Scope { return s.push(true) }
|
|
|
|
func (s *Scope) pop() *Scope {
|
|
if s.level == s.anc.level {
|
|
// propagate size and types, as scopes at same level share the same frame
|
|
s.anc.types = s.types
|
|
}
|
|
return s.anc
|
|
}
|
|
|
|
// lookup searches for a symbol in the current scope, and upper ones if not found
|
|
// it returns the symbol, the number of indirections level from the current scope
|
|
// and status (false if no result)
|
|
func (s *Scope) lookup(ident string) (*Symbol, int, bool) {
|
|
level := s.level
|
|
for s != nil {
|
|
if sym, ok := s.sym[ident]; ok {
|
|
return sym, level - s.level, true
|
|
}
|
|
s = s.anc
|
|
}
|
|
return nil, 0, false
|
|
}
|
|
|
|
func (s *Scope) getType(ident string) *Type {
|
|
var t *Type
|
|
if sym, _, found := s.lookup(ident); found {
|
|
if sym.kind == Typ {
|
|
t = sym.typ
|
|
}
|
|
}
|
|
return t
|
|
}
|
|
|
|
// add adds a type to the scope types array, and returns its index
|
|
func (s *Scope) add(typ *Type) (index int) {
|
|
if typ == nil {
|
|
log.Panic("nil type")
|
|
}
|
|
index = len(s.types)
|
|
var t reflect.Type
|
|
switch typ.cat {
|
|
case FuncT:
|
|
t = reflect.TypeOf((*Node)(nil))
|
|
case InterfaceT:
|
|
t = reflect.TypeOf((*valueInterface)(nil)).Elem()
|
|
default:
|
|
t = typ.TypeOf()
|
|
if t == nil {
|
|
log.Panic("nil reflect type")
|
|
}
|
|
}
|
|
s.types = append(s.types, t)
|
|
return
|
|
}
|