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

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

View File

@ -8,8 +8,8 @@ To build the compiler, you need these installed:
* `libunwind` (macOS should already have this one installed)
* `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
View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use roc_cli::{build, build_app, repl, DIRECTORY_OR_FILES};
use roc_cli::{build, build_app, docs, repl, DIRECTORY_OR_FILES};
use std::io;
use std::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!(),
}
}

View File

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

View File

@ -57,7 +57,7 @@ pub fn gen_and_eval<'a>(
let mut loaded = match loaded {
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(),

View File

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

View File

@ -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`
##

View File

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

View File

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

View File

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

View File

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

View File

@ -430,6 +430,20 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// log : Float a -> Float a
let log_needs_positive = SolvedType::TagUnion(
vec![(TagName::Global("LogNeedsPositive".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_type(
Symbol::NUM_LOG,
top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), log_needs_positive)),
),
);
// round : Float a -> Int b
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,

View File

@ -65,6 +65,13 @@ impl IntroducedVariables {
}
}
fn malformed(env: &mut Env, region: Region, name: &str) {
use roc_problem::can::RuntimeError::*;
let problem = MalformedTypeName((*name).into(), region);
env.problem(roc_problem::can::Problem::RuntimeError(problem));
}
pub fn canonicalize_annotation(
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &current.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>,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, &copy_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 &gt;&gt;&gt; into code block for now.</p>\n<pre><code class=\"language-roc\">codeblock</code></pre>\n".to_string(),
},
];
for module in &package.modules {
let template = documentation_to_template_data(&package, module);
assert_eq!(template.module_name, "Test");
template
.module_entries
.iter()
.zip(expected_entries.iter())
.for_each(|(x, y)| assert_eq!(x, y));
}
}
}

View File

@ -66,16 +66,14 @@ a:hover {
}
.pkg-and-logo {
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;
}
}

View File

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

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ use crate::editor::mvc::app_model::AppModel;
use crate::editor::mvc::app_update::{
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)?,
_ => (),
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@ impl CaretWSelect {
}
}
pub fn move_caret_w_mods(&mut self, new_pos: TextPos, mods: &Modifiers) -> UIResult<()> {
pub fn move_caret_w_mods(&self, new_pos: TextPos, mods: &Modifiers) -> UIResult<CaretWSelect> {
let old_caret_pos = self.caret_pos;
// 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))
}
}

View File

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

View File

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

View File

@ -34,6 +34,10 @@ pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult<Selection> {
validate_selection(raw_sel.start_pos, raw_sel.end_pos)
}
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,

View File

@ -1,5 +1,20 @@
use super::ui_error::{OutOfBounds, UIResult};
use snafu::OptionExt;
use std::slice::SliceIndex;
pub fn is_newline(char_ref: &char) -> bool {
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)
}

View File

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

View File

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