diff --git a/CHANGELOG.md b/CHANGELOG.md index 17f9fd8e..e96d66a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project does not currently adhere to a particular versioning scheme. ## [Unreleased] +### Changed + +- Improve error messages for redefinition of types and objects. ([#485][gh-485]) +- Don't allow tabs to be used for indentation or spacing. ([#463][gh-463]) + ### Fixed - Fixed readback of numeric operations. ([#467][gh-467]) @@ -88,10 +93,12 @@ and this project does not currently adhere to a particular versioning scheme. [0.2.35]: https://github.com/HigherOrderCO/Bend/releases/tag/0.2.35 [gh-424]: https://github.com/HigherOrderCO/Bend/issues/424 [gh-451]: https://github.com/HigherOrderCO/Bend/issues/451 +[gh-463]: https://github.com/HigherOrderCO/Bend/issues/463 [gh-465]: https://github.com/HigherOrderCO/Bend/issues/465 [gh-466]: https://github.com/HigherOrderCO/Bend/issues/466 [gh-467]: https://github.com/HigherOrderCO/Bend/issues/467 [gh-479]: https://github.com/HigherOrderCO/Bend/issues/479 +[gh-485]: https://github.com/HigherOrderCO/Bend/issues/485 [gh-502]: https://github.com/HigherOrderCO/Bend/issues/502 [gh-526]: https://github.com/HigherOrderCO/Bend/issues/526 [gh-528]: https://github.com/HigherOrderCO/Bend/issues/528 diff --git a/src/fun/parser.rs b/src/fun/parser.rs index af105b90..068e1c5a 100644 --- a/src/fun/parser.rs +++ b/src/fun/parser.rs @@ -65,7 +65,7 @@ impl<'a> TermParser<'a> { pub fn parse_book(&mut self, default_book: Book, builtin: bool) -> ParseResult { let mut book = default_book; - let mut indent = self.advance_newlines(); + let mut indent = self.advance_newlines()?; let mut last_rule = None; while !self.is_eof() { let ini_idx = *self.index(); @@ -115,7 +115,7 @@ impl<'a> TermParser<'a> { let (nam, adt) = self.parse_datatype(builtin)?; let end_idx = *self.index(); self.with_ctx(book.add_adt(nam, adt), ini_idx, end_idx)?; - indent = self.advance_newlines(); + indent = self.advance_newlines()?; last_rule = None; continue; } @@ -145,7 +145,7 @@ impl<'a> TermParser<'a> { // Adding the first rule of a new definition book.defs.insert(name.clone(), Definition { name: name.clone(), rules: vec![rule], builtin }); } - indent = self.advance_newlines(); + indent = self.advance_newlines()?; last_rule = Some(name); } @@ -864,34 +864,38 @@ pub trait ParserCommons<'a>: Parser<'a> { } fn consume_new_line(&mut self) -> ParseResult<()> { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.try_consume_exactly("\r"); self.labelled(|p| p.consume_exactly("\n"), "newline") } /// Skips trivia, returns the number of trivia characters skipped in the last line. - fn advance_newlines(&mut self) -> Indent { + fn advance_newlines(&mut self) -> ParseResult { loop { - let num_spaces = self.advance_trivia_inline(); + let num_spaces = self.advance_trivia_inline()?; if self.peek_one() == Some('\r') { self.advance_one(); } if self.peek_one() == Some('\n') { self.advance_one(); } else if self.is_eof() { - return Indent::Eof; + return Ok(Indent::Eof); } else { - return Indent::Val(num_spaces); + return Ok(Indent::Val(num_spaces)); } } } /// Advances the parser to the next non-trivia character in the same line. /// Returns how many characters were advanced. - fn advance_trivia_inline(&mut self) -> isize { + fn advance_trivia_inline(&mut self) -> ParseResult { let mut char_count = 0; while let Some(c) = self.peek_one() { - if " \t".contains(c) { + if c == '\t' { + let idx = *self.index(); + return self.with_ctx(Err("Tabs are not accepted for indentation.".to_string()), idx, idx); + } + if " ".contains(c) { self.advance_one(); char_count += 1; continue; @@ -909,12 +913,13 @@ pub trait ParserCommons<'a>: Parser<'a> { } break; } - char_count + Ok(char_count) } /// Skips until the next non-trivia character in the same line. - fn skip_trivia_inline(&mut self) { - self.advance_trivia_inline(); + fn skip_trivia_inline(&mut self) -> ParseResult<()> { + self.advance_trivia_inline()?; + Ok(()) } fn expected_spanned(&mut self, exp: &str, ini_idx: usize, end_idx: usize) -> ParseResult { diff --git a/src/imp/parser.rs b/src/imp/parser.rs index ce9ffa4e..19991873 100644 --- a/src/imp/parser.rs +++ b/src/imp/parser.rs @@ -79,7 +79,7 @@ impl<'a> PyParser<'a> { /// fn parse_simple_expr(&mut self, inline: bool) -> ParseResult { if inline { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; } else { self.skip_trivia(); } @@ -133,7 +133,7 @@ impl<'a> PyParser<'a> { // postfixes if inline { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; } else { self.skip_trivia(); } @@ -335,7 +335,7 @@ impl<'a> PyParser<'a> { } if inline { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; } else { self.skip_trivia(); } @@ -373,7 +373,7 @@ impl<'a> PyParser<'a> { fn parse_infix_expr(&mut self, prec: usize, inline: bool) -> ParseResult { maybe_grow(|| { if inline { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; } else { self.skip_trivia(); } @@ -382,7 +382,7 @@ impl<'a> PyParser<'a> { } let mut lhs = self.parse_infix_expr(prec + 1, inline)?; if inline { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; } else { self.skip_trivia(); } @@ -391,7 +391,7 @@ impl<'a> PyParser<'a> { self.try_parse_oper().unwrap(); let rhs = self.parse_infix_expr(prec + 1, inline)?; lhs = Expr::Opr { op, lhs: Box::new(lhs), rhs: Box::new(rhs) }; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; } else { break; } @@ -401,7 +401,7 @@ impl<'a> PyParser<'a> { } fn consume_indent_at_most(&mut self, expected: Indent) -> ParseResult { - let got = self.advance_newlines(); + let got = self.advance_newlines()?; match (expected, got) { (_, Indent::Eof) => Ok(Indent::Eof), (Indent::Val(expected), Indent::Val(got)) if got <= expected => Ok(Indent::Val(got)), @@ -410,7 +410,7 @@ impl<'a> PyParser<'a> { } fn consume_indent_exactly(&mut self, expected: Indent) -> ParseResult<()> { - let got = self.advance_newlines(); + let got = self.advance_newlines()?; match (expected, got) { (Indent::Eof, Indent::Eof) => Ok(()), (Indent::Val(expected), Indent::Val(got)) if got == expected => Ok(()), @@ -453,18 +453,18 @@ impl<'a> PyParser<'a> { let ini_idx = *self.index(); let pat = self.parse_assign_pattern()?; let end_idx = *self.index(); - self.skip_trivia_inline(); + self.skip_trivia_inline()?; // Assignment if self.starts_with("=") { self.advance_one(); let val = self.parse_expr(true)?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.try_consume_exactly(";"); if !self.is_eof() { self.consume_new_line()?; } - let nxt_indent = self.advance_newlines(); + let nxt_indent = self.advance_newlines()?; if nxt_indent == *indent { let (nxt, nxt_indent) = self.parse_statement(indent)?; let stmt = Stmt::Assign { pat, val: Box::new(val), nxt: Some(Box::new(nxt)) }; @@ -478,7 +478,7 @@ impl<'a> PyParser<'a> { if self.starts_with("<-") { self.consume("<-")?; let val = self.parse_expr(true)?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.try_consume_exactly(";"); self.consume_indent_exactly(*indent)?; let (nxt, nxt_indent) = self.parse_statement(indent)?; @@ -494,7 +494,7 @@ impl<'a> PyParser<'a> { } if let Some(op) = self.parse_in_place_op()? { let val = self.parse_expr(true)?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.try_consume_exactly(";"); self.consume_indent_exactly(*indent)?; let (nxt, nxt_indent) = self.parse_statement(indent)?; @@ -506,7 +506,7 @@ impl<'a> PyParser<'a> { } fn parse_in_place_op(&mut self) -> ParseResult> { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let op = if self.starts_with("+=") { self.consume("+=")?; Some(InPlaceOp::Add) @@ -539,18 +539,18 @@ impl<'a> PyParser<'a> { fn parse_return(&mut self) -> ParseResult<(Stmt, Indent)> { let term = self.parse_expr(true)?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.try_consume_exactly(";"); if !self.is_eof() { self.consume_new_line()?; } - let indent = self.advance_newlines(); + let indent = self.advance_newlines()?; Ok((Stmt::Return { term: Box::new(term) }, indent)) } fn parse_if(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> { let cond = self.parse_expr(true)?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; indent.enter_level(); @@ -564,7 +564,7 @@ impl<'a> PyParser<'a> { let mut elifs = Vec::new(); while self.try_parse_keyword("elif") { let cond = self.parse_expr(true)?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; indent.enter_level(); self.consume_indent_exactly(*indent)?; @@ -577,7 +577,7 @@ impl<'a> PyParser<'a> { elifs.push((cond, then)); } self.parse_keyword("else")?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; indent.enter_level(); @@ -609,7 +609,7 @@ impl<'a> PyParser<'a> { fn parse_match(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> { let (bnd, arg) = self.parse_match_arg()?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let (with_bnd, with_arg) = self.parse_with_clause()?; self.consume_new_line()?; indent.enter_level(); @@ -638,7 +638,7 @@ impl<'a> PyParser<'a> { let arg = self.parse_expr(true)?; let end_idx = *self.index(); - self.skip_trivia_inline(); + self.skip_trivia_inline()?; match (arg, self.starts_with("=")) { (Expr::Var { nam }, true) => { self.advance_one(); @@ -651,7 +651,7 @@ impl<'a> PyParser<'a> { } fn parse_with_clause(&mut self) -> ParseResult<(Vec>, Vec)> { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let res = if self.try_parse_keyword("with") { self.list_like(|p| p.parse_with_arg(), "", ":", ",", true, 1)?.into_iter().unzip() } else { @@ -663,7 +663,7 @@ impl<'a> PyParser<'a> { fn parse_with_arg(&mut self) -> ParseResult<(Option, Expr)> { let bind = self.parse_bend_name()?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; if self.try_consume("=") { let arg = self.parse_expr(false)?; Ok((Some(bind), arg)) @@ -674,14 +674,14 @@ impl<'a> PyParser<'a> { fn parse_match_case(&mut self, indent: &mut Indent) -> ParseResult<(MatchArm, Indent)> { self.parse_keyword("case")?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let pat = if self.try_consume_exactly("_") { None } else { let nam = self.labelled(|p| p.parse_bend_name(), "name or '_'")?; Some(nam) }; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; self.consume_new_line()?; indent.enter_level(); @@ -696,7 +696,7 @@ impl<'a> PyParser<'a> { fn parse_switch(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> { let (bnd, arg) = self.parse_match_arg()?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let (with_bnd, with_arg) = self.parse_with_clause()?; indent.enter_level(); @@ -741,7 +741,7 @@ impl<'a> PyParser<'a> { fn parse_switch_case(&mut self, indent: &mut Indent) -> ParseResult<(Option, Stmt, Indent)> { self.parse_keyword("case")?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let case = if let Some(c) = self.peek_one() { match c { '_' => { @@ -755,7 +755,7 @@ impl<'a> PyParser<'a> { return self.expected("number or '_'")?; }; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; self.consume_new_line()?; indent.enter_level(); @@ -772,7 +772,7 @@ impl<'a> PyParser<'a> { fn parse_fold(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> { // Actually identical to match, except the return let (bind, arg) = self.parse_match_arg()?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let (with_bnd, with_arg) = self.parse_with_clause()?; self.consume_new_line()?; indent.enter_level(); @@ -811,7 +811,7 @@ impl<'a> PyParser<'a> { self.consume_indent_exactly(*indent)?; self.parse_keyword("when")?; let cond = self.parse_expr(true)?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; self.consume_new_line()?; indent.enter_level(); @@ -824,7 +824,7 @@ impl<'a> PyParser<'a> { return self.expected_indent(*indent, nxt_indent); } self.parse_keyword("else")?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; self.consume_new_line()?; indent.enter_level(); @@ -862,9 +862,9 @@ impl<'a> PyParser<'a> { /// /// ? fn parse_with(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let typ = self.parse_bend_name()?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; self.consume_new_line()?; indent.enter_level(); @@ -911,7 +911,7 @@ impl<'a> PyParser<'a> { // Chn pattern if self.starts_with("$") { self.advance_one(); - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let nam = self.parse_bend_name()?; return Ok(AssignPattern::Chn(nam)); } @@ -932,13 +932,13 @@ impl<'a> PyParser<'a> { /// "open" {typ} ":" {var} ";"? {nxt} fn parse_open(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let typ = self.labelled(|p| p.parse_bend_name(), "type name")?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let var = self.labelled(|p| p.parse_bend_name(), "variable name")?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.try_consume_exactly(";"); self.consume_new_line()?; self.consume_indent_exactly(*indent)?; @@ -948,13 +948,13 @@ impl<'a> PyParser<'a> { } fn parse_use(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> { - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let nam = self.parse_bend_name()?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly("=")?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let bod = self.parse_expr(true)?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.try_consume_exactly(";"); self.consume_new_line()?; self.consume_indent_exactly(*indent)?; @@ -970,15 +970,15 @@ impl<'a> PyParser<'a> { return self.with_ctx(Err(msg), idx, idx + 1); } - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let name = self.parse_top_level_name()?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let params = if self.starts_with("(") { self.list_like(|p| p.parse_bend_name(), "(", ")", ",", true, 0)? } else { vec![] }; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; self.consume_new_line()?; indent.enter_level(); @@ -998,9 +998,9 @@ impl<'a> PyParser<'a> { return self.with_ctx(Err(msg), idx, idx + 1); } - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let typ_name = self.parse_top_level_name()?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; self.consume_exactly(":")?; self.consume_new_line()?; indent.enter_level(); @@ -1025,7 +1025,7 @@ impl<'a> PyParser<'a> { let ctr_name = self.parse_top_level_name()?; let ctr_name = Name::new(format!("{typ_name}/{ctr_name}")); let mut fields = Vec::new(); - self.skip_trivia_inline(); + self.skip_trivia_inline()?; if self.starts_with("{") { fields = self.list_like(|p| p.parse_variant_field(), "{", "}", ",", true, 0)?; } @@ -1039,9 +1039,9 @@ impl<'a> PyParser<'a> { return self.with_ctx(Err(msg), idx, idx + 1); } - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let name = self.parse_top_level_name()?; - self.skip_trivia_inline(); + self.skip_trivia_inline()?; let fields = if self.starts_with("{") { self.list_like(|p| p.parse_variant_field(), "{", "}", ",", true, 0)? } else { @@ -1050,7 +1050,7 @@ impl<'a> PyParser<'a> { if !self.is_eof() { self.consume_new_line()?; } - let nxt_indent = self.advance_newlines(); + let nxt_indent = self.advance_newlines()?; Ok((Variant { name, fields }, nxt_indent)) } diff --git a/tests/golden_tests/parse_file/tab.bend b/tests/golden_tests/parse_file/tab.bend new file mode 100644 index 00000000..8bd2523e --- /dev/null +++ b/tests/golden_tests/parse_file/tab.bend @@ -0,0 +1,3 @@ +def main: + x = 2 + return x + 1 diff --git a/tests/snapshots/parse_file__tab.bend.snap b/tests/snapshots/parse_file__tab.bend.snap new file mode 100644 index 00000000..22c0aa83 --- /dev/null +++ b/tests/snapshots/parse_file__tab.bend.snap @@ -0,0 +1,8 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/parse_file/tab.bend +--- +Errors: +In tests/golden_tests/parse_file/tab.bend : +Tabs are not accepted for indentation. + 2 |  x = 2