Got let-exprs and vars coexisting

This commit is contained in:
Richard Feldman 2019-05-22 19:24:31 -04:00
parent 23e2f04468
commit 21d1a99a1f
4 changed files with 124 additions and 49 deletions

View file

@ -20,5 +20,5 @@ pub enum Expr {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Operator {
Plus, Minus, Star, Slash, DoubleSlash,
Plus, Minus, Star, Slash, DoubleSlash, Equals,
}

View file

@ -5,10 +5,10 @@ use std::char;
use parse_state::{IndentablePosition};
use combine::parser::char::{char, string, spaces, digit, hex_digit, HexDigit, alpha_num};
use combine::parser::repeat::{many, count_min_max, skip_until};
use combine::parser::repeat::{many, count_min_max};
use combine::parser::item::{any, satisfy_map, value, position};
use combine::parser::combinator::{look_ahead, not_followed_by};
use combine::{attempt, choice, eof, many1, parser, Parser, optional, between, unexpected, unexpected_any};
use combine::{attempt, choice, eof, many1, parser, Parser, optional, between, unexpected_any};
use combine::error::{Consumed, ParseError};
use combine::stream::{Stream, Positioned};
@ -45,6 +45,12 @@ where I: Stream<Item = char, Position = IndentablePosition>,
many::<Vec<_>, _>(choice((char(' '), char('\n')))).with(value(()))
}
fn whitespace1<I>() -> impl Parser<Input = I, Output = ()>
where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position> {
many1::<Vec<_>, _>(choice((char(' '), char('\n')))).with(value(()))
}
fn spaces1<I>() -> impl Parser<Input = I, Output = ()>
where I: Stream<Item = char, Position = IndentablePosition>,
@ -71,8 +77,8 @@ where I: Stream<Item = char, Position = IndentablePosition>,
fn indented_space<I>(min_indent: i32) -> impl Parser<Input = I, Output = char>
where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position> {
indentation().then(move |indent| {
if indent >= min_indent {
position().then(move |pos: IndentablePosition| {
if pos.is_indenting || pos.indent_col >= min_indent {
choice((char(' '), char('\n'))).left()
} else {
unexpected_any("bad indentation on let-expression").right()
@ -95,8 +101,8 @@ parser! {
number_literal(),
char_literal(),
if_expr(min_indent),
func_or_var(min_indent),
let_expr(min_indent),
func_or_var(min_indent),
))
.and(
// Optionally follow the expression with an operator,
@ -174,6 +180,7 @@ where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position>
{
choice((
string("==").map(|_| Operator::Equals),
char('+').map(|_| Operator::Plus),
char('-').map(|_| Operator::Minus),
char('*').map(|_| Operator::Star),
@ -185,30 +192,32 @@ pub fn let_expr<I>(min_indent: i32) -> impl Parser<Input = I, Output = Expr>
where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position>
{
ident().and(indentation())
attempt(
ident().and(indentation()).message("malformed identifier inside declaration")
.skip(whitespace()).message("whitespace after identifier")
.and(
char('=').with(indentation())
// If the "=" after the identifier turns out to be
// either "==" or "=>" then this is not a declaration!
.skip(not_followed_by(choice((char('='), char('>')))))
)
)
.skip(whitespace())
.and(char('=').with(indentation()))
.skip(whitespace())
.then(|((var_name, original_indent), equals_sign_indent)| {
panic!("original_indent {}, equals_sign_indent {}", original_indent, equals_sign_indent);
if equals_sign_indent < original_indent /* `<` because '=' should be same indent or greater */ {
.then(move |((var_name, original_indent), equals_sign_indent)| {
if original_indent < min_indent {
unexpected_any("this declaration is outdented too far").left()
} else if equals_sign_indent < original_indent /* `<` because '=' should be same indent or greater */ {
unexpected_any("the = in this declaration seems outdented").left()
} else {
expr_body(original_indent + 1 /* declaration body must be indented relative to original decl */)
// .skip(
// skip_until(indentation().then(move |final_expr_indent| {
// // The final expr must be back at *exactly* the original indentation.
// if final_expr_indent == original_indent {
// value(()).left()
// } else {
// // This just means we haven't found the final expr yet.
// unexpected("this declaration is missing its return expression").right()
// }
// }))
// )
.and(expr_body(original_indent))
.map(move |(var_expr, in_expr)| {
Expr::Let(var_name.to_owned(), Box::new(var_expr), Box::new(in_expr))
.skip(whitespace1())
.and(expr_body(original_indent).and(indentation()))
.then(move |(var_expr, (in_expr, in_expr_indent))| {
if in_expr_indent != original_indent {
unexpected_any("the return expression was indented differently from the original declaration").left()
} else {
value(Expr::Let(var_name.to_owned(), Box::new(var_expr), Box::new(in_expr))).right()
}
}).right()
}
})
@ -219,7 +228,6 @@ where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position>
{
ident()
.skip(not_followed_by(attempt(spaces().with(char('=')))))
.and(optional(
attempt(
indented_spaces1(min_indent)
@ -247,13 +255,13 @@ where I: Stream<Item = char, Position = IndentablePosition>,
let ident_str:String = chars.into_iter().collect();
if valid_start_char {
if ident_str == "if" {
unexpected_any("Reserved keyword `if`").left()
} else {
value(ident_str).right()
match ident_str.as_str() {
"if" => unexpected_any("Reserved keyword `if`").left(),
"then" => unexpected_any("Reserved keyword `then`").left(),
_ => value(ident_str).right()
}
} else {
unexpected_any("Starting character").left()
unexpected_any("First character in an identifier that was not a lowercase letter").left()
}
})
}
@ -453,12 +461,12 @@ where I: Stream<Item = char, Position = IndentablePosition>,
}
},
Err(_) => {
unexpected_any("TODO Non-digit chars after decimal point in number literal").left()
unexpected_any("non-digit characters after decimal point in a number literal").left()
}
}
},
(Err(_), _) =>
unexpected_any("TODO looked like a number but was actually malformed ident").left()
unexpected_any("looked like a number but was actually malformed ident").left()
}
})
}

View file

@ -16,7 +16,7 @@ use combine::stream::state::{Positioner, RangePositioner};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct IndentablePosition {
/// Current line of the input
pub row: i32,
pub line: i32,
/// Current column of the input
pub column: i32,
@ -32,14 +32,14 @@ clone_resetable! { () IndentablePosition }
impl Default for IndentablePosition {
fn default() -> Self {
IndentablePosition { row: 1, column: 1, indent_col: 1, is_indenting: true }
IndentablePosition { line: 1, column: 1, indent_col: 1, is_indenting: true }
}
}
impl fmt::Display for IndentablePosition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "row: {}, column: {}, indent_col: {}, is_indenting: {}",
self.row, self.column, self.indent_col, self.is_indenting)
write!(f, "line: {}, column: {}, indent_col: {}, is_indenting: {}",
self.line, self.column, self.indent_col, self.is_indenting)
}
}
@ -62,7 +62,7 @@ impl Positioner<char> for IndentablePosition {
match *item {
'\n' => {
self.column = 1;
self.row += 1;
self.line += 1;
self.indent_col = 1;
self.is_indenting = true;
},

View file

@ -11,7 +11,7 @@ mod tests {
use roc::parse;
use roc::parse_state::{IndentablePosition};
use combine::{Parser, eof};
use combine::error::{ParseError, StringStreamError};
use combine::error::{ParseError};
use combine::stream::{Stream};
use combine::easy;
use combine::stream::state::{State};
@ -233,15 +233,18 @@ mod tests {
#[test]
fn parse_single_operator_with_var() {
assert_eq!(
parse_standalone("x + 1"),
// It's important that this isn't mistaken for
// a declaration like (x = 1)
parse_standalone("x == 1"),
Ok((Operator(
Box::new(Var("x".to_string())),
Plus,
Equals,
Box::new(Int(1))
), ""))
);
}
#[test]
fn parse_single_operator() {
match parse_standalone("1234 + 567") {
@ -250,6 +253,7 @@ mod tests {
assert_eq!(op, Plus);
assert_eq!(*v2, Int(567));
},
_ => panic!("Expression didn't parse"),
}
}
@ -575,7 +579,73 @@ mod tests {
// LET
#[test]
fn parse_let() {
fn parse_let_returning_number() {
assert_eq!(
// let x = 5 in -10
parse_standalone("x = 5\n-10"),
Ok((
Let("x".to_string(), Box::new(Int(5)), Box::new(Int(-10))),
"")
)
);
assert_eq!(
// let x = 5 in 10
parse_standalone("x=5\n-10"),
Ok((
Let("x".to_string(), Box::new(Int(5)), Box::new(Int(-10))),
"")
)
);
}
#[test]
fn parse_let_with_operator() {
assert_eq!(
// let x = 5 + 10 in -20
parse_standalone("x = 5 + 10\n-20"),
Ok((
Let("x".to_string(), Box::new(Int(5)), Box::new(Int(-10))),
"")
)
);
assert_eq!(
// let x = 5 + 10 in -20
parse_standalone("x=5\n +10\n-20"),
Ok((
Let("x".to_string(), Box::new(Int(5)), Box::new(Int(-10))),
"")
)
);
}
#[test]
fn parse_invalid_let_returning_number() {
assert!(
parse_standalone("x=5\n -10").is_err(),
"Expected parsing error"
);
}
#[test]
fn parse_nested_let() {
assert_eq!(
// let x = 5 in let y = 12 in 3
parse_standalone("x = 5\ny = 12\n3"),
Ok((
Let("x".to_string(), Box::new(Int(5)),
Box::new(
Let("y".to_string(), Box::new(Int(12)),
Box::new(Int(3))
))),
"")
)
);
}
#[test]
fn parse_let_returning_var() {
assert_eq!(
parse_standalone("x=5\nx"),
Ok((
@ -587,12 +657,9 @@ mod tests {
#[test]
fn parse_bad_equals_indent_let() {
assert_eq!(
parse_standalone(" x=\n5\n\n5"),
Ok((
Let("x".to_string(), Box::new(Int(5)), Box::new(Var("x".to_string()))),
"")
)
assert!(
parse_standalone(" x=\n5\n\n5").is_err(),
"Expected parsing error"
);
}
}