make IF/ELSE/THEN work!

This commit is contained in:
David 2021-02-20 15:52:27 -05:00
parent 2a1a6fc0d2
commit 6efa404712
7 changed files with 156 additions and 82 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -23,7 +24,7 @@ func (b *Builtins) Colon(c *Context) func() error {
// Semicolon sets the COMPILE/IMMEDIATE flag back to IMMEDIATE and adds the defintion to the dictionary // Semicolon sets the COMPILE/IMMEDIATE flag back to IMMEDIATE and adds the defintion to the dictionary
func (b *Builtins) Semicolon(c *Context) func() error { func (b *Builtins) Semicolon(c *Context) func() error {
return func() error { return func() error {
c.Dictionary.AddWord(c.Words[0], nil, c.Words[1:], false) c.Dictionary.AddWord(c.Words[0], Word{Name: c.Words[0], Source: c.Words[1:]})
c.Words = []string{} c.Words = []string{}
c.Flags.SetFlag("Immediate", true) c.Flags.SetFlag("Immediate", true)
return nil return nil
@ -391,24 +392,62 @@ func (b *Builtins) RFetch(s *Stack, r *Stack) func() error {
} }
} }
// If checks if NOS != 0 and conditionally executes any following statements if so // If checks if TOS != 0 and executes any following statements if it is. If TOS == 0, skip execution until ELSE or THEN.
func (b *Builtins) If(s *Stack, r *Stack) func() error { // Nested IFs and ELSE/THENs inside "non-executing" blocks still manipulate the if stack, in order to properly balance
// IFs and THENs. i.e. `0 IF 0 IF 42 EMIT THEN 42 EMIT THEN` should not print anything, but a naive "go until you see any THEN"
// would output "* ".
func (b *Builtins) If(s *Stack, i *Stack) func() error {
return func() error { return func() error {
// check to see the current IF state. If we're inside a non-executing branch,
// add our own non-execution state, AND tell ELSE to skip its work to boot.
itop, err := i.Pick(0)
if err != nil && !errors.Is(err, ErrUnderflow) {
return err
} else if err == nil && itop == 0 {
i.Push(-1)
return nil
}
tos, err := s.Pop()
if err != nil {
return err
}
// TOS was 0, don't execute this branch
if tos == 0 {
i.Push(0)
} else {
// TOS wasn't 0, execute until we see ELSE/THEN
i.Push(1)
}
return nil return nil
} }
} }
// Else executes a separate piece of code if NOS == 0 after an if check // Else executes a separate piece of code if the IF stack
func (b *Builtins) Else(s *Stack, r *Stack) func() error { func (b *Builtins) Else(i *Stack) func() error {
return func() error { return func() error {
itop, err := i.Pop()
if err != nil {
return err
}
// don't execute until we see our THEN
if itop == -1 || itop == 1 {
i.Push(0)
}
// we weren't running code, but now we can until THEN
if itop == 0 {
i.Push(1)
}
return nil return nil
} }
} }
// Then ends an If/Else block // Then ends an If/Else block
func (b *Builtins) Then(s *Stack, r *Stack) func() error { func (b *Builtins) Then(i *Stack) func() error {
return func() error { return func() error {
return nil // Pop off the existing IF/ELSE state and return
_, err := i.Pop()
return err
} }
} }

43
eval.go
View File

@ -9,8 +9,9 @@ import (
// Context is a set of Dictionary + Stacks + Flags representing a runtime environment // Context is a set of Dictionary + Stacks + Flags representing a runtime environment
type Context struct { type Context struct {
Dictionary Dictionary Dictionary Dictionary
Stack *Stack Stack *Stack // main stack
RStack *Stack RStack *Stack // "return" stack, for handling loops/backward jumps
IfStack *Stack // tracks nested branches
Flags Flags Flags Flags
Words []string Words []string
} }
@ -43,27 +44,25 @@ func (c *Context) Eval(line string) error {
continue continue
} }
int, err := strconv.Atoi(sword) ifcheck, _ := c.IfStack.Pick(0)
if err == nil { if len(c.IfStack.values) == 0 || (len(c.IfStack.values) > 0 && ifcheck == 1) || w.BranchCheck {
// it was a number! put it on the stack. int, err := strconv.Atoi(sword)
c.Stack.Push(int) if err == nil {
word = []byte{} // it was a number! put it on the stack.
continue c.Stack.Push(int)
} word = []byte{}
continue
}
// it wasn't a number. Is it a word we know? // run word
w, err = c.Dictionary.GetWord(sword) c.RStack.Push(i)
if err != nil { if err = c.Exec(w); err != nil {
return fmt.Errorf("could not parse %s; %v", w.Name, err) return err
} }
// run word i, err = c.RStack.Pop()
c.RStack.Push(i) if err != nil {
if err = c.Exec(w); err != nil { return fmt.Errorf("error while popping from return stack: %v", err)
return err }
}
i, err = c.RStack.Pop()
if err != nil {
return fmt.Errorf("error while popping from return stack: %v", err)
} }
word = []byte{} word = []byte{}
default: default:

78
main.go
View File

@ -11,6 +11,7 @@ import (
func main() { func main() {
stack := Stack{values: []int{}} stack := Stack{values: []int{}}
rstack := Stack{values: []int{}} rstack := Stack{values: []int{}}
ifstack := Stack{values: []int{}}
dict := Dictionary{} dict := Dictionary{}
@ -18,6 +19,7 @@ func main() {
Dictionary: dict, Dictionary: dict,
Stack: &stack, Stack: &stack,
RStack: &rstack, RStack: &rstack,
IfStack: &ifstack,
Flags: Flags{ Flags: Flags{
"Immediate": true, "Immediate": true,
"Comment": false, "Comment": false,
@ -27,50 +29,54 @@ func main() {
b := &Builtins{} b := &Builtins{}
// word definitions // word definitions
dict.AddWord(":", b.Colon(&c), nil, false) dict.AddWord(":", Word{Name: ":", Impl: b.Colon(&c)})
dict.AddWord(";", b.Semicolon(&c), nil, true) dict.AddWord(";", Word{Name: ";", Impl: b.Semicolon(&c), Immediate: true})
// comments // comments
dict.AddWord("(", b.OpenComment(&c), nil, true) dict.AddWord("(", Word{Name: "(", Impl: b.OpenComment(&c), Immediate: true})
dict.AddWord(")", b.CloseComment(&c), nil, true) dict.AddWord(")", Word{Name: ")", Impl: b.CloseComment(&c), Immediate: true})
// math // math
dict.AddWord("+", b.Add(&stack), nil, false) dict.AddWord("+", Word{Name: "+", Impl: b.Add(&stack)})
dict.AddWord("-", b.Sub(&stack), nil, false) dict.AddWord("-", Word{Name: "-", Impl: b.Sub(&stack)})
dict.AddWord("*", b.Mul(&stack), nil, false) dict.AddWord("*", Word{Name: "*", Impl: b.Mul(&stack)})
dict.AddWord("/", b.Div(&stack), nil, false) dict.AddWord("/", Word{Name: "/", Impl: b.Div(&stack)})
// output // output
dict.AddWord(".", b.Print(os.Stdout, &stack), nil, false) dict.AddWord(".", Word{Name: ".", Impl: b.Print(os.Stdout, &stack)})
dict.AddWord("EMIT", b.Emit(os.Stdout, &stack), nil, false) dict.AddWord("EMIT", Word{Name: "EMIT", Impl: b.Emit(os.Stdout, &stack)})
dict.AddWord("CR", nil, []string{"10", "EMIT"}, false) // emit a newline dict.AddWord("CR", Word{Name: "CR", Source: []string{"10", "EMIT"}}) // emit a newline
// logic // logic
dict.AddWord("=", b.Eq(&stack), nil, false) dict.AddWord("=", Word{Name: "=", Impl: b.Eq(&stack)})
dict.AddWord("0=", nil, []string{"0", "="}, false) dict.AddWord("0=", Word{Name: "0=", Source: []string{"0", "="}})
dict.AddWord("<>", b.NEq(&stack), nil, false) dict.AddWord("<>", Word{Name: "<>", Impl: b.NEq(&stack)})
dict.AddWord(">", b.Gt(&stack), nil, false) dict.AddWord(">", Word{Name: ">", Impl: b.Gt(&stack)})
dict.AddWord("<", b.Lt(&stack), nil, false) dict.AddWord("<", Word{Name: "<", Impl: b.Lt(&stack)})
dict.AddWord(">=", b.GtEq(&stack), nil, false) dict.AddWord(">=", Word{Name: ">=", Impl: b.GtEq(&stack)})
dict.AddWord("<=", b.LtEq(&stack), nil, false) dict.AddWord("<=", Word{Name: "<=", Impl: b.LtEq(&stack)})
dict.AddWord("0<", nil, []string{"0", "<"}, false) dict.AddWord("0<", Word{Name: "0<", Source: []string{"0", "<"}})
dict.AddWord("0>", nil, []string{"0", ">"}, false) dict.AddWord("0>", Word{Name: "0>", Source: []string{"0", ">"}})
// stack manipulation // stack manipulation
dict.AddWord("DUP", b.Dup(&stack), nil, false) dict.AddWord("DUP", Word{Name: "DUP", Impl: b.Dup(&stack)})
dict.AddWord("SWAP", b.Swap(&stack), nil, false) dict.AddWord("SWAP", Word{Name: "SWAP", Impl: b.Swap(&stack)})
dict.AddWord("OVER", b.Over(&stack), nil, false) dict.AddWord("OVER", Word{Name: "OVER", Impl: b.Over(&stack)})
dict.AddWord("DROP", b.Drop(&stack), nil, false) dict.AddWord("DROP", Word{Name: "DROP", Impl: b.Drop(&stack)})
dict.AddWord("ROT", b.Rot(&stack), nil, false) dict.AddWord("ROT", Word{Name: "ROT", Impl: b.Rot(&stack)})
// debugging // debugging
dict.AddWord("WORDS", b.Words(dict), nil, false) dict.AddWord("WORDS", Word{Name: "WORDS", Impl: b.Words(dict)})
dict.AddWord("FLAGS", b.Flags(c), nil, false) dict.AddWord("FLAGS", Word{Name: "FLAGS", Impl: b.Flags(c)})
dict.AddWord(".S", b.Debug(&stack), nil, false) dict.AddWord(".S", Word{Name: ".S", Impl: b.Debug(&stack)})
dict.AddWord(".R", b.Debug(&rstack), nil, false) dict.AddWord(".R", Word{Name: ".R", Impl: b.Debug(&rstack)})
dict.AddWord("R>", b.RFrom(&stack, &rstack), nil, false) dict.AddWord(".I", Word{Name: ".I", Impl: b.Debug(&ifstack)})
dict.AddWord(">R", b.ToR(&stack, &rstack), nil, false) dict.AddWord("R>", Word{Name: "R>", Impl: b.RFrom(&stack, &rstack)})
dict.AddWord("R@", b.RFetch(&stack, &rstack), nil, false) dict.AddWord(">R", Word{Name: ">R", Impl: b.ToR(&stack, &rstack)})
dict.AddWord("R@", Word{Name: "R@", Impl: b.RFetch(&stack, &rstack)})
// branching // branching
dict.AddWord("DO", b.Do(&stack, &rstack), nil, false) dict.AddWord("IF", Word{Name: "IF", Impl: b.If(&stack, &ifstack), BranchCheck: true})
dict.AddWord("LOOP", b.Loop(&stack, &rstack), nil, false) dict.AddWord("ELSE", Word{Name: "ELSE", Impl: b.Else(&ifstack), BranchCheck: true})
dict.AddWord("I", b.I(&stack, &rstack), nil, false) dict.AddWord("THEN", Word{Name: "THEN", Impl: b.Then(&ifstack), BranchCheck: true})
dict.AddWord("DO", Word{Name: "DO", Impl: b.Do(&stack, &rstack)})
dict.AddWord("LOOP", Word{Name: "LOOP", Impl: b.Loop(&stack, &rstack)})
dict.AddWord("I", Word{Name: "I", Impl: b.I(&stack, &rstack)})
// exit // exit
dict.AddWord("BYE", b.Quit(), nil, false) dict.AddWord("BYE", Word{Name: "BYE", Impl: b.Quit()})
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
fmt.Print("prosper\n") fmt.Print("prosper\n")

View File

@ -2,6 +2,12 @@ package main
import "fmt" import "fmt"
// ErrEmpty denotes an empty stack
var ErrEmpty = fmt.Errorf("stack empty")
// ErrUnderflow denotes a PICK that tried to pick a value that didn't exist
var ErrUnderflow = fmt.Errorf("cannot pick value from beyond stack depth")
// Stack is a stack of integers with no defined max depth // Stack is a stack of integers with no defined max depth
type Stack struct { type Stack struct {
values []int values []int
@ -10,7 +16,7 @@ type Stack struct {
// Pop returns the top of the stack // Pop returns the top of the stack
func (s *Stack) Pop() (int, error) { func (s *Stack) Pop() (int, error) {
if len(s.values) == 0 { if len(s.values) == 0 {
return 0, fmt.Errorf("stack empty") return 0, ErrEmpty
} }
i := s.values[0] i := s.values[0]
s.values = s.values[1:] s.values = s.values[1:]
@ -24,8 +30,8 @@ func (s *Stack) Push(i int) {
// Pick grabs a value from the given depth in the stack, non-destructively. // Pick grabs a value from the given depth in the stack, non-destructively.
func (s *Stack) Pick(i int) (int, error) { func (s *Stack) Pick(i int) (int, error) {
if len(s.values) < i { if len(s.values) <= i {
return 0, fmt.Errorf("cannot pick value from beyond stack depth") return 0, ErrUnderflow
} }
return s.values[i], nil return s.values[i], nil
} }

View File

@ -42,3 +42,25 @@ func TestStackPush(t *testing.T) {
t.Fail() t.Fail()
} }
} }
func TestStackPick(t *testing.T) {
s := Stack{}
i, err := s.Pick(0)
if err == nil {
t.Fail()
}
if i != 0 {
t.Fail()
}
s = Stack{
values: []int{1, 2, 3},
}
i, err = s.Pick(2)
if err != nil {
t.Fail()
}
if i != 3 {
t.Fail()
}
}

View File

@ -2,25 +2,24 @@ package main
import "fmt" import "fmt"
// ErrNoWordFound flags if the requested word is not in the dictionary
var ErrNoWordFound = fmt.Errorf("no word found")
// Dictionary is a simple map of names to words // Dictionary is a simple map of names to words
type Dictionary map[string]Word type Dictionary map[string]Word
// A Word defines a subroutine // A Word defines a subroutine
type Word struct { type Word struct {
Name string Name string
Impl func() error Impl func() error
Source []string Source []string
Immediate bool Immediate bool
BranchCheck bool
} }
// AddWord inserts a new word into the dictonary, optionally overwriting the existing word // AddWord inserts a new word into the dictonary, overwriting any existing word by that name
func (d Dictionary) AddWord(name string, impl func() error, source []string, immediate bool) { func (d Dictionary) AddWord(name string, w Word) {
d[name] = Word{ d[name] = w
Name: name,
Impl: impl,
Source: source,
Immediate: immediate,
}
} }
// GetWord returns a word from the dictionary or an error if it's undefined // GetWord returns a word from the dictionary or an error if it's undefined
@ -29,7 +28,7 @@ func (d Dictionary) GetWord(name string) (Word, error) {
if !ok { if !ok {
return Word{ return Word{
Name: name, Name: name,
}, fmt.Errorf("no word found") }, ErrNoWordFound
} }
return w, nil return w, nil
} }

View File

@ -5,7 +5,10 @@ import "testing"
func TestDictionary(t *testing.T) { func TestDictionary(t *testing.T) {
d := Dictionary{} d := Dictionary{}
d.AddWord("INC", nil, []string{"1", "+"}, false) d.AddWord("INC", Word{
Name: "INC",
Source: []string{"1", "+"},
})
w, err := d.GetWord("INC") w, err := d.GetWord("INC")
if err != nil { if err != nil {
t.Fail() t.Fail()