Merge remote-tracking branch 'origin/trunk' into list-replace

This commit is contained in:
Brendan Hansknecht 2022-02-27 00:28:08 -08:00
commit b802d681a3
90 changed files with 5667 additions and 313 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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);

View file

@ -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 { .. }

View file

@ -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,
}),
}
}

View file

@ -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 }

View file

@ -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),
}
}
}

View file

@ -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)

View file

@ -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
View file

@ -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",

View file

@ -3,3 +3,4 @@ pub mod markup;
pub mod markup_error;
pub mod slow_pool;
pub mod syntax_highlight;
pub mod underline_style;

View file

@ -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)]

View 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
}

View file

@ -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 = []

View file

@ -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");
}

View file

@ -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 => {

View file

@ -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))
}
}
}

View file

@ -854,6 +854,7 @@ fn pattern_to_vars_by_symbol(
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| SingleQuote(_)
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_)

View file

@ -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,
}),
}
}

View file

@ -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 { .. }

View file

@ -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(_)

View file

@ -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 { .. }

View file

@ -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(_)

View file

@ -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(

View file

@ -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,

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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('_');

View file

@ -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]

View file

@ -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" }

View file

@ -3619,7 +3619,12 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
_ => (&params[..], &param_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();

View file

@ -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" }

View file

@ -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);
}
}
}

View file

@ -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"

View file

@ -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" }

View file

@ -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(),

View file

@ -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"

View file

@ -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>]),

View file

@ -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(),

View file

@ -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),

View file

@ -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,

View file

@ -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::*;

View file

@ -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();

View file

@ -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,
}

View file

@ -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 = []

View file

@ -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() {

View file

@ -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),

View file

@ -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)]

View file

@ -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,

View file

@ -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(),
}
}
}

View file

@ -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
View file

@ -0,0 +1 @@
hello-gui

23
examples/gui/Hello.roc Normal file
View 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 },
]

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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

View file

@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-lib=dylib=app");
println!("cargo:rustc-link-search=.");
}

View file

@ -0,0 +1,3 @@
extern int rust_main();
int main() { return rust_main(); }

View 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)
}
}

View 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,
}
}

View file

@ -0,0 +1,5 @@
pub mod buffer;
pub mod ortho;
pub mod pipelines;
pub mod vertex;
pub mod quad;

View 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,
}
}

View 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,
})
}

View 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,
),
};
}

View 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,
},
],
};
}

View file

@ -0,0 +1,4 @@
pub mod colors;
pub mod lowlevel;
pub mod primitives;
pub mod style;

View file

@ -0,0 +1,2 @@
pub mod rect;
pub mod text;

View 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,
}

View 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))
}

View 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;
}

View file

@ -0,0 +1 @@
pub const DEFAULT_FONT_SIZE: f32 = 30.0;

View 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),
)
}

View 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
}

View file

@ -0,0 +1,3 @@
fn main() {
std::process::exit(host::rust_main());
}

View 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);
}
}

View 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>,
}

View file

@ -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"] }

View file

@ -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"}

View file

@ -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(

View file

@ -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 * }"#,
)
}

View file

@ -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,
])
}

View file

@ -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])

View file

@ -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
"#
),
)
}
}

View file

@ -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 = []

View file

@ -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 {