Compare commits
34 Commits
199203e73f
...
master
Author | SHA1 | Date | |
---|---|---|---|
8065d43efd | |||
2aa11b6585 | |||
8e33e13ffc | |||
b7c7e02697 | |||
efe408bed8 | |||
5e782bc728 | |||
7291ca86c8 | |||
0784661e7c | |||
e428c883db | |||
762b6c870c | |||
241470248b | |||
4727312da4 | |||
e998c286d7 | |||
a2e6115248 | |||
583fa79985 | |||
5048b445ae | |||
953764004d | |||
6c5df444da | |||
0676cad34d | |||
ff5346994f | |||
0d322b92df | |||
fee9325155 | |||
048557caf2 | |||
7251f1ba60 | |||
a75db93581 | |||
06ad13dd28 | |||
6efa404712 | |||
2a1a6fc0d2 | |||
1a229d5ddb | |||
7c76bc49af | |||
e5bb216f95 | |||
9d3f61338f | |||
626e90d54c | |||
852aaa6387 |
86
README.md
Normal file
86
README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Prosper
|
||||
|
||||
Prosper is a Forth-like stack-based language. While taking specific influence from Forth, it does not attempt to emulate or implement the ANS Forth standards.
|
||||
|
||||
Currently, the language is entirely interactive -- to explore, simply run `go run .` in the root of this project and an interactive session will begin.
|
||||
|
||||
## Syntax
|
||||
|
||||
Prosper is stack-based, as mentioned, so all the standard stack semantics are present:
|
||||
|
||||
```forth
|
||||
> 1 2 3 4 + * -
|
||||
ok
|
||||
> .
|
||||
-13 ok
|
||||
```
|
||||
|
||||
New words can be defined using `: ;`:
|
||||
|
||||
```forth
|
||||
> : SQUARE DUP * ;
|
||||
ok
|
||||
> 10 SQUARE .
|
||||
100 ok
|
||||
```
|
||||
|
||||
It has a single loop construct, `DO LOOP`:
|
||||
|
||||
```forth
|
||||
> : TOFIVE 5 0 DO I . LOOP ;
|
||||
ok
|
||||
> TOFIVE
|
||||
0 1 2 3 4 ok
|
||||
```
|
||||
|
||||
Branches using `IF ELSE THEN` work:
|
||||
|
||||
```forth
|
||||
> : EMOJI 0 < IF 128201 EMIT ELSE 128200 EMIT THEN ;
|
||||
ok
|
||||
> 100 EMOJI
|
||||
📈 ok
|
||||
> -100 EMOJI
|
||||
📉 ok
|
||||
```
|
||||
|
||||
Here's a Fibonacci implementation:
|
||||
|
||||
```forth
|
||||
> : FIB 2DUP + ;
|
||||
ok
|
||||
> : FIBN 0 1 ROT 1 DO FIB ROT DROP LOOP SWAP DROP ;
|
||||
ok
|
||||
> 10 FIBN .
|
||||
55 ok
|
||||
```
|
||||
|
||||
Propser also has a basic off-stack memory model using variables, constants, and pointers:
|
||||
|
||||
```forth
|
||||
> VARIABLE FOO
|
||||
ok
|
||||
> 10 FOO !
|
||||
ok
|
||||
> FOO @
|
||||
ok
|
||||
> .
|
||||
10 ok
|
||||
> 100 CONSTANT HUNDRED
|
||||
ok
|
||||
> HUNDRED HUNDRED * .
|
||||
10000 ok
|
||||
```
|
||||
|
||||
And, of course, it knows how to quit:
|
||||
|
||||
```forth
|
||||
> BYE
|
||||
bye
|
||||
```
|
||||
|
||||
## Future Plans
|
||||
|
||||
* Rearchitect how compilation works -- currently, non-built-in words are stored as raw sets of strings, which means in the case of things like IF/ELSE/THEN we have to re-parse the tree and use the IFStack to track the parse status. It'd be better to compile to some intermediate representation so that the compiler can store the JMP locations inline and execution just flows from instruction to instruction instead of re-parsing the entire descent-tree every time.
|
||||
* More looping constructs (`EXIT`, `?DO`, and `WHILE` would be nice).
|
||||
* Add ways for Prosper words to hook into the input/output streams (probably required for self-hosting a prosper compiler in prosper, which is a long way off).
|
610
builtins.go
610
builtins.go
@@ -1,6 +1,24 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FALSE is a flag for, what else, false
|
||||
var FALSE = 0
|
||||
|
||||
// TRUE is a flag for, what else, true.
|
||||
// It's -1 because historically, FORTHs would define TRUE as "all bits set to 1 in a cell",
|
||||
// and interpreting that literally flips the sign flag in a signed integer.
|
||||
// More generally, TRUE is defined as "not FALSE" for most purposes, and any non-zero integer will work.
|
||||
var TRUE = -1
|
||||
|
||||
// Builtins is a handy holder for our various default words
|
||||
type Builtins struct{}
|
||||
@@ -8,9 +26,108 @@ type Builtins struct{}
|
||||
// ErrExit is a special sentinel value to cease computation and quit
|
||||
var ErrExit = fmt.Errorf("exit requested")
|
||||
|
||||
// Colon sets the COMPILE/IMMEDIATE flag to COMPILE
|
||||
func (b *Builtins) Colon(c *Context) func(string) error {
|
||||
return func(_ string) error {
|
||||
c.Flags.SetFlag("Immediate", false)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Semicolon sets the COMPILE/IMMEDIATE flag back to IMMEDIATE and adds the defintion to the dictionary
|
||||
func (b *Builtins) Semicolon(c *Context) func(string) error {
|
||||
return func(_ string) error {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// OpenComment puts the parser into an "ignore" mode
|
||||
func (b *Builtins) OpenComment(c *Context) func(string) error {
|
||||
return func(next string) error {
|
||||
c.Flags.SetFlag("StashImmediate", c.Flags.GetFlag("Immediate"))
|
||||
for i := 0; i < len(next); i = i + 1 {
|
||||
switch next[i] {
|
||||
case ')':
|
||||
j, _ := c.RStack.Pop()
|
||||
c.RStack.Push(j + i - 1) // push the end-point onto the stack
|
||||
return nil
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// EOLComment discards the rest of the parsed line
|
||||
func (b *Builtins) EOLComment(c *Context) func(string) error {
|
||||
return func(next string) error {
|
||||
j, _ := c.RStack.Pop()
|
||||
c.RStack.Push(j + len(next)) // push the end-point onto the stack
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CloseComment resumes parsing by consuming itself and resetting the immediate flag
|
||||
func (b *Builtins) CloseComment(c *Context) func(string) error {
|
||||
return func(_ string) error {
|
||||
c.Flags.SetFlag("Immediate", c.Flags.GetFlag("StashImmediate"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OpenQuote consumes text until its closing pair to output
|
||||
func (b *Builtins) OpenQuote(out io.Writer, c *Context, close byte) func(string) error {
|
||||
if out == nil {
|
||||
out = os.Stdout
|
||||
}
|
||||
return func(next string) error {
|
||||
c.Flags.SetFlag("StashImmediate", c.Flags.GetFlag("Immediate"))
|
||||
w := []byte{}
|
||||
for i := 0; i < len(next); i = i + 1 {
|
||||
switch next[i] {
|
||||
case close:
|
||||
fmt.Fprint(out, string(w))
|
||||
j, _ := c.RStack.Pop()
|
||||
c.RStack.Push(j + i - 1) // push the end-point onto the stack
|
||||
return nil
|
||||
default:
|
||||
w = append(w, next[i])
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CloseQuote consumes itself.
|
||||
func (b *Builtins) CloseQuote(c *Context) func(string) error {
|
||||
return func(next string) error {
|
||||
c.Flags.SetFlag("Immediate", c.Flags.GetFlag("StashImmediate"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringBuffer consumes text until its closing byte, then puts that value into the context's stringbuffer
|
||||
func (b *Builtins) StringBuffer(c *Context, close byte) func(string) error {
|
||||
return func(next string) error {
|
||||
var buff bytes.Buffer
|
||||
err := b.OpenQuote(&buff, c, '"')(next)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := ioutil.ReadAll(&buff)
|
||||
c.StringBuffer = string(s)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Eq compares TOS and NOS and puts -1 on the stack if they're equal, 0 otherwise.
|
||||
func (b *Builtins) Eq(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Eq(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -20,17 +137,17 @@ func (b *Builtins) Eq(s *Stack) func() error {
|
||||
return err
|
||||
}
|
||||
if tos == nos {
|
||||
s.Push(-1)
|
||||
s.Push(TRUE)
|
||||
return nil
|
||||
}
|
||||
s.Push(0)
|
||||
s.Push(FALSE)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NEq compares TOS and NOS and puts -1 on the stack if they're not equal, 0 otherwise.
|
||||
func (b *Builtins) NEq(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) NEq(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -40,17 +157,17 @@ func (b *Builtins) NEq(s *Stack) func() error {
|
||||
return err
|
||||
}
|
||||
if tos != nos {
|
||||
s.Push(-1)
|
||||
s.Push(TRUE)
|
||||
return nil
|
||||
}
|
||||
s.Push(0)
|
||||
s.Push(FALSE)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Lt compares TOS and NOS and puts -1 on the stack if TOS is less than NOS, 0 otherwise.
|
||||
func (b *Builtins) Lt(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Lt(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -59,18 +176,18 @@ func (b *Builtins) Lt(s *Stack) func() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tos < nos {
|
||||
s.Push(-1)
|
||||
if nos < tos {
|
||||
s.Push(TRUE)
|
||||
return nil
|
||||
}
|
||||
s.Push(0)
|
||||
s.Push(FALSE)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Gt compares TOS and NOS and puts -1 on the stack if TOS is greater than NOS, 0 otherwise.
|
||||
func (b *Builtins) Gt(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Gt(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -79,18 +196,18 @@ func (b *Builtins) Gt(s *Stack) func() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tos > nos {
|
||||
s.Push(-1)
|
||||
if nos > tos {
|
||||
s.Push(TRUE)
|
||||
return nil
|
||||
}
|
||||
s.Push(0)
|
||||
s.Push(FALSE)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// LtEq compares TOS and NOS and puts -1 on the stack if TOS is less than or equal to NOS, 0 otherwise.
|
||||
func (b *Builtins) LtEq(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) LtEq(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -99,18 +216,18 @@ func (b *Builtins) LtEq(s *Stack) func() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tos <= nos {
|
||||
s.Push(-1)
|
||||
if nos <= tos {
|
||||
s.Push(TRUE)
|
||||
return nil
|
||||
}
|
||||
s.Push(0)
|
||||
s.Push(FALSE)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GtEq compares TOS and NOS and puts -1 on the stack if TOS is greater than or equal to NOS, 0 otherwise.
|
||||
func (b *Builtins) GtEq(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) GtEq(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -119,18 +236,18 @@ func (b *Builtins) GtEq(s *Stack) func() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tos >= nos {
|
||||
s.Push(-1)
|
||||
if nos >= tos {
|
||||
s.Push(TRUE)
|
||||
return nil
|
||||
}
|
||||
s.Push(0)
|
||||
s.Push(FALSE)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add sums the top two numbers on the stack and pushes the result
|
||||
func (b *Builtins) Add(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Add(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -145,8 +262,8 @@ func (b *Builtins) Add(s *Stack) func() error {
|
||||
}
|
||||
|
||||
// Sub performs NOS - TOS and pushes the result
|
||||
func (b *Builtins) Sub(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Sub(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -161,8 +278,8 @@ func (b *Builtins) Sub(s *Stack) func() error {
|
||||
}
|
||||
|
||||
// Mul multiplies the two numbers on the top of the stack and pushes the result
|
||||
func (b *Builtins) Mul(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Mul(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -177,8 +294,8 @@ func (b *Builtins) Mul(s *Stack) func() error {
|
||||
}
|
||||
|
||||
// Div performs NOS/TOS and pushes the (integer!) result
|
||||
func (b *Builtins) Div(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Div(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -192,21 +309,40 @@ func (b *Builtins) Div(s *Stack) func() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Print pops the stack and outputs it to stdout
|
||||
func (b *Builtins) Print(s *Stack) func() error {
|
||||
return func() error {
|
||||
// Mod performs NOS%TOS and pushes the (integer!) modulo result
|
||||
func (b *Builtins) Mod(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print(tos, " ")
|
||||
nos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Push(nos % tos)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Print pops the stack and outputs it to the writer with a trailing space (defaults to stdout)
|
||||
func (b *Builtins) Print(out io.Writer, s *Stack) func(string) error {
|
||||
if out == nil {
|
||||
out = os.Stdout
|
||||
}
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(out, tos, " ")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Dup pops the stack, then pushes two copies onto the stack
|
||||
func (b *Builtins) Dup(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Dup(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -218,8 +354,8 @@ func (b *Builtins) Dup(s *Stack) func() error {
|
||||
}
|
||||
|
||||
// Swap inverts the order of TOS and NOS
|
||||
func (b *Builtins) Swap(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Swap(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -235,34 +371,28 @@ func (b *Builtins) Swap(s *Stack) func() error {
|
||||
}
|
||||
|
||||
// Over duplicates NOS to TOS, resulting in NOS TOS NOS
|
||||
func (b *Builtins) Over(s *Stack) func() error {
|
||||
return func() error {
|
||||
tos, err := s.Pop()
|
||||
func (b *Builtins) Over(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
nos, err := s.Pick(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Push(nos)
|
||||
s.Push(tos)
|
||||
s.Push(nos)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Drop simply discards TOS
|
||||
func (b *Builtins) Drop(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Drop(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
_, err := s.Pop()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Rot cycles the first three items on the stack: TOS 1 2 3 -> TOS 3 1 2
|
||||
func (b *Builtins) Rot(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Rot(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -282,31 +412,66 @@ func (b *Builtins) Rot(s *Stack) func() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Pick duplicates TOS from within the stack to the top
|
||||
func (b *Builtins) Pick(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
swp, err := s.Pick(tos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Push(swp)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Words outputs a list of all known words in the dictionary
|
||||
func (b *Builtins) Words(d Dictionary) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Words(out io.Writer, d Dictionary) func(string) error {
|
||||
if out == nil {
|
||||
out = os.Stdout
|
||||
}
|
||||
return func(_ string) error {
|
||||
for n := range d {
|
||||
fmt.Printf("%s ", n)
|
||||
fmt.Fprintf(out, "%s ", n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Flags outputs a list of all flags
|
||||
func (b *Builtins) Flags(out io.Writer, c *Context) func(string) error {
|
||||
if out == nil {
|
||||
out = os.Stdout
|
||||
}
|
||||
return func(_ string) error {
|
||||
for n := range c.Flags {
|
||||
fmt.Fprintf(out, "%s %v\n", n, c.Flags.GetFlag(n))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Emit outputs the UTF-8 rune for the int on the top of the stack
|
||||
func (b *Builtins) Emit(s *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Emit(out io.Writer, s *Stack) func(string) error {
|
||||
if out == nil {
|
||||
out = os.Stdout
|
||||
}
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print(string(rune(tos)) + " ")
|
||||
fmt.Fprint(out, string(rune(tos))+" ")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ToR pops from the stack to the return stack
|
||||
func (b *Builtins) ToR(s *Stack, r *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) ToR(s *Stack, r *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -317,8 +482,8 @@ func (b *Builtins) ToR(s *Stack, r *Stack) func() error {
|
||||
}
|
||||
|
||||
// RFrom pops from the return stack to the stack
|
||||
func (b *Builtins) RFrom(s *Stack, r *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) RFrom(s *Stack, r *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tors, err := r.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -329,8 +494,8 @@ func (b *Builtins) RFrom(s *Stack, r *Stack) func() error {
|
||||
}
|
||||
|
||||
// RFetch copies from the return stack to the stack
|
||||
func (b *Builtins) RFetch(s *Stack, r *Stack) func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) RFetch(s *Stack, r *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
tors, err := r.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -341,25 +506,312 @@ func (b *Builtins) RFetch(s *Stack, r *Stack) func() error {
|
||||
}
|
||||
}
|
||||
|
||||
// CR prints a newline to standard out
|
||||
func (b *Builtins) CR() func() error {
|
||||
return func() error {
|
||||
fmt.Print("\n")
|
||||
// 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(string) error {
|
||||
return func(_ string) 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 FALSE, don't execute this branch
|
||||
if tos == FALSE {
|
||||
i.Push(0)
|
||||
return nil
|
||||
}
|
||||
// TOS wasn't 0, execute until we see ELSE/THEN
|
||||
i.Push(1)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Else executes a separate piece of code if the IF stack
|
||||
func (b *Builtins) Else(i *Stack) func(string) error {
|
||||
return func(_ string) 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(i *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
// Pop off the existing IF/ELSE state and return
|
||||
_, err := i.Pop()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 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(string) error {
|
||||
return func(_ string) 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 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(string) error {
|
||||
return func(_ string) 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(string) error {
|
||||
return func(_ string) error {
|
||||
counter, err := r.Pick(2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Push(counter)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Quit exits the repl
|
||||
func (b *Builtins) Quit() func() error {
|
||||
return func() error {
|
||||
func (b *Builtins) Quit() func(string) error {
|
||||
return func(_ string) error {
|
||||
return ErrExit
|
||||
}
|
||||
}
|
||||
|
||||
// Debug prints the stack without modifying it
|
||||
func (b *Builtins) Debug(s *Stack) func() error {
|
||||
return func() error {
|
||||
fmt.Print(s.values, " ")
|
||||
func (b *Builtins) Debug(out io.Writer, s *Stack) func(string) error {
|
||||
if out == nil {
|
||||
out = os.Stdout
|
||||
}
|
||||
return func(_ string) error {
|
||||
fmt.Fprint(out, s.values, " ")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// See prints the defintion of a word
|
||||
func (b *Builtins) See(out io.Writer, r *Stack, d Dictionary) func(string) error {
|
||||
if out == nil {
|
||||
out = os.Stdout
|
||||
}
|
||||
return func(next string) error {
|
||||
w := []byte{}
|
||||
// start at 1, not 0, because a ' ' is the character at 0.
|
||||
for i := 1; i < len(next); i = i + 1 {
|
||||
switch next[i] {
|
||||
case ' ':
|
||||
word, err := d.GetWord(string(w))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if word.Impl != nil {
|
||||
fmt.Fprintf(out, "%s [INTERNAL]\n", string(w))
|
||||
} else {
|
||||
fmt.Fprintf(out, ": %s %s ;\n", string(w), strings.Join(word.Source, " "))
|
||||
}
|
||||
j, err := r.Pop()
|
||||
r.Push(j + i)
|
||||
return err
|
||||
default:
|
||||
w = append(w, next[i])
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Depth puts the current count of stack items on the stacks
|
||||
func (b *Builtins) Depth(s *Stack) func(string) error {
|
||||
return func(_ string) error {
|
||||
s.Push(len(s.values))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Variable adds a new word to the dictionary that returns a pointer to memory
|
||||
func (b *Builtins) Variable(c *Context) func(string) error {
|
||||
return func(next string) error {
|
||||
w := []byte{}
|
||||
for i := 1; i < len(next); i = i + 1 {
|
||||
switch next[i] {
|
||||
case ' ':
|
||||
next := c.Memory.NextFreeAddress()
|
||||
if next == 0 {
|
||||
// don't use the 0 cell, since we can't distinguish that from the uninitialized field
|
||||
next = 1
|
||||
}
|
||||
c.Dictionary.AddWord(string(w), Word{Name: string(w), Variable: next})
|
||||
j, _ := c.RStack.Pop()
|
||||
c.RStack.Push(j + i - 1) // push the end-point onto the stack
|
||||
return c.Memory.Write(next, []int{0})
|
||||
default:
|
||||
w = append(w, next[i])
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Constant adds a new word to the dictionary that puts a value on the stack
|
||||
func (b *Builtins) Constant(c *Context) func(string) error {
|
||||
return func(next string) error {
|
||||
w := []byte{}
|
||||
for i := 1; i < len(next); i = i + 1 {
|
||||
switch next[i] {
|
||||
case ' ':
|
||||
v, err := c.Stack.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Dictionary.AddWord(string(w), Word{
|
||||
Name: string(w),
|
||||
Impl: func(_ string) error {
|
||||
c.Stack.Push(v)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
j, _ := c.RStack.Pop()
|
||||
c.RStack.Push(j + i - 1) // push the end-point onto the stack
|
||||
return nil
|
||||
default:
|
||||
w = append(w, next[i])
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Store places a value in memory
|
||||
func (b *Builtins) Store(c *Context) func(string) error {
|
||||
return func(_ string) error {
|
||||
addr, err := c.Stack.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val, err := c.Stack.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Memory.Write(addr, []int{val})
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch returns a value from memory and puts it on the stack
|
||||
func (b *Builtins) Fetch(c *Context) func(string) error {
|
||||
return func(_ string) error {
|
||||
addr, err := c.Stack.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := c.Memory.Read(addr, 1)
|
||||
c.Stack.Push(res[0])
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Load loads a library into the current working environment
|
||||
func (b *Builtins) Load(c *Context) func(string) error {
|
||||
return func(_ string) error {
|
||||
filename := strings.TrimSpace(c.StringBuffer)
|
||||
if filename == "" {
|
||||
return fmt.Errorf(`stringbuffer empty; try S" filename.prsp" LOAD`)
|
||||
}
|
||||
|
||||
// store stacks
|
||||
s, r, i := c.Stack, c.RStack, c.IfStack
|
||||
c.Stack = &Stack{values: []int{}}
|
||||
c.RStack = &Stack{values: []int{}}
|
||||
c.IfStack = &Stack{values: []int{}}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader := bufio.NewReader(f)
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
for {
|
||||
scan := scanner.Scan()
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
err = c.Eval(line + " ") // append a space to make sure we always close out our parse loop
|
||||
if err != nil && !errors.Is(err, ErrExit) {
|
||||
return fmt.Errorf("error in library evaluation: %v", err)
|
||||
}
|
||||
if scan == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
// restore stacks
|
||||
c.Stack = s
|
||||
c.RStack = r
|
||||
c.IfStack = i
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
331
builtins_test.go
Normal file
331
builtins_test.go
Normal file
@@ -0,0 +1,331 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
s := Stack{
|
||||
values: []int{1, 2},
|
||||
}
|
||||
b := Builtins{}
|
||||
err := b.Add(&s)("")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 3 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSub(t *testing.T) {
|
||||
s := Stack{
|
||||
values: []int{1, 2},
|
||||
}
|
||||
b := Builtins{}
|
||||
err := b.Sub(&s)("")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMul(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{4, 2},
|
||||
}
|
||||
err := b.Mul(&s)("")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 8 {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
s = Stack{
|
||||
values: []int{-4, 2},
|
||||
}
|
||||
err = b.Mul(&s)("")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != -8 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiv(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{2, 4},
|
||||
}
|
||||
err := b.Div(&s)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 2 {
|
||||
t.Log(s.values[0])
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
s = Stack{
|
||||
values: []int{2, -4},
|
||||
}
|
||||
err = b.Div(&s)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != -2 {
|
||||
t.Log(s.values[0])
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{200},
|
||||
}
|
||||
out := new(bytes.Buffer)
|
||||
err := b.Print(out, &s)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 0 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if string(out.Bytes()) != "200 " {
|
||||
t.Log(string(out.Bytes()), out.Bytes())
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDup(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{200},
|
||||
}
|
||||
err := b.Dup(&s)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 2 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != s.values[1] {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwap(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{200, 100},
|
||||
}
|
||||
err := b.Swap(&s)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 2 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 100 || s.values[1] != 200 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestOver(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{200, 100},
|
||||
}
|
||||
err := b.Over(&s)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 3 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 100 || s.values[1] != 200 || s.values[2] != 100 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrop(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{200, 100},
|
||||
}
|
||||
err := b.Drop(&s)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 100 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRot(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{100, 200, 300},
|
||||
}
|
||||
err := b.Rot(&s)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 3 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 300 || s.values[1] != 100 || s.values[2] != 200 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmit(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{42},
|
||||
}
|
||||
out := new(bytes.Buffer)
|
||||
err := b.Emit(out, &s)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 0 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if string(out.Bytes()) != "* " {
|
||||
t.Log(string(out.Bytes()), out.Bytes())
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestToR(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{200, 100},
|
||||
}
|
||||
r := Stack{
|
||||
values: []int{},
|
||||
}
|
||||
err := b.ToR(&s, &r)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if len(r.values) != 1 {
|
||||
t.Log(r.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 100 || r.values[0] != 200 {
|
||||
t.Log(s.values, r.values)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFrom(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{},
|
||||
}
|
||||
r := Stack{
|
||||
values: []int{200, 100},
|
||||
}
|
||||
err := b.RFrom(&s, &r)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if len(r.values) != 1 {
|
||||
t.Log(r.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 200 || r.values[0] != 100 {
|
||||
t.Log(s.values, r.values)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFetch(t *testing.T) {
|
||||
b := Builtins{}
|
||||
s := Stack{
|
||||
values: []int{},
|
||||
}
|
||||
r := Stack{
|
||||
values: []int{200, 100},
|
||||
}
|
||||
err := b.RFetch(&s, &r)("")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
if len(s.values) != 1 {
|
||||
t.Log(s.values)
|
||||
t.Fail()
|
||||
}
|
||||
if len(r.values) != 2 {
|
||||
t.Log(r.values)
|
||||
t.Fail()
|
||||
}
|
||||
if s.values[0] != 200 || r.values[0] != 200 || r.values[1] != 100 {
|
||||
t.Log(s.values, r.values)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
116
eval.go
116
eval.go
@@ -2,94 +2,94 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Context is a set of Dictionary + Stack representing a runtime environment
|
||||
// Context is a set of Dictionary + Stacks + Flags representing a runtime environment
|
||||
type Context struct {
|
||||
Dictionary Dictionary
|
||||
Stack *Stack
|
||||
RStack *Stack
|
||||
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
|
||||
var comment bool
|
||||
immediate := true
|
||||
|
||||
for i := 0; i < len(line); i = i + 1 {
|
||||
switch line[i] {
|
||||
case '(', ')': // comments
|
||||
if len(word) == 0 {
|
||||
if line[i] == '(' {
|
||||
comment = true
|
||||
continue
|
||||
}
|
||||
comment = false
|
||||
continue
|
||||
} else {
|
||||
word = append(word, line[i])
|
||||
}
|
||||
case ':', ';': // COMPILE/IMMEDIATE mode swapping
|
||||
if len(word) == 0 {
|
||||
if line[i] == ':' {
|
||||
immediate = false
|
||||
}
|
||||
} else {
|
||||
if line[i-1] == ' ' && line[i] == ';' {
|
||||
def := strings.SplitN(strings.TrimSpace(string(word)), " ", 2)
|
||||
c.Dictionary.AddWord(def[0], nil, def[1]+" ")
|
||||
word = []byte{}
|
||||
immediate = true
|
||||
} else {
|
||||
word = append(word, line[i])
|
||||
}
|
||||
}
|
||||
case ' ':
|
||||
if !immediate { // continue building our subroutine if we're not in immediate mode...
|
||||
word = append(word, line[i])
|
||||
continue
|
||||
}
|
||||
if len(word) == 0 || comment {
|
||||
sword := strings.TrimSpace(string(word))
|
||||
if len(word) == 0 {
|
||||
// empty space, just continue...
|
||||
continue
|
||||
}
|
||||
int, err := strconv.Atoi(string(word))
|
||||
if err == nil {
|
||||
// it was a number! put it on the stack.
|
||||
c.Stack.Push(int)
|
||||
// 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
|
||||
}
|
||||
// it wasn't a number. Is it a word we know?
|
||||
w, err := c.Dictionary.GetWord(string(word))
|
||||
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()
|
||||
|
||||
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
|
||||
}
|
||||
word = []byte{}
|
||||
} else if w.Source != "" {
|
||||
// user-defined word; let's descend...
|
||||
err := c.Eval(w.Source)
|
||||
i, err = c.RStack.Pop()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error while popping from return stack: %v", err)
|
||||
}
|
||||
word = []byte{}
|
||||
}
|
||||
word = []byte{}
|
||||
default:
|
||||
if !comment {
|
||||
word = append(word, line[i])
|
||||
}
|
||||
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
|
||||
}
|
||||
|
3
examples/example.prsp
Normal file
3
examples/example.prsp
Normal file
@@ -0,0 +1,3 @@
|
||||
.( Loading )
|
||||
: CUBE DUP DUP * * ;
|
||||
: SQUARE DUP * ;
|
14
flags.go
Normal file
14
flags.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
// Flags is a simple map of strings to boolean flag fields
|
||||
type Flags map[string]bool
|
||||
|
||||
// GetFlag returns a boolean for the given flag
|
||||
func (f Flags) GetFlag(s string) bool {
|
||||
return f[s]
|
||||
}
|
||||
|
||||
// SetFlag overwrites the existing boolean for the flag
|
||||
func (f Flags) SetFlag(s string, b bool) {
|
||||
f[s] = b
|
||||
}
|
2
go.mod
2
go.mod
@@ -1,3 +1,5 @@
|
||||
module git.yetaga.in/alazyreader/prosper
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/peterh/liner v1.2.1
|
||||
|
4
go.sum
Normal file
4
go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg=
|
||||
github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
|
244
main.go
244
main.go
@@ -1,71 +1,243 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/peterh/liner"
|
||||
)
|
||||
|
||||
type libraryFiles []string
|
||||
|
||||
func (l *libraryFiles) String() string {
|
||||
return strings.Join(*l, ", ")
|
||||
}
|
||||
|
||||
func (l *libraryFiles) Set(value string) error {
|
||||
*l = append(*l, strings.TrimSpace(value))
|
||||
return nil
|
||||
}
|
||||
|
||||
func historyFn() string {
|
||||
dataHome := os.Getenv("XDG_DATA_HOME")
|
||||
home := os.Getenv("HOME")
|
||||
if dataHome == "" {
|
||||
dataHome = filepath.Join(home, ".local/share/")
|
||||
}
|
||||
prosperDir := filepath.Join(dataHome, "prosper")
|
||||
os.MkdirAll(prosperDir, 0755)
|
||||
return filepath.Join(prosperDir, ".history")
|
||||
}
|
||||
|
||||
func completer(d Dictionary) liner.Completer {
|
||||
return func(line string) []string {
|
||||
candidates := []string{}
|
||||
words := strings.Split(line, " ")
|
||||
for w := range d {
|
||||
if strings.HasPrefix(w, words[len(words)-1]) {
|
||||
// must reconstruct the _full_ line to return for the completer
|
||||
pref := strings.Join(words[:len(words)-1], " ")
|
||||
if len(pref) != 0 {
|
||||
candidates = append(candidates, pref+" "+w)
|
||||
} else {
|
||||
candidates = append(candidates, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// flag setup
|
||||
var libs libraryFiles
|
||||
flag.Var(&libs, "l", "propser library file (may be repeated)")
|
||||
debug := flag.Bool("debug", false, "output debugging information")
|
||||
flag.Parse()
|
||||
|
||||
// create main context
|
||||
stack := Stack{values: []int{}}
|
||||
rstack := Stack{values: []int{}}
|
||||
ifstack := Stack{values: []int{}}
|
||||
|
||||
dict := Dictionary{}
|
||||
b := &Builtins{}
|
||||
dict.AddWord("+", b.Add(&stack), "")
|
||||
dict.AddWord("-", b.Sub(&stack), "")
|
||||
dict.AddWord("*", b.Mul(&stack), "")
|
||||
dict.AddWord("/", b.Div(&stack), "")
|
||||
dict.AddWord(".", b.Print(&stack), "")
|
||||
dict.AddWord("=", b.Eq(&stack), "")
|
||||
dict.AddWord("0=", nil, "0 = ")
|
||||
dict.AddWord("<>", b.NEq(&stack), "")
|
||||
dict.AddWord(">", b.Gt(&stack), "")
|
||||
dict.AddWord("<", b.Lt(&stack), "")
|
||||
dict.AddWord(">=", b.GtEq(&stack), "")
|
||||
dict.AddWord("<=", b.LtEq(&stack), "")
|
||||
dict.AddWord("DUP", b.Dup(&stack), "")
|
||||
dict.AddWord("SWAP", b.Swap(&stack), "")
|
||||
dict.AddWord("OVER", b.Over(&stack), "")
|
||||
dict.AddWord("DROP", b.Drop(&stack), "")
|
||||
dict.AddWord("ROT", b.Rot(&stack), "")
|
||||
dict.AddWord("WORDS", b.Words(dict), "")
|
||||
dict.AddWord(".S", b.Debug(&stack), "")
|
||||
dict.AddWord("EMIT", b.Emit(&stack), "")
|
||||
dict.AddWord("R>", b.RFrom(&stack, &rstack), "")
|
||||
dict.AddWord(">R", b.ToR(&stack, &rstack), "")
|
||||
dict.AddWord("R@", b.RFetch(&stack, &rstack), "")
|
||||
dict.AddWord("CR", b.CR(), "")
|
||||
dict.AddWord("QUIT", b.Quit(), "")
|
||||
|
||||
c := Context{
|
||||
Dictionary: dict,
|
||||
Stack: &stack,
|
||||
RStack: &rstack,
|
||||
IfStack: &ifstack,
|
||||
Flags: Flags{
|
||||
"Immediate": true,
|
||||
},
|
||||
Words: []string{},
|
||||
Memory: Memory{
|
||||
intern: map[int]int{},
|
||||
nextFree: 1,
|
||||
},
|
||||
StringBuffer: "",
|
||||
Output: os.Stdout,
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("prosper")
|
||||
defineBuiltIns(&c)
|
||||
|
||||
// load libraries
|
||||
for l := range libs {
|
||||
w, _ := c.Dictionary.GetWord("LOAD")
|
||||
c.StringBuffer = libs[l]
|
||||
err := w.Impl("")
|
||||
if err != nil {
|
||||
fmt.Fprintf(c.Output, "error in library %s: %v\n", libs[l], err)
|
||||
} else {
|
||||
fmt.Fprintf(c.Output, "loaded %s\n", libs[l])
|
||||
}
|
||||
}
|
||||
|
||||
// set up liner
|
||||
line := liner.NewLiner()
|
||||
line.SetCtrlCAborts(true)
|
||||
line.SetCompleter(completer(dict))
|
||||
|
||||
historyFile := historyFn()
|
||||
|
||||
if f, err := os.Open(historyFile); err == nil {
|
||||
_, err := line.ReadHistory(f)
|
||||
if err != nil && *debug {
|
||||
fmt.Printf("error reading line history: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if f, err := os.Create(historyFile); err != nil {
|
||||
if *debug {
|
||||
fmt.Printf("Error writing history file: %v", err)
|
||||
}
|
||||
} else {
|
||||
line.WriteHistory(f)
|
||||
f.Close()
|
||||
}
|
||||
line.Close()
|
||||
}()
|
||||
|
||||
// Welcome banner
|
||||
fmt.Print("prosper\n")
|
||||
|
||||
// read loop
|
||||
for {
|
||||
fmt.Print("\n> ")
|
||||
p := "> "
|
||||
if !c.Flags["Immediate"] {
|
||||
p = " "
|
||||
}
|
||||
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
// read line
|
||||
l, err := line.Prompt(p)
|
||||
if err == liner.ErrPromptAborted {
|
||||
fmt.Println("ctrl-C caught (try BYE)")
|
||||
continue
|
||||
} else if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
err = c.Eval(line + " ") // append a space to make sure we always close out our parse loop
|
||||
|
||||
// parse line
|
||||
l = strings.TrimSpace(l)
|
||||
line.AppendHistory(l)
|
||||
err = c.Eval(l + " ") // append a space to make sure we always close out our parse loop
|
||||
if errors.Is(err, ErrExit) {
|
||||
fmt.Printf("bye\n")
|
||||
os.Exit(0)
|
||||
break
|
||||
} else if err != nil {
|
||||
fmt.Printf("error in evaluation: %v\n", err)
|
||||
} else if c.Flags["Immediate"] {
|
||||
fmt.Print("ok\n")
|
||||
}
|
||||
fmt.Print("ok")
|
||||
}
|
||||
}
|
||||
|
||||
func defineBuiltIns(c *Context) {
|
||||
b := &Builtins{}
|
||||
// word definitions
|
||||
c.Dictionary.AddWord(":", Word{Name: ":", Impl: b.Colon(c)})
|
||||
c.Dictionary.AddWord(";", Word{Name: ";", Impl: b.Semicolon(c), Immediate: true})
|
||||
// comments and strings
|
||||
c.Dictionary.AddWord("(", Word{Name: "(", Impl: b.OpenComment(c), Immediate: true})
|
||||
c.Dictionary.AddWord(")", Word{Name: ")", Impl: b.CloseComment(c), Immediate: true})
|
||||
c.Dictionary.AddWord(`\`, Word{Name: `\`, Impl: b.EOLComment(c), Immediate: true})
|
||||
c.Dictionary.AddWord(`."`, Word{Name: `."`, Impl: b.OpenQuote(c.Output, c, '"')})
|
||||
c.Dictionary.AddWord(`"`, Word{Name: `"`, Impl: b.CloseQuote(c)})
|
||||
c.Dictionary.AddWord(`.(`, Word{Name: `.(`, Impl: b.OpenQuote(c.Output, c, ')'), Immediate: true})
|
||||
c.Dictionary.AddWord(`S"`, Word{Name: `S"`, Impl: b.StringBuffer(c, '"')})
|
||||
c.Dictionary.AddWord("LOAD", Word{Name: "LOAD", Impl: b.Load(c)})
|
||||
// math
|
||||
c.Dictionary.AddWord("+", Word{Name: "+", Impl: b.Add(c.Stack)})
|
||||
c.Dictionary.AddWord("-", Word{Name: "-", Impl: b.Sub(c.Stack)})
|
||||
c.Dictionary.AddWord("*", Word{Name: "*", Impl: b.Mul(c.Stack)})
|
||||
c.Dictionary.AddWord("/", Word{Name: "/", Impl: b.Div(c.Stack)})
|
||||
c.Dictionary.AddWord("MOD", Word{Name: "MOD", Impl: b.Mod(c.Stack)})
|
||||
c.Dictionary.AddWord("/MOD", Word{Name: "/MOD", Source: []string{"2DUP", "MOD", "ROT", "ROT", "/"}})
|
||||
c.Dictionary.AddWord("1+", Word{Name: "1+", Source: []string{"1", "+"}})
|
||||
c.Dictionary.AddWord("1-", Word{Name: "1-", Source: []string{"1", "-"}})
|
||||
c.Dictionary.AddWord("ABS", Word{Name: "ABS", Source: []string{"DUP", "0<", "IF", "NEGATE", "THEN"}})
|
||||
c.Dictionary.AddWord("NEGATE", Word{Name: "NEGATE", Source: []string{"-1", "*"}})
|
||||
c.Dictionary.AddWord("MAX", Word{Name: "MAX", Source: []string{"2DUP", "<", "IF", "SWAP", "THEN", "DROP"}})
|
||||
c.Dictionary.AddWord("MIN", Word{Name: "MIN", Source: []string{"2DUP", ">", "IF", "SWAP", "THEN", "DROP"}})
|
||||
// output
|
||||
c.Dictionary.AddWord(".", Word{Name: ".", Impl: b.Print(c.Output, c.Stack)})
|
||||
c.Dictionary.AddWord("EMIT", Word{Name: "EMIT", Impl: b.Emit(c.Output, c.Stack)})
|
||||
c.Dictionary.AddWord("CR", Word{Name: "CR", Source: []string{"10", "EMIT"}}) // emit a newline
|
||||
// logic
|
||||
c.Dictionary.AddWord("=", Word{Name: "=", Impl: b.Eq(c.Stack)})
|
||||
c.Dictionary.AddWord("0=", Word{Name: "0=", Source: []string{"0", "="}})
|
||||
c.Dictionary.AddWord("<>", Word{Name: "<>", Impl: b.NEq(c.Stack)})
|
||||
c.Dictionary.AddWord("0<>", Word{Name: "0<>", Source: []string{"0=", "0="}})
|
||||
c.Dictionary.AddWord(">", Word{Name: ">", Impl: b.Gt(c.Stack)})
|
||||
c.Dictionary.AddWord("<", Word{Name: "<", Impl: b.Lt(c.Stack)})
|
||||
c.Dictionary.AddWord(">=", Word{Name: ">=", Impl: b.GtEq(c.Stack)})
|
||||
c.Dictionary.AddWord("<=", Word{Name: "<=", Impl: b.LtEq(c.Stack)})
|
||||
c.Dictionary.AddWord("0<", Word{Name: "0<", Source: []string{"0", "<"}})
|
||||
c.Dictionary.AddWord("0>", Word{Name: "0>", Source: []string{"0", ">"}})
|
||||
// stack manipulation
|
||||
c.Dictionary.AddWord("DUP", Word{Name: "DUP", Impl: b.Dup(c.Stack)})
|
||||
c.Dictionary.AddWord("?DUP", Word{Name: "?DUP", Source: []string{"DUP", "0<>", "IF", "DUP", "THEN"}})
|
||||
c.Dictionary.AddWord("SWAP", Word{Name: "SWAP", Impl: b.Swap(c.Stack)})
|
||||
c.Dictionary.AddWord("OVER", Word{Name: "OVER", Impl: b.Over(c.Stack)})
|
||||
c.Dictionary.AddWord("DROP", Word{Name: "DROP", Impl: b.Drop(c.Stack)})
|
||||
c.Dictionary.AddWord("ROT", Word{Name: "ROT", Impl: b.Rot(c.Stack)})
|
||||
c.Dictionary.AddWord("PICK", Word{Name: "PICK", Impl: b.Pick(c.Stack)})
|
||||
c.Dictionary.AddWord("NIP", Word{Name: "NIP", Source: []string{"SWAP", "DROP"}})
|
||||
c.Dictionary.AddWord("TUCK", Word{Name: "TUCK", Source: []string{"SWAP", "OVER"}})
|
||||
// paired stack manipulation
|
||||
c.Dictionary.AddWord("2DROP", Word{Name: "2DROP", Source: []string{"DROP", "DROP"}})
|
||||
c.Dictionary.AddWord("2DUP", Word{Name: "2DUP", Source: []string{"OVER", "OVER"}})
|
||||
c.Dictionary.AddWord("2OVER", Word{Name: "2OVER", Source: []string{"3", "PICK", "3", "PICK"}})
|
||||
// memory access with variables and constants
|
||||
c.Dictionary.AddWord("VARIABLE", Word{Name: "VARIABLE", Impl: b.Variable(c)})
|
||||
c.Dictionary.AddWord("CONSTANT", Word{Name: "CONSTANT", Impl: b.Constant(c)})
|
||||
c.Dictionary.AddWord("!", Word{Name: "!", Impl: b.Store(c)})
|
||||
c.Dictionary.AddWord("@", Word{Name: "@", Impl: b.Fetch(c)})
|
||||
// debugging
|
||||
c.Dictionary.AddWord("WORDS", Word{Name: "WORDS", Impl: b.Words(c.Output, c.Dictionary)})
|
||||
c.Dictionary.AddWord("SEE", Word{Name: "SEE", Impl: b.See(c.Output, c.RStack, c.Dictionary)})
|
||||
c.Dictionary.AddWord("FLAGS", Word{Name: "FLAGS", Impl: b.Flags(c.Output, c)})
|
||||
c.Dictionary.AddWord(".S", Word{Name: ".S", Impl: b.Debug(c.Output, c.Stack)})
|
||||
c.Dictionary.AddWord(".R", Word{Name: ".R", Impl: b.Debug(c.Output, c.RStack)})
|
||||
c.Dictionary.AddWord(".I", Word{Name: ".I", Impl: b.Debug(c.Output, c.IfStack)})
|
||||
c.Dictionary.AddWord("DEPTH", Word{Name: "DEPTH", Impl: b.Depth(c.Stack)})
|
||||
c.Dictionary.AddWord("R>", Word{Name: "R>", Impl: b.RFrom(c.Stack, c.RStack)})
|
||||
c.Dictionary.AddWord(">R", Word{Name: ">R", Impl: b.ToR(c.Stack, c.RStack)})
|
||||
c.Dictionary.AddWord("R@", Word{Name: "R@", Impl: b.RFetch(c.Stack, c.RStack)})
|
||||
// branching
|
||||
c.Dictionary.AddWord("IF", Word{Name: "IF", Impl: b.If(c.Stack, c.IfStack), BranchCheck: true})
|
||||
c.Dictionary.AddWord("ELSE", Word{Name: "ELSE", Impl: b.Else(c.IfStack), BranchCheck: true})
|
||||
c.Dictionary.AddWord("THEN", Word{Name: "THEN", Impl: b.Then(c.IfStack), BranchCheck: true})
|
||||
c.Dictionary.AddWord("DO", Word{Name: "DO", Impl: b.Do(c.Stack, c.RStack)})
|
||||
c.Dictionary.AddWord("LOOP", Word{Name: "LOOP", Impl: b.Loop(c.Stack, c.RStack)})
|
||||
c.Dictionary.AddWord("I", Word{Name: "I", Impl: b.I(c.Stack, c.RStack)})
|
||||
// exit
|
||||
c.Dictionary.AddWord("BYE", Word{Name: "BYE", Impl: b.Quit()})
|
||||
}
|
||||
|
2
mem.go
2
mem.go
@@ -28,7 +28,7 @@ func (m *Memory) Write(addr int, values []int) error {
|
||||
m.intern[addr+i] = values[i]
|
||||
}
|
||||
// we've written past our marker, note that
|
||||
if m.nextFree < addr+len(values) {
|
||||
if m.nextFree <= addr+len(values) {
|
||||
m.nextFree = addr + len(values)
|
||||
}
|
||||
return nil
|
||||
|
16
stack.go
16
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:]
|
||||
@@ -21,3 +27,11 @@ func (s *Stack) Pop() (int, error) {
|
||||
func (s *Stack) Push(i int) {
|
||||
s.values = append([]int{i}, s.values...)
|
||||
}
|
||||
|
||||
// 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, 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()
|
||||
}
|
||||
}
|
||||
|
24
words.go
24
words.go
@@ -2,23 +2,25 @@ 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
|
||||
|
||||
// A Word defines a subroutine
|
||||
type Word struct {
|
||||
Name string
|
||||
Impl func() error
|
||||
Source string
|
||||
Name string // Name of our word/variable
|
||||
Impl func(string) error // built-in implementation of the word
|
||||
Source []string // source, if user-defined
|
||||
Variable int // if this is a variable, the memory address
|
||||
Immediate bool // is this word immediate?
|
||||
BranchCheck bool // is this word part of IF/ELSE/THEN?
|
||||
}
|
||||
|
||||
// AddWord inserts a new word into the dictonary, optionally overwriting the existing word
|
||||
func (d Dictionary) AddWord(name string, impl func() error, source string) {
|
||||
d[name] = Word{
|
||||
Name: name,
|
||||
Impl: impl,
|
||||
Source: source,
|
||||
}
|
||||
// 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
|
||||
@@ -27,7 +29,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, "1 + ")
|
||||
d.AddWord("INC", Word{
|
||||
Name: "INC",
|
||||
Source: []string{"1", "+"},
|
||||
})
|
||||
w, err := d.GetWord("INC")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
@@ -16,7 +19,7 @@ func TestDictionary(t *testing.T) {
|
||||
if w.Impl != nil {
|
||||
t.Fail()
|
||||
}
|
||||
if w.Source != "1 +" {
|
||||
if len(w.Source) != 2 || w.Source[0] != "1" || w.Source[1] != "+" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user