From 9d3f61338f45b5d74463ed543345bdebdfba0589 Mon Sep 17 00:00:00 2001 From: David Ashby Date: Sun, 14 Feb 2021 20:26:30 -0500 Subject: [PATCH] add DO LOOP --- builtins.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++++-- eval.go | 64 ++++++++++++++++++++++++++++------------------ main.go | 58 +++++++++++++++++++++-------------------- words.go | 16 +++++++----- words_test.go | 2 +- 5 files changed, 149 insertions(+), 62 deletions(-) diff --git a/builtins.go b/builtins.go index 1b685b2..950a329 100644 --- a/builtins.go +++ b/builtins.go @@ -362,16 +362,83 @@ func (b *Builtins) Then(s *Stack, r *Stack) func() error { } } -// Do sets up a loop +// Do sets up a loop by marking its own location as the return code. +// It also puts TOS and NOS onto the return stack to use as loop control variables. func (b *Builtins) Do(s *Stack, r *Stack) func() error { return func() error { + tors, err := r.Pop() + if err != nil { + return err + } + tos, err := s.Pop() + if err != nil { + return err + } + nos, err := s.Pop() + if err != nil { + return err + } + r.Push(nos) + r.Push(tos) + r.Push(tors) + r.Push(tors) return nil } } -// Loop closes a loop +// Loop closes a loop by removing its own location from the stack and letting the one that Do inserted get returned. +// If it's reached the end of the loop (checking the loop-control variables _underneath_ the return vars), +// it continues, otherwise it re-adds the original location of the Do and returns. func (b *Builtins) Loop(s *Stack, r *Stack) func() error { return func() error { + tors, err := r.Pop() + if err != nil { + return err + } + nors, err := r.Pop() + if err != nil { + return err + } + counter, err := r.Pop() + if err != nil { + return err + } + target, err := r.Pop() + if err != nil { + return err + } + counter = counter + 1 + if counter == target { + r.Push(tors) + } else { + r.Push(target) + r.Push(counter) + r.Push(nors) + r.Push(nors) + } + return nil + } +} + +// I puts the current value of the loop counter on the top of the stack +func (b *Builtins) I(s *Stack, r *Stack) func() error { + return func() error { + tors, err := r.Pop() + if err != nil { + return err + } + nors, err := r.Pop() + if err != nil { + return err + } + counter, err := r.Pop() + if err != nil { + return err + } + s.Push(counter) + r.Push(counter) + r.Push(nors) + r.Push(tors) return nil } } diff --git a/eval.go b/eval.go index af03d4c..aa70682 100644 --- a/eval.go +++ b/eval.go @@ -39,7 +39,7 @@ func (c *Context) Eval(line string) error { if line[i] == ':' { immediate = false } else if line[i-1] == ' ' && line[i] == ';' { - c.Dictionary.AddWord(words[0], nil, words[1:]) + c.Dictionary.AddWord(words[0], nil, words[1:], false) word = []byte{} immediate = true } @@ -47,19 +47,26 @@ func (c *Context) Eval(line string) error { word = append(word, line[i]) } case ' ': + sword := strings.TrimSpace(string(word)) if !immediate { // continue building our subroutine if we're not in immediate mode... if len(word) != 0 { // don't add empty words to our list - words = append(words, strings.TrimSpace(string(word))) - word = []byte{} + // Was that a word we know? + w, _ := c.Dictionary.GetWord(sword) + // check if it's an IMMEDIATE mode toggle word + immediate = w.Immediate + if !immediate { + words = append(words, sword) + word = []byte{} + continue + } } - continue } if len(word) == 0 || comment { // empty space, just continue... continue } - int, err := strconv.Atoi(string(word)) + int, err := strconv.Atoi(sword) if err == nil { // it was a number! put it on the stack. c.Stack.Push(int) @@ -68,30 +75,20 @@ func (c *Context) Eval(line string) error { } // it wasn't a number. Is it a word we know? - w, err := c.Dictionary.GetWord(string(word)) + w, err := c.Dictionary.GetWord(sword) if err != nil { return fmt.Errorf("could not parse %s; %v", w.Name, err) } - if w.Impl != nil { - // we have an implementation for that word. Run it. - err := w.Impl() - if err != nil { - return err - } - word = []byte{} - } else if len(w.Source) != 0 { - // user-defined word; let's descend... - c.RStack.Push(i) - err := c.Eval(strings.Join(w.Source, " ") + " ") - 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{} + // run word + c.RStack.Push(i) + if err = c.Exec(w); 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: if !comment { word = append(word, line[i]) @@ -101,3 +98,20 @@ func (c *Context) Eval(line string) error { return nil } + +func (c *Context) Exec(w Word) error { + if w.Impl != nil { + // we have an implementation for that word. Run it. + err := w.Impl() + if err != nil { + return err + } + } else if len(w.Source) != 0 { + // user-defined word; let's descend... + err := c.Eval(strings.Join(w.Source, " ") + " ") + if err != nil { + return err + } + } + return nil +} diff --git a/main.go b/main.go index 9ac3d19..7ba8868 100644 --- a/main.go +++ b/main.go @@ -14,33 +14,37 @@ func main() { dict := Dictionary{} b := &Builtins{} - dict.AddWord("+", b.Add(&stack), nil) - dict.AddWord("-", b.Sub(&stack), nil) - dict.AddWord("*", b.Mul(&stack), nil) - dict.AddWord("/", b.Div(&stack), nil) - dict.AddWord(".", b.Print(&stack), nil) - dict.AddWord("=", b.Eq(&stack), nil) - dict.AddWord("0=", nil, []string{"0", "="}) - dict.AddWord("<>", b.NEq(&stack), nil) - dict.AddWord(">", b.Gt(&stack), nil) - dict.AddWord("<", b.Lt(&stack), nil) - dict.AddWord(">=", b.GtEq(&stack), nil) - dict.AddWord("<=", b.LtEq(&stack), nil) - dict.AddWord("0<", nil, []string{"0", "<"}) - dict.AddWord("0>", nil, []string{"0", ">"}) - dict.AddWord("DUP", b.Dup(&stack), nil) - dict.AddWord("SWAP", b.Swap(&stack), nil) - dict.AddWord("OVER", b.Over(&stack), nil) - dict.AddWord("DROP", b.Drop(&stack), nil) - dict.AddWord("ROT", b.Rot(&stack), nil) - dict.AddWord("WORDS", b.Words(dict), nil) - dict.AddWord(".S", b.Debug(&stack), nil) - dict.AddWord("EMIT", b.Emit(&stack), nil) - dict.AddWord("R>", b.RFrom(&stack, &rstack), nil) - dict.AddWord(">R", b.ToR(&stack, &rstack), nil) - dict.AddWord("R@", b.RFetch(&stack, &rstack), nil) - dict.AddWord("CR", b.CR(), nil) - dict.AddWord("QUIT", b.Quit(), nil) + dict.AddWord("+", b.Add(&stack), nil, false) + dict.AddWord("-", b.Sub(&stack), nil, false) + dict.AddWord("*", b.Mul(&stack), nil, false) + dict.AddWord("/", b.Div(&stack), nil, false) + dict.AddWord(".", b.Print(&stack), nil, false) + dict.AddWord("=", b.Eq(&stack), nil, false) + dict.AddWord("0=", nil, []string{"0", "="}, false) + dict.AddWord("<>", b.NEq(&stack), nil, false) + dict.AddWord(">", b.Gt(&stack), nil, false) + dict.AddWord("<", b.Lt(&stack), nil, false) + dict.AddWord(">=", b.GtEq(&stack), nil, false) + dict.AddWord("<=", b.LtEq(&stack), nil, false) + dict.AddWord("0<", nil, []string{"0", "<"}, false) + dict.AddWord("0>", nil, []string{"0", ">"}, false) + dict.AddWord("DUP", b.Dup(&stack), nil, false) + dict.AddWord("SWAP", b.Swap(&stack), nil, false) + dict.AddWord("OVER", b.Over(&stack), nil, false) + dict.AddWord("DROP", b.Drop(&stack), nil, false) + dict.AddWord("ROT", b.Rot(&stack), nil, false) + dict.AddWord("WORDS", b.Words(dict), nil, false) + dict.AddWord(".S", b.Debug(&stack), nil, false) + dict.AddWord(".R", b.Debug(&rstack), nil, false) + dict.AddWord("EMIT", b.Emit(&stack), nil, false) + dict.AddWord("R>", b.RFrom(&stack, &rstack), nil, false) + dict.AddWord(">R", b.ToR(&stack, &rstack), nil, false) + dict.AddWord("R@", b.RFetch(&stack, &rstack), nil, false) + dict.AddWord("DO", b.Do(&stack, &rstack), nil, false) + dict.AddWord("LOOP", b.Loop(&stack, &rstack), nil, false) + dict.AddWord("I", b.I(&stack, &rstack), nil, false) + dict.AddWord("CR", b.CR(), nil, false) + dict.AddWord("QUIT", b.Quit(), nil, false) c := Context{ Dictionary: dict, diff --git a/words.go b/words.go index c42f8ea..ade5d2a 100644 --- a/words.go +++ b/words.go @@ -7,17 +7,19 @@ type Dictionary map[string]Word // A Word defines a subroutine type Word struct { - Name string - Impl func() error - Source []string + Name string + Impl func() error + Source []string + Immediate bool } // AddWord inserts a new word into the dictonary, optionally overwriting the existing word -func (d Dictionary) AddWord(name string, impl func() error, source []string) { +func (d Dictionary) AddWord(name string, impl func() error, source []string, immediate bool) { d[name] = Word{ - Name: name, - Impl: impl, - Source: source, + Name: name, + Impl: impl, + Source: source, + Immediate: immediate, } } diff --git a/words_test.go b/words_test.go index a9e468c..749612b 100644 --- a/words_test.go +++ b/words_test.go @@ -5,7 +5,7 @@ import "testing" func TestDictionary(t *testing.T) { d := Dictionary{} - d.AddWord("INC", nil, []string{"1", "+"}) + d.AddWord("INC", nil, []string{"1", "+"}, false) w, err := d.GetWord("INC") if err != nil { t.Fail()