diff options
author | Julien Dessaux | 2021-09-23 11:45:49 +0200 |
---|---|---|
committer | Julien Dessaux | 2021-09-23 11:45:49 +0200 |
commit | 759ee2aa10323d1960405c93f7cd4cf6d383ae7a (patch) | |
tree | 87648b4811b0264f1b54c454ed2569abe1abee94 /pkg/pointer | |
parent | Began coding the interpreter (only manages the minimal example for now!) (diff) | |
download | gofunge98-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.go | 52 | ||||
-rw-r--r-- | pkg/pointer/pointer_test.go | 64 | ||||
-rw-r--r-- | pkg/pointer/stack-stack.go | 95 | ||||
-rw-r--r-- | pkg/pointer/stack-stack_test.go | 260 | ||||
-rw-r--r-- | pkg/pointer/stack.go | 51 | ||||
-rw-r--r-- | pkg/pointer/stack_test.go | 74 |
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) +} |