make IF/ELSE/THEN work!
This commit is contained in:
		
							
								
								
									
										53
									
								
								builtins.go
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								builtins.go
									
									
									
									
									
								
							@@ -1,6 +1,7 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"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
 | 
			
		||||
func (b *Builtins) Semicolon(c *Context) 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.Flags.SetFlag("Immediate", true)
 | 
			
		||||
		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
 | 
			
		||||
func (b *Builtins) If(s *Stack, r *Stack) func() error {
 | 
			
		||||
// If checks if TOS != 0 and executes any following statements if it is. If TOS == 0, skip execution until ELSE or THEN.
 | 
			
		||||
// 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 {
 | 
			
		||||
		// 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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Else executes a separate piece of code if NOS == 0 after an if check
 | 
			
		||||
func (b *Builtins) Else(s *Stack, r *Stack) func() error {
 | 
			
		||||
// Else executes a separate piece of code if the IF stack
 | 
			
		||||
func (b *Builtins) Else(i *Stack) 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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 nil
 | 
			
		||||
		// Pop off the existing IF/ELSE state and return
 | 
			
		||||
		_, err := i.Pop()
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								eval.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								eval.go
									
									
									
									
									
								
							@@ -9,8 +9,9 @@ import (
 | 
			
		||||
// Context is a set of Dictionary + Stacks + Flags representing a runtime environment
 | 
			
		||||
type Context struct {
 | 
			
		||||
	Dictionary Dictionary
 | 
			
		||||
	Stack      *Stack
 | 
			
		||||
	RStack     *Stack
 | 
			
		||||
	Stack      *Stack // main stack
 | 
			
		||||
	RStack     *Stack // "return" stack, for handling loops/backward jumps
 | 
			
		||||
	IfStack    *Stack // tracks nested branches
 | 
			
		||||
	Flags      Flags
 | 
			
		||||
	Words      []string
 | 
			
		||||
}
 | 
			
		||||
@@ -43,6 +44,8 @@ func (c *Context) Eval(line string) error {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ifcheck, _ := c.IfStack.Pick(0)
 | 
			
		||||
			if len(c.IfStack.values) == 0 || (len(c.IfStack.values) > 0 && ifcheck == 1) || w.BranchCheck {
 | 
			
		||||
				int, err := strconv.Atoi(sword)
 | 
			
		||||
				if err == nil {
 | 
			
		||||
					// it was a number! put it on the stack.
 | 
			
		||||
@@ -51,11 +54,6 @@ func (c *Context) Eval(line string) error {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			// it wasn't a number. Is it a word we know?
 | 
			
		||||
			w, err = c.Dictionary.GetWord(sword)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("could not parse %s; %v", w.Name, err)
 | 
			
		||||
			}
 | 
			
		||||
				// run word
 | 
			
		||||
				c.RStack.Push(i)
 | 
			
		||||
				if err = c.Exec(w); err != nil {
 | 
			
		||||
@@ -65,6 +63,7 @@ func (c *Context) Eval(line string) error {
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return fmt.Errorf("error while popping from return stack: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			word = []byte{}
 | 
			
		||||
		default:
 | 
			
		||||
			word = append(word, line[i])
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										78
									
								
								main.go
									
									
									
									
									
								
							@@ -11,6 +11,7 @@ import (
 | 
			
		||||
func main() {
 | 
			
		||||
	stack := Stack{values: []int{}}
 | 
			
		||||
	rstack := Stack{values: []int{}}
 | 
			
		||||
	ifstack := Stack{values: []int{}}
 | 
			
		||||
 | 
			
		||||
	dict := Dictionary{}
 | 
			
		||||
 | 
			
		||||
@@ -18,6 +19,7 @@ func main() {
 | 
			
		||||
		Dictionary: dict,
 | 
			
		||||
		Stack:      &stack,
 | 
			
		||||
		RStack:     &rstack,
 | 
			
		||||
		IfStack:    &ifstack,
 | 
			
		||||
		Flags: Flags{
 | 
			
		||||
			"Immediate": true,
 | 
			
		||||
			"Comment":   false,
 | 
			
		||||
@@ -27,50 +29,54 @@ func main() {
 | 
			
		||||
 | 
			
		||||
	b := &Builtins{}
 | 
			
		||||
	// word definitions
 | 
			
		||||
	dict.AddWord(":", b.Colon(&c), nil, false)
 | 
			
		||||
	dict.AddWord(";", b.Semicolon(&c), nil, true)
 | 
			
		||||
	dict.AddWord(":", Word{Name: ":", Impl: b.Colon(&c)})
 | 
			
		||||
	dict.AddWord(";", Word{Name: ";", Impl: b.Semicolon(&c), Immediate: true})
 | 
			
		||||
	// comments
 | 
			
		||||
	dict.AddWord("(", b.OpenComment(&c), nil, true)
 | 
			
		||||
	dict.AddWord(")", b.CloseComment(&c), nil, true)
 | 
			
		||||
	dict.AddWord("(", Word{Name: "(", Impl: b.OpenComment(&c), Immediate: true})
 | 
			
		||||
	dict.AddWord(")", Word{Name: ")", Impl: b.CloseComment(&c), Immediate: true})
 | 
			
		||||
	// math
 | 
			
		||||
	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("+", Word{Name: "+", Impl: b.Add(&stack)})
 | 
			
		||||
	dict.AddWord("-", Word{Name: "-", Impl: b.Sub(&stack)})
 | 
			
		||||
	dict.AddWord("*", Word{Name: "*", Impl: b.Mul(&stack)})
 | 
			
		||||
	dict.AddWord("/", Word{Name: "/", Impl: b.Div(&stack)})
 | 
			
		||||
	// output
 | 
			
		||||
	dict.AddWord(".", b.Print(os.Stdout, &stack), nil, false)
 | 
			
		||||
	dict.AddWord("EMIT", b.Emit(os.Stdout, &stack), nil, false)
 | 
			
		||||
	dict.AddWord("CR", nil, []string{"10", "EMIT"}, false) // emit a newline
 | 
			
		||||
	dict.AddWord(".", Word{Name: ".", Impl: b.Print(os.Stdout, &stack)})
 | 
			
		||||
	dict.AddWord("EMIT", Word{Name: "EMIT", Impl: b.Emit(os.Stdout, &stack)})
 | 
			
		||||
	dict.AddWord("CR", Word{Name: "CR", Source: []string{"10", "EMIT"}}) // emit a newline
 | 
			
		||||
	// logic
 | 
			
		||||
	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("=", Word{Name: "=", Impl: b.Eq(&stack)})
 | 
			
		||||
	dict.AddWord("0=", Word{Name: "0=", Source: []string{"0", "="}})
 | 
			
		||||
	dict.AddWord("<>", Word{Name: "<>", Impl: b.NEq(&stack)})
 | 
			
		||||
	dict.AddWord(">", Word{Name: ">", Impl: b.Gt(&stack)})
 | 
			
		||||
	dict.AddWord("<", Word{Name: "<", Impl: b.Lt(&stack)})
 | 
			
		||||
	dict.AddWord(">=", Word{Name: ">=", Impl: b.GtEq(&stack)})
 | 
			
		||||
	dict.AddWord("<=", Word{Name: "<=", Impl: b.LtEq(&stack)})
 | 
			
		||||
	dict.AddWord("0<", Word{Name: "0<", Source: []string{"0", "<"}})
 | 
			
		||||
	dict.AddWord("0>", Word{Name: "0>", Source: []string{"0", ">"}})
 | 
			
		||||
	// stack manipulation
 | 
			
		||||
	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("DUP", Word{Name: "DUP", Impl: b.Dup(&stack)})
 | 
			
		||||
	dict.AddWord("SWAP", Word{Name: "SWAP", Impl: b.Swap(&stack)})
 | 
			
		||||
	dict.AddWord("OVER", Word{Name: "OVER", Impl: b.Over(&stack)})
 | 
			
		||||
	dict.AddWord("DROP", Word{Name: "DROP", Impl: b.Drop(&stack)})
 | 
			
		||||
	dict.AddWord("ROT", Word{Name: "ROT", Impl: b.Rot(&stack)})
 | 
			
		||||
	// debugging
 | 
			
		||||
	dict.AddWord("WORDS", b.Words(dict), nil, false)
 | 
			
		||||
	dict.AddWord("FLAGS", b.Flags(c), nil, false)
 | 
			
		||||
	dict.AddWord(".S", b.Debug(&stack), nil, false)
 | 
			
		||||
	dict.AddWord(".R", b.Debug(&rstack), 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("WORDS", Word{Name: "WORDS", Impl: b.Words(dict)})
 | 
			
		||||
	dict.AddWord("FLAGS", Word{Name: "FLAGS", Impl: b.Flags(c)})
 | 
			
		||||
	dict.AddWord(".S", Word{Name: ".S", Impl: b.Debug(&stack)})
 | 
			
		||||
	dict.AddWord(".R", Word{Name: ".R", Impl: b.Debug(&rstack)})
 | 
			
		||||
	dict.AddWord(".I", Word{Name: ".I", Impl: b.Debug(&ifstack)})
 | 
			
		||||
	dict.AddWord("R>", Word{Name: "R>", Impl: b.RFrom(&stack, &rstack)})
 | 
			
		||||
	dict.AddWord(">R", Word{Name: ">R", Impl: b.ToR(&stack, &rstack)})
 | 
			
		||||
	dict.AddWord("R@", Word{Name: "R@", Impl: b.RFetch(&stack, &rstack)})
 | 
			
		||||
	// branching
 | 
			
		||||
	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("IF", Word{Name: "IF", Impl: b.If(&stack, &ifstack), BranchCheck: true})
 | 
			
		||||
	dict.AddWord("ELSE", Word{Name: "ELSE", Impl: b.Else(&ifstack), BranchCheck: true})
 | 
			
		||||
	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
 | 
			
		||||
	dict.AddWord("BYE", b.Quit(), nil, false)
 | 
			
		||||
	dict.AddWord("BYE", Word{Name: "BYE", Impl: b.Quit()})
 | 
			
		||||
 | 
			
		||||
	reader := bufio.NewReader(os.Stdin)
 | 
			
		||||
	fmt.Print("prosper\n")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								stack.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								stack.go
									
									
									
									
									
								
							@@ -2,6 +2,12 @@ package main
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
type Stack struct {
 | 
			
		||||
	values []int
 | 
			
		||||
@@ -10,7 +16,7 @@ type Stack struct {
 | 
			
		||||
// Pop returns the top of the stack
 | 
			
		||||
func (s *Stack) Pop() (int, error) {
 | 
			
		||||
	if len(s.values) == 0 {
 | 
			
		||||
		return 0, fmt.Errorf("stack empty")
 | 
			
		||||
		return 0, ErrEmpty
 | 
			
		||||
	}
 | 
			
		||||
	i := s.values[0]
 | 
			
		||||
	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.
 | 
			
		||||
func (s *Stack) Pick(i int) (int, error) {
 | 
			
		||||
	if len(s.values) < i {
 | 
			
		||||
		return 0, fmt.Errorf("cannot pick value from beyond stack depth")
 | 
			
		||||
	if len(s.values) <= i {
 | 
			
		||||
		return 0, ErrUnderflow
 | 
			
		||||
	}
 | 
			
		||||
	return s.values[i], nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,3 +42,25 @@ func TestStackPush(t *testing.T) {
 | 
			
		||||
		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()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								words.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								words.go
									
									
									
									
									
								
							@@ -2,6 +2,9 @@ package main
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
type Dictionary map[string]Word
 | 
			
		||||
 | 
			
		||||
@@ -11,16 +14,12 @@ type Word struct {
 | 
			
		||||
	Impl        func() error
 | 
			
		||||
	Source      []string
 | 
			
		||||
	Immediate   bool
 | 
			
		||||
	BranchCheck 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, immediate bool) {
 | 
			
		||||
	d[name] = Word{
 | 
			
		||||
		Name:      name,
 | 
			
		||||
		Impl:      impl,
 | 
			
		||||
		Source:    source,
 | 
			
		||||
		Immediate: immediate,
 | 
			
		||||
	}
 | 
			
		||||
// AddWord inserts a new word into the dictonary, overwriting any existing word by that name
 | 
			
		||||
func (d Dictionary) AddWord(name string, w Word) {
 | 
			
		||||
	d[name] = w
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
		return Word{
 | 
			
		||||
			Name: name,
 | 
			
		||||
		}, fmt.Errorf("no word found")
 | 
			
		||||
		}, ErrNoWordFound
 | 
			
		||||
	}
 | 
			
		||||
	return w, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,10 @@ import "testing"
 | 
			
		||||
func TestDictionary(t *testing.T) {
 | 
			
		||||
	d := Dictionary{}
 | 
			
		||||
 | 
			
		||||
	d.AddWord("INC", nil, []string{"1", "+"}, false)
 | 
			
		||||
	d.AddWord("INC", Word{
 | 
			
		||||
		Name:   "INC",
 | 
			
		||||
		Source: []string{"1", "+"},
 | 
			
		||||
	})
 | 
			
		||||
	w, err := d.GetWord("INC")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fail()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user