diff options
Diffstat (limited to 'crates/rebel-parse/src/grammar/recipe.rs')
-rw-r--r-- | crates/rebel-parse/src/grammar/recipe.rs | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/crates/rebel-parse/src/grammar/recipe.rs b/crates/rebel-parse/src/grammar/recipe.rs new file mode 100644 index 0000000..f0605e4 --- /dev/null +++ b/crates/rebel-parse/src/grammar/recipe.rs @@ -0,0 +1,270 @@ +use std::borrow::Cow; + +use crate::{ + ast::{ + self, + expr::{self, Expr}, + pat::{DestrPat, Pat}, + typ::{self, Type}, + }, + token::*, +}; + +pub use rules::*; + +peg::parser! { + pub grammar rules<'a>() for TokenStream<'a> { + use expr::OpBinary::*; + use expr::OpUnary::*; + + pub rule recipe() -> ast::Recipe<'a> + = recipe:recipe_stmt()* { recipe } + + pub rule recipe_stmt() -> ast::RecipeStmt<'a> + = [Token::Keyword(Keyword::Fetch)] name:ident() p('{') entries:delimited(<struct_field()>, <p(',')>) p('}') { + ast::RecipeStmt::Fetch { name, entries } + } + / [Token::Keyword(Keyword::Task)] name:ident() p('(') params:func_params() p(')') + p('{') block:block() p('}') { + ast::RecipeStmt::Task { name, params, block } + } + / stmt:block_stmt() p(';') { + ast::RecipeStmt::BlockStmt(stmt) + } + + pub rule block() -> ast::Block<'a> + = block:block_stmt() ++ p(';') { ast::Block(block) } + + pub rule block_stmt() -> ast::BlockStmt<'a> + = [Token::Keyword(Keyword::Let)] dest:typed_pat() p('=') expr:expr() { + ast::BlockStmt::let_assign(dest, Some(expr)) + } + / [Token::Keyword(Keyword::Let)] dest:typed_pat() { + ast::BlockStmt::let_assign(dest, None) + } + / [Token::Keyword(Keyword::Fn)] ident:ident() p('(') params:func_params() p(')') + ret:tagged(<p2('-', '>')>, <typ()>)? p('{') block:block() p('}') + { + ast::BlockStmt::Fn { + ident, + params, + ret: ret.map(Box::new), + block, + } + } + / dest:destr_pat() op:assign_op() expr:expr() { + ast::BlockStmt::assign(dest, op, false, expr) + } + / dest:destr_pat() p2('=', '+') expr:expr() { + ast::BlockStmt::assign(dest, Some(Add), true, expr) + } + / expr:expr() { + ast::BlockStmt::Expr { expr: Box::new(expr) } + } + / { ast::BlockStmt::Empty } + + rule assign_op() -> Option<expr::OpBinary> + = p('=') { None } + / p2('+', '=') { Some(Add) } + / p2('-', '=') { Some(Sub) } + / p2('*', '=') { Some(Mul) } + / p2('/', '=') { Some(Div) } + / p2('%', '=') { Some(Rem) } + + rule typed_pat() -> ast::TypedPat<'a> + = pat:pat() typ:tagged(<p(':')>, <typ()>)? { ast::TypedPat { pat, typ } } + + pub rule typ() -> Type<'a> + = p('(') t:typ() p(')') { Type::Paren(Box::new(t)) } + / lit:typ_literal() { Type::Literal(lit) } + / path:path() { Type::Path(path) } + + rule typ_literal() -> typ::Literal<'a> + = p('(') p(')') { typ::Literal::Unit } + / p('(') elements:(typ() ++ p(',')) p(',')? p(')') { + typ::Literal::Tuple(elements) + } + / p('[') typ:typ() p(']') { + typ::Literal::Array(Box::new(typ)) + } + / [Token::Keyword(Keyword::Map)] p('{') key:typ() p2('=', '>') value:typ() p('}') { + typ::Literal::Map(Box::new(key), Box::new(value)) + } + / p('{') entries:delimited(<struct_field_typ()>, <p(',')>) p('}') { + typ::Literal::Struct(entries) + } + + pub rule pat() -> ast::pat::Pat<'a> + = p('(') pat:pat() p(')') { Pat::Paren(Box::new(pat)) } + / ident:ident() { Pat::Ident(ident) } + + pub rule destr_pat() -> DestrPat<'a> = precedence! { + base:@ p('[') index:expr() p(']') { + DestrPat::Index { base: Box::new(base), index: Box::new(index) } + } + -- + base:@ p('.') field:field() { + DestrPat::Field { base: Box::new(base), field } + } + -- + p('(') pat:destr_pat() p(')') { DestrPat::Paren(Box::new(pat)) } + path:path() { DestrPat::Path(path) } + } + + rule struct_field_typ() -> typ::StructField<'a> + = field:field() p(':') typ:typ() { + typ::StructField { name: field.name, typ } + } + + pub rule expr() -> Expr<'a> = precedence! { + left:(@) p2('|', '|') right:@ { Expr::binary(left, Or, right) } + -- + left:(@) p2('&', '&') right:@ { Expr::binary(left, And, right) } + -- + left:(@) p2('=', '=') right:@ { Expr::binary(left, Eq, right) } + left:(@) p2('!', '=') right:@ { Expr::binary(left, Ne, right) } + left:(@) p('<') right:@ { Expr::binary(left, Lt, right) } + left:(@) p('>') right:@ { Expr::binary(left, Gt, right) } + left:(@) p2('<', '=') right:@ { Expr::binary(left, Le, right) } + left:(@) p2('>', '=') right:@ { Expr::binary(left, Ge, right) } + -- + left:(@) p('+') right:@ { Expr::binary(left, Add, right) } + left:(@) p('-') right:@ { Expr::binary(left, Sub, right) } + -- + left:(@) p('*') right:@ { Expr::binary(left, Mul, right) } + left:(@) p('/') right:@ { Expr::binary(left, Div, right) } + left:(@) p('%') right:@ { Expr::binary(left, Rem, right) } + -- + p('-') expr:@ { Expr::unary(Neg, expr) } + p('!') expr:@ { Expr::unary(Not, expr) } + -- + expr:@ p('(') params:call_params() p(')') { + Expr::apply(expr, params) + } + base:@ p('[') index:expr() p(']') { Expr::index(base, index) } + -- + expr:@ p('.') method:field() p('(') params:call_params() p(')') { + Expr::method(expr, method, params) + } + base:@ p('.') field:field() { Expr::field(base, field) } + -- + e:atom() { e } + } + + rule atom() -> Expr<'a> + = p('(') e:expr() p(')') { Expr::paren(e) } + / [Token::Keyword(Keyword::If)] + if_blocks:(cond_block() ++ ([Token::Keyword(Keyword::Else)] [Token::Keyword(Keyword::If)])) + else_block:([Token::Keyword(Keyword::Else)] p('{') block:block() p('}') { Box::new(block) })? + { + Expr::IfElse { if_blocks, else_block } + } + / lit:literal() { Expr::Literal(lit) } + / p('{') block:block() p('}') { Expr::Block(block) } + / path:path() { Expr::Path(path) } + + rule cond_block() -> (Expr<'a>, ast::Block<'a>) + = cond:expr() p('{') block:block() p('}') { (cond, block) } + + rule call_params() -> Vec<expr::Expr<'a>> + = args:delimited(<expr()>, <p(',')>) { args } + + rule func_params() -> Vec<ast::FuncParam<'a>> + = params:delimited(<func_param()>, <p(',')>) { params } + + rule func_param() -> ast::FuncParam<'a> + = name:ident() p(':') typ:typ() { ast::FuncParam { name, typ } } + + rule literal() -> expr::Literal<'a> + = [Token::Keyword(Keyword::True)] { expr::Literal::Bool(true) } + / [Token::Keyword(Keyword::False)] { expr::Literal::Bool(false) } + / n:number() { expr::Literal::Int(n) } + / [Token::Str(Str { pieces, kind })] { ? + let pieces = pieces + .iter() + .map(|piece| piece.try_into()) + .collect::<Result<_, _>>()?; + Ok(expr::Literal::Str{ pieces, kind: *kind }) + } + / p('(') p(')') { expr::Literal::Unit } + / p('(') elements:(expr() ++ p(',')) p(',')? p(')') { + expr::Literal::Tuple(elements) + } + / p('[') elements:delimited(<expr()>, <p(',')>) p(']') { + expr::Literal::Array(elements) + } + / [Token::Keyword(Keyword::Map)] p('{') entries:delimited(<map_entry()>, <p(',')>) p('}') { + expr::Literal::Map(entries) + } + / p('{') entries:delimited(<struct_field()>, <p(',')>) p('}') { + expr::Literal::Struct(entries) + } + + rule map_entry() -> expr::MapEntry<'a> + = key:expr() p2('=', '>') value:expr() { + expr::MapEntry { key, value } + } + + rule struct_field() -> expr::StructField<'a> + = field:field() value:tagged(<p(':')>, <expr()>)? { + expr::StructField::new(field, value) + } + + rule path() -> ast::Path<'a> + = components:(ident() ++ p2(':', ':')) { + ast::Path { root: ast::PathRoot::Relative, components } + } + / components:(p2(':', ':') ident:ident() { ident })+ { + ast::Path { root: ast::PathRoot::Absolute, components } + } + / [Token::Keyword(Keyword::Recipe)] components:(p2(':', ':') ident:ident() { ident })* { + ast::Path { root: ast::PathRoot::Recipe, components } + } + / [Token::Keyword(Keyword::Task)] components:(p2(':', ':') ident:ident() { ident })* { + ast::Path { root: ast::PathRoot::Task, components } + } + + rule field() -> ast::Ident<'a> + = ident() + / [Token::Number(content)] { + ast::Ident { name: Cow::Borrowed(content) } + } + + rule number() -> i64 + = neg:p('-')? [Token::Number(s)] { ? + let (radix, rest) = if let Some(rest) = s.strip_prefix("0x") { + (16, rest) + } else if let Some(rest) = s.strip_prefix("0o") { + (8, rest) + } else if let Some(rest) = s.strip_prefix("0b") { + (2, rest) + } else { + (10, *s) + }; + let mut digits = rest.replace('_', ""); + if neg.is_some() { + digits = format!("-{digits}"); + } + i64::from_str_radix(&digits, radix).or(Err("number")) + } + + rule p_(ch: char) + = [Token::Punct(Punct(c, Spacing::Joint)) if *c == ch] {} + + rule p(ch: char) -> () + = [Token::Punct(Punct(c, _)) if *c == ch] {} + + rule p2(ch1: char, ch2: char) -> () + = p_(ch1) p(ch2) + + rule ident() -> ast::Ident<'a> + = [Token::Ident(name)] { ast::Ident { name: Cow::Borrowed(name) } } + + rule delimited<T>(expr: rule<T>, delim: rule<()>) -> Vec<T> + = values:(expr() ++ delim()) delim()? { values } + / { Vec::new() } + + rule tagged<T>(tag: rule<()>, value: rule<T>) -> T + = tag() v:value() { v } + } +} |