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