mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 23:04:49 +00:00
Revise CLI example
This commit is contained in:
parent
b944798fda
commit
659966c878
7 changed files with 258 additions and 38 deletions
|
@ -1,12 +1,14 @@
|
||||||
app "cli-example"
|
app "cli-example"
|
||||||
packages { base: "platform" }
|
packages { base: "platform" }
|
||||||
imports [ base.Task.{ Task, after }, base.File, base.Path ]
|
imports [ base.Task.{ Task, after }, base.Stdout ]
|
||||||
provides [ main ] to base
|
provides [ main ] to base
|
||||||
|
|
||||||
main : Task.Task {} (File.FileReadErr [BadUtf8])
|
main : Task.Task {} *
|
||||||
main =
|
main =
|
||||||
when Path.fromStr "Cargo.toml" is
|
Stdout.write "Hello, World!"
|
||||||
Ok path ->
|
# TODO accept args : List Str
|
||||||
Task.after (Task.putLine "Our Cargo.toml:") \_ ->
|
#when Path.fromStr "Cargo.toml" is
|
||||||
Task.after (File.readUtf8 path) (\line -> Task.putLine line)
|
# Ok path ->
|
||||||
_ -> Task.putLine "invalid path"
|
# Task.after (Task.putLine "Our Cargo.toml:") \_ ->
|
||||||
|
# Task.after (File.readUtf8 path) (\line -> Task.putLine line)
|
||||||
|
# _ -> Task.putLine "invalid path"
|
||||||
|
|
83
examples/cli/platform/File.roc
Normal file
83
examples/cli/platform/File.roc
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
interface File
|
||||||
|
exposes [ FileReadErr, FileOpenErr, FileWriteErr, DirReadErr, readUtf8 ]
|
||||||
|
imports [ Task.{ Task }, Effect.{ after }, Path ]
|
||||||
|
|
||||||
|
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 ]*)
|
||||||
|
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 answer.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/cli/platform/Path.roc
Normal file
16
examples/cli/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
|
|
@ -1,10 +1,15 @@
|
||||||
platform examples/quicksort
|
platform rtfeldman/roc-cli
|
||||||
requires { quicksort : List I64 -> List I64 }
|
requires { main : Task {} [] }
|
||||||
exposes []
|
exposes []
|
||||||
packages {}
|
packages {}
|
||||||
imports []
|
imports [ Task.{ Task }, File ]
|
||||||
provides [ mainForHost ]
|
provides [ mainForHost ]
|
||||||
effects Effect {}
|
effects Effect
|
||||||
|
{
|
||||||
|
#readAllUtf8 : Str -> Effect { errno : I32, bytes : List U8 },
|
||||||
|
putLine : Str -> Effect {},
|
||||||
|
getLine : Effect Str
|
||||||
|
}
|
||||||
|
|
||||||
mainForHost : List I64 -> List I64
|
mainForHost : Effect {} as Fx
|
||||||
mainForHost = \list -> quicksort list
|
mainForHost = main
|
||||||
|
|
6
examples/cli/platform/Stdout.roc
Normal file
6
examples/cli/platform/Stdout.roc
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
interface Stdout
|
||||||
|
exposes [ write ]
|
||||||
|
imports [ Task.{ Task }, Effect.{ Effect } ]
|
||||||
|
|
||||||
|
write : Str -> Effect {} #Task {} *
|
||||||
|
write = \str -> Effect.putLine st
|
34
examples/cli/platform/Task.roc
Normal file
34
examples/cli/platform/Task.roc
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
interface Task
|
||||||
|
exposes [ Task, succeed, fail, after, map, putLine ]
|
||||||
|
imports [ 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)
|
||||||
|
|
||||||
|
|
||||||
|
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 -> Effect.always (Err err) # Task.fail err does not work. WEIRD!
|
||||||
|
|
||||||
|
putLine : Str -> Task {} *
|
||||||
|
putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})
|
|
@ -1,50 +1,124 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use roc_std::alloca;
|
||||||
use roc_std::RocCallResult;
|
use roc_std::RocCallResult;
|
||||||
use roc_std::RocList;
|
use roc_std::RocStr;
|
||||||
|
use std::alloc::Layout;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[link_name = "roc__mainForHost_1_exposed"]
|
#[link_name = "roc__mainForHost_1_exposed"]
|
||||||
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
|
fn roc_main(output: *mut u8) -> ();
|
||||||
|
|
||||||
|
#[link_name = "roc__mainForHost_1_size"]
|
||||||
|
fn roc_main_size() -> i64;
|
||||||
|
|
||||||
|
#[link_name = "roc__mainForHost_1_Fx_caller"]
|
||||||
|
fn call_Fx(
|
||||||
|
flags: &(),
|
||||||
|
function_pointer: *const u8,
|
||||||
|
closure_data: *const u8,
|
||||||
|
output: *mut u8,
|
||||||
|
) -> ();
|
||||||
|
|
||||||
|
#[link_name = "roc__mainForHost_1_Fx_size"]
|
||||||
|
fn size_Fx() -> i64;
|
||||||
|
|
||||||
|
#[link_name = "roc__mainForHost_1_Fx_result_size"]
|
||||||
|
fn size_Fx_result() -> i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NUM_NUMS: usize = 100;
|
#[no_mangle]
|
||||||
|
pub fn roc_fx_putLine(line: RocStr) -> () {
|
||||||
|
let bytes = line.as_slice();
|
||||||
|
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
|
||||||
|
println!("{}", string);
|
||||||
|
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn roc_fx_getLine() -> RocStr {
|
||||||
|
use std::io::{self, BufRead};
|
||||||
|
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
||||||
|
|
||||||
|
RocStr::from_slice_with_capacity(line1.as_bytes(), line1.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 {
|
||||||
|
let size = size_Fx_result() as usize;
|
||||||
|
|
||||||
|
alloca::with_stack_bytes(size, |buffer| {
|
||||||
|
let buffer: *mut std::ffi::c_void = buffer;
|
||||||
|
let buffer: *mut u8 = buffer as *mut u8;
|
||||||
|
|
||||||
|
call_Fx(
|
||||||
|
&(),
|
||||||
|
function_pointer,
|
||||||
|
closure_data_ptr as *const u8,
|
||||||
|
buffer as *mut u8,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = &*(buffer as *mut RocCallResult<()>);
|
||||||
|
|
||||||
|
match output.into() {
|
||||||
|
Ok(_) => 0,
|
||||||
|
Err(e) => panic!("failed with {}", e),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn rust_main() -> isize {
|
pub fn rust_main() -> isize {
|
||||||
let nums: RocList<i64> = {
|
println!("Running Roc closure");
|
||||||
let mut nums = Vec::with_capacity(NUM_NUMS);
|
|
||||||
|
|
||||||
for index in 0..nums.capacity() {
|
|
||||||
let num = index as i64 % 12;
|
|
||||||
|
|
||||||
nums.push(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
RocList::from_slice(&nums)
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Running Roc quicksort on {} numbers...", nums.len());
|
|
||||||
let start_time = SystemTime::now();
|
let start_time = SystemTime::now();
|
||||||
|
|
||||||
|
let size = unsafe { roc_main_size() } as usize;
|
||||||
|
let layout = Layout::array::<u8>(size).unwrap();
|
||||||
let answer = unsafe {
|
let answer = unsafe {
|
||||||
use std::mem::MaybeUninit;
|
// TODO if this is 1024B or less, put it in a global scratch buffer
|
||||||
let mut output = MaybeUninit::uninit();
|
let buffer = std::alloc::alloc(layout);
|
||||||
|
|
||||||
quicksort(nums, &mut *output.as_mut_ptr());
|
roc_main(buffer);
|
||||||
|
|
||||||
match output.assume_init().into() {
|
let output = &*(buffer as *mut RocCallResult<()>);
|
||||||
Ok(value) => value,
|
|
||||||
Err(msg) => panic!("roc failed with message {}", msg),
|
match output.into() {
|
||||||
|
Ok(()) => {
|
||||||
|
let function_pointer = {
|
||||||
|
// this is a pointer to the location where the function pointer is stored
|
||||||
|
// we pass just the function pointer
|
||||||
|
let temp = buffer.offset(8) as *const i64;
|
||||||
|
|
||||||
|
(*temp) as *const u8
|
||||||
|
};
|
||||||
|
|
||||||
|
let closure_data_ptr = buffer.offset(16);
|
||||||
|
|
||||||
|
let result =
|
||||||
|
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8);
|
||||||
|
|
||||||
|
std::alloc::dealloc(buffer, layout);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
Err(msg) => {
|
||||||
|
std::alloc::dealloc(buffer, layout);
|
||||||
|
|
||||||
|
panic!("Roc failed with message: {}", msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let end_time = SystemTime::now();
|
let end_time = SystemTime::now();
|
||||||
let duration = end_time.duration_since(start_time).unwrap();
|
let duration = end_time.duration_since(start_time).unwrap();
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Roc quicksort took {:.4} ms to compute this answer: {:?}",
|
"Roc closure took {:.4} ms to compute this answer: {:?}",
|
||||||
duration.as_secs_f64() * 1000.0,
|
duration.as_secs_f64() * 1000.0,
|
||||||
// truncate the answer, so stdout is not swamped
|
// truncate the answer, so stdout is not swamped
|
||||||
&answer.as_slice()[0..20]
|
answer
|
||||||
);
|
);
|
||||||
|
|
||||||
// Exit code
|
// Exit code
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue