Skip to main content

zap/
schema.rs

1//! ZAP Schema Compiler
2//!
3//! Inspired by Cap'n Proto's zero-copy serialization approach, the ZAP Schema
4//! Compiler provides a unified schema language with two supported syntaxes:
5//!
6//! - **`.zap` (default)** - Whitespace-significant, clean minimal syntax
7//! - **`.capnp` (compatible)** - Backwards-compatible with existing Cap'n Proto schemas
8//!
9//! The `.zap` syntax is the recommended format for all new schemas, offering
10//! cleaner, more readable definitions where ordering determines field ordinals.
11//!
12//! # ZAP Syntax Example (Recommended)
13//!
14//! ```zap
15//! # ZAP Schema - clean and minimal
16//! # Colons and semicolons are auto-inserted by the compiler
17//!
18//! struct Person
19//!   name Text
20//!   age UInt32
21//!   email Text
22//!   address Address
23//!
24//! struct Address
25//!   street Text
26//!   city Text
27//!   zip Text
28//!
29//! enum Status
30//!   pending
31//!   active
32//!   completed
33//!
34//! interface Greeter
35//!   sayHello (name Text) -> (greeting Text)
36//!   sayGoodbye (name Text) -> ()
37//! ```
38//!
39//! # Cap'n Proto Compatibility
40//!
41//! For migration purposes, the compiler also accepts `.capnp` files with
42//! standard Cap'n Proto syntax including explicit ordinals (@0, @1, etc.)
43//! and brace-delimited blocks. This allows gradual migration from existing
44//! Cap'n Proto schemas to the cleaner ZAP format.
45
46use std::collections::hash_map::DefaultHasher;
47use std::hash::{Hash, Hasher};
48use std::path::Path;
49
50use crate::{Error, Result};
51
52/// Schema format type
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum SchemaFormat {
55    /// ZAP whitespace-significant syntax (default for new schemas)
56    Zap,
57    /// Cap'n Proto syntax (backwards compatible)
58    Capnp,
59}
60
61/// ZAP schema compiler
62///
63/// Supports both `.zap` (whitespace-significant) and `.capnp` (Cap'n Proto
64/// compatible) syntaxes. The `.zap` format is the recommended default for
65/// all new schema definitions.
66pub struct ZapSchema {
67    /// Source content
68    source: String,
69    /// File path (for ID generation)
70    path: String,
71    /// Schema format (auto-detected from extension or explicit)
72    format: SchemaFormat,
73}
74
75#[derive(Debug, Clone)]
76enum Item {
77    Comment(String),
78    Using(String),
79    Const {
80        name: String,
81        typ: String,
82        value: String,
83    },
84    Struct {
85        name: String,
86        fields: Vec<Field>,
87        unions: Vec<Union>,
88        nested: Vec<Item>,
89    },
90    Enum {
91        name: String,
92        variants: Vec<String>,
93    },
94    Interface {
95        name: String,
96        extends: Option<String>,
97        methods: Vec<Method>,
98        nested: Vec<Item>,
99    },
100}
101
102#[derive(Debug, Clone)]
103struct Field {
104    name: String,
105    typ: String,
106    default: Option<String>,
107}
108
109#[derive(Debug, Clone)]
110struct Union {
111    name: Option<String>,
112    fields: Vec<Field>,
113}
114
115#[derive(Debug, Clone)]
116struct Method {
117    name: String,
118    params: Vec<Field>,
119    results: Vec<Field>,
120}
121
122impl ZapSchema {
123    /// Create a new ZAP schema from source (defaults to .zap format)
124    pub fn new(source: &str, path: &str) -> Self {
125        let format = Self::detect_format(path, source);
126        Self {
127            source: source.to_string(),
128            path: path.to_string(),
129            format,
130        }
131    }
132
133    /// Create a schema with explicit format
134    pub fn with_format(source: &str, path: &str, format: SchemaFormat) -> Self {
135        Self {
136            source: source.to_string(),
137            path: path.to_string(),
138            format,
139        }
140    }
141
142    /// Load a ZAP schema from file (auto-detects format from extension)
143    pub fn from_file(path: &Path) -> Result<Self> {
144        let source = std::fs::read_to_string(path)
145            .map_err(|e| Error::Config(format!("failed to read schema: {}", e)))?;
146        let path_str = path.to_str().unwrap_or("schema.zap");
147        let format = Self::detect_format(path_str, &source);
148        Ok(Self {
149            source,
150            path: path_str.to_string(),
151            format,
152        })
153    }
154
155    /// Detect schema format from file extension or content
156    fn detect_format(path: &str, source: &str) -> SchemaFormat {
157        // Check file extension first
158        if path.ends_with(".capnp") {
159            return SchemaFormat::Capnp;
160        }
161        if path.ends_with(".zap") {
162            return SchemaFormat::Zap;
163        }
164
165        // Heuristic: if source contains @0x (file ID) or @N; patterns, it's capnp
166        if source.contains("@0x") || source.contains("@0;") || source.contains("@1;") {
167            return SchemaFormat::Capnp;
168        }
169
170        // Default to ZAP format for new schemas
171        SchemaFormat::Zap
172    }
173
174    /// Get the detected schema format
175    pub fn format(&self) -> &SchemaFormat {
176        &self.format
177    }
178
179    /// Generate a stable ID from a string
180    fn generate_id(seed: &str) -> u64 {
181        let mut hasher = DefaultHasher::new();
182        seed.hash(&mut hasher);
183        // ZAP IDs must have high bit set for uniqueness
184        hasher.finish() | 0x8000_0000_0000_0000
185    }
186
187    /// Parse the schema (dispatches based on format)
188    fn parse(&self) -> Result<Vec<Item>> {
189        match self.format {
190            SchemaFormat::Zap => self.parse_zap(),
191            SchemaFormat::Capnp => self.parse_capnp(),
192        }
193    }
194
195    /// Parse ZAP whitespace-significant format
196    fn parse_zap(&self) -> Result<Vec<Item>> {
197        let mut items = Vec::new();
198        let lines: Vec<&str> = self.source.lines().collect();
199        let mut i = 0;
200
201        while i < lines.len() {
202            let line = lines[i].trim();
203
204            // Skip empty lines
205            if line.is_empty() {
206                i += 1;
207                continue;
208            }
209
210            // Comments
211            if line.starts_with('#') {
212                items.push(Item::Comment(line[1..].trim().to_string()));
213                i += 1;
214                continue;
215            }
216
217            // Using/import
218            if line.starts_with("using ") {
219                items.push(Item::Using(line[6..].trim().to_string()));
220                i += 1;
221                continue;
222            }
223
224            // Const
225            if line.starts_with("const ") {
226                let rest = line[6..].trim();
227                if let Some((name_type, value)) = rest.split_once('=') {
228                    let name_type = name_type.trim();
229                    let value = value.trim().to_string();
230                    if let Some((name, typ)) = name_type.split_once(':') {
231                        items.push(Item::Const {
232                            name: name.trim().to_string(),
233                            typ: typ.trim().to_string(),
234                            value,
235                        });
236                    }
237                }
238                i += 1;
239                continue;
240            }
241
242            // Struct
243            if line.starts_with("struct ") {
244                let name = line[7..].trim().to_string();
245                let (fields, unions, nested, consumed) = self.parse_struct_body(&lines[i + 1..])?;
246                items.push(Item::Struct { name, fields, unions, nested });
247                i += 1 + consumed;
248                continue;
249            }
250
251            // Enum
252            if line.starts_with("enum ") {
253                let name = line[5..].trim().to_string();
254                let (variants, consumed) = self.parse_enum_body(&lines[i + 1..])?;
255                items.push(Item::Enum { name, variants });
256                i += 1 + consumed;
257                continue;
258            }
259
260            // Interface
261            if line.starts_with("interface ") {
262                let rest = line[10..].trim();
263                let (name, extends) = if let Some((n, e)) = rest.split_once("extends") {
264                    (n.trim().to_string(), Some(e.trim().to_string()))
265                } else {
266                    (rest.to_string(), None)
267                };
268                let (methods, nested, consumed) = self.parse_interface_body(&lines[i + 1..])?;
269                items.push(Item::Interface { name, extends, methods, nested });
270                i += 1 + consumed;
271                continue;
272            }
273
274            // Unknown line - skip
275            i += 1;
276        }
277
278        Ok(items)
279    }
280
281    /// Parse Cap'n Proto brace-delimited format (backwards compatible)
282    fn parse_capnp(&self) -> Result<Vec<Item>> {
283        let mut items = Vec::new();
284
285        // Remove comments and normalize whitespace for simpler parsing
286        let mut source = String::new();
287        for line in self.source.lines() {
288            let line = if let Some(idx) = line.find('#') {
289                &line[..idx]
290            } else {
291                line
292            };
293            source.push_str(line);
294            source.push(' ');
295        }
296
297        // Tokenize: split on whitespace and punctuation, keeping punctuation
298        let tokens = self.tokenize_capnp(&source);
299        let mut pos = 0;
300
301        while pos < tokens.len() {
302            let token = &tokens[pos];
303
304            // Skip file ID (@0xABC...)
305            if token.starts_with('@') {
306                pos += 1;
307                if pos < tokens.len() && tokens[pos] == ";" {
308                    pos += 1;
309                }
310                continue;
311            }
312
313            // Using/import
314            if token == "using" {
315                let mut import = String::new();
316                pos += 1;
317                while pos < tokens.len() && tokens[pos] != ";" {
318                    import.push_str(&tokens[pos]);
319                    import.push(' ');
320                    pos += 1;
321                }
322                items.push(Item::Using(import.trim().to_string()));
323                pos += 1; // skip ;
324                continue;
325            }
326
327            // Const
328            if token == "const" {
329                pos += 1;
330                let name = tokens.get(pos).cloned().unwrap_or_default();
331                pos += 1;
332                if tokens.get(pos).map(|s| s.as_str()) == Some(":") {
333                    pos += 1;
334                }
335                let typ = tokens.get(pos).cloned().unwrap_or_default();
336                pos += 1;
337                if tokens.get(pos).map(|s| s.as_str()) == Some("=") {
338                    pos += 1;
339                }
340                let value = tokens.get(pos).cloned().unwrap_or_default();
341                pos += 1;
342                if tokens.get(pos).map(|s| s.as_str()) == Some(";") {
343                    pos += 1;
344                }
345                items.push(Item::Const { name, typ, value });
346                continue;
347            }
348
349            // Struct
350            if token == "struct" {
351                pos += 1;
352                let name = tokens.get(pos).cloned().unwrap_or_default();
353                pos += 1;
354                // Skip optional ID
355                if pos < tokens.len() && tokens[pos].starts_with('@') {
356                    pos += 1;
357                }
358                // Find opening brace
359                while pos < tokens.len() && tokens[pos] != "{" {
360                    pos += 1;
361                }
362                pos += 1; // skip {
363
364                let (fields, unions, nested, new_pos) = self.parse_capnp_struct_body(&tokens, pos)?;
365                pos = new_pos;
366
367                items.push(Item::Struct { name, fields, unions, nested });
368                continue;
369            }
370
371            // Enum
372            if token == "enum" {
373                pos += 1;
374                let name = tokens.get(pos).cloned().unwrap_or_default();
375                pos += 1;
376                // Skip optional ID
377                if pos < tokens.len() && tokens[pos].starts_with('@') {
378                    pos += 1;
379                }
380                // Find opening brace
381                while pos < tokens.len() && tokens[pos] != "{" {
382                    pos += 1;
383                }
384                pos += 1; // skip {
385
386                let (variants, new_pos) = self.parse_capnp_enum_body(&tokens, pos)?;
387                pos = new_pos;
388
389                items.push(Item::Enum { name, variants });
390                continue;
391            }
392
393            // Interface
394            if token == "interface" {
395                pos += 1;
396                let name = tokens.get(pos).cloned().unwrap_or_default();
397                pos += 1;
398
399                // Check for extends
400                let mut extends = None;
401                if pos < tokens.len() && tokens[pos] == "extends" {
402                    pos += 1;
403                    if pos < tokens.len() && tokens[pos] == "(" {
404                        pos += 1;
405                        extends = tokens.get(pos).cloned();
406                        pos += 1;
407                        if pos < tokens.len() && tokens[pos] == ")" {
408                            pos += 1;
409                        }
410                    }
411                }
412
413                // Skip optional ID
414                if pos < tokens.len() && tokens[pos].starts_with('@') {
415                    pos += 1;
416                }
417                // Find opening brace
418                while pos < tokens.len() && tokens[pos] != "{" {
419                    pos += 1;
420                }
421                pos += 1; // skip {
422
423                let (methods, nested, new_pos) = self.parse_capnp_interface_body(&tokens, pos)?;
424                pos = new_pos;
425
426                items.push(Item::Interface { name, extends, methods, nested });
427                continue;
428            }
429
430            pos += 1;
431        }
432
433        Ok(items)
434    }
435
436    /// Tokenize Cap'n Proto source
437    fn tokenize_capnp(&self, source: &str) -> Vec<String> {
438        let mut tokens = Vec::new();
439        let mut current = String::new();
440        let mut in_string = false;
441        let mut chars = source.chars().peekable();
442
443        while let Some(c) = chars.next() {
444            if in_string {
445                current.push(c);
446                if c == '"' {
447                    tokens.push(std::mem::take(&mut current));
448                    in_string = false;
449                }
450                continue;
451            }
452
453            match c {
454                '"' => {
455                    if !current.is_empty() {
456                        tokens.push(std::mem::take(&mut current));
457                    }
458                    current.push(c);
459                    in_string = true;
460                }
461                '{' | '}' | '(' | ')' | ';' | ':' | '=' | ',' => {
462                    if !current.is_empty() {
463                        tokens.push(std::mem::take(&mut current));
464                    }
465                    tokens.push(c.to_string());
466                }
467                '-' if chars.peek() == Some(&'>') => {
468                    if !current.is_empty() {
469                        tokens.push(std::mem::take(&mut current));
470                    }
471                    chars.next();
472                    tokens.push("->".to_string());
473                }
474                c if c.is_whitespace() => {
475                    if !current.is_empty() {
476                        tokens.push(std::mem::take(&mut current));
477                    }
478                }
479                _ => {
480                    current.push(c);
481                }
482            }
483        }
484
485        if !current.is_empty() {
486            tokens.push(current);
487        }
488
489        tokens
490    }
491
492    /// Parse Cap'n Proto struct body
493    fn parse_capnp_struct_body(&self, tokens: &[String], mut pos: usize) -> Result<(Vec<Field>, Vec<Union>, Vec<Item>, usize)> {
494        let mut fields = Vec::new();
495        let mut unions = Vec::new();
496        let mut nested = Vec::new();
497
498        while pos < tokens.len() && tokens[pos] != "}" {
499            let token = &tokens[pos];
500
501            // Nested struct
502            if token == "struct" {
503                pos += 1;
504                let name = tokens.get(pos).cloned().unwrap_or_default();
505                pos += 1;
506                if pos < tokens.len() && tokens[pos].starts_with('@') {
507                    pos += 1;
508                }
509                while pos < tokens.len() && tokens[pos] != "{" {
510                    pos += 1;
511                }
512                pos += 1;
513                let (nfields, nunions, nnested, new_pos) = self.parse_capnp_struct_body(tokens, pos)?;
514                pos = new_pos;
515                nested.push(Item::Struct { name, fields: nfields, unions: nunions, nested: nnested });
516                continue;
517            }
518
519            // Nested enum
520            if token == "enum" {
521                pos += 1;
522                let name = tokens.get(pos).cloned().unwrap_or_default();
523                pos += 1;
524                if pos < tokens.len() && tokens[pos].starts_with('@') {
525                    pos += 1;
526                }
527                while pos < tokens.len() && tokens[pos] != "{" {
528                    pos += 1;
529                }
530                pos += 1;
531                let (variants, new_pos) = self.parse_capnp_enum_body(tokens, pos)?;
532                pos = new_pos;
533                nested.push(Item::Enum { name, variants });
534                continue;
535            }
536
537            // Union
538            if token == "union" {
539                pos += 1;
540                if pos < tokens.len() && tokens[pos] == "{" {
541                    pos += 1;
542                }
543                let mut union_fields = Vec::new();
544                while pos < tokens.len() && tokens[pos] != "}" {
545                    if let Some((field, new_pos)) = self.parse_capnp_field(tokens, pos)? {
546                        union_fields.push(field);
547                        pos = new_pos;
548                    } else {
549                        pos += 1;
550                    }
551                }
552                pos += 1; // skip }
553                unions.push(Union { name: None, fields: union_fields });
554                continue;
555            }
556
557            // Regular field
558            if let Some((field, new_pos)) = self.parse_capnp_field(tokens, pos)? {
559                fields.push(field);
560                pos = new_pos;
561            } else {
562                pos += 1;
563            }
564        }
565
566        if pos < tokens.len() && tokens[pos] == "}" {
567            pos += 1;
568        }
569
570        Ok((fields, unions, nested, pos))
571    }
572
573    /// Parse a single Cap'n Proto field
574    fn parse_capnp_field(&self, tokens: &[String], mut pos: usize) -> Result<Option<(Field, usize)>> {
575        // Field format: name @N :Type = default;
576        if pos >= tokens.len() {
577            return Ok(None);
578        }
579
580        let name = &tokens[pos];
581        if name == "}" || name == "union" || name == "struct" || name == "enum" {
582            return Ok(None);
583        }
584
585        pos += 1;
586
587        // Skip ordinal @N
588        if pos < tokens.len() && tokens[pos].starts_with('@') {
589            pos += 1;
590        }
591
592        // Expect :
593        if pos < tokens.len() && tokens[pos] == ":" {
594            pos += 1;
595        } else {
596            return Ok(None);
597        }
598
599        // Get type (may be compound like List(Foo))
600        let mut typ = String::new();
601        let mut paren_depth = 0;
602        while pos < tokens.len() {
603            let t = &tokens[pos];
604            if t == "=" || (t == ";" && paren_depth == 0) {
605                break;
606            }
607            if t == "(" {
608                paren_depth += 1;
609            }
610            if t == ")" {
611                paren_depth -= 1;
612            }
613            typ.push_str(t);
614            pos += 1;
615        }
616
617        // Check for default value
618        let default = if pos < tokens.len() && tokens[pos] == "=" {
619            pos += 1;
620            let mut val = String::new();
621            while pos < tokens.len() && tokens[pos] != ";" {
622                val.push_str(&tokens[pos]);
623                pos += 1;
624            }
625            Some(val)
626        } else {
627            None
628        };
629
630        // Skip ;
631        if pos < tokens.len() && tokens[pos] == ";" {
632            pos += 1;
633        }
634
635        Ok(Some((Field {
636            name: name.clone(),
637            typ,
638            default,
639        }, pos)))
640    }
641
642    /// Parse Cap'n Proto enum body
643    fn parse_capnp_enum_body(&self, tokens: &[String], mut pos: usize) -> Result<(Vec<String>, usize)> {
644        let mut variants = Vec::new();
645
646        while pos < tokens.len() && tokens[pos] != "}" {
647            let name = tokens[pos].clone();
648            if name != ";" && !name.starts_with('@') {
649                variants.push(name);
650            }
651            pos += 1;
652        }
653
654        if pos < tokens.len() && tokens[pos] == "}" {
655            pos += 1;
656        }
657
658        Ok((variants, pos))
659    }
660
661    /// Parse Cap'n Proto interface body
662    fn parse_capnp_interface_body(&self, tokens: &[String], mut pos: usize) -> Result<(Vec<Method>, Vec<Item>, usize)> {
663        let mut methods = Vec::new();
664        let mut nested = Vec::new();
665
666        while pos < tokens.len() && tokens[pos] != "}" {
667            let token = &tokens[pos];
668
669            // Nested enum
670            if token == "enum" {
671                pos += 1;
672                let name = tokens.get(pos).cloned().unwrap_or_default();
673                pos += 1;
674                if pos < tokens.len() && tokens[pos].starts_with('@') {
675                    pos += 1;
676                }
677                while pos < tokens.len() && tokens[pos] != "{" {
678                    pos += 1;
679                }
680                pos += 1;
681                let (variants, new_pos) = self.parse_capnp_enum_body(tokens, pos)?;
682                pos = new_pos;
683                nested.push(Item::Enum { name, variants });
684                continue;
685            }
686
687            // Nested struct
688            if token == "struct" {
689                pos += 1;
690                let name = tokens.get(pos).cloned().unwrap_or_default();
691                pos += 1;
692                if pos < tokens.len() && tokens[pos].starts_with('@') {
693                    pos += 1;
694                }
695                while pos < tokens.len() && tokens[pos] != "{" {
696                    pos += 1;
697                }
698                pos += 1;
699                let (nfields, nunions, nnested, new_pos) = self.parse_capnp_struct_body(tokens, pos)?;
700                pos = new_pos;
701                nested.push(Item::Struct { name, fields: nfields, unions: nunions, nested: nnested });
702                continue;
703            }
704
705            // Method: name @N (params) -> (results);
706            if let Some((method, new_pos)) = self.parse_capnp_method(tokens, pos)? {
707                methods.push(method);
708                pos = new_pos;
709            } else {
710                pos += 1;
711            }
712        }
713
714        if pos < tokens.len() && tokens[pos] == "}" {
715            pos += 1;
716        }
717
718        Ok((methods, nested, pos))
719    }
720
721    /// Parse a Cap'n Proto method
722    fn parse_capnp_method(&self, tokens: &[String], mut pos: usize) -> Result<Option<(Method, usize)>> {
723        if pos >= tokens.len() {
724            return Ok(None);
725        }
726
727        let name = &tokens[pos];
728        if name == "}" || name == "enum" || name == "struct" {
729            return Ok(None);
730        }
731
732        pos += 1;
733
734        // Skip ordinal @N
735        if pos < tokens.len() && tokens[pos].starts_with('@') {
736            pos += 1;
737        }
738
739        // Parse params
740        let params = if pos < tokens.len() && tokens[pos] == "(" {
741            let (p, new_pos) = self.parse_capnp_param_list(tokens, pos)?;
742            pos = new_pos;
743            p
744        } else {
745            Vec::new()
746        };
747
748        // Skip ->
749        if pos < tokens.len() && tokens[pos] == "->" {
750            pos += 1;
751        }
752
753        // Parse results
754        let results = if pos < tokens.len() && tokens[pos] == "(" {
755            let (r, new_pos) = self.parse_capnp_param_list(tokens, pos)?;
756            pos = new_pos;
757            r
758        } else {
759            Vec::new()
760        };
761
762        // Skip ;
763        if pos < tokens.len() && tokens[pos] == ";" {
764            pos += 1;
765        }
766
767        Ok(Some((Method {
768            name: name.clone(),
769            params,
770            results,
771        }, pos)))
772    }
773
774    /// Parse a Cap'n Proto parameter list
775    fn parse_capnp_param_list(&self, tokens: &[String], mut pos: usize) -> Result<(Vec<Field>, usize)> {
776        let mut params = Vec::new();
777
778        if pos < tokens.len() && tokens[pos] == "(" {
779            pos += 1;
780        }
781
782        while pos < tokens.len() && tokens[pos] != ")" {
783            if tokens[pos] == "," {
784                pos += 1;
785                continue;
786            }
787
788            let name = tokens[pos].clone();
789            pos += 1;
790
791            if pos < tokens.len() && tokens[pos] == ":" {
792                pos += 1;
793
794                // Get type
795                let mut typ = String::new();
796                let mut paren_depth = 0;
797                while pos < tokens.len() {
798                    let t = &tokens[pos];
799                    if (t == "," || t == ")") && paren_depth == 0 {
800                        break;
801                    }
802                    if t == "(" {
803                        paren_depth += 1;
804                    }
805                    if t == ")" && paren_depth > 0 {
806                        paren_depth -= 1;
807                    }
808                    typ.push_str(t);
809                    pos += 1;
810                }
811
812                params.push(Field { name, typ, default: None });
813            }
814        }
815
816        if pos < tokens.len() && tokens[pos] == ")" {
817            pos += 1;
818        }
819
820        Ok((params, pos))
821    }
822
823    /// Parse struct body (indented fields)
824    fn parse_struct_body(&self, lines: &[&str]) -> Result<(Vec<Field>, Vec<Union>, Vec<Item>, usize)> {
825        let mut fields = Vec::new();
826        let mut unions = Vec::new();
827        let mut nested = Vec::new();
828        let mut i = 0;
829
830        while i < lines.len() {
831            let line = lines[i];
832
833            // Check if still indented (part of struct)
834            if !line.starts_with("  ") && !line.starts_with("\t") && !line.trim().is_empty() {
835                break;
836            }
837
838            let trimmed = line.trim();
839            i += 1;
840
841            if trimmed.is_empty() {
842                continue;
843            }
844
845            // Skip comments
846            if trimmed.starts_with('#') {
847                continue;
848            }
849
850            // Nested struct
851            if trimmed.starts_with("struct ") {
852                let name = trimmed[7..].trim().to_string();
853                let (nfields, nunions, nnested, nconsumed) = self.parse_struct_body(&lines[i..])?;
854                nested.push(Item::Struct { name, fields: nfields, unions: nunions, nested: nnested });
855                i += nconsumed;
856                continue;
857            }
858
859            // Nested enum
860            if trimmed.starts_with("enum ") {
861                let name = trimmed[5..].trim().to_string();
862                let (variants, nconsumed) = self.parse_enum_body(&lines[i..])?;
863                nested.push(Item::Enum { name, variants });
864                i += nconsumed;
865                continue;
866            }
867
868            // Union
869            if trimmed.starts_with("union") {
870                let union_name = if trimmed.len() > 5 {
871                    Some(trimmed[5..].trim().to_string())
872                } else {
873                    None
874                };
875
876                // Parse union fields (more deeply indented)
877                let mut union_fields = Vec::new();
878                while i < lines.len() {
879                    let uline = lines[i];
880                    if !uline.starts_with("    ") && !uline.starts_with("\t\t") {
881                        if !uline.trim().is_empty() && (uline.starts_with("  ") || uline.starts_with("\t")) {
882                            break;
883                        }
884                    }
885                    let utrimmed = uline.trim();
886                    if utrimmed.is_empty() {
887                        i += 1;
888                        continue;
889                    }
890                    if !uline.starts_with("    ") && !uline.starts_with("\t\t") {
891                        break;
892                    }
893                    if let Some(field) = self.parse_field(utrimmed) {
894                        union_fields.push(field);
895                    }
896                    i += 1;
897                }
898
899                unions.push(Union {
900                    name: union_name.filter(|n| !n.is_empty()),
901                    fields: union_fields,
902                });
903                continue;
904            }
905
906            // Regular field
907            if let Some(field) = self.parse_field(trimmed) {
908                fields.push(field);
909            }
910        }
911
912        Ok((fields, unions, nested, i))
913    }
914
915    /// Parse a single field
916    fn parse_field(&self, line: &str) -> Option<Field> {
917        // Format: name Type = default (new clean format)
918        // or: name :Type = default (legacy format with colon)
919        let (name_type, default) = if let Some((nt, d)) = line.split_once('=') {
920            (nt.trim(), Some(d.trim().to_string()))
921        } else {
922            (line, None)
923        };
924
925        // Try colon format first (legacy): name :Type
926        if let Some((name, typ)) = name_type.split_once(':') {
927            return Some(Field {
928                name: name.trim().to_string(),
929                typ: typ.trim().to_string(),
930                default,
931            });
932        }
933
934        // Clean format (new): name Type
935        // Split on first whitespace to get name, rest is type
936        let parts: Vec<&str> = name_type.splitn(2, char::is_whitespace).collect();
937        if parts.len() == 2 {
938            let name = parts[0].trim();
939            let typ = parts[1].trim();
940            // Avoid matching keywords as field names
941            if !typ.is_empty() && !["struct", "enum", "interface", "union", "using", "const"].contains(&name) {
942                return Some(Field {
943                    name: name.to_string(),
944                    typ: typ.to_string(),
945                    default,
946                });
947            }
948        }
949
950        None
951    }
952
953    /// Parse enum body
954    fn parse_enum_body(&self, lines: &[&str]) -> Result<(Vec<String>, usize)> {
955        let mut variants = Vec::new();
956        let mut consumed = 0;
957
958        for line in lines {
959            if !line.starts_with("  ") && !line.starts_with("\t") && !line.trim().is_empty() {
960                break;
961            }
962
963            let trimmed = line.trim();
964            consumed += 1;
965
966            if trimmed.is_empty() || trimmed.starts_with('#') {
967                continue;
968            }
969
970            variants.push(trimmed.to_string());
971        }
972
973        Ok((variants, consumed))
974    }
975
976    /// Parse interface body
977    fn parse_interface_body(&self, lines: &[&str]) -> Result<(Vec<Method>, Vec<Item>, usize)> {
978        let mut methods = Vec::new();
979        let mut nested = Vec::new();
980        let mut i = 0;
981
982        while i < lines.len() {
983            let line = lines[i];
984
985            if !line.starts_with("  ") && !line.starts_with("\t") && !line.trim().is_empty() {
986                break;
987            }
988
989            let trimmed = line.trim();
990            i += 1;
991
992            if trimmed.is_empty() || trimmed.starts_with('#') {
993                continue;
994            }
995
996            // Nested enum
997            if trimmed.starts_with("enum ") {
998                let name = trimmed[5..].trim().to_string();
999                let (variants, nconsumed) = self.parse_enum_body(&lines[i..])?;
1000                nested.push(Item::Enum { name, variants });
1001                i += nconsumed;
1002                continue;
1003            }
1004
1005            // Nested struct
1006            if trimmed.starts_with("struct ") {
1007                let name = trimmed[7..].trim().to_string();
1008                let (nfields, nunions, nnested, nconsumed) = self.parse_struct_body(&lines[i..])?;
1009                nested.push(Item::Struct { name, fields: nfields, unions: nunions, nested: nnested });
1010                i += nconsumed;
1011                continue;
1012            }
1013
1014            // Method
1015            if let Some(method) = self.parse_method(trimmed) {
1016                methods.push(method);
1017            }
1018        }
1019
1020        Ok((methods, nested, i))
1021    }
1022
1023    /// Parse a method signature
1024    fn parse_method(&self, line: &str) -> Option<Method> {
1025        // Format: methodName (param :Type, ...) -> (result :Type, ...)
1026        // or: methodName (param :Type) -> ()
1027        // or: methodName () -> (result :Type)
1028
1029        let (name_params, results_str) = line.split_once("->")?;
1030        let name_params = name_params.trim();
1031
1032        // Extract method name and params
1033        let (name, params_str) = if let Some(idx) = name_params.find('(') {
1034            let name = name_params[..idx].trim();
1035            let params = name_params[idx..].trim();
1036            (name, params)
1037        } else {
1038            (name_params, "()")
1039        };
1040
1041        let params = self.parse_param_list(params_str);
1042        let results = self.parse_param_list(results_str.trim());
1043
1044        Some(Method {
1045            name: name.to_string(),
1046            params,
1047            results,
1048        })
1049    }
1050
1051    /// Parse a parameter list like "(name Text, age UInt32)" or "(name :Text, age :UInt32)"
1052    fn parse_param_list(&self, s: &str) -> Vec<Field> {
1053        let s = s.trim();
1054        if s == "()" || s.is_empty() {
1055            return Vec::new();
1056        }
1057
1058        // Remove parens
1059        let inner = s.trim_start_matches('(').trim_end_matches(')').trim();
1060        if inner.is_empty() {
1061            return Vec::new();
1062        }
1063
1064        // Handle nested parentheses in types like List(Text)
1065        let mut params = Vec::new();
1066        let mut current = String::new();
1067        let mut depth = 0;
1068
1069        for c in inner.chars() {
1070            match c {
1071                '(' => {
1072                    depth += 1;
1073                    current.push(c);
1074                }
1075                ')' => {
1076                    depth -= 1;
1077                    current.push(c);
1078                }
1079                ',' if depth == 0 => {
1080                    if let Some(field) = self.parse_field(current.trim()) {
1081                        params.push(field);
1082                    }
1083                    current.clear();
1084                }
1085                _ => {
1086                    current.push(c);
1087                }
1088            }
1089        }
1090
1091        // Handle last parameter
1092        if !current.is_empty() {
1093            if let Some(field) = self.parse_field(current.trim()) {
1094                params.push(field);
1095            }
1096        }
1097
1098        params
1099    }
1100
1101    /// Compile to ZAP wire format schema (internal representation)
1102    pub fn compile(&self) -> Result<String> {
1103        let items = self.parse()?;
1104        let mut output = String::new();
1105
1106        // File ID
1107        let file_id = Self::generate_id(&self.path);
1108        output.push_str(&format!("@{:#018x};\n\n", file_id));
1109
1110        for item in items {
1111            self.emit_item(&mut output, &item, 0)?;
1112        }
1113
1114        Ok(output)
1115    }
1116
1117    /// Emit an item at the given indentation level
1118    fn emit_item(&self, output: &mut String, item: &Item, indent: usize) -> Result<()> {
1119        let pad = "  ".repeat(indent);
1120
1121        match item {
1122            Item::Comment(text) => {
1123                output.push_str(&format!("{}# {}\n", pad, text));
1124            }
1125            Item::Using(import) => {
1126                output.push_str(&format!("{}using {};\n", pad, import));
1127            }
1128            Item::Const { name, typ, value } => {
1129                output.push_str(&format!("{}const {} :{} = {};\n", pad, name, typ, value));
1130            }
1131            Item::Struct { name, fields, unions, nested } => {
1132                let struct_id = Self::generate_id(&format!("{}:{}", self.path, name));
1133                output.push_str(&format!("{}struct {} @{:#018x} {{\n", pad, name, struct_id));
1134
1135                let mut ordinal = 0u16;
1136
1137                for field in fields {
1138                    let default_str = field
1139                        .default
1140                        .as_ref()
1141                        .map(|d| format!(" = {}", d))
1142                        .unwrap_or_default();
1143                    output.push_str(&format!(
1144                        "{}  {} @{} :{}{};\n",
1145                        pad, field.name, ordinal, field.typ, default_str
1146                    ));
1147                    ordinal += 1;
1148                }
1149
1150                for union in unions {
1151                    if let Some(ref uname) = union.name {
1152                        output.push_str(&format!("{}  {} :union {{\n", pad, uname));
1153                    } else {
1154                        output.push_str(&format!("{}  union {{\n", pad));
1155                    }
1156                    for field in &union.fields {
1157                        let default_str = field
1158                            .default
1159                            .as_ref()
1160                            .map(|d| format!(" = {}", d))
1161                            .unwrap_or_default();
1162                        output.push_str(&format!(
1163                            "{}    {} @{} :{}{};\n",
1164                            pad, field.name, ordinal, field.typ, default_str
1165                        ));
1166                        ordinal += 1;
1167                    }
1168                    output.push_str(&format!("{}  }}\n", pad));
1169                }
1170
1171                for nested_item in nested {
1172                    self.emit_item(output, nested_item, indent + 1)?;
1173                }
1174
1175                output.push_str(&format!("{}}}\n\n", pad));
1176            }
1177            Item::Enum { name, variants } => {
1178                let enum_id = Self::generate_id(&format!("{}:{}", self.path, name));
1179                output.push_str(&format!("{}enum {} @{:#018x} {{\n", pad, name, enum_id));
1180
1181                for (i, variant) in variants.iter().enumerate() {
1182                    output.push_str(&format!("{}  {} @{};\n", pad, variant, i));
1183                }
1184
1185                output.push_str(&format!("{}}}\n\n", pad));
1186            }
1187            Item::Interface { name, extends, methods, nested } => {
1188                let iface_id = Self::generate_id(&format!("{}:{}", self.path, name));
1189                let extends_str = extends
1190                    .as_ref()
1191                    .map(|e| format!(" extends({})", e))
1192                    .unwrap_or_default();
1193                output.push_str(&format!(
1194                    "{}interface {}{} @{:#018x} {{\n",
1195                    pad, name, extends_str, iface_id
1196                ));
1197
1198                for (i, method) in methods.iter().enumerate() {
1199                    let params_str = if method.params.is_empty() {
1200                        "()".to_string()
1201                    } else {
1202                        let ps: Vec<String> = method
1203                            .params
1204                            .iter()
1205                            .map(|p| format!("{} :{}", p.name, p.typ))
1206                            .collect();
1207                        format!("({})", ps.join(", "))
1208                    };
1209
1210                    let results_str = if method.results.is_empty() {
1211                        "()".to_string()
1212                    } else {
1213                        let rs: Vec<String> = method
1214                            .results
1215                            .iter()
1216                            .map(|r| format!("{} :{}", r.name, r.typ))
1217                            .collect();
1218                        format!("({})", rs.join(", "))
1219                    };
1220
1221                    output.push_str(&format!(
1222                        "{}  {} @{} {} -> {};\n",
1223                        pad, method.name, i, params_str, results_str
1224                    ));
1225                }
1226
1227                for nested_item in nested {
1228                    self.emit_item(output, nested_item, indent + 1)?;
1229                }
1230
1231                output.push_str(&format!("{}}}\n\n", pad));
1232            }
1233        }
1234
1235        Ok(())
1236    }
1237
1238    /// Compile to the legacy format (for compatibility)
1239    #[deprecated(note = "use compile() instead")]
1240    pub fn to_capnp(&self) -> Result<String> {
1241        self.compile()
1242    }
1243
1244    /// Convert any schema to clean ZAP whitespace-significant format
1245    ///
1246    /// This is useful for migrating existing .capnp schemas to the
1247    /// recommended .zap format.
1248    pub fn to_zap(&self) -> Result<String> {
1249        let items = self.parse()?;
1250        let mut output = String::new();
1251
1252        output.push_str("# ZAP Schema - converted to whitespace format\n\n");
1253
1254        for item in items {
1255            self.emit_zap_item(&mut output, &item, 0)?;
1256        }
1257
1258        Ok(output)
1259    }
1260
1261    /// Emit an item in ZAP whitespace format
1262    fn emit_zap_item(&self, output: &mut String, item: &Item, indent: usize) -> Result<()> {
1263        let pad = "  ".repeat(indent);
1264
1265        match item {
1266            Item::Comment(text) => {
1267                output.push_str(&format!("{}# {}\n", pad, text));
1268            }
1269            Item::Using(import) => {
1270                output.push_str(&format!("{}using {}\n", pad, import));
1271            }
1272            Item::Const { name, typ, value } => {
1273                output.push_str(&format!("{}const {} :{} = {}\n", pad, name, typ, value));
1274            }
1275            Item::Struct { name, fields, unions, nested } => {
1276                output.push_str(&format!("{}struct {}\n", pad, name));
1277
1278                for field in fields {
1279                    let default_str = field
1280                        .default
1281                        .as_ref()
1282                        .map(|d| format!(" = {}", d))
1283                        .unwrap_or_default();
1284                    output.push_str(&format!(
1285                        "{}  {} :{}{}\n",
1286                        pad, field.name, field.typ, default_str
1287                    ));
1288                }
1289
1290                for union in unions {
1291                    if let Some(ref uname) = union.name {
1292                        output.push_str(&format!("{}  union {}\n", pad, uname));
1293                    } else {
1294                        output.push_str(&format!("{}  union\n", pad));
1295                    }
1296                    for field in &union.fields {
1297                        let default_str = field
1298                            .default
1299                            .as_ref()
1300                            .map(|d| format!(" = {}", d))
1301                            .unwrap_or_default();
1302                        output.push_str(&format!(
1303                            "{}    {} :{}{}\n",
1304                            pad, field.name, field.typ, default_str
1305                        ));
1306                    }
1307                }
1308
1309                for nested_item in nested {
1310                    output.push('\n');
1311                    self.emit_zap_item(output, nested_item, indent + 1)?;
1312                }
1313
1314                output.push('\n');
1315            }
1316            Item::Enum { name, variants } => {
1317                output.push_str(&format!("{}enum {}\n", pad, name));
1318                for variant in variants {
1319                    output.push_str(&format!("{}  {}\n", pad, variant));
1320                }
1321                output.push('\n');
1322            }
1323            Item::Interface { name, extends, methods, nested } => {
1324                let extends_str = extends
1325                    .as_ref()
1326                    .map(|e| format!(" extends {}", e))
1327                    .unwrap_or_default();
1328                output.push_str(&format!("{}interface {}{}\n", pad, name, extends_str));
1329
1330                for method in methods {
1331                    let params_str = if method.params.is_empty() {
1332                        "()".to_string()
1333                    } else {
1334                        let ps: Vec<String> = method
1335                            .params
1336                            .iter()
1337                            .map(|p| format!("{} :{}", p.name, p.typ))
1338                            .collect();
1339                        format!("({})", ps.join(", "))
1340                    };
1341
1342                    let results_str = if method.results.is_empty() {
1343                        "()".to_string()
1344                    } else {
1345                        let rs: Vec<String> = method
1346                            .results
1347                            .iter()
1348                            .map(|r| format!("{} :{}", r.name, r.typ))
1349                            .collect();
1350                        format!("({})", rs.join(", "))
1351                    };
1352
1353                    output.push_str(&format!(
1354                        "{}  {} {} -> {}\n",
1355                        pad, method.name, params_str, results_str
1356                    ));
1357                }
1358
1359                for nested_item in nested {
1360                    output.push('\n');
1361                    self.emit_zap_item(output, nested_item, indent + 1)?;
1362                }
1363
1364                output.push('\n');
1365            }
1366        }
1367
1368        Ok(())
1369    }
1370
1371    /// Compile and write to file
1372    pub fn write(&self, output_path: &Path) -> Result<()> {
1373        let compiled = self.compile()?;
1374        std::fs::write(output_path, compiled)
1375            .map_err(|e| Error::Config(format!("failed to write schema: {}", e)))?;
1376        Ok(())
1377    }
1378
1379    /// Generate Rust structs from schema
1380    pub fn to_rust(&self) -> Result<String> {
1381        let items = self.parse()?;
1382        let mut output = String::new();
1383
1384        output.push_str("//! Generated by ZAP Schema Compiler\n");
1385        output.push_str("//! Do not edit manually\n\n");
1386        output.push_str("use serde::{Serialize, Deserialize};\n\n");
1387
1388        for item in items {
1389            self.emit_rust_item(&mut output, &item)?;
1390        }
1391
1392        Ok(output)
1393    }
1394
1395    /// Emit Rust code for an item
1396    fn emit_rust_item(&self, output: &mut String, item: &Item) -> Result<()> {
1397        match item {
1398            Item::Comment(text) => {
1399                output.push_str(&format!("/// {}\n", text));
1400            }
1401            Item::Struct { name, fields, unions, nested } => {
1402                output.push_str("#[derive(Debug, Clone, Serialize, Deserialize)]\n");
1403                output.push_str(&format!("pub struct {} {{\n", name));
1404
1405                for field in fields {
1406                    let rust_type = self.zap_type_to_rust(&field.typ);
1407                    output.push_str(&format!("    pub {}: {},\n",
1408                        to_snake_case(&field.name), rust_type));
1409                }
1410
1411                // Handle unions as enum fields
1412                for (i, union) in unions.iter().enumerate() {
1413                    let union_name = union.name.as_ref()
1414                        .cloned()
1415                        .unwrap_or_else(|| format!("{}Union{}", name, i));
1416                    output.push_str(&format!("    pub {}: {},\n",
1417                        to_snake_case(&union_name), union_name));
1418                }
1419
1420                output.push_str("}\n\n");
1421
1422                // Emit union enums
1423                for (i, union) in unions.iter().enumerate() {
1424                    let union_name = union.name.as_ref()
1425                        .cloned()
1426                        .unwrap_or_else(|| format!("{}Union{}", name, i));
1427                    output.push_str("#[derive(Debug, Clone, Serialize, Deserialize)]\n");
1428                    output.push_str(&format!("pub enum {} {{\n", union_name));
1429                    for field in &union.fields {
1430                        let rust_type = self.zap_type_to_rust(&field.typ);
1431                        output.push_str(&format!("    {}({}),\n",
1432                            to_pascal_case(&field.name), rust_type));
1433                    }
1434                    output.push_str("}\n\n");
1435                }
1436
1437                // Emit nested items
1438                for nested_item in nested {
1439                    self.emit_rust_item(output, nested_item)?;
1440                }
1441            }
1442            Item::Enum { name, variants } => {
1443                output.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\n");
1444                output.push_str(&format!("pub enum {} {{\n", name));
1445                for variant in variants {
1446                    output.push_str(&format!("    {},\n", to_pascal_case(variant)));
1447                }
1448                output.push_str("}\n\n");
1449            }
1450            Item::Interface { name, methods, nested, .. } => {
1451                // Generate trait
1452                output.push_str("#[async_trait::async_trait]\n");
1453                output.push_str(&format!("pub trait {} {{\n", name));
1454                for method in methods {
1455                    let params_str = method.params.iter()
1456                        .map(|p| format!("{}: {}", to_snake_case(&p.name), self.zap_type_to_rust(&p.typ)))
1457                        .collect::<Vec<_>>()
1458                        .join(", ");
1459
1460                    let result_type = if method.results.is_empty() {
1461                        "()".to_string()
1462                    } else if method.results.len() == 1 {
1463                        self.zap_type_to_rust(&method.results[0].typ)
1464                    } else {
1465                        let types: Vec<_> = method.results.iter()
1466                            .map(|r| self.zap_type_to_rust(&r.typ))
1467                            .collect();
1468                        format!("({})", types.join(", "))
1469                    };
1470
1471                    output.push_str(&format!("    async fn {}(&self{}{}) -> Result<{}, ZapError>;\n",
1472                        to_snake_case(&method.name),
1473                        if params_str.is_empty() { "" } else { ", " },
1474                        params_str,
1475                        result_type
1476                    ));
1477                }
1478                output.push_str("}\n\n");
1479
1480                // Emit nested items
1481                for nested_item in nested {
1482                    self.emit_rust_item(output, nested_item)?;
1483                }
1484            }
1485            _ => {}
1486        }
1487
1488        Ok(())
1489    }
1490
1491    /// Convert ZAP type to Rust type
1492    fn zap_type_to_rust(&self, typ: &str) -> String {
1493        match typ {
1494            "Text" => "String".to_string(),
1495            "Data" => "Vec<u8>".to_string(),
1496            "Void" => "()".to_string(),
1497            "Bool" => "bool".to_string(),
1498            "Int8" => "i8".to_string(),
1499            "Int16" => "i16".to_string(),
1500            "Int32" => "i32".to_string(),
1501            "Int64" => "i64".to_string(),
1502            "UInt8" => "u8".to_string(),
1503            "UInt16" => "u16".to_string(),
1504            "UInt32" => "u32".to_string(),
1505            "UInt64" => "u64".to_string(),
1506            "Float32" => "f32".to_string(),
1507            "Float64" => "f64".to_string(),
1508            t if t.starts_with("List(") && t.ends_with(")") => {
1509                let inner = &t[5..t.len()-1];
1510                format!("Vec<{}>", self.zap_type_to_rust(inner))
1511            }
1512            t => t.to_string(),  // Custom types pass through
1513        }
1514    }
1515}
1516
1517/// Convert to snake_case
1518fn to_snake_case(s: &str) -> String {
1519    let mut result = String::new();
1520    for (i, c) in s.chars().enumerate() {
1521        if c.is_uppercase() {
1522            if i > 0 {
1523                result.push('_');
1524            }
1525            result.push(c.to_lowercase().next().unwrap());
1526        } else {
1527            result.push(c);
1528        }
1529    }
1530    result
1531}
1532
1533/// Convert to PascalCase
1534fn to_pascal_case(s: &str) -> String {
1535    let mut result = String::new();
1536    let mut capitalize_next = true;
1537    for c in s.chars() {
1538        if c == '_' {
1539            capitalize_next = true;
1540        } else if capitalize_next {
1541            result.push(c.to_uppercase().next().unwrap());
1542            capitalize_next = false;
1543        } else {
1544            result.push(c);
1545        }
1546    }
1547    result
1548}
1549
1550/// Compile a .zap file
1551pub fn transpile(input: &Path, output: &Path) -> Result<()> {
1552    let schema = ZapSchema::from_file(input)?;
1553    schema.write(output)
1554}
1555
1556/// Compile .zap source string
1557pub fn transpile_str(source: &str, name: &str) -> Result<String> {
1558    let schema = ZapSchema::new(source, name);
1559    schema.compile()
1560}
1561
1562/// Compile .zap source to Rust code
1563pub fn compile_to_rust(source: &str, name: &str) -> Result<String> {
1564    let schema = ZapSchema::new(source, name);
1565    schema.to_rust()
1566}
1567
1568/// Convert .capnp source to .zap whitespace format
1569///
1570/// This is the recommended migration path for existing Cap'n Proto schemas.
1571pub fn capnp_to_zap(source: &str) -> Result<String> {
1572    let schema = ZapSchema::with_format(source, "input.capnp", SchemaFormat::Capnp);
1573    schema.to_zap()
1574}
1575
1576/// Convert a .capnp file to .zap file
1577pub fn migrate_capnp_to_zap(input: &Path, output: &Path) -> Result<()> {
1578    let schema = ZapSchema::from_file(input)?;
1579    let zap_source = schema.to_zap()?;
1580    std::fs::write(output, zap_source)
1581        .map_err(|e| Error::Config(format!("failed to write schema: {}", e)))?;
1582    Ok(())
1583}
1584
1585#[cfg(test)]
1586mod tests {
1587    use super::*;
1588
1589    #[test]
1590    fn test_simple_struct() {
1591        // Test new clean syntax (no colons)
1592        let source = r#"
1593struct Person
1594  name Text
1595  age UInt32
1596  email Text
1597"#;
1598        let result = transpile_str(source, "test.zap").unwrap();
1599        assert!(result.contains("struct Person"));
1600        assert!(result.contains("name @0 :Text"));
1601        assert!(result.contains("age @1 :UInt32"));
1602        assert!(result.contains("email @2 :Text"));
1603    }
1604
1605    #[test]
1606    fn test_simple_struct_legacy_syntax() {
1607        // Test legacy syntax with colons (backwards compatible)
1608        let source = r#"
1609struct Person
1610  name :Text
1611  age :UInt32
1612  email :Text
1613"#;
1614        let result = transpile_str(source, "test.zap").unwrap();
1615        assert!(result.contains("struct Person"));
1616        assert!(result.contains("name @0 :Text"));
1617        assert!(result.contains("age @1 :UInt32"));
1618        assert!(result.contains("email @2 :Text"));
1619    }
1620
1621    #[test]
1622    fn test_enum() {
1623        let source = r#"
1624enum Status
1625  pending
1626  active
1627  completed
1628  failed
1629"#;
1630        let result = transpile_str(source, "test.zap").unwrap();
1631        assert!(result.contains("enum Status"));
1632        assert!(result.contains("pending @0"));
1633        assert!(result.contains("active @1"));
1634        assert!(result.contains("completed @2"));
1635        assert!(result.contains("failed @3"));
1636    }
1637
1638    #[test]
1639    fn test_interface() {
1640        // Test new clean syntax (no colons)
1641        let source = r#"
1642interface Greeter
1643  sayHello (name Text) -> (greeting Text)
1644  sayGoodbye (name Text) -> ()
1645"#;
1646        let result = transpile_str(source, "test.zap").unwrap();
1647        assert!(result.contains("interface Greeter"));
1648        assert!(result.contains("sayHello @0"));
1649        assert!(result.contains("sayGoodbye @1"));
1650    }
1651
1652    #[test]
1653    fn test_interface_legacy_syntax() {
1654        // Test legacy syntax with colons (backwards compatible)
1655        let source = r#"
1656interface Greeter
1657  sayHello (name :Text) -> (greeting :Text)
1658  sayGoodbye (name :Text) -> ()
1659"#;
1660        let result = transpile_str(source, "test.zap").unwrap();
1661        assert!(result.contains("interface Greeter"));
1662        assert!(result.contains("sayHello @0"));
1663        assert!(result.contains("sayGoodbye @1"));
1664    }
1665
1666    #[test]
1667    fn test_struct_with_defaults() {
1668        // Test new clean syntax with defaults
1669        let source = r#"
1670struct Config
1671  host Text = "localhost"
1672  port UInt16 = 9999
1673  enabled Bool = true
1674"#;
1675        let result = transpile_str(source, "test.zap").unwrap();
1676        assert!(result.contains("host @0 :Text = \"localhost\""));
1677        assert!(result.contains("port @1 :UInt16 = 9999"));
1678        assert!(result.contains("enabled @2 :Bool = true"));
1679    }
1680
1681    #[test]
1682    fn test_comments_preserved() {
1683        let source = r#"
1684# This is a comment
1685struct Foo
1686  bar Text
1687"#;
1688        let result = transpile_str(source, "test.zap").unwrap();
1689        assert!(result.contains("# This is a comment"));
1690    }
1691
1692    #[test]
1693    fn test_using_import() {
1694        let source = r#"
1695using import "other.zap"
1696using Foo = import "foo.zap"
1697
1698struct Bar
1699  x Int32
1700"#;
1701        let result = transpile_str(source, "test.zap").unwrap();
1702        assert!(result.contains("using import \"other.zap\""));
1703        assert!(result.contains("using Foo = import \"foo.zap\""));
1704    }
1705
1706    #[test]
1707    fn test_interface_extends() {
1708        let source = r#"
1709interface Child extends Parent
1710  childMethod () -> ()
1711"#;
1712        let result = transpile_str(source, "test.zap").unwrap();
1713        assert!(result.contains("interface Child extends(Parent)"));
1714    }
1715
1716    #[test]
1717    fn test_const() {
1718        let source = r#"
1719const version :Text = "1.0.0"
1720const maxSize :UInt32 = 1024
1721"#;
1722        let result = transpile_str(source, "test.zap").unwrap();
1723        assert!(result.contains("const version :Text = \"1.0.0\""));
1724        assert!(result.contains("const maxSize :UInt32 = 1024"));
1725    }
1726
1727    #[test]
1728    fn test_rust_codegen() {
1729        let source = r#"
1730struct Person
1731  name Text
1732  age UInt32
1733
1734enum Status
1735  pending
1736  active
1737"#;
1738        let result = compile_to_rust(source, "test.zap").unwrap();
1739        assert!(result.contains("pub struct Person"));
1740        assert!(result.contains("pub name: String"));
1741        assert!(result.contains("pub age: u32"));
1742        assert!(result.contains("pub enum Status"));
1743        assert!(result.contains("Pending"));
1744        assert!(result.contains("Active"));
1745    }
1746
1747    #[test]
1748    fn test_complex_types() {
1749        // Test new clean syntax with complex types like List(Type)
1750        let source = r#"
1751struct Order
1752  items List(Item)
1753  quantities List(Int32)
1754  tags List(Text)
1755
1756struct Item
1757  name Text
1758  price Float64
1759"#;
1760        let result = transpile_str(source, "test.zap").unwrap();
1761        assert!(result.contains("items @0 :List(Item)"));
1762        assert!(result.contains("quantities @1 :List(Int32)"));
1763        assert!(result.contains("tags @2 :List(Text)"));
1764    }
1765
1766    #[test]
1767    fn test_interface_with_complex_params() {
1768        // Test interface with List types in parameters
1769        let source = r#"
1770interface Calculator
1771  sum (numbers List(Float64)) -> (total Float64)
1772  average (values List(Float64)) -> (avg Float64)
1773"#;
1774        let result = transpile_str(source, "test.zap").unwrap();
1775        // Output format: method @ordinal (params) -> (results);
1776        assert!(result.contains("sum @0"));
1777        assert!(result.contains("numbers :List(Float64)"));
1778        assert!(result.contains("total :Float64"));
1779        assert!(result.contains("average @1"));
1780        assert!(result.contains("values :List(Float64)"));
1781        assert!(result.contains("avg :Float64"));
1782    }
1783
1784    // Cap'n Proto backwards compatibility tests
1785
1786    #[test]
1787    fn test_capnp_struct() {
1788        let source = r#"
1789@0x9eb32e19f86ee174;
1790
1791struct Person @0xabcd1234 {
1792  name @0 :Text;
1793  age @1 :UInt32;
1794  email @2 :Text;
1795}
1796"#;
1797        let schema = ZapSchema::new(source, "test.capnp");
1798        assert_eq!(schema.format, SchemaFormat::Capnp);
1799        let result = schema.compile().unwrap();
1800        assert!(result.contains("struct Person"));
1801        assert!(result.contains("name @0 :Text"));
1802        assert!(result.contains("age @1 :UInt32"));
1803        assert!(result.contains("email @2 :Text"));
1804    }
1805
1806    #[test]
1807    fn test_capnp_enum() {
1808        let source = r#"
1809@0x9eb32e19f86ee174;
1810
1811enum Status @0x1234 {
1812  pending @0;
1813  active @1;
1814  completed @2;
1815}
1816"#;
1817        let schema = ZapSchema::new(source, "test.capnp");
1818        assert_eq!(schema.format, SchemaFormat::Capnp);
1819        let result = schema.compile().unwrap();
1820        assert!(result.contains("enum Status"));
1821        assert!(result.contains("pending @0"));
1822        assert!(result.contains("active @1"));
1823        assert!(result.contains("completed @2"));
1824    }
1825
1826    #[test]
1827    fn test_capnp_interface() {
1828        let source = r#"
1829@0x9eb32e19f86ee174;
1830
1831interface Greeter @0xabcd {
1832  sayHello @0 (name :Text) -> (greeting :Text);
1833  sayGoodbye @1 (name :Text) -> ();
1834}
1835"#;
1836        let schema = ZapSchema::new(source, "test.capnp");
1837        assert_eq!(schema.format, SchemaFormat::Capnp);
1838        let result = schema.compile().unwrap();
1839        assert!(result.contains("interface Greeter"));
1840        assert!(result.contains("sayHello @0"));
1841        assert!(result.contains("sayGoodbye @1"));
1842    }
1843
1844    #[test]
1845    fn test_capnp_interface_extends() {
1846        let source = r#"
1847@0x9eb32e19f86ee174;
1848
1849interface Child extends(Parent) @0xabcd {
1850  childMethod @0 () -> ();
1851}
1852"#;
1853        let schema = ZapSchema::new(source, "test.capnp");
1854        let result = schema.compile().unwrap();
1855        assert!(result.contains("interface Child extends(Parent)"));
1856    }
1857
1858    #[test]
1859    fn test_capnp_nested_types() {
1860        let source = r#"
1861@0x9eb32e19f86ee174;
1862
1863struct Outer @0x1234 {
1864  value @0 :Text;
1865
1866  struct Inner @0x5678 {
1867    x @0 :Int32;
1868  }
1869
1870  enum InnerEnum @0x9abc {
1871    a @0;
1872    b @1;
1873  }
1874}
1875"#;
1876        let schema = ZapSchema::new(source, "test.capnp");
1877        let result = schema.compile().unwrap();
1878        assert!(result.contains("struct Outer"));
1879        assert!(result.contains("struct Inner"));
1880        assert!(result.contains("enum InnerEnum"));
1881    }
1882
1883    #[test]
1884    fn test_format_detection_by_extension() {
1885        let zap_schema = ZapSchema::new("struct Foo { }", "test.zap");
1886        assert_eq!(zap_schema.format, SchemaFormat::Zap);
1887
1888        let capnp_schema = ZapSchema::new("struct Foo { }", "test.capnp");
1889        assert_eq!(capnp_schema.format, SchemaFormat::Capnp);
1890    }
1891
1892    #[test]
1893    fn test_format_detection_by_content() {
1894        // Content with @0; ordinals detected as capnp
1895        let source = "@0x1234; struct Foo { bar @0; }";
1896        let schema = ZapSchema::new(source, "test.schema");
1897        assert_eq!(schema.format, SchemaFormat::Capnp);
1898
1899        // Clean content defaults to zap (new syntax)
1900        let source = "struct Foo\n  bar Text";
1901        let schema = ZapSchema::new(source, "test.schema");
1902        assert_eq!(schema.format, SchemaFormat::Zap);
1903
1904        // Legacy zap syntax also detected as zap
1905        let source = "struct Foo\n  bar :Text";
1906        let schema = ZapSchema::new(source, "test.schema");
1907        assert_eq!(schema.format, SchemaFormat::Zap);
1908    }
1909
1910    #[test]
1911    fn test_explicit_format() {
1912        let source = "struct Foo { bar @0 :Text; }";
1913        let schema = ZapSchema::with_format(source, "test.txt", SchemaFormat::Zap);
1914        assert_eq!(schema.format, SchemaFormat::Zap);
1915    }
1916
1917    #[test]
1918    fn test_capnp_to_zap_conversion() {
1919        let capnp_source = r#"
1920@0x9eb32e19f86ee174;
1921
1922struct Person @0xabcd1234 {
1923  name @0 :Text;
1924  age @1 :UInt32;
1925  email @2 :Text;
1926}
1927
1928enum Status @0x1234 {
1929  pending @0;
1930  active @1;
1931  completed @2;
1932}
1933
1934interface Greeter @0xabcd {
1935  sayHello @0 (name :Text) -> (greeting :Text);
1936  sayGoodbye @1 () -> ();
1937}
1938"#;
1939        let result = capnp_to_zap(capnp_source).unwrap();
1940
1941        // Check struct is converted properly
1942        assert!(result.contains("struct Person"));
1943        assert!(result.contains("  name :Text"));
1944        assert!(result.contains("  age :UInt32"));
1945        assert!(result.contains("  email :Text"));
1946
1947        // Check enum
1948        assert!(result.contains("enum Status"));
1949        assert!(result.contains("  pending"));
1950        assert!(result.contains("  active"));
1951        assert!(result.contains("  completed"));
1952
1953        // Check interface
1954        assert!(result.contains("interface Greeter"));
1955        assert!(result.contains("sayHello"));
1956        assert!(result.contains("sayGoodbye"));
1957
1958        // Ensure no ordinals in output (clean zap format)
1959        assert!(!result.contains("@0"));
1960        assert!(!result.contains("@1"));
1961        assert!(!result.contains("@2"));
1962    }
1963
1964    // ==========================================================================
1965    // Extensive ZAP Syntax Tests
1966    // ==========================================================================
1967
1968    #[test]
1969    fn test_all_primitive_types() {
1970        let source = r#"
1971struct AllTypes
1972  int8Val Int8
1973  int16Val Int16
1974  int32Val Int32
1975  int64Val Int64
1976  uint8Val UInt8
1977  uint16Val UInt16
1978  uint32Val UInt32
1979  uint64Val UInt64
1980  float32Val Float32
1981  float64Val Float64
1982  boolVal Bool
1983  textVal Text
1984  dataVal Data
1985  voidVal Void
1986"#;
1987        let result = transpile_str(source, "test.zap").unwrap();
1988        assert!(result.contains("int8Val @0 :Int8"));
1989        assert!(result.contains("int16Val @1 :Int16"));
1990        assert!(result.contains("int32Val @2 :Int32"));
1991        assert!(result.contains("int64Val @3 :Int64"));
1992        assert!(result.contains("uint8Val @4 :UInt8"));
1993        assert!(result.contains("uint16Val @5 :UInt16"));
1994        assert!(result.contains("uint32Val @6 :UInt32"));
1995        assert!(result.contains("uint64Val @7 :UInt64"));
1996        assert!(result.contains("float32Val @8 :Float32"));
1997        assert!(result.contains("float64Val @9 :Float64"));
1998        assert!(result.contains("boolVal @10 :Bool"));
1999        assert!(result.contains("textVal @11 :Text"));
2000        assert!(result.contains("dataVal @12 :Data"));
2001        assert!(result.contains("voidVal @13 :Void"));
2002    }
2003
2004    #[test]
2005    fn test_nested_structs_deep() {
2006        let source = r#"
2007struct Level1
2008  name Text
2009  level2 Level2
2010
2011  struct Level2
2012    value Int32
2013    level3 Level3
2014
2015    struct Level3
2016      data Data
2017      count UInt64
2018"#;
2019        let result = transpile_str(source, "test.zap").unwrap();
2020        assert!(result.contains("struct Level1"));
2021        assert!(result.contains("struct Level2"));
2022        assert!(result.contains("struct Level3"));
2023        assert!(result.contains("name @0 :Text"));
2024        assert!(result.contains("level2 @1 :Level2"));
2025    }
2026
2027    #[test]
2028    fn test_union_in_struct() {
2029        let source = r#"
2030struct Result
2031  id Text
2032  union
2033    success Data
2034    error Text
2035    pending Void
2036"#;
2037        let result = transpile_str(source, "test.zap").unwrap();
2038        assert!(result.contains("struct Result"));
2039        assert!(result.contains("id @0 :Text"));
2040        assert!(result.contains("union {"));
2041        assert!(result.contains("success"));
2042        assert!(result.contains("error"));
2043        assert!(result.contains("pending"));
2044    }
2045
2046    #[test]
2047    fn test_named_union() {
2048        let source = r#"
2049struct Shape
2050  name Text
2051  union geometry
2052    circle Circle
2053    rectangle Rectangle
2054"#;
2055        let result = transpile_str(source, "test.zap").unwrap();
2056        assert!(result.contains("struct Shape"));
2057        assert!(result.contains("geometry :union"));
2058    }
2059
2060    #[test]
2061    fn test_multiple_interfaces() {
2062        let source = r#"
2063interface Reader
2064  read (offset UInt64, size UInt32) -> (data Data)
2065  size () -> (bytes UInt64)
2066
2067interface Writer
2068  write (data Data) -> (written UInt64)
2069  flush () -> ()
2070  close () -> ()
2071
2072interface ReadWriter extends Reader
2073  write (data Data) -> (written UInt64)
2074"#;
2075        let result = transpile_str(source, "test.zap").unwrap();
2076        assert!(result.contains("interface Reader"));
2077        assert!(result.contains("interface Writer"));
2078        assert!(result.contains("interface ReadWriter extends(Reader)"));
2079        assert!(result.contains("read @0"));
2080        assert!(result.contains("write @0"));
2081    }
2082
2083    #[test]
2084    fn test_interface_with_nested_types() {
2085        let source = r#"
2086interface Database
2087  query (sql Text) -> (results QueryResult)
2088  execute (sql Text) -> (affected UInt64)
2089
2090  struct QueryResult
2091    columns List(Text)
2092    rows List(Row)
2093
2094  struct Row
2095    values List(Text)
2096
2097  enum ErrorCode
2098    notFound
2099    permissionDenied
2100    timeout
2101    internal
2102"#;
2103        let result = transpile_str(source, "test.zap").unwrap();
2104        assert!(result.contains("interface Database"));
2105        assert!(result.contains("struct QueryResult"));
2106        assert!(result.contains("struct Row"));
2107        assert!(result.contains("enum ErrorCode"));
2108    }
2109
2110    #[test]
2111    fn test_list_of_lists() {
2112        let source = r#"
2113struct Matrix
2114  rows List(List(Float64))
2115  labels List(Text)
2116"#;
2117        let result = transpile_str(source, "test.zap").unwrap();
2118        assert!(result.contains("rows @0 :List(List(Float64))"));
2119        assert!(result.contains("labels @1 :List(Text)"));
2120    }
2121
2122    #[test]
2123    fn test_default_values_various_types() {
2124        let source = r#"
2125struct Config
2126  host Text = "localhost"
2127  port UInt16 = 8080
2128  maxConnections UInt32 = 100
2129  timeout Float64 = 30.0
2130  enabled Bool = true
2131  retryCount Int32 = 3
2132"#;
2133        let result = transpile_str(source, "test.zap").unwrap();
2134        assert!(result.contains("host @0 :Text = \"localhost\""));
2135        assert!(result.contains("port @1 :UInt16 = 8080"));
2136        assert!(result.contains("maxConnections @2 :UInt32 = 100"));
2137        assert!(result.contains("timeout @3 :Float64 = 30.0"));
2138        assert!(result.contains("enabled @4 :Bool = true"));
2139        assert!(result.contains("retryCount @5 :Int32 = 3"));
2140    }
2141
2142    #[test]
2143    fn test_enum_many_variants() {
2144        let source = r#"
2145enum HttpStatus
2146  continue100
2147  ok200
2148  created201
2149  accepted202
2150  noContent204
2151  movedPermanently301
2152  found302
2153  notModified304
2154  badRequest400
2155  unauthorized401
2156  forbidden403
2157  notFound404
2158  methodNotAllowed405
2159  internalServerError500
2160  notImplemented501
2161  badGateway502
2162  serviceUnavailable503
2163"#;
2164        let result = transpile_str(source, "test.zap").unwrap();
2165        assert!(result.contains("enum HttpStatus"));
2166        assert!(result.contains("continue100 @0"));
2167        assert!(result.contains("ok200 @1"));
2168        assert!(result.contains("serviceUnavailable503 @16"));
2169    }
2170
2171    #[test]
2172    fn test_method_multiple_params() {
2173        let source = r#"
2174interface DataService
2175  search (query Text, limit UInt32, offset UInt64, filters List(Text)) -> (results List(Data), total UInt64)
2176  aggregate (keys List(Text), operation Text, groupBy Text) -> (result Data)
2177"#;
2178        let result = transpile_str(source, "test.zap").unwrap();
2179        assert!(result.contains("search @0"));
2180        assert!(result.contains("query :Text"));
2181        assert!(result.contains("limit :UInt32"));
2182        assert!(result.contains("offset :UInt64"));
2183        assert!(result.contains("filters :List(Text)"));
2184        assert!(result.contains("results :List(Data)"));
2185        assert!(result.contains("total :UInt64"));
2186    }
2187
2188    #[test]
2189    fn test_empty_interface() {
2190        let source = r#"
2191interface Empty
2192"#;
2193        let result = transpile_str(source, "test.zap").unwrap();
2194        assert!(result.contains("interface Empty"));
2195    }
2196
2197    #[test]
2198    fn test_empty_struct() {
2199        let source = r#"
2200struct Empty
2201"#;
2202        let result = transpile_str(source, "test.zap").unwrap();
2203        assert!(result.contains("struct Empty"));
2204    }
2205
2206    #[test]
2207    fn test_comments_multiline() {
2208        let source = r#"
2209# This is the first comment
2210# This is the second comment
2211# This is the third comment
2212struct Documented
2213  # Field comment
2214  value Text
2215"#;
2216        let result = transpile_str(source, "test.zap").unwrap();
2217        assert!(result.contains("# This is the first comment"));
2218        assert!(result.contains("# This is the second comment"));
2219        assert!(result.contains("# This is the third comment"));
2220    }
2221
2222    #[test]
2223    fn test_mixed_syntax_colon_and_space() {
2224        // Test that both syntaxes work in the same file
2225        let source = r#"
2226struct MixedSyntax
2227  fieldWithColon :Text
2228  fieldWithSpace Int32
2229  anotherColon :UInt64
2230  anotherSpace Bool
2231"#;
2232        let result = transpile_str(source, "test.zap").unwrap();
2233        assert!(result.contains("fieldWithColon @0 :Text"));
2234        assert!(result.contains("fieldWithSpace @1 :Int32"));
2235        assert!(result.contains("anotherColon @2 :UInt64"));
2236        assert!(result.contains("anotherSpace @3 :Bool"));
2237    }
2238
2239    #[test]
2240    fn test_complex_nested_lists() {
2241        let source = r#"
2242struct ComplexData
2243  matrix List(List(List(Float64)))
2244  records List(Record)
2245  tags List(List(Text))
2246
2247struct Record
2248  id UInt64
2249  data Data
2250"#;
2251        let result = transpile_str(source, "test.zap").unwrap();
2252        assert!(result.contains("matrix @0 :List(List(List(Float64)))"));
2253        assert!(result.contains("records @1 :List(Record)"));
2254        assert!(result.contains("tags @2 :List(List(Text))"));
2255    }
2256
2257    #[test]
2258    fn test_interface_method_no_params_no_results() {
2259        let source = r#"
2260interface Simple
2261  ping () -> ()
2262  noop () -> ()
2263"#;
2264        let result = transpile_str(source, "test.zap").unwrap();
2265        assert!(result.contains("ping @0 () -> ()"));
2266        assert!(result.contains("noop @1 () -> ()"));
2267    }
2268
2269    #[test]
2270    fn test_interface_method_only_results() {
2271        let source = r#"
2272interface Generator
2273  generate () -> (value UInt64)
2274  timestamp () -> (seconds Int64, nanos UInt32)
2275"#;
2276        let result = transpile_str(source, "test.zap").unwrap();
2277        assert!(result.contains("generate @0 () -> (value :UInt64)"));
2278        assert!(result.contains("timestamp @1 () -> (seconds :Int64, nanos :UInt32)"));
2279    }
2280
2281    #[test]
2282    fn test_stable_ids_deterministic() {
2283        let source = r#"
2284struct TestStruct
2285  field Text
2286"#;
2287        let result1 = transpile_str(source, "test.zap").unwrap();
2288        let result2 = transpile_str(source, "test.zap").unwrap();
2289        // Same input should produce same output (deterministic IDs)
2290        assert_eq!(result1, result2);
2291    }
2292
2293    #[test]
2294    fn test_different_paths_different_ids() {
2295        let source = r#"
2296struct TestStruct
2297  field Text
2298"#;
2299        let result1 = transpile_str(source, "path1.zap").unwrap();
2300        let result2 = transpile_str(source, "path2.zap").unwrap();
2301        // Different paths should produce different file IDs
2302        assert_ne!(result1, result2);
2303    }
2304
2305    #[test]
2306    fn test_rust_codegen_interface() {
2307        let source = r#"
2308interface Calculator
2309  add (a Float64, b Float64) -> (result Float64)
2310  multiply (a Float64, b Float64) -> (result Float64)
2311"#;
2312        let result = compile_to_rust(source, "test.zap").unwrap();
2313        assert!(result.contains("pub trait Calculator"));
2314        assert!(result.contains("async fn add"));
2315        assert!(result.contains("async fn multiply"));
2316        assert!(result.contains("a: f64"));
2317        assert!(result.contains("b: f64"));
2318    }
2319
2320    #[test]
2321    fn test_rust_codegen_union() {
2322        let source = r#"
2323struct Response
2324  union
2325    success Data
2326    error Text
2327"#;
2328        let result = compile_to_rust(source, "test.zap").unwrap();
2329        assert!(result.contains("pub struct Response"));
2330        assert!(result.contains("pub enum"));
2331        assert!(result.contains("Success(Vec<u8>)"));
2332        assert!(result.contains("Error(String)"));
2333    }
2334
2335    #[test]
2336    fn test_full_mcp_like_schema() {
2337        // Test a realistic MCP-like schema
2338        let source = r#"
2339struct Tool
2340  name Text
2341  description Text
2342  schema Data
2343  annotations Metadata
2344
2345struct Metadata
2346  entries List(Entry)
2347
2348  struct Entry
2349    key Text
2350    value Text
2351
2352struct ToolCall
2353  id Text
2354  name Text
2355  args Data
2356
2357struct ToolResult
2358  id Text
2359  union
2360    content Data
2361    error Text
2362
2363interface ToolService
2364  listTools () -> (tools List(Tool))
2365  callTool (call ToolCall) -> (result ToolResult)
2366
2367enum LogLevel
2368  debug
2369  info
2370  warn
2371  error
2372"#;
2373        let result = transpile_str(source, "test.zap").unwrap();
2374        assert!(result.contains("struct Tool"));
2375        assert!(result.contains("struct Metadata"));
2376        assert!(result.contains("struct Entry"));
2377        assert!(result.contains("struct ToolCall"));
2378        assert!(result.contains("struct ToolResult"));
2379        assert!(result.contains("interface ToolService"));
2380        assert!(result.contains("enum LogLevel"));
2381        assert!(result.contains("listTools @0"));
2382        assert!(result.contains("callTool @1"));
2383    }
2384
2385    #[test]
2386    fn test_whitespace_variations() {
2387        // Test various whitespace patterns
2388        let source1 = "struct Foo\n  bar Text";
2389        let source2 = "struct Foo\n  bar Text\n";
2390        let source3 = "struct Foo\n  bar Text\n\n";
2391        let source4 = "\nstruct Foo\n  bar Text";
2392
2393        for (i, source) in [source1, source2, source3, source4].iter().enumerate() {
2394            let result = transpile_str(source, "test.zap");
2395            assert!(result.is_ok(), "Failed on source variant {}", i);
2396            let output = result.unwrap();
2397            assert!(output.contains("bar @0 :Text"), "Missing field in variant {}", i);
2398        }
2399    }
2400
2401    #[test]
2402    fn test_tab_indentation() {
2403        let source = "struct Foo\n\tbar Text\n\tbaz Int32";
2404        let result = transpile_str(source, "test.zap").unwrap();
2405        assert!(result.contains("bar @0 :Text"));
2406        assert!(result.contains("baz @1 :Int32"));
2407    }
2408
2409    #[test]
2410    fn test_special_characters_in_strings() {
2411        let source = r#"
2412struct Config
2413  path Text = "/usr/local/bin"
2414  pattern Text = ".*\\.txt"
2415  message Text = "Hello, World!"
2416"#;
2417        let result = transpile_str(source, "test.zap").unwrap();
2418        assert!(result.contains("\"/usr/local/bin\""));
2419        assert!(result.contains("\".*\\\\.txt\""));
2420        assert!(result.contains("\"Hello, World!\""));
2421    }
2422
2423    #[test]
2424    fn test_camel_case_names() {
2425        let source = r#"
2426struct MyComplexStructName
2427  myFieldName Text
2428  anotherFieldWithLongName UInt64
2429  yetAnotherOne Bool
2430
2431interface MyServiceInterface
2432  myMethodName (myParam Text) -> (myResult Data)
2433"#;
2434        let result = transpile_str(source, "test.zap").unwrap();
2435        assert!(result.contains("struct MyComplexStructName"));
2436        assert!(result.contains("myFieldName @0 :Text"));
2437        assert!(result.contains("interface MyServiceInterface"));
2438        assert!(result.contains("myMethodName @0"));
2439    }
2440
2441    #[test]
2442    fn test_snake_case_to_rust() {
2443        let source = r#"
2444struct TestStruct
2445  myFieldName Text
2446  anotherField Int32
2447"#;
2448        let result = compile_to_rust(source, "test.zap").unwrap();
2449        // Rust codegen should convert to snake_case
2450        assert!(result.contains("my_field_name"));
2451        assert!(result.contains("another_field"));
2452    }
2453
2454    // ==========================================================================
2455    // Edge Case Tests
2456    // ==========================================================================
2457
2458    #[test]
2459    fn test_single_field_struct() {
2460        let source = r#"
2461struct Single
2462  value Text
2463"#;
2464        let result = transpile_str(source, "test.zap").unwrap();
2465        assert!(result.contains("struct Single"));
2466        assert!(result.contains("value @0 :Text"));
2467    }
2468
2469    #[test]
2470    fn test_single_variant_enum() {
2471        let source = r#"
2472enum Single
2473  only
2474"#;
2475        let result = transpile_str(source, "test.zap").unwrap();
2476        assert!(result.contains("enum Single"));
2477        assert!(result.contains("only @0"));
2478    }
2479
2480    #[test]
2481    fn test_single_method_interface() {
2482        let source = r#"
2483interface Single
2484  method () -> ()
2485"#;
2486        let result = transpile_str(source, "test.zap").unwrap();
2487        assert!(result.contains("interface Single"));
2488        assert!(result.contains("method @0"));
2489    }
2490
2491    #[test]
2492    fn test_numeric_looking_names() {
2493        let source = r#"
2494struct Data123
2495  field456 Text
2496  x789 Int32
2497"#;
2498        let result = transpile_str(source, "test.zap").unwrap();
2499        assert!(result.contains("struct Data123"));
2500        assert!(result.contains("field456 @0 :Text"));
2501        assert!(result.contains("x789 @1 :Int32"));
2502    }
2503
2504    #[test]
2505    fn test_underscore_names() {
2506        let source = r#"
2507struct Under_Score
2508  field_name Text
2509  another_field Int32
2510"#;
2511        let result = transpile_str(source, "test.zap").unwrap();
2512        assert!(result.contains("struct Under_Score"));
2513        assert!(result.contains("field_name @0 :Text"));
2514    }
2515
2516    #[test]
2517    fn test_large_ordinals() {
2518        // Test struct with many fields to ensure ordinals count correctly
2519        let source = r#"
2520struct ManyFields
2521  f0 Text
2522  f1 Text
2523  f2 Text
2524  f3 Text
2525  f4 Text
2526  f5 Text
2527  f6 Text
2528  f7 Text
2529  f8 Text
2530  f9 Text
2531  f10 Text
2532  f11 Text
2533  f12 Text
2534  f13 Text
2535  f14 Text
2536  f15 Text
2537  f16 Text
2538  f17 Text
2539  f18 Text
2540  f19 Text
2541"#;
2542        let result = transpile_str(source, "test.zap").unwrap();
2543        assert!(result.contains("f0 @0 :Text"));
2544        assert!(result.contains("f10 @10 :Text"));
2545        assert!(result.contains("f19 @19 :Text"));
2546    }
2547}