package main 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{} // 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(string) error { return func(_ string) error { tos, err := s.Pop() if err != nil { return err } nos, err := s.Pop() if err != nil { return err } s.Push(tos + nos) return nil } } // Sub performs NOS - TOS and pushes the result func (b *Builtins) Sub(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 } 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(string) error { return func(_ string) error { tos, err := s.Pop() if err != nil { return err } nos, err := s.Pop() if err != nil { return err } s.Push(nos * tos) return nil } } // Div performs NOS/TOS and pushes the (integer!) result func (b *Builtins) Div(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 } s.Push(nos / tos) return nil } } // 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 } 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(string) error { return func(_ string) error { tos, err := s.Pop() if err != nil { return err } s.Push(tos) s.Push(tos) return nil } } // Swap inverts the order of TOS and NOS func (b *Builtins) Swap(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 } 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(string) error { return func(_ string) error { nos, err := s.Pick(1) if err != nil { return err } s.Push(nos) return nil } } // Drop simply discards TOS 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(string) error { return func(_ string) error { tos, err := s.Pop() if err != nil { return err } nos, err := s.Pop() if err != nil { return err } p3, err := s.Pop() if err != nil { return err } 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(out io.Writer, d Dictionary) func(string) error { if out == nil { out = os.Stdout } return func(_ string) error { for n := range d { 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(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, string(rune(tos))+" ") return nil } } // 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(string) error { return func(_ string) error { return ErrExit } } // Debug prints the stack without modifying it 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 } }