]> git.lizzy.rs Git - mt.git/blob - stack.go
Add high-level protocol (de)serialization
[mt.git] / stack.go
1 // In this file, JSON refers to WTF-JSON.
2 //
3 // BUG(mt): Itemstrings use variant of JSON called WTF-JSON
4 // where \u00XX escapes in string literals act like Go's \xXX escapes.
5 // This should not be fixed because it would break existing items.
6
7 package mt
8
9 import (
10         "fmt"
11         "io"
12         "strconv"
13         "strings"
14         "unicode/utf8"
15 )
16
17 type Stack struct {
18         Item
19         Count uint16
20 }
21
22 type Item struct {
23         Name string
24         Wear uint16
25         ItemMeta
26 }
27
28 // String returns the Stack's itemstring.
29 //
30 // BUG(mt): The term itemstring is somewhat misleading, because
31 // an itemstring represents a stack of items, not a single item.
32 func (s Stack) String() string {
33         if s.Count == 0 {
34                 return ""
35         }
36
37         n := 1
38         if s.ItemMeta != "" {
39                 n = 4
40         } else if s.Wear > 0 {
41                 n = 3
42         } else if s.Count > 1 {
43                 n = 2
44         }
45
46         return strings.Join([]string{
47                 optJSONStr(s.Name),
48                 fmt.Sprint(s.Count),
49                 fmt.Sprint(s.Wear),
50                 optJSONStr(string(s.ItemMeta)),
51         }[:n], " ")
52 }
53
54 func optJSONStr(s string) string {
55         for _, r := range s {
56                 if r <= ' ' || r == '"' || r >= utf8.RuneSelf {
57                         return jsonStr(s)
58                 }
59         }
60         return s
61 }
62
63 func jsonStr(s string) string {
64         esc := [256]byte{
65                 '\\': '\\',
66                 '"':  '"',
67                 '/':  '/',
68                 '\b': 'b',
69                 '\f': 'f',
70                 '\n': 'n',
71                 '\r': 'r',
72                 '\t': 't',
73         }
74
75         b := new(strings.Builder)
76
77         b.WriteByte('"')
78         for i := 0; i < len(s); i++ {
79                 switch c := s[i]; {
80                 case esc[c] != 0:
81                         fmt.Fprintf(b, "\\%c", esc[c])
82                 case ' ' <= c && c <= '~':
83                         b.WriteByte(c)
84                 default:
85                         fmt.Fprintf(b, "\\u%04x", c)
86                 }
87         }
88         b.WriteByte('"')
89
90         return b.String()
91 }
92
93 // Scan scans an itemstring, implementing the fmt.Scanner interface.
94 func (stk *Stack) Scan(state fmt.ScanState, verb rune) (err error) {
95         *stk = Stack{}
96
97         defer func() {
98                 if err == io.EOF {
99                         err = nil
100                 }
101         }()
102
103         nm, err := scanOptJSONStr(state)
104         if err != nil {
105                 return err
106         }
107         stk.Name = nm
108         stk.Count = 1
109
110         if _, err := fmt.Fscan(state, &stk.Count, &stk.Wear); err != nil {
111                 return err
112         }
113
114         s, err := scanOptJSONStr(state)
115         if err != nil {
116                 return err
117         }
118         stk.ItemMeta = ItemMeta(s)
119
120         return nil
121 }
122
123 func scanOptJSONStr(state fmt.ScanState) (string, error) {
124         state.SkipSpace()
125
126         r, _, err := state.ReadRune()
127         if err != nil {
128                 return "", err
129         }
130         state.UnreadRune()
131
132         if r == '"' {
133                 return scanJSONStr(state)
134         }
135
136         token, err := state.Token(false, func(r rune) bool {
137                 return r != ' ' && r != '\n'
138         })
139         return string(token), err
140 }
141
142 func scanJSONStr(state fmt.ScanState) (s string, rerr error) {
143         r, _, err := state.ReadRune()
144         if err != nil {
145                 return "", err
146         }
147         if r != '"' {
148                 return "", fmt.Errorf("unexpected rune: %q", r)
149         }
150
151         defer func() {
152                 if rerr == io.EOF {
153                         rerr = io.ErrUnexpectedEOF
154                 }
155         }()
156
157         b := new(strings.Builder)
158         for {
159                 r, _, err := state.ReadRune()
160                 if err != nil {
161                         return b.String(), err
162                 }
163
164                 switch r {
165                 case '"':
166                         return b.String(), nil
167                 case '\\':
168                         r, _, err := state.ReadRune()
169                         if err != nil {
170                                 return b.String(), err
171                         }
172
173                         switch r {
174                         case '\\', '"', '/':
175                                 b.WriteRune(r)
176                         case 'b':
177                                 b.WriteRune('\b')
178                         case 'f':
179                                 b.WriteRune('\f')
180                         case 'n':
181                                 b.WriteRune('\n')
182                         case 'r':
183                                 b.WriteRune('\r')
184                         case 't':
185                                 b.WriteRune('\t')
186                         case 'u':
187                                 var hex [4]rune
188                                 for i := range hex {
189                                         r, _, err := state.ReadRune()
190                                         if err != nil {
191                                                 return b.String(), err
192                                         }
193                                         hex[i] = r
194                                 }
195                                 n, err := strconv.ParseUint(string(hex[:]), 16, 8)
196                                 if err != nil {
197                                         return b.String(), err
198                                 }
199                                 b.WriteByte(byte(n))
200                         default:
201                                 return b.String(), fmt.Errorf("invalid escape: \\%c", r)
202                         }
203                 default:
204                         b.WriteRune(r)
205                 }
206         }
207 }