diff --git a/src/ARCHITECTURE.md b/src/ARCHITECTURE.md index 8017fada08..4ab7e541c8 100644 --- a/src/ARCHITECTURE.md +++ b/src/ARCHITECTURE.md @@ -12,7 +12,7 @@ For example, parsing would translate this string... ...into this `Expr` value: - Operator(Int(1), Plus, Int(2)) + BinOp(Int(1), Plus, Int(2)) > Technically it would be `Box::new(Int(1))` and `Box::new(Int(2))`, but that's beside the point for now. @@ -58,10 +58,10 @@ For example, let's say we had this code: The parser will translate this into the following `Expr`: - Operator( + BinOp( Int(1), Plus, - Operator(Int(2), Minus, Int(3)) + BinOp(Int(2), Minus, Int(3)) ) The `eval` function will take this `Expr` and translate it into this much simpler `Expr`: @@ -74,18 +74,18 @@ At this point it's become so simple that we can display it to the end user as th `eval` accomplishes this by doing a `match` on an `Expr` and resolving every operation it encounters. For example, when it first sees this: - Operator( + BinOp( Int(1), Plus, - Operator(Int(8), Minus, Int(3)) + BinOp(Int(8), Minus, Int(3)) ) The first thing it does is to call `eval` on the right `Expr` values on either side of the `Plus`. That results in: 1. Calling `eval` on `Int(1)`, which returns `Int(1)` since it can't be reduced any further. -2. Calling `eval` on `Operator(Int(8), Minus, Int(3))`, which in fact can be reduced further. +2. Calling `eval` on `BinOp(Int(8), Minus, Int(3))`, which in fact can be reduced further. -Since the second call to `eval` will match on another `Operator`, it's once again going to recursively call `eval` on both of its `Expr` values. Since those are both `Int` values, though, their `eval` calls will return them right away without doing anything else. +Since the second call to `eval` will match on another `BinOp`, it's once again going to recursively call `eval` on both of its `Expr` values. Since those are both `Int` values, though, their `eval` calls will return them right away without doing anything else. Now that it's evaluated the expressions on either side of the `Minus`, `eval` will look at the particular operator being applied to those expressoins (in this case, a minus operator) and check to see if the expressions it was given work with that operation. @@ -97,7 +97,7 @@ Assuming there's no type problem, `eval` can go ahead and run the Rust code of ` That concludes our original recursive call to `eval`, after which point we'll be evaluating this expression: - Operator( + BinOp( Int(1), Plus, Int(5) diff --git a/src/can/mod.rs b/src/can/mod.rs index ca17c06434..a6c9262c62 100644 --- a/src/can/mod.rs +++ b/src/can/mod.rs @@ -52,7 +52,7 @@ pub fn canonicalize_declaration<'a>( // operator precedence and associativity rules), before doing any other canonicalization. // // If we did this *during* canonicalization, then each time we - // visited an Operator node we'd recursively try to apply this to each of its nested + // visited an BinOp node we'd recursively try to apply this to each of its nested // operators, and thena again on *their* nested operators, ultimately applying the // rules multiple times unnecessarily. let loc_expr = operator::desugar(arena, &loc_expr); @@ -648,8 +648,11 @@ fn canonicalize_expr( sub_expr ); } - ast::Expr::Operator((_, loc_op, _)) => { - panic!("An operator did not get desugared somehow: {:?}", loc_op); + ast::Expr::BinOp((_, loc_op, _)) => { + panic!("A binary operator did not get desugared somehow: {:?}", loc_op); + } + ast::Expr::UnaryOp(_, op) => { + panic!("A binary operator did not get desugared somehow: {:?}", loc_op); } }; diff --git a/src/can/operator.rs b/src/can/operator.rs index 6ace70b4a7..57203e345d 100644 --- a/src/can/operator.rs +++ b/src/can/operator.rs @@ -1,19 +1,19 @@ use bumpalo::collections::Vec; use bumpalo::Bump; -use operator::Operator::Pizza; -use operator::{CalledVia, Operator}; +use operator::BinOp::Pizza; +use operator::{BinOp, CalledVia}; use parse::ast::Expr::{self, *}; use parse::ast::{AssignedField, Def}; use region::{Located, Region}; use types; -// Operator precedence logic adapted from Gluon by Markus Westerlind, MIT licensed +// 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>( arena: &'a Bump, left: Located>, - op: Located, + op: Located, right: Located>, ) -> Located> { let new_region = Region { @@ -23,7 +23,7 @@ fn new_op_expr<'a>( end_line: right.region.end_line, end_col: right.region.end_col, }; - let new_expr = Expr::Operator(arena.alloc((left, op, right))); + let new_expr = Expr::BinOp(arena.alloc((left, op, right))); Located { value: new_expr, @@ -32,7 +32,7 @@ fn new_op_expr<'a>( } /// Reorder the expression tree based on operator precedence and associativity rules, -/// then replace the Operator nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes. +/// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes. pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Located> { use operator::Associativity::*; use std::cmp::Ordering; @@ -51,6 +51,7 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Loca | MalformedIdent(_) | MalformedClosure | PrecedenceConflict(_, _, _) + | UnaryOp(_, _) | Variant(_, _) => loc_expr, Field(sub_expr, paths) => arena.alloc(Located { @@ -91,10 +92,10 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Loca region: loc_expr.region, value: Closure(loc_patterns, desugar(arena, loc_ret)), }), - Operator(_) => { + BinOp(_) => { let mut infixes = Infixes::new(arena.alloc(loc_expr)); let mut arg_stack: Vec<&'a Located> = Vec::new_in(arena); - let mut op_stack: Vec> = Vec::new_in(arena); + let mut op_stack: Vec> = Vec::new_in(arena); while let Some(token) = infixes.next() { match token { @@ -179,7 +180,7 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Loca // // 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!("Operators had the same associativity, but different precedence. This should never happen!"); + panic!("BinOps had the same associativity, but different precedence. This should never happen!"); } } } @@ -215,7 +216,7 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Loca region: loc_op.region, }); - Apply(loc_expr, args, CalledVia::Operator(binop)) + Apply(loc_expr, args, CalledVia::BinOp(binop)) } }; @@ -226,7 +227,6 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Loca arg_stack.pop().unwrap() } - Defs(defs, loc_ret) => { let mut desugared_defs = Vec::with_capacity_in(defs.len(), arena); @@ -253,12 +253,8 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Loca region: loc_expr.region, }) } - Case( - loc_cond_expr, - branches, // Vec<'a, &'a (Loc>, Loc>)>, - ) => { + Case(loc_cond_expr, branches) => { let loc_desugared_cond = &*arena.alloc(desugar(arena, &loc_cond_expr)); - let desugared_branches = Vec::with_capacity_in(branches.len(), arena); arena.alloc(Located { @@ -266,8 +262,31 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Loca region: loc_expr.region, }) } + UnaryOp(loc_arg, loc_op) => { + use operator::UnaryOp::*; + + let region = loc_op.region; + let op = loc_op.value; + let value = match op { + Negate => Var( + bumpalo::vec![in arena; types::MOD_NUM].into_bump_slice(), + "negate", + ), + Not => Var( + bumpalo::vec![in arena; types::MOD_BOOL].into_bump_slice(), + "not", + ), + }; + let loc_fn_var = arena.alloc(Located { region, value }); + let desugared_args = bumpalo::vec![in arena; desugar(arena, loc_arg)]; + + arena.alloc(Located { + value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)), + region: loc_expr.region, + }) + } SpaceBefore(expr, _) => { - // Since we've already begun canonicalization, these are no longer needed + // Since we've already begun canonicalization, spaces are no longer needed // and should be dropped. desugar( arena, @@ -285,7 +304,7 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Loca ) } SpaceAfter(expr, _) => { - // Since we've already begun canonicalization, these are no longer needed + // Since we've already begun canonicalization, spaces are no longer needed // and should be dropped. desugar( arena, @@ -346,8 +365,8 @@ fn desugar_field<'a>( } #[inline(always)] -fn desugar_binop<'a>(binop: &Operator, arena: &'a Bump) -> (&'a [&'a str], &'a str) { - use self::Operator::*; +fn desugar_binop<'a>(binop: &BinOp, arena: &'a Bump) -> (&'a [&'a str], &'a str) { + use self::BinOp::*; match binop { Caret => ( @@ -421,7 +440,7 @@ fn desugar_binop<'a>(binop: &Operator, arena: &'a Bump) -> (&'a [&'a str], &'a s #[derive(Debug, Clone, PartialEq)] enum InfixToken<'a> { Arg(&'a Located>), - Op(Located), + Op(Located), } /// An iterator that takes an expression that has had its operators grouped @@ -452,7 +471,7 @@ struct Infixes<'a> { /// The next part of the expression that we need to flatten remaining_expr: Option<&'a Located>>, /// Cached operator from a previous iteration - next_op: Option>, + next_op: Option>, } impl<'a> Infixes<'a> { @@ -474,7 +493,7 @@ impl<'a> Iterator for Infixes<'a> { .remaining_expr .take() .map(|loc_expr| match loc_expr.value { - Expr::Operator((left, op, right)) => { + Expr::BinOp((left, op, right)) => { self.remaining_expr = Some(right); self.next_op = Some(op.clone()); diff --git a/src/can/problem.rs b/src/can/problem.rs index 6f9dd629ea..7f8f4ecc29 100644 --- a/src/can/problem.rs +++ b/src/can/problem.rs @@ -1,6 +1,6 @@ use can::pattern::PatternType; use ident::{Ident, VariantName}; -use operator::Operator; +use operator::BinOp; use region::{Located, Region}; /// Problems that can occur in the course of canonicalization. @@ -21,7 +21,7 @@ pub enum Problem { #[derive(Clone, Debug, PartialEq)] pub enum PrecedenceProblem { - BothNonAssociative(Located, Located), + BothNonAssociative(Located, Located), } #[derive(Clone, Debug, PartialEq)] diff --git a/src/expr.rs b/src/expr.rs index 0c4299aea7..7312dee4a3 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,4 +1,4 @@ -use operator::Operator; +use operator::BinOp; use region::Located; use std::fmt; @@ -31,7 +31,7 @@ pub enum Expr { // Sugar If(Box>, Box>, Box>), - Operator(Box>, Located, Box>), + BinOp(Box>, Located, Box>), } #[derive(Clone, Debug, PartialEq)] @@ -112,7 +112,7 @@ impl Expr { .map(|(pattern, body)| (pattern, body.with_value(body.value.walk(transform)))) .collect(), ), - Operator(loc_left, loc_op, loc_right) => Operator( + BinOp(loc_left, loc_op, loc_right) => BinOp( Box::new(loc_left.with_value(loc_left.value.walk(transform))), loc_op, Box::new(loc_right.with_value(loc_right.value.walk(transform))), diff --git a/src/operator.rs b/src/operator.rs index 4f16f9e08d..ce62e293ce 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -1,4 +1,4 @@ -use self::Operator::*; +use self::BinOp::*; use std::cmp::Ordering; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -7,14 +7,22 @@ pub enum CalledVia { Space, /// Calling with an operator, e.g. (bar |> foo) or (1 + 2) - Operator(Operator), + BinOp(BinOp), - /// Calling with the unary (!) operator, e.g. (!foo bar baz) - UnaryNot, + /// Calling with a unary operator, e.g. (!foo bar baz) or (-foo bar baz) + UnaryOp(UnaryOp), } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Operator { +pub enum UnaryOp { + /// (-), e.g. (-x) + Negate, + /// (!), e.g. (!x) + Not, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BinOp { // highest precedence Caret, Star, @@ -62,7 +70,7 @@ pub enum Associativity { NonAssociative, } -impl Operator { +impl BinOp { pub fn associativity(&self) -> Associativity { use self::Associativity::*; @@ -90,13 +98,13 @@ impl Operator { } } -impl PartialOrd for Operator { +impl PartialOrd for BinOp { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for Operator { +impl Ord for BinOp { fn cmp(&self, other: &Self) -> Ordering { self.precedence().cmp(&other.precedence()) } diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 2ca66171bc..66d576ab52 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -2,7 +2,7 @@ use bumpalo::collections::String; use bumpalo::collections::Vec; use bumpalo::Bump; use operator::CalledVia; -use operator::Operator; +use operator::{BinOp, UnaryOp}; use parse::ident::Ident; use region::{Loc, Region}; @@ -137,7 +137,8 @@ pub enum Expr<'a> { /// To apply by name, do Apply(Var(...), ...) /// To apply a variant by name, do Apply(Variant(...), ...) Apply(&'a Loc>, Vec<'a, &'a Loc>>, CalledVia), - Operator(&'a (Loc>, Loc, Loc>)), + BinOp(&'a (Loc>, Loc, Loc>)), + UnaryOp(&'a Loc>, Loc), // Conditionals If(&'a (Loc>, Loc>, Loc>)), @@ -156,7 +157,7 @@ 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(Loc, Loc, &'a Loc>), + PrecedenceConflict(Loc, Loc, &'a Loc>), } #[derive(Debug, Clone, PartialEq)] diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 79c7c05e90..7031016c15 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -25,7 +25,7 @@ pub mod type_annotation; /// parsing the file use bumpalo::collections::Vec; use bumpalo::Bump; -use operator::{CalledVia, Operator}; +use operator::{BinOp, CalledVia, UnaryOp}; use parse; use parse::ast::{ AppHeader, AssignedField, Attempting, CommentOrNewline, Def, Expr, HeaderEntry, @@ -39,12 +39,12 @@ use parse::ident::{ident, unqualified_ident, variant_or_ident, Ident}; use parse::number_literal::number_literal; use parse::parser::{ allocated, and, attempt, between, char, either, loc, map, map_with_arena, not, not_followed_by, - one_of16, one_of2, one_of5, one_of6, one_of9, one_or_more, optional, skip_first, skip_second, + one_of10, one_of16, one_of2, one_of5, one_of6, one_or_more, optional, skip_first, skip_second, string, then, unexpected, unexpected_eof, zero_or_more, Either, Fail, FailReason, ParseResult, Parser, State, }; use parse::record::record; -use region::Located; +use region::{Located, Region}; pub fn module<'a>() -> impl Parser<'a, Module<'a>> { one_of2(interface_module(), app_module()) @@ -147,17 +147,6 @@ fn mod_header_entry<'a>() -> impl Parser<'a, HeaderEntry<'a>> { ) } -// pub fn app<'a>() -> impl Parser<'a, Module<'a>> { -// skip_first(string("app using Echo")) -// } - -// pub fn api_bridge<'a>() -> impl Parser<'a, Module<'a>> { -// and( -// skip_first(string("api bridge"), space1_around(ident())), -// skip_first(string("exposes"), space1_around(ident())), -// ) -// } - pub fn expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { // Recursive parsers must not directly invoke functions which return (impl Parser), // as this causes rustc to stack overflow. Thus, parse_expr must be a @@ -170,13 +159,14 @@ fn loc_parse_expr_body_without_operators<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Located>> { - one_of9( + one_of10( loc_parenthetical_expr(min_indent), loc(string_literal()), loc(number_literal()), loc(closure(min_indent)), loc(record_literal(min_indent)), loc(list_literal(min_indent)), + loc(unary_op(min_indent)), loc(case_expr(min_indent)), loc(if_expr(min_indent)), loc(ident_etc(min_indent)), @@ -184,6 +174,28 @@ fn loc_parse_expr_body_without_operators<'a>( .parse(arena, state) } +/// Unary (!) or (-) +/// +/// e.g. `!x` or `-x` +pub fn unary_op<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { + one_of2( + map_with_arena( + skip_first( + char('!'), + loc(move |arena, state| parse_expr(min_indent, arena, state)), + ), + |arena, loc_expr| Expr::UnaryOp(arena.alloc(loc_expr), UnaryOp::Not), + ), + map_with_arena( + skip_first( + char('-'), + loc(move |arena, state| parse_expr(min_indent, arena, state)), + ), + |arena, loc_expr| Expr::UnaryOp(arena.alloc(loc_expr), UnaryOp::Negate), + ), + ) +} + fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Expr<'a>> { let expr_parser = map_with_arena( and( @@ -191,11 +203,11 @@ fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseRe move |arena, state| loc_parse_expr_body_without_operators(min_indent, arena, state), // Parse the operator, with optional spaces before it. // - // Since spaces can only wrap an Expr, not an Operator, we have to first + // Since spaces can only wrap an Expr, not an BinOp, we have to first // parse the spaces and then attach them retroactively to the expression // preceding the operator (the one we parsed before considering operators). optional(and( - and(space0(min_indent), loc(operator())), + and(space0(min_indent), loc(binop())), // The spaces *after* the operator can be attached directly to // the expression following the operator. space0_before( @@ -216,7 +228,7 @@ fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseRe }; let tuple = arena.alloc((loc_expr1, loc_op, loc_expr2)); - Expr::Operator(tuple) + Expr::BinOp(tuple) } None => loc_expr1.value, }, @@ -403,7 +415,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result, | Expr::Field(_, _) | Expr::List(_) | Expr::Closure(_, _) - | Expr::Operator(_) + | Expr::BinOp(_) | Expr::Defs(_, _) | Expr::If(_) | Expr::Case(_, _) @@ -635,13 +647,14 @@ fn loc_parse_function_arg<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Located>> { - one_of9( + one_of10( loc_parenthetical_expr(min_indent), loc(string_literal()), loc(number_literal()), loc(closure(min_indent)), loc(record_literal(min_indent)), loc(list_literal(min_indent)), + loc(unary_op(min_indent)), loc(case_expr(min_indent)), loc(if_expr(min_indent)), loc(ident_without_apply()), @@ -915,7 +928,102 @@ pub fn if_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { } pub fn loc_function_args<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located>>> { - one_or_more(space1_before(loc_function_arg(min_indent), min_indent)) + // The rules for (-) are special-cased, and they come up here. They work like this: + // + // x - y # "x minus y" + // x-y # "x minus y" + // x- y # "x minus y" (probably written in a rush) + // x -y # "call x, passing (-y)" + // + // Since operators have higher precedence than function application, + // any time we encounter a '-' we need to check the spaces around it + // to see if it's a binop. If it is, we're immediately done parsing args! + one_or_more(move |arena, original_state| { + { + // First, parse spaces. (We have to do this regardless, because these are + // function args we're parsing.) + // + // The guaranteed presence of spaces means we don't need to consider + // the (x-y) and (x- y) scenarios, since those can only come up when + // the '-' has no spaces before it. + then(space1(min_indent), |arena, state, spaces| { + let mut chars = state.input.chars(); + + // First, make sure there's actually a '-' character next. + // Otherwise, it's definitely neither a unary nor a binary (-) operator! + let (loc_expr, state) = if let Some('-') = chars.next() { + // Now look ahead to see if there's a space after it. + // We won't consume that space, so we don't need to parse the + // entire thing - just the first char of it. + if let Some(potential_space) = chars.next() { + match potential_space { + ' ' | '\n' | '#' => { + // This is '-' with space(s) both before it and after it, + // e.g. (x - y) + // + // This must be a binary (-) operator, not unary! + // That means we're immediately done parsing function arguments. + // + // Error out so the binop parsing logic can take over from here. + return Err(( + Fail { + reason: FailReason::Unexpected('-', state.len_region(1)), + attempting: state.attempting, + }, + // Backtrack to before we parsed the spaces; + // the binop is going to parse those again. + original_state, + )); + } + _ => { + // This is '-' with space(s) before it, but none after, + // e.g. (x -y) + // + // This must be a unary (-) operator, not binary! + // + // All we consumed here was the one '-' char, so + // calculate the region and new state based on that. + let region = Region { + start_col: state.column, + start_line: state.line, + end_col: state.column + 1, + end_line: state.line, + }; + let state = state.advance_without_indenting(1)?; + // Continue parsing the function arg as normal. + let (loc_expr, state) = + loc_function_arg(min_indent).parse(arena, state)?; + let value = Expr::UnaryOp(arena.alloc(loc_expr), UnaryOp::Negate); + + (Located { region, value }, state) + } + } + } else { + // EOF immediately after (-) is a syntax error. + return Err(( + Fail { + reason: FailReason::Eof(state.len_region(1)), + attempting: state.attempting, + }, + state, + )); + } + } else { + // There isn't a '-' next, so proceed as normal. + loc_function_arg(min_indent).parse(arena, state)? + }; + + Ok(( + Located { + region: loc_expr.region, + value: Expr::SpaceBefore(arena.alloc(loc_expr.value), spaces), + }, + state, + )) + }) + } + .parse(arena, original_state) + }) } /// When we parse an ident like `foo ` it could be any of these: @@ -1043,26 +1151,26 @@ fn ident_to_expr<'a>(src: Ident<'a>) -> Expr<'a> { } } -pub fn operator<'a>() -> impl Parser<'a, Operator> { +fn binop<'a>() -> impl Parser<'a, BinOp> { one_of16( // Sorted from highest to lowest predicted usage in practice, // so that successful matches shorrt-circuit as early as possible. - map(string("|>"), |_| Operator::Pizza), - map(string("=="), |_| Operator::Equals), - map(string("&&"), |_| Operator::And), - map(string("||"), |_| Operator::Or), - map(char('+'), |_| Operator::Plus), - map(char('-'), |_| Operator::Minus), - map(char('*'), |_| Operator::Star), - map(char('/'), |_| Operator::Slash), - map(char('<'), |_| Operator::LessThan), - map(char('>'), |_| Operator::GreaterThan), - map(string("<="), |_| Operator::LessThanOrEq), - map(string(">="), |_| Operator::GreaterThanOrEq), - map(char('^'), |_| Operator::Caret), - map(char('%'), |_| Operator::Percent), - map(string("//"), |_| Operator::DoubleSlash), - map(string("%%"), |_| Operator::DoublePercent), + map(string("|>"), |_| BinOp::Pizza), + map(string("=="), |_| BinOp::Equals), + map(string("&&"), |_| BinOp::And), + map(string("||"), |_| BinOp::Or), + map(char('+'), |_| BinOp::Plus), + map(char('*'), |_| BinOp::Star), + map(char('-'), |_| BinOp::Minus), + map(char('/'), |_| BinOp::Slash), + map(char('<'), |_| BinOp::LessThan), + map(char('>'), |_| BinOp::GreaterThan), + map(string("<="), |_| BinOp::LessThanOrEq), + map(string(">="), |_| BinOp::GreaterThanOrEq), + map(char('^'), |_| BinOp::Caret), + map(char('%'), |_| BinOp::Percent), + map(string("//"), |_| BinOp::DoubleSlash), + map(string("%%"), |_| BinOp::DoublePercent), ) } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 869d811289..db28c7b45b 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -136,6 +136,22 @@ impl<'a> State<'a> { _ => Err(line_too_long(self.attempting, self.clone())), } } + + /// Returns a Region corresponding to the current state, but + /// with the end_col advanced by the given amount. This is + /// useful when parsing something "manually" (using input.chars()) + /// and thus wanting a Region while not having access to loc(). + pub fn len_region(&self, length: u16) -> Region { + Region { + start_col: self.column, + start_line: self.line, + end_col: self + .column + .checked_add(length) + .unwrap_or_else(|| panic!("len_region overflowed")), + end_line: self.line, + } + } } #[test] diff --git a/src/pretty_print_types.rs b/src/pretty_print_types.rs index 067b20215f..87249dac8a 100644 --- a/src/pretty_print_types.rs +++ b/src/pretty_print_types.rs @@ -42,7 +42,7 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, use_p ), EmptyRecord => buf.push_str(EMPTY_RECORD), Func(args, ret) => write_fn(args, ret, subs, buf, use_parens), - Operator(l_arg, r_arg, ret) => write_fn(vec![l_arg, r_arg], ret, subs, buf, use_parens), + BinOp(l_arg, r_arg, ret) => write_fn(vec![l_arg, r_arg], ret, subs, buf, use_parens), Erroneous(problem) => { buf.push_str(&format!("", problem)); } diff --git a/src/solve.rs b/src/solve.rs index 7072686c15..2e2e901756 100644 --- a/src/solve.rs +++ b/src/solve.rs @@ -108,12 +108,12 @@ fn type_to_variable<'a>(subs: &'a mut Subs, typ: Type) -> Variable { subs.fresh(Descriptor::from(content)) } - Operator(box_type) => { + BinOp(box_type) => { let op_type = *box_type; let l_var = type_to_variable(subs, op_type.left); let r_var = type_to_variable(subs, op_type.right); let ret_var = type_to_variable(subs, op_type.ret); - let content = Content::Structure(FlatType::Operator(l_var, r_var, ret_var)); + let content = Content::Structure(FlatType::BinOp(l_var, r_var, ret_var)); subs.fresh(Descriptor::from(content)) } diff --git a/src/subs.rs b/src/subs.rs index c0b9c38bfe..f84426c7b4 100644 --- a/src/subs.rs +++ b/src/subs.rs @@ -145,7 +145,7 @@ pub enum FlatType { args: Vec, }, Func(Vec, Variable), - Operator(Variable, Variable, Variable), + BinOp(Variable, Variable, Variable), Erroneous(Problem), EmptyRecord, } diff --git a/src/types.rs b/src/types.rs index 91f26f1d8c..745d9d746e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,13 +1,13 @@ use can::symbol::Symbol; use collections::ImMap; -use operator::{ArgSide, Operator}; +use operator::{ArgSide, BinOp}; use region::Located; use region::Region; use subs::Variable; // The standard modules pub const MOD_FLOAT: &'static str = "Float"; -pub const MOD_BOOL: &'static str = "Float"; +pub const MOD_BOOL: &'static str = "Bool"; pub const MOD_INT: &'static str = "Int"; pub const MOD_STR: &'static str = "Str"; pub const MOD_LIST: &'static str = "List"; @@ -25,7 +25,7 @@ pub enum Type { EmptyRec, /// A function. The types of its arguments, then the type of its return value. Function(Vec, Box), - Operator(Box), + BinOp(Box), /// Applying a type to some arguments (e.g. Map.Map String Int) Apply { module_name: Box, @@ -38,19 +38,6 @@ pub enum Type { } impl Type { - pub fn for_operator(op: Operator) -> OperatorType { - use self::Operator::*; - - match op { - Slash => op_type(Type::float(), Type::float(), Type::float()), - DoubleSlash => op_type(Type::int(), Type::int(), Type::int()), - // TODO actually, don't put these in types.rs - instead, replace them - // with an equivalence to their corresponding stdlib functions - e.g. - // Slash generates a new variable and an Eq constraint with Float.div. - _ => panic!("TODO types for operator {:?}", op), - } - } - pub fn num(args: Vec) -> Self { Type::Apply { module_name: MOD_NUM.into(), @@ -97,12 +84,12 @@ impl Type { } } -fn op_type(left: Type, right: Type, ret: Type) -> OperatorType { - OperatorType { left, right, ret } +fn op_type(left: Type, right: Type, ret: Type) -> BinOpType { + BinOpType { left, right, ret } } #[derive(PartialEq, Eq, Debug, Clone)] -pub struct OperatorType { +pub struct BinOpType { pub left: Type, pub right: Type, pub ret: Type, @@ -137,8 +124,8 @@ pub enum Reason { NamedFnArg(String /* function name */, u8 /* arg index */), AnonymousFnCall(u8 /* arity */), NamedFnCall(String /* function name */, u8 /* arity */), - OperatorArg(Operator, ArgSide), - OperatorRet(Operator), + BinOpArg(BinOp, ArgSide), + BinOpRet(BinOp), FloatLiteral, IntLiteral, InterpolatedStringVar, diff --git a/src/unify.rs b/src/unify.rs index 49a8dc0971..4c7cea2628 100644 --- a/src/unify.rs +++ b/src/unify.rs @@ -94,11 +94,11 @@ fn unify_flat_type(subs: &mut Subs, left: &FlatType, right: &FlatType) -> Descri from_content(Error(Problem::MissingArguments)) } } - (Operator(l_l_arg, l_r_arg, l_ret), Operator(r_l_arg, r_r_arg, r_ret)) => { + (BinOp(l_l_arg, l_r_arg, l_ret), BinOp(r_l_arg, r_r_arg, r_ret)) => { let l_arg = union_vars(subs, l_l_arg.clone(), r_l_arg.clone()); let r_arg = union_vars(subs, l_r_arg.clone(), r_r_arg.clone()); let ret = union_vars(subs, l_ret.clone(), r_ret.clone()); - let flat_type = Operator(l_arg, r_arg, ret); + let flat_type = BinOp(l_arg, r_arg, ret); from_content(Structure(flat_type)) } diff --git a/tests/test_canonicalize.rs b/tests/test_canonicalize.rs index a6d94f150e..2b30cbad24 100644 --- a/tests/test_canonicalize.rs +++ b/tests/test_canonicalize.rs @@ -157,9 +157,9 @@ mod test_canonicalize { // is_self_tail_recursive: false, // definition: Region::zero(), // args: vec![loc(Pattern::Identifier(sym("arg")))], - // body: loc(Expr::Operator( + // body: loc(Expr::BinOp( // loc_box(Expr::Var(sym("arg"))), - // loc(Operator::Plus), + // loc(BinOp::Plus), // loc_box(Expr::Int(1)) // )), // references: References { @@ -631,11 +631,11 @@ mod test_canonicalize { //// fn two_operator_precedence() { //// assert_eq!( //// parse_with_precedence("x + y * 5"), - //// Ok((Operator( + //// Ok((BinOp( //// loc_box(var("x")), //// loc(Plus), //// loc_box( - //// Operator( + //// BinOp( //// loc_box(var("y")), //// loc(Star), //// loc_box(Int(5)) @@ -647,9 +647,9 @@ mod test_canonicalize { //// assert_eq!( //// parse_with_precedence("x * y + 5"), - //// Ok((Operator( + //// Ok((BinOp( //// loc_box( - //// Operator( + //// BinOp( //// loc_box(var("x")), //// loc(Star), //// loc_box(var("y")), @@ -666,9 +666,9 @@ mod test_canonicalize { //// fn compare_and() { //// assert_eq!( //// parse_with_precedence("x > 1 || True"), - //// Ok((Operator( + //// Ok((BinOp( //// loc_box( - //// Operator( + //// BinOp( //// loc_box(var("x")), //// loc(GreaterThan), //// loc_box(Int(1)) diff --git a/tests/test_parse.rs b/tests/test_parse.rs index 735f5ab1ab..f5b4dda0f6 100644 --- a/tests/test_parse.rs +++ b/tests/test_parse.rs @@ -18,7 +18,7 @@ mod test_parse { use bumpalo::{self, Bump}; use helpers::parse_with; use roc::operator::CalledVia; - use roc::operator::Operator::*; + use roc::operator::BinOp::*; use roc::parse::ast::CommentOrNewline::*; use roc::parse::ast::Expr::{self, *}; use roc::parse::ast::Pattern::{self, *}; @@ -256,7 +256,7 @@ mod test_parse { Located::new(0, 0, 1, 2, Plus), Located::new(0, 0, 2, 3, Int("2")), )); - let expected = Operator(tuple); + let expected = BinOp(tuple); let actual = parse_with(&arena, "1+2"); assert_eq!(Ok(expected), actual); @@ -270,7 +270,7 @@ mod test_parse { Located::new(0, 0, 3, 4, Plus), Located::new(0, 0, 7, 8, Int("2")), )); - let expected = Operator(tuple); + let expected = BinOp(tuple); let actual = parse_with(&arena, "1 + 2"); assert_eq!(Ok(expected), actual); @@ -288,7 +288,7 @@ mod test_parse { Located::new(1, 1, 0, 1, Plus), Located::new(1, 1, 2, 3, Int("4")), )); - let expected = Operator(tuple); + let expected = BinOp(tuple); let actual = parse_with(&arena, "3 \n+ 4"); assert_eq!(Ok(expected), actual); @@ -305,7 +305,7 @@ mod test_parse { Located::new(0, 0, 3, 4, Star), Located::new(1, 1, 2, 3, spaced_int), )); - let expected = Operator(tuple); + let expected = BinOp(tuple); let actual = parse_with(&arena, "3 *\n 4"); assert_eq!(Ok(expected), actual); @@ -322,7 +322,7 @@ mod test_parse { Located::new(1, 1, 0, 1, Plus), Located::new(1, 1, 2, 3, Int("4")), )); - let expected = Operator(tuple); + let expected = BinOp(tuple); let actual = parse_with(&arena, "3 # test!\n+ 4"); assert_eq!(Ok(expected), actual); @@ -339,7 +339,7 @@ mod test_parse { Located::new(0, 0, 4, 5, Star), Located::new(1, 1, 1, 3, spaced_int), )); - let expected = Operator(tuple); + let expected = BinOp(tuple); let actual = parse_with(&arena, "12 * # test!\n 92"); assert_eq!(Ok(expected), actual); @@ -359,7 +359,7 @@ mod test_parse { Located::new(1, 1, 0, 1, Plus), Located::new(3, 3, 2, 3, spaced_int2), )); - let expected = Operator(tuple); + let expected = BinOp(tuple); let actual = parse_with(&arena, "3 \n+ \n\n 4"); assert_eq!(Ok(expected), actual); @@ -373,7 +373,7 @@ mod test_parse { Located::new(0, 0, 3, 4, Minus), Located::new(0, 0, 4, 5, Int("5")), )); - let expected = Operator(tuple); + let expected = BinOp(tuple); let actual = parse_with(&arena, "-12-5"); assert_eq!(Ok(expected), actual); @@ -387,7 +387,7 @@ mod test_parse { Located::new(0, 0, 2, 3, Star), Located::new(0, 0, 3, 5, Int("11")), )); - let expected = Operator(tuple); + let expected = BinOp(tuple); let actual = parse_with(&arena, "10*11"); assert_eq!(Ok(expected), actual); @@ -404,9 +404,9 @@ mod test_parse { let outer = arena.alloc(( Located::new(0, 0, 0, 2, Int("31")), Located::new(0, 0, 2, 3, Star), - Located::new(0, 0, 3, 9, Operator(inner)), + Located::new(0, 0, 3, 9, BinOp(inner)), )); - let expected = Operator(outer); + let expected = BinOp(outer); let actual = parse_with(&arena, "31*42+534"); assert_eq!(Ok(expected), actual);