mirror of
https://github.com/HigherOrderCO/Bend.git
synced 2024-09-11 03:45:29 +03:00
Add dylib functions, improve the builtins file a bit
This commit is contained in:
parent
060239a055
commit
d8d728a31d
212
docs/ffi.md
Normal file
212
docs/ffi.md
Normal file
@ -0,0 +1,212 @@
|
||||
# Dynamically linked libraries and foreign functions
|
||||
|
||||
We can add new IO functions to Bend during runtime by loading dynamic libraries.
|
||||
|
||||
## Using IO dynamic libraries in Bend
|
||||
|
||||
Here is an example of how we could load a Bend library that contains functions for working with directories.
|
||||
|
||||
```py
|
||||
import IO/DyLib
|
||||
from List import (Cons, Nil, filter)
|
||||
from Result import (Err, Ok)
|
||||
import String
|
||||
|
||||
def main():
|
||||
with IO:
|
||||
# Open the dynamic library file
|
||||
# The second argument is '0' if we want to load all functions immediately.
|
||||
# Otherwise it should be '1' when we want to load functions as we use them.
|
||||
# 'dl' is the unique id of the dynamic library.
|
||||
dl <- DyLib/open("./libbend_dirs.so", 0)
|
||||
|
||||
# We can now call functions from the dynamic library.
|
||||
# We need to know what functions are available in the dynamic library.
|
||||
# If you're writing a library for Bend that uses a dynamically linked library
|
||||
# you should wrap the IO calls so that users don't need to know what's in the dynamic library.
|
||||
|
||||
# The first argument is the dynamic library id.
|
||||
# The second argument is the name of the function we want to call as a String.
|
||||
# The third argument are the arguments to the function.
|
||||
# You need to know the types of the arguments and the return type of the function.
|
||||
|
||||
# In our example, 'ls' receives a path as a String and
|
||||
# returns a String with the result of the 'ls' command.
|
||||
files_bytes <- DyLib/call(dl, "ls", "/home")
|
||||
files_str = String/decode_utf8(files_bytes)
|
||||
files = String/split(files_str, '\n')
|
||||
|
||||
# We want to create a directory for a new user "my_user" if it doesn't exist.
|
||||
my_dir = filter(files, String/equals("my_user"))
|
||||
match my_dir:
|
||||
case Cons:
|
||||
# The directory already exists, do nothing.
|
||||
* <- print("Directory already exists.")
|
||||
status = -1
|
||||
case Nil:
|
||||
# The directory doesn't exist, create it.
|
||||
* <- DyLib/call(dl, "mkdir", "/home/my_user")
|
||||
* <- print("Directory created.")
|
||||
status = +0
|
||||
|
||||
# Here the program ends so we didn't need to close the dynamic library,
|
||||
# but it's good practice to do so once we know we won't need it anymore.
|
||||
* <- DyLib/close(dl)
|
||||
return wrap(status)
|
||||
```
|
||||
|
||||
## Writing IO dynamic libraries for Bend
|
||||
|
||||
Bend IO libraries need to be implemented in C or Cuda (depending on the backend you're targeting) using the HVM API.
|
||||
|
||||
### Writing libraries for the C runtime
|
||||
|
||||
The functions you call from Bend using `IO/DyLib/call` must have the following signature:
|
||||
|
||||
```c
|
||||
Port function_name(Net* net, Book* book, Port arg);
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `net` is a pointer to the current network state.
|
||||
- `book` is a pointer to the book of function definitions.
|
||||
- `arg` is a pointer to the arguments of the function.
|
||||
|
||||
The return value must be a `Port` that points to the return value of the function.
|
||||
|
||||
HVM provides some util functions to do the conversions from HVM to C and vice versa,
|
||||
so that you don't need to understand the details of the HVM runtime.
|
||||
|
||||
We can implement the example library from earlier for the C runtime with the following C code:
|
||||
|
||||
```c
|
||||
// This is a header file that contains the HVM API.
|
||||
#include <hvm.h>
|
||||
|
||||
// The headers we need to open and read directories.
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
// IO functions must have this exact signature.
|
||||
// The first argument is a pointer to the graph with the current state of the program.
|
||||
// The second argument is a pointer to the book of function definitions.
|
||||
// The third argument points to the arguments of the function.
|
||||
// The return value must be a port that points to the return value of the function.
|
||||
Port ls(Net* net, Book* book, Port arg) {
|
||||
// The arguments first need to be converted from HVM to C.
|
||||
// For the 'ls' function, this is just a single string.
|
||||
Str ls_path = readback_str(net, book, tup.elem_buf[0]);
|
||||
|
||||
// Now we can do the actual IO operations.
|
||||
// In this case, we list the contents of the directory
|
||||
// by calling the 'ls' program as a subprocess.
|
||||
char* cmd = malloc(strlen(ls_path) + strlen("ls ") + 1);
|
||||
sprintf(cmd, "ls %s", ls_path);
|
||||
free(ls_path.buf);
|
||||
|
||||
FILE* pipe = popen(cmd, "r");
|
||||
free(cmd);
|
||||
if (pipe == NULL) {
|
||||
fprintf(stderr, "failed to run command '%s': %s\n", cmd, strerror(errno));
|
||||
|
||||
// It'd be best practice to return a Result type instead of a null value (ERA).
|
||||
// If this command fails and the calling Bend program tries to use the result,
|
||||
// it will get corrupted and spit out garbage.
|
||||
return new_port(ERA, 0);
|
||||
}
|
||||
char buffer[512];
|
||||
char *output;
|
||||
size_t output_len = 0;
|
||||
while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
|
||||
size_t len = strlen(buffer);
|
||||
char* new_result = realloc(output, output_len + len + 1);
|
||||
if (new_result == NULL) {
|
||||
free(output);
|
||||
pclose(pipe);
|
||||
fprintf(stderr, "failed to allocate space for output of '%s': %s\n", cmd, strerror(errno));
|
||||
return new_port(ERA, 0);
|
||||
}
|
||||
output = new_result;
|
||||
strcpy(output + output_len, buffer);
|
||||
output_len += len;
|
||||
}
|
||||
|
||||
// After we're done with the operation, we convert it to HVM format.
|
||||
// In this case, the output is the output of the 'ls' command as a list of bytes.
|
||||
// We need to process it in Bend later to convert it to a list of file names.
|
||||
Bytes output_bytes = Bytes { .buf = output, .len = output_len };
|
||||
Port output_port = inject_bytes(net, output_bytes);
|
||||
|
||||
// Remember to free all the allocated memory.
|
||||
free(output);
|
||||
pclose(pipe);
|
||||
return output_port;
|
||||
}
|
||||
|
||||
Port mkdir(Net* net, Book* book, Port arg) {
|
||||
// We do the same thing here as in the 'ls' function,
|
||||
// except we call 'mkdir' which doesn't output anything.
|
||||
Str ls_path = readback_str(net, book, tup.elem_buf[0]);
|
||||
|
||||
char* cmd = malloc(strlen(ls_path) + strlen("mkdir ") + 1);
|
||||
sprintf(cmd, "mkdir %s", ls_path);
|
||||
int res = system(cmd);
|
||||
|
||||
free(ls_path.buf);
|
||||
free(cmd);
|
||||
return new_port(ERA, 0);
|
||||
}
|
||||
```
|
||||
|
||||
To compile this code into a library, we can use the `gcc` compiler and include the HVM header files.
|
||||
|
||||
Assuming that it's saved in a file called `libbend_dirs.c`, we can compile it with the following command:
|
||||
|
||||
```sh
|
||||
gcc -shared -o libbend_dirs.so -I /path/to/hvm/ libbend_dirs.c
|
||||
```
|
||||
|
||||
Now we can use the dynamic library in our Bend program, we just need to pass the path to the library to `IO/DyLib/open`.
|
||||
|
||||
### Writing libraries for the Cuda runtime
|
||||
|
||||
Writing libraries for the Cuda runtime is very similar to writing libraries for the C runtime.
|
||||
|
||||
The main difference is the function signature:
|
||||
|
||||
```c++
|
||||
Port function_name(GNet* gnet, Port argm)
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `gnet` is a pointer to the current network state.
|
||||
- `argm` is the argument to the function.
|
||||
|
||||
The return value must be a `Port` that points to the return value of the function.
|
||||
|
||||
To compile libraries of the Cuda runtime, we can use the `nvcc` compiler and include the HVM header files.
|
||||
|
||||
Assuming that it's saved in a file called `libbend_dirs.cu`, we can compile it with the following command:
|
||||
|
||||
```sh
|
||||
nvcc -shared -o libbend_dirs.so -I /path/to/hvm/ libbend_dirs.cu
|
||||
```
|
||||
|
||||
### Compiling Bend programs that use dynamic libraries
|
||||
|
||||
To compile the C or Cuda program generated from a Bend program that uses dynamic libraries, we need to use the `-rdynamic` flag to allow the dynamic library to use symbols from the main program.
|
||||
|
||||
For example, if we have a Bend program called `main.bend` that uses the dynamic library `libbend_dirs.so`, we need compile to it with the following commands:
|
||||
|
||||
```sh
|
||||
# Compiling for C
|
||||
bend gen-c my_app.bend > my_app.c
|
||||
gcc -rdynamic -lm my_app.c -o my_app
|
||||
|
||||
# Compiling for Cuda
|
||||
bend gen-cu my_app.bend > my_app.cu
|
||||
nvcc --compiler-options=-rdynamic my_app.cu -o my_app
|
||||
```
|
@ -12,5 +12,5 @@ to_church n = switch n {
|
||||
_: λf λx (f (to_church n-1 f x))
|
||||
}
|
||||
main =
|
||||
# Self-composes `not` 2^24-1 times and prints the result.
|
||||
((to_church 0xFFFFFF) fusing_not) # try replacing 'fusing_not' by 'not'. Will it still work?
|
||||
# Self-composes `not` 2^23-1 times and prints the result.
|
||||
((to_church 0x7FFFFF) fusing_not) # try replacing 'fusing_not' by 'not'. Will it still work?
|
||||
|
4
examples/hello_world.bend
Normal file
4
examples/hello_world.bend
Normal file
@ -0,0 +1,4 @@
|
||||
def main():
|
||||
with IO:
|
||||
* <- IO/print("Hello, world!")
|
||||
return wrap(0)
|
@ -2,22 +2,57 @@ type String = (Nil) | (Cons head ~tail)
|
||||
|
||||
type List = (Nil) | (Cons head ~tail)
|
||||
|
||||
List/length xs = fold xs with len=0, acc=[] {
|
||||
List/Nil: (len, (List/reverse acc))
|
||||
List/Cons: (xs.tail (+ len 1) (List/Cons xs.head acc))
|
||||
# List/length(xs: List(T)) -> (u24, List(T))
|
||||
# Returns the length of a list and the list itself.
|
||||
List/length xs = fold xs with len=0, acc=DiffList/new {
|
||||
List/Nil: (len, (DiffList/to_list acc))
|
||||
List/Cons: (xs.tail (+ len 1) (DiffList/append acc xs.head))
|
||||
}
|
||||
|
||||
# List/reverse(xs: List(T)) -> List(T)
|
||||
# Reverses a list.
|
||||
List/reverse xs = fold xs with acc=[] {
|
||||
List/Nil: acc
|
||||
List/Cons: (xs.tail (List/Cons xs.head acc))
|
||||
}
|
||||
|
||||
# List/flatten(xs: List(List(T))) -> List(T)
|
||||
# Flattens a list of lists.
|
||||
List/flatten (List/Cons x xs) = (List/concat x (List/flatten xs))
|
||||
List/flatten (List/Nil) = (List/Nil)
|
||||
|
||||
# List/concat(xs: List(T), ys: List(T)) -> List(T)
|
||||
# Concatenates two lists.
|
||||
List/concat (List/Cons x xs) ys = (List/Cons x (List/concat xs ys))
|
||||
List/concat (List/Nil) ys = ys
|
||||
|
||||
# List/split_once(xs: List(T), val: T) -> (Result(List(T), List(T)))
|
||||
# Splits a list into two lists at the first occurrence of a value.
|
||||
List/split_once xs val = (List/split_once.go xs val @x x)
|
||||
List/split_once.go List/Nil val acc = (Result/Err (acc List/Nil))
|
||||
List/split_once.go (List/Cons x xs) val acc =
|
||||
if (== val x) {
|
||||
(Result/Ok ((acc List/Nil), xs))
|
||||
} else {
|
||||
(List/split_once.go xs val @y (acc (List/Cons x y)))
|
||||
}
|
||||
|
||||
# DiffList/new() -> (List(T) -> List(T))
|
||||
# Create a new difference list
|
||||
DiffList/new = λx x
|
||||
|
||||
# DiffList/append(diff: List(T) -> List(T), val: T) -> (List(T) -> List(T))
|
||||
# Append a value to the end of the difference list
|
||||
DiffList/append diff val = λx (diff (List/Cons val x))
|
||||
|
||||
# DiffList/cons(diff: List(T) -> List(T), val: T) -> (List(T) -> List(T))
|
||||
# Append a value to the beginning of the difference list
|
||||
DiffList/cons diff val = λx (List/Cons val (diff x))
|
||||
|
||||
# DiffList/to_list(diff: List(T) -> List(T)) -> (List(T))
|
||||
# Convert a difference list to a list
|
||||
DiffList/to_list diff = (diff List/Nil)
|
||||
|
||||
type Nat = (Succ ~pred) | (Zero)
|
||||
|
||||
type Result = (Ok val) | (Err val)
|
||||
@ -123,12 +158,23 @@ def IO/sleep(seconds):
|
||||
## File IO
|
||||
|
||||
### File IO primitives
|
||||
IO/FS/open path mode = (IO/Call IO/MAGIC "OPEN" (path, mode) @x (IO/Done IO/MAGIC x))
|
||||
IO/FS/close file = (IO/Call IO/MAGIC "CLOSE" file @x (IO/Done IO/MAGIC x))
|
||||
IO/FS/read file num_bytes = (IO/Call IO/MAGIC "READ" (file, num_bytes) @x (IO/Done IO/MAGIC x))
|
||||
IO/FS/write file bytes = (IO/Call IO/MAGIC "WRITE" (file, bytes) @x (IO/Done IO/MAGIC x))
|
||||
IO/FS/seek file offset mode = (IO/Call IO/MAGIC "SEEK" (file, (offset, mode)) @x (IO/Done IO/MAGIC x))
|
||||
IO/FS/flush file = (IO/Call IO/MAGIC "FLUSH" file @x (IO/Done IO/MAGIC x))
|
||||
# IO/FS/open(path: String, mode: u24) -> (IO u24)
|
||||
IO/FS/open path mode = (IO/Call IO/MAGIC "OPEN" (path, mode) IO/wrap)
|
||||
|
||||
# IO/FS/close(file: u24) -> (IO None)
|
||||
IO/FS/close file = (IO/Call IO/MAGIC "CLOSE" file IO/wrap)
|
||||
|
||||
# IO/FS/read(file: u24, num_bytes: u24) -> (IO (List u24))
|
||||
IO/FS/read file num_bytes = (IO/Call IO/MAGIC "READ" (file, num_bytes) IO/wrap)
|
||||
|
||||
# IO/FS/write(file: u24, bytes: (List u24)) -> (IO None)
|
||||
IO/FS/write file bytes = (IO/Call IO/MAGIC "WRITE" (file, bytes) IO/wrap)
|
||||
|
||||
# IO/FS/seek(file: u24, offset: i24, mode: u24) -> (IO None)
|
||||
IO/FS/seek file offset mode = (IO/Call IO/MAGIC "SEEK" (file, (offset, mode)) IO/wrap)
|
||||
|
||||
# IO/FS/flush(file: u24) -> (IO None)
|
||||
IO/FS/flush file = (IO/Call IO/MAGIC "FLUSH" file IO/wrap)
|
||||
|
||||
### Always available files
|
||||
IO/FS/STDIN = 0
|
||||
@ -144,8 +190,9 @@ IO/FS/SEEK_CUR = +1
|
||||
IO/FS/SEEK_END = +2
|
||||
|
||||
### File utilities
|
||||
|
||||
# IO/FS/read_file(path: String) -> (IO (List u24))
|
||||
# Reads an entire file, returning a list of bytes.
|
||||
# def IO/FS/read_file(path: String) -> IO [u24]
|
||||
def IO/FS/read_file(path):
|
||||
with IO:
|
||||
fd <- IO/FS/open(path, "r")
|
||||
@ -153,8 +200,8 @@ def IO/FS/read_file(path):
|
||||
* <- IO/FS/close(fd)
|
||||
return wrap(bytes)
|
||||
|
||||
# IO/FS/read_to_end(fd: u24) -> (IO (List u24))
|
||||
# Reads the remaining contents of a file, returning a list of read bytes.
|
||||
# def IO/FS/read_to_end(fd: u24) -> IO [u24]
|
||||
def IO/FS/read_to_end(fd):
|
||||
return IO/FS/read_to_end.read_chunks(fd, [])
|
||||
|
||||
@ -168,8 +215,8 @@ def IO/FS/read_to_end.read_chunks(fd, chunks):
|
||||
case List/Cons:
|
||||
return IO/FS/read_to_end.read_chunks(fd, List/Cons(chunk, chunks))
|
||||
|
||||
# IO/FS/read_line(fd: u24) -> (IO (List u24))
|
||||
# Reads a single line from a file, returning a list of bytes.
|
||||
# def IO/FS/read_line(fd: u24) -> IO [u24]
|
||||
def IO/FS/read_line(fd):
|
||||
return IO/FS/read_line.read_chunks(fd, [])
|
||||
|
||||
@ -177,7 +224,7 @@ def IO/FS/read_line.read_chunks(fd, chunks):
|
||||
with IO:
|
||||
# Read line in 1kB chunks
|
||||
chunk <- IO/FS/read(fd, 1024)
|
||||
match res = Bytes/split_once(chunk, '\n'):
|
||||
match res = List/split_once(chunk, '\n'):
|
||||
case Result/Ok:
|
||||
(line, rest) = res.val
|
||||
(length, *) = List/length(rest)
|
||||
@ -190,8 +237,8 @@ def IO/FS/read_line.read_chunks(fd, chunks):
|
||||
chunks = List/Cons(line, chunks)
|
||||
return IO/FS/read_line.read_chunks(fd, chunks)
|
||||
|
||||
# IO/FS/write_file(path: String, bytes: (List u24)) -> (IO None)
|
||||
# Writes a list of bytes to a file given by a path.
|
||||
# def IO/FS/write_file(path: String, bytes: [u24]) -> IO *
|
||||
def IO/FS/write_file(path, bytes):
|
||||
with IO:
|
||||
f <- IO/FS/open(path, "w")
|
||||
@ -199,25 +246,16 @@ def IO/FS/write_file(path, bytes):
|
||||
* <- IO/FS/close(f)
|
||||
return wrap(bytes)
|
||||
|
||||
Bytes/split_once xs cond = (Bytes/split_once.go xs cond @x x)
|
||||
Bytes/split_once.go List/Nil cond acc = (Result/Err (acc List/Nil))
|
||||
Bytes/split_once.go (List/Cons x xs) cond acc =
|
||||
if (== cond x) {
|
||||
(Result/Ok ((acc List/Nil), xs))
|
||||
} else {
|
||||
(Bytes/split_once.go xs cond @y (acc (List/Cons x y)))
|
||||
}
|
||||
|
||||
### Standard input and output utilities
|
||||
|
||||
# IO/print(text: String) -> (IO *)
|
||||
# Prints a string to stdout, encoding it with utf-8.
|
||||
# def IO/print(text: String) -> IO *
|
||||
IO/print text = (IO/FS/write IO/FS/STDOUT (String/encode_utf8 text))
|
||||
|
||||
# IO/input() -> IO String
|
||||
# Read characters from stdin until a newline is found.
|
||||
# Returns the read input decoded as utf-8.
|
||||
# def IO/input() -> IO String
|
||||
IO/input = (IO/input.go @x x)
|
||||
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:
|
||||
@ -228,14 +266,40 @@ def IO/input.go(acc):
|
||||
return IO/input.go(acc)
|
||||
case List/Cons:
|
||||
if byte.head == '\n':
|
||||
bytes = acc(List/Nil)
|
||||
text = Bytes/decode_utf8(bytes)
|
||||
bytes = DiffList/to_list(acc)
|
||||
text = String/decode_utf8(bytes)
|
||||
return wrap(text)
|
||||
else:
|
||||
acc = lambda x: acc(List/Cons(byte.head, x))
|
||||
acc = DiffList/append(acc, byte.head)
|
||||
return IO/input.go(acc)
|
||||
|
||||
### Dynamically linked libraries
|
||||
|
||||
# IO/DyLib/open(path: String, lazy: u24) -> u24
|
||||
# 'path' is the path to the dl file.
|
||||
# 'lazy' is a boolean encoded as a u24 that determines if all functions are loaded lazily or upfront.
|
||||
# Returns an unique id to the dl object encoded as a u24
|
||||
def IO/DyLib/open(path, lazy):
|
||||
return IO/Call(IO/MAGIC, "DL_OPEN", (path, lazy), IO/wrap)
|
||||
|
||||
# IO/DyLib/call(dl: u24, fn: String, args: Any) -> Any
|
||||
# Calls a function of a previously opened dylib.
|
||||
# 'dl' is the id of the dl object
|
||||
# 'fn' is the name of the function in the dylib
|
||||
# 'args' are the arguments to the function. The expected values depend on the called function.
|
||||
# The returned value is determined by the called function.
|
||||
def IO/DyLib/call(dl, fn, args):
|
||||
return IO/Call(IO/MAGIC, "DL_CALL", (dl, fn, args), IO/wrap)
|
||||
|
||||
# IO/DyLib/close(dl: u24) -> None
|
||||
# Closes a previously open dylib.
|
||||
# 'dl' is the id of the dylib.
|
||||
# Returns nothing.
|
||||
def IO/DyLib/close(dl):
|
||||
return IO/Call(IO/MAGIC, "DL_CLOSE", (dl), IO/wrap)
|
||||
|
||||
# Lazy thunks
|
||||
# defer(val: T) -> ((T -> T) -> T)
|
||||
# We can defer the evaluation of a function by wrapping it in a thunk
|
||||
# Ex: @x (x @arg1 @arg2 @arg3 (f arg1 arg2 arg3) arg1 arg2 arg3)
|
||||
# This is only evaluated when we call it with 'undefer' (undefer my_thunk)
|
||||
@ -243,22 +307,15 @@ def IO/input.go(acc):
|
||||
# The example above can be written as:
|
||||
# (defer_arg (defer_arg (defer_arg (defer @arg1 @arg2 @arg3 (f arg1 arg2 arg3)) arg1) arg2) arg3)
|
||||
defer val = @x (x val)
|
||||
|
||||
# defer_arg(defered: A -> B -> C, arg: B) -> (A -> C)
|
||||
defer_arg defered arg = @x (defered x arg)
|
||||
|
||||
# undefer(defered: (A -> A) -> B) -> B
|
||||
undefer defered = (defered @x x)
|
||||
|
||||
|
||||
# Native numeric operations
|
||||
|
||||
# log(x: f24, base: f24) -> f24
|
||||
# Computes the logarithm of `x` with the specified `base`.
|
||||
hvm log:
|
||||
(x ($([|] $(x ret)) ret))
|
||||
|
||||
# atan2(x: f24, y: f24) -> f24
|
||||
# Has the same behaviour as `atan2f` in the C math lib.
|
||||
# Computes the arctangent of the quotient of its two arguments.
|
||||
hvm atan2:
|
||||
($([&] $(y ret)) (y ret))
|
||||
# Native number casts
|
||||
|
||||
# to_f24(x: native number) -> f24
|
||||
# Casts any native number to an f24.
|
||||
@ -279,13 +336,18 @@ hvm to_i24:
|
||||
|
||||
Utf8/REPLACEMENT_CHARACTER = '\u{FFFD}'
|
||||
|
||||
Bytes/decode_utf8 bytes =
|
||||
# String/decode_utf8(List u24) -> String
|
||||
# Decodes a sequence of bytes to a String using utf-8 encoding.
|
||||
String/decode_utf8 bytes =
|
||||
let (got, rest) = (Utf8/decode_character bytes)
|
||||
match rest {
|
||||
List/Nil: (String/Cons got String/Nil)
|
||||
List/Cons: (String/Cons got (Bytes/decode_utf8 rest))
|
||||
List/Cons: (String/Cons got (String/decode_utf8 rest))
|
||||
}
|
||||
|
||||
# Utf8/decode_character(List u24) -> (u24, List u24)
|
||||
# Decodes one utf-8 character from the start of a sequence of bytes.
|
||||
# Returns the decoded character and the remaining bytes.
|
||||
Utf8/decode_character [] = (0, [])
|
||||
Utf8/decode_character [a] = if (<= a 0x7F) { (a, []) } else { (Utf8/REPLACEMENT_CHARACTER, []) }
|
||||
Utf8/decode_character [a, b] =
|
||||
@ -346,6 +408,8 @@ Utf8/decode_character (List/Cons a (List/Cons b (List/Cons c (List/Cons d rest))
|
||||
}
|
||||
}
|
||||
|
||||
# String/encode_utf8(String) -> (List u24)
|
||||
# Encodes a string to a sequence of bytes using utf-8 encoding.
|
||||
String/encode_utf8 (String/Nil) = (List/Nil)
|
||||
String/encode_utf8 (String/Cons x xs) =
|
||||
use Utf8/rune1max = 0b01111111
|
||||
@ -379,16 +443,37 @@ String/encode_utf8 (String/Cons x xs) =
|
||||
}
|
||||
}
|
||||
|
||||
Bytes/decode_ascii (List/Cons x xs) = (String/Cons x (Bytes/decode_ascii xs))
|
||||
Bytes/decode_ascii (List/Nil) = (String/Nil)
|
||||
# String/decode_ascii(List u24) -> String
|
||||
# Decodes a sequence of bytes to a String using ascii encoding.
|
||||
String/decode_ascii (List/Cons x xs) = (String/Cons x (String/decode_ascii xs))
|
||||
String/decode_ascii (List/Nil) = (String/Nil)
|
||||
|
||||
# String/encode_ascii(String) -> (List u24)
|
||||
# Encodes a string to a sequence of bytes using ascii encoding.
|
||||
String/encode_ascii (String/Cons x xs) = (List/Cons x (String/encode_ascii xs))
|
||||
String/encode_ascii (String/Nil) = (List/Nil)
|
||||
|
||||
# Math
|
||||
|
||||
# The Pi constant.
|
||||
Math/PI = 3.1415926535
|
||||
# Math/PI() -> f24
|
||||
# The Pi (π) constant.
|
||||
def Math/PI():
|
||||
return 3.1415926535
|
||||
|
||||
def Math/E():
|
||||
return 2.718281828
|
||||
|
||||
# Math/log(x: f24, base: f24) -> f24
|
||||
# Computes the logarithm of `x` with the specified `base`.
|
||||
hvm Math/log:
|
||||
(x ($([|] $(x ret)) ret))
|
||||
|
||||
# Math/atan2(x: f24, y: f24) -> f24
|
||||
# Has the same behaviour as `atan2f` in the C math lib.
|
||||
# Computes the arctangent of the quotient of its two arguments.
|
||||
hvm Math/atan2:
|
||||
($([&] $(y ret)) (y ret))
|
||||
|
||||
|
||||
# Math/sin(a: f24) -> f24
|
||||
# Computes the sine of the given angle in radians.
|
||||
@ -420,15 +505,15 @@ Math/csc a = (/ 1.0 (Math/sin a))
|
||||
|
||||
# Math/atan(a: f24) -> f24
|
||||
# Computes the arctangent of the given angle.
|
||||
Math/atan a = (atan2 a 1.0)
|
||||
Math/atan a = (Math/atan2 a 1.0)
|
||||
|
||||
# Math/asin(a: f24) -> f24
|
||||
# Computes the arcsine of the given angle.
|
||||
Math/asin a = (atan2 a (Math/sqrt (- 1.0 (* a a))))
|
||||
Math/asin a = (Math/atan2 a (Math/sqrt (- 1.0 (* a a))))
|
||||
|
||||
# Math/acos(a: f24) -> f24
|
||||
# Computes the arccosine of the given angle.
|
||||
Math/acos a = (atan2 (Math/sqrt (- 1.0 (* a a))) a)
|
||||
Math/acos a = (Math/atan2 (Math/sqrt (- 1.0 (* a a))) a)
|
||||
|
||||
# Math/radians(a: f24) -> f24
|
||||
# Converts degrees to radians.
|
||||
|
@ -47,9 +47,9 @@ pub const BUILTIN_TYPES: &[&str] = &[LIST, STRING, NAT, TREE, MAP, IO];
|
||||
|
||||
impl ParseBook {
|
||||
pub fn builtins() -> Self {
|
||||
TermParser::new(BUILTINS)
|
||||
.parse_book(Self::default(), true)
|
||||
.expect("Error parsing builtin file, this should not happen")
|
||||
let book = TermParser::new(BUILTINS)
|
||||
.parse_book(Self::default(), true);
|
||||
book.unwrap_or_else(|e| panic!("Error parsing builtin file, this should not happen:\n{e}"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
use bend::{
|
||||
compile_book, desugar_book,
|
||||
diagnostics::{Diagnostics, DiagnosticsConfig, Severity},
|
||||
fun::{
|
||||
load_book::do_parse_book_default, net_to_term::net_to_term, term_to_net::Labels, Book, Ctx, Name, Term,
|
||||
},
|
||||
fun::{load_book::do_parse_book_default, net_to_term::net_to_term, term_to_net::Labels, Book, Ctx, Name},
|
||||
hvm::hvm_book_show_pretty,
|
||||
imports::DefaultLoader,
|
||||
load_to_book,
|
||||
@ -86,16 +84,6 @@ fn run_golden_test_dir_multiple(test_name: &str, run: &[&RunFn]) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_book_simple(
|
||||
book: Book,
|
||||
run_opts: RunOpts,
|
||||
compile_opts: CompileOpts,
|
||||
diagnostics_cfg: DiagnosticsConfig,
|
||||
args: Option<Vec<Term>>,
|
||||
) -> Result<(Term, String, Diagnostics), Diagnostics> {
|
||||
run_book(book, run_opts, compile_opts, diagnostics_cfg, args, "run").map(Option::unwrap)
|
||||
}
|
||||
|
||||
/* Snapshot/regression/golden tests
|
||||
|
||||
Each tests runs all the files in tests/golden_tests/<test name>.
|
||||
@ -150,13 +138,15 @@ fn linear_readback() {
|
||||
let book = do_parse_book_default(code, path)?;
|
||||
let compile_opts = CompileOpts::default().set_all();
|
||||
let diagnostics_cfg = DiagnosticsConfig::default();
|
||||
let (term, _, diags) = run_book_simple(
|
||||
let (term, _, diags) = run_book(
|
||||
book,
|
||||
RunOpts { linear_readback: true, ..Default::default() },
|
||||
compile_opts,
|
||||
diagnostics_cfg,
|
||||
None,
|
||||
)?;
|
||||
"run",
|
||||
)?
|
||||
.unwrap();
|
||||
let res = format!("{diags}{term}");
|
||||
Ok(res)
|
||||
});
|
||||
@ -180,7 +170,7 @@ fn run_file() {
|
||||
for adt_encoding in [AdtEncoding::NumScott, AdtEncoding::Scott] {
|
||||
let compile_opts = CompileOpts { adt_encoding, ..CompileOpts::default() };
|
||||
let (term, _, diags) =
|
||||
run_book_simple(book.clone(), run_opts.clone(), compile_opts, diagnostics_cfg, None)?;
|
||||
run_book(book.clone(), run_opts.clone(), compile_opts, diagnostics_cfg, None, "run")?.unwrap();
|
||||
res.push_str(&format!("{adt_encoding}:\n{diags}{term}\n\n"));
|
||||
}
|
||||
Ok(res)
|
||||
@ -205,7 +195,7 @@ fn import_system() {
|
||||
let mut res = String::new();
|
||||
|
||||
let compile_opts = CompileOpts::default();
|
||||
let (term, _, diags) = run_book_simple(book, run_opts, compile_opts, diagnostics_cfg, None)?;
|
||||
let (term, _, diags) = run_book(book, run_opts, compile_opts, diagnostics_cfg, None, "run")?.unwrap();
|
||||
res.push_str(&format!("{diags}{term}\n\n"));
|
||||
Ok(res)
|
||||
})],
|
||||
@ -363,7 +353,7 @@ fn hangs() {
|
||||
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Allow, false);
|
||||
|
||||
let thread = std::thread::spawn(move || {
|
||||
run_book_simple(book, RunOpts::default(), compile_opts, diagnostics_cfg, None)
|
||||
run_book(book, RunOpts::default(), compile_opts, diagnostics_cfg, None, "run")
|
||||
});
|
||||
std::thread::sleep(std::time::Duration::from_secs(expected_normalization_time));
|
||||
|
||||
@ -397,7 +387,8 @@ fn run_entrypoint() {
|
||||
book.entrypoint = Some(Name::new("foo"));
|
||||
let compile_opts = CompileOpts::default().set_all();
|
||||
let diagnostics_cfg = DiagnosticsConfig { ..DiagnosticsConfig::new(Severity::Error, true) };
|
||||
let (term, _, diags) = run_book_simple(book, RunOpts::default(), compile_opts, diagnostics_cfg, None)?;
|
||||
let (term, _, diags) =
|
||||
run_book(book, RunOpts::default(), compile_opts, diagnostics_cfg, None, "run")?.unwrap();
|
||||
let res = format!("{diags}{term}");
|
||||
Ok(res)
|
||||
})
|
||||
@ -459,7 +450,7 @@ fn io() {
|
||||
let compile_opts = CompileOpts::default();
|
||||
let diagnostics_cfg = DiagnosticsConfig::default();
|
||||
let (term, _, diags) =
|
||||
run_book_simple(book, RunOpts::default(), compile_opts, diagnostics_cfg, None)?;
|
||||
run_book(book, RunOpts::default(), compile_opts, diagnostics_cfg, None, "run-c")?.unwrap();
|
||||
let res = format!("{diags}{term}");
|
||||
Ok(format!("Strict mode:\n{res}"))
|
||||
}),
|
||||
@ -485,7 +476,8 @@ fn examples() -> Result<(), Diagnostics> {
|
||||
let book = do_parse_book_default(&code, path).unwrap();
|
||||
let compile_opts = CompileOpts::default();
|
||||
let diagnostics_cfg = DiagnosticsConfig::default();
|
||||
let (term, _, diags) = run_book_simple(book, RunOpts::default(), compile_opts, diagnostics_cfg, None)?;
|
||||
let (term, _, diags) =
|
||||
run_book(book, RunOpts::default(), compile_opts, diagnostics_cfg, None, "run-c")?.unwrap();
|
||||
let res = format!("{diags}{term}");
|
||||
|
||||
let mut settings = insta::Settings::clone_current();
|
||||
|
@ -140,7 +140,7 @@ main = (List/expand
|
||||
|
||||
0xFFFF
|
||||
|
||||
(log 2.0 3.0)
|
||||
(atan2 3.0 4.0)
|
||||
(Math/log 2.0 3.0)
|
||||
(Math/atan2 3.0 4.0)
|
||||
]
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
def main:
|
||||
use bytes = [72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140, 33]
|
||||
use s = "Hello, 世界!"
|
||||
return (String/encode_utf8(s), Bytes/decode_utf8(bytes))
|
||||
return (String/encode_utf8(s), String/decode_utf8(bytes))
|
||||
|
5
tests/snapshots/examples__hello_world.bend.snap
Normal file
5
tests/snapshots/examples__hello_world.bend.snap
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tests/golden_tests.rs
|
||||
input_file: examples/hello_world.bend
|
||||
---
|
||||
λa (a IO/Done/tag IO/MAGIC 0)
|
5
tests/snapshots/examples__parallel_sum.bend.snap
Normal file
5
tests/snapshots/examples__parallel_sum.bend.snap
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tests/golden_tests.rs
|
||||
input_file: examples/parallel_sum.bend
|
||||
---
|
||||
16744448
|
Loading…
Reference in New Issue
Block a user