mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Merge remote-tracking branch 'origin/main' into roc-dev-inline-expects
This commit is contained in:
commit
ebac056814
67 changed files with 851 additions and 1952 deletions
14
.github/workflows/markdown_link_check.yml
vendored
14
.github/workflows/markdown_link_check.yml
vendored
|
@ -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' }}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
21
TUTORIAL.md
21
TUTORIAL.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
1770
crates/cli_utils/Cargo.lock
generated
1770
crates/cli_utils/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
func = \x -> when n is
|
||||
0 -> 0
|
||||
42
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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>>,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
|
@ -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
|
||||
```
|
|
@ -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; };
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue