]> git.lizzy.rs Git - mt.git/blob - stack.go
Use sentinal correctly
[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 { return r != ' ' })
164         return string(token), err
165 }
166
167 func scanJSONStr(state fmt.ScanState) (s string, rerr error) {
168         r, _, err := state.ReadRune()
169         if err != nil {
170                 return "", err
171         }
172         if r != '"' {
173                 return "", fmt.Errorf("unexpected rune: %q", r)
174         }
175
176         defer func() {
177                 if rerr == io.EOF {
178                         rerr = io.ErrUnexpectedEOF
179                 }
180         }()
181
182         b := new(strings.Builder)
183         for {
184                 r, _, err := state.ReadRune()
185                 if err != nil {
186                         return b.String(), err
187                 }
188
189                 switch r {
190                 case '"':
191                         return b.String(), nil
192                 case '\\':
193                         r, _, err := state.ReadRune()
194                         if err != nil {
195                                 return b.String(), err
196                         }
197
198                         switch r {
199                         case '\\', '"', '/':
200                                 b.WriteRune(r)
201                         case 'b':
202                                 b.WriteRune('\b')
203                         case 'f':
204                                 b.WriteRune('\f')
205                         case 'n':
206                                 b.WriteRune('\n')
207                         case 'r':
208                                 b.WriteRune('\r')
209                         case 't':
210                                 b.WriteRune('\t')
211                         case 'u':
212                                 var hex [4]rune
213                                 for i := range hex {
214                                         r, _, err := state.ReadRune()
215                                         if err != nil {
216                                                 return b.String(), err
217                                         }
218                                         hex[i] = r
219                                 }
220                                 n, err := strconv.ParseUint(string(hex[:]), 16, 8)
221                                 if err != nil {
222                                         return b.String(), err
223                                 }
224                                 b.WriteByte(byte(n))
225                         default:
226                                 return b.String(), fmt.Errorf("invalid escape: \\%c", r)
227                         }
228                 default:
229                         b.WriteRune(r)
230                 }
231         }
232 }