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]
49fn escape_sequence_len(bytes: &[u8], pos: usize) -> usize {
50 if bytes.get(pos) != Some(&b'\\') {
51 return 0;
52 }
53 match bytes.get(pos + 1).copied() {
54 Some(b'\\') => match bytes.get(pos + 2).copied() {
55 Some(b'$') | Some(b']') | Some(b';') => 3,
56 _ => 2,
57 },
58 Some(b'`') => 2,
59 Some(_) => 1,
60 None => 1,
61 }
62}
63
64#[derive(Debug, Clone, Default)]
70pub struct ValidationConfig {
71 pub validate_arguments: bool,
73 pub validate_enums: bool,
75 pub validate_functions: bool,
77 pub validate_brackets: bool,
79}
80
81impl ValidationConfig {
82 pub fn strict() -> Self {
84 Self {
85 validate_arguments: true,
86 validate_enums: true,
87 validate_functions: true,
88 validate_brackets: true,
89 }
90 }
91
92 pub fn syntax_only() -> Self {
94 Self {
95 validate_arguments: false,
96 validate_enums: false,
97 validate_functions: false,
98 validate_brackets: true,
99 }
100 }
101
102 #[inline]
104 pub fn is_enabled(&self) -> bool {
105 self.validate_arguments
106 || self.validate_enums
107 || self.validate_functions
108 || self.validate_brackets
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub struct Span {
118 pub start: usize,
119 pub end: usize,
120}
121
122impl Span {
123 #[inline(always)]
124 pub const fn new(start: usize, end: usize) -> Self {
125 Self { start, end }
126 }
127
128 #[inline(always)]
129 pub fn offset(&mut self, offset: usize) {
130 self.start += offset;
131 self.end += offset;
132 }
133
134 #[inline(always)]
135 pub fn len(&self) -> usize {
136 self.end - self.start
137 }
138
139 #[inline(always)]
140 pub fn is_empty(&self) -> bool {
141 self.start >= self.end
142 }
143}
144
145#[derive(Debug, Clone, Default)]
146pub struct Modifiers {
147 pub silent: bool,
148 pub negated: bool,
149 pub count: Option<String>,
150 pub span: Option<Span>,
153}
154
155#[derive(Debug, Clone)]
156pub struct Argument {
157 pub parts: SmallVec<[AstNode; 4]>,
158 pub span: Span,
159}
160
161impl Argument {
162 pub fn is_empty(&self) -> bool {
164 self.parts.iter().all(|part| match part {
165 AstNode::Text { content, .. } => content.trim().is_empty(),
166 _ => false,
167 })
168 }
169
170 pub fn as_text(&self) -> Option<String> {
172 if self.parts.len() == 1 {
173 if let AstNode::Text { content, .. } = &self.parts[0] {
174 return Some(content.clone());
175 }
176 }
177
178 if self.parts.iter().all(|p| matches!(p, AstNode::Text { .. })) {
180 let mut result = String::new();
181 for part in &self.parts {
182 if let AstNode::Text { content, .. } = part {
183 result.push_str(&content);
184 }
185 }
186 return Some(result);
187 }
188
189 None
190 }
191}
192
193#[derive(Debug, Clone)]
194pub enum AstNode {
195 Program {
196 body: Vec<AstNode>,
197 span: Span,
198 },
199 Text {
200 content: String,
201 span: Span,
202 },
203 FunctionCall {
204 name: String,
205 name_span: Span,
207 modifier_span: Option<Span>,
210 args_span: Option<Span>,
213 args: Option<Vec<Argument>>,
214 modifiers: Modifiers,
215 full_span: Span,
218 span: Span,
220 },
221 JavaScript {
222 code: String,
223 span: Span,
224 },
225 Escaped {
226 content: String,
227 span: Span,
228 },
229}
230
231impl AstNode {
232 pub fn span(&self) -> Span {
233 match self {
234 AstNode::Program { span, .. }
235 | AstNode::Text { span, .. }
236 | AstNode::FunctionCall { span, .. }
237 | AstNode::JavaScript { span, .. }
238 | AstNode::Escaped { span, .. } => *span,
239 }
240 }
241
242 pub fn offset_spans(&mut self, offset: usize) {
243 match self {
244 AstNode::Program { body, span } => {
245 span.offset(offset);
246 for node in body {
247 node.offset_spans(offset);
248 }
249 }
250 AstNode::Text { span, .. }
251 | AstNode::JavaScript { span, .. }
252 | AstNode::Escaped { span, .. } => {
253 span.offset(offset);
254 }
255 AstNode::FunctionCall {
256 args,
257 span,
258 name_span,
259 modifier_span,
260 args_span,
261 full_span,
262 ..
263 } => {
264 span.offset(offset);
265 name_span.offset(offset);
266 full_span.offset(offset);
267 if let Some(ms) = modifier_span {
268 ms.offset(offset);
269 }
270 if let Some(as_) = args_span {
271 as_.offset(offset);
272 }
273 if let Some(args) = args {
274 for arg in args {
275 arg.span.offset(offset);
276 for part in &mut arg.parts {
277 part.offset_spans(offset);
278 }
279 }
280 }
281 }
282 }
283 }
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub enum ErrorKind {
292 Syntax,
293 ArgumentCount,
294 EnumValue,
295 UnknownFunction,
296 BracketUsage,
297}
298
299#[derive(Debug, Clone)]
300pub struct ParseError {
301 pub message: String,
302 pub span: Span,
303 pub kind: ErrorKind,
304}
305
306impl ParseError {
307 #[inline]
308 pub fn new(message: impl Into<String>, span: Span, kind: ErrorKind) -> Self {
309 Self {
310 message: message.into(),
311 span,
312 kind,
313 }
314 }
315
316 #[inline]
317 pub fn syntax(message: impl Into<String>, span: Span) -> Self {
318 Self::new(message, span, ErrorKind::Syntax)
319 }
320}
321
322#[cfg(feature = "validation")]
328const ENUM_ACCEPTS: &[(&str, usize)] = &[("$color", 0), ("$modifyChannelPerms", 2)];
329
330pub struct Parser<'src> {
335 source: &'src str,
336 bytes: &'src [u8],
337 pos: usize,
338 errors: Vec<ParseError>,
339 config: ValidationConfig,
340 #[cfg(feature = "validation")]
341 metadata: Option<Arc<MetadataManager>>,
342}
343
344impl<'src> Parser<'src> {
345 #[inline]
346 pub fn new(source: &'src str) -> Self {
347 Self {
348 source,
349 bytes: source.as_bytes(),
350 pos: 0,
351 errors: Vec::new(),
352 config: ValidationConfig::default(),
353 #[cfg(feature = "validation")]
354 metadata: None,
355 }
356 }
357
358 #[cfg(feature = "validation")]
360 #[inline]
361 pub fn with_config(source: &'src str, config: ValidationConfig) -> Self {
362 Self {
363 source,
364 bytes: source.as_bytes(),
365 pos: 0,
366 errors: Vec::new(),
367 config,
368 metadata: None,
369 }
370 }
371
372 #[cfg(feature = "validation")]
374 #[inline]
375 pub fn with_validation(
376 source: &'src str,
377 config: ValidationConfig,
378 metadata: Arc<MetadataManager>,
379 ) -> Self {
380 Self {
381 source,
382 bytes: source.as_bytes(),
383 pos: 0,
384 errors: Vec::new(),
385 config,
386 metadata: Some(metadata),
387 }
388 }
389
390 pub fn parse(mut self) -> (AstNode, Vec<ParseError>) {
391 let start = self.pos;
392 let mut body = Vec::new();
393
394 while !self.is_eof() {
395 if let Some(block_start) = self.find_code_block_start() {
397 if block_start > self.pos {
399 body.push(AstNode::Text {
400 content: self.slice(self.pos, block_start).to_string(),
401 span: Span::new(self.pos, block_start),
402 });
403 }
404
405 let content_start = block_start + 7; self.pos = content_start;
408
409 if let Some(block_end) = self.find_code_block_end() {
411 let content_len = block_end - content_start;
412
413 if content_len > 0 {
414 let inner_source = self.slice(content_start, block_end);
416
417 #[cfg(feature = "validation")]
418 let inner_parser = if self.config.is_enabled() {
419 if let Some(ref metadata) = self.metadata {
420 Parser::with_validation(
421 inner_source,
422 self.config.clone(),
423 metadata.clone(),
424 )
425 } else {
426 Parser::with_config(inner_source, self.config.clone())
427 }
428 } else {
429 Parser::new(inner_source)
430 };
431
432 #[cfg(not(feature = "validation"))]
433 let inner_parser = Parser::new(inner_source);
434
435 let (mut inner_ast, inner_errors) = inner_parser.parse_forge_script();
436
437 inner_ast.offset_spans(content_start);
438
439 match inner_ast {
440 AstNode::Program {
441 body: inner_body, ..
442 } => {
443 body.extend(inner_body);
444 }
445 _ => body.push(inner_ast),
446 }
447
448 for mut error in inner_errors {
449 error.span.offset(content_start);
450 self.errors.push(error);
451 }
452 }
453
454 self.pos = block_end + 1;
456 } else {
457 if self.config.validate_brackets {
459 self.errors.push(ParseError::syntax(
460 "Unclosed code block",
461 Span::new(block_start, self.source.len()),
462 ));
463 }
464 body.push(AstNode::Text {
465 content: self.slice(block_start, self.source.len()).to_string(),
466 span: Span::new(block_start, self.source.len()),
467 });
468 self.pos = self.source.len();
469 }
470 } else {
471 if self.pos < self.source.len() {
473 body.push(AstNode::Text {
474 content: self.slice(self.pos, self.source.len()).to_string(),
475 span: Span::new(self.pos, self.source.len()),
476 });
477 }
478 self.pos = self.source.len();
479 }
480 }
481
482 let span = Span::new(start, self.source.len());
483 (AstNode::Program { body, span }, self.errors)
484 }
485
486 fn parse_forge_script(mut self) -> (AstNode, Vec<ParseError>) {
487 let start = self.pos;
488 let mut body = Vec::new();
489
490 while !self.is_eof() {
491 if let Some(node) = self.parse_forge_node() {
492 body.push(node);
493 }
494 }
495
496 let span = Span::new(start, self.source.len());
497 (AstNode::Program { body, span }, self.errors)
498 }
499
500 #[inline(always)]
505 fn is_eof(&self) -> bool {
506 self.pos >= self.bytes.len()
507 }
508
509 #[inline(always)]
510 fn current_byte(&self) -> Option<u8> {
511 self.bytes.get(self.pos).copied()
512 }
513
514 #[inline(always)]
515 fn peek_byte(&self, offset: usize) -> Option<u8> {
516 self.bytes.get(self.pos + offset).copied()
517 }
518
519 #[inline(always)]
520 fn advance(&mut self) -> Option<u8> {
521 let byte = self.current_byte()?;
522 self.pos += 1;
523 Some(byte)
524 }
525
526 #[inline]
527 fn slice(&self, start: usize, end: usize) -> &'src str {
528 &self.source[start..end.min(self.source.len())]
529 }
530
531 fn find_code_block_start(&self) -> Option<usize> {
532 let mut p = self.pos;
533 while p + 7 <= self.bytes.len() {
534 if &self.bytes[p..p + 7] == b"code: `" {
536 let preceded_by_valid = p == 0
537 || self.bytes[p - 1].is_ascii_whitespace()
538 || self.bytes[p - 1] == b'{'
539 || self.bytes[p - 1] == b',';
540 if preceded_by_valid && !is_escaped(self.source, p + 6) {
541 return Some(p);
542 }
543 }
544 p += 1;
545 }
546 None
547 }
548
549 fn find_code_block_end(&self) -> Option<usize> {
556 let mut p = self.pos;
557 while p < self.bytes.len() {
558 if self.bytes[p] == b'\\' {
559 p += escape_sequence_len(self.bytes, p).max(1);
562 continue;
563 }
564 if self.bytes[p] == b'`' {
565 return Some(p);
566 }
567 p += 1;
568 }
569 None
570 }
571
572 fn parse_forge_node(&mut self) -> Option<AstNode> {
577 if self.current_byte() == Some(b'\\') {
580 return self.parse_escape_sequence();
581 }
582
583 if self.current_byte() == Some(b'$') {
585 if self.peek_byte(1) == Some(b'{') {
586 return Some(self.parse_javascript());
587 }
588 return Some(self.parse_function_call());
589 }
590
591 self.parse_text()
592 }
593
594 fn parse_text(&mut self) -> Option<AstNode> {
595 let start = self.pos;
596 while !self.is_eof() {
597 if self.current_byte() == Some(b'\\') {
599 break;
600 }
601 if self.current_byte() == Some(b'$') {
604 break;
605 }
606 self.advance();
607 }
608
609 if self.pos > start {
610 Some(AstNode::Text {
611 content: self.slice(start, self.pos).to_string(),
612 span: Span::new(start, self.pos),
613 })
614 } else {
615 None
616 }
617 }
618
619 fn parse_escape_sequence(&mut self) -> Option<AstNode> {
630 let start = self.pos;
631 self.advance(); match self.current_byte() {
634 Some(b'`') => {
636 self.advance();
637 Some(AstNode::Text {
638 content: "`".to_string(),
639 span: Span::new(start, self.pos),
640 })
641 }
642
643 Some(b'\\') => {
645 match self.peek_byte(1) {
646 Some(b'$') | Some(b']') | Some(b';') => {
647 let ch = self.peek_byte(1).unwrap() as char;
649 self.advance(); self.advance(); Some(AstNode::Text {
652 content: ch.to_string(),
653 span: Span::new(start, self.pos),
654 })
655 }
656 _ => {
657 self.advance(); Some(AstNode::Text {
660 content: "\\".to_string(),
661 span: Span::new(start, self.pos),
662 })
663 }
664 }
665 }
666
667 _ => Some(AstNode::Text {
670 content: "\\".to_string(),
671 span: Span::new(start, start + 1),
672 }),
673 }
674 }
675
676 fn parse_javascript(&mut self) -> AstNode {
677 let start = self.pos;
678 self.advance(); self.advance(); let brace_start = self.pos - 1;
681
682 if let Some(end) = self.find_matching_brace(brace_start) {
683 let code = self.slice(brace_start + 1, end).to_string();
684 self.pos = end + 1;
685 AstNode::JavaScript {
686 code,
687 span: Span::new(start, self.pos),
688 }
689 } else {
690 if self.config.validate_brackets {
691 self.errors.push(ParseError::syntax(
692 "Unclosed JavaScript expression",
693 Span::new(start, self.source.len()),
694 ));
695 }
696 self.pos = self.source.len();
697 AstNode::JavaScript {
698 code: String::new(),
699 span: Span::new(start, self.pos),
700 }
701 }
702 }
703
704 fn parse_function_call(&mut self) -> AstNode {
705 let start = self.pos;
706 self.advance(); let modifier_start = self.pos;
710 let modifiers = self.parse_modifiers();
711 let modifier_end = self.pos;
712
713 let modifier_span = if modifier_end > modifier_start {
715 Some(Span::new(modifier_start, modifier_end))
716 } else {
717 None
718 };
719
720 let name = self.parse_identifier();
722 let name_end = self.pos;
723
724 if name.is_empty() {
725 return AstNode::Text {
726 content: "$".to_string(),
727 span: Span::new(start, start + 1),
728 };
729 }
730
731 let name_span = Span::new(start, name_end);
733
734 if self.is_escape_function(&name) {
735 return self.parse_escape_function(start, name, name_span);
736 }
737
738 let has_brackets = self.current_byte() == Some(b'[');
740 let bracket_open = self.pos;
741
742 let args = if has_brackets {
743 self.parse_function_arguments()
744 } else {
745 None
746 };
747
748 let args_span = if has_brackets {
749 Some(Span::new(bracket_open, self.pos))
751 } else {
752 None
753 };
754
755 let full_span = Span::new(modifier_start, self.pos);
756 let span = Span::new(start, self.pos);
757
758 #[cfg(feature = "validation")]
760 if self.config.is_enabled() {
761 let full_name = if name.starts_with('$') {
762 name.clone()
763 } else {
764 format!("${}", name)
765 };
766
767 if let Some(ref metadata) = self.metadata {
768 let resolved = if has_brackets {
769 metadata.get_exact(&full_name)
770 } else {
771 metadata.get(&full_name)
772 };
773
774 if let Some(func) = resolved {
775 self.validate_function_call(
776 &full_name,
777 &func,
778 args.as_ref(),
779 has_brackets,
780 name_span,
781 );
782 } else if self.config.validate_functions {
783 let hint: Option<String> = if has_brackets {
784 metadata.get_prefix(&full_name).map(|(matched, _)| matched)
785 } else {
786 None
787 };
788
789 if let Some(matched) = hint {
790 self.errors.push(ParseError::new(
791 format!(
792 "Unknown function: {} (did you mean {}?)",
793 full_name, matched
794 ),
795 name_span,
796 ErrorKind::UnknownFunction,
797 ));
798 } else {
799 self.errors.push(ParseError::new(
800 format!("Unknown function: {}", full_name),
801 name_span,
802 ErrorKind::UnknownFunction,
803 ));
804 }
805 }
806 } else if self.config.validate_functions {
807 self.errors.push(ParseError::new(
808 format!(
809 "Cannot validate function {}: no metadata available",
810 full_name
811 ),
812 name_span,
813 ErrorKind::UnknownFunction,
814 ));
815 }
816 }
817
818 AstNode::FunctionCall {
819 name,
820 name_span,
821 modifier_span,
822 args_span,
823 args,
824 modifiers,
825 full_span,
826 span,
827 }
828 }
829
830 #[cfg(feature = "validation")]
835 fn validate_function_call(
836 &mut self,
837 name: &str,
838 func: &Function,
839 args: Option<&Vec<Argument>>,
840 has_brackets: bool,
841 name_span: Span,
842 ) {
843 if self.config.validate_brackets {
845 match func.brackets {
846 Some(true) => {
847 if !has_brackets {
848 self.errors.push(ParseError::new(
849 format!("{} requires brackets", name),
850 name_span,
851 ErrorKind::BracketUsage,
852 ));
853 }
854 }
855 Some(false) => {
856 }
858 None => {
859 if has_brackets {
860 self.errors.push(ParseError::new(
861 format!("{} does not accept brackets", name),
862 name_span,
863 ErrorKind::BracketUsage,
864 ));
865 }
866 }
867 }
868 }
869
870 if (self.config.validate_arguments || self.config.validate_enums) && has_brackets {
872 if let (Some(args), Some(func_args)) = (args, &func.args) {
873 self.validate_arguments(name, args, func_args, name_span);
874 }
875 }
876 }
877
878 #[cfg(feature = "validation")]
879 fn validate_arguments(
880 &mut self,
881 func_name: &str,
882 provided_args: &[Argument],
883 func_args: &[Arg],
884 name_span: Span,
885 ) {
886 let provided_count = provided_args.len();
887
888 let has_rest = func_args.iter().any(|a| a.rest);
889 let required_count = func_args
890 .iter()
891 .filter(|a| a.required.unwrap_or(false) && !a.rest)
892 .count();
893 let max_count = if has_rest {
894 usize::MAX
895 } else {
896 func_args.len()
897 };
898
899 if self.config.validate_arguments {
900 if provided_count < required_count {
901 self.errors.push(ParseError::new(
902 format!(
903 "{} requires at least {} argument(s), got {}",
904 func_name, required_count, provided_count
905 ),
906 name_span,
907 ErrorKind::ArgumentCount,
908 ));
909 } else if !has_rest && provided_count > max_count {
910 self.errors.push(ParseError::new(
911 format!(
912 "{} accepts at most {} argument(s), got {}",
913 func_name, max_count, provided_count
914 ),
915 name_span,
916 ErrorKind::ArgumentCount,
917 ));
918 }
919 }
920
921 if self.config.validate_enums {
922 for (i, provided_arg) in provided_args.iter().enumerate() {
923 let func_arg = if i < func_args.len() {
924 &func_args[i]
925 } else if has_rest {
926 func_args.last().unwrap()
927 } else {
928 continue;
929 };
930
931 if ENUM_ACCEPTS
932 .iter()
933 .any(|&(f, idx)| idx == i && f.eq_ignore_ascii_case(func_name))
934 {
935 continue;
936 }
937
938 self.validate_enum_value(func_name, provided_arg, func_arg, provided_arg.span);
939 }
940 }
941 }
942
943 #[cfg(feature = "validation")]
944 fn validate_enum_value(
945 &mut self,
946 func_name: &str,
947 arg: &Argument,
948 func_arg: &Arg,
949 name_span: Span,
950 ) {
951 if !func_arg.required.unwrap_or(false) && arg.is_empty() {
952 return;
953 }
954
955 let enum_values = if let Some(enum_name) = &func_arg.enum_name {
956 if let Some(ref metadata) = self.metadata {
957 metadata.get_enum(enum_name)
958 } else {
959 None
960 }
961 } else {
962 func_arg.arg_enum.clone()
963 };
964
965 if let Some(valid_values) = enum_values {
966 if let Some(text_value) = arg.as_text() {
967 let trimmed = text_value.trim();
968 if !trimmed.is_empty() && !valid_values.contains(&trimmed.to_string()) {
969 self.errors.push(ParseError::new(
970 format!(
971 "Invalid value for {} argument {}: expected one of {:?}",
972 func_name, func_arg.name, valid_values
973 ),
974 name_span,
975 ErrorKind::EnumValue,
976 ));
977 }
978 }
979 }
980 }
981
982 fn parse_modifiers(&mut self) -> Modifiers {
987 let mut modifiers = Modifiers::default();
988 let start = self.pos;
989
990 loop {
991 match self.current_byte() {
992 Some(b'!') => {
993 modifiers.silent = true;
994 self.advance();
995 }
996 Some(b'#') => {
997 modifiers.negated = true;
998 self.advance();
999 }
1000 Some(b'@') if self.peek_byte(1) == Some(b'[') => {
1001 self.advance(); let bracket_start = self.pos;
1003 self.advance(); if let Some(end) = self.find_matching_bracket(bracket_start) {
1005 modifiers.count = Some(self.slice(bracket_start + 1, end).to_string());
1006 self.pos = end + 1;
1007 } else if self.config.validate_brackets {
1008 self.errors.push(ParseError::syntax(
1009 "Unclosed modifier bracket",
1010 Span::new(bracket_start, bracket_start + 1),
1011 ));
1012 break;
1013 } else {
1014 break;
1015 }
1016 }
1017 _ => break,
1018 }
1019 }
1020
1021 let end = self.pos;
1022 if end > start {
1023 modifiers.span = Some(Span::new(start, end));
1024 }
1025
1026 modifiers
1027 }
1028
1029 #[inline]
1030 fn parse_identifier(&mut self) -> String {
1031 let start = self.pos;
1032 while let Some(b) = self.current_byte() {
1033 if b.is_ascii_alphanumeric() || b == b'_' {
1034 self.advance();
1035 } else {
1036 break;
1037 }
1038 }
1039 self.slice(start, self.pos).to_string()
1040 }
1041
1042 fn is_escape_function(&self, name: &str) -> bool {
1043 matches!(name, "c" | "C" | "escape")
1044 }
1045
1046 fn parse_escape_function(&mut self, start: usize, name: String, name_span: Span) -> AstNode {
1047 if self.current_byte() != Some(b'[') {
1048 if self.config.validate_brackets {
1049 self.errors.push(ParseError::new(
1050 format!("${} requires brackets", name),
1051 name_span,
1052 ErrorKind::BracketUsage,
1053 ));
1054 }
1055 return AstNode::Text {
1056 content: self.slice(start, self.pos).to_string(),
1057 span: Span::new(start, self.pos),
1058 };
1059 }
1060
1061 let bracket_start = self.pos;
1062 self.advance();
1063 if let Some(end) = self.find_matching_bracket(bracket_start) {
1064 let content = self.slice(bracket_start + 1, end).to_string();
1065 self.pos = end + 1;
1066 AstNode::Escaped {
1067 content,
1068 span: Span::new(start, self.pos),
1069 }
1070 } else {
1071 if self.config.validate_brackets {
1072 self.errors.push(ParseError::syntax(
1073 format!("Unclosed '[' for ${}", name),
1074 name_span,
1075 ));
1076 }
1077 self.pos = self.source.len();
1078 AstNode::Escaped {
1079 content: String::new(),
1080 span: Span::new(start, self.pos),
1081 }
1082 }
1083 }
1084
1085 fn parse_function_arguments(&mut self) -> Option<Vec<Argument>> {
1086 let bracket_start = self.pos;
1087 self.advance();
1088 if let Some(end) = self.find_matching_bracket(bracket_start) {
1089 let args_content = self.slice(bracket_start + 1, end);
1090 let parsed_args = self.parse_arguments(args_content, bracket_start + 1);
1091 self.pos = end + 1;
1092 Some(parsed_args)
1093 } else {
1094 if self.config.validate_brackets {
1095 self.errors.push(ParseError::syntax(
1096 "Unclosed function arguments",
1097 Span::new(bracket_start, bracket_start + 1),
1098 ));
1099 }
1100 None
1101 }
1102 }
1103
1104 fn parse_arguments(&mut self, content: &str, base_offset: usize) -> Vec<Argument> {
1105 let mut args = Vec::new();
1106 let mut current = String::new();
1107 let mut depth = 0usize;
1108 let bytes = content.as_bytes();
1109 let mut i = 0;
1110 let mut arg_start = 0usize;
1111 let mut arg_end = 0usize;
1112
1113 while i < bytes.len() {
1114 if bytes[i] == b'\\' {
1122 let skip = escape_sequence_len(bytes, i).max(1);
1123 let end = (i + skip).min(bytes.len());
1124 current.push_str(&content[i..end]);
1125 i = end;
1126 arg_end = i;
1127 continue;
1128 }
1129
1130 if bytes[i] == b'$' && depth == 0 {
1135 if let Some(esc_end) = self.find_escape_function_end(content, i) {
1136 current.push_str(&content[i..=esc_end]);
1137 i = esc_end + 1;
1138 arg_end = i;
1139 continue;
1140 }
1141 }
1142
1143 match bytes[i] {
1144 b'[' if self.is_function_bracket(content, i) => {
1148 depth += 1;
1149 current.push('[');
1150 arg_end = i + 1;
1151 }
1152 b']' if depth > 0 => {
1155 depth -= 1;
1156 current.push(']');
1157 arg_end = i + 1;
1158 }
1159 b';' if depth == 0 => {
1160 let arg_offset = base_offset + arg_start;
1161 let arg_len = arg_end - arg_start;
1162 let parts = self.parse_argument_parts(¤t, arg_offset);
1163 args.push(Argument {
1164 parts,
1165 span: Span::new(arg_offset, arg_offset + arg_len),
1166 });
1167 current.clear();
1168 arg_start = i + 1;
1169 arg_end = i + 1;
1170 }
1171 _ => {
1172 let ch_len = content[i..]
1173 .chars()
1174 .next()
1175 .map(|c| c.len_utf8())
1176 .unwrap_or(1);
1177 current.push_str(&content[i..i + ch_len]);
1178 arg_end = i + ch_len;
1179 i += ch_len - 1;
1180 }
1181 }
1182 i += 1;
1183 }
1184
1185 if !current.is_empty() || !args.is_empty() {
1186 let arg_offset = base_offset + arg_start;
1187 let arg_len = arg_end - arg_start;
1188 let parts = self.parse_argument_parts(¤t, arg_offset);
1189 args.push(Argument {
1190 parts,
1191 span: Span::new(arg_offset, arg_offset + arg_len),
1192 });
1193 }
1194 args
1195 }
1196
1197 fn parse_argument_parts(&mut self, content: &str, offset: usize) -> SmallVec<[AstNode; 4]> {
1198 if content.is_empty() {
1199 let mut parts = SmallVec::new();
1200 parts.push(AstNode::Text {
1201 content: String::new(),
1202 span: Span::new(offset, offset),
1203 });
1204 return parts;
1205 }
1206
1207 #[cfg(feature = "validation")]
1208 let inner_parser = if self.config.is_enabled() {
1209 if let Some(ref metadata) = self.metadata {
1210 Parser::with_validation(content, self.config.clone(), metadata.clone())
1211 } else {
1212 Parser::with_config(content, self.config.clone())
1213 }
1214 } else {
1215 Parser::new(content)
1216 };
1217
1218 #[cfg(not(feature = "validation"))]
1219 let inner_parser = Parser::new(content);
1220
1221 let (ast, errors) = inner_parser.parse_forge_script();
1222
1223 let nodes = if let AstNode::Program { mut body, .. } = ast {
1224 for node in &mut body {
1225 node.offset_spans(offset);
1226 }
1227 body
1228 } else {
1229 vec![ast]
1230 };
1231
1232 for mut error in errors {
1233 error.span.offset(offset);
1234 self.errors.push(error);
1235 }
1236
1237 let mut parts = SmallVec::new();
1238 for node in nodes {
1239 parts.push(node);
1240 }
1241 parts
1242 }
1243
1244 fn find_matching_bracket(&self, open_pos: usize) -> Option<usize> {
1262 let mut depth = 1usize;
1263 let mut p = open_pos + 1;
1264 while p < self.bytes.len() {
1265 if self.bytes[p] == b'\\' {
1266 p += escape_sequence_len(self.bytes, p).max(1);
1267 continue;
1268 }
1269 if self.bytes[p] == b'[' && self.is_function_bracket(self.source, p) {
1271 depth += 1;
1272 } else if self.bytes[p] == b']' {
1273 depth -= 1;
1274 if depth == 0 {
1275 return Some(p);
1276 }
1277 }
1278 p += 1;
1279 }
1280 None
1281 }
1282
1283 fn find_matching_brace(&self, open_pos: usize) -> Option<usize> {
1284 let mut depth = 1;
1285 let mut p = open_pos + 1;
1286 while p < self.bytes.len() {
1287 match self.bytes[p] {
1288 b'{' => depth += 1,
1289 b'}' => {
1290 depth -= 1;
1291 if depth == 0 {
1292 return Some(p);
1293 }
1294 }
1295 _ => {}
1296 }
1297 p += 1;
1298 }
1299 None
1300 }
1301
1302 fn is_function_bracket(&self, content: &str, idx: usize) -> bool {
1303 if idx == 0 || content.as_bytes().get(idx) != Some(&b'[') {
1304 return false;
1305 }
1306 let bytes = content.as_bytes();
1307 let mut i = idx;
1308 while i > 0 && (bytes[i - 1].is_ascii_alphanumeric() || bytes[i - 1] == b'_') {
1309 i -= 1;
1310 }
1311 while i > 0 && matches!(bytes[i - 1], b'!' | b'#' | b']') {
1312 if bytes[i - 1] == b']' {
1313 let mut d = 1;
1314 while i > 1 && d > 0 {
1315 i -= 1;
1316 if bytes[i - 1] == b']' {
1317 d += 1;
1318 } else if bytes[i - 1] == b'[' {
1319 d -= 1;
1320 }
1321 }
1322 if i < 2 || bytes[i - 2] != b'@' {
1323 return false;
1324 }
1325 i -= 2;
1326 } else {
1327 i -= 1;
1328 }
1329 }
1330 i > 0 && bytes[i - 1] == b'$' && (i == 1 || bytes[i - 2] != b'\\')
1331 }
1332
1333 fn find_escape_function_end(&self, content: &str, start: usize) -> Option<usize> {
1334 let bytes = content.as_bytes();
1335 let mut p = start + 1;
1336 while p < bytes.len() && matches!(bytes[p], b'!' | b'#') {
1337 p += 1;
1338 }
1339 let name_start = p;
1340 while p < bytes.len() && (bytes[p].is_ascii_alphanumeric() || bytes[p] == b'_') {
1341 p += 1;
1342 }
1343 if !self.is_escape_function(&content[name_start..p]) || bytes.get(p) != Some(&b'[') {
1344 return None;
1345 }
1346 let mut depth = 1usize;
1347 p += 1;
1348 while p < bytes.len() {
1349 if bytes[p] == b'\\' {
1350 p += escape_sequence_len(bytes, p).max(1);
1351 continue;
1352 }
1353 if bytes[p] == b'[' && self.is_function_bracket(content, p) {
1354 depth += 1;
1355 } else if bytes[p] == b']' {
1356 depth -= 1;
1357 if depth == 0 {
1358 return Some(p);
1359 }
1360 }
1361 p += 1;
1362 }
1363 None
1364 }
1365}
1366
1367pub fn parse(source: &str) -> (AstNode, Vec<ParseError>) {
1373 Parser::new(source).parse()
1374}
1375
1376pub fn parse_with_errors(source: &str) -> Result<AstNode, Vec<ParseError>> {
1378 let (ast, errors) = parse(source);
1379 if errors.is_empty() {
1380 Ok(ast)
1381 } else {
1382 Err(errors)
1383 }
1384}
1385
1386#[cfg(feature = "validation")]
1388pub fn parse_with_config(source: &str, config: ValidationConfig) -> (AstNode, Vec<ParseError>) {
1389 Parser::with_config(source, config).parse()
1390}
1391
1392#[cfg(feature = "validation")]
1394pub fn parse_with_validation(
1395 source: &str,
1396 config: ValidationConfig,
1397 metadata: Arc<MetadataManager>,
1398) -> (AstNode, Vec<ParseError>) {
1399 Parser::with_validation(source, config, metadata).parse()
1400}
1401
1402#[cfg(feature = "validation")]
1404pub fn parse_forge_script_with_validation(
1405 source: &str,
1406 config: ValidationConfig,
1407 metadata: Arc<MetadataManager>,
1408) -> (AstNode, Vec<ParseError>) {
1409 Parser::with_validation(source, config, metadata).parse_forge_script()
1410}
1411
1412#[cfg(feature = "validation")]
1414pub fn parse_strict(source: &str, metadata: Arc<MetadataManager>) -> (AstNode, Vec<ParseError>) {
1415 Parser::with_validation(source, ValidationConfig::strict(), metadata).parse()
1416}