Skip to main content

pflow_dsl/
codegen.rs

1//! Code generation from AST to Rust source code.
2
3use crate::ast::*;
4
5/// Generates Rust source code that constructs the schema.
6pub fn generate_rust(node: &SchemaNode, module_name: &str, fn_name: &str) -> Result<String, String> {
7    let mut b = String::new();
8
9    b.push_str(&format!("//! Generated schema for {}.\n\n", node.name));
10    b.push_str("use pflow_tokenmodel::schema::*;\n\n");
11
12    b.push_str(&format!(
13        "/// {} creates a schema from DSL definition.\n",
14        fn_name
15    ));
16    b.push_str(&format!("pub fn {}() -> Schema {{\n", fn_name));
17
18    b.push_str(&format!(
19        "    let mut schema = Schema::new({:?});\n",
20        node.name
21    ));
22    if !node.version.is_empty() {
23        b.push_str(&format!(
24            "    schema.version = {:?}.into();\n",
25            node.version
26        ));
27    }
28    b.push('\n');
29
30    // States
31    if !node.states.is_empty() {
32        b.push_str("    // States\n");
33        for s in &node.states {
34            b.push_str("    schema.add_state(State {\n");
35            b.push_str(&format!("        id: {:?}.into(),\n", s.id));
36            if s.kind == "token" {
37                b.push_str("        kind: Kind::Token,\n");
38            } else {
39                b.push_str("        kind: Kind::Data,\n");
40            }
41            if !s.typ.is_empty() {
42                b.push_str(&format!("        typ: {:?}.into(),\n", s.typ));
43            } else {
44                b.push_str("        typ: String::new(),\n");
45            }
46            match &s.initial {
47                Some(InitialValue::Int(n)) => {
48                    b.push_str(&format!(
49                        "        initial: Some(serde_json::Value::Number({}.into())),\n",
50                        n
51                    ));
52                }
53                Some(InitialValue::Str(v)) => {
54                    b.push_str(&format!(
55                        "        initial: Some(serde_json::Value::String({:?}.into())),\n",
56                        v
57                    ));
58                }
59                _ => {
60                    b.push_str("        initial: None,\n");
61                }
62            }
63            b.push_str(&format!("        exported: {},\n", s.exported));
64            b.push_str("    });\n");
65        }
66        b.push('\n');
67    }
68
69    // Actions
70    if !node.actions.is_empty() {
71        b.push_str("    // Actions\n");
72        for a in &node.actions {
73            b.push_str("    schema.add_action(Action {\n");
74            b.push_str(&format!("        id: {:?}.into(),\n", a.id));
75            if !a.guard.is_empty() {
76                b.push_str(&format!("        guard: {:?}.into(),\n", a.guard));
77            } else {
78                b.push_str("        guard: String::new(),\n");
79            }
80            b.push_str("        event_id: String::new(),\n");
81            b.push_str("        event_bindings: None,\n");
82            b.push_str("    });\n");
83        }
84        b.push('\n');
85    }
86
87    // Arcs
88    if !node.arcs.is_empty() {
89        b.push_str("    // Arcs\n");
90        for a in &node.arcs {
91            b.push_str("    schema.add_arc(Arc {\n");
92            b.push_str(&format!("        source: {:?}.into(),\n", a.source));
93            b.push_str(&format!("        target: {:?}.into(),\n", a.target));
94            if !a.keys.is_empty() {
95                let keys: Vec<String> = a.keys.iter().map(|k| format!("{:?}.into()", k)).collect();
96                b.push_str(&format!("        keys: vec![{}],\n", keys.join(", ")));
97            } else {
98                b.push_str("        keys: vec![],\n");
99            }
100            if !a.value.is_empty() {
101                b.push_str(&format!("        value: {:?}.into(),\n", a.value));
102            } else {
103                b.push_str("        value: String::new(),\n");
104            }
105            b.push_str("    });\n");
106        }
107        b.push('\n');
108    }
109
110    // Constraints
111    if !node.constraints.is_empty() {
112        b.push_str("    // Constraints\n");
113        for c in &node.constraints {
114            b.push_str("    schema.add_constraint(Constraint {\n");
115            b.push_str(&format!("        id: {:?}.into(),\n", c.id));
116            b.push_str(&format!("        expr: {:?}.into(),\n", c.expr));
117            b.push_str("    });\n");
118        }
119        b.push('\n');
120    }
121
122    b.push_str("    schema\n");
123    b.push_str("}\n");
124
125    let _ = module_name; // reserved for future use
126    Ok(b)
127}
128
129/// Parse DSL input and generate Rust code.
130pub fn generate_rust_from_dsl(
131    input: &str,
132    module_name: &str,
133    fn_name: &str,
134) -> Result<String, String> {
135    let node = crate::parser::parse(input)?;
136    generate_rust(&node, module_name, fn_name)
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_codegen_basic() {
145        let input = r#"(schema test
146  (version v1.0.0)
147  (states
148    (state count :kind token :initial 5)
149  )
150  (actions
151    (action inc)
152  )
153  (arcs
154    (arc inc -> count)
155  )
156)"#;
157
158        let code = generate_rust_from_dsl(input, "test", "make_schema").unwrap();
159        assert!(code.contains("pub fn make_schema()"));
160        assert!(code.contains("Schema::new"));
161        assert!(code.contains("Kind::Token"));
162        assert!(code.contains("\"count\""));
163    }
164}