Merge remote-tracking branch 'origin/main' into roc-dev-inline-expects

This commit is contained in:
Folkert 2022-10-21 23:05:34 +02:00
commit ebac056814
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
67 changed files with 851 additions and 1952 deletions

View file

@ -1,4 +1,7 @@
on: [pull_request]
on:
pull_request:
schedule:
- cron: '0 9 * * *' # 9=9am utc+0
name: Check Markdown links
@ -17,3 +20,12 @@ jobs:
use-verbose-mode: 'yes'
check-modified-files-only: 'yes'
base-branch: 'main'
check-modified-files-only: 'yes'
if: ${{ github.event_name == 'pull_request' }}
- uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: 'yes'
use-verbose-mode: 'yes'
base-branch: 'main'
check-modified-files-only: 'no'
if: ${{ github.event_name == 'schedule' }}

View file

@ -29,7 +29,7 @@ jobs:
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_apple_silicon-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_12_apple_silicon-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
- name: write version to file
run: ./ci/write_version.sh

View file

@ -40,7 +40,7 @@ Execute `cargo fmt --all` to fix the formatting.
- You can find good first issues [here][good-first-issues].
- [Fork](https://github.com/roc-lang/roc/fork) the repo so that you can apply your changes first on your own copy of the roc repo.
- It's a good idea to open a draft pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Click the button "ready for review" when it's ready.
- All your commits need to be signed to prevent impersonation:
- All your commits need to be signed [to prevent impersonation](https://dev.to/martiliones/how-i-got-linus-torvalds-in-my-contributors-on-github-3k4g):
1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below.
2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key).
3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)

View file

@ -1405,6 +1405,27 @@ imports [pf.Stdout, pf.Program, AdditionalModule, AnotherModule]
provides main to pf
```
## Comments
Comments that begin with `##` will be included in generated documentation (```roc docs```). They require a single space after the `##`, and can include code blocks by adding five spaces after `##`.
```coffee
## This is a comment for documentation, and includes a code block.
##
## x = 2
## expect x == 2
```
Roc also supports inline comments and line comments with `#`. They can be used to add information that won't be included in documentation.
```coffee
# This is a line comment that won't appear in documentation.
myFunction : U8 -> U8
myFunction = \bit -> bit % 2 # this is an inline comment
```
Roc does not have multiline comment syntax.
## Tasks
Tasks are technically not part of the Roc language, but they're very common in

View file

@ -1,4 +1,4 @@
#!/usr/bin/env bash
cp target/release/roc ./roc # to be able to exclude "target" later in the tar command
cp -r target/release/lib ./lib
tar -czvf $1 --exclude="target" --exclude="zig-cache" roc lib LICENSE LEGAL_DETAILS examples/helloWorld.roc examples/cli crates/roc_std
tar -czvf $1 --exclude="target" --exclude="zig-cache" roc lib LICENSE LEGAL_DETAILS examples/helloWorld.roc examples/platform-switching examples/cli crates/roc_std

View file

@ -401,7 +401,7 @@ mod cli_run {
// uses C platform
fn platform_switching_main() {
test_roc_app_slim(
"crates/cli_testing_examples/platform-switching",
"examples/platform-switching",
"main.roc",
"rocLovesPlatforms",
"Which platform am I running on now?\n",
@ -416,7 +416,7 @@ mod cli_run {
#[test]
fn platform_switching_rust() {
test_roc_app_slim(
"crates/cli_testing_examples/platform-switching",
"examples/platform-switching",
"rocLovesRust.roc",
"rocLovesRust",
"Roc <3 Rust!\n",
@ -427,7 +427,7 @@ mod cli_run {
#[test]
fn platform_switching_zig() {
test_roc_app_slim(
"crates/cli_testing_examples/platform-switching",
"examples/platform-switching",
"rocLovesZig.roc",
"rocLovesZig",
"Roc <3 Zig!\n",
@ -438,7 +438,7 @@ mod cli_run {
#[test]
fn platform_switching_wasm() {
test_roc_app_slim(
"crates/cli_testing_examples/platform-switching",
"examples/platform-switching",
"rocLovesWebAssembly.roc",
"rocLovesWebAssembly",
"Roc <3 Web Assembly!\n",
@ -449,7 +449,7 @@ mod cli_run {
#[test]
fn platform_switching_swift() {
test_roc_app_slim(
"crates/cli_testing_examples/platform-switching",
"examples/platform-switching",
"rocLovesSwift.roc",
"rocLovesSwift",
"Roc <3 Swift!\n",

File diff suppressed because it is too large Load diff

View file

@ -201,9 +201,9 @@ pub fn build_zig_host_native(
if let Some(shared_lib_path) = shared_lib_path {
command.args(&[
"build-exe",
"-fPIE",
// "-fPIE", PIE seems to fail on windows
shared_lib_path.to_str().unwrap(),
&bitcode::get_builtins_host_obj_path(),
&bitcode::get_builtins_windows_obj_path(),
]);
} else {
command.args(&["build-obj", "-fPIC"]);
@ -221,6 +221,7 @@ pub fn build_zig_host_native(
// include libc
"--library",
"c",
"-dynamic",
// cross-compile?
"-target",
target,

View file

@ -745,37 +745,54 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
} else if (list_a.isUnique()) {
const total_length: usize = list_a.len() + list_b.len();
if (list_a.bytes) |source| {
const new_source = if (list_a.capacity >= total_length)
source
else
utils.unsafeReallocate(
source,
alignment,
list_a.len(),
total_length,
element_width,
);
const resized_list_a = list_a.reallocate(alignment, total_length, element_width);
if (list_b.bytes) |source_b| {
@memcpy(new_source + list_a.len() * element_width, source_b, list_b.len() * element_width);
}
// These must exist, otherwise, the lists would have been empty.
const source_a = resized_list_a.bytes orelse unreachable;
const source_b = list_b.bytes orelse unreachable;
@memcpy(source_a + list_a.len() * element_width, source_b, list_b.len() * element_width);
return RocList{ .bytes = new_source, .length = total_length, .capacity = total_length };
}
// decrement list b.
utils.decref(source_b, list_b.len(), alignment);
return resized_list_a;
} else if (list_b.isUnique()) {
const total_length: usize = list_a.len() + list_b.len();
const resized_list_b = list_b.reallocate(alignment, total_length, element_width);
// These must exist, otherwise, the lists would have been empty.
const source_a = list_a.bytes orelse unreachable;
const source_b = resized_list_b.bytes orelse unreachable;
// This is a bit special, we need to first copy the elements of list_b to the end,
// then copy the elements of list_a to the beginning.
// This first call must use mem.copy because the slices might overlap.
const byte_count_a = list_a.len() * element_width;
const byte_count_b = list_b.len() * element_width;
mem.copy(u8, source_b[byte_count_a .. byte_count_a + byte_count_b], source_b[0..byte_count_b]);
@memcpy(source_b, source_a, byte_count_a);
// decrement list a.
utils.decref(source_a, list_a.len(), alignment);
return resized_list_b;
}
const total_length: usize = list_a.len() + list_b.len();
const output = RocList.allocate(alignment, total_length, element_width);
if (output.bytes) |target| {
if (list_a.bytes) |source| {
@memcpy(target, source, list_a.len() * element_width);
}
if (list_b.bytes) |source| {
@memcpy(target + list_a.len() * element_width, source, list_b.len() * element_width);
}
}
// These must exist, otherwise, the lists would have been empty.
const target = output.bytes orelse unreachable;
const source_a = list_a.bytes orelse unreachable;
const source_b = list_b.bytes orelse unreachable;
@memcpy(target, source_a, list_a.len() * element_width);
@memcpy(target + list_a.len() * element_width, source_b, list_b.len() * element_width);
// decrement list a and b.
utils.decref(source_a, list_a.len(), alignment);
utils.decref(source_b, list_b.len(), alignment);
return output;
}

View file

@ -224,11 +224,6 @@ LowLevelHasher := { originalSeed : U64, state : U64 } has [
addU32,
addU64,
addU128,
addI8,
addI16,
addI32,
addI64,
addI128,
complete,
},
]
@ -250,17 +245,6 @@ combineState = \@LowLevelHasher { originalSeed, state }, { a, b, seed, length }
complete = \@LowLevelHasher { state } -> state
addI8 = \hasher, i8 ->
addU8 hasher (Num.toU8 i8)
addI16 = \hasher, i16 ->
addU16 hasher (Num.toU16 i16)
addI32 = \hasher, i32 ->
addU32 hasher (Num.toU32 i32)
addI64 = \hasher, i64 ->
addU64 hasher (Num.toU64 i64)
addI128 = \hasher, i128 ->
addU128 hasher (Num.toU128 i128)
# These implementations hash each value individually with the seed and then mix
# the resulting hash with the state. There are other options that may be faster
# like using the output of the last hash as the seed to the current hash.

View file

@ -9,11 +9,11 @@ interface Hash
addU32,
addU64,
addU128,
addI8,
addI16,
addI32,
addI64,
addI128,
hashI8,
hashI16,
hashI32,
hashI64,
hashI128,
complete,
hashStrBytes,
hashList,
@ -55,21 +55,6 @@ Hasher has
## Adds a single U128 to the hasher.
addU128 : a, U128 -> a | a has Hasher
## Adds a single I8 to the hasher.
addI8 : a, I8 -> a | a has Hasher
## Adds a single I16 to the hasher.
addI16 : a, I16 -> a | a has Hasher
## Adds a single I32 to the hasher.
addI32 : a, I32 -> a | a has Hasher
## Adds a single I64 to the hasher.
addI64 : a, I64 -> a | a has Hasher
## Adds a single I128 to the hasher.
addI128 : a, I128 -> a | a has Hasher
## Completes the hasher, extracting a hash value from its
## accumulated hash state.
complete : a -> U64 | a has Hasher
@ -83,6 +68,26 @@ hashList = \hasher, lst ->
List.walk lst hasher \accumHasher, elem ->
hash accumHasher elem
## Adds a single I8 to a hasher.
hashI8 : a, I8 -> a | a has Hasher
hashI8 = \hasher, n -> addU8 hasher (Num.toU8 n)
## Adds a single I16 to a hasher.
hashI16 : a, I16 -> a | a has Hasher
hashI16 = \hasher, n -> addU16 hasher (Num.toU16 n)
## Adds a single I32 to a hasher.
hashI32 : a, I32 -> a | a has Hasher
hashI32 = \hasher, n -> addU32 hasher (Num.toU32 n)
## Adds a single I64 to a hasher.
hashI64 : a, I64 -> a | a has Hasher
hashI64 = \hasher, n -> addU64 hasher (Num.toU64 n)
## Adds a single I128 to a hasher.
hashI128 : a, I128 -> a | a has Hasher
hashI128 = \hasher, n -> addU128 hasher (Num.toU128 n)
## Adds a container of [Hash]able elements to a [Hasher] by hashing each element.
## The container is iterated using the walk method passed in.
## The order of the elements does not affect the final hash.

View file

@ -29,6 +29,8 @@ interface List
map3,
product,
walkUntil,
walkFrom,
walkFromUntil,
range,
sortWith,
drop,
@ -443,6 +445,22 @@ walkBackwardsUntil = \list, initial, func ->
Continue new -> new
Break new -> new
## Walks to the end of the list from a specified starting index
walkFrom : List elem, Nat, state, (state, elem -> state) -> state
walkFrom = \list, index, state, func ->
walkHelp : _, _ -> [Continue _, Break []]
walkHelp = \currentState, element -> Continue (func currentState element)
when List.iterHelp list state walkHelp index (List.len list) is
Continue new -> new
## A combination of [List.walkFrom] and [List.walkUntil]
walkFromUntil : List elem, Nat, state, (state, elem -> [Continue state, Break state]) -> state
walkFromUntil = \list, index, state, func ->
when List.iterHelp list state func index (List.len list) is
Continue new -> new
Break new -> new
sum : List (Num a) -> Num a
sum = \list ->
List.walk list 0 Num.add

View file

@ -314,7 +314,10 @@ fn to_encoder_record(
record_var,
ext_var: env.subs.fresh_unnamed_flex_var(),
field_var,
loc_expr: Box::new(Loc::at_zero(Var(rcd_sym, record_var))),
loc_expr: Box::new(Loc::at_zero(Var(
rcd_sym,
env.subs.fresh_unnamed_flex_var(),
))),
field: field_name,
};

View file

@ -99,7 +99,10 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (V
record_var,
field_var,
ext_var: env.subs.fresh_unnamed_flex_var(),
loc_expr: Box::new(Loc::at_zero(Expr::Var(rcd_sym, record_var))),
loc_expr: Box::new(Loc::at_zero(Expr::Var(
rcd_sym,
env.subs.fresh_unnamed_flex_var(),
))),
field: field_name,
};

View file

@ -123,19 +123,19 @@ impl FlatHash {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U128))
}
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I8))
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I8))
}
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I16))
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I16))
}
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I32))
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I32))
}
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I64))
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I64))
}
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I128))
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I128))
}
// NB: I believe it is okay to unwrap opaques here because derivers are only used
// by the backend, and the backend treats opaques like structural aliases.

View file

@ -3492,6 +3492,65 @@ mod test_fmt {
));
}
#[test]
fn def_when() {
expr_formats_same(indoc!(
r#"
myLongFunctionName = \x ->
when b is
1 | 2 ->
when c is
6 | 7 ->
8
3 | 4 ->
5
123
"#
));
}
#[test]
#[ignore] // TODO: reformat when-in-function-body with extra newline
fn def_when_with_python_indentation() {
expr_formats_to(
// vvv Currently this input formats to _itself_ :( vvv
// Instead, if the body of the `when` is multiline (the overwhelmingly common case)
// we want to make sure the `when` is at the beginning of the line, inserting
// a newline if necessary.
indoc!(
r#"
myLongFunctionName = \x -> when b is
1 | 2 ->
when c is
6 | 7 ->
8
3 | 4 ->
5
123
"#
),
indoc!(
r#"
myLongFunctionName = \x ->
when b is
1 | 2 ->
when c is
6 | 7 ->
8
3 | 4 ->
5
123
"#
),
);
}
#[test]
fn when_with_alternatives_1() {
expr_formats_same(indoc!(

View file

@ -1398,6 +1398,8 @@ define_builtins! {
75 LIST_WALK_TRY: "walkTry"
76 LIST_WALK_BACKWARDS_UNTIL: "walkBackwardsUntil"
77 LIST_COUNT_IF: "countIf"
78 LIST_WALK_FROM: "walkFrom"
79 LIST_WALK_FROM_UNTIL: "walkFromUntil"
}
7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias
@ -1526,11 +1528,11 @@ define_builtins! {
6 HASH_ADD_U32: "addU32"
7 HASH_ADD_U64: "addU64"
8 HASH_ADD_U128: "addU128"
9 HASH_ADD_I8: "addI8"
10 HASH_ADD_I16: "addI16"
11 HASH_ADD_I32: "addI32"
12 HASH_ADD_I64: "addI64"
13 HASH_ADD_I128: "addI128"
9 HASH_HASH_I8: "hashI8"
10 HASH_HASH_I16: "hashI16"
11 HASH_HASH_I32: "hashI32"
12 HASH_HASH_I64: "hashI64"
13 HASH_HASH_I128: "hashI128"
14 HASH_COMPLETE: "complete"
15 HASH_HASH_STR_BYTES: "hashStrBytes"
16 HASH_HASH_LIST: "hashList"

View file

@ -2080,7 +2080,7 @@ mod when {
/// Parsing when with indentation.
fn when_with_indent<'a>() -> impl Parser<'a, u32, EWhen<'a>> {
move |arena, state: State<'a>| {
let min_indent = state.column();
let min_indent = state.line_indent() + 1;
parser::keyword_e(keyword::WHEN, EWhen::When)
.parse(arena, state)
.map(|(progress, (), state)| (progress, min_indent, state))

View file

@ -0,0 +1,64 @@
Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-33,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier(
"func",
),
@7-33 Closure(
[
@8-9 Identifier(
"x",
),
],
@13-33 When(
@18-19 Var {
module_name: "",
ident: "n",
},
[
WhenBranch {
patterns: [
@27-28 SpaceBefore(
NumLiteral(
"0",
),
[
Newline,
],
),
],
value: @32-33 Num(
"0",
),
guard: None,
},
],
),
),
),
],
},
@34-36 SpaceBefore(
Num(
"42",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,3 @@
func = \x -> when n is
0 -> 0
42

View file

@ -284,6 +284,7 @@ mod test_parse {
pass/when_if_guard.expr,
pass/when_in_assignment.expr,
pass/when_in_function.expr,
pass/when_in_function_python_style_indent.expr,
pass/when_in_parens_indented.expr,
pass/when_in_parens.expr,
pass/when_with_alternative_patterns.expr,

View file

@ -66,11 +66,11 @@ fn immediates() {
check_single_lset_immediate(Hash, v!(U32), Symbol::HASH_ADD_U32);
check_single_lset_immediate(Hash, v!(U64), Symbol::HASH_ADD_U64);
check_single_lset_immediate(Hash, v!(U128), Symbol::HASH_ADD_U128);
check_single_lset_immediate(Hash, v!(I8), Symbol::HASH_ADD_I8);
check_single_lset_immediate(Hash, v!(I16), Symbol::HASH_ADD_I16);
check_single_lset_immediate(Hash, v!(I32), Symbol::HASH_ADD_I32);
check_single_lset_immediate(Hash, v!(I64), Symbol::HASH_ADD_I64);
check_single_lset_immediate(Hash, v!(I128), Symbol::HASH_ADD_I128);
check_single_lset_immediate(Hash, v!(I8), Symbol::HASH_HASH_I8);
check_single_lset_immediate(Hash, v!(I16), Symbol::HASH_HASH_I16);
check_single_lset_immediate(Hash, v!(I32), Symbol::HASH_HASH_I32);
check_single_lset_immediate(Hash, v!(I64), Symbol::HASH_HASH_I64);
check_single_lset_immediate(Hash, v!(I128), Symbol::HASH_HASH_I128);
check_single_lset_immediate(Hash, v!(STR), Symbol::HASH_HASH_STR_BYTES);
check_single_lset_immediate(Hash, v!(Symbol::LIST_LIST v!(U8)), Symbol::HASH_HASH_LIST);
check_single_lset_immediate(Hash, v!(Symbol::LIST_LIST v!(STR)), Symbol::HASH_HASH_LIST);

View file

@ -1122,11 +1122,6 @@ mod hash {
addU32: tAddU32,
addU64: tAddU64,
addU128: tAddU128,
addI8: tAddI8,
addI16: tAddI16,
addI32: tAddI32,
addI64: tAddI64,
addI128: tAddI128,
complete: tComplete,
}]
@ -1165,11 +1160,6 @@ mod hash {
tAddU32 = \@THasher total, n -> @THasher (do32 total n)
tAddU64 = \@THasher total, n -> @THasher (do64 total n)
tAddU128 = \@THasher total, n -> @THasher (do128 total n)
tAddI8 = \@THasher total, n -> @THasher (do8 total (Num.toU8 n))
tAddI16 = \@THasher total, n -> @THasher (do16 total (Num.toU16 n))
tAddI32 = \@THasher total, n -> @THasher (do32 total (Num.toU32 n))
tAddI64 = \@THasher total, n -> @THasher (do64 total (Num.toU64 n))
tAddI128 = \@THasher total, n -> @THasher (do128 total (Num.toU128 n))
tComplete = \@THasher _ -> Num.maxU64
tRead = \@THasher bytes -> bytes

View file

@ -956,6 +956,12 @@ fn list_walk_until_even_prefix_sum() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_from_sum() {
assert_evals_to!(r#"List.walkFrom [1, 2, 3] 1 0 Num.add"#, 5, i64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_if_empty_list_of_int() {
@ -3482,16 +3488,6 @@ fn list_let_generalization() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_backwards_until_sum() {
assert_evals_to!(
r#"List.walkBackwardsUntil [1, 2] 0 \a,b -> Continue (a + b)"#,
3,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_backwards_implements_position() {
@ -3520,6 +3516,16 @@ fn list_walk_backwards_implements_position() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_backwards_until_sum() {
assert_evals_to!(
r#"List.walkBackwardsUntil [1, 2] 0 \a,b -> Continue (a + b)"#,
3,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_backwards_until_even_prefix_sum() {
@ -3537,3 +3543,31 @@ fn list_walk_backwards_until_even_prefix_sum() {
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_from_until_sum() {
assert_evals_to!(
r#"List.walkFromUntil [1, 2, 3, 4] 2 0 \a,b -> Continue (a + b)"#,
7,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_from_even_prefix_sum() {
assert_evals_to!(
r#"
helper = \a, b ->
if Num.isEven b then
Continue (a + b)
else
Break a
List.walkFromUntil [2, 4, 8, 9] 1 0 helper"#,
4 + 8,
i64
);
}

View file

@ -9,7 +9,7 @@ use roc_code_markup::slow_pool::SlowPool;
use roc_highlight::highlight_parser::{highlight_defs, highlight_expr};
use roc_load::docs::DocEntry::DocDef;
use roc_load::docs::{DocEntry, TypeAnnotation};
use roc_load::docs::{ModuleDocumentation, RecordField};
use roc_load::docs::{Documentation, ModuleDocumentation, RecordField};
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
use roc_module::symbol::{IdentIdsByModule, Interns, ModuleId};
use roc_parse::ident::{parse_ident, Ident};
@ -28,7 +28,7 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
let loaded_modules = load_modules_for_files(filenames);
// TODO: get info from a package module; this is all hardcoded for now.
let mut package = roc_load::docs::Documentation {
let package = Documentation {
name: "documentation".to_string(),
version: "".to_string(),
docs: "Package introduction or README.".to_string(),
@ -104,7 +104,7 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
);
// Write each package's module docs html file
for loaded_module in package.modules.iter_mut() {
for loaded_module in package.modules.iter() {
for (module_id, module_docs) in loaded_module.documentation.iter() {
if *module_id == loaded_module.module_id {
let module_dir = build_dir.join(module_docs.name.replace('.', "/").as_str());
@ -113,6 +113,10 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
.expect("TODO gracefully handle not being able to create the module dir");
let rendered_module = template_html
.replace(
"<!-- Page title -->",
page_title(&package, module_docs).as_str(),
)
.replace(
"<!-- Package Name and Version -->",
render_name_and_version(package.name.as_str(), package.version.as_str())
@ -138,6 +142,13 @@ fn sidebar_link_url(module: &ModuleDocumentation) -> String {
url
}
fn page_title(package: &Documentation, module: &ModuleDocumentation) -> String {
let package_name = &package.name;
let module_name = &module.name;
let title = format!("<title>{module_name} - {package_name}</title>");
title
}
// converts plain-text code to highlighted html
pub fn syntax_highlight_expr(code_str: &str) -> DocsResult<String> {
let trimmed_code_str = code_str.trim_end().trim();

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<!-- <title>TODO populate this based on the module's name, e.g. "Parser - roc/parser"</title> -->
<!-- Page title -->
<!-- <meta name="description" content="TODO populate this based on the module's description"> -->
<meta name="viewport" content="width=device-width">
<script type="text/javascript" src="<!-- search.js -->" defer></script>

View file

@ -8,8 +8,8 @@ use bincode::{deserialize_from, serialize_into};
use memmap2::MmapMut;
use object::{
pe::{
self, ImageFileHeader, ImageImportDescriptor, ImageNtHeaders64, ImageSectionHeader,
ImageThunkData64,
self, ImageBaseRelocation, ImageFileHeader, ImageImportDescriptor, ImageNtHeaders64,
ImageSectionHeader, ImageThunkData64,
},
read::pe::ImportTable,
LittleEndian as LE, Object, RelocationTarget, SectionIndex,
@ -20,7 +20,8 @@ use roc_collections::{MutMap, VecMap};
use roc_error_macros::internal_error;
use crate::{
generate_dylib::APP_DLL, load_struct_inplace, load_struct_inplace_mut, open_mmap, open_mmap_mut,
generate_dylib::APP_DLL, load_struct_inplace, load_struct_inplace_mut,
load_structs_inplace_mut, open_mmap, open_mmap_mut,
};
/// The metadata stores information about/from the host .exe because
@ -48,7 +49,18 @@ struct PeMetadata {
dynamic_relocations: DynamicRelocationsPe,
/// File offset for the thunks of our dummy .dll
thunks_start_offset: usize,
thunks_start_offset_in_file: usize,
/// Offset for the thunks of our dummy .dll within the .rdata section
thunks_start_offset_in_section: usize,
/// Virtual address of the .rdata section
rdata_virtual_address: u32,
/// The offset into the file of the .reloc section
reloc_offset_in_file: usize,
reloc_section_index: usize,
/// Constants from the host .exe header
image_base: u64,
@ -96,7 +108,26 @@ impl PeMetadata {
.unwrap();
let dynamic_relocations = DynamicRelocationsPe::new(preprocessed_data);
let thunks_start_offset = find_thunks_start_offset(preprocessed_data, &dynamic_relocations);
let thunks_start_offset_in_file =
find_thunks_start_offset(preprocessed_data, &dynamic_relocations);
let rdata_section = dynhost_obj
.sections()
.find(|s| s.name() == Ok(".rdata"))
.unwrap();
let thunks_start_offset_in_section =
thunks_start_offset_in_file - rdata_section.file_range().unwrap().0 as usize;
let rdata_virtual_address = rdata_section.address() as u32;
let (reloc_section_index, reloc_section) = dynhost_obj
.sections()
.enumerate()
.find(|(_, s)| s.name() == Ok(".reloc"))
.unwrap();
let reloc_offset_in_file = reloc_section.file_range().unwrap().0 as usize;
let optional_header = dynhost_obj.nt_headers().optional_header;
let optional_header_offset = dynhost_obj.dos_header().nt_headers_offset() as usize
@ -142,7 +173,11 @@ impl PeMetadata {
imports,
exports,
dynamic_relocations,
thunks_start_offset,
thunks_start_offset_in_file,
thunks_start_offset_in_section,
rdata_virtual_address,
reloc_offset_in_file,
reloc_section_index,
}
}
}
@ -221,6 +256,8 @@ pub(crate) fn surgery_pe(executable_path: &Path, metadata_path: &Path, roc_app_b
let mut section_header_start = md.dynamic_relocations.section_headers_offset_in_file as usize
+ md.host_section_count * std::mem::size_of::<ImageSectionHeader>();
relocate_dummy_dll_entries(executable, &md);
let mut code_bytes_added = 0;
let mut data_bytes_added = 0;
let mut file_bytes_added = 0;
@ -249,7 +286,8 @@ pub(crate) fn surgery_pe(executable_path: &Path, metadata_path: &Path, roc_app_b
}
}
let virtual_size = length as u32;
// NOTE: sections cannot be zero in size!
let virtual_size = u32::max(1, length as u32);
let size_of_raw_data = next_multiple_of(length, file_alignment) as u32;
match kind {
@ -289,7 +327,12 @@ pub(crate) fn surgery_pe(executable_path: &Path, metadata_path: &Path, roc_app_b
let slice = &roc_app_bytes[section.file_range.start..section.file_range.end];
executable[offset..][..slice.len()].copy_from_slice(slice);
for (name, app_relocation) in section.relocations.iter() {
let it = section
.relocations
.iter()
.flat_map(|(name, rs)| rs.iter().map(move |r| (name, r)));
for (name, app_relocation) in it {
let AppRelocation {
offset_in_section,
relocation,
@ -359,7 +402,12 @@ pub(crate) fn surgery_pe(executable_path: &Path, metadata_path: &Path, roc_app_b
.map(|s| (s.name, s.offset_in_section as u64))
.collect();
redirect_dummy_dll_functions(executable, &symbols, &md.imports, md.thunks_start_offset);
redirect_dummy_dll_functions(
executable,
&symbols,
&md.imports,
md.thunks_start_offset_in_file,
);
}
#[derive(Debug, Serialize, Deserialize)]
@ -602,9 +650,14 @@ impl Preprocessor {
result[self.new_headers_size..].copy_from_slice(&data[self.old_headers_size..]);
}
fn write_dummy_sections(&self, result: &mut MmapMut, extra_sections: &[[u8; 8]]) {
fn write_dummy_sections(&self, result: &mut MmapMut, extra_section_names: &[[u8; 8]]) {
const W: usize = std::mem::size_of::<ImageSectionHeader>();
// only correct for the first section, but that is OK because it's overwritten later
// anyway. But, this value may be used to check whether a previous section overruns into
// the app sections.
let pointer_to_raw_data = result.len() - self.additional_length;
let previous_section_header =
load_struct_inplace::<ImageSectionHeader>(result, self.extra_sections_start - W);
@ -614,8 +667,14 @@ impl Preprocessor {
let mut next_virtual_address =
next_multiple_of(previous_section_header_end as usize, self.section_alignment);
for (i, name) in extra_sections.iter().enumerate() {
let header = ImageSectionHeader {
let extra_section_headers = load_structs_inplace_mut::<ImageSectionHeader>(
result,
self.extra_sections_start,
extra_section_names.len(),
);
for (header, name) in extra_section_headers.iter_mut().zip(extra_section_names) {
*header = ImageSectionHeader {
name: *name,
// NOTE: the virtual_size CANNOT BE ZERO! the binary is invalid if a section has
// zero virtual size. Setting it to 1 works, (because this one byte is not backed
@ -624,7 +683,7 @@ impl Preprocessor {
// NOTE: this must be a valid virtual address, using 0 is invalid!
virtual_address: object::U32::new(LE, next_virtual_address as u32),
size_of_raw_data: Default::default(),
pointer_to_raw_data: Default::default(),
pointer_to_raw_data: object::U32::new(LE, pointer_to_raw_data as u32),
pointer_to_relocations: Default::default(),
pointer_to_linenumbers: Default::default(),
number_of_relocations: Default::default(),
@ -632,10 +691,6 @@ impl Preprocessor {
characteristics: Default::default(),
};
let header_array: [u8; W] = unsafe { std::mem::transmute(header) };
result[self.extra_sections_start + i * W..][..W].copy_from_slice(&header_array);
next_virtual_address += self.section_alignment;
}
}
@ -828,7 +883,7 @@ struct Section {
/// File range of the section (in the app object)
file_range: Range<usize>,
kind: SectionKind,
relocations: MutMap<String, AppRelocation>,
relocations: MutMap<String, Vec<AppRelocation>>,
app_section_index: SectionIndex,
}
@ -904,7 +959,7 @@ impl AppSections {
_ => continue,
};
let mut relocations = MutMap::default();
let mut relocations: MutMap<String, Vec<AppRelocation>> = MutMap::default();
for (offset_in_section, relocation) in section.relocations() {
match relocation.target() {
@ -916,14 +971,14 @@ impl AppSections {
let address = symbol.as_ref().map(|s| s.address()).unwrap_or_default();
let name = symbol.and_then(|s| s.name()).unwrap_or_default();
relocations.insert(
name.to_string(),
AppRelocation {
relocations
.entry(name.to_string())
.or_default()
.push(AppRelocation {
offset_in_section,
address,
relocation,
},
);
});
}
_ => todo!(),
}
@ -1081,6 +1136,175 @@ fn write_section_header(
data[section_header_start..][..header_array.len()].copy_from_slice(&header_array);
}
fn write_image_base_relocation(
mmap: &mut [u8],
reloc_section_start: usize,
new_block_va: u32,
relocations: &[u16],
) -> usize {
// first, collect the blocks of relocations (each relocation block covers a 4K page)
// we will be moving stuff around, and have to work from the back to the front. However, the
// relocations are encoded in such a way that we can only decode them from front to back.
let mut next_block_start = reloc_section_start as u32;
let mut blocks = vec![];
let mut block_has_relocation = false;
loop {
let header =
load_struct_inplace_mut::<ImageBaseRelocation>(mmap, next_block_start as usize);
if header.virtual_address.get(LE) == 0 {
break;
}
if new_block_va == header.virtual_address.get(LE) {
block_has_relocation = true;
}
blocks.push((next_block_start, *header));
next_block_start += header.size_of_block.get(LE);
}
// this is 8 in practice
const HEADER_WIDTH: usize = std::mem::size_of::<ImageBaseRelocation>();
const ENTRY_WIDTH: usize = std::mem::size_of::<u16>();
// extra space that we'll use for the new relocations
let shift_amount = relocations.len() * ENTRY_WIDTH;
if block_has_relocation {
// now, starting from the back, shift sections that need to be shifted and add the
// new relocations to the right block
while let Some((block_start, header)) = blocks.pop() {
let header_va = header.virtual_address.get(LE);
let header_size = header.size_of_block.get(LE);
let block_start = block_start as usize;
match header_va.cmp(&new_block_va) {
std::cmp::Ordering::Greater => {
// shift this block
mmap.copy_within(
block_start..block_start + header_size as usize,
block_start + shift_amount,
);
}
std::cmp::Ordering::Equal => {
// extend this block
let header = load_struct_inplace_mut::<ImageBaseRelocation>(mmap, block_start);
let new_size = header.size_of_block.get(LE) + shift_amount as u32;
header.size_of_block.set(LE, new_size);
let number_of_entries = (new_size as usize - HEADER_WIDTH) / ENTRY_WIDTH;
let entries = load_structs_inplace_mut::<u16>(
mmap,
block_start + HEADER_WIDTH,
number_of_entries,
);
entries[number_of_entries - relocations.len()..].copy_from_slice(relocations);
// sort by VA. Upper 4 bits store the relocation type
entries.sort_unstable_by_key(|x| x & 0b0000_1111_1111_1111);
}
std::cmp::Ordering::Less => {
// done
break;
}
}
}
shift_amount
} else {
let header =
load_struct_inplace_mut::<ImageBaseRelocation>(mmap, next_block_start as usize);
let size_of_block = HEADER_WIDTH + relocations.len() * ENTRY_WIDTH;
header.virtual_address.set(LE, new_block_va);
header.size_of_block.set(LE, size_of_block as u32);
let number_of_entries = relocations.len();
let entries = load_structs_inplace_mut::<u16>(
mmap,
next_block_start as usize + HEADER_WIDTH,
number_of_entries,
);
entries[number_of_entries - relocations.len()..].copy_from_slice(relocations);
// sort by VA. Upper 4 bits store the relocation type
entries.sort_unstable_by_key(|x| x & 0b0000_1111_1111_1111);
size_of_block
}
}
/// the roc app functions are called from the host with an indirect call: the code will look
/// in a table to find the actual address of the app function. This table must be relocated,
/// because it contains absolute addresses to jump to.
fn relocate_dummy_dll_entries(executable: &mut [u8], md: &PeMetadata) {
let thunks_start_va = (md.rdata_virtual_address - md.image_base as u32)
+ md.thunks_start_offset_in_section as u32;
// relocations are defined per 4kb page
const BLOCK_SIZE: u32 = 4096;
let thunks_offset_in_block = thunks_start_va % BLOCK_SIZE;
let thunks_relocation_block_va = thunks_start_va - thunks_offset_in_block;
let relocations: Vec<_> = (0..md.dynamic_relocations.name_by_virtual_address.len())
.map(|i| (thunks_offset_in_block as usize + 2 * i) as u16)
.collect();
let added_reloc_bytes = write_image_base_relocation(
executable,
md.reloc_offset_in_file,
thunks_relocation_block_va,
&relocations,
);
// the reloc section got bigger, and we need to update the header with the new size
let reloc_section_header_start = md.dynamic_relocations.section_headers_offset_in_file as usize
+ md.reloc_section_index * std::mem::size_of::<ImageSectionHeader>();
let next_section = load_struct_inplace::<ImageSectionHeader>(
executable,
reloc_section_header_start + std::mem::size_of::<ImageSectionHeader>(),
);
let next_section_pointer_to_raw_data = next_section.pointer_to_raw_data.get(LE);
let reloc_section =
load_struct_inplace_mut::<ImageSectionHeader>(executable, reloc_section_header_start);
let old_section_size = reloc_section.virtual_size.get(LE);
let new_virtual_size = old_section_size + added_reloc_bytes as u32;
reloc_section.virtual_size.set(LE, new_virtual_size);
assert!(
reloc_section.pointer_to_raw_data.get(LE)
+ reloc_section.virtual_size.get(LE)
+ (added_reloc_bytes as u32)
< next_section_pointer_to_raw_data,
"new .reloc section is too big, and runs into the next section!",
);
// // in the data directories, update the length of the base relocations
// let dir = load_struct_inplace_mut::<pe::ImageDataDirectory>(
// executable,
// md.dynamic_relocations.data_directories_offset_in_file as usize
// + object::pe::IMAGE_DIRECTORY_ENTRY_BASERELOC
// * std::mem::size_of::<pe::ImageDataDirectory>(),
// );
//
// let old_dir_size = dir.size.get(LE);
// debug_assert_eq!(old_section_size, old_dir_size);
// dir.size.set(LE, new_virtual_size);
}
#[cfg(test)]
mod test {
const PE_DYNHOST: &[u8] = include_bytes!("../dynhost_benchmarks_windows.exe") as &[_];
@ -1560,7 +1784,6 @@ mod test {
#[cfg(windows)]
#[test]
#[ignore = "does not work yet"]
fn basics_windows() {
assert_eq!("Hello, 234567 32 1 3!\n", windows_test(test_basics))
}

View file

@ -19,9 +19,9 @@ mod storage;
pub use roc_box::RocBox;
pub use roc_dict::RocDict;
pub use roc_list::RocList;
pub use roc_list::{RocList, SendSafeRocList};
pub use roc_set::RocSet;
pub use roc_str::{InteriorNulError, RocStr};
pub use roc_str::{InteriorNulError, RocStr, SendSafeRocStr};
pub use storage::Storage;
// A list of C functions that are being imported

View file

@ -103,6 +103,44 @@ impl<T> RocList<T> {
self.len() == 0
}
pub fn is_unique(&self) -> bool {
if let Some(storage) = self.storage() {
storage.is_unique()
} else {
// If there is no storage, this list is empty.
// An empty list is always unique.
true
}
}
pub fn is_readonly(&self) -> bool {
if let Some(storage) = self.storage() {
storage.is_readonly()
} else {
false
}
}
/// Marks a list as readonly. This means that it will be leaked.
/// For constants passed in from platform to application, this may be reasonable.
///
/// # Safety
///
/// A value can be read-only in Roc for 3 reasons:
/// 1. The value is stored in read-only memory like a constant in the app.
/// 2. Our refcounting maxes out. When that happens, we saturate to read-only.
/// 3. This function is called
///
/// Any value that is set to read-only will be leaked.
/// There is no way to tell how many references it has and if it is safe to free.
/// As such, only values that should have a static lifetime for the entire application run
/// should be considered for marking read-only.
pub unsafe fn set_readonly(&self) {
if let Some((_, storage)) = self.elements_and_storage() {
storage.set(Storage::Readonly);
}
}
/// Note that there is no way to convert directly to a Vec.
///
/// This is because RocList values are not allocated using the system allocator, so
@ -586,6 +624,48 @@ where
}
}
// This is a RocList that is checked to ensure it is unique or readonly such that it can be sent between threads safely.
#[repr(transparent)]
pub struct SendSafeRocList<T>(RocList<T>);
unsafe impl<T> Send for SendSafeRocList<T> where T: Send {}
impl<T> Clone for SendSafeRocList<T>
where
T: Clone,
{
fn clone(&self) -> Self {
if self.0.is_readonly() {
SendSafeRocList(self.0.clone())
} else {
// To keep self send safe, this must copy.
SendSafeRocList(RocList::from_slice(&self.0))
}
}
}
impl<T> From<RocList<T>> for SendSafeRocList<T>
where
T: Clone,
{
fn from(l: RocList<T>) -> Self {
if l.is_unique() || l.is_readonly() {
SendSafeRocList(l)
} else {
// This is not unique, do a deep copy.
// TODO: look into proper into_iter that takes ownership.
// Then this won't need clone and will skip and refcount inc and dec for each element.
SendSafeRocList(RocList::from_slice(&l))
}
}
}
impl<T> From<SendSafeRocList<T>> for RocList<T> {
fn from(l: SendSafeRocList<T>) -> Self {
l.0
}
}
#[cfg(feature = "serde")]
struct RocListVisitor<T> {
marker: PhantomData<T>,

View file

@ -109,6 +109,41 @@ impl RocStr {
self.len() == 0
}
pub fn is_unique(&self) -> bool {
match self.as_enum_ref() {
RocStrInnerRef::HeapAllocated(roc_list) => roc_list.is_unique(),
RocStrInnerRef::SmallString(_) => true,
}
}
pub fn is_readonly(&self) -> bool {
match self.as_enum_ref() {
RocStrInnerRef::HeapAllocated(roc_list) => roc_list.is_readonly(),
RocStrInnerRef::SmallString(_) => false,
}
}
/// Marks a str as readonly. This means that it will be leaked.
/// For constants passed in from platform to application, this may be reasonable.
///
/// # Safety
///
/// A value can be read-only in Roc for 3 reasons:
/// 1. The value is stored in read-only memory like a constant in the app.
/// 2. Our refcounting maxes out. When that happens, we saturate to read-only.
/// 3. This function is called
///
/// Any value that is set to read-only will be leaked.
/// There is no way to tell how many references it has and if it is safe to free.
/// As such, only values that should have a static lifetime for the entire application run
/// should be considered for marking read-only.
pub unsafe fn set_readonly(&self) {
match self.as_enum_ref() {
RocStrInnerRef::HeapAllocated(roc_list) => unsafe { roc_list.set_readonly() },
RocStrInnerRef::SmallString(_) => {}
}
}
/// Note that there is no way to convert directly to a String.
///
/// This is because RocStr values are not allocated using the system allocator, so
@ -627,6 +662,40 @@ impl Drop for RocStr {
}
}
// This is a RocStr that is checked to ensure it is unique or readonly such that it can be sent between threads safely.
#[repr(transparent)]
pub struct SendSafeRocStr(RocStr);
unsafe impl Send for SendSafeRocStr {}
impl Clone for SendSafeRocStr {
fn clone(&self) -> Self {
if self.0.is_readonly() {
SendSafeRocStr(self.0.clone())
} else {
// To keep self send safe, this must copy.
SendSafeRocStr(RocStr::from(self.0.as_str()))
}
}
}
impl From<RocStr> for SendSafeRocStr {
fn from(s: RocStr) -> Self {
if s.is_unique() || s.is_readonly() {
SendSafeRocStr(s)
} else {
// This is not unique, do a deep copy.
SendSafeRocStr(RocStr::from(s.as_str()))
}
}
}
impl From<SendSafeRocStr> for RocStr {
fn from(s: SendSafeRocStr) -> Self {
s.0
}
}
#[repr(C)]
union RocStrInner {
heap_allocated: ManuallyDrop<RocList<u8>>,

View file

@ -59,7 +59,7 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
#[cfg(test)]
mod test_roc_std {
use roc_std::{RocBox, RocDec, RocList, RocResult, RocStr};
use roc_std::{RocBox, RocDec, RocList, RocResult, RocStr, SendSafeRocList, SendSafeRocStr};
fn roc_str_byte_representation(string: &RocStr) -> [u8; RocStr::SIZE] {
unsafe { core::mem::transmute_copy(string) }
@ -126,7 +126,7 @@ mod test_roc_std {
roc_str.reserve(42);
assert_gte!(roc_str.capacity(), 42);
assert_eq!(roc_str.capacity() >= 42, true);
}
#[test]
@ -135,7 +135,7 @@ mod test_roc_std {
roc_str.reserve(5000);
assert_gte!(roc_str.capacity(), 5000);
assert_eq!(roc_str.capacity() >= 5000, true);
}
#[test]
@ -296,6 +296,80 @@ mod test_roc_std {
let example = RocDec::from_str("1234.5678").unwrap();
assert_eq!(format!("{}", example), "1234.5678");
}
#[test]
fn safe_send_no_copy() {
let x = RocStr::from("This is a long string but still unique. Yay!!!");
assert_eq!(x.is_unique(), true);
let safe_x = SendSafeRocStr::from(x);
let new_x = RocStr::from(safe_x);
assert_eq!(new_x.is_unique(), true);
assert_eq!(
new_x.as_str(),
"This is a long string but still unique. Yay!!!"
);
}
#[test]
fn safe_send_requires_copy() {
let x = RocStr::from("This is a long string but still unique. Yay!!!");
let y = x.clone();
let z = y.clone();
assert_eq!(x.is_unique(), false);
assert_eq!(y.is_unique(), false);
assert_eq!(z.is_unique(), false);
let safe_x = SendSafeRocStr::from(x);
let new_x = RocStr::from(safe_x);
assert_eq!(new_x.is_unique(), true);
assert_eq!(y.is_unique(), false);
assert_eq!(z.is_unique(), false);
assert_eq!(
new_x.as_str(),
"This is a long string but still unique. Yay!!!"
);
}
#[test]
fn safe_send_small_str() {
let x = RocStr::from("short");
let y = x.clone();
let z = y.clone();
assert_eq!(x.is_unique(), true);
assert_eq!(y.is_unique(), true);
assert_eq!(z.is_unique(), true);
let safe_x = SendSafeRocStr::from(x);
let new_x = RocStr::from(safe_x);
assert_eq!(new_x.is_unique(), true);
assert_eq!(y.is_unique(), true);
assert_eq!(z.is_unique(), true);
assert_eq!(new_x.as_str(), "short");
}
#[test]
fn empty_list_is_unique() {
let roc_list = RocList::<RocStr>::empty();
assert_eq!(roc_list.is_unique(), true);
}
#[test]
fn readonly_list_is_sendsafe() {
let x = RocList::from_slice(&[1, 2, 3, 4, 5]);
unsafe { x.set_readonly() };
assert_eq!(x.is_readonly(), true);
let y = x.clone();
let z = y.clone();
let safe_x = SendSafeRocList::from(x);
let new_x = RocList::from(safe_x);
assert_eq!(new_x.is_readonly(), true);
assert_eq!(y.is_readonly(), true);
assert_eq!(z.is_readonly(), true);
assert_eq!(new_x.as_slice(), &[1, 2, 3, 4, 5]);
}
}
#[cfg(test)]

View file

@ -1,9 +1,9 @@
{ rev ? "541a3ca27c9a8220b46f4feb7dd8e94336a77f42", # nixpkgs master
{ rev ? "a7855f2235a1876f97473a76151fec2afa02b287", # nixpkgs master. Keep up to date with "nixpkgs">"locked">"rev" in flake.lock
nixpkgsSource ? builtins.fetchTarball {
url = "https://github.com/nixos/nixpkgs/tarball/${rev}";
sha256 = "sha256:1mxv0zigm98pawf05kd4s8ipvk1pvvdsn1yh978c5an97kz0ck5w";
sha256 = "sha256-5DGKX81wIPAAiLwUmUYECpA3vop94AHHR7WmGXSsQok=";
}, pkgs ? import nixpkgsSource { }
, cargoSha256 ? "sha256-treL2sWPcZ1NBwdab3FOb2FI2wT/Vt9tD4XRfJ8rYWA=", }:
, cargoSha256 ? "sha256-F6UOJZ5oDOZ+80z70A21VzDR0YtmgD0dnEcjPgpicpo=", }:
# we only this file to release a nix package, use flake.nix for development
let
rustPlatform = pkgs.rustPlatform;

View file

@ -1,20 +1,9 @@
# Examples
Run examples as follows:
1. Navigate to this `examples` folder
To run examples:
```bash
cd examples
roc run examples/hello-world/main.roc
```
2. Run a particular example, such as Hello World:
```bash
roc run hello-world/main.roc
```
`crates/cli_testing_examples/benchmarks/` contains some larger examples.
Some examples like `crates/cli_testing_examples/benchmarks/NQueens.roc` require input after running.
For NQueens, input 10 in the terminal and press enter.
[crates/cli_testing_examples/](https://github.com/roc-lang/roc/tree/main/crates/cli_testing_examples) has even more examples.

View file

@ -16,7 +16,7 @@ name = "host"
path = "src/main.rs"
[dependencies]
roc_std = { path = "../../../../crates/roc_std" }
roc_std = { path = "../../../crates/roc_std" }
libc = "0.2"
[workspace]

View file

@ -3,12 +3,12 @@
To run this website, first compile either of these identical apps:
```bash
# Option A: Compile crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc
cargo run -- build --target=wasm32 crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc
# Option A: Compile examples/platform-switching/rocLovesWebAssembly.roc
cargo run -- build --target=wasm32 examples/platform-switching/rocLovesWebAssembly.roc
# Option B: Compile crates/cli_testing_examples/platform-switching/main.roc with `pf: "web-assembly-platform/main.roc"` and move the result
cargo run -- build --target=wasm32 crates/cli_testing_examples/platform-switching/main.roc
(cd crates/cli_testing_examples/platform-switching && mv rocLovesPlatforms.wasm web-assembly-platform/rocLovesWebAssembly.wasm)
# Option B: Compile examples/platform-switching/main.roc with `pf: "web-assembly-platform/main.roc"` and move the result
cargo run -- build --target=wasm32 examples/platform-switching/main.roc
(cd examples/platform-switching && mv rocLovesPlatforms.wasm web-assembly-platform/rocLovesWebAssembly.wasm)
```
Then `cd` into the website directory
@ -16,7 +16,7 @@ and run any web server that can handle WebAssembly.
For example, with `http-server`:
```bash
cd crates/cli_testing_examples/platform-switching/web-assembly-platform
cd examples/platform-switching/web-assembly-platform
npm install -g http-server
http-server
```

View file

@ -128,5 +128,8 @@
};
formatter = pkgs.nixpkgs-fmt;
# You can build this package (the roc CLI) with the `nix build` command.
packages.default = import ./. { inherit pkgs; };
});
}

View file

@ -45,9 +45,9 @@ you need to install one or more of these platform language compilers, too.
```sh
# Note: If you installed Rust in this terminal session, you'll need to open a new one first!
./roc crates/cli_testing_examples/platform-switching/rocLovesRust.roc
./roc examples/platform-switching/rocLovesRust.roc
./roc crates/cli_testing_examples/platform-switching/rocLovesZig.roc
./roc examples/platform-switching/rocLovesZig.roc
./roc crates/cli_testing_examples/platform-switching/rocLovesC.roc
./roc examples/platform-switching/rocLovesC.roc
```

View file

@ -48,9 +48,9 @@ you need to install one or more of these platform language compilers, too.
```sh
# Note: If you installed rust in this terminal session, you'll need to open a new one first!
./roc crates/cli_testing_examples/platform-switching/rocLovesRust.roc
./roc examples/platform-switching/rocLovesRust.roc
./roc crates/cli_testing_examples/platform-switching/rocLovesZig.roc
./roc examples/platform-switching/rocLovesZig.roc
./roc crates/cli_testing_examples/platform-switching/rocLovesC.roc
./roc examples/platform-switching/rocLovesC.roc
```

View file

@ -42,9 +42,9 @@ you need to install one or more of these platform language compilers, too.
```sh
# Note: If you installed rust in this terminal session, you'll need to open a new one first!
./roc crates/cli_testing_examples/platform-switching/rocLovesRust.roc
./roc examples/platform-switching/rocLovesRust.roc
./roc crates/cli_testing_examples/platform-switching/rocLovesZig.roc
./roc examples/platform-switching/rocLovesZig.roc
./roc crates/cli_testing_examples/platform-switching/rocLovesC.roc
./roc examples/platform-switching/rocLovesC.roc
```

View file

@ -7,11 +7,11 @@
1. Run examples:
```sh
cargo run crates/cli_testing_examples/platform-switching/rocLovesRust.roc
cargo run examples/platform-switching/rocLovesRust.roc
# This requires installing the Zig compiler, too.
cargo run crates/cli_testing_examples/platform-switching/rocLovesZig.roc
cargo run examples/platform-switching/rocLovesZig.roc
# This requires installing the `clang` C compiler, too.
cargo run crates/cli_testing_examples/platform-switching/rocLovesC.roc
cargo run examples/platform-switching/rocLovesC.roc
```

View file

@ -16,7 +16,10 @@
"pattern": "https://opensource.org"
},
{
"pattern": "https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/deforestation-short-cut.pdf"
"pattern": "https://www.microsoft.com/en-us/research"
},
{
"pattern": "https://web.eecs.umich.edu"
}
]
}