mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 07:14:46 +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]]
|
||||
name = "clap"
|
||||
version = "3.2.18"
|
||||
version = "3.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15f2ea93df33549dbe2e8eecd1ca55269d63ae0b3ba1f55db030817d1c2867f"
|
||||
checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
|
@ -2809,10 +2809,11 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
|||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
|
@ -3446,7 +3447,7 @@ name = "roc_cli"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.2.18",
|
||||
"clap 3.2.20",
|
||||
"cli_utils",
|
||||
"const_format",
|
||||
"criterion",
|
||||
|
@ -3591,7 +3592,7 @@ dependencies = [
|
|||
name = "roc_docs_cli"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap 3.2.18",
|
||||
"clap 3.2.20",
|
||||
"libc",
|
||||
"roc_docs",
|
||||
]
|
||||
|
@ -3740,7 +3741,7 @@ name = "roc_glue"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.2.18",
|
||||
"clap 3.2.20",
|
||||
"cli_utils",
|
||||
"dircpy",
|
||||
"fnv",
|
||||
|
@ -3759,6 +3760,7 @@ dependencies = [
|
|||
"roc_std",
|
||||
"roc_target",
|
||||
"roc_test_utils",
|
||||
"roc_tracing",
|
||||
"roc_types",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
|
@ -3807,7 +3809,7 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"bincode",
|
||||
"bumpalo",
|
||||
"clap 3.2.18",
|
||||
"clap 3.2.20",
|
||||
"iced-x86",
|
||||
"mach_object",
|
||||
"memmap2 0.5.7",
|
||||
|
@ -4365,9 +4367,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.139"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6"
|
||||
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -4405,9 +4407,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.139"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb"
|
||||
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -60,7 +60,7 @@ roc_editor = { path = "../editor", optional = true }
|
|||
roc_linker = { path = "../linker" }
|
||||
roc_repl_cli = { path = "../repl_cli", optional = true }
|
||||
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"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
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_module = { path = "../compiler/module" }
|
||||
roc_utils = { path = "../utils" }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
palette = "0.6.1"
|
||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
|
|
@ -33,6 +33,9 @@ interface Str
|
|||
toU8,
|
||||
toI8,
|
||||
toScalars,
|
||||
replaceEach,
|
||||
replaceFirst,
|
||||
replaceLast,
|
||||
splitFirst,
|
||||
splitLast,
|
||||
walkUtf8WithIndex,
|
||||
|
@ -276,6 +279,65 @@ countUtf8Bytes : Str -> Nat
|
|||
## string slice that does not do bounds checking or utf-8 verification
|
||||
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
|
||||
## 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"
|
||||
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
|
||||
49 STR_CAPACITY: "capacity"
|
||||
50 STR_REPLACE_EACH: "replaceEach"
|
||||
51 STR_REPLACE_FIRST: "replaceFirst"
|
||||
52 STR_REPLACE_LAST: "replaceLast"
|
||||
}
|
||||
6 LIST: "List" => {
|
||||
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.
|
||||
let mut package = roc_load::docs::Documentation {
|
||||
name: "roc/builtins".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
name: "documentation".to_string(),
|
||||
version: "".to_string(),
|
||||
docs: "Package introduction or README.".to_string(),
|
||||
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(
|
||||
html_to_string(
|
||||
|
@ -477,60 +477,65 @@ fn new_line(buf: &mut String) {
|
|||
}
|
||||
|
||||
// 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);
|
||||
match type_ann {
|
||||
TypeAnnotation::TagUnion { tags, extension } => {
|
||||
let tags_len = tags.len();
|
||||
if tags.is_empty() {
|
||||
buf.push_str("[]");
|
||||
} else {
|
||||
let tags_len = tags.len();
|
||||
|
||||
let tag_union_indent = indent_level + 1;
|
||||
|
||||
if is_multiline {
|
||||
new_line(buf);
|
||||
|
||||
indent(buf, tag_union_indent);
|
||||
}
|
||||
|
||||
buf.push('[');
|
||||
|
||||
if is_multiline {
|
||||
new_line(buf);
|
||||
}
|
||||
|
||||
let next_indent_level = tag_union_indent + 1;
|
||||
|
||||
for (index, tag) in tags.iter().enumerate() {
|
||||
if is_multiline {
|
||||
indent(buf, next_indent_level);
|
||||
} else {
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
buf.push_str(tag.name.as_str());
|
||||
|
||||
for type_value in &tag.values {
|
||||
buf.push(' ');
|
||||
type_annotation_to_html(next_indent_level, buf, type_value);
|
||||
}
|
||||
let tag_union_indent = indent_level + 1;
|
||||
|
||||
if is_multiline {
|
||||
if index < (tags_len - 1) {
|
||||
buf.push(',');
|
||||
}
|
||||
new_line(buf);
|
||||
|
||||
indent(buf, tag_union_indent);
|
||||
}
|
||||
|
||||
buf.push('[');
|
||||
|
||||
if is_multiline {
|
||||
new_line(buf);
|
||||
}
|
||||
|
||||
let next_indent_level = tag_union_indent + 1;
|
||||
|
||||
for (index, tag) in tags.iter().enumerate() {
|
||||
if is_multiline {
|
||||
indent(buf, next_indent_level);
|
||||
}
|
||||
|
||||
buf.push_str(tag.name.as_str());
|
||||
|
||||
for type_value in &tag.values {
|
||||
buf.push(' ');
|
||||
type_annotation_to_html(next_indent_level, buf, type_value, true);
|
||||
}
|
||||
|
||||
if is_multiline {
|
||||
if index < (tags_len - 1) {
|
||||
buf.push(',');
|
||||
}
|
||||
|
||||
new_line(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if is_multiline {
|
||||
indent(buf, tag_union_indent);
|
||||
}
|
||||
|
||||
buf.push(']');
|
||||
}
|
||||
|
||||
if is_multiline {
|
||||
indent(buf, tag_union_indent);
|
||||
} else {
|
||||
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) => {
|
||||
buf.push_str(var_name);
|
||||
|
@ -539,82 +544,91 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ
|
|||
if parts.is_empty() {
|
||||
buf.push_str(name);
|
||||
} else {
|
||||
buf.push('(');
|
||||
if needs_parens {
|
||||
buf.push('(');
|
||||
}
|
||||
|
||||
buf.push_str(name);
|
||||
for part in parts {
|
||||
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 } => {
|
||||
let fields_len = fields.len();
|
||||
if fields.is_empty() {
|
||||
buf.push_str("{}");
|
||||
} else {
|
||||
let fields_len = fields.len();
|
||||
let record_indent = indent_level + 1;
|
||||
|
||||
let record_indent = indent_level + 1;
|
||||
|
||||
if is_multiline {
|
||||
new_line(buf);
|
||||
indent(buf, record_indent);
|
||||
}
|
||||
|
||||
buf.push('{');
|
||||
|
||||
if is_multiline {
|
||||
new_line(buf);
|
||||
}
|
||||
|
||||
let next_indent_level = record_indent + 1;
|
||||
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
if is_multiline {
|
||||
indent(buf, next_indent_level);
|
||||
new_line(buf);
|
||||
indent(buf, record_indent);
|
||||
}
|
||||
|
||||
buf.push('{');
|
||||
|
||||
if is_multiline {
|
||||
new_line(buf);
|
||||
}
|
||||
|
||||
let next_indent_level = record_indent + 1;
|
||||
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
if is_multiline {
|
||||
indent(buf, next_indent_level);
|
||||
} else {
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
let fields_name = match field {
|
||||
RecordField::RecordField { name, .. } => name,
|
||||
RecordField::OptionalField { name, .. } => name,
|
||||
RecordField::LabelOnly { name } => name,
|
||||
};
|
||||
|
||||
buf.push_str(fields_name.as_str());
|
||||
|
||||
match field {
|
||||
RecordField::RecordField {
|
||||
type_annotation, ..
|
||||
} => {
|
||||
buf.push_str(" : ");
|
||||
type_annotation_to_html(next_indent_level, buf, type_annotation, false);
|
||||
}
|
||||
RecordField::OptionalField {
|
||||
type_annotation, ..
|
||||
} => {
|
||||
buf.push_str(" ? ");
|
||||
type_annotation_to_html(next_indent_level, buf, type_annotation, false);
|
||||
}
|
||||
RecordField::LabelOnly { .. } => {}
|
||||
}
|
||||
|
||||
if is_multiline {
|
||||
if index < (fields_len - 1) {
|
||||
buf.push(',');
|
||||
}
|
||||
|
||||
new_line(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if is_multiline {
|
||||
indent(buf, record_indent);
|
||||
} else {
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
let fields_name = match field {
|
||||
RecordField::RecordField { name, .. } => name,
|
||||
RecordField::OptionalField { name, .. } => name,
|
||||
RecordField::LabelOnly { name } => name,
|
||||
};
|
||||
|
||||
buf.push_str(fields_name.as_str());
|
||||
|
||||
match field {
|
||||
RecordField::RecordField {
|
||||
type_annotation, ..
|
||||
} => {
|
||||
buf.push_str(" : ");
|
||||
type_annotation_to_html(next_indent_level, buf, type_annotation);
|
||||
}
|
||||
RecordField::OptionalField {
|
||||
type_annotation, ..
|
||||
} => {
|
||||
buf.push_str(" ? ");
|
||||
type_annotation_to_html(next_indent_level, buf, type_annotation);
|
||||
}
|
||||
RecordField::LabelOnly { .. } => {}
|
||||
}
|
||||
|
||||
if is_multiline {
|
||||
if index < (fields_len - 1) {
|
||||
buf.push(',');
|
||||
}
|
||||
|
||||
new_line(buf);
|
||||
}
|
||||
buf.push('}');
|
||||
}
|
||||
|
||||
if is_multiline {
|
||||
indent(buf, record_indent);
|
||||
} else {
|
||||
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 } => {
|
||||
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);
|
||||
}
|
||||
|
||||
type_annotation_to_html(indent_level, buf, arg);
|
||||
type_annotation_to_html(indent_level, buf, arg, false);
|
||||
|
||||
if peekable_args.peek().is_some() {
|
||||
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;
|
||||
}
|
||||
|
||||
type_annotation_to_html(next_indent_level, buf, output);
|
||||
type_annotation_to_html(next_indent_level, buf, output, false);
|
||||
}
|
||||
TypeAnnotation::Ability { members: _ } => {
|
||||
// TODO(abilities): fill me in
|
||||
|
|
|
@ -18,7 +18,7 @@ bench = false
|
|||
|
||||
[dependencies]
|
||||
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]
|
||||
libc = "0.2.132"
|
||||
|
|
|
@ -47,14 +47,14 @@ futures = "0.3.24"
|
|||
cgmath = "0.18.0"
|
||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||
colored = "2.0.0"
|
||||
pest = "2.1.3"
|
||||
pest = "2.3.1"
|
||||
pest_derive = "2.1.0"
|
||||
copypasta = "0.8.1"
|
||||
palette = "0.6.1"
|
||||
confy = { git = 'https://github.com/rust-cli/confy', features = [
|
||||
"yaml_conf"
|
||||
], default-features = false }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
nonempty = "0.8.0"
|
||||
fs_extra = "1.2.0"
|
||||
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_target = { path = "../compiler/roc_target" }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
roc_tracing = { path = "../tracing" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
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_macros = "0.24"
|
||||
indexmap = "1.8.1"
|
||||
|
|
|
@ -65,24 +65,45 @@ impl Types {
|
|||
}
|
||||
|
||||
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::*;
|
||||
|
||||
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) {
|
||||
(RocStr, RocStr) | (Bool, Bool) | (EmptyTagUnion, EmptyTagUnion) | (Unit, Unit) => true,
|
||||
(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(self.get_type(*err_a), self.get_type(*err_b))
|
||||
self.is_equivalent_help(
|
||||
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,
|
||||
(RocList(elem_a), RocList(elem_b))
|
||||
| (RocSet(elem_a), RocSet(elem_b))
|
||||
| (RocBox(elem_a), RocBox(elem_b))
|
||||
| (RecursivePointer(elem_a), RecursivePointer(elem_b)) => {
|
||||
self.is_equivalent(self.get_type(*elem_a), self.get_type(*elem_b))
|
||||
}
|
||||
| (RecursivePointer(elem_a), RecursivePointer(elem_b)) => self.is_equivalent_help(
|
||||
self.get_type_or_pending(*elem_a),
|
||||
self.get_type_or_pending(*elem_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(self.get_type(*val_a), self.get_type(*val_b))
|
||||
self.is_equivalent_help(
|
||||
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)) => {
|
||||
use RocTagUnion::*;
|
||||
|
@ -113,8 +134,10 @@ impl Types {
|
|||
},
|
||||
) => {
|
||||
tag_name_a == tag_name_b
|
||||
&& self
|
||||
.is_equivalent(self.get_type(*payload_a), self.get_type(*payload_b))
|
||||
&& self.is_equivalent_help(
|
||||
self.get_type_or_pending(*payload_a),
|
||||
self.get_type_or_pending(*payload_b),
|
||||
)
|
||||
}
|
||||
(Enumeration { tags: tags_a, .. }, Enumeration { tags: tags_b, .. }) => {
|
||||
tags_a == tags_b
|
||||
|
@ -157,9 +180,9 @@ impl Types {
|
|||
|((name_a, opt_id_a), (name_b, opt_id_b))| {
|
||||
name_a == name_b
|
||||
&& match (opt_id_a, opt_id_b) {
|
||||
(Some(id_a), Some(id_b)) => self.is_equivalent(
|
||||
self.get_type(*id_a),
|
||||
self.get_type(*id_b),
|
||||
(Some(id_a), Some(id_b)) => self.is_equivalent_help(
|
||||
self.get_type_or_pending(*id_a),
|
||||
self.get_type_or_pending(*id_b),
|
||||
),
|
||||
(None, None) => true,
|
||||
(None, Some(_)) | (Some(_), None) => false,
|
||||
|
@ -179,9 +202,9 @@ impl Types {
|
|||
|((name_a, opt_id_a), (name_b, opt_id_b))| {
|
||||
name_a == name_b
|
||||
&& match (opt_id_a, opt_id_b) {
|
||||
(Some(id_a), Some(id_b)) => self.is_equivalent(
|
||||
self.get_type(*id_a),
|
||||
self.get_type(*id_b),
|
||||
(Some(id_a), Some(id_b)) => self.is_equivalent_help(
|
||||
self.get_type_or_pending(*id_a),
|
||||
self.get_type_or_pending(*id_b),
|
||||
),
|
||||
(None, None) => true,
|
||||
(None, Some(_)) | (Some(_), None) => false,
|
||||
|
@ -241,7 +264,10 @@ impl Types {
|
|||
.zip(fields_b.iter())
|
||||
.all(|((name_a, id_a), (name_b, id_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 {
|
||||
false
|
||||
|
@ -261,7 +287,10 @@ impl Types {
|
|||
.zip(fields_b.iter())
|
||||
.all(|((name_a, id_a), (name_b, id_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 {
|
||||
false
|
||||
|
@ -283,10 +312,16 @@ impl Types {
|
|||
// with the same type could have completely different implementations!
|
||||
if name_a == name_b
|
||||
&& 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)| {
|
||||
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 {
|
||||
false
|
||||
|
@ -359,6 +394,12 @@ impl Types {
|
|||
typ: RocType,
|
||||
layout: Layout<'a>,
|
||||
) -> 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());
|
||||
|
||||
assert!(id.0 <= TypeId::MAX.0);
|
||||
|
@ -379,7 +420,15 @@ impl Types {
|
|||
pub fn get_type(&self, id: TypeId) -> &RocType {
|
||||
match self.types.get(id.0) {
|
||||
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)]
|
||||
pub enum RocType {
|
||||
RocStr,
|
||||
|
@ -653,6 +708,8 @@ impl<'a> Env<'a> {
|
|||
}
|
||||
|
||||
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
|
||||
.layout_cache
|
||||
.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#"
|
||||
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>) {
|
||||
|
|
|
@ -17,12 +17,12 @@ roc_build = { path = "../compiler/build" }
|
|||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
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"] }
|
||||
memmap2 = "0.5.7"
|
||||
object = { version = "0.29.0", features = ["read", "write"] }
|
||||
mach_object = "0.1"
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
bincode = "1.3.3"
|
||||
target-lexicon = "0.12.3"
|
||||
tempfile = "3.2.0"
|
||||
|
|
1
examples/interactive/.gitignore
vendored
1
examples/interactive/.gitignore
vendored
|
@ -5,3 +5,4 @@ effects
|
|||
form
|
||||
tui
|
||||
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,
|
||||
forever,
|
||||
loop,
|
||||
dirList,
|
||||
cwd,
|
||||
stdoutLine,
|
||||
stderrLine,
|
||||
stdinLine,
|
||||
sendRequest,
|
||||
fileReadBytes,
|
||||
fileDelete,
|
||||
fileWriteUtf8,
|
||||
fileWriteBytes,
|
||||
]
|
||||
imports [InternalHttp.{ Request, Response }, InternalFile]
|
||||
imports [InternalHttp.{ Request, Response }, InternalFile, InternalDir]
|
||||
generates Effect with [after, map, always, forever, loop]
|
||||
|
||||
stdoutLine : Str -> Effect {}
|
||||
|
@ -23,6 +26,10 @@ stdinLine : Effect Str
|
|||
|
||||
fileWriteBytes : List U8, List U8 -> 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)
|
||||
dirList : List U8 -> Effect (Result (List (List U8)) InternalDir.ReadErr)
|
||||
|
||||
cwd : Effect (List U8)
|
||||
|
||||
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
|
||||
exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes]
|
||||
imports [Effect, Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath]
|
||||
exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes, delete]
|
||||
imports [Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath, Effect.{ Effect }]
|
||||
|
||||
ReadErr : InternalFile.ReadErr
|
||||
|
||||
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)
|
||||
## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format
|
||||
## 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
|
||||
writeBytes path bytes
|
||||
|
||||
## Write bytes to a file.
|
||||
## Writes bytes to a file.
|
||||
##
|
||||
## # Writes the bytes 1, 2, 3 to the file `myfile.dat`.
|
||||
## 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.
|
||||
writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
||||
writeBytes = \path, bytes ->
|
||||
InternalPath.toBytes path
|
||||
|> Effect.fileWriteBytes bytes
|
||||
|> InternalTask.fromEffect
|
||||
|> Task.mapFail \err -> FileWriteErr path err
|
||||
toWriteTask path \pathBytes -> Effect.fileWriteBytes pathBytes bytes
|
||||
|
||||
## 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`.
|
||||
## 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.
|
||||
writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
||||
writeUtf8 = \path, str ->
|
||||
InternalPath.toBytes path
|
||||
|> Effect.fileWriteUtf8 str
|
||||
|> InternalTask.fromEffect
|
||||
|> Task.mapFail \err -> FileWriteErr path err
|
||||
toWriteTask path \bytes -> Effect.fileWriteUtf8 bytes str
|
||||
|
||||
## 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`.
|
||||
## 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.
|
||||
readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]* [Read [File]*]*
|
||||
readBytes = \path ->
|
||||
InternalPath.toBytes path
|
||||
|> Effect.fileReadBytes
|
||||
|> InternalTask.fromEffect
|
||||
|> Task.mapFail \err -> FileReadErr path err
|
||||
toReadTask path \bytes -> Effect.fileReadBytes bytes
|
||||
|
||||
## 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`.
|
||||
## File.readUtf8 (Path.fromStr "myfile.txt")
|
||||
|
@ -119,3 +132,16 @@ readUtf8 = \path ->
|
|||
# Err decodingErr -> Err (FileReadDecodeErr decodingErr)
|
||||
# Err readErr -> Err (FileReadErr readErr)
|
||||
# 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,
|
||||
unwrap,
|
||||
toBytes,
|
||||
fromArbitraryBytes,
|
||||
fromOsBytes,
|
||||
]
|
||||
imports []
|
||||
|
||||
|
@ -61,3 +63,11 @@ toBytes = \@InternalPath path ->
|
|||
FromOperatingSystem bytes -> bytes
|
||||
ArbitraryBytes bytes -> bytes
|
||||
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,
|
||||
# toComponents,
|
||||
# walkComponents,
|
||||
display,
|
||||
fromStr,
|
||||
fromBytes,
|
||||
withExtension,
|
||||
|
@ -82,13 +83,13 @@ fromBytes = \bytes ->
|
|||
## 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
|
||||
## 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
|
||||
## [displayUtf8].
|
||||
## [display].
|
||||
# toInner : Path -> [Str Str, Bytes (List U8)]
|
||||
## 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,
|
||||
## 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
|
||||
## 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,
|
||||
## 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)
|
||||
##
|
||||
## If you happen to know the [Charset] that was used to encode the path, you can use
|
||||
## [toStrUsingCharset] instead of [displayUtf8].
|
||||
# displayUtf8 : Path -> Str
|
||||
# displayUtf8 = \path ->
|
||||
# when InternalPath.unwrap path is
|
||||
# FromStr str -> str
|
||||
# FromOperatingSystem bytes | ArbitraryBytes bytes ->
|
||||
# Str.displayUtf8 bytes
|
||||
## If you happen to know the `Charset` that was used to encode the path, you can use
|
||||
## `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
|
||||
# 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 = \p1, p2 ->
|
||||
# when InternalPath.unwrap p1 is
|
||||
|
|
|
@ -216,11 +216,76 @@ pub fn os_str_from_list(bytes: &RocList<u8>) -> &OsStr {
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_fileReadBytes(path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr> {
|
||||
let path = path_from_roc_path(path);
|
||||
println!("TODO read bytes from {:?}", path);
|
||||
pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr> {
|
||||
use std::io::Read;
|
||||
|
||||
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]
|
||||
|
|
|
@ -1,17 +1,30 @@
|
|||
app "echo"
|
||||
app "file-io"
|
||||
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
|
||||
|
||||
main : Task.Task {} [] [Write [File, Stdout, Stderr]]
|
||||
main : Task.Task {} [] [Write [File, Stdout, Stderr], Read [File], Env]
|
||||
main =
|
||||
path = Path.fromStr "out.txt"
|
||||
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
|
||||
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 ->
|
||||
when result is
|
||||
Err (FileWriteErr _ PermissionDenied) -> Stderr.line "Err: PermissionDenied"
|
||||
Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported"
|
||||
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