From 80a09fef0043c8961f0ee92236a56cdb5c2d9d89 Mon Sep 17 00:00:00 2001 From: nikandfor Date: Mon, 19 Aug 2024 05:37:24 +0200 Subject: [PATCH] jval --- jval/decoder.go | 185 +++++++++++++++++++++++++++++++++++++++++++ jval/encoder.go | 73 +++++++++++++++++ jval/message.go | 25 ++++++ jval/message_test.go | 33 ++++++++ 4 files changed, 316 insertions(+) create mode 100644 jval/decoder.go create mode 100644 jval/encoder.go create mode 100644 jval/message.go create mode 100644 jval/message_test.go diff --git a/jval/decoder.go b/jval/decoder.go new file mode 100644 index 0000000..c24f67a --- /dev/null +++ b/jval/decoder.go @@ -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 + + 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) { + 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 + 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") +} diff --git a/jval/encoder.go b/jval/encoder.go new file mode 100644 index 0000000..3269a46 --- /dev/null +++ b/jval/encoder.go @@ -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 +} diff --git a/jval/message.go b/jval/message.go new file mode 100644 index 0000000..ed61f6a --- /dev/null +++ b/jval/message.go @@ -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 } diff --git a/jval/message_test.go b/jval/message_test.go new file mode 100644 index 0000000..752cf92 --- /dev/null +++ b/jval/message_test.go @@ -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) + } +}