aboutsummaryrefslogtreecommitdiff
path: root/pkg/field
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--pkg/field/error.go34
-rw-r--r--pkg/field/error_test.go20
-rw-r--r--pkg/field/field.go81
-rw-r--r--pkg/field/field_test.go154
-rw-r--r--pkg/field/test_data/dna.b988
-rw-r--r--pkg/field/test_data/empty.b980
-rw-r--r--pkg/field/test_data/factorial.b982
-rw-r--r--pkg/field/test_data/hello.b981
-rw-r--r--pkg/field/test_data/invalid.b982
-rw-r--r--pkg/field/test_data/minimal.b981
-rw-r--r--pkg/field/test_data/rn.b981
11 files changed, 304 insertions, 0 deletions
diff --git a/pkg/field/error.go b/pkg/field/error.go
new file mode 100644
index 0000000..82f2fec
--- /dev/null
+++ b/pkg/field/error.go
@@ -0,0 +1,34 @@
+package field
+
+import "fmt"
+
+// Read error
+type ReadError struct {
+ err error
+}
+
+func (e ReadError) Error() string {
+ return fmt.Sprintf("Failed to decode file")
+}
+func (e ReadError) Unwrap() error { return e.err }
+
+func newReadError(err error) error {
+ return &ReadError{
+ err: err,
+ }
+}
+
+// Funge decoding error
+type DecodeError struct {
+ msg string
+}
+
+func (e DecodeError) Error() string {
+ return fmt.Sprintf("Failed to decode file with message : %s", e.msg)
+}
+
+func newDecodeError(msg string) error {
+ return &DecodeError{
+ msg: msg,
+ }
+}
diff --git a/pkg/field/error_test.go b/pkg/field/error_test.go
new file mode 100644
index 0000000..45e8f83
--- /dev/null
+++ b/pkg/field/error_test.go
@@ -0,0 +1,20 @@
+package field
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func requireErrorTypeMatch(t *testing.T, err error, expected error) {
+ require.Equalf(t, reflect.TypeOf(err), reflect.TypeOf(expected), "Invalid error type. Got %s but expected %s", reflect.TypeOf(err), reflect.TypeOf(expected))
+}
+
+func TestErrorsCoverage(t *testing.T) {
+ readErr := ReadError{}
+ _ = readErr.Error()
+ _ = readErr.Unwrap()
+ decodeError := DecodeError{}
+ _ = decodeError.Error()
+}
diff --git a/pkg/field/field.go b/pkg/field/field.go
new file mode 100644
index 0000000..fde478a
--- /dev/null
+++ b/pkg/field/field.go
@@ -0,0 +1,81 @@
+package field
+
+import (
+ "bytes"
+ "io"
+)
+
+// 32-bit Befunge-98
+// =================
+// |-2,147,483,648
+// |
+// | x
+// -----+-----
+// -2,147,483,648 | 2,147,483,647
+// |
+// y|2,147,483,647
+
+type Field struct {
+ firstLineIndex int
+ length int
+ lines []Line
+}
+
+type Line struct {
+ firstColumnIndex int
+ length int
+ columns []byte
+}
+
+func LoadFile(fd io.Reader) (*Field, error) {
+ f := new(Field)
+ l := new(Line)
+ trailingSpaces := 0
+ for {
+ data := make([]byte, 4096)
+ if n, errRead := fd.Read(data); errRead != nil {
+ if errRead == io.EOF {
+ if f.length == 0 && l.length == 0 {
+ return nil, newDecodeError("No instruction on the first line of the file produces an unusable program in Befunge98")
+ }
+ if l.length > 0 {
+ f.length++
+ f.lines = append(f.lines, *l)
+ }
+ break
+ } else {
+ return nil, newReadError(errRead)
+ }
+ } else {
+ for i := 0; i < n; i++ {
+ if data[i] == '\n' || data[i] == '\r' {
+ if f.length == 0 && l.length == 0 {
+ return nil, newDecodeError("No instruction on the first line of the file produces an unusable program in Befunge98")
+ }
+ f.length++
+ f.lines = append(f.lines, *l)
+ l = new(Line)
+ trailingSpaces = 0
+ if i+1 < n && data[i] == '\r' && data[i+1] == '\n' {
+ i++
+ }
+ } else {
+ if l.length == 0 && data[i] == ' ' {
+ l.firstColumnIndex++ // trim leading spaces
+ } else {
+ if data[i] == ' ' {
+ trailingSpaces++
+ } else {
+ l.columns = append(l.columns, bytes.Repeat([]byte{' '}, trailingSpaces)...)
+ l.length += trailingSpaces
+ trailingSpaces = 0
+ l.length++
+ l.columns = append(l.columns, data[i])
+ }
+ }
+ }
+ }
+ }
+ }
+ return f, nil
+}
diff --git a/pkg/field/field_test.go b/pkg/field/field_test.go
new file mode 100644
index 0000000..52f7d80
--- /dev/null
+++ b/pkg/field/field_test.go
@@ -0,0 +1,154 @@
+package field
+
+import (
+ "io"
+ "os"
+ "testing"
+ "testing/iotest"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestLoadFile(t *testing.T) {
+ // minimal b98 file
+ minimalField := Field{
+ firstLineIndex: 0,
+ length: 1,
+ lines: []Line{
+ Line{
+ firstColumnIndex: 0,
+ length: 1,
+ columns: []byte{'@'},
+ },
+ },
+ }
+ // hello b98 file
+ helloField := Field{
+ firstLineIndex: 0,
+ length: 1,
+ lines: []Line{
+ Line{
+ firstColumnIndex: 0,
+ length: 24,
+ columns: []byte{'6', '4', '+', '"', '!', 'd', 'l', 'r', 'o', 'W', ' ', ',', 'o', 'l', 'l', 'e', 'H', '"', '>', ':', '#', ',', '_', '@'},
+ },
+ },
+ }
+ // factorial b98 file
+ factorialField := Field{
+ firstLineIndex: 0,
+ length: 2,
+ lines: []Line{
+ Line{
+ firstColumnIndex: 0,
+ length: 15,
+ columns: []byte{'&', '>', ':', '1', '-', ':', 'v', ' ', 'v', ' ', '*', '_', '$', '.', '@'},
+ },
+ Line{
+ firstColumnIndex: 1,
+ length: 11,
+ columns: []byte{'^', ' ', ' ', ' ', ' ', '_', '$', '>', '\\', ':', '^'},
+ },
+ },
+ }
+ // dna b98 file
+ dnaField := Field{
+ firstLineIndex: 0,
+ length: 8,
+ lines: []Line{
+ Line{
+ firstColumnIndex: 0,
+ length: 7,
+ columns: []byte{'7', '^', 'D', 'N', '>', 'v', 'A'},
+ },
+ Line{
+ firstColumnIndex: 0,
+ length: 7,
+ columns: []byte{'v', '_', '#', 'v', '?', ' ', 'v'},
+ },
+ Line{
+ firstColumnIndex: 0,
+ length: 7,
+ columns: []byte{'7', '^', '<', '"', '"', '"', '"'},
+ },
+ Line{
+ firstColumnIndex: 0,
+ length: 7,
+ columns: []byte{'3', ' ', ' ', 'A', 'C', 'G', 'T'},
+ },
+ Line{
+ firstColumnIndex: 0,
+ length: 7,
+ columns: []byte{'9', '0', '!', '"', '"', '"', '"'},
+ },
+ Line{
+ firstColumnIndex: 0,
+ length: 7,
+ columns: []byte{'4', '*', ':', '>', '>', '>', 'v'},
+ },
+ Line{
+ firstColumnIndex: 0,
+ length: 7,
+ columns: []byte{'+', '8', '^', '-', '1', ',', '<'},
+ },
+ Line{
+ firstColumnIndex: 0,
+ length: 7,
+ columns: []byte{'>', ' ', ',', '+', ',', '@', ')'},
+ },
+ },
+ }
+ // \r\n file b98 file
+ rnField := Field{
+ firstLineIndex: 0,
+ length: 1,
+ lines: []Line{
+ Line{
+ firstColumnIndex: 0,
+ length: 24,
+ columns: []byte{'6', '4', '+', '"', '!', 'd', 'l', 'r', 'o', 'W', ' ', ',', 'o', 'l', 'l', 'e', 'H', '"', '>', ':', '#', ',', '_', '@'},
+ },
+ },
+ }
+ // Test cases
+ type addError func(r io.Reader) io.Reader
+ testCases := []struct {
+ name string
+ input string
+ inputAddError addError
+ expected *Field
+ expectedError error
+ }{
+ {"Empty file", "test_data/empty.b98", nil, nil, &DecodeError{}},
+ {"Invalid file content", "test_data/invalid.b98", nil, nil, &DecodeError{}},
+ {"io error", "test_data/minimal.b98", iotest.TimeoutReader, nil, &ReadError{}},
+ {"minimal", "test_data/minimal.b98", nil, &minimalField, nil},
+ {"hello", "test_data/hello.b98", nil, &helloField, nil},
+ {"factorial", "test_data/factorial.b98", nil, &factorialField, nil},
+ {"dna", "test_data/dna.b98", nil, &dnaField, nil},
+ {"\\r\\n file", "test_data/rn.b98", nil, &rnField, nil},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ var fd io.Reader
+ file, err := os.Open(tc.input)
+ require.NoError(t, err, "Failed to open file")
+ defer file.Close()
+ if tc.inputAddError != nil {
+ fd = tc.inputAddError(file)
+ } else {
+ fd = file
+ }
+ valid, err := LoadFile(fd)
+ if tc.expectedError != nil {
+ require.Error(t, err)
+ requireErrorTypeMatch(t, err, tc.expectedError)
+ require.Nil(t, valid)
+ } else {
+ require.NoError(t, err)
+ }
+ assert.Equal(t, tc.expected, valid, "Invalid value")
+ })
+ }
+}
diff --git a/pkg/field/test_data/dna.b98 b/pkg/field/test_data/dna.b98
new file mode 100644
index 0000000..1bf5f80
--- /dev/null
+++ b/pkg/field/test_data/dna.b98
@@ -0,0 +1,8 @@
+7^DN>vA
+v_#v? v
+7^<""""
+3 ACGT
+90!""""
+4*:>>>v
++8^-1,<
+> ,+,@)
diff --git a/pkg/field/test_data/empty.b98 b/pkg/field/test_data/empty.b98
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pkg/field/test_data/empty.b98
diff --git a/pkg/field/test_data/factorial.b98 b/pkg/field/test_data/factorial.b98
new file mode 100644
index 0000000..4e4946c
--- /dev/null
+++ b/pkg/field/test_data/factorial.b98
@@ -0,0 +1,2 @@
+&>:1-:v v *_$.@
+ ^ _$>\:^
diff --git a/pkg/field/test_data/hello.b98 b/pkg/field/test_data/hello.b98
new file mode 100644
index 0000000..83822c0
--- /dev/null
+++ b/pkg/field/test_data/hello.b98
@@ -0,0 +1 @@
+64+"!dlroW ,olleH">:#,_@
diff --git a/pkg/field/test_data/invalid.b98 b/pkg/field/test_data/invalid.b98
new file mode 100644
index 0000000..0a9f039
--- /dev/null
+++ b/pkg/field/test_data/invalid.b98
@@ -0,0 +1,2 @@
+
+@
diff --git a/pkg/field/test_data/minimal.b98 b/pkg/field/test_data/minimal.b98
new file mode 100644
index 0000000..b516b2c
--- /dev/null
+++ b/pkg/field/test_data/minimal.b98
@@ -0,0 +1 @@
+@ \ No newline at end of file
diff --git a/pkg/field/test_data/rn.b98 b/pkg/field/test_data/rn.b98
new file mode 100644
index 0000000..8d35eb5
--- /dev/null
+++ b/pkg/field/test_data/rn.b98
@@ -0,0 +1 @@
+64+"!dlroW ,olleH">:#,_@