mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Fix parsing edge case re: function calls at eof
This commit is contained in:
parent
4df8064407
commit
c8edddfd48
7 changed files with 1283 additions and 963 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)| {
|
||||
|
|
|
@ -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
67
tests/helpers/mod.rs
Normal 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()))),
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
1916
tests/test_parse.rs
1916
tests/test_parse.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue