Skip to content

Commit

Permalink
jval
Browse files Browse the repository at this point in the history
  • Loading branch information
nikandfor committed Aug 19, 2024
1 parent 63a0ee9 commit 80a09fe
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 0 deletions.
185 changes: 185 additions & 0 deletions jval/decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package jval

import "nikand.dev/go/json"

type (
Decoder struct {
json.Decoder
}
)

const (
// Num
// Embedded
// Int: off
// Float: off
//
// Null, True, False
//
// String: off, len
//
// Array, Object: len, off -> []off

Check failure on line 21 in jval/decoder.go

View workflow job for this annotation

GitHub Actions / lint

Comment should end in a period (godot)

String = 0b1000_0000 // 0b1xxx_xxxx
Number = 0b0100_0000 // 0b01xx_xxxx

Array = 0b0010_0000 // 0b0010_xxxx
Object = 0b0011_0000 // 0b0011_xxxx

arrObj = 0b0011_0000

_ = 0b0001_0000 // 0b0001_xxxx

False = 0b0000_1100
True = 0b0000_1101
_ = 0b0000_1110
Null = 0b0000_1111

special = 0b1111_0000

strLen1 = String - 3
strLen2 = String - 2
strLen4 = String - 1

intLen1 = Number - 4
intLen2 = Number - 3
intLen4 = Number - 2
float = Number - 1
)

func (d Decoder) Decode(w, r []byte, st int) (_ []byte, off, i int, err error) {

Check failure on line 50 in jval/decoder.go

View workflow job for this annotation

GitHub Actions / lint

cognitive complexity 39 of func `(Decoder).Decode` is high (> 30) (gocognit)
var raw []byte

off = len(w)

tp, i, err := d.Type(r, st)
if err != nil {
return w, -1, i, err
}

switch tp {
case json.Null, json.Bool:
raw, i, err = d.Raw(r, i)
if err != nil {
return w, -1, i, err
}

var x byte

switch {
case tp == json.Null:
x = Null
case string(raw) == "true":
x = True
default:
x = False
}

w = append(w, x)

return w, off, i, nil
case json.Number:
raw, i, err = d.Raw(r, i)
if err != nil {
return
}

v := 0

for j := 0; j < len(raw); j++ {
if raw[j] >= '0' && raw[j] <= '9' {
v = v*10 + int(raw[j]-'0')
} else {
panic("number")
}
}

if v < intLen1 {
w = append(w, Number|byte(v))

return w, off, i, nil
}

panic("number")
case json.String:
w = append(w, String)

str := len(w)

w, i, err = d.DecodeString(r, i, w)
if err != nil {
return w, -1, i, err
}

l := len(w) - str

if l < strLen1 {
w[str-1] = String | byte(l)

return w, off, i, nil
}

w[str-1] = String | strLen1
// TODO

Check failure on line 123 in jval/decoder.go

View workflow job for this annotation

GitHub Actions / lint

todoCommentWithoutDetail: may want to add detail/assignee to this TODO/FIXME/BUG comment (gocritic)
panic("str")
}

sub := make([]int, 0, 8)

i, err = d.Enter(r, i, tp)
if err != nil {
return w, -1, i, err
}

for d.ForMore(r, &i, tp, &err) {
if tp == json.Object {
w, off, i, err = d.Decode(w, r, i)
if err != nil {
return w, -1, i, err
}

sub = append(sub, off)
}

w, off, i, err = d.Decode(w, r, i)
if err != nil {
return w, -1, i, err
}

sub = append(sub, off)
}
if err != nil {
return w, -1, i, err
}

if tp == json.Array {
tp = Array
} else {
tp = Object
}

off = len(w)

if len(sub) == 0 {
w = append(w, tp)

return w, off, i, nil
}

l := len(sub)
if tp == Object {
l /= 2
}

if l < 8 && off-sub[0] < 256 {
w = append(w, tp|byte(l))

for _, sub := range sub {
w = append(w, byte(off-sub))
}

return w, off, i, nil
}

panic("obj/arr")
}
73 changes: 73 additions & 0 deletions jval/encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package jval

import (
"fmt"

"nikand.dev/go/json"
)

type (
Encoder struct {
json.Encoder
}
)

func (e Encoder) Encode(w, r []byte, off int) []byte {
tp := r[off]

if tp&String == String {
l := int(r[off] &^ String)
str := off + 1

return e.AppendString(w, r[str:str+l])
}

if tp&Number == Number {
return fmt.Appendf(w, "%d", tp&0b0011_1111)
}

if tp&special == 0 {
return append(w, []string{
"false",
"true",
"",
"null",
}[tp&0x3]...)
}

l := int(tp &^ arrObj)
base := off + 1
tp &= arrObj

p := byte('[')
if tp == Object {
p = '{'
l *= 2
}

w = append(w, p)

for j := 0; j < l; {
if j != 0 {
w = append(w, ',')
}

if tp == Object {
sub := off - int(r[base+j])

w = e.Encode(w, r, sub)
w = append(w, ':')

j++
}

sub := off - int(r[base+j])

w = e.Encode(w, r, sub)
j++
}

w = append(w, p+2)

return w
}
25 changes: 25 additions & 0 deletions jval/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package jval

type (
Message struct {
b []byte
root int
}
)

func (m *Message) Decode(r []byte, st int) (i int, err error) {
var d Decoder

m.b, m.root, i, err = d.Decode(m.b[:0], r, st)

return
}

func (m *Message) Encode(w []byte) []byte {
var e Encoder

return e.Encode(w, m.b, m.root)
}

func (m *Message) BytesRoot() ([]byte, int) { return m.b, m.root }
func (m *Message) Reset(b []byte, root int) { m.b, m.root = b, root }
33 changes: 33 additions & 0 deletions jval/message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package jval

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestMessage(tb *testing.T) {
var b []byte
var m Message

for _, d := range []string{
`null`,
`true`,
`false`,
`0`, `1`, `10`, `55`,
`""`, `"abc"`, `"abcdefj01234567890"`,
`[]`, `[null,true,false,[0,5],"str"]`,
`{}`, `{"a":"b"}`, `{"a":"b","c":["d",null],"f":[1,2,[3,4]],"g":{"i":"j"}}`,
} {
i, err := m.Decode([]byte(d), 0)
assert.NoError(tb, err, `(%s)`, d)
assert.Equal(tb, len(d), i, `(%s)`, d)

raw, root := m.BytesRoot()
tb.Logf("(%s) -> @%x/%x % 2x", d, root, len(raw), raw)

b = m.Encode(b[:0])

assert.Equal(tb, d, string(b), "(%s)", b)
}
}

0 comments on commit 80a09fe

Please # to comment.