Expand description
A derive macro based library for flexible syntax tree formatting with pretty printing support.
syntaxfmt provides a trait based approach to formatting syntax trees with both compact
and pretty-printed output modes. It’s designed for compiler frontends, code generators,
and any application that needs to format structured data as text with optional formatting.
§Features
- Derive macro - for automatic implementation of formatting logic
- Dual formatting modes - compact and pretty-printed
- Collection support - automatic formatting for
Vec<T>,&[T], and[T; N]types - Boolean and Option support - conditional formatting for
boolandOption<T>types - Stateful formatting - pass user defined context through the formatting process
- Custom formatters - override default behavior with custom functions or by explicitly implementing
SyntaxFmt - Flexible attributes - control delimiters, indentation, and format strings
§Cargo Features
derive- enablesSyntaxFmtderive macro (on by default)
§Quick Start
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct FunctionCall<'src> {
name: &'src str,
#[syntax(format = "({content})", pretty_format = "( {content} )")]
args: &'src str,
}
let call = FunctionCall {
name: "println",
args: "\"Hello, world!\"",
};
// Compact formatting
assert_eq!(format!("{}", syntax_fmt(&call)), "println(\"Hello, world!\")");
// Pretty formatting with .pretty()
assert_eq!(format!("{}", syntax_fmt(&call).pretty()), "println( \"Hello, world!\" )");§Derive Macro Attributes
§Type-level attributes
#[syntax(delim = ", ")]- Delimiter between items of this type, used by Vec and slice implementations (default:",")#[syntax(pretty_delim = ",\n")]- Delimiter in pretty mode (default:", ")#[syntax(format = "prefix{content}suffix")]- For prefixes and suffixes around the whole type (default:"{content}")#[syntax(pretty_format = "prefix{content}suffix")]- For pretty prefixes and suffixes around the whole type (default:"{content}")#[syntax(state_bound = "MyTrait")]- Add trait bound for exposing functionality to custom formatter functions
§Field-level attributes
#[syntax(format = "prefix{content}suffix")]- For prefixes and suffixes around the field (default:"{content}")#[syntax(pretty_format = "prefix{content}suffix")]- For pretty prefixes and suffixes around the field (default:"{content}")#[syntax(content = my_formatter)]- Custom content formatter function#[syntax(empty_suffix = ";")]- Early out with this string when field is empty (for types which implementis_empty()function)#[syntax(indent)]- Write indentation before this field (pretty mode only)#[syntax(indent_region)]- Increase indent level for this field’s content#[syntax(skip)]- Skip this field during formatting
§Examples
§Basic struct formatting
Note that syntax attribute may be used at the type level or at the field level.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
#[syntax(format = "let {content};")]
struct LetStatement<'src> {
name: &'src str,
#[syntax(format = " = {content}")]
value: &'src str,
}
let stmt = LetStatement {
name: "x",
value: "42",
};
assert_eq!(format!("{}", syntax_fmt(&stmt)), "let x = 42;");§Optional and boolean fields
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
#[syntax(format = "{content};")]
struct ConstStatement<'src> {
#[syntax(format = "pub ")]
is_pub: bool,
#[syntax(format = "const {content}: i32")]
name: &'src str,
#[syntax(format = " = {content}")]
value: Option<i32>,
}
let stmt = ConstStatement {
is_pub: false,
name: "X",
value: None,
};
assert_eq!(format!("{}", syntax_fmt(&stmt)), "const X: i32;");
let pub_stmt = ConstStatement {
is_pub: true,
name: "X",
value: None,
};
assert_eq!(format!("{}", syntax_fmt(&pub_stmt)), "pub const X: i32;");
let value_stmt = ConstStatement {
is_pub: true,
name: "X",
value: Some(42),
};
assert_eq!(format!("{}", syntax_fmt(&value_stmt)), "pub const X: i32 = 42;");§Pretty printing with indentation
The indent_region attribute increases the indent level for a field’s content, and indent
writes indentation before formatting a field in pretty mode. The default indentation is four
spaces, which can be customized using the .indent() builder method.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct Statement<'src> {
#[syntax(format = "{content};", indent)]
code: &'src str,
}
#[derive(SyntaxFmt)]
struct Block<'src> {
#[syntax(
format = "{{{content}}}",
pretty_format = "{{\n{content}\n}}",
indent_region
)]
body: Statement<'src>,
}
let block = Block { body: Statement { code: "return 42" } };
assert_eq!(format!("{}", syntax_fmt(&block)), "{return 42;}");
assert_eq!(format!("{}", syntax_fmt(&block).pretty()), "{\n return 42;\n}");
assert_eq!(format!("{}", syntax_fmt(&block).pretty().indent("\t")), "{\n\treturn 42;\n}");§Using empty_suffix for empty collections
The empty_suffix attribute provides early-out formatting for empty collection fields.
When the field’s is_empty() function returns true, only the suffix is output instead
of the remainder of the struct fields. This is useful for syntax like mod name; vs
mod name { items }.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
#[syntax(delim = " ", pretty_delim = " ")]
struct Statement<'src>(&'src str);
#[derive(SyntaxFmt)]
#[syntax(format = "mod {content}")]
struct Module<'src> {
name: &'src str,
#[syntax(format = " {{{content}}}", empty_suffix = ";")]
items: Vec<Statement<'src>>,
}
let empty = Module {
name: "empty",
items: vec![],
};
assert_eq!(format!("{}", syntax_fmt(&empty)), "mod empty;");
let with_items = Module {
name: "lib",
items: vec![Statement("fn main()")],
};
assert_eq!(format!("{}", syntax_fmt(&with_items)), "mod lib {fn main()}");§Collection formatting
Collections (Vec<T>, &[T], [T; N]) are automatically formatted by iterating over their
elements and using the element type’s delimiter configuration.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
#[syntax(delim = "::", pretty_delim = " :: ")]
struct Segment<'src>(&'src str);
#[derive(SyntaxFmt)]
struct Path<'src> {
segments: Vec<Segment<'src>>,
}
let path = Path {
segments: vec![Segment("std"), Segment("collections"), Segment("HashMap")],
};
assert_eq!(
format!("{}", syntax_fmt(&path)),
"std::collections::HashMap"
);
assert_eq!(
format!("{}", syntax_fmt(&path).pretty()),
"std :: collections :: HashMap"
);§Custom formatters
The content attribute allows you to specify a custom formatting function for a field.
use syntaxfmt::{SyntaxFmt, SyntaxFormatter, syntax_fmt};
fn quote_formatter<S>(value: &str, ctx: &mut SyntaxFormatter<S>) -> std::fmt::Result {
write!(ctx, "\"{}\"", value)
}
#[derive(SyntaxFmt)]
struct StringLiteral<'src> {
#[syntax(content = quote_formatter)]
value: &'src str,
}
let lit = StringLiteral { value: "hello" };
assert_eq!(format!("{}", syntax_fmt(&lit)), "\"hello\"");§Stateful formatting with mutable state
You can manually implement SyntaxFmt or use custom formatter functions to access and
modify user-provided state during formatting.
use syntaxfmt::{SyntaxFmt, SyntaxFormatter, syntax_fmt};
// State that tracks variable assignments
struct VarTracker {
next_id: usize,
}
impl VarTracker {
fn allocate(&mut self) -> usize {
let id = self.next_id;
self.next_id += 1;
id
}
}
// A variable declaration that gets a unique ID
struct VarDecl<'src> {
name: &'src str,
}
impl<'src> SyntaxFmt<VarTracker> for VarDecl<'src> {
fn syntax_fmt(&self, ctx: &mut SyntaxFormatter<VarTracker>) -> std::fmt::Result {
let id = ctx.state_mut().allocate();
write!(ctx, "let {}_{} = ", self.name, id)
}
}
let mut tracker = VarTracker { next_id: 0 };
let decl_0 = VarDecl { name: "x" };
let decl_1 = VarDecl { name: "x" };
assert_eq!(format!("{}", syntax_fmt(&decl_0).state_mut(&mut tracker)), "let x_0 = ");
assert_eq!(format!("{}", syntax_fmt(&decl_1).state_mut(&mut tracker)), "let x_1 = ");
assert_eq!(tracker.next_id, 2);Structs§
- Syntax
Display - A wrapper that implements
Displayfor types implementingSyntaxFmt. - Syntax
Formatter - Context passed to formatting implementations, containing the formatter and formatting state.
Traits§
- Syntax
Fmt - Trait for types that can be formatted as syntax.
Functions§
- syntax_
fmt - Formats a syntax tree.