Compare commits
32 Commits
626e90d54c
...
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 |
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).
|
611
builtins.go
611
builtins.go
@@ -1,6 +1,24 @@
|
|||||||
package main
|
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
|
// Builtins is a handy holder for our various default words
|
||||||
type Builtins struct{}
|
type Builtins struct{}
|
||||||
@@ -8,9 +26,108 @@ type Builtins struct{}
|
|||||||
// ErrExit is a special sentinel value to cease computation and quit
|
// ErrExit is a special sentinel value to cease computation and quit
|
||||||
var ErrExit = fmt.Errorf("exit requested")
|
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.
|
// 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 {
|
func (b *Builtins) Eq(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -20,17 +137,17 @@ func (b *Builtins) Eq(s *Stack) func() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if tos == nos {
|
if tos == nos {
|
||||||
s.Push(-1)
|
s.Push(TRUE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.Push(0)
|
s.Push(FALSE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEq compares TOS and NOS and puts -1 on the stack if they're not equal, 0 otherwise.
|
// 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 {
|
func (b *Builtins) NEq(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -40,17 +157,17 @@ func (b *Builtins) NEq(s *Stack) func() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if tos != nos {
|
if tos != nos {
|
||||||
s.Push(-1)
|
s.Push(TRUE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.Push(0)
|
s.Push(FALSE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lt compares TOS and NOS and puts -1 on the stack if TOS is less than NOS, 0 otherwise.
|
// 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 {
|
func (b *Builtins) Lt(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -59,18 +176,18 @@ func (b *Builtins) Lt(s *Stack) func() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if tos < nos {
|
if nos < tos {
|
||||||
s.Push(-1)
|
s.Push(TRUE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.Push(0)
|
s.Push(FALSE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gt compares TOS and NOS and puts -1 on the stack if TOS is greater than NOS, 0 otherwise.
|
// 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 {
|
func (b *Builtins) Gt(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -79,18 +196,18 @@ func (b *Builtins) Gt(s *Stack) func() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if tos > nos {
|
if nos > tos {
|
||||||
s.Push(-1)
|
s.Push(TRUE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.Push(0)
|
s.Push(FALSE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LtEq compares TOS and NOS and puts -1 on the stack if TOS is less than or equal to NOS, 0 otherwise.
|
// 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 {
|
func (b *Builtins) LtEq(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -99,18 +216,18 @@ func (b *Builtins) LtEq(s *Stack) func() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if tos <= nos {
|
if nos <= tos {
|
||||||
s.Push(-1)
|
s.Push(TRUE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.Push(0)
|
s.Push(FALSE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GtEq compares TOS and NOS and puts -1 on the stack if TOS is greater than or equal to NOS, 0 otherwise.
|
// 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 {
|
func (b *Builtins) GtEq(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -119,18 +236,18 @@ func (b *Builtins) GtEq(s *Stack) func() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if tos >= nos {
|
if nos >= tos {
|
||||||
s.Push(-1)
|
s.Push(TRUE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.Push(0)
|
s.Push(FALSE)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add sums the top two numbers on the stack and pushes the result
|
// Add sums the top two numbers on the stack and pushes the result
|
||||||
func (b *Builtins) Add(s *Stack) func() error {
|
func (b *Builtins) Add(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -145,8 +262,8 @@ func (b *Builtins) Add(s *Stack) func() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sub performs NOS - TOS and pushes the result
|
// Sub performs NOS - TOS and pushes the result
|
||||||
func (b *Builtins) Sub(s *Stack) func() error {
|
func (b *Builtins) Sub(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// Mul multiplies the two numbers on the top of the stack and pushes the result
|
||||||
func (b *Builtins) Mul(s *Stack) func() error {
|
func (b *Builtins) Mul(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -177,8 +294,8 @@ func (b *Builtins) Mul(s *Stack) func() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Div performs NOS/TOS and pushes the (integer!) result
|
// Div performs NOS/TOS and pushes the (integer!) result
|
||||||
func (b *Builtins) Div(s *Stack) func() error {
|
func (b *Builtins) Div(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -192,21 +309,40 @@ func (b *Builtins) Div(s *Stack) func() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print pops the stack and outputs it to stdout
|
// Mod performs NOS%TOS and pushes the (integer!) modulo result
|
||||||
func (b *Builtins) Print(s *Stack) func() error {
|
func (b *Builtins) Mod(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dup pops the stack, then pushes two copies onto the stack
|
// Dup pops the stack, then pushes two copies onto the stack
|
||||||
func (b *Builtins) Dup(s *Stack) func() error {
|
func (b *Builtins) Dup(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -218,8 +354,8 @@ func (b *Builtins) Dup(s *Stack) func() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Swap inverts the order of TOS and NOS
|
// Swap inverts the order of TOS and NOS
|
||||||
func (b *Builtins) Swap(s *Stack) func() error {
|
func (b *Builtins) Swap(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -235,34 +371,28 @@ func (b *Builtins) Swap(s *Stack) func() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Over duplicates NOS to TOS, resulting in NOS TOS NOS
|
// Over duplicates NOS to TOS, resulting in NOS TOS NOS
|
||||||
func (b *Builtins) Over(s *Stack) func() error {
|
func (b *Builtins) Over(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
nos, err := s.Pick(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
nos, err := s.Pop()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Push(nos)
|
|
||||||
s.Push(tos)
|
|
||||||
s.Push(nos)
|
s.Push(nos)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop simply discards TOS
|
// Drop simply discards TOS
|
||||||
func (b *Builtins) Drop(s *Stack) func() error {
|
func (b *Builtins) Drop(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
_, err := s.Pop()
|
_, err := s.Pop()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rot cycles the first three items on the stack: TOS 1 2 3 -> TOS 3 1 2
|
// 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 {
|
func (b *Builtins) Rot(s *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// Words outputs a list of all known words in the dictionary
|
||||||
func (b *Builtins) Words(d Dictionary) func() error {
|
func (b *Builtins) Words(out io.Writer, d Dictionary) func(string) error {
|
||||||
return func() error {
|
if out == nil {
|
||||||
|
out = os.Stdout
|
||||||
|
}
|
||||||
|
return func(_ string) error {
|
||||||
for n := range d {
|
for n := range d {
|
||||||
fmt.Printf("%s %s\n", n, d[n].Source)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit outputs the UTF-8 rune for the int on the top of the stack
|
// Emit outputs the UTF-8 rune for the int on the top of the stack
|
||||||
func (b *Builtins) Emit(s *Stack) func() error {
|
func (b *Builtins) Emit(out io.Writer, s *Stack) func(string) error {
|
||||||
return func() error {
|
if out == nil {
|
||||||
|
out = os.Stdout
|
||||||
|
}
|
||||||
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Print(string(rune(tos)) + " ")
|
fmt.Fprint(out, string(rune(tos))+" ")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToR pops from the stack to the return stack
|
// ToR pops from the stack to the return stack
|
||||||
func (b *Builtins) ToR(s *Stack, r *Stack) func() error {
|
func (b *Builtins) ToR(s *Stack, r *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tos, err := s.Pop()
|
tos, err := s.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// RFrom pops from the return stack to the stack
|
||||||
func (b *Builtins) RFrom(s *Stack, r *Stack) func() error {
|
func (b *Builtins) RFrom(s *Stack, r *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tors, err := r.Pop()
|
tors, err := r.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// RFetch copies from the return stack to the stack
|
||||||
func (b *Builtins) RFetch(s *Stack, r *Stack) func() error {
|
func (b *Builtins) RFetch(s *Stack, r *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
tors, err := r.Pop()
|
tors, err := r.Pop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -341,60 +506,312 @@ func (b *Builtins) RFetch(s *Stack, r *Stack) func() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If checks if NOS != 0 and conditionally executes any following statements if so
|
// If checks if TOS != 0 and executes any following statements if it is. If TOS == 0, skip execution until ELSE or THEN.
|
||||||
func (b *Builtins) If(s *Stack, r *Stack) func() error {
|
// Nested IFs and ELSE/THENs inside "non-executing" blocks still manipulate the if stack, in order to properly balance
|
||||||
return func() error {
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else executes a separate piece of code if NOS == 0 after an if check
|
// Else executes a separate piece of code if the IF stack
|
||||||
func (b *Builtins) Else(s *Stack, r *Stack) func() error {
|
func (b *Builtins) Else(i *Stack) func(string) error {
|
||||||
return func() 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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then ends an If/Else block
|
// Then ends an If/Else block
|
||||||
func (b *Builtins) Then(s *Stack, r *Stack) func() error {
|
func (b *Builtins) Then(i *Stack) func(string) error {
|
||||||
return func() 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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do sets up a loop
|
// Loop closes a loop by removing its own location from the stack and letting the one that Do inserted get returned.
|
||||||
func (b *Builtins) Do(s *Stack, r *Stack) func() error {
|
// If it's reached the end of the loop (checking the loop-control variables _underneath_ the return vars),
|
||||||
return func() error {
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop closes a loop
|
// I puts the current value of the loop counter on the top of the stack
|
||||||
func (b *Builtins) Loop(s *Stack, r *Stack) func() error {
|
func (b *Builtins) I(s *Stack, r *Stack) func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
return nil
|
counter, err := r.Pick(2)
|
||||||
}
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
// CR prints a newline to standard out
|
s.Push(counter)
|
||||||
func (b *Builtins) CR() func() error {
|
|
||||||
return func() error {
|
|
||||||
fmt.Print("\n")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quit exits the repl
|
// Quit exits the repl
|
||||||
func (b *Builtins) Quit() func() error {
|
func (b *Builtins) Quit() func(string) error {
|
||||||
return func() error {
|
return func(_ string) error {
|
||||||
return ErrExit
|
return ErrExit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug prints the stack without modifying it
|
// Debug prints the stack without modifying it
|
||||||
func (b *Builtins) Debug(s *Stack) func() error {
|
func (b *Builtins) Debug(out io.Writer, s *Stack) func(string) error {
|
||||||
return func() error {
|
if out == nil {
|
||||||
fmt.Print(s.values, " ")
|
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
|
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()
|
||||||
|
}
|
||||||
|
}
|
114
eval.go
114
eval.go
@@ -2,87 +2,52 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Context is a set of Dictionary + Stacks representing a runtime environment
|
// Context is a set of Dictionary + Stacks + Flags representing a runtime environment
|
||||||
type Context struct {
|
type Context struct {
|
||||||
Dictionary Dictionary
|
Dictionary Dictionary
|
||||||
Stack *Stack
|
Stack *Stack // main stack
|
||||||
RStack *Stack
|
RStack *Stack // "return" stack, for handling loops/backward jumps
|
||||||
|
IfStack *Stack // tracks nested branches
|
||||||
|
Flags Flags
|
||||||
|
Words []string
|
||||||
|
Memory Memory
|
||||||
|
StringBuffer string
|
||||||
|
Output io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eval evaulates a given line, recursively descending into given words as needed
|
// Eval evaulates a given line, recursively descending into given words as needed
|
||||||
func (c *Context) Eval(line string) error {
|
func (c *Context) Eval(line string) error {
|
||||||
// state
|
// state
|
||||||
var word []byte
|
var word []byte
|
||||||
var words []string
|
|
||||||
var comment bool
|
|
||||||
immediate := true
|
|
||||||
|
|
||||||
for i := 0; i < len(line); i = i + 1 {
|
for i := 0; i < len(line); i = i + 1 {
|
||||||
switch line[i] {
|
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] == ';' {
|
|
||||||
c.Dictionary.AddWord(words[0], nil, words[1:])
|
|
||||||
word = []byte{}
|
|
||||||
immediate = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
word = append(word, line[i])
|
|
||||||
}
|
|
||||||
case ' ':
|
case ' ':
|
||||||
if !immediate { // continue building our subroutine if we're not in immediate mode...
|
sword := strings.TrimSpace(string(word))
|
||||||
if len(word) != 0 { // don't add empty words to our list
|
if len(word) == 0 {
|
||||||
words = append(words, strings.TrimSpace(string(word)))
|
|
||||||
word = []byte{}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(word) == 0 || comment {
|
|
||||||
// empty space, just continue...
|
// empty space, just continue...
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Is this a word we know?
|
||||||
|
w, _ := c.Dictionary.GetWord(sword)
|
||||||
|
|
||||||
int, err := strconv.Atoi(string(word))
|
// if we're not in immedate mode, and the word isn't immediate, add word to buffer and continue parsing
|
||||||
if err == nil {
|
if !c.Flags.GetFlag("Immediate") && !w.Immediate {
|
||||||
// it was a number! put it on the stack.
|
c.Words = append(c.Words, sword)
|
||||||
c.Stack.Push(int)
|
|
||||||
word = []byte{}
|
word = []byte{}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// it wasn't a number. Is it a word we know?
|
ifcheck, _ := c.IfStack.Pick(0)
|
||||||
w, err := c.Dictionary.GetWord(string(word))
|
if len(c.IfStack.values) == 0 || (len(c.IfStack.values) > 0 && ifcheck == 1) || w.BranchCheck {
|
||||||
if err != nil {
|
// run word
|
||||||
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()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
word = []byte{}
|
|
||||||
} else if len(w.Source) != 0 {
|
|
||||||
// user-defined word; let's descend...
|
|
||||||
c.RStack.Push(i)
|
c.RStack.Push(i)
|
||||||
err := c.Eval(strings.Join(w.Source, " ") + " ")
|
err := c.Exec(w, line[i:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -90,14 +55,41 @@ func (c *Context) Eval(line string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error while popping from return stack: %v", err)
|
return fmt.Errorf("error while popping from return stack: %v", err)
|
||||||
}
|
}
|
||||||
word = []byte{}
|
|
||||||
}
|
}
|
||||||
|
word = []byte{}
|
||||||
default:
|
default:
|
||||||
if !comment {
|
word = append(word, line[i])
|
||||||
word = append(word, line[i])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
module git.yetaga.in/alazyreader/prosper
|
||||||
|
|
||||||
go 1.15
|
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=
|
249
main.go
249
main.go
@@ -1,74 +1,243 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"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() {
|
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{}}
|
stack := Stack{values: []int{}}
|
||||||
rstack := Stack{values: []int{}}
|
rstack := Stack{values: []int{}}
|
||||||
|
ifstack := Stack{values: []int{}}
|
||||||
|
|
||||||
dict := Dictionary{}
|
dict := Dictionary{}
|
||||||
b := &Builtins{}
|
|
||||||
dict.AddWord("+", b.Add(&stack), nil)
|
|
||||||
dict.AddWord("-", b.Sub(&stack), nil)
|
|
||||||
dict.AddWord("*", b.Mul(&stack), nil)
|
|
||||||
dict.AddWord("/", b.Div(&stack), nil)
|
|
||||||
dict.AddWord(".", b.Print(&stack), nil)
|
|
||||||
dict.AddWord("=", b.Eq(&stack), nil)
|
|
||||||
dict.AddWord("0=", nil, []string{"0", "="})
|
|
||||||
dict.AddWord("<>", b.NEq(&stack), nil)
|
|
||||||
dict.AddWord(">", b.Gt(&stack), nil)
|
|
||||||
dict.AddWord("<", b.Lt(&stack), nil)
|
|
||||||
dict.AddWord(">=", b.GtEq(&stack), nil)
|
|
||||||
dict.AddWord("<=", b.LtEq(&stack), nil)
|
|
||||||
dict.AddWord("0<", nil, []string{"0", "<"})
|
|
||||||
dict.AddWord("0>", nil, []string{"0", ">"})
|
|
||||||
dict.AddWord("DUP", b.Dup(&stack), nil)
|
|
||||||
dict.AddWord("SWAP", b.Swap(&stack), nil)
|
|
||||||
dict.AddWord("OVER", b.Over(&stack), nil)
|
|
||||||
dict.AddWord("DROP", b.Drop(&stack), nil)
|
|
||||||
dict.AddWord("ROT", b.Rot(&stack), nil)
|
|
||||||
dict.AddWord("WORDS", b.Words(dict), nil)
|
|
||||||
dict.AddWord(".S", b.Debug(&stack), nil)
|
|
||||||
dict.AddWord("EMIT", b.Emit(&stack), nil)
|
|
||||||
dict.AddWord("R>", b.RFrom(&stack, &rstack), nil)
|
|
||||||
dict.AddWord(">R", b.ToR(&stack, &rstack), nil)
|
|
||||||
dict.AddWord("R@", b.RFetch(&stack, &rstack), nil)
|
|
||||||
dict.AddWord("CR", b.CR(), nil)
|
|
||||||
dict.AddWord("QUIT", b.Quit(), nil)
|
|
||||||
|
|
||||||
c := Context{
|
c := Context{
|
||||||
Dictionary: dict,
|
Dictionary: dict,
|
||||||
Stack: &stack,
|
Stack: &stack,
|
||||||
RStack: &rstack,
|
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)
|
defineBuiltIns(&c)
|
||||||
fmt.Print("prosper")
|
|
||||||
|
// 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
|
// read loop
|
||||||
for {
|
for {
|
||||||
fmt.Print("\n> ")
|
p := "> "
|
||||||
|
if !c.Flags["Immediate"] {
|
||||||
|
p = " "
|
||||||
|
}
|
||||||
|
|
||||||
line, err := reader.ReadString('\n')
|
// read line
|
||||||
if err != nil {
|
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())
|
fmt.Println(err.Error())
|
||||||
os.Exit(1)
|
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) {
|
if errors.Is(err, ErrExit) {
|
||||||
fmt.Printf("bye\n")
|
fmt.Printf("bye\n")
|
||||||
os.Exit(0)
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
fmt.Printf("error in evaluation: %v", err)
|
fmt.Printf("error in evaluation: %v\n", err)
|
||||||
} else {
|
} else if c.Flags["Immediate"] {
|
||||||
fmt.Print("ok")
|
fmt.Print("ok\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
m.intern[addr+i] = values[i]
|
||||||
}
|
}
|
||||||
// we've written past our marker, note that
|
// 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)
|
m.nextFree = addr + len(values)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
16
stack.go
16
stack.go
@@ -2,6 +2,12 @@ package main
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
// ErrEmpty denotes an empty stack
|
||||||
|
var ErrEmpty = fmt.Errorf("stack empty")
|
||||||
|
|
||||||
|
// ErrUnderflow denotes a PICK that tried to pick a value that didn't exist
|
||||||
|
var ErrUnderflow = fmt.Errorf("cannot pick value from beyond stack depth")
|
||||||
|
|
||||||
// Stack is a stack of integers with no defined max depth
|
// Stack is a stack of integers with no defined max depth
|
||||||
type Stack struct {
|
type Stack struct {
|
||||||
values []int
|
values []int
|
||||||
@@ -10,7 +16,7 @@ type Stack struct {
|
|||||||
// Pop returns the top of the stack
|
// Pop returns the top of the stack
|
||||||
func (s *Stack) Pop() (int, error) {
|
func (s *Stack) Pop() (int, error) {
|
||||||
if len(s.values) == 0 {
|
if len(s.values) == 0 {
|
||||||
return 0, fmt.Errorf("stack empty")
|
return 0, ErrEmpty
|
||||||
}
|
}
|
||||||
i := s.values[0]
|
i := s.values[0]
|
||||||
s.values = s.values[1:]
|
s.values = s.values[1:]
|
||||||
@@ -21,3 +27,11 @@ func (s *Stack) Pop() (int, error) {
|
|||||||
func (s *Stack) Push(i int) {
|
func (s *Stack) Push(i int) {
|
||||||
s.values = append([]int{i}, s.values...)
|
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()
|
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"
|
import "fmt"
|
||||||
|
|
||||||
|
// ErrNoWordFound flags if the requested word is not in the dictionary
|
||||||
|
var ErrNoWordFound = fmt.Errorf("no word found")
|
||||||
|
|
||||||
// Dictionary is a simple map of names to words
|
// Dictionary is a simple map of names to words
|
||||||
type Dictionary map[string]Word
|
type Dictionary map[string]Word
|
||||||
|
|
||||||
// A Word defines a subroutine
|
// A Word defines a subroutine
|
||||||
type Word struct {
|
type Word struct {
|
||||||
Name string
|
Name string // Name of our word/variable
|
||||||
Impl func() error
|
Impl func(string) error // built-in implementation of the word
|
||||||
Source []string
|
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
|
// AddWord inserts a new word into the dictonary, overwriting any existing word by that name
|
||||||
func (d Dictionary) AddWord(name string, impl func() error, source []string) {
|
func (d Dictionary) AddWord(name string, w Word) {
|
||||||
d[name] = Word{
|
d[name] = w
|
||||||
Name: name,
|
|
||||||
Impl: impl,
|
|
||||||
Source: source,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWord returns a word from the dictionary or an error if it's undefined
|
// GetWord returns a word from the dictionary or an error if it's undefined
|
||||||
@@ -27,7 +29,7 @@ func (d Dictionary) GetWord(name string) (Word, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return Word{
|
return Word{
|
||||||
Name: name,
|
Name: name,
|
||||||
}, fmt.Errorf("no word found")
|
}, ErrNoWordFound
|
||||||
}
|
}
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,10 @@ import "testing"
|
|||||||
func TestDictionary(t *testing.T) {
|
func TestDictionary(t *testing.T) {
|
||||||
d := Dictionary{}
|
d := Dictionary{}
|
||||||
|
|
||||||
d.AddWord("INC", nil, []string{"1", "+"})
|
d.AddWord("INC", Word{
|
||||||
|
Name: "INC",
|
||||||
|
Source: []string{"1", "+"},
|
||||||
|
})
|
||||||
w, err := d.GetWord("INC")
|
w, err := d.GetWord("INC")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
|
Reference in New Issue
Block a user