mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 08:17:40 +03:00
Add Task example
This commit is contained in:
parent
5aca31777a
commit
0342330c0e
18
examples/task/Main.roc
Normal file
18
examples/task/Main.roc
Normal file
@ -0,0 +1,18 @@
|
||||
app "effect-example" imports [ Effect ] provides [ main ] to "./platform"
|
||||
|
||||
|
||||
main : Effect.Effect {} as Fx
|
||||
main =
|
||||
when if 1 == 1 then True 3 else False 3.14 is
|
||||
True 3 -> Effect.putLine "Yay"
|
||||
_ -> Effect.putLine "Yay"
|
||||
|
||||
# main : Effect.Effect {} as Fx
|
||||
# main =
|
||||
# if RBTree.isEmpty (RBTree.insert 1 2 Empty) then
|
||||
# Effect.putLine "Yay"
|
||||
# |> Effect.after (\{} -> Effect.getLine)
|
||||
# |> Effect.after (\line -> Effect.putLine line)
|
||||
# else
|
||||
# Effect.putLine "Nay"
|
||||
#
|
81
examples/task/platform/File.roc
Normal file
81
examples/task/platform/File.roc
Normal file
@ -0,0 +1,81 @@
|
||||
interface File
|
||||
exposes [ Err, readUtf8, ]
|
||||
imports [ Task.{ Task }, Effect.{ after }, 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 Int 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 Str (FileReadErr [ BadUtf8 ]*)
|
||||
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 -> Ok answer.bytes # TODO use Str.fromUtf8 to validate a byte list as UTF-8 and return (Err BadUtf8) if validation fails
|
||||
1 -> Err (PermissionDenied path)
|
||||
2 -> Err (FileNotFound path)
|
||||
19 -> Err (FileWasDir path)
|
||||
# TODO handle other errno scenarios that could come up
|
||||
_ -> Err (UnknownError errno path)
|
||||
|
||||
## 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)
|
16
examples/task/platform/Path.roc
Normal file
16
examples/task/platform/Path.roc
Normal file
@ -0,0 +1,16 @@
|
||||
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
|
14
examples/task/platform/Pkg-Config.roc
Normal file
14
examples/task/platform/Pkg-Config.roc
Normal file
@ -0,0 +1,14 @@
|
||||
platform folkertdev/foo
|
||||
requires { main : Effect {} }
|
||||
exposes []
|
||||
packages {}
|
||||
imports [ File ]
|
||||
provides [ mainForHost ]
|
||||
effects Effect
|
||||
{
|
||||
# TODO change sig to Effect { errno : I32, bytes : List U8 }
|
||||
readAllUtf8 : Str -> Effect { errno : Int, bytes : Str }
|
||||
}
|
||||
|
||||
mainForHost : Effect {} as Fx
|
||||
mainForHost = main
|
32
examples/task/platform/Task.roc
Normal file
32
examples/task/platform/Task.roc
Normal file
@ -0,0 +1,32 @@
|
||||
interface Task
|
||||
exposes [ Task, succeed, fail, after, map ]
|
||||
imports [ Effect.{ Effect } ]
|
||||
|
||||
|
||||
Task ok err : Effect (Result ok err)
|
||||
|
||||
|
||||
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.after effect, \result ->
|
||||
when result is
|
||||
Ok a -> Task.succeed (transform a)
|
||||
Err err -> Task.fail err
|
111
examples/task/platform/host.zig
Normal file
111
examples/task/platform/host.zig
Normal file
@ -0,0 +1,111 @@
|
||||
const std = @import("std");
|
||||
const str = @import("../../../compiler/builtins/bitcode/src/str.zig");
|
||||
const testing = std.testing;
|
||||
|
||||
extern var errno: c_int;
|
||||
|
||||
const FILE = extern struct {
|
||||
unused: u8,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
pub const ReadResult = extern struct {
|
||||
bytes: RocStr, // TODO RocList<u8> once Roc supports U8
|
||||
errno: i64, // TODO i32 when Roc supports I32
|
||||
};
|
||||
|
||||
pub fn roc_fx_readAllUtf8(rocPath: *RocStr) ReadResult {
|
||||
// fopen wants a C string, so stack-allocate one using rocPath's contents
|
||||
const len = rocPath.len();
|
||||
var path: [len + 1]u8 = undefined;
|
||||
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 };
|
||||
}
|
||||
|
||||
test "read empty file" {
|
||||
std.debug.panic("hello!", .{});
|
||||
}
|
Loading…
Reference in New Issue
Block a user