From ff7ff11b268161165ae0850a0eb7fe0285cc0ef8 Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Wed, 7 Aug 2024 09:38:36 -0300 Subject: [PATCH 1/4] Update builtins to use io result --- src/fun/builtins.bend | 27 +++++++++++++++++++-------- tests/golden_tests/io/load.bend | 8 ++++---- tests/golden_tests/io/load_fail.bend | 10 +++++----- tests/golden_tests/io/store.bend | 8 ++++---- tests/golden_tests/io/store_fail.bend | 8 ++++---- 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/fun/builtins.bend b/src/fun/builtins.bend index 89c04fd15..fa1048059 100644 --- a/src/fun/builtins.bend +++ b/src/fun/builtins.bend @@ -159,6 +159,11 @@ type IO: Done { magic, expr } Call { magic, func, argm, cont } +type IOError: + Type + Name + Inner { value } + def IO/MAGIC: return (0xD0CA11, 0xFF1FF1) @@ -176,6 +181,12 @@ def IO/bind(a, b): def call(func, argm): return IO/Call(IO/MAGIC, func, argm, lambda x: IO/Done(IO/MAGIC, x)) +IO/done_on_err (IO/Call magic func argm cont) = (IO/Call magic func argm @res match res { + Result/Ok: (cont res.val) + Result/Err: (IO/Done IO/MAGIC (Result/Err res.val)) +}) +IO/done_on_err done = done + ## Time and sleep # Returns a monotonically increasing nanosecond timestamp as an u48 encoded as a pair of u24s. IO/get_time = (IO/Call IO/MAGIC "GET_TIME" * @x (IO/Done IO/MAGIC x)) @@ -230,9 +241,9 @@ IO/FS/SEEK_END = +2 # Reads an entire file, returning a list of bytes. def IO/FS/read_file(path): with IO: - fd <- IO/FS/open(path, "r") + fd <- IO/done_on_err(IO/FS/open(path, "r")) bytes <- IO/FS/read_to_end(fd) - * <- IO/FS/close(fd) + * <- IO/done_on_err(IO/FS/close(fd)) return wrap(bytes) # IO/FS/read_to_end(fd: u24) -> (IO (List u24)) @@ -243,7 +254,7 @@ def IO/FS/read_to_end(fd): def IO/FS/read_to_end.read_chunks(fd, chunks): with IO: # Read file in 1MB chunks - chunk <- IO/FS/read(fd, 1048576) + chunk <- IO/done_on_err(IO/FS/read(fd, 1048576)) match chunk: case List/Nil: return wrap(List/flatten(chunks)) @@ -258,7 +269,7 @@ def IO/FS/read_line(fd): def IO/FS/read_line.read_chunks(fd, chunks): with IO: # Read line in 1kB chunks - chunk <- IO/FS/read(fd, 1024) + chunk <- IO/done_on_err(IO/FS/read(fd, 1024)) match res = List/split_once(chunk, '\n'): case Result/Ok: (line, rest) = res.val @@ -276,9 +287,9 @@ def IO/FS/read_line.read_chunks(fd, chunks): # Writes a list of bytes to a file given by a path. def IO/FS/write_file(path, bytes): with IO: - f <- IO/FS/open(path, "w") - * <- IO/FS/write(f, bytes) - * <- IO/FS/close(f) + f <- IO/done_on_err(IO/FS/open(path, "w")) + * <- IO/done_on_err(IO/FS/write(f, bytes)) + * <- IO/done_on_err(IO/FS/close(f)) return wrap(bytes) ### Standard input and output utilities @@ -294,7 +305,7 @@ IO/input = (IO/input.go DiffList/new) def IO/input.go(acc): # TODO: This is slow and inefficient, should be done in hvm using fgets. with IO: - byte <- IO/FS/read(IO/FS/STDIN, 1) + byte <- IO/done_on_err(IO/FS/read(IO/FS/STDIN, 1)) match byte: case List/Nil: # Nothing read, try again (to simulate blocking a read) diff --git a/tests/golden_tests/io/load.bend b/tests/golden_tests/io/load.bend index 852673036..efc8e016a 100644 --- a/tests/golden_tests/io/load.bend +++ b/tests/golden_tests/io/load.bend @@ -1,6 +1,6 @@ (Main) = use path = "tests/golden_tests/io/load.txt" - (HVM.load path @result match result { - Result/ok: result.val; - Result/err: result.val; - }) + with IO { + ask file = (IO/FS/read_file path) + (String/decode_utf8 file) + } diff --git a/tests/golden_tests/io/load_fail.bend b/tests/golden_tests/io/load_fail.bend index 3208e90fa..d43fccdd8 100644 --- a/tests/golden_tests/io/load_fail.bend +++ b/tests/golden_tests/io/load_fail.bend @@ -1,6 +1,6 @@ (Main) = - use path = "tests/golden_tests/io/load_fail.txt" - (HVM.load path @result match result { - Result/ok: result.val; - Result/err: result.val; - }) + use path = "tests/golden_tests/io/missing_dir/load_fail.txt" + with IO { + ask file = (IO/FS/read_file path) + (String/decode_utf8 file) + } diff --git a/tests/golden_tests/io/store.bend b/tests/golden_tests/io/store.bend index 72b856823..ca55eb508 100644 --- a/tests/golden_tests/io/store.bend +++ b/tests/golden_tests/io/store.bend @@ -1,6 +1,6 @@ (Main) = use path = "tests/golden_tests/io/store.txt" - (HVM.store path "(Main) = 0" @result match result { - Result/ok: result.val; - Result/err: result.val; - }) + with IO { + ask res = (IO/FS/write_file path (String/encode_utf8 "(Main) = 0")) + res + } diff --git a/tests/golden_tests/io/store_fail.bend b/tests/golden_tests/io/store_fail.bend index 130b32e96..ef5b3086e 100644 --- a/tests/golden_tests/io/store_fail.bend +++ b/tests/golden_tests/io/store_fail.bend @@ -1,6 +1,6 @@ (Main) = use path = "tests/golden_tests/io/missing_dir/store_fail.txt" - (HVM.store path "(Main) = 0" @result match result { - Result/ok: result.val; - Result/err: result.val; - }) + with IO { + ask res = (IO/FS/write_file path (String/encode_utf8 "(Main) = 0")) + res + } From 7280819bf34e0381091c28654cab6c84d0cc4f34 Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Wed, 7 Aug 2024 10:45:08 -0300 Subject: [PATCH 2/4] Use unwrap in some io functions --- docs/builtins.md | 44 ++++++++++++++++++++++++++++++------------- src/fun/builtins.bend | 19 ++++++++++++++++--- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/docs/builtins.md b/docs/builtins.md index b5be28ce2..01e534122 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -118,6 +118,22 @@ Splits a list into two lists at the first occurrence of a value. List/split_once(xs: List(T), val: T) -> (Result(List(T), List(T))) ``` +## Result + +```python +type Result: + Ok { val: A } + Err { val: B } +``` + +### Result/unwrap + +Returns the inner value of `Result/Ok` or `Result/Err`. + +```python +def Result/unwrap(result: Result): A || B +``` + ## Tree ```python @@ -527,21 +543,23 @@ def IO/DyLib/open(path: String, lazy: u24) -> u24 ``` Loads a dynamic library file. -* `path` is the path to the library file. -* `lazy` is a boolean encoded as a `u24` that determines if all functions are loaded lazily (`1`) or upfront (`0`). -* Returns an unique id to the library object encoded as a `u24`. + +- `path` is the path to the library file. +- `lazy` is a boolean encoded as a `u24` that determines if all functions are loaded lazily (`1`) or upfront (`0`). +- Returns an unique id to the library object encoded as a `u24`. #### IO/DyLib/call -``` py +```py def IO/DyLib/call(dl: u24, fn: String, args: Any) -> Any ``` Calls a function of a previously opened library. -* `dl` is the id of the library object. -* `fn` is the name of the function in the library. -* `args` are the arguments to the function. The expected values depend on the called function. -* The returned value is determined by the called function. + +- `dl` is the id of the library object. +- `fn` is the name of the function in the library. +- `args` are the arguments to the function. The expected values depend on the called function. +- The returned value is determined by the called function. #### IO/DyLib/close @@ -550,8 +568,9 @@ def IO/DyLib/close(dl: u24) -> None ``` Closes a previously open library. -* `dl` is the id of the library object. -* Returns nothing (`*`). + +- `dl` is the id of the library object. +- Returns nothing (`*`). ## Native number casting @@ -585,7 +604,7 @@ Casts any native number to an i24. ```py def String/decode_utf8(bytes: [u24]) -> String -``` +``` Decodes a sequence of bytes to a String using utf-8 encoding. @@ -647,7 +666,6 @@ Computes the arctangent of `y / x`. Has the same behaviour as `atan2f` in the C math lib. - ### Math/PI Defines the Pi constant. @@ -781,4 +799,4 @@ def Math/round(n: f24) -> f24 You can force a function call to be evaluated lazily by wrapping it in a lazy thunk. In Bend, this can be expressed as `lambda x: x(my_function, arg1, arg2, ...)`. -To evaluate the thunk, you can use the `undefer` function or apply `lambda x: x` to it. \ No newline at end of file +To evaluate the thunk, you can use the `undefer` function or apply `lambda x: x` to it. diff --git a/src/fun/builtins.bend b/src/fun/builtins.bend index fa1048059..c7fef4c78 100644 --- a/src/fun/builtins.bend +++ b/src/fun/builtins.bend @@ -92,6 +92,11 @@ type Nat = (Succ ~pred) | (Zero) type Result = (Ok val) | (Err val) +Result/unwrap res = match res { + Result/Ok: res.val; + Result/Err: res.val; +} + type Tree: Node { ~left, ~right } Leaf { value } @@ -189,7 +194,10 @@ IO/done_on_err done = done ## Time and sleep # Returns a monotonically increasing nanosecond timestamp as an u48 encoded as a pair of u24s. -IO/get_time = (IO/Call IO/MAGIC "GET_TIME" * @x (IO/Done IO/MAGIC x)) +IO/get_time = with IO { + ask res = (IO/Call IO/MAGIC "GET_TIME" * @x (IO/Done IO/MAGIC x)) + (wrap (Result/unwrap res)) + } # Sleeps for the given number of nanoseconds, given by an u48 encoded as a pair of u24s. IO/nanosleep hi_lo = (IO/Call IO/MAGIC "SLEEP" hi_lo @x (IO/Done IO/MAGIC x)) @@ -199,7 +207,9 @@ def IO/sleep(seconds): nanos = seconds * 1_000_000_000.0 lo = to_u24(nanos % 0x1_000_000.0) hi = to_u24(nanos / 0x1_000_000.0) - return IO/nanosleep((hi, lo)) + with IO: + res <- IO/nanosleep((hi, lo)) + return wrap(Result/unwrap(res)) ## File IO @@ -296,7 +306,10 @@ def IO/FS/write_file(path, bytes): # IO/print(text: String) -> (IO *) # Prints a string to stdout, encoding it with utf-8. -IO/print text = (IO/FS/write IO/FS/STDOUT (String/encode_utf8 text)) +IO/print text = with IO { + ask res = (IO/FS/write IO/FS/STDOUT (String/encode_utf8 text)) + (wrap (Result/unwrap res)) + } # IO/input() -> IO String # Read characters from stdin until a newline is found. From 7ef8ff906084ccf65feb7a93c80eab4d2592ddb1 Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Wed, 7 Aug 2024 10:52:03 -0300 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d57b3307..6799e5317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project does not currently adhere to a particular versioning scheme. - Improve error messages in branching statements. ([#464][gh-464]) - Change branches to support ending with ask statements. ([#629][gh-629]) - Improve hexadecimal and binary floating numbers. ([#648][gh-648]) +- Change IO functions to return Result. ([#657][gh-657]) ## [0.2.36] - 2024-07-04 @@ -417,5 +418,6 @@ and this project does not currently adhere to a particular versioning scheme. [gh-642]: https://github.com/HigherOrderCO/Bend/issues/642 [gh-643]: https://github.com/HigherOrderCO/Bend/issues/643 [gh-648]: https://github.com/HigherOrderCO/Bend/issues/648 +[gh-657]: https://github.com/HigherOrderCO/Bend/issues/657 [gh-659]: https://github.com/HigherOrderCO/Bend/pull/659 [Unreleased]: https://github.com/HigherOrderCO/Bend/compare/0.2.36...HEAD From 2a1f0774d753f5df309bb4a46a4ce83269b92742 Mon Sep 17 00:00:00 2001 From: imaqtkatt <135721694+imaqtkatt@users.noreply.github.com> Date: Wed, 7 Aug 2024 12:47:54 -0300 Subject: [PATCH 4/4] Improve Result/unwrap docs Co-authored-by: Nicolas Abril --- docs/builtins.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/builtins.md b/docs/builtins.md index 01e534122..fc4733411 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -130,6 +130,8 @@ type Result: Returns the inner value of `Result/Ok` or `Result/Err`. +If the types `A` and `B` are different, should only be used in type unsafe programs or when only one variant is guaranteed to happen. + ```python def Result/unwrap(result: Result): A || B ```