Merge remote-tracking branch 'origin/main' into roc-dev-inline-expects

This commit is contained in:
Folkert 2022-10-09 18:46:57 +02:00
commit e62ab00c65
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
275 changed files with 4038 additions and 2432 deletions

View file

@ -0,0 +1,6 @@
*.dSYM
libhost.a
libapp.so
dynhost
preprocessedhost
metadata

View file

@ -0,0 +1,2 @@
fibonacci
quicksort

View file

@ -0,0 +1,15 @@
# Algorithm examples
To run:
```bash
cargo run fibonacci.roc
cargo run quicksort.roc
```
To run in release mode instead, do:
```bash
cargo run --release fibonacci.roc
cargo run --release quicksort.roc
```

View file

@ -0,0 +1,109 @@
const std = @import("std");
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
const maxInt = std.math.maxInt;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
const builtin = @import("builtin");
if (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;
// NOTE the LLVM backend expects this signature
// extern fn roc__mainForHost_1_exposed(i64, *i64) void;
extern fn roc__mainForHost_1_exposed(i64) i64;
const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque;
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
} else {
return malloc(size);
}
}
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
}
return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
}
free(@alignCast(Align, @ptrCast([*]u8, c_ptr)));
}
export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
std.process.exit(0);
}
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void {
return memcpy(dst, src, size);
}
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
pub export fn main() u8 {
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
// start time
var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable;
const result = roc__mainForHost_1_exposed(10);
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable;
stdout.print("{d}\n", .{result}) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;
return 0;
}
fn to_seconds(tms: std.os.timespec) f64 {
return @intToFloat(f64, tms.tv_sec) + (@intToFloat(f64, tms.tv_nsec) / 1_000_000_000.0);
}

View file

@ -0,0 +1,9 @@
platform "fibonacci"
requires {} { main : I64 -> I64 }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : I64 -> I64
mainForHost = \a -> main a

View file

@ -0,0 +1,13 @@
app "fibonacci"
packages { pf: "fibonacci-platform/main.roc" }
imports []
provides [main] to pf
main = \n -> fib n 0 1
# the clever implementation requires join points
fib = \n, a, b ->
if n == 0 then
a
else
fib (n - 1) b (a + b)

View file

@ -0,0 +1,140 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(input: RocList) callconv(.C) RocList;
const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque;
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
} else {
return malloc(size);
}
}
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
}
return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
}
free(@alignCast(Align, @ptrCast([*]u8, c_ptr)));
}
export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
std.process.exit(0);
}
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void {
return memcpy(dst, src, size);
}
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
// warning! the array is currently stack-allocated so don't make this too big
const NUM_NUMS = 100;
const RocList = extern struct { elements: [*]i64, length: usize, capacity: usize };
const Unit = extern struct {};
pub export fn main() u8 {
const stdout = std.io.getStdOut().writer();
var raw_numbers: [NUM_NUMS + 1]i64 = undefined;
// set refcount to one
raw_numbers[0] = -9223372036854775808;
var numbers = raw_numbers[1..];
for (numbers) |_, i| {
numbers[i] = @mod(@intCast(i64, i), 12);
}
var roc_list = RocList{ .elements = numbers, .length = NUM_NUMS, .capacity = NUM_NUMS };
// start time
var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult
const callresult: RocList = roc__mainForHost_1_exposed(roc_list);
// stdout the result
const length = std.math.min(20, callresult.length);
var result = callresult.elements[0..length];
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable;
for (result) |x, i| {
if (i == 0) {
stdout.print("[{}, ", .{x}) catch unreachable;
} else if (i == length - 1) {
stdout.print("{}]\n", .{x}) catch unreachable;
} else {
stdout.print("{}, ", .{x}) catch unreachable;
}
}
// TODO apparently the typestamps are still (partially) undefined?
// const delta = to_seconds(ts2) - to_seconds(ts1);
// stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;
return 0;
}
fn to_seconds(tms: std.os.timespec) f64 {
return @intToFloat(f64, tms.tv_sec) + (@intToFloat(f64, tms.tv_nsec) / 1_000_000_000.0);
}

View file

@ -0,0 +1,9 @@
platform "quicksort"
requires {} { quicksort : List I64 -> List I64 }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : List I64 -> List I64
mainForHost = \list -> quicksort list

View file

@ -0,0 +1,59 @@
app "quicksort"
packages { pf: "quicksort-platform/main.roc" }
imports []
provides [quicksort] to pf
quicksort = \originalList ->
n = List.len originalList
quicksortHelp originalList 0 (n - 1)
quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
Pair partitionIndex partitioned ->
partitioned
|> quicksortHelp low (partitionIndex - 1)
|> quicksortHelp (partitionIndex + 1) high
else
list
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
when partitionHelp low low initialList high pivot is
Pair newI newList ->
Pair newI (swap newI high newList)
Err _ ->
Pair low initialList
partitionHelp : Nat, Nat, List (Num c), Nat, Num c -> [Pair Nat (List (Num c))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
partitionHelp (i + 1) (j + 1) (swap i j list) high pivot
else
partitionHelp i (j + 1) list high pivot
Err _ ->
Pair i list
else
Pair i list
swap : Nat, Nat, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
list
|> List.set i atJ
|> List.set j atI
_ ->
# to prevent a decrement on list
# turns out this is very important for optimizations
list

View file

@ -0,0 +1,12 @@
cfold
closure
deriv
issue2279
nqueens
quicksortapp
rbtree-ck
rbtree-del
rbtree-insert
test-astar
test-base64
*.wasm

View file

@ -0,0 +1,124 @@
interface AStar
exposes [findPath, Model, initialModel, cheapestOpen, reconstructPath]
imports [Quicksort]
findPath = \costFn, moveFn, start, end ->
astar costFn moveFn end (initialModel start)
Model position : {
evaluated : Set position,
openSet : Set position,
costs : Dict position F64,
cameFrom : Dict position position,
}
initialModel : position -> Model position
initialModel = \start -> {
evaluated: Set.empty,
openSet: Set.single start,
costs: Dict.single start 0,
cameFrom: Dict.empty,
}
cheapestOpen : (position -> F64), Model position -> Result position {}
cheapestOpen = \costFn, model ->
model.openSet
|> Set.toList
|> List.keepOks
(\position ->
when Dict.get model.costs position is
Err _ -> Err {}
Ok cost -> Ok { cost: cost + costFn position, position }
)
|> Quicksort.sortBy .cost
|> List.first
|> Result.map .position
|> Result.mapErr (\_ -> {})
reconstructPath : Dict position position, position -> List position
reconstructPath = \cameFrom, goal ->
when Dict.get cameFrom goal is
Err _ -> []
Ok next -> List.append (reconstructPath cameFrom next) goal
updateCost : position, position, Model position -> Model position
updateCost = \current, neighbor, model ->
newCameFrom =
Dict.insert model.cameFrom neighbor current
newCosts =
Dict.insert model.costs neighbor distanceTo
distanceTo =
reconstructPath newCameFrom neighbor
|> List.len
|> Num.toFrac
newModel =
{ model &
costs: newCosts,
cameFrom: newCameFrom,
}
when Dict.get model.costs neighbor is
Err _ ->
newModel
Ok previousDistance ->
if distanceTo < previousDistance then
newModel
else
model
astar : (position, position -> F64), (position -> Set position), position, Model position -> Result (List position) {}
astar = \costFn, moveFn, goal, model ->
when cheapestOpen (\source -> costFn source goal) model is
Err {} -> Err {}
Ok current ->
if current == goal then
Ok (reconstructPath model.cameFrom goal)
else
modelPopped =
{ model &
openSet: Set.remove model.openSet current,
evaluated: Set.insert model.evaluated current,
}
neighbors =
moveFn current
newNeighbors =
Set.difference neighbors modelPopped.evaluated
modelWithNeighbors : Model position
modelWithNeighbors =
{ modelPopped &
openSet: Set.union modelPopped.openSet newNeighbors,
}
walker : Model position, position -> Model position
walker = \amodel, n -> updateCost current n amodel
modelWithCosts =
Set.walk newNeighbors modelWithNeighbors walker
astar costFn moveFn goal modelWithCosts
# takeStep = \moveFn, _goal, model, current ->
# modelPopped =
# { model &
# openSet: Set.remove model.openSet current,
# evaluated: Set.insert model.evaluated current,
# }
#
# neighbors = moveFn current
#
# newNeighbors = Set.difference neighbors modelPopped.evaluated
#
# modelWithNeighbors = { modelPopped & openSet: Set.union modelPopped.openSet newNeighbors }
#
# # a lot goes wrong here
# modelWithCosts =
# Set.walk newNeighbors modelWithNeighbors (\n, m -> updateCost current n m)
#
# modelWithCosts

View file

@ -0,0 +1,35 @@
interface Base64 exposes [fromBytes, fromStr, toBytes, toStr] imports [Base64.Decode, Base64.Encode]
# base 64 encoding from a sequence of bytes
fromBytes : List U8 -> Result Str [InvalidInput]*
fromBytes = \bytes ->
when Base64.Decode.fromBytes bytes is
Ok v ->
Ok v
Err _ ->
Err InvalidInput
# base 64 encoding from a string
fromStr : Str -> Result Str [InvalidInput]*
fromStr = \str ->
fromBytes (Str.toUtf8 str)
# base64-encode bytes to the original
toBytes : Str -> Result (List U8) [InvalidInput]*
toBytes = \str ->
Ok (Base64.Encode.toBytes str)
toStr : Str -> Result Str [InvalidInput]*
toStr = \str ->
when toBytes str is
Ok bytes ->
when Str.fromUtf8 bytes is
Ok v ->
Ok v
Err _ ->
Err InvalidInput
Err _ ->
Err InvalidInput

View file

@ -0,0 +1,120 @@
interface Base64.Decode exposes [fromBytes] imports [Bytes.Decode.{ ByteDecoder, DecodeProblem }]
fromBytes : List U8 -> Result Str DecodeProblem
fromBytes = \bytes ->
Bytes.Decode.decode bytes (decodeBase64 (List.len bytes))
decodeBase64 : Nat -> ByteDecoder Str
decodeBase64 = \width -> Bytes.Decode.loop loopHelp { remaining: width, string: "" }
loopHelp : { remaining : Nat, string : Str } -> ByteDecoder (Bytes.Decode.Step { remaining : Nat, string : Str } Str)
loopHelp = \{ remaining, string } ->
if remaining >= 3 then
x, y, z <- Bytes.Decode.map3 Bytes.Decode.u8 Bytes.Decode.u8 Bytes.Decode.u8
a : U32
a = Num.intCast x
b : U32
b = Num.intCast y
c : U32
c = Num.intCast z
combined = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy a 16) (Num.shiftLeftBy b 8)) c
Loop {
remaining: remaining - 3,
string: Str.concat string (bitsToChars combined 0),
}
else if remaining == 0 then
Bytes.Decode.succeed (Done string)
else if remaining == 2 then
x, y <- Bytes.Decode.map2 Bytes.Decode.u8 Bytes.Decode.u8
a : U32
a = Num.intCast x
b : U32
b = Num.intCast y
combined = Num.bitwiseOr (Num.shiftLeftBy a 16) (Num.shiftLeftBy b 8)
Done (Str.concat string (bitsToChars combined 1))
else
# remaining = 1
x <- Bytes.Decode.map Bytes.Decode.u8
a : U32
a = Num.intCast x
Done (Str.concat string (bitsToChars (Num.shiftLeftBy a 16) 2))
bitsToChars : U32, Int * -> Str
bitsToChars = \bits, missing ->
when Str.fromUtf8 (bitsToCharsHelp bits missing) is
Ok str -> str
Err _ -> ""
# Mask that can be used to get the lowest 6 bits of a binary number
lowest6BitsMask : Int *
lowest6BitsMask = 63
bitsToCharsHelp : U32, Int * -> List U8
bitsToCharsHelp = \bits, missing ->
# The input is 24 bits, which we have to partition into 4 6-bit segments. We achieve this by
# shifting to the right by (a multiple of) 6 to remove unwanted bits on the right, then `Num.bitwiseAnd`
# with `0b111111` (which is 2^6 - 1 or 63) (so, 6 1s) to remove unwanted bits on the left.
# any 6-bit number is a valid base64 digit, so this is actually safe
p =
Num.shiftRightZfBy bits 18
|> Num.intCast
|> unsafeToChar
q =
Num.bitwiseAnd (Num.shiftRightZfBy bits 12) lowest6BitsMask
|> Num.intCast
|> unsafeToChar
r =
Num.bitwiseAnd (Num.shiftRightZfBy bits 6) lowest6BitsMask
|> Num.intCast
|> unsafeToChar
s =
Num.bitwiseAnd bits lowest6BitsMask
|> Num.intCast
|> unsafeToChar
equals : U8
equals = 61
when missing is
0 -> [p, q, r, s]
1 -> [p, q, r, equals]
2 -> [p, q, equals, equals]
_ ->
# unreachable
[]
# Base64 index to character/digit
unsafeToChar : U8 -> U8
unsafeToChar = \n ->
if n <= 25 then
# uppercase characters
65 + n
else if n <= 51 then
# lowercase characters
97 + (n - 26)
else if n <= 61 then
# digit characters
48 + (n - 52)
else
# special cases
when n is
62 ->
# '+'
43
63 ->
# '/'
47
_ ->
# anything else is invalid '\u{0000}'
0

View file

@ -0,0 +1,176 @@
interface Base64.Encode
exposes [toBytes]
imports [Bytes.Encode.{ ByteEncoder }]
InvalidChar : U8
# State : [None, One U8, Two U8, Three U8]
toBytes : Str -> List U8
toBytes = \str ->
str
|> Str.toUtf8
|> encodeChunks
|> Bytes.Encode.sequence
|> Bytes.Encode.encode
encodeChunks : List U8 -> List ByteEncoder
encodeChunks = \bytes ->
List.walk bytes { output: [], accum: None } folder
|> encodeResidual
coerce : Nat, a -> a
coerce = \_, x -> x
# folder : { output : List ByteEncoder, accum : State }, U8 -> { output : List ByteEncoder, accum : State }
folder = \{ output, accum }, char ->
when accum is
Unreachable n -> coerce n { output, accum: Unreachable n }
None -> { output, accum: One char }
One a -> { output, accum: Two a char }
Two a b -> { output, accum: Three a b char }
Three a b c ->
when encodeCharacters a b c char is
Ok encoder ->
{
output: List.append output encoder,
accum: None,
}
Err _ ->
{ output, accum: None }
# SGVs bG8g V29y bGQ=
# encodeResidual : { output : List ByteEncoder, accum : State } -> List ByteEncoder
encodeResidual = \{ output, accum } ->
when accum is
Unreachable _ -> output
None -> output
One _ -> output
Two a b ->
when encodeCharacters a b equals equals is
Ok encoder -> List.append output encoder
Err _ -> output
Three a b c ->
when encodeCharacters a b c equals is
Ok encoder -> List.append output encoder
Err _ -> output
equals : U8
equals = 61
# Convert 4 characters to 24 bits (as an ByteEncoder)
encodeCharacters : U8, U8, U8, U8 -> Result ByteEncoder InvalidChar
encodeCharacters = \a, b, c, d ->
if !(isValidChar a) then
Err a
else if !(isValidChar b) then
Err b
else
# `=` is the padding character, and must be special-cased
# only the `c` and `d` char are allowed to be padding
n1 = unsafeConvertChar a
n2 = unsafeConvertChar b
x : U32
x = Num.intCast n1
y : U32
y = Num.intCast n2
if d == equals then
if c == equals then
n = Num.bitwiseOr (Num.shiftLeftBy x 18) (Num.shiftLeftBy y 12)
# masking higher bits is not needed, Encode.unsignedInt8 ignores higher bits
b1 : U8
b1 = Num.intCast (Num.shiftRightBy n 16)
Ok (Bytes.Encode.u8 b1)
else if !(isValidChar c) then
Err c
else
n3 = unsafeConvertChar c
z : U32
z = Num.intCast n3
n = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy x 18) (Num.shiftLeftBy y 12)) (Num.shiftLeftBy z 6)
combined : U16
combined = Num.intCast (Num.shiftRightBy n 8)
Ok (Bytes.Encode.u16 BE combined)
else if !(isValidChar d) then
Err d
else
n3 = unsafeConvertChar c
n4 = unsafeConvertChar d
z : U32
z = Num.intCast n3
w : U32
w = Num.intCast n4
n =
Num.bitwiseOr
(Num.bitwiseOr (Num.shiftLeftBy x 18) (Num.shiftLeftBy y 12))
(Num.bitwiseOr (Num.shiftLeftBy z 6) w)
b3 : U8
b3 = Num.intCast n
combined : U16
combined = Num.intCast (Num.shiftRightBy n 8)
Ok (Bytes.Encode.sequence [Bytes.Encode.u16 BE combined, Bytes.Encode.u8 b3])
# is the character a base64 digit?
# The base16 digits are: A-Z, a-z, 0-1, '+' and '/'
isValidChar : U8 -> Bool
isValidChar = \c ->
if isAlphaNum c then
Bool.true
else
when c is
43 ->
# '+'
Bool.true
47 ->
# '/'
Bool.true
_ ->
Bool.false
isAlphaNum : U8 -> Bool
isAlphaNum = \key ->
(key >= 48 && key <= 57) || (key >= 64 && key <= 90) || (key >= 97 && key <= 122)
# Convert a base64 character/digit to its index
# See also [Wikipedia](https://en.wikipedia.org/wiki/Base64#Base64_table)
unsafeConvertChar : U8 -> U8
unsafeConvertChar = \key ->
if key >= 65 && key <= 90 then
# A-Z
key - 65
else if key >= 97 && key <= 122 then
# a-z
(key - 97) + 26
else if key >= 48 && key <= 57 then
# 0-9
(key - 48) + 26 + 26
else
when key is
43 ->
# '+'
62
47 ->
# '/'
63
_ ->
0

View file

@ -0,0 +1,111 @@
interface Bytes.Decode exposes [ByteDecoder, decode, map, map2, u8, loop, Step, succeed, DecodeProblem, after, map3] imports []
State : { bytes : List U8, cursor : Nat }
DecodeProblem : [OutOfBytes]
ByteDecoder a := State -> [Good State a, Bad DecodeProblem]
decode : List U8, ByteDecoder a -> Result a DecodeProblem
decode = \bytes, @ByteDecoder decoder ->
when decoder { bytes, cursor: 0 } is
Good _ value ->
Ok value
Bad e ->
Err e
succeed : a -> ByteDecoder a
succeed = \value -> @ByteDecoder \state -> Good state value
map : ByteDecoder a, (a -> b) -> ByteDecoder b
map = \@ByteDecoder decoder, transform ->
@ByteDecoder
\state ->
when decoder state is
Good state1 value ->
Good state1 (transform value)
Bad e ->
Bad e
map2 : ByteDecoder a, ByteDecoder b, (a, b -> c) -> ByteDecoder c
map2 = \@ByteDecoder decoder1, @ByteDecoder decoder2, transform ->
@ByteDecoder
\state1 ->
when decoder1 state1 is
Good state2 a ->
when decoder2 state2 is
Good state3 b ->
Good state3 (transform a b)
Bad e ->
Bad e
Bad e ->
Bad e
map3 : ByteDecoder a, ByteDecoder b, ByteDecoder c, (a, b, c -> d) -> ByteDecoder d
map3 = \@ByteDecoder decoder1, @ByteDecoder decoder2, @ByteDecoder decoder3, transform ->
@ByteDecoder
\state1 ->
when decoder1 state1 is
Good state2 a ->
when decoder2 state2 is
Good state3 b ->
when decoder3 state3 is
Good state4 c ->
Good state4 (transform a b c)
Bad e ->
Bad e
Bad e ->
Bad e
Bad e ->
Bad e
after : ByteDecoder a, (a -> ByteDecoder b) -> ByteDecoder b
after = \@ByteDecoder decoder, transform ->
@ByteDecoder
\state ->
when decoder state is
Good state1 value ->
(@ByteDecoder decoder1) = transform value
decoder1 state1
Bad e ->
Bad e
u8 : ByteDecoder U8
u8 = @ByteDecoder
\state ->
when List.get state.bytes state.cursor is
Ok b ->
Good { state & cursor: state.cursor + 1 } b
Err _ ->
Bad OutOfBytes
Step state b : [Loop state, Done b]
loop : (state -> ByteDecoder (Step state a)), state -> ByteDecoder a
loop = \stepper, initial ->
@ByteDecoder
\state ->
loopHelp stepper initial state
loopHelp = \stepper, accum, state ->
(@ByteDecoder stepper1) = stepper accum
when stepper1 state is
Good newState (Done value) ->
Good newState value
Good newState (Loop newAccum) ->
loopHelp stepper newAccum newState
Bad e ->
Bad e

View file

@ -0,0 +1,134 @@
interface Bytes.Encode exposes [ByteEncoder, sequence, u8, u16, bytes, empty, encode] imports []
Endianness : [BE, LE]
ByteEncoder : [Signed8 I8, Unsigned8 U8, Signed16 Endianness I16, Unsigned16 Endianness U16, Sequence Nat (List ByteEncoder), Bytes (List U8)]
u8 : U8 -> ByteEncoder
u8 = \value -> Unsigned8 value
empty : ByteEncoder
empty =
foo : List ByteEncoder
foo = []
Sequence 0 foo
u16 : Endianness, U16 -> ByteEncoder
u16 = \endianness, value -> Unsigned16 endianness value
bytes : List U8 -> ByteEncoder
bytes = \bs -> Bytes bs
sequence : List ByteEncoder -> ByteEncoder
sequence = \encoders ->
Sequence (getWidths encoders 0) encoders
getWidth : ByteEncoder -> Nat
getWidth = \encoder ->
when encoder is
Signed8 _ -> 1
Unsigned8 _ -> 1
Signed16 _ _ -> 2
Unsigned16 _ _ -> 2
# Signed32 _ -> 4
# Unsigned32 _ -> 4
# Signed64 _ -> 8
# Unsigned64 _ -> 8
# Signed128 _ -> 16
# Unsigned128 _ -> 16
Sequence w _ -> w
Bytes bs -> List.len bs
getWidths : List ByteEncoder, Nat -> Nat
getWidths = \encoders, initial ->
List.walk encoders initial \accum, encoder -> accum + getWidth encoder
encode : ByteEncoder -> List U8
encode = \encoder ->
output = List.repeat 0 (getWidth encoder)
encodeHelp encoder 0 output
|> .output
encodeHelp : ByteEncoder, Nat, List U8 -> { output : List U8, offset : Nat }
encodeHelp = \encoder, offset, output ->
when encoder is
Unsigned8 value ->
{
output: List.set output offset value,
offset: offset + 1,
}
Signed8 value ->
cast : U8
cast = Num.intCast value
{
output: List.set output offset cast,
offset: offset + 1,
}
Unsigned16 endianness value ->
a : U8
a = Num.intCast (Num.shiftRightBy value 8)
b : U8
b = Num.intCast value
newOutput =
when endianness is
BE ->
output
|> List.set (offset + 0) a
|> List.set (offset + 1) b
LE ->
output
|> List.set (offset + 0) b
|> List.set (offset + 1) a
{
output: newOutput,
offset: offset + 2,
}
Signed16 endianness value ->
a : U8
a = Num.intCast (Num.shiftRightBy value 8)
b : U8
b = Num.intCast value
newOutput =
when endianness is
BE ->
output
|> List.set (offset + 0) a
|> List.set (offset + 1) b
LE ->
output
|> List.set (offset + 0) b
|> List.set (offset + 1) a
{
output: newOutput,
offset: offset + 1,
}
Bytes bs ->
List.walk
bs
{ output, offset }
\accum, byte -> {
offset: accum.offset + 1,
output: List.set accum.output offset byte,
}
Sequence _ encoders ->
List.walk
encoders
{ output, offset }
\accum, single ->
encodeHelp single accum.offset accum.output

View file

@ -0,0 +1,130 @@
app "cfold"
packages { pf: "platform/main.roc" }
imports [pf.Task]
provides [main] to pf
# adapted from https://github.com/koka-lang/koka/blob/master/test/bench/haskell/cfold.hs
main : Task.Task {} []
main =
Task.after
Task.getInt
\n ->
e = mkExpr n 1 # original koka n = 20 (set `ulimit -s unlimited` to avoid stack overflow for n = 20)
unoptimized = eval e
optimized = eval (constFolding (reassoc e))
unoptimized
|> Num.toStr
|> Str.concat " & "
|> Str.concat (Num.toStr optimized)
|> Task.putLine
Expr : [
Add Expr Expr,
Mul Expr Expr,
Val I64,
Var I64,
]
mkExpr : I64, I64 -> Expr
mkExpr = \n, v ->
when n is
0 ->
if v == 0 then Var 1 else Val v
_ ->
Add (mkExpr (n - 1) (v + 1)) (mkExpr (n - 1) (max (v - 1) 0))
max : I64, I64 -> I64
max = \a, b -> if a > b then a else b
appendAdd : Expr, Expr -> Expr
appendAdd = \e1, e2 ->
when e1 is
Add a1 a2 ->
Add a1 (appendAdd a2 e2)
_ ->
Add e1 e2
appendMul : Expr, Expr -> Expr
appendMul = \e1, e2 ->
when e1 is
Mul a1 a2 ->
Mul a1 (appendMul a2 e2)
_ ->
Mul e1 e2
eval : Expr -> I64
eval = \e ->
when e is
Var _ ->
0
Val v ->
v
Add l r ->
eval l + eval r
Mul l r ->
eval l * eval r
reassoc : Expr -> Expr
reassoc = \e ->
when e is
Add e1 e2 ->
x1 = reassoc e1
x2 = reassoc e2
appendAdd x1 x2
Mul e1 e2 ->
x1 = reassoc e1
x2 = reassoc e2
appendMul x1 x2
_ ->
e
constFolding : Expr -> Expr
constFolding = \e ->
when e is
Add e1 e2 ->
x1 = constFolding e1
x2 = constFolding e2
when Pair x1 x2 is
Pair (Val a) (Val b) ->
Val (a + b)
Pair (Val a) (Add (Val b) x) ->
Add (Val (a + b)) x
Pair (Val a) (Add x (Val b)) ->
Add (Val (a + b)) x
Pair y1 y2 ->
Add y1 y2
Mul e1 e2 ->
x1 = constFolding e1
x2 = constFolding e2
when Pair x1 x2 is
Pair (Val a) (Val b) ->
Val (a * b)
Pair (Val a) (Mul (Val b) x) ->
Mul (Val (a * b)) x
Pair (Val a) (Mul x (Val b)) ->
Mul (Val (a * b)) x
Pair y1 y2 ->
Add y1 y2
_ ->
e

View file

@ -0,0 +1,51 @@
app "closure"
packages { pf: "platform/main.roc" }
imports [pf.Task]
provides [main] to pf
# see https://github.com/roc-lang/roc/issues/985
main : Task.Task {} []
main = closure1 {}
# |> Task.after (\_ -> closure2 {})
# |> Task.after (\_ -> closure3 {})
# |> Task.after (\_ -> closure4 {})
# ---
closure1 : {} -> Task.Task {} []
closure1 = \_ ->
Task.succeed (foo toUnitBorrowed "a long string such that it's malloced")
|> Task.map \_ -> {}
toUnitBorrowed = \x -> Str.countGraphemes x
foo = \f, x -> f x
# ---
# closure2 : {} -> Task.Task {} []
# closure2 = \_ ->
# x : Str
# x = "a long string such that it's malloced"
#
# Task.succeed {}
# |> Task.map (\_ -> x)
# |> Task.map toUnit
#
# toUnit = \_ -> {}
#
# # ---
# closure3 : {} -> Task.Task {} []
# closure3 = \_ ->
# x : Str
# x = "a long string such that it's malloced"
#
# Task.succeed {}
# |> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {}))
#
# # ---
# closure4 : {} -> Task.Task {} []
# closure4 = \_ ->
# x : Str
# x = "a long string such that it's malloced"
#
# Task.succeed {}
# |> Task.after (\_ -> Task.succeed x)
# |> Task.map (\_ -> {})

View file

@ -0,0 +1,167 @@
app "deriv"
packages { pf: "platform/main.roc" }
imports [pf.Task]
provides [main] to pf
# based on: https://github.com/koka-lang/koka/blob/master/test/bench/haskell/deriv.hs
IO a : Task.Task a []
main : Task.Task {} []
main =
Task.after
Task.getInt
\n ->
x : Expr
x = Var "x"
f : Expr
f = pow x x
nest deriv n f # original koka n = 10
|> Task.map \_ -> {}
nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr
nest = \f, n, e -> Task.loop { s: n, f, m: n, x: e } nestHelp
State : { s : I64, f : I64, Expr -> IO Expr, m : I64, x : Expr }
nestHelp : State -> IO [Step State, Done Expr]
nestHelp = \{ s, f, m, x } ->
when m is
0 -> Task.succeed (Done x)
_ ->
w <- Task.after (f (s - m) x)
Task.succeed (Step { s, f, m: (m - 1), x: w })
Expr : [Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr]
divmod : I64, I64 -> Result { div : I64, mod : I64 } [DivByZero]*
divmod = \l, r ->
when Pair (Num.divTruncChecked l r) (Num.remChecked l r) is
Pair (Ok div) (Ok mod) -> Ok { div, mod }
_ -> Err DivByZero
pown : I64, I64 -> I64
pown = \a, n ->
when n is
0 -> 1
1 -> a
_ ->
when divmod n 2 is
Ok { div, mod } ->
b = pown a div
b * b * (if mod == 0 then 1 else a)
Err DivByZero ->
-1
add : Expr, Expr -> Expr
add = \a, b ->
when Pair a b is
Pair (Val n) (Val m) ->
Val (n + m)
Pair (Val 0) f ->
f
Pair f (Val 0) ->
f
Pair f (Val n) ->
add (Val n) f
Pair (Val n) (Add (Val m) f) ->
add (Val (n + m)) f
Pair f (Add (Val n) g) ->
add (Val n) (add f g)
Pair (Add f g) h ->
add f (add g h)
Pair f g ->
Add f g
mul : Expr, Expr -> Expr
mul = \a, b ->
when Pair a b is
Pair (Val n) (Val m) ->
Val (n * m)
Pair (Val 0) _ ->
Val 0
Pair _ (Val 0) ->
Val 0
Pair (Val 1) f ->
f
Pair f (Val 1) ->
f
Pair f (Val n) ->
mul (Val n) f
Pair (Val n) (Mul (Val m) f) ->
mul (Val (n * m)) f
Pair f (Mul (Val n) g) ->
mul (Val n) (mul f g)
Pair (Mul f g) h ->
mul f (mul g h)
Pair f g ->
Mul f g
pow : Expr, Expr -> Expr
pow = \a, b ->
when Pair a b is
Pair (Val m) (Val n) -> Val (pown m n)
Pair _ (Val 0) -> Val 1
Pair f (Val 1) -> f
Pair (Val 0) _ -> Val 0
Pair f g -> Pow f g
ln : Expr -> Expr
ln = \f ->
when f is
Val 1 -> Val 0
_ -> Ln f
d : Str, Expr -> Expr
d = \x, expr ->
when expr is
Val _ -> Val 0
Var y -> if x == y then Val 1 else Val 0
Add f g -> add (d x f) (d x g)
Mul f g -> add (mul f (d x g)) (mul g (d x f))
Pow f g ->
mul (pow f g) (add (mul (mul g (d x f)) (pow f (Val (-1)))) (mul (ln f) (d x g)))
Ln f ->
mul (d x f) (pow f (Val (-1)))
count : Expr -> I64
count = \expr ->
when expr is
Val _ -> 1
Var _ -> 1
Add f g -> count f + count g
Mul f g -> count f + count g
Pow f g -> count f + count g
Ln f -> count f
deriv : I64, Expr -> IO Expr
deriv = \i, f ->
fprime = d "x" f
line =
Num.toStr (i + 1)
|> Str.concat " count: "
|> Str.concat (Num.toStr (count fprime))
Task.putLine line
|> Task.after \_ -> Task.succeed fprime

View file

@ -0,0 +1,13 @@
app "issue2279"
packages { pf: "platform/main.roc" }
imports [Issue2279Help, pf.Task]
provides [main] to pf
main =
text =
if Bool.true then
Issue2279Help.text
else
Issue2279Help.asText 42
Task.putLine text

View file

@ -0,0 +1,7 @@
interface Issue2279Help
exposes [text, asText]
imports []
text = "Hello, world!"
asText = Num.toStr

View file

@ -0,0 +1,53 @@
app "nqueens"
packages { pf: "platform/main.roc" }
imports [pf.Task]
provides [main] to pf
main : Task.Task {} []
main =
Task.after
Task.getInt
\n ->
queens n # original koka 13
|> Num.toStr
|> Task.putLine
ConsList a : [Nil, Cons a (ConsList a)]
queens = \n -> length (findSolutions n n)
length : ConsList a -> I64
length = \xs -> lengthHelp xs 0
lengthHelp : ConsList a, I64 -> I64
lengthHelp = \foobar, acc ->
when foobar is
Cons _ lrest -> lengthHelp lrest (1 + acc)
Nil -> acc
safe : I64, I64, ConsList I64 -> Bool
safe = \queen, diagonal, xs ->
when xs is
Nil -> Bool.true
Cons q t ->
queen != q && queen != q + diagonal && queen != q - diagonal && safe queen (diagonal + 1) t
appendSafe : I64, ConsList I64, ConsList (ConsList I64) -> ConsList (ConsList I64)
appendSafe = \k, soln, solns ->
if k <= 0 then
solns
else if safe k 1 soln then
appendSafe (k - 1) soln (Cons (Cons k soln) solns)
else
appendSafe (k - 1) soln solns
extend = \n, acc, solutions ->
when solutions is
Nil -> acc
Cons soln rest -> extend n (appendSafe n soln acc) rest
findSolutions = \n, k ->
if k == 0 then
Cons Nil Nil
else
extend n Nil (findSolutions n (k - 1))

View file

@ -0,0 +1,75 @@
interface Quicksort exposes [sortBy, sortWith, show] imports []
show : List I64 -> Str
show = \list ->
if List.isEmpty list then
"[]"
else
content =
list
|> List.map Num.toStr
|> Str.joinWith ", "
"[\(content)]"
sortBy : List a, (a -> Num *) -> List a
sortBy = \list, toComparable ->
sortWith list (\x, y -> Num.compare (toComparable x) (toComparable y))
Order a : a, a -> [LT, GT, EQ]
sortWith : List a, (a, a -> [LT, GT, EQ]) -> List a
sortWith = \list, order ->
n = List.len list
quicksortHelp list order 0 (n - 1)
quicksortHelp : List a, Order a, Nat, Nat -> List a
quicksortHelp = \list, order, low, high ->
if low < high then
when partition low high list order is
Pair partitionIndex partitioned ->
partitioned
|> quicksortHelp order low (Num.subSaturated partitionIndex 1)
|> quicksortHelp order (partitionIndex + 1) high
else
list
partition : Nat, Nat, List a, Order a -> [Pair Nat (List a)]
partition = \low, high, initialList, order ->
when List.get initialList high is
Ok pivot ->
when partitionHelp low low initialList order high pivot is
Pair newI newList ->
Pair newI (swap newI high newList)
Err _ ->
Pair low initialList
partitionHelp : Nat, Nat, List c, Order c, Nat, c -> [Pair Nat (List c)]
partitionHelp = \i, j, list, order, high, pivot ->
if j < high then
when List.get list j is
Ok value ->
when order value pivot is
LT | EQ ->
partitionHelp (i + 1) (j + 1) (swap i j list) order high pivot
GT ->
partitionHelp i (j + 1) list order high pivot
Err _ ->
Pair i list
else
Pair i list
swap : Nat, Nat, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
list
|> List.set i atJ
|> List.set j atI
_ ->
[]

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,131 @@
app "rbtree-ck"
packages { pf: "platform/main.roc" }
imports [pf.Task]
provides [main] to pf
Color : [Red, Black]
Tree a b : [Leaf, Node Color (Tree a b) a b (Tree a b)]
Map : Tree I64 Bool
ConsList a : [Nil, Cons a (ConsList a)]
makeMap : I64, I64 -> ConsList Map
makeMap = \freq, n ->
makeMapHelp freq n Leaf Nil
makeMapHelp : I64, I64, Map, ConsList Map -> ConsList Map
makeMapHelp = \freq, n, m, acc ->
when n is
0 -> Cons m acc
_ ->
powerOf10 =
n % 10 == 0
m1 = insert m n powerOf10
isFrequency =
n % freq == 0
x = (if isFrequency then Cons m1 acc else acc)
makeMapHelp freq (n - 1) m1 x
fold : (a, b, omega -> omega), Tree a b, omega -> omega
fold = \f, tree, b ->
when tree is
Leaf -> b
Node _ l k v r -> fold f r (f k v (fold f l b))
main : Task.Task {} []
main =
Task.after
Task.getInt
\n ->
# original koka n = 4_200_000
ms : ConsList Map
ms = makeMap 5 n
when ms is
Cons head _ ->
val = fold (\_, v, r -> if v then r + 1 else r) head 0
val
|> Num.toStr
|> Task.putLine
Nil ->
Task.putLine "fail"
insert : Tree (Num k) v, Num k, v -> Tree (Num k) v
insert = \t, k, v -> if isRed t then setBlack (ins t k v) else ins t k v
setBlack : Tree a b -> Tree a b
setBlack = \tree ->
when tree is
Node _ l k v r -> Node Black l k v r
_ -> tree
isRed : Tree a b -> Bool
isRed = \tree ->
when tree is
Node Red _ _ _ _ -> Bool.true
_ -> Bool.false
lt = \x, y -> x < y
ins : Tree (Num k) v, Num k, v -> Tree (Num k) v
ins = \tree, kx, vx ->
when tree is
Leaf -> Node Red Leaf kx vx Leaf
Node Red a ky vy b ->
if lt kx ky then
Node Red (ins a kx vx) ky vy b
else if lt ky kx then
Node Red a ky vy (ins b kx vx)
else
Node Red a ky vy (ins b kx vx)
Node Black a ky vy b ->
if lt kx ky then
(if isRed a then balance1 (Node Black Leaf ky vy b) (ins a kx vx) else Node Black (ins a kx vx) ky vy b)
else if lt ky kx then
(if isRed b then balance2 (Node Black a ky vy Leaf) (ins b kx vx) else Node Black a ky vy (ins b kx vx))
else
Node Black a kx vx b
balance1 : Tree a b, Tree a b -> Tree a b
balance1 = \tree1, tree2 ->
when tree1 is
Leaf -> Leaf
Node _ _ kv vv t ->
when tree2 is
Node _ (Node Red l kx vx r1) ky vy r2 ->
Node Red (Node Black l kx vx r1) ky vy (Node Black r2 kv vv t)
Node _ l1 ky vy (Node Red l2 kx vx r) ->
Node Red (Node Black l1 ky vy l2) kx vx (Node Black r kv vv t)
Node _ l ky vy r ->
Node Black (Node Red l ky vy r) kv vv t
Leaf -> Leaf
balance2 : Tree a b, Tree a b -> Tree a b
balance2 = \tree1, tree2 ->
when tree1 is
Leaf -> Leaf
Node _ t kv vv _ ->
when tree2 is
Node _ (Node Red l kx1 vx1 r1) ky vy r2 ->
Node Red (Node Black t kv vv l) kx1 vx1 (Node Black r1 ky vy r2)
Node _ l1 ky vy (Node Red l2 kx2 vx2 r2) ->
Node Red (Node Black t kv vv l1) ky vy (Node Black l2 kx2 vx2 r2)
Node _ l ky vy r ->
Node Black t kv vv (Node Red l ky vy r)
Leaf ->
Leaf

View file

@ -0,0 +1,247 @@
app "rbtree-del"
packages { pf: "platform/main.roc" }
imports [pf.Task]
provides [main] to pf
Color : [Red, Black]
Tree a b : [Leaf, Node Color (Tree a b) a b (Tree a b)]
Map : Tree I64 Bool
ConsList a : [Nil, Cons a (ConsList a)]
main : Task.Task {} []
main =
Task.after
Task.getInt
\n ->
m = makeMap n # koka original n = 4_200_000
val = fold (\_, v, r -> if v then r + 1 else r) m 0
val
|> Num.toStr
|> Task.putLine
boom : Str -> a
boom = \_ -> boom ""
makeMap : I64 -> Map
makeMap = \n ->
makeMapHelp n n Leaf
makeMapHelp : I64, I64, Map -> Map
makeMapHelp = \total, n, m ->
when n is
0 -> m
_ ->
n1 = n - 1
powerOf10 =
n |> Num.isMultipleOf 10
t1 = insert m n powerOf10
isFrequency =
n |> Num.isMultipleOf 4
key = n1 + ((total - n1) // 5)
t2 = if isFrequency then delete t1 key else t1
makeMapHelp total n1 t2
fold : (a, b, omega -> omega), Tree a b, omega -> omega
fold = \f, tree, b ->
when tree is
Leaf -> b
Node _ l k v r -> fold f r (f k v (fold f l b))
depth : Tree * * -> I64
depth = \tree ->
when tree is
Leaf -> 1
Node _ l _ _ r -> 1 + depth l + depth r
insert : Map, I64, Bool -> Map
insert = \t, k, v -> if isRed t then setBlack (ins t k v) else ins t k v
setBlack : Tree a b -> Tree a b
setBlack = \tree ->
when tree is
Node _ l k v r -> Node Black l k v r
_ -> tree
isRed : Tree a b -> Bool
isRed = \tree ->
when tree is
Node Red _ _ _ _ -> Bool.true
_ -> Bool.false
ins : Tree I64 Bool, I64, Bool -> Tree I64 Bool
ins = \tree, kx, vx ->
when tree is
Leaf ->
Node Red Leaf kx vx Leaf
Node Red a ky vy b ->
when Num.compare kx ky is
LT -> Node Red (ins a kx vx) ky vy b
GT -> Node Red a ky vy (ins b kx vx)
EQ -> Node Red a ky vy (ins b kx vx)
Node Black a ky vy b ->
when Num.compare kx ky is
LT ->
when isRed a is
Bool.true -> balanceLeft (ins a kx vx) ky vy b
Bool.false -> Node Black (ins a kx vx) ky vy b
GT ->
when isRed b is
Bool.true -> balanceRight a ky vy (ins b kx vx)
Bool.false -> Node Black a ky vy (ins b kx vx)
EQ ->
Node Black a kx vx b
balanceLeft : Tree a b, a, b, Tree a b -> Tree a b
balanceLeft = \l, k, v, r ->
when l is
Leaf ->
Leaf
Node _ (Node Red lx kx vx rx) ky vy ry ->
Node Red (Node Black lx kx vx rx) ky vy (Node Black ry k v r)
Node _ ly ky vy (Node Red lx kx vx rx) ->
Node Red (Node Black ly ky vy lx) kx vx (Node Black rx k v r)
Node _ lx kx vx rx ->
Node Black (Node Red lx kx vx rx) k v r
balanceRight : Tree a b, a, b, Tree a b -> Tree a b
balanceRight = \l, k, v, r ->
when r is
Leaf ->
Leaf
Node _ (Node Red lx kx vx rx) ky vy ry ->
Node Red (Node Black l k v lx) kx vx (Node Black rx ky vy ry)
Node _ lx kx vx (Node Red ly ky vy ry) ->
Node Red (Node Black l k v lx) kx vx (Node Black ly ky vy ry)
Node _ lx kx vx rx ->
Node Black l k v (Node Red lx kx vx rx)
isBlack : Color -> Bool
isBlack = \c ->
when c is
Black -> Bool.true
Red -> Bool.false
Del a b : [Del (Tree a b) Bool]
setRed : Map -> Map
setRed = \t ->
when t is
Node _ l k v r ->
Node Red l k v r
_ ->
t
makeBlack : Map -> Del I64 Bool
makeBlack = \t ->
when t is
Node Red l k v r ->
Del (Node Black l k v r) Bool.false
_ ->
Del t Bool.true
rebalanceLeft = \c, l, k, v, r ->
when l is
Node Black _ _ _ _ ->
Del (balanceLeft (setRed l) k v r) (isBlack c)
Node Red lx kx vx rx ->
Del (Node Black lx kx vx (balanceLeft (setRed rx) k v r)) Bool.false
_ ->
boom "unreachable"
rebalanceRight = \c, l, k, v, r ->
when r is
Node Black _ _ _ _ ->
Del (balanceRight l k v (setRed r)) (isBlack c)
Node Red lx kx vx rx ->
Del (Node Black (balanceRight l k v (setRed lx)) kx vx rx) Bool.false
_ ->
boom "unreachable"
delMin = \t ->
when t is
Node Black Leaf k v r ->
when r is
Leaf ->
Delmin (Del Leaf Bool.true) k v
_ ->
Delmin (Del (setBlack r) Bool.false) k v
Node Red Leaf k v r ->
Delmin (Del r Bool.false) k v
Node c l k v r ->
when delMin l is
Delmin (Del lx Bool.true) kx vx ->
Delmin (rebalanceRight c lx k v r) kx vx
Delmin (Del lx Bool.false) kx vx ->
Delmin (Del (Node c lx k v r) Bool.false) kx vx
Leaf ->
Delmin (Del t Bool.false) 0 Bool.false
delete : Tree I64 Bool, I64 -> Tree I64 Bool
delete = \t, k ->
when del t k is
Del tx _ ->
setBlack tx
del : Tree I64 Bool, I64 -> Del I64 Bool
del = \t, k ->
when t is
Leaf ->
Del Leaf Bool.false
Node cx lx kx vx rx ->
if (k < kx) then
when del lx k is
Del ly Bool.true ->
rebalanceRight cx ly kx vx rx
Del ly Bool.false ->
Del (Node cx ly kx vx rx) Bool.false
else if (k > kx) then
when del rx k is
Del ry Bool.true ->
rebalanceLeft cx lx kx vx ry
Del ry Bool.false ->
Del (Node cx lx kx vx ry) Bool.false
else
when rx is
Leaf ->
if isBlack cx then makeBlack lx else Del lx Bool.false
Node _ _ _ _ _ ->
when delMin rx is
Delmin (Del ry Bool.true) ky vy ->
rebalanceLeft cx lx ky vy ry
Delmin (Del ry Bool.false) ky vy ->
Del (Node cx lx ky vy ry) Bool.false

View file

@ -0,0 +1,101 @@
app "rbtree-insert"
packages { pf: "platform/main.roc" }
imports [pf.Task]
provides [main] to pf
main : Task.Task {} []
main =
tree : RedBlackTree I64 {}
tree = insert 0 {} Empty
tree
|> show
|> Task.putLine
show : RedBlackTree I64 {} -> Str
show = \tree -> showRBTree tree Num.toStr (\{} -> "{}")
showRBTree : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
showRBTree = \tree, showKey, showValue ->
when tree is
Empty -> "Empty"
Node color key value left right ->
sColor = showColor color
sKey = showKey key
sValue = showValue value
sL = nodeInParens left showKey showValue
sR = nodeInParens right showKey showValue
"Node \(sColor) \(sKey) \(sValue) \(sL) \(sR)"
nodeInParens : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
nodeInParens = \tree, showKey, showValue ->
when tree is
Empty ->
showRBTree tree showKey showValue
Node _ _ _ _ _ ->
inner = showRBTree tree showKey showValue
"(\(inner))"
showColor : NodeColor -> Str
showColor = \color ->
when color is
Red -> "Red"
Black -> "Black"
NodeColor : [Red, Black]
RedBlackTree k v : [Node NodeColor k v (RedBlackTree k v) (RedBlackTree k v), Empty]
Key k : Num k
insert : Key k, v, RedBlackTree (Key k) v -> RedBlackTree (Key k) v
insert = \key, value, dict ->
when insertHelp key value dict is
Node Red k v l r -> Node Black k v l r
x -> x
insertHelp : Key k, v, RedBlackTree (Key k) v -> RedBlackTree (Key k) v
insertHelp = \key, value, dict ->
when dict is
Empty ->
# New nodes are always red. If it violates the rules, it will be fixed
# when balancing.
Node Red key value Empty Empty
Node nColor nKey nValue nLeft nRight ->
when Num.compare key nKey is
LT -> balance nColor nKey nValue (insertHelp key value nLeft) nRight
EQ -> Node nColor nKey value nLeft nRight
GT -> balance nColor nKey nValue nLeft (insertHelp key value nRight)
balance : NodeColor, k, v, RedBlackTree k v, RedBlackTree k v -> RedBlackTree k v
balance = \color, key, value, left, right ->
when right is
Node Red rK rV rLeft rRight ->
when left is
Node Red lK lV lLeft lRight ->
Node
Red
key
value
(Node Black lK lV lLeft lRight)
(Node Black rK rV rLeft rRight)
_ ->
Node color rK rV (Node Red key value left rLeft) rRight
_ ->
when left is
Node Red lK lV (Node Red llK llV llLeft llRight) lRight ->
Node
Red
lK
lV
(Node Black llK llV llLeft llRight)
(Node Black key value lRight right)
_ ->
Node color key value left right

View file

@ -0,0 +1,46 @@
app "test-astar"
packages { pf: "platform/main.roc" }
imports [pf.Task, AStar]
provides [main] to pf
main : Task.Task {} []
main =
Task.putLine (showBool test1)
# Task.after Task.getInt \n ->
# when n is
# 1 ->
# Task.putLine (showBool test1)
#
# _ ->
# ns = Num.toStr n
# Task.putLine "No test \(ns)"
showBool : Bool -> Str
showBool = \b ->
if
b
then
"True"
else
"False"
test1 : Bool
test1 =
example1 == [2, 4]
example1 : List I64
example1 =
step : I64 -> Set I64
step = \n ->
when n is
1 -> Set.fromList [2, 3]
2 -> Set.fromList [4]
3 -> Set.fromList [4]
_ -> Set.fromList []
cost : I64, I64 -> F64
cost = \_, _ -> 1
when AStar.findPath cost step 1 4 is
Ok path -> path
Err _ -> []

View file

@ -0,0 +1,18 @@
app "test-base64"
packages { pf: "platform/main.roc" }
imports [pf.Task, Base64]
provides [main] to pf
IO a : Task.Task a []
main : IO {}
main =
when Base64.fromBytes (Str.toUtf8 "Hello World") is
Err _ -> Task.putLine "sadness"
Ok encoded ->
Task.after
(Task.putLine (Str.concat "encoded: " encoded))
\_ ->
when Base64.toStr encoded is
Ok decoded -> Task.putLine (Str.concat "decoded: " decoded)
Err _ -> Task.putLine "sadness"

View file

@ -0,0 +1,10 @@
hosted Effect
exposes [Effect, after, map, always, forever, loop, putLine, putInt, getInt]
imports []
generates Effect with [after, map, always, forever, loop]
putLine : Str -> Effect {}
putInt : I64 -> Effect {}
getInt : Effect { value : I64, isError : Bool }

View file

@ -0,0 +1,78 @@
interface Task
exposes [Task, succeed, fail, after, map, putLine, putInt, getInt, forever, loop]
imports [pf.Effect]
Task ok err : Effect.Effect (Result ok err)
forever : Task val err -> Task * err
forever = \task ->
looper = \{} ->
task
|> Effect.map
\res ->
when res is
Ok _ -> Step {}
Err e -> Done (Err e)
Effect.loop {} looper
loop : state, (state -> Task [Step state, Done done] err) -> Task done err
loop = \state, step ->
looper = \current ->
step current
|> Effect.map
\res ->
when res is
Ok (Step newState) -> Step newState
Ok (Done result) -> Done (Ok result)
Err e -> Done (Err e)
Effect.loop state looper
succeed : val -> Task val *
succeed = \val ->
Effect.always (Ok val)
fail : err -> Task * err
fail = \val ->
Effect.always (Err val)
after : Task a err, (a -> Task b err) -> Task b err
after = \effect, transform ->
Effect.after
effect
\result ->
when result is
Ok a -> transform a
Err err -> Task.fail err
map : Task a err, (a -> b) -> Task b err
map = \effect, transform ->
Effect.map
effect
\result ->
when result is
Ok a -> Ok (transform a)
Err err -> Err err
putLine : Str -> Task {} *
putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})
putInt : I64 -> Task {} *
putInt = \line -> Effect.map (Effect.putInt line) (\_ -> Ok {})
getInt : Task I64 []
getInt =
Effect.after
Effect.getInt
\{ isError, value } ->
if
isError
then
# when errorCode is
# # A -> Task.fail InvalidCharacter
# # B -> Task.fail IOError
# _ ->
Task.succeed -1
else
Task.succeed value

View file

@ -0,0 +1,217 @@
const std = @import("std");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
const maxInt = std.math.maxInt;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
const builtin = @import("builtin");
if (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed_generic([*]u8) void;
extern fn roc__mainForHost_size() i64;
extern fn roc__mainForHost_1__Fx_caller(*const u8, [*]u8, [*]u8) void;
extern fn roc__mainForHost_1__Fx_size() i64;
extern fn roc__mainForHost_1__Fx_result_size() i64;
const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque;
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
} else {
return malloc(size);
}
}
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
}
return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
}
free(@alignCast(Align, @ptrCast([*]u8, c_ptr)));
}
export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
std.process.exit(0);
}
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void {
return memcpy(dst, src, size);
}
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
const Unit = extern struct {};
pub fn main() !u8 {
const stderr = std.io.getStdErr().writer();
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
const size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8);
const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?;
var output = @ptrCast([*]u8, raw_output);
defer {
roc_dealloc(raw_output, @alignOf(u64));
}
var timer = std.time.Timer.start() catch unreachable;
roc__mainForHost_1_exposed_generic(output);
const closure_data_pointer = @ptrCast([*]u8, output);
call_the_closure(closure_data_pointer);
const nanos = timer.read();
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0);
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
return 0;
}
fn to_seconds(tms: std.os.timespec) f64 {
return @intToFloat(f64, tms.tv_sec) + (@intToFloat(f64, tms.tv_nsec) / 1_000_000_000.0);
}
fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator;
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
const size = std.math.max(roc__mainForHost_1__Fx_result_size(), 8);
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output);
defer {
allocator.free(raw_output);
}
const flags: u8 = 0;
roc__mainForHost_1__Fx_caller(&flags, closure_data_pointer, output);
// The closure returns result, nothing interesting to do with it
return;
}
pub export fn roc_fx_putInt(int: i64) i64 {
const stdout = std.io.getStdOut().writer();
stdout.print("{d}", .{int}) catch unreachable;
stdout.print("\n", .{}) catch unreachable;
return 0;
}
export fn roc_fx_putLine(rocPath: *str.RocStr) callconv(.C) void {
const stdout = std.io.getStdOut().writer();
for (rocPath.asSlice()) |char| {
stdout.print("{c}", .{char}) catch unreachable;
}
stdout.print("\n", .{}) catch unreachable;
}
const GetInt = extern struct {
value: i64,
is_error: bool,
};
comptime {
if (@sizeOf(usize) == 8) {
@export(roc_fx_getInt_64bit, .{ .name = "roc_fx_getInt" });
} else {
@export(roc_fx_getInt_32bit, .{ .name = "roc_fx_getInt" });
}
}
fn roc_fx_getInt_64bit() callconv(.C) GetInt {
if (roc_fx_getInt_help()) |value| {
const get_int = GetInt{ .is_error = false, .value = value };
return get_int;
} else |err| switch (err) {
error.InvalidCharacter => {
return GetInt{ .is_error = true, .value = 0 };
},
else => {
return GetInt{ .is_error = true, .value = 0 };
},
}
return 0;
}
fn roc_fx_getInt_32bit(output: *GetInt) callconv(.C) void {
if (roc_fx_getInt_help()) |value| {
const get_int = GetInt{ .is_error = false, .value = value, .error_code = false };
output.* = get_int;
} else |err| switch (err) {
error.InvalidCharacter => {
output.* = GetInt{ .is_error = true, .value = 0, .error_code = false };
},
else => {
output.* = GetInt{ .is_error = true, .value = 0, .error_code = true };
},
}
return;
}
fn roc_fx_getInt_help() !i64 {
const stdout = std.io.getStdOut().writer();
stdout.print("Please enter an integer\n", .{}) catch unreachable;
const stdin = std.io.getStdIn().reader();
var buf: [40]u8 = undefined;
const line: []u8 = (try stdin.readUntilDelimiterOrEof(&buf, '\n')) orelse "";
return std.fmt.parseInt(i64, line, 10);
}

View file

@ -0,0 +1,9 @@
platform "benchmarks"
requires {} { main : Task {} [] }
exposes []
packages {}
imports [Task.{ Task }]
provides [mainForHost]
mainForHost : Task {} [] as Fx
mainForHost = main

View file

@ -0,0 +1,7 @@
rocLovesC
rocLovesPlatforms
rocLovesRust
rocLovesSwift
rocLovesWebAssembly
rocLovesZig
*.wasm

View file

@ -0,0 +1,20 @@
# Platform switching
To run, `cd` into this directory and run this in your terminal:
```bash
roc run
```
This will run `main.roc` because, unless you explicitly give it a filename, `roc run`
defaults to running a file named `main.roc`. Other `roc` commands (like `roc build`, `roc test`, and so on) also default to `main.roc` unless you explicitly give them a filename.
## About this example
This uses a very simple platform which does nothing more than printing the string you give it.
The line `main = "Which platform am I running on now?\n"` sets this string to be `"Which platform am I running on now?"` with a newline at the end, and the lines `packages { pf: "c-platform/main.roc" }` and `provides [main] to pf` specify that the `c-platform/` directory contains this app's platform.
This platform is called `c-platform` because its lower-level code is written in C. There's also a `rust-platform`, `zig-platform`, and so on; if you like, you can try switching `pf: "c-platform/main.roc"` to `pf: "zig-platform/main.roc"` or `pf: "rust-platform/main.roc"` to try one of those platforms instead. They all do similar things, so the application won't look any different.
If you want to start building your own platforms, these are some very simple example platforms to use as starting points.

View file

@ -0,0 +1,89 @@
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void* roc_alloc(size_t size, unsigned int alignment) { return malloc(size); }
void* roc_realloc(void* ptr, size_t new_size, size_t old_size,
unsigned int alignment) {
return realloc(ptr, new_size);
}
void roc_dealloc(void* ptr, unsigned int alignment) { free(ptr); }
void roc_panic(void* ptr, unsigned int alignment) {
char* msg = (char*)ptr;
fprintf(stderr,
"Application crashed with message\n\n %s\n\nShutting down\n", msg);
exit(0);
}
void* roc_memcpy(void* dest, const void* src, size_t n) {
return memcpy(dest, src, n);
}
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
struct RocStr {
char* bytes;
size_t len;
size_t capacity;
};
bool is_small_str(struct RocStr str) { return ((ssize_t)str.capacity) < 0; }
// Determine the length of the string, taking into
// account the small string optimization
size_t roc_str_len(struct RocStr str) {
char* bytes = (char*)&str;
char last_byte = bytes[sizeof(str) - 1];
char last_byte_xored = last_byte ^ 0b10000000;
size_t small_len = (size_t)(last_byte_xored);
size_t big_len = str.len;
// Avoid branch misprediction costs by always
// determining both small_len and big_len,
// so this compiles to a cmov instruction.
if (is_small_str(str)) {
return small_len;
} else {
return big_len;
}
}
extern void roc__mainForHost_1_exposed_generic(struct RocStr *string);
int main() {
struct RocStr str;
roc__mainForHost_1_exposed_generic(&str);
// Determine str_len and the str_bytes pointer,
// taking into account the small string optimization.
size_t str_len = roc_str_len(str);
char* str_bytes;
if (is_small_str(str)) {
str_bytes = (char*)&str;
} else {
str_bytes = str.bytes;
}
// Write to stdout
if (write(1, str_bytes, str_len) >= 0) {
// Writing succeeded!
// NOTE: the string is a static string, read from in the binary
// if you make it a heap-allocated string, it'll be leaked here
return 0;
} else {
printf("Error writing to stdout: %s\n", strerror(errno));
// NOTE: the string is a static string, read from in the binary
// if you make it a heap-allocated string, it'll be leaked here
return 1;
}
}

View file

@ -0,0 +1,9 @@
platform "echo-in-c"
requires {} { main : Str }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Str
mainForHost = main

View file

@ -0,0 +1,11 @@
app "rocLovesPlatforms"
packages { pf: "c-platform/main.roc" }
# To switch platforms, comment-out the line above and un-comment one below.
# packages { pf: "rust-platform/main.roc" }
# packages { pf: "swift-platform/main.roc" }
# packages { pf: "web-assembly-platform/main.roc" } # See ./web-assembly-platform/README.md
# packages { pf: "zig-platform/main.roc" }
imports []
provides [main] to pf
main = "Which platform am I running on now?\n"

View file

@ -0,0 +1,6 @@
app "rocLovesC"
packages { pf: "c-platform/main.roc" }
imports []
provides [main] to pf
main = "Roc <3 C!\n"

View file

@ -0,0 +1,6 @@
app "rocLovesRust"
packages { pf: "rust-platform/main.roc" }
imports []
provides [main] to pf
main = "Roc <3 Rust!\n"

View file

@ -0,0 +1,6 @@
app "rocLovesSwift"
packages { pf: "swift-platform/main.roc" }
imports []
provides [main] to pf
main = "Roc <3 Swift!\n"

View file

@ -0,0 +1,6 @@
app "rocLovesWebAssembly"
packages { pf: "web-assembly-platform/main.roc" }
imports []
provides [main] to pf
main = "Roc <3 Web Assembly!\n"

View file

@ -0,0 +1,6 @@
app "rocLovesZig"
packages { pf: "zig-platform/main.roc" }
imports []
provides [main] to pf
main = "Roc <3 Zig!\n"

View file

@ -0,0 +1,37 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "host"
version = "0.0.1"
dependencies = [
"libc",
"roc_std",
]
[[package]]
name = "libc"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
[[package]]
name = "roc_std"
version = "0.0.1"
dependencies = [
"arrayvec",
"static_assertions",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"

View file

@ -0,0 +1,22 @@
[package]
name = "host"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
links = "app"
[lib]
name = "host"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib"]
[[bin]]
name = "host"
path = "src/main.rs"
[dependencies]
roc_std = { path = "../../../../crates/roc_std" }
libc = "0.2"
[workspace]

View file

@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-lib=dylib=app");
println!("cargo:rustc-link-search=.");
}

View file

@ -0,0 +1,3 @@
extern int rust_main();
int main() { return rust_main(); }

View file

@ -0,0 +1,9 @@
platform "echo-in-rust"
requires {} { main : Str }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Str
mainForHost = main

View file

@ -0,0 +1,78 @@
#![allow(non_snake_case)]
use core::ffi::c_void;
use roc_std::RocStr;
use std::ffi::CStr;
use std::mem::ManuallyDrop;
use std::os::raw::c_char;
extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(_: &mut RocStr);
}
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return libc::malloc(size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return libc::realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return libc::free(c_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
unsafe {
// ManuallyDrop must be used here in order to prevent the RocStr from
// getting dropped as soon as it's no longer referenced anywhere, which
// happens earlier than the libc::write that receives a pointer to its data.
let mut roc_str = ManuallyDrop::new(RocStr::default());
roc_main(&mut roc_str);
let len = roc_str.len();
let str_bytes = roc_str.as_bytes().as_ptr() as *const libc::c_void;
if libc::write(1, str_bytes, len) < 0 {
panic!("Writing to stdout failed!");
}
ManuallyDrop::drop(&mut roc_str)
}
// Exit code
0
}

View file

@ -0,0 +1,3 @@
fn main() {
std::process::exit(host::rust_main() as _);
}

View file

@ -0,0 +1,9 @@
#include <stdlib.h>
struct RocStr {
char* bytes;
size_t len;
size_t capacity;
};
extern void roc__mainForHost_1_exposed_generic(const struct RocStr *data);

View file

@ -0,0 +1,62 @@
import Foundation
@_cdecl("roc_alloc")
func rocAlloc(size: Int, _alignment: UInt) -> UInt {
guard let ptr = malloc(size) else {
return 0
}
return UInt(bitPattern: ptr)
}
@_cdecl("roc_dealloc")
func rocDealloc(ptr: UInt, _alignment: UInt) {
free(UnsafeMutableRawPointer(bitPattern: ptr))
}
@_cdecl("roc_realloc")
func rocRealloc(ptr: UInt, _oldSize: Int, newSize: Int, _alignment: UInt) -> UInt {
guard let ptr = realloc(UnsafeMutableRawPointer(bitPattern: ptr), newSize) else {
return 0
}
return UInt(bitPattern: ptr)
}
func isSmallString(rocStr: RocStr) -> Bool {
return rocStr.capacity < 0
}
func getStrLen(rocStr: RocStr) -> Int {
if isSmallString(rocStr: rocStr) {
// Small String length is last in the byte of capacity.
var cap = rocStr.capacity
let count = MemoryLayout.size(ofValue: cap)
let bytes = Data(bytes: &cap, count: count)
let lastByte = bytes[count - 1]
return Int(lastByte ^ 0b1000_0000)
} else {
return rocStr.len
}
}
func getSwiftString(rocStr: RocStr) -> String {
let length = getStrLen(rocStr: rocStr)
if isSmallString(rocStr: rocStr) {
let data: Data = withUnsafePointer(to: rocStr) { ptr in
Data(bytes: ptr, count: length)
}
return String(data: data, encoding: .utf8)!
} else {
let data = Data(bytes: rocStr.bytes, count: length)
return String(data: data, encoding: .utf8)!
}
}
@_cdecl("main")
func main() -> UInt8 {
var rocStr = RocStr()
roc__mainForHost_1_exposed_generic(&rocStr)
print(getSwiftString(rocStr: rocStr), terminator: "")
return 0
}

View file

@ -0,0 +1,9 @@
platform "echo-in-swift"
requires {} { main : Str }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Str
mainForHost = main

View file

@ -0,0 +1,55 @@
# Hello, World!
To run this website, first compile either of these identical apps:
```bash
# Option A: Compile crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc
cargo run -- build --target=wasm32 crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc
# Option B: Compile crates/cli_testing_examples/platform-switching/main.roc with `pf: "web-assembly-platform/main.roc"` and move the result
cargo run -- build --target=wasm32 crates/cli_testing_examples/platform-switching/main.roc
(cd crates/cli_testing_examples/platform-switching && mv rocLovesPlatforms.wasm web-assembly-platform/rocLovesWebAssembly.wasm)
```
Then `cd` into the website directory
and run any web server that can handle WebAssembly.
For example, with `http-server`:
```bash
cd crates/cli_testing_examples/platform-switching/web-assembly-platform
npm install -g http-server
http-server
```
Now open your browser at <http://localhost:8080>
## Design Notes
This demonstrates the basic design of hosts: Roc code gets compiled into a pure
function (in this case, a thunk that always returns `"Hello, World!\n"`) and
then the host calls that function. Fundamentally, that's the whole idea! The host
might not even have a `main` - it could be a library, a plugin, anything.
Everything else is built on this basic "hosts calling linked pure functions" design.
For example, things get more interesting when the compiled Roc function returns
a `Task` - that is, a tagged union data structure containing function pointers
to callback closures. This lets the Roc pure function describe arbitrary
chainable effects, which the host can interpret to perform I/O as requested by
the Roc program. (The tagged union `Task` would have a variant for each supported
I/O operation.)
In this trivial example, it's very easy to line up the API between the host and
the Roc program. In a more involved host, this would be much trickier - especially
if the API were changing frequently during development.
The idea there is to have a first-class concept of "glue code" which host authors
can write (it would be plain Roc code, but with some extra keywords that aren't
available in normal modules - kinda like `port module` in Elm), and which
describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary.
Roc application authors only care about the Roc-host/Roc-app portion, and the
host author only cares about the Roc-host/C boundary when implementing the host.
Using this glue code, the Roc compiler can generate C header files describing the
boundary. This not only gets us host compatibility with C compilers, but also
Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen)
generates correct Rust FFI bindings from C headers.

View file

@ -0,0 +1,58 @@
async function roc_web_platform_run(wasm_filename, callback) {
const decoder = new TextDecoder();
let memory_bytes;
let exit_code;
function js_display_roc_string(str_bytes, str_len) {
const utf8_bytes = memory_bytes.subarray(str_bytes, str_bytes + str_len);
const js_string = decoder.decode(utf8_bytes);
callback(js_string);
}
const importObj = {
wasi_snapshot_preview1: {
proc_exit: (code) => {
if (code !== 0) {
console.error(`Exited with code ${code}`);
}
exit_code = code;
},
fd_write: (x) => { console.error(`fd_write not supported: ${x}`); }
},
env: {
js_display_roc_string,
roc_panic: (_pointer, _tag_id) => {
throw "Roc panicked!";
},
},
};
let wasm;
const response = await fetch(wasm_filename);
if (WebAssembly.instantiateStreaming) {
// streaming API has better performance if available
wasm = await WebAssembly.instantiateStreaming(response, importObj);
} else {
const module_bytes = await response.arrayBuffer();
wasm = await WebAssembly.instantiate(module_bytes, importObj);
}
memory_bytes = new Uint8Array(wasm.instance.exports.memory.buffer);
try {
wasm.instance.exports._start();
} catch (e) {
const is_ok = e.message === "unreachable" && exit_code === 0;
if (!is_ok) {
console.error(e);
}
}
}
if (typeof module !== "undefined") {
module.exports = {
roc_web_platform_run,
};
}

View file

@ -0,0 +1,25 @@
/**
* Node.js test file for helloWeb example
* We are not running this in CI currently, and Node.js is not a Roc dependency.
* But if you happen to have it, you can run this.
*/
// Node doesn't have the fetch API
const fs = require("fs/promises");
global.fetch = (filename) =>
fs.readFile(filename).then((buffer) => ({
arrayBuffer() {
return buffer;
},
}));
const { roc_web_platform_run } = require("./host");
roc_web_platform_run("./rocLovesWebAssembly.wasm", (string_from_roc) => {
const expected = "Roc <3 Web Assembly!\n";
if (string_from_roc !== expected) {
console.error(`Expected "${expected}", but got "${string_from_roc}"`);
process.exit(1);
}
console.log("OK");
});

View file

@ -0,0 +1,74 @@
const std = @import("std");
const str = @import("str");
const builtin = @import("builtin");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const Align = extern struct { a: usize, b: usize };
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) anyopaque;
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*anyopaque;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
extern fn memcpy(dest: *anyopaque, src: *anyopaque, count: usize) *anyopaque;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = alignment;
return malloc(size);
}
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = old_size;
_ = alignment;
return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
_ = alignment;
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
}
export fn roc_memcpy(dest: *anyopaque, src: *anyopaque, count: usize) callconv(.C) void {
_ = memcpy(dest, src, count);
}
// NOTE roc_panic is provided in the JS file, so it can throw an exception
const mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(*RocStr) void;
const Unit = extern struct {};
extern fn js_display_roc_string(str_bytes: ?[*]u8, str_len: usize) void;
pub fn main() u8 {
// actually call roc to populate the callresult
var callresult = RocStr.empty();
roc__mainForHost_1_exposed(&callresult);
// display the result using JavaScript
js_display_roc_string(callresult.asU8ptr(), callresult.len());
callresult.deinit();
return 0;
}

View file

@ -0,0 +1,12 @@
<html>
<body>
<div id="output"></div>
<script src="./host.js"></script>
<script>
const elem = document.getElementById("output");
roc_web_platform_run("./rocLovesWebAssembly.wasm", (string_from_roc) => {
elem.textContent = string_from_roc;
});
</script>
</body>
</html>

View file

@ -0,0 +1,9 @@
platform "echo-in-web-assembly"
requires {} { main : Str }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Str
mainForHost = main

View file

@ -0,0 +1,106 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque;
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
} else {
return malloc(size);
}
}
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
}
return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
}
free(@alignCast(Align, @ptrCast([*]u8, c_ptr)));
}
export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
std.process.exit(0);
}
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void {
return memcpy(dst, src, size);
}
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
const mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed_generic(*RocStr) void;
const Unit = extern struct {};
pub fn main() u8 {
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
var timer = std.time.Timer.start() catch unreachable;
// actually call roc to populate the callresult
var callresult = RocStr.empty();
roc__mainForHost_1_exposed_generic(&callresult);
const nanos = timer.read();
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0);
// stdout the result
stdout.print("{s}", .{callresult.asSlice()}) catch unreachable;
callresult.deinit();
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
return 0;
}

View file

@ -0,0 +1,9 @@
platform "echo-in-zig"
requires {} { main : Str }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Str
mainForHost = main