Expand description
A derive macro based library for flexible syntax tree formatting with pretty printing support.
syntaxfmt provides a trait and builder 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 dynamic formatting.
§Features
- Derive Macro - Automatic implementation via
#[derive(SyntaxFmt)] - Flexible Decorations - Add prefixes, suffixes, and collection delimiters
- Modal Formatting - Customise formatting output for different modes, normal and pretty
- Automatic Layout - Automated layout control with newlines and indentation
- Content Replacement - Override field formatting with literals or custom functions
- Conditional Formatting - Format based on arbitrary boolean expressions, with else support
- Stateful Formatting - Pass mutable or immutable state for context-aware output
§Cargo Features
derive- enablesSyntaxFmtderive macro (on by default)
§Quick Start
The simplest use case is to derive SyntaxFmt on your types and they’ll format
themselves by printing each field in order.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct BinaryOp<'src> {
left: &'src str,
op: &'src str,
right: &'src str,
}
let expr = BinaryOp { left: "x", op: "+", right: "y" };
assert_eq!(format!("{}", syntax_fmt(&expr)), "x+y");§Adding Decorations
Use pre (prefix) and suf (suffix) attributes to add syntax around fields.
The pre and suf attribute arguments can be applied at field, type, or
syntax_else level.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
#[syntax(pre = "let ", suf = ";")]
struct LetStatement<'src> {
name: &'src str,
#[syntax(pre = " = ")]
value: &'src str,
}
let stmt = LetStatement { name: "x", value: "42" };
assert_eq!(format!("{}", syntax_fmt(&stmt)), "let x = 42;");§Collections and Delimiters
Collections (Vec<T>, &[T], [T; N]) are formatted automatically with customizable
delimiters. The default delimiter is , for normal mode and , for pretty mode.
The delim attribute argument can be applied at field, type, or syntax_else level.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct Ident<'src>(&'src str);
#[derive(SyntaxFmt)]
struct Path<'src> {
#[syntax(delim = "::")]
segments: Vec<Ident<'src>>,
}
let path = Path {
segments: vec![Ident("std"), Ident("collections"), Ident("HashMap")],
};
assert_eq!(format!("{}", syntax_fmt(&path)), "std::collections::HashMap");§Skip Types and Fields
Use skip to exclude fields or entire types from output, useful for metadata or internal state.
The skip attribute argument can be applied at field or type level.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct Node<'src> {
value: &'src str,
#[syntax(skip)]
metadata: u32,
}
let node = Node { value: "data", metadata: 42 };
assert_eq!(format!("{}", syntax_fmt(&node)), "data");§Pretty Mode
Enable pretty printing with the .pretty() builder method. Use modal attributes
(arrays with two values) to specify different formatting for normal vs pretty mode.
Most attributes support modal values and can be applied at field, type, or
syntax_else level. Pretty mode also influences newlines and indentation, which
we’ll cover next.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct FunctionCall<'src> {
name: &'src str,
#[syntax(pre = ["(", "( "], suf = [")", " )"], delim = [", ", ", "])]
args: Vec<&'src str>,
}
let call = FunctionCall {
name: "max",
args: vec!["x", "y", "z"],
};
assert_eq!(format!("{}", syntax_fmt(&call)), "max(x, y, z)");
assert_eq!(format!("{}", syntax_fmt(&call).pretty()), "max( x, y, z )");§Indentation and Layout
Use ind (indent) to increase the indentation level for a field’s content. Use nl to
control newline positions:
beg- beginningpre- after prefixcont- after contentsuf- after suffix
Newlines default to "" (normal) and "\n"`` (pretty), and you can alter them with the .newline([“”, “\r\n”])` builder method.
Indentation segments default to "" (normal) and " " (pretty), and you can
alter them with the .indent(["", "\t"]) builder method.
The ind and nl attribute arguments can be applied at field, type, or
syntax_else level.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct Statement<'src> {
#[syntax(suf = ";")]
code: &'src str,
}
#[derive(SyntaxFmt)]
struct Block<'src> {
#[syntax(pre = "{", suf = "}", nl = [pre, cont], ind, delim = "")]
statements: Vec<Statement<'src>>,
}
let block = Block {
statements: vec![Statement { code: "return 42" }],
};
// Normal mode - no newlines or indentation
assert_eq!(format!("{}", syntax_fmt(&block)), "{return 42;}");
// Pretty mode - default newlines ("\n") and indentation (" ")
assert_eq!(format!("{}", syntax_fmt(&block).pretty()), "{\n return 42;\n}");
// Customize indentation to tabs
assert_eq!(
format!("{}", syntax_fmt(&block).pretty().indent(["", "\t"])),
"{\n\treturn 42;\n}"
);
// Customize newlines (e.g., Windows line endings)
assert_eq!(
format!("{}", syntax_fmt(&block).pretty().newline(["", "\r\n"])),
"{\r\n return 42;\r\n}"
);§Content Replacement
Replace a field’s value with literal text (or any value that implements
SyntaxFmt) using cont.
The cont attribute argument can be applied at field, type, or syntax_else level.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct Decl {
#[syntax(cont = "pub ", eval = *is_pub)]
is_pub: bool,
kw: &'static str,
}
let decl = Decl { is_pub: false, kw: "mod" };
assert_eq!(format!("{}", syntax_fmt(&decl)), "mod");
let decl = Decl { is_pub: true, kw: "fn" };
assert_eq!(format!("{}", syntax_fmt(&decl)), "pub fn");Use cont_with with a function for custom formatting logic.
The cont_with attribute argument can be applied at field, type, or
syntax_else level.
use syntaxfmt::{SyntaxFmt, SyntaxFormatter, syntax_fmt};
fn quote_formatter<S>(value: &str, f: &mut SyntaxFormatter<S>) -> std::fmt::Result {
write!(f, "\"{}\"", value)
}
#[derive(SyntaxFmt)]
struct StringLiteral<'src> {
#[syntax(cont_with = quote_formatter)]
value: &'src str,
}
let lit = StringLiteral { value: "hello" };
assert_eq!(format!("{}", syntax_fmt(&lit)), "\"hello\"");Or use cont_with with a closure inline.
use syntaxfmt::{SyntaxFmt, SyntaxFormatter, syntax_fmt};
#[derive(SyntaxFmt)]
struct Hex {
#[syntax(cont_with = |v: &u32, f: &mut SyntaxFormatter<_>| write!(f, "0x{:x}", v))]
value: u32,
}
let hex = Hex { value: 255 };
assert_eq!(format!("{}", syntax_fmt(&hex)), "0xff");§Conditional Formatting
For conditional logic with arbitrary expressions, use eval.
The eval attribute argument can be applied at field or type level.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
#[syntax(pre = "const ", suf = ";")]
struct ConstDecl<'src> {
name: &'src str,
#[syntax(pre = " = ", eval = *value > 100)]
value: u32,
}
let small = ConstDecl { name: "X", value: 50 };
assert_eq!(format!("{}", syntax_fmt(&small)), "const X;");
let large = ConstDecl { name: "Y", value: 200 };
assert_eq!(format!("{}", syntax_fmt(&large)), "const Y = 200;");Use eval_with with a function for reusable conditional logic.
The eval_with attribute argument can be applied at field or type level.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
fn is_long(s: &str) -> bool {
s.len() > 5
}
#[derive(SyntaxFmt)]
struct Comment<'src> {
#[syntax(pre = "// ", eval_with = is_long)]
text: &'src str,
}
let short = Comment { text: "ok" };
assert_eq!(format!("{}", syntax_fmt(&short)), "");
let long = Comment { text: "this is long" };
assert_eq!(format!("{}", syntax_fmt(&long)), "// this is long");Or use eval_with with a closure inline.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct Tagged<'src> {
#[syntax(pre = "#[", suf = "]", eval_with = |s: &str| s.starts_with("derive"))]
tag: &'src str,
}
let other = Tagged { tag: "allow(dead_code)" };
assert_eq!(format!("{}", syntax_fmt(&other)), "");
let derive = Tagged { tag: "derive(Debug)" };
assert_eq!(format!("{}", syntax_fmt(&derive)), "#[derive(Debug)]");Option<T> fields are only formatted when Some, and skip formatting when None.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
#[syntax(pre = "return ")]
struct Return<'src> {
value: Option<&'src str>,
}
let with_value = Return { value: Some("42") };
assert_eq!(format!("{}", syntax_fmt(&with_value)), "return 42");
let without = Return { value: None };
assert_eq!(format!("{}", syntax_fmt(&without)), "return ");When Option<T> fields have decorations, use eval or eval_with to
prevent decorations from appearing when None.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
#[syntax(pre = "let ", suf = ";")]
struct VarDecl<'src> {
name: &'src str,
#[syntax(pre = ": ", eval = ty.is_some())]
ty: Option<&'src str>,
#[syntax(pre = " = ", eval = value.is_some())]
value: Option<&'src str>,
}
let untyped = VarDecl { name: "x", ty: None, value: Some("42") };
assert_eq!(format!("{}", syntax_fmt(&untyped)), "let x = 42;");
let typed = VarDecl { name: "y", ty: Some("i32"), value: None };
assert_eq!(format!("{}", syntax_fmt(&typed)), "let y: i32;");§Fallback Formatting
An additional attribute #[syntax_else], which enables alternate
formatting when an eval or eval_with attribute argument is specified
and its result is false.
Fallback formatting has a restricted set of accepted attribute arguments:
pre, suf, delim, cont, ind, and nl.
use syntaxfmt::{SyntaxFmt, syntax_fmt};
#[derive(SyntaxFmt)]
struct MaybeValue {
#[syntax(eval = value.is_some())]
#[syntax_else(cont = "none")]
value: Option<i32>,
}
let some = MaybeValue { value: Some(42) };
assert_eq!(format!("{}", syntax_fmt(&some)), "42");
let none = MaybeValue { value: None };
assert_eq!(format!("{}", syntax_fmt(&none)), "none");§Stateful Formatting
Pass mutable or immutable state through formatting to enable context-aware output like
symbol resolution, ID generation, or tracking. Use the state or bound attribute with
the derive macro to specify the state type or trait bound. Both state and bound can
only be applied at type level.
use syntaxfmt::{SyntaxFmt, SyntaxFormatter, syntax_fmt};
trait SymbolResolver {
fn resolve(&self, name: &str) -> String;
}
struct MyResolver;
impl SymbolResolver for MyResolver {
fn resolve(&self, name: &str) -> String {
format!("resolved_{}", name)
}
}
fn resolve_name<S: SymbolResolver>(name: &str, f: &mut SyntaxFormatter<S>) -> std::fmt::Result {
let resolved = f.state().resolve(name);
write!(f, "{}", resolved)
}
#[derive(SyntaxFmt)]
#[syntax(bound = SymbolResolver)]
struct Identifier<'src> {
#[syntax(cont_with = resolve_name)]
name: &'src str,
}
let resolver = MyResolver;
let ident = Identifier { name: "foo" };
assert_eq!(format!("{}", syntax_fmt(&ident).state(&resolver)), "resolved_foo");§Additional State Examples
SyntaxFormatter::stateSyntaxFormatter::state_mutSyntaxFormatter::map_stateSyntaxFormatter::map_state_mut
§Putting it all Together
For a comprehensive example demonstrating nested structs, enums, indentation, newlines,
and stateful formatting with all attribute args (eval, cont, pre, suf, delim,
eval_with, cont_with, state, bound, etc.), see the
examples directory.
§Reference
§Attribute Summary
Attributes can be applied at the type, field, or syntax_else level.
| Argument | Description | Valid Location |
|---|---|---|
pre | Text before content | field/type/else |
suf | Text after content | field/type/else |
delim | Separator between collection elements | field/type/else |
cont | Literal replacement for field value | field/type/else |
cont_with | Custom formatter function/closure | field/type/else |
eval | Conditional expression | field/type |
eval_with | Conditional function/closure | field/type |
nl | Newline positions (beg, pre, cont, suf) | field/type/else |
ind | Increase indent level for field content | field/type/else |
skip | Omit field from formatting | field/type |
state | Specify state type (type-level only) | type |
bound | Add trait bound to state (type-level only) | type |
§Modal Attributes
Most attributes accept modal values as arrays: [normal_value, pretty_value]
Examples:
pre = ["(", "( "]- Different prefix for each modedelim = [",", ", "]- Different delimiter for each mode
§Built in Implementations
SyntaxFmt is implemented for:
- Primitives:
i8toi128,u8tou128,isize,usize,f32,f64,bool,char - Strings:
str,String - Collections:
Vec<T>,&[T],[T; N] - Options:
Option<T> - References:
&T,Box<T>,Rc<T>,Arc<T> - Tuples: Up to 8 elements
- Unit:
()
Please submit a ticket or submit a PR if a core implementation is missing.
§Builder Methods
Methods on SyntaxDisplay returned by syntax_fmt():
.pretty()- Enable pretty printing mode.indent(["normal", "pretty"])- Set indentation strings (default:["", " "]).newline(["normal", "pretty"])- Set newline strings (default:["", "\n"]).state(&state)- Pass immutable state.state_mut(&mut state)- Pass mutable state
Structs§
- Syntax
Display - A wrapper that implements
Displayfor types implementingSyntaxFmt. - Syntax
Formatter - Context passed to formatting implementations, containing the formatter and formatting state.
Enums§
- Mode
- Formatter mode
Constants§
Traits§
- Syntax
Fmt - Trait for types that can be formatted as syntax.
Functions§
- syntax_
fmt - Formats a syntax tree.