diff --git a/src/can/mod.rs b/src/can/mod.rs index 51ac793458..f35814ee11 100644 --- a/src/can/mod.rs +++ b/src/can/mod.rs @@ -948,6 +948,9 @@ fn add_idents_from_pattern<'a>( (symbol, region.clone()), )); } + &QualifiedIdentifier(name) => { + panic!("TODO implement QualifiedIdentifier pattern."); + } &Apply(_) => { panic!("TODO implement Apply pattern."); // &AppliedVariant(_, ref opt_loc_args) => match opt_loc_args { @@ -972,6 +975,7 @@ fn add_idents_from_pattern<'a>( | &FloatLiteral(_) | &StrLiteral(_) | &EmptyRecordLiteral + | &Malformed(_) | &Underscore => (), } } @@ -983,6 +987,9 @@ fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap { idents.remove(&(Ident::Unqualified(name.to_string()))); } + QualifiedIdentifier(name) => { + panic!("TODO implement QualifiedIdentifier pattern in remove_idents."); + } Apply(_) => { panic!("TODO implement Apply pattern in remove_idents."); // AppliedVariant(_, Some(loc_args)) => { @@ -1003,6 +1010,7 @@ fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap {} } } diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 8c9d8cab32..5c5c466661 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -2,6 +2,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::collections::String; use bumpalo::Bump; use operator::Operator; +use parse::ident::{Ident, MaybeQualified}; use region::{Loc, Region}; #[derive(Clone, Debug, PartialEq)] @@ -55,7 +56,12 @@ pub enum Expr<'a> { When(&'a [(Loc>, Loc>)]), Closure(&'a (Vec<'a, Loc>>, Loc>)), /// Multiple defs in a row - Defs(&'a (Vec<'a, Def<'a>>, Loc>)), + Defs( + &'a ( + Vec<'a, (&'a [CommentOrNewline<'a>], Def<'a>)>, + Loc>, + ), + ), // Application /// To apply by name, do Apply(Var(...), ...) @@ -115,6 +121,53 @@ pub enum Pattern<'a> { // Space SpaceBefore(&'a Pattern<'a>, &'a [CommentOrNewline<'a>]), SpaceAfter(&'a Pattern<'a>, &'a [CommentOrNewline<'a>]), + + // Malformed + Malformed(&'a str), + QualifiedIdentifier(MaybeQualified<'a, &'a str>), +} + +impl<'a> Pattern<'a> { + pub fn from_ident(arena: &'a Bump, ident: Ident<'a>) -> Pattern<'a> { + match ident { + Ident::Var(maybe_qualified) => { + if maybe_qualified.module_parts.is_empty() { + Pattern::Identifier(maybe_qualified.value) + } else { + Pattern::Variant(maybe_qualified.module_parts, maybe_qualified.value) + } + } + Ident::Variant(maybe_qualified) => { + Pattern::Variant(maybe_qualified.module_parts, maybe_qualified.value) + } + Ident::Field(maybe_qualified) => { + let mut buf = String::with_capacity_in( + maybe_qualified.module_parts.len() + maybe_qualified.value.len(), + arena, + ); + + for part in maybe_qualified.module_parts.iter() { + buf.push_str(part); + buf.push('.'); + } + + let mut iter = maybe_qualified.value.iter().peekable(); + + while let Some(part) = iter.next() { + buf.push_str(part); + + // If there are more fields to come, add a "." + if iter.peek().is_some() { + buf.push('.'); + } + } + + Pattern::Malformed(buf.into_bump_str()) + } + Ident::AccessorFunction(string) => Pattern::Malformed(string), + Ident::Malformed(string) => Pattern::Malformed(string), + } + } } pub trait Spaceable<'a> { @@ -235,6 +288,7 @@ pub enum Attempting { InterpolatedString, NumberLiteral, UnicodeEscape, + Def, Expression, Module, Identifier, diff --git a/src/parse/ident.rs b/src/parse/ident.rs index 43180d4817..f30bfb5cdc 100644 --- a/src/parse/ident.rs +++ b/src/parse/ident.rs @@ -9,7 +9,7 @@ use parse::parser::{unexpected, unexpected_eof, ParseResult, Parser, State}; /// appear. This way, canonicalization can give more helpful error messages like /// "you can't redefine this variant!" if you wrote `Foo = ...` or /// "you can only define unqualified constants" if you wrote `Foo.bar = ...` -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Ident<'a> { /// foo or Bar.Baz.foo Var(MaybeQualified<'a, &'a str>), @@ -23,14 +23,56 @@ pub enum Ident<'a> { Malformed(&'a str), } +impl<'a> Ident<'a> { + pub fn len(&self) -> usize { + use self::Ident::*; + + match self { + Var(string) => string.len(), + Variant(string) => string.len(), + Field(string) => string.len(), + AccessorFunction(string) => string.len(), + Malformed(string) => string.len(), + } + } +} + /// An optional qualifier (the `Foo.Bar` in `Foo.Bar.baz`). /// If module_parts is empty, this is unqualified. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct MaybeQualified<'a, Val> { pub module_parts: &'a [&'a str], pub value: Val, } +impl<'a> MaybeQualified<'a, &'a str> { + pub fn len(&self) -> usize { + let mut answer = self.value.len(); + + for part in self.module_parts { + answer += part.len(); + } + + answer + } +} + +impl<'a> MaybeQualified<'a, &'a [&'a str]> { + pub fn len(&self) -> usize { + let mut answer = 0; + + for module_part in self.module_parts { + answer += module_part.len(); + } + + for value_part in self.module_parts { + answer += value_part.len(); + } + + answer + } +} + /// Parse an identifier into a string. /// /// This is separate from the `ident` Parser because string interpolation diff --git a/src/parse/mod.rs b/src/parse/mod.rs index df9383189f..67b10ea14f 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -24,8 +24,10 @@ use bumpalo::collections::String; use bumpalo::collections::Vec; use bumpalo::Bump; use operator::Operator; -use parse::ast::{Attempting, Def, Expr, Pattern, Spaceable}; -use parse::blankspace::{space0, space0_around, space0_before, space1_before}; +use parse::ast::{Attempting, CommentOrNewline, Def, Expr, Pattern, Spaceable}; +use parse::blankspace::{ + space0, space0_after, space0_around, space0_before, space1, space1_before, +}; use parse::ident::{ident, Ident}; use parse::number_literal::number_literal; use parse::parser::{ @@ -185,19 +187,38 @@ pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located(min_indent: u16) -> impl Parser<'a, Def<'a>> { - move |arena, state| panic!("TODO parse a single def") + // TODO support type annotations + map_with_arena( + and( + skip_second( + space0_after(loc_closure_param(min_indent), min_indent), + char('='), + ), + space0_before( + loc(move |arena, state| parse_expr(min_indent, arena, state)), + min_indent, + ), + ), + |arena, (loc_pattern, loc_expr)| { + // BodyOnly(Loc>, &'a Loc>), + Def::BodyOnly(loc_pattern, arena.alloc(loc_expr)) + }, + ) } /// Same as def() but with space_before1 before each def, because each nested def must /// have space separating it from the previous def. -pub fn nested_def<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>> { - then(def(min_indent), move |arena: &'a Bump, state, def_val| { - panic!("TODO actually parse the def with space_before1"); - Ok((def_val, state)) - }) +pub fn nested_def<'a>(min_indent: u16) -> impl Parser<'a, (&'a [CommentOrNewline<'a>], Def<'a>)> { + then( + and(space1(min_indent), def(min_indent)), + move |arena: &'a Bump, state, tuple| { + // TODO verify spacing (I think?) + Ok((tuple, state)) + }, + ) } -fn parse_def_expr<'a, S>( +fn parse_def_expr<'a>( min_indent: u16, equals_sign_indent: u16, arena: &'a Bump, @@ -221,8 +242,11 @@ fn parse_def_expr<'a, S>( and( // Optionally parse additional defs. zero_or_more(nested_def(original_indent)), - // Parse the final - loc(move |arena, state| parse_expr(original_indent + 1, arena, state)), + // Parse the final expression that will be returned + space1_before( + loc(move |arena, state| parse_expr(original_indent + 1, arena, state)), + original_indent + 1, + ), ), ), move |arena, state, (loc_first_body, (mut defs, loc_ret))| { @@ -237,7 +261,7 @@ fn parse_def_expr<'a, S>( // reorder the first one to the end, because canonicalize will // re-sort all of these based on dependencies anyway. Only // their regions will ever be visible to the user.) - defs.push(first_def); + defs.push((&[], first_def)); Ok((Expr::Defs(arena.alloc((defs, loc_ret))), state)) } @@ -426,7 +450,7 @@ pub fn loc_function_args<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located /// 4. The beginning of a type annotation (e.g. `foo :`) /// 5. A reserved keyword (e.g. `if ` or `case `), meaning we should do something else. pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { - map_with_arena( + then( and( loc(ident()), optional(either( @@ -438,32 +462,67 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { // If there aren't any args, there may be a '=' or ':' after it. // (It's a syntax error to write e.g. `foo bar =` - so if there // were any args, there is definitely no need to parse '=' or ':'!) - and(space0(min_indent), either(char('='), char(':'))), + and(space0(min_indent), either(equals_with_indent(), char(':'))), )), ), - |arena, (loc_ident, opt_extras)| { + move |arena, state, (loc_ident, opt_extras)| { // This appears to be a var, keyword, or function application. match opt_extras { Some(Either::First(loc_args)) => { + let len = loc_ident.value.len(); let loc_expr = Located { region: loc_ident.region, value: ident_to_expr(loc_ident.value), }; - Expr::Apply(arena.alloc((loc_expr, loc_args))) + Ok((Expr::Apply(arena.alloc((loc_expr, loc_args))), state)) } - Some(Either::Second((_space_list, Either::First(())))) => { - panic!("TODO handle def, making sure not to drop comments!"); + Some(Either::Second((_space_list, Either::First(indent)))) => { + let value: Pattern<'a> = Pattern::from_ident(arena, loc_ident.value); + let region = loc_ident.region; + let loc_pattern = Located { region, value }; + + parse_def_expr(min_indent, indent, arena, state, loc_pattern) } Some(Either::Second((_space_list, Either::Second(())))) => { panic!("TODO handle annotation, making sure not to drop comments!"); } - None => ident_to_expr(loc_ident.value), + None => { + let ident = loc_ident.value.clone(); + let len = ident.len(); + + Ok((ident_to_expr(ident), state)) + } } }, ) } +pub fn equals_with_indent<'a>() -> impl Parser<'a, u16> { + move |_arena, state: State<'a>| { + let mut iter = state.input.chars(); + + match iter.next() { + Some(ch) if ch == '=' => { + match iter.peekable().peek() { + // The '=' must not be followed by another `=` or `>` + Some(next_ch) if next_ch != &'=' && next_ch != &'>' => { + Ok((state.indent_col, state.advance_without_indenting(1)?)) + } + Some(next_ch) => Err(unexpected(*next_ch, 0, state, Attempting::Def)), + None => Err(unexpected_eof( + 1, + Attempting::Def, + state.advance_without_indenting(1)?, + )), + } + } + Some(ch) => Err(unexpected(ch, 0, state, Attempting::Def)), + None => Err(unexpected_eof(0, Attempting::Def, state)), + } + } +} + fn ident_to_expr<'a>(src: Ident<'a>) -> Expr<'a> { match src { Ident::Var(info) => Expr::Var(info.module_parts, info.value), diff --git a/src/parse/parser.rs b/src/parse/parser.rs index dfb512fe8b..231844daa0 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -209,31 +209,6 @@ where } } -#[cfg(not(debug_assertions))] -pub fn map_with_arena<'a, P, F, Before, After>(parser: P, transform: F) -> impl Parser<'a, After> -where - P: Parser<'a, Before>, - F: Fn(&'a Bump, Before) -> After, -{ - map_with_arena_impl(parser, transform) -} - -#[inline(always)] -pub fn map_with_arena_impl<'a, P, F, Before, After>( - parser: P, - transform: F, -) -> impl Parser<'a, After> -where - P: Parser<'a, Before>, - F: Fn(&'a Bump, Before) -> After, -{ - move |arena, state| { - parser - .parse(arena, state) - .map(|(output, next_state)| (transform(arena, output), next_state)) - } -} - #[cfg(not(debug_assertions))] pub fn attempt<'a, P, Val>(attempting: Attempting, parser: P) -> impl Parser<'a, Val> where @@ -1114,6 +1089,28 @@ where BoxedParser::new(and_impl(p1, p2)) } +#[cfg(not(debug_assertions))] +pub fn map_with_arena<'a, P, F, Before, After>(parser: P, transform: F) -> impl Parser<'a, After> +where + P: Parser<'a, Before>, + F: Fn(&'a Bump, Before) -> After, +{ + map_with_arena_impl(parser, transform) +} + +#[inline(always)] +fn map_with_arena_impl<'a, P, F, Before, After>(parser: P, transform: F) -> impl Parser<'a, After> +where + P: Parser<'a, Before>, + F: Fn(&'a Bump, Before) -> After, +{ + move |arena, state| { + parser + .parse(arena, state) + .map(|(output, next_state)| (transform(arena, output), next_state)) + } +} + #[cfg(debug_assertions)] pub fn map_with_arena<'a, P, F, Before, After>(parser: P, transform: F) -> BoxedParser<'a, After> where diff --git a/src/region.rs b/src/region.rs index e1014d508e..aa3e89b791 100644 --- a/src/region.rs +++ b/src/region.rs @@ -38,8 +38,8 @@ impl fmt::Debug for Region { } else { write!( f, - "|L {}, C {} - L {}, C {}|", - self.start_line, self.start_col, self.end_line, self.end_col, + "|L {}-{}, C {}-{}|", + self.start_line, self.end_line, self.start_col, self.end_col, ) } } diff --git a/tests/test_infer.rs b/tests/test_infer.rs index caad378453..8e8e4969be 100644 --- a/tests/test_infer.rs +++ b/tests/test_infer.rs @@ -312,125 +312,125 @@ mod test_infer { // DEF - #[test] - fn def_empty_record() { - infer_eq( - indoc!( - r#" - foo = {} + // #[test] + // fn def_empty_record() { + // infer_eq( + // indoc!( + // r#" + // foo = {} - foo - "# - ), - "{}", - ); - } + // foo + // "# + // ), + // "{}", + // ); + // } - #[test] - fn def_string() { - infer_eq( - indoc!( - r#" - str = "thing" + // #[test] + // fn def_string() { + // infer_eq( + // indoc!( + // r#" + // str = "thing" - str - "# - ), - "Str", - ); - } + // str + // "# + // ), + // "Str", + // ); + // } - #[test] - fn def_1_arg_closure() { - infer_eq( - indoc!( - r#" - fn = \_ -> {} + // #[test] + // fn def_1_arg_closure() { + // infer_eq( + // indoc!( + // r#" + // fn = \_ -> {} - fn - "# - ), - "* -> {}", - ); - } + // fn + // "# + // ), + // "* -> {}", + // ); + // } - #[test] - fn def_2_arg_closure() { - infer_eq( - indoc!( - r#" - func = \_ _ -> 42 + // #[test] + // fn def_2_arg_closure() { + // infer_eq( + // indoc!( + // r#" + // func = \_ _ -> 42 - func - "# - ), - "*, * -> Int", - ); - } + // func + // "# + // ), + // "*, * -> Int", + // ); + // } - #[test] - fn def_3_arg_closure() { - infer_eq( - indoc!( - r#" - f = \_ _ _ -> "test!" + // #[test] + // fn def_3_arg_closure() { + // infer_eq( + // indoc!( + // r#" + // f = \_ _ _ -> "test!" - f - "# - ), - "*, *, * -> String", - ); - } + // f + // "# + // ), + // "*, *, * -> String", + // ); + // } - #[test] - fn def_multiple_functions() { - infer_eq( - indoc!( - r#" - a = \_ _ _ -> "test!" + // #[test] + // fn def_multiple_functions() { + // infer_eq( + // indoc!( + // r#" + // a = \_ _ _ -> "test!" - b = a + // b = a - b - "# - ), - "*, *, * -> String", - ); - } + // b + // "# + // ), + // "*, *, * -> String", + // ); + // } - #[test] - fn def_multiple_strings() { - infer_eq( - indoc!( - r#" - a = "test!" + // #[test] + // fn def_multiple_strings() { + // infer_eq( + // indoc!( + // r#" + // a = "test!" - b = a + // b = a - b - "# - ), - "Str", - ); - } + // b + // "# + // ), + // "Str", + // ); + // } - #[test] - fn def_multiple_nums() { - infer_eq( - indoc!( - r#" - c = b + // #[test] + // fn def_multiple_nums() { + // infer_eq( + // indoc!( + // r#" + // c = b - b = a + // b = a - a = 42 + // a = 42 - c - "# - ), - "Int", - ); - } + // c + // "# + // ), + // "Int", + // ); + // } // // CALLING FUNCTIONS diff --git a/tests/test_parse.rs b/tests/test_parse.rs index 3f2ee2f048..092f52b2e3 100644 --- a/tests/test_parse.rs +++ b/tests/test_parse.rs @@ -21,7 +21,7 @@ mod test_parse { use roc::parse::ast::CommentOrNewline::*; use roc::parse::ast::Expr::{self, *}; use roc::parse::ast::Pattern::*; - use roc::parse::ast::{Attempting, Spaceable}; + use roc::parse::ast::{Attempting, Def, Spaceable}; use roc::parse::parser::{Fail, FailReason}; use roc::region::{Located, Region}; use std::{f64, i64}; @@ -635,15 +635,26 @@ mod test_parse { #[test] fn basic_def() { + let arena = Bump::new(); + let newlines = bumpalo::vec![in &arena; Newline, Newline]; + let def = Def::BodyOnly( + Located::new(0, 0, 0, 1, Identifier("x")), + arena.alloc(Located::new(0, 0, 2, 3, Int("5"))), + ); + let defs = bumpalo::vec![in &arena; (Vec::new_in(&arena).into_bump_slice(), def)]; + let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let loc_ret = Located::new(2, 2, 0, 2, ret); + let expected = Defs(arena.alloc((defs, loc_ret))); + assert_parses_to( indoc!( r#" - x = 5 + x=5 42 "# ), - Str(""), + expected, ); }