Merge branch 'trunk' into bytes

This commit is contained in:
Folkert de Vries 2021-02-26 12:14:52 +01:00 committed by GitHub
commit 869d3d18d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1036 additions and 477 deletions

1
Cargo.lock generated
View file

@ -2950,6 +2950,7 @@ dependencies = [
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"tempfile", "tempfile",
"ven_pretty",
] ]
[[package]] [[package]]

View file

@ -135,6 +135,9 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
Err(LoadingProblem::ParsingFailedReport(report)) => { Err(LoadingProblem::ParsingFailedReport(report)) => {
print!("{}", report); print!("{}", report);
} }
Err(LoadingProblem::NoPlatform(report)) => {
print!("{}", report);
}
Err(other) => { Err(other) => {
panic!("build_file failed with error:\n{:?}", other); panic!("build_file failed with error:\n{:?}", other);
} }

View file

@ -230,9 +230,8 @@ mod cli_run {
#[test] #[test]
#[serial(astar)] #[serial(astar)]
fn run_astar_optimized_1() { fn run_astar_optimized_1() {
check_output_with_stdin( check_output(
&example_file("benchmarks", "TestAStar.roc"), &example_file("benchmarks", "TestAStar.roc"),
"1",
"test-astar", "test-astar",
&[], &[],
"True\n", "True\n",

View file

@ -674,9 +674,14 @@ pub fn canonicalize_expr<'a>(
Output::default(), Output::default(),
) )
} }
ast::Expr::If(cond, then_branch, else_branch) => { ast::Expr::If(if_thens, final_else_branch) => {
let (loc_cond, mut output) = let mut branches = Vec::with_capacity(1);
canonicalize_expr(env, var_store, scope, cond.region, &cond.value); let mut output = Output::default();
for (condition, then_branch) in if_thens.iter() {
let (loc_cond, cond_output) =
canonicalize_expr(env, var_store, scope, condition.region, &condition.value);
let (loc_then, then_output) = canonicalize_expr( let (loc_then, then_output) = canonicalize_expr(
env, env,
var_store, var_store,
@ -684,22 +689,28 @@ pub fn canonicalize_expr<'a>(
then_branch.region, then_branch.region,
&then_branch.value, &then_branch.value,
); );
branches.push((loc_cond, loc_then));
output.references = output.references.union(cond_output.references);
output.references = output.references.union(then_output.references);
}
let (loc_else, else_output) = canonicalize_expr( let (loc_else, else_output) = canonicalize_expr(
env, env,
var_store, var_store,
scope, scope,
else_branch.region, final_else_branch.region,
&else_branch.value, &final_else_branch.value,
); );
output.references = output.references.union(then_output.references);
output.references = output.references.union(else_output.references); output.references = output.references.union(else_output.references);
( (
If { If {
cond_var: var_store.fresh(), cond_var: var_store.fresh(),
branch_var: var_store.fresh(), branch_var: var_store.fresh(),
branches: vec![(loc_cond, loc_then)], branches,
final_else: Box::new(loc_else), final_else: Box::new(loc_else),
}, },
output, output,

View file

@ -290,16 +290,21 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
}), }),
) )
} }
If(condition, then_branch, else_branch) If(if_thens, final_else_branch) | Nested(If(if_thens, final_else_branch)) => {
| Nested(If(condition, then_branch, else_branch)) => { // If does not get desugared into `when` so we can give more targetted error messages during type checking.
// If does not get desugared yet so we can give more targetted error messages during let desugared_final_else = &*arena.alloc(desugar_expr(arena, &final_else_branch));
// type checking.
let desugared_cond = &*arena.alloc(desugar_expr(arena, &condition)); let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena);
let desugared_then = &*arena.alloc(desugar_expr(arena, &then_branch));
let desugared_else = &*arena.alloc(desugar_expr(arena, &else_branch)); for (condition, then_branch) in if_thens.iter() {
desugared_if_thens.push((
desugar_expr(arena, condition).clone(),
desugar_expr(arena, then_branch).clone(),
));
}
arena.alloc(Located { arena.alloc(Located {
value: If(desugared_cond, desugared_then, desugared_else), value: If(desugared_if_thens.into_bump_slice(), desugared_final_else),
region: loc_expr.region, region: loc_expr.region,
}) })
} }

View file

@ -58,8 +58,11 @@ impl<'a> Formattable<'a> for Expr<'a> {
loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline()) loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline())
} }
If(loc_cond, loc_if_true, loc_if_false) => { If(branches, final_else) => {
loc_cond.is_multiline() || loc_if_true.is_multiline() || loc_if_false.is_multiline() final_else.is_multiline()
|| branches
.iter()
.any(|(c, t)| c.is_multiline() || t.is_multiline())
} }
BinOp((loc_left, _, loc_right)) => { BinOp((loc_left, _, loc_right)) => {
@ -257,8 +260,8 @@ impl<'a> Formattable<'a> for Expr<'a> {
// still print the return value. // still print the return value.
ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
} }
If(loc_condition, loc_then, loc_else) => { If(branches, final_else) => {
fmt_if(buf, loc_condition, loc_then, loc_else, indent); fmt_if(buf, branches, final_else, self.is_multiline(), indent);
} }
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent), When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
List { List {
@ -629,15 +632,15 @@ fn fmt_when<'a>(
fn fmt_if<'a>( fn fmt_if<'a>(
buf: &mut String<'a>, buf: &mut String<'a>,
loc_condition: &'a Located<Expr<'a>>, branches: &'a [(Located<Expr<'a>>, Located<Expr<'a>>)],
loc_then: &'a Located<Expr<'a>>, final_else: &'a Located<Expr<'a>>,
loc_else: &'a Located<Expr<'a>>, is_multiline: bool,
indent: u16, indent: u16,
) { ) {
let is_multiline_then = loc_then.is_multiline(); // let is_multiline_then = loc_then.is_multiline();
let is_multiline_else = loc_else.is_multiline(); // let is_multiline_else = final_else.is_multiline();
let is_multiline_condition = loc_condition.is_multiline(); // let is_multiline_condition = loc_condition.is_multiline();
let is_multiline = is_multiline_then || is_multiline_else || is_multiline_condition; // let is_multiline = is_multiline_then || is_multiline_else || is_multiline_condition;
let return_indent = if is_multiline { let return_indent = if is_multiline {
indent + INDENT indent + INDENT
@ -645,6 +648,9 @@ fn fmt_if<'a>(
indent indent
}; };
for (loc_condition, loc_then) in branches.iter() {
let is_multiline_condition = loc_condition.is_multiline();
buf.push_str("if"); buf.push_str("if");
if is_multiline_condition { if is_multiline_condition {
@ -703,7 +709,12 @@ fn fmt_if<'a>(
Expr::SpaceAfter(expr_above, spaces_above) => { Expr::SpaceAfter(expr_above, spaces_above) => {
expr_above.format(buf, return_indent); expr_above.format(buf, return_indent);
fmt_comments_only(buf, spaces_above.iter(), NewlineAt::Top, return_indent); fmt_comments_only(
buf,
spaces_above.iter(),
NewlineAt::Top,
return_indent,
);
newline(buf, indent); newline(buf, indent);
} }
@ -720,6 +731,7 @@ fn fmt_if<'a>(
buf.push_str(" "); buf.push_str(" ");
loc_then.format(buf, return_indent); loc_then.format(buf, return_indent);
} }
}
if is_multiline { if is_multiline {
buf.push_str("else"); buf.push_str("else");
@ -728,7 +740,7 @@ fn fmt_if<'a>(
buf.push_str(" else "); buf.push_str(" else ");
} }
loc_else.format(buf, return_indent); final_else.format(buf, return_indent);
} }
pub fn fmt_closure<'a>( pub fn fmt_closure<'a>(

View file

@ -19,6 +19,7 @@ roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../reporting" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1" inlinable_string = "0.1"
parking_lot = { version = "0.11", features = ["deadlock_detection"] } parking_lot = { version = "0.11", features = ["deadlock_detection"] }

View file

@ -631,6 +631,7 @@ struct ModuleHeader<'a> {
module_id: ModuleId, module_id: ModuleId,
module_name: ModuleNameEnum<'a>, module_name: ModuleNameEnum<'a>,
module_path: PathBuf, module_path: PathBuf,
is_root_module: bool,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
deps_by_name: MutMap<PQModuleName<'a>, ModuleId>, deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
packages: MutMap<&'a str, PackageOrPath<'a>>, packages: MutMap<&'a str, PackageOrPath<'a>>,
@ -781,6 +782,14 @@ enum Msg<'a> {
FailedToParse(ParseProblem<'a, SyntaxError<'a>>), FailedToParse(ParseProblem<'a, SyntaxError<'a>>),
} }
#[derive(Debug)]
enum PlatformPath<'a> {
NotSpecified,
Valid(To<'a>),
RootIsInterface,
RootIsPkgConfig,
}
#[derive(Debug)] #[derive(Debug)]
struct State<'a> { struct State<'a> {
pub root_id: ModuleId, pub root_id: ModuleId,
@ -789,7 +798,7 @@ struct State<'a> {
pub stdlib: &'a StdLib, pub stdlib: &'a StdLib,
pub exposed_types: SubsByModule, pub exposed_types: SubsByModule,
pub output_path: Option<&'a str>, pub output_path: Option<&'a str>,
pub platform_path: Option<To<'a>>, pub platform_path: PlatformPath<'a>,
pub headers_parsed: MutSet<ModuleId>, pub headers_parsed: MutSet<ModuleId>,
@ -994,6 +1003,8 @@ pub enum LoadingProblem<'a> {
}, },
ParsingFailed(ParseProblem<'a, SyntaxError<'a>>), ParsingFailed(ParseProblem<'a, SyntaxError<'a>>),
UnexpectedHeader(String), UnexpectedHeader(String),
/// there is no platform (likely running an Interface module)
NoPlatform(String),
MsgChannelDied, MsgChannelDied,
ErrJoiningWorkerThreads, ErrJoiningWorkerThreads,
@ -1147,6 +1158,7 @@ impl<'a> LoadStart<'a> {
load_filename( load_filename(
arena, arena,
filename, filename,
true,
None, None,
Arc::clone(&arc_modules), Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module), Arc::clone(&ident_ids_by_module),
@ -1415,7 +1427,7 @@ where
goal_phase, goal_phase,
stdlib, stdlib,
output_path: None, output_path: None,
platform_path: None, platform_path: PlatformPath::NotSpecified,
module_cache: ModuleCache::default(), module_cache: ModuleCache::default(),
dependencies: Dependencies::default(), dependencies: Dependencies::default(),
procedures: MutMap::default(), procedures: MutMap::default(),
@ -1492,7 +1504,7 @@ where
state, state,
subs, subs,
exposed_to_host, exposed_to_host,
))); )?));
} }
Msg::FailedToParse(problem) => { Msg::FailedToParse(problem) => {
// Shut down all the worker threads. // Shut down all the worker threads.
@ -1623,16 +1635,25 @@ fn update<'a>(
match header_extra { match header_extra {
App { to_platform } => { App { to_platform } => {
debug_assert_eq!(state.platform_path, None); debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::Valid(to_platform.clone());
state.platform_path = Some(to_platform.clone());
} }
PkgConfig { .. } => { PkgConfig { .. } => {
debug_assert_eq!(state.platform_id, None); debug_assert_eq!(state.platform_id, None);
state.platform_id = Some(header.module_id); state.platform_id = Some(header.module_id);
if header.is_root_module {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::RootIsPkgConfig;
}
}
Interface => {
if header.is_root_module {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::RootIsInterface;
}
} }
Interface => {}
} }
// store an ID to name mapping, so we know the file to read when fetching dependencies' headers // store an ID to name mapping, so we know the file to read when fetching dependencies' headers
@ -2070,7 +2091,7 @@ fn finish_specialization(
state: State, state: State,
subs: Subs, subs: Subs,
exposed_to_host: MutMap<Symbol, Variable>, exposed_to_host: MutMap<Symbol, Variable>,
) -> MonomorphizedModule { ) -> Result<MonomorphizedModule, LoadingProblem> {
let 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")) .unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
.into_inner() .into_inner()
@ -2097,21 +2118,89 @@ fn finish_specialization(
.. ..
} = module_cache; } = module_cache;
let sources = sources let sources: MutMap<ModuleId, (PathBuf, Box<str>)> = sources
.into_iter() .into_iter()
.map(|(id, (path, src))| (id, (path, src.into()))) .map(|(id, (path, src))| (id, (path, src.into())))
.collect(); .collect();
let path_to_platform = { let path_to_platform = {
use PlatformPath::*;
let package_or_path = match platform_path { let package_or_path = match platform_path {
Some(To::ExistingPackage(shorthand)) => { Valid(To::ExistingPackage(shorthand)) => {
match (*state.arc_shorthands).lock().get(shorthand) { match (*state.arc_shorthands).lock().get(shorthand) {
Some(p_or_p) => p_or_p.clone(), Some(p_or_p) => p_or_p.clone(),
None => unreachable!(), None => unreachable!(),
} }
} }
Some(To::NewPackage(p_or_p)) => p_or_p, Valid(To::NewPackage(p_or_p)) => p_or_p,
None => panic!("no platform!"), 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));
}
}; };
match package_or_path { match package_or_path {
@ -2123,7 +2212,7 @@ fn finish_specialization(
let platform_path = path_to_platform.into(); let platform_path = path_to_platform.into();
MonomorphizedModule { Ok(MonomorphizedModule {
can_problems, can_problems,
mono_problems, mono_problems,
type_problems, type_problems,
@ -2136,7 +2225,7 @@ fn finish_specialization(
procedures, procedures,
sources, sources,
timings: state.timings, timings: state.timings,
} })
} }
fn finish( fn finish(
@ -2319,6 +2408,7 @@ fn load_module<'a>(
load_filename( load_filename(
arena, arena,
filename, filename,
false,
opt_shorthand, opt_shorthand,
module_ids, module_ids,
ident_ids_by_module, ident_ids_by_module,
@ -2358,6 +2448,7 @@ fn parse_header<'a>(
arena: &'a Bump, arena: &'a Bump,
read_file_duration: Duration, read_file_duration: Duration,
filename: PathBuf, filename: PathBuf,
is_root_module: bool,
opt_shorthand: Option<&'a str>, opt_shorthand: Option<&'a str>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>, module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -2383,6 +2474,7 @@ fn parse_header<'a>(
value: ModuleNameEnum::Interface(header.name.value), value: ModuleNameEnum::Interface(header.name.value),
}, },
filename, filename,
is_root_module,
opt_shorthand, opt_shorthand,
&[], &[],
header.exposes.into_bump_slice(), header.exposes.into_bump_slice(),
@ -2405,6 +2497,7 @@ fn parse_header<'a>(
value: ModuleNameEnum::App(header.name.value), value: ModuleNameEnum::App(header.name.value),
}, },
filename, filename,
is_root_module,
opt_shorthand, opt_shorthand,
packages, packages,
header.provides.into_bump_slice(), header.provides.into_bump_slice(),
@ -2507,9 +2600,11 @@ fn parse_header<'a>(
} }
/// Load a module by its filename /// Load a module by its filename
#[allow(clippy::too_many_arguments)]
fn load_filename<'a>( fn load_filename<'a>(
arena: &'a Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
is_root_module: bool,
opt_shorthand: Option<&'a str>, opt_shorthand: Option<&'a str>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>, module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -2525,6 +2620,7 @@ fn load_filename<'a>(
arena, arena,
file_io_duration, file_io_duration,
filename, filename,
is_root_module,
opt_shorthand, opt_shorthand,
module_ids, module_ids,
ident_ids_by_module, ident_ids_by_module,
@ -2559,6 +2655,7 @@ fn load_from_str<'a>(
arena, arena,
file_io_duration, file_io_duration,
filename, filename,
false,
None, None,
module_ids, module_ids,
ident_ids_by_module, ident_ids_by_module,
@ -2580,6 +2677,7 @@ enum ModuleNameEnum<'a> {
fn send_header<'a>( fn send_header<'a>(
loc_name: Located<ModuleNameEnum<'a>>, loc_name: Located<ModuleNameEnum<'a>>,
filename: PathBuf, filename: PathBuf,
is_root_module: bool,
opt_shorthand: Option<&'a str>, opt_shorthand: Option<&'a str>,
packages: &'a [Located<PackageEntry<'a>>], packages: &'a [Located<PackageEntry<'a>>],
exposes: &'a [Located<ExposesEntry<'a, &'a str>>], exposes: &'a [Located<ExposesEntry<'a, &'a str>>],
@ -2766,6 +2864,7 @@ fn send_header<'a>(
ModuleHeader { ModuleHeader {
module_id: home, module_id: home,
module_path: filename, module_path: filename,
is_root_module,
exposed_ident_ids: ident_ids, exposed_ident_ids: ident_ids,
module_name: loc_name.value, module_name: loc_name.value,
packages: package_entries, packages: package_entries,
@ -2787,6 +2886,7 @@ fn send_header<'a>(
fn send_header_two<'a>( fn send_header_two<'a>(
arena: &'a Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
is_root_module: bool,
shorthand: &'a str, shorthand: &'a str,
app_module_id: ModuleId, app_module_id: ModuleId,
packages: &'a [Located<PackageEntry<'a>>], packages: &'a [Located<PackageEntry<'a>>],
@ -2983,6 +3083,7 @@ fn send_header_two<'a>(
ModuleHeader { ModuleHeader {
module_id: home, module_id: home,
module_path: filename, module_path: filename,
is_root_module,
exposed_ident_ids: ident_ids, exposed_ident_ids: ident_ids,
module_name, module_name,
packages: package_entries, packages: package_entries,
@ -3126,6 +3227,7 @@ fn fabricate_pkg_config_module<'a>(
send_header_two( send_header_two(
arena, arena,
filename, filename,
false,
shorthand, shorthand,
app_module_id, app_module_id,
&[], &[],

View file

@ -127,7 +127,7 @@ pub enum Expr<'a> {
UnaryOp(&'a Loc<Expr<'a>>, Loc<UnaryOp>), UnaryOp(&'a Loc<Expr<'a>>, Loc<UnaryOp>),
// Conditionals // Conditionals
If(&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>), If(&'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)], &'a Loc<Expr<'a>>),
When( When(
/// The condition /// The condition
&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>,

View file

@ -60,11 +60,12 @@ where
) )
} }
pub fn space0_around_e<'a, P, S, E>( pub fn space0_around_ee<'a, P, S, E>(
parser: P, parser: P,
min_indent: u16, min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E, space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E, indent_before_problem: fn(Row, Col) -> E,
indent_after_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Located<S>, E> ) -> impl Parser<'a, Located<S>, E>
where where
S: Spaceable<'a>, S: Spaceable<'a>,
@ -75,8 +76,11 @@ where
{ {
parser::map_with_arena( parser::map_with_arena(
and( and(
space0_e(min_indent, space_problem, indent_problem), space0_e(min_indent, space_problem, indent_before_problem),
and(parser, space0_e(min_indent, space_problem, indent_problem)), and(
parser,
space0_e(min_indent, space_problem, indent_after_problem),
),
), ),
move |arena: &'a Bump, move |arena: &'a Bump,
tuples: ( tuples: (

View file

@ -2,8 +2,8 @@ use crate::ast::{
AssignedField, Attempting, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation, AssignedField, Attempting, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation,
}; };
use crate::blankspace::{ use crate::blankspace::{
line_comment, space0, space0_after, space0_after_e, space0_around, space0_around_e, line_comment, space0, space0_after, space0_after_e, space0_around, space0_around_ee,
space0_before, space0_before_e, space0_e, space1, space1_around, space1_before, spaces_exactly, space0_before, space0_before_e, space0_e, space1, space1_before, spaces_exactly,
}; };
use crate::ident::{global_tag_or_ident, ident, lowercase_ident, Ident}; use crate::ident::{global_tag_or_ident, ident, lowercase_ident, Ident};
use crate::keyword; use crate::keyword;
@ -11,8 +11,8 @@ use crate::number_literal::number_literal;
use crate::parser::{ use crate::parser::{
self, allocated, and_then_with_indent_level, ascii_char, ascii_string, attempt, backtrackable, self, allocated, and_then_with_indent_level, ascii_char, ascii_string, attempt, backtrackable,
fail, map, newline_char, not, not_followed_by, optional, sep_by1, specialize, specialize_ref, fail, map, newline_char, not, not_followed_by, optional, sep_by1, specialize, specialize_ref,
then, unexpected, unexpected_eof, word1, word2, EExpr, Either, ParseResult, Parser, State, then, unexpected, unexpected_eof, word1, word2, BadInputError, EExpr, Either, If, List,
SyntaxError, When, ParseResult, Parser, State, SyntaxError, When,
}; };
use crate::pattern::loc_closure_param; use crate::pattern::loc_closure_param;
use crate::type_annotation; use crate::type_annotation;
@ -324,7 +324,7 @@ pub fn expr_to_pattern<'a>(
| Expr::Closure(_, _) | Expr::Closure(_, _)
| Expr::BinOp(_) | Expr::BinOp(_)
| Expr::Defs(_, _) | Expr::Defs(_, _)
| Expr::If(_, _, _) | Expr::If(_, _)
| Expr::When(_, _) | Expr::When(_, _)
| Expr::MalformedClosure | Expr::MalformedClosure
| Expr::PrecedenceConflict(_, _, _, _) | Expr::PrecedenceConflict(_, _, _, _)
@ -1029,14 +1029,15 @@ mod when {
and!( and!(
when_with_indent(), when_with_indent(),
skip_second!( skip_second!(
space0_around_e( space0_around_ee(
loc!(specialize_ref( loc!(specialize_ref(
When::Syntax, When::Syntax,
move |arena, state| parse_expr(min_indent, arena, state) move |arena, state| parse_expr(min_indent, arena, state)
)), )),
min_indent, min_indent,
When::Space, When::Space,
When::IndentCondition When::IndentCondition,
When::IndentIs,
), ),
parser::keyword_e(keyword::IS, When::Is) parser::keyword_e(keyword::IS, When::Is)
) )
@ -1182,13 +1183,14 @@ mod when {
skip_first!( skip_first!(
parser::keyword_e(keyword::IF, When::IfToken), parser::keyword_e(keyword::IF, When::IfToken),
// TODO we should require space before the expression but not after // TODO we should require space before the expression but not after
space0_around_e( space0_around_ee(
loc!(specialize_ref(When::IfGuard, move |arena, state| { loc!(specialize_ref(When::IfGuard, move |arena, state| {
parse_expr(min_indent, arena, state) parse_expr(min_indent, arena, state)
})), })),
min_indent, min_indent,
When::Space, When::Space,
When::IndentIfGuard, When::IndentIfGuard,
When::IndentArrow,
) )
), ),
Some Some
@ -1234,41 +1236,100 @@ mod when {
} }
} }
pub fn if_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> { fn if_branch<'a>(
map_with_arena!( min_indent: u16,
and!( ) -> impl Parser<'a, (Located<Expr<'a>>, Located<Expr<'a>>), If<'a>> {
skip_first!( move |arena, state| {
parser::keyword(keyword::IF, min_indent), // NOTE: only parse spaces before the expression
space1_around( let (_, cond, state) = space0_around_ee(
specialize_ref(
If::Syntax,
loc!(move |arena, state| parse_expr(min_indent, arena, state)), loc!(move |arena, state| parse_expr(min_indent, arena, state)),
min_indent,
)
), ),
and!( min_indent,
skip_first!( If::Space,
parser::keyword(keyword::THEN, min_indent), If::IndentCondition,
space1_around( If::IndentThenToken,
)
.parse(arena, state)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
let (_, _, state) = parser::keyword_e(keyword::THEN, If::Then)
.parse(arena, state)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
let (_, then_branch, state) = space0_around_ee(
specialize_ref(
If::Syntax,
loc!(move |arena, state| parse_expr(min_indent, arena, state)), loc!(move |arena, state| parse_expr(min_indent, arena, state)),
min_indent,
)
), ),
skip_first!(
parser::keyword(keyword::ELSE, min_indent),
// NOTE changed this from space1_around to space1_before
space1_before(
loc!(move |arena, state| parse_expr(min_indent, arena, state)),
min_indent, min_indent,
If::Space,
If::IndentThenBranch,
If::IndentElseToken,
) )
) .parse(arena, state)
) .map_err(|(_, f, s)| (MadeProgress, f, s))?;
),
|arena: &'a Bump, (condition, (then_branch, else_branch))| { let (_, _, state) = parser::keyword_e(keyword::ELSE, If::Else)
Expr::If( .parse(arena, state)
&*arena.alloc(condition), .map_err(|(_, f, s)| (MadeProgress, f, s))?;
&*arena.alloc(then_branch),
&*arena.alloc(else_branch), Ok((MadeProgress, (cond, then_branch), state))
)
} }
}
pub fn if_expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, If<'a>> {
move |arena: &'a Bump, state| {
let (_, _, state) = parser::keyword_e(keyword::IF, If::If).parse(arena, state)?;
let mut branches = Vec::with_capacity_in(1, arena);
let mut loop_state = state;
let state_final_else = loop {
let (_, (cond, then_branch), state) = if_branch(min_indent).parse(arena, loop_state)?;
branches.push((cond, then_branch));
// try to parse another `if`
// NOTE this drops spaces between the `else` and the `if`
let optional_if = and!(
backtrackable(space0_e(min_indent, If::Space, If::IndentIf)),
parser::keyword_e(keyword::IF, If::If)
);
match optional_if.parse(arena, state) {
Err((_, _, state)) => break state,
Ok((_, _, state)) => {
loop_state = state;
continue;
}
}
};
let (_, else_branch, state) = space0_before_e(
specialize_ref(
If::Syntax,
loc!(move |arena, state| parse_expr(min_indent, arena, state)),
),
min_indent,
If::Space,
If::IndentElseBranch,
)
.parse(arena, state_final_else)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
let expr = Expr::If(branches.into_bump_slice(), arena.alloc(else_branch));
Ok((MadeProgress, expr, state))
}
}
pub fn if_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> {
specialize(
|e, r, c| SyntaxError::Expr(EExpr::If(e, r, c)),
if_expr_help(min_indent),
) )
} }
@ -1632,35 +1693,40 @@ fn binop<'a>() -> impl Parser<'a, BinOp, SyntaxError<'a>> {
map!(ascii_char(b'%'), |_| BinOp::Percent) map!(ascii_char(b'%'), |_| BinOp::Percent)
) )
} }
fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> {
specialize(
|e, r, c| SyntaxError::Expr(EExpr::List(e, r, c)),
list_literal_help(min_indent),
)
}
pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> { fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, List<'a>> {
let elems = collection_trailing_sep!( move |arena, state| {
ascii_char(b'['), let (_, (parsed_elems, final_comments), state) = collection_trailing_sep_e!(
loc!(expr(min_indent)), word1(b'[', List::Open),
ascii_char(b','), specialize_ref(List::Syntax, loc!(expr(min_indent))),
ascii_char(b']'), word1(b',', List::End),
min_indent word1(b']', List::End),
); min_indent,
List::Open,
List::Space,
List::IndentEnd
)
.parse(arena, state)?;
parser::attempt(
Attempting::List,
map_with_arena!(elems, |arena,
(parsed_elems, final_comments): (
Vec<'a, Located<Expr<'a>>>,
&'a [CommentOrNewline<'a>]
)| {
let mut allocated = Vec::with_capacity_in(parsed_elems.len(), arena); let mut allocated = Vec::with_capacity_in(parsed_elems.len(), arena);
for parsed_elem in parsed_elems { for parsed_elem in parsed_elems {
allocated.push(&*arena.alloc(parsed_elem)); allocated.push(&*arena.alloc(parsed_elem));
} }
Expr::List { let expr = Expr::List {
items: allocated.into_bump_slice(), items: allocated.into_bump_slice(),
final_comments, final_comments,
};
Ok((MadeProgress, expr, state))
} }
}),
)
} }
// Parser<'a, Vec<'a, Located<AssignedField<'a, S>>>> // Parser<'a, Vec<'a, Located<AssignedField<'a, S>>>>

View file

@ -378,12 +378,28 @@ pub enum EExpr<'a> {
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
When(When<'a>, Row, Col), When(When<'a>, Row, Col),
If(If<'a>, Row, Col),
List(List<'a>, Row, Col),
// EInParens(PInParens<'a>, Row, Col), // EInParens(PInParens<'a>, Row, Col),
IndentStart(Row, Col), IndentStart(Row, Col),
IndentEnd(Row, Col), IndentEnd(Row, Col),
} }
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum List<'a> {
Open(Row, Col),
End(Row, Col),
Space(BadInputError, Row, Col),
Syntax(&'a SyntaxError<'a>, Row, Col),
Expr(&'a EExpr<'a>, Row, Col),
IndentOpen(Row, Col),
IndentEnd(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum When<'a> { pub enum When<'a> {
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
@ -408,6 +424,26 @@ pub enum When<'a> {
PatternAlignment(u16, Row, Col), PatternAlignment(u16, Row, Col),
} }
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum If<'a> {
Space(BadInputError, Row, Col),
If(Row, Col),
Then(Row, Col),
Else(Row, Col),
// TODO make EEXpr
Condition(&'a EExpr<'a>, Row, Col),
ThenBranch(&'a EExpr<'a>, Row, Col),
ElseBranch(&'a EExpr<'a>, Row, Col),
Syntax(&'a SyntaxError<'a>, Row, Col),
IndentCondition(Row, Col),
IndentIf(Row, Col),
IndentThenToken(Row, Col),
IndentElseToken(Row, Col),
IndentThenBranch(Row, Col),
IndentElseBranch(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum EPattern<'a> { pub enum EPattern<'a> {
Record(PRecord<'a>, Row, Col), Record(PRecord<'a>, Row, Col),
@ -1431,10 +1467,11 @@ macro_rules! collection_trailing_sep_e {
and!( and!(
$crate::parser::trailing_sep_by0( $crate::parser::trailing_sep_by0(
$delimiter, $delimiter,
$crate::blankspace::space0_around_e( $crate::blankspace::space0_around_ee(
$elem, $elem,
$min_indent, $min_indent,
$space_problem, $space_problem,
$indent_problem,
$indent_problem $indent_problem
) )
), ),

View file

@ -1,5 +1,5 @@
use crate::ast::Pattern; use crate::ast::Pattern;
use crate::blankspace::{space0_around_e, space0_before_e, space0_e}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::ident::{ident, lowercase_ident, Ident}; use crate::ident::{ident, lowercase_ident, Ident};
use crate::number_literal::number_literal; use crate::number_literal::number_literal;
use crate::parser::Progress::{self, *}; use crate::parser::Progress::{self, *};
@ -133,11 +133,12 @@ fn loc_pattern_in_parens_help<'a>(
) -> impl Parser<'a, Located<Pattern<'a>>, PInParens<'a>> { ) -> impl Parser<'a, Located<Pattern<'a>>, PInParens<'a>> {
between!( between!(
word1(b'(', PInParens::Open), word1(b'(', PInParens::Open),
space0_around_e( space0_around_ee(
move |arena, state| specialize_ref(PInParens::Syntax, loc_pattern(min_indent)) move |arena, state| specialize_ref(PInParens::Syntax, loc_pattern(min_indent))
.parse(arena, state), .parse(arena, state),
min_indent, min_indent,
PInParens::Space, PInParens::Space,
PInParens::IndentOpen,
PInParens::IndentEnd, PInParens::IndentEnd,
), ),
word1(b')', PInParens::End) word1(b')', PInParens::End)

View file

@ -1,5 +1,5 @@
use crate::ast::{AssignedField, Tag, TypeAnnotation}; use crate::ast::{AssignedField, Tag, TypeAnnotation};
use crate::blankspace::{space0_around_e, space0_before_e, space0_e}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::ident::join_module_parts; use crate::ident::join_module_parts;
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
@ -146,11 +146,12 @@ fn loc_type_in_parens<'a>(
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, TInParens<'a>> { ) -> impl Parser<'a, Located<TypeAnnotation<'a>>, TInParens<'a>> {
between!( between!(
word1(b'(', TInParens::Open), word1(b'(', TInParens::Open),
space0_around_e( space0_around_ee(
move |arena, state| specialize_ref(TInParens::Type, expression(min_indent)) move |arena, state| specialize_ref(TInParens::Type, expression(min_indent))
.parse(arena, state), .parse(arena, state),
min_indent, min_indent,
TInParens::Space, TInParens::Space,
TInParens::IndentOpen,
TInParens::IndentEnd, TInParens::IndentEnd,
), ),
word1(b')', TInParens::End) word1(b')', TInParens::End)
@ -436,11 +437,12 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
let (p2, rest, state) = zero_or_more!(skip_first!( let (p2, rest, state) = zero_or_more!(skip_first!(
word1(b',', Type::TFunctionArgument), word1(b',', Type::TFunctionArgument),
one_of![ one_of![
space0_around_e( space0_around_ee(
term(min_indent), term(min_indent),
min_indent, min_indent,
Type::TSpace, Type::TSpace,
Type::TIndentStart Type::TIndentStart,
Type::TIndentEnd
), ),
|_, state: State<'a>| Err(( |_, state: State<'a>| Err((
NoProgress, NoProgress,

View file

@ -158,7 +158,10 @@ enum Context {
enum Node { enum Node {
WhenCondition, WhenCondition,
WhenBranch, WhenBranch,
// WhenIfGuard, IfCondition,
IfThenBranch,
IfElseBranch,
ListElement,
} }
fn to_expr_report<'a>( fn to_expr_report<'a>(
@ -173,10 +176,240 @@ fn to_expr_report<'a>(
match parse_problem { match parse_problem {
EExpr::When(when, row, col) => to_when_report(alloc, filename, context, &when, *row, *col), EExpr::When(when, row, col) => to_when_report(alloc, filename, context, &when, *row, *col),
EExpr::If(if_, row, col) => to_if_report(alloc, filename, context, &if_, *row, *col),
EExpr::List(list, row, col) => to_list_report(alloc, filename, context, &list, *row, *col),
_ => todo!("unhandled parse error: {:?}", parse_problem), _ => todo!("unhandled parse error: {:?}", parse_problem),
} }
} }
fn to_list_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
context: Context,
parse_problem: &roc_parse::parser::List<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::List;
match *parse_problem {
List::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
List::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
List::Expr(expr, row, col) => to_expr_report(
alloc,
filename,
Context::InNode(Node::ListElement, start_row, start_col, Box::new(context)),
expr,
row,
col,
),
List::Open(row, col) | List::End(row, col) => {
match dbg!(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);
let doc = alloc.stack(vec![
alloc.reflow(
r"I am partway through started parsing a list, but I got stuck here:",
),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc
.reflow(r"I was expecting to see a list entry before this comma, "),
alloc.reflow(r"so try adding a list entry"),
alloc.reflow(r" and see if that helps?"),
]),
]);
Report {
filename,
doc,
title: "UNFINISHED LIST".to_string(),
}
}
_ => {
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 am partway through started parsing a list, but I got stuck here:",
),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(
r"I was expecting to see a closing square bracket before this, ",
),
alloc.reflow(r"so try adding a "),
alloc.parser_suggestion("]"),
alloc.reflow(r" and see if that helps?"),
]),
alloc.concat(vec![
alloc.note("When "),
alloc.reflow(r"I get stuck like this, "),
alloc.reflow(r"it usually means that there is a missing parenthesis "),
alloc.reflow(r"or bracket somewhere earlier. "),
alloc.reflow(r"It could also be a stray keyword or operator."),
]),
]);
Report {
filename,
doc,
title: "UNFINISHED LIST".to_string(),
}
}
}
}
List::IndentOpen(row, col) | List::IndentEnd(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 cannot find the end of this list:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"You could change it to something like "),
alloc.parser_suggestion("[ 1, 2, 3 ]"),
alloc.reflow(" or even just "),
alloc.parser_suggestion("[]"),
alloc.reflow(". Anything where there is an open and a close square bracket, "),
alloc.reflow("and where the elements of the list are separated by commas."),
]),
note_for_tag_union_type_indent(alloc),
]);
Report {
filename,
doc,
title: "UNFINISHED LIST".to_string(),
}
}
}
}
fn to_if_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
context: Context,
parse_problem: &roc_parse::parser::If<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::If;
match *parse_problem {
If::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
If::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
If::Condition(expr, row, col) => to_expr_report(
alloc,
filename,
Context::InNode(Node::IfCondition, start_row, start_col, Box::new(context)),
expr,
row,
col,
),
If::ThenBranch(expr, row, col) => to_expr_report(
alloc,
filename,
Context::InNode(Node::IfThenBranch, start_row, start_col, Box::new(context)),
expr,
row,
col,
),
If::ElseBranch(expr, row, col) => to_expr_report(
alloc,
filename,
Context::InNode(Node::IfElseBranch, start_row, start_col, Box::new(context)),
expr,
row,
col,
),
If::If(_row, _col) => unreachable!("another branch would be taken"),
If::IndentIf(_row, _col) => unreachable!("another branch would be taken"),
If::Then(row, col) | If::IndentThenBranch(row, col) | If::IndentThenToken(row, col) => {
to_unfinished_if_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I was expecting to see the "),
alloc.keyword("then"),
alloc.reflow(r" keyword next."),
]),
)
}
If::Else(row, col) | If::IndentElseBranch(row, col) | If::IndentElseToken(row, col) => {
to_unfinished_if_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I was expecting to see the "),
alloc.keyword("else"),
alloc.reflow(r" keyword next."),
]),
)
}
If::IndentCondition(row, col) => to_unfinished_if_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I was expecting to see a expression next")
]),
),
}
}
fn to_unfinished_if_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
row: Row,
col: Col,
start_row: Row,
start_col: Col,
message: RocDocBuilder<'a>,
) -> Report<'a> {
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 an "),
alloc.keyword("if"),
alloc.reflow(r" expression, but I got stuck here:"),
]),
alloc.region_with_subregion(surroundings, region),
message,
]);
Report {
filename,
doc,
title: "UNFINISHED IF".to_string(),
}
}
fn to_when_report<'a>( fn to_when_report<'a>(
alloc: &'a RocDocAllocator<'a>, alloc: &'a RocDocAllocator<'a>,
filename: PathBuf, filename: PathBuf,
@ -792,6 +1025,23 @@ fn to_type_report<'a>(
} }
} }
Type::TIndentEnd(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 am partway through parsing a type, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.note("I may be confused by indentation"),
]);
Report {
filename,
doc,
title: "UNFINISHED TYPE".to_string(),
}
}
Type::TAsIndentStart(row, col) => { Type::TAsIndentStart(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col); let region = Region::from_row_col(*row, *col);
@ -1606,6 +1856,7 @@ fn to_space_report<'a>(
} }
} }
#[derive(Debug)]
enum Next<'a> { enum Next<'a> {
Keyword(&'a str), Keyword(&'a str),
// Operator(&'a str), // Operator(&'a str),

View file

@ -801,35 +801,36 @@ mod test_reporting {
) )
} }
// #[test] #[test]
// fn if_3_branch_mismatch() { fn if_3_branch_mismatch() {
// report_problem_as( report_problem_as(
// indoc!( indoc!(
// r#" r#"
// if True then 2 else if False then 2 else "foo" if True then 2 else if False then 2 else "foo"
// "# "#
// ), ),
// indoc!( indoc!(
// r#" r#"
// ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── TYPE MISMATCH
// The 2nd branch of this `if` does not match all the previous branches: The 3rd branch of this `if` does not match all the previous branches:
// 1│ if True then 2 else "foo" 1 if True then 2 else if False then 2 else "foo"
// ^^^^^ ^^^^^
// The 2nd branch is a string of type The 3rd branch is a string of type:
// Str Str
// But all the previous branches have the type But all the previous branches have type:
// Num a Num a
// "# I need all branches in an `if` to have the same type!
// ), "#
// ) ),
// } )
}
#[test] #[test]
fn when_branch_mismatch() { fn when_branch_mismatch() {
@ -4636,7 +4637,7 @@ mod test_reporting {
r#" r#"
UNFINISHED TYPE UNFINISHED TYPE
I just started parsing a type, but I got stuck here: I am partway through parsing a type, but I got stuck here:
1 f : I64, I64 1 f : I64, I64
^ ^
@ -4949,4 +4950,138 @@ mod test_reporting {
), ),
) )
} }
#[test]
fn if_outdented_then() {
// TODO I think we can do better here
report_problem_as(
indoc!(
r#"
x =
if 5 == 5
then 2 else 3
x
"#
),
indoc!(
r#"
UNFINISHED IF
I was partway through parsing an `if` expression, but I got stuck here:
2 if 5 == 5
^
I was expecting to see the `then` keyword next.
"#
),
)
}
#[test]
fn if_missing_else() {
// this should get better with time
report_problem_as(
indoc!(
r#"
if 5 == 5 then 2
"#
),
indoc!(
r#"
UNFINISHED IF
I was partway through parsing an `if` expression, but I got stuck here:
1 if 5 == 5 then 2
^
I was expecting to see the `else` keyword next.
"#
),
)
}
#[test]
fn list_double_comma() {
report_problem_as(
indoc!(
r#"
[ 1, 2, , 3 ]
"#
),
indoc!(
r#"
UNFINISHED LIST
I am partway through started parsing a list, but I got stuck here:
1 [ 1, 2, , 3 ]
^
I was expecting to see a list entry before this comma, so try adding a
list entry and see if that helps?
"#
),
)
}
#[test]
fn list_without_end() {
report_problem_as(
indoc!(
r#"
[ 1, 2,
"#
),
indoc!(
r#"
UNFINISHED LIST
I am partway through started parsing a list, but I got stuck here:
1 [ 1, 2,
^
I was expecting to see a closing square bracket before this, so try
adding a ] and see if that helps?
Note: When I get stuck like this, it usually means that there is a
missing parenthesis or bracket somewhere earlier. It could also be a
stray keyword or operator.
"#
),
)
}
#[test]
fn list_bad_indent() {
report_problem_as(
indoc!(
r#"
x = [ 1, 2,
]
x
"#
),
indoc!(
r#"
UNFINISHED LIST
I cannot find the end of this list:
1 x = [ 1, 2,
^
You could change it to something like [ 1, 2, 3 ] or even just [].
Anything where there is an open and a close square bracket, and where
the elements of the list are separated by commas.
Note: I may be confused by indentation
"#
),
)
}
} }

View file

@ -147,6 +147,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
let arena = Bump::new(); let arena = Bump::new();
let mut rects_arena = Bump::new(); let mut rects_arena = Bump::new();
let mut ast_arena = Bump::new();
// Render loop // Render loop
window.request_redraw(); window.request_redraw();
@ -255,8 +256,8 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
); );
} else { } else {
// queue_no_file_text(&size, NOTHING_OPENED, CODE_TXT_XY.into(), &mut glyph_brush); // queue_no_file_text(&size, NOTHING_OPENED, CODE_TXT_XY.into(), &mut glyph_brush);
ast_arena.reset();
let mut pool = Pool::with_capacity(12); let mut pool = Pool::with_capacity(1024);
let mut var_store = VarStore::default(); let mut var_store = VarStore::default();
let dep_idents = IdentIds::exposed_builtins(8); let dep_idents = IdentIds::exposed_builtins(8);
let mut module_ids = ModuleIds::default(); let mut module_ids = ModuleIds::default();
@ -280,7 +281,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
let (expr2, _) = crate::lang::expr::str_to_expr2( let (expr2, _) = crate::lang::expr::str_to_expr2(
&arena, &arena,
"{ x: 2, y: 5 }", "{ a: 1, b: 2, c: {x: 3, y: 4} }",
&mut env, &mut env,
&mut scope, &mut scope,
region, region,
@ -288,6 +289,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
.unwrap(); .unwrap();
super::render_ast::render_expr2( super::render_ast::render_expr2(
&ast_arena,
&mut env, &mut env,
&expr2, &expr2,
&size, &size,

View file

@ -1,16 +1,156 @@
use crate::editor::colors::CODE_COL; use crate::lang::pool::PoolStr;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use cgmath::Vector2;
use wgpu_glyph::GlyphBrush;
use winit::dpi::PhysicalSize;
use crate::{ use crate::{
editor::colors::CODE_COL,
graphics::{ graphics::{
primitives::text::{queue_code_text_draw, Text}, primitives::text::{queue_code_text_draw, Text},
style::CODE_FONT_SIZE, style::CODE_FONT_SIZE,
}, },
lang::{ast::Expr2, expr::Env}, lang::{ast::Expr2, expr::Env},
}; };
use cgmath::Vector2;
use wgpu_glyph::GlyphBrush; fn pool_str_len<'a>(env: &Env<'a>, pool_str: &PoolStr) -> usize {
use winit::dpi::PhysicalSize; env.pool.get_str(pool_str).len()
}
// calculate the str len, necessary for BumpString
fn expr2_to_len<'a>(env: &Env<'a>, expr2: &Expr2) -> usize {
match expr2 {
Expr2::SmallInt { text, .. } => pool_str_len(env, text),
Expr2::I128 { text, .. } => pool_str_len(env, text),
Expr2::U128 { text, .. } => pool_str_len(env, text),
Expr2::Float { text, .. } => pool_str_len(env, text),
Expr2::Str(text) => pool_str_len(env, text),
Expr2::GlobalTag { name, .. } => pool_str_len(env, name),
Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*expr_id);
expr2_to_len(env, expr)
}
Expr2::Var(symbol) => {
//TODO make bump_format to use arena
let text = format!("{:?}", symbol);
text.len()
}
Expr2::List { elems, .. } => {
let mut len_ctr = 2; // for '[' and ']'
for (idx, node_id) in elems.iter_node_ids().enumerate() {
let sub_expr2 = env.pool.get(node_id);
len_ctr += expr2_to_len(env, sub_expr2);
if idx + 1 < elems.len() {
len_ctr += 2; // for ", "
}
}
len_ctr
}
Expr2::Record { fields, .. } => {
let mut len_ctr = 2; // for '{' and '}'
for (idx, node_id) in fields.iter_node_ids().enumerate() {
let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(node_id);
len_ctr += pool_str_len(env, &pool_field_name);
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
let sub_expr2_len = expr2_to_len(env, sub_expr2);
len_ctr += sub_expr2_len;
if idx + 1 < fields.len() {
len_ctr += 2; // for ", "
}
}
len_ctr
}
rest => todo!("implement expr2_to_str for {:?}", rest),
}
}
fn get_bump_str<'a, 'b>(arena: &'a Bump, env: &Env<'b>, pool_str: &PoolStr) -> BumpString<'a> {
let env_str = env.pool.get_str(pool_str);
BumpString::from_str_in(env_str, arena)
}
pub fn expr2_to_str<'a, 'b>(arena: &'a Bump, env: &Env<'b>, expr2: &Expr2) -> BumpString<'a> {
match expr2 {
Expr2::SmallInt { text, .. } => get_bump_str(arena, env, text),
Expr2::I128 { text, .. } => get_bump_str(arena, env, text),
Expr2::U128 { text, .. } => get_bump_str(arena, env, text),
Expr2::Float { text, .. } => get_bump_str(arena, env, text),
Expr2::Str(text) => get_bump_str(arena, env, text),
Expr2::GlobalTag { name, .. } => get_bump_str(arena, env, name),
Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*expr_id);
expr2_to_str(arena, env, expr)
}
Expr2::Var(symbol) => {
//TODO make bump_format with arena
let text = format!("{:?}", symbol);
BumpString::from_str_in(&text, arena)
}
Expr2::List { elems, .. } => {
let mut bump_str = BumpString::with_capacity_in(expr2_to_len(env, expr2), arena);
bump_str.push('[');
for (idx, node_id) in elems.iter_node_ids().enumerate() {
let sub_expr2 = env.pool.get(node_id);
bump_str.push_str(&expr2_to_str(arena, env, sub_expr2));
if idx + 1 < elems.len() {
bump_str.push_str(", ")
}
}
bump_str.push(']');
bump_str
}
Expr2::Record { fields, .. } => {
let mut bump_str = BumpString::with_capacity_in(expr2_to_len(env, expr2), arena);
bump_str.push('{');
for (idx, node_id) in fields.iter_node_ids().enumerate() {
let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(node_id);
let field_name = env.pool.get_str(pool_field_name);
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
bump_str.push_str(field_name);
bump_str.push(':');
bump_str.push_str(&expr2_to_str(arena, env, sub_expr2));
if idx + 1 < fields.len() {
bump_str.push_str(", ")
}
}
bump_str.push('}');
bump_str
}
rest => todo!("implement expr2_to_str for {:?}", rest),
}
}
pub fn render_expr2<'a>( pub fn render_expr2<'a>(
arena: &'a Bump,
env: &mut Env<'a>, env: &mut Env<'a>,
expr2: &Expr2, expr2: &Expr2,
size: &PhysicalSize<u32>, size: &PhysicalSize<u32>,
@ -19,240 +159,18 @@ pub fn render_expr2<'a>(
) { ) {
let area_bounds = (size.width as f32, size.height as f32).into(); let area_bounds = (size.width as f32, size.height as f32).into();
match expr2 { let expr_str = expr2_to_str(arena, env, expr2);
Expr2::SmallInt { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush); // TODO format expr_str
}
Expr2::I128 { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::U128 { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Float { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Str(text) => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::GlobalTag { name, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(name),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*expr_id);
render_expr2(env, expr, size, position, glyph_brush);
}
Expr2::Var(symbol) => {
let text = format!("{:?}", symbol);
let code_text = Text { let code_text = Text {
position, position,
area_bounds, area_bounds,
color: CODE_COL.into(), color: CODE_COL.into(),
text: text.as_str(), text: &expr_str,
size: CODE_FONT_SIZE, size: CODE_FONT_SIZE,
..Default::default() ..Default::default()
}; };
queue_code_text_draw(&code_text, glyph_brush); queue_code_text_draw(&code_text, glyph_brush);
} }
Expr2::List { elems, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: "[",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
let mut x_pos = position.x;
for (idx, node_id) in elems.iter_node_ids().enumerate() {
let sub_expr2 = env.pool.get(node_id);
x_pos += 20.0;
render_expr2(
env,
sub_expr2,
size,
Vector2::new(x_pos, position.y),
glyph_brush,
);
if idx + 1 < elems.len() {
x_pos += 10.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: ",",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
}
x_pos += 20.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: "]",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Record { fields, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: "{",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
let mut x_pos = position.x;
for (idx, node_id) in fields.iter_node_ids().enumerate() {
let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(node_id);
let field_name = env.pool.get_str(pool_field_name);
x_pos += 20.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: field_name,
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
x_pos += 10.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: ":",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
x_pos += 20.0;
render_expr2(
env,
sub_expr2,
size,
Vector2::new(x_pos, position.y),
glyph_brush,
);
if idx + 1 < fields.len() {
x_pos += 10.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: ",",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
}
x_pos += 20.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: "}",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
rest => todo!("implement {:?} render", rest),
};
}

View file

@ -508,22 +508,31 @@ pub fn to_expr2<'a>(
Output::default(), Output::default(),
), ),
If(cond, then_branch, else_branch) => { If(branches, final_else) => {
let (cond, mut output) = to_expr2(env, scope, &cond.value, cond.region); let mut new_branches = Vec::with_capacity(branches.len());
let mut output = Output::default();
for (condition, then_branch) in branches.iter() {
let (cond, cond_output) = to_expr2(env, scope, &condition.value, condition.region);
let (then_expr, then_output) = let (then_expr, then_output) =
to_expr2(env, scope, &then_branch.value, then_branch.region); to_expr2(env, scope, &then_branch.value, then_branch.region);
let (else_expr, else_output) = output.references.union_mut(cond_output.references);
to_expr2(env, scope, &else_branch.value, else_branch.region);
output.references.union_mut(then_output.references); output.references.union_mut(then_output.references);
new_branches.push((cond, then_expr));
}
let (else_expr, else_output) =
to_expr2(env, scope, &final_else.value, final_else.region);
output.references.union_mut(else_output.references); output.references.union_mut(else_output.references);
let expr = Expr2::If { let expr = Expr2::If {
cond_var: env.var_store.fresh(), cond_var: env.var_store.fresh(),
expr_var: env.var_store.fresh(), expr_var: env.var_store.fresh(),
branches: PoolVec::new(vec![(cond, then_expr)].into_iter(), env.pool), branches: PoolVec::new(new_branches.into_iter(), env.pool),
final_else: env.pool.add(else_expr), final_else: env.pool.add(else_expr),
}; };