fix some string bugs and make external-file-loading work

This commit is contained in:
David 2021-02-26 20:49:10 -05:00
parent 7291ca86c8
commit 5e782bc728
4 changed files with 82 additions and 18 deletions

View File

@ -1,9 +1,12 @@
package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
)
@ -44,6 +47,7 @@ func (b *Builtins) Semicolon(c *Context) func(string) error {
// 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 ')':
@ -70,24 +74,25 @@ func (b *Builtins) EOLComment(c *Context) func(string) error {
// 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", false)
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, r *Stack, close byte) func(string) error {
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, _ := r.Pop()
r.Push(j + i - 1) // push the end-point onto the stack
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])
@ -101,10 +106,25 @@ func (b *Builtins) OpenQuote(out io.Writer, r *Stack, close byte) func(string) e
// 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 {
@ -755,3 +775,43 @@ func (b *Builtins) Fetch(c *Context) func(string) error {
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
}
}

22
eval.go
View File

@ -8,13 +8,14 @@ import (
// Context is a set of Dictionary + Stacks + Flags representing a runtime environment
type Context struct {
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
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
}
// Eval evaulates a given line, recursively descending into given words as needed
@ -32,12 +33,9 @@ func (c *Context) Eval(line string) error {
}
// Is this a word we know?
w, _ := c.Dictionary.GetWord(sword)
// check if it's an IMMEDIATE mode toggle word
if !c.Flags.GetFlag("Immediate") {
c.Flags.SetFlag("Immediate", w.Immediate)
}
if !c.Flags.GetFlag("Immediate") {
// 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

3
examples/example.prsp Normal file
View File

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

View File

@ -28,6 +28,7 @@ func main() {
intern: map[int]int{},
nextFree: 1,
},
StringBuffer: "",
}
b := &Builtins{}
@ -38,9 +39,11 @@ func main() {
dict.AddWord("(", Word{Name: "(", Impl: b.OpenComment(&c), Immediate: true})
dict.AddWord(")", Word{Name: ")", Impl: b.CloseComment(&c), Immediate: true})
dict.AddWord(`\`, Word{Name: `\`, Impl: b.EOLComment(&c), Immediate: true})
dict.AddWord(`."`, Word{Name: `."`, Impl: b.OpenQuote(os.Stdout, &rstack, '"')})
dict.AddWord(`."`, Word{Name: `."`, Impl: b.OpenQuote(os.Stdout, &c, '"')})
dict.AddWord(`"`, Word{Name: `"`, Impl: b.CloseQuote(&c)})
dict.AddWord(`.(`, Word{Name: `.(`, Impl: b.OpenQuote(os.Stdout, &rstack, ')'), Immediate: true})
dict.AddWord(`.(`, Word{Name: `.(`, Impl: b.OpenQuote(os.Stdout, &c, ')'), Immediate: true})
dict.AddWord(`S"`, Word{Name: `S"`, Impl: b.StringBuffer(&c, '"')})
dict.AddWord("LOAD", Word{Name: "LOAD", Impl: b.Load(&c)})
// math
dict.AddWord("+", Word{Name: "+", Impl: b.Add(&stack)})
dict.AddWord("-", Word{Name: "-", Impl: b.Sub(&stack)})