Skip to content

Commit

Permalink
Merge pull request #661 from HigherOrderCO/657-make-io-functions-retu…
Browse files Browse the repository at this point in the history
…rn-result

#657 Make IO functions return Result
  • Loading branch information
imaqtkatt authored Aug 7, 2024
2 parents 270f39c + 2a1f077 commit dc14ed2
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 41 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
46 changes: 33 additions & 13 deletions docs/builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ 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<A, B>:
Ok { val: A }
Err { val: B }
```

### Result/unwrap

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>): A || B
```

## Tree

```python
Expand Down Expand Up @@ -527,21 +545,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

Expand All @@ -550,8 +570,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

Expand Down Expand Up @@ -585,7 +606,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.

Expand Down Expand Up @@ -647,7 +668,6 @@ Computes the arctangent of `y / x`.

Has the same behaviour as `atan2f` in the C math lib.


### Math/PI

Defines the Pi constant.
Expand Down Expand Up @@ -781,4 +801,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.
To evaluate the thunk, you can use the `undefer` function or apply `lambda x: x` to it.
46 changes: 35 additions & 11 deletions src/fun/builtins.bend
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,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 }
Expand Down Expand Up @@ -185,6 +190,11 @@ type IO:
Done { magic, expr }
Call { magic, func, argm, cont }

type IOError:
Type
Name
Inner { value }

def IO/MAGIC:
return (0xD0CA11, 0xFF1FF1)

Expand All @@ -202,9 +212,18 @@ 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))
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))
Expand All @@ -214,7 +233,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

Expand Down Expand Up @@ -256,9 +277,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))
Expand All @@ -269,7 +290,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))
Expand All @@ -284,7 +305,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
Expand All @@ -302,16 +323,19 @@ 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

# 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.
Expand All @@ -320,7 +344,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)
Expand Down
8 changes: 4 additions & 4 deletions tests/golden_tests/io/load.bend
Original file line number Diff line number Diff line change
@@ -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)
}
10 changes: 5 additions & 5 deletions tests/golden_tests/io/load_fail.bend
Original file line number Diff line number Diff line change
@@ -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)
}
8 changes: 4 additions & 4 deletions tests/golden_tests/io/store.bend
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 4 additions & 4 deletions tests/golden_tests/io/store_fail.bend
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit dc14ed2

Please sign in to comment.