helix_core/
ast.rs

1use std::collections::HashMap;
2use crate::types::{Value, Duration, TimeUnit};
3#[derive(Debug, Clone)]
4pub struct HelixAst {
5    pub declarations: Vec<Declaration>,
6}
7#[derive(Debug, Clone)]
8pub enum Declaration {
9    Project(ProjectDecl),
10    Agent(AgentDecl),
11    Workflow(WorkflowDecl),
12    Memory(MemoryDecl),
13    Context(ContextDecl),
14    Crew(CrewDecl),
15    Pipeline(PipelineDecl),
16    Plugin(PluginDecl),
17    Database(DatabaseDecl),
18    Load(LoadDecl),
19    // Generic section declaration for dynamic sections like forge, model, tokenizer, etc.
20    Section(SectionDecl),
21}
22#[derive(Debug, Clone)]
23pub struct ProjectDecl {
24    pub name: String,
25    pub properties: HashMap<String, Expression>,
26}
27#[derive(Debug, Clone)]
28pub struct AgentDecl {
29    pub name: String,
30    pub properties: HashMap<String, Expression>,
31    pub capabilities: Option<Vec<String>>,
32    pub backstory: Option<BackstoryBlock>,
33    pub tools: Option<Vec<String>>,
34}
35#[derive(Debug, Clone)]
36pub struct WorkflowDecl {
37    pub name: String,
38    pub trigger: Option<Expression>,
39    pub steps: Vec<StepDecl>,
40    pub pipeline: Option<PipelineDecl>,
41    pub properties: HashMap<String, Expression>,
42}
43#[derive(Debug, Clone)]
44pub struct StepDecl {
45    pub name: String,
46    pub agent: Option<String>,
47    pub crew: Option<Vec<String>>,
48    pub task: Option<String>,
49    pub properties: HashMap<String, Expression>,
50}
51#[derive(Debug, Clone)]
52pub struct PipelineDecl {
53    pub flow: Vec<PipelineNode>,
54}
55#[derive(Debug, Clone)]
56pub enum PipelineNode {
57    Step(String),
58    Parallel(Vec<PipelineNode>),
59    Conditional {
60        condition: Expression,
61        then_branch: Box<PipelineNode>,
62        else_branch: Option<Box<PipelineNode>>,
63    },
64}
65#[derive(Debug, Clone)]
66pub struct MemoryDecl {
67    pub provider: String,
68    pub connection: String,
69    pub embeddings: Option<EmbeddingsDecl>,
70    pub properties: HashMap<String, Expression>,
71}
72#[derive(Debug, Clone)]
73pub struct EmbeddingsDecl {
74    pub model: String,
75    pub dimensions: u32,
76    pub properties: HashMap<String, Expression>,
77}
78#[derive(Debug, Clone)]
79pub struct ContextDecl {
80    pub name: String,
81    pub environment: String,
82    pub secrets: Option<HashMap<String, SecretRef>>,
83    pub variables: Option<HashMap<String, Expression>>,
84    pub properties: HashMap<String, Expression>,
85}
86#[derive(Debug, Clone)]
87pub struct CrewDecl {
88    pub name: String,
89    pub agents: Vec<String>,
90    pub process_type: Option<String>,
91    pub properties: HashMap<String, Expression>,
92}
93#[derive(Debug, Clone)]
94pub struct PluginDecl {
95    pub name: String,
96    pub source: String,
97    pub version: Option<String>,
98    pub config: HashMap<String, Expression>,
99}
100#[derive(Debug, Clone)]
101pub struct DatabaseDecl {
102    pub name: String,
103    pub path: Option<String>,
104    pub shards: Option<i64>,
105    pub compression: Option<bool>,
106    pub cache_size: Option<i64>,
107    pub vector_index: Option<VectorIndexConfig>,
108    pub properties: HashMap<String, Expression>,
109}
110#[derive(Debug, Clone)]
111pub struct VectorIndexConfig {
112    pub index_type: String,
113    pub dimensions: i64,
114    pub m: Option<i64>,
115    pub ef_construction: Option<i64>,
116    pub distance_metric: Option<String>,
117    pub properties: HashMap<String, Expression>,
118}
119#[derive(Debug, Clone)]
120pub struct LoadDecl {
121    pub file_name: String,
122    pub properties: HashMap<String, Expression>,
123}
124#[derive(Debug, Clone)]
125pub struct BackstoryBlock {
126    pub lines: Vec<String>,
127}
128#[derive(Debug, Clone)]
129pub enum SecretRef {
130    Environment(String),
131    Vault(String),
132    File(String),
133}
134#[derive(Debug, Clone)]
135pub enum Expression {
136    String(String),
137    Number(f64),
138    Bool(bool),
139    Duration(Duration),
140    Array(Vec<Expression>),
141    Object(HashMap<String, Expression>),
142    Variable(String),
143    Reference(String),
144    IndexedReference(String, String),
145    Identifier(String),
146    Pipeline(Vec<String>),
147    Block(Vec<Statement>),
148    TextBlock(Vec<String>),
149}
150#[derive(Debug, Clone)]
151pub enum Statement {
152    Assignment(String, Expression),
153    Declaration(Declaration),
154    Expression(Expression),
155}
156impl HelixAst {
157    pub fn new() -> Self {
158        HelixAst {
159            declarations: Vec::new(),
160        }
161    }
162    pub fn add_declaration(&mut self, decl: Declaration) {
163        self.declarations.push(decl);
164    }
165    pub fn get_projects(&self) -> Vec<&ProjectDecl> {
166        self.declarations
167            .iter()
168            .filter_map(|d| {
169                if let Declaration::Project(p) = d { Some(p) } else { None }
170            })
171            .collect()
172    }
173    pub fn get_agents(&self) -> Vec<&AgentDecl> {
174        self.declarations
175            .iter()
176            .filter_map(|d| {
177                if let Declaration::Agent(a) = d { Some(a) } else { None }
178            })
179            .collect()
180    }
181    pub fn get_workflows(&self) -> Vec<&WorkflowDecl> {
182        self.declarations
183            .iter()
184            .filter_map(|d| {
185                if let Declaration::Workflow(w) = d { Some(w) } else { None }
186            })
187            .collect()
188    }
189    pub fn get_contexts(&self) -> Vec<&ContextDecl> {
190        self.declarations
191            .iter()
192            .filter_map(|d| {
193                if let Declaration::Context(c) = d { Some(c) } else { None }
194            })
195            .collect()
196    }
197}
198#[allow(dead_code)]
199pub trait AstVisitor {
200    type Result;
201    fn visit_ast(&mut self, ast: &HelixAst) -> Self::Result;
202    fn visit_declaration(&mut self, decl: &Declaration) -> Self::Result;
203    fn visit_project(&mut self, project: &ProjectDecl) -> Self::Result;
204    fn visit_agent(&mut self, agent: &AgentDecl) -> Self::Result;
205    fn visit_workflow(&mut self, workflow: &WorkflowDecl) -> Self::Result;
206    fn visit_memory(&mut self, memory: &MemoryDecl) -> Self::Result;
207    fn visit_context(&mut self, context: &ContextDecl) -> Self::Result;
208    fn visit_crew(&mut self, crew: &CrewDecl) -> Self::Result;
209    fn visit_expression(&mut self, expr: &Expression) -> Self::Result;
210}
211pub struct AstPrettyPrinter {
212    indent: usize,
213    indent_str: String,
214}
215impl AstPrettyPrinter {
216    pub fn new() -> Self {
217        AstPrettyPrinter {
218            indent: 0,
219            indent_str: "  ".to_string(),
220        }
221    }
222    fn write_indent(&self) -> String {
223        self.indent_str.repeat(self.indent)
224    }
225    pub fn print(&mut self, ast: &HelixAst) -> String {
226        let mut result = String::new();
227        result.push_str("# HELIX Language AST\n\n");
228        for decl in &ast.declarations {
229            result.push_str(&self.print_declaration(decl));
230            result.push_str("\n");
231        }
232        result
233    }
234    fn print_declaration(&mut self, decl: &Declaration) -> String {
235        match decl {
236            Declaration::Project(p) => self.print_project(p),
237            Declaration::Agent(a) => self.print_agent(a),
238            Declaration::Workflow(w) => self.print_workflow(w),
239            Declaration::Memory(m) => self.print_memory(m),
240            Declaration::Context(c) => self.print_context(c),
241            Declaration::Crew(cr) => self.print_crew(cr),
242            Declaration::Pipeline(p) => self.print_pipeline(p),
243            Declaration::Plugin(p) => self.print_plugin(p),
244            Declaration::Database(d) => self.print_database(d),
245            Declaration::Load(l) => self.print_load(l),
246            Declaration::Section(s) => self.print_section(s),
247        }
248    }
249    fn print_project(&mut self, project: &ProjectDecl) -> String {
250        let mut result = format!(
251            "{}project \"{}\" {{\n", self.write_indent(), project.name
252        );
253        self.indent += 1;
254        for (key, value) in &project.properties {
255            result
256                .push_str(
257                    &format!(
258                        "{}{} = {}\n", self.write_indent(), key, self
259                        .print_expression(value)
260                    ),
261                );
262        }
263        self.indent -= 1;
264        result.push_str(&format!("{}}}\n", self.write_indent()));
265        result
266    }
267    fn print_agent(&mut self, agent: &AgentDecl) -> String {
268        let mut result = format!("{}agent \"{}\" {{\n", self.write_indent(), agent.name);
269        self.indent += 1;
270        for (key, value) in &agent.properties {
271            result
272                .push_str(
273                    &format!(
274                        "{}{} = {}\n", self.write_indent(), key, self
275                        .print_expression(value)
276                    ),
277                );
278        }
279        if let Some(capabilities) = &agent.capabilities {
280            result.push_str(&format!("{}capabilities [\n", self.write_indent()));
281            self.indent += 1;
282            for cap in capabilities {
283                result.push_str(&format!("{}\"{}\"\n", self.write_indent(), cap));
284            }
285            self.indent -= 1;
286            result.push_str(&format!("{}]\n", self.write_indent()));
287        }
288        if let Some(backstory) = &agent.backstory {
289            result.push_str(&format!("{}backstory {{\n", self.write_indent()));
290            self.indent += 1;
291            for line in &backstory.lines {
292                result.push_str(&format!("{}{}\n", self.write_indent(), line));
293            }
294            self.indent -= 1;
295            result.push_str(&format!("{}}}\n", self.write_indent()));
296        }
297        self.indent -= 1;
298        result.push_str(&format!("{}}}\n", self.write_indent()));
299        result
300    }
301    fn print_workflow(&mut self, workflow: &WorkflowDecl) -> String {
302        let mut result = format!(
303            "{}workflow \"{}\" {{\n", self.write_indent(), workflow.name
304        );
305        self.indent += 1;
306        if let Some(trigger) = &workflow.trigger {
307            result
308                .push_str(
309                    &format!(
310                        "{}trigger = {}\n", self.write_indent(), self
311                        .print_expression(trigger)
312                    ),
313                );
314        }
315        for step in &workflow.steps {
316            result.push_str(&self.print_step(step));
317        }
318        if let Some(pipeline) = &workflow.pipeline {
319            result.push_str(&self.print_pipeline(pipeline));
320        }
321        for (key, value) in &workflow.properties {
322            result
323                .push_str(
324                    &format!(
325                        "{}{} = {}\n", self.write_indent(), key, self
326                        .print_expression(value)
327                    ),
328                );
329        }
330        self.indent -= 1;
331        result.push_str(&format!("{}}}\n", self.write_indent()));
332        result
333    }
334    fn print_step(&mut self, step: &StepDecl) -> String {
335        let mut result = format!("{}step \"{}\" {{\n", self.write_indent(), step.name);
336        self.indent += 1;
337        if let Some(agent) = &step.agent {
338            result.push_str(&format!("{}agent = \"{}\"\n", self.write_indent(), agent));
339        }
340        if let Some(crew) = &step.crew {
341            result.push_str(&format!("{}crew = [", self.write_indent()));
342            result
343                .push_str(
344                    &crew
345                        .iter()
346                        .map(|c| format!("\"{}\"", c))
347                        .collect::<Vec<_>>()
348                        .join(", "),
349                );
350            result.push_str("]\n");
351        }
352        if let Some(task) = &step.task {
353            result.push_str(&format!("{}task = \"{}\"\n", self.write_indent(), task));
354        }
355        for (key, value) in &step.properties {
356            result
357                .push_str(
358                    &format!(
359                        "{}{} = {}\n", self.write_indent(), key, self
360                        .print_expression(value)
361                    ),
362                );
363        }
364        self.indent -= 1;
365        result.push_str(&format!("{}}}\n", self.write_indent()));
366        result
367    }
368    fn print_memory(&mut self, memory: &MemoryDecl) -> String {
369        let mut result = format!("{}memory {{\n", self.write_indent());
370        self.indent += 1;
371        result
372            .push_str(
373                &format!("{}provider = \"{}\"\n", self.write_indent(), memory.provider),
374            );
375        result
376            .push_str(
377                &format!(
378                    "{}connection = \"{}\"\n", self.write_indent(), memory.connection
379                ),
380            );
381        if let Some(embeddings) = &memory.embeddings {
382            result.push_str(&self.print_embeddings(embeddings));
383        }
384        for (key, value) in &memory.properties {
385            result
386                .push_str(
387                    &format!(
388                        "{}{} = {}\n", self.write_indent(), key, self
389                        .print_expression(value)
390                    ),
391                );
392        }
393        self.indent -= 1;
394        result.push_str(&format!("{}}}\n", self.write_indent()));
395        result
396    }
397    fn print_embeddings(&mut self, embeddings: &EmbeddingsDecl) -> String {
398        let mut result = format!("{}embeddings {{\n", self.write_indent());
399        self.indent += 1;
400        result
401            .push_str(
402                &format!("{}model = \"{}\"\n", self.write_indent(), embeddings.model),
403            );
404        result
405            .push_str(
406                &format!(
407                    "{}dimensions = {}\n", self.write_indent(), embeddings.dimensions
408                ),
409            );
410        for (key, value) in &embeddings.properties {
411            result
412                .push_str(
413                    &format!(
414                        "{}{} = {}\n", self.write_indent(), key, self
415                        .print_expression(value)
416                    ),
417                );
418        }
419        self.indent -= 1;
420        result.push_str(&format!("{}}}\n", self.write_indent()));
421        result
422    }
423    fn print_context(&mut self, context: &ContextDecl) -> String {
424        let mut result = format!(
425            "{}context \"{}\" {{\n", self.write_indent(), context.name
426        );
427        self.indent += 1;
428        result
429            .push_str(
430                &format!(
431                    "{}environment = \"{}\"\n", self.write_indent(), context.environment
432                ),
433            );
434        if let Some(secrets) = &context.secrets {
435            result.push_str(&format!("{}secrets {{\n", self.write_indent()));
436            self.indent += 1;
437            for (key, secret_ref) in secrets {
438                result
439                    .push_str(
440                        &format!(
441                            "{}{} = {}\n", self.write_indent(), key, self
442                            .print_secret_ref(secret_ref)
443                        ),
444                    );
445            }
446            self.indent -= 1;
447            result.push_str(&format!("{}}}\n", self.write_indent()));
448        }
449        if let Some(variables) = &context.variables {
450            result.push_str(&format!("{}variables {{\n", self.write_indent()));
451            self.indent += 1;
452            for (key, value) in variables {
453                result
454                    .push_str(
455                        &format!(
456                            "{}{} = {}\n", self.write_indent(), key, self
457                            .print_expression(value)
458                        ),
459                    );
460            }
461            self.indent -= 1;
462            result.push_str(&format!("{}}}\n", self.write_indent()));
463        }
464        for (key, value) in &context.properties {
465            result
466                .push_str(
467                    &format!(
468                        "{}{} = {}\n", self.write_indent(), key, self
469                        .print_expression(value)
470                    ),
471                );
472        }
473        self.indent -= 1;
474        result.push_str(&format!("{}}}\n", self.write_indent()));
475        result
476    }
477    fn print_crew(&mut self, crew: &CrewDecl) -> String {
478        let mut result = format!("{}crew \"{}\" {{\n", self.write_indent(), crew.name);
479        self.indent += 1;
480        result.push_str(&format!("{}agents [\n", self.write_indent()));
481        self.indent += 1;
482        for agent in &crew.agents {
483            result.push_str(&format!("{}\"{}\"\n", self.write_indent(), agent));
484        }
485        self.indent -= 1;
486        result.push_str(&format!("{}]\n", self.write_indent()));
487        if let Some(process_type) = &crew.process_type {
488            result
489                .push_str(
490                    &format!("{}process = \"{}\"\n", self.write_indent(), process_type),
491                );
492        }
493        for (key, value) in &crew.properties {
494            result
495                .push_str(
496                    &format!(
497                        "{}{} = {}\n", self.write_indent(), key, self
498                        .print_expression(value)
499                    ),
500                );
501        }
502        self.indent -= 1;
503        result.push_str(&format!("{}}}\n", self.write_indent()));
504        result
505    }
506    fn print_plugin(&mut self, plugin: &PluginDecl) -> String {
507        let mut result = format!(
508            "{}plugin \"{}\" {{\n", self.write_indent(), plugin.name
509        );
510        self.indent += 1;
511        result
512            .push_str(
513                &format!("{}source = \"{}\"\n", self.write_indent(), plugin.source),
514            );
515        if let Some(version) = &plugin.version {
516            result
517                .push_str(
518                    &format!("{}version = \"{}\"\n", self.write_indent(), version),
519                );
520        }
521        for (key, value) in &plugin.config {
522            result
523                .push_str(
524                    &format!(
525                        "{}{} = {}\n", self.write_indent(), key, self
526                        .print_expression(value)
527                    ),
528                );
529        }
530        self.indent -= 1;
531        result.push_str(&format!("{}}}\n", self.write_indent()));
532        result
533    }
534    fn print_database(&mut self, database: &DatabaseDecl) -> String {
535        let mut result = format!(
536            "{}database \"{}\" {{\n", self.write_indent(), database.name
537        );
538        self.indent += 1;
539        if let Some(path) = &database.path {
540            result.push_str(&format!("{}path = \"{}\"\n", self.write_indent(), path));
541        }
542        if let Some(shards) = database.shards {
543            result.push_str(&format!("{}shards = {}\n", self.write_indent(), shards));
544        }
545        if let Some(compression) = database.compression {
546            result
547                .push_str(
548                    &format!("{}compression = {}\n", self.write_indent(), compression),
549                );
550        }
551        if let Some(cache_size) = database.cache_size {
552            result
553                .push_str(
554                    &format!("{}cache_size = {}\n", self.write_indent(), cache_size),
555                );
556        }
557        if let Some(vector_index) = &database.vector_index {
558            result.push_str(&format!("{}vector_index {{\n", self.write_indent()));
559            self.indent += 1;
560            result
561                .push_str(
562                    &format!(
563                        "{}index_type = \"{}\"\n", self.write_indent(), vector_index
564                        .index_type
565                    ),
566                );
567            result
568                .push_str(
569                    &format!(
570                        "{}dimensions = {}\n", self.write_indent(), vector_index
571                        .dimensions
572                    ),
573                );
574            if let Some(m) = vector_index.m {
575                result.push_str(&format!("{}m = {}\n", self.write_indent(), m));
576            }
577            if let Some(ef_construction) = vector_index.ef_construction {
578                result
579                    .push_str(
580                        &format!(
581                            "{}ef_construction = {}\n", self.write_indent(),
582                            ef_construction
583                        ),
584                    );
585            }
586            if let Some(distance_metric) = &vector_index.distance_metric {
587                result
588                    .push_str(
589                        &format!(
590                            "{}distance_metric = \"{}\"\n", self.write_indent(),
591                            distance_metric
592                        ),
593                    );
594            }
595            self.indent -= 1;
596            result.push_str(&format!("{}}}\n", self.write_indent()));
597        }
598        for (key, value) in &database.properties {
599            result
600                .push_str(
601                    &format!(
602                        "{}{} = {}\n", self.write_indent(), key, self
603                        .print_expression(value)
604                    ),
605                );
606        }
607        self.indent -= 1;
608        result.push_str(&format!("{}}}\n", self.write_indent()));
609        result
610    }
611    fn print_load(&mut self, load: &LoadDecl) -> String {
612        let mut result = format!(
613            "{}load \"{}\" {{\n", self.write_indent(), load.file_name
614        );
615        self.indent += 1;
616        for (key, value) in &load.properties {
617            result
618                .push_str(
619                    &format!(
620                        "{}{} = {}\n", self.write_indent(), key, self
621                        .print_expression(value)
622                    ),
623                );
624        }
625        self.indent -= 1;
626        result.push_str(&format!("{}}}\n", self.write_indent()));
627        result
628    }
629    fn print_section(&mut self, section: &SectionDecl) -> String {
630        let mut result = format!("{}{} {{\n", self.write_indent(), section.name);
631        self.indent += 1;
632        for (key, value) in &section.properties {
633            result
634                .push_str(
635                    &format!(
636                        "{}{} = {}\n", self.write_indent(), key, self
637                        .print_expression(value)
638                    ),
639                );
640        }
641        self.indent -= 1;
642        result.push_str(&format!("{}}}\n", self.write_indent()));
643        result
644    }
645    fn print_pipeline(&mut self, pipeline: &PipelineDecl) -> String {
646        let mut result = format!("{}pipeline {{\n", self.write_indent());
647        self.indent += 1;
648        let flow_str = pipeline
649            .flow
650            .iter()
651            .map(|node| match node {
652                PipelineNode::Step(s) => s.clone(),
653                _ => "...".to_string(),
654            })
655            .collect::<Vec<_>>()
656            .join(" -> ");
657        result.push_str(&format!("{}{}\n", self.write_indent(), flow_str));
658        self.indent -= 1;
659        result.push_str(&format!("{}}}\n", self.write_indent()));
660        result
661    }
662    fn print_secret_ref(&mut self, secret_ref: &SecretRef) -> String {
663        match secret_ref {
664            SecretRef::Environment(var) => format!("${}", var),
665            SecretRef::Vault(path) => format!("vault:\"{}\"", path),
666            SecretRef::File(path) => format!("file:\"{}\"", path),
667        }
668    }
669    fn print_expression(&mut self, expr: &Expression) -> String {
670        match expr {
671            Expression::String(s) => format!("\"{}\"", s),
672            Expression::Number(n) => format!("{}", n),
673            Expression::Bool(b) => format!("{}", b),
674            Expression::Duration(d) => {
675                format!(
676                    "{}{}", d.value, match d.unit { TimeUnit::Seconds => "s",
677                    TimeUnit::Minutes => "m", TimeUnit::Hours => "h", TimeUnit::Days =>
678                    "d", }
679                )
680            }
681            Expression::Variable(v) => format!("${}", v),
682            Expression::Reference(r) => format!("@{}", r),
683            Expression::IndexedReference(file, key) => format!("@{}[{}]", file, key),
684            Expression::Identifier(i) => i.clone(),
685            Expression::Pipeline(stages) => stages.join(" -> "),
686            Expression::Array(items) => {
687                format!(
688                    "[{}]", items.iter().map(| i | self.print_expression(i)).collect::<
689                    Vec < _ >> ().join(", ")
690                )
691            }
692            Expression::Object(map) => {
693                let items = map
694                    .iter()
695                    .map(|(k, v)| format!("{} = {}", k, self.print_expression(v)))
696                    .collect::<Vec<_>>()
697                    .join(", ");
698                format!("{{ {} }}", items)
699            }
700            _ => "...".to_string(),
701        }
702    }
703}
704impl Expression {
705    pub fn as_string(&self) -> Option<String> {
706        match self {
707            Expression::String(s) => Some(s.clone()),
708            Expression::Identifier(s) => Some(s.clone()),
709            _ => None,
710        }
711    }
712    pub fn as_number(&self) -> Option<f64> {
713        match self {
714            Expression::Number(n) => Some(*n),
715            _ => None,
716        }
717    }
718    pub fn as_bool(&self) -> Option<bool> {
719        match self {
720            Expression::Bool(b) => Some(*b),
721            _ => None,
722        }
723    }
724    pub fn as_array(&self) -> Option<Vec<Expression>> {
725        match self {
726            Expression::Array(arr) => Some(arr.clone()),
727            _ => None,
728        }
729    }
730    pub fn as_object(&self) -> Option<&HashMap<String, Expression>> {
731        match self {
732            Expression::Object(map) => Some(map),
733            _ => None,
734        }
735    }
736    pub fn to_value(&self) -> Value {
737        match self {
738            Expression::String(s) => Value::String(s.clone()),
739            Expression::Number(n) => Value::Number(*n),
740            Expression::Bool(b) => Value::Bool(*b),
741            Expression::Duration(d) => Value::Duration(d.clone()),
742            Expression::Array(arr) => {
743                Value::Array(arr.iter().map(|e| e.to_value()).collect())
744            }
745            Expression::Object(map) => {
746                Value::Object(
747                    map.iter().map(|(k, v)| (k.clone(), v.to_value())).collect(),
748                )
749            }
750            Expression::Variable(v) => Value::Reference(format!("${}", v)),
751            Expression::Reference(r) => Value::Reference(format!("@{}", r)),
752            Expression::IndexedReference(file, key) => {
753                Value::Reference(format!("@{}[{}]", file, key))
754            }
755            Expression::Identifier(i) => Value::String(i.clone()),
756            _ => Value::String("".to_string()),
757        }
758    }
759}
760
761// Generic section declaration for dynamic sections
762#[derive(Debug, Clone)]
763pub struct SectionDecl {
764    pub name: String,
765    pub properties: HashMap<String, Expression>,
766}
767
768#[allow(dead_code)]
769pub struct AstBuilder {
770    ast: HelixAst,
771}
772#[allow(dead_code)]
773impl AstBuilder {
774    pub fn new() -> Self {
775        AstBuilder { ast: HelixAst::new() }
776    }
777    pub fn add_project(
778        mut self,
779        name: String,
780        properties: HashMap<String, Expression>,
781    ) -> Self {
782        self.ast.add_declaration(Declaration::Project(ProjectDecl { name, properties }));
783        self
784    }
785    pub fn add_agent(mut self, agent: AgentDecl) -> Self {
786        self.ast.add_declaration(Declaration::Agent(agent));
787        self
788    }
789    pub fn add_workflow(mut self, workflow: WorkflowDecl) -> Self {
790        self.ast.add_declaration(Declaration::Workflow(workflow));
791        self
792    }
793    pub fn add_context(mut self, context: ContextDecl) -> Self {
794        self.ast.add_declaration(Declaration::Context(context));
795        self
796    }
797    pub fn build(self) -> HelixAst {
798        self.ast
799    }
800}