diff --git a/src/can/expr.rs b/src/can/expr.rs index 15607796fa..f019cf16d9 100644 --- a/src/can/expr.rs +++ b/src/can/expr.rs @@ -10,7 +10,6 @@ pub enum Expr { // Literals Int(i64), Float(f64), - EmptyStr, Str(Box), Char(char), // OBSOLETE List(Vec>), diff --git a/src/can/mod.rs b/src/can/mod.rs index 758e157398..5aa5b36215 100644 --- a/src/can/mod.rs +++ b/src/can/mod.rs @@ -11,7 +11,7 @@ use collections::{ImMap, ImSet, MutMap, MutSet}; use ident::Ident; // use graph::{strongly_connected_component, topological_sort}; use operator::Operator; -// use operator::Operator::Pizza; +use operator::Operator::Pizza; use parse::ast; use region::{Located, Region}; use std::i64; @@ -28,7 +28,7 @@ pub mod symbol; pub fn canonicalize_declaration<'a>( home: String, name: &str, - loc_expr: Located>, + loc_expr: &'a Located>, declared_idents: &ImMap, declared_variants: &ImMap>>, ) -> ( @@ -72,33 +72,42 @@ impl Output { fn canonicalize<'a>( env: &mut Env, scope: &mut Scope, - loc_expr: Located>, + loc_expr: &'a Located>, ) -> (Located, Output) { use self::Expr::*; - let (expr, output) = match loc_expr.value { + let (expr, output) = match &loc_expr.value { ast::Expr::Int(string) => (int_from_parsed(string, &mut env.problems), Output::new()), ast::Expr::Float(string) => (float_from_parsed(string, &mut env.problems), Output::new()), - ast::Expr::EmptyRecord => (EmptyRecord, Output::new()), - ast::Expr::Str(string) => (Str(string.into()), Output::new()), - ast::Expr::EmptyStr => (EmptyStr, Output::new()), - ast::Expr::EmptyList => (EmptyList, Output::new()), + ast::Expr::Record(fields) => { + if fields.is_empty() { + (EmptyRecord, Output::new()) + } else { + panic!("TODO canonicalize nonempty record"); + } + } + ast::Expr::Str(string) => (Str(string.clone().into()), Output::new()), ast::Expr::List(elems) => { let mut output = Output::new(); - let mut can_elems = Vec::with_capacity(elems.len()); - for loc_elem in elems.into_iter() { - let (can_expr, elem_out) = canonicalize(env, scope, loc_elem); + if elems.is_empty() { + (EmptyList, output) + } else { + let mut can_elems = Vec::with_capacity(elems.len()); - output.references = output.references.union(elem_out.references); + for loc_elem in elems.iter() { + let (can_expr, elem_out) = canonicalize(env, scope, loc_elem); - can_elems.push(can_expr); + output.references = output.references.union(elem_out.references); + + can_elems.push(can_expr); + } + + // A list literal is never a tail call! + output.tail_call = None; + + (List(can_elems), output) } - - // A list literal is never a tail call! - output.tail_call = None; - - (List(can_elems), output) } //ast::Expr::If(loc_cond, loc_true, loc_false) => { @@ -128,86 +137,83 @@ fn canonicalize<'a>( // (expr, output) //} + ast::Expr::Apply((loc_fn, loc_args)) => { + // Canonicalize the function expression and its arguments + let (fn_expr, mut output) = canonicalize(env, scope, loc_fn); + let mut args = Vec::new(); + let mut outputs = Vec::new(); - //ast::Expr::Apply(loc_fn, loc_args) => { - // // Canonicalize the function expression and its arguments - // let (fn_expr, mut output) = canonicalize(env, scope, *loc_fn); - // let mut args = Vec::new(); - // let mut outputs = Vec::new(); + for loc_arg in loc_args.iter() { + let (arg_expr, arg_out) = canonicalize(env, scope, loc_arg); - // for loc_arg in loc_args { - // let (arg_expr, arg_out) = canonicalize(env, scope, loc_arg); + args.push(arg_expr); + outputs.push(arg_out); + } - // args.push(arg_expr); - // outputs.push(arg_out); - // } + match &fn_expr.value { + &Var(ref sym) => { + output.references.calls.insert(sym.clone()); + } + _ => (), + }; - // match &fn_expr.value { - // &Var(ref sym) => { - // output.references.calls.insert(sym.clone()); - // } - // _ => (), - // }; + let expr = Call(Box::new(fn_expr), args); - // let expr = Call(Box::new(fn_expr), args); + for arg_out in outputs { + output.references = output.references.union(arg_out.references); + } - // for arg_out in outputs { - // output.references = output.references.union(arg_out.references); - // } + // We're not tail-calling a symbol (by name), we're tail-calling a function value. + output.tail_call = None; - // // We're not tail-calling a symbol (by name), we're tail-calling a function value. - // output.tail_call = None; + (expr, output) + } + ast::Expr::Operator((loc_left, loc_op, loc_right)) => { + // Canonicalize the nested expressions + let (left_expr, left_out) = canonicalize(env, scope, loc_left); + let (right_expr, mut output) = canonicalize(env, scope, loc_right); - // (expr, output) - //} + // Incorporate both expressions into a combined Output value. + output.references = output.references.union(left_out.references); - //expr::Expr::Operator(loc_left, op, loc_right) => { - // // Canonicalize the nested expressions - // let (left_expr, left_out) = canonicalize(env, scope, *loc_left); - // let (right_expr, mut output) = canonicalize(env, scope, *loc_right); + // 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. + output.tail_call = match loc_op.value { + Pizza => match &right_expr.value { + &Var(ref sym) => Some(sym.clone()), + &Call(ref loc_boxed_expr, _) => match &loc_boxed_expr.value { + Var(sym) => Some(sym.clone()), + _ => None, + }, + _ => None, + }, + _ => None, + }; - // // Incorporate both expressions into a combined Output value. - // output.references = output.references.union(left_out.references); + let expr = Operator(Box::new(left_expr), loc_op.clone(), Box::new(right_expr)); - // // 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. - // output.tail_call = match op.value { - // Pizza => match &right_expr.value { - // &Var(ref sym) => Some(sym.clone()), - // &Call(ref loc_boxed_expr, _) => match (*loc_boxed_expr.clone()).value { - // Var(sym) => Some(sym), - // _ => None, - // }, - // _ => None, - // }, - // _ => None, - // }; + (expr, output) + } + // ast::Expr::Var(ident) => { + // let mut output = Output::new(); + // let can_expr = match resolve_ident(&env, &scope, ident, &mut output.references) { + // Ok(symbol) => Var(symbol), + // Err(ident) => { + // let loc_ident = Located { + // region: loc_expr.region.clone(), + // value: ident, + // }; - // let expr = Operator(Box::new(left_expr), op, Box::new(right_expr)); + // env.problem(Problem::UnrecognizedConstant(loc_ident.clone())); - // (expr, output) - //} + // RuntimeError(UnrecognizedConstant(loc_ident)) + // } + // }; - //expr::Expr::Var(ident) => { - // let mut output = Output::new(); - // let can_expr = match resolve_ident(&env, &scope, ident, &mut output.references) { - // Ok(symbol) => Var(symbol), - // Err(ident) => { - // let loc_ident = Located { - // region: loc_expr.region.clone(), - // value: ident, - // }; + // (can_expr, output) + // } - // env.problem(Problem::UnrecognizedConstant(loc_ident.clone())); - - // RuntimeError(UnrecognizedConstant(loc_ident)) - // } - // }; - - // (can_expr, output) - //} - - //expr::Expr::InterpolatedStr(pairs, suffix) => { + //ast::Expr::InterpolatedStr(pairs, suffix) => { // let mut output = Output::new(); // let can_pairs: Vec<(String, Located)> = pairs // .into_iter() @@ -248,7 +254,7 @@ fn canonicalize<'a>( // (InterpolatedStr(can_pairs, suffix), output) //} - //expr::Expr::ApplyVariant(variant_name, opt_args) => { + //ast::Expr::ApplyVariant(variant_name, opt_args) => { // // Canonicalize the arguments and union their references into our output. // // We'll do this even if the variant name isn't recognized, since we still // // want to report canonicalization problems with the variant's arguments, @@ -289,7 +295,7 @@ fn canonicalize<'a>( // (can_expr, output) //} - //expr::Expr::Assign(assignments, box_loc_returned) => { + //ast::Expr::Assign(assignments, box_loc_returned) => { // // The body expression gets a new scope for canonicalization. // // Shadow `scope` to make sure we don't accidentally use the original one for the // // rest of this block. @@ -544,7 +550,7 @@ fn canonicalize<'a>( // } //} - //expr::Expr::Closure(loc_arg_patterns, box_loc_body_expr) => { + //ast::Expr::Closure(loc_arg_patterns, box_loc_body_expr) => { // // The globally unique symbol that will refer to this closure once it gets converted // // into a top-level procedure for code gen. // // @@ -617,7 +623,7 @@ fn canonicalize<'a>( // (FunctionPointer(symbol), output) //} - //expr::Expr::Case(loc_cond, branches) => { + //ast::Expr::Case(loc_cond, branches) => { // // Canonicalize the conditional // let (can_cond, mut output) = canonicalize(env, scope, *loc_cond); // let mut can_branches = Vec::with_capacity(branches.len()); diff --git a/src/constrain.rs b/src/constrain.rs index 7ff6e06ef5..77d8f142b4 100644 --- a/src/constrain.rs +++ b/src/constrain.rs @@ -33,7 +33,6 @@ pub fn constrain( Int(_) => int_literal(subs, expected, region), Float(_) => float_literal(subs, expected, region), Str(_) => Eq(string(), expected, region), - EmptyStr => Eq(string(), expected, region), InterpolatedStr(pairs, _) => { let mut constraints = Vec::with_capacity(pairs.len() + 1); @@ -320,7 +319,7 @@ fn empty_list(var: Variable) -> Type { } fn string() -> Type { - builtin_type("String", "String", Vec::new()) + builtin_type("Str", "Str", Vec::new()) } fn _num(var: Variable) -> Type { diff --git a/src/lib.rs b/src/lib.rs index 0b2c7dc028..55b14e539b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![type_length_limit = "16777216"] + pub mod can; pub mod collections; pub mod graph; diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 8571102526..9b5d4fa5d8 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -1,8 +1,8 @@ use bumpalo::collections::vec::Vec; +use bumpalo::collections::String; use bumpalo::Bump; use operator::Operator; use region::{Loc, Region}; -use std::fmt::{self, Display, Formatter}; pub type VariantName = str; @@ -30,13 +30,20 @@ pub enum Expr<'a> { BinaryInt(&'a str), // String Literals - EmptyStr, Str(&'a str), BlockStr(&'a [&'a str]), + /// e.g. `(expr).foo.bar` + Field(&'a Loc>, Vec<'a, &'a str>), + /// e.g. `Foo.Bar.baz.qux` + QualifiedField(&'a [&'a str], &'a [&'a str]), + /// e.g. `.foo` + AccessorFunction(&'a str), - // List literals - EmptyList, + // Collection Literals List(Vec<'a, Loc>>), + Record(Vec<'a, Loc>>), + AssignField(Loc<&'a str>, &'a Loc>), + // Lookups Var(&'a [&'a str], &'a str), Variant(&'a [&'a str], &'a str), @@ -50,18 +57,9 @@ pub enum Expr<'a> { // Application /// To apply by name, do Apply(Var(...), ...) /// To apply a variant by name, do Apply(Variant(...), ...) - Apply(&'a (Loc>, &'a [Loc>])), + Apply(&'a (Loc>, Vec<'a, Loc>>)), Operator(&'a (Loc>, Loc, Loc>)), - // Product Types - EmptyRecord, - /// e.g. `(expr).foo.bar` - Field(&'a Expr<'a>, &'a [&'a str]), - /// e.g. `Foo.Bar.baz.qux` - QualifiedField(&'a [&'a str], &'a [&'a str]), - /// e.g. `.foo` - AccessorFunction(&'a str), - // Conditionals If(&'a Loc>), Then(&'a Loc>), @@ -103,7 +101,7 @@ pub enum Pattern<'a> { #[test] fn expr_size() { - // The size of the Expr data structure should be exactly 5 machine words. + // The size of the Expr data structure should be exactly 6 machine words. // This test helps avoid regressions wich accidentally increase its size! assert_eq!( std::mem::size_of::(), @@ -131,7 +129,7 @@ fn expr_size() { // It's also possible that 4 machine words might yield better performance // than 2, due to more data structures being inlinable, and therefore // having fewer pointers to chase. This seems worth investigating as well. - std::mem::size_of::() * 5 + std::mem::size_of::() * 6 ); } @@ -175,6 +173,7 @@ pub enum Attempting { Keyword, StringLiteral, RecordLiteral, + RecordFieldLabel, InterpolatedString, NumberLiteral, UnicodeEscape, @@ -221,21 +220,52 @@ impl<'a> Expr<'a> { } } -impl<'a> Display for Expr<'a> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use self::Expr::*; +pub fn format<'a>(arena: &'a Bump, expr: &'a Expr<'a>, _indent: u16) -> String<'a> { + use self::Expr::*; - match self { - EmptyStr => write!(f, "\"\""), - Str(string) => write!(f, "\"{}\"", string), - BlockStr(lines) => write!(f, "\"\"\"{}\"\"\"", lines.join("\n")), - Int(string) => string.fmt(f), - Float(string) => string.fmt(f), - HexInt(string) => write!(f, "0x{}", string), - BinaryInt(string) => write!(f, "0b{}", string), - OctalInt(string) => write!(f, "0o{}", string), - EmptyRecord => write!(f, "{}", "{}"), - other => panic!("TODO implement Display for AST variant {:?}", other), + let mut buf = String::new_in(arena); + + match expr { + Str(string) => { + buf.push('"'); + buf.push_str(string); + buf.push('"'); } + BlockStr(lines) => { + buf.push_str("\"\"\""); + for line in lines.iter() { + buf.push_str(line); + } + buf.push_str("\"\"\""); + } + Int(string) => buf.push_str(string), + Float(string) => buf.push_str(string), + HexInt(string) => { + buf.push('0'); + buf.push('x'); + buf.push_str(string); + } + BinaryInt(string) => { + buf.push('0'); + buf.push('b'); + buf.push_str(string); + } + OctalInt(string) => { + buf.push('0'); + buf.push('o'); + buf.push_str(string); + } + Record(fields) => { + buf.push('{'); + + for _field in fields { + panic!("TODO implement Display for record fields."); + } + + buf.push('}'); + } + other => panic!("TODO implement Display for AST variant {:?}", other), } + + buf } diff --git a/src/parse/blankspace.rs b/src/parse/blankspace.rs index c18329508e..c84e453418 100644 --- a/src/parse/blankspace.rs +++ b/src/parse/blankspace.rs @@ -2,7 +2,7 @@ use bumpalo::collections::string::String; use bumpalo::collections::vec::Vec; use bumpalo::Bump; use parse::ast::{Expr, Space}; -use parse::parser::{and, loc, map_with_arena, unexpected, unexpected_eof, Parser, State}; +use parse::parser::{and, map_with_arena, unexpected, unexpected_eof, Parser, State}; use region::Located; /// What type of comment (if any) are we currently parsing? @@ -13,15 +13,93 @@ enum CommentParsing { No, } +/// Parses the given expression with 0 or more (spaces/comments/newlines) before and/or after it. +/// Returns a Located where the location is around the Expr, ignoring the spaces. +/// If any newlines or comments were found, the Expr will be wrapped in a SpaceBefore and/or +/// SpaceAfter as appropriate. +pub fn space0_around<'a, P>(parser: P, min_indent: u16) -> impl Parser<'a, Located>> +where + P: Parser<'a, Located>>, +{ + map_with_arena( + and(space0(min_indent), and(parser, space0(min_indent))), + |arena, (spaces_before, (loc_expr, spaces_after))| { + if spaces_before.is_empty() { + if spaces_after.is_empty() { + loc_expr + } else { + Located { + region: loc_expr.region, + value: Expr::SpaceAfter(arena.alloc(loc_expr.value), spaces_after), + } + } + } else { + if spaces_after.is_empty() { + Located { + region: loc_expr.region, + value: Expr::SpaceBefore(spaces_before, arena.alloc(loc_expr.value)), + } + } else { + let wrapped_expr = Expr::SpaceAfter(arena.alloc(loc_expr.value), spaces_after); + + Located { + region: loc_expr.region, + value: Expr::SpaceBefore(spaces_before, arena.alloc(wrapped_expr)), + } + } + } + }, + ) +} + +/// Parses the given expression with 1 or more (spaces/comments/newlines) before and/or after it. +/// Returns a Located where the location is around the Expr, ignoring the spaces. +/// If any newlines or comments were found, the Expr will be wrapped in a SpaceBefore and/or +/// SpaceAfter as appropriate. +pub fn space1_around<'a, P>(parser: P, min_indent: u16) -> impl Parser<'a, Located>> +where + P: Parser<'a, Located>>, +{ + map_with_arena( + and(space1(min_indent), and(parser, space1(min_indent))), + |arena, (spaces_before, (loc_expr, spaces_after))| { + if spaces_before.is_empty() { + if spaces_after.is_empty() { + loc_expr + } else { + Located { + region: loc_expr.region, + value: Expr::SpaceAfter(arena.alloc(loc_expr.value), spaces_after), + } + } + } else { + if spaces_after.is_empty() { + Located { + region: loc_expr.region, + value: Expr::SpaceBefore(spaces_before, arena.alloc(loc_expr.value)), + } + } else { + let wrapped_expr = Expr::SpaceAfter(arena.alloc(loc_expr.value), spaces_after); + + Located { + region: loc_expr.region, + value: Expr::SpaceBefore(spaces_before, arena.alloc(wrapped_expr)), + } + } + } + }, + ) +} + /// Parses the given expression with 0 or more (spaces/comments/newlines) after it. /// Returns a Located where the location is around the Expr, ignoring the spaces. /// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found. pub fn space0_before<'a, P>(parser: P, min_indent: u16) -> impl Parser<'a, Located>> where - P: Parser<'a, Expr<'a>>, + P: Parser<'a, Located>>, { map_with_arena( - and(space0(min_indent), loc(parser)), + and(space0(min_indent), parser), |arena, (space_list, loc_expr)| { if space_list.is_empty() { loc_expr @@ -40,10 +118,10 @@ where /// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found. pub fn space1_before<'a, P>(parser: P, min_indent: u16) -> impl Parser<'a, Located>> where - P: Parser<'a, Expr<'a>>, + P: Parser<'a, Located>>, { map_with_arena( - and(space1(min_indent), loc(parser)), + and(space1(min_indent), parser), |arena, (space_list, loc_expr)| { if space_list.is_empty() { loc_expr @@ -62,10 +140,10 @@ where /// The Expr will be wrapped in a SpaceAfter if there were any newlines or comments found. pub fn space0_after<'a, P>(parser: P, min_indent: u16) -> impl Parser<'a, Located>> where - P: Parser<'a, Expr<'a>>, + P: Parser<'a, Located>>, { map_with_arena( - and(space0(min_indent), loc(parser)), + and(space0(min_indent), parser), |arena, (space_list, loc_expr)| { if space_list.is_empty() { loc_expr @@ -84,10 +162,10 @@ where /// The Expr will be wrapped in a SpaceAfter if there were any newlines or comments found. pub fn space1_after<'a, P>(parser: P, min_indent: u16) -> impl Parser<'a, Located>> where - P: Parser<'a, Expr<'a>>, + P: Parser<'a, Located>>, { map_with_arena( - and(space1(min_indent), loc(parser)), + and(space1(min_indent), parser), |arena, (space_list, loc_expr)| { if space_list.is_empty() { loc_expr @@ -286,7 +364,7 @@ fn spaces<'a>(require_at_least_one: bool, _min_indent: u16) -> impl Parser<'a, & } if require_at_least_one && chars_parsed == 0 { - Err(unexpected_eof(chars_parsed, state.attempting, state)) + Err(unexpected_eof(0, state.attempting, state)) } else { Ok((space_list.into_bump_slice(), state)) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 35361fd38c..e12b7988e1 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -8,38 +8,43 @@ pub mod parser; pub mod problems; pub mod string_literal; +use bumpalo::collections::String; use bumpalo::collections::Vec; use bumpalo::Bump; use operator::Operator; use parse::ast::{Attempting, Expr}; -use parse::blankspace::{space0, space0_before, space1_before}; +use parse::blankspace::{space0, space0_around, space0_before, space1_before}; use parse::ident::{ident, Ident}; use parse::number_literal::number_literal; use parse::parser::{ - and, attempt, char, either, loc, map, map_with_arena, one_of4, one_of6, one_or_more, optional, - skip_first, string, unexpected, unexpected_eof, Either, ParseResult, Parser, State, + and, attempt, between, char, either, loc, map, map_with_arena, one_of4, one_of8, one_or_more, + optional, sep_by0, skip_first, skip_second, string, unexpected, unexpected_eof, Either, + ParseResult, Parser, State, }; use parse::string_literal::string_literal; use region::Located; 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. + // as this causes rustc to stack overflow. Thus, parse_expr must be a + // separate function which recurses by calling itself directly. move |arena, state| parse_expr(min_indent, arena, state) } -fn parse_expr_body_without_operators<'a>( +fn loc_parse_expr_body_without_operators<'a>( min_indent: u16, arena: &'a Bump, state: State<'a>, -) -> ParseResult<'a, Expr<'a>> { - one_of6( - string_literal(), - record_literal(), - number_literal(), - when(min_indent), - conditional(min_indent), - ident_etc(min_indent), +) -> ParseResult<'a, Located>> { + one_of8( + loc_parenthetical_expr(min_indent), + loc(string_literal()), + loc(number_literal()), + loc(record_literal(min_indent)), + loc(list_literal(min_indent)), + loc(when(min_indent)), + loc(conditional(min_indent)), + loc(ident_etc(min_indent)), ) .parse(arena, state) } @@ -48,11 +53,18 @@ fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseRe let expr_parser = map_with_arena( and( // First parse the body without operators, then try to parse possible operators after. - loc(move |arena, state| parse_expr_body_without_operators(min_indent, arena, state)), + 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 + // 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())), + // The spaces *after* the operator can be attached directly to + // the expression following the operator. space0_before( - move |arena, state| parse_expr(min_indent, arena, state), + loc(move |arena, state| parse_expr(min_indent, arena, state)), min_indent, ), )), @@ -62,6 +74,7 @@ fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseRe let loc_expr1 = if spaces_before_op.is_empty() { loc_expr1 } else { + // Attach the spaces retroactively to the expression preceding the operator. Expr::with_spaces_after(arena, loc_expr1, spaces_before_op) }; let tuple = arena.alloc((loc_expr1, loc_op, loc_expr2)); @@ -75,10 +88,69 @@ fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseRe attempt(Attempting::Expression, expr_parser).parse(arena, state) } -fn function_arg<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { +pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located>> { + map_with_arena( + loc(and( + between( + char('('), + space0_around( + loc(move |arena, state| parse_expr(min_indent, arena, state)), + min_indent, + ), + char(')'), + ), + optional(either( + // There may optionally be function args after the ')' + // e.g. ((foo bar) baz) + loc_function_args(min_indent), + // 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 ':'!) + // + // Also, there may be a '.' for field access (e.g. `(foo).bar`), + // but we only want to look for that if there weren't any args, + // as if there were any args they'd have consumed it anyway + // e.g. in `((foo bar) baz.blah)` the `.blah` will be consumed by the `baz` parser + either( + one_or_more(skip_first(char('.'), field_label())), + and(space0(min_indent), either(char('='), char(':'))), + ), + )), + )), + |arena, loc_expr_with_extras| { + // We parse the parenthetical expression *and* the arguments after it + // in one region, so that (for example) the region for Apply includes its args. + let (loc_expr, opt_extras) = loc_expr_with_extras.value; + + match opt_extras { + Some(Either::First(loc_args)) => Located { + region: loc_expr_with_extras.region, + value: Expr::Apply(arena.alloc((loc_expr, loc_args))), + }, + // '=' after optional spaces + Some(Either::Second(Either::Second((_space_list, Either::First(()))))) => { + panic!("TODO handle def, making sure not to drop comments!"); + } + // ':' after optional spaces + Some(Either::Second(Either::Second((_space_list, Either::Second(()))))) => { + panic!("TODO handle annotation, making sure not to drop comments!"); + } + // '.' and a record field immediately after ')', no optional spaces + Some(Either::Second(Either::First(fields))) => Located { + region: loc_expr_with_extras.region, + value: Expr::Field(arena.alloc(loc_expr), fields), + }, + None => loc_expr, + } + }, + ) +} + +fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located>> { // Don't parse operators, because they have a higher precedence than function application. // If we encounter one, we're done parsing function args! - move |arena, state| parse_expr_body_without_operators(min_indent, arena, state) + move |arena, state| loc_parse_expr_body_without_operators(min_indent, arena, state) } pub fn when<'a>(_min_indent: u16) -> impl Parser<'a, Expr<'a>> { @@ -95,35 +167,35 @@ pub fn conditional<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { map_with_arena( skip_first( string(keyword::IF), - space1_before(expr(min_indent), min_indent), + space1_before(loc(expr(min_indent)), min_indent), ), |arena, loc_expr| Expr::If(arena.alloc(loc_expr)), ), map_with_arena( skip_first( string(keyword::THEN), - space1_before(expr(min_indent), min_indent), + space1_before(loc(expr(min_indent)), min_indent), ), |arena, loc_expr| Expr::Then(arena.alloc(loc_expr)), ), map_with_arena( skip_first( string(keyword::ELSE), - space1_before(expr(min_indent), min_indent), + space1_before(loc(expr(min_indent)), min_indent), ), |arena, loc_expr| Expr::Else(arena.alloc(loc_expr)), ), map_with_arena( skip_first( string(keyword::CASE), - space1_before(expr(min_indent), min_indent), + space1_before(loc(expr(min_indent)), min_indent), ), |arena, loc_expr| Expr::Case(arena.alloc(loc_expr)), ), ) } pub fn loc_function_args<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located>>> { - one_or_more(space1_before(function_arg(min_indent), min_indent)) + one_or_more(space1_before(loc_function_arg(min_indent), min_indent)) } /// When we parse an ident like `foo ` it could be any of these: @@ -148,7 +220,6 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { ), |arena, (loc_ident, opt_extras)| { // This appears to be a var, keyword, or function application. - match opt_extras { Some(Either::First(loc_args)) => { let loc_expr = Located { @@ -156,7 +227,7 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { value: ident_to_expr(loc_ident.value), }; - Expr::Apply(arena.alloc((loc_expr, loc_args.into_bump_slice()))) + Expr::Apply(arena.alloc((loc_expr, loc_args))) } Some(Either::Second((_space_list, Either::First(())))) => { panic!("TODO handle def, making sure not to drop comments!"); @@ -189,28 +260,98 @@ pub fn operator<'a>() -> impl Parser<'a, Operator> { ) } -pub fn record_literal<'a>() -> impl Parser<'a, Expr<'a>> { - move |_arena: &'a Bump, state: State<'a>| { +pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { + let elems = collection( + char('['), + loc(expr(min_indent)), + char(','), + char(']'), + min_indent, + ); + + attempt(Attempting::List, map(elems, Expr::List)) +} + +pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { + let field = map_with_arena( + and( + loc(skip_second(field_label(), char(':'))), + space0_before( + loc(move |arena, state| parse_expr(min_indent, arena, state)), + min_indent, + ), + ), + |arena, (label, loc_expr)| Expr::AssignField(label, arena.alloc(loc_expr)), + ); + let fields = collection(char('{'), loc(field), char(','), char('}'), min_indent); + + attempt(Attempting::List, map(fields, Expr::Record)) +} + +/// A record field, e.g. "email" in `.email` or in `email:` +pub fn field_label<'a>() -> impl Parser<'a, &'a str> { + move |arena, state: State<'a>| { let mut chars = state.input.chars(); - match chars.next() { - Some('{') => (), - Some(other_char) => { - return Err(unexpected(other_char, 0, state, Attempting::RecordLiteral)); + // Field labels must start with a lowercase letter. + let first_letter = match chars.next() { + Some(ch) => { + if ch.is_alphabetic() && ch.is_lowercase() { + ch + } else { + return Err(unexpected(ch, 0, state, Attempting::RecordFieldLabel)); + } } None => { - return Err(unexpected_eof(0, Attempting::RecordLiteral, state)); + return Err(unexpected_eof(0, Attempting::RecordFieldLabel, state)); + } + }; + + let mut buf = String::with_capacity_in(1, arena); + + buf.push(first_letter); + + while let Some(ch) = chars.next() { + // After the first character, only these are allowed: + // + // * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers + // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() + // * A ':' indicating the end of the field + if ch.is_alphabetic() || ch.is_ascii_digit() { + buf.push(ch); + } else { + // This is the end of the field. We're done! + break; } } - match chars.next() { - Some('}') => { - let next_state = state.advance_without_indenting(2)?; + let chars_parsed = buf.len(); - Ok((Expr::EmptyRecord, next_state)) - } - Some(other_char) => Err(unexpected(other_char, 0, state, Attempting::RecordLiteral)), - None => Err(unexpected_eof(0, Attempting::RecordLiteral, state)), - } + Ok(( + buf.into_bump_str(), + state.advance_without_indenting(chars_parsed)?, + )) } } + +/// Parse zero or more elements between two braces (e.g. square braces). +/// Elements can be optionally surrounded by spaces, and are separated by a +/// delimiter (e.g comma-separated). Braces and delimiters get discarded. +pub fn collection<'a, Elem, OpeningBrace, ClosingBrace, Sep>( + opening_brace: OpeningBrace, + elem: Elem, + sep: Sep, + closing_brace: ClosingBrace, + min_indent: u16, +) -> impl Parser<'a, Vec<'a, Located>>> +where + OpeningBrace: Parser<'a, ()>, + Elem: Parser<'a, Located>>, + Sep: Parser<'a, ()>, + ClosingBrace: Parser<'a, ()>, +{ + skip_first( + opening_brace, + skip_second(sep_by0(sep, space0_around(elem, min_indent)), closing_brace), + ) +} diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 4dae81a3b5..b9cf33a2f3 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -392,24 +392,41 @@ pub fn char<'a>(expected: char) -> impl Parser<'a, ()> { } } -/// A string with no newlines in it. -pub fn string<'a>(string: &'static str) -> impl Parser<'a, ()> { +/// A hardcoded keyword string with no newlines in it. +pub fn string<'a>(keyword: &'static str) -> impl Parser<'a, ()> { // We can't have newlines because we don't attempt to advance the row // in the state, only the column. - debug_assert!(!string.contains("\n")); + debug_assert!(!keyword.contains("\n")); move |_arena, state: State<'a>| { let input = state.input; - let len = string.len(); + let len = keyword.len(); // TODO do this comparison in one SIMD instruction (on supported systems) match input.get(0..len) { - Some(next_str) if next_str == string => Ok(((), state.advance_without_indenting(len)?)), + Some(next_str) if next_str == keyword => { + Ok(((), state.advance_without_indenting(len)?)) + } _ => Err(unexpected_eof(0, Attempting::Keyword, state)), } } } +/// Parse everything between two braces (e.g. parentheses), skipping both braces +/// and keeping only whatever was parsed in between them. +pub fn between<'a, P, OpeningBrace, ClosingBrace, Val>( + opening_brace: OpeningBrace, + parser: P, + closing_brace: ClosingBrace, +) -> impl Parser<'a, Val> +where + OpeningBrace: Parser<'a, ()>, + P: Parser<'a, Val>, + ClosingBrace: Parser<'a, ()>, +{ + skip_first(opening_brace, skip_second(parser, closing_brace)) +} + /// Parse zero or more values separated by a delimiter (e.g. a comma) whose /// values are discarded pub fn sep_by0<'a, P, D, Val>(delimiter: D, parser: P) -> impl Parser<'a, Vec<'a, Val>> @@ -417,17 +434,45 @@ where D: Parser<'a, ()>, P: Parser<'a, Val>, { - zero_or_more(skip_first(delimiter, parser)) -} + move |arena, state: State<'a>| { + let original_attempting = state.attempting; -/// Parse one or more values separated by a delimiter (e.g. a comma) whose -/// values are discarded -pub fn sep_by1<'a, P, D, Val>(delimiter: D, parser: P) -> impl Parser<'a, Vec<'a, Val>> -where - D: Parser<'a, ()>, - P: Parser<'a, Val>, -{ - one_or_more(skip_first(delimiter, parser)) + match parser.parse(arena, state) { + Ok((first_output, next_state)) => { + let mut state = next_state; + let mut buf = Vec::with_capacity_in(1, arena); + + buf.push(first_output); + + loop { + match delimiter.parse(arena, state) { + Ok(((), next_state)) => { + // If the delimiter passed, check the element parser. + match parser.parse(arena, next_state) { + Ok((next_output, next_state)) => { + state = next_state; + buf.push(next_output); + } + Err((fail, state)) => { + // If the delimiter parsed, but the following + // element did not, that's a fatal error. + return Err(( + Fail { + attempting: original_attempting, + ..fail + }, + state, + )); + } + } + } + Err((_, old_state)) => return Ok((buf, old_state)), + } + } + } + Err((_, new_state)) => return Ok((Vec::new_in(arena), new_state)), + } + } } pub fn satisfies<'a, P, A, F>(parser: P, predicate: F) -> impl Parser<'a, A> @@ -458,22 +503,24 @@ where P2: Parser<'a, B>, { move |arena: &'a Bump, state: State<'a>| { - let original_attempting = state.attempting; + // We have to clone this because if the first parser passes and then + // the second one fails, we need to revert back to the original state. + let original_state = state.clone(); match p1.parse(arena, state) { Ok((out1, state)) => match p2.parse(arena, state) { Ok((out2, state)) => Ok(((out1, out2), state)), - Err((fail, state)) => Err(( + Err((fail, _)) => Err(( Fail { - attempting: original_attempting, + attempting: original_state.attempting, ..fail }, - state, + original_state, )), }, Err((fail, state)) => Err(( Fail { - attempting: original_attempting, + attempting: original_state.attempting, ..fail }, state, @@ -537,6 +584,38 @@ where } } +/// If the first one parses, parse the second one; if it also parses, use the +/// output from the first one. +pub fn skip_second<'a, P1, P2, A, B>(p1: P1, p2: P2) -> impl Parser<'a, A> +where + P1: Parser<'a, A>, + P2: Parser<'a, B>, +{ + move |arena: &'a Bump, state: State<'a>| { + let original_attempting = state.attempting; + + match p1.parse(arena, state) { + Ok((out1, state)) => match p2.parse(arena, state) { + Ok((_, state)) => Ok((out1, state)), + Err((fail, state)) => Err(( + Fail { + attempting: original_attempting, + ..fail + }, + state, + )), + }, + Err((fail, state)) => Err(( + Fail { + attempting: original_attempting, + ..fail + }, + state, + )), + } + } +} + pub fn optional<'a, P, T>(parser: P) -> impl Parser<'a, Option> where P: Parser<'a, T>, diff --git a/src/parse/string_literal.rs b/src/parse/string_literal.rs index 2e8bbe896a..320355431c 100644 --- a/src/parse/string_literal.rs +++ b/src/parse/string_literal.rs @@ -42,7 +42,7 @@ pub fn string_literal<'a>() -> impl Parser<'a, Expr<'a>> { // literal begins with `"""` and is a block string. return parse_block_string(arena, state, &mut chars); } else { - Expr::EmptyStr + Expr::Str("") } } else { // Start at 1 so we omit the opening `"`. diff --git a/src/pretty_print_types.rs b/src/pretty_print_types.rs index 652efd4cac..0c4f1bf62b 100644 --- a/src/pretty_print_types.rs +++ b/src/pretty_print_types.rs @@ -50,8 +50,8 @@ fn write_apply( let write_parens = use_parens && !args.is_empty(); // Hardcoded type aliases - if module_name == "String" && type_name == "String" { - buf.push_str("String"); + if module_name == "Str" && type_name == "Str" { + buf.push_str("Str"); } else if module_name == "Num" && type_name == "Num" { let arg = args .into_iter() diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index 9b4babdb4b..e8845aad83 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -53,7 +53,7 @@ pub fn can_expr_with( let (loc_expr, output, problems, procedures) = can::canonicalize_declaration( home, name, - Located::new(0, 0, 0, 0, expr), + &Located::new(0, 0, 0, 0, expr), declared_idents, declared_variants, ); diff --git a/tests/test_format.rs b/tests/test_format.rs index ea1d544527..b499916224 100644 --- a/tests/test_format.rs +++ b/tests/test_format.rs @@ -9,7 +9,7 @@ extern crate roc; mod test_format { use bumpalo::Bump; use roc::parse; - use roc::parse::ast::{Attempting, Expr}; + use roc::parse::ast::{format, Attempting, Expr}; use roc::parse::parser::{Fail, Parser, State}; fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { @@ -26,7 +26,7 @@ mod test_format { let expected = expected.trim_end(); match parse_with(&arena, input) { - Ok(actual) => assert_eq!(format!("{}", actual), expected), + Ok(actual) => assert_eq!(format(&arena, &actual, 0), expected), Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", input, error) } } diff --git a/tests/test_infer.rs b/tests/test_infer.rs index 3867722415..0da1fa236b 100644 --- a/tests/test_infer.rs +++ b/tests/test_infer.rs @@ -68,155 +68,155 @@ mod test_infer { "type inference!" "# ), - "String", + "Str", ); } - // #[test] - // fn empty_string() { - // infer_eq( - // indoc!( - // r#" - // "" - // "# - // ), - // "String", - // ); - // } + #[test] + fn empty_string() { + infer_eq( + indoc!( + r#" + "" + "# + ), + "Str", + ); + } - // // LIST + // LIST - // #[test] - // fn empty_list() { - // infer_eq( - // indoc!( - // r#" - // [] - // "# - // ), - // "List *", - // ); - // } + #[test] + fn empty_list() { + infer_eq( + indoc!( + r#" + [] + "# + ), + "List *", + ); + } - // #[test] - // fn list_of_lists() { - // infer_eq( - // indoc!( - // r#" - // [[]] - // "# - // ), - // "List (List *)", - // ); - // } + #[test] + fn list_of_lists() { + infer_eq( + indoc!( + r#" + [[]] + "# + ), + "List (List *)", + ); + } - // #[test] - // fn triple_nested_list() { - // infer_eq( - // indoc!( - // r#" - // [[[]]] - // "# - // ), - // "List (List (List *))", - // ); - // } + #[test] + fn triple_nested_list() { + infer_eq( + indoc!( + r#" + [[[]]] + "# + ), + "List (List (List *))", + ); + } - // #[test] - // fn nested_empty_list() { - // infer_eq( - // indoc!( - // r#" - // [ [], [ [] ] ] - // "# - // ), - // "List (List (List *))", - // ); - // } + #[test] + fn nested_empty_list() { + infer_eq( + indoc!( + r#" + [ [], [ [] ] ] + "# + ), + "List (List (List *))", + ); + } - // #[test] - // fn list_of_one_int() { - // infer_eq( - // indoc!( - // r#" - // [42] - // "# - // ), - // "List Int", - // ); - // } + #[test] + fn list_of_one_int() { + infer_eq( + indoc!( + r#" + [42] + "# + ), + "List Int", + ); + } - // #[test] - // fn triple_nested_int_list() { - // infer_eq( - // indoc!( - // r#" - // [[[ 5 ]]] - // "# - // ), - // "List (List (List Int))", - // ); - // } + #[test] + fn triple_nested_int_list() { + infer_eq( + indoc!( + r#" + [[[ 5 ]]] + "# + ), + "List (List (List Int))", + ); + } - // #[test] - // fn list_of_ints() { - // infer_eq( - // indoc!( - // r#" - // [ 1, 2, 3 ] - // "# - // ), - // "List Int", - // ); - // } + #[test] + fn list_of_ints() { + infer_eq( + indoc!( + r#" + [ 1, 2, 3 ] + "# + ), + "List Int", + ); + } - // #[test] - // fn nested_list_of_ints() { - // infer_eq( - // indoc!( - // r#" - // [ [ 1 ], [ 2, 3 ] ] - // "# - // ), - // "List (List Int)", - // ); - // } + #[test] + fn nested_list_of_ints() { + infer_eq( + indoc!( + r#" + [ [ 1 ], [ 2, 3 ] ] + "# + ), + "List (List Int)", + ); + } - // #[test] - // fn list_of_one_string() { - // infer_eq( - // indoc!( - // r#" - // [ "cowabunga" ] - // "# - // ), - // "List String", - // ); - // } + #[test] + fn list_of_one_string() { + infer_eq( + indoc!( + r#" + [ "cowabunga" ] + "# + ), + "List Str", + ); + } - // #[test] - // fn triple_nested_string_list() { - // infer_eq( - // indoc!( - // r#" - // [[[ "foo" ]]] - // "# - // ), - // "List (List (List String))", - // ); - // } + #[test] + fn triple_nested_string_list() { + infer_eq( + indoc!( + r#" + [[[ "foo" ]]] + "# + ), + "List (List (List Str))", + ); + } - // #[test] - // fn list_of_strings() { - // infer_eq( - // indoc!( - // r#" - // [ "foo", "bar" ] - // "# - // ), - // "List String", - // ); - // } + #[test] + fn list_of_strings() { + infer_eq( + indoc!( + r#" + [ "foo", "bar" ] + "# + ), + "List Str", + ); + } // // INTERPOLATED STRING @@ -230,47 +230,47 @@ mod test_infer { // "type inference is \(whatItIs)!" // "# // ), - // "String", + // "Str", // ); // } - // // LIST MISMATCH + // LIST MISMATCH - // #[test] - // fn mismatch_heterogeneous_list() { - // infer_eq( - // indoc!( - // r#" - // [ "foo", 5 ] - // "# - // ), - // "List ", - // ); - // } + #[test] + fn mismatch_heterogeneous_list() { + infer_eq( + indoc!( + r#" + [ "foo", 5 ] + "# + ), + "List ", + ); + } - // #[test] - // fn mismatch_heterogeneous_nested_list() { - // infer_eq( - // indoc!( - // r#" - // [ [ "foo", 5 ] ] - // "# - // ), - // "List (List )", - // ); - // } + #[test] + fn mismatch_heterogeneous_nested_list() { + infer_eq( + indoc!( + r#" + [ [ "foo", 5 ] ] + "# + ), + "List (List )", + ); + } - // #[test] - // fn mismatch_heterogeneous_nested_empty_list() { - // infer_eq( - // indoc!( - // r#" - // [ [ 1 ], [ [] ] ] - // "# - // ), - // "List (List )", - // ); - // } + #[test] + fn mismatch_heterogeneous_nested_empty_list() { + infer_eq( + indoc!( + r#" + [ [ 1 ], [ [] ] ] + "# + ), + "List (List )", + ); + } // // CLOSURE @@ -336,7 +336,7 @@ mod test_infer { // str // "# // ), - // "String", + // "Str", // ); // } @@ -410,7 +410,7 @@ mod test_infer { // b // "# // ), - // "String", + // "Str", // ); // } @@ -462,43 +462,43 @@ mod test_infer { // ); // } - // // TODO type annotations - // // TODO fix identity inference - // // TODO BoundTypeVariables - // // TODO conditionals + // TODO type annotations + // TODO fix identity inference + // TODO BoundTypeVariables + // TODO conditionals - // // #[test] - // // fn indirect_always() { - // // infer_eq( - // // indoc!(r#" - // // always = \val -> (\_ -> val) - // // alwaysFoo = always "foo" + // #[test] + // fn indirect_always() { + // infer_eq( + // indoc!(r#" + // always = \val -> (\_ -> val) + // alwaysFoo = always "foo" - // // alwaysFoo 42 - // // "#), - // // "String" - // // ); - // // } + // alwaysFoo 42 + // "#), + // "Str" + // ); + // } - // // #[test] - // // fn identity() { - // // infer_eq( - // // indoc!(r#" - // // \val -> val - // // "#), - // // "a -> a" - // // ); - // // } + // #[test] + // fn identity() { + // infer_eq( + // indoc!(r#" + // \val -> val + // "#), + // "a -> a" + // ); + // } - // // #[test] - // // fn always_function() { - // // infer_eq( - // // indoc!(r#" - // // \val -> \_ -> val - // // "#), - // // "a -> (* -> a)" - // // ); - // // } + // #[test] + // fn always_function() { + // infer_eq( + // indoc!(r#" + // \val -> \_ -> val + // "#), + // "a -> (* -> a)" + // ); + // } // // OPERATORS diff --git a/tests/test_parse.rs b/tests/test_parse.rs index 3a821cd965..1e4fd33702 100644 --- a/tests/test_parse.rs +++ b/tests/test_parse.rs @@ -53,7 +53,7 @@ mod test_parse { "" "# ), - EmptyStr, + Str(""), ); } @@ -236,7 +236,11 @@ mod test_parse { #[test] fn empty_record() { - assert_parses_to("{}", EmptyRecord); + let arena = Bump::new(); + let expected = Record(Vec::new_in(&arena)); + let actual = parse_with(&arena, "{}"); + + assert_eq!(Ok(expected), actual); } // OPERATORS @@ -464,27 +468,63 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + // LISTS + + #[test] + fn empty_list() { + let arena = Bump::new(); + let elems = Vec::new_in(&arena); + let expected = List(elems); + let actual = parse_with(&arena, "[]"); + + assert_eq!(Ok(expected), actual); + } + + #[test] + fn packed_singleton_list() { + let arena = Bump::new(); + let elems = bumpalo::vec![in &arena; Located::new(0, 0, 1, 2, Int("1"))]; + let expected = List(elems); + let actual = parse_with(&arena, "[1]"); + + assert_eq!(Ok(expected), actual); + } + + #[test] + fn spaced_singleton_list() { + let arena = Bump::new(); + let elems = bumpalo::vec![in &arena; Located::new(0, 0, 2, 3, Int("1"))]; + let expected = List(elems); + let actual = parse_with(&arena, "[ 1 ]"); + + assert_eq!(Ok(expected), actual); + } + // FIELD ACCESS #[test] - fn basic_field_expr() { + fn parenthetical_basic_field() { let arena = Bump::new(); let module_parts = Vec::new_in(&arena).into_bump_slice(); - let expr = Var(module_parts, "rec"); - let fields = bumpalo::vec![in &arena; "field"].into_bump_slice(); - let expected = Field(arena.alloc(expr), fields); + let fields = bumpalo::vec![in &arena; "field"]; + let expected = Field( + arena.alloc(Located::new(0, 0, 1, 4, Var(module_parts, "rec"))), + fields, + ); let actual = parse_with(&arena, "(rec).field"); assert_eq!(Ok(expected), actual); } #[test] - fn field_expr_qualified_var() { + fn parenthetical_field_qualified_var() { let arena = Bump::new(); let module_parts = bumpalo::vec![in &arena; "One", "Two"].into_bump_slice(); - let expr = Var(module_parts, "rec"); - let fields = bumpalo::vec![in &arena; "field"].into_bump_slice(); - let expected = Field(arena.alloc(expr), fields); + let fields = bumpalo::vec![in &arena; "field"]; + let expected = Field( + arena.alloc(Located::new(0, 0, 1, 12, Var(module_parts, "rec"))), + fields, + ); let actual = parse_with(&arena, "(One.Two.rec).field"); assert_eq!(Ok(expected), actual); @@ -520,10 +560,7 @@ mod test_parse { let module_parts = Vec::new_in(&arena).into_bump_slice(); let arg = Located::new(0, 0, 5, 6, Int("1")); let args = bumpalo::vec![in &arena; arg]; - let tuple = arena.alloc(( - Located::new(0, 0, 0, 4, Var(module_parts, "whee")), - args.into_bump_slice(), - )); + let tuple = arena.alloc((Located::new(0, 0, 0, 4, Var(module_parts, "whee")), args)); let expected = Apply(tuple); let actual = parse_with(&arena, "whee 1"); @@ -537,10 +574,7 @@ mod test_parse { let arg1 = Located::new(0, 0, 6, 8, Int("12")); let arg2 = Located::new(0, 0, 10, 12, Int("34")); let args = bumpalo::vec![in &arena; arg1, arg2]; - let tuple = arena.alloc(( - Located::new(0, 0, 0, 4, Var(module_parts, "whee")), - args.into_bump_slice(), - )); + let tuple = arena.alloc((Located::new(0, 0, 0, 4, Var(module_parts, "whee")), args)); let expected = Apply(tuple); let actual = parse_with(&arena, "whee 12 34"); @@ -551,12 +585,9 @@ mod test_parse { fn parenthetical_apply() { let arena = Bump::new(); let module_parts = Vec::new_in(&arena).into_bump_slice(); - let arg = Located::new(0, 0, 5, 6, Int("1")); + let arg = Located::new(0, 0, 7, 8, Int("1")); let args = bumpalo::vec![in &arena; arg]; - let tuple = arena.alloc(( - Located::new(0, 0, 0, 4, Var(module_parts, "whee")), - args.into_bump_slice(), - )); + let tuple = arena.alloc((Located::new(0, 0, 1, 5, Var(module_parts, "whee")), args)); let expected = Apply(tuple); let actual = parse_with(&arena, "(whee) 1");