Handle things separated by things differently

I noticed that programs like '{} would parse correctly while '{ } would
expect an inner element. This was because the leading space was actually
part of the element parser, which is a violation of the (as of yet
unspoken) rule that parsers should not parse surrounding whitespace.

Because whitespace whas treated differently from everywhere else and
because this implementation was wrong, I decided to reimplement it,
abstracting the concept of things separated by other things with
optional trailing things. I did this in such a way that surrounding
whitespace is not touched.
This commit is contained in:
Joscha 2022-11-20 20:25:39 +01:00
parent 407786b98c
commit 6533c9dcf7
10 changed files with 116 additions and 77 deletions

View file

@ -47,3 +47,23 @@ impl HasSpan for Ident {
self.span self.span
} }
} }
#[derive(Debug, Clone)]
pub enum Separated<E, S1, S2> {
Empty(Span),
NonEmpty {
first_elem: E,
last_elems: Vec<(S1, E)>,
trailing: Option<S2>,
span: Span,
},
}
impl<E, S1, S2> HasSpan for Separated<E, S1, S2> {
fn span(&self) -> Span {
match self {
Separated::Empty(span) => *span,
Separated::NonEmpty { span, .. } => *span,
}
}
}

View file

@ -3,7 +3,7 @@ use std::fmt;
use crate::builtin::Builtin; use crate::builtin::Builtin;
use crate::span::{HasSpan, Span}; use crate::span::{HasSpan, Span};
use super::{Expr, Ident, Space}; use super::{Expr, Ident, Separated, Space};
#[derive(Clone)] #[derive(Clone)]
pub enum NumLitStr { pub enum NumLitStr {
@ -126,11 +126,13 @@ impl HasSpan for TableLitElem {
} }
/// `'{ a, foo: b }` /// `'{ a, foo: b }`
///
/// Structure: `'{ s0 elems s1 }`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TableLit { pub struct TableLit {
pub elems: Vec<(Space, TableLitElem, Space)>, pub s0: Space,
/// `Some` if there is a trailing comma, `None` otherwise. pub elems: Separated<TableLitElem, (Space, Space), Space>,
pub trailing_comma: Option<Space>, pub s1: Space,
pub span: Span, pub span: Span,
} }

View file

@ -1,6 +1,6 @@
use crate::span::{HasSpan, Span}; use crate::span::{HasSpan, Span};
use super::{Expr, Space, TableLitElem}; use super::{Expr, Separated, Space, TableLitElem};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Program { pub enum Program {
@ -12,12 +12,12 @@ pub enum Program {
span: Span, span: Span,
}, },
/// Structure: `s0 module elems trailing_comma` /// Structure: `s0 module s1 elems s2`
Module { Module {
s0: Space, s0: Space,
elems: Vec<(Space, TableLitElem, Space)>, s1: Space,
/// `Some` if there is a trailing comma, `None` otherwise. elems: Separated<TableLitElem, (Space, Space), Space>,
trailing_comma: Option<Space>, s2: Space,
span: Span, span: Span,
}, },
} }

View file

@ -1,6 +1,6 @@
use crate::span::{HasSpan, Span}; use crate::span::{HasSpan, Span};
use super::{Expr, Space, TableLitElem}; use super::{Expr, Separated, Space, TableLitElem};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum TableConstrElem { pub enum TableConstrElem {
@ -31,11 +31,13 @@ impl HasSpan for TableConstrElem {
} }
/// `{ a, b, foo: c, [d]: e }` /// `{ a, b, foo: c, [d]: e }`
///
/// Structure: `{ s0 elems s1 }`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TableConstr { pub struct TableConstr {
pub elems: Vec<(Space, TableConstrElem, Space)>, pub s0: Space,
/// `Some` if there is a trailing comma, `None` otherwise. pub elems: Separated<TableConstrElem, (Space, Space), Space>,
pub trailing_comma: Option<Space>, pub s1: Space,
pub span: Span, pub span: Span,
} }

View file

@ -1,6 +1,6 @@
use crate::span::{HasSpan, Span}; use crate::span::{HasSpan, Span};
use super::{Expr, Ident, Space}; use super::{Expr, Ident, Separated, Space};
// TODO Make table patterns recursive // TODO Make table patterns recursive
@ -30,12 +30,14 @@ impl HasSpan for TablePatternElem {
} }
} }
/// `'{ foo, bar: baz }` /// `{ foo, bar: baz }`
///
/// Structure: `{ s0 elems s1 }`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TablePattern { pub struct TablePattern {
pub elems: Vec<(Space, TablePatternElem, Space)>, pub s0: Space,
/// `Some` if there is a trailing comma, `None` otherwise. pub elems: Separated<TablePatternElem, (Space, Space), Space>,
pub trailing_comma: Option<Space>, pub s1: Space,
pub span: Span, pub span: Span,
} }

View file

@ -3,7 +3,7 @@
use chumsky::prelude::*; use chumsky::prelude::*;
use chumsky::text::Character; use chumsky::text::Character;
use crate::ast::{Ident, Line, Space}; use crate::ast::{Ident, Line, Separated, Space};
use crate::span::Span; use crate::span::Span;
pub type Error = Simple<char, Span>; pub type Error = Simple<char, Span>;
@ -58,3 +58,27 @@ pub fn ident() -> EParser<Ident> {
pub fn local(space: EParser<Space>) -> EParser<Option<Space>> { pub fn local(space: EParser<Space>) -> EParser<Option<Space>> {
text::keyword("local").ignore_then(space).or_not().boxed() text::keyword("local").ignore_then(space).or_not().boxed()
} }
// This function is more of a utility function. Because of this and to keep the
// code nicer, I have decided that the rules specified in the `parser` module
// don't apply to it.
pub fn separated_by<E: 'static, S1: 'static, S2: 'static>(
elem: impl Parser<char, E, Error = Error> + Clone + 'static,
separator: impl Parser<char, S1, Error = Error> + 'static,
trailing_separator: impl Parser<char, S2, Error = Error> + 'static,
) -> EParser<Separated<E, S1, S2>> {
elem.clone()
.then(separator.then(elem).repeated())
.then(trailing_separator.or_not())
.or_not()
.map_with_span(|s, span| match s {
Some(((first_elem, last_elems), trailing)) => Separated::NonEmpty {
first_elem,
last_elems,
trailing,
span,
},
None => Separated::Empty(span),
})
.boxed()
}

View file

@ -7,7 +7,7 @@ use crate::ast::{
}; };
use crate::builtin::Builtin; use crate::builtin::Builtin;
use super::basic::{EParser, Error}; use super::basic::{separated_by, EParser, Error};
fn builtin_lit() -> impl Parser<char, Builtin, Error = Error> { fn builtin_lit() -> impl Parser<char, Builtin, Error = Error> {
just('\'').ignore_then(choice(( just('\'').ignore_then(choice((
@ -154,22 +154,18 @@ fn table_lit(
space: EParser<Space>, space: EParser<Space>,
table_lit_elem: EParser<TableLitElem>, table_lit_elem: EParser<TableLitElem>,
) -> impl Parser<char, TableLit, Error = Error> { ) -> impl Parser<char, TableLit, Error = Error> {
let elem = space let separator = space.clone().then_ignore(just(',')).then(space.clone());
let trailing_separator = space.clone().then_ignore(just(','));
space
.clone() .clone()
.then(table_lit_elem) .then(separated_by(table_lit_elem, separator, trailing_separator))
.then(space.clone()) .then(space)
.map(|((s0, elem), s1)| (s0, elem, s1)); .delimited_by(just("'{"), just('}'))
.map_with_span(|((s0, elems), s1), span| TableLit {
let trailing_comma = just(',').ignore_then(space).or_not(); s0,
let elems = elem.separated_by(just(',')).then(trailing_comma);
just("'{")
.ignore_then(elems)
.then_ignore(just('}'))
.map_with_span(|(elems, trailing_comma), span| TableLit {
elems, elems,
trailing_comma, s1,
span, span,
}) })
} }

View file

@ -4,7 +4,7 @@ use chumsky::prelude::*;
use crate::ast::{Expr, Program, Space, TableLitElem}; use crate::ast::{Expr, Program, Space, TableLitElem};
use super::basic::EParser; use super::basic::{separated_by, EParser};
pub fn program( pub fn program(
space: EParser<Space>, space: EParser<Space>,
@ -17,20 +17,19 @@ pub fn program(
.then(space.clone()) .then(space.clone())
.map_with_span(|((s0, expr), s1), span| Program::Expr { s0, expr, s1, span }); .map_with_span(|((s0, expr), s1), span| Program::Expr { s0, expr, s1, span });
let elem = space let separator = space.clone().then_ignore(just(',')).then(space.clone());
.clone() let trailing_separator = space.clone().then_ignore(just(','));
.then(table_lit_elem)
.then(space.clone())
.map(|((s0, elem), s1)| (s0, elem, s1));
let trailing_comma = just(',').ignore_then(space.clone()).or_not();
let module = space let module = space
.clone()
.then_ignore(text::keyword("module")) .then_ignore(text::keyword("module"))
.then(elem.separated_by(just(','))) .then(space.clone())
.then(trailing_comma) .then(separated_by(table_lit_elem, separator, trailing_separator))
.map_with_span(|((s0, elems), trailing_comma), span| Program::Module { .then(space.clone())
.map_with_span(|(((s0, s1), elems), s2), span| Program::Module {
s0, s0,
s1,
elems, elems,
trailing_comma, s2,
span, span,
}); });

View file

@ -4,13 +4,13 @@ use chumsky::prelude::*;
use crate::ast::{Expr, Space, TableConstr, TableConstrElem, TableLitElem}; use crate::ast::{Expr, Space, TableConstr, TableConstrElem, TableLitElem};
use super::basic::{EParser, Error}; use super::basic::{separated_by, EParser, Error};
fn table_constr_elem( fn table_constr_elem(
space: EParser<Space>, space: EParser<Space>,
table_lit_elem: EParser<TableLitElem>, table_lit_elem: EParser<TableLitElem>,
expr: EParser<Expr>, expr: EParser<Expr>,
) -> impl Parser<char, TableConstrElem, Error = Error> { ) -> impl Parser<char, TableConstrElem, Error = Error> + Clone {
let lit = table_lit_elem.map(TableConstrElem::Lit); let lit = table_lit_elem.map(TableConstrElem::Lit);
let indexed = just('[') let indexed = just('[')
@ -42,22 +42,19 @@ pub fn table_constr(
table_lit_elem: EParser<TableLitElem>, table_lit_elem: EParser<TableLitElem>,
expr: EParser<Expr>, expr: EParser<Expr>,
) -> EParser<TableConstr> { ) -> EParser<TableConstr> {
let elem = space let elem = table_constr_elem(space.clone(), table_lit_elem, expr);
let separator = space.clone().then_ignore(just(',')).then(space.clone());
let trailing_separator = space.clone().then_ignore(just(','));
space
.clone() .clone()
.then(table_constr_elem(space.clone(), table_lit_elem, expr)) .then(separated_by(elem, separator, trailing_separator))
.then(space.clone()) .then(space)
.map(|((s0, elem), s1)| (s0, elem, s1)); .delimited_by(just('{'), just('}'))
.map_with_span(|((s0, elems), s1), span| TableConstr {
let trailing_comma = just(',').ignore_then(space).or_not(); s0,
let elems = elem.separated_by(just(',')).then(trailing_comma);
just('{')
.ignore_then(elems)
.then_ignore(just('}'))
.map_with_span(|(elems, trailing_comma), span| TableConstr {
elems, elems,
trailing_comma, s1,
span, span,
}) })
.boxed() .boxed()

View file

@ -4,12 +4,12 @@ use chumsky::prelude::*;
use crate::ast::{Expr, Ident, Space, TableDestr, TablePattern, TablePatternElem}; use crate::ast::{Expr, Ident, Space, TableDestr, TablePattern, TablePatternElem};
use super::basic::{EParser, Error}; use super::basic::{separated_by, EParser, Error};
fn table_pattern_elem( fn table_pattern_elem(
space: EParser<Space>, space: EParser<Space>,
ident: EParser<Ident>, ident: EParser<Ident>,
) -> impl Parser<char, TablePatternElem, Error = Error> { ) -> impl Parser<char, TablePatternElem, Error = Error> + Clone {
let positional = ident.clone().map(TablePatternElem::Positional); let positional = ident.clone().map(TablePatternElem::Positional);
let named = ident let named = ident
@ -30,22 +30,19 @@ fn table_pattern_elem(
} }
pub fn table_pattern(space: EParser<Space>, ident: EParser<Ident>) -> EParser<TablePattern> { pub fn table_pattern(space: EParser<Space>, ident: EParser<Ident>) -> EParser<TablePattern> {
let elem = space let elem = table_pattern_elem(space.clone(), ident);
let separator = space.clone().then_ignore(just(',')).then(space.clone());
let trailing_separator = space.clone().then_ignore(just(','));
space
.clone() .clone()
.then(table_pattern_elem(space.clone(), ident)) .then(separated_by(elem, separator, trailing_separator))
.then(space.clone()) .then(space)
.map(|((s0, elem), s1)| (s0, elem, s1)); .delimited_by(just('{'), just('}'))
.map_with_span(|((s0, elems), s1), span| TablePattern {
let trailing_comma = just(',').ignore_then(space).or_not(); s0,
let elems = elem.separated_by(just(',')).then(trailing_comma);
just('{')
.ignore_then(elems)
.then_ignore(just('}'))
.map_with_span(|(elems, trailing_comma), span| TablePattern {
elems, elems,
trailing_comma, s1,
span, span,
}) })
.boxed() .boxed()