#ast #recursion #pretty-print #proc-macro #tree #string

ast2str

A crate for pretty-printing ASTs and other recursive data structures

6 stable releases

1.4.1 Aug 19, 2022
1.4.0 Aug 2, 2022
1.3.0 Jul 31, 2022
1.2.1 Sep 6, 2021
1.1.1 Sep 4, 2021

#866 in Data structures

MIT license

49KB
675 lines

ast2str

crates.io Documentation Build Status

A proc macro for pretty-printing ASTs and other recursive data structures.

Basic Usage

Refer to the snippet below for a basic usage example, and see the integration test and these two repos (#1.1 + #1.2, #2.1) for larger samples.

// Import the macro
use ast2str::AstToStr;

type Span = std::ops::Range<usize>;

// Annotate some structs and enums as desired
#[derive(AstToStr)]
struct Label {
   #[quoted]
   name: &'static str,
   #[default = "Unresolved"]
   location: Option<usize>,
}

#[derive(AstToStr)]
enum Expr {
    Binary {
        left: Box<Expr>,
        #[quoted]
        operator: &'static str,
        right: Box<Expr>
    },
    Literal(#[rename = "value"] i32, #[skip] Span),
    List { items: Vec<Expr> },
    Label(#[forward] Label),
    Optional {
        #[skip_if = "Option::is_none"]
        value: Option<&'static str>
    }
}

let expr = Expr::Binary {
    left: Box::new(Expr::Literal(5, Span::default())),
    operator: "+",
    right: Box::new(Expr::List { items: vec![
       Expr::Label(Label { name: "x", location: Some(0) }),
       Expr::Label(Label { name: "y", location: Some(1) }),
       Expr::Label(Label { name: "z", location: None }),
       Expr::Optional { value: None },
       Expr::Optional { value: Some("a string") },
    ]})
};
assert_eq!(expr.ast_to_str(), r#"
Expr::Binary
├─left: Expr::Literal
│ ╰─value: 5
├─operator: `+`
╰─right: Expr::List
  ╰─items=↓
    ├─Label
    │ ├─name: `x`
    │ ╰─location: 0
    ├─Label
    │ ├─name: `y`
    │ ╰─location: 1
    ├─Label
    │ ├─name: `z`
    │ ╰─location: Unresolved
    ├─Expr::Optional
    ╰─Expr::Optional
      ╰─value: "a string"
"#.trim());

// The symbols used to draw the tree can be configured using the [`Symbols`] trait:
assert_eq!(expr.ast_to_str_impl(&ast2str::TestSymbols), r#"
Expr::Binary
  left: Expr::Literal
    value: 5
  operator: `+`
  right: Expr::List
    items=
      Label
        name: `x`
        location: 0
      Label
        name: `y`
        location: 1
      Label
        name: `z`
        location: Unresolved
      Expr::Optional
      Expr::Optional
        value: "a string"
"#.trim());

Available Attributes

Attribute
None Format the value with AstToStr
#[forward] Skip all other fields and return the AstToStr of the annotated field
#[skip] Skip the annotated field
#[display] Format the annotated field with Display instead of AstToStr
#[debug] Format the annotated field with Debug instead of AstToStr
#[quoted] Like #[display] but also wraps the value with backticks
#[list] Format the annotated field by executing AstToStr on every element of (&field).into_iter()
#[list(name_or_closure) Format the annotated field by applying the callback on every element of (&field).into_iter()
#[callback(name_or_closure)] Apply the given function or closure to &field and return the result
#[delegate = "getter"] Call self.getter() and format the result as a field
#[default = "value"] Only applies to Option types. If the value is Some(T), format &T with AstToStr. Otherwise, return the value of default
#[skip_if = "my_condition_fn"] Skip the annotated field if the specified function returns true

Dependencies

~1.3–1.8MB
~41K SLoC