mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 00:09:33 +03:00
Merge branch 'trunk' of github.com:rtfeldman/roc into type-start-parse-error
This commit is contained in:
commit
c827b52aba
@ -8,8 +8,8 @@ To build the compiler, you need these installed:
|
||||
* `libunwind` (macOS should already have this one installed)
|
||||
* `libc++-dev`
|
||||
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
|
||||
* [Zig](https://ziglang.org/) 0.7.1 or greater
|
||||
* a particular version of LLVM (see below)
|
||||
* [Zig](https://ziglang.org/), see below for version
|
||||
* LLVM, see below for version
|
||||
|
||||
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`.)
|
||||
|
||||
### Zig
|
||||
**version: 0.7.x**
|
||||
|
||||
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`
|
||||
For any other OS, checkout the [Zig installation page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
|
||||
|
||||
### LLVM
|
||||
|
||||
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.
|
||||
**version: 10.0.x**
|
||||
|
||||
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)"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@ -118,15 +118,30 @@ If you encounter `cannot find -lz` run `sudo apt install zlib1g-dev`.
|
||||
|
||||
### 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
|
||||
$ 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
|
||||
|
||||
|
46
Cargo.lock
generated
46
Cargo.lock
generated
@ -889,25 +889,6 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.0"
|
||||
@ -2013,6 +1994,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonempty"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fa586da3e43cc7df44aae0e21ed2e743218b876de3f38035683d30bd8a3828e"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
@ -2961,6 +2948,7 @@ dependencies = [
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_docs",
|
||||
"roc_editor",
|
||||
"roc_fmt",
|
||||
"roc_gen",
|
||||
@ -3012,6 +3000,25 @@ dependencies = [
|
||||
"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]]
|
||||
name = "roc_editor"
|
||||
version = "0.1.0"
|
||||
@ -3034,6 +3041,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"maplit",
|
||||
"nonempty",
|
||||
"page_size",
|
||||
"palette",
|
||||
"pest",
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM rust:1.50-slim-buster
|
||||
FROM rust:1.51-slim-buster
|
||||
WORKDIR /earthbuild
|
||||
|
||||
prep-debian:
|
||||
|
@ -33,6 +33,7 @@ target-all = [
|
||||
[dependencies]
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_can = { path = "../compiler/can" }
|
||||
roc_docs = { path = "../docs" }
|
||||
roc_parse = { path = "../compiler/parse" }
|
||||
roc_region = { path = "../compiler/region" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
|
@ -8,7 +8,7 @@ use roc_build::link::LinkType;
|
||||
use roc_gen::llvm::build::OptLevel;
|
||||
use roc_load::file::LoadingProblem;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::process::Command;
|
||||
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.")
|
||||
)
|
||||
)
|
||||
.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<()> {
|
||||
@ -132,10 +151,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
|
||||
.expect("TODO gracefully handle block_on failing");
|
||||
}
|
||||
}
|
||||
Err(LoadingProblem::ParsingFailedReport(report)) => {
|
||||
print!("{}", report);
|
||||
}
|
||||
Err(LoadingProblem::NoPlatform(report)) => {
|
||||
Err(LoadingProblem::FormattedReport(report)) => {
|
||||
print!("{}", report);
|
||||
}
|
||||
Err(other) => {
|
||||
|
@ -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::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use target_lexicon::Triple;
|
||||
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
|
@ -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 result_stack_size = layout.stack_size(env.ptr_bytes);
|
||||
|
@ -57,7 +57,7 @@ pub fn gen_and_eval<'a>(
|
||||
|
||||
let mut loaded = match loaded {
|
||||
Ok(v) => v,
|
||||
Err(LoadingProblem::ParsingFailedReport(report)) => {
|
||||
Err(LoadingProblem::FormattedReport(report)) => {
|
||||
return Ok(ReplOutput::Problems(vec![report]));
|
||||
}
|
||||
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 (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||
Some(layout) => layout.clone(),
|
||||
Some(layout) => *layout,
|
||||
None => {
|
||||
return Ok(ReplOutput::NoProblems {
|
||||
expr: "<function>".to_string(),
|
||||
|
@ -54,7 +54,7 @@ mod cli_run {
|
||||
) {
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
|
||||
if !compile_out.stderr.is_empty() {
|
||||
panic!(compile_out.stderr);
|
||||
panic!("{}", compile_out.stderr);
|
||||
}
|
||||
assert!(compile_out.status.success());
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
interface Bool2
|
||||
interface Bool
|
||||
exposes [ not, and, or, xor, isEq, isNotEq ]
|
||||
imports []
|
||||
|
||||
## Returns #False when given #True, and vice versa.
|
||||
## Returns `False` when given `True`, and vice versa.
|
||||
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`
|
||||
##
|
||||
@ -39,7 +39,7 @@ not : [True, False] -> [True, False]
|
||||
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`.
|
||||
##
|
||||
@ -55,14 +55,13 @@ and : Bool, Bool -> Bool
|
||||
##
|
||||
## In some languages, `&&` and `||` are special-cased in the compiler to skip
|
||||
## 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
|
||||
|
||||
## Exclusive or
|
||||
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`
|
||||
##
|
||||
|
@ -8,15 +8,14 @@ isEmpty : Dict * * -> Bool
|
||||
|
||||
## Convert each key and value in the #Dict to something new, by calling a conversion
|
||||
## function on each of them. Then return a new #Map of the converted keys and values.
|
||||
##
|
||||
##
|
||||
## >>> Dict.map {{ 3.14 => "pi", 1.0 => "one" }} \{ key, value } -> { key:
|
||||
##
|
||||
##
|
||||
## >>> Dict.map {[ "", "a", "bc" ]} Str.isEmpty
|
||||
##
|
||||
##
|
||||
## `map` functions like this are common in Roc, and they all work similarly.
|
||||
## See for example #Result.map, #List.map, and #Set.map.
|
||||
map :
|
||||
Dict beforeKey beforeValue,
|
||||
(\{ key: beforeKey, value: beforeValue } ->
|
||||
{ key: afterKey, value: afterValue }
|
||||
) -> Dict afterKey afterValue
|
||||
({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue })
|
||||
-> Dict afterKey afterValue
|
||||
|
@ -1,5 +1,54 @@
|
||||
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 []
|
||||
|
||||
## Types
|
||||
@ -298,7 +347,7 @@ oks : List (Result elem *) -> List elem
|
||||
##
|
||||
## > For a generalized version that returns whatever you like, instead of a `Pair`,
|
||||
## > 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.
|
||||
##
|
||||
@ -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
|
||||
##
|
||||
## 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
|
||||
|
@ -51,7 +51,7 @@ interface Num2
|
||||
##
|
||||
## In practice, these are rarely needed. It's most common to write
|
||||
## 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.
|
||||
##
|
||||
@ -102,21 +102,21 @@ Num range : @Num range
|
||||
## * 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.)
|
||||
## * 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
|
||||
I8 : Int @I8
|
||||
U8 : Int @U8
|
||||
U16 : Int @U16
|
||||
I16 : Int @I16
|
||||
U32 : Int @U32
|
||||
I32 : Int @I32
|
||||
I64 : Int @I64
|
||||
U64 : Int @U64
|
||||
I128 : Int @I128
|
||||
U128 : Int @U128
|
||||
Ilen : Int @Ilen
|
||||
Nat : Int @Nat
|
||||
I8 : Int [ @I8 ]
|
||||
U8 : Int [ @U8 ]
|
||||
U16 : Int [ @U16 ]
|
||||
I16 : Int [ @I16 ]
|
||||
U32 : Int [ @U32 ]
|
||||
I32 : Int [ @I32 ]
|
||||
I64 : Int [ @I64 ]
|
||||
U64 : Int [ @U64 ]
|
||||
I128 : Int [ @I128 ]
|
||||
U128 : Int [ @U128 ]
|
||||
Ilen : Int [ @Ilen ]
|
||||
Nat : Int [ @Nat ]
|
||||
|
||||
## 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
|
||||
|
||||
xor : Int -> Int -> Int
|
||||
xor : Int, Int -> Int
|
||||
|
||||
and : Int -> Int -> Int
|
||||
and : Int, Int -> Int
|
||||
|
||||
not : Int -> Int
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
interface Str2
|
||||
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 ]
|
||||
interface Str
|
||||
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 []
|
||||
## Types
|
||||
## # Types
|
||||
|
||||
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
|
||||
## to the basics.
|
||||
|
@ -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
|
||||
add_type(
|
||||
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
|
||||
add_type(
|
||||
Symbol::LIST_WALK,
|
||||
|
@ -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(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
@ -446,7 +453,16 @@ fn can_annotation_help(
|
||||
local_aliases,
|
||||
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();
|
||||
|
||||
introduced_variables.insert_wildcard(var);
|
||||
@ -542,8 +558,9 @@ fn can_assigned_fields<'a>(
|
||||
field = nested;
|
||||
continue 'inner;
|
||||
}
|
||||
Malformed(_) => {
|
||||
// TODO report this?
|
||||
Malformed(string) => {
|
||||
malformed(env, region, string);
|
||||
|
||||
// completely skip this element, advance to the next tag
|
||||
continue 'outer;
|
||||
}
|
||||
@ -645,8 +662,9 @@ fn can_tags<'a>(
|
||||
tag = nested;
|
||||
continue 'inner;
|
||||
}
|
||||
Tag::Malformed(_) => {
|
||||
// TODO report this?
|
||||
Tag::Malformed(string) => {
|
||||
malformed(env, region, string);
|
||||
|
||||
// completely skip this element, advance to the next tag
|
||||
continue 'outer;
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||
LIST_CONCAT => list_concat,
|
||||
LIST_CONTAINS => list_contains,
|
||||
LIST_SUM => list_sum,
|
||||
LIST_PRODUCT => list_product,
|
||||
LIST_PREPEND => list_prepend,
|
||||
LIST_JOIN => list_join,
|
||||
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_IS_MULTIPLE_OF => num_is_multiple_of,
|
||||
NUM_SQRT => num_sqrt,
|
||||
NUM_LOG => num_log,
|
||||
NUM_ROUND => num_round,
|
||||
NUM_IS_ODD => num_is_odd,
|
||||
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_CONTAINS => list_contains,
|
||||
Symbol::LIST_SUM => list_sum,
|
||||
Symbol::LIST_PRODUCT => list_product,
|
||||
Symbol::LIST_PREPEND => list_prepend,
|
||||
Symbol::LIST_JOIN => list_join,
|
||||
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_IS_MULTIPLE_OF => num_is_multiple_of,
|
||||
Symbol::NUM_SQRT => num_sqrt,
|
||||
Symbol::NUM_LOG => num_log,
|
||||
Symbol::NUM_ROUND => num_round,
|
||||
Symbol::NUM_IS_ODD => num_is_odd,
|
||||
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
|
||||
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let int_var = var_store.fresh();
|
||||
let int_percision_var = var_store.fresh();
|
||||
let body = Int(int_var, int_percision_var, i64::MAX.into());
|
||||
let int_precision_var = var_store.fresh();
|
||||
let body = Int(int_var, int_precision_var, i64::MAX.into());
|
||||
|
||||
Def {
|
||||
annotation: None,
|
||||
@ -426,8 +430,8 @@ fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
/// Num.minInt : Int
|
||||
fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let int_var = var_store.fresh();
|
||||
let int_percision_var = var_store.fresh();
|
||||
let body = Int(int_var, int_percision_var, i64::MIN.into());
|
||||
let int_precision_var = var_store.fresh();
|
||||
let body = Int(int_var, int_precision_var, i64::MIN.into());
|
||||
|
||||
Def {
|
||||
annotation: None,
|
||||
@ -1131,50 +1135,82 @@ fn num_sqrt(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 percision_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![(
|
||||
// if-condition
|
||||
no_region(
|
||||
// Num.neq denominator 0
|
||||
RunLowLevel {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(float_var, Var(Symbol::ARG_1)),
|
||||
(float_var, Float(unbound_zero_var, percision_var, 0.0)),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
},
|
||||
),
|
||||
// denominator was not zero
|
||||
no_region(
|
||||
// Ok (Float.#divUnchecked numerator denominator)
|
||||
tag(
|
||||
"Ok",
|
||||
vec![
|
||||
// Num.#divUnchecked numerator denominator
|
||||
RunLowLevel {
|
||||
op: LowLevel::NumSqrtUnchecked,
|
||||
args: vec![(float_var, Var(Symbol::ARG_1))],
|
||||
ret_var: float_var,
|
||||
},
|
||||
],
|
||||
var_store,
|
||||
),
|
||||
),
|
||||
)],
|
||||
final_else: Box::new(
|
||||
// denominator was zero
|
||||
no_region(RunLowLevel {
|
||||
op: LowLevel::NumGte,
|
||||
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(
|
||||
"Err",
|
||||
vec![tag("DivByZero", Vec::new(), var_store)],
|
||||
"Ok",
|
||||
vec![RunLowLevel {
|
||||
op: LowLevel::NumSqrtUnchecked,
|
||||
args: vec![(float_var, Var(Symbol::ARG_1))],
|
||||
ret_var: float_var,
|
||||
}],
|
||||
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(
|
||||
@ -1385,8 +1421,8 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
/// Num.maxI128: I128
|
||||
fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let int_var = var_store.fresh();
|
||||
let int_percision_var = var_store.fresh();
|
||||
let body = Int(int_var, int_percision_var, i128::MAX);
|
||||
let int_precision_var = var_store.fresh();
|
||||
let body = Int(int_var, int_precision_var, i128::MAX);
|
||||
|
||||
let std = roc_builtins::std::types();
|
||||
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
|
||||
fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let result_var = var_store.fresh();
|
||||
lowlevel_1(symbol, LowLevel::ListSum, var_store)
|
||||
}
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListSum,
|
||||
args: vec![(list_var, Var(Symbol::ARG_1))],
|
||||
ret_var: result_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(list_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
result_var,
|
||||
)
|
||||
/// List.product : List (Num a) -> Num a
|
||||
fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::ListProduct, var_store)
|
||||
}
|
||||
|
||||
/// 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 num_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 body = If {
|
||||
@ -2675,7 +2701,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(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,
|
||||
},
|
||||
@ -2724,7 +2750,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let num_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 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,
|
||||
Int(unbound_zero_var, unbound_zero_percision_var, 0),
|
||||
Int(unbound_zero_var, unbound_zero_precision_var, 0),
|
||||
),
|
||||
],
|
||||
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 len_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 ret_var = var_store.fresh();
|
||||
|
||||
@ -2810,7 +2836,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
RunLowLevel {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(len_var, Int(zero_var, zero_percision_var, 0)),
|
||||
(len_var, Int(zero_var, zero_precision_var, 0)),
|
||||
(
|
||||
len_var,
|
||||
RunLowLevel {
|
||||
@ -2834,7 +2860,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::ListGetUnsafe,
|
||||
args: vec![
|
||||
(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,
|
||||
},
|
||||
@ -2876,7 +2902,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let len_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 ret_var = var_store.fresh();
|
||||
|
||||
@ -2891,7 +2917,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
RunLowLevel {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(len_var, Int(num_var, num_percision_var, 0)),
|
||||
(len_var, Int(num_var, num_precision_var, 0)),
|
||||
(
|
||||
len_var,
|
||||
RunLowLevel {
|
||||
@ -2930,7 +2956,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
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,
|
||||
},
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -408,46 +408,7 @@ pub fn canonicalize_expr<'a>(
|
||||
}
|
||||
ast::Expr::Var { module_name, ident } => {
|
||||
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) => {
|
||||
can_defs_with_return(
|
||||
env,
|
||||
@ -767,11 +728,6 @@ pub fn canonicalize_expr<'a>(
|
||||
|
||||
(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 {
|
||||
string,
|
||||
base,
|
||||
|
@ -4,7 +4,7 @@ use roc_module::ident::ModuleName;
|
||||
use roc_module::operator::BinOp::Pizza;
|
||||
use roc_module::operator::{BinOp, CalledVia};
|
||||
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};
|
||||
|
||||
// BinOp precedence logic adapted from Gluon by Markus Westerlind, MIT licensed
|
||||
@ -59,7 +59,7 @@ fn new_op_call_expr<'a>(
|
||||
Located { value, region }
|
||||
}
|
||||
|
||||
fn desugar_defs<'a>(
|
||||
fn desugar_def_helps<'a>(
|
||||
arena: &'a Bump,
|
||||
region: Region,
|
||||
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::*;
|
||||
|
||||
match def {
|
||||
Body(loc_pattern, loc_expr) | Nested(Body(loc_pattern, loc_expr)) => {
|
||||
Body(loc_pattern, desugar_expr(arena, loc_expr))
|
||||
}
|
||||
SpaceBefore(def, _)
|
||||
| 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),
|
||||
Body(loc_pattern, loc_expr) => Body(loc_pattern, desugar_expr(arena, loc_expr)),
|
||||
SpaceBefore(def, _) | SpaceAfter(def, _) => desugar_def(arena, def),
|
||||
alias @ Alias { .. } => *alias,
|
||||
ann @ Annotation(_, _) => *ann,
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
}
|
||||
| Nested(AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
}) => AnnotatedBody {
|
||||
} => AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment: *comment,
|
||||
body_pattern: *body_pattern,
|
||||
body_expr: desugar_expr(arena, body_expr),
|
||||
},
|
||||
Nested(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>> {
|
||||
match &loc_expr.value {
|
||||
Float(_)
|
||||
| Nested(Float(_))
|
||||
| Num(_)
|
||||
| Nested(Num(_))
|
||||
| NonBase10Int { .. }
|
||||
| Nested(NonBase10Int { .. })
|
||||
| Str(_)
|
||||
| Nested(Str(_))
|
||||
| AccessorFunction(_)
|
||||
| Nested(AccessorFunction(_))
|
||||
| Var { .. }
|
||||
| Nested(Var { .. })
|
||||
| MalformedIdent(_, _)
|
||||
| Nested(MalformedIdent(_, _))
|
||||
| MalformedClosure
|
||||
| Nested(MalformedClosure)
|
||||
| PrecedenceConflict { .. }
|
||||
| Nested(PrecedenceConflict { .. })
|
||||
| GlobalTag(_)
|
||||
| Nested(GlobalTag(_))
|
||||
| PrivateTag(_)
|
||||
| Nested(PrivateTag(_)) => loc_expr,
|
||||
| PrivateTag(_) => loc_expr,
|
||||
|
||||
Access(sub_expr, paths) | Nested(Access(sub_expr, paths)) => {
|
||||
Access(sub_expr, paths) => {
|
||||
let region = loc_expr.region;
|
||||
let loc_sub_expr = Located {
|
||||
region,
|
||||
value: Nested(sub_expr),
|
||||
value: **sub_expr,
|
||||
};
|
||||
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 {
|
||||
items,
|
||||
final_comments,
|
||||
}
|
||||
| Nested(List {
|
||||
items,
|
||||
final_comments,
|
||||
}) => {
|
||||
} => {
|
||||
let mut new_items = Vec::with_capacity_in(items.len(), arena);
|
||||
|
||||
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 {
|
||||
fields,
|
||||
final_comments,
|
||||
}
|
||||
| Nested(Record {
|
||||
fields,
|
||||
final_comments,
|
||||
}) => {
|
||||
} => {
|
||||
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
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,
|
||||
update,
|
||||
final_comments,
|
||||
}
|
||||
| Nested(RecordUpdate {
|
||||
fields,
|
||||
update,
|
||||
final_comments,
|
||||
}) => {
|
||||
} => {
|
||||
// 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);
|
||||
|
||||
@ -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)) => {
|
||||
arena.alloc(Located {
|
||||
region: loc_expr.region,
|
||||
value: Closure(loc_patterns, desugar_expr(arena, loc_ret)),
|
||||
})
|
||||
}
|
||||
Backpassing(loc_patterns, loc_body, loc_ret)
|
||||
| Nested(Backpassing(loc_patterns, loc_body, loc_ret)) => {
|
||||
Closure(loc_patterns, loc_ret) => arena.alloc(Located {
|
||||
region: loc_expr.region,
|
||||
value: Closure(loc_patterns, desugar_expr(arena, loc_ret)),
|
||||
}),
|
||||
Backpassing(loc_patterns, loc_body, loc_ret) => {
|
||||
// loc_patterns <- loc_body
|
||||
//
|
||||
// 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)) => {
|
||||
desugar_bin_ops(arena, loc_expr.region, lefts, right)
|
||||
}
|
||||
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)) => {
|
||||
BinOps(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) => {
|
||||
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
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 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 mut alternatives = Vec::with_capacity_in(branch.patterns.len(), arena);
|
||||
for loc_pattern in branch.patterns.iter() {
|
||||
alternatives.push(Located {
|
||||
region: loc_pattern.region,
|
||||
value: Pattern::Nested(&loc_pattern.value),
|
||||
})
|
||||
}
|
||||
alternatives.extend(branch.patterns.iter().copied());
|
||||
|
||||
let desugared_guard = if let Some(guard) = &branch.guard {
|
||||
Some(desugar_expr(arena, guard).clone())
|
||||
Some(*desugar_expr(arena, guard))
|
||||
} else {
|
||||
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 {
|
||||
patterns: alternatives,
|
||||
value: Located {
|
||||
region: desugared.region,
|
||||
value: Nested(&desugared.value),
|
||||
},
|
||||
value: *desugared,
|
||||
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,
|
||||
})
|
||||
}
|
||||
UnaryOp(loc_arg, loc_op) | Nested(UnaryOp(loc_arg, loc_op)) => {
|
||||
UnaryOp(loc_arg, loc_op) => {
|
||||
use roc_module::operator::UnaryOp::*;
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
SpaceBefore(expr, _)
|
||||
| Nested(SpaceBefore(expr, _))
|
||||
| SpaceAfter(expr, _)
|
||||
| Nested(SpaceAfter(expr, _))
|
||||
| ParensAround(expr)
|
||||
| Nested(ParensAround(expr))
|
||||
| Nested(Nested(expr)) => {
|
||||
SpaceBefore(expr, _) | SpaceAfter(expr, _) | ParensAround(expr) => {
|
||||
// Since we've already begun canonicalization, spaces and parens
|
||||
// are no longer needed and should be dropped.
|
||||
desugar_expr(
|
||||
arena,
|
||||
arena.alloc(Located {
|
||||
value: Nested(expr),
|
||||
value: **expr,
|
||||
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.
|
||||
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() {
|
||||
desugared_if_thens.push((
|
||||
desugar_expr(arena, condition).clone(),
|
||||
desugar_expr(arena, then_branch).clone(),
|
||||
*desugar_expr(arena, condition),
|
||||
*desugar_expr(arena, then_branch),
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -238,7 +238,7 @@ pub fn canonicalize_pattern<'a>(
|
||||
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)
|
||||
}
|
||||
RecordDestructure(patterns) => {
|
||||
|
@ -19,7 +19,6 @@ impl<'a> Formattable<'a> for Def<'a> {
|
||||
SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => {
|
||||
spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline()
|
||||
}
|
||||
Nested(def) => def.is_multiline(),
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
@ -99,7 +98,6 @@ impl<'a> Formattable<'a> for Def<'a> {
|
||||
sub_def.format(buf, indent);
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
Nested(def) => def.format(buf, indent),
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
expr: loc_subexpr, ..
|
||||
}) => loc_subexpr.is_multiline(),
|
||||
|
||||
ParensAround(subexpr) | Nested(subexpr) => subexpr.is_multiline(),
|
||||
ParensAround(subexpr) => subexpr.is_multiline(),
|
||||
|
||||
Closure(loc_patterns, loc_body) => {
|
||||
// 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);
|
||||
}
|
||||
Nested(nested_expr) => {
|
||||
nested_expr.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
AccessorFunction(key) => {
|
||||
buf.push('.');
|
||||
buf.push_str(key);
|
||||
@ -508,8 +505,6 @@ fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
Nested(nested_expr) => empty_line_before_expr(nested_expr),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,6 @@ impl<'a> Formattable<'a> for Pattern<'a> {
|
||||
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::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(string) | MalformedIdent(string, _) => buf.push_str(string),
|
||||
QualifiedIdentifier { module_name, ident } => {
|
||||
|
@ -28,7 +28,7 @@ mod test_fmt {
|
||||
|
||||
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)
|
||||
};
|
||||
}
|
||||
|
||||
@ -1833,23 +1833,23 @@ mod test_fmt {
|
||||
indoc!(
|
||||
r#"
|
||||
when b is
|
||||
1 | 2 |
|
||||
3
|
||||
->
|
||||
1 | 2 |
|
||||
3
|
||||
->
|
||||
|
||||
4
|
||||
5 | 6 | 7 ->
|
||||
4
|
||||
5 | 6 | 7 ->
|
||||
|
||||
8
|
||||
9
|
||||
| 10 -> 11
|
||||
8
|
||||
9
|
||||
| 10 -> 11
|
||||
|
||||
12 | 13 ->
|
||||
when c is
|
||||
14 | 15 -> 16
|
||||
17
|
||||
| 18 -> 19
|
||||
20 -> 21
|
||||
12 | 13 ->
|
||||
when c is
|
||||
14 | 15 -> 16
|
||||
17
|
||||
| 18 -> 19
|
||||
20 -> 21
|
||||
|
||||
"#
|
||||
),
|
||||
|
@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash;
|
||||
use crate::llvm::build_list::{
|
||||
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_map2, list_map3, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set,
|
||||
list_single, list_sum, list_walk, list_walk_backwards,
|
||||
list_map2, list_map3, list_map_with_index, list_prepend, list_product, list_repeat,
|
||||
list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
|
||||
};
|
||||
use crate::llvm::build_str::{
|
||||
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,
|
||||
}
|
||||
|
||||
impl Into<OptimizationLevel> for OptLevel {
|
||||
fn into(self) -> OptimizationLevel {
|
||||
match self {
|
||||
impl From<OptLevel> for OptimizationLevel {
|
||||
fn from(level: OptLevel) -> Self {
|
||||
match level {
|
||||
OptLevel::Normal => OptimizationLevel::None,
|
||||
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),
|
||||
);
|
||||
|
||||
add_intrinsic(
|
||||
module,
|
||||
LLVM_LOG_F64,
|
||||
f64_type.fn_type(&[f64_type.into()], false),
|
||||
);
|
||||
|
||||
add_intrinsic(
|
||||
module,
|
||||
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_I32: &str = "llvm.memset.p0i8.i32";
|
||||
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_FABS_F64: &str = "llvm.fabs.f64";
|
||||
static LLVM_SIN_F64: &str = "llvm.sin.f64";
|
||||
@ -2030,7 +2037,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||
// access itself!
|
||||
// scope = scope.clone();
|
||||
|
||||
scope.insert(*symbol, (layout.clone(), val));
|
||||
scope.insert(*symbol, (*layout, val));
|
||||
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
|
||||
// so we can just treat this invoke as a normal call
|
||||
let stmt =
|
||||
roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass);
|
||||
let stmt = roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass);
|
||||
build_exp_stmt(env, layout_ids, scope, parent, &stmt)
|
||||
}
|
||||
|
||||
@ -2088,7 +2094,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||
scope,
|
||||
parent,
|
||||
*symbol,
|
||||
layout.clone(),
|
||||
*layout,
|
||||
function_value.into(),
|
||||
call.arguments,
|
||||
None,
|
||||
@ -2108,7 +2114,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||
scope,
|
||||
parent,
|
||||
*symbol,
|
||||
layout.clone(),
|
||||
*layout,
|
||||
function_ptr.into(),
|
||||
call.arguments,
|
||||
None,
|
||||
@ -2135,7 +2141,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||
scope,
|
||||
parent,
|
||||
*symbol,
|
||||
layout.clone(),
|
||||
*layout,
|
||||
function_ptr.into(),
|
||||
call.arguments,
|
||||
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);
|
||||
|
||||
let switch_args = SwitchArgsIr {
|
||||
cond_layout: cond_layout.clone(),
|
||||
cond_layout: *cond_layout,
|
||||
cond_symbol: *cond_symbol,
|
||||
branches,
|
||||
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()) {
|
||||
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
|
||||
@ -2277,7 +2283,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||
match modify {
|
||||
Inc(symbol, inc_amount) => {
|
||||
let (value, layout) = load_symbol_and_layout(scope, symbol);
|
||||
let layout = layout.clone();
|
||||
let layout = *layout;
|
||||
|
||||
if layout.contains_refcounted() {
|
||||
increment_refcount_layout(
|
||||
@ -3177,7 +3183,7 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
|
||||
|
||||
let function_pointer_type = {
|
||||
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
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
@ -3455,7 +3461,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
||||
// Add args to scope
|
||||
for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) {
|
||||
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);
|
||||
@ -3931,6 +3937,13 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
|
||||
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 => {
|
||||
// List.append : List elem, elem -> List elem
|
||||
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)
|
||||
}
|
||||
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor
|
||||
| NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
|
||||
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
|
||||
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
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);
|
||||
|
||||
scope.insert(symbol, (ret_layout.clone(), call_result));
|
||||
scope.insert(symbol, (*ret_layout, call_result));
|
||||
|
||||
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(),
|
||||
NumAbs => env.call_intrinsic(LLVM_FABS_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()]),
|
||||
NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]),
|
||||
NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]),
|
||||
|
@ -717,11 +717,7 @@ pub fn dict_walk<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
stepper_layout,
|
||||
&[
|
||||
key_layout.clone(),
|
||||
value_layout.clone(),
|
||||
accum_layout.clone(),
|
||||
],
|
||||
&[*key_layout, *value_layout, *accum_layout],
|
||||
)
|
||||
.as_global_value()
|
||||
.as_pointer_value();
|
||||
|
@ -70,7 +70,7 @@ fn build_hash_layout<'a, 'ctx, 'env>(
|
||||
unreachable!("recursion pointers should never be hashed directly")
|
||||
}
|
||||
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);
|
||||
|
||||
@ -287,7 +287,7 @@ fn hash_struct<'a, 'ctx, 'env>(
|
||||
unreachable!("The current layout should not be recursive, but is")
|
||||
}
|
||||
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(
|
||||
env.arena,
|
||||
@ -811,7 +811,7 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
field_layouts,
|
||||
WhenRecursive::Loop(union_layout.clone()),
|
||||
WhenRecursive::Loop(*union_layout),
|
||||
seed,
|
||||
struct_value,
|
||||
)
|
||||
|
@ -423,7 +423,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
|
||||
let ctx = env.context;
|
||||
|
||||
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) => (
|
||||
InPlace::InPlace,
|
||||
// this pointer will never actually be dereferenced
|
||||
@ -434,7 +434,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
|
||||
MemoryMode::Unique => InPlace::InPlace,
|
||||
MemoryMode::Refcounted => InPlace::Clone,
|
||||
},
|
||||
elem_layout.clone(),
|
||||
*elem_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")
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub fn list_walk<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
@ -868,7 +943,7 @@ fn list_walk_generic<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
func_layout,
|
||||
&[element_layout.clone(), default_layout.clone()],
|
||||
&[*element_layout, *default_layout],
|
||||
)
|
||||
.as_global_value()
|
||||
.as_pointer_value();
|
||||
@ -959,7 +1034,7 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
|
||||
env.builder.build_store(transform_ptr, transform);
|
||||
|
||||
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_pointer_value();
|
||||
|
||||
@ -1066,7 +1141,7 @@ pub fn list_keep_result<'a, 'ctx, 'env>(
|
||||
env.builder.build_store(transform_ptr, transform);
|
||||
|
||||
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_pointer_value();
|
||||
|
||||
@ -1130,7 +1205,7 @@ pub fn list_map<'a, 'ctx, 'env>(
|
||||
list,
|
||||
element_layout,
|
||||
bitcode::LIST_MAP,
|
||||
&[element_layout.clone()],
|
||||
&[*element_layout],
|
||||
)
|
||||
}
|
||||
|
||||
@ -1151,7 +1226,7 @@ pub fn list_map_with_index<'a, 'ctx, 'env>(
|
||||
list,
|
||||
element_layout,
|
||||
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");
|
||||
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 =
|
||||
build_transform_caller(env, layout_ids, transform_layout, &argument_layouts)
|
||||
.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");
|
||||
env.builder.build_store(transform_ptr, transform);
|
||||
|
||||
let argument_layouts = [
|
||||
element1_layout.clone(),
|
||||
element2_layout.clone(),
|
||||
element3_layout.clone(),
|
||||
];
|
||||
let argument_layouts = [*element1_layout, *element2_layout, *element3_layout];
|
||||
let stepper_caller =
|
||||
build_transform_caller(env, layout_ids, transform_layout, &argument_layouts)
|
||||
.as_global_value()
|
||||
|
@ -102,7 +102,7 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
|
||||
Builtin::List(_, elem) => build_list_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
&Layout::Builtin(builtin.clone()),
|
||||
&Layout::Builtin(*builtin),
|
||||
elem,
|
||||
lhs_val.into_struct_value(),
|
||||
rhs_val.into_struct_value(),
|
||||
@ -170,7 +170,7 @@ fn build_eq<'a, 'ctx, 'env>(
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -188,7 +188,7 @@ fn build_eq<'a, 'ctx, 'env>(
|
||||
build_tag_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
WhenRecursive::Loop(union_layout.clone()),
|
||||
WhenRecursive::Loop(union_layout),
|
||||
&layout,
|
||||
&union_layout,
|
||||
field1_cast.into(),
|
||||
@ -262,7 +262,7 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
|
||||
let is_equal = build_list_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
&Layout::Builtin(builtin.clone()),
|
||||
&Layout::Builtin(*builtin),
|
||||
elem,
|
||||
lhs_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")
|
||||
}
|
||||
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(
|
||||
env.arena,
|
||||
@ -717,7 +717,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
|
||||
field2_cast.into(),
|
||||
&field_layout,
|
||||
&field_layout,
|
||||
WhenRecursive::Loop(union_layout.clone()),
|
||||
WhenRecursive::Loop(*union_layout),
|
||||
)
|
||||
.into_int_value()
|
||||
}
|
||||
@ -1234,7 +1234,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
field_layouts,
|
||||
WhenRecursive::Loop(union_layout.clone()),
|
||||
WhenRecursive::Loop(*union_layout),
|
||||
struct1,
|
||||
struct2,
|
||||
)
|
||||
|
@ -454,7 +454,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
mode,
|
||||
&WhenRecursive::Loop(variant.clone()),
|
||||
&WhenRecursive::Loop(*variant),
|
||||
tags,
|
||||
value.into_pointer_value(),
|
||||
true,
|
||||
@ -470,7 +470,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
mode,
|
||||
&WhenRecursive::Loop(variant.clone()),
|
||||
&WhenRecursive::Loop(*variant),
|
||||
&*env.arena.alloc([other_fields]),
|
||||
value.into_pointer_value(),
|
||||
true,
|
||||
@ -484,7 +484,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
mode,
|
||||
&WhenRecursive::Loop(variant.clone()),
|
||||
&WhenRecursive::Loop(*variant),
|
||||
&*env.arena.alloc([*fields]),
|
||||
value.into_pointer_value(),
|
||||
true,
|
||||
@ -497,7 +497,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
mode,
|
||||
&WhenRecursive::Loop(variant.clone()),
|
||||
&WhenRecursive::Loop(*variant),
|
||||
tags,
|
||||
value.into_pointer_value(),
|
||||
false,
|
||||
@ -549,7 +549,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
|
||||
unreachable!("recursion pointers should never be hashed directly")
|
||||
}
|
||||
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);
|
||||
|
||||
|
@ -8,9 +8,9 @@ pub enum RocCallResult<T> {
|
||||
Failure(*mut c_char),
|
||||
}
|
||||
|
||||
impl<T: Sized> Into<Result<T, String>> for RocCallResult<T> {
|
||||
fn into(self) -> Result<T, String> {
|
||||
match self {
|
||||
impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
|
||||
fn from(call_result: RocCallResult<T>) -> Self {
|
||||
match call_result {
|
||||
Success(value) => Ok(value),
|
||||
Failure(failure) => Err({
|
||||
let raw = unsafe { CString::from_raw(failure) };
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![warn(clippy::all, clippy::dbg_macro)]
|
||||
// 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 roc_builtins::bitcode;
|
||||
@ -105,7 +105,7 @@ where
|
||||
} => {
|
||||
// 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)
|
||||
}
|
||||
Stmt::Switch {
|
||||
@ -252,32 +252,20 @@ where
|
||||
x => Err(format!("layout, {:?}, not implemented yet", x)),
|
||||
}
|
||||
}
|
||||
LowLevel::NumAcos => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::NUM_ACOS.to_string(),
|
||||
args,
|
||||
&[layout.clone()],
|
||||
layout,
|
||||
),
|
||||
LowLevel::NumAsin => self.build_fn_call(
|
||||
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::NumAcos => {
|
||||
self.build_fn_call(sym, bitcode::NUM_ACOS.to_string(), args, &[*layout], layout)
|
||||
}
|
||||
LowLevel::NumAsin => {
|
||||
self.build_fn_call(sym, bitcode::NUM_ASIN.to_string(), args, &[*layout], layout)
|
||||
}
|
||||
LowLevel::NumAtan => {
|
||||
self.build_fn_call(sym, bitcode::NUM_ATAN.to_string(), args, &[*layout], layout)
|
||||
}
|
||||
LowLevel::NumPowInt => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::NUM_POW_INT.to_string(),
|
||||
args,
|
||||
&[layout.clone(), layout.clone()],
|
||||
&[*layout, *layout],
|
||||
layout,
|
||||
),
|
||||
LowLevel::NumSub => {
|
||||
@ -472,7 +460,7 @@ where
|
||||
} => {
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,6 @@ mod helpers;
|
||||
|
||||
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
mod gen_num {
|
||||
//use roc_std::RocOrder;
|
||||
|
||||
#[test]
|
||||
fn i64_values() {
|
||||
assert_evals_to!("0", 0, i64);
|
||||
|
@ -5,6 +5,7 @@ use roc_can::builtins::builtin_defs_map;
|
||||
use roc_collections::all::MutMap;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
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
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn helper<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
src: &str,
|
||||
|
@ -120,7 +120,7 @@ fn generate_module_doc<'a>(
|
||||
(acc, None)
|
||||
}
|
||||
|
||||
Body(_, _) | Nested(_) => (acc, None),
|
||||
Body(_, _) => (acc, None),
|
||||
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
|
@ -784,6 +784,10 @@ enum Msg<'a> {
|
||||
},
|
||||
|
||||
FailedToParse(ParseProblem<'a, SyntaxError<'a>>),
|
||||
FailedToReadFile {
|
||||
filename: PathBuf,
|
||||
error: io::ErrorKind,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -996,18 +1000,16 @@ pub enum LoadingProblem<'a> {
|
||||
FileProblem {
|
||||
filename: PathBuf,
|
||||
error: io::ErrorKind,
|
||||
msg: &'static str,
|
||||
},
|
||||
ParsingFailed(ParseProblem<'a, SyntaxError<'a>>),
|
||||
UnexpectedHeader(String),
|
||||
/// there is no platform (likely running an Interface module)
|
||||
NoPlatform(String),
|
||||
|
||||
MsgChannelDied,
|
||||
ErrJoiningWorkerThreads,
|
||||
TriedToImportAppModule,
|
||||
/// a formatted report of parsing failure
|
||||
ParsingFailedReport(String),
|
||||
|
||||
/// a formatted report
|
||||
FormattedReport(String),
|
||||
}
|
||||
|
||||
pub enum Phases {
|
||||
@ -1399,6 +1401,14 @@ where
|
||||
Err(LoadingProblem::ParsingFailed(problem)) => {
|
||||
msg_tx.send(Msg::FailedToParse(problem)).unwrap();
|
||||
}
|
||||
Err(LoadingProblem::FileProblem {
|
||||
filename,
|
||||
error,
|
||||
}) => {
|
||||
msg_tx
|
||||
.send(Msg::FailedToReadFile { filename, error })
|
||||
.unwrap();
|
||||
}
|
||||
Err(other) => {
|
||||
return Err(other);
|
||||
}
|
||||
@ -1457,6 +1467,16 @@ where
|
||||
let worker_listeners = worker_listeners.into_bump_slice();
|
||||
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,
|
||||
// and processing those messages will in turn queue up more messages.
|
||||
for msg in msg_rx.iter() {
|
||||
@ -1490,12 +1510,7 @@ where
|
||||
// We're done! There should be no more messages pending.
|
||||
debug_assert!(msg_rx.is_empty());
|
||||
|
||||
// Shut down all the worker threads.
|
||||
for listener in worker_listeners {
|
||||
listener
|
||||
.send(WorkerMsg::Shutdown)
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
}
|
||||
shut_down_worker_threads!();
|
||||
|
||||
return Ok(LoadResult::Monomorphized(finish_specialization(
|
||||
state,
|
||||
@ -1503,50 +1518,29 @@ where
|
||||
exposed_to_host,
|
||||
)?));
|
||||
}
|
||||
Msg::FailedToReadFile { filename, error } => {
|
||||
shut_down_worker_threads!();
|
||||
|
||||
let buf = to_file_problem_report(&filename, error);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
|
||||
Msg::FailedToParse(problem) => {
|
||||
// Shut down all the worker threads.
|
||||
for listener in worker_listeners {
|
||||
listener
|
||||
.send(WorkerMsg::Shutdown)
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
}
|
||||
shut_down_worker_threads!();
|
||||
|
||||
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 src_lines: Vec<&str> = src.split('\n').collect();
|
||||
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
let mut module_ids = Arc::try_unwrap(state.arc_modules)
|
||||
let module_ids = Arc::try_unwrap(state.arc_modules)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("There were still outstanding Arc references to module_ids")
|
||||
})
|
||||
.into_inner()
|
||||
.into_module_ids();
|
||||
|
||||
let module_id =
|
||||
module_ids.get_or_insert(&"find module name somehow?".into());
|
||||
|
||||
let interns = Interns {
|
||||
let buf = to_parse_problem_report(
|
||||
problem,
|
||||
module_ids,
|
||||
all_ident_ids: state.constrained_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();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
return Err(LoadingProblem::ParsingFailedReport(buf));
|
||||
state.constrained_ident_ids,
|
||||
);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
msg => {
|
||||
// This is where most of the main thread's work gets done.
|
||||
@ -1950,7 +1944,7 @@ fn update<'a>(
|
||||
};
|
||||
|
||||
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(_) => {
|
||||
unreachable!();
|
||||
}
|
||||
Msg::FailedToReadFile { .. } => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2142,72 +2139,8 @@ fn finish_specialization(
|
||||
}
|
||||
Valid(To::NewPackage(p_or_p)) => p_or_p,
|
||||
other => {
|
||||
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
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));
|
||||
let buf = to_missing_platform_report(state.root_id, other);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
};
|
||||
|
||||
@ -2332,6 +2265,10 @@ fn load_pkg_config<'a>(
|
||||
)))
|
||||
}
|
||||
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
|
||||
let pkg_config_module_msg = fabricate_pkg_config_module(
|
||||
arena,
|
||||
@ -2342,6 +2279,7 @@ fn load_pkg_config<'a>(
|
||||
module_ids.clone(),
|
||||
ident_ids_by_module.clone(),
|
||||
&header,
|
||||
header_src,
|
||||
pkg_module_timing,
|
||||
)
|
||||
.1;
|
||||
@ -2360,7 +2298,7 @@ fn load_pkg_config<'a>(
|
||||
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
|
||||
}
|
||||
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 {
|
||||
filename,
|
||||
error: err.kind(),
|
||||
msg: "while reading a Pkg-Config.roc file",
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -2633,7 +2570,7 @@ fn parse_header<'a>(
|
||||
module_timing,
|
||||
)),
|
||||
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 {
|
||||
filename,
|
||||
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`
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn send_header_two<'a>(
|
||||
arena: &'a Bump,
|
||||
#[derive(Debug)]
|
||||
struct PlatformHeaderInfo<'a> {
|
||||
filename: PathBuf,
|
||||
is_root_module: bool,
|
||||
shorthand: &'a str,
|
||||
header_src: &'a str,
|
||||
app_module_id: ModuleId,
|
||||
packages: &'a [Located<PackageEntry<'a>>],
|
||||
provides: &'a [Located<ExposesEntry<'a, &'a str>>],
|
||||
requires: &'a [Located<TypedIdent<'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>,
|
||||
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
|
||||
@ -2958,6 +2900,18 @@ fn send_header_two<'a>(
|
||||
) -> (ModuleId, Msg<'a>) {
|
||||
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 mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
|
||||
@ -3149,7 +3103,7 @@ fn send_header_two<'a>(
|
||||
package_qualified_imported_modules,
|
||||
deps_by_name,
|
||||
exposes: exposed,
|
||||
header_src: "#builtin effect header",
|
||||
header_src,
|
||||
parse_state,
|
||||
exposed_imports: scope,
|
||||
module_timing,
|
||||
@ -3278,21 +3232,27 @@ fn fabricate_pkg_config_module<'a>(
|
||||
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
|
||||
header: &PlatformHeader<'a>,
|
||||
header_src: &'a str,
|
||||
module_timing: ModuleTiming,
|
||||
) -> (ModuleId, Msg<'a>) {
|
||||
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] =
|
||||
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(
|
||||
arena,
|
||||
filename,
|
||||
false,
|
||||
shorthand,
|
||||
app_module_id,
|
||||
&[],
|
||||
provides,
|
||||
header.requires.clone().into_bump_slice(),
|
||||
header.imports.clone().into_bump_slice(),
|
||||
info,
|
||||
parse_state,
|
||||
module_ids,
|
||||
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) {
|
||||
Ok((_, success, _state)) => success,
|
||||
Err((_, fail, _)) => {
|
||||
return Err(LoadingProblem::ParsingFailed(
|
||||
fail.into_parse_problem(header.module_path, source),
|
||||
));
|
||||
return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem(
|
||||
header.module_path,
|
||||
header.header_src,
|
||||
source,
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@ -3811,12 +3773,7 @@ fn make_specializations<'a>(
|
||||
// TODO: for now this final specialization pass is sequential,
|
||||
// with no parallelization at all. We should try to parallelize
|
||||
// this, but doing so will require a redesign of Procs.
|
||||
procs = roc_mono::ir::specialize_all(
|
||||
&mut mono_env,
|
||||
procs,
|
||||
&mut layout_cache,
|
||||
// &finished_info.vars_by_symbol,
|
||||
);
|
||||
procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
|
||||
|
||||
let external_specializations_requested = procs.externals_we_need.clone();
|
||||
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
|
||||
@ -4180,3 +4137,179 @@ where
|
||||
|
||||
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
|
||||
}
|
||||
|
8
compiler/load/tests/fixtures/build/no_deps/MissingDep.roc
vendored
Normal file
8
compiler/load/tests/fixtures/build/no_deps/MissingDep.roc
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
interface MissingDep
|
||||
exposes [ unit ]
|
||||
imports [ ThisFileIsMissing ]
|
||||
|
||||
Unit : [ Unit ]
|
||||
|
||||
unit : Unit
|
||||
unit = Unit
|
@ -32,87 +32,94 @@ mod test_load {
|
||||
|
||||
// HELPERS
|
||||
|
||||
fn multiple_modules(files: Vec<(&str, &str)>) -> LoadedModule {
|
||||
multiple_modules_help(files).unwrap()
|
||||
fn multiple_modules(files: Vec<(&str, &str)>) -> Result<LoadedModule, String> {
|
||||
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::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
|
||||
let arena = Bump::new();
|
||||
let arena = &arena;
|
||||
|
||||
let stdlib = roc_builtins::std::standard_stdlib();
|
||||
|
||||
let mut file_handles: Vec<_> = Vec::new();
|
||||
let exposed_types = MutMap::default();
|
||||
let loaded = {
|
||||
// create a temporary directory
|
||||
let dir = tempdir()?;
|
||||
|
||||
let app_module = files.pop().unwrap();
|
||||
let interfaces = files;
|
||||
// create a temporary directory
|
||||
let dir = tempdir()?;
|
||||
|
||||
debug_assert!(
|
||||
app_module.1.starts_with("app"),
|
||||
"The final module should be the application module"
|
||||
);
|
||||
let app_module = files.pop().unwrap();
|
||||
let interfaces = files;
|
||||
|
||||
for (name, source) in interfaces {
|
||||
let mut filename = PathBuf::from(name);
|
||||
filename.set_extension("roc");
|
||||
let file_path = dir.path().join(filename.clone());
|
||||
let mut file = File::create(file_path)?;
|
||||
writeln!(file, "{}", source)?;
|
||||
file_handles.push(file);
|
||||
}
|
||||
debug_assert!(
|
||||
app_module.1.starts_with("app"),
|
||||
"The final module should be the application module"
|
||||
);
|
||||
|
||||
let result = {
|
||||
let (name, source) = app_module;
|
||||
for (name, source) in interfaces {
|
||||
let mut filename = PathBuf::from(name);
|
||||
filename.set_extension("roc");
|
||||
let file_path = dir.path().join(filename.clone());
|
||||
let mut file = File::create(file_path)?;
|
||||
writeln!(file, "{}", source)?;
|
||||
file_handles.push(file);
|
||||
}
|
||||
|
||||
let filename = PathBuf::from(name);
|
||||
let file_path = dir.path().join(filename);
|
||||
let full_file_path = file_path.clone();
|
||||
let mut file = File::create(file_path)?;
|
||||
writeln!(file, "{}", source)?;
|
||||
file_handles.push(file);
|
||||
let result = {
|
||||
let (name, source) = app_module;
|
||||
|
||||
roc_load::file::load_and_typecheck(
|
||||
arena,
|
||||
full_file_path,
|
||||
&stdlib,
|
||||
dir.path(),
|
||||
exposed_types,
|
||||
8,
|
||||
builtin_defs_map,
|
||||
)
|
||||
};
|
||||
let filename = PathBuf::from(name);
|
||||
let file_path = dir.path().join(filename);
|
||||
let full_file_path = file_path.clone();
|
||||
let mut file = File::create(file_path)?;
|
||||
writeln!(file, "{}", source)?;
|
||||
file_handles.push(file);
|
||||
|
||||
dir.close()?;
|
||||
|
||||
result
|
||||
roc_load::file::load_and_typecheck(
|
||||
arena,
|
||||
full_file_path,
|
||||
arena.alloc(stdlib),
|
||||
dir.path(),
|
||||
exposed_types,
|
||||
8,
|
||||
builtin_defs_map,
|
||||
)
|
||||
};
|
||||
|
||||
let mut loaded_module = loaded.expect("failed to load module");
|
||||
dir.close()?;
|
||||
|
||||
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)
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn load_fixture(
|
||||
@ -134,9 +141,9 @@ mod test_load {
|
||||
);
|
||||
let mut loaded_module = match loaded {
|
||||
Ok(x) => x,
|
||||
Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => {
|
||||
Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
|
||||
println!("{}", report);
|
||||
panic!();
|
||||
panic!("{}", report);
|
||||
}
|
||||
Err(e) => panic!("{:?}", e),
|
||||
};
|
||||
@ -285,7 +292,8 @@ mod test_load {
|
||||
),
|
||||
),
|
||||
];
|
||||
multiple_modules(modules);
|
||||
|
||||
assert!(multiple_modules(modules).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -517,61 +525,69 @@ mod test_load {
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn load_records() {
|
||||
// use roc::types::{ErrorType, Mismatch, Problem, TypeExt};
|
||||
#[test]
|
||||
fn parse_problem() {
|
||||
let modules = vec![(
|
||||
"Main",
|
||||
indoc!(
|
||||
r#"
|
||||
app "test-app" packages { blah: "./blah" } provides [ main ] to blah
|
||||
|
||||
// let subs_by_module = MutMap::default();
|
||||
// let loaded_module =
|
||||
// load_fixture("interface_with_deps", "Records", subs_by_module);
|
||||
main = [
|
||||
"#
|
||||
),
|
||||
)];
|
||||
|
||||
// // NOTE: `a` here is unconstrained, so unifies with <type error>
|
||||
// let expected_types = hashmap! {
|
||||
// "Records.intVal" => "a",
|
||||
// };
|
||||
match multiple_modules(modules) {
|
||||
Err(report) => assert_eq!(
|
||||
report,
|
||||
indoc!(
|
||||
"
|
||||
\u{1b}[36m── UNFINISHED LIST ─────────────────────────────────────────────────────────────\u{1b}[0m
|
||||
|
||||
I cannot find the end of this list:
|
||||
|
||||
// let a = ErrorType::FlexVar("a".into());
|
||||
\u{1b}[36m3\u{1b}[0m\u{1b}[36m│\u{1b}[0m \u{1b}[37mmain = [\u{1b}[0m
|
||||
\u{1b}[31m^\u{1b}[0m
|
||||
|
||||
// let mut record = SendMap::default();
|
||||
// record.insert("x".into(), a);
|
||||
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.
|
||||
Anything where there is an open and a close square bracket, and where
|
||||
the elements of the list are separated by commas.
|
||||
|
||||
// let problem = Problem::Mismatch(
|
||||
// Mismatch::TypeMismatch,
|
||||
// ErrorType::Record(SendMap::default(), TypeExt::Closed),
|
||||
// ErrorType::Record(record, TypeExt::FlexOpen("b".into())),
|
||||
// );
|
||||
\u{1b}[4mNote\u{1b}[0m: I may be confused by indentation"
|
||||
)
|
||||
),
|
||||
Ok(_) => unreachable!("we expect failure here"),
|
||||
}
|
||||
}
|
||||
|
||||
// assert_eq!(loaded_module.problems, vec![problem]);
|
||||
// assert_eq!(expected_types.len(), loaded_module.declarations.len());
|
||||
#[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);
|
||||
|
||||
// let mut subs = loaded_module.solved.into_inner();
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"str" => "Str",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// for decl in loaded_module.declarations {
|
||||
// let def = match decl {
|
||||
// Declare(def) => def,
|
||||
// rec_decl @ DeclareRec(_) => {
|
||||
// panic!(
|
||||
// "Unexpected recursive def in module declarations: {:?}",
|
||||
// rec_decl
|
||||
// );
|
||||
// }
|
||||
// cycle @ InvalidCycle(_, _) => {
|
||||
// panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
|
||||
// }
|
||||
// };
|
||||
#[test]
|
||||
#[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);
|
||||
|
||||
// for (symbol, expr_var) in def.pattern_vars {
|
||||
// let content = subs.get(expr_var).content;
|
||||
|
||||
// name_all_type_vars(expr_var, &mut subs);
|
||||
|
||||
// let actual_str = content_to_string(content, &mut subs);
|
||||
// let expected_type = expected_types.get(symbol.as_str()).unwrap_or_else(|| {
|
||||
// panic!("Defs included an unexpected symbol: {:?}", symbol)
|
||||
// });
|
||||
|
||||
// assert_eq!((&symbol, expected_type), (&symbol, &actual_str.as_str()));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"str" => "Str",
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -117,21 +117,21 @@ impl From<InlinableString> for ModuleName {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<InlinableString> for ModuleName {
|
||||
fn into(self) -> InlinableString {
|
||||
self.0
|
||||
impl From<ModuleName> for InlinableString {
|
||||
fn from(name: ModuleName) -> Self {
|
||||
name.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<&'a InlinableString> for &'a ModuleName {
|
||||
fn into(self) -> &'a InlinableString {
|
||||
&self.0
|
||||
impl<'a> From<&'a ModuleName> for &'a InlinableString {
|
||||
fn from(name: &'a ModuleName) -> Self {
|
||||
&name.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Box<str>> for ModuleName {
|
||||
fn into(self) -> Box<str> {
|
||||
self.0.to_string().into()
|
||||
impl From<ModuleName> for Box<str> {
|
||||
fn from(name: ModuleName) -> Self {
|
||||
name.0.to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,9 +197,9 @@ impl<'a> From<String> for Lowercase {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<InlinableString> for Lowercase {
|
||||
fn into(self) -> InlinableString {
|
||||
self.0
|
||||
impl From<Lowercase> for InlinableString {
|
||||
fn from(lowercase: Lowercase) -> Self {
|
||||
lowercase.0
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,21 +234,21 @@ impl From<InlinableString> for Ident {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<InlinableString> for Ident {
|
||||
fn into(self) -> InlinableString {
|
||||
self.0
|
||||
impl From<Ident> for InlinableString {
|
||||
fn from(ident: Ident) -> Self {
|
||||
ident.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<&'a InlinableString> for &'a Ident {
|
||||
fn into(self) -> &'a InlinableString {
|
||||
&self.0
|
||||
impl<'a> From<&'a Ident> for &'a InlinableString {
|
||||
fn from(ident: &'a Ident) -> Self {
|
||||
&ident.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Box<str>> for Ident {
|
||||
fn into(self) -> Box<str> {
|
||||
self.0.to_string().into()
|
||||
impl From<Ident> for Box<str> {
|
||||
fn from(ident: Ident) -> Self {
|
||||
ident.0.to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![warn(clippy::all, clippy::dbg_macro)]
|
||||
// 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 low_level;
|
||||
|
@ -34,6 +34,7 @@ pub enum LowLevel {
|
||||
ListWalk,
|
||||
ListWalkBackwards,
|
||||
ListSum,
|
||||
ListProduct,
|
||||
ListKeepOks,
|
||||
ListKeepErrs,
|
||||
DictSize,
|
||||
@ -71,6 +72,7 @@ pub enum LowLevel {
|
||||
NumSin,
|
||||
NumCos,
|
||||
NumSqrtUnchecked,
|
||||
NumLogUnchecked,
|
||||
NumRound,
|
||||
NumToFloat,
|
||||
NumPow,
|
||||
|
@ -804,59 +804,60 @@ define_builtins! {
|
||||
43 NUM_MOD_INT: "modInt"
|
||||
44 NUM_MOD_FLOAT: "modFloat"
|
||||
45 NUM_SQRT: "sqrt"
|
||||
46 NUM_ROUND: "round"
|
||||
47 NUM_COMPARE: "compare"
|
||||
48 NUM_POW: "pow"
|
||||
49 NUM_CEILING: "ceiling"
|
||||
50 NUM_POW_INT: "powInt"
|
||||
51 NUM_FLOOR: "floor"
|
||||
52 NUM_ADD_WRAP: "addWrap"
|
||||
53 NUM_ADD_CHECKED: "addChecked"
|
||||
54 NUM_ATAN: "atan"
|
||||
55 NUM_ACOS: "acos"
|
||||
56 NUM_ASIN: "asin"
|
||||
57 NUM_AT_SIGNED128: "@Signed128"
|
||||
58 NUM_SIGNED128: "Signed128" imported
|
||||
59 NUM_AT_SIGNED64: "@Signed64"
|
||||
60 NUM_SIGNED64: "Signed64" imported
|
||||
61 NUM_AT_SIGNED32: "@Signed32"
|
||||
62 NUM_SIGNED32: "Signed32" imported
|
||||
63 NUM_AT_SIGNED16: "@Signed16"
|
||||
64 NUM_SIGNED16: "Signed16" imported
|
||||
65 NUM_AT_SIGNED8: "@Signed8"
|
||||
66 NUM_SIGNED8: "Signed8" imported
|
||||
67 NUM_AT_UNSIGNED128: "@Unsigned128"
|
||||
68 NUM_UNSIGNED128: "Unsigned128" imported
|
||||
69 NUM_AT_UNSIGNED64: "@Unsigned64"
|
||||
70 NUM_UNSIGNED64: "Unsigned64" imported
|
||||
71 NUM_AT_UNSIGNED32: "@Unsigned32"
|
||||
72 NUM_UNSIGNED32: "Unsigned32" imported
|
||||
73 NUM_AT_UNSIGNED16: "@Unsigned16"
|
||||
74 NUM_UNSIGNED16: "Unsigned16" imported
|
||||
75 NUM_AT_UNSIGNED8: "@Unsigned8"
|
||||
76 NUM_UNSIGNED8: "Unsigned8" imported
|
||||
77 NUM_AT_BINARY64: "@Binary64"
|
||||
78 NUM_BINARY64: "Binary64" imported
|
||||
79 NUM_AT_BINARY32: "@Binary32"
|
||||
80 NUM_BINARY32: "Binary32" imported
|
||||
81 NUM_BITWISE_AND: "bitwiseAnd"
|
||||
82 NUM_BITWISE_XOR: "bitwiseXor"
|
||||
83 NUM_BITWISE_OR: "bitwiseOr"
|
||||
84 NUM_SHIFT_LEFT: "shiftLeftBy"
|
||||
85 NUM_SHIFT_RIGHT: "shiftRightBy"
|
||||
86 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
|
||||
87 NUM_SUB_WRAP: "subWrap"
|
||||
88 NUM_SUB_CHECKED: "subChecked"
|
||||
89 NUM_MUL_WRAP: "mulWrap"
|
||||
90 NUM_MUL_CHECKED: "mulChecked"
|
||||
91 NUM_INT: "Int" imported
|
||||
92 NUM_FLOAT: "Float" imported
|
||||
93 NUM_AT_NATURAL: "@Natural"
|
||||
94 NUM_NATURAL: "Natural" imported
|
||||
95 NUM_NAT: "Nat" imported
|
||||
96 NUM_INT_CAST: "intCast"
|
||||
97 NUM_MAX_I128: "maxI128"
|
||||
98 NUM_IS_MULTIPLE_OF: "isMultipleOf"
|
||||
46 NUM_LOG: "log"
|
||||
47 NUM_ROUND: "round"
|
||||
48 NUM_COMPARE: "compare"
|
||||
49 NUM_POW: "pow"
|
||||
50 NUM_CEILING: "ceiling"
|
||||
51 NUM_POW_INT: "powInt"
|
||||
52 NUM_FLOOR: "floor"
|
||||
53 NUM_ADD_WRAP: "addWrap"
|
||||
54 NUM_ADD_CHECKED: "addChecked"
|
||||
55 NUM_ATAN: "atan"
|
||||
56 NUM_ACOS: "acos"
|
||||
57 NUM_ASIN: "asin"
|
||||
58 NUM_AT_SIGNED128: "@Signed128"
|
||||
59 NUM_SIGNED128: "Signed128" imported
|
||||
60 NUM_AT_SIGNED64: "@Signed64"
|
||||
61 NUM_SIGNED64: "Signed64" imported
|
||||
62 NUM_AT_SIGNED32: "@Signed32"
|
||||
63 NUM_SIGNED32: "Signed32" imported
|
||||
64 NUM_AT_SIGNED16: "@Signed16"
|
||||
65 NUM_SIGNED16: "Signed16" imported
|
||||
66 NUM_AT_SIGNED8: "@Signed8"
|
||||
67 NUM_SIGNED8: "Signed8" imported
|
||||
68 NUM_AT_UNSIGNED128: "@Unsigned128"
|
||||
69 NUM_UNSIGNED128: "Unsigned128" imported
|
||||
70 NUM_AT_UNSIGNED64: "@Unsigned64"
|
||||
71 NUM_UNSIGNED64: "Unsigned64" imported
|
||||
72 NUM_AT_UNSIGNED32: "@Unsigned32"
|
||||
73 NUM_UNSIGNED32: "Unsigned32" imported
|
||||
74 NUM_AT_UNSIGNED16: "@Unsigned16"
|
||||
75 NUM_UNSIGNED16: "Unsigned16" imported
|
||||
76 NUM_AT_UNSIGNED8: "@Unsigned8"
|
||||
77 NUM_UNSIGNED8: "Unsigned8" imported
|
||||
78 NUM_AT_BINARY64: "@Binary64"
|
||||
79 NUM_BINARY64: "Binary64" imported
|
||||
80 NUM_AT_BINARY32: "@Binary32"
|
||||
81 NUM_BINARY32: "Binary32" imported
|
||||
82 NUM_BITWISE_AND: "bitwiseAnd"
|
||||
83 NUM_BITWISE_XOR: "bitwiseXor"
|
||||
84 NUM_BITWISE_OR: "bitwiseOr"
|
||||
85 NUM_SHIFT_LEFT: "shiftLeftBy"
|
||||
86 NUM_SHIFT_RIGHT: "shiftRightBy"
|
||||
87 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
|
||||
88 NUM_SUB_WRAP: "subWrap"
|
||||
89 NUM_SUB_CHECKED: "subChecked"
|
||||
90 NUM_MUL_WRAP: "mulWrap"
|
||||
91 NUM_MUL_CHECKED: "mulChecked"
|
||||
92 NUM_INT: "Int" imported
|
||||
93 NUM_FLOAT: "Float" imported
|
||||
94 NUM_AT_NATURAL: "@Natural"
|
||||
95 NUM_NATURAL: "Natural" imported
|
||||
96 NUM_NAT: "Nat" imported
|
||||
97 NUM_INT_CAST: "intCast"
|
||||
98 NUM_MAX_I128: "maxI128"
|
||||
99 NUM_IS_MULTIPLE_OF: "isMultipleOf"
|
||||
|
||||
}
|
||||
2 BOOL: "Bool" => {
|
||||
@ -913,6 +914,7 @@ define_builtins! {
|
||||
23 LIST_MAP_WITH_INDEX: "mapWithIndex"
|
||||
24 LIST_MAP2: "map2"
|
||||
25 LIST_MAP3: "map3"
|
||||
26 LIST_PRODUCT: "product"
|
||||
}
|
||||
5 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||
|
@ -22,7 +22,7 @@ pub fn infer_borrow<'a>(
|
||||
};
|
||||
|
||||
for (key, proc) in procs {
|
||||
param_map.visit_proc(arena, proc, key.clone());
|
||||
param_map.visit_proc(arena, proc, *key);
|
||||
}
|
||||
|
||||
let mut env = BorrowInfState {
|
||||
@ -47,7 +47,7 @@ pub fn infer_borrow<'a>(
|
||||
// mutually recursive functions (or just make all their arguments owned)
|
||||
|
||||
for (key, proc) in procs {
|
||||
env.collect_proc(proc, key.1.clone());
|
||||
env.collect_proc(proc, key.1);
|
||||
}
|
||||
|
||||
if !env.modified {
|
||||
@ -113,7 +113,7 @@ impl<'a> ParamMap<'a> {
|
||||
Vec::from_iter_in(
|
||||
ps.iter().map(|p| Param {
|
||||
borrow: p.layout.is_refcounted(),
|
||||
layout: p.layout.clone(),
|
||||
layout: p.layout,
|
||||
symbol: p.symbol,
|
||||
}),
|
||||
arena,
|
||||
@ -125,7 +125,7 @@ impl<'a> ParamMap<'a> {
|
||||
Vec::from_iter_in(
|
||||
ps.iter().map(|(layout, symbol)| Param {
|
||||
borrow: should_borrow_layout(layout),
|
||||
layout: layout.clone(),
|
||||
layout: *layout,
|
||||
symbol: *symbol,
|
||||
}),
|
||||
arena,
|
||||
@ -140,7 +140,7 @@ impl<'a> ParamMap<'a> {
|
||||
Vec::from_iter_in(
|
||||
ps.iter().map(|(layout, symbol)| Param {
|
||||
borrow: false,
|
||||
layout: layout.clone(),
|
||||
layout: *layout,
|
||||
symbol: *symbol,
|
||||
}),
|
||||
arena,
|
||||
@ -367,7 +367,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
name, full_layout, ..
|
||||
} => {
|
||||
// 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) => {
|
||||
// the return value will be owned
|
||||
self.own_var(z);
|
||||
@ -499,7 +499,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
if self.current_proc == *g && x == *z {
|
||||
// anonymous functions (for which the ps may not be known)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@ -541,14 +541,14 @@ impl<'a> BorrowInfState<'a> {
|
||||
|
||||
Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => {
|
||||
// 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
|
||||
.items
|
||||
.insert(Key::Declaration(*x, layout.clone()), params);
|
||||
.insert(Key::Declaration(*x, *layout), params);
|
||||
}
|
||||
|
||||
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) => {
|
||||
@ -657,7 +657,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
ListWalk => 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)
|
||||
// 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
|
||||
| NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
||||
|
||||
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
|
||||
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumIntCast => {
|
||||
arena.alloc_slice_copy(&[irrelevant])
|
||||
}
|
||||
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
|
||||
| NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin
|
||||
| NumIntCast => arena.alloc_slice_copy(&[irrelevant]),
|
||||
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||
StrFromUtf8 => arena.alloc_slice_copy(&[owned]),
|
||||
StrToBytes => arena.alloc_slice_copy(&[owned]),
|
||||
|
@ -22,7 +22,7 @@ pub fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> Decision
|
||||
.into_iter()
|
||||
.map(|(guard, pattern, index)| Branch {
|
||||
goal: index,
|
||||
patterns: vec![(Path::Empty, guard, pattern)],
|
||||
patterns: vec![(Vec::new(), guard, pattern)],
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -52,7 +52,7 @@ impl<'a> Guard<'a> {
|
||||
pub enum DecisionTree<'a> {
|
||||
Match(Label),
|
||||
Decision {
|
||||
path: Path,
|
||||
path: Vec<PathInstruction>,
|
||||
edges: Vec<(Test<'a>, 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
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Branch<'a> {
|
||||
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 {
|
||||
@ -172,13 +161,13 @@ fn to_decision_tree(raw_branches: Vec<Branch>) -> DecisionTree {
|
||||
decision_tree.clone()
|
||||
}
|
||||
(_, None) => DecisionTree::Decision {
|
||||
path,
|
||||
path: path.clone(),
|
||||
edges: decision_edges,
|
||||
default: None,
|
||||
},
|
||||
(None, Some(_)) => to_decision_tree(fallback),
|
||||
_ => DecisionTree::Decision {
|
||||
path,
|
||||
path: path.clone(),
|
||||
edges: decision_edges,
|
||||
default: Some(Box::new(to_decision_tree(fallback))),
|
||||
},
|
||||
@ -218,8 +207,8 @@ fn flatten_patterns(branch: Branch) -> Branch {
|
||||
}
|
||||
|
||||
fn flatten<'a>(
|
||||
path_pattern: (Path, Guard<'a>, Pattern<'a>),
|
||||
path_patterns: &mut Vec<(Path, Guard<'a>, Pattern<'a>)>,
|
||||
path_pattern: (Vec<PathInstruction>, Guard<'a>, Pattern<'a>),
|
||||
path_patterns: &mut Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
|
||||
) {
|
||||
match path_pattern.2 {
|
||||
Pattern::AppliedTag {
|
||||
@ -241,8 +230,9 @@ fn flatten<'a>(
|
||||
// Theory: unbox doesn't have any value for us, because one-element tag unions
|
||||
// don't store the tag anyway.
|
||||
if arguments.len() == 1 {
|
||||
// NOTE here elm will unbox, but we don't use that
|
||||
path_patterns.push((
|
||||
Path::Unbox(Box::new(path)),
|
||||
path,
|
||||
path_pattern.1.clone(),
|
||||
Pattern::AppliedTag {
|
||||
union,
|
||||
@ -254,13 +244,15 @@ fn flatten<'a>(
|
||||
));
|
||||
} else {
|
||||
for (index, (arg_pattern, _)) in arguments.iter().enumerate() {
|
||||
let mut new_path = path.clone();
|
||||
new_path.push(PathInstruction {
|
||||
index: index as u64,
|
||||
tag_id,
|
||||
});
|
||||
|
||||
flatten(
|
||||
(
|
||||
Path::Index {
|
||||
index: index as u64,
|
||||
tag_id,
|
||||
path: Box::new(path.clone()),
|
||||
},
|
||||
new_path,
|
||||
// same guard here?
|
||||
path_pattern.1.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
|
||||
/// variables to "how to get their value". So a pattern like (Just (x,_)) will give
|
||||
/// 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) {
|
||||
Some(Branch { goal, patterns })
|
||||
if patterns
|
||||
@ -300,7 +292,7 @@ fn check_for_match(branches: &Vec<Branch>) -> Option<Label> {
|
||||
|
||||
fn gather_edges<'a>(
|
||||
branches: Vec<Branch<'a>>,
|
||||
path: &Path,
|
||||
path: &[PathInstruction],
|
||||
) -> (Vec<(Test<'a>, Vec<Branch<'a>>)>, Vec<Branch<'a>>) {
|
||||
let relevant_tests = tests_at_path(path, &branches);
|
||||
|
||||
@ -326,7 +318,7 @@ fn gather_edges<'a>(
|
||||
|
||||
/// 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!
|
||||
|
||||
let mut all_tests = Vec::new();
|
||||
@ -360,7 +352,11 @@ fn tests_at_path<'a>(selected_path: &Path, branches: &[Branch<'a>]) -> Vec<Test<
|
||||
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 Test::*;
|
||||
|
||||
@ -413,10 +409,10 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
|
||||
for destruct in destructs {
|
||||
match &destruct.typ {
|
||||
DestructType::Guard(guard) => {
|
||||
arguments.push((guard.clone(), destruct.layout.clone()));
|
||||
arguments.push((guard.clone(), destruct.layout));
|
||||
}
|
||||
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
|
||||
|
||||
fn edges_for<'a>(
|
||||
path: &Path,
|
||||
path: &[PathInstruction],
|
||||
branches: Vec<Branch<'a>>,
|
||||
test: Test<'a>,
|
||||
) -> (Test<'a>, Vec<Branch<'a>>) {
|
||||
@ -484,7 +480,7 @@ fn edges_for<'a>(
|
||||
|
||||
fn to_relevant_branch<'a>(
|
||||
test: &Test<'a>,
|
||||
path: &Path,
|
||||
path: &[PathInstruction],
|
||||
branch: &Branch<'a>,
|
||||
new_branches: &mut Vec<Branch<'a>>,
|
||||
) {
|
||||
@ -524,9 +520,9 @@ fn to_relevant_branch<'a>(
|
||||
|
||||
fn to_relevant_branch_help<'a>(
|
||||
test: &Test<'a>,
|
||||
path: &Path,
|
||||
mut start: Vec<(Path, Guard<'a>, Pattern<'a>)>,
|
||||
end: Vec<(Path, Guard<'a>, Pattern<'a>)>,
|
||||
path: &[PathInstruction],
|
||||
mut start: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
|
||||
end: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
|
||||
branch: &Branch<'a>,
|
||||
guard: Guard<'a>,
|
||||
pattern: Pattern<'a>,
|
||||
@ -550,15 +546,13 @@ fn to_relevant_branch_help<'a>(
|
||||
DestructType::Required(_) => Pattern::Underscore,
|
||||
};
|
||||
|
||||
(
|
||||
Path::Index {
|
||||
index: index as u64,
|
||||
tag_id: *tag_id,
|
||||
path: Box::new(path.clone()),
|
||||
},
|
||||
Guard::NoGuard,
|
||||
pattern,
|
||||
)
|
||||
let mut new_path = path.to_vec();
|
||||
new_path.push(PathInstruction {
|
||||
index: index as u64,
|
||||
tag_id: *tag_id,
|
||||
});
|
||||
|
||||
(new_path, Guard::NoGuard, pattern)
|
||||
});
|
||||
start.extend(sub_positions);
|
||||
start.extend(end);
|
||||
@ -597,26 +591,21 @@ fn to_relevant_branch_help<'a>(
|
||||
debug_assert_eq!(arguments.len(), 1);
|
||||
let arg = arguments[0].clone();
|
||||
{
|
||||
start.push((
|
||||
Path::Unbox(Box::new(path.clone())),
|
||||
guard,
|
||||
arg.0,
|
||||
));
|
||||
// NOTE here elm unboxes, but we ignore that
|
||||
// Path::Unbox(Box::new(path.clone()))
|
||||
start.push((path.to_vec(), guard, arg.0));
|
||||
start.extend(end);
|
||||
}
|
||||
}
|
||||
Wrapped::RecordOrSingleTagUnion => {
|
||||
let sub_positions = arguments.into_iter().enumerate().map(
|
||||
|(index, (pattern, _))| {
|
||||
(
|
||||
Path::Index {
|
||||
index: index as u64,
|
||||
tag_id,
|
||||
path: Box::new(path.clone()),
|
||||
},
|
||||
Guard::NoGuard,
|
||||
pattern,
|
||||
)
|
||||
let mut new_path = path.to_vec();
|
||||
new_path.push(PathInstruction {
|
||||
index: index as u64,
|
||||
tag_id,
|
||||
});
|
||||
(new_path, Guard::NoGuard, pattern)
|
||||
},
|
||||
);
|
||||
start.extend(sub_positions);
|
||||
@ -625,15 +614,12 @@ fn to_relevant_branch_help<'a>(
|
||||
Wrapped::MultiTagUnion => {
|
||||
let sub_positions = arguments.into_iter().enumerate().map(
|
||||
|(index, (pattern, _))| {
|
||||
(
|
||||
Path::Index {
|
||||
index: 1 + index as u64,
|
||||
tag_id,
|
||||
path: Box::new(path.clone()),
|
||||
},
|
||||
Guard::NoGuard,
|
||||
pattern,
|
||||
)
|
||||
let mut new_path = path.to_vec();
|
||||
new_path.push(PathInstruction {
|
||||
index: 1 + index as u64,
|
||||
tag_id,
|
||||
});
|
||||
(new_path, Guard::NoGuard, pattern)
|
||||
},
|
||||
);
|
||||
start.extend(sub_positions);
|
||||
@ -715,22 +701,22 @@ fn to_relevant_branch_help<'a>(
|
||||
enum Extract<'a> {
|
||||
NotFound,
|
||||
Found {
|
||||
start: Vec<(Path, Guard<'a>, Pattern<'a>)>,
|
||||
start: Vec<(Vec<PathInstruction>, 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>(
|
||||
selected_path: &Path,
|
||||
path_patterns: Vec<(Path, Guard<'a>, Pattern<'a>)>,
|
||||
selected_path: &[PathInstruction],
|
||||
path_patterns: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
|
||||
) -> Extract<'a> {
|
||||
let mut start = Vec::new();
|
||||
|
||||
// TODO potential ordering problem
|
||||
let mut it = path_patterns.into_iter();
|
||||
while let Some(current) = it.next() {
|
||||
if ¤t.0 == selected_path {
|
||||
if current.0 == selected_path {
|
||||
return Extract::Found {
|
||||
start,
|
||||
found_pattern: (current.1, current.2),
|
||||
@ -746,7 +732,7 @@ fn extract<'a>(
|
||||
|
||||
/// 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
|
||||
.patterns
|
||||
.iter()
|
||||
@ -775,7 +761,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
|
||||
|
||||
/// 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());
|
||||
|
||||
// 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
|
||||
I: Iterator<Item = &'a Path>,
|
||||
I: Iterator<Item = &'a Vec<PathInstruction>>,
|
||||
{
|
||||
match all_paths.next() {
|
||||
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
|
||||
I: Iterator<Item = &'a Path>,
|
||||
I: Iterator<Item = &'a Vec<PathInstruction>>,
|
||||
{
|
||||
match all_paths.next() {
|
||||
None => panic!("Cannot choose the best of zero paths. This should never happen."),
|
||||
@ -870,7 +862,7 @@ where
|
||||
|
||||
/// PATH PICKING HEURISTICS
|
||||
|
||||
fn small_defaults(branches: &[Branch], path: &Path) -> usize {
|
||||
fn small_defaults(branches: &[Branch], path: &[PathInstruction]) -> usize {
|
||||
branches
|
||||
.iter()
|
||||
.filter(|b| is_irrelevant_to(path, b))
|
||||
@ -878,8 +870,7 @@ fn small_defaults(branches: &[Branch], path: &Path) -> usize {
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn small_branching_factor(branches: &[Branch], path: &Path) -> usize {
|
||||
// TODO remove clone
|
||||
fn small_branching_factor(branches: &[Branch], path: &[PathInstruction]) -> usize {
|
||||
let (edges, fallback) = gather_edges(branches.to_vec(), path);
|
||||
|
||||
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> {
|
||||
Leaf(T),
|
||||
Chain {
|
||||
test_chain: Vec<(Path, Test<'a>)>,
|
||||
test_chain: Vec<(Vec<PathInstruction>, Test<'a>)>,
|
||||
success: Box<Decider<'a, T>>,
|
||||
failure: Box<Decider<'a, T>>,
|
||||
},
|
||||
FanOut {
|
||||
path: Path,
|
||||
path: Vec<PathInstruction>,
|
||||
tests: Vec<(Test<'a>, Decider<'a, T>)>,
|
||||
fallback: Box<Decider<'a, T>>,
|
||||
},
|
||||
@ -973,49 +964,22 @@ pub fn optimize_when<'a>(
|
||||
stmt
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PathInstruction {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PathInstruction {
|
||||
index: u64,
|
||||
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>(
|
||||
env: &mut Env<'a, '_>,
|
||||
mut symbol: Symbol,
|
||||
path: &Path,
|
||||
path: &[PathInstruction],
|
||||
mut layout: Layout<'a>,
|
||||
) -> (Symbol, StoresVec<'a>, Layout<'a>) {
|
||||
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();
|
||||
|
||||
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!(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_eq!(field_layouts.len(), 1);
|
||||
|
||||
let inner_layout = field_layouts[*index as usize].clone();
|
||||
let inner_expr = Expr::AccessAtIndex {
|
||||
index: *index,
|
||||
field_layouts: env.arena.alloc(field_layouts),
|
||||
@ -1042,7 +1005,8 @@ fn path_to_expr_help<'a>(
|
||||
};
|
||||
|
||||
symbol = env.unique_symbol();
|
||||
stores.push((symbol, inner_layout.clone(), inner_expr));
|
||||
let inner_layout = layout;
|
||||
stores.push((symbol, inner_layout, inner_expr));
|
||||
|
||||
break;
|
||||
}
|
||||
@ -1096,7 +1060,7 @@ fn path_to_expr_help<'a>(
|
||||
}
|
||||
|
||||
Layout::Struct(layouts) => layouts,
|
||||
other => env.arena.alloc([other.clone()]),
|
||||
other => env.arena.alloc([*other]),
|
||||
};
|
||||
|
||||
debug_assert!(
|
||||
@ -1109,8 +1073,8 @@ fn path_to_expr_help<'a>(
|
||||
);
|
||||
|
||||
let inner_layout = match &field_layouts[index as usize] {
|
||||
Layout::RecursivePointer => layout.clone(),
|
||||
other => other.clone(),
|
||||
Layout::RecursivePointer => layout,
|
||||
other => *other,
|
||||
};
|
||||
|
||||
let inner_expr = Expr::AccessAtIndex {
|
||||
@ -1121,7 +1085,7 @@ fn path_to_expr_help<'a>(
|
||||
};
|
||||
|
||||
symbol = env.unique_symbol();
|
||||
stores.push((symbol, inner_layout.clone(), inner_expr));
|
||||
stores.push((symbol, inner_layout, inner_expr));
|
||||
|
||||
layout = inner_layout;
|
||||
}
|
||||
@ -1135,11 +1099,11 @@ fn test_to_equality<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
cond_symbol: Symbol,
|
||||
cond_layout: &Layout<'a>,
|
||||
path: &Path,
|
||||
path: &[PathInstruction],
|
||||
test: Test<'a>,
|
||||
) -> (StoresVec<'a>, Symbol, Symbol, Layout<'a>) {
|
||||
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 {
|
||||
Test::IsCtor {
|
||||
@ -1273,7 +1237,7 @@ fn stores_and_condition<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
cond_symbol: Symbol,
|
||||
cond_layout: &Layout<'a>,
|
||||
test_chain: Vec<(Path, Test<'a>)>,
|
||||
test_chain: Vec<(Vec<PathInstruction>, Test<'a>)>,
|
||||
) -> (Tests<'a>, Option<(Symbol, JoinPointId, Stmt<'a>)>) {
|
||||
let mut tests = Vec::with_capacity(test_chain.len());
|
||||
|
||||
@ -1399,12 +1363,12 @@ fn compile_test_help<'a>(
|
||||
} => {
|
||||
let pass_info = BranchInfo::Constructor {
|
||||
scrutinee,
|
||||
layout: layout.clone(),
|
||||
layout,
|
||||
tag_id: pass,
|
||||
};
|
||||
let fail_info = BranchInfo::Constructor {
|
||||
scrutinee,
|
||||
layout: layout.clone(),
|
||||
layout,
|
||||
tag_id: fail,
|
||||
};
|
||||
|
||||
@ -1418,7 +1382,7 @@ fn compile_test_help<'a>(
|
||||
} => {
|
||||
let pass_info = BranchInfo::Constructor {
|
||||
scrutinee,
|
||||
layout: layout.clone(),
|
||||
layout,
|
||||
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!
|
||||
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() {
|
||||
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
|
||||
}
|
||||
@ -1501,22 +1465,22 @@ impl<'a> ConstructorKnown<'a> {
|
||||
fn from_test_chain(
|
||||
cond_symbol: Symbol,
|
||||
cond_layout: &Layout<'a>,
|
||||
test_chain: &[(Path, Test)],
|
||||
test_chain: &[(Vec<PathInstruction>, Test)],
|
||||
) -> Self {
|
||||
match test_chain {
|
||||
[(path, test)] => match (path, test) {
|
||||
(Path::Empty, Test::IsCtor { tag_id, union, .. }) => {
|
||||
[(path, test)] => match test {
|
||||
Test::IsCtor { tag_id, union, .. } if path.is_empty() => {
|
||||
if union.alternatives.len() == 2 {
|
||||
// excluded middle: we also know the tag_id in the fail branch
|
||||
ConstructorKnown::Both {
|
||||
layout: cond_layout.clone(),
|
||||
layout: *cond_layout,
|
||||
scrutinee: cond_symbol,
|
||||
pass: *tag_id,
|
||||
fail: (*tag_id == 0) as u8,
|
||||
}
|
||||
} else {
|
||||
ConstructorKnown::OnlyPass {
|
||||
layout: cond_layout.clone(),
|
||||
layout: *cond_layout,
|
||||
scrutinee: cond_symbol,
|
||||
tag_id: *tag_id,
|
||||
}
|
||||
@ -1541,7 +1505,7 @@ fn decide_to_branching<'a>(
|
||||
cond_layout: Layout<'a>,
|
||||
ret_layout: Layout<'a>,
|
||||
decider: Decider<'a, Choice<'a>>,
|
||||
jumps: &Vec<(u64, JoinPointId, Stmt<'a>)>,
|
||||
jumps: &[(u64, JoinPointId, Stmt<'a>)],
|
||||
) -> Stmt<'a> {
|
||||
use Choice::*;
|
||||
use Decider::*;
|
||||
@ -1569,8 +1533,8 @@ fn decide_to_branching<'a>(
|
||||
procs,
|
||||
layout_cache,
|
||||
cond_symbol,
|
||||
cond_layout.clone(),
|
||||
ret_layout.clone(),
|
||||
cond_layout,
|
||||
ret_layout,
|
||||
*success,
|
||||
jumps,
|
||||
);
|
||||
@ -1580,8 +1544,8 @@ fn decide_to_branching<'a>(
|
||||
procs,
|
||||
layout_cache,
|
||||
cond_symbol,
|
||||
cond_layout.clone(),
|
||||
ret_layout.clone(),
|
||||
cond_layout,
|
||||
ret_layout,
|
||||
*failure,
|
||||
jumps,
|
||||
);
|
||||
@ -1608,7 +1572,7 @@ fn decide_to_branching<'a>(
|
||||
compile_test_help(
|
||||
env,
|
||||
chain_branch_info,
|
||||
ret_layout.clone(),
|
||||
ret_layout,
|
||||
new_stores,
|
||||
lhs,
|
||||
rhs,
|
||||
@ -1643,15 +1607,15 @@ fn decide_to_branching<'a>(
|
||||
// switch on the tag discriminant (currently an i64 value)
|
||||
// NOTE the tag discriminant is not actually loaded, `cond` can point to a tag
|
||||
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(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
cond_symbol,
|
||||
cond_layout.clone(),
|
||||
ret_layout.clone(),
|
||||
cond_layout,
|
||||
ret_layout,
|
||||
*fallback,
|
||||
jumps,
|
||||
);
|
||||
@ -1667,8 +1631,8 @@ fn decide_to_branching<'a>(
|
||||
procs,
|
||||
layout_cache,
|
||||
cond_symbol,
|
||||
cond_layout.clone(),
|
||||
ret_layout.clone(),
|
||||
cond_layout,
|
||||
ret_layout,
|
||||
decider,
|
||||
jumps,
|
||||
);
|
||||
@ -1689,7 +1653,7 @@ fn decide_to_branching<'a>(
|
||||
|
||||
BranchInfo::Constructor {
|
||||
scrutinee: inner_cond_symbol,
|
||||
layout: inner_cond_layout.clone(),
|
||||
layout: inner_cond_layout,
|
||||
tag_id,
|
||||
}
|
||||
} else {
|
||||
@ -1704,7 +1668,7 @@ fn decide_to_branching<'a>(
|
||||
let default_branch_info = if tag_id_sum > 0 && union_size > 0 {
|
||||
BranchInfo::Constructor {
|
||||
scrutinee: inner_cond_symbol,
|
||||
layout: inner_cond_layout.clone(),
|
||||
layout: inner_cond_layout,
|
||||
tag_id: tag_id_sum as u8,
|
||||
}
|
||||
} else {
|
||||
@ -1847,7 +1811,7 @@ fn tree_to_decider(tree: DecisionTree) -> Decider<u64> {
|
||||
}
|
||||
|
||||
fn to_chain<'a>(
|
||||
path: Path,
|
||||
path: Vec<PathInstruction>,
|
||||
test: Test<'a>,
|
||||
success_tree: DecisionTree<'a>,
|
||||
failure_tree: DecisionTree<'a>,
|
||||
|
@ -141,7 +141,7 @@ impl<'a, 'i> Env<'a, 'i> {
|
||||
tag_id,
|
||||
} => {
|
||||
self.constructor_map.insert(*scrutinee, *tag_id as u64);
|
||||
self.layout_map.insert(*scrutinee, layout.clone());
|
||||
self.layout_map.insert(*scrutinee, *layout);
|
||||
}
|
||||
BranchInfo::None => (),
|
||||
}
|
||||
@ -167,7 +167,7 @@ impl<'a, 'i> Env<'a, 'i> {
|
||||
}
|
||||
Closure(arguments, closure_layout, 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.layout_map.insert(symbol, Layout::Struct(fields));
|
||||
}
|
||||
@ -247,7 +247,7 @@ fn layout_for_constructor<'a>(
|
||||
}
|
||||
Closure(arguments, closure_layout, 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)
|
||||
}
|
||||
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 layout = if let Layout::RecursivePointer = field_layout {
|
||||
full_layout.clone()
|
||||
*full_layout
|
||||
} else {
|
||||
field_layout.clone()
|
||||
*field_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) => {
|
||||
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);
|
||||
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 = {
|
||||
match stmt {
|
||||
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 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(_)) {
|
||||
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;
|
||||
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);
|
||||
|
||||
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 {
|
||||
cond_symbol: *cond_symbol,
|
||||
cond_layout: cond_layout.clone(),
|
||||
ret_layout: ret_layout.clone(),
|
||||
cond_layout: *cond_layout,
|
||||
ret_layout: *ret_layout,
|
||||
branches: new_branches.into_bump_slice(),
|
||||
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 {
|
||||
symbol: *symbol,
|
||||
call: call.clone(),
|
||||
layout: layout.clone(),
|
||||
layout: *layout,
|
||||
pass,
|
||||
fail,
|
||||
};
|
||||
|
@ -470,7 +470,7 @@ impl<'a> Context<'a> {
|
||||
name, full_layout, ..
|
||||
} => {
|
||||
// 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) => {
|
||||
let v = Expr::Call(crate::ir::Call {
|
||||
call_type,
|
||||
@ -695,7 +695,7 @@ impl<'a> Context<'a> {
|
||||
let pair = ctx.visit_variable_declaration(
|
||||
*symbol,
|
||||
(*expr).clone(),
|
||||
(*layout).clone(),
|
||||
*layout,
|
||||
b,
|
||||
&b_live_vars,
|
||||
);
|
||||
@ -712,13 +712,7 @@ impl<'a> Context<'a> {
|
||||
Let(symbol, expr, layout, cont) => {
|
||||
let ctx = self.update_var_info(*symbol, layout, expr);
|
||||
let (b, b_live_vars) = ctx.visit_stmt(cont);
|
||||
ctx.visit_variable_declaration(
|
||||
*symbol,
|
||||
expr.clone(),
|
||||
layout.clone(),
|
||||
b,
|
||||
&b_live_vars,
|
||||
)
|
||||
ctx.visit_variable_declaration(*symbol, expr.clone(), *layout, b, &b_live_vars)
|
||||
}
|
||||
|
||||
Invoke {
|
||||
@ -759,7 +753,7 @@ impl<'a> Context<'a> {
|
||||
call: call.clone(),
|
||||
pass,
|
||||
fail,
|
||||
layout: layout.clone(),
|
||||
layout: *layout,
|
||||
};
|
||||
|
||||
let cont = self.arena.alloc(invoke);
|
||||
@ -783,7 +777,7 @@ impl<'a> Context<'a> {
|
||||
name, full_layout, ..
|
||||
} => {
|
||||
// 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(
|
||||
call.arguments,
|
||||
ps,
|
||||
@ -908,8 +902,8 @@ impl<'a> Context<'a> {
|
||||
cond_symbol: *cond_symbol,
|
||||
branches,
|
||||
default_branch,
|
||||
cond_layout: cond_layout.clone(),
|
||||
ret_layout: ret_layout.clone(),
|
||||
cond_layout: *cond_layout,
|
||||
ret_layout: *ret_layout,
|
||||
});
|
||||
|
||||
(switch, case_live_vars)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,7 @@ pub enum LayoutProblem {
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
Builtin(Builtin<'a>),
|
||||
/// 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>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum UnionLayout<'a> {
|
||||
/// A non-recursive tag union
|
||||
/// 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> {
|
||||
/// the layout that this specific closure captures
|
||||
/// uses a Vec instead of a MutMap because it's Hash
|
||||
@ -258,7 +258,7 @@ impl<'a> ClosureLayout<'a> {
|
||||
// define the function pointer
|
||||
let function_ptr_layout = {
|
||||
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)
|
||||
};
|
||||
|
||||
@ -278,8 +278,8 @@ impl<'a> ClosureLayout<'a> {
|
||||
pub fn as_named_layout(&self, symbol: Symbol) -> Layout<'a> {
|
||||
let layouts = if self.captured.is_empty() {
|
||||
match self.layout {
|
||||
Layout::Struct(fields) if fields.len() == 1 => fields[0].clone(),
|
||||
other => other.clone(),
|
||||
Layout::Struct(fields) if fields.len() == 1 => fields[0],
|
||||
other => *other,
|
||||
}
|
||||
} else if let Some((_, tag_args)) = self
|
||||
.captured
|
||||
@ -287,7 +287,7 @@ impl<'a> ClosureLayout<'a> {
|
||||
.find(|(tn, _)| *tn == TagName::Closure(symbol))
|
||||
{
|
||||
if tag_args.len() == 1 {
|
||||
tag_args[0].clone()
|
||||
tag_args[0]
|
||||
} else {
|
||||
Layout::Struct(tag_args)
|
||||
}
|
||||
@ -303,13 +303,13 @@ impl<'a> ClosureLayout<'a> {
|
||||
|
||||
pub fn as_block_of_memory_layout(&self) -> Layout<'a> {
|
||||
match self.layout {
|
||||
Layout::Struct(fields) if fields.len() == 1 => fields[0].clone(),
|
||||
other => other.clone(),
|
||||
Layout::Struct(fields) if fields.len() == 1 => fields[0],
|
||||
other => *other,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn internal_layout(&self) -> Layout<'a> {
|
||||
self.layout.clone()
|
||||
*self.layout
|
||||
}
|
||||
|
||||
pub fn build_closure_data(
|
||||
@ -382,7 +382,7 @@ pub enum MemoryMode {
|
||||
Refcounted,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Builtin<'a> {
|
||||
Int128,
|
||||
Int64,
|
||||
@ -825,7 +825,7 @@ impl<'a> LayoutCache<'a> {
|
||||
// of a problem
|
||||
if false {
|
||||
let cached_layout = match &result {
|
||||
Ok(layout) => Cached(layout.clone()),
|
||||
Ok(layout) => Cached(*layout),
|
||||
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;
|
||||
// store the ID we're going to return and increment 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;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![warn(clippy::all, clippy::dbg_macro)]
|
||||
// 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 expand_rc;
|
||||
@ -13,7 +13,6 @@ pub mod tail_recursion;
|
||||
// For now, following this warning's advice will lead to nasty type inference errors.
|
||||
//#[allow(clippy::ptr_arg)]
|
||||
//pub mod decision_tree;
|
||||
#[allow(clippy::ptr_arg)]
|
||||
pub mod decision_tree;
|
||||
#[allow(clippy::ptr_arg)]
|
||||
pub mod exhaustive;
|
||||
|
@ -42,7 +42,7 @@ pub fn make_tail_recursive<'a>(
|
||||
let params = Vec::from_iter_in(
|
||||
args.iter().map(|(layout, symbol)| Param {
|
||||
symbol: *symbol,
|
||||
layout: layout.clone(),
|
||||
layout: *layout,
|
||||
borrow: true,
|
||||
}),
|
||||
arena,
|
||||
@ -117,7 +117,7 @@ fn insert_jumps<'a>(
|
||||
if opt_cont.is_some() {
|
||||
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 {
|
||||
None
|
||||
}
|
||||
@ -140,7 +140,7 @@ fn insert_jumps<'a>(
|
||||
let stmt = Invoke {
|
||||
symbol: *symbol,
|
||||
call: call.clone(),
|
||||
layout: layout.clone(),
|
||||
layout: *layout,
|
||||
pass,
|
||||
fail,
|
||||
};
|
||||
@ -222,10 +222,10 @@ fn insert_jumps<'a>(
|
||||
|
||||
Some(arena.alloc(Switch {
|
||||
cond_symbol: *cond_symbol,
|
||||
cond_layout: cond_layout.clone(),
|
||||
cond_layout: *cond_layout,
|
||||
default_branch,
|
||||
branches,
|
||||
ret_layout: ret_layout.clone(),
|
||||
ret_layout: *ret_layout,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
|
@ -69,7 +69,7 @@ mod test_mono {
|
||||
|
||||
let mut loaded = match loaded {
|
||||
Ok(x) => x,
|
||||
Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => {
|
||||
Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
|
||||
println!("{}", report);
|
||||
panic!();
|
||||
}
|
||||
@ -341,13 +341,13 @@ mod test_mono {
|
||||
"#,
|
||||
indoc!(
|
||||
r#"
|
||||
procedure Num.46 (#Attr.2):
|
||||
procedure Num.47 (#Attr.2):
|
||||
let Test.3 = lowlevel NumRound #Attr.2;
|
||||
ret Test.3;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.2 = 3.6f64;
|
||||
let Test.1 = CallByName Num.46 Test.2;
|
||||
let Test.1 = CallByName Num.47 Test.2;
|
||||
ret Test.1;
|
||||
"#
|
||||
),
|
||||
|
@ -12,20 +12,20 @@ pub enum Module<'a> {
|
||||
Platform { header: PlatformHeader<'a> },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct WhenBranch<'a> {
|
||||
pub patterns: &'a [Loc<Pattern<'a>>],
|
||||
pub value: Loc<Expr<'a>>,
|
||||
pub guard: Option<Loc<Expr<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct WhenPattern<'a> {
|
||||
pub pattern: Loc<Pattern<'a>>,
|
||||
pub guard: Option<Loc<Expr<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum StrSegment<'a> {
|
||||
Plaintext(&'a str), // e.g. "foo"
|
||||
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)!"
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum EscapedChar {
|
||||
Newline, // \n
|
||||
Tab, // \t
|
||||
@ -57,7 +57,7 @@ impl EscapedChar {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum StrLiteral<'a> {
|
||||
/// The most common case: a plain string with no escapes or interpolations
|
||||
PlainLine(&'a str),
|
||||
@ -74,7 +74,7 @@ pub enum StrLiteral<'a> {
|
||||
/// we move on to canonicalization, which often needs to allocate more because
|
||||
/// it's doing things like turning local variables into fully qualified symbols.
|
||||
/// 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> {
|
||||
// Number Literals
|
||||
Float(&'a str),
|
||||
@ -151,10 +151,6 @@ pub enum Expr<'a> {
|
||||
SpaceAfter(&'a Expr<'a>, &'a [CommentOrNewline<'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
|
||||
MalformedIdent(&'a str, crate::ident::BadIdent),
|
||||
MalformedClosure,
|
||||
@ -163,7 +159,7 @@ pub enum Expr<'a> {
|
||||
PrecedenceConflict(&'a PrecedenceConflict<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct PrecedenceConflict<'a> {
|
||||
pub whole_region: Region,
|
||||
pub binop1_position: Position,
|
||||
@ -173,7 +169,7 @@ pub struct PrecedenceConflict<'a> {
|
||||
pub expr: &'a Loc<Expr<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Def<'a> {
|
||||
// TODO in canonicalization, validate the pattern; only certain patterns
|
||||
// are allowed in annotations.
|
||||
@ -208,14 +204,10 @@ pub enum Def<'a> {
|
||||
SpaceBefore(&'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),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum TypeAnnotation<'a> {
|
||||
/// A function. The types of its arguments, then the type of its return value.
|
||||
Function(&'a [Loc<TypeAnnotation<'a>>], &'a Loc<TypeAnnotation<'a>>),
|
||||
@ -261,7 +253,7 @@ pub enum TypeAnnotation<'a> {
|
||||
Malformed(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Tag<'a> {
|
||||
Global {
|
||||
name: Loc<&'a str>,
|
||||
@ -281,7 +273,7 @@ pub enum Tag<'a> {
|
||||
Malformed(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum AssignedField<'a, Val> {
|
||||
// A required field with a label, e.g. `{ name: "blah" }` or `{ name : Str }`
|
||||
RequiredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
||||
@ -303,7 +295,7 @@ pub enum AssignedField<'a, Val> {
|
||||
Malformed(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum CommentOrNewline<'a> {
|
||||
Newline,
|
||||
LineComment(&'a str),
|
||||
@ -330,7 +322,7 @@ impl<'a> CommentOrNewline<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Pattern<'a> {
|
||||
// Identifier
|
||||
Identifier(&'a str),
|
||||
@ -351,10 +343,6 @@ pub enum Pattern<'a> {
|
||||
/// Can only occur inside of a RecordDestructure
|
||||
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
|
||||
NumLiteral(&'a str),
|
||||
NonBase10Literal {
|
||||
@ -464,8 +452,6 @@ impl<'a> Pattern<'a> {
|
||||
// { x, y ? False } = rec
|
||||
x == y
|
||||
}
|
||||
(Nested(x), Nested(y)) => x.equivalent(y),
|
||||
|
||||
// Literal
|
||||
(NumLiteral(x), NumLiteral(y)) => x == y,
|
||||
(
|
||||
|
@ -125,7 +125,7 @@ where
|
||||
E: 'a,
|
||||
{
|
||||
move |_, state: State<'a>| {
|
||||
if state.column > min_indent {
|
||||
if state.column >= min_indent {
|
||||
Ok((NoProgress, (), state))
|
||||
} else {
|
||||
Err((NoProgress, indent_problem(state.line, state.column), state))
|
||||
|
@ -16,16 +16,33 @@ use roc_region::all::{Located, Position, Region};
|
||||
|
||||
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>(
|
||||
min_indent: u16,
|
||||
arena: &'a bumpalo::Bump,
|
||||
state: State<'a>,
|
||||
) -> Result<Located<Expr<'a>>, EExpr<'a>> {
|
||||
let parser = space0_before_e(
|
||||
move |a, s| parse_loc_expr(min_indent, a, s),
|
||||
min_indent,
|
||||
EExpr::Space,
|
||||
EExpr::IndentStart,
|
||||
let parser = skip_second!(
|
||||
space0_before_e(
|
||||
move |a, s| parse_loc_expr(min_indent, a, s),
|
||||
min_indent,
|
||||
EExpr::Space,
|
||||
EExpr::IndentStart,
|
||||
),
|
||||
expr_end()
|
||||
);
|
||||
|
||||
match parser.parse(arena, state) {
|
||||
@ -35,9 +52,27 @@ pub fn test_parse_expr<'a>(
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MultiBackpassing {
|
||||
Allow,
|
||||
Disallow,
|
||||
pub struct ExprParseOptions {
|
||||
/// Check for and accept multi-backpassing syntax
|
||||
/// 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>> {
|
||||
@ -159,7 +194,7 @@ fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> {
|
||||
|
||||
fn parse_loc_term<'a>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
arena: &'a Bump,
|
||||
state: State<'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!(specialize(EExpr::Str, string_literal_help())),
|
||||
loc!(specialize(EExpr::Number, positive_number_literal_help())),
|
||||
loc!(specialize(
|
||||
EExpr::Lambda,
|
||||
closure_help(min_indent, multi_backpassing)
|
||||
)),
|
||||
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
|
||||
loc!(record_literal_help(min_indent)),
|
||||
loc!(specialize(EExpr::List, list_literal_help(min_indent))),
|
||||
loc!(map_with_arena!(
|
||||
@ -183,17 +215,14 @@ fn parse_loc_term<'a>(
|
||||
|
||||
fn loc_possibly_negative_or_negated_term<'a>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
) -> impl Parser<'a, Located<Expr<'a>>, EExpr<'a>> {
|
||||
one_of![
|
||||
|arena, state: State<'a>| {
|
||||
let initial = state;
|
||||
|
||||
let (_, (loc_op, loc_expr), state) = and!(loc!(unary_negate()), |a, s| parse_loc_term(
|
||||
min_indent,
|
||||
multi_backpassing,
|
||||
a,
|
||||
s
|
||||
min_indent, options, a, s
|
||||
))
|
||||
.parse(arena, state)?;
|
||||
|
||||
@ -205,7 +234,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
|
||||
loc!(specialize(EExpr::Number, number_literal_help())),
|
||||
loc!(map_with_arena!(
|
||||
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<_>, _)| {
|
||||
Expr::UnaryOp(
|
||||
@ -216,7 +245,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
|
||||
)),
|
||||
|arena, state| {
|
||||
// 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>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
start: Position,
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
|
||||
one_of![
|
||||
loc!(specialize(
|
||||
EExpr::If,
|
||||
if_expr_help(min_indent, multi_backpassing)
|
||||
)),
|
||||
loc!(specialize(EExpr::If, if_expr_help(min_indent, options))),
|
||||
loc!(specialize(
|
||||
EExpr::When,
|
||||
when::expr_help(min_indent, multi_backpassing)
|
||||
when::expr_help(min_indent, options)
|
||||
)),
|
||||
loc!(specialize(
|
||||
EExpr::Lambda,
|
||||
closure_help(min_indent, multi_backpassing)
|
||||
)),
|
||||
loc!(move |a, s| parse_expr_operator_chain(min_indent, multi_backpassing, start, a, s)),
|
||||
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
|
||||
loc!(move |a, s| parse_expr_operator_chain(min_indent, options, start, a, s)),
|
||||
fail_expr_start_e()
|
||||
]
|
||||
.parse(arena, state)
|
||||
@ -283,13 +306,13 @@ fn parse_expr_start<'a>(
|
||||
|
||||
fn parse_expr_operator_chain<'a>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
start: Position,
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
||||
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 end = state.get_position();
|
||||
@ -306,14 +329,7 @@ fn parse_expr_operator_chain<'a>(
|
||||
end,
|
||||
};
|
||||
|
||||
parse_expr_end(
|
||||
min_indent,
|
||||
multi_backpassing,
|
||||
start,
|
||||
expr_state,
|
||||
arena,
|
||||
state,
|
||||
)
|
||||
parse_expr_end(min_indent, options, start, expr_state, arena, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -685,7 +701,7 @@ struct DefState<'a> {
|
||||
}
|
||||
|
||||
fn parse_defs_end<'a>(
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
start: Position,
|
||||
mut def_state: DefState<'a>,
|
||||
arena: &'a Bump,
|
||||
@ -740,7 +756,7 @@ fn parse_defs_end<'a>(
|
||||
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)) => {
|
||||
let (_, ann_type, state) = specialize(
|
||||
@ -762,7 +778,7 @@ fn parse_defs_end<'a>(
|
||||
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)),
|
||||
@ -771,7 +787,7 @@ fn parse_defs_end<'a>(
|
||||
}
|
||||
|
||||
fn parse_defs_expr<'a>(
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
start: Position,
|
||||
def_state: DefState<'a>,
|
||||
arena: &'a Bump,
|
||||
@ -779,7 +795,7 @@ fn parse_defs_expr<'a>(
|
||||
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
||||
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),
|
||||
Ok((_, def_state, state)) => {
|
||||
// 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>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
start: Position,
|
||||
mut expr_state: ExprState<'a>,
|
||||
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 => {
|
||||
// negative terms
|
||||
|
||||
let (_, negated_expr, state) =
|
||||
parse_loc_term(min_indent, multi_backpassing, arena, state)?;
|
||||
let (_, negated_expr, state) = parse_loc_term(min_indent, options, arena, state)?;
|
||||
let new_end = state.get_position();
|
||||
|
||||
let arg = numeric_negate_expression(
|
||||
@ -856,14 +871,7 @@ fn parse_expr_operator<'a>(
|
||||
expr_state.spaces_after = spaces;
|
||||
expr_state.end = new_end;
|
||||
|
||||
parse_expr_end(
|
||||
min_indent,
|
||||
multi_backpassing,
|
||||
start,
|
||||
expr_state,
|
||||
arena,
|
||||
state,
|
||||
)
|
||||
parse_expr_end(min_indent, options, start, expr_state, arena, state)
|
||||
}
|
||||
BinOp::Assignment => {
|
||||
let expr_region = expr_state.expr.region;
|
||||
@ -912,7 +920,7 @@ fn parse_expr_operator<'a>(
|
||||
spaces_after: &[],
|
||||
};
|
||||
|
||||
parse_defs_expr(multi_backpassing, start, def_state, arena, state)
|
||||
parse_defs_expr(options, start, def_state, arena, state)
|
||||
}
|
||||
BinOp::Backpassing => {
|
||||
let expr_region = expr_state.expr.region;
|
||||
@ -1066,11 +1074,9 @@ fn parse_expr_operator<'a>(
|
||||
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)
|
||||
.parse(arena, state)
|
||||
{
|
||||
_ => match loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state) {
|
||||
Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)),
|
||||
Ok((_, mut new_expr, state)) => {
|
||||
let new_end = state.get_position();
|
||||
@ -1108,14 +1114,7 @@ fn parse_expr_operator<'a>(
|
||||
expr_state.spaces_after = spaces;
|
||||
|
||||
// TODO new start?
|
||||
parse_expr_end(
|
||||
min_indent,
|
||||
multi_backpassing,
|
||||
start,
|
||||
expr_state,
|
||||
arena,
|
||||
state,
|
||||
)
|
||||
parse_expr_end(min_indent, options, start, expr_state, arena, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1128,7 +1127,7 @@ fn parse_expr_operator<'a>(
|
||||
|
||||
fn parse_expr_end<'a>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
start: Position,
|
||||
mut expr_state: ExprState<'a>,
|
||||
arena: &'a Bump,
|
||||
@ -1136,7 +1135,7 @@ fn parse_expr_end<'a>(
|
||||
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
||||
let parser = skip_first!(
|
||||
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) {
|
||||
@ -1167,14 +1166,7 @@ fn parse_expr_end<'a>(
|
||||
expr_state.end = new_end;
|
||||
expr_state.spaces_after = new_spaces;
|
||||
|
||||
parse_expr_end(
|
||||
min_indent,
|
||||
multi_backpassing,
|
||||
start,
|
||||
expr_state,
|
||||
arena,
|
||||
state,
|
||||
)
|
||||
parse_expr_end(min_indent, options, start, expr_state, arena, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1187,19 +1179,12 @@ fn parse_expr_end<'a>(
|
||||
expr_state.consume_spaces(arena);
|
||||
expr_state.initial = before_op;
|
||||
parse_expr_operator(
|
||||
min_indent,
|
||||
multi_backpassing,
|
||||
start,
|
||||
expr_state,
|
||||
loc_op,
|
||||
arena,
|
||||
state,
|
||||
min_indent, options, start, expr_state, loc_op, arena, state,
|
||||
)
|
||||
}
|
||||
Err((NoProgress, _, mut state)) => {
|
||||
// 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.column += 1;
|
||||
|
||||
@ -1260,6 +1245,12 @@ fn parse_expr_end<'a>(
|
||||
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 {
|
||||
// roll back space parsing
|
||||
let state = expr_state.initial;
|
||||
@ -1277,7 +1268,15 @@ fn parse_loc_expr<'a>(
|
||||
arena: &'a Bump,
|
||||
state: State<'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>(
|
||||
@ -1285,17 +1284,25 @@ pub fn parse_loc_expr_no_multi_backpassing<'a>(
|
||||
arena: &'a Bump,
|
||||
state: State<'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>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
|
||||
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.
|
||||
@ -1339,9 +1346,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||
spaces,
|
||||
)),
|
||||
|
||||
Expr::ParensAround(sub_expr) | Expr::Nested(sub_expr) => {
|
||||
expr_to_pattern_help(arena, sub_expr)
|
||||
}
|
||||
Expr::ParensAround(sub_expr) => expr_to_pattern_help(arena, sub_expr),
|
||||
|
||||
Expr::Record {
|
||||
fields,
|
||||
@ -1385,7 +1390,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||
| Expr::RecordUpdate { .. }
|
||||
| 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)),
|
||||
}
|
||||
}
|
||||
@ -1414,7 +1419,7 @@ fn assigned_expr_field_to_pattern_help<'a>(
|
||||
AssignedField::OptionalValue(name, spaces, value) => {
|
||||
let result = arena.alloc(Located {
|
||||
region: value.region,
|
||||
value: value.value.clone(),
|
||||
value: value.value,
|
||||
});
|
||||
if spaces.is_empty() {
|
||||
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)?;
|
||||
|
||||
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) =
|
||||
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;
|
||||
|
||||
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 {
|
||||
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>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
) -> impl Parser<'a, Expr<'a>, ELambda<'a>> {
|
||||
map_with_arena!(
|
||||
skip_first!(
|
||||
@ -1516,7 +1526,7 @@ fn closure_help<'a>(
|
||||
// Parse the body
|
||||
space0_before_e(
|
||||
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,
|
||||
ELambda::Space,
|
||||
@ -1541,7 +1551,7 @@ mod when {
|
||||
/// Parser for when expressions.
|
||||
pub fn expr_help<'a>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
) -> impl Parser<'a, Expr<'a>, When<'a>> {
|
||||
then(
|
||||
and!(
|
||||
@ -1549,7 +1559,7 @@ mod when {
|
||||
skip_second!(
|
||||
space0_around_ee(
|
||||
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,
|
||||
When::Space,
|
||||
@ -1572,7 +1582,7 @@ mod when {
|
||||
// Everything in the branches must be indented at least as much as the case itself.
|
||||
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((
|
||||
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>> {
|
||||
move |arena, state| {
|
||||
fn branches<'a>(
|
||||
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);
|
||||
|
||||
// 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.
|
||||
|
||||
let (_, (loc_first_patterns, loc_first_guard), state) =
|
||||
branch_alternatives(min_indent).parse(arena, state)?;
|
||||
let loc_first_pattern = loc_first_patterns.first().unwrap();
|
||||
let original_indent = loc_first_pattern.region.start_col;
|
||||
let indented_more = original_indent + 1;
|
||||
let (_, ((pattern_indent_level, loc_first_patterns), loc_first_guard), mut state) =
|
||||
branch_alternatives(min_indent, options, None).parse(arena, state)?;
|
||||
let original_indent = pattern_indent_level;
|
||||
|
||||
state.indent_col = pattern_indent_level;
|
||||
|
||||
// Parse the first "->" and the expression after it.
|
||||
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.
|
||||
branches.push(arena.alloc(WhenBranch {
|
||||
@ -1619,19 +1634,21 @@ mod when {
|
||||
let branch_parser = map!(
|
||||
and!(
|
||||
then(
|
||||
branch_alternatives(min_indent),
|
||||
move |_arena, state, _, (loc_patterns, loc_guard)| {
|
||||
match alternatives_indented_correctly(&loc_patterns, original_indent) {
|
||||
Ok(()) => Ok((MadeProgress, (loc_patterns, loc_guard), state)),
|
||||
Err(indent) => Err((
|
||||
branch_alternatives(min_indent, options, Some(pattern_indent_level)),
|
||||
move |_arena, state, _, ((indent_col, loc_patterns), loc_guard)| {
|
||||
if pattern_indent_level == indent_col {
|
||||
Ok((MadeProgress, (loc_patterns, loc_guard), state))
|
||||
} else {
|
||||
let indent = pattern_indent_level - indent_col;
|
||||
Err((
|
||||
MadeProgress,
|
||||
When::PatternAlignment(indent, state.line, state.column),
|
||||
state,
|
||||
)),
|
||||
))
|
||||
}
|
||||
},
|
||||
),
|
||||
branch_result(indented_more)
|
||||
branch_result(original_indent + 1)
|
||||
),
|
||||
|((patterns, guard), expr)| {
|
||||
let patterns: Vec<'a, _> = patterns;
|
||||
@ -1661,40 +1678,36 @@ mod when {
|
||||
}
|
||||
}
|
||||
|
||||
Ok((MadeProgress, branches, state))
|
||||
Ok((
|
||||
MadeProgress,
|
||||
branches,
|
||||
State {
|
||||
indent_col: when_indent,
|
||||
..state
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsing alternative patterns in when branches.
|
||||
fn branch_alternatives<'a>(
|
||||
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!(
|
||||
sep_by1(word1(b'|', When::Bar), |arena, state| {
|
||||
let (_, spaces, state) =
|
||||
backtrackable(space0_e(min_indent, When::Space, When::IndentPattern))
|
||||
.parse(arena, state)?;
|
||||
|
||||
let (_, loc_pattern, state) = space0_after_e(
|
||||
specialize(When::Pattern, crate::pattern::loc_pattern_help(min_indent)),
|
||||
min_indent,
|
||||
When::Space,
|
||||
When::IndentPattern,
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
if spaces.is_empty() {
|
||||
loc_pattern
|
||||
} else {
|
||||
arena
|
||||
.alloc(loc_pattern.value)
|
||||
.with_spaces_before(spaces, loc_pattern.region)
|
||||
},
|
||||
state,
|
||||
))
|
||||
}),
|
||||
branch_alternatives_help(min_indent, pattern_indent_level),
|
||||
one_of![
|
||||
map!(
|
||||
skip_first!(
|
||||
@ -1702,7 +1715,7 @@ mod when {
|
||||
// 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)
|
||||
parse_loc_expr_with_options(min_indent + 1, options, arena, state)
|
||||
}),
|
||||
min_indent,
|
||||
When::Space,
|
||||
@ -1717,22 +1730,103 @@ mod when {
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if alternatives of a when branch are indented correctly.
|
||||
fn alternatives_indented_correctly<'a>(
|
||||
loc_patterns: &'a Vec<'a, Located<Pattern<'a>>>,
|
||||
original_indent: u16,
|
||||
) -> Result<(), u16> {
|
||||
let (first, rest) = loc_patterns.split_first().unwrap();
|
||||
let first_indented_correctly = first.region.start_col == original_indent;
|
||||
if first_indented_correctly {
|
||||
for when_pattern in rest.iter() {
|
||||
if when_pattern.region.start_col < original_indent {
|
||||
return Err(original_indent - when_pattern.region.start_col);
|
||||
fn branch_single_alternative<'a>(
|
||||
min_indent: u16,
|
||||
) -> impl Parser<'a, Located<Pattern<'a>>, When<'a>> {
|
||||
move |arena, state| {
|
||||
let (_, spaces, state) =
|
||||
backtrackable(space0_e(min_indent, When::Space, When::IndentPattern))
|
||||
.parse(arena, state)?;
|
||||
|
||||
let (_, loc_pattern, state) = space0_after_e(
|
||||
specialize(When::Pattern, crate::pattern::loc_pattern_help(min_indent)),
|
||||
min_indent,
|
||||
When::Space,
|
||||
When::IndentPattern,
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
if spaces.is_empty() {
|
||||
loc_pattern
|
||||
} else {
|
||||
arena
|
||||
.alloc(loc_pattern.value)
|
||||
.with_spaces_before(spaces, loc_pattern.region)
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn branch_alternatives_help<'a>(
|
||||
min_indent: u16,
|
||||
pattern_indent_level: Option<u16>,
|
||||
) -> impl Parser<'a, (Col, Vec<'a, Located<Pattern<'a>>>), When<'a>> {
|
||||
move |arena, state: State<'a>| {
|
||||
let initial = state;
|
||||
|
||||
// put no restrictions on the indent after the spaces; we'll check it manually
|
||||
match space0_e(0, When::Space, When::IndentPattern).parse(arena, state) {
|
||||
Err((MadeProgress, fail, _)) => Err((NoProgress, fail, initial)),
|
||||
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>(
|
||||
min_indent: u16,
|
||||
multi_backpassing: MultiBackpassing,
|
||||
options: ExprParseOptions,
|
||||
) -> impl Parser<'a, Expr<'a>, If<'a>> {
|
||||
move |arena: &'a Bump, 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(
|
||||
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,
|
||||
If::Space,
|
||||
|
@ -42,15 +42,15 @@ pub enum PackageOrPath<'a> {
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct ModuleName<'a>(&'a str);
|
||||
|
||||
impl<'a> Into<&'a str> for ModuleName<'a> {
|
||||
fn into(self) -> &'a str {
|
||||
self.0
|
||||
impl<'a> From<ModuleName<'a>> for &'a str {
|
||||
fn from(name: ModuleName<'a>) -> Self {
|
||||
name.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<InlinableString> for ModuleName<'a> {
|
||||
fn into(self) -> InlinableString {
|
||||
self.0.into()
|
||||
impl<'a> From<ModuleName<'a>> for InlinableString {
|
||||
fn from(name: ModuleName<'a>) -> InlinableString {
|
||||
name.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ pub enum SyntaxError<'a> {
|
||||
ReservedKeyword(Region),
|
||||
ArgumentsBeforeEquals(Region),
|
||||
NotYetImplemented(String),
|
||||
TODO,
|
||||
Todo,
|
||||
Type(Type<'a>),
|
||||
Pattern(EPattern<'a>),
|
||||
Expr(EExpr<'a>),
|
||||
@ -360,6 +360,7 @@ impl<'a> SyntaxError<'a> {
|
||||
pub fn into_parse_problem(
|
||||
self,
|
||||
filename: std::path::PathBuf,
|
||||
prefix: &'a str,
|
||||
bytes: &'a [u8],
|
||||
) -> ParseProblem<'a, SyntaxError<'a>> {
|
||||
ParseProblem {
|
||||
@ -368,6 +369,7 @@ impl<'a> SyntaxError<'a> {
|
||||
problem: self,
|
||||
filename,
|
||||
bytes,
|
||||
prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -379,6 +381,7 @@ pub type Col = u16;
|
||||
pub enum EExpr<'a> {
|
||||
Start(Row, Col),
|
||||
End(Row, Col),
|
||||
BadExprEnd(Row, Col),
|
||||
Space(BadInputError, Row, Col),
|
||||
|
||||
Dot(Row, Col),
|
||||
@ -679,6 +682,8 @@ pub struct ParseProblem<'a, T> {
|
||||
pub problem: T,
|
||||
pub filename: std::path::PathBuf,
|
||||
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> {
|
||||
@ -926,8 +931,8 @@ where
|
||||
state = next_state;
|
||||
buf.push(next_output);
|
||||
}
|
||||
Err((element_progress, fail, state)) => {
|
||||
return Err((element_progress, fail, state));
|
||||
Err((_, fail, state)) => {
|
||||
return Err((MadeProgress, fail, state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,8 +62,8 @@ pub fn loc_pattern_help<'a>(
|
||||
EPattern::Record,
|
||||
crate::pattern::record_pattern_help(min_indent)
|
||||
)),
|
||||
loc!(number_pattern_help()),
|
||||
loc!(string_pattern_help()),
|
||||
loc!(number_pattern_help())
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2550,6 +2550,247 @@ mod test_parse {
|
||||
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]
|
||||
fn when_with_records() {
|
||||
let arena = Bump::new();
|
||||
@ -2599,6 +2840,47 @@ mod test_parse {
|
||||
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]
|
||||
fn when_with_alternative_patterns() {
|
||||
let arena = Bump::new();
|
||||
@ -2620,9 +2902,9 @@ mod test_parse {
|
||||
let pattern2_alt =
|
||||
Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("bar"))), newlines);
|
||||
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 loc_expr2 = Located::new(3, 3, 10, 11, expr2);
|
||||
let loc_expr2 = Located::new(3, 3, 11, 12, expr2);
|
||||
let branch2 = &*arena.alloc(WhenBranch {
|
||||
patterns: arena.alloc([loc_pattern2, loc_pattern2_alt]),
|
||||
value: loc_expr2,
|
||||
@ -2642,7 +2924,7 @@ mod test_parse {
|
||||
when x is
|
||||
"blah" | "blop" -> 1
|
||||
"foo" |
|
||||
"bar" -> 2
|
||||
"bar" -> 2
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -134,6 +134,7 @@ pub enum RuntimeError {
|
||||
},
|
||||
InvalidPrecedence(PrecedenceProblem, Region),
|
||||
MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region),
|
||||
MalformedTypeName(Box<str>, Region),
|
||||
MalformedClosure(Region),
|
||||
InvalidRecordUpdate {
|
||||
region: Region,
|
||||
|
@ -776,8 +776,20 @@ fn pretty_runtime_error<'b>(
|
||||
}
|
||||
RuntimeError::MalformedIdentifier(_box_str, 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::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
|
||||
|
@ -272,22 +272,31 @@ fn to_expr_report<'a>(
|
||||
])
|
||||
.indent(4),
|
||||
])],
|
||||
b"->" => vec![alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The arrow "),
|
||||
alloc.parser_suggestion("->"),
|
||||
alloc.reflow(" is only used to define cases in a "),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow("."),
|
||||
]),
|
||||
alloc
|
||||
.vcat(vec![
|
||||
alloc.text("when color is"),
|
||||
alloc.text("Red -> \"stop!\"").indent(4),
|
||||
alloc.text("Green -> \"go!\"").indent(4),
|
||||
])
|
||||
.indent(4),
|
||||
])],
|
||||
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.reflow("The arrow "),
|
||||
alloc.parser_suggestion("->"),
|
||||
alloc.reflow(" is only used to define cases in a "),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow("."),
|
||||
]),
|
||||
alloc
|
||||
.vcat(vec![
|
||||
alloc.text("when color is"),
|
||||
alloc.text("Red -> \"stop!\"").indent(4),
|
||||
alloc.text("Green -> \"go!\"").indent(4),
|
||||
])
|
||||
.indent(4),
|
||||
])]
|
||||
}
|
||||
},
|
||||
b"!" => vec![
|
||||
alloc.reflow("The boolean negation operator "),
|
||||
alloc.parser_suggestion("!"),
|
||||
@ -458,6 +467,27 @@ fn to_expr_report<'a>(
|
||||
*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) => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_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) => {
|
||||
match dbg!(what_is_next(alloc.src_lines, row, col)) {
|
||||
match what_is_next(alloc.src_lines, row, col) {
|
||||
Next::Other(Some(',')) => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
@ -1387,25 +1417,68 @@ fn to_unfinished_when_report<'a>(
|
||||
start_row: Row,
|
||||
start_col: Col,
|
||||
message: RocDocBuilder<'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 region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(r"I was partway through parsing a "),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(r" expression, but I got stuck here:"),
|
||||
]),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
message,
|
||||
note_for_when_error(alloc),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
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_row_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 was partway through parsing a "),
|
||||
alloc.reflow(r"I am parsing a "),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(r" expression, but I got stuck here:"),
|
||||
alloc.reflow(r" expression right now, but this arrow is confusing me:"),
|
||||
]),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
message,
|
||||
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: "UNFINISHED WHEN".to_string(),
|
||||
title: "UNEXPECTED ARROW".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ mod test_reporting {
|
||||
|
||||
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);
|
||||
|
||||
callback(doc.pretty(&alloc).append(alloc.line()), buf)
|
||||
@ -190,8 +190,11 @@ mod test_reporting {
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
use roc_parse::parser::SyntaxError;
|
||||
let problem =
|
||||
SyntaxError::Header(fail).into_parse_problem(filename.clone(), src.as_bytes());
|
||||
let problem = SyntaxError::Header(fail).into_parse_problem(
|
||||
filename.clone(),
|
||||
"",
|
||||
src.as_bytes(),
|
||||
);
|
||||
let doc = parse_problem(&alloc, filename, 0, problem);
|
||||
|
||||
callback(doc.pretty(&alloc).append(alloc.line()), buf)
|
||||
@ -4483,7 +4486,7 @@ mod test_reporting {
|
||||
^
|
||||
|
||||
Tab characters are not allowed.
|
||||
"
|
||||
"
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -4547,7 +4550,19 @@ mod test_reporting {
|
||||
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 ──────────────────────────────────────────────────────────────────
|
||||
@ -4570,7 +4585,19 @@ mod test_reporting {
|
||||
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 ────────────────────────────────────────────────────────────────
|
||||
@ -4618,7 +4645,19 @@ mod test_reporting {
|
||||
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 ────────────────────────────────────────────────────────
|
||||
@ -4642,7 +4681,19 @@ mod test_reporting {
|
||||
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]
|
||||
fn empty_or_pattern() {
|
||||
// this should get better with time
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -4929,29 +4979,16 @@ mod test_reporting {
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── MISSING EXPRESSION ──────────────────────────────────────────────────────────
|
||||
|
||||
I am partway through parsing a definition, but I got stuck here:
|
||||
|
||||
1│ when Just 4 is
|
||||
── UNFINISHED PATTERN ──────────────────────────────────────────────────────────
|
||||
|
||||
I just started parsing a pattern, but I got stuck here:
|
||||
|
||||
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
|
||||
// "#
|
||||
// ),
|
||||
)
|
||||
}
|
||||
|
||||
@ -5083,29 +5120,111 @@ mod test_reporting {
|
||||
r#"
|
||||
when 4 is
|
||||
5 -> 2
|
||||
_ -> 2
|
||||
2 -> 2
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── UNFINISHED WHEN ─────────────────────────────────────────────────────────────
|
||||
── 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!
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
#[test]
|
||||
fn when_over_indented_underscore() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
when 4 is
|
||||
5 -> 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.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[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.
|
||||
|
||||
|
||||
when List.first plants is
|
||||
Ok n ->
|
||||
n
|
||||
|
||||
|
||||
Err _ ->
|
||||
200
|
||||
|
||||
|
||||
Notice the indentation. All patterns are aligned, and each branch is
|
||||
indented a bit more than the corresponding pattern. That is important!
|
||||
"#
|
||||
@ -5757,21 +5876,18 @@ mod test_reporting {
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── MISSING FINAL EXPRESSION ────────────────────────────────────────────────────
|
||||
|
||||
I am partway through parsing a definition's final expression, but I
|
||||
got stuck here:
|
||||
|
||||
── UNKNOWN OPERATOR ────────────────────────────────────────────────────────────
|
||||
|
||||
This looks like an operator, but it's not one I recognize!
|
||||
|
||||
1│ main = 5 -> 3
|
||||
^
|
||||
|
||||
This definition is missing a final expression. A nested definition
|
||||
must be followed by either another definition, or an expression
|
||||
|
||||
x = 4
|
||||
y = 2
|
||||
|
||||
x + y
|
||||
^^
|
||||
|
||||
The arrow -> is only used to define cases in a `when`.
|
||||
|
||||
when color is
|
||||
Red -> "stop!"
|
||||
Green -> "go!"
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -880,7 +880,7 @@ fn check_for_infinite_type(
|
||||
let var = loc_var.value;
|
||||
|
||||
let is_uniq_infer = matches!(
|
||||
subs.get(var).content,
|
||||
subs.get_ref(var).content,
|
||||
Content::Alias(Symbol::ATTR_ATTR, _, _)
|
||||
);
|
||||
|
||||
@ -1088,7 +1088,7 @@ fn generalize(
|
||||
for vars in all_but_last_pool {
|
||||
for &var in vars {
|
||||
if !subs.redundant(var) {
|
||||
let rank = subs.get(var).rank;
|
||||
let rank = subs.get_rank(var);
|
||||
|
||||
pools.get_mut(rank).push(var);
|
||||
}
|
||||
@ -1099,13 +1099,12 @@ fn generalize(
|
||||
// otherwise generalize
|
||||
for &var in last_pool {
|
||||
if !subs.redundant(var) {
|
||||
let mut desc = subs.get(var);
|
||||
let desc_rank = subs.get_rank(var);
|
||||
|
||||
if desc.rank < young_rank {
|
||||
pools.get_mut(desc.rank).push(var);
|
||||
if desc_rank < young_rank {
|
||||
pools.get_mut(desc_rank).push(var);
|
||||
} else {
|
||||
desc.rank = Rank::NONE;
|
||||
subs.set(var, desc);
|
||||
subs.set_rank(var, Rank::NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1121,18 +1120,8 @@ fn pool_to_rank_table(
|
||||
|
||||
// Sort the variables into buckets by rank.
|
||||
for &var in young_vars.iter() {
|
||||
let desc = subs.get(var);
|
||||
let rank = desc.rank;
|
||||
|
||||
subs.set(
|
||||
var,
|
||||
Descriptor {
|
||||
rank,
|
||||
mark: young_mark,
|
||||
content: desc.content,
|
||||
copy: desc.copy,
|
||||
},
|
||||
);
|
||||
let rank = subs.get_rank(var);
|
||||
subs.set_mark(var, young_mark);
|
||||
|
||||
debug_assert!(rank.into_usize() < young_rank.into_usize() + 1);
|
||||
pools.get_mut(rank).push(var);
|
||||
@ -1155,24 +1144,15 @@ fn adjust_rank(
|
||||
if desc.mark == young_mark {
|
||||
let Descriptor {
|
||||
content,
|
||||
rank,
|
||||
rank: _,
|
||||
mark: _,
|
||||
copy,
|
||||
} = desc;
|
||||
|
||||
// Mark the variable as visited before adjusting content, as it may be cyclic.
|
||||
subs.set(
|
||||
var,
|
||||
Descriptor {
|
||||
content: content.clone(),
|
||||
rank,
|
||||
mark: visit_mark,
|
||||
copy,
|
||||
},
|
||||
);
|
||||
subs.set_mark(var, visit_mark);
|
||||
|
||||
let max_rank =
|
||||
adjust_rank_content(subs, young_mark, visit_mark, group_rank, content.clone());
|
||||
let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, &content);
|
||||
|
||||
subs.set(
|
||||
var,
|
||||
@ -1208,7 +1188,7 @@ fn adjust_rank_content(
|
||||
young_mark: Mark,
|
||||
visit_mark: Mark,
|
||||
group_rank: Rank,
|
||||
content: Content,
|
||||
content: &Content,
|
||||
) -> Rank {
|
||||
use roc_types::subs::Content::*;
|
||||
use roc_types::subs::FlatType::*;
|
||||
@ -1224,14 +1204,15 @@ fn adjust_rank_content(
|
||||
let mut rank = Rank::toplevel();
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
@ -1244,12 +1225,13 @@ fn adjust_rank_content(
|
||||
young_mark,
|
||||
visit_mark,
|
||||
group_rank,
|
||||
closure_var,
|
||||
*closure_var,
|
||||
));
|
||||
}
|
||||
|
||||
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
|
||||
@ -1263,9 +1245,9 @@ fn adjust_rank_content(
|
||||
EmptyTagUnion => Rank::toplevel(),
|
||||
|
||||
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(
|
||||
subs,
|
||||
young_mark,
|
||||
@ -1279,7 +1261,7 @@ fn adjust_rank_content(
|
||||
}
|
||||
|
||||
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() {
|
||||
rank =
|
||||
@ -1290,9 +1272,9 @@ fn adjust_rank_content(
|
||||
}
|
||||
|
||||
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(
|
||||
subs, young_mark, visit_mark, group_rank, ext_var,
|
||||
subs, young_mark, visit_mark, group_rank, *ext_var,
|
||||
));
|
||||
|
||||
for var in tags.values().flatten() {
|
||||
@ -1305,10 +1287,11 @@ fn adjust_rank_content(
|
||||
|
||||
Boolean(Bool::Shared) => Rank::toplevel(),
|
||||
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 {
|
||||
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
|
||||
@ -1322,13 +1305,13 @@ fn adjust_rank_content(
|
||||
let mut rank = Rank::toplevel();
|
||||
|
||||
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()
|
||||
// this theory is not true in Roc! aliases of function types capture the closure var
|
||||
rank = rank.max(adjust_rank(
|
||||
subs, young_mark, visit_mark, group_rank, real_var,
|
||||
subs, young_mark, visit_mark, group_rank, *real_var,
|
||||
));
|
||||
|
||||
rank
|
||||
|
@ -1760,6 +1760,13 @@ fn list_sum() {
|
||||
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]
|
||||
fn list_keep_oks() {
|
||||
assert_evals_to!("List.keepOks [] (\\x -> x)", 0, i64);
|
||||
|
@ -361,7 +361,6 @@ mod gen_num {
|
||||
|
||||
#[test]
|
||||
fn f64_sqrt() {
|
||||
// FIXME this works with normal types, but fails when checking uniqueness types
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
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]
|
||||
fn f64_round_old() {
|
||||
assert_evals_to!("Num.round 3.6", 4, i64);
|
||||
|
@ -2258,3 +2258,41 @@ fn backpassing_result() {
|
||||
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
|
||||
);
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ pub fn helper<'a>(
|
||||
src: &str,
|
||||
stdlib: &'a roc_builtins::std::StdLib,
|
||||
leak: bool,
|
||||
ignore_problems: bool,
|
||||
context: &'a inkwell::context::Context,
|
||||
) -> (&'static str, String, Library) {
|
||||
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
|
||||
@ -74,7 +75,7 @@ pub fn helper<'a>(
|
||||
|
||||
let mut loaded = match loaded {
|
||||
Ok(x) => x,
|
||||
Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => {
|
||||
Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
|
||||
println!("{}", report);
|
||||
panic!();
|
||||
}
|
||||
@ -170,13 +171,13 @@ pub fn helper<'a>(
|
||||
println!("{}", lines.join("\n"));
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||
Some(found) => found.clone(),
|
||||
Some(found) => *found,
|
||||
None => panic!(
|
||||
"The main function symbol {:?} does not have a procedure in {:?}",
|
||||
main_fn_symbol,
|
||||
@ -331,7 +332,7 @@ pub fn helper<'a>(
|
||||
|
||||
#[macro_export]
|
||||
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 inkwell::context::Context;
|
||||
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 (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 expected = $expected;
|
||||
@ -354,7 +355,7 @@ macro_rules! assert_llvm_evals_to {
|
||||
};
|
||||
|
||||
($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
|
||||
// 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
|
||||
@ -392,7 +393,7 @@ macro_rules! assert_non_opt_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
// 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) => {{
|
||||
|
@ -93,9 +93,9 @@ impl VarStore {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Variable> for VarStore {
|
||||
fn into(self) -> Variable {
|
||||
Variable(self.next)
|
||||
impl From<VarStore> for Variable {
|
||||
fn from(store: VarStore) -> Self {
|
||||
Variable(store.next)
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,9 +139,9 @@ impl fmt::Debug for OptVariable {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Option<Variable>> for OptVariable {
|
||||
fn into(self) -> Option<Variable> {
|
||||
self.into_variable()
|
||||
impl From<OptVariable> for Option<Variable> {
|
||||
fn from(opt_var: OptVariable) -> Self {
|
||||
opt_var.into_variable()
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,9 +180,9 @@ impl Variable {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<OptVariable> for Variable {
|
||||
fn into(self) -> OptVariable {
|
||||
OptVariable(self.0)
|
||||
impl From<Variable> for OptVariable {
|
||||
fn from(var: Variable) -> Self {
|
||||
OptVariable(var.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,6 +306,18 @@ impl Subs {
|
||||
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 {
|
||||
self.utable.probe_value_without_compacting(key)
|
||||
}
|
||||
@ -471,9 +483,9 @@ impl fmt::Debug for Rank {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<usize> for Rank {
|
||||
fn into(self) -> usize {
|
||||
self.0
|
||||
impl From<Rank> for usize {
|
||||
fn from(rank: Rank) -> Self {
|
||||
rank.0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "docs"
|
||||
name = "roc_docs"
|
||||
version = "0.1.0"
|
||||
authors = ["Pablo Hirafuji <pablohirafuji@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
@ -1,5 +1,4 @@
|
||||
extern crate fs_extra;
|
||||
extern crate handlebars;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
@ -19,26 +18,26 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Template {
|
||||
package_name: String,
|
||||
package_version: String,
|
||||
module_name: String,
|
||||
module_docs: String,
|
||||
module_entries: Vec<ModuleEntry>,
|
||||
module_links: Vec<TemplateLink>,
|
||||
pub package_name: String,
|
||||
pub package_version: String,
|
||||
pub module_name: String,
|
||||
pub module_docs: String,
|
||||
pub module_entries: Vec<ModuleEntry>,
|
||||
pub module_links: Vec<TemplateLink>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Debug, PartialEq)]
|
||||
pub struct ModuleEntry {
|
||||
name: String,
|
||||
docs: String,
|
||||
pub name: String,
|
||||
pub docs: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TemplateLink {
|
||||
name: String,
|
||||
href: String,
|
||||
classes: String,
|
||||
entries: Vec<TemplateLinkEntry>,
|
||||
pub name: String,
|
||||
pub href: String,
|
||||
pub classes: String,
|
||||
pub entries: Vec<TemplateLinkEntry>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@ -46,27 +45,9 @@ pub struct TemplateLinkEntry {
|
||||
name: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
generate(
|
||||
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);
|
||||
|
||||
pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
||||
let files_docs = files_to_documentations(filenames, std_lib);
|
||||
//
|
||||
// TODO: get info from a file like "elm.json"
|
||||
let package = roc_load::docs::Documentation {
|
||||
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,
|
||||
};
|
||||
|
||||
// Remove old build folder, if exists
|
||||
let _ = fs::remove_dir_all(build_dir);
|
||||
if !build_dir.exists() {
|
||||
fs::create_dir_all(build_dir).expect("TODO gracefully handle unable to create build dir");
|
||||
}
|
||||
|
||||
let version_folder = build_dir
|
||||
.join(package.name.clone())
|
||||
.join(package.version.clone());
|
||||
// Copy over the assets
|
||||
fs::write(
|
||||
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::create_dir_all(&version_folder)
|
||||
.expect("TODO gracefully handle creating directories failing");
|
||||
fs::write(
|
||||
build_dir.join("styles.css"),
|
||||
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
|
||||
let mut handlebars = handlebars::Handlebars::new();
|
||||
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");
|
||||
|
||||
// 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 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 =
|
||||
fs::File::create(filepath).expect("TODO gracefully handle creating file failing");
|
||||
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");
|
||||
}
|
||||
|
||||
// Copy /static folder content to /build
|
||||
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, ©_options)
|
||||
.expect("TODO gracefully handle copying static content failing");
|
||||
println!("Docs generated at {}", build_dir.display());
|
||||
println!("🎉 Docs generated in {}", build_dir.display());
|
||||
}
|
||||
|
||||
fn files_to_documentations(
|
||||
pub fn files_to_documentations(
|
||||
filenames: Vec<PathBuf>,
|
||||
std_lib: StdLib,
|
||||
src_dir: &Path,
|
||||
) -> Vec<ModuleDocumentation> {
|
||||
let arena = Bump::new();
|
||||
let mut files_docs = vec![];
|
||||
|
||||
for filename in filenames {
|
||||
let mut src_dir = filename.clone();
|
||||
src_dir.pop();
|
||||
|
||||
match roc_load::file::load_and_typecheck(
|
||||
&arena,
|
||||
filename,
|
||||
&std_lib,
|
||||
src_dir,
|
||||
src_dir.as_path(),
|
||||
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)
|
||||
builtin_defs_map,
|
||||
) {
|
||||
Ok(mut loaded) => files_docs.extend(loaded.documentation.drain().map(|x| x.1)),
|
||||
Err(LoadingProblem::ParsingFailedReport(report)) => {
|
||||
Err(LoadingProblem::FormattedReport(report)) => {
|
||||
println!("{}", report);
|
||||
panic!();
|
||||
}
|
||||
@ -148,7 +132,10 @@ fn files_to_documentations(
|
||||
files_docs
|
||||
}
|
||||
|
||||
fn documentation_to_template_data(doc: &Documentation, module: &ModuleDocumentation) -> Template {
|
||||
pub fn documentation_to_template_data(
|
||||
doc: &Documentation,
|
||||
module: &ModuleDocumentation,
|
||||
) -> Template {
|
||||
Template {
|
||||
package_name: doc.name.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());
|
||||
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 >>> 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -66,16 +66,14 @@ a:hover {
|
||||
}
|
||||
|
||||
.pkg-and-logo {
|
||||
min-width: 0;
|
||||
/* necessary for text-overflow: ellipsis to work in descendants */
|
||||
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-color: var(--top-bar-bg);
|
||||
}
|
||||
|
||||
.pkg-and-logo a,
|
||||
.pkg-and-logo a:visited {
|
||||
.pkg-and-logo a, .pkg-and-logo a:visited {
|
||||
color: var(--top-bar-fg);
|
||||
}
|
||||
|
||||
@ -84,18 +82,11 @@ a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
min-width: 0;
|
||||
/* necessary for text-overflow: ellipsis to work in descendants */
|
||||
}
|
||||
|
||||
.search-button {
|
||||
flex-shrink: 0;
|
||||
/* always shrink the package name before these; they have a relatively constrained length */
|
||||
flex-shrink: 0; /* always shrink the package name before these; they have a relatively constrained length */
|
||||
padding: 12px 18px;
|
||||
margin-right: 42px;
|
||||
display: none;
|
||||
/* only show this in the mobile view */
|
||||
display: none; /* only show this in the mobile view */
|
||||
}
|
||||
|
||||
.version {
|
||||
@ -127,6 +118,8 @@ main {
|
||||
line-height: 1.85em;
|
||||
margin-top: 2px;
|
||||
padding: 48px;
|
||||
|
||||
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
|
||||
}
|
||||
|
||||
#sidebar-nav {
|
||||
@ -160,13 +153,11 @@ main {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 24px;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
/* necessary for text-overflow: ellipsis to work in descendants */
|
||||
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
|
||||
}
|
||||
|
||||
.top-header-triangle {
|
||||
@ -181,8 +172,8 @@ main {
|
||||
}
|
||||
|
||||
p {
|
||||
overflow-wrap: break-word;
|
||||
margin: 24px 0;
|
||||
overflow-wrap: break-word;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
@ -240,8 +231,7 @@ footer p {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.module-name a,
|
||||
.module-name a:visited {
|
||||
.module-name a, .module-name a:visited {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@ -259,8 +249,7 @@ footer p {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
a, a:visited {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
@ -325,20 +314,19 @@ pre code {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
#module-search,
|
||||
#module-search:focus {
|
||||
#module-search, #module-search:focus {
|
||||
opacity: 1;
|
||||
padding: 12px 16px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* Hide the "Search" label link when the text input has focus */
|
||||
#module-search:focus+#search-link {
|
||||
#module-search:focus + #search-link {
|
||||
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 {
|
||||
display: block;
|
||||
/* This is only visible in mobile. */
|
||||
display: block; /* This is only visible in mobile. */
|
||||
}
|
||||
|
||||
.top-header {
|
||||
justify-content: space-between;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
@ -443,21 +431,19 @@ pre code {
|
||||
}
|
||||
|
||||
main {
|
||||
grid-column-start: none;
|
||||
grid-column-end: none;
|
||||
grid-row-start: above-footer;
|
||||
grid-row-end: above-footer;
|
||||
padding: 18px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
#sidebar-nav {
|
||||
grid-column-start: none;
|
||||
grid-column-end: none;
|
||||
grid-row-start: sidebar;
|
||||
grid-row-end: sidebar;
|
||||
margin-top: 0;
|
||||
padding-left: 0;
|
||||
width: auto;
|
||||
@ -478,12 +464,30 @@ pre code {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.top-header {
|
||||
justify-content: space-between;
|
||||
body {
|
||||
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 {
|
||||
/* Display the sidebar below <main> without affecting tab index */
|
||||
flex-direction: column-reverse;
|
||||
.top-header-triangle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.pkg-and-logo {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pkg-full-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.pkg-full-name a {
|
||||
padding-top: 24px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@
|
||||
<title>The Roc Programming Language</title>
|
||||
<meta name="description" content="A language for building fast, reliable software.">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<script type="text/javascript" src="/search.js" defer></script>
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
<link rel="icon" href="favicon.svg">
|
||||
<script type="text/javascript" src="search.js" defer></script>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
49
docs/tests/test_docs.rs
Normal file
49
docs/tests/test_docs.rs
Normal 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 >>> 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [
|
||||
"yaml_conf"
|
||||
], default-features = false }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
nonempty = "0.6.0"
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.4"
|
||||
|
76
editor/src/editor/code_lines.rs
Normal file
76
editor/src/editor/code_lines.rs
Normal 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())
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ use crate::editor::mvc::app_model::AppModel;
|
||||
use crate::editor::mvc::app_update::{
|
||||
handle_copy, handle_cut, handle_paste, pass_keydown_to_focused,
|
||||
};
|
||||
use crate::editor::slow_pool::SlowPool;
|
||||
use winit::event::VirtualKeyCode::*;
|
||||
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
|
||||
|
||||
@ -12,7 +11,6 @@ pub fn handle_keydown(
|
||||
virtual_keycode: VirtualKeyCode,
|
||||
modifiers: ModifiersState,
|
||||
app_model: &mut AppModel,
|
||||
markup_node_pool: &mut SlowPool,
|
||||
) -> EdResult<()> {
|
||||
if let ElementState::Released = elem_state {
|
||||
return Ok(());
|
||||
@ -20,7 +18,7 @@ pub fn handle_keydown(
|
||||
|
||||
match virtual_keycode {
|
||||
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)?,
|
||||
@ -42,9 +40,7 @@ pub fn handle_keydown(
|
||||
}
|
||||
}
|
||||
|
||||
A | Home | End => {
|
||||
pass_keydown_to_focused(&modifiers, virtual_keycode, app_model, markup_node_pool)?
|
||||
}
|
||||
A | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use super::keyboard_input;
|
||||
use super::style::CODE_TXT_XY;
|
||||
use super::util::slice_get;
|
||||
use crate::editor::ed_error::print_ui_err;
|
||||
use crate::editor::resources::strings::NOTHING_OPENED;
|
||||
use crate::editor::slow_pool::SlowPool;
|
||||
@ -21,6 +20,7 @@ use crate::graphics::{
|
||||
use crate::lang::expr::Env;
|
||||
use crate::lang::pool::Pool;
|
||||
use crate::ui::ui_error::UIError::FileOpenFailed;
|
||||
use crate::ui::util::slice_get;
|
||||
use bumpalo::collections::String as BumpString;
|
||||
use bumpalo::Bump;
|
||||
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);
|
||||
|
||||
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) {
|
||||
Ok(file_as_str) => {
|
||||
code_str = BumpString::from_str_in(&file_as_str, &code_arena);
|
||||
file_path
|
||||
}
|
||||
|
||||
Err(e) => print_ui_err(&FileOpenFailed {
|
||||
path_str: file_path.to_string_lossy().to_string(),
|
||||
err_msg: e.to_string(),
|
||||
}),
|
||||
Err(e) => {
|
||||
print_ui_err(&FileOpenFailed {
|
||||
path_str: file_path.to_string_lossy().to_string(),
|
||||
err_msg: e.to_string(),
|
||||
});
|
||||
Path::new("")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Path::new("")
|
||||
};
|
||||
|
||||
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 {
|
||||
Ok(mut ed_model) => {
|
||||
@ -256,7 +268,6 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||
virtual_keycode,
|
||||
keyboard_modifiers,
|
||||
&mut app_model,
|
||||
&mut markup_node_pool,
|
||||
);
|
||||
|
||||
if let Err(e) = keydown_res {
|
||||
|
@ -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(())
|
||||
}
|
@ -1,3 +1,2 @@
|
||||
pub mod attribute;
|
||||
pub mod caret;
|
||||
pub mod nodes;
|
||||
|
@ -1,18 +1,13 @@
|
||||
use super::attribute::Attributes;
|
||||
use crate::editor::ed_error::GetContentOnNestedNode;
|
||||
use crate::editor::ed_error::NodeWithoutAttributes;
|
||||
use crate::editor::{
|
||||
ed_error::EdResult,
|
||||
slow_pool::{SlowNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
use crate::editor::slow_pool::SlowNodeId;
|
||||
use crate::editor::slow_pool::SlowPool;
|
||||
use crate::editor::syntax_highlight::HighlightStyle;
|
||||
use crate::lang::{
|
||||
ast::Expr2,
|
||||
expr::Env,
|
||||
pool::{NodeId, PoolStr},
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use snafu::OptionExt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MarkupNode {
|
||||
@ -38,117 +33,6 @@ pub enum MarkupNode {
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
Expr2::List { elems, .. } => {
|
||||
let mut children_ids = Vec::new();
|
||||
|
||||
children_ids.push(new_markup_node(
|
||||
let mut children_ids = vec![new_markup_node(
|
||||
"[ ".to_string(),
|
||||
node_id,
|
||||
HighlightStyle::Bracket,
|
||||
markup_node_pool,
|
||||
));
|
||||
)];
|
||||
|
||||
for (idx, node_id) in elems.iter_node_ids().enumerate() {
|
||||
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)
|
||||
}
|
||||
Expr2::Record { fields, .. } => {
|
||||
let mut children_ids = Vec::new();
|
||||
|
||||
children_ids.push(new_markup_node(
|
||||
let mut children_ids = vec![new_markup_node(
|
||||
"{ ".to_string(),
|
||||
node_id,
|
||||
HighlightStyle::Bracket,
|
||||
markup_node_pool,
|
||||
));
|
||||
)];
|
||||
|
||||
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);
|
||||
|
@ -1,3 +1,4 @@
|
||||
mod code_lines;
|
||||
mod config;
|
||||
mod ed_error;
|
||||
mod keyboard_input;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::app_model::AppModel;
|
||||
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 winit::event::{ModifiersState, VirtualKeyCode};
|
||||
|
||||
@ -38,13 +38,12 @@ pub fn pass_keydown_to_focused(
|
||||
modifiers_winit: &ModifiersState,
|
||||
virtual_keycode: VirtualKeyCode,
|
||||
app_model: &mut AppModel,
|
||||
markup_node_pool: &mut SlowPool,
|
||||
) -> EdResult<()> {
|
||||
let modifiers = from_winit(modifiers_winit);
|
||||
|
||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||
if ed_model.has_focus {
|
||||
ed_model.handle_key_down(&modifiers, virtual_keycode, markup_node_pool)?;
|
||||
ed_model.handle_key_down(&modifiers, virtual_keycode)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,39 +1,48 @@
|
||||
use crate::editor::code_lines::CodeLines;
|
||||
use crate::editor::slow_pool::{SlowNodeId, SlowPool};
|
||||
use crate::editor::syntax_highlight::HighlightStyle;
|
||||
use crate::editor::{
|
||||
ed_error::EdError::ParseError,
|
||||
ed_error::EdResult,
|
||||
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},
|
||||
};
|
||||
use crate::graphics::primitives::rect::Rect;
|
||||
use crate::lang::ast::Expr2;
|
||||
use crate::lang::expr::{str_to_expr2, Env};
|
||||
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 bumpalo::collections::String as BumpString;
|
||||
use bumpalo::Bump;
|
||||
use nonempty::NonEmpty;
|
||||
use roc_region::all::Region;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use winit::event::VirtualKeyCode;
|
||||
|
||||
pub type LeafIndex = usize;
|
||||
use VirtualKeyCode::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EdModel<'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 glyph_dim_rect_opt: Option<Rect>,
|
||||
pub has_focus: bool,
|
||||
// This HashSet may have less elements than there are carets. There can be multiple carets for a single node.
|
||||
caret_nodes: HashSet<(SlowNodeId, LeafIndex)>,
|
||||
dfs_ordered_leaves: Vec<SlowNodeId>,
|
||||
// Option<SlowNodeId>: MarkupNode that corresponds to caret position, Option because this SlowNodeId is only calculated when it needs to be used.
|
||||
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<SlowNodeId>)>,
|
||||
}
|
||||
|
||||
pub fn init_model<'a>(
|
||||
code_str: &'a BumpString,
|
||||
file_path: &'a Path,
|
||||
env: Env<'a>,
|
||||
code_arena: &'a Bump,
|
||||
markup_node_pool: &mut SlowPool,
|
||||
@ -65,62 +74,178 @@ pub fn init_model<'a>(
|
||||
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 {
|
||||
module,
|
||||
code_as_str: code_str,
|
||||
file_path,
|
||||
code_lines: CodeLines::from_str(code_str),
|
||||
markup_root_id,
|
||||
glyph_dim_rect_opt: None,
|
||||
has_focus: true,
|
||||
caret_nodes: vec![(node_w_caret_id, 0)].into_iter().collect(),
|
||||
dfs_ordered_leaves,
|
||||
caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)),
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a> EdModel<'a> {
|
||||
pub fn handle_key_down(
|
||||
pub fn move_caret(
|
||||
&mut self,
|
||||
_modifiers: &Modifiers,
|
||||
virtual_keycode: VirtualKeyCode,
|
||||
markup_node_pool: &mut SlowPool,
|
||||
) -> EdResult<()> {
|
||||
match virtual_keycode {
|
||||
VirtualKeyCode::Right => {
|
||||
let mut new_caret_nodes: Vec<(SlowNodeId, LeafIndex)> = Vec::new();
|
||||
move_fun: MoveCaretFun<CodeLines>,
|
||||
modifiers: &Modifiers,
|
||||
) -> UIResult<()> {
|
||||
for caret_tup in self.caret_w_select_vec.iter_mut() {
|
||||
caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
|
||||
caret_tup.1 = None;
|
||||
}
|
||||
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
new_caret_nodes.extend(move_carets_right_for_node(
|
||||
caret_node_id,
|
||||
*leaf_index,
|
||||
next_leaf_id_opt,
|
||||
markup_node_pool,
|
||||
)?);
|
||||
}
|
||||
impl<'a> SelectableLines for EdModel<'a> {
|
||||
fn get_caret(self) -> TextPos {
|
||||
self.caret_w_select_vec.first().0.caret_pos
|
||||
}
|
||||
|
||||
self.caret_nodes = new_caret_nodes.into_iter().collect();
|
||||
}
|
||||
VirtualKeyCode::Left => unimplemented!("TODO"),
|
||||
_ => (),
|
||||
};
|
||||
// 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(())
|
||||
}
|
||||
|
||||
pub fn get_next_leaf(&self, index: usize) -> Option<SlowNodeId> {
|
||||
self.dfs_ordered_leaves.get(index + 1).copied()
|
||||
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||
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(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
use super::ed_model::EdModel;
|
||||
use crate::editor::code_lines::CodeLines;
|
||||
use crate::editor::config::Config;
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::render_ast::build_code_graphics;
|
||||
use crate::editor::slow_pool::SlowPool;
|
||||
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 cgmath::Vector2;
|
||||
use snafu::OptionExt;
|
||||
@ -19,12 +22,62 @@ pub fn model_to_wgpu<'a>(
|
||||
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> {
|
||||
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),
|
||||
size,
|
||||
txt_coords,
|
||||
config,
|
||||
glyph_dim_rect,
|
||||
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)
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
use super::markup::attribute::{Attribute, Attributes};
|
||||
use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
|
||||
use crate::editor::slow_pool::SlowPool;
|
||||
use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get};
|
||||
use crate::graphics::primitives::rect::Rect;
|
||||
use crate::graphics::primitives::text as gr_text;
|
||||
use crate::ui::text::caret_w_select::make_caret_rect;
|
||||
use cgmath::Vector2;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
@ -67,40 +65,6 @@ fn markup_to_wgpu<'a>(
|
||||
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
|
||||
fn markup_to_wgpu_helper<'a>(
|
||||
markup_node: &'a MarkupNode,
|
||||
@ -132,7 +96,7 @@ fn markup_to_wgpu_helper<'a>(
|
||||
content,
|
||||
ast_node_id: _,
|
||||
syn_high_style,
|
||||
attributes,
|
||||
attributes: _,
|
||||
parent_id_opt: _,
|
||||
} => {
|
||||
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_scale(code_style.font_size);
|
||||
|
||||
rects.extend(draw_attributes(attributes, txt_row_col, code_style));
|
||||
txt_row_col.1 += content.len();
|
||||
wgpu_texts.push(glyph_text);
|
||||
}
|
||||
MarkupNode::Blank {
|
||||
ast_node_id: _,
|
||||
attributes,
|
||||
attributes: _,
|
||||
syn_high_style,
|
||||
parent_id_opt: _,
|
||||
} => {
|
||||
@ -171,8 +134,6 @@ fn markup_to_wgpu_helper<'a>(
|
||||
};
|
||||
rects.push(hole_rect);
|
||||
|
||||
rects.extend(draw_attributes(attributes, txt_row_col, code_style));
|
||||
|
||||
txt_row_col.1 += BLANK_PLACEHOLDER.len();
|
||||
wgpu_texts.push(glyph_text);
|
||||
}
|
||||
|
@ -1,18 +1,6 @@
|
||||
use super::ed_error::{EdResult, KeyNotFound, OutOfBounds};
|
||||
use super::ed_error::{EdResult, KeyNotFound};
|
||||
use snafu::OptionExt;
|
||||
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
|
||||
pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>(
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -809,7 +809,6 @@ pub fn to_expr2<'a>(
|
||||
// (RuntimeError(problem), Output::default())
|
||||
todo!()
|
||||
}
|
||||
Nested(sub_expr) => to_expr2(env, scope, sub_expr, 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
|
||||
|
@ -414,7 +414,7 @@ pub fn to_pattern2<'a>(
|
||||
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)
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![warn(clippy::all, clippy::dbg_macro)]
|
||||
// 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)]
|
||||
extern crate indoc;
|
||||
|
@ -1,4 +1,4 @@
|
||||
pub mod text;
|
||||
pub mod theme;
|
||||
pub mod ui_error;
|
||||
mod util;
|
||||
pub mod util;
|
||||
|
@ -4,8 +4,9 @@
|
||||
|
||||
use crate::ui::text::{
|
||||
caret_w_select::CaretWSelect,
|
||||
lines,
|
||||
lines::{Lines, MutSelectableLines, SelectableLines},
|
||||
selection::{validate_raw_sel, validate_selection, RawSelection, Selection},
|
||||
selection::{validate_raw_sel, RawSelection, Selection},
|
||||
text_pos::TextPos,
|
||||
};
|
||||
use crate::ui::ui_error::{
|
||||
@ -19,23 +20,17 @@ use bumpalo::collections::String as BumpString;
|
||||
use bumpalo::Bump;
|
||||
use ropey::Rope;
|
||||
use snafu::ensure;
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
fmt,
|
||||
fs::File,
|
||||
io,
|
||||
path::Path,
|
||||
};
|
||||
use std::{fmt, fs::File, io, path::Path};
|
||||
use winit::event::{VirtualKeyCode, VirtualKeyCode::*};
|
||||
|
||||
pub struct BigSelectableText {
|
||||
pub struct BigTextArea {
|
||||
pub caret_w_select: CaretWSelect,
|
||||
text_rope: Rope,
|
||||
pub path_str: String,
|
||||
arena: Bump,
|
||||
}
|
||||
|
||||
impl BigSelectableText {
|
||||
impl BigTextArea {
|
||||
fn check_bounds(&self, char_indx: usize) -> UIResult<()> {
|
||||
ensure!(
|
||||
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>> {
|
||||
Ok(Some(validate_selection(start_pos, end_pos)?))
|
||||
}
|
||||
|
||||
impl Lines for BigSelectableText {
|
||||
impl Lines for BigTextArea {
|
||||
fn get_line(&self, line_nr: usize) -> UIResult<&str> {
|
||||
ensure!(
|
||||
line_nr < self.nr_of_lines(),
|
||||
OutOfBounds {
|
||||
index: line_nr,
|
||||
collection_name: "BigSelectableText",
|
||||
collection_name: "BigTextArea",
|
||||
len: self.nr_of_lines(),
|
||||
}
|
||||
);
|
||||
@ -121,9 +112,13 @@ impl Lines for BigSelectableText {
|
||||
|
||||
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 {
|
||||
self.caret_w_select.caret_pos
|
||||
}
|
||||
@ -133,347 +128,39 @@ impl SelectableLines for BigSelectableText {
|
||||
}
|
||||
|
||||
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||
let old_selection_opt = self.get_selection();
|
||||
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);
|
||||
self.caret_w_select = lines::move_caret_left(self, self.caret_w_select, modifiers)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||
let old_selection_opt = self.get_selection();
|
||||
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);
|
||||
self.caret_w_select = lines::move_caret_right(self, self.caret_w_select, modifiers)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||
let old_selection_opt = self.get_selection();
|
||||
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);
|
||||
self.caret_w_select = lines::move_caret_up(self, self.caret_w_select, modifiers)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||
let old_selection_opt = self.get_selection();
|
||||
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);
|
||||
self.caret_w_select = lines::move_caret_down(self, self.caret_w_select, modifiers)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||
let curr_line_nr = self.caret_w_select.caret_pos.line;
|
||||
let old_col_nr = self.caret_w_select.caret_pos.column;
|
||||
self.caret_w_select = lines::move_caret_home(self, self.caret_w_select, modifiers)?;
|
||||
|
||||
let curr_line_str = self.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
|
||||
};
|
||||
|
||||
self.caret_w_select.move_caret_w_mods(
|
||||
TextPos {
|
||||
line: curr_line_nr,
|
||||
column: new_col_nr,
|
||||
},
|
||||
modifiers,
|
||||
)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||
let curr_line_nr = self.caret_w_select.caret_pos.line;
|
||||
let curr_line_len = self.line_len(curr_line_nr)?;
|
||||
self.caret_w_select = lines::move_caret_end(self, self.caret_w_select, modifiers)?;
|
||||
|
||||
let new_col = if let Some(last_char) = self.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,
|
||||
};
|
||||
|
||||
self.caret_w_select.move_caret_w_mods(new_pos, modifiers)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_selection(&self) -> Option<Selection> {
|
||||
@ -484,7 +171,7 @@ impl SelectableLines for BigSelectableText {
|
||||
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 {
|
||||
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);
|
||||
|
||||
if let Some(line_str_ref) = rope_slice.as_str() {
|
||||
Ok(Some(line_str_ref))
|
||||
Ok(Some(line_str_ref.to_string()))
|
||||
} else {
|
||||
// happens very rarely
|
||||
let line_str = rope_slice.chunks().collect::<String>();
|
||||
let arena_str_ref = self.arena.alloc(line_str);
|
||||
Ok(Some(arena_str_ref))
|
||||
Ok(Some(rope_slice.chunks().collect::<String>()))
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
@ -511,13 +195,17 @@ impl SelectableLines for BigSelectableText {
|
||||
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) {
|
||||
self.caret_w_select.selection_opt = None;
|
||||
}
|
||||
|
||||
fn select_all(&mut self) -> UIResult<()> {
|
||||
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 {
|
||||
start_pos: TextPos { line: 0, column: 0 },
|
||||
@ -530,12 +218,8 @@ impl SelectableLines for BigSelectableText {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn last_text_pos(&self) -> TextPos {
|
||||
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 last_text_pos(&self) -> UIResult<TextPos> {
|
||||
Ok(self.char_indx_to_pos(self.nr_of_chars()))
|
||||
}
|
||||
|
||||
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<()> {
|
||||
if self.is_selection_active() {
|
||||
self.del_selection()?;
|
||||
@ -658,7 +342,7 @@ impl MutSelectableLines for BigSelectableText {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BigSelectableText {
|
||||
impl Default for BigTextArea {
|
||||
fn default() -> Self {
|
||||
let caret_w_select = CaretWSelect::default();
|
||||
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 path_str = path_to_string(path);
|
||||
|
||||
Ok(BigSelectableText {
|
||||
Ok(BigTextArea {
|
||||
text_rope,
|
||||
path_str,
|
||||
..Default::default()
|
||||
@ -687,10 +371,10 @@ pub fn from_path(path: &Path) -> UIResult<BigSelectableText> {
|
||||
|
||||
#[allow(dead_code)]
|
||||
// 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);
|
||||
|
||||
BigSelectableText {
|
||||
BigTextArea {
|
||||
text_rope,
|
||||
..Default::default()
|
||||
}
|
||||
@ -723,9 +407,9 @@ fn rope_from_path(path: &Path) -> UIResult<Rope> {
|
||||
}
|
||||
|
||||
// need to explicitly omit arena
|
||||
impl fmt::Debug for BigSelectableText {
|
||||
impl fmt::Debug for BigTextArea {
|
||||
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("text_rope", &self.text_rope)
|
||||
.field("path_str", &self.path_str)
|
||||
@ -736,8 +420,8 @@ impl fmt::Debug for BigSelectableText {
|
||||
#[cfg(test)]
|
||||
pub mod test_big_sel_text {
|
||||
use crate::ui::text::{
|
||||
big_selectable_text::from_str,
|
||||
big_selectable_text::BigSelectableText,
|
||||
big_text_area::from_str,
|
||||
big_text_area::BigTextArea,
|
||||
caret_w_select::CaretWSelect,
|
||||
lines::{Lines, MutSelectableLines, SelectableLines},
|
||||
selection::validate_selection,
|
||||
@ -843,7 +527,7 @@ pub mod test_big_sel_text {
|
||||
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(
|
||||
&lines
|
||||
.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();
|
||||
|
||||
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 mut big_text = big_text_from_dsl_str(&lines_string_slice);
|
||||
let caret_w_select = convert_dsl_to_selection(&lines_string_slice).unwrap();
|
||||
@ -1141,7 +825,7 @@ pub mod test_big_sel_text {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
type MoveCaretFun = fn(&mut BigSelectableText, &Modifiers) -> UIResult<()>;
|
||||
type MoveCaretFun = fn(&mut BigTextArea, &Modifiers) -> UIResult<()>;
|
||||
|
||||
// Convert nice string representations and compare results
|
||||
fn assert_move(
|
||||
@ -1720,7 +1404,7 @@ pub mod test_big_sel_text {
|
||||
|
||||
#[test]
|
||||
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(&["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]
|
||||
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(&["|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]
|
||||
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(&["|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]
|
||||
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(&["|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]
|
||||
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)?;
|
||||
@ -3318,7 +3002,7 @@ pub mod test_big_sel_text {
|
||||
|
||||
#[test]
|
||||
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)?;
|
@ -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;
|
||||
|
||||
// one does not simply move the caret
|
||||
@ -75,10 +75,7 @@ impl CaretWSelect {
|
||||
None
|
||||
};
|
||||
|
||||
self.caret_pos = new_pos;
|
||||
self.selection_opt = valid_sel_opt;
|
||||
|
||||
Ok(())
|
||||
Ok(CaretWSelect::new(new_pos, valid_sel_opt))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,18 @@
|
||||
// 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::{
|
||||
selection::{RawSelection, Selection},
|
||||
text_pos::TextPos,
|
||||
};
|
||||
use crate::ui::ui_error::UIResult;
|
||||
use crate::ui::util::is_newline;
|
||||
use crate::window::keyboard_input::Modifiers;
|
||||
use bumpalo::collections::String as BumpString;
|
||||
use bumpalo::Bump;
|
||||
use std::cmp::max;
|
||||
use std::cmp::min;
|
||||
use winit::event::VirtualKeyCode;
|
||||
|
||||
pub trait Lines {
|
||||
@ -19,8 +24,9 @@ pub trait Lines {
|
||||
|
||||
fn nr_of_chars(&self) -> usize;
|
||||
|
||||
// TODO use pool allocation here
|
||||
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>;
|
||||
|
||||
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>>;
|
||||
}
|
||||
|
||||
pub trait SelectableLines {
|
||||
@ -44,17 +50,17 @@ pub trait SelectableLines {
|
||||
|
||||
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_sel_none(&mut self);
|
||||
|
||||
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect);
|
||||
|
||||
fn select_all(&mut self) -> UIResult<()>;
|
||||
|
||||
fn last_text_pos(&self) -> TextPos;
|
||||
|
||||
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>>;
|
||||
fn last_text_pos(&self) -> UIResult<TextPos>;
|
||||
|
||||
fn handle_key_down(
|
||||
&mut self,
|
||||
@ -75,3 +81,362 @@ pub trait MutSelectableLines {
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
pub mod big_selectable_text;
|
||||
pub mod big_text_area;
|
||||
pub mod caret_w_select;
|
||||
pub mod lines;
|
||||
pub mod selection;
|
||||
|
@ -34,6 +34,10 @@ pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult<Selection> {
|
||||
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> {
|
||||
ensure!(
|
||||
start_pos.line <= end_pos.line,
|
||||
|
@ -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 {
|
||||
let newline_codes = vec!['\u{d}', '\n'];
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -59,7 +59,10 @@ pub export fn main() u8 {
|
||||
|
||||
call_the_closure(function_pointer, closure_data_pointer);
|
||||
} 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;
|
||||
|
@ -502,11 +502,11 @@ pub enum RocCallResult<T> {
|
||||
Failure(*mut c_char),
|
||||
}
|
||||
|
||||
impl<T: Sized> Into<Result<T, &'static str>> for RocCallResult<T> {
|
||||
fn into(self) -> Result<T, &'static str> {
|
||||
impl<T: Sized> From<RocCallResult<T>> for Result<T, &'static str> {
|
||||
fn from(call_result: RocCallResult<T>) -> Self {
|
||||
use RocCallResult::*;
|
||||
|
||||
match self {
|
||||
match call_result {
|
||||
Success(value) => Ok(value),
|
||||
Failure(failure) => Err({
|
||||
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> {
|
||||
fn into(self) -> Result<T, &'a str> {
|
||||
impl<'a, T: Sized + Copy> From<&'a RocCallResult<T>> for Result<T, &'a str> {
|
||||
fn from(call_result: &'a RocCallResult<T>) -> Self {
|
||||
use RocCallResult::*;
|
||||
|
||||
match self {
|
||||
match call_result {
|
||||
Success(value) => Ok(*value),
|
||||
Failure(failure) => Err({
|
||||
let msg = unsafe {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user