diff --git a/builtins.go b/builtins.go index dc809e8..ab36b91 100644 --- a/builtins.go +++ b/builtins.go @@ -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 + } +} diff --git a/eval.go b/eval.go index 1da7d82..1bc8db6 100644 --- a/eval.go +++ b/eval.go @@ -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 diff --git a/examples/example.prsp b/examples/example.prsp new file mode 100644 index 0000000..4f81b16 --- /dev/null +++ b/examples/example.prsp @@ -0,0 +1,3 @@ +.( Loading ) +: CUBE DUP DUP * * ; +: SQUARE DUP * ; \ No newline at end of file diff --git a/main.go b/main.go index 292f231..6d76a0f 100644 --- a/main.go +++ b/main.go @@ -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)})