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("^\\s+(.+)$"); /** * 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<>(); for (String line : lines) { Matcher varMatcher = VARIABLE_PATTERN.matcher(line); Matcher targetMatcher = TARGET_PATTERN.matcher(line); Matcher commandMatcher = COMMAND_PATTERN.matcher(line); if (varMatcher.matches()) { // Stocke la variable variables.put(varMatcher.group(1), varMatcher.group(2)); } else if (targetMatcher.matches()) { if (firstTarget == null) { firstTarget = targetMatcher.group(1); } if (currentTarget != null) { rules.add(new Rule(currentTarget, dependencies, replaceVariables(commands), phonyTargets.contains(currentTarget))); } currentTarget = targetMatcher.group(1); dependencies = new ArrayList<>(Arrays.asList(targetMatcher.group(2).trim().split("\\s+"))); commands = new ArrayList<>(); } else if (commandMatcher.matches()) { commands.add(commandMatcher.group(1)); } } if (currentTarget != null) { rules.add(new Rule(currentTarget, dependencies, replaceVariables(commands), phonyTargets.contains(currentTarget))); } } catch (IOException e) { e.printStackTrace(); } return rules; } /** * Remplace les variables dans une liste de commandes. * Ex : "gcc $(FLAGS) -o main main.c" devient "gcc -ansi -pedantic -o main main.c" * @param commands Liste des commandes à modifier * @return Liste avec les variables remplacées */ private List replaceVariables(List commands) { List resolvedCommands = new ArrayList<>(); for (String command : commands) { boolean replaced; do { replaced = false; for (Map.Entry entry : variables.entrySet()) { String key = "$(" + entry.getKey() + ")"; if (command.contains(key)) { command = command.replace(key, entry.getValue()); replaced = true; } } } while (replaced); // Continue tant qu'on remplace des variables resolvedCommands.add(command); } return resolvedCommands; } /** * 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; } }