summaryrefslogtreecommitdiffstats
path: root/crates/rebel-parse/src/grammar/recipe.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/rebel-parse/src/grammar/recipe.rs')
-rw-r--r--crates/rebel-parse/src/grammar/recipe.rs270
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 }
+ }
+}