smol lang
Diffstat (limited to 'src/parser.rs')
-rw-r--r--src/parser.rs339
1 files changed, 339 insertions, 0 deletions
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..18aa536
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,339 @@
+use crate::lexer::{Lexer, Token};
+use chumsky::{
+ input::{SpannedInput, Stream},
+ prelude::*,
+ Parser,
+};
+mod types;
+mod util;
+use types::*;
+use util::*;
+
+impl<'s> FixMetaData<'s> {
+ pub fn from_pieces(
+ x: &[Spanned<FixMetaPiece<'s>>],
+ fixness: Fix,
+ e: Span,
+ ) -> Result<Self, Error<'s>> {
+ let mut looser_than = None;
+ let mut tighter_than = None;
+ let mut assoc = None;
+ for &(x, span) in x {
+ match x {
+ FixMetaPiece::A(_) if assoc.is_some() => {
+ return Err(Rich::custom(span, "duplicate associatity meta elements"))
+ }
+ FixMetaPiece::L(_) if looser_than.is_some() => {
+ return Err(Rich::custom(span, "duplicate loosseness meta elements"))
+ }
+ FixMetaPiece::T(_) if tighter_than.is_some() => {
+ return Err(Rich::custom(span, "duplicate tightness meta elements"))
+ }
+ FixMetaPiece::A(x) => assoc = Some(x),
+ FixMetaPiece::L(x) => looser_than = Some(x),
+ FixMetaPiece::T(x) => tighter_than = Some(x),
+ }
+ }
+
+ Ok(FixMetaData {
+ looser_than,
+ tighter_than,
+ fixness,
+ assoc,
+ })
+ .and_then(|x| match x.looser_than.empty().or(x.tighter_than.empty()) {
+ None => Err(Rich::custom(e, "precedence meta required")),
+ Some(()) => Ok(x),
+ })
+ }
+}
+
+fn expr<'s>() -> parser![Expr<'s>] {
+ recursive::<_, Expr, _, _, _>(|expr| {
+ let inline_expr = recursive(|inline_expr| {
+ let val = select! {
+ Token::Unit => Expr::Value(Value::Unit),
+ Token::Int(x) => Expr::Value(Value::Int(x)),
+ Token::Float(x) => Expr::Value(Value::Float(x)),
+ Token::String(s) => Expr::Value(Value::String(s)),
+ }
+ .labelled("value");
+
+ let decl = tok![let]
+ .ignore_then(tok![ident])
+ .then_ignore(tok![=])
+ .then(inline_expr.clone())
+ .map(|(name, body)| Expr::Let {
+ name,
+ rhs: Box::new(body),
+ })
+ .labelled("declare")
+ .boxed();
+
+ choice((
+ tok![ident].map(Expr::Ident),
+ decl,
+ val,
+ expr.clone().delimited_by(tok![lparen], tok![rparen]),
+ ))
+ .boxed()
+ });
+
+ let block = expr
+ .clone()
+ .delimited_by(tok![lbrace], tok![rbrace])
+ .boxed();
+
+ let r#if = recursive(|if_| {
+ tok![if]
+ .ignore_then(expr.clone())
+ .then(block.clone())
+ .then(tok![else].ignore_then(block.clone().or(if_)).or_not())
+ .map(|((cond, a), b)| Expr::If {
+ cond: Box::new(cond),
+ when: Box::new(a),
+ or: Box::new(b.unwrap_or_else(|| Expr::Value(Value::Unit))),
+ })
+ });
+
+ let block_expr = block.or(r#if);
+
+ let block_chain = block_expr
+ .clone()
+ .foldl(block_expr.clone().repeated(), |a, b| {
+ Expr::Semicolon(Box::new(a), Box::new(b))
+ });
+
+ block_chain.labelled("block").or(inline_expr.clone()).foldl(
+ tok![;].ignore_then(expr.or_not()).repeated(),
+ |a, b| {
+ Expr::Semicolon(
+ Box::new(a),
+ Box::new(b.unwrap_or_else(|| Expr::Value(Value::Unit))),
+ )
+ },
+ )
+ })
+}
+
+impl<'s> Type<'s> {
+ pub fn parse() -> parser![Type<'s>] {
+ recursive(|ty| {
+ let tuple = ty
+ .separated_by(tok![,])
+ .allow_trailing()
+ .collect::<Vec<_>>()
+ .delimited_by(tok![lparen], tok![rparen])
+ .map(|x| Type::Tuple(x.into_boxed_slice()))
+ .boxed();
+ let unit = tok![()].map(|_| Type::Unit);
+ let path = tok![ident].map(Type::Path);
+
+ choice((path, tuple, unit))
+ })
+ .labelled("type")
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum FixMetaPiece<'s> {
+ A(Associativity),
+ L(&'s str),
+ T(&'s str),
+}
+
+impl<'s> Meta<'s> {
+ fn fns() -> parser![Vec<&'s str>] {
+ tok![fnname]
+ .separated_by(tok![,])
+ .collect::<Vec<_>>()
+ .delimited_by(tok![lbrace].or_not(), tok![rbrace].or_not())
+ .labelled("functions")
+ }
+
+ fn associativity() -> parser![Spanned<Associativity>] {
+ tok![associativity]
+ .ignore_then(select! {
+ Token::FnIdent("<") => Associativity::Left,
+ Token::Ident("none") => Associativity::None,
+ Token::FnIdent(">") => Associativity::Right
+ })
+ .map_with(spanned!())
+ }
+ fn looser() -> parser![Spanned<&'s str>] {
+ tok![looser_than]
+ .ignore_then(tok![fnname])
+ .map_with(spanned!())
+ .labelled("looser than")
+ }
+
+ fn tighter() -> parser![Spanned<&'s str>] {
+ tok![tighter_than]
+ .ignore_then(tok![fnname])
+ .map_with(spanned!())
+ .labelled("tighter than")
+ }
+
+ fn like() -> parser![&'s str] {
+ tok![like]
+ .ignore_then(tok![fnname])
+ .delimited_by(tok![lbrace], tok![rbrace])
+ .labelled("like")
+ }
+
+ fn alias() -> parser![Meta<'s>] {
+ tok![alias]
+ .ignore_then(Meta::fns())
+ .map(Meta::Alias)
+ .labelled("alias meta")
+ }
+
+ fn pieces() -> parser![Vec<Spanned<FixMetaPiece<'s>>>] {
+ use FixMetaPiece::*;
+ choice((
+ Meta::associativity().map(|x| x.ml(A)),
+ Meta::looser().map(|x| x.ml(L)),
+ Meta::tighter().map(|x| x.ml(T)),
+ ))
+ .separated_by(tok![,])
+ .collect::<Vec<_>>()
+ .labelled("meta pices")
+ }
+
+ fn prefix() -> parser![Meta<'s>] {
+ tok![prefix]
+ .ignore_then(choice((
+ Meta::like().map(|x| Meta::Fix(FixMeta::Like(Fix::Pre, x))),
+ Meta::pieces()
+ .try_map(|x, e| {
+ FixMetaData::from_pieces(&x, Fix::Pre, e)
+ .and_then(|x| match x.assoc {
+ Some(_) => {
+ Err(Rich::custom(e, "prefix does not have associativity"))
+ }
+ None => Ok(x),
+ })
+ .map(|x| Meta::Fix(FixMeta::Data(x)))
+ })
+ .delimited_by(tok![lbrace], tok![rbrace]),
+ empty().map(|()| Meta::Fix(FixMeta::Default(Fix::Pre))),
+ )))
+ .labelled("prefix meta")
+ }
+
+ fn postfix() -> parser![Meta<'s>] {
+ tok![postfix]
+ .ignore_then(choice((
+ Meta::like().map(|x| Meta::Fix(FixMeta::Like(Fix::Post, x))),
+ Meta::pieces()
+ .try_map(|x, e| {
+ FixMetaData::from_pieces(&x, Fix::Post, e)
+ .and_then(|x| match x.assoc {
+ Some(_) => {
+ Err(Rich::custom(e, "postfix does not have associativity"))
+ }
+ None => Ok(x),
+ })
+ .map(|x| Meta::Fix(FixMeta::Data(x)))
+ })
+ .delimited_by(tok![lbrace], tok![rbrace]),
+ empty().map(|()| Meta::Fix(FixMeta::Default(Fix::Post))),
+ )))
+ .labelled("postfix meta")
+ }
+
+ fn infix() -> parser![Meta<'s>] {
+ tok![infix]
+ .ignore_then(choice((
+ Meta::like().map(|x| Meta::Fix(FixMeta::Like(Fix::In, x))),
+ Meta::pieces()
+ .try_map(|x, e| {
+ FixMetaData::from_pieces(&x, Fix::In, e)
+ .and_then(|x| match x.assoc {
+ None => Err(Rich::custom(e, "infix requires associativity")),
+ Some(_) => Ok(x),
+ })
+ .map(|x| Meta::Fix(FixMeta::Data(x)))
+ })
+ .delimited_by(tok![lbrace], tok![rbrace]),
+ empty().map(|()| Meta::Fix(FixMeta::Default(Fix::In))),
+ )))
+ .labelled("infix meta")
+ }
+
+ fn parse() -> parser![Vec<Meta<'s>>] {
+ choice((
+ Meta::alias(),
+ Meta::infix(),
+ Meta::prefix(),
+ Meta::postfix(),
+ ))
+ .separated_by(tok![,])
+ .collect::<Vec<_>>()
+ .delimited_by(tok![lbrack], tok![rbrack])
+ .labelled("meta")
+ }
+}
+
+impl<'s> FnDef<'s> {
+ pub fn args() -> parser![(Vec<(Type<'s>, Type<'s>)>, Option<Type<'s>>)] {
+ Type::parse()
+ .then_ignore(tok![:])
+ .then(Type::parse())
+ .separated_by(tok![,])
+ .collect::<Vec<_>>()
+ // note: `(a: _, b: _) -> c` is technically invalid, and should be (a, b): (_, _) -> c, but
+ .delimited_by(tok![lparen].or_not(), tok![rparen].or_not())
+ .then(tok![->].ignore_then(Type::parse()).or_not())
+ .labelled("function signature")
+ }
+
+ fn block() -> parser![Expr<'s>] {
+ expr()
+ .clone()
+ .delimited_by(tok![lbrace], tok![rbrace])
+ .labelled("block")
+ }
+
+ fn parse() -> parser![Self] {
+ tok![fnname]
+ .then(Self::args().delimited_by(tok![lparen], tok![rparen]))
+ .then(Self::block().or_not())
+ .then(Meta::parse())
+ .map(|(((name, (args, ret)), block), meta)| Self {
+ name,
+ args,
+ ret: ret.unwrap_or(Type::Unit),
+ block,
+ meta,
+ })
+ .labelled("function")
+ }
+}
+
+fn parser<'s>() -> parser![Ast<'s>] {
+ FnDef::parse()
+ .map(Stmt::Fn)
+ .repeated()
+ .collect()
+ .map(Ast::Module)
+}
+
+pub fn stream(lexer: Lexer<'_>, len: usize) -> SpannedInput<Token<'_>, Span, Stream<Lexer<'_>>> {
+ Stream::from_iter(lexer).spanned((len..len).into())
+}
+
+#[cfg(test)]
+pub fn code<'s>(x: &'s str) -> SpannedInput<Token<'s>, Span, Stream<Lexer<'s>>> {
+ stream(crate::lexer::lex(x), x.len())
+}
+
+pub fn parse(tokens: Lexer<'_>, len: usize) -> Result<Ast<'_>, Vec<Error<'_>>> {
+ parser().parse(stream(tokens, len)).into_result()
+}
+
+#[test]
+fn fn_test() {
+ assert_eq!(Meta::fns().parse(code("{ !, not }")).unwrap(), ["!", "not"]);
+ assert_eq!(Meta::fns().parse(code("!")).unwrap(), ["!"]);
+}