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 name_span,
702 );
703 } else if self.config.validate_functions {
704 self.errors.push(ParseError::new(
705 format!("Unknown function: {}", full_name),
706 name_span,
707 ErrorKind::UnknownFunction,
708 ));
709 }
710 } else if self.config.validate_functions {
711 self.errors.push(ParseError::new(
712 format!(
713 "Cannot validate function {}: no metadata available",
714 full_name
715 ),
716 name_span,
717 ErrorKind::UnknownFunction,
718 ));
719 }
720 }
721
722 AstNode::FunctionCall {
723 name,
724 name_span,
725 modifier_span,
726 args_span,
727 args,
728 modifiers,
729 full_span,
730 span,
731 }
732 }
733
734 #[cfg(feature = "validation")]
739 fn validate_function_call(
740 &mut self,
741 name: &str,
742 func: &Function,
743 args: Option<&Vec<Argument>>,
744 has_brackets: bool,
745 name_span: Span,
746 ) {
747 if self.config.validate_brackets {
749 match func.brackets {
750 Some(true) => {
751 if !has_brackets {
752 self.errors.push(ParseError::new(
753 format!("{} requires brackets", name),
754 name_span,
755 ErrorKind::BracketUsage,
756 ));
757 }
758 }
759 Some(false) => {
760 }
762 None => {
763 if has_brackets {
764 self.errors.push(ParseError::new(
765 format!("{} does not accept brackets", name),
766 name_span,
767 ErrorKind::BracketUsage,
768 ));
769 }
770 }
771 }
772 }
773
774 if (self.config.validate_arguments || self.config.validate_enums) && has_brackets {
776 if let (Some(args), Some(func_args)) = (args, &func.args) {
777 self.validate_arguments(name, args, func_args, name_span);
778 }
779 }
780 }
781
782 #[cfg(feature = "validation")]
783 fn validate_arguments(
784 &mut self,
785 func_name: &str,
786 provided_args: &[Argument],
787 func_args: &[Arg],
788 name_span: Span,
789 ) {
790 let provided_count = provided_args.len();
791
792 let has_rest = func_args.iter().any(|a| a.rest);
793 let required_count = func_args
794 .iter()
795 .filter(|a| a.required.unwrap_or(false) && !a.rest)
796 .count();
797 let max_count = if has_rest {
798 usize::MAX
799 } else {
800 func_args.len()
801 };
802
803 if self.config.validate_arguments {
804 if provided_count < required_count {
805 self.errors.push(ParseError::new(
806 format!(
807 "{} requires at least {} argument(s), got {}",
808 func_name, required_count, provided_count
809 ),
810 name_span,
811 ErrorKind::ArgumentCount,
812 ));
813 } else if !has_rest && provided_count > max_count {
814 self.errors.push(ParseError::new(
815 format!(
816 "{} accepts at most {} argument(s), got {}",
817 func_name, max_count, provided_count
818 ),
819 name_span,
820 ErrorKind::ArgumentCount,
821 ));
822 }
823 }
824
825 if self.config.validate_enums {
826 for (i, provided_arg) in provided_args.iter().enumerate() {
827 let func_arg = if i < func_args.len() {
828 &func_args[i]
829 } else if has_rest {
830 func_args.last().unwrap()
831 } else {
832 continue;
833 };
834
835 self.validate_enum_value(func_name, provided_arg, func_arg, name_span);
836 }
837 }
838 }
839
840 #[cfg(feature = "validation")]
841 fn validate_enum_value(
842 &mut self,
843 func_name: &str,
844 arg: &Argument,
845 func_arg: &Arg,
846 name_span: Span,
847 ) {
848 if !func_arg.required.unwrap_or(false) && arg.is_empty() {
849 return;
850 }
851
852 let enum_values = if let Some(enum_name) = &func_arg.enum_name {
853 if let Some(ref metadata) = self.metadata {
854 metadata.get_enum(enum_name)
855 } else {
856 None
857 }
858 } else {
859 func_arg.arg_enum.clone()
860 };
861
862 if let Some(valid_values) = enum_values {
863 if let Some(text_value) = arg.as_text() {
864 let trimmed = text_value.trim();
865 if !trimmed.is_empty() && !valid_values.contains(&trimmed.to_string()) {
866 self.errors.push(ParseError::new(
867 format!(
868 "Invalid value for {} argument {}: expected one of {:?}",
869 func_name, func_arg.name, valid_values
870 ),
871 name_span,
872 ErrorKind::EnumValue,
873 ));
874 }
875 }
876 }
877 }
878
879 fn parse_modifiers(&mut self) -> Modifiers {
884 let mut modifiers = Modifiers::default();
885 let start = self.pos;
886
887 loop {
888 match self.current_byte() {
889 Some(b'!') => {
890 modifiers.silent = true;
891 self.advance();
892 }
893 Some(b'#') => {
894 modifiers.negated = true;
895 self.advance();
896 }
897 Some(b'@') if self.peek_byte(1) == Some(b'[') => {
898 self.advance(); let bracket_start = self.pos;
900 self.advance(); if let Some(end) = self.find_matching_bracket(bracket_start) {
902 modifiers.count = Some(self.slice(bracket_start + 1, end).to_string());
903 self.pos = end + 1;
904 } else if self.config.validate_brackets {
905 self.errors.push(ParseError::syntax(
906 "Unclosed modifier bracket",
907 Span::new(bracket_start, bracket_start + 1),
908 ));
909 break;
910 } else {
911 break;
912 }
913 }
914 _ => break,
915 }
916 }
917
918 let end = self.pos;
919 if end > start {
920 modifiers.span = Some(Span::new(start, end));
921 }
922
923 modifiers
924 }
925
926 #[inline]
927 fn parse_identifier(&mut self) -> String {
928 let start = self.pos;
929 while let Some(b) = self.current_byte() {
930 if b.is_ascii_alphanumeric() || b == b'_' {
931 self.advance();
932 } else {
933 break;
934 }
935 }
936 self.slice(start, self.pos).to_string()
937 }
938
939 fn is_escape_function(&self, name: &str) -> bool {
940 matches!(name, "c" | "C" | "escape")
941 }
942
943 fn parse_escape_function(&mut self, start: usize, name: String, name_span: Span) -> AstNode {
944 if self.current_byte() != Some(b'[') {
945 if self.config.validate_brackets {
946 self.errors.push(ParseError::new(
947 format!("${} requires brackets", name),
948 name_span,
949 ErrorKind::BracketUsage,
950 ));
951 }
952 return AstNode::Text {
953 content: self.slice(start, self.pos).to_string(),
954 span: Span::new(start, self.pos),
955 };
956 }
957
958 let bracket_start = self.pos;
959 self.advance();
960 if let Some(end) = self.find_matching_bracket(bracket_start) {
961 let content = self.slice(bracket_start + 1, end).to_string();
962 self.pos = end + 1;
963 AstNode::Escaped {
964 content,
965 span: Span::new(start, self.pos),
966 }
967 } else {
968 if self.config.validate_brackets {
969 self.errors.push(ParseError::syntax(
970 format!("Unclosed '[' for ${}", name),
971 name_span,
972 ));
973 }
974 self.pos = self.source.len();
975 AstNode::Escaped {
976 content: String::new(),
977 span: Span::new(start, self.pos),
978 }
979 }
980 }
981
982 fn parse_function_arguments(&mut self) -> Option<Vec<Argument>> {
983 let bracket_start = self.pos;
984 self.advance();
985 if let Some(end) = self.find_matching_bracket(bracket_start) {
986 let args_content = self.slice(bracket_start + 1, end);
987 let parsed_args = self.parse_arguments(args_content, bracket_start + 1);
988 self.pos = end + 1;
989 Some(parsed_args)
990 } else {
991 if self.config.validate_brackets {
992 self.errors.push(ParseError::syntax(
993 "Unclosed function arguments",
994 Span::new(bracket_start, bracket_start + 1),
995 ));
996 }
997 None
998 }
999 }
1000
1001 fn parse_arguments(&mut self, content: &str, base_offset: usize) -> Vec<Argument> {
1002 let mut args = Vec::new();
1003 let mut current = String::new();
1004 let mut depth = 0;
1005 let bytes = content.as_bytes();
1006 let mut i = 0;
1007 let mut arg_start = 0;
1008
1009 while i < bytes.len() {
1010 if bytes[i] == b'$' && depth == 0 {
1011 if let Some(esc_end) = self.find_escape_function_end(content, i) {
1012 current.push_str(&content[i..=esc_end]);
1013 i = esc_end + 1;
1014 continue;
1015 }
1016 }
1017
1018 if bytes[i] == b'\\' {
1019 if let Some(next) = bytes.get(i + 1) {
1020 if matches!(*next, b'`' | b'$' | b'[' | b']' | b';' | b'\\') {
1021 current.push_str(&content[i..i + 2]);
1022 i += 2;
1023 continue;
1024 }
1025 }
1026 current.push('\\');
1027 i += 1;
1028 continue;
1029 }
1030
1031 match bytes[i] {
1032 b'[' if !is_escaped(content, i) && self.is_function_bracket(content, i) => {
1033 depth += 1;
1034 current.push('[');
1035 }
1036 b']' if depth > 0 && !is_escaped(content, i) => {
1037 depth -= 1;
1038 current.push(']');
1039 }
1040 b';' if depth == 0 => {
1041 let arg_offset = base_offset + arg_start;
1042 let parts = self.parse_argument_parts(¤t, arg_offset);
1043 args.push(Argument {
1044 parts,
1045 span: Span::new(arg_offset, arg_offset + current.len()),
1046 });
1047 current.clear();
1048 arg_start = i + 1;
1049 }
1050 _ => current.push(bytes[i] as char),
1051 }
1052 i += 1;
1053 }
1054
1055 if !current.is_empty() || !args.is_empty() {
1056 let arg_offset = base_offset + arg_start;
1057 let parts = self.parse_argument_parts(¤t, arg_offset);
1058 args.push(Argument {
1059 parts,
1060 span: Span::new(arg_offset, arg_offset + current.len()),
1061 });
1062 }
1063 args
1064 }
1065
1066 fn parse_argument_parts(&mut self, content: &str, offset: usize) -> SmallVec<[AstNode; 4]> {
1067 if content.is_empty() {
1068 let mut parts = SmallVec::new();
1069 parts.push(AstNode::Text {
1070 content: String::new(),
1071 span: Span::new(offset, offset),
1072 });
1073 return parts;
1074 }
1075
1076 #[cfg(feature = "validation")]
1077 let inner_parser = if self.config.is_enabled() {
1078 if let Some(ref metadata) = self.metadata {
1079 Parser::with_validation(content, self.config.clone(), metadata.clone())
1080 } else {
1081 Parser::with_config(content, self.config.clone())
1082 }
1083 } else {
1084 Parser::new(content)
1085 };
1086
1087 #[cfg(not(feature = "validation"))]
1088 let inner_parser = Parser::new(content);
1089
1090 let (ast, errors) = inner_parser.parse_forge_script();
1091
1092 let nodes = if let AstNode::Program { mut body, .. } = ast {
1093 for node in &mut body {
1094 node.offset_spans(offset);
1095 }
1096 body
1097 } else {
1098 vec![ast]
1099 };
1100
1101 for mut error in errors {
1102 error.span.offset(offset);
1103 self.errors.push(error);
1104 }
1105
1106 let mut parts = SmallVec::new();
1107 for node in nodes {
1108 parts.push(node);
1109 }
1110 parts
1111 }
1112
1113 fn find_matching_bracket(&self, open_pos: usize) -> Option<usize> {
1118 let mut depth = 1;
1119 let mut p = open_pos + 1;
1120 while p < self.bytes.len() {
1121 if self.bytes[p] == b'\\' {
1122 p += 2;
1123 continue;
1124 }
1125 if self.bytes[p] == b'[' && !is_escaped(self.source, p) {
1126 depth += 1;
1127 } else if self.bytes[p] == b']' && !is_escaped(self.source, p) {
1128 depth -= 1;
1129 if depth == 0 {
1130 return Some(p);
1131 }
1132 }
1133 p += 1;
1134 }
1135 None
1136 }
1137
1138 fn find_matching_brace(&self, open_pos: usize) -> Option<usize> {
1139 let mut depth = 1;
1140 let mut p = open_pos + 1;
1141 while p < self.bytes.len() {
1142 match self.bytes[p] {
1143 b'{' => depth += 1,
1144 b'}' => {
1145 depth -= 1;
1146 if depth == 0 {
1147 return Some(p);
1148 }
1149 }
1150 _ => {}
1151 }
1152 p += 1;
1153 }
1154 None
1155 }
1156
1157 fn is_function_bracket(&self, content: &str, idx: usize) -> bool {
1158 if idx == 0 || content.as_bytes().get(idx) != Some(&b'[') {
1159 return false;
1160 }
1161 let bytes = content.as_bytes();
1162 let mut i = idx;
1163 while i > 0 && (bytes[i - 1].is_ascii_alphanumeric() || bytes[i - 1] == b'_') {
1164 i -= 1;
1165 }
1166 while i > 0 && matches!(bytes[i - 1], b'!' | b'#' | b']') {
1167 if bytes[i - 1] == b']' {
1168 let mut d = 1;
1169 while i > 1 && d > 0 {
1170 i -= 1;
1171 if bytes[i - 1] == b']' {
1172 d += 1;
1173 } else if bytes[i - 1] == b'[' {
1174 d -= 1;
1175 }
1176 }
1177 if i < 2 || bytes[i - 2] != b'@' {
1178 return false;
1179 }
1180 i -= 2;
1181 } else {
1182 i -= 1;
1183 }
1184 }
1185 i > 0 && bytes[i - 1] == b'$' && (i == 1 || bytes[i - 2] != b'\\')
1186 }
1187
1188 fn find_escape_function_end(&self, content: &str, start: usize) -> Option<usize> {
1189 let bytes = content.as_bytes();
1190 let mut p = start + 1;
1191 while p < bytes.len() && matches!(bytes[p], b'!' | b'#') {
1192 p += 1;
1193 }
1194 let name_start = p;
1195 while p < bytes.len() && (bytes[p].is_ascii_alphanumeric() || bytes[p] == b'_') {
1196 p += 1;
1197 }
1198 if !self.is_escape_function(&content[name_start..p]) || bytes.get(p) != Some(&b'[') {
1199 return None;
1200 }
1201 let mut depth = 1;
1202 p += 1;
1203 while p < bytes.len() {
1204 if bytes[p] == b'\\' {
1205 p += 2;
1206 continue;
1207 }
1208 if bytes[p] == b'[' && !is_escaped(content, p) {
1209 depth += 1;
1210 } else if bytes[p] == b']' && !is_escaped(content, p) {
1211 depth -= 1;
1212 if depth == 0 {
1213 return Some(p);
1214 }
1215 }
1216 p += 1;
1217 }
1218 None
1219 }
1220}
1221
1222pub fn parse(source: &str) -> (AstNode, Vec<ParseError>) {
1228 Parser::new(source).parse()
1229}
1230
1231pub fn parse_with_errors(source: &str) -> Result<AstNode, Vec<ParseError>> {
1233 let (ast, errors) = parse(source);
1234 if errors.is_empty() {
1235 Ok(ast)
1236 } else {
1237 Err(errors)
1238 }
1239}
1240
1241#[cfg(feature = "validation")]
1243pub fn parse_with_config(source: &str, config: ValidationConfig) -> (AstNode, Vec<ParseError>) {
1244 Parser::with_config(source, config).parse()
1245}
1246
1247#[cfg(feature = "validation")]
1249pub fn parse_with_validation(
1250 source: &str,
1251 config: ValidationConfig,
1252 metadata: Arc<MetadataManager>,
1253) -> (AstNode, Vec<ParseError>) {
1254 Parser::with_validation(source, config, metadata).parse()
1255}
1256
1257#[cfg(feature = "validation")]
1259pub fn parse_forge_script_with_validation(
1260 source: &str,
1261 config: ValidationConfig,
1262 metadata: Arc<MetadataManager>,
1263) -> (AstNode, Vec<ParseError>) {
1264 Parser::with_validation(source, config, metadata).parse_forge_script()
1265}
1266
1267#[cfg(feature = "validation")]
1269pub fn parse_strict(source: &str, metadata: Arc<MetadataManager>) -> (AstNode, Vec<ParseError>) {
1270 Parser::with_validation(source, ValidationConfig::strict(), metadata).parse()
1271}