From 6d09f6c30d05a8da65fdfc7be8ccb11ee069eb2b Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sun, 28 Apr 2024 12:02:28 +0200 Subject: rebel-parse: ast: add post-parse validation --- crates/rebel-lang/examples/repl.rs | 14 +++++ crates/rebel-parse/src/ast.rs | 110 +++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/crates/rebel-lang/examples/repl.rs b/crates/rebel-lang/examples/repl.rs index e4c2550..185155b 100644 --- a/crates/rebel-lang/examples/repl.rs +++ b/crates/rebel-lang/examples/repl.rs @@ -96,6 +96,13 @@ fn main() -> rustyline::Result<()> { match &stmt { rebel_parse::ast::BodyStmt::Assign { dest: _, expr } => { + match expr.validate() { + Ok(_) => (), + Err(err) => { + println!("Validation error: {err:?}"); + continue; + } + }; match Type::ast_expr_type(&ctx, expr) { Ok(_) => (), Err(err) => { @@ -105,6 +112,13 @@ fn main() -> rustyline::Result<()> { }; } rebel_parse::ast::BodyStmt::Expr { expr } => { + match expr.validate() { + Ok(_) => (), + Err(err) => { + println!("Validation error: {err:?}"); + continue; + } + }; match Type::ast_expr_type(&ctx, expr) { Ok(_) => (), Err(err) => { diff --git a/crates/rebel-parse/src/ast.rs b/crates/rebel-parse/src/ast.rs index 90c02f5..1dcbdd9 100644 --- a/crates/rebel-parse/src/ast.rs +++ b/crates/rebel-parse/src/ast.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use crate::token; pub type Recipe<'a> = Vec>; @@ -121,6 +123,60 @@ impl<'a> Expr<'a> { 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)] @@ -166,6 +222,46 @@ impl<'a> Literal<'a> { 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)] @@ -220,6 +316,14 @@ pub enum OpBinary { 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>, @@ -229,3 +333,9 @@ pub struct Path<'a> { pub struct Ident<'a> { pub name: &'a str, } + +#[derive(Debug, Clone, Copy)] +pub enum ValidationError { + DuplicateKey, + NeedsParens, +} -- cgit v1.2.3