package fr.monlouyan.bakefile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class BakefileParser { /** * Nom du fichier Bakefile à parser (donc Bakefile...). */ private String filename; /** * Regex pour détecter les targets et leurs dépendances. * Format : "nom : dépendance1 dépendance2" */ private static final Pattern TARGET_PATTERN = Pattern.compile("^(\\S+)\\s*:\\s*(.*)$"); /** * Regex pour détecter les lignes de commande associées à une target. * Format : " gcc -o program program.c" (ligne indentée) */ private static final Pattern COMMAND_PATTERN = Pattern.compile("^\\t(.+)$"); /** * Regex pour détecter les définitions de variables. * Format : "FLAGS = -ansi -pedantic" */ private static final Pattern VARIABLE_PATTERN = Pattern.compile("^(\\w+)\\s*=\\s*(.*)$"); /** * Première cible trouvée dans le fichier Bakefile. */ private static String firstTarget; /** * Stocke les variables définies dans le Bakefile. */ private Map variables = new HashMap<>(); public BakefileParser(String filename) { this.filename = filename; firstTarget = null; } public List parse() { List rules = new ArrayList<>(); Set phonyTargets = new HashSet<>(); if (!Files.exists(Paths.get(filename))) { System.out.println("*** No targets specified and no makefile found. Stop."); System.exit(1); } try { List lines = Files.readAllLines(Paths.get(filename)); String currentTarget = null; List dependencies = new ArrayList<>(); List commands = new ArrayList<>(); int lineNumber = 0; for (String line : lines) { lineNumber++; Matcher varMatcher = VARIABLE_PATTERN.matcher(line); Matcher targetMatcher = TARGET_PATTERN.matcher(line); Matcher commandMatcher = COMMAND_PATTERN.matcher(line); if (line.trim().isEmpty()) { continue; } if (line.matches("^ +.*$")) { // Détecte les lignes commençant par des espaces System.err.println(filename + ":" + lineNumber + ": *** missing separator. Stop."); System.exit(1); } if (varMatcher.matches()) { variables.put(varMatcher.group(1), varMatcher.group(2)); } else if (targetMatcher.matches()) { if (currentTarget != null) { rules.add(new Rule( replaceVariables(currentTarget), replaceVariablesInList(dependencies), replaceVariablesInList(commands), phonyTargets.contains(currentTarget) )); } currentTarget = targetMatcher.group(1); String depStr = targetMatcher.group(2).trim(); dependencies = depStr.isEmpty() ? new ArrayList<>() : new ArrayList<>(Arrays.asList(depStr.split("\\s+"))); if (firstTarget == null) { firstTarget = replaceVariables(currentTarget); } if (currentTarget.equals("clean")) { phonyTargets.add(currentTarget); } commands = new ArrayList<>(); } else if (commandMatcher.matches()) { commands.add(commandMatcher.group(1)); } } if (currentTarget != null) { rules.add(new Rule( replaceVariables(currentTarget), replaceVariablesInList(dependencies), replaceVariablesInList(commands), phonyTargets.contains(currentTarget) )); } } catch (IOException e) { e.printStackTrace(); } return rules; } private String replaceVariables(String input) { if (input == null) return null; String result = input; boolean replaced; do { replaced = false; for (Map.Entry entry : variables.entrySet()) { String key = "$(" + entry.getKey() + ")"; if (result.contains(key)) { result = result.replace(key, entry.getValue()); replaced = true; } } } while (replaced); return result; } private List replaceVariablesInList(List items) { List 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() { return firstTarget; } }