use mlf_codegen::{CodeGenerator, GeneratorContext, register_generator}; use mlf_lang::ast::*; use std::fmt::Write; pub struct GoGenerator; impl GoGenerator { pub const NAME: &'static str = "go"; fn generate_type( &self, ty: &Type, optional: bool, ctx: &GeneratorContext, ) -> Result { let base_type = match ty { Type::Primitive { kind, .. } => match kind { PrimitiveType::Null => "interface{}", PrimitiveType::Boolean => "bool", PrimitiveType::Integer => "int64", PrimitiveType::String => "string", PrimitiveType::Bytes => "[]byte", PrimitiveType::Blob => "[]byte", // Annotation idea: @goType("custom.BlobType") } .to_string(), Type::Reference { path, .. } => { let path_str = path.to_string(); match path_str.as_str() { // Map standard library types "Datetime" => "string".to_string(), // ISO 8601 string "Did" | "AtUri" | "Cid" | "AtIdentifier" | "Handle" | "Nsid" | "Tid" | "RecordKey" | "Uri" | "Language" => "string".to_string(), _ => { // Local reference path.segments.last().unwrap().name.clone() } } } Type::Array { inner, .. } => { let inner_type = self.generate_type(inner, false, ctx)?; format!("[]{}", inner_type) } Type::Union { .. } => { // Go doesn't have union types, use interface{} // Annotation idea: @goUnion to generate type switch helpers "interface{}".to_string() } Type::Object { fields, .. } => { let mut obj = String::from("struct {\n"); for field in fields { let field_name = self.capitalize(&field.name.name); let field_type = self.generate_type(&field.ty, field.optional, ctx)?; let json_name = field.name.name.clone(); if !field.docs.is_empty() { write!(obj, "\t\t// {}\n", field.docs[0].text).unwrap(); } write!( obj, "\t\t{} {} `json:\"{}", field_name, field_type, json_name ) .unwrap(); if field.optional { write!(obj, ",omitempty").unwrap(); } writeln!(obj, "\"`").unwrap(); } obj.push_str("\t}"); obj } Type::Parenthesized { inner, .. } => { return self.generate_type(inner, optional, ctx); } Type::Constrained { base, .. } => { return self.generate_type(base, optional, ctx); } Type::Unknown { .. } => "interface{}".to_string(), }; // For optional fields, use pointer types if optional { Ok(format!("*{}", base_type)) } else { Ok(base_type) } } fn capitalize(&self, s: &str) -> String { let mut chars = s.chars(); match chars.next() { None => String::new(), Some(f) => f.to_uppercase().collect::() + chars.as_str(), } } fn generate_doc_comment(&self, docs: &[DocComment]) -> String { if docs.is_empty() { return String::new(); } let mut result = String::new(); for doc in docs { result.push_str("// "); result.push_str(&doc.text); result.push('\n'); } result } } impl CodeGenerator for GoGenerator { fn name(&self) -> &'static str { Self::NAME } fn description(&self) -> &'static str { "Generate Go structs and client code" } fn file_extension(&self) -> &'static str { ".go" } fn generate(&self, ctx: &GeneratorContext) -> Result { let mut output = String::new(); // Header comment writeln!(output, "// Generated from {}", ctx.namespace).unwrap(); writeln!(output, "// Do not edit manually").unwrap(); writeln!(output).unwrap(); // Package name (use last segment of namespace) let package_name = ctx.namespace.split('.').last().unwrap_or("lexicon"); writeln!(output, "package {}\n", package_name).unwrap(); // Generate code for each item for item in &ctx.lexicon.items { match item { Item::Record(record) => { output.push_str(&self.generate_doc_comment(&record.docs)); writeln!( output, "type {} struct {{", self.capitalize(&record.name.name) ) .unwrap(); for field in &record.fields { let field_name = self.capitalize(&field.name.name); let field_type = self.generate_type(&field.ty, field.optional, ctx)?; let json_name = &field.name.name; if !field.docs.is_empty() { writeln!(output, "\t// {}", field.docs[0].text).unwrap(); } write!( output, "\t{} {} `json:\"{}\"", field_name, field_type, json_name ) .unwrap(); if field.optional { write!(output, ",omitempty").unwrap(); } writeln!(output, "`").unwrap(); } writeln!(output, "}}\n").unwrap(); } Item::DefType(def) => { output.push_str(&self.generate_doc_comment(&def.docs)); let type_name = self.capitalize(&def.name.name); match &def.ty { Type::Object { .. } => { // Object types become structs writeln!( output, "type {} {}\n", type_name, self.generate_type(&def.ty, false, ctx)? ) .unwrap(); } _ => { // Other types become type aliases writeln!( output, "type {} {}\n", type_name, self.generate_type(&def.ty, false, ctx)? ) .unwrap(); } } } Item::InlineType(inline) => { output.push_str(&self.generate_doc_comment(&inline.docs)); writeln!( output, "type {} {}\n", self.capitalize(&inline.name.name), self.generate_type(&inline.ty, false, ctx)? ) .unwrap(); } Item::Token(token) => { output.push_str(&self.generate_doc_comment(&token.docs)); let const_name = token.name.name.to_uppercase(); writeln!(output, "const {} = \"{}\"\n", const_name, token.name.name).unwrap(); } Item::Query(_) | Item::Procedure(_) | Item::Subscription(_) => { // TODO: Generate client methods } Item::Use(_) | Item::SelfItem(_) => { // Skip `use` statements and the `self { }` item — both // are lexicon-level metadata, not types to emit. } } } Ok(output) } } // Register the Go generator pub static GO_GENERATOR: GoGenerator = GoGenerator; register_generator!(GO_GENERATOR);