From e949188319bfa52e14ae76c560775574ebbb7a38 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 21 Oct 2019 08:09:50 -0400 Subject: [PATCH] wip --- src/can/env.rs | 2 + src/can/expr.rs | 7 +- src/can/mod.rs | 137 +++++-------------------- src/can/{precedence.rs => operator.rs} | 118 +++++++++++++++++++-- src/can/procedure.rs | 3 + src/can/symbol.rs | 8 +- src/gen/mod.rs | 111 ++++++++++++++++++-- src/operator.rs | 36 +++---- src/parse/ast.rs | 5 +- src/parse/mod.rs | 11 +- src/region.rs | 9 ++ src/types.rs | 1 + 12 files changed, 286 insertions(+), 162 deletions(-) rename src/can/{precedence.rs => operator.rs} (69%) diff --git a/src/can/env.rs b/src/can/env.rs index 82e5f47622..497f624cbc 100644 --- a/src/can/env.rs +++ b/src/can/env.rs @@ -45,6 +45,7 @@ impl Env { definition: Region, references: References, var: Variable, + ret_var: Variable, ) -> () { // We can't if the closure is self tail recursive yet, because it doesn't know its final name yet. // (Assign sets that.) Assume this is false, and let Assign change it to true after it sets final name. @@ -58,6 +59,7 @@ impl Env { definition, references, var, + ret_var, }; self.procedures.insert(symbol, procedure); diff --git a/src/can/expr.rs b/src/can/expr.rs index bee3d689fa..f312887086 100644 --- a/src/can/expr.rs +++ b/src/can/expr.rs @@ -1,6 +1,7 @@ use can::pattern::Pattern; use can::problem::RuntimeError; use can::symbol::Symbol; +use operator::CalledVia; use region::Located; use std::i64; use subs::Variable; @@ -17,7 +18,7 @@ pub enum Expr { Var(Variable, Symbol), /// Works the same as Var, but has an important marking purpose. /// See 13623e3f5f65ea2d703cf155f16650c1e8246502 for the bug this fixed. - FunctionPointer(Symbol), + FunctionPointer(Variable, Symbol), // Pattern Matching Case( @@ -31,8 +32,8 @@ pub enum Expr { Box>, ), - // Application - Call(Variable, Box>, Vec>), + CallByName(Symbol, Vec>, CalledVia), + CallPointer(Box, Vec>, CalledVia), // Product Types Record(Variable, Vec, Located)>>), diff --git a/src/can/mod.rs b/src/can/mod.rs index 48574595ee..b3f17e2df9 100644 --- a/src/can/mod.rs +++ b/src/can/mod.rs @@ -12,8 +12,6 @@ use collections::{ImMap, ImSet, MutMap, MutSet}; use constrain; use graph::{strongly_connected_component, topological_sort}; use ident::Ident; -use operator::ArgSide; -use operator::Operator::Pizza; use parse::ast::{self, Def}; use region::{Located, Region}; use std::i64; @@ -26,8 +24,8 @@ use types::Type::{self, *}; pub mod env; pub mod expr; +pub mod operator; pub mod pattern; -pub mod precedence; pub mod problem; pub mod procedure; pub mod scope; @@ -50,12 +48,14 @@ pub fn canonicalize_declaration<'a>( Vec, MutMap, ) { - // Apply operator precedence and associativity rules once, before doing any - // other canonicalization. If we did this *during* canonicalization, then each time we + // Desugar operators (convert them to Apply calls, taking into account + // 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 // operators, and thena again on *their* nested operators, ultimately applying the // rules multiple times unnecessarily. - let loc_expr = precedence::apply_precedence_and_associativity(arena, loc_expr); + let loc_expr = operator::desugar(arena, loc_expr); // If we're canonicalizing the declaration `foo = ...` inside the `Main` module, // scope_prefix will be "Main$foo$" and its first closure will be named "Main$foo$0" @@ -214,7 +214,7 @@ fn canonicalize_expr( // (expr, output) //} - ast::Expr::Apply((loc_fn, loc_args)) => { + ast::Expr::Apply((loc_fn, loc_args, application_style)) => { // The expression that evaluates to the function being called, e.g. `foo` in // (foo) bar baz let fn_var = subs.mk_flex_var(); @@ -277,14 +277,20 @@ fn canonicalize_expr( outputs.push(arg_out); } - match &fn_expr.value { - &Var(_, ref sym) => { + let expr = match &fn_expr.value { + &Var(_, ref sym) | &FunctionPointer(_, ref sym) => { + // In the FunctionPointer case, we're calling an inline closure; + // something like ((\a b -> a + b) 1 2) output.references.calls.insert(sym.clone()); - } - _ => (), - }; - let expr = Call(subs.mk_flex_var(), Box::new(fn_expr), args); + CallByName(sym.clone(), args, *application_style) + } + _ => { + // This could be something like ((if True then fn1 else fn2) arg1 arg2). + // Use CallPointer here. + panic!("TODO support function calls that aren't by name, via CallPointer"); + } + }; for arg_out in outputs { output.references = output.references.union(arg_out.references); @@ -311,101 +317,6 @@ fn canonicalize_expr( (expr, output) } - ast::Expr::Operator((loc_left, loc_op, loc_right)) => { - let op = loc_op.value; - let l_arg_var = subs.mk_flex_var(); - let l_arg_type = Variable(l_arg_var); - // Canonicalize the nested expressions - let (left_expr, left_out) = canonicalize_expr( - env, - subs, - scope, - loc_left.region.clone(), - &loc_left.value, - NoExpectation(l_arg_type.clone()), - ); - let l_con = left_out.constraint; - let r_arg_var = subs.mk_flex_var(); - let r_arg_type = Variable(r_arg_var); - let (right_expr, mut output) = canonicalize_expr( - env, - subs, - scope, - loc_right.region.clone(), - &loc_right.value, - NoExpectation(r_arg_type.clone()), - ); - let r_con = output.constraint; - - // Incorporate both expressions into a combined Output value. - output.references = output.references.union(left_out.references); - - // The pizza operator is the only one that can be a tail call, - // because it's the only one that can call a function by name. - let expr = match op { - Pizza => { - output.tail_call = match right_expr.value { - Var(_, sym) => Some(sym.clone()), - Call(_, loc_boxed_expr, _) => match &loc_boxed_expr.value { - Var(_, sym) => Some(sym.clone()), - _ => None, - }, - _ => None, - }; - - panic!("TODO translate |> operator into a Call. NOTE: This will require shifting the above canonicalization code around."); - } - _ => { - output.tail_call = None; - - // Left arg constraints - let op_types = Type::for_operator(loc_op.value); - let l_reason = Reason::OperatorArg(op, ArgSide::Left); - let l_expected = ForReason(l_reason, op_types.left, region.clone()); - - // Right arg constraints - let r_reason = Reason::OperatorArg(op, ArgSide::Right); - let r_expected = ForReason(r_reason, op_types.right, region.clone()); - - // Return value constraints - let ret_var = subs.mk_flex_var(); - let ret_type = Variable(ret_var); - let ret_reason = Reason::OperatorRet(op); - let expected_ret_type = ForReason(ret_reason, op_types.ret, region.clone()); - - // TODO occurs check! - // let vars = vec![fn_var, ret_var, l_var, r_var]; - // return $ exists (funcVar:resultVar:argVars) $ CAnd ... - - // Combined constraint - output.constraint = And(vec![ - // the constraint from constrain on l_expr, expecting its hardcoded type - l_con, - // The variable should ultimately equal the hardcoded expected type - Eq(l_arg_type, l_expected, region.clone()), - // the constraint from constrain on r_expr, expecting its hardcoded type - r_con, - // The variable should ultimately equal the hardcoded expected type - Eq(r_arg_type, r_expected, region.clone()), - // The operator's args and return type should be its hardcoded types - Eq(ret_type.clone(), expected_ret_type, region.clone()), - // Finally, link the operator's return type to the given expected type - Eq(ret_type, expected, region.clone()), - ]); - - let value = Var(subs.mk_flex_var(), loc_op.value.desugar().into()); - let loc_var = Located { - value, - region: loc_op.region.clone(), - }; - let args = vec![left_expr, right_expr]; - - Call(subs.mk_flex_var(), Box::new(loc_var), args) - } - }; - - (expr, output) - } ast::Expr::Var(module_parts, name) => { let symbol = if module_parts.is_empty() { scope.symbol(name) @@ -626,10 +537,11 @@ fn canonicalize_expr( region.clone(), output.references.clone(), var, + args.ret_var, ); // Always return a function pointer, in case that's how the closure is being used (e.g. with Apply). - (FunctionPointer(symbol), output) + (FunctionPointer(var, symbol), output) } // ast::Expr::Case(loc_cond, branches) => { @@ -749,6 +661,9 @@ fn canonicalize_expr( (answer, Output::new(constraint)) } + ast::Expr::Operator((loc_left, loc_op, loc_right)) => { + panic!("An operator did not get desugared somehow: {:?}", loc_op); + } }; // At the end, diff used_idents and assigned_idents to see which were unused. @@ -1474,7 +1389,7 @@ fn can_defs<'a>( ( &ast::Pattern::Identifier(ref name), &Pattern::Identifier(_, ref assigned_symbol), - &FunctionPointer(ref symbol), + &FunctionPointer(_, ref symbol), ) => { // Since everywhere in the code it'll be referred to by its assigned name, // remove its generated name from the procedure map. (We'll re-insert it later.) @@ -1723,6 +1638,7 @@ struct Args { pub vars: Vec, pub typ: Type, pub ret_type: Type, + pub ret_var: Variable, } fn constrain_args<'a, I>(args: I, scope: &Scope, subs: &mut Subs, state: &mut PatternState) -> Args @@ -1742,6 +1658,7 @@ where vars, typ, ret_type, + ret_var, } } diff --git a/src/can/precedence.rs b/src/can/operator.rs similarity index 69% rename from src/can/precedence.rs rename to src/can/operator.rs index fc8d5c0b84..1c1c3bd69b 100644 --- a/src/can/precedence.rs +++ b/src/can/operator.rs @@ -1,8 +1,10 @@ use bumpalo::collections::Vec; use bumpalo::Bump; -use operator::Operator; -use parse::ast::Expr; +use operator::Operator::Pizza; +use operator::{CalledVia, Operator}; +use parse::ast::Expr::{self, *}; use region::{Located, Region}; +use types; // Operator precedence logic adapted from Gluon by Markus Westerlind, MIT licensed // https://github.com/gluon-lang/gluon @@ -28,11 +30,9 @@ fn new_op_expr<'a>( } } -/// Reorder the expression tree based on operator precedence and associativity rules. -pub fn apply_precedence_and_associativity<'a>( - arena: &'a Bump, - expr: Located>, -) -> Located> { +/// Reorder the expression tree based on operator precedence and associativity rules, +/// then replace the Operator nodes with Apply nodes. +pub fn desugar<'a>(arena: &'a Bump, expr: Located>) -> Located> { use operator::Associativity::*; use std::cmp::Ordering; @@ -139,11 +139,38 @@ pub fn apply_precedence_and_associativity<'a>( } } - for op in op_stack.into_iter().rev() { + for loc_op in op_stack.into_iter().rev() { let right = arg_stack.pop().unwrap(); let left = arg_stack.pop().unwrap(); - arg_stack.push(arena.alloc(new_op_expr(arena, left.clone(), op, right.clone()))); + let region = Region::span_across(&left.region, &right.region); + let expr = match loc_op.value { + Pizza => { + // Rewrite the Pizza operator into an Apply + panic!("TODO desugar |> operator into an Apply"); + } + binop => { + // This is a normal binary operator like (+), so desugar it + // into the appropriate function call. + let (module_parts, name) = desugar_binop(&binop, arena); + let mut args = Vec::with_capacity_in(2, arena); + + args.push(left.clone()); + args.push(right.clone()); + + let loc_expr = Located { + value: Expr::Var(module_parts, name), + region: loc_op.region, + }; + + Apply(arena.alloc((loc_expr, args, CalledVia::Operator(binop)))) + } + }; + + arg_stack.push(arena.alloc(Located { + region, + value: expr, + })); } assert_eq!(arg_stack.len(), 1); @@ -151,6 +178,79 @@ pub fn apply_precedence_and_associativity<'a>( arg_stack.pop().unwrap().clone() } +#[inline(always)] +fn desugar_binop<'a>(binop: &Operator, arena: &'a Bump) -> (&'a [&'a str], &'a str) { + use self::Operator::*; + + match binop { + Caret => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "pow", + ), + Star => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "mul", + ), + Slash => ( + bumpalo::vec![ in arena; types::MOD_FLOAT ].into_bump_slice(), + "div", + ), + DoubleSlash => ( + bumpalo::vec![ in arena; types::MOD_INT ].into_bump_slice(), + "div", + ), + Percent => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "rem", + ), + DoublePercent => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "mod", + ), + Plus => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "plus", + ), + Minus => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "sub", + ), + Equals => ( + bumpalo::vec![ in arena; types::MOD_BOOL ].into_bump_slice(), + "isEq", + ), + NotEquals => ( + bumpalo::vec![ in arena; types::MOD_BOOL ].into_bump_slice(), + "isNotEq", + ), + LessThan => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "isLt", + ), + GreaterThan => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "isGt", + ), + LessThanOrEq => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "isLte", + ), + GreaterThanOrEq => ( + bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(), + "isGte", + ), + And => ( + bumpalo::vec![ in arena; types::MOD_BOOL ].into_bump_slice(), + "and", + ), + Or => ( + bumpalo::vec![ in arena; types::MOD_BOOL ].into_bump_slice(), + "or", + ), + Pizza => panic!("Cannot desugar the |> operator"), + } +} + #[derive(Debug, Clone, PartialEq)] enum InfixToken<'a> { Arg(&'a Located>), diff --git a/src/can/procedure.rs b/src/can/procedure.rs index 537865feab..0c2dab9b45 100644 --- a/src/can/procedure.rs +++ b/src/can/procedure.rs @@ -14,6 +14,7 @@ pub struct Procedure { pub body: Located, pub references: References, pub var: Variable, + pub ret_var: Variable, } impl Procedure { @@ -23,6 +24,7 @@ impl Procedure { body: Located, references: References, var: Variable, + ret_var: Variable, ) -> Procedure { Procedure { name: None, @@ -32,6 +34,7 @@ impl Procedure { body, references, var, + ret_var, } } } diff --git a/src/can/symbol.rs b/src/can/symbol.rs index c13a1087ce..1435953862 100644 --- a/src/can/symbol.rs +++ b/src/can/symbol.rs @@ -21,12 +21,14 @@ impl Symbol { &VariantName::Qualified(ref path, ref name) => Symbol::new(path, name), } } + + pub fn into_boxed_str(self) -> Box { + self.0 + } } impl Into> for Symbol { fn into(self) -> Box { - let Symbol(string) = self; - - string + self.0 } } diff --git a/src/gen/mod.rs b/src/gen/mod.rs index c0b275b112..d21afbc51d 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -26,9 +26,16 @@ use inkwell::context::Context; use inkwell::module::Module; use inkwell::passes::PassManager; use inkwell::types::BasicTypeEnum; -use inkwell::values::{BasicValueEnum, FloatValue, FunctionValue, PointerValue}; +use inkwell::values::{BasicValueEnum, FloatValue, FunctionValue, IntValue, PointerValue}; use inkwell::FloatPredicate; +use can::expr; +use can::procedure::Procedure; +use can::symbol::Symbol; +use collections::ImMap; +use collections::MutMap; +use subs::Variable; + // ====================================================================================== // LEXER ================================================================================ // ====================================================================================== @@ -119,6 +126,101 @@ pub enum Expr { }, } +pub struct ModuleBuilder<'a> { + pub context: &'a Context, + pub builder: &'a Builder, + pub fpm: &'a PassManager, + pub module: &'a Module, + pub function: &'a Function, + pub procedures: &'a MutMap, + + fn_value_opt: Option, +} + +enum TypedVal { + FloatConst(FloatValue), + IntConst(IntValue), + Typed(Variable, BasicValueEnum), +} + +impl Into for TypedVal { + fn into(self) -> BasicValueEnum { + use self::TypedVal::*; + + match self { + FloatConst(val) => val.into(), + IntConst(val) => val.into(), + Typed(_, val) => val, + } + } +} + +impl<'a> ModuleBuilder<'a> { + #[inline] + fn get_function(&self, name: &str) -> Option { + self.module.get_function(name) + } + + fn compile_expr( + &mut self, + expr: &expr::Expr, + vars: &mut ImMap, + ) -> TypedVal { + use self::TypedVal::*; + use can::expr::Expr::*; + + match *expr { + Int(num) => IntConst(self.context.i64_type().const_int(num as u64, false)), + Float(num) => FloatConst(self.context.f64_type().const_float(num)), + + // Var and FunctionPointer do the same thing; they are only different + // for the benefit of canonicalization, which uses FunctionPointer + // to name functions. + Var(type_var, ref symbol) | FunctionPointer(type_var, ref symbol) => { + match vars.get(symbol) { + Some(var) => Typed( + type_var, + self.builder + .build_load(*var, &*(symbol.clone()).into_boxed_str()), + ), + None => panic!( + "Roc compiler bug: could not find symbol `{:?}` with type var `{:?}`", + symbol, type_var + ), + } + } + + Defs(_, _, _) => panic!("TODO gen defs"), + CallByName(symbol, loc_args, _) => { + let func = self + .get_function(&*(symbol.clone()).into_boxed_str()) + .unwrap_or_else(|| { + panic!("Roc compiler error: Unrecognized function `{:?}`", symbol) + }); + let enum_args: Vec = loc_args + .iter() + .map(|loc_arg| self.compile_expr(&loc_arg.value, vars).into()) + .collect(); + let proc = self.procedures.get(&symbol).unwrap_or_else(|| { + panic!("Roc compiler error: Unrecognized procedure `{:?}`", symbol) + }); + + Typed( + proc.ret_var, + self.builder + .build_call(func, &enum_args, "tmp") // TODO replace "tmp" + .try_as_basic_value() + .left() + .unwrap_or_else(|| panic!("Roc compiler error: Invalid call.")), + ) + } + Str(_) | List(_, _) | Case(_, _, _) | Record(_, _) | EmptyRecord | RuntimeError(_) => { + panic!("TODO compile_expr for {:?}", expr); + } + } + } +} + impl<'a> Emitter<'a> { /// Gets a defined function given its name. #[inline] @@ -586,14 +688,7 @@ fn gen() { is_anon: false, }; - // TODO remove this dead code - for prev in &Vec::new() { - Emitter::compile(&context, &builder, &fpm, &module, prev) - .expect("Cannot re-add previously compiled function."); - } - // make main(), a function which returns an f64 - Emitter::compile(&context, &builder, &fpm, &module, &function).expect("Error compiling main"); // let fn_type = context.f64_type().fn_type(&Vec::new(), false); diff --git a/src/operator.rs b/src/operator.rs index 83030bf2c2..4d5a812880 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -1,7 +1,17 @@ use self::Operator::*; -use can::symbol::Symbol; use std::cmp::Ordering; -use types; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CalledVia { + /// Calling with space, e.g. (foo bar) + Space, + + /// Calling with an operator, e.g. (bar |> foo) or (1 + 2) + Operator(Operator), + + /// Calling with the unary (!) operator, e.g. (!foo bar baz) + UnaryNot, +} #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Operator { @@ -15,6 +25,7 @@ pub enum Operator { Plus, Minus, Equals, + NotEquals, LessThan, GreaterThan, LessThanOrEq, @@ -75,27 +86,6 @@ impl Operator { Pizza => 1, } } - - pub fn desugar(&self) -> Symbol { - match self { - Caret => Symbol::new(types::MOD_NUM, "pow"), - Star => Symbol::new(types::MOD_NUM, "mul"), - Slash => Symbol::new("Float", "div"), - DoubleSlash => Symbol::new("Int", "div"), - Percent => Symbol::new(types::MOD_NUM, "rem"), - DoublePercent => Symbol::new(types::MOD_NUM, "mod"), - Plus => Symbol::new(types::MOD_NUM, "plus"), - Minus => Symbol::new(types::MOD_NUM, "sub"), - Equals => Symbol::new("Bool", "isEqual"), - LessThan => Symbol::new(types::MOD_NUM, "isLessThan"), - GreaterThan => Symbol::new(types::MOD_NUM, "isGreaterThan"), - LessThanOrEq => Symbol::new(types::MOD_NUM, "isLessThanOrEqualTo"), - GreaterThanOrEq => Symbol::new(types::MOD_NUM, "isGreaterThanOrEqualTo"), - And => Symbol::new("Bool", "and"), - Or => Symbol::new("Bool", "or"), - Pizza => panic!("Cannot desugar the |> operator"), - } - } } impl PartialOrd for Operator { diff --git a/src/parse/ast.rs b/src/parse/ast.rs index f233c78015..c253589fe6 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -1,6 +1,7 @@ use bumpalo::collections::String; use bumpalo::collections::Vec; use bumpalo::Bump; +use operator::CalledVia; use operator::Operator; use parse::ident::{Ident, MaybeQualified}; use region::{Loc, Region}; @@ -65,7 +66,7 @@ pub enum Expr<'a> { // Application /// To apply by name, do Apply(Var(...), ...) /// To apply a variant by name, do Apply(Variant(...), ...) - Apply(&'a (Loc>, Vec<'a, Loc>>)), + Apply(&'a (Loc>, Vec<'a, Loc>>, CalledVia)), Operator(&'a (Loc>, Loc, Loc>)), // Conditionals @@ -383,7 +384,7 @@ pub fn format<'a>( buf.push_str(name); } - Apply((loc_expr, loc_args)) => { + Apply((loc_expr, loc_args, _)) => { if apply_needs_parens { buf.push('('); } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index bae1099480..0792708c0f 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -23,7 +23,7 @@ pub mod string_literal; use bumpalo::collections::String; use bumpalo::collections::Vec; use bumpalo::Bump; -use operator::Operator; +use operator::{CalledVia, Operator}; use parse; use parse::ast::{Attempting, Def, Expr, Pattern, Spaceable}; use parse::blankspace::{ @@ -163,7 +163,7 @@ pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located Ok(( Located { region: loc_expr_with_extras.region, - value: Expr::Apply(arena.alloc((loc_expr, loc_args))), + value: Expr::Apply(arena.alloc((loc_expr, loc_args, CalledVia::Space))), }, state, )), @@ -231,7 +231,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result, } } Expr::Variant(module_parts, value) => Ok(Pattern::Variant(module_parts, value)), - Expr::Apply((loc_val, loc_args)) => { + Expr::Apply((loc_val, loc_args, _)) => { let region = loc_val.region.clone(); let value = expr_to_pattern(arena, &loc_val.value)?; let val_pattern = arena.alloc(Located { region, value }); @@ -759,7 +759,10 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { value: ident_to_expr(loc_ident.value), }; - Ok((Expr::Apply(arena.alloc((loc_expr, loc_args))), state)) + Ok(( + Expr::Apply(arena.alloc((loc_expr, loc_args, CalledVia::Space))), + state, + )) } Some(Either::Second((spaces_before_equals, Either::First(equals_indent)))) => { let pattern: Pattern<'a> = Pattern::from_ident(arena, loc_ident.value); diff --git a/src/region.rs b/src/region.rs index aa3e89b791..2b1d405718 100644 --- a/src/region.rs +++ b/src/region.rs @@ -20,6 +20,15 @@ impl Region { end_col: 0, } } + + pub fn span_across(start: &Region, end: &Region) -> Self { + Region { + start_line: start.start_line, + end_line: end.end_line, + start_col: start.start_col, + end_col: end.end_col, + } + } } #[test] diff --git a/src/types.rs b/src/types.rs index 51fe91be03..23bc15db09 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,6 +7,7 @@ use subs::Variable; // The standard modules pub const MOD_FLOAT: &'static str = "Float"; +pub const MOD_BOOL: &'static str = "Float"; pub const MOD_INT: &'static str = "Int"; pub const MOD_STR: &'static str = "Str"; pub const MOD_LIST: &'static str = "List";