summaryrefslogtreecommitdiff
path: root/go/forth/forth.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/forth/forth.go')
-rw-r--r--go/forth/forth.go217
1 files changed, 217 insertions, 0 deletions
diff --git a/go/forth/forth.go b/go/forth/forth.go
new file mode 100644
index 0000000..8675941
--- /dev/null
+++ b/go/forth/forth.go
@@ -0,0 +1,217 @@
+package forth
+
+import (
+ "errors"
+ "log"
+ "strconv"
+ "strings"
+)
+
+type stacker interface {
+ push(int) error
+ pop() (int, error)
+ values() []int
+}
+
+type stack []int
+
+func (s *stack) push(n int) error {
+ *s = append(*s, n)
+ return nil
+}
+
+func (s *stack) pop() (int, error) {
+ depth := len(*s)
+ if depth < 1 {
+ return 0, errors.New("stack underflow")
+ }
+ tos := (*s)[depth-1]
+ *s = (*s)[:depth-1]
+ return tos, nil
+}
+
+func (s *stack) values() []int {
+ return []int(*s)
+}
+
+func pop2(s stacker) (int, int, error) {
+ tos, err := s.pop()
+ if err != nil {
+ return 0, 0, err
+ }
+ nos, err := s.pop()
+ if err != nil {
+ return 0, 0, err
+ }
+ return tos, nos, nil
+}
+
+func pushN(s stacker, n ...int) error {
+ for _, v := range n {
+ if err := s.push(v); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+type dictionary map[string][]func(stacker) error
+
+func (d dictionary) find(name string) ([]func(stacker) error, bool) {
+ fs, ok := d[strings.ToLower(name)]
+ return fs, ok
+}
+
+func (d dictionary) add(name string, fs ...func(stacker) error) {
+ d[strings.ToLower(name)] = fs
+}
+
+func add(s stacker) error {
+ tos, nos, err := pop2(s)
+ if err != nil {
+ return err
+ }
+ return s.push(nos + tos)
+}
+
+func sub(s stacker) error {
+ tos, nos, err := pop2(s)
+ if err != nil {
+ return err
+ }
+ return s.push(nos - tos)
+}
+
+func mul(s stacker) error {
+ tos, nos, err := pop2(s)
+ if err != nil {
+ return err
+ }
+ return s.push(nos * tos)
+}
+
+func div(s stacker) error {
+ tos, nos, err := pop2(s)
+ if err != nil {
+ return err
+ }
+ if tos == 0 {
+ return errors.New("division by zero")
+ }
+ return s.push(nos / tos)
+}
+
+func dup(s stacker) error {
+ tos, err := s.pop()
+ if err != nil {
+ return err
+ }
+ return pushN(s, tos, tos)
+}
+
+func drop(s stacker) error {
+ _, err := s.pop()
+ return err
+}
+
+func swap(s stacker) error {
+ tos, nos, err := pop2(s)
+ if err != nil {
+ return err
+ }
+ return pushN(s, tos, nos)
+}
+
+func over(s stacker) error {
+ tos, nos, err := pop2(s)
+ if err != nil {
+ return err
+ }
+ return pushN(s, nos, tos, nos)
+}
+
+func literal(n int) func(stacker) error {
+ return func(s stacker) error {
+ return s.push(n)
+ }
+}
+
+func number(word string) (int, bool) {
+ n, err := strconv.Atoi(word)
+ return n, err == nil
+}
+
+func index(s []string, r string) int {
+ for i, v := range s {
+ if v == r {
+ return i
+ }
+ }
+ return -1
+}
+
+func compile(dict dictionary, s []string) ([]func(stacker) error, error) {
+ log.Println("compile", s)
+ var words []func(stacker) error
+ for i := 0; i < len(s); i++ {
+ v := s[i]
+ // lookup dictionary first
+ if w, ok := dict.find(v); ok {
+ words = append(words, w...)
+ continue
+ }
+ if v == ":" {
+ next := index(s[i+1:], ";")
+ if next == -1 {
+ return nil, errors.New("unterminated colon operator")
+ }
+ name := s[i+1]
+ if _, ok := number(name); ok {
+ return nil, errors.New("name cannot be a number")
+ }
+ w, err := compile(dict, s[i+2:i+next+1])
+ if err != nil {
+ return nil, err
+ }
+ log.Println("add", name)
+ dict.add(name, w...)
+ i += next + 1
+ continue
+ }
+ // try to parse literal
+ if n, ok := number(v); ok {
+ words = append(words, literal(n))
+ continue
+ }
+ return nil, errors.New("unknown word")
+ }
+ return words, nil
+}
+
+func Forth(v []string) ([]int, error) {
+ s := new(stack)
+ dict := make(dictionary)
+ dict.add("+", add)
+ dict.add("-", sub)
+ dict.add("*", mul)
+ dict.add("/", div)
+ dict.add("dup", dup)
+ dict.add("drop", drop)
+ dict.add("swap", swap)
+ dict.add("over", over)
+
+ var words []func(stacker) error
+ for _, line := range v {
+ w, err := compile(dict, strings.Split(line, " "))
+ if err != nil {
+ return nil, err
+ }
+ words = append(words, w...)
+ }
+ for _, w := range words {
+ if err := w(s); err != nil {
+ return nil, err
+ }
+ }
+ return s.values(), nil
+}