#![allow(clippy::manual_map)] use bumpalo::collections::Vec; use bumpalo::Bump; use roc_error_macros::internal_error; use roc_module::called_via::BinOp::Pizza; use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::ModuleName; use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::{ AssignedField, Collection, Pattern, RecordBuilderField, StrLiteral, StrSegment, ValueDef, WhenBranch, }; use roc_region::all::{LineInfo, Loc, Region}; // BinOp precedence logic adapted from Gluon by Markus Westerlind // https://github.com/gluon-lang/gluon - license information can be found in // the LEGAL_DETAILS file in the root directory of this distribution. // // Thank you, Markus! fn new_op_call_expr<'a>( arena: &'a Bump, left: &'a Loc>, loc_op: Loc, right: &'a Loc>, ) -> Loc> { 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); 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(Loc { value: Expr::Var { module_name, ident }, region: loc_op.region, }); Apply(loc_expr, args, CalledVia::BinOp(binop)) } }; Loc { region, value } } fn desugar_value_def<'a>( arena: &'a Bump, def: &'a ValueDef<'a>, src: &'a str, line_info: &mut Option, module_path: &str, ) -> ValueDef<'a> { use ValueDef::*; match def { Body(loc_pattern, loc_expr) => Body( desugar_loc_pattern(arena, loc_pattern, src, line_info, module_path), desugar_expr(arena, loc_expr, src, line_info, module_path), ), ann @ Annotation(_, _) => *ann, AnnotatedBody { ann_pattern, ann_type, comment, body_pattern, body_expr, } => AnnotatedBody { ann_pattern, ann_type, comment: *comment, body_pattern, body_expr: desugar_expr(arena, body_expr, src, line_info, module_path), }, Dbg { condition, preceding_comment, } => { let desugared_condition = &*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); Dbg { condition: desugared_condition, preceding_comment: *preceding_comment, } } Expect { condition, preceding_comment, } => { let desugared_condition = &*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); Expect { condition: desugared_condition, preceding_comment: *preceding_comment, } } ExpectFx { condition, preceding_comment, } => { let desugared_condition = &*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); ExpectFx { condition: desugared_condition, preceding_comment: *preceding_comment, } } } } pub fn desugar_defs<'a>( arena: &'a Bump, defs: &mut roc_parse::ast::Defs<'a>, src: &'a str, line_info: &mut Option, module_path: &str, ) { for value_def in defs.value_defs.iter_mut() { *value_def = desugar_value_def(arena, arena.alloc(*value_def), src, line_info, module_path); } } /// Reorder the expression tree based on operator precedence and associativity rules, /// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes. pub fn desugar_expr<'a>( arena: &'a Bump, loc_expr: &'a Loc>, src: &'a str, line_info: &mut Option, module_path: &str, ) -> &'a Loc> { match &loc_expr.value { Float(..) | Num(..) | NonBase10Int { .. } | SingleQuote(_) | AccessorFunction(_) | Var { .. } | Underscore { .. } | MalformedIdent(_, _) | MalformedClosure | PrecedenceConflict { .. } | MultipleRecordBuilders { .. } | UnappliedRecordBuilder { .. } | Tag(_) | OpaqueRef(_) | IngestedFile(_, _) | Crash => loc_expr, Str(str_literal) => match str_literal { StrLiteral::PlainLine(_) => loc_expr, StrLiteral::Line(segments) => { let region = loc_expr.region; let value = Str(StrLiteral::Line(desugar_str_segments( arena, segments, src, line_info, module_path, ))); arena.alloc(Loc { region, value }) } StrLiteral::Block(lines) => { let region = loc_expr.region; let new_lines = Vec::from_iter_in( lines.iter().map(|segments| { desugar_str_segments(arena, segments, src, line_info, module_path) }), arena, ); let value = Str(StrLiteral::Block(new_lines.into_bump_slice())); arena.alloc(Loc { region, value }) } }, TupleAccess(sub_expr, paths) => { let region = loc_expr.region; let loc_sub_expr = Loc { region, value: **sub_expr, }; let value = TupleAccess( &desugar_expr( arena, arena.alloc(loc_sub_expr), src, line_info, module_path, ) .value, paths, ); arena.alloc(Loc { region, value }) } RecordAccess(sub_expr, paths) => { let region = loc_expr.region; let loc_sub_expr = Loc { region, value: **sub_expr, }; let value = RecordAccess( &desugar_expr( arena, arena.alloc(loc_sub_expr), src, line_info, module_path, ) .value, paths, ); arena.alloc(Loc { region, value }) } List(items) => { let mut new_items = Vec::with_capacity_in(items.len(), arena); for item in items.iter() { new_items.push(desugar_expr(arena, item, src, line_info, module_path)); } let new_items = new_items.into_bump_slice(); let value: Expr<'a> = List(items.replace_items(new_items)); arena.alloc(Loc { region: loc_expr.region, value, }) } Record(fields) => { let mut allocated = Vec::with_capacity_in(fields.len(), arena); for field in fields.iter() { let value = desugar_field(arena, &field.value, src, line_info, module_path); allocated.push(Loc { value, region: field.region, }); } let fields = fields.replace_items(allocated.into_bump_slice()); arena.alloc(Loc { region: loc_expr.region, value: Record(fields), }) } Tuple(fields) => { let mut allocated = Vec::with_capacity_in(fields.len(), arena); for field in fields.iter() { let expr = desugar_expr(arena, field, src, line_info, module_path); allocated.push(expr); } let fields = fields.replace_items(allocated.into_bump_slice()); arena.alloc(Loc { region: loc_expr.region, value: Tuple(fields), }) } RecordUpdate { fields, update } => { // NOTE the `update` field is always a `Var { .. }`, we only desugar it to get rid of // any spaces before/after let new_update = desugar_expr(arena, update, src, line_info, module_path); let mut allocated = Vec::with_capacity_in(fields.len(), arena); for field in fields.iter() { let value = desugar_field(arena, &field.value, src, line_info, module_path); allocated.push(Loc { value, region: field.region, }); } let new_fields = fields.replace_items(allocated.into_bump_slice()); arena.alloc(Loc { region: loc_expr.region, value: RecordUpdate { update: new_update, fields: new_fields, }, }) } Closure(loc_patterns, loc_ret) => arena.alloc(Loc { region: loc_expr.region, value: Closure( desugar_loc_patterns(arena, loc_patterns, src, line_info, module_path), desugar_expr(arena, loc_ret, src, line_info, module_path), ), }), Backpassing(loc_patterns, loc_body, loc_ret) => { // loc_patterns <- loc_body // // loc_ret // first desugar the body, because it may contain |> let desugared_body = desugar_expr(arena, loc_body, src, line_info, module_path); let desugared_ret = desugar_expr(arena, loc_ret, src, line_info, module_path); let desugared_loc_patterns = desugar_loc_patterns(arena, loc_patterns, src, line_info, module_path); let closure = Expr::Closure(desugared_loc_patterns, desugared_ret); let loc_closure = Loc::at(loc_expr.region, closure); match &desugared_body.value { Expr::Apply(function, arguments, called_via) => { let mut new_arguments: Vec<'a, &'a Loc>> = Vec::with_capacity_in(arguments.len() + 1, arena); new_arguments.extend(arguments.iter()); new_arguments.push(arena.alloc(loc_closure)); let call = Expr::Apply(function, new_arguments.into_bump_slice(), *called_via); let loc_call = Loc::at(loc_expr.region, call); arena.alloc(loc_call) } _ => { // 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 = Loc::at(loc_expr.region, call); arena.alloc(loc_call) } } } RecordBuilder(_) => arena.alloc(Loc { value: UnappliedRecordBuilder(loc_expr), region: loc_expr.region, }), BinOps(lefts, right) => desugar_bin_ops( arena, loc_expr.region, lefts, right, src, line_info, module_path, ), Defs(defs, loc_ret) => { let mut defs = (*defs).clone(); desugar_defs(arena, &mut defs, src, line_info, module_path); let loc_ret = desugar_expr(arena, loc_ret, src, line_info, module_path); arena.alloc(Loc::at(loc_expr.region, Defs(arena.alloc(defs), loc_ret))) } Apply(loc_fn, loc_args, called_via) => { let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena); let mut builder_apply_exprs = None; for loc_arg in loc_args.iter() { let mut current = loc_arg.value; let arg = loop { match current { RecordBuilder(fields) => { if builder_apply_exprs.is_some() { return arena.alloc(Loc { value: MultipleRecordBuilders(loc_expr), region: loc_expr.region, }); } let builder_arg = record_builder_arg(arena, loc_arg.region, fields); builder_apply_exprs = Some(builder_arg.apply_exprs); break builder_arg.closure; } SpaceBefore(expr, _) | SpaceAfter(expr, _) => { current = *expr; } _ => break loc_arg, } }; desugared_args.push(desugar_expr(arena, arg, src, line_info, module_path)); } let desugared_args = desugared_args.into_bump_slice(); let mut apply: &Loc = arena.alloc(Loc { value: Apply( desugar_expr(arena, loc_fn, src, line_info, module_path), desugared_args, *called_via, ), region: loc_expr.region, }); match builder_apply_exprs { None => {} Some(apply_exprs) => { for expr in apply_exprs { let desugared_expr = desugar_expr(arena, expr, src, line_info, module_path); let args = std::slice::from_ref(arena.alloc(apply)); apply = arena.alloc(Loc { value: Apply(desugared_expr, args, CalledVia::RecordBuilder), region: loc_expr.region, }); } } } apply } When(loc_cond_expr, branches) => { let loc_desugared_cond = &*arena.alloc(desugar_expr( arena, loc_cond_expr, src, line_info, module_path, )); let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena); for branch in branches.iter() { let desugared_expr = desugar_expr(arena, &branch.value, src, line_info, module_path); let desugared_patterns = desugar_loc_patterns(arena, branch.patterns, src, line_info, module_path); let desugared_guard = if let Some(guard) = &branch.guard { Some(*desugar_expr(arena, guard, src, line_info, module_path)) } else { None }; desugared_branches.push(&*arena.alloc(WhenBranch { patterns: desugared_patterns, value: *desugared_expr, guard: desugared_guard, })); } let desugared_branches = desugared_branches.into_bump_slice(); arena.alloc(Loc { value: When(loc_desugared_cond, desugared_branches), region: loc_expr.region, }) } UnaryOp(loc_arg, loc_op) => { use roc_module::called_via::UnaryOp::*; let region = loc_op.region; let op = loc_op.value; // TODO desugar this in canonicalization instead, so we can work // in terms of integers exclusively and not need to create strings // which canonicalization then needs to look up, check if they're exposed, etc let value = match op { Negate => Var { module_name: ModuleName::NUM, ident: "neg", }, Not => Var { module_name: ModuleName::BOOL, ident: "not", }, }; let loc_fn_var = arena.alloc(Loc { region, value }); let desugared_args = arena.alloc([desugar_expr(arena, loc_arg, src, line_info, module_path)]); arena.alloc(Loc { value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)), region: loc_expr.region, }) } SpaceBefore(expr, _) | SpaceAfter(expr, _) => { // Since we've already begun canonicalization, spaces and parens // are no longer needed and should be dropped. desugar_expr( arena, arena.alloc(Loc { value: **expr, region: loc_expr.region, }), src, line_info, module_path, ) } ParensAround(expr) => { let desugared = desugar_expr( arena, arena.alloc(Loc { value: **expr, region: loc_expr.region, }), src, line_info, module_path, ); arena.alloc(Loc { value: ParensAround(&desugared.value), region: loc_expr.region, }) } If(if_thens, final_else_branch) => { // If does not get desugared into `when` so we can give more targeted error messages during type checking. let desugared_final_else = &*arena.alloc(desugar_expr( arena, final_else_branch, src, line_info, module_path, )); let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena); for (condition, then_branch) in if_thens.iter() { desugared_if_thens.push(( *desugar_expr(arena, condition, src, line_info, module_path), *desugar_expr(arena, then_branch, src, line_info, module_path), )); } arena.alloc(Loc { value: If(desugared_if_thens.into_bump_slice(), desugared_final_else), region: loc_expr.region, }) } Expect(condition, continuation) => { let desugared_condition = &*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); let desugared_continuation = &*arena.alloc(desugar_expr( arena, continuation, src, line_info, module_path, )); arena.alloc(Loc { value: Expect(desugared_condition, desugared_continuation), region: loc_expr.region, }) } Dbg(condition, continuation) => { // Desugars a `dbg x` statement into essentially // Inspect.toInspector x |> Inspect.apply (Inspect.init {}) |> Inspect.toDbgStr |> LowLevelDbg let desugared_continuation = &*arena.alloc(desugar_expr( arena, continuation, src, line_info, module_path, )); let region = condition.region; // Inspect.toInspector x let to_inspector_fn = Var { module_name: ModuleName::INSPECT, ident: "toInspector", }; let loc_to_inspector_fn_var = arena.alloc(Loc { value: to_inspector_fn, region, }); let desugared_to_inspector_args = arena.alloc([desugar_expr(arena, condition, src, line_info, module_path)]); let inspector = arena.alloc(Loc { value: Apply( loc_to_inspector_fn_var, desugared_to_inspector_args, CalledVia::Space, ), region, }); // Inspect.init {} let init_fn = Var { module_name: ModuleName::INSPECT, ident: "init", }; let loc_init_fn_var = arena.alloc(Loc { value: init_fn, region, }); let empty_record_args = arena.alloc([&*arena.alloc(Loc { value: Record(Collection::empty()), region, })]); let formatter = arena.alloc(Loc { value: Apply(loc_init_fn_var, empty_record_args, CalledVia::Space), region, }); // |> Inspect.apply (Inspect.init {}) let apply_fn = Var { module_name: ModuleName::INSPECT, ident: "apply", }; let loc_apply_fn_var = arena.alloc(Loc { value: apply_fn, region, }); let apply_args = arena.alloc([&*inspector, &*formatter]); let applied_formatter = arena.alloc(Loc { value: Apply(loc_apply_fn_var, apply_args, CalledVia::Space), region, }); // |> Inspect.toDbgStr let to_dbg_str = Var { module_name: ModuleName::INSPECT, ident: "toDbgStr", }; let loc_to_dbg_str_fn_var = arena.alloc(Loc { value: to_dbg_str, region, }); let to_dbg_str_args = arena.alloc([&*applied_formatter]); let dbg_str = arena.alloc(Loc { value: Apply(loc_to_dbg_str_fn_var, to_dbg_str_args, CalledVia::Space), region, }); // line_info is an option so that we can lazily calculate it. // That way it there are no `dbg` statements, we never pay the cast of scanning the source an extra time. if matches!(line_info, None) { *line_info = Some(LineInfo::new(src)); } let line_col = line_info.as_ref().unwrap().convert_pos(region.start()); let dbg_src = src .split_at(region.start().offset as usize) .1 .split_at((region.end().offset - region.start().offset) as usize) .0; // |> LowLevelDbg arena.alloc(Loc { value: LowLevelDbg( arena.alloc(( &*arena.alloc_str(&format!("{}:{}", module_path, line_col.line + 1)), &*arena.alloc_str(dbg_src), )), dbg_str, desugared_continuation, ), region: loc_expr.region, }) } LowLevelDbg(_, _, _) => unreachable!("Only exists after desugaring"), } } fn desugar_str_segments<'a>( arena: &'a Bump, segments: &'a [StrSegment<'a>], src: &'a str, line_info: &mut Option, module_path: &str, ) -> &'a [StrSegment<'a>] { Vec::from_iter_in( segments.iter().map(|segment| match segment { StrSegment::Plaintext(_) | StrSegment::Unicode(_) | StrSegment::EscapedChar(_) => { *segment } StrSegment::Interpolated(loc_expr) => { let loc_desugared = desugar_expr( arena, arena.alloc(Loc { region: loc_expr.region, value: *loc_expr.value, }), src, line_info, module_path, ); StrSegment::Interpolated(Loc { region: loc_desugared.region, value: arena.alloc(loc_desugared.value), }) } }), arena, ) .into_bump_slice() } fn desugar_field<'a>( arena: &'a Bump, field: &'a AssignedField<'a, Expr<'a>>, src: &'a str, line_info: &mut Option, module_path: &str, ) -> AssignedField<'a, Expr<'a>> { use roc_parse::ast::AssignedField::*; match field { RequiredValue(loc_str, spaces, loc_expr) => RequiredValue( Loc { value: loc_str.value, region: loc_str.region, }, spaces, desugar_expr(arena, loc_expr, src, line_info, module_path), ), OptionalValue(loc_str, spaces, loc_expr) => OptionalValue( Loc { value: loc_str.value, region: loc_str.region, }, spaces, desugar_expr(arena, loc_expr, src, line_info, module_path), ), LabelOnly(loc_str) => { // Desugar { x } into { x: x } let loc_expr = Loc { value: Var { module_name: "", ident: loc_str.value, }, region: loc_str.region, }; RequiredValue( Loc { value: loc_str.value, region: loc_str.region, }, &[], desugar_expr(arena, arena.alloc(loc_expr), src, line_info, module_path), ) } SpaceBefore(field, _spaces) => desugar_field(arena, field, src, line_info, module_path), SpaceAfter(field, _spaces) => desugar_field(arena, field, src, line_info, module_path), Malformed(string) => Malformed(string), } } fn desugar_loc_patterns<'a>( arena: &'a Bump, loc_patterns: &'a [Loc>], src: &'a str, line_info: &mut Option, module_path: &str, ) -> &'a [Loc>] { Vec::from_iter_in( loc_patterns.iter().map(|loc_pattern| Loc { region: loc_pattern.region, value: desugar_pattern(arena, loc_pattern.value, src, line_info, module_path), }), arena, ) .into_bump_slice() } fn desugar_loc_pattern<'a>( arena: &'a Bump, loc_pattern: &'a Loc>, src: &'a str, line_info: &mut Option, module_path: &str, ) -> &'a Loc> { arena.alloc(Loc { region: loc_pattern.region, value: desugar_pattern(arena, loc_pattern.value, src, line_info, module_path), }) } fn desugar_pattern<'a>( arena: &'a Bump, pattern: Pattern<'a>, src: &'a str, line_info: &mut Option, module_path: &str, ) -> Pattern<'a> { use roc_parse::ast::Pattern::*; match pattern { Identifier(_) | Tag(_) | OpaqueRef(_) | NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) | Underscore(_) | SingleQuote(_) | ListRest(_) | Malformed(_) | MalformedIdent(_, _) | QualifiedIdentifier { .. } => pattern, Apply(tag, arg_patterns) => { // Skip desugaring the tag, it should either be a Tag or OpaqueRef let desugared_arg_patterns = Vec::from_iter_in( arg_patterns.iter().map(|arg_pattern| Loc { region: arg_pattern.region, value: desugar_pattern(arena, arg_pattern.value, src, line_info, module_path), }), arena, ) .into_bump_slice(); Apply(tag, desugared_arg_patterns) } RecordDestructure(field_patterns) => { let mut allocated = Vec::with_capacity_in(field_patterns.len(), arena); for field_pattern in field_patterns.iter() { let value = desugar_pattern(arena, field_pattern.value, src, line_info, module_path); allocated.push(Loc { value, region: field_pattern.region, }); } let field_patterns = field_patterns.replace_items(allocated.into_bump_slice()); RecordDestructure(field_patterns) } RequiredField(name, field_pattern) => RequiredField( name, desugar_loc_pattern(arena, field_pattern, src, line_info, module_path), ), OptionalField(name, expr) => { OptionalField(name, desugar_expr(arena, expr, src, line_info, module_path)) } Tuple(patterns) => { let mut allocated = Vec::with_capacity_in(patterns.len(), arena); for pattern in patterns.iter() { let value = desugar_pattern(arena, pattern.value, src, line_info, module_path); allocated.push(Loc { value, region: pattern.region, }); } let patterns = patterns.replace_items(allocated.into_bump_slice()); Tuple(patterns) } List(patterns) => { let mut allocated = Vec::with_capacity_in(patterns.len(), arena); for pattern in patterns.iter() { let value = desugar_pattern(arena, pattern.value, src, line_info, module_path); allocated.push(Loc { value, region: pattern.region, }); } let patterns = patterns.replace_items(allocated.into_bump_slice()); List(patterns) } As(sub_pattern, symbol) => As( desugar_loc_pattern(arena, sub_pattern, src, line_info, module_path), symbol, ), SpaceBefore(sub_pattern, _spaces) => { desugar_pattern(arena, *sub_pattern, src, line_info, module_path) } SpaceAfter(sub_pattern, _spaces) => { desugar_pattern(arena, *sub_pattern, src, line_info, module_path) } } } struct RecordBuilderArg<'a> { closure: &'a Loc>, apply_exprs: Vec<'a, &'a Loc>>, } fn record_builder_arg<'a>( arena: &'a Bump, region: Region, fields: Collection<'a, Loc>>, ) -> RecordBuilderArg<'a> { let mut record_fields = Vec::with_capacity_in(fields.len(), arena); let mut apply_exprs = Vec::with_capacity_in(fields.len(), arena); let mut apply_field_names = Vec::with_capacity_in(fields.len(), arena); // Build the record that the closure will return and gather apply expressions for field in fields.iter() { let mut current = field.value; let new_field = loop { match current { RecordBuilderField::Value(label, spaces, expr) => { break AssignedField::RequiredValue(label, spaces, expr) } RecordBuilderField::ApplyValue(label, _, _, expr) => { apply_field_names.push(label); apply_exprs.push(expr); let var = arena.alloc(Loc { region: label.region, value: Expr::Var { module_name: "", ident: arena.alloc("#".to_owned() + label.value), }, }); break AssignedField::RequiredValue(label, &[], var); } RecordBuilderField::LabelOnly(label) => break AssignedField::LabelOnly(label), RecordBuilderField::SpaceBefore(sub_field, _) => { current = *sub_field; } RecordBuilderField::SpaceAfter(sub_field, _) => { current = *sub_field; } RecordBuilderField::Malformed(malformed) => { break AssignedField::Malformed(malformed) } } }; record_fields.push(Loc { value: new_field, region: field.region, }); } let record_fields = fields.replace_items(record_fields.into_bump_slice()); let mut body = arena.alloc(Loc { value: Record(record_fields), region, }); // Construct the builder's closure // // { x: #x, y: #y, z: 3 } // \#y -> { x: #x, y: #y, z: 3 } // \#x -> \#y -> { x: #x, y: #y, z: 3 } for label in apply_field_names.iter().rev() { let name = arena.alloc("#".to_owned() + label.value); let ident = roc_parse::ast::Pattern::Identifier(name); let arg_pattern = arena.alloc(Loc { value: ident, region: label.region, }); body = arena.alloc(Loc { value: Closure(std::slice::from_ref(arg_pattern), body), region, }); } RecordBuilderArg { closure: body, apply_exprs, } } // TODO move this desugaring to canonicalization, so we can use Symbols instead of strings #[inline(always)] fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { use self::BinOp::*; match binop { Caret => (ModuleName::NUM, "pow"), Star => (ModuleName::NUM, "mul"), Slash => (ModuleName::NUM, "div"), DoubleSlash => (ModuleName::NUM, "divTrunc"), Percent => (ModuleName::NUM, "rem"), Plus => (ModuleName::NUM, "add"), Minus => (ModuleName::NUM, "sub"), Equals => (ModuleName::BOOL, "isEq"), NotEquals => (ModuleName::BOOL, "isNotEq"), LessThan => (ModuleName::NUM, "isLt"), GreaterThan => (ModuleName::NUM, "isGt"), LessThanOrEq => (ModuleName::NUM, "isLte"), GreaterThanOrEq => (ModuleName::NUM, "isGte"), And => (ModuleName::BOOL, "and"), Or => (ModuleName::BOOL, "or"), Pizza => unreachable!("Cannot desugar the |> operator"), Assignment => unreachable!("Cannot desugar the = operator"), IsAliasType => unreachable!("Cannot desugar the : operator"), IsOpaqueType => unreachable!("Cannot desugar the := operator"), Backpassing => unreachable!("Cannot desugar the <- operator"), } } fn desugar_bin_ops<'a>( arena: &'a Bump, whole_region: Region, lefts: &'a [(Loc>, Loc)], right: &'a Loc>, src: &'a str, line_info: &mut Option, module_path: &str, ) -> &'a Loc> { let mut arg_stack: Vec<&'a Loc> = Vec::with_capacity_in(lefts.len() + 1, arena); let mut op_stack: Vec> = Vec::with_capacity_in(lefts.len(), arena); for (loc_expr, loc_op) in lefts { arg_stack.push(desugar_expr(arena, loc_expr, src, line_info, module_path)); 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, src, line_info, module_path); 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 Loc>), Push(Loc), Skip, } fn run_binop_step<'a>( arena: &'a Bump, whole_region: Region, arg_stack: &mut Vec<&'a Loc>>, op_stack: &mut Vec>, next_op: Loc, ) -> Result<(), &'a Loc>> { 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 Loc>>, op_stack: &mut Vec>, next_op: Loc, ) -> Step<'a> { use roc_module::called_via::Associativity::*; use std::cmp::Ordering; 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))); Step::Push(next_op) } Ordering::Greater => { // Swap op_stack.push(stack_op); op_stack.push(next_op); Step::Skip } 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(); arg_stack .push(arena.alloc(new_op_call_expr(arena, left, stack_op, right))); Step::Push(next_op) } (RightAssociative, RightAssociative) => { // Swap op_stack.push(stack_op); op_stack.push(next_op); Step::Skip } (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)); Step::Error(arena.alloc(Loc { 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! internal_error!("BinOps had the same associativity, but different precedence. This should never happen!"); } } } } } None => { op_stack.push(next_op); Step::Skip } } }