mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-13 09:49:11 +03:00
Merge branch 'trunk' into fix-when-branch-solving
This commit is contained in:
commit
64ed7eea95
1
AUTHORS
1
AUTHORS
@ -77,3 +77,4 @@ Cai Bingjun <62678643+C-BJ@users.noreply.github.com>
|
||||
Jared Cone <jared.cone@gmail.com>
|
||||
Sean Hagstrom <sean@seanhagstrom.com>
|
||||
Kas Buunk <kasbuunk@icloud.com>
|
||||
Oskar Hahn <mail@oshahn.de>
|
||||
|
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -4491,9 +4491,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff"
|
||||
checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
|
39
TUTORIAL.md
39
TUTORIAL.md
@ -115,18 +115,19 @@ Create a new file called `Hello.roc` and put this inside it:
|
||||
|
||||
```coffee
|
||||
app "hello"
|
||||
packages { pf: "examples/cli/platform" }
|
||||
packages { pf: "examples/interactive/cli-platform" }
|
||||
imports [ pf.Stdout ]
|
||||
provides [ main ] to pf
|
||||
|
||||
main = Stdout.line "I'm a Roc application!"
|
||||
```
|
||||
|
||||
> **NOTE:** This assumes you've put Hello.roc in the root directory of the
|
||||
> Roc source code. If you'd like to put it somewhere else, you'll need to replace
|
||||
> `"examples/cli/"` with the path to the `examples/cli/` folder in
|
||||
> that source code. In the future, Roc will have the tutorial built in, and this
|
||||
> aside will no longer be necessary!
|
||||
> **NOTE:** This assumes you've put Hello.roc in the root directory of the Roc
|
||||
> source code. If you'd like to put it somewhere else, you'll need to replace
|
||||
> `"examples/interactive/cli-platform"` with the path to the
|
||||
> `examples/interactive/cli-platform` folder in that source code. In the future,
|
||||
> Roc will have the tutorial built in, and this aside will no longer be
|
||||
> necessary!
|
||||
|
||||
Try running this with:
|
||||
|
||||
@ -1236,7 +1237,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`:
|
||||
|
||||
```coffee
|
||||
app "hello"
|
||||
packages { pf: "examples/cli/platform" }
|
||||
packages { pf: "examples/interactive/cli-platform" }
|
||||
imports [ pf.Stdout ]
|
||||
provides main to pf
|
||||
```
|
||||
@ -1254,14 +1255,14 @@ without running it by running `roc build Hello.roc`.
|
||||
The remaining lines all involve the *platform* this application is built on:
|
||||
|
||||
```coffee
|
||||
packages { pf: "examples/cli/platform" }
|
||||
packages { pf: "examples/interactive/cli-platform" }
|
||||
imports [ pf.Stdout ]
|
||||
provides main to pf
|
||||
```
|
||||
|
||||
The `packages { pf: "examples/cli/platform" }` part says two things:
|
||||
The `packages { pf: "examples/interactive/cli-platform" }` part says two things:
|
||||
|
||||
- We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/platform"`
|
||||
- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform"`
|
||||
- We're going to name that package `pf` so we can refer to it more concisely in the future.
|
||||
|
||||
The `imports [ pf.Stdout ]` line says that we want to import the `Stdout` module
|
||||
@ -1281,17 +1282,18 @@ calling a function named `line` which is exposed by a module named
|
||||
When we write `imports [ pf.Stdout ]`, it specifies that the `Stdout`
|
||||
module comes from the `pf` package.
|
||||
|
||||
Since `pf` was the name we chose for the `examples/cli/platform` package
|
||||
(when we wrote `packages { pf: "examples/cli/platform" }`), this `imports` line
|
||||
tells the Roc compiler that when we call `Stdout.line`, it should look for that
|
||||
`line` function in the `Stdout` module of the `examples/cli/platform` package.
|
||||
Since `pf` was the name we chose for the `examples/interactive/cli-platform`
|
||||
package (when we wrote `packages { pf: "examples/interactive/cli-platform" }`),
|
||||
this `imports` line tells the Roc compiler that when we call `Stdout.line`, it
|
||||
should look for that `line` function in the `Stdout` module of the
|
||||
`examples/interactive/cli-platform` package.
|
||||
|
||||
# Building a Command-Line Interface (CLI)
|
||||
|
||||
## Tasks
|
||||
|
||||
Tasks are technically not part of the Roc language, but they're very common in
|
||||
platforms. Let's use the CLI platform in `examples/cli` as an example!
|
||||
platforms. Let's use the CLI platform in `examples/interactive/cli-platform` as an example!
|
||||
|
||||
In the CLI platform, we have four operations we can do:
|
||||
|
||||
@ -1306,7 +1308,7 @@ First, let's do a basic "Hello World" using the tutorial app.
|
||||
|
||||
```coffee
|
||||
app "cli-tutorial"
|
||||
packages { pf: "examples/cli/platform" }
|
||||
packages { pf: "examples/interactive/cli-platform" }
|
||||
imports [ pf.Stdout ]
|
||||
provides [ main ] to pf
|
||||
|
||||
@ -1343,7 +1345,7 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai
|
||||
|
||||
```swift
|
||||
app "cli-tutorial"
|
||||
packages { pf: "examples/cli/platform" }
|
||||
packages { pf: "examples/interactive/cli-platform" }
|
||||
imports [ pf.Stdout, pf.Stdin, pf.Task ]
|
||||
provides [ main ] to pf
|
||||
|
||||
@ -1393,7 +1395,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo
|
||||
|
||||
```haskell
|
||||
app "cli-tutorial"
|
||||
packages { pf: "examples/cli/platform" }
|
||||
packages { pf: "examples/interactive/cli-platform" }
|
||||
imports [ pf.Stdout, pf.Stdin, pf.Task.{ await } ]
|
||||
provides [ main ] to pf
|
||||
|
||||
@ -1944,7 +1946,6 @@ Here are various Roc expressions involving operators, and what they desugar to.
|
||||
| `a // b` | `Num.divTrunc a b` |
|
||||
| `a ^ b` | `Num.pow a b` |
|
||||
| `a % b` | `Num.rem a b` |
|
||||
| `a %% b` | `Num.mod a b` |
|
||||
| `a >> b` | `Num.shr a b` |
|
||||
| `a << b` | `Num.shl a b` |
|
||||
| `-a` | `Num.neg a` |
|
||||
|
@ -66,7 +66,7 @@ const_format = "0.2.22"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
mimalloc = { version = "0.1.26", default-features = false }
|
||||
|
||||
target-lexicon = "0.12.2"
|
||||
target-lexicon = "0.12.3"
|
||||
tempfile = "3.2.0"
|
||||
wasmer-wasi = { version = "2.0.0", optional = true }
|
||||
|
||||
|
@ -256,8 +256,8 @@ mod cli_run {
|
||||
return;
|
||||
}
|
||||
}
|
||||
"hello-gui" => {
|
||||
// Since this one requires opening a window, we do `roc build` on it but don't run it.
|
||||
"hello-gui" | "breakout" => {
|
||||
// Since these require opening a window, we do `roc build` on them but don't run them.
|
||||
build_example(&file_name, &["--optimize"]);
|
||||
|
||||
return;
|
||||
@ -394,6 +394,14 @@ mod cli_run {
|
||||
expected_ending: "",
|
||||
use_valgrind: false,
|
||||
},
|
||||
breakout:"breakout" => Example {
|
||||
filename: "breakout.roc",
|
||||
executable_filename: "breakout",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
expected_ending: "",
|
||||
use_valgrind: false,
|
||||
},
|
||||
quicksort:"algorithms" => Example {
|
||||
filename: "quicksort.roc",
|
||||
executable_filename: "quicksort",
|
||||
|
@ -167,6 +167,20 @@ For a more detailed understanding of the compilation phases, see the `Phase`, `B
|
||||
|
||||
## Debugging intermediate representations
|
||||
|
||||
### Debugging the typechecker
|
||||
|
||||
Setting the following environment variables:
|
||||
|
||||
- `ROC_PRINT_UNIFICATIONS` prints all type unifications that are done,
|
||||
before and after the unification.
|
||||
- `ROC_PRINT_MISMATCHES` prints all type mismatches hit during unification.
|
||||
- `ROC_PRETTY_PRINT_ALIAS_CONTENTS` expands the contents of aliases during
|
||||
pretty-printing of types.
|
||||
|
||||
Note that this is only relevant during debug builds. Eventually we should have
|
||||
some better debugging tools here, see https://github.com/rtfeldman/roc/issues/2486
|
||||
for one.
|
||||
|
||||
### The mono IR
|
||||
|
||||
If you observe a miscomplication, you may first want to check the generated mono
|
||||
|
@ -279,7 +279,8 @@ fn build_entry_point(
|
||||
let block = builder.add_block();
|
||||
|
||||
// to the modelling language, the arguments appear out of thin air
|
||||
let argument_type = build_tuple_type(&mut builder, layout.arguments)?;
|
||||
let argument_type =
|
||||
build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?;
|
||||
|
||||
// does not make any assumptions about the input
|
||||
// let argument = builder.add_unknown_with(block, &[], argument_type)?;
|
||||
@ -308,7 +309,11 @@ fn build_entry_point(
|
||||
|
||||
let block = builder.add_block();
|
||||
|
||||
let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?;
|
||||
let type_id = layout_spec(
|
||||
&mut builder,
|
||||
&Layout::struct_no_name_order(layouts),
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let argument = builder.add_unknown_with(block, &[], type_id)?;
|
||||
|
||||
@ -352,8 +357,9 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)>
|
||||
let arg_type_id = layout_spec(
|
||||
&mut builder,
|
||||
&Layout::struct_no_name_order(&argument_layouts),
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?;
|
||||
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let spec = builder.build(arg_type_id, ret_type_id, root)?;
|
||||
|
||||
@ -457,10 +463,14 @@ fn stmt_spec<'a>(
|
||||
let mut type_ids = Vec::new();
|
||||
|
||||
for p in parameters.iter() {
|
||||
type_ids.push(layout_spec(builder, &p.layout)?);
|
||||
type_ids.push(layout_spec(
|
||||
builder,
|
||||
&p.layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?);
|
||||
}
|
||||
|
||||
let ret_type_id = layout_spec(builder, layout)?;
|
||||
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
|
||||
|
||||
@ -500,14 +510,14 @@ fn stmt_spec<'a>(
|
||||
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
|
||||
}
|
||||
Jump(id, symbols) => {
|
||||
let ret_type_id = layout_spec(builder, layout)?;
|
||||
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let argument = build_tuple_value(builder, env, block, symbols)?;
|
||||
|
||||
let jpid = env.join_points[id];
|
||||
builder.add_jump(block, jpid, argument, ret_type_id)
|
||||
}
|
||||
RuntimeError(_) => {
|
||||
let type_id = layout_spec(builder, layout)?;
|
||||
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
builder.add_terminate(block, type_id)
|
||||
}
|
||||
@ -556,11 +566,15 @@ fn build_recursive_tuple_type(
|
||||
builder.add_tuple_type(&field_types)
|
||||
}
|
||||
|
||||
fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result<TypeId> {
|
||||
fn build_tuple_type(
|
||||
builder: &mut impl TypeContext,
|
||||
layouts: &[Layout],
|
||||
when_recursive: &WhenRecursive,
|
||||
) -> Result<TypeId> {
|
||||
let mut field_types = Vec::new();
|
||||
|
||||
for field in layouts.iter() {
|
||||
field_types.push(layout_spec(builder, field)?);
|
||||
field_types.push(layout_spec(builder, field, when_recursive)?);
|
||||
}
|
||||
|
||||
builder.add_tuple_type(&field_types)
|
||||
@ -691,7 +705,7 @@ fn call_spec(
|
||||
.map(|symbol| env.symbols[symbol])
|
||||
.collect();
|
||||
|
||||
let result_type = layout_spec(builder, ret_layout)?;
|
||||
let result_type = layout_spec(builder, ret_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
builder.add_unknown_with(block, &arguments, result_type)
|
||||
}
|
||||
@ -761,7 +775,8 @@ fn call_spec(
|
||||
};
|
||||
|
||||
let state_layout = argument_layouts[0];
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let init_state = state;
|
||||
|
||||
add_loop(builder, block, state_type, init_state, loop_body)
|
||||
@ -782,7 +797,8 @@ fn call_spec(
|
||||
};
|
||||
|
||||
let state_layout = argument_layouts[0];
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let init_state = state;
|
||||
|
||||
add_loop(builder, block, state_type, init_state, loop_body)
|
||||
@ -806,7 +822,8 @@ fn call_spec(
|
||||
};
|
||||
|
||||
let state_layout = argument_layouts[0];
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let init_state = state;
|
||||
|
||||
add_loop(builder, block, state_type, init_state, loop_body)
|
||||
@ -828,10 +845,12 @@ fn call_spec(
|
||||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(builder, return_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
@ -851,10 +870,12 @@ fn call_spec(
|
||||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(builder, return_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
@ -879,7 +900,8 @@ fn call_spec(
|
||||
};
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let init_state = list;
|
||||
|
||||
add_loop(builder, block, state_type, init_state, loop_body)
|
||||
@ -903,10 +925,12 @@ fn call_spec(
|
||||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(builder, return_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
@ -936,10 +960,12 @@ fn call_spec(
|
||||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(builder, return_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
@ -975,10 +1001,12 @@ fn call_spec(
|
||||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(builder, return_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
@ -1010,7 +1038,8 @@ fn call_spec(
|
||||
};
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let init_state = list;
|
||||
|
||||
add_loop(builder, block, state_type, init_state, loop_body)
|
||||
@ -1087,11 +1116,13 @@ fn call_spec(
|
||||
)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(builder, &output_element_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(builder, &output_element_layout, &WhenRecursive::Unreachable)?;
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(&output_element_layout));
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
add_loop(builder, block, state_type, init_state, loop_body)
|
||||
}
|
||||
@ -1108,7 +1139,8 @@ fn call_spec(
|
||||
};
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::Bool);
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let init_state = new_num(builder, block)?;
|
||||
|
||||
@ -1127,7 +1159,8 @@ fn call_spec(
|
||||
};
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::Bool);
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let init_state = new_num(builder, block)?;
|
||||
|
||||
@ -1139,7 +1172,8 @@ fn call_spec(
|
||||
// ListFindUnsafe returns { value: v, found: Bool=Int1 }
|
||||
let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)];
|
||||
let output_layout = Layout::struct_no_name_order(&output_layouts);
|
||||
let output_type = layout_spec(builder, &output_layout)?;
|
||||
let output_type =
|
||||
layout_spec(builder, &output_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let loop_body = |builder: &mut FuncDefBuilder, block, output| {
|
||||
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
||||
@ -1201,7 +1235,7 @@ fn lowlevel_spec(
|
||||
) -> Result<ValueId> {
|
||||
use LowLevel::*;
|
||||
|
||||
let type_id = layout_spec(builder, layout)?;
|
||||
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let mode = update_mode.to_bytes();
|
||||
let update_mode_var = UpdateModeVar(&mode);
|
||||
|
||||
@ -1323,8 +1357,8 @@ fn lowlevel_spec(
|
||||
}
|
||||
DictEmpty => match layout {
|
||||
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
|
||||
let key_id = layout_spec(builder, key_layout)?;
|
||||
let value_id = layout_spec(builder, value_layout)?;
|
||||
let key_id = layout_spec(builder, key_layout, &WhenRecursive::Unreachable)?;
|
||||
let value_id = layout_spec(builder, value_layout, &WhenRecursive::Unreachable)?;
|
||||
new_dict(builder, block, key_id, value_id)
|
||||
}
|
||||
_ => unreachable!("empty array does not have a list layout"),
|
||||
@ -1367,7 +1401,7 @@ fn lowlevel_spec(
|
||||
// TODO overly pessimstic
|
||||
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
|
||||
|
||||
let result_type = layout_spec(builder, layout)?;
|
||||
let result_type = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
builder.add_unknown_with(block, &arguments, result_type)
|
||||
}
|
||||
@ -1478,7 +1512,8 @@ fn expr_spec<'a>(
|
||||
|
||||
let value_id = match tag_layout {
|
||||
UnionLayout::NonRecursive(tags) => {
|
||||
let variant_types = non_recursive_variant_types(builder, tags)?;
|
||||
let variant_types =
|
||||
non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?;
|
||||
let value_id = build_tuple_value(builder, env, block, arguments)?;
|
||||
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
|
||||
}
|
||||
@ -1592,7 +1627,7 @@ fn expr_spec<'a>(
|
||||
builder.add_get_tuple_field(block, value_id, *index as u32)
|
||||
}
|
||||
Array { elem_layout, elems } => {
|
||||
let type_id = layout_spec(builder, elem_layout)?;
|
||||
let type_id = layout_spec(builder, elem_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let list = new_list(builder, block, type_id)?;
|
||||
|
||||
@ -1619,19 +1654,19 @@ fn expr_spec<'a>(
|
||||
|
||||
EmptyArray => match layout {
|
||||
Layout::Builtin(Builtin::List(element_layout)) => {
|
||||
let type_id = layout_spec(builder, element_layout)?;
|
||||
let type_id = layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?;
|
||||
new_list(builder, block, type_id)
|
||||
}
|
||||
_ => unreachable!("empty array does not have a list layout"),
|
||||
},
|
||||
Reset { symbol, .. } => {
|
||||
let type_id = layout_spec(builder, layout)?;
|
||||
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let value_id = env.symbols[symbol];
|
||||
|
||||
builder.add_unknown_with(block, &[value_id], type_id)
|
||||
}
|
||||
RuntimeErrorFunction(_) => {
|
||||
let type_id = layout_spec(builder, layout)?;
|
||||
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
builder.add_terminate(block, type_id)
|
||||
}
|
||||
@ -1658,18 +1693,24 @@ fn literal_spec(
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result<TypeId> {
|
||||
layout_spec_help(builder, layout, &WhenRecursive::Unreachable)
|
||||
fn layout_spec(
|
||||
builder: &mut impl TypeContext,
|
||||
layout: &Layout,
|
||||
when_recursive: &WhenRecursive,
|
||||
) -> Result<TypeId> {
|
||||
layout_spec_help(builder, layout, when_recursive)
|
||||
}
|
||||
|
||||
fn non_recursive_variant_types(
|
||||
builder: &mut impl TypeContext,
|
||||
tags: &[&[Layout]],
|
||||
// If there is a recursive pointer latent within this layout, coming from a containing layout.
|
||||
when_recursive: &WhenRecursive,
|
||||
) -> Result<Vec<TypeId>> {
|
||||
let mut result = Vec::with_capacity(tags.len());
|
||||
|
||||
for tag in tags.iter() {
|
||||
result.push(build_tuple_type(builder, tag)?);
|
||||
result.push(build_tuple_type(builder, tag, when_recursive)?);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
@ -1701,7 +1742,7 @@ fn layout_spec_help(
|
||||
builder.add_tuple_type(&[])
|
||||
}
|
||||
UnionLayout::NonRecursive(tags) => {
|
||||
let variant_types = non_recursive_variant_types(builder, tags)?;
|
||||
let variant_types = non_recursive_variant_types(builder, tags, when_recursive)?;
|
||||
builder.add_union_type(&variant_types)
|
||||
}
|
||||
UnionLayout::Recursive(_)
|
||||
|
@ -30,7 +30,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
libloading = "0.7.1"
|
||||
tempfile = "3.2.0"
|
||||
inkwell = { path = "../../vendor/inkwell", optional = true }
|
||||
target-lexicon = "0.12.2"
|
||||
target-lexicon = "0.12.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
serde_json = "1.0.69"
|
||||
|
@ -4,7 +4,7 @@ Builtins are the functions and modules that are implicitly imported into every m
|
||||
|
||||
### module/src/symbol.rs
|
||||
|
||||
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
|
||||
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `rem` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
|
||||
|
||||
Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.
|
||||
|
||||
|
@ -61,6 +61,7 @@ interface Num
|
||||
isPositive,
|
||||
isZero,
|
||||
log,
|
||||
logChecked,
|
||||
maxFloat,
|
||||
maxI8,
|
||||
maxU8,
|
||||
@ -88,6 +89,7 @@ interface Num
|
||||
pow,
|
||||
powInt,
|
||||
rem,
|
||||
remChecked,
|
||||
round,
|
||||
shiftLeftBy,
|
||||
shiftRightBy,
|
||||
@ -97,6 +99,7 @@ interface Num
|
||||
subChecked,
|
||||
subWrap,
|
||||
sqrt,
|
||||
sqrtChecked,
|
||||
tan,
|
||||
toI8,
|
||||
toI8Checked,
|
||||
@ -802,27 +805,18 @@ toDec : Num * -> Dec
|
||||
## This is the same as the #// operator.
|
||||
divTrunc : Int a, Int a -> Int a
|
||||
|
||||
## Perform flooring modulo on two integers.
|
||||
## Obtain the remainder (truncating modulo) from the division of two integers.
|
||||
##
|
||||
## Modulo is the same as remainder when working with positive numbers,
|
||||
## but if either number is negative, then modulo works differently.
|
||||
## `a % b` is shorthand for `Num.rem a b`.
|
||||
##
|
||||
## Additionally, flooring modulo uses [Float].floor on the result.
|
||||
## >>> 5 % 7
|
||||
##
|
||||
## (Use [Float].mod for non-flooring modulo.)
|
||||
## >>> Num.rem 5 7
|
||||
##
|
||||
## Return `Err DivByZero` if the second integer is zero, because division by zero is undefined in mathematics.
|
||||
## >>> -8 % -3
|
||||
##
|
||||
## `a %% b` is shorthand for `Int.modFloor a b`.
|
||||
##
|
||||
## >>> 5 %% 7
|
||||
##
|
||||
## >>> Int.modFloor 5 7
|
||||
##
|
||||
## >>> -8 %% -3
|
||||
##
|
||||
## >>> Int.modFloor -8 -3
|
||||
#modFloor : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
## >>> Num.rem -8 -3
|
||||
rem : Int a, Int a -> Int a
|
||||
|
||||
|
||||
## Bitwise
|
||||
@ -1094,31 +1088,6 @@ atan : Float a -> Float a
|
||||
## >>> |> Num.div 2.0
|
||||
div : Float a, Float a -> Float a
|
||||
|
||||
## Perform modulo on two [Float]s.
|
||||
##
|
||||
## Modulo is the same as remainder when working with positive numbers,
|
||||
## but if either number is negative, then modulo works differently.
|
||||
##
|
||||
## `a % b` is shorthand for `Num.mod a b`.
|
||||
##
|
||||
## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero),
|
||||
## and as such, so is modulo by zero. Because of this, you should make sure never
|
||||
## to pass zero for the second argument to this function!
|
||||
##
|
||||
## Passing [mod] a [Dec] value of zero for its second argument will cause a panic.
|
||||
## Passing [mod] a [F32] and [F64] value for its second argument will cause it
|
||||
## to return [*NaN*](Num.isNaN).
|
||||
##
|
||||
## >>> 5.0 % 7.0
|
||||
##
|
||||
## >>> Num.mod 5 7
|
||||
##
|
||||
## `Num.mod` can be convenient in pipelines.
|
||||
##
|
||||
## >>> Num.pi
|
||||
## >>> |> Num.mod 2.0
|
||||
mod : Float a, Float a -> Float a
|
||||
|
||||
## Raises a [Float] to the power of another [Float].
|
||||
##
|
||||
## `
|
||||
@ -1314,7 +1283,7 @@ isInfinite : Float * -> Bool
|
||||
##
|
||||
## >>> Num.isNaN 12.3
|
||||
##
|
||||
## >>> Num.isNaN (Num.sqrt -2)
|
||||
## >>> Num.isNaN (Num.pow -1 0.5)
|
||||
##
|
||||
## *NaN* is unusual from other numberic values in that:
|
||||
## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `False` if either argument is *NaN*.
|
||||
|
@ -66,10 +66,13 @@ interface Num
|
||||
isPositive,
|
||||
isNegative,
|
||||
rem,
|
||||
remChecked,
|
||||
div,
|
||||
divChecked,
|
||||
sqrt,
|
||||
sqrtChecked,
|
||||
log,
|
||||
logChecked,
|
||||
round,
|
||||
ceiling,
|
||||
floor,
|
||||
@ -239,19 +242,22 @@ asin : Float a -> Float a
|
||||
acos : Float a -> Float a
|
||||
atan : Float a -> Float a
|
||||
|
||||
sqrt : Float a -> Result (Float a) [ SqrtOfNegative ]*
|
||||
log : Float a -> Result (Float a) [ LogNeedsPositive ]*
|
||||
sqrt : Float a -> Float a
|
||||
sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]*
|
||||
log : Float a -> Float a
|
||||
logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]*
|
||||
|
||||
div : Float a, Float a -> Float a
|
||||
divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]*
|
||||
|
||||
divCeil : Int a, Int a -> Int a
|
||||
divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
|
||||
divTrunc : Int a, Int a -> Int a
|
||||
divTruncChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
# mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
|
||||
|
||||
rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
# mod : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
rem : Int a, Int a -> Int a
|
||||
remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
|
||||
isMultipleOf : Int a, Int a -> Bool
|
||||
|
||||
bitwiseAnd : Int a, Int a -> Int a
|
||||
|
@ -393,16 +393,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
Box::new(int_type(flex(TVAR2)))
|
||||
);
|
||||
|
||||
// rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
// rem : Int a, Int a -> Int a
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_REM,
|
||||
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
|
||||
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
|
||||
Box::new(int_type(flex(TVAR1))),
|
||||
);
|
||||
|
||||
// mod : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
// remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_MOD_INT,
|
||||
Symbol::NUM_REM_CHECKED,
|
||||
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
|
||||
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
|
||||
);
|
||||
@ -680,36 +680,43 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_DIV_FLOAT_CHECKED,
|
||||
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
|
||||
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())),
|
||||
);
|
||||
|
||||
// mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_MOD_FLOAT,
|
||||
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
|
||||
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero)),
|
||||
);
|
||||
|
||||
// sqrt : Float a -> Float a
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_SQRT,
|
||||
vec![float_type(flex(TVAR1))],
|
||||
Box::new(float_type(flex(TVAR1))),
|
||||
);
|
||||
|
||||
// sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]*
|
||||
let sqrt_of_negative = SolvedType::TagUnion(
|
||||
vec![(TagName::Global("SqrtOfNegative".into()), vec![])],
|
||||
Box::new(SolvedType::Wildcard),
|
||||
);
|
||||
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_SQRT,
|
||||
Symbol::NUM_SQRT_CHECKED,
|
||||
vec![float_type(flex(TVAR1))],
|
||||
Box::new(result_type(float_type(flex(TVAR1)), sqrt_of_negative)),
|
||||
);
|
||||
|
||||
// log : Float a -> Float a
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_LOG,
|
||||
vec![float_type(flex(TVAR1))],
|
||||
Box::new(float_type(flex(TVAR1))),
|
||||
);
|
||||
|
||||
// logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]*
|
||||
let log_needs_positive = SolvedType::TagUnion(
|
||||
vec![(TagName::Global("LogNeedsPositive".into()), vec![])],
|
||||
Box::new(SolvedType::Wildcard),
|
||||
);
|
||||
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_LOG,
|
||||
Symbol::NUM_LOG_CHECKED,
|
||||
vec![float_type(flex(TVAR1))],
|
||||
Box::new(result_type(float_type(flex(TVAR1)), log_needs_positive)),
|
||||
);
|
||||
|
@ -204,9 +204,12 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||
NUM_ABS => num_abs,
|
||||
NUM_NEG => num_neg,
|
||||
NUM_REM => num_rem,
|
||||
NUM_REM_CHECKED => num_rem_checked,
|
||||
NUM_IS_MULTIPLE_OF => num_is_multiple_of,
|
||||
NUM_SQRT => num_sqrt,
|
||||
NUM_SQRT_CHECKED => num_sqrt_checked,
|
||||
NUM_LOG => num_log,
|
||||
NUM_LOG_CHECKED => num_log_checked,
|
||||
NUM_ROUND => num_round,
|
||||
NUM_IS_ODD => num_is_odd,
|
||||
NUM_IS_EVEN => num_is_even,
|
||||
@ -713,6 +716,23 @@ fn bool_and(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
fn num_unaryop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
|
||||
let num_var = var_store.fresh();
|
||||
let body = RunLowLevel {
|
||||
op,
|
||||
args: vec![(num_var, Var(Symbol::ARG_1))],
|
||||
ret_var: num_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(num_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
num_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// Num a, Num a -> Num a
|
||||
fn num_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
|
||||
let num_var = var_store.fresh();
|
||||
@ -1152,8 +1172,13 @@ fn num_to_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.sqrt : Float -> Result Float [ SqrtOfNegative ]*
|
||||
/// Num.sqrt : Float a -> Float a
|
||||
fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
num_unaryop(symbol, var_store, LowLevel::NumSqrtUnchecked)
|
||||
}
|
||||
|
||||
/// Num.sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]*
|
||||
fn num_sqrt_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let float_var = var_store.fresh();
|
||||
let unbound_zero_var = var_store.fresh();
|
||||
@ -1201,8 +1226,13 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.log : Float -> Result Float [ LogNeedsPositive ]*
|
||||
/// Num.log : Float a -> Float a
|
||||
fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
num_unaryop(symbol, var_store, LowLevel::NumLogUnchecked)
|
||||
}
|
||||
|
||||
/// Num.logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]*
|
||||
fn num_log_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let float_var = var_store.fresh();
|
||||
let unbound_zero_var = var_store.fresh();
|
||||
@ -4084,8 +4114,13 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
/// Num.rem : Int a, Int a -> Int a
|
||||
fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
num_binop(symbol, var_store, LowLevel::NumRemUnchecked)
|
||||
}
|
||||
|
||||
/// Num.remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
fn num_rem_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let num_var = var_store.fresh();
|
||||
let unbound_zero_var = var_store.fresh();
|
||||
let bool_var = var_store.fresh();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -182,8 +182,4 @@ impl<'a> Env<'a> {
|
||||
pub fn problem(&mut self, problem: Problem) {
|
||||
self.problems.push(problem)
|
||||
}
|
||||
|
||||
pub fn register_closure(&mut self, symbol: Symbol, references: References) {
|
||||
self.closures.insert(symbol, references);
|
||||
}
|
||||
}
|
||||
|
@ -716,7 +716,9 @@ pub fn canonicalize_expr<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
env.register_closure(symbol, output.references.clone());
|
||||
// store the references of this function in the Env. This information is used
|
||||
// when we canonicalize a surrounding def (if it exists)
|
||||
env.closures.insert(symbol, output.references.clone());
|
||||
|
||||
let mut captured_symbols: Vec<_> = captured_symbols
|
||||
.into_iter()
|
||||
|
@ -425,7 +425,6 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
|
||||
Slash => (ModuleName::NUM, "div"),
|
||||
DoubleSlash => (ModuleName::NUM, "divTrunc"),
|
||||
Percent => (ModuleName::NUM, "rem"),
|
||||
DoublePercent => (ModuleName::NUM, "mod"),
|
||||
Plus => (ModuleName::NUM, "add"),
|
||||
Minus => (ModuleName::NUM, "sub"),
|
||||
Equals => (ModuleName::BOOL, "isEq"),
|
||||
|
@ -39,6 +39,31 @@ impl ReferenceMatrix {
|
||||
pub fn get(&self, index: usize) -> bool {
|
||||
self.bitvec[index]
|
||||
}
|
||||
|
||||
pub fn is_recursive(&self, index: usize) -> bool {
|
||||
let mut scheduled = self.row_slice(index).to_bitvec();
|
||||
let mut visited = self.row_slice(index).to_bitvec();
|
||||
|
||||
// yes this is a bit inefficient because rows are visited repeatedly.
|
||||
while scheduled.any() {
|
||||
for one in scheduled.iter_ones() {
|
||||
if one == index {
|
||||
return true;
|
||||
}
|
||||
|
||||
visited |= self.row_slice(one)
|
||||
}
|
||||
|
||||
// i.e. visited did not change
|
||||
if visited.count_ones() == scheduled.count_ones() {
|
||||
break;
|
||||
}
|
||||
|
||||
scheduled |= &visited;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Topological sort and strongly-connected components
|
||||
|
@ -372,7 +372,6 @@ fn push_op(buf: &mut Buf, op: BinOp) {
|
||||
called_via::BinOp::Slash => buf.push('/'),
|
||||
called_via::BinOp::DoubleSlash => buf.push_str("//"),
|
||||
called_via::BinOp::Percent => buf.push('%'),
|
||||
called_via::BinOp::DoublePercent => buf.push_str("%%"),
|
||||
called_via::BinOp::Plus => buf.push('+'),
|
||||
called_via::BinOp::Minus => buf.push('-'),
|
||||
called_via::BinOp::Equals => buf.push_str("=="),
|
||||
@ -1104,7 +1103,6 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
|
||||
| BinOp::Slash
|
||||
| BinOp::DoubleSlash
|
||||
| BinOp::Percent
|
||||
| BinOp::DoublePercent
|
||||
| BinOp::Plus
|
||||
| BinOp::Minus
|
||||
| BinOp::Equals
|
||||
|
@ -2537,7 +2537,7 @@ mod test_fmt {
|
||||
indoc!(
|
||||
r#"
|
||||
2 % 3
|
||||
%% 5
|
||||
// 5
|
||||
+ 7
|
||||
"#
|
||||
),
|
||||
@ -2545,7 +2545,7 @@ mod test_fmt {
|
||||
r#"
|
||||
2
|
||||
% 3
|
||||
%% 5
|
||||
// 5
|
||||
+ 7
|
||||
"#
|
||||
),
|
||||
|
@ -19,7 +19,7 @@ roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
target-lexicon = "0.12.2"
|
||||
target-lexicon = "0.12.3"
|
||||
# TODO: Deal with the update of object to 0.27.
|
||||
# It looks like it breaks linking the generated objects.
|
||||
# Probably just need to specify an extra field that used to be implicit or something.
|
||||
|
@ -18,4 +18,4 @@ roc_std = { path = "../../roc_std", default-features = false }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
inkwell = { path = "../../vendor/inkwell" }
|
||||
target-lexicon = "0.12.2"
|
||||
target-lexicon = "0.12.3"
|
||||
|
@ -6915,7 +6915,6 @@ fn build_float_binop<'a, 'ctx, 'env>(
|
||||
NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(),
|
||||
NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(),
|
||||
NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(),
|
||||
NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(),
|
||||
NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(),
|
||||
NumPow => env.call_intrinsic(&LLVM_POW[float_width], &[lhs.into(), rhs.into()]),
|
||||
_ => {
|
||||
|
@ -1809,7 +1809,39 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
||||
|
||||
for (i, field_layout) in field_layouts.iter().enumerate() {
|
||||
if let Layout::RecursivePointer = field_layout {
|
||||
panic!("non-recursive tag unions cannot contain naked recursion pointers!");
|
||||
let recursive_union_layout = match when_recursive {
|
||||
WhenRecursive::Unreachable => {
|
||||
panic!("non-recursive tag unions cannot contain naked recursion pointers!");
|
||||
}
|
||||
WhenRecursive::Loop(recursive_union_layout) => recursive_union_layout,
|
||||
};
|
||||
|
||||
// This field is a pointer to the recursive pointer.
|
||||
let field_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
|
||||
.unwrap();
|
||||
|
||||
// This is the actual pointer to the recursive data.
|
||||
let field_value = env.builder.build_load(field_ptr, "load_recursive_pointer");
|
||||
|
||||
debug_assert!(field_value.is_pointer_value());
|
||||
|
||||
// therefore we must cast it to our desired type
|
||||
let union_type =
|
||||
basic_type_from_layout(env, &Layout::Union(*recursive_union_layout));
|
||||
let recursive_ptr_field_value =
|
||||
cast_basic_basic(env.builder, field_value, union_type);
|
||||
|
||||
modify_refcount_layout_help(
|
||||
env,
|
||||
parent,
|
||||
layout_ids,
|
||||
mode.to_call_mode(fn_val),
|
||||
when_recursive,
|
||||
recursive_ptr_field_value,
|
||||
&Layout::RecursivePointer,
|
||||
)
|
||||
} else if field_layout.contains_refcounted() {
|
||||
let field_ptr = env
|
||||
.builder
|
||||
|
@ -34,7 +34,6 @@ pub enum BinOp {
|
||||
Slash,
|
||||
DoubleSlash,
|
||||
Percent,
|
||||
DoublePercent,
|
||||
Plus,
|
||||
Minus,
|
||||
Equals,
|
||||
@ -58,8 +57,8 @@ impl BinOp {
|
||||
pub fn width(self) -> u16 {
|
||||
match self {
|
||||
Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1,
|
||||
DoubleSlash | DoublePercent | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq
|
||||
| And | Or | Pizza => 2,
|
||||
DoubleSlash | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq | And | Or
|
||||
| Pizza => 2,
|
||||
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
|
||||
}
|
||||
}
|
||||
@ -97,9 +96,7 @@ impl BinOp {
|
||||
use self::Associativity::*;
|
||||
|
||||
match self {
|
||||
Pizza | Star | Slash | DoubleSlash | DoublePercent | Percent | Plus | Minus => {
|
||||
LeftAssociative
|
||||
}
|
||||
Pizza | Star | Slash | DoubleSlash | Percent | Plus | Minus => LeftAssociative,
|
||||
And | Or | Caret => RightAssociative,
|
||||
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => {
|
||||
NonAssociative
|
||||
@ -111,7 +108,7 @@ impl BinOp {
|
||||
fn precedence(self) -> u8 {
|
||||
match self {
|
||||
Caret => 7,
|
||||
Star | Slash | DoubleSlash | DoublePercent | Percent => 6,
|
||||
Star | Slash | DoubleSlash | Percent => 6,
|
||||
Plus | Minus => 5,
|
||||
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4,
|
||||
And => 3,
|
||||
@ -142,7 +139,6 @@ impl std::fmt::Display for BinOp {
|
||||
Slash => "/",
|
||||
DoubleSlash => "//",
|
||||
Percent => "%",
|
||||
DoublePercent => "%%",
|
||||
Plus => "+",
|
||||
Minus => "-",
|
||||
Equals => "==",
|
||||
|
@ -294,14 +294,17 @@ impl LowLevelWrapperType {
|
||||
Symbol::NUM_DIV_FLOAT_CHECKED => WrapperIsRequired,
|
||||
Symbol::NUM_DIV_CEIL => CanBeReplacedBy(NumDivCeilUnchecked),
|
||||
Symbol::NUM_DIV_CEIL_CHECKED => WrapperIsRequired,
|
||||
Symbol::NUM_REM => WrapperIsRequired,
|
||||
Symbol::NUM_REM => CanBeReplacedBy(NumRemUnchecked),
|
||||
Symbol::NUM_REM_CHECKED => WrapperIsRequired,
|
||||
Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf),
|
||||
Symbol::NUM_ABS => CanBeReplacedBy(NumAbs),
|
||||
Symbol::NUM_NEG => CanBeReplacedBy(NumNeg),
|
||||
Symbol::NUM_SIN => CanBeReplacedBy(NumSin),
|
||||
Symbol::NUM_COS => CanBeReplacedBy(NumCos),
|
||||
Symbol::NUM_SQRT => WrapperIsRequired,
|
||||
Symbol::NUM_LOG => WrapperIsRequired,
|
||||
Symbol::NUM_SQRT => CanBeReplacedBy(NumSqrtUnchecked),
|
||||
Symbol::NUM_SQRT_CHECKED => WrapperIsRequired,
|
||||
Symbol::NUM_LOG => CanBeReplacedBy(NumLogUnchecked),
|
||||
Symbol::NUM_LOG_CHECKED => WrapperIsRequired,
|
||||
Symbol::NUM_ROUND => CanBeReplacedBy(NumRound),
|
||||
Symbol::NUM_TO_FLOAT => CanBeReplacedBy(NumToFloat),
|
||||
Symbol::NUM_POW => CanBeReplacedBy(NumPow),
|
||||
|
@ -950,121 +950,117 @@ define_builtins! {
|
||||
41 NUM_DIV_FLOAT_CHECKED: "divChecked"
|
||||
42 NUM_DIV_TRUNC: "divTrunc"
|
||||
43 NUM_DIV_TRUNC_CHECKED: "divTruncChecked"
|
||||
44 NUM_MOD_INT: "modInt"
|
||||
45 NUM_MOD_INT_CHECKED: "modIntChecked"
|
||||
46 NUM_MOD_FLOAT: "modFloat"
|
||||
47 NUM_MOD_FLOAT_CHECKED: "modFloatChecked"
|
||||
48 NUM_SQRT: "sqrt"
|
||||
49 NUM_SQRT_CHECKED: "sqrtChecked"
|
||||
50 NUM_LOG: "log"
|
||||
51 NUM_LOG_CHECKED: "logChecked"
|
||||
52 NUM_ROUND: "round"
|
||||
53 NUM_COMPARE: "compare"
|
||||
54 NUM_POW: "pow"
|
||||
55 NUM_CEILING: "ceiling"
|
||||
56 NUM_POW_INT: "powInt"
|
||||
57 NUM_FLOOR: "floor"
|
||||
58 NUM_ADD_WRAP: "addWrap"
|
||||
59 NUM_ADD_CHECKED: "addChecked"
|
||||
60 NUM_ADD_SATURATED: "addSaturated"
|
||||
61 NUM_ATAN: "atan"
|
||||
62 NUM_ACOS: "acos"
|
||||
63 NUM_ASIN: "asin"
|
||||
64 NUM_AT_SIGNED128: "@Signed128"
|
||||
65 NUM_SIGNED128: "Signed128"
|
||||
66 NUM_AT_SIGNED64: "@Signed64"
|
||||
67 NUM_SIGNED64: "Signed64"
|
||||
68 NUM_AT_SIGNED32: "@Signed32"
|
||||
69 NUM_SIGNED32: "Signed32"
|
||||
70 NUM_AT_SIGNED16: "@Signed16"
|
||||
71 NUM_SIGNED16: "Signed16"
|
||||
72 NUM_AT_SIGNED8: "@Signed8"
|
||||
73 NUM_SIGNED8: "Signed8"
|
||||
74 NUM_AT_UNSIGNED128: "@Unsigned128"
|
||||
75 NUM_UNSIGNED128: "Unsigned128"
|
||||
76 NUM_AT_UNSIGNED64: "@Unsigned64"
|
||||
77 NUM_UNSIGNED64: "Unsigned64"
|
||||
78 NUM_AT_UNSIGNED32: "@Unsigned32"
|
||||
79 NUM_UNSIGNED32: "Unsigned32"
|
||||
80 NUM_AT_UNSIGNED16: "@Unsigned16"
|
||||
81 NUM_UNSIGNED16: "Unsigned16"
|
||||
82 NUM_AT_UNSIGNED8: "@Unsigned8"
|
||||
83 NUM_UNSIGNED8: "Unsigned8"
|
||||
84 NUM_AT_BINARY64: "@Binary64"
|
||||
85 NUM_BINARY64: "Binary64"
|
||||
86 NUM_AT_BINARY32: "@Binary32"
|
||||
87 NUM_BINARY32: "Binary32"
|
||||
88 NUM_BITWISE_AND: "bitwiseAnd"
|
||||
89 NUM_BITWISE_XOR: "bitwiseXor"
|
||||
90 NUM_BITWISE_OR: "bitwiseOr"
|
||||
91 NUM_SHIFT_LEFT: "shiftLeftBy"
|
||||
92 NUM_SHIFT_RIGHT: "shiftRightBy"
|
||||
93 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
|
||||
94 NUM_SUB_WRAP: "subWrap"
|
||||
95 NUM_SUB_CHECKED: "subChecked"
|
||||
96 NUM_SUB_SATURATED: "subSaturated"
|
||||
97 NUM_MUL_WRAP: "mulWrap"
|
||||
98 NUM_MUL_CHECKED: "mulChecked"
|
||||
99 NUM_INT: "Int"
|
||||
100 NUM_FLOAT: "Float"
|
||||
101 NUM_AT_NATURAL: "@Natural"
|
||||
102 NUM_NATURAL: "Natural"
|
||||
103 NUM_NAT: "Nat"
|
||||
104 NUM_INT_CAST: "intCast"
|
||||
105 NUM_IS_MULTIPLE_OF: "isMultipleOf"
|
||||
106 NUM_AT_DECIMAL: "@Decimal"
|
||||
107 NUM_DECIMAL: "Decimal"
|
||||
108 NUM_DEC: "Dec" // the Num.Dectype alias
|
||||
109 NUM_BYTES_TO_U16: "bytesToU16"
|
||||
110 NUM_BYTES_TO_U32: "bytesToU32"
|
||||
111 NUM_CAST_TO_NAT: "#castToNat"
|
||||
112 NUM_DIV_CEIL: "divCeil"
|
||||
113 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
|
||||
114 NUM_TO_STR: "toStr"
|
||||
115 NUM_MIN_I8: "minI8"
|
||||
116 NUM_MAX_I8: "maxI8"
|
||||
117 NUM_MIN_U8: "minU8"
|
||||
118 NUM_MAX_U8: "maxU8"
|
||||
119 NUM_MIN_I16: "minI16"
|
||||
120 NUM_MAX_I16: "maxI16"
|
||||
121 NUM_MIN_U16: "minU16"
|
||||
122 NUM_MAX_U16: "maxU16"
|
||||
123 NUM_MIN_I32: "minI32"
|
||||
124 NUM_MAX_I32: "maxI32"
|
||||
125 NUM_MIN_U32: "minU32"
|
||||
126 NUM_MAX_U32: "maxU32"
|
||||
127 NUM_MIN_I64: "minI64"
|
||||
128 NUM_MAX_I64: "maxI64"
|
||||
129 NUM_MIN_U64: "minU64"
|
||||
130 NUM_MAX_U64: "maxU64"
|
||||
131 NUM_MIN_I128: "minI128"
|
||||
132 NUM_MAX_I128: "maxI128"
|
||||
133 NUM_TO_I8: "toI8"
|
||||
134 NUM_TO_I8_CHECKED: "toI8Checked"
|
||||
135 NUM_TO_I16: "toI16"
|
||||
136 NUM_TO_I16_CHECKED: "toI16Checked"
|
||||
137 NUM_TO_I32: "toI32"
|
||||
138 NUM_TO_I32_CHECKED: "toI32Checked"
|
||||
139 NUM_TO_I64: "toI64"
|
||||
140 NUM_TO_I64_CHECKED: "toI64Checked"
|
||||
141 NUM_TO_I128: "toI128"
|
||||
142 NUM_TO_I128_CHECKED: "toI128Checked"
|
||||
143 NUM_TO_U8: "toU8"
|
||||
144 NUM_TO_U8_CHECKED: "toU8Checked"
|
||||
145 NUM_TO_U16: "toU16"
|
||||
146 NUM_TO_U16_CHECKED: "toU16Checked"
|
||||
147 NUM_TO_U32: "toU32"
|
||||
148 NUM_TO_U32_CHECKED: "toU32Checked"
|
||||
149 NUM_TO_U64: "toU64"
|
||||
150 NUM_TO_U64_CHECKED: "toU64Checked"
|
||||
151 NUM_TO_U128: "toU128"
|
||||
152 NUM_TO_U128_CHECKED: "toU128Checked"
|
||||
153 NUM_TO_NAT: "toNat"
|
||||
154 NUM_TO_NAT_CHECKED: "toNatChecked"
|
||||
155 NUM_TO_F32: "toF32"
|
||||
156 NUM_TO_F32_CHECKED: "toF32Checked"
|
||||
157 NUM_TO_F64: "toF64"
|
||||
158 NUM_TO_F64_CHECKED: "toF64Checked"
|
||||
44 NUM_SQRT: "sqrt"
|
||||
45 NUM_SQRT_CHECKED: "sqrtChecked"
|
||||
46 NUM_LOG: "log"
|
||||
47 NUM_LOG_CHECKED: "logChecked"
|
||||
48 NUM_ROUND: "round"
|
||||
49 NUM_COMPARE: "compare"
|
||||
50 NUM_POW: "pow"
|
||||
51 NUM_CEILING: "ceiling"
|
||||
52 NUM_POW_INT: "powInt"
|
||||
53 NUM_FLOOR: "floor"
|
||||
54 NUM_ADD_WRAP: "addWrap"
|
||||
55 NUM_ADD_CHECKED: "addChecked"
|
||||
56 NUM_ADD_SATURATED: "addSaturated"
|
||||
57 NUM_ATAN: "atan"
|
||||
58 NUM_ACOS: "acos"
|
||||
59 NUM_ASIN: "asin"
|
||||
60 NUM_AT_SIGNED128: "@Signed128"
|
||||
61 NUM_SIGNED128: "Signed128"
|
||||
62 NUM_AT_SIGNED64: "@Signed64"
|
||||
63 NUM_SIGNED64: "Signed64"
|
||||
64 NUM_AT_SIGNED32: "@Signed32"
|
||||
65 NUM_SIGNED32: "Signed32"
|
||||
66 NUM_AT_SIGNED16: "@Signed16"
|
||||
67 NUM_SIGNED16: "Signed16"
|
||||
68 NUM_AT_SIGNED8: "@Signed8"
|
||||
69 NUM_SIGNED8: "Signed8"
|
||||
70 NUM_AT_UNSIGNED128: "@Unsigned128"
|
||||
71 NUM_UNSIGNED128: "Unsigned128"
|
||||
72 NUM_AT_UNSIGNED64: "@Unsigned64"
|
||||
73 NUM_UNSIGNED64: "Unsigned64"
|
||||
74 NUM_AT_UNSIGNED32: "@Unsigned32"
|
||||
75 NUM_UNSIGNED32: "Unsigned32"
|
||||
76 NUM_AT_UNSIGNED16: "@Unsigned16"
|
||||
77 NUM_UNSIGNED16: "Unsigned16"
|
||||
78 NUM_AT_UNSIGNED8: "@Unsigned8"
|
||||
79 NUM_UNSIGNED8: "Unsigned8"
|
||||
80 NUM_AT_BINARY64: "@Binary64"
|
||||
81 NUM_BINARY64: "Binary64"
|
||||
82 NUM_AT_BINARY32: "@Binary32"
|
||||
83 NUM_BINARY32: "Binary32"
|
||||
84 NUM_BITWISE_AND: "bitwiseAnd"
|
||||
85 NUM_BITWISE_XOR: "bitwiseXor"
|
||||
86 NUM_BITWISE_OR: "bitwiseOr"
|
||||
87 NUM_SHIFT_LEFT: "shiftLeftBy"
|
||||
88 NUM_SHIFT_RIGHT: "shiftRightBy"
|
||||
89 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
|
||||
90 NUM_SUB_WRAP: "subWrap"
|
||||
91 NUM_SUB_CHECKED: "subChecked"
|
||||
92 NUM_SUB_SATURATED: "subSaturated"
|
||||
93 NUM_MUL_WRAP: "mulWrap"
|
||||
94 NUM_MUL_CHECKED: "mulChecked"
|
||||
95 NUM_INT: "Int"
|
||||
96 NUM_FLOAT: "Float"
|
||||
97 NUM_AT_NATURAL: "@Natural"
|
||||
98 NUM_NATURAL: "Natural"
|
||||
99 NUM_NAT: "Nat"
|
||||
100 NUM_INT_CAST: "intCast"
|
||||
101 NUM_IS_MULTIPLE_OF: "isMultipleOf"
|
||||
102 NUM_AT_DECIMAL: "@Decimal"
|
||||
103 NUM_DECIMAL: "Decimal"
|
||||
104 NUM_DEC: "Dec" // the Num.Dectype alias
|
||||
105 NUM_BYTES_TO_U16: "bytesToU16"
|
||||
106 NUM_BYTES_TO_U32: "bytesToU32"
|
||||
107 NUM_CAST_TO_NAT: "#castToNat"
|
||||
108 NUM_DIV_CEIL: "divCeil"
|
||||
109 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
|
||||
110 NUM_TO_STR: "toStr"
|
||||
111 NUM_MIN_I8: "minI8"
|
||||
112 NUM_MAX_I8: "maxI8"
|
||||
113 NUM_MIN_U8: "minU8"
|
||||
114 NUM_MAX_U8: "maxU8"
|
||||
115 NUM_MIN_I16: "minI16"
|
||||
116 NUM_MAX_I16: "maxI16"
|
||||
117 NUM_MIN_U16: "minU16"
|
||||
118 NUM_MAX_U16: "maxU16"
|
||||
119 NUM_MIN_I32: "minI32"
|
||||
120 NUM_MAX_I32: "maxI32"
|
||||
121 NUM_MIN_U32: "minU32"
|
||||
122 NUM_MAX_U32: "maxU32"
|
||||
123 NUM_MIN_I64: "minI64"
|
||||
124 NUM_MAX_I64: "maxI64"
|
||||
125 NUM_MIN_U64: "minU64"
|
||||
126 NUM_MAX_U64: "maxU64"
|
||||
127 NUM_MIN_I128: "minI128"
|
||||
128 NUM_MAX_I128: "maxI128"
|
||||
129 NUM_TO_I8: "toI8"
|
||||
130 NUM_TO_I8_CHECKED: "toI8Checked"
|
||||
131 NUM_TO_I16: "toI16"
|
||||
132 NUM_TO_I16_CHECKED: "toI16Checked"
|
||||
133 NUM_TO_I32: "toI32"
|
||||
134 NUM_TO_I32_CHECKED: "toI32Checked"
|
||||
135 NUM_TO_I64: "toI64"
|
||||
136 NUM_TO_I64_CHECKED: "toI64Checked"
|
||||
137 NUM_TO_I128: "toI128"
|
||||
138 NUM_TO_I128_CHECKED: "toI128Checked"
|
||||
139 NUM_TO_U8: "toU8"
|
||||
140 NUM_TO_U8_CHECKED: "toU8Checked"
|
||||
141 NUM_TO_U16: "toU16"
|
||||
142 NUM_TO_U16_CHECKED: "toU16Checked"
|
||||
143 NUM_TO_U32: "toU32"
|
||||
144 NUM_TO_U32_CHECKED: "toU32Checked"
|
||||
145 NUM_TO_U64: "toU64"
|
||||
146 NUM_TO_U64_CHECKED: "toU64Checked"
|
||||
147 NUM_TO_U128: "toU128"
|
||||
148 NUM_TO_U128_CHECKED: "toU128Checked"
|
||||
149 NUM_TO_NAT: "toNat"
|
||||
150 NUM_TO_NAT_CHECKED: "toNatChecked"
|
||||
151 NUM_TO_F32: "toF32"
|
||||
152 NUM_TO_F32_CHECKED: "toF32Checked"
|
||||
153 NUM_TO_F64: "toF64"
|
||||
154 NUM_TO_F64_CHECKED: "toF64Checked"
|
||||
}
|
||||
2 BOOL: "Bool" => {
|
||||
0 BOOL_BOOL: "Bool" // the Bool.Bool type alias
|
||||
|
@ -3499,15 +3499,24 @@ pub fn with_hole<'a>(
|
||||
|
||||
OpaqueRef { argument, .. } => {
|
||||
let (arg_var, loc_arg_expr) = *argument;
|
||||
with_hole(
|
||||
env,
|
||||
loc_arg_expr.value,
|
||||
arg_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
|
||||
match can_reuse_symbol(env, procs, &loc_arg_expr.value) {
|
||||
// Opaques decay to their argument.
|
||||
ReuseSymbol::Value(real_name) => {
|
||||
let mut result = hole.clone();
|
||||
substitute_in_exprs(arena, &mut result, assigned, real_name);
|
||||
result
|
||||
}
|
||||
_ => with_hole(
|
||||
env,
|
||||
loc_arg_expr.value,
|
||||
arg_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
Record {
|
||||
|
@ -311,6 +311,50 @@ impl<'a> UnionLayout<'a> {
|
||||
.append(alloc.intersperse(tags_doc, ", "))
|
||||
.append(alloc.text("]"))
|
||||
}
|
||||
Recursive(tags) => {
|
||||
let tags_doc = tags.iter().map(|fields| {
|
||||
alloc.text("C ").append(alloc.intersperse(
|
||||
fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)),
|
||||
" ",
|
||||
))
|
||||
});
|
||||
alloc
|
||||
.text("[<r>")
|
||||
.append(alloc.intersperse(tags_doc, ", "))
|
||||
.append(alloc.text("]"))
|
||||
}
|
||||
NonNullableUnwrapped(fields) => {
|
||||
let fields_doc = alloc.text("C ").append(alloc.intersperse(
|
||||
fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)),
|
||||
" ",
|
||||
));
|
||||
alloc
|
||||
.text("[<rnnu>")
|
||||
.append(fields_doc)
|
||||
.append(alloc.text("]"))
|
||||
}
|
||||
NullableUnwrapped {
|
||||
nullable_id,
|
||||
other_fields,
|
||||
} => {
|
||||
let fields_doc = alloc.text("C ").append(
|
||||
alloc.intersperse(
|
||||
other_fields
|
||||
.iter()
|
||||
.map(|x| x.to_doc(alloc, Parens::InTypeParam)),
|
||||
" ",
|
||||
),
|
||||
);
|
||||
let tags_doc = if nullable_id {
|
||||
alloc.concat(vec![alloc.text("<null>, "), fields_doc])
|
||||
} else {
|
||||
alloc.concat(vec![fields_doc, alloc.text(", <null>")])
|
||||
};
|
||||
alloc
|
||||
.text("[<rnu>")
|
||||
.append(tags_doc)
|
||||
.append(alloc.text("]"))
|
||||
}
|
||||
_ => alloc.text("TODO"),
|
||||
}
|
||||
}
|
||||
@ -1731,7 +1775,7 @@ fn layout_from_flat_type<'a>(
|
||||
|
||||
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
|
||||
|
||||
Ok(layout_from_tag_union(arena, &tags, subs, env.target_info))
|
||||
Ok(layout_from_tag_union(env, &tags))
|
||||
}
|
||||
FunctionOrTagUnion(tag_name, _, ext_var) => {
|
||||
debug_assert!(
|
||||
@ -1742,7 +1786,7 @@ fn layout_from_flat_type<'a>(
|
||||
let union_tags = UnionTags::from_tag_name_index(tag_name);
|
||||
let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var);
|
||||
|
||||
Ok(layout_from_tag_union(arena, &tags, subs, env.target_info))
|
||||
Ok(layout_from_tag_union(env, &tags))
|
||||
}
|
||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
|
||||
@ -2071,23 +2115,14 @@ fn is_recursive_tag_union(layout: &Layout) -> bool {
|
||||
}
|
||||
|
||||
fn union_sorted_tags_help_new<'a>(
|
||||
arena: &'a Bump,
|
||||
env: &mut Env<'a, '_>,
|
||||
tags_list: &[(&'_ TagName, &[Variable])],
|
||||
opt_rec_var: Option<Variable>,
|
||||
subs: &Subs,
|
||||
target_info: TargetInfo,
|
||||
) -> UnionVariant<'a> {
|
||||
// sort up front; make sure the ordering stays intact!
|
||||
let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena);
|
||||
let mut tags_list = Vec::from_iter_in(tags_list.iter(), env.arena);
|
||||
tags_list.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
|
||||
|
||||
let mut env = Env {
|
||||
arena,
|
||||
subs,
|
||||
seen: Vec::new_in(arena),
|
||||
target_info,
|
||||
};
|
||||
|
||||
match tags_list.len() {
|
||||
0 => {
|
||||
// trying to instantiate a type with no values
|
||||
@ -2098,18 +2133,19 @@ fn union_sorted_tags_help_new<'a>(
|
||||
let tag_name = tag_name.clone();
|
||||
|
||||
// just one tag in the union (but with arguments) can be a struct
|
||||
let mut layouts = Vec::with_capacity_in(tags_list.len(), arena);
|
||||
let mut layouts = Vec::with_capacity_in(tags_list.len(), env.arena);
|
||||
|
||||
// special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int
|
||||
match tag_name {
|
||||
TagName::Private(Symbol::NUM_AT_NUM) => {
|
||||
let var = arguments[0];
|
||||
layouts
|
||||
.push(unwrap_num_tag(subs, var, target_info).expect("invalid num layout"));
|
||||
layouts.push(
|
||||
unwrap_num_tag(env.subs, var, env.target_info).expect("invalid num layout"),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
for &var in arguments {
|
||||
match Layout::from_var(&mut env, var) {
|
||||
match Layout::from_var(env, var) {
|
||||
Ok(layout) => {
|
||||
layouts.push(layout);
|
||||
}
|
||||
@ -2129,8 +2165,8 @@ fn union_sorted_tags_help_new<'a>(
|
||||
}
|
||||
|
||||
layouts.sort_by(|layout1, layout2| {
|
||||
let size1 = layout1.alignment_bytes(target_info);
|
||||
let size2 = layout2.alignment_bytes(target_info);
|
||||
let size1 = layout1.alignment_bytes(env.target_info);
|
||||
let size2 = layout2.alignment_bytes(env.target_info);
|
||||
|
||||
size2.cmp(&size1)
|
||||
});
|
||||
@ -2151,7 +2187,7 @@ fn union_sorted_tags_help_new<'a>(
|
||||
}
|
||||
num_tags => {
|
||||
// default path
|
||||
let mut answer = Vec::with_capacity_in(tags_list.len(), arena);
|
||||
let mut answer = Vec::with_capacity_in(tags_list.len(), env.arena);
|
||||
let mut has_any_arguments = false;
|
||||
|
||||
let mut nullable: Option<(TagIdIntType, TagName)> = None;
|
||||
@ -2174,17 +2210,19 @@ fn union_sorted_tags_help_new<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena);
|
||||
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, env.arena);
|
||||
|
||||
for &var in arguments {
|
||||
match Layout::from_var(&mut env, var) {
|
||||
match Layout::from_var(env, var) {
|
||||
Ok(layout) => {
|
||||
has_any_arguments = true;
|
||||
|
||||
// make sure to not unroll recursive types!
|
||||
let self_recursion = opt_rec_var.is_some()
|
||||
&& subs.get_root_key_without_compacting(var)
|
||||
== subs.get_root_key_without_compacting(opt_rec_var.unwrap())
|
||||
&& env.subs.get_root_key_without_compacting(var)
|
||||
== env
|
||||
.subs
|
||||
.get_root_key_without_compacting(opt_rec_var.unwrap())
|
||||
&& is_recursive_tag_union(&layout);
|
||||
|
||||
if self_recursion {
|
||||
@ -2207,8 +2245,8 @@ fn union_sorted_tags_help_new<'a>(
|
||||
}
|
||||
|
||||
arg_layouts.sort_by(|layout1, layout2| {
|
||||
let size1 = layout1.alignment_bytes(target_info);
|
||||
let size2 = layout2.alignment_bytes(target_info);
|
||||
let size1 = layout1.alignment_bytes(env.target_info);
|
||||
let size2 = layout2.alignment_bytes(env.target_info);
|
||||
|
||||
size2.cmp(&size1)
|
||||
});
|
||||
@ -2229,7 +2267,7 @@ fn union_sorted_tags_help_new<'a>(
|
||||
3..=MAX_ENUM_SIZE if !has_any_arguments => {
|
||||
// type can be stored in a byte
|
||||
// needs the sorted tag names to determine the tag_id
|
||||
let mut tag_names = Vec::with_capacity_in(answer.len(), arena);
|
||||
let mut tag_names = Vec::with_capacity_in(answer.len(), env.arena);
|
||||
|
||||
for (tag_name, _) in answer {
|
||||
tag_names.push(tag_name);
|
||||
@ -2488,27 +2526,15 @@ pub fn union_sorted_tags_help<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_from_newtype<'a>(
|
||||
arena: &'a Bump,
|
||||
tags: &UnsortedUnionTags,
|
||||
subs: &Subs,
|
||||
target_info: TargetInfo,
|
||||
) -> Layout<'a> {
|
||||
debug_assert!(tags.is_newtype_wrapper(subs));
|
||||
fn layout_from_newtype<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> {
|
||||
debug_assert!(tags.is_newtype_wrapper(env.subs));
|
||||
|
||||
let (tag_name, var) = tags.get_newtype(subs);
|
||||
let (tag_name, var) = tags.get_newtype(env.subs);
|
||||
|
||||
if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) {
|
||||
unwrap_num_tag(subs, var, target_info).expect("invalid Num argument")
|
||||
unwrap_num_tag(env.subs, var, env.target_info).expect("invalid Num argument")
|
||||
} else {
|
||||
let mut env = Env {
|
||||
arena,
|
||||
subs,
|
||||
seen: Vec::new_in(arena),
|
||||
target_info,
|
||||
};
|
||||
|
||||
match Layout::from_var(&mut env, var) {
|
||||
match Layout::from_var(env, var) {
|
||||
Ok(layout) => layout,
|
||||
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
|
||||
// If we encounter an unbound type var (e.g. `Ok *`)
|
||||
@ -2525,16 +2551,11 @@ fn layout_from_newtype<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_from_tag_union<'a>(
|
||||
arena: &'a Bump,
|
||||
tags: &UnsortedUnionTags,
|
||||
subs: &Subs,
|
||||
target_info: TargetInfo,
|
||||
) -> Layout<'a> {
|
||||
fn layout_from_tag_union<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> {
|
||||
use UnionVariant::*;
|
||||
|
||||
if tags.is_newtype_wrapper(subs) {
|
||||
return layout_from_newtype(arena, tags, subs, target_info);
|
||||
if tags.is_newtype_wrapper(env.subs) {
|
||||
return layout_from_newtype(env, tags);
|
||||
}
|
||||
|
||||
let tags_vec = &tags.tags;
|
||||
@ -2545,12 +2566,11 @@ fn layout_from_tag_union<'a>(
|
||||
|
||||
let &var = arguments.iter().next().unwrap();
|
||||
|
||||
unwrap_num_tag(subs, var, target_info).expect("invalid Num argument")
|
||||
unwrap_num_tag(env.subs, var, env.target_info).expect("invalid Num argument")
|
||||
}
|
||||
_ => {
|
||||
let opt_rec_var = None;
|
||||
let variant =
|
||||
union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, target_info);
|
||||
let variant = union_sorted_tags_help_new(env, tags_vec, opt_rec_var);
|
||||
|
||||
match variant {
|
||||
Never => Layout::VOID,
|
||||
@ -2576,7 +2596,7 @@ fn layout_from_tag_union<'a>(
|
||||
NonRecursive {
|
||||
sorted_tag_layouts: tags,
|
||||
} => {
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena);
|
||||
tag_layouts.extend(tags.iter().map(|r| r.1));
|
||||
|
||||
Layout::Union(UnionLayout::NonRecursive(tag_layouts.into_bump_slice()))
|
||||
@ -2585,7 +2605,7 @@ fn layout_from_tag_union<'a>(
|
||||
Recursive {
|
||||
sorted_tag_layouts: tags,
|
||||
} => {
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena);
|
||||
tag_layouts.extend(tags.iter().map(|r| r.1));
|
||||
|
||||
debug_assert!(tag_layouts.len() > 1);
|
||||
@ -2597,7 +2617,7 @@ fn layout_from_tag_union<'a>(
|
||||
nullable_name: _,
|
||||
sorted_tag_layouts: tags,
|
||||
} => {
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
|
||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena);
|
||||
tag_layouts.extend(tags.iter().map(|r| r.1));
|
||||
|
||||
Layout::Union(UnionLayout::NullableWrapped {
|
||||
|
@ -30,7 +30,6 @@
|
||||
">="
|
||||
">"
|
||||
"^"
|
||||
"%%"
|
||||
"%"
|
||||
|
||||
"->"
|
@ -2762,7 +2762,6 @@ where
|
||||
"&&" => good!(BinOp::And, 2),
|
||||
"||" => good!(BinOp::Or, 2),
|
||||
"//" => good!(BinOp::DoubleSlash, 2),
|
||||
"%%" => good!(BinOp::DoublePercent, 2),
|
||||
"->" => {
|
||||
// makes no progress, so it does not interfere with `_ if isGood -> ...`
|
||||
Err((NoProgress, to_error("->", state.pos()), state))
|
||||
|
@ -6,4 +6,4 @@ license = "UPL-1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
target-lexicon = "0.12.2"
|
||||
target-lexicon = "0.12.3"
|
||||
|
@ -37,7 +37,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
either = "1.6.1"
|
||||
libc = "0.2.106"
|
||||
inkwell = { path = "../../vendor/inkwell" }
|
||||
target-lexicon = "0.12.2"
|
||||
target-lexicon = "0.12.3"
|
||||
libloading = "0.7.1"
|
||||
wasmer-wasi = "2.0.0"
|
||||
tempfile = "3.2.0"
|
||||
|
@ -2534,7 +2534,7 @@ fn list_keep_oks() {
|
||||
RocList<i64>
|
||||
);
|
||||
assert_evals_to!(
|
||||
"List.keepOks [1,2] (\\x -> x % 2)",
|
||||
"List.keepOks [1,2] (\\x -> Num.remChecked x 2)",
|
||||
RocList::from_slice(&[1, 0]),
|
||||
RocList<i64>
|
||||
);
|
||||
@ -2561,7 +2561,7 @@ fn list_keep_errs() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
List.keepErrs [0,1,2] (\x -> x % 0 |> Result.mapErr (\_ -> 32))
|
||||
List.keepErrs [0,1,2] (\x -> Num.remChecked x 0 |> Result.mapErr (\_ -> 32))
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[32, 32, 32]),
|
||||
|
@ -473,7 +473,7 @@ fn f64_sqrt() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.sqrt 100 is
|
||||
when Num.sqrtChecked 100 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
@ -489,9 +489,7 @@ fn f64_log() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.log 7.38905609893 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
Num.log 7.38905609893
|
||||
"#
|
||||
),
|
||||
1.999999999999912,
|
||||
@ -501,11 +499,11 @@ fn f64_log() {
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn f64_log_one() {
|
||||
fn f64_log_checked_one() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.log 1 is
|
||||
when Num.logChecked 1 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
@ -521,7 +519,7 @@ fn f64_sqrt_zero() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.sqrt 0 is
|
||||
when Num.sqrtChecked 0 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
@ -533,11 +531,11 @@ fn f64_sqrt_zero() {
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn f64_sqrt_negative() {
|
||||
fn f64_sqrt_checked_negative() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.sqrt -1 is
|
||||
when Num.sqrtChecked -1 is
|
||||
Err _ -> 42
|
||||
Ok val -> val
|
||||
"#
|
||||
@ -549,11 +547,11 @@ fn f64_sqrt_negative() {
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn f64_log_zero() {
|
||||
fn f64_log_checked_zero() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.log 0 is
|
||||
when Num.logChecked 0 is
|
||||
Err _ -> 42
|
||||
Ok val -> val
|
||||
"#
|
||||
@ -569,13 +567,12 @@ fn f64_log_negative() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.log -1 is
|
||||
Err _ -> 42
|
||||
Ok val -> val
|
||||
Num.log -1
|
||||
"#
|
||||
),
|
||||
42.0,
|
||||
f64
|
||||
true,
|
||||
f64,
|
||||
|f: f64| f.is_nan()
|
||||
);
|
||||
}
|
||||
|
||||
@ -1082,9 +1079,7 @@ fn gen_rem_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.rem 8 3 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
Num.rem 8 3
|
||||
"#
|
||||
),
|
||||
2,
|
||||
@ -1094,11 +1089,11 @@ fn gen_rem_i64() {
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn gen_rem_div_by_zero_i64() {
|
||||
fn gen_rem_checked_div_by_zero_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.rem 8 0 is
|
||||
when Num.remChecked 8 0 is
|
||||
Err DivByZero -> 4
|
||||
Ok _ -> -23
|
||||
"#
|
||||
|
@ -593,6 +593,27 @@ fn top_level_constant() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn top_level_destructure() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
{a, b} = { a: 1, b: 2 }
|
||||
|
||||
main =
|
||||
|
||||
a + b
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn linked_list_len_0() {
|
||||
@ -2972,6 +2993,7 @@ fn mix_function_and_closure_level_of_indirection() {
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg_attr(debug_assertions, ignore)] // this test stack-overflows the compiler in debug mode
|
||||
fn do_pass_bool_byte_closure_layout() {
|
||||
// see https://github.com/rtfeldman/roc/pull/1706
|
||||
// the distinction is actually important, dropping that info means some functions just get
|
||||
|
@ -1580,3 +1580,28 @@ fn issue_2725_alias_polymorphic_lambda() {
|
||||
i64
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn opaque_assign_to_symbol() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ out ] to "./platform"
|
||||
|
||||
Variable := U8
|
||||
|
||||
fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ]
|
||||
fromUtf8 = \char ->
|
||||
Ok ($Variable char)
|
||||
|
||||
out =
|
||||
when fromUtf8 98 is
|
||||
Ok ($Variable n) -> n
|
||||
_ -> 1
|
||||
"#
|
||||
),
|
||||
98,
|
||||
u8
|
||||
)
|
||||
}
|
||||
|
6
compiler/test_mono/generated/issue_2810.txt
Normal file
6
compiler/test_mono/generated/issue_2810.txt
Normal file
@ -0,0 +1,6 @@
|
||||
procedure Test.0 ():
|
||||
let Test.16 : [C TODO, C ] = SystemTool ;
|
||||
let Test.14 : TODO = Job Test.16;
|
||||
let Test.13 : [C TODO, C ] = FromJob Test.14;
|
||||
let Test.4 : TODO = Job Test.13;
|
||||
ret Test.4;
|
10
compiler/test_mono/generated/opaque_assign_to_symbol.txt
Normal file
10
compiler/test_mono/generated/opaque_assign_to_symbol.txt
Normal file
@ -0,0 +1,10 @@
|
||||
procedure : `#UserApp.fromUtf8` [C {}, C U8]
|
||||
procedure = `#UserApp.fromUtf8` (`#UserApp.char`):
|
||||
let `#UserApp.3` : [C {}, C U8] = Ok `#UserApp.4`;
|
||||
ret `#UserApp.3`;
|
||||
|
||||
procedure : `#UserApp.out` [C {}, C U8]
|
||||
procedure = `#UserApp.out` ():
|
||||
let `#UserApp.2` : U8 = 98i64;
|
||||
let `#UserApp.1` : [C {}, C U8] = CallByName `#UserApp.fromUtf8` `#UserApp.2`;
|
||||
ret `#UserApp.1`;
|
@ -1283,6 +1283,23 @@ fn issue_2583_specialize_errors_behind_unified_branches() {
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn issue_2810() {
|
||||
indoc!(
|
||||
r#"
|
||||
Command : [ Command Tool ]
|
||||
|
||||
Job : [ Job Command ]
|
||||
|
||||
Tool : [ SystemTool, FromJob Job ]
|
||||
|
||||
a : Job
|
||||
a = Job (Command (FromJob (Job (Command SystemTool))))
|
||||
a
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn issue_2811() {
|
||||
indoc!(
|
||||
@ -1313,6 +1330,23 @@ fn specialize_ability_call() {
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn opaque_assign_to_symbol() {
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ out ] to "./platform"
|
||||
|
||||
Variable := U8
|
||||
|
||||
fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ]
|
||||
fromUtf8 = \char ->
|
||||
Ok ($Variable char)
|
||||
|
||||
out = fromUtf8 98
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
// #[ignore]
|
||||
// #[mono_test]
|
||||
// fn static_str_closure() {
|
||||
|
@ -450,7 +450,9 @@ fn write_content<'a>(
|
||||
}
|
||||
|
||||
// useful for debugging
|
||||
if false {
|
||||
if cfg!(debug_assertions)
|
||||
&& std::env::var("ROC_PRETTY_PRINT_ALIAS_CONTENTS").is_ok()
|
||||
{
|
||||
buf.push_str("[[ but really ");
|
||||
let content = subs.get_content_without_compacting(*_actual);
|
||||
write_content(env, ctx, content, subs, buf, parens);
|
||||
|
@ -772,7 +772,15 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
|
||||
AliasKind::Opaque => "Opaque",
|
||||
};
|
||||
|
||||
write!(f, "{}({:?}, {:?}, {:?})", wrap, name, slice, actual)
|
||||
write!(
|
||||
f,
|
||||
"{}({:?}, {:?}, <{:?}>{:?})",
|
||||
wrap,
|
||||
name,
|
||||
slice,
|
||||
actual,
|
||||
SubsFmtContent(subs.get_content_without_compacting(*actual), subs)
|
||||
)
|
||||
}
|
||||
Content::RangedNumber(typ, range) => {
|
||||
let slice = subs.get_subs_slice(*range);
|
||||
@ -833,7 +841,16 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f
|
||||
|
||||
let (it, new_ext) = tags.sorted_iterator_and_ext(subs, *ext);
|
||||
for (name, slice) in it {
|
||||
write!(f, "{:?} {:?}, ", name, slice)?;
|
||||
write!(f, "{:?} ", name)?;
|
||||
for var in slice {
|
||||
write!(
|
||||
f,
|
||||
"<{:?}>{:?} ",
|
||||
var,
|
||||
SubsFmtContent(subs.get_content_without_compacting(*var), subs)
|
||||
)?;
|
||||
}
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "]<{:?}>", new_ext)
|
||||
@ -1889,7 +1906,14 @@ impl Subs {
|
||||
}
|
||||
|
||||
pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec<Variable>)> {
|
||||
occurs(self, &[], var)
|
||||
occurs(self, &[], var, false)
|
||||
}
|
||||
|
||||
pub fn occurs_including_recursion_vars(
|
||||
&self,
|
||||
var: Variable,
|
||||
) -> Result<(), (Variable, Vec<Variable>)> {
|
||||
occurs(self, &[], var, true)
|
||||
}
|
||||
|
||||
pub fn mark_tag_union_recursive(
|
||||
@ -2862,6 +2886,7 @@ fn occurs(
|
||||
subs: &Subs,
|
||||
seen: &[Variable],
|
||||
input_var: Variable,
|
||||
include_recursion_var: bool,
|
||||
) -> Result<(), (Variable, Vec<Variable>)> {
|
||||
use self::Content::*;
|
||||
use self::FlatType::*;
|
||||
@ -2885,47 +2910,77 @@ fn occurs(
|
||||
new_seen.push(root_var);
|
||||
|
||||
match flat_type {
|
||||
Apply(_, args) => {
|
||||
short_circuit(subs, root_var, &new_seen, subs.get_subs_slice(*args).iter())
|
||||
}
|
||||
Apply(_, args) => short_circuit(
|
||||
subs,
|
||||
root_var,
|
||||
&new_seen,
|
||||
subs.get_subs_slice(*args).iter(),
|
||||
include_recursion_var,
|
||||
),
|
||||
Func(arg_vars, closure_var, ret_var) => {
|
||||
let it = once(ret_var)
|
||||
.chain(once(closure_var))
|
||||
.chain(subs.get_subs_slice(*arg_vars).iter());
|
||||
short_circuit(subs, root_var, &new_seen, it)
|
||||
short_circuit(subs, root_var, &new_seen, it, include_recursion_var)
|
||||
}
|
||||
Record(vars_by_field, ext_var) => {
|
||||
let slice =
|
||||
SubsSlice::new(vars_by_field.variables_start, vars_by_field.length);
|
||||
let it = once(ext_var).chain(subs.get_subs_slice(slice).iter());
|
||||
short_circuit(subs, root_var, &new_seen, it)
|
||||
short_circuit(subs, root_var, &new_seen, it, include_recursion_var)
|
||||
}
|
||||
TagUnion(tags, ext_var) => {
|
||||
for slice_index in tags.variables() {
|
||||
let slice = subs[slice_index];
|
||||
for var_index in slice {
|
||||
let var = subs[var_index];
|
||||
short_circuit_help(subs, root_var, &new_seen, var)?;
|
||||
short_circuit_help(
|
||||
subs,
|
||||
root_var,
|
||||
&new_seen,
|
||||
var,
|
||||
include_recursion_var,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
short_circuit_help(subs, root_var, &new_seen, *ext_var)
|
||||
short_circuit_help(
|
||||
subs,
|
||||
root_var,
|
||||
&new_seen,
|
||||
*ext_var,
|
||||
include_recursion_var,
|
||||
)
|
||||
}
|
||||
FunctionOrTagUnion(_, _, ext_var) => {
|
||||
let it = once(ext_var);
|
||||
short_circuit(subs, root_var, &new_seen, it)
|
||||
short_circuit(subs, root_var, &new_seen, it, include_recursion_var)
|
||||
}
|
||||
RecursiveTagUnion(_rec_var, tags, ext_var) => {
|
||||
// TODO rec_var is excluded here, verify that this is correct
|
||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
if include_recursion_var {
|
||||
new_seen.push(subs.get_root_key_without_compacting(*rec_var));
|
||||
}
|
||||
for slice_index in tags.variables() {
|
||||
let slice = subs[slice_index];
|
||||
for var_index in slice {
|
||||
let var = subs[var_index];
|
||||
short_circuit_help(subs, root_var, &new_seen, var)?;
|
||||
short_circuit_help(
|
||||
subs,
|
||||
root_var,
|
||||
&new_seen,
|
||||
var,
|
||||
include_recursion_var,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
short_circuit_help(subs, root_var, &new_seen, *ext_var)
|
||||
short_circuit_help(
|
||||
subs,
|
||||
root_var,
|
||||
&new_seen,
|
||||
*ext_var,
|
||||
include_recursion_var,
|
||||
)
|
||||
}
|
||||
EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()),
|
||||
}
|
||||
@ -2936,7 +2991,7 @@ fn occurs(
|
||||
|
||||
for var_index in args.into_iter() {
|
||||
let var = subs[var_index];
|
||||
short_circuit_help(subs, root_var, &new_seen, var)?;
|
||||
short_circuit_help(subs, root_var, &new_seen, var, include_recursion_var)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -2945,7 +3000,7 @@ fn occurs(
|
||||
let mut new_seen = seen.to_owned();
|
||||
new_seen.push(root_var);
|
||||
|
||||
short_circuit_help(subs, root_var, &new_seen, *typ)?;
|
||||
short_circuit_help(subs, root_var, &new_seen, *typ, include_recursion_var)?;
|
||||
// _range_vars excluded because they are not explicitly part of the type.
|
||||
|
||||
Ok(())
|
||||
@ -2960,12 +3015,13 @@ fn short_circuit<'a, T>(
|
||||
root_key: Variable,
|
||||
seen: &[Variable],
|
||||
iter: T,
|
||||
include_recursion_var: bool,
|
||||
) -> Result<(), (Variable, Vec<Variable>)>
|
||||
where
|
||||
T: Iterator<Item = &'a Variable>,
|
||||
{
|
||||
for var in iter {
|
||||
short_circuit_help(subs, root_key, seen, *var)?;
|
||||
short_circuit_help(subs, root_key, seen, *var, include_recursion_var)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -2977,8 +3033,9 @@ fn short_circuit_help(
|
||||
root_key: Variable,
|
||||
seen: &[Variable],
|
||||
var: Variable,
|
||||
include_recursion_var: bool,
|
||||
) -> Result<(), (Variable, Vec<Variable>)> {
|
||||
if let Err((v, mut vec)) = occurs(subs, seen, var) {
|
||||
if let Err((v, mut vec)) = occurs(subs, seen, var, include_recursion_var) {
|
||||
vec.push(root_key);
|
||||
return Err((v, vec));
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Content::{self, *};
|
||||
use roc_types::subs::{
|
||||
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable,
|
||||
RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice,
|
||||
RecordFields, Subs, SubsFmtContent, SubsIndex, SubsSlice, UnionTags, Variable,
|
||||
VariableSubsSlice,
|
||||
};
|
||||
use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, RecordField};
|
||||
|
||||
@ -316,10 +317,10 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option
|
||||
ctx.first,
|
||||
ctx.second,
|
||||
ctx.first,
|
||||
roc_types::subs::SubsFmtContent(&content_1, subs),
|
||||
SubsFmtContent(&content_1, subs),
|
||||
mode,
|
||||
ctx.second,
|
||||
roc_types::subs::SubsFmtContent(&content_2, subs),
|
||||
SubsFmtContent(&content_2, subs),
|
||||
);
|
||||
|
||||
unsafe { UNIFICATION_DEPTH = new_depth };
|
||||
@ -576,7 +577,15 @@ fn unify_structure(
|
||||
RecursionVar { structure, .. } => match flat_type {
|
||||
FlatType::TagUnion(_, _) => {
|
||||
// unify the structure with this unrecursive tag union
|
||||
unify_pool(subs, pool, ctx.first, *structure, ctx.mode)
|
||||
let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode);
|
||||
|
||||
if outcome.mismatches.is_empty() {
|
||||
outcome.union(fix_tag_union_recursion_variable(
|
||||
subs, ctx, ctx.first, other,
|
||||
));
|
||||
}
|
||||
|
||||
outcome
|
||||
}
|
||||
FlatType::RecursiveTagUnion(rec, _, _) => {
|
||||
debug_assert!(is_recursion_var(subs, *rec));
|
||||
@ -585,7 +594,15 @@ fn unify_structure(
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(_, _, _) => {
|
||||
// unify the structure with this unrecursive tag union
|
||||
unify_pool(subs, pool, ctx.first, *structure, ctx.mode)
|
||||
let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode);
|
||||
|
||||
if outcome.mismatches.is_empty() {
|
||||
outcome.union(fix_tag_union_recursion_variable(
|
||||
subs, ctx, ctx.first, other,
|
||||
));
|
||||
}
|
||||
|
||||
outcome
|
||||
}
|
||||
// Only tag unions can be recursive; everything else is an error.
|
||||
_ => mismatch!(
|
||||
@ -643,6 +660,57 @@ fn unify_structure(
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that a non-recursive tag union, when unified with a recursion var to become a recursive
|
||||
/// tag union, properly contains a recursion variable that recurses on itself.
|
||||
//
|
||||
// When might this not be the case? For example, in the code
|
||||
//
|
||||
// Indirect : [ Indirect ConsList ]
|
||||
//
|
||||
// ConsList : [ Nil, Cons Indirect ]
|
||||
//
|
||||
// l : ConsList
|
||||
// l = Cons (Indirect (Cons (Indirect Nil)))
|
||||
// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a
|
||||
// # ~~~~~~~~~~~~~~~~~~~~~ region-b
|
||||
// l
|
||||
//
|
||||
// Suppose `ConsList` has the expanded type `[ Nil, Cons [ Indirect <rec> ] ] as <rec>`.
|
||||
// After unifying the tag application annotated "region-b" with the recursion variable `<rec>`,
|
||||
// the tentative total-type of the application annotated "region-a" would be
|
||||
// `<v> = [ Nil, Cons [ Indirect <v> ] ] as <rec>`. That is, the type of the recursive tag union
|
||||
// would be inlined at the site "v", rather than passing through the correct recursion variable
|
||||
// "rec" first.
|
||||
//
|
||||
// This is not incorrect from a type perspective, but causes problems later on for e.g. layout
|
||||
// determination, which expects recursion variables to be placed correctly. Attempting to detect
|
||||
// this during layout generation does not work so well because it may be that there *are* recursive
|
||||
// tag unions that should be inlined, and not pass through recursion variables. So instead, try to
|
||||
// resolve these cases here.
|
||||
//
|
||||
// See tests labeled "issue_2810" for more examples.
|
||||
fn fix_tag_union_recursion_variable(
|
||||
subs: &mut Subs,
|
||||
ctx: &Context,
|
||||
tag_union_promoted_to_recursive: Variable,
|
||||
recursion_var: &Content,
|
||||
) -> Outcome {
|
||||
debug_assert!(matches!(
|
||||
subs.get_content_without_compacting(tag_union_promoted_to_recursive),
|
||||
Structure(FlatType::RecursiveTagUnion(..))
|
||||
));
|
||||
|
||||
let has_recursing_recursive_variable = subs
|
||||
.occurs_including_recursion_vars(tag_union_promoted_to_recursive)
|
||||
.is_err();
|
||||
|
||||
if !has_recursing_recursive_variable {
|
||||
merge(subs, ctx, *recursion_var)
|
||||
} else {
|
||||
Outcome::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_record(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
|
@ -41,8 +41,8 @@ Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr
|
||||
|
||||
divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]*
|
||||
divmod = \l, r ->
|
||||
when Pair (l // r) (l % r) is
|
||||
Pair div (Ok mod) ->
|
||||
when Pair (Num.divTruncChecked l r) (Num.remChecked l r) is
|
||||
Pair (Ok div) (Ok mod) ->
|
||||
Ok { div, mod }
|
||||
|
||||
_ ->
|
||||
|
@ -23,12 +23,12 @@ makeMapHelp = \freq, n, m, acc ->
|
||||
|
||||
_ ->
|
||||
powerOf10 =
|
||||
(n % 10 |> resultWithDefault 0) == 0
|
||||
n % 10 == 0
|
||||
|
||||
m1 = insert m n powerOf10
|
||||
|
||||
isFrequency =
|
||||
(n % freq |> resultWithDefault 0) == 0
|
||||
n % freq == 0
|
||||
|
||||
x = (if isFrequency then Cons m1 acc else acc)
|
||||
|
||||
@ -43,15 +43,6 @@ fold = \f, tree, b ->
|
||||
Node _ l k v r ->
|
||||
fold f r (f k v (fold f l b))
|
||||
|
||||
resultWithDefault : Result a e, a -> a
|
||||
resultWithDefault = \res, default ->
|
||||
when res is
|
||||
Ok v ->
|
||||
v
|
||||
|
||||
Err _ ->
|
||||
default
|
||||
|
||||
main : Task.Task {} []
|
||||
main =
|
||||
Task.after
|
||||
|
1
examples/breakout/.gitignore
vendored
Normal file
1
examples/breakout/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
breakout
|
168
examples/breakout/breakout.roc
Normal file
168
examples/breakout/breakout.roc
Normal file
@ -0,0 +1,168 @@
|
||||
app "breakout"
|
||||
packages { pf: "platform" }
|
||||
imports [ pf.Game.{ Bounds, Elem, Event } ]
|
||||
provides [ program ] { Model } to pf
|
||||
|
||||
paddleWidth = 0.2# width of the paddle, as a % of screen width
|
||||
paddleHeight = 50# height of the paddle, in pixels
|
||||
paddleSpeed = 65# how many pixels the paddle moves per keypress
|
||||
blockHeight = 80# height of a block, in pixels
|
||||
blockBorder = 0.025# border of a block, as a % of its width
|
||||
ballSize = 55
|
||||
numRows = 4
|
||||
numCols = 8
|
||||
numBlocks = numRows * numCols
|
||||
|
||||
Model : {
|
||||
# Screen height and width
|
||||
height : F32,
|
||||
width : F32,
|
||||
# Paddle X-coordinate
|
||||
paddleX : F32,
|
||||
# Ball coordinates
|
||||
ballX : F32,
|
||||
ballY : F32,
|
||||
dBallX : F32,
|
||||
# delta x - how much it moves per tick
|
||||
dBallY : F32,
|
||||
# delta y - how much it moves per tick
|
||||
}
|
||||
|
||||
init : Bounds -> Model
|
||||
init = \{ width, height } ->
|
||||
{
|
||||
# Screen height and width
|
||||
width,
|
||||
height,
|
||||
# Paddle X-coordinate
|
||||
paddleX: (width * 0.5) - (paddleWidth * width * 0.5),
|
||||
# Ball coordinates
|
||||
ballX: width * 0.5,
|
||||
ballY: height * 0.4,
|
||||
# Delta - how much ball moves in each tick
|
||||
dBallX: 4,
|
||||
dBallY: 4,
|
||||
}
|
||||
|
||||
update : Model, Event -> Model
|
||||
update = \model, event ->
|
||||
when event is
|
||||
Resize size ->
|
||||
{ model & width: size.width, height: size.height }
|
||||
|
||||
KeyDown Left ->
|
||||
{ model & paddleX: model.paddleX - paddleSpeed }
|
||||
|
||||
KeyDown Right ->
|
||||
{ model & paddleX: model.paddleX + paddleSpeed }
|
||||
|
||||
Tick _ ->
|
||||
tick model
|
||||
|
||||
_ ->
|
||||
model
|
||||
|
||||
tick : Model -> Model
|
||||
tick = \model ->
|
||||
model
|
||||
|> moveBall
|
||||
|
||||
moveBall : Model -> Model
|
||||
moveBall = \model ->
|
||||
ballX = model.ballX + model.dBallX
|
||||
ballY = model.ballY + model.dBallY
|
||||
|
||||
paddleTop = model.height - blockHeight - (paddleHeight * 2)
|
||||
paddleLeft = model.paddleX
|
||||
paddleRight = paddleLeft + (model.width * paddleWidth)
|
||||
|
||||
# If its y used to be less than the paddle, and now it's greater than or equal,
|
||||
# then this is the frame where the ball collided with it.
|
||||
crossingPaddle = model.ballY < paddleTop && ballY >= paddleTop
|
||||
|
||||
# If it collided with the paddle, bounce off.
|
||||
directionChange =
|
||||
if crossingPaddle && (ballX >= paddleLeft && ballX <= paddleRight) then
|
||||
-1f32
|
||||
else
|
||||
1f32
|
||||
|
||||
dBallX = model.dBallX * directionChange
|
||||
dBallY = model.dBallY * directionChange
|
||||
|
||||
{ model & ballX, ballY, dBallX, dBallY }
|
||||
|
||||
render : Model -> List Elem
|
||||
render = \model ->
|
||||
|
||||
blocks = List.map
|
||||
(List.range 0 numBlocks)
|
||||
\index ->
|
||||
col =
|
||||
Num.rem index numCols
|
||||
|> Num.toF32
|
||||
|
||||
row =
|
||||
index
|
||||
// numCols
|
||||
|> Num.toF32
|
||||
|
||||
red = col / Num.toF32 numCols
|
||||
green = row / Num.toF32 numRows
|
||||
blue = Num.toF32 index / Num.toF32 numBlocks
|
||||
|
||||
color = { r: red * 0.8, g: 0.2 + green * 0.6, b: 0.2 + blue * 0.8, a: 1 }
|
||||
|
||||
{ row, col, color }
|
||||
|
||||
blockWidth = model.width / numCols
|
||||
|
||||
rects =
|
||||
List.joinMap
|
||||
blocks
|
||||
\{ row, col, color } ->
|
||||
left = Num.toF32 col * blockWidth
|
||||
top = Num.toF32 (row * blockHeight)
|
||||
border = blockBorder * blockWidth
|
||||
|
||||
outer = Rect
|
||||
{
|
||||
left,
|
||||
top,
|
||||
width: blockWidth,
|
||||
height: blockHeight,
|
||||
color: { r: color.r * 0.8, g: color.g * 0.8, b: color.b * 0.8, a: 1 },
|
||||
}
|
||||
|
||||
inner = Rect
|
||||
{
|
||||
left: left + border,
|
||||
top: top + border,
|
||||
width: blockWidth - (border * 2),
|
||||
height: blockHeight - (border * 2),
|
||||
color,
|
||||
}
|
||||
|
||||
[ outer, inner ]
|
||||
|
||||
ball =
|
||||
color = { r: 0.7, g: 0.3, b: 0.9, a: 1.0 }
|
||||
width = ballSize
|
||||
height = ballSize
|
||||
left = model.ballX
|
||||
top = model.ballY
|
||||
|
||||
Rect { left, top, width, height, color }
|
||||
|
||||
paddle =
|
||||
color = { r: 0.8, g: 0.8, b: 0.8, a: 1.0 }
|
||||
width = model.width * paddleWidth
|
||||
height = paddleHeight
|
||||
left = model.paddleX
|
||||
top = model.height - blockHeight - height
|
||||
|
||||
Rect { left, top, width, height, color }
|
||||
|
||||
List.concat rects [ paddle, ball ]
|
||||
|
||||
program = { init, update, render }
|
17
examples/breakout/hello.roc
Normal file
17
examples/breakout/hello.roc
Normal file
@ -0,0 +1,17 @@
|
||||
app "breakout"
|
||||
packages { pf: "platform" }
|
||||
imports [ pf.Game.{ Bounds, Elem, Event } ]
|
||||
provides [ program ] { Model } to pf
|
||||
|
||||
Model : { text : Str }
|
||||
|
||||
init : Bounds -> Model
|
||||
init = \_ -> { text: "Hello, World!" }
|
||||
|
||||
update : Model, Event -> Model
|
||||
update = \model, _ -> model
|
||||
|
||||
render : Model -> List Elem
|
||||
render = \model -> [ Text model.text ]
|
||||
|
||||
program = { init, update, render }
|
1
examples/breakout/platform/.gitignore
vendored
Normal file
1
examples/breakout/platform/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
target
|
20
examples/breakout/platform/Action.roc
Normal file
20
examples/breakout/platform/Action.roc
Normal file
@ -0,0 +1,20 @@
|
||||
interface Action
|
||||
exposes [ Action, none, update, map ]
|
||||
imports []
|
||||
|
||||
Action state : [ None, Update state ]
|
||||
|
||||
none : Action *
|
||||
none = None
|
||||
|
||||
update : state -> Action state
|
||||
update = Update
|
||||
|
||||
map : Action a, (a -> b) -> Action b
|
||||
map = \action, transform ->
|
||||
when action is
|
||||
None ->
|
||||
None
|
||||
|
||||
Update state ->
|
||||
Update (transform state)
|
2835
examples/breakout/platform/Cargo.lock
generated
Normal file
2835
examples/breakout/platform/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
75
examples/breakout/platform/Cargo.toml
Normal file
75
examples/breakout/platform/Cargo.toml
Normal file
@ -0,0 +1,75 @@
|
||||
[package]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2018"
|
||||
|
||||
# Needed to be able to run on non-Windows systems for some reason. Without this, cargo panics with:
|
||||
#
|
||||
# error: DX12 API enabled on non-Windows OS. If your project is not using resolver="2" in Cargo.toml, it should.
|
||||
resolver = "2"
|
||||
|
||||
[lib]
|
||||
name = "host"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[[bin]]
|
||||
name = "host"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
roc_std = { path = "../../../roc_std" }
|
||||
libc = "0.2"
|
||||
arrayvec = "0.7.2"
|
||||
page_size = "0.4.2"
|
||||
# when changing winit version, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time.
|
||||
winit = "0.26.1"
|
||||
wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "0545e36" }
|
||||
wgpu_glyph = { git = "https://github.com/Anton-4/wgpu_glyph", rev = "257d109" }
|
||||
glyph_brush = "0.7.2"
|
||||
log = "0.4.14"
|
||||
env_logger = "0.9.0"
|
||||
futures = "0.3.17"
|
||||
cgmath = "0.18.0"
|
||||
snafu = { version = "0.6.10", features = ["backtraces"] }
|
||||
colored = "2.0.0"
|
||||
pest = "2.1.3"
|
||||
pest_derive = "2.1.0"
|
||||
copypasta = "0.7.1"
|
||||
palette = "0.6.0"
|
||||
confy = { git = 'https://github.com/rust-cli/confy', features = [
|
||||
"yaml_conf"
|
||||
], default-features = false }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
nonempty = "0.7.0"
|
||||
fs_extra = "1.2.0"
|
||||
rodio = { version = "0.14.0", optional = true } # to play sounds
|
||||
threadpool = "1.8.1"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# confy is currently unused but should not be removed
|
||||
normal = ["confy"]
|
||||
#development = []
|
||||
#build = []
|
||||
|
||||
[features]
|
||||
default = []
|
||||
with_sound = ["rodio"]
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.7.2"
|
||||
features = ["derive"]
|
||||
|
||||
[workspace]
|
||||
|
||||
# Optimizations based on https://deterministic.space/high-performance-rust.html
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
||||
# debug = true # enable when profiling
|
||||
[profile.bench]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
193
examples/breakout/platform/Elem.roc
Normal file
193
examples/breakout/platform/Elem.roc
Normal file
@ -0,0 +1,193 @@
|
||||
interface Elem
|
||||
exposes [ Elem, PressEvent, row, col, text, button, none, translate, list ]
|
||||
imports [ Action.{ Action } ]
|
||||
|
||||
Elem state :
|
||||
# PERFORMANCE NOTE:
|
||||
# If there are 8 or fewer tags here, then on a 64-bit system, the tag can be stored
|
||||
# in the pointer - for massive memory savings. Try extremely hard to always limit the number
|
||||
# of tags in this union to 8 or fewer!
|
||||
[
|
||||
Button (ButtonConfig state) (Elem state),
|
||||
Text Str,
|
||||
Col (List (Elem state)),
|
||||
Row (List (Elem state)),
|
||||
Lazy (Result { state, elem : Elem state } [ NotCached ] -> { state, elem : Elem state }),
|
||||
# TODO FIXME: using this definition of Lazy causes a stack overflow in the compiler!
|
||||
# Lazy (Result (Cached state) [ NotCached ] -> Cached state),
|
||||
None,
|
||||
]
|
||||
|
||||
## Used internally in the type definition of Lazy
|
||||
Cached state : { state, elem : Elem state }
|
||||
|
||||
ButtonConfig state : { onPress : state, PressEvent -> Action state }
|
||||
|
||||
PressEvent : { button : [ Touch, Mouse [ Left, Right, Middle ] ] }
|
||||
|
||||
text : Str -> Elem *
|
||||
text = \str ->
|
||||
Text str
|
||||
|
||||
button : { onPress : state, PressEvent -> Action state }, Elem state -> Elem state
|
||||
button = \config, label ->
|
||||
Button config label
|
||||
|
||||
row : List (Elem state) -> Elem state
|
||||
row = \children ->
|
||||
Row children
|
||||
|
||||
col : List (Elem state) -> Elem state
|
||||
col = \children ->
|
||||
Col children
|
||||
|
||||
lazy : state, (state -> Elem state) -> Elem state
|
||||
lazy = \state, render ->
|
||||
# This function gets called by the host during rendering. It will
|
||||
# receive the cached state and element (wrapped in Ok) if we've
|
||||
# ever rendered this before, and Err otherwise.
|
||||
Lazy
|
||||
\result ->
|
||||
when result is
|
||||
Ok cached if cached.state == state ->
|
||||
# If we have a cached value, and the new state is the
|
||||
# same as the cached one, then we can return exactly
|
||||
# what we had cached.
|
||||
cached
|
||||
|
||||
_ ->
|
||||
# Either the state changed or else we didn't have a
|
||||
# cached value to use. Either way, we need to render
|
||||
# with the new state and store that for future use.
|
||||
{ state, elem: render state }
|
||||
|
||||
none : Elem *
|
||||
none = None# I've often wanted this in elm/html. Usually end up resorting to (Html.text "") - this seems nicer.
|
||||
## Change an element's state type.
|
||||
##
|
||||
## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed.
|
||||
## State : { photo : Photo }
|
||||
##
|
||||
## render : State -> Elem State
|
||||
## render = \state ->
|
||||
## child : Elem State
|
||||
## child =
|
||||
## Photo.render state.photo
|
||||
## |> Elem.translate .photo &photo
|
||||
##
|
||||
## col {} [ child, otherElems ]
|
||||
##
|
||||
translate = \child, toChild, toParent ->
|
||||
when child is
|
||||
Text str ->
|
||||
Text str
|
||||
|
||||
Col elems ->
|
||||
Col (List.map elems \elem -> translate elem toChild toParent)
|
||||
|
||||
Row elems ->
|
||||
Row (List.map elems \elem -> translate elem toChild toParent)
|
||||
|
||||
Button config label ->
|
||||
onPress = \parentState, event ->
|
||||
toChild parentState
|
||||
|> config.onPress event
|
||||
|> Action.map \c -> toParent parentState c
|
||||
|
||||
Button { onPress } (translate label toChild toParent)
|
||||
|
||||
Lazy renderChild ->
|
||||
Lazy
|
||||
\parentState ->
|
||||
{ elem, state } = renderChild (toChild parentState)
|
||||
|
||||
{
|
||||
elem: translate toChild toParent newChild,
|
||||
state: toParent parentState state,
|
||||
}
|
||||
|
||||
None ->
|
||||
None
|
||||
|
||||
## Render a list of elements, using [Elem.translate] on each of them.
|
||||
##
|
||||
## Convenient when you have a [List] in your state and want to make
|
||||
## a [List] of child elements out of it.
|
||||
##
|
||||
## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed.
|
||||
## State : { photos : List Photo }
|
||||
##
|
||||
## render : State -> Elem State
|
||||
## render = \state ->
|
||||
## children : List (Elem State)
|
||||
## children =
|
||||
## Elem.list Photo.render state .photos &photos
|
||||
##
|
||||
## col {} children
|
||||
## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed
|
||||
list : (child -> Elem child), parent, (parent -> List child), (parent, List child -> parent) -> List (Elem parent)
|
||||
list = \renderChild, parent, toChildren, toParent ->
|
||||
List.mapWithIndex
|
||||
(toChildren parent)
|
||||
\index, child ->
|
||||
toChild = \par -> List.get (toChildren par) index
|
||||
|
||||
newChild = translateOrDrop
|
||||
child
|
||||
toChild
|
||||
\par, ch ->
|
||||
toChildren par
|
||||
|> List.set ch index
|
||||
|> toParent
|
||||
|
||||
renderChild newChild
|
||||
|
||||
## Internal helper function for Elem.list
|
||||
##
|
||||
## Tries to translate a child to a parent, but
|
||||
## if the child has been removed from the parent,
|
||||
## drops it.
|
||||
##
|
||||
## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed
|
||||
translateOrDrop : Elem child, (parent -> Result child *), (parent, child -> parent) -> Elem parent
|
||||
translateOrDrop = \child, toChild, toParent ->
|
||||
when child is
|
||||
Text str ->
|
||||
Text str
|
||||
|
||||
Col elems ->
|
||||
Col (List.map elems \elem -> translateOrDrop elem toChild toParent)
|
||||
|
||||
Row elems ->
|
||||
Row (List.map elems \elem -> translateOrDrop elem toChild toParent)
|
||||
|
||||
Button config label ->
|
||||
onPress = \parentState, event ->
|
||||
when toChild parentState is
|
||||
Ok newChild ->
|
||||
newChild
|
||||
|> config.onPress event
|
||||
|> Action.map \c -> toParent parentState c
|
||||
|
||||
Err _ ->
|
||||
# The child was removed from the list before this onPress handler resolved.
|
||||
# (For example, by a previous event handler that fired simultaneously.)
|
||||
Action.none
|
||||
|
||||
Button { onPress } (translateOrDrop label toChild toParent)
|
||||
|
||||
Lazy childState renderChild ->
|
||||
Lazy
|
||||
(toParent childState)
|
||||
\parentState ->
|
||||
when toChild parentState is
|
||||
Ok newChild ->
|
||||
renderChild newChild
|
||||
|> translateOrDrop toChild toParent
|
||||
|
||||
Err _ ->
|
||||
None
|
||||
|
||||
# I don't think this should ever happen in practice.
|
||||
None ->
|
||||
None
|
13
examples/breakout/platform/Game.roc
Normal file
13
examples/breakout/platform/Game.roc
Normal file
@ -0,0 +1,13 @@
|
||||
interface Game
|
||||
exposes [ Bounds, Elem, Event ]
|
||||
imports []
|
||||
|
||||
Rgba : { r : F32, g : F32, b : F32, a : F32 }
|
||||
|
||||
Bounds : { height : F32, width : F32 }
|
||||
|
||||
Elem : [ Rect { color : Rgba, left : F32, top : F32, width : F32, height : F32 }, Text Str ]
|
||||
|
||||
KeyCode : [ Left, Right, Other ]
|
||||
|
||||
Event : [ Resize { width : F32, height : F32 }, KeyDown KeyCode, KeyUp KeyCode, Tick U128 ]
|
14
examples/breakout/platform/Package-Config.roc
Normal file
14
examples/breakout/platform/Package-Config.roc
Normal file
@ -0,0 +1,14 @@
|
||||
platform "gui"
|
||||
requires { Model } { program : _ }
|
||||
exposes [ Game ]
|
||||
packages {}
|
||||
imports [ Game.{ Bounds, Elem, Event } ]
|
||||
provides [ programForHost ]
|
||||
|
||||
# TODO allow changing the window title - maybe via a Task, since that shouldn't happen all the time
|
||||
programForHost : {
|
||||
init : (Bounds -> Model) as Init,
|
||||
update : (Model, Event -> Model) as Update,
|
||||
render : (Model -> List Elem) as Render,
|
||||
}
|
||||
programForHost = program
|
4
examples/breakout/platform/build.rs
Normal file
4
examples/breakout/platform/build.rs
Normal file
@ -0,0 +1,4 @@
|
||||
fn main() {
|
||||
println!("cargo:rustc-link-lib=dylib=app");
|
||||
println!("cargo:rustc-link-search=.");
|
||||
}
|
3
examples/breakout/platform/host.c
Normal file
3
examples/breakout/platform/host.c
Normal file
@ -0,0 +1,3 @@
|
||||
extern int rust_main();
|
||||
|
||||
int main() { return rust_main(); }
|
50
examples/breakout/platform/src/graphics/colors.rs
Normal file
50
examples/breakout/platform/src/graphics/colors.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use cgmath::Vector4;
|
||||
use palette::{FromColor, Hsv, Srgb};
|
||||
|
||||
/// This order is optimized for what Roc will send
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||
pub struct Rgba {
|
||||
a: f32,
|
||||
b: f32,
|
||||
g: f32,
|
||||
r: f32,
|
||||
}
|
||||
|
||||
impl Rgba {
|
||||
pub const WHITE: Self = Self::new(1.0, 1.0, 1.0, 1.0);
|
||||
|
||||
pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
|
||||
pub const fn to_array(self) -> [f32; 4] {
|
||||
[self.r, self.g, self.b, self.a]
|
||||
}
|
||||
|
||||
pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> Self {
|
||||
Self::from_hsba(hue, saturation, brightness, 1.0)
|
||||
}
|
||||
|
||||
pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> Self {
|
||||
let rgb = Srgb::from_color(Hsv::new(
|
||||
hue as f32,
|
||||
(saturation as f32) / 100.0,
|
||||
(brightness as f32) / 100.0,
|
||||
));
|
||||
|
||||
Self::new(rgb.red, rgb.green, rgb.blue, alpha)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for [f32; 4] {
|
||||
fn from(rgba: Rgba) -> Self {
|
||||
rgba.to_array()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for Vector4<f32> {
|
||||
fn from(rgba: Rgba) -> Self {
|
||||
Vector4::new(rgba.r, rgba.b, rgba.g, rgba.a)
|
||||
}
|
||||
}
|
96
examples/breakout/platform/src/graphics/lowlevel/buffer.rs
Normal file
96
examples/breakout/platform/src/graphics/lowlevel/buffer.rs
Normal file
@ -0,0 +1,96 @@
|
||||
// Contains parts of https://github.com/sotrh/learn-wgpu
|
||||
// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS
|
||||
// file in the root directory of this distribution.
|
||||
//
|
||||
// Thank you, Benjamin!
|
||||
|
||||
// Contains parts of https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl
|
||||
// By Héctor Ramón, Iced contributors Licensed under the MIT license.
|
||||
// The license is included in the LEGAL_DETAILS file in the root directory of this distribution.
|
||||
|
||||
// Thank you Héctor Ramón and Iced contributors!
|
||||
|
||||
use std::mem;
|
||||
|
||||
use super::{quad::Quad, vertex::Vertex};
|
||||
use crate::graphics::primitives::rect::RectElt;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
pub struct RectBuffers {
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub quad_buffer: wgpu::Buffer,
|
||||
}
|
||||
|
||||
pub const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
||||
|
||||
const QUAD_VERTS: [Vertex; 4] = [
|
||||
Vertex {
|
||||
_position: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
_position: [1.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
_position: [1.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
_position: [0.0, 1.0],
|
||||
},
|
||||
];
|
||||
|
||||
pub const MAX_QUADS: usize = 1_000;
|
||||
|
||||
pub fn create_rect_buffers(
|
||||
gpu_device: &wgpu::Device,
|
||||
cmd_encoder: &mut wgpu::CommandEncoder,
|
||||
rects: &[RectElt],
|
||||
) -> RectBuffers {
|
||||
let vertex_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&QUAD_VERTS),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
let index_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&QUAD_INDICES),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
let quad_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: mem::size_of::<Quad>() as u64 * MAX_QUADS as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let quads: Vec<Quad> = rects.iter().map(|rect| to_quad(rect)).collect();
|
||||
|
||||
let buffer_size = (quads.len() as u64) * Quad::SIZE;
|
||||
|
||||
let staging_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&quads),
|
||||
usage: wgpu::BufferUsages::COPY_SRC,
|
||||
});
|
||||
|
||||
cmd_encoder.copy_buffer_to_buffer(&staging_buffer, 0, &quad_buffer, 0, buffer_size);
|
||||
|
||||
RectBuffers {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
quad_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_quad(rect_elt: &RectElt) -> Quad {
|
||||
Quad {
|
||||
pos: rect_elt.rect.pos.into(),
|
||||
width: rect_elt.rect.width,
|
||||
height: rect_elt.rect.height,
|
||||
color: (rect_elt.color.to_array()),
|
||||
border_color: rect_elt.border_color.into(),
|
||||
border_width: rect_elt.border_width,
|
||||
}
|
||||
}
|
5
examples/breakout/platform/src/graphics/lowlevel/mod.rs
Normal file
5
examples/breakout/platform/src/graphics/lowlevel/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod buffer;
|
||||
pub mod ortho;
|
||||
pub mod pipelines;
|
||||
pub mod vertex;
|
||||
pub mod quad;
|
118
examples/breakout/platform/src/graphics/lowlevel/ortho.rs
Normal file
118
examples/breakout/platform/src/graphics/lowlevel/ortho.rs
Normal file
@ -0,0 +1,118 @@
|
||||
use cgmath::{Matrix4, Ortho};
|
||||
use wgpu::util::DeviceExt;
|
||||
use wgpu::{
|
||||
BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer,
|
||||
ShaderStages,
|
||||
};
|
||||
|
||||
// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
// We can't use cgmath with bytemuck directly so we'll have
|
||||
// to convert the Matrix4 into a 4x4 f32 array
|
||||
ortho: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new(w: u32, h: u32) -> Self {
|
||||
let ortho: Matrix4<f32> = Ortho::<f32> {
|
||||
left: 0.0,
|
||||
right: w as f32,
|
||||
bottom: h as f32,
|
||||
top: 0.0,
|
||||
near: -1.0,
|
||||
far: 1.0,
|
||||
}
|
||||
.into();
|
||||
Self {
|
||||
ortho: ortho.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update orthographic buffer according to new window size
|
||||
pub fn update_ortho_buffer(
|
||||
inner_width: u32,
|
||||
inner_height: u32,
|
||||
gpu_device: &wgpu::Device,
|
||||
ortho_buffer: &Buffer,
|
||||
cmd_queue: &wgpu::Queue,
|
||||
) {
|
||||
let new_uniforms = Uniforms::new(inner_width, inner_height);
|
||||
|
||||
let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Ortho uniform buffer"),
|
||||
contents: bytemuck::cast_slice(&[new_uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC,
|
||||
});
|
||||
|
||||
// get a command encoder for the current frame
|
||||
let mut encoder = gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Resize"),
|
||||
});
|
||||
|
||||
// overwrite the new buffer over the old one
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&new_ortho_buffer,
|
||||
0,
|
||||
ortho_buffer,
|
||||
0,
|
||||
(std::mem::size_of::<Uniforms>() * vec![new_uniforms].as_slice().len())
|
||||
as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
cmd_queue.submit(Some(encoder.finish()));
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OrthoResources {
|
||||
pub buffer: Buffer,
|
||||
pub bind_group_layout: BindGroupLayout,
|
||||
pub bind_group: BindGroup,
|
||||
}
|
||||
|
||||
pub fn init_ortho(
|
||||
inner_width: u32,
|
||||
inner_height: u32,
|
||||
gpu_device: &wgpu::Device,
|
||||
) -> OrthoResources {
|
||||
let uniforms = Uniforms::new(inner_width, inner_height);
|
||||
|
||||
let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Ortho uniform buffer"),
|
||||
contents: bytemuck::cast_slice(&[uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
// bind groups consist of extra resources that are provided to the shaders
|
||||
let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("Ortho bind group layout"),
|
||||
});
|
||||
|
||||
let ortho_bind_group = gpu_device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &ortho_bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: ortho_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("Ortho bind group"),
|
||||
});
|
||||
|
||||
OrthoResources {
|
||||
buffer: ortho_buffer,
|
||||
bind_group_layout: ortho_bind_group_layout,
|
||||
bind_group: ortho_bind_group,
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
use super::ortho::{init_ortho, OrthoResources};
|
||||
use super::quad::Quad;
|
||||
use super::vertex::Vertex;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct RectResources {
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
pub ortho: OrthoResources,
|
||||
}
|
||||
|
||||
pub fn make_rect_pipeline(
|
||||
gpu_device: &wgpu::Device,
|
||||
surface_config: &wgpu::SurfaceConfiguration,
|
||||
) -> RectResources {
|
||||
let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device);
|
||||
|
||||
let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[&ortho.bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
let pipeline = create_render_pipeline(
|
||||
gpu_device,
|
||||
&pipeline_layout,
|
||||
surface_config.format,
|
||||
&wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/quad.wgsl"))),
|
||||
},
|
||||
);
|
||||
|
||||
RectResources { pipeline, ortho }
|
||||
}
|
||||
|
||||
pub fn create_render_pipeline(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::PipelineLayout,
|
||||
color_format: wgpu::TextureFormat,
|
||||
shader_module_desc: &wgpu::ShaderModuleDescriptor,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(shader_module_desc);
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Render pipeline"),
|
||||
layout: Some(layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[Vertex::DESC, Quad::DESC],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[wgpu::ColorTargetState {
|
||||
format: color_format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent {
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
},
|
||||
alpha: wgpu::BlendComponent::REPLACE,
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
})
|
||||
}
|
31
examples/breakout/platform/src/graphics/lowlevel/quad.rs
Normal file
31
examples/breakout/platform/src/graphics/lowlevel/quad.rs
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
|
||||
/// A polygon with 4 corners
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Quad {
|
||||
pub pos: [f32; 2],
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub color: [f32; 4],
|
||||
pub border_color: [f32; 4],
|
||||
pub border_width: f32,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for Quad {}
|
||||
unsafe impl bytemuck::Zeroable for Quad {}
|
||||
|
||||
impl Quad {
|
||||
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||
pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
|
||||
array_stride: Self::SIZE,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
1 => Float32x2,
|
||||
2 => Float32,
|
||||
3 => Float32,
|
||||
4 => Float32x4,
|
||||
5 => Float32x4,
|
||||
6 => Float32,
|
||||
),
|
||||
};
|
||||
}
|
35
examples/breakout/platform/src/graphics/lowlevel/vertex.rs
Normal file
35
examples/breakout/platform/src/graphics/lowlevel/vertex.rs
Normal file
@ -0,0 +1,35 @@
|
||||
// Inspired by https://github.com/sotrh/learn-wgpu
|
||||
// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS
|
||||
// file in the root directory of this distribution.
|
||||
//
|
||||
// Thank you, Benjamin!
|
||||
|
||||
// Inspired by https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl
|
||||
// By Héctor Ramón, Iced contributors Licensed under the MIT license.
|
||||
// The license is included in the LEGAL_DETAILS file in the root directory of this distribution.
|
||||
|
||||
// Thank you Héctor Ramón and Iced contributors!
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Zeroable, Pod)]
|
||||
pub struct Vertex {
|
||||
pub _position: [f32; 2],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||
pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
|
||||
array_stride: Self::SIZE,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
// position
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
4
examples/breakout/platform/src/graphics/mod.rs
Normal file
4
examples/breakout/platform/src/graphics/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod colors;
|
||||
pub mod lowlevel;
|
||||
pub mod primitives;
|
||||
pub mod style;
|
@ -0,0 +1,2 @@
|
||||
pub mod rect;
|
||||
pub mod text;
|
27
examples/breakout/platform/src/graphics/primitives/rect.rs
Normal file
27
examples/breakout/platform/src/graphics/primitives/rect.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use crate::graphics::colors::Rgba;
|
||||
use cgmath::Vector2;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct RectElt {
|
||||
pub rect: Rect,
|
||||
pub color: Rgba,
|
||||
pub border_width: f32,
|
||||
pub border_color: Rgba,
|
||||
}
|
||||
|
||||
/// These fields are ordered this way because in Roc, the corresponding stuct is:
|
||||
///
|
||||
/// { top : F32, left : F32, width : F32, height : F32 }
|
||||
///
|
||||
/// alphabetically, that's { height, left, top, width } - which works out to the same as:
|
||||
///
|
||||
/// struct Rect { height: f32, pos: Vector2<f32>, width: f32 }
|
||||
///
|
||||
/// ...because Vector2<f32> is a repr(C) struct of { x: f32, y: f32 }
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Rect {
|
||||
pub height: f32,
|
||||
pub pos: Vector2<f32>,
|
||||
pub width: f32,
|
||||
}
|
134
examples/breakout/platform/src/graphics/primitives/text.rs
Normal file
134
examples/breakout/platform/src/graphics/primitives/text.rs
Normal file
@ -0,0 +1,134 @@
|
||||
// Adapted from https://github.com/sotrh/learn-wgpu
|
||||
// by Benjamin Hansen - license information can be found in the COPYRIGHT
|
||||
// file in the root directory of this distribution.
|
||||
//
|
||||
// Thank you, Benjamin!
|
||||
|
||||
use crate::graphics::colors::Rgba;
|
||||
use crate::graphics::style::DEFAULT_FONT_SIZE;
|
||||
use ab_glyph::{FontArc, InvalidFont};
|
||||
use cgmath::Vector2;
|
||||
use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Text<'a> {
|
||||
pub position: Vector2<f32>,
|
||||
pub area_bounds: Vector2<f32>,
|
||||
pub color: Rgba,
|
||||
pub text: &'a str,
|
||||
pub size: f32,
|
||||
pub visible: bool,
|
||||
pub centered: bool,
|
||||
}
|
||||
|
||||
impl<'a> Default for Text<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
position: (0.0, 0.0).into(),
|
||||
area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(),
|
||||
color: Rgba::WHITE,
|
||||
text: "",
|
||||
size: DEFAULT_FONT_SIZE,
|
||||
visible: true,
|
||||
centered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn layout_from_text(text: &Text) -> wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker> {
|
||||
// wgpu_glyph::Layout::default().h_align(if text.centered {
|
||||
// wgpu_glyph::HorizontalAlign::Center
|
||||
// } else {
|
||||
// wgpu_glyph::HorizontalAlign::Left
|
||||
// })
|
||||
// }
|
||||
|
||||
// fn section_from_text<'a>(
|
||||
// text: &'a Text,
|
||||
// layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
|
||||
// ) -> wgpu_glyph::Section<'a> {
|
||||
// Section {
|
||||
// screen_position: text.position.into(),
|
||||
// bounds: text.area_bounds.into(),
|
||||
// layout,
|
||||
// ..Section::default()
|
||||
// }
|
||||
// .add_text(
|
||||
// wgpu_glyph::Text::new(text.text)
|
||||
// .with_color(text.color)
|
||||
// .with_scale(text.size),
|
||||
// )
|
||||
// }
|
||||
|
||||
// pub fn owned_section_from_text(text: &Text) -> OwnedSection {
|
||||
// let layout = layout_from_text(text);
|
||||
|
||||
// OwnedSection {
|
||||
// screen_position: text.position.into(),
|
||||
// bounds: text.area_bounds.into(),
|
||||
// layout,
|
||||
// ..OwnedSection::default()
|
||||
// }
|
||||
// .add_text(
|
||||
// glyph_brush::OwnedText::new(text.text)
|
||||
// .with_color(Vector4::from(text.color))
|
||||
// .with_scale(text.size),
|
||||
// )
|
||||
// }
|
||||
|
||||
// pub fn owned_section_from_glyph_texts(
|
||||
// text: Vec<glyph_brush::OwnedText>,
|
||||
// screen_position: (f32, f32),
|
||||
// area_bounds: (f32, f32),
|
||||
// layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
|
||||
// ) -> glyph_brush::OwnedSection {
|
||||
// glyph_brush::OwnedSection {
|
||||
// screen_position,
|
||||
// bounds: area_bounds,
|
||||
// layout,
|
||||
// text,
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) {
|
||||
// let layout = layout_from_text(text);
|
||||
|
||||
// let section = section_from_text(text, layout);
|
||||
|
||||
// glyph_brush.queue(section.clone());
|
||||
// }
|
||||
|
||||
// fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect {
|
||||
// let position = glyph.glyph.position;
|
||||
// let px_scale = glyph.glyph.scale;
|
||||
// let width = glyph_width(&glyph.glyph);
|
||||
// let height = px_scale.y;
|
||||
// let top_y = glyph_top_y(&glyph.glyph);
|
||||
|
||||
// Rect {
|
||||
// pos: [position.x, top_y].into(),
|
||||
// width,
|
||||
// height,
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn glyph_top_y(glyph: &Glyph) -> f32 {
|
||||
// let height = glyph.scale.y;
|
||||
|
||||
// glyph.position.y - height * 0.75
|
||||
// }
|
||||
|
||||
// pub fn glyph_width(glyph: &Glyph) -> f32 {
|
||||
// glyph.scale.x * 0.4765
|
||||
// }
|
||||
|
||||
pub fn build_glyph_brush(
|
||||
gpu_device: &wgpu::Device,
|
||||
render_format: wgpu::TextureFormat,
|
||||
) -> Result<GlyphBrush<()>, InvalidFont> {
|
||||
let inconsolata = FontArc::try_from_slice(include_bytes!(
|
||||
"../../../../../../editor/Inconsolata-Regular.ttf"
|
||||
))?;
|
||||
|
||||
Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format))
|
||||
}
|
60
examples/breakout/platform/src/graphics/shaders/quad.wgsl
Normal file
60
examples/breakout/platform/src/graphics/shaders/quad.wgsl
Normal file
@ -0,0 +1,60 @@
|
||||
|
||||
|
||||
struct Globals {
|
||||
ortho: mat4x4<f32>;
|
||||
};
|
||||
|
||||
@group(0)
|
||||
@binding(0)
|
||||
var<uniform> globals: Globals;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>;
|
||||
};
|
||||
|
||||
struct Quad {
|
||||
@location(1) pos: vec2<f32>; // can't use the name "position" twice for compatibility with metal on MacOS
|
||||
@location(2) width: f32;
|
||||
@location(3) height: f32;
|
||||
@location(4) color: vec4<f32>;
|
||||
@location(5) border_color: vec4<f32>;
|
||||
@location(6) border_width: f32;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>;
|
||||
@location(0) color: vec4<f32>;
|
||||
@location(1) border_color: vec4<f32>;
|
||||
@location(2) border_width: f32;
|
||||
};
|
||||
|
||||
@stage(vertex)
|
||||
fn vs_main(
|
||||
input: VertexInput,
|
||||
quad: Quad
|
||||
) -> VertexOutput {
|
||||
|
||||
var transform: mat4x4<f32> = mat4x4<f32>(
|
||||
vec4<f32>(quad.width, 0.0, 0.0, 0.0),
|
||||
vec4<f32>(0.0, quad.height, 0.0, 0.0),
|
||||
vec4<f32>(0.0, 0.0, 1.0, 0.0),
|
||||
vec4<f32>(quad.pos, 0.0, 1.0)
|
||||
);
|
||||
|
||||
var out: VertexOutput;
|
||||
|
||||
out.position = globals.ortho * transform * vec4<f32>(input.position, 0.0, 1.0);;
|
||||
out.color = quad.color;
|
||||
out.border_color = quad.border_color;
|
||||
out.border_width = quad.border_width;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@stage(fragment)
|
||||
fn fs_main(
|
||||
input: VertexOutput
|
||||
) -> @location(0) vec4<f32> {
|
||||
return input.color;
|
||||
}
|
1
examples/breakout/platform/src/graphics/style.rs
Normal file
1
examples/breakout/platform/src/graphics/style.rs
Normal file
@ -0,0 +1 @@
|
||||
pub const DEFAULT_FONT_SIZE: f32 = 30.0;
|
513
examples/breakout/platform/src/gui.rs
Normal file
513
examples/breakout/platform/src/gui.rs
Normal file
@ -0,0 +1,513 @@
|
||||
use crate::{
|
||||
graphics::{
|
||||
colors::Rgba,
|
||||
lowlevel::buffer::create_rect_buffers,
|
||||
lowlevel::{buffer::MAX_QUADS, ortho::update_ortho_buffer},
|
||||
lowlevel::{buffer::QUAD_INDICES, pipelines},
|
||||
primitives::{
|
||||
rect::{Rect, RectElt},
|
||||
text::build_glyph_brush,
|
||||
},
|
||||
},
|
||||
roc::{self, Bounds, RocElem, RocElemTag, RocEvent},
|
||||
};
|
||||
use cgmath::{Vector2, Vector4};
|
||||
use glyph_brush::{GlyphCruncher, OwnedSection};
|
||||
use pipelines::RectResources;
|
||||
use std::{
|
||||
error::Error,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView};
|
||||
use wgpu_glyph::GlyphBrush;
|
||||
use winit::{
|
||||
dpi::PhysicalSize,
|
||||
event,
|
||||
event::{ElementState, Event, ModifiersState, StartCause},
|
||||
event_loop::ControlFlow,
|
||||
platform::run_return::EventLoopExtRunReturn,
|
||||
};
|
||||
|
||||
// Inspired by:
|
||||
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, which is licensed under the MIT license
|
||||
// https://github.com/cloudhead/rgx by Alexis Sellier, which is licensed under the MIT license
|
||||
//
|
||||
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
|
||||
|
||||
const TIME_BETWEEN_TICKS: Duration = Duration::new(0, 1000 / 60);
|
||||
|
||||
pub fn run_event_loop(title: &str, window_bounds: Bounds) -> Result<(), Box<dyn Error>> {
|
||||
let (mut model, mut elems) = roc::init_and_render(window_bounds);
|
||||
|
||||
// Open window and create a surface
|
||||
let mut event_loop = winit::event_loop::EventLoop::new();
|
||||
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_inner_size(PhysicalSize::new(window_bounds.width, window_bounds.height))
|
||||
.with_title(title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
macro_rules! update_and_rerender {
|
||||
($event:expr) => {
|
||||
// TODO use (model, elems) = ... once we've upgraded rust versions
|
||||
let pair = roc::update_and_render(model, $event);
|
||||
|
||||
model = pair.0;
|
||||
elems = pair.1;
|
||||
|
||||
window.request_redraw();
|
||||
};
|
||||
}
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
|
||||
// Initialize GPU
|
||||
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.expect(r#"Request adapter
|
||||
If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor
|
||||
"#);
|
||||
|
||||
adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::default(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Request device")
|
||||
});
|
||||
|
||||
// Create staging belt and a local pool
|
||||
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
|
||||
let mut local_pool = futures::executor::LocalPool::new();
|
||||
let local_spawner = local_pool.spawner();
|
||||
|
||||
// Prepare swap chain
|
||||
let render_format = wgpu::TextureFormat::Bgra8Unorm;
|
||||
let mut size = window.inner_size();
|
||||
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: render_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Mailbox,
|
||||
};
|
||||
|
||||
surface.configure(&gpu_device, &surface_config);
|
||||
|
||||
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config);
|
||||
|
||||
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
|
||||
let mut keyboard_modifiers = ModifiersState::empty();
|
||||
|
||||
// Render loop
|
||||
let app_start_time = Instant::now();
|
||||
let mut next_tick = app_start_time + TIME_BETWEEN_TICKS;
|
||||
|
||||
window.request_redraw();
|
||||
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
match event {
|
||||
// Close
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
// Resize
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::Resized(new_size),
|
||||
..
|
||||
} => {
|
||||
size = new_size;
|
||||
|
||||
surface.configure(
|
||||
&gpu_device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: render_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Mailbox,
|
||||
},
|
||||
);
|
||||
|
||||
update_ortho_buffer(
|
||||
size.width,
|
||||
size.height,
|
||||
&gpu_device,
|
||||
&rect_resources.ortho.buffer,
|
||||
&cmd_queue,
|
||||
);
|
||||
|
||||
update_and_rerender!(RocEvent::Resize(Bounds {
|
||||
height: size.height as f32,
|
||||
width: size.width as f32,
|
||||
}));
|
||||
}
|
||||
// Keyboard input
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
event::WindowEvent::KeyboardInput {
|
||||
input:
|
||||
event::KeyboardInput {
|
||||
virtual_keycode: Some(keycode),
|
||||
state: input_state,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let roc_event = match input_state {
|
||||
ElementState::Pressed => RocEvent::KeyDown(keycode.into()),
|
||||
ElementState::Released => RocEvent::KeyUp(keycode.into()),
|
||||
};
|
||||
|
||||
model = roc::update(model, roc_event);
|
||||
}
|
||||
// Modifiers Changed
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::ModifiersChanged(modifiers),
|
||||
..
|
||||
} => {
|
||||
keyboard_modifiers = modifiers;
|
||||
}
|
||||
Event::RedrawRequested { .. } => {
|
||||
// Get a command cmd_encoder for the current frame
|
||||
let mut cmd_encoder =
|
||||
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Redraw"),
|
||||
});
|
||||
|
||||
let surface_texture = surface
|
||||
.get_current_texture()
|
||||
.expect("Failed to acquire next SwapChainTexture");
|
||||
|
||||
let view = surface_texture
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
for elem in elems.iter() {
|
||||
let (_bounds, drawable) = to_drawable(
|
||||
elem,
|
||||
Bounds {
|
||||
width: size.width as f32,
|
||||
height: size.height as f32,
|
||||
},
|
||||
&mut glyph_brush,
|
||||
);
|
||||
|
||||
process_drawable(
|
||||
drawable,
|
||||
&mut staging_belt,
|
||||
&mut glyph_brush,
|
||||
&mut cmd_encoder,
|
||||
&view,
|
||||
&gpu_device,
|
||||
&rect_resources,
|
||||
wgpu::LoadOp::Load,
|
||||
Bounds {
|
||||
width: size.width as f32,
|
||||
height: size.height as f32,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
staging_belt.finish();
|
||||
cmd_queue.submit(Some(cmd_encoder.finish()));
|
||||
surface_texture.present();
|
||||
|
||||
// Recall unused staging buffers
|
||||
use futures::task::SpawnExt;
|
||||
|
||||
local_spawner
|
||||
.spawn(staging_belt.recall())
|
||||
.expect("Recall staging belt");
|
||||
|
||||
local_pool.run_until_stalled();
|
||||
}
|
||||
Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
requested_resume, ..
|
||||
}) => {
|
||||
// Only run this logic if this is the tick we originally requested.
|
||||
if requested_resume == next_tick {
|
||||
let now = Instant::now();
|
||||
|
||||
// Set a new next_tick *before* running update and rerender,
|
||||
// so their runtime isn't factored into when we want to render next.
|
||||
next_tick = now + TIME_BETWEEN_TICKS;
|
||||
|
||||
let tick = now.saturating_duration_since(app_start_time);
|
||||
|
||||
update_and_rerender!(RocEvent::Tick(tick));
|
||||
|
||||
*control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Keep waiting until the next tick.
|
||||
*control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_rects(
|
||||
all_rects: &[RectElt],
|
||||
cmd_encoder: &mut CommandEncoder,
|
||||
texture_view: &TextureView,
|
||||
gpu_device: &wgpu::Device,
|
||||
rect_resources: &RectResources,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
) {
|
||||
let rect_buffers = create_rect_buffers(gpu_device, cmd_encoder, all_rects);
|
||||
|
||||
let mut render_pass = begin_render_pass(cmd_encoder, texture_view, load_op);
|
||||
|
||||
render_pass.set_pipeline(&rect_resources.pipeline);
|
||||
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
|
||||
|
||||
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
|
||||
render_pass.set_vertex_buffer(1, rect_buffers.quad_buffer.slice(..));
|
||||
|
||||
render_pass.set_index_buffer(
|
||||
rect_buffers.index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint16,
|
||||
);
|
||||
|
||||
render_pass.draw_indexed(0..QUAD_INDICES.len() as u32, 0, 0..MAX_QUADS as u32);
|
||||
}
|
||||
|
||||
fn begin_render_pass<'a>(
|
||||
cmd_encoder: &'a mut CommandEncoder,
|
||||
texture_view: &'a TextureView,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
) -> RenderPass<'a> {
|
||||
cmd_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||
view: texture_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: load_op,
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: None,
|
||||
label: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Drawable {
|
||||
pos: Vector2<f32>,
|
||||
bounds: Bounds,
|
||||
content: DrawableContent,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum DrawableContent {
|
||||
/// This stores an actual Section because an earlier step needs to know the bounds of
|
||||
/// the text, and making a Section is a convenient way to compute those bounds.
|
||||
Text(OwnedSection, Vector2<f32>),
|
||||
FillRect {
|
||||
color: Rgba,
|
||||
border_width: f32,
|
||||
border_color: Rgba,
|
||||
},
|
||||
}
|
||||
|
||||
fn process_drawable(
|
||||
drawable: Drawable,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
glyph_brush: &mut GlyphBrush<()>,
|
||||
cmd_encoder: &mut CommandEncoder,
|
||||
texture_view: &TextureView,
|
||||
gpu_device: &wgpu::Device,
|
||||
rect_resources: &RectResources,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
texture_size: Bounds,
|
||||
) {
|
||||
draw(
|
||||
drawable.bounds,
|
||||
drawable.content,
|
||||
drawable.pos,
|
||||
staging_belt,
|
||||
glyph_brush,
|
||||
cmd_encoder,
|
||||
texture_view,
|
||||
gpu_device,
|
||||
rect_resources,
|
||||
load_op,
|
||||
texture_size,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw(
|
||||
bounds: Bounds,
|
||||
content: DrawableContent,
|
||||
pos: Vector2<f32>,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
glyph_brush: &mut GlyphBrush<()>,
|
||||
cmd_encoder: &mut CommandEncoder,
|
||||
texture_view: &TextureView,
|
||||
gpu_device: &wgpu::Device,
|
||||
rect_resources: &RectResources,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
texture_size: Bounds,
|
||||
) {
|
||||
use DrawableContent::*;
|
||||
|
||||
match content {
|
||||
Text(section, offset) => {
|
||||
glyph_brush.queue(section.with_screen_position(pos + offset).to_borrowed());
|
||||
|
||||
glyph_brush
|
||||
.draw_queued(
|
||||
gpu_device,
|
||||
staging_belt,
|
||||
cmd_encoder,
|
||||
texture_view,
|
||||
texture_size.width as u32, // TODO why do we make these be u32 and then cast to f32 in orthorgraphic_projection?
|
||||
texture_size.height as u32,
|
||||
)
|
||||
.expect("Failed to draw text element");
|
||||
}
|
||||
FillRect {
|
||||
color,
|
||||
border_width,
|
||||
border_color,
|
||||
} => {
|
||||
// TODO store all these colors and things in FillRect
|
||||
let rect_elt = RectElt {
|
||||
rect: Rect {
|
||||
pos,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
color,
|
||||
border_width,
|
||||
border_color,
|
||||
};
|
||||
|
||||
// TODO inline draw_rects into here!
|
||||
draw_rects(
|
||||
&[rect_elt],
|
||||
cmd_encoder,
|
||||
texture_view,
|
||||
gpu_device,
|
||||
rect_resources,
|
||||
load_op,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// focused_elem is the currently-focused element (or NULL if nothing has the focus)
|
||||
fn to_drawable(
|
||||
elem: &RocElem,
|
||||
bounds: Bounds,
|
||||
glyph_brush: &mut GlyphBrush<()>,
|
||||
) -> (Bounds, Drawable) {
|
||||
use RocElemTag::*;
|
||||
|
||||
match elem.tag() {
|
||||
Rect => {
|
||||
let rect = unsafe { &elem.entry().rect };
|
||||
|
||||
let bounds = Bounds {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
};
|
||||
|
||||
let drawable = Drawable {
|
||||
pos: (rect.left, rect.top).into(),
|
||||
bounds,
|
||||
content: DrawableContent::FillRect {
|
||||
color: rect.color,
|
||||
border_width: 1.0,
|
||||
border_color: rect.color,
|
||||
},
|
||||
};
|
||||
|
||||
(bounds, drawable)
|
||||
}
|
||||
Text => {
|
||||
let text = unsafe { &elem.entry().text };
|
||||
let is_centered = true; // TODO don't hardcode this
|
||||
let layout = wgpu_glyph::Layout::default().h_align(if is_centered {
|
||||
wgpu_glyph::HorizontalAlign::Center
|
||||
} else {
|
||||
wgpu_glyph::HorizontalAlign::Left
|
||||
});
|
||||
|
||||
let section = owned_section_from_str(text.as_str(), bounds, layout);
|
||||
|
||||
// Calculate the bounds and offset by measuring glyphs
|
||||
let text_bounds;
|
||||
let offset;
|
||||
|
||||
match glyph_brush.glyph_bounds(section.to_borrowed()) {
|
||||
Some(glyph_bounds) => {
|
||||
text_bounds = Bounds {
|
||||
width: glyph_bounds.max.x - glyph_bounds.min.x,
|
||||
height: glyph_bounds.max.y - glyph_bounds.min.y,
|
||||
};
|
||||
|
||||
offset = (-glyph_bounds.min.x, -glyph_bounds.min.y).into();
|
||||
}
|
||||
None => {
|
||||
text_bounds = Bounds {
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
};
|
||||
|
||||
offset = (0.0, 0.0).into();
|
||||
}
|
||||
}
|
||||
|
||||
let drawable = Drawable {
|
||||
pos: (0.0, 0.0).into(), // TODO store the pos in Text and read it here
|
||||
bounds: text_bounds,
|
||||
content: DrawableContent::Text(section, offset),
|
||||
};
|
||||
|
||||
(text_bounds, drawable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn owned_section_from_str(
|
||||
string: &str,
|
||||
bounds: Bounds,
|
||||
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
|
||||
) -> OwnedSection {
|
||||
// TODO don't hardcode any of this!
|
||||
let color = Rgba::WHITE;
|
||||
let size: f32 = 40.0;
|
||||
|
||||
OwnedSection {
|
||||
bounds: (bounds.width, bounds.height),
|
||||
layout,
|
||||
..OwnedSection::default()
|
||||
}
|
||||
.add_text(
|
||||
glyph_brush::OwnedText::new(string)
|
||||
.with_color(Vector4::from(color))
|
||||
.with_scale(size),
|
||||
)
|
||||
}
|
16
examples/breakout/platform/src/lib.rs
Normal file
16
examples/breakout/platform/src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
||||
mod graphics;
|
||||
mod gui;
|
||||
mod roc;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main() -> i32 {
|
||||
let bounds = roc::Bounds {
|
||||
width: 1900.0,
|
||||
height: 1000.0,
|
||||
};
|
||||
|
||||
gui::run_event_loop("RocOut!", bounds).expect("Error running event loop");
|
||||
|
||||
// Exit code
|
||||
0
|
||||
}
|
3
examples/breakout/platform/src/main.rs
Normal file
3
examples/breakout/platform/src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
std::process::exit(host::rust_main());
|
||||
}
|
435
examples/breakout/platform/src/roc.rs
Normal file
435
examples/breakout/platform/src/roc.rs
Normal file
@ -0,0 +1,435 @@
|
||||
use crate::graphics::colors::Rgba;
|
||||
use core::alloc::Layout;
|
||||
use core::ffi::c_void;
|
||||
use core::mem::ManuallyDrop;
|
||||
use roc_std::{ReferenceCount, RocList, RocStr};
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::raw::c_char;
|
||||
use std::time::Duration;
|
||||
use winit::event::VirtualKeyCode;
|
||||
|
||||
extern "C" {
|
||||
// program
|
||||
|
||||
#[link_name = "roc__programForHost_1_exposed_generic"]
|
||||
fn roc_program() -> ();
|
||||
|
||||
#[link_name = "roc__programForHost_size"]
|
||||
fn roc_program_size() -> i64;
|
||||
|
||||
// init
|
||||
|
||||
#[link_name = "roc__programForHost_1_Init_caller"]
|
||||
fn call_init(size: *const Bounds, closure_data: *const u8, output: *mut Model);
|
||||
|
||||
#[link_name = "roc__programForHost_1_Init_size"]
|
||||
fn init_size() -> i64;
|
||||
|
||||
#[link_name = "roc__programForHost_1_Init_result_size"]
|
||||
fn init_result_size() -> i64;
|
||||
|
||||
// update
|
||||
|
||||
#[link_name = "roc__programForHost_1_Update_caller"]
|
||||
fn call_update(
|
||||
model: *const Model,
|
||||
event: *const RocEvent,
|
||||
closure_data: *const u8,
|
||||
output: *mut Model,
|
||||
);
|
||||
|
||||
#[link_name = "roc__programForHost_1_Update_size"]
|
||||
fn update_size() -> i64;
|
||||
|
||||
#[link_name = "roc__programForHost_1_Update_result_size"]
|
||||
fn update_result_size() -> i64;
|
||||
|
||||
// render
|
||||
|
||||
#[link_name = "roc__programForHost_1_Render_caller"]
|
||||
fn call_render(model: *const Model, closure_data: *const u8, output: *mut RocList<RocElem>);
|
||||
|
||||
#[link_name = "roc__programForHost_1_Render_size"]
|
||||
fn roc_render_size() -> i64;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union RocEventEntry {
|
||||
pub key_down: RocKeyCode,
|
||||
pub key_up: RocKeyCode,
|
||||
pub resize: Bounds,
|
||||
pub tick: [u8; 16], // u128 is unsupported in repr(C)
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RocEventTag {
|
||||
KeyDown = 0,
|
||||
KeyUp,
|
||||
Resize,
|
||||
Tick,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
|
||||
pub struct RocEvent {
|
||||
entry: RocEventEntry,
|
||||
tag: RocEventTag,
|
||||
}
|
||||
|
||||
impl Debug for RocEvent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use RocEventTag::*;
|
||||
|
||||
match self.tag() {
|
||||
KeyDown => unsafe { self.entry().key_down }.fmt(f),
|
||||
KeyUp => unsafe { self.entry().key_up }.fmt(f),
|
||||
Resize => unsafe { self.entry().resize }.fmt(f),
|
||||
Tick => unsafe { self.entry().tick }.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RocEvent {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub fn tag(&self) -> RocEventTag {
|
||||
self.tag
|
||||
}
|
||||
|
||||
pub fn entry(&self) -> &RocEventEntry {
|
||||
&self.entry
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Resize(size: Bounds) -> Self {
|
||||
Self {
|
||||
tag: RocEventTag::Resize,
|
||||
entry: RocEventEntry { resize: size },
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn KeyDown(keycode: RocKeyCode) -> Self {
|
||||
Self {
|
||||
tag: RocEventTag::KeyDown,
|
||||
entry: RocEventEntry { key_down: keycode },
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn KeyUp(keycode: RocKeyCode) -> Self {
|
||||
Self {
|
||||
tag: RocEventTag::KeyUp,
|
||||
entry: RocEventEntry { key_up: keycode },
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Tick(duration: Duration) -> Self {
|
||||
Self {
|
||||
tag: RocEventTag::Tick,
|
||||
entry: RocEventEntry {
|
||||
tick: duration.as_nanos().to_ne_bytes(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RocKeyCode {
|
||||
Left = 0,
|
||||
Other,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl From<VirtualKeyCode> for RocKeyCode {
|
||||
fn from(keycode: VirtualKeyCode) -> Self {
|
||||
use VirtualKeyCode::*;
|
||||
|
||||
match keycode {
|
||||
Left => RocKeyCode::Left,
|
||||
Right => RocKeyCode::Right,
|
||||
_ => RocKeyCode::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
|
||||
return libc::malloc(size);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_realloc(
|
||||
c_ptr: *mut c_void,
|
||||
new_size: usize,
|
||||
_old_size: usize,
|
||||
_alignment: u32,
|
||||
) -> *mut c_void {
|
||||
return libc::realloc(c_ptr, new_size);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
|
||||
return libc::free(c_ptr);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
|
||||
match tag_id {
|
||||
0 => {
|
||||
let slice = CStr::from_ptr(c_ptr as *const c_char);
|
||||
let string = slice.to_str().unwrap();
|
||||
eprintln!("Roc hit a panic: {}", string);
|
||||
std::process::exit(1);
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
|
||||
libc::memcpy(dst, src, n)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
|
||||
libc::memset(dst, c, n)
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ElemId(*const RocElemEntry);
|
||||
|
||||
#[repr(C)]
|
||||
pub union RocElemEntry {
|
||||
pub rect: ManuallyDrop<RocRect>,
|
||||
pub text: ManuallyDrop<RocStr>,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RocElemTag {
|
||||
Rect = 0,
|
||||
Text = 1,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
|
||||
pub struct RocElem {
|
||||
entry: RocElemEntry,
|
||||
tag: RocElemTag,
|
||||
}
|
||||
|
||||
impl Debug for RocElem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use RocElemTag::*;
|
||||
|
||||
match self.tag() {
|
||||
Rect => unsafe { &*self.entry().rect }.fmt(f),
|
||||
Text => unsafe { &*self.entry().text }.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RocElem {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub fn tag(&self) -> RocElemTag {
|
||||
self.tag
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn entry(&self) -> &RocElemEntry {
|
||||
&self.entry
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn rect(styles: ButtonStyles) -> RocElem {
|
||||
todo!("restore rect() method")
|
||||
// let rect = RocRect { styles };
|
||||
// let entry = RocElemEntry {
|
||||
// rect: ManuallyDrop::new(rect),
|
||||
// };
|
||||
|
||||
// Self::elem_from_tag(entry, RocElemTag::Rect)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn text<T: Into<RocStr>>(into_roc_str: T) -> RocElem {
|
||||
todo!("TODO restore text method")
|
||||
// let entry = RocElemEntry {
|
||||
// text: ManuallyDrop::new(into_roc_str.into()),
|
||||
// };
|
||||
|
||||
// Self::elem_from_tag(entry, RocElemTag::Text)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct RocRect {
|
||||
pub color: Rgba,
|
||||
|
||||
// These must be in this order for alphabetization!
|
||||
pub height: f32,
|
||||
pub left: f32,
|
||||
pub top: f32,
|
||||
pub width: f32,
|
||||
}
|
||||
|
||||
unsafe impl ReferenceCount for RocElem {
|
||||
/// Increment the reference count.
|
||||
fn increment(&self) {
|
||||
use RocElemTag::*;
|
||||
|
||||
match self.tag() {
|
||||
Rect => { /* nothing to increment! */ }
|
||||
Text => unsafe { &*self.entry().text }.increment(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrement the reference count.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `ptr` points to a value with a non-zero
|
||||
/// reference count.
|
||||
unsafe fn decrement(ptr: *const Self) {
|
||||
use RocElemTag::*;
|
||||
|
||||
let elem = &*ptr;
|
||||
|
||||
match elem.tag() {
|
||||
Rect => { /* nothing to decrement! */ }
|
||||
Text => ReferenceCount::decrement(&*elem.entry().text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct ButtonStyles {
|
||||
pub bg_color: Rgba,
|
||||
pub border_color: Rgba,
|
||||
pub border_width: f32,
|
||||
pub text_color: Rgba,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct Bounds {
|
||||
pub height: f32,
|
||||
pub width: f32,
|
||||
}
|
||||
|
||||
type Model = c_void;
|
||||
|
||||
/// Call the app's init function, then render and return that result
|
||||
pub fn init_and_render(bounds: Bounds) -> (*const Model, RocList<RocElem>) {
|
||||
let closure_data_buf;
|
||||
let closure_layout;
|
||||
|
||||
// Call init to get the initial model
|
||||
let model = unsafe {
|
||||
let ret_val_layout = Layout::array::<u8>(init_result_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model;
|
||||
|
||||
closure_layout = Layout::array::<u8>(init_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
closure_data_buf = std::alloc::alloc(closure_layout);
|
||||
|
||||
call_init(&bounds, closure_data_buf, ret_val_buf);
|
||||
|
||||
ret_val_buf
|
||||
};
|
||||
|
||||
// Call render passing the model to get the initial Elems
|
||||
let elems = unsafe {
|
||||
let mut ret_val: MaybeUninit<RocList<RocElem>> = MaybeUninit::uninit();
|
||||
|
||||
// Reuse the buffer from the previous closure if possible
|
||||
let closure_data_buf =
|
||||
std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize);
|
||||
|
||||
call_render(model, closure_data_buf, ret_val.as_mut_ptr());
|
||||
|
||||
std::alloc::dealloc(closure_data_buf, closure_layout);
|
||||
|
||||
ret_val.assume_init()
|
||||
};
|
||||
|
||||
(model, elems)
|
||||
}
|
||||
|
||||
/// Call the app's update function, then render and return that result
|
||||
pub fn update(model: *const Model, event: RocEvent) -> *const Model {
|
||||
let closure_data_buf;
|
||||
let closure_layout;
|
||||
|
||||
// Call update to get the new model
|
||||
unsafe {
|
||||
let ret_val_layout = Layout::array::<u8>(update_result_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model;
|
||||
|
||||
closure_layout = Layout::array::<u8>(update_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
closure_data_buf = std::alloc::alloc(closure_layout);
|
||||
|
||||
call_update(model, &event, closure_data_buf, ret_val_buf);
|
||||
|
||||
ret_val_buf
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the app's update function, then render and return that result
|
||||
pub fn update_and_render(model: *const Model, event: RocEvent) -> (*const Model, RocList<RocElem>) {
|
||||
let closure_data_buf;
|
||||
let closure_layout;
|
||||
|
||||
// Call update to get the new model
|
||||
let model = unsafe {
|
||||
let ret_val_layout = Layout::array::<u8>(update_result_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model;
|
||||
|
||||
closure_layout = Layout::array::<u8>(update_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
closure_data_buf = std::alloc::alloc(closure_layout);
|
||||
|
||||
call_update(model, &event, closure_data_buf, ret_val_buf);
|
||||
|
||||
ret_val_buf
|
||||
};
|
||||
|
||||
// Call render passing the model to get the initial Elems
|
||||
let elems = unsafe {
|
||||
let mut ret_val: MaybeUninit<RocList<RocElem>> = MaybeUninit::uninit();
|
||||
|
||||
// Reuse the buffer from the previous closure if possible
|
||||
let closure_data_buf =
|
||||
std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize);
|
||||
|
||||
call_render(model, closure_data_buf, ret_val.as_mut_ptr());
|
||||
|
||||
std::alloc::dealloc(closure_data_buf, closure_layout);
|
||||
|
||||
ret_val.assume_init()
|
||||
};
|
||||
|
||||
(model, elems)
|
||||
}
|
1
examples/gui/platform/.gitignore
vendored
Normal file
1
examples/gui/platform/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
target
|
172
examples/gui/platform/src/focus.rs
Normal file
172
examples/gui/platform/src/focus.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use crate::roc::{ElemId, RocElem, RocElemTag};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Focus {
|
||||
focused: Option<ElemId>,
|
||||
focused_ancestors: Vec<(ElemId, usize)>,
|
||||
}
|
||||
|
||||
impl Default for Focus {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
focused: None,
|
||||
focused_ancestors: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Focus {
|
||||
pub fn focused_elem(&self) -> Option<ElemId> {
|
||||
self.focused
|
||||
}
|
||||
|
||||
/// e.g. the user pressed Tab.
|
||||
///
|
||||
/// This is in contrast to next_local, which advances within a button group.
|
||||
/// For example, if I have three radio buttons in a group, pressing the
|
||||
/// arrow keys will cycle through them over and over without exiting the group -
|
||||
/// whereas pressing Tab will cycle through them once and then exit the group.
|
||||
pub fn next_global(&mut self, root: &RocElem) {
|
||||
match self.focused {
|
||||
Some(focused) => {
|
||||
// while let Some((ancestor_id, index)) = self.focused_ancestors.pop() {
|
||||
// let ancestor = ancestor_id.elem();
|
||||
|
||||
// // TODO FIXME - right now this will re-traverse a lot of ground! To prevent this,
|
||||
// // we should remember past indices searched, and tell the ancestors "hey stop searching when"
|
||||
// // you reach these indices, because they were already covered previously.
|
||||
// // One potentially easy way to do this: pass a min_index and max_index, and only look between those!
|
||||
// //
|
||||
// // Related idea: instead of doing .pop() here, iterate normally so we can `break;` after storing
|
||||
// // `new_ancestors = Some(next_ancestors);` - this way, we still have access to the full ancestry, and
|
||||
// // can maybe even pass it in to make it clear what work has already been done!
|
||||
// if let Some((new_id, new_ancestors)) =
|
||||
// Self::next_focusable_sibling(focused, Some(ancestor), Some(index))
|
||||
// {
|
||||
// // We found the next element to focus, so record that.
|
||||
// self.focused = Some(new_id);
|
||||
|
||||
// // We got a path to the new focusable's ancestor(s), so add them to the path.
|
||||
// // (This may restore some of the ancestors we've been .pop()-ing as we iterated.)
|
||||
// self.focused_ancestors.extend(new_ancestors);
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Need to write a bunch of tests for this, especially tests of focus wrapping around - e.g.
|
||||
// // what happens if it wraps around to a sibling? What happens if it wraps around to something
|
||||
// // higher up the tree? Lower down the tree? What if nothing is focusable?
|
||||
// // A separate question: what if we should have a separate text-to-speech concept separate from focus?
|
||||
// }
|
||||
}
|
||||
None => {
|
||||
// Nothing was focused in the first place, so try to focus the root.
|
||||
if root.is_focusable() {
|
||||
self.focused = Some(root.id());
|
||||
self.focused_ancestors = Vec::new();
|
||||
} else if let Some((new_id, new_ancestors)) =
|
||||
Self::next_focusable_sibling(root, None, None)
|
||||
{
|
||||
// If the root itself is not focusable, use its next focusable sibling.
|
||||
self.focused = Some(new_id);
|
||||
self.focused_ancestors = new_ancestors;
|
||||
}
|
||||
|
||||
// Regardless of whether we found a focusable Elem, we're done.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the next focusable sibling element after this one.
|
||||
/// If this element has no siblings, or no *next* sibling after the given index
|
||||
/// (e.g. the given index refers to the last element in a Row element), return None.
|
||||
fn next_focusable_sibling(
|
||||
elem: &RocElem,
|
||||
ancestor: Option<&RocElem>,
|
||||
opt_index: Option<usize>,
|
||||
) -> Option<(ElemId, Vec<(ElemId, usize)>)> {
|
||||
use RocElemTag::*;
|
||||
|
||||
match elem.tag() {
|
||||
Button | Text => None,
|
||||
Row | Col => {
|
||||
let children = unsafe { &elem.entry().row_or_col.children.as_slice() };
|
||||
let iter = match opt_index {
|
||||
Some(focus_index) => children[0..focus_index].iter(),
|
||||
None => children.iter(),
|
||||
};
|
||||
|
||||
for child in iter {
|
||||
if let Some(focused) = Self::next_focusable_sibling(child, ancestor, None) {
|
||||
return Some(focused);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_global_button_root() {
|
||||
use crate::roc::{ButtonStyles, RocElem};
|
||||
|
||||
let child = RocElem::text("");
|
||||
let root = RocElem::button(ButtonStyles::default(), child);
|
||||
let mut focus = Focus::default();
|
||||
|
||||
// At first, nothing should be focused.
|
||||
assert_eq!(focus.focused_elem(), None);
|
||||
|
||||
focus.next_global(&root);
|
||||
|
||||
// Buttons should be focusable, so advancing focus should give the button focus.
|
||||
assert_eq!(focus.focused_elem(), Some(root.id()));
|
||||
|
||||
// Since the button is at the root, advancing again should maintain focus on it.
|
||||
focus.next_global(&root);
|
||||
assert_eq!(focus.focused_elem(), Some(root.id()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_global_text_root() {
|
||||
let root = RocElem::text("");
|
||||
let mut focus = Focus::default();
|
||||
|
||||
// At first, nothing should be focused.
|
||||
assert_eq!(focus.focused_elem(), None);
|
||||
|
||||
focus.next_global(&root);
|
||||
|
||||
// Text should not be focusable, so advancing focus should have no effect here.
|
||||
assert_eq!(focus.focused_elem(), None);
|
||||
|
||||
// Just to double-check, advancing a second time should not change this.
|
||||
focus.next_global(&root);
|
||||
assert_eq!(focus.focused_elem(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_global_row() {
|
||||
use crate::roc::{ButtonStyles, RocElem};
|
||||
|
||||
let child = RocElem::text("");
|
||||
let button = RocElem::button(ButtonStyles::default(), child);
|
||||
let button_id = button.id();
|
||||
let root = RocElem::row(&[button] as &[_]);
|
||||
let mut focus = Focus::default();
|
||||
|
||||
// At first, nothing should be focused.
|
||||
assert_eq!(focus.focused_elem(), None);
|
||||
|
||||
focus.next_global(&root);
|
||||
|
||||
// Buttons should be focusable, so advancing focus should give the first button in the row focus.
|
||||
assert_eq!(focus.focused_elem(), Some(button_id));
|
||||
|
||||
// Since the button is the only element in the row, advancing again should maintain focus on it.
|
||||
focus.next_global(&root);
|
||||
assert_eq!(focus.focused_elem(), Some(button_id));
|
||||
}
|
@ -63,7 +63,6 @@ pub enum Token {
|
||||
OpAnd = 0b_0110_1101,
|
||||
OpOr = 0b_0110_1110,
|
||||
OpDoubleSlash = 0b_0110_1111,
|
||||
OpDoublePercent = 0b_0111_0001,
|
||||
OpBackpassing = 0b_0111_1010,
|
||||
|
||||
TodoNextThing = 0b_1000_0000,
|
||||
@ -395,7 +394,6 @@ fn lex_operator(bytes: &[u8]) -> (Token, usize) {
|
||||
b"&" => Token::Ampersand,
|
||||
b"||" => Token::OpOr,
|
||||
b"//" => Token::OpDoubleSlash,
|
||||
b"%%" => Token::OpDoublePercent,
|
||||
b"->" => Token::Arrow,
|
||||
b"<-" => Token::OpBackpassing,
|
||||
op => {
|
||||
|
@ -368,7 +368,6 @@ mod test_peg_grammar {
|
||||
/ [T::OpSlash]
|
||||
/ [T::OpDoubleSlash]
|
||||
/ [T::OpPercent]
|
||||
/ [T::OpDoublePercent]
|
||||
rule mul_level_expr() =
|
||||
unary_expr() (mul_level_op() unary_expr())*
|
||||
|
||||
|
@ -28,5 +28,5 @@ memmap2 = "0.5.3"
|
||||
object = { version = "0.26.2", features = ["read", "write"] }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
bincode = "1.3.3"
|
||||
target-lexicon = "0.12.2"
|
||||
target-lexicon = "0.12.3"
|
||||
tempfile = "3.2.0"
|
||||
|
@ -86,12 +86,17 @@ enum NewtypeKind<'a> {
|
||||
///
|
||||
/// The returned list of newtype containers is ordered by increasing depth. As an example,
|
||||
/// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`.
|
||||
fn unroll_newtypes<'a>(
|
||||
///
|
||||
/// If we pass through aliases, the top-level alias that should be displayed to the user is passed
|
||||
/// back as an option.
|
||||
///
|
||||
/// Returns (new type containers, optional alias content, real content).
|
||||
fn unroll_newtypes_and_aliases<'a>(
|
||||
env: &Env<'a, 'a>,
|
||||
mut content: &'a Content,
|
||||
) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) {
|
||||
) -> (Vec<'a, NewtypeKind<'a>>, Option<&'a Content>, &'a Content) {
|
||||
let mut newtype_containers = Vec::with_capacity_in(1, env.arena);
|
||||
let mut force_alias_content = None;
|
||||
let mut alias_content = None;
|
||||
loop {
|
||||
match content {
|
||||
Content::Structure(FlatType::TagUnion(tags, _))
|
||||
@ -118,18 +123,19 @@ fn unroll_newtypes<'a>(
|
||||
}
|
||||
Content::Alias(_, _, real_var, _) => {
|
||||
// We need to pass through aliases too, because their underlying types may have
|
||||
// unrolled newtypes. In such cases return the list of unrolled newtypes, but keep
|
||||
// the content as the alias for readability. For example,
|
||||
// unrolled newtypes. For example,
|
||||
// T : { a : Str }
|
||||
// v : T
|
||||
// v = { a : "value" }
|
||||
// v
|
||||
// Here we need the newtype container to be `[RecordField(a)]`, but the content to
|
||||
// remain as the alias `T`.
|
||||
force_alias_content = Some(content);
|
||||
// Here we need the newtype container to be `[RecordField(a)]`.
|
||||
//
|
||||
// At the end of the day what we should show to the user is the alias content, not
|
||||
// what's inside, so keep that around too.
|
||||
alias_content = Some(content);
|
||||
content = env.subs.get_content_without_compacting(*real_var);
|
||||
}
|
||||
_ => return (newtype_containers, force_alias_content.unwrap_or(content)),
|
||||
_ => return (newtype_containers, alias_content, content),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -140,8 +146,8 @@ fn apply_newtypes<'a>(
|
||||
mut expr: Expr<'a>,
|
||||
) -> Expr<'a> {
|
||||
let arena = env.arena;
|
||||
// Reverse order of what we receieve from `unroll_newtypes` since we want the deepest
|
||||
// container applied first.
|
||||
// Reverse order of what we receieve from `unroll_newtypes_and_aliases` since
|
||||
// we want the deepest container applied first.
|
||||
for container in newtype_containers.into_iter().rev() {
|
||||
match container {
|
||||
NewtypeKind::Tag(tag_name) => {
|
||||
@ -162,13 +168,6 @@ fn apply_newtypes<'a>(
|
||||
expr
|
||||
}
|
||||
|
||||
fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content {
|
||||
while let Content::Alias(_, _, real, _) = content {
|
||||
content = env.subs.get_content_without_compacting(*real);
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content {
|
||||
while let Content::RecursionVar { structure, .. } = content {
|
||||
content = env.subs.get_content_without_compacting(*structure);
|
||||
@ -278,13 +277,18 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||
layout: &Layout<'a>,
|
||||
content: &'a Content,
|
||||
) -> Result<Expr<'a>, ToAstProblem> {
|
||||
let (newtype_containers, content) = unroll_newtypes(env, content);
|
||||
let content = unroll_aliases(env, content);
|
||||
let (newtype_containers, alias_content, raw_content) =
|
||||
unroll_newtypes_and_aliases(env, content);
|
||||
|
||||
macro_rules! num_helper {
|
||||
($ty:ty) => {
|
||||
app.call_function(main_fn_name, |_, num: $ty| {
|
||||
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
||||
num_to_ast(
|
||||
env,
|
||||
number_literal_to_ast(env.arena, num),
|
||||
// We determine the number from what the alias looks like.
|
||||
alias_content.unwrap_or(raw_content),
|
||||
)
|
||||
})
|
||||
};
|
||||
}
|
||||
@ -292,17 +296,17 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||
let result = match layout {
|
||||
Layout::Builtin(Builtin::Bool) => Ok(app
|
||||
.call_function(main_fn_name, |mem: &A::Memory, num: bool| {
|
||||
bool_to_ast(env, mem, num, content)
|
||||
bool_to_ast(env, mem, num, raw_content)
|
||||
})),
|
||||
Layout::Builtin(Builtin::Int(int_width)) => {
|
||||
use IntWidth::*;
|
||||
|
||||
let result = match (content, int_width) {
|
||||
let result = match (raw_content, int_width) {
|
||||
(Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)), U8) => num_helper!(u8),
|
||||
(_, U8) => {
|
||||
// This is not a number, it's a tag union or something else
|
||||
app.call_function(main_fn_name, |mem: &A::Memory, num: u8| {
|
||||
byte_to_ast(env, mem, num, content)
|
||||
byte_to_ast(env, mem, num, raw_content)
|
||||
})
|
||||
}
|
||||
// The rest are numbers... for now
|
||||
@ -344,14 +348,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||
Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function(
|
||||
main_fn_name,
|
||||
|mem: &A::Memory, (addr, len): (usize, usize)| {
|
||||
list_to_ast(env, mem, addr, len, elem_layout, content)
|
||||
list_to_ast(env, mem, addr, len, elem_layout, raw_content)
|
||||
},
|
||||
)),
|
||||
Layout::Builtin(other) => {
|
||||
todo!("add support for rendering builtin {:?} to the REPL", other)
|
||||
}
|
||||
Layout::Struct { field_layouts, .. } => {
|
||||
let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content {
|
||||
let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match raw_content {
|
||||
Content::Structure(FlatType::Record(fields, _)) => {
|
||||
Ok(struct_to_ast(env, mem, addr, *fields))
|
||||
}
|
||||
@ -413,7 +417,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||
main_fn_name,
|
||||
size as usize,
|
||||
|mem: &'a A::Memory, addr: usize| {
|
||||
addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content)
|
||||
addr_to_ast(
|
||||
env,
|
||||
mem,
|
||||
addr,
|
||||
layout,
|
||||
WhenRecursive::Unreachable,
|
||||
raw_content,
|
||||
)
|
||||
},
|
||||
))
|
||||
}
|
||||
@ -432,7 +443,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||
addr,
|
||||
layout,
|
||||
WhenRecursive::Loop(*layout),
|
||||
content,
|
||||
raw_content,
|
||||
)
|
||||
},
|
||||
))
|
||||
@ -447,7 +458,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||
main_fn_name,
|
||||
size as usize,
|
||||
|mem: &A::Memory, addr| {
|
||||
addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content)
|
||||
addr_to_ast(
|
||||
env,
|
||||
mem,
|
||||
addr,
|
||||
layout,
|
||||
WhenRecursive::Unreachable,
|
||||
raw_content,
|
||||
)
|
||||
},
|
||||
))
|
||||
}
|
||||
@ -493,9 +511,10 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||
}};
|
||||
}
|
||||
|
||||
let (newtype_containers, content) = unroll_newtypes(env, content);
|
||||
let content = unroll_aliases(env, content);
|
||||
let expr = match (content, layout) {
|
||||
let (newtype_containers, _alias_content, raw_content) =
|
||||
unroll_newtypes_and_aliases(env, content);
|
||||
|
||||
let expr = match (raw_content, layout) {
|
||||
(Content::Structure(FlatType::Func(_, _, _)), _)
|
||||
| (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION,
|
||||
(_, Layout::Builtin(Builtin::Bool)) => {
|
||||
@ -503,7 +522,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||
// num is always false at the moment.
|
||||
let num: bool = mem.deref_bool(addr);
|
||||
|
||||
bool_to_ast(env, mem, num, content)
|
||||
bool_to_ast(env, mem, num, raw_content)
|
||||
}
|
||||
(_, Layout::Builtin(Builtin::Int(int_width))) => {
|
||||
use IntWidth::*;
|
||||
@ -534,14 +553,14 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||
let elem_addr = mem.deref_usize(addr);
|
||||
let len = mem.deref_usize(addr + env.target_info.ptr_width() as usize);
|
||||
|
||||
list_to_ast(env, mem, elem_addr, len, elem_layout, content)
|
||||
list_to_ast(env, mem, elem_addr, len, elem_layout, raw_content)
|
||||
}
|
||||
(_, Layout::Builtin(Builtin::Str)) => {
|
||||
let string = mem.deref_str(addr);
|
||||
let arena_str = env.arena.alloc_str(string);
|
||||
Expr::Str(StrLiteral::PlainLine(arena_str))
|
||||
}
|
||||
(_, Layout::Struct{field_layouts, ..}) => match content {
|
||||
(_, Layout::Struct{field_layouts, ..}) => match raw_content {
|
||||
Content::Structure(FlatType::Record(fields, _)) => {
|
||||
struct_to_ast(env, mem, addr, *fields)
|
||||
}
|
||||
@ -566,7 +585,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||
}
|
||||
},
|
||||
(_, Layout::RecursivePointer) => {
|
||||
match (content, when_recursive) {
|
||||
match (raw_content, when_recursive) {
|
||||
(Content::RecursionVar {
|
||||
structure,
|
||||
opt_name: _,
|
||||
@ -580,7 +599,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||
(_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => {
|
||||
let union_layout = UnionLayout::NonRecursive(union_layouts);
|
||||
|
||||
let tags = match content {
|
||||
let tags = match raw_content {
|
||||
Content::Structure(FlatType::TagUnion(tags, _)) => tags,
|
||||
other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other),
|
||||
};
|
||||
@ -614,7 +633,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||
)
|
||||
}
|
||||
(_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => {
|
||||
let (rec_var, tags) = match content {
|
||||
let (rec_var, tags) = match raw_content {
|
||||
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
|
||||
_ => unreachable!("any other content would have a different layout"),
|
||||
};
|
||||
@ -644,7 +663,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||
)
|
||||
}
|
||||
(_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => {
|
||||
let (rec_var, tags) = match unroll_recursion_var(env, content) {
|
||||
let (rec_var, tags) = match unroll_recursion_var(env, raw_content) {
|
||||
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
|
||||
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
|
||||
};
|
||||
@ -672,7 +691,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||
)
|
||||
}
|
||||
(_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => {
|
||||
let (rec_var, tags) = match unroll_recursion_var(env, content) {
|
||||
let (rec_var, tags) = match unroll_recursion_var(env, raw_content) {
|
||||
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
|
||||
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
|
||||
};
|
||||
@ -706,7 +725,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||
}
|
||||
}
|
||||
(_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => {
|
||||
let (rec_var, tags) = match unroll_recursion_var(env, content) {
|
||||
let (rec_var, tags) = match unroll_recursion_var(env, raw_content) {
|
||||
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
|
||||
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
|
||||
};
|
||||
@ -803,7 +822,8 @@ fn list_to_ast<'a, M: ReplAppMemory>(
|
||||
for index in 0..len {
|
||||
let offset_bytes = index * elem_size;
|
||||
let elem_addr = addr + offset_bytes;
|
||||
let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content);
|
||||
let (newtype_containers, _alias_content, elem_content) =
|
||||
unroll_newtypes_and_aliases(env, elem_content);
|
||||
let expr = addr_to_ast(
|
||||
env,
|
||||
mem,
|
||||
|
@ -56,7 +56,7 @@ fn float_addition() {
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn num_rem() {
|
||||
expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*");
|
||||
expect_success("299 % 10", "9 : Int *");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
@ -1145,3 +1145,42 @@ fn issue_2818() {
|
||||
r"<function> : {} -> List Str",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_2810_recursive_layout_inside_nonrecursive() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Command : [ Command Tool ]
|
||||
|
||||
Job : [ Job Command ]
|
||||
|
||||
Tool : [ SystemTool, FromJob Job ]
|
||||
|
||||
a : Job
|
||||
a = Job (Command (FromJob (Job (Command SystemTool))))
|
||||
a
|
||||
"#
|
||||
),
|
||||
"Job (Command (FromJob (Job (Command SystemTool)))) : Job",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_nullable_unwrapped_passing_through_alias() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Deep : [ L DeepList ]
|
||||
|
||||
DeepList : [ Nil, Cons Deep ]
|
||||
|
||||
v : DeepList
|
||||
v = (Cons (L (Cons (L (Cons (L Nil))))))
|
||||
|
||||
v
|
||||
"#
|
||||
),
|
||||
"Cons (L (Cons (L (Cons (L Nil))))) : DeepList",
|
||||
)
|
||||
}
|
||||
|
@ -7128,26 +7128,23 @@ I need all branches in an `if` to have the same type!
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
Something is off with the body of the `f` definition:
|
||||
|
||||
1│ f : a, b, * -> *
|
||||
2│ f = \_, _, x2 ->
|
||||
|
||||
Something is off with the body of the `inner` definition:
|
||||
|
||||
3│ inner : * -> *
|
||||
4│ inner = \y -> y
|
||||
5│ inner x2
|
||||
^^^^^^^^
|
||||
|
||||
The type annotation on `f` says this `inner` call should have the type:
|
||||
|
||||
^
|
||||
|
||||
The type annotation on `inner` says this `y` value should have the type:
|
||||
|
||||
*
|
||||
|
||||
However, the type of this `inner` call is connected to another type in a
|
||||
|
||||
However, the type of this `y` value is connected to another type in a
|
||||
way that isn't reflected in this annotation.
|
||||
|
||||
|
||||
Tip: Any connection between types must use a named type variable, not
|
||||
a `*`! Maybe the annotation on `f` should have a named type variable in
|
||||
place of the `*`?
|
||||
a `*`! Maybe the annotation on `inner` should have a named type variable
|
||||
in place of the `*`?
|
||||
"#
|
||||
),
|
||||
)
|
||||
@ -10039,4 +10036,23 @@ I need all branches in an `if` to have the same type!
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn always_function() {
|
||||
// from https://github.com/rtfeldman/roc/commit/1372737f5e53ee5bb96d7e1b9593985e5537023a
|
||||
// There was a bug where this reported UnusedArgument("val")
|
||||
// since it was used only in the returned function only.
|
||||
//
|
||||
// we want this to not give any warnings/errors!
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
always = \val -> \_ -> val
|
||||
|
||||
always
|
||||
"#
|
||||
),
|
||||
"",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1297,7 +1297,6 @@ Here are various Roc expressions involving operators, and what they desugar to.
|
||||
| `a // b` | `Num.divTrunc a b` |
|
||||
| `a ^ b` | `Num.pow a b` |
|
||||
| `a % b` | `Num.rem a b` |
|
||||
| `a %% b` | `Num.mod a b` |
|
||||
| `a >> b` | `Num.shr a b` |
|
||||
| `a << b` | `Num.shl a b` |
|
||||
| `-a` | `Num.neg a` |
|
||||
|
252
roc_std/Cargo.lock
generated
Normal file
252
roc_std/Cargo.lock
generated
Normal file
@ -0,0 +1,252 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7906a9fababaeacb774f72410e497a1d18de916322e33797bb2cd29baa23c9e"
|
||||
dependencies = [
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "output_vt100"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"ctor",
|
||||
"diff",
|
||||
"output_vt100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quickcheck"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quickcheck_macros"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indoc",
|
||||
"libc",
|
||||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
@ -63,7 +63,7 @@ where
|
||||
let new_size = elements_offset + core::mem::size_of::<T>() * (self.len() + slice.len());
|
||||
|
||||
let new_ptr = if let Some((elements, storage)) = self.elements_and_storage() {
|
||||
// Decrement the lists refence count.
|
||||
// Decrement the list's refence count.
|
||||
let mut copy = storage.get();
|
||||
let is_unique = copy.decrease();
|
||||
|
||||
@ -275,6 +275,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<&[T]> for RocList<T>
|
||||
where
|
||||
T: ReferenceCount,
|
||||
{
|
||||
fn from(slice: &[T]) -> Self {
|
||||
Self::from_slice(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIterator for RocList<T>
|
||||
where
|
||||
T: ReferenceCount,
|
||||
|
Loading…
Reference in New Issue
Block a user