Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

json.gno

4.67 Kb · 243 lines
  1package game31
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"gno.land/p/nt/avl/v0"
  8)
  9
 10// jsonStr returns s as a quoted, escaped JSON string (game28 concatenated raw
 11// user strings into JSON, so a guild name with a quote broke every reader).
 12func jsonStr(s string) string {
 13	var b strings.Builder
 14	b.WriteByte('"')
 15	for i := 0; i < len(s); i++ {
 16		c := s[i]
 17		switch c {
 18		case '"':
 19			b.WriteString(`\"`)
 20		case '\\':
 21			b.WriteString(`\\`)
 22		case '\n':
 23			b.WriteString(`\n`)
 24		case '\r':
 25			b.WriteString(`\r`)
 26		case '\t':
 27			b.WriteString(`\t`)
 28		default:
 29			if c < 0x20 {
 30				b.WriteString(`\u00`)
 31				b.WriteByte("0123456789abcdef"[c>>4])
 32				b.WriteByte("0123456789abcdef"[c&0xf])
 33			} else {
 34				b.WriteByte(c)
 35			}
 36		}
 37	}
 38	b.WriteByte('"')
 39	return b.String()
 40}
 41
 42// sanitizeName trims and strips control characters from a display name.
 43func sanitizeName(s string) string {
 44	var b strings.Builder
 45	for _, r := range strings.TrimSpace(s) {
 46		if r < 0x20 || r == 0x7f {
 47			continue
 48		}
 49		b.WriteRune(r)
 50	}
 51	return b.String()
 52}
 53
 54// isSafeId reports whether s is a short [a-z0-9_.-] identifier.
 55func isSafeId(s string, maxLen int) bool {
 56	if s == "" || len(s) > maxLen {
 57		return false
 58	}
 59	for i := 0; i < len(s); i++ {
 60		c := s[i]
 61		if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.') {
 62			return false
 63		}
 64	}
 65	return true
 66}
 67
 68func isHex64(s string) bool {
 69	if len(s) != 64 {
 70		return false
 71	}
 72	for i := 0; i < len(s); i++ {
 73		c := s[i]
 74		if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') {
 75			return false
 76		}
 77	}
 78	return true
 79}
 80
 81// parseStringRows parses a JSON array of arrays of strings, e.g.
 82// [["g1..","grin","10"],["g1..","joy","4"]]. Numbers are accepted unquoted.
 83// Panics on malformed input.
 84func parseStringRows(s string) [][]string {
 85	p := &jparser{s: s}
 86	p.ws()
 87	p.expect('[')
 88	var rows [][]string
 89	p.ws()
 90	if p.peek() == ']' {
 91		p.i++
 92		return rows
 93	}
 94	for {
 95		p.ws()
 96		p.expect('[')
 97		var row []string
 98		p.ws()
 99		if p.peek() == ']' {
100			p.i++
101		} else {
102			for {
103				p.ws()
104				row = append(row, p.value())
105				p.ws()
106				if p.peek() == ',' {
107					p.i++
108					continue
109				}
110				p.expect(']')
111				break
112			}
113		}
114		rows = append(rows, row)
115		p.ws()
116		if p.peek() == ',' {
117			p.i++
118			continue
119		}
120		p.expect(']')
121		break
122	}
123	p.ws()
124	if p.i != len(p.s) {
125		panic("invalid JSON: trailing data")
126	}
127	return rows
128}
129
130// parseStringList parses ["a","b",...].
131func parseStringList(s string) []string {
132	rows := parseStringRows("[" + s + "]")
133	if len(rows) != 1 {
134		panic("invalid JSON list")
135	}
136	return rows[0]
137}
138
139type jparser struct {
140	s string
141	i int
142}
143
144func (p *jparser) peek() byte {
145	if p.i >= len(p.s) {
146		panic("invalid JSON: unexpected end")
147	}
148	return p.s[p.i]
149}
150
151func (p *jparser) ws() {
152	for p.i < len(p.s) && (p.s[p.i] == ' ' || p.s[p.i] == '\n' || p.s[p.i] == '\t' || p.s[p.i] == '\r') {
153		p.i++
154	}
155}
156
157func (p *jparser) expect(c byte) {
158	if p.peek() != c {
159		panic("invalid JSON: expected " + string(c) + " at " + strconv.Itoa(p.i))
160	}
161	p.i++
162}
163
164func (p *jparser) value() string {
165	if p.peek() != '"' {
166		st := p.i
167		for p.i < len(p.s) && (p.s[p.i] >= '0' && p.s[p.i] <= '9' || p.s[p.i] == '-') {
168			p.i++
169		}
170		if st == p.i {
171			panic("invalid JSON: expected string or number at " + strconv.Itoa(st))
172		}
173		return p.s[st:p.i]
174	}
175	p.i++
176	var b strings.Builder
177	for {
178		c := p.peek()
179		p.i++
180		if c == '"' {
181			return b.String()
182		}
183		if c == '\\' {
184			e := p.peek()
185			p.i++
186			switch e {
187			case '"', '\\', '/':
188				b.WriteByte(e)
189			case 'n':
190				b.WriteByte('\n')
191			case 't':
192				b.WriteByte('\t')
193			case 'r':
194				b.WriteByte('\r')
195			default:
196				panic("invalid JSON escape")
197			}
198			continue
199		}
200		b.WriteByte(c)
201	}
202}
203
204// ── avl uint helpers ─────────────────────────────────────────────────────────
205
206func getU(t *avl.Tree, key string) uint64 {
207	if !t.Has(key) {
208		return 0
209	}
210	v, _ := strconv.ParseUint(t.Get(key).(string), 10, 64)
211	return v
212}
213
214func setU(t *avl.Tree, key string, v uint64) {
215	t.Set(key, strconv.FormatUint(v, 10))
216}
217
218// addU adds d to a stored counter; panics instead of wrapping on overflow.
219func addU(t *avl.Tree, key string, d uint64) uint64 {
220	old := getU(t, key)
221	v := old + d
222	if v < old {
223		panic("math overflow")
224	}
225	setU(t, key, v)
226	return v
227}
228
229func balOf(addr, coin string) uint64 { return getU(&balances, addr+":"+coin) }
230
231func creditBal(addr, coin string, amt uint64) {
232	if amt > 0 {
233		addU(&balances, addr+":"+coin, amt)
234	}
235}
236
237func debitBal(addr, coin string, amt uint64) {
238	have := balOf(addr, coin)
239	if have < amt {
240		panic("insufficient " + coin + ": need " + strconv.FormatUint(amt, 10) + " have " + strconv.FormatUint(have, 10))
241	}
242	setU(&balances, addr+":"+coin, have-amt)
243}