Skip to main content

vb6parse/parsers/cst/
mod.rs

1//! Concrete Syntax Tree (CST) implementation for VB6.
2//!
3//! This module provides a CST that wraps the rowan library internally while
4//! providing a public API that doesn't expose rowan types directly.
5//!
6//! # Overview
7//!
8//! The CST (Concrete Syntax Tree) represents the complete structure of VB6 source code,
9//! including all tokens such as whitespace, comments, and keywords. Unlike an AST
10//! (Abstract Syntax Tree), a CST preserves all the original formatting and structure
11//! of the source code, making it ideal for tools like formatters, linters, and
12//! source-to-source transformations.
13//!
14//! # Architecture
15//!
16//! This implementation uses the [`rowan`](https://docs.rs/rowan/) library internally
17//! for efficient CST representation, but all rowan types are kept private to the module.
18//! The public API only exposes:
19//!
20//! - [`ConcreteSyntaxTree`] - The main CST struct
21//! - [`SyntaxKind`] - An enum representing all possible node and token types
22//! - [`parse`] - A function to parse a [`TokenStream`] into a CST
23//! - [`CstNode`] - A structure for navigating and querying the CST
24//!
25//! # Example Usage
26//!
27//! ```rust
28//! use vb6parse::language::Token;
29//! use vb6parse::parsers::cst::parse;
30//! use vb6parse::lexer::TokenStream;
31//!
32//! // Create a token stream
33//! let tokens = vec![
34//!     ("Sub", Token::SubKeyword),
35//!     (" ", Token::Whitespace),
36//!     ("Main", Token::Identifier),
37//!     ("(", Token::LeftParenthesis),
38//!     (")", Token::RightParenthesis),
39//!     ("\n", Token::Newline),
40//! ];
41//!
42//! let token_stream = TokenStream::new("test.bas".to_string(), tokens);
43//!
44//! // Parse into a CST
45//! let cst = parse(token_stream);
46//!
47//! // Use the CST
48//! println!("Text: {}", cst.text());
49//! println!("Children: {}", cst.child_count());
50//! ```
51//!
52//! # Navigating the CST
53//!
54//! The CST provides rich navigation capabilities for traversing and querying the tree.
55//! Both [`ConcreteSyntaxTree`] and [`CstNode`] provide parallel navigation APIs:
56//!
57//! ## Root-Level Navigation
58//!
59//! ```rust
60//! use vb6parse::ConcreteSyntaxTree;
61//! use vb6parse::parsers::SyntaxKind;
62//!
63//! let source = "Sub Test()\nEnd Sub\n";
64//! let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
65//!
66//! if !failures.is_empty() {
67//!   eprintln!("Errors during parsing:");
68//!   for failure in failures {
69//!       failure.print();
70//!   }
71//!   panic!("Failed to parse source code.");
72//! }
73//!
74//! let cst = cst_opt.expect("Failed to parse source");
75//!
76//!
77//! // Access root-level children
78//! println!("Child count: {}", cst.child_count());
79//! let first = cst.first_child();
80//!
81//! // Search root-level children
82//! let subs: Vec<_> = cst.children_by_kind(SyntaxKind::SubStatement).collect();
83//! ```
84//!
85//! ## Node-Level Navigation
86//!
87//! Once you have a [`CstNode`], you can navigate its structure:
88//!
89//! ```rust
90//! # use vb6parse::ConcreteSyntaxTree;
91//! # use vb6parse::parsers::SyntaxKind;
92//! # let source = "Sub Test()\nDim x\nEnd Sub\n";
93//! # let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
94//! # let cst = cst_opt.expect("Failed to parse source");
95//!
96//! let root = cst.to_serializable().root;
97//!
98//! // Direct children
99//! println!("Child count: {}", root.child_count());
100//! let first = root.first_child();
101//!
102//! // Filter by kind
103//! let statements: Vec<_> = root.children_by_kind(SyntaxKind::DimStatement).collect();
104//!
105//! // Recursive search
106//! let dim_stmt = root.find(SyntaxKind::DimStatement);
107//! let all_identifiers = root.find_all(SyntaxKind::Identifier);
108//!
109//! // Filter tokens
110//! let non_tokens: Vec<_> = root.non_token_children().collect();
111//! let significant: Vec<_> = root.significant_children().collect();
112//!
113//! // Custom predicates
114//! let keywords = root.find_all_if(|n| {
115//!     matches!(n.kind(), SyntaxKind::SubKeyword | SyntaxKind::DimKeyword)
116//! });
117//!
118//! // Iterate all nodes
119//! for node in root.descendants() {
120//!     if node.is_significant() {
121//!         println!("{:?}: {}", node.kind(), node.text());
122//!     }
123//! }
124//! ```
125//!
126//! ## Navigation Methods
127//!
128//! Available on both [`ConcreteSyntaxTree`] and [`CstNode`]:
129//!
130//! **Basic Access:**
131//! - `child_count()` - Number of direct children
132//! - `first_child()`, `last_child()`, `child_at(index)` - Access specific children
133//!
134//! **By Kind:**
135//! - `children_by_kind(kind)` - Iterator over children of a specific kind
136//! - `first_child_by_kind(kind)` - First child of a specific kind
137//! - `contains_kind(kind)` - Check if a kind exists in children
138//!
139//! **Recursive Search:**
140//! - `find(kind)` - Find first descendant of a specific kind
141//! - `find_all(kind)` - Find all descendants of a specific kind
142//!
143//! **Token Filtering:**
144//! - `non_token_children()` - Structural nodes only
145//! - `token_children()` - Tokens only
146//! - `first_non_whitespace_child()` - Skip leading whitespace
147//! - `significant_children()` - Exclude whitespace and newlines
148//!
149//! **Predicate-Based:**
150//! - `find_if(predicate)` - Find first node matching a custom condition
151//! - `find_all_if(predicate)` - Find all nodes matching a custom condition
152//!
153//! **Tree Traversal:**
154//! - `descendants()` - Depth-first iterator over all nodes
155//! - `depth_first_iter()` - Alias for `descendants()`
156//!
157//! **Convenience Checkers** (`CstNode` only):
158//! - `is_whitespace()` - Check if node is whitespace.
159//! - `is_newline()` - Check if node is newline.
160//! - `is_comment()` - Check if node is an end-of-Line or REM comment.
161//! - `is_trivia()` - Whitespace, newline, end-of-Line comment, or REM comment.
162//! - `is_significant()` - Not trivia.
163//!
164//! For more details, see the documentation for [`ConcreteSyntaxTree`] and [`CstNode`].
165//!
166//! # Design Principles
167//!
168//! 1. **No rowan types exposed**: All public APIs use custom types that don't expose rowan.
169//! 2. **Complete representation**: The CST includes all tokens, including whitespace and comments.
170//! 3. **Efficient**: Uses rowan's red-green tree architecture for memory efficiency.
171//! 4. **Type-safe**: All syntax kinds are represented as a Rust enum for compile-time safety.
172
173use std::collections::HashMap;
174use std::num::NonZeroUsize;
175
176use crate::errors::{ErrorKind, FormError};
177use crate::files::common::{
178    Creatable, Exposed, FileAttributes, FileFormatVersion, NameSpace, ObjectReference,
179    PreDeclaredID, Properties,
180};
181use crate::io::{SourceFile, SourceStream};
182use crate::language::{
183    CheckBoxProperties, ComboBoxProperties, CommandButtonProperties, Control, ControlKind,
184    DataProperties, DirListBoxProperties, DriveListBoxProperties, FileListBoxProperties, Font,
185    Form, FormProperties, FormRoot, FrameProperties, LabelProperties, ListBoxProperties, MDIForm,
186    MDIFormProperties, MenuControl, MenuProperties, OptionButtonProperties, PictureBoxProperties,
187    PropertyGroup, TextBoxProperties, Token,
188};
189use crate::lexer::{tokenize, TokenStream};
190use crate::parsers::SyntaxKind;
191use crate::ParseResult;
192
193use either::Either;
194use rowan::{GreenNode, GreenNodeBuilder, Language};
195use serde::Serialize;
196
197// Submodules for organized CST parsing
198mod assignment;
199mod attribute_statements;
200mod declarations;
201mod deftype_statements;
202mod enum_statements;
203mod for_statements;
204mod function_statements;
205mod helpers;
206mod if_statements;
207mod loop_statements;
208mod navigation;
209mod option_statements;
210mod parameters;
211mod properties;
212mod property_statements;
213mod select_statements;
214mod sub_statements;
215mod type_statements;
216
217// Re-export navigation types
218pub use navigation::CstNode;
219
220/// A serializable representation of the CST for snapshot testing.
221///
222/// This struct wraps the tree structure in a way that can be serialized
223/// with serde, making it suitable for use with snapshot testing tools like insta.
224#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, Hash)]
225pub struct SerializableTree {
226    /// The root node of the tree
227    pub root: CstNode,
228}
229
230/// Helper function to serialize `ConcreteSyntaxTree` as `SerializableTree`
231pub(crate) fn serialize_cst<S>(cst: &ConcreteSyntaxTree, serializer: S) -> Result<S::Ok, S::Error>
232where
233    S: serde::Serializer,
234{
235    cst.to_serializable().serialize(serializer)
236}
237
238/// The language type for VB6 syntax trees.
239#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
240pub enum VB6Language {}
241
242impl Language for VB6Language {
243    type Kind = SyntaxKind;
244
245    fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
246        SyntaxKind::from_raw(raw)
247    }
248
249    fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
250        kind.to_raw()
251    }
252}
253
254/// Extract typed property groups from a Vec<PropertyGroup>
255fn extract_property_groups(groups: &[PropertyGroup]) -> ExtractedGroups {
256    let mut font = None;
257
258    for group in groups {
259        if group.name.eq_ignore_ascii_case("Font") {
260            if let Ok(f) = Font::try_from(group) {
261                font = Some(f);
262            }
263        }
264        // Future: handle other property group types (Images, etc.)
265    }
266
267    ExtractedGroups { font }
268}
269
270/// Struct to hold extracted property groups for a control
271struct ExtractedGroups {
272    font: Option<Font>,
273    // Future: add other property group types
274}
275
276/// A Concrete Syntax Tree for VB6 code.
277///
278/// This structure wraps the rowan library's `GreenNode` internally but provides
279/// a public API that doesn't expose rowan types.
280#[derive(Debug, Clone, PartialEq, Eq, Hash)]
281pub struct ConcreteSyntaxTree {
282    /// The root green node (internal implementation detail)
283    root: GreenNode,
284}
285
286impl ConcreteSyntaxTree {
287    /// Create a new CST from a `GreenNode` (internal use only)
288    fn new(root: GreenNode) -> Self {
289        Self { root }
290    }
291
292    /// Parse a CST from a `SourceFile`.
293    ///
294    /// # Arguments
295    ///
296    /// * `source_file` - The source file to parse.
297    ///
298    /// # Returns
299    ///
300    /// A result containing the parsed CST or an error.
301    #[must_use]
302    pub fn from_source(source_file: &SourceFile) -> ParseResult<'_, Self> {
303        Self::from_text(
304            source_file.file_name().to_string(),
305            source_file.source_stream().contents,
306        )
307    }
308
309    /// Parse a CST from source code.
310    ///
311    /// # Arguments
312    ///
313    /// * `file_name` - The name of the source file.
314    /// * `contents` - The contents of the source file.
315    ///
316    /// # Returns
317    ///
318    /// A result containing the parsed CST or an error.
319    pub fn from_text<S>(file_name: S, contents: &str) -> ParseResult<'_, Self>
320    where
321        S: Into<String>,
322    {
323        let mut source_stream = SourceStream::new(file_name.into(), contents);
324        let token_stream_result = tokenize(&mut source_stream);
325        let (token_stream_opt, failures) = token_stream_result.unpack();
326
327        let Some(token_stream) = token_stream_opt else {
328            return ParseResult::new(None, failures);
329        };
330
331        let cst = parse(token_stream);
332
333        ParseResult::new(Some(cst), failures)
334    }
335
336    /// Get the kind of the root node
337    #[must_use]
338    pub fn root_kind(&self) -> SyntaxKind {
339        SyntaxKind::from_raw(self.root.kind())
340    }
341
342    /// Convert the CST to a serializable representation.
343    ///
344    /// This method creates a `SerializableTree` that can be used with
345    /// snapshot testing tools like `insta`. The serializable tree contains
346    /// the complete tree structure as a hierarchy of `CstNode` instances.
347    ///
348    /// # Example
349    ///
350    /// ```rust
351    /// use vb6parse::ConcreteSyntaxTree;
352    ///
353    /// let source = "Sub Test()\nEnd Sub\n";
354    /// let result = ConcreteSyntaxTree::from_text("test.bas", source);
355    ///
356    /// let (cst_opt, failures) = result.unpack();
357    ///
358    /// let cst = cst_opt.expect("Failed to parse source");
359    ///
360    /// if !failures.is_empty() {
361    ///     for failure in failures.iter() {
362    ///         failure.print();
363    ///     }
364    ///     panic!("Failed to parse source with {} errors.", failures.len());
365    /// };
366    ///
367    /// let serializable = cst.to_serializable();
368    ///
369    /// // Can now be used with insta::assert_yaml_snapshot!
370    /// ```
371    #[must_use]
372    pub fn to_serializable(&self) -> SerializableTree {
373        SerializableTree {
374            root: self.to_root_node(),
375        }
376    }
377
378    /// Convert the internal rowan tree to a root `CstNode`.
379    ///
380    /// # Returns
381    ///
382    /// The root `CstNode` representing the entire CST.
383    #[must_use]
384    pub fn to_root_node(&self) -> CstNode {
385        CstNode::new(SyntaxKind::Root, self.text(), false, self.children())
386    }
387
388    /// Create a new CST with specified node kinds removed from the root level.
389    ///
390    /// This method filters out direct children of the root node that match any of the
391    /// specified kinds. This is useful for removing nodes that have already been parsed
392    /// into structured data (like version statements, attributes, etc.) to avoid duplication.
393    ///
394    /// # Arguments
395    ///
396    /// * `kinds_to_remove` - A slice of `SyntaxKind` values to filter out
397    ///
398    /// # Returns
399    ///
400    /// A new `ConcreteSyntaxTree` with the specified kinds removed from the root level.
401    ///
402    /// # Example
403    ///
404    /// ```rust
405    /// use vb6parse::ConcreteSyntaxTree;
406    /// use vb6parse::parsers::SyntaxKind;
407    ///
408    /// let source = "VERSION 5.00\nSub Test()\nEnd Sub\n";
409    /// let result = ConcreteSyntaxTree::from_text("test.bas", source);
410    /// let (cst_opt, failures) = result.unpack();
411    /// let cst = cst_opt.expect("Failed to parse source");
412    ///
413    /// // Remove version statement since it's already parsed
414    /// let filtered = cst.without_kinds(&[SyntaxKind::VersionStatement]);
415    ///
416    /// assert!(!filtered.contains_kind(SyntaxKind::VersionStatement));
417    /// ```
418    #[must_use]
419    pub fn without_kinds(&self, kinds_to_remove: &[SyntaxKind]) -> Self {
420        let syntax_node = rowan::SyntaxNode::<VB6Language>::new_root(self.root.clone());
421        let mut builder = GreenNodeBuilder::new();
422
423        builder.start_node(SyntaxKind::Root.to_raw());
424
425        // Iterate through children and only add those not in the filter list
426        for child in syntax_node.children_with_tokens() {
427            let child_kind = match &child {
428                rowan::NodeOrToken::Node(node) => node.kind(),
429                rowan::NodeOrToken::Token(token) => token.kind(),
430            };
431
432            // Skip if this kind should be removed
433            if kinds_to_remove.contains(&child_kind) {
434                continue;
435            }
436
437            // Add the child to the new tree
438            Self::clone_node_or_token(&mut builder, child);
439        }
440
441        builder.finish_node();
442        let new_root = builder.finish();
443
444        Self::new(new_root)
445    }
446
447    /// Recursively clone a node or token into a builder
448    fn clone_node_or_token(
449        builder: &mut GreenNodeBuilder<'static>,
450        node_or_token: rowan::NodeOrToken<
451            rowan::SyntaxNode<VB6Language>,
452            rowan::SyntaxToken<VB6Language>,
453        >,
454    ) {
455        match node_or_token {
456            rowan::NodeOrToken::Node(node) => {
457                builder.start_node(node.kind().to_raw());
458                for child in node.children_with_tokens() {
459                    Self::clone_node_or_token(builder, child);
460                }
461                builder.finish_node();
462            }
463            rowan::NodeOrToken::Token(token) => {
464                builder.token(token.kind().to_raw(), token.text());
465            }
466        }
467    }
468}
469
470/// Parse a `TokenStream` into a Concrete Syntax Tree.
471///
472/// This function takes a `TokenStream` and constructs a CST that represents
473/// the structure of the VB6 code.
474///
475/// # Arguments
476///
477/// * `tokens` - The token stream to parse
478///
479/// # Returns
480///
481/// A `ConcreteSyntaxTree` representing the parsed code.
482///
483/// # Example
484///
485/// ```rust
486/// use vb6parse::lexer::TokenStream;
487/// use vb6parse::parsers::cst::parse;
488///
489/// let tokens = TokenStream::new("example.bas".to_string(), vec![]);
490/// let cst = parse(tokens);
491/// ```
492#[must_use]
493pub fn parse(tokens: TokenStream) -> ConcreteSyntaxTree {
494    let parser = Parser::new(tokens);
495    parser.parse_root()
496}
497
498/// Internal parser state for building the CST
499pub(crate) struct Parser<'a> {
500    pub(crate) tokens: Vec<(&'a str, Token)>,
501    pub(crate) pos: usize,
502    pub(crate) builder: GreenNodeBuilder<'static>,
503    pub(crate) parsing_header: bool,
504}
505
506impl<'a> Parser<'a> {
507    fn new(token_stream: TokenStream<'a>) -> Self {
508        Parser {
509            tokens: token_stream.into_tokens(),
510            pos: 0,
511            builder: GreenNodeBuilder::new(),
512            parsing_header: true,
513        }
514    }
515
516    /// Create parser for direct extraction mode (control-only parsing)
517    pub(crate) fn new_direct_extraction(tokens: Vec<(&'a str, Token)>, pos: usize) -> Self {
518        Parser {
519            tokens,
520            pos,
521            builder: GreenNodeBuilder::new(),
522            parsing_header: true,
523        }
524    }
525
526    // Create parser for hybrid mode (`FormFile` optimization)
527    // ==================== Direct Extraction Helpers ====================
528    // These methods support direct extraction without CST building
529
530    /// Consume the parser and return the remaining tokens
531    /// Used to get tokens after direct extraction for CST building
532    pub(crate) fn into_tokens(self) -> Vec<(&'a str, Token)> {
533        // Return tokens from current position onwards
534        self.tokens[self.pos..].to_vec()
535    }
536
537    /// Skip whitespace tokens without consuming them into the CST
538    pub(crate) fn skip_whitespace(&mut self) {
539        while self.at_token(Token::Whitespace) {
540            self.pos += 1;
541        }
542    }
543
544    /// Skip whitespace and newline tokens without consuming them into the CST
545    pub(crate) fn skip_whitespace_and_newlines(&mut self) {
546        while self.at_token(Token::Whitespace) || self.at_token(Token::Newline) {
547            self.pos += 1;
548        }
549    }
550
551    /// Consume and advance past the current token without adding to CST
552    /// Returns the consumed token for inspection
553    pub(crate) fn consume_advance(&mut self) -> Option<(&'a str, Token)> {
554        if self.pos < self.tokens.len() {
555            let token = self.tokens[self.pos];
556            self.pos += 1;
557            Some(token)
558        } else {
559            None
560        }
561    }
562
563    // ==================== Direct Extraction Methods ====================
564
565    /// Parse VERSION statement directly without building CST
566    ///
567    /// Extracts the file format version (e.g., \"VERSION 5.00\") by directly
568    /// parsing tokens without CST construction overhead.
569    ///
570    /// # Returns
571    ///
572    /// A `ParseResult` containing:
573    /// - `result`: `Some(FileFormatVersion)` if found and valid, `None` if not present or invalid
574    /// - `failures`: Empty vec (no errors generated for missing VERSION)
575    pub(crate) fn parse_version_direct(&mut self) -> ParseResult<'a, FileFormatVersion> {
576        self.skip_whitespace();
577
578        // Check if VERSION keyword is present
579        if !self.at_token(Token::VersionKeyword) {
580            return ParseResult::new(None, Vec::new());
581        }
582
583        self.consume_advance(); // VERSION keyword
584        self.skip_whitespace();
585
586        // Parse version number (e.g., \"5.00\" or \"1.0\")
587        let version_result = if let Some((text, token)) = self.tokens.get(self.pos) {
588            match token {
589                Token::SingleLiteral | Token::DoubleLiteral | Token::IntegerLiteral => {
590                    let version_str = text.trim();
591                    self.consume_advance();
592
593                    // Parse \"major.minor\" format
594                    let parts: Vec<&str> = version_str.split('.').collect();
595                    if parts.len() == 2 {
596                        if let (Ok(major), Ok(minor)) =
597                            (parts[0].parse::<u8>(), parts[1].parse::<u8>())
598                        {
599                            Some(FileFormatVersion { major, minor })
600                        } else {
601                            None
602                        }
603                    } else {
604                        None
605                    }
606                }
607                _ => None,
608            }
609        } else {
610            None
611        };
612
613        // Skip optional CLASS keyword and trailing whitespace
614        self.skip_whitespace();
615        if self.at_token(Token::ClassKeyword) {
616            self.consume_advance();
617        }
618        self.skip_whitespace_and_newlines();
619
620        ParseResult::new(version_result, Vec::new())
621    }
622
623    // ==================== Core Control Extraction Methods ====================
624
625    /// Check if current token is `BeginProperty` identifier
626    fn is_begin_property(&self) -> bool {
627        if let Some((text, token)) = self.tokens.get(self.pos) {
628            *token == Token::Identifier && text.eq_ignore_ascii_case("BeginProperty")
629        } else {
630            false
631        }
632    }
633
634    /// Check if current token is an identifier matching the target text (case-insensitive)
635    fn is_identifier_text(&self, target: &str) -> bool {
636        if let Some((text, token)) = self.tokens.get(self.pos) {
637            *token == Token::Identifier && text.eq_ignore_ascii_case(target)
638        } else {
639            false
640        }
641    }
642
643    /// Convert a Menu-typed Control into `MenuControl`
644    fn control_to_menu(control: Control) -> MenuControl {
645        let (name, tag, index, kind) = control.into_parts();
646
647        if let ControlKind::Menu {
648            properties,
649            sub_menus,
650        } = kind
651        {
652            MenuControl::new(name, tag, index, properties, sub_menus)
653        } else {
654            // Fallback: create empty menu control
655            MenuControl::new(name, tag, index, MenuProperties::default(), Vec::new())
656        }
657    }
658
659    /// Parse control type directly from tokens (e.g., "VB.Form", "VB.CommandButton")
660    fn parse_control_type_direct(&mut self) -> String {
661        let mut parts = Vec::new();
662
663        // Parse identifier or keyword
664        if self.is_identifier() || self.at_keyword() {
665            if let Some((text, _)) = self.tokens.get(self.pos) {
666                parts.push(text.to_string());
667                self.consume_advance();
668            }
669        }
670
671        // Parse dot-separated parts (e.g., "VB.Form")
672        while self.at_token(Token::PeriodOperator) {
673            self.consume_advance(); // dot
674            if self.is_identifier() || self.at_keyword() {
675                if let Some((text, _)) = self.tokens.get(self.pos) {
676                    parts.push(".".to_string());
677                    parts.push(text.to_string());
678                    self.consume_advance();
679                }
680            }
681        }
682
683        parts.join("")
684    }
685
686    /// Parse control name directly from tokens
687    fn parse_control_name_direct(&mut self) -> String {
688        if self.is_identifier() || self.at_keyword() {
689            if let Some((text, _)) = self.tokens.get(self.pos) {
690                let name = text.to_string();
691                self.consume_advance();
692                return name;
693            }
694        }
695        String::new()
696    }
697
698    /// Parse a property assignment (Key = Value) directly from tokens
699    /// Returns (key, value) tuple
700    fn parse_property_direct(&mut self) -> Option<(String, String)> {
701        // Parse property key
702        let key = if self.is_identifier() || self.at_keyword() {
703            if let Some((text, _)) = self.tokens.get(self.pos) {
704                let k = text.to_string();
705                self.consume_advance();
706                k
707            } else {
708                return None;
709            }
710        } else {
711            return None;
712        };
713
714        self.skip_whitespace();
715
716        // Parse = sign
717        if !self.at_token(Token::EqualityOperator) {
718            return None;
719        }
720        self.consume_advance();
721        self.skip_whitespace();
722
723        // Parse value (everything until newline/colon)
724        // Special case: Resource references like "file.frx":0000 or $"file.frx":0000
725        // should include the colon and offset. The quotes should be preserved.
726        let mut value_parts = Vec::new();
727        let mut in_resource_reference = false;
728
729        while !self.is_at_end() && !self.at_token(Token::Newline) {
730            if let Some((text, token)) = self.tokens.get(self.pos) {
731                let text_copy = *text;
732                let token_copy = *token;
733
734                // Check if we see a dollar sign
735                if token_copy == Token::DollarSign {
736                    value_parts.push(text_copy);
737                    self.consume_advance();
738                }
739                // If we see a string literal (with or without $), check if resource reference follows
740                else if token_copy == Token::StringLiteral {
741                    value_parts.push(text_copy);
742                    self.consume_advance();
743
744                    // Peek ahead - if next token is colon, this is a resource reference
745                    if let Some((_, next_token)) = self.tokens.get(self.pos) {
746                        if *next_token == Token::ColonOperator {
747                            in_resource_reference = true;
748                        }
749                    }
750                }
751                // If in resource reference, capture colon
752                else if in_resource_reference && token_copy == Token::ColonOperator {
753                    value_parts.push(text_copy);
754                    self.consume_advance();
755                }
756                // If in resource reference and we see the offset number, capture it and stop
757                else if in_resource_reference
758                    && (token_copy == Token::IntegerLiteral || token_copy == Token::LongLiteral)
759                {
760                    value_parts.push(text_copy);
761                    self.consume_advance();
762                    break; // Done with resource reference
763                }
764                // If we hit a colon and not in resource reference, stop
765                else if token_copy == Token::ColonOperator {
766                    break;
767                }
768                // Otherwise, capture the token
769                else {
770                    value_parts.push(text_copy);
771                    self.consume_advance();
772                }
773            } else {
774                break;
775            }
776        }
777
778        // Skip newline
779        self.skip_whitespace_and_newlines();
780
781        // Join tokens directly without intermediate conversion
782        let value = value_parts.concat().trim().to_string();
783        Some((key, value))
784    }
785
786    /// Parse property group directly (BeginProperty...EndProperty)
787    fn parse_property_group_direct(&mut self) -> Option<PropertyGroup> {
788        // Expect BeginProperty identifier
789        if !self.is_identifier_text("BeginProperty") {
790            return None;
791        }
792
793        self.consume_advance(); // BeginProperty
794        self.skip_whitespace();
795
796        // Parse property group name and optional GUID
797        let (name, guid) = self.parse_property_group_name_direct();
798        self.skip_whitespace_and_newlines();
799
800        // Parse nested properties and property groups
801        let mut properties = HashMap::new();
802
803        while !self.is_at_end() && !self.is_identifier_text("EndProperty") {
804            self.skip_whitespace();
805
806            if self.is_identifier_text("EndProperty") {
807                break;
808            }
809
810            if self.is_identifier_text("BeginProperty") {
811                // Nested property group
812                if let Some(nested_group) = self.parse_property_group_direct() {
813                    properties.insert(nested_group.name.clone(), Either::Right(nested_group));
814                }
815            } else if self.is_identifier() || self.at_keyword() {
816                // Regular property
817                if let Some((key, value)) = self.parse_property_direct() {
818                    properties.insert(key, Either::Left(value));
819                }
820            } else {
821                self.consume_advance();
822            }
823        }
824
825        // Parse EndProperty
826        if self.is_identifier_text("EndProperty") {
827            self.consume_advance();
828            self.skip_whitespace_and_newlines();
829        }
830
831        Some(PropertyGroup {
832            name,
833            guid,
834            properties,
835        })
836    }
837
838    /// Parse property group name and extract optional GUID
839    /// Format: "Name {GUID}" or just "Name"
840    fn parse_property_group_name_direct(&mut self) -> (String, Option<uuid::Uuid>) {
841        let mut name_parts: Vec<&str> = Vec::new();
842        let mut guid_parts: Vec<&str> = Vec::new();
843        let mut in_guid = false;
844
845        // Collect tokens until newline
846        while !self.is_at_end() && !self.at_token(Token::Newline) {
847            if let Some((text, token)) = self.tokens.get(self.pos) {
848                if *token == Token::LeftCurlyBrace {
849                    // Start of GUID
850                    in_guid = true;
851                } else if *token == Token::RightCurlyBrace {
852                    // End of GUID
853                    in_guid = false;
854                } else if *token != Token::Whitespace && *token != Token::EndOfLineComment {
855                    // Collect non-whitespace tokens
856                    if in_guid {
857                        guid_parts.push(*text);
858                    } else {
859                        name_parts.push(*text);
860                    }
861                }
862            }
863            self.consume_advance();
864        }
865
866        let name = name_parts.concat();
867        let guid = if guid_parts.is_empty() {
868            None
869        } else {
870            let guid_str = guid_parts.concat();
871            uuid::Uuid::parse_str(&guid_str).ok()
872        };
873
874        (name, guid)
875    }
876
877    /// Parse Object statements directly (without CST)
878    /// Phase 5: Direct extraction of Object references
879    pub(crate) fn parse_objects_direct(&mut self) -> Vec<ObjectReference> {
880        let mut objects = Vec::new();
881
882        self.skip_whitespace_and_newlines();
883
884        // Continue parsing Object statements until we hit something else
885        while self.at_token(Token::ObjectKeyword) {
886            if let Some(obj_ref) = self.parse_single_object_direct() {
887                objects.push(obj_ref);
888            }
889            self.skip_whitespace_and_newlines();
890        }
891
892        objects
893    }
894
895    /// Parse a single Object statement line
896    /// Format: Object = "{UUID}#version#flags"; "filename"
897    /// Or:     Object = *\G{UUID}#version#flags; "filename"
898    fn parse_single_object_direct(&mut self) -> Option<ObjectReference> {
899        // Expect "Object" keyword
900        if !self.at_token(Token::ObjectKeyword) {
901            return None;
902        }
903        self.consume_advance(); // Object
904        self.skip_whitespace();
905
906        // Expect "="
907        if !self.at_token(Token::EqualityOperator) {
908            return None;
909        }
910        self.consume_advance(); // =
911        self.skip_whitespace();
912
913        // Check for optional "*\G" prefix (embedded object)
914        let mut _is_embedded = false;
915        if self.at_token(Token::MultiplicationOperator) {
916            self.consume_advance(); // *
917                                    // Expect \G (backslash followed by identifier "G")
918            if let Some((_text, token)) = self.tokens.get(self.pos) {
919                if *token == Token::BackwardSlashOperator {
920                    self.consume_advance(); // \
921                    if let Some((text2, token2)) = self.tokens.get(self.pos) {
922                        if *token2 == Token::Identifier && text2.eq_ignore_ascii_case("G") {
923                            self.consume_advance(); // G
924                            _is_embedded = true;
925                        }
926                    }
927                }
928            }
929        }
930        self.skip_whitespace();
931
932        // Parse first string literal or GUID tokens: "{UUID}#version#flags"
933        // The GUID may be tokenized as:
934        //   - A StringLiteral: "{UUID}#version#flags"
935        //   - Individual tokens: { UUID-parts } # version # flags
936        let uuid_part = if let Some((text, token)) = self.tokens.get(self.pos) {
937            if *token == Token::StringLiteral {
938                // String literal format
939                let s = text.trim_matches('"').to_string();
940                self.consume_advance();
941                s
942            } else if *token == Token::LeftCurlyBrace {
943                // Token format: { guid-parts } #version# flags
944                // Collect all tokens until semicolon
945                let mut parts: Vec<&str> = Vec::new();
946                while !self.is_at_end() && !self.at_token(Token::Semicolon) {
947                    if let Some((text, token)) = self.tokens.get(self.pos) {
948                        // Skip whitespace but collect everything else
949                        if *token != Token::Whitespace {
950                            parts.push(text);
951                        }
952                        self.consume_advance();
953                    } else {
954                        break;
955                    }
956                }
957                // Reconstruct the UUID part string
958                // Need to convert: { ... } #version# flags -> {UUID}#version#flags
959                parts.concat()
960            } else {
961                return None;
962            }
963        } else {
964            return None;
965        };
966
967        self.skip_whitespace();
968
969        // Expect semicolon
970        if !self.at_token(Token::Semicolon) {
971            return None;
972        }
973        self.consume_advance(); // ;
974        self.skip_whitespace();
975
976        // Parse second string literal: filename
977        let file_name = if let Some((text, token)) = self.tokens.get(self.pos) {
978            if *token == Token::StringLiteral {
979                let s = text.trim_matches('"').to_string();
980                self.consume_advance();
981                s
982            } else {
983                return None;
984            }
985        } else {
986            return None;
987        };
988
989        // Parse UUID part: {UUID}#version#flags or UUID#version#flags
990        let parts: Vec<&str> = uuid_part.split('#').collect();
991        if parts.len() >= 3 {
992            // Extract UUID (remove braces if present)
993            let uuid_str = parts[0].trim_matches(|c| c == '{' || c == '}');
994
995            if let Ok(uuid) = uuid::Uuid::parse_str(uuid_str) {
996                let version = parts[1].to_string();
997                let unknown1 = parts[2].to_string();
998
999                return Some(ObjectReference::Compiled {
1000                    uuid,
1001                    version,
1002                    unknown1,
1003                    file_name,
1004                });
1005            }
1006        }
1007
1008        None
1009    }
1010
1011    /// Parse Attribute statements directly (without CST)
1012    /// Phase 6: Direct extraction of file attributes
1013    pub(crate) fn parse_attributes_direct(&mut self) -> FileAttributes {
1014        let mut name = String::new();
1015        let mut global_name_space = NameSpace::Local;
1016        let mut creatable = Creatable::True;
1017        let mut predeclared_id = PreDeclaredID::False;
1018        let mut exposed = Exposed::False;
1019        let mut description: Option<String> = None;
1020        let mut ext_key: HashMap<String, String> = HashMap::new();
1021
1022        self.skip_whitespace_and_newlines();
1023
1024        // Continue parsing Attribute statements until we hit something else
1025        while self.at_token(Token::AttributeKeyword) {
1026            if let Some((key, value)) = self.parse_single_attribute_direct() {
1027                // Process the extracted key-value pair
1028                match key.as_str() {
1029                    "VB_Name" => {
1030                        name = value;
1031                    }
1032                    "VB_GlobalNameSpace" => {
1033                        global_name_space = if value == "True" || value == "-1" {
1034                            NameSpace::Global
1035                        } else {
1036                            NameSpace::Local
1037                        };
1038                    }
1039                    "VB_Creatable" => {
1040                        creatable = if value == "True" || value == "-1" {
1041                            Creatable::True
1042                        } else {
1043                            Creatable::False
1044                        };
1045                    }
1046                    "VB_PredeclaredId" => {
1047                        predeclared_id = if value == "True" || value == "-1" {
1048                            PreDeclaredID::True
1049                        } else {
1050                            PreDeclaredID::False
1051                        };
1052                    }
1053                    "VB_Exposed" => {
1054                        exposed = if value == "True" || value == "-1" {
1055                            Exposed::True
1056                        } else {
1057                            Exposed::False
1058                        };
1059                    }
1060                    "VB_Description" => {
1061                        description = Some(value);
1062                    }
1063                    _ => {
1064                        // Store any other attributes in ext_key
1065                        ext_key.insert(key, value);
1066                    }
1067                }
1068            }
1069            self.skip_whitespace_and_newlines();
1070        }
1071
1072        FileAttributes {
1073            name,
1074            global_name_space,
1075            creatable,
1076            predeclared_id,
1077            exposed,
1078            description,
1079            ext_key,
1080        }
1081    }
1082
1083    /// Parse a single Attribute statement line
1084    /// ```Attribute VB_Name = "Value"```
1085    /// Or
1086    /// ```Attribute VB_GlobalNameSpace = True```
1087    fn parse_single_attribute_direct(&mut self) -> Option<(String, String)> {
1088        // Expect "Attribute" keyword
1089        if !self.at_token(Token::AttributeKeyword) {
1090            return None;
1091        }
1092        self.consume_advance(); // Attribute
1093        self.skip_whitespace();
1094
1095        // Parse attribute key (e.g., "VB_Name")
1096        let key = if let Some((text, token)) = self.tokens.get(self.pos) {
1097            if *token == Token::Identifier {
1098                let k = text.to_string();
1099                self.consume_advance();
1100                k
1101            } else {
1102                return None;
1103            }
1104        } else {
1105            return None;
1106        };
1107
1108        self.skip_whitespace();
1109
1110        // Expect "="
1111        if !self.at_token(Token::EqualityOperator) {
1112            return None;
1113        }
1114        self.consume_advance(); // =
1115        self.skip_whitespace();
1116
1117        // Parse value (can be string, True/False, or number)
1118        let mut value = String::new();
1119        let mut found_value = false;
1120
1121        // Check for negative sign first (for values like "-1")
1122        if self.at_token(Token::SubtractionOperator) {
1123            value.push('-');
1124            self.consume_advance();
1125            self.skip_whitespace();
1126        }
1127
1128        if let Some((text, token)) = self.tokens.get(self.pos) {
1129            match token {
1130                Token::StringLiteral => {
1131                    // Remove surrounding quotes
1132                    value.push_str(text.trim().trim_matches('"'));
1133                    self.consume_advance();
1134                    found_value = true;
1135                }
1136                Token::TrueKeyword => {
1137                    value.push_str("True");
1138                    self.consume_advance();
1139                    found_value = true;
1140                }
1141                Token::FalseKeyword => {
1142                    value.push_str("False");
1143                    self.consume_advance();
1144                    found_value = true;
1145                }
1146                Token::IntegerLiteral | Token::LongLiteral => {
1147                    value.push_str(text.trim());
1148                    self.consume_advance();
1149                    found_value = true;
1150                }
1151                _ => {}
1152            }
1153        }
1154
1155        // Consume the rest of the line (for complex attributes like VB_Ext_KEY)
1156        // Skip until we hit a newline or end of tokens
1157        while self.pos < self.tokens.len() {
1158            if let Some((_, token)) = self.tokens.get(self.pos) {
1159                if *token == Token::Newline {
1160                    break;
1161                }
1162                self.consume_advance();
1163            } else {
1164                break;
1165            }
1166        }
1167
1168        if found_value {
1169            Some((key, value))
1170        } else {
1171            None
1172        }
1173    }
1174
1175    /// Build `FormRoot` from control type string and properties
1176    ///
1177    /// This function is used for parsing top-level form elements.
1178    /// Only `VB.Form` and `VB.MDIForm` are valid top-level types.
1179    fn build_form_root(
1180        control_type: &str,
1181        control_name: String,
1182        tag: String,
1183        index: i32,
1184        properties: Properties,
1185        groups: &[PropertyGroup],
1186        child_controls: Vec<Control>,
1187        menus: Vec<MenuControl>,
1188    ) -> Result<FormRoot, ErrorKind> {
1189        match control_type {
1190            "VB.Form" => {
1191                let mut form_properties: FormProperties = properties.into();
1192                // Override with property group if present
1193                let extracted_groups = extract_property_groups(groups);
1194                if let Some(font) = extracted_groups.font {
1195                    form_properties.font = Some(font);
1196                }
1197
1198                Ok(FormRoot::Form(Form {
1199                    name: control_name,
1200                    tag,
1201                    index,
1202                    properties: form_properties,
1203                    controls: child_controls,
1204                    menus,
1205                }))
1206            }
1207            "VB.MDIForm" => {
1208                let mut mdi_form_properties: MDIFormProperties = properties.into();
1209                // Override with property group if present
1210                let extracted_groups = extract_property_groups(groups);
1211                if let Some(font) = extracted_groups.font {
1212                    mdi_form_properties.font = Some(font);
1213                }
1214
1215                Ok(FormRoot::MDIForm(MDIForm {
1216                    name: control_name,
1217                    tag,
1218                    index,
1219                    properties: mdi_form_properties,
1220                    controls: child_controls,
1221                    menus,
1222                }))
1223            }
1224            _ => Err(ErrorKind::Form(FormError::InvalidTopLevelControl {
1225                control_type: control_type.to_string(),
1226            })),
1227        }
1228    }
1229
1230    /// Build `ControlKind` from control type string and properties
1231    ///
1232    /// Note: This function rejects `VB.Form` and `VB.MDIForm` as they are now
1233    /// top-level types only and cannot be child controls.
1234    fn build_control_kind(
1235        control_type: &str,
1236        properties: Properties,
1237        child_controls: Vec<Control>,
1238        menus: Vec<MenuControl>,
1239        property_groups: Vec<PropertyGroup>,
1240    ) -> ControlKind {
1241        use ControlKind;
1242        // Extract typed property groups
1243        let groups = extract_property_groups(&property_groups);
1244
1245        match control_type {
1246            "VB.CommandButton" => {
1247                let mut props: CommandButtonProperties = properties.into();
1248                // Override with property group if present
1249                if let Some(font) = groups.font {
1250                    props.font = Some(font);
1251                }
1252                ControlKind::CommandButton { properties: props }
1253            }
1254            "VB.Data" => {
1255                let mut props: DataProperties = properties.into();
1256                // Override with property group if present
1257                if let Some(font) = groups.font {
1258                    props.font = Some(font);
1259                }
1260                ControlKind::Data { properties: props }
1261            }
1262            "VB.TextBox" => {
1263                let mut props: TextBoxProperties = properties.into();
1264                // Override with property group if present
1265                if let Some(font) = groups.font {
1266                    props.font = Some(font);
1267                }
1268                ControlKind::TextBox { properties: props }
1269            }
1270            "VB.Label" => {
1271                let mut props: LabelProperties = properties.into();
1272                // Override with property group if present
1273                if let Some(font) = groups.font {
1274                    props.font = Some(font);
1275                }
1276                ControlKind::Label { properties: props }
1277            }
1278            "VB.CheckBox" => {
1279                let mut props: CheckBoxProperties = properties.into();
1280                // Override with property group if present
1281                if let Some(font) = groups.font {
1282                    props.font = Some(font);
1283                }
1284                ControlKind::CheckBox { properties: props }
1285            }
1286            "VB.Line" => ControlKind::Line {
1287                properties: properties.into(),
1288            },
1289            "VB.Shape" => ControlKind::Shape {
1290                properties: properties.into(),
1291            },
1292            "VB.ListBox" => {
1293                let mut props: ListBoxProperties = properties.into();
1294                // Override with property group if present
1295                if let Some(font) = groups.font {
1296                    props.font = Some(font);
1297                }
1298                ControlKind::ListBox { properties: props }
1299            }
1300            "VB.ComboBox" => {
1301                let mut props: ComboBoxProperties = properties.into();
1302                // Override with property group if present
1303                if let Some(font) = groups.font {
1304                    props.font = Some(font);
1305                }
1306                ControlKind::ComboBox { properties: props }
1307            }
1308            "VB.Timer" => ControlKind::Timer {
1309                properties: properties.into(),
1310            },
1311            "VB.HScrollBar" => ControlKind::HScrollBar {
1312                properties: properties.into(),
1313            },
1314            "VB.VScrollBar" => ControlKind::VScrollBar {
1315                properties: properties.into(),
1316            },
1317            "VB.Frame" => {
1318                let mut props: FrameProperties = properties.into();
1319                // Override with property group if present
1320                if let Some(font) = groups.font {
1321                    props.font = Some(font);
1322                }
1323                ControlKind::Frame {
1324                    properties: props,
1325                    controls: child_controls,
1326                }
1327            }
1328            "VB.PictureBox" => {
1329                let mut props: PictureBoxProperties = properties.into();
1330                // Override with property group if present
1331                if let Some(font) = groups.font {
1332                    props.font = Some(font);
1333                }
1334                ControlKind::PictureBox {
1335                    properties: props,
1336                    controls: child_controls,
1337                }
1338            }
1339            "VB.FileListBox" => {
1340                let mut props: FileListBoxProperties = properties.into();
1341                // Override with property group if present
1342                if let Some(font) = groups.font {
1343                    props.font = Some(font);
1344                }
1345                ControlKind::FileListBox { properties: props }
1346            }
1347            "VB.DirListBox" => {
1348                let mut props: DirListBoxProperties = properties.into();
1349                // Override with property group if present
1350                if let Some(font) = groups.font {
1351                    props.font = Some(font);
1352                }
1353                ControlKind::DirListBox { properties: props }
1354            }
1355            "VB.DriveListBox" => {
1356                let mut props: DriveListBoxProperties = properties.into();
1357                // Override with property group if present
1358                if let Some(font) = groups.font {
1359                    props.font = Some(font);
1360                }
1361                ControlKind::DriveListBox { properties: props }
1362            }
1363            "VB.Image" => ControlKind::Image {
1364                properties: properties.into(),
1365            },
1366            "VB.OptionButton" => {
1367                let mut props: OptionButtonProperties = properties.into();
1368                // Override with property group if present
1369                if let Some(font) = groups.font {
1370                    props.font = Some(font);
1371                }
1372                ControlKind::OptionButton { properties: props }
1373            }
1374            "VB.OLE" => ControlKind::Ole {
1375                properties: properties.into(),
1376            },
1377            "VB.Menu" => ControlKind::Menu {
1378                properties: properties.into(),
1379                sub_menus: menus,
1380            },
1381            _ => ControlKind::Custom {
1382                properties: properties.into(),
1383                property_groups,
1384            },
1385        }
1386    }
1387
1388    /// Parse properties block directly to Control without building CST
1389    /// Phase 4: Full implementation with nested controls and property groups
1390    pub(crate) fn parse_properties_block_to_control(&mut self) -> ParseResult<'a, Control> {
1391        self.skip_whitespace();
1392
1393        // Expect BEGIN keyword
1394        if !self.at_token(Token::BeginKeyword) {
1395            return ParseResult::new(None, Vec::new());
1396        }
1397
1398        self.consume_advance(); // BEGIN
1399        self.skip_whitespace();
1400
1401        // Parse control type (e.g., "VB.Form")
1402        let control_type = self.parse_control_type_direct();
1403        self.skip_whitespace();
1404
1405        // Parse control name
1406        let control_name = self.parse_control_name_direct();
1407        self.skip_whitespace_and_newlines();
1408
1409        // Parse properties, child controls, and property groups
1410        let mut properties = Properties::new();
1411        let mut child_controls = Vec::new();
1412        let mut menus = Vec::new();
1413        let mut property_groups = Vec::new();
1414        let mut failures = Vec::new();
1415
1416        while !self.is_at_end() && !self.at_token(Token::EndKeyword) {
1417            self.skip_whitespace();
1418
1419            if self.at_token(Token::EndKeyword) {
1420                break;
1421            }
1422
1423            if self.at_token(Token::BeginKeyword) {
1424                // Nested control (Begin VB.xxx)
1425                let child_result = self.parse_properties_block_to_control();
1426                let (child_opt, child_failures) = child_result.unpack();
1427                failures.extend(child_failures);
1428
1429                if let Some(child) = child_opt {
1430                    // Check if it's a menu control
1431                    if matches!(child.kind(), ControlKind::Menu { .. }) {
1432                        menus.push(Self::control_to_menu(child));
1433                    } else {
1434                        child_controls.push(child);
1435                    }
1436                }
1437            } else if self.is_begin_property() {
1438                // Parse property group (BeginProperty)
1439                if let Some(group) = self.parse_property_group_direct() {
1440                    property_groups.push(group);
1441                }
1442            } else if self.is_identifier() || self.at_keyword() {
1443                // Parse property (Key = Value)
1444                if let Some((key, value)) = self.parse_property_direct() {
1445                    // Remove surrounding quotes if this is a simple string literal
1446                    // BUT NOT if it's a resource reference (contains ":digit" pattern)
1447                    let is_resource_reference = value.contains(':')
1448                        && value
1449                            .split(':')
1450                            .next_back()
1451                            .is_some_and(|part| part.chars().all(|c| c.is_ascii_digit()));
1452
1453                    let cleaned_value = if !is_resource_reference
1454                        && value.starts_with('"')
1455                        && value.ends_with('"')
1456                        && value.len() >= 2
1457                    {
1458                        &value[1..value.len() - 1]
1459                    } else {
1460                        &value
1461                    };
1462                    properties.insert(&key, cleaned_value);
1463                }
1464            } else {
1465                // Skip unknown token
1466                self.consume_advance();
1467            }
1468        }
1469
1470        // Parse END keyword
1471        if self.at_token(Token::EndKeyword) {
1472            self.consume_advance();
1473            self.skip_whitespace_and_newlines();
1474        }
1475
1476        // Extract tag and index from properties
1477        let tag = properties.get("Tag").cloned().unwrap_or_default();
1478        let index = properties
1479            .get("Index")
1480            .and_then(|s| s.parse().ok())
1481            .unwrap_or(0);
1482
1483        // Build control kind with all components
1484        let kind = Self::build_control_kind(
1485            &control_type,
1486            properties,
1487            child_controls,
1488            menus,
1489            property_groups,
1490        );
1491
1492        let control = Control::new(control_name, tag, index, kind);
1493
1494        ParseResult::new(Some(control), failures)
1495    }
1496
1497    /// Parse properties block directly to `FormRoot` for top-level form elements.
1498    ///
1499    /// This function is specifically for parsing the top-level form element in
1500    /// `.frm`, `.ctl`, and `.dob` files. It enforces that only `VB.Form` or
1501    /// `VB.MDIForm` can be used as the root element.
1502    ///
1503    /// # Returns
1504    ///
1505    /// A `ParseResult` containing either a `FormRoot` (`Form` or `MDIForm`) or `None`,
1506    /// along with any parsing failures encountered.
1507    pub(crate) fn parse_properties_block_to_form_root(&mut self) -> ParseResult<'a, FormRoot> {
1508        use Properties;
1509
1510        let mut groups = Vec::new();
1511
1512        self.skip_whitespace();
1513
1514        // Expect BEGIN keyword
1515        if !self.at_token(Token::BeginKeyword) {
1516            return ParseResult::new(None, Vec::new());
1517        }
1518
1519        self.consume_advance(); // BEGIN
1520        self.skip_whitespace();
1521
1522        // Parse control type (e.g., "VB.Form" or "VB.MDIForm")
1523        let control_type = self.parse_control_type_direct();
1524        self.skip_whitespace();
1525
1526        // Parse control name
1527        let control_name = self.parse_control_name_direct();
1528        self.skip_whitespace_and_newlines();
1529
1530        // Parse properties, child controls, and property groups
1531        let mut properties = Properties::new();
1532        let mut child_controls = Vec::new();
1533        let mut menus = Vec::new();
1534        let mut failures = Vec::new();
1535
1536        while !self.is_at_end() && !self.at_token(Token::EndKeyword) {
1537            self.skip_whitespace();
1538
1539            if self.at_token(Token::EndKeyword) {
1540                break;
1541            }
1542
1543            if self.at_token(Token::BeginKeyword) {
1544                // Nested control (Begin VB.xxx) - use parse_properties_block_to_control for children
1545                let child_result = self.parse_properties_block_to_control();
1546                let (child_opt, child_failures) = child_result.unpack();
1547                failures.extend(child_failures);
1548
1549                if let Some(child) = child_opt {
1550                    // Check if it's a menu control
1551                    if matches!(child.kind(), ControlKind::Menu { .. }) {
1552                        menus.push(Self::control_to_menu(child));
1553                    } else {
1554                        child_controls.push(child);
1555                    }
1556                }
1557            } else if self.is_begin_property() {
1558                // Parse property group (BeginProperty)
1559                if let Some(group) = self.parse_property_group_direct() {
1560                    // Property groups are not used in Form/MDIForm, but we parse them anyway
1561                    // to avoid errors if they appear
1562                    groups.push(group);
1563                }
1564            } else if self.is_identifier() || self.at_keyword() {
1565                // Parse property (Key = Value)
1566                if let Some((key, value)) = self.parse_property_direct() {
1567                    // Remove surrounding quotes if this is a simple string literal
1568                    // BUT NOT if it's a resource reference (contains ":digit" pattern)
1569                    let is_resource_reference = value.contains(':')
1570                        && value
1571                            .split(':')
1572                            .next_back()
1573                            .is_some_and(|part| part.chars().all(|c| c.is_ascii_digit()));
1574
1575                    let cleaned_value = if !is_resource_reference
1576                        && value.starts_with('"')
1577                        && value.ends_with('"')
1578                        && value.len() >= 2
1579                    {
1580                        &value[1..value.len() - 1]
1581                    } else {
1582                        &value
1583                    };
1584                    properties.insert(&key, cleaned_value);
1585                }
1586            } else {
1587                // Skip unknown token
1588                self.consume_advance();
1589            }
1590        }
1591
1592        // Parse END keyword
1593        if self.at_token(Token::EndKeyword) {
1594            self.consume_advance();
1595            self.skip_whitespace_and_newlines();
1596        }
1597
1598        // Extract tag and index from properties
1599        let tag = properties.get("Tag").cloned().unwrap_or_default();
1600        let index = properties
1601            .get("Index")
1602            .and_then(|s| s.parse().ok())
1603            .unwrap_or(0);
1604
1605        // Build FormRoot with all components
1606        if let Ok(form_root) = Self::build_form_root(
1607            &control_type,
1608            control_name,
1609            tag,
1610            index,
1611            properties,
1612            &groups,
1613            child_controls,
1614            menus,
1615        ) {
1616            ParseResult::new(Some(form_root), failures)
1617        } else {
1618            // If invalid top-level control type, return a default Form as fallback
1619            let default_form = FormRoot::Form(Form {
1620                name: String::new(),
1621                tag: String::new(),
1622                index: 0,
1623                properties: FormProperties::default(),
1624                controls: Vec::new(),
1625                menus: Vec::new(),
1626            });
1627            ParseResult::new(Some(default_form), failures)
1628        }
1629    }
1630
1631    /// Parse a complete module/class/form (the top-level structure)
1632    ///
1633    /// This function loops through all tokens and identifies what kind of
1634    /// VB6 construct to parse based on the current token. As more VB6 syntax
1635    /// is supported, additional branches can be added to this loop.
1636    fn parse_root(mut self) -> ConcreteSyntaxTree {
1637        self.builder.start_node(SyntaxKind::Root.to_raw());
1638
1639        // Parse VERSION statement (if present)
1640        if self.at_token(Token::VersionKeyword) {
1641            self.parse_version_statement();
1642        }
1643
1644        // Parse BEGIN ... END block (if present)
1645        if self.at_token(Token::BeginKeyword) {
1646            self.parse_properties_block();
1647        }
1648
1649        // Parse Attribute statements (if present)
1650        // These come after the PropertiesBlock in forms/classes
1651        while self.at_token(Token::AttributeKeyword) {
1652            self.parse_attribute_statement();
1653        }
1654
1655        self.parse_module_body();
1656        self.builder.finish_node(); // Root
1657
1658        let root = self.builder.finish();
1659        ConcreteSyntaxTree::new(root)
1660    }
1661
1662    fn parse_module_body(&mut self) {
1663        while !self.is_at_end() {
1664            // For a CST, we need to consume ALL tokens, including whitespace and comments
1665            // We look ahead to determine structure, but still consume everything
1666
1667            // Check what kind of statement or declaration we're looking at
1668            match self.current_token() {
1669                // BEGIN ... END block (for forms/classes with properties)
1670                // This can appear after Object statements in form files
1671                Some(Token::BeginKeyword) => {
1672                    self.parse_properties_block();
1673                }
1674                // Object statement: Object = "{UUID}#version#flags"; "filename"
1675                // Only parse as ObjectStatement if it matches the proper format
1676                Some(Token::ObjectKeyword) if self.is_object_statement() => {
1677                    self.parse_object_statement();
1678                }
1679                // Attribute statement: Attribute VB_Name = "..."
1680                Some(Token::AttributeKeyword) => {
1681                    self.parse_attribute_statement();
1682                }
1683                Some(Token::OptionKeyword) => {
1684                    // Peek ahead to check if this is Option Base, Option Compare, or Option Private
1685                    if let Some(Token::BaseKeyword) = self.peek_next_keyword() {
1686                        self.parse_option_base_statement();
1687                    } else if let Some(Token::CompareKeyword) = self.peek_next_keyword() {
1688                        self.parse_option_compare_statement();
1689                    } else if let Some(Token::PrivateKeyword) = self.peek_next_keyword() {
1690                        self.parse_option_private_statement();
1691                    } else {
1692                        self.parse_option_statement();
1693                    }
1694                }
1695                // DefType statements: DefInt, DefLng, DefStr, etc.
1696                Some(
1697                    Token::DefBoolKeyword
1698                    | Token::DefByteKeyword
1699                    | Token::DefIntKeyword
1700                    | Token::DefLngKeyword
1701                    | Token::DefCurKeyword
1702                    | Token::DefSngKeyword
1703                    | Token::DefDblKeyword
1704                    | Token::DefDecKeyword
1705                    | Token::DefDateKeyword
1706                    | Token::DefStrKeyword
1707                    | Token::DefObjKeyword
1708                    | Token::DefVarKeyword,
1709                ) => {
1710                    self.parse_deftype_statement();
1711                }
1712                // Declare statement: Declare Sub/Function Name Lib "..."
1713                Some(Token::DeclareKeyword) => {
1714                    self.parse_declare_statement();
1715                }
1716                // Event statement: Event Name(...)
1717                Some(Token::EventKeyword) => {
1718                    self.parse_event_statement();
1719                }
1720                // Implements statement: Implements InterfaceName
1721                Some(Token::ImplementsKeyword) => {
1722                    self.parse_implements_statement();
1723                }
1724                // Enum statement: Enum Name ... End Enum
1725                Some(Token::EnumKeyword) => {
1726                    self.parse_enum_statement();
1727                }
1728                // Type statement: Type Name ... End Type
1729                Some(Token::TypeKeyword) => {
1730                    self.parse_type_statement();
1731                }
1732                // Sub procedure: Sub Name(...)
1733                Some(Token::SubKeyword) => {
1734                    self.parse_sub_statement();
1735                }
1736                // Function Procedure Syntax:
1737                //
1738                // [Public | Private | Friend] [ Static ] Function name [ ( arglist ) ] [ As type ]
1739                //
1740                Some(Token::FunctionKeyword) => {
1741                    self.parse_function_statement();
1742                }
1743                // Property Procedure Syntax:
1744                //
1745                // [Public | Private | Friend] [ Static ] Property Get|Let|Set name [ ( arglist ) ] [ As type ]
1746                //
1747                Some(Token::PropertyKeyword) => {
1748                    self.parse_property_statement();
1749                }
1750                // Variable declarations: Dim/Const
1751                // For Public/Private/Friend/Static, we need to look ahead to see if it's a
1752                // function/sub declaration or a variable declaration
1753                Some(Token::DimKeyword | Token::ConstKeyword) => {
1754                    self.parse_dim();
1755                }
1756                // Public/Private/Friend/Static - could be function/sub/property or declaration
1757                Some(
1758                    Token::PrivateKeyword
1759                    | Token::PublicKeyword
1760                    | Token::FriendKeyword
1761                    | Token::StaticKeyword,
1762                ) => {
1763                    // Look ahead to see if this is a function/sub/property/enum declaration
1764                    // Peek at the next 2 keywords to handle cases like "Public Static Function"
1765                    let next_keywords: Vec<_> = self
1766                        .peek_next_count_keywords(NonZeroUsize::new(2).unwrap())
1767                        .collect();
1768
1769                    match next_keywords.as_slice() {
1770                        // Direct: Public/Private/Friend Function, Sub, Property, Enum, Type, Declare, or Event
1771                        [Token::FunctionKeyword, ..] => self.parse_function_statement(), // Function
1772                        [Token::SubKeyword, ..] => self.parse_sub_statement(),           // Sub
1773                        [Token::PropertyKeyword, ..] => self.parse_property_statement(), // Property
1774                        [Token::DeclareKeyword, ..] => self.parse_declare_statement(),   // Declare
1775                        [Token::EnumKeyword, ..] => self.parse_enum_statement(),         // Enum
1776                        [Token::TypeKeyword, ..] => self.parse_type_statement(),         // Type
1777                        [Token::EventKeyword, ..] => self.parse_event_statement(),       // Event
1778                        [Token::ImplementsKeyword, ..] => self.parse_implements_statement(), // Implements
1779                        // With Static: Public/Private/Friend Static Function, Sub, or Property
1780                        [Token::StaticKeyword, Token::FunctionKeyword] => {
1781                            self.parse_function_statement();
1782                        }
1783                        [Token::StaticKeyword, Token::SubKeyword] => {
1784                            self.parse_sub_statement();
1785                        }
1786                        [Token::StaticKeyword, Token::PropertyKeyword] => {
1787                            self.parse_property_statement();
1788                        }
1789                        // Anything else is a declaration
1790                        _ => self.parse_dim(),
1791                    }
1792                }
1793                // Anything else - check if it's a statement, label, assignment, or unknown
1794                _ => {
1795                    // Whitespace, newlines, and comments - consume directly FIRST
1796                    // This must be checked before is_at_procedure_call to avoid
1797                    // treating REM comments as procedure calls
1798                    if matches!(
1799                        self.current_token(),
1800                        Some(
1801                            Token::Whitespace
1802                                | Token::Newline
1803                                | Token::EndOfLineComment
1804                                | Token::RemComment
1805                        )
1806                    ) {
1807                        self.consume_token();
1808                    // Try control flow statements
1809                    } else if self.is_control_flow_keyword() {
1810                        self.parse_control_flow_statement();
1811                    // Try built-in statements
1812                    } else if self.is_library_statement_keyword() {
1813                        self.parse_library_statement();
1814                    // Try array statements
1815                    } else if self.is_variable_declaration_keyword() {
1816                        self.parse_array_statement();
1817                    // Try to parse common statements using centralized dispatcher
1818                    } else if self.is_statement_keyword() {
1819                        self.parse_statement();
1820                    // Check if this is a label (identifier followed by colon)
1821                    } else if self.is_at_label() {
1822                        self.parse_label_statement();
1823                    // Check for Let statement (optional assignment keyword)
1824                    } else if self.at_token(Token::LetKeyword) {
1825                        self.parse_let_statement();
1826                    // Check if this looks like an assignment statement (identifier = expression)
1827                    // This must come BEFORE at_keyword() check to handle keywords used as variables
1828                    } else if self.is_at_assignment() {
1829                        self.parse_assignment_statement();
1830                    // Check if this looks like a procedure call (identifier without assignment)
1831                    } else if self.is_at_procedure_call() {
1832                        self.parse_procedure_call();
1833                    } else if self.is_identifier() || self.at_keyword() {
1834                        self.consume_token();
1835                    } else {
1836                        // This is purely being done this way to make it easier during development.
1837                        // In a full implementation, we would have specific parsing functions
1838                        // for all VB6 constructs with anything unrecognized being treated as an error node.
1839                        self.consume_token_as_unknown();
1840                    }
1841                }
1842            }
1843        }
1844    }
1845
1846    /// Check if the current token is a control flow keyword.
1847    /// Checks both current position and next non-whitespace token.
1848    fn is_control_flow_keyword(&self) -> bool {
1849        let token = if self.at_token(Token::Whitespace) {
1850            self.peek_next_keyword()
1851        } else {
1852            self.current_token().copied()
1853        };
1854
1855        matches!(
1856            token,
1857            Some(
1858                Token::IfKeyword
1859                    | Token::SelectKeyword
1860                    | Token::ForKeyword
1861                    | Token::DoKeyword
1862                    | Token::WhileKeyword
1863                    | Token::GotoKeyword
1864                    | Token::GoSubKeyword
1865                    | Token::ReturnKeyword
1866                    | Token::ResumeKeyword
1867                    | Token::ExitKeyword
1868                    | Token::OnKeyword
1869            )
1870        )
1871    }
1872
1873    /// Dispatch control flow statement parsing to the appropriate parser.
1874    fn parse_control_flow_statement(&mut self) {
1875        let token = if self.at_token(Token::Whitespace) {
1876            self.peek_next_keyword()
1877        } else {
1878            self.current_token().copied()
1879        };
1880
1881        match token {
1882            Some(Token::IfKeyword) => {
1883                self.parse_if_statement();
1884            }
1885            Some(Token::SelectKeyword) => {
1886                self.parse_select_case_statement();
1887            }
1888            Some(Token::ForKeyword) => {
1889                // Peek ahead to see if next keyword is "Each"
1890                // Need to peek TWO keywords ahead if we're currently at whitespace
1891                let next_kw = if self.at_token(Token::Whitespace) {
1892                    // We peeked to find "For", now peek one more for "Each"
1893                    self.peek_next_count_keywords(NonZeroUsize::new(2).unwrap())
1894                        .nth(1)
1895                } else {
1896                    // We're directly at "For", peek once for "Each"
1897                    self.peek_next_keyword()
1898                };
1899
1900                if let Some(Token::EachKeyword) = next_kw {
1901                    self.parse_for_each_statement();
1902                } else {
1903                    self.parse_for_statement();
1904                }
1905            }
1906            Some(Token::DoKeyword) => {
1907                self.parse_do_statement();
1908            }
1909            Some(Token::WhileKeyword) => {
1910                self.parse_while_statement();
1911            }
1912            Some(Token::GotoKeyword) => {
1913                self.parse_goto_statement();
1914            }
1915            Some(Token::GoSubKeyword) => {
1916                self.parse_gosub_statement();
1917            }
1918            Some(Token::ReturnKeyword) => {
1919                self.parse_return_statement();
1920            }
1921            Some(Token::ResumeKeyword) => {
1922                self.parse_resume_statement();
1923            }
1924            Some(Token::ExitKeyword) => {
1925                self.parse_exit_statement();
1926            }
1927            Some(Token::OnKeyword) => {
1928                // Look ahead to distinguish between On Error, On GoTo, and On GoSub
1929                // Need to peek different amounts depending on whether we're at whitespace
1930                let next_kw = if self.at_token(Token::Whitespace) {
1931                    // We peeked to find "On", now peek one more for "Error/GoTo/GoSub"
1932                    self.peek_next_count_keywords(NonZeroUsize::new(2).unwrap())
1933                        .nth(1)
1934                } else {
1935                    // We're directly at "On", peek once for next keyword
1936                    self.peek_next_keyword()
1937                };
1938
1939                if let Some(Token::ErrorKeyword) = next_kw {
1940                    self.parse_on_error_statement();
1941                } else {
1942                    // Need to scan ahead to find GoTo or GoSub keyword
1943                    // to distinguish between On GoTo and On GoSub
1944                    let peek_start = if self.at_token(Token::Whitespace) {
1945                        2
1946                    } else {
1947                        1
1948                    };
1949                    let keywords: Vec<Token> = self
1950                        .peek_next_count_keywords(NonZeroUsize::new(20).unwrap())
1951                        .skip(peek_start)
1952                        .collect();
1953
1954                    let has_goto = keywords.contains(&Token::GotoKeyword);
1955                    let has_gosub = keywords.contains(&Token::GoSubKeyword);
1956
1957                    if has_goto {
1958                        self.parse_on_goto_statement();
1959                    } else if has_gosub {
1960                        self.parse_on_gosub_statement();
1961                    } else {
1962                        // Fallback - treat as On Error if we can't determine
1963                        self.parse_on_error_statement();
1964                    }
1965                }
1966            }
1967            _ => {}
1968        }
1969    }
1970
1971    /// Check if the current token is an array statement keyword.
1972    /// Checks both current position and next non-whitespace token.
1973    fn is_variable_declaration_keyword(&self) -> bool {
1974        let token = if self.at_token(Token::Whitespace) {
1975            self.peek_next_keyword()
1976        } else {
1977            self.current_token().copied()
1978        };
1979
1980        matches!(token, Some(Token::ReDimKeyword | Token::EraseKeyword))
1981    }
1982
1983    /// Check if we're at an Object statement with proper format.
1984    ///
1985    /// Object statements in VB6 forms have the format:
1986    /// `Object = "{GUID}#version#flags"; "filename"`
1987    /// or
1988    /// `Object = *\G{GUID}#version#flags; "filename"`
1989    ///
1990    /// This checks if the pattern matches before committing to parse as `ObjectStatement`.
1991    #[allow(clippy::needless_continue)] // continue on whitespace is needed but clippy is incorrectly catching here.
1992    fn is_object_statement(&self) -> bool {
1993        // Must start with Object keyword
1994        if !self.at_token(Token::ObjectKeyword) {
1995            return false;
1996        }
1997
1998        // Look ahead to verify it matches Object statement pattern
1999        // Skip whitespace, should find =, then whitespace, then string or *\G pattern
2000        let mut found_equals = false;
2001        for (_text, token) in self.tokens.iter().skip(self.pos + 1) {
2002            match token {
2003                // TODO: Change this parsing to better handle leading whitespace on object statements.
2004                Token::Whitespace => continue,
2005                Token::EqualityOperator if !found_equals => {
2006                    found_equals = true;
2007                }
2008                // After =, we expect either a quoted string starting with { or * for type library refs
2009                Token::StringLiteral | Token::MultiplicationOperator if found_equals => {
2010                    // Valid Object statement - string literal after =
2011                    // or
2012                    // Could be *\G{ pattern for type libraries
2013                    return true;
2014                }
2015                // If we hit anything else after =, not an Object statement
2016                _ if found_equals => return false,
2017                // If we hit a newline before =, not an Object statement
2018                Token::Newline | Token::EndOfLineComment | Token::RemComment => {
2019                    return false;
2020                }
2021                _ => return false,
2022            }
2023        }
2024        false
2025    }
2026
2027    /// Dispatch array statement parsing to the appropriate parser.
2028    fn parse_array_statement(&mut self) {
2029        let token = if self.at_token(Token::Whitespace) {
2030            self.peek_next_keyword()
2031        } else {
2032            self.current_token().copied()
2033        };
2034
2035        match token {
2036            Some(Token::ReDimKeyword) => {
2037                self.parse_redim_statement();
2038            }
2039            Some(Token::EraseKeyword) => {
2040                self.parse_erase_statement();
2041            }
2042            _ => {}
2043        }
2044    }
2045
2046    /// Parse a statement list, consuming tokens until a termination condition is met.
2047    ///
2048    /// This is a generic statement list parser that can handle different termination conditions:
2049    /// - End Sub, End Function, End If, etc.
2050    /// - `ElseIf` or Else (for If statements)
2051    ///
2052    /// # Arguments
2053    /// * `stop_conditions` - A closure that returns true when the block should stop parsing
2054    pub(crate) fn parse_statement_list<F>(&mut self, stop_conditions: F)
2055    where
2056        F: Fn(&Parser) -> bool,
2057    {
2058        // Statement lists can appear in both header and body, so we do not modify parsing_header here.
2059
2060        // Start a StatementList node
2061        self.builder.start_node(SyntaxKind::StatementList.to_raw());
2062
2063        while !self.is_at_end() {
2064            if stop_conditions(self) {
2065                break;
2066            }
2067
2068            // Try control flow statements first
2069            if self.is_control_flow_keyword() {
2070                self.parse_control_flow_statement();
2071                continue;
2072            }
2073
2074            // Try built-in library statements
2075            if self.is_library_statement_keyword() {
2076                self.parse_library_statement();
2077                continue;
2078            }
2079
2080            // Try array statements
2081            if self.is_variable_declaration_keyword() {
2082                self.parse_array_statement();
2083                continue;
2084            }
2085
2086            // Try to parse a statement using the centralized dispatcher
2087            if self.is_statement_keyword() {
2088                self.parse_statement();
2089                continue;
2090            }
2091
2092            // Handle other constructs that aren't in parse_statement
2093            match self.current_token() {
2094                // Whitespace, newlines, and comments - consume directly FIRST
2095                // This must be checked before is_at_procedure_call to avoid
2096                // treating REM comments as procedure calls
2097                Some(
2098                    Token::Whitespace
2099                    | Token::Newline
2100                    | Token::EndOfLineComment
2101                    | Token::RemComment,
2102                ) => {
2103                    self.consume_token();
2104                }
2105                // Variable declarations: Dim/Private/Public/Const/Static
2106                Some(
2107                    Token::DimKeyword
2108                    | Token::PrivateKeyword
2109                    | Token::PublicKeyword
2110                    | Token::ConstKeyword
2111                    | Token::StaticKeyword,
2112                ) => {
2113                    self.parse_dim();
2114                }
2115                // Anything else - check if it's a label, assignment, procedure call, or unknown
2116                _ => {
2117                    // Check if this is a label (identifier followed by colon)
2118                    if self.is_at_label() {
2119                        self.parse_label_statement();
2120                    // Check for Let statement (optional assignment keyword)
2121                    } else if self.at_token(Token::LetKeyword)
2122                        || (self.at_token(Token::Whitespace)
2123                            && self.peek_next_keyword() == Some(Token::LetKeyword))
2124                    {
2125                        self.parse_let_statement();
2126                    // Check if this looks like an assignment statement (identifier = expression)
2127                    } else if self.is_at_assignment() {
2128                        self.parse_assignment_statement();
2129                    // Check if this looks like a procedure call (identifier without assignment)
2130                    } else if self.is_at_procedure_call() {
2131                        self.parse_procedure_call();
2132                    } else {
2133                        self.consume_token_as_unknown();
2134                    }
2135                }
2136            }
2137        }
2138        self.builder.finish_node(); // StatementList
2139    }
2140}
2141
2142#[cfg(test)]
2143mod tests {
2144    use super::Parser;
2145    use crate::parsers::cst::{
2146        ControlKind, Creatable, Exposed, FormRoot, NameSpace, ObjectReference, PreDeclaredID,
2147    };
2148    use crate::*;
2149
2150    use assert_matches::assert_matches;
2151
2152    #[test]
2153    fn parse_single_quote_comment() {
2154        let code = "' This is a comment\nSub Main()\n";
2155
2156        let mut source_stream = SourceStream::new("test.bas", code);
2157        let result = tokenize(&mut source_stream);
2158        let (token_stream_opt, _failures) = result.unpack();
2159
2160        let token_stream = token_stream_opt.expect("Tokenization failed");
2161        let cst = parse(token_stream);
2162
2163        assert_eq!(cst.root_kind(), SyntaxKind::Root);
2164        // Should have 2 children: the comment and the SubStatement
2165        assert_eq!(cst.child_count(), 3); // 2 statements + EOF
2166        assert!(cst.text().contains("' This is a comment"));
2167        assert!(cst.text().contains("Sub Main()"));
2168
2169        // Use navigation methods
2170        assert!(cst.contains_kind(SyntaxKind::EndOfLineComment));
2171        assert!(cst.contains_kind(SyntaxKind::SubStatement));
2172
2173        let first = cst.first_child().expect("Expected first child");
2174        assert_eq!(first.kind(), SyntaxKind::EndOfLineComment);
2175        assert!(first.is_token());
2176    }
2177
2178    #[test]
2179    fn syntax_kind_conversions() {
2180        // Test keyword conversions
2181        assert_eq!(
2182            SyntaxKind::from(Token::FunctionKeyword),
2183            SyntaxKind::FunctionKeyword
2184        );
2185        assert_eq!(SyntaxKind::from(Token::IfKeyword), SyntaxKind::IfKeyword);
2186        assert_eq!(SyntaxKind::from(Token::ForKeyword), SyntaxKind::ForKeyword);
2187
2188        // Test operators
2189        assert_eq!(
2190            SyntaxKind::from(Token::AdditionOperator),
2191            SyntaxKind::AdditionOperator
2192        );
2193        assert_eq!(
2194            SyntaxKind::from(Token::EqualityOperator),
2195            SyntaxKind::EqualityOperator
2196        );
2197
2198        // Test literals
2199        assert_eq!(
2200            SyntaxKind::from(Token::StringLiteral),
2201            SyntaxKind::StringLiteral
2202        );
2203        assert_eq!(
2204            SyntaxKind::from(Token::IntegerLiteral),
2205            SyntaxKind::IntegerLiteral
2206        );
2207        assert_eq!(
2208            SyntaxKind::from(Token::LongLiteral),
2209            SyntaxKind::LongLiteral
2210        );
2211        assert_eq!(
2212            SyntaxKind::from(Token::SingleLiteral),
2213            SyntaxKind::SingleLiteral
2214        );
2215        assert_eq!(
2216            SyntaxKind::from(Token::DoubleLiteral),
2217            SyntaxKind::DoubleLiteral
2218        );
2219        assert_eq!(
2220            SyntaxKind::from(Token::DateTimeLiteral),
2221            SyntaxKind::DateLiteral
2222        );
2223    }
2224
2225    #[test]
2226    fn parse_empty_stream() {
2227        let source = "";
2228        let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
2229        let cst = cst_opt.expect("Failed to parse source");
2230
2231        assert_eq!(cst.root_kind(), SyntaxKind::Root);
2232        assert_eq!(cst.child_count(), 0);
2233    }
2234
2235    #[test]
2236    fn parse_rem_comment() {
2237        let source = "REM This is a REM comment\nSub Test()\nEnd Sub\n";
2238        let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
2239        let cst = cst_opt.expect("Failed to parse source");
2240
2241        assert_eq!(cst.root_kind(), SyntaxKind::Root);
2242        // Should have 2 children: the REM comment and the SubStatement
2243        assert_eq!(cst.child_count(), 3); // 2 statements + EOF
2244        assert!(cst.text().contains("REM This is a REM comment"));
2245        assert!(cst.text().contains("Sub Test()"));
2246
2247        // Verify REM comment is preserved
2248        let debug = cst.debug_tree();
2249        assert!(debug.contains("RemComment"));
2250    }
2251
2252    #[test]
2253    fn parse_mixed_comments() {
2254        let source = "' Single quote comment\nREM REM comment\nSub Test()\nEnd Sub\n";
2255        let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
2256        let cst = cst_opt.expect("Failed to parse source");
2257
2258        assert_eq!(cst.root_kind(), SyntaxKind::Root);
2259        // Should have 5 children: EndOfLineComment, Newline, RemComment, Newline, SubStatement
2260        assert_eq!(cst.child_count(), 5);
2261        assert!(cst.text().contains("' Single quote comment"));
2262        assert!(cst.text().contains("REM REM comment"));
2263
2264        // Use navigation methods
2265        let children = cst.children();
2266        assert_eq!(children[0].kind(), SyntaxKind::EndOfLineComment);
2267        assert_eq!(children[1].kind(), SyntaxKind::Newline);
2268        assert_eq!(children[2].kind(), SyntaxKind::RemComment);
2269        assert_eq!(children[3].kind(), SyntaxKind::Newline);
2270        assert_eq!(children[4].kind(), SyntaxKind::SubStatement);
2271
2272        assert!(cst.contains_kind(SyntaxKind::EndOfLineComment));
2273        assert!(cst.contains_kind(SyntaxKind::RemComment));
2274    }
2275
2276    #[test]
2277    fn cst_with_comments() {
2278        let source = "' This is a comment\nSub Main()\n";
2279        let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
2280        let cst = cst_opt.expect("Failed to parse source");
2281
2282        // Now has 3 children: comment token, newline token, SubStatement
2283        assert_eq!(cst.child_count(), 3);
2284        assert!(cst.text().contains("' This is a comment"));
2285        assert!(cst.text().contains("Sub Main()"));
2286    }
2287
2288    #[test]
2289    fn cst_serializable_tree() {
2290        let source = "Sub Test()\nEnd Sub\n";
2291        let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
2292        let cst = cst_opt.expect("Failed to parse source");
2293
2294        // Convert to serializable format
2295        let serializable = cst.to_serializable();
2296
2297        // Verify structure
2298        assert_eq!(serializable.root.kind(), SyntaxKind::Root);
2299        assert!(!serializable.root.is_token());
2300        assert_eq!(serializable.root.children().len(), 1);
2301        assert_eq!(
2302            serializable.root.children()[0].kind(),
2303            SyntaxKind::SubStatement
2304        );
2305
2306        // Can be used with insta for snapshot testing:
2307        // insta::assert_yaml_snapshot!(serializable);
2308    }
2309
2310    #[test]
2311    fn cst_serializable_with_insta() {
2312        let source = "Dim x As Integer\n";
2313        let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
2314        let cst = cst_opt.expect("Failed to parse source");
2315        let serializable = cst.to_serializable();
2316
2317        // Example of using with insta (commented out to not create snapshot files in normal test runs)
2318        // insta::assert_yaml_snapshot!(serializable);
2319
2320        // Verify it's serializable by checking structure
2321        assert!(!serializable.root.children().is_empty());
2322    }
2323
2324    #[test]
2325    fn parser_mode_full_cst_default() {
2326        let source = "Sub Test()\nEnd Sub\n";
2327        let mut stream = SourceStream::new("test.bas".to_string(), source);
2328        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2329        let token_stream = token_stream_opt.expect("Tokenization failed");
2330
2331        let parser = Parser::new(token_stream);
2332        // Verify parser was created successfully
2333        assert_eq!(parser.pos, 0);
2334    }
2335
2336    #[test]
2337    fn parser_mode_direct_extraction() {
2338        let source = "Sub Test()\nEnd Sub\n";
2339        let mut stream = SourceStream::new("test.bas".to_string(), source);
2340        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2341        let token_stream = token_stream_opt.expect("Tokenization failed");
2342        let tokens = token_stream.into_tokens();
2343
2344        let parser = Parser::new_direct_extraction(tokens, 0);
2345        assert_eq!(parser.pos, 0);
2346    }
2347
2348    #[test]
2349    fn parser_constructors_preserve_tokens() {
2350        let source = "VERSION 5.00\n";
2351        let mut stream = SourceStream::new("test.frm".to_string(), source);
2352        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2353        let token_stream = token_stream_opt.expect("Tokenization failed");
2354
2355        let tokens_vec = token_stream.into_tokens();
2356        let token_count = tokens_vec.len();
2357
2358        let parser = Parser::new_direct_extraction(tokens_vec, 0);
2359        assert_eq!(parser.tokens.len(), token_count);
2360        assert!(parser.tokens[0].1 == Token::VersionKeyword);
2361    }
2362
2363    #[test]
2364    fn parser_new_with_position() {
2365        let source = "VERSION 5.00\nSub Test()\nEnd Sub\n";
2366        let mut stream = SourceStream::new("test.bas".to_string(), source);
2367        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2368        let token_stream = token_stream_opt.expect("Tokenization failed");
2369        let tokens = token_stream.into_tokens();
2370
2371        // Create parser starting at position 3 (after VERSION keyword, whitespace, and version number)
2372        let parser = Parser::new_direct_extraction(tokens, 3);
2373        assert_eq!(parser.pos, 3);
2374    }
2375
2376    #[test]
2377    fn parse_version_direct_with_version() {
2378        let source = "VERSION 5.00\nSub Test()\nEnd Sub\n";
2379        let mut stream = SourceStream::new("test.frm".to_string(), source);
2380        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2381        let token_stream = token_stream_opt.expect("Tokenization failed");
2382        let tokens = token_stream.into_tokens();
2383
2384        let mut parser = Parser::new_direct_extraction(tokens, 0);
2385        let (version_opt, failures) = parser.parse_version_direct().unpack();
2386
2387        assert!(version_opt.is_some());
2388        let version = version_opt.expect("Expected version to be parsed");
2389        assert_eq!(version.major, 5);
2390        assert_eq!(version.minor, 0);
2391        assert!(failures.is_empty());
2392    }
2393
2394    #[test]
2395    fn parse_version_direct_without_version() {
2396        let source = "Sub Test()\nEnd Sub\n";
2397        let mut stream = SourceStream::new("test.bas".to_string(), source);
2398        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2399        let token_stream = token_stream_opt.expect("Tokenization failed");
2400        let tokens = token_stream.into_tokens();
2401
2402        let mut parser = Parser::new_direct_extraction(tokens, 0);
2403        let (version_opt, failures) = parser.parse_version_direct().unpack();
2404
2405        assert!(version_opt.is_none());
2406        assert!(failures.is_empty());
2407    }
2408
2409    #[test]
2410    fn parse_version_direct_with_class_keyword() {
2411        let source = "VERSION 1.0 CLASS\nSub Test()\nEnd Sub\n";
2412        let mut stream = SourceStream::new("test.cls".to_string(), source);
2413        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2414        let token_stream = token_stream_opt.expect("Tokenization failed");
2415        let tokens = token_stream.into_tokens();
2416
2417        let mut parser = Parser::new_direct_extraction(tokens, 0);
2418        let (version_opt, failures) = parser.parse_version_direct().unpack();
2419
2420        assert!(version_opt.is_some());
2421        let version = version_opt.expect("Expected version to be parsed");
2422        assert_eq!(version.major, 1);
2423        assert_eq!(version.minor, 0);
2424        assert!(failures.is_empty());
2425    }
2426
2427    #[test]
2428    fn parse_version_direct_version_100() {
2429        let source = "VERSION 1.00\n";
2430        let mut stream = SourceStream::new("test.cls".to_string(), source);
2431        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2432        let token_stream = token_stream_opt.expect("Tokenization failed");
2433        let tokens = token_stream.into_tokens();
2434
2435        let mut parser = Parser::new_direct_extraction(tokens, 0);
2436        let (version_opt, _failures) = parser.parse_version_direct().unpack();
2437
2438        assert!(version_opt.is_some());
2439        let version = version_opt.expect("Expected version to be parsed");
2440        assert_eq!(version.major, 1);
2441        assert_eq!(version.minor, 0);
2442    }
2443
2444    #[test]
2445    fn parse_version_direct_with_whitespace() {
2446        let source = "  VERSION   5.00  \nSub Test()\n";
2447        let mut stream = SourceStream::new("test.frm".to_string(), source);
2448        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2449        let token_stream = token_stream_opt.expect("Tokenization failed");
2450        let tokens = token_stream.into_tokens();
2451
2452        let mut parser = Parser::new_direct_extraction(tokens, 0);
2453        let (version_opt, _failures) = parser.parse_version_direct().unpack();
2454
2455        assert!(version_opt.is_some());
2456        let version = version_opt.expect("Expected version to be parsed");
2457        assert_eq!(version.major, 5);
2458        assert_eq!(version.minor, 0);
2459    }
2460
2461    #[test]
2462    fn parse_version_direct_position_advances() {
2463        let source = "VERSION 5.00\nBegin VB.Form Form1\nEnd\n";
2464        let mut stream = SourceStream::new("test.frm".to_string(), source);
2465        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2466        let token_stream = token_stream_opt.expect("Tokenization failed");
2467        let tokens = token_stream.into_tokens();
2468
2469        let mut parser = Parser::new_direct_extraction(tokens, 0);
2470        let initial_pos = parser.pos;
2471        let _result = parser.parse_version_direct();
2472
2473        // Position should have advanced past VERSION statement
2474        assert!(parser.pos > initial_pos);
2475
2476        // Should now be positioned at Begin keyword
2477        assert_eq!(parser.current_token(), Some(&Token::BeginKeyword));
2478    }
2479
2480    #[test]
2481    fn parse_version_direct_accuracy() {
2482        let test_cases = vec![
2483            ("VERSION 5.00\n", Some((5, 0))),
2484            ("VERSION 1.0\n", Some((1, 0))),
2485            ("VERSION 6.00 CLASS\n", Some((6, 0))),
2486            ("VERSION 4.00\n", Some((4, 0))),
2487            ("Sub Test()\n", None), // No VERSION
2488        ];
2489
2490        for (source, expected) in test_cases {
2491            let mut stream = SourceStream::new("test.vb".to_string(), source);
2492            let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2493            let token_stream = token_stream_opt.expect("Tokenization failed");
2494            let tokens = token_stream.into_tokens();
2495
2496            let mut parser = Parser::new_direct_extraction(tokens, 0);
2497            let (version_opt, _failures) = parser.parse_version_direct().unpack();
2498
2499            match expected {
2500                Some((major, minor)) => {
2501                    assert!(version_opt.is_some(), "Expected version for: {source}");
2502                    let version = version_opt.expect("Expected version to be parsed");
2503                    assert_eq!(version.major, major, "Major mismatch for: {source}");
2504                    assert_eq!(version.minor, minor, "Minor mismatch for: {source}");
2505                }
2506                None => {
2507                    assert!(version_opt.is_none(), "Expected no version for: {source}");
2508                }
2509            }
2510        }
2511    }
2512
2513    #[test]
2514    fn parse_control_type_direct_simple() {
2515        let source = "VB.Form Form1\n";
2516        let mut stream = SourceStream::new("test.frm".to_string(), source);
2517        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2518        let token_stream = token_stream_opt.expect("Tokenization failed");
2519        let tokens = token_stream.into_tokens();
2520
2521        let mut parser = Parser::new_direct_extraction(tokens, 0);
2522        let control_type = parser.parse_control_type_direct();
2523
2524        assert_eq!(control_type, "VB.Form");
2525    }
2526
2527    #[test]
2528    fn parse_control_name_direct_simple() {
2529        let source = "Form1 \n";
2530        let mut stream = SourceStream::new("test.frm".to_string(), source);
2531        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2532        let token_stream = token_stream_opt.expect("Tokenization failed");
2533        let tokens = token_stream.into_tokens();
2534
2535        let mut parser = Parser::new_direct_extraction(tokens, 0);
2536        let control_name = parser.parse_control_name_direct();
2537
2538        assert_eq!(control_name, "Form1");
2539    }
2540
2541    #[test]
2542    fn parse_property_direct_simple() {
2543        let source = "Caption = \"Hello World\"\n";
2544        let mut stream = SourceStream::new("test.frm".to_string(), source);
2545        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2546        let token_stream = token_stream_opt.expect("Tokenization failed");
2547        let tokens = token_stream.into_tokens();
2548
2549        let mut parser = Parser::new_direct_extraction(tokens, 0);
2550        let property = parser.parse_property_direct();
2551
2552        assert!(property.is_some());
2553        let (key, value) = property.expect("Expected property to be parsed");
2554        assert_eq!(key, "Caption");
2555        assert_eq!(value, "\"Hello World\"");
2556    }
2557
2558    #[test]
2559    fn parse_properties_block_to_control_simple_form() {
2560        let source = r#"Begin VB.Form Form1
2561   Caption = "Test Form"
2562   ClientHeight = 3000
2563   ClientWidth = 4000
2564End
2565"#;
2566        let mut stream = SourceStream::new("test.frm".to_string(), source);
2567        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2568        let token_stream = token_stream_opt.expect("Tokenization failed");
2569        let tokens = token_stream.into_tokens();
2570
2571        let mut parser = Parser::new_direct_extraction(tokens, 0);
2572        let (control_opt, failures) = parser.parse_properties_block_to_form_root().unpack();
2573
2574        assert!(failures.is_empty(), "Expected no failures");
2575        assert!(control_opt.is_some(), "Expected form root to be parsed");
2576
2577        let form_root = control_opt.expect("Expected form root to be parsed");
2578        assert_eq!(form_root.name(), "Form1");
2579
2580        // Verify it's a Form
2581        assert!(form_root.is_form());
2582    }
2583
2584    #[test]
2585    fn parse_properties_block_to_control_command_button() {
2586        let source = r#"Begin VB.CommandButton Command1
2587   Caption = "Click Me"
2588   Height = 495
2589   Width = 1215
2590End
2591"#;
2592        let mut stream = SourceStream::new("test.frm".to_string(), source);
2593        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2594        let token_stream = token_stream_opt.expect("Tokenization failed");
2595        let tokens = token_stream.into_tokens();
2596
2597        let mut parser = Parser::new_direct_extraction(tokens, 0);
2598        let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
2599
2600        assert!(failures.is_empty());
2601        assert!(control_opt.is_some());
2602
2603        let control = control_opt.expect("Expected control to be parsed");
2604        assert_eq!(control.name(), "Command1");
2605        assert_matches!(control.kind(), ControlKind::CommandButton { .. });
2606    }
2607
2608    #[test]
2609    fn parse_properties_block_to_control_textbox() {
2610        let source = r#"Begin VB.TextBox Text1
2611   Text = "Initial Text"
2612   Height = 300
2613   Width = 2000
2614End
2615"#;
2616        let mut stream = SourceStream::new("test.frm".to_string(), source);
2617        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2618        let token_stream = token_stream_opt.expect("Tokenization failed");
2619        let tokens = token_stream.into_tokens();
2620
2621        let mut parser = Parser::new_direct_extraction(tokens, 0);
2622        let (control_opt, _failures) = parser.parse_properties_block_to_control().unpack();
2623
2624        assert!(control_opt.is_some());
2625        let control = control_opt.expect("Expected control to be parsed");
2626        assert_eq!(control.name(), "Text1");
2627        assert_matches!(control.kind(), ControlKind::TextBox { .. });
2628    }
2629
2630    #[test]
2631    fn parse_properties_block_without_begin() {
2632        let source = "Caption = \"Test\"\nEnd\n";
2633        let mut stream = SourceStream::new("test.frm".to_string(), source);
2634        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2635        let token_stream = token_stream_opt.expect("Tokenization failed");
2636        let tokens = token_stream.into_tokens();
2637
2638        let mut parser = Parser::new_direct_extraction(tokens, 0);
2639        let (control_opt, _failures) = parser.parse_properties_block_to_control().unpack();
2640
2641        // Should return None when BEGIN is missing
2642        assert!(control_opt.is_none());
2643    }
2644
2645    #[test]
2646    fn parse_form_with_nested_control() {
2647        let source = r#"Begin VB.Form Form1
2648   Caption = "Main Form"
2649   Begin VB.CommandButton Command1
2650      Caption = "Click Me"
2651      Height = 400
2652   End
2653End
2654"#;
2655        let mut stream = SourceStream::new("test.frm".to_string(), source);
2656        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2657        let token_stream = token_stream_opt.expect("Tokenization failed");
2658        let tokens = token_stream.into_tokens();
2659
2660        let mut parser = Parser::new_direct_extraction(tokens, 0);
2661        let (form_root_opt, failures) = parser.parse_properties_block_to_form_root().unpack();
2662
2663        assert!(failures.is_empty(), "Should have no failures");
2664        assert!(form_root_opt.is_some());
2665        let form_root = form_root_opt.expect("Expected form root to be parsed");
2666        assert_eq!(form_root.name(), "Form1");
2667
2668        // Check form has child controls
2669        if let FormRoot::Form(form) = &form_root {
2670            assert_eq!(form.controls.len(), 1);
2671            assert_eq!(form.controls[0].name(), "Command1");
2672            assert_matches!(form.controls[0].kind(), ControlKind::CommandButton { .. });
2673        } else {
2674            panic!("Expected Form");
2675        }
2676    }
2677
2678    #[test]
2679    fn parse_frame_with_multiple_nested_controls() {
2680        let source = r#"Begin VB.Frame Frame1
2681   Caption = "Options"
2682   Begin VB.CheckBox Check1
2683      Caption = "Option 1"
2684   End
2685   Begin VB.CheckBox Check2
2686      Caption = "Option 2"
2687   End
2688End
2689"#;
2690        let mut stream = SourceStream::new("test.frm".to_string(), source);
2691        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2692        let token_stream = token_stream_opt.expect("Tokenization failed");
2693        let tokens = token_stream.into_tokens();
2694
2695        let mut parser = Parser::new_direct_extraction(tokens, 0);
2696        let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
2697
2698        assert!(failures.is_empty());
2699        assert!(control_opt.is_some());
2700        let control = control_opt.expect("Expected control to be parsed");
2701        assert_eq!(control.name(), "Frame1");
2702
2703        // Check frame has 2 child checkboxes
2704        if let ControlKind::Frame { controls, .. } = control.kind() {
2705            assert_eq!(controls.len(), 2);
2706            assert_eq!(controls[0].name(), "Check1");
2707            assert_eq!(controls[1].name(), "Check2");
2708        } else {
2709            panic!("Expected Frame control kind");
2710        }
2711    }
2712
2713    #[test]
2714    fn parse_control_with_property_group() {
2715        let source = r#"Begin VB.CommandButton Command1
2716   Caption = "Button"
2717   BeginProperty Font
2718      Name = "Arial"
2719      Size = 12
2720   EndProperty
2721End
2722"#;
2723        let mut stream = SourceStream::new("test.frm".to_string(), source);
2724        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2725        let token_stream = token_stream_opt.expect("Tokenization failed");
2726        let tokens = token_stream.into_tokens();
2727
2728        let mut parser = Parser::new_direct_extraction(tokens, 0);
2729        let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
2730
2731        assert!(failures.is_empty());
2732        assert!(control_opt.is_some());
2733        let control = control_opt.expect("Expected control to be parsed");
2734        assert_eq!(control.name(), "Command1");
2735
2736        // Check for property - CommandButton should have parsed successfully
2737        // Property groups are stored in Custom control kind, not specific control types
2738        assert_matches!(control.kind(), ControlKind::CommandButton { .. });
2739    }
2740
2741    #[test]
2742    fn parse_custom_control_with_property_group() {
2743        let source = r#"Begin MSComctlLib.TreeView TreeView1
2744   BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851}
2745      Name = "MS Sans Serif"
2746      Size = 8.25
2747      Charset = 0
2748   EndProperty
2749   Caption = "Tree"
2750End
2751"#;
2752        let mut stream = SourceStream::new("test.frm".to_string(), source);
2753        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2754        let token_stream = token_stream_opt.expect("Tokenization failed");
2755        let tokens = token_stream.into_tokens();
2756
2757        let mut parser = Parser::new_direct_extraction(tokens, 0);
2758        let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
2759
2760        assert!(failures.is_empty());
2761        assert!(control_opt.is_some());
2762        let control = control_opt.expect("Expected control to be parsed");
2763        assert_eq!(control.name(), "TreeView1");
2764
2765        // Check for Custom control with property groups
2766        if let ControlKind::Custom {
2767            property_groups, ..
2768        } = control.kind()
2769        {
2770            assert_eq!(property_groups.len(), 1);
2771            assert_eq!(property_groups[0].name, "Font");
2772            assert!(property_groups[0].guid.is_some());
2773        } else {
2774            panic!("Expected Custom control kind");
2775        }
2776    }
2777
2778    #[test]
2779    fn parse_simple_object_statement() {
2780        let source = r#"Object = "{12345678-1234-1234-1234-123456789ABC}#1.0#0"; "MyLib.dll""#;
2781        let mut stream = SourceStream::new("test.frm".to_string(), source);
2782        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2783        let token_stream = token_stream_opt.expect("Tokenization failed");
2784        let tokens = token_stream.into_tokens();
2785
2786        let mut parser = Parser::new_direct_extraction(tokens, 0);
2787        let objects = parser.parse_objects_direct();
2788
2789        assert_eq!(objects.len(), 1);
2790        match &objects[0] {
2791            ObjectReference::Compiled {
2792                uuid,
2793                version,
2794                unknown1,
2795                file_name,
2796            } => {
2797                assert_eq!(
2798                    uuid.to_string().to_uppercase(),
2799                    "12345678-1234-1234-1234-123456789ABC"
2800                );
2801                assert_eq!(version, "1.0");
2802                assert_eq!(unknown1, "0");
2803                assert_eq!(file_name, "MyLib.dll");
2804            }
2805            ObjectReference::Project { .. } => {
2806                panic!("Expected Compiled object reference")
2807            }
2808        }
2809    }
2810
2811    #[test]
2812    fn parse_multiple_object_statements() {
2813        let source = r#"Object = "{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}#1.0#0"; "Lib1.dll"
2814Object = "{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}#2.0#1"; "Lib2.ocx"
2815"#;
2816        let mut stream = SourceStream::new("test.frm".to_string(), source);
2817        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2818        let token_stream = token_stream_opt.expect("Tokenization failed");
2819        let tokens = token_stream.into_tokens();
2820
2821        let mut parser = Parser::new_direct_extraction(tokens, 0);
2822        let objects = parser.parse_objects_direct();
2823
2824        assert_eq!(objects.len(), 2);
2825
2826        match &objects[0] {
2827            ObjectReference::Compiled { file_name, .. } => {
2828                assert_eq!(file_name, "Lib1.dll");
2829            }
2830            ObjectReference::Project { .. } => {
2831                panic!("Expected Compiled object reference")
2832            }
2833        }
2834
2835        match &objects[1] {
2836            ObjectReference::Compiled { file_name, .. } => {
2837                assert_eq!(file_name, "Lib2.ocx");
2838            }
2839            ObjectReference::Project { .. } => {
2840                panic!("Expected Compiled object reference")
2841            }
2842        }
2843    }
2844
2845    #[test]
2846    fn parse_embedded_object_statement() {
2847        let source = r#"Object = *\G{87654321-4321-4321-4321-CBA987654321}#3.0#5; "Embedded.ocx""#;
2848        let mut stream = SourceStream::new("test.frm".to_string(), source);
2849        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2850        let token_stream = token_stream_opt.expect("Tokenization failed");
2851        let tokens = token_stream.into_tokens();
2852
2853        let mut parser = Parser::new_direct_extraction(tokens, 0);
2854        let objects = parser.parse_objects_direct();
2855
2856        assert_eq!(objects.len(), 1);
2857        match &objects[0] {
2858            ObjectReference::Compiled {
2859                uuid,
2860                version,
2861                file_name,
2862                ..
2863            } => {
2864                assert_eq!(
2865                    uuid.to_string().to_uppercase(),
2866                    "87654321-4321-4321-4321-CBA987654321"
2867                );
2868                assert_eq!(version, "3.0");
2869                assert_eq!(file_name, "Embedded.ocx");
2870            }
2871            ObjectReference::Project { .. } => {
2872                panic!("Expected Compiled object reference")
2873            }
2874        }
2875    }
2876
2877    #[test]
2878    fn parse_nested_property_groups() {
2879        use either::Either;
2880
2881        let source = r#"Begin Custom.Control Ctrl1
2882   BeginProperty Outer
2883      Value1 = "Test"
2884      BeginProperty Inner
2885         Value2 = "Nested"
2886      EndProperty
2887   EndProperty
2888End
2889"#;
2890        let mut stream = SourceStream::new("test.frm".to_string(), source);
2891        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2892        let token_stream = token_stream_opt.expect("Tokenization failed");
2893        let tokens = token_stream.into_tokens();
2894
2895        let mut parser = Parser::new_direct_extraction(tokens, 0);
2896        let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
2897
2898        assert!(failures.is_empty());
2899        assert!(control_opt.is_some());
2900        let control = control_opt.expect("Expected control to be parsed");
2901
2902        // Check for nested property groups
2903        if let ControlKind::Custom {
2904            property_groups, ..
2905        } = control.kind()
2906        {
2907            assert_eq!(property_groups.len(), 1);
2908            assert_eq!(property_groups[0].name, "Outer");
2909
2910            // Check for nested group
2911
2912            if let Some(Either::Right(inner)) = property_groups[0].properties.get("Inner") {
2913                assert_eq!(inner.name, "Inner");
2914            } else {
2915                panic!("Expected nested Inner property group");
2916            }
2917        } else {
2918            panic!("Expected Custom control kind");
2919        }
2920    }
2921
2922    #[test]
2923    fn parse_deeply_nested_controls() {
2924        let source = r#"Begin VB.Form Form1
2925   Caption = "Outer"
2926   Begin VB.PictureBox Picture1
2927      Begin VB.Frame Frame1
2928         Begin VB.Label Label1
2929            Caption = "Deep"
2930         End
2931      End
2932   End
2933End
2934"#;
2935        let mut stream = SourceStream::new("test.frm".to_string(), source);
2936        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2937        let token_stream = token_stream_opt.expect("Tokenization failed");
2938        let tokens = token_stream.into_tokens();
2939
2940        let mut parser = Parser::new_direct_extraction(tokens, 0);
2941        let (form_root_opt, failures) = parser.parse_properties_block_to_form_root().unpack();
2942
2943        assert!(failures.is_empty());
2944        assert!(form_root_opt.is_some());
2945        let form_root = form_root_opt.expect("Expected form root to be parsed");
2946
2947        // Verify deep nesting: Form > PictureBox > Frame > Label
2948        if let FormRoot::Form(form) = &form_root {
2949            assert_eq!(form.controls.len(), 1);
2950            if let ControlKind::PictureBox { controls, .. } = form.controls[0].kind() {
2951                assert_eq!(controls.len(), 1);
2952                if let ControlKind::Frame { controls, .. } = controls[0].kind() {
2953                    assert_eq!(controls.len(), 1);
2954                    assert_eq!(controls[0].name(), "Label1");
2955                } else {
2956                    panic!("Expected Frame");
2957                }
2958            } else {
2959                panic!("Expected PictureBox");
2960            }
2961        } else {
2962            panic!("Expected Form");
2963        }
2964    }
2965
2966    #[test]
2967    fn parse_simple_string_attribute() {
2968        let source = r#"Attribute VB_Name = "Form1"
2969"#;
2970        let mut stream = SourceStream::new("test.frm".to_string(), source);
2971        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2972        let token_stream = token_stream_opt.expect("Tokenization failed");
2973        let tokens = token_stream.into_tokens();
2974
2975        let mut parser = Parser::new_direct_extraction(tokens, 0);
2976        let attrs = parser.parse_attributes_direct();
2977
2978        assert_eq!(attrs.name, "Form1");
2979        assert_eq!(attrs.global_name_space, NameSpace::Local);
2980        assert_eq!(attrs.creatable, Creatable::True);
2981        assert_eq!(attrs.predeclared_id, PreDeclaredID::False);
2982        assert_eq!(attrs.exposed, Exposed::False);
2983        assert_eq!(attrs.description, None);
2984    }
2985
2986    #[test]
2987    fn parse_boolean_attributes() {
2988        let source = r"Attribute VB_GlobalNameSpace = False
2989Attribute VB_Creatable = True
2990Attribute VB_PredeclaredId = True
2991Attribute VB_Exposed = False
2992";
2993        let mut stream = SourceStream::new("test.frm".to_string(), source);
2994        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
2995        let token_stream = token_stream_opt.expect("Tokenization failed");
2996        let tokens = token_stream.into_tokens();
2997
2998        let mut parser = Parser::new_direct_extraction(tokens, 0);
2999        let attrs = parser.parse_attributes_direct();
3000
3001        assert_eq!(attrs.global_name_space, NameSpace::Local);
3002        assert_eq!(attrs.creatable, Creatable::True);
3003        assert_eq!(attrs.predeclared_id, PreDeclaredID::True);
3004        assert_eq!(attrs.exposed, Exposed::False);
3005    }
3006
3007    #[test]
3008    fn parse_numeric_attribute() {
3009        let source = r"Attribute VB_PredeclaredId = -1
3010";
3011        let mut stream = SourceStream::new("test.frm".to_string(), source);
3012        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
3013        let token_stream = token_stream_opt.expect("Tokenization failed");
3014        let tokens = token_stream.into_tokens();
3015
3016        let mut parser = Parser::new_direct_extraction(tokens, 0);
3017        let attrs = parser.parse_attributes_direct();
3018
3019        // -1 is truthy in VB6, so should be parsed as true
3020        assert_eq!(attrs.predeclared_id, PreDeclaredID::True);
3021    }
3022
3023    #[test]
3024    fn parse_multiple_attributes() {
3025        let source = r#"Attribute VB_Name = "MyForm"
3026Attribute VB_GlobalNameSpace = False
3027Attribute VB_Creatable = False
3028Attribute VB_PredeclaredId = True
3029Attribute VB_Exposed = False
3030Attribute VB_Description = "This is a test form"
3031"#;
3032        let mut stream = SourceStream::new("test.frm".to_string(), source);
3033        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
3034        let token_stream = token_stream_opt.expect("Tokenization failed");
3035        let tokens = token_stream.into_tokens();
3036
3037        let mut parser = Parser::new_direct_extraction(tokens, 0);
3038        let attrs = parser.parse_attributes_direct();
3039
3040        assert_eq!(attrs.name, "MyForm");
3041        assert_eq!(attrs.global_name_space, NameSpace::Local);
3042        assert_eq!(attrs.creatable, Creatable::False);
3043        assert_eq!(attrs.predeclared_id, PreDeclaredID::True);
3044        assert_eq!(attrs.exposed, Exposed::False);
3045        assert_eq!(attrs.description, Some("This is a test form".to_string()));
3046    }
3047
3048    #[test]
3049    fn parse_ext_key_attributes() {
3050        let source = r#"Attribute VB_Name = "Form1"
3051Attribute VB_Ext_KEY = "CustomKey" ,"CustomValue"
3052Attribute VB_Description = "Test"
3053"#;
3054        let mut stream = SourceStream::new("test.frm".to_string(), source);
3055        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
3056        let token_stream = token_stream_opt.expect("Tokenization failed");
3057        let tokens = token_stream.into_tokens();
3058
3059        let mut parser = Parser::new_direct_extraction(tokens, 0);
3060        let attrs = parser.parse_attributes_direct();
3061
3062        assert_eq!(attrs.name, "Form1");
3063        assert_eq!(attrs.description, Some("Test".to_string()));
3064        assert_eq!(attrs.ext_key.len(), 1);
3065        assert!(attrs.ext_key.contains_key("VB_Ext_KEY"));
3066    }
3067
3068    #[test]
3069    fn parse_empty_attributes() {
3070        let source = r"";
3071        let mut stream = SourceStream::new("test.frm".to_string(), source);
3072        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
3073        let token_stream = token_stream_opt.expect("Tokenization failed");
3074        let tokens = token_stream.into_tokens();
3075
3076        let mut parser = Parser::new_direct_extraction(tokens, 0);
3077        let attrs = parser.parse_attributes_direct();
3078
3079        assert_eq!(attrs.name, "");
3080        assert_eq!(attrs.global_name_space, NameSpace::Local);
3081        assert_eq!(attrs.creatable, Creatable::True);
3082        assert_eq!(attrs.predeclared_id, PreDeclaredID::False);
3083        assert_eq!(attrs.exposed, Exposed::False);
3084        assert_eq!(attrs.description, None);
3085        assert!(attrs.ext_key.is_empty());
3086    }
3087
3088    #[test]
3089    fn parse_resource_reference_property() {
3090        let source = r#"Caption = $"Gradient.frx":0000
3091"#;
3092        let mut stream = SourceStream::new("test.frm".to_string(), source);
3093        let (token_stream_opt, _) = tokenize(&mut stream).unpack();
3094        let token_stream = token_stream_opt.expect("Tokenization failed");
3095        let tokens = token_stream.into_tokens();
3096
3097        let mut parser = Parser::new_direct_extraction(tokens, 0);
3098        let property = parser.parse_property_direct();
3099
3100        assert!(property.is_some());
3101        let (key, value) = property.expect("Expected property to be parsed");
3102        assert_eq!(key, "Caption");
3103        assert_eq!(value, r#"$"Gradient.frx":0000"#);
3104    }
3105}