Skip to main content

tensorlogic_adapters/
codegen.rs

1//! Code generation from schemas.
2//!
3//! This module provides utilities for generating code in various target languages
4//! from TensorLogic schemas, enabling type-safe programming and API generation.
5//!
6//! Supported targets:
7//! - Rust: Type definitions with bounds checking
8//! - GraphQL: Schema definitions for API development
9//! - TypeScript: Interface and type definitions
10//! - Python: Type stubs and PyO3 bindings
11
12use std::fmt::Write as FmtWrite;
13
14use crate::{DomainInfo, PredicateInfo, SymbolTable};
15
16/// Code generator for Rust types from schemas.
17pub struct RustCodegen {
18    /// Module name for generated code
19    module_name: String,
20    /// Whether to derive common traits
21    derive_common: bool,
22    /// Whether to include documentation comments
23    include_docs: bool,
24}
25
26impl RustCodegen {
27    /// Create a new Rust code generator.
28    pub fn new(module_name: impl Into<String>) -> Self {
29        Self {
30            module_name: module_name.into(),
31            derive_common: true,
32            include_docs: true,
33        }
34    }
35
36    /// Set whether to derive common traits (Clone, Debug, etc.).
37    pub fn with_common_derives(mut self, enable: bool) -> Self {
38        self.derive_common = enable;
39        self
40    }
41
42    /// Set whether to include documentation comments.
43    pub fn with_docs(mut self, enable: bool) -> Self {
44        self.include_docs = enable;
45        self
46    }
47
48    /// Generate complete Rust module from a symbol table.
49    pub fn generate(&self, table: &SymbolTable) -> String {
50        let mut code = String::new();
51
52        // Module header
53        writeln!(code, "//! Generated from TensorLogic schema.").unwrap();
54        writeln!(code, "//! Module: {}", self.module_name).unwrap();
55        writeln!(code, "//!").unwrap();
56        writeln!(code, "//! This code was automatically generated.").unwrap();
57        writeln!(code, "//! DO NOT EDIT MANUALLY.").unwrap();
58        writeln!(code).unwrap();
59
60        // Use statements
61        writeln!(code, "#![allow(dead_code)]").unwrap();
62        writeln!(code).unwrap();
63
64        // Generate domain types
65        writeln!(code, "// ============================================").unwrap();
66        writeln!(code, "// Domain Types").unwrap();
67        writeln!(code, "// ============================================").unwrap();
68        writeln!(code).unwrap();
69
70        for domain in table.domains.values() {
71            self.generate_domain(&mut code, domain);
72            writeln!(code).unwrap();
73        }
74
75        // Generate predicate types
76        writeln!(code, "// ============================================").unwrap();
77        writeln!(code, "// Predicate Types").unwrap();
78        writeln!(code, "// ============================================").unwrap();
79        writeln!(code).unwrap();
80
81        for predicate in table.predicates.values() {
82            self.generate_predicate(&mut code, predicate, table);
83            writeln!(code).unwrap();
84        }
85
86        // Generate schema metadata type
87        writeln!(code, "// ============================================").unwrap();
88        writeln!(code, "// Schema Metadata").unwrap();
89        writeln!(code, "// ============================================").unwrap();
90        writeln!(code).unwrap();
91        self.generate_schema_metadata(&mut code, table);
92
93        code
94    }
95
96    /// Generate domain type.
97    fn generate_domain(&self, code: &mut String, domain: &DomainInfo) {
98        if self.include_docs {
99            if let Some(ref desc) = domain.description {
100                writeln!(code, "/// {}", desc).unwrap();
101            } else {
102                writeln!(code, "/// Domain: {}", domain.name).unwrap();
103            }
104            writeln!(code, "///").unwrap();
105            writeln!(code, "/// Cardinality: {}", domain.cardinality).unwrap();
106        }
107
108        if self.derive_common {
109            writeln!(code, "#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]").unwrap();
110        }
111
112        let type_name = Self::to_type_name(&domain.name);
113        writeln!(code, "pub struct {}(pub usize);", type_name).unwrap();
114        writeln!(code).unwrap();
115
116        // Generate constructor and accessors
117        writeln!(code, "impl {} {{", type_name).unwrap();
118        writeln!(
119            code,
120            "    /// Maximum valid ID for this domain (exclusive)."
121        )
122        .unwrap();
123        writeln!(
124            code,
125            "    pub const CARDINALITY: usize = {};",
126            domain.cardinality
127        )
128        .unwrap();
129        writeln!(code).unwrap();
130
131        writeln!(code, "    /// Create a new {} instance.", type_name).unwrap();
132        writeln!(code, "    ///").unwrap();
133        writeln!(code, "    /// # Panics").unwrap();
134        writeln!(code, "    ///").unwrap();
135        writeln!(code, "    /// Panics if `id >= {}`.", domain.cardinality).unwrap();
136        writeln!(code, "    pub fn new(id: usize) -> Self {{").unwrap();
137        writeln!(code, "        assert!(id < Self::CARDINALITY, \"ID {{}} exceeds cardinality {{}}\", id, Self::CARDINALITY);", ).unwrap();
138        writeln!(code, "        Self(id)").unwrap();
139        writeln!(code, "    }}").unwrap();
140        writeln!(code).unwrap();
141
142        writeln!(
143            code,
144            "    /// Create a new {} instance without bounds checking.",
145            type_name
146        )
147        .unwrap();
148        writeln!(code, "    ///").unwrap();
149        writeln!(code, "    /// # Safety").unwrap();
150        writeln!(code, "    ///").unwrap();
151        writeln!(
152            code,
153            "    /// Caller must ensure `id < {}`.",
154            domain.cardinality
155        )
156        .unwrap();
157        writeln!(
158            code,
159            "    pub unsafe fn new_unchecked(id: usize) -> Self {{"
160        )
161        .unwrap();
162        writeln!(code, "        Self(id)").unwrap();
163        writeln!(code, "    }}").unwrap();
164        writeln!(code).unwrap();
165
166        writeln!(code, "    /// Get the underlying ID.").unwrap();
167        writeln!(code, "    pub fn id(&self) -> usize {{").unwrap();
168        writeln!(code, "        self.0").unwrap();
169        writeln!(code, "    }}").unwrap();
170
171        writeln!(code, "}}").unwrap();
172    }
173
174    /// Generate predicate type.
175    fn generate_predicate(
176        &self,
177        code: &mut String,
178        predicate: &PredicateInfo,
179        _table: &SymbolTable,
180    ) {
181        if self.include_docs {
182            if let Some(ref desc) = predicate.description {
183                writeln!(code, "/// {}", desc).unwrap();
184            } else {
185                writeln!(code, "/// Predicate: {}", predicate.name).unwrap();
186            }
187            writeln!(code, "///").unwrap();
188            writeln!(code, "/// Arity: {}", predicate.arg_domains.len()).unwrap();
189
190            if let Some(ref constraints) = predicate.constraints {
191                if !constraints.properties.is_empty() {
192                    writeln!(code, "///").unwrap();
193                    writeln!(code, "/// Properties:").unwrap();
194                    for prop in &constraints.properties {
195                        writeln!(code, "/// - {:?}", prop).unwrap();
196                    }
197                }
198            }
199        }
200
201        if self.derive_common {
202            writeln!(code, "#[derive(Clone, Debug, PartialEq, Eq, Hash)]").unwrap();
203        }
204
205        let type_name = Self::to_type_name(&predicate.name);
206
207        // Generate struct with typed fields
208        if predicate.arg_domains.is_empty() {
209            // Nullary predicate
210            writeln!(code, "pub struct {};", type_name).unwrap();
211        } else if predicate.arg_domains.len() == 1 {
212            // Unary predicate
213            let domain_type = Self::to_type_name(&predicate.arg_domains[0]);
214            writeln!(code, "pub struct {}(pub {});", type_name, domain_type).unwrap();
215        } else {
216            // N-ary predicate - use tuple struct
217            write!(code, "pub struct {}(", type_name).unwrap();
218            for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
219                if i > 0 {
220                    write!(code, ", ").unwrap();
221                }
222                write!(code, "pub {}", Self::to_type_name(domain_name)).unwrap();
223            }
224            writeln!(code, ");").unwrap();
225        }
226
227        writeln!(code).unwrap();
228
229        // Generate constructor and accessors
230        writeln!(code, "impl {} {{", type_name).unwrap();
231
232        if !predicate.arg_domains.is_empty() {
233            // Constructor
234            writeln!(code, "    /// Create a new {} instance.", type_name).unwrap();
235            write!(code, "    pub fn new(").unwrap();
236            for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
237                if i > 0 {
238                    write!(code, ", ").unwrap();
239                }
240                write!(code, "arg{}: {}", i, Self::to_type_name(domain_name)).unwrap();
241            }
242            writeln!(code, ") -> Self {{").unwrap();
243
244            if predicate.arg_domains.len() == 1 {
245                writeln!(code, "        Self(arg0)").unwrap();
246            } else {
247                write!(code, "        Self(").unwrap();
248                for i in 0..predicate.arg_domains.len() {
249                    if i > 0 {
250                        write!(code, ", ").unwrap();
251                    }
252                    write!(code, "arg{}", i).unwrap();
253                }
254                writeln!(code, ")").unwrap();
255            }
256            writeln!(code, "    }}").unwrap();
257            writeln!(code).unwrap();
258
259            // Accessor methods
260            for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
261                writeln!(code, "    /// Get argument {}.", i).unwrap();
262                writeln!(
263                    code,
264                    "    pub fn arg{}(&self) -> {} {{",
265                    i,
266                    Self::to_type_name(domain_name)
267                )
268                .unwrap();
269                if predicate.arg_domains.len() == 1 {
270                    writeln!(code, "        self.0").unwrap();
271                } else {
272                    writeln!(code, "        self.{}", i).unwrap();
273                }
274                writeln!(code, "    }}").unwrap();
275                writeln!(code).unwrap();
276            }
277        }
278
279        writeln!(code, "}}").unwrap();
280    }
281
282    /// Generate schema metadata type.
283    fn generate_schema_metadata(&self, code: &mut String, table: &SymbolTable) {
284        writeln!(code, "/// Schema metadata and statistics.").unwrap();
285        writeln!(code, "pub struct SchemaMetadata;").unwrap();
286        writeln!(code).unwrap();
287
288        writeln!(code, "impl SchemaMetadata {{").unwrap();
289        writeln!(code, "    /// Number of domains in the schema.").unwrap();
290        writeln!(
291            code,
292            "    pub const DOMAIN_COUNT: usize = {};",
293            table.domains.len()
294        )
295        .unwrap();
296        writeln!(code).unwrap();
297
298        writeln!(code, "    /// Number of predicates in the schema.").unwrap();
299        writeln!(
300            code,
301            "    pub const PREDICATE_COUNT: usize = {};",
302            table.predicates.len()
303        )
304        .unwrap();
305        writeln!(code).unwrap();
306
307        writeln!(code, "    /// Total cardinality across all domains.").unwrap();
308        let total_card: usize = table.domains.values().map(|d| d.cardinality).sum();
309        writeln!(
310            code,
311            "    pub const TOTAL_CARDINALITY: usize = {};",
312            total_card
313        )
314        .unwrap();
315
316        writeln!(code, "}}").unwrap();
317    }
318
319    /// Convert a domain/predicate name to a Rust type name (PascalCase).
320    fn to_type_name(name: &str) -> String {
321        // Simple conversion: capitalize first letter of each word
322        name.split('_')
323            .map(|word| {
324                let mut chars = word.chars();
325                match chars.next() {
326                    None => String::new(),
327                    Some(first) => first.to_uppercase().chain(chars).collect(),
328                }
329            })
330            .collect()
331    }
332}
333
334/// Code generator for GraphQL schemas from symbol tables.
335///
336/// This generator creates GraphQL type definitions, queries, and mutations
337/// from TensorLogic schemas, enabling API development with type-safe schemas.
338pub struct GraphQLCodegen {
339    /// Schema name
340    schema_name: String,
341    /// Whether to include descriptions
342    include_descriptions: bool,
343    /// Whether to generate Query type
344    generate_queries: bool,
345    /// Whether to generate Mutation type
346    generate_mutations: bool,
347}
348
349impl GraphQLCodegen {
350    /// Create a new GraphQL code generator.
351    pub fn new(schema_name: impl Into<String>) -> Self {
352        Self {
353            schema_name: schema_name.into(),
354            include_descriptions: true,
355            generate_queries: true,
356            generate_mutations: false,
357        }
358    }
359
360    /// Set whether to include descriptions.
361    pub fn with_descriptions(mut self, enable: bool) -> Self {
362        self.include_descriptions = enable;
363        self
364    }
365
366    /// Set whether to generate Query type.
367    pub fn with_queries(mut self, enable: bool) -> Self {
368        self.generate_queries = enable;
369        self
370    }
371
372    /// Set whether to generate Mutation type.
373    pub fn with_mutations(mut self, enable: bool) -> Self {
374        self.generate_mutations = enable;
375        self
376    }
377
378    /// Generate complete GraphQL schema from a symbol table.
379    pub fn generate(&self, table: &SymbolTable) -> String {
380        let mut schema = String::new();
381
382        // Schema header
383        writeln!(schema, "# Generated GraphQL Schema").unwrap();
384        writeln!(schema, "# Schema: {}", self.schema_name).unwrap();
385        writeln!(schema, "#").unwrap();
386        writeln!(
387            schema,
388            "# This schema was automatically generated from TensorLogic."
389        )
390        .unwrap();
391        writeln!(schema, "# DO NOT EDIT MANUALLY.").unwrap();
392        writeln!(schema).unwrap();
393
394        // Generate domain types
395        writeln!(schema, "# ==========================================").unwrap();
396        writeln!(schema, "# Domain Types").unwrap();
397        writeln!(schema, "# ==========================================").unwrap();
398        writeln!(schema).unwrap();
399
400        for domain in table.domains.values() {
401            self.generate_domain_type(&mut schema, domain);
402            writeln!(schema).unwrap();
403        }
404
405        // Generate predicate types
406        writeln!(schema, "# ==========================================").unwrap();
407        writeln!(schema, "# Predicate Types").unwrap();
408        writeln!(schema, "# ==========================================").unwrap();
409        writeln!(schema).unwrap();
410
411        for predicate in table.predicates.values() {
412            self.generate_predicate_type(&mut schema, predicate, table);
413            writeln!(schema).unwrap();
414        }
415
416        // Generate Query type
417        if self.generate_queries {
418            writeln!(schema, "# ==========================================").unwrap();
419            writeln!(schema, "# Query Operations").unwrap();
420            writeln!(schema, "# ==========================================").unwrap();
421            writeln!(schema).unwrap();
422            self.generate_query_type(&mut schema, table);
423            writeln!(schema).unwrap();
424        }
425
426        // Generate Mutation type
427        if self.generate_mutations {
428            writeln!(schema, "# ==========================================").unwrap();
429            writeln!(schema, "# Mutation Operations").unwrap();
430            writeln!(schema, "# ==========================================").unwrap();
431            writeln!(schema).unwrap();
432            self.generate_mutation_type(&mut schema, table);
433            writeln!(schema).unwrap();
434        }
435
436        // Schema definition
437        writeln!(schema, "# ==========================================").unwrap();
438        writeln!(schema, "# Schema Definition").unwrap();
439        writeln!(schema, "# ==========================================").unwrap();
440        writeln!(schema).unwrap();
441        writeln!(schema, "schema {{").unwrap();
442        if self.generate_queries {
443            writeln!(schema, "  query: Query").unwrap();
444        }
445        if self.generate_mutations {
446            writeln!(schema, "  mutation: Mutation").unwrap();
447        }
448        writeln!(schema, "}}").unwrap();
449
450        schema
451    }
452
453    /// Generate GraphQL type for a domain.
454    fn generate_domain_type(&self, schema: &mut String, domain: &DomainInfo) {
455        let type_name = Self::to_graphql_type_name(&domain.name);
456
457        if self.include_descriptions {
458            if let Some(ref desc) = domain.description {
459                writeln!(schema, "\"\"\"\n{}\n\"\"\"", desc).unwrap();
460            } else {
461                writeln!(schema, "\"\"\"\nDomain: {}\n\"\"\"", domain.name).unwrap();
462            }
463        }
464
465        writeln!(schema, "type {} {{", type_name).unwrap();
466        writeln!(schema, "  \"Unique identifier\"").unwrap();
467        writeln!(schema, "  id: ID!").unwrap();
468        writeln!(
469            schema,
470            "  \"Integer index (0 to {})\"",
471            domain.cardinality - 1
472        )
473        .unwrap();
474        writeln!(schema, "  index: Int!").unwrap();
475        writeln!(schema, "}}").unwrap();
476    }
477
478    /// Generate GraphQL type for a predicate.
479    fn generate_predicate_type(
480        &self,
481        schema: &mut String,
482        predicate: &PredicateInfo,
483        _table: &SymbolTable,
484    ) {
485        let type_name = Self::to_graphql_type_name(&predicate.name);
486
487        if self.include_descriptions {
488            if let Some(ref desc) = predicate.description {
489                writeln!(schema, "\"\"\"\n{}\n\"\"\"", desc).unwrap();
490            } else {
491                writeln!(schema, "\"\"\"\nPredicate: {}\n\"\"\"", predicate.name).unwrap();
492            }
493        }
494
495        writeln!(schema, "type {} {{", type_name).unwrap();
496
497        // Add ID field
498        writeln!(schema, "  \"Unique identifier\"").unwrap();
499        writeln!(schema, "  id: ID!").unwrap();
500
501        // Add argument fields
502        for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
503            let field_name = format!("arg{}", i);
504            let field_type = Self::to_graphql_type_name(domain_name);
505            writeln!(schema, "  \"Argument {} of type {}\"", i, domain_name).unwrap();
506            writeln!(schema, "  {}: {}!", field_name, field_type).unwrap();
507        }
508
509        writeln!(schema, "}}").unwrap();
510    }
511
512    /// Generate Query type.
513    fn generate_query_type(&self, schema: &mut String, table: &SymbolTable) {
514        writeln!(schema, "\"\"\"").unwrap();
515        writeln!(schema, "Root query type for retrieving data").unwrap();
516        writeln!(schema, "\"\"\"").unwrap();
517        writeln!(schema, "type Query {{").unwrap();
518
519        // Domain queries
520        for domain in table.domains.values() {
521            let type_name = Self::to_graphql_type_name(&domain.name);
522            let field_name = Self::to_graphql_field_name(&domain.name);
523
524            writeln!(schema, "  \"Get {} by ID\"", domain.name).unwrap();
525            writeln!(schema, "  {}(id: ID!): {}", field_name, type_name).unwrap();
526            writeln!(schema).unwrap();
527
528            writeln!(schema, "  \"List all {}s\"", domain.name).unwrap();
529            writeln!(schema, "  {}s: [{}!]!", field_name, type_name).unwrap();
530            writeln!(schema).unwrap();
531        }
532
533        // Predicate queries
534        for predicate in table.predicates.values() {
535            let type_name = Self::to_graphql_type_name(&predicate.name);
536            let field_name = Self::to_graphql_field_name(&predicate.name);
537
538            writeln!(schema, "  \"Query {} predicate\"", predicate.name).unwrap();
539
540            // Build query with argument filters
541            write!(schema, "  {}(", field_name).unwrap();
542            for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
543                if i > 0 {
544                    write!(schema, ", ").unwrap();
545                }
546                let arg_type = Self::to_graphql_type_name(domain_name);
547                write!(schema, "arg{}: {}", i, arg_type).unwrap();
548            }
549            writeln!(schema, "): [{}!]!", type_name).unwrap();
550            writeln!(schema).unwrap();
551        }
552
553        writeln!(schema, "}}").unwrap();
554    }
555
556    /// Generate Mutation type.
557    fn generate_mutation_type(&self, schema: &mut String, table: &SymbolTable) {
558        writeln!(schema, "\"\"\"").unwrap();
559        writeln!(schema, "Root mutation type for modifying data").unwrap();
560        writeln!(schema, "\"\"\"").unwrap();
561        writeln!(schema, "type Mutation {{").unwrap();
562
563        // Predicate mutations (add/remove)
564        for predicate in table.predicates.values() {
565            let type_name = Self::to_graphql_type_name(&predicate.name);
566
567            // Add mutation
568            writeln!(schema, "  \"Add {} instance\"", predicate.name).unwrap();
569            write!(schema, "  add{}(", type_name).unwrap();
570            for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
571                if i > 0 {
572                    write!(schema, ", ").unwrap();
573                }
574                let arg_type = Self::to_graphql_type_name(domain_name);
575                write!(schema, "arg{}: {}!", i, arg_type).unwrap();
576            }
577            writeln!(schema, "): {}!", type_name).unwrap();
578            writeln!(schema).unwrap();
579
580            // Remove mutation
581            writeln!(schema, "  \"Remove {} instance\"", predicate.name).unwrap();
582            writeln!(schema, "  remove{}(id: ID!): Boolean!", type_name).unwrap();
583            writeln!(schema).unwrap();
584        }
585
586        writeln!(schema, "}}").unwrap();
587    }
588
589    /// Convert a name to GraphQL type name (PascalCase).
590    fn to_graphql_type_name(name: &str) -> String {
591        RustCodegen::to_type_name(name) // Reuse Rust converter
592    }
593
594    /// Convert a name to GraphQL field name (camelCase).
595    fn to_graphql_field_name(name: &str) -> String {
596        let parts: Vec<&str> = name.split('_').collect();
597        if parts.is_empty() {
598            return String::new();
599        }
600
601        let mut result = parts[0].to_lowercase();
602        for part in &parts[1..] {
603            if let Some(first_char) = part.chars().next() {
604                result.push_str(&first_char.to_uppercase().to_string());
605                result.push_str(&part[first_char.len_utf8()..]);
606            }
607        }
608        result
609    }
610}
611
612/// Code generator for TypeScript definitions from symbol tables.
613///
614/// This generator creates TypeScript interface and type definitions
615/// from TensorLogic schemas, enabling type-safe TypeScript development.
616pub struct TypeScriptCodegen {
617    /// Module name
618    module_name: String,
619    /// Whether to export types
620    export_types: bool,
621    /// Whether to include JSDoc comments
622    include_jsdoc: bool,
623    /// Whether to generate validation functions
624    generate_validators: bool,
625}
626
627impl TypeScriptCodegen {
628    /// Create a new TypeScript code generator.
629    pub fn new(module_name: impl Into<String>) -> Self {
630        Self {
631            module_name: module_name.into(),
632            export_types: true,
633            include_jsdoc: true,
634            generate_validators: true,
635        }
636    }
637
638    /// Set whether to export types.
639    pub fn with_exports(mut self, enable: bool) -> Self {
640        self.export_types = enable;
641        self
642    }
643
644    /// Set whether to include JSDoc comments.
645    pub fn with_jsdoc(mut self, enable: bool) -> Self {
646        self.include_jsdoc = enable;
647        self
648    }
649
650    /// Set whether to generate validator functions.
651    pub fn with_validators(mut self, enable: bool) -> Self {
652        self.generate_validators = enable;
653        self
654    }
655
656    /// Generate complete TypeScript module from a symbol table.
657    pub fn generate(&self, table: &SymbolTable) -> String {
658        let mut code = String::new();
659
660        // Module header
661        writeln!(code, "/**").unwrap();
662        writeln!(code, " * Generated from TensorLogic schema").unwrap();
663        writeln!(code, " * Module: {}", self.module_name).unwrap();
664        writeln!(code, " *").unwrap();
665        writeln!(code, " * This code was automatically generated.").unwrap();
666        writeln!(code, " * DO NOT EDIT MANUALLY.").unwrap();
667        writeln!(code, " */").unwrap();
668        writeln!(code).unwrap();
669
670        // Generate domain types
671        writeln!(code, "// ==========================================").unwrap();
672        writeln!(code, "// Domain Types").unwrap();
673        writeln!(code, "// ==========================================").unwrap();
674        writeln!(code).unwrap();
675
676        for domain in table.domains.values() {
677            self.generate_domain_type(&mut code, domain);
678            writeln!(code).unwrap();
679        }
680
681        // Generate predicate types
682        writeln!(code, "// ==========================================").unwrap();
683        writeln!(code, "// Predicate Types").unwrap();
684        writeln!(code, "// ==========================================").unwrap();
685        writeln!(code).unwrap();
686
687        for predicate in table.predicates.values() {
688            self.generate_predicate_type(&mut code, predicate, table);
689            writeln!(code).unwrap();
690        }
691
692        // Generate validator functions if enabled
693        if self.generate_validators {
694            writeln!(code, "// ==========================================").unwrap();
695            writeln!(code, "// Validator Functions").unwrap();
696            writeln!(code, "// ==========================================").unwrap();
697            writeln!(code).unwrap();
698
699            for domain in table.domains.values() {
700                self.generate_domain_validator(&mut code, domain);
701                writeln!(code).unwrap();
702            }
703        }
704
705        // Generate schema metadata
706        writeln!(code, "// ==========================================").unwrap();
707        writeln!(code, "// Schema Metadata").unwrap();
708        writeln!(code, "// ==========================================").unwrap();
709        writeln!(code).unwrap();
710        self.generate_schema_metadata(&mut code, table);
711
712        code
713    }
714
715    /// Generate TypeScript interface for a domain.
716    fn generate_domain_type(&self, code: &mut String, domain: &DomainInfo) {
717        let type_name = Self::to_typescript_type_name(&domain.name);
718        let export = if self.export_types { "export " } else { "" };
719
720        if self.include_jsdoc {
721            writeln!(code, "/**").unwrap();
722            if let Some(ref desc) = domain.description {
723                writeln!(code, " * {}", desc).unwrap();
724            } else {
725                writeln!(code, " * Domain: {}", domain.name).unwrap();
726            }
727            writeln!(code, " *").unwrap();
728            writeln!(code, " * Cardinality: {}", domain.cardinality).unwrap();
729            writeln!(code, " */").unwrap();
730        }
731
732        writeln!(code, "{}interface {} {{", export, type_name).unwrap();
733        writeln!(code, "  readonly id: number;").unwrap();
734        writeln!(code, "}}").unwrap();
735        writeln!(code).unwrap();
736
737        // Generate branded type for stronger typing
738        writeln!(
739            code,
740            "{}type {}Id = number & {{ readonly __brand: '{}' }};",
741            export, type_name, type_name
742        )
743        .unwrap();
744    }
745
746    /// Generate TypeScript interface for a predicate.
747    fn generate_predicate_type(
748        &self,
749        code: &mut String,
750        predicate: &PredicateInfo,
751        _table: &SymbolTable,
752    ) {
753        let type_name = Self::to_typescript_type_name(&predicate.name);
754        let export = if self.export_types { "export " } else { "" };
755
756        if self.include_jsdoc {
757            writeln!(code, "/**").unwrap();
758            if let Some(ref desc) = predicate.description {
759                writeln!(code, " * {}", desc).unwrap();
760            } else {
761                writeln!(code, " * Predicate: {}", predicate.name).unwrap();
762            }
763            writeln!(code, " *").unwrap();
764            writeln!(code, " * Arity: {}", predicate.arg_domains.len()).unwrap();
765
766            if let Some(ref constraints) = predicate.constraints {
767                if !constraints.properties.is_empty() {
768                    writeln!(code, " *").unwrap();
769                    writeln!(code, " * Properties:").unwrap();
770                    for prop in &constraints.properties {
771                        writeln!(code, " * - {:?}", prop).unwrap();
772                    }
773                }
774            }
775            writeln!(code, " */").unwrap();
776        }
777
778        writeln!(code, "{}interface {} {{", export, type_name).unwrap();
779        writeln!(code, "  readonly id: string;").unwrap();
780
781        // Add argument fields
782        for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
783            let field_name = format!("arg{}", i);
784            let field_type = format!("{}Id", Self::to_typescript_type_name(domain_name));
785            writeln!(code, "  readonly {}: {};", field_name, field_type).unwrap();
786        }
787
788        writeln!(code, "}}").unwrap();
789    }
790
791    /// Generate validator function for a domain.
792    fn generate_domain_validator(&self, code: &mut String, domain: &DomainInfo) {
793        let type_name = Self::to_typescript_type_name(&domain.name);
794        let export = if self.export_types { "export " } else { "" };
795
796        writeln!(code, "/**").unwrap();
797        writeln!(code, " * Validate {} ID", type_name).unwrap();
798        writeln!(
799            code,
800            " * @param id - The ID to validate (must be in range [0, {}))",
801            domain.cardinality
802        )
803        .unwrap();
804        writeln!(code, " * @returns true if valid, false otherwise").unwrap();
805        writeln!(code, " */").unwrap();
806
807        writeln!(
808            code,
809            "{}function is{}Id(id: number): id is {}Id {{",
810            export, type_name, type_name
811        )
812        .unwrap();
813        writeln!(
814            code,
815            "  return Number.isInteger(id) && id >= 0 && id < {};",
816            domain.cardinality
817        )
818        .unwrap();
819        writeln!(code, "}}").unwrap();
820    }
821
822    /// Generate schema metadata constant.
823    fn generate_schema_metadata(&self, code: &mut String, table: &SymbolTable) {
824        let export = if self.export_types { "export " } else { "" };
825
826        writeln!(code, "/**").unwrap();
827        writeln!(code, " * Schema metadata and statistics").unwrap();
828        writeln!(code, " */").unwrap();
829
830        writeln!(code, "{}const SCHEMA_METADATA = {{", export).unwrap();
831        writeln!(code, "  domainCount: {},", table.domains.len()).unwrap();
832        writeln!(code, "  predicateCount: {},", table.predicates.len()).unwrap();
833
834        let total_card: usize = table.domains.values().map(|d| d.cardinality).sum();
835        writeln!(code, "  totalCardinality: {},", total_card).unwrap();
836
837        writeln!(code, "  domains: {{").unwrap();
838        for domain in table.domains.values() {
839            writeln!(
840                code,
841                "    '{}': {{ cardinality: {} }},",
842                domain.name, domain.cardinality
843            )
844            .unwrap();
845        }
846        writeln!(code, "  }},").unwrap();
847
848        writeln!(code, "  predicates: {{").unwrap();
849        for predicate in table.predicates.values() {
850            writeln!(
851                code,
852                "    '{}': {{ arity: {} }},",
853                predicate.name,
854                predicate.arg_domains.len()
855            )
856            .unwrap();
857        }
858        writeln!(code, "  }},").unwrap();
859
860        writeln!(code, "}} as const;").unwrap();
861    }
862
863    /// Convert a name to TypeScript type name (PascalCase).
864    fn to_typescript_type_name(name: &str) -> String {
865        RustCodegen::to_type_name(name) // Reuse Rust converter
866    }
867}
868
869/// Code generator for Python type stubs and PyO3 bindings.
870///
871/// This generator creates Python type stubs (.pyi) and optionally PyO3
872/// binding code from TensorLogic schemas.
873pub struct PythonCodegen {
874    /// Module name
875    module_name: String,
876    /// Whether to generate PyO3 bindings (vs. just stubs)
877    generate_pyo3: bool,
878    /// Whether to include docstrings
879    include_docs: bool,
880    /// Whether to generate dataclass decorators
881    use_dataclasses: bool,
882}
883
884impl PythonCodegen {
885    /// Create a new Python code generator.
886    pub fn new(module_name: impl Into<String>) -> Self {
887        Self {
888            module_name: module_name.into(),
889            generate_pyo3: false,
890            include_docs: true,
891            use_dataclasses: true,
892        }
893    }
894
895    /// Set whether to generate PyO3 bindings.
896    pub fn with_pyo3(mut self, enable: bool) -> Self {
897        self.generate_pyo3 = enable;
898        self
899    }
900
901    /// Set whether to include docstrings.
902    pub fn with_docs(mut self, enable: bool) -> Self {
903        self.include_docs = enable;
904        self
905    }
906
907    /// Set whether to use dataclasses.
908    pub fn with_dataclasses(mut self, enable: bool) -> Self {
909        self.use_dataclasses = enable;
910        self
911    }
912
913    /// Generate complete Python module from a symbol table.
914    pub fn generate(&self, table: &SymbolTable) -> String {
915        if self.generate_pyo3 {
916            self.generate_pyo3_bindings(table)
917        } else {
918            self.generate_type_stubs(table)
919        }
920    }
921
922    /// Generate Python type stubs (.pyi file).
923    fn generate_type_stubs(&self, table: &SymbolTable) -> String {
924        let mut code = String::new();
925
926        // Module header
927        writeln!(code, "\"\"\"").unwrap();
928        writeln!(code, "Generated from TensorLogic schema").unwrap();
929        writeln!(code, "Module: {}", self.module_name).unwrap();
930        writeln!(code).unwrap();
931        writeln!(code, "This code was automatically generated.").unwrap();
932        writeln!(code, "DO NOT EDIT MANUALLY.").unwrap();
933        writeln!(code, "\"\"\"").unwrap();
934        writeln!(code).unwrap();
935
936        // Imports
937        writeln!(code, "from typing import NewType, Final").unwrap();
938        if self.use_dataclasses {
939            writeln!(code, "from dataclasses import dataclass").unwrap();
940        }
941        writeln!(code).unwrap();
942
943        // Generate domain types
944        writeln!(code, "# ==========================================").unwrap();
945        writeln!(code, "# Domain Types").unwrap();
946        writeln!(code, "# ==========================================").unwrap();
947        writeln!(code).unwrap();
948
949        for domain in table.domains.values() {
950            self.generate_domain_stub(&mut code, domain);
951            writeln!(code).unwrap();
952        }
953
954        // Generate predicate types
955        writeln!(code, "# ==========================================").unwrap();
956        writeln!(code, "# Predicate Types").unwrap();
957        writeln!(code, "# ==========================================").unwrap();
958        writeln!(code).unwrap();
959
960        for predicate in table.predicates.values() {
961            self.generate_predicate_stub(&mut code, predicate, table);
962            writeln!(code).unwrap();
963        }
964
965        // Generate schema metadata
966        writeln!(code, "# ==========================================").unwrap();
967        writeln!(code, "# Schema Metadata").unwrap();
968        writeln!(code, "# ==========================================").unwrap();
969        writeln!(code).unwrap();
970        self.generate_schema_metadata_stub(&mut code, table);
971
972        code
973    }
974
975    /// Generate PyO3 Rust bindings.
976    fn generate_pyo3_bindings(&self, table: &SymbolTable) -> String {
977        let mut code = String::new();
978
979        // Module header
980        writeln!(code, "//! PyO3 bindings for TensorLogic schema").unwrap();
981        writeln!(code, "//! Module: {}", self.module_name).unwrap();
982        writeln!(code, "//!").unwrap();
983        writeln!(code, "//! This code was automatically generated.").unwrap();
984        writeln!(code, "//! DO NOT EDIT MANUALLY.").unwrap();
985        writeln!(code).unwrap();
986
987        writeln!(code, "use pyo3::prelude::*;").unwrap();
988        writeln!(code).unwrap();
989
990        // Generate domain classes
991        writeln!(code, "// ==========================================").unwrap();
992        writeln!(code, "// Domain Types").unwrap();
993        writeln!(code, "// ==========================================").unwrap();
994        writeln!(code).unwrap();
995
996        for domain in table.domains.values() {
997            self.generate_domain_pyo3(&mut code, domain);
998            writeln!(code).unwrap();
999        }
1000
1001        // Generate predicate classes
1002        writeln!(code, "// ==========================================").unwrap();
1003        writeln!(code, "// Predicate Types").unwrap();
1004        writeln!(code, "// ==========================================").unwrap();
1005        writeln!(code).unwrap();
1006
1007        for predicate in table.predicates.values() {
1008            self.generate_predicate_pyo3(&mut code, predicate);
1009            writeln!(code).unwrap();
1010        }
1011
1012        // Generate module registration
1013        writeln!(code, "// ==========================================").unwrap();
1014        writeln!(code, "// Module Registration").unwrap();
1015        writeln!(code, "// ==========================================").unwrap();
1016        writeln!(code).unwrap();
1017        self.generate_module_registration(&mut code, table);
1018
1019        code
1020    }
1021
1022    /// Generate Python type stub for a domain.
1023    fn generate_domain_stub(&self, code: &mut String, domain: &DomainInfo) {
1024        let type_name = Self::to_python_class_name(&domain.name);
1025
1026        // NewType for branded ID
1027        writeln!(code, "{} = NewType('{}', int)", type_name, type_name).unwrap();
1028        writeln!(code).unwrap();
1029
1030        // Cardinality constant
1031        writeln!(
1032            code,
1033            "{}_CARDINALITY: Final[int] = {}",
1034            domain.name.to_uppercase(),
1035            domain.cardinality
1036        )
1037        .unwrap();
1038        writeln!(code).unwrap();
1039
1040        // Validator function
1041        writeln!(code, "def is_valid_{}(id: int) -> bool:", domain.name).unwrap();
1042        if self.include_docs {
1043            writeln!(code, "    \"\"\"").unwrap();
1044            if let Some(ref desc) = domain.description {
1045                writeln!(code, "    {}", desc).unwrap();
1046                writeln!(code).unwrap();
1047            }
1048            writeln!(code, "    Validate {} ID.", type_name).unwrap();
1049            writeln!(code).unwrap();
1050            writeln!(code, "    Args:").unwrap();
1051            writeln!(code, "        id: The ID to validate").unwrap();
1052            writeln!(code).unwrap();
1053            writeln!(code, "    Returns:").unwrap();
1054            writeln!(
1055                code,
1056                "        True if id is in range [0, {}), False otherwise",
1057                domain.cardinality
1058            )
1059            .unwrap();
1060            writeln!(code, "    \"\"\"").unwrap();
1061        }
1062        writeln!(code, "    ...").unwrap();
1063    }
1064
1065    /// Generate Python type stub for a predicate.
1066    fn generate_predicate_stub(
1067        &self,
1068        code: &mut String,
1069        predicate: &PredicateInfo,
1070        _table: &SymbolTable,
1071    ) {
1072        let class_name = Self::to_python_class_name(&predicate.name);
1073
1074        if self.include_docs {
1075            writeln!(code, "\"\"\"").unwrap();
1076            if let Some(ref desc) = predicate.description {
1077                writeln!(code, "{}", desc).unwrap();
1078            } else {
1079                writeln!(code, "Predicate: {}", predicate.name).unwrap();
1080            }
1081            writeln!(code).unwrap();
1082            writeln!(code, "Arity: {}", predicate.arg_domains.len()).unwrap();
1083            writeln!(code, "\"\"\"").unwrap();
1084        }
1085
1086        if self.use_dataclasses {
1087            writeln!(code, "@dataclass(frozen=True)").unwrap();
1088        }
1089
1090        writeln!(code, "class {}:", class_name).unwrap();
1091
1092        if self.include_docs && predicate.description.is_none() {
1093            writeln!(code, "    \"\"\"{}\"\"\"", predicate.name).unwrap();
1094        }
1095
1096        // Add fields
1097        for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
1098            let field_name = format!("arg{}", i);
1099            let field_type = Self::to_python_class_name(domain_name);
1100            writeln!(code, "    {}: {}", field_name, field_type).unwrap();
1101        }
1102
1103        if predicate.arg_domains.is_empty() {
1104            writeln!(code, "    pass").unwrap();
1105        }
1106    }
1107
1108    /// Generate PyO3 class for a domain.
1109    fn generate_domain_pyo3(&self, code: &mut String, domain: &DomainInfo) {
1110        let type_name = Self::to_python_class_name(&domain.name);
1111
1112        writeln!(code, "#[pyclass]").unwrap();
1113        writeln!(code, "#[derive(Clone, Copy, Debug)]").unwrap();
1114        writeln!(code, "pub struct {} {{", type_name).unwrap();
1115        writeln!(code, "    #[pyo3(get)]").unwrap();
1116        writeln!(code, "    pub id: usize,").unwrap();
1117        writeln!(code, "}}").unwrap();
1118        writeln!(code).unwrap();
1119
1120        writeln!(code, "#[pymethods]").unwrap();
1121        writeln!(code, "impl {} {{", type_name).unwrap();
1122
1123        // Constructor
1124        writeln!(code, "    #[new]").unwrap();
1125        writeln!(code, "    pub fn new(id: usize) -> PyResult<Self> {{").unwrap();
1126        writeln!(code, "        if id >= {} {{", domain.cardinality).unwrap();
1127        writeln!(
1128            code,
1129            "            return Err(pyo3::exceptions::PyValueError::new_err("
1130        )
1131        .unwrap();
1132        writeln!(
1133            code,
1134            "                format!(\"ID {{}} exceeds cardinality {}\", id)",
1135            domain.cardinality
1136        )
1137        .unwrap();
1138        writeln!(code, "            ));").unwrap();
1139        writeln!(code, "        }}").unwrap();
1140        writeln!(code, "        Ok(Self {{ id }})").unwrap();
1141        writeln!(code, "    }}").unwrap();
1142        writeln!(code).unwrap();
1143
1144        // String representation
1145        writeln!(code, "    fn __repr__(&self) -> String {{").unwrap();
1146        writeln!(code, "        format!(\"{}({{}})\", self.id)", type_name).unwrap();
1147        writeln!(code, "    }}").unwrap();
1148
1149        writeln!(code, "}}").unwrap();
1150    }
1151
1152    /// Generate PyO3 class for a predicate.
1153    fn generate_predicate_pyo3(&self, code: &mut String, predicate: &PredicateInfo) {
1154        let type_name = Self::to_python_class_name(&predicate.name);
1155
1156        writeln!(code, "#[pyclass]").unwrap();
1157        writeln!(code, "#[derive(Clone, Debug)]").unwrap();
1158        writeln!(code, "pub struct {} {{", type_name).unwrap();
1159
1160        for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
1161            let field_type = Self::to_python_class_name(domain_name);
1162            writeln!(code, "    #[pyo3(get)]").unwrap();
1163            writeln!(code, "    pub arg{}: {},", i, field_type).unwrap();
1164        }
1165
1166        writeln!(code, "}}").unwrap();
1167        writeln!(code).unwrap();
1168
1169        writeln!(code, "#[pymethods]").unwrap();
1170        writeln!(code, "impl {} {{", type_name).unwrap();
1171
1172        // Constructor
1173        writeln!(code, "    #[new]").unwrap();
1174        write!(code, "    pub fn new(").unwrap();
1175        for (i, domain_name) in predicate.arg_domains.iter().enumerate() {
1176            if i > 0 {
1177                write!(code, ", ").unwrap();
1178            }
1179            write!(
1180                code,
1181                "arg{}: {}",
1182                i,
1183                Self::to_python_class_name(domain_name)
1184            )
1185            .unwrap();
1186        }
1187        writeln!(code, ") -> Self {{").unwrap();
1188
1189        if predicate.arg_domains.is_empty() {
1190            writeln!(code, "        Self {{}}").unwrap();
1191        } else {
1192            write!(code, "        Self {{ ").unwrap();
1193            for i in 0..predicate.arg_domains.len() {
1194                if i > 0 {
1195                    write!(code, ", ").unwrap();
1196                }
1197                write!(code, "arg{}", i).unwrap();
1198            }
1199            writeln!(code, " }}").unwrap();
1200        }
1201        writeln!(code, "    }}").unwrap();
1202
1203        writeln!(code, "}}").unwrap();
1204    }
1205
1206    /// Generate module registration for PyO3.
1207    fn generate_module_registration(&self, code: &mut String, table: &SymbolTable) {
1208        writeln!(code, "#[pymodule]").unwrap();
1209        writeln!(
1210            code,
1211            "fn {}(_py: Python, m: &PyModule) -> PyResult<()> {{",
1212            self.module_name.replace('-', "_")
1213        )
1214        .unwrap();
1215
1216        // Register domain classes
1217        for domain in table.domains.values() {
1218            let type_name = Self::to_python_class_name(&domain.name);
1219            writeln!(code, "    m.add_class::<{}>()?;", type_name).unwrap();
1220        }
1221
1222        // Register predicate classes
1223        for predicate in table.predicates.values() {
1224            let type_name = Self::to_python_class_name(&predicate.name);
1225            writeln!(code, "    m.add_class::<{}>()?;", type_name).unwrap();
1226        }
1227
1228        writeln!(code, "    Ok(())").unwrap();
1229        writeln!(code, "}}").unwrap();
1230    }
1231
1232    /// Generate schema metadata stub.
1233    fn generate_schema_metadata_stub(&self, code: &mut String, table: &SymbolTable) {
1234        writeln!(code, "class SchemaMetadata:").unwrap();
1235        if self.include_docs {
1236            writeln!(code, "    \"\"\"Schema metadata and statistics\"\"\"").unwrap();
1237        }
1238        writeln!(
1239            code,
1240            "    DOMAIN_COUNT: Final[int] = {}",
1241            table.domains.len()
1242        )
1243        .unwrap();
1244        writeln!(
1245            code,
1246            "    PREDICATE_COUNT: Final[int] = {}",
1247            table.predicates.len()
1248        )
1249        .unwrap();
1250
1251        let total_card: usize = table.domains.values().map(|d| d.cardinality).sum();
1252        writeln!(code, "    TOTAL_CARDINALITY: Final[int] = {}", total_card).unwrap();
1253    }
1254
1255    /// Convert a name to Python class name (PascalCase).
1256    fn to_python_class_name(name: &str) -> String {
1257        RustCodegen::to_type_name(name) // Reuse Rust converter
1258    }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263    use super::*;
1264
1265    #[test]
1266    fn test_to_type_name() {
1267        assert_eq!(RustCodegen::to_type_name("person"), "Person");
1268        assert_eq!(RustCodegen::to_type_name("Person"), "Person");
1269        assert_eq!(RustCodegen::to_type_name("student_record"), "StudentRecord");
1270        assert_eq!(RustCodegen::to_type_name("HTTP_Request"), "HTTPRequest");
1271    }
1272
1273    #[test]
1274    fn test_generate_simple_schema() {
1275        let mut table = SymbolTable::new();
1276        table
1277            .add_domain(DomainInfo::new("Person", 100).with_description("A person entity"))
1278            .unwrap();
1279
1280        let codegen = RustCodegen::new("test_module");
1281        let code = codegen.generate(&table);
1282
1283        assert!(code.contains("pub struct Person(pub usize);"));
1284        assert!(code.contains("CARDINALITY: usize = 100"));
1285        assert!(code.contains("A person entity"));
1286    }
1287
1288    #[test]
1289    fn test_generate_predicate() {
1290        let mut table = SymbolTable::new();
1291        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1292
1293        let pred = PredicateInfo::new("knows", vec!["Person".to_string(), "Person".to_string()])
1294            .with_description("Person knows another person");
1295        table.add_predicate(pred).unwrap();
1296
1297        let codegen = RustCodegen::new("test_module");
1298        let code = codegen.generate(&table);
1299
1300        assert!(code.contains("pub struct Knows(pub Person, pub Person);"));
1301        assert!(code.contains("Person knows another person"));
1302        assert!(code.contains("pub fn new(arg0: Person, arg1: Person)"));
1303    }
1304
1305    #[test]
1306    fn test_generate_without_docs() {
1307        let mut table = SymbolTable::new();
1308        table
1309            .add_domain(DomainInfo::new("Person", 100).with_description("A person"))
1310            .unwrap();
1311
1312        let codegen = RustCodegen::new("test_module").with_docs(false);
1313        let code = codegen.generate(&table);
1314
1315        // Should not contain descriptive doc comments (module header is ok)
1316        assert!(!code.contains("/// A person"));
1317        assert!(!code.contains("/// Cardinality:"));
1318        // Should still contain the struct
1319        assert!(code.contains("pub struct Person"));
1320    }
1321
1322    #[test]
1323    fn test_generate_without_derives() {
1324        let mut table = SymbolTable::new();
1325        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1326
1327        let codegen = RustCodegen::new("test_module").with_common_derives(false);
1328        let code = codegen.generate(&table);
1329
1330        // Should not contain derive attributes
1331        assert!(!code.contains("#[derive("));
1332        // Should still contain the struct
1333        assert!(code.contains("pub struct Person"));
1334    }
1335
1336    #[test]
1337    fn test_generate_unary_predicate() {
1338        let mut table = SymbolTable::new();
1339        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1340
1341        let pred = PredicateInfo::new("adult", vec!["Person".to_string()]);
1342        table.add_predicate(pred).unwrap();
1343
1344        let codegen = RustCodegen::new("test_module");
1345        let code = codegen.generate(&table);
1346
1347        assert!(code.contains("pub struct Adult(pub Person);"));
1348        assert!(code.contains("pub fn new(arg0: Person)"));
1349    }
1350
1351    #[test]
1352    fn test_generate_metadata() {
1353        let mut table = SymbolTable::new();
1354        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1355        table.add_domain(DomainInfo::new("Course", 50)).unwrap();
1356
1357        let pred = PredicateInfo::new("enrolled", vec!["Person".to_string(), "Course".to_string()]);
1358        table.add_predicate(pred).unwrap();
1359
1360        let codegen = RustCodegen::new("test_module");
1361        let code = codegen.generate(&table);
1362
1363        assert!(code.contains("DOMAIN_COUNT: usize = 2"));
1364        assert!(code.contains("PREDICATE_COUNT: usize = 1"));
1365        assert!(code.contains("TOTAL_CARDINALITY: usize = 150"));
1366    }
1367
1368    // GraphQL code generation tests
1369    #[test]
1370    fn test_graphql_field_name_conversion() {
1371        assert_eq!(GraphQLCodegen::to_graphql_field_name("person"), "person");
1372        assert_eq!(
1373            GraphQLCodegen::to_graphql_field_name("student_record"),
1374            "studentRecord"
1375        );
1376        assert_eq!(
1377            GraphQLCodegen::to_graphql_field_name("http_request"),
1378            "httpRequest"
1379        );
1380    }
1381
1382    #[test]
1383    fn test_graphql_generate_simple_schema() {
1384        let mut table = SymbolTable::new();
1385        table
1386            .add_domain(DomainInfo::new("Person", 100).with_description("A person entity"))
1387            .unwrap();
1388
1389        let codegen = GraphQLCodegen::new("TestSchema");
1390        let schema = codegen.generate(&table);
1391
1392        assert!(schema.contains("# Generated GraphQL Schema"));
1393        assert!(schema.contains("type Person {"));
1394        assert!(schema.contains("id: ID!"));
1395        assert!(schema.contains("index: Int!"));
1396        assert!(schema.contains("A person entity"));
1397    }
1398
1399    #[test]
1400    fn test_graphql_generate_with_predicate() {
1401        let mut table = SymbolTable::new();
1402        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1403
1404        let pred = PredicateInfo::new("knows", vec!["Person".to_string(), "Person".to_string()])
1405            .with_description("Person knows another person");
1406        table.add_predicate(pred).unwrap();
1407
1408        let codegen = GraphQLCodegen::new("TestSchema");
1409        let schema = codegen.generate(&table);
1410
1411        assert!(schema.contains("type Knows {"));
1412        assert!(schema.contains("arg0: Person!"));
1413        assert!(schema.contains("arg1: Person!"));
1414        assert!(schema.contains("Person knows another person"));
1415    }
1416
1417    #[test]
1418    fn test_graphql_generate_queries() {
1419        let mut table = SymbolTable::new();
1420        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1421
1422        let pred = PredicateInfo::new("adult", vec!["Person".to_string()]);
1423        table.add_predicate(pred).unwrap();
1424
1425        let codegen = GraphQLCodegen::new("TestSchema").with_queries(true);
1426        let schema = codegen.generate(&table);
1427
1428        assert!(schema.contains("type Query {"));
1429        assert!(schema.contains("person(id: ID!): Person"));
1430        assert!(schema.contains("persons: [Person!]!"));
1431        assert!(schema.contains("adult(arg0: Person): [Adult!]!"));
1432    }
1433
1434    #[test]
1435    fn test_graphql_generate_mutations() {
1436        let mut table = SymbolTable::new();
1437        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1438
1439        let pred = PredicateInfo::new("adult", vec!["Person".to_string()]);
1440        table.add_predicate(pred).unwrap();
1441
1442        let codegen = GraphQLCodegen::new("TestSchema")
1443            .with_queries(false)
1444            .with_mutations(true);
1445        let schema = codegen.generate(&table);
1446
1447        assert!(schema.contains("type Mutation {"));
1448        assert!(schema.contains("addAdult(arg0: Person!): Adult!"));
1449        assert!(schema.contains("removeAdult(id: ID!): Boolean!"));
1450        assert!(!schema.contains("type Query"));
1451    }
1452
1453    #[test]
1454    fn test_graphql_without_descriptions() {
1455        let mut table = SymbolTable::new();
1456        table
1457            .add_domain(DomainInfo::new("Person", 100).with_description("A person"))
1458            .unwrap();
1459
1460        let codegen = GraphQLCodegen::new("TestSchema").with_descriptions(false);
1461        let schema = codegen.generate(&table);
1462
1463        assert!(!schema.contains("A person"));
1464        assert!(schema.contains("type Person {"));
1465    }
1466
1467    #[test]
1468    fn test_graphql_schema_definition() {
1469        let mut table = SymbolTable::new();
1470        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1471
1472        let codegen = GraphQLCodegen::new("TestSchema")
1473            .with_queries(true)
1474            .with_mutations(true);
1475        let schema = codegen.generate(&table);
1476
1477        assert!(schema.contains("schema {"));
1478        assert!(schema.contains("query: Query"));
1479        assert!(schema.contains("mutation: Mutation"));
1480    }
1481
1482    #[test]
1483    fn test_graphql_complex_predicate() {
1484        let mut table = SymbolTable::new();
1485        table.add_domain(DomainInfo::new("Student", 80)).unwrap();
1486        table.add_domain(DomainInfo::new("Course", 50)).unwrap();
1487        table.add_domain(DomainInfo::new("Grade", 5)).unwrap();
1488
1489        let pred = PredicateInfo::new(
1490            "grade",
1491            vec![
1492                "Student".to_string(),
1493                "Course".to_string(),
1494                "Grade".to_string(),
1495            ],
1496        );
1497        table.add_predicate(pred).unwrap();
1498
1499        let codegen = GraphQLCodegen::new("TestSchema");
1500        let schema = codegen.generate(&table);
1501
1502        assert!(schema.contains("type Grade {"));
1503        assert!(schema.contains("arg0: Student!"));
1504        assert!(schema.contains("arg1: Course!"));
1505        assert!(schema.contains("arg2: Grade!"));
1506    }
1507
1508    // TypeScript code generation tests
1509    #[test]
1510    fn test_typescript_generate_simple_schema() {
1511        let mut table = SymbolTable::new();
1512        table
1513            .add_domain(DomainInfo::new("Person", 100).with_description("A person entity"))
1514            .unwrap();
1515
1516        let codegen = TypeScriptCodegen::new("test_module");
1517        let code = codegen.generate(&table);
1518
1519        assert!(code.contains("export interface Person {"));
1520        assert!(code.contains("readonly id: number;"));
1521        assert!(code.contains("export type PersonId = number"));
1522        assert!(code.contains("A person entity"));
1523        assert!(code.contains("Cardinality: 100"));
1524    }
1525
1526    #[test]
1527    fn test_typescript_generate_with_predicate() {
1528        let mut table = SymbolTable::new();
1529        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1530
1531        let pred = PredicateInfo::new("knows", vec!["Person".to_string(), "Person".to_string()])
1532            .with_description("Person knows another person");
1533        table.add_predicate(pred).unwrap();
1534
1535        let codegen = TypeScriptCodegen::new("test_module");
1536        let code = codegen.generate(&table);
1537
1538        assert!(code.contains("export interface Knows {"));
1539        assert!(code.contains("readonly arg0: PersonId;"));
1540        assert!(code.contains("readonly arg1: PersonId;"));
1541        assert!(code.contains("Person knows another person"));
1542    }
1543
1544    #[test]
1545    fn test_typescript_generate_validators() {
1546        let mut table = SymbolTable::new();
1547        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1548
1549        let codegen = TypeScriptCodegen::new("test_module").with_validators(true);
1550        let code = codegen.generate(&table);
1551
1552        assert!(code.contains("export function isPersonId(id: number): id is PersonId {"));
1553        assert!(code.contains("Number.isInteger(id) && id >= 0 && id < 100"));
1554    }
1555
1556    #[test]
1557    fn test_typescript_without_exports() {
1558        let mut table = SymbolTable::new();
1559        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1560
1561        let codegen = TypeScriptCodegen::new("test_module").with_exports(false);
1562        let code = codegen.generate(&table);
1563
1564        // Should not have export keywords
1565        let export_count = code.matches("export ").count();
1566        assert_eq!(export_count, 0);
1567        assert!(code.contains("interface Person {"));
1568    }
1569
1570    #[test]
1571    fn test_typescript_without_jsdoc() {
1572        let mut table = SymbolTable::new();
1573        table
1574            .add_domain(DomainInfo::new("Person", 100).with_description("A person"))
1575            .unwrap();
1576
1577        let codegen = TypeScriptCodegen::new("test_module").with_jsdoc(false);
1578        let code = codegen.generate(&table);
1579
1580        assert!(!code.contains("A person"));
1581        assert!(code.contains("export interface Person {"));
1582    }
1583
1584    #[test]
1585    fn test_typescript_metadata_generation() {
1586        let mut table = SymbolTable::new();
1587        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1588        table.add_domain(DomainInfo::new("Course", 50)).unwrap();
1589
1590        let pred = PredicateInfo::new("enrolled", vec!["Person".to_string(), "Course".to_string()]);
1591        table.add_predicate(pred).unwrap();
1592
1593        let codegen = TypeScriptCodegen::new("test_module");
1594        let code = codegen.generate(&table);
1595
1596        assert!(code.contains("export const SCHEMA_METADATA = {"));
1597        assert!(code.contains("domainCount: 2,"));
1598        assert!(code.contains("predicateCount: 1,"));
1599        assert!(code.contains("totalCardinality: 150,"));
1600    }
1601
1602    // Python code generation tests
1603    #[test]
1604    fn test_python_generate_simple_stubs() {
1605        let mut table = SymbolTable::new();
1606        table
1607            .add_domain(DomainInfo::new("Person", 100).with_description("A person entity"))
1608            .unwrap();
1609
1610        let codegen = PythonCodegen::new("test_module");
1611        let code = codegen.generate(&table);
1612
1613        assert!(code.contains("Person = NewType('Person', int)"));
1614        assert!(code.contains("PERSON_CARDINALITY: Final[int] = 100"));
1615        assert!(code.contains("def is_valid_Person(id: int) -> bool:"));
1616        assert!(code.contains("A person entity"));
1617    }
1618
1619    #[test]
1620    fn test_python_generate_with_predicate() {
1621        let mut table = SymbolTable::new();
1622        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1623
1624        let pred = PredicateInfo::new("knows", vec!["Person".to_string(), "Person".to_string()])
1625            .with_description("Person knows another person");
1626        table.add_predicate(pred).unwrap();
1627
1628        let codegen = PythonCodegen::new("test_module");
1629        let code = codegen.generate(&table);
1630
1631        assert!(code.contains("@dataclass(frozen=True)"));
1632        assert!(code.contains("class Knows:"));
1633        assert!(code.contains("Person knows another person"));
1634        assert!(code.contains("arg0: Person"));
1635        assert!(code.contains("arg1: Person"));
1636    }
1637
1638    #[test]
1639    fn test_python_generate_pyo3_bindings() {
1640        let mut table = SymbolTable::new();
1641        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1642
1643        let codegen = PythonCodegen::new("test_module").with_pyo3(true);
1644        let code = codegen.generate(&table);
1645
1646        assert!(code.contains("use pyo3::prelude::*;"));
1647        assert!(code.contains("#[pyclass]"));
1648        assert!(code.contains("pub struct Person {"));
1649        assert!(code.contains("#[pyo3(get)]"));
1650        assert!(code.contains("pub id: usize,"));
1651        assert!(code.contains("#[pymethods]"));
1652        assert!(code.contains("#[new]"));
1653        assert!(code.contains("fn __repr__(&self)"));
1654    }
1655
1656    #[test]
1657    fn test_python_without_docs() {
1658        let mut table = SymbolTable::new();
1659        table
1660            .add_domain(DomainInfo::new("Person", 100).with_description("A person"))
1661            .unwrap();
1662
1663        let codegen = PythonCodegen::new("test_module").with_docs(false);
1664        let code = codegen.generate(&table);
1665
1666        // Should not contain docstrings (except the module header)
1667        let docstring_count = code.matches("\"\"\"").count();
1668        assert_eq!(docstring_count, 2); // Only module header
1669    }
1670
1671    #[test]
1672    fn test_python_without_dataclasses() {
1673        let mut table = SymbolTable::new();
1674        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1675
1676        let pred = PredicateInfo::new("adult", vec!["Person".to_string()]);
1677        table.add_predicate(pred).unwrap();
1678
1679        let codegen = PythonCodegen::new("test_module").with_dataclasses(false);
1680        let code = codegen.generate(&table);
1681
1682        assert!(!code.contains("@dataclass"));
1683        assert!(code.contains("class Adult:"));
1684    }
1685
1686    #[test]
1687    fn test_python_pyo3_module_registration() {
1688        let mut table = SymbolTable::new();
1689        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1690        table.add_domain(DomainInfo::new("Course", 50)).unwrap();
1691
1692        let pred = PredicateInfo::new("enrolled", vec!["Person".to_string(), "Course".to_string()]);
1693        table.add_predicate(pred).unwrap();
1694
1695        let codegen = PythonCodegen::new("test_module").with_pyo3(true);
1696        let code = codegen.generate(&table);
1697
1698        assert!(code.contains("#[pymodule]"));
1699        assert!(code.contains("fn test_module(_py: Python, m: &PyModule)"));
1700        assert!(code.contains("m.add_class::<Person>()?;"));
1701        assert!(code.contains("m.add_class::<Course>()?;"));
1702        assert!(code.contains("m.add_class::<Enrolled>()?;"));
1703    }
1704
1705    #[test]
1706    fn test_python_metadata_generation() {
1707        let mut table = SymbolTable::new();
1708        table.add_domain(DomainInfo::new("Person", 100)).unwrap();
1709        table.add_domain(DomainInfo::new("Course", 50)).unwrap();
1710
1711        let codegen = PythonCodegen::new("test_module");
1712        let code = codegen.generate(&table);
1713
1714        assert!(code.contains("class SchemaMetadata:"));
1715        assert!(code.contains("DOMAIN_COUNT: Final[int] = 2"));
1716        assert!(code.contains("TOTAL_CARDINALITY: Final[int] = 150"));
1717    }
1718}