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#[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#[derive(Debug, Clone, Default)]
69pub struct ValidationConfig {
70 pub validate_arguments: bool,
72 pub validate_enums: bool,
74 pub validate_functions: bool,
76 pub validate_brackets: bool,
78}
79
80impl ValidationConfig {
81 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 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 #[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#[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 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 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 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 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 name_span: Span,
206 modifier_span: Option<Span>,
209 args_span: Option<Span>,
212 args: Option<Vec<Argument>>,
213 modifiers: Modifiers,
214 full_span: Span,
217 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#[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#[cfg(feature = "validation")]
327const ENUM_ACCEPTS: &[(&str, usize)] = &[("$color", 0), ("$modifyChannelPerms", 2)];
328
329pub 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 #[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 #[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 if let Some(block_start) = self.find_code_block_start() {
396 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 let content_start = block_start + 7; self.pos = content_start;
407
408 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 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 self.pos = block_end + 1;
455 } else {
456 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 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 #[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 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 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 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 fn parse_forge_node(&mut self) -> Option<AstNode> {
576 if self.current_byte() == Some(b'\\') {
579 return self.parse_escape_sequence();
580 }
581
582 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 if self.current_byte() == Some(b'\\') {
598 break;
599 }
600 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 fn parse_escape_sequence(&mut self) -> Option<AstNode> {
628 let start = self.pos;
629 self.advance(); match self.current_byte() {
632 Some(b'`') => {
634 self.advance();
635 Some(AstNode::Text {
636 content: "`".to_string(),
637 span: Span::new(start, self.pos),
638 })
639 }
640
641 Some(b'\\') => {
643 match self.peek_byte(1) {
644 Some(b'$') | Some(b']') => {
645 let ch = self.peek_byte(1).unwrap() as char;
647 self.advance(); self.advance(); Some(AstNode::Text {
650 content: ch.to_string(),
651 span: Span::new(start, self.pos),
652 })
653 }
654 _ => {
655 self.advance(); Some(AstNode::Text {
658 content: "\\".to_string(),
659 span: Span::new(start, self.pos),
660 })
661 }
662 }
663 }
664
665 _ => 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(); self.advance(); 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(); let modifier_start = self.pos;
708 let modifiers = self.parse_modifiers();
709 let modifier_end = self.pos;
710
711 let modifier_span = if modifier_end > modifier_start {
713 Some(Span::new(modifier_start, modifier_end))
714 } else {
715 None
716 };
717
718 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 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 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 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 #[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 #[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 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 }
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 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 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(); let bracket_start = self.pos;
1001 self.advance(); 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 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 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 b'[' if self.is_function_bracket(content, i) => {
1143 depth += 1;
1144 current.push('[');
1145 arg_end = i + 1;
1146 }
1147 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(¤t, 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(¤t, 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 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 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
1362pub fn parse(source: &str) -> (AstNode, Vec<ParseError>) {
1368 Parser::new(source).parse()
1369}
1370
1371pub 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#[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#[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#[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#[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}