Merge branch 'trunk' of github.com:rtfeldman/roc into type-start-parse-error

This commit is contained in:
Eric Correia 2021-03-30 08:38:34 -04:00
commit c827b52aba
101 changed files with 3560 additions and 2680 deletions

View File

@ -8,8 +8,8 @@ To build the compiler, you need these installed:
* `libunwind` (macOS should already have this one installed) * `libunwind` (macOS should already have this one installed)
* `libc++-dev` * `libc++-dev`
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu) * Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
* [Zig](https://ziglang.org/) 0.7.1 or greater * [Zig](https://ziglang.org/), see below for version
* a particular version of LLVM (see below) * LLVM, see below for version
To run the test suite (via `cargo test`), you additionally need to install: To run the test suite (via `cargo test`), you additionally need to install:
@ -24,21 +24,21 @@ MacOS systems should already have `libunwind`, but other systems will need to in
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.)
### Zig ### Zig
**version: 0.7.x**
If you're on MacOS, you can install with `brew install zig` If you're on MacOS, you can install with `brew install zig`
If you're on Ubuntu and use Snap, you can install with `snap install zig --classic --beta` If you're on Ubuntu and use Snap, you can install with `snap install zig --classic --beta`
For any other OS, checkout the [Zig installation page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager) For any other OS, checkout the [Zig installation page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
### LLVM ### LLVM
**version: 10.0.x**
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 and Debian, you can use the `Automatic installation script` at [apt.llvm.org](https://apt.llvm.org): 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)" sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
``` ```
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.) For macOS, check the troubleshooting section below.
There are also plenty of alternative options at http://releases.llvm.org/download.html There are also plenty of alternative options at http://releases.llvm.org/download.html
@ -118,15 +118,30 @@ If you encounter `cannot find -lz` run `sudo apt install zlib1g-dev`.
### LLVM installation on macOS ### 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 By default homebrew will try to install llvm 11, which is currently
unsupported. You need to install an older version (10.0.0_3) by doing:
``` ```
$ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50fb0b24dbe30f5e975210bdad63257f517/Formula/llvm.rb $ brew edit llvm
# Replace the contents of the file with https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50fb0b24dbe30f5e975210bdad63257f517/Formula/llvm.rb
# we expect llvm-as-10 to be present
$ ln -s /usr/local/opt/llvm/bin/{llvm-as,llvm-as-10}
# "pinning" ensures that homebrew doesn't update it automatically # "pinning" ensures that homebrew doesn't update it automatically
$ brew pin llvm $ brew pin llvm
``` ```
If that doesn't work and you get a `brew` error `Error: Calling Installation of llvm from a GitHub commit URL is disabled! Use 'brew extract llvm' to stable tap on GitHub instead.` while trying the above solution, you can follow the steps extracting the formula into your private tap (one public version is at `sladwig/tap/llvm`). If installing LLVM still fails, it might help to run `sudo xcode-select -r` before installing again. It might also be useful to add these exports to your shell:
```
export PATH="/usr/local/opt/llvm/bin:$PATH"
export LDFLAGS="-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
export CPPFLAGS="-I/usr/local/opt/llvm/include"
```
If installing LLVM still fails, it might help to run `sudo xcode-select -r` before installing again.
### LLVM installation on Windows ### LLVM installation on Windows

46
Cargo.lock generated
View File

@ -889,25 +889,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "docs"
version = "0.1.0"
dependencies = [
"bumpalo",
"fs_extra",
"handlebars",
"maplit",
"pretty_assertions 0.5.1",
"pulldown-cmark",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_load",
"serde",
"serde_derive",
"serde_json",
]
[[package]] [[package]]
name = "downcast-rs" name = "downcast-rs"
version = "1.2.0" version = "1.2.0"
@ -2013,6 +1994,12 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "nonempty"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fa586da3e43cc7df44aae0e21ed2e743218b876de3f38035683d30bd8a3828e"
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.14"
@ -2961,6 +2948,7 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_docs",
"roc_editor", "roc_editor",
"roc_fmt", "roc_fmt",
"roc_gen", "roc_gen",
@ -3012,6 +3000,25 @@ dependencies = [
"roc_types", "roc_types",
] ]
[[package]]
name = "roc_docs"
version = "0.1.0"
dependencies = [
"bumpalo",
"fs_extra",
"handlebars",
"maplit",
"pretty_assertions 0.5.1",
"pulldown-cmark",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_load",
"serde",
"serde_derive",
"serde_json",
]
[[package]] [[package]]
name = "roc_editor" name = "roc_editor"
version = "0.1.0" version = "0.1.0"
@ -3034,6 +3041,7 @@ dependencies = [
"libc", "libc",
"log", "log",
"maplit", "maplit",
"nonempty",
"page_size", "page_size",
"palette", "palette",
"pest", "pest",

View File

@ -1,4 +1,4 @@
FROM rust:1.50-slim-buster FROM rust:1.51-slim-buster
WORKDIR /earthbuild WORKDIR /earthbuild
prep-debian: prep-debian:

View File

@ -33,6 +33,7 @@ target-all = [
[dependencies] [dependencies]
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
roc_can = { path = "../compiler/can" } roc_can = { path = "../compiler/can" }
roc_docs = { path = "../docs" }
roc_parse = { path = "../compiler/parse" } roc_parse = { path = "../compiler/parse" }
roc_region = { path = "../compiler/region" } roc_region = { path = "../compiler/region" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }

View File

@ -8,7 +8,7 @@ use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
use std::io; use std::io;
use std::path::Path; use std::path::{Path, PathBuf};
use std::process; use std::process;
use std::process::Command; use std::process::Command;
use target_lexicon::Triple; use target_lexicon::Triple;
@ -76,6 +76,25 @@ pub fn build_app<'a>() -> App<'a> {
.help("(optional) The directory or files to open on launch.") .help("(optional) The directory or files to open on launch.")
) )
) )
.subcommand(
App::new("docs")
.about("Generate documentation for Roc modules")
.arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)
.required(true)
.help("The directory or files to build documentation for")
)
)
}
pub fn docs(files: Vec<PathBuf>) {
roc_docs::generate(
files,
roc_builtins::std::standard_stdlib(),
Path::new("./generated-docs"),
)
} }
pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
@ -132,10 +151,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
.expect("TODO gracefully handle block_on failing"); .expect("TODO gracefully handle block_on failing");
} }
} }
Err(LoadingProblem::ParsingFailedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {
print!("{}", report);
}
Err(LoadingProblem::NoPlatform(report)) => {
print!("{}", report); print!("{}", report);
} }
Err(other) => { Err(other) => {

View File

@ -1,6 +1,6 @@
use roc_cli::{build, build_app, repl, DIRECTORY_OR_FILES}; use roc_cli::{build, build_app, docs, repl, DIRECTORY_OR_FILES};
use std::io; use std::io;
use std::path::Path; use std::path::{Path, PathBuf};
use target_lexicon::Triple; use target_lexicon::Triple;
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
@ -35,6 +35,21 @@ fn main() -> io::Result<()> {
} }
} }
} }
Some("docs") => {
let values = matches
.subcommand_matches("docs")
.unwrap()
.values_of_os(DIRECTORY_OR_FILES)
.unwrap();
let paths = values
.map(|os_str| Path::new(os_str).to_path_buf())
.collect::<Vec<PathBuf>>();
docs(paths);
Ok(())
}
_ => unreachable!(), _ => unreachable!(),
} }
} }

View File

@ -148,7 +148,7 @@ fn jit_to_ast_help<'a>(
} }
}; };
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()]; let fields = [Layout::Builtin(Builtin::Int64), *layout];
let layout = Layout::Struct(&fields); let layout = Layout::Struct(&fields);
let result_stack_size = layout.stack_size(env.ptr_bytes); let result_stack_size = layout.stack_size(env.ptr_bytes);

View File

@ -57,7 +57,7 @@ pub fn gen_and_eval<'a>(
let mut loaded = match loaded { let mut loaded = match loaded {
Ok(v) => v, Ok(v) => v,
Err(LoadingProblem::ParsingFailedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {
return Ok(ReplOutput::Problems(vec![report])); return Ok(ReplOutput::Problems(vec![report]));
} }
Err(e) => { Err(e) => {
@ -153,7 +153,7 @@ pub fn gen_and_eval<'a>(
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns); let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
Some(layout) => layout.clone(), Some(layout) => *layout,
None => { None => {
return Ok(ReplOutput::NoProblems { return Ok(ReplOutput::NoProblems {
expr: "<function>".to_string(), expr: "<function>".to_string(),

View File

@ -54,7 +54,7 @@ mod cli_run {
) { ) {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
if !compile_out.stderr.is_empty() { if !compile_out.stderr.is_empty() {
panic!(compile_out.stderr); panic!("{}", compile_out.stderr);
} }
assert!(compile_out.status.success()); assert!(compile_out.status.success());

View File

@ -1,11 +1,11 @@
interface Bool2 interface Bool
exposes [ not, and, or, xor, isEq, isNotEq ] exposes [ not, and, or, xor, isEq, isNotEq ]
imports [] imports []
## Returns #False when given #True, and vice versa. ## Returns `False` when given `True`, and vice versa.
not : [True, False] -> [True, False] not : [True, False] -> [True, False]
## Returns #True when given #True and #True, and #False when either argument is #False. ## Returns `True` when given `True` and `True`, and `False` when either argument is `False`.
## ##
## `a && b` is shorthand for `Bool.and a b` ## `a && b` is shorthand for `Bool.and a b`
## ##
@ -39,7 +39,7 @@ not : [True, False] -> [True, False]
and : Bool, Bool -> Bool and : Bool, Bool -> Bool
## Returns #True when given #True for either argument, and #False only when given #False and #False. ## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`.
## ##
## `a || b` is shorthand for `Bool.or a b`. ## `a || b` is shorthand for `Bool.or a b`.
## ##
@ -55,14 +55,13 @@ and : Bool, Bool -> Bool
## ##
## In some languages, `&&` and `||` are special-cased in the compiler to skip ## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances. ## evaluating the expression after the operator under certain circumstances.
## ## # In Roc, this is not the case. See the performance notes for #Bool.and for details.
## In Roc, this is not the case. See the performance notes for #Bool.and for details.
or : Bool, Bool -> Bool or : Bool, Bool -> Bool
## Exclusive or ## Exclusive or
xor : Bool, Bool -> Bool xor : Bool, Bool -> Bool
## Returns #True if the two values are *structurally equal*, and #False otherwise. ## Returns `True` if the two values are *structurally equal*, and `False` otherwise.
## ##
## `a == b` is shorthand for `Bool.isEq a b` ## `a == b` is shorthand for `Bool.isEq a b`
## ##

View File

@ -17,6 +17,5 @@ isEmpty : Dict * * -> Bool
## See for example #Result.map, #List.map, and #Set.map. ## See for example #Result.map, #List.map, and #Set.map.
map : map :
Dict beforeKey beforeValue, Dict beforeKey beforeValue,
(\{ key: beforeKey, value: beforeValue } -> ({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue })
{ key: afterKey, value: afterValue } -> Dict afterKey afterValue
) -> Dict afterKey afterValue

View File

@ -1,5 +1,54 @@
interface List2 interface List2
exposes [ List, single, empty, repeat, range, reverse, sort, map, mapWithIndex, mapOrCancel, mapOks, update, updater, allOks, append, prepend, concat, join, joinMap, oks, zip, zipMap, keepIf, dropIf, first, last, get, max, min, put, drop, append, prepend, dropLast, dropFirst, takeFirst, takeLast, split, sublist, walk, walkBackwards, walkUntil, walkBackwardsUntil, len, isEmpty, contains, all, any ] exposes
[ List
, single
, empty
, repeat
, range
, reverse
, sort
, map
, mapWithIndex
, mapOrCancel
, mapOks
, update
, updater
, allOks
, append
, prepend
, concat
, join
, joinMap
, oks
, zip
, zipMap
, keepIf
, dropIf
, first
, last
, get
, max
, min
, put
, drop
, append
, prepend
, dropLast
, dropFirst
, takeFirst
, takeLast
, split
, sublist
, walk
, walkBackwards
, walkUntil
, walkBackwardsUntil
, len
, isEmpty
, contains
, all
, any
]
imports [] imports []
## Types ## Types
@ -298,7 +347,7 @@ oks : List (Result elem *) -> List elem
## ##
## > For a generalized version that returns whatever you like, instead of a `Pair`, ## > For a generalized version that returns whatever you like, instead of a `Pair`,
## > see `zipMap`. ## > see `zipMap`.
zip : List a, List b, -> List [ Pair a b ]* zip : List a, List b -> List [ Pair a b ]*
## Like `zip` but you can specify what to do with each element. ## Like `zip` but you can specify what to do with each element.
## ##
@ -307,7 +356,7 @@ zip : List a, List b, -> List [ Pair a b ]*
## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3 ## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3
## ##
## Accepts up to 8 lists. ## Accepts up to 8 lists.
zipMap : List a, List b, (a, b) -> List c zipMap : List a, List b, (a, b -> c) -> List c
## Filter ## Filter

View File

@ -51,7 +51,7 @@ interface Num2
## ##
## In practice, these are rarely needed. It's most common to write ## In practice, these are rarely needed. It's most common to write
## number literals without any suffix. ## number literals without any suffix.
Num range : @Num range Num range : [ @Num range ]
## A fixed-size integer - that is, a number with no fractional component. ## A fixed-size integer - that is, a number with no fractional component.
## ##
@ -102,21 +102,21 @@ Num range : @Num range
## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly. ## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly.
## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.) ## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.)
## * Finally, if a particular numeric calculation is running too slowly, you can try experimenting with other number sizes. This rarely makes a meaningful difference, but some processors can operate on different number sizes at different speeds. ## * Finally, if a particular numeric calculation is running too slowly, you can try experimenting with other number sizes. This rarely makes a meaningful difference, but some processors can operate on different number sizes at different speeds.
Int size : Num (@Int size) Int size : Num [ @Int size ]
## A signed 8-bit integer, ranging from -128 to 127 ## A signed 8-bit integer, ranging from -128 to 127
I8 : Int @I8 I8 : Int [ @I8 ]
U8 : Int @U8 U8 : Int [ @U8 ]
U16 : Int @U16 U16 : Int [ @U16 ]
I16 : Int @I16 I16 : Int [ @I16 ]
U32 : Int @U32 U32 : Int [ @U32 ]
I32 : Int @I32 I32 : Int [ @I32 ]
I64 : Int @I64 I64 : Int [ @I64 ]
U64 : Int @U64 U64 : Int [ @U64 ]
I128 : Int @I128 I128 : Int [ @I128 ]
U128 : Int @U128 U128 : Int [ @U128 ]
Ilen : Int @Ilen Ilen : Int [ @Ilen ]
Nat : Int @Nat Nat : Int [ @Nat ]
## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values. ## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values.
## ##
@ -574,9 +574,9 @@ divRound : Int, Int -> Int
## Bitwise ## Bitwise
xor : Int -> Int -> Int xor : Int, Int -> Int
and : Int -> Int -> Int and : Int, Int -> Int
not : Int -> Int not : Int -> Int

View File

@ -1,7 +1,7 @@
interface Str2 interface Str
exposes [ Str2, decimal, split, isEmpty, startsWith, endsWith, contains, anyGraphemes, allGraphemes, join, joinWith, padGraphemesStart, padGraphemesEnd, graphemes, reverseGraphemes, isCaseInsensitiveEq, isCaseInsensitiveNeq, walkGraphemes, isCapitalized, isAllUppercase, isAllLowercase, toUtf8, toUtf16, toUtf32, walkUtf8, walkUtf16, walkUtf32, walkRevUtf8, walkRevUtf16, walkRevUtf32 ] exposes [ Str, decimal, split, isEmpty, startsWith, endsWith, contains, anyGraphemes, allGraphemes, join, joinWith, padGraphemesStart, padGraphemesEnd, graphemes, reverseGraphemes, isCaseInsensitiveEq, isCaseInsensitiveNeq, walkGraphemes, isCapitalized, isAllUppercase, isAllLowercase, toUtf8, toUtf16, toUtf32, walkUtf8, walkUtf16, walkUtf32, walkRevUtf8, walkRevUtf16, walkRevUtf32 ]
imports [] imports []
## Types ## # Types
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks ## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
## to the basics. ## to the basics.

View File

@ -430,6 +430,20 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// log : Float a -> Float a
let log_needs_positive = SolvedType::TagUnion(
vec![(TagName::Global("LogNeedsPositive".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_type(
Symbol::NUM_LOG,
top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), log_needs_positive)),
),
);
// round : Float a -> Int b // round : Float a -> Int b
add_type( add_type(
Symbol::NUM_ROUND, Symbol::NUM_ROUND,
@ -722,6 +736,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// product : List (Num a) -> Num a
add_type(
Symbol::LIST_PRODUCT,
top_level_function(
vec![list_type(num_type(flex(TVAR1)))],
Box::new(num_type(flex(TVAR1))),
),
);
// walk : List elem, (elem -> accum -> accum), accum -> accum // walk : List elem, (elem -> accum -> accum), accum -> accum
add_type( add_type(
Symbol::LIST_WALK, Symbol::LIST_WALK,

View File

@ -65,6 +65,13 @@ impl IntroducedVariables {
} }
} }
fn malformed(env: &mut Env, region: Region, name: &str) {
use roc_problem::can::RuntimeError::*;
let problem = MalformedTypeName((*name).into(), region);
env.problem(roc_problem::can::Problem::RuntimeError(problem));
}
pub fn canonicalize_annotation( pub fn canonicalize_annotation(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
@ -446,7 +453,16 @@ fn can_annotation_help(
local_aliases, local_aliases,
references, references,
), ),
Wildcard | Malformed(_) => { Wildcard => {
let var = var_store.fresh();
introduced_variables.insert_wildcard(var);
Type::Variable(var)
}
Malformed(string) => {
malformed(env, region, string);
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_wildcard(var); introduced_variables.insert_wildcard(var);
@ -542,8 +558,9 @@ fn can_assigned_fields<'a>(
field = nested; field = nested;
continue 'inner; continue 'inner;
} }
Malformed(_) => { Malformed(string) => {
// TODO report this? malformed(env, region, string);
// completely skip this element, advance to the next tag // completely skip this element, advance to the next tag
continue 'outer; continue 'outer;
} }
@ -645,8 +662,9 @@ fn can_tags<'a>(
tag = nested; tag = nested;
continue 'inner; continue 'inner;
} }
Tag::Malformed(_) => { Tag::Malformed(string) => {
// TODO report this? malformed(env, region, string);
// completely skip this element, advance to the next tag // completely skip this element, advance to the next tag
continue 'outer; continue 'outer;
} }

View File

@ -77,6 +77,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_CONCAT => list_concat, LIST_CONCAT => list_concat,
LIST_CONTAINS => list_contains, LIST_CONTAINS => list_contains,
LIST_SUM => list_sum, LIST_SUM => list_sum,
LIST_PRODUCT => list_product,
LIST_PREPEND => list_prepend, LIST_PREPEND => list_prepend,
LIST_JOIN => list_join, LIST_JOIN => list_join,
LIST_MAP => list_map, LIST_MAP => list_map,
@ -138,6 +139,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_REM => num_rem, NUM_REM => num_rem,
NUM_IS_MULTIPLE_OF => num_is_multiple_of, NUM_IS_MULTIPLE_OF => num_is_multiple_of,
NUM_SQRT => num_sqrt, NUM_SQRT => num_sqrt,
NUM_LOG => num_log,
NUM_ROUND => num_round, NUM_ROUND => num_round,
NUM_IS_ODD => num_is_odd, NUM_IS_ODD => num_is_odd,
NUM_IS_EVEN => num_is_even, NUM_IS_EVEN => num_is_even,
@ -217,6 +219,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::LIST_CONCAT => list_concat, Symbol::LIST_CONCAT => list_concat,
Symbol::LIST_CONTAINS => list_contains, Symbol::LIST_CONTAINS => list_contains,
Symbol::LIST_SUM => list_sum, Symbol::LIST_SUM => list_sum,
Symbol::LIST_PRODUCT => list_product,
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_MAP => list_map,
@ -274,6 +277,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_REM => num_rem, Symbol::NUM_REM => num_rem,
Symbol::NUM_IS_MULTIPLE_OF => num_is_multiple_of, Symbol::NUM_IS_MULTIPLE_OF => num_is_multiple_of,
Symbol::NUM_SQRT => num_sqrt, Symbol::NUM_SQRT => num_sqrt,
Symbol::NUM_LOG => num_log,
Symbol::NUM_ROUND => num_round, Symbol::NUM_ROUND => num_round,
Symbol::NUM_IS_ODD => num_is_odd, Symbol::NUM_IS_ODD => num_is_odd,
Symbol::NUM_IS_EVEN => num_is_even, Symbol::NUM_IS_EVEN => num_is_even,
@ -411,8 +415,8 @@ fn lowlevel_4(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
/// Num.maxInt : Int /// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let int_percision_var = var_store.fresh(); let int_precision_var = var_store.fresh();
let body = Int(int_var, int_percision_var, i64::MAX.into()); let body = Int(int_var, int_precision_var, i64::MAX.into());
Def { Def {
annotation: None, annotation: None,
@ -426,8 +430,8 @@ fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.minInt : Int /// Num.minInt : Int
fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let int_percision_var = var_store.fresh(); let int_precision_var = var_store.fresh();
let body = Int(int_var, int_percision_var, i64::MIN.into()); let body = Int(int_var, int_precision_var, i64::MIN.into());
Def { Def {
annotation: None, annotation: None,
@ -1131,50 +1135,82 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let float_var = var_store.fresh(); let float_var = var_store.fresh();
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = var_store.fresh();
let percision_var = var_store.fresh(); let precision_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let body = If { let body = If {
branch_var: ret_var, branch_var: ret_var,
cond_var: bool_var, cond_var: bool_var,
branches: vec![( branches: vec![(
// if-condition no_region(RunLowLevel {
no_region( op: LowLevel::NumGte,
// Num.neq denominator 0
RunLowLevel {
op: LowLevel::NotEq,
args: vec![ args: vec![
(float_var, Var(Symbol::ARG_1)), (float_var, Var(Symbol::ARG_1)),
(float_var, Float(unbound_zero_var, percision_var, 0.0)), (float_var, Float(unbound_zero_var, precision_var, 0.0)),
], ],
ret_var: bool_var, ret_var: bool_var,
}, }),
), no_region(tag(
// denominator was not zero
no_region(
// Ok (Float.#divUnchecked numerator denominator)
tag(
"Ok", "Ok",
vec![ vec![RunLowLevel {
// Num.#divUnchecked numerator denominator
RunLowLevel {
op: LowLevel::NumSqrtUnchecked, op: LowLevel::NumSqrtUnchecked,
args: vec![(float_var, Var(Symbol::ARG_1))], args: vec![(float_var, Var(Symbol::ARG_1))],
ret_var: float_var, ret_var: float_var,
}, }],
],
var_store,
),
),
)],
final_else: Box::new(
// denominator was zero
no_region(tag(
"Err",
vec![tag("DivByZero", Vec::new(), var_store)],
var_store, var_store,
)), )),
), )],
final_else: Box::new(no_region(tag(
"Err",
vec![tag("SqrtOfNegative", Vec::new(), var_store)],
var_store,
))),
};
defn(
symbol,
vec![(float_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
/// Num.log : Float -> Result Float [ LogNeedsPositive ]*
fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let float_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
let precision_var = var_store.fresh();
let ret_var = var_store.fresh();
let body = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
no_region(RunLowLevel {
op: LowLevel::NumGt,
args: vec![
(float_var, Var(Symbol::ARG_1)),
(float_var, Float(unbound_zero_var, precision_var, 0.0)),
],
ret_var: bool_var,
}),
no_region(tag(
"Ok",
vec![RunLowLevel {
op: LowLevel::NumLogUnchecked,
args: vec![(float_var, Var(Symbol::ARG_1))],
ret_var: float_var,
}],
var_store,
)),
)],
final_else: Box::new(no_region(tag(
"Err",
vec![tag("LogNeedsPositive", Vec::new(), var_store)],
var_store,
))),
}; };
defn( defn(
@ -1385,8 +1421,8 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.maxI128: I128 /// Num.maxI128: I128
fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let int_percision_var = var_store.fresh(); let int_precision_var = var_store.fresh();
let body = Int(int_var, int_percision_var, i128::MAX); let body = Int(int_var, int_precision_var, i128::MAX);
let std = roc_builtins::std::types(); let std = roc_builtins::std::types();
let solved = std.get(&symbol).unwrap(); let solved = std.get(&symbol).unwrap();
@ -2116,22 +2152,12 @@ fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// List.sum : List (Num a) -> Num a /// List.sum : List (Num a) -> Num a
fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); lowlevel_1(symbol, LowLevel::ListSum, var_store)
let result_var = var_store.fresh(); }
let body = RunLowLevel { /// List.product : List (Num a) -> Num a
op: LowLevel::ListSum, fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
args: vec![(list_var, Var(Symbol::ARG_1))], lowlevel_1(symbol, LowLevel::ListProduct, var_store)
ret_var: result_var,
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
result_var,
)
} }
/// List.keepIf : List elem, (elem -> Bool) -> List elem /// List.keepIf : List elem, (elem -> Bool) -> List elem
@ -2661,7 +2687,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = var_store.fresh();
let percision_var = var_store.fresh(); let precision_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let body = If { let body = If {
@ -2675,7 +2701,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
(num_var, Float(unbound_zero_var, percision_var, 0.0)), (num_var, Float(unbound_zero_var, precision_var, 0.0)),
], ],
ret_var: bool_var, ret_var: bool_var,
}, },
@ -2724,7 +2750,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = var_store.fresh();
let unbound_zero_percision_var = var_store.fresh(); let unbound_zero_precision_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let body = If { let body = If {
@ -2740,7 +2766,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
( (
num_var, num_var,
Int(unbound_zero_var, unbound_zero_percision_var, 0), Int(unbound_zero_var, unbound_zero_precision_var, 0),
), ),
], ],
ret_var: bool_var, ret_var: bool_var,
@ -2795,7 +2821,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let zero_var = var_store.fresh(); let zero_var = var_store.fresh();
let zero_percision_var = var_store.fresh(); let zero_precision_var = var_store.fresh();
let list_elem_var = var_store.fresh(); let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
@ -2810,7 +2836,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, Int(zero_var, zero_percision_var, 0)), (len_var, Int(zero_var, zero_precision_var, 0)),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2834,7 +2860,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe, op: LowLevel::ListGetUnsafe,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(len_var, Int(zero_var, zero_percision_var, 0)), (len_var, Int(zero_var, zero_precision_var, 0)),
], ],
ret_var: list_elem_var, ret_var: list_elem_var,
}, },
@ -2876,7 +2902,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let num_percision_var = var_store.fresh(); let num_precision_var = var_store.fresh();
let list_elem_var = var_store.fresh(); let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
@ -2891,7 +2917,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, Int(num_var, num_percision_var, 0)), (len_var, Int(num_var, num_precision_var, 0)),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2930,7 +2956,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var, ret_var: len_var,
}, },
), ),
(arg_var, Int(num_var, num_percision_var, 1)), (arg_var, Int(num_var, num_precision_var, 1)),
], ],
ret_var: len_var, ret_var: len_var,
}, },

View File

@ -1481,7 +1481,7 @@ fn to_pending_def<'a>(
} }
} }
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) | Nested(sub_def) => { SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => {
to_pending_def(env, var_store, sub_def, scope, pattern_type) to_pending_def(env, var_store, sub_def, scope, pattern_type)
} }

View File

@ -408,46 +408,7 @@ pub fn canonicalize_expr<'a>(
} }
ast::Expr::Var { module_name, ident } => { ast::Expr::Var { module_name, ident } => {
canonicalize_lookup(env, scope, module_name, ident, region) canonicalize_lookup(env, scope, module_name, ident, region)
} //ast::Expr::InterpolatedStr(pairs, suffix) => { }
// let mut output = Output::new();
// let can_pairs: Vec<(String, Located<Expr>)> = pairs
// .into_iter()
// .map(|(string, loc_ident)| {
// // From a language design perspective, we only permit idents in interpolation.
// // However, in a canonical Expr we store it as a full Expr, not a Symbol.
// // This is so that we can resolve it to either Var or Unrecognized; if we
// // stored it as a Symbol, we couldn't record runtime errors here.
// let can_expr = match resolve_ident(
// &env,
// &scope,
// loc_ident.value,
// &mut output.references,
// ) {
// Ok(symbol) => Var(symbol),
// Err(ident) => {
// let loc_ident = Located {
// region: loc_ident.region,
// value: ident,
// };
// env.problem(Problem::LookupNotInScope(loc_ident.clone()));
// RuntimeError(LookupNotInScope(loc_ident))
// }
// };
// (
// string,
// Located {
// region: loc_ident.region,
// value: can_expr,
// },
// )
// })
// .collect();
// (InterpolatedStr(can_pairs, suffix), output)
//}
ast::Expr::Defs(loc_defs, loc_ret) => { ast::Expr::Defs(loc_defs, loc_ret) => {
can_defs_with_return( can_defs_with_return(
env, env,
@ -767,11 +728,6 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default()) (RuntimeError(problem), Output::default())
} }
ast::Expr::Nested(sub_expr) => {
let (answer, output) = canonicalize_expr(env, var_store, scope, region, sub_expr);
(answer.value, output)
}
ast::Expr::NonBase10Int { ast::Expr::NonBase10Int {
string, string,
base, base,

View File

@ -4,7 +4,7 @@ use roc_module::ident::ModuleName;
use roc_module::operator::BinOp::Pizza; use roc_module::operator::BinOp::Pizza;
use roc_module::operator::{BinOp, CalledVia}; use roc_module::operator::{BinOp, CalledVia};
use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{AssignedField, Def, Pattern, WhenBranch}; use roc_parse::ast::{AssignedField, Def, WhenBranch};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
// BinOp precedence logic adapted from Gluon by Markus Westerlind, MIT licensed // BinOp precedence logic adapted from Gluon by Markus Westerlind, MIT licensed
@ -59,7 +59,7 @@ fn new_op_call_expr<'a>(
Located { value, region } Located { value, region }
} }
fn desugar_defs<'a>( fn desugar_def_helps<'a>(
arena: &'a Bump, arena: &'a Bump,
region: Region, region: Region,
defs: &'a [&'a Located<Def<'a>>], defs: &'a [&'a Located<Def<'a>>],
@ -88,39 +88,23 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
use roc_parse::ast::Def::*; use roc_parse::ast::Def::*;
match def { match def {
Body(loc_pattern, loc_expr) | Nested(Body(loc_pattern, loc_expr)) => { Body(loc_pattern, loc_expr) => Body(loc_pattern, desugar_expr(arena, loc_expr)),
Body(loc_pattern, desugar_expr(arena, loc_expr)) SpaceBefore(def, _) | SpaceAfter(def, _) => desugar_def(arena, def),
} alias @ Alias { .. } => *alias,
SpaceBefore(def, _) ann @ Annotation(_, _) => *ann,
| SpaceAfter(def, _)
| Nested(SpaceBefore(def, _))
| Nested(SpaceAfter(def, _)) => desugar_def(arena, def),
Nested(Nested(def)) => desugar_def(arena, def),
alias @ Alias { .. } => Nested(alias),
Nested(alias @ Alias { .. }) => Nested(alias),
ann @ Annotation(_, _) => Nested(ann),
Nested(ann @ Annotation(_, _)) => Nested(ann),
AnnotatedBody { AnnotatedBody {
ann_pattern, ann_pattern,
ann_type, ann_type,
comment, comment,
body_pattern, body_pattern,
body_expr, body_expr,
} } => AnnotatedBody {
| Nested(AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr,
}) => AnnotatedBody {
ann_pattern, ann_pattern,
ann_type, ann_type,
comment: *comment, comment: *comment,
body_pattern: *body_pattern, body_pattern: *body_pattern,
body_expr: desugar_expr(arena, body_expr), body_expr: desugar_expr(arena, body_expr),
}, },
Nested(NotYetImplemented(s)) => todo!("{}", s),
NotYetImplemented(s) => todo!("{}", s), NotYetImplemented(s) => todo!("{}", s),
} }
} }
@ -130,33 +114,22 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Located<Expr<'a>> { pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Located<Expr<'a>> {
match &loc_expr.value { match &loc_expr.value {
Float(_) Float(_)
| Nested(Float(_))
| Num(_) | Num(_)
| Nested(Num(_))
| NonBase10Int { .. } | NonBase10Int { .. }
| Nested(NonBase10Int { .. })
| Str(_) | Str(_)
| Nested(Str(_))
| AccessorFunction(_) | AccessorFunction(_)
| Nested(AccessorFunction(_))
| Var { .. } | Var { .. }
| Nested(Var { .. })
| MalformedIdent(_, _) | MalformedIdent(_, _)
| Nested(MalformedIdent(_, _))
| MalformedClosure | MalformedClosure
| Nested(MalformedClosure)
| PrecedenceConflict { .. } | PrecedenceConflict { .. }
| Nested(PrecedenceConflict { .. })
| GlobalTag(_) | GlobalTag(_)
| Nested(GlobalTag(_)) | PrivateTag(_) => loc_expr,
| PrivateTag(_)
| Nested(PrivateTag(_)) => loc_expr,
Access(sub_expr, paths) | Nested(Access(sub_expr, paths)) => { Access(sub_expr, paths) => {
let region = loc_expr.region; let region = loc_expr.region;
let loc_sub_expr = Located { let loc_sub_expr = Located {
region, region,
value: Nested(sub_expr), value: **sub_expr,
}; };
let value = Access(&desugar_expr(arena, arena.alloc(loc_sub_expr)).value, paths); let value = Access(&desugar_expr(arena, arena.alloc(loc_sub_expr)).value, paths);
@ -165,11 +138,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
List { List {
items, items,
final_comments, final_comments,
} } => {
| Nested(List {
items,
final_comments,
}) => {
let mut new_items = Vec::with_capacity_in(items.len(), arena); let mut new_items = Vec::with_capacity_in(items.len(), arena);
for item in items.iter() { for item in items.iter() {
@ -189,11 +158,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
Record { Record {
fields, fields,
final_comments, final_comments,
} } => {
| Nested(Record {
fields,
final_comments,
}) => {
let mut new_fields = Vec::with_capacity_in(fields.len(), arena); let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() { for field in fields.iter() {
@ -220,12 +185,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
fields, fields,
update, update,
final_comments, final_comments,
} } => {
| Nested(RecordUpdate {
fields,
update,
final_comments,
}) => {
// NOTE the `update` field is always a `Var { .. }` and does not need to be desugared // NOTE the `update` field is always a `Var { .. }` and does not need to be desugared
let mut new_fields = Vec::with_capacity_in(fields.len(), arena); let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
@ -249,14 +209,11 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
}, },
}) })
} }
Closure(loc_patterns, loc_ret) | Nested(Closure(loc_patterns, loc_ret)) => { Closure(loc_patterns, loc_ret) => arena.alloc(Located {
arena.alloc(Located {
region: loc_expr.region, region: loc_expr.region,
value: Closure(loc_patterns, desugar_expr(arena, loc_ret)), value: Closure(loc_patterns, desugar_expr(arena, loc_ret)),
}) }),
} Backpassing(loc_patterns, loc_body, loc_ret) => {
Backpassing(loc_patterns, loc_body, loc_ret)
| Nested(Backpassing(loc_patterns, loc_body, loc_ret)) => {
// loc_patterns <- loc_body // loc_patterns <- loc_body
// //
// loc_ret // loc_ret
@ -293,13 +250,9 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
} }
} }
} }
BinOps(lefts, right) | Nested(BinOps(lefts, right)) => { BinOps(lefts, right) => desugar_bin_ops(arena, loc_expr.region, lefts, right),
desugar_bin_ops(arena, loc_expr.region, lefts, right) Defs(defs, loc_ret) => desugar_def_helps(arena, loc_expr.region, *defs, loc_ret),
} Apply(loc_fn, loc_args, called_via) => {
Defs(defs, loc_ret) | Nested(Defs(defs, loc_ret)) => {
desugar_defs(arena, loc_expr.region, *defs, loc_ret)
}
Apply(loc_fn, loc_args, called_via) | Nested(Apply(loc_fn, loc_args, called_via)) => {
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena); let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena);
for loc_arg in loc_args.iter() { for loc_arg in loc_args.iter() {
@ -313,7 +266,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
region: loc_expr.region, region: loc_expr.region,
}) })
} }
When(loc_cond_expr, branches) | Nested(When(loc_cond_expr, branches)) => { When(loc_cond_expr, branches) => {
let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, &loc_cond_expr)); let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, &loc_cond_expr));
let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena); let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena);
@ -321,15 +274,10 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
let desugared = desugar_expr(arena, &branch.value); let desugared = desugar_expr(arena, &branch.value);
let mut alternatives = Vec::with_capacity_in(branch.patterns.len(), arena); let mut alternatives = Vec::with_capacity_in(branch.patterns.len(), arena);
for loc_pattern in branch.patterns.iter() { alternatives.extend(branch.patterns.iter().copied());
alternatives.push(Located {
region: loc_pattern.region,
value: Pattern::Nested(&loc_pattern.value),
})
}
let desugared_guard = if let Some(guard) = &branch.guard { let desugared_guard = if let Some(guard) = &branch.guard {
Some(desugar_expr(arena, guard).clone()) Some(*desugar_expr(arena, guard))
} else { } else {
None None
}; };
@ -338,10 +286,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
desugared_branches.push(&*arena.alloc(WhenBranch { desugared_branches.push(&*arena.alloc(WhenBranch {
patterns: alternatives, patterns: alternatives,
value: Located { value: *desugared,
region: desugared.region,
value: Nested(&desugared.value),
},
guard: desugared_guard, guard: desugared_guard,
})); }));
} }
@ -353,7 +298,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
region: loc_expr.region, region: loc_expr.region,
}) })
} }
UnaryOp(loc_arg, loc_op) | Nested(UnaryOp(loc_arg, loc_op)) => { UnaryOp(loc_arg, loc_op) => {
use roc_module::operator::UnaryOp::*; use roc_module::operator::UnaryOp::*;
let region = loc_op.region; let region = loc_op.region;
@ -379,24 +324,18 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
region: loc_expr.region, region: loc_expr.region,
}) })
} }
SpaceBefore(expr, _) SpaceBefore(expr, _) | SpaceAfter(expr, _) | ParensAround(expr) => {
| Nested(SpaceBefore(expr, _))
| SpaceAfter(expr, _)
| Nested(SpaceAfter(expr, _))
| ParensAround(expr)
| Nested(ParensAround(expr))
| Nested(Nested(expr)) => {
// Since we've already begun canonicalization, spaces and parens // Since we've already begun canonicalization, spaces and parens
// are no longer needed and should be dropped. // are no longer needed and should be dropped.
desugar_expr( desugar_expr(
arena, arena,
arena.alloc(Located { arena.alloc(Located {
value: Nested(expr), value: **expr,
region: loc_expr.region, region: loc_expr.region,
}), }),
) )
} }
If(if_thens, final_else_branch) | Nested(If(if_thens, final_else_branch)) => { If(if_thens, final_else_branch) => {
// If does not get desugared into `when` so we can give more targetted error messages during type checking. // If does not get desugared into `when` so we can give more targetted error messages during type checking.
let desugared_final_else = &*arena.alloc(desugar_expr(arena, &final_else_branch)); let desugared_final_else = &*arena.alloc(desugar_expr(arena, &final_else_branch));
@ -404,8 +343,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
for (condition, then_branch) in if_thens.iter() { for (condition, then_branch) in if_thens.iter() {
desugared_if_thens.push(( desugared_if_thens.push((
desugar_expr(arena, condition).clone(), *desugar_expr(arena, condition),
desugar_expr(arena, then_branch).clone(), *desugar_expr(arena, then_branch),
)); ));
} }

View File

@ -238,7 +238,7 @@ pub fn canonicalize_pattern<'a>(
ptype => unsupported_pattern(env, ptype, region), ptype => unsupported_pattern(env, ptype, region),
}, },
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => { SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region) return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
} }
RecordDestructure(patterns) => { RecordDestructure(patterns) => {

View File

@ -19,7 +19,6 @@ impl<'a> Formattable<'a> for Def<'a> {
SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => { SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => {
spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline() spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline()
} }
Nested(def) => def.is_multiline(),
NotYetImplemented(s) => todo!("{}", s), NotYetImplemented(s) => todo!("{}", s),
} }
} }
@ -99,7 +98,6 @@ impl<'a> Formattable<'a> for Def<'a> {
sub_def.format(buf, indent); sub_def.format(buf, indent);
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
} }
Nested(def) => def.format(buf, indent),
NotYetImplemented(s) => todo!("{}", s), NotYetImplemented(s) => todo!("{}", s),
} }
} }

View File

@ -75,7 +75,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
expr: loc_subexpr, .. expr: loc_subexpr, ..
}) => loc_subexpr.is_multiline(), }) => loc_subexpr.is_multiline(),
ParensAround(subexpr) | Nested(subexpr) => subexpr.is_multiline(), ParensAround(subexpr) => subexpr.is_multiline(),
Closure(loc_patterns, loc_body) => { Closure(loc_patterns, loc_body) => {
// check the body first because it's more likely to be multiline // check the body first because it's more likely to be multiline
@ -298,9 +298,6 @@ impl<'a> Formattable<'a> for Expr<'a> {
sub_expr.format_with_options(buf, parens, newlines, indent); sub_expr.format_with_options(buf, parens, newlines, indent);
} }
Nested(nested_expr) => {
nested_expr.format_with_options(buf, parens, newlines, indent);
}
AccessorFunction(key) => { AccessorFunction(key) => {
buf.push('.'); buf.push('.');
buf.push_str(key); buf.push_str(key);
@ -508,8 +505,6 @@ fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
false false
} }
Nested(nested_expr) => empty_line_before_expr(nested_expr),
_ => false, _ => false,
} }
} }

View File

@ -22,8 +22,6 @@ impl<'a> Formattable<'a> for Pattern<'a> {
spaces.iter().any(|s| s.is_comment()) spaces.iter().any(|s| s.is_comment())
} }
Pattern::Nested(nested_pat) => nested_pat.is_multiline(),
Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()), Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()),
Pattern::RequiredField(_, subpattern) => subpattern.is_multiline(), Pattern::RequiredField(_, subpattern) => subpattern.is_multiline(),
@ -153,10 +151,6 @@ impl<'a> Formattable<'a> for Pattern<'a> {
} }
} }
Nested(sub_pattern) => {
sub_pattern.format_with_options(buf, parens, newlines, indent);
}
// Malformed // Malformed
Malformed(string) | MalformedIdent(string, _) => buf.push_str(string), Malformed(string) | MalformedIdent(string, _) => buf.push_str(string),
QualifiedIdentifier { module_name, ident } => { QualifiedIdentifier { module_name, ident } => {

View File

@ -28,7 +28,7 @@ mod test_fmt {
assert_eq!(buf, expected) assert_eq!(buf, expected)
} }
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", input, error) Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error)
}; };
} }

View File

@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{ use crate::llvm::build_list::{
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map,
list_map2, list_map3, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set, list_map2, list_map3, list_map_with_index, list_prepend, list_product, list_repeat,
list_single, list_sum, list_walk, list_walk_backwards, list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
}; };
use crate::llvm::build_str::{ use crate::llvm::build_str::{
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
@ -90,9 +90,9 @@ pub enum OptLevel {
Optimize, Optimize,
} }
impl Into<OptimizationLevel> for OptLevel { impl From<OptLevel> for OptimizationLevel {
fn into(self) -> OptimizationLevel { fn from(level: OptLevel) -> Self {
match self { match level {
OptLevel::Normal => OptimizationLevel::None, OptLevel::Normal => OptimizationLevel::None,
OptLevel::Optimize => OptimizationLevel::Aggressive, OptLevel::Optimize => OptimizationLevel::Aggressive,
} }
@ -337,6 +337,12 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
f64_type.fn_type(&[f64_type.into()], false), f64_type.fn_type(&[f64_type.into()], false),
); );
add_intrinsic(
module,
LLVM_LOG_F64,
f64_type.fn_type(&[f64_type.into()], false),
);
add_intrinsic( add_intrinsic(
module, module,
LLVM_LROUND_I64_F64, LLVM_LROUND_I64_F64,
@ -455,6 +461,7 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32"; static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
static LLVM_SQRT_F64: &str = "llvm.sqrt.f64"; static LLVM_SQRT_F64: &str = "llvm.sqrt.f64";
static LLVM_LOG_F64: &str = "llvm.log.f64";
static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64"; static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
static LLVM_FABS_F64: &str = "llvm.fabs.f64"; static LLVM_FABS_F64: &str = "llvm.fabs.f64";
static LLVM_SIN_F64: &str = "llvm.sin.f64"; static LLVM_SIN_F64: &str = "llvm.sin.f64";
@ -2030,7 +2037,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
// access itself! // access itself!
// scope = scope.clone(); // scope = scope.clone();
scope.insert(*symbol, (layout.clone(), val)); scope.insert(*symbol, (*layout, val));
stack.push(*symbol); stack.push(*symbol);
} }
@ -2063,8 +2070,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
} => { } => {
// when the fail case is just Rethrow, there is no cleanup work to do // when the fail case is just Rethrow, there is no cleanup work to do
// so we can just treat this invoke as a normal call // so we can just treat this invoke as a normal call
let stmt = let stmt = roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass);
roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass);
build_exp_stmt(env, layout_ids, scope, parent, &stmt) build_exp_stmt(env, layout_ids, scope, parent, &stmt)
} }
@ -2088,7 +2094,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
scope, scope,
parent, parent,
*symbol, *symbol,
layout.clone(), *layout,
function_value.into(), function_value.into(),
call.arguments, call.arguments,
None, None,
@ -2108,7 +2114,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
scope, scope,
parent, parent,
*symbol, *symbol,
layout.clone(), *layout,
function_ptr.into(), function_ptr.into(),
call.arguments, call.arguments,
None, None,
@ -2135,7 +2141,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
scope, scope,
parent, parent,
*symbol, *symbol,
layout.clone(), *layout,
function_ptr.into(), function_ptr.into(),
call.arguments, call.arguments,
Some(closure_data), Some(closure_data),
@ -2194,7 +2200,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes); basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
let switch_args = SwitchArgsIr { let switch_args = SwitchArgsIr {
cond_layout: cond_layout.clone(), cond_layout: *cond_layout,
cond_symbol: *cond_symbol, cond_symbol: *cond_symbol,
branches, branches,
default_branch: default_branch.1, default_branch: default_branch.1,
@ -2242,7 +2248,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
for (ptr, param) in joinpoint_args.iter().zip(parameters.iter()) { for (ptr, param) in joinpoint_args.iter().zip(parameters.iter()) {
let value = env.builder.build_load(*ptr, "load_jp_argument"); let value = env.builder.build_load(*ptr, "load_jp_argument");
scope.insert(param.symbol, (param.layout.clone(), value)); scope.insert(param.symbol, (param.layout, value));
} }
// put the continuation in // put the continuation in
@ -2277,7 +2283,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
match modify { match modify {
Inc(symbol, inc_amount) => { Inc(symbol, inc_amount) => {
let (value, layout) = load_symbol_and_layout(scope, symbol); let (value, layout) = load_symbol_and_layout(scope, symbol);
let layout = layout.clone(); let layout = *layout;
if layout.contains_refcounted() { if layout.contains_refcounted() {
increment_refcount_layout( increment_refcount_layout(
@ -3177,7 +3183,7 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
let function_pointer_type = { let function_pointer_type = {
let function_layout = let function_layout =
ClosureLayout::extend_function_layout(arena, arguments, closure.clone(), result); ClosureLayout::extend_function_layout(arena, arguments, *closure, result);
// this is already a (function) pointer type // this is already a (function) pointer type
basic_type_from_layout(arena, context, &function_layout, env.ptr_bytes) basic_type_from_layout(arena, context, &function_layout, env.ptr_bytes)
@ -3252,7 +3258,7 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
); );
// STEP 4: build a {} -> u64 function that gives the size of the closure // STEP 4: build a {} -> u64 function that gives the size of the closure
let layout = Layout::Closure(arguments, closure.clone(), result); let layout = Layout::Closure(arguments, *closure, result);
build_host_exposed_alias_size(env, def_name, alias_symbol, &layout); build_host_exposed_alias_size(env, def_name, alias_symbol, &layout);
} }
@ -3455,7 +3461,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
// Add args to scope // Add args to scope
for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) { for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) {
set_name(arg_val, arg_symbol.ident_string(&env.interns)); set_name(arg_val, arg_symbol.ident_string(&env.interns));
scope.insert(*arg_symbol, (layout.clone(), arg_val)); scope.insert(*arg_symbol, (*layout, arg_val));
} }
let body = build_exp_stmt(env, layout_ids, &mut scope, fn_val, &proc.body); let body = build_exp_stmt(env, layout_ids, &mut scope, fn_val, &proc.body);
@ -3931,6 +3937,13 @@ fn run_low_level<'a, 'ctx, 'env>(
list_sum(env, parent, list, layout) list_sum(env, parent, list, layout)
} }
ListProduct => {
debug_assert_eq!(args.len(), 1);
let list = load_symbol(scope, &args[0]);
list_product(env, parent, list, 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);
@ -3963,8 +3976,8 @@ fn run_low_level<'a, 'ctx, 'env>(
list_join(env, inplace, parent, list, outer_list_layout) list_join(env, inplace, parent, list, outer_list_layout)
} }
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
| NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => { | NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]); let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]);
@ -4559,7 +4572,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
{ {
env.builder.position_at_end(pass_block); env.builder.position_at_end(pass_block);
scope.insert(symbol, (ret_layout.clone(), call_result)); scope.insert(symbol, (*ret_layout, call_result));
build_exp_stmt(env, layout_ids, scope, parent, pass); build_exp_stmt(env, layout_ids, scope, parent, pass);
@ -5273,6 +5286,7 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
NumNeg => bd.build_float_neg(arg, "negate_float").into(), NumNeg => bd.build_float_neg(arg, "negate_float").into(),
NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]), NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]),
NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_F64, &[arg.into()]), NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_F64, &[arg.into()]),
NumLogUnchecked => env.call_intrinsic(LLVM_LOG_F64, &[arg.into()]),
NumRound => env.call_intrinsic(LLVM_LROUND_I64_F64, &[arg.into()]), NumRound => env.call_intrinsic(LLVM_LROUND_I64_F64, &[arg.into()]),
NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]), NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]),
NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]), NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]),

View File

@ -717,11 +717,7 @@ pub fn dict_walk<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
stepper_layout, stepper_layout,
&[ &[*key_layout, *value_layout, *accum_layout],
key_layout.clone(),
value_layout.clone(),
accum_layout.clone(),
],
) )
.as_global_value() .as_global_value()
.as_pointer_value(); .as_pointer_value();

View File

@ -70,7 +70,7 @@ fn build_hash_layout<'a, 'ctx, 'env>(
unreachable!("recursion pointers should never be hashed directly") unreachable!("recursion pointers should never be hashed directly")
} }
WhenRecursive::Loop(union_layout) => { WhenRecursive::Loop(union_layout) => {
let layout = Layout::Union(union_layout.clone()); let layout = Layout::Union(union_layout);
let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes); let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
@ -287,7 +287,7 @@ fn hash_struct<'a, 'ctx, 'env>(
unreachable!("The current layout should not be recursive, but is") unreachable!("The current layout should not be recursive, but is")
} }
WhenRecursive::Loop(union_layout) => { WhenRecursive::Loop(union_layout) => {
let field_layout = Layout::Union(union_layout.clone()); let field_layout = Layout::Union(*union_layout);
let bt = basic_type_from_layout( let bt = basic_type_from_layout(
env.arena, env.arena,
@ -811,7 +811,7 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
field_layouts, field_layouts,
WhenRecursive::Loop(union_layout.clone()), WhenRecursive::Loop(*union_layout),
seed, seed,
struct_value, struct_value,
) )

View File

@ -423,7 +423,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
let ctx = env.context; let ctx = env.context;
let wrapper_struct = list.into_struct_value(); let wrapper_struct = list.into_struct_value();
let (input_inplace, element_layout) = match list_layout.clone() { let (input_inplace, element_layout) = match *list_layout {
Layout::Builtin(Builtin::EmptyList) => ( Layout::Builtin(Builtin::EmptyList) => (
InPlace::InPlace, InPlace::InPlace,
// this pointer will never actually be dereferenced // this pointer will never actually be dereferenced
@ -434,7 +434,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
MemoryMode::Unique => InPlace::InPlace, MemoryMode::Unique => InPlace::InPlace,
MemoryMode::Refcounted => InPlace::Clone, MemoryMode::Refcounted => InPlace::Clone,
}, },
elem_layout.clone(), *elem_layout,
), ),
_ => unreachable!("Invalid layout {:?} in List.reverse", list_layout), _ => unreachable!("Invalid layout {:?} in List.reverse", list_layout),
@ -788,6 +788,81 @@ pub fn list_sum<'a, 'ctx, 'env>(
builder.build_load(accum_alloca, "load_final_acum") builder.build_load(accum_alloca, "load_final_acum")
} }
/// List.product : List (Num a) -> Num a
pub fn list_product<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
list: BasicValueEnum<'ctx>,
default_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let ctx = env.context;
let builder = env.builder;
let list_wrapper = list.into_struct_value();
let len = list_len(env.builder, list_wrapper);
let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes);
let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum");
let default: BasicValueEnum = match accum_type {
BasicTypeEnum::IntType(int_type) => int_type.const_int(1, false).into(),
BasicTypeEnum::FloatType(float_type) => float_type.const_float(1.0).into(),
_ => unreachable!(""),
};
builder.build_store(accum_alloca, default);
let then_block = ctx.append_basic_block(parent, "then");
let cont_block = ctx.append_basic_block(parent, "branchcont");
let condition = builder.build_int_compare(
IntPredicate::UGT,
len,
ctx.i64_type().const_zero(),
"list_non_empty",
);
builder.build_conditional_branch(condition, then_block, cont_block);
builder.position_at_end(then_block);
let elem_ptr_type = get_ptr_type(&accum_type, AddressSpace::Generic);
let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type);
let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| {
// load current accumulator
let current = builder.build_load(accum_alloca, "retrieve_accum");
let new_current = build_num_binop(
env,
parent,
current,
default_layout,
elem,
default_layout,
roc_module::low_level::LowLevel::NumMul,
);
builder.build_store(accum_alloca, new_current);
};
incrementing_elem_loop(
builder,
ctx,
parent,
list_ptr,
len,
"#index",
walk_right_loop,
);
builder.build_unconditional_branch(cont_block);
builder.position_at_end(cont_block);
builder.build_load(accum_alloca, "load_final_acum")
}
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum /// List.walk : List elem, (elem -> accum -> accum), accum -> accum
pub fn list_walk<'a, 'ctx, 'env>( pub fn list_walk<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -868,7 +943,7 @@ fn list_walk_generic<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
func_layout, func_layout,
&[element_layout.clone(), default_layout.clone()], &[*element_layout, *default_layout],
) )
.as_global_value() .as_global_value()
.as_pointer_value(); .as_pointer_value();
@ -959,7 +1034,7 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
env.builder.build_store(transform_ptr, transform); env.builder.build_store(transform_ptr, transform);
let stepper_caller = let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &[element_layout.clone()]) build_transform_caller(env, layout_ids, transform_layout, &[*element_layout])
.as_global_value() .as_global_value()
.as_pointer_value(); .as_pointer_value();
@ -1066,7 +1141,7 @@ pub fn list_keep_result<'a, 'ctx, 'env>(
env.builder.build_store(transform_ptr, transform); env.builder.build_store(transform_ptr, transform);
let stepper_caller = let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &[before_layout.clone()]) build_transform_caller(env, layout_ids, transform_layout, &[*before_layout])
.as_global_value() .as_global_value()
.as_pointer_value(); .as_pointer_value();
@ -1130,7 +1205,7 @@ pub fn list_map<'a, 'ctx, 'env>(
list, list,
element_layout, element_layout,
bitcode::LIST_MAP, bitcode::LIST_MAP,
&[element_layout.clone()], &[*element_layout],
) )
} }
@ -1151,7 +1226,7 @@ pub fn list_map_with_index<'a, 'ctx, 'env>(
list, list,
element_layout, element_layout,
bitcode::LIST_MAP_WITH_INDEX, bitcode::LIST_MAP_WITH_INDEX,
&[Layout::Builtin(Builtin::Usize), element_layout.clone()], &[Layout::Builtin(Builtin::Usize), *element_layout],
) )
} }
@ -1255,7 +1330,7 @@ pub fn list_map2<'a, 'ctx, 'env>(
let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr"); let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, transform); env.builder.build_store(transform_ptr, transform);
let argument_layouts = [element1_layout.clone(), element2_layout.clone()]; let argument_layouts = [*element1_layout, *element2_layout];
let stepper_caller = let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &argument_layouts) build_transform_caller(env, layout_ids, transform_layout, &argument_layouts)
.as_global_value() .as_global_value()
@ -1351,11 +1426,7 @@ pub fn list_map3<'a, 'ctx, 'env>(
let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr"); let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, transform); env.builder.build_store(transform_ptr, transform);
let argument_layouts = [ let argument_layouts = [*element1_layout, *element2_layout, *element3_layout];
element1_layout.clone(),
element2_layout.clone(),
element3_layout.clone(),
];
let stepper_caller = let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &argument_layouts) build_transform_caller(env, layout_ids, transform_layout, &argument_layouts)
.as_global_value() .as_global_value()

View File

@ -102,7 +102,7 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
Builtin::List(_, elem) => build_list_eq( Builtin::List(_, elem) => build_list_eq(
env, env,
layout_ids, layout_ids,
&Layout::Builtin(builtin.clone()), &Layout::Builtin(*builtin),
elem, elem,
lhs_val.into_struct_value(), lhs_val.into_struct_value(),
rhs_val.into_struct_value(), rhs_val.into_struct_value(),
@ -170,7 +170,7 @@ fn build_eq<'a, 'ctx, 'env>(
} }
WhenRecursive::Loop(union_layout) => { WhenRecursive::Loop(union_layout) => {
let layout = Layout::Union(union_layout.clone()); let layout = Layout::Union(union_layout);
let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes); let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
@ -188,7 +188,7 @@ fn build_eq<'a, 'ctx, 'env>(
build_tag_eq( build_tag_eq(
env, env,
layout_ids, layout_ids,
WhenRecursive::Loop(union_layout.clone()), WhenRecursive::Loop(union_layout),
&layout, &layout,
&union_layout, &union_layout,
field1_cast.into(), field1_cast.into(),
@ -262,7 +262,7 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
let is_equal = build_list_eq( let is_equal = build_list_eq(
env, env,
layout_ids, layout_ids,
&Layout::Builtin(builtin.clone()), &Layout::Builtin(*builtin),
elem, elem,
lhs_val.into_struct_value(), lhs_val.into_struct_value(),
rhs_val.into_struct_value(), rhs_val.into_struct_value(),
@ -690,7 +690,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
unreachable!("The current layout should not be recursive, but is") unreachable!("The current layout should not be recursive, but is")
} }
WhenRecursive::Loop(union_layout) => { WhenRecursive::Loop(union_layout) => {
let field_layout = Layout::Union(union_layout.clone()); let field_layout = Layout::Union(*union_layout);
let bt = basic_type_from_layout( let bt = basic_type_from_layout(
env.arena, env.arena,
@ -717,7 +717,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
field2_cast.into(), field2_cast.into(),
&field_layout, &field_layout,
&field_layout, &field_layout,
WhenRecursive::Loop(union_layout.clone()), WhenRecursive::Loop(*union_layout),
) )
.into_int_value() .into_int_value()
} }
@ -1234,7 +1234,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
field_layouts, field_layouts,
WhenRecursive::Loop(union_layout.clone()), WhenRecursive::Loop(*union_layout),
struct1, struct1,
struct2, struct2,
) )

View File

@ -454,7 +454,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
mode, mode,
&WhenRecursive::Loop(variant.clone()), &WhenRecursive::Loop(*variant),
tags, tags,
value.into_pointer_value(), value.into_pointer_value(),
true, true,
@ -470,7 +470,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
mode, mode,
&WhenRecursive::Loop(variant.clone()), &WhenRecursive::Loop(*variant),
&*env.arena.alloc([other_fields]), &*env.arena.alloc([other_fields]),
value.into_pointer_value(), value.into_pointer_value(),
true, true,
@ -484,7 +484,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
mode, mode,
&WhenRecursive::Loop(variant.clone()), &WhenRecursive::Loop(*variant),
&*env.arena.alloc([*fields]), &*env.arena.alloc([*fields]),
value.into_pointer_value(), value.into_pointer_value(),
true, true,
@ -497,7 +497,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
mode, mode,
&WhenRecursive::Loop(variant.clone()), &WhenRecursive::Loop(*variant),
tags, tags,
value.into_pointer_value(), value.into_pointer_value(),
false, false,
@ -549,7 +549,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
unreachable!("recursion pointers should never be hashed directly") unreachable!("recursion pointers should never be hashed directly")
} }
WhenRecursive::Loop(union_layout) => { WhenRecursive::Loop(union_layout) => {
let layout = Layout::Union(union_layout.clone()); let layout = Layout::Union(*union_layout);
let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes); let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);

View File

@ -8,9 +8,9 @@ pub enum RocCallResult<T> {
Failure(*mut c_char), Failure(*mut c_char),
} }
impl<T: Sized> Into<Result<T, String>> for RocCallResult<T> { impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
fn into(self) -> Result<T, String> { fn from(call_result: RocCallResult<T>) -> Self {
match self { match call_result {
Success(value) => Ok(value), Success(value) => Ok(value),
Failure(failure) => Err({ Failure(failure) => Err({
let raw = unsafe { CString::from_raw(failure) }; let raw = unsafe { CString::from_raw(failure) };

View File

@ -1,6 +1,6 @@
#![warn(clippy::all, clippy::dbg_macro)] #![warn(clippy::all, clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
use bumpalo::{collections::Vec, Bump}; use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode; use roc_builtins::bitcode;
@ -105,7 +105,7 @@ where
} => { } => {
// for now, treat invoke as a normal call // for now, treat invoke as a normal call
let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass); let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass);
self.build_stmt(&stmt) self.build_stmt(&stmt)
} }
Stmt::Switch { Stmt::Switch {
@ -252,32 +252,20 @@ where
x => Err(format!("layout, {:?}, not implemented yet", x)), x => Err(format!("layout, {:?}, not implemented yet", x)),
} }
} }
LowLevel::NumAcos => self.build_fn_call( LowLevel::NumAcos => {
sym, self.build_fn_call(sym, bitcode::NUM_ACOS.to_string(), args, &[*layout], layout)
bitcode::NUM_ACOS.to_string(), }
args, LowLevel::NumAsin => {
&[layout.clone()], self.build_fn_call(sym, bitcode::NUM_ASIN.to_string(), args, &[*layout], layout)
layout, }
), LowLevel::NumAtan => {
LowLevel::NumAsin => self.build_fn_call( self.build_fn_call(sym, bitcode::NUM_ATAN.to_string(), args, &[*layout], layout)
sym, }
bitcode::NUM_ASIN.to_string(),
args,
&[layout.clone()],
layout,
),
LowLevel::NumAtan => self.build_fn_call(
sym,
bitcode::NUM_ATAN.to_string(),
args,
&[layout.clone()],
layout,
),
LowLevel::NumPowInt => self.build_fn_call( LowLevel::NumPowInt => self.build_fn_call(
sym, sym,
bitcode::NUM_POW_INT.to_string(), bitcode::NUM_POW_INT.to_string(),
args, args,
&[layout.clone(), layout.clone()], &[*layout, *layout],
layout, layout,
), ),
LowLevel::NumSub => { LowLevel::NumSub => {
@ -472,7 +460,7 @@ where
} => { } => {
// for now, treat invoke as a normal call // for now, treat invoke as a normal call
let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass); let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass);
self.scan_ast(&stmt); self.scan_ast(&stmt);
} }

View File

@ -12,8 +12,6 @@ mod helpers;
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] #[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod gen_num { mod gen_num {
//use roc_std::RocOrder;
#[test] #[test]
fn i64_values() { fn i64_values() {
assert_evals_to!("0", 0, i64); assert_evals_to!("0", 0, i64);

View File

@ -5,6 +5,7 @@ use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use tempfile::tempdir; use tempfile::tempdir;
#[allow(dead_code)]
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
@ -18,6 +19,7 @@ fn promote_expr_to_module(src: &str) -> String {
buffer buffer
} }
#[allow(dead_code)]
pub fn helper<'a>( pub fn helper<'a>(
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
src: &str, src: &str,

View File

@ -120,7 +120,7 @@ fn generate_module_doc<'a>(
(acc, None) (acc, None)
} }
Body(_, _) | Nested(_) => (acc, None), Body(_, _) => (acc, None),
NotYetImplemented(s) => todo!("{}", s), NotYetImplemented(s) => todo!("{}", s),
} }

View File

@ -784,6 +784,10 @@ enum Msg<'a> {
}, },
FailedToParse(ParseProblem<'a, SyntaxError<'a>>), FailedToParse(ParseProblem<'a, SyntaxError<'a>>),
FailedToReadFile {
filename: PathBuf,
error: io::ErrorKind,
},
} }
#[derive(Debug)] #[derive(Debug)]
@ -996,18 +1000,16 @@ pub enum LoadingProblem<'a> {
FileProblem { FileProblem {
filename: PathBuf, filename: PathBuf,
error: io::ErrorKind, error: io::ErrorKind,
msg: &'static str,
}, },
ParsingFailed(ParseProblem<'a, SyntaxError<'a>>), ParsingFailed(ParseProblem<'a, SyntaxError<'a>>),
UnexpectedHeader(String), UnexpectedHeader(String),
/// there is no platform (likely running an Interface module)
NoPlatform(String),
MsgChannelDied, MsgChannelDied,
ErrJoiningWorkerThreads, ErrJoiningWorkerThreads,
TriedToImportAppModule, TriedToImportAppModule,
/// a formatted report of parsing failure
ParsingFailedReport(String), /// a formatted report
FormattedReport(String),
} }
pub enum Phases { pub enum Phases {
@ -1399,6 +1401,14 @@ where
Err(LoadingProblem::ParsingFailed(problem)) => { Err(LoadingProblem::ParsingFailed(problem)) => {
msg_tx.send(Msg::FailedToParse(problem)).unwrap(); msg_tx.send(Msg::FailedToParse(problem)).unwrap();
} }
Err(LoadingProblem::FileProblem {
filename,
error,
}) => {
msg_tx
.send(Msg::FailedToReadFile { filename, error })
.unwrap();
}
Err(other) => { Err(other) => {
return Err(other); return Err(other);
} }
@ -1457,6 +1467,16 @@ where
let worker_listeners = worker_listeners.into_bump_slice(); let worker_listeners = worker_listeners.into_bump_slice();
let msg_tx = msg_tx.clone(); let msg_tx = msg_tx.clone();
macro_rules! shut_down_worker_threads {
() => {
for listener in worker_listeners {
listener
.send(WorkerMsg::Shutdown)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
}
};
}
// The root module will have already queued up messages to process, // The root module will have already queued up messages to process,
// and processing those messages will in turn queue up more messages. // and processing those messages will in turn queue up more messages.
for msg in msg_rx.iter() { for msg in msg_rx.iter() {
@ -1490,12 +1510,7 @@ where
// We're done! There should be no more messages pending. // We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty()); debug_assert!(msg_rx.is_empty());
// Shut down all the worker threads. shut_down_worker_threads!();
for listener in worker_listeners {
listener
.send(WorkerMsg::Shutdown)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
}
return Ok(LoadResult::Monomorphized(finish_specialization( return Ok(LoadResult::Monomorphized(finish_specialization(
state, state,
@ -1503,50 +1518,29 @@ where
exposed_to_host, exposed_to_host,
)?)); )?));
} }
Msg::FailedToParse(problem) => { Msg::FailedToReadFile { filename, error } => {
// Shut down all the worker threads. shut_down_worker_threads!();
for listener in worker_listeners {
listener let buf = to_file_problem_report(&filename, error);
.send(WorkerMsg::Shutdown) return Err(LoadingProblem::FormattedReport(buf));
.map_err(|_| LoadingProblem::MsgChannelDied)?;
} }
use roc_reporting::report::{ Msg::FailedToParse(problem) => {
parse_problem, RocDocAllocator, DEFAULT_PALETTE, shut_down_worker_threads!();
};
// TODO this is not in fact safe let module_ids = Arc::try_unwrap(state.arc_modules)
let src = unsafe { from_utf8_unchecked(problem.bytes) };
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
let mut module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
panic!("There were still outstanding Arc references to module_ids") panic!("There were still outstanding Arc references to module_ids")
}) })
.into_inner() .into_inner()
.into_module_ids(); .into_module_ids();
let module_id = let buf = to_parse_problem_report(
module_ids.get_or_insert(&"find module name somehow?".into()); problem,
let interns = Interns {
module_ids, module_ids,
all_ident_ids: state.constrained_ident_ids, state.constrained_ident_ids,
}; );
return Err(LoadingProblem::FormattedReport(buf));
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
let starting_line = 0;
let report =
parse_problem(&alloc, problem.filename.clone(), starting_line, problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
return Err(LoadingProblem::ParsingFailedReport(buf));
} }
msg => { msg => {
// This is where most of the main thread's work gets done. // This is where most of the main thread's work gets done.
@ -1950,7 +1944,7 @@ fn update<'a>(
}; };
for (layout, pend) in specs { for (layout, pend) in specs {
existing.insert(layout.clone(), pend.clone()); existing.insert(*layout, pend.clone());
} }
} }
} }
@ -2086,6 +2080,9 @@ fn update<'a>(
Msg::FailedToParse(_) => { Msg::FailedToParse(_) => {
unreachable!(); unreachable!();
} }
Msg::FailedToReadFile { .. } => {
unreachable!();
}
} }
} }
@ -2142,72 +2139,8 @@ fn finish_specialization(
} }
Valid(To::NewPackage(p_or_p)) => p_or_p, Valid(To::NewPackage(p_or_p)) => p_or_p,
other => { other => {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE}; let buf = to_missing_platform_report(state.root_id, other);
use ven_pretty::DocAllocator; return Err(LoadingProblem::FormattedReport(buf));
let module_id = state.root_id;
let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&[], module_id, &interns);
let report = {
match other {
Valid(_) => unreachable!(),
NotSpecified => {
let doc = alloc.stack(vec![
alloc.reflow("I could not find a platform based on your input file."),
alloc.reflow(r"Does the module header contain an entry that looks like this:"),
alloc
.parser_suggestion(" packages { base: \"platform\" }")
.indent(4),
alloc.reflow("See also TODO."),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsInterface => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a interface file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsPkgConfig => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a package config file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
}
};
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
return Err(LoadingProblem::NoPlatform(buf));
} }
}; };
@ -2332,6 +2265,10 @@ fn load_pkg_config<'a>(
))) )))
} }
Ok((ast::Module::Platform { header }, parser_state)) => { Ok((ast::Module::Platform { header }, parser_state)) => {
let delta = bytes.len() - parser_state.bytes.len();
let chomped = &bytes[..delta];
let header_src = unsafe { std::str::from_utf8_unchecked(chomped) };
// make a Pkg-Config module that ultimately exposes `main` to the host // make a Pkg-Config module that ultimately exposes `main` to the host
let pkg_config_module_msg = fabricate_pkg_config_module( let pkg_config_module_msg = fabricate_pkg_config_module(
arena, arena,
@ -2342,6 +2279,7 @@ fn load_pkg_config<'a>(
module_ids.clone(), module_ids.clone(),
ident_ids_by_module.clone(), ident_ids_by_module.clone(),
&header, &header,
header_src,
pkg_module_timing, pkg_module_timing,
) )
.1; .1;
@ -2360,7 +2298,7 @@ fn load_pkg_config<'a>(
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
} }
Err(fail) => Err(LoadingProblem::ParsingFailed( Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, bytes), SyntaxError::Header(fail).into_parse_problem(filename, "", bytes),
)), )),
} }
} }
@ -2368,7 +2306,6 @@ fn load_pkg_config<'a>(
Err(err) => Err(LoadingProblem::FileProblem { Err(err) => Err(LoadingProblem::FileProblem {
filename, filename,
error: err.kind(), error: err.kind(),
msg: "while reading a Pkg-Config.roc file",
}), }),
} }
} }
@ -2633,7 +2570,7 @@ fn parse_header<'a>(
module_timing, module_timing,
)), )),
Err(fail) => Err(LoadingProblem::ParsingFailed( Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, src_bytes), SyntaxError::Header(fail).into_parse_problem(filename, "", src_bytes),
)), )),
} }
} }
@ -2670,7 +2607,6 @@ fn load_filename<'a>(
Err(err) => Err(LoadingProblem::FileProblem { Err(err) => Err(LoadingProblem::FileProblem {
filename, filename,
error: err.kind(), error: err.kind(),
msg: "in `load_filename`",
}), }),
} }
} }
@ -2939,18 +2875,24 @@ fn send_header<'a>(
) )
} }
// TODO refactor so more logic is shared with `send_header` #[derive(Debug)]
#[allow(clippy::too_many_arguments)] struct PlatformHeaderInfo<'a> {
fn send_header_two<'a>(
arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
is_root_module: bool, is_root_module: bool,
shorthand: &'a str, shorthand: &'a str,
header_src: &'a str,
app_module_id: ModuleId, app_module_id: ModuleId,
packages: &'a [Located<PackageEntry<'a>>], packages: &'a [Located<PackageEntry<'a>>],
provides: &'a [Located<ExposesEntry<'a, &'a str>>], provides: &'a [Located<ExposesEntry<'a, &'a str>>],
requires: &'a [Located<TypedIdent<'a>>], requires: &'a [Located<TypedIdent<'a>>],
imports: &'a [Located<ImportsEntry<'a>>], imports: &'a [Located<ImportsEntry<'a>>],
}
// TODO refactor so more logic is shared with `send_header`
#[allow(clippy::too_many_arguments)]
fn send_header_two<'a>(
arena: &'a Bump,
info: PlatformHeaderInfo<'a>,
parse_state: parser::State<'a>, parse_state: parser::State<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>, module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -2958,6 +2900,18 @@ fn send_header_two<'a>(
) -> (ModuleId, Msg<'a>) { ) -> (ModuleId, Msg<'a>) {
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
let PlatformHeaderInfo {
filename,
shorthand,
is_root_module,
header_src,
app_module_id,
packages,
provides,
requires,
imports,
} = info;
let declared_name: InlinableString = "".into(); let declared_name: InlinableString = "".into();
let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> = let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
@ -3149,7 +3103,7 @@ fn send_header_two<'a>(
package_qualified_imported_modules, package_qualified_imported_modules,
deps_by_name, deps_by_name,
exposes: exposed, exposes: exposed,
header_src: "#builtin effect header", header_src,
parse_state, parse_state,
exposed_imports: scope, exposed_imports: scope,
module_timing, module_timing,
@ -3278,21 +3232,27 @@ fn fabricate_pkg_config_module<'a>(
module_ids: Arc<Mutex<PackageModuleIds<'a>>>, module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
header: &PlatformHeader<'a>, header: &PlatformHeader<'a>,
header_src: &'a str,
module_timing: ModuleTiming, module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) { ) -> (ModuleId, Msg<'a>) {
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] = let provides: &'a [Located<ExposesEntry<'a, &'a str>>] =
header.provides.clone().into_bump_slice(); header.provides.clone().into_bump_slice();
let info = PlatformHeaderInfo {
filename,
is_root_module: false,
shorthand,
header_src,
app_module_id,
packages: &[],
provides,
requires: header.requires.clone().into_bump_slice(),
imports: header.imports.clone().into_bump_slice(),
};
send_header_two( send_header_two(
arena, arena,
filename, info,
false,
shorthand,
app_module_id,
&[],
provides,
header.requires.clone().into_bump_slice(),
header.imports.clone().into_bump_slice(),
parse_state, parse_state,
module_ids, module_ids,
ident_ids_by_module, ident_ids_by_module,
@ -3683,9 +3643,11 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
let parsed_defs = match module_defs().parse(&arena, parse_state) { let parsed_defs = match module_defs().parse(&arena, parse_state) {
Ok((_, success, _state)) => success, Ok((_, success, _state)) => success,
Err((_, fail, _)) => { Err((_, fail, _)) => {
return Err(LoadingProblem::ParsingFailed( return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem(
fail.into_parse_problem(header.module_path, source), header.module_path,
)); header.header_src,
source,
)));
} }
}; };
@ -3811,12 +3773,7 @@ fn make_specializations<'a>(
// TODO: for now this final specialization pass is sequential, // TODO: for now this final specialization pass is sequential,
// with no parallelization at all. We should try to parallelize // with no parallelization at all. We should try to parallelize
// this, but doing so will require a redesign of Procs. // this, but doing so will require a redesign of Procs.
procs = roc_mono::ir::specialize_all( procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
&mut mono_env,
procs,
&mut layout_cache,
// &finished_info.vars_by_symbol,
);
let external_specializations_requested = procs.externals_we_need.clone(); let external_specializations_requested = procs.externals_we_need.clone();
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena); let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
@ -4180,3 +4137,179 @@ where
Ok(()) Ok(())
} }
fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
let src_lines: Vec<&str> = Vec::new();
let mut module_ids = ModuleIds::default();
let module_id = module_ids.get_or_insert(&"find module name somehow?".into());
let interns = Interns::default();
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
let report = match error {
io::ErrorKind::NotFound => {
let doc = alloc.stack(vec![
alloc.reflow(r"I am looking for this file, but it's not there:"),
alloc
.parser_suggestion(filename.to_str().unwrap())
.indent(4),
alloc.concat(vec![
alloc.reflow(r"Is the file supposed to be there? "),
alloc.reflow("Maybe there is a typo in the file name?"),
]),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "FILE NOT FOUND".to_string(),
}
}
io::ErrorKind::PermissionDenied => {
let doc = alloc.stack(vec![
alloc.reflow(r"I don't have the required permissions to read this file:"),
alloc
.parser_suggestion(filename.to_str().unwrap())
.indent(4),
alloc.concat(vec![
alloc.reflow(r"Is it the right file? Maybe change its permissions?")
]),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "PERMISSION DENIED".to_string(),
}
}
_ => {
let error = std::io::Error::from(error);
let formatted = format!("{}", error);
let doc = alloc.concat(vec![
alloc.reflow(r"I tried to read this file, but ran into a "),
alloc.text(formatted),
alloc.reflow(r" problem."),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "FILE PROBLEM".to_string(),
}
}
};
let mut buf = String::new();
let palette = DEFAULT_PALETTE;
report.render_color_terminal(&mut buf, &alloc, &palette);
buf
}
fn to_parse_problem_report<'a>(
problem: ParseProblem<'a, SyntaxError<'a>>,
mut module_ids: ModuleIds,
all_ident_ids: MutMap<ModuleId, IdentIds>,
) -> String {
use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE};
// TODO this is not in fact safe
let src = unsafe { from_utf8_unchecked(problem.bytes) };
let mut src_lines: Vec<&str> = problem.prefix.lines().collect();
src_lines.extend(src.lines().skip(1));
let module_id = module_ids.get_or_insert(&"find module name somehow?".into());
let interns = Interns {
module_ids,
all_ident_ids,
};
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
let starting_line = 0;
let report = parse_problem(&alloc, problem.filename.clone(), starting_line, problem);
let mut buf = String::new();
let palette = DEFAULT_PALETTE;
report.render_color_terminal(&mut buf, &alloc, &palette);
buf
}
fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> String {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
use PlatformPath::*;
// Report parsing and canonicalization problems
let interns = Interns::default();
let alloc = RocDocAllocator::new(&[], module_id, &interns);
let report = {
match other {
Valid(_) => unreachable!(),
NotSpecified => {
let doc = alloc.stack(vec![
alloc.reflow("I could not find a platform based on your input file."),
alloc.reflow(r"Does the module header contain an entry that looks like this:"),
alloc
.parser_suggestion(" packages { base: \"platform\" }")
.indent(4),
alloc.reflow("See also TODO."),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsInterface => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a interface file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsPkgConfig => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a package config file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
}
};
let palette = DEFAULT_PALETTE;
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
buf
}

View File

@ -0,0 +1,8 @@
interface MissingDep
exposes [ unit ]
imports [ ThisFileIsMissing ]
Unit : [ Unit ]
unit : Unit
unit = Unit

View File

@ -32,24 +32,50 @@ mod test_load {
// HELPERS // HELPERS
fn multiple_modules(files: Vec<(&str, &str)>) -> LoadedModule { fn multiple_modules(files: Vec<(&str, &str)>) -> Result<LoadedModule, String> {
multiple_modules_help(files).unwrap() use roc_load::file::LoadingProblem;
let arena = Bump::new();
let arena = &arena;
match multiple_modules_help(arena, files) {
Err(io_error) => panic!("IO trouble: {:?}", io_error),
Ok(Err(LoadingProblem::FormattedReport(buf))) => Err(buf),
Ok(Err(loading_problem)) => Err(format!("{:?}", loading_problem)),
Ok(Ok(mut loaded_module)) => {
let home = loaded_module.module_id;
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
);
assert_eq!(
loaded_module
.type_problems
.remove(&home)
.unwrap_or_default(),
Vec::new()
);
Ok(loaded_module)
}
}
} }
fn multiple_modules_help(mut files: Vec<(&str, &str)>) -> Result<LoadedModule, std::io::Error> { fn multiple_modules_help<'a>(
arena: &'a Bump,
mut files: Vec<(&str, &str)>,
) -> Result<Result<LoadedModule, roc_load::file::LoadingProblem<'a>>, std::io::Error> {
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use tempfile::tempdir; use tempfile::tempdir;
let arena = Bump::new();
let arena = &arena;
let stdlib = roc_builtins::std::standard_stdlib(); let stdlib = roc_builtins::std::standard_stdlib();
let mut file_handles: Vec<_> = Vec::new(); let mut file_handles: Vec<_> = Vec::new();
let exposed_types = MutMap::default(); let exposed_types = MutMap::default();
let loaded = {
// create a temporary directory // create a temporary directory
let dir = tempdir()?; let dir = tempdir()?;
@ -83,7 +109,7 @@ mod test_load {
roc_load::file::load_and_typecheck( roc_load::file::load_and_typecheck(
arena, arena,
full_file_path, full_file_path,
&stdlib, arena.alloc(stdlib),
dir.path(), dir.path(),
exposed_types, exposed_types,
8, 8,
@ -93,26 +119,7 @@ mod test_load {
dir.close()?; dir.close()?;
result Ok(result)
};
let mut loaded_module = loaded.expect("failed to load module");
let home = loaded_module.module_id;
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
);
assert_eq!(
loaded_module
.type_problems
.remove(&home)
.unwrap_or_default(),
Vec::new()
);
Ok(loaded_module)
} }
fn load_fixture( fn load_fixture(
@ -134,9 +141,9 @@ mod test_load {
); );
let mut loaded_module = match loaded { let mut loaded_module = match loaded {
Ok(x) => x, Ok(x) => x,
Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => { Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
println!("{}", report); println!("{}", report);
panic!(); panic!("{}", report);
} }
Err(e) => panic!("{:?}", e), Err(e) => panic!("{:?}", e),
}; };
@ -285,7 +292,8 @@ mod test_load {
), ),
), ),
]; ];
multiple_modules(modules);
assert!(multiple_modules(modules).is_ok());
} }
#[test] #[test]
@ -517,61 +525,69 @@ mod test_load {
); );
} }
// #[test] #[test]
// fn load_records() { fn parse_problem() {
// use roc::types::{ErrorType, Mismatch, Problem, TypeExt}; let modules = vec![(
"Main",
indoc!(
r#"
app "test-app" packages { blah: "./blah" } provides [ main ] to blah
// let subs_by_module = MutMap::default(); main = [
// let loaded_module = "#
// load_fixture("interface_with_deps", "Records", subs_by_module); ),
)];
// // NOTE: `a` here is unconstrained, so unifies with <type error> match multiple_modules(modules) {
// let expected_types = hashmap! { Err(report) => assert_eq!(
// "Records.intVal" => "a", report,
// }; indoc!(
"
\u{1b}[36m UNFINISHED LIST \u{1b}[0m
// let a = ErrorType::FlexVar("a".into()); I cannot find the end of this list:
// let mut record = SendMap::default(); \u{1b}[36m3\u{1b}[0m\u{1b}[36m\u{1b}[0m \u{1b}[37mmain = [\u{1b}[0m
// record.insert("x".into(), a); \u{1b}[31m^\u{1b}[0m
// let problem = Problem::Mismatch( You could change it to something like \u{1b}[33m[ 1, 2, 3 ]\u{1b}[0m or even just \u{1b}[33m[]\u{1b}[0m.
// Mismatch::TypeMismatch, Anything where there is an open and a close square bracket, and where
// ErrorType::Record(SendMap::default(), TypeExt::Closed), the elements of the list are separated by commas.
// ErrorType::Record(record, TypeExt::FlexOpen("b".into())),
// );
// assert_eq!(loaded_module.problems, vec![problem]); \u{1b}[4mNote\u{1b}[0m: I may be confused by indentation"
// assert_eq!(expected_types.len(), loaded_module.declarations.len()); )
),
Ok(_) => unreachable!("we expect failure here"),
}
}
// let mut subs = loaded_module.solved.into_inner(); #[test]
#[should_panic(
expected = "FileProblem { filename: \"tests/fixtures/build/interface_with_deps/invalid$name.roc\", error: NotFound }"
)]
fn file_not_found() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "invalid$name", subs_by_module);
// for decl in loaded_module.declarations { expect_types(
// let def = match decl { loaded_module,
// Declare(def) => def, hashmap! {
// rec_decl @ DeclareRec(_) => { "str" => "Str",
// panic!( },
// "Unexpected recursive def in module declarations: {:?}", );
// rec_decl }
// );
// }
// cycle @ InvalidCycle(_, _) => {
// panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
// }
// };
// for (symbol, expr_var) in def.pattern_vars { #[test]
// let content = subs.get(expr_var).content; #[should_panic(expected = "FILE NOT FOUND")]
fn imported_file_not_found() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("no_deps", "MissingDep", subs_by_module);
// name_all_type_vars(expr_var, &mut subs); expect_types(
loaded_module,
// let actual_str = content_to_string(content, &mut subs); hashmap! {
// let expected_type = expected_types.get(symbol.as_str()).unwrap_or_else(|| { "str" => "Str",
// panic!("Defs included an unexpected symbol: {:?}", symbol) },
// }); );
}
// assert_eq!((&symbol, expected_type), (&symbol, &actual_str.as_str()));
// }
// }
// }
} }

View File

@ -117,21 +117,21 @@ impl From<InlinableString> for ModuleName {
} }
} }
impl Into<InlinableString> for ModuleName { impl From<ModuleName> for InlinableString {
fn into(self) -> InlinableString { fn from(name: ModuleName) -> Self {
self.0 name.0
} }
} }
impl<'a> Into<&'a InlinableString> for &'a ModuleName { impl<'a> From<&'a ModuleName> for &'a InlinableString {
fn into(self) -> &'a InlinableString { fn from(name: &'a ModuleName) -> Self {
&self.0 &name.0
} }
} }
impl<'a> Into<Box<str>> for ModuleName { impl From<ModuleName> for Box<str> {
fn into(self) -> Box<str> { fn from(name: ModuleName) -> Self {
self.0.to_string().into() name.0.to_string().into()
} }
} }
@ -197,9 +197,9 @@ impl<'a> From<String> for Lowercase {
} }
} }
impl Into<InlinableString> for Lowercase { impl From<Lowercase> for InlinableString {
fn into(self) -> InlinableString { fn from(lowercase: Lowercase) -> Self {
self.0 lowercase.0
} }
} }
@ -234,21 +234,21 @@ impl From<InlinableString> for Ident {
} }
} }
impl Into<InlinableString> for Ident { impl From<Ident> for InlinableString {
fn into(self) -> InlinableString { fn from(ident: Ident) -> Self {
self.0 ident.0
} }
} }
impl<'a> Into<&'a InlinableString> for &'a Ident { impl<'a> From<&'a Ident> for &'a InlinableString {
fn into(self) -> &'a InlinableString { fn from(ident: &'a Ident) -> Self {
&self.0 &ident.0
} }
} }
impl<'a> Into<Box<str>> for Ident { impl From<Ident> for Box<str> {
fn into(self) -> Box<str> { fn from(ident: Ident) -> Self {
self.0.to_string().into() ident.0.to_string().into()
} }
} }

View File

@ -1,6 +1,6 @@
#![warn(clippy::all, clippy::dbg_macro)] #![warn(clippy::all, clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod ident; pub mod ident;
pub mod low_level; pub mod low_level;

View File

@ -34,6 +34,7 @@ pub enum LowLevel {
ListWalk, ListWalk,
ListWalkBackwards, ListWalkBackwards,
ListSum, ListSum,
ListProduct,
ListKeepOks, ListKeepOks,
ListKeepErrs, ListKeepErrs,
DictSize, DictSize,
@ -71,6 +72,7 @@ pub enum LowLevel {
NumSin, NumSin,
NumCos, NumCos,
NumSqrtUnchecked, NumSqrtUnchecked,
NumLogUnchecked,
NumRound, NumRound,
NumToFloat, NumToFloat,
NumPow, NumPow,

View File

@ -804,59 +804,60 @@ define_builtins! {
43 NUM_MOD_INT: "modInt" 43 NUM_MOD_INT: "modInt"
44 NUM_MOD_FLOAT: "modFloat" 44 NUM_MOD_FLOAT: "modFloat"
45 NUM_SQRT: "sqrt" 45 NUM_SQRT: "sqrt"
46 NUM_ROUND: "round" 46 NUM_LOG: "log"
47 NUM_COMPARE: "compare" 47 NUM_ROUND: "round"
48 NUM_POW: "pow" 48 NUM_COMPARE: "compare"
49 NUM_CEILING: "ceiling" 49 NUM_POW: "pow"
50 NUM_POW_INT: "powInt" 50 NUM_CEILING: "ceiling"
51 NUM_FLOOR: "floor" 51 NUM_POW_INT: "powInt"
52 NUM_ADD_WRAP: "addWrap" 52 NUM_FLOOR: "floor"
53 NUM_ADD_CHECKED: "addChecked" 53 NUM_ADD_WRAP: "addWrap"
54 NUM_ATAN: "atan" 54 NUM_ADD_CHECKED: "addChecked"
55 NUM_ACOS: "acos" 55 NUM_ATAN: "atan"
56 NUM_ASIN: "asin" 56 NUM_ACOS: "acos"
57 NUM_AT_SIGNED128: "@Signed128" 57 NUM_ASIN: "asin"
58 NUM_SIGNED128: "Signed128" imported 58 NUM_AT_SIGNED128: "@Signed128"
59 NUM_AT_SIGNED64: "@Signed64" 59 NUM_SIGNED128: "Signed128" imported
60 NUM_SIGNED64: "Signed64" imported 60 NUM_AT_SIGNED64: "@Signed64"
61 NUM_AT_SIGNED32: "@Signed32" 61 NUM_SIGNED64: "Signed64" imported
62 NUM_SIGNED32: "Signed32" imported 62 NUM_AT_SIGNED32: "@Signed32"
63 NUM_AT_SIGNED16: "@Signed16" 63 NUM_SIGNED32: "Signed32" imported
64 NUM_SIGNED16: "Signed16" imported 64 NUM_AT_SIGNED16: "@Signed16"
65 NUM_AT_SIGNED8: "@Signed8" 65 NUM_SIGNED16: "Signed16" imported
66 NUM_SIGNED8: "Signed8" imported 66 NUM_AT_SIGNED8: "@Signed8"
67 NUM_AT_UNSIGNED128: "@Unsigned128" 67 NUM_SIGNED8: "Signed8" imported
68 NUM_UNSIGNED128: "Unsigned128" imported 68 NUM_AT_UNSIGNED128: "@Unsigned128"
69 NUM_AT_UNSIGNED64: "@Unsigned64" 69 NUM_UNSIGNED128: "Unsigned128" imported
70 NUM_UNSIGNED64: "Unsigned64" imported 70 NUM_AT_UNSIGNED64: "@Unsigned64"
71 NUM_AT_UNSIGNED32: "@Unsigned32" 71 NUM_UNSIGNED64: "Unsigned64" imported
72 NUM_UNSIGNED32: "Unsigned32" imported 72 NUM_AT_UNSIGNED32: "@Unsigned32"
73 NUM_AT_UNSIGNED16: "@Unsigned16" 73 NUM_UNSIGNED32: "Unsigned32" imported
74 NUM_UNSIGNED16: "Unsigned16" imported 74 NUM_AT_UNSIGNED16: "@Unsigned16"
75 NUM_AT_UNSIGNED8: "@Unsigned8" 75 NUM_UNSIGNED16: "Unsigned16" imported
76 NUM_UNSIGNED8: "Unsigned8" imported 76 NUM_AT_UNSIGNED8: "@Unsigned8"
77 NUM_AT_BINARY64: "@Binary64" 77 NUM_UNSIGNED8: "Unsigned8" imported
78 NUM_BINARY64: "Binary64" imported 78 NUM_AT_BINARY64: "@Binary64"
79 NUM_AT_BINARY32: "@Binary32" 79 NUM_BINARY64: "Binary64" imported
80 NUM_BINARY32: "Binary32" imported 80 NUM_AT_BINARY32: "@Binary32"
81 NUM_BITWISE_AND: "bitwiseAnd" 81 NUM_BINARY32: "Binary32" imported
82 NUM_BITWISE_XOR: "bitwiseXor" 82 NUM_BITWISE_AND: "bitwiseAnd"
83 NUM_BITWISE_OR: "bitwiseOr" 83 NUM_BITWISE_XOR: "bitwiseXor"
84 NUM_SHIFT_LEFT: "shiftLeftBy" 84 NUM_BITWISE_OR: "bitwiseOr"
85 NUM_SHIFT_RIGHT: "shiftRightBy" 85 NUM_SHIFT_LEFT: "shiftLeftBy"
86 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" 86 NUM_SHIFT_RIGHT: "shiftRightBy"
87 NUM_SUB_WRAP: "subWrap" 87 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
88 NUM_SUB_CHECKED: "subChecked" 88 NUM_SUB_WRAP: "subWrap"
89 NUM_MUL_WRAP: "mulWrap" 89 NUM_SUB_CHECKED: "subChecked"
90 NUM_MUL_CHECKED: "mulChecked" 90 NUM_MUL_WRAP: "mulWrap"
91 NUM_INT: "Int" imported 91 NUM_MUL_CHECKED: "mulChecked"
92 NUM_FLOAT: "Float" imported 92 NUM_INT: "Int" imported
93 NUM_AT_NATURAL: "@Natural" 93 NUM_FLOAT: "Float" imported
94 NUM_NATURAL: "Natural" imported 94 NUM_AT_NATURAL: "@Natural"
95 NUM_NAT: "Nat" imported 95 NUM_NATURAL: "Natural" imported
96 NUM_INT_CAST: "intCast" 96 NUM_NAT: "Nat" imported
97 NUM_MAX_I128: "maxI128" 97 NUM_INT_CAST: "intCast"
98 NUM_IS_MULTIPLE_OF: "isMultipleOf" 98 NUM_MAX_I128: "maxI128"
99 NUM_IS_MULTIPLE_OF: "isMultipleOf"
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
@ -913,6 +914,7 @@ define_builtins! {
23 LIST_MAP_WITH_INDEX: "mapWithIndex" 23 LIST_MAP_WITH_INDEX: "mapWithIndex"
24 LIST_MAP2: "map2" 24 LIST_MAP2: "map2"
25 LIST_MAP3: "map3" 25 LIST_MAP3: "map3"
26 LIST_PRODUCT: "product"
} }
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

View File

@ -22,7 +22,7 @@ pub fn infer_borrow<'a>(
}; };
for (key, proc) in procs { for (key, proc) in procs {
param_map.visit_proc(arena, proc, key.clone()); param_map.visit_proc(arena, proc, *key);
} }
let mut env = BorrowInfState { let mut env = BorrowInfState {
@ -47,7 +47,7 @@ pub fn infer_borrow<'a>(
// mutually recursive functions (or just make all their arguments owned) // mutually recursive functions (or just make all their arguments owned)
for (key, proc) in procs { for (key, proc) in procs {
env.collect_proc(proc, key.1.clone()); env.collect_proc(proc, key.1);
} }
if !env.modified { if !env.modified {
@ -113,7 +113,7 @@ impl<'a> ParamMap<'a> {
Vec::from_iter_in( Vec::from_iter_in(
ps.iter().map(|p| Param { ps.iter().map(|p| Param {
borrow: p.layout.is_refcounted(), borrow: p.layout.is_refcounted(),
layout: p.layout.clone(), layout: p.layout,
symbol: p.symbol, symbol: p.symbol,
}), }),
arena, arena,
@ -125,7 +125,7 @@ impl<'a> ParamMap<'a> {
Vec::from_iter_in( Vec::from_iter_in(
ps.iter().map(|(layout, symbol)| Param { ps.iter().map(|(layout, symbol)| Param {
borrow: should_borrow_layout(layout), borrow: should_borrow_layout(layout),
layout: layout.clone(), layout: *layout,
symbol: *symbol, symbol: *symbol,
}), }),
arena, arena,
@ -140,7 +140,7 @@ impl<'a> ParamMap<'a> {
Vec::from_iter_in( Vec::from_iter_in(
ps.iter().map(|(layout, symbol)| Param { ps.iter().map(|(layout, symbol)| Param {
borrow: false, borrow: false,
layout: layout.clone(), layout: *layout,
symbol: *symbol, symbol: *symbol,
}), }),
arena, arena,
@ -367,7 +367,7 @@ impl<'a> BorrowInfState<'a> {
name, full_layout, .. name, full_layout, ..
} => { } => {
// get the borrow signature of the applied function // get the borrow signature of the applied function
match self.param_map.get_symbol(*name, full_layout.clone()) { match self.param_map.get_symbol(*name, *full_layout) {
Some(ps) => { Some(ps) => {
// the return value will be owned // the return value will be owned
self.own_var(z); self.own_var(z);
@ -499,7 +499,7 @@ impl<'a> BorrowInfState<'a> {
if self.current_proc == *g && x == *z { if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known) // anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine // can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g, full_layout.clone()) { if let Some(ps) = self.param_map.get_symbol(*g, *full_layout) {
self.own_params_using_args(ys, ps) self.own_params_using_args(ys, ps)
} }
} }
@ -541,14 +541,14 @@ impl<'a> BorrowInfState<'a> {
Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => { Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => {
// ensure that the function pointed to is in the param map // ensure that the function pointed to is in the param map
if let Some(params) = self.param_map.get_symbol(*fsymbol, layout.clone()) { if let Some(params) = self.param_map.get_symbol(*fsymbol, *layout) {
self.param_map self.param_map
.items .items
.insert(Key::Declaration(*x, layout.clone()), params); .insert(Key::Declaration(*x, *layout), params);
} }
self.collect_stmt(b); self.collect_stmt(b);
self.preserve_tail_call(*x, &Expr::FunctionPointer(*fsymbol, layout.clone()), b); self.preserve_tail_call(*x, &Expr::FunctionPointer(*fsymbol, *layout), b);
} }
Let(x, v, _, b) => { Let(x, v, _, b) => {
@ -657,7 +657,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]), ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]), ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
ListSum => arena.alloc_slice_copy(&[borrowed]), ListSum | ListProduct => arena.alloc_slice_copy(&[borrowed]),
// TODO when we have lists with capacity (if ever) // TODO when we have lists with capacity (if ever)
// List.append should own its first argument // List.append should own its first argument
@ -671,10 +671,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
| NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy
| NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumIntCast => { | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin
arena.alloc_slice_copy(&[irrelevant]) | NumIntCast => arena.alloc_slice_copy(&[irrelevant]),
}
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
StrFromUtf8 => arena.alloc_slice_copy(&[owned]), StrFromUtf8 => arena.alloc_slice_copy(&[owned]),
StrToBytes => arena.alloc_slice_copy(&[owned]), StrToBytes => arena.alloc_slice_copy(&[owned]),

View File

@ -22,7 +22,7 @@ pub fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> Decision
.into_iter() .into_iter()
.map(|(guard, pattern, index)| Branch { .map(|(guard, pattern, index)| Branch {
goal: index, goal: index,
patterns: vec![(Path::Empty, guard, pattern)], patterns: vec![(Vec::new(), guard, pattern)],
}) })
.collect(); .collect();
@ -52,7 +52,7 @@ impl<'a> Guard<'a> {
pub enum DecisionTree<'a> { pub enum DecisionTree<'a> {
Match(Label), Match(Label),
Decision { Decision {
path: Path, path: Vec<PathInstruction>,
edges: Vec<(Test<'a>, DecisionTree<'a>)>, edges: Vec<(Test<'a>, DecisionTree<'a>)>,
default: Option<Box<DecisionTree<'a>>>, default: Option<Box<DecisionTree<'a>>>,
}, },
@ -132,23 +132,12 @@ impl<'a> Hash for Test<'a> {
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Path {
Index {
index: u64,
tag_id: u8,
path: Box<Path>,
},
Unbox(Box<Path>),
Empty,
}
// ACTUALLY BUILD DECISION TREES // ACTUALLY BUILD DECISION TREES
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
struct Branch<'a> { struct Branch<'a> {
goal: Label, goal: Label,
patterns: Vec<(Path, Guard<'a>, Pattern<'a>)>, patterns: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
} }
fn to_decision_tree(raw_branches: Vec<Branch>) -> DecisionTree { fn to_decision_tree(raw_branches: Vec<Branch>) -> DecisionTree {
@ -172,13 +161,13 @@ fn to_decision_tree(raw_branches: Vec<Branch>) -> DecisionTree {
decision_tree.clone() decision_tree.clone()
} }
(_, None) => DecisionTree::Decision { (_, None) => DecisionTree::Decision {
path, path: path.clone(),
edges: decision_edges, edges: decision_edges,
default: None, default: None,
}, },
(None, Some(_)) => to_decision_tree(fallback), (None, Some(_)) => to_decision_tree(fallback),
_ => DecisionTree::Decision { _ => DecisionTree::Decision {
path, path: path.clone(),
edges: decision_edges, edges: decision_edges,
default: Some(Box::new(to_decision_tree(fallback))), default: Some(Box::new(to_decision_tree(fallback))),
}, },
@ -218,8 +207,8 @@ fn flatten_patterns(branch: Branch) -> Branch {
} }
fn flatten<'a>( fn flatten<'a>(
path_pattern: (Path, Guard<'a>, Pattern<'a>), path_pattern: (Vec<PathInstruction>, Guard<'a>, Pattern<'a>),
path_patterns: &mut Vec<(Path, Guard<'a>, Pattern<'a>)>, path_patterns: &mut Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
) { ) {
match path_pattern.2 { match path_pattern.2 {
Pattern::AppliedTag { Pattern::AppliedTag {
@ -241,8 +230,9 @@ fn flatten<'a>(
// Theory: unbox doesn't have any value for us, because one-element tag unions // Theory: unbox doesn't have any value for us, because one-element tag unions
// don't store the tag anyway. // don't store the tag anyway.
if arguments.len() == 1 { if arguments.len() == 1 {
// NOTE here elm will unbox, but we don't use that
path_patterns.push(( path_patterns.push((
Path::Unbox(Box::new(path)), path,
path_pattern.1.clone(), path_pattern.1.clone(),
Pattern::AppliedTag { Pattern::AppliedTag {
union, union,
@ -254,13 +244,15 @@ fn flatten<'a>(
)); ));
} else { } else {
for (index, (arg_pattern, _)) in arguments.iter().enumerate() { for (index, (arg_pattern, _)) in arguments.iter().enumerate() {
flatten( let mut new_path = path.clone();
( new_path.push(PathInstruction {
Path::Index {
index: index as u64, index: index as u64,
tag_id, tag_id,
path: Box::new(path.clone()), });
},
flatten(
(
new_path,
// same guard here? // same guard here?
path_pattern.1.clone(), path_pattern.1.clone(),
arg_pattern.clone(), arg_pattern.clone(),
@ -283,7 +275,7 @@ fn flatten<'a>(
/// path. If that is the case we give the resulting label and a mapping from free /// path. If that is the case we give the resulting label and a mapping from free
/// variables to "how to get their value". So a pattern like (Just (x,_)) will give /// variables to "how to get their value". So a pattern like (Just (x,_)) will give
/// us something like ("x" => value.0.0) /// us something like ("x" => value.0.0)
fn check_for_match(branches: &Vec<Branch>) -> Option<Label> { fn check_for_match(branches: &[Branch]) -> Option<Label> {
match branches.get(0) { match branches.get(0) {
Some(Branch { goal, patterns }) Some(Branch { goal, patterns })
if patterns if patterns
@ -300,7 +292,7 @@ fn check_for_match(branches: &Vec<Branch>) -> Option<Label> {
fn gather_edges<'a>( fn gather_edges<'a>(
branches: Vec<Branch<'a>>, branches: Vec<Branch<'a>>,
path: &Path, path: &[PathInstruction],
) -> (Vec<(Test<'a>, Vec<Branch<'a>>)>, Vec<Branch<'a>>) { ) -> (Vec<(Test<'a>, Vec<Branch<'a>>)>, Vec<Branch<'a>>) {
let relevant_tests = tests_at_path(path, &branches); let relevant_tests = tests_at_path(path, &branches);
@ -326,7 +318,7 @@ fn gather_edges<'a>(
/// FIND RELEVANT TESTS /// FIND RELEVANT TESTS
fn tests_at_path<'a>(selected_path: &Path, branches: &[Branch<'a>]) -> Vec<Test<'a>> { fn tests_at_path<'a>(selected_path: &[PathInstruction], branches: &[Branch<'a>]) -> Vec<Test<'a>> {
// NOTE the ordering of the result is important! // NOTE the ordering of the result is important!
let mut all_tests = Vec::new(); let mut all_tests = Vec::new();
@ -360,7 +352,11 @@ fn tests_at_path<'a>(selected_path: &Path, branches: &[Branch<'a>]) -> Vec<Test<
unique unique
} }
fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut Vec<Test<'a>>) { fn test_at_path<'a>(
selected_path: &[PathInstruction],
branch: &Branch<'a>,
all_tests: &mut Vec<Test<'a>>,
) {
use Pattern::*; use Pattern::*;
use Test::*; use Test::*;
@ -413,10 +409,10 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
for destruct in destructs { for destruct in destructs {
match &destruct.typ { match &destruct.typ {
DestructType::Guard(guard) => { DestructType::Guard(guard) => {
arguments.push((guard.clone(), destruct.layout.clone())); arguments.push((guard.clone(), destruct.layout));
} }
DestructType::Required(_) => { DestructType::Required(_) => {
arguments.push((Pattern::Underscore, destruct.layout.clone())); arguments.push((Pattern::Underscore, destruct.layout));
} }
} }
} }
@ -469,7 +465,7 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
/// BUILD EDGES /// BUILD EDGES
fn edges_for<'a>( fn edges_for<'a>(
path: &Path, path: &[PathInstruction],
branches: Vec<Branch<'a>>, branches: Vec<Branch<'a>>,
test: Test<'a>, test: Test<'a>,
) -> (Test<'a>, Vec<Branch<'a>>) { ) -> (Test<'a>, Vec<Branch<'a>>) {
@ -484,7 +480,7 @@ fn edges_for<'a>(
fn to_relevant_branch<'a>( fn to_relevant_branch<'a>(
test: &Test<'a>, test: &Test<'a>,
path: &Path, path: &[PathInstruction],
branch: &Branch<'a>, branch: &Branch<'a>,
new_branches: &mut Vec<Branch<'a>>, new_branches: &mut Vec<Branch<'a>>,
) { ) {
@ -524,9 +520,9 @@ fn to_relevant_branch<'a>(
fn to_relevant_branch_help<'a>( fn to_relevant_branch_help<'a>(
test: &Test<'a>, test: &Test<'a>,
path: &Path, path: &[PathInstruction],
mut start: Vec<(Path, Guard<'a>, Pattern<'a>)>, mut start: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
end: Vec<(Path, Guard<'a>, Pattern<'a>)>, end: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
branch: &Branch<'a>, branch: &Branch<'a>,
guard: Guard<'a>, guard: Guard<'a>,
pattern: Pattern<'a>, pattern: Pattern<'a>,
@ -550,15 +546,13 @@ fn to_relevant_branch_help<'a>(
DestructType::Required(_) => Pattern::Underscore, DestructType::Required(_) => Pattern::Underscore,
}; };
( let mut new_path = path.to_vec();
Path::Index { new_path.push(PathInstruction {
index: index as u64, index: index as u64,
tag_id: *tag_id, tag_id: *tag_id,
path: Box::new(path.clone()), });
},
Guard::NoGuard, (new_path, Guard::NoGuard, pattern)
pattern,
)
}); });
start.extend(sub_positions); start.extend(sub_positions);
start.extend(end); start.extend(end);
@ -597,26 +591,21 @@ fn to_relevant_branch_help<'a>(
debug_assert_eq!(arguments.len(), 1); debug_assert_eq!(arguments.len(), 1);
let arg = arguments[0].clone(); let arg = arguments[0].clone();
{ {
start.push(( // NOTE here elm unboxes, but we ignore that
Path::Unbox(Box::new(path.clone())), // Path::Unbox(Box::new(path.clone()))
guard, start.push((path.to_vec(), guard, arg.0));
arg.0,
));
start.extend(end); start.extend(end);
} }
} }
Wrapped::RecordOrSingleTagUnion => { Wrapped::RecordOrSingleTagUnion => {
let sub_positions = arguments.into_iter().enumerate().map( let sub_positions = arguments.into_iter().enumerate().map(
|(index, (pattern, _))| { |(index, (pattern, _))| {
( let mut new_path = path.to_vec();
Path::Index { new_path.push(PathInstruction {
index: index as u64, index: index as u64,
tag_id, tag_id,
path: Box::new(path.clone()), });
}, (new_path, Guard::NoGuard, pattern)
Guard::NoGuard,
pattern,
)
}, },
); );
start.extend(sub_positions); start.extend(sub_positions);
@ -625,15 +614,12 @@ fn to_relevant_branch_help<'a>(
Wrapped::MultiTagUnion => { Wrapped::MultiTagUnion => {
let sub_positions = arguments.into_iter().enumerate().map( let sub_positions = arguments.into_iter().enumerate().map(
|(index, (pattern, _))| { |(index, (pattern, _))| {
( let mut new_path = path.to_vec();
Path::Index { new_path.push(PathInstruction {
index: 1 + index as u64, index: 1 + index as u64,
tag_id, tag_id,
path: Box::new(path.clone()), });
}, (new_path, Guard::NoGuard, pattern)
Guard::NoGuard,
pattern,
)
}, },
); );
start.extend(sub_positions); start.extend(sub_positions);
@ -715,22 +701,22 @@ fn to_relevant_branch_help<'a>(
enum Extract<'a> { enum Extract<'a> {
NotFound, NotFound,
Found { Found {
start: Vec<(Path, Guard<'a>, Pattern<'a>)>, start: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
found_pattern: (Guard<'a>, Pattern<'a>), found_pattern: (Guard<'a>, Pattern<'a>),
end: Vec<(Path, Guard<'a>, Pattern<'a>)>, end: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
}, },
} }
fn extract<'a>( fn extract<'a>(
selected_path: &Path, selected_path: &[PathInstruction],
path_patterns: Vec<(Path, Guard<'a>, Pattern<'a>)>, path_patterns: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
) -> Extract<'a> { ) -> Extract<'a> {
let mut start = Vec::new(); let mut start = Vec::new();
// TODO potential ordering problem // TODO potential ordering problem
let mut it = path_patterns.into_iter(); let mut it = path_patterns.into_iter();
while let Some(current) = it.next() { while let Some(current) = it.next() {
if &current.0 == selected_path { if current.0 == selected_path {
return Extract::Found { return Extract::Found {
start, start,
found_pattern: (current.1, current.2), found_pattern: (current.1, current.2),
@ -746,7 +732,7 @@ fn extract<'a>(
/// FIND IRRELEVANT BRANCHES /// FIND IRRELEVANT BRANCHES
fn is_irrelevant_to<'a>(selected_path: &Path, branch: &Branch<'a>) -> bool { fn is_irrelevant_to<'a>(selected_path: &[PathInstruction], branch: &Branch<'a>) -> bool {
match branch match branch
.patterns .patterns
.iter() .iter()
@ -775,7 +761,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
/// PICK A PATH /// PICK A PATH
fn pick_path<'a>(branches: &'a [Branch]) -> &'a Path { fn pick_path<'a>(branches: &'a [Branch]) -> &'a Vec<PathInstruction> {
let mut all_paths = Vec::with_capacity(branches.len()); let mut all_paths = Vec::with_capacity(branches.len());
// is choice path // is choice path
@ -804,9 +790,12 @@ fn pick_path<'a>(branches: &'a [Branch]) -> &'a Path {
} }
} }
fn bests_by_small_branching_factor<'a, I>(branches: &[Branch], mut all_paths: I) -> Vec<&'a Path> fn bests_by_small_branching_factor<'a, I>(
branches: &[Branch],
mut all_paths: I,
) -> Vec<&'a Vec<PathInstruction>>
where where
I: Iterator<Item = &'a Path>, I: Iterator<Item = &'a Vec<PathInstruction>>,
{ {
match all_paths.next() { match all_paths.next() {
None => panic!("Cannot choose the best of zero paths. This should never happen."), None => panic!("Cannot choose the best of zero paths. This should never happen."),
@ -836,9 +825,12 @@ where
} }
} }
fn bests_by_small_defaults<'a, I>(branches: &[Branch], mut all_paths: I) -> Vec<&'a Path> fn bests_by_small_defaults<'a, I>(
branches: &[Branch],
mut all_paths: I,
) -> Vec<&'a Vec<PathInstruction>>
where where
I: Iterator<Item = &'a Path>, I: Iterator<Item = &'a Vec<PathInstruction>>,
{ {
match all_paths.next() { match all_paths.next() {
None => panic!("Cannot choose the best of zero paths. This should never happen."), None => panic!("Cannot choose the best of zero paths. This should never happen."),
@ -870,7 +862,7 @@ where
/// PATH PICKING HEURISTICS /// PATH PICKING HEURISTICS
fn small_defaults(branches: &[Branch], path: &Path) -> usize { fn small_defaults(branches: &[Branch], path: &[PathInstruction]) -> usize {
branches branches
.iter() .iter()
.filter(|b| is_irrelevant_to(path, b)) .filter(|b| is_irrelevant_to(path, b))
@ -878,8 +870,7 @@ fn small_defaults(branches: &[Branch], path: &Path) -> usize {
.sum() .sum()
} }
fn small_branching_factor(branches: &[Branch], path: &Path) -> usize { fn small_branching_factor(branches: &[Branch], path: &[PathInstruction]) -> usize {
// TODO remove clone
let (edges, fallback) = gather_edges(branches.to_vec(), path); let (edges, fallback) = gather_edges(branches.to_vec(), path);
edges.len() + (if fallback.is_empty() { 0 } else { 1 }) edges.len() + (if fallback.is_empty() { 0 } else { 1 })
@ -889,12 +880,12 @@ fn small_branching_factor(branches: &[Branch], path: &Path) -> usize {
enum Decider<'a, T> { enum Decider<'a, T> {
Leaf(T), Leaf(T),
Chain { Chain {
test_chain: Vec<(Path, Test<'a>)>, test_chain: Vec<(Vec<PathInstruction>, Test<'a>)>,
success: Box<Decider<'a, T>>, success: Box<Decider<'a, T>>,
failure: Box<Decider<'a, T>>, failure: Box<Decider<'a, T>>,
}, },
FanOut { FanOut {
path: Path, path: Vec<PathInstruction>,
tests: Vec<(Test<'a>, Decider<'a, T>)>, tests: Vec<(Test<'a>, Decider<'a, T>)>,
fallback: Box<Decider<'a, T>>, fallback: Box<Decider<'a, T>>,
}, },
@ -973,49 +964,22 @@ pub fn optimize_when<'a>(
stmt stmt
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct PathInstruction { pub struct PathInstruction {
index: u64, index: u64,
tag_id: u8, tag_id: u8,
} }
fn reverse_path(mut path: &Path) -> Vec<PathInstruction> {
let mut result = Vec::new();
loop {
match path {
Path::Unbox(nested) => {
path = nested;
}
Path::Empty => break,
Path::Index {
index,
tag_id,
path: nested,
} => {
result.push(PathInstruction {
index: *index,
tag_id: *tag_id,
});
path = nested;
}
}
}
result.reverse();
result
}
fn path_to_expr_help<'a>( fn path_to_expr_help<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
mut symbol: Symbol, mut symbol: Symbol,
path: &Path, path: &[PathInstruction],
mut layout: Layout<'a>, mut layout: Layout<'a>,
) -> (Symbol, StoresVec<'a>, Layout<'a>) { ) -> (Symbol, StoresVec<'a>, Layout<'a>) {
let mut stores = bumpalo::collections::Vec::new_in(env.arena); let mut stores = bumpalo::collections::Vec::new_in(env.arena);
let instructions = reverse_path(path); // let instructions = reverse_path(path);
let instructions = path;
let mut it = instructions.iter().peekable(); let mut it = instructions.iter().peekable();
while let Some(PathInstruction { index, tag_id }) = it.next() { while let Some(PathInstruction { index, tag_id }) = it.next() {
@ -1027,13 +991,12 @@ fn path_to_expr_help<'a>(
debug_assert_eq!(*tag_id, 0); debug_assert_eq!(*tag_id, 0);
debug_assert!(it.peek().is_none()); debug_assert!(it.peek().is_none());
let field_layouts = vec![layout.clone()]; let field_layouts = vec![layout];
debug_assert!(*index < field_layouts.len() as u64); debug_assert!(*index < field_layouts.len() as u64);
debug_assert_eq!(field_layouts.len(), 1); debug_assert_eq!(field_layouts.len(), 1);
let inner_layout = field_layouts[*index as usize].clone();
let inner_expr = Expr::AccessAtIndex { let inner_expr = Expr::AccessAtIndex {
index: *index, index: *index,
field_layouts: env.arena.alloc(field_layouts), field_layouts: env.arena.alloc(field_layouts),
@ -1042,7 +1005,8 @@ fn path_to_expr_help<'a>(
}; };
symbol = env.unique_symbol(); symbol = env.unique_symbol();
stores.push((symbol, inner_layout.clone(), inner_expr)); let inner_layout = layout;
stores.push((symbol, inner_layout, inner_expr));
break; break;
} }
@ -1096,7 +1060,7 @@ fn path_to_expr_help<'a>(
} }
Layout::Struct(layouts) => layouts, Layout::Struct(layouts) => layouts,
other => env.arena.alloc([other.clone()]), other => env.arena.alloc([*other]),
}; };
debug_assert!( debug_assert!(
@ -1109,8 +1073,8 @@ fn path_to_expr_help<'a>(
); );
let inner_layout = match &field_layouts[index as usize] { let inner_layout = match &field_layouts[index as usize] {
Layout::RecursivePointer => layout.clone(), Layout::RecursivePointer => layout,
other => other.clone(), other => *other,
}; };
let inner_expr = Expr::AccessAtIndex { let inner_expr = Expr::AccessAtIndex {
@ -1121,7 +1085,7 @@ fn path_to_expr_help<'a>(
}; };
symbol = env.unique_symbol(); symbol = env.unique_symbol();
stores.push((symbol, inner_layout.clone(), inner_expr)); stores.push((symbol, inner_layout, inner_expr));
layout = inner_layout; layout = inner_layout;
} }
@ -1135,11 +1099,11 @@ fn test_to_equality<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
cond_symbol: Symbol, cond_symbol: Symbol,
cond_layout: &Layout<'a>, cond_layout: &Layout<'a>,
path: &Path, path: &[PathInstruction],
test: Test<'a>, test: Test<'a>,
) -> (StoresVec<'a>, Symbol, Symbol, Layout<'a>) { ) -> (StoresVec<'a>, Symbol, Symbol, Layout<'a>) {
let (rhs_symbol, mut stores, _layout) = let (rhs_symbol, mut stores, _layout) =
path_to_expr_help(env, cond_symbol, &path, cond_layout.clone()); path_to_expr_help(env, cond_symbol, &path, *cond_layout);
match test { match test {
Test::IsCtor { Test::IsCtor {
@ -1273,7 +1237,7 @@ fn stores_and_condition<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
cond_symbol: Symbol, cond_symbol: Symbol,
cond_layout: &Layout<'a>, cond_layout: &Layout<'a>,
test_chain: Vec<(Path, Test<'a>)>, test_chain: Vec<(Vec<PathInstruction>, Test<'a>)>,
) -> (Tests<'a>, Option<(Symbol, JoinPointId, Stmt<'a>)>) { ) -> (Tests<'a>, Option<(Symbol, JoinPointId, Stmt<'a>)>) {
let mut tests = Vec::with_capacity(test_chain.len()); let mut tests = Vec::with_capacity(test_chain.len());
@ -1399,12 +1363,12 @@ fn compile_test_help<'a>(
} => { } => {
let pass_info = BranchInfo::Constructor { let pass_info = BranchInfo::Constructor {
scrutinee, scrutinee,
layout: layout.clone(), layout,
tag_id: pass, tag_id: pass,
}; };
let fail_info = BranchInfo::Constructor { let fail_info = BranchInfo::Constructor {
scrutinee, scrutinee,
layout: layout.clone(), layout,
tag_id: fail, tag_id: fail,
}; };
@ -1418,7 +1382,7 @@ fn compile_test_help<'a>(
} => { } => {
let pass_info = BranchInfo::Constructor { let pass_info = BranchInfo::Constructor {
scrutinee, scrutinee,
layout: layout.clone(), layout,
tag_id, tag_id,
}; };
@ -1473,11 +1437,11 @@ fn compile_tests<'a>(
// the guard is the final thing that we check, so needs to be layered on first! // the guard is the final thing that we check, so needs to be layered on first!
if let Some((_, id, stmt)) = opt_guard { if let Some((_, id, stmt)) = opt_guard {
cond = compile_guard(env, ret_layout.clone(), id, arena.alloc(stmt), fail, cond); cond = compile_guard(env, ret_layout, id, arena.alloc(stmt), fail, cond);
} }
for (new_stores, lhs, rhs, _layout) in tests.into_iter() { for (new_stores, lhs, rhs, _layout) in tests.into_iter() {
cond = compile_test(env, ret_layout.clone(), new_stores, lhs, rhs, fail, cond); cond = compile_test(env, ret_layout, new_stores, lhs, rhs, fail, cond);
} }
cond cond
} }
@ -1501,22 +1465,22 @@ impl<'a> ConstructorKnown<'a> {
fn from_test_chain( fn from_test_chain(
cond_symbol: Symbol, cond_symbol: Symbol,
cond_layout: &Layout<'a>, cond_layout: &Layout<'a>,
test_chain: &[(Path, Test)], test_chain: &[(Vec<PathInstruction>, Test)],
) -> Self { ) -> Self {
match test_chain { match test_chain {
[(path, test)] => match (path, test) { [(path, test)] => match test {
(Path::Empty, Test::IsCtor { tag_id, union, .. }) => { Test::IsCtor { tag_id, union, .. } if path.is_empty() => {
if union.alternatives.len() == 2 { if union.alternatives.len() == 2 {
// excluded middle: we also know the tag_id in the fail branch // excluded middle: we also know the tag_id in the fail branch
ConstructorKnown::Both { ConstructorKnown::Both {
layout: cond_layout.clone(), layout: *cond_layout,
scrutinee: cond_symbol, scrutinee: cond_symbol,
pass: *tag_id, pass: *tag_id,
fail: (*tag_id == 0) as u8, fail: (*tag_id == 0) as u8,
} }
} else { } else {
ConstructorKnown::OnlyPass { ConstructorKnown::OnlyPass {
layout: cond_layout.clone(), layout: *cond_layout,
scrutinee: cond_symbol, scrutinee: cond_symbol,
tag_id: *tag_id, tag_id: *tag_id,
} }
@ -1541,7 +1505,7 @@ fn decide_to_branching<'a>(
cond_layout: Layout<'a>, cond_layout: Layout<'a>,
ret_layout: Layout<'a>, ret_layout: Layout<'a>,
decider: Decider<'a, Choice<'a>>, decider: Decider<'a, Choice<'a>>,
jumps: &Vec<(u64, JoinPointId, Stmt<'a>)>, jumps: &[(u64, JoinPointId, Stmt<'a>)],
) -> Stmt<'a> { ) -> Stmt<'a> {
use Choice::*; use Choice::*;
use Decider::*; use Decider::*;
@ -1569,8 +1533,8 @@ fn decide_to_branching<'a>(
procs, procs,
layout_cache, layout_cache,
cond_symbol, cond_symbol,
cond_layout.clone(), cond_layout,
ret_layout.clone(), ret_layout,
*success, *success,
jumps, jumps,
); );
@ -1580,8 +1544,8 @@ fn decide_to_branching<'a>(
procs, procs,
layout_cache, layout_cache,
cond_symbol, cond_symbol,
cond_layout.clone(), cond_layout,
ret_layout.clone(), ret_layout,
*failure, *failure,
jumps, jumps,
); );
@ -1608,7 +1572,7 @@ fn decide_to_branching<'a>(
compile_test_help( compile_test_help(
env, env,
chain_branch_info, chain_branch_info,
ret_layout.clone(), ret_layout,
new_stores, new_stores,
lhs, lhs,
rhs, rhs,
@ -1643,15 +1607,15 @@ fn decide_to_branching<'a>(
// switch on the tag discriminant (currently an i64 value) // switch on the tag discriminant (currently an i64 value)
// NOTE the tag discriminant is not actually loaded, `cond` can point to a tag // NOTE the tag discriminant is not actually loaded, `cond` can point to a tag
let (inner_cond_symbol, cond_stores_vec, inner_cond_layout) = let (inner_cond_symbol, cond_stores_vec, inner_cond_layout) =
path_to_expr_help(env, cond_symbol, &path, cond_layout.clone()); path_to_expr_help(env, cond_symbol, &path, cond_layout);
let default_branch = decide_to_branching( let default_branch = decide_to_branching(
env, env,
procs, procs,
layout_cache, layout_cache,
cond_symbol, cond_symbol,
cond_layout.clone(), cond_layout,
ret_layout.clone(), ret_layout,
*fallback, *fallback,
jumps, jumps,
); );
@ -1667,8 +1631,8 @@ fn decide_to_branching<'a>(
procs, procs,
layout_cache, layout_cache,
cond_symbol, cond_symbol,
cond_layout.clone(), cond_layout,
ret_layout.clone(), ret_layout,
decider, decider,
jumps, jumps,
); );
@ -1689,7 +1653,7 @@ fn decide_to_branching<'a>(
BranchInfo::Constructor { BranchInfo::Constructor {
scrutinee: inner_cond_symbol, scrutinee: inner_cond_symbol,
layout: inner_cond_layout.clone(), layout: inner_cond_layout,
tag_id, tag_id,
} }
} else { } else {
@ -1704,7 +1668,7 @@ fn decide_to_branching<'a>(
let default_branch_info = if tag_id_sum > 0 && union_size > 0 { let default_branch_info = if tag_id_sum > 0 && union_size > 0 {
BranchInfo::Constructor { BranchInfo::Constructor {
scrutinee: inner_cond_symbol, scrutinee: inner_cond_symbol,
layout: inner_cond_layout.clone(), layout: inner_cond_layout,
tag_id: tag_id_sum as u8, tag_id: tag_id_sum as u8,
} }
} else { } else {
@ -1847,7 +1811,7 @@ fn tree_to_decider(tree: DecisionTree) -> Decider<u64> {
} }
fn to_chain<'a>( fn to_chain<'a>(
path: Path, path: Vec<PathInstruction>,
test: Test<'a>, test: Test<'a>,
success_tree: DecisionTree<'a>, success_tree: DecisionTree<'a>,
failure_tree: DecisionTree<'a>, failure_tree: DecisionTree<'a>,

View File

@ -141,7 +141,7 @@ impl<'a, 'i> Env<'a, 'i> {
tag_id, tag_id,
} => { } => {
self.constructor_map.insert(*scrutinee, *tag_id as u64); self.constructor_map.insert(*scrutinee, *tag_id as u64);
self.layout_map.insert(*scrutinee, layout.clone()); self.layout_map.insert(*scrutinee, *layout);
} }
BranchInfo::None => (), BranchInfo::None => (),
} }
@ -167,7 +167,7 @@ impl<'a, 'i> Env<'a, 'i> {
} }
Closure(arguments, closure_layout, result) => { Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result); let fpointer = Layout::FunctionPointer(arguments, result);
let fields = self.arena.alloc([fpointer, closure_layout.layout.clone()]); let fields = self.arena.alloc([fpointer, *closure_layout.layout]);
self.constructor_map.insert(symbol, 0); self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields)); self.layout_map.insert(symbol, Layout::Struct(fields));
} }
@ -247,7 +247,7 @@ fn layout_for_constructor<'a>(
} }
Closure(arguments, closure_layout, result) => { Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result); let fpointer = Layout::FunctionPointer(arguments, result);
let fields = arena.alloc([fpointer, closure_layout.layout.clone()]); let fields = arena.alloc([fpointer, *closure_layout.layout]);
HasFields(fields) HasFields(fields)
} }
other => unreachable!("weird layout {:?}", other), other => unreachable!("weird layout {:?}", other),
@ -315,9 +315,9 @@ fn work_for_constructor<'a>(
let alias_symbol = Env::manual_unique_symbol(env.home, env.ident_ids); let alias_symbol = Env::manual_unique_symbol(env.home, env.ident_ids);
let layout = if let Layout::RecursivePointer = field_layout { let layout = if let Layout::RecursivePointer = field_layout {
full_layout.clone() *full_layout
} else { } else {
field_layout.clone() *field_layout
}; };
env.deferred.assignments.push((alias_symbol, expr, layout)); env.deferred.assignments.push((alias_symbol, expr, layout));
@ -375,7 +375,7 @@ pub fn expand_and_cancel_proc<'a>(
} }
Layout::Closure(arguments, closure_layout, result) => { Layout::Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result); let fpointer = Layout::FunctionPointer(arguments, result);
let fields = env.arena.alloc([fpointer, closure_layout.layout.clone()]); let fields = env.arena.alloc([fpointer, *closure_layout.layout]);
env.insert_struct_info(*symbol, fields); env.insert_struct_info(*symbol, fields);
introduced.push(*symbol); introduced.push(*symbol);
} }
@ -412,7 +412,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
let mut result = { let mut result = {
match stmt { match stmt {
Let(mut symbol, expr, layout, cont) => { Let(mut symbol, expr, layout, cont) => {
env.layout_map.insert(symbol, layout.clone()); env.layout_map.insert(symbol, *layout);
let mut expr = expr; let mut expr = expr;
let mut layout = layout; let mut layout = layout;
@ -423,7 +423,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
while !matches!(&expr, Expr::AccessAtIndex { .. } | Expr::Struct(_)) { while !matches!(&expr, Expr::AccessAtIndex { .. } | Expr::Struct(_)) {
if let Stmt::Let(symbol1, expr1, layout1, cont1) = cont { if let Stmt::Let(symbol1, expr1, layout1, cont1) = cont {
literal_stack.push((symbol, expr.clone(), layout.clone())); literal_stack.push((symbol, expr.clone(), *layout));
symbol = *symbol1; symbol = *symbol1;
expr = expr1; expr = expr1;
@ -479,7 +479,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
} }
} }
let stmt = Let(symbol, expr.clone(), layout.clone(), new_cont); let stmt = Let(symbol, expr.clone(), *layout, new_cont);
let mut stmt = &*env.arena.alloc(stmt); let mut stmt = &*env.arena.alloc(stmt);
for (symbol, expr, layout) in literal_stack.into_iter().rev() { for (symbol, expr, layout) in literal_stack.into_iter().rev() {
@ -519,8 +519,8 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
let stmt = Switch { let stmt = Switch {
cond_symbol: *cond_symbol, cond_symbol: *cond_symbol,
cond_layout: cond_layout.clone(), cond_layout: *cond_layout,
ret_layout: ret_layout.clone(), ret_layout: *ret_layout,
branches: new_branches.into_bump_slice(), branches: new_branches.into_bump_slice(),
default_branch: new_default, default_branch: new_default,
}; };
@ -579,7 +579,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
let stmt = Invoke { let stmt = Invoke {
symbol: *symbol, symbol: *symbol,
call: call.clone(), call: call.clone(),
layout: layout.clone(), layout: *layout,
pass, pass,
fail, fail,
}; };

View File

@ -470,7 +470,7 @@ impl<'a> Context<'a> {
name, full_layout, .. name, full_layout, ..
} => { } => {
// get the borrow signature // get the borrow signature
match self.param_map.get_symbol(*name, full_layout.clone()) { match self.param_map.get_symbol(*name, *full_layout) {
Some(ps) => { Some(ps) => {
let v = Expr::Call(crate::ir::Call { let v = Expr::Call(crate::ir::Call {
call_type, call_type,
@ -695,7 +695,7 @@ impl<'a> Context<'a> {
let pair = ctx.visit_variable_declaration( let pair = ctx.visit_variable_declaration(
*symbol, *symbol,
(*expr).clone(), (*expr).clone(),
(*layout).clone(), *layout,
b, b,
&b_live_vars, &b_live_vars,
); );
@ -712,13 +712,7 @@ impl<'a> Context<'a> {
Let(symbol, expr, layout, cont) => { Let(symbol, expr, layout, cont) => {
let ctx = self.update_var_info(*symbol, layout, expr); let ctx = self.update_var_info(*symbol, layout, expr);
let (b, b_live_vars) = ctx.visit_stmt(cont); let (b, b_live_vars) = ctx.visit_stmt(cont);
ctx.visit_variable_declaration( ctx.visit_variable_declaration(*symbol, expr.clone(), *layout, b, &b_live_vars)
*symbol,
expr.clone(),
layout.clone(),
b,
&b_live_vars,
)
} }
Invoke { Invoke {
@ -759,7 +753,7 @@ impl<'a> Context<'a> {
call: call.clone(), call: call.clone(),
pass, pass,
fail, fail,
layout: layout.clone(), layout: *layout,
}; };
let cont = self.arena.alloc(invoke); let cont = self.arena.alloc(invoke);
@ -783,7 +777,7 @@ impl<'a> Context<'a> {
name, full_layout, .. name, full_layout, ..
} => { } => {
// get the borrow signature // get the borrow signature
match self.param_map.get_symbol(*name, full_layout.clone()) { match self.param_map.get_symbol(*name, *full_layout) {
Some(ps) => self.add_dec_after_application( Some(ps) => self.add_dec_after_application(
call.arguments, call.arguments,
ps, ps,
@ -908,8 +902,8 @@ impl<'a> Context<'a> {
cond_symbol: *cond_symbol, cond_symbol: *cond_symbol,
branches, branches,
default_branch, default_branch,
cond_layout: cond_layout.clone(), cond_layout: *cond_layout,
ret_layout: ret_layout.clone(), ret_layout: *ret_layout,
}); });
(switch, case_live_vars) (switch, case_live_vars)

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ pub enum LayoutProblem {
} }
/// Types for code gen must be monomorphic. No type variables allowed! /// Types for code gen must be monomorphic. No type variables allowed!
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Layout<'a> { pub enum Layout<'a> {
Builtin(Builtin<'a>), Builtin(Builtin<'a>),
/// A layout that is empty (turns into the empty struct in LLVM IR /// A layout that is empty (turns into the empty struct in LLVM IR
@ -39,7 +39,7 @@ pub enum Layout<'a> {
Pointer(&'a Layout<'a>), Pointer(&'a Layout<'a>),
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum UnionLayout<'a> { pub enum UnionLayout<'a> {
/// A non-recursive tag union /// A non-recursive tag union
/// e.g. `Result a e : [ Ok a, Err e ]` /// e.g. `Result a e : [ Ok a, Err e ]`
@ -93,7 +93,7 @@ impl<'a> UnionLayout<'a> {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ClosureLayout<'a> { pub struct ClosureLayout<'a> {
/// the layout that this specific closure captures /// the layout that this specific closure captures
/// uses a Vec instead of a MutMap because it's Hash /// uses a Vec instead of a MutMap because it's Hash
@ -258,7 +258,7 @@ impl<'a> ClosureLayout<'a> {
// define the function pointer // define the function pointer
let function_ptr_layout = { let function_ptr_layout = {
let mut temp = Vec::from_iter_in(argument_layouts.iter().cloned(), arena); let mut temp = Vec::from_iter_in(argument_layouts.iter().cloned(), arena);
temp.push(closure_data_layout.clone()); temp.push(*closure_data_layout);
Layout::FunctionPointer(temp.into_bump_slice(), ret_layout) Layout::FunctionPointer(temp.into_bump_slice(), ret_layout)
}; };
@ -278,8 +278,8 @@ impl<'a> ClosureLayout<'a> {
pub fn as_named_layout(&self, symbol: Symbol) -> Layout<'a> { pub fn as_named_layout(&self, symbol: Symbol) -> Layout<'a> {
let layouts = if self.captured.is_empty() { let layouts = if self.captured.is_empty() {
match self.layout { match self.layout {
Layout::Struct(fields) if fields.len() == 1 => fields[0].clone(), Layout::Struct(fields) if fields.len() == 1 => fields[0],
other => other.clone(), other => *other,
} }
} else if let Some((_, tag_args)) = self } else if let Some((_, tag_args)) = self
.captured .captured
@ -287,7 +287,7 @@ impl<'a> ClosureLayout<'a> {
.find(|(tn, _)| *tn == TagName::Closure(symbol)) .find(|(tn, _)| *tn == TagName::Closure(symbol))
{ {
if tag_args.len() == 1 { if tag_args.len() == 1 {
tag_args[0].clone() tag_args[0]
} else { } else {
Layout::Struct(tag_args) Layout::Struct(tag_args)
} }
@ -303,13 +303,13 @@ impl<'a> ClosureLayout<'a> {
pub fn as_block_of_memory_layout(&self) -> Layout<'a> { pub fn as_block_of_memory_layout(&self) -> Layout<'a> {
match self.layout { match self.layout {
Layout::Struct(fields) if fields.len() == 1 => fields[0].clone(), Layout::Struct(fields) if fields.len() == 1 => fields[0],
other => other.clone(), other => *other,
} }
} }
pub fn internal_layout(&self) -> Layout<'a> { pub fn internal_layout(&self) -> Layout<'a> {
self.layout.clone() *self.layout
} }
pub fn build_closure_data( pub fn build_closure_data(
@ -382,7 +382,7 @@ pub enum MemoryMode {
Refcounted, Refcounted,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Builtin<'a> { pub enum Builtin<'a> {
Int128, Int128,
Int64, Int64,
@ -825,7 +825,7 @@ impl<'a> LayoutCache<'a> {
// of a problem // of a problem
if false { if false {
let cached_layout = match &result { let cached_layout = match &result {
Ok(layout) => Cached(layout.clone()), Ok(layout) => Cached(*layout),
Err(problem) => Problem(problem.clone()), Err(problem) => Problem(problem.clone()),
}; };
@ -2057,7 +2057,7 @@ impl<'a> LayoutIds<'a> {
// If we had to default to next_id, it must not have been found; // If we had to default to next_id, it must not have been found;
// store the ID we're going to return and increment next_id. // store the ID we're going to return and increment next_id.
if answer == ids.next_id { if answer == ids.next_id {
ids.by_id.insert(layout.clone(), ids.next_id); ids.by_id.insert(*layout, ids.next_id);
ids.next_id += 1; ids.next_id += 1;
} }

View File

@ -1,6 +1,6 @@
#![warn(clippy::all, clippy::dbg_macro)] #![warn(clippy::all, clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod borrow; pub mod borrow;
pub mod expand_rc; pub mod expand_rc;
@ -13,7 +13,6 @@ pub mod tail_recursion;
// For now, following this warning's advice will lead to nasty type inference errors. // For now, following this warning's advice will lead to nasty type inference errors.
//#[allow(clippy::ptr_arg)] //#[allow(clippy::ptr_arg)]
//pub mod decision_tree; //pub mod decision_tree;
#[allow(clippy::ptr_arg)]
pub mod decision_tree; pub mod decision_tree;
#[allow(clippy::ptr_arg)] #[allow(clippy::ptr_arg)]
pub mod exhaustive; pub mod exhaustive;

View File

@ -42,7 +42,7 @@ pub fn make_tail_recursive<'a>(
let params = Vec::from_iter_in( let params = Vec::from_iter_in(
args.iter().map(|(layout, symbol)| Param { args.iter().map(|(layout, symbol)| Param {
symbol: *symbol, symbol: *symbol,
layout: layout.clone(), layout: *layout,
borrow: true, borrow: true,
}), }),
arena, arena,
@ -117,7 +117,7 @@ fn insert_jumps<'a>(
if opt_cont.is_some() { if opt_cont.is_some() {
let cont = opt_cont.unwrap_or(cont); let cont = opt_cont.unwrap_or(cont);
Some(arena.alloc(Let(*symbol, expr.clone(), layout.clone(), cont))) Some(arena.alloc(Let(*symbol, expr.clone(), *layout, cont)))
} else { } else {
None None
} }
@ -140,7 +140,7 @@ fn insert_jumps<'a>(
let stmt = Invoke { let stmt = Invoke {
symbol: *symbol, symbol: *symbol,
call: call.clone(), call: call.clone(),
layout: layout.clone(), layout: *layout,
pass, pass,
fail, fail,
}; };
@ -222,10 +222,10 @@ fn insert_jumps<'a>(
Some(arena.alloc(Switch { Some(arena.alloc(Switch {
cond_symbol: *cond_symbol, cond_symbol: *cond_symbol,
cond_layout: cond_layout.clone(), cond_layout: *cond_layout,
default_branch, default_branch,
branches, branches,
ret_layout: ret_layout.clone(), ret_layout: *ret_layout,
})) }))
} else { } else {
None None

View File

@ -69,7 +69,7 @@ mod test_mono {
let mut loaded = match loaded { let mut loaded = match loaded {
Ok(x) => x, Ok(x) => x,
Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => { Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
println!("{}", report); println!("{}", report);
panic!(); panic!();
} }
@ -341,13 +341,13 @@ mod test_mono {
"#, "#,
indoc!( indoc!(
r#" r#"
procedure Num.46 (#Attr.2): procedure Num.47 (#Attr.2):
let Test.3 = lowlevel NumRound #Attr.2; let Test.3 = lowlevel NumRound #Attr.2;
ret Test.3; ret Test.3;
procedure Test.0 (): procedure Test.0 ():
let Test.2 = 3.6f64; let Test.2 = 3.6f64;
let Test.1 = CallByName Num.46 Test.2; let Test.1 = CallByName Num.47 Test.2;
ret Test.1; ret Test.1;
"# "#
), ),

View File

@ -12,20 +12,20 @@ pub enum Module<'a> {
Platform { header: PlatformHeader<'a> }, Platform { header: PlatformHeader<'a> },
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct WhenBranch<'a> { pub struct WhenBranch<'a> {
pub patterns: &'a [Loc<Pattern<'a>>], pub patterns: &'a [Loc<Pattern<'a>>],
pub value: Loc<Expr<'a>>, pub value: Loc<Expr<'a>>,
pub guard: Option<Loc<Expr<'a>>>, pub guard: Option<Loc<Expr<'a>>>,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct WhenPattern<'a> { pub struct WhenPattern<'a> {
pub pattern: Loc<Pattern<'a>>, pub pattern: Loc<Pattern<'a>>,
pub guard: Option<Loc<Expr<'a>>>, pub guard: Option<Loc<Expr<'a>>>,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum StrSegment<'a> { pub enum StrSegment<'a> {
Plaintext(&'a str), // e.g. "foo" Plaintext(&'a str), // e.g. "foo"
Unicode(Loc<&'a str>), // e.g. "00A0" in "\u(00A0)" Unicode(Loc<&'a str>), // e.g. "00A0" in "\u(00A0)"
@ -33,7 +33,7 @@ pub enum StrSegment<'a> {
Interpolated(Loc<&'a Expr<'a>>), // e.g. (name) in "Hi, \(name)!" Interpolated(Loc<&'a Expr<'a>>), // e.g. (name) in "Hi, \(name)!"
} }
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum EscapedChar { pub enum EscapedChar {
Newline, // \n Newline, // \n
Tab, // \t Tab, // \t
@ -57,7 +57,7 @@ impl EscapedChar {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum StrLiteral<'a> { pub enum StrLiteral<'a> {
/// The most common case: a plain string with no escapes or interpolations /// The most common case: a plain string with no escapes or interpolations
PlainLine(&'a str), PlainLine(&'a str),
@ -74,7 +74,7 @@ pub enum StrLiteral<'a> {
/// we move on to canonicalization, which often needs to allocate more because /// we move on to canonicalization, which often needs to allocate more because
/// it's doing things like turning local variables into fully qualified symbols. /// it's doing things like turning local variables into fully qualified symbols.
/// Once canonicalization is done, the arena and the input string get dropped. /// Once canonicalization is done, the arena and the input string get dropped.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum Expr<'a> { pub enum Expr<'a> {
// Number Literals // Number Literals
Float(&'a str), Float(&'a str),
@ -151,10 +151,6 @@ pub enum Expr<'a> {
SpaceAfter(&'a Expr<'a>, &'a [CommentOrNewline<'a>]), SpaceAfter(&'a Expr<'a>, &'a [CommentOrNewline<'a>]),
ParensAround(&'a Expr<'a>), ParensAround(&'a Expr<'a>),
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
/// It lets us take an (&Expr) and create a plain (Expr) from it.
Nested(&'a Expr<'a>),
// Problems // Problems
MalformedIdent(&'a str, crate::ident::BadIdent), MalformedIdent(&'a str, crate::ident::BadIdent),
MalformedClosure, MalformedClosure,
@ -163,7 +159,7 @@ pub enum Expr<'a> {
PrecedenceConflict(&'a PrecedenceConflict<'a>), PrecedenceConflict(&'a PrecedenceConflict<'a>),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct PrecedenceConflict<'a> { pub struct PrecedenceConflict<'a> {
pub whole_region: Region, pub whole_region: Region,
pub binop1_position: Position, pub binop1_position: Position,
@ -173,7 +169,7 @@ pub struct PrecedenceConflict<'a> {
pub expr: &'a Loc<Expr<'a>>, pub expr: &'a Loc<Expr<'a>>,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum Def<'a> { pub enum Def<'a> {
// TODO in canonicalization, validate the pattern; only certain patterns // TODO in canonicalization, validate the pattern; only certain patterns
// are allowed in annotations. // are allowed in annotations.
@ -208,14 +204,10 @@ pub enum Def<'a> {
SpaceBefore(&'a Def<'a>, &'a [CommentOrNewline<'a>]), SpaceBefore(&'a Def<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a Def<'a>, &'a [CommentOrNewline<'a>]), SpaceAfter(&'a Def<'a>, &'a [CommentOrNewline<'a>]),
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
/// It lets us take a (&Def) and create a plain (Def) from it.
Nested(&'a Def<'a>),
NotYetImplemented(&'static str), NotYetImplemented(&'static str),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum TypeAnnotation<'a> { pub enum TypeAnnotation<'a> {
/// A function. The types of its arguments, then the type of its return value. /// A function. The types of its arguments, then the type of its return value.
Function(&'a [Loc<TypeAnnotation<'a>>], &'a Loc<TypeAnnotation<'a>>), Function(&'a [Loc<TypeAnnotation<'a>>], &'a Loc<TypeAnnotation<'a>>),
@ -261,7 +253,7 @@ pub enum TypeAnnotation<'a> {
Malformed(&'a str), Malformed(&'a str),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum Tag<'a> { pub enum Tag<'a> {
Global { Global {
name: Loc<&'a str>, name: Loc<&'a str>,
@ -281,7 +273,7 @@ pub enum Tag<'a> {
Malformed(&'a str), Malformed(&'a str),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum AssignedField<'a, Val> { pub enum AssignedField<'a, Val> {
// A required field with a label, e.g. `{ name: "blah" }` or `{ name : Str }` // A required field with a label, e.g. `{ name: "blah" }` or `{ name : Str }`
RequiredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>), RequiredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
@ -303,7 +295,7 @@ pub enum AssignedField<'a, Val> {
Malformed(&'a str), Malformed(&'a str),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum CommentOrNewline<'a> { pub enum CommentOrNewline<'a> {
Newline, Newline,
LineComment(&'a str), LineComment(&'a str),
@ -330,7 +322,7 @@ impl<'a> CommentOrNewline<'a> {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum Pattern<'a> { pub enum Pattern<'a> {
// Identifier // Identifier
Identifier(&'a str), Identifier(&'a str),
@ -351,10 +343,6 @@ pub enum Pattern<'a> {
/// Can only occur inside of a RecordDestructure /// Can only occur inside of a RecordDestructure
OptionalField(&'a str, &'a Loc<Expr<'a>>), OptionalField(&'a str, &'a Loc<Expr<'a>>),
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
/// It lets us take an (&Expr) and create a plain (Expr) from it.
Nested(&'a Pattern<'a>),
// Literal // Literal
NumLiteral(&'a str), NumLiteral(&'a str),
NonBase10Literal { NonBase10Literal {
@ -464,8 +452,6 @@ impl<'a> Pattern<'a> {
// { x, y ? False } = rec // { x, y ? False } = rec
x == y x == y
} }
(Nested(x), Nested(y)) => x.equivalent(y),
// Literal // Literal
(NumLiteral(x), NumLiteral(y)) => x == y, (NumLiteral(x), NumLiteral(y)) => x == y,
( (

View File

@ -125,7 +125,7 @@ where
E: 'a, E: 'a,
{ {
move |_, state: State<'a>| { move |_, state: State<'a>| {
if state.column > min_indent { if state.column >= min_indent {
Ok((NoProgress, (), state)) Ok((NoProgress, (), state))
} else { } else {
Err((NoProgress, indent_problem(state.line, state.column), state)) Err((NoProgress, indent_problem(state.line, state.column), state))

View File

@ -16,16 +16,33 @@ use roc_region::all::{Located, Position, Region};
use crate::parser::Progress::{self, *}; use crate::parser::Progress::{self, *};
fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> {
|_arena, state: State<'a>| {
if state.has_reached_end() {
Ok((NoProgress, (), state))
} else {
Err((
NoProgress,
EExpr::BadExprEnd(state.line, state.column),
state,
))
}
}
}
pub fn test_parse_expr<'a>( pub fn test_parse_expr<'a>(
min_indent: u16, min_indent: u16,
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
state: State<'a>, state: State<'a>,
) -> Result<Located<Expr<'a>>, EExpr<'a>> { ) -> Result<Located<Expr<'a>>, EExpr<'a>> {
let parser = space0_before_e( let parser = skip_second!(
space0_before_e(
move |a, s| parse_loc_expr(min_indent, a, s), move |a, s| parse_loc_expr(min_indent, a, s),
min_indent, min_indent,
EExpr::Space, EExpr::Space,
EExpr::IndentStart, EExpr::IndentStart,
),
expr_end()
); );
match parser.parse(arena, state) { match parser.parse(arena, state) {
@ -35,9 +52,27 @@ pub fn test_parse_expr<'a>(
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MultiBackpassing { pub struct ExprParseOptions {
Allow, /// Check for and accept multi-backpassing syntax
Disallow, /// This is usually true, but false within list/record literals
/// because the comma separating backpassing arguments conflicts
/// with the comma separating literal elements
accept_multi_backpassing: bool,
/// Check for the `->` token, and raise an error if found
/// This is usually true, but false in if-guards
///
/// > Just foo if foo == 2 -> ...
check_for_arrow: bool,
}
impl Default for ExprParseOptions {
fn default() -> Self {
ExprParseOptions {
accept_multi_backpassing: true,
check_for_arrow: true,
}
}
} }
pub fn expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { pub fn expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
@ -159,7 +194,7 @@ fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> {
fn parse_loc_term<'a>( fn parse_loc_term<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
@ -167,10 +202,7 @@ fn parse_loc_term<'a>(
loc_expr_in_parens_etc_help(min_indent), loc_expr_in_parens_etc_help(min_indent),
loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::Str, string_literal_help())),
loc!(specialize(EExpr::Number, positive_number_literal_help())), loc!(specialize(EExpr::Number, positive_number_literal_help())),
loc!(specialize( loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
EExpr::Lambda,
closure_help(min_indent, multi_backpassing)
)),
loc!(record_literal_help(min_indent)), loc!(record_literal_help(min_indent)),
loc!(specialize(EExpr::List, list_literal_help(min_indent))), loc!(specialize(EExpr::List, list_literal_help(min_indent))),
loc!(map_with_arena!( loc!(map_with_arena!(
@ -183,17 +215,14 @@ fn parse_loc_term<'a>(
fn loc_possibly_negative_or_negated_term<'a>( fn loc_possibly_negative_or_negated_term<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
) -> impl Parser<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> impl Parser<'a, Located<Expr<'a>>, EExpr<'a>> {
one_of![ one_of![
|arena, state: State<'a>| { |arena, state: State<'a>| {
let initial = state; let initial = state;
let (_, (loc_op, loc_expr), state) = and!(loc!(unary_negate()), |a, s| parse_loc_term( let (_, (loc_op, loc_expr), state) = and!(loc!(unary_negate()), |a, s| parse_loc_term(
min_indent, min_indent, options, a, s
multi_backpassing,
a,
s
)) ))
.parse(arena, state)?; .parse(arena, state)?;
@ -205,7 +234,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
loc!(specialize(EExpr::Number, number_literal_help())), loc!(specialize(EExpr::Number, number_literal_help())),
loc!(map_with_arena!( loc!(map_with_arena!(
and!(loc!(word1(b'!', EExpr::Start)), |a, s| { and!(loc!(word1(b'!', EExpr::Start)), |a, s| {
parse_loc_term(min_indent, multi_backpassing, a, s) parse_loc_term(min_indent, options, a, s)
}), }),
|arena: &'a Bump, (loc_op, loc_expr): (Located<_>, _)| { |arena: &'a Bump, (loc_op, loc_expr): (Located<_>, _)| {
Expr::UnaryOp( Expr::UnaryOp(
@ -216,7 +245,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
)), )),
|arena, state| { |arena, state| {
// TODO use parse_loc_term_better // TODO use parse_loc_term_better
parse_loc_term(min_indent, multi_backpassing, arena, state) parse_loc_term(min_indent, options, arena, state)
} }
] ]
} }
@ -257,25 +286,19 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> {
fn parse_expr_start<'a>( fn parse_expr_start<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
one_of![ one_of![
loc!(specialize( loc!(specialize(EExpr::If, if_expr_help(min_indent, options))),
EExpr::If,
if_expr_help(min_indent, multi_backpassing)
)),
loc!(specialize( loc!(specialize(
EExpr::When, EExpr::When,
when::expr_help(min_indent, multi_backpassing) when::expr_help(min_indent, options)
)), )),
loc!(specialize( loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
EExpr::Lambda, loc!(move |a, s| parse_expr_operator_chain(min_indent, options, start, a, s)),
closure_help(min_indent, multi_backpassing)
)),
loc!(move |a, s| parse_expr_operator_chain(min_indent, multi_backpassing, start, a, s)),
fail_expr_start_e() fail_expr_start_e()
] ]
.parse(arena, state) .parse(arena, state)
@ -283,13 +306,13 @@ fn parse_expr_start<'a>(
fn parse_expr_operator_chain<'a>( fn parse_expr_operator_chain<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let (_, expr, state) = let (_, expr, state) =
loc_possibly_negative_or_negated_term(min_indent, multi_backpassing).parse(arena, state)?; loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state)?;
let initial = state; let initial = state;
let end = state.get_position(); let end = state.get_position();
@ -306,14 +329,7 @@ fn parse_expr_operator_chain<'a>(
end, end,
}; };
parse_expr_end( parse_expr_end(min_indent, options, start, expr_state, arena, state)
min_indent,
multi_backpassing,
start,
expr_state,
arena,
state,
)
} }
} }
} }
@ -685,7 +701,7 @@ struct DefState<'a> {
} }
fn parse_defs_end<'a>( fn parse_defs_end<'a>(
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
mut def_state: DefState<'a>, mut def_state: DefState<'a>,
arena: &'a Bump, arena: &'a Bump,
@ -740,7 +756,7 @@ fn parse_defs_end<'a>(
loc_def_expr, loc_def_expr,
); );
parse_defs_end(multi_backpassing, start, def_state, arena, state) parse_defs_end(options, start, def_state, arena, state)
} }
Ok((_, BinOp::HasType, state)) => { Ok((_, BinOp::HasType, state)) => {
let (_, ann_type, state) = specialize( let (_, ann_type, state) = specialize(
@ -762,7 +778,7 @@ fn parse_defs_end<'a>(
ann_type, ann_type,
); );
parse_defs_end(multi_backpassing, start, def_state, arena, state) parse_defs_end(options, start, def_state, arena, state)
} }
_ => Ok((MadeProgress, def_state, initial)), _ => Ok((MadeProgress, def_state, initial)),
@ -771,7 +787,7 @@ fn parse_defs_end<'a>(
} }
fn parse_defs_expr<'a>( fn parse_defs_expr<'a>(
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
def_state: DefState<'a>, def_state: DefState<'a>,
arena: &'a Bump, arena: &'a Bump,
@ -779,7 +795,7 @@ fn parse_defs_expr<'a>(
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let min_indent = start.col; let min_indent = start.col;
match parse_defs_end(multi_backpassing, start, def_state, arena, state) { match parse_defs_end(options, start, def_state, arena, state) {
Err(bad) => Err(bad), Err(bad) => Err(bad),
Ok((_, def_state, state)) => { Ok((_, def_state, state)) => {
// this is no def, because there is no `=` or `:`; parse as an expr // this is no def, because there is no `=` or `:`; parse as an expr
@ -812,7 +828,7 @@ fn parse_defs_expr<'a>(
fn parse_expr_operator<'a>( fn parse_expr_operator<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
mut expr_state: ExprState<'a>, mut expr_state: ExprState<'a>,
loc_op: Located<BinOp>, loc_op: Located<BinOp>,
@ -832,8 +848,7 @@ fn parse_expr_operator<'a>(
BinOp::Minus if expr_state.end != op_start && op_end == new_start => { BinOp::Minus if expr_state.end != op_start && op_end == new_start => {
// negative terms // negative terms
let (_, negated_expr, state) = let (_, negated_expr, state) = parse_loc_term(min_indent, options, arena, state)?;
parse_loc_term(min_indent, multi_backpassing, arena, state)?;
let new_end = state.get_position(); let new_end = state.get_position();
let arg = numeric_negate_expression( let arg = numeric_negate_expression(
@ -856,14 +871,7 @@ fn parse_expr_operator<'a>(
expr_state.spaces_after = spaces; expr_state.spaces_after = spaces;
expr_state.end = new_end; expr_state.end = new_end;
parse_expr_end( parse_expr_end(min_indent, options, start, expr_state, arena, state)
min_indent,
multi_backpassing,
start,
expr_state,
arena,
state,
)
} }
BinOp::Assignment => { BinOp::Assignment => {
let expr_region = expr_state.expr.region; let expr_region = expr_state.expr.region;
@ -912,7 +920,7 @@ fn parse_expr_operator<'a>(
spaces_after: &[], spaces_after: &[],
}; };
parse_defs_expr(multi_backpassing, start, def_state, arena, state) parse_defs_expr(options, start, def_state, arena, state)
} }
BinOp::Backpassing => { BinOp::Backpassing => {
let expr_region = expr_state.expr.region; let expr_region = expr_state.expr.region;
@ -1066,11 +1074,9 @@ fn parse_expr_operator<'a>(
spaces_after: &[], spaces_after: &[],
}; };
parse_defs_expr(multi_backpassing, start, def_state, arena, state) parse_defs_expr(options, start, def_state, arena, state)
} }
_ => match loc_possibly_negative_or_negated_term(min_indent, multi_backpassing) _ => match loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state) {
.parse(arena, state)
{
Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)),
Ok((_, mut new_expr, state)) => { Ok((_, mut new_expr, state)) => {
let new_end = state.get_position(); let new_end = state.get_position();
@ -1108,14 +1114,7 @@ fn parse_expr_operator<'a>(
expr_state.spaces_after = spaces; expr_state.spaces_after = spaces;
// TODO new start? // TODO new start?
parse_expr_end( parse_expr_end(min_indent, options, start, expr_state, arena, state)
min_indent,
multi_backpassing,
start,
expr_state,
arena,
state,
)
} }
} }
} }
@ -1128,7 +1127,7 @@ fn parse_expr_operator<'a>(
fn parse_expr_end<'a>( fn parse_expr_end<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
mut expr_state: ExprState<'a>, mut expr_state: ExprState<'a>,
arena: &'a Bump, arena: &'a Bump,
@ -1136,7 +1135,7 @@ fn parse_expr_end<'a>(
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let parser = skip_first!( let parser = skip_first!(
crate::blankspace::check_indent(min_indent, EExpr::IndentEnd), crate::blankspace::check_indent(min_indent, EExpr::IndentEnd),
move |a, s| parse_loc_term(min_indent, multi_backpassing, a, s) move |a, s| parse_loc_term(min_indent, options, a, s)
); );
match parser.parse(arena, state) { match parser.parse(arena, state) {
@ -1167,14 +1166,7 @@ fn parse_expr_end<'a>(
expr_state.end = new_end; expr_state.end = new_end;
expr_state.spaces_after = new_spaces; expr_state.spaces_after = new_spaces;
parse_expr_end( parse_expr_end(min_indent, options, start, expr_state, arena, state)
min_indent,
multi_backpassing,
start,
expr_state,
arena,
state,
)
} }
} }
} }
@ -1187,19 +1179,12 @@ fn parse_expr_end<'a>(
expr_state.consume_spaces(arena); expr_state.consume_spaces(arena);
expr_state.initial = before_op; expr_state.initial = before_op;
parse_expr_operator( parse_expr_operator(
min_indent, min_indent, options, start, expr_state, loc_op, arena, state,
multi_backpassing,
start,
expr_state,
loc_op,
arena,
state,
) )
} }
Err((NoProgress, _, mut state)) => { Err((NoProgress, _, mut state)) => {
// try multi-backpassing // try multi-backpassing
if multi_backpassing == MultiBackpassing::Allow && state.bytes.starts_with(b",") if options.accept_multi_backpassing && state.bytes.starts_with(b",") {
{
state.bytes = &state.bytes[1..]; state.bytes = &state.bytes[1..];
state.column += 1; state.column += 1;
@ -1260,6 +1245,12 @@ fn parse_expr_end<'a>(
Ok((MadeProgress, ret, state)) Ok((MadeProgress, ret, state))
} }
} }
} else if options.check_for_arrow && state.bytes.starts_with(b"->") {
Err((
MadeProgress,
EExpr::BadOperator(&[b'-', b'>'], state.line, state.column),
state,
))
} else { } else {
// roll back space parsing // roll back space parsing
let state = expr_state.initial; let state = expr_state.initial;
@ -1277,7 +1268,15 @@ fn parse_loc_expr<'a>(
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
parse_loc_expr_with_options(min_indent, MultiBackpassing::Allow, arena, state) parse_loc_expr_with_options(
min_indent,
ExprParseOptions {
accept_multi_backpassing: true,
..Default::default()
},
arena,
state,
)
} }
pub fn parse_loc_expr_no_multi_backpassing<'a>( pub fn parse_loc_expr_no_multi_backpassing<'a>(
@ -1285,17 +1284,25 @@ pub fn parse_loc_expr_no_multi_backpassing<'a>(
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
parse_loc_expr_with_options(min_indent, MultiBackpassing::Disallow, arena, state) parse_loc_expr_with_options(
min_indent,
ExprParseOptions {
accept_multi_backpassing: false,
..Default::default()
},
arena,
state,
)
} }
fn parse_loc_expr_with_options<'a>( fn parse_loc_expr_with_options<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
let start = state.get_position(); let start = state.get_position();
parse_expr_start(min_indent, multi_backpassing, start, arena, state) parse_expr_start(min_indent, options, start, arena, state)
} }
/// If the given Expr would parse the same way as a valid Pattern, convert it. /// If the given Expr would parse the same way as a valid Pattern, convert it.
@ -1339,9 +1346,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
spaces, spaces,
)), )),
Expr::ParensAround(sub_expr) | Expr::Nested(sub_expr) => { Expr::ParensAround(sub_expr) => expr_to_pattern_help(arena, sub_expr),
expr_to_pattern_help(arena, sub_expr)
}
Expr::Record { Expr::Record {
fields, fields,
@ -1385,7 +1390,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::RecordUpdate { .. } | Expr::RecordUpdate { .. }
| Expr::UnaryOp(_, _) => Err(()), | Expr::UnaryOp(_, _) => Err(()),
Expr::Str(string) => Ok(Pattern::StrLiteral(string.clone())), Expr::Str(string) => Ok(Pattern::StrLiteral(*string)),
Expr::MalformedIdent(string, _problem) => Ok(Pattern::Malformed(string)), Expr::MalformedIdent(string, _problem) => Ok(Pattern::Malformed(string)),
} }
} }
@ -1414,7 +1419,7 @@ fn assigned_expr_field_to_pattern_help<'a>(
AssignedField::OptionalValue(name, spaces, value) => { AssignedField::OptionalValue(name, spaces, value) => {
let result = arena.alloc(Located { let result = arena.alloc(Located {
region: value.region, region: value.region,
value: value.value.clone(), value: value.value,
}); });
if spaces.is_empty() { if spaces.is_empty() {
Pattern::OptionalField(name.value, result) Pattern::OptionalField(name.value, result)
@ -1449,8 +1454,13 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, E
space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?;
let start = state.get_position(); let start = state.get_position();
let (_, def_state, state) =
parse_defs_end(MultiBackpassing::Disallow, start, def_state, arena, state)?; let options = ExprParseOptions {
accept_multi_backpassing: false,
check_for_arrow: true,
};
let (_, def_state, state) = parse_defs_end(options, start, def_state, arena, state)?;
let (_, final_space, state) = let (_, final_space, state) =
space0_e(start.col, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; space0_e(start.col, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?;
@ -1462,7 +1472,7 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, E
let last = def_state.defs.len() - 1; let last = def_state.defs.len() - 1;
for (i, ref_def) in def_state.defs.into_iter().enumerate() { for (i, ref_def) in def_state.defs.into_iter().enumerate() {
let mut def = ref_def.clone(); let mut def = *ref_def;
if i == first { if i == first {
def = arena def = arena
@ -1488,7 +1498,7 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, E
fn closure_help<'a>( fn closure_help<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, ELambda<'a>> { ) -> impl Parser<'a, Expr<'a>, ELambda<'a>> {
map_with_arena!( map_with_arena!(
skip_first!( skip_first!(
@ -1516,7 +1526,7 @@ fn closure_help<'a>(
// Parse the body // Parse the body
space0_before_e( space0_before_e(
specialize_ref(ELambda::Body, move |arena, state| { specialize_ref(ELambda::Body, move |arena, state| {
parse_loc_expr_with_options(min_indent, multi_backpassing, arena, state) parse_loc_expr_with_options(min_indent, options, arena, state)
}), }),
min_indent, min_indent,
ELambda::Space, ELambda::Space,
@ -1541,7 +1551,7 @@ mod when {
/// Parser for when expressions. /// Parser for when expressions.
pub fn expr_help<'a>( pub fn expr_help<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, When<'a>> { ) -> impl Parser<'a, Expr<'a>, When<'a>> {
then( then(
and!( and!(
@ -1549,7 +1559,7 @@ mod when {
skip_second!( skip_second!(
space0_around_ee( space0_around_ee(
specialize_ref(When::Condition, move |arena, state| { specialize_ref(When::Condition, move |arena, state| {
parse_loc_expr_with_options(min_indent, multi_backpassing, arena, state) parse_loc_expr_with_options(min_indent, options, arena, state)
}), }),
min_indent, min_indent,
When::Space, When::Space,
@ -1572,7 +1582,7 @@ mod when {
// Everything in the branches must be indented at least as much as the case itself. // Everything in the branches must be indented at least as much as the case itself.
let min_indent = case_indent; let min_indent = case_indent;
let (p1, branches, state) = branches(min_indent).parse(arena, state)?; let (p1, branches, state) = branches(min_indent, options).parse(arena, state)?;
Ok(( Ok((
progress.or(p1), progress.or(p1),
@ -1592,22 +1602,27 @@ mod when {
} }
} }
fn branches<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, When<'a>> { fn branches<'a>(
move |arena, state| { min_indent: u16,
options: ExprParseOptions,
) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, When<'a>> {
move |arena, state: State<'a>| {
let when_indent = state.indent_col;
let mut branches: Vec<'a, &'a WhenBranch<'a>> = Vec::with_capacity_in(2, arena); let mut branches: Vec<'a, &'a WhenBranch<'a>> = Vec::with_capacity_in(2, arena);
// 1. Parse the first branch and get its indentation level. (It must be >= min_indent.) // 1. Parse the first branch and get its indentation level. (It must be >= min_indent.)
// 2. Parse the other branches. Their indentation levels must be == the first branch's. // 2. Parse the other branches. Their indentation levels must be == the first branch's.
let (_, (loc_first_patterns, loc_first_guard), state) = let (_, ((pattern_indent_level, loc_first_patterns), loc_first_guard), mut state) =
branch_alternatives(min_indent).parse(arena, state)?; branch_alternatives(min_indent, options, None).parse(arena, state)?;
let loc_first_pattern = loc_first_patterns.first().unwrap(); let original_indent = pattern_indent_level;
let original_indent = loc_first_pattern.region.start_col;
let indented_more = original_indent + 1; state.indent_col = pattern_indent_level;
// Parse the first "->" and the expression after it. // Parse the first "->" and the expression after it.
let (_, loc_first_expr, mut state) = let (_, loc_first_expr, mut state) =
branch_result(indented_more).parse(arena, state)?; branch_result(original_indent + 1).parse(arena, state)?;
// Record this as the first branch, then optionally parse additional branches. // Record this as the first branch, then optionally parse additional branches.
branches.push(arena.alloc(WhenBranch { branches.push(arena.alloc(WhenBranch {
@ -1619,19 +1634,21 @@ mod when {
let branch_parser = map!( let branch_parser = map!(
and!( and!(
then( then(
branch_alternatives(min_indent), branch_alternatives(min_indent, options, Some(pattern_indent_level)),
move |_arena, state, _, (loc_patterns, loc_guard)| { move |_arena, state, _, ((indent_col, loc_patterns), loc_guard)| {
match alternatives_indented_correctly(&loc_patterns, original_indent) { if pattern_indent_level == indent_col {
Ok(()) => Ok((MadeProgress, (loc_patterns, loc_guard), state)), Ok((MadeProgress, (loc_patterns, loc_guard), state))
Err(indent) => Err(( } else {
let indent = pattern_indent_level - indent_col;
Err((
MadeProgress, MadeProgress,
When::PatternAlignment(indent, state.line, state.column), When::PatternAlignment(indent, state.line, state.column),
state, state,
)), ))
} }
}, },
), ),
branch_result(indented_more) branch_result(original_indent + 1)
), ),
|((patterns, guard), expr)| { |((patterns, guard), expr)| {
let patterns: Vec<'a, _> = patterns; let patterns: Vec<'a, _> = patterns;
@ -1661,16 +1678,62 @@ mod when {
} }
} }
Ok((MadeProgress, branches, state)) Ok((
MadeProgress,
branches,
State {
indent_col: when_indent,
..state
},
))
} }
} }
/// Parsing alternative patterns in when branches. /// Parsing alternative patterns in when branches.
fn branch_alternatives<'a>( fn branch_alternatives<'a>(
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>), When<'a>> { options: ExprParseOptions,
pattern_indent_level: Option<u16>,
) -> impl Parser<
'a,
(
(Col, Vec<'a, Located<Pattern<'a>>>),
Option<Located<Expr<'a>>>,
),
When<'a>,
> {
let options = ExprParseOptions {
check_for_arrow: false,
..options
};
and!( and!(
sep_by1(word1(b'|', When::Bar), |arena, state| { branch_alternatives_help(min_indent, pattern_indent_level),
one_of![
map!(
skip_first!(
parser::keyword_e(keyword::IF, When::IfToken),
// TODO we should require space before the expression but not after
space0_around_ee(
specialize_ref(When::IfGuard, move |arena, state| {
parse_loc_expr_with_options(min_indent + 1, options, arena, state)
}),
min_indent,
When::Space,
When::IndentIfGuard,
When::IndentArrow,
)
),
Some
),
|_, s| Ok((NoProgress, None, s))
]
)
}
fn branch_single_alternative<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, When<'a>> {
move |arena, state| {
let (_, spaces, state) = let (_, spaces, state) =
backtrackable(space0_e(min_indent, When::Space, When::IndentPattern)) backtrackable(space0_e(min_indent, When::Space, When::IndentPattern))
.parse(arena, state)?; .parse(arena, state)?;
@ -1694,45 +1757,76 @@ mod when {
}, },
state, state,
)) ))
}), }
one_of![
map!(
skip_first!(
parser::keyword_e(keyword::IF, When::IfToken),
// TODO we should require space before the expression but not after
space0_around_ee(
specialize_ref(When::IfGuard, move |arena, state| {
parse_loc_expr(min_indent, arena, state)
}),
min_indent,
When::Space,
When::IndentIfGuard,
When::IndentArrow,
)
),
Some
),
|_, s| Ok((NoProgress, None, s))
]
)
} }
/// Check if alternatives of a when branch are indented correctly. fn branch_alternatives_help<'a>(
fn alternatives_indented_correctly<'a>( min_indent: u16,
loc_patterns: &'a Vec<'a, Located<Pattern<'a>>>, pattern_indent_level: Option<u16>,
original_indent: u16, ) -> impl Parser<'a, (Col, Vec<'a, Located<Pattern<'a>>>), When<'a>> {
) -> Result<(), u16> { move |arena, state: State<'a>| {
let (first, rest) = loc_patterns.split_first().unwrap(); let initial = state;
let first_indented_correctly = first.region.start_col == original_indent;
if first_indented_correctly { // put no restrictions on the indent after the spaces; we'll check it manually
for when_pattern in rest.iter() { match space0_e(0, When::Space, When::IndentPattern).parse(arena, state) {
if when_pattern.region.start_col < original_indent { Err((MadeProgress, fail, _)) => Err((NoProgress, fail, initial)),
return Err(original_indent - when_pattern.region.start_col); Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)),
Ok((_progress, spaces, state)) => {
match pattern_indent_level {
Some(wanted) if state.column > wanted => {
// this branch is indented too much
Err((
NoProgress,
When::IndentPattern(state.line, state.column),
initial,
))
}
Some(wanted) if state.column < wanted => {
let indent = wanted - state.column;
Err((
NoProgress,
When::PatternAlignment(indent, state.line, state.column),
initial,
))
}
_ => {
let pattern_indent =
min_indent.max(pattern_indent_level.unwrap_or(min_indent));
// the region is not reliable for the indent col in the case of
// parentheses around patterns
let pattern_indent_col = state.column;
let parser = sep_by1(
word1(b'|', When::Bar),
branch_single_alternative(pattern_indent + 1),
);
match parser.parse(arena, state) {
Err((MadeProgress, fail, state)) => {
Err((MadeProgress, fail, state))
}
Err((NoProgress, fail, _)) => {
// roll back space parsing if the pattern made no progress
Err((NoProgress, fail, initial))
}
Ok((_, mut loc_patterns, state)) => {
// tag spaces onto the first parsed pattern
if !spaces.is_empty() {
if let Some(first) = loc_patterns.get_mut(0) {
*first = arena
.alloc(first.value)
.with_spaces_before(spaces, first.region);
}
}
Ok((MadeProgress, (pattern_indent_col, loc_patterns), state))
}
}
}
}
} }
} }
Ok(())
} else {
Err(original_indent - first.region.start_col)
} }
} }
@ -1795,7 +1889,7 @@ fn if_branch<'a>(
fn if_expr_help<'a>( fn if_expr_help<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, If<'a>> { ) -> impl Parser<'a, Expr<'a>, If<'a>> {
move |arena: &'a Bump, state| { move |arena: &'a Bump, state| {
let (_, _, state) = parser::keyword_e(keyword::IF, If::If).parse(arena, state)?; let (_, _, state) = parser::keyword_e(keyword::IF, If::If).parse(arena, state)?;
@ -1827,7 +1921,7 @@ fn if_expr_help<'a>(
let (_, else_branch, state) = space0_before_e( let (_, else_branch, state) = space0_before_e(
specialize_ref(If::ElseBranch, move |arena, state| { specialize_ref(If::ElseBranch, move |arena, state| {
parse_loc_expr_with_options(min_indent, multi_backpassing, arena, state) parse_loc_expr_with_options(min_indent, options, arena, state)
}), }),
min_indent, min_indent,
If::Space, If::Space,

View File

@ -42,15 +42,15 @@ pub enum PackageOrPath<'a> {
#[derive(Clone, PartialEq, Eq, Debug, Hash)] #[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct ModuleName<'a>(&'a str); pub struct ModuleName<'a>(&'a str);
impl<'a> Into<&'a str> for ModuleName<'a> { impl<'a> From<ModuleName<'a>> for &'a str {
fn into(self) -> &'a str { fn from(name: ModuleName<'a>) -> Self {
self.0 name.0
} }
} }
impl<'a> Into<InlinableString> for ModuleName<'a> { impl<'a> From<ModuleName<'a>> for InlinableString {
fn into(self) -> InlinableString { fn from(name: ModuleName<'a>) -> InlinableString {
self.0.into() name.0.into()
} }
} }

View File

@ -185,7 +185,7 @@ pub enum SyntaxError<'a> {
ReservedKeyword(Region), ReservedKeyword(Region),
ArgumentsBeforeEquals(Region), ArgumentsBeforeEquals(Region),
NotYetImplemented(String), NotYetImplemented(String),
TODO, Todo,
Type(Type<'a>), Type(Type<'a>),
Pattern(EPattern<'a>), Pattern(EPattern<'a>),
Expr(EExpr<'a>), Expr(EExpr<'a>),
@ -360,6 +360,7 @@ impl<'a> SyntaxError<'a> {
pub fn into_parse_problem( pub fn into_parse_problem(
self, self,
filename: std::path::PathBuf, filename: std::path::PathBuf,
prefix: &'a str,
bytes: &'a [u8], bytes: &'a [u8],
) -> ParseProblem<'a, SyntaxError<'a>> { ) -> ParseProblem<'a, SyntaxError<'a>> {
ParseProblem { ParseProblem {
@ -368,6 +369,7 @@ impl<'a> SyntaxError<'a> {
problem: self, problem: self,
filename, filename,
bytes, bytes,
prefix,
} }
} }
} }
@ -379,6 +381,7 @@ pub type Col = u16;
pub enum EExpr<'a> { pub enum EExpr<'a> {
Start(Row, Col), Start(Row, Col),
End(Row, Col), End(Row, Col),
BadExprEnd(Row, Col),
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
Dot(Row, Col), Dot(Row, Col),
@ -679,6 +682,8 @@ pub struct ParseProblem<'a, T> {
pub problem: T, pub problem: T,
pub filename: std::path::PathBuf, pub filename: std::path::PathBuf,
pub bytes: &'a [u8], pub bytes: &'a [u8],
/// prefix is usually the header (for parse problems in the body), or empty
pub prefix: &'a str,
} }
pub trait Parser<'a, Output, Error> { pub trait Parser<'a, Output, Error> {
@ -926,8 +931,8 @@ where
state = next_state; state = next_state;
buf.push(next_output); buf.push(next_output);
} }
Err((element_progress, fail, state)) => { Err((_, fail, state)) => {
return Err((element_progress, fail, state)); return Err((MadeProgress, fail, state));
} }
} }
} }

View File

@ -62,8 +62,8 @@ pub fn loc_pattern_help<'a>(
EPattern::Record, EPattern::Record,
crate::pattern::record_pattern_help(min_indent) crate::pattern::record_pattern_help(min_indent)
)), )),
loc!(number_pattern_help()),
loc!(string_pattern_help()), loc!(string_pattern_help()),
loc!(number_pattern_help())
) )
} }

View File

@ -2550,6 +2550,247 @@ mod test_parse {
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
#[test]
fn when_with_negative_numbers() {
let arena = Bump::new();
let newlines = &[Newline];
let pattern1 = Pattern::SpaceBefore(arena.alloc(NumLiteral("1")), newlines);
let loc_pattern1 = Located::new(1, 1, 1, 2, pattern1);
let expr1 = Num("2");
let loc_expr1 = Located::new(1, 1, 6, 7, expr1);
let branch1 = &*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern1]),
value: loc_expr1,
guard: None,
});
let newlines = &[Newline];
let pattern2 = Pattern::SpaceBefore(arena.alloc(NumLiteral("-3")), newlines);
let loc_pattern2 = Located::new(2, 2, 1, 3, pattern2);
let expr2 = Num("4");
let loc_expr2 = Located::new(2, 2, 7, 8, expr2);
let branch2 = &*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern2]),
value: loc_expr2,
guard: None,
});
let branches = &[branch1, branch2];
let var = Var {
module_name: "",
ident: "x",
};
let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_expr_with(
&arena,
indoc!(
r#"
when x is
1 -> 2
-3 -> 4
"#
),
);
assert_eq!(Ok(expected), actual);
}
#[test]
fn when_with_function_application() {
let arena = Bump::new();
let newlines = &[Newline];
let pattern1 = Pattern::SpaceBefore(arena.alloc(NumLiteral("1")), newlines);
let loc_pattern1 = Located::new(1, 1, 4, 5, pattern1);
let num_2 = Num("2");
let num_neg = Located::new(
1,
1,
9,
16,
Expr::Var {
module_name: "Num",
ident: "neg",
},
);
let expr0 = Located::new(2, 2, 5, 6, Expr::SpaceBefore(&num_2, &[Newline]));
let expr1 = Expr::Apply(
&num_neg,
&*arena.alloc([&*arena.alloc(expr0)]),
CalledVia::Space,
);
let loc_expr1 = Located::new(1, 2, 9, 6, expr1);
let branch1 = &*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern1]),
value: loc_expr1,
guard: None,
});
let newlines = &[Newline];
let pattern2 = Pattern::SpaceBefore(arena.alloc(Underscore("")), newlines);
let loc_pattern2 = Located::new(3, 3, 4, 5, pattern2);
let expr2 = Num("4");
let loc_expr2 = Located::new(3, 3, 9, 10, expr2);
let branch2 = &*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern2]),
value: loc_expr2,
guard: None,
});
let branches = &[branch1, branch2];
let var = Var {
module_name: "",
ident: "x",
};
let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_expr_with(
&arena,
indoc!(
r#"
when x is
1 -> Num.neg
2
_ -> 4
"#
),
);
assert_eq!(Ok(expected), actual);
}
#[test]
fn when_if_guard() {
let arena = Bump::new();
let branch1 = {
let newlines = &[Newline];
let pattern1 = Pattern::SpaceBefore(arena.alloc(Underscore("")), newlines);
let loc_pattern1 = Located::new(1, 1, 4, 5, pattern1);
let num_1 = Num("1");
let expr1 = Located::new(
2,
2,
8,
9,
Expr::SpaceBefore(arena.alloc(num_1), &[Newline]),
);
let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1);
&*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern1]),
value: loc_expr1,
guard: None,
})
};
let branch2 = {
let pattern1 = Pattern::SpaceBefore(arena.alloc(Underscore("")), &[Newline, Newline]);
let loc_pattern1 = Located::new(4, 4, 4, 5, pattern1);
let num_1 = Num("2");
let expr1 = Located::new(
5,
5,
8,
9,
Expr::SpaceBefore(arena.alloc(num_1), &[Newline]),
);
let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1);
&*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern1]),
value: loc_expr1,
guard: None,
})
};
let branch3 = {
let pattern1 =
Pattern::SpaceBefore(arena.alloc(Pattern::GlobalTag("Ok")), &[Newline, Newline]);
let loc_pattern1 = Located::new(7, 7, 4, 6, pattern1);
let num_1 = Num("3");
let expr1 = Located::new(
8,
8,
8,
9,
Expr::SpaceBefore(arena.alloc(num_1), &[Newline]),
);
let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1);
&*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern1]),
value: loc_expr1,
guard: None,
})
};
let branches = &[branch1, branch2, branch3];
let var = Var {
module_name: "",
ident: "x",
};
let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_expr_with(
&arena,
indoc!(
r#"
when x is
_ ->
1
_ ->
2
Ok ->
3
"#
),
);
assert_eq!(Ok(expected), actual);
}
#[test]
fn when_in_parens() {
let arena = Bump::new();
let branch1 = {
let newlines = &[Newline];
let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::GlobalTag("Ok")), newlines);
let loc_pattern1 = Located::new(1, 1, 4, 6, pattern1);
let num_1 = Num("3");
let expr1 = Located::new(
2,
2,
8,
9,
Expr::SpaceBefore(arena.alloc(num_1), &[Newline]),
);
let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1);
&*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern1]),
value: loc_expr1,
guard: None,
})
};
let branches = &[branch1];
let var = Var {
module_name: "",
ident: "x",
};
let loc_cond = Located::new(0, 0, 6, 7, var);
let when = Expr::When(arena.alloc(loc_cond), branches);
let expected = Expr::ParensAround(&when);
let actual = parse_expr_with(
&arena,
indoc!(
r#"
(when x is
Ok ->
3)
"#
),
);
assert_eq!(Ok(expected), actual);
}
#[test] #[test]
fn when_with_records() { fn when_with_records() {
let arena = Bump::new(); let arena = Bump::new();
@ -2599,6 +2840,47 @@ mod test_parse {
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
#[test]
fn when_in_parens_indented() {
let arena = Bump::new();
let branch1 = {
let newlines = &[Newline];
let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::GlobalTag("Ok")), newlines);
let loc_pattern1 = Located::new(1, 1, 4, 6, pattern1);
let num_1 = Num("3");
let expr1 = Located::new(1, 1, 10, 11, num_1);
let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1);
&*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern1]),
value: loc_expr1,
guard: None,
})
};
let branches = &[branch1];
let var = Var {
module_name: "",
ident: "x",
};
let loc_cond = Located::new(0, 0, 6, 7, var);
let when = Expr::When(arena.alloc(loc_cond), branches);
let spaced = Expr::SpaceAfter(&when, &[Newline]);
let expected = Expr::ParensAround(&spaced);
let actual = parse_expr_with(
&arena,
indoc!(
r#"
(when x is
Ok -> 3
)
"#
),
);
assert_eq!(Ok(expected), actual);
}
#[test] #[test]
fn when_with_alternative_patterns() { fn when_with_alternative_patterns() {
let arena = Bump::new(); let arena = Bump::new();
@ -2620,9 +2902,9 @@ mod test_parse {
let pattern2_alt = let pattern2_alt =
Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("bar"))), newlines); Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("bar"))), newlines);
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, 2, 7, pattern2_alt);
let expr2 = Num("2"); let expr2 = Num("2");
let loc_expr2 = Located::new(3, 3, 10, 11, expr2); let loc_expr2 = Located::new(3, 3, 11, 12, expr2);
let branch2 = &*arena.alloc(WhenBranch { let branch2 = &*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern2, loc_pattern2_alt]), patterns: arena.alloc([loc_pattern2, loc_pattern2_alt]),
value: loc_expr2, value: loc_expr2,

View File

@ -134,6 +134,7 @@ pub enum RuntimeError {
}, },
InvalidPrecedence(PrecedenceProblem, Region), InvalidPrecedence(PrecedenceProblem, Region),
MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region), MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region),
MalformedTypeName(Box<str>, Region),
MalformedClosure(Region), MalformedClosure(Region),
InvalidRecordUpdate { InvalidRecordUpdate {
region: Region, region: Region,

View File

@ -776,8 +776,20 @@ fn pretty_runtime_error<'b>(
} }
RuntimeError::MalformedIdentifier(_box_str, bad_ident, surroundings) => { RuntimeError::MalformedIdentifier(_box_str, bad_ident, surroundings) => {
to_bad_ident_expr_report(alloc, bad_ident, surroundings) to_bad_ident_expr_report(alloc, bad_ident, surroundings)
}
RuntimeError::MalformedTypeName(_box_str, surroundings) => {
alloc.stack(vec![
alloc.reflow(r"I am confused by this type name:"),
alloc.region(surroundings),
alloc.concat(vec![
alloc.reflow("Type names start with an uppercase letter, "),
alloc.reflow("and can optionally be qualified by a module name, like "),
alloc.parser_suggestion("Bool"),
alloc.reflow(" or "),
alloc.parser_suggestion("Http.Request.Request"),
alloc.reflow("."),
]),
])
} }
RuntimeError::MalformedClosure(_) => todo!(""), RuntimeError::MalformedClosure(_) => todo!(""),
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str) RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)

View File

@ -272,7 +272,14 @@ fn to_expr_report<'a>(
]) ])
.indent(4), .indent(4),
])], ])],
b"->" => vec![alloc.stack(vec![ b"->" => match context {
Context::InNode(Node::WhenBranch, _row, _col, _) => {
return to_unexpected_arrow_report(
alloc, filename, *row, *col, start_row, start_col,
);
}
_ => {
vec![alloc.stack(vec![
alloc.concat(vec![ alloc.concat(vec![
alloc.reflow("The arrow "), alloc.reflow("The arrow "),
alloc.parser_suggestion("->"), alloc.parser_suggestion("->"),
@ -287,7 +294,9 @@ fn to_expr_report<'a>(
alloc.text("Green -> \"go!\"").indent(4), alloc.text("Green -> \"go!\"").indent(4),
]) ])
.indent(4), .indent(4),
])], ])]
}
},
b"!" => vec![ b"!" => vec![
alloc.reflow("The boolean negation operator "), alloc.reflow("The boolean negation operator "),
alloc.parser_suggestion("!"), alloc.parser_suggestion("!"),
@ -458,6 +467,27 @@ fn to_expr_report<'a>(
*col, *col,
), ),
EExpr::BadExprEnd(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("Whatever I am running into is confusing me a lot! "),
alloc.reflow("Normally I can give fairly specific hints, "),
alloc.reflow("but something is really tripping me up this time."),
]),
]);
Report {
filename,
doc,
title: "SYNTAX PROBLEM".to_string(),
}
}
EExpr::Colon(row, col) => { EExpr::Colon(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col); let region = Region::from_row_col(*row, *col);
@ -982,7 +1012,7 @@ fn to_list_report<'a>(
), ),
List::Open(row, col) | List::End(row, col) => { List::Open(row, col) | List::End(row, col) => {
match dbg!(what_is_next(alloc.src_lines, row, col)) { match what_is_next(alloc.src_lines, row, col) {
Next::Other(Some(',')) => { Next::Other(Some(',')) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col); let region = Region::from_row_col(row, col);
@ -1388,6 +1418,12 @@ fn to_unfinished_when_report<'a>(
start_col: Col, start_col: Col,
message: RocDocBuilder<'a>, message: RocDocBuilder<'a>,
) -> Report<'a> { ) -> Report<'a> {
match what_is_next(alloc.src_lines, row, col) {
Next::Token("->") => {
to_unexpected_arrow_report(alloc, filename, row, col, start_row, start_col)
}
_ => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col); let region = Region::from_row_col(row, col);
@ -1407,6 +1443,43 @@ fn to_unfinished_when_report<'a>(
doc, doc,
title: "UNFINISHED WHEN".to_string(), title: "UNFINISHED WHEN".to_string(),
} }
}
}
}
fn to_unexpected_arrow_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
row: Row,
col: Col,
start_row: Row,
start_col: Col,
) -> Report<'a> {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_rows_cols(row, col, row, col + 2);
let doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow(r"I am parsing a "),
alloc.keyword("when"),
alloc.reflow(r" expression right now, but this arrow is confusing me:"),
]),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"It makes sense to see arrows around here, "),
alloc.reflow(r"so I suspect it is something earlier."),
alloc.reflow(
r"Maybe this pattern is indented a bit farther from the previous patterns?",
),
]),
note_for_when_error(alloc),
]);
Report {
filename,
doc,
title: "UNEXPECTED ARROW".to_string(),
}
} }
fn note_for_when_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { fn note_for_when_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> {

View File

@ -129,7 +129,7 @@ mod test_reporting {
let alloc = RocDocAllocator::new(&src_lines, home, &interns); let alloc = RocDocAllocator::new(&src_lines, home, &interns);
let problem = fail.into_parse_problem(filename.clone(), src.as_bytes()); let problem = fail.into_parse_problem(filename.clone(), "", src.as_bytes());
let doc = parse_problem(&alloc, filename, 0, problem); let doc = parse_problem(&alloc, filename, 0, problem);
callback(doc.pretty(&alloc).append(alloc.line()), buf) callback(doc.pretty(&alloc).append(alloc.line()), buf)
@ -190,8 +190,11 @@ mod test_reporting {
let alloc = RocDocAllocator::new(&src_lines, home, &interns); let alloc = RocDocAllocator::new(&src_lines, home, &interns);
use roc_parse::parser::SyntaxError; use roc_parse::parser::SyntaxError;
let problem = let problem = SyntaxError::Header(fail).into_parse_problem(
SyntaxError::Header(fail).into_parse_problem(filename.clone(), src.as_bytes()); filename.clone(),
"",
src.as_bytes(),
);
let doc = parse_problem(&alloc, filename, 0, problem); let doc = parse_problem(&alloc, filename, 0, problem);
callback(doc.pretty(&alloc).append(alloc.line()), buf) callback(doc.pretty(&alloc).append(alloc.line()), buf)
@ -4547,7 +4550,19 @@ mod test_reporting {
f f
"# "#
), ),
indoc!(r#""#), indoc!(
r#"
SYNTAX PROBLEM
I am confused by this type name:
1 f : Foo..Bar
^^^^^^^^
Type names start with an uppercase letter, and can optionally be
qualified by a module name, like Bool or Http.Request.Request.
"#
),
) )
// ── DOUBLE DOT ────────────────────────────────────────────────────────────────── // ── DOUBLE DOT ──────────────────────────────────────────────────────────────────
@ -4570,7 +4585,19 @@ mod test_reporting {
f f
"# "#
), ),
indoc!(r#""#), indoc!(
r#"
SYNTAX PROBLEM
I am confused by this type name:
1 f : Foo.Bar.
^^^^^^^^
Type names start with an uppercase letter, and can optionally be
qualified by a module name, like Bool or Http.Request.Request.
"#
),
) )
// ── TRAILING DOT ──────────────────────────────────────────────────────────────── // ── TRAILING DOT ────────────────────────────────────────────────────────────────
@ -4618,7 +4645,19 @@ mod test_reporting {
f f
"# "#
), ),
indoc!(r#""#), indoc!(
r#"
SYNTAX PROBLEM
I am confused by this type name:
1 f : Foo.1
^^^^^
Type names start with an uppercase letter, and can optionally be
qualified by a module name, like Bool or Http.Request.Request.
"#
),
) )
// ── WEIRD QUALIFIED NAME ──────────────────────────────────────────────────────── // ── WEIRD QUALIFIED NAME ────────────────────────────────────────────────────────
@ -4642,7 +4681,19 @@ mod test_reporting {
f f
"# "#
), ),
indoc!(r#""#), indoc!(
r#"
SYNTAX PROBLEM
I am confused by this type name:
1 f : Foo.foo
^^^^^^^
Type names start with an uppercase letter, and can optionally be
qualified by a module name, like Bool or Http.Request.Request.
"#
),
) )
} }
@ -4915,7 +4966,6 @@ mod test_reporting {
#[test] #[test]
fn empty_or_pattern() { fn empty_or_pattern() {
// this should get better with time
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
@ -4929,29 +4979,16 @@ mod test_reporting {
), ),
indoc!( indoc!(
r#" r#"
MISSING EXPRESSION UNFINISHED PATTERN
I am partway through parsing a definition, but I got stuck here: I just started parsing a pattern, but I got stuck here:
1 when Just 4 is
2 Just 4 | -> 2 Just 4 | ->
^ ^
I was expecting to see an expression like 42 or "hello". Note: I may be confused by indentation
"# "#
), ),
// indoc!(
// r#"
// ── UNFINISHED PATTERN ──────────────────────────────────────────────────────────
//
// I just started parsing a pattern, but I got stuck here:
//
// 2│ Just 4 | ->
// ^
//
// Note: I may be confused by indentation
// "#
// ),
) )
} }
@ -5078,6 +5115,57 @@ mod test_reporting {
#[test] #[test]
fn when_outdented_branch() { fn when_outdented_branch() {
// this should get better with time // this should get better with time
report_problem_as(
indoc!(
r#"
when 4 is
5 -> 2
2 -> 2
"#
),
indoc!(
r#"
SYNTAX PROBLEM
I got stuck here:
1 when 4 is
2 5 -> 2
^
Whatever I am running into is confusing me a lot! Normally I can give
fairly specific hints, but something is really tripping me up this
time.
"#
),
// TODO this formerly gave
//
// ── UNFINISHED WHEN ─────────────────────────────────────────────────────────────
//
// I was partway through parsing a `when` expression, but I got stuck here:
//
// 3│ _ -> 2
// ^
//
// I suspect this is a pattern that is not indented enough? (by 2 spaces)
//
// but that requires parsing the next pattern blindly, irrespective of indentation. Can
// we find an efficient solution that doesn't require parsing an extra pattern for
// every `when`, i.e. we want a good error message for the test case above, but for
// a valid `when`, we don't want to do extra work, e.g. here
//
// x
// when x is
// n -> n
//
// 4
//
// We don't want to parse the `4` and say it's an outdented pattern!
)
}
#[test]
fn when_over_indented_underscore() {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
@ -5088,14 +5176,45 @@ mod test_reporting {
), ),
indoc!( indoc!(
r#" r#"
UNFINISHED WHEN SYNTAX PROBLEM
I was partway through parsing a `when` expression, but I got stuck here: I got stuck here:
3 _ -> 2 1 when 4 is
2 5 -> 2
^ ^
I suspect this is a pattern that is not indented enough? (by 2 spaces) Whatever I am running into is confusing me a lot! Normally I can give
fairly specific hints, but something is really tripping me up this
time.
"#
),
)
}
#[test]
fn when_over_indented_int() {
report_problem_as(
indoc!(
r#"
when 4 is
5 -> Num.neg
2 -> 2
"#
),
indoc!(
r#"
UNEXPECTED ARROW
I am parsing a `when` expression right now, but this arrow is confusing
me:
3 2 -> 2
^^
It makes sense to see arrows around here, so I suspect it is something
earlier.Maybe this pattern is indented a bit farther from the previous
patterns?
Note: Here is an example of a valid `when` expression for reference. Note: Here is an example of a valid `when` expression for reference.
@ -5757,21 +5876,18 @@ mod test_reporting {
), ),
indoc!( indoc!(
r#" r#"
MISSING FINAL EXPRESSION UNKNOWN OPERATOR
I am partway through parsing a definition's final expression, but I This looks like an operator, but it's not one I recognize!
got stuck here:
1 main = 5 -> 3 1 main = 5 -> 3
^ ^^
This definition is missing a final expression. A nested definition The arrow -> is only used to define cases in a `when`.
must be followed by either another definition, or an expression
x = 4 when color is
y = 2 Red -> "stop!"
Green -> "go!"
x + y
"# "#
), ),
) )

View File

@ -880,7 +880,7 @@ fn check_for_infinite_type(
let var = loc_var.value; let var = loc_var.value;
let is_uniq_infer = matches!( let is_uniq_infer = matches!(
subs.get(var).content, subs.get_ref(var).content,
Content::Alias(Symbol::ATTR_ATTR, _, _) Content::Alias(Symbol::ATTR_ATTR, _, _)
); );
@ -1088,7 +1088,7 @@ fn generalize(
for vars in all_but_last_pool { for vars in all_but_last_pool {
for &var in vars { for &var in vars {
if !subs.redundant(var) { if !subs.redundant(var) {
let rank = subs.get(var).rank; let rank = subs.get_rank(var);
pools.get_mut(rank).push(var); pools.get_mut(rank).push(var);
} }
@ -1099,13 +1099,12 @@ fn generalize(
// otherwise generalize // otherwise generalize
for &var in last_pool { for &var in last_pool {
if !subs.redundant(var) { if !subs.redundant(var) {
let mut desc = subs.get(var); let desc_rank = subs.get_rank(var);
if desc.rank < young_rank { if desc_rank < young_rank {
pools.get_mut(desc.rank).push(var); pools.get_mut(desc_rank).push(var);
} else { } else {
desc.rank = Rank::NONE; subs.set_rank(var, Rank::NONE);
subs.set(var, desc);
} }
} }
} }
@ -1121,18 +1120,8 @@ fn pool_to_rank_table(
// Sort the variables into buckets by rank. // Sort the variables into buckets by rank.
for &var in young_vars.iter() { for &var in young_vars.iter() {
let desc = subs.get(var); let rank = subs.get_rank(var);
let rank = desc.rank; subs.set_mark(var, young_mark);
subs.set(
var,
Descriptor {
rank,
mark: young_mark,
content: desc.content,
copy: desc.copy,
},
);
debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); debug_assert!(rank.into_usize() < young_rank.into_usize() + 1);
pools.get_mut(rank).push(var); pools.get_mut(rank).push(var);
@ -1155,24 +1144,15 @@ fn adjust_rank(
if desc.mark == young_mark { if desc.mark == young_mark {
let Descriptor { let Descriptor {
content, content,
rank, rank: _,
mark: _, mark: _,
copy, copy,
} = desc; } = desc;
// Mark the variable as visited before adjusting content, as it may be cyclic. // Mark the variable as visited before adjusting content, as it may be cyclic.
subs.set( subs.set_mark(var, visit_mark);
var,
Descriptor {
content: content.clone(),
rank,
mark: visit_mark,
copy,
},
);
let max_rank = let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, &content);
adjust_rank_content(subs, young_mark, visit_mark, group_rank, content.clone());
subs.set( subs.set(
var, var,
@ -1208,7 +1188,7 @@ fn adjust_rank_content(
young_mark: Mark, young_mark: Mark,
visit_mark: Mark, visit_mark: Mark,
group_rank: Rank, group_rank: Rank,
content: Content, content: &Content,
) -> Rank { ) -> Rank {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;
@ -1224,14 +1204,15 @@ fn adjust_rank_content(
let mut rank = Rank::toplevel(); let mut rank = Rank::toplevel();
for var in args { for var in args {
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); rank =
rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
} }
rank rank
} }
Func(arg_vars, closure_var, ret_var) => { Func(arg_vars, closure_var, ret_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, ret_var); let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ret_var);
// TODO investigate further. // TODO investigate further.
// //
@ -1244,12 +1225,13 @@ fn adjust_rank_content(
young_mark, young_mark,
visit_mark, visit_mark,
group_rank, group_rank,
closure_var, *closure_var,
)); ));
} }
for var in arg_vars { for var in arg_vars {
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); rank =
rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
} }
rank rank
@ -1263,9 +1245,9 @@ fn adjust_rank_content(
EmptyTagUnion => Rank::toplevel(), EmptyTagUnion => Rank::toplevel(),
Record(fields, ext_var) => { Record(fields, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, ext_var); let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
for (_, var) in fields { for var in fields.values() {
rank = rank.max(adjust_rank( rank = rank.max(adjust_rank(
subs, subs,
young_mark, young_mark,
@ -1279,7 +1261,7 @@ fn adjust_rank_content(
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, ext_var); let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
for var in tags.values().flatten() { for var in tags.values().flatten() {
rank = rank =
@ -1290,9 +1272,9 @@ fn adjust_rank_content(
} }
RecursiveTagUnion(rec_var, tags, ext_var) => { RecursiveTagUnion(rec_var, tags, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, rec_var); let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *rec_var);
rank = rank.max(adjust_rank( rank = rank.max(adjust_rank(
subs, young_mark, visit_mark, group_rank, ext_var, subs, young_mark, visit_mark, group_rank, *ext_var,
)); ));
for var in tags.values().flatten() { for var in tags.values().flatten() {
@ -1305,10 +1287,11 @@ fn adjust_rank_content(
Boolean(Bool::Shared) => Rank::toplevel(), Boolean(Bool::Shared) => Rank::toplevel(),
Boolean(Bool::Container(cvar, mvars)) => { Boolean(Bool::Container(cvar, mvars)) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, cvar); let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *cvar);
for var in mvars { for var in mvars {
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); rank =
rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
} }
rank rank
@ -1322,13 +1305,13 @@ fn adjust_rank_content(
let mut rank = Rank::toplevel(); let mut rank = Rank::toplevel();
for (_, var) in args { for (_, var) in args {
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
} }
// from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel() // from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel()
// this theory is not true in Roc! aliases of function types capture the closure var // this theory is not true in Roc! aliases of function types capture the closure var
rank = rank.max(adjust_rank( rank = rank.max(adjust_rank(
subs, young_mark, visit_mark, group_rank, real_var, subs, young_mark, visit_mark, group_rank, *real_var,
)); ));
rank rank

View File

@ -1760,6 +1760,13 @@ fn list_sum() {
assert_evals_to!("List.sum [ 1.1, 2.2, 3.3 ]", 6.6, f64); assert_evals_to!("List.sum [ 1.1, 2.2, 3.3 ]", 6.6, f64);
} }
#[test]
fn list_product() {
assert_evals_to!("List.product []", 1, i64);
assert_evals_to!("List.product [ 1, 2, 3 ]", 6, i64);
assert_evals_to!("List.product [ 1.1, 2.2, 3.3 ]", 1.1 * 2.2 * 3.3, f64);
}
#[test] #[test]
fn list_keep_oks() { fn list_keep_oks() {
assert_evals_to!("List.keepOks [] (\\x -> x)", 0, i64); assert_evals_to!("List.keepOks [] (\\x -> x)", 0, i64);

View File

@ -361,7 +361,6 @@ mod gen_num {
#[test] #[test]
fn f64_sqrt() { fn f64_sqrt() {
// FIXME this works with normal types, but fails when checking uniqueness types
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -375,6 +374,96 @@ mod gen_num {
); );
} }
#[test]
fn f64_log() {
assert_evals_to!(
indoc!(
r#"
when Num.log 7.38905609893 is
Ok val -> val
Err _ -> -1
"#
),
1.999999999999912,
f64
);
}
#[test]
fn f64_log_one() {
assert_evals_to!(
indoc!(
r#"
when Num.log 1 is
Ok val -> val
Err _ -> -1
"#
),
0.0,
f64
);
}
#[test]
fn f64_sqrt_zero() {
assert_evals_to!(
indoc!(
r#"
when Num.sqrt 0 is
Ok val -> val
Err _ -> -1
"#
),
0.0,
f64
);
}
#[test]
fn f64_sqrt_negative() {
assert_evals_to!(
indoc!(
r#"
when Num.sqrt -1 is
Err _ -> 42
Ok val -> val
"#
),
42.0,
f64
);
}
#[test]
fn f64_log_zero() {
assert_evals_to!(
indoc!(
r#"
when Num.log 0 is
Err _ -> 42
Ok val -> val
"#
),
42.0,
f64
);
}
#[test]
fn f64_log_negative() {
assert_evals_to!(
indoc!(
r#"
when Num.log -1 is
Err _ -> 42
Ok val -> val
"#
),
42.0,
f64
);
}
#[test] #[test]
fn f64_round_old() { fn f64_round_old() {
assert_evals_to!("Num.round 3.6", 4, i64); assert_evals_to!("Num.round 3.6", 4, i64);

View File

@ -2258,3 +2258,41 @@ fn backpassing_result() {
i64 i64
); );
} }
#[test]
#[should_panic(
expected = "Shadowing { original_region: |L 3-3, C 4-5|, shadow: |L 5-5, C 6-7| Ident(\\\"x\\\") }"
)]
fn function_malformed_pattern() {
assert_evals_to!(
indoc!(
r#"
x = 3
(\x -> x) 42
"#
),
3,
i64
);
}
#[test]
#[should_panic(expected = "Hit an erroneous type when creating a layout for")]
fn call_invalid_layout() {
assert_llvm_evals_to!(
indoc!(
r#"
f : I64 -> I64
f = \x -> x
f {}
"#
),
3,
i64,
|x| x,
false,
true
);
}

View File

@ -37,6 +37,7 @@ pub fn helper<'a>(
src: &str, src: &str,
stdlib: &'a roc_builtins::std::StdLib, stdlib: &'a roc_builtins::std::StdLib,
leak: bool, leak: bool,
ignore_problems: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) { ) -> (&'static str, String, Library) {
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope}; use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
@ -74,7 +75,7 @@ pub fn helper<'a>(
let mut loaded = match loaded { let mut loaded = match loaded {
Ok(x) => x, Ok(x) => x,
Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => { Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
println!("{}", report); println!("{}", report);
panic!(); panic!();
} }
@ -170,13 +171,13 @@ pub fn helper<'a>(
println!("{}", lines.join("\n")); println!("{}", lines.join("\n"));
// only crash at this point if there were no delayed_errors // only crash at this point if there were no delayed_errors
if delayed_errors.is_empty() { if delayed_errors.is_empty() && !ignore_problems {
assert_eq!(0, 1, "Mistakes were made"); assert_eq!(0, 1, "Mistakes were made");
} }
} }
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
Some(found) => found.clone(), Some(found) => *found,
None => panic!( None => panic!(
"The main function symbol {:?} does not have a procedure in {:?}", "The main function symbol {:?} does not have a procedure in {:?}",
main_fn_symbol, main_fn_symbol,
@ -331,7 +332,7 @@ pub fn helper<'a>(
#[macro_export] #[macro_export]
macro_rules! assert_llvm_evals_to { macro_rules! assert_llvm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr, $ignore_problems:expr) => {
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use roc_gen::run_jit_function; use roc_gen::run_jit_function;
@ -343,7 +344,7 @@ macro_rules! assert_llvm_evals_to {
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let (main_fn_name, errors, lib) = let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context); $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, $ignore_problems, &context);
let transform = |success| { let transform = |success| {
let expected = $expected; let expected = $expected;
@ -354,7 +355,7 @@ macro_rules! assert_llvm_evals_to {
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
assert_llvm_evals_to!($src, $expected, $ty, $transform, true); assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false);
}; };
} }
@ -375,7 +376,7 @@ macro_rules! assert_evals_to {
// parsing the source, so that there's no chance their passing // parsing the source, so that there's no chance their passing
// or failing depends on leftover state from the previous one. // or failing depends on leftover state from the previous one.
{ {
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak); assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak, false);
} }
{ {
// NOTE at the moment, the optimized tests do the same thing // NOTE at the moment, the optimized tests do the same thing
@ -392,7 +393,7 @@ macro_rules! assert_non_opt_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument. // Same as above, except with an additional transformation argument.
{ {
assert_llvm_evals_to!($src, $expected, $ty, $transform, true); assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false);
} }
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {{ ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {{

View File

@ -93,9 +93,9 @@ impl VarStore {
} }
} }
impl Into<Variable> for VarStore { impl From<VarStore> for Variable {
fn into(self) -> Variable { fn from(store: VarStore) -> Self {
Variable(self.next) Variable(store.next)
} }
} }
@ -139,9 +139,9 @@ impl fmt::Debug for OptVariable {
} }
} }
impl Into<Option<Variable>> for OptVariable { impl From<OptVariable> for Option<Variable> {
fn into(self) -> Option<Variable> { fn from(opt_var: OptVariable) -> Self {
self.into_variable() opt_var.into_variable()
} }
} }
@ -180,9 +180,9 @@ impl Variable {
} }
} }
impl Into<OptVariable> for Variable { impl From<Variable> for OptVariable {
fn into(self) -> OptVariable { fn from(var: Variable) -> Self {
OptVariable(self.0) OptVariable(var.0)
} }
} }
@ -306,6 +306,18 @@ impl Subs {
self.utable.probe_value(key) self.utable.probe_value(key)
} }
pub fn get_ref(&self, key: Variable) -> &Descriptor {
&self.utable.probe_value_ref(key).value
}
pub fn get_rank(&mut self, key: Variable) -> Rank {
self.utable.probe_value_ref(key).value.rank
}
pub fn get_mark(&mut self, key: Variable) -> Mark {
self.utable.probe_value_ref(key).value.mark
}
pub fn get_without_compacting(&self, key: Variable) -> Descriptor { pub fn get_without_compacting(&self, key: Variable) -> Descriptor {
self.utable.probe_value_without_compacting(key) self.utable.probe_value_without_compacting(key)
} }
@ -471,9 +483,9 @@ impl fmt::Debug for Rank {
} }
} }
impl Into<usize> for Rank { impl From<Rank> for usize {
fn into(self) -> usize { fn from(rank: Rank) -> Self {
self.0 rank.0
} }
} }

View File

@ -1,5 +1,5 @@
[package] [package]
name = "docs" name = "roc_docs"
version = "0.1.0" version = "0.1.0"
authors = ["Pablo Hirafuji <pablohirafuji@gmail.com>"] authors = ["Pablo Hirafuji <pablohirafuji@gmail.com>"]
edition = "2018" edition = "2018"

View File

@ -1,5 +1,4 @@
extern crate fs_extra; extern crate fs_extra;
extern crate handlebars;
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
@ -19,26 +18,26 @@ use std::path::{Path, PathBuf};
#[derive(Serialize)] #[derive(Serialize)]
pub struct Template { pub struct Template {
package_name: String, pub package_name: String,
package_version: String, pub package_version: String,
module_name: String, pub module_name: String,
module_docs: String, pub module_docs: String,
module_entries: Vec<ModuleEntry>, pub module_entries: Vec<ModuleEntry>,
module_links: Vec<TemplateLink>, pub module_links: Vec<TemplateLink>,
} }
#[derive(Serialize, Clone, Debug, PartialEq)] #[derive(Serialize, Clone, Debug, PartialEq)]
pub struct ModuleEntry { pub struct ModuleEntry {
name: String, pub name: String,
docs: String, pub docs: String,
} }
#[derive(Serialize)] #[derive(Serialize)]
pub struct TemplateLink { pub struct TemplateLink {
name: String, pub name: String,
href: String, pub href: String,
classes: String, pub classes: String,
entries: Vec<TemplateLinkEntry>, pub entries: Vec<TemplateLinkEntry>,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -46,27 +45,9 @@ pub struct TemplateLinkEntry {
name: String, name: String,
} }
fn main() { pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
generate( let files_docs = files_to_documentations(filenames, std_lib);
vec![ //
PathBuf::from(r"../compiler/builtins/docs/Bool.roc"),
PathBuf::from(r"../compiler/builtins/docs/Dict.roc"),
// Not working
// PathBuf::from(r"../compiler/builtins/docs/List.roc"),
// Not working
// PathBuf::from(r"../compiler/builtins/docs/Num.roc"),
PathBuf::from(r"../compiler/builtins/docs/Set.roc"),
PathBuf::from(r"../compiler/builtins/docs/Str.roc"),
],
roc_builtins::std::standard_stdlib(),
Path::new("../compiler/builtins/docs"),
Path::new("./build"),
)
}
pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, src_dir: &Path, build_dir: &Path) {
let files_docs = files_to_documentations(filenames, std_lib, src_dir);
// TODO: get info from a file like "elm.json" // TODO: get info from a file like "elm.json"
let package = roc_load::docs::Documentation { let package = roc_load::docs::Documentation {
name: "roc/builtins".to_string(), name: "roc/builtins".to_string(),
@ -75,21 +56,33 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, src_dir: &Path, build_
modules: files_docs, modules: files_docs,
}; };
// Remove old build folder, if exists if !build_dir.exists() {
let _ = fs::remove_dir_all(build_dir); fs::create_dir_all(build_dir).expect("TODO gracefully handle unable to create build dir");
}
let version_folder = build_dir // Copy over the assets
.join(package.name.clone()) fs::write(
.join(package.version.clone()); build_dir.join("search.js"),
include_str!("./static/search.js"),
)
.expect("TODO gracefully handle failing to make the search javascript");
// Make sure the output directories exists fs::write(
fs::create_dir_all(&version_folder) build_dir.join("styles.css"),
.expect("TODO gracefully handle creating directories failing"); include_str!("./static/styles.css"),
)
.expect("TODO gracefully handle failing to make the stylesheet");
fs::write(
build_dir.join("favicon.svg"),
include_str!("./static/favicon.svg"),
)
.expect("TODO gracefully handle failing to make the favicon");
// Register handlebars template // Register handlebars template
let mut handlebars = handlebars::Handlebars::new(); let mut handlebars = handlebars::Handlebars::new();
handlebars handlebars
.register_template_file("page", "./src/templates/page.hbs") .register_template_file("page", "./docs/src/templates/page.hbs")
.expect("TODO gracefully handle registering template failing"); .expect("TODO gracefully handle registering template failing");
// Write each package's module docs html file // Write each package's module docs html file
@ -97,7 +90,7 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, src_dir: &Path, build_
let template = documentation_to_template_data(&package, module); let template = documentation_to_template_data(&package, module);
let handlebars_data = handlebars::to_json(&template); let handlebars_data = handlebars::to_json(&template);
let filepath = version_folder.join(format!("{}.html", module.name)); let filepath = build_dir.join(format!("{}.html", module.name));
let mut output_file = let mut output_file =
fs::File::create(filepath).expect("TODO gracefully handle creating file failing"); fs::File::create(filepath).expect("TODO gracefully handle creating file failing");
handlebars handlebars
@ -105,40 +98,31 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, src_dir: &Path, build_
.expect("TODO gracefully handle writing file failing"); .expect("TODO gracefully handle writing file failing");
} }
// Copy /static folder content to /build println!("🎉 Docs generated in {}", build_dir.display());
let copy_options = fs_extra::dir::CopyOptions {
overwrite: true,
skip_exist: false,
buffer_size: 64000, //64kb
copy_inside: false,
content_only: true,
depth: 0,
};
fs_extra::dir::copy("./src/static/", &build_dir, &copy_options)
.expect("TODO gracefully handle copying static content failing");
println!("Docs generated at {}", build_dir.display());
} }
fn files_to_documentations( pub fn files_to_documentations(
filenames: Vec<PathBuf>, filenames: Vec<PathBuf>,
std_lib: StdLib, std_lib: StdLib,
src_dir: &Path,
) -> Vec<ModuleDocumentation> { ) -> Vec<ModuleDocumentation> {
let arena = Bump::new(); let arena = Bump::new();
let mut files_docs = vec![]; let mut files_docs = vec![];
for filename in filenames { for filename in filenames {
let mut src_dir = filename.clone();
src_dir.pop();
match roc_load::file::load_and_typecheck( match roc_load::file::load_and_typecheck(
&arena, &arena,
filename, filename,
&std_lib, &std_lib,
src_dir, src_dir.as_path(),
MutMap::default(), MutMap::default(),
8, // TODO: Is it okay to hardcode ptr_bytes here? I think it should be fine since we'er only type checking (also, 8 => 32bit system) 8, // TODO: Is it okay to hardcode ptr_bytes here? I think it should be fine since we'er only type checking (also, 8 => 32bit system)
builtin_defs_map, builtin_defs_map,
) { ) {
Ok(mut loaded) => files_docs.extend(loaded.documentation.drain().map(|x| x.1)), Ok(mut loaded) => files_docs.extend(loaded.documentation.drain().map(|x| x.1)),
Err(LoadingProblem::ParsingFailedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {
println!("{}", report); println!("{}", report);
panic!(); panic!();
} }
@ -148,7 +132,10 @@ fn files_to_documentations(
files_docs files_docs
} }
fn documentation_to_template_data(doc: &Documentation, module: &ModuleDocumentation) -> Template { pub fn documentation_to_template_data(
doc: &Documentation,
module: &ModuleDocumentation,
) -> Template {
Template { Template {
package_name: doc.name.clone(), package_name: doc.name.clone(),
package_version: doc.version.clone(), package_version: doc.version.clone(),
@ -248,51 +235,3 @@ fn markdown_to_html(markdown: String) -> String {
pulldown_cmark::html::push_html(&mut docs_html, docs_parser.into_iter()); pulldown_cmark::html::push_html(&mut docs_html, docs_parser.into_iter());
docs_html docs_html
} }
#[cfg(test)]
mod test_docs {
use super::*;
#[test]
fn internal() {
let files_docs = files_to_documentations(
vec![PathBuf::from(r"tests/fixtures/Interface.roc")],
roc_builtins::std::standard_stdlib(),
Path::new("tests/fixtures"),
);
let package = roc_load::docs::Documentation {
name: "roc/builtins".to_string(),
version: "1.0.0".to_string(),
docs: "Package introduction or README.".to_string(),
modules: files_docs,
};
let expected_entries = vec![
ModuleEntry {
name: "singleline".to_string(),
docs: "<p>Single line documentation.</p>\n".to_string(),
},
ModuleEntry {
name: "multiline".to_string(),
docs: "<p>Multiline documentation.\nWithout any complex syntax yet!</p>\n".to_string(),
}, ModuleEntry {
name: "multiparagraph".to_string(),
docs: "<p>Multiparagraph documentation.</p>\n<p>Without any complex syntax yet!</p>\n".to_string(),
}, ModuleEntry {
name: "codeblock".to_string(),
docs: "<p>Turns &gt;&gt;&gt; into code block for now.</p>\n<pre><code class=\"language-roc\">codeblock</code></pre>\n".to_string(),
},
];
for module in &package.modules {
let template = documentation_to_template_data(&package, module);
assert_eq!(template.module_name, "Test");
template
.module_entries
.iter()
.zip(expected_entries.iter())
.for_each(|(x, y)| assert_eq!(x, y));
}
}
}

View File

@ -66,16 +66,14 @@ a:hover {
} }
.pkg-and-logo { .pkg-and-logo {
min-width: 0; min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
/* necessary for text-overflow: ellipsis to work in descendants */
display: flex; display: flex;
align-items: center; align-items: center;
height: 100%; height: 100%;
background-color: var(--top-bar-bg); background-color: var(--top-bar-bg);
} }
.pkg-and-logo a, .pkg-and-logo a, .pkg-and-logo a:visited {
.pkg-and-logo a:visited {
color: var(--top-bar-fg); color: var(--top-bar-fg);
} }
@ -84,18 +82,11 @@ a:hover {
text-decoration: none; text-decoration: none;
} }
.main-container {
min-width: 0;
/* necessary for text-overflow: ellipsis to work in descendants */
}
.search-button { .search-button {
flex-shrink: 0; flex-shrink: 0; /* always shrink the package name before these; they have a relatively constrained length */
/* always shrink the package name before these; they have a relatively constrained length */
padding: 12px 18px; padding: 12px 18px;
margin-right: 42px; margin-right: 42px;
display: none; display: none; /* only show this in the mobile view */
/* only show this in the mobile view */
} }
.version { .version {
@ -127,6 +118,8 @@ main {
line-height: 1.85em; line-height: 1.85em;
margin-top: 2px; margin-top: 2px;
padding: 48px; padding: 48px;
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
} }
#sidebar-nav { #sidebar-nav {
@ -160,13 +153,11 @@ main {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
flex-wrap: nowrap; flex-wrap: nowrap;
flex-grow: 1;
box-sizing: border-box; box-sizing: border-box;
font-family: var(--font-sans); font-family: var(--font-sans);
font-size: 24px; font-size: 24px;
height: 100%; height: 100%;
min-width: 0; min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
/* necessary for text-overflow: ellipsis to work in descendants */
} }
.top-header-triangle { .top-header-triangle {
@ -240,8 +231,7 @@ footer p {
margin-bottom: 48px; margin-bottom: 48px;
} }
.module-name a, .module-name a, .module-name a:visited {
.module-name a:visited {
color: inherit; color: inherit;
} }
@ -259,8 +249,7 @@ footer p {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
a, a, a:visited {
a:visited {
color: var(--link-color); color: var(--link-color);
} }
@ -325,20 +314,19 @@ pre code {
height: 0; height: 0;
} }
#module-search, #module-search, #module-search:focus {
#module-search:focus {
opacity: 1; opacity: 1;
padding: 12px 16px; padding: 12px 16px;
height: 48px; height: 48px;
} }
/* Show the "Search" label link when the text input has a placeholder */ /* Show the "Search" label link when the text input has a placeholder */
#module-search:placeholder-shown+#search-link { #module-search:placeholder-shown + #search-link {
display: flex; display: flex;
} }
/* Hide the "Search" label link when the text input has focus */ /* Hide the "Search" label link when the text input has focus */
#module-search:focus+#search-link { #module-search:focus + #search-link {
display: none; display: none;
} }
@ -398,13 +386,13 @@ pre code {
} }
} }
@media only screen and (max-device-width: 480px) { @media only screen and (max-device-width: 480px) and (orientation: portrait) {
.search-button { .search-button {
display: block; display: block; /* This is only visible in mobile. */
/* This is only visible in mobile. */
} }
.top-header { .top-header {
justify-content: space-between;
width: auto; width: auto;
} }
@ -443,21 +431,19 @@ pre code {
} }
main { main {
grid-column-start: none;
grid-column-end: none;
grid-row-start: above-footer;
grid-row-end: above-footer;
padding: 18px; padding: 18px;
font-size: 16px; font-size: 16px;
} }
.container { #sidebar-nav {
margin: 0; grid-column-start: none;
min-width: 320px; grid-column-end: none;
max-width: 100%; grid-row-start: sidebar;
} grid-row-end: sidebar;
.content {
flex-direction: column;
}
.sidebar {
margin-top: 0; margin-top: 0;
padding-left: 0; padding-left: 0;
width: auto; width: auto;
@ -478,12 +464,30 @@ pre code {
font-size: 16px; font-size: 16px;
} }
.top-header { body {
justify-content: space-between; grid-template-columns: auto;
grid-template-rows: [top-header] var(--top-header-height) [before-sidebar] auto [sidebar] auto [above-footer] auto [footer] auto;
/* [before-sidebar] 1fr [sidebar] var(--sidebar-width) [main-content] fit-content(calc(1280px - var(--sidebar-width))) [end] 1fr; */
margin: 0;
min-width: 320px;
max-width: 100%;
} }
.content { .top-header-triangle {
/* Display the sidebar below <main> without affecting tab index */ display: none;
flex-direction: column-reverse; }
.pkg-and-logo {
width: 100%;
}
.pkg-full-name {
flex-grow: 1;
}
.pkg-full-name a {
padding-top: 24px;
padding-bottom: 12px;
} }
} }

View File

@ -6,9 +6,9 @@
<title>The Roc Programming Language</title> <title>The Roc Programming Language</title>
<meta name="description" content="A language for building fast, reliable software."> <meta name="description" content="A language for building fast, reliable software.">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<link rel="icon" href="/favicon.svg"> <link rel="icon" href="favicon.svg">
<script type="text/javascript" src="/search.js" defer></script> <script type="text/javascript" src="search.js" defer></script>
<link rel="stylesheet" href="/styles.css"> <link rel="stylesheet" href="styles.css">
</head> </head>
<body> <body>

49
docs/tests/test_docs.rs Normal file
View File

@ -0,0 +1,49 @@
use roc_docs::{documentation_to_template_data, files_to_documentations, ModuleEntry};
use std::path::PathBuf;
#[cfg(test)]
mod test_docs {
use super::*;
#[test]
fn internal() {
let files_docs = files_to_documentations(
vec![PathBuf::from(r"tests/fixtures/Interface.roc")],
roc_builtins::std::standard_stdlib(),
);
let package = roc_load::docs::Documentation {
name: "roc/builtins".to_string(),
version: "1.0.0".to_string(),
docs: "Package introduction or README.".to_string(),
modules: files_docs,
};
let expected_entries = vec![
ModuleEntry {
name: "singleline".to_string(),
docs: "<p>Single line documentation.</p>\n".to_string(),
},
ModuleEntry {
name: "multiline".to_string(),
docs: "<p>Multiline documentation.\nWithout any complex syntax yet!</p>\n".to_string(),
}, ModuleEntry {
name: "multiparagraph".to_string(),
docs: "<p>Multiparagraph documentation.</p>\n<p>Without any complex syntax yet!</p>\n".to_string(),
}, ModuleEntry {
name: "codeblock".to_string(),
docs: "<p>Turns &gt;&gt;&gt; into code block for now.</p>\n<pre><code class=\"language-roc\">codeblock</code></pre>\n".to_string(),
},
];
for module in &package.modules {
let template = documentation_to_template_data(&package, module);
assert_eq!(template.module_name, "Test");
template
.module_entries
.iter()
.zip(expected_entries.iter())
.for_each(|(x, y)| assert_eq!(x, y));
}
}
}

View File

@ -48,6 +48,7 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [
"yaml_conf" "yaml_conf"
], default-features = false } ], default-features = false }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
nonempty = "0.6.0"
[dependencies.bytemuck] [dependencies.bytemuck]
version = "1.4" version = "1.4"

View File

@ -0,0 +1,76 @@
use crate::ui::text::lines::Lines;
use crate::ui::ui_error::UIResult;
use crate::ui::util::slice_get;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
#[derive(Debug)]
pub struct CodeLines {
pub lines: Vec<String>,
pub nr_of_chars: usize,
}
impl CodeLines {
pub fn from_str(code_str: &str) -> CodeLines {
CodeLines {
lines: split_inclusive(code_str),
nr_of_chars: code_str.len(),
}
}
}
//TODO use rust's split_inclusive once it's no longer unstable
fn split_inclusive(code_str: &str) -> Vec<String> {
let mut split_vec: Vec<String> = Vec::new();
let mut temp_str = String::new();
for token in code_str.chars() {
if token != '\n' {
temp_str.push(token);
} else {
split_vec.push(temp_str);
temp_str = String::new();
temp_str.push(token);
}
}
if !temp_str.is_empty() {
split_vec.push(temp_str);
}
split_vec
}
impl Lines for CodeLines {
fn get_line(&self, line_nr: usize) -> UIResult<&str> {
let line_string = slice_get(line_nr, &self.lines)?;
Ok(&line_string)
}
fn line_len(&self, line_nr: usize) -> UIResult<usize> {
self.get_line(line_nr).map(|line| line.len())
}
fn nr_of_lines(&self) -> usize {
self.lines.len()
}
fn nr_of_chars(&self) -> usize {
self.nr_of_chars
}
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> {
let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena);
for line in &self.lines {
lines.push_str(&line);
}
lines
}
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
Ok(self.get_line(line_nr)?.chars().last())
}
}

View File

@ -3,7 +3,6 @@ use crate::editor::mvc::app_model::AppModel;
use crate::editor::mvc::app_update::{ use crate::editor::mvc::app_update::{
handle_copy, handle_cut, handle_paste, pass_keydown_to_focused, handle_copy, handle_cut, handle_paste, pass_keydown_to_focused,
}; };
use crate::editor::slow_pool::SlowPool;
use winit::event::VirtualKeyCode::*; use winit::event::VirtualKeyCode::*;
use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
@ -12,7 +11,6 @@ pub fn handle_keydown(
virtual_keycode: VirtualKeyCode, virtual_keycode: VirtualKeyCode,
modifiers: ModifiersState, modifiers: ModifiersState,
app_model: &mut AppModel, app_model: &mut AppModel,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> { ) -> EdResult<()> {
if let ElementState::Released = elem_state { if let ElementState::Released = elem_state {
return Ok(()); return Ok(());
@ -20,7 +18,7 @@ pub fn handle_keydown(
match virtual_keycode { match virtual_keycode {
Left | Up | Right | Down => { Left | Up | Right | Down => {
pass_keydown_to_focused(&modifiers, virtual_keycode, app_model, markup_node_pool)? pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?
} }
Copy => handle_copy(app_model)?, Copy => handle_copy(app_model)?,
@ -42,9 +40,7 @@ pub fn handle_keydown(
} }
} }
A | Home | End => { A | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,
pass_keydown_to_focused(&modifiers, virtual_keycode, app_model, markup_node_pool)?
}
_ => (), _ => (),
} }

View File

@ -1,6 +1,5 @@
use super::keyboard_input; use super::keyboard_input;
use super::style::CODE_TXT_XY; use super::style::CODE_TXT_XY;
use super::util::slice_get;
use crate::editor::ed_error::print_ui_err; use crate::editor::ed_error::print_ui_err;
use crate::editor::resources::strings::NOTHING_OPENED; use crate::editor::resources::strings::NOTHING_OPENED;
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
@ -21,6 +20,7 @@ use crate::graphics::{
use crate::lang::expr::Env; use crate::lang::expr::Env;
use crate::lang::pool::Pool; use crate::lang::pool::Pool;
use crate::ui::ui_error::UIError::FileOpenFailed; use crate::ui::ui_error::UIError::FileOpenFailed;
use crate::ui::util::slice_get;
use bumpalo::collections::String as BumpString; use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use cgmath::Vector2; use cgmath::Vector2;
@ -153,21 +153,33 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
let mut code_str = BumpString::from_str_in("", &code_arena); let mut code_str = BumpString::from_str_in("", &code_arena);
if let Some(file_path) = file_path_opt { let file_path = if let Some(file_path) = file_path_opt {
match std::fs::read_to_string(file_path) { match std::fs::read_to_string(file_path) {
Ok(file_as_str) => { Ok(file_as_str) => {
code_str = BumpString::from_str_in(&file_as_str, &code_arena); code_str = BumpString::from_str_in(&file_as_str, &code_arena);
file_path
} }
Err(e) => print_ui_err(&FileOpenFailed { Err(e) => {
print_ui_err(&FileOpenFailed {
path_str: file_path.to_string_lossy().to_string(), path_str: file_path.to_string_lossy().to_string(),
err_msg: e.to_string(), err_msg: e.to_string(),
}), });
Path::new("")
} }
} }
} else {
Path::new("")
};
let ed_model_opt = { let ed_model_opt = {
let ed_model_res = ed_model::init_model(&code_str, env, &code_arena, &mut markup_node_pool); let ed_model_res = ed_model::init_model(
&code_str,
file_path,
env,
&code_arena,
&mut markup_node_pool,
);
match ed_model_res { match ed_model_res {
Ok(mut ed_model) => { Ok(mut ed_model) => {
@ -256,7 +268,6 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
virtual_keycode, virtual_keycode,
keyboard_modifiers, keyboard_modifiers,
&mut app_model, &mut app_model,
&mut markup_node_pool,
); );
if let Err(e) = keydown_res { if let Err(e) = keydown_res {

View File

@ -1,130 +0,0 @@
use crate::editor::mvc::ed_model::LeafIndex;
use crate::editor::{
ed_error::{CaretNotFound, EdResult, NodeWithoutAttributes},
markup::attribute::Caret,
markup::nodes::MarkupNode,
slow_pool::{SlowNodeId, SlowPool},
};
use snafu::ensure;
// Returns id of node that has Caret attribute
pub fn set_caret_at_start(
markup_node_id: SlowNodeId,
markup_node_pool: &mut SlowPool,
) -> EdResult<SlowNodeId> {
let markup_node = markup_node_pool.get_mut(markup_node_id);
match markup_node {
MarkupNode::Nested {
ast_node_id: _,
children_ids: _,
parent_id_opt: _,
} => NodeWithoutAttributes {}.fail(),
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes,
parent_id_opt: _,
} => {
attributes.add(Caret::new_attr(0));
Ok(markup_node_id)
}
MarkupNode::Blank {
ast_node_id: _,
attributes,
syn_high_style: _,
parent_id_opt: _,
} => {
attributes.add(Caret::new_attr(0));
Ok(markup_node_id)
}
}
}
// Returns nodes containing the carets after the move, as well as its position in a DFS ordered list of leaves.
pub fn move_carets_right_for_node(
node_with_caret_id: SlowNodeId,
caret_id_leaf_index: LeafIndex,
next_leaf_id_opt: Option<SlowNodeId>,
markup_node_pool: &mut SlowPool,
) -> EdResult<Vec<(SlowNodeId, LeafIndex)>> {
let carets = get_carets(node_with_caret_id, markup_node_pool)?;
let node_content = markup_node_pool.get(node_with_caret_id).get_content()?;
ensure!(
!carets.is_empty(),
CaretNotFound {
node_id: node_with_caret_id
}
);
let mut new_nodes_w_carets = Vec::new();
for caret in carets {
let (new_node, new_leaf_index) = if caret.offset_col + 1 < node_content.len() {
increase_caret_offset(node_with_caret_id, caret.offset_col, markup_node_pool)?;
(node_with_caret_id, caret_id_leaf_index)
} else if let Some(next_child_id) = next_leaf_id_opt {
delete_caret(node_with_caret_id, caret.offset_col, markup_node_pool)?;
let next_child = markup_node_pool.get_mut(next_child_id);
let child_attrs = next_child.get_mut_attributes()?;
child_attrs.add_caret(0);
(next_child_id, caret_id_leaf_index + 1)
} else if caret.offset_col + 1 == node_content.len() {
// For last char in editor.
// In other places we jump to start of next node instead of going to end of
// this node, otherwise it would be like there is a space between every node.
increase_caret_offset(node_with_caret_id, caret.offset_col, markup_node_pool)?;
(node_with_caret_id, caret_id_leaf_index)
} else {
// Caret is at its end, keep it here.
(node_with_caret_id, caret_id_leaf_index)
};
new_nodes_w_carets.push((new_node, new_leaf_index));
}
Ok(new_nodes_w_carets)
}
fn get_carets(node_with_caret_id: SlowNodeId, markup_node_pool: &SlowPool) -> EdResult<Vec<Caret>> {
let curr_node = markup_node_pool.get(node_with_caret_id);
let attributes = curr_node.get_attributes()?;
Ok(attributes.get_carets())
}
// this method assumes the current caret position is checked and increasing it will not lead to an invalid caret position
fn increase_caret_offset(
node_id: SlowNodeId,
offset_col: usize,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> {
let node = markup_node_pool.get_mut(node_id);
let attrs = node.get_mut_attributes()?;
let mut carets = attrs.get_mut_carets();
for caret in carets.iter_mut() {
if caret.offset_col == offset_col {
caret.offset_col += 1;
}
}
Ok(())
}
fn delete_caret(
node_id: SlowNodeId,
offset_col: usize,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> {
let node = markup_node_pool.get_mut(node_id);
let attrs = node.get_mut_attributes()?;
attrs.delete_caret(offset_col, node_id)?;
Ok(())
}

View File

@ -1,3 +1,2 @@
pub mod attribute; pub mod attribute;
pub mod caret;
pub mod nodes; pub mod nodes;

View File

@ -1,18 +1,13 @@
use super::attribute::Attributes; use super::attribute::Attributes;
use crate::editor::ed_error::GetContentOnNestedNode; use crate::editor::slow_pool::SlowNodeId;
use crate::editor::ed_error::NodeWithoutAttributes; use crate::editor::slow_pool::SlowPool;
use crate::editor::{ use crate::editor::syntax_highlight::HighlightStyle;
ed_error::EdResult,
slow_pool::{SlowNodeId, SlowPool},
syntax_highlight::HighlightStyle,
};
use crate::lang::{ use crate::lang::{
ast::Expr2, ast::Expr2,
expr::Env, expr::Env,
pool::{NodeId, PoolStr}, pool::{NodeId, PoolStr},
}; };
use bumpalo::Bump; use bumpalo::Bump;
use snafu::OptionExt;
#[derive(Debug)] #[derive(Debug)]
pub enum MarkupNode { pub enum MarkupNode {
@ -38,117 +33,6 @@ pub enum MarkupNode {
pub const BLANK_PLACEHOLDER: &str = " "; pub const BLANK_PLACEHOLDER: &str = " ";
impl MarkupNode {
pub fn get_children_ids(&self) -> Vec<SlowNodeId> {
match self {
MarkupNode::Nested {
ast_node_id: _,
children_ids,
parent_id_opt: _,
} => children_ids.to_vec(),
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes: _,
parent_id_opt: _,
} => Vec::new(),
MarkupNode::Blank {
ast_node_id: _,
attributes: _,
syn_high_style: _,
parent_id_opt: _,
} => Vec::new(),
}
}
// can't be &str, this creates borrowing issues
pub fn get_content(&self) -> EdResult<String> {
match self {
MarkupNode::Nested {
ast_node_id: _,
children_ids: _,
parent_id_opt: _,
} => GetContentOnNestedNode {}.fail(),
MarkupNode::Text {
content,
ast_node_id: _,
syn_high_style: _,
attributes: _,
parent_id_opt: _,
} => Ok(content.clone()),
MarkupNode::Blank {
ast_node_id: _,
attributes: _,
syn_high_style: _,
parent_id_opt: _,
} => Ok(BLANK_PLACEHOLDER.to_owned()),
}
}
// Do Depth First Search and return SlowNodeId's in order of encounter
// The returning vec is used for caret movement
pub fn get_dfs_leaves(
&self,
node_id: SlowNodeId,
markup_node_pool: &SlowPool,
ordered_leaves: &mut Vec<SlowNodeId>,
) {
let children_ids = self.get_children_ids();
if children_ids.is_empty() {
ordered_leaves.push(node_id);
} else {
for child_id in self.get_children_ids() {
let child = markup_node_pool.get(child_id);
child.get_dfs_leaves(child_id, markup_node_pool, ordered_leaves);
}
}
}
pub fn get_mut_attributes(&mut self) -> EdResult<&mut Attributes> {
let attrs_ref = match self {
MarkupNode::Nested { .. } => None,
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes,
parent_id_opt: _,
} => Some(attributes),
MarkupNode::Blank {
ast_node_id: _,
attributes,
syn_high_style: _,
parent_id_opt: _,
} => Some(attributes),
};
attrs_ref.with_context(|| NodeWithoutAttributes {})
}
pub fn get_attributes(&self) -> EdResult<&Attributes> {
let attrs_ref = match self {
MarkupNode::Nested { .. } => None,
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes,
parent_id_opt: _,
} => Some(attributes),
MarkupNode::Blank {
ast_node_id: _,
attributes,
syn_high_style: _,
parent_id_opt: _,
} => Some(attributes),
};
attrs_ref.with_context(|| NodeWithoutAttributes {})
}
}
fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
pool_str.as_str(env.pool).to_owned() pool_str.as_str(env.pool).to_owned()
} }
@ -212,14 +96,12 @@ pub fn expr2_to_markup<'a, 'b>(
new_markup_node(text, node_id, HighlightStyle::Variable, markup_node_pool) new_markup_node(text, node_id, HighlightStyle::Variable, markup_node_pool)
} }
Expr2::List { elems, .. } => { Expr2::List { elems, .. } => {
let mut children_ids = Vec::new(); let mut children_ids = vec![new_markup_node(
children_ids.push(new_markup_node(
"[ ".to_string(), "[ ".to_string(),
node_id, node_id,
HighlightStyle::Bracket, HighlightStyle::Bracket,
markup_node_pool, markup_node_pool,
)); )];
for (idx, node_id) in elems.iter_node_ids().enumerate() { for (idx, node_id) in elems.iter_node_ids().enumerate() {
let sub_expr2 = env.pool.get(node_id); let sub_expr2 = env.pool.get(node_id);
@ -251,14 +133,12 @@ pub fn expr2_to_markup<'a, 'b>(
markup_node_pool.add(list_node) markup_node_pool.add(list_node)
} }
Expr2::Record { fields, .. } => { Expr2::Record { fields, .. } => {
let mut children_ids = Vec::new(); let mut children_ids = vec![new_markup_node(
children_ids.push(new_markup_node(
"{ ".to_string(), "{ ".to_string(),
node_id, node_id,
HighlightStyle::Bracket, HighlightStyle::Bracket,
markup_node_pool, markup_node_pool,
)); )];
for (idx, field_node_id) in fields.iter_node_ids().enumerate() { for (idx, field_node_id) in fields.iter_node_ids().enumerate() {
let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(field_node_id); let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(field_node_id);

View File

@ -1,3 +1,4 @@
mod code_lines;
mod config; mod config;
mod ed_error; mod ed_error;
mod keyboard_input; mod keyboard_input;

View File

@ -1,6 +1,6 @@
use super::app_model::AppModel; use super::app_model::AppModel;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::slow_pool::SlowPool; use crate::ui::text::lines::SelectableLines;
use crate::window::keyboard_input::from_winit; use crate::window::keyboard_input::from_winit;
use winit::event::{ModifiersState, VirtualKeyCode}; use winit::event::{ModifiersState, VirtualKeyCode};
@ -38,13 +38,12 @@ pub fn pass_keydown_to_focused(
modifiers_winit: &ModifiersState, modifiers_winit: &ModifiersState,
virtual_keycode: VirtualKeyCode, virtual_keycode: VirtualKeyCode,
app_model: &mut AppModel, app_model: &mut AppModel,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> { ) -> EdResult<()> {
let modifiers = from_winit(modifiers_winit); let modifiers = from_winit(modifiers_winit);
if let Some(ref mut ed_model) = app_model.ed_model_opt { if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus { if ed_model.has_focus {
ed_model.handle_key_down(&modifiers, virtual_keycode, markup_node_pool)?; ed_model.handle_key_down(&modifiers, virtual_keycode)?;
} }
} }

View File

@ -1,39 +1,48 @@
use crate::editor::code_lines::CodeLines;
use crate::editor::slow_pool::{SlowNodeId, SlowPool}; use crate::editor::slow_pool::{SlowNodeId, SlowPool};
use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::{ use crate::editor::{
ed_error::EdError::ParseError, ed_error::EdError::ParseError,
ed_error::EdResult, ed_error::EdResult,
markup::attribute::{Attributes, Caret}, markup::attribute::{Attributes, Caret},
markup::caret::{move_carets_right_for_node, set_caret_at_start},
markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode}, markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode},
}; };
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::lang::ast::Expr2; use crate::lang::ast::Expr2;
use crate::lang::expr::{str_to_expr2, Env}; use crate::lang::expr::{str_to_expr2, Env};
use crate::lang::scope::Scope; use crate::lang::scope::Scope;
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::text::lines::MoveCaretFun;
use crate::ui::text::selection::validate_raw_sel;
use crate::ui::text::selection::RawSelection;
use crate::ui::text::selection::Selection;
use crate::ui::text::text_pos::TextPos;
use crate::ui::text::{lines, lines::Lines, lines::SelectableLines};
use crate::ui::ui_error::UIResult;
use crate::window::keyboard_input::Modifiers; use crate::window::keyboard_input::Modifiers;
use bumpalo::collections::String as BumpString; use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use nonempty::NonEmpty;
use roc_region::all::Region; use roc_region::all::Region;
use std::collections::HashSet; use std::path::Path;
use winit::event::VirtualKeyCode; use winit::event::VirtualKeyCode;
use VirtualKeyCode::*;
pub type LeafIndex = usize;
#[derive(Debug)] #[derive(Debug)]
pub struct EdModel<'a> { pub struct EdModel<'a> {
pub module: EdModule<'a>, pub module: EdModule<'a>,
pub code_as_str: &'a str, pub file_path: &'a Path,
pub code_lines: CodeLines,
pub markup_root_id: SlowNodeId, pub markup_root_id: SlowNodeId,
pub glyph_dim_rect_opt: Option<Rect>, pub glyph_dim_rect_opt: Option<Rect>,
pub has_focus: bool, pub has_focus: bool,
// This HashSet may have less elements than there are carets. There can be multiple carets for a single node. // Option<SlowNodeId>: MarkupNode that corresponds to caret position, Option because this SlowNodeId is only calculated when it needs to be used.
caret_nodes: HashSet<(SlowNodeId, LeafIndex)>, pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<SlowNodeId>)>,
dfs_ordered_leaves: Vec<SlowNodeId>,
} }
pub fn init_model<'a>( pub fn init_model<'a>(
code_str: &'a BumpString, code_str: &'a BumpString,
file_path: &'a Path,
env: Env<'a>, env: Env<'a>,
code_arena: &'a Bump, code_arena: &'a Bump,
markup_node_pool: &mut SlowPool, markup_node_pool: &mut SlowPool,
@ -65,62 +74,178 @@ pub fn init_model<'a>(
temp_markup_root_id temp_markup_root_id
}; };
let mut dfs_ordered_leaves = Vec::new();
markup_node_pool.get(markup_root_id).get_dfs_leaves(
markup_root_id,
markup_node_pool,
&mut dfs_ordered_leaves,
);
// unwrap because it's not possible to only have a single Nested node without children.
let first_leaf_id = dfs_ordered_leaves.first().unwrap();
let node_w_caret_id = set_caret_at_start(*first_leaf_id, markup_node_pool)?;
Ok(EdModel { Ok(EdModel {
module, module,
code_as_str: code_str, file_path,
code_lines: CodeLines::from_str(code_str),
markup_root_id, markup_root_id,
glyph_dim_rect_opt: None, glyph_dim_rect_opt: None,
has_focus: true, has_focus: true,
caret_nodes: vec![(node_w_caret_id, 0)].into_iter().collect(), caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)),
dfs_ordered_leaves,
}) })
} }
impl<'a> EdModel<'a> { impl<'a> EdModel<'a> {
pub fn handle_key_down( pub fn move_caret(
&mut self, &mut self,
_modifiers: &Modifiers, move_fun: MoveCaretFun<CodeLines>,
virtual_keycode: VirtualKeyCode, modifiers: &Modifiers,
markup_node_pool: &mut SlowPool, ) -> UIResult<()> {
) -> EdResult<()> { for caret_tup in self.caret_w_select_vec.iter_mut() {
match virtual_keycode { caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
VirtualKeyCode::Right => { caret_tup.1 = None;
let mut new_caret_nodes: Vec<(SlowNodeId, LeafIndex)> = Vec::new();
for (caret_node_id_ref, leaf_index) in self.caret_nodes.iter() {
let caret_node_id = *caret_node_id_ref;
let next_leaf_id_opt = self.get_next_leaf(*leaf_index);
new_caret_nodes.extend(move_carets_right_for_node(
caret_node_id,
*leaf_index,
next_leaf_id_opt,
markup_node_pool,
)?);
} }
self.caret_nodes = new_caret_nodes.into_iter().collect(); Ok(())
} }
VirtualKeyCode::Left => unimplemented!("TODO"), }
_ => (),
}; impl<'a> SelectableLines for EdModel<'a> {
fn get_caret(self) -> TextPos {
self.caret_w_select_vec.first().0.caret_pos
}
// keeps active selection
fn set_caret(&mut self, caret_pos: TextPos) {
let caret_tup = self.caret_w_select_vec.first_mut();
caret_tup.0.caret_pos = caret_pos;
caret_tup.1 = None;
}
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_left;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(()) Ok(())
} }
pub fn get_next_leaf(&self, index: usize) -> Option<SlowNodeId> { fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
self.dfs_ordered_leaves.get(index + 1).copied() let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_right;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_up;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_down;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_home;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_end;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn get_selection(&self) -> Option<Selection> {
self.caret_w_select_vec.first().0.selection_opt
}
fn is_selection_active(&self) -> bool {
self.get_selection().is_some()
}
fn get_selected_str(&self) -> UIResult<Option<String>> {
if let Some(selection) = self.get_selection() {
let start_line_index = selection.start_pos.line;
let start_col = selection.start_pos.column;
let end_line_index = selection.end_pos.line;
let end_col = selection.end_pos.column;
if start_line_index == end_line_index {
let line_ref = self.code_lines.get_line(start_line_index)?;
Ok(Some(line_ref[start_col..end_col].to_string()))
} else {
let full_str = String::new();
// TODO
Ok(Some(full_str))
}
} else {
Ok(None)
}
}
fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()> {
self.caret_w_select_vec.first_mut().0.selection_opt = Some(validate_raw_sel(raw_sel)?);
Ok(())
}
fn set_sel_none(&mut self) {
self.caret_w_select_vec.first_mut().0.selection_opt = None;
}
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {
self.caret_w_select_vec.first_mut().0 = caret_w_sel;
}
fn select_all(&mut self) -> UIResult<()> {
if self.code_lines.nr_of_chars() > 0 {
let last_pos = self.last_text_pos()?;
self.set_raw_sel(RawSelection {
start_pos: TextPos { line: 0, column: 0 },
end_pos: last_pos,
})?;
self.set_caret(last_pos);
}
Ok(())
}
fn last_text_pos(&self) -> UIResult<TextPos> {
let nr_of_lines = self.code_lines.lines.len();
let last_line_index = nr_of_lines - 1;
let last_line = self.code_lines.get_line(last_line_index)?;
Ok(TextPos {
line: self.code_lines.lines.len() - 1,
column: last_line.len(),
})
}
fn handle_key_down(
&mut self,
modifiers: &Modifiers,
virtual_keycode: VirtualKeyCode,
) -> UIResult<()> {
match virtual_keycode {
Left => self.move_caret_left(modifiers),
Up => self.move_caret_up(modifiers),
Right => self.move_caret_right(modifiers),
Down => self.move_caret_down(modifiers),
A => {
if modifiers.ctrl {
self.select_all()
} else {
Ok(())
}
}
Home => self.move_caret_home(modifiers),
End => self.move_caret_end(modifiers),
_ => Ok(()),
}
} }
} }

View File

@ -1,9 +1,12 @@
use super::ed_model::EdModel; use super::ed_model::EdModel;
use crate::editor::code_lines::CodeLines;
use crate::editor::config::Config; use crate::editor::config::Config;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::render_ast::build_code_graphics; use crate::editor::render_ast::build_code_graphics;
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::ui::text::caret_w_select::make_caret_rect;
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::ui_error::MissingGlyphDims; use crate::ui::ui_error::MissingGlyphDims;
use cgmath::Vector2; use cgmath::Vector2;
use snafu::OptionExt; use snafu::OptionExt;
@ -19,12 +22,62 @@ pub fn model_to_wgpu<'a>(
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> { ) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> {
let glyph_dim_rect = ed_model.glyph_dim_rect_opt.context(MissingGlyphDims {})?; let glyph_dim_rect = ed_model.glyph_dim_rect_opt.context(MissingGlyphDims {})?;
build_code_graphics( let (section, mut rects) = build_code_graphics(
markup_node_pool.get(ed_model.markup_root_id), markup_node_pool.get(ed_model.markup_root_id),
size, size,
txt_coords, txt_coords,
config, config,
glyph_dim_rect, glyph_dim_rect,
markup_node_pool, markup_node_pool,
) )?;
let mut all_code_string = String::new();
for txt in section.text.iter() {
all_code_string.push_str(txt.text);
}
ed_model.code_lines = CodeLines::from_str(&all_code_string);
let caret_w_sel_vec = ed_model
.caret_w_select_vec
.iter()
.map(|(caret_w_sel, _)| *caret_w_sel)
.collect();
let mut sel_rects =
build_selection_graphics(caret_w_sel_vec, txt_coords, config, glyph_dim_rect)?;
rects.append(&mut sel_rects);
Ok((section, rects))
}
pub fn build_selection_graphics(
caret_w_select_vec: Vec<CaretWSelect>,
txt_coords: Vector2<f32>,
config: &Config,
glyph_dim_rect: Rect,
) -> EdResult<Vec<Rect>> {
let mut rects = Vec::new();
let char_width = glyph_dim_rect.width;
let char_height = glyph_dim_rect.height;
for caret_w_sel in caret_w_select_vec {
let caret_row = caret_w_sel.caret_pos.line as f32;
let caret_col = caret_w_sel.caret_pos.column as f32;
let top_left_x = txt_coords.x + caret_col * char_width;
let top_left_y = txt_coords.y + caret_row * char_height;
rects.push(make_caret_rect(
top_left_x,
top_left_y,
&glyph_dim_rect,
&config.ed_theme.ui_theme,
))
}
Ok(rects)
} }

View File

@ -1,10 +1,8 @@
use super::markup::attribute::{Attribute, Attributes};
use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get};
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::graphics::primitives::text as gr_text; use crate::graphics::primitives::text as gr_text;
use crate::ui::text::caret_w_select::make_caret_rect;
use cgmath::Vector2; use cgmath::Vector2;
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
@ -67,40 +65,6 @@ fn markup_to_wgpu<'a>(
Ok((wgpu_texts, rects)) Ok((wgpu_texts, rects))
} }
fn draw_attributes(
attributes: &Attributes,
txt_row_col: &(usize, usize),
code_style: &CodeStyle,
) -> Vec<Rect> {
let char_width = code_style.glyph_dim_rect.width;
attributes
.all
.iter()
.map(|attr| match attr {
Attribute::Caret { caret } => {
let caret_col = caret.offset_col as f32;
let top_left_x = code_style.txt_coords.x
+ (txt_row_col.1 as f32) * char_width
+ caret_col * char_width;
let top_left_y = code_style.txt_coords.y
+ (txt_row_col.0 as f32) * char_width
+ char_width * 0.2;
make_caret_rect(
top_left_x,
top_left_y,
&code_style.glyph_dim_rect,
&code_style.ed_theme.ui_theme,
)
}
rest => todo!("implement draw_attributes for {:?}", rest),
})
.collect()
}
// TODO use text_row // TODO use text_row
fn markup_to_wgpu_helper<'a>( fn markup_to_wgpu_helper<'a>(
markup_node: &'a MarkupNode, markup_node: &'a MarkupNode,
@ -132,7 +96,7 @@ fn markup_to_wgpu_helper<'a>(
content, content,
ast_node_id: _, ast_node_id: _,
syn_high_style, syn_high_style,
attributes, attributes: _,
parent_id_opt: _, parent_id_opt: _,
} => { } => {
let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?; let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?;
@ -141,13 +105,12 @@ fn markup_to_wgpu_helper<'a>(
.with_color(colors::to_slice(*highlight_color)) .with_color(colors::to_slice(*highlight_color))
.with_scale(code_style.font_size); .with_scale(code_style.font_size);
rects.extend(draw_attributes(attributes, txt_row_col, code_style));
txt_row_col.1 += content.len(); txt_row_col.1 += content.len();
wgpu_texts.push(glyph_text); wgpu_texts.push(glyph_text);
} }
MarkupNode::Blank { MarkupNode::Blank {
ast_node_id: _, ast_node_id: _,
attributes, attributes: _,
syn_high_style, syn_high_style,
parent_id_opt: _, parent_id_opt: _,
} => { } => {
@ -171,8 +134,6 @@ fn markup_to_wgpu_helper<'a>(
}; };
rects.push(hole_rect); rects.push(hole_rect);
rects.extend(draw_attributes(attributes, txt_row_col, code_style));
txt_row_col.1 += BLANK_PLACEHOLDER.len(); txt_row_col.1 += BLANK_PLACEHOLDER.len();
wgpu_texts.push(glyph_text); wgpu_texts.push(glyph_text);
} }

View File

@ -1,18 +1,6 @@
use super::ed_error::{EdResult, KeyNotFound, OutOfBounds}; use super::ed_error::{EdResult, KeyNotFound};
use snafu::OptionExt; use snafu::OptionExt;
use std::collections::HashMap; use std::collections::HashMap;
use std::slice::SliceIndex;
// replace vec methods that return Option with ones that return Result and proper Error
pub fn slice_get<T>(index: usize, slice: &[T]) -> EdResult<&<usize as SliceIndex<[T]>>::Output> {
let elt_ref = slice.get(index).context(OutOfBounds {
index,
collection_name: "Slice",
len: slice.len(),
})?;
Ok(elt_ref)
}
// replace HashMap method that returns Option with one that returns Result and proper Error // replace HashMap method that returns Option with one that returns Result and proper Error
pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>( pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>(

View File

@ -247,7 +247,7 @@ fn to_pending_def<'a>(
} }
} }
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) | Nested(sub_def) => { SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => {
to_pending_def(env, sub_def, scope, pattern_type) to_pending_def(env, sub_def, scope, pattern_type)
} }

View File

@ -809,7 +809,6 @@ pub fn to_expr2<'a>(
// (RuntimeError(problem), Output::default()) // (RuntimeError(problem), Output::default())
todo!() todo!()
} }
Nested(sub_expr) => to_expr2(env, scope, sub_expr, region),
Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region), Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region),
// Below this point, we shouln't see any of these nodes anymore because // Below this point, we shouln't see any of these nodes anymore because

View File

@ -414,7 +414,7 @@ pub fn to_pattern2<'a>(
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => { SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
return to_pattern2(env, scope, pattern_type, sub_pattern, region) return to_pattern2(env, scope, pattern_type, sub_pattern, region)
} }
}; };

View File

@ -1,6 +1,6 @@
#![warn(clippy::all, clippy::dbg_macro)] #![warn(clippy::all, clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
#[cfg_attr(test, macro_use)] #[cfg_attr(test, macro_use)]
extern crate indoc; extern crate indoc;

View File

@ -1,4 +1,4 @@
pub mod text; pub mod text;
pub mod theme; pub mod theme;
pub mod ui_error; pub mod ui_error;
mod util; pub mod util;

View File

@ -4,8 +4,9 @@
use crate::ui::text::{ use crate::ui::text::{
caret_w_select::CaretWSelect, caret_w_select::CaretWSelect,
lines,
lines::{Lines, MutSelectableLines, SelectableLines}, lines::{Lines, MutSelectableLines, SelectableLines},
selection::{validate_raw_sel, validate_selection, RawSelection, Selection}, selection::{validate_raw_sel, RawSelection, Selection},
text_pos::TextPos, text_pos::TextPos,
}; };
use crate::ui::ui_error::{ use crate::ui::ui_error::{
@ -19,23 +20,17 @@ use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use ropey::Rope; use ropey::Rope;
use snafu::ensure; use snafu::ensure;
use std::{ use std::{fmt, fs::File, io, path::Path};
cmp::{max, min},
fmt,
fs::File,
io,
path::Path,
};
use winit::event::{VirtualKeyCode, VirtualKeyCode::*}; use winit::event::{VirtualKeyCode, VirtualKeyCode::*};
pub struct BigSelectableText { pub struct BigTextArea {
pub caret_w_select: CaretWSelect, pub caret_w_select: CaretWSelect,
text_rope: Rope, text_rope: Rope,
pub path_str: String, pub path_str: String,
arena: Bump, arena: Bump,
} }
impl BigSelectableText { impl BigTextArea {
fn check_bounds(&self, char_indx: usize) -> UIResult<()> { fn check_bounds(&self, char_indx: usize) -> UIResult<()> {
ensure!( ensure!(
char_indx <= self.text_rope.len_chars(), char_indx <= self.text_rope.len_chars(),
@ -71,17 +66,13 @@ impl BigSelectableText {
} }
} }
fn validate_sel_opt(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> { impl Lines for BigTextArea {
Ok(Some(validate_selection(start_pos, end_pos)?))
}
impl Lines for BigSelectableText {
fn get_line(&self, line_nr: usize) -> UIResult<&str> { fn get_line(&self, line_nr: usize) -> UIResult<&str> {
ensure!( ensure!(
line_nr < self.nr_of_lines(), line_nr < self.nr_of_lines(),
OutOfBounds { OutOfBounds {
index: line_nr, index: line_nr,
collection_name: "BigSelectableText", collection_name: "BigTextArea",
len: self.nr_of_lines(), len: self.nr_of_lines(),
} }
); );
@ -121,9 +112,13 @@ impl Lines for BigSelectableText {
lines lines
} }
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
Ok(self.get_line(line_nr)?.chars().last())
}
} }
impl SelectableLines for BigSelectableText { impl SelectableLines for BigTextArea {
fn get_caret(self) -> TextPos { fn get_caret(self) -> TextPos {
self.caret_w_select.caret_pos self.caret_w_select.caret_pos
} }
@ -133,347 +128,39 @@ impl SelectableLines for BigSelectableText {
} }
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection(); self.caret_w_select = lines::move_caret_left(self, self.caret_w_select, modifiers)?;
let old_caret_pos = self.caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => {
(old_selection.start_pos.line, old_selection.start_pos.column)
}
None => unreachable!(),
}
} else if old_col_nr == 0 {
if old_line_nr == 0 {
(0, 0)
} else {
let curr_line_len = self.line_len(old_line_nr - 1)?;
(old_line_nr - 1, curr_line_len - 1)
}
} else {
(old_line_nr, old_col_nr - 1)
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos >= old_selection.end_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
validate_sel_opt(old_selection.start_pos, new_caret_pos)?
}
} else {
validate_sel_opt(
TextPos {
line: line_nr,
column: col_nr,
},
old_selection.end_pos,
)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
TextPos {
line: line_nr,
column: col_nr,
},
TextPos {
line: old_line_nr,
column: old_col_nr,
},
)?
} else {
None
}
} else {
None
};
self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt);
Ok(()) Ok(())
} }
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection(); self.caret_w_select = lines::move_caret_right(self, self.caret_w_select, modifiers)?;
let old_caret_pos = self.caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else {
let curr_line = self.get_line(old_line_nr)?;
if let Some(last_char) = curr_line.chars().last() {
if is_newline(&last_char) {
if old_col_nr + 1 > curr_line.len() - 1 {
(old_line_nr + 1, 0)
} else {
(old_line_nr, old_col_nr + 1)
}
} else if old_col_nr < curr_line.len() {
(old_line_nr, old_col_nr + 1)
} else {
(old_line_nr, old_col_nr)
}
} else {
(old_line_nr, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
validate_sel_opt(new_caret_pos, old_selection.end_pos)?
}
} else {
validate_sel_opt(
old_selection.start_pos,
TextPos {
line: line_nr,
column: col_nr,
},
)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
TextPos {
line: old_line_nr,
column: old_col_nr,
},
TextPos {
line: line_nr,
column: col_nr,
},
)?
} else {
None
}
} else {
None
};
self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt);
Ok(()) Ok(())
} }
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection(); self.caret_w_select = lines::move_caret_up(self, self.caret_w_select, modifiers)?;
let old_caret_pos = self.caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => {
(old_selection.start_pos.line, old_selection.start_pos.column)
}
None => unreachable!(),
}
} else if old_line_nr == 0 {
(old_line_nr, 0)
} else {
let prev_line_len = self.line_len(old_line_nr - 1)?;
if prev_line_len <= old_col_nr {
(old_line_nr - 1, prev_line_len - 1)
} else {
(old_line_nr - 1, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_selection.end_pos <= old_caret_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
validate_sel_opt(
min(old_selection.start_pos, new_caret_pos),
max(old_selection.start_pos, new_caret_pos),
)?
}
} else {
validate_sel_opt(new_caret_pos, old_selection.end_pos)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
min(old_caret_pos, new_caret_pos),
max(old_caret_pos, new_caret_pos),
)?
} else {
None
}
} else {
None
};
self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt);
Ok(()) Ok(())
} }
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection(); self.caret_w_select = lines::move_caret_down(self, self.caret_w_select, modifiers)?;
let old_caret_pos = self.caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else if old_line_nr + 1 >= self.nr_of_lines() {
let curr_line_len = self.line_len(old_line_nr)?;
(old_line_nr, curr_line_len)
} else {
let next_line = self.get_line(old_line_nr + 1)?;
if next_line.len() <= old_col_nr {
if let Some(last_char) = next_line.chars().last() {
if is_newline(&last_char) {
(old_line_nr + 1, next_line.len() - 1)
} else {
(old_line_nr + 1, next_line.len())
}
} else {
(old_line_nr + 1, 0)
}
} else {
(old_line_nr + 1, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
validate_sel_opt(
min(old_selection.end_pos, new_caret_pos),
max(old_selection.end_pos, new_caret_pos),
)?
}
} else {
validate_sel_opt(old_selection.start_pos, new_caret_pos)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
min(old_caret_pos, new_caret_pos),
max(old_caret_pos, new_caret_pos),
)?
} else {
None
}
} else {
None
};
self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt);
Ok(()) Ok(())
} }
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let curr_line_nr = self.caret_w_select.caret_pos.line; self.caret_w_select = lines::move_caret_home(self, self.caret_w_select, modifiers)?;
let old_col_nr = self.caret_w_select.caret_pos.column;
let curr_line_str = self.get_line(curr_line_nr)?; Ok(())
let line_char_iter = curr_line_str.chars();
let mut first_no_space_char_col = 0;
let mut non_space_found = false;
for c in line_char_iter {
if !c.is_whitespace() {
non_space_found = true;
break;
} else {
first_no_space_char_col += 1;
}
}
if !non_space_found {
first_no_space_char_col = 0;
}
let new_col_nr = if first_no_space_char_col == old_col_nr {
0
} else {
first_no_space_char_col
};
self.caret_w_select.move_caret_w_mods(
TextPos {
line: curr_line_nr,
column: new_col_nr,
},
modifiers,
)
} }
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let curr_line_nr = self.caret_w_select.caret_pos.line; self.caret_w_select = lines::move_caret_end(self, self.caret_w_select, modifiers)?;
let curr_line_len = self.line_len(curr_line_nr)?;
let new_col = if let Some(last_char) = self.last_char(curr_line_nr)? { Ok(())
if is_newline(&last_char) {
curr_line_len - 1
} else {
curr_line_len
}
} else {
0
};
let new_pos = TextPos {
line: curr_line_nr,
column: new_col,
};
self.caret_w_select.move_caret_w_mods(new_pos, modifiers)
} }
fn get_selection(&self) -> Option<Selection> { fn get_selection(&self) -> Option<Selection> {
@ -484,7 +171,7 @@ impl SelectableLines for BigSelectableText {
self.get_selection().is_some() self.get_selection().is_some()
} }
fn get_selected_str(&self) -> UIResult<Option<&str>> { fn get_selected_str(&self) -> UIResult<Option<String>> {
if let Some(val_sel) = self.caret_w_select.selection_opt { if let Some(val_sel) = self.caret_w_select.selection_opt {
let (start_char_indx, end_char_indx) = self.sel_to_tup(val_sel); let (start_char_indx, end_char_indx) = self.sel_to_tup(val_sel);
@ -493,12 +180,9 @@ impl SelectableLines for BigSelectableText {
let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx); let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx);
if let Some(line_str_ref) = rope_slice.as_str() { if let Some(line_str_ref) = rope_slice.as_str() {
Ok(Some(line_str_ref)) Ok(Some(line_str_ref.to_string()))
} else { } else {
// happens very rarely Ok(Some(rope_slice.chunks().collect::<String>()))
let line_str = rope_slice.chunks().collect::<String>();
let arena_str_ref = self.arena.alloc(line_str);
Ok(Some(arena_str_ref))
} }
} else { } else {
Ok(None) Ok(None)
@ -511,13 +195,17 @@ impl SelectableLines for BigSelectableText {
Ok(()) Ok(())
} }
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {
self.caret_w_select = caret_w_sel;
}
fn set_sel_none(&mut self) { fn set_sel_none(&mut self) {
self.caret_w_select.selection_opt = None; self.caret_w_select.selection_opt = None;
} }
fn select_all(&mut self) -> UIResult<()> { fn select_all(&mut self) -> UIResult<()> {
if self.nr_of_chars() > 0 { if self.nr_of_chars() > 0 {
let last_pos = self.last_text_pos(); let last_pos = self.last_text_pos()?;
self.set_raw_sel(RawSelection { self.set_raw_sel(RawSelection {
start_pos: TextPos { line: 0, column: 0 }, start_pos: TextPos { line: 0, column: 0 },
@ -530,12 +218,8 @@ impl SelectableLines for BigSelectableText {
Ok(()) Ok(())
} }
fn last_text_pos(&self) -> TextPos { fn last_text_pos(&self) -> UIResult<TextPos> {
self.char_indx_to_pos(self.nr_of_chars()) Ok(self.char_indx_to_pos(self.nr_of_chars()))
}
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
Ok(self.get_line(line_nr)?.chars().last())
} }
fn handle_key_down( fn handle_key_down(
@ -563,7 +247,7 @@ impl SelectableLines for BigSelectableText {
} }
} }
impl MutSelectableLines for BigSelectableText { impl MutSelectableLines for BigTextArea {
fn insert_char(&mut self, new_char: &char) -> UIResult<()> { fn insert_char(&mut self, new_char: &char) -> UIResult<()> {
if self.is_selection_active() { if self.is_selection_active() {
self.del_selection()?; self.del_selection()?;
@ -658,7 +342,7 @@ impl MutSelectableLines for BigSelectableText {
} }
} }
impl Default for BigSelectableText { impl Default for BigTextArea {
fn default() -> Self { fn default() -> Self {
let caret_w_select = CaretWSelect::default(); let caret_w_select = CaretWSelect::default();
let text_rope = Rope::from_str(""); let text_rope = Rope::from_str("");
@ -674,11 +358,11 @@ impl Default for BigSelectableText {
} }
} }
pub fn from_path(path: &Path) -> UIResult<BigSelectableText> { pub fn from_path(path: &Path) -> UIResult<BigTextArea> {
let text_rope = rope_from_path(path)?; let text_rope = rope_from_path(path)?;
let path_str = path_to_string(path); let path_str = path_to_string(path);
Ok(BigSelectableText { Ok(BigTextArea {
text_rope, text_rope,
path_str, path_str,
..Default::default() ..Default::default()
@ -687,10 +371,10 @@ pub fn from_path(path: &Path) -> UIResult<BigSelectableText> {
#[allow(dead_code)] #[allow(dead_code)]
// used by tests but will also be used in the future // used by tests but will also be used in the future
pub fn from_str(text: &str) -> BigSelectableText { pub fn from_str(text: &str) -> BigTextArea {
let text_rope = Rope::from_str(text); let text_rope = Rope::from_str(text);
BigSelectableText { BigTextArea {
text_rope, text_rope,
..Default::default() ..Default::default()
} }
@ -723,9 +407,9 @@ fn rope_from_path(path: &Path) -> UIResult<Rope> {
} }
// need to explicitly omit arena // need to explicitly omit arena
impl fmt::Debug for BigSelectableText { impl fmt::Debug for BigTextArea {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BigSelectableText") f.debug_struct("BigTextArea")
.field("caret_w_select", &self.caret_w_select) .field("caret_w_select", &self.caret_w_select)
.field("text_rope", &self.text_rope) .field("text_rope", &self.text_rope)
.field("path_str", &self.path_str) .field("path_str", &self.path_str)
@ -736,8 +420,8 @@ impl fmt::Debug for BigSelectableText {
#[cfg(test)] #[cfg(test)]
pub mod test_big_sel_text { pub mod test_big_sel_text {
use crate::ui::text::{ use crate::ui::text::{
big_selectable_text::from_str, big_text_area::from_str,
big_selectable_text::BigSelectableText, big_text_area::BigTextArea,
caret_w_select::CaretWSelect, caret_w_select::CaretWSelect,
lines::{Lines, MutSelectableLines, SelectableLines}, lines::{Lines, MutSelectableLines, SelectableLines},
selection::validate_selection, selection::validate_selection,
@ -843,7 +527,7 @@ pub mod test_big_sel_text {
Ok(elt_ref) Ok(elt_ref)
} }
pub fn big_text_from_dsl_str(lines: &[String]) -> BigSelectableText { pub fn big_text_from_dsl_str(lines: &[String]) -> BigTextArea {
from_str( from_str(
&lines &lines
.iter() .iter()
@ -853,7 +537,7 @@ pub mod test_big_sel_text {
) )
} }
pub fn all_lines_vec(big_sel_text: &BigSelectableText) -> Vec<String> { pub fn all_lines_vec(big_sel_text: &BigTextArea) -> Vec<String> {
let mut lines: Vec<String> = Vec::new(); let mut lines: Vec<String> = Vec::new();
for i in 0..big_sel_text.nr_of_lines() { for i in 0..big_sel_text.nr_of_lines() {
@ -963,7 +647,7 @@ pub mod test_big_sel_text {
} }
} }
pub fn gen_big_text(lines: &[&str]) -> Result<BigSelectableText, String> { pub fn gen_big_text(lines: &[&str]) -> Result<BigTextArea, String> {
let lines_string_slice: Vec<String> = lines.iter().map(|l| l.to_string()).collect(); let lines_string_slice: Vec<String> = lines.iter().map(|l| l.to_string()).collect();
let mut big_text = big_text_from_dsl_str(&lines_string_slice); let mut big_text = big_text_from_dsl_str(&lines_string_slice);
let caret_w_select = convert_dsl_to_selection(&lines_string_slice).unwrap(); let caret_w_select = convert_dsl_to_selection(&lines_string_slice).unwrap();
@ -1141,7 +825,7 @@ pub mod test_big_sel_text {
Ok(()) Ok(())
} }
type MoveCaretFun = fn(&mut BigSelectableText, &Modifiers) -> UIResult<()>; type MoveCaretFun = fn(&mut BigTextArea, &Modifiers) -> UIResult<()>;
// Convert nice string representations and compare results // Convert nice string representations and compare results
fn assert_move( fn assert_move(
@ -1720,7 +1404,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn move_home() -> Result<(), String> { fn move_home() -> Result<(), String> {
let move_caret_home = BigSelectableText::move_caret_home; let move_caret_home = BigTextArea::move_caret_home;
assert_move(&["|"], &["|"], &no_mods(), move_caret_home)?; assert_move(&["|"], &["|"], &no_mods(), move_caret_home)?;
assert_move(&["a|"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["a|"], &["|a"], &no_mods(), move_caret_home)?;
assert_move(&["|a"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["|a"], &["|a"], &no_mods(), move_caret_home)?;
@ -1834,7 +1518,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn move_end() -> Result<(), String> { fn move_end() -> Result<(), String> {
let move_caret_end = BigSelectableText::move_caret_end; let move_caret_end = BigTextArea::move_caret_end;
assert_move(&["|"], &["|"], &no_mods(), move_caret_end)?; assert_move(&["|"], &["|"], &no_mods(), move_caret_end)?;
assert_move(&["|a"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["|a"], &["a|"], &no_mods(), move_caret_end)?;
assert_move(&["a|"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["a|"], &["a|"], &no_mods(), move_caret_end)?;
@ -2467,7 +2151,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn start_selection_home() -> Result<(), String> { fn start_selection_home() -> Result<(), String> {
let move_caret_home = BigSelectableText::move_caret_home; let move_caret_home = BigTextArea::move_caret_home;
assert_move(&["|"], &["|"], &shift_pressed(), move_caret_home)?; assert_move(&["|"], &["|"], &shift_pressed(), move_caret_home)?;
assert_move(&["|a"], &["|a"], &shift_pressed(), move_caret_home)?; assert_move(&["|a"], &["|a"], &shift_pressed(), move_caret_home)?;
assert_move(&["a|"], &["|[a]"], &shift_pressed(), move_caret_home)?; assert_move(&["a|"], &["|[a]"], &shift_pressed(), move_caret_home)?;
@ -2587,7 +2271,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn start_selection_end() -> Result<(), String> { fn start_selection_end() -> Result<(), String> {
let move_caret_end = BigSelectableText::move_caret_end; let move_caret_end = BigTextArea::move_caret_end;
assert_move(&["|"], &["|"], &shift_pressed(), move_caret_end)?; assert_move(&["|"], &["|"], &shift_pressed(), move_caret_end)?;
assert_move(&["|a"], &["[a]|"], &shift_pressed(), move_caret_end)?; assert_move(&["|a"], &["[a]|"], &shift_pressed(), move_caret_end)?;
assert_move(&["a|"], &["a|"], &shift_pressed(), move_caret_end)?; assert_move(&["a|"], &["a|"], &shift_pressed(), move_caret_end)?;
@ -3253,7 +2937,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn end_selection_home() -> Result<(), String> { fn end_selection_home() -> Result<(), String> {
let move_caret_home = BigSelectableText::move_caret_home; let move_caret_home = BigTextArea::move_caret_home;
assert_move(&["[a]|"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["[a]|"], &["|a"], &no_mods(), move_caret_home)?;
assert_move(&["|[a]"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["|[a]"], &["|a"], &no_mods(), move_caret_home)?;
assert_move(&[" |[a]"], &["| a"], &no_mods(), move_caret_home)?; assert_move(&[" |[a]"], &["| a"], &no_mods(), move_caret_home)?;
@ -3318,7 +3002,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn end_selection_end() -> Result<(), String> { fn end_selection_end() -> Result<(), String> {
let move_caret_end = BigSelectableText::move_caret_end; let move_caret_end = BigTextArea::move_caret_end;
assert_move(&["|[a]"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["|[a]"], &["a|"], &no_mods(), move_caret_end)?;
assert_move(&["[a]|"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["[a]|"], &["a|"], &no_mods(), move_caret_end)?;
assert_move(&[" a|[ ]"], &[" a |"], &no_mods(), move_caret_end)?; assert_move(&[" a|[ ]"], &[" a |"], &no_mods(), move_caret_end)?;

View File

@ -37,7 +37,7 @@ impl CaretWSelect {
} }
} }
pub fn move_caret_w_mods(&mut self, new_pos: TextPos, mods: &Modifiers) -> UIResult<()> { pub fn move_caret_w_mods(&self, new_pos: TextPos, mods: &Modifiers) -> UIResult<CaretWSelect> {
let old_caret_pos = self.caret_pos; let old_caret_pos = self.caret_pos;
// one does not simply move the caret // one does not simply move the caret
@ -75,10 +75,7 @@ impl CaretWSelect {
None None
}; };
self.caret_pos = new_pos; Ok(CaretWSelect::new(new_pos, valid_sel_opt))
self.selection_opt = valid_sel_opt;
Ok(())
} }
} }

View File

@ -1,13 +1,18 @@
// Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license // Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::text::selection::validate_sel_opt;
use crate::ui::text::{ use crate::ui::text::{
selection::{RawSelection, Selection}, selection::{RawSelection, Selection},
text_pos::TextPos, text_pos::TextPos,
}; };
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::UIResult;
use crate::ui::util::is_newline;
use crate::window::keyboard_input::Modifiers; use crate::window::keyboard_input::Modifiers;
use bumpalo::collections::String as BumpString; use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use std::cmp::max;
use std::cmp::min;
use winit::event::VirtualKeyCode; use winit::event::VirtualKeyCode;
pub trait Lines { pub trait Lines {
@ -19,8 +24,9 @@ pub trait Lines {
fn nr_of_chars(&self) -> usize; fn nr_of_chars(&self) -> usize;
// TODO use pool allocation here
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>; fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>;
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>>;
} }
pub trait SelectableLines { pub trait SelectableLines {
@ -44,17 +50,17 @@ pub trait SelectableLines {
fn is_selection_active(&self) -> bool; fn is_selection_active(&self) -> bool;
fn get_selected_str(&self) -> UIResult<Option<&str>>; fn get_selected_str(&self) -> UIResult<Option<String>>;
fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()>; fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()>;
fn set_sel_none(&mut self); fn set_sel_none(&mut self);
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect);
fn select_all(&mut self) -> UIResult<()>; fn select_all(&mut self) -> UIResult<()>;
fn last_text_pos(&self) -> TextPos; fn last_text_pos(&self) -> UIResult<TextPos>;
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>>;
fn handle_key_down( fn handle_key_down(
&mut self, &mut self,
@ -75,3 +81,362 @@ pub trait MutSelectableLines {
fn del_selection(&mut self) -> UIResult<()>; fn del_selection(&mut self) -> UIResult<()>;
} }
// T: Lines
pub type MoveCaretFun<T> = fn(&T, CaretWSelect, &Modifiers) -> UIResult<CaretWSelect>;
pub fn move_caret_left<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let old_selection_opt = caret_w_select.selection_opt;
let old_caret_pos = caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.start_pos.line, old_selection.start_pos.column),
None => unreachable!(),
}
} else if old_col_nr == 0 {
if old_line_nr == 0 {
(0, 0)
} else {
let curr_line_len = lines.line_len(old_line_nr - 1)?;
(old_line_nr - 1, curr_line_len - 1)
}
} else {
(old_line_nr, old_col_nr - 1)
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos >= old_selection.end_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
validate_sel_opt(old_selection.start_pos, new_caret_pos)?
}
} else {
validate_sel_opt(
TextPos {
line: line_nr,
column: col_nr,
},
old_selection.end_pos,
)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
TextPos {
line: line_nr,
column: col_nr,
},
TextPos {
line: old_line_nr,
column: old_col_nr,
},
)?
} else {
None
}
} else {
None
};
Ok(CaretWSelect::new(new_caret_pos, new_selection_opt))
}
pub fn move_caret_right<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let old_selection_opt = caret_w_select.selection_opt;
let old_caret_pos = caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else {
let curr_line = lines.get_line(old_line_nr)?;
if let Some(last_char) = curr_line.chars().last() {
if is_newline(&last_char) {
if old_col_nr + 1 > curr_line.len() - 1 {
(old_line_nr + 1, 0)
} else {
(old_line_nr, old_col_nr + 1)
}
} else if old_col_nr < curr_line.len() {
(old_line_nr, old_col_nr + 1)
} else {
(old_line_nr, old_col_nr)
}
} else {
(old_line_nr, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
validate_sel_opt(new_caret_pos, old_selection.end_pos)?
}
} else {
validate_sel_opt(
old_selection.start_pos,
TextPos {
line: line_nr,
column: col_nr,
},
)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
TextPos {
line: old_line_nr,
column: old_col_nr,
},
TextPos {
line: line_nr,
column: col_nr,
},
)?
} else {
None
}
} else {
None
};
Ok(CaretWSelect::new(new_caret_pos, new_selection_opt))
}
pub fn move_caret_up<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let old_selection_opt = caret_w_select.selection_opt;
let old_caret_pos = caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.start_pos.line, old_selection.start_pos.column),
None => unreachable!(),
}
} else if old_line_nr == 0 {
(old_line_nr, 0)
} else {
let prev_line_len = lines.line_len(old_line_nr - 1)?;
if prev_line_len <= old_col_nr {
(old_line_nr - 1, prev_line_len - 1)
} else {
(old_line_nr - 1, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_selection.end_pos <= old_caret_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
validate_sel_opt(
min(old_selection.start_pos, new_caret_pos),
max(old_selection.start_pos, new_caret_pos),
)?
}
} else {
validate_sel_opt(new_caret_pos, old_selection.end_pos)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
min(old_caret_pos, new_caret_pos),
max(old_caret_pos, new_caret_pos),
)?
} else {
None
}
} else {
None
};
Ok(CaretWSelect::new(new_caret_pos, new_selection_opt))
}
pub fn move_caret_down<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let old_selection_opt = caret_w_select.selection_opt;
let old_caret_pos = caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else if old_line_nr + 1 >= lines.nr_of_lines() {
let curr_line_len = lines.line_len(old_line_nr)?;
(old_line_nr, curr_line_len)
} else {
let next_line = lines.get_line(old_line_nr + 1)?;
if next_line.len() <= old_col_nr {
if let Some(last_char) = next_line.chars().last() {
if is_newline(&last_char) {
(old_line_nr + 1, next_line.len() - 1)
} else {
(old_line_nr + 1, next_line.len())
}
} else {
(old_line_nr + 1, 0)
}
} else {
(old_line_nr + 1, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
validate_sel_opt(
min(old_selection.end_pos, new_caret_pos),
max(old_selection.end_pos, new_caret_pos),
)?
}
} else {
validate_sel_opt(old_selection.start_pos, new_caret_pos)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
min(old_caret_pos, new_caret_pos),
max(old_caret_pos, new_caret_pos),
)?
} else {
None
}
} else {
None
};
Ok(CaretWSelect::new(new_caret_pos, new_selection_opt))
}
pub fn move_caret_home<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let curr_line_nr = caret_w_select.caret_pos.line;
let old_col_nr = caret_w_select.caret_pos.column;
let curr_line_str = lines.get_line(curr_line_nr)?;
let line_char_iter = curr_line_str.chars();
let mut first_no_space_char_col = 0;
let mut non_space_found = false;
for c in line_char_iter {
if !c.is_whitespace() {
non_space_found = true;
break;
} else {
first_no_space_char_col += 1;
}
}
if !non_space_found {
first_no_space_char_col = 0;
}
let new_col_nr = if first_no_space_char_col == old_col_nr {
0
} else {
first_no_space_char_col
};
caret_w_select.move_caret_w_mods(
TextPos {
line: curr_line_nr,
column: new_col_nr,
},
modifiers,
)
}
pub fn move_caret_end<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let curr_line_nr = caret_w_select.caret_pos.line;
let curr_line_len = lines.line_len(curr_line_nr)?;
let new_col = if let Some(last_char) = lines.last_char(curr_line_nr)? {
if is_newline(&last_char) {
curr_line_len - 1
} else {
curr_line_len
}
} else {
0
};
let new_pos = TextPos {
line: curr_line_nr,
column: new_col,
};
caret_w_select.move_caret_w_mods(new_pos, modifiers)
}

View File

@ -1,4 +1,4 @@
pub mod big_selectable_text; pub mod big_text_area;
pub mod caret_w_select; pub mod caret_w_select;
pub mod lines; pub mod lines;
pub mod selection; pub mod selection;

View File

@ -34,6 +34,10 @@ pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult<Selection> {
validate_selection(raw_sel.start_pos, raw_sel.end_pos) validate_selection(raw_sel.start_pos, raw_sel.end_pos)
} }
pub fn validate_sel_opt(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> {
Ok(Some(validate_selection(start_pos, end_pos)?))
}
pub fn validate_selection(start_pos: TextPos, end_pos: TextPos) -> UIResult<Selection> { pub fn validate_selection(start_pos: TextPos, end_pos: TextPos) -> UIResult<Selection> {
ensure!( ensure!(
start_pos.line <= end_pos.line, start_pos.line <= end_pos.line,

View File

@ -1,5 +1,20 @@
use super::ui_error::{OutOfBounds, UIResult};
use snafu::OptionExt;
use std::slice::SliceIndex;
pub fn is_newline(char_ref: &char) -> bool { pub fn is_newline(char_ref: &char) -> bool {
let newline_codes = vec!['\u{d}', '\n']; let newline_codes = vec!['\u{d}', '\n'];
newline_codes.contains(char_ref) newline_codes.contains(char_ref)
} }
// replace vec method that return Option with one that return Result and proper Error
pub fn slice_get<T>(index: usize, slice: &[T]) -> UIResult<&<usize as SliceIndex<[T]>>::Output> {
let elt_ref = slice.get(index).context(OutOfBounds {
index,
collection_name: "Slice",
len: slice.len(),
})?;
Ok(elt_ref)
}

View File

@ -59,7 +59,10 @@ pub export fn main() u8 {
call_the_closure(function_pointer, closure_data_pointer); call_the_closure(function_pointer, closure_data_pointer);
} else { } else {
unreachable; const msg = @intToPtr([*:0]const u8, elements[1]);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
return 0;
} }
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;

View File

@ -502,11 +502,11 @@ pub enum RocCallResult<T> {
Failure(*mut c_char), Failure(*mut c_char),
} }
impl<T: Sized> Into<Result<T, &'static str>> for RocCallResult<T> { impl<T: Sized> From<RocCallResult<T>> for Result<T, &'static str> {
fn into(self) -> Result<T, &'static str> { fn from(call_result: RocCallResult<T>) -> Self {
use RocCallResult::*; use RocCallResult::*;
match self { match call_result {
Success(value) => Ok(value), Success(value) => Ok(value),
Failure(failure) => Err({ Failure(failure) => Err({
let msg = unsafe { let msg = unsafe {
@ -529,11 +529,11 @@ impl<T: Sized> Into<Result<T, &'static str>> for RocCallResult<T> {
} }
} }
impl<'a, T: Sized + Copy> Into<Result<T, &'a str>> for &'a RocCallResult<T> { impl<'a, T: Sized + Copy> From<&'a RocCallResult<T>> for Result<T, &'a str> {
fn into(self) -> Result<T, &'a str> { fn from(call_result: &'a RocCallResult<T>) -> Self {
use RocCallResult::*; use RocCallResult::*;
match self { match call_result {
Success(value) => Ok(*value), Success(value) => Ok(*value),
Failure(failure) => Err({ Failure(failure) => Err({
let msg = unsafe { let msg = unsafe {

Some files were not shown because too many files have changed in this diff Show More