diff --git a/forth/eval.go b/forth/eval.go index e0b5d66..aeead67 100644 --- a/forth/eval.go +++ b/forth/eval.go @@ -2,17 +2,173 @@ 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 errors.New("not enough elements on stack") + } + 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 errors.New("not enough elements on stack") + } + 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 { + _, err := strconv.Atoi(word) + if 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 { - return &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) { - return nil, nil + 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 }