Round-trip Printing
Implement ToTokens to convert AST back to formatted output.
Basic Pattern
impl ToTokens for MyNode {
fn write(&self, p: &mut Printer) {
self.child1.value.write(p);
p.space();
self.child2.value.write(p);
}
}
Token Printing
// Custom ToTokens implementations for tokens that need special handling.
// Other token ToTokens are auto-generated by parser_kit!
impl ToTokens for tokens::BasicStringToken {
fn write(&self, p: &mut Printer) {
// BasicString stores content without quotes, so we add them back for round-trip
p.word("\"");
p.word(&self.0);
p.word("\"");
}
}
impl ToTokens for tokens::NewlineToken {
fn write(&self, p: &mut Printer) {
p.newline();
}
}
Note: BasicStringToken strips quotes during lexing, so we re-add them for output.
Trivia
Preserve newlines and comments:
impl ToTokens for Trivia {
fn write(&self, p: &mut Printer) {
match self {
Trivia::Newline(nl) => nl.value.write(p),
Trivia::Comment(c) => c.value.write(p),
}
}
}
Key-Value Pairs
impl ToTokens for KeyValue {
fn write(&self, p: &mut Printer) {
self.key.value.write(p);
p.space();
self.eq.value.write(p);
p.space();
self.value.value.write(p);
}
}
Spacing around = is a style choice—adjust as needed.
Arrays
Handle items with optional trailing commas:
impl ToTokens for ArrayItem {
fn write(&self, p: &mut Printer) {
self.value.value.write(p);
if let Some(comma) = &self.comma {
comma.value.write(p);
p.space();
}
}
}
impl ToTokens for Array {
fn write(&self, p: &mut Printer) {
self.lbracket.value.write(p);
for item in &self.items {
item.write(p);
}
self.rbracket.value.write(p);
}
}
Tables
impl ToTokens for TableItem {
fn write(&self, p: &mut Printer) {
match self {
TableItem::Trivia(trivia) => trivia.write(p),
TableItem::KeyValue(kv) => kv.value.write(p),
}
}
}
impl ToTokens for Table {
fn write(&self, p: &mut Printer) {
self.lbracket.value.write(p);
self.name.value.write(p);
self.rbracket.value.write(p);
for item in &self.items {
item.write(p);
}
}
}
Documents
impl ToTokens for DocumentItem {
fn write(&self, p: &mut Printer) {
match self {
DocumentItem::Trivia(trivia) => trivia.write(p),
DocumentItem::KeyValue(kv) => kv.value.write(p),
DocumentItem::Table(table) => table.value.write(p),
}
}
}
impl ToTokens for Document {
fn write(&self, p: &mut Printer) {
for item in &self.items {
item.write(p);
}
}
}
Using the Output
// Parse
let mut stream = TokenStream::lex(input)?;
let doc: Spanned<Document> = stream.parse()?;
// Print using trait method
let output = doc.value.to_string_formatted();
// Or manual printer
let mut printer = Printer::new();
doc.value.write(&mut printer);
let output = printer.finish();
Printer Methods Reference
| Method | Effect |
|---|---|
word(s) | Append string |
token(&tok) | Append token’s display |
space() | Single space |
newline() | Line break |
open_block() | Indent + newline |
close_block() | Dedent + newline |
indent() | Increase indent |
dedent() | Decrease indent |
write_separated(&items, sep) | Items with separator |
Formatting Choices
The ToTokens implementation defines your output format:
// Compact: key=value
self.key.value.write(p);
self.eq.value.write(p);
self.value.value.write(p);
// Spaced: key = value
self.key.value.write(p);
p.space();
self.eq.value.write(p);
p.space();
self.value.value.write(p);
For exact round-trip, store original spacing as trivia. For normalized output, apply consistent rules in write().