aimx/writer.rs
1//! Buffered writer for serializing AIM data structures.
2//!
3//! This module provides a buffered writer implementation for efficiently
4//! serializing AST and AIM data structures to their text representations.
5//! It includes specialized formatting functions for different data types
6//! and supports various output modes including escaped and quoted formats.
7//!
8//! The Writer uses several optimization techniques:
9//! - Buffered writing with a single String buffer to minimize allocations
10//! - Efficient number formatting using `itoa` and `ryu` crates
11//! - Direct character writing when possible
12//! - Specialized formatters for each data type
13//!
14//! # Examples
15//!
16//! ```rust
17//! use aimx::writer::{Writer, PrintMode, Prefix};
18//! use aimx::{Literal, Value};
19//!
20//! // Writing simple text
21//! let mut writer = Writer::stringizer();
22//! writer.write_str("Hello, world!");
23//! assert_eq!(writer.finish(), "Hello, world!");
24//!
25//! // Writing values with different modes
26//! let value = Value::Literal(Literal::from_text("test".to_string()));
27//!
28//! // String mode - no escaping
29//! let mut writer = Writer::stringizer();
30//! writer.print_value(&Prefix::None, &value);
31//! assert_eq!(writer.finish(), "test");
32//!
33//! // Sanitized mode - escape special characters
34//! let mut writer = Writer::sanitizer();
35//! writer.print_value(&Prefix::None, &value);
36//! assert_eq!(writer.finish(), "test");
37//!
38//! // Formula mode - quote and escape special characters
39//! let mut writer = Writer::formulizer();
40//! writer.print_value(&Prefix::None, &value);
41//! assert_eq!(writer.finish(), "\"test\"");
42//! ```
43//!
44//! # Modes and Formatting
45//!
46//! The Writer supports three main formatting modes through the [`PrintMode`] enum:
47//! - [`PrintMode::None`] - No special formatting (stringizer)
48//! - [`PrintMode::Escape`] - Escape special characters (sanitizer)
49//! - [`PrintMode::QuoteEscape`] - Quote and escape special characters (formulizer)
50//!
51//! Prefix styles for list formatting are available through the [`Prefix`] enum:
52//! - [`Prefix::None`] - No prefix
53//! - [`Prefix::Unordered`] - Unordered list prefix (e.g., "- ")
54//! - [`Prefix::Ordered`] - Ordered list prefix (e.g., "1. ")
55//!
56//! # Implementation Details
57//!
58//! The Writer is designed for performance with several key optimizations:
59//! - Buffered writing with a single String buffer to minimize allocations
60//! - Efficient number formatting using `itoa` and `ryu` crates for integers and floats
61//! - Direct character writing when possible
62//! - Specialized formatters for each data type
63//! - Thread-safe operation for use in concurrent scenarios
64//!
65//! # See Also
66//!
67//! - [`Value`] - Runtime value representation that uses Writer for formatting
68//! - [`Literal`] - Compile-time literal value representation
69//! - [`Reference`] - Variable reference representation
70use jiff::civil::DateTime;
71use crate::{
72 ExpressionLike, Literal, Reference, Value
73};
74
75/// Prefix style for list items.
76#[derive(Debug, PartialEq, Clone, Copy)]
77pub enum Prefix {
78 /// No prefix
79 None,
80 /// Unordered list prefix (e.g., "- ")
81 Unordered,
82 /// Ordered list prefix (e.g., "1. ")
83 Ordered,
84}
85
86/// Output formatting mode for the writer.
87#[derive(Debug, PartialEq, Clone, Copy)]
88pub enum PrintMode {
89 /// No special formatting
90 None,
91 /// Escape special characters
92 Escape,
93 /// Quote and escape special characters
94 QuoteEscape,
95}
96
97/// A buffered writer for serializing AST/AIM data structures.
98///
99/// The Writer provides efficient serialization of AST/AIM components to their text representation.
100/// It uses buffered writing with specialized formatting functions for different data types,
101/// providing optimal performance for generating AIM files.
102///
103/// # Implementation Details
104///
105/// The Writer uses several optimization techniques:
106/// - Buffered writing with a single String buffer to minimize allocations
107/// - Efficient number formatting using `itoa` and `ryu` crates
108/// - Direct character writing when possible
109/// - Specialized formatters for each data type
110pub struct Writer {
111 buffer: String,
112 int: itoa::Buffer,
113 float: ryu::Buffer,
114 mode: PrintMode,
115 prefix: Prefix,
116}
117
118impl Writer {
119 /// Creates a new Writer instance with an empty buffer.
120 ///
121 /// # Arguments
122 ///
123 /// * `format` - The PrintMode to use for output formatting
124 ///
125 /// # Returns
126 ///
127 /// * `Writer` - A new Writer instance
128 pub fn new(format: PrintMode, prefix: Prefix) -> Self {
129 Self {
130 buffer: String::new(),
131 int: itoa::Buffer::new(),
132 float: ryu::Buffer::new(),
133 mode: format,
134 prefix,
135 }
136 }
137
138 /// Creates a new Writer instance for writing with an empty buffer.
139 ///
140 /// # Returns
141 ///
142 /// * `Writer` - A new Writer instance
143 pub fn stringizer() -> Self {
144 Self {
145 buffer: String::new(),
146 int: itoa::Buffer::new(),
147 float: ryu::Buffer::new(),
148 mode: PrintMode::None,
149 prefix: Prefix::Unordered,
150 }
151 }
152
153 pub fn is_stringizer(&self) -> bool {
154 match self.mode {
155 PrintMode::None => true,
156 _ => false,
157 }
158 }
159
160 /// Creates a new Writer instance for sanitized writing with an empty buffer.
161 ///
162 /// # Returns
163 ///
164 /// * `Writer` - A new sanitized Writer instance
165 pub fn sanitizer() -> Self {
166 Self {
167 buffer: String::new(),
168 int: itoa::Buffer::new(),
169 float: ryu::Buffer::new(),
170 mode: PrintMode::Escape,
171 prefix: Prefix::Unordered,
172 }
173 }
174
175 pub fn is_sanitizer(&self) -> bool {
176 match self.mode {
177 PrintMode::Escape => true,
178 _ => false,
179 }
180 }
181
182 /// Creates a new Writer instance for writing formulas with an empty buffer.
183 ///
184 /// # Returns
185 ///
186 /// * `Writer` - A new formula Writer instance
187 pub fn formulizer() -> Self {
188 Self {
189 buffer: String::new(),
190 int: itoa::Buffer::new(),
191 float: ryu::Buffer::new(),
192 mode: PrintMode::QuoteEscape,
193 prefix: Prefix::None,
194 }
195 }
196
197 pub fn is_formulizer(&self) -> bool {
198 match self.mode {
199 PrintMode::QuoteEscape => true,
200 _ => false,
201 }
202 }
203
204 pub fn to_sanitized(text: &str) -> String {
205 let mut writer = Writer::sanitizer();
206 writer.escape(text);
207 writer.finish()
208 }
209
210 pub fn to_formula(text: &str) -> String {
211 let mut writer = Writer::formulizer();
212 writer.quote_escape(text);
213 writer.finish()
214 }
215
216 pub fn prefix(&self) -> Prefix {
217 self.prefix
218 }
219
220 pub fn print_mode(&self) -> PrintMode {
221 self.mode
222 }
223
224 /// Writes a string slice to the buffer.
225 ///
226 /// # Arguments
227 ///
228 /// * `s` - The string slice to write
229 pub fn write_str(&mut self, s: &str) {
230 self.buffer.push_str(s);
231 }
232
233 /// Writes a single character to the buffer.
234 ///
235 /// # Arguments
236 ///
237 /// * `c` - The character to write
238 pub fn write_char(&mut self, c: char) {
239 self.buffer.push(c);
240 }
241
242 /// Writes a line ending (newline character) to the buffer.
243 pub fn write_eol(&mut self) {
244 self.buffer.push('\n');
245 }
246
247 /// Writes a string slice and line ending to the buffer.
248 ///
249 /// # Arguments
250 ///
251 /// * `s` - The string slice to write
252 pub fn write_line(&mut self, s: &str) {
253 self.buffer.push_str(s);
254 self.buffer.push('\n');
255 }
256
257 /// Writes a floating-point number to the buffer using efficient formatting.
258 ///
259 /// This method uses the ryu crate for efficient float-to-string conversion
260 /// and removes unnecessary trailing ".0" for integer values.
261 ///
262 /// # Arguments
263 ///
264 /// * `value` - The f64 value to write
265 pub fn write_f64(&mut self, value: f64) {
266 if value.is_normal() {
267 let mut string = self.float.format(value);
268 if let Some(integer) = string.strip_suffix(".0") {
269 string = integer;
270 }
271 self.buffer.push_str(string);
272 } else if value.is_infinite() {
273 if value.is_sign_positive() {
274 self.buffer.push_str("+Inf");
275 }
276 else {
277 self.buffer.push_str("-Inf");
278 }
279 } else if value.is_nan() {
280 self.buffer.push_str("NaN");
281 } else {
282 self.buffer.push('0');
283 }
284 }
285
286 /// Writes an unsigned integer to the buffer using efficient formatting.
287 ///
288 /// This method uses the itoa crate for efficient integer-to-string conversion.
289 ///
290 /// # Arguments
291 ///
292 /// * `value` - The u32 value to write
293 pub fn write_unsigned(&mut self, value: u32) {
294 self.buffer.push_str(self.int.format(value));
295 }
296
297 /// Print text with the current mode's formatting rules.
298 ///
299 /// # Arguments
300 ///
301 /// * `text` - The text to print
302 pub fn print(&mut self, text: &str) {
303 match self.mode {
304 PrintMode::None => self.write_str(text),
305 PrintMode::Escape => self.escape(text),
306 PrintMode::QuoteEscape => self.quote_escape(text),
307 }
308 }
309
310 /// Writes a string literal with proper escaping.
311 ///
312 /// This method properly escapes special characters in string literals:
313 /// - `"` becomes `\"`
314 /// - `\` becomes `\\`
315 /// - Control characters like newline, tab, etc. are escaped appropriately
316 ///
317 /// # Arguments
318 ///
319 /// * `text` - The text to escape and write
320 pub fn escape(&mut self, text: &str) {
321 for c in text.chars() {
322 match c {
323 '"' => self.buffer.push_str("\\\""),
324 '\\' => self.buffer.push_str("\\\\"),
325 '\u{08}' => self.buffer.push_str("\\b"),
326 '\u{0C}' => self.buffer.push_str("\\f"),
327 '\n' => self.buffer.push_str("\\n"),
328 '\r' => self.buffer.push_str("\\r"),
329 '\t' => self.buffer.push_str("\\t"),
330 // ignore other control characters
331 c if c < '\u{20}' => {}
332 _ => self.buffer.push(c),
333 }
334 }
335 }
336
337 /// Writes a string literal with proper quoting and escaping.
338 ///
339 /// This method surrounds the text with double quotes and applies
340 /// proper escaping for special characters.
341 ///
342 /// # Arguments
343 ///
344 /// * `text` - The text to quote, escape, and write
345 pub fn quote_escape(&mut self, text: &str) {
346 self.buffer.push('"');
347 self.escape(text);
348 self.buffer.push('"');
349 }
350
351 /// Writes a string literal surrounded by angle brackets with proper escaping.
352 ///
353 /// This method surrounds the text with angle brackets and applies
354 /// proper escaping for special characters.
355 ///
356 /// # Arguments
357 ///
358 /// * `text` - The text to tag, escape, and write
359 pub fn tag_escape(&mut self, text: &str) {
360 self.buffer.push('<');
361 self.escape(text);
362 self.buffer.push('>');
363 }
364
365 /// Writes a date/time value in ISO 8601 format.
366 ///
367 /// The date is formatted as: `YYYY-MM-DDTHH:MM:SS.sss`
368 ///
369 /// # Arguments
370 ///
371 /// * `date` - The DateTime to format and write
372 pub fn write_date(&mut self, date: &DateTime) {
373 // Format as ISO 8601 string: "2023-12-25T10:30:45.000"
374 // Write year
375 self.buffer.push_str(self.int.format(date.year()));
376 self.buffer.push('-');
377
378 // Write month (padded to 2 digits)
379 let month = date.month();
380 if month < 10 {
381 self.buffer.push('0');
382 }
383 self.buffer.push_str(self.int.format(month));
384 self.buffer.push('-');
385
386 // Write day (padded to 2 digits)
387 let day = date.day();
388 if day < 10 {
389 self.buffer.push('0');
390 }
391 self.buffer.push_str(self.int.format(day));
392 self.buffer.push('T');
393
394 // Write hour (padded to 2 digits)
395 let hour = date.hour();
396 if hour < 10 {
397 self.buffer.push('0');
398 }
399 self.buffer.push_str(self.int.format(hour));
400 self.buffer.push(':');
401
402 // Write minute (padded to 2 digits)
403 let minute = date.minute();
404 if minute < 10 {
405 self.buffer.push('0');
406 }
407 self.buffer.push_str(self.int.format(minute));
408 self.buffer.push(':');
409
410 // Write second (padded to 2 digits)
411 let second = date.second();
412 if second < 10 {
413 self.buffer.push('0');
414 }
415 self.buffer.push_str(self.int.format(second));
416
417 let millisecond = date.subsec_nanosecond() / 1000000;
418 if millisecond > 0 {
419 self.buffer.push('.');
420 if millisecond < 100 {
421 self.buffer.push('0');
422 }
423 if millisecond < 10 {
424 self.buffer.push('0');
425 }
426 self.buffer.push_str(self.int.format(millisecond));
427 }
428 }
429
430 /// Writes a boolean value as "true" or "false".
431 ///
432 /// # Arguments
433 ///
434 /// * `state` - The boolean value to write
435 pub fn write_bool(&mut self, state: bool) {
436 if state {
437 self.buffer.push_str("true");
438 }
439 else {
440 self.buffer.push_str("false");
441 }
442 }
443
444 /// Print a task with the current mode's formatting rules.
445 ///
446 /// # Arguments
447 ///
448 /// * `state` - The task status (Some(true) = completed, Some(false) = failed, None = pending)
449 /// * `text` - The task text
450 pub fn print_task(&mut self, state: &Option<bool>, text: &str) {
451 match self.mode {
452 PrintMode::None => self.write_task(state, text),
453 PrintMode::Escape => self.escape_task(state, text),
454 PrintMode::QuoteEscape => self.quote_escape_task(state, text),
455 }
456 }
457
458 /// Write a task in its raw (non-escaped) textual representation.
459 ///
460 /// # Arguments
461 ///
462 /// * `state` - The task status
463 /// * `text` - The task text
464 fn write_task(&mut self, state: &Option<bool>, text: &str) {
465 match state {
466 Some(check) => self.buffer.push_str(if *check { "[x] " } else { "[-] " }),
467 None => self.buffer.push_str("[ ] "),
468 }
469 self.buffer.push_str(text);
470 }
471
472 /// Escape a task in its JSON/C lang textual representation.
473 ///
474 /// # Arguments
475 ///
476 /// * `state` - The task status
477 /// * `text` - The task text
478 fn escape_task(&mut self, state: &Option<bool>, text: &str) {
479 match state {
480 Some(check) => self.buffer.push_str(if *check { "[x] " } else { "[-] " }),
481 None => self.buffer.push_str("[ ] "),
482 }
483 self.escape(text);
484 }
485
486 /// Quote escape a task in its AIM textual representation.
487 ///
488 /// # Arguments
489 ///
490 /// * `state` - The task status
491 /// * `text` - The task text
492 fn quote_escape_task(&mut self, state: &Option<bool>, text: &str) {
493 match state {
494 Some(check) => self.buffer.push_str(if *check { "[x] " } else { "[-] " }),
495 None => self.buffer.push_str("[ ] "),
496 }
497 self.quote_escape(text);
498 }
499
500 /// Writes a version header in the format `[epoch[:partial]]\n`.
501 ///
502 /// # Arguments
503 ///
504 /// * `epoc` - The epoch number
505 /// * `partial` - The partial version number
506 pub fn write_version(&mut self, epoc: u32, partial: u32) {
507 self.buffer.push('[');
508 self.write_unsigned(epoc);
509 if partial > 0 {
510 self.buffer.push(':');
511 self.write_unsigned(partial);
512 }
513 self.buffer.push(']');
514 self.write_eol();
515 }
516
517 /// Print a literal with the current mode's formatting rules.
518 ///
519 /// # Arguments
520 ///
521 /// * `literal` - The literal to print
522 pub fn print_literal(&mut self, literal: &Literal) {
523 match self.mode {
524 PrintMode::None => self.write_literal(literal),
525 PrintMode::Escape => self.escape_literal(literal),
526 PrintMode::QuoteEscape => self.quote_escape_literal(literal),
527 }
528 }
529
530 /// Prints a literal value in its raw (non-escaped) textual representation.
531 ///
532 /// # Arguments
533 ///
534 /// * `literal` - The literal to write
535 fn write_literal(&mut self, literal: &Literal) {
536 match literal {
537 Literal::Bool(b) => self.write_bool(*b),
538 Literal::Date(d) => self.write_date(d),
539 Literal::Empty => {}
540 Literal::Number(n) => self.write_f64(*n),
541 Literal::Task(s, t) => self.write_task(s, t),
542 Literal::Text(s) => self.buffer.push_str(s),
543 }
544 }
545
546 /// Prints a literal value in its JSON/C land textual representation.
547 ///
548 /// # Arguments
549 ///
550 /// * `literal` - The literal to escape and write
551 fn escape_literal(&mut self, literal: &Literal) {
552 match literal {
553 Literal::Bool(b) => self.write_bool(*b),
554 Literal::Date(d) => self.write_date(d),
555 Literal::Empty => {}
556 Literal::Number(n) => self.write_f64(*n),
557 Literal::Task(s, t) => self.escape_task(s, t),
558 Literal::Text(s) => self.escape(s),
559 }
560 }
561
562 /// Writes a literal value in its AIM textual representation.
563 ///
564 /// # Arguments
565 ///
566 /// * `literal` - The literal to quote, escape, and write
567 fn quote_escape_literal(&mut self, literal: &Literal) {
568 match literal {
569 Literal::Bool(b) => self.write_bool(*b),
570 Literal::Date(d) => self.write_date(d),
571 Literal::Empty => {}
572 Literal::Number(n) => self.write_f64(*n),
573 Literal::Task(s, t) => self.quote_escape_task(s, t),
574 Literal::Text(s) => self.quote_escape(s),
575 }
576 }
577
578 /// Writes a reference in its textual representation.
579 ///
580 /// # Arguments
581 ///
582 /// * `reference` - The reference to write
583 pub fn write_reference(&mut self, reference: &Reference) {
584 match reference {
585 Reference::One(first) => self.buffer.push_str(first),
586 // Relative := identifier.identifier
587 Reference::Two(first, second) => {
588 self.buffer.push_str(first);
589 self.buffer.push('.');
590 self.buffer.push_str(second);
591 }
592 // Relative := identifier.identifier.identifier
593 Reference::Three(first, second, third) => {
594 self.buffer.push_str(first);
595 self.buffer.push('.');
596 self.buffer.push_str(second);
597 self.buffer.push('.');
598 self.buffer.push_str(third);
599 }
600 }
601 }
602
603 /// Print format information with the current mode's formatting rules.
604 ///
605 /// # Arguments
606 ///
607 /// * `instruction` - The instruction text
608 /// * `format` - The format text
609 /// * `array` - The array of format parameters
610 pub fn print_format(&mut self, instruction: &str, format: &str, array: &Vec<String>) {
611 match self.mode {
612 PrintMode::None => self.write_format(instruction, format, array),
613 PrintMode::Escape => self.escape_format(instruction, format, array),
614 PrintMode::QuoteEscape => self.quote_escape_format(instruction, format, array),
615 }
616 }
617
618 /// Write format information in raw format.
619 ///
620 /// # Arguments
621 ///
622 /// * `instruction` - The instruction text
623 /// * `format` - The format text
624 /// * `array` - The array of format parameters
625 fn write_format(&mut self, instruction: &str, format: &str, array: &Vec<String>) {
626 self.buffer.push_str(instruction);
627 self.buffer.push_str(" <");
628 self.buffer.push_str(format);
629 self.buffer.push_str("> ");
630 for (index, text) in array.iter().enumerate() {
631 if index > 0 {
632 self.buffer.push_str(", ");
633 }
634 self.buffer.push_str(text);
635 }
636 }
637
638 /// Write format information with escaping.
639 ///
640 /// # Arguments
641 ///
642 /// * `instruction` - The instruction text
643 /// * `format` - The format text
644 /// * `array` - The array of format parameters
645 fn escape_format(&mut self, instruction: &str, format: &str, array: &Vec<String>) {
646 self.escape(instruction);
647 self.buffer.push(' ');
648 self.tag_escape(format);
649 self.buffer.push(' ');
650 for (index, text) in array.iter().enumerate() {
651 if index > 0 {
652 self.buffer.push_str(", ");
653 }
654 self.escape(text);
655 }
656 }
657
658 /// Write format information with quoting and escaping.
659 ///
660 /// # Arguments
661 ///
662 /// * `instruction` - The instruction text
663 /// * `format` - The format text
664 /// * `array` - The array of format parameters
665 fn quote_escape_format(&mut self, instruction: &str, format: &str, array: &Vec<String>) {
666 self.quote_escape(instruction);
667 self.buffer.push(' ');
668 self.tag_escape(format);
669 self.buffer.push(' ');
670 for (index, text) in array.iter().enumerate() {
671 if index > 0 {
672 self.buffer.push_str(", ");
673 }
674 self.quote_escape(text);
675 }
676 }
677
678 /// Writes a list prefix based on the prefix type and index.
679 ///
680 /// # Arguments
681 ///
682 /// * `prefix` - The prefix type to write
683 /// * `index` - The index for ordered lists
684 pub fn write_prefix(&mut self, indent: isize, prefix: &Prefix, index: usize) {
685 match prefix {
686 Prefix::Ordered => {
687 for _ in 0..indent {
688 self.buffer.push('\t');
689 }
690 self.write_unsigned((index + 1) as u32);
691 self.buffer.push_str(". ");
692 }
693 Prefix::Unordered => {
694 for _ in 0..indent {
695 self.buffer.push('\t');
696 }
697 self.buffer.push_str("- ");
698 }
699 _ => {}
700 }
701 }
702
703 pub fn print_errata(&mut self, reason: &str, formula: &str, location: &str) {
704 match self.mode {
705 PrintMode::None => self.write_errata(reason, formula, location),
706 PrintMode::Escape => self.escape_errata(reason, formula, location),
707 // Print the original expression
708 PrintMode::QuoteEscape => self.quote_escape_errata(reason, formula),
709 }
710 }
711
712 fn write_errata(&mut self, reason: &str, formula: &str, location: &str) {
713 if reason.len() > 0 {
714 self.buffer.push_str(reason);
715 self.buffer.push('\n');
716 }
717 self.buffer.push_str(formula);
718 self.buffer.push('\n');
719 if location.len() > 0 && let Some(offset) = formula.find(location) {
720 for _ in 0..offset {
721 self.buffer.push(' ');
722 }
723 self.buffer.push('^');
724 self.buffer.push('\n');
725 }
726 }
727
728 fn escape_errata(&mut self, reason: &str, formula: &str, location: &str) {
729 let formula= Writer::to_sanitized(formula);
730 let location= Writer::to_sanitized(location);
731 if reason.len() > 0 {
732 self.escape(&reason);
733 self.buffer.push('\n');
734 }
735 self.buffer.push_str(&formula);
736 self.buffer.push('\n');
737 if location.len() > 0 && let Some(offset) = formula.find(&location) {
738 for _ in 0..offset {
739 self.buffer.push(' ');
740 }
741 self.buffer.push('^');
742 self.buffer.push('\n');
743 }
744 }
745
746 fn quote_escape_errata(&mut self, reason: &str, formula: &str) {
747 // Just print the expression
748 if formula.len() > 0 {
749 self.escape(&formula);
750 }
751 else {
752 self.escape(&reason);
753 }
754 }
755
756 /// Print format information with the current mode's formatting rules.
757 ///
758 /// # Arguments
759 ///
760 /// * `prefix` - The instruction text
761 /// * `format` - The format text
762 /// * `array` - The array of format parameters
763 pub fn print_value(&mut self, prefix: &Prefix, value: &Value) {
764 match self.mode {
765 PrintMode::None => self.write_value(-1, prefix, 0, value),
766 PrintMode::Escape => self.escape_value(-1, prefix, 0, value),
767 PrintMode::QuoteEscape => self.quote_escape_value(0, value),
768 }
769 }
770
771 /// Prints a value in its raw (non-escaped) textual representation.
772 fn write_value(&mut self, indent: isize, prefix: &Prefix, index: usize, value: &Value) {
773 match value {
774 Value::Empty | Value::Node(_) => {},
775 Value::Errata(errata) => errata.write(self),
776 Value::Literal(literal) => {
777 if indent >= 0 {
778 self.write_prefix(indent, prefix, index);
779 }
780 self.write_literal(literal)
781 }
782 Value::Array(values) => {
783 for (index, value) in values.iter().enumerate() {
784 if index > 0 {
785 self.buffer.push('\n');
786 }
787 self.write_value(indent + 1, prefix, index, value);
788 }
789 }
790 Value::Closure(closure) => closure.write(self),
791 Value::Assignment(reference) => reference.write(self),
792 Value::Format(format) => format.write(self),
793 Value::Eval(eval) => eval.write(self),
794 }
795 }
796
797 /// Writes a value in its JSON/C lang textual representation.
798 fn escape_value(&mut self, indent: isize, prefix: &Prefix, index: usize, value: &Value) {
799 match value {
800 Value::Empty | Value::Node(_) => {},
801 Value::Errata(errata) => errata.write(self),
802 Value::Literal(literal) => {
803 if indent >= 0 {
804 self.write_prefix(indent, prefix, index);
805 }
806 self.escape_literal(literal);
807 }
808 Value::Array(values) => {
809 for (index, value) in values.iter().enumerate() {
810 if index > 0 {
811 self.buffer.push_str("\\n");
812 }
813 self.escape_value(indent + 1, prefix, index, value);
814 }
815 }
816 Value::Closure(closure) => closure.write(self),
817 Value::Assignment(reference) => reference.write(self),
818 Value::Format(format) => format.write(self),
819 Value::Eval(eval) => eval.write(self),
820 }
821 }
822
823 /// Writes a value in its AIM textual representation.
824 fn quote_escape_value(&mut self, indent: usize, value: &Value) {
825 match value {
826 Value::Empty | Value::Node(_) => {},
827 Value::Errata(errata) => errata.write(self),
828 Value::Literal(literal) => self.quote_escape_literal(literal),
829 Value::Array(values) => {
830 if indent > 0 {
831 self.buffer.push('(');
832 }
833 for (index, value) in values.iter().enumerate() {
834 if index > 0 {
835 self.buffer.push_str(", ");
836 }
837 self.quote_escape_value(indent + 1, value);
838 }
839 if indent > 0 {
840 self.buffer.push(')');
841 }
842 }
843 Value::Closure(closure) => closure.write(self),
844 Value::Assignment(reference) => reference.write(self),
845 Value::Format(format) => format.write(self),
846 Value::Eval(eval) => eval.write(self),
847 }
848 }
849
850
851 /// Writes a unary operation with the operator and operand.
852 ///
853 /// # Arguments
854 ///
855 /// * `op` - The operator string
856 /// * `unary` - The operand expression
857 pub fn write_unary_op(&mut self, op: &str, unary: &dyn ExpressionLike) {
858 self.write_str(op);
859 unary.write(self);
860 }
861
862 /// Writes a binary operation with left operand, operator, and right operand.
863 ///
864 /// # Arguments
865 ///
866 /// * `left` - The left operand expression
867 /// * `op` - The operator string
868 /// * `right` - The right operand expression
869 pub fn write_binary_op(&mut self, left: &dyn ExpressionLike, op: &str, right: &dyn ExpressionLike) {
870 left.write(self);
871 self.write_str(op);
872 right.write(self);
873 }
874
875 /// Consumes the writer and returns the final string buffer.
876 ///
877 /// # Returns
878 ///
879 /// * `String` - The final accumulated string
880 pub fn finish(self) -> String {
881 self.buffer
882 }
883}