diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4b51f50..9a41e845 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ on: - pull_request: +# pull_request: workflow_dispatch: # this cancels workflows currently in progress if you start a new one diff --git a/Cargo.lock b/Cargo.lock index 67f24e28..074866b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,9 +64,9 @@ checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" [[package]] name = "cc" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "shlex", ] @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -512,7 +512,7 @@ dependencies = [ [[package]] name = "roc_std" version = "0.0.1" -source = "git+https://github.com/roc-lang/roc.git#0ce43ffd1e2f1387370006e1c300fe8ec74343c7" +source = "git+https://github.com/roc-lang/roc.git#fbf448cac882ed9f81cb68242e0091a75f4f202d" dependencies = [ "arrayvec", "static_assertions", @@ -521,7 +521,7 @@ dependencies = [ [[package]] name = "roc_std_heap" version = "0.0.1" -source = "git+https://github.com/roc-lang/roc.git#0ce43ffd1e2f1387370006e1c300fe8ec74343c7" +source = "git+https://github.com/roc-lang/roc.git#fbf448cac882ed9f81cb68242e0091a75f4f202d" dependencies = [ "memmap2", "roc_std", @@ -623,9 +623,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", diff --git a/build.roc b/build.roc index 51841c00..d55ca685 100644 --- a/build.roc +++ b/build.roc @@ -13,50 +13,47 @@ import cli.Env ## Check basic-cli-build-steps.png for a diagram that shows what the code does. ## main! : _ => Result {} _ -main! = \_ -> +main! = \_args -> - roc_cmd = Env.var! "ROC" |> Result.withDefault "roc" + roc_cmd = Env.var!("ROC") |> Result.with_default("roc") debug_mode = - when Env.var! "DEBUG" is - Ok str if !(Str.isEmpty str) -> Debug + when Env.var!("DEBUG") is + Ok(str) if !(Str.is_empty(str)) -> Debug _ -> Release - try roc_version! roc_cmd + roc_version!(roc_cmd)? - os_and_arch = try get_os_and_arch! {} + os_and_arch = get_os_and_arch!({})? - stub_lib_path = "platform/libapp.$(stub_file_extension os_and_arch)" + stub_lib_path = "platform/libapp.$(stub_file_extension(os_and_arch))" - try build_stub_app_lib! roc_cmd stub_lib_path + build_stub_app_lib!(roc_cmd, stub_lib_path)? - try cargo_build_host! debug_mode + cargo_build_host!(debug_mode)? - rust_target_folder = try get_rust_target_folder! debug_mode + rust_target_folder = get_rust_target_folder!(debug_mode)? - try copy_host_lib! os_and_arch rust_target_folder + copy_host_lib!(os_and_arch, rust_target_folder)? - try preprocess_host! roc_cmd stub_lib_path rust_target_folder + preprocess_host!(roc_cmd, stub_lib_path, rust_target_folder)? - try info! "Successfully built platform files!" + info!("Successfully built platform files!")? - Ok {} + Ok({}) roc_version! : Str => Result {} _ roc_version! = \roc_cmd -> - try info! "Checking provided roc; executing `$(roc_cmd) version`:" + info!("Checking provided roc; executing `$(roc_cmd) version`:")? - roc_cmd - |> Cmd.exec! ["version"] - |> Result.mapErr RocVersionCheckFailed + Cmd.exec!(roc_cmd, ["version"]) + |> Result.map_err(RocVersionCheckFailed) get_os_and_arch! : {} => Result OSAndArch _ get_os_and_arch! = \{} -> - try info! "Getting the native operating system and architecture ..." + info!("Getting the native operating system and architecture ...")? - { os, arch } = Env.platform! {} - - convert_os_and_arch!! { os, arch } + convert_os_and_arch!(Env.platform!({})) OSAndArch : [ MacosArm64, @@ -70,19 +67,18 @@ OSAndArch : [ convert_os_and_arch! : _ => Result OSAndArch _ convert_os_and_arch! = \{ os, arch } -> when (os, arch) is - (MACOS, AARCH64) -> Ok MacosArm64 - (MACOS, X64) -> Ok MacosX64 - (LINUX, AARCH64) -> Ok LinuxArm64 - (LINUX, X64) -> Ok LinuxX64 - _ -> Err (UnsupportedNative os arch) + (MACOS, AARCH64) -> Ok(MacosArm64) + (MACOS, X64) -> Ok(MacosX64) + (LINUX, AARCH64) -> Ok(LinuxArm64) + (LINUX, X64) -> Ok(LinuxX64) + _ -> Err(UnsupportedNative(os, arch)) build_stub_app_lib! : Str, Str => Result {} _ build_stub_app_lib! = \roc_cmd, stub_lib_path -> - try info! "Building stubbed app shared library ..." + info!("Building stubbed app shared library ...")? - roc_cmd - |> Cmd.exec! ["build", "--lib", "platform/libapp.roc", "--output", stub_lib_path, "--optimize"] - |> Result.mapErr ErrBuildingAppStub + Cmd.exec!(roc_cmd, ["build", "--lib", "platform/libapp.roc", "--output", stub_lib_path, "--optimize"]) + |> Result.map_err(ErrBuildingAppStub) stub_file_extension : OSAndArch -> Str stub_file_extension = \os_and_arch -> @@ -106,53 +102,58 @@ get_rust_target_folder! = \debug_mode -> debug_or_release = if debug_mode == Debug then "debug" else "release" - when Env.var! "CARGO_BUILD_TARGET" is - Ok target_env_var -> - if Str.isEmpty target_env_var then - Ok "target/$(debug_or_release)/" + when Env.var!("CARGO_BUILD_TARGET") is + Ok(target_env_var) -> + if Str.is_empty(target_env_var) then + Ok("target/$(debug_or_release)/") else - Ok "target/$(target_env_var)/$(debug_or_release)/" + Ok("target/$(target_env_var)/$(debug_or_release)/") - Err e -> - try info! "Failed to get env var CARGO_BUILD_TARGET with error $(Inspect.toStr e). Assuming default CARGO_BUILD_TARGET (native)..." + Err(e) -> + info!("Failed to get env var CARGO_BUILD_TARGET with error $(Inspect.to_str(e)). Assuming default CARGO_BUILD_TARGET (native)...")? - Ok "target/$(debug_or_release)/" + Ok("target/$(debug_or_release)/") cargo_build_host! : [Debug, Release] => Result {} _ cargo_build_host! = \debug_mode -> - cargo_build_args = + + cargo_build_args! = \{} -> when debug_mode is - Debug -> Result.map (info! "Building rust host in debug mode...") \_ -> ["build"] - Release -> Result.map (info! "Building rust host ...") \_ -> ["build", "--release"] + Debug -> + info!("Building rust host in debug mode...")? + Ok(["build"]) + + Release -> + info!("Building rust host ...")? + Ok(["build", "--release"]) + + args = cargo_build_args!({})? - "cargo" - |> Cmd.exec! (try cargo_build_args) - |> Result.mapErr ErrBuildingHostBinaries + Cmd.exec!("cargo", args) + |> Result.map_err(ErrBuildingHostBinaries) copy_host_lib! : OSAndArch, Str => Result {} _ copy_host_lib! = \os_and_arch, rust_target_folder -> host_build_path = "$(rust_target_folder)libhost.a" - host_dest_path = "platform/$(prebuilt_static_lib_file os_and_arch)" + host_dest_path = "platform/$(prebuilt_static_lib_file(os_and_arch))" - try info! "Moving the prebuilt binary from $(host_build_path) to $(host_dest_path) ..." + info!("Moving the prebuilt binary from $(host_build_path) to $(host_dest_path) ...")? - "cp" - |> Cmd.exec! [host_build_path, host_dest_path] - |> Result.mapErr ErrMovingPrebuiltLegacyBinary + Cmd.exec!("cp", [host_build_path, host_dest_path]) + |> Result.map_err(ErrMovingPrebuiltLegacyBinary) preprocess_host! : Str, Str, Str => Result {} _ preprocess_host! = \roc_cmd, stub_lib_path, rust_target_folder -> - try info! "Preprocessing surgical host ..." + info!("Preprocessing surgical host ...")? surgical_build_path = "$(rust_target_folder)host" - roc_cmd - |> Cmd.exec! ["preprocess-host", surgical_build_path, "platform/main.roc", stub_lib_path] - |> Result.mapErr ErrPreprocessingSurgicalBinary + Cmd.exec!(roc_cmd, ["preprocess-host", surgical_build_path, "platform/main.roc", stub_lib_path]) + |> Result.map_err(ErrPreprocessingSurgicalBinary) info! : Str => Result {} _ info! = \msg -> - Stdout.line! "\u(001b)[34mINFO:\u(001b)[0m $(msg)" + Stdout.line!("\u(001b)[34mINFO:\u(001b)[0m $(msg)") diff --git a/ci/expect_scripts/http-get-json.exp b/ci/expect_scripts/http-get-json.exp deleted file mode 100644 index d2af026a..00000000 --- a/ci/expect_scripts/http-get-json.exp +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/expect - -# uncomment line below for debugging -# exp_internal 1 - -set timeout 7 - -source ./ci/expect_scripts/shared-code.exp - -# Start server to test with in the background and capture its process ID -set server_pid [exec ./ci/rust_http_server/target/release/rust_http_server &] -sleep 3 - -spawn $env(EXAMPLES_DIR)http-get-json - -expect "The json I received was: { foo: \"Hello Json!\" }\r\n" { - exec kill $server_pid - - expect eof { - check_exit_and_segfault - } -} - -exec kill $server_pid - -puts stderr "\nError: output was different from expected value." -exit 1 diff --git a/ci/rust_http_server/src/main.rs b/ci/rust_http_server/src/main.rs index 46a5c5ee..207d34c7 100644 --- a/ci/rust_http_server/src/main.rs +++ b/ci/rust_http_server/src/main.rs @@ -3,7 +3,6 @@ use hyper::service::{make_service_fn, service_fn}; use std::convert::Infallible; async fn handle_request(_req: Request) -> Result, Infallible> { - // Encode.toBytes {foo: "Hello Json!"} Json.utf8 let json_bytes: Vec = vec![123, 34, 102, 111, 111, 34, 58, 34, 72, 101, 108, 108, 111, 32, 74, 115, 111, 110, 33, 34, 125]; let response = Response::builder() diff --git a/examples/args.roc b/examples/args.roc index be3e884b..d10a56fd 100644 --- a/examples/args.roc +++ b/examples/args.roc @@ -10,12 +10,12 @@ import pf.Arg exposing [Arg] main! : List Arg => Result {} _ main! = \raw_args -> - args = List.map raw_args Arg.display + args = List.map(raw_args, Arg.display) # get the second argument, the first is the executable's path - when List.get args 1 |> Result.mapErr (\_ -> ZeroArgsGiven) is - Err ZeroArgsGiven -> - Err (Exit 1 "Error ZeroArgsGiven:\n\tI expected one argument, but I got none.\n\tRun the app like this: `roc main.roc -- input.txt`") + when List.get(args, 1) |> Result.map_err(\_ -> ZeroArgsGiven) is + Err(ZeroArgsGiven) -> + Err(Exit(1, "Error ZeroArgsGiven:\n\tI expected one argument, but I got none.\n\tRun the app like this: `roc main.roc -- input.txt`")) - Ok first_arg -> - Stdout.line! "received argument: $(first_arg)" + Ok(first_arg) -> + Stdout.line!("received argument: $(first_arg)") diff --git a/examples/command.roc b/examples/command.roc index 145ebfa1..0c6af341 100644 --- a/examples/command.roc +++ b/examples/command.roc @@ -6,32 +6,32 @@ import pf.Stdout import pf.Cmd main! = \_args -> - try status_example! {} + status_example!({})? - try output_example! {} + output_example!({})? - try exec_example! {} + exec_example!({})? - Ok {} + Ok({}) exec_example! : {} => Result {} _ -exec_example! = \{} -> Cmd.exec! "echo" ["EXEC"] +exec_example! = \{} -> Cmd.exec!("echo", ["EXEC"]) # Run "env" with verbose option, clear all environment variables, and pass in # "FOO" and "BAZ". status_example! : {} => Result {} _ status_example! = \{} -> result = - Cmd.new "env" - |> Cmd.arg "-v" + Cmd.new("env") + |> Cmd.arg("-v") |> Cmd.clear_envs - |> Cmd.envs [("FOO", "BAR"), ("BAZ", "DUCK")] + |> Cmd.envs([("FOO", "BAR"), ("BAZ", "DUCK")]) |> Cmd.status! when result is - Ok exit_code if exit_code == 0 -> Ok {} - Ok exit_code -> Stdout.line! "Child exited with non-zero code: $(Num.toStr exit_code)" - Err err -> Stdout.line! "Error executing command: $(Inspect.toStr err)" + Ok(exit_code) if exit_code == 0 -> Ok({}) + Ok(exit_code) -> Stdout.line!("Child exited with non-zero code: $(Num.to_str(exit_code))") + Err(err) -> Stdout.line!("Error executing command: $(Inspect.to_str(err))") # Run "env" with verbose option, clear all environment variables, and pass in # only as an environment variable "FOO" @@ -39,12 +39,12 @@ output_example! : {} => Result {} _ output_example! = \{} -> output = - Cmd.new "env" + Cmd.new("env") |> Cmd.clear_envs - |> Cmd.env "FOO" "BAR" - |> Cmd.args ["-v"] + |> Cmd.env("FOO", "BAR") + |> Cmd.args(["-v"]) |> Cmd.output! - msg = Str.fromUtf8 output.stdout |> Result.withDefault "Failed to decode stdout" + msg = Str.from_utf8(output.stdout) |> Result.with_default("Failed to decode stdout") - Stdout.write! msg + Stdout.write!(msg) diff --git a/examples/countdown.roc b/examples/countdown.roc index 5f16cf9d..1bf45ca4 100644 --- a/examples/countdown.roc +++ b/examples/countdown.roc @@ -6,15 +6,15 @@ import pf.Stdin import pf.Stdout main! = \_args -> - try Stdout.line! "\nLet's count down from 3 together - all you have to do is press ." - _ = Stdin.line! {} - tick! 3 + Stdout.line!("\nLet's count down from 3 together - all you have to do is press .")? + _ = Stdin.line!({}) + tick!(3) tick! = \n -> if n == 0 then - try Stdout.line! "🎉 SURPRISE! Happy Birthday! 🎂" - Ok {} + Stdout.line!("🎉 SURPRISE! Happy Birthday! 🎂")? + Ok({}) else - try Stdout.line! (n |> Num.toStr |> \s -> "$(s)...") - _ = Stdin.line! {} - tick! (n - 1) + Stdout.line!((n |> Num.to_str |> \s -> "$(s)..."))? + _ = Stdin.line!({}) + tick!((n - 1)) diff --git a/examples/dir.roc b/examples/dir.roc index 34ddcc69..693e73f7 100644 --- a/examples/dir.roc +++ b/examples/dir.roc @@ -9,32 +9,34 @@ import pf.Path main! = \_args -> # Create a directory - try Dir.create! "dirExampleE" + Dir.create!("dirExampleE")? # Create a directory and its parents - try Dir.create_all! "dirExampleA/b/c/child" + Dir.create_all!("dirExampleA/b/c/child")? # Create a child directory - try Dir.create! "dirExampleA/child" + Dir.create!("dirExampleA/child")? # List the contents of a directory paths_as_str = - Dir.list! "dirExampleA" - |> Result.map \paths -> List.map paths Path.display + Dir.list!("dirExampleA") + |> Result.map(\paths -> List.map(paths, Path.display)) |> try # Check the contents of the directory - expect (Set.fromList paths_as_str) == (Set.fromList ["dirExampleA/b", "dirExampleA/child"]) + expect (Set.from_list(paths_as_str)) == (Set.from_list(["dirExampleA/b", "dirExampleA/child"])) # Try to create a directory without a parent (should fail, ignore error) - when Dir.create! "dirExampleD/child" is - Ok {} -> {} - Err _ -> {} + when Dir.create!("dirExampleD/child") is + Ok({}) -> {} + Err(_) -> {} # Delete an empty directory - try Dir.delete_empty! "dirExampleE" + Dir.delete_empty!("dirExampleE")? # Delete all directories recursively - try Dir.delete_all! "dirExampleA" + Dir.delete_all!("dirExampleA")? - Stdout.line! "Success!" + Stdout.line!("Success!")? + + Ok({}) diff --git a/examples/echo.roc b/examples/echo.roc index 5ffd53e8..81ea9eb9 100644 --- a/examples/echo.roc +++ b/examples/echo.roc @@ -6,41 +6,42 @@ import pf.Stdin import pf.Stdout main! = \_args -> - try Stdout.line! "Shout into this cave and hear the echo!" - - tick! {} + Stdout.line!("Shout into this cave and hear the echo!")? + tick!({}) tick! : {} => Result {} [StdoutErr _] tick! = \{} -> - when Stdin.line! {} is - Ok str -> - try Stdout.line! (echo str) - tick! {} + when Stdin.line!({}) is + Ok(str) -> + Stdout.line!(echo(str))? + tick!({}) - Err EndOfFile -> - try Stdout.line! (echo "Received end of input (EOF).") - Ok {} + Err(EndOfFile) -> + Stdout.line!(echo("Received end of input (EOF)."))? + Ok({}) - Err (StdinErr err) -> - try Stdout.line! (echo "Unable to read input $(Inspect.toStr err)") - Ok {} + Err(StdinErr(err)) -> + Stdout.line!(echo("Unable to read input $(Inspect.to_str(err))"))? + Ok({}) echo : Str -> Str echo = \shout -> - silence = \length -> List.repeat ' ' length + silence = \length -> List.repeat(' ', length) shout - |> Str.toUtf8 - |> List.mapWithIndex \_, i -> - length = (List.len (Str.toUtf8 shout) - i) - phrase = (List.splitAt (Str.toUtf8 shout) length).before - - List.concat (silence (if i == 0 then 2 * length else length)) phrase + |> Str.to_utf8 + |> List.map_with_index( + \_, i -> + length = (List.len(Str.to_utf8(shout)) - i) + phrase = (List.split_at(Str.to_utf8(shout), length)).before + + List.concat(silence((if i == 0 then 2 * length else length)), phrase), + ) |> List.join - |> Str.fromUtf8 - |> Result.withDefault "" + |> Str.from_utf8 + |> Result.with_default("") expect message = "hello!" - echoed = echo message + echoed = echo(message) echoed == " hello! hello hell hel he h" diff --git a/examples/env-var.roc b/examples/env-var.roc index 0c083790..6dfb2a0f 100644 --- a/examples/env-var.roc +++ b/examples/env-var.roc @@ -9,16 +9,16 @@ import pf.Env main! = \_args -> - editor = try Env.decode! "EDITOR" + editor = Env.decode!("EDITOR")? - try Stdout.line! "Your favorite editor is $(editor)!" + Stdout.line!("Your favorite editor is $(editor)!")? # Env.decode! does not return the same type everywhere. # The type is determined based on type inference. - # Here `Str.joinWith` forces the type that Env.decode! returns to be `List Str` + # Here `Str.join_with` forces the type that Env.decode! returns to be `List Str` joined_letters = - Env.decode! "LETTERS" - |> Result.map \letters -> Str.joinWith letters " " + Env.decode!("LETTERS") + |> Result.map(\letters -> Str.join_with(letters, " ")) |> try - Stdout.line! "Your favorite letters are: $(joined_letters)" + Stdout.line!("Your favorite letters are: $(joined_letters)") diff --git a/examples/file-mixed.roc b/examples/file-mixed.roc index 6579cc3f..43197599 100644 --- a/examples/file-mixed.roc +++ b/examples/file-mixed.roc @@ -13,36 +13,38 @@ out_txt_path = "out.txt" task! = \{} -> - cwd_str = Path.display (try Env.cwd! {}) + cwd_str = Path.display(Env.cwd!({})?) - try Stdout.line! "cwd: $(cwd_str)" + Stdout.line!("cwd: $(cwd_str)")? - dir_entries = try Dir.list! cwd_str + dir_entries = try(Dir.list!, cwd_str) - dir_entries_tr = Str.joinWith (List.map dir_entries Path.display) "\n " + dir_entries_tr = Str.join_with(List.map(dir_entries, Path.display), "\n ") - try Stdout.line! "Directory contents:\n $(dir_entries_tr)\n" + Stdout.line!("Directory contents:\n $(dir_entries_tr)\n")? - try Stdout.line! "Writing a string to out.txt" + Stdout.line!("Writing a string to out.txt")? - try File.write_utf8! "a string!" out_txt_path + File.write_utf8!("a string!", out_txt_path)? - contents = try File.read_utf8! out_txt_path + contents = File.read_utf8!(out_txt_path)? - Stdout.line! "I read the file back. Its contents: \"$(contents)\"" + Stdout.line!("I read the file back. Its contents: \"$(contents)\"")? + + Ok({}) main! = \_args -> - when task! {} is - Ok {} -> Stdout.line! "Successfully wrote a string to out.txt" - Err err -> + when task!({}) is + Ok({}) -> Stdout.line!("Successfully wrote a string to out.txt") + Err(err) -> msg = when err is - FileWriteErr _ PermissionDenied -> "PermissionDenied" - FileWriteErr _ Unsupported -> "Unsupported" - FileWriteErr _ (Unrecognized _ other) -> other - FileReadErr _ _ -> "Error reading file" + FileWriteErr(_, PermissionDenied) -> "PermissionDenied" + FileWriteErr(_, Unsupported) -> "Unsupported" + FileWriteErr(_, Unrecognized(_, other)) -> other + FileReadErr(_, _) -> "Error reading file" _ -> "Uh oh, there was an error!" - try Stderr.line! msg + Stderr.line!(msg)? - Err (Exit 1 "unable to write file: $(msg)") # non-zero exit code to indicate failure + Err(Exit(1, "unable to write file: $(msg)")) # non-zero exit code to indicate failure diff --git a/examples/file-read-buffered.roc b/examples/file-read-buffered.roc index 639b8957..4b288788 100644 --- a/examples/file-read-buffered.roc +++ b/examples/file-read-buffered.roc @@ -19,11 +19,11 @@ import pf.File # See examples/file-read.roc if you want to read the full contents at once. main! = \_args -> - reader = try File.open_reader! "LICENSE" + reader = File.open_reader!("LICENSE")? - read_summary = try process_line! reader { lines_read: 0, bytes_read: 0 } + read_summary = process_line!(reader, { lines_read: 0, bytes_read: 0 })? - Stdout.line! "Done reading file: $(Inspect.toStr read_summary)" + Stdout.line!("Done reading file: $(Inspect.to_str(read_summary))") ReadSummary : { lines_read : U64, @@ -33,15 +33,18 @@ ReadSummary : { ## Count the number of lines and the number of bytes read. process_line! : File.Reader, ReadSummary => Result ReadSummary _ process_line! = \reader, { lines_read, bytes_read } -> - when File.read_line! reader is - Ok bytes if List.len bytes == 0 -> - Ok { lines_read, bytes_read } - - Ok bytes -> - process_line! reader { - lines_read: lines_read + 1, - bytes_read: bytes_read + (List.len bytes |> Num.intCast), - } - - Err err -> - Err (ErrorReadingLine (Inspect.toStr err)) + when File.read_line!(reader) is + Ok(bytes) if List.len(bytes) == 0 -> + Ok({ lines_read, bytes_read }) + + Ok(bytes) -> + process_line!( + reader, + { + lines_read: lines_read + 1, + bytes_read: bytes_read + (List.len(bytes) |> Num.int_cast), + }, + ) + + Err(err) -> + Err(ErrorReadingLine(Inspect.to_str(err))) diff --git a/examples/file-read.roc b/examples/file-read.roc index 534ebe26..b9f1640a 100644 --- a/examples/file-read.roc +++ b/examples/file-read.roc @@ -6,22 +6,22 @@ import pf.Stdout import pf.File main! = \_args -> - when run! {} is - Ok {} -> Ok {} - Err err -> + when run!({}) is + Ok({}) -> Ok({}) + Err(err) -> msg = when err is - FileWriteErr _ PermissionDenied -> "PermissionDenied" - FileWriteErr _ Unsupported -> "Unsupported" - FileWriteErr _ (Unrecognized _ other) -> other - FileReadErr _ _ -> "Error reading file" + FileWriteErr(_, PermissionDenied) -> "PermissionDenied" + FileWriteErr(_, Unsupported) -> "Unsupported" + FileWriteErr(_, Unrecognized(_, other)) -> other + FileReadErr(_, _) -> "Error reading file" _ -> "Uh oh, there was an error!" - Err (Exit 1 "unable to read file: $(msg)") # non-zero exit code to indicate failure + Err(Exit(1, "unable to read file: $(msg)")) # non-zero exit code to indicate failure run! = \{} -> file_name = "LICENSE" - contents = try File.read_utf8! file_name - lines = Str.splitOn contents "\n" + contents = File.read_utf8!(file_name)? + lines = Str.split_on(contents, "\n") - Stdout.line! (Str.concat "First line of $(file_name): " (List.first lines |> Result.withDefault "err")) + Stdout.line!(Str.concat("First line of $(file_name): ", (List.first(lines) |> Result.with_default("err")))) diff --git a/examples/form.roc b/examples/form.roc index a6317785..fdcf7b40 100644 --- a/examples/form.roc +++ b/examples/form.roc @@ -7,12 +7,12 @@ import pf.Stdout main! = \_args -> - try Stdout.line! "What's your first name?" + Stdout.line!("What's your first name?")? - first = try Stdin.line! {} + first = Stdin.line!({})? - try Stdout.line! "What's your last name?" + Stdout.line!("What's your last name?")? - last = try Stdin.line! {} + last = Stdin.line!({})? - Stdout.line! "Hi, $(first) $(last)! 👋" + Stdout.line!("Hi, $(first) $(last)! 👋") diff --git a/examples/hello-world.roc b/examples/hello-world.roc index eedc9172..816b9992 100644 --- a/examples/hello-world.roc +++ b/examples/hello-world.roc @@ -5,4 +5,4 @@ app [main!] { pf: platform "../platform/main.roc" } import pf.Stdout main! = \_args -> - Stdout.line! "Hello, World!" + Stdout.line!("Hello, World!") diff --git a/examples/http-get-json.roc b/examples/http-get-json.roc deleted file mode 100644 index 7b8fbcba..00000000 --- a/examples/http-get-json.roc +++ /dev/null @@ -1,19 +0,0 @@ -app [main!] { - pf: platform "../platform/main.roc", - json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.11.0/z45Wzc-J39TLNweQUoLw3IGZtkQiEN3lTBv3BXErRjQ.tar.br", -} - -# To run this example: check the README.md in this folder - -import pf.Http -import pf.Stdout -import json.Json - -# HTTP GET request with easy decoding to json -main! = \_args -> - - # Easy decoding/deserialization of { "foo": "something" } into a Roc var - { foo } = try Http.get! "http://localhost:8000" Json.utf8 - # If you want to see an example of the server side, see basic-cli/ci/rust_http_server/src/main.rs - - Stdout.line! "The json I received was: { foo: \"$(foo)\" }" diff --git a/examples/http-get.roc b/examples/http-get.roc index 812e1fbd..52c56230 100644 --- a/examples/http-get.roc +++ b/examples/http-get.roc @@ -8,14 +8,16 @@ import pf.Stdout main! = \_args -> - response = Http.send! { - method: Get, - headers: [], - uri: "http://www.example.com", - body: [], - timeout_ms: TimeoutMilliseconds 5000, - } + response = Http.send!( + { + method: GET, + headers: [], + uri: "http://www.example.com", + body: [], + timeout_ms: TimeoutMilliseconds(5000), + }, + ) - body = (Str.fromUtf8 response.body)? + body = (Str.from_utf8(response.body))? - Stdout.line! "Response body:\n\t$(body)." + Stdout.line!("Response body:\n\t$(body).") diff --git a/examples/path.roc b/examples/path.roc index 3eea366b..879942db 100644 --- a/examples/path.roc +++ b/examples/path.roc @@ -7,11 +7,11 @@ import pf.Path main! = \_args -> - path = Path.from_str "path.roc" + path = Path.from_str("path.roc") - a = try Path.is_file! path - b = try Path.is_dir! path - c = try Path.is_sym_link! path - d = try Path.type! path + a = Path.is_file!(path)? + b = Path.is_dir!(path)? + c = Path.is_sym_link!(path)? + d = Path.type!(path)? - Stdout.line! "isFile: $(Inspect.toStr a) isDir: $(Inspect.toStr b) isSymLink: $(Inspect.toStr c) type: $(Inspect.toStr d)" + Stdout.line!("isFile: $(Inspect.to_str(a)) isDir: $(Inspect.to_str(b)) isSymLink: $(Inspect.to_str(c)) type: $(Inspect.to_str(d))") diff --git a/examples/piping.roc b/examples/piping.roc index d982efb3..26853ae9 100644 --- a/examples/piping.roc +++ b/examples/piping.roc @@ -7,10 +7,10 @@ import pf.Stdin # Try piping in some text like this: `echo -e "test\n123" | roc piping.roc` main! = \_args -> - lines = count! 0 - Stdout.line! "I read $(Num.toStr lines) lines from stdin." + lines = count!(0) + Stdout.line!("I read $(Num.to_str(lines)) lines from stdin.") count! = \n -> - when Stdin.line! {} is - Ok _ -> count! (n + 1) - Err _ -> n + when Stdin.line!({}) is + Ok(_) -> count!((n + 1)) + Err(_) -> n diff --git a/examples/record-builder.roc b/examples/record-builder.roc index cadcac34..77bc4991 100644 --- a/examples/record-builder.roc +++ b/examples/record-builder.roc @@ -7,20 +7,19 @@ import pf.Stdout # To run this example: check the README.md in this folder main! = \_args -> - { apples, oranges } = try - { Result.map2 <- - apples: get_fruit! Apples |> Result.map join_strs, - oranges: get_fruit! Oranges |> Result.map join_strs, - } + { apples, oranges } = { Result.map2 <- + apples: get_fruit!(Apples) |> Result.map(join_strs), + oranges: get_fruit!(Oranges) |> Result.map(join_strs), + }? - Stdout.line! "Apples: $(apples)\nOranges: $(oranges)" + Stdout.line!("Apples: $(apples)\nOranges: $(oranges)") -join_strs = \fruits -> Str.joinWith fruits ", " +join_strs = \fruits -> Str.join_with(fruits, ", ") ## This doesn't actually perform any effects, but we can imagine that it does ## for the sake of this example, maybe it fetches data from a server or reads a file. get_fruit! : [Apples, Oranges] => Result (List Str) * get_fruit! = \request -> when request is - Apples -> Ok ["Granny Smith", "Pink Lady", "Golden Delicious"] - Oranges -> Ok ["Navel", "Blood Orange", "Clementine"] + Apples -> Ok(["Granny Smith", "Pink Lady", "Golden Delicious"]) + Oranges -> Ok(["Navel", "Blood Orange", "Clementine"]) diff --git a/examples/result.roc b/examples/result.roc index 29e6fd68..e27da51c 100644 --- a/examples/result.roc +++ b/examples/result.roc @@ -5,16 +5,16 @@ import pf.Stdout # To run this example: check the README.md in this folder main! = \_args -> - when check_file! "good" is - Ok Good -> Stdout.line! "GOOD" - Ok Bad -> Stdout.line! "BAD" - Err IOError -> Stdout.line! "IOError" + when check_file!("good") is + Ok(Good) -> Stdout.line!("GOOD") + Ok(Bad) -> Stdout.line!("BAD") + Err(IOError) -> Stdout.line!("IOError") check_file! : Str => Result [Good, Bad] [IOError] check_file! = \str -> if str == "good" then - Ok Good + Ok(Good) else if str == "bad" then - Ok Bad + Ok(Bad) else - Err IOError + Err(IOError) diff --git a/examples/sqlite.roc b/examples/sqlite.roc index f014c9f4..d2d43033 100644 --- a/examples/sqlite.roc +++ b/examples/sqlite.roc @@ -7,29 +7,38 @@ import pf.Sqlite # To run this example: check the README.md in this folder main! = \_args -> - db_path = try Env.var! "DB_PATH" + db_path = Env.var!("DB_PATH")? - todo = try query_todos_by_status! db_path "todo" + todo = query_todos_by_status!(db_path, "todo")? - try Stdout.line! "Todo Tasks:" - try List.forEachTry! todo \{ id, task } -> - Stdout.line! "\tid: $(id), task: $(task)" + Stdout.line!("Todo Tasks:")? + List.for_each_try!( + todo, + \{ id, task } -> + Stdout.line!("\tid: $(id), task: $(task)"), + )? - completed = try query_todos_by_status! db_path "completed" + completed = query_todos_by_status!(db_path, "completed")? - try Stdout.line! "\nCompleted Tasks:" - try List.forEachTry! completed \{ id, task } -> - Stdout.line! "\tid: $(id), task: $(task)" + Stdout.line!("\nCompleted Tasks:")? - Ok {} + List.for_each_try!( + completed, + \{ id, task } -> + Stdout.line!("\tid: $(id), task: $(task)"), + )? + + Ok({}) query_todos_by_status! = \db_path, status -> - Sqlite.query_many! { - path: db_path, - query: "SELECT id, task FROM todos WHERE status = :status;", - bindings: [{ name: ":status", value: String status }], - rows: { Sqlite.decode_record <- - id: Sqlite.i64 "id" |> Sqlite.map_value Num.toStr, - task: Sqlite.str "task", + Sqlite.query_many!( + { + path: db_path, + query: "SELECT id, task FROM todos WHERE status = :status;", + bindings: [{ name: ":status", value: String(status) }], + rows: { Sqlite.decode_record <- + id: Sqlite.i64("id") |> Sqlite.map_value(Num.to_str), + task: Sqlite.str("task"), + }, }, - } + ) diff --git a/examples/stdin.roc b/examples/stdin.roc index bb1cfe56..76bc2a95 100644 --- a/examples/stdin.roc +++ b/examples/stdin.roc @@ -7,29 +7,33 @@ import pf.Stdin # To run this example: check the README.md in this folder main! = \_args -> - try Stdout.line! "Enter a series of number characters (0-9):" + Stdout.line!("Enter a series of number characters (0-9):")? - number_bytes = try take_number_bytes! {} + number_bytes = take_number_bytes!({})? - if List.isEmpty number_bytes then - Stderr.line! "Expected a series of number characters (0-9)" + if List.is_empty(number_bytes) then + Stderr.line!("Expected a series of number characters (0-9)") else - when Str.fromUtf8 number_bytes is - Ok n_str -> - Stdout.line! "Got number $(n_str)" + when Str.from_utf8(number_bytes) is + Ok(n_str) -> + Stdout.line!("Got number $(n_str)") - Err _ -> - Stderr.line! "Error, bad utf8" + Err(_) -> + Stderr.line!("Error, bad utf8") take_number_bytes! : {} => Result (List U8) _ take_number_bytes! = \{} -> - bytes_read = try Stdin.bytes! {} + bytes_read = Stdin.bytes!({})? number_bytes = - List.walk bytes_read [] \bytes, b -> - if b >= '0' && b <= '9' then - List.append bytes b - else - bytes - - Ok number_bytes + List.walk( + bytes_read, + [], + \bytes, b -> + if b >= '0' && b <= '9' then + List.append(bytes, b) + else + bytes, + ) + + Ok(number_bytes) diff --git a/examples/task-list.roc b/examples/task-list.roc index 31af2b1c..22c341e5 100644 --- a/examples/task-list.roc +++ b/examples/task-list.roc @@ -6,12 +6,12 @@ import pf.Stdout main! = \_args -> # Prints out each of the authors - print! ["Foo", "Bar", "Baz"] + print!(["Foo", "Bar", "Baz"]) print! : List Str => Result {} _ print! = \authors -> when authors is - [] -> Ok {} + [] -> Ok({}) [author, .. as rest] -> - try Stdout.line! author - print! rest + Stdout.line!(author)? + print!(rest) diff --git a/examples/tcp-client.roc b/examples/tcp-client.roc index 1e5830cd..27cb8de3 100644 --- a/examples/tcp-client.roc +++ b/examples/tcp-client.roc @@ -8,16 +8,16 @@ import pf.Stderr # To run this example: check the README.md in this folder main! = \_args -> - when run! {} is - Ok {} -> Ok {} - Err err -> handle_err! err + when run!({}) is + Ok({}) -> Ok({}) + Err(err) -> handle_err!(err) handle_err! : []_ => Result {} _ handle_err! = \error -> when error is - TcpConnectErr err -> - err_str = Tcp.connect_err_to_str err - Stderr.line! + TcpConnectErr(err) -> + err_str = Tcp.connect_err_to_str(err) + Stderr.line!( """ Failed to connect: $(err_str) @@ -26,46 +26,49 @@ handle_err! = \error -> If you want an echo server you can run: $ ncat -e \$(which cat) -l 8085 - """ + """, + ) - TcpReadBadUtf8 _ -> - Stderr.line! "Received invalid UTF-8 data" + TcpReadBadUtf8(_) -> + Stderr.line!("Received invalid UTF-8 data") - TcpReadErr err -> - err_str = Tcp.stream_err_to_str err - Stderr.line! "Error while reading: $(err_str)" + TcpReadErr(err) -> + err_str = Tcp.stream_err_to_str(err) + Stderr.line!("Error while reading: $(err_str)") - TcpWriteErr err -> - err_str = Tcp.stream_err_to_str err - Stderr.line! "Error while writing: $(err_str)" + TcpWriteErr(err) -> + err_str = Tcp.stream_err_to_str(err) + Stderr.line!("Error while writing: $(err_str)") - other -> Stderr.line! "Got other error: $(Inspect.toStr other)" + other -> Stderr.line!("Got other error: $(Inspect.to_str(other))") run! : {} => Result {} _ run! = \{} -> - stream = try Tcp.connect! "127.0.0.1" 8085 + stream = Tcp.connect!("127.0.0.1", 8085)? - try Stdout.line! "Connected!" + Stdout.line!("Connected!")? - loop! {} \_ -> - Result.map (tick! stream) Step + loop!( + {}, + \_ -> Result.map(tick!(stream), Step), + ) tick! : Tcp.Stream => Result {} _ tick! = \stream -> - try Stdout.write! "> " + Stdout.write!("> ")? - out_msg = try Stdin.line! {} + out_msg = Stdin.line!({})? - try Tcp.write_utf8! stream "$(out_msg)\n" + Tcp.write_utf8!(stream, "$(out_msg)\n")? - in_msg = try Tcp.read_line! stream + in_msg = Tcp.read_line!(stream)? - Stdout.line! "< $(in_msg)" + Stdout.line!("< $(in_msg)") loop! : state, (state => Result [Step state, Done done] err) => Result done err loop! = \state, fn! -> - when fn! state is - Err err -> Err err - Ok (Done done) -> Ok done - Ok (Step next) -> loop! next fn! + when fn!(state) is + Err(err) -> Err(err) + Ok(Done(done)) -> Ok(done) + Ok(Step(next)) -> loop!(next, fn!) diff --git a/examples/temp-dir.roc b/examples/temp-dir.roc index 4ce96fa4..d7ab3fff 100644 --- a/examples/temp-dir.roc +++ b/examples/temp-dir.roc @@ -12,7 +12,7 @@ import pf.Path ## for example: `roc build examples/temp-dir.roc --linker=legacy` main! = \_args -> - temp_dir_str = Path.display (Env.temp_dir! {}) + temp_dir_str = Path.display(Env.temp_dir!({})) - Stdout.line! "The temp dir path is $(temp_dir_str)" - |> Result.mapErr \err -> Exit 1 "Failed to print temp dir:\n\t$(Inspect.toStr err)" + Stdout.line!("The temp dir path is $(temp_dir_str)") + |> Result.map_err(\err -> Exit(1, "Failed to print temp dir:\n\t$(Inspect.to_str(err))")) diff --git a/examples/time.roc b/examples/time.roc index b12f39db..5577fea1 100644 --- a/examples/time.roc +++ b/examples/time.roc @@ -7,12 +7,12 @@ import pf.Sleep # To run this example: check the README.md in this folder main! = \_args -> - start = Utc.now! {} + start = Utc.now!({}) - Sleep.millis! 1500 + Sleep.millis!(1500) - finish = Utc.now! {} + finish = Utc.now!({}) - duration = Num.toStr (Utc.delta_as_nanos start finish) + duration = Num.to_str(Utc.delta_as_nanos(start, finish)) - Stdout.line! "Completed in $(duration)ns" + Stdout.line!("Completed in $(duration)ns") diff --git a/flake.lock b/flake.lock index 077bd9fb..0990e2a4 100644 --- a/flake.lock +++ b/flake.lock @@ -102,11 +102,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1734960752, - "narHash": "sha256-QVcT/lUgDRoBccvaZqIAefNInrN6bSuqUJNs9cfjKLY=", + "lastModified": 1736378239, + "narHash": "sha256-J7/bHTiRAefX/czxRkTbik/iMaJDL5ZmTnMfXJE+/nY=", "owner": "roc-lang", "repo": "roc", - "rev": "a58b1013e756749da5c35431efde834a70a29717", + "rev": "fbf448cac882ed9f81cb68242e0091a75f4f202d", "type": "github" }, "original": { @@ -154,11 +154,11 @@ ] }, "locked": { - "lastModified": 1734834660, - "narHash": "sha256-bm8V+Cu8rWJA+vKQnc94mXTpSDgvedyoDKxTVi/uJfw=", + "lastModified": 1736303309, + "narHash": "sha256-IKrk7RL+Q/2NC6+Ql6dwwCNZI6T6JH2grTdJaVWHF0A=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "b070e6030118680977bc2388868c4b3963872134", + "rev": "a0b81d4fa349d9af1765b0f0b4a899c13776f706", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index c3195b8f..f9dd5e4e 100644 --- a/flake.nix +++ b/flake.nix @@ -2,6 +2,7 @@ description = "Basic cli devShell flake"; inputs = { + roc.url = "github:roc-lang/roc"; nixpkgs.follows = "roc/nixpkgs"; diff --git a/platform/Arg.roc b/platform/Arg.roc index eacbabda..9ebbeb7e 100644 --- a/platform/Arg.roc +++ b/platform/Arg.roc @@ -15,22 +15,22 @@ module [ ## encoding for you, but for quick-and-dirty code you can use [display] to ## convert these to [Str] in a lossy way. Arg := [Unix (List U8), Windows (List U16)] - implements [Eq, Inspect { toInspector: arg_inspector }] + implements [Eq, Inspect { to_inspector: arg_inspector }] arg_inspector : Arg -> Inspector f where f implements InspectFormatter -arg_inspector = \arg -> Inspect.str (display arg) +arg_inspector = \arg -> Inspect.str(display(arg)) test_hello : Arg -test_hello = Arg.from_os_raw (Unix [72, 101, 108, 108, 111]) +test_hello = Arg.from_os_raw(Unix([72, 101, 108, 108, 111])) -expect Arg.display test_hello == "Hello" -expect Inspect.toStr test_hello == "\"Hello\"" +expect Arg.display(test_hello) == "Hello" +expect Inspect.to_str(test_hello) == "\"Hello\"" ## Unwrap an [Arg] into a raw, OS-aware numeric list. ## ## This is a good way to pass [Arg]s to Roc packages. to_os_raw : Arg -> [Unix (List U8), Windows (List U16)] -to_os_raw = \@Arg inner -> inner +to_os_raw = \@Arg(inner) -> inner ## Wrap a raw, OS-aware numeric list into an [Arg]. from_os_raw : [Unix (List U8), Windows (List U16)] -> Arg @@ -40,16 +40,16 @@ from_os_raw = @Arg ## ## NB: this will currently crash if there is invalid utf8 bytes, in future this will be lossy and replace any invalid bytes with the [Unicode Replacement Character U+FFFD �](https://en.wikipedia.org/wiki/Specials_(Unicode_block)) display : Arg -> Str -display = \@Arg inner -> +display = \@Arg(inner) -> when inner is - Unix bytes -> + Unix(bytes) -> # TODO replace with Str.from_utf8_lossy : List U8 -> Str # see https://github.com/roc-lang/roc/issues/7390 - when Str.fromUtf8 bytes is - Ok str -> str - Err _ -> crash "tried to display Arg containing invalid utf-8" + when Str.from_utf8(bytes) is + Ok(str) -> str + Err(_) -> crash("tried to display Arg containing invalid utf-8") - Windows _ -> + Windows(_) -> # TODO replace with Str.from_utf16_lossy : List U16 -> Str # see https://github.com/roc-lang/roc/issues/7390 - crash "display for utf-16 Arg not yet supported" + crash("display for utf-16 Arg not yet supported") diff --git a/platform/Cmd.roc b/platform/Cmd.roc index 109cd66d..c79e30cc 100644 --- a/platform/Cmd.roc +++ b/platform/Cmd.roc @@ -25,77 +25,79 @@ Output : InternalCmd.Output ## Create a new command to execute the given program in a child process. new : Str -> Cmd new = \program -> - @Cmd { - program, - args: [], - envs: [], - clear_envs: Bool.false, - } + @Cmd( + { + program, + args: [], + envs: [], + clear_envs: Bool.false, + }, + ) ## Add a single argument to the command. ## ! Shell features like variable subsitition (e.g. `$FOO`), glob patterns (e.g. `*.txt`), ... are not available. ## ## ``` ## # Represent the command "ls -l" -## Cmd.new "ls" -## |> Cmd.arg "-l" +## Cmd.new("ls") +## |> Cmd.arg("-l") ## ``` ## arg : Cmd, Str -> Cmd -arg = \@Cmd cmd, value -> - @Cmd ({ cmd & args: List.append cmd.args value }) +arg = \@Cmd(cmd), value -> + @Cmd({ cmd & args: List.append(cmd.args, value) }) ## Add multiple arguments to the command. ## ! Shell features like variable subsitition (e.g. `$FOO`), glob patterns (e.g. `*.txt`), ... are not available. ## ## ``` ## # Represent the command "ls -l -a" -## Cmd.new "ls" -## |> Cmd.args ["-l", "-a"] +## Cmd.new("ls") +## |> Cmd.args(["-l", "-a"]) ## ``` ## args : Cmd, List Str -> Cmd -args = \@Cmd cmd, values -> - @Cmd ({ cmd & args: List.concat cmd.args values }) +args = \@Cmd(cmd), values -> + @Cmd({ cmd & args: List.concat(cmd.args, values) }) ## Add a single environment variable to the command. ## ## ``` ## # Run "env" and add the environment variable "FOO" with value "BAR" -## Cmd.new "env" -## |> Cmd.env "FOO" "BAR" +## Cmd.new("env") +## |> Cmd.env("FOO", "BAR") ## ``` ## env : Cmd, Str, Str -> Cmd -env = \@Cmd cmd, key, value -> - @Cmd ({ cmd & envs: List.concat cmd.envs [key, value] }) +env = \@Cmd(cmd), key, value -> + @Cmd({ cmd & envs: List.concat(cmd.envs, [key, value]) }) ## Add multiple environment variables to the command. ## ## ``` ## # Run "env" and add the variables "FOO" and "BAZ" -## Cmd.new "env" -## |> Cmd.envs [("FOO", "BAR"), ("BAZ", "DUCK")] +## Cmd.new("env") +## |> Cmd.envs([("FOO", "BAR"), ("BAZ", "DUCK")]) ## ``` ## envs : Cmd, List (Str, Str) -> Cmd -envs = \@Cmd cmd, key_values -> - values = key_values |> List.joinMap \(key, value) -> [key, value] - @Cmd { cmd & envs: List.concat cmd.envs values } +envs = \@Cmd(cmd), key_values -> + values = key_values |> List.join_map(\(key, value) -> [key, value]) + @Cmd({ cmd & envs: List.concat(cmd.envs, values) }) ## Clear all environment variables, and prevent inheriting from parent, only ## the environment variables provided to command are available to the child. ## ## ``` ## # Represents "env" with only "FOO" environment variable set -## Cmd.new "env" +## Cmd.new("env") ## |> Cmd.clear_envs -## |> Cmd.env "FOO" "BAR" +## |> Cmd.env("FOO", "BAR") ## ``` ## clear_envs : Cmd -> Cmd -clear_envs = \@Cmd cmd -> - @Cmd { cmd & clear_envs: Bool.true } +clear_envs = \@Cmd(cmd) -> + @Cmd({ cmd & clear_envs: Bool.true }) ## Execute command and capture stdout and stderr ## @@ -103,32 +105,32 @@ clear_envs = \@Cmd cmd -> ## > to read from the stdin stream will result in the stream immediately closing. ## output! : Cmd => Output -output! = \@Cmd cmd -> - Host.command_output! cmd +output! = \@Cmd(cmd) -> + Host.command_output!(cmd) |> InternalCmd.from_host_output ## Execute command and inherit stdin, stdout and stderr from parent ## status! : Cmd => Result I32 [CmdStatusErr InternalIOErr.IOErr] -status! = \@Cmd cmd -> - Host.command_status! cmd - |> Result.mapErr InternalIOErr.handle_err - |> Result.mapErr CmdStatusErr +status! = \@Cmd(cmd) -> + Host.command_status!(cmd) + |> Result.map_err(InternalIOErr.handle_err) + |> Result.map_err(CmdStatusErr) ## Execute command and inherit stdin, stdout and stderr from parent ## ## ``` ## # Call echo to print "hello world" -## Cmd.exec! "echo" ["hello world"] +## Cmd.exec!("echo", ["hello world"]) ## ``` exec! : Str, List Str => Result {} [CmdStatusErr InternalIOErr.IOErr] exec! = \program, arguments -> exit_code = - new program - |> args arguments + new(program) + |> args(arguments) |> status!? if exit_code == 0i32 then - Ok {} + Ok({}) else - Err (CmdStatusErr (Other "Non-zero exit code $(Num.toStr exit_code)")) + Err(CmdStatusErr(Other("Non-zero exit code $(Num.to_str(exit_code))"))) diff --git a/platform/Dir.roc b/platform/Dir.roc index 5fd9a9be..14e6411e 100644 --- a/platform/Dir.roc +++ b/platform/Dir.roc @@ -26,7 +26,7 @@ DirEntry : Path.DirEntry ## > [Path.list_dir!] does the same thing, except it takes a [Path] instead of a [Str]. list! : Str => Result (List Path) [DirErr IOErr] list! = \path -> - Path.list_dir! (Path.from_str path) + Path.list_dir!(Path.from_str(path)) ## Deletes a directory if it's empty ## @@ -39,7 +39,7 @@ list! = \path -> ## > [Path.delete_empty!] does the same thing, except it takes a [Path] instead of a [Str]. delete_empty! : Str => Result {} [DirErr IOErr] delete_empty! = \path -> - Path.delete_empty! (Path.from_str path) + Path.delete_empty!(Path.from_str(path)) ## Recursively deletes the directory as well as all files and directories ## inside it. @@ -53,7 +53,7 @@ delete_empty! = \path -> ## > [Path.delete_all!] does the same thing, except it takes a [Path] instead of a [Str]. delete_all! : Str => Result {} [DirErr IOErr] delete_all! = \path -> - Path.delete_all! (Path.from_str path) + Path.delete_all!(Path.from_str(path)) ## Creates a directory ## @@ -65,7 +65,7 @@ delete_all! = \path -> ## > [Path.create_dir!] does the same thing, except it takes a [Path] instead of a [Str]. create! : Str => Result {} [DirErr IOErr] create! = \path -> - Path.create_dir! (Path.from_str path) + Path.create_dir!(Path.from_str(path)) ## Creates a directory recursively adding any missing parent directories. ## @@ -76,4 +76,4 @@ create! = \path -> ## > [Path.create_all!] does the same thing, except it takes a [Path] instead of a [Str]. create_all! : Str => Result {} [DirErr IOErr] create_all! = \path -> - Path.create_all! (Path.from_str path) + Path.create_all!(Path.from_str(path)) diff --git a/platform/Env.roc b/platform/Env.roc index 14c015f2..01d3a2be 100644 --- a/platform/Env.roc +++ b/platform/Env.roc @@ -18,27 +18,27 @@ import Host ## from the environment. File operations on relative [Path]s are relative to this directory. cwd! : {} => Result Path [CwdUnavailable] cwd! = \{} -> - bytes = Host.cwd! {} |> Result.withDefault [] + bytes = Host.cwd!({}) |> Result.with_default([]) - if List.isEmpty bytes then - Err CwdUnavailable + if List.is_empty(bytes) then + Err(CwdUnavailable) else - Ok (InternalPath.from_arbitrary_bytes bytes) + Ok(InternalPath.from_arbitrary_bytes(bytes)) ## Sets the [current working directory](https://en.wikipedia.org/wiki/Working_directory) ## in the environment. After changing it, file operations on relative [Path]s will be relative ## to this directory. set_cwd! : Path => Result {} [InvalidCwd] set_cwd! = \path -> - Host.set_cwd! (InternalPath.to_bytes path) - |> Result.mapErr \{} -> InvalidCwd + Host.set_cwd!(InternalPath.to_bytes(path)) + |> Result.map_err(\{} -> InvalidCwd) ## Gets the path to the currently-running executable. exe_path! : {} => Result Path [ExePathUnavailable] exe_path! = \{} -> - when Host.exe_path! {} is - Ok bytes -> Ok (InternalPath.from_os_bytes bytes) - Err {} -> Err ExePathUnavailable + when Host.exe_path!({}) is + Ok(bytes) -> Ok(InternalPath.from_os_bytes(bytes)) + Err({}) -> Err(ExePathUnavailable) ## Reads the given environment variable. ## @@ -46,8 +46,8 @@ exe_path! = \{} -> ## [Unicode replacement character](https://unicode.org/glossary/#replacement_character) ('�'). var! : Str => Result Str [VarNotFound] var! = \name -> - Host.env_var! name - |> Result.mapErr \{} -> VarNotFound + Host.env_var!(name) + |> Result.map_err(\{} -> VarNotFound) ## Reads the given environment variable and attempts to decode it. ## @@ -66,7 +66,7 @@ var! = \name -> ## ``` ## # Reads "NUM_THINGS" and decodes into a U16 ## get_u16_var! : Str => Result U16 [VarNotFound, DecodeErr DecodeError] [Read [Env]] -## get_u16_var! = \var -> Env.decode! var +## get_u16_var! = \var -> Env.decode!(var) ## ``` ## ## If `NUM_THINGS=123` then `getU16Var` succeeds with the value of `123u16`. @@ -76,12 +76,12 @@ var! = \name -> ## decode! : Str => Result val [VarNotFound, DecodeErr DecodeError] where val implements Decoding decode! = \name -> - when Host.env_var! name is - Err {} -> Err VarNotFound - Ok var_str -> - Str.toUtf8 var_str - |> Decode.fromBytes (EnvDecoding.format {}) - |> Result.mapErr (\_ -> DecodeErr TooShort) + when Host.env_var!(name) is + Err({}) -> Err(VarNotFound) + Ok(var_str) -> + Str.to_utf8(var_str) + |> Decode.from_bytes(EnvDecoding.format({})) + |> Result.map_err(\_ -> DecodeErr(TooShort)) ## Reads all the process's environment variables into a [Dict]. ## @@ -89,8 +89,8 @@ decode! = \name -> ## will be used in place of any parts of keys or values that are invalid Unicode. dict! : {} => Dict Str Str dict! = \{} -> - Host.env_dict! {} - |> Dict.fromList + Host.env_dict!({}) + |> Dict.from_list # ## Walks over the process's environment variables as key-value arguments to the walking function. # ## @@ -137,7 +137,7 @@ OS : [LINUX, MACOS, WINDOWS, OTHER Str] platform! : {} => { arch : ARCH, os : OS } platform! = \{} -> - from_rust = Host.current_arch_os! {} + from_rust = Host.current_arch_os!({}) arch = when from_rust.arch is @@ -145,14 +145,14 @@ platform! = \{} -> "x86_64" -> X64 "arm" -> ARM "aarch64" -> AARCH64 - _ -> OTHER from_rust.arch + _ -> OTHER(from_rust.arch) os = when from_rust.os is "linux" -> LINUX "macos" -> MACOS "windows" -> WINDOWS - _ -> OTHER from_rust.os + _ -> OTHER(from_rust.os) { arch, os } @@ -167,5 +167,5 @@ platform! = \{} -> ## temp_dir! : {} => Path temp_dir! = \{} -> - Host.temp_dir! {} + Host.temp_dir!({}) |> InternalPath.from_os_bytes diff --git a/platform/EnvDecoding.roc b/platform/EnvDecoding.roc index 0ca77b55..2b819e9a 100644 --- a/platform/EnvDecoding.roc +++ b/platform/EnvDecoding.roc @@ -27,82 +27,95 @@ EnvFormat := {} implements [ ] format : {} -> EnvFormat -format = \{} -> @EnvFormat {} +format = \{} -> @EnvFormat({}) decode_bytes_to_num = \bytes, transformer -> - when Str.fromUtf8 bytes is - Ok s -> - when transformer s is - Ok n -> { result: Ok n, rest: [] } - Err _ -> { result: Err TooShort, rest: bytes } + when Str.from_utf8(bytes) is + Ok(s) -> + when transformer(s) is + Ok(n) -> { result: Ok(n), rest: [] } + Err(_) -> { result: Err(TooShort), rest: bytes } - Err _ -> { result: Err TooShort, rest: bytes } + Err(_) -> { result: Err(TooShort), rest: bytes } -env_u8 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU8 -env_u16 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU16 -env_u32 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU32 -env_u64 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU64 -env_u128 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU128 -env_i8 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI8 -env_i16 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI16 -env_i32 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI32 -env_i64 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI64 -env_i128 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI128 -env_f32 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toF32 -env_f64 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toF64 -env_dec = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toDec +env_u8 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u8)) +env_u16 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u16)) +env_u32 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u32)) +env_u64 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u64)) +env_u128 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u128)) +env_i8 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i8)) +env_i16 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i16)) +env_i32 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i32)) +env_i64 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i64)) +env_i128 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i128)) +env_f32 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_f32)) +env_f64 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_f64)) +env_dec = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_dec)) -env_bool = Decode.custom \bytes, @EnvFormat {} -> - when Str.fromUtf8 bytes is - Ok "true" -> { result: Ok Bool.true, rest: [] } - Ok "false" -> { result: Ok Bool.false, rest: [] } - _ -> { result: Err TooShort, rest: bytes } +env_bool = Decode.custom( + \bytes, @EnvFormat({}) -> + when Str.from_utf8(bytes) is + Ok("true") -> { result: Ok(Bool.true), rest: [] } + Ok("false") -> { result: Ok(Bool.false), rest: [] } + _ -> { result: Err(TooShort), rest: bytes }, +) -env_string = Decode.custom \bytes, @EnvFormat {} -> - when Str.fromUtf8 bytes is - Ok s -> { result: Ok s, rest: [] } - Err _ -> { result: Err TooShort, rest: bytes } +env_string = Decode.custom( + \bytes, @EnvFormat({}) -> + when Str.from_utf8(bytes) is + Ok(s) -> { result: Ok(s), rest: [] } + Err(_) -> { result: Err(TooShort), rest: bytes }, +) -env_list = \decode_elem -> Decode.custom \bytes, @EnvFormat {} -> - # Per our supported methods of decoding, this is either a list of strings or - # a list of numbers; in either case, the list of bytes must be Utf-8 - # decodable. So just parse it as a list of strings and pass each chunk to - # the element decoder. By construction, our element decoders expect to parse - # a whole list of bytes anyway. - decode_elems = \all_bytes, accum -> - { to_parse, remainder } = - when List.splitFirst all_bytes (Num.toU8 ',') is - Ok { before, after } -> - { to_parse: before, remainder: Some after } +env_list = \decode_elem -> + Decode.custom( + \bytes, @EnvFormat({}) -> + # Per our supported methods of decoding, this is either a list of strings or + # a list of numbers; in either case, the list of bytes must be Utf-8 + # decodable. So just parse it as a list of strings and pass each chunk to + # the element decoder. By construction, our element decoders expect to parse + # a whole list of bytes anyway. + decode_elems = \all_bytes, accum -> + { to_parse, remainder } = + when List.split_first(all_bytes, Num.to_u8(',')) is + Ok({ before, after }) -> + { to_parse: before, remainder: Some(after) } - Err NotFound -> - { to_parse: all_bytes, remainder: None } + Err(NotFound) -> + { to_parse: all_bytes, remainder: None } - when Decode.decodeWith to_parse decode_elem (@EnvFormat {}) is - { result, rest } -> - when result is - Ok val -> - when remainder is - Some rest_bytes -> decode_elems rest_bytes (List.append accum val) - None -> Done (List.append accum val) + when Decode.decode_with(to_parse, decode_elem, @EnvFormat({})) is + { result, rest } -> + when result is + Ok(val) -> + when remainder is + Some(rest_bytes) -> decode_elems(rest_bytes, List.append(accum, val)) + None -> Done(List.append(accum, val)) - Err e -> Errored e rest + Err(e) -> Errored(e, rest) - when decode_elems bytes [] is - Errored e rest -> { result: Err e, rest } - Done vals -> - { result: Ok vals, rest: [] } + when decode_elems(bytes, []) is + Errored(e, rest) -> { result: Err(e), rest } + Done(vals) -> + { result: Ok(vals), rest: [] }, + ) # TODO: we must currently annotate the arrows here so that the lambda sets are # exercised, and the solver can find an ambient lambda set for the # specialization. env_record : _, (_, _ -> [Keep (Decoder _ _), Skip]), (_, _ -> _) -> Decoder _ _ -env_record = \_initialState, _stepField, _finalizer -> Decode.custom \bytes, @EnvFormat {} -> - { result: Err TooShort, rest: bytes } +env_record = \_initialState, _stepField, _finalizer -> + Decode.custom( + \bytes, @EnvFormat({}) -> + { result: Err(TooShort), rest: bytes }, + ) # TODO: we must currently annotate the arrows here so that the lambda sets are # exercised, and the solver can find an ambient lambda set for the # specialization. env_tuple : _, (_, _ -> [Next (Decoder _ _), TooLong]), (_ -> _) -> Decoder _ _ -env_tuple = \_initialState, _stepElem, _finalizer -> Decode.custom \bytes, @EnvFormat {} -> - { result: Err TooShort, rest: bytes } +env_tuple = \_initialState, _stepElem, _finalizer -> + Decode.custom( + \bytes, @EnvFormat({}) -> + { result: Err(TooShort), rest: bytes }, + ) diff --git a/platform/File.roc b/platform/File.roc index 32900cff..d4d42f1d 100644 --- a/platform/File.roc +++ b/platform/File.roc @@ -44,17 +44,18 @@ IOErr : InternalIOErr.IOErr ## ## First encode a `val` using a given `fmt` which implements the ability [Encode.EncoderFormatting](https://www.roc-lang.org/builtins/Encode#EncoderFormatting). ## -## For example, suppose you have a `Json.toCompactUtf8` which implements +## For example, suppose you have a `Json.to_compact_utf8` which implements ## [Encode.EncoderFormatting](https://www.roc-lang.org/builtins/Encode#EncoderFormatting). ## You can use this to write [JSON](https://en.wikipedia.org/wiki/JSON) ## data to a file like this: ## ## ``` ## # Writes `{"some":"json stuff"}` to the file `output.json`: -## File.write! -## { some: "json stuff" } -## (Path.from_str "output.json") -## Json.toCompactUtf8 +## File.write!( +## { some: "json stuff" }, +## Path.from_str("output.json"), +## Json.to_compact_utf8, +## ) ## ``` ## ## This opens the file first and closes it after writing to it. @@ -65,13 +66,13 @@ IOErr : InternalIOErr.IOErr ## > [Path.write!] does the same thing, except it takes a [Path] instead of a [Str]. write! : val, Str, fmt => Result {} [FileWriteErr Path IOErr] where val implements Encoding, fmt implements EncoderFormatting write! = \val, path, fmt -> - Path.write! val (Path.from_str path) fmt + Path.write!(val, Path.from_str(path), fmt) ## Writes bytes to a file. ## ## ``` ## # Writes the bytes 1, 2, 3 to the file `myfile.dat`. -## File.write_bytes! [1, 2, 3] (Path.from_str "myfile.dat") +## File.write_bytes!([1, 2, 3], Path.from_str("myfile.dat"))? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -81,13 +82,13 @@ write! = \val, path, fmt -> ## > [Path.write_bytes!] does the same thing, except it takes a [Path] instead of a [Str]. write_bytes! : List U8, Str => Result {} [FileWriteErr Path IOErr] write_bytes! = \bytes, path -> - Path.write_bytes! bytes (Path.from_str path) + Path.write_bytes!(bytes, Path.from_str(path)) ## Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## ## ``` ## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`. -## File.write_utf8! "Hello!" "myfile.txt" +## File.write_utf8!("Hello!", "myfile.txt")? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -97,7 +98,7 @@ write_bytes! = \bytes, path -> ## > [Path.write_utf8!] does the same thing, except it takes a [Path] instead of a [Str]. write_utf8! : Str, Str => Result {} [FileWriteErr Path IOErr] write_utf8! = \str, path -> - Path.write_utf8! str (Path.from_str path) + Path.write_utf8!(str, Path.from_str(path)) ## Deletes a file from the filesystem. ## @@ -109,7 +110,7 @@ write_utf8! = \str, path -> ## ## ``` ## # Deletes the file named `myfile.dat` -## File.delete! (Path.from_str "myfile.dat") [1, 2, 3] +## File.delete!(Path.from_str("myfile.dat"), [1, 2, 3])? ## ``` ## ## > This does not securely erase the file's contents from disk; instead, the operating @@ -121,13 +122,13 @@ write_utf8! = \str, path -> ## > [Path.delete!] does the same thing, except it takes a [Path] instead of a [Str]. delete! : Str => Result {} [FileWriteErr Path IOErr] delete! = \path -> - Path.delete! (Path.from_str path) + Path.delete!(Path.from_str(path)) ## Reads all the bytes in a file. ## ## ``` ## # Read all the bytes in `myfile.txt`. -## File.read_bytes! "myfile.txt" +## bytes = File.read_bytes!("myfile.txt")? ## ``` ## ## This opens the file first and closes it after reading its contents. @@ -137,13 +138,13 @@ delete! = \path -> ## > [Path.read_bytes!] does the same thing, except it takes a [Path] instead of a [Str]. read_bytes! : Str => Result (List U8) [FileReadErr Path IOErr] read_bytes! = \path -> - Path.read_bytes! (Path.from_str path) + Path.read_bytes!(Path.from_str(path)) ## Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ## ## ``` ## # Reads UTF-8 encoded text into a Str from the file "myfile.txt" -## File.read_utf8! "myfile.txt" +## str = File.read_utf8!("myfile.txt")? ## ``` ## ## This opens the file first and closes it after reading its contents. @@ -154,7 +155,7 @@ read_bytes! = \path -> ## > [Path.read_utf8!] does the same thing, except it takes a [Path] instead of a [Str]. read_utf8! : Str => Result Str [FileReadErr Path IOErr, FileReadUtf8Err Path _] read_utf8! = \path -> - Path.read_utf8! (Path.from_str path) + Path.read_utf8!(Path.from_str(path)) # read : Str, fmt => Result contents [FileReadErr Path ReadErr, FileReadDecodingFailed] where contents implements Decoding, fmt implements DecoderFormatting # read = \path, fmt -> @@ -170,7 +171,7 @@ read_utf8! = \path -> ## > [Path.hard_link!] does the same thing, except it takes a [Path] instead of a [Str]. hard_link! : Str => Result {} [LinkErr IOErr] hard_link! = \path -> - Path.hard_link! (Path.from_str path) + Path.hard_link!(Path.from_str(path)) ## Returns True if the path exists on disk and is pointing at a directory. ## Returns False if the path exists and it is not a directory. If the path does not exist, @@ -181,7 +182,7 @@ hard_link! = \path -> ## > [Path.is_dir!] does the same thing, except it takes a [Path] instead of a [Str]. is_dir! : Str => Result Bool [PathErr IOErr] is_dir! = \path -> - Path.is_dir! (Path.from_str path) + Path.is_dir!(Path.from_str(path)) ## Returns True if the path exists on disk and is pointing at a regular file. ## Returns False if the path exists and it is not a file. If the path does not exist, @@ -192,7 +193,7 @@ is_dir! = \path -> ## > [Path.is_file!] does the same thing, except it takes a [Path] instead of a [Str]. is_file! : Str => Result Bool [PathErr IOErr] is_file! = \path -> - Path.is_file! (Path.from_str path) + Path.is_file!(Path.from_str(path)) ## Returns True if the path exists on disk and is pointing at a symbolic link. ## Returns False if the path exists and it is not a symbolic link. If the path does not exist, @@ -203,7 +204,7 @@ is_file! = \path -> ## > [Path.is_sym_link!] does the same thing, except it takes a [Path] instead of a [Str]. is_sym_link! : Str => Result Bool [PathErr IOErr] is_sym_link! = \path -> - Path.is_sym_link! (Path.from_str path) + Path.is_sym_link!(Path.from_str(path)) ## Return the type of the path if the path exists on disk. ## This uses [rust's std::path::is_symlink](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_symlink). @@ -211,7 +212,7 @@ is_sym_link! = \path -> ## > [Path.type!] does the same thing, except it takes a [Path] instead of a [Str]. type! : Str => Result [IsFile, IsDir, IsSymLink] [PathErr IOErr] type! = \path -> - Path.type! (Path.from_str path) + Path.type!(Path.from_str(path)) Reader := { reader : Host.FileReader, path : Path } @@ -223,12 +224,12 @@ Reader := { reader : Host.FileReader, path : Path } ## Use [read_utf8!] if you want to get the entire file contents at once. open_reader! : Str => Result Reader [GetFileReadErr Path IOErr] open_reader! = \path_str -> - path = Path.from_str path_str + path = Path.from_str(path_str) # 0 means with default capacity - Host.file_reader! (Str.toUtf8 path_str) 0 - |> Result.mapErr \err -> GetFileReadErr path (InternalIOErr.handle_err err) - |> Result.map \reader -> @Reader { reader, path } + Host.file_reader!(Str.to_utf8(path_str), 0) + |> Result.map_err(\err -> GetFileReadErr(path, InternalIOErr.handle_err(err))) + |> Result.map(\reader -> @Reader({ reader, path })) ## Try to open a `File.Reader` for buffered (= part by part) reading given a path string. ## The buffer will be created with the specified capacity. @@ -239,11 +240,11 @@ open_reader! = \path_str -> ## Use [read_utf8!] if you want to get the entire file contents at once. open_reader_with_capacity! : Str, U64 => Result Reader [GetFileReadErr Path IOErr] open_reader_with_capacity! = \path_str, capacity -> - path = Path.from_str path_str + path = Path.from_str(path_str) - Host.file_reader! (Str.toUtf8 path_str) capacity - |> Result.mapErr \err -> GetFileReadErr path (InternalIOErr.handle_err err) - |> Result.map \reader -> @Reader { reader, path } + Host.file_reader!(Str.to_utf8(path_str), capacity) + |> Result.map_err(\err -> GetFileReadErr(path, InternalIOErr.handle_err(err))) + |> Result.map(\reader -> @Reader({ reader, path })) ## Try to read a line from a file given a Reader. ## The line will be provided as the list of bytes (`List U8`) until a newline (`0xA` byte). @@ -254,6 +255,6 @@ open_reader_with_capacity! = \path_str, capacity -> ## ## Use [read_utf8!] if you want to get the entire file contents at once. read_line! : Reader => Result (List U8) [FileReadErr Path IOErr] -read_line! = \@Reader { reader, path } -> - Host.file_read_line! reader - |> Result.mapErr \err -> FileReadErr path (InternalIOErr.handle_err err) +read_line! = \@Reader({ reader, path }) -> + Host.file_read_line!(reader) + |> Result.map_err(\err -> FileReadErr(path, InternalIOErr.handle_err(err))) diff --git a/platform/FileMetadata.roc b/platform/FileMetadata.roc index b66dbc17..a78b4a96 100644 --- a/platform/FileMetadata.roc +++ b/platform/FileMetadata.roc @@ -20,19 +20,19 @@ FileMetadata := { ## Returns the number of bytes in the associated file. bytes : FileMetadata -> U64 -bytes = \@FileMetadata info -> info.bytes +bytes = \@FileMetadata(info) -> info.bytes ## Returns [Bool.true] if the associated file is read-only. is_readonly : FileMetadata -> Bool -is_readonly = \@FileMetadata info -> info.is_readonly +is_readonly = \@FileMetadata(info) -> info.is_readonly ## Returns the type of the associated file. type : FileMetadata -> [File, Dir, Symlink] -type = \@FileMetadata info -> info.type +type = \@FileMetadata(info) -> info.type ## Returns the mode of the associated file. mode : FileMetadata -> [Unix U32, NonUnix] -mode = \@FileMetadata info -> info.mode +mode = \@FileMetadata(info) -> info.mode # TODO need to create a Time module and return something like Time.Utc here. # lastModified : FileMetadata -> Utc diff --git a/platform/Http.roc b/platform/Http.roc index 374e4170..8930500c 100644 --- a/platform/Http.roc +++ b/platform/Http.roc @@ -36,7 +36,7 @@ Response : InternalHttp.Response ## default_request : Request default_request = { - method: Get, + method: GET, headers: [], uri: "", body: [], @@ -56,12 +56,11 @@ header = \(name, value) -> { name, value } ## ``` ## # Prints out the HTML of the Roc-lang website. ## response = -## { Http.default_request & url: "https://www.roc-lang.org" } -## |> Http.send! +## Http.send!({ Http.default_request & url: "https://www.roc-lang.org" }) ## ## response.body -## |> Str.fromUtf8 -## |> Result.withDefault "Invalid UTF-8" +## |> Str.from_utf8 +## |> Result.with_default("Invalid UTF-8") ## |> Stdout.line ## ``` send! : Request => Response @@ -77,20 +76,20 @@ send! = \request -> ## ``` ## import json.Json ## -## # On the server side we send `Encode.toBytes {foo: "Hello Json!"} Json.utf8` -## { foo } = Http.get! "http://localhost:8000" Json.utf8 +## # On the server side we send `Encode.to_bytes {foo: "Hello Json!"} Json.utf8` +## { foo } = Http.get!("http://localhost:8000", Json.utf8)? ## ``` get! : Str, fmt => Result body [HttpDecodingFailed] where body implements Decoding, fmt implements DecoderFormatting get! = \uri, fmt -> - response = send! { default_request & uri } + response = send!({ default_request & uri }) - Decode.fromBytes response.body fmt - |> Result.mapErr \_ -> HttpDecodingFailed + Decode.from_bytes(response.body, fmt) + |> Result.map_err(\_ -> HttpDecodingFailed) get_utf8! : Str => Result Str [BadBody Str] get_utf8! = \uri -> - response = send! { default_request & uri } + response = send!({ default_request & uri }) response.body - |> Str.fromUtf8 - |> Result.mapErr \_ -> BadBody "Invalid UTF-8" + |> Str.from_utf8 + |> Result.map_err(\_ -> BadBody("Invalid UTF-8")) diff --git a/platform/InternalArg.roc b/platform/InternalArg.roc index 1f5f2f33..bf9f5c91 100644 --- a/platform/InternalArg.roc +++ b/platform/InternalArg.roc @@ -8,7 +8,7 @@ ArgToAndFromHost := { } to_os_raw : ArgToAndFromHost -> [Unix (List U8), Windows (List U16)] -to_os_raw = \@ArgToAndFromHost inner -> +to_os_raw = \@ArgToAndFromHost(inner) -> when inner.type is - Unix -> Unix inner.unix - Windows -> Windows inner.windows + Unix -> Unix(inner.unix) + Windows -> Windows(inner.windows) diff --git a/platform/InternalCmd.roc b/platform/InternalCmd.roc index 2fe8f9bb..7be8a527 100644 --- a/platform/InternalCmd.roc +++ b/platform/InternalCmd.roc @@ -22,7 +22,7 @@ Output : { from_host_output : OutputFromHost -> Output from_host_output = \{ status, stdout, stderr } -> { - status: Result.mapErr status InternalIOErr.handle_err, + status: Result.map_err(status, InternalIOErr.handle_err), stdout, stderr, } diff --git a/platform/InternalDateTime.roc b/platform/InternalDateTime.roc index 0b219e73..cf9dd220 100644 --- a/platform/InternalDateTime.roc +++ b/platform/InternalDateTime.roc @@ -8,18 +8,18 @@ DateTime : { year : I128, month : I128, day : I128, hours : I128, minutes : I128 to_iso_8601 : DateTime -> Str to_iso_8601 = \{ year, month, day, hours, minutes, seconds } -> - year_str = year_with_padded_zeros year - month_str = month_with_padded_zeros month - day_str = day_with_padded_zeros day - hour_str = hours_with_padded_zeros hours - minute_str = minutes_with_padded_zeros minutes - seconds_str = seconds_with_padded_zeros seconds + year_str = year_with_padded_zeros(year) + month_str = month_with_padded_zeros(month) + day_str = day_with_padded_zeros(day) + hour_str = hours_with_padded_zeros(hours) + minute_str = minutes_with_padded_zeros(minutes) + seconds_str = seconds_with_padded_zeros(seconds) "$(year_str)-$(month_str)-$(day_str)T$(hour_str):$(minute_str):$(seconds_str)Z" year_with_padded_zeros : I128 -> Str year_with_padded_zeros = \year -> - year_str = Num.toStr year + year_str = Num.to_str(year) if year < 10 then "000$(year_str)" else if year < 100 then @@ -31,7 +31,7 @@ year_with_padded_zeros = \year -> month_with_padded_zeros : I128 -> Str month_with_padded_zeros = \month -> - month_str = Num.toStr month + month_str = Num.to_str(month) if month < 10 then "0$(month_str)" else @@ -59,37 +59,37 @@ is_leap_year = \year -> (year % 400 == 0) # expecpt when also divisible by 400 ) -expect is_leap_year 2000 -expect is_leap_year 2012 -expect !(is_leap_year 1900) -expect !(is_leap_year 2015) -expect List.map [2023, 1988, 1992, 1996] is_leap_year == [Bool.false, Bool.true, Bool.true, Bool.true] -expect List.map [1700, 1800, 1900, 2100, 2200, 2300, 2500, 2600] is_leap_year == [Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false] +expect is_leap_year(2000) +expect is_leap_year(2012) +expect !(is_leap_year(1900)) +expect !(is_leap_year(2015)) +expect List.map([2023, 1988, 1992, 1996], is_leap_year) == [Bool.false, Bool.true, Bool.true, Bool.true] +expect List.map([1700, 1800, 1900, 2100, 2200, 2300, 2500, 2600], is_leap_year) == [Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false] days_in_month : I128, I128 -> I128 days_in_month = \year, month -> - if List.contains [1, 3, 5, 7, 8, 10, 12] month then + if List.contains([1, 3, 5, 7, 8, 10, 12], month) then 31 - else if List.contains [4, 6, 9, 11] month then + else if List.contains([4, 6, 9, 11], month) then 30 else if month == 2 then - (if is_leap_year year then 29 else 28) + (if is_leap_year(year) then 29 else 28) else 0 -expect days_in_month 2023 1 == 31 # January -expect days_in_month 2023 2 == 28 # February -expect days_in_month 1996 2 == 29 # February in a leap year -expect days_in_month 2023 3 == 31 # March -expect days_in_month 2023 4 == 30 # April -expect days_in_month 2023 5 == 31 # May -expect days_in_month 2023 6 == 30 # June -expect days_in_month 2023 7 == 31 # July -expect days_in_month 2023 8 == 31 # August -expect days_in_month 2023 9 == 30 # September -expect days_in_month 2023 10 == 31 # October -expect days_in_month 2023 11 == 30 # November -expect days_in_month 2023 12 == 31 # December +expect days_in_month(2023, 1) == 31 # January +expect days_in_month(2023, 2) == 28 # February +expect days_in_month(1996, 2) == 29 # February in a leap year +expect days_in_month(2023, 3) == 31 # March +expect days_in_month(2023, 4) == 30 # April +expect days_in_month(2023, 5) == 31 # May +expect days_in_month(2023, 6) == 30 # June +expect days_in_month(2023, 7) == 31 # July +expect days_in_month(2023, 8) == 31 # August +expect days_in_month(2023, 9) == 30 # September +expect days_in_month(2023, 10) == 31 # October +expect days_in_month(2023, 11) == 30 # November +expect days_in_month(2023, 12) == 31 # December epoch_millis_to_datetime : I128 -> DateTime epoch_millis_to_datetime = \millis -> @@ -100,56 +100,63 @@ epoch_millis_to_datetime = \millis -> month = 1 year = 1970 - epoch_millis_to_datetimeHelp { - year, - month, - day, - hours: hours % 24, - minutes: minutes % 60, - seconds: seconds % 60, - } + epoch_millis_to_datetimeHelp( + { + year, + month, + day, + hours: hours % 24, + minutes: minutes % 60, + seconds: seconds % 60, + }, + ) epoch_millis_to_datetimeHelp : DateTime -> DateTime epoch_millis_to_datetimeHelp = \current -> - count_days_in_month = days_in_month current.year current.month + count_days_in_month = days_in_month(current.year, current.month) count_days_in_prev_month = if current.month == 1 then - days_in_month (current.year - 1) 12 + days_in_month((current.year - 1), 12) else - days_in_month current.year (current.month - 1) + days_in_month(current.year, (current.month - 1)) if current.day < 1 then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & year: if current.month == 1 then current.year - 1 else current.year, month: if current.month == 1 then 12 else current.month - 1, day: current.day + count_days_in_prev_month, - } + }, + ) else if current.hours < 0 then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & day: current.day - 1, hours: current.hours + 24, - } + }, + ) else if current.minutes < 0 then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & hours: current.hours - 1, minutes: current.minutes + 60, - } + }, + ) else if current.seconds < 0 then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & minutes: current.minutes - 1, seconds: current.seconds + 60, - } + }, + ) else if current.day > count_days_in_month then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & year: if current.month == 12 then current.year + 1 else current.year, month: if current.month == 12 then 1 else current.month + 1, day: current.day - count_days_in_month, - } + }, + ) else current diff --git a/platform/InternalHttp.roc b/platform/InternalHttp.roc index 54a7a2a7..e994ca09 100644 --- a/platform/InternalHttp.roc +++ b/platform/InternalHttp.roc @@ -19,7 +19,7 @@ module [ # FOR ROC # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods -Method : [Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch, Extension Str] +Method : [OPTIONS, GET, POST, PUT, DELETE, HEAD, TRACE, CONNECT, PATCH, EXTENSION Str] # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers Header : { name : Str, value : Str } @@ -64,72 +64,72 @@ to_host_response = \{ status, headers, body } -> { to_host_request : Request -> RequestToAndFromHost to_host_request = \{ method, headers, uri, body, timeout_ms } -> { - method: to_host_method method, - method_ext: to_host_method_ext method, + method: to_host_method(method), + method_ext: to_host_method_ext(method), headers, uri, body, - timeout_ms: to_host_timeout timeout_ms, + timeout_ms: to_host_timeout(timeout_ms), } to_host_method : Method -> _ to_host_method = \method -> when method is - Options -> 5 - Get -> 3 - Post -> 7 - Put -> 8 - Delete -> 1 - Head -> 4 - Trace -> 9 - Connect -> 0 - Patch -> 6 - Extension _ -> 2 + OPTIONS -> 5 + GET -> 3 + POST -> 7 + PUT -> 8 + DELETE -> 1 + HEAD -> 4 + TRACE -> 9 + CONNECT -> 0 + PATCH -> 6 + EXTENSION(_) -> 2 to_host_method_ext : Method -> Str to_host_method_ext = \method -> when method is - Extension ext -> ext + EXTENSION(ext) -> ext _ -> "" to_host_timeout : _ -> U64 to_host_timeout = \timeout -> when timeout is - TimeoutMilliseconds ms -> ms + TimeoutMilliseconds(ms) -> ms NoTimeout -> 0 from_host_request : RequestToAndFromHost -> Request from_host_request = \{ method, method_ext, headers, uri, body, timeout_ms } -> { - method: from_host_method method method_ext, + method: from_host_method(method, method_ext), headers, uri, body, - timeout_ms: from_host_timeout timeout_ms, + timeout_ms: from_host_timeout(timeout_ms), } from_host_method : U64, Str -> Method from_host_method = \tag, ext -> when tag is - 5 -> Options - 3 -> Get - 7 -> Post - 8 -> Put - 1 -> Delete - 4 -> Head - 9 -> Trace - 0 -> Connect - 6 -> Patch - 2 -> Extension ext - _ -> crash "invalid tag from host" + 5 -> OPTIONS + 3 -> GET + 7 -> POST + 8 -> PUT + 1 -> DELETE + 4 -> HEAD + 9 -> TRACE + 0 -> CONNECT + 6 -> PATCH + 2 -> EXTENSION(ext) + _ -> crash("invalid tag from host") from_host_timeout : U64 -> [TimeoutMilliseconds U64, NoTimeout] from_host_timeout = \timeout -> when timeout is 0 -> NoTimeout - _ -> TimeoutMilliseconds timeout + _ -> TimeoutMilliseconds(timeout) -expect from_host_timeout 0 == NoTimeout -expect from_host_timeout 1 == TimeoutMilliseconds 1 +expect from_host_timeout(0) == NoTimeout +expect from_host_timeout(1) == TimeoutMilliseconds(1) from_host_response : ResponseToAndFromHost -> Response from_host_response = \{ status, headers, body } -> { diff --git a/platform/InternalIOErr.roc b/platform/InternalIOErr.roc index ef95d767..5d5f20e5 100644 --- a/platform/InternalIOErr.roc +++ b/platform/InternalIOErr.roc @@ -55,4 +55,4 @@ handle_err = \{ tag, msg } -> Interrupted -> Interrupted Unsupported -> Unsupported OutOfMemory -> OutOfMemory - Other | EndOfFile -> Other msg + Other | EndOfFile -> Other(msg) diff --git a/platform/InternalPath.roc b/platform/InternalPath.roc index 258fc8b1..05779bf7 100644 --- a/platform/InternalPath.roc +++ b/platform/InternalPath.roc @@ -57,22 +57,22 @@ wrap : UnwrappedPath -> InternalPath wrap = @InternalPath unwrap : InternalPath -> UnwrappedPath -unwrap = \@InternalPath raw -> raw +unwrap = \@InternalPath(raw) -> raw ## TODO do this in the host, and iterate over the Str ## bytes when possible instead of always converting to ## a heap-allocated List. to_bytes : InternalPath -> List U8 -to_bytes = \@InternalPath path -> +to_bytes = \@InternalPath(path) -> when path is - FromOperatingSystem bytes -> bytes - ArbitraryBytes bytes -> bytes - FromStr str -> Str.toUtf8 str + FromOperatingSystem(bytes) -> bytes + ArbitraryBytes(bytes) -> bytes + FromStr(str) -> Str.to_utf8(str) from_arbitrary_bytes : List U8 -> InternalPath from_arbitrary_bytes = \bytes -> - @InternalPath (ArbitraryBytes bytes) + @InternalPath(ArbitraryBytes(bytes)) from_os_bytes : List U8 -> InternalPath from_os_bytes = \bytes -> - @InternalPath (FromOperatingSystem bytes) + @InternalPath(FromOperatingSystem(bytes)) diff --git a/platform/Locale.roc b/platform/Locale.roc index 57a9b51d..3ffddfba 100644 --- a/platform/Locale.roc +++ b/platform/Locale.roc @@ -10,8 +10,8 @@ import Host ## The returned [Str] is a BCP 47 language tag, like `en-US` or `fr-CA`. get! : {} => Result Str [NotAvailable] get! = \{} -> - Host.get_locale! {} - |> Result.mapErr \{} -> NotAvailable + Host.get_locale!({}) + |> Result.map_err(\{} -> NotAvailable) ## Returns the preferred locales for the system or application. ## diff --git a/platform/Path.roc b/platform/Path.roc index 6116e20e..4f3bf215 100644 --- a/platform/Path.roc +++ b/platform/Path.roc @@ -57,10 +57,11 @@ IOErr : InternalIOErr.IOErr ## ## ``` ## # Writes `{"some":"json stuff"}` to the file `output.json`: -## Path.write! -## { some: "json stuff" } -## (Path.from_str "output.json") -## Json.toCompactUtf8 +## Path.write!( +## { some: "json stuff" }, +## Path.from_str("output.json"), +## Json.toCompactUtf8, +## )? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -69,16 +70,16 @@ IOErr : InternalIOErr.IOErr ## > To write unformatted bytes to a file, you can use [Path.write_bytes!] instead. write! : val, Path, fmt => Result {} [FileWriteErr Path IOErr] where val implements Encoding, fmt implements EncoderFormatting write! = \val, path, fmt -> - bytes = Encode.toBytes val fmt + bytes = Encode.to_bytes(val, fmt) # TODO handle encoding errors here, once they exist - write_bytes! bytes path + write_bytes!(bytes, path) ## Writes bytes to a file. ## ## ``` ## # Writes the bytes 1, 2, 3 to the file `myfile.dat`. -## Path.write_bytes! [1, 2, 3] (Path.from_str "myfile.dat") +## Path.write_bytes!([1, 2, 3], Path.from_str("myfile.dat"))? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -86,16 +87,16 @@ write! = \val, path, fmt -> ## > To format data before writing it to a file, you can use [Path.write!] instead. write_bytes! : List U8, Path => Result {} [FileWriteErr Path IOErr] write_bytes! = \bytes, path -> - path_bytes = InternalPath.to_bytes path + path_bytes = InternalPath.to_bytes(path) - Host.file_write_bytes! path_bytes bytes - |> Result.mapErr \err -> FileWriteErr path (InternalIOErr.handle_err err) + Host.file_write_bytes!(path_bytes, bytes) + |> Result.map_err(\err -> FileWriteErr(path, InternalIOErr.handle_err(err))) ## Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## ## ``` ## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`. -## Path.write_utf8! "Hello!" (Path.from_str "myfile.txt") +## Path.write_utf8!("Hello!", Path.from_str("myfile.txt"))? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -103,10 +104,10 @@ write_bytes! = \bytes, path -> ## > To write unformatted bytes to a file, you can use [Path.write_bytes!] instead. write_utf8! : Str, Path => Result {} [FileWriteErr Path IOErr] write_utf8! = \str, path -> - path_bytes = InternalPath.to_bytes path + path_bytes = InternalPath.to_bytes(path) - Host.file_write_utf8! path_bytes str - |> Result.mapErr \err -> FileWriteErr path (InternalIOErr.handle_err err) + Host.file_write_utf8!(path_bytes, str) + |> Result.map_err(\err -> FileWriteErr(path, InternalIOErr.handle_err(err))) ## Note that the path may not be valid depending on the filesystem where it is used. ## For example, paths containing `:` are valid on ext4 and NTFS filesystems, but not @@ -119,7 +120,7 @@ write_utf8! = \str, path -> ## up front for a false sense of security (given symlinks, parts of a path being renamed, etc.). from_str : Str -> Path from_str = \str -> - FromStr str + FromStr(str) |> InternalPath.wrap ## Not all filesystems use Unicode paths. This function can be used to create a path which @@ -129,7 +130,7 @@ from_str = \str -> ## (e.g. `Path.read_bytes` or `WriteStream.openPath`) will fail. from_bytes : List U8 -> Path from_bytes = \bytes -> - ArbitraryBytes bytes + ArbitraryBytes(bytes) |> InternalPath.wrap ## Unfortunately, operating system paths do not include information about which charset @@ -165,13 +166,13 @@ from_bytes = \bytes -> ## `toStrUsingCharset` instead of [display]. display : Path -> Str display = \path -> - when InternalPath.unwrap path is - FromStr str -> str - FromOperatingSystem bytes | ArbitraryBytes bytes -> - when Str.fromUtf8 bytes is - Ok str -> str + when InternalPath.unwrap(path) is + FromStr(str) -> str + FromOperatingSystem(bytes) | ArbitraryBytes(bytes) -> + when Str.from_utf8(bytes) is + Ok(str) -> str # TODO: this should use the builtin Str.display to display invalid UTF-8 chars in just the right spots, but that does not exist yet! - Err _ -> "�" + Err(_) -> "�" ## Returns true if the path exists on disk and is pointing at a directory. ## Returns `Ok false` if the path exists and it is not a directory. If the path does not exist, @@ -182,8 +183,8 @@ display = \path -> ## > [`File.is_dir`](File#is_dir!) does the same thing, except it takes a [Str] instead of a [Path]. is_dir! : Path => Result Bool [PathErr IOErr] is_dir! = \path -> - res = type!? path - Ok (res == IsDir) + res = type!(path)? + Ok((res == IsDir)) ## Returns true if the path exists on disk and is pointing at a regular file. ## Returns `Ok false` if the path exists and it is not a file. If the path does not exist, @@ -194,8 +195,8 @@ is_dir! = \path -> ## > [`File.is_file`](File#is_file!) does the same thing, except it takes a [Str] instead of a [Path]. is_file! : Path => Result Bool [PathErr IOErr] is_file! = \path -> - res = type!? path - Ok (res == IsFile) + res = type!(path)? + Ok((res == IsFile)) ## Returns true if the path exists on disk and is pointing at a symbolic link. ## Returns `Ok false` if the path exists and it is not a symbolic link. If the path does not exist, @@ -206,59 +207,61 @@ is_file! = \path -> ## > [`File.is_sym_link`](File#is_sym_link!) does the same thing, except it takes a [Str] instead of a [Path]. is_sym_link! : Path => Result Bool [PathErr IOErr] is_sym_link! = \path -> - res = type!? path - Ok (res == IsSymLink) + res = type!(path)? + Ok((res == IsSymLink)) ## Return the type of the path if the path exists on disk. ## ## > [`File.type`](File#type!) does the same thing, except it takes a [Str] instead of a [Path]. type! : Path => Result [IsFile, IsDir, IsSymLink] [PathErr IOErr] type! = \path -> - Host.path_type! (InternalPath.to_bytes path) - |> Result.mapErr \err -> PathErr (InternalIOErr.handle_err err) - |> Result.map \path_type -> - if path_type.is_sym_link then - IsSymLink - else if path_type.is_dir then - IsDir - else - IsFile + Host.path_type!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> PathErr(InternalIOErr.handle_err(err))) + |> Result.map( + \path_type -> + if path_type.is_sym_link then + IsSymLink + else if path_type.is_dir then + IsDir + else + IsFile, + ) ## If the last component of this path has no `.`, appends `.` followed by the given string. ## Otherwise, replaces everything after the last `.` with the given string. ## ## ``` ## # Each of these gives "foo/bar/baz.txt" -## Path.from_str "foo/bar/baz" |> Path.with_extension "txt" -## Path.from_str "foo/bar/baz." |> Path.with_extension "txt" -## Path.from_str "foo/bar/baz.xz" |> Path.with_extension "txt" +## Path.from_str("foo/bar/baz") |> Path.with_extension("txt") +## Path.from_str("foo/bar/baz.") |> Path.with_extension("txt") +## Path.from_str("foo/bar/baz.xz") |> Path.with_extension("txt") ## ``` with_extension : Path, Str -> Path with_extension = \path, extension -> - when InternalPath.unwrap path is - FromOperatingSystem bytes | ArbitraryBytes bytes -> + when InternalPath.unwrap(path) is + FromOperatingSystem(bytes) | ArbitraryBytes(bytes) -> before_dot = - when List.splitLast bytes (Num.toU8 '.') is - Ok { before } -> before - Err NotFound -> bytes + when List.split_last(bytes, Num.to_u8('.')) is + Ok({ before }) -> before + Err(NotFound) -> bytes before_dot - |> List.reserve (Str.countUtf8Bytes extension |> Num.intCast |> Num.addSaturated 1) - |> List.append (Num.toU8 '.') - |> List.concat (Str.toUtf8 extension) + |> List.reserve((Str.count_utf8_bytes(extension) |> Num.int_cast |> Num.add_saturated(1))) + |> List.append(Num.to_u8('.')) + |> List.concat(Str.to_utf8(extension)) |> ArbitraryBytes |> InternalPath.wrap - FromStr str -> + FromStr(str) -> before_dot = - when Str.splitLast str "." is - Ok { before } -> before - Err NotFound -> str + when Str.split_last(str, ".") is + Ok({ before }) -> before + Err(NotFound) -> str before_dot - |> Str.reserve (Str.countUtf8Bytes extension |> Num.addSaturated 1) - |> Str.concat "." - |> Str.concat extension + |> Str.reserve((Str.count_utf8_bytes(extension) |> Num.add_saturated(1))) + |> Str.concat(".") + |> Str.concat(extension) |> FromStr |> InternalPath.wrap @@ -272,7 +275,7 @@ with_extension = \path, extension -> ## ## ``` ## # Deletes the file named `myfile.dat` -## Path.delete (Path.from_str "myfile.dat") [1, 2, 3] +## Path.delete(Path.from_str("myfile.dat"), [1, 2, 3])? ## ``` ## ## > This does not securely erase the file's contents from disk; instead, the operating @@ -284,14 +287,14 @@ with_extension = \path, extension -> ## > [`File.delete`](File#delete!) does the same thing, except it takes a [Str] instead of a [Path]. delete! : Path => Result {} [FileWriteErr Path IOErr] delete! = \path -> - Host.file_delete! (InternalPath.to_bytes path) - |> Result.mapErr \err -> FileWriteErr path (InternalIOErr.handle_err err) + Host.file_delete!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> FileWriteErr(path, InternalIOErr.handle_err(err))) ## Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ## ## ``` ## # Reads UTF-8 encoded text into a Str from the file "myfile.txt" -## Path.read_utf8 (Path.from_str "myfile.txt") +## contents_str = Path.read_utf8(Path.from_str("myfile.txt"))? ## ``` ## ## This opens the file first and closes it after reading its contents. @@ -303,17 +306,17 @@ delete! = \path -> read_utf8! : Path => Result Str [FileReadErr Path IOErr, FileReadUtf8Err Path _] read_utf8! = \path -> bytes = - Host.file_read_bytes! (InternalPath.to_bytes path) - |> Result.mapErr? \read_err -> FileReadErr path (InternalIOErr.handle_err read_err) + Host.file_read_bytes!(InternalPath.to_bytes(path)) + |> Result.map_err?(\read_err -> FileReadErr(path, InternalIOErr.handle_err(read_err))) - Str.fromUtf8 bytes - |> Result.mapErr \err -> FileReadUtf8Err path err + Str.from_utf8(bytes) + |> Result.map_err(\err -> FileReadUtf8Err(path, err)) ## Reads all the bytes in a file. ## ## ``` ## # Read all the bytes in `myfile.txt`. -## Path.read_bytes! (Path.from_str "myfile.txt") +## contents_bytes = Path.read_bytes!(Path.from_str("myfile.txt"))? ## ``` ## ## This opens the file first and closes it after reading its contents. @@ -323,17 +326,17 @@ read_utf8! = \path -> ## > [`File.read_bytes`](File#read_bytes!) does the same thing, except it takes a [Str] instead of a [Path]. read_bytes! : Path => Result (List U8) [FileReadErr Path IOErr] read_bytes! = \path -> - Host.file_read_bytes! (InternalPath.to_bytes path) - |> Result.mapErr \err -> FileReadErr path (InternalIOErr.handle_err err) + Host.file_read_bytes!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> FileReadErr(path, InternalIOErr.handle_err(err))) ## Lists the files and directories inside the directory. ## ## > [`Dir.list`](Dir#list!) does the same thing, except it takes a [Str] instead of a [Path]. list_dir! : Path => Result (List Path) [DirErr IOErr] list_dir! = \path -> - when Host.dir_list! (InternalPath.to_bytes path) is - Ok entries -> Ok (List.map entries InternalPath.from_os_bytes) - Err err -> Err (DirErr (InternalIOErr.handle_err err)) + when Host.dir_list!(InternalPath.to_bytes(path)) is + Ok(entries) -> Ok(List.map(entries, InternalPath.from_os_bytes)) + Err(err) -> Err(DirErr(InternalIOErr.handle_err(err))) ## Deletes a directory if it's empty ## @@ -346,8 +349,8 @@ list_dir! = \path -> ## > [`Dir.delete_empty`](Dir#delete_empty!) does the same thing, except it takes a [Str] instead of a [Path]. delete_empty! : Path => Result {} [DirErr IOErr] delete_empty! = \path -> - Host.dir_delete_empty! (InternalPath.to_bytes path) - |> Result.mapErr \err -> DirErr (InternalIOErr.handle_err err) + Host.dir_delete_empty!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> DirErr(InternalIOErr.handle_err(err))) ## Recursively deletes a directory as well as all files and directories ## inside it. @@ -361,8 +364,8 @@ delete_empty! = \path -> ## > [`Dir.delete_all`](Dir#delete_all!) does the same thing, except it takes a [Str] instead of a [Path]. delete_all! : Path => Result {} [DirErr IOErr] delete_all! = \path -> - Host.dir_delete_all! (InternalPath.to_bytes path) - |> Result.mapErr \err -> DirErr (InternalIOErr.handle_err err) + Host.dir_delete_all!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> DirErr(InternalIOErr.handle_err(err))) ## Creates a directory ## @@ -374,8 +377,8 @@ delete_all! = \path -> ## > [`Dir.create`](Dir#create!) does the same thing, except it takes a [Str] instead of a [Path]. create_dir! : Path => Result {} [DirErr IOErr] create_dir! = \path -> - Host.dir_create! (InternalPath.to_bytes path) - |> Result.mapErr \err -> DirErr (InternalIOErr.handle_err err) + Host.dir_create!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> DirErr(InternalIOErr.handle_err(err))) ## Creates a directory recursively adding any missing parent directories. ## @@ -386,8 +389,8 @@ create_dir! = \path -> ## > [`Dir.create_all`](Dir#create_all!) does the same thing, except it takes a [Str] instead of a [Path]. create_all! : Path => Result {} [DirErr IOErr] create_all! = \path -> - Host.dir_create_all! (InternalPath.to_bytes path) - |> Result.mapErr \err -> DirErr (InternalIOErr.handle_err err) + Host.dir_create_all!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> DirErr(InternalIOErr.handle_err(err))) ## Creates a new hard link on the filesystem. ## @@ -399,6 +402,6 @@ create_all! = \path -> ## > [File.hard_link!] does the same thing, except it takes a [Str] instead of a [Path]. hard_link! : Path => Result {} [LinkErr IOErr] hard_link! = \path -> - Host.hard_link! (InternalPath.to_bytes path) - |> Result.mapErr InternalIOErr.handle_err - |> Result.mapErr LinkErr + Host.hard_link!(InternalPath.to_bytes(path)) + |> Result.map_err(InternalIOErr.handle_err) + |> Result.map_err(LinkErr) diff --git a/platform/Sleep.roc b/platform/Sleep.roc index 8c677455..e5ca383f 100644 --- a/platform/Sleep.roc +++ b/platform/Sleep.roc @@ -9,4 +9,4 @@ import Host ## millis! : U64 => {} millis! = \n -> - Host.sleep_millis! n + Host.sleep_millis!(n) diff --git a/platform/Sqlite.roc b/platform/Sqlite.roc index dfe044f0..549722ef 100644 --- a/platform/Sqlite.roc +++ b/platform/Sqlite.roc @@ -145,19 +145,19 @@ Stmt := Box {} ## preparing the query each time it is called. This is usually done in `init!` with the prepared `Stmt` stored in the model. ## ## ``` -## prepared_query = try Sqlite.prepare! { +## prepared_query = Sqlite.prepare!({ ## path : "path/to/database.db", ## query : "SELECT * FROM todos;", -## } +## })? ## -## Sqlite.query_many_prepared! { +## Sqlite.query_many_prepared!({ ## stmt: prepared_query, ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "task", +## id: Sqlite.i64("id"), +## task: Sqlite.str("task"), ## }, -## } +## }) ## ``` prepare! : { @@ -166,51 +166,51 @@ prepare! : } => Result Stmt [SqliteErr ErrCode Str] prepare! = \{ path, query: q } -> - Host.sqlite_prepare! path q - |> Result.map @Stmt - |> Result.mapErr internal_to_external_error + Host.sqlite_prepare!(path, q) + |> Result.map(@Stmt) + |> Result.map_err(internal_to_external_error) # internal use only bind! : Stmt, List Binding => Result {} [SqliteErr ErrCode Str] -bind! = \@Stmt stmt, bindings -> - Host.sqlite_bind! stmt bindings - |> Result.mapErr internal_to_external_error +bind! = \@Stmt(stmt), bindings -> + Host.sqlite_bind!(stmt, bindings) + |> Result.map_err(internal_to_external_error) # internal use only columns! : Stmt => List Str -columns! = \@Stmt stmt -> - Host.sqlite_columns! stmt +columns! = \@Stmt(stmt) -> + Host.sqlite_columns!(stmt) # internal use only column_value! : Stmt, U64 => Result Value [SqliteErr ErrCode Str] -column_value! = \@Stmt stmt, i -> - Host.sqlite_column_value! stmt i - |> Result.mapErr internal_to_external_error +column_value! = \@Stmt(stmt), i -> + Host.sqlite_column_value!(stmt, i) + |> Result.map_err(internal_to_external_error) # internal use only step! : Stmt => Result [Row, Done] [SqliteErr ErrCode Str] -step! = \@Stmt stmt -> - Host.sqlite_step! stmt - |> Result.mapErr internal_to_external_error +step! = \@Stmt(stmt) -> + Host.sqlite_step!(stmt) + |> Result.map_err(internal_to_external_error) # internal use only reset! : Stmt => Result {} [SqliteErr ErrCode Str] -reset! = \@Stmt stmt -> - Host.sqlite_reset! stmt - |> Result.mapErr internal_to_external_error +reset! = \@Stmt(stmt) -> + Host.sqlite_reset!(stmt) + |> Result.map_err(internal_to_external_error) ## Execute a SQL statement that doesn't return any rows (like INSERT, UPDATE, DELETE). ## ## Example: ## ``` -## Sqlite.execute! { +## Sqlite.execute!({ ## path: "path/to/database.db", ## query: "INSERT INTO users (first, last) VALUES (:first, :last);", ## bindings: [ -## { name: ":first", value: String "John" }, -## { name: ":last", value: String "Smith" }, +## { name: ":first", value: String("John") }, +## { name: ":last", value: String("Smith") }, ## ], -## } +## })? ## ``` execute! : { @@ -220,8 +220,8 @@ execute! : } => Result {} [SqliteErr ErrCode Str, UnhandledRows] execute! = \{ path, query: q, bindings } -> - stmt = try prepare! { path, query: q } - execute_prepared! { stmt, bindings } + stmt = try(prepare!, { path, query: q }) + execute_prepared!({ stmt, bindings }) ## Execute a prepared SQL statement that doesn't return any rows. ## @@ -234,30 +234,30 @@ execute_prepared! : } => Result {} [SqliteErr ErrCode Str, UnhandledRows] execute_prepared! = \{ stmt, bindings } -> - try bind! stmt bindings - res = step! stmt - try reset! stmt + try(bind!, stmt, bindings) + res = step!(stmt) + try(reset!, stmt) when res is - Ok Done -> - Ok {} + Ok(Done) -> + Ok({}) - Ok Row -> - Err UnhandledRows + Ok(Row) -> + Err(UnhandledRows) - Err e -> - Err e + Err(e) -> + Err(e) ## Execute a SQL query and decode exactly one row into a value. ## ## Example: ## ``` ## # count the number of rows in the `users` table -## count = try Sqlite.query! { +## count = Sqlite.query!({ ## path: db_path, ## query: "SELECT COUNT(*) as \"count\" FROM users;", ## bindings: [], -## row: Sqlite.u64 "count", -## } +## row: Sqlite.u64("count"), +## })? ## ``` query! : { @@ -268,8 +268,8 @@ query! : } => Result a (SqlDecodeErr (RowCountErr err)) query! = \{ path, query: q, bindings, row } -> - stmt = try prepare! { path, query: q } - query_prepared! { stmt, bindings, row } + stmt = try(prepare!, { path, query: q }) + query_prepared!({ stmt, bindings, row }) ## Execute a prepared SQL query and decode exactly one row into a value. ## @@ -283,24 +283,24 @@ query_prepared! : } => Result a (SqlDecodeErr (RowCountErr err)) query_prepared! = \{ stmt, bindings, row: decode } -> - try bind! stmt bindings - res = decode_exactly_one_row! stmt decode - try reset! stmt + try(bind!, stmt, bindings) + res = decode_exactly_one_row!(stmt, decode) + try(reset!, stmt) res ## Execute a SQL query and decode multiple rows into a list of values. ## ## Example: ## ``` -## Sqlite.query_many! { +## rows = Sqlite.query_many!({ ## path: "path/to/database.db", ## query: "SELECT * FROM todos;", ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "task", +## id: Sqlite.i64("id"), +## task: Sqlite.str("task"), ## }, -## } +## })? ## ``` query_many! : { @@ -311,8 +311,8 @@ query_many! : } => Result (List a) (SqlDecodeErr err) query_many! = \{ path, query: q, bindings, rows } -> - stmt = try prepare! { path, query: q } - query_many_prepared! { stmt, bindings, rows } + stmt = try(prepare!, { path, query: q }) + query_many_prepared!({ stmt, bindings, rows }) ## Execute a prepared SQL query and decode multiple rows into a list of values. ## @@ -326,9 +326,9 @@ query_many_prepared! : } => Result (List a) (SqlDecodeErr err) query_many_prepared! = \{ stmt, bindings, rows: decode } -> - try bind! stmt bindings - res = decode_rows! stmt decode - try reset! stmt + try(bind!, stmt, bindings) + res = decode_rows!(stmt, decode) + try(reset!, stmt) res SqlDecodeErr err : [FieldNotFound Str, SqliteErr ErrCode Str]err @@ -339,119 +339,128 @@ SqlDecode a err := List Str -> (Stmt => Result a (SqlDecodeErr err)) ## Example: ## ``` ## { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "task", +## id: Sqlite.i64("id"), +## task: Sqlite.str("task"), ## } ## ``` decode_record : SqlDecode a err, SqlDecode b err, (a, b -> c) -> SqlDecode c err -decode_record = \@SqlDecode gen_first, @SqlDecode gen_second, mapper -> - @SqlDecode \cols -> - decode_first! = gen_first cols - decode_second! = gen_second cols - - \stmt -> - first = try decode_first! stmt - second = try decode_second! stmt - Ok (mapper first second) +decode_record = \@SqlDecode(gen_first), @SqlDecode(gen_second), mapper -> + @SqlDecode( + \cols -> + decode_first! = gen_first(cols) + decode_second! = gen_second(cols) + + \stmt -> + first = try(decode_first!, stmt) + second = try(decode_second!, stmt) + Ok(mapper(first, second)), + ) ## Transform the output of a decoder by applying a function to the decoded value. ## ## Example: ## ``` -## Sqlite.i64 "id" |> Sqlite.map_value Num.toStr +## Sqlite.i64("id") |> Sqlite.map_value(Num.to_str) ## ``` map_value : SqlDecode a err, (a -> b) -> SqlDecode b err -map_value = \@SqlDecode gen_decode, mapper -> - @SqlDecode \cols -> - decode! = gen_decode cols +map_value = \@SqlDecode(gen_decode), mapper -> + @SqlDecode( + \cols -> + decode! = gen_decode(cols) - \stmt -> - val = try decode! stmt - Ok (mapper val) + \stmt -> + val = try(decode!, stmt) + Ok(mapper(val)), + ) RowCountErr err : [NoRowsReturned, TooManyRowsReturned]err # internal use only decode_exactly_one_row! : Stmt, SqlDecode a (RowCountErr err) => Result a (SqlDecodeErr (RowCountErr err)) -decode_exactly_one_row! = \stmt, @SqlDecode gen_decode -> - cols = columns! stmt - decode_row! = gen_decode cols +decode_exactly_one_row! = \stmt, @SqlDecode(gen_decode) -> + cols = columns!(stmt) + decode_row! = gen_decode(cols) - when try step! stmt is + when try(step!, stmt) is Row -> - row = try decode_row! stmt - when try step! stmt is + row = try(decode_row!, stmt) + when try(step!, stmt) is Done -> - Ok row + Ok(row) Row -> - Err TooManyRowsReturned + Err(TooManyRowsReturned) Done -> - Err NoRowsReturned + Err(NoRowsReturned) # internal use only decode_rows! : Stmt, SqlDecode a err => Result (List a) (SqlDecodeErr err) -decode_rows! = \stmt, @SqlDecode gen_decode -> - cols = columns! stmt - decode_row! = gen_decode cols +decode_rows! = \stmt, @SqlDecode(gen_decode) -> + cols = columns!(stmt) + decode_row! = gen_decode(cols) helper! = \out -> - when try step! stmt is + when try(step!, stmt) is Done -> - Ok out + Ok(out) Row -> - row = try decode_row! stmt + row = try(decode_row!, stmt) - List.append out row + List.append(out, row) |> helper! - helper! [] + helper!([]) # internal use only decoder : (Value -> Result a (SqlDecodeErr err)) -> (Str -> SqlDecode a err) -decoder = \fn -> \name -> - @SqlDecode \cols -> - - found = List.findFirstIndex cols \x -> x == name - when found is - Ok index -> - \stmt -> - try column_value! stmt index - |> fn - - Err NotFound -> - \_ -> - Err (FieldNotFound name) +decoder = \fn -> + \name -> + @SqlDecode( + \cols -> + + found = List.find_first_index(cols, \x -> x == name) + when found is + Ok(index) -> + \stmt -> + try(column_value!, stmt, index) + |> fn + + Err(NotFound) -> + \_ -> + Err(FieldNotFound(name)), + ) ## Decode a [Value] keeping it tagged. This is useful when data could be many possible types. ## ## For example here we build a decoder that decodes the rows into a list of records with `id` and `mixed_data` fields: ## ``` -## Sqlite.query_many! { +## rows = Sqlite.query_many!({ ## path: "path/to/database.db", ## query: "SELECT id, mix_data FROM users;", ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## mix_data: Sqlite.tagged_value "mixed_data", +## id: Sqlite.i64("id"), +## mix_data: Sqlite.tagged_value("mixed_data"), ## }, -## } +## })? ## ``` tagged_value : Str -> SqlDecode Value [] -tagged_value = decoder \val -> - Ok val +tagged_value = decoder( + \val -> + Ok(val), +) to_unexpected_type_err = \val -> type = when val is - Integer _ -> Integer - Real _ -> Real - String _ -> String - Bytes _ -> Bytes + Integer(_) -> Integer + Real(_) -> Real + String(_) -> String + Bytes(_) -> Bytes Null -> Null - Err (UnexpectedType type) + Err(UnexpectedType(type)) UnexpectedTypeErr : [UnexpectedType [Integer, Real, String, Bytes, Null]] @@ -459,97 +468,105 @@ UnexpectedTypeErr : [UnexpectedType [Integer, Real, String, Bytes, Null]] ## ## For example here we build a decoder that decodes the rows into a list of records with `id` and `name` fields: ## ``` -## Sqlite.query_many! { +## rows = Sqlite.query_many!({ ## path: "path/to/database.db", ## query: "SELECT id, name FROM users;", ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "name", +## id: Sqlite.i64("id"), +## task: Sqlite.str("name"), ## }, -## } +## })? ## ``` str : Str -> SqlDecode Str UnexpectedTypeErr -str = decoder \val -> - when val is - String s -> Ok s - _ -> to_unexpected_type_err val +str = decoder( + \val -> + when val is + String(s) -> Ok(s) + _ -> to_unexpected_type_err(val), +) ## Decode a [Value] to a [List U8]. bytes : Str -> SqlDecode (List U8) UnexpectedTypeErr -bytes = decoder \val -> - when val is - Bytes b -> Ok b - _ -> to_unexpected_type_err val +bytes = decoder( + \val -> + when val is + Bytes(b) -> Ok(b) + _ -> to_unexpected_type_err(val), +) # internal use only int_decoder : (I64 -> Result a err) -> (Str -> SqlDecode a [FailedToDecodeInteger err]UnexpectedTypeErr) int_decoder = \cast -> - decoder \val -> - when val is - Integer i -> cast i |> Result.mapErr FailedToDecodeInteger - _ -> to_unexpected_type_err val + decoder( + \val -> + when val is + Integer(i) -> cast(i) |> Result.map_err(FailedToDecodeInteger) + _ -> to_unexpected_type_err(val), + ) # internal use only real_decoder : (F64 -> Result a err) -> (Str -> SqlDecode a [FailedToDecodeReal err]UnexpectedTypeErr) real_decoder = \cast -> - decoder \val -> - when val is - Real r -> cast r |> Result.mapErr FailedToDecodeReal - _ -> to_unexpected_type_err val + decoder( + \val -> + when val is + Real(r) -> cast(r) |> Result.map_err(FailedToDecodeReal) + _ -> to_unexpected_type_err(val), + ) ## Decode a [Value] to a [I64]. ## ## For example here we build a decoder that decodes the rows into a list of records with `id` and `name` fields: ## ``` -## Sqlite.query_many! { +## rows = Sqlite.query_many!({ ## path: "path/to/database.db", ## query: "SELECT id, name FROM users;", ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "name", +## id: Sqlite.i64("id"), +## task: Sqlite.str("name"), ## }, -## } +## })? ## ``` i64 : Str -> SqlDecode I64 [FailedToDecodeInteger []]UnexpectedTypeErr -i64 = int_decoder Ok +i64 = int_decoder(Ok) ## Decode a [Value] to a [I32]. i32 : Str -> SqlDecode I32 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -i32 = int_decoder Num.toI32Checked +i32 = int_decoder(Num.to_i32_checked) ## Decode a [Value] to a [I16]. i16 : Str -> SqlDecode I16 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -i16 = int_decoder Num.toI16Checked +i16 = int_decoder(Num.to_i16_checked) ## Decode a [Value] to a [I8]. i8 : Str -> SqlDecode I8 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -i8 = int_decoder Num.toI8Checked +i8 = int_decoder(Num.to_i8_checked) ## Decode a [Value] to a [U64]. u64 : Str -> SqlDecode U64 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -u64 = int_decoder Num.toU64Checked +u64 = int_decoder(Num.to_u64_checked) ## Decode a [Value] to a [U32]. u32 : Str -> SqlDecode U32 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -u32 = int_decoder Num.toU32Checked +u32 = int_decoder(Num.to_u32_checked) ## Decode a [Value] to a [U16]. u16 : Str -> SqlDecode U16 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -u16 = int_decoder Num.toU16Checked +u16 = int_decoder(Num.to_u16_checked) ## Decode a [Value] to a [U8]. u8 : Str -> SqlDecode U8 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -u8 = int_decoder Num.toU8Checked +u8 = int_decoder(Num.to_u8_checked) ## Decode a [Value] to a [F64]. f64 : Str -> SqlDecode F64 [FailedToDecodeReal []]UnexpectedTypeErr -f64 = real_decoder Ok +f64 = real_decoder(Ok) ## Decode a [Value] to a [F32]. f32 : Str -> SqlDecode F32 [FailedToDecodeReal []]UnexpectedTypeErr -f32 = real_decoder (\x -> Num.toF32 x |> Ok) +f32 = real_decoder(\x -> Num.to_f32(x) |> Ok) # TODO: Mising Num.toDec and Num.toDecChecked # dec = realSqlDecoder Ok @@ -562,77 +579,85 @@ Nullable a : [NotNull a, Null] ## Decode a [Value] to a [Nullable Str]. nullable_str : Str -> SqlDecode (Nullable Str) UnexpectedTypeErr -nullable_str = decoder \val -> - when val is - String s -> Ok (NotNull s) - Null -> Ok Null - _ -> to_unexpected_type_err val +nullable_str = decoder( + \val -> + when val is + String(s) -> Ok(NotNull(s)) + Null -> Ok(Null) + _ -> to_unexpected_type_err(val), +) ## Decode a [Value] to a [Nullable (List U8)]. nullable_bytes : Str -> SqlDecode (Nullable (List U8)) UnexpectedTypeErr -nullable_bytes = decoder \val -> - when val is - Bytes b -> Ok (NotNull b) - Null -> Ok Null - _ -> to_unexpected_type_err val +nullable_bytes = decoder( + \val -> + when val is + Bytes(b) -> Ok(NotNull(b)) + Null -> Ok(Null) + _ -> to_unexpected_type_err(val), +) # internal use only nullable_int_decoder : (I64 -> Result a err) -> (Str -> SqlDecode (Nullable a) [FailedToDecodeInteger err]UnexpectedTypeErr) nullable_int_decoder = \cast -> - decoder \val -> - when val is - Integer i -> cast i |> Result.map NotNull |> Result.mapErr FailedToDecodeInteger - Null -> Ok Null - _ -> to_unexpected_type_err val + decoder( + \val -> + when val is + Integer(i) -> cast(i) |> Result.map(NotNull) |> Result.map_err(FailedToDecodeInteger) + Null -> Ok(Null) + _ -> to_unexpected_type_err(val), + ) # internal use only nullable_real_decoder : (F64 -> Result a err) -> (Str -> SqlDecode (Nullable a) [FailedToDecodeReal err]UnexpectedTypeErr) nullable_real_decoder = \cast -> - decoder \val -> - when val is - Real r -> cast r |> Result.map NotNull |> Result.mapErr FailedToDecodeReal - Null -> Ok Null - _ -> to_unexpected_type_err val + decoder( + \val -> + when val is + Real(r) -> cast(r) |> Result.map(NotNull) |> Result.map_err(FailedToDecodeReal) + Null -> Ok(Null) + _ -> to_unexpected_type_err(val), + ) ## Decode a [Value] to a [Nullable I64]. nullable_i64 : Str -> SqlDecode (Nullable I64) [FailedToDecodeInteger []]UnexpectedTypeErr -nullable_i64 = nullable_int_decoder Ok +nullable_i64 = nullable_int_decoder(Ok) ## Decode a [Value] to a [Nullable I32]. nullable_i32 : Str -> SqlDecode (Nullable I32) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_i32 = nullable_int_decoder Num.toI32Checked +nullable_i32 = nullable_int_decoder(Num.to_i32_checked) ## Decode a [Value] to a [Nullable I16]. nullable_i16 : Str -> SqlDecode (Nullable I16) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_i16 = nullable_int_decoder Num.toI16Checked +nullable_i16 = nullable_int_decoder(Num.to_i16_checked) ## Decode a [Value] to a [Nullable I8]. nullable_i8 : Str -> SqlDecode (Nullable I8) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_i8 = nullable_int_decoder Num.toI8Checked +nullable_i8 = nullable_int_decoder(Num.to_i8_checked) ## Decode a [Value] to a [Nullable U64]. nullable_u64 : Str -> SqlDecode (Nullable U64) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_u64 = nullable_int_decoder Num.toU64Checked +nullable_u64 = nullable_int_decoder(Num.to_u64_checked) ## Decode a [Value] to a [Nullable U32]. nullable_u32 : Str -> SqlDecode (Nullable U32) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_u32 = nullable_int_decoder Num.toU32Checked +nullable_u32 = nullable_int_decoder(Num.to_u32_checked) ## Decode a [Value] to a [Nullable U16]. nullable_u16 : Str -> SqlDecode (Nullable U16) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_u16 = nullable_int_decoder Num.toU16Checked +nullable_u16 = nullable_int_decoder(Num.to_u16_checked) ## Decode a [Value] to a [Nullable U8]. nullable_u8 : Str -> SqlDecode (Nullable U8) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_u8 = nullable_int_decoder Num.toU8Checked +nullable_u8 = nullable_int_decoder(Num.to_u8_checked) ## Decode a [Value] to a [Nullable F64]. nullable_f64 : Str -> SqlDecode (Nullable F64) [FailedToDecodeReal []]UnexpectedTypeErr -nullable_f64 = nullable_real_decoder Ok +nullable_f64 = nullable_real_decoder(Ok) ## Decode a [Value] to a [Nullable F32]. nullable_f32 : Str -> SqlDecode (Nullable F32) [FailedToDecodeReal []]UnexpectedTypeErr -nullable_f32 = nullable_real_decoder (\x -> Num.toF32 x |> Ok) +nullable_f32 = nullable_real_decoder(\x -> Num.to_f32(x) |> Ok) # TODO: Mising Num.toDec and Num.toDecChecked # nullable_dec = nullable_real_decoder Ok @@ -640,7 +665,7 @@ nullable_f32 = nullable_real_decoder (\x -> Num.toF32 x |> Ok) # internal use only internal_to_external_error : InternalSqlite.SqliteError -> [SqliteErr ErrCode Str] internal_to_external_error = \{ code, message } -> - SqliteErr (code_from_i64 code) message + SqliteErr(code_from_i64(code), message) # internal use only code_from_i64 : I64 -> ErrCode @@ -706,7 +731,7 @@ code_from_i64 = \code -> else if code == 101 then Done else - Unknown code + Unknown(code) ## Convert a [ErrCode] to a pretty string for display purposes. errcode_to_str : ErrCode -> Str @@ -742,4 +767,4 @@ errcode_to_str = \code -> Warning -> "Warning: Warnings from sqlite3_log()" Row -> "Row: sqlite3_step() has another row ready" Done -> "Done: sqlite3_step() has finished executing" - Unknown c -> "Unknown: error code $(Num.toStr c) not known" + Unknown(c) -> "Unknown: error code $(Num.to_str(c)) not known" diff --git a/platform/Stderr.roc b/platform/Stderr.roc index d5aedf35..90bbc4ca 100644 --- a/platform/Stderr.roc +++ b/platform/Stderr.roc @@ -36,14 +36,14 @@ IOErr : [ handle_err : InternalIOErr.IOErrFromHost -> [StderrErr IOErr] handle_err = \{ tag, msg } -> when tag is - NotFound -> StderrErr NotFound - PermissionDenied -> StderrErr PermissionDenied - BrokenPipe -> StderrErr BrokenPipe - AlreadyExists -> StderrErr AlreadyExists - Interrupted -> StderrErr Interrupted - Unsupported -> StderrErr Unsupported - OutOfMemory -> StderrErr OutOfMemory - Other | EndOfFile -> StderrErr (Other msg) + NotFound -> StderrErr(NotFound) + PermissionDenied -> StderrErr(PermissionDenied) + BrokenPipe -> StderrErr(BrokenPipe) + AlreadyExists -> StderrErr(AlreadyExists) + Interrupted -> StderrErr(Interrupted) + Unsupported -> StderrErr(Unsupported) + OutOfMemory -> StderrErr(OutOfMemory) + Other | EndOfFile -> StderrErr(Other(msg)) ## Write the given string to [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)), ## followed by a newline. @@ -51,8 +51,8 @@ handle_err = \{ tag, msg } -> ## > To write to `stderr` without the newline, see [Stderr.write!]. line! : Str => Result {} [StderrErr IOErr] line! = \str -> - Host.stderr_line! str - |> Result.mapErr handle_err + Host.stderr_line!(str) + |> Result.map_err(handle_err) ## Write the given string to [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)). ## @@ -62,5 +62,5 @@ line! = \str -> ## > To write to `stderr` with a newline at the end, see [Stderr.line!]. write! : Str => Result {} [StderrErr IOErr] write! = \str -> - Host.stderr_write! str - |> Result.mapErr handle_err + Host.stderr_write!(str) + |> Result.map_err(handle_err) diff --git a/platform/Stdin.roc b/platform/Stdin.roc index 96fe164c..f492c5cb 100644 --- a/platform/Stdin.roc +++ b/platform/Stdin.roc @@ -37,15 +37,15 @@ IOErr : [ handle_err : InternalIOErr.IOErrFromHost -> [EndOfFile, StdinErr IOErr] handle_err = \{ tag, msg } -> when tag is - NotFound -> StdinErr NotFound - PermissionDenied -> StdinErr PermissionDenied - BrokenPipe -> StdinErr BrokenPipe - AlreadyExists -> StdinErr AlreadyExists - Interrupted -> StdinErr Interrupted - Unsupported -> StdinErr Unsupported - OutOfMemory -> StdinErr OutOfMemory + NotFound -> StdinErr(NotFound) + PermissionDenied -> StdinErr(PermissionDenied) + BrokenPipe -> StdinErr(BrokenPipe) + AlreadyExists -> StdinErr(AlreadyExists) + Interrupted -> StdinErr(Interrupted) + Unsupported -> StdinErr(Unsupported) + OutOfMemory -> StdinErr(OutOfMemory) EndOfFile -> EndOfFile - Other -> StdinErr (Other msg) + Other -> StdinErr(Other(msg)) ## Read a line from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). ## @@ -55,8 +55,8 @@ handle_err = \{ tag, msg } -> ## the user knows it's necessary to enter something before the program will continue. line! : {} => Result Str [EndOfFile, StdinErr IOErr] line! = \{} -> - Host.stdin_line! {} - |> Result.mapErr handle_err + Host.stdin_line!({}) + |> Result.map_err(handle_err) ## Read bytes from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). ## ‼️ This function can read no more than 16,384 bytes at a time. Use [readToEnd!] if you need more. @@ -66,21 +66,23 @@ line! = \{} -> ## without buffering until Enter key is pressed. bytes! : {} => Result (List U8) [EndOfFile, StdinErr IOErr] bytes! = \{} -> - Host.stdin_bytes! {} - |> Result.mapErr handle_err + Host.stdin_bytes!({}) + |> Result.map_err(handle_err) ## Read all bytes from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)) until EOF in this source. read_to_end! : {} => Result (List U8) [StdinErr IOErr] read_to_end! = \{} -> - Host.stdin_read_to_end! {} - |> Result.mapErr \{ tag, msg } -> - when tag is - NotFound -> StdinErr NotFound - PermissionDenied -> StdinErr PermissionDenied - BrokenPipe -> StdinErr BrokenPipe - AlreadyExists -> StdinErr AlreadyExists - Interrupted -> StdinErr Interrupted - Unsupported -> StdinErr Unsupported - OutOfMemory -> StdinErr OutOfMemory - EndOfFile -> crash "unreachable, reading to EOF" - Other -> StdinErr (Other msg) + Host.stdin_read_to_end!({}) + |> Result.map_err( + \{ tag, msg } -> + when tag is + NotFound -> StdinErr(NotFound) + PermissionDenied -> StdinErr(PermissionDenied) + BrokenPipe -> StdinErr(BrokenPipe) + AlreadyExists -> StdinErr(AlreadyExists) + Interrupted -> StdinErr(Interrupted) + Unsupported -> StdinErr(Unsupported) + OutOfMemory -> StdinErr(OutOfMemory) + EndOfFile -> crash("unreachable, reading to EOF") + Other -> StdinErr(Other(msg)), + ) diff --git a/platform/Stdout.roc b/platform/Stdout.roc index 567ce3e2..4dcca59a 100644 --- a/platform/Stdout.roc +++ b/platform/Stdout.roc @@ -36,14 +36,14 @@ IOErr : [ handle_err : InternalIOErr.IOErrFromHost -> [StdoutErr IOErr] handle_err = \{ tag, msg } -> when tag is - NotFound -> StdoutErr NotFound - PermissionDenied -> StdoutErr PermissionDenied - BrokenPipe -> StdoutErr BrokenPipe - AlreadyExists -> StdoutErr AlreadyExists - Interrupted -> StdoutErr Interrupted - Unsupported -> StdoutErr Unsupported - OutOfMemory -> StdoutErr OutOfMemory - Other | EndOfFile -> StdoutErr (Other msg) + NotFound -> StdoutErr(NotFound) + PermissionDenied -> StdoutErr(PermissionDenied) + BrokenPipe -> StdoutErr(BrokenPipe) + AlreadyExists -> StdoutErr(AlreadyExists) + Interrupted -> StdoutErr(Interrupted) + Unsupported -> StdoutErr(Unsupported) + OutOfMemory -> StdoutErr(OutOfMemory) + Other | EndOfFile -> StdoutErr(Other(msg)) ## Write the given string to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)), ## followed by a newline. @@ -52,8 +52,8 @@ handle_err = \{ tag, msg } -> ## line! : Str => Result {} [StdoutErr IOErr] line! = \str -> - Host.stdout_line! str - |> Result.mapErr handle_err + Host.stdout_line!(str) + |> Result.map_err(handle_err) ## Write the given string to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)). ## @@ -63,5 +63,5 @@ line! = \str -> ## > To write to `stdout` with a newline at the end, see [Stdout.line!]. write! : Str => Result {} [StdoutErr IOErr] write! = \str -> - Host.stdout_write! str - |> Result.mapErr handle_err + Host.stdout_write!(str) + |> Result.map_err(handle_err) diff --git a/platform/Tcp.roc b/platform/Tcp.roc index 797f3d86..01b23fa4 100644 --- a/platform/Tcp.roc +++ b/platform/Tcp.roc @@ -42,7 +42,7 @@ parse_connect_err = \err -> "ErrorKind::Interrupted" -> Interrupted "ErrorKind::TimedOut" -> TimedOut "ErrorKind::Unsupported" -> Unsupported - other -> Unrecognized other + other -> Unrecognized(other) ## Represents errors that can occur when performing an effect with a [Stream]. StreamErr : [ @@ -66,13 +66,13 @@ parse_stream_err = \err -> "ErrorKind::Interrupted" -> Interrupted "ErrorKind::OutOfMemory" -> OutOfMemory "ErrorKind::BrokenPipe" -> BrokenPipe - other -> Unrecognized other + other -> Unrecognized(other) ## Opens a TCP connection to a remote host. ## ## ``` ## # Connect to localhost:8080 -## stream = Tcp.connect! "localhost" 8080 +## stream = Tcp.connect!("localhost", 8080)? ## ``` ## ## The connection is automatically closed when the last reference to the stream is dropped. @@ -85,46 +85,47 @@ parse_stream_err = \err -> ## connect! : Str, U16 => Result Stream ConnectErr connect! = \host, port -> - Host.tcp_connect! host port - |> Result.map @Stream - |> Result.mapErr parse_connect_err + Host.tcp_connect!(host, port) + |> Result.map(@Stream) + |> Result.map_err(parse_connect_err) ## Read up to a number of bytes from the TCP stream. ## ## ``` -## # Read up to 64 bytes from the stream and convert to a Str -## received = File.read_up_to! stream 64 -## Str.fromUtf8 received +## # Read up to 64 bytes from the stream +## received_bytes = File.read_up_to!(stream, 64)? ## ``` ## ## > To read an exact number of bytes or fail, you can use [Tcp.read_exactly!] instead. read_up_to! : Stream, U64 => Result (List U8) [TcpReadErr StreamErr] -read_up_to! = \@Stream stream, bytes_to_read -> - Host.tcp_read_up_to! stream bytes_to_read - |> Result.mapErr \err -> TcpReadErr (parse_stream_err err) +read_up_to! = \@Stream(stream), bytes_to_read -> + Host.tcp_read_up_to!(stream, bytes_to_read) + |> Result.map_err(\err -> TcpReadErr(parse_stream_err(err))) ## Read an exact number of bytes or fail. ## ## ``` -## bytes = File.read_exactly!? stream 64 +## bytes = File.read_exactly!(stream, 64)? ## ``` ## ## `TcpUnexpectedEOF` is returned if the stream ends before the specfied number of bytes is reached. ## read_exactly! : Stream, U64 => Result (List U8) [TcpReadErr StreamErr, TcpUnexpectedEOF] -read_exactly! = \@Stream stream, bytes_to_read -> - Host.tcp_read_exactly! stream bytes_to_read - |> Result.mapErr \err -> - if err == unexpected_eof_error_message then - TcpUnexpectedEOF - else - TcpReadErr (parse_stream_err err) +read_exactly! = \@Stream(stream), bytes_to_read -> + Host.tcp_read_exactly!(stream, bytes_to_read) + |> Result.map_err( + \err -> + if err == unexpected_eof_error_message then + TcpUnexpectedEOF + else + TcpReadErr(parse_stream_err(err)), + ) ## Read until a delimiter or EOF is reached. ## ## ``` ## # Read until null terminator -## File.read_until! stream 0 +## bytes = File.read_until!(stream, 0)? ## ``` ## ## If found, the delimiter is included as the last byte. @@ -132,58 +133,58 @@ read_exactly! = \@Stream stream, bytes_to_read -> ## > To read until a newline is found, you can use [Tcp.read_line!] which ## conveniently decodes to a [Str]. read_until! : Stream, U8 => Result (List U8) [TcpReadErr StreamErr] -read_until! = \@Stream stream, byte -> - Host.tcp_read_until! stream byte - |> Result.mapErr \err -> TcpReadErr (parse_stream_err err) +read_until! = \@Stream(stream), byte -> + Host.tcp_read_until!(stream, byte) + |> Result.map_err(\err -> TcpReadErr(parse_stream_err(err))) ## Read until a newline or EOF is reached. ## ## ``` ## # Read a line and then print it to `stdout` -## lineStr = File.read_line! stream -## Stdout.line lineStr +## line_str = File.read_line!(stream)? +## Stdout.line(lineStr)? ## ``` ## ## If found, the newline is included as the last character in the [Str]. ## read_line! : Stream => Result Str [TcpReadErr StreamErr, TcpReadBadUtf8 _] read_line! = \stream -> - bytes = read_until!? stream '\n' + bytes = read_until!(stream, '\n')? - Str.fromUtf8 bytes - |> Result.mapErr TcpReadBadUtf8 + Str.from_utf8(bytes) + |> Result.map_err(TcpReadBadUtf8) ## Writes bytes to a TCP stream. ## ## ``` ## # Writes the bytes 1, 2, 3 -## Tcp.write!? stream [1, 2, 3] +## Tcp.write!(stream, [1, 2, 3])? ## ``` ## ## > To write a [Str], you can use [Tcp.write_utf8!] instead. write! : Stream, List U8 => Result {} [TcpWriteErr StreamErr] -write! = \@Stream stream, bytes -> - Host.tcp_write! stream bytes - |> Result.mapErr \err -> TcpWriteErr (parse_stream_err err) +write! = \@Stream(stream), bytes -> + Host.tcp_write!(stream, bytes) + |> Result.map_err(\err -> TcpWriteErr(parse_stream_err(err))) ## Writes a [Str] to a TCP stream, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## ## ``` ## # Write "Hi from Roc!" encoded as UTF-8 -## Tcp.write_utf8! stream "Hi from Roc!" +## Tcp.write_utf8!(stream, "Hi from Roc!")? ## ``` ## ## > To write unformatted bytes, you can use [Tcp.write!] instead. write_utf8! : Stream, Str => Result {} [TcpWriteErr StreamErr] write_utf8! = \stream, str -> - write! stream (Str.toUtf8 str) + write!(stream, Str.to_utf8(str)) ## Convert a [ConnectErr] to a [Str] you can print. ## ## ``` ## when err is -## TcpPerfomErr (TcpConnectErr connectErr) -> -## Stderr.line (Tcp.connect_err_to_str connectErr) +## TcpPerfomErr(TcpConnectErr(connectErr)) -> +## Stderr.line!(Tcp.connect_err_to_str(connectErr)) ## ``` ## connect_err_to_str : ConnectErr -> Str @@ -196,19 +197,19 @@ connect_err_to_str = \err -> Interrupted -> "Interrupted" TimedOut -> "TimedOut" Unsupported -> "Unsupported" - Unrecognized message -> "Unrecognized Error: $(message)" + Unrecognized(message) -> "Unrecognized Error: $(message)" ## Convert a [StreamErr] to a [Str] you can print. ## ## ``` ## when err is -## TcpPerformErr (TcpReadErr err) -> -## errStr = Tcp.stream_err_to_str err -## Stderr.line "Error while reading: $(errStr)" +## TcpPerformErr(TcpReadErr(err)) -> +## errStr = Tcp.stream_err_to_str(err) +## Stderr.line!("Error while reading: $(errStr)") ## -## TcpPerformErr (TcpWriteErr err) -> -## errStr = Tcp.stream_err_to_str err -## Stderr.line "Error while writing: $(errStr)" +## TcpPerformErr(TcpWriteErr(err)) -> +## errStr = Tcp.stream_err_to_str(err) +## Stderr.line!("Error while writing: $(errStr)") ## ``` ## stream_err_to_str : StreamErr -> Str @@ -221,4 +222,4 @@ stream_err_to_str = \err -> Interrupted -> "Interrupted" OutOfMemory -> "OutOfMemory" BrokenPipe -> "BrokenPipe" - Unrecognized message -> "Unrecognized Error: $(message)" + Unrecognized(message) -> "Unrecognized Error: $(message)" diff --git a/platform/Tty.roc b/platform/Tty.roc index 380e1a31..49b570f2 100644 --- a/platform/Tty.roc +++ b/platform/Tty.roc @@ -21,7 +21,7 @@ import Host ## enable_raw_mode! : {} => {} enable_raw_mode! = \{} -> - Host.tty_mode_raw! {} + Host.tty_mode_raw!({}) ## Revert terminal to default behaviour ## @@ -29,4 +29,4 @@ enable_raw_mode! = \{} -> ## disable_raw_mode! : {} => {} disable_raw_mode! = \{} -> - Host.tty_mode_canonical! {} + Host.tty_mode_canonical!({}) diff --git a/platform/Url.roc b/platform/Url.roc index 6f00f254..b96cd41b 100644 --- a/platform/Url.roc +++ b/platform/Url.roc @@ -12,6 +12,7 @@ module [ with_query, with_fragment, query_params, + path, ] ## A [Uniform Resource Locator](https://en.wikipedia.org/wiki/URL). @@ -25,54 +26,54 @@ Url := Str implements [Inspect] ## ## The following example reserves 50 bytes, then builds the url `https://example.com/stuff?caf%C3%A9=du%20Monde&email=hi%40example.com`; ## ``` -## Url.from_str "https://example.com" -## |> Url.reserve 50 -## |> Url.append "stuff" -## |> Url.append_param "café" "du Monde" -## |> Url.append_param "email" "hi@example.com" +## Url.from_str("https://example.com") +## |> Url.reserve(50) +## |> Url.append("stuff") +## |> Url.append_param("café", "du Monde") +## |> Url.append_param("email", "hi@example.com") ## ``` -## The [Str.countUtf8Bytes](https://www.roc-lang.org/builtins/Str#countUtf8Bytes) function can be helpful in finding out how many bytes to reserve. +## The [Str.count_utf8_bytes](https://www.roc-lang.org/builtins/Str#countUtf8Bytes) function can be helpful in finding out how many bytes to reserve. ## -## There is no `Url.withCapacity` because it's better to reserve extra capacity +## There is no `Url.with_capacity` because it's better to reserve extra capacity ## on a [Str] first, and then pass that string to [Url.from_str]. This function will make use ## of the extra capacity. reserve : Url, U64 -> Url -reserve = \@Url str, cap -> - @Url (Str.reserve str (Num.intCast cap)) +reserve = \@Url(str), cap -> + @Url(Str.reserve(str, Num.int_cast(cap))) ## Create a [Url] without validating or [percent-encoding](https://en.wikipedia.org/wiki/Percent-encoding) ## anything. ## ## ``` -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## ``` ## ## URLs can be absolute, like `https://example.com`, or they can be relative, like `/blah`. ## ## ``` -## Url.from_str "/this/is#relative" +## Url.from_str("/this/is#relative") ## ``` ## ## Since nothing is validated, this can return invalid URLs. ## ## ``` -## Url.from_str "https://this is not a valid URL, not at all!" +## Url.from_str("https://this is not a valid URL, not at all!") ## ``` ## ## Naturally, passing invalid URLs to functions that need valid ones will tend to result in errors. ## from_str : Str -> Url -from_str = \str -> @Url str +from_str = \str -> @Url(str) ## Return a [Str] representation of this URL. ## ``` ## # Gives "https://example.com/two%20words" -## Url.from_str "https://example.com" -## |> Url.append "two words" +## Url.from_str("https://example.com") +## |> Url.append("two words") ## |> Url.to_str ## ``` to_str : Url -> Str -to_str = \@Url str -> str +to_str = \@Url(str) -> str ## [Percent-encodes](https://en.wikipedia.org/wiki/Percent-encoding) a ## [path component](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax) @@ -83,105 +84,105 @@ to_str = \@Url str -> str ## ## ``` ## # Gives https://example.com/some%20stuff -## Url.from_str "https://example.com" -## |> Url.append "some stuff" +## Url.from_str("https://example.com") +## |> Url.append("some stuff") ## ## # Gives https://example.com/stuff?search=blah#fragment -## Url.from_str "https://example.com?search=blah#fragment" -## |> Url.append "stuff" +## Url.from_str("https://example.com?search=blah#fragment") +## |> Url.append("stuff") ## ## # Gives https://example.com/things/stuff/more/etc/" ## Url.from_str "https://example.com/things/" -## |> Url.append "/stuff/" -## |> Url.append "/more/etc/" +## |> Url.append("/stuff/") +## |> Url.append("/more/etc/") ## ## # Gives https://example.com/things -## Url.from_str "https://example.com/things" -## |> Url.append "" +## Url.from_str("https://example.com/things") +## |> Url.append("") ## ``` append : Url, Str -> Url -append = \@Url url_str, suffix_unencoded -> - suffix = percent_encode suffix_unencoded +append = \@Url(url_str), suffix_unencoded -> + suffix = percent_encode(suffix_unencoded) - when Str.splitFirst url_str "?" is - Ok { before, after } -> + when Str.split_first(url_str, "?") is + Ok({ before, after }) -> bytes = - Str.countUtf8Bytes before + Str.count_utf8_bytes(before) + 1 # for "/" - + Str.countUtf8Bytes suffix + + Str.count_utf8_bytes(suffix) + 1 # for "?" - + Str.countUtf8Bytes after + + Str.count_utf8_bytes(after) before - |> Str.reserve bytes - |> append_help suffix - |> Str.concat "?" - |> Str.concat after + |> Str.reserve(bytes) + |> append_help(suffix) + |> Str.concat("?") + |> Str.concat(after) |> @Url - Err NotFound -> + Err(NotFound) -> # There wasn't a query, but there might still be a fragment - when Str.splitFirst url_str "#" is - Ok { before, after } -> + when Str.split_first(url_str, "#") is + Ok({ before, after }) -> bytes = - Str.countUtf8Bytes before + Str.count_utf8_bytes(before) + 1 # for "/" - + Str.countUtf8Bytes suffix + + Str.count_utf8_bytes(suffix) + 1 # for "#" - + Str.countUtf8Bytes after + + Str.count_utf8_bytes(after) before - |> Str.reserve bytes - |> append_help suffix - |> Str.concat "#" - |> Str.concat after + |> Str.reserve(bytes) + |> append_help(suffix) + |> Str.concat("#") + |> Str.concat(after) |> @Url - Err NotFound -> + Err(NotFound) -> # No query and no fragment, so just append it - @Url (append_help url_str suffix) + @Url(append_help(url_str, suffix)) ## Internal helper append_help : Str, Str -> Str append_help = \prefix, suffix -> - if Str.endsWith prefix "/" then - if Str.startsWith suffix "/" then + if Str.ends_with(prefix, "/") then + if Str.starts_with(suffix, "/") then # Avoid a double-slash by appending only the part of the suffix after the "/" - when Str.splitFirst suffix "/" is - Ok { after } -> + when Str.split_first(suffix, "/") is + Ok({ after }) -> # TODO `expect before == ""` - Str.concat prefix after + Str.concat(prefix, after) - Err NotFound -> + Err(NotFound) -> # This should never happen, because we already verified # that the suffix startsWith "/" # TODO `expect Bool.false` here with a comment - Str.concat prefix suffix + Str.concat(prefix, suffix) else # prefix ends with "/" but suffix doesn't start with one, so just append. - Str.concat prefix suffix - else if Str.startsWith suffix "/" then + Str.concat(prefix, suffix) + else if Str.starts_with(suffix, "/") then # Suffix starts with "/" but prefix doesn't end with one, so just append them. - Str.concat prefix suffix - else if Str.isEmpty prefix then + Str.concat(prefix, suffix) + else if Str.is_empty(prefix) then # Prefix is empty; return suffix. suffix - else if Str.isEmpty suffix then + else if Str.is_empty(suffix) then # Suffix is empty; return prefix. prefix else # Neither is empty, but neither has a "/", so add one in between. prefix - |> Str.concat "/" - |> Str.concat suffix + |> Str.concat("/") + |> Str.concat(suffix) ## Internal helper. This is intentionally unexposed so that you don't accidentally ## double-encode things. If you really want to percent-encode an arbitrary string, ## you can always do: ## ## ``` -## Url.from_str "" -## |> Url.append myStrToEncode +## Url.from_str("") +## |> Url.append(myStrToEncode) ## |> Url.to_str ## ``` ## @@ -193,38 +194,42 @@ percent_encode : Str -> Str percent_encode = \input -> # Optimistically assume we won't need any percent encoding, and can have # the same capacity as the input string. If we're wrong, it will get doubled. - initial_output = List.withCapacity (Str.countUtf8Bytes input |> Num.intCast) + initial_output = List.with_capacity((Str.count_utf8_bytes(input) |> Num.int_cast)) answer = - List.walk (Str.toUtf8 input) initial_output \output, byte -> - # Spec for percent-encoding: https://www.ietf.org/rfc/rfc3986.txt - if - (byte >= 97 && byte <= 122) # lowercase ASCII - || (byte >= 65 && byte <= 90) # uppercase ASCII - || (byte >= 48 && byte <= 57) # digit - then - # This is the most common case: an unreserved character, - # which needs no encoding in a path - List.append output byte - else - when byte is - 46 # '.' - | 95 # '_' - | 126 # '~' - | 150 -> # '-' - # These special characters can all be unescaped in paths - List.append output byte - - _ -> - # This needs encoding in a path - suffix = - Str.toUtf8 percent_encoded - |> List.sublist { len: 3, start: 3 * Num.intCast byte } - - List.concat output suffix - - Str.fromUtf8 answer - |> Result.withDefault "" # This should never fail + List.walk( + Str.to_utf8(input), + initial_output, + \output, byte -> + # Spec for percent-encoding: https://www.ietf.org/rfc/rfc3986.txt + if + (byte >= 97 && byte <= 122) # lowercase ASCII + || (byte >= 65 && byte <= 90) # uppercase ASCII + || (byte >= 48 && byte <= 57) # digit + then + # This is the most common case: an unreserved character, + # which needs no encoding in a path + List.append(output, byte) + else + when byte is + 46 # '.' + | 95 # '_' + | 126 # '~' + | 150 -> # '-' + # These special characters can all be unescaped in paths + List.append(output, byte) + + _ -> + # This needs encoding in a path + suffix = + Str.to_utf8(percent_encoded) + |> List.sublist({ len: 3, start: 3 * Num.int_cast(byte) }) + + List.concat(output, suffix), + ) + + Str.from_utf8(answer) + |> Result.with_default("") # This should never fail ## Adds a [Str] query parameter to the end of the [Url]. ## @@ -232,49 +237,49 @@ percent_encode = \input -> ## ## ``` ## # Gives https://example.com?email=someone%40example.com -## Url.from_str "https://example.com" -## |> Url.append_param "email" "someone@example.com" +## Url.from_str("https://example.com") +## |> Url.append_param("email", "someone@example.com") ## ``` ## ## This can be called multiple times on the same URL. ## ## ``` ## # Gives https://example.com?caf%C3%A9=du%20Monde&email=hi%40example.com -## Url.from_str "https://example.com" -## |> Url.append_param "café" "du Monde" -## |> Url.append_param "email" "hi@example.com" +## Url.from_str("https://example.com") +## |> Url.append_param("café", "du Monde") +## |> Url.append_param("email", "hi@example.com") ## ``` ## append_param : Url, Str, Str -> Url -append_param = \@Url url_str, key, value -> +append_param = \@Url(url_str), key, value -> { without_fragment, after_query } = - when Str.splitLast url_str "#" is - Ok { before, after } -> + when Str.split_last(url_str, "#") is + Ok({ before, after }) -> # The fragment is almost certainly going to be a small string, # so this interpolation should happen on the stack. { without_fragment: before, after_query: "#$(after)" } - Err NotFound -> + Err(NotFound) -> { without_fragment: url_str, after_query: "" } - encoded_key = percent_encode key - encoded_value = percent_encode value + encoded_key = percent_encode(key) + encoded_value = percent_encode(value) bytes = - Str.countUtf8Bytes without_fragment + Str.count_utf8_bytes(without_fragment) + 1 # for "?" or "&" - + Str.countUtf8Bytes encoded_key + + Str.count_utf8_bytes(encoded_key) + 1 # for "=" - + Str.countUtf8Bytes encoded_value - + Str.countUtf8Bytes after_query + + Str.count_utf8_bytes(encoded_value) + + Str.count_utf8_bytes(after_query) without_fragment - |> Str.reserve bytes - |> Str.concat (if has_query (@Url without_fragment) then "&" else "?") - |> Str.concat encoded_key - |> Str.concat "=" - |> Str.concat encoded_value - |> Str.concat after_query + |> Str.reserve(bytes) + |> Str.concat((if has_query(@Url(without_fragment)) then "&" else "?")) + |> Str.concat(encoded_key) + |> Str.concat("=") + |> Str.concat(encoded_value) + |> Str.concat(after_query) |> @Url ## Replaces the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)—the part @@ -284,44 +289,44 @@ append_param = \@Url url_str, key, value -> ## ## ``` ## # Gives https://example.com?newQuery=thisRightHere#stuff -## Url.from_str "https://example.com?key1=val1&key2=val2#stuff" -## |> Url.with_query "newQuery=thisRightHere" +## Url.from_str("https://example.com?key1=val1&key2=val2#stuff") +## |> Url.with_query("newQuery=thisRightHere") ## ## # Gives https://example.com#stuff -## Url.from_str "https://example.com?key1=val1&key2=val2#stuff" -## |> Url.with_query "" +## Url.from_str("https://example.com?key1=val1&key2=val2#stuff") +## |> Url.with_query("") ## ``` with_query : Url, Str -> Url -with_query = \@Url url_str, query_str -> +with_query = \@Url(url_str), query_str -> { without_fragment, after_query } = - when Str.splitLast url_str "#" is - Ok { before, after } -> + when Str.split_last(url_str, "#") is + Ok({ before, after }) -> # The fragment is almost certainly going to be a small string, # so this interpolation should happen on the stack. { without_fragment: before, after_query: "#$(after)" } - Err NotFound -> + Err(NotFound) -> { without_fragment: url_str, after_query: "" } before_query = - when Str.splitLast without_fragment "?" is - Ok { before } -> before - Err NotFound -> without_fragment + when Str.split_last(without_fragment, "?") is + Ok({ before }) -> before + Err(NotFound) -> without_fragment - if Str.isEmpty query_str then - @Url (Str.concat before_query after_query) + if Str.is_empty(query_str) then + @Url(Str.concat(before_query, after_query)) else bytes = - Str.countUtf8Bytes before_query + Str.count_utf8_bytes(before_query) + 1 # for "?" - + Str.countUtf8Bytes query_str - + Str.countUtf8Bytes after_query + + Str.count_utf8_bytes(query_str) + + Str.count_utf8_bytes(after_query) before_query - |> Str.reserve bytes - |> Str.concat "?" - |> Str.concat query_str - |> Str.concat after_query + |> Str.reserve(bytes) + |> Str.concat("?") + |> Str.concat(query_str) + |> Str.concat(after_query) |> @Url ## Returns the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)—the part after @@ -331,40 +336,40 @@ with_query = \@Url url_str, query_str -> ## ## ``` ## # Gives "key1=val1&key2=val2&key3=val3" -## Url.from_str "https://example.com?key1=val1&key2=val2&key3=val3#stuff" +## Url.from_str("https://example.com?key1=val1&key2=val2&key3=val3#stuff") ## |> Url.query ## ## # Gives "" -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## |> Url.query ## ``` ## query : Url -> Str -query = \@Url url_str -> +query = \@Url(url_str) -> without_fragment = - when Str.splitLast url_str "#" is - Ok { before } -> before - Err NotFound -> url_str + when Str.split_last(url_str, "#") is + Ok({ before }) -> before + Err(NotFound) -> url_str - when Str.splitLast without_fragment "?" is - Ok { after } -> after - Err NotFound -> "" + when Str.split_last(without_fragment, "?") is + Ok({ after }) -> after + Err(NotFound) -> "" ## Returns [Bool.true] if the URL has a `?` in it. ## ## ``` ## # Gives Bool.true -## Url.from_str "https://example.com?key=value#stuff" +## Url.from_str("https://example.com?key=value#stuff") ## |> Url.has_query ## ## # Gives Bool.false -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## |> Url.has_query ## ``` ## has_query : Url -> Bool -has_query = \@Url url_str -> - Str.contains url_str "?" +has_query = \@Url(url_str) -> + Str.contains(url_str, "?") ## Returns the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax)—the part after ## the `#`, if it has one. @@ -373,19 +378,19 @@ has_query = \@Url url_str -> ## ## ``` ## # Gives "stuff" -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## |> Url.fragment ## ## # Gives "" -## Url.from_str "https://example.com" +## Url.from_str("https://example.com") ## |> Url.fragment ## ``` ## fragment : Url -> Str -fragment = \@Url url_str -> - when Str.splitLast url_str "#" is - Ok { after } -> after - Err NotFound -> "" +fragment = \@Url(url_str) -> + when Str.split_last(url_str, "#") is + Ok({ after }) -> after + Err(NotFound) -> "" ## Replaces the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax). ## @@ -393,52 +398,52 @@ fragment = \@Url url_str -> ## ## ``` ## # Gives https://example.com#things -## Url.from_str "https://example.com#stuff" -## |> Url.with_fragment "things" +## Url.from_str("https://example.com#stuff") +## |> Url.with_fragment("things") ## ## # Gives https://example.com#things -## Url.from_str "https://example.com" -## |> Url.with_fragment "things" +## Url.from_str("https://example.com") +## |> Url.with_fragment("things") ## ## # Gives https://example.com -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## |> Url.with_fragment "" ## ``` ## with_fragment : Url, Str -> Url -with_fragment = \@Url url_str, fragment_str -> - when Str.splitLast url_str "#" is - Ok { before } -> - if Str.isEmpty fragment_str then +with_fragment = \@Url(url_str), fragment_str -> + when Str.split_last(url_str, "#") is + Ok({ before }) -> + if Str.is_empty(fragment_str) then # If the given fragment is empty, remove the URL's fragment - @Url before + @Url(before) else # Replace the URL's old fragment with this one, discarding `after` - @Url "$(before)#$(fragment_str)" + @Url("$(before)#$(fragment_str)") - Err NotFound -> - if Str.isEmpty fragment_str then + Err(NotFound) -> + if Str.is_empty(fragment_str) then # If the given fragment is empty, leave the URL as having no fragment - @Url url_str + @Url(url_str) else # The URL didn't have a fragment, so give it this one - @Url "$(url_str)#$(fragment_str)" + @Url("$(url_str)#$(fragment_str)") ## Returns [Bool.true] if the URL has a `#` in it. ## ## ``` ## # Gives Bool.true -## Url.from_str "https://example.com?key=value#stuff" +## Url.from_str("https://example.com?key=value#stuff") ## |> Url.has_fragment ## ## # Gives Bool.false -## Url.from_str "https://example.com?key=value" +## Url.from_str("https://example.com?key=value") ## |> Url.has_fragment ## ``` ## has_fragment : Url -> Bool -has_fragment = \@Url url_str -> - Str.contains url_str "#" +has_fragment = \@Url(url_str) -> + Str.contains(url_str, "#") # Adapted from the percent-encoding crate, © The rust-url developers, Apache2-licensed # @@ -448,9 +453,50 @@ percent_encoded = "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13% query_params : Url -> Dict Str Str query_params = \url -> - query url - |> Str.splitOn "&" - |> List.walk (Dict.empty {}) \dict, pair -> - when Str.splitFirst pair "=" is - Ok { before, after } -> Dict.insert dict before after - Err NotFound -> Dict.insert dict pair "" + query(url) + |> Str.split_on("&") + |> List.walk( + Dict.empty({}), + \dict, pair -> + when Str.split_first(pair, "=") is + Ok({ before, after }) -> Dict.insert(dict, before, after) + Err(NotFound) -> Dict.insert(dict, pair, ""), + ) + +## Returns the URL's [path](https://en.wikipedia.org/wiki/URL#Syntax)—the part after +## the scheme and authority (e.g. `https://`) but before any `?` or `#` it might have. +## +## Returns `""` if the URL has no path. +## +## ``` +## # Gives "example.com/" +## Url.fromStr("https://example.com/?key1=val1&key2=val2&key3=val3#stuff") +## |> Url.path +## ``` +## +## ``` +## # Gives "/foo/" +## Url.fromStr("/foo/?key1=val1&key2=val2&key3=val3#stuff") +## |> Url.path +## ``` +path : Url -> Str +path = \@Url urlStr -> + withoutAuthority = + when Str.split_first urlStr ":" is + Ok { after } -> + when Str.split_first after "//" is + # Only drop the `//` if it's right after the `://` like in `https://` + # (so, `before` is empty) - otherwise, the `//` is part of the path! + Ok { before, after: afterSlashes } if Str.is_empty before -> afterSlashes + _ -> after + + # There's no `//` and also no `:` so this must be a path-only URL, e.g. "/foo?bar=baz#blah" + Err NotFound -> urlStr + + # Drop the query and/or fragment + when Str.split_last withoutAuthority "?" is + Ok { before } -> before + Err NotFound -> + when Str.split_last withoutAuthority "#" is + Ok { before } -> before + Err NotFound -> withoutAuthority diff --git a/platform/Utc.roc b/platform/Utc.roc index 9415b405..7703d193 100644 --- a/platform/Utc.roc +++ b/platform/Utc.roc @@ -19,24 +19,24 @@ Utc := I128 implements [Inspect] ## Duration since UNIX EPOCH now! : {} => Utc now! = \{} -> - @Utc (Num.toI128 (Host.posix_time! {})) + @Utc(Num.to_i128(Host.posix_time!({}))) # Constant number of nanoseconds in a millisecond nanos_per_milli = 1_000_000 ## Convert Utc timestamp to milliseconds to_millis_since_epoch : Utc -> I128 -to_millis_since_epoch = \@Utc nanos -> +to_millis_since_epoch = \@Utc(nanos) -> nanos // nanos_per_milli ## Convert milliseconds to Utc timestamp from_millis_since_epoch : I128 -> Utc from_millis_since_epoch = \millis -> - @Utc (millis * nanos_per_milli) + @Utc((millis * nanos_per_milli)) ## Convert Utc timestamp to nanoseconds to_nanos_since_epoch : Utc -> I128 -to_nanos_since_epoch = \@Utc nanos -> +to_nanos_since_epoch = \@Utc(nanos) -> nanos ## Convert nanoseconds to Utc timestamp @@ -46,34 +46,34 @@ from_nanos_since_epoch = @Utc ## Calculate milliseconds between two Utc timestamps delta_as_millis : Utc, Utc -> U128 delta_as_millis = \utc_a, utc_b -> - (delta_as_nanos utc_a utc_b) // nanos_per_milli + (delta_as_nanos(utc_a, utc_b)) // nanos_per_milli ## Calculate nanoseconds between two Utc timestamps delta_as_nanos : Utc, Utc -> U128 -delta_as_nanos = \@Utc nanos_a, @Utc nanos_b -> +delta_as_nanos = \@Utc(nanos_a), @Utc(nanos_b) -> # bitwiseXor for best performance - nanos_a_shifted = Num.bitwiseXor (Num.toU128 nanos_a) (Num.shiftLeftBy 1 127) - nanos_b_shifted = Num.bitwiseXor (Num.toU128 nanos_b) (Num.shiftLeftBy 1 127) + nanos_a_shifted = Num.bitwise_xor(Num.to_u128(nanos_a), Num.shift_left_by(1, 127)) + nanos_b_shifted = Num.bitwise_xor(Num.to_u128(nanos_b), Num.shift_left_by(1, 127)) - Num.absDiff nanos_a_shifted nanos_b_shifted + Num.abs_diff(nanos_a_shifted, nanos_b_shifted) ## Convert Utc timestamp to ISO 8601 string ## Example: 2023-11-14T23:39:39Z to_iso_8601 : Utc -> Str -to_iso_8601 = \@Utc nanos -> +to_iso_8601 = \@Utc(nanos) -> nanos - |> Num.divTrunc nanos_per_milli + |> Num.div_trunc(nanos_per_milli) |> InternalDateTime.epoch_millis_to_datetime |> InternalDateTime.to_iso_8601 # TESTS -expect delta_as_nanos (from_nanos_since_epoch 0) (from_nanos_since_epoch 0) == 0 -expect delta_as_nanos (from_nanos_since_epoch 1) (from_nanos_since_epoch 2) == 1 -expect delta_as_nanos (from_nanos_since_epoch -1) (from_nanos_since_epoch 1) == 2 -expect delta_as_nanos (from_nanos_since_epoch Num.minI128) (from_nanos_since_epoch Num.maxI128) == Num.maxU128 +expect delta_as_nanos(from_nanos_since_epoch(0), from_nanos_since_epoch(0)) == 0 +expect delta_as_nanos(from_nanos_since_epoch(1), from_nanos_since_epoch(2)) == 1 +expect delta_as_nanos(from_nanos_since_epoch(-1), from_nanos_since_epoch(1)) == 2 +expect delta_as_nanos(from_nanos_since_epoch(Num.min_i128), from_nanos_since_epoch(Num.max_i128)) == Num.max_u128 -expect delta_as_millis (from_millis_since_epoch 0) (from_millis_since_epoch 0) == 0 -expect delta_as_millis (from_nanos_since_epoch 1) (from_nanos_since_epoch 2) == 0 -expect delta_as_millis (from_millis_since_epoch 1) (from_millis_since_epoch 2) == 1 -expect delta_as_millis (from_millis_since_epoch -1) (from_millis_since_epoch 1) == 2 -expect delta_as_millis (from_nanos_since_epoch Num.minI128) (from_nanos_since_epoch Num.maxI128) == Num.maxU128 // nanos_per_milli +expect delta_as_millis(from_millis_since_epoch(0), from_millis_since_epoch(0)) == 0 +expect delta_as_millis(from_nanos_since_epoch(1), from_nanos_since_epoch(2)) == 0 +expect delta_as_millis(from_millis_since_epoch(1), from_millis_since_epoch(2)) == 1 +expect delta_as_millis(from_millis_since_epoch(-1), from_millis_since_epoch(1)) == 2 +expect delta_as_millis(from_nanos_since_epoch(Num.min_i128), from_nanos_since_epoch(Num.max_i128)) == Num.max_u128 // nanos_per_milli diff --git a/platform/glue-internal-arg.roc b/platform/glue-internal-arg.roc index 010cad8f..b4ff8a02 100644 --- a/platform/glue-internal-arg.roc +++ b/platform/glue-internal-arg.roc @@ -12,9 +12,9 @@ platform "glue-types" exposes [] packages {} imports [] - provides [mainForHost] + provides [main_for_host] import InternalArg -mainForHost : InternalArg.ArgToAndFromHost -mainForHost = main +main_for_host : InternalArg.ArgToAndFromHost +main_for_host = main diff --git a/platform/glue-internal-cmd.roc b/platform/glue-internal-cmd.roc index ad9d8696..4a113622 100644 --- a/platform/glue-internal-cmd.roc +++ b/platform/glue-internal-cmd.roc @@ -12,9 +12,9 @@ platform "glue-types" exposes [] packages {} imports [] - provides [mainForHost] + provides [main_for_host] import InternalCmd -mainForHost : InternalCmd.OutputFromHost -mainForHost = main +main_for_host : InternalCmd.OutputFromHost +main_for_host = main diff --git a/platform/glue-internal-http.roc b/platform/glue-internal-http.roc index f0285ee4..6505bbef 100644 --- a/platform/glue-internal-http.roc +++ b/platform/glue-internal-http.roc @@ -12,12 +12,12 @@ platform "glue-types" exposes [] packages {} imports [] - provides [mainForHost] + provides [main_for_host] import InternalHttp -mainForHost : { +main_for_host : { a : InternalHttp.RequestToAndFromHost, b : InternalHttp.ResponseToAndFromHost, } -mainForHost = main +main_for_host = main diff --git a/platform/libapp.roc b/platform/libapp.roc index 52874983..64972aae 100644 --- a/platform/libapp.roc +++ b/platform/libapp.roc @@ -3,4 +3,4 @@ app [main!] { pf: platform "main.roc" } # Throw an error here so we can easily confirm the host # executable built correctly just by running it. main! : _ => Result {} [Exit I32 Str]_ -main! = \_ -> Err JustAStub +main! = \_ -> Err(JustAStub) diff --git a/platform/main.roc b/platform/main.roc index 4d096bbb..e57ed198 100644 --- a/platform/main.roc +++ b/platform/main.roc @@ -33,26 +33,26 @@ main_for_host! = \raw_args -> args = raw_args - |> List.map InternalArg.to_os_raw - |> List.map Arg.from_os_raw + |> List.map(InternalArg.to_os_raw) + |> List.map(Arg.from_os_raw) - when main! args is - Ok {} -> 0 - Err (Exit code msg) -> - if Str.isEmpty msg then + when main!(args) is + Ok({}) -> 0 + Err(Exit(code, msg)) -> + if Str.is_empty(msg) then code else - _ = Stderr.line! msg + _ = Stderr.line!(msg) code - Err msg -> + Err(msg) -> help_msg = """ Program exited with error: - $(Inspect.toStr msg) + $(Inspect.to_str(msg)) - Tip: If you do not want to exit on this error, use `Result.mapErr` to handle the error. Docs for `Result.mapErr`: + Tip: If you do not want to exit on this error, use `Result.map_err` to handle the error. Docs for `Result.map_err`: """ - _ = Stderr.line! help_msg + _ = Stderr.line!(help_msg) 1