amalgam_codegen/
go.rs

1//! Go code generator
2
3use crate::{Codegen, CodegenError};
4use amalgam_core::{
5    types::{Field, Type},
6    IR,
7};
8use std::fmt::Write;
9
10pub struct GoCodegen {
11    indent_size: usize,
12}
13
14impl GoCodegen {
15    pub fn new() -> Self {
16        Self { indent_size: 4 }
17    }
18
19    fn indent(&self, level: usize) -> String {
20        " ".repeat(level * self.indent_size)
21    }
22
23    fn type_to_go(&self, ty: &Type) -> Result<String, CodegenError> {
24        match ty {
25            Type::String => Ok("string".to_string()),
26            Type::Number => Ok("float64".to_string()),
27            Type::Integer => Ok("int64".to_string()),
28            Type::Bool => Ok("bool".to_string()),
29            Type::Null => Ok("interface{}".to_string()),
30            Type::Any => Ok("interface{}".to_string()),
31
32            Type::Array(elem) => {
33                let elem_type = self.type_to_go(elem)?;
34                Ok(format!("[]{}", elem_type))
35            }
36
37            Type::Map { key, value } => {
38                let key_type = self.type_to_go(key)?;
39                let value_type = self.type_to_go(value)?;
40                Ok(format!("map[{}]{}", key_type, value_type))
41            }
42
43            Type::Optional(inner) => {
44                let inner_type = self.type_to_go(inner)?;
45                Ok(format!("*{}", inner_type))
46            }
47
48            Type::Record { fields, .. } => {
49                let mut result = String::from("struct {\n");
50                for (name, field) in fields {
51                    let field_str = self.field_to_go(name, field, 1)?;
52                    result.push_str(&field_str);
53                    result.push('\n');
54                }
55                result.push_str(&self.indent(0));
56                result.push('}');
57                Ok(result)
58            }
59
60            Type::Union(_) => {
61                // Go doesn't have union types, use interface{}
62                Ok("interface{}".to_string())
63            }
64
65            Type::TaggedUnion { .. } => {
66                // Would need to generate interface with type assertion
67                Ok("interface{}".to_string())
68            }
69
70            Type::Reference(name) => Ok(name.clone()),
71
72            Type::Contract { .. } => {
73                // Contracts become interfaces in Go
74                Ok("interface{}".to_string())
75            }
76        }
77    }
78
79    fn field_to_go(
80        &self,
81        name: &str,
82        field: &Field,
83        indent_level: usize,
84    ) -> Result<String, CodegenError> {
85        let indent = self.indent(indent_level);
86        let go_name = self.to_go_field_name(name);
87        let type_str = self.type_to_go(&field.ty)?;
88
89        let mut result = format!("{}{} {}", indent, go_name, type_str);
90
91        // Add JSON tag
92        let mut tags = Vec::new();
93        tags.push(format!("json:\"{}\"", name));
94
95        if !field.required {
96            tags[0] = format!("json:\"{},omitempty\"", name);
97        }
98
99        if !tags.is_empty() {
100            result.push_str(&format!(" `{}`", tags.join(" ")));
101        }
102
103        if let Some(desc) = &field.description {
104            result = format!("{}// {}\n{}", indent, desc, result);
105        }
106
107        Ok(result)
108    }
109
110    fn to_go_field_name(&self, name: &str) -> String {
111        // Convert to Go public field name (capitalize first letter)
112        let mut chars = name.chars();
113        match chars.next() {
114            None => String::new(),
115            Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
116        }
117    }
118}
119
120impl Default for GoCodegen {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126impl Codegen for GoCodegen {
127    fn generate(&mut self, ir: &IR) -> Result<String, CodegenError> {
128        let mut output = String::new();
129
130        for module in &ir.modules {
131            writeln!(output, "// Code generated by amalgam. DO NOT EDIT.")
132                .map_err(|e| CodegenError::Generation(e.to_string()))?;
133            writeln!(output).map_err(|e| CodegenError::Generation(e.to_string()))?;
134
135            writeln!(output, "package {}", module.name)
136                .map_err(|e| CodegenError::Generation(e.to_string()))?;
137            writeln!(output).map_err(|e| CodegenError::Generation(e.to_string()))?;
138
139            // Generate imports
140            if !module.imports.is_empty() {
141                writeln!(output, "import (")
142                    .map_err(|e| CodegenError::Generation(e.to_string()))?;
143                for import in &module.imports {
144                    writeln!(output, "{}\"{}\"", self.indent(1), import.path)
145                        .map_err(|e| CodegenError::Generation(e.to_string()))?;
146                }
147                writeln!(output, ")").map_err(|e| CodegenError::Generation(e.to_string()))?;
148                writeln!(output).map_err(|e| CodegenError::Generation(e.to_string()))?;
149            }
150
151            // Generate type definitions
152            for type_def in &module.types {
153                if let Some(doc) = &type_def.documentation {
154                    writeln!(output, "// {}", doc)
155                        .map_err(|e| CodegenError::Generation(e.to_string()))?;
156                }
157
158                let type_str = self.type_to_go(&type_def.ty)?;
159                writeln!(output, "type {} {}", type_def.name, type_str)
160                    .map_err(|e| CodegenError::Generation(e.to_string()))?;
161                writeln!(output).map_err(|e| CodegenError::Generation(e.to_string()))?;
162            }
163
164            // Generate constants
165            if !module.constants.is_empty() {
166                writeln!(output, "const (").map_err(|e| CodegenError::Generation(e.to_string()))?;
167                for constant in &module.constants {
168                    if let Some(doc) = &constant.documentation {
169                        writeln!(output, "{}// {}", self.indent(1), doc)
170                            .map_err(|e| CodegenError::Generation(e.to_string()))?;
171                    }
172
173                    writeln!(
174                        output,
175                        "{}{} = {}",
176                        self.indent(1),
177                        constant.name,
178                        serde_json::to_string(&constant.value)
179                            .unwrap_or_else(|_| "nil".to_string())
180                    )
181                    .map_err(|e| CodegenError::Generation(e.to_string()))?;
182                }
183                writeln!(output, ")").map_err(|e| CodegenError::Generation(e.to_string()))?;
184            }
185        }
186
187        Ok(output)
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_simple_type_generation() {
197        let codegen = GoCodegen::new();
198
199        assert_eq!(codegen.type_to_go(&Type::String).unwrap(), "string");
200        assert_eq!(codegen.type_to_go(&Type::Number).unwrap(), "float64");
201        assert_eq!(codegen.type_to_go(&Type::Integer).unwrap(), "int64");
202        assert_eq!(codegen.type_to_go(&Type::Bool).unwrap(), "bool");
203        assert_eq!(codegen.type_to_go(&Type::Any).unwrap(), "interface{}");
204    }
205
206    #[test]
207    fn test_array_generation() {
208        let codegen = GoCodegen::new();
209        let array_type = Type::Array(Box::new(Type::String));
210        assert_eq!(codegen.type_to_go(&array_type).unwrap(), "[]string");
211    }
212
213    #[test]
214    fn test_map_generation() {
215        let codegen = GoCodegen::new();
216        let map_type = Type::Map {
217            key: Box::new(Type::String),
218            value: Box::new(Type::Integer),
219        };
220        assert_eq!(codegen.type_to_go(&map_type).unwrap(), "map[string]int64");
221    }
222}