Skip to content

Commit 0894dc5

Browse files
authored
feat(stdlib): Add Exception.toString (#2143)
1 parent f97c011 commit 0894dc5

File tree

12 files changed

+237
-118
lines changed

12 files changed

+237
-118
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module ExceptionTest
2+
3+
from "exception" include Exception
4+
5+
exception Test1
6+
exception Test2(String)
7+
// Exception.toString
8+
assert Exception.toString(Failure("Test")) == "Failure: Test"
9+
assert Exception.toString(Test1) == "Test1"
10+
assert Exception.toString(Test2("Test")) == "Test2(\"Test\")"
11+
12+
// Exception.registerPrinter
13+
let printer = e => {
14+
match (e) {
15+
Test1 => Some("Test1: This is a test"),
16+
Test2(s) => Some("Test2"),
17+
_ => None,
18+
}
19+
}
20+
Exception.registerPrinter(printer)
21+
assert Exception.toString(Test1) == "Test1: This is a test"
22+
assert Exception.toString(Test2("Test")) == "Test2"

compiler/test/suites/basic_functionality.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,6 @@ describe("basic functionality", ({test, testSkip}) => {
377377
~config_fn=smallestFileConfig,
378378
"smallest_grain_program",
379379
"",
380-
5165,
380+
4750,
381381
);
382382
});

compiler/test/suites/stdlib.re

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ describe("stdlib", ({test, testSkip}) => {
7777
assertStdlib("bytes.test");
7878
assertStdlib("buffer.test");
7979
assertStdlib("char.test");
80+
assertStdlib("exception.test");
8081
assertStdlib("float32.test");
8182
assertStdlib("float64.test");
8283
assertStdlib("hash.test");

stdlib/exception.gr

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ from "runtime/exception" include Exception
3939
*
4040
* @since v0.3.0
4141
*/
42-
@disableGC
43-
provide let rec registerPrinter = (printer: Exception => Option<String>) => {
44-
// This function _must_ be @disableGC because the printer list uses
45-
// unsafe types. Not really a memory leak as this list is never collected
42+
provide let registerPrinter = Exception.registerPrinter
4643

47-
// no need to increment refcount on f; we just don't decRef it at the end of the function
48-
Exception.printers = WasmI32.fromGrain((printer, Exception.printers))
49-
Memory.decRef(WasmI32.fromGrain(registerPrinter))
50-
void
51-
}
44+
/**
45+
* Gets the string representation of the given exception.
46+
*
47+
* @param e: The exception to stringify
48+
*
49+
* @returns The string representation of the exception
50+
*
51+
* @since v0.7.0
52+
*/
53+
provide let toString = Exception.toString

stdlib/exception.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,28 @@ Exception.registerPrinter(e => {
6565
throw ExampleError(1) // Error found on line: 1
6666
```
6767

68+
### Exception.**toString**
69+
70+
<details disabled>
71+
<summary tabindex="-1">Added in <code>next</code></summary>
72+
No other changes yet.
73+
</details>
74+
75+
```grain
76+
toString : (e: Exception) => String
77+
```
78+
79+
Gets the string representation of the given exception.
80+
81+
Parameters:
82+
83+
|param|type|description|
84+
|-----|----|-----------|
85+
|`e`|`Exception`|The exception to stringify|
86+
87+
Returns:
88+
89+
|type|description|
90+
|----|-----------|
91+
|`String`|The string representation of the exception|
92+

stdlib/pervasives.gr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ provide primitive unbox = "@unbox"
236236
primitive elideTypeInfo = "@meta.elide_type_info"
237237
@unsafe
238238
let setupExceptions = () => {
239-
Exception.dangerouslyRegisterPrinter(e => {
239+
Exception.registerPrinter(e => {
240240
match (e) {
241241
Failure(msg) => Some("Failure: " ++ msg),
242242
InvalidArgument(msg) => Some("Invalid argument: " ++ msg),
@@ -247,7 +247,7 @@ let setupExceptions = () => {
247247
// If type information is elided, remove dependency on toString as
248248
// it will have no effect on exceptions
249249
if (!elideTypeInfo) {
250-
Exception.dangerouslyRegisterBasePrinter(e => Some(toString(e)))
250+
Exception.registerBasePrinter(e => toString(e))
251251
}
252252
}
253253

stdlib/runtime/exception.gr

Lines changed: 60 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,74 @@
1-
@runtimeMode
1+
@noPervasives
22
module Exception
33

4-
from "runtime/unsafe/wasmi32" include WasmI32
5-
use WasmI32.{ (==), (+), (-) }
6-
7-
foreign wasm fd_write:
8-
(WasmI32, WasmI32, WasmI32, WasmI32) => WasmI32 from "wasi_snapshot_preview1"
9-
10-
primitive unreachable = "@unreachable"
11-
12-
provide let mut printers = 0n
13-
14-
// These functions are dangerous because they leak runtime memory and perform
15-
// no GC operations. As such, they should only be called by this module and/or
16-
// modules that understand these restrictions, namely Pervasives.
17-
18-
provide let dangerouslyRegisterBasePrinter = f => {
19-
let mut current = printers
20-
while (true) {
21-
// There will be at least one printer registered by the time this is called
22-
let (_, next) = WasmI32.toGrain(current):
23-
(Exception => Option<String>, WasmI32)
24-
if (next == 0n) {
25-
// Using a tuple in runtime mode is typically disallowed as there is no way
26-
// to reclaim the memory, but this function is only called once
27-
let newBase = (WasmI32.fromGrain(f), 0n)
28-
WasmI32.store(current, WasmI32.fromGrain(newBase), 12n)
29-
break
30-
}
31-
current = next
32-
}
33-
// We don't decRef the closure or arguments here to avoid a cyclic dep. on Memory.
34-
// This is fine, as this function should only be called once.
35-
void
36-
}
37-
38-
provide let dangerouslyRegisterPrinter = f => {
39-
printers = WasmI32.fromGrain((f, printers))
40-
// We don't decRef the closure or arguments here to avoid a cyclic dep. on Memory.
41-
// This is fine, as this function is only called seldomly.
42-
void
43-
}
44-
45-
// avoid cirular dependency on gc
46-
let incRef = v => {
47-
let ptr = WasmI32.fromGrain(v) - 8n
48-
WasmI32.store(ptr, WasmI32.load(ptr, 0n) + 1n, 0n)
49-
v
50-
}
4+
from "runtime/unsafe/panic" include Panic
515

526
let _GENERIC_EXCEPTION_NAME = "GrainException"
53-
54-
let exceptionToString = (e: Exception) => {
55-
let mut result = _GENERIC_EXCEPTION_NAME
56-
let mut current = printers
57-
while (true) {
58-
if (current == 0n) return result
59-
let (printer, next) = WasmI32.toGrain(current):
60-
(Exception => Option<String>, WasmI32)
61-
// as GC is not available, manually increment the references
62-
match (incRef(printer)(incRef(e))) {
63-
Some(str) => return str,
64-
None => {
65-
current = next
7+
let mut basePrinter = None
8+
let mut printers = []
9+
10+
/**
11+
* Registers a base exception printer. If no other exception printers are
12+
* registered, the base printer is used to convert an exception to a string.
13+
*
14+
* @param printer: The base exception printer to register
15+
*
16+
* @since v0.7.0
17+
*/
18+
provide let registerBasePrinter = (printer: Exception => String) =>
19+
basePrinter = Some(printer)
20+
21+
/**
22+
* Registers an exception printer. When an exception is thrown, all registered
23+
* printers are called in order from the most recently registered printer to
24+
* the least recently registered printer. The first `Some` value returned is
25+
* used as the exception's string value.
26+
*
27+
* @param printer: The exception printer to register
28+
*
29+
* @since v0.7.0
30+
*/
31+
provide let registerPrinter = (printer: Exception => Option<String>) =>
32+
printers = [printer, ...printers]
33+
34+
/**
35+
* Gets the string representation of the given exception.
36+
*
37+
* @param e: The exception to stringify
38+
*
39+
* @returns The string representation of the exception
40+
*
41+
* @since v0.7.0
42+
*/
43+
provide let toString = (e: Exception) => {
44+
let rec exceptionToString = (e, printers) => {
45+
match (printers) {
46+
[] => match (basePrinter) {
47+
Some(f) => f(e),
48+
None => _GENERIC_EXCEPTION_NAME,
49+
},
50+
[printer, ...rest] => {
51+
match (printer(e)) {
52+
Some(s) => s,
53+
None => exceptionToString(e, rest),
54+
}
6655
},
6756
}
6857
}
69-
return result
70-
}
71-
72-
// HACK: Allocate static buffer for printing (40 bytes)
73-
// Would be nice to have a better way to allocate a static block from
74-
// the runtime heap, but this is the only module that needs to do it
75-
let iov = WasmI32.fromGrain([> 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n])
76-
77-
provide let panic = (msg: String) => {
78-
let ptr = WasmI32.fromGrain(msg)
79-
let written = iov + 32n
80-
let lf = iov + 36n
81-
WasmI32.store(iov, ptr + 8n, 0n)
82-
WasmI32.store(iov, WasmI32.load(ptr, 4n), 4n)
83-
WasmI32.store8(lf, 10n, 0n)
84-
WasmI32.store(iov, lf, 8n)
85-
WasmI32.store(iov, 1n, 12n)
86-
fd_write(2n, iov, 2n, written)
87-
unreachable()
58+
exceptionToString(e, printers)
8859
}
8960

61+
/**
62+
* Throws an uncatchable exception and traps.
63+
*
64+
* @param e: The exception to throw
65+
*/
9066
provide let panicWithException = (e: Exception) => {
91-
panic(exceptionToString(e))
67+
Panic.panic(toString(e))
9268
}
9369

70+
// Runtime exceptions
71+
9472
provide exception DivisionByZero
9573
provide exception ModuloByZero
9674
provide exception Overflow
@@ -126,4 +104,4 @@ let runtimeErrorPrinter = e => {
126104
}
127105
}
128106

129-
dangerouslyRegisterPrinter(runtimeErrorPrinter)
107+
registerPrinter(runtimeErrorPrinter)

stdlib/runtime/exception.md

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,84 @@ title: Exception
66

77
Functions and constants included in the Exception module.
88

9-
### Exception.**printers**
9+
### Exception.**registerBasePrinter**
10+
11+
<details disabled>
12+
<summary tabindex="-1">Added in <code>next</code></summary>
13+
No other changes yet.
14+
</details>
1015

1116
```grain
12-
printers : WasmI32
17+
registerBasePrinter : (printer: (Exception => String)) => Void
1318
```
1419

15-
### Exception.**dangerouslyRegisterBasePrinter**
20+
Registers a base exception printer. If no other exception printers are
21+
registered, the base printer is used to convert an exception to a string.
1622

17-
```grain
18-
dangerouslyRegisterBasePrinter : (f: a) => Void
19-
```
23+
Parameters:
24+
25+
|param|type|description|
26+
|-----|----|-----------|
27+
|`printer`|`Exception => String`|The base exception printer to register|
28+
29+
### Exception.**registerPrinter**
2030

21-
### Exception.**dangerouslyRegisterPrinter**
31+
<details disabled>
32+
<summary tabindex="-1">Added in <code>next</code></summary>
33+
No other changes yet.
34+
</details>
2235

2336
```grain
24-
dangerouslyRegisterPrinter : (f: a) => Void
37+
registerPrinter : (printer: (Exception => Option<String>)) => Void
2538
```
2639

27-
### Exception.**panic**
40+
Registers an exception printer. When an exception is thrown, all registered
41+
printers are called in order from the most recently registered printer to
42+
the least recently registered printer. The first `Some` value returned is
43+
used as the exception's string value.
44+
45+
Parameters:
46+
47+
|param|type|description|
48+
|-----|----|-----------|
49+
|`printer`|`Exception => Option<String>`|The exception printer to register|
50+
51+
### Exception.**toString**
52+
53+
<details disabled>
54+
<summary tabindex="-1">Added in <code>next</code></summary>
55+
No other changes yet.
56+
</details>
2857

2958
```grain
30-
panic : (msg: String) => a
59+
toString : (e: Exception) => String
3160
```
3261

62+
Gets the string representation of the given exception.
63+
64+
Parameters:
65+
66+
|param|type|description|
67+
|-----|----|-----------|
68+
|`e`|`Exception`|The exception to stringify|
69+
70+
Returns:
71+
72+
|type|description|
73+
|----|-----------|
74+
|`String`|The string representation of the exception|
75+
3376
### Exception.**panicWithException**
3477

3578
```grain
3679
panicWithException : (e: Exception) => a
3780
```
3881

82+
Throws an uncatchable exception and traps.
83+
84+
Parameters:
85+
86+
|param|type|description|
87+
|-----|----|-----------|
88+
|`e`|`Exception`|The exception to throw|
89+

0 commit comments

Comments
 (0)