diff options
-rw-r--r-- | go.mod | 8 | ||||
-rw-r--r-- | go.sum | 13 | ||||
-rw-r--r-- | pkg/field/error.go | 34 | ||||
-rw-r--r-- | pkg/field/error_test.go | 20 | ||||
-rw-r--r-- | pkg/field/field.go | 81 | ||||
-rw-r--r-- | pkg/field/field_test.go | 154 | ||||
-rw-r--r-- | pkg/field/test_data/dna.b98 | 8 | ||||
-rw-r--r-- | pkg/field/test_data/empty.b98 | 0 | ||||
-rw-r--r-- | pkg/field/test_data/factorial.b98 | 2 | ||||
-rw-r--r-- | pkg/field/test_data/hello.b98 | 1 | ||||
-rw-r--r-- | pkg/field/test_data/invalid.b98 | 2 | ||||
-rw-r--r-- | pkg/field/test_data/minimal.b98 | 1 | ||||
-rw-r--r-- | pkg/field/test_data/rn.b98 | 1 |
13 files changed, 325 insertions, 0 deletions
@@ -1,3 +1,11 @@ module git.adyxax.org/adyxax/gofunge go 1.17 + +require github.com/stretchr/testify v1.7.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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">:#,_@
|