Began implementing the befunge field data structure

This commit is contained in:
Julien Dessaux 2021-09-14 17:56:04 +02:00
parent 79a970515f
commit 4bacbc2375
13 changed files with 325 additions and 0 deletions

34
pkg/field/error.go Normal file
View file

@ -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,
}
}

20
pkg/field/error_test.go Normal file
View file

@ -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()
}

81
pkg/field/field.go Normal file
View file

@ -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
}

154
pkg/field/field_test.go Normal file
View file

@ -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")
})
}
}

View file

@ -0,0 +1,8 @@
7^DN>vA
v_#v? v
7^<""""
3 ACGT
90!""""
4*:>>>v
+8^-1,<
> ,+,@)

View file

View file

@ -0,0 +1,2 @@
&>:1-:v v *_$.@
^ _$>\:^

View file

@ -0,0 +1 @@
64+"!dlroW ,olleH">:#,_@

View file

@ -0,0 +1,2 @@
@

View file

@ -0,0 +1 @@
@

View file

@ -0,0 +1 @@
64+"!dlroW ,olleH">:#,_@