mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-21 07:49:17 +03:00
Merge pull request #1090 from rtfeldman/binop-improvements
Binop improvements
This commit is contained in:
commit
e238c2e202
@ -121,7 +121,6 @@ fn jit_to_ast_help<'a>(
|
||||
}
|
||||
Layout::PhantomEmptyStruct => Ok(run_jit_function!(lib, main_fn_name, &u8, |_| {
|
||||
Expr::Record {
|
||||
update: None,
|
||||
fields: &[],
|
||||
final_comments: env.arena.alloc([]),
|
||||
}
|
||||
@ -474,7 +473,6 @@ fn struct_to_ast<'a>(
|
||||
let output = env.arena.alloc([loc_field]);
|
||||
|
||||
Expr::Record {
|
||||
update: None,
|
||||
fields: output,
|
||||
final_comments: &[],
|
||||
}
|
||||
@ -510,7 +508,6 @@ fn struct_to_ast<'a>(
|
||||
let output = output.into_bump_slice();
|
||||
|
||||
Expr::Record {
|
||||
update: None,
|
||||
fields: output,
|
||||
final_comments: &[],
|
||||
}
|
||||
@ -554,7 +551,6 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
|
||||
};
|
||||
|
||||
Expr::Record {
|
||||
update: None,
|
||||
fields: arena.alloc([loc_assigned_field]),
|
||||
final_comments: arena.alloc([]),
|
||||
}
|
||||
@ -667,7 +663,6 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
|
||||
};
|
||||
|
||||
Expr::Record {
|
||||
update: None,
|
||||
fields: arena.alloc([loc_assigned_field]),
|
||||
final_comments: &[],
|
||||
}
|
||||
@ -783,7 +778,6 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
|
||||
};
|
||||
|
||||
Expr::Record {
|
||||
update: None,
|
||||
fields: arena.alloc([loc_assigned_field]),
|
||||
final_comments: arena.alloc([]),
|
||||
}
|
||||
|
@ -207,7 +207,37 @@ pub fn canonicalize_expr<'a>(
|
||||
}
|
||||
ast::Expr::Record {
|
||||
fields,
|
||||
update: Some(loc_update),
|
||||
final_comments: _,
|
||||
} => {
|
||||
if fields.is_empty() {
|
||||
(EmptyRecord, Output::default())
|
||||
} else {
|
||||
match canonicalize_fields(env, var_store, scope, region, fields) {
|
||||
Ok((can_fields, output)) => (
|
||||
Record {
|
||||
record_var: var_store.fresh(),
|
||||
fields: can_fields,
|
||||
},
|
||||
output,
|
||||
),
|
||||
Err(CanonicalizeRecordProblem::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
}) => (
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
}),
|
||||
Output::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Expr::RecordUpdate {
|
||||
fields,
|
||||
update: loc_update,
|
||||
final_comments: _,
|
||||
} => {
|
||||
let (can_update, update_out) =
|
||||
@ -253,37 +283,6 @@ pub fn canonicalize_expr<'a>(
|
||||
(answer, Output::default())
|
||||
}
|
||||
}
|
||||
ast::Expr::Record {
|
||||
fields,
|
||||
update: None,
|
||||
final_comments: _,
|
||||
} => {
|
||||
if fields.is_empty() {
|
||||
(EmptyRecord, Output::default())
|
||||
} else {
|
||||
match canonicalize_fields(env, var_store, scope, region, fields) {
|
||||
Ok((can_fields, output)) => (
|
||||
Record {
|
||||
record_var: var_store.fresh(),
|
||||
fields: can_fields,
|
||||
},
|
||||
output,
|
||||
),
|
||||
Err(CanonicalizeRecordProblem::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
}) => (
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
}),
|
||||
Output::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
||||
ast::Expr::List {
|
||||
items: loc_elems, ..
|
||||
@ -720,10 +719,34 @@ pub fn canonicalize_expr<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
ast::Expr::PrecedenceConflict(whole_region, binop1, binop2, _expr) => {
|
||||
ast::Expr::PrecedenceConflict(ast::PrecedenceConflict {
|
||||
whole_region,
|
||||
binop1_position,
|
||||
binop2_position,
|
||||
binop1,
|
||||
binop2,
|
||||
expr: _,
|
||||
}) => {
|
||||
use roc_problem::can::RuntimeError::*;
|
||||
|
||||
let problem = PrecedenceProblem::BothNonAssociative(*whole_region, *binop1, *binop2);
|
||||
let region1 = Region::new(
|
||||
binop1_position.row,
|
||||
binop1_position.row,
|
||||
binop1_position.col,
|
||||
binop1_position.col + binop1.width(),
|
||||
);
|
||||
let loc_binop1 = Located::at(region1, *binop1);
|
||||
|
||||
let region2 = Region::new(
|
||||
binop2_position.row,
|
||||
binop2_position.row,
|
||||
binop2_position.col,
|
||||
binop2_position.col + binop2.width(),
|
||||
);
|
||||
let loc_binop2 = Located::at(region2, *binop2);
|
||||
|
||||
let problem =
|
||||
PrecedenceProblem::BothNonAssociative(*whole_region, loc_binop1, loc_binop2);
|
||||
|
||||
env.problem(Problem::PrecedenceProblem(problem.clone()));
|
||||
|
||||
@ -780,9 +803,9 @@ pub fn canonicalize_expr<'a>(
|
||||
bad_expr
|
||||
);
|
||||
}
|
||||
bad_expr @ ast::Expr::BinOp(_) => {
|
||||
bad_expr @ ast::Expr::BinOps { .. } => {
|
||||
panic!(
|
||||
"A binary operator did not get desugared somehow: {:#?}",
|
||||
"A binary operator chain did not get desugared somehow: {:#?}",
|
||||
bad_expr
|
||||
);
|
||||
}
|
||||
|
@ -10,25 +10,53 @@ use roc_region::all::{Located, Region};
|
||||
// BinOp precedence logic adapted from Gluon by Markus Westerlind, MIT licensed
|
||||
// https://github.com/gluon-lang/gluon
|
||||
// Thank you, Markus!
|
||||
fn new_op_expr<'a>(
|
||||
|
||||
fn new_op_call_expr<'a>(
|
||||
arena: &'a Bump,
|
||||
left: Located<Expr<'a>>,
|
||||
op: Located<BinOp>,
|
||||
right: Located<Expr<'a>>,
|
||||
left: &'a Located<Expr<'a>>,
|
||||
loc_op: Located<BinOp>,
|
||||
right: &'a Located<Expr<'a>>,
|
||||
) -> Located<Expr<'a>> {
|
||||
let new_region = Region {
|
||||
start_line: left.region.start_line,
|
||||
start_col: left.region.start_col,
|
||||
let region = Region::span_across(&left.region, &right.region);
|
||||
|
||||
end_line: right.region.end_line,
|
||||
end_col: right.region.end_col,
|
||||
let value = match loc_op.value {
|
||||
Pizza => {
|
||||
// Rewrite the Pizza operator into an Apply
|
||||
|
||||
match &right.value {
|
||||
Apply(function, arguments, _called_via) => {
|
||||
let mut args = Vec::with_capacity_in(1 + arguments.len(), arena);
|
||||
|
||||
args.push(left);
|
||||
args.extend(arguments.iter());
|
||||
|
||||
let args = args.into_bump_slice();
|
||||
|
||||
Apply(function, args, CalledVia::BinOp(Pizza))
|
||||
}
|
||||
_ => {
|
||||
// e.g. `1 |> (if b then (\a -> a) else (\c -> c))`
|
||||
Apply(right, arena.alloc([left]), CalledVia::BinOp(Pizza))
|
||||
}
|
||||
}
|
||||
}
|
||||
binop => {
|
||||
// This is a normal binary operator like (+), so desugar it
|
||||
// into the appropriate function call.
|
||||
let (module_name, ident) = binop_to_function(binop);
|
||||
|
||||
let args = arena.alloc([left, right]);
|
||||
|
||||
let loc_expr = arena.alloc(Located {
|
||||
value: Expr::Var { module_name, ident },
|
||||
region: loc_op.region,
|
||||
});
|
||||
|
||||
Apply(loc_expr, args, CalledVia::BinOp(binop))
|
||||
}
|
||||
};
|
||||
let new_expr = Expr::BinOp(arena.alloc((left, op, right)));
|
||||
|
||||
Located {
|
||||
value: new_expr,
|
||||
region: new_region,
|
||||
}
|
||||
Located { value, region }
|
||||
}
|
||||
|
||||
fn desugar_defs<'a>(
|
||||
@ -117,8 +145,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
| Nested(MalformedIdent(_, _))
|
||||
| MalformedClosure
|
||||
| Nested(MalformedClosure)
|
||||
| PrecedenceConflict(_, _, _, _)
|
||||
| Nested(PrecedenceConflict(_, _, _, _))
|
||||
| PrecedenceConflict { .. }
|
||||
| Nested(PrecedenceConflict { .. })
|
||||
| GlobalTag(_)
|
||||
| Nested(GlobalTag(_))
|
||||
| PrivateTag(_)
|
||||
@ -160,12 +188,10 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
}
|
||||
Record {
|
||||
fields,
|
||||
update,
|
||||
final_comments,
|
||||
}
|
||||
| Nested(Record {
|
||||
fields,
|
||||
update,
|
||||
final_comments,
|
||||
}) => {
|
||||
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
|
||||
@ -184,6 +210,39 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
arena.alloc(Located {
|
||||
region: loc_expr.region,
|
||||
value: Record {
|
||||
fields: new_fields,
|
||||
final_comments,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
RecordUpdate {
|
||||
fields,
|
||||
update,
|
||||
final_comments,
|
||||
}
|
||||
| Nested(RecordUpdate {
|
||||
fields,
|
||||
update,
|
||||
final_comments,
|
||||
}) => {
|
||||
// NOTE the `update` field is always a `Var { .. }` and does not need to be desugared
|
||||
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
for field in fields.iter() {
|
||||
let value = desugar_field(arena, &field.value);
|
||||
|
||||
new_fields.push(Located {
|
||||
value,
|
||||
region: field.region,
|
||||
});
|
||||
}
|
||||
|
||||
let new_fields = new_fields.into_bump_slice();
|
||||
|
||||
arena.alloc(Located {
|
||||
region: loc_expr.region,
|
||||
value: RecordUpdate {
|
||||
update: *update,
|
||||
fields: new_fields,
|
||||
final_comments,
|
||||
@ -205,12 +264,12 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
// first desugar the body, because it may contain |>
|
||||
let desugared_body = desugar_expr(arena, loc_body);
|
||||
|
||||
let desugared_ret = desugar_expr(arena, loc_ret);
|
||||
let closure = Expr::Closure(loc_patterns, desugared_ret);
|
||||
let loc_closure = Located::at_zero(closure);
|
||||
|
||||
match &desugared_body.value {
|
||||
Expr::Apply(function, arguments, called_via) => {
|
||||
let desugared_ret = desugar_expr(arena, loc_ret);
|
||||
let closure = Expr::Closure(loc_patterns, desugared_ret);
|
||||
let loc_closure = Located::at_zero(closure);
|
||||
|
||||
let mut new_arguments: Vec<'a, &'a Located<Expr<'a>>> =
|
||||
Vec::with_capacity_in(arguments.len() + 1, arena);
|
||||
new_arguments.extend(arguments.iter());
|
||||
@ -221,10 +280,22 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
|
||||
arena.alloc(loc_call)
|
||||
}
|
||||
_ => panic!(),
|
||||
_ => {
|
||||
// e.g. `x <- (if b then (\a -> a) else (\c -> c))`
|
||||
let call = Expr::Apply(
|
||||
desugared_body,
|
||||
arena.alloc([&*arena.alloc(loc_closure)]),
|
||||
CalledVia::Space,
|
||||
);
|
||||
let loc_call = Located::at(loc_expr.region, call);
|
||||
|
||||
arena.alloc(loc_call)
|
||||
}
|
||||
}
|
||||
}
|
||||
BinOp(_) | Nested(BinOp(_)) => desugar_bin_op(arena, loc_expr),
|
||||
BinOps(lefts, right) | Nested(BinOps(lefts, right)) => {
|
||||
desugar_bin_ops(arena, loc_expr.region, lefts, right)
|
||||
}
|
||||
Defs(defs, loc_ret) | Nested(Defs(defs, loc_ret)) => {
|
||||
desugar_defs(arena, loc_expr.region, *defs, loc_ret)
|
||||
}
|
||||
@ -301,9 +372,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
},
|
||||
};
|
||||
let loc_fn_var = arena.alloc(Located { region, value });
|
||||
let desugared_args = bumpalo::vec![in arena; desugar_expr(arena, loc_arg)];
|
||||
|
||||
let desugared_args = desugared_args.into_bump_slice();
|
||||
let desugared_args = arena.alloc([desugar_expr(arena, loc_arg)]);
|
||||
|
||||
arena.alloc(Located {
|
||||
value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)),
|
||||
@ -426,261 +495,149 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
|
||||
}
|
||||
}
|
||||
|
||||
fn desugar_bin_op<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'_>>) -> &'a Located<Expr<'a>> {
|
||||
fn desugar_bin_ops<'a>(
|
||||
arena: &'a Bump,
|
||||
whole_region: Region,
|
||||
lefts: &'a [(Located<Expr<'_>>, Located<BinOp>)],
|
||||
right: &'a Located<Expr<'_>>,
|
||||
) -> &'a Located<Expr<'a>> {
|
||||
let mut arg_stack: Vec<&'a Located<Expr>> = Vec::with_capacity_in(lefts.len() + 1, arena);
|
||||
let mut op_stack: Vec<Located<BinOp>> = Vec::with_capacity_in(lefts.len(), arena);
|
||||
|
||||
for (loc_expr, loc_op) in lefts {
|
||||
arg_stack.push(desugar_expr(arena, loc_expr));
|
||||
match run_binop_step(arena, whole_region, &mut arg_stack, &mut op_stack, *loc_op) {
|
||||
Err(problem) => return problem,
|
||||
Ok(()) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
let mut expr = desugar_expr(arena, right);
|
||||
|
||||
for (left, loc_op) in arg_stack.into_iter().zip(op_stack.into_iter()).rev() {
|
||||
expr = arena.alloc(new_op_call_expr(arena, left, loc_op, expr));
|
||||
}
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
enum Step<'a> {
|
||||
Error(&'a Located<Expr<'a>>),
|
||||
Push(Located<BinOp>),
|
||||
Skip,
|
||||
}
|
||||
|
||||
fn run_binop_step<'a>(
|
||||
arena: &'a Bump,
|
||||
whole_region: Region,
|
||||
arg_stack: &mut Vec<&'a Located<Expr<'a>>>,
|
||||
op_stack: &mut Vec<Located<BinOp>>,
|
||||
next_op: Located<BinOp>,
|
||||
) -> Result<(), &'a Located<Expr<'a>>> {
|
||||
use Step::*;
|
||||
|
||||
match binop_step(arena, whole_region, arg_stack, op_stack, next_op) {
|
||||
Error(problem) => Err(problem),
|
||||
Push(loc_op) => run_binop_step(arena, whole_region, arg_stack, op_stack, loc_op),
|
||||
Skip => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn binop_step<'a>(
|
||||
arena: &'a Bump,
|
||||
whole_region: Region,
|
||||
arg_stack: &mut Vec<&'a Located<Expr<'a>>>,
|
||||
op_stack: &mut Vec<Located<BinOp>>,
|
||||
next_op: Located<BinOp>,
|
||||
) -> Step<'a> {
|
||||
use roc_module::operator::Associativity::*;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
let mut infixes = Infixes::new(loc_expr);
|
||||
let mut arg_stack: Vec<&'a Located<Expr>> = Vec::new_in(arena);
|
||||
let mut op_stack: Vec<Located<BinOp>> = Vec::new_in(arena);
|
||||
match op_stack.pop() {
|
||||
Some(stack_op) => {
|
||||
match next_op.value.cmp(&stack_op.value) {
|
||||
Ordering::Less => {
|
||||
// Inline
|
||||
let right = arg_stack.pop().unwrap();
|
||||
let left = arg_stack.pop().unwrap();
|
||||
|
||||
while let Some(token) = infixes.next() {
|
||||
match token {
|
||||
InfixToken::Arg(next_expr) => arg_stack.push(next_expr),
|
||||
InfixToken::Op(next_op) => {
|
||||
match op_stack.pop() {
|
||||
Some(stack_op) => {
|
||||
match next_op.value.cmp(&stack_op.value) {
|
||||
Ordering::Less => {
|
||||
// Inline
|
||||
let right = arg_stack.pop().unwrap();
|
||||
let left = arg_stack.pop().unwrap();
|
||||
arg_stack.push(arena.alloc(new_op_call_expr(arena, left, stack_op, right)));
|
||||
|
||||
infixes.next_op = Some(next_op);
|
||||
arg_stack.push(arena.alloc(new_op_expr(
|
||||
arena,
|
||||
Located {
|
||||
value: Nested(&left.value),
|
||||
region: left.region,
|
||||
},
|
||||
stack_op,
|
||||
Located {
|
||||
value: Nested(&right.value),
|
||||
region: right.region,
|
||||
},
|
||||
)));
|
||||
}
|
||||
Step::Push(next_op)
|
||||
}
|
||||
|
||||
Ordering::Greater => {
|
||||
// Swap
|
||||
op_stack.push(stack_op);
|
||||
op_stack.push(next_op);
|
||||
}
|
||||
Ordering::Greater => {
|
||||
// Swap
|
||||
op_stack.push(stack_op);
|
||||
op_stack.push(next_op);
|
||||
|
||||
Ordering::Equal => {
|
||||
match (
|
||||
next_op.value.associativity(),
|
||||
stack_op.value.associativity(),
|
||||
) {
|
||||
(LeftAssociative, LeftAssociative) => {
|
||||
// Inline
|
||||
let right = arg_stack.pop().unwrap();
|
||||
let left = arg_stack.pop().unwrap();
|
||||
Step::Skip
|
||||
}
|
||||
|
||||
infixes.next_op = Some(next_op);
|
||||
arg_stack.push(arena.alloc(new_op_expr(
|
||||
arena,
|
||||
Located {
|
||||
value: Nested(&left.value),
|
||||
region: left.region,
|
||||
},
|
||||
stack_op,
|
||||
Located {
|
||||
value: Nested(&right.value),
|
||||
region: right.region,
|
||||
},
|
||||
)));
|
||||
}
|
||||
Ordering::Equal => {
|
||||
match (
|
||||
next_op.value.associativity(),
|
||||
stack_op.value.associativity(),
|
||||
) {
|
||||
(LeftAssociative, LeftAssociative) => {
|
||||
// Inline
|
||||
let right = arg_stack.pop().unwrap();
|
||||
let left = arg_stack.pop().unwrap();
|
||||
|
||||
(RightAssociative, RightAssociative) => {
|
||||
// Swap
|
||||
op_stack.push(stack_op);
|
||||
op_stack.push(next_op);
|
||||
}
|
||||
arg_stack
|
||||
.push(arena.alloc(new_op_call_expr(arena, left, stack_op, right)));
|
||||
|
||||
(NonAssociative, NonAssociative) => {
|
||||
// Both operators were non-associative, e.g. (True == False == False).
|
||||
// We should tell the author to disambiguate by grouping them with parens.
|
||||
let bad_op = next_op;
|
||||
let right = arg_stack.pop().unwrap();
|
||||
let left = arg_stack.pop().unwrap();
|
||||
let broken_expr = new_op_expr(
|
||||
arena,
|
||||
Located {
|
||||
value: Nested(&left.value),
|
||||
region: left.region,
|
||||
},
|
||||
next_op,
|
||||
Located {
|
||||
value: Nested(&right.value),
|
||||
region: right.region,
|
||||
},
|
||||
);
|
||||
let region = broken_expr.region;
|
||||
let value = Expr::PrecedenceConflict(
|
||||
loc_expr.region,
|
||||
stack_op,
|
||||
bad_op,
|
||||
arena.alloc(broken_expr),
|
||||
);
|
||||
|
||||
return arena.alloc(Located { region, value });
|
||||
}
|
||||
|
||||
_ => {
|
||||
// The operators had the same precedence but different associativity.
|
||||
//
|
||||
// In many languages, this case can happen due to (for example) <| and |> having the same
|
||||
// precedence but different associativity. Languages which support custom operators with
|
||||
// (e.g. Haskell) can potentially have arbitrarily many of these cases.
|
||||
//
|
||||
// By design, Roc neither allows custom operators nor has any built-in operators with
|
||||
// the same precedence and different associativity, so this should never happen!
|
||||
panic!("BinOps had the same associativity, but different precedence. This should never happen!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => op_stack.push(next_op),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for loc_op in op_stack.into_iter().rev() {
|
||||
let right = desugar_expr(arena, arg_stack.pop().unwrap());
|
||||
let left = desugar_expr(arena, arg_stack.pop().unwrap());
|
||||
|
||||
let region = Region::span_across(&left.region, &right.region);
|
||||
let value = match loc_op.value {
|
||||
Pizza => {
|
||||
// Rewrite the Pizza operator into an Apply
|
||||
|
||||
match &right.value {
|
||||
Apply(function, arguments, _called_via) => {
|
||||
let mut args = Vec::with_capacity_in(1 + arguments.len(), arena);
|
||||
|
||||
args.push(left);
|
||||
|
||||
for arg in arguments.iter() {
|
||||
args.push(arg);
|
||||
Step::Push(next_op)
|
||||
}
|
||||
|
||||
let args = args.into_bump_slice();
|
||||
(RightAssociative, RightAssociative) => {
|
||||
// Swap
|
||||
op_stack.push(stack_op);
|
||||
op_stack.push(next_op);
|
||||
|
||||
Apply(function, args, CalledVia::BinOp(Pizza))
|
||||
}
|
||||
expr => {
|
||||
// e.g. `1 |> (if b then (\a -> a) else (\c -> c))`
|
||||
let mut args = Vec::with_capacity_in(1, arena);
|
||||
Step::Skip
|
||||
}
|
||||
|
||||
args.push(left);
|
||||
(NonAssociative, NonAssociative) => {
|
||||
// Both operators were non-associative, e.g. (True == False == False).
|
||||
// We should tell the author to disambiguate by grouping them with parens.
|
||||
let bad_op = next_op;
|
||||
let right = arg_stack.pop().unwrap();
|
||||
let left = arg_stack.pop().unwrap();
|
||||
let broken_expr =
|
||||
arena.alloc(new_op_call_expr(arena, left, stack_op, right));
|
||||
let region = broken_expr.region;
|
||||
let data = roc_parse::ast::PrecedenceConflict {
|
||||
whole_region,
|
||||
binop1_position: stack_op.region.start(),
|
||||
binop1: stack_op.value,
|
||||
binop2_position: bad_op.region.start(),
|
||||
binop2: bad_op.value,
|
||||
expr: arena.alloc(broken_expr),
|
||||
};
|
||||
let value = Expr::PrecedenceConflict(arena.alloc(data));
|
||||
|
||||
let function = arena.alloc(Located {
|
||||
value: Nested(expr),
|
||||
region: right.region,
|
||||
});
|
||||
Step::Error(arena.alloc(Located { region, value }))
|
||||
}
|
||||
|
||||
let args = args.into_bump_slice();
|
||||
|
||||
Apply(function, args, CalledVia::BinOp(Pizza))
|
||||
_ => {
|
||||
// The operators had the same precedence but different associativity.
|
||||
//
|
||||
// In many languages, this case can happen due to (for example) <| and |> having the same
|
||||
// precedence but different associativity. Languages which support custom operators with
|
||||
// (e.g. Haskell) can potentially have arbitrarily many of these cases.
|
||||
//
|
||||
// By design, Roc neither allows custom operators nor has any built-in operators with
|
||||
// the same precedence and different associativity, so this should never happen!
|
||||
panic!("BinOps had the same associativity, but different precedence. This should never happen!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binop => {
|
||||
// This is a normal binary operator like (+), so desugar it
|
||||
// into the appropriate function call.
|
||||
let (module_name, ident) = binop_to_function(binop);
|
||||
let mut args = Vec::with_capacity_in(2, arena);
|
||||
|
||||
args.push(left);
|
||||
args.push(right);
|
||||
|
||||
let loc_expr = arena.alloc(Located {
|
||||
value: Expr::Var { module_name, ident },
|
||||
region: loc_op.region,
|
||||
});
|
||||
|
||||
let args = args.into_bump_slice();
|
||||
|
||||
Apply(loc_expr, args, CalledVia::BinOp(binop))
|
||||
}
|
||||
};
|
||||
|
||||
arg_stack.push(arena.alloc(Located { region, value }));
|
||||
}
|
||||
|
||||
assert_eq!(arg_stack.len(), 1);
|
||||
|
||||
arg_stack.pop().unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum InfixToken<'a> {
|
||||
Arg(&'a Located<Expr<'a>>),
|
||||
Op(Located<BinOp>),
|
||||
}
|
||||
|
||||
/// An iterator that takes an expression that has had its operators grouped
|
||||
/// with _right associativity_, and yeilds a sequence of `InfixToken`s. This
|
||||
/// is useful for reparsing the operators with their correct associativies
|
||||
/// and precedences.
|
||||
///
|
||||
/// For example, the expression:
|
||||
///
|
||||
/// ```text
|
||||
/// (1 + (2 ^ (4 * (6 - 8))))
|
||||
/// ```
|
||||
///
|
||||
/// Will result in the following iterations:
|
||||
///
|
||||
/// ```text
|
||||
/// Arg: 1
|
||||
/// Op: +
|
||||
/// Arg: 2
|
||||
/// Op: ^
|
||||
/// Arg: 4
|
||||
/// Op: *
|
||||
/// Arg: 6
|
||||
/// Op: -
|
||||
/// Arg: 8
|
||||
/// ```
|
||||
struct Infixes<'a> {
|
||||
/// The next part of the expression that we need to flatten
|
||||
remaining_expr: Option<&'a Located<Expr<'a>>>,
|
||||
/// Cached operator from a previous iteration
|
||||
next_op: Option<Located<BinOp>>,
|
||||
}
|
||||
|
||||
impl<'a> Infixes<'a> {
|
||||
fn new(expr: &'a Located<Expr<'a>>) -> Infixes<'a> {
|
||||
Infixes {
|
||||
remaining_expr: Some(expr),
|
||||
next_op: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Infixes<'a> {
|
||||
type Item = InfixToken<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<InfixToken<'a>> {
|
||||
match self.next_op.take() {
|
||||
Some(op) => Some(InfixToken::Op(op)),
|
||||
None => self
|
||||
.remaining_expr
|
||||
.take()
|
||||
.map(|loc_expr| match loc_expr.value {
|
||||
Expr::BinOp((left, loc_op, right))
|
||||
| Expr::Nested(Expr::BinOp((left, loc_op, right))) => {
|
||||
self.remaining_expr = Some(right);
|
||||
self.next_op = Some(*loc_op);
|
||||
|
||||
InfixToken::Arg(left)
|
||||
}
|
||||
_ => InfixToken::Arg(loc_expr),
|
||||
}),
|
||||
}
|
||||
None => {
|
||||
op_stack.push(next_op);
|
||||
Step::Skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,18 +65,15 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
.any(|(c, t)| c.is_multiline() || t.is_multiline())
|
||||
}
|
||||
|
||||
BinOp((loc_left, _, loc_right)) => {
|
||||
let next_is_multiline_bin_op: bool = match &loc_right.value {
|
||||
Expr::BinOp((_, _, nested_loc_right)) => nested_loc_right.is_multiline(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
next_is_multiline_bin_op || loc_left.is_multiline() || loc_right.is_multiline()
|
||||
BinOps(lefts, loc_right) => {
|
||||
lefts.iter().any(|(loc_expr, _)| loc_expr.is_multiline())
|
||||
|| loc_right.is_multiline()
|
||||
}
|
||||
|
||||
UnaryOp(loc_subexpr, _) | PrecedenceConflict(_, _, _, loc_subexpr) => {
|
||||
loc_subexpr.is_multiline()
|
||||
}
|
||||
UnaryOp(loc_subexpr, _)
|
||||
| PrecedenceConflict(roc_parse::ast::PrecedenceConflict {
|
||||
expr: loc_subexpr, ..
|
||||
}) => loc_subexpr.is_multiline(),
|
||||
|
||||
ParensAround(subexpr) | Nested(subexpr) => subexpr.is_multiline(),
|
||||
|
||||
@ -97,6 +94,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
}
|
||||
|
||||
Record { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
|
||||
RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,11 +238,17 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
buf.push_str(string);
|
||||
}
|
||||
Record {
|
||||
fields,
|
||||
final_comments,
|
||||
} => {
|
||||
fmt_record(buf, None, fields, final_comments, indent);
|
||||
}
|
||||
RecordUpdate {
|
||||
fields,
|
||||
update,
|
||||
final_comments,
|
||||
} => {
|
||||
fmt_record(buf, *update, fields, final_comments, indent);
|
||||
fmt_record(buf, Some(*update), fields, final_comments, indent);
|
||||
}
|
||||
Closure(loc_patterns, loc_ret) => {
|
||||
fmt_closure(buf, loc_patterns, loc_ret, indent);
|
||||
@ -281,15 +285,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
} => {
|
||||
fmt_list(buf, &items, final_comments, indent);
|
||||
}
|
||||
BinOp((loc_left_side, bin_op, loc_right_side)) => fmt_bin_op(
|
||||
buf,
|
||||
loc_left_side,
|
||||
bin_op,
|
||||
loc_right_side,
|
||||
false,
|
||||
parens,
|
||||
indent,
|
||||
),
|
||||
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
|
||||
UnaryOp(sub_expr, unary_op) => {
|
||||
match &unary_op.value {
|
||||
operator::UnaryOp::Negate => {
|
||||
@ -316,7 +312,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
}
|
||||
MalformedIdent(_, _) => {}
|
||||
MalformedClosure => {}
|
||||
PrecedenceConflict(_, _, _, _) => {}
|
||||
PrecedenceConflict { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -351,28 +347,8 @@ fn format_str_segment<'a>(seg: &StrSegment<'a>, buf: &mut String<'a>, indent: u1
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_bin_op<'a>(
|
||||
buf: &mut String<'a>,
|
||||
loc_left_side: &'a Located<Expr<'a>>,
|
||||
loc_bin_op: &'a Located<BinOp>,
|
||||
loc_right_side: &'a Located<Expr<'a>>,
|
||||
part_of_multi_line_bin_ops: bool,
|
||||
apply_needs_parens: Parens,
|
||||
indent: u16,
|
||||
) {
|
||||
loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, indent);
|
||||
|
||||
let is_multiline = (&loc_right_side.value).is_multiline()
|
||||
|| (&loc_left_side.value).is_multiline()
|
||||
|| part_of_multi_line_bin_ops;
|
||||
|
||||
if is_multiline {
|
||||
newline(buf, indent + INDENT)
|
||||
} else {
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
match &loc_bin_op.value {
|
||||
fn push_op(buf: &mut String, op: BinOp) {
|
||||
match op {
|
||||
operator::BinOp::Caret => buf.push('^'),
|
||||
operator::BinOp::Star => buf.push('*'),
|
||||
operator::BinOp::Slash => buf.push('/'),
|
||||
@ -394,26 +370,35 @@ fn fmt_bin_op<'a>(
|
||||
operator::BinOp::HasType => unreachable!(),
|
||||
operator::BinOp::Backpassing => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
buf.push(' ');
|
||||
fn fmt_bin_ops<'a>(
|
||||
buf: &mut String<'a>,
|
||||
lefts: &'a [(Located<Expr<'a>>, Located<BinOp>)],
|
||||
loc_right_side: &'a Located<Expr<'a>>,
|
||||
part_of_multi_line_bin_ops: bool,
|
||||
apply_needs_parens: Parens,
|
||||
indent: u16,
|
||||
) {
|
||||
let is_multiline = part_of_multi_line_bin_ops
|
||||
|| (&loc_right_side.value).is_multiline()
|
||||
|| lefts.iter().any(|(expr, _)| expr.value.is_multiline());
|
||||
|
||||
match &loc_right_side.value {
|
||||
Expr::BinOp((nested_left_side, nested_bin_op, nested_right_side)) => {
|
||||
fmt_bin_op(
|
||||
buf,
|
||||
nested_left_side,
|
||||
nested_bin_op,
|
||||
nested_right_side,
|
||||
is_multiline,
|
||||
apply_needs_parens,
|
||||
indent,
|
||||
);
|
||||
for (loc_left_side, loc_bin_op) in lefts {
|
||||
loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, indent);
|
||||
|
||||
if is_multiline {
|
||||
newline(buf, indent + INDENT)
|
||||
} else {
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
_ => {
|
||||
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent);
|
||||
}
|
||||
push_op(buf, loc_bin_op.value);
|
||||
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent);
|
||||
}
|
||||
|
||||
fn fmt_list<'a>(
|
||||
|
@ -47,6 +47,18 @@ pub enum BinOp {
|
||||
Backpassing,
|
||||
}
|
||||
|
||||
impl BinOp {
|
||||
/// how wide this operator is when typed out
|
||||
pub fn width(self) -> u16 {
|
||||
match self {
|
||||
Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1,
|
||||
DoubleSlash | DoublePercent | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq
|
||||
| And | Or | Pizza => 2,
|
||||
Assignment | HasType | Backpassing => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ArgSide {
|
||||
Left,
|
||||
|
@ -3,7 +3,7 @@ use crate::ident::Ident;
|
||||
use bumpalo::collections::String;
|
||||
use bumpalo::Bump;
|
||||
use roc_module::operator::{BinOp, CalledVia, UnaryOp};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_region::all::{Loc, Position, Region};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Module<'a> {
|
||||
@ -98,8 +98,13 @@ pub enum Expr<'a> {
|
||||
final_comments: &'a [CommentOrNewline<'a>],
|
||||
},
|
||||
|
||||
RecordUpdate {
|
||||
update: &'a Loc<Expr<'a>>,
|
||||
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>],
|
||||
final_comments: &'a &'a [CommentOrNewline<'a>],
|
||||
},
|
||||
|
||||
Record {
|
||||
update: Option<&'a Loc<Expr<'a>>>,
|
||||
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>],
|
||||
final_comments: &'a [CommentOrNewline<'a>],
|
||||
},
|
||||
@ -124,7 +129,7 @@ pub enum Expr<'a> {
|
||||
/// To apply by name, do Apply(Var(...), ...)
|
||||
/// To apply a tag by name, do Apply(Tag(...), ...)
|
||||
Apply(&'a Loc<Expr<'a>>, &'a [&'a Loc<Expr<'a>>], CalledVia),
|
||||
BinOp(&'a (Loc<Expr<'a>>, Loc<BinOp>, Loc<Expr<'a>>)),
|
||||
BinOps(&'a [(Loc<Expr<'a>>, Loc<BinOp>)], &'a Loc<Expr<'a>>),
|
||||
UnaryOp(&'a Loc<Expr<'a>>, Loc<UnaryOp>),
|
||||
|
||||
// Conditionals
|
||||
@ -155,7 +160,17 @@ pub enum Expr<'a> {
|
||||
MalformedClosure,
|
||||
// Both operators were non-associative, e.g. (True == False == False).
|
||||
// We should tell the author to disambiguate by grouping them with parens.
|
||||
PrecedenceConflict(Region, Loc<BinOp>, Loc<BinOp>, &'a Loc<Expr<'a>>),
|
||||
PrecedenceConflict(&'a PrecedenceConflict<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PrecedenceConflict<'a> {
|
||||
pub whole_region: Region,
|
||||
pub binop1_position: Position,
|
||||
pub binop2_position: Position,
|
||||
pub binop1: BinOp,
|
||||
pub binop2: BinOp,
|
||||
pub expr: &'a Loc<Expr<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -416,15 +416,18 @@ fn parse_expr_final<'a>(
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
||||
let mut expr = to_call(arena, expr_state.arguments, expr_state.expr);
|
||||
let right_arg = to_call(arena, expr_state.arguments, expr_state.expr);
|
||||
|
||||
for (left_arg, op) in expr_state.operators.into_iter().rev() {
|
||||
let region = Region::span_across(&left_arg.region, &expr.region);
|
||||
let new = Expr::BinOp(arena.alloc((left_arg, op, expr)));
|
||||
expr = Located::at(region, new);
|
||||
}
|
||||
let expr = if expr_state.operators.is_empty() {
|
||||
right_arg.value
|
||||
} else {
|
||||
Expr::BinOps(
|
||||
expr_state.operators.into_bump_slice(),
|
||||
arena.alloc(right_arg),
|
||||
)
|
||||
};
|
||||
|
||||
Ok((MadeProgress, expr.value, state))
|
||||
Ok((MadeProgress, expr, state))
|
||||
}
|
||||
|
||||
fn to_call<'a>(
|
||||
@ -1257,21 +1260,7 @@ fn parse_expr_end<'a>(
|
||||
// roll back space parsing
|
||||
let state = expr_state.initial;
|
||||
|
||||
if expr_state.operators.is_empty() {
|
||||
let expr = to_call(arena, expr_state.arguments, expr_state.expr);
|
||||
|
||||
Ok((MadeProgress, expr.value, state))
|
||||
} else {
|
||||
let mut expr = to_call(arena, expr_state.arguments, expr_state.expr);
|
||||
|
||||
for (left_arg, op) in expr_state.operators.into_iter().rev() {
|
||||
let region = Region::span_across(&left_arg.region, &expr.region);
|
||||
let new = Expr::BinOp(arena.alloc((left_arg, op, expr)));
|
||||
expr = Located::at(region, new);
|
||||
}
|
||||
|
||||
Ok((MadeProgress, expr.value, state))
|
||||
}
|
||||
parse_expr_final(expr_state, arena, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1352,7 +1341,6 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||
|
||||
Expr::Record {
|
||||
fields,
|
||||
update: None,
|
||||
final_comments: _,
|
||||
} => {
|
||||
let mut loc_patterns = Vec::with_capacity_in(fields.len(), arena);
|
||||
@ -1384,15 +1372,13 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||
| Expr::List { .. }
|
||||
| Expr::Closure(_, _)
|
||||
| Expr::Backpassing(_, _, _)
|
||||
| Expr::BinOp(_)
|
||||
| Expr::BinOps { .. }
|
||||
| Expr::Defs(_, _)
|
||||
| Expr::If(_, _)
|
||||
| Expr::When(_, _)
|
||||
| Expr::MalformedClosure
|
||||
| Expr::PrecedenceConflict(_, _, _, _)
|
||||
| Expr::Record {
|
||||
update: Some(_), ..
|
||||
}
|
||||
| Expr::PrecedenceConflict { .. }
|
||||
| Expr::RecordUpdate { .. }
|
||||
| Expr::UnaryOp(_, _) => Err(()),
|
||||
|
||||
Expr::Str(string) => Ok(Pattern::StrLiteral(string.clone())),
|
||||
@ -2085,10 +2071,16 @@ fn record_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'
|
||||
let (opt_update, loc_assigned_fields_with_comments) = loc_record.value;
|
||||
|
||||
// This is a record literal, not a destructure.
|
||||
let mut value = Expr::Record {
|
||||
update: opt_update.map(|loc_expr| &*arena.alloc(loc_expr)),
|
||||
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(),
|
||||
final_comments: loc_assigned_fields_with_comments.value.1,
|
||||
let mut value = match opt_update {
|
||||
Some(update) => Expr::RecordUpdate {
|
||||
update: &*arena.alloc(update),
|
||||
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(),
|
||||
final_comments: arena.alloc(loc_assigned_fields_with_comments.value.1),
|
||||
},
|
||||
None => Expr::Record {
|
||||
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(),
|
||||
final_comments: loc_assigned_fields_with_comments.value.1,
|
||||
},
|
||||
};
|
||||
|
||||
// there can be field access, e.g. `{ x : 4 }.x`
|
||||
|
@ -82,6 +82,19 @@ mod test_parse {
|
||||
}
|
||||
}
|
||||
|
||||
fn single_binop<'a>(
|
||||
arena: &'a Bump,
|
||||
args: (
|
||||
Located<Expr<'a>>,
|
||||
Located<roc_module::operator::BinOp>,
|
||||
Located<Expr<'a>>,
|
||||
),
|
||||
) -> Expr<'a> {
|
||||
let (left, op, right) = args;
|
||||
|
||||
Expr::BinOps(arena.alloc([(left, op)]), arena.alloc(right))
|
||||
}
|
||||
|
||||
// STRING LITERALS
|
||||
|
||||
fn expect_parsed_str(input: &str, expected: &str) {
|
||||
@ -414,7 +427,6 @@ mod test_parse {
|
||||
let arena = Bump::new();
|
||||
let expected = Record {
|
||||
fields: &[],
|
||||
update: None,
|
||||
final_comments: &[],
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "{}");
|
||||
@ -435,7 +447,7 @@ mod test_parse {
|
||||
&[],
|
||||
arena.alloc(Located::new(0, 0, 25, 26, Num("0"))),
|
||||
);
|
||||
let fields = &[
|
||||
let fields: &[_] = &[
|
||||
Located::new(0, 0, 16, 20, label1),
|
||||
Located::new(0, 0, 22, 26, label2),
|
||||
];
|
||||
@ -444,10 +456,10 @@ mod test_parse {
|
||||
ident: "baz",
|
||||
};
|
||||
let update_target = Located::new(0, 0, 2, 13, var);
|
||||
let expected = Record {
|
||||
update: Some(&*arena.alloc(update_target)),
|
||||
let expected = RecordUpdate {
|
||||
update: &*arena.alloc(update_target),
|
||||
fields,
|
||||
final_comments: &[],
|
||||
final_comments: &(&[] as &[_]),
|
||||
};
|
||||
|
||||
let actual = parse_expr_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }");
|
||||
@ -480,7 +492,6 @@ mod test_parse {
|
||||
Located::new(0, 0, 28, 32, label2),
|
||||
];
|
||||
let expected = Record {
|
||||
update: None,
|
||||
fields,
|
||||
final_comments: &[],
|
||||
};
|
||||
@ -494,12 +505,12 @@ mod test_parse {
|
||||
#[test]
|
||||
fn one_plus_two() {
|
||||
let arena = Bump::new();
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, Num("1")),
|
||||
Located::new(0, 0, 1, 2, Plus),
|
||||
Located::new(0, 0, 2, 3, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "1+2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -508,12 +519,12 @@ mod test_parse {
|
||||
#[test]
|
||||
fn one_minus_two() {
|
||||
let arena = Bump::new();
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, Num("1")),
|
||||
Located::new(0, 0, 1, 2, Minus),
|
||||
Located::new(0, 0, 2, 3, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "1-2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -522,7 +533,7 @@ mod test_parse {
|
||||
#[test]
|
||||
fn var_minus_two() {
|
||||
let arena = Bump::new();
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(
|
||||
0,
|
||||
0,
|
||||
@ -535,8 +546,8 @@ mod test_parse {
|
||||
),
|
||||
Located::new(0, 0, 1, 2, Minus),
|
||||
Located::new(0, 0, 2, 3, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "x-2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -545,12 +556,12 @@ mod test_parse {
|
||||
#[test]
|
||||
fn add_with_spaces() {
|
||||
let arena = Bump::new();
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, Num("1")),
|
||||
Located::new(0, 0, 3, 4, Plus),
|
||||
Located::new(0, 0, 7, 8, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "1 + 2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -559,12 +570,12 @@ mod test_parse {
|
||||
#[test]
|
||||
fn sub_with_spaces() {
|
||||
let arena = Bump::new();
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, Num("1")),
|
||||
Located::new(0, 0, 3, 4, Minus),
|
||||
Located::new(0, 0, 7, 8, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "1 - 2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -582,12 +593,12 @@ mod test_parse {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, var),
|
||||
Located::new(0, 0, 2, 3, Plus),
|
||||
Located::new(0, 0, 4, 5, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "x + 2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -604,12 +615,12 @@ mod test_parse {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, var),
|
||||
Located::new(0, 0, 2, 3, Minus),
|
||||
Located::new(0, 0, 4, 5, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "x - 2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -619,12 +630,12 @@ mod test_parse {
|
||||
fn newline_before_add() {
|
||||
let arena = Bump::new();
|
||||
let spaced_int = Expr::SpaceAfter(arena.alloc(Num("3")), &[Newline]);
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, spaced_int),
|
||||
Located::new(1, 1, 0, 1, Plus),
|
||||
Located::new(1, 1, 2, 3, Num("4")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "3 \n+ 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -634,12 +645,12 @@ mod test_parse {
|
||||
fn newline_before_sub() {
|
||||
let arena = Bump::new();
|
||||
let spaced_int = Expr::SpaceAfter(arena.alloc(Num("3")), &[Newline]);
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, spaced_int),
|
||||
Located::new(1, 1, 0, 1, Minus),
|
||||
Located::new(1, 1, 2, 3, Num("4")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "3 \n- 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -649,12 +660,12 @@ mod test_parse {
|
||||
fn newline_after_mul() {
|
||||
let arena = Bump::new();
|
||||
let spaced_int = arena.alloc(Num("4")).before(&[Newline]);
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, Num("3")),
|
||||
Located::new(0, 0, 3, 4, Star),
|
||||
Located::new(1, 1, 2, 3, spaced_int),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "3 *\n 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -664,12 +675,12 @@ mod test_parse {
|
||||
fn newline_after_sub() {
|
||||
let arena = Bump::new();
|
||||
let spaced_int = arena.alloc(Num("4")).before(&[Newline]);
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, Num("3")),
|
||||
Located::new(0, 0, 3, 4, Minus),
|
||||
Located::new(1, 1, 2, 3, spaced_int),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "3 -\n 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -679,16 +690,16 @@ mod test_parse {
|
||||
fn newline_and_spaces_before_less_than() {
|
||||
let arena = Bump::new();
|
||||
let spaced_int = arena.alloc(Num("1")).after(&[Newline]);
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 4, 5, spaced_int),
|
||||
Located::new(1, 1, 4, 5, LessThan),
|
||||
Located::new(1, 1, 6, 7, Num("2")),
|
||||
));
|
||||
);
|
||||
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
let def = Def::Body(
|
||||
arena.alloc(Located::new(0, 0, 0, 1, Identifier("x"))),
|
||||
arena.alloc(Located::new(0, 1, 4, 7, BinOp(tuple))),
|
||||
arena.alloc(Located::new(0, 1, 4, 7, single_binop(&arena, tuple))),
|
||||
);
|
||||
let loc_def = &*arena.alloc(Located::new(0, 1, 0, 7, def));
|
||||
let defs = &[loc_def];
|
||||
@ -696,7 +707,7 @@ mod test_parse {
|
||||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
// let expected = BinOp(tuple);
|
||||
// let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "x = 1\n < 2\n\n42");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -706,12 +717,12 @@ mod test_parse {
|
||||
fn comment_with_non_ascii() {
|
||||
let arena = Bump::new();
|
||||
let spaced_int = arena.alloc(Num("3")).after(&[LineComment(" 2 × 2")]);
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, spaced_int),
|
||||
Located::new(1, 1, 0, 1, Plus),
|
||||
Located::new(1, 1, 2, 3, Num("4")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "3 # 2 × 2\n+ 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -721,12 +732,12 @@ mod test_parse {
|
||||
fn comment_before_op() {
|
||||
let arena = Bump::new();
|
||||
let spaced_int = arena.alloc(Num("3")).after(&[LineComment(" test!")]);
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, spaced_int),
|
||||
Located::new(1, 1, 0, 1, Plus),
|
||||
Located::new(1, 1, 2, 3, Num("4")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "3 # test!\n+ 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -736,12 +747,12 @@ mod test_parse {
|
||||
fn comment_after_op() {
|
||||
let arena = Bump::new();
|
||||
let spaced_int = arena.alloc(Num("92")).before(&[LineComment(" test!")]);
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 2, Num("12")),
|
||||
Located::new(0, 0, 4, 5, Star),
|
||||
Located::new(1, 1, 1, 3, spaced_int),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "12 * # test!\n 92");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -752,12 +763,12 @@ mod test_parse {
|
||||
let arena = Bump::new();
|
||||
let spaced_int1 = arena.alloc(Num("3")).after(&[Newline]);
|
||||
let spaced_int2 = arena.alloc(Num("4")).before(&[Newline, Newline]);
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, spaced_int1),
|
||||
Located::new(1, 1, 0, 1, Plus),
|
||||
Located::new(3, 3, 2, 3, spaced_int2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "3 \n+ \n\n 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -776,12 +787,12 @@ mod test_parse {
|
||||
module_name: "",
|
||||
ident: "y",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, var1),
|
||||
Located::new(0, 0, 1, 2, Minus),
|
||||
Located::new(0, 0, 3, 4, var2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "x- y");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -790,12 +801,12 @@ mod test_parse {
|
||||
#[test]
|
||||
fn minus_twelve_minus_five() {
|
||||
let arena = Bump::new();
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 3, Num("-12")),
|
||||
Located::new(0, 0, 3, 4, Minus),
|
||||
Located::new(0, 0, 4, 5, Num("5")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "-12-5");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -804,12 +815,12 @@ mod test_parse {
|
||||
#[test]
|
||||
fn ten_times_eleven() {
|
||||
let arena = Bump::new();
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 2, Num("10")),
|
||||
Located::new(0, 0, 2, 3, Star),
|
||||
Located::new(0, 0, 3, 5, Num("11")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "10*11");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -818,17 +829,20 @@ mod test_parse {
|
||||
#[test]
|
||||
fn multiple_operators() {
|
||||
let arena = Bump::new();
|
||||
let inner = arena.alloc((
|
||||
Located::new(0, 0, 3, 5, Num("42")),
|
||||
Located::new(0, 0, 5, 6, Plus),
|
||||
Located::new(0, 0, 6, 9, Num("534")),
|
||||
));
|
||||
let outer = arena.alloc((
|
||||
Located::new(0, 0, 0, 2, Num("31")),
|
||||
Located::new(0, 0, 2, 3, Star),
|
||||
Located::new(0, 0, 3, 9, BinOp(inner)),
|
||||
));
|
||||
let expected = BinOp(outer);
|
||||
|
||||
let lefts = [
|
||||
(
|
||||
Located::new(0, 0, 0, 2, Num("31")),
|
||||
Located::new(0, 0, 2, 3, Star),
|
||||
),
|
||||
(
|
||||
Located::new(0, 0, 3, 5, Num("42")),
|
||||
Located::new(0, 0, 5, 6, Plus),
|
||||
),
|
||||
];
|
||||
let right = Located::new(0, 0, 6, 9, Num("534"));
|
||||
|
||||
let expected = BinOps(&lefts, &right);
|
||||
let actual = parse_expr_with(&arena, "31*42+534");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -845,12 +859,12 @@ mod test_parse {
|
||||
module_name: "",
|
||||
ident: "y",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, var1),
|
||||
Located::new(0, 0, 1, 3, Equals),
|
||||
Located::new(0, 0, 3, 4, var2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "x==y");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -867,12 +881,12 @@ mod test_parse {
|
||||
module_name: "",
|
||||
ident: "y",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
let tuple = (
|
||||
Located::new(0, 0, 0, 1, var1),
|
||||
Located::new(0, 0, 2, 4, Equals),
|
||||
Located::new(0, 0, 5, 6, var2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
);
|
||||
let expected = single_binop(&arena, tuple);
|
||||
let actual = parse_expr_with(&arena, "x == y");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -1860,7 +1874,6 @@ mod test_parse {
|
||||
let identifier_z = Located::new(2, 2, 0, 1, Identifier("z"));
|
||||
|
||||
let empty_record = Record {
|
||||
update: None,
|
||||
fields: &[],
|
||||
final_comments: &[],
|
||||
};
|
||||
@ -1956,11 +1969,14 @@ mod test_parse {
|
||||
|
||||
let loc_closure = Located::new(0, 0, 8, 23, apply);
|
||||
|
||||
let binop = Expr::BinOp(arena.alloc((
|
||||
loc_var_x,
|
||||
Located::new(2, 2, 2, 3, roc_module::operator::BinOp::Plus),
|
||||
loc_var_y,
|
||||
)));
|
||||
let binop = single_binop(
|
||||
&arena,
|
||||
(
|
||||
loc_var_x,
|
||||
Located::new(2, 2, 2, 3, roc_module::operator::BinOp::Plus),
|
||||
loc_var_y,
|
||||
),
|
||||
);
|
||||
|
||||
let spaced_binop = Expr::SpaceBefore(arena.alloc(binop), &[Newline, Newline]);
|
||||
|
||||
@ -3363,6 +3379,11 @@ mod test_parse {
|
||||
assert!(actual.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_expr_size() {
|
||||
assert_eq!(std::mem::size_of::<roc_parse::ast::Expr>(), 40);
|
||||
}
|
||||
|
||||
// PARSE ERROR
|
||||
|
||||
// TODO this should be parse error, but isn't!
|
||||
|
@ -65,7 +65,6 @@ mod test_reporting {
|
||||
problems: can_problems,
|
||||
..
|
||||
} = can_expr(arena, expr_src)?;
|
||||
dbg!(&loc_expr);
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
|
||||
for (var, name) in output.introduced_variables.name_by_var {
|
||||
|
@ -379,9 +379,9 @@ pub fn to_expr2<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
Record {
|
||||
RecordUpdate {
|
||||
fields,
|
||||
update: Some(loc_update),
|
||||
update: loc_update,
|
||||
final_comments: _,
|
||||
} => {
|
||||
let (can_update, update_out) =
|
||||
@ -435,7 +435,6 @@ pub fn to_expr2<'a>(
|
||||
|
||||
Record {
|
||||
fields,
|
||||
update: None,
|
||||
final_comments: _,
|
||||
} => {
|
||||
if fields.is_empty() {
|
||||
@ -779,7 +778,7 @@ pub fn to_expr2<'a>(
|
||||
(expr, output)
|
||||
}
|
||||
|
||||
PrecedenceConflict(_whole_region, _binop1, _binop2, _expr) => {
|
||||
PrecedenceConflict { .. } => {
|
||||
// use roc_problem::can::RuntimeError::*;
|
||||
//
|
||||
// let problem = PrecedenceProblem::BothNonAssociative(
|
||||
@ -833,9 +832,9 @@ pub fn to_expr2<'a>(
|
||||
bad_expr
|
||||
);
|
||||
}
|
||||
bad_expr @ BinOp(_) => {
|
||||
bad_expr @ BinOps { .. } => {
|
||||
panic!(
|
||||
"A binary operator did not get desugared somehow: {:#?}",
|
||||
"A binary operator chain did not get desugared somehow: {:#?}",
|
||||
bad_expr
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user