Améliorations du parser et ajout de nouveaux fichiers de test pour vérifier le comportement des variables sur plusieurs lignes avec Bake et Make, et suppression des fichiers obsolètes.

This commit is contained in:
Moncef STITI 2025-02-22 21:21:51 +01:00
parent a228509e8f
commit 51d6970ef1
13 changed files with 184 additions and 157 deletions

@ -9,41 +9,54 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class BakefileParser { public class BakefileParser {
/**
/**
* Nom du fichier Bakefile à parser (donc Bakefile...). * Nom du fichier Bakefile à parser (donc Bakefile...).
*/ */
private String filename; private String filename;
/** /**
* Regex pour détecter les targets et leurs dépendances. * Regex pour détecter les targets et leurs dépendances.
* Format : "nom : dépendance1 dépendance2" * Format : "nom : dépendance1 dépendance2"
*/ */
private static final Pattern TARGET_PATTERN = Pattern.compile("^(\\S+)\\s*:\\s*([^#]*?)\\s*(?:#.*)?$"); private static final Pattern TARGET_PATTERN = Pattern.compile("^(\\S+)\\s*:\\s*([^#]*?)\\s*(?:#.*)?$");
/** /**
* Regex pour détecter les lignes de commande associées à une target. * Regex pour détecter les lignes de commande associées à une target.
* Format : " gcc -o program program.c" (ligne indentée) * Format : " gcc -o program program.c" (ligne indentée)
*/ */
private static final Pattern COMMAND_PATTERN = Pattern.compile("^\\t(.+)$"); private static final Pattern COMMAND_PATTERN = Pattern.compile("^\\t(.+)$");
/** /**
* Regex pour détecter les définitions de variables. * Regex pour détecter les définitions de variables.
* Format : "FLAGS = -ansi -pedantic" * Format : "FLAGS = -ansi -pedantic"
*/ */
private static final Pattern VARIABLE_PATTERN = Pattern.compile("^(\\w+)\\s*=\\s*([^#]*?)\\s*(?:#.*)?$"); private static final Pattern VARIABLE_PATTERN = Pattern.compile("^(\\w+)\\s*=\\s*([^#]*?)\\s*(?:#.*)?$");
/** /**
* Regex pour détecter les déclarations .PHONY * Regex pour détecter les déclarations .PHONY
* Format : ".PHONY: clean all" * Format : ".PHONY: clean all"
*/ */
private static final Pattern PHONY_PATTERN = Pattern.compile("^\\.PHONY:\\s*([^#]*?)\\s*(?:#.*)?$"); private static final Pattern PHONY_PATTERN = Pattern.compile("^\\.PHONY:\\s*([^#]*?)\\s*(?:#.*)?$");
/** /**
* Regex pour détecter les lignes de continuation.
* Format : " gcc -o program program.c \"
*/
private static final Pattern CONTINUATION_PATTERN = Pattern.compile("^(.*)\\\\\\s*$");
/**
* Regex pour détecter les références de variables.
* Format : "${VAR}" ou "$(VAR)"
*/
private static final Pattern VARIABLE_REFERENCE = Pattern.compile("\\$\\{(\\w+)\\}|\\$\\((\\w+)\\)");
/**
* Première cible trouvée dans le fichier Bakefile. * Première cible trouvée dans le fichier Bakefile.
*/ */
private static String firstTarget; private static String firstTarget;
/** /**
* Stocke les variables définies dans le Bakefile. * Stocke les variables définies dans le Bakefile.
*/ */
private Map<String, String> variables = new HashMap<>(); private Map<String, String> variables = new HashMap<>();
@ -53,15 +66,77 @@ public class BakefileParser {
firstTarget = null; firstTarget = null;
} }
private String handleContinuationLines(List<String> lines, int startIndex) {
StringBuilder combinedLine = new StringBuilder();
int i = startIndex;
while (i < lines.size()) {
String line = lines.get(i);
Matcher contMatcher = CONTINUATION_PATTERN.matcher(line);
if (contMatcher.matches()) {
// Ajouter la ligne sans le backslash
combinedLine.append(contMatcher.group(1).trim()).append(" ");
i++;
} else {
// Ajouter la dernière ligne et sortir
combinedLine.append(line.trim());
break;
}
}
return combinedLine.toString();
}
private String replaceVariables(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);
if (!processedVars.contains(varName) && variables.containsKey(varName)) {
String replacement = variables.get(varName);
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();
}
private List<String> replaceVariablesInList(List<String> items) {
return items.stream()
.map(this::replaceVariables)
.collect(Collectors.toList());
}
private List<String> splitDependencies(String depStr) { private List<String> splitDependencies(String depStr) {
if (depStr == null || depStr.trim().isEmpty()) { if (depStr == null || depStr.trim().isEmpty()) {
return new ArrayList<>(); return new ArrayList<>();
} }
// Remplacer les variables avant de split
String resolvedStr = replaceVariables(depStr.trim()); String resolvedStr = replaceVariables(depStr.trim());
// Split sur un ou plusieurs espaces et filtrer les chaînes vides
return Arrays.stream(resolvedStr.split("\\s+")) return Arrays.stream(resolvedStr.split("\\s+"))
.map(String::trim) .map(String::trim)
.filter(s -> !s.isEmpty()) .filter(s -> !s.isEmpty())
@ -82,34 +157,48 @@ public class BakefileParser {
String currentTarget = null; String currentTarget = null;
List<String> dependencies = new ArrayList<>(); List<String> dependencies = new ArrayList<>();
List<String> commands = new ArrayList<>(); List<String> commands = new ArrayList<>();
int lineNumber = 0;
for (int i = 0; i < lines.size(); i++) {
for (String line : lines) { String line = lines.get(i);
lineNumber++;
// Vérifier si la ligne a un caractère de continuation
Matcher varMatcher = VARIABLE_PATTERN.matcher(line); Matcher contMatcher = CONTINUATION_PATTERN.matcher(line);
Matcher targetMatcher = TARGET_PATTERN.matcher(line); if (contMatcher.matches()) {
Matcher commandMatcher = COMMAND_PATTERN.matcher(line); // Récupérer toute la définition multi-ligne
Matcher phonyMatcher = PHONY_PATTERN.matcher(line); line = handleContinuationLines(lines, i);
// Ajuster i pour sauter les lignes traitées
while (i + 1 < lines.size() &&
CONTINUATION_PATTERN.matcher(lines.get(i)).matches()) {
i++;
}
}
if (line.trim().isEmpty()) { if (line.trim().isEmpty()) {
continue; continue;
} }
if (line.matches("^ +.*$")) { if (line.matches("^ +.*$")) {
System.err.println(filename + ":" + lineNumber + ": *** missing separator. Stop."); System.err.println(filename + ":" + (i+1) + ": *** missing separator. Stop.");
System.exit(1); System.exit(1);
} }
Matcher varMatcher = VARIABLE_PATTERN.matcher(line);
Matcher targetMatcher = TARGET_PATTERN.matcher(line);
Matcher commandMatcher = COMMAND_PATTERN.matcher(line);
Matcher phonyMatcher = PHONY_PATTERN.matcher(line);
if (phonyMatcher.matches()) { if (phonyMatcher.matches()) {
// Ajouter les cibles .PHONY à l'ensemble des cibles phony
String[] phonies = phonyMatcher.group(1).trim().split("\\s+"); String[] phonies = phonyMatcher.group(1).trim().split("\\s+");
Collections.addAll(phonyTargets, phonies); Collections.addAll(phonyTargets, phonies);
continue; continue;
} }
if (varMatcher.matches()) { if (varMatcher.matches()) {
variables.put(varMatcher.group(1), varMatcher.group(2).trim()); String varName = varMatcher.group(1);
String varValue = varMatcher.group(2).trim();
// Évaluer les variables référencées dans la valeur
varValue = replaceVariables(varValue);
variables.put(varName, varValue);
} else if (targetMatcher.matches()) { } else if (targetMatcher.matches()) {
if (currentTarget != null) { if (currentTarget != null) {
String resolvedTarget = replaceVariables(currentTarget.trim()); String resolvedTarget = replaceVariables(currentTarget.trim());
@ -121,7 +210,6 @@ public class BakefileParser {
phonyTargets.contains(resolvedTarget) phonyTargets.contains(resolvedTarget)
)); ));
// Ne définir firstTarget que si ce n'est pas une cible .PHONY
if (firstTarget == null && !phonyTargets.contains(resolvedTarget)) { if (firstTarget == null && !phonyTargets.contains(resolvedTarget)) {
firstTarget = resolvedTarget; firstTarget = resolvedTarget;
} }
@ -145,7 +233,6 @@ public class BakefileParser {
phonyTargets.contains(resolvedTarget) phonyTargets.contains(resolvedTarget)
)); ));
// Vérifier une dernière fois pour firstTarget
if (firstTarget == null && !phonyTargets.contains(resolvedTarget)) { if (firstTarget == null && !phonyTargets.contains(resolvedTarget)) {
firstTarget = resolvedTarget; firstTarget = resolvedTarget;
} }
@ -157,36 +244,10 @@ public class BakefileParser {
return rules; return rules;
} }
private String replaceVariables(String input) { /**
if (input == null) return null; * Récupérer la première cible
String result = input; * @return String la première cible
boolean replaced; */
do {
replaced = false;
for (Map.Entry<String, String> entry : variables.entrySet()) {
String key = "$(" + entry.getKey() + ")";
if (result.contains(key)) {
// Préserver les espaces dans les valeurs des variables
result = result.replace(key, entry.getValue());
replaced = true;
}
}
} while (replaced);
return result.trim();
}
private List<String> replaceVariablesInList(List<String> items) {
List<String> resolved = new ArrayList<>();
for (String item : items) {
resolved.add(replaceVariables(item));
}
return resolved;
}
/**
* Permet de récupérer la première cible trouvée dans le fichier Bakefile.
* @return La première cible trouvée
*/
public static String getFirstTarget() { public static String getFirstTarget() {
return firstTarget; return firstTarget;
} }

@ -1,40 +1,21 @@
# Ce makefile viens de : https://iut-fbleau.fr/sitebp/doc_make/makefile/ COMPILER = gcc
### VARIABLES ###
CC = gcc
CFLAGS = -Wall \ CFLAGS = -Wall \
-ansi \ -ansi \
-pedantic -pedantic
LIBS = -lm OUTPUT = program
EXE = exemple
OFILES = extension.o \ OBJS = main.o module.o
main.o
# Règle principale
### BUT PAR DEFAUT ### $(OUTPUT): $(OBJS)
$(COMPILER) $(CFLAGS) -o $(OUTPUT) $(OBJS)
but : ${EXE}
main.o: main.c
### REGLES ESSENTIELLES ### $(COMPILER) $(CFLAGS) -c main.c -o main.o
extension.o : extension.h module.o: module.c
$(COMPILER) $(CFLAGS) -c module.c -o module.o
main.o : extension.h
clean:
${EXE} : ${OFILES} rm -f $(OUTPUT) $(OBJS)
$(CC) $(CFLAGS) -o ${EXE} ${OFILES} ${LIBS}
### REGLES OPTIONNELLES ###
run : but
./${EXE}
clean :
-rm -f ${OFILES} ${EXE}
mrproper : clean but
### BUTS FACTICES ###
.PHONY : but clean mrproper
### FIN ###

@ -1,6 +0,0 @@
#include <stdio.h>
#include "extension.h"
void afficher_message(void) {
printf("Hello, world! Ceci est un message depuis extension.c\n");
}

@ -1,6 +0,0 @@
#ifndef EXTENSION_H
#define EXTENSION_H
void afficher_message(void);
#endif /* EXTENSION_H */

@ -1,6 +1,8 @@
#include "extension.h" #include <stdio.h>
#include "module.h"
int main(void) { int main(void) {
afficher_message(); printf("Hello from main!\n");
print_message();
return 0; return 0;
} }

@ -0,0 +1,6 @@
#include <stdio.h>
#include "module.h"
void print_message(void) {
printf("Hello from module!\n");
}

@ -0,0 +1,6 @@
#ifndef MODULE_H
#define MODULE_H
void print_message(void);
#endif

@ -1,40 +1,21 @@
# Ce makefile viens de : https://iut-fbleau.fr/sitebp/doc_make/makefile/ COMPILER = gcc
### VARIABLES ###
CC = gcc
CFLAGS = -Wall \ CFLAGS = -Wall \
-ansi \ -ansi \
-pedantic -pedantic
LIBS = -lm OUTPUT = program
EXE = exemple
OFILES = extension.o \ OBJS = main.o module.o
main.o
# Règle principale
### BUT PAR DEFAUT ### $(OUTPUT): $(OBJS)
$(COMPILER) $(CFLAGS) -o $(OUTPUT) $(OBJS)
but : ${EXE}
main.o: main.c
### REGLES ESSENTIELLES ### $(COMPILER) $(CFLAGS) -c main.c -o main.o
extension.o : extension.h module.o: module.c
$(COMPILER) $(CFLAGS) -c module.c -o module.o
main.o : extension.h
clean:
${EXE} : ${OFILES} rm -f $(OUTPUT) $(OBJS)
$(CC) $(CFLAGS) -o ${EXE} ${OFILES} ${LIBS}
### REGLES OPTIONNELLES ###
run : but
./${EXE}
clean :
-rm -f ${OFILES} ${EXE}
mrproper : clean but
### BUTS FACTICES ###
.PHONY : but clean mrproper
### FIN ###

@ -1,6 +0,0 @@
#include <stdio.h>
#include "extension.h"
void afficher_message(void) {
printf("Hello, world! Ceci est un message depuis extension.c\n");
}

@ -1,6 +0,0 @@
#ifndef EXTENSION_H
#define EXTENSION_H
void afficher_message(void);
#endif /* EXTENSION_H */

@ -1,6 +1,8 @@
#include "extension.h" #include <stdio.h>
#include "module.h"
int main(void) { int main(void) {
afficher_message(); printf("Hello from main!\n");
print_message();
return 0; return 0;
} }

@ -0,0 +1,6 @@
#include <stdio.h>
#include "module.h"
void print_message(void) {
printf("Hello from module!\n");
}

@ -0,0 +1,6 @@
#ifndef MODULE_H
#define MODULE_H
void print_message(void);
#endif