/
charmedNum.go
230 lines (211 loc) · 5.45 KB
/
charmedNum.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package charmed
import (
"fmt"
"math/bits"
"strconv"
"strings"
"github.com/ionous/tell/charm"
"github.com/ionous/tell/runes"
)
type modeType int
const (
modePending modeType = iota
modeInt
modeHex
modeFloat
)
// return a state which reads until the end of string, returns error if finished incorrectly
type NumParser struct {
runes strings.Builder
mode modeType
}
func (*NumParser) String() string {
return "Numbers"
}
// fix: this is currently less of a number parser, and more a number validator.
func (p *NumParser) accept(q rune, s charm.State) charm.State {
p.runes.WriteRune(q)
return s
}
// returns int64 or float64
func (p *NumParser) GetNumber() (ret any, err error) {
switch s := p.runes.String(); p.mode {
case modeInt:
ret = fromInt(s)
case modeHex:
ret = fromHex(s)
case modeFloat:
ret = fromFloat(s)
default:
err = fmt.Errorf("unknown number: '%v' is %v.", s, p.mode)
}
return
}
// helper to turn a string into a value
func (p *NumParser) GetFloat() (ret float64, err error) {
switch s := p.runes.String(); p.mode {
case modeInt:
ret = float64(fromInt(s))
case modeHex:
ret = float64(fromHex(s))
case modeFloat:
ret = fromFloat(s)
default:
err = fmt.Errorf("unknown number: '%v' is %v.", s, p.mode)
}
return
}
// return a state capable of digit parsing.
// note: this doesn't support leading with just a "."
func (p *NumParser) Decode() charm.State {
return charm.Statement("numberDecoder", func(r rune) (ret charm.State) {
switch r {
case '-', '+':
ret = p.accept(r, charm.Statement("after lead plus", func(r rune) (ret charm.State) {
if runes.IsNumber(r) {
p.mode = modeInt
ret = p.accept(r, charm.Statement("num plus", p.leadingDigit))
}
return
}))
case '0':
// 0 can standalone; but, it might be followed by a hex qualifier.
p.mode = modeInt
ret = p.accept(r, charm.Statement("hex check", func(r rune) (ret charm.State) {
// https://golang.org/ref/spec#hex_literal
switch {
case r == 'x' || r == 'X':
p.mode = modePending
ret = p.accept(r, charm.Statement("hex parse", func(r rune) (ret charm.State) {
if runes.IsHex(r) {
p.mode = modeHex
ret = p.accept(r, charm.Statement("num hex", p.hexDigits))
}
return
}))
default:
// delegate to number and dot checking...
// in a statecharmed, it would be a super-state, and
// x (above) would jump to a sibling of that super-state.
ret = p.leadingDigit(r)
}
return
}))
default:
if runes.IsNumber(r) {
// https://golang.org/ref/spec#float_lit
p.mode = modeInt
ret = p.accept(r, charm.Statement("num digits", p.leadingDigit))
}
}
return
})
}
// a string of numbers, possibly followed by a decimal or exponent separator.
// note: golang numbers can end in a pure ".", this does not allow that.
func (p *NumParser) leadingDigit(r rune) (ret charm.State) {
switch {
case runes.IsNumber(r):
ret = p.accept(r, charm.Statement("leading dig", p.leadingDigit))
case r == '.':
p.mode = modePending
ret = p.accept(r, charm.Statement("decimal", func(r rune) (ret charm.State) {
if runes.IsNumber(r) {
p.mode = modeFloat
ret = p.accept(r, charm.Statement("decimal digits", p.leadingDigit))
} else {
ret = p.tryExponent(r) // delegate to exponent checking,,,
}
return
}))
default:
ret = p.tryExponent(r) // delegate to exponent checking,,,
}
return
}
// https://golang.org/ref/spec#exponent
// exponent = ( "e" | "E" ) [ "+" | "-" ] decimals
func (p *NumParser) tryExponent(r rune) (ret charm.State) {
switch {
case r == 'e' || r == 'E':
p.mode = modePending
ret = p.accept(r, charm.Statement("exp", func(r rune) (ret charm.State) {
switch {
case runes.IsNumber(r):
p.mode = modeFloat
ret = p.accept(r, charm.Statement("exp decimal", p.decimals))
case r == '+' || r == '-':
ret = p.accept(r, charm.Statement("exp power", func(r rune) (ret charm.State) {
if runes.IsNumber(r) {
p.mode = modeFloat
ret = p.accept(r, charm.Statement("exp num", p.decimals))
}
return
}))
}
return
}))
}
return
}
// a chain of decimal digits 0-9
func (p *NumParser) decimals(r rune) (ret charm.State) {
if runes.IsNumber(r) {
ret = p.accept(r, charm.Statement("decimals", p.decimals))
}
return
}
// a chain of hex digits 0-9, a-f
func (p *NumParser) hexDigits(r rune) (ret charm.State) {
if runes.IsHex(r) {
ret = p.accept(r, charm.Statement("hexDigits", p.hexDigits))
}
return
}
func fromInt(s string) (ret int) {
s, negate := unary(s)
if i, e := strconv.ParseInt(s, 10, bits.UintSize); e != nil {
panic(e)
} else if negate {
ret = -int(i)
} else {
ret = int(i)
}
return
}
func fromHex(s string) (ret uint) {
// hex string - chops out the 0x qualifier
if i, e := strconv.ParseUint(s[2:], 16, bits.UintSize); e != nil {
panic(e)
} else {
ret = uint(i) // no negative for hex.
}
return
}
func fromFloat(s string) (ret float64) {
s, negate := unary(s)
if f, e := strconv.ParseFloat(s, 64); e != nil {
panic(e)
} else if negate {
ret = -f
} else {
ret = f
}
return
}
// in golang, leading +/- are unary operators;
// here, they are considered optional parts decimal numbers.
// note: strconv's base 10 parser doesnt handle leading signs.
// we therefore leave them out of our result, and just flag the negative ones.
func unary(s string) (ret string, negate bool) {
switch s[0] {
case '-':
ret = s[1:]
negate = true
case '+':
ret = s[1:]
default:
ret = s
}
return
}