Skip to content

Commit

Permalink
WIP encoding values
Browse files Browse the repository at this point in the history
  • Loading branch information
wkhere committed Apr 30, 2024
1 parent 013c35e commit 6988d25
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 51 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
default: test

generated = tokentype_string.go opcode_string.go testapi_test.go
generated = tokentype_string.go opcode_string.go typecode_string.go testapi_test.go

bcl: stringer $(generated) go.mod go.sum *.go cmd/bcl/*.go
go build ./cmd/bcl
Expand All @@ -11,6 +11,9 @@ tokentype_string.go: token.go
opcode_string.go: opcode.go
go generate

typecode_string.go: typecode.go
go generate

testapi_test.go: test.py basic_test.go
go generate && go fmt testapi_test.go

Expand Down
121 changes: 120 additions & 1 deletion encoding.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package bcl

import "github.com/mohae/uvarint"
import (
stdbinary "encoding/binary"
"fmt"
"math"

"github.com/mohae/uvarint"
)

func u16ToBytes(p []byte, x uint16) {
p[0] = byte(x >> 8 & 0xff)
Expand All @@ -18,3 +24,116 @@ func uvarintToBytes(p []byte, x uint64) int {
func uvarintFromBytes(p []byte) (uint64, int) {
return uvarint.Decode(p)
}

func varintToBytes(p []byte, x int64) int {
return uvarintToBytes(p, i64ToU64(x))
}

func varintFromBytes(p []byte) (int64, int) {
u, n := uvarintFromBytes(p)
return u64ToI64(u), n
}

func i64ToU64(x int64) uint64 {
if x < 0 {
return uint64(-x) ^ 0xffffffffffffffff + 1
}
return uint64(x)
}

func u64ToI64(x uint64) int64 {
if x > math.MaxInt64 {
return -(int64(x^0xffffffffffffffff) + 1)
}
return int64(x)
}

func valueToBytes(p []byte, v value) (n int) {
p[0] = byte(typecodeOf(v))
p = p[1:] // local copy
n++

switch x := v.(type) {
case int:
n += varintToBytes(p, int64(x))

case float64:
stdbinary.BigEndian.PutUint64(p, math.Float64bits(x))
n += 8

case string:
i := uvarintToBytes(p, uint64(len(x)))
p = p[i:]
if len(p) < len(x) {
n += i
panic("no space ")
}
copy(p, []byte(x))
n += i + len(x)

case bool:
if x {
p[0] = 1
} else {
p[0] = 0
}
n++

default:
// whether it's nil or invalid type, don't emit the value
}
return n
}

func valueFromBytes(p []byte) (value, int) {
switch c, p := typecode(p[0]), p[1:]; c {
case typeINT:
x, n := varintFromBytes(p)
return int(x), 1 + n

case typeFLOAT:
return math.Float64frombits(stdbinary.BigEndian.Uint64(p)), 1 + 8

case typeSTR:
k, i := uvarintFromBytes(p)
return string(p[i : i+int(k)]), 1 + i + int(k)

case typeBOOL:
return p[0] != 0, 1 + 1

case typeNIL:
return nil, 1

default:
panic(errInvalidType{p[0]})
}
}

func typecodeOf(v value) typecode {
switch v.(type) {
case int:
return typeINT
case float64:
return typeFLOAT
case string:
return typeSTR
case bool:
return typeBOOL
default:
if v == nil {
return typeNIL
}
panic(errInvalidValue{v})
}
}

type errInvalidType struct{ byte }
type errInvalidValue struct{ value }

func (e errInvalidType) Error() string {
return fmt.Sprintf("invalid type: %q", e.byte)
}

func (e errInvalidValue) Error() string {
return fmt.Sprintf("invalid value: %v", e.value)
}
111 changes: 111 additions & 0 deletions encoding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package bcl

import (
"math"
"testing"
"testing/quick"
)

var qcConf = &quick.Config{MaxCount: 1000}

func TestEncodingU16(t *testing.T) {
f := func(x uint16) bool {
var b [2]byte
var p []byte = b[:]
u16ToBytes(p, x)
return x == u16FromBytes(p)
}
if err := quick.Check(f, qcConf); err != nil {
t.Error(err)
}
}

func TestEncodingUvarint(t *testing.T) {
f := func(x uint64) bool {
var b [9]byte
var p []byte = b[:]
n := uvarintToBytes(p, x)
y, m := uvarintFromBytes(p)
return n == m && x == y
}
if err := quick.Check(f, qcConf); err != nil {
t.Error(err)
}
}

func TestEncodingVarint(t *testing.T) {
f := func(x int64) bool {
var b [9]byte
var p []byte = b[:]
n := varintToBytes(p, x)
y, m := varintFromBytes(p)
return n == m && x == y
}
if err := quick.Check(f, qcConf); err != nil {
t.Error(err)
}
}

func testEncodingValue(t *testing.T, x value) {
t.Helper()

var b [12]byte // for the longest string below
var p []byte = b[:]

n := valueToBytes(p, x)
y, m := valueFromBytes(p)

if n != m {
t.Errorf("x=%v: size mismatch n=%d m=%d", x, n, m)
}
if x != y {
t.Errorf("mismatch x=%v y=%v", x, y)
}
}

func TestEncodingValuesTab(t *testing.T) {
tab := []value{
nil,
0, 1, 2, -1, -2, 10, 127, 128, -127, -128,
math.MaxInt64, math.MaxInt64 - 1, math.MinInt64, math.MinInt64 + 1,
0.0, 0.5, -0.5, 1234.5, -1234.5,
math.MaxFloat64, -math.MaxFloat64,
true, false,
"", "foo", "1234567890",
}
for _, v := range tab {
testEncodingValue(t, v)
}
}

func qcEncodingValue(x value) bool {
var b [11]byte
var p []byte = b[:]

n := valueToBytes(p, x)
y, m := valueFromBytes(p)

return n == m && x == y
}

func qcEncodingString(s string) bool {
p := make([]byte, 1+9+len(s))

n := valueToBytes(p, s)
y, m := valueFromBytes(p)

return n == m && s == y
}

func TestEncodingValuesQC(t *testing.T) {
var (
fInt = func(x int) bool { return qcEncodingValue(x) }
fFloat = func(x float64) bool { return qcEncodingValue(x) }
fString = func(x string) bool { return qcEncodingString(x) }
)
for _, f := range []any{fInt, fFloat, fString} {
if err := quick.Check(f, qcConf); err != nil {
t.Error(err)
}
}
}
99 changes: 50 additions & 49 deletions testapi_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions typecode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package bcl

type typecode byte

const (
typeNIL typecode = iota
typeINT
typeFLOAT
typeSTR
typeBOOL
)

//go:generate stringer -type typecode -trimprefix type
Loading

0 comments on commit 6988d25

Please sign in to comment.