From 1c1d58d864cb769e19d1bcfb0ec1968e7bf2ee62 Mon Sep 17 00:00:00 2001 From: anon5 Date: Mon, 22 Feb 2021 21:09:59 +0000 Subject: [PATCH] Add inv (de)serialization --- inv.go | 192 +++++++++++++++++++++++++++++++++++++++++++ readrune.go | 116 ++++++++++++++++++++++++++ stack.go | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 540 insertions(+) create mode 100644 inv.go create mode 100644 readrune.go create mode 100644 stack.go diff --git a/inv.go b/inv.go new file mode 100644 index 0000000..5113ee3 --- /dev/null +++ b/inv.go @@ -0,0 +1,192 @@ +package mt + +import ( + "fmt" + "io" + "reflect" +) + +type sentinal struct { + err error +} + +func (s *sentinal) ret(err error) { + s.err = err + panic(s) +} + +type Inv []NamedInvList + +type NamedInvList struct { + Name string + InvList +} + +func (inv Inv) List(name string) *NamedInvList { + for i, l := range inv { + if l.Name == name { + return &inv[i] + } + } + return nil +} + +func (i Inv) Serialize(w io.Writer) error { + for _, l := range i { + if _, err := fmt.Fprintln(w, "List", l.Name, len(l.Stacks)); err != nil { + return err + } + if err := l.Serialize(w); err != nil { + return err + } + } + _, err := fmt.Fprintln(w, "EndInventory") + return err +} + +func (i *Inv) Deserialize(r io.Reader) (err error) { + s := new(sentinal) + defer func() { + r := recover() + if r, ok := r.(sentinal); ok { + err = r.err + } + }() + + old := *i + *i = nil + + for { + if err := readCmdLn(r, map[string]interface{}{ + "List": func(name string, size int) { + l := old.List(name) + if l == nil { + l = &NamedInvList{Name: name} + } + + if err := l.Deserialize(r); err != nil { + s.ret(fmt.Errorf("List %s %d: %w", name, size, err)) + } + if len(l.Stacks) != size { + s.ret(fmt.Errorf("List %s %d: contains %d stacks", name, size, len(l.Stacks))) + } + + *i = append(*i, *l) + }, + "KeepList": func(name string) { + l := old.List(name) + if l == nil { + s.ret(fmt.Errorf("KeepList %s: list does not exist", name)) + } + + *i = append(*i, *l) + }, + "EndInventory": func() { + s.ret(nil) + }, + }); err != nil { + if err == io.EOF { + s.ret(io.ErrUnexpectedEOF) + } + s.ret(err) + } + } +} + +type InvList struct { + Width int + Stacks []Stack +} + +func (l InvList) Serialize(w io.Writer) error { + if _, err := fmt.Fprintln(w, "Width", l.Width); err != nil { + return err + } + for _, i := range l.Stacks { + if i.Count > 0 { + if _, err := fmt.Fprintln(w, "Item", i); err != nil { + return err + } + } else { + if _, err := fmt.Fprintln(w, "Empty"); err != nil { + return err + } + } + } + _, err := fmt.Fprintln(w, "EndInventoryList") + return err +} + +func (i *InvList) Deserialize(r io.Reader) (err error) { + s := new(sentinal) + defer func() { + r := recover() + if r, ok := r.(sentinal); ok { + err = r.err + } + }() + + if _, err := fmt.Fscanf(r, "Width %d\n", &i.Width); err != nil { + s.ret(err) + } + + i.Stacks = i.Stacks[:0] + + for { + if err := readCmdLn(r, map[string]interface{}{ + "Empty": func() { + i.Stacks = append(i.Stacks, Stack{}) + }, + "Item": func(stk Stack) { + i.Stacks = append(i.Stacks, stk) + }, + "Keep": func() { + if len(i.Stacks) < cap(i.Stacks) { + i.Stacks = i.Stacks[:len(i.Stacks)+1] + } else { + i.Stacks = append(i.Stacks, Stack{}) + } + }, + "EndInventoryList": func() { + s.ret(nil) + }, + }); err != nil { + if err == io.EOF { + s.ret(io.ErrUnexpectedEOF) + } + s.ret(err) + } + } +} + +func readCmdLn(r io.Reader, cmds map[string]interface{}) error { + if _, ok := r.(io.RuneScanner); !ok { + r = &readRune{Reader: r, peekRune: -1} + } + + var cmd string + if _, err := fmt.Fscan(r, &cmd); err != nil { + return err + } + + f, ok := cmds[cmd] + if !ok { + return fmt.Errorf("unsupported line type: %+q", cmd) + } + + t := reflect.TypeOf(f) + + a := make([]interface{}, t.NumIn()) + for i := range a { + a[i] = reflect.New(t.In(i)).Interface() + } + fmt.Fscanln(r, a...) + + args := make([]reflect.Value, t.NumIn()) + for i := range args { + args[i] = reflect.ValueOf(a[i]).Elem() + } + reflect.ValueOf(f).Call(args) + + return nil +} diff --git a/readrune.go b/readrune.go new file mode 100644 index 0000000..775e06f --- /dev/null +++ b/readrune.go @@ -0,0 +1,116 @@ +/* +Based on go1.16/src/fmt/scan.go. + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package mt + +import ( + "errors" + "io" + "unicode/utf8" +) + +// readRune is a structure to enable reading UTF-8 encoded code points +// from an io.Reader. It is used if the Reader given to the scanner does +// not already implement io.RuneScanner. +type readRune struct { + io.Reader + buf [utf8.UTFMax]byte // used only inside ReadRune + pending int // number of bytes in pendBuf; only >0 for bad UTF-8 + pendBuf [utf8.UTFMax]byte // bytes left over + peekRune rune // if >=0 next rune; when <0 is ^(previous Rune) +} + +// readByte returns the next byte from the input, which may be +// left over from a previous read if the UTF-8 was ill-formed. +func (r *readRune) readByte() (b byte, err error) { + if r.pending > 0 { + b = r.pendBuf[0] + copy(r.pendBuf[0:], r.pendBuf[1:]) + r.pending-- + return + } + n, err := io.ReadFull(r, r.pendBuf[:1]) + if n != 1 { + return 0, err + } + return r.pendBuf[0], err +} + +// ReadRune returns the next UTF-8 encoded code point from the +// io.Reader inside r. +func (r *readRune) ReadRune() (rr rune, size int, err error) { + if r.peekRune >= 0 { + rr = r.peekRune + r.peekRune = ^r.peekRune + size = utf8.RuneLen(rr) + return + } + r.buf[0], err = r.readByte() + if err != nil { + return + } + if r.buf[0] < utf8.RuneSelf { // fast check for common ASCII case + rr = rune(r.buf[0]) + size = 1 // Known to be 1. + // Flip the bits of the rune so it's available to UnreadRune. + r.peekRune = ^rr + return + } + var n int + for n = 1; !utf8.FullRune(r.buf[:n]); n++ { + r.buf[n], err = r.readByte() + if err != nil { + if err == io.EOF { + err = nil + break + } + return + } + } + rr, size = utf8.DecodeRune(r.buf[:n]) + if size < n { // an error, save the bytes for the next read + copy(r.pendBuf[r.pending:], r.buf[size:n]) + r.pending += n - size + } + // Flip the bits of the rune so it's available to UnreadRune. + r.peekRune = ^rr + return +} + +func (r *readRune) UnreadRune() error { + if r.peekRune >= 0 { + return errors.New("fmt: scanning called UnreadRune with no rune available") + } + // Reverse bit flip of previously read rune to obtain valid >=0 state. + r.peekRune = ^r.peekRune + return nil +} diff --git a/stack.go b/stack.go new file mode 100644 index 0000000..fa83d8d --- /dev/null +++ b/stack.go @@ -0,0 +1,232 @@ +// In this file, JSON refers to WTF-JSON, a variant of JSON used by Minetest +// where \u00XX escapes in string literals act like Go's \xXX escapes. + +package mt + +import ( + "fmt" + "io" + "strconv" + "strings" + "unicode/utf8" +) + +type Stack struct { + Item + Count uint16 +} + +type Item struct { + Name string + Wear uint16 + ItemMeta +} + +type ItemMeta string + +func (m ItemMeta) Field(name string) (s string, ok bool) { + if len(m) > 0 && m[0] == 1 { + m = m[1:] + eat := func(stop byte) string { + for i := 0; i < len(m); i++ { + if m[i] == stop { + defer func() { + m = m[i+1:] + }() + return string(m[:i]) + } + } + defer func() { + m = "" + }() + return string(m) + } + for len(m) > 0 { + if eat(2) == name { + s = eat(3) + ok = true + } + } + return + } + + if name == "" { + return string(m), true + } + + return "", false +} + +func (s Stack) String() string { + if s.Count == 0 { + return "" + } + + n := 1 + if s.ItemMeta != "" { + n = 4 + } else if s.Wear > 0 { + n = 3 + } else if s.Count > 1 { + n = 2 + } + + return strings.Join([]string{ + optJSONStr(s.Name), + fmt.Sprint(s.Count), + fmt.Sprint(s.Wear), + optJSONStr(string(s.ItemMeta)), + }[:n], " ") +} + +func optJSONStr(s string) string { + for _, r := range s { + if r <= ' ' || r == '"' || r >= utf8.RuneSelf { + return jsonStr(s) + } + } + return s +} + +func jsonStr(s string) string { + esc := [256]byte{ + '\\': '\\', + '"': '"', + '/': '/', + '\b': 'b', + '\f': 'f', + '\n': 'n', + '\r': 'r', + '\t': 't', + } + + b := new(strings.Builder) + + b.WriteByte('"') + for i := 0; i < len(s); i++ { + switch c := s[i]; { + case esc[c] != 0: + fmt.Fprintf(b, "\\%c", esc[c]) + case ' ' <= c && c <= '~': + b.WriteByte(c) + default: + fmt.Fprintf(b, "\\u%04x", c) + } + } + b.WriteByte('"') + + return b.String() +} + +func (stk *Stack) Scan(state fmt.ScanState, verb rune) (err error) { + *stk = Stack{} + + defer func() { + if err == io.EOF { + err = nil + } + }() + + nm, err := scanOptJSONStr(state) + if err != nil { + return err + } + stk.Name = nm + stk.Count = 1 + + if _, err := fmt.Fscan(state, &stk.Count, &stk.Wear); err != nil { + return err + } + + s, err := scanOptJSONStr(state) + if err != nil { + return err + } + stk.ItemMeta = ItemMeta(s) + + return nil +} + +func scanOptJSONStr(state fmt.ScanState) (string, error) { + state.SkipSpace() + + r, _, err := state.ReadRune() + if err != nil { + return "", err + } + state.UnreadRune() + + if r == '"' { + return scanJSONStr(state) + } + + token, err := state.Token(false, func(r rune) bool { return r != ' ' }) + return string(token), err +} + +func scanJSONStr(state fmt.ScanState) (s string, rerr error) { + r, _, err := state.ReadRune() + if err != nil { + return "", err + } + if r != '"' { + return "", fmt.Errorf("unexpected rune: %q", r) + } + + defer func() { + if rerr == io.EOF { + rerr = io.ErrUnexpectedEOF + } + }() + + b := new(strings.Builder) + for { + r, _, err := state.ReadRune() + if err != nil { + return b.String(), err + } + + switch r { + case '"': + return b.String(), nil + case '\\': + r, _, err := state.ReadRune() + if err != nil { + return b.String(), err + } + + switch r { + case '\\', '"', '/': + b.WriteRune(r) + case 'b': + b.WriteRune('\b') + case 'f': + b.WriteRune('\f') + case 'n': + b.WriteRune('\n') + case 'r': + b.WriteRune('\r') + case 't': + b.WriteRune('\t') + case 'u': + var hex [4]rune + for i := range hex { + r, _, err := state.ReadRune() + if err != nil { + return b.String(), err + } + hex[i] = r + } + n, err := strconv.ParseUint(string(hex[:]), 16, 8) + if err != nil { + return b.String(), err + } + b.WriteByte(byte(n)) + default: + return b.String(), fmt.Errorf("invalid escape: \\%c", r) + } + default: + b.WriteRune(r) + } + } +} -- 2.44.0