Skip to main content

forge_kit/
parser.rs

1//! High-performance AST parser for ForgeScript with optional validation
2//!
3//! This module provides a fast, single-pass parser that builds a proper Abstract Syntax Tree
4//! with extensive optimizations for speed and memory efficiency, plus optional validation.
5
6use smallvec::SmallVec;
7
8// Optional validation support
9#[cfg(feature = "validation")]
10use crate::metadata::MetadataManager;
11#[cfg(feature = "validation")]
12use crate::types::{Arg, Function};
13#[cfg(feature = "validation")]
14use std::sync::Arc;
15
16// ============================================================================
17// Utility: Escape Detection
18// ============================================================================
19
20/// Determines if a character at a given byte index is escaped by backslashes.
21/// This checks for an odd number of preceding backslashes.
22#[inline]
23pub fn is_escaped(code: &str, byte_idx: usize) -> bool {
24    if byte_idx == 0 || !code.is_char_boundary(byte_idx) {
25        return false;
26    }
27    let bytes = code.as_bytes();
28    let mut count = 0;
29    let mut i = byte_idx;
30    while i > 0 && bytes[i - 1] == b'\\' {
31        count += 1;
32        i -= 1;
33    }
34    count % 2 != 0
35}
36
37// ============================================================================
38// Validation Configuration
39// ============================================================================
40
41/// Configuration for parser validation
42#[derive(Debug, Clone, Default)]
43pub struct ValidationConfig {
44    /// Validate argument counts against function metadata
45    pub validate_arguments: bool,
46    /// Validate enum values against defined enums
47    pub validate_enums: bool,
48    /// Validate that all functions exist in metadata
49    pub validate_functions: bool,
50    /// Validate bracket usage (required/optional/forbidden)
51    pub validate_brackets: bool,
52    /// Validate escape sequences are properly formed
53    pub validate_escapes: bool,
54}
55
56impl ValidationConfig {
57    /// Enable all validations
58    pub fn strict() -> Self {
59        Self {
60            validate_arguments: true,
61            validate_enums: true,
62            validate_functions: true,
63            validate_brackets: true,
64            validate_escapes: true,
65        }
66    }
67
68    /// Enable only syntax validations (no metadata required)
69    pub fn syntax_only() -> Self {
70        Self {
71            validate_arguments: false,
72            validate_enums: false,
73            validate_functions: false,
74            validate_brackets: true,
75            validate_escapes: true,
76        }
77    }
78
79    /// Check if any validation is enabled
80    #[inline]
81    pub fn is_enabled(&self) -> bool {
82        self.validate_arguments
83            || self.validate_enums
84            || self.validate_functions
85            || self.validate_brackets
86            || self.validate_escapes
87    }
88}
89
90// ============================================================================
91// AST Node Definitions
92// ============================================================================
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct Span {
96    pub start: usize,
97    pub end: usize,
98}
99
100impl Span {
101    #[inline(always)]
102    pub const fn new(start: usize, end: usize) -> Self {
103        Self { start, end }
104    }
105
106    #[inline(always)]
107    pub fn offset(&mut self, offset: usize) {
108        self.start += offset;
109        self.end += offset;
110    }
111
112    #[inline(always)]
113    pub fn len(&self) -> usize {
114        self.end - self.start
115    }
116
117    #[inline(always)]
118    pub fn is_empty(&self) -> bool {
119        self.start >= self.end
120    }
121}
122
123#[derive(Debug, Clone, Default)]
124pub struct Modifiers {
125    pub silent: bool,
126    pub negated: bool,
127    pub count: Option<String>,
128}
129
130#[derive(Debug, Clone)]
131pub struct Argument {
132    pub parts: SmallVec<[AstNode; 4]>,
133    pub span: Span,
134}
135
136impl Argument {
137    /// Check if argument is effectively empty (only whitespace/empty text nodes)
138    pub fn is_empty(&self) -> bool {
139        self.parts.iter().all(|part| match part {
140            AstNode::Text { content, .. } => content.trim().is_empty(),
141            _ => false,
142        })
143    }
144
145    /// Get literal text value if argument is purely text
146    pub fn as_text(&self) -> Option<String> {
147        if self.parts.len() == 1 {
148            if let AstNode::Text { content, .. } = &self.parts[0] {
149                return Some(content.clone());
150            }
151        }
152
153        // Try to concatenate if all parts are text
154        if self.parts.iter().all(|p| matches!(p, AstNode::Text { .. })) {
155            let mut result = String::new();
156            for part in &self.parts {
157                if let AstNode::Text { content, .. } = part {
158                    result.push_str(&content);
159                }
160            }
161            return Some(result);
162        }
163
164        None
165    }
166}
167
168#[derive(Debug, Clone)]
169pub enum AstNode {
170    Program {
171        body: Vec<AstNode>,
172        span: Span,
173    },
174    Text {
175        content: String,
176        span: Span,
177    },
178    FunctionCall {
179        name: String,
180        args: Option<Vec<Argument>>,
181        modifiers: Modifiers,
182        span: Span,
183    },
184    JavaScript {
185        code: String,
186        span: Span,
187    },
188    Escaped {
189        content: String,
190        span: Span,
191    },
192}
193
194impl AstNode {
195    pub fn span(&self) -> Span {
196        match self {
197            AstNode::Program { span, .. }
198            | AstNode::Text { span, .. }
199            | AstNode::FunctionCall { span, .. }
200            | AstNode::JavaScript { span, .. }
201            | AstNode::Escaped { span, .. } => *span,
202        }
203    }
204
205    pub fn offset_spans(&mut self, offset: usize) {
206        match self {
207            AstNode::Program { body, span } => {
208                span.offset(offset);
209                for node in body {
210                    node.offset_spans(offset);
211                }
212            }
213            AstNode::Text { span, .. }
214            | AstNode::JavaScript { span, .. }
215            | AstNode::Escaped { span, .. } => {
216                span.offset(offset);
217            }
218            AstNode::FunctionCall { args, span, .. } => {
219                span.offset(offset);
220                if let Some(args) = args {
221                    for arg in args {
222                        arg.span.offset(offset);
223                        for part in &mut arg.parts {
224                            part.offset_spans(offset);
225                        }
226                    }
227                }
228            }
229        }
230    }
231}
232
233// ============================================================================
234// Parse Errors
235// ============================================================================
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub enum ErrorKind {
239    Syntax,
240    ArgumentCount,
241    EnumValue,
242    UnknownFunction,
243    BracketUsage,
244    InvalidEscape,
245}
246
247#[derive(Debug, Clone)]
248pub struct ParseError {
249    pub message: String,
250    pub span: Span,
251    pub kind: ErrorKind,
252}
253
254impl ParseError {
255    #[inline]
256    pub fn new(message: impl Into<String>, span: Span, kind: ErrorKind) -> Self {
257        Self {
258            message: message.into(),
259            span,
260            kind,
261        }
262    }
263
264    #[inline]
265    pub fn syntax(message: impl Into<String>, span: Span) -> Self {
266        Self::new(message, span, ErrorKind::Syntax)
267    }
268}
269
270// ============================================================================
271// Parser
272// ============================================================================
273
274pub struct Parser<'src> {
275    source: &'src str,
276    bytes: &'src [u8],
277    pos: usize,
278    errors: Vec<ParseError>,
279    config: ValidationConfig,
280    #[cfg(feature = "validation")]
281    metadata: Option<Arc<MetadataManager>>,
282}
283
284impl<'src> Parser<'src> {
285    #[inline]
286    pub fn new(source: &'src str) -> Self {
287        Self {
288            source,
289            bytes: source.as_bytes(),
290            pos: 0,
291            errors: Vec::new(),
292            config: ValidationConfig::default(),
293            #[cfg(feature = "validation")]
294            metadata: None,
295        }
296    }
297
298    /// Create parser with validation configuration (requires "validation" feature)
299    #[cfg(feature = "validation")]
300    #[inline]
301    pub fn with_config(source: &'src str, config: ValidationConfig) -> Self {
302        Self {
303            source,
304            bytes: source.as_bytes(),
305            pos: 0,
306            errors: Vec::new(),
307            config,
308            metadata: None,
309        }
310    }
311
312    /// Create parser with validation and metadata (requires "validation" feature)
313    #[cfg(feature = "validation")]
314    #[inline]
315    pub fn with_validation(
316        source: &'src str,
317        config: ValidationConfig,
318        metadata: Arc<MetadataManager>,
319    ) -> Self {
320        Self {
321            source,
322            bytes: source.as_bytes(),
323            pos: 0,
324            errors: Vec::new(),
325            config,
326            metadata: Some(metadata),
327        }
328    }
329
330    pub fn parse(mut self) -> (AstNode, Vec<ParseError>) {
331        let start = self.pos;
332        let mut body = Vec::new();
333
334        while !self.is_eof() {
335            // Find start of "code: `" block
336            if let Some(block_start) = self.find_code_block_start() {
337                // Add text before block
338                if block_start > self.pos {
339                    body.push(AstNode::Text {
340                        content: self.slice(self.pos, block_start).to_string(),
341                        span: Span::new(self.pos, block_start),
342                    });
343                }
344
345                // Move pos to start of content (after "code: `")
346                let content_start = block_start + 7; // len("code: `")
347                self.pos = content_start;
348
349                // Find end of block (unescaped `)
350                if let Some(block_end) = self.find_code_block_end() {
351                    let content_len = block_end - content_start;
352
353                    if content_len > 0 {
354                        // Parse content inside block
355                        let inner_source = self.slice(content_start, block_end);
356
357                        #[cfg(feature = "validation")]
358                        let inner_parser = if self.config.is_enabled() {
359                            if let Some(ref metadata) = self.metadata {
360                                Parser::with_validation(
361                                    inner_source,
362                                    self.config.clone(),
363                                    metadata.clone(),
364                                )
365                            } else {
366                                Parser::with_config(inner_source, self.config.clone())
367                            }
368                        } else {
369                            Parser::new(inner_source)
370                        };
371
372                        #[cfg(not(feature = "validation"))]
373                        let inner_parser = Parser::new(inner_source);
374
375                        let (mut inner_ast, inner_errors) = inner_parser.parse_forge_script();
376
377                        inner_ast.offset_spans(content_start);
378
379                        match inner_ast {
380                            AstNode::Program {
381                                body: inner_body, ..
382                            } => {
383                                body.extend(inner_body);
384                            }
385                            _ => body.push(inner_ast),
386                        }
387
388                        for mut error in inner_errors {
389                            error.span.offset(content_start);
390                            self.errors.push(error);
391                        }
392                    }
393
394                    // Move past closing backtick
395                    self.pos = block_end + 1;
396                } else {
397                    // Unclosed block
398                    if self.config.validate_brackets {
399                        self.errors.push(ParseError::syntax(
400                            "Unclosed code block",
401                            Span::new(block_start, self.source.len()),
402                        ));
403                    }
404                    body.push(AstNode::Text {
405                        content: self.slice(block_start, self.source.len()).to_string(),
406                        span: Span::new(block_start, self.source.len()),
407                    });
408                    self.pos = self.source.len();
409                }
410            } else {
411                // No more blocks, rest is text
412                if self.pos < self.source.len() {
413                    body.push(AstNode::Text {
414                        content: self.slice(self.pos, self.source.len()).to_string(),
415                        span: Span::new(self.pos, self.source.len()),
416                    });
417                }
418                self.pos = self.source.len();
419            }
420        }
421
422        let span = Span::new(start, self.source.len());
423        (AstNode::Program { body, span }, self.errors)
424    }
425
426    fn parse_forge_script(mut self) -> (AstNode, Vec<ParseError>) {
427        let start = self.pos;
428        let mut body = Vec::new();
429
430        while !self.is_eof() {
431            if let Some(node) = self.parse_forge_node() {
432                body.push(node);
433            }
434        }
435
436        let span = Span::new(start, self.source.len());
437        (AstNode::Program { body, span }, self.errors)
438    }
439
440    // ========================================================================
441    // Character/Position Utilities
442    // ========================================================================
443
444    #[inline(always)]
445    fn is_eof(&self) -> bool {
446        self.pos >= self.bytes.len()
447    }
448
449    #[inline(always)]
450    fn current_byte(&self) -> Option<u8> {
451        self.bytes.get(self.pos).copied()
452    }
453
454    #[inline(always)]
455    fn peek_byte(&self, offset: usize) -> Option<u8> {
456        self.bytes.get(self.pos + offset).copied()
457    }
458
459    #[inline(always)]
460    fn advance(&mut self) -> Option<u8> {
461        let byte = self.current_byte()?;
462        self.pos += 1;
463        Some(byte)
464    }
465
466    #[inline]
467    fn slice(&self, start: usize, end: usize) -> &'src str {
468        &self.source[start..end.min(self.source.len())]
469    }
470
471    #[inline]
472    fn is_escaped_at(&self, pos: usize) -> bool {
473        is_escaped(self.source, pos)
474    }
475
476    fn find_code_block_start(&self) -> Option<usize> {
477        let mut p = self.pos;
478        while p + 7 <= self.bytes.len() {
479            // Check for "code: `"
480            if &self.bytes[p..p + 7] == b"code: `" {
481                let preceded_by_valid = p == 0
482                    || self.bytes[p - 1].is_ascii_whitespace()
483                    || self.bytes[p - 1] == b'{'
484                    || self.bytes[p - 1] == b',';
485                if preceded_by_valid && !is_escaped(self.source, p + 6) {
486                    return Some(p);
487                }
488            }
489            p += 1;
490        }
491        None
492    }
493
494    fn find_code_block_end(&self) -> Option<usize> {
495        let mut p = self.pos;
496        while p < self.bytes.len() {
497            if self.bytes[p] == b'`' && !is_escaped(self.source, p) {
498                return Some(p);
499            }
500            p += 1;
501        }
502        None
503    }
504
505    // ========================================================================
506    // High-Level Parsing
507    // ========================================================================
508
509    fn parse_forge_node(&mut self) -> Option<AstNode> {
510        // Handle backslash escapes explicitly
511        if self.current_byte() == Some(b'\\') {
512            return self.parse_escape_sequence();
513        }
514
515        // Handle $ sequences
516        if self.current_byte() == Some(b'$') && !self.is_escaped_at(self.pos) {
517            if self.peek_byte(1) == Some(b'{') {
518                return Some(self.parse_javascript());
519            }
520            return Some(self.parse_function_call());
521        }
522
523        self.parse_text()
524    }
525
526    fn parse_text(&mut self) -> Option<AstNode> {
527        let start = self.pos;
528        while !self.is_eof() {
529            if self.current_byte() == Some(b'\\') {
530                break;
531            }
532            if self.current_byte() == Some(b'$') && !self.is_escaped_at(self.pos) {
533                break;
534            }
535            self.advance();
536        }
537
538        if self.pos > start {
539            Some(AstNode::Text {
540                content: self.slice(start, self.pos).to_string(),
541                span: Span::new(start, self.pos),
542            })
543        } else {
544            None
545        }
546    }
547
548    fn parse_escape_sequence(&mut self) -> Option<AstNode> {
549        let start = self.pos;
550        self.advance(); // consume '\'
551
552        if let Some(next) = self.current_byte() {
553            // If the next char is a backslash, we have a \\ pair (escaped backslash)
554            if next == b'\\' {
555                self.advance();
556                return Some(AstNode::Text {
557                    content: "\\".to_string(),
558                    span: Span::new(start, self.pos),
559                });
560            }
561            // If it escapes a special char
562            if matches!(next, b'$' | b'[' | b']' | b';' | b'`') {
563                self.advance();
564                return Some(AstNode::Text {
565                    content: self.slice(start + 1, self.pos).to_string(),
566                    span: Span::new(start, self.pos),
567                });
568            }
569
570            // Invalid escape sequence
571            if self.config.validate_escapes {
572                self.errors.push(ParseError::new(
573                    format!("Invalid escape sequence: \\{}", next as char),
574                    Span::new(start, start + 2),
575                    ErrorKind::InvalidEscape,
576                ));
577            }
578        }
579
580        // Just a lone backslash
581        Some(AstNode::Text {
582            content: "\\".to_string(),
583            span: Span::new(start, start + 1),
584        })
585    }
586
587    fn parse_javascript(&mut self) -> AstNode {
588        let start = self.pos;
589        self.advance(); // '$'
590        self.advance(); // '{'
591        let brace_start = self.pos - 1;
592
593        if let Some(end) = self.find_matching_brace(brace_start) {
594            let code = self.slice(brace_start + 1, end).to_string();
595            self.pos = end + 1;
596            AstNode::JavaScript {
597                code,
598                span: Span::new(start, self.pos),
599            }
600        } else {
601            if self.config.validate_brackets {
602                self.errors.push(ParseError::syntax(
603                    "Unclosed JavaScript expression",
604                    Span::new(start, self.source.len()),
605                ));
606            }
607            self.pos = self.source.len();
608            AstNode::JavaScript {
609                code: String::new(),
610                span: Span::new(start, self.pos),
611            }
612        }
613    }
614
615    fn parse_function_call(&mut self) -> AstNode {
616        let start = self.pos;
617        self.advance(); // '$'
618
619        let modifiers = self.parse_modifiers();
620        let name = self.parse_identifier();
621
622        if name.is_empty() {
623            return AstNode::Text {
624                content: "$".to_string(),
625                span: Span::new(start, start + 1),
626            };
627        }
628
629        if self.is_escape_function(&name) {
630            return self.parse_escape_function(start, name);
631        }
632
633        let has_brackets = self.current_byte() == Some(b'[');
634        let args = if has_brackets {
635            self.parse_function_arguments()
636        } else {
637            None
638        };
639
640        let span = Span::new(start, self.pos);
641
642        // Validate with metadata if available
643        #[cfg(feature = "validation")]
644        if self.config.is_enabled() {
645            // Add $ prefix if not present for validation
646            let full_name = if name.starts_with('$') {
647                name.clone()
648            } else {
649                format!("${}", name)
650            };
651
652            if let Some(ref metadata) = self.metadata {
653                if let Some(func) = metadata.get(&full_name) {
654                    self.validate_function_call(
655                        &full_name,
656                        &func,
657                        args.as_ref(),
658                        has_brackets,
659                        span,
660                    );
661                } else if self.config.validate_functions {
662                    self.errors.push(ParseError::new(
663                        format!("Unknown function: {}", full_name),
664                        span,
665                        ErrorKind::UnknownFunction,
666                    ));
667                }
668            } else if self.config.validate_functions {
669                // Validation requested but no metadata available
670                self.errors.push(ParseError::new(
671                    format!(
672                        "Cannot validate function {}: no metadata available",
673                        full_name
674                    ),
675                    span,
676                    ErrorKind::UnknownFunction,
677                ));
678            }
679        }
680
681        AstNode::FunctionCall {
682            name,
683            args,
684            modifiers,
685            span,
686        }
687    }
688
689    // ========================================================================
690    // Validation
691    // ========================================================================
692
693    #[cfg(feature = "validation")]
694    fn validate_function_call(
695        &mut self,
696        name: &str,
697        func: &Function,
698        args: Option<&Vec<Argument>>,
699        has_brackets: bool,
700        span: Span,
701    ) {
702        // Validate brackets usage
703        if self.config.validate_brackets {
704            match func.brackets {
705                Some(true) => {
706                    // Brackets required
707                    if !has_brackets {
708                        self.errors.push(ParseError::new(
709                            format!("{} requires brackets", name),
710                            span,
711                            ErrorKind::BracketUsage,
712                        ));
713                    }
714                }
715                Some(false) => {
716                    // Brackets optional (no error either way)
717                }
718                None => {
719                    // Brackets forbidden
720                    if has_brackets {
721                        self.errors.push(ParseError::new(
722                            format!("{} does not accept brackets", name),
723                            span,
724                            ErrorKind::BracketUsage,
725                        ));
726                    }
727                }
728            }
729        }
730
731        // Validate argument count and enums
732        if (self.config.validate_arguments || self.config.validate_enums) && has_brackets {
733            if let (Some(args), Some(func_args)) = (args, &func.args) {
734                self.validate_arguments(name, args, func_args, span);
735            }
736        }
737    }
738
739    #[cfg(feature = "validation")]
740    fn validate_arguments(
741        &mut self,
742        func_name: &str,
743        provided_args: &[Argument],
744        func_args: &[Arg],
745        span: Span,
746    ) {
747        let provided_count = provided_args.len();
748
749        // Check for rest parameter
750        let has_rest = func_args.iter().any(|a| a.rest);
751
752        // Count required arguments
753        let required_count = func_args
754            .iter()
755            .filter(|a| a.required.unwrap_or(false) && !a.rest)
756            .count();
757
758        // Count total non-rest arguments
759        let max_count = if has_rest {
760            usize::MAX
761        } else {
762            func_args.len()
763        };
764
765        // Validate argument count
766        if self.config.validate_arguments {
767            if provided_count < required_count {
768                self.errors.push(ParseError::new(
769                    format!(
770                        "{} requires at least {} argument(s), got {}",
771                        func_name, required_count, provided_count
772                    ),
773                    span,
774                    ErrorKind::ArgumentCount,
775                ));
776            } else if !has_rest && provided_count > max_count {
777                self.errors.push(ParseError::new(
778                    format!(
779                        "{} accepts at most {} argument(s), got {}",
780                        func_name, max_count, provided_count
781                    ),
782                    span,
783                    ErrorKind::ArgumentCount,
784                ));
785            }
786        }
787
788        // Validate enum values
789        if self.config.validate_enums {
790            for (i, provided_arg) in provided_args.iter().enumerate() {
791                // Get the corresponding function argument definition
792                let func_arg = if i < func_args.len() {
793                    &func_args[i]
794                } else if has_rest {
795                    // Use last argument (rest parameter) for overflow
796                    func_args.last().unwrap()
797                } else {
798                    continue;
799                };
800
801                self.validate_enum_value(func_name, provided_arg, func_arg);
802            }
803        }
804    }
805
806    #[cfg(feature = "validation")]
807    fn validate_enum_value(&mut self, func_name: &str, arg: &Argument, func_arg: &Arg) {
808        // Skip if not required and empty
809        if !func_arg.required.unwrap_or(false) && arg.is_empty() {
810            return;
811        }
812
813        // Get enum values
814        let enum_values = if let Some(enum_name) = &func_arg.enum_name {
815            // Get from metadata
816            if let Some(ref metadata) = self.metadata {
817                metadata.get_enum(enum_name)
818            } else {
819                None
820            }
821        } else {
822            // Use inline enum
823            func_arg.arg_enum.clone()
824        };
825
826        if let Some(valid_values) = enum_values {
827            // Only validate if argument is pure text
828            if let Some(text_value) = arg.as_text() {
829                let trimmed = text_value.trim();
830
831                if !trimmed.is_empty() && !valid_values.contains(&trimmed.to_string()) {
832                    self.errors.push(ParseError::new(
833                        format!(
834                            "Invalid value '{}' for {} in {}. Expected one of: {}",
835                            trimmed,
836                            func_arg.name,
837                            func_name,
838                            valid_values.join(", ")
839                        ),
840                        arg.span,
841                        ErrorKind::EnumValue,
842                    ));
843                }
844            }
845        }
846    }
847
848    // ========================================================================
849    // Parsing Helpers
850    // ========================================================================
851
852    fn parse_modifiers(&mut self) -> Modifiers {
853        let mut modifiers = Modifiers::default();
854        loop {
855            match self.current_byte() {
856                Some(b'!') => {
857                    modifiers.silent = true;
858                    self.advance();
859                }
860                Some(b'#') => {
861                    modifiers.negated = true;
862                    self.advance();
863                }
864                Some(b'@') if self.peek_byte(1) == Some(b'[') => {
865                    self.advance(); // '@'
866                    let bracket_start = self.pos;
867                    self.advance(); // '['
868                    if let Some(end) = self.find_matching_bracket(bracket_start) {
869                        modifiers.count = Some(self.slice(bracket_start + 1, end).to_string());
870                        self.pos = end + 1;
871                    } else if self.config.validate_brackets {
872                        self.errors.push(ParseError::syntax(
873                            "Unclosed modifier bracket",
874                            Span::new(bracket_start, bracket_start + 1),
875                        ));
876                        break;
877                    } else {
878                        break;
879                    }
880                }
881                _ => break,
882            }
883        }
884        modifiers
885    }
886
887    #[inline]
888    fn parse_identifier(&mut self) -> String {
889        let start = self.pos;
890        while let Some(b) = self.current_byte() {
891            if b.is_ascii_alphanumeric() || b == b'_' {
892                self.advance();
893            } else {
894                break;
895            }
896        }
897        self.slice(start, self.pos).to_string()
898    }
899
900    fn is_escape_function(&self, name: &str) -> bool {
901        matches!(name, "c" | "C" | "escape")
902    }
903
904    fn parse_escape_function(&mut self, start: usize, name: String) -> AstNode {
905        if self.current_byte() != Some(b'[') {
906            if self.config.validate_brackets {
907                self.errors.push(ParseError::new(
908                    format!("${} requires brackets", name),
909                    Span::new(start, self.pos),
910                    ErrorKind::BracketUsage,
911                ));
912            }
913            return AstNode::Text {
914                content: self.slice(start, self.pos).to_string(),
915                span: Span::new(start, self.pos),
916            };
917        }
918
919        let bracket_start = self.pos;
920        self.advance();
921        if let Some(end) = self.find_matching_bracket(bracket_start) {
922            let content = self.slice(bracket_start + 1, end).to_string();
923            self.pos = end + 1;
924            AstNode::Escaped {
925                content,
926                span: Span::new(start, self.pos),
927            }
928        } else {
929            if self.config.validate_brackets {
930                self.errors.push(ParseError::syntax(
931                    format!("Unclosed '[' for ${}", name),
932                    Span::new(start, self.source.len()),
933                ));
934            }
935            self.pos = self.source.len();
936            AstNode::Escaped {
937                content: String::new(),
938                span: Span::new(start, self.pos),
939            }
940        }
941    }
942
943    fn parse_function_arguments(&mut self) -> Option<Vec<Argument>> {
944        let bracket_start = self.pos;
945        self.advance();
946        if let Some(end) = self.find_matching_bracket(bracket_start) {
947            let args_content = self.slice(bracket_start + 1, end);
948            let parsed_args = self.parse_arguments(args_content, bracket_start + 1);
949            self.pos = end + 1;
950            Some(parsed_args)
951        } else {
952            if self.config.validate_brackets {
953                self.errors.push(ParseError::syntax(
954                    "Unclosed function arguments",
955                    Span::new(bracket_start, bracket_start + 1),
956                ));
957            }
958            None
959        }
960    }
961
962    fn parse_arguments(&mut self, content: &str, base_offset: usize) -> Vec<Argument> {
963        let mut args = Vec::new();
964        let mut current = String::new();
965        let mut depth = 0;
966        let bytes = content.as_bytes();
967        let mut i = 0;
968        let mut arg_start = 0;
969
970        while i < bytes.len() {
971            if bytes[i] == b'$' && depth == 0 {
972                if let Some(esc_end) = self.find_escape_function_end(content, i) {
973                    current.push_str(&content[i..=esc_end]);
974                    i = esc_end + 1;
975                    continue;
976                }
977            }
978
979            if bytes[i] == b'\\' {
980                if let Some(next) = bytes.get(i + 1) {
981                    if matches!(*next, b'`' | b'$' | b'[' | b']' | b';' | b'\\') {
982                        current.push_str(&content[i..i + 2]);
983                        i += 2;
984                        continue;
985                    }
986                }
987                current.push('\\');
988                i += 1;
989                continue;
990            }
991
992            match bytes[i] {
993                b'[' if !is_escaped(content, i) && self.is_function_bracket(content, i) => {
994                    depth += 1;
995                    current.push('[');
996                }
997                b']' if depth > 0 && !is_escaped(content, i) => {
998                    depth -= 1;
999                    current.push(']');
1000                }
1001                b';' if depth == 0 => {
1002                    let arg_offset = base_offset + arg_start;
1003                    let parts = self.parse_argument_parts(&current, arg_offset);
1004                    args.push(Argument {
1005                        parts,
1006                        span: Span::new(arg_offset, arg_offset + current.len()),
1007                    });
1008                    current.clear();
1009                    arg_start = i + 1;
1010                }
1011                _ => current.push(bytes[i] as char),
1012            }
1013            i += 1;
1014        }
1015
1016        if !current.is_empty() || !args.is_empty() {
1017            let arg_offset = base_offset + arg_start;
1018            let parts = self.parse_argument_parts(&current, arg_offset);
1019            args.push(Argument {
1020                parts,
1021                span: Span::new(arg_offset, arg_offset + current.len()),
1022            });
1023        }
1024        args
1025    }
1026
1027    fn parse_argument_parts(&mut self, content: &str, offset: usize) -> SmallVec<[AstNode; 4]> {
1028        if content.is_empty() {
1029            let mut parts = SmallVec::new();
1030            parts.push(AstNode::Text {
1031                content: String::new(),
1032                span: Span::new(offset, offset),
1033            });
1034            return parts;
1035        }
1036
1037        #[cfg(feature = "validation")]
1038        let inner_parser = if self.config.is_enabled() {
1039            if let Some(ref metadata) = self.metadata {
1040                Parser::with_validation(content, self.config.clone(), metadata.clone())
1041            } else {
1042                Parser::with_config(content, self.config.clone())
1043            }
1044        } else {
1045            Parser::new(content)
1046        };
1047
1048        #[cfg(not(feature = "validation"))]
1049        let inner_parser = Parser::new(content);
1050
1051        let (ast, errors) = inner_parser.parse_forge_script();
1052
1053        let nodes = if let AstNode::Program { mut body, .. } = ast {
1054            for node in &mut body {
1055                node.offset_spans(offset);
1056            }
1057            body
1058        } else {
1059            vec![ast]
1060        };
1061
1062        for mut error in errors {
1063            error.span.offset(offset);
1064            self.errors.push(error);
1065        }
1066
1067        let mut parts = SmallVec::new();
1068        for node in nodes {
1069            parts.push(node);
1070        }
1071        parts
1072    }
1073
1074    // ========================================================================
1075    // Matching Utilities
1076    // ========================================================================
1077
1078    fn find_matching_bracket(&self, open_pos: usize) -> Option<usize> {
1079        let mut depth = 1;
1080        let mut p = open_pos + 1;
1081        while p < self.bytes.len() {
1082            if self.bytes[p] == b'\\' {
1083                p += 2;
1084                continue;
1085            }
1086            if self.bytes[p] == b'[' && !is_escaped(self.source, p) {
1087                depth += 1;
1088            } else if self.bytes[p] == b']' && !is_escaped(self.source, p) {
1089                depth -= 1;
1090                if depth == 0 {
1091                    return Some(p);
1092                }
1093            }
1094            p += 1;
1095        }
1096        None
1097    }
1098
1099    fn find_matching_brace(&self, open_pos: usize) -> Option<usize> {
1100        let mut depth = 1;
1101        let mut p = open_pos + 1;
1102        while p < self.bytes.len() {
1103            match self.bytes[p] {
1104                b'{' => depth += 1,
1105                b'}' => {
1106                    depth -= 1;
1107                    if depth == 0 {
1108                        return Some(p);
1109                    }
1110                }
1111                _ => {}
1112            }
1113            p += 1;
1114        }
1115        None
1116    }
1117
1118    fn is_function_bracket(&self, content: &str, idx: usize) -> bool {
1119        if idx == 0 || content.as_bytes().get(idx) != Some(&b'[') {
1120            return false;
1121        }
1122        let bytes = content.as_bytes();
1123        let mut i = idx;
1124        while i > 0 && (bytes[i - 1].is_ascii_alphanumeric() || bytes[i - 1] == b'_') {
1125            i -= 1;
1126        }
1127        while i > 0 && matches!(bytes[i - 1], b'!' | b'#' | b']') {
1128            if bytes[i - 1] == b']' {
1129                let mut d = 1;
1130                while i > 1 && d > 0 {
1131                    i -= 1;
1132                    if bytes[i - 1] == b']' {
1133                        d += 1;
1134                    } else if bytes[i - 1] == b'[' {
1135                        d -= 1;
1136                    }
1137                }
1138                if i < 2 || bytes[i - 2] != b'@' {
1139                    return false;
1140                }
1141                i -= 2;
1142            } else {
1143                i -= 1;
1144            }
1145        }
1146        i > 0 && bytes[i - 1] == b'$' && (i == 1 || bytes[i - 2] != b'\\')
1147    }
1148
1149    fn find_escape_function_end(&self, content: &str, start: usize) -> Option<usize> {
1150        let bytes = content.as_bytes();
1151        let mut p = start + 1;
1152        while p < bytes.len() && matches!(bytes[p], b'!' | b'#') {
1153            p += 1;
1154        }
1155        let name_start = p;
1156        while p < bytes.len() && (bytes[p].is_ascii_alphanumeric() || bytes[p] == b'_') {
1157            p += 1;
1158        }
1159        if !self.is_escape_function(&content[name_start..p]) || bytes.get(p) != Some(&b'[') {
1160            return None;
1161        }
1162        let mut depth = 1;
1163        p += 1;
1164        while p < bytes.len() {
1165            if bytes[p] == b'\\' {
1166                p += 2;
1167                continue;
1168            }
1169            if bytes[p] == b'[' && !is_escaped(content, p) {
1170                depth += 1;
1171            } else if bytes[p] == b']' && !is_escaped(content, p) {
1172                depth -= 1;
1173                if depth == 0 {
1174                    return Some(p);
1175                }
1176            }
1177            p += 1;
1178        }
1179        None
1180    }
1181}
1182
1183// ============================================================================
1184// Public API
1185// ============================================================================
1186
1187/// Parse ForgeScript source code into an AST (no validation)
1188pub fn parse(source: &str) -> (AstNode, Vec<ParseError>) {
1189    Parser::new(source).parse()
1190}
1191
1192/// Parse with error handling
1193pub fn parse_with_errors(source: &str) -> Result<AstNode, Vec<ParseError>> {
1194    let (ast, errors) = parse(source);
1195    if errors.is_empty() {
1196        Ok(ast)
1197    } else {
1198        Err(errors)
1199    }
1200}
1201
1202/// Parse with validation configuration (requires "validation" feature)
1203#[cfg(feature = "validation")]
1204pub fn parse_with_config(source: &str, config: ValidationConfig) -> (AstNode, Vec<ParseError>) {
1205    Parser::with_config(source, config).parse()
1206}
1207
1208/// Parse with validation and metadata (requires "validation" feature)
1209#[cfg(feature = "validation")]
1210pub fn parse_with_validation(
1211    source: &str,
1212    config: ValidationConfig,
1213    metadata: Arc<MetadataManager>,
1214) -> (AstNode, Vec<ParseError>) {
1215    Parser::with_validation(source, config, metadata).parse()
1216}
1217
1218/// Parse with strict validation (requires "validation" feature)
1219#[cfg(feature = "validation")]
1220pub fn parse_strict(source: &str, metadata: Arc<MetadataManager>) -> (AstNode, Vec<ParseError>) {
1221    Parser::with_validation(source, ValidationConfig::strict(), metadata).parse()
1222}