Skip to main content

busbar_sf_agentscript/
serializer.rs

1//! AST Serializer - Convert AgentScript AST back to source text.
2//!
3//! This module provides functionality to serialize an [`AgentFile`] AST back into
4//! valid AgentScript source code. This enables:
5//! - Visual editors that modify the AST and regenerate source
6//! - Code transformation and refactoring tools
7//! - AST-based code generators
8//!
9//! # Example
10//!
11//! ```rust
12//! use busbar_sf_agentscript::{parse, serialize};
13//!
14//! let source = r#"
15//! config:
16//!    agent_name: "Test"
17//!
18//! topic main:
19//!    description: "Main topic"
20//! "#;
21//!
22//! let ast = parse(source).unwrap();
23//! let regenerated = serialize(&ast);
24//! println!("{}", regenerated);
25//! ```
26//!
27//! # Formatting
28//!
29//! The serializer produces idiomatic AgentScript with:
30//! - 3-space indentation (AgentScript standard)
31//! - Consistent spacing and newlines
32//! - Proper quoting of strings
33//! - Correct reference formatting (`@namespace.path`)
34
35use crate::ast::*;
36use std::fmt::Write;
37
38/// Serialize an AgentFile AST to AgentScript source code.
39///
40/// # Example
41///
42/// ```rust
43/// use busbar_sf_agentscript::{parse, serialize};
44///
45/// let ast = parse("config:\n   agent_name: \"Test\"\n").unwrap();
46/// let source = serialize(&ast);
47/// assert!(source.contains("agent_name: \"Test\""));
48/// ```
49pub fn serialize(agent: &AgentFile) -> String {
50    let mut w = Writer::new();
51    w.write_agent_file(agent);
52    w.finish()
53}
54
55/// Internal writer for building output.
56struct Writer {
57    output: String,
58    indent: usize,
59}
60
61impl Writer {
62    fn new() -> Self {
63        Self {
64            output: String::new(),
65            indent: 0,
66        }
67    }
68
69    fn finish(self) -> String {
70        self.output
71    }
72
73    /// Write indentation at current level (3 spaces per level).
74    fn write_indent(&mut self) {
75        for _ in 0..self.indent {
76            self.output.push_str("   ");
77        }
78    }
79
80    /// Increase indentation level.
81    fn indent(&mut self) {
82        self.indent += 1;
83    }
84
85    /// Decrease indentation level.
86    fn dedent(&mut self) {
87        if self.indent > 0 {
88            self.indent -= 1;
89        }
90    }
91
92    /// Write a line with current indentation.
93    fn writeln(&mut self, text: &str) {
94        self.write_indent();
95        writeln!(self.output, "{}", text).unwrap();
96    }
97
98    /// Write without indentation or newline.
99    #[allow(dead_code)]
100    fn write(&mut self, text: &str) {
101        write!(self.output, "{}", text).unwrap();
102    }
103
104    /// Write a newline.
105    fn newline(&mut self) {
106        self.output.push('\n');
107    }
108
109    // ========================================================================
110    // Top-Level File Structure
111    // ========================================================================
112
113    fn write_agent_file(&mut self, agent: &AgentFile) {
114        // Write blocks in standard order
115        if let Some(config) = &agent.config {
116            self.write_config_block(&config.node);
117            self.newline();
118        }
119
120        if let Some(variables) = &agent.variables {
121            self.write_variables_block(&variables.node);
122            self.newline();
123        }
124
125        if let Some(system) = &agent.system {
126            self.write_system_block(&system.node);
127            self.newline();
128        }
129
130        for connection in &agent.connections {
131            self.write_connection_block(&connection.node);
132            self.newline();
133        }
134
135        if let Some(knowledge) = &agent.knowledge {
136            self.write_knowledge_block(&knowledge.node);
137            self.newline();
138        }
139
140        if let Some(language) = &agent.language {
141            self.write_language_block(&language.node);
142            self.newline();
143        }
144
145        if let Some(start_agent) = &agent.start_agent {
146            self.write_start_agent_block(&start_agent.node);
147            self.newline();
148        }
149
150        for topic in &agent.topics {
151            self.write_topic_block(&topic.node);
152            self.newline();
153        }
154    }
155
156    // ========================================================================
157    // Config Block
158    // ========================================================================
159
160    fn write_config_block(&mut self, config: &ConfigBlock) {
161        self.writeln("config:");
162        self.indent();
163
164        self.write_indent();
165        write!(self.output, "agent_name: \"{}\"", config.agent_name.node).unwrap();
166        self.newline();
167
168        if let Some(label) = &config.agent_label {
169            self.write_indent();
170            write!(self.output, "agent_label: \"{}\"", label.node).unwrap();
171            self.newline();
172        }
173
174        if let Some(desc) = &config.description {
175            self.write_indent();
176            write!(self.output, "description: \"{}\"", escape_string(&desc.node)).unwrap();
177            self.newline();
178        }
179
180        if let Some(agent_type) = &config.agent_type {
181            self.write_indent();
182            write!(self.output, "agent_type: \"{}\"", agent_type.node).unwrap();
183            self.newline();
184        }
185
186        if let Some(user) = &config.default_agent_user {
187            self.write_indent();
188            write!(self.output, "default_agent_user: \"{}\"", user.node).unwrap();
189            self.newline();
190        }
191
192        self.dedent();
193    }
194
195    // ========================================================================
196    // Variables Block
197    // ========================================================================
198
199    fn write_variables_block(&mut self, vars: &VariablesBlock) {
200        self.writeln("variables:");
201        self.indent();
202
203        for var in &vars.variables {
204            self.write_variable_decl(&var.node);
205        }
206
207        self.dedent();
208    }
209
210    fn write_variable_decl(&mut self, var: &VariableDecl) {
211        self.write_indent();
212        write!(self.output, "{}: ", var.name.node).unwrap();
213
214        // Write kind and type
215        match var.kind {
216            VariableKind::Mutable => {
217                write!(self.output, "mutable {}", self.type_to_string(&var.ty.node)).unwrap();
218                if let Some(default) = &var.default {
219                    write!(self.output, " = {}", self.expr_to_string(&default.node)).unwrap();
220                }
221            }
222            VariableKind::Linked => {
223                write!(self.output, "linked {}", self.type_to_string(&var.ty.node)).unwrap();
224            }
225        }
226        self.newline();
227
228        // Write metadata
229        self.indent();
230        if let Some(desc) = &var.description {
231            self.write_indent();
232            write!(self.output, "description: \"{}\"", escape_string(&desc.node)).unwrap();
233            self.newline();
234        }
235
236        if let Some(source) = &var.source {
237            self.write_indent();
238            write!(self.output, "source: {}", self.reference_to_string(&source.node)).unwrap();
239            self.newline();
240        }
241        self.dedent();
242    }
243
244    // ========================================================================
245    // System Block
246    // ========================================================================
247
248    fn write_system_block(&mut self, system: &SystemBlock) {
249        self.writeln("system:");
250        self.indent();
251
252        if let Some(instructions) = &system.instructions {
253            self.write_indent();
254            write!(self.output, "instructions:").unwrap();
255            self.write_instructions(&instructions.node);
256        }
257
258        if let Some(messages) = &system.messages {
259            self.writeln("messages:");
260            self.indent();
261
262            if let Some(welcome) = &messages.node.welcome {
263                self.write_indent();
264                write!(self.output, "welcome: \"{}\"", escape_string(&welcome.node)).unwrap();
265                self.newline();
266            }
267
268            if let Some(error) = &messages.node.error {
269                self.write_indent();
270                write!(self.output, "error: \"{}\"", escape_string(&error.node)).unwrap();
271                self.newline();
272            }
273
274            self.dedent();
275        }
276
277        self.dedent();
278    }
279
280    // ========================================================================
281    // Connection Block
282    // ========================================================================
283
284    fn write_connection_block(&mut self, connection: &ConnectionBlock) {
285        // Write: connection <name>:
286        self.write_indent();
287        write!(self.output, "connection {}:", connection.name.node).unwrap();
288        self.newline();
289
290        self.indent();
291        for entry in &connection.entries {
292            self.write_indent();
293            write!(
294                self.output,
295                "{}: \"{}\"",
296                entry.node.name.node,
297                escape_string(&entry.node.value.node)
298            )
299            .unwrap();
300            self.newline();
301        }
302        self.dedent();
303    }
304
305    // ========================================================================
306    // Knowledge Block
307    // ========================================================================
308
309    fn write_knowledge_block(&mut self, knowledge: &KnowledgeBlock) {
310        self.writeln("knowledge:");
311        self.indent();
312
313        for entry in &knowledge.entries {
314            self.write_indent();
315            write!(
316                self.output,
317                "{}: {}",
318                entry.node.name.node,
319                self.expr_to_string(&entry.node.value.node)
320            )
321            .unwrap();
322            self.newline();
323        }
324
325        self.dedent();
326    }
327
328    // ========================================================================
329    // Language Block
330    // ========================================================================
331
332    fn write_language_block(&mut self, language: &LanguageBlock) {
333        self.writeln("language:");
334        self.indent();
335
336        for entry in &language.entries {
337            self.write_indent();
338            write!(
339                self.output,
340                "{}: {}",
341                entry.node.name.node,
342                self.expr_to_string(&entry.node.value.node)
343            )
344            .unwrap();
345            self.newline();
346        }
347
348        self.dedent();
349    }
350
351    // ========================================================================
352    // Start Agent Block
353    // ========================================================================
354
355    fn write_start_agent_block(&mut self, start_agent: &StartAgentBlock) {
356        self.write_indent();
357        write!(self.output, "start_agent {}:", start_agent.name.node).unwrap();
358        self.newline();
359
360        self.indent();
361
362        if let Some(desc) = &start_agent.description {
363            self.write_indent();
364            write!(self.output, "description: \"{}\"", escape_string(&desc.node)).unwrap();
365            self.newline();
366        }
367
368        if let Some(system) = &start_agent.system {
369            self.write_topic_system_override(&system.node);
370        }
371
372        if let Some(actions) = &start_agent.actions {
373            self.write_actions_block(&actions.node);
374        }
375
376        if let Some(before) = &start_agent.before_reasoning {
377            self.writeln("before_reasoning:");
378            self.indent();
379            self.write_directive_block(&before.node);
380            self.dedent();
381        }
382
383        if let Some(reasoning) = &start_agent.reasoning {
384            self.write_reasoning_block(&reasoning.node);
385        }
386
387        if let Some(after) = &start_agent.after_reasoning {
388            self.writeln("after_reasoning:");
389            self.indent();
390            self.write_directive_block(&after.node);
391            self.dedent();
392        }
393
394        self.dedent();
395    }
396
397    // ========================================================================
398    // Topic Block
399    // ========================================================================
400
401    fn write_topic_block(&mut self, topic: &TopicBlock) {
402        self.write_indent();
403        write!(self.output, "topic {}:", topic.name.node).unwrap();
404        self.newline();
405
406        self.indent();
407
408        if let Some(desc) = &topic.description {
409            self.write_indent();
410            write!(self.output, "description: \"{}\"", escape_string(&desc.node)).unwrap();
411            self.newline();
412        }
413
414        if let Some(system) = &topic.system {
415            self.write_topic_system_override(&system.node);
416        }
417
418        if let Some(actions) = &topic.actions {
419            self.write_actions_block(&actions.node);
420        }
421
422        if let Some(before) = &topic.before_reasoning {
423            self.writeln("before_reasoning:");
424            self.indent();
425            self.write_directive_block(&before.node);
426            self.dedent();
427        }
428
429        if let Some(reasoning) = &topic.reasoning {
430            self.write_reasoning_block(&reasoning.node);
431        }
432
433        if let Some(after) = &topic.after_reasoning {
434            self.writeln("after_reasoning:");
435            self.indent();
436            self.write_directive_block(&after.node);
437            self.dedent();
438        }
439
440        self.dedent();
441    }
442
443    fn write_topic_system_override(&mut self, system: &TopicSystemOverride) {
444        if let Some(instructions) = &system.instructions {
445            self.writeln("system:");
446            self.indent();
447            self.write_indent();
448            write!(self.output, "instructions:").unwrap();
449            self.write_instructions(&instructions.node);
450            self.dedent();
451        }
452    }
453
454    // ========================================================================
455    // Actions Block
456    // ========================================================================
457
458    fn write_actions_block(&mut self, actions: &ActionsBlock) {
459        self.writeln("actions:");
460        self.indent();
461
462        for action in &actions.actions {
463            self.write_action_def(&action.node);
464        }
465
466        self.dedent();
467    }
468
469    fn write_action_def(&mut self, action: &ActionDef) {
470        self.write_indent();
471        write!(self.output, "{}:", action.name.node).unwrap();
472        self.newline();
473
474        self.indent();
475
476        if let Some(desc) = &action.description {
477            self.write_indent();
478            write!(self.output, "description: \"{}\"", escape_string(&desc.node)).unwrap();
479            self.newline();
480        }
481
482        if let Some(label) = &action.label {
483            self.write_indent();
484            write!(self.output, "label: \"{}\"", escape_string(&label.node)).unwrap();
485            self.newline();
486        }
487
488        if let Some(target) = &action.target {
489            self.write_indent();
490            write!(self.output, "target: \"{}\"", escape_string(&target.node)).unwrap();
491            self.newline();
492        }
493
494        if let Some(confirm) = &action.require_user_confirmation {
495            self.write_indent();
496            write!(
497                self.output,
498                "require_user_confirmation: {}",
499                if confirm.node { "True" } else { "False" }
500            )
501            .unwrap();
502            self.newline();
503        }
504
505        if let Some(progress) = &action.include_in_progress_indicator {
506            self.write_indent();
507            write!(
508                self.output,
509                "include_in_progress_indicator: {}",
510                if progress.node { "True" } else { "False" }
511            )
512            .unwrap();
513            self.newline();
514        }
515
516        if let Some(msg) = &action.progress_indicator_message {
517            self.write_indent();
518            write!(self.output, "progress_indicator_message: \"{}\"", escape_string(&msg.node))
519                .unwrap();
520            self.newline();
521        }
522
523        if let Some(inputs) = &action.inputs {
524            self.writeln("inputs:");
525            self.indent();
526            for param in &inputs.node {
527                self.write_param_def(&param.node);
528            }
529            self.dedent();
530        }
531
532        if let Some(outputs) = &action.outputs {
533            self.writeln("outputs:");
534            self.indent();
535            for param in &outputs.node {
536                self.write_param_def(&param.node);
537            }
538            self.dedent();
539        }
540
541        self.dedent();
542    }
543
544    fn write_param_def(&mut self, param: &ParamDef) {
545        self.write_indent();
546        write!(self.output, "{}: {}", param.name.node, self.type_to_string(&param.ty.node))
547            .unwrap();
548        self.newline();
549
550        self.indent();
551
552        if let Some(desc) = &param.description {
553            self.write_indent();
554            write!(self.output, "description: \"{}\"", escape_string(&desc.node)).unwrap();
555            self.newline();
556        }
557
558        if let Some(label) = &param.label {
559            self.write_indent();
560            write!(self.output, "label: \"{}\"", escape_string(&label.node)).unwrap();
561            self.newline();
562        }
563
564        if let Some(required) = &param.is_required {
565            self.write_indent();
566            write!(self.output, "is_required: {}", if required.node { "True" } else { "False" })
567                .unwrap();
568            self.newline();
569        }
570
571        if let Some(filter) = &param.filter_from_agent {
572            self.write_indent();
573            write!(
574                self.output,
575                "filter_from_agent: {}",
576                if filter.node { "True" } else { "False" }
577            )
578            .unwrap();
579            self.newline();
580        }
581
582        if let Some(displayable) = &param.is_displayable {
583            self.write_indent();
584            write!(
585                self.output,
586                "is_displayable: {}",
587                if displayable.node { "True" } else { "False" }
588            )
589            .unwrap();
590            self.newline();
591        }
592
593        if let Some(complex) = &param.complex_data_type_name {
594            self.write_indent();
595            write!(self.output, "complex_data_type_name: \"{}\"", escape_string(&complex.node))
596                .unwrap();
597            self.newline();
598        }
599
600        self.dedent();
601    }
602
603    // ========================================================================
604    // Directive Block (before_reasoning, after_reasoning)
605    // ========================================================================
606
607    fn write_directive_block(&mut self, block: &DirectiveBlock) {
608        for stmt in &block.statements {
609            self.write_statement(&stmt.node, false);
610        }
611    }
612
613    fn write_statement(&mut self, stmt: &Stmt, _in_reasoning: bool) {
614        match stmt {
615            Stmt::Set { target, value } => {
616                self.write_indent();
617                write!(
618                    self.output,
619                    "set {} = {}",
620                    self.reference_to_string(&target.node),
621                    self.expr_to_string(&value.node)
622                )
623                .unwrap();
624                self.newline();
625            }
626            Stmt::Run {
627                action,
628                with_clauses,
629                set_clauses,
630            } => {
631                self.write_indent();
632                write!(self.output, "run {}", self.reference_to_string(&action.node)).unwrap();
633                self.newline();
634
635                self.indent();
636                for with_clause in with_clauses {
637                    self.write_with_clause(&with_clause.node);
638                }
639                for set_clause in set_clauses {
640                    self.write_set_clause(&set_clause.node);
641                }
642                self.dedent();
643            }
644            Stmt::If {
645                condition,
646                then_block,
647                else_block,
648            } => {
649                self.write_indent();
650                write!(self.output, "if {}:", self.expr_to_string(&condition.node)).unwrap();
651                self.newline();
652
653                self.indent();
654                for then_stmt in then_block {
655                    self.write_statement(&then_stmt.node, _in_reasoning);
656                }
657                self.dedent();
658
659                if let Some(else_stmts) = else_block {
660                    self.writeln("else:");
661                    self.indent();
662                    for else_stmt in else_stmts {
663                        self.write_statement(&else_stmt.node, _in_reasoning);
664                    }
665                    self.dedent();
666                }
667            }
668            Stmt::Transition { target } => {
669                self.write_indent();
670                write!(self.output, "transition to {}", self.reference_to_string(&target.node))
671                    .unwrap();
672                self.newline();
673            }
674        }
675    }
676
677    // ========================================================================
678    // Reasoning Block
679    // ========================================================================
680
681    fn write_reasoning_block(&mut self, reasoning: &ReasoningBlock) {
682        self.writeln("reasoning:");
683        self.indent();
684
685        if let Some(instructions) = &reasoning.instructions {
686            self.write_indent();
687            write!(self.output, "instructions:").unwrap();
688            self.write_instructions(&instructions.node);
689        }
690
691        if let Some(actions) = &reasoning.actions {
692            self.writeln("actions:");
693            self.indent();
694            for action in &actions.node {
695                self.write_reasoning_action(&action.node);
696            }
697            self.dedent();
698        }
699
700        self.dedent();
701    }
702
703    fn write_reasoning_action(&mut self, action: &ReasoningAction) {
704        self.write_indent();
705        write!(
706            self.output,
707            "{}: {}",
708            action.name.node,
709            self.reasoning_action_target_to_string(&action.target.node)
710        )
711        .unwrap();
712        self.newline();
713
714        self.indent();
715
716        if let Some(desc) = &action.description {
717            self.write_indent();
718            write!(self.output, "description: \"{}\"", escape_string(&desc.node)).unwrap();
719            self.newline();
720        }
721
722        if let Some(available) = &action.available_when {
723            self.write_indent();
724            write!(self.output, "available when {}", self.expr_to_string(&available.node)).unwrap();
725            self.newline();
726        }
727
728        for with_clause in &action.with_clauses {
729            self.write_with_clause(&with_clause.node);
730        }
731
732        for set_clause in &action.set_clauses {
733            self.write_set_clause(&set_clause.node);
734        }
735
736        for run_clause in &action.run_clauses {
737            self.write_run_clause(&run_clause.node);
738        }
739
740        for if_clause in &action.if_clauses {
741            self.write_if_clause(&if_clause.node);
742        }
743
744        if let Some(transition) = &action.transition {
745            self.write_indent();
746            write!(self.output, "transition to {}", self.reference_to_string(&transition.node))
747                .unwrap();
748            self.newline();
749        }
750
751        self.dedent();
752    }
753
754    fn write_with_clause(&mut self, with: &WithClause) {
755        self.write_indent();
756        write!(self.output, "with {} = ", with.param.node).unwrap();
757        match &with.value.node {
758            WithValue::Expr(expr) => {
759                write!(self.output, "{}", self.expr_to_string(expr)).unwrap();
760            }
761        }
762        self.newline();
763    }
764
765    fn write_set_clause(&mut self, set: &SetClause) {
766        self.write_indent();
767        write!(
768            self.output,
769            "set {} = {}",
770            self.reference_to_string(&set.target.node),
771            self.expr_to_string(&set.source.node)
772        )
773        .unwrap();
774        self.newline();
775    }
776
777    fn write_run_clause(&mut self, run: &RunClause) {
778        self.write_indent();
779        write!(self.output, "run {}", self.reference_to_string(&run.action.node)).unwrap();
780        self.newline();
781
782        self.indent();
783        for with_clause in &run.with_clauses {
784            self.write_with_clause(&with_clause.node);
785        }
786        for set_clause in &run.set_clauses {
787            self.write_set_clause(&set_clause.node);
788        }
789        self.dedent();
790    }
791
792    fn write_if_clause(&mut self, if_clause: &IfClause) {
793        self.write_indent();
794        write!(self.output, "if {}:", self.expr_to_string(&if_clause.condition.node)).unwrap();
795        self.newline();
796
797        if let Some(transition) = &if_clause.transition {
798            self.indent();
799            self.write_indent();
800            write!(self.output, "transition to {}", self.reference_to_string(&transition.node))
801                .unwrap();
802            self.newline();
803            self.dedent();
804        }
805    }
806
807    // ========================================================================
808    // Instructions
809    // ========================================================================
810
811    fn write_instructions(&mut self, instructions: &Instructions) {
812        match instructions {
813            Instructions::Simple(text) => {
814                // Simple string on same line
815                write!(self.output, " \"{}\"", escape_string(text)).unwrap();
816                self.newline();
817            }
818            Instructions::Static(lines) => {
819                // Static multiline: caller already wrote "instructions:", we add "|"
820                write!(self.output, "|").unwrap();
821                self.newline();
822                self.indent();
823                for line in lines {
824                    self.write_indent();
825                    write!(self.output, "{}", line.node).unwrap();
826                    self.newline();
827                }
828                self.dedent();
829            }
830            Instructions::Dynamic(parts) => {
831                // Dynamic multiline: caller already wrote "instructions:", we add "->"
832                write!(self.output, "->").unwrap();
833                self.newline();
834                self.indent();
835                for part in parts {
836                    self.write_instruction_part(&part.node);
837                }
838                self.dedent();
839            }
840        }
841    }
842
843    fn write_instruction_part(&mut self, part: &InstructionPart) {
844        match part {
845            InstructionPart::Text(text) => {
846                // Text may contain newlines - first line gets |, continuation lines get extra indent
847                let lines: Vec<&str> = text.split('\n').collect();
848                for (i, line) in lines.iter().enumerate() {
849                    self.write_indent();
850                    if i == 0 {
851                        write!(self.output, "| {}", line).unwrap();
852                    } else {
853                        // Continuation line - extra indent, no pipe
854                        write!(self.output, "  {}", line).unwrap();
855                    }
856                    self.newline();
857                }
858            }
859            InstructionPart::Interpolation(expr) => {
860                self.write_indent();
861                write!(self.output, "{{!{}}}", self.expr_to_string(expr)).unwrap();
862                self.newline();
863            }
864            InstructionPart::Conditional {
865                condition,
866                then_parts,
867                else_parts,
868            } => {
869                self.write_indent();
870                write!(self.output, "if {}:", self.expr_to_string(&condition.node)).unwrap();
871                self.newline();
872
873                self.indent();
874                for then_part in then_parts {
875                    self.write_instruction_part(&then_part.node);
876                }
877                self.dedent();
878
879                if let Some(else_ps) = else_parts {
880                    self.writeln("else:");
881                    self.indent();
882                    for else_part in else_ps {
883                        self.write_instruction_part(&else_part.node);
884                    }
885                    self.dedent();
886                }
887            }
888        }
889    }
890
891    // ========================================================================
892    // Helper Methods
893    // ========================================================================
894
895    #[allow(clippy::only_used_in_recursion)]
896    fn type_to_string(&self, ty: &Type) -> String {
897        match ty {
898            Type::String => "string".to_string(),
899            Type::Number => "number".to_string(),
900            Type::Boolean => "boolean".to_string(),
901            Type::Object => "object".to_string(),
902            Type::Date => "date".to_string(),
903            Type::Timestamp => "timestamp".to_string(),
904            Type::Currency => "currency".to_string(),
905            Type::Id => "id".to_string(),
906            Type::Datetime => "datetime".to_string(),
907            Type::Time => "time".to_string(),
908            Type::Integer => "integer".to_string(),
909            Type::Long => "long".to_string(),
910            Type::List(inner) => format!("list[{}]", self.type_to_string(inner)),
911        }
912    }
913
914    fn reference_to_string(&self, reference: &Reference) -> String {
915        reference.full_path()
916    }
917
918    fn reasoning_action_target_to_string(&self, target: &ReasoningActionTarget) -> String {
919        match target {
920            ReasoningActionTarget::Action(r) => self.reference_to_string(r),
921            ReasoningActionTarget::TransitionTo(r) => {
922                format!("@utils.transition to {}", self.reference_to_string(r))
923            }
924            ReasoningActionTarget::Escalate => "@utils.escalate".to_string(),
925            ReasoningActionTarget::SetVariables => "@utils.setVariables".to_string(),
926            ReasoningActionTarget::TopicDelegate(r) => self.reference_to_string(r),
927        }
928    }
929
930    fn expr_to_string(&self, expr: &Expr) -> String {
931        match expr {
932            Expr::Reference(r) => self.reference_to_string(r),
933            Expr::String(s) => format!("\"{}\"", escape_string(s)),
934            Expr::Number(n) => {
935                // Format numbers nicely
936                if n.fract() == 0.0 && n.is_finite() {
937                    format!("{:.0}", n)
938                } else {
939                    format!("{}", n)
940                }
941            }
942            Expr::Bool(b) => {
943                if *b {
944                    "True".to_string()
945                } else {
946                    "False".to_string()
947                }
948            }
949            Expr::None => "None".to_string(),
950            Expr::SlotFill => "...".to_string(),
951            Expr::List(items) => {
952                let items_str: Vec<_> =
953                    items.iter().map(|i| self.expr_to_string(&i.node)).collect();
954                format!("[{}]", items_str.join(", "))
955            }
956            Expr::Object(map) => {
957                let pairs: Vec<_> = map
958                    .iter()
959                    .map(|(k, v)| format!("{}: {}", k, self.expr_to_string(&v.node)))
960                    .collect();
961                format!("{{{}}}", pairs.join(", "))
962            }
963            Expr::BinOp { left, op, right } => {
964                format!(
965                    "{} {} {}",
966                    self.expr_to_string(&left.node),
967                    self.binop_to_string(op),
968                    self.expr_to_string(&right.node)
969                )
970            }
971            Expr::UnaryOp { op, operand } => {
972                format!("{} {}", self.unaryop_to_string(op), self.expr_to_string(&operand.node))
973            }
974            Expr::Ternary {
975                condition,
976                then_expr,
977                else_expr,
978            } => {
979                format!(
980                    "{} if {} else {}",
981                    self.expr_to_string(&then_expr.node),
982                    self.expr_to_string(&condition.node),
983                    self.expr_to_string(&else_expr.node)
984                )
985            }
986            Expr::Property { object, field } => {
987                format!("{}.{}", self.expr_to_string(&object.node), field.node)
988            }
989            Expr::Index { object, index } => {
990                format!(
991                    "{}[{}]",
992                    self.expr_to_string(&object.node),
993                    self.expr_to_string(&index.node)
994                )
995            }
996        }
997    }
998
999    fn binop_to_string(&self, op: &BinOp) -> &'static str {
1000        match op {
1001            BinOp::Eq => "==",
1002            BinOp::Ne => "!=",
1003            BinOp::Lt => "<",
1004            BinOp::Gt => ">",
1005            BinOp::Le => "<=",
1006            BinOp::Ge => ">=",
1007            BinOp::Is => "is",
1008            BinOp::IsNot => "is not",
1009            BinOp::And => "and",
1010            BinOp::Or => "or",
1011            BinOp::Add => "+",
1012            BinOp::Sub => "-",
1013        }
1014    }
1015
1016    fn unaryop_to_string(&self, op: &UnaryOp) -> &'static str {
1017        match op {
1018            UnaryOp::Not => "not",
1019            UnaryOp::Neg => "-",
1020        }
1021    }
1022}
1023
1024/// Escape special characters in strings.
1025fn escape_string(s: &str) -> String {
1026    s.replace('\\', "\\\\")
1027        .replace('"', "\\\"")
1028        .replace('\n', "\\n")
1029        .replace('\r', "\\r")
1030        .replace('\t', "\\t")
1031}
1032
1033#[cfg(test)]
1034mod tests {
1035    use super::*;
1036
1037    #[test]
1038    fn test_serialize_minimal_config() {
1039        let mut agent = AgentFile::new();
1040        agent.config = Some(Spanned::new(
1041            ConfigBlock {
1042                agent_name: Spanned::new("TestAgent".to_string(), 0..10),
1043                agent_label: None,
1044                description: None,
1045                agent_type: None,
1046                default_agent_user: None,
1047            },
1048            0..10,
1049        ));
1050
1051        let output = serialize(&agent);
1052        assert!(output.contains("config:"));
1053        assert!(output.contains("agent_name: \"TestAgent\""));
1054    }
1055
1056    #[test]
1057    fn test_serialize_variable() {
1058        let mut agent = AgentFile::new();
1059        agent.variables = Some(Spanned::new(
1060            VariablesBlock {
1061                variables: vec![Spanned::new(
1062                    VariableDecl {
1063                        name: Spanned::new("test_var".to_string(), 0..8),
1064                        kind: VariableKind::Mutable,
1065                        ty: Spanned::new(Type::String, 0..6),
1066                        default: Some(Spanned::new(Expr::String("default".to_string()), 0..7)),
1067                        description: Some(Spanned::new("Test variable".to_string(), 0..13)),
1068                        source: None,
1069                    },
1070                    0..50,
1071                )],
1072            },
1073            0..50,
1074        ));
1075
1076        let output = serialize(&agent);
1077        assert!(output.contains("variables:"));
1078        assert!(output.contains("test_var: mutable string"));
1079        assert!(output.contains("description: \"Test variable\""));
1080    }
1081
1082    #[test]
1083    fn test_serialize_topic() {
1084        let mut agent = AgentFile::new();
1085        agent.topics = vec![Spanned::new(
1086            TopicBlock {
1087                name: Spanned::new("main".to_string(), 0..4),
1088                description: Some(Spanned::new("Main topic".to_string(), 0..10)),
1089                system: None,
1090                actions: None,
1091                before_reasoning: None,
1092                reasoning: None,
1093                after_reasoning: None,
1094            },
1095            0..50,
1096        )];
1097
1098        let output = serialize(&agent);
1099        assert!(output.contains("topic main:"));
1100        assert!(output.contains("description: \"Main topic\""));
1101    }
1102
1103    #[test]
1104    fn test_escape_string() {
1105        assert_eq!(escape_string("hello"), "hello");
1106        assert_eq!(escape_string("hello\"world"), "hello\\\"world");
1107        assert_eq!(escape_string("line1\nline2"), "line1\\nline2");
1108        assert_eq!(escape_string("tab\there"), "tab\\there");
1109    }
1110
1111    #[test]
1112    fn test_type_to_string() {
1113        let w = Writer::new();
1114        assert_eq!(w.type_to_string(&Type::String), "string");
1115        assert_eq!(w.type_to_string(&Type::Number), "number");
1116        assert_eq!(w.type_to_string(&Type::Boolean), "boolean");
1117        assert_eq!(w.type_to_string(&Type::List(Box::new(Type::String))), "list[string]");
1118    }
1119
1120    #[test]
1121    fn test_expr_to_string() {
1122        let w = Writer::new();
1123        assert_eq!(w.expr_to_string(&Expr::String("test".to_string())), "\"test\"");
1124        assert_eq!(w.expr_to_string(&Expr::Number(42.0)), "42");
1125        assert_eq!(w.expr_to_string(&Expr::Number(3.15)), "3.15");
1126        assert_eq!(w.expr_to_string(&Expr::Bool(true)), "True");
1127        assert_eq!(w.expr_to_string(&Expr::Bool(false)), "False");
1128        assert_eq!(w.expr_to_string(&Expr::None), "None");
1129    }
1130}