Add dylib functions, improve the builtins file a bit

This commit is contained in:
Nicolas Abril 2024-07-08 18:02:53 +02:00
parent 060239a055
commit d8d728a31d
11 changed files with 382 additions and 79 deletions

212
docs/ffi.md Normal file
View 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
```

View File

@ -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?

View File

@ -0,0 +1,4 @@
def main():
with IO:
* <- IO/print("Hello, world!")
return wrap(0)

View File

@ -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.

View File

@ -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}"))
}
}

View File

@ -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();

View File

@ -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)
]
)

View File

@ -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))

View File

@ -0,0 +1,5 @@
---
source: tests/golden_tests.rs
input_file: examples/hello_world.bend
---
λa (a IO/Done/tag IO/MAGIC 0)

View File

@ -0,0 +1,5 @@
---
source: tests/golden_tests.rs
input_file: examples/parallel_sum.bend
---
16744448