mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
Merge remote-tracking branch 'origin/trunk' into recursive-layouts
This commit is contained in:
commit
f857203673
64 changed files with 5025 additions and 2214 deletions
|
@ -7,14 +7,36 @@ To build the compiler, you need a particular version of LLVM installed on your s
|
||||||
|
|
||||||
To see which version of LLVM you need, take a look at `Cargo.toml`, in particular the `branch` section of the `inkwell` dependency. It should have something like `llvmX-Y` where X and Y are the major and minor revisions of LLVM you need.
|
To see which version of LLVM you need, take a look at `Cargo.toml`, in particular the `branch` section of the `inkwell` dependency. It should have something like `llvmX-Y` where X and Y are the major and minor revisions of LLVM you need.
|
||||||
|
|
||||||
For Ubuntu, I used the `Automatic installation script` at [apt.llvm.org](https://apt.llvm.org) - but there are plenty of alternative options at http://releases.llvm.org/download.html
|
For Ubuntu and Debian, you can use the `Automatic installation script` at [apt.llvm.org](https://apt.llvm.org):
|
||||||
|
```
|
||||||
|
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
|
||||||
|
```
|
||||||
|
|
||||||
### Troubleshooting LLVM installation on Linux
|
For macOS, you can run `brew install llvm` (but before you do so, check the version with `brew info llvm`--if it's 10.0.1, you may need to install a slightly older version. See below for details.)
|
||||||
|
|
||||||
|
There are also plenty of alternative options at http://releases.llvm.org/download.html
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
### LLVM installation on Linux
|
||||||
|
|
||||||
On some Linux systems we've seen the error "failed to run custom build command for x11".
|
On some Linux systems we've seen the error "failed to run custom build command for x11".
|
||||||
On Ubuntu, running `sudo apt-get install cmake libx11-dev` fixed this.
|
On Ubuntu, running `sudo apt-get install cmake libx11-dev` fixed this.
|
||||||
|
|
||||||
### Troubleshooting LLVM installation on Windows
|
### LLVM installation on macOS
|
||||||
|
|
||||||
|
It looks like LLVM 10.0.1 [has some issues with libxml2 on macOS](https://discourse.brew.sh/t/llvm-config-10-0-1-advertise-libxml2-tbd-as-system-libs/8593). You can install the older 10.0.0_3 by doing
|
||||||
|
|
||||||
|
```
|
||||||
|
$ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50fb0b24dbe30f5e975210bdad63257f517/Formula/llvm.rb
|
||||||
|
# "pinning" ensures that homebrew doesn't update it automatically
|
||||||
|
$ brew pin llvm
|
||||||
|
```
|
||||||
|
|
||||||
|
### LLVM installation on Windows
|
||||||
|
|
||||||
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source
|
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source
|
||||||
on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMacKenzie) (thank you, Ian!), here's what worked for me:
|
on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMacKenzie) (thank you, Ian!), here's what worked for me:
|
||||||
|
@ -27,6 +49,7 @@ on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMa
|
||||||
6. Once that completed, I ran `nmake` to build LLVM. (This took about 2 hours on my laptop.)
|
6. Once that completed, I ran `nmake` to build LLVM. (This took about 2 hours on my laptop.)
|
||||||
7. Finally, I set an environment variable `LLVM_SYS_100_PREFIX` to point to the `build` directory where I ran the `cmake` command.
|
7. Finally, I set an environment variable `LLVM_SYS_100_PREFIX` to point to the `build` directory where I ran the `cmake` command.
|
||||||
|
|
||||||
|
|
||||||
Once all that was done, `cargo` ran successfully for Roc!
|
Once all that was done, `cargo` ran successfully for Roc!
|
||||||
|
|
||||||
## Use LLD for the linker
|
## Use LLD for the linker
|
||||||
|
|
101
Cargo.lock
generated
101
Cargo.lock
generated
|
@ -1952,43 +1952,6 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "roc-cli"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"clap 3.0.0-beta.1",
|
|
||||||
"im",
|
|
||||||
"im-rc",
|
|
||||||
"indoc",
|
|
||||||
"inkwell",
|
|
||||||
"inlinable_string",
|
|
||||||
"maplit",
|
|
||||||
"pretty_assertions",
|
|
||||||
"quickcheck",
|
|
||||||
"quickcheck_macros",
|
|
||||||
"roc_build",
|
|
||||||
"roc_builtins",
|
|
||||||
"roc_can",
|
|
||||||
"roc_collections",
|
|
||||||
"roc_constrain",
|
|
||||||
"roc_editor",
|
|
||||||
"roc_gen",
|
|
||||||
"roc_load",
|
|
||||||
"roc_module",
|
|
||||||
"roc_mono",
|
|
||||||
"roc_parse",
|
|
||||||
"roc_problem",
|
|
||||||
"roc_region",
|
|
||||||
"roc_reporting",
|
|
||||||
"roc_solve",
|
|
||||||
"roc_types",
|
|
||||||
"roc_unify",
|
|
||||||
"roc_uniq",
|
|
||||||
"target-lexicon",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roc_build"
|
name = "roc_build"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -2065,6 +2028,46 @@ dependencies = [
|
||||||
"ven_graph",
|
"ven_graph",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roc_cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"clap 3.0.0-beta.1",
|
||||||
|
"im",
|
||||||
|
"im-rc",
|
||||||
|
"indoc",
|
||||||
|
"inkwell",
|
||||||
|
"inlinable_string",
|
||||||
|
"libc",
|
||||||
|
"maplit",
|
||||||
|
"pretty_assertions",
|
||||||
|
"quickcheck",
|
||||||
|
"quickcheck_macros",
|
||||||
|
"roc_build",
|
||||||
|
"roc_builtins",
|
||||||
|
"roc_can",
|
||||||
|
"roc_collections",
|
||||||
|
"roc_constrain",
|
||||||
|
"roc_editor",
|
||||||
|
"roc_fmt",
|
||||||
|
"roc_gen",
|
||||||
|
"roc_load",
|
||||||
|
"roc_module",
|
||||||
|
"roc_mono",
|
||||||
|
"roc_parse",
|
||||||
|
"roc_problem",
|
||||||
|
"roc_region",
|
||||||
|
"roc_reporting",
|
||||||
|
"roc_solve",
|
||||||
|
"roc_types",
|
||||||
|
"roc_unify",
|
||||||
|
"roc_uniq",
|
||||||
|
"strip-ansi-escapes",
|
||||||
|
"target-lexicon",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roc_collections"
|
name = "roc_collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -2595,6 +2598,15 @@ dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strip-ansi-escapes"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d63676e2abafa709460982ddc02a3bb586b6d15a49b75c212e06edd3933acee"
|
||||||
|
dependencies = [
|
||||||
|
"vte",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -2789,6 +2801,12 @@ version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af41d708427f8fd0e915dcebb2cae0f0e6acb2a939b2d399c265c39a38a18942"
|
checksum = "af41d708427f8fd0e915dcebb2cae0f0e6acb2a939b2d399c265c39a38a18942"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vec_map"
|
name = "vec_map"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
@ -2833,6 +2851,15 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vte"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f42f536e22f7fcbb407639765c8fd78707a33109301f834a594758bedd6e8cf"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
|
14
README.md
14
README.md
|
@ -1,6 +1,18 @@
|
||||||
# Not ready to be shared yet!
|
# Not ready to be shared yet!
|
||||||
|
|
||||||
Roc is a language for building reliable applications on top of fast platforms.
|
Roc is a language to help anyone create delightful software.
|
||||||
|
|
||||||
|
Here's [a short talk](https://youtu.be/ZnYa99QoznE?t=4790) introducing it at a meetup.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
1. [Install Rust](https://rustup.rs/)
|
||||||
|
2. [Build from source](BUILDING_FROM_SOURCE.md)
|
||||||
|
3. In a terminal, run this from the root folder:
|
||||||
|
```
|
||||||
|
cargo run repl
|
||||||
|
```
|
||||||
|
4. Check out [these tests](https://github.com/rtfeldman/roc/blob/trunk/cli/tests/repl_eval.rs) for examples of using the REPL
|
||||||
|
|
||||||
## Applications and Platforms
|
## Applications and Platforms
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "roc-cli"
|
name = "roc_cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||||
repository = "https://github.com/rtfeldman/roc"
|
repository = "https://github.com/rtfeldman/roc"
|
||||||
|
@ -47,6 +47,7 @@ roc_mono = { path = "../compiler/mono" }
|
||||||
roc_load = { path = "../compiler/load" }
|
roc_load = { path = "../compiler/load" }
|
||||||
roc_gen = { path = "../compiler/gen" }
|
roc_gen = { path = "../compiler/gen" }
|
||||||
roc_build = { path = "../compiler/build" }
|
roc_build = { path = "../compiler/build" }
|
||||||
|
roc_fmt = { path = "../compiler/fmt" }
|
||||||
roc_reporting = { path = "../compiler/reporting" }
|
roc_reporting = { path = "../compiler/reporting" }
|
||||||
roc_editor = { path = "../editor" }
|
roc_editor = { path = "../editor" }
|
||||||
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
|
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
|
||||||
|
@ -56,6 +57,8 @@ im-rc = "14" # im and im-rc should always have the same version!
|
||||||
bumpalo = { version = "3.2", features = ["collections"] }
|
bumpalo = { version = "3.2", features = ["collections"] }
|
||||||
inlinable_string = "0.1"
|
inlinable_string = "0.1"
|
||||||
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
|
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
|
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
|
||||||
#
|
#
|
||||||
# The reason for this fork is that the way Inkwell is designed, you have to use
|
# The reason for this fork is that the way Inkwell is designed, you have to use
|
||||||
|
@ -82,3 +85,4 @@ maplit = "1.0.1"
|
||||||
indoc = "0.3.3"
|
indoc = "0.3.3"
|
||||||
quickcheck = "0.8"
|
quickcheck = "0.8"
|
||||||
quickcheck_macros = "0.8"
|
quickcheck_macros = "0.8"
|
||||||
|
strip-ansi-escapes = "0.1"
|
||||||
|
|
413
cli/src/repl.rs
413
cli/src/repl.rs
|
@ -1,18 +1,18 @@
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use inkwell::context::Context;
|
use inkwell::context::Context;
|
||||||
use inkwell::execution_engine::JitFunction;
|
use inkwell::execution_engine::ExecutionEngine;
|
||||||
use inkwell::types::BasicType;
|
use inkwell::types::BasicType;
|
||||||
use inkwell::OptimizationLevel;
|
use inkwell::OptimizationLevel;
|
||||||
use roc_builtins::unique::uniq_stdlib;
|
use roc_builtins::unique::uniq_stdlib;
|
||||||
use roc_can::constraint::Constraint;
|
use roc_can::constraint::Constraint;
|
||||||
use roc_can::env::Env;
|
|
||||||
use roc_can::expected::Expected;
|
use roc_can::expected::Expected;
|
||||||
use roc_can::expr::{canonicalize_expr, Output};
|
use roc_can::expr::{canonicalize_expr, Expr, Output};
|
||||||
use roc_can::operator;
|
use roc_can::operator;
|
||||||
use roc_can::scope::Scope;
|
use roc_can::scope::Scope;
|
||||||
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap, SendSet};
|
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap, SendSet};
|
||||||
use roc_constrain::expr::constrain_expr;
|
use roc_constrain::expr::constrain_expr;
|
||||||
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
|
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
|
||||||
|
use roc_fmt::annotation::{Formattable, Newlines, Parens};
|
||||||
use roc_gen::layout_id::LayoutIds;
|
use roc_gen::layout_id::LayoutIds;
|
||||||
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
|
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
|
||||||
use roc_gen::llvm::convert::basic_type_from_layout;
|
use roc_gen::llvm::convert::basic_type_from_layout;
|
||||||
|
@ -35,13 +35,17 @@ use std::path::PathBuf;
|
||||||
use std::str::from_utf8_unchecked;
|
use std::str::from_utf8_unchecked;
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
|
pub const WELCOME_MESSAGE: &str = "\n The rockin’ \u{001b}[36mroc repl\u{001b}[0m\n\u{001b}[35m────────────────────────\u{001b}[0m\n\n";
|
||||||
|
pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit.\n";
|
||||||
|
pub const PROMPT: &str = "\n\u{001b}[36m»\u{001b}[0m ";
|
||||||
|
pub const ELLIPSIS: &str = "\u{001b}[36m…\u{001b}[0m ";
|
||||||
|
|
||||||
|
mod eval;
|
||||||
|
|
||||||
pub fn main() -> io::Result<()> {
|
pub fn main() -> io::Result<()> {
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
|
|
||||||
println!(
|
print!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS);
|
||||||
"\n The rockin’ \u{001b}[36mroc repl\u{001b}[0m\n\u{001b}[35m────────────────────────\u{001b}[0m\n\n{}",
|
|
||||||
WELCOME_MESSAGE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Loop
|
// Loop
|
||||||
|
|
||||||
|
@ -50,9 +54,9 @@ pub fn main() -> io::Result<()> {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if pending_src.is_empty() {
|
if pending_src.is_empty() {
|
||||||
print!("\n\u{001b}[36m»\u{001b}[0m ");
|
print!("{}", PROMPT);
|
||||||
} else {
|
} else {
|
||||||
print!("\u{001b}[36m…\u{001b}[0m ");
|
print!("{}", ELLIPSIS);
|
||||||
}
|
}
|
||||||
|
|
||||||
io::stdout().flush().unwrap();
|
io::stdout().flush().unwrap();
|
||||||
|
@ -65,13 +69,15 @@ pub fn main() -> io::Result<()> {
|
||||||
.expect("there was no next line")
|
.expect("there was no next line")
|
||||||
.expect("the line could not be read");
|
.expect("the line could not be read");
|
||||||
|
|
||||||
match line.trim() {
|
let line = line.trim();
|
||||||
|
|
||||||
|
match line.to_lowercase().as_str() {
|
||||||
":help" => {
|
":help" => {
|
||||||
println!("Use :exit to exit.");
|
println!("Use :exit to exit.");
|
||||||
}
|
}
|
||||||
"" => {
|
"" => {
|
||||||
if pending_src.is_empty() {
|
if pending_src.is_empty() {
|
||||||
println!("\n{}", WELCOME_MESSAGE);
|
print!("\n{}", INSTRUCTIONS);
|
||||||
} else if prev_line_blank {
|
} else if prev_line_blank {
|
||||||
// After two blank lines in a row, give up and try parsing it
|
// After two blank lines in a row, give up and try parsing it
|
||||||
// even though it's going to fail. This way you don't get stuck.
|
// even though it's going to fail. This way you don't get stuck.
|
||||||
|
@ -95,7 +101,7 @@ pub fn main() -> io::Result<()> {
|
||||||
":exit" => {
|
":exit" => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
line => {
|
_ => {
|
||||||
let result = if pending_src.is_empty() {
|
let result = if pending_src.is_empty() {
|
||||||
print_output(line)
|
print_output(line)
|
||||||
} else {
|
} else {
|
||||||
|
@ -137,16 +143,16 @@ pub fn main() -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
const WELCOME_MESSAGE: &str =
|
|
||||||
"Enter an expression, or :help for a list of commands, or :exit to exit.";
|
|
||||||
|
|
||||||
fn report_parse_error(fail: Fail) {
|
fn report_parse_error(fail: Fail) {
|
||||||
println!("TODO Gracefully report parse error in repl: {:?}", fail);
|
println!("TODO Gracefully report parse error in repl: {:?}", fail);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_output(src: &str) -> Result<String, Fail> {
|
fn print_output(src: &str) -> Result<String, Fail> {
|
||||||
gen(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|(answer, answer_type)| {
|
gen(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
|
||||||
format!("\n{} \u{001b}[35m:\u{001b}[0m {}", answer, answer_type)
|
ReplOutput::NoProblems { expr, expr_type } => {
|
||||||
|
format!("\n{} \u{001b}[35m:\u{001b}[0m {}", expr, expr_type)
|
||||||
|
}
|
||||||
|
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +160,7 @@ pub fn repl_home() -> ModuleId {
|
||||||
ModuleIds::default().get_or_insert(&"REPL".into())
|
ModuleIds::default().get_or_insert(&"REPL".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, String), Fail> {
|
fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> {
|
||||||
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
|
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
|
||||||
|
|
||||||
// Look up the types and expressions of the `provided` values
|
// Look up the types and expressions of the `provided` values
|
||||||
|
@ -188,195 +194,223 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
|
||||||
//
|
//
|
||||||
// TODO: maybe Reporting should have this be an Option?
|
// TODO: maybe Reporting should have this be an Option?
|
||||||
let path = PathBuf::new();
|
let path = PathBuf::new();
|
||||||
|
let total_problems = can_problems.len() + type_problems.len();
|
||||||
|
|
||||||
for problem in can_problems.into_iter() {
|
if total_problems == 0 {
|
||||||
let report = can_problem(&alloc, path.clone(), problem);
|
let context = Context::create();
|
||||||
let mut buf = String::new();
|
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
|
||||||
|
let builder = context.create_builder();
|
||||||
|
|
||||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
// pretty-print the expr type string for later.
|
||||||
|
name_all_type_vars(var, &mut subs);
|
||||||
|
|
||||||
println!("\n{}\n", buf);
|
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
|
||||||
}
|
let (module_pass, function_pass) =
|
||||||
|
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
||||||
|
|
||||||
for problem in type_problems.into_iter() {
|
// Compute main_fn_type before moving subs to Env
|
||||||
let report = type_problem(&alloc, path.clone(), problem);
|
let main_ret_layout = Layout::new(&arena, content.clone(), &subs).unwrap_or_else(|err| {
|
||||||
let mut buf = String::new();
|
panic!(
|
||||||
|
"Code gen error in test: could not convert Content to main_layout. Err was {:?}",
|
||||||
|
err
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let execution_engine = module
|
||||||
|
.create_jit_execution_engine(OptimizationLevel::None)
|
||||||
|
.expect("Error creating JIT execution engine for test");
|
||||||
|
|
||||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
// Without calling this, we get a linker error when building this crate
|
||||||
|
// in --release mode and then trying to eval anything in the repl.
|
||||||
|
ExecutionEngine::link_in_mc_jit();
|
||||||
|
|
||||||
println!("\n{}\n", buf);
|
let main_fn_type = basic_type_from_layout(&arena, &context, &main_ret_layout, ptr_bytes)
|
||||||
}
|
.fn_type(&[], false);
|
||||||
|
let main_fn_name = "$Test.main";
|
||||||
|
|
||||||
let context = Context::create();
|
// Compile and add all the Procs before adding main
|
||||||
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
|
let mut env = roc_gen::llvm::build::Env {
|
||||||
let builder = context.create_builder();
|
arena: &arena,
|
||||||
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
builder: &builder,
|
||||||
|
context: &context,
|
||||||
|
interns,
|
||||||
|
module,
|
||||||
|
ptr_bytes,
|
||||||
|
leak: false,
|
||||||
|
exposed_to_host: MutSet::default(),
|
||||||
|
};
|
||||||
|
let mut procs = Procs::default();
|
||||||
|
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
||||||
|
let mut layout_ids = LayoutIds::default();
|
||||||
|
|
||||||
// pretty-print the expr type string for later.
|
// Populate Procs and get the low-level Expr from the canonical Expr
|
||||||
name_all_type_vars(var, &mut subs);
|
let mut mono_problems = Vec::new();
|
||||||
|
let mut mono_env = roc_mono::ir::Env {
|
||||||
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
|
arena: &arena,
|
||||||
|
subs: &mut subs,
|
||||||
// Compute main_fn_type before moving subs to Env
|
problems: &mut mono_problems,
|
||||||
let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
|
home,
|
||||||
panic!(
|
ident_ids: &mut ident_ids,
|
||||||
"Code gen error in test: could not convert to layout. Err was {:?}",
|
|
||||||
err
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let execution_engine = module
|
|
||||||
.create_jit_execution_engine(OptimizationLevel::None)
|
|
||||||
.expect("Error creating JIT execution engine for test");
|
|
||||||
|
|
||||||
let main_fn_type =
|
|
||||||
basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false);
|
|
||||||
let main_fn_name = "$Test.main";
|
|
||||||
|
|
||||||
// Compile and add all the Procs before adding main
|
|
||||||
let mut env = roc_gen::llvm::build::Env {
|
|
||||||
arena: &arena,
|
|
||||||
builder: &builder,
|
|
||||||
context: &context,
|
|
||||||
interns,
|
|
||||||
module,
|
|
||||||
ptr_bytes,
|
|
||||||
leak: false,
|
|
||||||
exposed_to_host: MutSet::default(),
|
|
||||||
};
|
|
||||||
let mut procs = Procs::default();
|
|
||||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
|
||||||
let mut layout_ids = LayoutIds::default();
|
|
||||||
|
|
||||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
|
||||||
let mut mono_problems = Vec::new();
|
|
||||||
let mut mono_env = roc_mono::ir::Env {
|
|
||||||
arena: &arena,
|
|
||||||
subs: &mut subs,
|
|
||||||
problems: &mut mono_problems,
|
|
||||||
home,
|
|
||||||
ident_ids: &mut ident_ids,
|
|
||||||
};
|
|
||||||
|
|
||||||
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
|
|
||||||
|
|
||||||
let param_map = roc_mono::borrow::ParamMap::default();
|
|
||||||
let main_body = roc_mono::inc_dec::visit_declaration(
|
|
||||||
mono_env.arena,
|
|
||||||
mono_env.arena.alloc(param_map),
|
|
||||||
mono_env.arena.alloc(main_body),
|
|
||||||
);
|
|
||||||
let mut headers = {
|
|
||||||
let num_headers = match &procs.pending_specializations {
|
|
||||||
Some(map) => map.len(),
|
|
||||||
None => 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Vec::with_capacity(num_headers)
|
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
|
||||||
};
|
|
||||||
let mut layout_cache = LayoutCache::default();
|
|
||||||
let mut procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
|
|
||||||
|
|
||||||
assert_eq!(
|
let param_map = roc_mono::borrow::ParamMap::default();
|
||||||
procs.runtime_errors,
|
let main_body = roc_mono::inc_dec::visit_declaration(
|
||||||
roc_collections::all::MutMap::default()
|
mono_env.arena,
|
||||||
);
|
mono_env.arena.alloc(param_map),
|
||||||
|
mono_env.arena.alloc(main_body),
|
||||||
|
);
|
||||||
|
let mut headers = {
|
||||||
|
let num_headers = match &procs.pending_specializations {
|
||||||
|
Some(map) => map.len(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
|
||||||
// Put this module's ident_ids back in the interns, so we can use them in env.
|
Vec::with_capacity(num_headers)
|
||||||
// This must happen *after* building the headers, because otherwise there's
|
};
|
||||||
// a conflicting mutable borrow on ident_ids.
|
let mut layout_cache = LayoutCache::default();
|
||||||
env.interns.all_ident_ids.insert(home, ident_ids);
|
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
|
||||||
|
|
||||||
// Add all the Proc headers to the module.
|
assert_eq!(
|
||||||
// We have to do this in a separate pass first,
|
procs.runtime_errors,
|
||||||
// because their bodies may reference each other.
|
roc_collections::all::MutMap::default()
|
||||||
|
);
|
||||||
|
|
||||||
let mut gen_scope = roc_gen::llvm::build::Scope::default();
|
let (mut procs, param_map) = procs.get_specialized_procs_help(mono_env.arena);
|
||||||
for ((symbol, layout), proc) in procs.specialized.drain() {
|
let main_body = roc_mono::inc_dec::visit_declaration(
|
||||||
use roc_mono::ir::InProgressProc::*;
|
mono_env.arena,
|
||||||
|
param_map,
|
||||||
|
mono_env.arena.alloc(main_body),
|
||||||
|
);
|
||||||
|
|
||||||
match proc {
|
// Put this module's ident_ids back in the interns, so we can use them in env.
|
||||||
InProgress => {
|
// This must happen *after* building the headers, because otherwise there's
|
||||||
panic!("A specialization was still marked InProgress after monomorphization had completed: {:?} with layout {:?}", symbol, layout);
|
// a conflicting mutable borrow on ident_ids.
|
||||||
}
|
env.interns.all_ident_ids.insert(home, ident_ids);
|
||||||
Done(proc) => {
|
|
||||||
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
|
||||||
|
|
||||||
headers.push((proc, fn_val));
|
// Add all the Proc headers to the module.
|
||||||
|
// We have to do this in a separate pass first,
|
||||||
|
// because their bodies may reference each other.
|
||||||
|
for ((symbol, layout), proc) in procs.drain() {
|
||||||
|
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||||
|
|
||||||
|
headers.push((proc, fn_val));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build each proc using its header info.
|
||||||
|
for (proc, fn_val) in headers {
|
||||||
|
// NOTE: This is here to be uncommented in case verification fails.
|
||||||
|
// (This approach means we don't have to defensively clone name here.)
|
||||||
|
//
|
||||||
|
// println!("\n\nBuilding and then verifying function {}\n\n", name);
|
||||||
|
build_proc(&env, &mut layout_ids, proc, fn_val);
|
||||||
|
|
||||||
|
if fn_val.verify(true) {
|
||||||
|
function_pass.run_on(&fn_val);
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"\n\nFunction {:?} failed LLVM verification in build. Its content was:\n",
|
||||||
|
fn_val.get_name().to_str().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
fn_val.print_to_stderr();
|
||||||
|
|
||||||
|
panic!(
|
||||||
|
"The preceding code was from {:?}, which failed LLVM verification in build.",
|
||||||
|
fn_val.get_name().to_str().unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Build each proc using its header info.
|
// Add main to the module.
|
||||||
for (proc, fn_val) in headers {
|
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
|
||||||
// NOTE: This is here to be uncommented in case verification fails.
|
let cc = roc_gen::llvm::build::FAST_CALL_CONV;
|
||||||
// (This approach means we don't have to defensively clone name here.)
|
|
||||||
//
|
|
||||||
// println!("\n\nBuilding and then verifying function {}\n\n", name);
|
|
||||||
build_proc(&env, &mut layout_ids, proc, fn_val);
|
|
||||||
|
|
||||||
if fn_val.verify(true) {
|
main_fn.set_call_conventions(cc);
|
||||||
fpm.run_on(&fn_val);
|
|
||||||
|
// Add main's body
|
||||||
|
let basic_block = context.append_basic_block(main_fn, "entry");
|
||||||
|
|
||||||
|
builder.position_at_end(basic_block);
|
||||||
|
|
||||||
|
// builds the function body (return statement included)
|
||||||
|
roc_gen::llvm::build::build_exp_stmt(
|
||||||
|
&env,
|
||||||
|
&mut layout_ids,
|
||||||
|
&mut roc_gen::llvm::build::Scope::default(),
|
||||||
|
main_fn,
|
||||||
|
&main_body,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||||
|
// env.module.print_to_stderr();
|
||||||
|
|
||||||
|
if main_fn.verify(true) {
|
||||||
|
function_pass.run_on(&main_fn);
|
||||||
} else {
|
} else {
|
||||||
// NOTE: If this fails, uncomment the above println to debug.
|
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
|
||||||
panic!(
|
|
||||||
"Non-main function failed LLVM verification. Uncomment the above println to debug!"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Add main to the module.
|
module_pass.run_on(env.module);
|
||||||
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
|
|
||||||
let cc = roc_gen::llvm::build::FAST_CALL_CONV;
|
|
||||||
|
|
||||||
main_fn.set_call_conventions(cc);
|
// Verify the module
|
||||||
|
if let Err(errors) = env.module.verify() {
|
||||||
|
panic!("Errors defining module: {:?}", errors);
|
||||||
|
}
|
||||||
|
|
||||||
// Add main's body
|
// Uncomment this to see the module's optimized LLVM instruction output:
|
||||||
let basic_block = context.append_basic_block(main_fn, "entry");
|
// env.module.print_to_stderr();
|
||||||
|
|
||||||
builder.position_at_end(basic_block);
|
let answer = unsafe {
|
||||||
|
eval::jit_to_ast(
|
||||||
|
&arena,
|
||||||
|
execution_engine,
|
||||||
|
main_fn_name,
|
||||||
|
&main_ret_layout,
|
||||||
|
&content,
|
||||||
|
&env.interns,
|
||||||
|
home,
|
||||||
|
&subs,
|
||||||
|
ptr_bytes,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut expr = bumpalo::collections::String::new_in(&arena);
|
||||||
|
|
||||||
let ret = roc_gen::llvm::build::build_exp_stmt(
|
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
|
||||||
&env,
|
|
||||||
&mut layout_ids,
|
|
||||||
&mut gen_scope,
|
|
||||||
main_fn,
|
|
||||||
&main_body,
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_return(Some(&ret));
|
Ok(ReplOutput::NoProblems {
|
||||||
|
expr: expr.into_bump_str().to_string(),
|
||||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
expr_type: expr_type_str,
|
||||||
// env.module.print_to_stderr();
|
})
|
||||||
|
|
||||||
if main_fn.verify(true) {
|
|
||||||
fpm.run_on(&main_fn);
|
|
||||||
} else {
|
} else {
|
||||||
panic!("Main function {} failed LLVM verification. Uncomment things near this error message for more details.", main_fn_name);
|
// There were problems; report them and return.
|
||||||
|
let mut lines = Vec::with_capacity(total_problems);
|
||||||
|
|
||||||
|
for problem in can_problems.into_iter() {
|
||||||
|
let report = can_problem(&alloc, path.clone(), problem);
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||||
|
|
||||||
|
lines.push(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
for problem in type_problems.into_iter() {
|
||||||
|
let report = type_problem(&alloc, path.clone(), problem);
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||||
|
|
||||||
|
lines.push(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ReplOutput::Problems(lines))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mpm.run_on(module);
|
enum ReplOutput {
|
||||||
|
Problems(Vec<String>),
|
||||||
// Verify the module
|
NoProblems { expr: String, expr_type: String },
|
||||||
if let Err(errors) = env.module.verify() {
|
|
||||||
panic!("Errors defining module: {:?}", errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
|
||||||
// env.module.print_to_stderr();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let main: JitFunction<
|
|
||||||
unsafe extern "C" fn() -> i64, /* TODO have this return Str, and in the generated code make sure to call the appropriate string conversion function on the return val based on its type! */
|
|
||||||
> = execution_engine
|
|
||||||
.get_function(main_fn_name)
|
|
||||||
.ok()
|
|
||||||
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
|
|
||||||
.expect("errored");
|
|
||||||
|
|
||||||
let result = main.call();
|
|
||||||
let output = format!("{}", result);
|
|
||||||
Ok((output, expr_type_str))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn infer_expr(
|
pub fn infer_expr(
|
||||||
|
@ -536,7 +570,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_bytes: &[u8]) -> Result<
|
||||||
|
|
||||||
let mut scope = Scope::new(home);
|
let mut scope = Scope::new(home);
|
||||||
let dep_idents = IdentIds::exposed_builtins(0);
|
let dep_idents = IdentIds::exposed_builtins(0);
|
||||||
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
let mut env = roc_can::env::Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
||||||
let (loc_expr, output) = canonicalize_expr(
|
let (loc_expr, output) = canonicalize_expr(
|
||||||
&mut env,
|
&mut env,
|
||||||
&mut var_store,
|
&mut var_store,
|
||||||
|
@ -545,6 +579,31 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_bytes: &[u8]) -> Result<
|
||||||
&loc_expr.value,
|
&loc_expr.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add builtin defs (e.g. List.get) directly to the canonical Expr,
|
||||||
|
// since we aren't using modules here.
|
||||||
|
let mut with_builtins = loc_expr.value;
|
||||||
|
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
|
||||||
|
|
||||||
|
for (symbol, def) in builtin_defs {
|
||||||
|
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
|
||||||
|
{
|
||||||
|
with_builtins = Expr::LetNonRec(
|
||||||
|
Box::new(def),
|
||||||
|
Box::new(Located {
|
||||||
|
region: Region::zero(),
|
||||||
|
value: with_builtins,
|
||||||
|
}),
|
||||||
|
var_store.fresh(),
|
||||||
|
SendMap::default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let loc_expr = Located {
|
||||||
|
region: loc_expr.region,
|
||||||
|
value: with_builtins,
|
||||||
|
};
|
||||||
|
|
||||||
let constraint = constrain_expr(
|
let constraint = constrain_expr(
|
||||||
&roc_constrain::expr::Env {
|
&roc_constrain::expr::Env {
|
||||||
rigids: ImMap::default(),
|
rigids: ImMap::default(),
|
||||||
|
|
418
cli/src/repl/eval.rs
Normal file
418
cli/src/repl/eval.rs
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
use bumpalo::collections::Vec;
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use inkwell::execution_engine::{ExecutionEngine, JitFunction};
|
||||||
|
use roc_collections::all::MutMap;
|
||||||
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
|
use roc_module::operator::CalledVia;
|
||||||
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
|
use roc_mono::layout::{Builtin, Layout};
|
||||||
|
use roc_parse::ast::{AssignedField, Expr, StrLiteral};
|
||||||
|
use roc_region::all::{Located, Region};
|
||||||
|
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||||
|
use roc_types::types::RecordField;
|
||||||
|
|
||||||
|
struct Env<'a, 'env> {
|
||||||
|
arena: &'a Bump,
|
||||||
|
subs: &'env Subs,
|
||||||
|
ptr_bytes: u32,
|
||||||
|
interns: &'env Interns,
|
||||||
|
home: ModuleId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JIT execute the given main function, and then wrap its results in an Expr
|
||||||
|
/// so we can display them to the user using the formatter.
|
||||||
|
///
|
||||||
|
/// We need the original types in order to properly render records and tag unions,
|
||||||
|
/// because at runtime those are structs - that is, unlabeled memory offsets.
|
||||||
|
/// By traversing the type signature while we're traversing the layout, once
|
||||||
|
/// we get to a struct or tag, we know what the labels are and can turn them
|
||||||
|
/// back into the appropriate user-facing literals.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub unsafe fn jit_to_ast<'a>(
|
||||||
|
arena: &'a Bump,
|
||||||
|
execution_engine: ExecutionEngine,
|
||||||
|
main_fn_name: &str,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
content: &Content,
|
||||||
|
interns: &Interns,
|
||||||
|
home: ModuleId,
|
||||||
|
subs: &Subs,
|
||||||
|
ptr_bytes: u32,
|
||||||
|
) -> Expr<'a> {
|
||||||
|
let env = Env {
|
||||||
|
arena,
|
||||||
|
subs,
|
||||||
|
ptr_bytes,
|
||||||
|
home,
|
||||||
|
interns,
|
||||||
|
};
|
||||||
|
|
||||||
|
jit_to_ast_help(&env, execution_engine, main_fn_name, layout, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! jit_map {
|
||||||
|
($execution_engine: expr, $main_fn_name: expr, $ty: ty, $transform: expr) => {{
|
||||||
|
unsafe {
|
||||||
|
let main: JitFunction<unsafe extern "C" fn() -> $ty> = $execution_engine
|
||||||
|
.get_function($main_fn_name)
|
||||||
|
.ok()
|
||||||
|
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
|
||||||
|
.expect("errored");
|
||||||
|
|
||||||
|
$transform(main.call())
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn jit_to_ast_help<'a>(
|
||||||
|
env: &Env<'a, '_>,
|
||||||
|
execution_engine: ExecutionEngine,
|
||||||
|
main_fn_name: &str,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
content: &Content,
|
||||||
|
) -> Expr<'a> {
|
||||||
|
match layout {
|
||||||
|
Layout::Builtin(Builtin::Int64) => {
|
||||||
|
jit_map!(execution_engine, main_fn_name, i64, |num| num_to_ast(
|
||||||
|
env,
|
||||||
|
i64_to_ast(env.arena, num),
|
||||||
|
content
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::Float64) => {
|
||||||
|
jit_map!(execution_engine, main_fn_name, f64, |num| num_to_ast(
|
||||||
|
env,
|
||||||
|
f64_to_ast(env.arena, num),
|
||||||
|
content
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => jit_map!(
|
||||||
|
execution_engine,
|
||||||
|
main_fn_name,
|
||||||
|
&'static str,
|
||||||
|
|string: &'static str| { str_slice_to_ast(env.arena, env.arena.alloc(string)) }
|
||||||
|
),
|
||||||
|
Layout::Builtin(Builtin::EmptyList) => {
|
||||||
|
jit_map!(execution_engine, main_fn_name, &'static str, |_| {
|
||||||
|
Expr::List(Vec::new_in(env.arena))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::List(_, elem_layout)) => jit_map!(
|
||||||
|
execution_engine,
|
||||||
|
main_fn_name,
|
||||||
|
(*const libc::c_void, usize),
|
||||||
|
|(ptr, len): (*const libc::c_void, usize)| {
|
||||||
|
list_to_ast(env, ptr, len, elem_layout, content)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Layout::Struct(field_layouts) => {
|
||||||
|
let ptr_to_ast = |ptr: *const libc::c_void| match content {
|
||||||
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
|
struct_to_ast(env, ptr, field_layouts, fields)
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
unreachable!(
|
||||||
|
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
|
||||||
|
other
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Functions can return structs of either 8 or 16 bytes, depending
|
||||||
|
// on whether we're compiling for a 64-bit or 32-bit target.
|
||||||
|
match env.ptr_bytes {
|
||||||
|
// 64-bit target (8-byte pointers, 16-byte structs)
|
||||||
|
8 => jit_map!(
|
||||||
|
execution_engine,
|
||||||
|
main_fn_name,
|
||||||
|
[u8; 16],
|
||||||
|
|bytes: [u8; 16]| { ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) }
|
||||||
|
),
|
||||||
|
// 32-bit target (4-byte pointers, 8-byte structs)
|
||||||
|
4 => jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
|
||||||
|
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
|
||||||
|
}),
|
||||||
|
other => {
|
||||||
|
panic!("Unsupported target: Roc cannot currently compile to systems where pointers are {} bytes in length.", other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
todo!("TODO add support for rendering {:?} in the REPL", other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ptr_to_ast<'a>(
|
||||||
|
env: &Env<'a, '_>,
|
||||||
|
ptr: *const libc::c_void,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
content: &Content,
|
||||||
|
) -> Expr<'a> {
|
||||||
|
match layout {
|
||||||
|
Layout::Builtin(Builtin::Int64) => {
|
||||||
|
let num = unsafe { *(ptr as *const i64) };
|
||||||
|
|
||||||
|
num_to_ast(env, i64_to_ast(env.arena, num), content)
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::Float64) => {
|
||||||
|
let num = unsafe { *(ptr as *const f64) };
|
||||||
|
|
||||||
|
num_to_ast(env, f64_to_ast(env.arena, num), content)
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::EmptyList) => Expr::List(Vec::new_in(env.arena)),
|
||||||
|
Layout::Builtin(Builtin::List(_, elem_layout)) => {
|
||||||
|
// Turn the (ptr, len) wrapper struct into actual ptr and len values.
|
||||||
|
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
|
||||||
|
let ptr = unsafe { *(ptr as *const *const libc::c_void) };
|
||||||
|
|
||||||
|
list_to_ast(env, ptr, len, elem_layout, content)
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::EmptyStr) => Expr::Str(StrLiteral::PlainLine("")),
|
||||||
|
Layout::Builtin(Builtin::Str) => {
|
||||||
|
let arena_str = unsafe { *(ptr as *const &'static str) };
|
||||||
|
|
||||||
|
str_slice_to_ast(env.arena, arena_str)
|
||||||
|
}
|
||||||
|
Layout::Struct(field_layouts) => match content {
|
||||||
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
|
struct_to_ast(env, ptr, field_layouts, fields)
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
unreachable!(
|
||||||
|
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
|
||||||
|
other
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
other => {
|
||||||
|
todo!(
|
||||||
|
"TODO add support for rendering pointer to {:?} in the REPL",
|
||||||
|
other
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_to_ast<'a>(
|
||||||
|
env: &Env<'a, '_>,
|
||||||
|
ptr: *const libc::c_void,
|
||||||
|
len: usize,
|
||||||
|
elem_layout: &Layout<'a>,
|
||||||
|
content: &Content,
|
||||||
|
) -> Expr<'a> {
|
||||||
|
let elem_content = match content {
|
||||||
|
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, vars)) => {
|
||||||
|
debug_assert_eq!(vars.len(), 1);
|
||||||
|
|
||||||
|
let elem_var = *vars.first().unwrap();
|
||||||
|
|
||||||
|
env.subs.get_without_compacting(elem_var).content
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
unreachable!(
|
||||||
|
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
|
||||||
|
other
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let arena = env.arena;
|
||||||
|
let mut output = Vec::with_capacity_in(len, &arena);
|
||||||
|
let elem_size = elem_layout.stack_size(env.ptr_bytes);
|
||||||
|
|
||||||
|
for index in 0..(len as isize) {
|
||||||
|
let offset_bytes: isize = index * elem_size as isize;
|
||||||
|
let elem_ptr = unsafe { ptr.offset(offset_bytes) };
|
||||||
|
let loc_expr = &*arena.alloc(Located {
|
||||||
|
value: ptr_to_ast(env, elem_ptr, elem_layout, &elem_content),
|
||||||
|
region: Region::zero(),
|
||||||
|
});
|
||||||
|
|
||||||
|
output.push(loc_expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::List(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn struct_to_ast<'a>(
|
||||||
|
env: &Env<'a, '_>,
|
||||||
|
ptr: *const libc::c_void,
|
||||||
|
field_layouts: &[Layout<'a>],
|
||||||
|
fields: &MutMap<Lowercase, RecordField<Variable>>,
|
||||||
|
) -> Expr<'a> {
|
||||||
|
let arena = env.arena;
|
||||||
|
let subs = env.subs;
|
||||||
|
let mut output = Vec::with_capacity_in(field_layouts.len(), &arena);
|
||||||
|
|
||||||
|
// The fields, sorted alphabetically
|
||||||
|
let sorted_fields = {
|
||||||
|
let mut vec = fields
|
||||||
|
.iter()
|
||||||
|
.collect::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>();
|
||||||
|
|
||||||
|
vec.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
|
||||||
|
|
||||||
|
vec
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
|
||||||
|
|
||||||
|
// We'll advance this as we iterate through the fields
|
||||||
|
let mut field_ptr = ptr;
|
||||||
|
|
||||||
|
for ((label, field), field_layout) in sorted_fields.iter().zip(field_layouts.iter()) {
|
||||||
|
let content = subs.get_without_compacting(*field.as_inner()).content;
|
||||||
|
let loc_expr = &*arena.alloc(Located {
|
||||||
|
value: ptr_to_ast(env, field_ptr, field_layout, &content),
|
||||||
|
region: Region::zero(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let field_name = Located {
|
||||||
|
value: &*arena.alloc_str(label.as_str()),
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
let loc_field = Located {
|
||||||
|
value: AssignedField::RequiredValue(field_name, &[], loc_expr),
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
output.push(loc_field);
|
||||||
|
|
||||||
|
// Advance the field pointer to the next field.
|
||||||
|
field_ptr = unsafe { ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) };
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Record {
|
||||||
|
update: None,
|
||||||
|
fields: output,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> {
|
||||||
|
use Content::*;
|
||||||
|
|
||||||
|
let arena = env.arena;
|
||||||
|
|
||||||
|
match content {
|
||||||
|
Structure(flat_type) => {
|
||||||
|
match flat_type {
|
||||||
|
FlatType::Apply(Symbol::NUM_NUM, _) => num_expr,
|
||||||
|
FlatType::Record(fields, _) => {
|
||||||
|
// This was a single-field record that got unwrapped at runtime.
|
||||||
|
// Even if it was an i64 at runtime, we still need to report
|
||||||
|
// it as a record with the correct field name!
|
||||||
|
// Its type signature will tell us that.
|
||||||
|
debug_assert_eq!(fields.len(), 1);
|
||||||
|
|
||||||
|
let (label, field) = fields.iter().next().unwrap();
|
||||||
|
let loc_label = Located {
|
||||||
|
value: &*arena.alloc_str(label.as_str()),
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let assigned_field = {
|
||||||
|
// We may be multiple levels deep in nested tag unions
|
||||||
|
// and/or records (e.g. { a: { b: { c: 5 } } }),
|
||||||
|
// so we need to do this recursively on the field type.
|
||||||
|
let field_var = *field.as_inner();
|
||||||
|
let field_content = env.subs.get_without_compacting(field_var).content;
|
||||||
|
let loc_expr = Located {
|
||||||
|
value: num_to_ast(env, num_expr, &field_content),
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
|
||||||
|
};
|
||||||
|
let loc_assigned_field = Located {
|
||||||
|
value: assigned_field,
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Expr::Record {
|
||||||
|
update: None,
|
||||||
|
fields: bumpalo::vec![in arena; loc_assigned_field],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FlatType::TagUnion(tags, _) => {
|
||||||
|
// This was a single-tag union that got unwrapped at runtime.
|
||||||
|
debug_assert_eq!(tags.len(), 1);
|
||||||
|
|
||||||
|
let (tag_name, payload_vars) = tags.iter().next().unwrap();
|
||||||
|
|
||||||
|
// If this tag union represents a number, skip right to
|
||||||
|
// returning tis as an Expr::Num
|
||||||
|
if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name {
|
||||||
|
return num_expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let loc_tag_expr = {
|
||||||
|
let tag_name = &tag_name.as_string(env.interns, env.home);
|
||||||
|
let tag_expr = if tag_name.starts_with('@') {
|
||||||
|
Expr::PrivateTag(arena.alloc_str(tag_name))
|
||||||
|
} else {
|
||||||
|
Expr::GlobalTag(arena.alloc_str(tag_name))
|
||||||
|
};
|
||||||
|
|
||||||
|
&*arena.alloc(Located {
|
||||||
|
value: tag_expr,
|
||||||
|
region: Region::zero(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let payload = {
|
||||||
|
// Since this has the layout of a number, there should be
|
||||||
|
// exactly one payload in this tag.
|
||||||
|
debug_assert_eq!(payload_vars.len(), 1);
|
||||||
|
|
||||||
|
let var = *payload_vars.iter().next().unwrap();
|
||||||
|
let content = env.subs.get_without_compacting(var).content;
|
||||||
|
|
||||||
|
let loc_payload = &*arena.alloc(Located {
|
||||||
|
value: num_to_ast(env, num_expr, &content),
|
||||||
|
region: Region::zero(),
|
||||||
|
});
|
||||||
|
|
||||||
|
bumpalo::vec![in arena; loc_payload]
|
||||||
|
};
|
||||||
|
|
||||||
|
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
panic!("Unexpected FlatType {:?} in num_to_ast", other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Alias(_, _, var) => {
|
||||||
|
let content = env.subs.get_without_compacting(*var).content;
|
||||||
|
|
||||||
|
num_to_ast(env, num_expr, &content)
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
panic!("Unexpected FlatType {:?} in num_to_ast", other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is centralized in case we want to format it differently later,
|
||||||
|
/// e.g. adding underscores for large numbers
|
||||||
|
fn i64_to_ast(arena: &Bump, num: i64) -> Expr<'_> {
|
||||||
|
Expr::Num(arena.alloc(format!("{}", num)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is centralized in case we want to format it differently later,
|
||||||
|
/// e.g. adding underscores for large numbers
|
||||||
|
fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> {
|
||||||
|
Expr::Num(arena.alloc(format!("{}", num)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
|
||||||
|
if string.contains('\n') {
|
||||||
|
todo!(
|
||||||
|
"this string contains newlines, so render it as a multiline string: {:?}",
|
||||||
|
Expr::Str(StrLiteral::PlainLine(string))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Expr::Str(StrLiteral::PlainLine(string))
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,88 +7,11 @@ extern crate roc_collections;
|
||||||
extern crate roc_load;
|
extern crate roc_load;
|
||||||
extern crate roc_module;
|
extern crate roc_module;
|
||||||
|
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod cli_run {
|
mod cli_run {
|
||||||
use std::env;
|
use crate::helpers::{example_file, run_roc};
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::{Command, ExitStatus};
|
|
||||||
|
|
||||||
// HELPERS
|
|
||||||
|
|
||||||
pub struct Out {
|
|
||||||
pub stdout: String,
|
|
||||||
pub stderr: String,
|
|
||||||
pub status: ExitStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path_to_roc_binary() -> PathBuf {
|
|
||||||
// Adapted from https://github.com/volta-cli/volta/blob/cefdf7436a15af3ce3a38b8fe53bb0cfdb37d3dd/tests/acceptance/support/sandbox.rs#L680 - BSD-2-Clause licensed
|
|
||||||
let mut path = env::var_os("CARGO_BIN_PATH")
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.or_else(|| {
|
|
||||||
env::current_exe().ok().map(|mut path| {
|
|
||||||
path.pop();
|
|
||||||
if path.ends_with("deps") { path.pop();
|
|
||||||
}
|
|
||||||
path
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set, and couldn't be inferred from context. Can't run CLI tests."));
|
|
||||||
|
|
||||||
path.push("roc");
|
|
||||||
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_roc(args: &[&str]) -> Out {
|
|
||||||
let mut cmd = Command::new(path_to_roc_binary());
|
|
||||||
|
|
||||||
for arg in args {
|
|
||||||
cmd.arg(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = cmd
|
|
||||||
.output()
|
|
||||||
.expect("failed to execute compiled `roc` binary in CLI test");
|
|
||||||
|
|
||||||
Out {
|
|
||||||
stdout: String::from_utf8(output.stdout).unwrap(),
|
|
||||||
stderr: String::from_utf8(output.stderr).unwrap(),
|
|
||||||
status: output.status,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn example_dir(dir_name: &str) -> PathBuf {
|
|
||||||
let mut path = env::current_exe().ok().unwrap();
|
|
||||||
|
|
||||||
// Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06
|
|
||||||
path.pop();
|
|
||||||
|
|
||||||
// If we're in deps/ get rid of deps/ in target/debug/deps/
|
|
||||||
if path.ends_with("deps") {
|
|
||||||
path.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get rid of target/debug/ so we're back at the project root
|
|
||||||
path.pop();
|
|
||||||
path.pop();
|
|
||||||
|
|
||||||
// Descend into examples/{dir_name}
|
|
||||||
path.push("examples");
|
|
||||||
path.push(dir_name);
|
|
||||||
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf {
|
|
||||||
let mut path = example_dir(dir_name);
|
|
||||||
|
|
||||||
path.push(file_name);
|
|
||||||
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
// TESTS
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_hello_world() {
|
fn run_hello_world() {
|
||||||
|
|
180
cli/tests/helpers.rs
Normal file
180
cli/tests/helpers.rs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
extern crate bumpalo;
|
||||||
|
extern crate inlinable_string;
|
||||||
|
extern crate roc_collections;
|
||||||
|
extern crate roc_load;
|
||||||
|
extern crate roc_module;
|
||||||
|
// extern crate roc_cli; // TODO FIXME why doesn't this resolve?
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::{Command, ExitStatus, Stdio};
|
||||||
|
|
||||||
|
pub struct Out {
|
||||||
|
pub stdout: String,
|
||||||
|
pub stderr: String,
|
||||||
|
pub status: ExitStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO get these from roc_cli::repl instead, after figuring out why
|
||||||
|
// `extern crate roc_cli;` doesn't work.
|
||||||
|
const WELCOME_MESSAGE: &str = "\n The rockin’ \u{001b}[36mroc repl\u{001b}[0m\n\u{001b}[35m────────────────────────\u{001b}[0m\n\n";
|
||||||
|
const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit.\n";
|
||||||
|
const PROMPT: &str = "\n\u{001b}[36m»\u{001b}[0m ";
|
||||||
|
|
||||||
|
pub fn path_to_roc_binary() -> PathBuf {
|
||||||
|
// Adapted from https://github.com/volta-cli/volta/blob/cefdf7436a15af3ce3a38b8fe53bb0cfdb37d3dd/tests/acceptance/support/sandbox.rs#L680 - BSD-2-Clause licensed
|
||||||
|
let mut path = env::var_os("CARGO_BIN_PATH")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.or_else(|| {
|
||||||
|
env::current_exe().ok().map(|mut path| {
|
||||||
|
path.pop();
|
||||||
|
if path.ends_with("deps") { path.pop();
|
||||||
|
}
|
||||||
|
path
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set, and couldn't be inferred from context. Can't run CLI tests."));
|
||||||
|
|
||||||
|
path.push("roc");
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn run_roc(args: &[&str]) -> Out {
|
||||||
|
let mut cmd = Command::new(path_to_roc_binary());
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
cmd.arg(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = cmd
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute compiled `roc` binary in CLI test");
|
||||||
|
|
||||||
|
Out {
|
||||||
|
stdout: String::from_utf8(output.stdout).unwrap(),
|
||||||
|
stderr: String::from_utf8(output.stderr).unwrap(),
|
||||||
|
status: output.status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn example_dir(dir_name: &str) -> PathBuf {
|
||||||
|
let mut path = env::current_exe().ok().unwrap();
|
||||||
|
|
||||||
|
// Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06
|
||||||
|
path.pop();
|
||||||
|
|
||||||
|
// If we're in deps/ get rid of deps/ in target/debug/deps/
|
||||||
|
if path.ends_with("deps") {
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get rid of target/debug/ so we're back at the project root
|
||||||
|
path.pop();
|
||||||
|
path.pop();
|
||||||
|
|
||||||
|
// Descend into examples/{dir_name}
|
||||||
|
path.push("examples");
|
||||||
|
path.push(dir_name);
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf {
|
||||||
|
let mut path = example_dir(dir_name);
|
||||||
|
|
||||||
|
path.push(file_name);
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn repl_eval(input: &str) -> Out {
|
||||||
|
let mut cmd = Command::new(path_to_roc_binary());
|
||||||
|
|
||||||
|
cmd.arg("repl");
|
||||||
|
|
||||||
|
let mut child = cmd
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("failed to execute compiled `roc` binary in CLI test");
|
||||||
|
|
||||||
|
{
|
||||||
|
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||||
|
|
||||||
|
// Send the input expression
|
||||||
|
stdin
|
||||||
|
.write_all(input.as_bytes())
|
||||||
|
.expect("Failed to write input to stdin");
|
||||||
|
|
||||||
|
// Evaluate the expression
|
||||||
|
stdin
|
||||||
|
.write_all("\n".as_bytes())
|
||||||
|
.expect("Failed to write newline to stdin");
|
||||||
|
|
||||||
|
// Gracefully exit the repl
|
||||||
|
stdin
|
||||||
|
.write_all(":exit\n".as_bytes())
|
||||||
|
.expect("Failed to write :exit to stdin");
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = child
|
||||||
|
.wait_with_output()
|
||||||
|
.expect("Error waiting for REPL child process to exit.");
|
||||||
|
|
||||||
|
// Remove the initial instructions from the output.
|
||||||
|
|
||||||
|
// TODO get these from roc_cli::repl instead, after figuring out why
|
||||||
|
// `extern crate roc_cli;` doesn't work.
|
||||||
|
let expected_instructions = format!("{}{}{}", WELCOME_MESSAGE, INSTRUCTIONS, PROMPT);
|
||||||
|
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
stdout.starts_with(&expected_instructions),
|
||||||
|
"Unexpected repl output: {}",
|
||||||
|
stdout
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_, answer) = stdout.split_at(expected_instructions.len());
|
||||||
|
let answer = if answer.is_empty() {
|
||||||
|
// The repl crashed before completing the evaluation.
|
||||||
|
// This is most likely due to a segfault.
|
||||||
|
if output.status.to_string() == "signal: 11" {
|
||||||
|
panic!(
|
||||||
|
"repl segfaulted during the test. Stderr was {:?}",
|
||||||
|
String::from_utf8(output.stderr).unwrap()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let expected_after_answer = format!("\n{}", PROMPT);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
answer.ends_with(&expected_after_answer),
|
||||||
|
"Unexpected repl output after answer: {}",
|
||||||
|
answer
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use [1..] to trim the leading '\n'
|
||||||
|
// and (len - 1) to trim the trailing '\n'
|
||||||
|
let (answer, _) = answer[1..].split_at(answer.len() - expected_after_answer.len() - 1);
|
||||||
|
|
||||||
|
// Remove ANSI escape codes from the answer - for example:
|
||||||
|
//
|
||||||
|
// Before: "42 \u{1b}[35m:\u{1b}[0m Num *"
|
||||||
|
// After: "42 : Num *"
|
||||||
|
strip_ansi_escapes::strip(answer).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
Out {
|
||||||
|
stdout: String::from_utf8(answer).unwrap(),
|
||||||
|
stderr: String::from_utf8(output.stderr).unwrap(),
|
||||||
|
status: output.status,
|
||||||
|
}
|
||||||
|
}
|
250
cli/tests/repl_eval.rs
Normal file
250
cli/tests/repl_eval.rs
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate pretty_assertions;
|
||||||
|
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod repl_eval {
|
||||||
|
use crate::helpers;
|
||||||
|
|
||||||
|
fn expect_success(input: &str, expected: &str) {
|
||||||
|
let out = helpers::repl_eval(input);
|
||||||
|
|
||||||
|
assert_eq!(&out.stderr, "");
|
||||||
|
assert_eq!(&out.stdout, expected);
|
||||||
|
assert!(out.status.success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_0() {
|
||||||
|
expect_success("0", "0 : Num *");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_42() {
|
||||||
|
expect_success("42", "42 : Num *");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_0x0() {
|
||||||
|
expect_success("0x0", "0 : Int");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_0x42() {
|
||||||
|
expect_success("0x42", "66 : Int");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_0point0() {
|
||||||
|
expect_success("0.0", "0 : Float");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_4point2() {
|
||||||
|
expect_success("4.2", "4.2 : Float");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn num_addition() {
|
||||||
|
expect_success("1 + 2", "3 : Num *");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn int_addition() {
|
||||||
|
expect_success("0x1 + 2", "3 : Int");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn float_addition() {
|
||||||
|
expect_success("1.1 + 2", "3.1 : Float");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_empty_str() {
|
||||||
|
expect_success("\"\"", "\"\" : Str");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_ascii_str() {
|
||||||
|
expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_utf8_str() {
|
||||||
|
expect_success("\"👩👩👦👦\"", "\"👩👩👦👦\" : Str");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_concat() {
|
||||||
|
expect_success(
|
||||||
|
"Str.concat \"Hello, \" \"World!\"",
|
||||||
|
"\"Hello, World!\" : Str",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_empty_list() {
|
||||||
|
expect_success("[]", "[] : List *");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_num_list() {
|
||||||
|
expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_int_list() {
|
||||||
|
expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List Int");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_float_list() {
|
||||||
|
expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List Float");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_string_list() {
|
||||||
|
expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_string_list() {
|
||||||
|
expect_success(
|
||||||
|
r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ]"#,
|
||||||
|
r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ] : List (List (List Str))"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_num_list() {
|
||||||
|
expect_success(
|
||||||
|
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ]"#,
|
||||||
|
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List (Num *)))"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_int_list() {
|
||||||
|
expect_success(
|
||||||
|
r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#,
|
||||||
|
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List Int))"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_float_list() {
|
||||||
|
expect_success(
|
||||||
|
r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#,
|
||||||
|
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List Float))"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_concat() {
|
||||||
|
expect_success(
|
||||||
|
"List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]",
|
||||||
|
"[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List Float",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_1_field_i64_record() {
|
||||||
|
// Even though this gets unwrapped at runtime, the repl should still
|
||||||
|
// report it as a record
|
||||||
|
expect_success("{ foo: 42 }", "{ foo: 42 } : { foo : Num * }");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_1_field_f64_record() {
|
||||||
|
// Even though this gets unwrapped at runtime, the repl should still
|
||||||
|
// report it as a record
|
||||||
|
expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float }");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_1_field_i64_record() {
|
||||||
|
// Even though this gets unwrapped at runtime, the repl should still
|
||||||
|
// report it as a record
|
||||||
|
expect_success(
|
||||||
|
"{ foo: { bar: { baz: 42 } } }",
|
||||||
|
"{ foo: { bar: { baz: 42 } } } : { foo : { bar : { baz : Num * } } }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_1_field_f64_record() {
|
||||||
|
// Even though this gets unwrapped at runtime, the repl should still
|
||||||
|
// report it as a record
|
||||||
|
expect_success(
|
||||||
|
"{ foo: { bar: { baz: 4.2 } } }",
|
||||||
|
"{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float } } }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_2_field_i64_record() {
|
||||||
|
expect_success(
|
||||||
|
"{ foo: 0x4, bar: 0x2 }",
|
||||||
|
"{ bar: 2, foo: 4 } : { bar : Int, foo : Int }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done
|
||||||
|
// #[test]
|
||||||
|
// fn basic_2_field_f64_record() {
|
||||||
|
// expect_success(
|
||||||
|
// "{ foo: 4.1, bar: 2.3 }",
|
||||||
|
// "{ bar: 2.3, foo: 4.1 } : { bar : Float, foo : Float }",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn basic_2_field_mixed_record() {
|
||||||
|
// expect_success(
|
||||||
|
// "{ foo: 4.1, bar: 2 }",
|
||||||
|
// "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float }",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done
|
||||||
|
//
|
||||||
|
// #[test]
|
||||||
|
// fn basic_3_field_record() {
|
||||||
|
// expect_success(
|
||||||
|
// "{ foo: 4.1, bar: 2, baz: 0x5 }",
|
||||||
|
// "{ foo: 4.1, bar: 2, baz: 0x5 } : { foo : Float, bar : Num *, baz : Int }",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_of_1_field_records() {
|
||||||
|
// Even though these get unwrapped at runtime, the repl should still
|
||||||
|
// report them as records
|
||||||
|
expect_success("[ { foo: 42 } ]", "[ { foo: 42 } ] : List { foo : Num * }");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_of_2_field_records() {
|
||||||
|
expect_success(
|
||||||
|
"[ { foo: 4.1, bar: 2 } ]",
|
||||||
|
"[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn multiline_string() {
|
||||||
|
// // If a string contains newlines, format it as a multiline string in the output
|
||||||
|
// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\"");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done
|
||||||
|
//
|
||||||
|
// #[test]
|
||||||
|
// fn list_of_3_field_records() {
|
||||||
|
// expect_success(
|
||||||
|
// "[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
|
||||||
|
// "[ { foo: 4.1, bar: 2, baz: 0x3 } ] : List { foo : Float, bar : Num *, baz : Int }",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
|
@ -215,6 +215,23 @@ mapOrCancel : List before, (before -> Result after err) -> Result (List after) e
|
||||||
## >>> List.mapOks [ "", "a", "bc", "", "d", "ef", "" ]
|
## >>> List.mapOks [ "", "a", "bc", "", "d", "ef", "" ]
|
||||||
mapOks : List before, (before -> Result after *) -> List after
|
mapOks : List before, (before -> Result after *) -> List after
|
||||||
|
|
||||||
|
## Returns a list with the element at the given index having been transformed by
|
||||||
|
## the given function.
|
||||||
|
##
|
||||||
|
## For a version of this which gives you more control over when to perform
|
||||||
|
## the transformation, see #List.updater
|
||||||
|
##
|
||||||
|
## ## Performance notes
|
||||||
|
##
|
||||||
|
## In particular when updating nested collections, this is potentially much more
|
||||||
|
## efficient than using #List.get to obtain the element, transforming it,
|
||||||
|
## and then putting it back in the same place.
|
||||||
|
update : List elem, Len, (elem -> elem) -> List elem
|
||||||
|
|
||||||
|
## A more flexible version of #List.update, which returns an "updater" function
|
||||||
|
## that lets you delay performing the update until later.
|
||||||
|
updater : List elem, Len -> { elem, new : elem -> List elem }
|
||||||
|
|
||||||
## If all the elements in the list are #Ok, return a new list containing the
|
## If all the elements in the list are #Ok, return a new list containing the
|
||||||
## contents of those #Ok tags. If any elements are #Err, return #Err.
|
## contents of those #Ok tags. If any elements are #Err, return #Err.
|
||||||
allOks : List (Result ok err) -> Result (List ok) err
|
allOks : List (Result ok err) -> Result (List ok) err
|
||||||
|
|
|
@ -775,9 +775,35 @@ div = \numerator, denominator ->
|
||||||
##
|
##
|
||||||
## >>> Float.pi
|
## >>> Float.pi
|
||||||
## >>> |> Float.mod 2.0
|
## >>> |> Float.mod 2.0
|
||||||
mod : Float a, Float a -> Result Float DivByZero
|
mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
|
||||||
|
|
||||||
tryMod : Float a, Float a -> Result (Float a) [ DivByZero ]*
|
## Raises a #Float to the power of another #Float.
|
||||||
|
##
|
||||||
|
## `
|
||||||
|
## For an #Int alternative to this function, see #Num.raise.
|
||||||
|
pow : Float a, Float a -> Float a
|
||||||
|
|
||||||
|
## Raises an integer to the power of another, by multiplying the integer by
|
||||||
|
## itself the given number of times.
|
||||||
|
##
|
||||||
|
## This process is known as [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring).
|
||||||
|
##
|
||||||
|
## For a #Float alternative to this function, which supports negative exponents,
|
||||||
|
## see #Num.exp.
|
||||||
|
##
|
||||||
|
## >>> Num.exp 5 0
|
||||||
|
##
|
||||||
|
## >>> Num.exp 5 1
|
||||||
|
##
|
||||||
|
## >>> Num.exp 5 2
|
||||||
|
##
|
||||||
|
## >>> Num.exp 5 6
|
||||||
|
##
|
||||||
|
## ## Performance Notes
|
||||||
|
##
|
||||||
|
## Be careful! Even though this function takes only a #U8, it is very easy to
|
||||||
|
## overflow
|
||||||
|
expBySquaring : Int a, U8 -> Int a
|
||||||
|
|
||||||
## Return the reciprocal of a #Float - that is, divides `1.0` by the given number.
|
## Return the reciprocal of a #Float - that is, divides `1.0` by the given number.
|
||||||
##
|
##
|
||||||
|
@ -786,7 +812,9 @@ tryMod : Float a, Float a -> Result (Float a) [ DivByZero ]*
|
||||||
## For a version that does not crash, use #tryRecip
|
## For a version that does not crash, use #tryRecip
|
||||||
recip : Float a -> Result (Float a) [ DivByZero ]*
|
recip : Float a -> Result (Float a) [ DivByZero ]*
|
||||||
|
|
||||||
|
## NOTE: Need to come up a suffix alternative to the "try" prefix.
|
||||||
|
## This should be like (for example) recipTry so that it's more discoverable
|
||||||
|
## in documentation and editor autocomplete when you type "recip"
|
||||||
tryRecip : Float a -> Result (Float a) [ DivByZero ]*
|
tryRecip : Float a -> Result (Float a) [ DivByZero ]*
|
||||||
|
|
||||||
## Return an approximation of the absolute value of the square root of the #Float.
|
## Return an approximation of the absolute value of the square root of the #Float.
|
||||||
|
|
|
@ -491,6 +491,31 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// walkRight : List elem, (elem -> accum -> accum), accum -> accum
|
||||||
|
add_type(
|
||||||
|
Symbol::LIST_WALK_RIGHT,
|
||||||
|
SolvedType::Func(
|
||||||
|
vec![
|
||||||
|
list_type(flex(TVAR1)),
|
||||||
|
SolvedType::Func(vec![flex(TVAR1), flex(TVAR2)], Box::new(flex(TVAR2))),
|
||||||
|
flex(TVAR2),
|
||||||
|
],
|
||||||
|
Box::new(flex(TVAR2)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// keepIf : List elem, (elem -> Bool) -> List elem
|
||||||
|
add_type(
|
||||||
|
Symbol::LIST_KEEP_IF,
|
||||||
|
SolvedType::Func(
|
||||||
|
vec![
|
||||||
|
list_type(flex(TVAR1)),
|
||||||
|
SolvedType::Func(vec![flex(TVAR1)], Box::new(bool_type())),
|
||||||
|
],
|
||||||
|
Box::new(list_type(flex(TVAR1))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// map : List before, (before -> after) -> List after
|
// map : List before, (before -> after) -> List after
|
||||||
add_type(
|
add_type(
|
||||||
Symbol::LIST_MAP,
|
Symbol::LIST_MAP,
|
||||||
|
@ -503,19 +528,6 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// foldr : List a, (a -> b -> b), b -> b
|
|
||||||
add_type(
|
|
||||||
Symbol::LIST_FOLDR,
|
|
||||||
SolvedType::Func(
|
|
||||||
vec![
|
|
||||||
list_type(flex(TVAR1)),
|
|
||||||
SolvedType::Func(vec![flex(TVAR1), flex(TVAR2)], Box::new(flex(TVAR2))),
|
|
||||||
flex(TVAR2),
|
|
||||||
],
|
|
||||||
Box::new(flex(TVAR2)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// append : List elem, elem -> List elem
|
// append : List elem, elem -> List elem
|
||||||
add_type(
|
add_type(
|
||||||
Symbol::LIST_APPEND,
|
Symbol::LIST_APPEND,
|
||||||
|
|
|
@ -783,11 +783,26 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// foldr : Attr (* | u) (List (Attr u a))
|
// keepIf : Attr * (List a)
|
||||||
// , Attr Shared (Attr u a -> b -> b)
|
// , Attr Shared (a -> Attr * Bool)
|
||||||
// , b
|
// -> Attr * (List a)
|
||||||
// -> b
|
add_type(Symbol::LIST_KEEP_IF, {
|
||||||
add_type(Symbol::LIST_FOLDR, {
|
let_tvars! { a, star1, star2, star3 };
|
||||||
|
|
||||||
|
unique_function(
|
||||||
|
vec![
|
||||||
|
list_type(star1, a),
|
||||||
|
shared(SolvedType::Func(vec![flex(a)], Box::new(bool_type(star2)))),
|
||||||
|
],
|
||||||
|
list_type(star3, a),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// walkRight : Attr (* | u) (List (Attr u a))
|
||||||
|
// , Attr Shared (Attr u a -> b -> b)
|
||||||
|
// , b
|
||||||
|
// -> b
|
||||||
|
add_type(Symbol::LIST_WALK_RIGHT, {
|
||||||
let_tvars! { u, a, b, star1 };
|
let_tvars! { u, a, b, star1 };
|
||||||
|
|
||||||
unique_function(
|
unique_function(
|
||||||
|
|
|
@ -63,6 +63,9 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
||||||
Symbol::LIST_CONCAT => list_concat,
|
Symbol::LIST_CONCAT => list_concat,
|
||||||
Symbol::LIST_PREPEND => list_prepend,
|
Symbol::LIST_PREPEND => list_prepend,
|
||||||
Symbol::LIST_JOIN => list_join,
|
Symbol::LIST_JOIN => list_join,
|
||||||
|
Symbol::LIST_MAP => list_map,
|
||||||
|
Symbol::LIST_KEEP_IF => list_keep_if,
|
||||||
|
Symbol::LIST_WALK_RIGHT => list_walk_right,
|
||||||
Symbol::NUM_ADD => num_add,
|
Symbol::NUM_ADD => num_add,
|
||||||
Symbol::NUM_SUB => num_sub,
|
Symbol::NUM_SUB => num_sub,
|
||||||
Symbol::NUM_MUL => num_mul,
|
Symbol::NUM_MUL => num_mul,
|
||||||
|
@ -944,6 +947,82 @@ fn list_join(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
|
||||||
|
fn list_walk_right(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let list_var = var_store.fresh();
|
||||||
|
let func_var = var_store.fresh();
|
||||||
|
let accum_var = var_store.fresh();
|
||||||
|
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::ListWalkRight,
|
||||||
|
args: vec![
|
||||||
|
(list_var, Var(Symbol::ARG_1)),
|
||||||
|
(func_var, Var(Symbol::ARG_2)),
|
||||||
|
(accum_var, Var(Symbol::ARG_3)),
|
||||||
|
],
|
||||||
|
ret_var: accum_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![
|
||||||
|
(list_var, Symbol::ARG_1),
|
||||||
|
(func_var, Symbol::ARG_2),
|
||||||
|
(accum_var, Symbol::ARG_3),
|
||||||
|
],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
accum_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List.keepIf : List elem, (elem -> Bool) -> List elem
|
||||||
|
fn list_keep_if(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let list_var = var_store.fresh();
|
||||||
|
let func_var = var_store.fresh();
|
||||||
|
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::ListKeepIf,
|
||||||
|
args: vec![
|
||||||
|
(list_var, Var(Symbol::ARG_1)),
|
||||||
|
(func_var, Var(Symbol::ARG_2)),
|
||||||
|
],
|
||||||
|
ret_var: list_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![(list_var, Symbol::ARG_1), (func_var, Symbol::ARG_2)],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
list_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List.map : List before, (before -> after) -> List after
|
||||||
|
fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let list_var = var_store.fresh();
|
||||||
|
let func_var = var_store.fresh();
|
||||||
|
let ret_list_var = var_store.fresh();
|
||||||
|
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::ListMap,
|
||||||
|
args: vec![
|
||||||
|
(list_var, Var(Symbol::ARG_1)),
|
||||||
|
(func_var, Var(Symbol::ARG_2)),
|
||||||
|
],
|
||||||
|
ret_var: ret_list_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![(list_var, Symbol::ARG_1), (func_var, Symbol::ARG_2)],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
ret_list_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Num.rem : Int, Int -> Result Int [ DivByZero ]*
|
/// Num.rem : Int, Int -> Result Int [ DivByZero ]*
|
||||||
fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let num_var = var_store.fresh();
|
let num_var = var_store.fresh();
|
||||||
|
|
|
@ -9,19 +9,20 @@ use crate::num::{
|
||||||
use crate::pattern::{canonicalize_pattern, Pattern};
|
use crate::pattern::{canonicalize_pattern, Pattern};
|
||||||
use crate::procedure::References;
|
use crate::procedure::References;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
|
use inlinable_string::InlinableString;
|
||||||
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
|
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
use roc_module::low_level::LowLevel;
|
use roc_module::low_level::LowLevel;
|
||||||
use roc_module::operator::CalledVia;
|
use roc_module::operator::CalledVia;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_parse::ast;
|
use roc_parse::ast::{self, EscapedChar, StrLiteral};
|
||||||
use roc_parse::pattern::PatternType::*;
|
use roc_parse::pattern::PatternType::*;
|
||||||
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
use roc_types::subs::{VarStore, Variable};
|
use roc_types::subs::{VarStore, Variable};
|
||||||
use roc_types::types::Alias;
|
use roc_types::types::Alias;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::i64;
|
use std::{char, i64, u32};
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq)]
|
#[derive(Clone, Default, Debug, PartialEq)]
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
|
@ -55,8 +56,7 @@ pub enum Expr {
|
||||||
// Int and Float store a variable to generate better error messages
|
// Int and Float store a variable to generate better error messages
|
||||||
Int(Variable, i64),
|
Int(Variable, i64),
|
||||||
Float(Variable, f64),
|
Float(Variable, f64),
|
||||||
Str(Box<str>),
|
Str(InlinableString),
|
||||||
BlockStr(Box<str>),
|
|
||||||
List {
|
List {
|
||||||
list_var: Variable, // required for uniqueness of the list
|
list_var: Variable, // required for uniqueness of the list
|
||||||
elem_var: Variable,
|
elem_var: Variable,
|
||||||
|
@ -247,12 +247,7 @@ pub fn canonicalize_expr<'a>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::Expr::Str(string) => (Str((*string).into()), Output::default()),
|
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
||||||
ast::Expr::BlockStr(lines) => {
|
|
||||||
let joined = lines.iter().copied().collect::<Vec<&str>>().join("\n");
|
|
||||||
|
|
||||||
(BlockStr(joined.into()), Output::default())
|
|
||||||
}
|
|
||||||
ast::Expr::List(loc_elems) => {
|
ast::Expr::List(loc_elems) => {
|
||||||
if loc_elems.is_empty() {
|
if loc_elems.is_empty() {
|
||||||
(
|
(
|
||||||
|
@ -1045,8 +1040,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
other @ Num(_, _)
|
other @ Num(_, _)
|
||||||
| other @ Int(_, _)
|
| other @ Int(_, _)
|
||||||
| other @ Float(_, _)
|
| other @ Float(_, _)
|
||||||
| other @ Str(_)
|
| other @ Str { .. }
|
||||||
| other @ BlockStr(_)
|
|
||||||
| other @ RuntimeError(_)
|
| other @ RuntimeError(_)
|
||||||
| other @ EmptyRecord
|
| other @ EmptyRecord
|
||||||
| other @ Accessor { .. }
|
| other @ Accessor { .. }
|
||||||
|
@ -1323,3 +1317,170 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flatten_str_literal<'a>(
|
||||||
|
env: &mut Env<'a>,
|
||||||
|
var_store: &mut VarStore,
|
||||||
|
scope: &mut Scope,
|
||||||
|
literal: &StrLiteral<'a>,
|
||||||
|
) -> (Expr, Output) {
|
||||||
|
use ast::StrLiteral::*;
|
||||||
|
|
||||||
|
match literal {
|
||||||
|
PlainLine(str_slice) => (Expr::Str((*str_slice).into()), Output::default()),
|
||||||
|
Line(segments) => flatten_str_lines(env, var_store, scope, &[segments]),
|
||||||
|
Block(lines) => flatten_str_lines(env, var_store, scope, lines),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
||||||
|
match expr {
|
||||||
|
ast::Expr::Var { .. } => true,
|
||||||
|
ast::Expr::Access(sub_expr, _) => is_valid_interpolation(sub_expr),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StrSegment {
|
||||||
|
Interpolation(Located<Expr>),
|
||||||
|
Plaintext(InlinableString),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flatten_str_lines<'a>(
|
||||||
|
env: &mut Env<'a>,
|
||||||
|
var_store: &mut VarStore,
|
||||||
|
scope: &mut Scope,
|
||||||
|
lines: &[&[ast::StrSegment<'a>]],
|
||||||
|
) -> (Expr, Output) {
|
||||||
|
use ast::StrSegment::*;
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
let mut segments = Vec::new();
|
||||||
|
let mut output = Output::default();
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
for segment in line.iter() {
|
||||||
|
match segment {
|
||||||
|
Plaintext(string) => {
|
||||||
|
buf.push_str(string);
|
||||||
|
}
|
||||||
|
Unicode(loc_hex_digits) => match u32::from_str_radix(loc_hex_digits.value, 16) {
|
||||||
|
Ok(code_pt) => match char::from_u32(code_pt) {
|
||||||
|
Some(ch) => {
|
||||||
|
buf.push(ch);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
env.problem(Problem::InvalidUnicodeCodePoint(loc_hex_digits.region));
|
||||||
|
|
||||||
|
return (
|
||||||
|
Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePoint(
|
||||||
|
loc_hex_digits.region,
|
||||||
|
)),
|
||||||
|
output,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
env.problem(Problem::InvalidHexadecimal(loc_hex_digits.region));
|
||||||
|
|
||||||
|
return (
|
||||||
|
Expr::RuntimeError(RuntimeError::InvalidHexadecimal(
|
||||||
|
loc_hex_digits.region,
|
||||||
|
)),
|
||||||
|
output,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Interpolated(loc_expr) => {
|
||||||
|
if is_valid_interpolation(loc_expr.value) {
|
||||||
|
// Interpolations desugar to Str.concat calls
|
||||||
|
output.references.calls.insert(Symbol::STR_CONCAT);
|
||||||
|
|
||||||
|
if !buf.is_empty() {
|
||||||
|
segments.push(StrSegment::Plaintext(buf.into()));
|
||||||
|
|
||||||
|
buf = String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (loc_expr, new_output) = canonicalize_expr(
|
||||||
|
env,
|
||||||
|
var_store,
|
||||||
|
scope,
|
||||||
|
loc_expr.region,
|
||||||
|
loc_expr.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
output.union(new_output);
|
||||||
|
|
||||||
|
segments.push(StrSegment::Interpolation(loc_expr));
|
||||||
|
} else {
|
||||||
|
env.problem(Problem::InvalidInterpolation(loc_expr.region));
|
||||||
|
|
||||||
|
return (
|
||||||
|
Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)),
|
||||||
|
output,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EscapedChar(escaped) => buf.push(unescape_char(escaped)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !buf.is_empty() {
|
||||||
|
segments.push(StrSegment::Plaintext(buf.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
(desugar_str_segments(var_store, segments), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve stirng interpolations by desugaring a sequence of StrSegments
|
||||||
|
/// into nested calls to Str.concat
|
||||||
|
fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) -> Expr {
|
||||||
|
use StrSegment::*;
|
||||||
|
|
||||||
|
let mut iter = segments.into_iter().rev();
|
||||||
|
let mut loc_expr = match iter.next() {
|
||||||
|
Some(Plaintext(string)) => Located::new(0, 0, 0, 0, Expr::Str(string)),
|
||||||
|
Some(Interpolation(loc_expr)) => loc_expr,
|
||||||
|
None => {
|
||||||
|
// No segments? Empty string!
|
||||||
|
|
||||||
|
Located::new(0, 0, 0, 0, Expr::Str("".into()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for seg in iter {
|
||||||
|
let loc_new_expr = match seg {
|
||||||
|
Plaintext(string) => Located::new(0, 0, 0, 0, Expr::Str(string)),
|
||||||
|
Interpolation(loc_interpolated_expr) => loc_interpolated_expr,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fn_expr = Located::new(0, 0, 0, 0, Expr::Var(Symbol::STR_CONCAT));
|
||||||
|
let expr = Expr::Call(
|
||||||
|
Box::new((var_store.fresh(), fn_expr, var_store.fresh())),
|
||||||
|
vec![
|
||||||
|
(var_store.fresh(), loc_new_expr),
|
||||||
|
(var_store.fresh(), loc_expr),
|
||||||
|
],
|
||||||
|
CalledVia::Space,
|
||||||
|
);
|
||||||
|
|
||||||
|
loc_expr = Located::new(0, 0, 0, 0, expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
loc_expr.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the char that would have been originally parsed to
|
||||||
|
pub fn unescape_char(escaped: &EscapedChar) -> char {
|
||||||
|
use EscapedChar::*;
|
||||||
|
|
||||||
|
match escaped {
|
||||||
|
Backslash => '\\',
|
||||||
|
Quote => '"',
|
||||||
|
CarriageReturn => '\r',
|
||||||
|
Tab => '\t',
|
||||||
|
Newline => '\n',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -68,8 +68,6 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||||
| Nested(NonBase10Int { .. })
|
| Nested(NonBase10Int { .. })
|
||||||
| Str(_)
|
| Str(_)
|
||||||
| Nested(Str(_))
|
| Nested(Str(_))
|
||||||
| BlockStr(_)
|
|
||||||
| Nested(BlockStr(_))
|
|
||||||
| AccessorFunction(_)
|
| AccessorFunction(_)
|
||||||
| Nested(AccessorFunction(_))
|
| Nested(AccessorFunction(_))
|
||||||
| Var { .. }
|
| Var { .. }
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use crate::expr::{canonicalize_expr, Expr, Output};
|
use crate::expr::{canonicalize_expr, unescape_char, Expr, Output};
|
||||||
use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_parse::ast;
|
use roc_parse::ast::{self, StrLiteral, StrSegment};
|
||||||
use roc_parse::pattern::PatternType;
|
use roc_parse::pattern::PatternType;
|
||||||
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
|
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
|
@ -230,16 +230,8 @@ pub fn canonicalize_pattern<'a>(
|
||||||
ptype => unsupported_pattern(env, ptype, region),
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
},
|
},
|
||||||
|
|
||||||
StrLiteral(string) => match pattern_type {
|
StrLiteral(literal) => match pattern_type {
|
||||||
WhenBranch => {
|
WhenBranch => flatten_str_literal(literal),
|
||||||
// TODO report whether string was malformed
|
|
||||||
Pattern::StrLiteral((*string).into())
|
|
||||||
}
|
|
||||||
ptype => unsupported_pattern(env, ptype, region),
|
|
||||||
},
|
|
||||||
|
|
||||||
BlockStrLiteral(_lines) => match pattern_type {
|
|
||||||
WhenBranch => todo!("TODO block string literal pattern"),
|
|
||||||
ptype => unsupported_pattern(env, ptype, region),
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -473,3 +465,38 @@ fn add_bindings_from_patterns(
|
||||||
| UnsupportedPattern(_) => (),
|
| UnsupportedPattern(_) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flatten_str_literal(literal: &StrLiteral<'_>) -> Pattern {
|
||||||
|
use ast::StrLiteral::*;
|
||||||
|
|
||||||
|
match literal {
|
||||||
|
PlainLine(str_slice) => Pattern::StrLiteral((*str_slice).into()),
|
||||||
|
Line(segments) => flatten_str_lines(&[segments]),
|
||||||
|
Block(lines) => flatten_str_lines(lines),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flatten_str_lines(lines: &[&[StrSegment<'_>]]) -> Pattern {
|
||||||
|
use StrSegment::*;
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
for segment in line.iter() {
|
||||||
|
match segment {
|
||||||
|
Plaintext(string) => {
|
||||||
|
buf.push_str(string);
|
||||||
|
}
|
||||||
|
Unicode(loc_digits) => {
|
||||||
|
todo!("parse unicode digits {:?}", loc_digits);
|
||||||
|
}
|
||||||
|
Interpolated(loc_expr) => {
|
||||||
|
return Pattern::UnsupportedPattern(loc_expr.region);
|
||||||
|
}
|
||||||
|
EscapedChar(escaped) => buf.push(unescape_char(escaped)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern::StrLiteral(buf.into())
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
||||||
let state = State::new(input.as_bytes(), Attempting::Module);
|
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||||
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
||||||
let answer = parser.parse(&arena, state);
|
let answer = parser.parse(&arena, state);
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,10 @@ mod test_can {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn expr_str(contents: &str) -> Expr {
|
||||||
|
Expr::Str(contents.into())
|
||||||
|
}
|
||||||
|
|
||||||
// NUMBER LITERALS
|
// NUMBER LITERALS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1179,161 +1183,61 @@ mod test_can {
|
||||||
//}
|
//}
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//// STRING LITERALS
|
// STRING LITERALS
|
||||||
|
|
||||||
//
|
#[test]
|
||||||
// #[test]
|
fn string_with_valid_unicode_escapes() {
|
||||||
// fn string_with_valid_unicode_escapes() {
|
assert_can(r#""x\u(00A0)x""#, expr_str("x\u{00A0}x"));
|
||||||
// expect_parsed_str("x\u{00A0}x", r#""x\u{00A0}x""#);
|
assert_can(r#""x\u(101010)x""#, expr_str("x\u{101010}x"));
|
||||||
// expect_parsed_str("x\u{101010}x", r#""x\u{101010}x""#);
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn string_with_too_large_unicode_escape() {
|
// fn string_with_too_large_unicode_escape() {
|
||||||
// // Should be too big - max size should be 10FFFF.
|
// // Should be too big - max size should be 10FFFF.
|
||||||
// // (Rust has this restriction. I assume it's a good idea.)
|
// // (Rust has this restriction. I assume it's a good idea.)
|
||||||
// assert_malformed_str(
|
// assert_malformed_str(
|
||||||
// r#""abc\u{110000}def""#,
|
// r#""abc\u{110000}def""#,
|
||||||
// vec![Located::new(0, 7, 0, 12, Problem::UnicodeCodePointTooLarge)],
|
// vec![Located::new(0, 7, 0, 12, Problem::UnicodeCodePointTooLarge)],
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn string_with_no_unicode_digits() {
|
// fn string_with_no_unicode_digits() {
|
||||||
// // No digits specified
|
// // No digits specified
|
||||||
// assert_malformed_str(
|
// assert_malformed_str(
|
||||||
// r#""blah\u{}foo""#,
|
// r#""blah\u{}foo""#,
|
||||||
// vec![Located::new(0, 5, 0, 8, Problem::NoUnicodeDigits)],
|
// vec![Located::new(0, 5, 0, 8, Problem::NoUnicodeDigits)],
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn string_with_no_unicode_opening_brace() {
|
// fn string_with_no_unicode_opening_brace() {
|
||||||
// // No opening curly brace. It can't be sure if the closing brace
|
// // No opening curly brace. It can't be sure if the closing brace
|
||||||
// // was intended to be a closing brace for the unicode escape, so
|
// // was intended to be a closing brace for the unicode escape, so
|
||||||
// // report that there were no digits specified.
|
// // report that there were no digits specified.
|
||||||
// assert_malformed_str(
|
// assert_malformed_str(
|
||||||
// r#""abc\u00A0}def""#,
|
// r#""abc\u00A0}def""#,
|
||||||
// vec![Located::new(0, 4, 0, 5, Problem::NoUnicodeDigits)],
|
// vec![Located::new(0, 4, 0, 5, Problem::NoUnicodeDigits)],
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn string_with_no_unicode_closing_brace() {
|
// fn string_with_no_unicode_closing_brace() {
|
||||||
// // No closing curly brace
|
// // No closing curly brace
|
||||||
// assert_malformed_str(
|
// assert_malformed_str(
|
||||||
// r#""blah\u{stuff""#,
|
// r#""blah\u{stuff""#,
|
||||||
// vec![Located::new(0, 5, 0, 12, Problem::MalformedEscapedUnicode)],
|
// vec![Located::new(0, 5, 0, 12, Problem::MalformedEscapedUnicode)],
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn string_with_no_unicode_braces() {
|
// fn string_with_no_unicode_braces() {
|
||||||
// // No curly braces
|
// // No curly braces
|
||||||
// assert_malformed_str(
|
// assert_malformed_str(
|
||||||
// r#""zzzz\uzzzzz""#,
|
// r#""zzzz\uzzzzz""#,
|
||||||
// vec![Located::new(0, 5, 0, 6, Problem::NoUnicodeDigits)],
|
// vec![Located::new(0, 5, 0, 6, Problem::NoUnicodeDigits)],
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn string_with_interpolation_at_start() {
|
|
||||||
// let input = indoc!(
|
|
||||||
// r#"
|
|
||||||
// "\(abc)defg"
|
|
||||||
// "#
|
|
||||||
// );
|
|
||||||
// let (args, ret) = (vec![("", Located::new(0, 2, 0, 4, Var("abc")))], "defg");
|
|
||||||
// let arena = Bump::new();
|
|
||||||
// let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
// assert_eq!(
|
|
||||||
// Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
// actual
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn string_with_interpolation_at_end() {
|
|
||||||
// let input = indoc!(
|
|
||||||
// r#"
|
|
||||||
// "abcd\(efg)"
|
|
||||||
// "#
|
|
||||||
// );
|
|
||||||
// let (args, ret) = (vec![("abcd", Located::new(0, 6, 0, 8, Var("efg")))], "");
|
|
||||||
// let arena = Bump::new();
|
|
||||||
// let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
// assert_eq!(
|
|
||||||
// Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
// actual
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn string_with_interpolation_in_middle() {
|
|
||||||
// let input = indoc!(
|
|
||||||
// r#"
|
|
||||||
// "abc\(defg)hij"
|
|
||||||
// "#
|
|
||||||
// );
|
|
||||||
// let (args, ret) = (vec![("abc", Located::new(0, 5, 0, 8, Var("defg")))], "hij");
|
|
||||||
// let arena = Bump::new();
|
|
||||||
// let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
// assert_eq!(
|
|
||||||
// Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
// actual
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn string_with_two_interpolations_in_middle() {
|
|
||||||
// let input = indoc!(
|
|
||||||
// r#"
|
|
||||||
// "abc\(defg)hi\(jkl)mn"
|
|
||||||
// "#
|
|
||||||
// );
|
|
||||||
// let (args, ret) = (
|
|
||||||
// vec![
|
|
||||||
// ("abc", Located::new(0, 5, 0, 8, Var("defg"))),
|
|
||||||
// ("hi", Located::new(0, 14, 0, 16, Var("jkl"))),
|
|
||||||
// ],
|
|
||||||
// "mn",
|
|
||||||
// );
|
|
||||||
// let arena = Bump::new();
|
|
||||||
// let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
// assert_eq!(
|
|
||||||
// Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
// actual
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn string_with_four_interpolations() {
|
|
||||||
// let input = indoc!(
|
|
||||||
// r#"
|
|
||||||
// "\(abc)def\(ghi)jkl\(mno)pqrs\(tuv)"
|
|
||||||
// "#
|
|
||||||
// );
|
|
||||||
// let (args, ret) = (
|
|
||||||
// vec![
|
|
||||||
// ("", Located::new(0, 2, 0, 4, Var("abc"))),
|
|
||||||
// ("def", Located::new(0, 11, 0, 13, Var("ghi"))),
|
|
||||||
// ("jkl", Located::new(0, 20, 0, 22, Var("mno"))),
|
|
||||||
// ("pqrs", Located::new(0, 30, 0, 32, Var("tuv"))),
|
|
||||||
// ],
|
|
||||||
// "",
|
|
||||||
// );
|
|
||||||
// let arena = Bump::new();
|
|
||||||
// let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
// assert_eq!(
|
|
||||||
// Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
// actual
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn string_with_escaped_interpolation() {
|
// fn string_with_escaped_interpolation() {
|
||||||
|
@ -1341,13 +1245,12 @@ mod test_can {
|
||||||
// // This should NOT be string interpolation, because of the \\
|
// // This should NOT be string interpolation, because of the \\
|
||||||
// indoc!(
|
// indoc!(
|
||||||
// r#"
|
// r#"
|
||||||
// "abcd\\(efg)hij"
|
// "abcd\\(efg)hij"
|
||||||
// "#
|
// "#
|
||||||
// ),
|
// ),
|
||||||
// Str(r#"abcd\(efg)hij"#.into()),
|
// Str(r#"abcd\(efg)hij"#.into()),
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn string_without_escape() {
|
// fn string_without_escape() {
|
||||||
|
@ -1384,4 +1287,6 @@ mod test_can {
|
||||||
// TODO test hex/oct/binary conversion to numbers
|
// TODO test hex/oct/binary conversion to numbers
|
||||||
//
|
//
|
||||||
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence!
|
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence!
|
||||||
|
//
|
||||||
|
// TODO test for multiline block string literals in pattern matches
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,7 @@ pub fn constrain_expr(
|
||||||
|
|
||||||
exists(vars, And(cons))
|
exists(vars, And(cons))
|
||||||
}
|
}
|
||||||
Str(_) | BlockStr(_) => Eq(str_type(), expected, Category::Str, region),
|
Str(_) => Eq(str_type(), expected, Category::Str, region),
|
||||||
List {
|
List {
|
||||||
elem_var,
|
elem_var,
|
||||||
loc_elems,
|
loc_elems,
|
||||||
|
@ -758,7 +758,10 @@ pub fn constrain_expr(
|
||||||
Box::new(Type::Variable(*ext_var)),
|
Box::new(Type::Variable(*ext_var)),
|
||||||
),
|
),
|
||||||
expected.clone(),
|
expected.clone(),
|
||||||
Category::TagApply(name.clone()),
|
Category::TagApply {
|
||||||
|
tag_name: name.clone(),
|
||||||
|
args_count: arguments.len(),
|
||||||
|
},
|
||||||
region,
|
region,
|
||||||
);
|
);
|
||||||
let ast_con = Eq(
|
let ast_con = Eq(
|
||||||
|
|
|
@ -507,7 +507,7 @@ pub fn constrain_expr(
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
BlockStr(_) | Str(_) => {
|
Str(_) => {
|
||||||
let uniq_type = var_store.fresh();
|
let uniq_type = var_store.fresh();
|
||||||
let inferred = str_type(Bool::variable(uniq_type));
|
let inferred = str_type(Bool::variable(uniq_type));
|
||||||
|
|
||||||
|
@ -624,13 +624,19 @@ pub fn constrain_expr(
|
||||||
let union_con = Eq(
|
let union_con = Eq(
|
||||||
union_type,
|
union_type,
|
||||||
expected.clone(),
|
expected.clone(),
|
||||||
Category::TagApply(name.clone()),
|
Category::TagApply {
|
||||||
|
tag_name: name.clone(),
|
||||||
|
args_count: arguments.len(),
|
||||||
|
},
|
||||||
region,
|
region,
|
||||||
);
|
);
|
||||||
let ast_con = Eq(
|
let ast_con = Eq(
|
||||||
Type::Variable(*variant_var),
|
Type::Variable(*variant_var),
|
||||||
expected,
|
expected,
|
||||||
Category::TagApply(name.clone()),
|
Category::TagApply {
|
||||||
|
tag_name: name.clone(),
|
||||||
|
args_count: arguments.len(),
|
||||||
|
},
|
||||||
region,
|
region,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::spaces::{
|
||||||
};
|
};
|
||||||
use bumpalo::collections::{String, Vec};
|
use bumpalo::collections::{String, Vec};
|
||||||
use roc_module::operator::{self, BinOp};
|
use roc_module::operator::{self, BinOp};
|
||||||
|
use roc_parse::ast::StrSegment;
|
||||||
use roc_parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch};
|
use roc_parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch};
|
||||||
use roc_region::all::Located;
|
use roc_region::all::Located;
|
||||||
|
|
||||||
|
@ -28,7 +29,6 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||||
Float(_)
|
Float(_)
|
||||||
| Num(_)
|
| Num(_)
|
||||||
| NonBase10Int { .. }
|
| NonBase10Int { .. }
|
||||||
| Str(_)
|
|
||||||
| Access(_, _)
|
| Access(_, _)
|
||||||
| AccessorFunction(_)
|
| AccessorFunction(_)
|
||||||
| Var { .. }
|
| Var { .. }
|
||||||
|
@ -42,7 +42,20 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||||
|
|
||||||
List(elems) => elems.iter().any(|loc_expr| loc_expr.is_multiline()),
|
List(elems) => elems.iter().any(|loc_expr| loc_expr.is_multiline()),
|
||||||
|
|
||||||
BlockStr(lines) => lines.len() > 1,
|
Str(literal) => {
|
||||||
|
use roc_parse::ast::StrLiteral::*;
|
||||||
|
|
||||||
|
match literal {
|
||||||
|
PlainLine(_) | Line(_) => {
|
||||||
|
// If this had any newlines, it'd have parsed as Block.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Block(lines) => {
|
||||||
|
// Block strings don't *have* to be multiline!
|
||||||
|
lines.len() > 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Apply(loc_expr, args, _) => {
|
Apply(loc_expr, args, _) => {
|
||||||
loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline())
|
loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline())
|
||||||
}
|
}
|
||||||
|
@ -112,9 +125,53 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||||
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||||
buf.push(')');
|
buf.push(')');
|
||||||
}
|
}
|
||||||
Str(string) => {
|
Str(literal) => {
|
||||||
|
use roc_parse::ast::StrLiteral::*;
|
||||||
|
|
||||||
buf.push('"');
|
buf.push('"');
|
||||||
buf.push_str(string);
|
match literal {
|
||||||
|
PlainLine(string) => {
|
||||||
|
buf.push_str(string);
|
||||||
|
}
|
||||||
|
Line(segments) => {
|
||||||
|
for seg in segments.iter() {
|
||||||
|
format_str_segment(seg, buf, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Block(lines) => {
|
||||||
|
buf.push_str("\"\"");
|
||||||
|
|
||||||
|
if lines.len() > 1 {
|
||||||
|
// Since we have multiple lines, format this with
|
||||||
|
// the `"""` symbols on their own lines, and the
|
||||||
|
newline(buf, indent);
|
||||||
|
|
||||||
|
for segments in lines.iter() {
|
||||||
|
for seg in segments.iter() {
|
||||||
|
format_str_segment(seg, buf, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
newline(buf, indent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is a single-line block string, for example:
|
||||||
|
//
|
||||||
|
// """Whee, "quotes" inside quotes!"""
|
||||||
|
|
||||||
|
// This loop will run either 0 or 1 times.
|
||||||
|
for segments in lines.iter() {
|
||||||
|
for seg in segments.iter() {
|
||||||
|
format_str_segment(seg, buf, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't print a newline here, because we either
|
||||||
|
// just printed 1 or 0 lines.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str("\"\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
buf.push('"');
|
buf.push('"');
|
||||||
}
|
}
|
||||||
Var { module_name, ident } => {
|
Var { module_name, ident } => {
|
||||||
|
@ -152,13 +209,6 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||||
buf.push(')');
|
buf.push(')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BlockStr(lines) => {
|
|
||||||
buf.push_str("\"\"\"");
|
|
||||||
for line in lines.iter() {
|
|
||||||
buf.push_str(line);
|
|
||||||
}
|
|
||||||
buf.push_str("\"\"\"");
|
|
||||||
}
|
|
||||||
Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => {
|
Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => {
|
||||||
buf.push_str(string)
|
buf.push_str(string)
|
||||||
}
|
}
|
||||||
|
@ -252,6 +302,36 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_str_segment<'a>(seg: &StrSegment<'a>, buf: &mut String<'a>, indent: u16) {
|
||||||
|
use StrSegment::*;
|
||||||
|
|
||||||
|
match seg {
|
||||||
|
Plaintext(string) => {
|
||||||
|
buf.push_str(string);
|
||||||
|
}
|
||||||
|
Unicode(loc_str) => {
|
||||||
|
buf.push_str("\\u(");
|
||||||
|
buf.push_str(loc_str.value); // e.g. "00A0" in "\u(00A0)"
|
||||||
|
buf.push(')');
|
||||||
|
}
|
||||||
|
EscapedChar(escaped) => {
|
||||||
|
buf.push('\\');
|
||||||
|
buf.push(escaped.to_parsed_char());
|
||||||
|
}
|
||||||
|
Interpolated(loc_expr) => {
|
||||||
|
buf.push_str("\\(");
|
||||||
|
// e.g. (name) in "Hi, \(name)!"
|
||||||
|
loc_expr.value.format_with_options(
|
||||||
|
buf,
|
||||||
|
Parens::NotNeeded, // We already printed parens!
|
||||||
|
Newlines::No, // Interpolations can never have newlines
|
||||||
|
indent,
|
||||||
|
);
|
||||||
|
buf.push(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn fmt_bin_op<'a>(
|
fn fmt_bin_op<'a>(
|
||||||
buf: &mut String<'a>,
|
buf: &mut String<'a>,
|
||||||
loc_left_side: &'a Located<Expr<'a>>,
|
loc_left_side: &'a Located<Expr<'a>>,
|
||||||
|
|
|
@ -37,7 +37,6 @@ impl<'a> Formattable<'a> for Pattern<'a> {
|
||||||
| Pattern::NonBase10Literal { .. }
|
| Pattern::NonBase10Literal { .. }
|
||||||
| Pattern::FloatLiteral(_)
|
| Pattern::FloatLiteral(_)
|
||||||
| Pattern::StrLiteral(_)
|
| Pattern::StrLiteral(_)
|
||||||
| Pattern::BlockStrLiteral(_)
|
|
||||||
| Pattern::Underscore
|
| Pattern::Underscore
|
||||||
| Pattern::Malformed(_)
|
| Pattern::Malformed(_)
|
||||||
| Pattern::QualifiedIdentifier { .. } => false,
|
| Pattern::QualifiedIdentifier { .. } => false,
|
||||||
|
@ -126,11 +125,8 @@ impl<'a> Formattable<'a> for Pattern<'a> {
|
||||||
buf.push_str(string);
|
buf.push_str(string);
|
||||||
}
|
}
|
||||||
FloatLiteral(string) => buf.push_str(string),
|
FloatLiteral(string) => buf.push_str(string),
|
||||||
StrLiteral(string) => buf.push_str(string),
|
StrLiteral(literal) => {
|
||||||
BlockStrLiteral(lines) => {
|
todo!("Format string literal: {:?}", literal);
|
||||||
for line in *lines {
|
|
||||||
buf.push_str(line)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Underscore => buf.push('_'),
|
Underscore => buf.push('_'),
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ mod test_fmt {
|
||||||
use roc_parse::parser::{Fail, Parser, State};
|
use roc_parse::parser::{Fail, Parser, State};
|
||||||
|
|
||||||
fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, Fail> {
|
fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, Fail> {
|
||||||
let state = State::new(input.as_bytes(), Attempting::Module);
|
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||||
let parser = space0_before(loc!(roc_parse::expr::expr(0)), 0);
|
let parser = space0_before(loc!(roc_parse::expr::expr(0)), 0);
|
||||||
let answer = parser.parse(&arena, state);
|
let answer = parser.parse(&arena, state);
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ mod test_fmt {
|
||||||
fn escaped_unicode_string() {
|
fn escaped_unicode_string() {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
"unicode: \u{A00A}!"
|
"unicode: \u(A00A)!"
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -206,47 +206,47 @@ mod test_fmt {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
fn empty_block_string() {
|
// fn empty_block_string() {
|
||||||
expr_formats_same(indoc!(
|
// expr_formats_same(indoc!(
|
||||||
r#"
|
// r#"
|
||||||
""""""
|
// """"""
|
||||||
"#
|
// "#
|
||||||
));
|
// ));
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
fn basic_block_string() {
|
// fn basic_block_string() {
|
||||||
expr_formats_same(indoc!(
|
// expr_formats_same(indoc!(
|
||||||
r#"
|
// r#"
|
||||||
"""blah"""
|
// """blah"""
|
||||||
"#
|
// "#
|
||||||
));
|
// ));
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
fn newlines_block_string() {
|
// fn newlines_block_string() {
|
||||||
expr_formats_same(indoc!(
|
// expr_formats_same(indoc!(
|
||||||
r#"
|
// r#"
|
||||||
"""blah
|
// """blah
|
||||||
spam
|
// spam
|
||||||
foo"""
|
// foo"""
|
||||||
"#
|
// "#
|
||||||
));
|
// ));
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
fn quotes_block_string() {
|
// fn quotes_block_string() {
|
||||||
expr_formats_same(indoc!(
|
// expr_formats_same(indoc!(
|
||||||
r#"
|
// r#"
|
||||||
"""
|
// """
|
||||||
|
|
||||||
"" \""" ""\"
|
// "" \""" ""\"
|
||||||
|
|
||||||
"""
|
// """
|
||||||
"#
|
// "#
|
||||||
));
|
// ));
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero() {
|
fn zero() {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::layout_id::LayoutIds;
|
use crate::layout_id::LayoutIds;
|
||||||
use crate::llvm::build_list::{
|
use crate::llvm::build_list::{
|
||||||
allocate_list, build_basic_phi2, clone_nonempty_list, empty_list, empty_polymorphic_list,
|
allocate_list, build_basic_phi2, clone_nonempty_list, empty_list, empty_polymorphic_list,
|
||||||
incrementing_index_loop, list_append, list_concat, list_get_unsafe, list_is_not_empty,
|
incrementing_elem_loop, list_append, list_concat, list_get_unsafe, list_is_not_empty,
|
||||||
list_join, list_len, list_prepend, list_repeat, list_reverse, list_set, list_single,
|
list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat, list_reverse, list_set,
|
||||||
load_list_ptr,
|
list_single, list_walk_right, load_list_ptr, store_list, LoopListArg,
|
||||||
};
|
};
|
||||||
use crate::llvm::compare::{build_eq, build_neq};
|
use crate::llvm::compare::{build_eq, build_neq};
|
||||||
use crate::llvm::convert::{
|
use crate::llvm::convert::{
|
||||||
|
@ -46,7 +46,6 @@ pub enum OptLevel {
|
||||||
Optimize,
|
Optimize,
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub type Scope<'a, 'ctx> = ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>;
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq)]
|
#[derive(Default, Debug, Clone, PartialEq)]
|
||||||
pub struct Scope<'a, 'ctx> {
|
pub struct Scope<'a, 'ctx> {
|
||||||
symbols: ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>,
|
symbols: ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>,
|
||||||
|
@ -1999,9 +1998,9 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
// List.reverse : List elem -> List elem
|
// List.reverse : List elem -> List elem
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
let list = &args[0];
|
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
||||||
|
|
||||||
list_reverse(env, parent, scope, list)
|
list_reverse(env, parent, list, list_layout)
|
||||||
}
|
}
|
||||||
ListConcat => {
|
ListConcat => {
|
||||||
debug_assert_eq!(args.len(), 2);
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
@ -2012,6 +2011,47 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
list_concat(env, parent, first_list, second_list, list_layout)
|
list_concat(env, parent, first_list, second_list, list_layout)
|
||||||
}
|
}
|
||||||
|
ListMap => {
|
||||||
|
// List.map : List before, (before -> after) -> List after
|
||||||
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
|
||||||
|
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
||||||
|
|
||||||
|
let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]);
|
||||||
|
|
||||||
|
list_map(env, parent, func, func_layout, list, list_layout)
|
||||||
|
}
|
||||||
|
ListKeepIf => {
|
||||||
|
// List.keepIf : List elem, (elem -> Bool) -> List elem
|
||||||
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
|
||||||
|
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
||||||
|
|
||||||
|
let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]);
|
||||||
|
|
||||||
|
list_keep_if(env, parent, func, func_layout, list, list_layout)
|
||||||
|
}
|
||||||
|
ListWalkRight => {
|
||||||
|
// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
|
||||||
|
debug_assert_eq!(args.len(), 3);
|
||||||
|
|
||||||
|
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
||||||
|
|
||||||
|
let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]);
|
||||||
|
|
||||||
|
let (default, default_layout) = load_symbol_and_layout(env, scope, &args[2]);
|
||||||
|
|
||||||
|
list_walk_right(
|
||||||
|
env,
|
||||||
|
parent,
|
||||||
|
list,
|
||||||
|
list_layout,
|
||||||
|
func,
|
||||||
|
func_layout,
|
||||||
|
default,
|
||||||
|
default_layout,
|
||||||
|
)
|
||||||
|
}
|
||||||
ListAppend => {
|
ListAppend => {
|
||||||
// List.append : List elem, elem -> List elem
|
// List.append : List elem, elem -> List elem
|
||||||
debug_assert_eq!(args.len(), 2);
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
@ -2035,9 +2075,8 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
let (list, outer_list_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
let (list, outer_list_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
||||||
let outer_wrapper_struct = list.into_struct_value();
|
|
||||||
|
|
||||||
list_join(env, parent, outer_wrapper_struct, outer_list_layout)
|
list_join(env, parent, list, outer_list_layout)
|
||||||
}
|
}
|
||||||
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumToFloat => {
|
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumToFloat => {
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
@ -2317,14 +2356,9 @@ fn str_concat<'a, 'ctx, 'env>(
|
||||||
let combined_str_ptr = allocate_list(env, &CHAR_LAYOUT, combined_str_len);
|
let combined_str_ptr = allocate_list(env, &CHAR_LAYOUT, combined_str_len);
|
||||||
|
|
||||||
// FIRST LOOP
|
// FIRST LOOP
|
||||||
let first_loop = |first_index| {
|
let first_str_ptr = load_list_ptr(builder, first_str_wrapper, ptr_type);
|
||||||
let first_str_ptr = load_list_ptr(builder, first_str_wrapper, ptr_type);
|
|
||||||
|
|
||||||
// The pointer to the element in the first list
|
|
||||||
let first_str_char_ptr = unsafe {
|
|
||||||
builder.build_in_bounds_gep(first_str_ptr, &[first_index], "load_index")
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let first_loop = |first_index, first_str_elem| {
|
||||||
// The pointer to the element in the combined list
|
// The pointer to the element in the combined list
|
||||||
let combined_str_elem_ptr = unsafe {
|
let combined_str_elem_ptr = unsafe {
|
||||||
builder.build_in_bounds_gep(
|
builder.build_in_bounds_gep(
|
||||||
|
@ -2334,19 +2368,20 @@ fn str_concat<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let first_str_elem = builder.build_load(first_str_char_ptr, "get_elem");
|
|
||||||
|
|
||||||
// Mutate the new array in-place to change the element.
|
// Mutate the new array in-place to change the element.
|
||||||
builder.build_store(combined_str_elem_ptr, first_str_elem);
|
builder.build_store(combined_str_elem_ptr, first_str_elem);
|
||||||
};
|
};
|
||||||
|
|
||||||
let index_name = "#index";
|
let index_name = "#index";
|
||||||
|
|
||||||
let index_alloca = incrementing_index_loop(
|
let index_alloca = incrementing_elem_loop(
|
||||||
builder,
|
builder,
|
||||||
parent,
|
parent,
|
||||||
ctx,
|
ctx,
|
||||||
first_str_len,
|
LoopListArg {
|
||||||
|
ptr: first_str_ptr,
|
||||||
|
len: first_str_len,
|
||||||
|
},
|
||||||
index_name,
|
index_name,
|
||||||
None,
|
None,
|
||||||
first_loop,
|
first_loop,
|
||||||
|
@ -2356,14 +2391,9 @@ fn str_concat<'a, 'ctx, 'env>(
|
||||||
builder.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
builder.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
||||||
|
|
||||||
// SECOND LOOP
|
// SECOND LOOP
|
||||||
let second_loop = |second_index| {
|
let second_str_ptr = load_list_ptr(builder, second_str_wrapper, ptr_type);
|
||||||
let second_str_ptr = load_list_ptr(builder, second_str_wrapper, ptr_type);
|
|
||||||
|
|
||||||
// The pointer to the element in the second list
|
|
||||||
let second_str_char_ptr = unsafe {
|
|
||||||
builder.build_in_bounds_gep(second_str_ptr, &[second_index], "load_index")
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let second_loop = |second_index, second_str_elem| {
|
||||||
// The pointer to the element in the combined str.
|
// The pointer to the element in the combined str.
|
||||||
// Note that the pointer does not start at the index
|
// Note that the pointer does not start at the index
|
||||||
// 0, it starts at the index of first_str_len. In that
|
// 0, it starts at the index of first_str_len. In that
|
||||||
|
@ -2382,55 +2412,24 @@ fn str_concat<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let second_str_elem = builder.build_load(second_str_char_ptr, "get_elem");
|
|
||||||
|
|
||||||
// Mutate the new array in-place to change the element.
|
// Mutate the new array in-place to change the element.
|
||||||
builder.build_store(combined_str_char_ptr, second_str_elem);
|
builder.build_store(combined_str_char_ptr, second_str_elem);
|
||||||
};
|
};
|
||||||
|
|
||||||
incrementing_index_loop(
|
incrementing_elem_loop(
|
||||||
builder,
|
builder,
|
||||||
parent,
|
parent,
|
||||||
ctx,
|
ctx,
|
||||||
second_str_len,
|
LoopListArg {
|
||||||
|
ptr: second_str_ptr,
|
||||||
|
len: second_str_len,
|
||||||
|
},
|
||||||
index_name,
|
index_name,
|
||||||
Some(index_alloca),
|
Some(index_alloca),
|
||||||
second_loop,
|
second_loop,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ptr_bytes = env.ptr_bytes;
|
store_list(env, combined_str_ptr, combined_str_len)
|
||||||
let int_type = ptr_int(ctx, ptr_bytes);
|
|
||||||
let ptr_as_int = builder.build_ptr_to_int(combined_str_ptr, int_type, "list_cast_ptr");
|
|
||||||
|
|
||||||
let struct_type = collection(ctx, ptr_bytes);
|
|
||||||
|
|
||||||
let mut struct_val;
|
|
||||||
|
|
||||||
// Store the pointer
|
|
||||||
struct_val = builder
|
|
||||||
.build_insert_value(
|
|
||||||
struct_type.get_undef(),
|
|
||||||
ptr_as_int,
|
|
||||||
Builtin::WRAPPER_PTR,
|
|
||||||
"insert_ptr",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Store the length
|
|
||||||
struct_val = builder
|
|
||||||
.build_insert_value(
|
|
||||||
struct_val,
|
|
||||||
combined_str_len,
|
|
||||||
Builtin::WRAPPER_LEN,
|
|
||||||
"insert_len",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
builder.build_bitcast(
|
|
||||||
struct_val.into_struct_value(),
|
|
||||||
collection(ctx, ptr_bytes),
|
|
||||||
"cast_collection",
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
build_basic_phi2(
|
build_basic_phi2(
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -34,7 +34,15 @@ mod gen_list {
|
||||||
fn list_append() {
|
fn list_append() {
|
||||||
assert_evals_to!("List.append [1] 2", &[1, 2], &'static [i64]);
|
assert_evals_to!("List.append [1] 2", &[1, 2], &'static [i64]);
|
||||||
assert_evals_to!("List.append [1, 1] 2", &[1, 1, 2], &'static [i64]);
|
assert_evals_to!("List.append [1, 1] 2", &[1, 1, 2], &'static [i64]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_append_to_empty_list() {
|
||||||
assert_evals_to!("List.append [] 3", &[3], &'static [i64]);
|
assert_evals_to!("List.append [] 3", &[3], &'static [i64]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_append_to_empty_list_of_int() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -48,11 +56,19 @@ mod gen_list {
|
||||||
&[3, 3],
|
&[3, 3],
|
||||||
&'static [i64]
|
&'static [i64]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_append_bools() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
"List.append [ True, False ] True",
|
"List.append [ True, False ] True",
|
||||||
&[true, false, true],
|
&[true, false, true],
|
||||||
&'static [bool]
|
&'static [bool]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_append_longer_list() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
"List.append [ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 ] 23",
|
"List.append [ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 ] 23",
|
||||||
&[11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
|
&[11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
@ -78,12 +94,19 @@ mod gen_list {
|
||||||
&[6, 4],
|
&[6, 4],
|
||||||
&'static [i64]
|
&'static [i64]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_prepend_bools() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
"List.prepend [ True, False ] True",
|
"List.prepend [ True, False ] True",
|
||||||
&[true, true, false],
|
&[true, true, false],
|
||||||
&'static [bool]
|
&'static [bool]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_prepend_big_list() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
"List.prepend [ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100 ] 9",
|
"List.prepend [ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100 ] 9",
|
||||||
&[9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100],
|
&[9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100],
|
||||||
|
@ -92,20 +115,292 @@ mod gen_list {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_join() {
|
fn list_walk_right_empty_all_inline() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
List.walkRight [0x1] (\a, b -> a + b) 0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
empty : List Int
|
||||||
|
empty =
|
||||||
|
[]
|
||||||
|
|
||||||
|
List.walkRight empty (\a, b -> a + b) 0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_keep_if_empty_list_of_int() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
empty : List Int
|
||||||
|
empty =
|
||||||
|
[]
|
||||||
|
|
||||||
|
List.keepIf empty (\x -> True)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_keep_if_empty_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
alwaysTrue : Int -> Bool
|
||||||
|
alwaysTrue = \_ ->
|
||||||
|
True
|
||||||
|
|
||||||
|
|
||||||
|
List.keepIf [] alwaysTrue
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_keep_if_always_true_for_non_empty_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
alwaysTrue : Int -> Bool
|
||||||
|
alwaysTrue = \i ->
|
||||||
|
True
|
||||||
|
|
||||||
|
oneThroughEight : List Int
|
||||||
|
oneThroughEight =
|
||||||
|
[1,2,3,4,5,6,7,8]
|
||||||
|
|
||||||
|
List.keepIf oneThroughEight alwaysTrue
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_keep_if_always_false_for_non_empty_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
alwaysFalse : Int -> Bool
|
||||||
|
alwaysFalse = \i ->
|
||||||
|
False
|
||||||
|
|
||||||
|
List.keepIf [1,2,3,4,5,6,7,8] alwaysFalse
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_keep_if_one() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
intIsLessThanThree : Int -> Bool
|
||||||
|
intIsLessThanThree = \i ->
|
||||||
|
i < 3
|
||||||
|
|
||||||
|
List.keepIf [1,2,3,4,5,6,7,8] intIsLessThanThree
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[1, 2],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// "panicked at 'not yet implemented: Handle equals for builtin layouts Str == Str'"
|
||||||
|
//
|
||||||
|
// #[test]
|
||||||
|
// fn list_keep_if_str_is_hello() {
|
||||||
|
// assert_evals_to!(
|
||||||
|
// indoc!(
|
||||||
|
// r#"
|
||||||
|
// strIsHello : Str -> Bool
|
||||||
|
// strIsHello = \str ->
|
||||||
|
// str == "Hello"
|
||||||
|
//
|
||||||
|
// List.keepIf ["Hello", "Hello", "Goodbye"] strIsHello
|
||||||
|
// "#
|
||||||
|
// ),
|
||||||
|
// &["Hello", "Hello"],
|
||||||
|
// &'static [&'static str]
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_map_on_empty_list_with_int_layout() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
empty : List Int
|
||||||
|
empty =
|
||||||
|
[]
|
||||||
|
|
||||||
|
List.map empty (\x -> x)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_map_on_non_empty_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
nonEmpty : List Int
|
||||||
|
nonEmpty =
|
||||||
|
[ 1 ]
|
||||||
|
|
||||||
|
List.map nonEmpty (\x -> x)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[1],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_map_changes_input() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
nonEmpty : List Int
|
||||||
|
nonEmpty =
|
||||||
|
[ 1 ]
|
||||||
|
|
||||||
|
List.map nonEmpty (\x -> x + 1)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[2],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_map_on_big_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
nonEmpty : List Int
|
||||||
|
nonEmpty =
|
||||||
|
[ 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 ]
|
||||||
|
|
||||||
|
List.map nonEmpty (\x -> x * 2)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[2, 4, 6, 8, 10, 2, 4, 6, 8, 10, 2, 4, 6, 8, 10, 2, 4, 6, 8, 10, 2, 4, 6, 8, 10],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_map_with_type_change() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
nonEmpty : List Int
|
||||||
|
nonEmpty =
|
||||||
|
[ 1, 1, -4, 1, 2 ]
|
||||||
|
|
||||||
|
|
||||||
|
List.map nonEmpty (\x -> x > 0)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[true, true, false, true, true],
|
||||||
|
&'static [bool]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_map_using_defined_function() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
nonEmpty : List Int
|
||||||
|
nonEmpty =
|
||||||
|
[ 2, 2, -4, 2, 3 ]
|
||||||
|
|
||||||
|
greaterThanOne : Int -> Bool
|
||||||
|
greaterThanOne = \i ->
|
||||||
|
i > 1
|
||||||
|
|
||||||
|
List.map nonEmpty greaterThanOne
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[true, true, false, true, true],
|
||||||
|
&'static [bool]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_map_all_inline() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
List.map [] (\x -> x > 0)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[],
|
||||||
|
&'static [bool]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_join_empty_list() {
|
||||||
assert_evals_to!("List.join []", &[], &'static [i64]);
|
assert_evals_to!("List.join []", &[], &'static [i64]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_join_one_list() {
|
||||||
assert_evals_to!("List.join [ [1, 2, 3 ] ]", &[1, 2, 3], &'static [i64]);
|
assert_evals_to!("List.join [ [1, 2, 3 ] ]", &[1, 2, 3], &'static [i64]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_join_two_non_empty_lists() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
"List.join [ [1, 2, 3 ] , [4 ,5, 6] ]",
|
"List.join [ [1, 2, 3 ] , [4 ,5, 6] ]",
|
||||||
&[1, 2, 3, 4, 5, 6],
|
&[1, 2, 3, 4, 5, 6],
|
||||||
&'static [i64]
|
&'static [i64]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_join_two_non_empty_lists_of_float() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
"List.join [ [ 1.2, 1.1 ], [ 2.1, 2.2 ] ]",
|
"List.join [ [ 1.2, 1.1 ], [ 2.1, 2.2 ] ]",
|
||||||
&[1.2, 1.1, 2.1, 2.2],
|
&[1.2, 1.1, 2.1, 2.2],
|
||||||
&'static [f64]
|
&'static [f64]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_join_to_big_list() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -127,7 +422,10 @@ mod gen_list {
|
||||||
],
|
],
|
||||||
&'static [f64]
|
&'static [f64]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_join_defined_empty_list() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -141,9 +439,15 @@ mod gen_list {
|
||||||
&[0.2, 11.11],
|
&[0.2, 11.11],
|
||||||
&'static [f64]
|
&'static [f64]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_join_all_empty_lists() {
|
||||||
assert_evals_to!("List.join [ [], [], [] ]", &[], &'static [f64]);
|
assert_evals_to!("List.join [ [], [], [] ]", &[], &'static [f64]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_join_one_empty_list() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
"List.join [ [ 1.2, 1.1 ], [] ]",
|
"List.join [ [ 1.2, 1.1 ], [] ]",
|
||||||
&[1.2, 1.1],
|
&[1.2, 1.1],
|
||||||
|
@ -193,6 +497,10 @@ mod gen_list {
|
||||||
);
|
);
|
||||||
assert_evals_to!("List.reverse [1, 2, 3]", &[3, 2, 1], &'static [i64]);
|
assert_evals_to!("List.reverse [1, 2, 3]", &[3, 2, 1], &'static [i64]);
|
||||||
assert_evals_to!("List.reverse [4]", &[4], &'static [i64]);
|
assert_evals_to!("List.reverse [4]", &[4], &'static [i64]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_reverse_empty_list_of_int() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -206,6 +514,10 @@ mod gen_list {
|
||||||
&[],
|
&[],
|
||||||
&'static [i64]
|
&'static [i64]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_reverse_empty_list() {
|
||||||
assert_evals_to!("List.reverse []", &[], &'static [i64]);
|
assert_evals_to!("List.reverse []", &[], &'static [i64]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,9 +543,12 @@ mod gen_list {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_concat_vanilla() {
|
fn list_concat_two_empty_lists() {
|
||||||
assert_evals_to!("List.concat [] []", &[], &'static [i64]);
|
assert_evals_to!("List.concat [] []", &[], &'static [i64]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_concat_two_empty_lists_of_int() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -251,16 +566,25 @@ mod gen_list {
|
||||||
&[],
|
&[],
|
||||||
&'static [i64]
|
&'static [i64]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_concat_second_list_is_empty() {
|
||||||
assert_evals_to!("List.concat [ 12, 13 ] []", &[12, 13], &'static [i64]);
|
assert_evals_to!("List.concat [ 12, 13 ] []", &[12, 13], &'static [i64]);
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
"List.concat [ 34, 43 ] [ 64, 55, 66 ]",
|
"List.concat [ 34, 43 ] [ 64, 55, 66 ]",
|
||||||
&[34, 43, 64, 55, 66],
|
&[34, 43, 64, 55, 66],
|
||||||
&'static [i64]
|
&'static [i64]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_concat_first_list_is_empty() {
|
||||||
assert_evals_to!("List.concat [] [ 23, 24 ]", &[23, 24], &'static [i64]);
|
assert_evals_to!("List.concat [] [ 23, 24 ]", &[23, 24], &'static [i64]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_concat_two_non_empty_lists() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
"List.concat [1, 2 ] [ 3, 4 ]",
|
"List.concat [1, 2 ] [ 3, 4 ]",
|
||||||
&[1, 2, 3, 4],
|
&[1, 2, 3, 4],
|
||||||
|
@ -420,24 +744,20 @@ mod gen_list {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO getting this to work requires generating a runtime error for the Ok
|
#[test]
|
||||||
// branch here, which is not yet something we support as of when this
|
fn first_empty_list() {
|
||||||
// test was originally written.
|
assert_evals_to!(
|
||||||
//
|
indoc!(
|
||||||
// #[test]
|
r#"
|
||||||
// fn first_empty_list() {
|
when List.first [] is
|
||||||
// assert_evals_to!(
|
Ok val -> val
|
||||||
// indoc!(
|
Err _ -> -1
|
||||||
// r#"
|
"#
|
||||||
// when List.first [] is
|
),
|
||||||
// Ok val -> val
|
-1,
|
||||||
// Err _ -> -1
|
i64
|
||||||
// "#
|
);
|
||||||
// ),
|
}
|
||||||
// -1,
|
|
||||||
// i64
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_empty_list() {
|
fn get_empty_list() {
|
||||||
|
@ -845,151 +1165,151 @@ mod gen_list {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn foobar2() {
|
fn foobar2() {
|
||||||
// with_larger_debug_stack(|| {
|
with_larger_debug_stack(|| {
|
||||||
// assert_evals_to!(
|
assert_evals_to!(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// quicksort : List (Num a) -> List (Num a)
|
quicksort : List (Num a) -> List (Num a)
|
||||||
// quicksort = \list ->
|
quicksort = \list ->
|
||||||
// quicksortHelp list 0 (List.len list - 1)
|
quicksortHelp list 0 (List.len list - 1)
|
||||||
//
|
|
||||||
//
|
|
||||||
// quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
||||||
// quicksortHelp = \list, low, high ->
|
quicksortHelp = \list, low, high ->
|
||||||
// if low < high then
|
if low < high then
|
||||||
// when partition low high list is
|
when partition low high list is
|
||||||
// Pair partitionIndex partitioned ->
|
Pair partitionIndex partitioned ->
|
||||||
// partitioned
|
partitioned
|
||||||
// |> quicksortHelp low (partitionIndex - 1)
|
|> quicksortHelp low (partitionIndex - 1)
|
||||||
// |> quicksortHelp (partitionIndex + 1) high
|
|> quicksortHelp (partitionIndex + 1) high
|
||||||
// else
|
else
|
||||||
// list
|
list
|
||||||
//
|
|
||||||
//
|
|
||||||
// swap : Int, Int, List a -> List a
|
swap : Int, Int, List a -> List a
|
||||||
// swap = \i, j, list ->
|
swap = \i, j, list ->
|
||||||
// when Pair (List.get list i) (List.get list j) is
|
when Pair (List.get list i) (List.get list j) is
|
||||||
// Pair (Ok atI) (Ok atJ) ->
|
Pair (Ok atI) (Ok atJ) ->
|
||||||
// list
|
list
|
||||||
// |> List.set i atJ
|
|> List.set i atJ
|
||||||
// |> List.set j atI
|
|> List.set j atI
|
||||||
//
|
|
||||||
// _ ->
|
_ ->
|
||||||
// []
|
[]
|
||||||
//
|
|
||||||
// partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
|
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
|
||||||
// partition = \low, high, initialList ->
|
partition = \low, high, initialList ->
|
||||||
// when List.get initialList high is
|
when List.get initialList high is
|
||||||
// Ok pivot ->
|
Ok pivot ->
|
||||||
// when partitionHelp (low - 1) low initialList high pivot is
|
when partitionHelp (low - 1) low initialList high pivot is
|
||||||
// Pair newI newList ->
|
Pair newI newList ->
|
||||||
// Pair (newI + 1) (swap (newI + 1) high newList)
|
Pair (newI + 1) (swap (newI + 1) high newList)
|
||||||
//
|
|
||||||
// Err _ ->
|
Err _ ->
|
||||||
// Pair (low - 1) initialList
|
Pair (low - 1) initialList
|
||||||
//
|
|
||||||
//
|
|
||||||
// partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
|
partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
|
||||||
// partitionHelp = \i, j, list, high, pivot ->
|
partitionHelp = \i, j, list, high, pivot ->
|
||||||
// # if j < high then
|
# if j < high then
|
||||||
// if False then
|
if False then
|
||||||
// when List.get list j is
|
when List.get list j is
|
||||||
// Ok value ->
|
Ok value ->
|
||||||
// if value <= pivot then
|
if value <= pivot then
|
||||||
// partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
|
partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
|
||||||
// else
|
else
|
||||||
// partitionHelp i (j + 1) list high pivot
|
partitionHelp i (j + 1) list high pivot
|
||||||
//
|
|
||||||
// Err _ ->
|
Err _ ->
|
||||||
// Pair i list
|
Pair i list
|
||||||
// else
|
else
|
||||||
// Pair i list
|
Pair i list
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// quicksort [ 7, 4, 21, 19 ]
|
quicksort [ 7, 4, 21, 19 ]
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// &[19, 7, 4, 21],
|
&[19, 7, 4, 21],
|
||||||
// &'static [i64],
|
&'static [i64]
|
||||||
// );
|
);
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn foobar() {
|
fn foobar() {
|
||||||
// with_larger_debug_stack(|| {
|
with_larger_debug_stack(|| {
|
||||||
// assert_evals_to!(
|
assert_evals_to!(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// quicksort : List (Num a) -> List (Num a)
|
quicksort : List (Num a) -> List (Num a)
|
||||||
// quicksort = \list ->
|
quicksort = \list ->
|
||||||
// quicksortHelp list 0 (List.len list - 1)
|
quicksortHelp list 0 (List.len list - 1)
|
||||||
//
|
|
||||||
//
|
|
||||||
// quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
||||||
// quicksortHelp = \list, low, high ->
|
quicksortHelp = \list, low, high ->
|
||||||
// if low < high then
|
if low < high then
|
||||||
// when partition low high list is
|
when partition low high list is
|
||||||
// Pair partitionIndex partitioned ->
|
Pair partitionIndex partitioned ->
|
||||||
// partitioned
|
partitioned
|
||||||
// |> quicksortHelp low (partitionIndex - 1)
|
|> quicksortHelp low (partitionIndex - 1)
|
||||||
// |> quicksortHelp (partitionIndex + 1) high
|
|> quicksortHelp (partitionIndex + 1) high
|
||||||
// else
|
else
|
||||||
// list
|
list
|
||||||
//
|
|
||||||
//
|
|
||||||
// swap : Int, Int, List a -> List a
|
swap : Int, Int, List a -> List a
|
||||||
// swap = \i, j, list ->
|
swap = \i, j, list ->
|
||||||
// when Pair (List.get list i) (List.get list j) is
|
when Pair (List.get list i) (List.get list j) is
|
||||||
// Pair (Ok atI) (Ok atJ) ->
|
Pair (Ok atI) (Ok atJ) ->
|
||||||
// list
|
list
|
||||||
// |> List.set i atJ
|
|> List.set i atJ
|
||||||
// |> List.set j atI
|
|> List.set j atI
|
||||||
//
|
|
||||||
// _ ->
|
_ ->
|
||||||
// []
|
[]
|
||||||
//
|
|
||||||
// partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
|
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
|
||||||
// partition = \low, high, initialList ->
|
partition = \low, high, initialList ->
|
||||||
// when List.get initialList high is
|
when List.get initialList high is
|
||||||
// Ok pivot ->
|
Ok pivot ->
|
||||||
// when partitionHelp (low - 1) low initialList high pivot is
|
when partitionHelp (low - 1) low initialList high pivot is
|
||||||
// Pair newI newList ->
|
Pair newI newList ->
|
||||||
// Pair (newI + 1) (swap (newI + 1) high newList)
|
Pair (newI + 1) (swap (newI + 1) high newList)
|
||||||
//
|
|
||||||
// Err _ ->
|
Err _ ->
|
||||||
// Pair (low - 1) initialList
|
Pair (low - 1) initialList
|
||||||
//
|
|
||||||
//
|
|
||||||
// partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
|
partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
|
||||||
// partitionHelp = \i, j, list, high, pivot ->
|
partitionHelp = \i, j, list, high, pivot ->
|
||||||
// if j < high then
|
if j < high then
|
||||||
// when List.get list j is
|
when List.get list j is
|
||||||
// Ok value ->
|
Ok value ->
|
||||||
// if value <= pivot then
|
if value <= pivot then
|
||||||
// partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
|
partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
|
||||||
// else
|
else
|
||||||
// partitionHelp i (j + 1) list high pivot
|
partitionHelp i (j + 1) list high pivot
|
||||||
//
|
|
||||||
// Err _ ->
|
Err _ ->
|
||||||
// Pair i list
|
Pair i list
|
||||||
// else
|
else
|
||||||
// Pair i list
|
Pair i list
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// when List.first (quicksort [0x1]) is
|
when List.first (quicksort [0x1]) is
|
||||||
// _ -> 4
|
_ -> 4
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// 4,
|
4,
|
||||||
// i64,
|
i64
|
||||||
// );
|
);
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_list_increment_decrement() {
|
fn empty_list_increment_decrement() {
|
||||||
|
|
|
@ -87,7 +87,7 @@ pub fn infer_expr(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
||||||
let state = State::new(input.as_bytes(), Attempting::Module);
|
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||||
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
||||||
let answer = parser.parse(&arena, state);
|
let answer = parser.parse(&arena, state);
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
||||||
let state = State::new(input.as_bytes(), Attempting::Module);
|
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||||
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
||||||
let answer = parser.parse(&arena, state);
|
let answer = parser.parse(&arena, state);
|
||||||
|
|
||||||
|
|
|
@ -257,7 +257,7 @@ mod test_load {
|
||||||
expect_types(
|
expect_types(
|
||||||
loaded_module,
|
loaded_module,
|
||||||
hashmap! {
|
hashmap! {
|
||||||
"findPath" => "{ costFunction : (position, position -> Float), end : position, moveFunction : (position -> Set position), start : position } -> Result (List position) [ KeyNotFound ]*",
|
"findPath" => "{ costFunction : position, position -> Float, end : position, moveFunction : position -> Set position, start : position } -> Result (List position) [ KeyNotFound ]*",
|
||||||
"initialModel" => "position -> Model position",
|
"initialModel" => "position -> Model position",
|
||||||
"reconstructPath" => "Map position position, position -> List position",
|
"reconstructPath" => "Map position position, position -> List position",
|
||||||
"updateCost" => "position, position, Model position -> Model position",
|
"updateCost" => "position, position, Model position -> Model position",
|
||||||
|
|
|
@ -213,7 +213,7 @@ mod test_uniq_load {
|
||||||
expect_types(
|
expect_types(
|
||||||
loaded_module,
|
loaded_module,
|
||||||
hashmap! {
|
hashmap! {
|
||||||
"findPath" => "Attr * (Attr * { costFunction : (Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float)), end : (Attr Shared position), moveFunction : (Attr Shared (Attr Shared position -> Attr * (Set (Attr * position)))), start : (Attr Shared position) } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))",
|
"findPath" => "Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))",
|
||||||
"initialModel" => "Attr * (Attr Shared position -> Attr * (Model (Attr Shared position)))",
|
"initialModel" => "Attr * (Attr Shared position -> Attr * (Model (Attr Shared position)))",
|
||||||
"reconstructPath" => "Attr Shared (Attr Shared (Map (Attr * position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))",
|
"reconstructPath" => "Attr Shared (Attr Shared (Map (Attr * position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))",
|
||||||
"updateCost" => "Attr * (Attr Shared position, Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr Shared (Model (Attr Shared position)))",
|
"updateCost" => "Attr * (Attr Shared position, Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr Shared (Model (Attr Shared position)))",
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub enum TagName {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TagName {
|
impl TagName {
|
||||||
pub fn into_string(self, interns: &Interns, home: ModuleId) -> InlinableString {
|
pub fn as_string(&self, interns: &Interns, home: ModuleId) -> InlinableString {
|
||||||
match self {
|
match self {
|
||||||
TagName::Global(uppercase) => uppercase.as_inline_str().clone(),
|
TagName::Global(uppercase) => uppercase.as_inline_str().clone(),
|
||||||
TagName::Private(symbol) => symbol.fully_qualified(interns, home),
|
TagName::Private(symbol) => symbol.fully_qualified(interns, home),
|
||||||
|
|
|
@ -15,6 +15,9 @@ pub enum LowLevel {
|
||||||
ListAppend,
|
ListAppend,
|
||||||
ListPrepend,
|
ListPrepend,
|
||||||
ListJoin,
|
ListJoin,
|
||||||
|
ListMap,
|
||||||
|
ListKeepIf,
|
||||||
|
ListWalkRight,
|
||||||
NumAdd,
|
NumAdd,
|
||||||
NumSub,
|
NumSub,
|
||||||
NumMul,
|
NumMul,
|
||||||
|
|
|
@ -666,7 +666,7 @@ define_builtins! {
|
||||||
6 LIST_MAP: "map"
|
6 LIST_MAP: "map"
|
||||||
7 LIST_LEN: "len"
|
7 LIST_LEN: "len"
|
||||||
8 LIST_FOLDL: "foldl"
|
8 LIST_FOLDL: "foldl"
|
||||||
9 LIST_FOLDR: "foldr"
|
9 LIST_WALK_RIGHT: "walkRight"
|
||||||
10 LIST_CONCAT: "concat"
|
10 LIST_CONCAT: "concat"
|
||||||
11 LIST_FIRST: "first"
|
11 LIST_FIRST: "first"
|
||||||
12 LIST_SINGLE: "single"
|
12 LIST_SINGLE: "single"
|
||||||
|
@ -674,6 +674,7 @@ define_builtins! {
|
||||||
14 LIST_REVERSE: "reverse"
|
14 LIST_REVERSE: "reverse"
|
||||||
15 LIST_PREPEND: "prepend"
|
15 LIST_PREPEND: "prepend"
|
||||||
16 LIST_JOIN: "join"
|
16 LIST_JOIN: "join"
|
||||||
|
17 LIST_KEEP_IF: "keepIf"
|
||||||
}
|
}
|
||||||
5 RESULT: "Result" => {
|
5 RESULT: "Result" => {
|
||||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub fn infer_borrow<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||||
enum Key {
|
pub enum Key {
|
||||||
Declaration(Symbol),
|
Declaration(Symbol),
|
||||||
JoinPoint(JoinPointId),
|
JoinPoint(JoinPointId),
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,24 @@ pub struct ParamMap<'a> {
|
||||||
items: MutMap<Key, &'a [Param<'a>]>,
|
items: MutMap<Key, &'a [Param<'a>]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for ParamMap<'a> {
|
||||||
|
type Item = (Key, &'a [Param<'a>]);
|
||||||
|
type IntoIter = <std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.items.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a ParamMap<'a> {
|
||||||
|
type Item = (&'a Key, &'a &'a [Param<'a>]);
|
||||||
|
type IntoIter = <&'a std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.items.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ParamMap<'a> {
|
impl<'a> ParamMap<'a> {
|
||||||
pub fn get_symbol(&self, symbol: Symbol) -> Option<&'a [Param<'a>]> {
|
pub fn get_symbol(&self, symbol: Symbol) -> Option<&'a [Param<'a>]> {
|
||||||
let key = Key::Declaration(symbol);
|
let key = Key::Declaration(symbol);
|
||||||
|
@ -491,14 +509,17 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||||
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||||
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||||
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||||
|
ListConcat | StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||||
|
|
||||||
ListSingle => arena.alloc_slice_copy(&[irrelevant]),
|
ListSingle => arena.alloc_slice_copy(&[irrelevant]),
|
||||||
ListRepeat => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
ListRepeat => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
||||||
ListReverse => arena.alloc_slice_copy(&[owned]),
|
ListReverse => arena.alloc_slice_copy(&[owned]),
|
||||||
ListConcat | StrConcat => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
|
||||||
ListAppend => arena.alloc_slice_copy(&[owned, owned]),
|
ListAppend => arena.alloc_slice_copy(&[owned, owned]),
|
||||||
ListPrepend => arena.alloc_slice_copy(&[owned, owned]),
|
ListPrepend => arena.alloc_slice_copy(&[owned, owned]),
|
||||||
ListJoin => arena.alloc_slice_copy(&[irrelevant]),
|
ListJoin => arena.alloc_slice_copy(&[irrelevant]),
|
||||||
|
ListMap => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||||
|
ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||||
|
ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
|
||||||
|
|
||||||
Eq | NotEq | And | Or | NumAdd | NumSub | NumMul | NumGt | NumGte | NumLt | NumLte
|
Eq | NotEq | And | Or | NumAdd | NumSub | NumMul | NumGt | NumGte | NumLt | NumLte
|
||||||
| NumDivUnchecked | NumRemUnchecked => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
| NumDivUnchecked | NumRemUnchecked => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
||||||
|
|
|
@ -221,9 +221,24 @@ fn consume_expr(m: &VarMap, e: &Expr<'_>) -> bool {
|
||||||
|
|
||||||
impl<'a> Context<'a> {
|
impl<'a> Context<'a> {
|
||||||
pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self {
|
pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self {
|
||||||
|
let mut vars = MutMap::default();
|
||||||
|
|
||||||
|
for (key, _) in param_map.into_iter() {
|
||||||
|
if let crate::borrow::Key::Declaration(symbol) = key {
|
||||||
|
vars.insert(
|
||||||
|
*symbol,
|
||||||
|
VarInfo {
|
||||||
|
reference: false, // assume function symbols are global constants
|
||||||
|
persistent: true, // assume function symbols are global constants
|
||||||
|
consume: false, // no need to consume this variable
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
arena,
|
arena,
|
||||||
vars: MutMap::default(),
|
vars,
|
||||||
jp_live_vars: MutMap::default(),
|
jp_live_vars: MutMap::default(),
|
||||||
local_context: LocalContext::default(),
|
local_context: LocalContext::default(),
|
||||||
param_map,
|
param_map,
|
||||||
|
|
|
@ -362,6 +362,89 @@ impl<'a> Procs<'a> {
|
||||||
None => unreachable!("insert_exposed was called after the pending specializations phase had already completed!"),
|
None => unreachable!("insert_exposed was called after the pending specializations phase had already completed!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
pub fn insert_passed_by_name(
|
||||||
|
&mut self,
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
fn_var: Variable,
|
||||||
|
name: Symbol,
|
||||||
|
layout: Layout<'a>,
|
||||||
|
layout_cache: &mut LayoutCache<'a>,
|
||||||
|
) {
|
||||||
|
let tuple = (name, layout);
|
||||||
|
|
||||||
|
// If we've already specialized this one, no further work is needed.
|
||||||
|
if self.specialized.contains_key(&tuple) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're done with that tuple, so move layout back out to avoid cloning it.
|
||||||
|
let (name, layout) = tuple;
|
||||||
|
|
||||||
|
// now we have to pull some tricks to extract the return var and pattern vars from Subs
|
||||||
|
match get_args_ret_var(env.subs, fn_var) {
|
||||||
|
Some((pattern_vars, ret_var)) => {
|
||||||
|
let pattern_vars = Vec::from_iter_in(pattern_vars.into_iter(), env.arena);
|
||||||
|
let pending = PendingSpecialization {
|
||||||
|
pattern_vars,
|
||||||
|
ret_var,
|
||||||
|
fn_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This should only be called when pending_specializations is Some.
|
||||||
|
// Otherwise, it's being called in the wrong pass!
|
||||||
|
match &mut self.pending_specializations {
|
||||||
|
Some(pending_specializations) => {
|
||||||
|
// register the pending specialization, so this gets code genned later
|
||||||
|
add_pending(pending_specializations, name, layout, pending)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let symbol = name;
|
||||||
|
|
||||||
|
// TODO should pending_procs hold a Rc<Proc>?
|
||||||
|
let partial_proc = self.partial_procs.get(&symbol).unwrap().clone();
|
||||||
|
|
||||||
|
// Mark this proc as in-progress, so if we're dealing with
|
||||||
|
// mutually recursive functions, we don't loop forever.
|
||||||
|
// (We had a bug around this before this system existed!)
|
||||||
|
self.specialized
|
||||||
|
.insert((symbol, layout.clone()), InProgress);
|
||||||
|
|
||||||
|
match specialize(env, self, symbol, layout_cache, pending, partial_proc) {
|
||||||
|
Ok(proc) => {
|
||||||
|
self.specialized
|
||||||
|
.insert((symbol, layout.clone()), Done(proc));
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
let error_msg =
|
||||||
|
format!("TODO generate a RuntimeError message for {:?}", error);
|
||||||
|
self.runtime_errors
|
||||||
|
.insert(symbol, env.arena.alloc(error_msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
unreachable!(
|
||||||
|
"trying to insert a symbol that is not a function: {:?} {:?}",
|
||||||
|
name, other
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_args_ret_var(subs: &Subs, var: Variable) -> Option<(std::vec::Vec<Variable>, Variable)> {
|
||||||
|
match subs.get_without_compacting(var).content {
|
||||||
|
Content::Structure(FlatType::Func(pattern_vars, ret_var)) => Some((pattern_vars, ret_var)),
|
||||||
|
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => {
|
||||||
|
get_args_ret_var(subs, args[1])
|
||||||
|
}
|
||||||
|
Content::Alias(_, _, actual) => get_args_ret_var(subs, actual),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_pending<'a>(
|
fn add_pending<'a>(
|
||||||
|
@ -502,6 +585,7 @@ pub enum Stmt<'a> {
|
||||||
Jump(JoinPointId, &'a [Symbol]),
|
Jump(JoinPointId, &'a [Symbol]),
|
||||||
RuntimeError(&'a str),
|
RuntimeError(&'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Literal<'a> {
|
pub enum Literal<'a> {
|
||||||
// Literals
|
// Literals
|
||||||
|
@ -667,7 +751,9 @@ impl<'a> Expr<'a> {
|
||||||
match self {
|
match self {
|
||||||
Literal(lit) => lit.to_doc(alloc),
|
Literal(lit) => lit.to_doc(alloc),
|
||||||
|
|
||||||
FunctionPointer(symbol, _) => symbol_to_doc(alloc, *symbol),
|
FunctionPointer(symbol, _) => alloc
|
||||||
|
.text("FunctionPointer ")
|
||||||
|
.append(symbol_to_doc(alloc, *symbol)),
|
||||||
|
|
||||||
FunctionCall {
|
FunctionCall {
|
||||||
call_type, args, ..
|
call_type, args, ..
|
||||||
|
@ -1225,7 +1311,7 @@ pub fn with_hole<'a>(
|
||||||
hole,
|
hole,
|
||||||
),
|
),
|
||||||
|
|
||||||
Str(string) | BlockStr(string) => Stmt::Let(
|
Str(string) => Stmt::Let(
|
||||||
assigned,
|
assigned,
|
||||||
Expr::Literal(Literal::Str(arena.alloc(string))),
|
Expr::Literal(Literal::Str(arena.alloc(string))),
|
||||||
Layout::Builtin(Builtin::Str),
|
Layout::Builtin(Builtin::Str),
|
||||||
|
@ -1432,12 +1518,9 @@ pub fn with_hole<'a>(
|
||||||
let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena);
|
let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena);
|
||||||
|
|
||||||
for (_, arg) in args.iter() {
|
for (_, arg) in args.iter() {
|
||||||
if let roc_can::expr::Expr::Var(symbol) = arg.value {
|
field_symbols.push(possible_reuse_symbol(env, procs, &arg.value));
|
||||||
field_symbols.push(symbol);
|
|
||||||
} else {
|
|
||||||
field_symbols.push(env.unique_symbol());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let field_symbols = field_symbols.into_bump_slice();
|
||||||
|
|
||||||
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
|
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
|
||||||
let layout = layout_cache
|
let layout = layout_cache
|
||||||
|
@ -1447,30 +1530,10 @@ pub fn with_hole<'a>(
|
||||||
});
|
});
|
||||||
|
|
||||||
// even though this was originally a Tag, we treat it as a Struct from now on
|
// even though this was originally a Tag, we treat it as a Struct from now on
|
||||||
let mut stmt = Stmt::Let(
|
let stmt = Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole);
|
||||||
assigned,
|
|
||||||
Expr::Struct(field_symbols.clone().into_bump_slice()),
|
|
||||||
layout,
|
|
||||||
hole,
|
|
||||||
);
|
|
||||||
|
|
||||||
for ((_, arg), symbol) in args.into_iter().rev().zip(field_symbols.iter().rev())
|
let iter = args.into_iter().rev().zip(field_symbols.iter().rev());
|
||||||
{
|
assign_to_symbols(env, procs, layout_cache, iter, stmt)
|
||||||
// if this argument is already a symbol, we don't need to re-define it
|
|
||||||
if let roc_can::expr::Expr::Var(_) = arg.value {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
stmt = with_hole(
|
|
||||||
env,
|
|
||||||
arg.value,
|
|
||||||
procs,
|
|
||||||
layout_cache,
|
|
||||||
*symbol,
|
|
||||||
env.arena.alloc(stmt),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt
|
|
||||||
}
|
}
|
||||||
Wrapped(sorted_tag_layouts) => {
|
Wrapped(sorted_tag_layouts) => {
|
||||||
let union_size = sorted_tag_layouts.len() as u8;
|
let union_size = sorted_tag_layouts.len() as u8;
|
||||||
|
@ -1485,11 +1548,7 @@ pub fn with_hole<'a>(
|
||||||
field_symbols.push(tag_id_symbol);
|
field_symbols.push(tag_id_symbol);
|
||||||
|
|
||||||
for (_, arg) in args.iter() {
|
for (_, arg) in args.iter() {
|
||||||
if let roc_can::expr::Expr::Var(symbol) = arg.value {
|
field_symbols.push(possible_reuse_symbol(env, procs, &arg.value));
|
||||||
field_symbols.push(symbol);
|
|
||||||
} else {
|
|
||||||
field_symbols.push(env.unique_symbol());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut layouts: Vec<&'a [Layout<'a>]> =
|
let mut layouts: Vec<&'a [Layout<'a>]> =
|
||||||
|
@ -1510,23 +1569,9 @@ pub fn with_hole<'a>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut stmt = Stmt::Let(assigned, tag, layout, hole);
|
let mut stmt = Stmt::Let(assigned, tag, layout, hole);
|
||||||
|
let iter = args.into_iter().rev().zip(field_symbols.iter().rev());
|
||||||
|
|
||||||
for ((_, arg), symbol) in args.into_iter().rev().zip(field_symbols.iter().rev())
|
stmt = assign_to_symbols(env, procs, layout_cache, iter, stmt);
|
||||||
{
|
|
||||||
// if this argument is already a symbol, we don't need to re-define it
|
|
||||||
if let roc_can::expr::Expr::Var(_) = arg.value {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt = with_hole(
|
|
||||||
env,
|
|
||||||
arg.value,
|
|
||||||
procs,
|
|
||||||
layout_cache,
|
|
||||||
*symbol,
|
|
||||||
env.arena.alloc(stmt),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// define the tag id
|
// define the tag id
|
||||||
stmt = Stmt::Let(
|
stmt = Stmt::Let(
|
||||||
|
@ -1555,16 +1600,18 @@ pub fn with_hole<'a>(
|
||||||
for (label, layout) in sorted_fields.into_iter() {
|
for (label, layout) in sorted_fields.into_iter() {
|
||||||
field_layouts.push(layout);
|
field_layouts.push(layout);
|
||||||
|
|
||||||
|
// TODO how should function pointers be handled here?
|
||||||
match fields.remove(&label) {
|
match fields.remove(&label) {
|
||||||
Some(field) => {
|
Some(field) => match can_reuse_symbol(procs, &field.loc_expr.value) {
|
||||||
if let roc_can::expr::Expr::Var(symbol) = field.loc_expr.value {
|
Some(reusable) => {
|
||||||
field_symbols.push(symbol);
|
field_symbols.push(reusable);
|
||||||
can_fields.push(None);
|
can_fields.push(None);
|
||||||
} else {
|
}
|
||||||
|
None => {
|
||||||
field_symbols.push(env.unique_symbol());
|
field_symbols.push(env.unique_symbol());
|
||||||
can_fields.push(Some(field));
|
can_fields.push(Some(field));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
None => {
|
None => {
|
||||||
// this field was optional, but not given
|
// this field was optional, but not given
|
||||||
continue;
|
continue;
|
||||||
|
@ -1735,11 +1782,7 @@ pub fn with_hole<'a>(
|
||||||
loc_cond,
|
loc_cond,
|
||||||
branches,
|
branches,
|
||||||
} => {
|
} => {
|
||||||
let cond_symbol = if let roc_can::expr::Expr::Var(symbol) = loc_cond.value {
|
let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value);
|
||||||
symbol
|
|
||||||
} else {
|
|
||||||
env.unique_symbol()
|
|
||||||
};
|
|
||||||
|
|
||||||
let id = JoinPointId(env.unique_symbol());
|
let id = JoinPointId(env.unique_symbol());
|
||||||
|
|
||||||
|
@ -1756,18 +1799,15 @@ pub fn with_hole<'a>(
|
||||||
);
|
);
|
||||||
|
|
||||||
// define the `when` condition
|
// define the `when` condition
|
||||||
if let roc_can::expr::Expr::Var(_) = loc_cond.value {
|
stmt = assign_to_symbol(
|
||||||
// do nothing
|
env,
|
||||||
} else {
|
procs,
|
||||||
stmt = with_hole(
|
layout_cache,
|
||||||
env,
|
cond_var,
|
||||||
loc_cond.value,
|
*loc_cond,
|
||||||
procs,
|
cond_symbol,
|
||||||
layout_cache,
|
stmt,
|
||||||
cond_symbol,
|
);
|
||||||
env.arena.alloc(stmt),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let layout = layout_cache
|
let layout = layout_cache
|
||||||
.from_var(env.arena, expr_var, env.subs)
|
.from_var(env.arena, expr_var, env.subs)
|
||||||
|
@ -1800,11 +1840,7 @@ pub fn with_hole<'a>(
|
||||||
} => {
|
} => {
|
||||||
let mut arg_symbols = Vec::with_capacity_in(loc_elems.len(), env.arena);
|
let mut arg_symbols = Vec::with_capacity_in(loc_elems.len(), env.arena);
|
||||||
for arg_expr in loc_elems.iter() {
|
for arg_expr in loc_elems.iter() {
|
||||||
if let roc_can::expr::Expr::Var(symbol) = arg_expr.value {
|
arg_symbols.push(possible_reuse_symbol(env, procs, &arg_expr.value));
|
||||||
arg_symbols.push(symbol);
|
|
||||||
} else {
|
|
||||||
arg_symbols.push(env.unique_symbol());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let arg_symbols = arg_symbols.into_bump_slice();
|
let arg_symbols = arg_symbols.into_bump_slice();
|
||||||
|
|
||||||
|
@ -1819,30 +1855,20 @@ pub fn with_hole<'a>(
|
||||||
|
|
||||||
let mode = crate::layout::mode_from_var(list_var, env.subs);
|
let mode = crate::layout::mode_from_var(list_var, env.subs);
|
||||||
|
|
||||||
let mut stmt = Stmt::Let(
|
let stmt = Stmt::Let(
|
||||||
assigned,
|
assigned,
|
||||||
expr,
|
expr,
|
||||||
Layout::Builtin(Builtin::List(mode, env.arena.alloc(elem_layout))),
|
Layout::Builtin(Builtin::List(mode, env.arena.alloc(elem_layout))),
|
||||||
hole,
|
hole,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (arg_expr, symbol) in loc_elems.into_iter().rev().zip(arg_symbols.iter().rev()) {
|
let iter = loc_elems
|
||||||
// if this argument is already a symbol, we don't need to re-define it
|
.into_iter()
|
||||||
if let roc_can::expr::Expr::Var(_) = arg_expr.value {
|
.rev()
|
||||||
continue;
|
.map(|e| (elem_var, e))
|
||||||
}
|
.zip(arg_symbols.iter().rev());
|
||||||
|
|
||||||
stmt = with_hole(
|
assign_to_symbols(env, procs, layout_cache, iter, stmt)
|
||||||
env,
|
|
||||||
arg_expr.value,
|
|
||||||
procs,
|
|
||||||
layout_cache,
|
|
||||||
*symbol,
|
|
||||||
env.arena.alloc(stmt),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Access {
|
Access {
|
||||||
|
@ -1876,11 +1902,7 @@ pub fn with_hole<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let record_symbol = if let roc_can::expr::Expr::Var(symbol) = loc_expr.value {
|
let record_symbol = possible_reuse_symbol(env, procs, &loc_expr.value);
|
||||||
symbol
|
|
||||||
} else {
|
|
||||||
env.unique_symbol()
|
|
||||||
};
|
|
||||||
|
|
||||||
let wrapped = {
|
let wrapped = {
|
||||||
let record_layout = layout_cache
|
let record_layout = layout_cache
|
||||||
|
@ -1906,18 +1928,15 @@ pub fn with_hole<'a>(
|
||||||
|
|
||||||
let mut stmt = Stmt::Let(assigned, expr, layout, hole);
|
let mut stmt = Stmt::Let(assigned, expr, layout, hole);
|
||||||
|
|
||||||
if let roc_can::expr::Expr::Var(_) = loc_expr.value {
|
stmt = assign_to_symbol(
|
||||||
// do nothing
|
env,
|
||||||
} else {
|
procs,
|
||||||
stmt = with_hole(
|
layout_cache,
|
||||||
env,
|
record_var,
|
||||||
loc_expr.value,
|
*loc_expr,
|
||||||
procs,
|
record_symbol,
|
||||||
layout_cache,
|
stmt,
|
||||||
record_symbol,
|
);
|
||||||
env.arena.alloc(stmt),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
stmt
|
stmt
|
||||||
}
|
}
|
||||||
|
@ -1948,35 +1967,8 @@ pub fn with_hole<'a>(
|
||||||
Call(boxed, loc_args, _) => {
|
Call(boxed, loc_args, _) => {
|
||||||
let (fn_var, loc_expr, ret_var) = *boxed;
|
let (fn_var, loc_expr, ret_var) = *boxed;
|
||||||
|
|
||||||
/*
|
|
||||||
Var(symbol) => {
|
|
||||||
if procs.module_thunks.contains(&symbol) {
|
|
||||||
let partial_proc = procs.partial_procs.get(&symbol).unwrap();
|
|
||||||
let fn_var = partial_proc.annotation;
|
|
||||||
let ret_var = fn_var; // These are the same for a thunk.
|
|
||||||
|
|
||||||
// This is a top-level declaration, which will code gen to a 0-arity thunk.
|
|
||||||
call_by_name(
|
|
||||||
env,
|
|
||||||
procs,
|
|
||||||
fn_var,
|
|
||||||
ret_var,
|
|
||||||
symbol,
|
|
||||||
std::vec::Vec::new(),
|
|
||||||
layout_cache,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// NOTE Load will always increment the refcount
|
|
||||||
Expr::Load(symbol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// match from_can(env, loc_expr.value, procs, layout_cache) {
|
// match from_can(env, loc_expr.value, procs, layout_cache) {
|
||||||
match loc_expr.value {
|
match loc_expr.value {
|
||||||
roc_can::expr::Expr::Var(proc_name) if procs.module_thunks.contains(&proc_name) => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
roc_can::expr::Expr::Var(proc_name) => call_by_name(
|
roc_can::expr::Expr::Var(proc_name) => call_by_name(
|
||||||
env,
|
env,
|
||||||
procs,
|
procs,
|
||||||
|
@ -2072,11 +2064,7 @@ pub fn with_hole<'a>(
|
||||||
let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena);
|
let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena);
|
||||||
|
|
||||||
for (_, arg_expr) in args.iter() {
|
for (_, arg_expr) in args.iter() {
|
||||||
if let roc_can::expr::Expr::Var(symbol) = arg_expr {
|
arg_symbols.push(possible_reuse_symbol(env, procs, &arg_expr));
|
||||||
arg_symbols.push(*symbol);
|
|
||||||
} else {
|
|
||||||
arg_symbols.push(env.unique_symbol());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let arg_symbols = arg_symbols.into_bump_slice();
|
let arg_symbols = arg_symbols.into_bump_slice();
|
||||||
|
|
||||||
|
@ -2085,27 +2073,14 @@ pub fn with_hole<'a>(
|
||||||
.from_var(env.arena, ret_var, env.subs)
|
.from_var(env.arena, ret_var, env.subs)
|
||||||
.unwrap_or_else(|err| todo!("TODO turn fn_var into a RuntimeError {:?}", err));
|
.unwrap_or_else(|err| todo!("TODO turn fn_var into a RuntimeError {:?}", err));
|
||||||
|
|
||||||
let mut result = Stmt::Let(assigned, Expr::RunLowLevel(op, arg_symbols), layout, hole);
|
let result = Stmt::Let(assigned, Expr::RunLowLevel(op, arg_symbols), layout, hole);
|
||||||
|
|
||||||
for ((_arg_var, arg_expr), symbol) in
|
let iter = args
|
||||||
args.into_iter().rev().zip(arg_symbols.iter().rev())
|
.into_iter()
|
||||||
{
|
.rev()
|
||||||
// if this argument is already a symbol, we don't need to re-define it
|
.map(|(a, b)| (a, Located::at_zero(b)))
|
||||||
if let roc_can::expr::Expr::Var(_) = arg_expr {
|
.zip(arg_symbols.iter().rev());
|
||||||
continue;
|
assign_to_symbols(env, procs, layout_cache, iter, result)
|
||||||
}
|
|
||||||
|
|
||||||
result = with_hole(
|
|
||||||
env,
|
|
||||||
arg_expr,
|
|
||||||
procs,
|
|
||||||
layout_cache,
|
|
||||||
*symbol,
|
|
||||||
env.arena.alloc(result),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(format!("{:?}", e))),
|
RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(format!("{:?}", e))),
|
||||||
}
|
}
|
||||||
|
@ -2127,13 +2102,9 @@ pub fn from_can<'a>(
|
||||||
loc_cond,
|
loc_cond,
|
||||||
branches,
|
branches,
|
||||||
} => {
|
} => {
|
||||||
let cond_symbol = if let roc_can::expr::Expr::Var(symbol) = loc_cond.value {
|
let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value);
|
||||||
symbol
|
|
||||||
} else {
|
|
||||||
env.unique_symbol()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut stmt = from_can_when(
|
let stmt = from_can_when(
|
||||||
env,
|
env,
|
||||||
cond_var,
|
cond_var,
|
||||||
expr_var,
|
expr_var,
|
||||||
|
@ -2146,20 +2117,15 @@ pub fn from_can<'a>(
|
||||||
);
|
);
|
||||||
|
|
||||||
// define the `when` condition
|
// define the `when` condition
|
||||||
if let roc_can::expr::Expr::Var(_) = loc_cond.value {
|
assign_to_symbol(
|
||||||
// do nothing
|
env,
|
||||||
} else {
|
procs,
|
||||||
stmt = with_hole(
|
layout_cache,
|
||||||
env,
|
cond_var,
|
||||||
loc_cond.value,
|
*loc_cond,
|
||||||
procs,
|
cond_symbol,
|
||||||
layout_cache,
|
stmt,
|
||||||
cond_symbol,
|
)
|
||||||
env.arena.alloc(stmt),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
stmt
|
|
||||||
}
|
}
|
||||||
If {
|
If {
|
||||||
cond_var,
|
cond_var,
|
||||||
|
@ -3066,6 +3032,88 @@ fn store_record_destruct<'a>(
|
||||||
Ok(stmt)
|
Ok(stmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We want to re-use symbols that are not function symbols
|
||||||
|
/// for any other expression, we create a new symbol, and will
|
||||||
|
/// later make sure it gets assigned the correct value.
|
||||||
|
fn can_reuse_symbol<'a>(procs: &Procs<'a>, expr: &roc_can::expr::Expr) -> Option<Symbol> {
|
||||||
|
if let roc_can::expr::Expr::Var(symbol) = expr {
|
||||||
|
if procs.partial_procs.contains_key(&symbol) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(*symbol)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn possible_reuse_symbol<'a>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
procs: &Procs<'a>,
|
||||||
|
expr: &roc_can::expr::Expr,
|
||||||
|
) -> Symbol {
|
||||||
|
match can_reuse_symbol(procs, expr) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => env.unique_symbol(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_to_symbol<'a>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
procs: &mut Procs<'a>,
|
||||||
|
layout_cache: &mut LayoutCache<'a>,
|
||||||
|
arg_var: Variable,
|
||||||
|
loc_arg: Located<roc_can::expr::Expr>,
|
||||||
|
symbol: Symbol,
|
||||||
|
result: Stmt<'a>,
|
||||||
|
) -> Stmt<'a> {
|
||||||
|
// if this argument is already a symbol, we don't need to re-define it
|
||||||
|
if let roc_can::expr::Expr::Var(original) = loc_arg.value {
|
||||||
|
if procs.partial_procs.contains_key(&original) {
|
||||||
|
// this symbol is a function, that is used by-name (e.g. as an argument to another
|
||||||
|
// function). Register it with the current variable, then create a function pointer
|
||||||
|
// to it in the IR.
|
||||||
|
let layout = layout_cache
|
||||||
|
.from_var(env.arena, arg_var, env.subs)
|
||||||
|
.expect("creating layout does not fail");
|
||||||
|
procs.insert_passed_by_name(env, arg_var, original, layout.clone(), layout_cache);
|
||||||
|
|
||||||
|
return Stmt::Let(
|
||||||
|
symbol,
|
||||||
|
Expr::FunctionPointer(original, layout.clone()),
|
||||||
|
layout,
|
||||||
|
env.arena.alloc(result),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
with_hole(
|
||||||
|
env,
|
||||||
|
loc_arg.value,
|
||||||
|
procs,
|
||||||
|
layout_cache,
|
||||||
|
symbol,
|
||||||
|
env.arena.alloc(result),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_to_symbols<'a, I>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
procs: &mut Procs<'a>,
|
||||||
|
layout_cache: &mut LayoutCache<'a>,
|
||||||
|
iter: I,
|
||||||
|
mut result: Stmt<'a>,
|
||||||
|
) -> Stmt<'a>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = ((Variable, Located<roc_can::expr::Expr>), &'a Symbol)>,
|
||||||
|
{
|
||||||
|
for ((arg_var, loc_arg), symbol) in iter {
|
||||||
|
result = assign_to_symbol(env, procs, layout_cache, arg_var, loc_arg, *symbol, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn call_by_name<'a>(
|
fn call_by_name<'a>(
|
||||||
env: &mut Env<'a, '_>,
|
env: &mut Env<'a, '_>,
|
||||||
|
@ -3085,16 +3133,13 @@ fn call_by_name<'a>(
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena);
|
let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena);
|
||||||
|
|
||||||
let mut field_symbols = Vec::with_capacity_in(loc_args.len(), env.arena);
|
let field_symbols = Vec::from_iter_in(
|
||||||
|
loc_args
|
||||||
for (_, arg_expr) in loc_args.iter() {
|
.iter()
|
||||||
if let roc_can::expr::Expr::Var(symbol) = arg_expr.value {
|
.map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)),
|
||||||
field_symbols.push(symbol);
|
arena,
|
||||||
} else {
|
)
|
||||||
field_symbols.push(env.unique_symbol());
|
.into_bump_slice();
|
||||||
}
|
|
||||||
}
|
|
||||||
let field_symbols = field_symbols.into_bump_slice();
|
|
||||||
|
|
||||||
for (var, _) in &loc_args {
|
for (var, _) in &loc_args {
|
||||||
match layout_cache.from_var(&env.arena, *var, &env.subs) {
|
match layout_cache.from_var(&env.arena, *var, &env.subs) {
|
||||||
|
@ -3132,26 +3177,10 @@ fn call_by_name<'a>(
|
||||||
args: field_symbols,
|
args: field_symbols,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result = Stmt::Let(assigned, call, ret_layout.clone(), hole);
|
let result = Stmt::Let(assigned, call, ret_layout.clone(), hole);
|
||||||
|
|
||||||
for ((_, loc_arg), symbol) in
|
let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev());
|
||||||
loc_args.into_iter().rev().zip(field_symbols.iter().rev())
|
assign_to_symbols(env, procs, layout_cache, iter, result)
|
||||||
{
|
|
||||||
// if this argument is already a symbol, we don't need to re-define it
|
|
||||||
if let roc_can::expr::Expr::Var(_) = loc_arg.value {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result = with_hole(
|
|
||||||
env,
|
|
||||||
loc_arg.value,
|
|
||||||
procs,
|
|
||||||
layout_cache,
|
|
||||||
*symbol,
|
|
||||||
env.arena.alloc(result),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
} else {
|
} else {
|
||||||
let pending = PendingSpecialization {
|
let pending = PendingSpecialization {
|
||||||
pattern_vars,
|
pattern_vars,
|
||||||
|
@ -3188,26 +3217,10 @@ fn call_by_name<'a>(
|
||||||
args: field_symbols,
|
args: field_symbols,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result = Stmt::Let(assigned, call, ret_layout.clone(), hole);
|
let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev());
|
||||||
|
|
||||||
for ((_, loc_arg), symbol) in
|
let result = Stmt::Let(assigned, call, ret_layout.clone(), hole);
|
||||||
loc_args.into_iter().rev().zip(field_symbols.iter().rev())
|
assign_to_symbols(env, procs, layout_cache, iter, result)
|
||||||
{
|
|
||||||
// if this argument is already a symbol, we don't need to re-define it
|
|
||||||
if let roc_can::expr::Expr::Var(_) = loc_arg.value {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result = with_hole(
|
|
||||||
env,
|
|
||||||
loc_arg.value,
|
|
||||||
procs,
|
|
||||||
layout_cache,
|
|
||||||
*symbol,
|
|
||||||
env.arena.alloc(result),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let opt_partial_proc = procs.partial_procs.get(&proc_name);
|
let opt_partial_proc = procs.partial_procs.get(&proc_name);
|
||||||
|
@ -3245,29 +3258,15 @@ fn call_by_name<'a>(
|
||||||
args: field_symbols,
|
args: field_symbols,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result =
|
let iter = loc_args
|
||||||
Stmt::Let(assigned, call, ret_layout.clone(), hole);
|
|
||||||
|
|
||||||
for ((_, loc_arg), symbol) in loc_args
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.rev()
|
.rev()
|
||||||
.zip(field_symbols.iter().rev())
|
.zip(field_symbols.iter().rev());
|
||||||
{
|
|
||||||
// if this argument is already a symbol, we don't need to re-define it
|
|
||||||
if let roc_can::expr::Expr::Var(_) = loc_arg.value {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result = with_hole(
|
|
||||||
env,
|
|
||||||
loc_arg.value,
|
|
||||||
procs,
|
|
||||||
layout_cache,
|
|
||||||
*symbol,
|
|
||||||
env.arena.alloc(result),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
let result =
|
||||||
|
Stmt::Let(assigned, call, ret_layout.clone(), hole);
|
||||||
|
|
||||||
|
assign_to_symbols(env, procs, layout_cache, iter, result)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let error_msg = env.arena.alloc(format!(
|
let error_msg = env.arena.alloc(format!(
|
||||||
|
@ -3285,7 +3284,9 @@ fn call_by_name<'a>(
|
||||||
None => {
|
None => {
|
||||||
// This must have been a runtime error.
|
// This must have been a runtime error.
|
||||||
match procs.runtime_errors.get(&proc_name) {
|
match procs.runtime_errors.get(&proc_name) {
|
||||||
Some(error) => Stmt::RuntimeError(error),
|
Some(error) => {
|
||||||
|
Stmt::RuntimeError(env.arena.alloc(format!("{:?}", error)))
|
||||||
|
}
|
||||||
None => unreachable!("Proc name {:?} is invalid", proc_name),
|
None => unreachable!("Proc name {:?} is invalid", proc_name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3294,10 +3295,10 @@ fn call_by_name<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(e) => {
|
||||||
// This function code gens to a runtime error,
|
// This function code gens to a runtime error,
|
||||||
// so attempting to call it will immediately crash.
|
// so attempting to call it will immediately crash.
|
||||||
Stmt::RuntimeError("")
|
Stmt::RuntimeError(env.arena.alloc(format!("{:?}", e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -354,7 +354,7 @@ fn layout_from_flat_type<'a>(
|
||||||
// Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer
|
// Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
let var = args.get(0).unwrap();
|
let var = args.first().unwrap();
|
||||||
let content = subs.get_without_compacting(*var).content;
|
let content = subs.get_without_compacting(*var).content;
|
||||||
|
|
||||||
layout_from_num_content(content)
|
layout_from_num_content(content)
|
||||||
|
|
|
@ -1742,6 +1742,24 @@ mod test_mono {
|
||||||
d = f { x: Red }
|
d = f { x: Red }
|
||||||
|
|
||||||
a * b * c * d
|
a * b * c * d
|
||||||
|
#[test]
|
||||||
|
fn list_map() {
|
||||||
|
compiles_to_ir(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
main = \{} ->
|
||||||
|
nonEmpty : List Int
|
||||||
|
nonEmpty =
|
||||||
|
[ 1, 1, -4, 1, 2 ]
|
||||||
|
|
||||||
|
|
||||||
|
greaterThanOne : Int -> Bool
|
||||||
|
greaterThanOne = \i ->
|
||||||
|
i > 0
|
||||||
|
|
||||||
|
List.map nonEmpty greaterThanOne
|
||||||
|
|
||||||
|
main {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
|
|
|
@ -84,6 +84,46 @@ pub struct WhenPattern<'a> {
|
||||||
pub guard: Option<Loc<Expr<'a>>>,
|
pub guard: Option<Loc<Expr<'a>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum StrSegment<'a> {
|
||||||
|
Plaintext(&'a str), // e.g. "foo"
|
||||||
|
Unicode(Loc<&'a str>), // e.g. "00A0" in "\u(00A0)"
|
||||||
|
EscapedChar(EscapedChar), // e.g. '\n' in "Hello!\n"
|
||||||
|
Interpolated(Loc<&'a Expr<'a>>), // e.g. (name) in "Hi, \(name)!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub enum EscapedChar {
|
||||||
|
Newline, // \n
|
||||||
|
Tab, // \t
|
||||||
|
Quote, // \"
|
||||||
|
Backslash, // \\
|
||||||
|
CarriageReturn, // \r
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EscapedChar {
|
||||||
|
/// Returns the char that would have been originally parsed to
|
||||||
|
pub fn to_parsed_char(&self) -> char {
|
||||||
|
use EscapedChar::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Backslash => '\\',
|
||||||
|
Quote => '"',
|
||||||
|
CarriageReturn => 'r',
|
||||||
|
Tab => 't',
|
||||||
|
Newline => 'n',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum StrLiteral<'a> {
|
||||||
|
/// The most common case: a plain string with no escapes or interpolations
|
||||||
|
PlainLine(&'a str),
|
||||||
|
Line(&'a [StrSegment<'a>]),
|
||||||
|
Block(&'a [&'a [StrSegment<'a>]]),
|
||||||
|
}
|
||||||
|
|
||||||
/// A parsed expression. This uses lifetimes extensively for two reasons:
|
/// A parsed expression. This uses lifetimes extensively for two reasons:
|
||||||
///
|
///
|
||||||
/// 1. It uses Bump::alloc for all allocations, which returns a reference.
|
/// 1. It uses Bump::alloc for all allocations, which returns a reference.
|
||||||
|
@ -105,8 +145,7 @@ pub enum Expr<'a> {
|
||||||
},
|
},
|
||||||
|
|
||||||
// String Literals
|
// String Literals
|
||||||
Str(&'a str),
|
Str(StrLiteral<'a>), // string without escapes in it
|
||||||
BlockStr(&'a [&'a str]),
|
|
||||||
/// Look up exactly one field on a record, e.g. (expr).foo.
|
/// Look up exactly one field on a record, e.g. (expr).foo.
|
||||||
Access(&'a Expr<'a>, &'a str),
|
Access(&'a Expr<'a>, &'a str),
|
||||||
/// e.g. `.foo`
|
/// e.g. `.foo`
|
||||||
|
@ -336,8 +375,7 @@ pub enum Pattern<'a> {
|
||||||
is_negative: bool,
|
is_negative: bool,
|
||||||
},
|
},
|
||||||
FloatLiteral(&'a str),
|
FloatLiteral(&'a str),
|
||||||
StrLiteral(&'a str),
|
StrLiteral(StrLiteral<'a>),
|
||||||
BlockStrLiteral(&'a [&'a str]),
|
|
||||||
Underscore,
|
Underscore,
|
||||||
|
|
||||||
// Space
|
// Space
|
||||||
|
@ -455,7 +493,6 @@ impl<'a> Pattern<'a> {
|
||||||
) => string_x == string_y && base_x == base_y && is_negative_x == is_negative_y,
|
) => string_x == string_y && base_x == base_y && is_negative_x == is_negative_y,
|
||||||
(FloatLiteral(x), FloatLiteral(y)) => x == y,
|
(FloatLiteral(x), FloatLiteral(y)) => x == y,
|
||||||
(StrLiteral(x), StrLiteral(y)) => x == y,
|
(StrLiteral(x), StrLiteral(y)) => x == y,
|
||||||
(BlockStrLiteral(x), BlockStrLiteral(y)) => x == y,
|
|
||||||
(Underscore, Underscore) => true,
|
(Underscore, Underscore) => true,
|
||||||
|
|
||||||
// Space
|
// Space
|
||||||
|
@ -584,7 +621,7 @@ impl<'a> Spaceable<'a> for Def<'a> {
|
||||||
pub enum Attempting {
|
pub enum Attempting {
|
||||||
List,
|
List,
|
||||||
Keyword,
|
Keyword,
|
||||||
StringLiteral,
|
StrLiteral,
|
||||||
RecordLiteral,
|
RecordLiteral,
|
||||||
RecordFieldLabel,
|
RecordFieldLabel,
|
||||||
InterpolatedString,
|
InterpolatedString,
|
||||||
|
@ -596,6 +633,7 @@ pub enum Attempting {
|
||||||
Module,
|
Module,
|
||||||
Record,
|
Record,
|
||||||
Identifier,
|
Identifier,
|
||||||
|
HexDigit,
|
||||||
ConcreteType,
|
ConcreteType,
|
||||||
TypeVariable,
|
TypeVariable,
|
||||||
WhenCondition,
|
WhenCondition,
|
||||||
|
|
|
@ -300,12 +300,8 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
|
||||||
base: *base,
|
base: *base,
|
||||||
is_negative: *is_negative,
|
is_negative: *is_negative,
|
||||||
}),
|
}),
|
||||||
Expr::Str(string) => Ok(Pattern::StrLiteral(string)),
|
|
||||||
Expr::MalformedIdent(string) => Ok(Pattern::Malformed(string)),
|
|
||||||
|
|
||||||
// These would not have parsed as patterns
|
// These would not have parsed as patterns
|
||||||
Expr::BlockStr(_)
|
Expr::AccessorFunction(_)
|
||||||
| Expr::AccessorFunction(_)
|
|
||||||
| Expr::Access(_, _)
|
| Expr::Access(_, _)
|
||||||
| Expr::List(_)
|
| Expr::List(_)
|
||||||
| Expr::Closure(_, _)
|
| Expr::Closure(_, _)
|
||||||
|
@ -322,6 +318,9 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
|
||||||
attempting: Attempting::Def,
|
attempting: Attempting::Def,
|
||||||
reason: FailReason::InvalidPattern,
|
reason: FailReason::InvalidPattern,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
Expr::Str(string) => Ok(Pattern::StrLiteral(string.clone())),
|
||||||
|
Expr::MalformedIdent(string) => Ok(Pattern::Malformed(string)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -580,11 +579,7 @@ fn annotation_or_alias<'a>(
|
||||||
QualifiedIdentifier { .. } => {
|
QualifiedIdentifier { .. } => {
|
||||||
panic!("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`");
|
panic!("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`");
|
||||||
}
|
}
|
||||||
NumLiteral(_)
|
NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) => {
|
||||||
| NonBase10Literal { .. }
|
|
||||||
| FloatLiteral(_)
|
|
||||||
| StrLiteral(_)
|
|
||||||
| BlockStrLiteral(_) => {
|
|
||||||
panic!("TODO gracefully handle trying to annotate a litera");
|
panic!("TODO gracefully handle trying to annotate a litera");
|
||||||
}
|
}
|
||||||
Underscore => {
|
Underscore => {
|
||||||
|
@ -916,10 +911,7 @@ fn number_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
fn string_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||||
map!(crate::string_literal::parse(), |result| match result {
|
map!(crate::string_literal::parse(), Pattern::StrLiteral)
|
||||||
crate::string_literal::StringLiteral::Line(string) => Pattern::StrLiteral(string),
|
|
||||||
crate::string_literal::StringLiteral::Block(lines) => Pattern::BlockStrLiteral(lines),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||||
|
@ -1789,8 +1781,5 @@ pub fn global_tag<'a>() -> impl Parser<'a, &'a str> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn string_literal<'a>() -> impl Parser<'a, Expr<'a>> {
|
pub fn string_literal<'a>() -> impl Parser<'a, Expr<'a>> {
|
||||||
map!(crate::string_literal::parse(), |result| match result {
|
map!(crate::string_literal::parse(), Expr::Str)
|
||||||
crate::string_literal::StringLiteral::Line(string) => Expr::Str(string),
|
|
||||||
crate::string_literal::StringLiteral::Block(lines) => Expr::BlockStr(lines),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -445,6 +445,29 @@ pub fn ascii_char<'a>(expected: char) -> impl Parser<'a, ()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One or more ASCII hex digits. (Useful when parsing unicode escape codes,
|
||||||
|
/// which must consist entirely of ASCII hex digits.)
|
||||||
|
pub fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str> {
|
||||||
|
move |arena, state: State<'a>| {
|
||||||
|
let mut buf = bumpalo::collections::String::new_in(arena);
|
||||||
|
|
||||||
|
for &byte in state.bytes.iter() {
|
||||||
|
if (byte as char).is_ascii_hexdigit() {
|
||||||
|
buf.push(byte as char);
|
||||||
|
} else if buf.is_empty() {
|
||||||
|
// We didn't find any hex digits!
|
||||||
|
return Err(unexpected(0, state, Attempting::Keyword));
|
||||||
|
} else {
|
||||||
|
let state = state.advance_without_indenting(buf.len())?;
|
||||||
|
|
||||||
|
return Ok((buf.into_bump_str(), state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(unexpected_eof(0, Attempting::HexDigit, state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A single UTF-8-encoded char. This will both parse *and* validate that the
|
/// A single UTF-8-encoded char. This will both parse *and* validate that the
|
||||||
/// char is valid UTF-8, but it will *not* advance the state.
|
/// char is valid UTF-8, but it will *not* advance the state.
|
||||||
pub fn peek_utf8_char<'a>(state: &State<'a>) -> Result<(char, usize), FailReason> {
|
pub fn peek_utf8_char<'a>(state: &State<'a>) -> Result<(char, usize), FailReason> {
|
||||||
|
|
|
@ -1,90 +1,242 @@
|
||||||
use crate::ast::Attempting;
|
use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment};
|
||||||
use crate::parser::{parse_utf8, unexpected, unexpected_eof, ParseResult, Parser, State};
|
use crate::expr;
|
||||||
|
use crate::parser::{
|
||||||
|
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof,
|
||||||
|
ParseResult, Parser, State,
|
||||||
|
};
|
||||||
use bumpalo::collections::vec::Vec;
|
use bumpalo::collections::vec::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
|
||||||
pub enum StringLiteral<'a> {
|
pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
|
||||||
Line(&'a str),
|
use StrLiteral::*;
|
||||||
Block(&'a [&'a str]),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse<'a>() -> impl Parser<'a, StringLiteral<'a>> {
|
move |arena: &'a Bump, mut state: State<'a>| {
|
||||||
move |arena: &'a Bump, state: State<'a>| {
|
|
||||||
let mut bytes = state.bytes.iter();
|
let mut bytes = state.bytes.iter();
|
||||||
|
|
||||||
// String literals must start with a quote.
|
// String literals must start with a quote.
|
||||||
// If this doesn't, it must not be a string literal!
|
// If this doesn't, it must not be a string literal!
|
||||||
match bytes.next() {
|
match bytes.next() {
|
||||||
Some(&byte) => {
|
Some(&byte) => {
|
||||||
if byte != b'"' {
|
if byte != b'"' {
|
||||||
return Err(unexpected(0, state, Attempting::StringLiteral));
|
return Err(unexpected(0, state, Attempting::StrLiteral));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
return Err(unexpected_eof(0, Attempting::StringLiteral, state));
|
return Err(unexpected_eof(0, Attempting::StrLiteral, state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advance past the opening quotation mark.
|
||||||
|
state = state.advance_without_indenting(1)?;
|
||||||
|
|
||||||
// At the parsing stage we keep the entire raw string, because the formatter
|
// At the parsing stage we keep the entire raw string, because the formatter
|
||||||
// needs the raw string. (For example, so it can "remember" whether you
|
// needs the raw string. (For example, so it can "remember" whether you
|
||||||
// wrote \u{...} or the actual unicode character itself.)
|
// wrote \u{...} or the actual unicode character itself.)
|
||||||
//
|
//
|
||||||
// Later, in canonicalization, we'll do things like resolving
|
|
||||||
// unicode escapes and string interpolation.
|
|
||||||
//
|
|
||||||
// Since we're keeping the entire raw string, all we need to track is
|
// Since we're keeping the entire raw string, all we need to track is
|
||||||
// how many characters we've parsed. So far, that's 1 (the opening `"`).
|
// how many characters we've parsed. So far, that's 1 (the opening `"`).
|
||||||
let mut parsed_chars = 1;
|
let mut segment_parsed_bytes = 0;
|
||||||
let mut prev_byte = b'"';
|
let mut segments = Vec::new_in(arena);
|
||||||
|
|
||||||
while let Some(&byte) = bytes.next() {
|
macro_rules! escaped_char {
|
||||||
parsed_chars += 1;
|
($ch:expr) => {
|
||||||
|
// Record the escaped char.
|
||||||
|
segments.push(StrSegment::EscapedChar($ch));
|
||||||
|
|
||||||
// Potentially end the string (unless this is an escaped `"`!)
|
// Advance past the segment we just added
|
||||||
if byte == b'"' && prev_byte != b'\\' {
|
state = state.advance_without_indenting(segment_parsed_bytes)?;
|
||||||
let (string, state) = if parsed_chars == 2 {
|
|
||||||
match bytes.next() {
|
// Reset the segment
|
||||||
Some(byte) if *byte == b'"' => {
|
segment_parsed_bytes = 0;
|
||||||
// If the first three chars were all `"`, then this
|
};
|
||||||
// literal begins with `"""` and is a block string.
|
}
|
||||||
return parse_block_string(arena, state, &mut bytes);
|
|
||||||
}
|
macro_rules! end_segment {
|
||||||
_ => ("", state.advance_without_indenting(2)?),
|
($transform:expr) => {
|
||||||
}
|
// Don't push anything if the string would be empty.
|
||||||
} else {
|
if segment_parsed_bytes > 1 {
|
||||||
// Start at 1 so we omit the opening `"`.
|
// This function is always called after we just parsed
|
||||||
// Subtract 1 from parsed_chars so we omit the closing `"`.
|
// something which signalled that we should end the
|
||||||
let string_bytes = &state.bytes[1..(parsed_chars - 1)];
|
// current segment - so use segment_parsed_bytes - 1 here,
|
||||||
|
// to exclude that char we just parsed.
|
||||||
|
let string_bytes = &state.bytes[0..(segment_parsed_bytes - 1)];
|
||||||
|
|
||||||
match parse_utf8(string_bytes) {
|
match parse_utf8(string_bytes) {
|
||||||
Ok(string) => (string, state.advance_without_indenting(parsed_chars)?),
|
Ok(string) => {
|
||||||
|
state = state.advance_without_indenting(string.len())?;
|
||||||
|
|
||||||
|
segments.push($transform(string));
|
||||||
|
}
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
return state.fail(reason);
|
return state.fail(reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return Ok((StringLiteral::Line(string), state));
|
// Depending on where this macro is used, in some
|
||||||
} else if byte == b'\n' {
|
// places this is unused.
|
||||||
// This is a single-line string, which cannot have newlines!
|
#[allow(unused_assignments)]
|
||||||
// Treat this as an unclosed string literal, and consume
|
{
|
||||||
// all remaining chars. This will mask all other errors, but
|
// This function is always called after we just parsed
|
||||||
// it should make it easiest to debug; the file will be a giant
|
// something which signalled that we should end the
|
||||||
// error starting from where the open quote appeared.
|
// current segment.
|
||||||
return Err(unexpected(
|
segment_parsed_bytes = 1;
|
||||||
state.bytes.len() - 1,
|
}
|
||||||
state,
|
};
|
||||||
Attempting::StringLiteral,
|
}
|
||||||
));
|
|
||||||
} else {
|
while let Some(&byte) = bytes.next() {
|
||||||
prev_byte = byte;
|
// This is for the byte we just grabbed from the iterator.
|
||||||
|
segment_parsed_bytes += 1;
|
||||||
|
|
||||||
|
match byte {
|
||||||
|
b'"' => {
|
||||||
|
// This is the end of the string!
|
||||||
|
if segment_parsed_bytes == 1 && segments.is_empty() {
|
||||||
|
match bytes.next() {
|
||||||
|
Some(b'"') => {
|
||||||
|
// If the very first three chars were all `"`,
|
||||||
|
// then this literal begins with `"""`
|
||||||
|
// and is a block string.
|
||||||
|
return parse_block_string(arena, state, &mut bytes);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Advance 1 for the close quote
|
||||||
|
return Ok((PlainLine(""), state.advance_without_indenting(1)?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
end_segment!(StrSegment::Plaintext);
|
||||||
|
|
||||||
|
let expr = if segments.len() == 1 {
|
||||||
|
// We had exactly one segment, so this is a candidate
|
||||||
|
// to be StrLiteral::Plaintext
|
||||||
|
match segments.pop().unwrap() {
|
||||||
|
StrSegment::Plaintext(string) => StrLiteral::PlainLine(string),
|
||||||
|
other => {
|
||||||
|
let vec = bumpalo::vec![in arena; other];
|
||||||
|
|
||||||
|
StrLiteral::Line(vec.into_bump_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Line(segments.into_bump_slice())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Advance the state 1 to account for the closing `"`
|
||||||
|
return Ok((expr, state.advance_without_indenting(1)?));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
b'\n' => {
|
||||||
|
// This is a single-line string, which cannot have newlines!
|
||||||
|
// Treat this as an unclosed string literal, and consume
|
||||||
|
// all remaining chars. This will mask all other errors, but
|
||||||
|
// it should make it easiest to debug; the file will be a giant
|
||||||
|
// error starting from where the open quote appeared.
|
||||||
|
return Err(unexpected(
|
||||||
|
state.bytes.len() - 1,
|
||||||
|
state,
|
||||||
|
Attempting::StrLiteral,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
b'\\' => {
|
||||||
|
// We're about to begin an escaped segment of some sort!
|
||||||
|
//
|
||||||
|
// Record the current segment so we can begin a new one.
|
||||||
|
// End it right before the `\` char we just parsed.
|
||||||
|
end_segment!(StrSegment::Plaintext);
|
||||||
|
|
||||||
|
// This is for the byte we're about to parse.
|
||||||
|
segment_parsed_bytes += 1;
|
||||||
|
|
||||||
|
// This is the start of a new escape. Look at the next byte
|
||||||
|
// to figure out what type of escape it is.
|
||||||
|
match bytes.next() {
|
||||||
|
Some(b'(') => {
|
||||||
|
// Advance past the `\(` before using the expr parser
|
||||||
|
state = state.advance_without_indenting(2)?;
|
||||||
|
|
||||||
|
let original_byte_count = state.bytes.len();
|
||||||
|
|
||||||
|
// This is an interpolated variable.
|
||||||
|
// Parse an arbitrary expression, then give a
|
||||||
|
// canonicalization error if that expression variant
|
||||||
|
// is not allowed inside a string interpolation.
|
||||||
|
let (loc_expr, new_state) =
|
||||||
|
skip_second!(loc(allocated(expr::expr(0))), ascii_char(')'))
|
||||||
|
.parse(arena, state)?;
|
||||||
|
|
||||||
|
// Advance the iterator past the expr we just parsed.
|
||||||
|
for _ in 0..(original_byte_count - new_state.bytes.len()) {
|
||||||
|
bytes.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
segments.push(StrSegment::Interpolated(loc_expr));
|
||||||
|
|
||||||
|
// Reset the segment
|
||||||
|
segment_parsed_bytes = 0;
|
||||||
|
state = new_state;
|
||||||
|
}
|
||||||
|
Some(b'u') => {
|
||||||
|
// Advance past the `\u` before using the expr parser
|
||||||
|
state = state.advance_without_indenting(2)?;
|
||||||
|
|
||||||
|
let original_byte_count = state.bytes.len();
|
||||||
|
|
||||||
|
// Parse the hex digits, surrounded by parens, then
|
||||||
|
// give a canonicalization error if the digits form
|
||||||
|
// an invalid unicode code point.
|
||||||
|
let (loc_digits, new_state) =
|
||||||
|
between!(ascii_char('('), loc(ascii_hex_digits()), ascii_char(')'))
|
||||||
|
.parse(arena, state)?;
|
||||||
|
|
||||||
|
// Advance the iterator past the expr we just parsed.
|
||||||
|
for _ in 0..(original_byte_count - new_state.bytes.len()) {
|
||||||
|
bytes.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
segments.push(StrSegment::Unicode(loc_digits));
|
||||||
|
|
||||||
|
// Reset the segment
|
||||||
|
segment_parsed_bytes = 0;
|
||||||
|
state = new_state;
|
||||||
|
}
|
||||||
|
Some(b'\\') => {
|
||||||
|
escaped_char!(EscapedChar::Backslash);
|
||||||
|
}
|
||||||
|
Some(b'"') => {
|
||||||
|
escaped_char!(EscapedChar::Quote);
|
||||||
|
}
|
||||||
|
Some(b'r') => {
|
||||||
|
escaped_char!(EscapedChar::CarriageReturn);
|
||||||
|
}
|
||||||
|
Some(b't') => {
|
||||||
|
escaped_char!(EscapedChar::Tab);
|
||||||
|
}
|
||||||
|
Some(b'n') => {
|
||||||
|
escaped_char!(EscapedChar::Newline);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Invalid escape! A backslash must be followed
|
||||||
|
// by either an open paren or else one of the
|
||||||
|
// escapable characters (\n, \t, \", \\, etc)
|
||||||
|
return Err(unexpected(
|
||||||
|
state.bytes.len() - 1,
|
||||||
|
state,
|
||||||
|
Attempting::StrLiteral,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// All other characters need no special handling.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We ran out of characters before finding a closed quote
|
// We ran out of characters before finding a closed quote
|
||||||
Err(unexpected_eof(
|
Err(unexpected_eof(
|
||||||
parsed_chars,
|
state.bytes.len(),
|
||||||
Attempting::StringLiteral,
|
Attempting::StrLiteral,
|
||||||
state.clone(),
|
state.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -94,7 +246,7 @@ fn parse_block_string<'a, I>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
state: State<'a>,
|
state: State<'a>,
|
||||||
bytes: &mut I,
|
bytes: &mut I,
|
||||||
) -> ParseResult<'a, StringLiteral<'a>>
|
) -> ParseResult<'a, StrLiteral<'a>>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = &'a u8>,
|
I: Iterator<Item = &'a u8>,
|
||||||
{
|
{
|
||||||
|
@ -112,42 +264,47 @@ where
|
||||||
parsed_chars += 1;
|
parsed_chars += 1;
|
||||||
|
|
||||||
// Potentially end the string (unless this is an escaped `"`!)
|
// Potentially end the string (unless this is an escaped `"`!)
|
||||||
if *byte == b'"' && prev_byte != b'\\' {
|
match byte {
|
||||||
if quotes_seen == 2 {
|
b'"' if prev_byte != b'\\' => {
|
||||||
// three consecutive qoutes, end string
|
if quotes_seen == 2 {
|
||||||
|
// three consecutive qoutes, end string
|
||||||
|
|
||||||
// Subtract 3 from parsed_chars so we omit the closing `"`.
|
// Subtract 3 from parsed_chars so we omit the closing `"`.
|
||||||
let line_bytes = &state.bytes[line_start..(parsed_chars - 3)];
|
let line_bytes = &state.bytes[line_start..(parsed_chars - 3)];
|
||||||
|
|
||||||
return match parse_utf8(line_bytes) {
|
return match parse_utf8(line_bytes) {
|
||||||
|
Ok(line) => {
|
||||||
|
// state = state.advance_without_indenting(parsed_chars)?;
|
||||||
|
|
||||||
|
// lines.push(line);
|
||||||
|
|
||||||
|
// Ok((StrLiteral::Block(lines.into_bump_slice()), state))
|
||||||
|
todo!("TODO parse this line in a block string: {:?}", line);
|
||||||
|
}
|
||||||
|
Err(reason) => state.fail(reason),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
quotes_seen += 1;
|
||||||
|
}
|
||||||
|
b'\n' => {
|
||||||
|
// note this includes the newline
|
||||||
|
let line_bytes = &state.bytes[line_start..parsed_chars];
|
||||||
|
|
||||||
|
match parse_utf8(line_bytes) {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
let state = state.advance_without_indenting(parsed_chars)?;
|
|
||||||
|
|
||||||
lines.push(line);
|
lines.push(line);
|
||||||
|
|
||||||
Ok((StringLiteral::Block(arena.alloc(lines)), state))
|
quotes_seen = 0;
|
||||||
|
line_start = parsed_chars;
|
||||||
|
}
|
||||||
|
Err(reason) => {
|
||||||
|
return state.fail(reason);
|
||||||
}
|
}
|
||||||
Err(reason) => state.fail(reason),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
quotes_seen += 1;
|
|
||||||
} else if *byte == b'\n' {
|
|
||||||
// note this includes the newline
|
|
||||||
let line_bytes = &state.bytes[line_start..parsed_chars];
|
|
||||||
|
|
||||||
match parse_utf8(line_bytes) {
|
|
||||||
Ok(line) => {
|
|
||||||
lines.push(line);
|
|
||||||
|
|
||||||
quotes_seen = 0;
|
|
||||||
line_start = parsed_chars;
|
|
||||||
}
|
|
||||||
Err(reason) => {
|
|
||||||
return state.fail(reason);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
_ => {
|
||||||
quotes_seen = 0;
|
quotes_seen = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_byte = *byte;
|
prev_byte = *byte;
|
||||||
|
@ -156,8 +313,8 @@ where
|
||||||
// We ran out of characters before finding 3 closing quotes
|
// We ran out of characters before finding 3 closing quotes
|
||||||
Err(unexpected_eof(
|
Err(unexpected_eof(
|
||||||
parsed_chars,
|
parsed_chars,
|
||||||
// TODO custom BlockStringLiteral?
|
// TODO custom BlockStrLiteral?
|
||||||
Attempting::StringLiteral,
|
Attempting::StrLiteral,
|
||||||
state,
|
state,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
||||||
let state = State::new(input.as_bytes(), Attempting::Module);
|
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||||
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
||||||
let answer = parser.parse(&arena, state);
|
let answer = parser.parse(&arena, state);
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,11 @@ mod test_parse {
|
||||||
use roc_parse::ast::CommentOrNewline::*;
|
use roc_parse::ast::CommentOrNewline::*;
|
||||||
use roc_parse::ast::Expr::{self, *};
|
use roc_parse::ast::Expr::{self, *};
|
||||||
use roc_parse::ast::Pattern::{self, *};
|
use roc_parse::ast::Pattern::{self, *};
|
||||||
|
use roc_parse::ast::StrLiteral::*;
|
||||||
|
use roc_parse::ast::StrSegment::*;
|
||||||
use roc_parse::ast::{
|
use roc_parse::ast::{
|
||||||
Attempting, Def, InterfaceHeader, Spaceable, Tag, TypeAnnotation, WhenBranch,
|
self, Attempting, Def, EscapedChar, InterfaceHeader, Spaceable, Tag, TypeAnnotation,
|
||||||
|
WhenBranch,
|
||||||
};
|
};
|
||||||
use roc_parse::header::ModuleName;
|
use roc_parse::header::ModuleName;
|
||||||
use roc_parse::module::{interface_header, module_defs};
|
use roc_parse::module::{interface_header, module_defs};
|
||||||
|
@ -35,7 +38,7 @@ mod test_parse {
|
||||||
|
|
||||||
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
|
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let actual = parse_with(&arena, input);
|
let actual = parse_with(&arena, input.trim());
|
||||||
|
|
||||||
assert_eq!(Ok(expected_expr), actual);
|
assert_eq!(Ok(expected_expr), actual);
|
||||||
}
|
}
|
||||||
|
@ -48,10 +51,44 @@ mod test_parse {
|
||||||
assert_eq!(Err(expected_fail), actual);
|
assert_eq!(Err(expected_fail), actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_segments<E: Fn(&Bump) -> Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) {
|
||||||
|
let arena = Bump::new();
|
||||||
|
let actual = parse_with(&arena, arena.alloc(input));
|
||||||
|
let expected_slice = to_expected(&arena).into_bump_slice();
|
||||||
|
let expected_expr = Expr::Str(Line(expected_slice));
|
||||||
|
|
||||||
|
assert_eq!(Ok(expected_expr), actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parses_with_escaped_char<
|
||||||
|
I: Fn(&str) -> String,
|
||||||
|
E: Fn(EscapedChar, &Bump) -> Vec<'_, ast::StrSegment<'_>>,
|
||||||
|
>(
|
||||||
|
to_input: I,
|
||||||
|
to_expected: E,
|
||||||
|
) {
|
||||||
|
let arena = Bump::new();
|
||||||
|
|
||||||
|
// Try parsing with each of the escaped chars Roc supports
|
||||||
|
for (string, escaped) in &[
|
||||||
|
("\\\\", EscapedChar::Backslash),
|
||||||
|
("\\n", EscapedChar::Newline),
|
||||||
|
("\\r", EscapedChar::CarriageReturn),
|
||||||
|
("\\t", EscapedChar::Tab),
|
||||||
|
("\\\"", EscapedChar::Quote),
|
||||||
|
] {
|
||||||
|
let actual = parse_with(&arena, arena.alloc(to_input(string)));
|
||||||
|
let expected_slice = to_expected(*escaped, &arena).into_bump_slice();
|
||||||
|
let expected_expr = Expr::Str(Line(expected_slice));
|
||||||
|
|
||||||
|
assert_eq!(Ok(expected_expr), actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// STRING LITERALS
|
// STRING LITERALS
|
||||||
|
|
||||||
fn expect_parsed_str(input: &str, expected: &str) {
|
fn expect_parsed_str(input: &str, expected: &str) {
|
||||||
assert_parses_to(expected, Str(input.into()));
|
assert_parses_to(expected, Expr::Str(PlainLine(input)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -59,10 +96,10 @@ mod test_parse {
|
||||||
assert_parses_to(
|
assert_parses_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
""
|
""
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
Str(""),
|
Str(PlainLine("")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,10 +108,10 @@ mod test_parse {
|
||||||
assert_parses_to(
|
assert_parses_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
"x"
|
"x"
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
Str("x".into()),
|
Expr::Str(PlainLine("x".into())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,10 +120,10 @@ mod test_parse {
|
||||||
assert_parses_to(
|
assert_parses_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
"foo"
|
"foo"
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
Str("foo".into()),
|
Expr::Str(PlainLine("foo".into())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,19 +138,155 @@ mod test_parse {
|
||||||
expect_parsed_str("123 abc 456 def", r#""123 abc 456 def""#);
|
expect_parsed_str("123 abc 456 def", r#""123 abc 456 def""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BACKSLASH ESCAPES
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_with_special_escapes() {
|
fn string_with_escaped_char_at_end() {
|
||||||
expect_parsed_str(r#"x\\x"#, r#""x\\x""#);
|
parses_with_escaped_char(
|
||||||
expect_parsed_str(r#"x\"x"#, r#""x\"x""#);
|
|esc| format!(r#""abcd{}""#, esc),
|
||||||
expect_parsed_str(r#"x\tx"#, r#""x\tx""#);
|
|esc, arena| bumpalo::vec![in arena; Plaintext("abcd"), EscapedChar(esc)],
|
||||||
expect_parsed_str(r#"x\rx"#, r#""x\rx""#);
|
);
|
||||||
expect_parsed_str(r#"x\nx"#, r#""x\nx""#);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_with_single_quote() {
|
fn string_with_escaped_char_in_front() {
|
||||||
// This shoud NOT be escaped in a string.
|
parses_with_escaped_char(
|
||||||
expect_parsed_str("x'x", r#""x'x""#);
|
|esc| format!(r#""{}abcd""#, esc),
|
||||||
|
|esc, arena| bumpalo::vec![in arena; EscapedChar(esc), Plaintext("abcd")],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_escaped_char_in_middle() {
|
||||||
|
parses_with_escaped_char(
|
||||||
|
|esc| format!(r#""ab{}cd""#, esc),
|
||||||
|
|esc, arena| bumpalo::vec![in arena; Plaintext("ab"), EscapedChar(esc), Plaintext("cd")],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_multiple_escaped_chars() {
|
||||||
|
parses_with_escaped_char(
|
||||||
|
|esc| format!(r#""{}abc{}de{}fghi{}""#, esc, esc, esc, esc),
|
||||||
|
|esc, arena| bumpalo::vec![in arena; EscapedChar(esc), Plaintext("abc"), EscapedChar(esc), Plaintext("de"), EscapedChar(esc), Plaintext("fghi"), EscapedChar(esc)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UNICODE ESCAPES
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unicode_escape_in_middle() {
|
||||||
|
assert_segments(r#""Hi, \u(123)!""#, |arena| {
|
||||||
|
bumpalo::vec![in arena;
|
||||||
|
Plaintext("Hi, "),
|
||||||
|
Unicode(Located::new(0, 0, 8, 11, "123")),
|
||||||
|
Plaintext("!")
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unicode_escape_in_front() {
|
||||||
|
assert_segments(r#""\u(1234) is a unicode char""#, |arena| {
|
||||||
|
bumpalo::vec![in arena;
|
||||||
|
Unicode(Located::new(0, 0, 4, 8, "1234")),
|
||||||
|
Plaintext(" is a unicode char")
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unicode_escape_in_back() {
|
||||||
|
assert_segments(r#""this is unicode: \u(1)""#, |arena| {
|
||||||
|
bumpalo::vec![in arena;
|
||||||
|
Plaintext("this is unicode: "),
|
||||||
|
Unicode(Located::new(0, 0, 21, 22, "1"))
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unicode_escape_multiple() {
|
||||||
|
assert_segments(r#""\u(a1) this is \u(2Bcd) unicode \u(ef97)""#, |arena| {
|
||||||
|
bumpalo::vec![in arena;
|
||||||
|
Unicode(Located::new(0, 0, 4, 6, "a1")),
|
||||||
|
Plaintext(" this is "),
|
||||||
|
Unicode(Located::new(0, 0, 19, 23, "2Bcd")),
|
||||||
|
Plaintext(" unicode "),
|
||||||
|
Unicode(Located::new(0, 0, 36, 40, "ef97"))
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// INTERPOLATION
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_interpolation_in_middle() {
|
||||||
|
assert_segments(r#""Hi, \(name)!""#, |arena| {
|
||||||
|
let expr = arena.alloc(Var {
|
||||||
|
module_name: "",
|
||||||
|
ident: "name",
|
||||||
|
});
|
||||||
|
|
||||||
|
bumpalo::vec![in arena;
|
||||||
|
Plaintext("Hi, "),
|
||||||
|
Interpolated(Located::new(0, 0, 7, 11, expr)),
|
||||||
|
Plaintext("!")
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_interpolation_in_front() {
|
||||||
|
assert_segments(r#""\(name), hi!""#, |arena| {
|
||||||
|
let expr = arena.alloc(Var {
|
||||||
|
module_name: "",
|
||||||
|
ident: "name",
|
||||||
|
});
|
||||||
|
|
||||||
|
bumpalo::vec![in arena;
|
||||||
|
Interpolated(Located::new(0, 0, 3, 7, expr)),
|
||||||
|
Plaintext(", hi!")
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_interpolation_in_back() {
|
||||||
|
assert_segments(r#""Hello \(name)""#, |arena| {
|
||||||
|
let expr = arena.alloc(Var {
|
||||||
|
module_name: "",
|
||||||
|
ident: "name",
|
||||||
|
});
|
||||||
|
|
||||||
|
bumpalo::vec![in arena;
|
||||||
|
Plaintext("Hello "),
|
||||||
|
Interpolated(Located::new(0, 0, 9, 13, expr))
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_multiple_interpolations() {
|
||||||
|
assert_segments(r#""Hi, \(name)! How is \(project) going?""#, |arena| {
|
||||||
|
let expr1 = arena.alloc(Var {
|
||||||
|
module_name: "",
|
||||||
|
ident: "name",
|
||||||
|
});
|
||||||
|
|
||||||
|
let expr2 = arena.alloc(Var {
|
||||||
|
module_name: "",
|
||||||
|
ident: "project",
|
||||||
|
});
|
||||||
|
|
||||||
|
bumpalo::vec![in arena;
|
||||||
|
Plaintext("Hi, "),
|
||||||
|
Interpolated(Located::new(0, 0, 7, 11, expr1)),
|
||||||
|
Plaintext("! How is "),
|
||||||
|
Interpolated(Located::new(0, 0, 23, 30, expr2)),
|
||||||
|
Plaintext(" going?")
|
||||||
|
]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -460,7 +633,7 @@ mod test_parse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn comment_with_unicode() {
|
fn comment_with_non_ascii() {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let spaced_int = arena
|
let spaced_int = arena
|
||||||
.alloc(Num("3"))
|
.alloc(Num("3"))
|
||||||
|
@ -1859,19 +2032,23 @@ mod test_parse {
|
||||||
fn two_branch_when() {
|
fn two_branch_when() {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let newlines = bumpalo::vec![in &arena; Newline];
|
let newlines = bumpalo::vec![in &arena; Newline];
|
||||||
let pattern1 =
|
let pattern1 = Pattern::SpaceBefore(
|
||||||
Pattern::SpaceBefore(arena.alloc(StrLiteral("blah")), newlines.into_bump_slice());
|
arena.alloc(StrLiteral(PlainLine(""))),
|
||||||
let loc_pattern1 = Located::new(1, 1, 1, 7, pattern1);
|
newlines.into_bump_slice(),
|
||||||
|
);
|
||||||
|
let loc_pattern1 = Located::new(1, 1, 1, 3, pattern1);
|
||||||
let expr1 = Num("1");
|
let expr1 = Num("1");
|
||||||
let loc_expr1 = Located::new(1, 1, 11, 12, expr1);
|
let loc_expr1 = Located::new(1, 1, 7, 8, expr1);
|
||||||
let branch1 = &*arena.alloc(WhenBranch {
|
let branch1 = &*arena.alloc(WhenBranch {
|
||||||
patterns: bumpalo::vec![in &arena;loc_pattern1],
|
patterns: bumpalo::vec![in &arena;loc_pattern1],
|
||||||
value: loc_expr1,
|
value: loc_expr1,
|
||||||
guard: None,
|
guard: None,
|
||||||
});
|
});
|
||||||
let newlines = bumpalo::vec![in &arena; Newline];
|
let newlines = bumpalo::vec![in &arena; Newline];
|
||||||
let pattern2 =
|
let pattern2 = Pattern::SpaceBefore(
|
||||||
Pattern::SpaceBefore(arena.alloc(StrLiteral("mise")), newlines.into_bump_slice());
|
arena.alloc(StrLiteral(PlainLine("mise"))),
|
||||||
|
newlines.into_bump_slice(),
|
||||||
|
);
|
||||||
let loc_pattern2 = Located::new(2, 2, 1, 7, pattern2);
|
let loc_pattern2 = Located::new(2, 2, 1, 7, pattern2);
|
||||||
let expr2 = Num("2");
|
let expr2 = Num("2");
|
||||||
let loc_expr2 = Located::new(2, 2, 11, 12, expr2);
|
let loc_expr2 = Located::new(2, 2, 11, 12, expr2);
|
||||||
|
@ -1891,9 +2068,9 @@ mod test_parse {
|
||||||
&arena,
|
&arena,
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
when x is
|
when x is
|
||||||
"blah" -> 1
|
"" -> 1
|
||||||
"mise" -> 2
|
"mise" -> 2
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -2003,9 +2180,11 @@ mod test_parse {
|
||||||
fn when_with_alternative_patterns() {
|
fn when_with_alternative_patterns() {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let newlines = bumpalo::vec![in &arena; Newline];
|
let newlines = bumpalo::vec![in &arena; Newline];
|
||||||
let pattern1 =
|
let pattern1 = Pattern::SpaceBefore(
|
||||||
Pattern::SpaceBefore(arena.alloc(StrLiteral("blah")), newlines.into_bump_slice());
|
arena.alloc(StrLiteral(PlainLine("blah"))),
|
||||||
let pattern1_alt = StrLiteral("blop");
|
newlines.into_bump_slice(),
|
||||||
|
);
|
||||||
|
let pattern1_alt = StrLiteral(PlainLine("blop"));
|
||||||
let loc_pattern1 = Located::new(1, 1, 1, 7, pattern1);
|
let loc_pattern1 = Located::new(1, 1, 1, 7, pattern1);
|
||||||
let loc_pattern1_alt = Located::new(1, 1, 10, 16, pattern1_alt);
|
let loc_pattern1_alt = Located::new(1, 1, 10, 16, pattern1_alt);
|
||||||
let expr1 = Num("1");
|
let expr1 = Num("1");
|
||||||
|
@ -2016,11 +2195,15 @@ mod test_parse {
|
||||||
guard: None,
|
guard: None,
|
||||||
});
|
});
|
||||||
let newlines = bumpalo::vec![in &arena; Newline];
|
let newlines = bumpalo::vec![in &arena; Newline];
|
||||||
let pattern2 =
|
let pattern2 = Pattern::SpaceBefore(
|
||||||
Pattern::SpaceBefore(arena.alloc(StrLiteral("foo")), newlines.into_bump_slice());
|
arena.alloc(StrLiteral(PlainLine("foo"))),
|
||||||
|
newlines.into_bump_slice(),
|
||||||
|
);
|
||||||
let newlines = bumpalo::vec![in &arena; Newline];
|
let newlines = bumpalo::vec![in &arena; Newline];
|
||||||
let pattern2_alt =
|
let pattern2_alt = Pattern::SpaceBefore(
|
||||||
Pattern::SpaceBefore(arena.alloc(StrLiteral("bar")), newlines.into_bump_slice());
|
arena.alloc(StrLiteral(PlainLine("bar"))),
|
||||||
|
newlines.into_bump_slice(),
|
||||||
|
);
|
||||||
let loc_pattern2 = Located::new(2, 2, 1, 6, pattern2);
|
let loc_pattern2 = Located::new(2, 2, 1, 6, pattern2);
|
||||||
let loc_pattern2_alt = Located::new(3, 3, 1, 6, pattern2_alt);
|
let loc_pattern2_alt = Located::new(3, 3, 1, 6, pattern2_alt);
|
||||||
let expr2 = Num("2");
|
let expr2 = Num("2");
|
||||||
|
@ -2133,14 +2316,14 @@ mod test_parse {
|
||||||
let def2 = SpaceAfter(
|
let def2 = SpaceAfter(
|
||||||
arena.alloc(Body(
|
arena.alloc(Body(
|
||||||
arena.alloc(Located::new(2, 2, 0, 3, pattern2)),
|
arena.alloc(Located::new(2, 2, 0, 3, pattern2)),
|
||||||
arena.alloc(Located::new(2, 2, 6, 10, Str("hi"))),
|
arena.alloc(Located::new(2, 2, 6, 10, Str(PlainLine("hi")))),
|
||||||
)),
|
)),
|
||||||
newlines2.into_bump_slice(),
|
newlines2.into_bump_slice(),
|
||||||
);
|
);
|
||||||
let def3 = SpaceAfter(
|
let def3 = SpaceAfter(
|
||||||
arena.alloc(Body(
|
arena.alloc(Body(
|
||||||
arena.alloc(Located::new(3, 3, 0, 3, pattern3)),
|
arena.alloc(Located::new(3, 3, 0, 3, pattern3)),
|
||||||
arena.alloc(Located::new(3, 3, 6, 13, Str("stuff"))),
|
arena.alloc(Located::new(3, 3, 6, 13, Str(PlainLine("stuff")))),
|
||||||
)),
|
)),
|
||||||
newlines3.into_bump_slice(),
|
newlines3.into_bump_slice(),
|
||||||
);
|
);
|
||||||
|
@ -2426,12 +2609,10 @@ mod test_parse {
|
||||||
// )
|
// )
|
||||||
// "#
|
// "#
|
||||||
// ),
|
// ),
|
||||||
// Str(""),
|
// Str(PlainLine("")),
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence!
|
|
||||||
//
|
|
||||||
// TODO test for non-ASCII variables
|
// TODO test for non-ASCII variables
|
||||||
//
|
//
|
||||||
// TODO verify that when a string literal contains a newline before the
|
// TODO verify that when a string literal contains a newline before the
|
||||||
|
|
|
@ -55,6 +55,9 @@ pub enum Problem {
|
||||||
alias_name: Symbol,
|
alias_name: Symbol,
|
||||||
region: Region,
|
region: Region,
|
||||||
},
|
},
|
||||||
|
InvalidInterpolation(Region),
|
||||||
|
InvalidHexadecimal(Region),
|
||||||
|
InvalidUnicodeCodePoint(Region),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -125,6 +128,10 @@ pub enum RuntimeError {
|
||||||
|
|
||||||
NonExhaustivePattern,
|
NonExhaustivePattern,
|
||||||
|
|
||||||
|
InvalidInterpolation(Region),
|
||||||
|
InvalidHexadecimal(Region),
|
||||||
|
InvalidUnicodeCodePoint(Region),
|
||||||
|
|
||||||
/// When the author specifies a type annotation but no implementation
|
/// When the author specifies a type annotation but no implementation
|
||||||
NoImplementation,
|
NoImplementation,
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,7 @@ pub fn can_problem<'b>(
|
||||||
alloc.region(variable_region),
|
alloc.region(variable_region),
|
||||||
alloc.reflow("Roc does not allow unused type parameters!"),
|
alloc.reflow("Roc does not allow unused type parameters!"),
|
||||||
// TODO add link to this guide section
|
// TODO add link to this guide section
|
||||||
alloc.hint().append(alloc.reflow(
|
alloc.tip().append(alloc.reflow(
|
||||||
"If you want an unused type parameter (a so-called \"phantom type\"), \
|
"If you want an unused type parameter (a so-called \"phantom type\"), \
|
||||||
read the guide section on phantom data.",
|
read the guide section on phantom data.",
|
||||||
)),
|
)),
|
||||||
|
@ -262,6 +262,24 @@ pub fn can_problem<'b>(
|
||||||
alloc.reflow(" can occur in this position."),
|
alloc.reflow(" can occur in this position."),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
Problem::InvalidHexadecimal(region) => {
|
||||||
|
todo!(
|
||||||
|
"TODO report an invalid hexadecimal number in a \\u(...) code point at region {:?}",
|
||||||
|
region
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Problem::InvalidUnicodeCodePoint(region) => {
|
||||||
|
todo!(
|
||||||
|
"TODO report an invalid \\u(...) code point at region {:?}",
|
||||||
|
region
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Problem::InvalidInterpolation(region) => {
|
||||||
|
todo!(
|
||||||
|
"TODO report an invalid string interpolation at region {:?}",
|
||||||
|
region
|
||||||
|
);
|
||||||
|
}
|
||||||
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -309,7 +327,7 @@ fn pretty_runtime_error<'b>(
|
||||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||||
))
|
))
|
||||||
// TODO "are you trying to mutate a variable?
|
// TODO "are you trying to mutate a variable?
|
||||||
// TODO hint?
|
// TODO tip?
|
||||||
} else {
|
} else {
|
||||||
alloc.stack(vec![
|
alloc.stack(vec![
|
||||||
alloc
|
alloc
|
||||||
|
@ -334,7 +352,7 @@ fn pretty_runtime_error<'b>(
|
||||||
.map(|s| alloc.symbol_unqualified(s))
|
.map(|s| alloc.symbol_unqualified(s))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
),
|
),
|
||||||
// TODO hint?
|
// TODO tip?
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,12 +371,12 @@ fn pretty_runtime_error<'b>(
|
||||||
QualifiedIdentifier => " qualified ",
|
QualifiedIdentifier => " qualified ",
|
||||||
};
|
};
|
||||||
|
|
||||||
let hint = match problem {
|
let tip = match problem {
|
||||||
MalformedInt | MalformedFloat | MalformedBase(_) => alloc
|
MalformedInt | MalformedFloat | MalformedBase(_) => alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Learn more about number literals at TODO")),
|
.append(alloc.reflow("Learn more about number literals at TODO")),
|
||||||
Unknown => alloc.nil(),
|
Unknown => alloc.nil(),
|
||||||
QualifiedIdentifier => alloc.hint().append(
|
QualifiedIdentifier => alloc.tip().append(
|
||||||
alloc.reflow("In patterns, only private and global tags can be qualified"),
|
alloc.reflow("In patterns, only private and global tags can be qualified"),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
@ -370,7 +388,7 @@ fn pretty_runtime_error<'b>(
|
||||||
alloc.reflow("pattern is malformed:"),
|
alloc.reflow("pattern is malformed:"),
|
||||||
]),
|
]),
|
||||||
alloc.region(region),
|
alloc.region(region),
|
||||||
hint,
|
tip,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
RuntimeError::UnsupportedPattern(_) => {
|
RuntimeError::UnsupportedPattern(_) => {
|
||||||
|
@ -392,8 +410,8 @@ fn pretty_runtime_error<'b>(
|
||||||
RuntimeError::MalformedClosure(_) => todo!(""),
|
RuntimeError::MalformedClosure(_) => todo!(""),
|
||||||
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
|
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
|
||||||
| RuntimeError::InvalidFloat(sign @ FloatErrorKind::NegativeInfinity, region, _raw_str) => {
|
| RuntimeError::InvalidFloat(sign @ FloatErrorKind::NegativeInfinity, region, _raw_str) => {
|
||||||
let hint = alloc
|
let tip = alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Learn more about number literals at TODO"));
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
let big_or_small = if let FloatErrorKind::PositiveInfinity = sign {
|
let big_or_small = if let FloatErrorKind::PositiveInfinity = sign {
|
||||||
|
@ -415,12 +433,12 @@ fn pretty_runtime_error<'b>(
|
||||||
alloc.reflow(" and "),
|
alloc.reflow(" and "),
|
||||||
alloc.text(format!("{:e}", f64::MAX)),
|
alloc.text(format!("{:e}", f64::MAX)),
|
||||||
]),
|
]),
|
||||||
hint,
|
tip,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
RuntimeError::InvalidFloat(FloatErrorKind::Error, region, _raw_str) => {
|
RuntimeError::InvalidFloat(FloatErrorKind::Error, region, _raw_str) => {
|
||||||
let hint = alloc
|
let tip = alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Learn more about number literals at TODO"));
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
alloc.stack(vec![
|
alloc.stack(vec![
|
||||||
|
@ -431,7 +449,7 @@ fn pretty_runtime_error<'b>(
|
||||||
alloc.concat(vec![
|
alloc.concat(vec![
|
||||||
alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"),
|
alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"),
|
||||||
]),
|
]),
|
||||||
hint,
|
tip,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
RuntimeError::InvalidInt(error @ IntErrorKind::InvalidDigit, base, region, _raw_str)
|
RuntimeError::InvalidInt(error @ IntErrorKind::InvalidDigit, base, region, _raw_str)
|
||||||
|
@ -471,8 +489,8 @@ fn pretty_runtime_error<'b>(
|
||||||
Binary => "0 and 1",
|
Binary => "0 and 1",
|
||||||
};
|
};
|
||||||
|
|
||||||
let hint = alloc
|
let tip = alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Learn more about number literals at TODO"));
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
alloc.stack(vec![
|
alloc.stack(vec![
|
||||||
|
@ -490,7 +508,7 @@ fn pretty_runtime_error<'b>(
|
||||||
alloc.text(charset),
|
alloc.text(charset),
|
||||||
alloc.text("."),
|
alloc.text("."),
|
||||||
]),
|
]),
|
||||||
hint,
|
tip,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str)
|
RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str)
|
||||||
|
@ -501,8 +519,8 @@ fn pretty_runtime_error<'b>(
|
||||||
"big"
|
"big"
|
||||||
};
|
};
|
||||||
|
|
||||||
let hint = alloc
|
let tip = alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Learn more about number literals at TODO"));
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
alloc.stack(vec![
|
alloc.stack(vec![
|
||||||
|
@ -513,7 +531,7 @@ fn pretty_runtime_error<'b>(
|
||||||
]),
|
]),
|
||||||
alloc.region(region),
|
alloc.region(region),
|
||||||
alloc.reflow("Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."),
|
alloc.reflow("Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."),
|
||||||
hint,
|
tip,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
RuntimeError::InvalidRecordUpdate { region } => alloc.stack(vec![
|
RuntimeError::InvalidRecordUpdate { region } => alloc.stack(vec![
|
||||||
|
@ -524,6 +542,24 @@ fn pretty_runtime_error<'b>(
|
||||||
alloc.region(region),
|
alloc.region(region),
|
||||||
alloc.reflow("Only variables can be updated with record update syntax."),
|
alloc.reflow("Only variables can be updated with record update syntax."),
|
||||||
]),
|
]),
|
||||||
|
RuntimeError::InvalidHexadecimal(region) => {
|
||||||
|
todo!(
|
||||||
|
"TODO runtime error for an invalid hexadecimal number in a \\u(...) code point at region {:?}",
|
||||||
|
region
|
||||||
|
);
|
||||||
|
}
|
||||||
|
RuntimeError::InvalidUnicodeCodePoint(region) => {
|
||||||
|
todo!(
|
||||||
|
"TODO runtime error for an invalid \\u(...) code point at region {:?}",
|
||||||
|
region
|
||||||
|
);
|
||||||
|
}
|
||||||
|
RuntimeError::InvalidInterpolation(region) => {
|
||||||
|
todo!(
|
||||||
|
"TODO runtime error for an invalid string interpolation at region {:?}",
|
||||||
|
region
|
||||||
|
);
|
||||||
|
}
|
||||||
RuntimeError::NoImplementation => todo!("no implementation, unreachable"),
|
RuntimeError::NoImplementation => todo!("no implementation, unreachable"),
|
||||||
RuntimeError::NonExhaustivePattern => {
|
RuntimeError::NonExhaustivePattern => {
|
||||||
unreachable!("not currently reported (but can blow up at runtime)")
|
unreachable!("not currently reported (but can blow up at runtime)")
|
||||||
|
|
|
@ -498,6 +498,14 @@ fn to_expr_report<'b>(
|
||||||
Reason::ElemInList { index } => {
|
Reason::ElemInList { index } => {
|
||||||
let ith = index.ordinal();
|
let ith = index.ordinal();
|
||||||
|
|
||||||
|
// Don't say "the previous elements all have the type" if
|
||||||
|
// there was only 1 previous element!
|
||||||
|
let prev_elems_msg = if index.to_zero_based() == 1 {
|
||||||
|
"However, the 1st element has the type:"
|
||||||
|
} else {
|
||||||
|
"However, the preceding elements in the list all have the type:"
|
||||||
|
};
|
||||||
|
|
||||||
report_mismatch(
|
report_mismatch(
|
||||||
alloc,
|
alloc,
|
||||||
filename,
|
filename,
|
||||||
|
@ -506,13 +514,10 @@ fn to_expr_report<'b>(
|
||||||
expected_type,
|
expected_type,
|
||||||
region,
|
region,
|
||||||
Some(expr_region),
|
Some(expr_region),
|
||||||
alloc.string(format!(
|
alloc.reflow("This list contains elements with different types:"),
|
||||||
"The {} element of this list does not match all the previous elements:",
|
alloc.string(format!("Its {} element is", ith)),
|
||||||
ith
|
alloc.reflow(prev_elems_msg),
|
||||||
)),
|
Some(alloc.reflow("I need every element in a list to have the same type!")),
|
||||||
alloc.string(format!("The {} element is", ith)),
|
|
||||||
alloc.reflow("But all the previous elements in the list have type:"),
|
|
||||||
Some(alloc.reflow("I need all elements of a list to have the same type!")),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Reason::RecordUpdateValue(field) => report_mismatch(
|
Reason::RecordUpdateValue(field) => report_mismatch(
|
||||||
|
@ -776,7 +781,7 @@ fn to_expr_report<'b>(
|
||||||
unreachable!("I don't think these can be reached")
|
unreachable!("I don't think these can be reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
Reason::InterpolatedStringVar => {
|
Reason::StrInterpolation => {
|
||||||
unimplemented!("string interpolation is not implemented yet")
|
unimplemented!("string interpolation is not implemented yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,7 +824,7 @@ fn type_comparison<'b>(
|
||||||
lines.push(alloc.concat(context_hints));
|
lines.push(alloc.concat(context_hints));
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.extend(problems_to_hint(alloc, comparison.problems));
|
lines.extend(problems_to_tip(alloc, comparison.problems));
|
||||||
|
|
||||||
alloc.stack(lines)
|
alloc.stack(lines)
|
||||||
}
|
}
|
||||||
|
@ -835,7 +840,7 @@ fn lone_type<'b>(
|
||||||
|
|
||||||
let mut lines = vec![i_am_seeing, comparison.actual, further_details];
|
let mut lines = vec![i_am_seeing, comparison.actual, further_details];
|
||||||
|
|
||||||
lines.extend(problems_to_hint(alloc, comparison.problems));
|
lines.extend(problems_to_tip(alloc, comparison.problems));
|
||||||
|
|
||||||
alloc.stack(lines)
|
alloc.stack(lines)
|
||||||
}
|
}
|
||||||
|
@ -870,15 +875,46 @@ fn add_category<'b>(
|
||||||
Int => alloc.concat(vec![this_is, alloc.text(" an integer of type:")]),
|
Int => alloc.concat(vec![this_is, alloc.text(" an integer of type:")]),
|
||||||
Float => alloc.concat(vec![this_is, alloc.text(" a float of type:")]),
|
Float => alloc.concat(vec![this_is, alloc.text(" a float of type:")]),
|
||||||
Str => alloc.concat(vec![this_is, alloc.text(" a string of type:")]),
|
Str => alloc.concat(vec![this_is, alloc.text(" a string of type:")]),
|
||||||
|
StrInterpolation => alloc.concat(vec![
|
||||||
|
this_is,
|
||||||
|
alloc.text(" a value in a string interpolation, which was of type:"),
|
||||||
|
]),
|
||||||
|
|
||||||
Lambda => alloc.concat(vec![this_is, alloc.text(" an anonymous function of type:")]),
|
Lambda => alloc.concat(vec![this_is, alloc.text(" an anonymous function of type:")]),
|
||||||
|
|
||||||
TagApply(TagName::Global(name)) => alloc.concat(vec![
|
TagApply {
|
||||||
|
tag_name: TagName::Global(name),
|
||||||
|
args_count: 0,
|
||||||
|
} => alloc.concat(vec![
|
||||||
|
alloc.text("This "),
|
||||||
|
alloc.global_tag_name(name.to_owned()),
|
||||||
|
if name.as_str() == "True" || name.as_str() == "False" {
|
||||||
|
alloc.text(" boolean has the type:")
|
||||||
|
} else {
|
||||||
|
alloc.text(" global tag has the type:")
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
TagApply {
|
||||||
|
tag_name: TagName::Private(name),
|
||||||
|
args_count: 0,
|
||||||
|
} => alloc.concat(vec![
|
||||||
|
alloc.text("This "),
|
||||||
|
alloc.private_tag_name(*name),
|
||||||
|
alloc.text(" private tag has the type:"),
|
||||||
|
]),
|
||||||
|
|
||||||
|
TagApply {
|
||||||
|
tag_name: TagName::Global(name),
|
||||||
|
args_count: _,
|
||||||
|
} => alloc.concat(vec![
|
||||||
alloc.text("This "),
|
alloc.text("This "),
|
||||||
alloc.global_tag_name(name.to_owned()),
|
alloc.global_tag_name(name.to_owned()),
|
||||||
alloc.text(" global tag application has the type:"),
|
alloc.text(" global tag application has the type:"),
|
||||||
]),
|
]),
|
||||||
TagApply(TagName::Private(name)) => alloc.concat(vec![
|
TagApply {
|
||||||
|
tag_name: TagName::Private(name),
|
||||||
|
args_count: _,
|
||||||
|
} => alloc.concat(vec![
|
||||||
alloc.text("This "),
|
alloc.text("This "),
|
||||||
alloc.private_tag_name(*name),
|
alloc.private_tag_name(*name),
|
||||||
alloc.text(" private tag application has the type:"),
|
alloc.text(" private tag application has the type:"),
|
||||||
|
@ -1081,7 +1117,7 @@ fn pattern_type_comparision<'b>(
|
||||||
comparison.expected,
|
comparison.expected,
|
||||||
];
|
];
|
||||||
|
|
||||||
lines.extend(problems_to_hint(alloc, comparison.problems));
|
lines.extend(problems_to_tip(alloc, comparison.problems));
|
||||||
lines.extend(reason_hints);
|
lines.extend(reason_hints);
|
||||||
|
|
||||||
alloc.stack(lines)
|
alloc.stack(lines)
|
||||||
|
@ -1156,7 +1192,7 @@ pub enum Problem {
|
||||||
OptionalRequiredMismatch(Lowercase),
|
OptionalRequiredMismatch(Lowercase),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn problems_to_hint<'b>(
|
fn problems_to_tip<'b>(
|
||||||
alloc: &'b RocDocAllocator<'b>,
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
mut problems: Vec<Problem>,
|
mut problems: Vec<Problem>,
|
||||||
) -> Option<RocDocBuilder<'b>> {
|
) -> Option<RocDocBuilder<'b>> {
|
||||||
|
@ -2261,27 +2297,24 @@ fn type_problem_to_pretty<'b>(
|
||||||
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
||||||
let suggestion = alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
let suggestion = alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
||||||
|
|
||||||
let hint1 = alloc
|
let tip1 = alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Seems like a record field typo. Maybe "))
|
.append(alloc.reflow("Seems like a record field typo. Maybe "))
|
||||||
.append(found)
|
.append(found)
|
||||||
.append(alloc.reflow(" should be "))
|
.append(alloc.reflow(" should be "))
|
||||||
.append(suggestion)
|
.append(suggestion)
|
||||||
.append(alloc.text("?"));
|
.append(alloc.text("?"));
|
||||||
|
|
||||||
let hint2 = alloc.hint().append(alloc.reflow(ADD_ANNOTATIONS));
|
let tip2 = alloc.tip().append(alloc.reflow(ADD_ANNOTATIONS));
|
||||||
|
|
||||||
hint1
|
tip1.append(alloc.line()).append(alloc.line()).append(tip2)
|
||||||
.append(alloc.line())
|
|
||||||
.append(alloc.line())
|
|
||||||
.append(hint2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FieldsMissing(missing) => match missing.split_last() {
|
FieldsMissing(missing) => match missing.split_last() {
|
||||||
None => alloc.nil(),
|
None => alloc.nil(),
|
||||||
Some((f1, [])) => alloc
|
Some((f1, [])) => alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Looks like the "))
|
.append(alloc.reflow("Looks like the "))
|
||||||
.append(f1.as_str().to_owned())
|
.append(f1.as_str().to_owned())
|
||||||
.append(alloc.reflow(" field is missing.")),
|
.append(alloc.reflow(" field is missing.")),
|
||||||
|
@ -2289,7 +2322,7 @@ fn type_problem_to_pretty<'b>(
|
||||||
let separator = alloc.reflow(", ");
|
let separator = alloc.reflow(", ");
|
||||||
|
|
||||||
alloc
|
alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Looks like the "))
|
.append(alloc.reflow("Looks like the "))
|
||||||
.append(
|
.append(
|
||||||
alloc.intersperse(init.iter().map(|v| v.as_str().to_owned()), separator),
|
alloc.intersperse(init.iter().map(|v| v.as_str().to_owned()), separator),
|
||||||
|
@ -2302,9 +2335,9 @@ fn type_problem_to_pretty<'b>(
|
||||||
TagTypo(typo, possibilities_tn) => {
|
TagTypo(typo, possibilities_tn) => {
|
||||||
let possibilities = possibilities_tn
|
let possibilities = possibilities_tn
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tag_name| tag_name.into_string(alloc.interns, alloc.home))
|
.map(|tag_name| tag_name.as_string(alloc.interns, alloc.home))
|
||||||
.collect();
|
.collect();
|
||||||
let typo_str = format!("{}", typo.into_string(alloc.interns, alloc.home));
|
let typo_str = format!("{}", typo.as_string(alloc.interns, alloc.home));
|
||||||
let suggestions = suggest::sort(&typo_str, possibilities);
|
let suggestions = suggest::sort(&typo_str, possibilities);
|
||||||
|
|
||||||
match suggestions.get(0) {
|
match suggestions.get(0) {
|
||||||
|
@ -2315,20 +2348,17 @@ fn type_problem_to_pretty<'b>(
|
||||||
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
||||||
let suggestion = alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
let suggestion = alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
||||||
|
|
||||||
let hint1 = alloc
|
let tip1 = alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Seems like a tag typo. Maybe "))
|
.append(alloc.reflow("Seems like a tag typo. Maybe "))
|
||||||
.append(found)
|
.append(found)
|
||||||
.append(" should be ")
|
.append(" should be ")
|
||||||
.append(suggestion)
|
.append(suggestion)
|
||||||
.append(alloc.text("?"));
|
.append(alloc.text("?"));
|
||||||
|
|
||||||
let hint2 = alloc.hint().append(alloc.reflow(ADD_ANNOTATIONS));
|
let tip2 = alloc.tip().append(alloc.reflow(ADD_ANNOTATIONS));
|
||||||
|
|
||||||
hint1
|
tip1.append(alloc.line()).append(alloc.line()).append(tip2)
|
||||||
.append(alloc.line())
|
|
||||||
.append(alloc.line())
|
|
||||||
.append(hint2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2345,7 +2375,7 @@ fn type_problem_to_pretty<'b>(
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
alloc.hint().append(line)
|
alloc.tip().append(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
BadRigidVar(x, tipe) => {
|
BadRigidVar(x, tipe) => {
|
||||||
|
@ -2353,7 +2383,7 @@ fn type_problem_to_pretty<'b>(
|
||||||
|
|
||||||
let bad_rigid_var = |name: Lowercase, a_thing| {
|
let bad_rigid_var = |name: Lowercase, a_thing| {
|
||||||
alloc
|
alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("The type annotation uses the type variable "))
|
.append(alloc.reflow("The type annotation uses the type variable "))
|
||||||
.append(alloc.type_variable(name))
|
.append(alloc.type_variable(name))
|
||||||
.append(alloc.reflow(" to say that this definition can produce any type of value. But in the body I see that it will only produce "))
|
.append(alloc.reflow(" to say that this definition can produce any type of value. But in the body I see that it will only produce "))
|
||||||
|
@ -2365,7 +2395,7 @@ fn type_problem_to_pretty<'b>(
|
||||||
let line = r#" as separate type variables. Your code seems to be saying they are the same though. Maybe they should be the same your type annotation? Maybe your code uses them in a weird way?"#;
|
let line = r#" as separate type variables. Your code seems to be saying they are the same though. Maybe they should be the same your type annotation? Maybe your code uses them in a weird way?"#;
|
||||||
|
|
||||||
alloc
|
alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Your type annotation uses "))
|
.append(alloc.reflow("Your type annotation uses "))
|
||||||
.append(alloc.type_variable(a))
|
.append(alloc.type_variable(a))
|
||||||
.append(alloc.reflow(" and "))
|
.append(alloc.reflow(" and "))
|
||||||
|
@ -2392,12 +2422,12 @@ fn type_problem_to_pretty<'b>(
|
||||||
Boolean(_) => bad_rigid_var(x, alloc.reflow("a uniqueness attribute value")),
|
Boolean(_) => bad_rigid_var(x, alloc.reflow("a uniqueness attribute value")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IntFloat => alloc.hint().append(alloc.concat(vec![
|
IntFloat => alloc.tip().append(alloc.concat(vec![
|
||||||
alloc.reflow("Convert between "),
|
alloc.reflow("You can convert between "),
|
||||||
alloc.type_str("Int"),
|
alloc.type_str("Int"),
|
||||||
alloc.reflow(" and "),
|
alloc.reflow(" and "),
|
||||||
alloc.type_str("Float"),
|
alloc.type_str("Float"),
|
||||||
alloc.reflow(" with "),
|
alloc.reflow(" using functions like "),
|
||||||
alloc.symbol_qualified(Symbol::NUM_TO_FLOAT),
|
alloc.symbol_qualified(Symbol::NUM_TO_FLOAT),
|
||||||
alloc.reflow(" and "),
|
alloc.reflow(" and "),
|
||||||
alloc.symbol_qualified(Symbol::NUM_ROUND),
|
alloc.symbol_qualified(Symbol::NUM_ROUND),
|
||||||
|
@ -2407,26 +2437,26 @@ fn type_problem_to_pretty<'b>(
|
||||||
TagsMissing(missing) => match missing.split_last() {
|
TagsMissing(missing) => match missing.split_last() {
|
||||||
None => alloc.nil(),
|
None => alloc.nil(),
|
||||||
Some((f1, [])) => {
|
Some((f1, [])) => {
|
||||||
let hint1 = alloc
|
let tip1 = alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Looks like a closed tag union does not have the "))
|
.append(alloc.reflow("Looks like a closed tag union does not have the "))
|
||||||
.append(alloc.tag_name(f1.clone()))
|
.append(alloc.tag_name(f1.clone()))
|
||||||
.append(alloc.reflow(" tag."));
|
.append(alloc.reflow(" tag."));
|
||||||
|
|
||||||
let hint2 = alloc.hint().append(alloc.reflow(
|
let tip2 = alloc.tip().append(alloc.reflow(
|
||||||
"Closed tag unions can't grow, \
|
"Closed tag unions can't grow, \
|
||||||
because that might change the size in memory. \
|
because that might change the size in memory. \
|
||||||
Can you use an open tag union?",
|
Can you use an open tag union?",
|
||||||
));
|
));
|
||||||
|
|
||||||
alloc.stack(vec![hint1, hint2])
|
alloc.stack(vec![tip1, tip2])
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((last, init)) => {
|
Some((last, init)) => {
|
||||||
let separator = alloc.reflow(", ");
|
let separator = alloc.reflow(", ");
|
||||||
|
|
||||||
let hint1 = alloc
|
let tip1 = alloc
|
||||||
.hint()
|
.tip()
|
||||||
.append(alloc.reflow("Looks like a closed tag union does not have the "))
|
.append(alloc.reflow("Looks like a closed tag union does not have the "))
|
||||||
.append(
|
.append(
|
||||||
alloc
|
alloc
|
||||||
|
@ -2436,16 +2466,16 @@ fn type_problem_to_pretty<'b>(
|
||||||
.append(alloc.tag_name(last.clone()))
|
.append(alloc.tag_name(last.clone()))
|
||||||
.append(alloc.reflow(" tags."));
|
.append(alloc.reflow(" tags."));
|
||||||
|
|
||||||
let hint2 = alloc.hint().append(alloc.reflow(
|
let tip2 = alloc.tip().append(alloc.reflow(
|
||||||
"Closed tag unions can't grow, \
|
"Closed tag unions can't grow, \
|
||||||
because that might change the size in memory. \
|
because that might change the size in memory. \
|
||||||
Can you use an open tag union?",
|
Can you use an open tag union?",
|
||||||
));
|
));
|
||||||
|
|
||||||
alloc.stack(vec![hint1, hint2])
|
alloc.stack(vec![tip1, tip2])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
OptionalRequiredMismatch(field) => alloc.hint().append(alloc.concat(vec![
|
OptionalRequiredMismatch(field) => alloc.tip().append(alloc.concat(vec![
|
||||||
alloc.reflow("To extract the "),
|
alloc.reflow("To extract the "),
|
||||||
alloc.record_field(field),
|
alloc.record_field(field),
|
||||||
alloc.reflow(
|
alloc.reflow(
|
||||||
|
|
|
@ -20,7 +20,12 @@ const CYCLE_LN: &str = ["| ", "│ "][!IS_WINDOWS as usize];
|
||||||
const CYCLE_MID: &str = ["| |", "│ ↓"][!IS_WINDOWS as usize];
|
const CYCLE_MID: &str = ["| |", "│ ↓"][!IS_WINDOWS as usize];
|
||||||
const CYCLE_END: &str = ["+-<---+", "└─────┘"][!IS_WINDOWS as usize];
|
const CYCLE_END: &str = ["+-<---+", "└─────┘"][!IS_WINDOWS as usize];
|
||||||
|
|
||||||
const GUTTER_BAR: &str = " ┆";
|
const GUTTER_BAR: &str = "│";
|
||||||
|
const ERROR_UNDERLINE: &str = "^";
|
||||||
|
|
||||||
|
/// The number of monospace spaces the gutter bar takes up.
|
||||||
|
/// (This is not necessarily the same as GUTTER_BAR.len()!)
|
||||||
|
const GUTTER_BAR_WIDTH: usize = 1;
|
||||||
|
|
||||||
pub fn cycle<'b>(
|
pub fn cycle<'b>(
|
||||||
alloc: &'b RocDocAllocator<'b>,
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
|
@ -91,12 +96,15 @@ impl<'b> Report<'b> {
|
||||||
self.doc
|
self.doc
|
||||||
} else {
|
} else {
|
||||||
let header = format!(
|
let header = format!(
|
||||||
"-- {} {}",
|
"── {} {}",
|
||||||
self.title,
|
self.title,
|
||||||
"-".repeat(80 - (self.title.len() + 4))
|
"─".repeat(80 - (self.title.len() + 4))
|
||||||
);
|
);
|
||||||
|
|
||||||
alloc.stack(vec![alloc.text(header), self.doc])
|
alloc.stack(vec![
|
||||||
|
alloc.text(header).annotate(Annotation::Header),
|
||||||
|
self.doc,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +118,7 @@ pub struct Palette<'a> {
|
||||||
pub alias: &'a str,
|
pub alias: &'a str,
|
||||||
pub error: &'a str,
|
pub error: &'a str,
|
||||||
pub line_number: &'a str,
|
pub line_number: &'a str,
|
||||||
|
pub header: &'a str,
|
||||||
pub gutter_bar: &'a str,
|
pub gutter_bar: &'a str,
|
||||||
pub module_name: &'a str,
|
pub module_name: &'a str,
|
||||||
pub binop: &'a str,
|
pub binop: &'a str,
|
||||||
|
@ -126,7 +135,8 @@ pub const DEFAULT_PALETTE: Palette = Palette {
|
||||||
alias: YELLOW_CODE,
|
alias: YELLOW_CODE,
|
||||||
error: RED_CODE,
|
error: RED_CODE,
|
||||||
line_number: CYAN_CODE,
|
line_number: CYAN_CODE,
|
||||||
gutter_bar: MAGENTA_CODE,
|
header: CYAN_CODE,
|
||||||
|
gutter_bar: CYAN_CODE,
|
||||||
module_name: GREEN_CODE,
|
module_name: GREEN_CODE,
|
||||||
binop: GREEN_CODE,
|
binop: GREEN_CODE,
|
||||||
typo: YELLOW_CODE,
|
typo: YELLOW_CODE,
|
||||||
|
@ -317,10 +327,11 @@ impl<'a> RocDocAllocator<'a> {
|
||||||
content.annotate(Annotation::TypeBlock).indent(4)
|
content.annotate(Annotation::TypeBlock).indent(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hint(&'a self) -> DocBuilder<'a, Self, Annotation> {
|
pub fn tip(&'a self) -> DocBuilder<'a, Self, Annotation> {
|
||||||
self.text("Hint:")
|
self.text("Tip")
|
||||||
|
.annotate(Annotation::Tip)
|
||||||
|
.append(":")
|
||||||
.append(self.softline())
|
.append(self.softline())
|
||||||
.annotate(Annotation::Hint)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn region_all_the_things(
|
pub fn region_all_the_things(
|
||||||
|
@ -389,13 +400,16 @@ impl<'a> RocDocAllocator<'a> {
|
||||||
let overlapping = sub_region2.start_col < sub_region1.end_col;
|
let overlapping = sub_region2.start_col < sub_region1.end_col;
|
||||||
|
|
||||||
let highlight = if overlapping {
|
let highlight = if overlapping {
|
||||||
self.text("^".repeat((sub_region2.end_col - sub_region1.start_col) as usize))
|
self.text(
|
||||||
|
ERROR_UNDERLINE.repeat((sub_region2.end_col - sub_region1.start_col) as usize),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let highlight1 = "^".repeat((sub_region1.end_col - sub_region1.start_col) as usize);
|
let highlight1 =
|
||||||
|
ERROR_UNDERLINE.repeat((sub_region1.end_col - sub_region1.start_col) as usize);
|
||||||
let highlight2 = if sub_region1 == sub_region2 {
|
let highlight2 = if sub_region1 == sub_region2 {
|
||||||
"".repeat(0)
|
"".repeat(0)
|
||||||
} else {
|
} else {
|
||||||
"^".repeat((sub_region2.end_col - sub_region2.start_col) as usize)
|
ERROR_UNDERLINE.repeat((sub_region2.end_col - sub_region2.start_col) as usize)
|
||||||
};
|
};
|
||||||
let inbetween = " "
|
let inbetween = " "
|
||||||
.repeat((sub_region2.start_col.saturating_sub(sub_region1.end_col)) as usize);
|
.repeat((sub_region2.start_col.saturating_sub(sub_region1.end_col)) as usize);
|
||||||
|
@ -407,8 +421,9 @@ impl<'a> RocDocAllocator<'a> {
|
||||||
|
|
||||||
let highlight_line = self
|
let highlight_line = self
|
||||||
.line()
|
.line()
|
||||||
.append(self.text(" ".repeat(max_line_number_length)))
|
// Omit the gutter bar when we know there are no further
|
||||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
// line numbers to be printed after this!
|
||||||
|
.append(self.text(" ".repeat(max_line_number_length + GUTTER_BAR_WIDTH)))
|
||||||
.append(if sub_region1.is_empty() && sub_region2.is_empty() {
|
.append(if sub_region1.is_empty() && sub_region2.is_empty() {
|
||||||
self.nil()
|
self.nil()
|
||||||
} else {
|
} else {
|
||||||
|
@ -490,11 +505,13 @@ impl<'a> RocDocAllocator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if error_highlight_line {
|
if error_highlight_line {
|
||||||
let highlight_text = "^".repeat((sub_region.end_col - sub_region.start_col) as usize);
|
let highlight_text =
|
||||||
|
ERROR_UNDERLINE.repeat((sub_region.end_col - sub_region.start_col) as usize);
|
||||||
let highlight_line = self
|
let highlight_line = self
|
||||||
.line()
|
.line()
|
||||||
.append(self.text(" ".repeat(max_line_number_length)))
|
// Omit the gutter bar when we know there are no further
|
||||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
// line numbers to be printed after this!
|
||||||
|
.append(self.text(" ".repeat(max_line_number_length + GUTTER_BAR_WIDTH)))
|
||||||
.append(if highlight_text.is_empty() {
|
.append(if highlight_text.is_empty() {
|
||||||
self.nil()
|
self.nil()
|
||||||
} else {
|
} else {
|
||||||
|
@ -575,7 +592,8 @@ pub enum Annotation {
|
||||||
Module,
|
Module,
|
||||||
Typo,
|
Typo,
|
||||||
TypoSuggestion,
|
TypoSuggestion,
|
||||||
Hint,
|
Tip,
|
||||||
|
Header,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render with minimal formatting
|
/// Render with minimal formatting
|
||||||
|
@ -718,7 +736,7 @@ where
|
||||||
Emphasized => {
|
Emphasized => {
|
||||||
self.write_str(BOLD_CODE)?;
|
self.write_str(BOLD_CODE)?;
|
||||||
}
|
}
|
||||||
Url | Hint => {
|
Url | Tip => {
|
||||||
self.write_str(UNDERLINE_CODE)?;
|
self.write_str(UNDERLINE_CODE)?;
|
||||||
}
|
}
|
||||||
PlainText => {
|
PlainText => {
|
||||||
|
@ -745,6 +763,9 @@ where
|
||||||
Error => {
|
Error => {
|
||||||
self.write_str(self.palette.error)?;
|
self.write_str(self.palette.error)?;
|
||||||
}
|
}
|
||||||
|
Header => {
|
||||||
|
self.write_str(self.palette.header)?;
|
||||||
|
}
|
||||||
LineNumber => {
|
LineNumber => {
|
||||||
self.write_str(self.palette.line_number)?;
|
self.write_str(self.palette.line_number)?;
|
||||||
}
|
}
|
||||||
|
@ -773,8 +794,8 @@ where
|
||||||
None => {}
|
None => {}
|
||||||
Some(annotation) => match annotation {
|
Some(annotation) => match annotation {
|
||||||
Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar
|
Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar
|
||||||
| Typo | TypoSuggestion | Structure | CodeBlock | PlainText | LineNumber | Hint
|
| Typo | TypoSuggestion | Structure | CodeBlock | PlainText | LineNumber | Tip
|
||||||
| Module => {
|
| Module | Header => {
|
||||||
self.write_str(RESET_CODE)?;
|
self.write_str(RESET_CODE)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -93,7 +93,7 @@ pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
||||||
let state = State::new(input.as_bytes(), Attempting::Module);
|
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||||
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
||||||
let answer = parser.parse(&arena, state);
|
let answer = parser.parse(&arena, state);
|
||||||
|
|
||||||
|
|
|
@ -277,21 +277,53 @@ mod solve_expr {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// // INTERPOLATED STRING
|
// INTERPOLATED STRING
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn infer_interpolated_string() {
|
fn infer_interpolated_string() {
|
||||||
// infer_eq(
|
infer_eq(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// whatItIs = "great"
|
whatItIs = "great"
|
||||||
|
|
||||||
// "type inference is \(whatItIs)!"
|
"type inference is \(whatItIs)!"
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// "Str",
|
"Str",
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn infer_interpolated_var() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
whatItIs = "great"
|
||||||
|
|
||||||
|
str = "type inference is \(whatItIs)!"
|
||||||
|
|
||||||
|
whatItIs
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Str",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn infer_interpolated_field() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
rec = { whatItIs: "great" }
|
||||||
|
|
||||||
|
str = "type inference is \(rec.whatItIs)!"
|
||||||
|
|
||||||
|
rec
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ whatItIs : Str }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// LIST MISMATCH
|
// LIST MISMATCH
|
||||||
|
|
||||||
|
@ -2685,4 +2717,32 @@ mod solve_expr {
|
||||||
"{ x : Int, y ? Bool }* -> { x : Int, y : Bool }",
|
"{ x : Int, y ? Bool }* -> { x : Int, y : Bool }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_walk_right() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
List.walkRight
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"List a, (a, b -> b), b -> b",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_walk_right_example() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
empty : List Int
|
||||||
|
empty =
|
||||||
|
[]
|
||||||
|
|
||||||
|
List.walkRight empty (\a, b -> a + b) 0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Int",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -893,7 +893,7 @@ mod solve_uniq_expr {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn record() {
|
fn record() {
|
||||||
infer_eq("{ foo: 42 }", "Attr * { foo : (Attr * (Num (Attr * *))) }");
|
infer_eq("{ foo: 42 }", "Attr * { foo : Attr * (Num (Attr * *)) }");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -930,7 +930,7 @@ mod solve_uniq_expr {
|
||||||
{ user & year: "foo" }
|
{ user & year: "foo" }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { name : (Attr * Str), year : (Attr * Str) }",
|
"Attr * { name : Attr * Str, year : Attr * Str }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1017,7 +1017,7 @@ mod solve_uniq_expr {
|
||||||
.left
|
.left
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
"Attr * (Attr (* | b) { left : Attr b a }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1029,7 +1029,7 @@ mod solve_uniq_expr {
|
||||||
\rec -> rec.left
|
\rec -> rec.left
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
"Attr * (Attr (* | b) { left : Attr b a }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1041,7 +1041,7 @@ mod solve_uniq_expr {
|
||||||
\{ left, right } -> { left, right }
|
\{ left, right } -> { left, right }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b | d) { left : (Attr b a), right : (Attr d c) }* -> Attr * { left : (Attr b a), right : (Attr d c) })"
|
"Attr * (Attr (* | b | d) { left : Attr b a, right : Attr d c }* -> Attr * { left : Attr b a, right : Attr d c })"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1136,7 +1136,7 @@ mod solve_uniq_expr {
|
||||||
\{ left } -> left
|
\{ left } -> left
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
"Attr * (Attr (* | b) { left : Attr b a }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1148,7 +1148,7 @@ mod solve_uniq_expr {
|
||||||
\{ left } -> left
|
\{ left } -> left
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
"Attr * (Attr (* | b) { left : Attr b a }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1178,7 +1178,7 @@ mod solve_uniq_expr {
|
||||||
x
|
x
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
"Attr * (Attr (* | b) { left : Attr b a }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1193,7 +1193,7 @@ mod solve_uniq_expr {
|
||||||
x
|
x
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
"Attr * (Attr (* | b) { left : Attr b a }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1211,7 +1211,7 @@ mod solve_uniq_expr {
|
||||||
{ numIdentity, p, q }
|
{ numIdentity, p, q }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { numIdentity : (Attr Shared (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p)))), p : (Attr * (Num (Attr * p))), q : (Attr * Float) }"
|
"Attr * { numIdentity : Attr Shared (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p))), p : Attr * (Num (Attr * p)), q : Attr * Float }"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1227,7 +1227,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr c { x : (Attr Shared a) }b -> Attr c { x : (Attr Shared a) }b)",
|
"Attr * (Attr c { x : Attr Shared a }b -> Attr c { x : Attr Shared a }b)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1243,7 +1243,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr d { x : (Attr Shared a), y : (Attr Shared b) }c -> Attr d { x : (Attr Shared a), y : (Attr Shared b) }c)",
|
"Attr * (Attr d { x : Attr Shared a, y : Attr Shared b }c -> Attr d { x : Attr Shared a, y : Attr Shared b }c)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1262,7 +1262,7 @@ mod solve_uniq_expr {
|
||||||
p)
|
p)
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr d { x : (Attr Shared a), y : (Attr Shared b) }c -> Attr d { x : (Attr Shared a), y : (Attr Shared b) }c)"
|
"Attr * (Attr d { x : Attr Shared a, y : Attr Shared b }c -> Attr d { x : Attr Shared a, y : Attr Shared b }c)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1278,7 +1278,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr c { x : (Attr Shared a) }b -> Attr c { x : (Attr Shared a) }b)",
|
"Attr * (Attr c { x : Attr Shared a }b -> Attr c { x : Attr Shared a }b)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1290,7 +1290,7 @@ mod solve_uniq_expr {
|
||||||
\r -> { r & x: r.x, y: r.x }
|
\r -> { r & x: r.x, y: r.x }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr c { x : (Attr Shared a), y : (Attr Shared a) }b -> Attr c { x : (Attr Shared a), y : (Attr Shared a) }b)"
|
"Attr * (Attr c { x : Attr Shared a, y : Attr Shared a }b -> Attr c { x : Attr Shared a, y : Attr Shared a }b)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1306,7 +1306,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (f | d) { foo : (Attr d { bar : (Attr Shared a), baz : (Attr Shared b) }c) }e -> Attr (f | d) { foo : (Attr d { bar : (Attr Shared a), baz : (Attr Shared b) }c) }e)"
|
"Attr * (Attr (f | d) { foo : Attr d { bar : Attr Shared a, baz : Attr Shared b }c }e -> Attr (f | d) { foo : Attr d { bar : Attr Shared a, baz : Attr Shared b }c }e)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1324,7 +1324,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (e | c) { foo : (Attr c { bar : (Attr Shared a) }b) }d -> Attr (e | c) { foo : (Attr c { bar : (Attr Shared a) }b) }d)"
|
"Attr * (Attr (e | c) { foo : Attr c { bar : Attr Shared a }b }d -> Attr (e | c) { foo : Attr c { bar : Attr Shared a }b }d)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1343,7 +1343,7 @@ mod solve_uniq_expr {
|
||||||
s
|
s
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr c { x : (Attr Shared a), y : (Attr Shared a) }b -> Attr c { x : (Attr Shared a), y : (Attr Shared a) }b)",
|
"Attr * (Attr c { x : Attr Shared a, y : Attr Shared a }b -> Attr c { x : Attr Shared a, y : Attr Shared a }b)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1359,7 +1359,7 @@ mod solve_uniq_expr {
|
||||||
r.tic.tac.toe
|
r.tic.tac.toe
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b | c | d | e | f) { foo : (Attr (d | b | c) { bar : (Attr (c | b) { baz : (Attr b a) }*) }*), tic : (Attr (f | b | e) { tac : (Attr (e | b) { toe : (Attr b a) }*) }*) }* -> Attr b a)"
|
"Attr * (Attr (* | b | c | d | e | f) { foo : Attr (d | b | c) { bar : Attr (c | b) { baz : Attr b a }* }*, tic : Attr (f | b | e) { tac : Attr (e | b) { toe : Attr b a }* }* }* -> Attr b a)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1516,7 +1516,7 @@ mod solve_uniq_expr {
|
||||||
{ y: s.left }
|
{ y: s.left }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { y : (Attr Shared (Num (Attr * *))) }",
|
"Attr * { y : Attr Shared (Num (Attr * *)) }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1533,7 +1533,7 @@ mod solve_uniq_expr {
|
||||||
0 -> { x: s.left, y: r }
|
0 -> { x: s.left, y: r }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { x : (Attr Shared Str), y : (Attr Shared Str) }",
|
"Attr * { x : Attr Shared Str, y : Attr Shared Str }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1552,7 +1552,7 @@ mod solve_uniq_expr {
|
||||||
0 -> { x: v, y: r }
|
0 -> { x: v, y: r }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { x : (Attr Shared Str), y : (Attr Shared Str) }",
|
"Attr * { x : Attr Shared Str, y : Attr Shared Str }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1567,7 +1567,7 @@ mod solve_uniq_expr {
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
// it's fine that the inner fields are not shared: only shared extraction is possible
|
// it's fine that the inner fields are not shared: only shared extraction is possible
|
||||||
"Attr * { left : (Attr Shared { left : (Attr * (Num (Attr * *))), right : (Attr * (Num (Attr * *))) }), right : (Attr Shared { left : (Attr * (Num (Attr * *))), right : (Attr * (Num (Attr * *))) }) }",
|
"Attr * { left : Attr Shared { left : Attr * (Num (Attr * *)), right : Attr * (Num (Attr * *)) }, right : Attr Shared { left : Attr * (Num (Attr * *)), right : Attr * (Num (Attr * *)) } }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1895,7 +1895,7 @@ mod solve_uniq_expr {
|
||||||
head
|
head
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | c) (AssocList (Attr a k) (Attr b v)) -> Attr * (Maybe (Attr c { key : (Attr a k), value : (Attr b v) })))"
|
"Attr * (Attr (* | c) (AssocList (Attr a k) (Attr b v)) -> Attr * (Maybe (Attr c { key : Attr a k, value : Attr b v })))"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1919,7 +1919,7 @@ mod solve_uniq_expr {
|
||||||
head
|
head
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | c) (ConsList (Attr c { key : (Attr a k), value : (Attr b v) })) -> Attr * (Maybe (Attr c { key : (Attr a k), value : (Attr b v) })))"
|
"Attr * (Attr (* | c) (ConsList (Attr c { key : Attr a k, value : Attr b v })) -> Attr * (Maybe (Attr c { key : Attr a k, value : Attr b v })))"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2173,7 +2173,7 @@ mod solve_uniq_expr {
|
||||||
{ p, q }
|
{ p, q }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr * (List (Attr Shared a)) -> Attr * { p : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))), q : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))) })"
|
"Attr * (Attr * (List (Attr Shared a)) -> Attr * { p : Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*)), q : Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*)) })"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2220,11 +2220,11 @@ mod solve_uniq_expr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_foldr_sum() {
|
fn list_walkRight_sum() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
sum = \list -> List.foldr list Num.add 0
|
sum = \list -> List.walkRight list Num.add 0
|
||||||
|
|
||||||
sum
|
sum
|
||||||
"#
|
"#
|
||||||
|
@ -2291,9 +2291,9 @@ mod solve_uniq_expr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_foldr() {
|
fn list_walkRight() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
"List.foldr",
|
"List.walkRight",
|
||||||
"Attr * (Attr (* | b) (List (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)",
|
"Attr * (Attr (* | b) (List (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2313,11 +2313,11 @@ mod solve_uniq_expr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_foldr_reverse() {
|
fn list_walkRight_reverse() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
reverse = \list -> List.foldr list (\e, l -> List.append l e) []
|
reverse = \list -> List.walkRight list (\e, l -> List.append l e) []
|
||||||
|
|
||||||
reverse
|
reverse
|
||||||
"#
|
"#
|
||||||
|
@ -2607,7 +2607,7 @@ mod solve_uniq_expr {
|
||||||
f
|
f
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a) { x : (Attr a b) } -> Attr a b)",
|
"Attr * (Attr (* | a) { x : Attr a b } -> Attr a b)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2725,8 +2725,8 @@ mod solve_uniq_expr {
|
||||||
f
|
f
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
//"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))",
|
//"Attr * (Attr (* | a | b) { p : Attr a *, q : Attr b * }* -> Attr * (Num (Attr * *)))",
|
||||||
"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))"
|
"Attr * (Attr (* | a | b) { p : Attr a *, q : Attr b * }* -> Attr * (Num (Attr * *)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2956,7 +2956,7 @@ mod solve_uniq_expr {
|
||||||
findPath
|
findPath
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr * { costFunction : (Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float)), end : (Attr Shared position), moveFunction : (Attr Shared (Attr Shared position -> Attr * (Set (Attr * position)))), start : (Attr Shared position) } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))"
|
"Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))"
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3024,7 +3024,7 @@ mod solve_uniq_expr {
|
||||||
negatePoint { x: 1, y: 2 }
|
negatePoint { x: 1, y: 2 }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { x : (Attr * Int), y : (Attr * Int), z : (Attr * (Num (Attr * c))) }",
|
"Attr * { x : Attr * Int, y : Attr * Int, z : Attr * (Num (Attr * c)) }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3041,7 +3041,7 @@ mod solve_uniq_expr {
|
||||||
{ a, b }
|
{ a, b }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { a : (Attr * { x : (Attr * Int), y : (Attr * Int), z : (Attr * (Num (Attr * c))) }), b : (Attr * { blah : (Attr * Str), x : (Attr * Int), y : (Attr * Int), z : (Attr * (Num (Attr * c))) }) }"
|
"Attr * { a : Attr * { x : Attr * Int, y : Attr * Int, z : Attr * (Num (Attr * c)) }, b : Attr * { blah : Attr * Str, x : Attr * Int, y : Attr * Int, z : Attr * (Num (Attr * c)) } }"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3055,7 +3055,7 @@ mod solve_uniq_expr {
|
||||||
negatePoint { x: 1, y: 2.1, z: 0x3 }
|
negatePoint { x: 1, y: 2.1, z: 0x3 }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * Int) }",
|
"Attr * { x : Attr * (Num (Attr * a)), y : Attr * Float, z : Attr * Int }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3072,7 +3072,7 @@ mod solve_uniq_expr {
|
||||||
{ a, b }
|
{ a, b }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { a : (Attr * { x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * c) }), b : (Attr * { blah : (Attr * Str), x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * c) }) }"
|
"Attr * { a : Attr * { x : Attr * (Num (Attr * a)), y : Attr * Float, z : Attr * c }, b : Attr * { blah : Attr * Str, x : Attr * (Num (Attr * a)), y : Attr * Float, z : Attr * c } }"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3084,7 +3084,7 @@ mod solve_uniq_expr {
|
||||||
\{ x, y ? 0 } -> x + y
|
\{ x, y ? 0 } -> x + y
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b | c) { x : (Attr b (Num (Attr b a))), y ? (Attr c (Num (Attr c a))) }* -> Attr d (Num (Attr d a)))"
|
"Attr * (Attr (* | b | c) { x : Attr b (Num (Attr b a)), y ? Attr c (Num (Attr c a)) }* -> Attr d (Num (Attr d a)))"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3112,7 +3112,35 @@ mod solve_uniq_expr {
|
||||||
{ x, y ? 0 } -> x + y
|
{ x, y ? 0 } -> x + y
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | b | c) { x : (Attr b (Num (Attr b a))), y ? (Attr c (Num (Attr c a))) }* -> Attr d (Num (Attr d a)))"
|
"Attr * (Attr (* | b | c) { x : Attr b (Num (Attr b a)), y ? Attr c (Num (Attr c a)) }* -> Attr d (Num (Attr d a)))"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_walk_right() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
List.walkRight
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr * (Attr (* | b) (List (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_walk_right_example() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
empty : List Int
|
||||||
|
empty =
|
||||||
|
[]
|
||||||
|
|
||||||
|
List.walkRight empty (\a, b -> a + b) 0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr a Int",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
27
compiler/str/Cargo.toml
Normal file
27
compiler/str/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
[package]
|
||||||
|
name = "roc_str"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
roc_collections = { path = "../collections" }
|
||||||
|
roc_region = { path = "../region" }
|
||||||
|
roc_module = { path = "../module" }
|
||||||
|
roc_types = { path = "../types" }
|
||||||
|
roc_can = { path = "../can" }
|
||||||
|
roc_unify = { path = "../unify" }
|
||||||
|
roc_problem = { path = "../problem" }
|
||||||
|
bumpalo = { version = "3.2", features = ["collections"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
roc_constrain = { path = "../constrain" }
|
||||||
|
roc_builtins = { path = "../builtins" }
|
||||||
|
roc_parse = { path = "../parse" }
|
||||||
|
roc_solve = { path = "../solve" }
|
||||||
|
pretty_assertions = "0.5.1"
|
||||||
|
maplit = "1.0.1"
|
||||||
|
indoc = "0.3.3"
|
||||||
|
quickcheck = "0.8"
|
||||||
|
quickcheck_macros = "0.8"
|
209
compiler/str/README.md
Normal file
209
compiler/str/README.md
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
# `Str`
|
||||||
|
|
||||||
|
This is the in-memory representation for `Str`. To explain how `Str` is laid out in memory, it's helpful to start with how `List` is laid out.
|
||||||
|
|
||||||
|
## Empty list
|
||||||
|
|
||||||
|
An empty `List Str` is essentially this Rust type with all 0s in memory:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct List {
|
||||||
|
pointer: *Str, // pointers are the same size as `usize`
|
||||||
|
length: usize
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On a 64-bit system, this `struct` would take up 16B in memory. On a 32-bit sysem, it would take up 8B.
|
||||||
|
|
||||||
|
Here's what the fields mean:
|
||||||
|
|
||||||
|
* `pointer` is the memory address of the heap-allocated memory containing the `Bool` elements. For an empty list, the pointer is null (that is, 0).
|
||||||
|
* `length` is the number of `Bool` elements in the list. For an empty list, this is also 0.
|
||||||
|
|
||||||
|
## Nonempty list
|
||||||
|
|
||||||
|
Now let's say we define a `List Str` with two elements in it, like so: `[ "foo", "bar" ]`.
|
||||||
|
|
||||||
|
First we'd have the `struct` above, with both `length` and `capacity` set to 2. Then, we'd have some memory allocated on the heap, and `pointer` would store that memory's address.
|
||||||
|
|
||||||
|
Here's how that heap memory would be laid out on a 64-bit system. It's a total of 48 bytes.
|
||||||
|
|
||||||
|
```
|
||||||
|
|------16B------|------16B------|---8B---|---8B---|
|
||||||
|
string #1 string #2 refcount unused
|
||||||
|
```
|
||||||
|
|
||||||
|
Just like how `List` is a `struct` that takes up `2 * usize` bytes in memory, `Str` takes up the same amount of memory - namely, 16B on a 64-bit system. That's why each of the two strings take up 16B of this heap-allocated memory. (Those structs may also point to other heap memory, but they could also be empty strings! Either way we just store the structs in the list, which take up 16B.)
|
||||||
|
|
||||||
|
We'll get to what the refcount is for shortly, but first let's talk about the memory layout. The refcount is a `usize` integer, so 8B on our 64-bit system. Why is there 8B of unused memory after it?
|
||||||
|
|
||||||
|
This is because of memory alignment. Whenever a system loads some memory from a memory address, it's much more efficient if the address is a multiple of the number of bytes it wants to get. So if we want to load a 16B string struct, we want its address to be a multiple of 16.
|
||||||
|
|
||||||
|
When we're allocating memory on the heap, the way we specify what alignment we want is to say how big each element is, and how many of them we want. In this case, we say we want 16B elements, and we want 3 of them. Then we use the first 16B slot to store the 8B refcount, and the 8B after it are unused.
|
||||||
|
|
||||||
|
This is memory-inefficient, but it's the price we pay for having all the 16B strings stored in addresses that are multiples of 16. It'd be worse for performance if we tried to pack everything tightly, so we accept the memory inefficiency as a cost of achieving better overall execution speed.
|
||||||
|
|
||||||
|
> Note: if we happened to have 8B elements instead of 16B elements, the alignment would be 8 anyway and we'd have no unused memory.
|
||||||
|
|
||||||
|
## Reference counting
|
||||||
|
|
||||||
|
Let's go back to the refcount - short for "reference count."
|
||||||
|
|
||||||
|
The refcount is a `usize` integer which counts how many times this `List` has been shared. For example, if we named this list `myList` and then wrote `[ myList, myList, myList ]` then we'd increment that refcount 3 times because `myList` is now being shared three more times.
|
||||||
|
|
||||||
|
If we were to later call `List.pop` on that list, and the result was an in-place mutation that removed one of the `myList` entries, we'd decrement the refcount. If we did that again and again until the refcount got all the way down to 0, meaning nothing is using it anymore, then we'd deallocate these 48B of heap memory because nobody is using them anymore.
|
||||||
|
|
||||||
|
In some cases, the compiler can detect that no reference counting is necessary. In that scenario, it doesn't bother allocating extra space for the refcount; instead, it inserts an instruction to allocate the memory at the appropriate place, another to free it later, and that's it.
|
||||||
|
|
||||||
|
## Pointing to the first element
|
||||||
|
|
||||||
|
The fact that the reference count may or may not be present could creat a tricky situation for some `List` operations.
|
||||||
|
|
||||||
|
For example, should `List.get 0` return the first 16B of the heap-allocated bytes, or the second 16B? If there's a reference count in the first 16B, it should return the second 16B. If there's no refcount, it should return the first 16B.
|
||||||
|
|
||||||
|
To solve this, the pointer in the List struct *always* points to the first element in the list. That means to access the reference count, it does negative pointer arithmetic to get the address at 16B *preceding* the memory address it has stored in its pointer field.
|
||||||
|
|
||||||
|
## Growing lists
|
||||||
|
|
||||||
|
If uniqueness typing tells us that a list is Unique, we know two things about it:
|
||||||
|
|
||||||
|
1. It doesn't need a refcount, because nothing else ever references it.
|
||||||
|
2. It can be mutated in-place.
|
||||||
|
|
||||||
|
One of the in-place mutations that can happen to a list is that its length can increase. For example, if I call `List.append list1 list2`, and `list1` is unique, then we'll attempt to append `list2`'s contents in-place into `list1`.
|
||||||
|
|
||||||
|
Calling `List.append` on a Shared list results in allocating a new chunk of heap memory large enough to hold both lists (with a fresh refcount, since nothing is referencing the new memory yet), then copying the contents of both lists into the new memory, and finally decrementing the refcount of the old memory.
|
||||||
|
|
||||||
|
Calling `List.append` on a Unique list can potentially be done in-place instead.
|
||||||
|
|
||||||
|
First, `List.append` repurposes the `usize` slot normally used to store refcount, and stores a `capacity` counter in there instead of a refcount. (After all, unique lists don't need to be refcounted.) A list's capacity refers to how many elements the list *can* hold given the memory it has allocated to it, which is always guaranteed to be at least as many as its length.
|
||||||
|
|
||||||
|
When calling `List.append list1 list2` on a unique `list1`, first we'll check to see if `list1.capacity <= list1.length + list2.length`. If it is, then we can copy in the new values without needing to allocate more memory for `list1`.
|
||||||
|
|
||||||
|
If there is not enough capacity to fit both lists, then we can try to call [`realloc`](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/realloc?view=vs-2019) to hopefully extend the size of our allocated memory. If `realloc` succeeds (meaning there happened to be enough free memory right after our current allocation), then we update `capacity` to reflect the new amount of space, and move on.
|
||||||
|
|
||||||
|
> **Note:** The reason we store capacity right after the last element in the list is becasue of how memory cache lines work. Whenever we need to access `capacity`, it's because we're about to increase the length of the list, which means that we will most certainly be writing to the memory location right after its last element. That in turn means that we'll need to have that memory location in cache, which in turn means that looking up the `capacity` there is guaranteed not to cause a cache miss. (It's possible that writing the new capacity value to a later address could cause a cache miss, but this strategy minimizes the chance of that happening.) An alternate design would be where we store the capacity right before the first element in the list. In that design we wouldn't have to re-write the capacity value at the end of the list every time we grew it, but we'd be much more likely to incur more cache misses that way - because we're working at the end of the list, not at the beginning. Cache misses are many times more expensive than an extra write to a memory address that's in cache already, not to mention the potential extra load instruction to add the length to the memory address of the first element (instead of subtracting 1 from that address), so we optimize for minimizing the highly expensive cache misses by always paying a tiny additional cost when increasing the length of the list, as well as a potential even tinier cost (zero, if the length already happens to be in a register) when looking up its capacity or refcount.
|
||||||
|
|
||||||
|
If `realloc` fails, then we have to fall back on the same "allocate new memory and copy everything" strategy that we do with shared lists.
|
||||||
|
|
||||||
|
When you have a way to anticipate that a list will want to grow incrementally to a certain size, you can avoid such extra allocations by using `List.reserve` to guarantee more capacity up front. (`List.reserve` works like Rust's [`Vec::reserve`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.reserve).)
|
||||||
|
|
||||||
|
> **Note:** Calling `List.reserve 0 myList` will have no effect on a Unique list, but on a Shared list it will clone `myList` and return a Unique one. If you want to do a bunch of in-place mutations on a list, but it's currently Shared, calling `List.reserve 0` on it to get a Unique clone could actually be an effective performance optimization!
|
||||||
|
|
||||||
|
## Capacity and alignment
|
||||||
|
|
||||||
|
Some lists may end up beginning with excess capacity due to memory alignment requirements. Since the refcount is `usize`, all lists need a minimum of that alignment. For example, on a 64-bit system, a `List Bool` has an alignment of 8B even though bools can fit in 1B.
|
||||||
|
|
||||||
|
This means the list `[ True, True, False ]` would have a memory layout like this):
|
||||||
|
|
||||||
|
```
|
||||||
|
|--------------8B--------------|--1B--|--1B--|--1B--|-----5B-----|
|
||||||
|
either refcount or capacity bool1 bool2 bool3 unused
|
||||||
|
```
|
||||||
|
|
||||||
|
As such, if this list is Unique, it would start out with a length of 3 and a capacity of 8.
|
||||||
|
|
||||||
|
Since each bool value is a byte, it's okay for them to be packed side-by-side even though the overall alignment of the list elements is 8. This is fine because each of their individual memory addresses will end up being a multiple of their size in bytes.
|
||||||
|
|
||||||
|
Note that unlike in the `List Str` example before, there wouldn't be any unused memory between the refcount (or capacity, depending on whether the list was shared or unique) and the first element in the list. That will always be the case when the size of the refcount is no bigger than the alignment of the list's elements.
|
||||||
|
|
||||||
|
## Distinguishing bewteen refcount and capacity in the host
|
||||||
|
|
||||||
|
If I'm a platform author, and I receive a `List` from the application, it's important that I be able to tell whether I'm dealing with a refcount or a capacity. (The uniqueness type information will have been erased by this time, because some applications will return a Unique list while others return a Shared list, so I need to be able to tell using runtime information only which is which.) This way, I can know to either increment the refcount, or to feel free to mutate it in-place using the capacity value.
|
||||||
|
|
||||||
|
We use a very simple system to distinguish the two: if the high bit in that `usize` value is 0, then it's capacity. If it's 1, then it's a refcount.
|
||||||
|
|
||||||
|
This has a couple of implications:
|
||||||
|
|
||||||
|
* `capacity` actually has a maximum of `isize::MAX`, not `usize::MAX` - because if its high bit flips to 1, then now suddenly it's considered a refcount by the host. As it happens, capacity's maximum value is naturally `isize::MAX` anyway, so there's no downside here.
|
||||||
|
* `refcount` actually begins at `isize::MIN` and increments towards 0, rather than beginning at 0 and incrementing towards a larger number. When a decrement instruction is executed and the refcount is `isize::MIN`, it gets freed instead. Since all refcounts do is count up and down, and they only ever compare the refcount to a fixed constant, there's no real performance cost to comparing to `isize::MIN` instead of to 0. So this has no significant downside either.
|
||||||
|
|
||||||
|
Using this representation, hosts can trivially distinguish any list they receive as being either refcounted or having a capacity value, without any runtime cost in either the refcounted case or the capacity case.
|
||||||
|
|
||||||
|
### Saturated reference count
|
||||||
|
|
||||||
|
What happens if the reference count overflows? As in, we try to reference the same list more than `isize` times?
|
||||||
|
|
||||||
|
In this situation, the reference count becomes unreliable. Suppose we try to increment it 3 more times after it's already been incremented `isize` times, and since we can't store any further numbers without flipping the high bit from 1 to 0 (meaning it will become a capacity value instead of a refcount), we leave it at -1. If we later decrement it `isize` times, we'll be down to `isize::MIN` and will free the memory, even though 3 things are still referencing that memory!
|
||||||
|
|
||||||
|
This would be a total disaster, so what we do instead is that we decide to leak the memory. Once the reference count hits -1, we neither increment nor decrement it ever again, which in turn means we will never free it. So -1 is a special reference count meaning "this memory has become unreclaimable, and must never be freed."
|
||||||
|
|
||||||
|
This has the downside of being potentially wasteful of the program's memory, but it's less detrimental to user experience than a crash, and it doesn't impact correctness at all.
|
||||||
|
|
||||||
|
## Summary of Lists
|
||||||
|
|
||||||
|
Lists are a `2 * usize` struct which contains a length and a pointer.
|
||||||
|
|
||||||
|
That pointer is a memory address (null in the case of an empty list) which points to the first element in a sequential array of memory.
|
||||||
|
|
||||||
|
If that pointer is shared in multiple places, then there will be a `usize` reference count stored right before the first element of the list. There may be unused memory after the refcount if `usize` is smaller than the alignment of one of the list's elements.
|
||||||
|
|
||||||
|
Refcounts get incremented each time a list gets shared somewhere, and decremented each time that shared value is no longer referenced by anything else (for example, by going out of scope). Once there are no more references, the list's heap memory can be safely freed. If a reference count gets all the way up to `usize`, then it will never be decremented again and the memory will never be freed.
|
||||||
|
|
||||||
|
Whenever a list grows, it will grow in-place if it's Unique and there is enough capacity. (Capacity is stored where a refcount would be in a Shared list.) If there isn't enough capacity - even after trying `realloc` - or if the list is Shared, then instead new heap memory will be allocated, all the necessary elements will get copied into it, and the original list's refcount will be decremented.
|
||||||
|
|
||||||
|
## Strings
|
||||||
|
|
||||||
|
Strings have several things in common with lists:
|
||||||
|
|
||||||
|
* They are a `2 * usize` struct, sometimes with a non-null pointer to some heap memory
|
||||||
|
* They have a length and a capacity, and they can grow in basically the same way
|
||||||
|
* They are reference counted in basically the same way
|
||||||
|
|
||||||
|
However, they also have two things going on that lists do not:
|
||||||
|
|
||||||
|
* The Small String Optimization
|
||||||
|
* Literals stored in read-only memory
|
||||||
|
|
||||||
|
## The Small String Optimization
|
||||||
|
|
||||||
|
In practice, a lot of strings are pretty small. For example, the string `"Richard Feldman"` can be stored in 15 UTF-8 bytes. If we stored that string the same way we store a list, then on a 64-bit system we'd need a 16B struct, which would include a pointer to 24B of heap memory (including the refcount/capacity and one unused byte for alignment).
|
||||||
|
|
||||||
|
That's a total of 48B to store 15B of data, when we could have fit the whole string into the original 16B we needed for the struct, with one byte left over.
|
||||||
|
|
||||||
|
The Small String Optimization is where we store strings directly in the struct, assuming they can fit in there. We reserve one of those bytes to indicate whether this is a Small String or a larger one that actually uses a pointer.
|
||||||
|
|
||||||
|
## String Memory Layout
|
||||||
|
|
||||||
|
How do we tell small strings apart from nonsmall strings?
|
||||||
|
|
||||||
|
We make use of the fact that lengths (for both strings *and* lists) are `usize` values which have a maximum value of `isize::MAX` rather than `usize::MAX`. This is because `List.get` compiles down to an array access operation, and LLVM uses `isize` indices for those because they do signed arithmetic on the pointer in case the caller wants to add a negative number to the address. (We don't want to, as it happens, but that's what the low-level API supports, so we are bound by its limitations.)
|
||||||
|
|
||||||
|
Since the string's length is a `usize` value with a maximum of `isize::MAX`, we can be sure that its most significant bit will always be 0, not 1. (If it were a 1, that would be a negative `isize`!) We can use this fact to use that spare bit as a flag indicating whether the string is small: if that bit is a 1, it's a small string; otherwise, it's a nonsmall string.
|
||||||
|
|
||||||
|
This makes calculating the length of the string a multi-step process:
|
||||||
|
|
||||||
|
1. Get the length field out of the struct.
|
||||||
|
2. Look at its highest bit. If that bit is 0, return the length as-is.
|
||||||
|
3. If the bit is 1, then this is a small string, and its length is packed into the highest byte of the `usize` length field we're currently examining. Take that byte and bit shift it by 1 (to drop the `1` flag we used to indicate this is a small string), cast the resulting byte to `usize`, and that's our length. (Actually we bit shift by 4, not 1, because we only need 4 bits to store a length of 0-16, and the leftover 3 bits can potentially be useful in the future.)
|
||||||
|
|
||||||
|
Using this strategy with a [conditional move instruction](https://stackoverflow.com/questions/14131096/why-is-a-conditional-move-not-vulnerable-for-branch-prediction-failure), we can always get the length of a `Str` in 2-3 cheap instructions on a single `usize` value, without any chance of a branch misprediction.
|
||||||
|
|
||||||
|
Thus, the layout of a small string on a 64-bit big-endian architecture would be:
|
||||||
|
|
||||||
|
```
|
||||||
|
|-----------usize length field----------|-----------usize pointer field---------|
|
||||||
|
|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|
|
||||||
|
len 'R' 'i' 'c' 'h' 'a' 'r' 'd' ' ' 'F' 'e' 'l' 'd' 'm' 'a' 'n'
|
||||||
|
```
|
||||||
|
|
||||||
|
The `len` value here would be the number 15, plus a 1 (to flag that this is a small string) that would always get bit-shifted away. The capacity of a small Unique string is always equal to `2 * usize`, because that's how much you can fit without promoting to a nonsmall string.
|
||||||
|
|
||||||
|
## Endianness
|
||||||
|
|
||||||
|
The preceding memory layout example works on a big-endian architecture, but most CPUs are little-endian. That means the high bit where we want to store the flag (the 0 or 1
|
||||||
|
that would make an `isize` either negative or positive) will actually be the `usize`'s last byte rather than its first byte.
|
||||||
|
|
||||||
|
That means we'd have to move swap the order of the struct's length and pointer fields. Here's how the string `"Roc string"` would be stored on a little-endian system:
|
||||||
|
|
||||||
|
```
|
||||||
|
|-----------usize pointer field---------|-----------usize length field----------|
|
||||||
|
|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|
|
||||||
|
'R' 'o' 'c' ' ' 's' 't' 'r' 'i' 'n' 'g' 0 0 0 0 0 len
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, `len` would have the same format as before (including the extra 1 in the same position, which we'd bit shift away) except that it'd store a length of 10 instead of 15.
|
||||||
|
|
||||||
|
Notice that the leftover bytes are stored as zeroes. This is handy because it means we can convert small Roc strings into C strings (which are 0-terminated) for free as long as they have at least one unused byte. Also notice that `usize pointer field` and `usize length field` have been swapped compared to the preceding example!
|
||||||
|
|
||||||
|
## Storing string literals in read-only memory
|
66
compiler/str/src/lib.rs
Normal file
66
compiler/str/src/lib.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#![crate_type = "lib"]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
/// Str does Roc-style collection reference counting, which means the collection
|
||||||
|
/// may or may not be safe to mutate in-place, may or may not be reference counted,
|
||||||
|
/// and may or may not need to be freed when no longer in use. Whether each of
|
||||||
|
/// these is true for a given collection can be determined by inspecting
|
||||||
|
/// that collection at runtime.
|
||||||
|
///
|
||||||
|
/// Details:
|
||||||
|
///
|
||||||
|
/// 1. If the collection is empty, it does not allocate on the heap.
|
||||||
|
/// 2. If it is nonempty, its pointer points to the first element of a "backing array" on the heap.
|
||||||
|
/// 3. There is an extra `isize` right before that backing array (still on the heap) which stores the
|
||||||
|
/// "flexible reference count" ("flexcount" for short).
|
||||||
|
/// 4. The flexcount can refer to one of three things, depending on whether it is positive,
|
||||||
|
/// negative, or zero.
|
||||||
|
/// 5. If the flexcount is positive, then it's a capacity. The capacity refers to the number of
|
||||||
|
/// collection elements in the backing array. This collection can be mutated in-place, until it
|
||||||
|
/// runs out of capacity. At that point, it will need a new backing array. Once it goes out of
|
||||||
|
/// scope, the backing array should be freed by the system allocator - but free() must be passed
|
||||||
|
/// a pointer to the flexcount slot, not to element 0 (because the flexcount slot is where the
|
||||||
|
/// original allocation began). Capacity will always be at least 1, because otherwise we would
|
||||||
|
/// not have allocated on the heap in the first place.
|
||||||
|
/// 6. If the flexcount is 0, then this collection resides in readonly memory. That means it cannot
|
||||||
|
/// be mutated in-place (and attempting to do so will segfault), and it must not be attempted to
|
||||||
|
/// be freed. It exists in memory forever!
|
||||||
|
/// 7. If the flexcount is negative, then it is a reference count. Treat the collection as immutable, just like
|
||||||
|
/// if the flexcount were 0, except free it when there are no more references to it. Instead of the reference count
|
||||||
|
/// starting at 0 or 1 and incrementing when new references are added, this refcount starts with all bits being 1 (so, isize::MIN) and
|
||||||
|
/// increments towards 0 when new references are added. When a reference is removed, if all bits are 1, then it should be freed. If so many new references are added that it gets incremented all the way from isize::MAX to 0, then, as is best practice when running out of reference counts, it will leak. (Leaking memory is typically less bad than crashing, and this should essentially never happen.) This happens automatically because when the flexcount is 0, it's assumed that the collection is in readonly memory and should not be freed - which is nice because it means there is no extra conditional required to implement this edge case.
|
||||||
|
/// 8. If a collection has a refcount of isize::MIN (meaning nothing else references it), it may or may not be safe to convert it to a capacity,
|
||||||
|
/// depending on whether it contains other refcounted collections. For example, a Str
|
||||||
|
/// is a collection of all bytes, so if it has a refcount of all 1 bits, it can be safely
|
||||||
|
/// converted to a capacity (the initial capacity should be equal to the collection's length),
|
||||||
|
/// after which point it can be safely mutated in-place. However, a refcounted List of Lists with a refcount of isize::MIN will not be safe to convert to a capacity, unless the inner Lists also happen to have refcounts of isize::MIN. This is because mutate-in-place operations like removing an element from a list do not check for refcounts in the elements they remove, which means removing an element from the newly mutable-in-place list would cause memory leaks in its refcounted contents. (They'd have been removed, but their reference counts would not have been adjusted accordingly.)
|
||||||
|
///
|
||||||
|
/// Note that because of these runtime conditionals, modifying and freeing Roc collections are both
|
||||||
|
/// cheaper operations in generated Roc code than in host code. Since the Roc compiler knows
|
||||||
|
/// statically whether a collection is refcounted, unique, or readonly, it does not bother with
|
||||||
|
/// these checks at runtime. A host, however, cannot have that information statically (since it may be different
|
||||||
|
/// for different applications), and so must check at runtime instead.
|
||||||
|
struct Str {
|
||||||
|
bytes: [16, u8];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn empty_() -> Str {
|
||||||
|
Str {
|
||||||
|
bytes : [0; 16]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn len_(string: Str) -> usize {
|
||||||
|
let disc = discriminant(str);
|
||||||
|
|
||||||
|
if disc == 0 {
|
||||||
|
// It's a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn discriminant(string: &Str) -> u8 {
|
||||||
|
// cast the first 8 bytes to be u64, return its lsbyte
|
||||||
|
}
|
|
@ -427,7 +427,7 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
|
||||||
subs.get_without_compacting(var).content,
|
subs.get_without_compacting(var).content,
|
||||||
subs,
|
subs,
|
||||||
buf,
|
buf,
|
||||||
parens,
|
Parens::Unnecessary,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,8 +467,8 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
|
||||||
|
|
||||||
sorted_fields.sort_by(|(a, _), (b, _)| {
|
sorted_fields.sort_by(|(a, _), (b, _)| {
|
||||||
a.clone()
|
a.clone()
|
||||||
.into_string(interns, home)
|
.as_string(interns, home)
|
||||||
.cmp(&b.clone().into_string(&interns, home))
|
.cmp(&b.as_string(&interns, home))
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut any_written_yet = false;
|
let mut any_written_yet = false;
|
||||||
|
@ -480,7 +480,7 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
|
||||||
any_written_yet = true;
|
any_written_yet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push_str(&label.into_string(&interns, home));
|
buf.push_str(&label.as_string(&interns, home));
|
||||||
|
|
||||||
for var in vars {
|
for var in vars {
|
||||||
buf.push(' ');
|
buf.push(' ');
|
||||||
|
@ -533,7 +533,7 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
|
||||||
} else {
|
} else {
|
||||||
any_written_yet = true;
|
any_written_yet = true;
|
||||||
}
|
}
|
||||||
buf.push_str(&label.into_string(&interns, home));
|
buf.push_str(&label.as_string(&interns, home));
|
||||||
|
|
||||||
for var in vars {
|
for var in vars {
|
||||||
buf.push(' ');
|
buf.push(' ');
|
||||||
|
|
|
@ -151,10 +151,9 @@ impl Variable {
|
||||||
pub const EMPTY_TAG_UNION: Variable = Variable(2);
|
pub const EMPTY_TAG_UNION: Variable = Variable(2);
|
||||||
// Builtins
|
// Builtins
|
||||||
const BOOL_ENUM: Variable = Variable(3);
|
const BOOL_ENUM: Variable = Variable(3);
|
||||||
pub const BOOL: Variable = Variable(4);
|
pub const BOOL: Variable = Variable(4); // Used in `if` conditions
|
||||||
pub const LIST_GET: Variable = Variable(5);
|
|
||||||
|
|
||||||
pub const NUM_RESERVED_VARS: usize = 6;
|
pub const NUM_RESERVED_VARS: usize = 5;
|
||||||
|
|
||||||
const FIRST_USER_SPACE_VAR: Variable = Variable(Self::NUM_RESERVED_VARS as u32);
|
const FIRST_USER_SPACE_VAR: Variable = Variable(Self::NUM_RESERVED_VARS as u32);
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,16 @@ impl<T> RecordField<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_inner(&self) -> &T {
|
||||||
|
use RecordField::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Optional(t) => t,
|
||||||
|
Required(t) => t,
|
||||||
|
Demanded(t) => t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn map<F, U>(&self, mut f: F) -> RecordField<U>
|
pub fn map<F, U>(&self, mut f: F) -> RecordField<U>
|
||||||
where
|
where
|
||||||
F: FnMut(&T) -> U,
|
F: FnMut(&T) -> U,
|
||||||
|
@ -894,7 +904,7 @@ pub enum Reason {
|
||||||
FloatLiteral,
|
FloatLiteral,
|
||||||
IntLiteral,
|
IntLiteral,
|
||||||
NumLiteral,
|
NumLiteral,
|
||||||
InterpolatedStringVar,
|
StrInterpolation,
|
||||||
WhenBranch {
|
WhenBranch {
|
||||||
index: Index,
|
index: Index,
|
||||||
},
|
},
|
||||||
|
@ -917,9 +927,13 @@ pub enum Category {
|
||||||
Lookup(Symbol),
|
Lookup(Symbol),
|
||||||
CallResult(Option<Symbol>),
|
CallResult(Option<Symbol>),
|
||||||
LowLevelOpResult(LowLevel),
|
LowLevelOpResult(LowLevel),
|
||||||
TagApply(TagName),
|
TagApply {
|
||||||
|
tag_name: TagName,
|
||||||
|
args_count: usize,
|
||||||
|
},
|
||||||
Lambda,
|
Lambda,
|
||||||
Uniqueness,
|
Uniqueness,
|
||||||
|
StrInterpolation,
|
||||||
|
|
||||||
// storing variables in the ast
|
// storing variables in the ast
|
||||||
Storage,
|
Storage,
|
||||||
|
|
|
@ -787,8 +787,7 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) {
|
||||||
| Num(_, _)
|
| Num(_, _)
|
||||||
| Int(_, _)
|
| Int(_, _)
|
||||||
| Float(_, _)
|
| Float(_, _)
|
||||||
| Str(_)
|
| Str { .. }
|
||||||
| BlockStr(_)
|
|
||||||
| EmptyRecord
|
| EmptyRecord
|
||||||
| Accessor { .. }
|
| Accessor { .. }
|
||||||
| RunLowLevel { .. } => {}
|
| RunLowLevel { .. } => {}
|
||||||
|
|
|
@ -93,7 +93,7 @@ pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
||||||
let state = State::new(input.as_bytes(), Attempting::Module);
|
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||||
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
||||||
let answer = parser.parse(&arena, state);
|
let answer = parser.parse(&arena, state);
|
||||||
|
|
||||||
|
|
65
editor/editor-ideas.md
Normal file
65
editor/editor-ideas.md
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
(For background, [this talk](https://youtu.be/ZnYa99QoznE?t=4790) has an overview of the design goals for the editor.)
|
||||||
|
|
||||||
|
# Editor Ideas
|
||||||
|
|
||||||
|
Here are some ideas and interesting resources for the editor. Feel free to make a PR to add more!
|
||||||
|
|
||||||
|
## Sources of Potential Inspiration
|
||||||
|
|
||||||
|
These are potentially inspirational resources for the editor's design.
|
||||||
|
|
||||||
|
### Package-specific editor integrations
|
||||||
|
|
||||||
|
(Or possibly module-specific integrations, type-specific integrations, etc.)
|
||||||
|
|
||||||
|
* [What FP can learn from Smalltalk](https://youtu.be/baxtyeFVn3w) by [Aditya Siram](https://github.com/deech)
|
||||||
|
* [Moldable development](https://youtu.be/Pot9GnHFOVU) by [Tudor Gîrba](https://github.com/girba)
|
||||||
|
* [Unity game engine](https://unity.com/)
|
||||||
|
* Scripts can expose values as text inputs, sliders, checkboxes, etc or even generate custom graphical inputs
|
||||||
|
* Drag-n-drop game objects and component into script interfaces
|
||||||
|
|
||||||
|
### Live Interactivity
|
||||||
|
|
||||||
|
* [Up and Down the Ladder of Abstraction](http://worrydream.com/LadderOfAbstraction/) by [Bret Victor](http://worrydream.com/)
|
||||||
|
* [7 Bret Victor talks](https://www.youtube.com/watch?v=PUv66718DII&list=PLS4RYH2XfpAmswi1WDU6lwwggruEZrlPH)
|
||||||
|
* [Against the Current](https://youtu.be/WT2CMS0MxJ0) by [Chris Granger](https://github.com/ibdknox/)
|
||||||
|
* [Sketch-n-Sketch: Interactive SVG Programming with Direct Manipulation](https://youtu.be/YuGVC8VqXz0) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/)
|
||||||
|
* [Xi](https://xi-editor.io/) modern text editor with concurrent editing (related to [Druid](https://github.com/linebender/druid))
|
||||||
|
* [Self](https://selflanguage.org/) programming language
|
||||||
|
|
||||||
|
### Structured Editing
|
||||||
|
|
||||||
|
* [Deuce](http://ravichugh.github.io/sketch-n-sketch/) (videos on the right) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/) and others
|
||||||
|
* [Fructure: A Structured Editing Engine in Racket](https://youtu.be/CnbVCNIh1NA) by Andrew Blinn
|
||||||
|
* [Hazel: A Live FP Environment with Typed Holes](https://youtu.be/UkDSL0U9ndQ) by [Cyrus Omar](https://web.eecs.umich.edu/~comar/)
|
||||||
|
* [Dark Demo](https://youtu.be/QgimI2SnpTQ) by [Ellen Chisa](https://twitter.com/ellenchisa)
|
||||||
|
* [Introduction to JetBrains MPS](https://youtu.be/JoyzxjgVlQw) by [Kolja Dummann](https://www.youtube.com/channel/UCq_mWDvKdXYJJzBmXkci17w)
|
||||||
|
* [Eve](http://witheve.com/)
|
||||||
|
* code editor as prose writer
|
||||||
|
* live preview
|
||||||
|
* possible inspiration for live interactivity as well
|
||||||
|
* [Unreal Engine 4](https://www.unrealengine.com/en-US/)
|
||||||
|
* [Blueprints](https://docs.unrealengine.com/en-US/Engine/Blueprints/index.html) visual scripting (not suggesting visual scripting for Roc)
|
||||||
|
|
||||||
|
### Non-Code Related Inspiration
|
||||||
|
|
||||||
|
* [Scrivner](https://www.literatureandlatte.com/scrivener/overview) writing app for novelists, screenwriters, and more
|
||||||
|
* Word processors (Word, Google Docs, etc)
|
||||||
|
* Comments that are parallel to the text of the document.
|
||||||
|
* Comments can act as discussions and not just statements.
|
||||||
|
* Easy tooling around adding tables and other stylised text
|
||||||
|
* Excel and Google Sheets
|
||||||
|
* Not sure, maybe something they do well that we (code editors) could learn from
|
||||||
|
|
||||||
|
## General Thoughts/Ideas
|
||||||
|
|
||||||
|
Thoughts and ideas possibly taken from above inspirations or separate.
|
||||||
|
|
||||||
|
* ACCESSIBILITY!!!
|
||||||
|
* From Google Docs' comments, adding tests in a similar manner, where they exists in the same "document" but parallel to the code being written
|
||||||
|
* Makes sense for unit tests, keeps the test close to the source
|
||||||
|
* Doesn't necessarily make sense for integration or e2e testing
|
||||||
|
* Maybe easier to manually trigger a test related to exactly what code you're writing
|
||||||
|
* "Error mode" where the editor jumps you to the next error
|
||||||
|
* Similar in theory to diff tools that jump you to the next merge conflict
|
||||||
|
* dependency recommendation
|
32
name-and-logo.md
Normal file
32
name-and-logo.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<img width="185" alt="The Roc logo, an origami bird" src="https://user-images.githubusercontent.com/1094080/92188927-e61ebd00-ee2b-11ea-97ef-2fc88e0094b0.png">
|
||||||
|
|
||||||
|
# Name and Logo
|
||||||
|
|
||||||
|
The Roc programming language is named after [a mythical bird](https://en.wikipedia.org/wiki/Roc_(mythology)).
|
||||||
|
|
||||||
|
That’s why the logo is a bird. It’s specifically an [*origami* bird](https://youtu.be/9gni1t1k1uY) as a homage
|
||||||
|
to [Elm](https://elm-lang.org/)’s tangram logo.
|
||||||
|
|
||||||
|
Roc is a direct descendant of Elm. The languages are similar, but not the same.
|
||||||
|
[Origami](https://en.wikipedia.org/wiki/Origami) likewise has similarities to [tangrams](https://en.wikipedia.org/wiki/Tangram), although they are not the same.
|
||||||
|
Both involve making a surprising variety of things
|
||||||
|
from simple primitives. [*Folds*](https://en.wikipedia.org/wiki/Fold_(higher-order_function))
|
||||||
|
are also common in functional programming.
|
||||||
|
|
||||||
|
The logo was made by tracing triangles onto a photo of a physical origami bird.
|
||||||
|
It’s made of triangles because triangles are a foundational primitive in
|
||||||
|
computer graphics.
|
||||||
|
|
||||||
|
The name was chosen because it makes for a three-letter file extension, it means
|
||||||
|
something fantastical, and it has incredible potential for puns.
|
||||||
|
|
||||||
|
# Different Ways to Spell Roc
|
||||||
|
|
||||||
|
* **Roc** - traditional
|
||||||
|
* **roc** - low-key
|
||||||
|
* **ROC** - [YELLING](https://package.elm-lang.org/packages/elm/core/latest/String#toUpper)
|
||||||
|
* **Röc** - [metal 🤘](https://en.wikipedia.org/wiki/Metal_umlaut)
|
||||||
|
|
||||||
|
# Fun Facts
|
||||||
|
|
||||||
|
Roc translates to 鹏 in Chinese, [which means](https://www.mdbg.net/chinese/dictionary?page=worddict&wdrst=0&wdqb=%E9%B9%8F) "a large fabulous bird."
|
|
@ -225,7 +225,7 @@ not only will this type-check, but at the end we get a combined error type which
|
||||||
has the union of all the possible errors that could have occurred in this sequence.
|
has the union of all the possible errors that could have occurred in this sequence.
|
||||||
We can then handle those errors using a single `when`, like so:
|
We can then handle those errors using a single `when`, like so:
|
||||||
|
|
||||||
```elm
|
```coffeescript
|
||||||
when error is
|
when error is
|
||||||
# Http.Err possibilities
|
# Http.Err possibilities
|
||||||
PageNotFound -> ...
|
PageNotFound -> ...
|
||||||
|
@ -243,11 +243,8 @@ when error is
|
||||||
DiskFull -> ...
|
DiskFull -> ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Here is the set of slightly different types that will make the original chained
|
||||||
|
expression compile. (`after` is unchanged.)
|
||||||
Here is a set
|
|
||||||
of slightly different types that would make the above expression compile.
|
|
||||||
(`after` is unchanged.)
|
|
||||||
|
|
||||||
```elm
|
```elm
|
||||||
File.read : Filename -> Task File.Data (File.ReadErr *)
|
File.read : Filename -> Task File.Data (File.ReadErr *)
|
||||||
|
@ -257,28 +254,21 @@ Http.get : Url -> Task Http.Response (Http.Err *)
|
||||||
after : Task a err, (a -> Task b err) -> Task b err
|
after : Task a err, (a -> Task b err) -> Task b err
|
||||||
```
|
```
|
||||||
|
|
||||||
The key is that each of the error types expands to a Roc *tag union*. Here's how
|
The key is that each of the error types is a type alias for a Roc *tag union*.
|
||||||
they look:
|
Here's how they look:
|
||||||
|
|
||||||
```elm
|
```elm
|
||||||
Http.Err a : [PageNotFound, Timeout, BadPayload]a
|
Http.Err a : [ PageNotFound, Timeout, BadPayload ]a
|
||||||
File.ReadErr a : [FileNotFound, Corrupted, BadFormat]a
|
File.ReadErr a : [ FileNotFound, Corrupted, BadFormat ]a
|
||||||
File.WriteErr a : [FileNotFound, DiskFull]a
|
File.WriteErr a : [ FileNotFound, DiskFull ]a
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In Elm, these would be defined as custom types (aka algebraic data types) using
|
||||||
|
the `type` keyword. However, instead of traditional algebraic data types, Roc has
|
||||||
|
only tags - which work more like OCaml's *polymorphic variants*, and which
|
||||||
|
can be used in type aliases without a separate `type` keyword. (Roc has no `type` keyword.)
|
||||||
|
|
||||||
```elm
|
Here are some examples of using tags in a REPL:
|
||||||
first : List elem -> [Ok elem, ListWasEmpty]*
|
|
||||||
```
|
|
||||||
|
|
||||||
> It's motivated primarily by error handling in chained effects
|
|
||||||
> (e.g. multiple consecutive `Task.andThen`s between tasks with incompatible error types),
|
|
||||||
> which doesn't really come up in Elm but often will in Roc. Explaining how
|
|
||||||
> this improves that situation is out of scope for this document; even without
|
|
||||||
> that explanation, this section is already the longest!
|
|
||||||
|
|
||||||
Instead of traditional algebraic data types (like Elm has), Roc uses something more like
|
|
||||||
OCaml's *polymorphic variants*. In Roc they're called *tags*. Here are some examples of using tags in a REPL:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
> True
|
> True
|
||||||
|
@ -303,7 +293,8 @@ Foo "hi" 5 : [ Foo Str Int ]*
|
||||||
Foo 1 2 : [ Foo Int Int ]*
|
Foo 1 2 : [ Foo Int Int ]*
|
||||||
```
|
```
|
||||||
|
|
||||||
Tags are different from variants in Elm in several ways.
|
Tags have a lot in common with traditional algebraic data types' *variants*,
|
||||||
|
but are different in several ways.
|
||||||
|
|
||||||
One difference is that you can make up any tag you want, on the fly, and use it in any module,
|
One difference is that you can make up any tag you want, on the fly, and use it in any module,
|
||||||
without declaring it first. (These cannot be used to create opaque types; we'll discuss those
|
without declaring it first. (These cannot be used to create opaque types; we'll discuss those
|
||||||
|
@ -613,7 +604,7 @@ Here, the open record's type variable appears attached to the `}`.
|
||||||
|
|
||||||
> In the Elm example, the `a` is unbound, which in Roc means it appears as `*`.
|
> In the Elm example, the `a` is unbound, which in Roc means it appears as `*`.
|
||||||
|
|
||||||
Here's how that looks with a bound type varaible. In Elm:
|
Here's how that looks with a bound type variable. In Elm:
|
||||||
|
|
||||||
```elm
|
```elm
|
||||||
{ a | x : Int, y : Int } -> { a | x : Int, y : Int }
|
{ a | x : Int, y : Int } -> { a | x : Int, y : Int }
|
||||||
|
@ -770,7 +761,7 @@ Roc has `List`, `Set`, and `Map` in the standard library.
|
||||||
|
|
||||||
Here are the differences:
|
Here are the differences:
|
||||||
|
|
||||||
* `List` in Roc uses the term "list" the way Python does: to mean an unordered sequence of elements. Roc's `List` is more like an array, in that all the elements are sequential in memory and can be accessed in constant time. It still uses the `[` `]` syntax for list literals. Also there is no `::` operator because "cons" is not an efficient operation on an array like it is in a linked list.
|
* `List` in Roc uses the term "list" the way Python does: to mean an ordered sequence of elements. Roc's `List` is more like an array, in that all the elements are sequential in memory and can be accessed in constant time. It still uses the `[` `]` syntax for list literals. Also there is no `::` operator because "cons" is not an efficient operation on an array like it is in a linked list.
|
||||||
* `Map` in Roc is like `Dict` in Elm, except it's backed by hashing rather than ordering. Roc silently computes hash values for any value that can be used with `==`, so instead of a `comparable` constraint on `Set` elements and `Map` keys, in Roc they instead have the *functionless* constraint indicated with a `'`. So to add to a `Set` you use `Set.add : Set 'elem, 'elem -> Set 'elem`, and putting a value into a Map is `Map.put : Map 'key val, 'key, val -> Map 'key val`.
|
* `Map` in Roc is like `Dict` in Elm, except it's backed by hashing rather than ordering. Roc silently computes hash values for any value that can be used with `==`, so instead of a `comparable` constraint on `Set` elements and `Map` keys, in Roc they instead have the *functionless* constraint indicated with a `'`. So to add to a `Set` you use `Set.add : Set 'elem, 'elem -> Set 'elem`, and putting a value into a Map is `Map.put : Map 'key val, 'key, val -> Map 'key val`.
|
||||||
* `Set` in Roc is like `Set` in Elm: it's shorthand for a `Map` with keys but no value, and it has a slightly different API.
|
* `Set` in Roc is like `Set` in Elm: it's shorthand for a `Map` with keys but no value, and it has a slightly different API.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue