Crates.io | typeset-parser |
lib.rs | typeset-parser |
version | 3.1.0 |
created_at | 2023-03-25 15:02:09.366691+00 |
updated_at | 2025-08-17 22:37:47.153472+00 |
description | Compile time macro parser for typeset |
homepage | https://docs.rs/typeset-parser/latest/typeset-parser/ |
repository | https://github.com/soren-n/typeset-rs/tree/main/typeset-parser |
max_upload_size | |
id | 820337 |
size | 54,867 |
Compile-time macro parser for the typeset pretty printing library.
This crate provides the layout!
procedural macro that allows you to write typeset layouts using a concise DSL syntax instead of manually constructing layout trees with function calls.
Add both typeset
and typeset-parser
to your Cargo.toml
:
[dependencies]
typeset = "2.0.5"
typeset-parser = "2.0.5"
Then use the macro in your code:
use typeset::*;
use typeset_parser::layout;
let my_layout = layout! {
"Hello" + "World" @
nest("Indented" + "content")
};
let doc = compile(my_layout);
println!("{}", render(doc, 2, 40));
layout! { "Hello, World!" }
// Equivalent to: text("Hello, World!".to_string())
You can reference Rust variables containing Box<Layout>
values:
let name = text("Alice".to_string());
let greeting = layout! { "Hello" + name };
layout! { null }
// Equivalent to: null()
Operator | Name | Equivalent Function Call | Description |
---|---|---|---|
& |
Unpadded composition | comp(left, right, false, false) |
Join without spaces |
+ |
Padded composition | comp(left, right, true, false) |
Join with spaces |
!& |
Fixed unpadded | comp(left, right, false, true) |
Unpadded with infix fix |
!+ |
Fixed padded | comp(left, right, true, true) |
Padded with infix fix |
@ |
Line break | line(left, right) |
Force line break |
@@ |
Double line break | line(left, line(null(), right)) |
Force blank line |
Constructor | Syntax | Equivalent Function Call | Description |
---|---|---|---|
fix |
fix(layout) |
fix(layout) |
Prevent breaking |
grp |
grp(layout) |
grp(layout) |
Group breaking |
seq |
seq(layout) |
seq(layout) |
Sequential breaking |
nest |
nest(layout) |
nest(layout) |
Fixed indentation |
pack |
pack(layout) |
pack(layout) |
Aligned indentation |
use typeset_parser::layout;
// Simple composition
let basic = layout! { "Name:" + "Alice" };
// With line breaks
let multiline = layout! {
"First line" @
"Second line" @@
"After blank line"
};
// Function signature formatting
let params = vec![
text("param1".to_string()),
text("param2".to_string()),
text("param3".to_string()),
];
let function = layout! {
"fn" + "my_function" & "(" &
pack(seq(params[0].clone() & "," + params[1].clone() & "," + params[2].clone())) &
")" + "{" @
nest("// function body") @
"}"
};
let document = layout! {
fix("# ") & "Title" @@
fix("## ") & "Section" @
"This is a paragraph with" + "multiple words" +
"that will break intelligently." @@
fix("```") @
nest("code example") @
fix("```")
};
let json_object = layout! {
"{" @
nest(
fix("\"name\":") + "\"Alice\"" & "," @
fix("\"age\":") + "30" & "," @
fix("\"city\":") + "\"New York\""
) @
"}"
};
The DSL respects standard operator precedence:
()
- highest precedencefix
, grp
, seq
, nest
, pack
@
, @@
&
, !&
, +
, !+
- lowest precedenceUse parentheses to override default precedence:
layout! {
"prefix" + (fix("fixed" & "together")) + "suffix"
// vs
"prefix" + fix("fixed" & "together") + "suffix"
}
The !&
and !+
operators create infix-fixed compositions, which are syntactic sugar for fixing the boundary between two layouts:
// These are equivalent:
layout! { left !+ right }
comp(left, right, true, true)
// Useful for separators that must stay attached:
layout! { "item1" !+ "," + "item2" !+ "," + "item3" }
The macro provides helpful compile-time error messages:
// This will produce a clear error:
layout! { unknown_operator x y }
// ^^^^^^^^^^^^^^^^
// Error: Expected a unary operator
You can freely mix DSL syntax with manual constructor calls:
let manual_part = comp(text("manual".to_string()), text("construction".to_string()), true, false);
let mixed = layout! {
"DSL" + "part" @
manual_part @
"more" + "DSL"
};
To see what code the macro generates, use cargo expand
(requires cargo-expand
):
cargo install cargo-expand
cargo expand --example your_example
This will show you the expanded Rust code that the macro produces.
The complete grammar for the layout DSL:
layout ::= binary | atom
binary ::= atom operator layout
atom ::= primary | unary
unary ::= constructor primary
primary ::= variable | string | null | "(" layout ")"
operator ::= "@" | "@@" | "&" | "!&" | "+" | "!+"
constructor ::= "fix" | "grp" | "seq" | "nest" | "pack"
variable ::= IDENT
string ::= STRING_LITERAL
null ::= "null"
Manual | DSL | Notes |
---|---|---|
text("hello".to_string()) |
"hello" |
Much more concise |
comp(a, b, true, false) |
a + b |
Clearer intent |
line(a, b) |
a @ b |
More readable |
nest(comp(a, b, true, false)) |
nest(a + b) |
Easy nesting |
Complex nested calls | Flat operator syntax | Much easier to read |
The DSL is especially valuable for complex layouts where manual construction becomes unwieldy.
This is a procedural macro crate built with:
syn
for parsing Rust syntaxquote
for generating Rust codeproc-macro2
for token stream manipulationThe main parsing logic is in src/lib.rs
with a recursive descent parser for the layout DSL grammar.