--- /dev/null
+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
+}
--- /dev/null
+/*
+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
+}
--- /dev/null
+// 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)
+ }
+ }
+}