Files
tablizer/vendor/github.com/glycerine/zygomys/zygo/stack.go
2024-05-14 12:10:58 +02:00

279 lines
6.2 KiB
Go

package zygo
import (
"fmt"
"strings"
)
type StackElem interface {
IsStackElem()
}
type Stack struct {
tos int
elements []StackElem
env *Zlisp
Name string // type name
// package support:
PackageName string
IsPackage bool
}
func (s *Stack) SexpString(ps *PrintState) string {
if ps == nil {
ps = NewPrintState()
}
var label string
head := ""
if s.IsPackage {
head = "(package " + s.PackageName
} else {
label = "scope " + s.Name
}
str, err := s.Show(s.env, ps, s.Name)
if err != nil {
return "(" + label + ")"
}
return head + " " + str + " )"
}
// Type() satisfies the Sexp interface, returning the type of the value.
func (s *Stack) Type() *RegisteredType {
return GoStructRegistry.Lookup("packageScopeStack")
}
func (env *Zlisp) NewStack(size int) *Stack {
return &Stack{
tos: -1,
// elements: make([]StackElem, size),
elements: make([]StackElem, 0),
env: env,
}
}
func (stack *Stack) Clone() *Stack {
ret := &Stack{}
ret.tos = stack.tos
ret.env = stack.env
ret.elements = make([]StackElem, len(stack.elements))
for i := range stack.elements {
ret.elements[i] = stack.elements[i]
}
return ret
}
func (stack *Stack) Top() int {
return stack.tos
}
func (stack *Stack) PushAllTo(target *Stack) int {
if stack.tos < 0 {
return 0
}
for _, v := range stack.elements[0 : stack.tos+1] {
target.Push(v)
}
return stack.tos + 1
}
func (stack *Stack) IsEmpty() bool {
return stack.tos < 0
}
func (stack *Stack) Push(elem StackElem) {
// we have to lazily recover from errors here where the stack
// didn't get unwound...
n := len(stack.elements)
// n-1 is the last legal entry, which should be top of stack too.
switch {
case stack.tos == n-1:
// normal, 99% of the time.
stack.tos++
stack.elements = append(stack.elements, elem)
case stack.tos > n-1:
// really irretreivably problematic
panic(fmt.Sprintf("stack %p is really messed up! starting size=%v > "+
"len(stack.elements)=%v:\n here is stack: '%s'\n",
stack, stack.tos-1, n, stack.SexpString(nil)))
default:
// INVAR stack.tos < n-1
// We might get here if an error caused the last operation to abort,
// resulting in the call stack pop upon returning never happening.
// So we'll lazily cleanup now and carry on.
stack.TruncateToSize(stack.tos + 1)
stack.tos++
stack.elements = append(stack.elements, elem)
}
}
func (stack *Stack) GetTop() StackElem {
s, err := stack.Get(0)
if err != nil {
panic(err)
}
return s
}
func (stack *Stack) Size() int {
return stack.tos + 1
}
var StackUnderFlowErr = fmt.Errorf("invalid stack access: underflow")
func (stack *Stack) Get(n int) (StackElem, error) {
if stack.tos-n < 0 {
err := StackUnderFlowErr
return nil, err
}
return stack.elements[stack.tos-n], nil
}
func (stack *Stack) Pop() (StackElem, error) {
// always make a new array,
// so we can use for the closure stack-of-scopes.
elem, err := stack.Get(0)
if err != nil {
return nil, err
}
// invar n > 0
n := stack.Size()
if n == 0 {
return nil, fmt.Errorf("Stack.Pop() on emtpy stack")
}
el := make([]StackElem, n-1)
copy(el, stack.elements)
stack.elements = el
stack.tos--
return elem, nil
}
func (stack *Stack) IsStackElem() {}
func (stack *Stack) Show(env *Zlisp, ps *PrintState, label string) (string, error) {
//P("debug: Stack.Show starting with stack = %p, ps = %p, Package: '%s', IsPkg: %v", stack, ps, stack.PackageName, stack.IsPackage)
if ps.GetSeen(stack) {
return fmt.Sprintf("already-saw Stack %p in Show", stack), nil
} else {
ps.SetSeen(stack, "Stack in Show")
}
s := ""
rep := strings.Repeat(" ", ps.GetIndent())
s += fmt.Sprintf("%s %s\n", rep, label)
n := stack.Top()
for i := 0; i <= n; i++ {
ele, err := stack.Get(n - i)
if err != nil {
panic(fmt.Errorf("stack access error on %v: %v", i, err))
}
showme, canshow := ele.(Showable)
if canshow {
r, err := showme.Show(env, ps.AddIndent(4),
fmt.Sprintf("elem %v of %s:", i, label))
if err != nil {
return "", err
}
s += r
}
}
return s, nil
}
// set newsize to 0 to truncate everything
func (stack *Stack) TruncateToSize(newsize int) {
el := make([]StackElem, newsize)
copy(el, stack.elements)
stack.elements = el
stack.tos = newsize - 1
}
// nestedPathGetSet does a top-down lookup, as opposed to LexicalLookupSymbol which is bottom up
func (s *Stack) nestedPathGetSet(env *Zlisp, dotpaths []string, setVal *Sexp) (Sexp, error) {
if len(dotpaths) == 0 {
return SexpNull, fmt.Errorf("internal error: in nestedPathGetSet() dotpaths" +
" had zero length")
}
curStack := s
var ret Sexp = SexpNull
var err error
var scop *Scope
lenpath := len(dotpaths)
//P("\n in nestedPathGetSet, dotpaths=%#v\n", dotpaths)
for i := range dotpaths {
curSym := env.MakeSymbol(stripAnyDotPrefix(dotpaths[i]))
if !curStack.IsPackage {
return SexpNull, fmt.Errorf("error locating symbol '%s': current Stack is not a package", curSym.name)
}
ret, err, scop = curStack.LookupSymbol(curSym, nil)
if err != nil {
return SexpNull, fmt.Errorf("could not find symbol '%s' in current package '%v'",
curSym.name, curStack.PackageName)
}
if setVal != nil && i == lenpath-1 {
// check if private
err = errIfPrivate(curSym.name, curStack)
if err != nil {
return SexpNull, err
}
// assign now
scop.Map[curSym.number] = *setVal
// done with SET
return *setVal, nil
}
if i == lenpath-1 {
// final element
switch ret.(type) {
case *Stack:
// allow package within package to be inspected.
// done with GET
return ret, nil
default:
// don't allow private value within package to be inspected.
err = errIfPrivate(curSym.name, curStack)
if err != nil {
return SexpNull, err
}
}
// done with GET
return ret, nil
}
// invar: i < lenpath-1, so go deeper
switch x := ret.(type) {
case *SexpHash:
err = errIfPrivate(curSym.name, curStack)
if err != nil {
return SexpNull, err
}
//P("\n found hash in x at i=%d, looping to next i\n", i)
return x.nestedPathGetSet(env, dotpaths[1:], setVal)
case *Stack:
curStack = x
default:
return SexpNull, fmt.Errorf("not a record or scope: cannot get field '%s'"+
" out of type %T)", dotpaths[i+1][1:], x)
}
}
return ret, nil
}