1use smallvec::SmallVec;
7
8#[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#[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#[derive(Debug, Clone, Default)]
43pub struct ValidationConfig {
44 pub validate_arguments: bool,
46 pub validate_enums: bool,
48 pub validate_functions: bool,
50 pub validate_brackets: bool,
52}
53
54impl ValidationConfig {
55 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 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 #[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#[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 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 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 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 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 name_span: Span,
180 modifier_span: Option<Span>,
183 args_span: Option<Span>,
186 args: Option<Vec<Argument>>,
187 modifiers: Modifiers,
188 full_span: Span,
191 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#[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
295pub 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 #[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 #[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 if let Some(block_start) = self.find_code_block_start() {
362 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 let content_start = block_start + 7; self.pos = content_start;
373
374 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 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 self.pos = block_end + 1;
421 } else {
422 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 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 #[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 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 fn parse_forge_node(&mut self) -> Option<AstNode> {
535 if self.current_byte() == Some(b'\\') {
537 return self.parse_escape_sequence();
538 }
539
540 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(); if let Some(next) = self.current_byte() {
578 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 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 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(); self.advance(); 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(); let modifier_start = self.pos;
637 let modifiers = self.parse_modifiers();
638 let modifier_end = self.pos;
639
640 let modifier_span = if modifier_end > modifier_start {
642 Some(Span::new(modifier_start, modifier_end))
643 } else {
644 None
645 };
646
647 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 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 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 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 #[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 #[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 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 }
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 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 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(); let bracket_start = self.pos;
897 self.advance(); 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(¤t, 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(¤t, 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 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
1219pub fn parse(source: &str) -> (AstNode, Vec<ParseError>) {
1225 Parser::new(source).parse()
1226}
1227
1228pub 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#[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#[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#[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#[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}