From cc74c370458d0f30b138db27c435792af914dde3 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Nov 2019 05:37:19 -0500 Subject: [PATCH] Use loc! macro instead of loc() --- src/parse/mod.rs | 111 ++++++++++++++++++----------------- src/parse/parser.rs | 64 +++++++++----------- src/parse/record.rs | 15 ++++- src/parse/type_annotation.rs | 14 ++--- tests/helpers/mod.rs | 35 ++++++++++- tests/test_format.rs | 6 +- 6 files changed, 138 insertions(+), 107 deletions(-) diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 51eafacfdc..959286e2c0 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -5,6 +5,7 @@ pub mod ident; pub mod keyword; pub mod module; pub mod number_literal; +#[macro_use] pub mod parser; pub mod problems; pub mod record; @@ -27,7 +28,7 @@ use parse::collection::collection; 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, + allocated, and, attempt, between, char, either, map, map_with_arena, not, not_followed_by, one_of10, one_of17, one_of2, one_of3, 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, @@ -57,7 +58,7 @@ fn app_module<'a>() -> impl Parser<'a, Module<'a>> { fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> { map( and( - skip_first(string("interface"), and(space1(1), loc(ident()))), + skip_first(string("interface"), and(space1(1), loc!(ident()))), and(mod_header_list("exposes"), mod_header_list("imports")), ), |( @@ -118,7 +119,7 @@ fn mod_header_list<'a>( > { and( and(skip_second(space1(1), string(kw)), space1(1)), - collection(char('['), loc(mod_header_entry()), char(','), char(']'), 1), + collection(char('['), loc!(mod_header_entry()), char(','), char(']'), 1), ) } @@ -150,15 +151,15 @@ fn loc_parse_expr_body_without_operators<'a>( ) -> ParseResult<'a, Located>> { 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)), + 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)), ) .parse(arena, state) } @@ -170,8 +171,8 @@ pub fn unary_op<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { one_of2( map_with_arena( and( - loc(char('!')), - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(char('!')), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), ), |arena, (loc_op, loc_expr)| { Expr::UnaryOp(arena.alloc(loc_expr), loc_op.map(|_| UnaryOp::Not)) @@ -179,8 +180,8 @@ pub fn unary_op<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { ), map_with_arena( and( - loc(char('-')), - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(char('-')), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), ), |arena, (loc_op, loc_expr)| { Expr::UnaryOp(arena.alloc(loc_expr), loc_op.map(|_| UnaryOp::Negate)) @@ -200,11 +201,11 @@ fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseRe // 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(binop())), + and(space0(min_indent), loc!(binop())), // The spaces *after* the operator can be attached directly to // the expression following the operator. space0_before( - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), min_indent, ), )), @@ -232,11 +233,11 @@ fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseRe pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located>> { then( - loc(and( + loc!(and( between( char('('), space0_around( - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), min_indent, ), char(')'), @@ -427,11 +428,11 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result, /// It would be too weird to parse; imagine `(UserId userId) : ...` above `(UserId userId) = ...` pub fn loc_parenthetical_def<'a>(min_indent: u16) -> impl Parser<'a, Located>> { move |arena, state| { - let (loc_tuple, state) = loc(and( + let (loc_tuple, state) = loc!(and( space0_after( between( char('('), - space0_around(loc(pattern(min_indent)), min_indent), + space0_around(loc!(pattern(min_indent)), min_indent), char(')'), ), min_indent, @@ -484,7 +485,7 @@ pub fn def<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>> { // Spaces after the '=' (at a normal indentation level) and then the expr. // The expr itself must be indented more than the pattern and '=' space0_before( - loc(move |arena, state| parse_expr(indented_more, arena, state)), + loc!(move |arena, state| parse_expr(indented_more, arena, state)), min_indent, ), ), @@ -579,17 +580,17 @@ fn parse_def_expr<'a>( // // It should be indented more than the original, and it will // end when outdented again. - loc(move |arena, state| parse_expr(indented_more, arena, state)), + loc!(move |arena, state| parse_expr(indented_more, arena, state)), and( // Optionally parse additional defs. zero_or_more(allocated(space1_before( - loc(def(original_indent)), + loc!(def(original_indent)), original_indent, ))), // Parse the final expression that will be returned. // It should be indented the same amount as the original. space1_before( - loc(move |arena, state| parse_expr(original_indent, arena, state)), + loc!(move |arena, state| parse_expr(original_indent, arena, state)), original_indent, ), ), @@ -640,15 +641,15 @@ fn loc_parse_function_arg<'a>( ) -> ParseResult<'a, Located>> { 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()), + 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()), ) .parse(arena, state) } @@ -689,7 +690,7 @@ fn closure<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { attempt( Attempting::ClosureBody, space0_before( - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), min_indent, ), ), @@ -714,20 +715,20 @@ fn parse_closure_param<'a>( ) -> ParseResult<'a, Located>> { one_of5( // An ident is the most common param, e.g. \foo -> ... - loc(ident_pattern()), + loc!(ident_pattern()), // Underscore is also common, e.g. \_ -> ... - loc(underscore_pattern()), + loc!(underscore_pattern()), // You can destructure records in params, e.g. \{ x, y } -> ... - loc(record_destructure(min_indent)), + loc!(record_destructure(min_indent)), // If you wrap it in parens, you can match any arbitrary pattern at all. // e.g. \User.UserId userId -> ... between( char('('), - space0_around(loc(pattern(min_indent)), min_indent), + space0_around(loc!(pattern(min_indent)), min_indent), char(')'), ), // The least common, but still allowed, e.g. \Foo -> ... - loc(map(unqualified_variant(), |name| { + loc!(map(unqualified_variant(), |name| { Pattern::Variant(&[], name) })), ) @@ -766,7 +767,7 @@ fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> { map( collection( char('{'), - loc(ident_pattern()), + loc!(ident_pattern()), char(','), char('}'), min_indent, @@ -791,7 +792,7 @@ pub fn case_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { Attempting::CaseCondition, skip_second( space1_around( - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), min_indent, ), string(keyword::WHEN), @@ -825,7 +826,7 @@ pub fn case_branches<'a>( // 2. Parse the other branches. Their indentation levels must be == the first branch's. let (mut loc_first_pattern, state) = - space1_before(loc(pattern(min_indent)), min_indent).parse(arena, state)?; + space1_before(loc!(pattern(min_indent)), min_indent).parse(arena, state)?; let original_indent = state.indent_col; let indented_more = original_indent + 1; let (spaces_before_arrow, state) = space0(min_indent).parse(arena, state)?; @@ -844,7 +845,7 @@ pub fn case_branches<'a>( string("->"), // The expr must be indented more than the pattern preceding it space0_before( - loc(move |arena, state| parse_expr(indented_more, arena, state)), + loc!(move |arena, state| parse_expr(indented_more, arena, state)), indented_more, ), ) @@ -855,7 +856,7 @@ pub fn case_branches<'a>( let branch_parser = and( then( - space1_around(loc(pattern(min_indent)), min_indent), + space1_around(loc!(pattern(min_indent)), min_indent), move |_arena, state, loc_pattern| { if state.indent_col == original_indent { Ok((loc_pattern, state)) @@ -869,7 +870,7 @@ pub fn case_branches<'a>( skip_first( string("->"), space1_before( - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), min_indent, ), ), @@ -900,7 +901,7 @@ pub fn if_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { skip_first( string(keyword::IF), space1_around( - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), min_indent, ), ), @@ -908,14 +909,14 @@ pub fn if_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { skip_first( string(keyword::THEN), space1_around( - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), min_indent, ), ), skip_first( string(keyword::ELSE), space1_before( - loc(move |arena, state| parse_expr(min_indent, arena, state)), + loc!(move |arena, state| parse_expr(min_indent, arena, state)), min_indent, ), ), @@ -945,7 +946,7 @@ fn unary_negate_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located(min_indent: u16) -> impl Parser<'a, Vec<'a, Located(min_indent: u16) -> impl Parser<'a, Expr<'a>> { then( and( - loc(ident()), + loc!(ident()), optional(either( // There may optionally be function args after this ident loc_function_args(min_indent), @@ -1074,7 +1075,7 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { } pub fn ident_without_apply<'a>() -> impl Parser<'a, Expr<'a>> { - then(loc(ident()), move |_arena, state, loc_ident| { + then(loc!(ident()), move |_arena, state, loc_ident| { Ok((ident_to_expr(loc_ident.value), state)) }) } @@ -1155,7 +1156,7 @@ fn binop<'a>() -> impl Parser<'a, BinOp> { pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { let elems = collection( char('['), - loc(expr(min_indent)), + loc!(expr(min_indent)), char(','), char(']'), min_indent, @@ -1181,7 +1182,7 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { and( attempt( Attempting::Record, - loc(record(loc(expr(min_indent)), min_indent)), + loc!(record(loc!(expr(min_indent)), min_indent)), ), optional(and(space0(min_indent), equals_with_indent())), ), diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 83554934df..ef6197b8a4 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -1,7 +1,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use parse::ast::Attempting; -use region::{Located, Region}; +use region::Region; use std::{char, u16}; /// A position in a source file. @@ -330,33 +330,6 @@ where } } -#[inline(always)] -pub fn loc_impl<'a, P, Val>(parser: P) -> impl Parser<'a, Located> -where - P: Parser<'a, Val>, -{ - move |arena, state: State<'a>| { - let start_col = state.column; - let start_line = state.line; - - match parser.parse(arena, state) { - Ok((value, state)) => { - let end_col = state.column; - let end_line = state.line; - let region = Region { - start_col, - start_line, - end_col, - end_line, - }; - - Ok((Located { region, value }, state)) - } - Err((fail, state)) => Err((fail, state)), - } - } -} - #[inline(always)] pub fn zero_or_more_impl<'a, P, A>(parser: P) -> impl Parser<'a, Vec<'a, A>> where @@ -1334,15 +1307,6 @@ where BoxedParser::new(map_with_arena_impl(parser, transform)) } -pub fn loc<'a, P, Val>(parser: P) -> BoxedParser<'a, Located> -where - P: Parser<'a, Val>, - P: 'a, - Val: 'a, -{ - BoxedParser::new(loc_impl(parser)) -} - pub fn attempt<'a, P, Val>(attempting: Attempting, parser: P) -> BoxedParser<'a, Val> where P: Parser<'a, Val>, @@ -1379,3 +1343,29 @@ where { BoxedParser::new(one_or_more_impl(parser)) } + +#[macro_export] +macro_rules! loc { + ($parser:expr) => { + move |arena, state: State<'a>| { + let start_col = state.column; + let start_line = state.line; + + match $parser.parse(arena, state) { + Ok((value, state)) => { + let end_col = state.column; + let end_line = state.line; + let region = Region { + start_col, + start_line, + end_col, + end_line, + }; + + Ok((Located { region, value }, state)) + } + Err((fail, state)) => Err((fail, state)), + } + } + }; +} diff --git a/src/parse/record.rs b/src/parse/record.rs index 19d7f4c226..91e270633e 100644 --- a/src/parse/record.rs +++ b/src/parse/record.rs @@ -4,8 +4,8 @@ use parse::ast::Spaceable; use parse::blankspace::{space0, space0_before}; use parse::collection::collection; use parse::ident::unqualified_ident; -use parse::parser::{and, char, loc, map_with_arena, optional, skip_first, Parser}; -use region::Located; +use parse::parser::{and, char, map_with_arena, optional, skip_first, Parser, State}; +use region::{Located, Region}; /// Parse a record - generally one of these two: /// @@ -30,6 +30,15 @@ where ) } +/// For some reason, record() needs to use this instead of using the loc! macro directly. +#[inline(always)] +pub fn loc<'a, P, Val>(parser: P) -> impl Parser<'a, Located> +where + P: Parser<'a, Val>, +{ + loc!(parser) +} + fn record_field<'a, P, S>(val_parser: P, min_indent: u16) -> impl Parser<'a, AssignedField<'a, S>> where P: Parser<'a, Located>, @@ -42,7 +51,7 @@ where map_with_arena( and( // You must have a field name, e.g. "email" - loc(unqualified_ident()), + loc!(unqualified_ident()), and( space0(min_indent), // Having a value is optional; both `{ email }` and `{ email: blah }` work. diff --git a/src/parse/type_annotation.rs b/src/parse/type_annotation.rs index 91e8a98869..abdf936222 100644 --- a/src/parse/type_annotation.rs +++ b/src/parse/type_annotation.rs @@ -5,22 +5,22 @@ use collections::arena_join; use parse::ast::{Attempting, TypeAnnotation}; use parse::blankspace::{space0_around, space1_before}; use parse::parser::{ - and, between, char, loc, map, map_with_arena, one_of5, optional, skip_first, string, - unexpected, unexpected_eof, zero_or_more, ParseResult, Parser, State, + and, between, char, map, map_with_arena, one_of5, optional, skip_first, string, unexpected, + unexpected_eof, zero_or_more, ParseResult, Parser, State, }; use parse::record::record; -use region::Located; +use region::{Located, Region}; pub fn located<'a>(min_indent: u16) -> impl Parser<'a, Located>> { one_of5( // The `*` type variable, e.g. in (List *) Wildcard, - map(loc(char('*')), |loc_val| { + map(loc!(char('*')), |loc_val| { loc_val.map(|_| TypeAnnotation::Wildcard) }), loc_parenthetical_type(min_indent), - loc(record_type(min_indent)), - loc(applied_type(min_indent)), - loc(parse_type_variable), + loc!(record_type(min_indent)), + loc!(applied_type(min_indent)), + loc!(parse_type_variable), ) } diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index fbfadc9a56..2b99f26869 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -1,5 +1,4 @@ extern crate bumpalo; -extern crate roc; use self::bumpalo::Bump; use roc::can; @@ -13,11 +12,41 @@ use roc::ident::Ident; use roc::parse; use roc::parse::ast::{self, Attempting}; use roc::parse::blankspace::space0_before; -use roc::parse::parser::{loc, Fail, Parser, State}; +use roc::parse::parser::{Fail, Parser, State}; use roc::region::{Located, Region}; use roc::subs::{Subs, Variable}; use roc::types::{Expected, Type}; +// TODO Figure out some way to import this macro from roc::parse::parser - it has been +// surprisingly difficult to convince an integration test to do this. +// +// See https://users.rust-lang.org/t/sharing-code-and-macros-in-tests-directory/3098/4 +#[macro_export] +macro_rules! loc { + ($parser:expr) => { + move |arena, state: State<'a>| { + let start_col = state.column; + let start_line = state.line; + + match $parser.parse(arena, state) { + Ok((value, state)) => { + let end_col = state.column; + let end_line = state.line; + let region = Region { + start_col, + start_line, + end_col, + end_line, + }; + + Ok((Located { region, value }, state)) + } + Err((fail, state)) => Err((fail, state)), + } + } + }; +} + #[allow(dead_code)] pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) @@ -26,7 +55,7 @@ pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, #[allow(dead_code)] pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result>, Fail> { let state = State::new(&input, Attempting::Module); - let parser = space0_before(loc(parse::expr(0)), 0); + let parser = space0_before(loc!(parse::expr(0)), 0); let answer = parser.parse(&arena, state); answer diff --git a/tests/test_format.rs b/tests/test_format.rs index 7db24035bb..364a01fd04 100644 --- a/tests/test_format.rs +++ b/tests/test_format.rs @@ -3,6 +3,7 @@ extern crate pretty_assertions; #[macro_use] extern crate indoc; extern crate bumpalo; +#[macro_use] extern crate roc; #[cfg(test)] @@ -11,11 +12,12 @@ mod test_format { use roc::parse; use roc::parse::ast::{format, Attempting, Expr}; use roc::parse::blankspace::space0_before; - use roc::parse::parser::{loc, Fail, Parser, State}; + use roc::parse::parser::{Fail, Parser, State}; + use roc::region::{Located, Region}; fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { let state = State::new(&input, Attempting::Module); - let parser = space0_before(loc(parse::expr(0)), 0); + let parser = space0_before(loc!(parse::expr(0)), 0); let answer = parser.parse(&arena, state); answer