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