1use 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 Ok("interface{}".to_string())
60 }
61
62 Type::TaggedUnion { .. } => {
63 Ok("interface{}".to_string())
65 }
66
67 Type::Reference(name) => Ok(name.clone()),
68
69 Type::Contract { .. } => {
70 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 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 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 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 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 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}