A human-friendly DSL for ATProto Lexicons
27
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 229 lines 8.6 kB view raw
1use mlf_codegen::{CodeGenerator, GeneratorContext, register_generator}; 2use mlf_lang::ast::*; 3use std::fmt::Write; 4 5pub struct GoGenerator; 6 7impl GoGenerator { 8 pub const NAME: &'static str = "go"; 9 10 fn generate_type( 11 &self, 12 ty: &Type, 13 optional: bool, 14 ctx: &GeneratorContext, 15 ) -> Result<String, String> { 16 let base_type = match ty { 17 Type::Primitive { kind, .. } => match kind { 18 PrimitiveType::Null => "interface{}", 19 PrimitiveType::Boolean => "bool", 20 PrimitiveType::Integer => "int64", 21 PrimitiveType::String => "string", 22 PrimitiveType::Bytes => "[]byte", 23 PrimitiveType::Blob => "[]byte", // Annotation idea: @goType("custom.BlobType") 24 } 25 .to_string(), 26 Type::Reference { path, .. } => { 27 let path_str = path.to_string(); 28 match path_str.as_str() { 29 // Map standard library types 30 "Datetime" => "string".to_string(), // ISO 8601 string 31 "Did" | "AtUri" | "Cid" | "AtIdentifier" | "Handle" | "Nsid" | "Tid" 32 | "RecordKey" | "Uri" | "Language" => "string".to_string(), 33 _ => { 34 // Local reference 35 path.segments.last().unwrap().name.clone() 36 } 37 } 38 } 39 Type::Array { inner, .. } => { 40 let inner_type = self.generate_type(inner, false, ctx)?; 41 format!("[]{}", inner_type) 42 } 43 Type::Union { .. } => { 44 // Go doesn't have union types, use interface{} 45 // Annotation idea: @goUnion to generate type switch helpers 46 "interface{}".to_string() 47 } 48 Type::Object { fields, .. } => { 49 let mut obj = String::from("struct {\n"); 50 for field in fields { 51 let field_name = self.capitalize(&field.name.name); 52 let field_type = self.generate_type(&field.ty, field.optional, ctx)?; 53 let json_name = field.name.name.clone(); 54 55 if !field.docs.is_empty() { 56 write!(obj, "\t\t// {}\n", field.docs[0].text).unwrap(); 57 } 58 write!( 59 obj, 60 "\t\t{} {} `json:\"{}", 61 field_name, field_type, json_name 62 ) 63 .unwrap(); 64 if field.optional { 65 write!(obj, ",omitempty").unwrap(); 66 } 67 writeln!(obj, "\"`").unwrap(); 68 } 69 obj.push_str("\t}"); 70 obj 71 } 72 Type::Parenthesized { inner, .. } => { 73 return self.generate_type(inner, optional, ctx); 74 } 75 Type::Constrained { base, .. } => { 76 return self.generate_type(base, optional, ctx); 77 } 78 Type::Unknown { .. } => "interface{}".to_string(), 79 }; 80 81 // For optional fields, use pointer types 82 if optional { 83 Ok(format!("*{}", base_type)) 84 } else { 85 Ok(base_type) 86 } 87 } 88 89 fn capitalize(&self, s: &str) -> String { 90 let mut chars = s.chars(); 91 match chars.next() { 92 None => String::new(), 93 Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(), 94 } 95 } 96 97 fn generate_doc_comment(&self, docs: &[DocComment]) -> String { 98 if docs.is_empty() { 99 return String::new(); 100 } 101 102 let mut result = String::new(); 103 for doc in docs { 104 result.push_str("// "); 105 result.push_str(&doc.text); 106 result.push('\n'); 107 } 108 result 109 } 110} 111 112impl CodeGenerator for GoGenerator { 113 fn name(&self) -> &'static str { 114 Self::NAME 115 } 116 117 fn description(&self) -> &'static str { 118 "Generate Go structs and client code" 119 } 120 121 fn file_extension(&self) -> &'static str { 122 ".go" 123 } 124 125 fn generate(&self, ctx: &GeneratorContext) -> Result<String, String> { 126 let mut output = String::new(); 127 128 // Header comment 129 writeln!(output, "// Generated from {}", ctx.namespace).unwrap(); 130 writeln!(output, "// Do not edit manually").unwrap(); 131 writeln!(output).unwrap(); 132 133 // Package name (use last segment of namespace) 134 let package_name = ctx.namespace.split('.').last().unwrap_or("lexicon"); 135 writeln!(output, "package {}\n", package_name).unwrap(); 136 137 // Generate code for each item 138 for item in &ctx.lexicon.items { 139 match item { 140 Item::Record(record) => { 141 output.push_str(&self.generate_doc_comment(&record.docs)); 142 writeln!( 143 output, 144 "type {} struct {{", 145 self.capitalize(&record.name.name) 146 ) 147 .unwrap(); 148 149 for field in &record.fields { 150 let field_name = self.capitalize(&field.name.name); 151 let field_type = self.generate_type(&field.ty, field.optional, ctx)?; 152 let json_name = &field.name.name; 153 154 if !field.docs.is_empty() { 155 writeln!(output, "\t// {}", field.docs[0].text).unwrap(); 156 } 157 write!( 158 output, 159 "\t{} {} `json:\"{}\"", 160 field_name, field_type, json_name 161 ) 162 .unwrap(); 163 if field.optional { 164 write!(output, ",omitempty").unwrap(); 165 } 166 writeln!(output, "`").unwrap(); 167 } 168 169 writeln!(output, "}}\n").unwrap(); 170 } 171 Item::DefType(def) => { 172 output.push_str(&self.generate_doc_comment(&def.docs)); 173 let type_name = self.capitalize(&def.name.name); 174 175 match &def.ty { 176 Type::Object { .. } => { 177 // Object types become structs 178 writeln!( 179 output, 180 "type {} {}\n", 181 type_name, 182 self.generate_type(&def.ty, false, ctx)? 183 ) 184 .unwrap(); 185 } 186 _ => { 187 // Other types become type aliases 188 writeln!( 189 output, 190 "type {} {}\n", 191 type_name, 192 self.generate_type(&def.ty, false, ctx)? 193 ) 194 .unwrap(); 195 } 196 } 197 } 198 Item::InlineType(inline) => { 199 output.push_str(&self.generate_doc_comment(&inline.docs)); 200 writeln!( 201 output, 202 "type {} {}\n", 203 self.capitalize(&inline.name.name), 204 self.generate_type(&inline.ty, false, ctx)? 205 ) 206 .unwrap(); 207 } 208 Item::Token(token) => { 209 output.push_str(&self.generate_doc_comment(&token.docs)); 210 let const_name = token.name.name.to_uppercase(); 211 writeln!(output, "const {} = \"{}\"\n", const_name, token.name.name).unwrap(); 212 } 213 Item::Query(_) | Item::Procedure(_) | Item::Subscription(_) => { 214 // TODO: Generate client methods 215 } 216 Item::Use(_) | Item::SelfItem(_) => { 217 // Skip `use` statements and the `self { }` item — both 218 // are lexicon-level metadata, not types to emit. 219 } 220 } 221 } 222 223 Ok(output) 224 } 225} 226 227// Register the Go generator 228pub static GO_GENERATOR: GoGenerator = GoGenerator; 229register_generator!(GO_GENERATOR);