Merge branch 'trunk' of github.com:rtfeldman/roc into input_record

This commit is contained in:
Anton-4 2021-03-27 19:17:17 +01:00
commit 5d43ffcbee
43 changed files with 1442 additions and 687 deletions

View file

@ -31,14 +31,12 @@ For any other OS, checkout the [Zig installation page](https://github.com/ziglan
### 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.
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 +116,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

View file

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

View file

@ -90,7 +90,11 @@ pub fn build_app<'a>() -> App<'a> {
}
pub fn docs(files: Vec<PathBuf>) {
roc_docs::generate(files, roc_builtins::std::standard_stdlib(), Path::new("./"))
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<()> {
@ -147,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

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

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

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

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

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

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;

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

@ -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.
@ -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,
)));
}
};
@ -4180,3 +4142,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

@ -424,6 +424,8 @@ impl<'a> Procs<'a> {
is_self_recursive: bool,
ret_var: Variable,
) {
let number_of_arguments = loc_args.len();
match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) {
Ok((_, pattern_symbols, body)) => {
// a named closure. Since these aren't specialized by the surrounding
@ -444,17 +446,22 @@ impl<'a> Procs<'a> {
}
Err(error) => {
// If the function has invalid patterns in its arguments,
// its call sites will code gen to runtime errors. This happens
// at the call site so we don't have to try to define the
// function LLVM, which would be difficult considering LLVM
// wants to know what symbols each argument corresponds to,
// and in this case the patterns were invalid, so we don't know
// what the symbols ought to be.
let mut pattern_symbols = Vec::with_capacity_in(number_of_arguments, env.arena);
let error_msg = format!("TODO generate a RuntimeError message for {:?}", error);
for _ in 0..number_of_arguments {
pattern_symbols.push(env.unique_symbol());
}
self.runtime_errors.insert(name, env.arena.alloc(error_msg));
self.partial_procs.insert(
name,
PartialProc {
annotation,
pattern_symbols: pattern_symbols.into_bump_slice(),
captured_symbols: CapturedSymbols::None,
body: roc_can::expr::Expr::RuntimeError(error.value),
is_self_recursive: false,
},
);
}
}
}
@ -1788,10 +1795,11 @@ fn specialize_external<'a>(
let snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.snapshot();
let unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
let _unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
debug_assert!(is_valid, "unificaton failure for {:?}", proc_name);
// This will not hold for programs with type errors
// let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
// debug_assert!(is_valid, "unificaton failure for {:?}", proc_name);
// if this is a closure, add the closure record argument
let pattern_symbols = if let CapturedSymbols::Captured(_) = captured_symbols {
@ -2131,9 +2139,7 @@ fn build_specialized_proc_adapter<'a>(
arg_layouts.push(layout);
}
let ret_layout = layout_cache
.from_var(&env.arena, ret_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
let ret_layout = layout_cache.from_var(&env.arena, ret_var, env.subs)?;
build_specialized_proc(
env.arena,

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;

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!();
}

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)
}
]
}
@ -260,25 +289,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)
@ -286,13 +309,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();
@ -309,14 +332,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)
}
}
}
@ -688,7 +704,7 @@ struct DefState<'a> {
}
fn parse_defs_end<'a>(
multi_backpassing: MultiBackpassing,
options: ExprParseOptions,
start: Position,
mut def_state: DefState<'a>,
arena: &'a Bump,
@ -743,7 +759,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(
@ -765,7 +781,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)),
@ -774,7 +790,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,
@ -782,7 +798,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
@ -815,7 +831,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>,
@ -835,8 +851,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(
@ -859,14 +874,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;
@ -915,7 +923,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;
@ -1062,11 +1070,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();
@ -1104,14 +1110,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)
}
}
}
@ -1124,7 +1123,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,
@ -1132,7 +1131,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) {
@ -1163,14 +1162,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)
}
}
}
@ -1183,19 +1175,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;
@ -1256,6 +1241,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;
@ -1273,7 +1264,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>(
@ -1281,17 +1280,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.
@ -1443,8 +1450,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)?;
@ -1482,7 +1494,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!(
@ -1510,7 +1522,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,
@ -1535,7 +1547,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!(
@ -1543,7 +1555,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,
@ -1566,7 +1578,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),
@ -1586,22 +1598,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 {
@ -1613,19 +1630,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;
@ -1655,40 +1674,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!(
@ -1696,7 +1711,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,
@ -1711,22 +1726,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)
}
}
@ -1789,7 +1885,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)?;
@ -1821,7 +1917,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

@ -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)
@ -4964,7 +4967,6 @@ mod test_reporting {
#[test]
fn empty_or_pattern() {
// this should get better with time
report_problem_as(
indoc!(
r#"
@ -4978,29 +4980,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
// "#
// ),
)
}
@ -5132,29 +5121,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!
"#
@ -5806,21 +5877,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

@ -2258,3 +2258,21 @@ 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
);
}

View file

@ -74,7 +74,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!();
}

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

@ -56,6 +56,29 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
modules: files_docs,
};
if !build_dir.exists() {
fs::create_dir_all(build_dir).expect("TODO gracefully handle unable to create build dir");
}
// 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");
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
@ -75,7 +98,7 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
.expect("TODO gracefully handle writing file failing");
}
println!("Docs generated at {}", build_dir.display());
println!("🎉 Docs generated in {}", build_dir.display());
}
pub fn files_to_documentations(
@ -99,7 +122,7 @@ pub fn files_to_documentations(
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!();
}

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>

View file

@ -1,5 +1,5 @@
use roc_docs::{documentation_to_template_data, files_to_documentations, ModuleEntry};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
#[cfg(test)]
mod test_docs {

View file

@ -195,14 +195,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);
@ -234,14 +232,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(
LEFT_ACCOLADE.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,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

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

View file

@ -31,6 +31,7 @@
//! The best way to see how it is used is to read the `tests.rs` file;
//! search for e.g. `UnitKey`.
use std::cmp::Ordering;
use std::fmt::{self, Debug};
use std::marker;
use std::ops::Range;
@ -361,17 +362,23 @@ impl<S: UnificationStore> UnificationTable<S> {
}
};
self.redirect_root(new_rank, redirected, new_root, new_value);
} else if rank_a > rank_b {
// a has greater rank, so a should become b's parent,
// i.e., b should redirect to a.
self.redirect_root(rank_a, key_b, key_a, new_value);
} else if rank_a < rank_b {
// b has greater rank, so a should redirect to b.
self.redirect_root(rank_b, key_a, key_b, new_value);
} else {
// If equal, redirect one to the other and increment the
// other's rank.
self.redirect_root(rank_a + 1, key_a, key_b, new_value);
match rank_a.cmp(&rank_b) {
Ordering::Greater => {
// a has greater rank, so a should become b's parent,
// i.e., b should redirect to a.
self.redirect_root(rank_a, key_b, key_a, new_value);
}
Ordering::Less => {
// b has greater rank, so a should redirect to b.
self.redirect_root(rank_b, key_a, key_b, new_value);
}
Ordering::Equal => {
// If equal, redirect one to the other and increment the
// other's rank.
self.redirect_root(rank_a + 1, key_a, key_b, new_value);
}
}
}
}