Compare commits

...

36 Commits

Author SHA1 Message Date
8065d43efd shut up my syntax highlighter about un-labeled source blocks 2021-02-28 13:22:33 -05:00
2aa11b6585 update README with new goals 2021-02-28 13:21:27 -05:00
8e33e13ffc add a nice readline experience (with history!) 2021-02-28 12:30:21 -05:00
b7c7e02697 initial pass at libs done, although it needs more work 2021-02-26 22:19:51 -05:00
efe408bed8 basic command-line library handling 2021-02-26 22:09:43 -05:00
5e782bc728 fix some string bugs and make external-file-loading work 2021-02-26 20:49:10 -05:00
7291ca86c8 implement line-comments 2021-02-25 22:09:59 -05:00
0784661e7c fix bug in variable addressing if they weren't immediately written to 2021-02-25 21:13:03 -05:00
e428c883db CONSTANT 2021-02-25 21:04:35 -05:00
762b6c870c VARIABLE 2021-02-24 21:27:52 -05:00
241470248b reorder built-ins a bit 2021-02-24 20:46:31 -05:00
4727312da4 implement string handling! 2021-02-24 20:44:56 -05:00
e998c286d7 check off SEE 2021-02-24 20:19:44 -05:00
a2e6115248 implement SEE and make comments suck less 2021-02-24 20:19:25 -05:00
583fa79985 fibonacci code 2021-02-22 16:55:33 -05:00
5048b445ae remove unused DIVMOD builtin 2021-02-21 15:06:08 -05:00
953764004d fix silly word collision with -1 built-in 2021-02-21 15:04:51 -05:00
6c5df444da add a readme 2021-02-21 12:22:08 -05:00
0676cad34d move int parsing to after word check 2021-02-20 19:22:36 -05:00
ff5346994f whoops, lost the bad-word error case 2021-02-20 19:18:45 -05:00
0d322b92df define /MOD in prosper, not as a built-in 2021-02-20 19:13:28 -05:00
fee9325155 comment on the nature of truth and falsehood 2021-02-20 18:59:41 -05:00
048557caf2 add yet more common words 2021-02-20 18:47:10 -05:00
7251f1ba60 NEGATE/MAX/MIN implemented in forth, not builtins 2021-02-20 18:15:09 -05:00
a75db93581 add more common math and fix my comparison checks 2021-02-20 18:03:02 -05:00
06ad13dd28 abstract the output of the last few places I missed 2021-02-20 17:12:34 -05:00
6efa404712 make IF/ELSE/THEN work! 2021-02-20 15:52:27 -05:00
2a1a6fc0d2 tests, abstracting output for PRINT and EMIT 2021-02-20 12:09:04 -05:00
1a229d5ddb abstract flags, implement comments as words, fix some bugs 2021-02-15 19:24:04 -05:00
7c76bc49af remove special-case : ; from parser 2021-02-15 15:35:40 -05:00
e5bb216f95 implement PICK on stacks to make the implementation of I less silly 2021-02-14 20:32:38 -05:00
9d3f61338f add DO LOOP 2021-02-14 20:26:30 -05:00
626e90d54c parser location doesn't need to be in the context, does it? 2021-02-14 14:30:15 -05:00
852aaa6387 start using return stack to track evaluation location 2021-02-14 14:27:02 -05:00
199203e73f add equality checking and lt,gt,etc 2021-02-14 12:20:34 -05:00
7212f3d9f6 add return stack 2021-02-14 11:58:43 -05:00
14 changed files with 1446 additions and 171 deletions

86
README.md Normal file
View 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).

View File

@@ -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,201 +26,792 @@ 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(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
nos, err := s.Pop()
if err != nil {
return err
}
if tos == nos {
s.Push(TRUE)
return nil
}
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(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
nos, err := s.Pop()
if err != nil {
return err
}
if tos != nos {
s.Push(TRUE)
return nil
}
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(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
nos, err := s.Pop()
if err != nil {
return err
}
if nos < tos {
s.Push(TRUE)
return nil
}
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(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
nos, err := s.Pop()
if err != nil {
return err
}
if nos > tos {
s.Push(TRUE)
return nil
}
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(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
nos, err := s.Pop()
if err != nil {
return err
}
if nos <= tos {
s.Push(TRUE)
return nil
}
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(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
nos, err := s.Pop()
if err != nil {
return err
}
if nos >= tos {
s.Push(TRUE)
return nil
}
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 {
r1, err := s.Pop()
func (b *Builtins) Add(s *Stack) func(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
r2, err := s.Pop()
nos, err := s.Pop()
if err != nil {
return err
}
s.Push(r1 + r2)
s.Push(tos + nos)
return nil
}
}
// Sub performs NOS - TOS and pushes the result
func (b *Builtins) Sub(s *Stack) func() error {
return func() error {
r1, err := s.Pop()
func (b *Builtins) Sub(s *Stack) func(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
r2, err := s.Pop()
nos, err := s.Pop()
if err != nil {
return err
}
s.Push(r2 - r1)
s.Push(nos - tos)
return nil
}
}
// 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 {
r1, err := s.Pop()
func (b *Builtins) Mul(s *Stack) func(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
r2, err := s.Pop()
nos, err := s.Pop()
if err != nil {
return err
}
s.Push(r2 * r1)
s.Push(nos * tos)
return nil
}
}
// Div performs NOS/TOS and pushes the (integer!) result
func (b *Builtins) Div(s *Stack) func() error {
return func() error {
r1, err := s.Pop()
func (b *Builtins) Div(s *Stack) func(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
r2, err := s.Pop()
nos, err := s.Pop()
if err != nil {
return err
}
s.Push(r2 / r1)
s.Push(nos / tos)
return nil
}
}
// Print pops the stack and outputs it to stdout
func (b *Builtins) Print(s *Stack) func() error {
return func() error {
r1, err := s.Pop()
// 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(r1, " ")
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 {
r1, err := s.Pop()
func (b *Builtins) Dup(s *Stack) func(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
s.Push(r1)
s.Push(r1)
s.Push(tos)
s.Push(tos)
return nil
}
}
// Swap inverts the order of TOS and NOS
func (b *Builtins) Swap(s *Stack) func() error {
return func() error {
r1, err := s.Pop()
func (b *Builtins) Swap(s *Stack) func(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
r2, err := s.Pop()
nos, err := s.Pop()
if err != nil {
return err
}
s.Push(r1)
s.Push(r2)
s.Push(tos)
s.Push(nos)
return nil
}
}
// Over duplicates NOS to TOS, resulting in NOS TOS NOS
func (b *Builtins) Over(s *Stack) func() error {
return func() error {
r1, 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
}
r2, err := s.Pop()
if err != nil {
return err
}
s.Push(r2)
s.Push(r1)
s.Push(r2)
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 {
r1, err := s.Pop()
func (b *Builtins) Rot(s *Stack) func(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
r2, err := s.Pop()
nos, err := s.Pop()
if err != nil {
return err
}
r3, err := s.Pop()
p3, err := s.Pop()
if err != nil {
return err
}
s.Push(r2)
s.Push(r1)
s.Push(r3)
s.Push(nos)
s.Push(tos)
s.Push(p3)
return err
}
}
// 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 {
i, err := s.Pop()
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(i)) + " ")
fmt.Fprint(out, string(rune(tos))+" ")
return nil
}
}
// CR prints a newline to standard out
func (b *Builtins) CR() func() error {
return func() error {
fmt.Print("\n")
// ToR pops from the stack to the return stack
func (b *Builtins) ToR(s *Stack, r *Stack) func(string) error {
return func(_ string) error {
tos, err := s.Pop()
if err != nil {
return err
}
r.Push(tos)
return nil
}
}
// RFrom pops from the return stack to the stack
func (b *Builtins) RFrom(s *Stack, r *Stack) func(string) error {
return func(_ string) error {
tors, err := r.Pop()
if err != nil {
return err
}
s.Push(tors)
return nil
}
}
// RFetch copies from the return stack to the stack
func (b *Builtins) RFetch(s *Stack, r *Stack) func(string) error {
return func(_ string) error {
tors, err := r.Pop()
if err != nil {
return err
}
r.Push(tors)
s.Push(tors)
return nil
}
}
// 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
View 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()
}
}

115
eval.go
View File

@@ -2,93 +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
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
View File

@@ -0,0 +1,3 @@
.( Loading )
: CUBE DUP DUP * * ;
: SQUARE DUP * ;

14
flags.go Normal file
View 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
View File

@@ -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
View 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=

236
main.go
View File

@@ -1,59 +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("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("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
View File

@@ -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

View File

@@ -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
}

View File

@@ -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()
}
}

View File

@@ -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
}

View File

@@ -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()
}
}