amalgam_codegen/
go.rs

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