diff --git a/chotki_test.go b/chotki_test.go index 2e3f345..49bcb14 100644 --- a/chotki_test.go +++ b/chotki_test.go @@ -55,7 +55,7 @@ func TestChotki_Sync(t *testing.T) { bvv, err := b.VersionVector() assert.Nil(t, err) - assert.Equal(t, "1,a-0-1,b-0-1", bvv.String()) + assert.Equal(t, "0-2-3,a-0-1,b-0-1", bvv.String()) b.DumpAll() diff --git a/log0.go b/log0.go index abfeac6..39e5a4b 100644 --- a/log0.go +++ b/log0.go @@ -6,11 +6,44 @@ import ( "github.com/learn-decentralized-systems/toytlv" ) +const id1 = rdx.ID0 + rdx.ProInc +const ID2 = id1 + rdx.ProInc +const NamesID = ID2 + 1 +const NodesID = ID2 + 2 +const NodeInfoID = ID2 + 3 + // FORMAT: replica creation packet var Log0 = toyqueue.Records{ toytlv.Record('Y', toytlv.Record('I', rdx.ID0.ZipBytes()), // identifier, `src-0` toytlv.Record('R', rdx.ID0.ZipBytes()), // reference, `0-0` - toytlv.Record('S', rdx.Stlv("Sname")), // replica Name (string) + toytlv.Record('T', rdx.Ttlv("Name")), // replica Name (string) + toytlv.Record('T', rdx.Ttlv("S")), + ), + toytlv.Record('C', + toytlv.Record('I', id1.ZipBytes()), // identifier, `src-0` + toytlv.Record('R', rdx.ID0.ZipBytes()), // reference, `0-0` + toytlv.Record('T', rdx.Ttlv("Names")), // global-scope names + toytlv.Record('T', rdx.Ttlv("M")), + toytlv.Record('T', rdx.Ttlv("Nodes")), // replica addresses + toytlv.Record('T', rdx.Ttlv("M")), + toytlv.Record('T', rdx.Ttlv("NodeInfo")), // replica addresses + toytlv.Record('T', rdx.Ttlv("M")), + ), + toytlv.Record('O', + toytlv.Record('I', ID2.ZipBytes()), + toytlv.Record('R', id1.ZipBytes()), + toytlv.Record('M', + toytlv.Record('T', rdx.Ttlv("0")), + toytlv.Record('R', rdx.Rtlv(rdx.ID0)), + toytlv.Record('T', rdx.Ttlv("Global")), + toytlv.Record('R', rdx.Rtlv(ID2)), + toytlv.Record('T', rdx.Ttlv("Names")), + toytlv.Record('R', rdx.Rtlv(ID2+1)), + toytlv.Record('T', rdx.Ttlv("Nodes")), + toytlv.Record('R', rdx.Rtlv(ID2+2)), + ), + toytlv.Record('M'), + toytlv.Record('M'), ), } diff --git a/objects.go b/objects.go index 16f5e80..1809289 100644 --- a/objects.go +++ b/objects.go @@ -217,7 +217,7 @@ func (cho *Chotki) NewObject(tid rdx.ID, fields ...string) (id rdx.ID, err error } var packet toyqueue.Records for i := 0; i < len(fields); i++ { - rdt := byte(form[i+1].RdxType) + rdt := form[i+1].RdxType tlv := rdx.Xparse(rdt, fields[i]) if tlv == nil { return rdx.BadId, ErrBadValueForAType @@ -337,3 +337,23 @@ func (cho *Chotki) IncNField(fid rdx.ID) (id rdx.ID, err error) { id, err = cho.CommitPacket('E', fid.ZeroOff(), tlvs) return } + +func (cho *Chotki) ObjectFieldMapTermId(fid rdx.ID) (themap map[string]rdx.ID, err error) { + rdt, tlv, e := cho.ObjectFieldTLV(fid) + if e != nil { + return nil, e + } + if rdt != rdx.Mapping { + return nil, ErrWrongFieldType + } + themap = rdx.MnativeTR(tlv) + return +} + +func (cho *Chotki) EditFieldTLV(fid rdx.ID, delta []byte) (id rdx.ID, err error) { + tlvs := toyqueue.Records{} + tlvs = append(tlvs, toytlv.TinyRecord('F', rdx.ZipUint64(fid.Off()))) + tlvs = append(tlvs, delta) + id, err = cho.CommitPacket('E', fid.ZeroOff(), tlvs) + return +} diff --git a/rdx/ELM.go b/rdx/ELM.go index ea82e34..c009807 100644 --- a/rdx/ELM.go +++ b/rdx/ELM.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "github.com/learn-decentralized-systems/toytlv" + "slices" "sort" ) @@ -36,32 +37,32 @@ func TimeFromZipBytes(zip []byte) (t Time) { return } -var ErrBadISFR = errors.New("bad ISFR record") +var ErrBadFIRST = errors.New("bad FIRST record") func MelAppend(to []byte, lit byte, t Time, body []byte) []byte { tb := toytlv.TinyRecord('T', t.ZipBytes()) return toytlv.Append(to, lit, tb, body) } -func MelReSource(isfr []byte, src uint64) (ret []byte, err error) { +func MelReSource(first []byte, src uint64) (ret []byte, err error) { var lit byte var time Time var body []byte - rest := isfr + rest := first for len(rest) > 0 { - at := len(isfr) - len(rest) + at := len(first) - len(rest) lit, time, body, rest, err = ParseEnvelopedFIRST(rest) if err != nil { return } if time.src != src { - ret = make([]byte, at, len(isfr)*2) - copy(ret, isfr[:at]) + ret = make([]byte, at, len(first)*2) + copy(ret, first[:at]) break } } if ret == nil && err == nil { - return isfr, nil + return first, nil } for err == nil { time.src = src @@ -219,15 +220,33 @@ func Mstring(tlv []byte) (txt string) { return string(ret) } +type kv struct { + k, v []byte +} + // parse a text form into a TLV value func Mparse(txt string) (tlv []byte) { rdx, err := ParseRDX([]byte(txt)) if err != nil || rdx == nil || rdx.RdxType != Mapping { return nil } - for i := 0; i < len(rdx.Nested); i++ { - n := &rdx.Nested[i] - tlv = appendParsedFirstTlv(tlv, n) + kvs := []kv{} + pairs := rdx.Nested + for i := 0; i+1 < len(pairs); i += 2 { + next := kv{ + FIRSTparsee(pairs[i].RdxType, string(pairs[i].Text)), + FIRSTparsee(pairs[i+1].RdxType, string(pairs[i+1].Text)), + } + if next.k != nil && next.v != nil { + kvs = append(kvs, next) + } + } + slices.SortFunc(kvs, func(i, j kv) int { + return FIRSTcompare(i.k, j.k) + }) + for _, x := range kvs { + tlv = append(tlv, x.k...) + tlv = append(tlv, x.v...) } return } @@ -244,9 +263,84 @@ func Mmerge(tlvs [][]byte) (merged []byte) { return } +func MnativeTR(tlv []byte) MapTR { + ret := make(map[string]ID) + it := FIRSTIterator{TLV: tlv} + for it.Next() { + keyrdt, _, key := it.ParsedValue() + if !it.Next() { + break + } + valrdt, _, val := it.ParsedValue() + if keyrdt != Term || valrdt != Reference { + continue + } + id := IDFromZipBytes(val) + ret[string(key)] = id + } + return ret +} + +func MparseTR(arg *RDX) MapTR { + if arg == nil || arg.RdxType != Mapping { + return nil + } + ret := make(MapTR) + kvs := arg.Nested + for i := 0; i+1 < len(kvs); i += 2 { + if kvs[i].RdxType != Term || kvs[i+1].RdxType != Reference { + continue + } + ret[string(kvs[i].Text)] = IDFromText(kvs[i+1].Text) + } + return ret +} + +type MapTR map[string]ID + +func (m MapTR) String() string { + var keys []string + for key := range m { + keys = append(keys, key) + } + var ret []byte + ret = append(ret, '{', '\n') + for _, key := range keys { + ret = append(ret, '\t') + ret = append(ret, key...) + ret = append(ret, ':', '\t') + ret = append(ret, m[key].String()...) + ret = append(ret, ',', '\n') + } + ret = append(ret, '}') + return string(ret) +} + // produce an op that turns the old value into the new one -func Mdelta(tlv []byte, new_val int64) (tlv_delta []byte) { - return nil // todo +func MdeltaTR(tlv []byte, changes MapTR) (tlv_delta []byte) { + it := MIterator{it: FIRSTIterator{TLV: tlv}} + for it.Next() { + if it.lit != Term { + continue + } + change, ok := changes[string(it.val)] + if !ok { + continue + } + new_rev := ZagZigUint64(it.revz) + if new_rev < 0 { + new_rev = -new_rev + } + new_rev++ + tlv_delta = append(tlv_delta, toytlv.Record(Term, FIRSTtlv(new_rev, 0, it.val))...) + tlv_delta = append(tlv_delta, toytlv.Record(Reference, FIRSTtlv(new_rev, 0, change.ZipBytes()))...) + delete(changes, string(it.val)) + } + for key, val := range changes { + tlv_delta = append(tlv_delta, toytlv.Record(Term, Ttlv(key))...) + tlv_delta = append(tlv_delta, toytlv.Record(Reference, Rtlv(val))...) + } + return } // checks a TLV value for validity (format violations) @@ -260,6 +354,7 @@ func Mdiff(tlv []byte, vvdiff VV) []byte { type MIterator struct { it FIRSTIterator + keye []byte val []byte src uint64 revz uint64 @@ -270,6 +365,7 @@ type MIterator struct { func (a *MIterator) Next() (got bool) { tlv := a.it.TLV got = a.it.Next() // skip value + a.keye = a.it.one a.val = a.it.val a.src = a.it.src a.revz = a.it.revz @@ -283,7 +379,7 @@ func (a *MIterator) Next() (got bool) { func (a *MIterator) Merge(b SortedIterator) int { bb := b.(*MIterator) - cmp := bytes.Compare(a.val, bb.val) + cmp := FIRSTcompare(a.keye, bb.keye) if cmp < 0 { return MergeAB } else if cmp > 0 { @@ -486,3 +582,26 @@ func Mrdx2tlv(a *RDX) (tlv []byte) { func ELMdefault() (tlv []byte) { return []byte{} } + +func ELMstring(tlv []byte) string { + ret := []byte{} + it := FIRSTIterator{TLV: tlv} + for it.Next() { + switch it.lit { + case 'F': + ret = append(ret, Fstring(it.bare)...) + case 'I': + ret = append(ret, Istring(it.bare)...) + case 'R': + ret = append(ret, Rstring(it.bare)...) + case 'S': + ret = append(ret, Sstring(it.bare)...) + case 'T': + ret = append(ret, Tstring(it.bare)...) + default: + ret = append(ret, '?') + } + ret = append(ret, ',') + } + return string(ret) +} diff --git a/rdx/ELM_test.go b/rdx/ELM_test.go index 09500df..c1e74ad 100644 --- a/rdx/ELM_test.go +++ b/rdx/ELM_test.go @@ -22,9 +22,9 @@ func TestEmerge(t *testing.T) { } func TestMmerge(t *testing.T) { - tlv1 := Mparse("{1: 2, 3: 4, 5:6}") + tlv1 := Mparse("{1: 2, 5:6, 3: 4}") assert.Equal(t, "{1:2,3:4,5:6}", Mstring(tlv1)) - tlv2 := Mparse("{3:4, 5:6, 7:8}") + tlv2 := Mparse("{ 7:8, 3:4, 5:6}") tlv12 := Mmerge(toyqueue.Records{tlv1, tlv2}) str12 := Mstring(tlv12) assert.Equal(t, "{1:2,3:4,5:6,7:8}", str12) diff --git a/rdx/FIRST.go b/rdx/FIRST.go index 6bf4b30..a1d80cf 100644 --- a/rdx/FIRST.go +++ b/rdx/FIRST.go @@ -35,7 +35,7 @@ func FIRSTparsez(bulk []byte) (zrev uint64, src uint64, value []byte) { return } -// Parses an enveloped ISFR record +// Parses an enveloped FIRST record func ParseEnvelopedFIRST(data []byte) (lit byte, t Time, value, rest []byte, err error) { var hlen, blen int lit, hlen, blen = toytlv.ProbeHeader(data) @@ -43,12 +43,12 @@ func ParseEnvelopedFIRST(data []byte) (lit byte, t Time, value, rest []byte, err err = toytlv.ErrIncomplete return } - rec := data[:hlen+blen] + rec := data[hlen : hlen+blen] rest = data[hlen+blen:] - tlit, thlen, tblen := toytlv.ProbeHeader(data) + tlit, thlen, tblen := toytlv.ProbeHeader(rec) tlen := thlen + tblen if (tlit != 'T' && tlit != '0') || (tlen > len(rec)) { - err = ErrBadISFR + err = ErrBadFIRST return } tsb := rec[thlen:tlen] @@ -210,6 +210,17 @@ func FIRSTrdxs2tlvs(a []RDX) (tlv toyqueue.Records) { return } +func FIRSTcompare(a, b []byte) int { + alit, ahlen, ablen := toytlv.ProbeHeader(a) + blit, bhlen, bblen := toytlv.ProbeHeader(b) + if alit != blit { + return int(alit) - int(blit) + } + _, _, av := FIRSTparsez(a[ahlen : ahlen+ablen]) + _, _, bv := FIRSTparsez(b[bhlen : bhlen+bblen]) + return bytes.Compare(av, bv) +} + // I is a last-write-wins int64 func Idefault() []byte { return FIRSTdefault('I') } diff --git a/rdx/X.go b/rdx/X.go index 62bcbe8..acc3683 100644 --- a/rdx/X.go +++ b/rdx/X.go @@ -25,6 +25,22 @@ func Xparse(rdt byte, val string) (tlv []byte) { return } +func FIRSTparsee(rdt byte, val string) (tlv []byte) { + switch rdt { + case 'F': + tlv = toytlv.Record(rdt, Fparse(val)) + case 'I': + tlv = toytlv.Record(rdt, Iparse(val)) + case 'R': + tlv = toytlv.Record(rdt, Rparse(val)) + case 'S': + tlv = toytlv.Record(rdt, Sparse(val)) + case 'T': + tlv = toytlv.Record(rdt, Tparse(val)) + } + return +} + func Xmerge(rdt byte, tlvs [][]byte) (tlv []byte) { switch rdt { case 'C', 'O', 'Y': // object's ref is immutable @@ -43,6 +59,12 @@ func Xmerge(rdt byte, tlvs [][]byte) (tlv []byte) { tlv = Nmerge(tlvs) case 'Z': tlv = Zmerge(tlvs) + case 'E': + tlv = Emerge(tlvs) + case 'L': + tlv = Lmerge(tlvs) + case 'M': + tlv = Mmerge(tlvs) case 'V': tlv = Vmerge(tlvs) default: @@ -69,6 +91,8 @@ func Xstring(rdt byte, tlv []byte) string { return Tstring(tlv) case 'N': return Nstring(tlv) + case 'M': + return ELMstring(tlv) default: hex := make([]byte, len(tlv)*2) hex2.Encode(hex, tlv) diff --git a/rdx/rdx.go b/rdx/rdx.go index 08c9538..9bc9d4c 100644 --- a/rdx/rdx.go +++ b/rdx/rdx.go @@ -6,7 +6,7 @@ import ( ) const ( - None = byte('0') + None = byte(0) Float = byte('F') Integer = byte('I') Reference = byte('R') diff --git a/rdx/rdx.rl b/rdx/rdx.rl index 509c8c2..032346b 100644 --- a/rdx/rdx.rl +++ b/rdx/rdx.rl @@ -54,7 +54,7 @@ action opush { n := rdx.Nested n = append(n, RDX{Parent: rdx}) rdx.Nested = n - rdx.RdxType = Map; + rdx.RdxType = Mapping; rdx = &n[len(n)-1] nest++; } @@ -66,14 +66,14 @@ action opop { } nest--; rdx = rdx.Parent; - if rdx.RdxType != ESet && rdx.RdxType!=Map { + if rdx.RdxType != Eulerian && rdx.RdxType!=Mapping { cs = _RDX_error; fbreak; } if len(rdx.Nested)==1 { - rdx.RdxType = ESet + rdx.RdxType = Eulerian } - if rdx.RdxType == Map { + if rdx.RdxType == Mapping { if (len(rdx.Nested)&1)==1 { cs = _RDX_error; fbreak; @@ -86,7 +86,7 @@ action apush { n := rdx.Nested n = append(n, RDX{Parent: rdx}) rdx.Nested = n - rdx.RdxType = LArray; + rdx.RdxType = Linear; rdx = &n[len(n)-1] nest++; } @@ -98,7 +98,7 @@ action apop { } nest--; rdx = rdx.Parent; - if rdx.RdxType != LArray { + if rdx.RdxType != Linear { cs = _RDX_error; fbreak; } @@ -111,9 +111,9 @@ action comma { fbreak; } n := rdx.Parent.Nested - if rdx.Parent.RdxType==Map { + if rdx.Parent.RdxType==Mapping { if len(n)==1 { - rdx.Parent.RdxType = ESet + rdx.Parent.RdxType = Eulerian } else if (len(n)&1)==1 { cs = _RDX_error; fbreak; @@ -131,7 +131,7 @@ action colon { fbreak; } n := rdx.Parent.Nested - if rdx.Parent.RdxType==Map { + if rdx.Parent.RdxType==Mapping { if (len(n)&1)==0 { cs = _RDX_error; fbreak; diff --git a/repl/commands.go b/repl/commands.go index 28a0cdf..c0426b7 100644 --- a/repl/commands.go +++ b/repl/commands.go @@ -570,3 +570,29 @@ func (repl *REPL) CommandTell(arg *rdx.RDX) (id rdx.ID, err error) { } return } + +var HelpName = errors.New("name, name Obj, name {Obj: b0b-12-1}") + +func (repl *REPL) CommandName(arg *rdx.RDX) (id rdx.ID, err error) { + id = rdx.BadId + var names rdx.MapTR + names, err = repl.Host.ObjectFieldMapTermId(chotki.NamesID) + if err != nil { + return + } + if arg == nil || arg.RdxType == rdx.None { + fmt.Println(names.String()) + id = chotki.ID2 + } else if arg.RdxType == rdx.Term { + key := string(arg.Text) + fmt.Printf("{%s:%s}\n", key, names[key]) + } else if arg.RdxType == rdx.Mapping { + _, tlv, _ := repl.Host.ObjectFieldTLV(chotki.NamesID) + parsed := rdx.MparseTR(arg) + delta := toytlv.Record('M', rdx.MdeltaTR(tlv, parsed)) + id, err = repl.Host.EditFieldTLV(chotki.NamesID, delta) + } else { + err = HelpName + } + return +} diff --git a/repl/repl.go b/repl/repl.go index 50df10a..b3c46c9 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -26,20 +26,38 @@ var ErrBadPath = errors.New("bad path") var completer = readline.NewPrefixCompleter( readline.PcItem("help"), + readline.PcItem("create"), + readline.PcItem("open"), + readline.PcItem("close"), + readline.PcItem("exit"), + readline.PcItem("quit"), + readline.PcItem("listen"), - readline.PcItem("talk"), - readline.PcItem("mute"), - readline.PcItem("bye"), + readline.PcItem("connect"), + + readline.PcItem("class"), + readline.PcItem("new"), + readline.PcItem("edit"), + readline.PcItem("cat"), + readline.PcItem("list"), readline.PcItem("name"), - readline.PcItem("get"), - readline.PcItem("list"), - readline.PcItem("put"), - readline.PcItem("set"), + readline.PcItem("dump", + readline.PcItem("objects"), + readline.PcItem("vv"), + readline.PcItem("all"), + ), - readline.PcItem("exit"), - readline.PcItem("quit"), + readline.PcItem("tell"), + readline.PcItem("mute"), + + readline.PcItem("ping"), + readline.PcItem("pong"), + readline.PcItem("pinc"), + readline.PcItem("ponc"), + readline.PcItem("tinc"), + readline.PcItem("sinc"), ) func filterInput(r rune) (rune, bool) { @@ -138,6 +156,8 @@ func (repl *REPL) REPL() (id rdx.ID, err error) { id, err = repl.CommandList(arg) case "cat": id, err = repl.CommandCat(arg) + case "name": + id, err = repl.CommandName(arg) // ----- networking ----- case "listen": id, err = repl.CommandListen(arg) @@ -171,21 +191,6 @@ func (repl *REPL) REPL() (id rdx.ID, err error) { func main() { - /* - if len(os.Args) > 1 { - rno := uint64(1) - _, err := fmt.Sscanf(os.Args[1], "%d", &rno) - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, "Usage: Chotki 123") - os.Exit(-2) - } - err = re.Open(rno) - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(-1) - } - }*/ - repl := REPL{} err := repl.Open()