shad-go/forth/eval.go

173 lines
3.7 KiB
Go

//go:build !solution
package main
import (
"errors"
"strconv"
"strings"
)
type Evaluator struct {
stack []int
words map[string](func() error)
}
func (e *Evaluator) push(n int) {
e.stack = append(e.stack, n)
}
func (e *Evaluator) pop() (int, error) {
size := len(e.stack)
if size == 0 {
return 0, errors.New("nothing to pop")
}
n := e.stack[size-1]
e.stack = e.stack[:size-1]
return n, nil
}
func (e *Evaluator) popTwo() (int, int, error) {
if len(e.stack) < 2 {
return 0, 0, errors.New("not enough elements on stack")
}
second, _ := e.pop()
first, _ := e.pop()
return first, second, nil
}
// Used to initialzie arithemtic operations in words map
func (e *Evaluator) arithmeticOp(op func(int, int) int) func() error {
return func() error {
a, b, err := e.popTwo()
if err != nil {
return err
}
e.push(op(a, b))
return nil
}
}
func (e *Evaluator) initBasicWords() {
e.words["+"] = e.arithmeticOp(func(a, b int) int { return a + b })
e.words["-"] = e.arithmeticOp(func(a, b int) int { return a - b })
e.words["*"] = e.arithmeticOp(func(a, b int) int { return a * b })
e.words["/"] = func() error {
a, b, err := e.popTwo()
if err != nil {
return err
}
if b == 0 {
return errors.New("division by zero")
}
e.push(a / b)
return nil
}
e.words["dup"] = func() error {
size := len(e.stack)
if size == 0 {
return errors.New("not enough elements on stack")
}
e.stack = append(e.stack, e.stack[size-1])
return nil
}
e.words["over"] = func() error {
size := len(e.stack)
if size < 2 {
return errors.New("not enough elements on stack")
}
e.stack = append(e.stack, e.stack[size-2])
return nil
}
e.words["drop"] = func() error {
_, err := e.pop()
return err
}
e.words["swap"] = func() error {
size := len(e.stack)
if size < 2 {
return errors.New("not enough elements on stack")
}
e.stack[size-1], e.stack[size-2] = e.stack[size-2], e.stack[size-1]
return nil
}
}
func (e *Evaluator) getFunc(word string) (func() error, error) {
call, found := e.words[word]
if found {
return call, nil
}
num, err := strconv.Atoi(word)
if err != nil {
return nil, errors.New("word is not numerical or is not defined")
}
call = func() error {
e.push(num)
return nil
}
return call, nil
}
func (e *Evaluator) defineWord(word string, def string) error {
if _, err := strconv.Atoi(word); err == nil {
return errors.New("word definition must not be numeric")
}
definitionWords := strings.Fields(def)
callChain := make([](func() error), len(definitionWords))
for i, w := range definitionWords {
call, err := e.getFunc(w)
if err != nil {
return err
}
callChain[i] = call
}
e.words[word] = func() error {
for _, call := range callChain {
err := call()
if err != nil {
return err
}
}
return nil
}
return nil
}
// NewEvaluator creates evaluator.
func NewEvaluator() *Evaluator {
eval := &Evaluator{make([]int, 0), make(map[string](func() error))}
eval.initBasicWords()
return eval
}
// Process evaluates sequence of words or definition.
//
// Returns resulting stack state and an error.
func (e *Evaluator) Process(row string) ([]int, error) {
row = strings.ToLower(row)
if strings.HasPrefix(row, ":") && strings.HasSuffix(row, ";") {
row = strings.Trim(row, " :;")
wordAndDef := strings.SplitN(row, " ", 2)
if len(wordAndDef) < 2 {
return e.stack, errors.New("couldn't define a new word - incorrect syntax")
}
word, def := wordAndDef[0], wordAndDef[1]
err := e.defineWord(word, def)
if err != nil {
return e.stack, err
}
return e.stack, nil
}
for _, word := range strings.Fields(row) {
call, err := e.getFunc(word)
if err != nil {
return e.stack, err
}
err = call()
if err != nil {
return e.stack, err
}
}
return e.stack, nil
}