mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +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
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
### 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.
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
===========================================================
|
||||
|
||||
* 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 {
|
||||
whole_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)]
|
||||
fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
|
||||
let range_type = pool.get(range);
|
||||
|
|
|
@ -39,6 +39,7 @@ pub enum Pattern2 {
|
|||
IntLiteral(IntVal), // 16B
|
||||
FloatLiteral(FloatVal), // 16B
|
||||
StrLiteral(PoolStr), // 8B
|
||||
CharacterLiteral(char), // 4B
|
||||
Underscore, // 0B
|
||||
GlobalTag {
|
||||
whole_var: Variable, // 4B
|
||||
|
@ -249,6 +250,26 @@ pub fn to_pattern2<'a>(
|
|||
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) => {
|
||||
// Canonicalize the tag's name.
|
||||
Pattern2::GlobalTag {
|
||||
|
@ -506,6 +527,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
|
|||
| IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| StrLiteral(_)
|
||||
| CharacterLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| Shadowed { .. }
|
||||
|
@ -566,6 +588,7 @@ pub fn symbols_and_variables_from_pattern(
|
|||
| IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| StrLiteral(_)
|
||||
| CharacterLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| Shadowed { .. }
|
||||
|
|
|
@ -160,12 +160,17 @@ impl<'a> Env<'a> {
|
|||
})
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!(
|
||||
"Module {} exists, but is not recorded in dep_idents",
|
||||
module_name
|
||||
)
|
||||
}
|
||||
None => Err(RuntimeError::ModuleNotImported {
|
||||
module_name,
|
||||
imported_modules: self
|
||||
.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())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,21 +15,24 @@ test = false
|
|||
bench = false
|
||||
|
||||
[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"]
|
||||
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"]
|
||||
|
||||
run-wasm32 = ["wasmer", "wasmer-wasi"]
|
||||
|
||||
# Compiling for a different platform than the host can cause linker errors.
|
||||
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"]
|
||||
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
|
||||
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
|
||||
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
|
||||
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
|
||||
target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
|
||||
|
||||
target-all = [
|
||||
"target-aarch64",
|
||||
|
@ -50,15 +53,15 @@ roc_module = { path = "../compiler/module" }
|
|||
roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_mono = { path = "../compiler/mono" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_build = { path = "../compiler/build", default-features = false }
|
||||
roc_build = { path = "../compiler/build" }
|
||||
roc_fmt = { path = "../compiler/fmt" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
roc_editor = { path = "../editor", optional = true }
|
||||
roc_linker = { path = "../linker" }
|
||||
roc_repl_cli = { path = "../repl_cli" }
|
||||
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
roc_repl_cli = { path = "../repl_cli", optional = true }
|
||||
clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
const_format = "0.2.22"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
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::SpaceBefore(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::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,11 +63,17 @@ fn main() -> io::Result<()> {
|
|||
}
|
||||
}
|
||||
Some((CMD_REPL, _)) => {
|
||||
#[cfg(feature = "llvm")]
|
||||
{
|
||||
roc_repl_cli::main()?;
|
||||
|
||||
// Exit 0 if the repl exited normally
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "llvm"))]
|
||||
todo!("enable roc repl without llvm");
|
||||
}
|
||||
Some((CMD_EDIT, matches)) => {
|
||||
match matches
|
||||
.values_of_os(DIRECTORY_OR_FILES)
|
||||
|
|
|
@ -13,7 +13,8 @@ extern crate indoc;
|
|||
mod cli_run {
|
||||
use cli_utils::helpers::{
|
||||
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 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(
|
||||
file: &Path,
|
||||
stdin: &[&str],
|
||||
|
@ -96,12 +108,7 @@ mod cli_run {
|
|||
all_flags.extend_from_slice(&["--valgrind"]);
|
||||
}
|
||||
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_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);
|
||||
build_example(file, &all_flags[..]);
|
||||
|
||||
let out = if use_valgrind && ALLOW_VALGRIND {
|
||||
let (valgrind_out, raw_xml) = if let Some(input_file) = input_file {
|
||||
|
@ -238,6 +245,17 @@ mod cli_run {
|
|||
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",
|
||||
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 {
|
||||
filename: "Quicksort.roc",
|
||||
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"
|
||||
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
|
@ -2504,6 +2510,7 @@ dependencies = [
|
|||
name = "roc_builtins"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
|
@ -2518,6 +2525,7 @@ dependencies = [
|
|||
"bumpalo",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_problem",
|
||||
|
@ -2587,6 +2595,7 @@ dependencies = [
|
|||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
|
@ -2687,6 +2696,7 @@ dependencies = [
|
|||
"roc_mono",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
|
@ -2760,6 +2770,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
|
|
|
@ -3,3 +3,4 @@ pub mod markup;
|
|||
pub mod markup_error;
|
||||
pub mod slow_pool;
|
||||
pub mod syntax_highlight;
|
||||
pub mod underline_style;
|
||||
|
|
|
@ -55,8 +55,13 @@ pub enum Attribute {
|
|||
HighlightStart { highlight_start: HighlightStart },
|
||||
HighlightEnd { highlight_end: HighlightEnd },
|
||||
|
||||
UnderlineStart { underline_start: UnderlineStart },
|
||||
UnderlineEnd { underline_end: UnderlineEnd },
|
||||
Underline { underline_spec: UnderlineSpec },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UnderlineSpec {
|
||||
Partial { start: usize, end: usize },
|
||||
Full,
|
||||
}
|
||||
|
||||
#[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_dev = { path = "../gen_dev", default-features = false }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_std = { path = "../../roc_std", default-features = false }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
libloading = "0.7.1"
|
||||
tempfile = "3.2.0"
|
||||
|
@ -35,7 +35,6 @@ target-lexicon = "0.12.2"
|
|||
serde_json = "1.0.69"
|
||||
|
||||
[features]
|
||||
default = ["llvm", "target-aarch64", "target-x86_64", "target-wasm32"]
|
||||
target-arm = []
|
||||
target-aarch64 = ["roc_gen_dev/target-aarch64"]
|
||||
target-x86 = []
|
||||
|
|
|
@ -835,8 +835,6 @@ fn link_linux(
|
|||
|
||||
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
||||
|
||||
init_arch(target);
|
||||
|
||||
// NOTE: order of arguments to `ld` matters here!
|
||||
// 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,
|
||||
) -> CodeGenTiming {
|
||||
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");
|
||||
}
|
||||
OptLevel::Normal | OptLevel::Development => {
|
||||
|
|
|
@ -130,7 +130,9 @@ fn make_apply_symbol(
|
|||
// it was imported but it doesn't expose this ident.
|
||||
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(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| SingleQuote(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_)
|
||||
|
|
|
@ -124,12 +124,17 @@ impl<'a> Env<'a> {
|
|||
})
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!(
|
||||
"Module {} exists, but is not recorded in dep_idents",
|
||||
module_name
|
||||
)
|
||||
}
|
||||
None => Err(RuntimeError::ModuleNotImported {
|
||||
module_name,
|
||||
imported_modules: self
|
||||
.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())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ pub enum Expr {
|
|||
Int(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||
Float(Variable, Variable, Box<str>, f64, FloatBound),
|
||||
Str(Box<str>),
|
||||
SingleQuote(char),
|
||||
List {
|
||||
elem_var: Variable,
|
||||
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::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) => {
|
||||
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 @ Float(..)
|
||||
| other @ Str { .. }
|
||||
| other @ SingleQuote(_)
|
||||
| other @ RuntimeError(_)
|
||||
| other @ EmptyRecord
|
||||
| other @ Accessor { .. }
|
||||
|
|
|
@ -572,6 +572,7 @@ fn fix_values_captured_in_closure_pattern(
|
|||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| SingleQuote(_)
|
||||
| Underscore
|
||||
| Shadowed(..)
|
||||
| MalformedPattern(_, _)
|
||||
|
@ -629,6 +630,7 @@ fn fix_values_captured_in_closure_expr(
|
|||
| Int(..)
|
||||
| Float(..)
|
||||
| Str(_)
|
||||
| SingleQuote(_)
|
||||
| Var(_)
|
||||
| EmptyRecord
|
||||
| RuntimeError(_)
|
||||
|
|
|
@ -126,6 +126,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
| Num(..)
|
||||
| NonBase10Int { .. }
|
||||
| Str(_)
|
||||
| SingleQuote(_)
|
||||
| AccessorFunction(_)
|
||||
| Var { .. }
|
||||
| Underscore { .. }
|
||||
|
|
|
@ -39,6 +39,7 @@ pub enum Pattern {
|
|||
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
|
||||
StrLiteral(Box<str>),
|
||||
SingleQuote(char),
|
||||
Underscore,
|
||||
|
||||
// Runtime Exceptions
|
||||
|
@ -108,6 +109,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
|||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| SingleQuote(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_)
|
||||
|
@ -309,6 +311,23 @@ pub fn canonicalize_pattern<'a>(
|
|||
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, _) => {
|
||||
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
|
||||
}
|
||||
|
@ -560,6 +579,7 @@ fn add_bindings_from_patterns(
|
|||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| SingleQuote(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| 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)]
|
||||
pub fn num_binary64() -> Type {
|
||||
let alias_content = Type::TagUnion(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 roc_can::annotation::IntroducedVariables;
|
||||
|
@ -213,6 +213,7 @@ pub fn constrain_expr(
|
|||
exists(vars, And(cons))
|
||||
}
|
||||
Str(_) => Eq(str_type(), expected, Category::Str, region),
|
||||
SingleQuote(_) => Eq(num_u32(), expected, Category::Character, region),
|
||||
List {
|
||||
elem_var,
|
||||
loc_elems,
|
||||
|
|
|
@ -60,6 +60,7 @@ fn headers_from_annotation_help(
|
|||
| NumLiteral(..)
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| SingleQuote(_)
|
||||
| StrLiteral(_) => true,
|
||||
|
||||
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 {
|
||||
whole_var,
|
||||
ext_var,
|
||||
|
|
|
@ -49,7 +49,6 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
|||
);
|
||||
buf.newline();
|
||||
buf.indent(braces_indent);
|
||||
buf.push(end);
|
||||
} else {
|
||||
// is_multiline == false
|
||||
// there is no comment to add
|
||||
|
@ -67,7 +66,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
|||
if !items.is_empty() {
|
||||
buf.spaces(1);
|
||||
}
|
||||
}
|
||||
|
||||
buf.push(end);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ impl<'a> Formattable for Expr<'a> {
|
|||
Float(..)
|
||||
| Num(..)
|
||||
| NonBase10Int { .. }
|
||||
| SingleQuote(_)
|
||||
| Access(_, _)
|
||||
| AccessorFunction(_)
|
||||
| Var { .. }
|
||||
|
@ -209,6 +210,11 @@ impl<'a> Formattable for Expr<'a> {
|
|||
buf.indent(indent);
|
||||
buf.push_str(string)
|
||||
}
|
||||
SingleQuote(string) => {
|
||||
buf.push('\'');
|
||||
buf.push_str(string);
|
||||
buf.push('\'');
|
||||
}
|
||||
&NonBase10Int {
|
||||
base,
|
||||
string,
|
||||
|
|
|
@ -36,6 +36,7 @@ impl<'a> Formattable for Pattern<'a> {
|
|||
| Pattern::NonBase10Literal { .. }
|
||||
| Pattern::FloatLiteral(..)
|
||||
| Pattern::StrLiteral(_)
|
||||
| Pattern::SingleQuote(_)
|
||||
| Pattern::Underscore(_)
|
||||
| Pattern::Malformed(_)
|
||||
| Pattern::MalformedIdent(_, _)
|
||||
|
@ -147,6 +148,11 @@ impl<'a> Formattable for Pattern<'a> {
|
|||
StrLiteral(literal) => {
|
||||
todo!("Format string literal: {:?}", literal);
|
||||
}
|
||||
SingleQuote(string) => {
|
||||
buf.push('\'');
|
||||
buf.push_str(string);
|
||||
buf.push('\'');
|
||||
}
|
||||
Underscore(name) => {
|
||||
buf.indent(indent);
|
||||
buf.push('_');
|
||||
|
|
|
@ -30,7 +30,7 @@ packed_struct = "0.10.0"
|
|||
[dev-dependencies]
|
||||
roc_can = { path = "../can" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_std = { path = "../../roc_std", default-features = false }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
||||
[features]
|
||||
|
|
|
@ -13,7 +13,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_std = { path = "../../roc_std", default-features = false }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
inkwell = { path = "../../vendor/inkwell" }
|
||||
|
|
|
@ -3619,7 +3619,12 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
|
|||
_ => (¶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 arg_type = arg.get_type();
|
||||
|
|
|
@ -12,5 +12,5 @@ roc_collections = { path = "../collections" }
|
|||
roc_module = { path = "../module" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_std = { path = "../../roc_std", default-features = false }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
use core::cmp::Ordering;
|
||||
use core::convert::From;
|
||||
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.
|
||||
/// 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> {
|
||||
if index < self.len() {
|
||||
Some(unsafe {
|
||||
let raw = if self.is_small_str() {
|
||||
self.get_small_str_ptr().add(index)
|
||||
} else {
|
||||
self.elements.add(index)
|
||||
};
|
||||
|
||||
&*raw
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.as_bytes().get(index)
|
||||
}
|
||||
|
||||
pub fn get_bytes(&self) -> *const u8 {
|
||||
|
@ -93,59 +82,38 @@ impl IdentStr {
|
|||
(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();
|
||||
|
||||
match len.cmp(&mem::size_of::<Self>()) {
|
||||
Ordering::Less => {
|
||||
// This fits in a small string, but needs its length recorded
|
||||
let mut answer_bytes: [u8; mem::size_of::<Self>()] = unsafe {
|
||||
mem::transmute::<Self, [u8; mem::size_of::<Self>()]>(Self::default())
|
||||
};
|
||||
let mut bytes = [0; mem::size_of::<Self>()];
|
||||
|
||||
// Copy the bytes from the slice into the answer
|
||||
let dest_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) };
|
||||
// Copy the bytes from the slice into bytes.
|
||||
bytes[..len].copy_from_slice(slice);
|
||||
|
||||
// Write length and small string bit to last byte of length.
|
||||
{
|
||||
let mut bytes = answer.length.to_ne_bytes();
|
||||
bytes[mem::size_of::<usize>() * 2 - 1] = u8::MAX - len as u8;
|
||||
|
||||
bytes[mem::size_of::<usize>() - 1] = u8::MAX - len as u8;
|
||||
|
||||
answer.length = usize::from_ne_bytes(bytes);
|
||||
}
|
||||
|
||||
answer
|
||||
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(bytes) }
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// This fits in a small string, and is exactly long enough to
|
||||
// take up the entire available struct
|
||||
let mut answer_bytes: [u8; mem::size_of::<Self>()] = unsafe {
|
||||
mem::transmute::<Self, [u8; mem::size_of::<Self>()]>(Self::default())
|
||||
};
|
||||
let mut bytes = [0; mem::size_of::<Self>()];
|
||||
|
||||
// Copy the bytes from the slice into the answer
|
||||
let dest_slice = unsafe {
|
||||
slice::from_raw_parts_mut(&mut answer_bytes as *mut u8, mem::size_of::<Self>())
|
||||
};
|
||||
bytes.copy_from_slice(slice);
|
||||
|
||||
dest_slice.copy_from_slice(slice);
|
||||
|
||||
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(answer_bytes) }
|
||||
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(bytes) }
|
||||
}
|
||||
Ordering::Greater => {
|
||||
// This needs a big string
|
||||
let elements = unsafe {
|
||||
let align = mem::align_of::<u8>();
|
||||
let elements = unsafe {
|
||||
let layout = Layout::from_size_align_unchecked(len, align);
|
||||
|
||||
System.alloc(layout)
|
||||
alloc(layout)
|
||||
};
|
||||
|
||||
// Turn the new elements into a slice, and copy the existing
|
||||
|
@ -167,7 +135,9 @@ impl IdentStr {
|
|||
pub fn as_slice(&self) -> &[u8] {
|
||||
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()) }
|
||||
} else {
|
||||
unsafe { from_raw_parts(self.elements, self.length) }
|
||||
|
@ -186,15 +156,12 @@ impl IdentStr {
|
|||
/// # Safety
|
||||
/// 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!
|
||||
pub unsafe fn write_c_str(&self, buf: *mut char) {
|
||||
if self.is_small_str() {
|
||||
ptr::copy_nonoverlapping(self.get_small_str_ptr(), buf as *mut u8, self.len());
|
||||
} else {
|
||||
ptr::copy_nonoverlapping(self.elements, buf as *mut u8, self.len());
|
||||
}
|
||||
pub unsafe fn write_c_str(&self, buf: *mut c_char) {
|
||||
let bytes = self.as_bytes();
|
||||
ptr::copy_nonoverlapping(bytes.as_ptr().cast(), buf, bytes.len());
|
||||
|
||||
// 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 {
|
||||
fn from(str: &str) -> Self {
|
||||
Self::from_slice(str.as_bytes())
|
||||
Self::from_str(str)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for IdentStr {
|
||||
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 {
|
||||
fn clone(&self) -> Self {
|
||||
if self.is_small_str() || self.is_empty() {
|
||||
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,
|
||||
}
|
||||
}
|
||||
Self::from_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IdentStr {
|
||||
fn drop(&mut self) {
|
||||
if !self.is_empty() && !self.is_small_str() {
|
||||
unsafe {
|
||||
let align = mem::align_of::<u8>();
|
||||
unsafe {
|
||||
let layout = Layout::from_size_align_unchecked(self.length, align);
|
||||
|
||||
System.dealloc(self.elements as *mut _, layout);
|
||||
dealloc(self.elements as *mut _, layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ roc_reporting = { path = "../../reporting" }
|
|||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
parking_lot = { version = "0.11.2" }
|
||||
parking_lot = "0.11.2"
|
||||
crossbeam = "0.8.1"
|
||||
num_cpus = "1.13.0"
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ roc_types = { path = "../types" }
|
|||
roc_can = { path = "../can" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_std = { path = "../../roc_std", default-features = false }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
|
|
|
@ -2040,8 +2040,11 @@ fn pattern_to_when<'a>(
|
|||
}
|
||||
|
||||
UnwrappedOpaque { .. } => todo_opaques!(),
|
||||
|
||||
IntLiteral(..) | NumLiteral(..) | FloatLiteral(..) | StrLiteral(_) => {
|
||||
IntLiteral(..)
|
||||
| NumLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(..)
|
||||
| roc_can::pattern::Pattern::SingleQuote(..) => {
|
||||
// These patters are refutable, and thus should never occur outside a `when` expression
|
||||
// They should have been replaced with `UnsupportedPattern` during canonicalization
|
||||
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
|
||||
|
@ -3147,6 +3150,13 @@ pub fn with_hole<'a>(
|
|||
hole,
|
||||
),
|
||||
|
||||
SingleQuote(character) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Int(character as _)),
|
||||
Layout::int_width(IntWidth::I32),
|
||||
hole,
|
||||
),
|
||||
|
||||
Num(var, num_str, num, _bound) => {
|
||||
// first figure out what kind of number this is
|
||||
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 {
|
||||
debug_assert!(
|
||||
!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!(
|
||||
|
@ -7745,6 +7756,7 @@ fn from_can_pattern_help<'a>(
|
|||
}
|
||||
}
|
||||
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
|
||||
SingleQuote(c) => Ok(Pattern::IntLiteral(*c as _, IntWidth::I32)),
|
||||
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
|
||||
original_region: *region,
|
||||
shadow: ident.clone(),
|
||||
|
|
|
@ -16,7 +16,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
|||
encode_unicode = "0.3.6"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
criterion = { version = "0.3.5", features = ["html_reports"] }
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
quickcheck = "1.0.3"
|
||||
|
|
|
@ -152,6 +152,8 @@ pub enum Expr<'a> {
|
|||
Access(&'a Expr<'a>, &'a str),
|
||||
/// e.g. `.foo`
|
||||
AccessorFunction(&'a str),
|
||||
/// eg 'b'
|
||||
SingleQuote(&'a str),
|
||||
|
||||
// Collection Literals
|
||||
List(Collection<'a, &'a Loc<Expr<'a>>>),
|
||||
|
@ -462,6 +464,7 @@ pub enum Pattern<'a> {
|
|||
FloatLiteral(&'a str),
|
||||
StrLiteral(StrLiteral<'a>),
|
||||
Underscore(&'a str),
|
||||
SingleQuote(&'a str),
|
||||
|
||||
// Space
|
||||
SpaceBefore(&'a Pattern<'a>, &'a [CommentOrNewline<'a>]),
|
||||
|
|
|
@ -195,6 +195,7 @@ fn parse_loc_term_or_underscore<'a>(
|
|||
one_of!(
|
||||
loc_expr_in_parens_etc_help(min_indent),
|
||||
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::Lambda, closure_help(min_indent, options))),
|
||||
loc!(underscore_expression()),
|
||||
|
@ -217,6 +218,7 @@ fn parse_loc_term<'a>(
|
|||
one_of!(
|
||||
loc_expr_in_parens_etc_help(min_indent),
|
||||
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::Lambda, closure_help(min_indent, options))),
|
||||
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::Str(string) => Ok(Pattern::StrLiteral(*string)),
|
||||
Expr::SingleQuote(string) => Ok(Pattern::SingleQuote(*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)
|
||||
}
|
||||
|
||||
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> {
|
||||
map!(
|
||||
crate::number_literal::positive_number_literal(),
|
||||
|
|
|
@ -355,6 +355,7 @@ pub enum EExpr<'a> {
|
|||
InParens(EInParens<'a>, Position),
|
||||
Record(ERecord<'a>, Position),
|
||||
Str(EString<'a>, Position),
|
||||
SingleQuote(EString<'a>, Position),
|
||||
Number(ENumber, 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!(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)
|
||||
)),
|
||||
loc!(string_pattern_help()),
|
||||
loc!(single_quote_pattern_help()),
|
||||
loc!(number_pattern_help())
|
||||
)
|
||||
.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>(
|
||||
min_indent: u32,
|
||||
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>> {
|
||||
use StrLiteral::*;
|
||||
|
||||
|
|
|
@ -507,19 +507,28 @@ mod test_parse {
|
|||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn all_f64_values_parse(num: f64) {
|
||||
let string = num.to_string();
|
||||
if string.contains('.') {
|
||||
assert_parses_to(&string, Float(&string));
|
||||
} 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));
|
||||
fn all_f64_values_parse(mut num: f64) {
|
||||
// NaN, Infinity, -Infinity (these would all parse as tags in Roc)
|
||||
if !num.is_finite() {
|
||||
num = 0.0;
|
||||
}
|
||||
|
||||
// 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]
|
||||
// fn type_signature_def() {
|
||||
// let arena = Bump::new();
|
||||
|
|
|
@ -171,10 +171,37 @@ pub enum RuntimeError {
|
|||
region: Region,
|
||||
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 {
|
||||
/// The name of the module that was referenced
|
||||
module_name: ModuleName,
|
||||
/// A list of modules which *have* been imported
|
||||
imported_modules: MutSet<Box<str>>,
|
||||
/// Where the problem occurred
|
||||
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),
|
||||
MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region),
|
||||
|
@ -203,6 +230,11 @@ pub enum RuntimeError {
|
|||
VoidValue,
|
||||
|
||||
ExposedButNotDefined(Symbol),
|
||||
|
||||
/// where ''
|
||||
EmptySingleQuote(Region),
|
||||
/// where 'aa'
|
||||
MultipleCharsInSingleQuote(Region),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
@ -213,4 +245,6 @@ pub enum MalformedPatternProblem {
|
|||
Unknown,
|
||||
QualifiedIdentifier,
|
||||
BadIdent(roc_parse::ident::BadIdent),
|
||||
EmptySingleQuote,
|
||||
MultipleCharsInSingleQuote,
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ roc_reporting = { path = "../../reporting" }
|
|||
roc_load = { path = "../load" }
|
||||
roc_can = { path = "../can" }
|
||||
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_std = { path = "../../roc_std" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
@ -52,7 +52,7 @@ wasmer = { version = "2.0.0", default-features = false, features = ["default-cra
|
|||
|
||||
[features]
|
||||
default = ["gen-llvm"]
|
||||
gen-llvm = []
|
||||
gen-llvm = ["roc_build/llvm"]
|
||||
gen-dev = []
|
||||
gen-wasm = []
|
||||
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]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn dec_float_alias() {
|
||||
|
|
|
@ -1862,6 +1862,13 @@ impl UnionTags {
|
|||
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 {
|
||||
Self::from_slices(
|
||||
SubsSlice::new(index.index, 1),
|
||||
|
|
|
@ -1304,6 +1304,7 @@ pub enum Category {
|
|||
Num,
|
||||
List,
|
||||
Str,
|
||||
Character,
|
||||
|
||||
// records
|
||||
Record,
|
||||
|
@ -1325,6 +1326,7 @@ pub enum PatternCategory {
|
|||
Num,
|
||||
Int,
|
||||
Float,
|
||||
Character,
|
||||
}
|
||||
|
||||
#[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::text as gr_text;
|
||||
use cgmath::Vector2;
|
||||
use roc_code_markup::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
|
||||
use roc_code_markup::slow_pool::{MarkNodeId, SlowPool};
|
||||
use roc_code_markup::syntax_highlight::HighlightStyle;
|
||||
use roc_code_markup::{
|
||||
markup::{
|
||||
attribute::Attribute,
|
||||
nodes::{MarkupNode, BLANK_PLACEHOLDER},
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
underline_style::UnderlineStyle,
|
||||
};
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
use crate::{editor::config::Config, graphics::colors};
|
||||
|
@ -94,6 +100,9 @@ fn markup_to_wgpu_helper<'a>(
|
|||
txt_row_col: &mut (usize, usize),
|
||||
mark_node_pool: &'a SlowPool,
|
||||
) -> EdResult<()> {
|
||||
let char_width = code_style.glyph_dim_rect.width;
|
||||
let char_height = code_style.glyph_dim_rect.height;
|
||||
|
||||
match markup_node {
|
||||
MarkupNode::Nested {
|
||||
ast_node_id: _,
|
||||
|
@ -124,7 +133,7 @@ fn markup_to_wgpu_helper<'a>(
|
|||
content,
|
||||
ast_node_id: _,
|
||||
syn_high_style,
|
||||
attributes: _,
|
||||
attributes,
|
||||
parent_id_opt: _,
|
||||
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 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_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();
|
||||
|
||||
for _ in 0..*newlines_at_end {
|
||||
|
@ -160,9 +197,6 @@ fn markup_to_wgpu_helper<'a>(
|
|||
let highlight_color =
|
||||
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 {
|
||||
top_left_coords: (
|
||||
code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width,
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
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 std::collections::HashMap;
|
||||
|
||||
|
@ -12,6 +15,7 @@ pub struct EdTheme {
|
|||
pub subtle_text: RgbaTup,
|
||||
pub syntax_high_map: HashMap<HighlightStyle, RgbaTup>,
|
||||
pub ui_theme: UITheme,
|
||||
pub underline_color_map: HashMap<UnderlineStyle, RgbaTup>,
|
||||
}
|
||||
|
||||
impl Default for EdTheme {
|
||||
|
@ -21,6 +25,7 @@ impl Default for EdTheme {
|
|||
subtle_text: from_hsb(240, 5, 60),
|
||||
syntax_high_map: default_highlight_map(),
|
||||
ui_theme: UITheme::default(),
|
||||
underline_color_map: default_underline_color_map(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
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)]
|
||||
#[repr(C)]
|
||||
pub struct Rect {
|
||||
pub color: (f32, f32, f32, f32),
|
||||
pub height: f32,
|
||||
pub top_left_coords: Vector2<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]
|
||||
roc_mono = { path = "../compiler/mono" }
|
||||
roc_build = { path = "../compiler/build", default-features = false }
|
||||
roc_build = { path = "../compiler/build" }
|
||||
roc_collections = { path = "../compiler/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"] }
|
||||
memmap2 = "0.5.0"
|
||||
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
|
||||
|
||||
[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]
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
const_format = "0.2.22"
|
||||
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-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
|
||||
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_gen_llvm = {path = "../compiler/gen_llvm"}
|
||||
roc_load = {path = "../compiler/load"}
|
||||
|
|
|
@ -5,11 +5,11 @@ use std::cmp::{max_by_key, min_by_key};
|
|||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::MutMap;
|
||||
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_mono::ir::ProcLayout;
|
||||
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_region::all::{Loc, Region};
|
||||
|
@ -70,6 +70,7 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NewtypeKind<'a> {
|
||||
Tag(&'a TagName),
|
||||
RecordField(&'a str),
|
||||
|
@ -89,10 +90,11 @@ fn unroll_newtypes<'a>(
|
|||
mut content: &'a Content,
|
||||
) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) {
|
||||
let mut newtype_containers = Vec::with_capacity_in(1, env.arena);
|
||||
let mut force_alias_content = None;
|
||||
loop {
|
||||
match content {
|
||||
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
|
||||
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
|
||||
|
@ -113,7 +115,20 @@ fn unroll_newtypes<'a>(
|
|||
let field_var = *field.as_inner();
|
||||
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, .. } => {
|
||||
let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content {
|
||||
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, _)) => {
|
||||
debug_assert_eq!(tags.len(), 1);
|
||||
|
||||
|
@ -518,7 +529,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
|||
}
|
||||
(_, Layout::Struct{field_layouts, ..}) => match content {
|
||||
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, _)) => {
|
||||
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, &[])
|
||||
}
|
||||
Content::Structure(FlatType::EmptyRecord) => {
|
||||
struct_to_ast(env, mem, addr, &[], RecordFields::empty())
|
||||
struct_to_ast(env, mem, addr, RecordFields::empty())
|
||||
}
|
||||
other => {
|
||||
unreachable!(
|
||||
|
@ -841,30 +852,46 @@ fn struct_to_ast<'a, M: ReplAppMemory>(
|
|||
env: &Env<'a, 'a>,
|
||||
mem: &'a M,
|
||||
addr: usize,
|
||||
field_layouts: &'a [Layout<'a>],
|
||||
record_fields: RecordFields,
|
||||
) -> Expr<'a> {
|
||||
let arena = env.arena;
|
||||
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(
|
||||
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 {
|
||||
// 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 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 {
|
||||
value: addr_to_ast(
|
||||
env,
|
||||
mem,
|
||||
addr,
|
||||
&Layout::struct_no_name_order(field_layouts),
|
||||
&Layout::struct_no_name_order(inner_layouts),
|
||||
WhenRecursive::Unreachable,
|
||||
inner_content,
|
||||
),
|
||||
|
@ -880,19 +907,20 @@ fn struct_to_ast<'a, M: ReplAppMemory>(
|
|||
region: Region::zero(),
|
||||
};
|
||||
|
||||
let output = env.arena.alloc([loc_field]);
|
||||
let output = arena.alloc([loc_field]);
|
||||
|
||||
Expr::Record(Collection::with_items(output))
|
||||
} 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
|
||||
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 content = subs.get_content_without_compacting(var);
|
||||
let field_layout = field_to_layout.get(&label).unwrap();
|
||||
|
||||
let loc_expr = &*arena.alloc(Loc {
|
||||
value: addr_to_ast(
|
||||
|
|
|
@ -840,7 +840,7 @@ fn function_in_list() {
|
|||
fn function_in_record() {
|
||||
expect_success(
|
||||
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 * }"#,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -945,13 +945,17 @@ fn pretty_runtime_error<'b>(
|
|||
}
|
||||
Unknown => " ",
|
||||
QualifiedIdentifier => " qualified ",
|
||||
EmptySingleQuote => " empty character literal ",
|
||||
MultipleCharsInSingleQuote => " overfull literal ",
|
||||
};
|
||||
|
||||
let tip = match problem {
|
||||
MalformedInt | MalformedFloat | MalformedBase(_) => alloc
|
||||
.tip()
|
||||
.append(alloc.reflow("Learn more about number literals at TODO")),
|
||||
Unknown | BadIdent(_) => alloc.nil(),
|
||||
EmptySingleQuote | MultipleCharsInSingleQuote | Unknown | BadIdent(_) => {
|
||||
alloc.nil()
|
||||
}
|
||||
QualifiedIdentifier => alloc.tip().append(
|
||||
alloc.reflow("In patterns, only private and global tags can be qualified"),
|
||||
),
|
||||
|
@ -1015,8 +1019,16 @@ fn pretty_runtime_error<'b>(
|
|||
module_name,
|
||||
imported_modules,
|
||||
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;
|
||||
}
|
||||
|
@ -1333,6 +1345,37 @@ fn pretty_runtime_error<'b>(
|
|||
|
||||
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 {
|
||||
usage:
|
||||
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>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
lines: &LineInfo,
|
||||
region: roc_region::all::Region,
|
||||
name: &ModuleName,
|
||||
options: MutSet<Box<str>>,
|
||||
module_exists: bool,
|
||||
) -> RocDocBuilder<'b> {
|
||||
// If the module exists, sugguest that the user import it
|
||||
let details = if module_exists {
|
||||
// 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![
|
||||
if suggestions.is_empty() {
|
||||
// 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"),
|
||||
]);
|
||||
|
||||
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() {
|
||||
no_suggestion_details
|
||||
])
|
||||
} else {
|
||||
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
|
||||
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
||||
.indent(4),
|
||||
|
@ -1543,6 +1591,6 @@ fn module_not_found<'b>(
|
|||
alloc.reflow("` module is not imported:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region)),
|
||||
to_details(default_no, default_yes),
|
||||
details,
|
||||
])
|
||||
}
|
||||
|
|
|
@ -1100,7 +1100,6 @@ fn format_category<'b>(
|
|||
]),
|
||||
alloc.text(" produces:"),
|
||||
),
|
||||
|
||||
List => (
|
||||
alloc.concat(vec![this_is, alloc.text(" a list")]),
|
||||
alloc.text(" of type:"),
|
||||
|
@ -1128,17 +1127,18 @@ fn format_category<'b>(
|
|||
]),
|
||||
alloc.text(" which was of type:"),
|
||||
),
|
||||
|
||||
Character => (
|
||||
alloc.concat(vec![this_is, alloc.text(" a character")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
Lambda => (
|
||||
alloc.concat(vec![this_is, alloc.text(" an anonymous function")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
|
||||
ClosureSize => (
|
||||
alloc.concat(vec![this_is, alloc.text(" the closure size of a function")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
|
||||
TagApply {
|
||||
tag_name: TagName::Global(name),
|
||||
args_count: 0,
|
||||
|
@ -1472,6 +1472,7 @@ fn add_pattern_category<'b>(
|
|||
Num => alloc.reflow(" numbers:"),
|
||||
Int => alloc.reflow(" integers:"),
|
||||
Float => alloc.reflow(" floats:"),
|
||||
Character => alloc.reflow(" characters:"),
|
||||
};
|
||||
|
||||
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"
|
||||
quickcheck = "1.0.3"
|
||||
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;
|
||||
|
||||
// A list of C functions that are being imported
|
||||
#[cfg(feature = "platform")]
|
||||
extern "C" {
|
||||
pub fn roc_alloc(size: usize, alignment: u32) -> *mut c_void;
|
||||
pub fn roc_realloc(
|
||||
|
@ -26,6 +27,30 @@ extern "C" {
|
|||
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)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum RocOrder {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue