aboutsummaryrefslogtreecommitdiff
path: root/pkg/pointer
diff options
context:
space:
mode:
authorJulien Dessaux2021-09-23 11:45:49 +0200
committerJulien Dessaux2021-09-23 11:45:49 +0200
commit759ee2aa10323d1960405c93f7cd4cf6d383ae7a (patch)
tree87648b4811b0264f1b54c454ed2569abe1abee94 /pkg/pointer
parentBegan coding the interpreter (only manages the minimal example for now!) (diff)
downloadgofunge98-759ee2aa10323d1960405c93f7cd4cf6d383ae7a.tar.gz
gofunge98-759ee2aa10323d1960405c93f7cd4cf6d383ae7a.tar.bz2
gofunge98-759ee2aa10323d1960405c93f7cd4cf6d383ae7a.zip
Each pointer needs its own stack, merging the two packages to avoid a circular dependency
Diffstat (limited to 'pkg/pointer')
-rw-r--r--pkg/pointer/pointer.go52
-rw-r--r--pkg/pointer/pointer_test.go64
-rw-r--r--pkg/pointer/stack-stack.go95
-rw-r--r--pkg/pointer/stack-stack_test.go260
-rw-r--r--pkg/pointer/stack.go51
-rw-r--r--pkg/pointer/stack_test.go74
6 files changed, 591 insertions, 5 deletions
diff --git a/pkg/pointer/pointer.go b/pkg/pointer/pointer.go
index a833f5b..4d847bf 100644
--- a/pkg/pointer/pointer.go
+++ b/pkg/pointer/pointer.go
@@ -1,6 +1,10 @@
package pointer
-import "git.adyxax.org/adyxax/gofunge/pkg/field"
+import (
+ "math/rand"
+
+ "git.adyxax.org/adyxax/gofunge/pkg/field"
+)
type Pointer struct {
// the position
@@ -12,12 +16,14 @@ type Pointer struct {
// The Storage offset
sox int
soy int
+ // The stack
+ ss *StackStack
// The next element for the multi-"threaded" b98 interpreter
Next *Pointer
}
func NewPointer() *Pointer {
- return &Pointer{dx: 1}
+ return &Pointer{dx: 1, ss: NewStackStack()}
}
func (p Pointer) Split() *Pointer {
@@ -31,3 +37,45 @@ func (p *Pointer) Step(f field.Field) {
func (p Pointer) Get(f field.Field) int {
return f.Get(p.x, p.y)
}
+
+func (p *Pointer) Set(x, y int) {
+ p.x, p.y = x, y
+}
+
+func (p *Pointer) RedirectTo(dx, dy int) {
+ p.dx, p.dy = dx, dy
+}
+
+func (p *Pointer) Reverse() {
+ p.dx, p.dy = -p.dx, -p.dy
+}
+
+func (p *Pointer) Redirect(c int) bool {
+ switch c {
+ case '^':
+ p.dx, p.dy = 0, -1
+ case '>':
+ p.dx, p.dy = 1, 0
+ case 'v':
+ p.dx, p.dy = 0, 1
+ case '<':
+ p.dx, p.dy = -1, 0
+ case '?':
+ directions := []int{0, -1, 1, 0, 0, 1, -1, 0}
+ r := 2 * rand.Intn(4)
+ p.dx, p.dy = directions[r], directions[r+1]
+ case '[':
+ p.dx, p.dy = p.dy, -p.dx
+ case ']':
+ p.dx, p.dy = -p.dy, p.dx
+ case 'r':
+ p.Reverse()
+ case 'x':
+ dy := p.ss.Pop()
+ dx := p.ss.Pop()
+ p.RedirectTo(dx, dy)
+ default:
+ return false
+ }
+ return true
+}
diff --git a/pkg/pointer/pointer_test.go b/pkg/pointer/pointer_test.go
index ccbf486..4885d6b 100644
--- a/pkg/pointer/pointer_test.go
+++ b/pkg/pointer/pointer_test.go
@@ -9,7 +9,7 @@ import (
)
func TestNewPointer(t *testing.T) {
- require.Equal(t, NewPointer(), &Pointer{dx: 1})
+ require.Equal(t, NewPointer(), &Pointer{dx: 1, ss: NewStackStack()})
}
func TestSplit(t *testing.T) {
@@ -23,8 +23,8 @@ func TestSplit(t *testing.T) {
// We check that p2 is a real copy
p.Step(*f)
p2.Step(*f)
- require.Equal(t, &Pointer{x: 1, y: 0, dx: 1}, p)
- require.Equal(t, &Pointer{x: 1, y: 0, dx: 1}, p2)
+ require.Equal(t, &Pointer{x: 1, y: 0, dx: 1, ss: NewStackStack()}, p)
+ require.Equal(t, &Pointer{x: 1, y: 0, dx: 1, ss: NewStackStack()}, p2)
}
func TestStep(t *testing.T) { // Step is thoroughly tested in the field package
@@ -50,3 +50,61 @@ func TestGet(t *testing.T) {
v := p.Get(*f)
require.Equal(t, int('@'), v)
}
+
+func TestSet(t *testing.T) {
+ p := NewPointer()
+ p.Set(3, 14)
+ require.Equal(t, 3, p.x)
+ require.Equal(t, 14, p.y)
+}
+
+func TestRedirectTo(t *testing.T) {
+ p := NewPointer()
+ p.RedirectTo(3, 14)
+ require.Equal(t, 3, p.dx)
+ require.Equal(t, 14, p.dy)
+}
+
+func TestReverse(t *testing.T) {
+ p := NewPointer()
+ p.RedirectTo(3, 14)
+ p.Reverse()
+ require.Equal(t, -3, p.dx)
+ require.Equal(t, -14, p.dy)
+}
+
+func TestRedirect(t *testing.T) {
+ testCases := []struct {
+ name string
+ input byte
+ expectedDx int
+ expectedDy int
+ }{
+ {"up", '^', 0, -1},
+ {"right", '>', 1, 0},
+ {"down", 'v', 0, 1},
+ {"left", '<', -1, 0},
+ {"turn left", '[', 14, -3},
+ {"turn right", ']', -14, 3},
+ {"reverse", 'r', -3, -14},
+ {"redirectTo", 'x', 2, 7},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ p := NewPointer()
+ p.RedirectTo(3, 14)
+ p.ss.Push(2)
+ p.ss.Push(7)
+ v := p.Redirect(int(tc.input))
+ require.Equal(t, true, v)
+ require.Equal(t, tc.expectedDx, p.dx, "Invalid dx value")
+ require.Equal(t, tc.expectedDy, p.dy, "Invalid dy value")
+ })
+ }
+ // We cannot really test random, can we? This just gives coverage
+ p := NewPointer()
+ v := p.Redirect(int('?'))
+ require.Equal(t, true, v)
+ // A character that does not redirect should return false
+ require.Equal(t, false, p.Redirect(int('@')))
+}
diff --git a/pkg/pointer/stack-stack.go b/pkg/pointer/stack-stack.go
new file mode 100644
index 0000000..6d22f51
--- /dev/null
+++ b/pkg/pointer/stack-stack.go
@@ -0,0 +1,95 @@
+package pointer
+
+type StackStack struct {
+ head *Stack
+ height int
+}
+
+func NewStackStack() *StackStack {
+ return &StackStack{
+ head: NewStack(),
+ height: 1,
+ }
+}
+
+func (ss *StackStack) Begin(p *Pointer) {
+ ss.height++
+ soss := ss.head
+ n := soss.Pop()
+ np := n
+ if np < 0 {
+ np = -np
+ }
+ toss := &Stack{
+ size: np,
+ height: np,
+ data: make([]int, np),
+ next: soss,
+ }
+ ss.head = toss
+ max := n - soss.height
+ if max < 0 {
+ max = 0
+ }
+ for i := n - 1; i >= max; i-- {
+ toss.data[i] = soss.data[soss.height-n+i]
+ }
+ x, y := p.GetStorageOffset()
+ soss.Push(x)
+ soss.Push(y)
+ p.CalculateNewStorageOffset()
+}
+
+func (ss *StackStack) End(p *Pointer) (reflect bool) {
+ if ss.height == 1 {
+ return true
+ }
+ soss := ss.head.next
+ n := ss.head.Pop()
+ y := soss.Pop()
+ x := soss.Pop()
+ p.SetStorageOffset(x, y)
+ if n > 0 {
+ soss.height += n
+ if soss.size < soss.height {
+ soss.data = append(soss.data, make([]int, soss.height-soss.size)...)
+ soss.size = soss.height
+ }
+ for i := n; i > 0; i-- {
+ soss.data[soss.height-i] = ss.head.data[ss.head.height-i]
+ }
+ } else {
+ soss.height += n
+ if soss.height < 0 {
+ soss.height = 0
+ }
+ }
+ ss.height--
+ ss.head = ss.head.next
+ return false
+}
+
+func (ss *StackStack) Under() (reflect bool) {
+ if ss.height == 1 {
+ return true
+ }
+ n := ss.head.Pop()
+ if n > 0 {
+ for i := 0; i < n; i++ {
+ ss.head.Push(ss.head.next.Pop())
+ }
+ } else {
+ for i := 0; i < -n; i++ {
+ ss.head.next.Push(ss.head.Pop())
+ }
+ }
+ return false
+}
+
+func (ss StackStack) Pop() int {
+ return ss.head.Pop()
+}
+
+func (ss StackStack) Push(v int) {
+ ss.head.Push(v)
+}
diff --git a/pkg/pointer/stack-stack_test.go b/pkg/pointer/stack-stack_test.go
new file mode 100644
index 0000000..fd4c9ea
--- /dev/null
+++ b/pkg/pointer/stack-stack_test.go
@@ -0,0 +1,260 @@
+package pointer
+
+import (
+ "os"
+ "testing"
+
+ "git.adyxax.org/adyxax/gofunge/pkg/field"
+ "github.com/stretchr/testify/require"
+)
+
+func TestBegin(t *testing.T) {
+ t.Run("empty", func(t *testing.T) {
+ expected := NewStackStack()
+ expected.head = &Stack{data: make([]int, 0), next: expected.head}
+ expected.head.next.Push(0)
+ expected.head.next.Push(0)
+ expected.height++
+ ss := NewStackStack()
+ p := NewPointer()
+ ss.Begin(p)
+ require.Equal(t, expected, ss)
+ x, y := p.GetStorageOffset()
+ require.Equal(t, 1, x)
+ require.Equal(t, 0, y)
+ // Let's push another one
+ expected.head = &Stack{data: make([]int, 0), next: expected.head}
+ expected.head.next.Push(1)
+ expected.head.next.Push(0)
+ expected.height++
+ ss.Begin(p)
+ require.Equal(t, expected, ss)
+ x, y = p.GetStorageOffset()
+ require.Equal(t, 1, x)
+ require.Equal(t, 0, y)
+ })
+ t.Run("negative", func(t *testing.T) {
+ expected := NewStackStack()
+ expected.head = &Stack{size: 5, height: 5, data: make([]int, 5), next: expected.head}
+ expected.head.next.Push(0)
+ expected.head.next.Push(0)
+ expected.height++
+ p := NewPointer()
+ file, err := os.Open("../field/test_data/hello.b98")
+ require.NoError(t, err, "Failed to open file")
+ f, err := field.Load(file)
+ p.Step(*f)
+ ss := NewStackStack()
+ ss.head.Push(-5)
+ ss.Begin(p)
+ require.Equal(t, expected, ss)
+ x, y := p.GetStorageOffset()
+ require.Equal(t, 2, x)
+ require.Equal(t, 0, y)
+ })
+ t.Run("ask to copy more than we have", func(t *testing.T) {
+ expected := NewStackStack()
+ expected.head = &Stack{size: 34, height: 34, data: make([]int, 34), next: expected.head}
+ expected.head.data[33] = 18
+ expected.head.next.Push(18)
+ expected.head.next.Push(2)
+ expected.head.next.Push(3)
+ expected.height++
+ p := NewPointer()
+ p.SetStorageOffset(2, 3)
+ file, err := os.Open("../field/test_data/hello.b98")
+ require.NoError(t, err, "Failed to open file")
+ f, err := field.Load(file)
+ p.Step(*f)
+ ss := NewStackStack()
+ ss.head.Push(18)
+ ss.head.Push(34)
+ ss.Begin(p)
+ require.Equal(t, expected, ss)
+ x, y := p.GetStorageOffset()
+ require.Equal(t, 2, x)
+ require.Equal(t, 0, y)
+ })
+ t.Run("normal", func(t *testing.T) {
+ expected := NewStackStack()
+ expected.head = &Stack{size: 4, height: 4, data: []int{12, 14, -2, 5}, next: expected.head}
+ expected.head.next.Push(7)
+ expected.head.next.Push(12)
+ expected.head.next.Push(14)
+ expected.head.next.Push(-2)
+ expected.head.next.Push(5)
+ expected.head.next.Push(36)
+ expected.head.next.Push(42)
+ expected.height++
+ p := NewPointer()
+ p.SetStorageOffset(36, 42)
+ ss := NewStackStack()
+ ss.head.Push(7)
+ ss.head.Push(12)
+ ss.head.Push(14)
+ ss.head.Push(-2)
+ ss.head.Push(5)
+ ss.head.Push(4)
+ ss.Begin(p)
+ require.Equal(t, expected, ss)
+ })
+}
+
+func TestEnd(t *testing.T) {
+ t.Run("empty", func(t *testing.T) {
+ expected := NewStackStack()
+ p := NewPointer()
+ ss := NewStackStack()
+ ss.Begin(p)
+ reflect := ss.End(p)
+ require.Equal(t, false, reflect)
+ require.Equal(t, expected, ss)
+ })
+ t.Run("drop", func(t *testing.T) {
+ expected := NewStackStack()
+ expected.head.Push(7)
+ expected.head.Push(12)
+ expected.head.Push(14)
+ expected.head.Push(-2)
+ expected.head.Push(5)
+ expected.head.Pop()
+ expected.head.Pop()
+ expected.head.Pop()
+ p := NewPointer()
+ ss := NewStackStack()
+ ss.head.Push(7)
+ ss.head.Push(12)
+ ss.head.Push(14)
+ ss.head.Push(-2)
+ ss.head.Push(5)
+ ss.head.Push(0)
+ ss.Begin(p)
+ ss.head.Push(18)
+ ss.head.Push(42)
+ ss.head.Push(-3)
+ reflect := ss.End(p)
+ require.Equal(t, false, reflect)
+ require.Equal(t, expected, ss)
+ })
+ t.Run("drop too much", func(t *testing.T) {
+ expected := NewStackStack()
+ p := NewPointer()
+ ss := NewStackStack()
+ ss.Begin(p)
+ ss.head.Push(-3)
+ reflect := ss.End(p)
+ require.Equal(t, false, reflect)
+ require.Equal(t, expected, ss)
+ })
+ t.Run("reflect", func(t *testing.T) {
+ expected := NewStackStack()
+ p := NewPointer()
+ ss := NewStackStack()
+ reflect := ss.End(p)
+ require.Equal(t, true, reflect)
+ require.Equal(t, expected, ss)
+ })
+ t.Run("transfert", func(t *testing.T) {
+ expected := NewStackStack()
+ expected.head.size = 5
+ expected.head.data = make([]int, 5)
+ expected.head.Push(7)
+ expected.head.Push(12)
+ expected.head.Push(14)
+ expected.head.Push(-2)
+ expected.head.Push(5)
+ p := NewPointer()
+ ss := NewStackStack()
+ ss.head.size = 4
+ ss.head.data = make([]int, 4)
+ ss.head.Push(7)
+ ss.head.Push(0)
+ ss.Begin(p)
+ ss.head.Push(0)
+ ss.head.Push(18)
+ ss.head.Push(42)
+ ss.head.Push(7)
+ ss.head.Push(12)
+ ss.head.Push(14)
+ ss.head.Push(-2)
+ ss.head.Push(5)
+ ss.head.Push(4)
+ reflect := ss.End(p)
+ require.Equal(t, false, reflect)
+ require.Equal(t, expected, ss)
+ })
+}
+
+func TestUnder(t *testing.T) {
+ t.Run("empty", func(t *testing.T) {
+ expected := NewStackStack()
+ ss := NewStackStack()
+ reflect := ss.Under()
+ require.Equal(t, true, reflect)
+ require.Equal(t, expected, ss)
+ })
+ t.Run("positive", func(t *testing.T) {
+ pe := NewPointer()
+ expected := NewStackStack()
+ expected.head.Push(1)
+ expected.head.Push(2)
+ expected.head.Push(3)
+ expected.head.Push(6)
+ expected.head.Push(0)
+ expected.Begin(pe)
+ expected.head.Push(0)
+ expected.head.Push(0)
+ expected.head.Push(6)
+ expected.head.next.Pop()
+ expected.head.next.Pop()
+ expected.head.next.Pop()
+ p := NewPointer()
+ ss := NewStackStack()
+ ss.head.Push(1)
+ ss.head.Push(2)
+ ss.head.Push(3)
+ ss.head.Push(6)
+ ss.head.Push(0)
+ ss.Begin(p)
+ ss.head.Push(3)
+ reflect := ss.Under()
+ require.Equal(t, false, reflect)
+ require.Equal(t, expected, ss)
+ })
+ t.Run("negative", func(t *testing.T) {
+ pe := NewPointer()
+ expected := NewStackStack()
+ expected.Begin(pe)
+ expected.head.next.Push(12)
+ expected.head.next.Push(5)
+ expected.head.next.Push(8)
+ expected.head.Push(8)
+ expected.head.Push(5)
+ expected.head.Push(12)
+ expected.head.Push(-3)
+ expected.head.Pop()
+ expected.head.Pop()
+ expected.head.Pop()
+ expected.head.Pop()
+ p := NewPointer()
+ ss := NewStackStack()
+ ss.Begin(p)
+ ss.head.Push(8)
+ ss.head.Push(5)
+ ss.head.Push(12)
+ ss.head.Push(-3)
+ reflect := ss.Under()
+ require.Equal(t, false, reflect)
+ require.Equal(t, expected, ss)
+ })
+}
+
+func TestPushPop(t *testing.T) {
+ ss := NewStackStack()
+ ss.Push(12)
+ ss.Push(5)
+ v := ss.Pop()
+ require.Equal(t, 5, v)
+ v = ss.Pop()
+ require.Equal(t, 12, v)
+}
diff --git a/pkg/pointer/stack.go b/pkg/pointer/stack.go
new file mode 100644
index 0000000..1832dbb
--- /dev/null
+++ b/pkg/pointer/stack.go
@@ -0,0 +1,51 @@
+package pointer
+
+type Stack struct {
+ size int
+ height int
+ data []int
+ next *Stack // Pointer to the next element in the stack stack
+}
+
+func NewStack() *Stack {
+ return &Stack{
+ size: 32,
+ height: 0,
+ data: make([]int, 32),
+ next: nil,
+ }
+}
+
+func (s *Stack) Clear() {
+ s.height = 0
+}
+
+func (s *Stack) Duplicate() {
+ if s.height > 0 {
+ s.Push(s.data[s.height-1])
+ }
+}
+
+func (s *Stack) Pop() int {
+ if s.height > 0 {
+ s.height--
+ return s.data[s.height]
+ }
+ return 0
+}
+
+func (s *Stack) Push(value int) {
+ if s.height >= s.size {
+ s.size += 32
+ s.data = append(s.data, make([]int, 32)...)
+ }
+ s.data[s.height] = value
+ s.height++
+}
+
+func (s *Stack) Swap() {
+ a := s.Pop()
+ b := s.Pop()
+ s.Push(a)
+ s.Push(b)
+}
diff --git a/pkg/pointer/stack_test.go b/pkg/pointer/stack_test.go
new file mode 100644
index 0000000..1b16085
--- /dev/null
+++ b/pkg/pointer/stack_test.go
@@ -0,0 +1,74 @@
+package pointer
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestClear(t *testing.T) {
+ s := NewStack()
+ s.Clear()
+ require.Equal(t, 0, s.height)
+}
+
+func TestDupicate(t *testing.T) {
+ s := NewStack()
+ s2 := NewStack()
+ s.Duplicate()
+ require.Equal(t, s2.height, s.height)
+ s.Push(12)
+ s.Duplicate()
+ s2.Push(12)
+ s2.Push(12)
+ require.Equal(t, s2.height, s.height)
+ require.Equal(t, s2.data, s.data)
+}
+
+func TestPop(t *testing.T) {
+ s := NewStack()
+ v := s.Pop()
+ require.Equal(t, 0, v)
+ s.Push(12)
+ s.Push(14)
+ v = s.Pop()
+ require.Equal(t, 14, v)
+ v = s.Pop()
+ require.Equal(t, 12, v)
+ v = s.Pop()
+ require.Equal(t, 0, v)
+}
+
+func TestPush(t *testing.T) {
+ s := NewStack()
+ for i := 0; i < 32; i++ {
+ s.Push(i)
+ }
+ require.Equal(t, 32, s.size)
+ s.Push(-1)
+ require.Equal(t, 64, s.size)
+}
+
+func TestSwap(t *testing.T) {
+ s := NewStack()
+ s2 := NewStack()
+ s.Swap()
+ s2.Push(0)
+ s2.Push(0)
+ require.Equal(t, s2, s)
+ s.Clear()
+ s.Push(1)
+ s.Swap()
+ s2.Clear()
+ s2.Push(1)
+ s2.Push(0)
+ require.Equal(t, s2, s)
+ s.Clear()
+ s.Push(1)
+ s.Push(2)
+ s2.Clear()
+ s2.Push(2)
+ s2.Push(1)
+ s.Swap()
+ require.Equal(t, s2, s)
+}