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