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                        span,
702                    );
703                } else if self.config.validate_functions {
704                    self.errors.push(ParseError::new(
705                        format!("Unknown function: {}", full_name),
706                        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                    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        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                            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                            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, 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        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                    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                    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);
836            }
837        }
838    }
839
840    #[cfg(feature = "validation")]
841    fn validate_enum_value(&mut self, func_name: &str, arg: &Argument, func_arg: &Arg) {
842        if !func_arg.required.unwrap_or(false) && arg.is_empty() {
843            return;
844        }
845
846        let enum_values = if let Some(enum_name) = &func_arg.enum_name {
847            if let Some(ref metadata) = self.metadata {
848                metadata.get_enum(enum_name)
849            } else {
850                None
851            }
852        } else {
853            func_arg.arg_enum.clone()
854        };
855
856        if let Some(valid_values) = enum_values {
857            if let Some(text_value) = arg.as_text() {
858                let trimmed = text_value.trim();
859                if !trimmed.is_empty() && !valid_values.contains(&trimmed.to_string()) {
860                    self.errors.push(ParseError::new(
861                        format!(
862                            "Invalid value '{}' for {} in {}. Expected one of: {}",
863                            trimmed,
864                            func_arg.name,
865                            func_name,
866                            valid_values.join(", ")
867                        ),
868                        arg.span,
869                        ErrorKind::EnumValue,
870                    ));
871                }
872            }
873        }
874    }
875
876    // ========================================================================
877    // Parsing Helpers
878    // ========================================================================
879
880    fn parse_modifiers(&mut self) -> Modifiers {
881        let mut modifiers = Modifiers::default();
882        let start = self.pos;
883
884        loop {
885            match self.current_byte() {
886                Some(b'!') => {
887                    modifiers.silent = true;
888                    self.advance();
889                }
890                Some(b'#') => {
891                    modifiers.negated = true;
892                    self.advance();
893                }
894                Some(b'@') if self.peek_byte(1) == Some(b'[') => {
895                    self.advance(); // '@'
896                    let bracket_start = self.pos;
897                    self.advance(); // '['
898                    if let Some(end) = self.find_matching_bracket(bracket_start) {
899                        modifiers.count = Some(self.slice(bracket_start + 1, end).to_string());
900                        self.pos = end + 1;
901                    } else if self.config.validate_brackets {
902                        self.errors.push(ParseError::syntax(
903                            "Unclosed modifier bracket",
904                            Span::new(bracket_start, bracket_start + 1),
905                        ));
906                        break;
907                    } else {
908                        break;
909                    }
910                }
911                _ => break,
912            }
913        }
914
915        let end = self.pos;
916        if end > start {
917            modifiers.span = Some(Span::new(start, end));
918        }
919
920        modifiers
921    }
922
923    #[inline]
924    fn parse_identifier(&mut self) -> String {
925        let start = self.pos;
926        while let Some(b) = self.current_byte() {
927            if b.is_ascii_alphanumeric() || b == b'_' {
928                self.advance();
929            } else {
930                break;
931            }
932        }
933        self.slice(start, self.pos).to_string()
934    }
935
936    fn is_escape_function(&self, name: &str) -> bool {
937        matches!(name, "c" | "C" | "escape")
938    }
939
940    fn parse_escape_function(&mut self, start: usize, name: String, _name_span: Span) -> AstNode {
941        if self.current_byte() != Some(b'[') {
942            if self.config.validate_brackets {
943                self.errors.push(ParseError::new(
944                    format!("${} requires brackets", name),
945                    Span::new(start, self.pos),
946                    ErrorKind::BracketUsage,
947                ));
948            }
949            return AstNode::Text {
950                content: self.slice(start, self.pos).to_string(),
951                span: Span::new(start, self.pos),
952            };
953        }
954
955        let bracket_start = self.pos;
956        self.advance();
957        if let Some(end) = self.find_matching_bracket(bracket_start) {
958            let content = self.slice(bracket_start + 1, end).to_string();
959            self.pos = end + 1;
960            AstNode::Escaped {
961                content,
962                span: Span::new(start, self.pos),
963            }
964        } else {
965            if self.config.validate_brackets {
966                self.errors.push(ParseError::syntax(
967                    format!("Unclosed '[' for ${}", name),
968                    Span::new(start, self.source.len()),
969                ));
970            }
971            self.pos = self.source.len();
972            AstNode::Escaped {
973                content: String::new(),
974                span: Span::new(start, self.pos),
975            }
976        }
977    }
978
979    fn parse_function_arguments(&mut self) -> Option<Vec<Argument>> {
980        let bracket_start = self.pos;
981        self.advance();
982        if let Some(end) = self.find_matching_bracket(bracket_start) {
983            let args_content = self.slice(bracket_start + 1, end);
984            let parsed_args = self.parse_arguments(args_content, bracket_start + 1);
985            self.pos = end + 1;
986            Some(parsed_args)
987        } else {
988            if self.config.validate_brackets {
989                self.errors.push(ParseError::syntax(
990                    "Unclosed function arguments",
991                    Span::new(bracket_start, bracket_start + 1),
992                ));
993            }
994            None
995        }
996    }
997
998    fn parse_arguments(&mut self, content: &str, base_offset: usize) -> Vec<Argument> {
999        let mut args = Vec::new();
1000        let mut current = String::new();
1001        let mut depth = 0;
1002        let bytes = content.as_bytes();
1003        let mut i = 0;
1004        let mut arg_start = 0;
1005
1006        while i < bytes.len() {
1007            if bytes[i] == b'$' && depth == 0 {
1008                if let Some(esc_end) = self.find_escape_function_end(content, i) {
1009                    current.push_str(&content[i..=esc_end]);
1010                    i = esc_end + 1;
1011                    continue;
1012                }
1013            }
1014
1015            if bytes[i] == b'\\' {
1016                if let Some(next) = bytes.get(i + 1) {
1017                    if matches!(*next, b'`' | b'$' | b'[' | b']' | b';' | b'\\') {
1018                        current.push_str(&content[i..i + 2]);
1019                        i += 2;
1020                        continue;
1021                    }
1022                }
1023                current.push('\\');
1024                i += 1;
1025                continue;
1026            }
1027
1028            match bytes[i] {
1029                b'[' if !is_escaped(content, i) && self.is_function_bracket(content, i) => {
1030                    depth += 1;
1031                    current.push('[');
1032                }
1033                b']' if depth > 0 && !is_escaped(content, i) => {
1034                    depth -= 1;
1035                    current.push(']');
1036                }
1037                b';' if depth == 0 => {
1038                    let arg_offset = base_offset + arg_start;
1039                    let parts = self.parse_argument_parts(&current, arg_offset);
1040                    args.push(Argument {
1041                        parts,
1042                        span: Span::new(arg_offset, arg_offset + current.len()),
1043                    });
1044                    current.clear();
1045                    arg_start = i + 1;
1046                }
1047                _ => current.push(bytes[i] as char),
1048            }
1049            i += 1;
1050        }
1051
1052        if !current.is_empty() || !args.is_empty() {
1053            let arg_offset = base_offset + arg_start;
1054            let parts = self.parse_argument_parts(&current, arg_offset);
1055            args.push(Argument {
1056                parts,
1057                span: Span::new(arg_offset, arg_offset + current.len()),
1058            });
1059        }
1060        args
1061    }
1062
1063    fn parse_argument_parts(&mut self, content: &str, offset: usize) -> SmallVec<[AstNode; 4]> {
1064        if content.is_empty() {
1065            let mut parts = SmallVec::new();
1066            parts.push(AstNode::Text {
1067                content: String::new(),
1068                span: Span::new(offset, offset),
1069            });
1070            return parts;
1071        }
1072
1073        #[cfg(feature = "validation")]
1074        let inner_parser = if self.config.is_enabled() {
1075            if let Some(ref metadata) = self.metadata {
1076                Parser::with_validation(content, self.config.clone(), metadata.clone())
1077            } else {
1078                Parser::with_config(content, self.config.clone())
1079            }
1080        } else {
1081            Parser::new(content)
1082        };
1083
1084        #[cfg(not(feature = "validation"))]
1085        let inner_parser = Parser::new(content);
1086
1087        let (ast, errors) = inner_parser.parse_forge_script();
1088
1089        let nodes = if let AstNode::Program { mut body, .. } = ast {
1090            for node in &mut body {
1091                node.offset_spans(offset);
1092            }
1093            body
1094        } else {
1095            vec![ast]
1096        };
1097
1098        for mut error in errors {
1099            error.span.offset(offset);
1100            self.errors.push(error);
1101        }
1102
1103        let mut parts = SmallVec::new();
1104        for node in nodes {
1105            parts.push(node);
1106        }
1107        parts
1108    }
1109
1110    // ========================================================================
1111    // Matching Utilities
1112    // ========================================================================
1113
1114    fn find_matching_bracket(&self, open_pos: usize) -> Option<usize> {
1115        let mut depth = 1;
1116        let mut p = open_pos + 1;
1117        while p < self.bytes.len() {
1118            if self.bytes[p] == b'\\' {
1119                p += 2;
1120                continue;
1121            }
1122            if self.bytes[p] == b'[' && !is_escaped(self.source, p) {
1123                depth += 1;
1124            } else if self.bytes[p] == b']' && !is_escaped(self.source, p) {
1125                depth -= 1;
1126                if depth == 0 {
1127                    return Some(p);
1128                }
1129            }
1130            p += 1;
1131        }
1132        None
1133    }
1134
1135    fn find_matching_brace(&self, open_pos: usize) -> Option<usize> {
1136        let mut depth = 1;
1137        let mut p = open_pos + 1;
1138        while p < self.bytes.len() {
1139            match self.bytes[p] {
1140                b'{' => depth += 1,
1141                b'}' => {
1142                    depth -= 1;
1143                    if depth == 0 {
1144                        return Some(p);
1145                    }
1146                }
1147                _ => {}
1148            }
1149            p += 1;
1150        }
1151        None
1152    }
1153
1154    fn is_function_bracket(&self, content: &str, idx: usize) -> bool {
1155        if idx == 0 || content.as_bytes().get(idx) != Some(&b'[') {
1156            return false;
1157        }
1158        let bytes = content.as_bytes();
1159        let mut i = idx;
1160        while i > 0 && (bytes[i - 1].is_ascii_alphanumeric() || bytes[i - 1] == b'_') {
1161            i -= 1;
1162        }
1163        while i > 0 && matches!(bytes[i - 1], b'!' | b'#' | b']') {
1164            if bytes[i - 1] == b']' {
1165                let mut d = 1;
1166                while i > 1 && d > 0 {
1167                    i -= 1;
1168                    if bytes[i - 1] == b']' {
1169                        d += 1;
1170                    } else if bytes[i - 1] == b'[' {
1171                        d -= 1;
1172                    }
1173                }
1174                if i < 2 || bytes[i - 2] != b'@' {
1175                    return false;
1176                }
1177                i -= 2;
1178            } else {
1179                i -= 1;
1180            }
1181        }
1182        i > 0 && bytes[i - 1] == b'$' && (i == 1 || bytes[i - 2] != b'\\')
1183    }
1184
1185    fn find_escape_function_end(&self, content: &str, start: usize) -> Option<usize> {
1186        let bytes = content.as_bytes();
1187        let mut p = start + 1;
1188        while p < bytes.len() && matches!(bytes[p], b'!' | b'#') {
1189            p += 1;
1190        }
1191        let name_start = p;
1192        while p < bytes.len() && (bytes[p].is_ascii_alphanumeric() || bytes[p] == b'_') {
1193            p += 1;
1194        }
1195        if !self.is_escape_function(&content[name_start..p]) || bytes.get(p) != Some(&b'[') {
1196            return None;
1197        }
1198        let mut depth = 1;
1199        p += 1;
1200        while p < bytes.len() {
1201            if bytes[p] == b'\\' {
1202                p += 2;
1203                continue;
1204            }
1205            if bytes[p] == b'[' && !is_escaped(content, p) {
1206                depth += 1;
1207            } else if bytes[p] == b']' && !is_escaped(content, p) {
1208                depth -= 1;
1209                if depth == 0 {
1210                    return Some(p);
1211                }
1212            }
1213            p += 1;
1214        }
1215        None
1216    }
1217}
1218
1219// ============================================================================
1220// Public API
1221// ============================================================================
1222
1223/// Parse ForgeScript source code into an AST (no validation)
1224pub fn parse(source: &str) -> (AstNode, Vec<ParseError>) {
1225    Parser::new(source).parse()
1226}
1227
1228/// Parse with error handling
1229pub fn parse_with_errors(source: &str) -> Result<AstNode, Vec<ParseError>> {
1230    let (ast, errors) = parse(source);
1231    if errors.is_empty() {
1232        Ok(ast)
1233    } else {
1234        Err(errors)
1235    }
1236}
1237
1238/// Parse with validation configuration (requires "validation" feature)
1239#[cfg(feature = "validation")]
1240pub fn parse_with_config(source: &str, config: ValidationConfig) -> (AstNode, Vec<ParseError>) {
1241    Parser::with_config(source, config).parse()
1242}
1243
1244/// Parse with validation and metadata (requires "validation" feature)
1245#[cfg(feature = "validation")]
1246pub fn parse_with_validation(
1247    source: &str,
1248    config: ValidationConfig,
1249    metadata: Arc<MetadataManager>,
1250) -> (AstNode, Vec<ParseError>) {
1251    Parser::with_validation(source, config, metadata).parse()
1252}
1253
1254/// Parse ForgeScript directly (no wrapper) with validation
1255#[cfg(feature = "validation")]
1256pub fn parse_forge_script_with_validation(
1257    source: &str,
1258    config: ValidationConfig,
1259    metadata: Arc<MetadataManager>,
1260) -> (AstNode, Vec<ParseError>) {
1261    Parser::with_validation(source, config, metadata).parse_forge_script()
1262}
1263
1264/// Parse with strict validation (requires "validation" feature)
1265#[cfg(feature = "validation")]
1266pub fn parse_strict(source: &str, metadata: Arc<MetadataManager>) -> (AstNode, Vec<ParseError>) {
1267    Parser::with_validation(source, ValidationConfig::strict(), metadata).parse()
1268}