mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Merge branch 'main' into pass-args-to-cli
This commit is contained in:
commit
ddbfd48ca3
28 changed files with 697 additions and 189 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -475,9 +475,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.18"
|
version = "3.2.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b15f2ea93df33549dbe2e8eecd1ca55269d63ae0b3ba1f55db030817d1c2867f"
|
checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
@ -2809,10 +2809,11 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.1.3"
|
version = "2.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
"ucd-trie",
|
"ucd-trie",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3446,7 +3447,7 @@ name = "roc_cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"clap 3.2.18",
|
"clap 3.2.20",
|
||||||
"cli_utils",
|
"cli_utils",
|
||||||
"const_format",
|
"const_format",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
@ -3591,7 +3592,7 @@ dependencies = [
|
||||||
name = "roc_docs_cli"
|
name = "roc_docs_cli"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 3.2.18",
|
"clap 3.2.20",
|
||||||
"libc",
|
"libc",
|
||||||
"roc_docs",
|
"roc_docs",
|
||||||
]
|
]
|
||||||
|
@ -3740,7 +3741,7 @@ name = "roc_glue"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"clap 3.2.18",
|
"clap 3.2.20",
|
||||||
"cli_utils",
|
"cli_utils",
|
||||||
"dircpy",
|
"dircpy",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
@ -3759,6 +3760,7 @@ dependencies = [
|
||||||
"roc_std",
|
"roc_std",
|
||||||
"roc_target",
|
"roc_target",
|
||||||
"roc_test_utils",
|
"roc_test_utils",
|
||||||
|
"roc_tracing",
|
||||||
"roc_types",
|
"roc_types",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
|
@ -3807,7 +3809,7 @@ version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"clap 3.2.18",
|
"clap 3.2.20",
|
||||||
"iced-x86",
|
"iced-x86",
|
||||||
"mach_object",
|
"mach_object",
|
||||||
"memmap2 0.5.7",
|
"memmap2 0.5.7",
|
||||||
|
@ -4365,9 +4367,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.139"
|
version = "1.0.144"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6"
|
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
@ -4405,9 +4407,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.139"
|
version = "1.0.144"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb"
|
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -60,7 +60,7 @@ roc_editor = { path = "../editor", optional = true }
|
||||||
roc_linker = { path = "../linker" }
|
roc_linker = { path = "../linker" }
|
||||||
roc_repl_cli = { path = "../repl_cli", optional = true }
|
roc_repl_cli = { path = "../repl_cli", optional = true }
|
||||||
roc_tracing = { path = "../tracing" }
|
roc_tracing = { path = "../tracing" }
|
||||||
clap = { version = "3.2.18", default-features = false, features = ["std", "color", "suggestions"] }
|
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
|
||||||
const_format = { version = "0.2.23", features = ["const_generics"] }
|
const_format = { version = "0.2.23", features = ["const_generics"] }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
mimalloc = { version = "0.1.26", default-features = false }
|
mimalloc = { version = "0.1.26", default-features = false }
|
||||||
|
|
|
@ -10,7 +10,7 @@ description = "Our own markup language for Roc code. Used by the editor and the
|
||||||
roc_ast = { path = "../ast" }
|
roc_ast = { path = "../ast" }
|
||||||
roc_module = { path = "../compiler/module" }
|
roc_module = { path = "../compiler/module" }
|
||||||
roc_utils = { path = "../utils" }
|
roc_utils = { path = "../utils" }
|
||||||
serde = { version = "1.0.130", features = ["derive"] }
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
palette = "0.6.1"
|
palette = "0.6.1"
|
||||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
|
|
|
@ -33,6 +33,9 @@ interface Str
|
||||||
toU8,
|
toU8,
|
||||||
toI8,
|
toI8,
|
||||||
toScalars,
|
toScalars,
|
||||||
|
replaceEach,
|
||||||
|
replaceFirst,
|
||||||
|
replaceLast,
|
||||||
splitFirst,
|
splitFirst,
|
||||||
splitLast,
|
splitLast,
|
||||||
walkUtf8WithIndex,
|
walkUtf8WithIndex,
|
||||||
|
@ -276,6 +279,65 @@ countUtf8Bytes : Str -> Nat
|
||||||
## string slice that does not do bounds checking or utf-8 verification
|
## string slice that does not do bounds checking or utf-8 verification
|
||||||
substringUnsafe : Str, Nat, Nat -> Str
|
substringUnsafe : Str, Nat, Nat -> Str
|
||||||
|
|
||||||
|
## Returns the string with each occurrence of a substring replaced with a replacement.
|
||||||
|
## If the substring is not found, returns `Err NotFound`.
|
||||||
|
##
|
||||||
|
## Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
|
||||||
|
replaceEach : Str, Str, Str -> Result Str [NotFound]*
|
||||||
|
replaceEach = \haystack, needle, flower ->
|
||||||
|
when splitFirst haystack needle is
|
||||||
|
Ok { before, after } ->
|
||||||
|
# We found at least one needle, so start the buffer off with
|
||||||
|
# `before` followed by the first replacement flower.
|
||||||
|
Str.reserve "" (Str.countUtf8Bytes haystack)
|
||||||
|
|> Str.concat before
|
||||||
|
|> Str.concat flower
|
||||||
|
|> replaceEachHelp after needle flower
|
||||||
|
|> Ok
|
||||||
|
|
||||||
|
Err err -> Err err
|
||||||
|
|
||||||
|
replaceEachHelp : Str, Str, Str, Str -> Str
|
||||||
|
replaceEachHelp = \buf, haystack, needle, flower ->
|
||||||
|
when splitFirst haystack needle is
|
||||||
|
Ok { before, after } ->
|
||||||
|
buf
|
||||||
|
|> Str.concat before
|
||||||
|
|> Str.concat flower
|
||||||
|
|> replaceEachHelp after needle flower
|
||||||
|
|
||||||
|
Err NotFound -> Str.concat buf haystack
|
||||||
|
|
||||||
|
expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
|
||||||
|
|
||||||
|
## Returns the string with the first occurrence of a substring replaced with a replacement.
|
||||||
|
## If the substring is not found, returns `Err NotFound`.
|
||||||
|
##
|
||||||
|
## Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
|
||||||
|
replaceFirst : Str, Str, Str -> Result Str [NotFound]*
|
||||||
|
replaceFirst = \haystack, needle, flower ->
|
||||||
|
when splitFirst haystack needle is
|
||||||
|
Ok { before, after } ->
|
||||||
|
Ok "\(before)\(flower)\(after)"
|
||||||
|
|
||||||
|
Err err -> Err err
|
||||||
|
|
||||||
|
expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
|
||||||
|
|
||||||
|
## Returns the string with the last occurrence of a substring replaced with a replacement.
|
||||||
|
## If the substring is not found, returns `Err NotFound`.
|
||||||
|
##
|
||||||
|
## Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
|
||||||
|
replaceLast : Str, Str, Str -> Result Str [NotFound]*
|
||||||
|
replaceLast = \haystack, needle, flower ->
|
||||||
|
when splitLast haystack needle is
|
||||||
|
Ok { before, after } ->
|
||||||
|
Ok "\(before)\(flower)\(after)"
|
||||||
|
|
||||||
|
Err err -> Err err
|
||||||
|
|
||||||
|
expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
|
||||||
|
|
||||||
## Returns the string before the first occurrence of a delimiter, as well as the
|
## Returns the string before the first occurrence of a delimiter, as well as the
|
||||||
## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
|
## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
|
||||||
##
|
##
|
||||||
|
|
|
@ -1243,6 +1243,9 @@ define_builtins! {
|
||||||
47 STR_TO_NUM: "strToNum"
|
47 STR_TO_NUM: "strToNum"
|
||||||
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
|
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
|
||||||
49 STR_CAPACITY: "capacity"
|
49 STR_CAPACITY: "capacity"
|
||||||
|
50 STR_REPLACE_EACH: "replaceEach"
|
||||||
|
51 STR_REPLACE_FIRST: "replaceFirst"
|
||||||
|
52 STR_REPLACE_LAST: "replaceLast"
|
||||||
}
|
}
|
||||||
6 LIST: "List" => {
|
6 LIST: "List" => {
|
||||||
0 LIST_LIST: "List" imported // the List.List type alias
|
0 LIST_LIST: "List" imported // the List.List type alias
|
||||||
|
|
|
@ -29,8 +29,8 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
|
||||||
|
|
||||||
// TODO: get info from a package module; this is all hardcoded for now.
|
// TODO: get info from a package module; this is all hardcoded for now.
|
||||||
let mut package = roc_load::docs::Documentation {
|
let mut package = roc_load::docs::Documentation {
|
||||||
name: "roc/builtins".to_string(),
|
name: "documentation".to_string(),
|
||||||
version: "1.0.0".to_string(),
|
version: "".to_string(),
|
||||||
docs: "Package introduction or README.".to_string(),
|
docs: "Package introduction or README.".to_string(),
|
||||||
modules: loaded_modules,
|
modules: loaded_modules,
|
||||||
};
|
};
|
||||||
|
@ -239,7 +239,7 @@ fn render_module_documentation(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type_annotation_to_html(0, &mut content, type_ann);
|
type_annotation_to_html(0, &mut content, type_ann, false);
|
||||||
|
|
||||||
buf.push_str(
|
buf.push_str(
|
||||||
html_to_string(
|
html_to_string(
|
||||||
|
@ -477,10 +477,18 @@ fn new_line(buf: &mut String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// html is written to buf
|
// html is written to buf
|
||||||
fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &TypeAnnotation) {
|
fn type_annotation_to_html(
|
||||||
|
indent_level: usize,
|
||||||
|
buf: &mut String,
|
||||||
|
type_ann: &TypeAnnotation,
|
||||||
|
needs_parens: bool,
|
||||||
|
) {
|
||||||
let is_multiline = should_be_multiline(type_ann);
|
let is_multiline = should_be_multiline(type_ann);
|
||||||
match type_ann {
|
match type_ann {
|
||||||
TypeAnnotation::TagUnion { tags, extension } => {
|
TypeAnnotation::TagUnion { tags, extension } => {
|
||||||
|
if tags.is_empty() {
|
||||||
|
buf.push_str("[]");
|
||||||
|
} else {
|
||||||
let tags_len = tags.len();
|
let tags_len = tags.len();
|
||||||
|
|
||||||
let tag_union_indent = indent_level + 1;
|
let tag_union_indent = indent_level + 1;
|
||||||
|
@ -502,15 +510,13 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ
|
||||||
for (index, tag) in tags.iter().enumerate() {
|
for (index, tag) in tags.iter().enumerate() {
|
||||||
if is_multiline {
|
if is_multiline {
|
||||||
indent(buf, next_indent_level);
|
indent(buf, next_indent_level);
|
||||||
} else {
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push_str(tag.name.as_str());
|
buf.push_str(tag.name.as_str());
|
||||||
|
|
||||||
for type_value in &tag.values {
|
for type_value in &tag.values {
|
||||||
buf.push(' ');
|
buf.push(' ');
|
||||||
type_annotation_to_html(next_indent_level, buf, type_value);
|
type_annotation_to_html(next_indent_level, buf, type_value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_multiline {
|
if is_multiline {
|
||||||
|
@ -524,13 +530,12 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ
|
||||||
|
|
||||||
if is_multiline {
|
if is_multiline {
|
||||||
indent(buf, tag_union_indent);
|
indent(buf, tag_union_indent);
|
||||||
} else {
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push(']');
|
buf.push(']');
|
||||||
|
}
|
||||||
|
|
||||||
type_annotation_to_html(indent_level, buf, extension);
|
type_annotation_to_html(indent_level, buf, extension, true);
|
||||||
}
|
}
|
||||||
TypeAnnotation::BoundVariable(var_name) => {
|
TypeAnnotation::BoundVariable(var_name) => {
|
||||||
buf.push_str(var_name);
|
buf.push_str(var_name);
|
||||||
|
@ -539,18 +544,26 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ
|
||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
buf.push_str(name);
|
buf.push_str(name);
|
||||||
} else {
|
} else {
|
||||||
|
if needs_parens {
|
||||||
buf.push('(');
|
buf.push('(');
|
||||||
|
}
|
||||||
|
|
||||||
buf.push_str(name);
|
buf.push_str(name);
|
||||||
for part in parts {
|
for part in parts {
|
||||||
buf.push(' ');
|
buf.push(' ');
|
||||||
type_annotation_to_html(indent_level, buf, part);
|
type_annotation_to_html(indent_level, buf, part, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if needs_parens {
|
||||||
buf.push(')');
|
buf.push(')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
TypeAnnotation::Record { fields, extension } => {
|
TypeAnnotation::Record { fields, extension } => {
|
||||||
|
if fields.is_empty() {
|
||||||
|
buf.push_str("{}");
|
||||||
|
} else {
|
||||||
let fields_len = fields.len();
|
let fields_len = fields.len();
|
||||||
|
|
||||||
let record_indent = indent_level + 1;
|
let record_indent = indent_level + 1;
|
||||||
|
|
||||||
if is_multiline {
|
if is_multiline {
|
||||||
|
@ -586,13 +599,13 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ
|
||||||
type_annotation, ..
|
type_annotation, ..
|
||||||
} => {
|
} => {
|
||||||
buf.push_str(" : ");
|
buf.push_str(" : ");
|
||||||
type_annotation_to_html(next_indent_level, buf, type_annotation);
|
type_annotation_to_html(next_indent_level, buf, type_annotation, false);
|
||||||
}
|
}
|
||||||
RecordField::OptionalField {
|
RecordField::OptionalField {
|
||||||
type_annotation, ..
|
type_annotation, ..
|
||||||
} => {
|
} => {
|
||||||
buf.push_str(" ? ");
|
buf.push_str(" ? ");
|
||||||
type_annotation_to_html(next_indent_level, buf, type_annotation);
|
type_annotation_to_html(next_indent_level, buf, type_annotation, false);
|
||||||
}
|
}
|
||||||
RecordField::LabelOnly { .. } => {}
|
RecordField::LabelOnly { .. } => {}
|
||||||
}
|
}
|
||||||
|
@ -613,8 +626,9 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push('}');
|
buf.push('}');
|
||||||
|
}
|
||||||
|
|
||||||
type_annotation_to_html(indent_level, buf, extension);
|
type_annotation_to_html(indent_level, buf, extension, true);
|
||||||
}
|
}
|
||||||
TypeAnnotation::Function { args, output } => {
|
TypeAnnotation::Function { args, output } => {
|
||||||
let mut peekable_args = args.iter().peekable();
|
let mut peekable_args = args.iter().peekable();
|
||||||
|
@ -626,7 +640,7 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ
|
||||||
indent(buf, indent_level + 1);
|
indent(buf, indent_level + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
type_annotation_to_html(indent_level, buf, arg);
|
type_annotation_to_html(indent_level, buf, arg, false);
|
||||||
|
|
||||||
if peekable_args.peek().is_some() {
|
if peekable_args.peek().is_some() {
|
||||||
buf.push_str(", ");
|
buf.push_str(", ");
|
||||||
|
@ -646,7 +660,7 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ
|
||||||
next_indent_level += 1;
|
next_indent_level += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
type_annotation_to_html(next_indent_level, buf, output);
|
type_annotation_to_html(next_indent_level, buf, output, false);
|
||||||
}
|
}
|
||||||
TypeAnnotation::Ability { members: _ } => {
|
TypeAnnotation::Ability { members: _ } => {
|
||||||
// TODO(abilities): fill me in
|
// TODO(abilities): fill me in
|
||||||
|
|
|
@ -18,7 +18,7 @@ bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
roc_docs = { path = "../docs" }
|
roc_docs = { path = "../docs" }
|
||||||
clap = { version = "3.2.18", default-features = false, features = ["std", "color", "suggestions", "derive"] }
|
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions", "derive"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
libc = "0.2.132"
|
libc = "0.2.132"
|
||||||
|
|
|
@ -47,14 +47,14 @@ futures = "0.3.24"
|
||||||
cgmath = "0.18.0"
|
cgmath = "0.18.0"
|
||||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||||
colored = "2.0.0"
|
colored = "2.0.0"
|
||||||
pest = "2.1.3"
|
pest = "2.3.1"
|
||||||
pest_derive = "2.1.0"
|
pest_derive = "2.1.0"
|
||||||
copypasta = "0.8.1"
|
copypasta = "0.8.1"
|
||||||
palette = "0.6.1"
|
palette = "0.6.1"
|
||||||
confy = { git = 'https://github.com/rust-cli/confy', features = [
|
confy = { git = 'https://github.com/rust-cli/confy', features = [
|
||||||
"yaml_conf"
|
"yaml_conf"
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
serde = { version = "1.0.130", features = ["derive"] }
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
nonempty = "0.8.0"
|
nonempty = "0.8.0"
|
||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
rodio = { version = "0.15.0", optional = true } # to play sounds
|
rodio = { version = "0.15.0", optional = true } # to play sounds
|
||||||
|
|
|
@ -18,9 +18,10 @@ roc_module = { path = "../compiler/module" }
|
||||||
roc_collections = { path = "../compiler/collections" }
|
roc_collections = { path = "../compiler/collections" }
|
||||||
roc_target = { path = "../compiler/roc_target" }
|
roc_target = { path = "../compiler/roc_target" }
|
||||||
roc_error_macros = { path = "../error_macros" }
|
roc_error_macros = { path = "../error_macros" }
|
||||||
|
roc_tracing = { path = "../tracing" }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
target-lexicon = "0.12.3"
|
target-lexicon = "0.12.3"
|
||||||
clap = { version = "3.2.18", default-features = false, features = ["std", "color", "suggestions", "derive"] }
|
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions", "derive"] }
|
||||||
strum = "0.24.0"
|
strum = "0.24.0"
|
||||||
strum_macros = "0.24"
|
strum_macros = "0.24"
|
||||||
indexmap = "1.8.1"
|
indexmap = "1.8.1"
|
||||||
|
|
|
@ -65,24 +65,45 @@ impl Types {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_equivalent(&self, a: &RocType, b: &RocType) -> bool {
|
pub fn is_equivalent(&self, a: &RocType, b: &RocType) -> bool {
|
||||||
|
self.is_equivalent_help(RocTypeOrPending::Type(a), RocTypeOrPending::Type(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_equivalent_help(&self, a: RocTypeOrPending, b: RocTypeOrPending) -> bool {
|
||||||
use RocType::*;
|
use RocType::*;
|
||||||
|
|
||||||
|
let (a, b) = match (a, b) {
|
||||||
|
(RocTypeOrPending::Type(a), RocTypeOrPending::Type(b)) => (a, b),
|
||||||
|
(RocTypeOrPending::Pending, RocTypeOrPending::Pending) => return true,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(RocStr, RocStr) | (Bool, Bool) | (EmptyTagUnion, EmptyTagUnion) | (Unit, Unit) => true,
|
(RocStr, RocStr) | (Bool, Bool) | (EmptyTagUnion, EmptyTagUnion) | (Unit, Unit) => true,
|
||||||
(RocResult(ok_a, err_a), RocResult(ok_b, err_b)) => {
|
(RocResult(ok_a, err_a), RocResult(ok_b, err_b)) => {
|
||||||
self.is_equivalent(self.get_type(*ok_a), self.get_type(*ok_b))
|
self.is_equivalent_help(
|
||||||
&& self.is_equivalent(self.get_type(*err_a), self.get_type(*err_b))
|
self.get_type_or_pending(*ok_a),
|
||||||
|
self.get_type_or_pending(*ok_b),
|
||||||
|
) && self.is_equivalent_help(
|
||||||
|
self.get_type_or_pending(*err_a),
|
||||||
|
self.get_type_or_pending(*err_b),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
(Num(num_a), Num(num_b)) => num_a == num_b,
|
(Num(num_a), Num(num_b)) => num_a == num_b,
|
||||||
(RocList(elem_a), RocList(elem_b))
|
(RocList(elem_a), RocList(elem_b))
|
||||||
| (RocSet(elem_a), RocSet(elem_b))
|
| (RocSet(elem_a), RocSet(elem_b))
|
||||||
| (RocBox(elem_a), RocBox(elem_b))
|
| (RocBox(elem_a), RocBox(elem_b))
|
||||||
| (RecursivePointer(elem_a), RecursivePointer(elem_b)) => {
|
| (RecursivePointer(elem_a), RecursivePointer(elem_b)) => self.is_equivalent_help(
|
||||||
self.is_equivalent(self.get_type(*elem_a), self.get_type(*elem_b))
|
self.get_type_or_pending(*elem_a),
|
||||||
}
|
self.get_type_or_pending(*elem_b),
|
||||||
|
),
|
||||||
(RocDict(key_a, val_a), RocDict(key_b, val_b)) => {
|
(RocDict(key_a, val_a), RocDict(key_b, val_b)) => {
|
||||||
self.is_equivalent(self.get_type(*key_a), self.get_type(*key_b))
|
self.is_equivalent_help(
|
||||||
&& self.is_equivalent(self.get_type(*val_a), self.get_type(*val_b))
|
self.get_type_or_pending(*key_a),
|
||||||
|
self.get_type_or_pending(*key_b),
|
||||||
|
) && self.is_equivalent_help(
|
||||||
|
self.get_type_or_pending(*val_a),
|
||||||
|
self.get_type_or_pending(*val_b),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
(TagUnion(union_a), TagUnion(union_b)) => {
|
(TagUnion(union_a), TagUnion(union_b)) => {
|
||||||
use RocTagUnion::*;
|
use RocTagUnion::*;
|
||||||
|
@ -113,8 +134,10 @@ impl Types {
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
tag_name_a == tag_name_b
|
tag_name_a == tag_name_b
|
||||||
&& self
|
&& self.is_equivalent_help(
|
||||||
.is_equivalent(self.get_type(*payload_a), self.get_type(*payload_b))
|
self.get_type_or_pending(*payload_a),
|
||||||
|
self.get_type_or_pending(*payload_b),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
(Enumeration { tags: tags_a, .. }, Enumeration { tags: tags_b, .. }) => {
|
(Enumeration { tags: tags_a, .. }, Enumeration { tags: tags_b, .. }) => {
|
||||||
tags_a == tags_b
|
tags_a == tags_b
|
||||||
|
@ -157,9 +180,9 @@ impl Types {
|
||||||
|((name_a, opt_id_a), (name_b, opt_id_b))| {
|
|((name_a, opt_id_a), (name_b, opt_id_b))| {
|
||||||
name_a == name_b
|
name_a == name_b
|
||||||
&& match (opt_id_a, opt_id_b) {
|
&& match (opt_id_a, opt_id_b) {
|
||||||
(Some(id_a), Some(id_b)) => self.is_equivalent(
|
(Some(id_a), Some(id_b)) => self.is_equivalent_help(
|
||||||
self.get_type(*id_a),
|
self.get_type_or_pending(*id_a),
|
||||||
self.get_type(*id_b),
|
self.get_type_or_pending(*id_b),
|
||||||
),
|
),
|
||||||
(None, None) => true,
|
(None, None) => true,
|
||||||
(None, Some(_)) | (Some(_), None) => false,
|
(None, Some(_)) | (Some(_), None) => false,
|
||||||
|
@ -179,9 +202,9 @@ impl Types {
|
||||||
|((name_a, opt_id_a), (name_b, opt_id_b))| {
|
|((name_a, opt_id_a), (name_b, opt_id_b))| {
|
||||||
name_a == name_b
|
name_a == name_b
|
||||||
&& match (opt_id_a, opt_id_b) {
|
&& match (opt_id_a, opt_id_b) {
|
||||||
(Some(id_a), Some(id_b)) => self.is_equivalent(
|
(Some(id_a), Some(id_b)) => self.is_equivalent_help(
|
||||||
self.get_type(*id_a),
|
self.get_type_or_pending(*id_a),
|
||||||
self.get_type(*id_b),
|
self.get_type_or_pending(*id_b),
|
||||||
),
|
),
|
||||||
(None, None) => true,
|
(None, None) => true,
|
||||||
(None, Some(_)) | (Some(_), None) => false,
|
(None, Some(_)) | (Some(_), None) => false,
|
||||||
|
@ -241,7 +264,10 @@ impl Types {
|
||||||
.zip(fields_b.iter())
|
.zip(fields_b.iter())
|
||||||
.all(|((name_a, id_a), (name_b, id_b))| {
|
.all(|((name_a, id_a), (name_b, id_b))| {
|
||||||
name_a == name_b
|
name_a == name_b
|
||||||
&& self.is_equivalent(self.get_type(*id_a), self.get_type(*id_b))
|
&& self.is_equivalent_help(
|
||||||
|
self.get_type_or_pending(*id_a),
|
||||||
|
self.get_type_or_pending(*id_b),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -261,7 +287,10 @@ impl Types {
|
||||||
.zip(fields_b.iter())
|
.zip(fields_b.iter())
|
||||||
.all(|((name_a, id_a), (name_b, id_b))| {
|
.all(|((name_a, id_a), (name_b, id_b))| {
|
||||||
name_a == name_b
|
name_a == name_b
|
||||||
&& self.is_equivalent(self.get_type(*id_a), self.get_type(*id_b))
|
&& self.is_equivalent_help(
|
||||||
|
self.get_type_or_pending(*id_a),
|
||||||
|
self.get_type_or_pending(*id_b),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -283,10 +312,16 @@ impl Types {
|
||||||
// with the same type could have completely different implementations!
|
// with the same type could have completely different implementations!
|
||||||
if name_a == name_b
|
if name_a == name_b
|
||||||
&& args_a.len() == args_b.len()
|
&& args_a.len() == args_b.len()
|
||||||
&& self.is_equivalent(self.get_type(*ret_a), self.get_type(*ret_b))
|
&& self.is_equivalent_help(
|
||||||
|
self.get_type_or_pending(*ret_a),
|
||||||
|
self.get_type_or_pending(*ret_b),
|
||||||
|
)
|
||||||
{
|
{
|
||||||
args_a.iter().zip(args_b.iter()).all(|(id_a, id_b)| {
|
args_a.iter().zip(args_b.iter()).all(|(id_a, id_b)| {
|
||||||
self.is_equivalent(self.get_type(*id_a), self.get_type(*id_b))
|
self.is_equivalent_help(
|
||||||
|
self.get_type_or_pending(*id_a),
|
||||||
|
self.get_type_or_pending(*id_b),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -359,6 +394,12 @@ impl Types {
|
||||||
typ: RocType,
|
typ: RocType,
|
||||||
layout: Layout<'a>,
|
layout: Layout<'a>,
|
||||||
) -> TypeId {
|
) -> TypeId {
|
||||||
|
for (id, existing_type) in self.types.iter().enumerate() {
|
||||||
|
if self.is_equivalent(&typ, existing_type) {
|
||||||
|
return TypeId(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let id = TypeId(self.types.len());
|
let id = TypeId(self.types.len());
|
||||||
|
|
||||||
assert!(id.0 <= TypeId::MAX.0);
|
assert!(id.0 <= TypeId::MAX.0);
|
||||||
|
@ -379,7 +420,15 @@ impl Types {
|
||||||
pub fn get_type(&self, id: TypeId) -> &RocType {
|
pub fn get_type(&self, id: TypeId) -> &RocType {
|
||||||
match self.types.get(id.0) {
|
match self.types.get(id.0) {
|
||||||
Some(typ) => typ,
|
Some(typ) => typ,
|
||||||
None => unreachable!(),
|
None => unreachable!("{:?}", id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_type_or_pending(&self, id: TypeId) -> RocTypeOrPending {
|
||||||
|
match self.types.get(id.0) {
|
||||||
|
Some(typ) => RocTypeOrPending::Type(typ),
|
||||||
|
None if id == TypeId::PENDING => RocTypeOrPending::Pending,
|
||||||
|
None => unreachable!("{:?}", id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,6 +491,12 @@ impl Types {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RocTypeOrPending<'a> {
|
||||||
|
Type(&'a RocType),
|
||||||
|
/// A pending recursive pointer
|
||||||
|
Pending,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum RocType {
|
pub enum RocType {
|
||||||
RocStr,
|
RocStr,
|
||||||
|
@ -653,6 +708,8 @@ impl<'a> Env<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_type(&mut self, var: Variable, types: &mut Types) -> TypeId {
|
fn add_type(&mut self, var: Variable, types: &mut Types) -> TypeId {
|
||||||
|
roc_tracing::debug!(content=?roc_types::subs::SubsFmtContent(self.subs.get_content_without_compacting(var), self.subs), "adding type");
|
||||||
|
|
||||||
let layout = self
|
let layout = self
|
||||||
.layout_cache
|
.layout_cache
|
||||||
.from_var(self.arena, var, self.subs)
|
.from_var(self.arena, var, self.subs)
|
||||||
|
|
5
crates/glue/tests/fixtures/multiple-modules/Dep1.roc
vendored
Normal file
5
crates/glue/tests/fixtures/multiple-modules/Dep1.roc
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
interface Dep1 exposes [DepStr1, string] imports []
|
||||||
|
|
||||||
|
DepStr1 := [ S Str ]
|
||||||
|
|
||||||
|
string = \s -> @DepStr1 (S s)
|
5
crates/glue/tests/fixtures/multiple-modules/Dep2.roc
vendored
Normal file
5
crates/glue/tests/fixtures/multiple-modules/Dep2.roc
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
interface Dep2 exposes [DepStr2, string] imports []
|
||||||
|
|
||||||
|
DepStr2 := [ R Str ]
|
||||||
|
|
||||||
|
string = \s -> @DepStr2 (R s)
|
6
crates/glue/tests/fixtures/multiple-modules/app.roc
vendored
Normal file
6
crates/glue/tests/fixtures/multiple-modules/app.roc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
app "app"
|
||||||
|
packages { pf: "platform.roc" }
|
||||||
|
imports [pf.Dep1, pf.Dep2]
|
||||||
|
provides [main] to pf
|
||||||
|
|
||||||
|
main = {s1: Dep1.string "hello", s2: Dep2.string "world"}
|
11
crates/glue/tests/fixtures/multiple-modules/platform.roc
vendored
Normal file
11
crates/glue/tests/fixtures/multiple-modules/platform.roc
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
platform "test-platform"
|
||||||
|
requires {} { main : _ }
|
||||||
|
exposes []
|
||||||
|
packages {}
|
||||||
|
imports [Dep1, Dep2]
|
||||||
|
provides [mainForHost]
|
||||||
|
|
||||||
|
Combined : {s1: Dep1.DepStr1, s2: Dep2.DepStr2}
|
||||||
|
|
||||||
|
mainForHost : Combined
|
||||||
|
mainForHost = main
|
99
crates/glue/tests/fixtures/multiple-modules/src/lib.rs
vendored
Normal file
99
crates/glue/tests/fixtures/multiple-modules/src/lib.rs
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
mod test_glue;
|
||||||
|
|
||||||
|
use indoc::indoc;
|
||||||
|
use test_glue::Combined;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#[link_name = "roc__mainForHost_1_exposed_generic"]
|
||||||
|
fn roc_main(_: *mut Combined);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rust_main() -> i32 {
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::hash_set::HashSet;
|
||||||
|
|
||||||
|
let tag_union = unsafe {
|
||||||
|
let mut ret: core::mem::MaybeUninit<Combined> = core::mem::MaybeUninit::uninit();
|
||||||
|
|
||||||
|
roc_main(ret.as_mut_ptr());
|
||||||
|
|
||||||
|
ret.assume_init()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify that it has all the expected traits.
|
||||||
|
|
||||||
|
assert!(tag_union == tag_union); // PartialEq
|
||||||
|
assert!(tag_union.clone() == tag_union.clone()); // Clone
|
||||||
|
|
||||||
|
assert!(tag_union.partial_cmp(&tag_union) == Some(Ordering::Equal)); // PartialOrd
|
||||||
|
assert!(tag_union.cmp(&tag_union) == Ordering::Equal); // Ord
|
||||||
|
|
||||||
|
print!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
combined was: {:?}
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
tag_union,
|
||||||
|
); // Debug
|
||||||
|
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
|
||||||
|
set.insert(tag_union.clone()); // Eq, Hash
|
||||||
|
set.insert(tag_union);
|
||||||
|
|
||||||
|
assert_eq!(set.len(), 1);
|
||||||
|
|
||||||
|
// Exit code
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Externs required by roc_std and by the Roc app
|
||||||
|
|
||||||
|
use core::ffi::c_void;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
|
||||||
|
return libc::malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_realloc(
|
||||||
|
c_ptr: *mut c_void,
|
||||||
|
new_size: usize,
|
||||||
|
_old_size: usize,
|
||||||
|
_alignment: u32,
|
||||||
|
) -> *mut c_void {
|
||||||
|
return libc::realloc(c_ptr, new_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
|
||||||
|
return libc::free(c_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
|
||||||
|
match tag_id {
|
||||||
|
0 => {
|
||||||
|
let slice = CStr::from_ptr(c_ptr as *const c_char);
|
||||||
|
let string = slice.to_str().unwrap();
|
||||||
|
eprintln!("Roc hit a panic: {}", string);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
|
||||||
|
libc::memcpy(dst, src, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
|
||||||
|
libc::memset(dst, c, n)
|
||||||
|
}
|
|
@ -124,6 +124,9 @@ mod glue_cli_run {
|
||||||
list_recursive_union:"list-recursive-union" => indoc!(r#"
|
list_recursive_union:"list-recursive-union" => indoc!(r#"
|
||||||
rbt was: Rbt { default: Job::Job(R1 { command: Command::Command(R2 { args: [], tool: Tool::SystemTool("test") }), inputFiles: ["foo"], job: [] }) }
|
rbt was: Rbt { default: Job::Job(R1 { command: Command::Command(R2 { args: [], tool: Tool::SystemTool("test") }), inputFiles: ["foo"], job: [] }) }
|
||||||
"#),
|
"#),
|
||||||
|
multiple_modules:"multiple-modules" => indoc!(r#"
|
||||||
|
combined was: Combined { s1: DepStr1::S("hello"), s2: DepStr2::R("world") }
|
||||||
|
"#),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_for_tests(all_fixtures: &mut roc_collections::VecSet<String>) {
|
fn check_for_tests(all_fixtures: &mut roc_collections::VecSet<String>) {
|
||||||
|
|
|
@ -17,12 +17,12 @@ roc_build = { path = "../compiler/build" }
|
||||||
roc_collections = { path = "../compiler/collections" }
|
roc_collections = { path = "../compiler/collections" }
|
||||||
roc_error_macros = { path = "../error_macros" }
|
roc_error_macros = { path = "../error_macros" }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
clap = { version = "3.2.18", default-features = false, features = ["std", "color", "suggestions"] }
|
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
|
||||||
iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
|
iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
|
||||||
memmap2 = "0.5.7"
|
memmap2 = "0.5.7"
|
||||||
object = { version = "0.29.0", features = ["read", "write"] }
|
object = { version = "0.29.0", features = ["read", "write"] }
|
||||||
mach_object = "0.1"
|
mach_object = "0.1"
|
||||||
serde = { version = "1.0.130", features = ["derive"] }
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
target-lexicon = "0.12.3"
|
target-lexicon = "0.12.3"
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
|
|
1
examples/interactive/.gitignore
vendored
1
examples/interactive/.gitignore
vendored
|
@ -5,3 +5,4 @@ effects
|
||||||
form
|
form
|
||||||
tui
|
tui
|
||||||
http-get
|
http-get
|
||||||
|
file-io
|
||||||
|
|
25
examples/interactive/cli-platform/Dir.roc
Normal file
25
examples/interactive/cli-platform/Dir.roc
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
interface Dir
|
||||||
|
exposes [ReadErr, DeleteErr, DirEntry, deleteEmptyDir, deleteRecursive, list]
|
||||||
|
imports [Effect, Task.{ Task }, InternalTask, Path.{ Path }, InternalPath, InternalDir]
|
||||||
|
|
||||||
|
ReadErr : InternalDir.ReadErr
|
||||||
|
|
||||||
|
DeleteErr : InternalDir.DeleteErr
|
||||||
|
|
||||||
|
DirEntry : InternalDir.DirEntry
|
||||||
|
|
||||||
|
## Lists the files and directories inside the directory.
|
||||||
|
list : Path -> Task (List Path) [DirReadErr Path ReadErr]* [Read [File]*]*
|
||||||
|
list = \path ->
|
||||||
|
effect = Effect.map (Effect.dirList (InternalPath.toBytes path)) \result ->
|
||||||
|
when result is
|
||||||
|
Ok entries -> Ok (List.map entries InternalPath.fromOsBytes)
|
||||||
|
Err err -> Err (DirReadErr path err)
|
||||||
|
|
||||||
|
InternalTask.fromEffect effect
|
||||||
|
|
||||||
|
## Deletes a directory if it's empty.
|
||||||
|
deleteEmptyDir : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]*
|
||||||
|
|
||||||
|
## Recursively deletes the directory as well as all files and directories inside it.
|
||||||
|
deleteRecursive : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]*
|
|
@ -6,15 +6,18 @@ hosted Effect
|
||||||
always,
|
always,
|
||||||
forever,
|
forever,
|
||||||
loop,
|
loop,
|
||||||
|
dirList,
|
||||||
|
cwd,
|
||||||
stdoutLine,
|
stdoutLine,
|
||||||
stderrLine,
|
stderrLine,
|
||||||
stdinLine,
|
stdinLine,
|
||||||
sendRequest,
|
sendRequest,
|
||||||
fileReadBytes,
|
fileReadBytes,
|
||||||
|
fileDelete,
|
||||||
fileWriteUtf8,
|
fileWriteUtf8,
|
||||||
fileWriteBytes,
|
fileWriteBytes,
|
||||||
]
|
]
|
||||||
imports [InternalHttp.{ Request, Response }, InternalFile]
|
imports [InternalHttp.{ Request, Response }, InternalFile, InternalDir]
|
||||||
generates Effect with [after, map, always, forever, loop]
|
generates Effect with [after, map, always, forever, loop]
|
||||||
|
|
||||||
stdoutLine : Str -> Effect {}
|
stdoutLine : Str -> Effect {}
|
||||||
|
@ -23,6 +26,10 @@ stdinLine : Effect Str
|
||||||
|
|
||||||
fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr)
|
fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr)
|
||||||
fileWriteUtf8 : List U8, Str -> Effect (Result {} InternalFile.WriteErr)
|
fileWriteUtf8 : List U8, Str -> Effect (Result {} InternalFile.WriteErr)
|
||||||
|
fileDelete : List U8 -> Effect (Result {} InternalFile.WriteErr)
|
||||||
fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr)
|
fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr)
|
||||||
|
dirList : List U8 -> Effect (Result (List (List U8)) InternalDir.ReadErr)
|
||||||
|
|
||||||
|
cwd : Effect (List U8)
|
||||||
|
|
||||||
sendRequest : Box Request -> Effect Response
|
sendRequest : Box Request -> Effect Response
|
||||||
|
|
15
examples/interactive/cli-platform/Env.roc
Normal file
15
examples/interactive/cli-platform/Env.roc
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
interface Env
|
||||||
|
exposes [cwd]
|
||||||
|
imports [Task.{ Task }, Path.{ Path }, InternalPath, Effect, InternalTask]
|
||||||
|
|
||||||
|
## Reads the [current working directory](https://en.wikipedia.org/wiki/Working_directory)
|
||||||
|
## from the environment.
|
||||||
|
cwd : Task Path [CwdUnavailable]* [Env]*
|
||||||
|
cwd =
|
||||||
|
effect = Effect.map Effect.cwd \bytes ->
|
||||||
|
if List.isEmpty bytes then
|
||||||
|
Err CwdUnavailable
|
||||||
|
else
|
||||||
|
Ok (InternalPath.fromArbitraryBytes bytes)
|
||||||
|
|
||||||
|
InternalTask.fromEffect effect
|
|
@ -1,11 +1,13 @@
|
||||||
interface File
|
interface File
|
||||||
exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes]
|
exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes, delete]
|
||||||
imports [Effect, Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath]
|
imports [Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath, Effect.{ Effect }]
|
||||||
|
|
||||||
ReadErr : InternalFile.ReadErr
|
ReadErr : InternalFile.ReadErr
|
||||||
|
|
||||||
WriteErr : InternalFile.WriteErr
|
WriteErr : InternalFile.WriteErr
|
||||||
|
|
||||||
|
## Encodes a value using the given `EncodingFormat` and writes it to a file.
|
||||||
|
##
|
||||||
## For example, suppose you have a [JSON](https://en.wikipedia.org/wiki/JSON)
|
## For example, suppose you have a [JSON](https://en.wikipedia.org/wiki/JSON)
|
||||||
## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format
|
## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format
|
||||||
## to write some encodable data to a file as JSON, like so:
|
## to write some encodable data to a file as JSON, like so:
|
||||||
|
@ -31,7 +33,7 @@ write = \path, val, fmt ->
|
||||||
# TODO handle encoding errors here, once they exist
|
# TODO handle encoding errors here, once they exist
|
||||||
writeBytes path bytes
|
writeBytes path bytes
|
||||||
|
|
||||||
## Write bytes to a file.
|
## Writes bytes to a file.
|
||||||
##
|
##
|
||||||
## # Writes the bytes 1, 2, 3 to the file `myfile.dat`.
|
## # Writes the bytes 1, 2, 3 to the file `myfile.dat`.
|
||||||
## File.writeBytes (Path.fromStr "myfile.dat") [1, 2, 3]
|
## File.writeBytes (Path.fromStr "myfile.dat") [1, 2, 3]
|
||||||
|
@ -41,12 +43,9 @@ write = \path, val, fmt ->
|
||||||
## To format data before writing it to a file, you can use [File.write] instead.
|
## To format data before writing it to a file, you can use [File.write] instead.
|
||||||
writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
||||||
writeBytes = \path, bytes ->
|
writeBytes = \path, bytes ->
|
||||||
InternalPath.toBytes path
|
toWriteTask path \pathBytes -> Effect.fileWriteBytes pathBytes bytes
|
||||||
|> Effect.fileWriteBytes bytes
|
|
||||||
|> InternalTask.fromEffect
|
|
||||||
|> Task.mapFail \err -> FileWriteErr path err
|
|
||||||
|
|
||||||
## Write a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8).
|
## 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`.
|
## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`.
|
||||||
## File.writeUtf8 (Path.fromStr "myfile.txt") "Hello!"
|
## File.writeUtf8 (Path.fromStr "myfile.txt") "Hello!"
|
||||||
|
@ -56,12 +55,29 @@ writeBytes = \path, bytes ->
|
||||||
## To write unformatted bytes to a file, you can use [File.writeBytes] instead.
|
## To write unformatted bytes to a file, you can use [File.writeBytes] instead.
|
||||||
writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
||||||
writeUtf8 = \path, str ->
|
writeUtf8 = \path, str ->
|
||||||
InternalPath.toBytes path
|
toWriteTask path \bytes -> Effect.fileWriteUtf8 bytes str
|
||||||
|> Effect.fileWriteUtf8 str
|
|
||||||
|> InternalTask.fromEffect
|
|
||||||
|> Task.mapFail \err -> FileWriteErr path err
|
|
||||||
|
|
||||||
## Read all the bytes in a file.
|
## Deletes a file from the filesystem.
|
||||||
|
##
|
||||||
|
## # Deletes the file named
|
||||||
|
## File.delete (Path.fromStr "myfile.dat") [1, 2, 3]
|
||||||
|
##
|
||||||
|
## Note that this does not securely erase the file's contents from disk; instead, the operating
|
||||||
|
## system marks the space it was occupying as safe to write over in the future. Also, the operating
|
||||||
|
## system may not immediately mark the space as free; for example, on Windows it will wait until
|
||||||
|
## the last file handle to it is closed, and on UNIX, it will not remove it until the last
|
||||||
|
## [hard link](https://en.wikipedia.org/wiki/Hard_link) to it has been deleted.
|
||||||
|
##
|
||||||
|
## This performs a [`DeleteFile`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deletefile)
|
||||||
|
## on Windows and [`unlink`](https://en.wikipedia.org/wiki/Unlink_(Unix)) on UNIX systems.
|
||||||
|
##
|
||||||
|
## On Windows, this will fail when attempting to delete a readonly file; the file's
|
||||||
|
## readonly permission must be disabled before it can be successfully deleted.
|
||||||
|
delete : Path -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
||||||
|
delete = \path ->
|
||||||
|
toWriteTask path \bytes -> Effect.fileDelete bytes
|
||||||
|
|
||||||
|
## Reads all the bytes in a file.
|
||||||
##
|
##
|
||||||
## # Read all the bytes in `myfile.txt`.
|
## # Read all the bytes in `myfile.txt`.
|
||||||
## File.readBytes (Path.fromStr "myfile.txt")
|
## File.readBytes (Path.fromStr "myfile.txt")
|
||||||
|
@ -71,12 +87,9 @@ writeUtf8 = \path, str ->
|
||||||
## To read and decode data from a file, you can use `File.read` instead.
|
## To read and decode data from a file, you can use `File.read` instead.
|
||||||
readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]* [Read [File]*]*
|
readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]* [Read [File]*]*
|
||||||
readBytes = \path ->
|
readBytes = \path ->
|
||||||
InternalPath.toBytes path
|
toReadTask path \bytes -> Effect.fileReadBytes bytes
|
||||||
|> Effect.fileReadBytes
|
|
||||||
|> InternalTask.fromEffect
|
|
||||||
|> Task.mapFail \err -> FileReadErr path err
|
|
||||||
|
|
||||||
## Read a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text.
|
## 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`.
|
## # Reads UTF-8 encoded text into a `Str` from the file `myfile.txt`.
|
||||||
## File.readUtf8 (Path.fromStr "myfile.txt")
|
## File.readUtf8 (Path.fromStr "myfile.txt")
|
||||||
|
@ -119,3 +132,16 @@ readUtf8 = \path ->
|
||||||
# Err decodingErr -> Err (FileReadDecodeErr decodingErr)
|
# Err decodingErr -> Err (FileReadDecodeErr decodingErr)
|
||||||
# Err readErr -> Err (FileReadErr readErr)
|
# Err readErr -> Err (FileReadErr readErr)
|
||||||
# InternalTask.fromEffect effect
|
# InternalTask.fromEffect effect
|
||||||
|
toWriteTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileWriteErr Path err]* [Write [File]*]*
|
||||||
|
toWriteTask = \path, toEffect ->
|
||||||
|
InternalPath.toBytes path
|
||||||
|
|> toEffect
|
||||||
|
|> InternalTask.fromEffect
|
||||||
|
|> Task.mapFail \err -> FileWriteErr path err
|
||||||
|
|
||||||
|
toReadTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileReadErr Path err]* [Read [File]*]*
|
||||||
|
toReadTask = \path, toEffect ->
|
||||||
|
InternalPath.toBytes path
|
||||||
|
|> toEffect
|
||||||
|
|> InternalTask.fromEffect
|
||||||
|
|> Task.mapFail \err -> FileReadErr path err
|
||||||
|
|
32
examples/interactive/cli-platform/FileMetadata.roc
Normal file
32
examples/interactive/cli-platform/FileMetadata.roc
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
interface FileMetadata
|
||||||
|
exposes [FileMetadata, bytes, type, isReadonly, mode]
|
||||||
|
imports []
|
||||||
|
|
||||||
|
# Design note: this is an opaque type rather than a type alias so that
|
||||||
|
# we can add new operating system info if new OS releases introduce them,
|
||||||
|
# as a backwards-compatible change.
|
||||||
|
FileMetadata := {
|
||||||
|
bytes : U64,
|
||||||
|
type : [File, Dir, Symlink],
|
||||||
|
isReadonly : Bool,
|
||||||
|
mode : [Unix U32, NonUnix],
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes : FileMetadata -> U64
|
||||||
|
bytes = \@FileMetadata info -> info.bytes
|
||||||
|
|
||||||
|
isReadonly : FileMetadata -> Bool
|
||||||
|
isReadonly = \@FileMetadata info -> info.isReadonly
|
||||||
|
|
||||||
|
type : FileMetadata -> [File, Dir, Symlink]
|
||||||
|
type = \@FileMetadata info -> info.type
|
||||||
|
|
||||||
|
mode : FileMetadata -> [Unix U32, NonUnix]
|
||||||
|
mode = \@FileMetadata info -> info.mode
|
||||||
|
|
||||||
|
# TODO need to create a Time module and return something like Time.Utc here.
|
||||||
|
# lastModified : FileMetadata -> Utc
|
||||||
|
# TODO need to create a Time module and return something like Time.Utc here.
|
||||||
|
# lastAccessed : FileMetadata -> Utc
|
||||||
|
# TODO need to create a Time module and return something like Time.Utc here.
|
||||||
|
# created : FileMetadata -> Utc
|
41
examples/interactive/cli-platform/InternalDir.roc
Normal file
41
examples/interactive/cli-platform/InternalDir.roc
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
interface InternalDir
|
||||||
|
exposes [ReadErr, DeleteErr, DirEntry]
|
||||||
|
imports [FileMetadata.{ FileMetadata }, Path.{ Path }]
|
||||||
|
|
||||||
|
DirEntry : {
|
||||||
|
path : Path,
|
||||||
|
type : [File, Dir, Symlink],
|
||||||
|
metadata : FileMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadErr : [
|
||||||
|
NotFound,
|
||||||
|
Interrupted,
|
||||||
|
InvalidFilename,
|
||||||
|
PermissionDenied,
|
||||||
|
TooManySymlinks, # aka FilesystemLoop
|
||||||
|
TooManyHardlinks,
|
||||||
|
TimedOut,
|
||||||
|
StaleNetworkFileHandle,
|
||||||
|
NotADirectory,
|
||||||
|
OutOfMemory,
|
||||||
|
Unsupported,
|
||||||
|
Unrecognized I32 Str,
|
||||||
|
]
|
||||||
|
|
||||||
|
DeleteErr : [
|
||||||
|
NotFound,
|
||||||
|
Interrupted,
|
||||||
|
InvalidFilename,
|
||||||
|
PermissionDenied,
|
||||||
|
TooManySymlinks, # aka FilesystemLoop
|
||||||
|
TooManyHardlinks,
|
||||||
|
TimedOut,
|
||||||
|
StaleNetworkFileHandle,
|
||||||
|
NotADirectory,
|
||||||
|
ReadOnlyFilesystem,
|
||||||
|
DirectoryNotEmpty,
|
||||||
|
OutOfMemory,
|
||||||
|
Unsupported,
|
||||||
|
Unrecognized I32 Str,
|
||||||
|
]
|
|
@ -5,6 +5,8 @@ interface InternalPath
|
||||||
wrap,
|
wrap,
|
||||||
unwrap,
|
unwrap,
|
||||||
toBytes,
|
toBytes,
|
||||||
|
fromArbitraryBytes,
|
||||||
|
fromOsBytes,
|
||||||
]
|
]
|
||||||
imports []
|
imports []
|
||||||
|
|
||||||
|
@ -61,3 +63,11 @@ toBytes = \@InternalPath path ->
|
||||||
FromOperatingSystem bytes -> bytes
|
FromOperatingSystem bytes -> bytes
|
||||||
ArbitraryBytes bytes -> bytes
|
ArbitraryBytes bytes -> bytes
|
||||||
FromStr str -> Str.toUtf8 str
|
FromStr str -> Str.toUtf8 str
|
||||||
|
|
||||||
|
fromArbitraryBytes : List U8 -> InternalPath
|
||||||
|
fromArbitraryBytes = \bytes ->
|
||||||
|
@InternalPath (ArbitraryBytes bytes)
|
||||||
|
|
||||||
|
fromOsBytes : List U8 -> InternalPath
|
||||||
|
fromOsBytes = \bytes ->
|
||||||
|
@InternalPath (FromOperatingSystem bytes)
|
||||||
|
|
|
@ -6,6 +6,7 @@ interface Path
|
||||||
WindowsRoot,
|
WindowsRoot,
|
||||||
# toComponents,
|
# toComponents,
|
||||||
# walkComponents,
|
# walkComponents,
|
||||||
|
display,
|
||||||
fromStr,
|
fromStr,
|
||||||
fromBytes,
|
fromBytes,
|
||||||
withExtension,
|
withExtension,
|
||||||
|
@ -82,13 +83,13 @@ fromBytes = \bytes ->
|
||||||
## have been encoded with the same charset as the operating system's curent locale (which
|
## have been encoded with the same charset as the operating system's curent locale (which
|
||||||
## typically does not change after it is set during installation of the OS), so
|
## typically does not change after it is set during installation of the OS), so
|
||||||
## this should convert a [Path] to a valid string as long as the path was created
|
## this should convert a [Path] to a valid string as long as the path was created
|
||||||
## with the given [Charset]. (Use [Env.charset] to get the current system charset.)
|
## with the given `Charset`. (Use `Env.charset` to get the current system charset.)
|
||||||
##
|
##
|
||||||
## For a conversion to [Str] that is lossy but does not return a [Result], see
|
## For a conversion to [Str] that is lossy but does not return a [Result], see
|
||||||
## [displayUtf8].
|
## [display].
|
||||||
# toInner : Path -> [Str Str, Bytes (List U8)]
|
# toInner : Path -> [Str Str, Bytes (List U8)]
|
||||||
## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8),
|
## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8),
|
||||||
## and converts it to a string using [Str.displayUtf8].
|
## and converts it to a string using `Str.display`.
|
||||||
##
|
##
|
||||||
## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens,
|
## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens,
|
||||||
## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
|
## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
|
||||||
|
@ -103,17 +104,21 @@ fromBytes = \bytes ->
|
||||||
## Converting paths to strings can be an unreliable operation, because operating systems
|
## Converting paths to strings can be an unreliable operation, because operating systems
|
||||||
## don't record the paths' encodings. This means it's possible for the path to have been
|
## don't record the paths' encodings. This means it's possible for the path to have been
|
||||||
## encoded with a different character set than UTF-8 even if UTF-8 is the system default,
|
## encoded with a different character set than UTF-8 even if UTF-8 is the system default,
|
||||||
## which means when [displayUtf8] converts them to a string, the string may include gibberish.
|
## which means when [display] converts them to a string, the string may include gibberish.
|
||||||
## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863)
|
## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863)
|
||||||
##
|
##
|
||||||
## If you happen to know the [Charset] that was used to encode the path, you can use
|
## If you happen to know the `Charset` that was used to encode the path, you can use
|
||||||
## [toStrUsingCharset] instead of [displayUtf8].
|
## `toStrUsingCharset` instead of [display].
|
||||||
# displayUtf8 : Path -> Str
|
display : Path -> Str
|
||||||
# displayUtf8 = \path ->
|
display = \path ->
|
||||||
# when InternalPath.unwrap path is
|
when InternalPath.unwrap path is
|
||||||
# FromStr str -> str
|
FromStr str -> str
|
||||||
# FromOperatingSystem bytes | ArbitraryBytes bytes ->
|
FromOperatingSystem bytes | ArbitraryBytes bytes ->
|
||||||
# Str.displayUtf8 bytes
|
when Str.fromUtf8 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 _ -> "<22>"
|
||||||
|
|
||||||
# isEq : Path, Path -> Bool
|
# isEq : Path, Path -> Bool
|
||||||
# isEq = \p1, p2 ->
|
# isEq = \p1, p2 ->
|
||||||
# when InternalPath.unwrap p1 is
|
# when InternalPath.unwrap p1 is
|
||||||
|
|
|
@ -216,11 +216,76 @@ pub fn os_str_from_list(bytes: &RocList<u8>) -> &OsStr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn roc_fx_fileReadBytes(path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr> {
|
pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr> {
|
||||||
let path = path_from_roc_path(path);
|
use std::io::Read;
|
||||||
println!("TODO read bytes from {:?}", path);
|
|
||||||
|
|
||||||
RocResult::ok(RocList::empty())
|
let mut bytes = Vec::new();
|
||||||
|
|
||||||
|
match File::open(path_from_roc_path(roc_path)) {
|
||||||
|
Ok(mut file) => match file.read_to_end(&mut bytes) {
|
||||||
|
Ok(_bytes_read) => RocResult::ok(RocList::from(bytes.as_slice())),
|
||||||
|
Err(_) => {
|
||||||
|
todo!("Report a file write error");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
todo!("Report a file open error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn roc_fx_fileDelete(roc_path: &RocList<u8>) -> RocResult<(), ReadErr> {
|
||||||
|
match std::fs::remove_file(path_from_roc_path(roc_path)) {
|
||||||
|
Ok(()) => RocResult::ok(()),
|
||||||
|
Err(_) => {
|
||||||
|
todo!("Report a file write error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn roc_fx_cwd() -> RocList<u8> {
|
||||||
|
// TODO instead, call getcwd on UNIX and GetCurrentDirectory on Windows
|
||||||
|
match std::env::current_dir() {
|
||||||
|
Ok(path_buf) => os_str_to_roc_path(path_buf.into_os_string().as_os_str()),
|
||||||
|
Err(_) => {
|
||||||
|
// Default to empty path
|
||||||
|
RocList::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn roc_fx_dirList(
|
||||||
|
// TODO: this RocResult should use Dir.WriteErr - but right now it's File.WriteErr
|
||||||
|
// because glue doesn't have Dir.WriteErr yet.
|
||||||
|
roc_path: &RocList<u8>,
|
||||||
|
) -> RocResult<RocList<RocList<u8>>, WriteErr> {
|
||||||
|
println!("Dir.list...");
|
||||||
|
match std::fs::read_dir(path_from_roc_path(roc_path)) {
|
||||||
|
Ok(dir_entries) => RocResult::ok(
|
||||||
|
dir_entries
|
||||||
|
.map(|opt_dir_entry| match opt_dir_entry {
|
||||||
|
Ok(entry) => os_str_to_roc_path(entry.path().into_os_string().as_os_str()),
|
||||||
|
Err(_) => {
|
||||||
|
todo!("handle dir_entry path didn't resolve")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<RocList<RocList<u8>>>(),
|
||||||
|
),
|
||||||
|
Err(_) => {
|
||||||
|
todo!("handle Dir.list error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
/// TODO convert from EncodeWide to RocPath on Windows
|
||||||
|
fn os_str_to_roc_path(os_str: &OsStr) -> RocList<u8> {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
RocList::from(os_str.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|
|
@ -1,17 +1,30 @@
|
||||||
app "echo"
|
app "file-io"
|
||||||
packages { pf: "cli-platform/main.roc" }
|
packages { pf: "cli-platform/main.roc" }
|
||||||
imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path]
|
imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path, pf.Env, pf.Dir]
|
||||||
provides [main] to pf
|
provides [main] to pf
|
||||||
|
|
||||||
main : Task.Task {} [] [Write [File, Stdout, Stderr]]
|
main : Task.Task {} [] [Write [File, Stdout, Stderr], Read [File], Env]
|
||||||
main =
|
main =
|
||||||
|
path = Path.fromStr "out.txt"
|
||||||
task =
|
task =
|
||||||
|
cwd <- Env.cwd |> Task.await
|
||||||
|
cwdStr = Path.display cwd
|
||||||
|
|
||||||
|
_ <- Stdout.line "cwd: \(cwdStr)" |> Task.await
|
||||||
|
dirEntries <- Dir.list cwd |> Task.await
|
||||||
|
contentsStr = Str.joinWith (List.map dirEntries Path.display) "\n "
|
||||||
|
|
||||||
|
_ <- Stdout.line "Directory contents:\n \(contentsStr)\n" |> Task.await
|
||||||
_ <- Stdout.line "Writing a string to out.txt" |> Task.await
|
_ <- Stdout.line "Writing a string to out.txt" |> Task.await
|
||||||
File.writeUtf8 (Path.fromStr "out.txt") "a string!\n"
|
_ <- File.writeUtf8 path "a string!" |> Task.await
|
||||||
|
contents <- File.readUtf8 path |> Task.await
|
||||||
|
Stdout.line "I read the file back. Its contents: \"\(contents)\""
|
||||||
|
|
||||||
Task.attempt task \result ->
|
Task.attempt task \result ->
|
||||||
when result is
|
when result is
|
||||||
Err (FileWriteErr _ PermissionDenied) -> Stderr.line "Err: PermissionDenied"
|
Err (FileWriteErr _ PermissionDenied) -> Stderr.line "Err: PermissionDenied"
|
||||||
Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported"
|
Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported"
|
||||||
Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)"
|
Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)"
|
||||||
_ -> Stdout.line "Successfully wrote a string to out.txt"
|
Err (FileReadErr _ _) -> Stderr.line "Error reading file"
|
||||||
|
Err _ -> Stderr.line "Uh oh, there was an error!"
|
||||||
|
Ok _ -> Stdout.line "Successfully wrote a string to out.txt"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue