1use std::fmt::Write as FmtWrite;
13
14use crate::{DomainInfo, PredicateInfo, SymbolTable};
15
16pub struct RustCodegen {
18 module_name: String,
20 derive_common: bool,
22 include_docs: bool,
24}
25
26impl RustCodegen {
27 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 pub fn with_common_derives(mut self, enable: bool) -> Self {
38 self.derive_common = enable;
39 self
40 }
41
42 pub fn with_docs(mut self, enable: bool) -> Self {
44 self.include_docs = enable;
45 self
46 }
47
48 pub fn generate(&self, table: &SymbolTable) -> String {
50 let mut code = String::new();
51
52 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 writeln!(code, "#![allow(dead_code)]").unwrap();
62 writeln!(code).unwrap();
63
64 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 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 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 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 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 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 if predicate.arg_domains.is_empty() {
209 writeln!(code, "pub struct {};", type_name).unwrap();
211 } else if predicate.arg_domains.len() == 1 {
212 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 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 writeln!(code, "impl {} {{", type_name).unwrap();
231
232 if !predicate.arg_domains.is_empty() {
233 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 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 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 fn to_type_name(name: &str) -> String {
321 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
334pub struct GraphQLCodegen {
339 schema_name: String,
341 include_descriptions: bool,
343 generate_queries: bool,
345 generate_mutations: bool,
347}
348
349impl GraphQLCodegen {
350 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 pub fn with_descriptions(mut self, enable: bool) -> Self {
362 self.include_descriptions = enable;
363 self
364 }
365
366 pub fn with_queries(mut self, enable: bool) -> Self {
368 self.generate_queries = enable;
369 self
370 }
371
372 pub fn with_mutations(mut self, enable: bool) -> Self {
374 self.generate_mutations = enable;
375 self
376 }
377
378 pub fn generate(&self, table: &SymbolTable) -> String {
380 let mut schema = String::new();
381
382 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 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 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 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 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 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 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 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 writeln!(schema, " \"Unique identifier\"").unwrap();
499 writeln!(schema, " id: ID!").unwrap();
500
501 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 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 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 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 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 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 for predicate in table.predicates.values() {
565 let type_name = Self::to_graphql_type_name(&predicate.name);
566
567 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 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 fn to_graphql_type_name(name: &str) -> String {
591 RustCodegen::to_type_name(name) }
593
594 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
612pub struct TypeScriptCodegen {
617 module_name: String,
619 export_types: bool,
621 include_jsdoc: bool,
623 generate_validators: bool,
625}
626
627impl TypeScriptCodegen {
628 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 pub fn with_exports(mut self, enable: bool) -> Self {
640 self.export_types = enable;
641 self
642 }
643
644 pub fn with_jsdoc(mut self, enable: bool) -> Self {
646 self.include_jsdoc = enable;
647 self
648 }
649
650 pub fn with_validators(mut self, enable: bool) -> Self {
652 self.generate_validators = enable;
653 self
654 }
655
656 pub fn generate(&self, table: &SymbolTable) -> String {
658 let mut code = String::new();
659
660 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 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 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 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 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 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 writeln!(
739 code,
740 "{}type {}Id = number & {{ readonly __brand: '{}' }};",
741 export, type_name, type_name
742 )
743 .unwrap();
744 }
745
746 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 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 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 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 fn to_typescript_type_name(name: &str) -> String {
865 RustCodegen::to_type_name(name) }
867}
868
869pub struct PythonCodegen {
874 module_name: String,
876 generate_pyo3: bool,
878 include_docs: bool,
880 use_dataclasses: bool,
882}
883
884impl PythonCodegen {
885 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 pub fn with_pyo3(mut self, enable: bool) -> Self {
897 self.generate_pyo3 = enable;
898 self
899 }
900
901 pub fn with_docs(mut self, enable: bool) -> Self {
903 self.include_docs = enable;
904 self
905 }
906
907 pub fn with_dataclasses(mut self, enable: bool) -> Self {
909 self.use_dataclasses = enable;
910 self
911 }
912
913 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 fn generate_type_stubs(&self, table: &SymbolTable) -> String {
924 let mut code = String::new();
925
926 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 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 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 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 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 fn generate_pyo3_bindings(&self, table: &SymbolTable) -> String {
977 let mut code = String::new();
978
979 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 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 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 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 fn generate_domain_stub(&self, code: &mut String, domain: &DomainInfo) {
1024 let type_name = Self::to_python_class_name(&domain.name);
1025
1026 writeln!(code, "{} = NewType('{}', int)", type_name, type_name).unwrap();
1028 writeln!(code).unwrap();
1029
1030 writeln!(
1032 code,
1033 "{}_CARDINALITY: Final[int] = {}",
1034 domain.name.to_uppercase(),
1035 domain.cardinality
1036 )
1037 .unwrap();
1038 writeln!(code).unwrap();
1039
1040 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 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 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 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 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 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 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 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 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 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 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 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 fn to_python_class_name(name: &str) -> String {
1257 RustCodegen::to_type_name(name) }
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 assert!(!code.contains("/// A person"));
1317 assert!(!code.contains("/// Cardinality:"));
1318 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 assert!(!code.contains("#[derive("));
1332 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 #[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 #[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 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 #[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 let docstring_count = code.matches("\"\"\"").count();
1668 assert_eq!(docstring_count, 2); }
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}