mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 22:09:09 +00:00
Merge remote-tracking branch 'origin/trunk' into list-replace
This commit is contained in:
commit
b802d681a3
90 changed files with 5667 additions and 313 deletions
|
@ -1,90 +1,5 @@
|
||||||
# Building the Roc compiler from source
|
# Building the Roc compiler from source
|
||||||
|
|
||||||
|
|
||||||
## Installing LLVM, Zig, valgrind, and Python
|
|
||||||
|
|
||||||
To build the compiler, you need these installed:
|
|
||||||
|
|
||||||
* [Zig](https://ziglang.org/), see below for version
|
|
||||||
* `libxkbcommon` - macOS seems to have it already; on Ubuntu or Debian you can get it with `apt-get install libxkbcommon-dev`
|
|
||||||
* On Debian/Ubuntu `sudo apt-get install pkg-config`
|
|
||||||
* LLVM, see below for version
|
|
||||||
|
|
||||||
To run the test suite (via `cargo test`), you additionally need to install:
|
|
||||||
|
|
||||||
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
|
|
||||||
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
|
|
||||||
|
|
||||||
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
|
|
||||||
|
|
||||||
### libcxb libraries
|
|
||||||
|
|
||||||
You may see an error like this during builds:
|
|
||||||
|
|
||||||
```
|
|
||||||
/usr/bin/ld: cannot find -lxcb-render
|
|
||||||
/usr/bin/ld: cannot find -lxcb-shape
|
|
||||||
/usr/bin/ld: cannot find -lxcb-xfixes
|
|
||||||
```
|
|
||||||
|
|
||||||
If so, you can fix it like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Zig
|
|
||||||
**version: 0.8.0**
|
|
||||||
|
|
||||||
For any OS, you can use [`zigup`](https://github.com/marler8997/zigup) to manage zig installations.
|
|
||||||
|
|
||||||
If you prefer a package manager, you can try the following:
|
|
||||||
- For MacOS, you can install with `brew install zig`
|
|
||||||
- For, Ubuntu, you can use Snap, you can install with `snap install zig --classic --beta`
|
|
||||||
- For other systems, checkout this [page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
|
|
||||||
|
|
||||||
If you want to install it manually, you can also download Zig directly [here](https://ziglang.org/download/). Just make sure you download the right version, the bleeding edge master build is the first download link on this page.
|
|
||||||
|
|
||||||
### LLVM
|
|
||||||
**version: 12.0.x**
|
|
||||||
|
|
||||||
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
|
|
||||||
`$(brew --prefix llvm@12)/bin` to your `PATH`. You can confirm this worked by
|
|
||||||
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
|
|
||||||
You may also need to manually specify a prefix env var like so:
|
|
||||||
```
|
|
||||||
export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12
|
|
||||||
```
|
|
||||||
|
|
||||||
For Ubuntu and Debian:
|
|
||||||
```
|
|
||||||
sudo apt -y install lsb-release software-properties-common gnupg
|
|
||||||
wget https://apt.llvm.org/llvm.sh
|
|
||||||
chmod +x llvm.sh
|
|
||||||
./llvm.sh 12
|
|
||||||
```
|
|
||||||
|
|
||||||
If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`.
|
|
||||||
By default, the script installs them as `clang-12` and `llvm-as-12`,
|
|
||||||
respectively. You can address this with symlinks like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo ln -s /usr/bin/clang-12 /usr/bin/clang
|
|
||||||
```
|
|
||||||
```
|
|
||||||
sudo ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as
|
|
||||||
````
|
|
||||||
|
|
||||||
There are also alternative installation options at http://releases.llvm.org/download.html
|
|
||||||
|
|
||||||
[Troubleshooting](#troubleshooting)
|
|
||||||
|
|
||||||
### Building
|
|
||||||
|
|
||||||
Use `cargo build` to build the whole project.
|
|
||||||
Use `cargo run help` to see all subcommands.
|
|
||||||
To use the `repl` subcommand, execute `cargo run repl`.
|
|
||||||
|
|
||||||
## Using Nix
|
## Using Nix
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
@ -168,6 +83,90 @@ Check the [nixGL repo](https://github.com/guibou/nixGL) for other graphics confi
|
||||||
Create an issue if you run into problems not listed here.
|
Create an issue if you run into problems not listed here.
|
||||||
That will help us improve this document for everyone who reads it in the future!
|
That will help us improve this document for everyone who reads it in the future!
|
||||||
|
|
||||||
|
## Manual Install
|
||||||
|
|
||||||
|
To build the compiler, you need these installed:
|
||||||
|
|
||||||
|
* [Zig](https://ziglang.org/), see below for version
|
||||||
|
* `libxkbcommon` - macOS seems to have it already; on Ubuntu or Debian you can get it with `apt-get install libxkbcommon-dev`
|
||||||
|
* On Debian/Ubuntu `sudo apt-get install pkg-config`
|
||||||
|
* LLVM, see below for version
|
||||||
|
|
||||||
|
To run the test suite (via `cargo test`), you additionally need to install:
|
||||||
|
|
||||||
|
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
|
||||||
|
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
|
||||||
|
|
||||||
|
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
|
||||||
|
|
||||||
|
### libcxb libraries
|
||||||
|
|
||||||
|
You may see an error like this during builds:
|
||||||
|
|
||||||
|
```
|
||||||
|
/usr/bin/ld: cannot find -lxcb-render
|
||||||
|
/usr/bin/ld: cannot find -lxcb-shape
|
||||||
|
/usr/bin/ld: cannot find -lxcb-xfixes
|
||||||
|
```
|
||||||
|
|
||||||
|
If so, you can fix it like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zig
|
||||||
|
**version: 0.8.0**
|
||||||
|
|
||||||
|
For any OS, you can use [`zigup`](https://github.com/marler8997/zigup) to manage zig installations.
|
||||||
|
|
||||||
|
If you prefer a package manager, you can try the following:
|
||||||
|
- For MacOS, you can install with `brew install zig`
|
||||||
|
- For, Ubuntu, you can use Snap, you can install with `snap install zig --classic --beta`
|
||||||
|
- For other systems, checkout this [page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
|
||||||
|
|
||||||
|
If you want to install it manually, you can also download Zig directly [here](https://ziglang.org/download/). Just make sure you download the right version, the bleeding edge master build is the first download link on this page.
|
||||||
|
|
||||||
|
### LLVM
|
||||||
|
**version: 12.0.x**
|
||||||
|
|
||||||
|
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
|
||||||
|
`$(brew --prefix llvm@12)/bin` to your `PATH`. You can confirm this worked by
|
||||||
|
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
|
||||||
|
You may also need to manually specify a prefix env var like so:
|
||||||
|
```
|
||||||
|
export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12
|
||||||
|
```
|
||||||
|
|
||||||
|
For Ubuntu and Debian:
|
||||||
|
```
|
||||||
|
sudo apt -y install lsb-release software-properties-common gnupg
|
||||||
|
wget https://apt.llvm.org/llvm.sh
|
||||||
|
chmod +x llvm.sh
|
||||||
|
./llvm.sh 12
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`.
|
||||||
|
By default, the script installs them as `clang-12` and `llvm-as-12`,
|
||||||
|
respectively. You can address this with symlinks like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo ln -s /usr/bin/clang-12 /usr/bin/clang
|
||||||
|
```
|
||||||
|
```
|
||||||
|
sudo ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as
|
||||||
|
````
|
||||||
|
|
||||||
|
There are also alternative installation options at http://releases.llvm.org/download.html
|
||||||
|
|
||||||
|
[Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
Use `cargo build` to build the whole project.
|
||||||
|
Use `cargo run help` to see all subcommands.
|
||||||
|
To use the `repl` subcommand, execute `cargo run repl`.
|
||||||
|
|
||||||
### LLVM installation on Linux
|
### LLVM installation on Linux
|
||||||
|
|
||||||
For a current list of all dependency versions and their names in apt, see the Earthfile.
|
For a current list of all dependency versions and their names in apt, see the Earthfile.
|
||||||
|
|
|
@ -515,3 +515,24 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
===========================================================
|
===========================================================
|
||||||
|
|
||||||
|
* iced - https://github.com/iced-rs/iced
|
||||||
|
|
||||||
|
Copyright 2019 Héctor Ramón, Iced contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1474,6 +1474,15 @@ pub fn constrain_pattern<'a>(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CharacterLiteral(_) => {
|
||||||
|
state.constraints.push(Constraint::Pattern(
|
||||||
|
region,
|
||||||
|
PatternCategory::Character,
|
||||||
|
num_unsigned32(env.pool),
|
||||||
|
expected,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
RecordDestructure {
|
RecordDestructure {
|
||||||
whole_var,
|
whole_var,
|
||||||
ext_var,
|
ext_var,
|
||||||
|
@ -1927,6 +1936,26 @@ fn _num_signed64(pool: &mut Pool) -> Type2 {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn num_unsigned32(pool: &mut Pool) -> Type2 {
|
||||||
|
let alias_content = Type2::TagUnion(
|
||||||
|
PoolVec::new(
|
||||||
|
std::iter::once((
|
||||||
|
TagName::Private(Symbol::NUM_UNSIGNED32),
|
||||||
|
PoolVec::empty(pool),
|
||||||
|
)),
|
||||||
|
pool,
|
||||||
|
),
|
||||||
|
pool.add(Type2::EmptyTagUnion),
|
||||||
|
);
|
||||||
|
|
||||||
|
Type2::Alias(
|
||||||
|
Symbol::NUM_UNSIGNED32,
|
||||||
|
PoolVec::empty(pool),
|
||||||
|
pool.add(alias_content),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
|
fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
|
||||||
let range_type = pool.get(range);
|
let range_type = pool.get(range);
|
||||||
|
|
|
@ -39,6 +39,7 @@ pub enum Pattern2 {
|
||||||
IntLiteral(IntVal), // 16B
|
IntLiteral(IntVal), // 16B
|
||||||
FloatLiteral(FloatVal), // 16B
|
FloatLiteral(FloatVal), // 16B
|
||||||
StrLiteral(PoolStr), // 8B
|
StrLiteral(PoolStr), // 8B
|
||||||
|
CharacterLiteral(char), // 4B
|
||||||
Underscore, // 0B
|
Underscore, // 0B
|
||||||
GlobalTag {
|
GlobalTag {
|
||||||
whole_var: Variable, // 4B
|
whole_var: Variable, // 4B
|
||||||
|
@ -249,6 +250,26 @@ pub fn to_pattern2<'a>(
|
||||||
ptype => unsupported_pattern(env, ptype, region),
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
SingleQuote(string) => match pattern_type {
|
||||||
|
WhenBranch => {
|
||||||
|
let mut it = string.chars().peekable();
|
||||||
|
if let Some(char) = it.next() {
|
||||||
|
if it.peek().is_none() {
|
||||||
|
Pattern2::CharacterLiteral(char)
|
||||||
|
} else {
|
||||||
|
// multiple chars is found
|
||||||
|
let problem = MalformedPatternProblem::MultipleCharsInSingleQuote;
|
||||||
|
malformed_pattern(env, problem, region)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no characters found
|
||||||
|
let problem = MalformedPatternProblem::EmptySingleQuote;
|
||||||
|
malformed_pattern(env, problem, region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
|
},
|
||||||
|
|
||||||
GlobalTag(name) => {
|
GlobalTag(name) => {
|
||||||
// Canonicalize the tag's name.
|
// Canonicalize the tag's name.
|
||||||
Pattern2::GlobalTag {
|
Pattern2::GlobalTag {
|
||||||
|
@ -506,6 +527,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
|
||||||
| IntLiteral(_)
|
| IntLiteral(_)
|
||||||
| FloatLiteral(_)
|
| FloatLiteral(_)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
|
| CharacterLiteral(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| Shadowed { .. }
|
| Shadowed { .. }
|
||||||
|
@ -566,6 +588,7 @@ pub fn symbols_and_variables_from_pattern(
|
||||||
| IntLiteral(_)
|
| IntLiteral(_)
|
||||||
| FloatLiteral(_)
|
| FloatLiteral(_)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
|
| CharacterLiteral(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| Shadowed { .. }
|
| Shadowed { .. }
|
||||||
|
|
|
@ -160,12 +160,17 @@ impl<'a> Env<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => Err(RuntimeError::ModuleNotImported {
|
||||||
panic!(
|
module_name,
|
||||||
"Module {} exists, but is not recorded in dep_idents",
|
imported_modules: self
|
||||||
module_name
|
.dep_idents
|
||||||
)
|
.keys()
|
||||||
}
|
.filter_map(|module_id| self.module_ids.get_name(*module_id))
|
||||||
|
.map(|module_name| module_name.as_ref().into())
|
||||||
|
.collect(),
|
||||||
|
region,
|
||||||
|
module_exists: true,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,6 +182,7 @@ impl<'a> Env<'a> {
|
||||||
.map(|string| string.as_ref().into())
|
.map(|string| string.as_ref().into())
|
||||||
.collect(),
|
.collect(),
|
||||||
region,
|
region,
|
||||||
|
module_exists: false,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,21 +15,24 @@ test = false
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"]
|
default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor", "llvm"]
|
||||||
|
|
||||||
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
|
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
|
||||||
i386-cli-run = ["target-x86"]
|
i386-cli-run = ["target-x86"]
|
||||||
|
|
||||||
|
# TODO: change to roc_repl_cli/llvm once roc_repl can run without llvm.
|
||||||
|
llvm = ["roc_build/llvm", "roc_repl_cli"]
|
||||||
|
|
||||||
editor = ["roc_editor"]
|
editor = ["roc_editor"]
|
||||||
|
|
||||||
run-wasm32 = ["wasmer", "wasmer-wasi"]
|
run-wasm32 = ["wasmer", "wasmer-wasi"]
|
||||||
|
|
||||||
# Compiling for a different platform than the host can cause linker errors.
|
# Compiling for a different platform than the host can cause linker errors.
|
||||||
target-arm = ["roc_build/target-arm"]
|
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
|
||||||
target-aarch64 = ["roc_build/target-aarch64"]
|
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
|
||||||
target-x86 = ["roc_build/target-x86"]
|
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
|
||||||
target-x86_64 = ["roc_build/target-x86_64"]
|
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
|
||||||
target-wasm32 = ["roc_build/target-wasm32"]
|
target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
|
||||||
|
|
||||||
target-all = [
|
target-all = [
|
||||||
"target-aarch64",
|
"target-aarch64",
|
||||||
|
@ -50,15 +53,15 @@ roc_module = { path = "../compiler/module" }
|
||||||
roc_builtins = { path = "../compiler/builtins" }
|
roc_builtins = { path = "../compiler/builtins" }
|
||||||
roc_mono = { path = "../compiler/mono" }
|
roc_mono = { path = "../compiler/mono" }
|
||||||
roc_load = { path = "../compiler/load" }
|
roc_load = { path = "../compiler/load" }
|
||||||
roc_build = { path = "../compiler/build", default-features = false }
|
roc_build = { path = "../compiler/build" }
|
||||||
roc_fmt = { path = "../compiler/fmt" }
|
roc_fmt = { path = "../compiler/fmt" }
|
||||||
roc_target = { path = "../compiler/roc_target" }
|
roc_target = { path = "../compiler/roc_target" }
|
||||||
roc_reporting = { path = "../reporting" }
|
roc_reporting = { path = "../reporting" }
|
||||||
roc_error_macros = { path = "../error_macros" }
|
roc_error_macros = { path = "../error_macros" }
|
||||||
roc_editor = { path = "../editor", optional = true }
|
roc_editor = { path = "../editor", optional = true }
|
||||||
roc_linker = { path = "../linker" }
|
roc_linker = { path = "../linker" }
|
||||||
roc_repl_cli = { path = "../repl_cli" }
|
roc_repl_cli = { path = "../repl_cli", optional = true }
|
||||||
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
||||||
const_format = "0.2.22"
|
const_format = "0.2.22"
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
mimalloc = { version = "0.1.26", default-features = false }
|
mimalloc = { version = "0.1.26", default-features = false }
|
||||||
|
|
|
@ -607,6 +607,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
||||||
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
|
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
|
||||||
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
|
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||||
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
|
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||||
|
Expr::SingleQuote(a) => Expr::Num(a),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -649,6 +650,7 @@ impl<'a> RemoveSpaces<'a> for Pattern<'a> {
|
||||||
}
|
}
|
||||||
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
|
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||||
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
|
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||||
|
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,10 +63,16 @@ fn main() -> io::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some((CMD_REPL, _)) => {
|
Some((CMD_REPL, _)) => {
|
||||||
roc_repl_cli::main()?;
|
#[cfg(feature = "llvm")]
|
||||||
|
{
|
||||||
|
roc_repl_cli::main()?;
|
||||||
|
|
||||||
// Exit 0 if the repl exited normally
|
// Exit 0 if the repl exited normally
|
||||||
Ok(0)
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "llvm"))]
|
||||||
|
todo!("enable roc repl without llvm");
|
||||||
}
|
}
|
||||||
Some((CMD_EDIT, matches)) => {
|
Some((CMD_EDIT, matches)) => {
|
||||||
match matches
|
match matches
|
||||||
|
|
|
@ -13,7 +13,8 @@ extern crate indoc;
|
||||||
mod cli_run {
|
mod cli_run {
|
||||||
use cli_utils::helpers::{
|
use cli_utils::helpers::{
|
||||||
example_file, examples_dir, extract_valgrind_errors, fixture_file, fixtures_dir,
|
example_file, examples_dir, extract_valgrind_errors, fixture_file, fixtures_dir,
|
||||||
known_bad_file, run_cmd, run_roc, run_with_valgrind, ValgrindError, ValgrindErrorXWhat,
|
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
|
||||||
|
ValgrindErrorXWhat,
|
||||||
};
|
};
|
||||||
use roc_test_utils::assert_multiline_str_eq;
|
use roc_test_utils::assert_multiline_str_eq;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
@ -80,6 +81,17 @@ mod cli_run {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_example(file: &Path, flags: &[&str]) -> Out {
|
||||||
|
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
|
||||||
|
if !compile_out.stderr.is_empty() {
|
||||||
|
panic!("roc build had stderr: {}", compile_out.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
|
||||||
|
|
||||||
|
compile_out
|
||||||
|
}
|
||||||
|
|
||||||
fn check_output_with_stdin(
|
fn check_output_with_stdin(
|
||||||
file: &Path,
|
file: &Path,
|
||||||
stdin: &[&str],
|
stdin: &[&str],
|
||||||
|
@ -96,12 +108,7 @@ mod cli_run {
|
||||||
all_flags.extend_from_slice(&["--valgrind"]);
|
all_flags.extend_from_slice(&["--valgrind"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat());
|
build_example(file, &all_flags[..]);
|
||||||
if !compile_out.stderr.is_empty() {
|
|
||||||
panic!("roc build had stderr: {}", compile_out.stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
|
|
||||||
|
|
||||||
let out = if use_valgrind && ALLOW_VALGRIND {
|
let out = if use_valgrind && ALLOW_VALGRIND {
|
||||||
let (valgrind_out, raw_xml) = if let Some(input_file) = input_file {
|
let (valgrind_out, raw_xml) = if let Some(input_file) = input_file {
|
||||||
|
@ -238,6 +245,17 @@ mod cli_run {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"hello-gui" => {
|
||||||
|
// Since this one requires opening a window, we do `roc build` on it but don't run it.
|
||||||
|
if cfg!(target_os = "linux") {
|
||||||
|
// The surgical linker can successfully link this on Linux, but the legacy linker errors!
|
||||||
|
build_example(&file_name, &["--optimize", "--roc-linker"]);
|
||||||
|
} else {
|
||||||
|
build_example(&file_name, &["--optimize"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,6 +372,14 @@ mod cli_run {
|
||||||
expected_ending:"55\n",
|
expected_ending:"55\n",
|
||||||
use_valgrind: true,
|
use_valgrind: true,
|
||||||
},
|
},
|
||||||
|
gui:"gui" => Example {
|
||||||
|
filename: "Hello.roc",
|
||||||
|
executable_filename: "hello-gui",
|
||||||
|
stdin: &[],
|
||||||
|
input_file: None,
|
||||||
|
expected_ending: "",
|
||||||
|
use_valgrind: false,
|
||||||
|
},
|
||||||
quicksort:"quicksort" => Example {
|
quicksort:"quicksort" => Example {
|
||||||
filename: "Quicksort.roc",
|
filename: "Quicksort.roc",
|
||||||
executable_filename: "quicksort",
|
executable_filename: "quicksort",
|
||||||
|
|
11
cli_utils/Cargo.lock
generated
11
cli_utils/Cargo.lock
generated
|
@ -897,6 +897,12 @@ version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
|
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dunce"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
@ -2504,6 +2510,7 @@ dependencies = [
|
||||||
name = "roc_builtins"
|
name = "roc_builtins"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"dunce",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
|
@ -2518,6 +2525,7 @@ dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"roc_builtins",
|
"roc_builtins",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
|
"roc_error_macros",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
"roc_problem",
|
"roc_problem",
|
||||||
|
@ -2587,6 +2595,7 @@ dependencies = [
|
||||||
"roc_builtins",
|
"roc_builtins",
|
||||||
"roc_can",
|
"roc_can",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
|
"roc_error_macros",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
|
@ -2687,6 +2696,7 @@ dependencies = [
|
||||||
"roc_mono",
|
"roc_mono",
|
||||||
"roc_problem",
|
"roc_problem",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
|
"roc_reporting",
|
||||||
"roc_solve",
|
"roc_solve",
|
||||||
"roc_target",
|
"roc_target",
|
||||||
"roc_types",
|
"roc_types",
|
||||||
|
@ -2760,6 +2770,7 @@ dependencies = [
|
||||||
"roc_can",
|
"roc_can",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_constrain",
|
"roc_constrain",
|
||||||
|
"roc_error_macros",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_mono",
|
"roc_mono",
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
|
|
|
@ -3,3 +3,4 @@ pub mod markup;
|
||||||
pub mod markup_error;
|
pub mod markup_error;
|
||||||
pub mod slow_pool;
|
pub mod slow_pool;
|
||||||
pub mod syntax_highlight;
|
pub mod syntax_highlight;
|
||||||
|
pub mod underline_style;
|
||||||
|
|
|
@ -55,8 +55,13 @@ pub enum Attribute {
|
||||||
HighlightStart { highlight_start: HighlightStart },
|
HighlightStart { highlight_start: HighlightStart },
|
||||||
HighlightEnd { highlight_end: HighlightEnd },
|
HighlightEnd { highlight_end: HighlightEnd },
|
||||||
|
|
||||||
UnderlineStart { underline_start: UnderlineStart },
|
Underline { underline_spec: UnderlineSpec },
|
||||||
UnderlineEnd { underline_end: UnderlineEnd },
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum UnderlineSpec {
|
||||||
|
Partial { start: usize, end: usize },
|
||||||
|
Full,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
20
code_markup/src/underline_style.rs
Normal file
20
code_markup/src/underline_style.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::colors::{from_hsb, RgbaTup};
|
||||||
|
|
||||||
|
#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub enum UnderlineStyle {
|
||||||
|
Error,
|
||||||
|
Warning,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_underline_color_map() -> HashMap<UnderlineStyle, RgbaTup> {
|
||||||
|
let mut underline_colors = HashMap::new();
|
||||||
|
|
||||||
|
underline_colors.insert(UnderlineStyle::Error, from_hsb(0, 50, 75));
|
||||||
|
underline_colors.insert(UnderlineStyle::Warning, from_hsb(60, 50, 75));
|
||||||
|
|
||||||
|
underline_colors
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ roc_gen_llvm = { path = "../gen_llvm", optional = true }
|
||||||
roc_gen_wasm = { path = "../gen_wasm", optional = true }
|
roc_gen_wasm = { path = "../gen_wasm", optional = true }
|
||||||
roc_gen_dev = { path = "../gen_dev", default-features = false }
|
roc_gen_dev = { path = "../gen_dev", default-features = false }
|
||||||
roc_reporting = { path = "../../reporting" }
|
roc_reporting = { path = "../../reporting" }
|
||||||
roc_std = { path = "../../roc_std" }
|
roc_std = { path = "../../roc_std", default-features = false }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
libloading = "0.7.1"
|
libloading = "0.7.1"
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
|
@ -35,7 +35,6 @@ target-lexicon = "0.12.2"
|
||||||
serde_json = "1.0.69"
|
serde_json = "1.0.69"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["llvm", "target-aarch64", "target-x86_64", "target-wasm32"]
|
|
||||||
target-arm = []
|
target-arm = []
|
||||||
target-aarch64 = ["roc_gen_dev/target-aarch64"]
|
target-aarch64 = ["roc_gen_dev/target-aarch64"]
|
||||||
target-x86 = []
|
target-x86 = []
|
||||||
|
|
|
@ -835,8 +835,6 @@ fn link_linux(
|
||||||
|
|
||||||
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
||||||
|
|
||||||
init_arch(target);
|
|
||||||
|
|
||||||
// NOTE: order of arguments to `ld` matters here!
|
// NOTE: order of arguments to `ld` matters here!
|
||||||
// The `-l` flags should go after the `.o` arguments
|
// The `-l` flags should go after the `.o` arguments
|
||||||
|
|
||||||
|
@ -1108,13 +1106,3 @@ fn validate_output(file_name: &str, cmd_name: &str, output: Output) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "llvm")]
|
|
||||||
fn init_arch(target: &Triple) {
|
|
||||||
crate::target::init_arch(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "llvm"))]
|
|
||||||
fn init_arch(_target: &Triple) {
|
|
||||||
panic!("Tried to initialize LLVM when crate was not built with `feature = \"llvm\"` enabled");
|
|
||||||
}
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ pub fn gen_from_mono_module(
|
||||||
_emit_debug_info: bool,
|
_emit_debug_info: bool,
|
||||||
) -> CodeGenTiming {
|
) -> CodeGenTiming {
|
||||||
match opt_level {
|
match opt_level {
|
||||||
OptLevel::Optimize => {
|
OptLevel::Optimize | OptLevel::Size => {
|
||||||
todo!("Return this error message in a better way: optimized builds not supported without llvm backend");
|
todo!("Return this error message in a better way: optimized builds not supported without llvm backend");
|
||||||
}
|
}
|
||||||
OptLevel::Normal | OptLevel::Development => {
|
OptLevel::Normal | OptLevel::Development => {
|
||||||
|
|
|
@ -130,7 +130,9 @@ fn make_apply_symbol(
|
||||||
// it was imported but it doesn't expose this ident.
|
// it was imported but it doesn't expose this ident.
|
||||||
env.problem(roc_problem::can::Problem::RuntimeError(problem));
|
env.problem(roc_problem::can::Problem::RuntimeError(problem));
|
||||||
|
|
||||||
Err(Type::Erroneous(Problem::UnrecognizedIdent((*ident).into())))
|
// A failed import should have already been reported through
|
||||||
|
// roc_can::env::Env::qualified_lookup's checks
|
||||||
|
Err(Type::Erroneous(Problem::SolvedTypeError))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -854,6 +854,7 @@ fn pattern_to_vars_by_symbol(
|
||||||
| IntLiteral(..)
|
| IntLiteral(..)
|
||||||
| FloatLiteral(..)
|
| FloatLiteral(..)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
|
| SingleQuote(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_)
|
| UnsupportedPattern(_)
|
||||||
|
|
|
@ -124,12 +124,17 @@ impl<'a> Env<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => Err(RuntimeError::ModuleNotImported {
|
||||||
panic!(
|
module_name,
|
||||||
"Module {} exists, but is not recorded in dep_idents",
|
imported_modules: self
|
||||||
module_name
|
.dep_idents
|
||||||
)
|
.keys()
|
||||||
}
|
.filter_map(|module_id| self.module_ids.get_name(*module_id))
|
||||||
|
.map(|module_name| module_name.as_ref().into())
|
||||||
|
.collect(),
|
||||||
|
region,
|
||||||
|
module_exists: true,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,6 +146,7 @@ impl<'a> Env<'a> {
|
||||||
.map(|string| string.as_ref().into())
|
.map(|string| string.as_ref().into())
|
||||||
.collect(),
|
.collect(),
|
||||||
region,
|
region,
|
||||||
|
module_exists: false,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ pub enum Expr {
|
||||||
Int(Variable, Variable, Box<str>, IntValue, IntBound),
|
Int(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||||
Float(Variable, Variable, Box<str>, f64, FloatBound),
|
Float(Variable, Variable, Box<str>, f64, FloatBound),
|
||||||
Str(Box<str>),
|
Str(Box<str>),
|
||||||
|
SingleQuote(char),
|
||||||
List {
|
List {
|
||||||
elem_var: Variable,
|
elem_var: Variable,
|
||||||
loc_elems: Vec<Loc<Expr>>,
|
loc_elems: Vec<Loc<Expr>>,
|
||||||
|
@ -323,6 +324,28 @@ pub fn canonicalize_expr<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
||||||
|
|
||||||
|
ast::Expr::SingleQuote(string) => {
|
||||||
|
let mut it = string.chars().peekable();
|
||||||
|
if let Some(char) = it.next() {
|
||||||
|
if it.peek().is_none() {
|
||||||
|
(Expr::SingleQuote(char), Output::default())
|
||||||
|
} else {
|
||||||
|
// multiple chars is found
|
||||||
|
let error = roc_problem::can::RuntimeError::MultipleCharsInSingleQuote(region);
|
||||||
|
let answer = Expr::RuntimeError(error);
|
||||||
|
|
||||||
|
(answer, Output::default())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no characters found
|
||||||
|
let error = roc_problem::can::RuntimeError::EmptySingleQuote(region);
|
||||||
|
let answer = Expr::RuntimeError(error);
|
||||||
|
|
||||||
|
(answer, Output::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ast::Expr::List(loc_elems) => {
|
ast::Expr::List(loc_elems) => {
|
||||||
if loc_elems.is_empty() {
|
if loc_elems.is_empty() {
|
||||||
(
|
(
|
||||||
|
@ -1267,6 +1290,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
| other @ Int(..)
|
| other @ Int(..)
|
||||||
| other @ Float(..)
|
| other @ Float(..)
|
||||||
| other @ Str { .. }
|
| other @ Str { .. }
|
||||||
|
| other @ SingleQuote(_)
|
||||||
| other @ RuntimeError(_)
|
| other @ RuntimeError(_)
|
||||||
| other @ EmptyRecord
|
| other @ EmptyRecord
|
||||||
| other @ Accessor { .. }
|
| other @ Accessor { .. }
|
||||||
|
|
|
@ -572,6 +572,7 @@ fn fix_values_captured_in_closure_pattern(
|
||||||
| IntLiteral(..)
|
| IntLiteral(..)
|
||||||
| FloatLiteral(..)
|
| FloatLiteral(..)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
|
| SingleQuote(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
| Shadowed(..)
|
| Shadowed(..)
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
|
@ -629,6 +630,7 @@ fn fix_values_captured_in_closure_expr(
|
||||||
| Int(..)
|
| Int(..)
|
||||||
| Float(..)
|
| Float(..)
|
||||||
| Str(_)
|
| Str(_)
|
||||||
|
| SingleQuote(_)
|
||||||
| Var(_)
|
| Var(_)
|
||||||
| EmptyRecord
|
| EmptyRecord
|
||||||
| RuntimeError(_)
|
| RuntimeError(_)
|
||||||
|
|
|
@ -126,6 +126,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
||||||
| Num(..)
|
| Num(..)
|
||||||
| NonBase10Int { .. }
|
| NonBase10Int { .. }
|
||||||
| Str(_)
|
| Str(_)
|
||||||
|
| SingleQuote(_)
|
||||||
| AccessorFunction(_)
|
| AccessorFunction(_)
|
||||||
| Var { .. }
|
| Var { .. }
|
||||||
| Underscore { .. }
|
| Underscore { .. }
|
||||||
|
|
|
@ -39,6 +39,7 @@ pub enum Pattern {
|
||||||
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
|
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||||
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
|
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
|
||||||
StrLiteral(Box<str>),
|
StrLiteral(Box<str>),
|
||||||
|
SingleQuote(char),
|
||||||
Underscore,
|
Underscore,
|
||||||
|
|
||||||
// Runtime Exceptions
|
// Runtime Exceptions
|
||||||
|
@ -108,6 +109,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||||
| IntLiteral(..)
|
| IntLiteral(..)
|
||||||
| FloatLiteral(..)
|
| FloatLiteral(..)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
|
| SingleQuote(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_)
|
| UnsupportedPattern(_)
|
||||||
|
@ -309,6 +311,23 @@ pub fn canonicalize_pattern<'a>(
|
||||||
ptype => unsupported_pattern(env, ptype, region),
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
SingleQuote(string) => {
|
||||||
|
let mut it = string.chars().peekable();
|
||||||
|
if let Some(char) = it.next() {
|
||||||
|
if it.peek().is_none() {
|
||||||
|
Pattern::SingleQuote(char)
|
||||||
|
} else {
|
||||||
|
// multiple chars is found
|
||||||
|
let problem = MalformedPatternProblem::MultipleCharsInSingleQuote;
|
||||||
|
malformed_pattern(env, problem, region)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no characters found
|
||||||
|
let problem = MalformedPatternProblem::EmptySingleQuote;
|
||||||
|
malformed_pattern(env, problem, region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
|
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
|
||||||
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
|
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
|
||||||
}
|
}
|
||||||
|
@ -560,6 +579,7 @@ fn add_bindings_from_patterns(
|
||||||
| IntLiteral(..)
|
| IntLiteral(..)
|
||||||
| FloatLiteral(..)
|
| FloatLiteral(..)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
|
| SingleQuote(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_)
|
| UnsupportedPattern(_)
|
||||||
|
|
|
@ -193,6 +193,21 @@ pub fn num_floatingpoint(range: Type) -> Type {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn num_u32() -> Type {
|
||||||
|
builtin_alias(Symbol::NUM_U32, vec![], Box::new(num_int(num_unsigned32())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn num_unsigned32() -> Type {
|
||||||
|
let alias_content = Type::TagUnion(
|
||||||
|
vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])],
|
||||||
|
Box::new(Type::EmptyTagUnion),
|
||||||
|
);
|
||||||
|
|
||||||
|
builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn num_binary64() -> Type {
|
pub fn num_binary64() -> Type {
|
||||||
let alias_content = Type::TagUnion(
|
let alias_content = Type::TagUnion(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::builtins::{
|
use crate::builtins::{
|
||||||
empty_list_type, float_literal, int_literal, list_type, num_literal, str_type,
|
empty_list_type, float_literal, int_literal, list_type, num_literal, num_u32, str_type,
|
||||||
};
|
};
|
||||||
use crate::pattern::{constrain_pattern, PatternState};
|
use crate::pattern::{constrain_pattern, PatternState};
|
||||||
use roc_can::annotation::IntroducedVariables;
|
use roc_can::annotation::IntroducedVariables;
|
||||||
|
@ -213,6 +213,7 @@ pub fn constrain_expr(
|
||||||
exists(vars, And(cons))
|
exists(vars, And(cons))
|
||||||
}
|
}
|
||||||
Str(_) => Eq(str_type(), expected, Category::Str, region),
|
Str(_) => Eq(str_type(), expected, Category::Str, region),
|
||||||
|
SingleQuote(_) => Eq(num_u32(), expected, Category::Character, region),
|
||||||
List {
|
List {
|
||||||
elem_var,
|
elem_var,
|
||||||
loc_elems,
|
loc_elems,
|
||||||
|
|
|
@ -60,6 +60,7 @@ fn headers_from_annotation_help(
|
||||||
| NumLiteral(..)
|
| NumLiteral(..)
|
||||||
| IntLiteral(..)
|
| IntLiteral(..)
|
||||||
| FloatLiteral(..)
|
| FloatLiteral(..)
|
||||||
|
| SingleQuote(_)
|
||||||
| StrLiteral(_) => true,
|
| StrLiteral(_) => true,
|
||||||
|
|
||||||
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
|
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
|
||||||
|
@ -252,6 +253,15 @@ pub fn constrain_pattern(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SingleQuote(_) => {
|
||||||
|
state.constraints.push(Constraint::Pattern(
|
||||||
|
region,
|
||||||
|
PatternCategory::Character,
|
||||||
|
builtins::num_u32(),
|
||||||
|
expected,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
RecordDestructure {
|
RecordDestructure {
|
||||||
whole_var,
|
whole_var,
|
||||||
ext_var,
|
ext_var,
|
||||||
|
|
|
@ -49,7 +49,6 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
||||||
);
|
);
|
||||||
buf.newline();
|
buf.newline();
|
||||||
buf.indent(braces_indent);
|
buf.indent(braces_indent);
|
||||||
buf.push(end);
|
|
||||||
} else {
|
} else {
|
||||||
// is_multiline == false
|
// is_multiline == false
|
||||||
// there is no comment to add
|
// there is no comment to add
|
||||||
|
@ -67,7 +66,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
||||||
if !items.is_empty() {
|
if !items.is_empty() {
|
||||||
buf.spaces(1);
|
buf.spaces(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push(end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf.push(end);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ impl<'a> Formattable for Expr<'a> {
|
||||||
Float(..)
|
Float(..)
|
||||||
| Num(..)
|
| Num(..)
|
||||||
| NonBase10Int { .. }
|
| NonBase10Int { .. }
|
||||||
|
| SingleQuote(_)
|
||||||
| Access(_, _)
|
| Access(_, _)
|
||||||
| AccessorFunction(_)
|
| AccessorFunction(_)
|
||||||
| Var { .. }
|
| Var { .. }
|
||||||
|
@ -209,6 +210,11 @@ impl<'a> Formattable for Expr<'a> {
|
||||||
buf.indent(indent);
|
buf.indent(indent);
|
||||||
buf.push_str(string)
|
buf.push_str(string)
|
||||||
}
|
}
|
||||||
|
SingleQuote(string) => {
|
||||||
|
buf.push('\'');
|
||||||
|
buf.push_str(string);
|
||||||
|
buf.push('\'');
|
||||||
|
}
|
||||||
&NonBase10Int {
|
&NonBase10Int {
|
||||||
base,
|
base,
|
||||||
string,
|
string,
|
||||||
|
|
|
@ -36,6 +36,7 @@ impl<'a> Formattable for Pattern<'a> {
|
||||||
| Pattern::NonBase10Literal { .. }
|
| Pattern::NonBase10Literal { .. }
|
||||||
| Pattern::FloatLiteral(..)
|
| Pattern::FloatLiteral(..)
|
||||||
| Pattern::StrLiteral(_)
|
| Pattern::StrLiteral(_)
|
||||||
|
| Pattern::SingleQuote(_)
|
||||||
| Pattern::Underscore(_)
|
| Pattern::Underscore(_)
|
||||||
| Pattern::Malformed(_)
|
| Pattern::Malformed(_)
|
||||||
| Pattern::MalformedIdent(_, _)
|
| Pattern::MalformedIdent(_, _)
|
||||||
|
@ -147,6 +148,11 @@ impl<'a> Formattable for Pattern<'a> {
|
||||||
StrLiteral(literal) => {
|
StrLiteral(literal) => {
|
||||||
todo!("Format string literal: {:?}", literal);
|
todo!("Format string literal: {:?}", literal);
|
||||||
}
|
}
|
||||||
|
SingleQuote(string) => {
|
||||||
|
buf.push('\'');
|
||||||
|
buf.push_str(string);
|
||||||
|
buf.push('\'');
|
||||||
|
}
|
||||||
Underscore(name) => {
|
Underscore(name) => {
|
||||||
buf.indent(indent);
|
buf.indent(indent);
|
||||||
buf.push('_');
|
buf.push('_');
|
||||||
|
|
|
@ -30,7 +30,7 @@ packed_struct = "0.10.0"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
roc_can = { path = "../can" }
|
roc_can = { path = "../can" }
|
||||||
roc_parse = { path = "../parse" }
|
roc_parse = { path = "../parse" }
|
||||||
roc_std = { path = "../../roc_std" }
|
roc_std = { path = "../../roc_std", default-features = false }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -13,7 +13,7 @@ roc_builtins = { path = "../builtins" }
|
||||||
roc_error_macros = { path = "../../error_macros" }
|
roc_error_macros = { path = "../../error_macros" }
|
||||||
roc_mono = { path = "../mono" }
|
roc_mono = { path = "../mono" }
|
||||||
roc_target = { path = "../roc_target" }
|
roc_target = { path = "../roc_target" }
|
||||||
roc_std = { path = "../../roc_std" }
|
roc_std = { path = "../../roc_std", default-features = false }
|
||||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
inkwell = { path = "../../vendor/inkwell" }
|
inkwell = { path = "../../vendor/inkwell" }
|
||||||
|
|
|
@ -3619,7 +3619,12 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
|
||||||
_ => (¶ms[..], ¶m_types[..]),
|
_ => (¶ms[..], ¶m_types[..]),
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert_eq!(params.len(), param_types.len());
|
debug_assert!(
|
||||||
|
params.len() == param_types.len(),
|
||||||
|
"when exposing a function to the host, params.len() was {}, but param_types.len() was {}",
|
||||||
|
params.len(),
|
||||||
|
param_types.len()
|
||||||
|
);
|
||||||
|
|
||||||
let it = params.iter().zip(param_types).map(|(arg, fastcc_type)| {
|
let it = params.iter().zip(param_types).map(|(arg, fastcc_type)| {
|
||||||
let arg_type = arg.get_type();
|
let arg_type = arg.get_type();
|
||||||
|
|
|
@ -12,5 +12,5 @@ roc_collections = { path = "../collections" }
|
||||||
roc_module = { path = "../module" }
|
roc_module = { path = "../module" }
|
||||||
roc_mono = { path = "../mono" }
|
roc_mono = { path = "../mono" }
|
||||||
roc_target = { path = "../roc_target" }
|
roc_target = { path = "../roc_target" }
|
||||||
roc_std = { path = "../../roc_std" }
|
roc_std = { path = "../../roc_std", default-features = false }
|
||||||
roc_error_macros = { path = "../../error_macros" }
|
roc_error_macros = { path = "../../error_macros" }
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
use core::convert::From;
|
use core::convert::From;
|
||||||
use core::{fmt, mem, ptr, slice};
|
use core::{fmt, mem, ptr, slice};
|
||||||
use std::alloc::{GlobalAlloc, Layout, System};
|
use std::alloc::{alloc, dealloc, Layout};
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
/// A string which can store identifiers using the small string optimization.
|
/// A string which can store identifiers using the small string optimization.
|
||||||
/// It relies on the invariant that it cannot store null characters to store
|
/// It relies on the invariant that it cannot store null characters to store
|
||||||
|
@ -66,19 +67,7 @@ impl IdentStr {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, index: usize) -> Option<&u8> {
|
pub fn get(&self, index: usize) -> Option<&u8> {
|
||||||
if index < self.len() {
|
self.as_bytes().get(index)
|
||||||
Some(unsafe {
|
|
||||||
let raw = if self.is_small_str() {
|
|
||||||
self.get_small_str_ptr().add(index)
|
|
||||||
} else {
|
|
||||||
self.elements.add(index)
|
|
||||||
};
|
|
||||||
|
|
||||||
&*raw
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bytes(&self) -> *const u8 {
|
pub fn get_bytes(&self) -> *const u8 {
|
||||||
|
@ -93,59 +82,38 @@ impl IdentStr {
|
||||||
(self as *const IdentStr).cast()
|
(self as *const IdentStr).cast()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_slice(slice: &[u8]) -> Self {
|
fn from_str(str: &str) -> Self {
|
||||||
|
let slice = str.as_bytes();
|
||||||
let len = slice.len();
|
let len = slice.len();
|
||||||
|
|
||||||
match len.cmp(&mem::size_of::<Self>()) {
|
match len.cmp(&mem::size_of::<Self>()) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
// This fits in a small string, but needs its length recorded
|
let mut bytes = [0; mem::size_of::<Self>()];
|
||||||
let mut answer_bytes: [u8; mem::size_of::<Self>()] = unsafe {
|
|
||||||
mem::transmute::<Self, [u8; mem::size_of::<Self>()]>(Self::default())
|
|
||||||
};
|
|
||||||
|
|
||||||
// Copy the bytes from the slice into the answer
|
// Copy the bytes from the slice into bytes.
|
||||||
let dest_slice =
|
bytes[..len].copy_from_slice(slice);
|
||||||
unsafe { slice::from_raw_parts_mut(&mut answer_bytes as *mut u8, len) };
|
|
||||||
|
|
||||||
dest_slice.copy_from_slice(slice);
|
|
||||||
|
|
||||||
let mut answer: Self =
|
|
||||||
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(answer_bytes) };
|
|
||||||
|
|
||||||
// Write length and small string bit to last byte of length.
|
// Write length and small string bit to last byte of length.
|
||||||
{
|
bytes[mem::size_of::<usize>() * 2 - 1] = u8::MAX - len as u8;
|
||||||
let mut bytes = answer.length.to_ne_bytes();
|
|
||||||
|
|
||||||
bytes[mem::size_of::<usize>() - 1] = u8::MAX - len as u8;
|
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(bytes) }
|
||||||
|
|
||||||
answer.length = usize::from_ne_bytes(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
answer
|
|
||||||
}
|
}
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
// This fits in a small string, and is exactly long enough to
|
// This fits in a small string, and is exactly long enough to
|
||||||
// take up the entire available struct
|
// take up the entire available struct
|
||||||
let mut answer_bytes: [u8; mem::size_of::<Self>()] = unsafe {
|
let mut bytes = [0; mem::size_of::<Self>()];
|
||||||
mem::transmute::<Self, [u8; mem::size_of::<Self>()]>(Self::default())
|
|
||||||
};
|
|
||||||
|
|
||||||
// Copy the bytes from the slice into the answer
|
// Copy the bytes from the slice into the answer
|
||||||
let dest_slice = unsafe {
|
bytes.copy_from_slice(slice);
|
||||||
slice::from_raw_parts_mut(&mut answer_bytes as *mut u8, mem::size_of::<Self>())
|
|
||||||
};
|
|
||||||
|
|
||||||
dest_slice.copy_from_slice(slice);
|
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(bytes) }
|
||||||
|
|
||||||
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(answer_bytes) }
|
|
||||||
}
|
}
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
// This needs a big string
|
// This needs a big string
|
||||||
|
let align = mem::align_of::<u8>();
|
||||||
let elements = unsafe {
|
let elements = unsafe {
|
||||||
let align = mem::align_of::<u8>();
|
|
||||||
let layout = Layout::from_size_align_unchecked(len, align);
|
let layout = Layout::from_size_align_unchecked(len, align);
|
||||||
|
alloc(layout)
|
||||||
System.alloc(layout)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Turn the new elements into a slice, and copy the existing
|
// Turn the new elements into a slice, and copy the existing
|
||||||
|
@ -167,7 +135,9 @@ impl IdentStr {
|
||||||
pub fn as_slice(&self) -> &[u8] {
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
use core::slice::from_raw_parts;
|
use core::slice::from_raw_parts;
|
||||||
|
|
||||||
if self.is_small_str() {
|
if self.is_empty() {
|
||||||
|
&[]
|
||||||
|
} else if self.is_small_str() {
|
||||||
unsafe { from_raw_parts(self.get_small_str_ptr(), self.len()) }
|
unsafe { from_raw_parts(self.get_small_str_ptr(), self.len()) }
|
||||||
} else {
|
} else {
|
||||||
unsafe { from_raw_parts(self.elements, self.length) }
|
unsafe { from_raw_parts(self.elements, self.length) }
|
||||||
|
@ -186,15 +156,12 @@ impl IdentStr {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// This assumes the given buffer has enough space, so make sure you only
|
/// This assumes the given buffer has enough space, so make sure you only
|
||||||
/// pass in a pointer to an allocation that's at least as long as this Str!
|
/// pass in a pointer to an allocation that's at least as long as this Str!
|
||||||
pub unsafe fn write_c_str(&self, buf: *mut char) {
|
pub unsafe fn write_c_str(&self, buf: *mut c_char) {
|
||||||
if self.is_small_str() {
|
let bytes = self.as_bytes();
|
||||||
ptr::copy_nonoverlapping(self.get_small_str_ptr(), buf as *mut u8, self.len());
|
ptr::copy_nonoverlapping(bytes.as_ptr().cast(), buf, bytes.len());
|
||||||
} else {
|
|
||||||
ptr::copy_nonoverlapping(self.elements, buf as *mut u8, self.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
// null-terminate
|
// null-terminate
|
||||||
*(buf.add(self.len())) = '\0';
|
*buf.add(self.len()) = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,13 +184,13 @@ impl std::ops::Deref for IdentStr {
|
||||||
|
|
||||||
impl From<&str> for IdentStr {
|
impl From<&str> for IdentStr {
|
||||||
fn from(str: &str) -> Self {
|
fn from(str: &str) -> Self {
|
||||||
Self::from_slice(str.as_bytes())
|
Self::from_str(str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for IdentStr {
|
impl From<String> for IdentStr {
|
||||||
fn from(str: String) -> Self {
|
fn from(str: String) -> Self {
|
||||||
Self::from_slice(str.as_bytes())
|
Self::from_str(&str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,44 +246,17 @@ impl std::hash::Hash for IdentStr {
|
||||||
|
|
||||||
impl Clone for IdentStr {
|
impl Clone for IdentStr {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
if self.is_small_str() || self.is_empty() {
|
Self::from_str(self.as_str())
|
||||||
Self {
|
|
||||||
elements: self.elements,
|
|
||||||
length: self.length,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let capacity_size = core::mem::size_of::<usize>();
|
|
||||||
let copy_length = self.length + capacity_size;
|
|
||||||
let elements = unsafe {
|
|
||||||
let align = mem::align_of::<u8>();
|
|
||||||
let layout = Layout::from_size_align_unchecked(copy_length, align);
|
|
||||||
let raw_ptr = System.alloc(layout);
|
|
||||||
|
|
||||||
let dest_slice = slice::from_raw_parts_mut(raw_ptr, copy_length);
|
|
||||||
let src_ptr = self.elements as *mut u8;
|
|
||||||
let src_slice = slice::from_raw_parts(src_ptr, copy_length);
|
|
||||||
|
|
||||||
dest_slice.copy_from_slice(src_slice);
|
|
||||||
|
|
||||||
raw_ptr as *mut u8
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
elements,
|
|
||||||
length: self.length,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for IdentStr {
|
impl Drop for IdentStr {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if !self.is_empty() && !self.is_small_str() {
|
if !self.is_empty() && !self.is_small_str() {
|
||||||
|
let align = mem::align_of::<u8>();
|
||||||
unsafe {
|
unsafe {
|
||||||
let align = mem::align_of::<u8>();
|
|
||||||
let layout = Layout::from_size_align_unchecked(self.length, align);
|
let layout = Layout::from_size_align_unchecked(self.length, align);
|
||||||
|
dealloc(self.elements as *mut _, layout);
|
||||||
System.dealloc(self.elements as *mut _, layout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ roc_reporting = { path = "../../reporting" }
|
||||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||||
ven_pretty = { path = "../../vendor/pretty" }
|
ven_pretty = { path = "../../vendor/pretty" }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
parking_lot = { version = "0.11.2" }
|
parking_lot = "0.11.2"
|
||||||
crossbeam = "0.8.1"
|
crossbeam = "0.8.1"
|
||||||
num_cpus = "1.13.0"
|
num_cpus = "1.13.0"
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ roc_types = { path = "../types" }
|
||||||
roc_can = { path = "../can" }
|
roc_can = { path = "../can" }
|
||||||
roc_unify = { path = "../unify" }
|
roc_unify = { path = "../unify" }
|
||||||
roc_solve = { path = "../solve" }
|
roc_solve = { path = "../solve" }
|
||||||
roc_std = { path = "../../roc_std" }
|
roc_std = { path = "../../roc_std", default-features = false }
|
||||||
roc_problem = { path = "../problem" }
|
roc_problem = { path = "../problem" }
|
||||||
roc_builtins = { path = "../builtins" }
|
roc_builtins = { path = "../builtins" }
|
||||||
roc_target = { path = "../roc_target" }
|
roc_target = { path = "../roc_target" }
|
||||||
|
|
|
@ -2040,8 +2040,11 @@ fn pattern_to_when<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
UnwrappedOpaque { .. } => todo_opaques!(),
|
UnwrappedOpaque { .. } => todo_opaques!(),
|
||||||
|
IntLiteral(..)
|
||||||
IntLiteral(..) | NumLiteral(..) | FloatLiteral(..) | StrLiteral(_) => {
|
| NumLiteral(..)
|
||||||
|
| FloatLiteral(..)
|
||||||
|
| StrLiteral(..)
|
||||||
|
| roc_can::pattern::Pattern::SingleQuote(..) => {
|
||||||
// These patters are refutable, and thus should never occur outside a `when` expression
|
// These patters are refutable, and thus should never occur outside a `when` expression
|
||||||
// They should have been replaced with `UnsupportedPattern` during canonicalization
|
// They should have been replaced with `UnsupportedPattern` during canonicalization
|
||||||
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
|
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
|
||||||
|
@ -3147,6 +3150,13 @@ pub fn with_hole<'a>(
|
||||||
hole,
|
hole,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
SingleQuote(character) => Stmt::Let(
|
||||||
|
assigned,
|
||||||
|
Expr::Literal(Literal::Int(character as _)),
|
||||||
|
Layout::int_width(IntWidth::I32),
|
||||||
|
hole,
|
||||||
|
),
|
||||||
|
|
||||||
Num(var, num_str, num, _bound) => {
|
Num(var, num_str, num, _bound) => {
|
||||||
// first figure out what kind of number this is
|
// first figure out what kind of number this is
|
||||||
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
|
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
|
||||||
|
@ -7226,7 +7236,8 @@ fn call_by_name_help<'a>(
|
||||||
} else {
|
} else {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
!field_symbols.is_empty(),
|
!field_symbols.is_empty(),
|
||||||
"should be in the list of imported_module_thunks"
|
"{} should be in the list of imported_module_thunks",
|
||||||
|
proc_name
|
||||||
);
|
);
|
||||||
|
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
|
@ -7745,6 +7756,7 @@ fn from_can_pattern_help<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
|
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
|
||||||
|
SingleQuote(c) => Ok(Pattern::IntLiteral(*c as _, IntWidth::I32)),
|
||||||
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
|
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
|
||||||
original_region: *region,
|
original_region: *region,
|
||||||
shadow: ident.clone(),
|
shadow: ident.clone(),
|
||||||
|
|
|
@ -16,7 +16,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
encode_unicode = "0.3.6"
|
encode_unicode = "0.3.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3.5", features = ["html_reports"] }
|
||||||
pretty_assertions = "1.0.0"
|
pretty_assertions = "1.0.0"
|
||||||
indoc = "1.0.3"
|
indoc = "1.0.3"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
|
|
|
@ -152,6 +152,8 @@ pub enum Expr<'a> {
|
||||||
Access(&'a Expr<'a>, &'a str),
|
Access(&'a Expr<'a>, &'a str),
|
||||||
/// e.g. `.foo`
|
/// e.g. `.foo`
|
||||||
AccessorFunction(&'a str),
|
AccessorFunction(&'a str),
|
||||||
|
/// eg 'b'
|
||||||
|
SingleQuote(&'a str),
|
||||||
|
|
||||||
// Collection Literals
|
// Collection Literals
|
||||||
List(Collection<'a, &'a Loc<Expr<'a>>>),
|
List(Collection<'a, &'a Loc<Expr<'a>>>),
|
||||||
|
@ -462,6 +464,7 @@ pub enum Pattern<'a> {
|
||||||
FloatLiteral(&'a str),
|
FloatLiteral(&'a str),
|
||||||
StrLiteral(StrLiteral<'a>),
|
StrLiteral(StrLiteral<'a>),
|
||||||
Underscore(&'a str),
|
Underscore(&'a str),
|
||||||
|
SingleQuote(&'a str),
|
||||||
|
|
||||||
// Space
|
// Space
|
||||||
SpaceBefore(&'a Pattern<'a>, &'a [CommentOrNewline<'a>]),
|
SpaceBefore(&'a Pattern<'a>, &'a [CommentOrNewline<'a>]),
|
||||||
|
|
|
@ -195,6 +195,7 @@ fn parse_loc_term_or_underscore<'a>(
|
||||||
one_of!(
|
one_of!(
|
||||||
loc_expr_in_parens_etc_help(min_indent),
|
loc_expr_in_parens_etc_help(min_indent),
|
||||||
loc!(specialize(EExpr::Str, string_literal_help())),
|
loc!(specialize(EExpr::Str, string_literal_help())),
|
||||||
|
loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())),
|
||||||
loc!(specialize(EExpr::Number, positive_number_literal_help())),
|
loc!(specialize(EExpr::Number, positive_number_literal_help())),
|
||||||
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
|
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
|
||||||
loc!(underscore_expression()),
|
loc!(underscore_expression()),
|
||||||
|
@ -217,6 +218,7 @@ fn parse_loc_term<'a>(
|
||||||
one_of!(
|
one_of!(
|
||||||
loc_expr_in_parens_etc_help(min_indent),
|
loc_expr_in_parens_etc_help(min_indent),
|
||||||
loc!(specialize(EExpr::Str, string_literal_help())),
|
loc!(specialize(EExpr::Str, string_literal_help())),
|
||||||
|
loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())),
|
||||||
loc!(specialize(EExpr::Number, positive_number_literal_help())),
|
loc!(specialize(EExpr::Number, positive_number_literal_help())),
|
||||||
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
|
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
|
||||||
loc!(record_literal_help(min_indent)),
|
loc!(record_literal_help(min_indent)),
|
||||||
|
@ -1534,6 +1536,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||||
| Expr::UnaryOp(_, _) => Err(()),
|
| Expr::UnaryOp(_, _) => Err(()),
|
||||||
|
|
||||||
Expr::Str(string) => Ok(Pattern::StrLiteral(*string)),
|
Expr::Str(string) => Ok(Pattern::StrLiteral(*string)),
|
||||||
|
Expr::SingleQuote(string) => Ok(Pattern::SingleQuote(*string)),
|
||||||
Expr::MalformedIdent(string, _problem) => Ok(Pattern::Malformed(string)),
|
Expr::MalformedIdent(string, _problem) => Ok(Pattern::Malformed(string)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2352,6 +2355,13 @@ fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
|
||||||
map!(crate::string_literal::parse(), Expr::Str)
|
map!(crate::string_literal::parse(), Expr::Str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn single_quote_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
|
||||||
|
map!(
|
||||||
|
crate::string_literal::parse_single_quote(),
|
||||||
|
Expr::SingleQuote
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
|
fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
|
||||||
map!(
|
map!(
|
||||||
crate::number_literal::positive_number_literal(),
|
crate::number_literal::positive_number_literal(),
|
||||||
|
|
|
@ -355,6 +355,7 @@ pub enum EExpr<'a> {
|
||||||
InParens(EInParens<'a>, Position),
|
InParens(EInParens<'a>, Position),
|
||||||
Record(ERecord<'a>, Position),
|
Record(ERecord<'a>, Position),
|
||||||
Str(EString<'a>, Position),
|
Str(EString<'a>, Position),
|
||||||
|
SingleQuote(EString<'a>, Position),
|
||||||
Number(ENumber, Position),
|
Number(ENumber, Position),
|
||||||
List(EList<'a>, Position),
|
List(EList<'a>, Position),
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ pub fn loc_pattern_help<'a>(min_indent: u32) -> impl Parser<'a, Loc<Pattern<'a>>
|
||||||
)),
|
)),
|
||||||
loc!(number_pattern_help()),
|
loc!(number_pattern_help()),
|
||||||
loc!(string_pattern_help()),
|
loc!(string_pattern_help()),
|
||||||
|
loc!(single_quote_pattern_help()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +109,7 @@ fn loc_parse_tag_pattern_arg<'a>(
|
||||||
crate::pattern::record_pattern_help(min_indent)
|
crate::pattern::record_pattern_help(min_indent)
|
||||||
)),
|
)),
|
||||||
loc!(string_pattern_help()),
|
loc!(string_pattern_help()),
|
||||||
|
loc!(single_quote_pattern_help()),
|
||||||
loc!(number_pattern_help())
|
loc!(number_pattern_help())
|
||||||
)
|
)
|
||||||
.parse(arena, state)
|
.parse(arena, state)
|
||||||
|
@ -159,6 +161,16 @@ fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn single_quote_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
|
||||||
|
specialize(
|
||||||
|
|_, pos| EPattern::Start(pos),
|
||||||
|
map!(
|
||||||
|
crate::string_literal::parse_single_quote(),
|
||||||
|
Pattern::SingleQuote
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn loc_ident_pattern_help<'a>(
|
fn loc_ident_pattern_help<'a>(
|
||||||
min_indent: u32,
|
min_indent: u32,
|
||||||
can_have_arguments: bool,
|
can_have_arguments: bool,
|
||||||
|
|
|
@ -35,6 +35,100 @@ macro_rules! advance_state {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_single_quote<'a>() -> impl Parser<'a, &'a str, EString<'a>> {
|
||||||
|
move |arena: &'a Bump, mut state: State<'a>| {
|
||||||
|
if state.bytes().starts_with(b"\'") {
|
||||||
|
// we will be parsing a single-quote-string
|
||||||
|
} else {
|
||||||
|
return Err((NoProgress, EString::Open(state.pos()), state));
|
||||||
|
}
|
||||||
|
|
||||||
|
// early return did not hit, just advance one byte
|
||||||
|
state = advance_state!(state, 1)?;
|
||||||
|
|
||||||
|
// Handle back slaches in byte literal
|
||||||
|
// - starts with a backslash and used as an escape character. ex: '\n', '\t'
|
||||||
|
// - single quote floating (un closed single quote) should be an error
|
||||||
|
match state.bytes().first() {
|
||||||
|
Some(b'\\') => {
|
||||||
|
state = advance_state!(state, 1)?;
|
||||||
|
match state.bytes().first() {
|
||||||
|
Some(&ch) => {
|
||||||
|
state = advance_state!(state, 1)?;
|
||||||
|
if (ch == b'n' || ch == b'r' || ch == b't' || ch == b'\'' || ch == b'\\')
|
||||||
|
&& (state.bytes().first() == Some(&b'\''))
|
||||||
|
{
|
||||||
|
state = advance_state!(state, 1)?;
|
||||||
|
let test = match ch {
|
||||||
|
b'n' => '\n',
|
||||||
|
b't' => '\t',
|
||||||
|
b'r' => '\r',
|
||||||
|
// since we checked the current char between the single quotes we
|
||||||
|
// know they are valid UTF-8, allowing us to use 'from_u32_unchecked'
|
||||||
|
_ => unsafe { char::from_u32_unchecked(ch as u32) },
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok((MadeProgress, &*arena.alloc_str(&test.to_string()), state));
|
||||||
|
}
|
||||||
|
// invalid error, backslah escaping something we do not recognize
|
||||||
|
return Err((NoProgress, EString::CodePtEnd(state.pos()), state));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// no close quote found
|
||||||
|
return Err((NoProgress, EString::CodePtEnd(state.pos()), state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
// do nothing for other characters, handled below
|
||||||
|
}
|
||||||
|
None => return Err((NoProgress, EString::CodePtEnd(state.pos()), state)),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bytes = state.bytes().iter();
|
||||||
|
let mut end_index = 1;
|
||||||
|
|
||||||
|
// Copy paste problem in mono
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match bytes.next() {
|
||||||
|
Some(b'\'') => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Some(_) => end_index += 1,
|
||||||
|
None => {
|
||||||
|
return Err((NoProgress, EString::Open(state.pos()), state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if end_index == 1 {
|
||||||
|
// no progress was made
|
||||||
|
// this case is a double single quote, ex: ''
|
||||||
|
// not supporting empty single quotes
|
||||||
|
return Err((NoProgress, EString::Open(state.pos()), state));
|
||||||
|
}
|
||||||
|
|
||||||
|
if end_index > (std::mem::size_of::<u32>() + 1) {
|
||||||
|
// bad case: too big to fit into u32
|
||||||
|
return Err((NoProgress, EString::Open(state.pos()), state));
|
||||||
|
}
|
||||||
|
|
||||||
|
// happy case -> we have some bytes that will fit into a u32
|
||||||
|
// ending up w/ a slice of bytes that we want to convert into an integer
|
||||||
|
let raw_bytes = &state.bytes()[0..end_index - 1];
|
||||||
|
|
||||||
|
state = advance_state!(state, end_index)?;
|
||||||
|
match std::str::from_utf8(raw_bytes) {
|
||||||
|
Ok(string) => Ok((MadeProgress, string, state)),
|
||||||
|
Err(_) => {
|
||||||
|
// invalid UTF-8
|
||||||
|
return Err((NoProgress, EString::CodePtEnd(state.pos()), state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
|
pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
|
||||||
use StrLiteral::*;
|
use StrLiteral::*;
|
||||||
|
|
||||||
|
|
|
@ -507,19 +507,28 @@ mod test_parse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn all_f64_values_parse(num: f64) {
|
fn all_f64_values_parse(mut num: f64) {
|
||||||
let string = num.to_string();
|
// NaN, Infinity, -Infinity (these would all parse as tags in Roc)
|
||||||
if string.contains('.') {
|
if !num.is_finite() {
|
||||||
assert_parses_to(&string, Float(&string));
|
num = 0.0;
|
||||||
} else if num.is_nan() {
|
|
||||||
assert_parses_to(&string, Expr::GlobalTag(&string));
|
|
||||||
} else if num.is_finite() {
|
|
||||||
// These are whole numbers. Add the `.0` back to make float.
|
|
||||||
let float_string = format!("{}.0", string);
|
|
||||||
assert_parses_to(&float_string, Float(&float_string));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These can potentially be whole numbers. `Display` omits the decimal point for those,
|
||||||
|
// causing them to no longer be parsed as fractional numbers by Roc.
|
||||||
|
// Using `Debug` instead of `Display` ensures they always have a decimal point.
|
||||||
|
let float_string = format!("{:?}", num);
|
||||||
|
|
||||||
|
assert_parses_to(float_string.as_str(), Float(float_string.as_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SINGLE QUOTE LITERAL
|
||||||
|
#[test]
|
||||||
|
fn single_quote() {
|
||||||
|
assert_parses_to("'b'", Expr::SingleQuote("b"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// RECORD LITERALS
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn type_signature_def() {
|
// fn type_signature_def() {
|
||||||
// let arena = Bump::new();
|
// let arena = Bump::new();
|
||||||
|
|
|
@ -171,10 +171,37 @@ pub enum RuntimeError {
|
||||||
region: Region,
|
region: Region,
|
||||||
exposed_values: Vec<Lowercase>,
|
exposed_values: Vec<Lowercase>,
|
||||||
},
|
},
|
||||||
|
/// A module was referenced, but hasn't been imported anywhere in the program
|
||||||
|
///
|
||||||
|
/// An example would be:
|
||||||
|
/// ```roc
|
||||||
|
/// app "hello"
|
||||||
|
/// packages { pf: "platform" }
|
||||||
|
/// imports [ pf.Stdout]
|
||||||
|
/// provides [ main ] to pf
|
||||||
|
///
|
||||||
|
/// main : Task.Task {} [] // Task isn't imported!
|
||||||
|
/// main = Stdout.line "I'm a Roc application!"
|
||||||
|
/// ```
|
||||||
ModuleNotImported {
|
ModuleNotImported {
|
||||||
|
/// The name of the module that was referenced
|
||||||
module_name: ModuleName,
|
module_name: ModuleName,
|
||||||
|
/// A list of modules which *have* been imported
|
||||||
imported_modules: MutSet<Box<str>>,
|
imported_modules: MutSet<Box<str>>,
|
||||||
|
/// Where the problem occurred
|
||||||
region: Region,
|
region: Region,
|
||||||
|
/// Whether or not the module exists at all
|
||||||
|
///
|
||||||
|
/// This is used to suggest that the user import the module, as opposed to fix a
|
||||||
|
/// typo in the spelling. For example, if the user typed `Task`, and the platform
|
||||||
|
/// exposes a `Task` module that hasn't been imported, we can sugguest that they
|
||||||
|
/// add the import statement.
|
||||||
|
///
|
||||||
|
/// On the other hand, if the user typed `Tesk`, they might want to check their
|
||||||
|
/// spelling.
|
||||||
|
///
|
||||||
|
/// If unsure, this should be set to `false`
|
||||||
|
module_exists: bool,
|
||||||
},
|
},
|
||||||
InvalidPrecedence(PrecedenceProblem, Region),
|
InvalidPrecedence(PrecedenceProblem, Region),
|
||||||
MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region),
|
MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region),
|
||||||
|
@ -203,6 +230,11 @@ pub enum RuntimeError {
|
||||||
VoidValue,
|
VoidValue,
|
||||||
|
|
||||||
ExposedButNotDefined(Symbol),
|
ExposedButNotDefined(Symbol),
|
||||||
|
|
||||||
|
/// where ''
|
||||||
|
EmptySingleQuote(Region),
|
||||||
|
/// where 'aa'
|
||||||
|
MultipleCharsInSingleQuote(Region),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
@ -213,4 +245,6 @@ pub enum MalformedPatternProblem {
|
||||||
Unknown,
|
Unknown,
|
||||||
QualifiedIdentifier,
|
QualifiedIdentifier,
|
||||||
BadIdent(roc_parse::ident::BadIdent),
|
BadIdent(roc_parse::ident::BadIdent),
|
||||||
|
EmptySingleQuote,
|
||||||
|
MultipleCharsInSingleQuote,
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ roc_reporting = { path = "../../reporting" }
|
||||||
roc_load = { path = "../load" }
|
roc_load = { path = "../load" }
|
||||||
roc_can = { path = "../can" }
|
roc_can = { path = "../can" }
|
||||||
roc_parse = { path = "../parse" }
|
roc_parse = { path = "../parse" }
|
||||||
roc_build = { path = "../build" }
|
roc_build = { path = "../build", features = ["target-aarch64", "target-x86_64", "target-wasm32"] }
|
||||||
roc_target = { path = "../roc_target" }
|
roc_target = { path = "../roc_target" }
|
||||||
roc_std = { path = "../../roc_std" }
|
roc_std = { path = "../../roc_std" }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
|
@ -52,7 +52,7 @@ wasmer = { version = "2.0.0", default-features = false, features = ["default-cra
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["gen-llvm"]
|
default = ["gen-llvm"]
|
||||||
gen-llvm = []
|
gen-llvm = ["roc_build/llvm"]
|
||||||
gen-dev = []
|
gen-dev = []
|
||||||
gen-wasm = []
|
gen-wasm = []
|
||||||
wasm-cli-run = []
|
wasm-cli-run = []
|
||||||
|
|
|
@ -357,6 +357,66 @@ fn u8_hex_int_alias() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn character_literal() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = 'A'
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
65,
|
||||||
|
u32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn character_literal_back_slash() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = '\\'
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
92,
|
||||||
|
u32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn character_literal_single_quote() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = '\''
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
39,
|
||||||
|
u32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn character_literal_new_line() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = '\n'
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
10,
|
||||||
|
u32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn dec_float_alias() {
|
fn dec_float_alias() {
|
||||||
|
|
|
@ -1862,6 +1862,13 @@ impl UnionTags {
|
||||||
slice.length == 1
|
slice.length == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_newtype_wrapper_of_global_tag(&self, subs: &Subs) -> bool {
|
||||||
|
self.is_newtype_wrapper(subs) && {
|
||||||
|
let tags = &subs.tag_names[self.tag_names().indices()];
|
||||||
|
matches!(tags[0], TagName::Global(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_tag_name_index(index: SubsIndex<TagName>) -> Self {
|
pub fn from_tag_name_index(index: SubsIndex<TagName>) -> Self {
|
||||||
Self::from_slices(
|
Self::from_slices(
|
||||||
SubsSlice::new(index.index, 1),
|
SubsSlice::new(index.index, 1),
|
||||||
|
|
|
@ -1304,6 +1304,7 @@ pub enum Category {
|
||||||
Num,
|
Num,
|
||||||
List,
|
List,
|
||||||
Str,
|
Str,
|
||||||
|
Character,
|
||||||
|
|
||||||
// records
|
// records
|
||||||
Record,
|
Record,
|
||||||
|
@ -1325,6 +1326,7 @@ pub enum PatternCategory {
|
||||||
Num,
|
Num,
|
||||||
Int,
|
Int,
|
||||||
Float,
|
Float,
|
||||||
|
Character,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
|
|
@ -3,9 +3,15 @@ use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get};
|
||||||
use crate::graphics::primitives::rect::Rect;
|
use crate::graphics::primitives::rect::Rect;
|
||||||
use crate::graphics::primitives::text as gr_text;
|
use crate::graphics::primitives::text as gr_text;
|
||||||
use cgmath::Vector2;
|
use cgmath::Vector2;
|
||||||
use roc_code_markup::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
|
use roc_code_markup::{
|
||||||
use roc_code_markup::slow_pool::{MarkNodeId, SlowPool};
|
markup::{
|
||||||
use roc_code_markup::syntax_highlight::HighlightStyle;
|
attribute::Attribute,
|
||||||
|
nodes::{MarkupNode, BLANK_PLACEHOLDER},
|
||||||
|
},
|
||||||
|
slow_pool::{MarkNodeId, SlowPool},
|
||||||
|
syntax_highlight::HighlightStyle,
|
||||||
|
underline_style::UnderlineStyle,
|
||||||
|
};
|
||||||
use winit::dpi::PhysicalSize;
|
use winit::dpi::PhysicalSize;
|
||||||
|
|
||||||
use crate::{editor::config::Config, graphics::colors};
|
use crate::{editor::config::Config, graphics::colors};
|
||||||
|
@ -94,6 +100,9 @@ fn markup_to_wgpu_helper<'a>(
|
||||||
txt_row_col: &mut (usize, usize),
|
txt_row_col: &mut (usize, usize),
|
||||||
mark_node_pool: &'a SlowPool,
|
mark_node_pool: &'a SlowPool,
|
||||||
) -> EdResult<()> {
|
) -> EdResult<()> {
|
||||||
|
let char_width = code_style.glyph_dim_rect.width;
|
||||||
|
let char_height = code_style.glyph_dim_rect.height;
|
||||||
|
|
||||||
match markup_node {
|
match markup_node {
|
||||||
MarkupNode::Nested {
|
MarkupNode::Nested {
|
||||||
ast_node_id: _,
|
ast_node_id: _,
|
||||||
|
@ -124,7 +133,7 @@ fn markup_to_wgpu_helper<'a>(
|
||||||
content,
|
content,
|
||||||
ast_node_id: _,
|
ast_node_id: _,
|
||||||
syn_high_style,
|
syn_high_style,
|
||||||
attributes: _,
|
attributes,
|
||||||
parent_id_opt: _,
|
parent_id_opt: _,
|
||||||
newlines_at_end,
|
newlines_at_end,
|
||||||
} => {
|
} => {
|
||||||
|
@ -132,10 +141,38 @@ fn markup_to_wgpu_helper<'a>(
|
||||||
|
|
||||||
let full_content = markup_node.get_full_content().replace("\n", "\\n"); // any \n left here should be escaped so that it can be shown as \n
|
let full_content = markup_node.get_full_content().replace("\n", "\\n"); // any \n left here should be escaped so that it can be shown as \n
|
||||||
|
|
||||||
let glyph_text = glyph_brush::OwnedText::new(full_content)
|
let glyph_text = glyph_brush::OwnedText::new(&full_content)
|
||||||
.with_color(colors::to_slice(*highlight_color))
|
.with_color(colors::to_slice(*highlight_color))
|
||||||
.with_scale(code_style.font_size);
|
.with_scale(code_style.font_size);
|
||||||
|
|
||||||
|
for attribute in &attributes.all {
|
||||||
|
match attribute {
|
||||||
|
Attribute::Underline { underline_spec: _ } => {
|
||||||
|
// TODO use underline_spec
|
||||||
|
let top_left_coords = (
|
||||||
|
code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width,
|
||||||
|
code_style.txt_coords.y
|
||||||
|
+ (txt_row_col.0 as f32) * char_height
|
||||||
|
+ 1.0 * char_height,
|
||||||
|
);
|
||||||
|
|
||||||
|
let underline_rect = Rect {
|
||||||
|
top_left_coords: top_left_coords.into(),
|
||||||
|
width: char_width * (full_content.len() as f32),
|
||||||
|
height: 5.0,
|
||||||
|
color: *code_style
|
||||||
|
.ed_theme
|
||||||
|
.underline_color_map
|
||||||
|
.get(&UnderlineStyle::Error)
|
||||||
|
.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
rects.push(underline_rect);
|
||||||
|
}
|
||||||
|
rest => todo!("handle Attribute: {:?}", rest),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
txt_row_col.1 += content.len();
|
txt_row_col.1 += content.len();
|
||||||
|
|
||||||
for _ in 0..*newlines_at_end {
|
for _ in 0..*newlines_at_end {
|
||||||
|
@ -160,9 +197,6 @@ fn markup_to_wgpu_helper<'a>(
|
||||||
let highlight_color =
|
let highlight_color =
|
||||||
map_get(&code_style.ed_theme.syntax_high_map, &HighlightStyle::Blank)?;
|
map_get(&code_style.ed_theme.syntax_high_map, &HighlightStyle::Blank)?;
|
||||||
|
|
||||||
let char_width = code_style.glyph_dim_rect.width;
|
|
||||||
let char_height = code_style.glyph_dim_rect.height;
|
|
||||||
|
|
||||||
let blank_rect = Rect {
|
let blank_rect = Rect {
|
||||||
top_left_coords: (
|
top_left_coords: (
|
||||||
code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width,
|
code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width,
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use gr_colors::{from_hsb, RgbaTup};
|
use gr_colors::{from_hsb, RgbaTup};
|
||||||
use roc_code_markup::syntax_highlight::{default_highlight_map, HighlightStyle};
|
use roc_code_markup::{
|
||||||
|
syntax_highlight::{default_highlight_map, HighlightStyle},
|
||||||
|
underline_style::{default_underline_color_map, UnderlineStyle},
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -12,6 +15,7 @@ pub struct EdTheme {
|
||||||
pub subtle_text: RgbaTup,
|
pub subtle_text: RgbaTup,
|
||||||
pub syntax_high_map: HashMap<HighlightStyle, RgbaTup>,
|
pub syntax_high_map: HashMap<HighlightStyle, RgbaTup>,
|
||||||
pub ui_theme: UITheme,
|
pub ui_theme: UITheme,
|
||||||
|
pub underline_color_map: HashMap<UnderlineStyle, RgbaTup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EdTheme {
|
impl Default for EdTheme {
|
||||||
|
@ -21,6 +25,7 @@ impl Default for EdTheme {
|
||||||
subtle_text: from_hsb(240, 5, 60),
|
subtle_text: from_hsb(240, 5, 60),
|
||||||
syntax_high_map: default_highlight_map(),
|
syntax_high_map: default_highlight_map(),
|
||||||
ui_theme: UITheme::default(),
|
ui_theme: UITheme::default(),
|
||||||
|
underline_color_map: default_underline_color_map(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
use cgmath::Vector2;
|
use cgmath::Vector2;
|
||||||
|
|
||||||
|
/// 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:
|
||||||
|
///
|
||||||
|
/// height: f32, pos: Vector2<f32>, width: f32
|
||||||
|
///
|
||||||
|
/// ...because Vector2<f32> is a repr(C) struct of { x: f32, y: f32 }
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
|
pub color: (f32, f32, f32, f32),
|
||||||
|
pub height: f32,
|
||||||
pub top_left_coords: Vector2<f32>,
|
pub top_left_coords: Vector2<f32>,
|
||||||
pub width: f32,
|
pub width: f32,
|
||||||
pub height: f32,
|
|
||||||
pub color: (f32, f32, f32, f32),
|
|
||||||
}
|
}
|
||||||
|
|
1
examples/gui/.gitignore
vendored
Normal file
1
examples/gui/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hello-gui
|
23
examples/gui/Hello.roc
Normal file
23
examples/gui/Hello.roc
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
app "hello-gui"
|
||||||
|
packages { pf: "platform" }
|
||||||
|
imports []# [ pf.Action.{ Action }, pf.Elem.{ button, text, row, col } ]
|
||||||
|
provides [ render ] to pf
|
||||||
|
|
||||||
|
render =
|
||||||
|
div0 = \numerator, denominator -> (numerator / denominator) |> Result.withDefault 0
|
||||||
|
|
||||||
|
rgba = \r, g, b, a -> { r: div0 r 255, g: div0 g 255, b: div0 b 255, a }
|
||||||
|
|
||||||
|
styles = { bgColor: rgba 100 50 50 1, borderColor: rgba 10 20 30 1, borderWidth: 10, textColor: rgba 220 220 250 1 }
|
||||||
|
|
||||||
|
Col
|
||||||
|
[
|
||||||
|
Row
|
||||||
|
[
|
||||||
|
Button (Text "Corner ") styles,
|
||||||
|
Button (Text "Top Mid ") { styles & bgColor: rgba 100 100 50 1 },
|
||||||
|
Button (Text "Top Right ") { styles & bgColor: rgba 50 50 150 1 },
|
||||||
|
],
|
||||||
|
Button (Text "Mid Left ") { styles & bgColor: rgba 150 100 100 1 },
|
||||||
|
Button (Text "Bottom Left") { styles & bgColor: rgba 150 50 50 1 },
|
||||||
|
]
|
20
examples/gui/platform/Action.roc
Normal file
20
examples/gui/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)
|
2826
examples/gui/platform/Cargo.lock
generated
Normal file
2826
examples/gui/platform/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
76
examples/gui/platform/Cargo.toml
Normal file
76
examples/gui/platform/Cargo.toml
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
[package]
|
||||||
|
name = "host"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Roc Contributors"]
|
||||||
|
license = "UPL-1.0"
|
||||||
|
edition = "2018"
|
||||||
|
links = "app"
|
||||||
|
|
||||||
|
# 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 = "thin"
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
|
# debug = true # enable when profiling
|
||||||
|
[profile.bench]
|
||||||
|
lto = "thin"
|
||||||
|
codegen-units = 1
|
193
examples/gui/platform/Elem.roc
Normal file
193
examples/gui/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
|
15
examples/gui/platform/Package-Config.roc
Normal file
15
examples/gui/platform/Package-Config.roc
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
platform "examples/hello-world"
|
||||||
|
requires {} { render : Elem }
|
||||||
|
exposes []
|
||||||
|
packages {}
|
||||||
|
imports []
|
||||||
|
provides [ renderForHost ]
|
||||||
|
|
||||||
|
Rgba : { r : F32, g : F32, b : F32, a : F32 }
|
||||||
|
|
||||||
|
ButtonStyles : { bgColor : Rgba, borderColor : Rgba, borderWidth : F32, textColor : Rgba }
|
||||||
|
|
||||||
|
Elem : [ Button Elem ButtonStyles, Col (List Elem), Row (List Elem), Text Str ]
|
||||||
|
|
||||||
|
renderForHost : Elem
|
||||||
|
renderForHost = render
|
4
examples/gui/platform/build.rs
Normal file
4
examples/gui/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/gui/platform/host.c
Normal file
3
examples/gui/platform/host.c
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
extern int rust_main();
|
||||||
|
|
||||||
|
int main() { return rust_main(); }
|
50
examples/gui/platform/src/graphics/colors.rs
Normal file
50
examples/gui/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)]
|
||||||
|
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.b, self.g, 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/gui/platform/src/graphics/lowlevel/buffer.rs
Normal file
96
examples/gui/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 = 100_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: Some("iced_wgpu::quad instance buffer"),
|
||||||
|
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/gui/platform/src/graphics/lowlevel/mod.rs
Normal file
5
examples/gui/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/gui/platform/src/graphics/lowlevel/ortho.rs
Normal file
118
examples/gui/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,
|
||||||
|
}
|
||||||
|
}
|
72
examples/gui/platform/src/graphics/lowlevel/pipelines.rs
Normal file
72
examples/gui/platform/src/graphics/lowlevel/pipelines.rs
Normal file
|
@ -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/gui/platform/src/graphics/lowlevel/quad.rs
Normal file
31
examples/gui/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/gui/platform/src/graphics/lowlevel/vertex.rs
Normal file
35
examples/gui/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/gui/platform/src/graphics/mod.rs
Normal file
4
examples/gui/platform/src/graphics/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod colors;
|
||||||
|
pub mod lowlevel;
|
||||||
|
pub mod primitives;
|
||||||
|
pub mod style;
|
2
examples/gui/platform/src/graphics/primitives/mod.rs
Normal file
2
examples/gui/platform/src/graphics/primitives/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod rect;
|
||||||
|
pub mod text;
|
27
examples/gui/platform/src/graphics/primitives/rect.rs
Normal file
27
examples/gui/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,
|
||||||
|
}
|
137
examples/gui/platform/src/graphics/primitives/text.rs
Normal file
137
examples/gui/platform/src/graphics/primitives/text.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// 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, Glyph, InvalidFont};
|
||||||
|
use cgmath::{Vector2, Vector4};
|
||||||
|
use glyph_brush::OwnedSection;
|
||||||
|
use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, Section};
|
||||||
|
|
||||||
|
use super::rect::Rect;
|
||||||
|
|
||||||
|
#[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/gui/platform/src/graphics/shaders/quad.wgsl
Normal file
60
examples/gui/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/gui/platform/src/graphics/style.rs
Normal file
1
examples/gui/platform/src/graphics/style.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub const DEFAULT_FONT_SIZE: f32 = 30.0;
|
650
examples/gui/platform/src/gui.rs
Normal file
650
examples/gui/platform/src/gui.rs
Normal file
|
@ -0,0 +1,650 @@
|
||||||
|
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::{RocElem, RocElemTag},
|
||||||
|
};
|
||||||
|
use cgmath::{Vector2, Vector4};
|
||||||
|
use glyph_brush::OwnedSection;
|
||||||
|
use pipelines::RectResources;
|
||||||
|
use roc_std::RocStr;
|
||||||
|
use std::error::Error;
|
||||||
|
use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView};
|
||||||
|
use wgpu_glyph::{GlyphBrush, GlyphCruncher};
|
||||||
|
use winit::{
|
||||||
|
dpi::PhysicalSize,
|
||||||
|
event,
|
||||||
|
event::{Event, ModifiersState},
|
||||||
|
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/
|
||||||
|
|
||||||
|
fn run_event_loop(title: &str, root: RocElem) -> Result<(), Box<dyn Error>> {
|
||||||
|
// 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(1900.0, 1000.0))
|
||||||
|
.with_title(title)
|
||||||
|
.build(&event_loop)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
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 is_animating = true;
|
||||||
|
|
||||||
|
let mut keyboard_modifiers = ModifiersState::empty();
|
||||||
|
|
||||||
|
// Render loop
|
||||||
|
window.request_redraw();
|
||||||
|
|
||||||
|
event_loop.run_return(|event, _, control_flow| {
|
||||||
|
// TODO dynamically switch this on/off depending on whether any
|
||||||
|
// animations are running. Should conserve CPU usage and battery life!
|
||||||
|
if is_animating {
|
||||||
|
*control_flow = ControlFlow::Poll;
|
||||||
|
} else {
|
||||||
|
*control_flow = ControlFlow::Wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//Received Character
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: event::WindowEvent::ReceivedCharacter(_ch),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// let input_outcome_res =
|
||||||
|
// app_update::handle_new_char(&ch, &mut app_model, keyboard_modifiers);
|
||||||
|
// if let Err(e) = input_outcome_res {
|
||||||
|
// print_err(&e)
|
||||||
|
// } else if let Ok(InputOutcome::Ignored) = input_outcome_res {
|
||||||
|
// println!("Input '{}' ignored!", ch);
|
||||||
|
// }
|
||||||
|
todo!("TODO handle character input");
|
||||||
|
}
|
||||||
|
//Keyboard Input
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: event::WindowEvent::KeyboardInput { input: _, .. },
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// if let Some(virtual_keycode) = input.virtual_keycode {
|
||||||
|
// if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||||
|
// if ed_model.has_focus {
|
||||||
|
// let keydown_res = keyboard_input::handle_keydown(
|
||||||
|
// input.state,
|
||||||
|
// virtual_keycode,
|
||||||
|
// keyboard_modifiers,
|
||||||
|
// &mut app_model,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if let Err(e) = keydown_res {
|
||||||
|
// print_err(&e)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
todo!("TODO handle keyboard input");
|
||||||
|
}
|
||||||
|
//Modifiers Changed
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: event::WindowEvent::ModifiersChanged(modifiers),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
keyboard_modifiers = modifiers;
|
||||||
|
}
|
||||||
|
Event::MainEventsCleared => window.request_redraw(),
|
||||||
|
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 text_section in &rects_and_texts.text_sections_behind {
|
||||||
|
// let borrowed_text = text_section.to_borrowed();
|
||||||
|
|
||||||
|
// glyph_brush.queue(borrowed_text);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// draw first layer of text
|
||||||
|
// glyph_brush
|
||||||
|
// .draw_queued(
|
||||||
|
// &gpu_device,
|
||||||
|
// &mut staging_belt,
|
||||||
|
// &mut cmd_encoder,
|
||||||
|
// &view,
|
||||||
|
// size.width,
|
||||||
|
// size.height,
|
||||||
|
// )
|
||||||
|
// .expect("Failed to draw first layer of text.");
|
||||||
|
|
||||||
|
// draw rects on top of first text layer
|
||||||
|
// draw_rects(
|
||||||
|
// &rects_and_texts.rects_front,
|
||||||
|
// &mut cmd_encoder,
|
||||||
|
// &view,
|
||||||
|
// &gpu_device,
|
||||||
|
// &rect_resources,
|
||||||
|
// wgpu::LoadOp::Load,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// TODO use with_capacity based on some heuristic
|
||||||
|
let (_bounds, drawable) = to_drawable(
|
||||||
|
&root,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// for text_section in &rects_and_texts.text_sections_front {
|
||||||
|
// let borrowed_text = text_section.to_borrowed();
|
||||||
|
|
||||||
|
// glyph_brush.queue(borrowed_text);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// draw text
|
||||||
|
// glyph_brush
|
||||||
|
// .draw_queued(
|
||||||
|
// &gpu_device,
|
||||||
|
// &mut staging_belt,
|
||||||
|
// &mut cmd_encoder,
|
||||||
|
// &view,
|
||||||
|
// size.width,
|
||||||
|
// size.height,
|
||||||
|
// )
|
||||||
|
// .expect("Failed to draw queued text.");
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
*control_flow = winit::event_loop::ControlFlow::Wait;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(title: RocStr, root: RocElem) {
|
||||||
|
run_event_loop(title.as_str(), root).expect("Error running event loop");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
struct Bounds {
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Drawable {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
Multi(Vec<Drawable>),
|
||||||
|
Offset(Vec<(Vector2<f32>, Drawable)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
// TODO iterate through drawables,
|
||||||
|
// calculating a pos using offset,
|
||||||
|
// calling draw and updating bounding boxes
|
||||||
|
let pos: Vector2<f32> = (0.0, 0.0).into();
|
||||||
|
|
||||||
|
draw(
|
||||||
|
drawable.bounds,
|
||||||
|
drawable.content,
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Offset(children) => {
|
||||||
|
for (offset, child) in children.into_iter() {
|
||||||
|
draw(
|
||||||
|
child.bounds,
|
||||||
|
child.content,
|
||||||
|
pos + offset,
|
||||||
|
staging_belt,
|
||||||
|
glyph_brush,
|
||||||
|
cmd_encoder,
|
||||||
|
texture_view,
|
||||||
|
gpu_device,
|
||||||
|
rect_resources,
|
||||||
|
load_op,
|
||||||
|
texture_size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Multi(children) => {
|
||||||
|
for child in children.into_iter() {
|
||||||
|
draw(
|
||||||
|
child.bounds,
|
||||||
|
child.content,
|
||||||
|
pos,
|
||||||
|
staging_belt,
|
||||||
|
glyph_brush,
|
||||||
|
cmd_encoder,
|
||||||
|
texture_view,
|
||||||
|
gpu_device,
|
||||||
|
rect_resources,
|
||||||
|
load_op,
|
||||||
|
texture_size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_drawable(
|
||||||
|
elem: &RocElem,
|
||||||
|
bounds: Bounds,
|
||||||
|
glyph_brush: &mut GlyphBrush<()>,
|
||||||
|
) -> (Bounds, Drawable) {
|
||||||
|
use RocElemTag::*;
|
||||||
|
|
||||||
|
match elem.tag() {
|
||||||
|
Button => {
|
||||||
|
let button = unsafe { &elem.entry().button };
|
||||||
|
let styles = button.styles;
|
||||||
|
let (child_bounds, child_drawable) = to_drawable(&*button.child, bounds, glyph_brush);
|
||||||
|
|
||||||
|
let button_drawable = Drawable {
|
||||||
|
bounds: child_bounds,
|
||||||
|
content: DrawableContent::FillRect {
|
||||||
|
color: styles.bg_color,
|
||||||
|
border_width: styles.border_width,
|
||||||
|
border_color: styles.border_color,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let drawable = Drawable {
|
||||||
|
bounds: child_bounds,
|
||||||
|
content: DrawableContent::Multi(vec![button_drawable, child_drawable]),
|
||||||
|
};
|
||||||
|
|
||||||
|
(child_bounds, drawable)
|
||||||
|
}
|
||||||
|
Text => {
|
||||||
|
// TODO let text color and font settings inherit from parent
|
||||||
|
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 {
|
||||||
|
bounds: text_bounds,
|
||||||
|
content: DrawableContent::Text(section, offset),
|
||||||
|
};
|
||||||
|
|
||||||
|
(text_bounds, drawable)
|
||||||
|
}
|
||||||
|
Row => {
|
||||||
|
let row = unsafe { &elem.entry().row_or_col };
|
||||||
|
let mut final_bounds = Bounds::default();
|
||||||
|
let mut offset: Vector2<f32> = (0.0, 0.0).into();
|
||||||
|
let mut offset_entries = Vec::with_capacity(row.children.len());
|
||||||
|
|
||||||
|
for child in row.children.as_slice().iter() {
|
||||||
|
let (child_bounds, child_drawable) = to_drawable(&child, bounds, glyph_brush);
|
||||||
|
|
||||||
|
offset_entries.push((offset, child_drawable));
|
||||||
|
|
||||||
|
// Make sure the final height is enough to fit this child
|
||||||
|
final_bounds.height = final_bounds.height.max(child_bounds.height);
|
||||||
|
|
||||||
|
// Add the child's width to the final width
|
||||||
|
final_bounds.width = final_bounds.width + child_bounds.width;
|
||||||
|
|
||||||
|
// Offset the next child to make sure it appears after this one.
|
||||||
|
offset.x += child_bounds.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
final_bounds,
|
||||||
|
Drawable {
|
||||||
|
bounds: final_bounds,
|
||||||
|
content: DrawableContent::Offset(offset_entries),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Col => {
|
||||||
|
let col = unsafe { &elem.entry().row_or_col };
|
||||||
|
let mut final_bounds = Bounds::default();
|
||||||
|
let mut offset: Vector2<f32> = (0.0, 0.0).into();
|
||||||
|
let mut offset_entries = Vec::with_capacity(col.children.len());
|
||||||
|
|
||||||
|
for child in col.children.as_slice().iter() {
|
||||||
|
let (child_bounds, child_drawable) = to_drawable(&child, bounds, glyph_brush);
|
||||||
|
|
||||||
|
offset_entries.push((offset, child_drawable));
|
||||||
|
|
||||||
|
// Make sure the final width is enough to fit this child
|
||||||
|
final_bounds.width = final_bounds.width.max(child_bounds.width);
|
||||||
|
|
||||||
|
// Add the child's height to the final height
|
||||||
|
final_bounds.height = final_bounds.height + child_bounds.height;
|
||||||
|
|
||||||
|
// Offset the next child to make sure it appears after this one.
|
||||||
|
offset.y += child_bounds.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
final_bounds,
|
||||||
|
Drawable {
|
||||||
|
bounds: final_bounds,
|
||||||
|
content: DrawableContent::Offset(offset_entries),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
21
examples/gui/platform/src/lib.rs
Normal file
21
examples/gui/platform/src/lib.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
mod graphics;
|
||||||
|
mod gui;
|
||||||
|
mod rects_and_texts;
|
||||||
|
mod roc;
|
||||||
|
|
||||||
|
use crate::roc::RocElem;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#[link_name = "roc__renderForHost_1_exposed"]
|
||||||
|
fn roc_render() -> RocElem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rust_main() -> i32 {
|
||||||
|
let root_elem = unsafe { roc_render() };
|
||||||
|
|
||||||
|
gui::render("test title".into(), root_elem);
|
||||||
|
|
||||||
|
// Exit code
|
||||||
|
0
|
||||||
|
}
|
3
examples/gui/platform/src/main.rs
Normal file
3
examples/gui/platform/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
std::process::exit(host::rust_main());
|
||||||
|
}
|
70
examples/gui/platform/src/rects_and_texts.rs
Normal file
70
examples/gui/platform/src/rects_and_texts.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
use crate::graphics::primitives::rect::RectElt;
|
||||||
|
use crate::graphics::primitives::text::{owned_section_from_text, Text};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RectsAndTexts {
|
||||||
|
pub text_sections_behind: Vec<glyph_brush::OwnedSection>, // displayed in front of rect_behind, behind everything else
|
||||||
|
pub text_sections_front: Vec<glyph_brush::OwnedSection>, // displayed in front of everything
|
||||||
|
pub rects_behind: Vec<RectElt>, // displayed at lowest depth
|
||||||
|
pub rects_front: Vec<RectElt>, // displayed in front of text_sections_behind, behind text_sections_front
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RectsAndTexts {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
text_sections_behind: Vec::new(),
|
||||||
|
text_sections_front: Vec::new(),
|
||||||
|
rects_behind: Vec::new(),
|
||||||
|
rects_front: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
rects_behind: Vec<RectElt>,
|
||||||
|
texts_behind: Vec<Text>,
|
||||||
|
rects_front: Vec<RectElt>,
|
||||||
|
texts_front: Vec<Text>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
text_sections_behind: texts_behind
|
||||||
|
.iter()
|
||||||
|
.map(|txt| owned_section_from_text(txt))
|
||||||
|
.collect(),
|
||||||
|
text_sections_front: texts_front
|
||||||
|
.iter()
|
||||||
|
.map(|txt| owned_section_from_text(txt))
|
||||||
|
.collect(),
|
||||||
|
rects_behind,
|
||||||
|
rects_front,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_text_behind(&mut self, new_text_section: glyph_brush::OwnedSection) {
|
||||||
|
self.text_sections_behind.push(new_text_section);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_text_front(&mut self, new_text_section: glyph_brush::OwnedSection) {
|
||||||
|
self.text_sections_front.push(new_text_section);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_rect_behind(&mut self, new_rect: RectElt) {
|
||||||
|
self.rects_behind.push(new_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_rects_behind(&mut self, new_rects: Vec<RectElt>) {
|
||||||
|
self.rects_behind.extend(new_rects);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_rect_front(&mut self, new_rect: RectElt) {
|
||||||
|
self.rects_front.push(new_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extend(&mut self, rects_and_texts: RectsAndTexts) {
|
||||||
|
self.text_sections_behind
|
||||||
|
.extend(rects_and_texts.text_sections_behind);
|
||||||
|
self.text_sections_front
|
||||||
|
.extend(rects_and_texts.text_sections_front);
|
||||||
|
self.rects_behind.extend(rects_and_texts.rects_behind);
|
||||||
|
self.rects_front.extend(rects_and_texts.rects_front);
|
||||||
|
}
|
||||||
|
}
|
150
examples/gui/platform/src/roc.rs
Normal file
150
examples/gui/platform/src/roc.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
use crate::graphics::colors::Rgba;
|
||||||
|
use core::ffi::c_void;
|
||||||
|
use core::mem::{self, ManuallyDrop};
|
||||||
|
use roc_std::{ReferenceCount, RocList, RocStr};
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
|
||||||
|
return libc::malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_realloc(
|
||||||
|
c_ptr: *mut c_void,
|
||||||
|
new_size: usize,
|
||||||
|
_old_size: usize,
|
||||||
|
_alignment: u32,
|
||||||
|
) -> *mut c_void {
|
||||||
|
return libc::realloc(c_ptr, new_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
|
||||||
|
return libc::free(c_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
|
||||||
|
match tag_id {
|
||||||
|
0 => {
|
||||||
|
let slice = CStr::from_ptr(c_ptr as *const c_char);
|
||||||
|
let string = slice.to_str().unwrap();
|
||||||
|
eprintln!("Roc hit a panic: {}", string);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
|
||||||
|
libc::memcpy(dst, src, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
|
||||||
|
libc::memset(dst, c, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
|
||||||
|
pub struct RocElem {
|
||||||
|
entry: *const RocElemEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RocElem {
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
pub fn tag(&self) -> RocElemTag {
|
||||||
|
// On a 64-bit system, the last 3 bits of the pointer store the tag
|
||||||
|
unsafe { mem::transmute::<u8, RocElemTag>((self.entry as u8) & 0b0000_0111) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entry(&self) -> &RocElemEntry {
|
||||||
|
// On a 64-bit system, the last 3 bits of the pointer store the tag
|
||||||
|
let cleared = self.entry as usize & !0b111;
|
||||||
|
|
||||||
|
unsafe { &*(cleared as *const RocElemEntry) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[allow(unused)] // This is actually used, just via a mem::transmute from u8
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum RocElemTag {
|
||||||
|
Button = 0,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct RocButton {
|
||||||
|
pub child: ManuallyDrop<RocElem>,
|
||||||
|
pub styles: ButtonStyles,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct RocRowOrCol {
|
||||||
|
pub children: RocList<RocElem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl ReferenceCount for RocElem {
|
||||||
|
/// Increment the reference count.
|
||||||
|
fn increment(&self) {
|
||||||
|
use RocElemTag::*;
|
||||||
|
|
||||||
|
match self.tag() {
|
||||||
|
Button => unsafe { &*self.entry().button.child }.increment(),
|
||||||
|
Text => unsafe { &*self.entry().text }.increment(),
|
||||||
|
Row | Col => {
|
||||||
|
let children = unsafe { &self.entry().row_or_col.children };
|
||||||
|
|
||||||
|
for child in children.as_slice().iter() {
|
||||||
|
child.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() {
|
||||||
|
Button => ReferenceCount::decrement(&*elem.entry().button.child),
|
||||||
|
Text => ReferenceCount::decrement(&*elem.entry().text),
|
||||||
|
Row | Col => {
|
||||||
|
let children = &elem.entry().row_or_col.children;
|
||||||
|
|
||||||
|
for child in children.as_slice().iter() {
|
||||||
|
ReferenceCount::decrement(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct ButtonStyles {
|
||||||
|
pub bg_color: Rgba,
|
||||||
|
pub border_color: Rgba,
|
||||||
|
pub border_width: f32,
|
||||||
|
pub text_color: Rgba,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub union RocElemEntry {
|
||||||
|
pub button: ManuallyDrop<RocButton>,
|
||||||
|
pub text: ManuallyDrop<RocStr>,
|
||||||
|
pub row_or_col: ManuallyDrop<RocRowOrCol>,
|
||||||
|
}
|
|
@ -19,10 +19,10 @@ bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
roc_mono = { path = "../compiler/mono" }
|
roc_mono = { path = "../compiler/mono" }
|
||||||
roc_build = { path = "../compiler/build", default-features = false }
|
roc_build = { path = "../compiler/build" }
|
||||||
roc_collections = { path = "../compiler/collections" }
|
roc_collections = { path = "../compiler/collections" }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
||||||
iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
|
iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
|
||||||
memmap2 = "0.5.0"
|
memmap2 = "0.5.0"
|
||||||
object = { version = "0.26.2", features = ["read", "write"] }
|
object = { version = "0.26.2", features = ["read", "write"] }
|
||||||
|
|
|
@ -5,16 +5,25 @@ version = "0.1.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# pipe target to roc_build
|
||||||
|
target-arm = ["roc_build/target-arm"]
|
||||||
|
target-aarch64 = ["roc_build/target-aarch64"]
|
||||||
|
target-x86 = ["roc_build/target-x86"]
|
||||||
|
target-x86_64 = ["roc_build/target-x86_64"]
|
||||||
|
target-wasm32 = ["roc_build/target-wasm32"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bumpalo = {version = "3.8.0", features = ["collections"]}
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
const_format = "0.2.22"
|
const_format = "0.2.22"
|
||||||
inkwell = {path = "../vendor/inkwell"}
|
inkwell = {path = "../vendor/inkwell"}
|
||||||
libloading = {version = "0.7.1"}
|
libloading = "0.7.1"
|
||||||
rustyline = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
|
rustyline = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
|
||||||
rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
|
rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
|
||||||
target-lexicon = "0.12.2"
|
target-lexicon = "0.12.2"
|
||||||
|
|
||||||
roc_build = {path = "../compiler/build"}
|
# TODO: make llvm optional
|
||||||
|
roc_build = {path = "../compiler/build", features = ["llvm"]}
|
||||||
roc_collections = {path = "../compiler/collections"}
|
roc_collections = {path = "../compiler/collections"}
|
||||||
roc_gen_llvm = {path = "../compiler/gen_llvm"}
|
roc_gen_llvm = {path = "../compiler/gen_llvm"}
|
||||||
roc_load = {path = "../compiler/load"}
|
roc_load = {path = "../compiler/load"}
|
||||||
|
|
|
@ -6,7 +6,7 @@ version = "0.1.0"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bumpalo = {version = "3.8.0", features = ["collections"]}
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
|
|
||||||
roc_builtins = {path = "../compiler/builtins"}
|
roc_builtins = {path = "../compiler/builtins"}
|
||||||
roc_can = {path = "../compiler/can"}
|
roc_can = {path = "../compiler/can"}
|
||||||
|
|
|
@ -5,11 +5,11 @@ use std::cmp::{max_by_key, min_by_key};
|
||||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_module::called_via::CalledVia;
|
use roc_module::called_via::CalledVia;
|
||||||
use roc_module::ident::TagName;
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
use roc_mono::ir::ProcLayout;
|
use roc_mono::ir::ProcLayout;
|
||||||
use roc_mono::layout::{
|
use roc_mono::layout::{
|
||||||
union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant, WrappedVariant,
|
union_sorted_tags_help, Builtin, Layout, LayoutCache, UnionLayout, UnionVariant, WrappedVariant,
|
||||||
};
|
};
|
||||||
use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
|
use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
|
||||||
use roc_region::all::{Loc, Region};
|
use roc_region::all::{Loc, Region};
|
||||||
|
@ -70,6 +70,7 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum NewtypeKind<'a> {
|
enum NewtypeKind<'a> {
|
||||||
Tag(&'a TagName),
|
Tag(&'a TagName),
|
||||||
RecordField(&'a str),
|
RecordField(&'a str),
|
||||||
|
@ -89,10 +90,11 @@ fn unroll_newtypes<'a>(
|
||||||
mut content: &'a Content,
|
mut content: &'a Content,
|
||||||
) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) {
|
) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) {
|
||||||
let mut newtype_containers = Vec::with_capacity_in(1, env.arena);
|
let mut newtype_containers = Vec::with_capacity_in(1, env.arena);
|
||||||
|
let mut force_alias_content = None;
|
||||||
loop {
|
loop {
|
||||||
match content {
|
match content {
|
||||||
Content::Structure(FlatType::TagUnion(tags, _))
|
Content::Structure(FlatType::TagUnion(tags, _))
|
||||||
if tags.is_newtype_wrapper(env.subs) =>
|
if tags.is_newtype_wrapper_of_global_tag(env.subs) =>
|
||||||
{
|
{
|
||||||
let (tag_name, vars): (&TagName, &[Variable]) = tags
|
let (tag_name, vars): (&TagName, &[Variable]) = tags
|
||||||
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
|
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
|
||||||
|
@ -113,7 +115,20 @@ fn unroll_newtypes<'a>(
|
||||||
let field_var = *field.as_inner();
|
let field_var = *field.as_inner();
|
||||||
content = env.subs.get_content_without_compacting(field_var);
|
content = env.subs.get_content_without_compacting(field_var);
|
||||||
}
|
}
|
||||||
_ => return (newtype_containers, content),
|
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,
|
||||||
|
// 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);
|
||||||
|
content = env.subs.get_content_without_compacting(*real_var);
|
||||||
|
}
|
||||||
|
_ => return (newtype_containers, force_alias_content.unwrap_or(content)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,15 +349,11 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||||
Layout::Struct { field_layouts, .. } => {
|
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 content {
|
||||||
Content::Structure(FlatType::Record(fields, _)) => {
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
Ok(struct_to_ast(env, mem, addr, field_layouts, *fields))
|
Ok(struct_to_ast(env, mem, addr, *fields))
|
||||||
|
}
|
||||||
|
Content::Structure(FlatType::EmptyRecord) => {
|
||||||
|
Ok(struct_to_ast(env, mem, addr, RecordFields::empty()))
|
||||||
}
|
}
|
||||||
Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast(
|
|
||||||
env,
|
|
||||||
mem,
|
|
||||||
addr,
|
|
||||||
field_layouts,
|
|
||||||
RecordFields::empty(),
|
|
||||||
)),
|
|
||||||
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
||||||
debug_assert_eq!(tags.len(), 1);
|
debug_assert_eq!(tags.len(), 1);
|
||||||
|
|
||||||
|
@ -518,7 +529,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||||
}
|
}
|
||||||
(_, Layout::Struct{field_layouts, ..}) => match content {
|
(_, Layout::Struct{field_layouts, ..}) => match content {
|
||||||
Content::Structure(FlatType::Record(fields, _)) => {
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
struct_to_ast(env, mem, addr, field_layouts, *fields)
|
struct_to_ast(env, mem, addr, *fields)
|
||||||
}
|
}
|
||||||
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
||||||
debug_assert_eq!(tags.len(), 1);
|
debug_assert_eq!(tags.len(), 1);
|
||||||
|
@ -531,7 +542,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||||
single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, &[])
|
single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, &[])
|
||||||
}
|
}
|
||||||
Content::Structure(FlatType::EmptyRecord) => {
|
Content::Structure(FlatType::EmptyRecord) => {
|
||||||
struct_to_ast(env, mem, addr, &[], RecordFields::empty())
|
struct_to_ast(env, mem, addr, RecordFields::empty())
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
|
@ -841,30 +852,46 @@ fn struct_to_ast<'a, M: ReplAppMemory>(
|
||||||
env: &Env<'a, 'a>,
|
env: &Env<'a, 'a>,
|
||||||
mem: &'a M,
|
mem: &'a M,
|
||||||
addr: usize,
|
addr: usize,
|
||||||
field_layouts: &'a [Layout<'a>],
|
|
||||||
record_fields: RecordFields,
|
record_fields: RecordFields,
|
||||||
) -> Expr<'a> {
|
) -> Expr<'a> {
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
let subs = env.subs;
|
let subs = env.subs;
|
||||||
let mut output = Vec::with_capacity_in(field_layouts.len(), arena);
|
let mut output = Vec::with_capacity_in(record_fields.len(), arena);
|
||||||
|
|
||||||
let sorted_fields: Vec<_> = Vec::from_iter_in(
|
let sorted_fields: Vec<_> = Vec::from_iter_in(
|
||||||
record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD),
|
record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD),
|
||||||
env.arena,
|
arena,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut layout_cache = LayoutCache::new(env.target_info);
|
||||||
|
// We recalculate the layouts here because we will have compiled the record so that its fields
|
||||||
|
// are sorted by descending alignment, and then alphabetic, but the type of the record is
|
||||||
|
// always only sorted alphabetically. We want to arrange the rendered record in the order of
|
||||||
|
// the type.
|
||||||
|
let field_to_layout: MutMap<Lowercase, Layout> = sorted_fields
|
||||||
|
.iter()
|
||||||
|
.map(|(label, field)| {
|
||||||
|
let layout = layout_cache
|
||||||
|
.from_var(arena, *field.as_inner(), env.subs)
|
||||||
|
.unwrap();
|
||||||
|
(label.clone(), layout)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
if sorted_fields.len() == 1 {
|
if sorted_fields.len() == 1 {
|
||||||
// this is a 1-field wrapper record around another record or 1-tag tag union
|
// this is a 1-field wrapper record around another record or 1-tag tag union
|
||||||
let (label, field) = sorted_fields.into_iter().next().unwrap();
|
let (label, field) = sorted_fields.into_iter().next().unwrap();
|
||||||
|
|
||||||
let inner_content = env.subs.get_content_without_compacting(field.into_inner());
|
let inner_content = env.subs.get_content_without_compacting(field.into_inner());
|
||||||
|
debug_assert_eq!(field_to_layout.len(), 1);
|
||||||
|
let inner_layouts = arena.alloc([field_to_layout.into_values().next().unwrap()]);
|
||||||
|
|
||||||
let loc_expr = &*arena.alloc(Loc {
|
let loc_expr = &*arena.alloc(Loc {
|
||||||
value: addr_to_ast(
|
value: addr_to_ast(
|
||||||
env,
|
env,
|
||||||
mem,
|
mem,
|
||||||
addr,
|
addr,
|
||||||
&Layout::struct_no_name_order(field_layouts),
|
&Layout::struct_no_name_order(inner_layouts),
|
||||||
WhenRecursive::Unreachable,
|
WhenRecursive::Unreachable,
|
||||||
inner_content,
|
inner_content,
|
||||||
),
|
),
|
||||||
|
@ -880,19 +907,20 @@ fn struct_to_ast<'a, M: ReplAppMemory>(
|
||||||
region: Region::zero(),
|
region: Region::zero(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let output = env.arena.alloc([loc_field]);
|
let output = arena.alloc([loc_field]);
|
||||||
|
|
||||||
Expr::Record(Collection::with_items(output))
|
Expr::Record(Collection::with_items(output))
|
||||||
} else {
|
} else {
|
||||||
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
|
debug_assert_eq!(sorted_fields.len(), field_to_layout.len());
|
||||||
|
|
||||||
// We'll advance this as we iterate through the fields
|
// We'll advance this as we iterate through the fields
|
||||||
let mut field_addr = addr;
|
let mut field_addr = addr;
|
||||||
|
|
||||||
for ((label, field), field_layout) in sorted_fields.into_iter().zip(field_layouts.iter()) {
|
for (label, field) in sorted_fields.into_iter() {
|
||||||
let var = field.into_inner();
|
let var = field.into_inner();
|
||||||
|
|
||||||
let content = subs.get_content_without_compacting(var);
|
let content = subs.get_content_without_compacting(var);
|
||||||
|
let field_layout = field_to_layout.get(&label).unwrap();
|
||||||
|
|
||||||
let loc_expr = &*arena.alloc(Loc {
|
let loc_expr = &*arena.alloc(Loc {
|
||||||
value: addr_to_ast(
|
value: addr_to_ast(
|
||||||
|
|
|
@ -840,7 +840,7 @@ fn function_in_list() {
|
||||||
fn function_in_record() {
|
fn function_in_record() {
|
||||||
expect_success(
|
expect_success(
|
||||||
r#"{ n: 1, adder: \x -> x + 1 }"#,
|
r#"{ n: 1, adder: \x -> x + 1 }"#,
|
||||||
r#"{ adder: <function>, n: <function> } : { adder : Num a -> Num a, n : Num * }"#,
|
r#"{ adder: <function>, n: 1 } : { adder : Num a -> Num a, n : Num * }"#,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -975,3 +975,48 @@ fn issue_2343_complete_mono_with_shadowed_vars() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_with_type_behind_alias() {
|
||||||
|
expect_success(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
T : { a: Str }
|
||||||
|
v : T
|
||||||
|
v = { a: "value" }
|
||||||
|
v
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
r#"{ a: "value" } : T"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tag_with_type_behind_alias() {
|
||||||
|
expect_success(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
T : [ A Str ]
|
||||||
|
v : T
|
||||||
|
v = A "value"
|
||||||
|
v
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
r#"A "value" : T"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wasm"))]
|
||||||
|
#[test]
|
||||||
|
fn issue_2588_record_with_function_and_nonfunction() {
|
||||||
|
expect_success(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = 1
|
||||||
|
f = \n -> n * 2
|
||||||
|
{ y: f x, f }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
r#"{ f: <function>, y: 2 } : { f : Num a -> Num a, y : Num * }"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ crate-type = ["cdylib"]
|
||||||
roc_builtins = {path = "../compiler/builtins"}
|
roc_builtins = {path = "../compiler/builtins"}
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bumpalo = {version = "3.8.0", features = ["collections"]}
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
futures = {version = "0.3.17", optional = true}
|
futures = { version = "0.3.17", optional = true }
|
||||||
js-sys = "0.3.56"
|
js-sys = "0.3.56"
|
||||||
wasm-bindgen = "0.2.79"
|
wasm-bindgen = "0.2.79"
|
||||||
wasm-bindgen-futures = "0.4.29"
|
wasm-bindgen-futures = "0.4.29"
|
||||||
|
|
|
@ -945,13 +945,17 @@ fn pretty_runtime_error<'b>(
|
||||||
}
|
}
|
||||||
Unknown => " ",
|
Unknown => " ",
|
||||||
QualifiedIdentifier => " qualified ",
|
QualifiedIdentifier => " qualified ",
|
||||||
|
EmptySingleQuote => " empty character literal ",
|
||||||
|
MultipleCharsInSingleQuote => " overfull literal ",
|
||||||
};
|
};
|
||||||
|
|
||||||
let tip = match problem {
|
let tip = match problem {
|
||||||
MalformedInt | MalformedFloat | MalformedBase(_) => alloc
|
MalformedInt | MalformedFloat | MalformedBase(_) => alloc
|
||||||
.tip()
|
.tip()
|
||||||
.append(alloc.reflow("Learn more about number literals at TODO")),
|
.append(alloc.reflow("Learn more about number literals at TODO")),
|
||||||
Unknown | BadIdent(_) => alloc.nil(),
|
EmptySingleQuote | MultipleCharsInSingleQuote | Unknown | BadIdent(_) => {
|
||||||
|
alloc.nil()
|
||||||
|
}
|
||||||
QualifiedIdentifier => alloc.tip().append(
|
QualifiedIdentifier => alloc.tip().append(
|
||||||
alloc.reflow("In patterns, only private and global tags can be qualified"),
|
alloc.reflow("In patterns, only private and global tags can be qualified"),
|
||||||
),
|
),
|
||||||
|
@ -1015,8 +1019,16 @@ fn pretty_runtime_error<'b>(
|
||||||
module_name,
|
module_name,
|
||||||
imported_modules,
|
imported_modules,
|
||||||
region,
|
region,
|
||||||
|
module_exists,
|
||||||
} => {
|
} => {
|
||||||
doc = module_not_found(alloc, lines, region, &module_name, imported_modules);
|
doc = module_not_found(
|
||||||
|
alloc,
|
||||||
|
lines,
|
||||||
|
region,
|
||||||
|
&module_name,
|
||||||
|
imported_modules,
|
||||||
|
module_exists,
|
||||||
|
);
|
||||||
|
|
||||||
title = MODULE_NOT_IMPORTED;
|
title = MODULE_NOT_IMPORTED;
|
||||||
}
|
}
|
||||||
|
@ -1333,6 +1345,37 @@ fn pretty_runtime_error<'b>(
|
||||||
|
|
||||||
title = MISSING_DEFINITION;
|
title = MISSING_DEFINITION;
|
||||||
}
|
}
|
||||||
|
RuntimeError::EmptySingleQuote(region) => {
|
||||||
|
let tip = alloc
|
||||||
|
.tip()
|
||||||
|
.append(alloc.reflow("Learn more about character literals at TODO"));
|
||||||
|
|
||||||
|
doc = alloc.stack(vec![
|
||||||
|
alloc.concat(vec![alloc.reflow("This character literal is empty.")]),
|
||||||
|
alloc.region(lines.convert_region(region)),
|
||||||
|
tip,
|
||||||
|
]);
|
||||||
|
|
||||||
|
title = SYNTAX_PROBLEM;
|
||||||
|
}
|
||||||
|
RuntimeError::MultipleCharsInSingleQuote(region) => {
|
||||||
|
let tip = alloc
|
||||||
|
.tip()
|
||||||
|
.append(alloc.reflow("Learn more about character literals at TODO"));
|
||||||
|
|
||||||
|
doc = alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This character literal contains more than one code point.")
|
||||||
|
]),
|
||||||
|
alloc.region(lines.convert_region(region)),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("Character literals can only contain one code point.")
|
||||||
|
]),
|
||||||
|
tip,
|
||||||
|
]);
|
||||||
|
|
||||||
|
title = SYNTAX_PROBLEM;
|
||||||
|
}
|
||||||
RuntimeError::OpaqueNotDefined {
|
RuntimeError::OpaqueNotDefined {
|
||||||
usage:
|
usage:
|
||||||
Loc {
|
Loc {
|
||||||
|
@ -1501,34 +1544,39 @@ fn not_found<'b>(
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a message informing the user that a module was referenced, but not found
|
||||||
|
///
|
||||||
|
/// See [`roc_problem::can::ModuleNotImported`]
|
||||||
fn module_not_found<'b>(
|
fn module_not_found<'b>(
|
||||||
alloc: &'b RocDocAllocator<'b>,
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
lines: &LineInfo,
|
lines: &LineInfo,
|
||||||
region: roc_region::all::Region,
|
region: roc_region::all::Region,
|
||||||
name: &ModuleName,
|
name: &ModuleName,
|
||||||
options: MutSet<Box<str>>,
|
options: MutSet<Box<str>>,
|
||||||
|
module_exists: bool,
|
||||||
) -> RocDocBuilder<'b> {
|
) -> RocDocBuilder<'b> {
|
||||||
let mut suggestions =
|
// If the module exists, sugguest that the user import it
|
||||||
suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect());
|
let details = if module_exists {
|
||||||
suggestions.truncate(4);
|
// TODO: Maybe give an example of how to do that
|
||||||
|
alloc.reflow("Did you mean to import it?")
|
||||||
|
} else {
|
||||||
|
// If the module might not exist, sugguest that it's a typo
|
||||||
|
let mut suggestions =
|
||||||
|
suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect());
|
||||||
|
suggestions.truncate(4);
|
||||||
|
|
||||||
let default_no = alloc.concat(vec![
|
|
||||||
alloc.reflow("Is there an "),
|
|
||||||
alloc.keyword("import"),
|
|
||||||
alloc.reflow(" or "),
|
|
||||||
alloc.keyword("exposing"),
|
|
||||||
alloc.reflow(" missing up-top"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let default_yes = alloc
|
|
||||||
.reflow("Is there an import missing? Perhaps there is a typo. Did you mean one of these?");
|
|
||||||
|
|
||||||
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
|
||||||
if suggestions.is_empty() {
|
if suggestions.is_empty() {
|
||||||
no_suggestion_details
|
// We don't have any recommended spelling corrections
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("Is there an "),
|
||||||
|
alloc.keyword("import"),
|
||||||
|
alloc.reflow(" or "),
|
||||||
|
alloc.keyword("exposing"),
|
||||||
|
alloc.reflow(" missing up-top"),
|
||||||
|
])
|
||||||
} else {
|
} else {
|
||||||
alloc.stack(vec![
|
alloc.stack(vec![
|
||||||
yes_suggestion_details,
|
alloc.reflow("Is there an import missing? Perhaps there is a typo. Did you mean one of these?"),
|
||||||
alloc
|
alloc
|
||||||
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
||||||
.indent(4),
|
.indent(4),
|
||||||
|
@ -1543,6 +1591,6 @@ fn module_not_found<'b>(
|
||||||
alloc.reflow("` module is not imported:"),
|
alloc.reflow("` module is not imported:"),
|
||||||
]),
|
]),
|
||||||
alloc.region(lines.convert_region(region)),
|
alloc.region(lines.convert_region(region)),
|
||||||
to_details(default_no, default_yes),
|
details,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1100,7 +1100,6 @@ fn format_category<'b>(
|
||||||
]),
|
]),
|
||||||
alloc.text(" produces:"),
|
alloc.text(" produces:"),
|
||||||
),
|
),
|
||||||
|
|
||||||
List => (
|
List => (
|
||||||
alloc.concat(vec![this_is, alloc.text(" a list")]),
|
alloc.concat(vec![this_is, alloc.text(" a list")]),
|
||||||
alloc.text(" of type:"),
|
alloc.text(" of type:"),
|
||||||
|
@ -1128,17 +1127,18 @@ fn format_category<'b>(
|
||||||
]),
|
]),
|
||||||
alloc.text(" which was of type:"),
|
alloc.text(" which was of type:"),
|
||||||
),
|
),
|
||||||
|
Character => (
|
||||||
|
alloc.concat(vec![this_is, alloc.text(" a character")]),
|
||||||
|
alloc.text(" of type:"),
|
||||||
|
),
|
||||||
Lambda => (
|
Lambda => (
|
||||||
alloc.concat(vec![this_is, alloc.text(" an anonymous function")]),
|
alloc.concat(vec![this_is, alloc.text(" an anonymous function")]),
|
||||||
alloc.text(" of type:"),
|
alloc.text(" of type:"),
|
||||||
),
|
),
|
||||||
|
|
||||||
ClosureSize => (
|
ClosureSize => (
|
||||||
alloc.concat(vec![this_is, alloc.text(" the closure size of a function")]),
|
alloc.concat(vec![this_is, alloc.text(" the closure size of a function")]),
|
||||||
alloc.text(" of type:"),
|
alloc.text(" of type:"),
|
||||||
),
|
),
|
||||||
|
|
||||||
TagApply {
|
TagApply {
|
||||||
tag_name: TagName::Global(name),
|
tag_name: TagName::Global(name),
|
||||||
args_count: 0,
|
args_count: 0,
|
||||||
|
@ -1472,6 +1472,7 @@ fn add_pattern_category<'b>(
|
||||||
Num => alloc.reflow(" numbers:"),
|
Num => alloc.reflow(" numbers:"),
|
||||||
Int => alloc.reflow(" integers:"),
|
Int => alloc.reflow(" integers:"),
|
||||||
Float => alloc.reflow(" floats:"),
|
Float => alloc.reflow(" floats:"),
|
||||||
|
Character => alloc.reflow(" characters:"),
|
||||||
};
|
};
|
||||||
|
|
||||||
alloc.concat(vec![i_am_trying_to_match, rest])
|
alloc.concat(vec![i_am_trying_to_match, rest])
|
||||||
|
|
|
@ -8274,4 +8274,35 @@ I need all branches in an `if` to have the same type!
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unimported_modules_reported() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
main : Task.Task {} []
|
||||||
|
main = "whatever man you don't even know my type"
|
||||||
|
main
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── MODULE NOT IMPORTED ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
The `Task` module is not imported:
|
||||||
|
|
||||||
|
1│ main : Task.Task {} []
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Is there an import missing? Perhaps there is a typo. Did you mean one
|
||||||
|
of these?
|
||||||
|
|
||||||
|
Test
|
||||||
|
List
|
||||||
|
Num
|
||||||
|
Set
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,4 +13,8 @@ indoc = "1.0.3"
|
||||||
pretty_assertions = "1.0.0"
|
pretty_assertions = "1.0.0"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
libc = "0.2"
|
libc = "0.2.106"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["platform"]
|
||||||
|
platform = []
|
||||||
|
|
|
@ -15,6 +15,7 @@ pub use roc_list::RocList;
|
||||||
pub use roc_str::RocStr;
|
pub use roc_str::RocStr;
|
||||||
|
|
||||||
// A list of C functions that are being imported
|
// A list of C functions that are being imported
|
||||||
|
#[cfg(feature = "platform")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn roc_alloc(size: usize, alignment: u32) -> *mut c_void;
|
pub fn roc_alloc(size: usize, alignment: u32) -> *mut c_void;
|
||||||
pub fn roc_realloc(
|
pub fn roc_realloc(
|
||||||
|
@ -26,6 +27,30 @@ extern "C" {
|
||||||
pub fn roc_dealloc(ptr: *mut c_void, alignment: u32);
|
pub fn roc_dealloc(ptr: *mut c_void, alignment: u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// This is only marked unsafe to typecheck without warnings in the rest of the code here.
|
||||||
|
#[cfg(not(feature = "platform"))]
|
||||||
|
pub unsafe extern "C" fn roc_alloc(_size: usize, _alignment: u32) -> *mut c_void {
|
||||||
|
unimplemented!("It is not valid to call roc alloc from within the compiler. Please use the \"platform\" feature if this is a platform.")
|
||||||
|
}
|
||||||
|
/// # Safety
|
||||||
|
/// This is only marked unsafe to typecheck without warnings in the rest of the code here.
|
||||||
|
#[cfg(not(feature = "platform"))]
|
||||||
|
pub unsafe extern "C" fn roc_realloc(
|
||||||
|
_ptr: *mut c_void,
|
||||||
|
_new_size: usize,
|
||||||
|
_old_size: usize,
|
||||||
|
_alignment: u32,
|
||||||
|
) -> *mut c_void {
|
||||||
|
unimplemented!("It is not valid to call roc realloc from within the compiler. Please use the \"platform\" feature if this is a platform.")
|
||||||
|
}
|
||||||
|
/// # Safety
|
||||||
|
/// This is only marked unsafe to typecheck without warnings in the rest of the code here.
|
||||||
|
#[cfg(not(feature = "platform"))]
|
||||||
|
pub unsafe extern "C" fn roc_dealloc(_ptr: *mut c_void, _alignment: u32) {
|
||||||
|
unimplemented!("It is not valid to call roc dealloc from within the compiler. Please use the \"platform\" feature if this is a platform.")
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum RocOrder {
|
pub enum RocOrder {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue