]> git.lizzy.rs Git - mt.git/blob - stack.go
Add WaitGroup to SerializePkt
[mt.git] / stack.go
1 // In this file, JSON refers to WTF-JSON.
2 //
3 // BUG(mt): Stackstrings 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 stackstring.
29 func (s Stack) String() string {
30         if s.Count == 0 {
31                 return ""
32         }
33
34         n := 1
35         if s.ItemMeta != "" {
36                 n = 4
37         } else if s.Wear > 0 {
38                 n = 3
39         } else if s.Count > 1 {
40                 n = 2
41         }
42
43         return strings.Join([]string{
44                 optJSONStr(s.Name),
45                 fmt.Sprint(s.Count),
46                 fmt.Sprint(s.Wear),
47                 optJSONStr(string(s.ItemMeta)),
48         }[:n], " ")
49 }
50
51 func optJSONStr(s string) string {
52         for _, r := range s {
53                 if r <= ' ' || r == '"' || r >= utf8.RuneSelf {
54                         return jsonStr(s)
55                 }
56         }
57         return s
58 }
59
60 func jsonStr(s string) string {
61         esc := [256]byte{
62                 '\\': '\\',
63                 '"':  '"',
64                 '/':  '/',
65                 '\b': 'b',
66                 '\f': 'f',
67                 '\n': 'n',
68                 '\r': 'r',
69                 '\t': 't',
70         }
71
72         b := new(strings.Builder)
73
74         b.WriteByte('"')
75         for i := 0; i < len(s); i++ {
76                 switch c := s[i]; {
77                 case esc[c] != 0:
78                         fmt.Fprintf(b, "\\%c", esc[c])
79                 case ' ' <= c && c <= '~':
80                         b.WriteByte(c)
81                 default:
82                         fmt.Fprintf(b, "\\u%04x", c)
83                 }
84         }
85         b.WriteByte('"')
86
87         return b.String()
88 }
89
90 // Scan scans a stackstring, implementing the fmt.Scanner interface.
91 func (stk *Stack) Scan(state fmt.ScanState, verb rune) (err error) {
92         *stk = Stack{}
93
94         defer func() {
95                 if err == io.EOF {
96                         err = nil
97                 }
98         }()
99
100         nm, err := scanOptJSONStr(state)
101         if err != nil {
102                 return err
103         }
104         stk.Name = nm
105         stk.Count = 1
106
107         if _, err := fmt.Fscan(state, &stk.Count, &stk.Wear); err != nil {
108                 return err
109         }
110
111         s, err := scanOptJSONStr(state)
112         if err != nil {
113                 return err
114         }
115         stk.ItemMeta = ItemMeta(s)
116
117         return nil
118 }
119
120 func scanOptJSONStr(state fmt.ScanState) (string, error) {
121         state.SkipSpace()
122
123         r, _, err := state.ReadRune()
124         if err != nil {
125                 return "", err
126         }
127         state.UnreadRune()
128
129         if r == '"' {
130                 return scanJSONStr(state)
131         }
132
133         token, err := state.Token(false, func(r rune) bool {
134                 return r != ' ' && r != '\n'
135         })
136         return string(token), err
137 }
138
139 func scanJSONStr(state fmt.ScanState) (s string, rerr error) {
140         r, _, err := state.ReadRune()
141         if err != nil {
142                 return "", err
143         }
144         if r != '"' {
145                 return "", fmt.Errorf("unexpected rune: %q", r)
146         }
147
148         defer func() {
149                 if rerr == io.EOF {
150                         rerr = io.ErrUnexpectedEOF
151                 }
152         }()
153
154         b := new(strings.Builder)
155         for {
156                 r, _, err := state.ReadRune()
157                 if err != nil {
158                         return b.String(), err
159                 }
160
161                 switch r {
162                 case '"':
163                         return b.String(), nil
164                 case '\\':
165                         r, _, err := state.ReadRune()
166                         if err != nil {
167                                 return b.String(), err
168                         }
169
170                         switch r {
171                         case '\\', '"', '/':
172                                 b.WriteRune(r)
173                         case 'b':
174                                 b.WriteRune('\b')
175                         case 'f':
176                                 b.WriteRune('\f')
177                         case 'n':
178                                 b.WriteRune('\n')
179                         case 'r':
180                                 b.WriteRune('\r')
181                         case 't':
182                                 b.WriteRune('\t')
183                         case 'u':
184                                 var hex [4]rune
185                                 for i := range hex {
186                                         r, _, err := state.ReadRune()
187                                         if err != nil {
188                                                 return b.String(), err
189                                         }
190                                         hex[i] = r
191                                 }
192                                 n, err := strconv.ParseUint(string(hex[:]), 16, 8)
193                                 if err != nil {
194                                         return b.String(), err
195                                 }
196                                 b.WriteByte(byte(n))
197                         default:
198                                 return b.String(), fmt.Errorf("invalid escape: \\%c", r)
199                         }
200                 default:
201                         b.WriteRune(r)
202                 }
203         }
204 }