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