//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 }