Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(stdlib)!: Replace parseInt error strings with structured error enum #1755

Merged
merged 10 commits into from
Mar 4, 2024
21 changes: 9 additions & 12 deletions compiler/src/typed/oprint.re
Original file line number Diff line number Diff line change
Expand Up @@ -660,8 +660,8 @@ and print_out_sig_item = ppf =>
// | Orec_first => "type"
// | Orec_next => "and"
// };
let kwd =
switch (td.otype_type) {
let rec resolve_kwd = out_type => {
switch (out_type) {
| Otyp_record(_) => "record"
| Otyp_sum(_) => "enum"
| Otyp_variant(_, _, _, _) =>
Expand All @@ -673,8 +673,7 @@ and print_out_sig_item = ppf =>
| Otyp_abstract
| Otyp_tuple(_)
| Otyp_constr(_, _) => "type"
| Otyp_manifest(_, _) =>
failwith("NYI: Otyp_manifest pretty-printer")
| Otyp_manifest(_, original_type) => resolve_kwd(original_type)
| Otyp_object(_, _) => failwith("NYI: Otyp_object pretty-printer")
| Otyp_stuff(_) => failwith("NYI: Otyp_stuff pretty-printer")
| Otyp_var(_, _) => failwith("NYI: Otyp_var pretty-printer")
Expand All @@ -683,6 +682,10 @@ and print_out_sig_item = ppf =>
| Otyp_attribute(_, _) =>
failwith("NYI: Otyp_attribute pretty-printer")
};
};

let kwd = resolve_kwd(td.otype_type);

print_out_type_decl(kwd, ppf, td);
}
| Osig_value(vd) => {
Expand Down Expand Up @@ -735,17 +738,11 @@ and print_out_type_decl = (kwd, ppf, td) => {
)
};

let print_manifest = ppf =>
fun
| Otyp_manifest(ty, _) => fprintf(ppf, " =@ %a", out_type^, ty)
| _ => ();

let print_name_params = ppf =>
fprintf(ppf, "%s %t%a", kwd, type_defined, print_manifest, td.otype_type);
let print_name_params = ppf => fprintf(ppf, "%s %t", kwd, type_defined);

let ty =
switch (td.otype_type) {
| Otyp_manifest(_, ty) => ty
| Otyp_manifest(_, original_type) => original_type
| _ => td.otype_type
};

Expand Down
20 changes: 16 additions & 4 deletions compiler/test/stdlib/number.test.gr
Original file line number Diff line number Diff line change
Expand Up @@ -624,10 +624,22 @@ assert Result.isErr(Number.parseInt("zzzzz", 37))
assert Result.isErr(Number.parseInt("zzzzz", 9223372036854775807))
assert Result.isErr(Number.parseInt("10", 1.23))
assert Result.isErr(Number.parseInt("10", 2/3))
assert match (Number.parseInt("zzzzz", 10)) {
Err(Number.ParseIntInvalidDigit) => true,
_ => false,
}
assert match (Number.parseInt("", 10)) {
Err(Number.ParseIntEmptyString) => true,
_ => false,
}
assert match (Number.parseInt("10", 2/3)) {
Err(Number.ParseIntInvalidRadix) => true,
_ => false,
}

// Number.parse
// These tests primarily focus on rational parsing
assert Number.parse("") == Err("Invalid input")
assert Result.isErr(Number.parse(""))
assert Number.parse("42") == Ok(42)
assert Number.parse("123.45") == Ok(123.45)
assert Number.parse("1/1") == Ok(1)
Expand All @@ -639,9 +651,9 @@ assert Number.parse("-3/-9") == Ok(1/3)
assert Number.parse("0x3/-9") == Ok(-1/3)
assert Number.parse("3/-0x9") == Ok(-1/3)
assert Number.parse("9223372036854775808/27_670_116_110_564_327_424") == Ok(1/3)
assert Number.parse("1/2/") == Err("Invalid digit in input")
assert Number.parse("1/") == Err("Invalid input")
assert Number.parse("1//") == Err("Invalid digit in input")
assert Result.isErr(Number.parse("1/2/"))
assert Result.isErr(Number.parse("1/"))
assert Result.isErr(Number.parse("1//"))
phated marked this conversation as resolved.
Show resolved Hide resolved

// Number.sign
assert Number.sign(-10000) == -1
Expand Down
6 changes: 5 additions & 1 deletion stdlib/number.gr
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ from "runtime/atof/parse" include Parse as Atof
from "runtime/unsafe/tags" include Tags
from "runtime/exception" include Exception

use Atoi.{ type ParseIntError }

provide { type ParseIntError }
/**
* Pi represented as a Number value.
*
Expand Down Expand Up @@ -567,13 +570,14 @@ provide let isClose = (a, b, relativeTolerance=1e-9, absoluteTolerance=0.0) => {
*
* @param string: The string to parse
* @param radix: The number system base to use when parsing the input string
* @returns `Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise
* @returns `Ok(value)` containing the parsed number on a successful parse or `Err(err)` containing a variant of `ParseIntError`
*
* @example Number.parseInt("1", radix=10) == Ok(1)
* @example Number.parseInt("-1", radix=10) == Ok(-1)
* @example Number.parseInt("0xf0", radix=16) == Ok(0x0f0)
*
* @since v0.4.5
* @history v0.6.0: Switched from a string-based error message to a structured error enum
*/
provide let parseInt = Atoi.parseInt

Expand Down
63 changes: 56 additions & 7 deletions stdlib/number.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,47 @@ Infinity
NaN
```

## Types

Type declarations included in the Number module.

### Number.**ParseIntError**

<details disabled>
<summary tabindex="-1">Added in <code>next</code></summary>
No other changes yet.
</details>

```grain
enum ParseIntError {
ParseIntEmptyString,
ParseIntInvalidDigit,
ParseIntInvalidRadix,
}
```

Represents an error that occurred trying to parse an integer.

Variants:

```grain
ParseIntEmptyString
```

Represents an error caused by trying to parse an empty string.

```grain
ParseIntInvalidDigit
```

Represents an error caused by trying to parse a string with an invalid character.

```grain
ParseIntInvalidRadix
```

Represents an error caused by trying to parse with an invalid radix.

## Values

Functions and constants included in the Number module.
Expand Down Expand Up @@ -1091,13 +1132,21 @@ Number.isClose(4, 4.1, relativeTolerance=0.024) == false

### Number.**parseInt**

<details disabled>
<summary tabindex="-1">Added in <code>0.4.5</code></summary>
No other changes yet.
<details>
<summary>Added in <code>0.4.5</code></summary>
<table>
<thead>
<tr><th>version</th><th>changes</th></tr>
</thead>
<tbody>
<tr><td><code>next</code></td><td>Switched from a string-based error message to a structured error enum</td></tr>
</tbody>
</table>
</details>

```grain
parseInt : (string: String, radix: Number) => Result<Number, String>
parseInt :
(string: String, radix: Number) => Result<Number, Atoi.ParseIntError>
```

Parses a string representation of an integer into a `Number` using the
Expand All @@ -1119,7 +1168,7 @@ Returns:

|type|description|
|----|-----------|
|`Result<Number, String>`|`Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise|
|`Result<Number, Atoi.ParseIntError>`|`Ok(value)` containing the parsed number on a successful parse or `Err(err)` containing a variant of `ParseIntError`|
phated marked this conversation as resolved.
Show resolved Hide resolved

Examples:

Expand Down Expand Up @@ -1183,7 +1232,7 @@ No other changes yet.
</details>

```grain
parse : (input: String) => Result<Number, String>
parse : (input: String) => Result<Number, Atoi.ParseIntError>
```

Parses a string representation of an integer, float, or rational into a `Number`.
Expand All @@ -1199,7 +1248,7 @@ Returns:

|type|description|
|----|-----------|
|`Result<Number, String>`|`Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise|
|`Result<Number, Atoi.ParseIntError>`|`Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise|

Examples:

Expand Down
30 changes: 25 additions & 5 deletions stdlib/runtime/atoi/parse.gr
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ from "runtime/bigint" include Bigint as BI
from "runtime/numbers" include Numbers
use Numbers.{ reducedInteger }

/**
* Represents an error that occurred trying to parse an integer.
*
* @since v0.6.0
*/
provide enum ParseIntError {
/**
* Represents an error caused by trying to parse an empty string.
*/
ParseIntEmptyString,
/**
* Represents an error caused by trying to parse a string with an invalid character.
*/
ParseIntInvalidDigit,
/**
* Represents an error caused by trying to parse with an invalid radix.
*/
ParseIntInvalidRadix,
}

primitive (&&) = "@and"
primitive (||) = "@or"

Expand Down Expand Up @@ -58,11 +78,11 @@ provide let parseInt = (string: String, radix: Number) => {
radix < WasmI32.fromGrain(2) ||
radix > WasmI32.fromGrain(36)
) {
return Err("Radix must be an integer between 2 and 36")
return Err(ParseIntInvalidRadix)
}

if (WasmI32.eqz(strLen)) {
return Err("Invalid input")
return Err(ParseIntEmptyString)
}

let mut char = WasmI32.load8U(offset, 0n)
Expand Down Expand Up @@ -123,12 +143,12 @@ provide let parseInt = (string: String, radix: Number) => {
c when c - _CHAR_A < 26n => digit = char - _CHAR_A + 10n,
c when c - _CHAR_a < 26n => digit = char - _CHAR_a + 10n,
_ => {
return Err("Invalid digit in input")
return Err(ParseIntInvalidDigit)
},
}

if (digit >= WasmI32.wrapI64(radix)) {
return Err("Invalid digit in input")
return Err(ParseIntInvalidDigit)
}

let digit = WasmI64.extendI32U(digit)
Expand Down Expand Up @@ -174,7 +194,7 @@ provide let parseInt = (string: String, radix: Number) => {
}
use WasmI64.{ (*) }
// TODO: Verify this is suitable for handling "_"
if (WasmI32.eqz(sawDigit)) return Err("Invalid digit in input")
if (WasmI32.eqz(sawDigit)) return Err(ParseIntInvalidDigit)

if (WasmI32.eqz(isBigInt)) {
let value = if (negative) value else value * -1N
Expand Down
43 changes: 42 additions & 1 deletion stdlib/runtime/atoi/parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,54 @@
title: Parse
---

## Types

Type declarations included in the Parse module.

### Parse.**ParseIntError**

<details disabled>
<summary tabindex="-1">Added in <code>next</code></summary>
No other changes yet.
</details>

```grain
enum ParseIntError {
ParseIntEmptyString,
ParseIntInvalidDigit,
ParseIntInvalidRadix,
}
```

Represents an error that occurred trying to parse an integer.

Variants:

```grain
ParseIntEmptyString
```

Represents an error caused by trying to parse an empty string.

```grain
ParseIntInvalidDigit
```

Represents an error caused by trying to parse a string with an invalid character.

```grain
ParseIntInvalidRadix
```

Represents an error caused by trying to parse with an invalid radix.

## Values

Functions and constants included in the Parse module.

### Parse.**parseInt**

```grain
parseInt : (string: String, radix: Number) => Result<Number, String>
parseInt : (string: String, radix: Number) => Result<Number, ParseIntError>
```

Loading