use crate::ast::{self, Expr}; use crate::token::*; pub use rules::*; peg::parser! { pub grammar rules<'a>() for TokenStream<'a> { use ast::OpBinary::*; use ast::OpUnary::*; pub rule recipe() -> ast::Recipe<'a> = recipe:recipe_stmt()* { recipe } pub rule recipe_stmt() -> ast::RecipeStmt<'a> = keyword_fetch() name:ident() p('{') body:body() p('}') { ast::RecipeStmt::Fetch { name, body: Vec::new() } } / keyword_task() name:ident() p('(') params:func_params() p(')') p('{') body:body() p('}') { ast::RecipeStmt::Task { name, params, body } } / stmt:body_stmt() { ast::RecipeStmt::BodyStmt(stmt) } pub rule body() -> ast::Body<'a> = body:body_stmt()* { body } pub rule body_stmt() -> ast::BodyStmt<'a> = left:typed_expr() op:assign_op() right:expr() p(';') { ast::BodyStmt::assign(left, op, right) } rule assign_op() -> Option = p2('+', '=') { Some(Add) } / p2('-', '=') { Some(Sub) } / p2('*', '=') { Some(Mul) } / p2('/', '=') { Some(Div) } / p2('%', '=') { Some(Rem) } / p('=') { None } rule typed_expr() -> ast::TypedExpr<'a> = expr:expr() typ:tagged(, )? { ast::TypedExpr { expr, 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) } expr:@ p('[') index:expr() p(']') { Expr::index(expr, index) } -- expr:@ p('.') method:field() p('(') params:call_params() p(')') { Expr::method(expr, method, params) } expr:@ p('.') field:field() { Expr::field(expr, field) } -- p('(') e:expr() p(')') { Expr::paren(e) } e:atom() { e } } rule atom() -> Expr<'a> = lit:literal() { Expr::Literal(lit) } / path:path() { Expr::Path(path) } rule call_params() -> Vec> = args:delimited(, ) { args } rule func_params() -> Vec> = params:delimited(, ) { params } rule func_param() -> ast::FuncParam<'a> = name:ident() p(':') typ:expr() { ast::FuncParam { name, typ } } rule literal() -> ast::Literal<'a> = keyword_true() { ast::Literal::Boolean(true) } / keyword_false() { ast::Literal::Boolean(false) } / [Token::Number(content)] { ? ast::Literal::number(content) } / [Token::String(String { pieces, .. })] { ? let ast_pieces = pieces.iter().map(|piece| piece.try_into()).collect::>()?; Ok(ast::Literal::String(ast_pieces)) } / p('(') p(')') { ast::Literal::Unit } / p('(') elements:(expr() ** p(',')) p(',')? p(')') { ast::Literal::Tuple(elements) } / p('[') elements:delimited(, ) p(']') { ast::Literal::Array(elements) } / p('{') entries:delimited(, ) p('}') { ast::Literal::Map(entries) } rule map_entry() -> ast::MapEntry<'a> = key:field() p('=') value:expr() { ast::MapEntry { key: key.name, value } } rule path() -> ast::Path<'a> = components:ident() ++ p2(':', ':') { ast::Path { components } } rule field() -> ast::Ident<'a> = ident() / [Token::Number(content)] { ast::Ident { name: content } } 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> = !keyword() [Token::Ident(name)] { ast::Ident { name } } rule keyword() = keyword_true() / keyword_false() / keyword_fetch() / keyword_task() rule keyword_true() = const_ident("true") rule keyword_false() = const_ident("false") rule keyword_fetch() = const_ident("fetch") rule keyword_task() = const_ident("task") rule const_ident(keyword: &str) = [Token::Ident(name) if *name == keyword] rule delimited(expr: rule, delim: rule<()>) -> Vec = values:(expr() ++ delim()) delim()? { values } / { Vec::new() } rule tagged(tag: rule<()>, value: rule) -> T = tag() v:value() { v } } }