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

parse.gno

3.79 Kb · 130 lines
  1package payout
  2
  3// Both parsers read their input in a single pass, byte by byte, and bound it
  4// before they read it. On chain every interpreted operation is paid for, and
  5// an oversized string must cost its sender nothing but a refusal.
  6//
  7// They only accept an address in its canonical form, see validAddress.
  8
  9const (
 10	// addressLen is the length of a gno address: "g1" and 38 characters.
 11	addressLen = 40
 12	// maxPlayersLen bounds a players string: MaxPlayers addresses, each
 13	// followed by a separator.
 14	maxPlayersLen = MaxPlayers * (addressLen + 1)
 15	// maxDeathsLen bounds a deaths string: MaxDeaths entries
 16	// of two addresses, each followed by a separator.
 17	maxDeathsLen = MaxDeaths * 2 * (addressLen + 1)
 18)
 19
 20// ParsePlayers parses a players string: comma-separated addresses, for
 21// example
 22//
 23//	g1aaa...,g1bbb...,g1ccc...
 24//
 25// It returns 1 to MaxPlayers addresses in the order of the string, each one
 26// valid and listed once. Whitespace, empty entries and a trailing separator
 27// are refused. Whether there are enough players for a round is decided by
 28// Rule.Settle, and by the realm that starts the round.
 29func ParsePlayers(s string) ([]address, error) {
 30	if s == "" {
 31		return nil, ErrNoPlayers
 32	}
 33	if len(s) > maxPlayersLen {
 34		return nil, ErrTooManyPlayers
 35	}
 36	var players []address
 37	i := 0
 38	for {
 39		if len(players) == MaxPlayers {
 40			return nil, ErrTooManyPlayers
 41		}
 42		end := indexFrom(s, i, ',')
 43		addr := address(s[i:end])
 44		if !validAddress(addr) {
 45			return nil, ErrPlayersFormat
 46		}
 47		if indexOf(players, addr) >= 0 {
 48			return nil, ErrDuplicatePlayer
 49		}
 50		players = append(players, addr)
 51		if end == len(s) {
 52			return players, nil
 53		}
 54		i = end + 1
 55		if i == len(s) {
 56			return nil, ErrPlayersFormat // a trailing ","
 57		}
 58	}
 59}
 60
 61// ParseDeaths parses a deaths string: "victim:killer" entries
 62// separated by ";", in the order they happened, "victim:" when nobody shot
 63// the victim down, for example
 64//
 65//	g1bbb...:g1aaa...;g1ccc...:
 66//
 67// The empty string means nobody died and gives an empty list. At
 68// most MaxDeaths entries are accepted, and every address must be valid.
 69// Whitespace, empty entries and a trailing separator are refused.
 70//
 71// It only checks the syntax. Whether the victims and the killers are players
 72// of the round, and how often they died, is decided by Rule.Settle.
 73func ParseDeaths(s string) ([]Death, error) {
 74	if s == "" {
 75		return nil, nil
 76	}
 77	if len(s) > maxDeathsLen {
 78		return nil, ErrTooManyDeaths
 79	}
 80	var out []Death
 81	i := 0
 82	for {
 83		if len(out) == MaxDeaths {
 84			return nil, ErrTooManyDeaths
 85		}
 86		colon := indexFrom(s, i, ':')
 87		if colon == len(s) {
 88			return nil, ErrDeathsFormat
 89		}
 90		end := indexFrom(s, colon+1, ';')
 91
 92		victim := address(s[i:colon])
 93		if !validAddress(victim) {
 94			return nil, ErrDeathsFormat
 95		}
 96		killer := address(s[colon+1 : end])
 97		if killer != "" && !validAddress(killer) {
 98			return nil, ErrDeathsFormat
 99		}
100		out = append(out, Death{Victim: victim, Killer: killer})
101		if end == len(s) {
102			return out, nil
103		}
104		i = end + 1
105		if i == len(s) {
106			return nil, ErrDeathsFormat // a trailing ";"
107		}
108	}
109}
110
111// validAddress reports whether addr is a valid address in its canonical,
112// lowercase form.
113//
114// Bech32 can also be written in all capitals, and address.IsValid accepts
115// that. To a realm the capitals would be a second name for the same account,
116// one that matches no player of a round. Bech32 does not allow mixing cases,
117// so looking at the first character is enough. The length is checked first:
118// it is free, and it keeps an overlong string away from the decoder.
119func validAddress(addr address) bool {
120	return len(addr) == addressLen && addr[0] == 'g' && addr.IsValid()
121}
122
123// indexFrom returns the index of the first c in s at or after i, or len(s)
124// when there is none.
125func indexFrom(s string, i int, c byte) int {
126	for i < len(s) && s[i] != c {
127		i++
128	}
129	return i
130}