use std::collections::HashSet; use crate::token; pub type Recipe<'a> = Vec>; #[derive(Debug, Clone, PartialEq, Eq)] pub enum RecipeStmt<'a> { BodyStmt(BodyStmt<'a>), Fetch { name: Ident<'a>, body: Body<'a>, }, Task { name: Ident<'a>, params: Vec>, body: Body<'a>, }, } impl<'a> RecipeStmt<'a> { pub fn validate(&self) -> Result<(), ValidationError> { match self { RecipeStmt::BodyStmt(stmt) => stmt.validate(), RecipeStmt::Fetch { name: _, body } => body.validate(), RecipeStmt::Task { name: _, params: _, body, } => { // TODO: Validate params? body.validate() } } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Body<'a>(pub Vec>); impl<'a> Body<'a> { pub fn validate(&self) -> Result<(), ValidationError> { for stmt in &self.0 { stmt.validate()?; } Ok(()) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum BodyStmt<'a> { Assign { dest: TypedExpr<'a>, expr: Expr<'a> }, Expr { expr: Expr<'a> }, Empty, } impl<'a> BodyStmt<'a> { pub(crate) fn assign(dest: TypedExpr<'a>, op: Option, expr: Expr<'a>) -> Self { match op { Some(op) => { let dest_expr = dest.expr.clone(); BodyStmt::Assign { dest, expr: Expr::binary(dest_expr, op, expr), } } None => BodyStmt::Assign { dest, expr }, } } pub fn validate(&self) -> Result<(), ValidationError> { match self { BodyStmt::Assign { dest, expr } => { // TODO: Extend destination validation dest.expr.validate()?; expr.validate() } BodyStmt::Expr { expr } => expr.validate(), BodyStmt::Empty => Ok(()), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Expr<'a> { Binary { left: Box>, op: OpBinary, right: Box>, }, Unary { op: OpUnary, expr: Box>, }, Apply { expr: Box>, params: Vec>, }, Method { expr: Box>, method: Ident<'a>, params: Vec>, }, Index { expr: Box>, index: Box>, }, Field { expr: Box>, field: Ident<'a>, }, Paren(Box>), Path(Path<'a>), Literal(Literal<'a>), } impl<'a> Expr<'a> { pub(crate) fn binary(left: Expr<'a>, op: OpBinary, right: Expr<'a>) -> Self { Expr::Binary { left: Box::new(left), op, right: Box::new(right), } } pub(crate) fn unary(op: OpUnary, expr: Expr<'a>) -> Self { Expr::Unary { op, expr: Box::new(expr), } } pub(crate) fn apply(expr: Expr<'a>, params: Vec>) -> Self { Expr::Apply { expr: Box::new(expr), params, } } pub(crate) fn method(expr: Expr<'a>, method: Ident<'a>, params: Vec>) -> Self { Expr::Method { expr: Box::new(expr), method, params, } } pub(crate) fn index(expr: Expr<'a>, index: Expr<'a>) -> Self { Expr::Index { expr: Box::new(expr), index: Box::new(index), } } pub(crate) fn field(expr: Expr<'a>, field: Ident<'a>) -> Self { Expr::Field { expr: Box::new(expr), field, } } pub(crate) fn paren(expr: Expr<'a>) -> Self { Expr::Paren(Box::new(expr)) } pub fn validate(&self) -> Result<(), ValidationError> { match self { Expr::Binary { left, op, right } => { left.validate()?; right.validate()?; if op.is_comparision() && (left.is_binary_comparison() || right.is_binary_comparison()) { return Err(ValidationError::NeedsParens); } Ok(()) } Expr::Unary { op: _, expr } => expr.validate(), Expr::Apply { expr, params } => { for param in params { param.validate()?; } expr.validate() } Expr::Method { expr, method: _, params, } => { for param in params { param.validate()?; } expr.validate() } Expr::Index { expr, index } => { index.validate()?; expr.validate() } Expr::Field { expr, field: _ } => expr.validate(), Expr::Paren(expr) => expr.validate(), Expr::Path(_) => Ok(()), Expr::Literal(lit) => lit.validate(), } } fn is_binary_comparison(&self) -> bool { let Expr::Binary { left: _, op, right: _, } = self else { return false; }; op.is_comparision() } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypedExpr<'a> { pub expr: Expr<'a>, pub typ: Option>, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct FuncParam<'a> { pub name: Ident<'a>, pub typ: Expr<'a>, } pub use token::StrKind; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Literal<'a> { Unit, Bool(bool), Int(u64), Str { pieces: Vec>, kind: StrKind, }, Tuple(Vec>), Array(Vec>), Map(Vec>), } impl<'a> Literal<'a> { pub(crate) fn number(s: &'a str) -> Result { 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 digits = rest.replace('_', ""); let value = u64::from_str_radix(&digits, radix).or(Err("number"))?; Ok(Literal::Int(value)) } fn validate(&self) -> Result<(), ValidationError> { match self { Literal::Unit => Ok(()), Literal::Bool(_) => Ok(()), Literal::Int(_) => Ok(()), Literal::Str { pieces, kind: _ } => { for piece in pieces { match piece { StrPiece::Chars(_) => {} StrPiece::Escape(_) => {} StrPiece::Interp(expr) => expr.validate()?, } } Ok(()) } Literal::Tuple(elems) => { for elem in elems { elem.validate()?; } Ok(()) } Literal::Array(elems) => { for elem in elems { elem.validate()?; } Ok(()) } Literal::Map(entries) => { let mut keys = HashSet::new(); for MapEntry { key, value } in entries { if !keys.insert(key) { return Err(ValidationError::DuplicateKey); } value.validate()?; } Ok(()) } } } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum StrPiece<'a> { Chars(&'a str), Escape(char), Interp(Expr<'a>), } impl<'a> TryFrom<&token::StrPiece<'a>> for StrPiece<'a> { type Error = &'static str; fn try_from(value: &token::StrPiece<'a>) -> Result { use crate::recipe; Ok(match value { token::StrPiece::Chars(chars) => StrPiece::Chars(chars), token::StrPiece::Escape(c) => StrPiece::Escape(*c), token::StrPiece::Interp(tokens) => StrPiece::Interp( recipe::expr(tokens).or(Err("Invalid expression in string interpolation"))?, ), }) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct MapEntry<'a> { pub key: &'a str, pub value: Expr<'a>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OpUnary { Not, Neg, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OpBinary { Add, Sub, Mul, Div, Rem, And, Or, Eq, Lt, Le, Ne, Ge, Gt, } impl OpBinary { fn is_comparision(self) -> bool { use OpBinary::*; matches!(self, Eq | Lt | Le | Ne | Ge | Gt) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Path<'a> { pub components: Vec>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Ident<'a> { pub name: &'a str, } #[derive(Debug, Clone, Copy)] pub enum ValidationError { DuplicateKey, NeedsParens, }