-
-
Notifications
You must be signed in to change notification settings - Fork 115
/
parse.gr
212 lines (183 loc) 路 5.45 KB
/
parse.gr
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
/* grainc-flags --no-pervasives */
module Parse
include "runtime/unsafe/wasmi32"
from WasmI32 use {
(+),
(-),
gtU as (>),
geU as (>=),
ltU as (<),
(>>),
(==),
(!=),
(&),
}
include "runtime/unsafe/wasmi64"
include "runtime/unsafe/memory"
include "runtime/unsafe/tags"
include "runtime/bigint" as BI
include "runtime/numbers"
from Numbers use { reducedInteger }
/**
* Represents an error caused by trying to parse an empty string.
*
* @since v0.6.0
*/
provide exception PraseIntEmptyString
/**
* Represents an error caused by trying to parse a string with an invalid character.
*
* @since v0.6.0
*/
provide exception PraseIntInvalidDigit
/**
* Represents an error caused by trying to parse with an invalid radix.
*
* @since v0.6.0
*/
provide exception ParseIntInvalidRadix
primitive (&&) = "@and"
primitive (||) = "@or"
@unsafe
provide let parseInt = (string: String, radix: Number) => {
from WasmI64 use { (+) as addWasmI64 }
let _CHAR_0 = 0x30n
let _CHAR_B = 0x42n
let _CHAR_b = 0x62n
let _CHAR_O = 0x4fn
let _CHAR_o = 0x6fn
let _CHAR_X = 0x58n
let _CHAR_x = 0x78n
let _CHAR_A = 0x41n
let _CHAR_a = 0x61n
let _CHAR_UNDERSCORE = 0x5fn
let _CHAR_MINUS = 0x2dn
let _INT_MIN = -9223372036854775808N
// Don't need to process Unicode length since if the string
// contains non-ascii characters, it's not a valid integer
let strLen = WasmI32.load(WasmI32.fromGrain(string), 4n)
// Our pointer within the string we're parsing, offset by the
// header
let mut offset = WasmI32.fromGrain(string) + 8n
let strEnd = offset + strLen
let radix = WasmI32.fromGrain(radix)
if (
WasmI32.eqz(radix & Tags._GRAIN_NUMBER_TAG_MASK) ||
radix < WasmI32.fromGrain(2) ||
radix > WasmI32.fromGrain(36)
) {
return Err(ParseIntInvalidRadix)
}
if (WasmI32.eqz(strLen)) {
return Err(PraseIntEmptyString)
}
let mut char = WasmI32.load8U(offset, 0n)
let mut limit = addWasmI64(_INT_MIN, 1N)
// Check for a sign
let mut negative = false
if (char == _CHAR_MINUS) {
negative = true
offset += 1n
limit = _INT_MIN
char = WasmI32.load8U(offset, 0n)
}
let mut radix = WasmI64.extendI32S(radix >> 1n)
// Check if we should override the supplied radix
if (char == _CHAR_0 && strLen > 2n) {
match (WasmI32.load8U(offset, 1n)) {
c when c == _CHAR_B || c == _CHAR_b => {
radix = 2N
offset += 2n
},
c when c == _CHAR_O || c == _CHAR_o => {
radix = 8N
offset += 2n
},
c when c == _CHAR_X || c == _CHAR_x => {
radix = 16N
offset += 2n
},
_ => void,
}
}
// We try to avoid allocating a BigInt if it's not needed
let mut value = 0N
let mut radixBigInt = 0n
let mut valueBigInt = 0n
let mut isBigInt = 0n
let mut sawDigit = 0n
for (let mut i = offset; i < strEnd; i += 1n) {
let char = WasmI32.load8U(i, 0n)
// Ignore underscore characters
if (char == _CHAR_UNDERSCORE) {
continue
}
sawDigit = 1n
let mut digit = 0n
match (char) {
c when c - _CHAR_0 < 10n => digit = char - _CHAR_0,
c when c - _CHAR_A < 26n => digit = char - _CHAR_A + 10n,
c when c - _CHAR_a < 26n => digit = char - _CHAR_a + 10n,
_ => {
return Err(PraseIntInvalidDigit)
},
}
if (digit >= WasmI32.wrapI64(radix)) {
return Err(PraseIntInvalidDigit)
}
let digit = WasmI64.extendI32U(digit)
if (WasmI32.eqz(isBigInt)) {
from WasmI64 use { (+) }
from WasmI64 use { (*), (<) }
let prevValue = value
value *= radix
// Check for overflow
// 64-bit int min + 1
if (value < limit + digit) {
// we overflowed. allocate BigInt and use instead
isBigInt = 1n
valueBigInt = BI.makeWrappedUint64(prevValue * -1N)
radixBigInt = BI.makeWrappedUint64(radix)
let newvalue = BI.mul(valueBigInt, radixBigInt)
Memory.decRef(valueBigInt)
valueBigInt = newvalue
let newvalue = BI.addInt(valueBigInt, digit)
Memory.decRef(valueBigInt)
valueBigInt = newvalue
} else {
from WasmI64 use { (-) }
// To quote the OpenJDK,
// "Accumulating negatively avoids surprises near MAX_VALUE"
// The minimum value of a 64-bit integer (-9223372036854775808) can't be
// represented as a positive number because it would be larger than the
// maximum 64-bit integer (9223372036854775807), so we'd be unable to
// parse negatives as positives and multiply by the sign at the end.
// Instead, we represent all positive numbers as negative numbers since
// we have one unit more headroom.
value -= digit
}
} else {
let newvalue = BI.mul(valueBigInt, radixBigInt)
Memory.decRef(valueBigInt)
valueBigInt = newvalue
let newvalue = BI.addInt(valueBigInt, digit)
Memory.decRef(valueBigInt)
valueBigInt = newvalue
}
}
from WasmI64 use { (*) }
// TODO: Verify this is suitable for handling "_"
if (WasmI32.eqz(sawDigit)) return Err(PraseIntInvalidDigit)
if (WasmI32.eqz(isBigInt)) {
let value = if (negative) value else value * -1N
let number = WasmI32.toGrain(Memory.incRef(reducedInteger(value))): Number
return Ok(number)
}
// BigInt number is accumulated in positive form
if (negative) {
let newvalue = BI.negate(valueBigInt)
Memory.decRef(valueBigInt)
return Ok(WasmI32.toGrain(newvalue))
}
return Ok(WasmI32.toGrain(valueBigInt))
}