smol lang
Diffstat (limited to 'src/parser.rs')
| -rw-r--r-- | src/parser.rs | 339 |
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(), ["!"]); +} |