~evanj/forth

7959b06e59708beead712f5ad8a6bd1c64394d4f — Evan J 2 years ago
Feat(*): Project init.
3 files changed, 460 insertions(+), 0 deletions(-)

A go.mod
A go.sum
A main.go
A  => go.mod +3 -0
@@ 1,3 @@
module git.sr.ht/~evanj/forth

go 1.14

A  => go.sum +0 -0
A  => main.go +457 -0
@@ 1,457 @@
package main

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"strconv"
	"sync"
	"unicode"
)

type typ int

const (
	TypWord typ = iota
	TypNumber
)

type node struct {
	typ    typ
	word   string
	number float64
}

func (n node) String() string {
	switch n.typ {
	case TypWord:
		return fmt.Sprintf("%v", n.word)
	case TypNumber:
		return fmt.Sprintf("%v", n.number)
	default:
		panic("unreachable")
	}
}

type word func(currStack []node, output chan interface{}, done chan struct{}, ech chan error) (nextStack []node)

type d struct {
	*sync.RWMutex
	words map[string]word
}

func newDict() *d {
	m := map[string]word{
		".s":     builtinPrint,
		"print":  builtinPrint,
		".":      builtinDrop,
		"drop":   builtinDrop,
		"dup":    builtinDup,
		"swap":   builtinSwap,
		"rot":    builtinRot,
		"emit":   builtinEmit,
		"cr":     builtinCR,
		"=":      builtinMathEq,
		"eq":     builtinMathEq,
		">":      builtinMathGT,
		"gt":     builtinMathGT,
		"<":      builtinMathLT,
		"lt":     builtinMathLT,
		"+":      builtinMathAdd,
		"add":    builtinMathAdd,
		"-":      builtinMathSub,
		"sub":    builtinMathSub,
		"*":      builtinMathMul,
		"mul":    builtinMathMul,
		"/":      builtinMathDiv,
		"div":    builtinMathDiv,
		"!=":     builtinInvert,
		"invert": builtinInvert,
		"bye":    builtinBye,
	}
	return &d{&sync.RWMutex{}, m}
}

type s struct {
	*sync.RWMutex
	nodes []node
}

func newStack() *s {
	return &s{&sync.RWMutex{}, []node{}}
}

var (
	ErrStackBad = errors.New("bad stack")

	dict  = newDict()
	stack = newStack()
)

func main() {
	var (
		input  = make(chan byte)
		mode1  = make(chan node) // for interpreter
		mode2  = make(chan node) // for compiler
		output = make(chan interface{})
		ech    = make(chan error)
		done   = make(chan struct{})
	)

	go read(os.Stdin, input, done, ech)
	go token(input, mode1, mode2, ech)
	go interpret(mode1, output, done, ech)
	go compile(mode2, ech)
	go write(os.Stdin, output)

	select {
	case <-done:
		return
	case err := <-ech:
		log.Fatal(err)
	}
}

func read(from io.Reader, c chan byte, done chan struct{}, ech chan error) {
	for {
		bytes := make([]byte, 1)
		_, err := from.Read(bytes)
		if errors.Is(err, io.EOF) {
			done <- struct{}{}
			return
		}
		if err != nil {
			ech <- err
			break
		}
		for _, b := range bytes {
			c <- b
		}
	}
}

func token(input chan byte, mode1, mode2 chan node, ech chan error) {
	type State int

	const (
		Start State = iota
		Interpret
		Compile
	)

	var (
		word  string
		state = Start
	)

	for b := range input {
		isSpace := unicode.IsSpace(rune(b))

		switch state {

		case Start:
			word = ""

			if isSpace {
				break
			}

			if b == ':' {
				state = Compile
				break
			}

			state = Interpret
			word += string(b)

		case Interpret:
			if isSpace {
				// TODO: parse current word into node, send to stack.
				node, err := tonode(word)
				if err != nil {
					ech <- err
					break
				}
				mode1 <- node
				state = Start
				break
			}

			word += string(b)

		case Compile:
			panic("TODO: Implement compile state in tokenizer.")

		}
	}
}

// interpret will execute words within dict with what's on stack.
func interpret(mode1 chan node, output chan interface{}, done chan struct{}, ech chan error) {
	for n := range mode1 {
		func(n node) {
			dict.RLock()
			stack.Lock()

			defer dict.RUnlock()
			defer stack.Unlock()

			switch n.typ {

			case TypWord:
				if word, ok := dict.words[n.word]; ok {
					// Execute!
					stack.nodes = word(stack.nodes, output, done, ech)
					break
				}

				// Add to stack.
				stack.nodes = append(stack.nodes, n)

			case TypNumber:
				stack.nodes = append(stack.nodes, n)
			}
		}(n)
	}
}

// compile will add words to dict.
func compile(stack chan node, ech chan error) {
}

func write(to io.Writer, output chan interface{}) {
	for it := range output {
		fmt.Fprint(to, it)
	}
}

func tonode(input string) (node, error) {
	f, err := strconv.ParseFloat(input, 64)
	if err != nil {
		return node{typ: TypWord, word: input}, nil
	}
	return node{typ: TypNumber, number: f}, nil
}

// BUILTINS

func builtinPrint(currStack []node, output chan interface{}, done chan struct{}, ech chan error) (nextStack []node) {
	output <- fmt.Sprintf("<%d> ", len(currStack))
	for _, n := range currStack {
		output <- n
		output <- " "
	}
	output <- "\n"
	return currStack
}

func builtinDrop(currStack []node, output chan interface{}, done chan struct{}, ech chan error) (nextStack []node) {
	if len(currStack) < 1 {
		ech <- ErrStackBad
		return currStack
	}
	_, nextStack = currStack[len(currStack)-1], currStack[:len(currStack)-1]
	return
}

func builtinDup(currStack []node, output chan interface{}, done chan struct{}, ech chan error) (nextStack []node) {
	if len(currStack) < 1 {
		ech <- ErrStackBad
		return currStack
	}
	latest, nextStack := currStack[len(currStack)-1], currStack[:len(currStack)-1]
	return append(nextStack, latest, latest)
}

func builtinSwap(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 2 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	b, stack := stack[len(stack)-1], stack[:len(stack)-1]
	return append(stack, a, b)
}

func builtinRot(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 3 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	b, stack := stack[len(stack)-1], stack[:len(stack)-1]
	c, stack := stack[len(stack)-1], stack[:len(stack)-1]
	return append(stack, b, a, c)
}

func builtinEmit(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 1 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	if a.typ != TypNumber {
		ech <- ErrStackBad
		return append(stack, a)
	}
	output <- string(int(a.number)) // TODO: Remove int(...) here.
	output <- "\n"
	return stack
}

func builtinCR(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	output <- "\n"
	return stack
}

func builtinMathEq(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 2 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	b, stack := stack[len(stack)-1], stack[:len(stack)-1]
	c, err := mathop(a, b, func(a, b float64) node { return totrue(a == b) })
	if err != nil {
		ech <- ErrStackBad
		return append(stack, b, a)
	}
	return append(stack, c)
}

func builtinMathGT(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 2 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	b, stack := stack[len(stack)-1], stack[:len(stack)-1]
	c, err := mathop(a, b, func(a, b float64) node { return totrue(a > b) })
	if err != nil {
		ech <- ErrStackBad
		return append(stack, b, a)
	}
	return append(stack, c)
}

func builtinMathLT(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 2 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	b, stack := stack[len(stack)-1], stack[:len(stack)-1]
	c, err := mathop(a, b, func(a, b float64) node { return totrue(a < b) })
	if err != nil {
		ech <- ErrStackBad
		return append(stack, b, a)
	}
	return append(stack, c)
}

func builtinInvert(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 1 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	if a.typ != TypNumber {
		ech <- ErrStackBad
		return append(stack, a)
	}
	if a.number != float64(0) {
		return append(stack, node{TypNumber, "", float64(0)})
	}
	return append(stack, node{TypNumber, "", float64(-1)})
}

func builtinBye(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	done <- struct{}{}
	return stack
}

func builtinMathAdd(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 2 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	b, stack := stack[len(stack)-1], stack[:len(stack)-1]
	c, err := mathop(a, b, func(a, b float64) node {
		return node{TypNumber, "", a + b}
	})
	if err != nil {
		ech <- ErrStackBad
		return append(stack, b, a)
	}
	return append(stack, c)
}

func builtinMathSub(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 2 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	b, stack := stack[len(stack)-1], stack[:len(stack)-1]
	c, err := mathop(a, b, func(a, b float64) node {
		return node{TypNumber, "", a - b}
	})
	if err != nil {
		ech <- ErrStackBad
		return append(stack, b, a)
	}
	return append(stack, c)
}

func builtinMathMul(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 2 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	b, stack := stack[len(stack)-1], stack[:len(stack)-1]
	c, err := mathop(a, b, func(a, b float64) node {
		return node{TypNumber, "", a * b}
	})
	if err != nil {
		ech <- ErrStackBad
		return append(stack, b, a)
	}
	return append(stack, c)
}

func builtinMathDiv(stack []node, output chan interface{}, done chan struct{}, ech chan error) []node {
	if len(stack) < 2 {
		ech <- ErrStackBad
		return stack
	}
	a, stack := stack[len(stack)-1], stack[:len(stack)-1]
	b, stack := stack[len(stack)-1], stack[:len(stack)-1]
	c, err := mathop(a, b, func(a, b float64) node {
		return node{TypNumber, "", a / b}
	})
	if err != nil {
		ech <- ErrStackBad
		return append(stack, b, a)
	}
	return append(stack, c)
}

// helpers

func totrue(val bool) node {
	if val {
		return node{TypNumber, "", float64(-1)}
	}
	return node{TypNumber, "", float64(0)}
}

func mathop(a, b node, f func(a, b float64) node) (node, error) {
	if a.typ != TypNumber || b.typ != TypNumber {
		return node{}, ErrStackBad
	}
	return f(b.number, a.number), nil
}