From c4397daad872ef00f2805f5366ecb4b5e651acea Mon Sep 17 00:00:00 2001
From: Moncef STITI <moncef.stiti@etu.u-pec.fr>
Date: Sun, 16 Mar 2025 17:00:22 +0100
Subject: [PATCH] Correction bug

---
 src/fr/monlouyan/bakefile/BakefileParser.java | 480 ++++++++++--------
 .../monlouyan/bakefile/CommandExecutor.java   |  22 +-
 2 files changed, 276 insertions(+), 226 deletions(-)

diff --git a/src/fr/monlouyan/bakefile/BakefileParser.java b/src/fr/monlouyan/bakefile/BakefileParser.java
index a308e70..b374a77 100644
--- a/src/fr/monlouyan/bakefile/BakefileParser.java
+++ b/src/fr/monlouyan/bakefile/BakefileParser.java
@@ -58,7 +58,7 @@ public class BakefileParser {
 	 * Regex pour détecter les références de variables.
 	 * Format : "${VAR}" ou "$(VAR)"
 	 */
-    private static final Pattern VARIABLE_REFERENCE = Pattern.compile("\\$\\{(\\w+)\\}|\\$\\((\\w+)\\)");
+    public static final Pattern VARIABLE_REFERENCE = Pattern.compile("\\$\\{(\\w+)\\}|\\$\\((\\w+)\\)");
     
 	/**
      * Première cible trouvée dans le fichier Bakefile.
@@ -68,7 +68,7 @@ public class BakefileParser {
 	/**
      * Stocke les variables définies dans le Bakefile.
      */
-    private Map<String, String> variables = new HashMap<>();
+    private static Map<String, String> variables = new HashMap<>();
     
 	/**
 	 * Constructeur de la classe BakefileParser.
@@ -151,10 +151,13 @@ public class BakefileParser {
 			
 			while (matcher.find()) {
 				String varName = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
-				// Modification ici: remplacer par la valeur de la variable ou par une chaîne vide si elle n'existe pas
+				
+				// Vérifier si la variable existe
 				String replacement = "";
 				if (variables.containsKey(varName)) {
-					replacement = variables.get(varName);
+					String varValue = variables.get(varName);
+					// Ne pas remplacer récursivement ici pour éviter les boucles infinies
+					replacement = varValue;
 				} else if (BakeCLI.isDebug()) {
 					System.out.println("Debug: Variable '" + varName + "' not defined, replacing with empty string");
 				}
@@ -179,17 +182,6 @@ public class BakefileParser {
 		return result.trim();
 	}
 
-	/**
-	 * Remplacer les variables dans une liste de chaînes.
-	 * @param items Liste de chaînes à traiter
-	 * @return Liste de chaînes avec les variables remplacées
-	 */
-    private List<String> replaceVariablesInList(List<String> items) {
-        return items.stream()
-                   .map(this::replaceVariables)
-                   .collect(Collectors.toList());
-    }
-
 	/**
 	 * Découper les dépendances en une liste de chaînes.
 	 * @param depStr Chaîne de dépendances
@@ -227,215 +219,263 @@ public class BakefileParser {
     }
 
 	/**
- * Analyser le fichier Bakefile pour extraire les règles de build.
- * @return Liste des règles extraites
- */
-public List<Rule> parse() {
-    List<Rule> rules = new ArrayList<>();
-    Set<String> phonyTargets = new HashSet<>();
+	 * Analyser le fichier Bakefile pour extraire les règles de build.
+	 * @return Liste des règles extraites
+	 */
+	public List<Rule> parse() {
+		List<Rule> rules = new ArrayList<>();
+		Set<String> phonyTargets = new HashSet<>();
 
-    if (!Files.exists(Paths.get(filename))) {
-        System.out.println("*** No targets specified and no makefile found.  Stop.");
-        System.exit(2);
-    }       
+		if (!Files.exists(Paths.get(filename))) {
+			System.out.println("*** No targets specified and no makefile found.  Stop.");
+			System.exit(2);
+		}       
 
-    try {
-        List<String> lines = Files.readAllLines(Paths.get(filename));
-        List<String> currentTargets = null;
-        List<String> dependencies = new ArrayList<>();
-        List<String> commands = new ArrayList<>();
-        List<List<String>> displayCommands = new ArrayList<>();
-        
-        // Variable pour suivre si la ligne précédente était une continuation
-        boolean previousLineContinues = false;
-        
-        for (int i = 0; i < lines.size(); i++) {
-            String line = lines.get(i).replace("\r", "");
-            
-            // Ignorer les lignes vides
-            if (line.trim().isEmpty()) {
-                previousLineContinues = false; // Réinitialiser le flag de continuation
-                continue;
-            }
-            
-            // Gérer les erreurs de format (espaces au lieu de tabulations)
-            // Mais uniquement si ce n'est pas une continuation de variable
-            if (line.matches("^ +.*$") && !previousLineContinues) {
-                System.err.println(filename + ":" + (i+1) + ": *** missing separator.  Stop.");
-                System.exit(2);
-            }
-            
-            // Vérifier si cette ligne se termine par un backslash (continuation)
-            previousLineContinues = line.trim().endsWith("\\");
-            
-            // Matcher pour les déclarations .PHONY
-            Matcher phonyMatcher = PHONY_PATTERN.matcher(line);
-            if (phonyMatcher.matches()) {
-                String[] phonies = phonyMatcher.group(1).trim().split("\\s+");
-                Collections.addAll(phonyTargets, phonies);
-                continue;
-            }
-            
-            // Matcher pour les déclarations de variables
-            Matcher varMatcher = VARIABLE_PATTERN.matcher(line);
-            if (varMatcher.matches()) {
-                String varName = varMatcher.group(1);
-                String varValue = varMatcher.group(2).trim();
-                
-                // Vérifier si la ligne se termine par un backslash (continuation)
-                if (varValue.endsWith("\\")) {
-                    StringBuilder fullValue = new StringBuilder(varValue.substring(0, varValue.length() - 1).trim());
-                    int j = i + 1;
-                    
-                    while (j < lines.size()) {
-                        String nextLine = lines.get(j).trim();
-                        
-                        if (nextLine.endsWith("\\")) {
-                            fullValue.append(" ").append(nextLine.substring(0, nextLine.length() - 1).trim());
-                            j++;
-                        } else {
-                            fullValue.append(" ").append(nextLine);
-                            i = j; // Mettre à jour l'indice principal pour sauter les lignes traitées
-                            break;
-                        }
-                    }
-                    
-                    varValue = fullValue.toString();
-                }
-                
-                // Évaluer les variables référencées dans la valeur
-                varValue = replaceVariables(varValue);
-                variables.put(varName, varValue);
-                
-                if (BakeCLI.isDebug()) {
-                    System.out.println("Debug: Variable defined: " + varName + " = " + varValue);
-                }
-                continue;
-            }
-            
-            // Matcher pour les cibles et dépendances
-            Matcher targetMatcher = TARGET_PATTERN.matcher(line);
-            if (targetMatcher.matches()) {
-                // Si nous avions des cibles précédentes, créons les règles correspondantes
-                if (currentTargets != null) {
-                    // Créer une règle pour chaque cible avec les mêmes dépendances et commandes
-                    for (String target : currentTargets) {
-                        String resolvedTarget = replaceVariables(target.trim());
-                        rules.add(new Rule(
-                            resolvedTarget,
-                            replaceVariablesInList(dependencies),
-                            replaceVariablesInList(commands),
-                            displayCommands,
-                            phonyTargets.contains(resolvedTarget)
-                        ));
-                        
-                        if (firstTarget == null) {
-                            firstTarget = resolvedTarget;
-                        }
-                    }
-                }
-                
-                // Configuration pour les nouvelles cibles
-                String targetStr = targetMatcher.group(1);
-                if (BakeCLI.isDebug()) {
-                    System.out.println("Debug: Raw target(s): " + targetStr);
-                }
-                currentTargets = splitTargets(targetStr);
-                
-                if (BakeCLI.isDebug()) {
-                    System.out.println("Debug: Resolved targets: " + currentTargets);
-                }
-                
-                String depStr = targetMatcher.group(2);
-                dependencies = splitDependencies(depStr);
-                commands = new ArrayList<>();
-                displayCommands = new ArrayList<>();
-                continue;
-            }
-            
-            // Matcher pour les lignes de commande
-            Matcher commandMatcher = COMMAND_PATTERN.matcher(line);
-            if (commandMatcher.matches()) {
-                String command = commandMatcher.group(1);
-                
-                // Gérer la continuation de ligne
-                if (command.endsWith("\\")) {
-                    // Traiter la séquence complète de continuation
-                    Object[] result = handleContinuationLines(lines, i);
-                    String fullCommand = (String)result[0];
-                    @SuppressWarnings("unchecked")
-                    List<String> rawLines = (List<String>)result[1];
-                    int linesUsed = (Integer)result[2];
-                    
-                    // Ajouter la commande complète pour l'exécution
-                    commands.add(fullCommand);
-                    
-                    // Ajouter les lignes brutes pour l'affichage
-                    displayCommands.add(rawLines);
-                    
-                    // Ajuster i pour sauter les lignes traitées (moins 1 car la boucle for incrémente i)
-                    i += linesUsed - 1;
-                } else {
-                    String executableCommand = command;
-                    commands.add(executableCommand);
-                    // Pour l'affichage, préserver le formatage mais remplacer les variables
-                    String displayLine = line;
-                    // Ne pas modifier les lignes qui commencent par @ (silencieuses)
-                    if (command.startsWith("@")) {
-                        displayLine = line; // Garder le formatage complet pour les commandes silencieuses
-                    } else {
-                        // Remplacer les variables dans la partie de la commande uniquement (après la tabulation)
-                        Matcher cmdMatcher = COMMAND_PATTERN.matcher(line);
-                        if (cmdMatcher.matches()) {
-                            String cmdPart = cmdMatcher.group(1);
-                            String cmdWithVars = replaceVariables(cmdPart);
-                            displayLine = "\t" + cmdWithVars;
-                        }
-                    }
-                    List<String> singleLineDisplay = new ArrayList<>();
-                    singleLineDisplay.add(displayLine);
-                    displayCommands.add(singleLineDisplay);
-                }
-                continue;
-            }
-        }
+		try {
+			List<String> lines = Files.readAllLines(Paths.get(filename));
+			List<String> currentTargets = null;
+			List<String> dependencies = new ArrayList<>();
+			List<String> commands = new ArrayList<>();
+			List<List<String>> displayCommands = new ArrayList<>();
+			
+			// Variable pour suivre si la ligne précédente était une continuation
+			boolean previousLineContinues = false;
+			
+			for (int i = 0; i < lines.size(); i++) {
+				String line = lines.get(i).replace("\r", "");
+				
+				// Ignorer les lignes vides
+				if (line.trim().isEmpty()) {
+					previousLineContinues = false; // Réinitialiser le flag de continuation
+					continue;
+				}
+				
+				// Gérer les erreurs de format (espaces au lieu de tabulations)
+				// Mais uniquement si ce n'est pas une continuation de variable
+				if (line.matches("^ +.*$") && !previousLineContinues) {
+					System.err.println(filename + ":" + (i+1) + ": *** missing separator.  Stop.");
+					System.exit(2);
+				}
+				
+				// Vérifier si cette ligne se termine par un backslash (continuation)
+				previousLineContinues = line.trim().endsWith("\\");
+				
+				// Matcher pour les déclarations .PHONY
+				Matcher phonyMatcher = PHONY_PATTERN.matcher(line);
+				if (phonyMatcher.matches()) {
+					String[] phonies = phonyMatcher.group(1).trim().split("\\s+");
+					Collections.addAll(phonyTargets, phonies);
+					continue;
+				}
+				
+				// Matcher pour les déclarations de variables
+				Matcher varMatcher = VARIABLE_PATTERN.matcher(line);
+				if (varMatcher.matches()) {
+					String varName = varMatcher.group(1);
+					String varValue = varMatcher.group(2).trim();
+					
+					// Vérifier si la ligne se termine par un backslash (continuation)
+					if (varValue.endsWith("\\")) {
+						StringBuilder fullValue = new StringBuilder(varValue.substring(0, varValue.length() - 1).trim());
+						int j = i + 1;
+						
+						while (j < lines.size()) {
+							String nextLine = lines.get(j).trim();
+							
+							if (nextLine.endsWith("\\")) {
+								fullValue.append(" ").append(nextLine.substring(0, nextLine.length() - 1).trim());
+								j++;
+							} else {
+								fullValue.append(" ").append(nextLine);
+								i = j; // Mettre à jour l'indice principal pour sauter les lignes traitées
+								break;
+							}
+						}
+						
+						varValue = fullValue.toString();
+					}
+					
+					// Évaluer les variables référencées dans la valeur
+					variables.put(varName, varValue);
+					
+					if (BakeCLI.isDebug()) {
+						System.out.println("Debug: Variable defined: " + varName + " = " + varValue);
+					}
+					continue;
+				}
+				
+				// Matcher pour les cibles et dépendances
+				Matcher targetMatcher = TARGET_PATTERN.matcher(line);
+				if (targetMatcher.matches()) {
+					// Si nous avions des cibles précédentes, créons les règles correspondantes
+					if (currentTargets != null) {
+						// Créer une règle pour chaque cible avec les mêmes dépendances et commandes
+						for (String target : currentTargets) {
+							String resolvedTarget = replaceVariables(target.trim());
+							rules.add(new Rule(
+								resolvedTarget,
+								dependencies,
+								commands,
+								displayCommands,
+								phonyTargets.contains(resolvedTarget)
+							));
+							
+							if (firstTarget == null) {
+								firstTarget = resolvedTarget;
+							}
+						}
+					}
+					
+					// Configuration pour les nouvelles cibles
+					String targetStr = targetMatcher.group(1);
+					if (BakeCLI.isDebug()) {
+						System.out.println("Debug: Raw target(s): " + targetStr);
+					}
+					currentTargets = splitTargets(targetStr);
+					
+					if (BakeCLI.isDebug()) {
+						System.out.println("Debug: Resolved targets: " + currentTargets);
+					}
+					
+					String depStr = targetMatcher.group(2);
+					dependencies = splitDependencies(depStr);
+					commands = new ArrayList<>();
+					displayCommands = new ArrayList<>();
+					continue;
+				}
+				
+				// Matcher pour les lignes de commande
+				Matcher commandMatcher = COMMAND_PATTERN.matcher(line);
+				if (commandMatcher.matches()) {
+					String command = commandMatcher.group(1);
+					
+					// Gérer la continuation de ligne
+					if (command.endsWith("\\")) {
+						// Traiter la séquence complète de continuation
+						Object[] result = handleContinuationLines(lines, i);
+						String fullCommand = (String)result[0];
+						@SuppressWarnings("unchecked")
+						List<String> rawLines = (List<String>)result[1];
+						int linesUsed = (Integer)result[2];
+						
+						// Ajouter la commande complète pour l'exécution
+						commands.add(fullCommand);
+						
+						// Ajouter les lignes brutes pour l'affichage
+						displayCommands.add(rawLines);
+						
+						// Ajuster i pour sauter les lignes traitées (moins 1 car la boucle for incrémente i)
+						i += linesUsed - 1;
+					} else {
+						String executableCommand = command;
+						commands.add(executableCommand);
+						// Pour l'affichage, préserver le formatage mais remplacer les variables
+						String displayLine = line;
+						// Ne pas modifier les lignes qui commencent par @ (silencieuses)
+						if (command.startsWith("@")) {
+							displayLine = line; // Garder le formatage complet pour les commandes silencieuses
+						} else {
+							// Remplacer les variables dans la partie de la commande uniquement (après la tabulation)
+							Matcher cmdMatcher = COMMAND_PATTERN.matcher(line);
+							if (cmdMatcher.matches()) {
+								String cmdPart = cmdMatcher.group(1);
+								displayLine = "\t" + cmdPart;  // <= Garder les références aux variables
+							}
+						}
+						List<String> singleLineDisplay = new ArrayList<>();
+						singleLineDisplay.add(displayLine);
+						displayCommands.add(singleLineDisplay);
+					}
+					continue;
+				}
+			}
 
-        // Traiter les dernières cibles
-        if (currentTargets != null) {
-            // Créer une règle pour chaque cible avec les mêmes dépendances et commandes
-            for (String target : currentTargets) {
-                String resolvedTarget = replaceVariables(target.trim());
-                rules.add(new Rule(
-                    resolvedTarget,
-                    replaceVariablesInList(dependencies),
-                    replaceVariablesInList(commands),
-                    displayCommands,
-                    phonyTargets.contains(resolvedTarget)
-                ));
-                
-                if (firstTarget == null) {
-                    firstTarget = resolvedTarget;
-                }
-            }
-        }
-        
-        if (BakeCLI.isDebug()) {
-            System.out.println("Debug: First target is: " + firstTarget);
-            System.out.println("Debug: Parsed " + rules.size() + " rules.");
-            for (Rule rule : rules) {
-                System.out.println("Debug: Rule: " + rule.getName());
-                System.out.println("Debug:   Commands: " + rule.getCommands().size());
-                for (String cmd : rule.getCommands()) {
-                    System.out.println("Debug:     [" + cmd + "]");
-                }
-            }
-        }
+			// Traiter les dernières cibles
+			if (currentTargets != null) {
+				// Créer une règle pour chaque cible avec les mêmes dépendances et commandes
+				for (String target : currentTargets) {
+					String resolvedTarget = replaceVariables(target.trim());
+					rules.add(new Rule(
+						resolvedTarget,
+						dependencies,
+						commands,
+						displayCommands,
+						phonyTargets.contains(resolvedTarget)
+					));
+					
+					if (firstTarget == null) {
+						firstTarget = resolvedTarget;
+					}
+				}
+			}
+			
+			if (BakeCLI.isDebug()) {
+				System.out.println("Debug: First target is: " + firstTarget);
+				System.out.println("Debug: Parsed " + rules.size() + " rules.");
+				for (Rule rule : rules) {
+					System.out.println("Debug: Rule: " + rule.getName());
+					System.out.println("Debug:   Commands: " + rule.getCommands().size());
+					for (String cmd : rule.getCommands()) {
+						System.out.println("Debug:     [" + cmd + "]");
+					}
+				}
+			}
 
-    } catch (IOException e) {
-        e.printStackTrace();
-    }
-    return rules;
-}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return rules;
+	}
+
+	/**
+	 * Remplacer les variables dans une chaîne (version statique pour l'évaluation tardive).
+	 * @param input Chaîne à traiter
+	 * @return Chaîne avec les variables remplacées
+	 */
+	public static String expandVariables(String input) {
+		if (input == null) return null;
+		
+		String result = input;
+		Set<String> processedVars = new HashSet<>();
+		boolean changed;
+		
+		do {
+			changed = false;
+			Matcher matcher = VARIABLE_REFERENCE.matcher(result);
+			StringBuffer sb = new StringBuffer();
+			
+			while (matcher.find()) {
+				String varName = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
+				
+				// Vérifier si la variable existe
+				String replacement = "";
+				if (variables.containsKey(varName)) {
+					String varValue = variables.get(varName);
+					// Ne pas remplacer récursivement ici pour éviter les boucles infinies
+					replacement = varValue;
+				} else if (BakeCLI.isDebug()) {
+					System.out.println("Debug: Variable '" + varName + "' not defined, replacing with empty string");
+				}
+				
+				matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
+				changed = true;
+				processedVars.add(varName);
+			}
+			matcher.appendTail(sb);
+			result = sb.toString();
+			
+			// Si aucun changement n'a été fait dans ce passage, arrêter
+			if (!changed) {
+				break;
+			}
+			
+			// Réinitialiser processedVars pour le prochain passage si nécessaire
+			processedVars.clear();
+			
+		} while (changed);
+		
+		return result.trim();
+	}
 
 	/**
 	 * Récupérer la première cible
diff --git a/src/fr/monlouyan/bakefile/CommandExecutor.java b/src/fr/monlouyan/bakefile/CommandExecutor.java
index 0c46343..95b41c4 100644
--- a/src/fr/monlouyan/bakefile/CommandExecutor.java
+++ b/src/fr/monlouyan/bakefile/CommandExecutor.java
@@ -114,6 +114,8 @@ public class CommandExecutor {
             if (ruleNeedsUpdate) {
                 try {
                     if(!isCircular && !silent){
+						String displayCommand = BakefileParser.expandVariables(command);
+
 						// Afficher les lignes formatées avec traitement spécial pour les continuations
 						if (displayLines != null && !displayLines.isEmpty()) {
 							boolean isFirstLine = true;
@@ -121,26 +123,34 @@ public class CommandExecutor {
 							for (String line : displayLines) {
 								if (isFirstLine) {
 									// Pour la première ligne, toujours supprimer l'indentation
+									String expandedLine = line;
 									if (line.startsWith("\t")) {
-										System.out.println(line.substring(1));
+										String content = line.substring(1);
+										expandedLine = "\t" + BakefileParser.expandVariables(content);
+										System.out.println(expandedLine.substring(1));
 									} else {
-										System.out.println(line);
+										expandedLine = BakefileParser.expandVariables(line);
+                    					System.out.println(expandedLine);
 									}
 									isFirstLine = false;
 								} else {
 									// Pour les lignes suivantes d'une continuation, conserver l'indentation
-									System.out.println(line);
+									String expandedLine = BakefileParser.expandVariables(line);
+									System.out.println(expandedLine);
 								}
 							}
 						} else {
 							// Cas d'une commande simple (une seule ligne)
-							if (command.startsWith("\t")) {
-								System.out.println(command.substring(1));
+							if (displayCommand.startsWith("\t")) {
+								System.out.println(displayCommand.substring(1));
 							} else {
-								System.out.println(command);
+								System.out.println(displayCommand);
 							}
 						}
 					}
+
+					actualCommand = BakefileParser.expandVariables(actualCommand);
+
                     if (debug) System.out.println("Debug: Executing " + actualCommand);
                     ProcessBuilder pb = new ProcessBuilder("sh", "-c", actualCommand);
                     pb.inheritIO();