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