1use crate::lex::lex;
2use crate::SyntaxKind;
3use crate::SyntaxKind::*;
4use rowan::ast::AstNode;
5use std::str::FromStr;
6
7#[derive(Debug)]
8pub enum Error {
10 Io(std::io::Error),
12
13 Parse(ParseError),
15}
16
17impl std::fmt::Display for Error {
18 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
19 match &self {
20 Error::Io(e) => write!(f, "IO error: {}", e),
21 Error::Parse(e) => write!(f, "Parse error: {}", e),
22 }
23 }
24}
25
26impl From<std::io::Error> for Error {
27 fn from(e: std::io::Error) -> Self {
28 Error::Io(e)
29 }
30}
31
32impl std::error::Error for Error {}
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub struct ParseError {
37 pub errors: Vec<ErrorInfo>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct ErrorInfo {
44 pub message: String,
46 pub line: usize,
48 pub context: String,
50}
51
52impl std::fmt::Display for ParseError {
53 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54 for err in &self.errors {
55 writeln!(f, "Error at line {}: {}", err.line, err.message)?;
56 writeln!(f, "{}| {}", err.line, err.context)?;
57 }
58 Ok(())
59 }
60}
61
62impl std::error::Error for ParseError {}
63
64impl From<ParseError> for Error {
65 fn from(e: ParseError) -> Self {
66 Error::Parse(e)
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
74pub enum Lang {}
75impl rowan::Language for Lang {
76 type Kind = SyntaxKind;
77 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
78 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
79 }
80 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
81 kind.into()
82 }
83}
84
85use rowan::GreenNode;
88
89use rowan::GreenNodeBuilder;
93
94#[derive(Debug)]
97pub(crate) struct Parse {
98 pub(crate) green_node: GreenNode,
99 #[allow(unused)]
100 pub(crate) errors: Vec<ErrorInfo>,
101}
102
103pub(crate) fn parse(text: &str) -> Parse {
104 struct Parser {
105 tokens: Vec<(SyntaxKind, String)>,
108 builder: GreenNodeBuilder<'static>,
110 errors: Vec<ErrorInfo>,
113 original_text: String,
115 }
116
117 impl Parser {
118 fn error(&mut self, msg: String) {
119 self.builder.start_node(ERROR.into());
120
121 let (line, context) = if self.current() == Some(INDENT) {
122 let lines: Vec<&str> = self.original_text.lines().collect();
124 let tab_line = lines
125 .iter()
126 .enumerate()
127 .find(|(_, line)| line.starts_with('\t'))
128 .map(|(i, _)| i + 1)
129 .unwrap_or(1);
130
131 let next_line = tab_line + 1;
133 if next_line <= lines.len() {
134 (next_line, lines[next_line - 1].to_string())
135 } else {
136 (tab_line, lines[tab_line - 1].to_string())
137 }
138 } else {
139 let line = self.get_line_number_for_position(self.tokens.len());
140 (line, self.get_context_for_line(line))
141 };
142
143 let message = if self.current() == Some(INDENT) && !msg.contains("indented") {
144 if !self.tokens.is_empty() && self.tokens[self.tokens.len() - 1].0 == IDENTIFIER {
145 "expected ':'".to_string()
146 } else {
147 "indented line not part of a rule".to_string()
148 }
149 } else {
150 msg
151 };
152
153 self.errors.push(ErrorInfo {
154 message,
155 line,
156 context,
157 });
158
159 if self.current().is_some() {
160 self.bump();
161 }
162 self.builder.finish_node();
163 }
164
165 fn get_line_number_for_position(&self, position: usize) -> usize {
166 if position >= self.tokens.len() {
167 return self.original_text.matches('\n').count() + 1;
168 }
169
170 self.tokens[0..position]
172 .iter()
173 .filter(|(kind, _)| *kind == NEWLINE)
174 .count()
175 + 1
176 }
177
178 fn get_context_for_line(&self, line_number: usize) -> String {
179 self.original_text
180 .lines()
181 .nth(line_number - 1)
182 .unwrap_or("")
183 .to_string()
184 }
185
186 fn parse_recipe_line(&mut self) {
187 self.builder.start_node(RECIPE.into());
188
189 if self.current() != Some(INDENT) {
191 self.error("recipe line must start with a tab".to_string());
192 self.builder.finish_node();
193 return;
194 }
195 self.bump();
196
197 while self.current().is_some() && self.current() != Some(NEWLINE) {
200 self.bump();
201 }
202
203 if self.current() == Some(NEWLINE) {
205 self.bump();
206 }
207
208 self.builder.finish_node();
209 }
210
211 fn parse_rule_target(&mut self) -> bool {
212 match self.current() {
213 Some(IDENTIFIER) => {
214 if self.is_archive_member() {
216 self.parse_archive_member();
217 } else {
218 self.bump();
219 }
220 true
221 }
222 Some(DOLLAR) => {
223 self.parse_variable_reference();
224 true
225 }
226 _ => {
227 self.error("expected rule target".to_string());
228 false
229 }
230 }
231 }
232
233 fn is_archive_member(&self) -> bool {
234 if self.tokens.len() < 2 {
237 return false;
238 }
239
240 let current_is_identifier = self.current() == Some(IDENTIFIER);
242 let next_is_lparen =
243 self.tokens.len() > 1 && self.tokens[self.tokens.len() - 2].0 == LPAREN;
244
245 current_is_identifier && next_is_lparen
246 }
247
248 fn parse_archive_member(&mut self) {
249 if self.current() == Some(IDENTIFIER) {
260 self.bump();
261 }
262
263 if self.current() == Some(LPAREN) {
265 self.bump();
266
267 self.builder.start_node(ARCHIVE_MEMBERS.into());
269
270 while self.current().is_some() && self.current() != Some(RPAREN) {
272 match self.current() {
273 Some(IDENTIFIER) | Some(TEXT) => {
274 self.builder.start_node(ARCHIVE_MEMBER.into());
276 self.bump();
277 self.builder.finish_node();
278 }
279 Some(WHITESPACE) => self.bump(),
280 Some(DOLLAR) => {
281 self.builder.start_node(ARCHIVE_MEMBER.into());
283 self.parse_variable_reference();
284 self.builder.finish_node();
285 }
286 _ => break,
287 }
288 }
289
290 self.builder.finish_node();
292
293 if self.current() == Some(RPAREN) {
295 self.bump();
296 } else {
297 self.error("expected ')' to close archive member".to_string());
298 }
299 }
300 }
301
302 fn parse_rule_dependencies(&mut self) {
303 self.builder.start_node(EXPR.into());
304 while self.current().is_some() && self.current() != Some(NEWLINE) {
305 match self.current() {
306 Some(IDENTIFIER) if self.is_archive_member() => {
307 self.parse_archive_member();
308 }
309 _ => self.bump(),
310 }
311 }
312 self.builder.finish_node();
313 }
314
315 fn parse_rule_recipes(&mut self) {
316 loop {
317 match self.current() {
318 Some(INDENT) => {
319 self.parse_recipe_line();
320 }
321 Some(NEWLINE) => {
322 self.bump();
323 break;
324 }
325 _ => break,
326 }
327 }
328 }
329
330 fn find_and_consume_colon(&mut self) -> bool {
331 self.skip_ws();
333
334 if self.current() == Some(OPERATOR) && self.tokens.last().unwrap().1 == ":" {
336 self.bump();
337 return true;
338 }
339
340 let has_colon = self
342 .tokens
343 .iter()
344 .rev()
345 .any(|(kind, text)| *kind == OPERATOR && text == ":");
346
347 if has_colon {
348 while self.current().is_some() {
350 if self.current() == Some(OPERATOR)
351 && self.tokens.last().map(|(_, text)| text.as_str()) == Some(":")
352 {
353 self.bump();
354 return true;
355 }
356 self.bump();
357 }
358 }
359
360 self.error("expected ':'".to_string());
361 false
362 }
363
364 fn parse_rule(&mut self) {
365 self.builder.start_node(RULE.into());
366
367 self.skip_ws();
369 let has_target = self.parse_rule_target();
370
371 let has_colon = if has_target {
373 self.find_and_consume_colon()
374 } else {
375 false
376 };
377
378 if has_target && has_colon {
380 self.skip_ws();
381 self.parse_rule_dependencies();
382 self.expect_eol();
383
384 self.parse_rule_recipes();
386 }
387
388 self.builder.finish_node();
389 }
390
391 fn parse_comment(&mut self) {
392 if self.current() == Some(COMMENT) {
393 self.bump(); if self.current() == Some(NEWLINE) {
397 self.bump(); } else if self.current() == Some(WHITESPACE) {
399 self.skip_ws();
401 if self.current() == Some(NEWLINE) {
402 self.bump();
403 }
404 }
405 } else {
407 self.error("expected comment".to_string());
408 }
409 }
410
411 fn parse_assignment(&mut self) {
412 self.builder.start_node(VARIABLE.into());
413
414 self.skip_ws();
416 if self.current() == Some(IDENTIFIER) && self.tokens.last().unwrap().1 == "export" {
417 self.bump();
418 self.skip_ws();
419 }
420
421 match self.current() {
423 Some(IDENTIFIER) => self.bump(),
424 Some(DOLLAR) => self.parse_variable_reference(),
425 _ => {
426 self.error("expected variable name".to_string());
427 self.builder.finish_node();
428 return;
429 }
430 }
431
432 self.skip_ws();
434 match self.current() {
435 Some(OPERATOR) => {
436 let op = &self.tokens.last().unwrap().1;
437 if ["=", ":=", "::=", ":::=", "+=", "?=", "!="].contains(&op.as_str()) {
438 self.bump();
439 self.skip_ws();
440
441 self.builder.start_node(EXPR.into());
443 while self.current().is_some() && self.current() != Some(NEWLINE) {
444 self.bump();
445 }
446 self.builder.finish_node();
447
448 if self.current() == Some(NEWLINE) {
450 self.bump();
451 } else {
452 self.error("expected newline after variable value".to_string());
453 }
454 } else {
455 self.error(format!("invalid assignment operator: {}", op));
456 }
457 }
458 _ => self.error("expected assignment operator".to_string()),
459 }
460
461 self.builder.finish_node();
462 }
463
464 fn parse_variable_reference(&mut self) {
465 self.builder.start_node(EXPR.into());
466 self.bump(); if self.current() == Some(LPAREN) {
469 self.bump(); let mut is_function = false;
473
474 if self.current() == Some(IDENTIFIER) {
475 let function_name = &self.tokens.last().unwrap().1;
476 let known_functions = [
478 "shell", "wildcard", "call", "eval", "file", "abspath", "dir",
479 ];
480 if known_functions.contains(&function_name.as_str()) {
481 is_function = true;
482 }
483 }
484
485 if is_function {
486 self.bump();
488
489 self.consume_balanced_parens(1);
491 } else {
492 self.parse_parenthesized_expr_internal(true);
494 }
495 } else {
496 self.error("expected ( after $ in variable reference".to_string());
497 }
498
499 self.builder.finish_node();
500 }
501
502 fn parse_parenthesized_expr(&mut self) {
504 self.builder.start_node(EXPR.into());
505
506 if self.current() != Some(LPAREN) {
507 self.error("expected opening parenthesis".to_string());
508 self.builder.finish_node();
509 return;
510 }
511
512 self.bump(); self.parse_parenthesized_expr_internal(false);
514 self.builder.finish_node();
515 }
516
517 fn parse_parenthesized_expr_internal(&mut self, is_variable_ref: bool) {
519 let mut paren_count = 1;
520
521 while paren_count > 0 && self.current().is_some() {
522 match self.current() {
523 Some(LPAREN) => {
524 paren_count += 1;
525 self.bump();
526 self.builder.start_node(EXPR.into());
528 }
529 Some(RPAREN) => {
530 paren_count -= 1;
531 self.bump();
532 if paren_count > 0 {
533 self.builder.finish_node();
534 }
535 }
536 Some(QUOTE) => {
537 self.parse_quoted_string();
539 }
540 Some(DOLLAR) => {
541 self.parse_variable_reference();
543 }
544 Some(_) => self.bump(),
545 None => {
546 self.error(if is_variable_ref {
547 "unclosed variable reference".to_string()
548 } else {
549 "unclosed parenthesis".to_string()
550 });
551 break;
552 }
553 }
554 }
555
556 if !is_variable_ref {
557 self.skip_ws();
558 self.expect_eol();
559 }
560 }
561
562 fn parse_quoted_string(&mut self) {
564 self.bump(); while !self.is_at_eof() && self.current() != Some(QUOTE) {
566 self.bump();
567 }
568 if self.current() == Some(QUOTE) {
569 self.bump();
570 }
571 }
572
573 fn parse_conditional_keyword(&mut self) -> Option<String> {
574 if self.current() != Some(IDENTIFIER) {
575 self.error(
576 "expected conditional keyword (ifdef, ifndef, ifeq, or ifneq)".to_string(),
577 );
578 return None;
579 }
580
581 let token = self.tokens.last().unwrap().1.clone();
582 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&token.as_str()) {
583 self.error(format!("unknown conditional directive: {}", token));
584 return None;
585 }
586
587 self.bump();
588 Some(token)
589 }
590
591 fn parse_simple_condition(&mut self) {
592 self.builder.start_node(EXPR.into());
593
594 self.skip_ws();
596
597 let mut found_var = false;
599
600 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
601 match self.current() {
602 Some(WHITESPACE) => self.skip_ws(),
603 Some(DOLLAR) => {
604 found_var = true;
605 self.parse_variable_reference();
606 }
607 Some(_) => {
608 found_var = true;
610 self.bump();
611 }
612 None => break,
613 }
614 }
615
616 if !found_var {
617 self.error("expected condition after conditional directive".to_string());
619 }
620
621 self.builder.finish_node();
622
623 if self.current() == Some(NEWLINE) {
625 self.bump();
626 } else if !self.is_at_eof() {
627 self.skip_until_newline();
628 }
629 }
630
631 fn is_conditional_directive(&self, token: &str) -> bool {
633 token == "ifdef"
634 || token == "ifndef"
635 || token == "ifeq"
636 || token == "ifneq"
637 || token == "else"
638 || token == "elif"
639 || token == "endif"
640 }
641
642 fn handle_conditional_token(&mut self, token: &str, depth: &mut usize) -> bool {
644 match token {
645 "ifdef" | "ifndef" | "ifeq" | "ifneq" => {
646 *depth += 1;
647 self.parse_conditional();
648 true
649 }
650 "else" | "elif" => {
651 if *depth == 0 {
653 self.error(format!("{} without matching if", token));
654 self.bump();
656 false
657 } else {
658 self.bump();
660
661 if token == "elif" {
663 self.skip_ws();
664
665 if self.current() == Some(IDENTIFIER) {
667 let next_token = &self.tokens.last().unwrap().1;
668 if next_token == "ifeq"
669 || next_token == "ifdef"
670 || next_token == "ifndef"
671 || next_token == "ifneq"
672 {
673 match next_token.as_str() {
675 "ifdef" | "ifndef" => {
676 self.bump(); self.skip_ws();
678 self.parse_simple_condition();
679 }
680 "ifeq" | "ifneq" => {
681 self.bump(); self.skip_ws();
683 self.parse_parenthesized_expr();
684 }
685 _ => unreachable!(),
686 }
687 } else {
688 self.builder.start_node(EXPR.into());
690 while self.current().is_some()
692 && self.current() != Some(NEWLINE)
693 {
694 self.bump();
695 }
696 self.builder.finish_node();
697 if self.current() == Some(NEWLINE) {
698 self.bump();
699 }
700 }
701 } else {
702 self.builder.start_node(EXPR.into());
704 while self.current().is_some() && self.current() != Some(NEWLINE) {
706 self.bump();
707 }
708 self.builder.finish_node();
709 if self.current() == Some(NEWLINE) {
710 self.bump();
711 }
712 }
713 } else {
714 self.expect_eol();
716 }
717 true
718 }
719 }
720 "endif" => {
721 if *depth == 0 {
723 self.error("endif without matching if".to_string());
724 self.bump();
726 false
727 } else {
728 *depth -= 1;
729 self.bump();
731
732 self.skip_ws();
734
735 if self.current() == Some(COMMENT) {
740 self.parse_comment();
741 } else if self.current() == Some(NEWLINE) {
742 self.bump();
743 } else if self.current() == Some(WHITESPACE) {
744 self.skip_ws();
746 if self.current() == Some(NEWLINE) {
747 self.bump();
748 }
749 } else if !self.is_at_eof() {
751 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
754 self.bump();
755 }
756 if self.current() == Some(NEWLINE) {
757 self.bump();
758 }
759 }
760 true
763 }
764 }
765 _ => false,
766 }
767 }
768
769 fn parse_conditional(&mut self) {
770 self.builder.start_node(CONDITIONAL.into());
771
772 let Some(token) = self.parse_conditional_keyword() else {
774 self.skip_until_newline();
775 self.builder.finish_node();
776 return;
777 };
778
779 self.skip_ws();
781
782 match token.as_str() {
784 "ifdef" | "ifndef" => {
785 self.parse_simple_condition();
786 }
787 "ifeq" | "ifneq" => {
788 self.parse_parenthesized_expr();
789 }
790 _ => unreachable!("Invalid conditional token"),
791 }
792
793 self.skip_ws();
795 if self.current() == Some(COMMENT) {
796 self.parse_comment();
797 } else {
798 self.expect_eol();
799 }
800
801 let mut depth = 1;
803
804 let mut position_count = std::collections::HashMap::<usize, usize>::new();
806 let max_repetitions = 15; while depth > 0 && !self.is_at_eof() {
809 let current_pos = self.tokens.len();
811 *position_count.entry(current_pos).or_insert(0) += 1;
812
813 if position_count.get(¤t_pos).unwrap() > &max_repetitions {
816 break;
819 }
820
821 match self.current() {
822 None => {
823 self.error("unterminated conditional (missing endif)".to_string());
824 break;
825 }
826 Some(IDENTIFIER) => {
827 let token = self.tokens.last().unwrap().1.clone();
828 if !self.handle_conditional_token(&token, &mut depth) {
829 if token == "include" || token == "-include" || token == "sinclude" {
830 self.parse_include();
831 } else {
832 self.parse_normal_content();
833 }
834 }
835 }
836 Some(INDENT) => self.parse_recipe_line(),
837 Some(WHITESPACE) => self.bump(),
838 Some(COMMENT) => self.parse_comment(),
839 Some(NEWLINE) => self.bump(),
840 Some(DOLLAR) => self.parse_normal_content(),
841 Some(QUOTE) => self.parse_quoted_string(),
842 Some(_) => {
843 self.bump();
845 }
846 }
847 }
848
849 self.builder.finish_node();
850 }
851
852 fn parse_normal_content(&mut self) {
854 self.skip_ws();
856
857 if self.is_assignment_line() {
859 self.parse_assignment();
860 } else {
861 self.parse_rule();
863 }
864 }
865
866 fn parse_include(&mut self) {
867 self.builder.start_node(INCLUDE.into());
868
869 if self.current() != Some(IDENTIFIER)
871 || (!["include", "-include", "sinclude"]
872 .contains(&self.tokens.last().unwrap().1.as_str()))
873 {
874 self.error("expected include directive".to_string());
875 self.builder.finish_node();
876 return;
877 }
878 self.bump();
879 self.skip_ws();
880
881 self.builder.start_node(EXPR.into());
883 let mut found_path = false;
884
885 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
886 match self.current() {
887 Some(WHITESPACE) => self.skip_ws(),
888 Some(DOLLAR) => {
889 found_path = true;
890 self.parse_variable_reference();
891 }
892 Some(_) => {
893 found_path = true;
895 self.bump();
896 }
897 None => break,
898 }
899 }
900
901 if !found_path {
902 self.error("expected file path after include".to_string());
903 }
904
905 self.builder.finish_node();
906
907 if self.current() == Some(NEWLINE) {
909 self.bump();
910 } else if !self.is_at_eof() {
911 self.error("expected newline after include".to_string());
912 self.skip_until_newline();
913 }
914
915 self.builder.finish_node();
916 }
917
918 fn parse_identifier_token(&mut self) -> bool {
919 let token = &self.tokens.last().unwrap().1;
920
921 if token.starts_with("%") {
923 self.parse_rule();
924 return true;
925 }
926
927 if token.starts_with("if") {
928 self.parse_conditional();
929 return true;
930 }
931
932 if token == "include" || token == "-include" || token == "sinclude" {
933 self.parse_include();
934 return true;
935 }
936
937 self.parse_normal_content();
939 true
940 }
941
942 fn parse_token(&mut self) -> bool {
943 match self.current() {
944 None => false,
945 Some(IDENTIFIER) => {
946 let token = &self.tokens.last().unwrap().1;
947 if self.is_conditional_directive(token) {
948 self.parse_conditional();
949 true
950 } else {
951 self.parse_identifier_token()
952 }
953 }
954 Some(DOLLAR) => {
955 self.parse_normal_content();
956 true
957 }
958 Some(NEWLINE) => {
959 self.bump();
960 true
961 }
962 Some(COMMENT) => {
963 self.parse_comment();
964 true
965 }
966 Some(WHITESPACE) => {
967 if self.is_end_of_file_or_newline_after_whitespace() {
969 self.skip_ws();
972 return true;
973 }
974
975 let look_ahead_pos = self.tokens.len().saturating_sub(1);
978 let mut is_documentation_or_help = false;
979
980 if look_ahead_pos > 0 {
981 let next_token = &self.tokens[look_ahead_pos - 1];
982 if next_token.0 == IDENTIFIER
985 || next_token.0 == COMMENT
986 || next_token.0 == TEXT
987 {
988 is_documentation_or_help = true;
989 }
990 }
991
992 if is_documentation_or_help {
993 self.skip_ws();
996 while self.current().is_some() && self.current() != Some(NEWLINE) {
997 self.bump();
998 }
999 if self.current() == Some(NEWLINE) {
1000 self.bump();
1001 }
1002 } else {
1003 self.skip_ws();
1004 }
1005 true
1006 }
1007 Some(INDENT) => {
1008 #[cfg(test)]
1013 {
1014 let is_in_test = self.original_text.lines().count() < 20;
1017 let tokens_as_str = self
1018 .tokens
1019 .iter()
1020 .rev()
1021 .take(10)
1022 .map(|(_kind, text)| text.as_str())
1023 .collect::<Vec<_>>()
1024 .join(" ");
1025
1026 let in_conditional = tokens_as_str.contains("ifdef")
1028 || tokens_as_str.contains("ifndef")
1029 || tokens_as_str.contains("ifeq")
1030 || tokens_as_str.contains("ifneq")
1031 || tokens_as_str.contains("else")
1032 || tokens_as_str.contains("endif");
1033
1034 if is_in_test && !in_conditional {
1035 self.error("indented line not part of a rule".to_string());
1036 }
1037 }
1038
1039 self.bump();
1041
1042 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1044 self.bump();
1045 }
1046 if self.current() == Some(NEWLINE) {
1047 self.bump();
1048 }
1049 true
1050 }
1051 Some(kind) => {
1052 self.error(format!("unexpected token {:?}", kind));
1053 self.bump();
1054 true
1055 }
1056 }
1057 }
1058
1059 fn parse(mut self) -> Parse {
1060 self.builder.start_node(ROOT.into());
1061
1062 while self.parse_token() {}
1063
1064 self.builder.finish_node();
1065
1066 Parse {
1067 green_node: self.builder.finish(),
1068 errors: self.errors,
1069 }
1070 }
1071
1072 fn is_assignment_line(&mut self) -> bool {
1074 let assignment_ops = ["=", ":=", "::=", ":::=", "+=", "?=", "!="];
1075 let mut pos = self.tokens.len().saturating_sub(1);
1076 let mut seen_identifier = false;
1077 let mut seen_export = false;
1078
1079 while pos > 0 {
1080 let (kind, text) = &self.tokens[pos];
1081
1082 match kind {
1083 NEWLINE => break,
1084 IDENTIFIER if text == "export" => seen_export = true,
1085 IDENTIFIER if !seen_identifier => seen_identifier = true,
1086 OPERATOR if assignment_ops.contains(&text.as_str()) => {
1087 return seen_identifier || seen_export
1088 }
1089 OPERATOR if text == ":" => return false, WHITESPACE => (),
1091 _ if seen_export => return true, _ => return false,
1093 }
1094 pos = pos.saturating_sub(1);
1095 }
1096 false
1097 }
1098
1099 fn bump(&mut self) {
1101 let (kind, text) = self.tokens.pop().unwrap();
1102 self.builder.token(kind.into(), text.as_str());
1103 }
1104 fn current(&self) -> Option<SyntaxKind> {
1106 self.tokens.last().map(|(kind, _)| *kind)
1107 }
1108
1109 fn expect_eol(&mut self) {
1110 self.skip_ws();
1112
1113 match self.current() {
1114 Some(NEWLINE) => {
1115 self.bump();
1116 }
1117 None => {
1118 }
1120 n => {
1121 self.error(format!("expected newline, got {:?}", n));
1122 self.skip_until_newline();
1124 }
1125 }
1126 }
1127
1128 fn is_at_eof(&self) -> bool {
1130 self.current().is_none()
1131 }
1132
1133 fn is_at_eof_or_only_whitespace(&self) -> bool {
1135 if self.is_at_eof() {
1136 return true;
1137 }
1138
1139 self.tokens
1141 .iter()
1142 .rev()
1143 .all(|(kind, _)| matches!(*kind, WHITESPACE | NEWLINE))
1144 }
1145
1146 fn skip_ws(&mut self) {
1147 while self.current() == Some(WHITESPACE) {
1148 self.bump()
1149 }
1150 }
1151
1152 fn skip_until_newline(&mut self) {
1153 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1154 self.bump();
1155 }
1156 if self.current() == Some(NEWLINE) {
1157 self.bump();
1158 }
1159 }
1160
1161 fn consume_balanced_parens(&mut self, start_paren_count: usize) -> usize {
1163 let mut paren_count = start_paren_count;
1164
1165 while paren_count > 0 && self.current().is_some() {
1166 match self.current() {
1167 Some(LPAREN) => {
1168 paren_count += 1;
1169 self.bump();
1170 }
1171 Some(RPAREN) => {
1172 paren_count -= 1;
1173 self.bump();
1174 if paren_count == 0 {
1175 break;
1176 }
1177 }
1178 Some(DOLLAR) => {
1179 self.parse_variable_reference();
1181 }
1182 Some(_) => self.bump(),
1183 None => {
1184 self.error("unclosed parenthesis".to_string());
1185 break;
1186 }
1187 }
1188 }
1189
1190 paren_count
1191 }
1192
1193 fn is_end_of_file_or_newline_after_whitespace(&self) -> bool {
1195 if self.is_at_eof_or_only_whitespace() {
1197 return true;
1198 }
1199
1200 if self.tokens.len() <= 1 {
1202 return true;
1203 }
1204
1205 false
1206 }
1207
1208 #[cfg(test)]
1210 fn is_in_test_environment(&self) -> bool {
1211 self.original_text.lines().count() < 20
1214 }
1215 }
1216
1217 let mut tokens = lex(text);
1218 tokens.reverse();
1219 Parser {
1220 tokens,
1221 builder: GreenNodeBuilder::new(),
1222 errors: Vec::new(),
1223 original_text: text.to_string(),
1224 }
1225 .parse()
1226}
1227
1228type SyntaxNode = rowan::SyntaxNode<Lang>;
1234#[allow(unused)]
1235type SyntaxToken = rowan::SyntaxToken<Lang>;
1236#[allow(unused)]
1237type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
1238
1239impl Parse {
1240 fn syntax(&self) -> SyntaxNode {
1241 SyntaxNode::new_root_mut(self.green_node.clone())
1242 }
1243
1244 fn root(&self) -> Makefile {
1245 Makefile::cast(self.syntax()).unwrap()
1246 }
1247}
1248
1249macro_rules! ast_node {
1250 ($ast:ident, $kind:ident) => {
1251 #[derive(PartialEq, Eq, Hash)]
1252 #[repr(transparent)]
1253 pub struct $ast(SyntaxNode);
1255
1256 impl AstNode for $ast {
1257 type Language = Lang;
1258
1259 fn can_cast(kind: SyntaxKind) -> bool {
1260 kind == $kind
1261 }
1262
1263 fn cast(syntax: SyntaxNode) -> Option<Self> {
1264 if Self::can_cast(syntax.kind()) {
1265 Some(Self(syntax))
1266 } else {
1267 None
1268 }
1269 }
1270
1271 fn syntax(&self) -> &SyntaxNode {
1272 &self.0
1273 }
1274 }
1275
1276 impl core::fmt::Display for $ast {
1277 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
1278 write!(f, "{}", self.0.text())
1279 }
1280 }
1281 };
1282}
1283
1284ast_node!(Makefile, ROOT);
1285ast_node!(Rule, RULE);
1286ast_node!(Identifier, IDENTIFIER);
1287ast_node!(VariableDefinition, VARIABLE);
1288ast_node!(Include, INCLUDE);
1289ast_node!(ArchiveMembers, ARCHIVE_MEMBERS);
1290ast_node!(ArchiveMember, ARCHIVE_MEMBER);
1291
1292impl ArchiveMembers {
1293 pub fn archive_name(&self) -> Option<String> {
1295 for element in self.syntax().children_with_tokens() {
1297 if let Some(token) = element.as_token() {
1298 if token.kind() == IDENTIFIER {
1299 return Some(token.text().to_string());
1300 } else if token.kind() == LPAREN {
1301 break;
1303 }
1304 }
1305 }
1306 None
1307 }
1308
1309 pub fn members(&self) -> impl Iterator<Item = ArchiveMember> + '_ {
1311 self.syntax().children().filter_map(ArchiveMember::cast)
1312 }
1313
1314 pub fn member_names(&self) -> Vec<String> {
1316 self.members().map(|m| m.text()).collect()
1317 }
1318}
1319
1320impl ArchiveMember {
1321 pub fn text(&self) -> String {
1323 self.syntax().text().to_string().trim().to_string()
1324 }
1325}
1326
1327impl VariableDefinition {
1328 pub fn name(&self) -> Option<String> {
1330 self.syntax().children_with_tokens().find_map(|it| {
1331 it.as_token().and_then(|it| {
1332 if it.kind() == IDENTIFIER && it.text() != "export" {
1333 Some(it.text().to_string())
1334 } else {
1335 None
1336 }
1337 })
1338 })
1339 }
1340
1341 pub fn raw_value(&self) -> Option<String> {
1343 self.syntax()
1344 .children()
1345 .find(|it| it.kind() == EXPR)
1346 .map(|it| it.text().into())
1347 }
1348
1349 pub fn remove(&mut self) {
1360 let index = self.syntax().index();
1361 if let Some(parent) = self.syntax().parent() {
1362 parent.splice_children(index..index + 1, vec![]);
1363 }
1364 }
1365
1366 pub fn set_value(&mut self, new_value: &str) {
1379 let expr_index = self
1381 .syntax()
1382 .children()
1383 .find(|it| it.kind() == EXPR)
1384 .map(|it| it.index());
1385
1386 if let Some(expr_idx) = expr_index {
1387 let mut builder = GreenNodeBuilder::new();
1389 builder.start_node(EXPR.into());
1390 builder.token(IDENTIFIER.into(), new_value);
1391 builder.finish_node();
1392
1393 let new_expr = SyntaxNode::new_root_mut(builder.finish());
1394
1395 self.0
1397 .splice_children(expr_idx..expr_idx + 1, vec![new_expr.into()]);
1398 }
1399 }
1400}
1401
1402impl Makefile {
1403 pub fn new() -> Makefile {
1405 let mut builder = GreenNodeBuilder::new();
1406
1407 builder.start_node(ROOT.into());
1408 builder.finish_node();
1409
1410 let syntax = SyntaxNode::new_root_mut(builder.finish());
1411 Makefile(syntax)
1412 }
1413
1414 pub fn parse(text: &str) -> crate::Parse<Makefile> {
1416 crate::Parse::<Makefile>::parse_makefile(text)
1417 }
1418
1419 pub fn code(&self) -> String {
1421 self.syntax().text().to_string()
1422 }
1423
1424 pub fn is_root(&self) -> bool {
1426 self.syntax().kind() == ROOT
1427 }
1428
1429 pub fn read<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1431 let mut buf = String::new();
1432 r.read_to_string(&mut buf)?;
1433 Ok(buf.parse()?)
1434 }
1435
1436 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1438 let mut buf = String::new();
1439 r.read_to_string(&mut buf)?;
1440
1441 let parsed = parse(&buf);
1442 Ok(parsed.root())
1443 }
1444
1445 pub fn rules(&self) -> impl Iterator<Item = Rule> + '_ {
1454 self.syntax().children().filter_map(Rule::cast)
1455 }
1456
1457 pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1459 self.rules()
1460 .filter(move |rule| rule.targets().any(|t| t == target))
1461 }
1462
1463 pub fn variable_definitions(&self) -> impl Iterator<Item = VariableDefinition> {
1465 self.syntax()
1466 .children()
1467 .filter_map(VariableDefinition::cast)
1468 }
1469
1470 pub fn find_variable<'a>(
1485 &'a self,
1486 name: &'a str,
1487 ) -> impl Iterator<Item = VariableDefinition> + 'a {
1488 self.variable_definitions()
1489 .filter(move |var| var.name().as_deref() == Some(name))
1490 }
1491
1492 pub fn add_rule(&mut self, target: &str) -> Rule {
1502 let mut builder = GreenNodeBuilder::new();
1503 builder.start_node(RULE.into());
1504 builder.token(IDENTIFIER.into(), target);
1505 builder.token(OPERATOR.into(), ":");
1506 builder.token(NEWLINE.into(), "\n");
1507 builder.finish_node();
1508
1509 let syntax = SyntaxNode::new_root_mut(builder.finish());
1510 let pos = self.0.children_with_tokens().count();
1511 self.0.splice_children(pos..pos, vec![syntax.into()]);
1512 Rule(self.0.children().nth(pos).unwrap())
1513 }
1514
1515 pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1517 let mut buf = String::new();
1518 r.read_to_string(&mut buf)?;
1519
1520 let parsed = parse(&buf);
1521 if !parsed.errors.is_empty() {
1522 Err(Error::Parse(ParseError {
1523 errors: parsed.errors,
1524 }))
1525 } else {
1526 Ok(parsed.root())
1527 }
1528 }
1529
1530 pub fn replace_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1541 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1542
1543 if rules.is_empty() {
1544 return Err(Error::Parse(ParseError {
1545 errors: vec![ErrorInfo {
1546 message: "Cannot replace rule in empty makefile".to_string(),
1547 line: 1,
1548 context: "replace_rule".to_string(),
1549 }],
1550 }));
1551 }
1552
1553 if index >= rules.len() {
1554 return Err(Error::Parse(ParseError {
1555 errors: vec![ErrorInfo {
1556 message: format!(
1557 "Rule index {} out of bounds (max {})",
1558 index,
1559 rules.len() - 1
1560 ),
1561 line: 1,
1562 context: "replace_rule".to_string(),
1563 }],
1564 }));
1565 }
1566
1567 let target_node = &rules[index];
1568 let target_index = target_node.index();
1569
1570 self.0.splice_children(
1572 target_index..target_index + 1,
1573 vec![new_rule.0.clone().into()],
1574 );
1575 Ok(())
1576 }
1577
1578 pub fn remove_rule(&mut self, index: usize) -> Result<Rule, Error> {
1589 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1590
1591 if rules.is_empty() {
1592 return Err(Error::Parse(ParseError {
1593 errors: vec![ErrorInfo {
1594 message: "Cannot remove rule from empty makefile".to_string(),
1595 line: 1,
1596 context: "remove_rule".to_string(),
1597 }],
1598 }));
1599 }
1600
1601 if index >= rules.len() {
1602 return Err(Error::Parse(ParseError {
1603 errors: vec![ErrorInfo {
1604 message: format!(
1605 "Rule index {} out of bounds (max {})",
1606 index,
1607 rules.len() - 1
1608 ),
1609 line: 1,
1610 context: "remove_rule".to_string(),
1611 }],
1612 }));
1613 }
1614
1615 let target_node = rules[index].clone();
1616 let target_index = target_node.index();
1617
1618 self.0
1620 .splice_children(target_index..target_index + 1, vec![]);
1621 Ok(Rule(target_node))
1622 }
1623
1624 pub fn insert_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1636 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1637
1638 if index > rules.len() {
1639 return Err(Error::Parse(ParseError {
1640 errors: vec![ErrorInfo {
1641 message: format!("Rule index {} out of bounds (max {})", index, rules.len()),
1642 line: 1,
1643 context: "insert_rule".to_string(),
1644 }],
1645 }));
1646 }
1647
1648 let target_index = if index == rules.len() {
1649 self.0.children_with_tokens().count()
1651 } else {
1652 rules[index].index()
1654 };
1655
1656 self.0
1658 .splice_children(target_index..target_index, vec![new_rule.0.clone().into()]);
1659 Ok(())
1660 }
1661
1662 pub fn includes(&self) -> impl Iterator<Item = Include> {
1672 self.syntax().children().filter_map(Include::cast)
1673 }
1674
1675 pub fn included_files(&self) -> impl Iterator<Item = String> + '_ {
1685 fn collect_includes(node: &SyntaxNode) -> Vec<Include> {
1688 let mut includes = Vec::new();
1689
1690 if let Some(include) = Include::cast(node.clone()) {
1692 includes.push(include);
1693 }
1694
1695 for child in node.children() {
1697 includes.extend(collect_includes(&child));
1698 }
1699
1700 includes
1701 }
1702
1703 let includes = collect_includes(self.syntax());
1705
1706 includes.into_iter().map(|include| {
1708 include
1709 .syntax()
1710 .children()
1711 .find(|node| node.kind() == EXPR)
1712 .map(|expr| expr.text().to_string().trim().to_string())
1713 .unwrap_or_default()
1714 })
1715 }
1716}
1717
1718impl FromStr for Rule {
1719 type Err = crate::Error;
1720
1721 fn from_str(s: &str) -> Result<Self, Self::Err> {
1722 Rule::parse(s).to_rule_result()
1723 }
1724}
1725
1726impl FromStr for Makefile {
1727 type Err = crate::Error;
1728
1729 fn from_str(s: &str) -> Result<Self, Self::Err> {
1730 Makefile::parse(s).to_result()
1731 }
1732}
1733
1734impl Rule {
1735 pub fn parse(text: &str) -> crate::Parse<Rule> {
1737 crate::Parse::<Rule>::parse_rule(text)
1738 }
1739
1740 fn collect_variable_reference(
1742 &self,
1743 tokens: &mut std::iter::Peekable<impl Iterator<Item = SyntaxElement>>,
1744 ) -> Option<String> {
1745 let mut var_ref = String::new();
1746
1747 if let Some(token) = tokens.next() {
1749 if let Some(t) = token.as_token() {
1750 if t.kind() == DOLLAR {
1751 var_ref.push_str(t.text());
1752
1753 if let Some(next) = tokens.peek() {
1755 if let Some(nt) = next.as_token() {
1756 if nt.kind() == LPAREN {
1757 var_ref.push_str(nt.text());
1759 tokens.next();
1760
1761 let mut paren_count = 1;
1763
1764 for next_token in tokens.by_ref() {
1766 if let Some(nt) = next_token.as_token() {
1767 var_ref.push_str(nt.text());
1768
1769 if nt.kind() == LPAREN {
1770 paren_count += 1;
1771 } else if nt.kind() == RPAREN {
1772 paren_count -= 1;
1773 if paren_count == 0 {
1774 break;
1775 }
1776 }
1777 }
1778 }
1779
1780 return Some(var_ref);
1781 }
1782 }
1783 }
1784
1785 for next_token in tokens.by_ref() {
1787 if let Some(nt) = next_token.as_token() {
1788 var_ref.push_str(nt.text());
1789 if nt.kind() == RPAREN {
1790 break;
1791 }
1792 }
1793 }
1794 return Some(var_ref);
1795 }
1796 }
1797 }
1798
1799 None
1800 }
1801
1802 pub fn targets(&self) -> impl Iterator<Item = String> + '_ {
1812 let mut result = Vec::new();
1813 let mut tokens = self
1814 .syntax()
1815 .children_with_tokens()
1816 .take_while(|it| it.as_token().map(|t| t.kind() != OPERATOR).unwrap_or(true))
1817 .peekable();
1818
1819 while let Some(token) = tokens.peek().cloned() {
1820 if let Some(node) = token.as_node() {
1821 tokens.next(); if node.kind() == EXPR {
1823 let mut var_content = String::new();
1825 for child in node.children_with_tokens() {
1826 if let Some(t) = child.as_token() {
1827 var_content.push_str(t.text());
1828 }
1829 }
1830 if !var_content.is_empty() {
1831 result.push(var_content);
1832 }
1833 }
1834 } else if let Some(t) = token.as_token() {
1835 if t.kind() == DOLLAR {
1836 if let Some(var_ref) = self.collect_variable_reference(&mut tokens) {
1837 result.push(var_ref);
1838 }
1839 } else if t.kind() == IDENTIFIER {
1840 let ident_text = t.text().to_string();
1842 tokens.next(); if let Some(next) = tokens.peek() {
1846 if let Some(next_token) = next.as_token() {
1847 if next_token.kind() == LPAREN {
1848 let mut archive_target = ident_text;
1850 archive_target.push_str(next_token.text()); tokens.next(); while let Some(token) = tokens.peek() {
1855 if let Some(node) = token.as_node() {
1856 if node.kind() == ARCHIVE_MEMBERS {
1857 archive_target.push_str(&node.text().to_string());
1858 tokens.next();
1859 } else {
1860 tokens.next();
1861 }
1862 } else if let Some(t) = token.as_token() {
1863 if t.kind() == RPAREN {
1864 archive_target.push_str(t.text());
1865 tokens.next();
1866 break;
1867 } else {
1868 tokens.next();
1869 }
1870 } else {
1871 break;
1872 }
1873 }
1874 result.push(archive_target);
1875 } else {
1876 result.push(ident_text);
1878 }
1879 } else {
1880 result.push(ident_text);
1882 }
1883 } else {
1884 result.push(ident_text);
1886 }
1887 } else {
1888 tokens.next(); }
1890 }
1891 }
1892 result.into_iter()
1893 }
1894
1895 pub fn prerequisites(&self) -> impl Iterator<Item = String> + '_ {
1904 let mut found_operator = false;
1906 let mut result = Vec::new();
1907
1908 for token in self.syntax().children_with_tokens() {
1909 if let Some(t) = token.as_token() {
1910 if t.kind() == OPERATOR {
1911 found_operator = true;
1912 continue;
1913 }
1914 }
1915
1916 if found_operator {
1917 if let Some(node) = token.as_node() {
1918 if node.kind() == EXPR {
1919 let mut tokens = node.children_with_tokens().peekable();
1921 while let Some(token) = tokens.peek().cloned() {
1922 if let Some(node) = token.as_node() {
1923 if node.kind() == ARCHIVE_MEMBERS {
1924 result.push(node.text().to_string());
1926 }
1927 tokens.next(); } else if let Some(t) = token.as_token() {
1929 if t.kind() == DOLLAR {
1930 if let Some(var_ref) =
1931 self.collect_variable_reference(&mut tokens)
1932 {
1933 result.push(var_ref);
1934 }
1935 } else if t.kind() == IDENTIFIER {
1936 result.push(t.text().to_string());
1937 tokens.next(); } else {
1939 tokens.next(); }
1941 } else {
1942 tokens.next(); }
1944 }
1945 break; }
1947 }
1948 }
1949 }
1950
1951 result.into_iter()
1952 }
1953
1954 pub fn recipes(&self) -> impl Iterator<Item = String> {
1963 self.syntax()
1964 .children()
1965 .filter(|it| it.kind() == RECIPE)
1966 .flat_map(|it| {
1967 it.children_with_tokens().filter_map(|it| {
1968 it.as_token().and_then(|t| {
1969 if t.kind() == TEXT {
1970 Some(t.text().to_string())
1971 } else {
1972 None
1973 }
1974 })
1975 })
1976 })
1977 }
1978
1979 pub fn replace_command(&mut self, i: usize, line: &str) -> bool {
1989 let index = self
1991 .syntax()
1992 .children()
1993 .filter(|it| it.kind() == RECIPE)
1994 .nth(i);
1995
1996 let index = match index {
1997 Some(node) => node.index(),
1998 None => return false,
1999 };
2000
2001 let mut builder = GreenNodeBuilder::new();
2002 builder.start_node(RECIPE.into());
2003 builder.token(INDENT.into(), "\t");
2004 builder.token(TEXT.into(), line);
2005 builder.token(NEWLINE.into(), "\n");
2006 builder.finish_node();
2007
2008 let syntax = SyntaxNode::new_root_mut(builder.finish());
2009
2010 self.0
2011 .splice_children(index..index + 1, vec![syntax.into()]);
2012
2013 true
2014 }
2015
2016 pub fn push_command(&mut self, line: &str) {
2026 let index = self
2028 .0
2029 .children_with_tokens()
2030 .filter(|it| it.kind() == RECIPE)
2031 .last();
2032
2033 let index = index.map_or_else(
2034 || self.0.children_with_tokens().count(),
2035 |it| it.index() + 1,
2036 );
2037
2038 let mut builder = GreenNodeBuilder::new();
2039 builder.start_node(RECIPE.into());
2040 builder.token(INDENT.into(), "\t");
2041 builder.token(TEXT.into(), line);
2042 builder.token(NEWLINE.into(), "\n");
2043 builder.finish_node();
2044 let syntax = SyntaxNode::new_root_mut(builder.finish());
2045
2046 self.0.splice_children(index..index, vec![syntax.into()]);
2047 }
2048
2049 pub fn remove_command(&mut self, index: usize) -> bool {
2059 let recipes: Vec<_> = self
2060 .syntax()
2061 .children()
2062 .filter(|n| n.kind() == RECIPE)
2063 .collect();
2064
2065 if index >= recipes.len() {
2066 return false;
2067 }
2068
2069 let target_node = &recipes[index];
2070 let target_index = target_node.index();
2071
2072 self.0
2073 .splice_children(target_index..target_index + 1, vec![]);
2074 true
2075 }
2076
2077 pub fn insert_command(&mut self, index: usize, line: &str) -> bool {
2088 let recipes: Vec<_> = self
2089 .syntax()
2090 .children()
2091 .filter(|n| n.kind() == RECIPE)
2092 .collect();
2093
2094 if index > recipes.len() {
2095 return false;
2096 }
2097
2098 let target_index = if index == recipes.len() {
2099 recipes.last().map(|n| n.index() + 1).unwrap_or_else(|| {
2101 self.0.children_with_tokens().count()
2103 })
2104 } else {
2105 recipes[index].index()
2107 };
2108
2109 let mut builder = GreenNodeBuilder::new();
2110 builder.start_node(RECIPE.into());
2111 builder.token(INDENT.into(), "\t");
2112 builder.token(TEXT.into(), line);
2113 builder.token(NEWLINE.into(), "\n");
2114 builder.finish_node();
2115 let syntax = SyntaxNode::new_root_mut(builder.finish());
2116
2117 self.0
2118 .splice_children(target_index..target_index, vec![syntax.into()]);
2119 true
2120 }
2121
2122 pub fn recipe_count(&self) -> usize {
2131 self.syntax()
2132 .children()
2133 .filter(|n| n.kind() == RECIPE)
2134 .count()
2135 }
2136
2137 pub fn clear_commands(&mut self) {
2147 let recipes: Vec<_> = self
2148 .syntax()
2149 .children()
2150 .filter(|n| n.kind() == RECIPE)
2151 .collect();
2152
2153 if recipes.is_empty() {
2154 return;
2155 }
2156
2157 for recipe in recipes.iter().rev() {
2159 let index = recipe.index();
2160 self.0.splice_children(index..index + 1, vec![]);
2161 }
2162 }
2163}
2164
2165impl Default for Makefile {
2166 fn default() -> Self {
2167 Self::new()
2168 }
2169}
2170
2171impl Include {
2172 pub fn path(&self) -> Option<String> {
2174 self.syntax()
2175 .children()
2176 .find(|it| it.kind() == EXPR)
2177 .map(|it| it.text().to_string().trim().to_string())
2178 }
2179
2180 pub fn is_optional(&self) -> bool {
2182 let text = self.syntax().text();
2183 text.to_string().starts_with("-include") || text.to_string().starts_with("sinclude")
2184 }
2185}
2186
2187#[cfg(test)]
2188mod tests {
2189 use super::*;
2190
2191 #[test]
2192 fn test_conditionals() {
2193 let code = "ifdef DEBUG\n DEBUG_FLAG := 1\nendif\n";
2197 let mut buf = code.as_bytes();
2198 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse basic ifdef");
2199 assert!(makefile.code().contains("DEBUG_FLAG"));
2200
2201 let code =
2203 "ifeq ($(OS),Windows_NT)\n RESULT := windows\nelse\n RESULT := unix\nendif\n";
2204 let mut buf = code.as_bytes();
2205 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq/ifneq");
2206 assert!(makefile.code().contains("RESULT"));
2207 assert!(makefile.code().contains("windows"));
2208
2209 let code = "ifdef DEBUG\n CFLAGS += -g\n ifdef VERBOSE\n CFLAGS += -v\n endif\nelse\n CFLAGS += -O2\nendif\n";
2211 let mut buf = code.as_bytes();
2212 let makefile = Makefile::read_relaxed(&mut buf)
2213 .expect("Failed to parse nested conditionals with else");
2214 assert!(makefile.code().contains("CFLAGS"));
2215 assert!(makefile.code().contains("VERBOSE"));
2216
2217 let code = "ifdef DEBUG\nendif\n";
2219 let mut buf = code.as_bytes();
2220 let makefile =
2221 Makefile::read_relaxed(&mut buf).expect("Failed to parse empty conditionals");
2222 assert!(makefile.code().contains("ifdef DEBUG"));
2223
2224 let code = "ifeq ($(OS),Windows)\n EXT := .exe\nelif ifeq ($(OS),Linux)\n EXT := .bin\nelse\n EXT := .out\nendif\n";
2226 let mut buf = code.as_bytes();
2227 let makefile =
2228 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditionals with elif");
2229 assert!(makefile.code().contains("EXT"));
2230
2231 let code = "ifXYZ DEBUG\nDEBUG := 1\nendif\n";
2233 let mut buf = code.as_bytes();
2234 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse with recovery");
2235 assert!(makefile.code().contains("DEBUG"));
2236
2237 let code = "ifdef \nDEBUG := 1\nendif\n";
2239 let mut buf = code.as_bytes();
2240 let makefile = Makefile::read_relaxed(&mut buf)
2241 .expect("Failed to parse with recovery - missing condition");
2242 assert!(makefile.code().contains("DEBUG"));
2243 }
2244
2245 #[test]
2246 fn test_parse_simple() {
2247 const SIMPLE: &str = r#"VARIABLE = value
2248
2249rule: dependency
2250 command
2251"#;
2252 let parsed = parse(SIMPLE);
2253 assert!(parsed.errors.is_empty());
2254 let node = parsed.syntax();
2255 assert_eq!(
2256 format!("{:#?}", node),
2257 r#"ROOT@0..44
2258 VARIABLE@0..17
2259 IDENTIFIER@0..8 "VARIABLE"
2260 WHITESPACE@8..9 " "
2261 OPERATOR@9..10 "="
2262 WHITESPACE@10..11 " "
2263 EXPR@11..16
2264 IDENTIFIER@11..16 "value"
2265 NEWLINE@16..17 "\n"
2266 NEWLINE@17..18 "\n"
2267 RULE@18..44
2268 IDENTIFIER@18..22 "rule"
2269 OPERATOR@22..23 ":"
2270 WHITESPACE@23..24 " "
2271 EXPR@24..34
2272 IDENTIFIER@24..34 "dependency"
2273 NEWLINE@34..35 "\n"
2274 RECIPE@35..44
2275 INDENT@35..36 "\t"
2276 TEXT@36..43 "command"
2277 NEWLINE@43..44 "\n"
2278"#
2279 );
2280
2281 let root = parsed.root();
2282
2283 let mut rules = root.rules().collect::<Vec<_>>();
2284 assert_eq!(rules.len(), 1);
2285 let rule = rules.pop().unwrap();
2286 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2287 assert_eq!(rule.prerequisites().collect::<Vec<_>>(), vec!["dependency"]);
2288 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2289
2290 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2291 assert_eq!(variables.len(), 1);
2292 let variable = variables.pop().unwrap();
2293 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2294 assert_eq!(variable.raw_value(), Some("value".to_string()));
2295 }
2296
2297 #[test]
2298 fn test_parse_export_assign() {
2299 const EXPORT: &str = r#"export VARIABLE := value
2300"#;
2301 let parsed = parse(EXPORT);
2302 assert!(parsed.errors.is_empty());
2303 let node = parsed.syntax();
2304 assert_eq!(
2305 format!("{:#?}", node),
2306 r#"ROOT@0..25
2307 VARIABLE@0..25
2308 IDENTIFIER@0..6 "export"
2309 WHITESPACE@6..7 " "
2310 IDENTIFIER@7..15 "VARIABLE"
2311 WHITESPACE@15..16 " "
2312 OPERATOR@16..18 ":="
2313 WHITESPACE@18..19 " "
2314 EXPR@19..24
2315 IDENTIFIER@19..24 "value"
2316 NEWLINE@24..25 "\n"
2317"#
2318 );
2319
2320 let root = parsed.root();
2321
2322 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2323 assert_eq!(variables.len(), 1);
2324 let variable = variables.pop().unwrap();
2325 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2326 assert_eq!(variable.raw_value(), Some("value".to_string()));
2327 }
2328
2329 #[test]
2330 fn test_parse_multiple_prerequisites() {
2331 const MULTIPLE_PREREQUISITES: &str = r#"rule: dependency1 dependency2
2332 command
2333
2334"#;
2335 let parsed = parse(MULTIPLE_PREREQUISITES);
2336 assert!(parsed.errors.is_empty());
2337 let node = parsed.syntax();
2338 assert_eq!(
2339 format!("{:#?}", node),
2340 r#"ROOT@0..40
2341 RULE@0..40
2342 IDENTIFIER@0..4 "rule"
2343 OPERATOR@4..5 ":"
2344 WHITESPACE@5..6 " "
2345 EXPR@6..29
2346 IDENTIFIER@6..17 "dependency1"
2347 WHITESPACE@17..18 " "
2348 IDENTIFIER@18..29 "dependency2"
2349 NEWLINE@29..30 "\n"
2350 RECIPE@30..39
2351 INDENT@30..31 "\t"
2352 TEXT@31..38 "command"
2353 NEWLINE@38..39 "\n"
2354 NEWLINE@39..40 "\n"
2355"#
2356 );
2357 let root = parsed.root();
2358
2359 let rule = root.rules().next().unwrap();
2360 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2361 assert_eq!(
2362 rule.prerequisites().collect::<Vec<_>>(),
2363 vec!["dependency1", "dependency2"]
2364 );
2365 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2366 }
2367
2368 #[test]
2369 fn test_add_rule() {
2370 let mut makefile = Makefile::new();
2371 let rule = makefile.add_rule("rule");
2372 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2373 assert_eq!(
2374 rule.prerequisites().collect::<Vec<_>>(),
2375 Vec::<String>::new()
2376 );
2377
2378 assert_eq!(makefile.to_string(), "rule:\n");
2379 }
2380
2381 #[test]
2382 fn test_push_command() {
2383 let mut makefile = Makefile::new();
2384 let mut rule = makefile.add_rule("rule");
2385
2386 rule.push_command("command");
2388 rule.push_command("command2");
2389
2390 assert_eq!(
2392 rule.recipes().collect::<Vec<_>>(),
2393 vec!["command", "command2"]
2394 );
2395
2396 rule.push_command("command3");
2398 assert_eq!(
2399 rule.recipes().collect::<Vec<_>>(),
2400 vec!["command", "command2", "command3"]
2401 );
2402
2403 assert_eq!(
2405 makefile.to_string(),
2406 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2407 );
2408
2409 assert_eq!(
2411 rule.to_string(),
2412 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2413 );
2414 }
2415
2416 #[test]
2417 fn test_replace_command() {
2418 let mut makefile = Makefile::new();
2419 let mut rule = makefile.add_rule("rule");
2420
2421 rule.push_command("command");
2423 rule.push_command("command2");
2424
2425 assert_eq!(
2427 rule.recipes().collect::<Vec<_>>(),
2428 vec!["command", "command2"]
2429 );
2430
2431 rule.replace_command(0, "new command");
2433 assert_eq!(
2434 rule.recipes().collect::<Vec<_>>(),
2435 vec!["new command", "command2"]
2436 );
2437
2438 assert_eq!(makefile.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2440
2441 assert_eq!(rule.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2443 }
2444
2445 #[test]
2446 fn test_parse_rule_without_newline() {
2447 let rule = "rule: dependency\n\tcommand".parse::<Rule>().unwrap();
2448 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2449 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2450 let rule = "rule: dependency".parse::<Rule>().unwrap();
2451 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2452 assert_eq!(rule.recipes().collect::<Vec<_>>(), Vec::<String>::new());
2453 }
2454
2455 #[test]
2456 fn test_parse_makefile_without_newline() {
2457 let makefile = "rule: dependency\n\tcommand".parse::<Makefile>().unwrap();
2458 assert_eq!(makefile.rules().count(), 1);
2459 }
2460
2461 #[test]
2462 fn test_from_reader() {
2463 let makefile = Makefile::from_reader("rule: dependency\n\tcommand".as_bytes()).unwrap();
2464 assert_eq!(makefile.rules().count(), 1);
2465 }
2466
2467 #[test]
2468 fn test_parse_with_tab_after_last_newline() {
2469 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n\t".as_bytes()).unwrap();
2470 assert_eq!(makefile.rules().count(), 1);
2471 }
2472
2473 #[test]
2474 fn test_parse_with_space_after_last_newline() {
2475 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n ".as_bytes()).unwrap();
2476 assert_eq!(makefile.rules().count(), 1);
2477 }
2478
2479 #[test]
2480 fn test_parse_with_comment_after_last_newline() {
2481 let makefile =
2482 Makefile::from_reader("rule: dependency\n\tcommand\n#comment".as_bytes()).unwrap();
2483 assert_eq!(makefile.rules().count(), 1);
2484 }
2485
2486 #[test]
2487 fn test_parse_with_variable_rule() {
2488 let makefile =
2489 Makefile::from_reader("RULE := rule\n$(RULE): dependency\n\tcommand".as_bytes())
2490 .unwrap();
2491
2492 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2494 assert_eq!(vars.len(), 1);
2495 assert_eq!(vars[0].name(), Some("RULE".to_string()));
2496 assert_eq!(vars[0].raw_value(), Some("rule".to_string()));
2497
2498 let rules = makefile.rules().collect::<Vec<_>>();
2500 assert_eq!(rules.len(), 1);
2501 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["$(RULE)"]);
2502 assert_eq!(
2503 rules[0].prerequisites().collect::<Vec<_>>(),
2504 vec!["dependency"]
2505 );
2506 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2507 }
2508
2509 #[test]
2510 fn test_parse_with_variable_dependency() {
2511 let makefile =
2512 Makefile::from_reader("DEP := dependency\nrule: $(DEP)\n\tcommand".as_bytes()).unwrap();
2513
2514 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2516 assert_eq!(vars.len(), 1);
2517 assert_eq!(vars[0].name(), Some("DEP".to_string()));
2518 assert_eq!(vars[0].raw_value(), Some("dependency".to_string()));
2519
2520 let rules = makefile.rules().collect::<Vec<_>>();
2522 assert_eq!(rules.len(), 1);
2523 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2524 assert_eq!(rules[0].prerequisites().collect::<Vec<_>>(), vec!["$(DEP)"]);
2525 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2526 }
2527
2528 #[test]
2529 fn test_parse_with_variable_command() {
2530 let makefile =
2531 Makefile::from_reader("COM := command\nrule: dependency\n\t$(COM)".as_bytes()).unwrap();
2532
2533 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2535 assert_eq!(vars.len(), 1);
2536 assert_eq!(vars[0].name(), Some("COM".to_string()));
2537 assert_eq!(vars[0].raw_value(), Some("command".to_string()));
2538
2539 let rules = makefile.rules().collect::<Vec<_>>();
2541 assert_eq!(rules.len(), 1);
2542 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2543 assert_eq!(
2544 rules[0].prerequisites().collect::<Vec<_>>(),
2545 vec!["dependency"]
2546 );
2547 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["$(COM)"]);
2548 }
2549
2550 #[test]
2551 fn test_regular_line_error_reporting() {
2552 let input = "rule target\n\tcommand";
2553
2554 let parsed = parse(input);
2556 let direct_error = &parsed.errors[0];
2557
2558 assert_eq!(direct_error.line, 2);
2560 assert!(
2561 direct_error.message.contains("expected"),
2562 "Error message should contain 'expected': {}",
2563 direct_error.message
2564 );
2565 assert_eq!(direct_error.context, "\tcommand");
2566
2567 let reader_result = Makefile::from_reader(input.as_bytes());
2569 let parse_error = match reader_result {
2570 Ok(_) => panic!("Expected Parse error from from_reader"),
2571 Err(err) => match err {
2572 self::Error::Parse(parse_err) => parse_err,
2573 _ => panic!("Expected Parse error"),
2574 },
2575 };
2576
2577 let error_text = parse_error.to_string();
2579 assert!(error_text.contains("Error at line 2:"));
2580 assert!(error_text.contains("2| \tcommand"));
2581 }
2582
2583 #[test]
2584 fn test_parsing_error_context_with_bad_syntax() {
2585 let input = "#begin comment\n\t(╯°□°)╯︵ ┻━┻\n#end comment";
2587
2588 match Makefile::from_reader(input.as_bytes()) {
2590 Ok(makefile) => {
2591 assert_eq!(
2593 makefile.rules().count(),
2594 0,
2595 "Should not have found any rules"
2596 );
2597 }
2598 Err(err) => match err {
2599 self::Error::Parse(error) => {
2600 assert!(error.errors[0].line >= 2, "Error line should be at least 2");
2602 assert!(
2603 !error.errors[0].context.is_empty(),
2604 "Error context should not be empty"
2605 );
2606 }
2607 _ => panic!("Unexpected error type"),
2608 },
2609 };
2610 }
2611
2612 #[test]
2613 fn test_error_message_format() {
2614 let parse_error = ParseError {
2616 errors: vec![ErrorInfo {
2617 message: "test error".to_string(),
2618 line: 42,
2619 context: "some problematic code".to_string(),
2620 }],
2621 };
2622
2623 let error_text = parse_error.to_string();
2624 assert!(error_text.contains("Error at line 42: test error"));
2625 assert!(error_text.contains("42| some problematic code"));
2626 }
2627
2628 #[test]
2629 fn test_line_number_calculation() {
2630 let test_cases = [
2632 ("rule dependency\n\tcommand", 2), ("#comment\n\t(╯°□°)╯︵ ┻━┻", 2), ("var = value\n#comment\n\tindented line", 3), ];
2636
2637 for (input, expected_line) in test_cases {
2638 match input.parse::<Makefile>() {
2640 Ok(_) => {
2641 continue;
2644 }
2645 Err(err) => {
2646 if let Error::Parse(parse_err) = err {
2647 assert_eq!(
2649 parse_err.errors[0].line, expected_line,
2650 "Line number should match the expected line"
2651 );
2652
2653 if parse_err.errors[0].message.contains("indented") {
2655 assert!(
2656 parse_err.errors[0].context.starts_with('\t'),
2657 "Context for indentation errors should include the tab character"
2658 );
2659 }
2660 } else {
2661 panic!("Expected parse error, got: {:?}", err);
2662 }
2663 }
2664 }
2665 }
2666 }
2667
2668 #[test]
2669 fn test_conditional_features() {
2670 let code = r#"
2672# Set variables based on DEBUG flag
2673ifdef DEBUG
2674 CFLAGS += -g -DDEBUG
2675else
2676 CFLAGS = -O2
2677endif
2678
2679# Define a build rule
2680all: $(OBJS)
2681 $(CC) $(CFLAGS) -o $@ $^
2682"#;
2683
2684 let mut buf = code.as_bytes();
2685 let makefile =
2686 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditional features");
2687
2688 assert!(!makefile.code().is_empty(), "Makefile has content");
2691
2692 let rules = makefile.rules().collect::<Vec<_>>();
2694 assert!(!rules.is_empty(), "Should have found rules");
2695
2696 assert!(code.contains("ifdef DEBUG"));
2698 assert!(code.contains("endif"));
2699
2700 let code_with_var = r#"
2702# Define a variable first
2703CC = gcc
2704
2705ifdef DEBUG
2706 CFLAGS += -g -DDEBUG
2707else
2708 CFLAGS = -O2
2709endif
2710
2711all: $(OBJS)
2712 $(CC) $(CFLAGS) -o $@ $^
2713"#;
2714
2715 let mut buf = code_with_var.as_bytes();
2716 let makefile =
2717 Makefile::read_relaxed(&mut buf).expect("Failed to parse with explicit variable");
2718
2719 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2721 assert!(
2722 !vars.is_empty(),
2723 "Should have found at least the CC variable definition"
2724 );
2725 }
2726
2727 #[test]
2728 fn test_include_directive() {
2729 let parsed = parse("include config.mk\ninclude $(TOPDIR)/rules.mk\ninclude *.mk\n");
2730 assert!(parsed.errors.is_empty());
2731 let node = parsed.syntax();
2732 assert!(format!("{:#?}", node).contains("INCLUDE@"));
2733 }
2734
2735 #[test]
2736 fn test_export_variables() {
2737 let parsed = parse("export SHELL := /bin/bash\n");
2738 assert!(parsed.errors.is_empty());
2739 let makefile = parsed.root();
2740 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2741 assert_eq!(vars.len(), 1);
2742 let shell_var = vars
2743 .iter()
2744 .find(|v| v.name() == Some("SHELL".to_string()))
2745 .unwrap();
2746 assert!(shell_var.raw_value().unwrap().contains("bin/bash"));
2747 }
2748
2749 #[test]
2750 fn test_variable_scopes() {
2751 let parsed =
2752 parse("SIMPLE = value\nIMMEDIATE := value\nCONDITIONAL ?= value\nAPPEND += value\n");
2753 assert!(parsed.errors.is_empty());
2754 let makefile = parsed.root();
2755 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2756 assert_eq!(vars.len(), 4);
2757 let var_names: Vec<_> = vars.iter().filter_map(|v| v.name()).collect();
2758 assert!(var_names.contains(&"SIMPLE".to_string()));
2759 assert!(var_names.contains(&"IMMEDIATE".to_string()));
2760 assert!(var_names.contains(&"CONDITIONAL".to_string()));
2761 assert!(var_names.contains(&"APPEND".to_string()));
2762 }
2763
2764 #[test]
2765 fn test_pattern_rule_parsing() {
2766 let parsed = parse("%.o: %.c\n\t$(CC) -c -o $@ $<\n");
2767 assert!(parsed.errors.is_empty());
2768 let makefile = parsed.root();
2769 let rules = makefile.rules().collect::<Vec<_>>();
2770 assert_eq!(rules.len(), 1);
2771 assert_eq!(rules[0].targets().next().unwrap(), "%.o");
2772 assert!(rules[0].recipes().next().unwrap().contains("$@"));
2773 }
2774
2775 #[test]
2776 fn test_include_variants() {
2777 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\ninclude $(VAR)/generated.mk\n";
2779 let parsed = parse(makefile_str);
2780 assert!(parsed.errors.is_empty());
2781
2782 let node = parsed.syntax();
2784 let debug_str = format!("{:#?}", node);
2785
2786 assert_eq!(debug_str.matches("INCLUDE@").count(), 4);
2788
2789 let makefile = parsed.root();
2791
2792 let include_count = makefile
2794 .syntax()
2795 .children()
2796 .filter(|child| child.kind() == INCLUDE)
2797 .count();
2798 assert_eq!(include_count, 4);
2799
2800 assert!(makefile
2802 .included_files()
2803 .any(|path| path.contains("$(VAR)")));
2804 }
2805
2806 #[test]
2807 fn test_include_api() {
2808 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\n";
2810 let makefile: Makefile = makefile_str.parse().unwrap();
2811
2812 let includes: Vec<_> = makefile.includes().collect();
2814 assert_eq!(includes.len(), 3);
2815
2816 assert!(!includes[0].is_optional()); assert!(includes[1].is_optional()); assert!(includes[2].is_optional()); let files: Vec<_> = makefile.included_files().collect();
2823 assert_eq!(files, vec!["simple.mk", "optional.mk", "synonym.mk"]);
2824
2825 assert_eq!(includes[0].path(), Some("simple.mk".to_string()));
2827 assert_eq!(includes[1].path(), Some("optional.mk".to_string()));
2828 assert_eq!(includes[2].path(), Some("synonym.mk".to_string()));
2829 }
2830
2831 #[test]
2832 fn test_include_integration() {
2833 let phony_makefile = Makefile::from_reader(
2837 ".PHONY: build\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
2838 .as_bytes()
2839 ).unwrap();
2840
2841 assert_eq!(phony_makefile.rules().count(), 2);
2843
2844 let normal_rules_count = phony_makefile
2846 .rules()
2847 .filter(|r| !r.targets().any(|t| t.starts_with('.')))
2848 .count();
2849 assert_eq!(normal_rules_count, 1);
2850
2851 assert_eq!(phony_makefile.includes().count(), 1);
2853 assert_eq!(phony_makefile.included_files().next().unwrap(), ".env");
2854
2855 let simple_makefile = Makefile::from_reader(
2857 "\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
2858 .as_bytes(),
2859 )
2860 .unwrap();
2861 assert_eq!(simple_makefile.rules().count(), 1);
2862 assert_eq!(simple_makefile.includes().count(), 1);
2863 }
2864
2865 #[test]
2866 fn test_real_conditional_directives() {
2867 let conditional = "ifdef DEBUG\nCFLAGS = -g\nelse\nCFLAGS = -O2\nendif\n";
2869 let mut buf = conditional.as_bytes();
2870 let makefile =
2871 Makefile::read_relaxed(&mut buf).expect("Failed to parse basic if/else conditional");
2872 let code = makefile.code();
2873 assert!(code.contains("ifdef DEBUG"));
2874 assert!(code.contains("else"));
2875 assert!(code.contains("endif"));
2876
2877 let nested = "ifdef DEBUG\nCFLAGS = -g\nifdef VERBOSE\nCFLAGS += -v\nendif\nendif\n";
2879 let mut buf = nested.as_bytes();
2880 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse nested ifdef");
2881 let code = makefile.code();
2882 assert!(code.contains("ifdef DEBUG"));
2883 assert!(code.contains("ifdef VERBOSE"));
2884
2885 let ifeq = "ifeq ($(OS),Windows_NT)\nTARGET = app.exe\nelse\nTARGET = app\nendif\n";
2887 let mut buf = ifeq.as_bytes();
2888 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq form");
2889 let code = makefile.code();
2890 assert!(code.contains("ifeq"));
2891 assert!(code.contains("Windows_NT"));
2892 }
2893
2894 #[test]
2895 fn test_indented_text_outside_rules() {
2896 let help_text = "help:\n\t@echo \"Available targets:\"\n\t@echo \" help show help\"\n";
2898 let parsed = parse(help_text);
2899 assert!(parsed.errors.is_empty());
2900
2901 let root = parsed.root();
2903 let rules = root.rules().collect::<Vec<_>>();
2904 assert_eq!(rules.len(), 1);
2905
2906 let help_rule = &rules[0];
2907 let recipes = help_rule.recipes().collect::<Vec<_>>();
2908 assert_eq!(recipes.len(), 2);
2909 assert!(recipes[0].contains("Available targets"));
2910 assert!(recipes[1].contains("help"));
2911 }
2912
2913 #[test]
2914 fn test_comment_handling_in_recipes() {
2915 let recipe_comment = "build:\n\t# This is a comment\n\tgcc -o app main.c\n";
2917
2918 let parsed = parse(recipe_comment);
2920
2921 assert!(
2923 parsed.errors.is_empty(),
2924 "Should parse recipe with comments without errors"
2925 );
2926
2927 let root = parsed.root();
2929 let rules = root.rules().collect::<Vec<_>>();
2930 assert_eq!(rules.len(), 1, "Should find exactly one rule");
2931
2932 let build_rule = &rules[0];
2934 assert_eq!(
2935 build_rule.targets().collect::<Vec<_>>(),
2936 vec!["build"],
2937 "Rule should have 'build' as target"
2938 );
2939
2940 let recipes = build_rule.recipes().collect::<Vec<_>>();
2944 assert_eq!(
2945 recipes.len(),
2946 1,
2947 "Should find exactly one recipe line (comment lines are filtered)"
2948 );
2949 assert!(
2950 recipes[0].contains("gcc -o app"),
2951 "Recipe should be the command line"
2952 );
2953 assert!(
2954 !recipes[0].contains("This is a comment"),
2955 "Comments should not be included in recipe lines"
2956 );
2957 }
2958
2959 #[test]
2960 fn test_multiline_variables() {
2961 let multiline = "SOURCES = main.c \\\n util.c\n";
2963
2964 let parsed = parse(multiline);
2966
2967 let root = parsed.root();
2969 let vars = root.variable_definitions().collect::<Vec<_>>();
2970 assert!(!vars.is_empty(), "Should find at least one variable");
2971
2972 let operators = "CFLAGS := -Wall \\\n -Werror\n";
2976 let parsed_operators = parse(operators);
2977
2978 let root = parsed_operators.root();
2980 let vars = root.variable_definitions().collect::<Vec<_>>();
2981 assert!(
2982 !vars.is_empty(),
2983 "Should find at least one variable with := operator"
2984 );
2985
2986 let append = "LDFLAGS += -L/usr/lib \\\n -lm\n";
2988 let parsed_append = parse(append);
2989
2990 let root = parsed_append.root();
2992 let vars = root.variable_definitions().collect::<Vec<_>>();
2993 assert!(
2994 !vars.is_empty(),
2995 "Should find at least one variable with += operator"
2996 );
2997 }
2998
2999 #[test]
3000 fn test_whitespace_and_eof_handling() {
3001 let blank_lines = "VAR = value\n\n\n";
3003
3004 let parsed_blank = parse(blank_lines);
3005
3006 let root = parsed_blank.root();
3008 let vars = root.variable_definitions().collect::<Vec<_>>();
3009 assert_eq!(
3010 vars.len(),
3011 1,
3012 "Should find one variable in blank lines test"
3013 );
3014
3015 let trailing_space = "VAR = value \n";
3017
3018 let parsed_space = parse(trailing_space);
3019
3020 let root = parsed_space.root();
3022 let vars = root.variable_definitions().collect::<Vec<_>>();
3023 assert_eq!(
3024 vars.len(),
3025 1,
3026 "Should find one variable in trailing space test"
3027 );
3028
3029 let no_newline = "VAR = value";
3031
3032 let parsed_no_newline = parse(no_newline);
3033
3034 let root = parsed_no_newline.root();
3036 let vars = root.variable_definitions().collect::<Vec<_>>();
3037 assert_eq!(vars.len(), 1, "Should find one variable in no newline test");
3038 assert_eq!(
3039 vars[0].name(),
3040 Some("VAR".to_string()),
3041 "Variable name should be VAR"
3042 );
3043 }
3044
3045 #[test]
3046 fn test_complex_variable_references() {
3047 let wildcard = "SOURCES = $(wildcard *.c)\n";
3049 let parsed = parse(wildcard);
3050 assert!(parsed.errors.is_empty());
3051
3052 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3054 let parsed = parse(nested);
3055 assert!(parsed.errors.is_empty());
3056
3057 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3059 let parsed = parse(patsubst);
3060 assert!(parsed.errors.is_empty());
3061 }
3062
3063 #[test]
3064 fn test_complex_variable_references_minimal() {
3065 let wildcard = "SOURCES = $(wildcard *.c)\n";
3067 let parsed = parse(wildcard);
3068 assert!(parsed.errors.is_empty());
3069
3070 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3072 let parsed = parse(nested);
3073 assert!(parsed.errors.is_empty());
3074
3075 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3077 let parsed = parse(patsubst);
3078 assert!(parsed.errors.is_empty());
3079 }
3080
3081 #[test]
3082 fn test_multiline_variable_with_backslash() {
3083 let content = r#"
3084LONG_VAR = This is a long variable \
3085 that continues on the next line \
3086 and even one more line
3087"#;
3088
3089 let mut buf = content.as_bytes();
3091 let makefile =
3092 Makefile::read_relaxed(&mut buf).expect("Failed to parse multiline variable");
3093
3094 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3096 assert_eq!(
3097 vars.len(),
3098 1,
3099 "Expected 1 variable but found {}",
3100 vars.len()
3101 );
3102 let var_value = vars[0].raw_value();
3103 assert!(var_value.is_some(), "Variable value is None");
3104
3105 let value_str = var_value.unwrap();
3107 assert!(
3108 value_str.contains("long variable"),
3109 "Value doesn't contain expected content"
3110 );
3111 }
3112
3113 #[test]
3114 fn test_multiline_variable_with_mixed_operators() {
3115 let content = r#"
3116PREFIX ?= /usr/local
3117CFLAGS := -Wall -O2 \
3118 -I$(PREFIX)/include \
3119 -DDEBUG
3120"#;
3121 let mut buf = content.as_bytes();
3123 let makefile = Makefile::read_relaxed(&mut buf)
3124 .expect("Failed to parse multiline variable with operators");
3125
3126 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3128 assert!(
3129 vars.len() >= 1,
3130 "Expected at least 1 variable, found {}",
3131 vars.len()
3132 );
3133
3134 let prefix_var = vars
3136 .iter()
3137 .find(|v| v.name().unwrap_or_default() == "PREFIX");
3138 assert!(prefix_var.is_some(), "Expected to find PREFIX variable");
3139 assert!(
3140 prefix_var.unwrap().raw_value().is_some(),
3141 "PREFIX variable has no value"
3142 );
3143
3144 let cflags_var = vars
3146 .iter()
3147 .find(|v| v.name().unwrap_or_default().contains("CFLAGS"));
3148 assert!(
3149 cflags_var.is_some(),
3150 "Expected to find CFLAGS variable (or part of it)"
3151 );
3152 }
3153
3154 #[test]
3155 fn test_indented_help_text() {
3156 let content = r#"
3157.PHONY: help
3158help:
3159 @echo "Available targets:"
3160 @echo " build - Build the project"
3161 @echo " test - Run tests"
3162 @echo " clean - Remove build artifacts"
3163"#;
3164 let mut buf = content.as_bytes();
3166 let makefile =
3167 Makefile::read_relaxed(&mut buf).expect("Failed to parse indented help text");
3168
3169 let rules = makefile.rules().collect::<Vec<_>>();
3171 assert!(!rules.is_empty(), "Expected at least one rule");
3172
3173 let help_rule = rules.iter().find(|r| r.targets().any(|t| t == "help"));
3175 assert!(help_rule.is_some(), "Expected to find help rule");
3176
3177 let recipes = help_rule.unwrap().recipes().collect::<Vec<_>>();
3179 assert!(
3180 !recipes.is_empty(),
3181 "Expected at least one recipe line in help rule"
3182 );
3183 assert!(
3184 recipes.iter().any(|r| r.contains("Available targets")),
3185 "Expected to find 'Available targets' in recipes"
3186 );
3187 }
3188
3189 #[test]
3190 fn test_indented_lines_in_conditionals() {
3191 let content = r#"
3192ifdef DEBUG
3193 CFLAGS += -g -DDEBUG
3194 # This is a comment inside conditional
3195 ifdef VERBOSE
3196 CFLAGS += -v
3197 endif
3198endif
3199"#;
3200 let mut buf = content.as_bytes();
3202 let makefile = Makefile::read_relaxed(&mut buf)
3203 .expect("Failed to parse indented lines in conditionals");
3204
3205 let code = makefile.code();
3207 assert!(code.contains("ifdef DEBUG"));
3208 assert!(code.contains("ifdef VERBOSE"));
3209 assert!(code.contains("endif"));
3210 }
3211
3212 #[test]
3213 fn test_recipe_with_colon() {
3214 let content = r#"
3215build:
3216 @echo "Building at: $(shell date)"
3217 gcc -o program main.c
3218"#;
3219 let parsed = parse(content);
3220 assert!(
3221 parsed.errors.is_empty(),
3222 "Failed to parse recipe with colon: {:?}",
3223 parsed.errors
3224 );
3225 }
3226
3227 #[test]
3228 #[ignore]
3229 fn test_double_colon_rules() {
3230 let content = r#"
3233%.o :: %.c
3234 $(CC) -c $< -o $@
3235
3236# Double colon allows multiple rules for same target
3237all:: prerequisite1
3238 @echo "First rule for all"
3239
3240all:: prerequisite2
3241 @echo "Second rule for all"
3242"#;
3243 let mut buf = content.as_bytes();
3244 let makefile =
3245 Makefile::read_relaxed(&mut buf).expect("Failed to parse double colon rules");
3246
3247 let rules = makefile.rules().collect::<Vec<_>>();
3249 assert!(!rules.is_empty(), "Expected at least one rule");
3250
3251 let all_rules = rules
3253 .iter()
3254 .filter(|r| r.targets().any(|t| t.contains("all")));
3255 assert!(
3256 all_rules.count() > 0,
3257 "Expected to find at least one rule containing 'all'"
3258 );
3259 }
3260
3261 #[test]
3262 fn test_elif_directive() {
3263 let content = r#"
3264ifeq ($(OS),Windows_NT)
3265 TARGET = windows
3266elif ifeq ($(OS),Darwin)
3267 TARGET = macos
3268elif ifeq ($(OS),Linux)
3269 TARGET = linux
3270else
3271 TARGET = unknown
3272endif
3273"#;
3274 let mut buf = content.as_bytes();
3276 let _makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse elif directive");
3277
3278 }
3281
3282 #[test]
3283 fn test_ambiguous_assignment_vs_rule() {
3284 const VAR_ASSIGNMENT: &str = "VARIABLE = value\n";
3286
3287 let mut buf = std::io::Cursor::new(VAR_ASSIGNMENT);
3288 let makefile =
3289 Makefile::read_relaxed(&mut buf).expect("Failed to parse variable assignment");
3290
3291 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3292 let rules = makefile.rules().collect::<Vec<_>>();
3293
3294 assert_eq!(vars.len(), 1, "Expected 1 variable, found {}", vars.len());
3295 assert_eq!(rules.len(), 0, "Expected 0 rules, found {}", rules.len());
3296
3297 assert_eq!(vars[0].name(), Some("VARIABLE".to_string()));
3298
3299 const SIMPLE_RULE: &str = "target: dependency\n";
3301
3302 let mut buf = std::io::Cursor::new(SIMPLE_RULE);
3303 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse simple rule");
3304
3305 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3306 let rules = makefile.rules().collect::<Vec<_>>();
3307
3308 assert_eq!(vars.len(), 0, "Expected 0 variables, found {}", vars.len());
3309 assert_eq!(rules.len(), 1, "Expected 1 rule, found {}", rules.len());
3310
3311 let rule = &rules[0];
3312 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target"]);
3313 }
3314
3315 #[test]
3316 fn test_nested_conditionals() {
3317 let content = r#"
3318ifdef RELEASE
3319 CFLAGS += -O3
3320 ifndef DEBUG
3321 ifneq ($(ARCH),arm)
3322 CFLAGS += -march=native
3323 else
3324 CFLAGS += -mcpu=cortex-a72
3325 endif
3326 endif
3327endif
3328"#;
3329 let mut buf = content.as_bytes();
3331 let makefile =
3332 Makefile::read_relaxed(&mut buf).expect("Failed to parse nested conditionals");
3333
3334 let code = makefile.code();
3336 assert!(code.contains("ifdef RELEASE"));
3337 assert!(code.contains("ifndef DEBUG"));
3338 assert!(code.contains("ifneq"));
3339 }
3340
3341 #[test]
3342 fn test_space_indented_recipes() {
3343 let content = r#"
3346build:
3347 @echo "Building with spaces instead of tabs"
3348 gcc -o program main.c
3349"#;
3350 let mut buf = content.as_bytes();
3352 let makefile =
3353 Makefile::read_relaxed(&mut buf).expect("Failed to parse space-indented recipes");
3354
3355 let rules = makefile.rules().collect::<Vec<_>>();
3357 assert!(!rules.is_empty(), "Expected at least one rule");
3358
3359 let build_rule = rules.iter().find(|r| r.targets().any(|t| t == "build"));
3361 assert!(build_rule.is_some(), "Expected to find build rule");
3362 }
3363
3364 #[test]
3365 fn test_complex_variable_functions() {
3366 let content = r#"
3367FILES := $(shell find . -name "*.c")
3368OBJS := $(patsubst %.c,%.o,$(FILES))
3369NAME := $(if $(PROGRAM),$(PROGRAM),a.out)
3370HEADERS := ${wildcard *.h}
3371"#;
3372 let parsed = parse(content);
3373 assert!(
3374 parsed.errors.is_empty(),
3375 "Failed to parse complex variable functions: {:?}",
3376 parsed.errors
3377 );
3378 }
3379
3380 #[test]
3381 fn test_nested_variable_expansions() {
3382 let content = r#"
3383VERSION = 1.0
3384PACKAGE = myapp
3385TARBALL = $(PACKAGE)-$(VERSION).tar.gz
3386INSTALL_PATH = $(shell echo $(PREFIX) | sed 's/\/$//')
3387"#;
3388 let parsed = parse(content);
3389 assert!(
3390 parsed.errors.is_empty(),
3391 "Failed to parse nested variable expansions: {:?}",
3392 parsed.errors
3393 );
3394 }
3395
3396 #[test]
3397 fn test_special_directives() {
3398 let content = r#"
3399# Special makefile directives
3400.PHONY: all clean
3401.SUFFIXES: .c .o
3402.DEFAULT: all
3403
3404# Variable definition and export directive
3405export PATH := /usr/bin:/bin
3406"#;
3407 let mut buf = content.as_bytes();
3409 let makefile =
3410 Makefile::read_relaxed(&mut buf).expect("Failed to parse special directives");
3411
3412 let rules = makefile.rules().collect::<Vec<_>>();
3414
3415 let phony_rule = rules
3417 .iter()
3418 .find(|r| r.targets().any(|t| t.contains(".PHONY")));
3419 assert!(phony_rule.is_some(), "Expected to find .PHONY rule");
3420
3421 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3423 assert!(!vars.is_empty(), "Expected to find at least one variable");
3424 }
3425
3426 #[test]
3429 fn test_comprehensive_real_world_makefile() {
3430 let content = r#"
3432# Basic variable assignment
3433VERSION = 1.0.0
3434
3435# Phony target
3436.PHONY: all clean
3437
3438# Simple rule
3439all:
3440 echo "Building version $(VERSION)"
3441
3442# Another rule with dependencies
3443clean:
3444 rm -f *.o
3445"#;
3446
3447 let parsed = parse(content);
3449
3450 assert!(parsed.errors.is_empty(), "Expected no parsing errors");
3452
3453 let variables = parsed.root().variable_definitions().collect::<Vec<_>>();
3455 assert!(!variables.is_empty(), "Expected at least one variable");
3456 assert_eq!(
3457 variables[0].name(),
3458 Some("VERSION".to_string()),
3459 "Expected VERSION variable"
3460 );
3461
3462 let rules = parsed.root().rules().collect::<Vec<_>>();
3464 assert!(!rules.is_empty(), "Expected at least one rule");
3465
3466 let rule_targets: Vec<String> = rules
3468 .iter()
3469 .flat_map(|r| r.targets().collect::<Vec<_>>())
3470 .collect();
3471 assert!(
3472 rule_targets.contains(&".PHONY".to_string()),
3473 "Expected .PHONY rule"
3474 );
3475 assert!(
3476 rule_targets.contains(&"all".to_string()),
3477 "Expected 'all' rule"
3478 );
3479 assert!(
3480 rule_targets.contains(&"clean".to_string()),
3481 "Expected 'clean' rule"
3482 );
3483 }
3484
3485 #[test]
3486 fn test_indented_help_text_outside_rules() {
3487 let content = r#"
3489# Targets with help text
3490help:
3491 @echo "Available targets:"
3492 @echo " build build the project"
3493 @echo " test run tests"
3494 @echo " clean clean build artifacts"
3495
3496# Another target
3497clean:
3498 rm -rf build/
3499"#;
3500
3501 let parsed = parse(content);
3503
3504 assert!(
3506 parsed.errors.is_empty(),
3507 "Failed to parse indented help text"
3508 );
3509
3510 let rules = parsed.root().rules().collect::<Vec<_>>();
3512 assert_eq!(rules.len(), 2, "Expected to find two rules");
3513
3514 let help_rule = rules
3516 .iter()
3517 .find(|r| r.targets().any(|t| t == "help"))
3518 .expect("Expected to find help rule");
3519
3520 let clean_rule = rules
3521 .iter()
3522 .find(|r| r.targets().any(|t| t == "clean"))
3523 .expect("Expected to find clean rule");
3524
3525 let help_recipes = help_rule.recipes().collect::<Vec<_>>();
3527 assert!(
3528 !help_recipes.is_empty(),
3529 "Help rule should have recipe lines"
3530 );
3531 assert!(
3532 help_recipes
3533 .iter()
3534 .any(|line| line.contains("Available targets")),
3535 "Help recipes should include 'Available targets' line"
3536 );
3537
3538 let clean_recipes = clean_rule.recipes().collect::<Vec<_>>();
3540 assert!(
3541 !clean_recipes.is_empty(),
3542 "Clean rule should have recipe lines"
3543 );
3544 assert!(
3545 clean_recipes.iter().any(|line| line.contains("rm -rf")),
3546 "Clean recipes should include 'rm -rf' command"
3547 );
3548 }
3549
3550 #[test]
3551 fn test_makefile1_phony_pattern() {
3552 let content = "#line 2145\n.PHONY: $(PHONY)\n";
3554
3555 let result = parse(content);
3557
3558 assert!(
3560 result.errors.is_empty(),
3561 "Failed to parse .PHONY: $(PHONY) pattern"
3562 );
3563
3564 let rules = result.root().rules().collect::<Vec<_>>();
3566 assert_eq!(rules.len(), 1, "Expected 1 rule");
3567 assert_eq!(
3568 rules[0].targets().next().unwrap(),
3569 ".PHONY",
3570 "Expected .PHONY rule"
3571 );
3572
3573 let prereqs = rules[0].prerequisites().collect::<Vec<_>>();
3575 assert_eq!(prereqs.len(), 1, "Expected 1 prerequisite");
3576 assert_eq!(prereqs[0], "$(PHONY)", "Expected $(PHONY) prerequisite");
3577 }
3578
3579 #[test]
3580 fn test_skip_until_newline_behavior() {
3581 let input = "text without newline";
3583 let parsed = parse(input);
3584 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
3586
3587 let input_with_newline = "text\nafter newline";
3588 let parsed2 = parse(input_with_newline);
3589 assert!(parsed2.errors.is_empty() || !parsed2.errors.is_empty());
3590 }
3591
3592 #[test]
3593 fn test_error_with_indent_token() {
3594 let input = "\tinvalid indented line";
3596 let parsed = parse(input);
3597 assert!(!parsed.errors.is_empty());
3599
3600 let error_msg = &parsed.errors[0].message;
3601 assert!(error_msg.contains("indented") || error_msg.contains("part of a rule"));
3602 }
3603
3604 #[test]
3605 fn test_conditional_token_handling() {
3606 let input = r#"
3608ifndef VAR
3609 CFLAGS = -DTEST
3610endif
3611"#;
3612 let parsed = parse(input);
3613 let makefile = parsed.root();
3615 let _vars = makefile.variable_definitions().collect::<Vec<_>>();
3616 let nested = r#"
3620ifdef DEBUG
3621 ifndef RELEASE
3622 CFLAGS = -g
3623 endif
3624endif
3625"#;
3626 let parsed_nested = parse(nested);
3627 let _makefile = parsed_nested.root();
3629 }
3630
3631 #[test]
3632 fn test_include_vs_conditional_logic() {
3633 let input = r#"
3635include file.mk
3636ifdef VAR
3637 VALUE = 1
3638endif
3639"#;
3640 let parsed = parse(input);
3641 let makefile = parsed.root();
3643 let includes = makefile.includes().collect::<Vec<_>>();
3644 assert!(includes.len() >= 1 || parsed.errors.len() > 0);
3646
3647 let optional_include = r#"
3649-include optional.mk
3650ifndef VAR
3651 VALUE = default
3652endif
3653"#;
3654 let parsed2 = parse(optional_include);
3655 let _makefile = parsed2.root();
3657 }
3658
3659 #[test]
3660 fn test_balanced_parens_counting() {
3661 let input = r#"
3663VAR = $(call func,$(nested,arg),extra)
3664COMPLEX = $(if $(condition),$(then_val),$(else_val))
3665"#;
3666 let parsed = parse(input);
3667 assert!(parsed.errors.is_empty());
3668
3669 let makefile = parsed.root();
3670 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3671 assert_eq!(vars.len(), 2);
3672 }
3673
3674 #[test]
3675 fn test_documentation_lookahead() {
3676 let input = r#"
3678# Documentation comment
3679help:
3680 @echo "Usage instructions"
3681 @echo "More help text"
3682"#;
3683 let parsed = parse(input);
3684 assert!(parsed.errors.is_empty());
3685
3686 let makefile = parsed.root();
3687 let rules = makefile.rules().collect::<Vec<_>>();
3688 assert_eq!(rules.len(), 1);
3689 assert_eq!(rules[0].targets().next().unwrap(), "help");
3690 }
3691
3692 #[test]
3693 fn test_edge_case_empty_input() {
3694 let parsed = parse("");
3696 assert!(parsed.errors.is_empty());
3697
3698 let parsed2 = parse(" \n \n");
3700 let _makefile = parsed2.root();
3703 }
3704
3705 #[test]
3706 fn test_malformed_conditional_recovery() {
3707 let input = r#"
3709ifdef
3710 # Missing condition variable
3711endif
3712"#;
3713 let parsed = parse(input);
3714 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
3717 }
3718
3719 #[test]
3720 fn test_replace_rule() {
3721 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
3722 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
3723
3724 makefile.replace_rule(0, new_rule).unwrap();
3725
3726 let targets: Vec<_> = makefile
3727 .rules()
3728 .flat_map(|r| r.targets().collect::<Vec<_>>())
3729 .collect();
3730 assert_eq!(targets, vec!["new_rule", "rule2"]);
3731
3732 let recipes: Vec<_> = makefile.rules().next().unwrap().recipes().collect();
3733 assert_eq!(recipes, vec!["new_command"]);
3734 }
3735
3736 #[test]
3737 fn test_replace_rule_out_of_bounds() {
3738 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
3739 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
3740
3741 let result = makefile.replace_rule(5, new_rule);
3742 assert!(result.is_err());
3743 }
3744
3745 #[test]
3746 fn test_remove_rule() {
3747 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\nrule3:\n\tcommand3\n"
3748 .parse()
3749 .unwrap();
3750
3751 let removed = makefile.remove_rule(1).unwrap();
3752 assert_eq!(removed.targets().collect::<Vec<_>>(), vec!["rule2"]);
3753
3754 let remaining_targets: Vec<_> = makefile
3755 .rules()
3756 .flat_map(|r| r.targets().collect::<Vec<_>>())
3757 .collect();
3758 assert_eq!(remaining_targets, vec!["rule1", "rule3"]);
3759 assert_eq!(makefile.rules().count(), 2);
3760 }
3761
3762 #[test]
3763 fn test_remove_rule_out_of_bounds() {
3764 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
3765
3766 let result = makefile.remove_rule(5);
3767 assert!(result.is_err());
3768 }
3769
3770 #[test]
3771 fn test_insert_rule() {
3772 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
3773 let new_rule: Rule = "inserted_rule:\n\tinserted_command\n".parse().unwrap();
3774
3775 makefile.insert_rule(1, new_rule).unwrap();
3776
3777 let targets: Vec<_> = makefile
3778 .rules()
3779 .flat_map(|r| r.targets().collect::<Vec<_>>())
3780 .collect();
3781 assert_eq!(targets, vec!["rule1", "inserted_rule", "rule2"]);
3782 assert_eq!(makefile.rules().count(), 3);
3783 }
3784
3785 #[test]
3786 fn test_insert_rule_at_end() {
3787 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
3788 let new_rule: Rule = "end_rule:\n\tend_command\n".parse().unwrap();
3789
3790 makefile.insert_rule(1, new_rule).unwrap();
3791
3792 let targets: Vec<_> = makefile
3793 .rules()
3794 .flat_map(|r| r.targets().collect::<Vec<_>>())
3795 .collect();
3796 assert_eq!(targets, vec!["rule1", "end_rule"]);
3797 }
3798
3799 #[test]
3800 fn test_insert_rule_out_of_bounds() {
3801 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
3802 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
3803
3804 let result = makefile.insert_rule(5, new_rule);
3805 assert!(result.is_err());
3806 }
3807
3808 #[test]
3809 fn test_remove_command() {
3810 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
3811 .parse()
3812 .unwrap();
3813
3814 rule.remove_command(1);
3815 let recipes: Vec<_> = rule.recipes().collect();
3816 assert_eq!(recipes, vec!["command1", "command3"]);
3817 assert_eq!(rule.recipe_count(), 2);
3818 }
3819
3820 #[test]
3821 fn test_remove_command_out_of_bounds() {
3822 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
3823
3824 let result = rule.remove_command(5);
3825 assert!(!result);
3826 }
3827
3828 #[test]
3829 fn test_insert_command() {
3830 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand3\n".parse().unwrap();
3831
3832 rule.insert_command(1, "command2");
3833 let recipes: Vec<_> = rule.recipes().collect();
3834 assert_eq!(recipes, vec!["command1", "command2", "command3"]);
3835 }
3836
3837 #[test]
3838 fn test_insert_command_at_end() {
3839 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
3840
3841 rule.insert_command(1, "command2");
3842 let recipes: Vec<_> = rule.recipes().collect();
3843 assert_eq!(recipes, vec!["command1", "command2"]);
3844 }
3845
3846 #[test]
3847 fn test_insert_command_in_empty_rule() {
3848 let mut rule: Rule = "rule:\n".parse().unwrap();
3849
3850 rule.insert_command(0, "new_command");
3851 let recipes: Vec<_> = rule.recipes().collect();
3852 assert_eq!(recipes, vec!["new_command"]);
3853 }
3854
3855 #[test]
3856 fn test_recipe_count() {
3857 let rule1: Rule = "rule:\n".parse().unwrap();
3858 assert_eq!(rule1.recipe_count(), 0);
3859
3860 let rule2: Rule = "rule:\n\tcommand1\n\tcommand2\n".parse().unwrap();
3861 assert_eq!(rule2.recipe_count(), 2);
3862 }
3863
3864 #[test]
3865 fn test_clear_commands() {
3866 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
3867 .parse()
3868 .unwrap();
3869
3870 rule.clear_commands();
3871 assert_eq!(rule.recipe_count(), 0);
3872
3873 let recipes: Vec<_> = rule.recipes().collect();
3874 assert_eq!(recipes, Vec::<String>::new());
3875
3876 let targets: Vec<_> = rule.targets().collect();
3878 assert_eq!(targets, vec!["rule"]);
3879 }
3880
3881 #[test]
3882 fn test_clear_commands_empty_rule() {
3883 let mut rule: Rule = "rule:\n".parse().unwrap();
3884
3885 rule.clear_commands();
3886 assert_eq!(rule.recipe_count(), 0);
3887
3888 let targets: Vec<_> = rule.targets().collect();
3889 assert_eq!(targets, vec!["rule"]);
3890 }
3891
3892 #[test]
3893 fn test_rule_manipulation_preserves_structure() {
3894 let input = r#"# Comment
3896VAR = value
3897
3898rule1:
3899 command1
3900
3901# Another comment
3902rule2:
3903 command2
3904
3905VAR2 = value2
3906"#;
3907
3908 let mut makefile: Makefile = input.parse().unwrap();
3909 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
3910
3911 makefile.insert_rule(1, new_rule).unwrap();
3913
3914 let targets: Vec<_> = makefile
3916 .rules()
3917 .flat_map(|r| r.targets().collect::<Vec<_>>())
3918 .collect();
3919 assert_eq!(targets, vec!["rule1", "new_rule", "rule2"]);
3920
3921 let vars: Vec<_> = makefile.variable_definitions().collect();
3923 assert_eq!(vars.len(), 2);
3924
3925 let output = makefile.code();
3927 assert!(output.contains("# Comment"));
3928 assert!(output.contains("VAR = value"));
3929 assert!(output.contains("# Another comment"));
3930 assert!(output.contains("VAR2 = value2"));
3931 }
3932
3933 #[test]
3934 fn test_replace_rule_with_multiple_targets() {
3935 let mut makefile: Makefile = "target1 target2: dep\n\tcommand\n".parse().unwrap();
3936 let new_rule: Rule = "new_target: new_dep\n\tnew_command\n".parse().unwrap();
3937
3938 makefile.replace_rule(0, new_rule).unwrap();
3939
3940 let targets: Vec<_> = makefile
3941 .rules()
3942 .flat_map(|r| r.targets().collect::<Vec<_>>())
3943 .collect();
3944 assert_eq!(targets, vec!["new_target"]);
3945 }
3946
3947 #[test]
3948 fn test_empty_makefile_operations() {
3949 let mut makefile = Makefile::new();
3950
3951 assert!(makefile
3953 .replace_rule(0, "rule:\n\tcommand\n".parse().unwrap())
3954 .is_err());
3955 assert!(makefile.remove_rule(0).is_err());
3956
3957 let new_rule: Rule = "first_rule:\n\tcommand\n".parse().unwrap();
3959 makefile.insert_rule(0, new_rule).unwrap();
3960 assert_eq!(makefile.rules().count(), 1);
3961 }
3962
3963 #[test]
3964 fn test_command_operations_preserve_indentation() {
3965 let mut rule: Rule = "rule:\n\t\tdeep_indent\n\tshallow_indent\n"
3966 .parse()
3967 .unwrap();
3968
3969 rule.insert_command(1, "middle_command");
3970 let recipes: Vec<_> = rule.recipes().collect();
3971 assert_eq!(
3972 recipes,
3973 vec!["\tdeep_indent", "middle_command", "shallow_indent"]
3974 );
3975 }
3976
3977 #[test]
3978 fn test_rule_operations_with_variables_and_includes() {
3979 let input = r#"VAR1 = value1
3980include common.mk
3981
3982rule1:
3983 command1
3984
3985VAR2 = value2
3986include other.mk
3987
3988rule2:
3989 command2
3990"#;
3991
3992 let mut makefile: Makefile = input.parse().unwrap();
3993
3994 makefile.remove_rule(0).unwrap();
3996
3997 let output = makefile.code();
3999 assert!(output.contains("VAR1 = value1"));
4000 assert!(output.contains("include common.mk"));
4001 assert!(output.contains("VAR2 = value2"));
4002 assert!(output.contains("include other.mk"));
4003
4004 assert_eq!(makefile.rules().count(), 1);
4006 let remaining_targets: Vec<_> = makefile
4007 .rules()
4008 .flat_map(|r| r.targets().collect::<Vec<_>>())
4009 .collect();
4010 assert_eq!(remaining_targets, vec!["rule2"]);
4011 }
4012
4013 #[test]
4014 fn test_command_manipulation_edge_cases() {
4015 let mut empty_rule: Rule = "empty:\n".parse().unwrap();
4017 assert_eq!(empty_rule.recipe_count(), 0);
4018
4019 empty_rule.insert_command(0, "first_command");
4020 assert_eq!(empty_rule.recipe_count(), 1);
4021
4022 let mut empty_rule2: Rule = "empty:\n".parse().unwrap();
4024 empty_rule2.clear_commands();
4025 assert_eq!(empty_rule2.recipe_count(), 0);
4026 }
4027
4028 #[test]
4029 fn test_archive_member_parsing() {
4030 let input = "libfoo.a(bar.o): bar.c\n\tgcc -c bar.c -o bar.o\n\tar r libfoo.a bar.o\n";
4032 let parsed = parse(input);
4033 assert!(
4034 parsed.errors.is_empty(),
4035 "Should parse archive member without errors"
4036 );
4037
4038 let makefile = parsed.root();
4039 let rules: Vec<_> = makefile.rules().collect();
4040 assert_eq!(rules.len(), 1);
4041
4042 let target_text = rules[0].targets().next().unwrap();
4044 assert_eq!(target_text, "libfoo.a(bar.o)");
4045 }
4046
4047 #[test]
4048 fn test_archive_member_multiple_members() {
4049 let input = "libfoo.a(bar.o baz.o): bar.c baz.c\n\tgcc -c bar.c baz.c\n\tar r libfoo.a bar.o baz.o\n";
4051 let parsed = parse(input);
4052 assert!(
4053 parsed.errors.is_empty(),
4054 "Should parse multiple archive members"
4055 );
4056
4057 let makefile = parsed.root();
4058 let rules: Vec<_> = makefile.rules().collect();
4059 assert_eq!(rules.len(), 1);
4060 }
4061
4062 #[test]
4063 fn test_archive_member_in_dependencies() {
4064 let input =
4066 "program: main.o libfoo.a(bar.o) libfoo.a(baz.o)\n\tgcc -o program main.o libfoo.a\n";
4067 let parsed = parse(input);
4068 assert!(
4069 parsed.errors.is_empty(),
4070 "Should parse archive members in dependencies"
4071 );
4072
4073 let makefile = parsed.root();
4074 let rules: Vec<_> = makefile.rules().collect();
4075 assert_eq!(rules.len(), 1);
4076 }
4077
4078 #[test]
4079 fn test_archive_member_with_variables() {
4080 let input = "$(LIB)($(OBJ)): $(SRC)\n\t$(CC) -c $(SRC)\n\t$(AR) r $(LIB) $(OBJ)\n";
4082 let parsed = parse(input);
4083 assert!(
4085 parsed.errors.is_empty(),
4086 "Should parse archive members with variables"
4087 );
4088 }
4089
4090 #[test]
4091 fn test_archive_member_ast_access() {
4092 let input = "libtest.a(foo.o bar.o): foo.c bar.c\n\tgcc -c foo.c bar.c\n";
4094 let parsed = parse(input);
4095 let makefile = parsed.root();
4096
4097 let archive_member_count = makefile
4099 .syntax()
4100 .descendants()
4101 .filter(|n| n.kind() == ARCHIVE_MEMBERS)
4102 .count();
4103
4104 assert!(
4105 archive_member_count > 0,
4106 "Should find ARCHIVE_MEMBERS nodes in AST"
4107 );
4108 }
4109
4110 #[test]
4111 fn test_large_makefile_performance() {
4112 let mut makefile = Makefile::new();
4114
4115 for i in 0..100 {
4117 let rule_name = format!("rule{}", i);
4118 let _rule = makefile
4119 .add_rule(&rule_name)
4120 .push_command(&format!("command{}", i));
4121 }
4122
4123 assert_eq!(makefile.rules().count(), 100);
4124
4125 let new_rule: Rule = "middle_rule:\n\tmiddle_command\n".parse().unwrap();
4127 makefile.replace_rule(50, new_rule).unwrap();
4128
4129 let rule_50_targets: Vec<_> = makefile.rules().nth(50).unwrap().targets().collect();
4131 assert_eq!(rule_50_targets, vec!["middle_rule"]);
4132
4133 assert_eq!(makefile.rules().count(), 100); }
4135
4136 #[test]
4137 fn test_complex_recipe_manipulation() {
4138 let mut complex_rule: Rule = r#"complex:
4139 @echo "Starting build"
4140 $(CC) $(CFLAGS) -o $@ $<
4141 @echo "Build complete"
4142 chmod +x $@
4143"#
4144 .parse()
4145 .unwrap();
4146
4147 assert_eq!(complex_rule.recipe_count(), 4);
4148
4149 complex_rule.remove_command(0); complex_rule.remove_command(1); let final_recipes: Vec<_> = complex_rule.recipes().collect();
4154 assert_eq!(final_recipes.len(), 2);
4155 assert!(final_recipes[0].contains("$(CC)"));
4156 assert!(final_recipes[1].contains("chmod"));
4157 }
4158
4159 #[test]
4160 fn test_variable_definition_remove() {
4161 let makefile: Makefile = r#"VAR1 = value1
4162VAR2 = value2
4163VAR3 = value3
4164"#
4165 .parse()
4166 .unwrap();
4167
4168 assert_eq!(makefile.variable_definitions().count(), 3);
4170
4171 let mut var2 = makefile
4173 .variable_definitions()
4174 .nth(1)
4175 .expect("Should have second variable");
4176 assert_eq!(var2.name(), Some("VAR2".to_string()));
4177 var2.remove();
4178
4179 assert_eq!(makefile.variable_definitions().count(), 2);
4181 let var_names: Vec<_> = makefile
4182 .variable_definitions()
4183 .filter_map(|v| v.name())
4184 .collect();
4185 assert_eq!(var_names, vec!["VAR1", "VAR3"]);
4186 }
4187
4188 #[test]
4189 fn test_variable_definition_set_value() {
4190 let makefile: Makefile = "VAR = old_value\n".parse().unwrap();
4191
4192 let mut var = makefile
4193 .variable_definitions()
4194 .next()
4195 .expect("Should have variable");
4196 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4197
4198 var.set_value("new_value");
4200
4201 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4203 assert!(makefile.code().contains("VAR = new_value"));
4204 }
4205
4206 #[test]
4207 fn test_variable_definition_set_value_preserves_format() {
4208 let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
4209
4210 let mut var = makefile
4211 .variable_definitions()
4212 .next()
4213 .expect("Should have variable");
4214 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4215
4216 var.set_value("new_value");
4218
4219 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4221 let code = makefile.code();
4222 assert!(code.contains("export"), "Should preserve export prefix");
4223 assert!(code.contains(":="), "Should preserve := operator");
4224 assert!(code.contains("new_value"), "Should have new value");
4225 }
4226
4227 #[test]
4228 fn test_makefile_find_variable() {
4229 let makefile: Makefile = r#"VAR1 = value1
4230VAR2 = value2
4231VAR3 = value3
4232"#
4233 .parse()
4234 .unwrap();
4235
4236 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4238 assert_eq!(vars.len(), 1);
4239 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4240 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4241
4242 assert_eq!(makefile.find_variable("NONEXISTENT").count(), 0);
4244 }
4245
4246 #[test]
4247 fn test_makefile_find_variable_with_export() {
4248 let makefile: Makefile = r#"VAR1 = value1
4249export VAR2 := value2
4250VAR3 = value3
4251"#
4252 .parse()
4253 .unwrap();
4254
4255 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4257 assert_eq!(vars.len(), 1);
4258 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4259 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4260 }
4261
4262 #[test]
4263 fn test_makefile_find_variable_multiple() {
4264 let makefile: Makefile = r#"VAR1 = value1
4265VAR1 = value2
4266VAR2 = other
4267VAR1 = value3
4268"#
4269 .parse()
4270 .unwrap();
4271
4272 let vars: Vec<_> = makefile.find_variable("VAR1").collect();
4274 assert_eq!(vars.len(), 3);
4275 assert_eq!(vars[0].raw_value(), Some("value1".to_string()));
4276 assert_eq!(vars[1].raw_value(), Some("value2".to_string()));
4277 assert_eq!(vars[2].raw_value(), Some("value3".to_string()));
4278
4279 let var2s: Vec<_> = makefile.find_variable("VAR2").collect();
4281 assert_eq!(var2s.len(), 1);
4282 assert_eq!(var2s[0].raw_value(), Some("other".to_string()));
4283 }
4284
4285 #[test]
4286 fn test_variable_remove_and_find() {
4287 let makefile: Makefile = r#"VAR1 = value1
4288VAR2 = value2
4289VAR3 = value3
4290"#
4291 .parse()
4292 .unwrap();
4293
4294 let mut var2 = makefile
4296 .find_variable("VAR2")
4297 .next()
4298 .expect("Should find VAR2");
4299 var2.remove();
4300
4301 assert_eq!(makefile.find_variable("VAR2").count(), 0);
4303
4304 assert_eq!(makefile.find_variable("VAR1").count(), 1);
4306 assert_eq!(makefile.find_variable("VAR3").count(), 1);
4307 }
4308}