Fix parsing edge case re: function calls at eof

This commit is contained in:
Richard Feldman 2019-07-25 08:27:50 -04:00
parent 4df8064407
commit c8edddfd48
7 changed files with 1283 additions and 963 deletions

View file

@ -57,6 +57,10 @@ pub enum Ident {
pub struct Path(String);
impl Path {
pub fn new(string: String) -> Path {
Path(string)
}
pub fn into_string(self) -> String {
let Path(str) = self;

View file

@ -5,7 +5,7 @@ pub mod parse_state;
pub mod operator;
pub mod region;
pub mod canonicalize;
mod collections;
pub mod collections;
// mod ena;
// #[macro_use]

View file

@ -394,10 +394,11 @@ where I: Stream<Item = char, Position = IndentablePosition>,
string("then"),
string("else"),
string("when"),
eof().with(value(""))
))
)
)
),
)
)
)
)
@ -538,7 +539,7 @@ pub fn func_or_var<I>(min_indent: u32) -> impl Parser<Input = I, Output = Expr>
where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position>
{
ident().and(optional(attempt(apply_args(min_indent))))
ident().and(optional(apply_args(min_indent)))
.map(|(name, opt_args): (String, Option<Vec<Located<Expr>>>)| {
// Use optional(sep_by1()) over sep_by() to avoid
// allocating a Vec in the common case where this is a var
@ -559,10 +560,10 @@ where I: Stream<Item = char, Position = IndentablePosition>,
.with(
sep_by1(
located(pattern(min_indent)),
attempt(many1::<Vec<_>, _>(skipped_indented_whitespace_char(min_indent).skip(not_followed_by(string("=>")))))
attempt(many1::<Vec<_>, _>(skipped_indented_whitespace_char(min_indent).skip(not_followed_by(string("->")))))
))
.skip(indented_whitespaces(min_indent))
.skip(string("=>"))
.skip(string("->"))
.skip(indented_whitespaces1(min_indent))
.and(located(expr_body(min_indent)))
.map(|(patterns, closure_body)| {

View file

@ -1,3 +1,5 @@
use std::fmt;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Region {
pub start_line: u32,
@ -7,7 +9,7 @@ pub struct Region {
pub end_col: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Located<T> {
pub region: Region,
pub value: T,
@ -30,3 +32,25 @@ impl<T> Located<T> {
Located { region: self.region, value: transform(&self.value) }
}
}
impl<T> fmt::Debug for Located<T>
where T: fmt::Debug
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let region = self.region;
if region.start_line == 0 && region.start_col == 0 && region.end_line == 0 && region.end_col == 0 {
// In tests, it's super common to set all Located values to 0.
// Also in tests, we don't want to bother printing the locations
// because it makes failed assertions much harder to read.
self.value.fmt(f)
} else {
write!(f, "|L {}, C {} - L {}, C {}| {:?}",
region.start_line, region.start_col,
region.end_line, region.end_col,
self.value
)
}
}
}

67
tests/helpers/mod.rs Normal file
View file

@ -0,0 +1,67 @@
use roc::expr::{Expr, Pattern};
use roc::region::{Located, Region};
pub fn loc_box<T>(val: T) -> Box<Located<T>> {
Box::new(loc(val))
}
pub fn loc<T>(val: T) -> Located<T> {
Located::new(val, Region {
start_line: 0,
start_col: 0,
end_line: 0,
end_col: 0,
})
}
pub fn zero_loc<T>(located_val: Located<T>) -> Located<T> {
loc(located_val.value)
}
/// Zero out the parse locations on everything in this Expr, so we can compare expected/actual without
/// having to account for that.
pub fn zero_loc_expr(expr: Expr) -> Expr {
use roc::expr::Expr::*;
match expr {
Int(_) | Frac(_, _) | Approx(_) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord => expr,
InterpolatedStr(pairs, string) => InterpolatedStr(pairs.into_iter().map(|( prefix, ident )| ( prefix, zero_loc(ident))).collect(), string),
Assign(assignments, loc_ret) => {
let zeroed_assignments =
assignments.into_iter().map(|( pattern, loc_expr )|
( zero_loc_pattern(pattern), loc(zero_loc_expr(loc_expr.value)) )
).collect();
Assign(zeroed_assignments, loc_box(zero_loc_expr((*loc_ret).value)))
},
CallByName(ident, args) => CallByName(ident, args.into_iter().map(|arg| loc(zero_loc_expr(arg.value))).collect()),
Apply(fn_expr, args) => Apply(loc_box(zero_loc_expr((*fn_expr).value)), args.into_iter().map(|arg| loc(zero_loc_expr(arg.value))).collect()),
Operator(left, op, right) => Operator(loc_box(zero_loc_expr((*left).value)), zero_loc(op), loc_box(zero_loc_expr((*right).value))),
Closure(patterns, body) => Closure(patterns.into_iter().map(zero_loc).collect(), loc_box(zero_loc_expr((*body).value))),
ApplyVariant(_, None) => expr,
ApplyVariant(name, Some(args)) => ApplyVariant(name, Some(args.into_iter().map(|arg| loc(zero_loc_expr(arg.value))).collect())),
If(condition, if_true, if_false) => If(loc_box(zero_loc_expr((*condition).value)), loc_box(zero_loc_expr((*if_true).value)), loc_box(zero_loc_expr((*if_false).value))),
Case(condition, branches) =>
Case(
loc_box(zero_loc_expr((*condition).value)),
branches.into_iter().map(|( pattern, loc_expr )| ( zero_loc_pattern(pattern), loc(zero_loc_expr(loc_expr.value)) )).collect()
),
}
}
/// Zero out the parse locations on everything in this Pattern, so we can compare expected/actual without
/// having to account for that.
pub fn zero_loc_pattern(loc_pattern: Located<Pattern>) -> Located<Pattern> {
use roc::expr::Pattern::*;
let pattern = loc_pattern.value;
match pattern {
Identifier(_) | Integer(_) | Fraction(_, _) | ExactString(_) | EmptyRecordLiteral | Underscore => loc(pattern),
Variant(loc_name, None) =>
loc(Variant(loc(loc_name.value), None)),
Variant(loc_name, Some(opt_located_patterns)) =>
loc(Variant(loc(loc_name.value), Some(opt_located_patterns.into_iter().map(|loc_pat| zero_loc_pattern(loc_pat)).collect()))),
}
}

View file

@ -1,10 +1,222 @@
#[macro_use] extern crate pretty_assertions;
#[macro_use] extern crate indoc;
extern crate combine;
extern crate roc;
mod helpers;
#[cfg(test)]
mod test_canonicalize {
use roc::canonicalize;
use roc::canonicalize::{Expr, Output, Problem, Resolved, LocalSymbol, Symbol};
use roc::canonicalize::Expr::*;
use roc::canonicalize::Pattern::*;
use roc::expr::{Path, Ident};
use roc::operator::Operator::*;
use roc::expr;
use roc::region::Located;
use roc::parse;
use roc::collections::{ImMap, ImSet};
use roc::parse_state::{IndentablePosition};
use combine::{Parser, eof};
use combine::stream::state::{State};
use helpers::{loc, loc_box, zero_loc_expr};
fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>) {
can_expr_with(expr_str, &ImMap::default(), &ImMap::default())
}
fn can_expr_with(
expr_str: &str,
declared_idents: &ImMap<(Option<Path>, String), Located<expr::Ident>>,
declared_variants: &ImMap<(Path, String), Located<expr::VariantName>>,
) -> (Expr, Output, Vec<Problem>) {
let parse_state: State<&str, IndentablePosition> = State::with_positioner(expr_str, IndentablePosition::default());
let expr = match parse::expr().skip(eof()).easy_parse(parse_state) {
Ok((expr, state)) => {
if !state.input.is_empty() {
panic!("There were unconsumed chars left over after parsing \"{}\" - the leftover string was: \"{}\"",
expr_str.to_string(), state.input.to_string())
}
expr
},
Err(errors) => {
panic!("Parse error trying to parse \"{}\" - {}", expr_str.to_string(), errors.to_string())
}
};
let home = Path::new("TestModule".to_string());
let (loc_expr, output, problems) =
canonicalize::canonicalize_declaration(home, loc(zero_loc_expr(expr)), declared_idents, declared_variants);
(loc_expr.value, output, problems)
}
fn recognized_local_sym(string: &str) -> Resolved<Symbol> {
Resolved::Recognized(local_sym(string))
}
fn local_sym(string: &str) -> Symbol {
Symbol::Local(local(string))
}
fn local(string: &str) -> LocalSymbol {
LocalSymbol::new(string.to_string())
}
fn check_output(
output: Output,
applied_variants: Vec<(Path, &str)>,
referenced_idents: Vec<(Option<Path>, &str)>,
tail_call: Option<Symbol>
) {
assert_eq!(
output.applied_variants,
ImSet::from(
applied_variants.into_iter().map(|(path, str_ref)|
(path, str_ref.to_string())
).collect::<Vec<_>>()
)
);
assert_eq!(
output.referenced_idents,
ImSet::from(
referenced_idents.into_iter().map(|(opt_path, str_ref)|
(opt_path, str_ref.to_string())
).collect::<Vec<_>>()
)
);
assert_eq!(output.tail_call, tail_call);
}
#[test]
fn basic_unrecognized_constant() {
let (expr, output, problems) = can_expr(indoc!(r#"
x
"#));
assert_eq!(problems, vec![
Problem::UnrecognizedConstant(loc(Ident::Unqualified("x".to_string())))
]);
assert_eq!(expr,
Var(Resolved::UnrecognizedConstant(loc(Ident::Unqualified("x".to_string()))))
);
check_output(output, vec![], vec![], None);
}
#[test]
fn complex_unrecognized_constant() {
let (expr, output, problems) = can_expr(indoc!(r#"
a = 5
b = 6
a + b * z
"#));
assert_eq!(problems, vec![
Problem::UnrecognizedConstant(loc(Ident::Unqualified("z".to_string())))
]);
assert_eq!(expr,
Assign(
vec![
(loc(Identifier(local_sym("a"))), loc(Int(5))),
(loc(Identifier(local_sym("b"))), loc(Int(6))),
],
loc_box(Operator(
loc_box(Var(recognized_local_sym("a"))),
loc(Plus),
loc_box(Operator(
loc_box(Var(recognized_local_sym("b"))),
loc(Star),
loc_box(Var(Resolved::UnrecognizedConstant(loc(Ident::Unqualified("z".to_string())))))
)),
))
)
);
check_output(output, vec![], vec![(None, "a"), (None, "b")], None);
}
#[test]
fn can_fibonacci() {
return ();
let (expr, output, problems) = can_expr(indoc!(r#"
fibonacci = \num =>
if num < 2 then
num
else
fibonacci (num - 1) + fibonacci (num - 2)
fibonacci 9
"#));
// assert_eq!(problems, vec![
// Problem::UnrecognizedConstant(loc(Ident::Unqualified("z".to_string())))
// ]);
// assert_eq!(expr,
// Assign(
// vec![
// (loc(Identifier(local_sym("a"))), loc(Int(5))),
// (loc(Identifier(local_sym("b"))), loc(Int(6))),
// ],
// loc_box(Operator(
// loc_box(Var(recognized_local_sym("a"))),
// loc(Plus),
// loc_box(Operator(
// loc_box(Var(recognized_local_sym("b"))),
// loc(Star),
// loc_box(Var(Resolved::UnrecognizedConstant(loc(Ident::Unqualified("z".to_string())))))
// )),
// ))
// )
// );
check_output(output, vec![], vec![(None, "num"), (None, "fibonacci")], None);
}
// #[test]
// fn can_fibonacci() {
// let (expr, output, problems) = can_expr(indoc!(r#"
// fibonacci = \num =>
// if num < 2 then
// num
// else
// fibonacci (num - 1) + fibonacci (num - 2)
// "#));
// assert_eq!(problems, vec![
// Problem::UnrecognizedConstant(loc(Ident::Unqualified("z".to_string())))
// ]);
// assert_eq!(expr,
// Assign(
// vec![
// (loc(Identifier(local_sym("a"))), loc(Int(5))),
// (loc(Identifier(local_sym("b"))), loc(Int(6))),
// ],
// loc_box(Operator(
// loc_box(Var(recognized_local_sym("a"))),
// loc(Plus),
// loc_box(Operator(
// loc_box(Var(recognized_local_sym("b"))),
// loc(Star),
// loc_box(Var(Resolved::UnrecognizedConstant(loc(Ident::Unqualified("z".to_string())))))
// )),
// ))
// )
// );
// check_output(output, vec![], vec![(None, "a".to_string()), (None, "b".to_string())], None);
// }
// OPERATOR PRECEDENCE

File diff suppressed because it is too large Load diff