package main import ( "fmt" "io" "strconv" "strings" ) // Context is a set of Dictionary + Stacks + Flags representing a runtime environment type Context struct { Dictionary Dictionary Stack *Stack // main stack RStack *Stack // "return" stack, for handling loops/backward jumps IfStack *Stack // tracks nested branches Flags Flags Words []string Memory Memory StringBuffer string Output io.Writer } // Eval evaulates a given line, recursively descending into given words as needed func (c *Context) Eval(line string) error { // state var word []byte for i := 0; i < len(line); i = i + 1 { switch line[i] { case ' ': sword := strings.TrimSpace(string(word)) if len(word) == 0 { // empty space, just continue... continue } // Is this a word we know? w, _ := c.Dictionary.GetWord(sword) // if we're not in immedate mode, and the word isn't immediate, add word to buffer and continue parsing if !c.Flags.GetFlag("Immediate") && !w.Immediate { c.Words = append(c.Words, sword) word = []byte{} continue } ifcheck, _ := c.IfStack.Pick(0) if len(c.IfStack.values) == 0 || (len(c.IfStack.values) > 0 && ifcheck == 1) || w.BranchCheck { // run word c.RStack.Push(i) err := c.Exec(w, line[i:]) if err != nil { return err } i, err = c.RStack.Pop() if err != nil { return fmt.Errorf("error while popping from return stack: %v", err) } } word = []byte{} default: word = append(word, line[i]) } } return nil } // Exec wraps the branched execution of words (either built-in or user-defined) func (c *Context) Exec(w Word, s string) error { if w.Impl != nil { // we have an implementation for that word. Run it. err := w.Impl(s) if err != nil { return fmt.Errorf("error during built-in: %w", err) } } else if len(w.Source) != 0 { // user-defined word; let's descend... err := c.Eval(strings.Join(w.Source, " ") + " ") if err != nil { return fmt.Errorf("error during nested evaluation of word %+v: %w", w, err) } } else if w.Variable > 0 { // it's a variable, let's get that address and put it on the stack c.Stack.Push(w.Variable) } else { it, err := strconv.Atoi(w.Name) if err == nil { // it was a number! put it on the stack. c.Stack.Push(it) } else { return fmt.Errorf("unable to parse word %s", w.Name) } } return nil }