package bjc.rgens.parser; import java.io.BufferedReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import java.util.Scanner; import java.util.Set; /** * Represents a set of grammars that can share rules via exports. * * @author EVE */ public class RGrammarSet { /* Contains all the grammars in this set. */ private Map grammars; /* Contains all the exported rules from grammars. */ private Map exportedRules; /* Contains which export came from which grammar. */ private Map exportFrom; /* Contains which file a grammar was loaded from. */ private Map loadedFrom; public static final boolean PERF = true; /** Create a new set of randomized grammars. */ public RGrammarSet() { grammars = new HashMap<>(); exportedRules = new HashMap<>(); exportFrom = new TreeMap<>(); loadedFrom = new HashMap<>(); } /** * Add a grammar to this grammar set. * * @param grammarName * The name of the grammar to add. * * @param gram * The grammar to add. * * @throws IllegalArgumentException * If the grammar name is invalid. */ public void addGrammar(String grammarName, RGrammar gram) { /* Make sure a grammar is valid. */ if (grammarName == null) { throw new NullPointerException("Grammar name must not be null"); } else if (gram == null) { throw new NullPointerException("Grammar must not be null"); } else if (grammarName.equals("")) { throw new IllegalArgumentException("The empty string is not a valid grammar name"); } grammars.put(grammarName, gram); /* Process exports from the grammar. */ for (Rule export : gram.getExportedRules()) { exportedRules.put(export.name, gram); exportFrom.put(export.name, grammarName); } /* Add exports to grammar. */ gram.setImportedRules(exportedRules); } /** * Get a grammar from this grammar set. * * @param grammarName * The name of the grammar to get. * * @return * The grammar with that name. * * @throws IllegalArgumentException * If the grammar name is invalid or not present in this set. */ public RGrammar getGrammar(String grammarName) { /* Check arguments. */ if (grammarName == null) { throw new NullPointerException("Grammar name must not be null"); } else if (grammarName.equals("")) { throw new IllegalArgumentException("The empty string is not a valid grammar name"); } else if (!grammars.containsKey(grammarName)) { String msg = String.format("No grammar with name '%s' found", grammarName); throw new IllegalArgumentException(msg); } return grammars.get(grammarName); } /** * Get the grammar a rule was exported from. * * @param exportName * The name of the exported rule. * * @return * The grammar the exported rule came from. * * @throws IllegalArgumentException * If the export name is invalid or not present in this set. */ public RGrammar getExportSource(String exportName) { /* Check arguments. */ if (exportName == null) { throw new NullPointerException("Export name must not be null"); } else if (exportName.equals("")) { throw new IllegalArgumentException("The empty string is not a valid rule name"); } else if (!exportedRules.containsKey(exportName)) { String msg = String.format("No export with name '%s' defined", exportName); throw new IllegalArgumentException(msg); } return exportedRules.get(exportName); } /** * Get the source of an exported rule. * * This will often be a grammar name, but is not required to be one. * * @param exportName * The name of the exported rule. * * @return * The source of an exported rule. * * @throws IllegalArgumentException * If the exported rule is invalid or not present in this set. */ public String exportedFrom(String exportName) { /* Check arguments. */ if (exportName == null) { throw new NullPointerException("Export name must not be null"); } else if (exportName.equals("")) { throw new IllegalArgumentException("The empty string is not a valid rule name"); } else if (!exportedRules.containsKey(exportName)) { String msg = String.format("No export with name '%s' defined", exportName); throw new IllegalArgumentException(msg); } return exportFrom.getOrDefault(exportName, "Unknown"); } /** * Get the source of an grammar * * This will often be a file name, but is not required to be one. * * @param grammarName * The name of the exported grammar. * * @return * The source of an exported grammar. * * @throws IllegalArgumentException * If the exported grammar is invalid or not present in this set. */ public String loadedFrom(String grammarName) { /* Check arguments. */ if (grammarName == null) { throw new NullPointerException("Grammar name must not be null"); } else if (grammarName.equals("")) { throw new IllegalArgumentException("The empty string is not a valid grammar name"); } else if (grammarName.equals("unknown")) { return grammarName; } else if (!grammars.containsKey(grammarName)) { String msg = String.format("No grammar with name '%s' defined", grammarName); throw new IllegalArgumentException(msg); } return loadedFrom.getOrDefault(grammarName, "Unknown"); } /** * Get the names of all the grammars in this set. * * @return * The names of all the grammars in this set. */ public Set getGrammars() { return grammars.keySet(); } /** * Get the names of all the exported rules in this set. * * @return * The names of all the exported rules in this set. */ public Set getExportedRules() { return exportedRules.keySet(); } /** * Load a grammar set from a configuration file. * * @param cfgFile * The configuration file to load from. * * @return * The grammar set created by the configuration file. * * @throws IOException * If something goes wrong during configuration loading. */ public static RGrammarSet fromConfigFile(Path cfgFile) throws IOException { /* The grammar set to hand back. */ RGrammarSet set = new RGrammarSet(); long startCFGTime = System.nanoTime(); /* Get the directory that contains the config file. */ Path cfgParent = cfgFile.getParent(); try(Scanner scn = new Scanner(cfgFile)) { /* Execute lines from the configuration file. */ while (scn.hasNextLine()) { String ln = scn.nextLine().trim(); /* Ignore blank/comment lines. */ if (ln.equals("")) continue; if (ln.startsWith("#")) continue; /* Handle mixed whitespace. */ ln = ln.replaceAll("\\s+", " "); /* * Get the place where the name of the grammar * ends. */ int nameIdx = ln.indexOf(" "); if (nameIdx == -1) { throw new GrammarException("Must specify a name for a loaded grammar"); } /* Name and path of grammar. */ String name = ln.substring(0, nameIdx); Path path = Paths.get(ln.substring(nameIdx).trim()); /* * Convert from configuration relative path to * absolute path. */ Path convPath = cfgParent.resolve(path.toString()); if(Files.isDirectory(convPath)) { /* @TODO implement subset grammars */ throw new GrammarException("Sub-grammar sets aren't implemented yet"); } else if (convPath.getFileName().toString().endsWith(".gram")) { /* Load grammar file. */ try { long startFileTime = System.nanoTime(); BufferedReader fis = Files.newBufferedReader(convPath); RGrammar gram = RGrammarParser.readGrammar(fis); fis.close(); long endFileTime = System.nanoTime(); long fileTime = endFileTime - startFileTime; if(PERF) System.err.printf("\tPERF: Read grammar %s in %d ns (%f s)\n", convPath, fileTime, fileTime / 1000000000.0); /* Add grammar to the set. */ set.addGrammar(name, gram); /* * Mark where the grammar came * from. */ set.loadedFrom.put(name, path.toString()); } catch (GrammarException gex) { String msg = String.format("Error loading file '%s'", path); throw new GrammarException(msg, gex); } } else { String msg = String.format("Unrecognized file type '%s'", convPath.getFileName()); throw new GrammarException(msg); } } } long endCFGTime = System.nanoTime(); long cfgDur = endCFGTime - startCFGTime; if(PERF) System.err.printf("\n\nPERF: Read config file %s in %d ns (%f s)\n", cfgFile, cfgDur, cfgDur / 1000000000.0); return set; } }