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)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Operator { 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 parse_state::{IndentablePosition};
use combine::parser::char::{char, string, spaces, digit, hex_digit, HexDigit, alpha_num}; 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::item::{any, satisfy_map, value, position};
use combine::parser::combinator::{look_ahead, not_followed_by}; 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::error::{Consumed, ParseError};
use combine::stream::{Stream, Positioned}; use combine::stream::{Stream, Positioned};
@ -45,6 +45,12 @@ where I: Stream<Item = char, Position = IndentablePosition>,
many::<Vec<_>, _>(choice((char(' '), char('\n')))).with(value(())) 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 = ()> fn spaces1<I>() -> impl Parser<Input = I, Output = ()>
where I: Stream<Item = char, Position = IndentablePosition>, 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> fn indented_space<I>(min_indent: i32) -> impl Parser<Input = I, Output = char>
where I: Stream<Item = char, Position = IndentablePosition>, where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position> { I::Error: ParseError<I::Item, I::Range, I::Position> {
indentation().then(move |indent| { position().then(move |pos: IndentablePosition| {
if indent >= min_indent { if pos.is_indenting || pos.indent_col >= min_indent {
choice((char(' '), char('\n'))).left() choice((char(' '), char('\n'))).left()
} else { } else {
unexpected_any("bad indentation on let-expression").right() unexpected_any("bad indentation on let-expression").right()
@ -95,8 +101,8 @@ parser! {
number_literal(), number_literal(),
char_literal(), char_literal(),
if_expr(min_indent), if_expr(min_indent),
func_or_var(min_indent),
let_expr(min_indent), let_expr(min_indent),
func_or_var(min_indent),
)) ))
.and( .and(
// Optionally follow the expression with an operator, // 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> I::Error: ParseError<I::Item, I::Range, I::Position>
{ {
choice(( choice((
string("==").map(|_| Operator::Equals),
char('+').map(|_| Operator::Plus), char('+').map(|_| Operator::Plus),
char('-').map(|_| Operator::Minus), char('-').map(|_| Operator::Minus),
char('*').map(|_| Operator::Star), 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>, where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position> 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()) .skip(whitespace())
.and(char('=').with(indentation())) .then(move |((var_name, original_indent), equals_sign_indent)| {
.skip(whitespace()) if original_indent < min_indent {
.then(|((var_name, original_indent), equals_sign_indent)| { unexpected_any("this declaration is outdented too far").left()
panic!("original_indent {}, equals_sign_indent {}", original_indent, equals_sign_indent); } else if equals_sign_indent < original_indent /* `<` because '=' should be same indent or greater */ {
if equals_sign_indent < original_indent /* `<` because '=' should be same indent or greater */ {
unexpected_any("the = in this declaration seems outdented").left() unexpected_any("the = in this declaration seems outdented").left()
} else { } else {
expr_body(original_indent + 1 /* declaration body must be indented relative to original decl */) expr_body(original_indent + 1 /* declaration body must be indented relative to original decl */)
// .skip( .skip(whitespace1())
// skip_until(indentation().then(move |final_expr_indent| { .and(expr_body(original_indent).and(indentation()))
// // The final expr must be back at *exactly* the original indentation. .then(move |(var_expr, (in_expr, in_expr_indent))| {
// if final_expr_indent == original_indent { if in_expr_indent != original_indent {
// value(()).left() unexpected_any("the return expression was indented differently from the original declaration").left()
// } else { } else {
// // This just means we haven't found the final expr yet. value(Expr::Let(var_name.to_owned(), Box::new(var_expr), Box::new(in_expr))).right()
// 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))
}).right() }).right()
} }
}) })
@ -219,7 +228,6 @@ where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position> I::Error: ParseError<I::Item, I::Range, I::Position>
{ {
ident() ident()
.skip(not_followed_by(attempt(spaces().with(char('=')))))
.and(optional( .and(optional(
attempt( attempt(
indented_spaces1(min_indent) indented_spaces1(min_indent)
@ -247,13 +255,13 @@ where I: Stream<Item = char, Position = IndentablePosition>,
let ident_str:String = chars.into_iter().collect(); let ident_str:String = chars.into_iter().collect();
if valid_start_char { if valid_start_char {
if ident_str == "if" { match ident_str.as_str() {
unexpected_any("Reserved keyword `if`").left() "if" => unexpected_any("Reserved keyword `if`").left(),
} else { "then" => unexpected_any("Reserved keyword `then`").left(),
value(ident_str).right() _ => value(ident_str).right()
} }
} else { } 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(_) => { 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(_), _) => (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)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct IndentablePosition { pub struct IndentablePosition {
/// Current line of the input /// Current line of the input
pub row: i32, pub line: i32,
/// Current column of the input /// Current column of the input
pub column: i32, pub column: i32,
@ -32,14 +32,14 @@ clone_resetable! { () IndentablePosition }
impl Default for IndentablePosition { impl Default for IndentablePosition {
fn default() -> Self { 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 { impl fmt::Display for IndentablePosition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "row: {}, column: {}, indent_col: {}, is_indenting: {}", write!(f, "line: {}, column: {}, indent_col: {}, is_indenting: {}",
self.row, self.column, self.indent_col, self.is_indenting) self.line, self.column, self.indent_col, self.is_indenting)
} }
} }
@ -62,7 +62,7 @@ impl Positioner<char> for IndentablePosition {
match *item { match *item {
'\n' => { '\n' => {
self.column = 1; self.column = 1;
self.row += 1; self.line += 1;
self.indent_col = 1; self.indent_col = 1;
self.is_indenting = true; self.is_indenting = true;
}, },

View file

@ -11,7 +11,7 @@ mod tests {
use roc::parse; use roc::parse;
use roc::parse_state::{IndentablePosition}; use roc::parse_state::{IndentablePosition};
use combine::{Parser, eof}; use combine::{Parser, eof};
use combine::error::{ParseError, StringStreamError}; use combine::error::{ParseError};
use combine::stream::{Stream}; use combine::stream::{Stream};
use combine::easy; use combine::easy;
use combine::stream::state::{State}; use combine::stream::state::{State};
@ -233,15 +233,18 @@ mod tests {
#[test] #[test]
fn parse_single_operator_with_var() { fn parse_single_operator_with_var() {
assert_eq!( 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( Ok((Operator(
Box::new(Var("x".to_string())), Box::new(Var("x".to_string())),
Plus, Equals,
Box::new(Int(1)) Box::new(Int(1))
), "")) ), ""))
); );
} }
#[test] #[test]
fn parse_single_operator() { fn parse_single_operator() {
match parse_standalone("1234 + 567") { match parse_standalone("1234 + 567") {
@ -250,6 +253,7 @@ mod tests {
assert_eq!(op, Plus); assert_eq!(op, Plus);
assert_eq!(*v2, Int(567)); assert_eq!(*v2, Int(567));
}, },
_ => panic!("Expression didn't parse"), _ => panic!("Expression didn't parse"),
} }
} }
@ -575,7 +579,73 @@ mod tests {
// LET // LET
#[test] #[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!( assert_eq!(
parse_standalone("x=5\nx"), parse_standalone("x=5\nx"),
Ok(( Ok((
@ -587,12 +657,9 @@ mod tests {
#[test] #[test]
fn parse_bad_equals_indent_let() { fn parse_bad_equals_indent_let() {
assert_eq!( assert!(
parse_standalone(" x=\n5\n\n5"), parse_standalone(" x=\n5\n\n5").is_err(),
Ok(( "Expected parsing error"
Let("x".to_string(), Box::new(Int(5)), Box::new(Var("x".to_string()))),
"")
)
); );
} }
} }