Skip to content

Commit

Permalink
chore(stdlib)!: Replace parseInt error strings with structured erro…
Browse files Browse the repository at this point in the history
…r enum (#1755)

Co-authored-by: Blaine Bublitz <blaine.bublitz@gmail.com>
  • Loading branch information
spotandjake and phated authored Mar 4, 2024
1 parent 5a6e4c6 commit ea26d18
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 30 deletions.
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//"))

// 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`|

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>
```

0 comments on commit ea26d18

Please sign in to comment.