Skip to content

Commit 59e89d1

Browse files
authored
feat(stdlib): Add parse function to Number module (#1517)
fix(runtime): Properly divide bigints in the number type chore(stdlib): Format runtime
1 parent 3ac27cc commit 59e89d1

File tree

6 files changed

+111
-4
lines changed

6 files changed

+111
-4
lines changed

compiler/test/stdlib/number.test.gr

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ assert Number.isNaN(Number.mul(Number.nan, Number.nan))
7777
assert Number.div(25, 5) == 5
7878
assert Number.div(9223372036854775809, 9) == 1024819115206086201
7979
assert Number.div(9223372036854775809, 9223372036854775809) == 1
80+
assert Number.div(9223372036854775808, 27670116110564327424) == 1/3
8081
assert Number.div(Number.infinity, 10) == Number.infinity
8182
assert Number.isNaN(Number.div(Number.infinity, Number.infinity))
8283
assert Number.isNaN(Number.div(Number.infinity, Number.nan))
@@ -606,6 +607,24 @@ assert Result.isErr(Number.parseInt("zzzzz", 9223372036854775807))
606607
assert Result.isErr(Number.parseInt("10", 1.23))
607608
assert Result.isErr(Number.parseInt("10", 2/3))
608609

610+
// Number.parse
611+
// These tests primarily focus on rational parsing
612+
assert Number.parse("") == Err("Invalid input")
613+
assert Number.parse("42") == Ok(42)
614+
assert Number.parse("123.45") == Ok(123.45)
615+
assert Number.parse("1/1") == Ok(1)
616+
assert Number.parse("1/3") == Ok(1/3)
617+
assert Number.parse("9/3") == Ok(3)
618+
assert Number.parse("3/9") == Ok(1/3)
619+
assert Number.parse("-3/9") == Ok(-1/3)
620+
assert Number.parse("-3/-9") == Ok(1/3)
621+
assert Number.parse("0x3/-9") == Ok(-1/3)
622+
assert Number.parse("3/-0x9") == Ok(-1/3)
623+
assert Number.parse("9223372036854775808/27_670_116_110_564_327_424") == Ok(1/3)
624+
assert Number.parse("1/2/") == Err("Invalid digit in input")
625+
assert Number.parse("1/") == Err("Invalid input")
626+
assert Number.parse("1//") == Err("Invalid digit in input")
627+
609628
// Number.sign
610629
assert Number.sign(-10000) == -1
611630
assert Number.sign(22222) == 1

stdlib/number.gr

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
} from "runtime/numbers"
2121
import Atoi from "runtime/atoi/parse"
2222
import Atof from "runtime/atof/parse"
23-
import { newFloat64, newInt64 } from "runtime/dataStructures"
23+
import { newFloat64, newInt64, allocateString } from "runtime/dataStructures"
2424
import Tags from "runtime/unsafe/tags"
2525
import Exception from "runtime/exception"
2626

@@ -904,6 +904,68 @@ export let parseInt = Atoi.parseInt
904904
*/
905905
export let parseFloat = Atof.parseFloat
906906

907+
/**
908+
* Parses a string representation of an integer, float, or rational into a `Number`.
909+
* Underscores that appear in the numeric portion of the input are ignored.
910+
*
911+
* @param input: The string to parse
912+
* @returns `Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise
913+
*
914+
* @since v0.5.5
915+
*/
916+
@unsafe
917+
export let parse = input => {
918+
match (parseInt(input, 10)) {
919+
Ok(number) => Ok(number),
920+
Err(msg) =>
921+
match (parseFloat(input)) {
922+
Ok(number) => Ok(number),
923+
Err(_) => {
924+
// Split the input on a `/` and attempt to parse a rational
925+
let (+) = WasmI32.add
926+
let (-) = WasmI32.sub
927+
let (<) = WasmI32.ltU
928+
let (==) = WasmI32.eq
929+
930+
// Search for `/`
931+
let input = WasmI32.fromGrain(input)
932+
let len = WasmI32.load(input, 4n)
933+
let mut slashIdx = -1n
934+
for (let mut i = 0n; i < len; i += 1n) {
935+
if (WasmI32.load8U(input + i, 8n) == 0x2fn) {
936+
slashIdx = i
937+
break
938+
}
939+
}
940+
941+
if (slashIdx == -1n) {
942+
Err(msg)
943+
} else {
944+
let numeratorLen = slashIdx
945+
let denominatorLen = len - slashIdx - 1n
946+
947+
let numerator = allocateString(numeratorLen)
948+
Memory.copy(numerator + 8n, input + 8n, numeratorLen)
949+
let numerator = WasmI32.toGrain(numerator): String
950+
951+
let denominator = allocateString(denominatorLen)
952+
Memory.copy(
953+
denominator + 8n,
954+
input + 8n + slashIdx + 1n,
955+
denominatorLen
956+
)
957+
let denominator = WasmI32.toGrain(denominator): String
958+
959+
match ((parseInt(numerator, 10), parseInt(denominator, 10))) {
960+
(Ok(numerator), Ok(denominator)) => Ok(numerator / denominator),
961+
(Err(msg), _) | (_, Err(msg)) => Err(msg),
962+
}
963+
}
964+
},
965+
},
966+
}
967+
}
968+
907969
/**
908970
* Computes how many times pi has to be subtracted to achieve the required bounds for sin.
909971
*/

stdlib/number.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,32 @@ Returns:
755755
|----|-----------|
756756
|`Result<Number, String>`|`Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise|
757757

758+
### Number.**parse**
759+
760+
<details disabled>
761+
<summary tabindex="-1">Added in <code>next</code></summary>
762+
No other changes yet.
763+
</details>
764+
765+
```grain
766+
parse : String -> Result<Number, String>
767+
```
768+
769+
Parses a string representation of an integer, float, or rational into a `Number`.
770+
Underscores that appear in the numeric portion of the input are ignored.
771+
772+
Parameters:
773+
774+
|param|type|description|
775+
|-----|----|-----------|
776+
|`input`|`String`|The string to parse|
777+
778+
Returns:
779+
780+
|type|description|
781+
|----|-----------|
782+
|`Result<Number, String>`|`Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise|
783+
758784
### Number.**sin**
759785

760786
<details>

stdlib/runtime/atof/common.gr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export let power = (q: WasmI32) => {
152152
let (*) = WasmI32.mul
153153
let (>>) = WasmI32.shrS
154154

155-
(q * (152_170n + 65536n) >> 16n) + 63n
155+
((q * (152_170n + 65536n)) >> 16n) + 63n
156156
}
157157

158158
@unsafe

stdlib/runtime/atof/parse.gr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ let parse8Digits = (digits: WasmI64) => {
8282
let c = (b & _MASK) * 0x000F_4240_0000_0064N
8383
let d = (b >> 16N & _MASK) * 0x0000_2710_0000_0001N
8484

85-
c + d >> 32N
85+
(c + d) >> 32N
8686
}
8787

8888
@unsafe

stdlib/runtime/numbers.gr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1426,7 +1426,7 @@ let numberTimesDivideBigIntHelp = (x, y, isDivide) => {
14261426
},
14271427
t when WasmI32.eq(t, Tags._GRAIN_BIGINT_BOXED_NUM_TAG) => {
14281428
if (isDivide) {
1429-
reducedBigInteger(BI.div(x, y))
1429+
reducedFractionBigInt(x, y)
14301430
} else {
14311431
reducedBigInteger(BI.mul(x, y))
14321432
}

0 commit comments

Comments
 (0)