use ast2str::{utils::AstToStrExt, AstToStr}; use pretty_assertions::assert_eq; pub type Ptr<'a, T> = Box>; #[derive(Debug)] pub enum Op { Add, Mul, } pub trait Numeric: std::fmt::Debug + Clone + Copy + PartialEq + std::ops::Add + std::ops::Mul { } impl Numeric for f64 {} #[derive(Debug, AstToStr)] pub enum LitValue<'a, T: Numeric = f64> { Num(#[debug] T), Bool(#[display] bool), Str(#[quoted] &'a str), Null, } #[derive(AstToStr)] #[allow(unused)] pub struct Literal<'a, T: Numeric> { #[debug] value: LitValue<'a, T>, #[skip] lexeme: &'a str, } impl<'a, T: Numeric> Literal<'a, T> { pub fn new(value: LitValue<'a, T>) -> Self { Self { value, lexeme: "default lexeme", } } } pub struct Letters<'a> { letters: &'a str, } impl<'a> IntoIterator for &'_ Letters<'a> { type Item = &'a u8; type IntoIter = std::slice::Iter<'a, u8>; fn into_iter(self) -> Self::IntoIter { self.letters.as_bytes().iter() } } #[derive(AstToStr)] pub struct Token<'a> { #[delegate = "lexeme"] #[rename = "lexeme"] source: &'a str, #[skip] span: std::ops::Range, } impl<'a> Token<'a> { pub fn lexeme(&self) -> &'a str { &self.source[self.span.clone()] } } #[derive(AstToStr)] pub enum ExprKind<'a, T: Numeric> { Literal(#[forward] Literal<'a, T>), SpecialValue(#[forward] LitValue<'a, T>), Binary( #[rename = "left"] Ptr<'a, T>, #[debug] #[rename = "operator"] Op, #[rename = "right"] Ptr<'a, T>, ), Maybe { #[default = "Nothing"] value: Option>, }, SomeIterable(#[list] Letters<'a>), Mappable(#[list(|byte| byte.wrapping_mul(*byte))] Letters<'a>), Summable { #[callback(|x: &Vec<_>| x.iter().sum::())] #[rename = "sum"] values: Vec, }, List(#[rename = "elements"] Vec>), Variable(#[rename = "name"] Token<'a>), Conditional { #[skip] skip_me_always: (), #[skip_if = "Option::is_none"] condition: Option>, #[rename = "values"] #[skip_if = "Vec::is_empty"] my_values: Vec>, }, } #[derive(AstToStr)] pub struct Expr<'a, T> where T: Numeric, { #[forward] pub kind: ExprKind<'a, T>, pub some_other_field: (), } impl<'a, T: Numeric> Expr<'a, T> { pub fn new(kind: ExprKind<'a, T>) -> Self { Self { kind, some_other_field: (), } } } fn get_ast() -> Expr<'static, f64> { macro_rules! expr { (unboxed $name:ident { $($arg:ident : $value:expr),* }) => { Expr::new(ExprKind::$name { $($arg : $value),* }) }; (unboxed $name:ident $($arg:expr),*) => { Expr::new(ExprKind::$name($($arg),*)) }; ($name:ident $($arg:expr),*) => { Ptr::new(expr!(unboxed $name $($arg),*)) }; } macro_rules! lit { ($name:ident $value:expr) => { Literal::new(LitValue::$name($value)) }; ($name:ident) => { Literal::new(LitValue::$name) }; } expr!(unboxed List vec![ expr!(unboxed Binary expr!(Literal lit!(Num 1.0)), Op::Add, expr!(Binary expr!(Literal lit!(Bool true)), Op::Mul, expr!(Literal lit!(Str "a string")) ) ), expr!(unboxed Maybe { value: None }), expr!(unboxed Maybe { value: Some(expr!(Literal lit!(Null))) }), expr!(unboxed SomeIterable Letters { letters: "abc" }), expr!(unboxed Mappable Letters { letters: "def" }), expr!(unboxed Summable { values: vec![1, 2, 3, 4] }), expr!(unboxed SpecialValue LitValue::Num(2.0)), expr!(unboxed SpecialValue LitValue::Bool(false)), expr!(unboxed SpecialValue LitValue::Str("another string")), expr!(unboxed Variable Token { source: "a variable ", span: 2..10 }), expr!(unboxed Conditional { skip_me_always: (), condition: None, my_values: vec![] }), expr!(unboxed Conditional { skip_me_always: (), condition: None, my_values: vec![expr!(unboxed Literal lit!(Bool false))] }), expr!(unboxed Conditional { skip_me_always: (), condition: Some(expr!(Literal lit!(Bool true))), my_values: vec![] }), expr!(unboxed Conditional { skip_me_always: (), condition: Some(expr!(Literal lit!(Bool true))), my_values: vec![expr!(unboxed Literal lit!(Bool false))] }), ]) } #[test] fn test_ast_to_str() { let ast = get_ast(); assert_eq!( ast.ast_to_str().trim().with_display_as_debug_wrapper(), r#" ExprKind::List ╰─elements=↓ ├─ExprKind::Binary │ ├─left: Literal │ │ ╰─value: Num(1.0) │ ├─operator: Add │ ╰─right: ExprKind::Binary │ ├─left: Literal │ │ ╰─value: Bool(true) │ ├─operator: Mul │ ╰─right: Literal │ ╰─value: Str("a string") ├─ExprKind::Maybe │ ╰─value: Nothing ├─ExprKind::Maybe │ ╰─value: Literal │ ╰─value: Null ├─ExprKind::SomeIterable │ ╰─field0=↓ │ ├─97 │ ├─98 │ ╰─99 ├─ExprKind::Mappable │ ╰─field0=↓ │ ├─16 │ ├─217 │ ╰─164 ├─ExprKind::Summable │ ╰─sum: 10 ├─LitValue::Num │ ╰─field0: 2.0 ├─LitValue::Bool │ ╰─field0: false ├─LitValue::Str │ ╰─field0: `another string` ├─ExprKind::Variable │ ╰─name: Token │ ╰─lexeme: "variable" ├─ExprKind::Conditional ├─ExprKind::Conditional │ ╰─values=↓ │ ╰─Literal │ ╰─value: Bool(false) ├─ExprKind::Conditional │ ╰─condition: Literal │ ╰─value: Bool(true) ╰─ExprKind::Conditional ├─condition: Literal │ ╰─value: Bool(true) ╰─values=↓ ╰─Literal ╰─value: Bool(false)"# .trim() .with_display_as_debug_wrapper() ); } #[test] fn test_ast_to_str_with_custom_symbols() { let ast = get_ast(); assert_eq!( ast.ast_to_str_impl(&ast2str::TestSymbols) .trim() .with_display_as_debug_wrapper(), r#" ExprKind::List elements= ExprKind::Binary left: Literal value: Num(1.0) operator: Add right: ExprKind::Binary left: Literal value: Bool(true) operator: Mul right: Literal value: Str("a string") ExprKind::Maybe value: Nothing ExprKind::Maybe value: Literal value: Null ExprKind::SomeIterable field0= 97 98 99 ExprKind::Mappable field0= 16 217 164 ExprKind::Summable sum: 10 LitValue::Num field0: 2.0 LitValue::Bool field0: false LitValue::Str field0: `another string` ExprKind::Variable name: Token lexeme: "variable" ExprKind::Conditional ExprKind::Conditional values= Literal value: Bool(false) ExprKind::Conditional condition: Literal value: Bool(true) ExprKind::Conditional condition: Literal value: Bool(true) values= Literal value: Bool(false) "# .trim() .with_display_as_debug_wrapper() ); }