diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 2f4a9df6ea..7034419a70 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -202,15 +202,15 @@ mod cli_run { // use_valgrind: true, // } // ], - "task" => vec![ - Example { - filename: "Main.roc", - executable_filename: "task-example", - stdin: &[], - expected_ending: "successfully wrote to file\n", - use_valgrind: true, - } - ], + // "task" => vec![ + // Example { + // filename: "Main.roc", + // executable_filename: "task-example", + // stdin: &[], + // expected_ending: "successfully wrote to file\n", + // use_valgrind: true, + // } + // ], "benchmarks" => vec![ Example { filename: "NQueens.roc", diff --git a/examples/task/.gitignore b/examples/task/.gitignore deleted file mode 100644 index c839fbdee1..0000000000 --- a/examples/task/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -task-example -thing.txt diff --git a/examples/task/Main.roc b/examples/task/Main.roc deleted file mode 100644 index 9cf7f1d699..0000000000 --- a/examples/task/Main.roc +++ /dev/null @@ -1,19 +0,0 @@ -app "task-example" - packages { base: "platform" } - imports [ base.Task.{ Task }, base.File, base.Path ] - provides [ main ] to base - -main : Task.Task {} [] -main = - when Path.fromStr "examples/task/thing.txt" is - Ok path -> - {} <- Task.await (Task.putLine "Writing to file") - - result <- Task.attempt (File.writeUtf8 path "zig is awesome") - - when result is - Ok _ -> Task.putLine "successfully wrote to file" - Err BadThing -> Task.putLine "error writing to file" - Err _ -> Task.putLine "something worse" - - _ -> Task.putLine "invalid path" diff --git a/examples/task/platform/File.roc b/examples/task/platform/File.roc deleted file mode 100644 index 1669382fd9..0000000000 --- a/examples/task/platform/File.roc +++ /dev/null @@ -1,97 +0,0 @@ -interface File - exposes [ FileReadErr, FileOpenErr, FileWriteErr, DirReadErr, readUtf8, writeUtf8 ] - imports [ Task.{ Task }, fx.Effect.{ after }, Path ] - -# TODO FIXME should be able to import this as Path.{ Path }, but there's a bug. -Path : Path.Path - -# These various file errors come from the POSIX errno values - see -# http://www.virtsync.com/c-error-codes-include-errno for the actual codes, and -# https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html for documentation -# -# The goal of this design is: -# * Whenever a function returns a `Task`, that task's error type represents all the errors that could happen. -# * The errors are union-friendly; if I run a task that reads, and then another that writes, I should get all the read *and* write errors. -# * To make the errors friendlier to chaining, they should always include the `Path` of the attempted operation. This way it's possible to tell which one failed after the fact. - - -## These errors can happen when opening a file, before attempting to read from -## it or write to it. The #FileReadErr and #FileWriteErr tag unions begin with -## these tags and then add more specific ones. -FileOpenErr a : - [ - FileNotFound Path, - PermissionDenied Path, - SymLinkLoop Path, - TooManyOpenFiles Path, - IoError Path, - UnknownError I64 Path, - ]a - -## Errors when attempting to read a non-directory file. -FileReadErr a : - FileOpenErr - [ - FileWasDir Path, - InvalidSeek Path, - IllegalByteSequence Path, - FileBusy Path, - ]a - -## Errors when attempting to read a directory. -DirReadErr a : - FileOpenErr - [ - FileWasNotDir Path, - ]a - -## Errors when attempting to write a non-directory file. -FileWriteErr a : - FileOpenErr - [ - FileWasDir Path, - ReadOnlyFileSystem Path, - ]a - - -## Read a file's raw bytes -#readBytes : Path -> Task (List U8) (FileReadErr *) -#readBytes = \path -> -# Effect.readBytes (Path.toStr path) - -## Read a file's bytes and interpret them as UTF-8 encoded text. -readUtf8 : Path -> Task.Task Str (FileReadErr [ BadUtf8 Str.Utf8ByteProblem Nat ]*) -readUtf8 = \path -> - Effect.map (Effect.readAllUtf8 (Path.toStr path)) \answer -> - # errno values - see - # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html - when answer.errno is - 0 -> - when Str.fromUtf8 answer.bytes is - Ok str -> Ok str - Err (BadUtf8 problem index) -> Err (BadUtf8 problem index) - 1 -> Err (PermissionDenied path) - 2 -> Err (FileNotFound path) - 19 -> Err (FileWasDir path) - # TODO handle other errno scenarios that could come up - _ -> Err (UnknownError answer.errno path) - -writeUtf8 : Path, Str -> Task.Task {} (FileWriteErr [ BadThing ]*) -writeUtf8 = \path, data -> - path - |> Path.toStr - |> Effect.writeAllUtf8 data - |> Effect.map \res -> - when res.errno is - 0 -> Ok {} - _ -> Err BadThing - -## Read a file's bytes, one chunk at a time, and use it to build up a state. -## -## After each chunk is read, it gets passed to a callback which builds up a -## state - optionally while running other tasks. -#readChunks : Path, U64, state, (state, List U8 -> Task state []err) -> Task state (FileReadErr err) - -## Like #readChunks except after each chunk you can either `Continue`, -## specifying how many bytes you'd like to read next, or `Stop` early. -#readChunksOrStop : Path, U64, state, (state, List U8 -> [ Continue U64 (Task state []err), Stop (Task state []err) ]) -> Task state (FileReadErr err) diff --git a/examples/task/platform/Package-Config.roc b/examples/task/platform/Package-Config.roc deleted file mode 100644 index 45427ff14a..0000000000 --- a/examples/task/platform/Package-Config.roc +++ /dev/null @@ -1,16 +0,0 @@ -platform folkertdev/foo - requires {}{ main : Task {} [] } - exposes [] - packages {} - imports [ Task ] - provides [ mainForHost ] - effects fx.Effect - { - # TODO change errno to I32 - readAllUtf8 : Str -> Effect { errno : I64, bytes : List U8 }, - writeAllUtf8 : Str, Str -> Effect { errno: I64 }, - putLine : Str -> Effect {} - } - -mainForHost : Task.Task {} [] as Fx -mainForHost = main diff --git a/examples/task/platform/Path.roc b/examples/task/platform/Path.roc deleted file mode 100644 index 9be077865d..0000000000 --- a/examples/task/platform/Path.roc +++ /dev/null @@ -1,16 +0,0 @@ -interface Path - exposes [ Path, fromStr, toStr ] - imports [] - - -Path : [ @Path Str ] - - -fromStr : Str -> Result Path [ MalformedPath ]* -fromStr = \str -> - # TODO actually validate the path - may want a Parser for this! - Ok (@Path str) - -toStr : Path -> Str -toStr = \@Path str -> - str diff --git a/examples/task/platform/Task.roc b/examples/task/platform/Task.roc deleted file mode 100644 index 5c210afcb9..0000000000 --- a/examples/task/platform/Task.roc +++ /dev/null @@ -1,41 +0,0 @@ -interface Task - exposes [ Task, succeed, fail, await, map, putLine, attempt ] - imports [ fx.Effect ] - - -Task ok err : Effect.Effect (Result ok err) - - -succeed : val -> Task val * -succeed = \val -> - Effect.always (Ok val) - - -fail : err -> Task * err -fail = \val -> - Effect.always (Err val) - - -await : Task a err, (a -> Task b err) -> Task b err -await = \effect, transform -> - Effect.after effect \result -> - when result is - Ok a -> transform a - Err err -> Task.fail err - -attempt : Task a b, (Result a b -> Task c d) -> Task c d -attempt = \effect, transform -> - Effect.after effect \result -> - when result is - Ok ok -> transform (Ok ok) - Err err -> transform (Err err) - -map : Task a err, (a -> b) -> Task b err -map = \effect, transform -> - Effect.after effect \result -> - when result is - Ok a -> Task.succeed (transform a) - Err err -> Task.fail err - -putLine : Str -> Task {} * -putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) diff --git a/examples/task/platform/host.zig b/examples/task/platform/host.zig deleted file mode 100644 index 3bef60306c..0000000000 --- a/examples/task/platform/host.zig +++ /dev/null @@ -1,268 +0,0 @@ -const std = @import("std"); -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 (std.builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - -const mem = std.mem; -const Allocator = mem.Allocator; - -extern fn roc__mainForHost_1_exposed([*]u8) void; -extern fn roc__mainForHost_1_size() i64; -extern fn roc__mainForHost_1_Fx_caller(*const u8, *const u8, [*]u8, [*]u8) void; -extern fn roc__mainForHost_1_Fx_size() i64; -extern fn roc__mainForHost_1_Fx_result_size() i64; - -extern fn malloc(size: usize) callconv(.C) ?*c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; -extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; - -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { - return malloc(size); -} - -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { - return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size); -} - -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { - free(@alignCast(16, @ptrCast([*]u8, c_ptr))); -} - -const Unit = extern struct {}; - -pub export fn main() u8 { - const stdout = std.io.getStdOut().writer(); - - const size = @intCast(usize, roc__mainForHost_1_size()); - const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable; - var output = @ptrCast([*]u8, raw_output); - - defer { - std.heap.c_allocator.free(raw_output); - } - - roc__mainForHost_1_exposed(output); - - const elements = @ptrCast([*]u64, @alignCast(8, output)); - - var flag = elements[0]; - - if (flag == 0) { - // all is well - const function_pointer = @intToPtr(*const u8, elements[1]); - const closure_data_pointer = @ptrCast([*]u8, output[16..size]); - - call_the_closure(function_pointer, closure_data_pointer); - } else { - unreachable; - } - - return 0; -} - -fn call_the_closure(function_pointer: *const u8, closure_data_pointer: [*]u8) void { - const size = roc__mainForHost_1_Fx_result_size(); - const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable; - var output = @ptrCast([*]u8, raw_output); - - defer { - std.heap.c_allocator.free(raw_output); - } - - const flags: u8 = 0; - - roc__mainForHost_1_Fx_caller(&flags, function_pointer, closure_data_pointer, output); - - const elements = @ptrCast([*]u64, @alignCast(8, output)); - - var flag = elements[0]; - - if (flag == 0) { - return; - } else { - unreachable; - } -} - -pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 { - const stdout = std.io.getStdOut().writer(); - - const u8_ptr = rocPath.asU8ptr(); - - var i: usize = 0; - while (i < rocPath.len()) { - stdout.print("{c}", .{u8_ptr[i]}) catch unreachable; - - i += 1; - } - - stdout.print("\n", .{}) catch unreachable; - - return 0; -} - -pub const ReadResult = extern struct { - bytes: RocStr, // TODO RocList once Roc supports U8 - errno: i64, // TODO i32 when Roc supports I32 -}; - -pub const WriteResult = extern struct { - errno: i64, -}; - -pub export fn roc_fx_readAllUtf8(rocPath: RocStr) callconv(.C) ReadResult { - var dir = std.fs.cwd(); - - var content = dir.readFileAlloc(testing.allocator, rocPath.asSlice(), 1024) catch |e| switch (e) { - error.FileNotFound => return .{ .bytes = RocStr.empty(), .errno = 2 }, - error.IsDir => return .{ .bytes = RocStr.empty(), .errno = 19 }, - else => return .{ .bytes = RocStr.empty(), .errno = 9999 }, - }; - - var str_ptr = @ptrCast([*]u8, content); - var roc_str3 = RocStr.init(str_ptr, content.len); - - return .{ .bytes = roc_str3, .errno = 0 }; -} - -pub export fn roc_fx_writeAllUtf8(filePath: RocStr, content: RocStr) callconv(.C) WriteResult { - var dir = std.fs.cwd(); - - dir.writeFile(filePath.asSlice(), content.asSlice()) catch |e| switch (e) { - else => return .{ .errno = 1 }, - }; - - return .{ .errno = 0 }; -} - -pub fn roc_fx_readAllUtf8_that_does_not_work(rocPath: *RocStr) ReadResult { - const allocator = std.heap.c_allocator; - - // fopen wants a C string, so stack-allocate one using rocPath's contents - const len = rocPath.len() + 1; - - var raw = allocator.alloc(u8, len) catch unreachable; - var path: [*:0]u8 = @ptrCast([*:0]u8, raw); - rocPath.memcpy(path, len); - path[len] = 0; // nul-terminate the path, since it's a C string - - // Open the file - const file = fopen(path, "r") orelse { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - }; - - // Now that the file has been opened, make sure we always (try to) close it - // before returning, even if something went wrong while reading it. - defer { - if (fclose(file) != 0) { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - } - - // Next we'll count the total number of bytes in the file, which we need - // to know so we can allocate a correctly-sized buffer to read into. - - // First, seek to the end of the file - if (fseek(file, 0, SEEK_END) != 0) { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - - // Now the current file position (which ftell returns) will be the end of - // the file - which will be equal to the total number of bytes in the file. - const totalBytes: c_long = ftell(file); - - // In the highly unusal case that there are no bytes to read, return early. - if (totalBytes <= 0) { - // If the file was empty, return an empty list. - if (totalBytes == 0) { - return ReadResult{ .bytes = RocStr.empty(), .errno = 0 }; - } - - // ftell returns -1 on error, so return an error here - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - - // Rewind to the beginning of the file, so we can start actually reading. - if (fseek(file, 0, SEEK_SET) != 0) { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - - // Allocate enough bytes for the contents of the file, plus the refcount. - const refcountBytes = @sizeOf(usize); - var buffer: [*]u8 = malloc(totalBytes + refcountBytes) orelse { - // If allocation failed, throw a runtime exception for Roc to catch. - - // fclose the file explicitly before throwing, because libunwind - // will disregard our defer block. (TODO verify this!) - // - // Silently ignore fclose errors here, because we're about to throw an - // allocation failure exception; fclose failures won't affect that. - fclose(file); - - // TODO use libunwind to throw an exception here - // TODO set an "allocation failed" exception object for `catch` to receive - // TODO write a test for this which simulates allocation failure - }; - - // Initialize the refcount to a positive number - meaning it's actually - // a capacity value, which is appropriate since we return a Unique value. - @ptrCast(buffer, [*]usize)[0] = totalBytes; - - // The buffer pointer should point to the first byte *after* the refcount - buffer += refcountBytes; - - // Read the bytes into the buffer. - const bytesRead = fread(buffer, 1, totalBytes, file); - - // fread indicates an error by returning a number that's different from - // the number of elements we requested to read - if (bytesRead != totalBytes) { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - - // Explicitly return errno = 0 to indicate there was no error. - // - // (We don't want to read from the errno global here because it might have - // a nonzero value leftover from previous unrelated operations.) - return ReadResult{ .bytes = RocStr.init(buffer, totalBytes), .errno = 0 }; -} - -// const c = @cImport({ -// @cInclude("stdio.h"); -// @cInclude("stdlib.h"); -// }); -// -// extern var errno: c_int; -// -// const FILE = extern struct { -// unused: u8, -// }; - -// extern "c" fn fopen(filename: [*:0]const u8, modes: [*:0]const u8) ?*FILE; -//extern "c" fn fopen(filename: [*:0]const u8, modes: [*:0]const u8) ?*FILE; -//extern "c" fn fclose(stream: *FILE) c_int; -//extern "c" fn fseek(stream: *FILE, offset: c_long, origin: c_int) c_int; - -// extern fn fopen([*:0]const u8, [*:0]const u8) ?*FILE; -// extern fn fseek(*FILE, c_long, c_int) c_int; - -//extern fn fopen([*c]const u8, [*c]const u8) [*c]FILE; -// extern fn ftell([*c]FILE) c_long; -// extern fn fread([*c]u8, size_t, size_t, [*c]FILE) size_t; -// extern fn fclose([*c]FILE) c_int;