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 pub validate_escapes: bool,
54}
55
56impl ValidationConfig {
57 pub fn strict() -> Self {
59 Self {
60 validate_arguments: true,
61 validate_enums: true,
62 validate_functions: true,
63 validate_brackets: true,
64 validate_escapes: true,
65 }
66 }
67
68 pub fn syntax_only() -> Self {
70 Self {
71 validate_arguments: false,
72 validate_enums: false,
73 validate_functions: false,
74 validate_brackets: true,
75 validate_escapes: true,
76 }
77 }
78
79 #[inline]
81 pub fn is_enabled(&self) -> bool {
82 self.validate_arguments
83 || self.validate_enums
84 || self.validate_functions
85 || self.validate_brackets
86 || self.validate_escapes
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct Span {
96 pub start: usize,
97 pub end: usize,
98}
99
100impl Span {
101 #[inline(always)]
102 pub const fn new(start: usize, end: usize) -> Self {
103 Self { start, end }
104 }
105
106 #[inline(always)]
107 pub fn offset(&mut self, offset: usize) {
108 self.start += offset;
109 self.end += offset;
110 }
111
112 #[inline(always)]
113 pub fn len(&self) -> usize {
114 self.end - self.start
115 }
116
117 #[inline(always)]
118 pub fn is_empty(&self) -> bool {
119 self.start >= self.end
120 }
121}
122
123#[derive(Debug, Clone, Default)]
124pub struct Modifiers {
125 pub silent: bool,
126 pub negated: bool,
127 pub count: Option<String>,
128}
129
130#[derive(Debug, Clone)]
131pub struct Argument {
132 pub parts: SmallVec<[AstNode; 4]>,
133 pub span: Span,
134}
135
136impl Argument {
137 pub fn is_empty(&self) -> bool {
139 self.parts.iter().all(|part| match part {
140 AstNode::Text { content, .. } => content.trim().is_empty(),
141 _ => false,
142 })
143 }
144
145 pub fn as_text(&self) -> Option<String> {
147 if self.parts.len() == 1 {
148 if let AstNode::Text { content, .. } = &self.parts[0] {
149 return Some(content.clone());
150 }
151 }
152
153 if self.parts.iter().all(|p| matches!(p, AstNode::Text { .. })) {
155 let mut result = String::new();
156 for part in &self.parts {
157 if let AstNode::Text { content, .. } = part {
158 result.push_str(&content);
159 }
160 }
161 return Some(result);
162 }
163
164 None
165 }
166}
167
168#[derive(Debug, Clone)]
169pub enum AstNode {
170 Program {
171 body: Vec<AstNode>,
172 span: Span,
173 },
174 Text {
175 content: String,
176 span: Span,
177 },
178 FunctionCall {
179 name: String,
180 args: Option<Vec<Argument>>,
181 modifiers: Modifiers,
182 span: Span,
183 },
184 JavaScript {
185 code: String,
186 span: Span,
187 },
188 Escaped {
189 content: String,
190 span: Span,
191 },
192}
193
194impl AstNode {
195 pub fn span(&self) -> Span {
196 match self {
197 AstNode::Program { span, .. }
198 | AstNode::Text { span, .. }
199 | AstNode::FunctionCall { span, .. }
200 | AstNode::JavaScript { span, .. }
201 | AstNode::Escaped { span, .. } => *span,
202 }
203 }
204
205 pub fn offset_spans(&mut self, offset: usize) {
206 match self {
207 AstNode::Program { body, span } => {
208 span.offset(offset);
209 for node in body {
210 node.offset_spans(offset);
211 }
212 }
213 AstNode::Text { span, .. }
214 | AstNode::JavaScript { span, .. }
215 | AstNode::Escaped { span, .. } => {
216 span.offset(offset);
217 }
218 AstNode::FunctionCall { args, span, .. } => {
219 span.offset(offset);
220 if let Some(args) = args {
221 for arg in args {
222 arg.span.offset(offset);
223 for part in &mut arg.parts {
224 part.offset_spans(offset);
225 }
226 }
227 }
228 }
229 }
230 }
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub enum ErrorKind {
239 Syntax,
240 ArgumentCount,
241 EnumValue,
242 UnknownFunction,
243 BracketUsage,
244 InvalidEscape,
245}
246
247#[derive(Debug, Clone)]
248pub struct ParseError {
249 pub message: String,
250 pub span: Span,
251 pub kind: ErrorKind,
252}
253
254impl ParseError {
255 #[inline]
256 pub fn new(message: impl Into<String>, span: Span, kind: ErrorKind) -> Self {
257 Self {
258 message: message.into(),
259 span,
260 kind,
261 }
262 }
263
264 #[inline]
265 pub fn syntax(message: impl Into<String>, span: Span) -> Self {
266 Self::new(message, span, ErrorKind::Syntax)
267 }
268}
269
270pub struct Parser<'src> {
275 source: &'src str,
276 bytes: &'src [u8],
277 pos: usize,
278 errors: Vec<ParseError>,
279 config: ValidationConfig,
280 #[cfg(feature = "validation")]
281 metadata: Option<Arc<MetadataManager>>,
282}
283
284impl<'src> Parser<'src> {
285 #[inline]
286 pub fn new(source: &'src str) -> Self {
287 Self {
288 source,
289 bytes: source.as_bytes(),
290 pos: 0,
291 errors: Vec::new(),
292 config: ValidationConfig::default(),
293 #[cfg(feature = "validation")]
294 metadata: None,
295 }
296 }
297
298 #[cfg(feature = "validation")]
300 #[inline]
301 pub fn with_config(source: &'src str, config: ValidationConfig) -> Self {
302 Self {
303 source,
304 bytes: source.as_bytes(),
305 pos: 0,
306 errors: Vec::new(),
307 config,
308 metadata: None,
309 }
310 }
311
312 #[cfg(feature = "validation")]
314 #[inline]
315 pub fn with_validation(
316 source: &'src str,
317 config: ValidationConfig,
318 metadata: Arc<MetadataManager>,
319 ) -> Self {
320 Self {
321 source,
322 bytes: source.as_bytes(),
323 pos: 0,
324 errors: Vec::new(),
325 config,
326 metadata: Some(metadata),
327 }
328 }
329
330 pub fn parse(mut self) -> (AstNode, Vec<ParseError>) {
331 let start = self.pos;
332 let mut body = Vec::new();
333
334 while !self.is_eof() {
335 if let Some(block_start) = self.find_code_block_start() {
337 if block_start > self.pos {
339 body.push(AstNode::Text {
340 content: self.slice(self.pos, block_start).to_string(),
341 span: Span::new(self.pos, block_start),
342 });
343 }
344
345 let content_start = block_start + 7; self.pos = content_start;
348
349 if let Some(block_end) = self.find_code_block_end() {
351 let content_len = block_end - content_start;
352
353 if content_len > 0 {
354 let inner_source = self.slice(content_start, block_end);
356
357 #[cfg(feature = "validation")]
358 let inner_parser = if self.config.is_enabled() {
359 if let Some(ref metadata) = self.metadata {
360 Parser::with_validation(
361 inner_source,
362 self.config.clone(),
363 metadata.clone(),
364 )
365 } else {
366 Parser::with_config(inner_source, self.config.clone())
367 }
368 } else {
369 Parser::new(inner_source)
370 };
371
372 #[cfg(not(feature = "validation"))]
373 let inner_parser = Parser::new(inner_source);
374
375 let (mut inner_ast, inner_errors) = inner_parser.parse_forge_script();
376
377 inner_ast.offset_spans(content_start);
378
379 match inner_ast {
380 AstNode::Program {
381 body: inner_body, ..
382 } => {
383 body.extend(inner_body);
384 }
385 _ => body.push(inner_ast),
386 }
387
388 for mut error in inner_errors {
389 error.span.offset(content_start);
390 self.errors.push(error);
391 }
392 }
393
394 self.pos = block_end + 1;
396 } else {
397 if self.config.validate_brackets {
399 self.errors.push(ParseError::syntax(
400 "Unclosed code block",
401 Span::new(block_start, self.source.len()),
402 ));
403 }
404 body.push(AstNode::Text {
405 content: self.slice(block_start, self.source.len()).to_string(),
406 span: Span::new(block_start, self.source.len()),
407 });
408 self.pos = self.source.len();
409 }
410 } else {
411 if self.pos < self.source.len() {
413 body.push(AstNode::Text {
414 content: self.slice(self.pos, self.source.len()).to_string(),
415 span: Span::new(self.pos, self.source.len()),
416 });
417 }
418 self.pos = self.source.len();
419 }
420 }
421
422 let span = Span::new(start, self.source.len());
423 (AstNode::Program { body, span }, self.errors)
424 }
425
426 fn parse_forge_script(mut self) -> (AstNode, Vec<ParseError>) {
427 let start = self.pos;
428 let mut body = Vec::new();
429
430 while !self.is_eof() {
431 if let Some(node) = self.parse_forge_node() {
432 body.push(node);
433 }
434 }
435
436 let span = Span::new(start, self.source.len());
437 (AstNode::Program { body, span }, self.errors)
438 }
439
440 #[inline(always)]
445 fn is_eof(&self) -> bool {
446 self.pos >= self.bytes.len()
447 }
448
449 #[inline(always)]
450 fn current_byte(&self) -> Option<u8> {
451 self.bytes.get(self.pos).copied()
452 }
453
454 #[inline(always)]
455 fn peek_byte(&self, offset: usize) -> Option<u8> {
456 self.bytes.get(self.pos + offset).copied()
457 }
458
459 #[inline(always)]
460 fn advance(&mut self) -> Option<u8> {
461 let byte = self.current_byte()?;
462 self.pos += 1;
463 Some(byte)
464 }
465
466 #[inline]
467 fn slice(&self, start: usize, end: usize) -> &'src str {
468 &self.source[start..end.min(self.source.len())]
469 }
470
471 #[inline]
472 fn is_escaped_at(&self, pos: usize) -> bool {
473 is_escaped(self.source, pos)
474 }
475
476 fn find_code_block_start(&self) -> Option<usize> {
477 let mut p = self.pos;
478 while p + 7 <= self.bytes.len() {
479 if &self.bytes[p..p + 7] == b"code: `" {
481 let preceded_by_valid = p == 0
482 || self.bytes[p - 1].is_ascii_whitespace()
483 || self.bytes[p - 1] == b'{'
484 || self.bytes[p - 1] == b',';
485 if preceded_by_valid && !is_escaped(self.source, p + 6) {
486 return Some(p);
487 }
488 }
489 p += 1;
490 }
491 None
492 }
493
494 fn find_code_block_end(&self) -> Option<usize> {
495 let mut p = self.pos;
496 while p < self.bytes.len() {
497 if self.bytes[p] == b'`' && !is_escaped(self.source, p) {
498 return Some(p);
499 }
500 p += 1;
501 }
502 None
503 }
504
505 fn parse_forge_node(&mut self) -> Option<AstNode> {
510 if self.current_byte() == Some(b'\\') {
512 return self.parse_escape_sequence();
513 }
514
515 if self.current_byte() == Some(b'$') && !self.is_escaped_at(self.pos) {
517 if self.peek_byte(1) == Some(b'{') {
518 return Some(self.parse_javascript());
519 }
520 return Some(self.parse_function_call());
521 }
522
523 self.parse_text()
524 }
525
526 fn parse_text(&mut self) -> Option<AstNode> {
527 let start = self.pos;
528 while !self.is_eof() {
529 if self.current_byte() == Some(b'\\') {
530 break;
531 }
532 if self.current_byte() == Some(b'$') && !self.is_escaped_at(self.pos) {
533 break;
534 }
535 self.advance();
536 }
537
538 if self.pos > start {
539 Some(AstNode::Text {
540 content: self.slice(start, self.pos).to_string(),
541 span: Span::new(start, self.pos),
542 })
543 } else {
544 None
545 }
546 }
547
548 fn parse_escape_sequence(&mut self) -> Option<AstNode> {
549 let start = self.pos;
550 self.advance(); if let Some(next) = self.current_byte() {
553 if next == b'\\' {
555 self.advance();
556 return Some(AstNode::Text {
557 content: "\\".to_string(),
558 span: Span::new(start, self.pos),
559 });
560 }
561 if matches!(next, b'$' | b'[' | b']' | b';' | b'`') {
563 self.advance();
564 return Some(AstNode::Text {
565 content: self.slice(start + 1, self.pos).to_string(),
566 span: Span::new(start, self.pos),
567 });
568 }
569
570 if self.config.validate_escapes {
572 self.errors.push(ParseError::new(
573 format!("Invalid escape sequence: \\{}", next as char),
574 Span::new(start, start + 2),
575 ErrorKind::InvalidEscape,
576 ));
577 }
578 }
579
580 Some(AstNode::Text {
582 content: "\\".to_string(),
583 span: Span::new(start, start + 1),
584 })
585 }
586
587 fn parse_javascript(&mut self) -> AstNode {
588 let start = self.pos;
589 self.advance(); self.advance(); let brace_start = self.pos - 1;
592
593 if let Some(end) = self.find_matching_brace(brace_start) {
594 let code = self.slice(brace_start + 1, end).to_string();
595 self.pos = end + 1;
596 AstNode::JavaScript {
597 code,
598 span: Span::new(start, self.pos),
599 }
600 } else {
601 if self.config.validate_brackets {
602 self.errors.push(ParseError::syntax(
603 "Unclosed JavaScript expression",
604 Span::new(start, self.source.len()),
605 ));
606 }
607 self.pos = self.source.len();
608 AstNode::JavaScript {
609 code: String::new(),
610 span: Span::new(start, self.pos),
611 }
612 }
613 }
614
615 fn parse_function_call(&mut self) -> AstNode {
616 let start = self.pos;
617 self.advance(); let modifiers = self.parse_modifiers();
620 let name = self.parse_identifier();
621
622 if name.is_empty() {
623 return AstNode::Text {
624 content: "$".to_string(),
625 span: Span::new(start, start + 1),
626 };
627 }
628
629 if self.is_escape_function(&name) {
630 return self.parse_escape_function(start, name);
631 }
632
633 let has_brackets = self.current_byte() == Some(b'[');
634 let args = if has_brackets {
635 self.parse_function_arguments()
636 } else {
637 None
638 };
639
640 let span = Span::new(start, self.pos);
641
642 #[cfg(feature = "validation")]
644 if self.config.is_enabled() {
645 let full_name = if name.starts_with('$') {
647 name.clone()
648 } else {
649 format!("${}", name)
650 };
651
652 if let Some(ref metadata) = self.metadata {
653 if let Some(func) = metadata.get(&full_name) {
654 self.validate_function_call(
655 &full_name,
656 &func,
657 args.as_ref(),
658 has_brackets,
659 span,
660 );
661 } else if self.config.validate_functions {
662 self.errors.push(ParseError::new(
663 format!("Unknown function: {}", full_name),
664 span,
665 ErrorKind::UnknownFunction,
666 ));
667 }
668 } else if self.config.validate_functions {
669 self.errors.push(ParseError::new(
671 format!(
672 "Cannot validate function {}: no metadata available",
673 full_name
674 ),
675 span,
676 ErrorKind::UnknownFunction,
677 ));
678 }
679 }
680
681 AstNode::FunctionCall {
682 name,
683 args,
684 modifiers,
685 span,
686 }
687 }
688
689 #[cfg(feature = "validation")]
694 fn validate_function_call(
695 &mut self,
696 name: &str,
697 func: &Function,
698 args: Option<&Vec<Argument>>,
699 has_brackets: bool,
700 span: Span,
701 ) {
702 if self.config.validate_brackets {
704 match func.brackets {
705 Some(true) => {
706 if !has_brackets {
708 self.errors.push(ParseError::new(
709 format!("{} requires brackets", name),
710 span,
711 ErrorKind::BracketUsage,
712 ));
713 }
714 }
715 Some(false) => {
716 }
718 None => {
719 if has_brackets {
721 self.errors.push(ParseError::new(
722 format!("{} does not accept brackets", name),
723 span,
724 ErrorKind::BracketUsage,
725 ));
726 }
727 }
728 }
729 }
730
731 if (self.config.validate_arguments || self.config.validate_enums) && has_brackets {
733 if let (Some(args), Some(func_args)) = (args, &func.args) {
734 self.validate_arguments(name, args, func_args, span);
735 }
736 }
737 }
738
739 #[cfg(feature = "validation")]
740 fn validate_arguments(
741 &mut self,
742 func_name: &str,
743 provided_args: &[Argument],
744 func_args: &[Arg],
745 span: Span,
746 ) {
747 let provided_count = provided_args.len();
748
749 let has_rest = func_args.iter().any(|a| a.rest);
751
752 let required_count = func_args
754 .iter()
755 .filter(|a| a.required.unwrap_or(false) && !a.rest)
756 .count();
757
758 let max_count = if has_rest {
760 usize::MAX
761 } else {
762 func_args.len()
763 };
764
765 if self.config.validate_arguments {
767 if provided_count < required_count {
768 self.errors.push(ParseError::new(
769 format!(
770 "{} requires at least {} argument(s), got {}",
771 func_name, required_count, provided_count
772 ),
773 span,
774 ErrorKind::ArgumentCount,
775 ));
776 } else if !has_rest && provided_count > max_count {
777 self.errors.push(ParseError::new(
778 format!(
779 "{} accepts at most {} argument(s), got {}",
780 func_name, max_count, provided_count
781 ),
782 span,
783 ErrorKind::ArgumentCount,
784 ));
785 }
786 }
787
788 if self.config.validate_enums {
790 for (i, provided_arg) in provided_args.iter().enumerate() {
791 let func_arg = if i < func_args.len() {
793 &func_args[i]
794 } else if has_rest {
795 func_args.last().unwrap()
797 } else {
798 continue;
799 };
800
801 self.validate_enum_value(func_name, provided_arg, func_arg);
802 }
803 }
804 }
805
806 #[cfg(feature = "validation")]
807 fn validate_enum_value(&mut self, func_name: &str, arg: &Argument, func_arg: &Arg) {
808 if !func_arg.required.unwrap_or(false) && arg.is_empty() {
810 return;
811 }
812
813 let enum_values = if let Some(enum_name) = &func_arg.enum_name {
815 if let Some(ref metadata) = self.metadata {
817 metadata.get_enum(enum_name)
818 } else {
819 None
820 }
821 } else {
822 func_arg.arg_enum.clone()
824 };
825
826 if let Some(valid_values) = enum_values {
827 if let Some(text_value) = arg.as_text() {
829 let trimmed = text_value.trim();
830
831 if !trimmed.is_empty() && !valid_values.contains(&trimmed.to_string()) {
832 self.errors.push(ParseError::new(
833 format!(
834 "Invalid value '{}' for {} in {}. Expected one of: {}",
835 trimmed,
836 func_arg.name,
837 func_name,
838 valid_values.join(", ")
839 ),
840 arg.span,
841 ErrorKind::EnumValue,
842 ));
843 }
844 }
845 }
846 }
847
848 fn parse_modifiers(&mut self) -> Modifiers {
853 let mut modifiers = Modifiers::default();
854 loop {
855 match self.current_byte() {
856 Some(b'!') => {
857 modifiers.silent = true;
858 self.advance();
859 }
860 Some(b'#') => {
861 modifiers.negated = true;
862 self.advance();
863 }
864 Some(b'@') if self.peek_byte(1) == Some(b'[') => {
865 self.advance(); let bracket_start = self.pos;
867 self.advance(); if let Some(end) = self.find_matching_bracket(bracket_start) {
869 modifiers.count = Some(self.slice(bracket_start + 1, end).to_string());
870 self.pos = end + 1;
871 } else if self.config.validate_brackets {
872 self.errors.push(ParseError::syntax(
873 "Unclosed modifier bracket",
874 Span::new(bracket_start, bracket_start + 1),
875 ));
876 break;
877 } else {
878 break;
879 }
880 }
881 _ => break,
882 }
883 }
884 modifiers
885 }
886
887 #[inline]
888 fn parse_identifier(&mut self) -> String {
889 let start = self.pos;
890 while let Some(b) = self.current_byte() {
891 if b.is_ascii_alphanumeric() || b == b'_' {
892 self.advance();
893 } else {
894 break;
895 }
896 }
897 self.slice(start, self.pos).to_string()
898 }
899
900 fn is_escape_function(&self, name: &str) -> bool {
901 matches!(name, "c" | "C" | "escape")
902 }
903
904 fn parse_escape_function(&mut self, start: usize, name: String) -> AstNode {
905 if self.current_byte() != Some(b'[') {
906 if self.config.validate_brackets {
907 self.errors.push(ParseError::new(
908 format!("${} requires brackets", name),
909 Span::new(start, self.pos),
910 ErrorKind::BracketUsage,
911 ));
912 }
913 return AstNode::Text {
914 content: self.slice(start, self.pos).to_string(),
915 span: Span::new(start, self.pos),
916 };
917 }
918
919 let bracket_start = self.pos;
920 self.advance();
921 if let Some(end) = self.find_matching_bracket(bracket_start) {
922 let content = self.slice(bracket_start + 1, end).to_string();
923 self.pos = end + 1;
924 AstNode::Escaped {
925 content,
926 span: Span::new(start, self.pos),
927 }
928 } else {
929 if self.config.validate_brackets {
930 self.errors.push(ParseError::syntax(
931 format!("Unclosed '[' for ${}", name),
932 Span::new(start, self.source.len()),
933 ));
934 }
935 self.pos = self.source.len();
936 AstNode::Escaped {
937 content: String::new(),
938 span: Span::new(start, self.pos),
939 }
940 }
941 }
942
943 fn parse_function_arguments(&mut self) -> Option<Vec<Argument>> {
944 let bracket_start = self.pos;
945 self.advance();
946 if let Some(end) = self.find_matching_bracket(bracket_start) {
947 let args_content = self.slice(bracket_start + 1, end);
948 let parsed_args = self.parse_arguments(args_content, bracket_start + 1);
949 self.pos = end + 1;
950 Some(parsed_args)
951 } else {
952 if self.config.validate_brackets {
953 self.errors.push(ParseError::syntax(
954 "Unclosed function arguments",
955 Span::new(bracket_start, bracket_start + 1),
956 ));
957 }
958 None
959 }
960 }
961
962 fn parse_arguments(&mut self, content: &str, base_offset: usize) -> Vec<Argument> {
963 let mut args = Vec::new();
964 let mut current = String::new();
965 let mut depth = 0;
966 let bytes = content.as_bytes();
967 let mut i = 0;
968 let mut arg_start = 0;
969
970 while i < bytes.len() {
971 if bytes[i] == b'$' && depth == 0 {
972 if let Some(esc_end) = self.find_escape_function_end(content, i) {
973 current.push_str(&content[i..=esc_end]);
974 i = esc_end + 1;
975 continue;
976 }
977 }
978
979 if bytes[i] == b'\\' {
980 if let Some(next) = bytes.get(i + 1) {
981 if matches!(*next, b'`' | b'$' | b'[' | b']' | b';' | b'\\') {
982 current.push_str(&content[i..i + 2]);
983 i += 2;
984 continue;
985 }
986 }
987 current.push('\\');
988 i += 1;
989 continue;
990 }
991
992 match bytes[i] {
993 b'[' if !is_escaped(content, i) && self.is_function_bracket(content, i) => {
994 depth += 1;
995 current.push('[');
996 }
997 b']' if depth > 0 && !is_escaped(content, i) => {
998 depth -= 1;
999 current.push(']');
1000 }
1001 b';' if depth == 0 => {
1002 let arg_offset = base_offset + arg_start;
1003 let parts = self.parse_argument_parts(¤t, arg_offset);
1004 args.push(Argument {
1005 parts,
1006 span: Span::new(arg_offset, arg_offset + current.len()),
1007 });
1008 current.clear();
1009 arg_start = i + 1;
1010 }
1011 _ => current.push(bytes[i] as char),
1012 }
1013 i += 1;
1014 }
1015
1016 if !current.is_empty() || !args.is_empty() {
1017 let arg_offset = base_offset + arg_start;
1018 let parts = self.parse_argument_parts(¤t, arg_offset);
1019 args.push(Argument {
1020 parts,
1021 span: Span::new(arg_offset, arg_offset + current.len()),
1022 });
1023 }
1024 args
1025 }
1026
1027 fn parse_argument_parts(&mut self, content: &str, offset: usize) -> SmallVec<[AstNode; 4]> {
1028 if content.is_empty() {
1029 let mut parts = SmallVec::new();
1030 parts.push(AstNode::Text {
1031 content: String::new(),
1032 span: Span::new(offset, offset),
1033 });
1034 return parts;
1035 }
1036
1037 #[cfg(feature = "validation")]
1038 let inner_parser = if self.config.is_enabled() {
1039 if let Some(ref metadata) = self.metadata {
1040 Parser::with_validation(content, self.config.clone(), metadata.clone())
1041 } else {
1042 Parser::with_config(content, self.config.clone())
1043 }
1044 } else {
1045 Parser::new(content)
1046 };
1047
1048 #[cfg(not(feature = "validation"))]
1049 let inner_parser = Parser::new(content);
1050
1051 let (ast, errors) = inner_parser.parse_forge_script();
1052
1053 let nodes = if let AstNode::Program { mut body, .. } = ast {
1054 for node in &mut body {
1055 node.offset_spans(offset);
1056 }
1057 body
1058 } else {
1059 vec![ast]
1060 };
1061
1062 for mut error in errors {
1063 error.span.offset(offset);
1064 self.errors.push(error);
1065 }
1066
1067 let mut parts = SmallVec::new();
1068 for node in nodes {
1069 parts.push(node);
1070 }
1071 parts
1072 }
1073
1074 fn find_matching_bracket(&self, open_pos: usize) -> Option<usize> {
1079 let mut depth = 1;
1080 let mut p = open_pos + 1;
1081 while p < self.bytes.len() {
1082 if self.bytes[p] == b'\\' {
1083 p += 2;
1084 continue;
1085 }
1086 if self.bytes[p] == b'[' && !is_escaped(self.source, p) {
1087 depth += 1;
1088 } else if self.bytes[p] == b']' && !is_escaped(self.source, p) {
1089 depth -= 1;
1090 if depth == 0 {
1091 return Some(p);
1092 }
1093 }
1094 p += 1;
1095 }
1096 None
1097 }
1098
1099 fn find_matching_brace(&self, open_pos: usize) -> Option<usize> {
1100 let mut depth = 1;
1101 let mut p = open_pos + 1;
1102 while p < self.bytes.len() {
1103 match self.bytes[p] {
1104 b'{' => depth += 1,
1105 b'}' => {
1106 depth -= 1;
1107 if depth == 0 {
1108 return Some(p);
1109 }
1110 }
1111 _ => {}
1112 }
1113 p += 1;
1114 }
1115 None
1116 }
1117
1118 fn is_function_bracket(&self, content: &str, idx: usize) -> bool {
1119 if idx == 0 || content.as_bytes().get(idx) != Some(&b'[') {
1120 return false;
1121 }
1122 let bytes = content.as_bytes();
1123 let mut i = idx;
1124 while i > 0 && (bytes[i - 1].is_ascii_alphanumeric() || bytes[i - 1] == b'_') {
1125 i -= 1;
1126 }
1127 while i > 0 && matches!(bytes[i - 1], b'!' | b'#' | b']') {
1128 if bytes[i - 1] == b']' {
1129 let mut d = 1;
1130 while i > 1 && d > 0 {
1131 i -= 1;
1132 if bytes[i - 1] == b']' {
1133 d += 1;
1134 } else if bytes[i - 1] == b'[' {
1135 d -= 1;
1136 }
1137 }
1138 if i < 2 || bytes[i - 2] != b'@' {
1139 return false;
1140 }
1141 i -= 2;
1142 } else {
1143 i -= 1;
1144 }
1145 }
1146 i > 0 && bytes[i - 1] == b'$' && (i == 1 || bytes[i - 2] != b'\\')
1147 }
1148
1149 fn find_escape_function_end(&self, content: &str, start: usize) -> Option<usize> {
1150 let bytes = content.as_bytes();
1151 let mut p = start + 1;
1152 while p < bytes.len() && matches!(bytes[p], b'!' | b'#') {
1153 p += 1;
1154 }
1155 let name_start = p;
1156 while p < bytes.len() && (bytes[p].is_ascii_alphanumeric() || bytes[p] == b'_') {
1157 p += 1;
1158 }
1159 if !self.is_escape_function(&content[name_start..p]) || bytes.get(p) != Some(&b'[') {
1160 return None;
1161 }
1162 let mut depth = 1;
1163 p += 1;
1164 while p < bytes.len() {
1165 if bytes[p] == b'\\' {
1166 p += 2;
1167 continue;
1168 }
1169 if bytes[p] == b'[' && !is_escaped(content, p) {
1170 depth += 1;
1171 } else if bytes[p] == b']' && !is_escaped(content, p) {
1172 depth -= 1;
1173 if depth == 0 {
1174 return Some(p);
1175 }
1176 }
1177 p += 1;
1178 }
1179 None
1180 }
1181}
1182
1183pub fn parse(source: &str) -> (AstNode, Vec<ParseError>) {
1189 Parser::new(source).parse()
1190}
1191
1192pub fn parse_with_errors(source: &str) -> Result<AstNode, Vec<ParseError>> {
1194 let (ast, errors) = parse(source);
1195 if errors.is_empty() {
1196 Ok(ast)
1197 } else {
1198 Err(errors)
1199 }
1200}
1201
1202#[cfg(feature = "validation")]
1204pub fn parse_with_config(source: &str, config: ValidationConfig) -> (AstNode, Vec<ParseError>) {
1205 Parser::with_config(source, config).parse()
1206}
1207
1208#[cfg(feature = "validation")]
1210pub fn parse_with_validation(
1211 source: &str,
1212 config: ValidationConfig,
1213 metadata: Arc<MetadataManager>,
1214) -> (AstNode, Vec<ParseError>) {
1215 Parser::with_validation(source, config, metadata).parse()
1216}
1217
1218#[cfg(feature = "validation")]
1220pub fn parse_strict(source: &str, metadata: Arc<MetadataManager>) -> (AstNode, Vec<ParseError>) {
1221 Parser::with_validation(source, ValidationConfig::strict(), metadata).parse()
1222}