make IF/ELSE/THEN work!
This commit is contained in:
parent
2a1a6fc0d2
commit
6efa404712
53
builtins.go
53
builtins.go
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
eval.go
13
eval.go
@ -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,6 +44,8 @@ func (c *Context) Eval(line string) error {
|
|||||||
continue
|
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)
|
int, err := strconv.Atoi(sword)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// it was a number! put it on the stack.
|
// it was a number! put it on the stack.
|
||||||
@ -51,11 +54,6 @@ func (c *Context) Eval(line string) error {
|
|||||||
continue
|
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
|
// run word
|
||||||
c.RStack.Push(i)
|
c.RStack.Push(i)
|
||||||
if err = c.Exec(w); err != nil {
|
if err = c.Exec(w); err != nil {
|
||||||
@ -65,6 +63,7 @@ func (c *Context) Eval(line string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error while popping from return stack: %v", err)
|
return fmt.Errorf("error while popping from return stack: %v", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
word = []byte{}
|
word = []byte{}
|
||||||
default:
|
default:
|
||||||
word = append(word, line[i])
|
word = append(word, line[i])
|
||||||
|
78
main.go
78
main.go
@ -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")
|
||||||
|
12
stack.go
12
stack.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
17
words.go
17
words.go
@ -2,6 +2,9 @@ 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
|
||||||
|
|
||||||
@ -11,16 +14,12 @@ type Word struct {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user