From ec6172f07edf81d8e8be22b2b4b4d7190316ce95 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 20 Jul 2023 19:51:43 +0200 Subject: [PATCH] work in progress: add some 'Forge' (Forth by Go) functionality --- forge/forge.go | 2 ++ forge/stack/stack.go | 53 ++++++++++++++++++++++++++++++++++++++++++++ forge/voc/voc.go | 42 +++++++++++++++++++++++++++++++++++ tests/forge_test.go | 44 ++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 forge/forge.go create mode 100644 forge/stack/stack.go create mode 100644 forge/voc/voc.go create mode 100644 tests/forge_test.go diff --git a/forge/forge.go b/forge/forge.go new file mode 100644 index 0000000..59e8311 --- /dev/null +++ b/forge/forge.go @@ -0,0 +1,2 @@ +// Package forge implements sort of a stack-based interpreter. +package forge diff --git a/forge/stack/stack.go b/forge/stack/stack.go new file mode 100644 index 0000000..c326c9b --- /dev/null +++ b/forge/stack/stack.go @@ -0,0 +1,53 @@ +// package stack provides a generic first-in / first-out stack. +package stack + +// stack interface + +type Stack[V any] interface { + Depth() int + Push(V) + Pop() V + Peek(int) V + Replace(int, V) +} + +// simple slice implementation + +type stack[V any] struct { + tos int + data []V +} + +func NewStack[V any]() *stack[V] { + return &stack[V]{ + tos: -1, + data: make([]V, 0, 5), + } +} + +func (st *stack[V]) Depth() int { + return st.tos + 1 +} + +func (st *stack[V]) Push(v V) { + st.tos += 1 + if st.tos >= len(st.data) { + st.data = append(st.data, v) + } else { + st.data[st.tos] = v + } +} + +func (st *stack[V]) Pop() V { + v := st.data[st.tos] + st.tos -= 1 + return v +} + +func (st *stack[V]) Peek(i int) V { + return st.data[st.tos-i] +} + +func (st *stack[V]) Replace(i int, v V) { + st.data[st.tos-i] = v +} diff --git a/forge/voc/voc.go b/forge/voc/voc.go new file mode 100644 index 0000000..29a4c9d --- /dev/null +++ b/forge/voc/voc.go @@ -0,0 +1,42 @@ +// Package voc implements a generic vocabulary. Vocabularies may be nested +// and are searched upwards using the parent vocabulary until this is nil. +// Individual entries contain slices of values; the last item in a +// slice is the current value. +package voc + +import ( + "git.sr.ht/~cco/go-scopes/lib/funky" +) + +type VocData[V any] map[string][]V + +type Vocabulary[V any] struct { + parent *Vocabulary[V] + data VocData[V] +} + +func NewVoc[V any](parent *Vocabulary[V]) *Vocabulary[V] { + return &Vocabulary[V]{ + parent: parent, + data: VocData[V]{}, + } +} +func (voc *Vocabulary[V]) Register(name string, it V) { + vi, ok := voc.data[name] + if ok { + voc.data[name] = append(vi, it) + } else { + voc.data[name] = []V{it} + } +} + +func (voc *Vocabulary[V]) Lookup(name string) funky.Maybe[V] { + vi, ok := voc.data[name] + if ok { + return funky.Just(vi[len(vi)-1]) + } + if voc.parent != nil { + return voc.parent.Lookup(name) + } + return funky.Nothing[V]() +} diff --git a/tests/forge_test.go b/tests/forge_test.go new file mode 100644 index 0000000..315fb76 --- /dev/null +++ b/tests/forge_test.go @@ -0,0 +1,44 @@ +package scopes_test + +import ( + tbase "testing" + + "git.sr.ht/~cco/go-scopes/forge/stack" + "git.sr.ht/~cco/go-scopes/forge/voc" + "git.sr.ht/~cco/go-scopes/testing" +) + +func TestForge(tb *tbase.T) { + t := testing.SetUp(tb) + t.Run("stack", StackTest) + t.Run("voc", VocTest) +} + +func StackTest(t *testing.T) { + var st stack.Stack[int] = stack.NewStack[int]() + st.Push(3) + st.Push(4) + t.AssertEqual(st.Depth(), 2) + st.Push(5) + t.AssertEqual(st.Peek(0), 5) + st.Replace(1, 99) + st.Push(6) + t.AssertEqual(st.Peek(2), 99) + st.Push(7) + st.Push(8) + st.Push(9) + t.AssertEqual(st.Peek(2), 7) + st.Pop() + t.AssertEqual(st.Pop(), 8) + t.AssertEqual(st.Peek(st.Depth()-1), 3) +} + +func VocTest(t *testing.T) { + v1 := voc.NewVoc[int](nil) + v1.Register("one", 1) + m := v1.Lookup("one") + t.AssertEqual(m.IsNothing(), false) + t.AssertEqual(m.Value(), 1) + m = v1.Lookup("two") + t.AssertEqual(m.IsNothing(), true) +}