mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Got let-exprs and vars coexisting
This commit is contained in:
parent
23e2f04468
commit
21d1a99a1f
4 changed files with 124 additions and 49 deletions
|
@ -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,
|
||||
}
|
||||
|
|
74
src/parse.rs
74
src/parse.rs
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue