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