diff options
| author | bculkin2442 <bjculkin@mix.wvu.edu> | 2019-07-21 15:52:00 -0400 |
|---|---|---|
| committer | bculkin2442 <bjculkin@mix.wvu.edu> | 2019-07-21 15:52:00 -0400 |
| commit | 6b810efa8765047a669be0e9ff6501ddda5c49a9 (patch) | |
| tree | 0e65344afe3a58efdc662a22867bc8ab2948f3e6 /src | |
| parent | dd4bdab11c715fa2dcc438f287806b50f8467fd3 (diff) | |
| parent | a4d1507727e9aeeea0982ebbda785a961902d46c (diff) | |
Merge cleanups
Diffstat (limited to 'src')
20 files changed, 1161 insertions, 582 deletions
diff --git a/src/main/java/bjc/rgens/parser/ConfigLoader.java b/src/main/java/bjc/rgens/parser/ConfigLoader.java index 67c1cf7..028d99e 100644 --- a/src/main/java/bjc/rgens/parser/ConfigLoader.java +++ b/src/main/java/bjc/rgens/parser/ConfigLoader.java @@ -4,17 +4,34 @@ import static bjc.rgens.parser.RGrammarLogging.error; import static bjc.rgens.parser.RGrammarLogging.info; import static bjc.rgens.parser.RGrammarLogging.perf; -import java.io.BufferedReader; +import bjc.utils.data.ITree; +import bjc.utils.data.QueuedIterator; +import bjc.utils.data.Tree; + +import bjc.utils.funcutils.FileUtils; +import bjc.utils.funcutils.IteratorUtils; +import bjc.utils.funcutils.StringUtils; + +import java.io.File; +import java.io.FileReader; +import java.io.Reader; import java.io.IOException; + import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; + import java.util.Scanner; import bjc.rgens.parser.templates.GrammarTemplate; import bjc.utils.funcutils.FileUtils; import bjc.utils.ioutils.LevelSplitter; +/** + * Class that performs loading of grammar sets from config files. + * + * @author Ben Culkin + */ public class ConfigLoader { /** * Load a grammar set from a configuration file. @@ -28,60 +45,76 @@ public class ConfigLoader { * @throws IOException * If something goes wrong during configuration loading. */ - public static ConfigSet fromConfigFile(Path cfgFile) throws IOException { - ConfigSet cfgSet = new ConfigSet(); + public static ConfigSet fromConfigFile(Path cfgFile, LoadOptions lopts) throws IOException { + String msg = String.format("INFO: Loading config file %s", cfgFile); + + ITree<String> errTree = new Tree<>(msg); + + return fromConfigFile(cfgFile, lopts, errTree); + } + + /** + * Load a grammar set from a configuration file. + * + * @param cfgFile + * The configuration file to load from. + * + * @param errs + * A place to add errors that occur during loading. + * + * @return + * The grammar set created by the configuration file. + * + * @throws IOException + * If something goes wrong during configuration loading. + */ + public static ConfigSet fromConfigFile(Path cfgFile, LoadOptions lopts, ITree<String> errs) throws IOException { + lopts.cfgFile = cfgFile; + lopts.cfgSet = new ConfigSet(); /* The grammar set we're parsing into. */ - RGrammarSet set = new RGrammarSet(); - cfgSet.grammars.put("default", set); - set.belongsTo = cfgSet; - set.name = "default"; + lopts.gramSet = lopts.cfgSet.createGSet(lopts.defName); long startCFGTime = System.nanoTime(); - /* Get the directory that contains the config file. */ - Path cfgParent = cfgFile.getParent(); + // Get the directory that contains the config file. + if (lopts.parent == null) lopts.parent = cfgFile.getParent(); try(Scanner scn = new Scanner(cfgFile)) { int lno = 0; - /* Execute lines from the configuration file. */ while (scn.hasNextLine()) { - String ln = scn.nextLine().trim(); - + // Execute a line from the configuration file. lno += 1; - try { - /* Ignore blank/comment lines. */ - if (ln.equals("")) continue; + String ln = scn.nextLine().trim().replaceAll("\\s+", " "); - if (ln.startsWith("#")) continue; + // Ignore blank/comment lines. + if (ln.equals("")) continue; + if (ln.startsWith("#")) continue; - ln = ln.replaceAll("\\s+", " "); - String[] parts = LevelSplitter.def.levelSplit(ln, " ").toArray(new String[0]); + ITree<String> header = new Tree<>(String.format("INFO: Processed line %d", lno)); - /* Get line type */ - if(parts.length < 1) { - throw new GrammarException("Must specify config line type"); - } + String[] parts = StringUtils.levelSplit(ln, " ").toArray(new String[0]); + + if(parts.length < 1) { + // Must specify a line type + header.addChild("ERROR: Must specify config line type"); + } else { String type = parts[0]; switch(type) { - case "load": - loadConfigLine(parts, cfgSet, set, cfgParent); + case "load": + loadConfigLine(parts, lopts, header); break; default: - throw new GrammarException("Unknown config line type " + type); - } - } catch(GrammarException gex) { - System.out.printf("ERROR: Line %s of config set %s (%s)\n", lno, cfgFile, gex.getRootMessage()); + String fmt = String.format("ERROR: Unknown config line type %s", type); - error(gex, "Line %s of config set %s (%s)", lno, cfgFile, gex.getRootMessage()); - gex.printStackTrace(); - - System.out.println(); - System.out.println(); + header.addChild(fmt); + } } + + errs.addChild(header); } } @@ -89,57 +122,72 @@ public class ConfigLoader { long cfgDur = endCFGTime - startCFGTime; - perf("Read config file %s in %d ns (%f s)", cfgFile, cfgDur, cfgDur / 1000000000.0); + if (lopts.doPerf) { + String fmt = String.format("PERF: Read config file %s in %d ns (%f s)", cfgFile, cfgDur, cfgDur / 1000000000.0); + + errs.addChild(fmt); + } - return cfgSet; + return lopts.cfgSet; } - private static void loadConfigLine(String[] parts, ConfigSet cfgSet, RGrammarSet set, Path cfgParent) throws IOException { - /* - * Get the place where the tag ID ends - */ + private static void loadConfigLine(String[] parts, LoadOptions lopts, ITree<String> errs) throws IOException { if(parts.length < 2) { - throw new GrammarException("Must specify object tag"); + // Must specify an object type + errs.addChild("ERROR: Must specify type for config object"); + + return; } + String tag = parts[1]; - /* - * Get the place where the name of the grammar - * ends. - */ if (parts.length < 3) { - throw new GrammarException("Must specify a name for a loaded " + tag); + // Must specify an object name + String fmt = String.format("ERROR: Must specify a name for config object of type '%s'", tag); + + errs.addChild(fmt); + + return; } + String name = parts[2]; switch(tag) { case "template": - loadTemplate(name, parts, cfgSet, set, cfgParent); + loadTemplate(name, parts, lopts, errs); break; case "subset": { + String fmt = String.format("ERROR: Sub-grammar sets aren't implemented yet"); + /* - *@TODO Ben Culkin 9/8/17 :SubsetGrammar - * + * @TODO Ben Culkin 9/8/17 :SubsetGrammar * Implement subset grammars. + * */ - throw new GrammarException("Sub-grammar sets aren't implemented yet"); + errs.addChild(fmt); } case "gram": case "grammar": - loadGrammar(name, parts, cfgSet, set, cfgParent); + loadGrammar(name, parts, lopts, errs); break; case "directory": - loadDirectory(name, parts, cfgSet, set, cfgParent); + loadDirectory(name, parts, lopts, errs); break; default: - throw new GrammarException(String.format("Unrecognized tag type '%s'", tag)); + String fmt = String.format("ERROR: Unrecognized config object type '%s'", tag); + + errs.addChild(fmt); } } - private static void loadDirectory(String name, String[] parts, ConfigSet cfgSet, RGrammarSet set, Path cfgParent) throws IOException { + private static void loadDirectory(String name, String[] parts, LoadOptions lopts, ITree<String> errs) throws IOException { if(parts.length < 4) { - throw new GrammarException(String.format("Must specify a path to load directory '%s' from")); + String fmt = String.format("ERROR: Must specify a path to load directory '%s' from", name); + + errs.addChild(fmt); + + return; } Path path = Paths.get(parts[3]); @@ -148,63 +196,82 @@ public class ConfigLoader { * Convert from configuration relative path to * absolute path. */ - Path dirPath = cfgParent.resolve(path.toString()); + Path dirPath = lopts.parent.resolve(path.toString()); if(!Files.isDirectory(dirPath)) { - throw new GrammarException(String.format("%s is not a valid directory", dirPath)); + String fmt = String.format("ERROR: '%s' is not a valid directory", dirPath); + + errs.addChild(fmt); } else { - FileUtils.traverseDirectory(dirPath, (fle, atts) -> { - // We want to consider all the files - return true; - }, (fle, atts) -> { - Path normFle = fle.normalize(); - - String fleName = normFle.toString(); - - try { - if(fleName.endsWith(".gram")) { - BufferedReader rdr = Files.newBufferedReader(normFle); + // Create an iterator over all of the files in the + // provided directory + QueuedIterator<File> dirItr = new QueuedIterator<>(dirPath.toFile().listFiles()); - doLoadGrammar(rdr, null, cfgSet, set, dirPath, normFle); - } else if(fleName.endsWith(".gtpl")) { - BufferedReader rdr = Files.newBufferedReader(normFle); + ITree<String> header = new Tree<>(String.format("INFO: Bulk-loading files from directory '%s'", lopts.parent)); - doLoadTemplate(rdr, null, cfgSet, set, dirPath); - } else if(fleName.endsWith(".class")) { - // Ignore these + while (dirItr.hasNext()) { + File curFile = dirItr.next(); + + String fName = curFile.toString(); + + ITree<String> kid = new Tree<>(String.format("INFO: Processing file '%s'", fName)); + + Path oldPar = lopts.parent; + lopts.parent = curFile.toPath().getParent(); + + try { + if (curFile.isDirectory()) { + dirItr.last(curFile.listFiles()); + } else if (fName.endsWith(".gram")) { + Reader rdr = new FileReader(curFile); + + doLoadGrammar(rdr, null, lopts, kid); + } else if (fName.endsWith(".gtpl")) { + Reader rdr = new FileReader(curFile); + + doLoadTemplate(rdr, null, lopts, kid); + } else if (fName.endsWith(".class")) { + // These get ignored } else { - info("Ignoring file '%s' of unknown type", fleName); + String fmt = String.format("WARN: Ignoring unknown type of file '%s'"); + + kid.addChild(fmt); + } - } catch (GrammarException gex) { - error(gex, "Error loading file %s (%s)", normFle, gex.getRootMessage()); } catch (IOException ioex) { - error(ioex, "Error loading file %s", normFle); + kid.addChild("ERROR: " + ioex.getMessage()); + } finally { + lopts.parent = oldPar; } - return true; - }); + header.addChild(kid); + } + + errs.addChild(header); } } - private static void doLoadTemplate(BufferedReader rdr, String name, ConfigSet cfgSet, RGrammarSet set, Path convPath) throws IOException { + private static void doLoadTemplate(Reader rdr, String name, LoadOptions lopts, ITree<String> errs) throws IOException { String actName; long startFileTime = System.nanoTime(); - GrammarTemplate template = GrammarTemplate.readTemplate(rdr); - template.belongsTo = cfgSet; + GrammarTemplate template = GrammarTemplate.readTemplate(rdr, errs); + template.belongsTo = lopts.cfgSet; if(template.name == null) { if(name == null) { - info("Using default name for template from path '%s'", convPath); + String fmt = String.format("INFO: Using default name for template"); + + errs.addChild(fmt); actName = "default-name"; } else { actName = name; } - info("Naming unnamed template loaded from path %s off config name '%s'", - convPath, actName); + String fmt = String.format("INFO: Naming unnamed template off name '%s' specified in config", actName); + errs.addChild(fmt); template.name = actName; } @@ -215,11 +282,14 @@ public class ConfigLoader { long fileTime = endFileTime - startFileTime; - perf("Read template %s (from %s) in %d ns (%f s)", - template.name, convPath, fileTime, fileTime / 1000000000.0); + if (lopts.doPerf) { + String fmt = String.format("PERF: Read template %s in %d ns (%f s)", template.name, fileTime, fileTime / 1000000000.0); + + errs.addChild(fmt); + } /* Add grammar to the set. */ - cfgSet.templates.put(template.name, template); + lopts.cfgSet.templates.put(template.name, template); /* * @NOTE @@ -231,9 +301,13 @@ public class ConfigLoader { //set.loadedFrom.put(template.name, path.toString()); } - private static void loadTemplate(String name, String[] parts, ConfigSet cfgSet, RGrammarSet set, Path cfgParent) throws IOException { + private static void loadTemplate(String name, String[] parts, LoadOptions lopts, ITree<String> errs) throws IOException { if(parts.length < 4) { - throw new GrammarException(String.format("Must specify a path to load template '%s' from", name)); + String fmt = String.format("ERROR: Must specify a path to load template '%s' from", name); + + errs.addChild(fmt); + + return; } Path path = Paths.get(parts[3]); @@ -242,39 +316,48 @@ public class ConfigLoader { * Convert from configuration relative path to * absolute path. */ - Path convPath = cfgParent.resolve(path.toString()); + Path convPath = lopts.parent.resolve(path.toString()); - if(Files.isDirectory(convPath)) { - throw new GrammarException(String.format("%s is not a valid grammar file", convPath)); + if(!Files.exists(convPath) || Files.isDirectory(convPath)) { + String fmt = String.format("ERROR: '%s' is not a valid grammar file", convPath); + + errs.addChild(fmt); } else { /* Load template file. */ + Reader rdr = new FileReader(convPath.toFile()); + + String fmt = String.format("INFO: Loading template '%s' from '%s'", name, convPath); + ITree<String> kid = new Tree<>(fmt); + + Path oldPar = lopts.parent; + lopts.parent = convPath.getParent(); + try { - BufferedReader fis = Files.newBufferedReader(convPath); - doLoadTemplate(fis, name, cfgSet, set, convPath); - } catch (GrammarException gex) { - String msg = String.format("Error loading template file '%s'", path); - throw new GrammarException(msg, gex, gex.getRootMessage()); + doLoadTemplate(rdr, name, lopts, kid); + } finally { + lopts.parent = oldPar; } + + errs.addChild(kid); } } - private static void doLoadGrammar(BufferedReader rdr, String name, ConfigSet cfgSet, RGrammarSet set, Path convPath, Path pth) throws IOException { + private static void doLoadGrammar(Reader rdr, String name, LoadOptions lopts, ITree<String> errs) throws IOException { String actName; long startFileTime = System.nanoTime(); - RGrammar gram = RGrammarParser.readGrammar(rdr); + RGrammar gram = RGrammarParser.readGrammar(rdr, lopts, errs); if(gram.name == null) { if(name == null) { - info("Using default name from grammar for '%s'", convPath); + errs.addChild("INFO: Using default name for grammar"); actName = "default-name"; } else { actName = name; } - info("Naming unnamed grammar loaded from %s off config name '%s'", - pth, actName); + String fmt = String.format("Naming unnamed grammar off config name '%s'", actName); gram.name = actName; } @@ -285,22 +368,29 @@ public class ConfigLoader { long fileTime = endFileTime - startFileTime; - perf("Read grammar %s (from %s) in %d ns (%f s)", - gram.name, convPath, fileTime, fileTime / 1000000000.0); + if (lopts.doPerf) { + String fmt = String.format("PERF: Read grammar %s in %d ns (%f s)", gram.name, fileTime, fileTime / 1000000000.0); + + errs.addChild(fmt); + } /* Add grammar to the set. */ - set.addGrammar(gram.name, gram); + lopts.gramSet.addGrammar(gram.name, gram); /* * Mark where the grammar came * from. */ - set.loadedFrom.put(gram.name, pth.toString()); + lopts.gramSet.loadedFrom.put(gram.name, lopts.parent.toString()); } - private static void loadGrammar(String name, String[] parts, ConfigSet cfgSet, RGrammarSet set, Path cfgParent) throws IOException { + private static void loadGrammar(String name, String[] parts, LoadOptions lopts, ITree<String> errs) throws IOException { if(parts.length < 4) { - throw new GrammarException(String.format("Must provide a path to load grammar '%s' from", name)); + String fmt = String.format("ERROR: Must provide a path to load grammar '%s' from", name); + + errs.addChild(fmt); + + return; } Path path = Paths.get(parts[3]); @@ -309,23 +399,32 @@ public class ConfigLoader { * Convert from configuration relative path to * absolute path. */ - Path convPath = cfgParent.resolve(path.toString()); + Path convPath = lopts.parent.resolve(path.toString()); + + if(!Files.exists(convPath) || Files.isDirectory(convPath)) { + String fmt = String.format("ERROR: %s is not a valid grammar file", convPath); - if(Files.isDirectory(convPath)) { - throw new GrammarException(String.format("%s is not a valid grammar file", convPath)); + errs.addChild(fmt); } else { - /* Load grammar file. */ + Path oldPar = lopts.parent; + lopts.parent = convPath.getParent(); + try { - //long startFileTime = System.nanoTime(); + /* Load grammar file. */ + long startFileTime = System.nanoTime(); + + Reader rdr = new FileReader(convPath.toFile()); - BufferedReader fis = Files.newBufferedReader(convPath); - doLoadGrammar(fis, name, cfgSet, set, convPath, path); - } catch(GrammarException gex) { - String msg = String.format("Error loading grammar '%s'", path); - throw new GrammarException(msg, gex, gex.getRootMessage()); + ITree<String> kid = new Tree<>(String.format("INFO: Loading grammar '%s' from '%s'", name, convPath)); + doLoadGrammar(rdr, name, lopts, kid); + + errs.addChild(kid); } catch (IOException ioex) { - String msg = String.format("Error loading grammar '%s'", path); - throw new GrammarException(msg, ioex); + String msg = String.format("ERROR: %s", ioex.getMessage()); + + errs.addChild(msg); + } finally { + lopts.parent = oldPar; } } } diff --git a/src/main/java/bjc/rgens/parser/ConfigSet.java b/src/main/java/bjc/rgens/parser/ConfigSet.java index 8945a0f..8d7c63e 100644 --- a/src/main/java/bjc/rgens/parser/ConfigSet.java +++ b/src/main/java/bjc/rgens/parser/ConfigSet.java @@ -6,13 +6,24 @@ import java.util.Map; import bjc.rgens.parser.templates.GrammarTemplate; public class ConfigSet { - public final Map<String, RGrammarSet> grammars; + public final Map<String, RGrammarSet> grammars; public final Map<String, GrammarTemplate> templates; - public final Map<String, ConfigSet> subconfigs; + public final Map<String, ConfigSet> subconfigs; public ConfigSet() { grammars = new HashMap<>(); templates = new HashMap<>(); subconfigs = new HashMap<>(); } + + public RGrammarSet createGSet(String name) { + RGrammarSet st = new RGrammarSet(); + + st.belongsTo = this; + st.name = name; + + grammars.put(name, st); + + return st; + } } diff --git a/src/main/java/bjc/rgens/parser/FlatRuleCase.java b/src/main/java/bjc/rgens/parser/FlatRuleCase.java index 17b037e..3e5378e 100644 --- a/src/main/java/bjc/rgens/parser/FlatRuleCase.java +++ b/src/main/java/bjc/rgens/parser/FlatRuleCase.java @@ -3,8 +3,10 @@ package bjc.rgens.parser; import bjc.rgens.parser.elements.CaseElement; import bjc.utils.funcdata.IList; +import java.util.List; + public class FlatRuleCase extends RuleCase { - public FlatRuleCase(IList<CaseElement> elms) { + public FlatRuleCase(List<CaseElement> elms) { super(elms); } @@ -15,7 +17,7 @@ public class FlatRuleCase extends RuleCase { } } - public FlatRuleCase withElements(IList<CaseElement> elms) { + public FlatRuleCase withElements(List<CaseElement> elms) { return new FlatRuleCase(elms); } } diff --git a/src/main/java/bjc/rgens/parser/LoadOptions.java b/src/main/java/bjc/rgens/parser/LoadOptions.java new file mode 100644 index 0000000..1af0923 --- /dev/null +++ b/src/main/java/bjc/rgens/parser/LoadOptions.java @@ -0,0 +1,50 @@ +package bjc.rgens.parser; + +import java.nio.file.Path; + +/** + * Options used during the loading of config sets and config set related things. + * + * @author Ben Culkin. + */ +public class LoadOptions { + /** + * Should timings be tracked? + */ + public boolean doPerf; + + /** + * Should debug stats be tracked? + */ + public boolean doDebug; + + /** + * Should trace stats be tracked? + */ + public boolean doTrace; + + /** + * The default grammar set name. + */ + public String defName; + + /** + * The path the current file is in. + */ + public Path parent; + + /** + * The file the current config set is being loaded from. + */ + public Path cfgFile; + + /** + * The config set being loaded. + */ + public ConfigSet cfgSet; + + /** + * The grammar set being loaded. + */ + public RGrammarSet gramSet; +} diff --git a/src/main/java/bjc/rgens/parser/NormalRuleCase.java b/src/main/java/bjc/rgens/parser/NormalRuleCase.java index 965c126..dcb56cb 100644 --- a/src/main/java/bjc/rgens/parser/NormalRuleCase.java +++ b/src/main/java/bjc/rgens/parser/NormalRuleCase.java @@ -1,10 +1,12 @@ package bjc.rgens.parser; +import java.util.List; + import bjc.rgens.parser.elements.CaseElement; import bjc.utils.funcdata.IList; public class NormalRuleCase extends RuleCase { - public NormalRuleCase(IList<CaseElement> elms) { + public NormalRuleCase(List<CaseElement> elms) { super(elms); } @@ -19,7 +21,7 @@ public class NormalRuleCase extends RuleCase { } } - public NormalRuleCase withElements(IList<CaseElement> elms) { + public NormalRuleCase withElements(List<CaseElement> elms) { return new NormalRuleCase(elms); } } diff --git a/src/main/java/bjc/rgens/parser/RGrammar.java b/src/main/java/bjc/rgens/parser/RGrammar.java index a2410c8..5b2ae08 100755 --- a/src/main/java/bjc/rgens/parser/RGrammar.java +++ b/src/main/java/bjc/rgens/parser/RGrammar.java @@ -2,6 +2,15 @@ package bjc.rgens.parser; import static bjc.utils.data.IPair.pair; +import bjc.utils.data.IPair; +import bjc.utils.data.ITree; +import bjc.utils.data.Pair; +import bjc.utils.data.Tree; +import bjc.utils.funcutils.StringUtils; +import bjc.utils.ioutils.ReportWriter; + +import bjc.rgens.parser.elements.*; + import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; @@ -14,7 +23,6 @@ import java.util.Set; import org.apache.commons.text.similarity.LevenshteinDistance; -import bjc.rgens.parser.elements.CaseElement; import bjc.utils.data.IPair; import bjc.utils.ioutils.ReportWriter; import edu.gatech.gtri.bktree.BkTreeSearcher; @@ -285,18 +293,27 @@ public class RGrammar { * initial rule. */ public void setInitialRule(String initRule) { + setInitialRule(initRule, new Tree<>()); + } + + public void setInitialRule(String initRule, ITree<String> errs) { /* Passing null, nulls our initial rule. */ if (initRule == null) { this.initialRule = null; + return; } if (initRule.equals("")) { - throw new GrammarException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } else if (!rules.containsKey(initRule)) { - String msg = String.format("No rule '%s' local to this grammar (%s) defined.", initRule, name); + String msg = String.format("ERROR: No rule '%s' local to this grammar (%s) defined.", initRule, name); - throw new GrammarException(msg); + errs.addChild(msg); + + return; } initialRule = initRule; diff --git a/src/main/java/bjc/rgens/parser/RGrammarBuilder.java b/src/main/java/bjc/rgens/parser/RGrammarBuilder.java index 70b738b..7296801 100755 --- a/src/main/java/bjc/rgens/parser/RGrammarBuilder.java +++ b/src/main/java/bjc/rgens/parser/RGrammarBuilder.java @@ -12,9 +12,13 @@ import java.util.Set; import bjc.rgens.parser.elements.CaseElement; import bjc.utils.data.IPair; +import bjc.utils.data.ITree; import bjc.utils.data.Pair; +import bjc.utils.data.Tree; + import bjc.utils.funcdata.FunctionalList; import bjc.utils.funcdata.IList; + import bjc.utils.funcutils.ListUtils; import bjc.utils.funcutils.SetUtils; @@ -57,10 +61,19 @@ public class RGrammarBuilder { * The rule by that name, or a new one if none existed. */ public Rule getOrCreateRule(String rName) { - if(rName == null) - throw new NullPointerException("Rule name must not be null"); - else if(rName.equals("")) - throw new IllegalArgumentException("The empty string is not a valid rule name"); + return getOrCreateRule(rName, new Tree<>()); + } + + public Rule getOrCreateRule(String rName, ITree<String> errs) { + if(rName == null) { + errs.addChild("ERROR: Rule name must not be null"); + + return null; + } else if(rName.equals("")) { + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return null; + } if(rules.containsKey(rName)) { return rules.get(rName); @@ -114,10 +127,17 @@ public class RGrammarBuilder { * If the rule is either not valid or not defined in the grammar. */ public void setInitialRule(String init) { + } + + public void setInitialRule(String init, ITree<String> errs) { if (init == null) { - throw new NullPointerException("init must not be null"); + errs.addChild("init must not be null"); + + return; } else if (init.equals("")) { - throw new IllegalArgumentException("The empty string is not a valid rule name"); + errs.addChild("The empty string is not a valid rule name"); + + return; } initialRule = init; @@ -133,10 +153,18 @@ public class RGrammarBuilder { * If the rule is either not valid or not defined in the grammar. */ public void addExport(String export) { + addExport(export, new Tree<>()); + } + + public void addExport(String export, ITree<String> errs) { if (export == null) { - throw new NullPointerException("Export name must not be null"); + errs.addChild("ERROR: Export name must not be null"); + + return; } else if (export.equals("")) { - throw new NullPointerException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } exportedRules.add(export); @@ -155,8 +183,8 @@ public class RGrammarBuilder { * If the rule name is either invalid or not defined by this * grammar, or if the suffix is invalid. */ - public void suffixWith(String ruleName, IList<CaseElement> suffixes) { - affixWith(ruleName, suffixes, AffixType.SUFFIX); + public void suffixWith(String ruleName, List<CaseElement> suffixes) { + affixWith(ruleName, suffixes, AffixType.SUFFIX, new Tree<>()); } @@ -173,8 +201,8 @@ public class RGrammarBuilder { * If the rule name is either invalid or not defined by this * grammar, or if the prefix is invalid. */ - public void prefixWith(String ruleName, IList<CaseElement> prefixes) { - affixWith(ruleName, prefixes, AffixType.PREFIX); + public void prefixWith(String ruleName, List<CaseElement> prefixes) { + affixWith(ruleName, prefixes, AffixType.PREFIX, new Tree<>()); } /** @@ -190,8 +218,8 @@ public class RGrammarBuilder { * If the rule name is either invalid or not defined by this * grammar, or if the prefix/suffix is invalid. */ - public void circumfixWith(String ruleName, IList<CaseElement> prefixes) { - affixWith(ruleName, prefixes, AffixType.CIRCUMFIX); + public void circumfixWith(String ruleName, List<CaseElement> prefixes) { + affixWith(ruleName, prefixes, AffixType.CIRCUMFIX, new Tree<>()); } public static enum AffixType { @@ -208,32 +236,39 @@ public class RGrammarBuilder { } } - public void affixWith(String ruleName, IList<CaseElement> affixes, AffixType type) { + public void affixWith(String ruleName, List<CaseElement> affixes, AffixType type) { + affixWith(ruleName, affixes, type, new Tree<>()); + } + + public void affixWith(String ruleName, List<CaseElement> affixes, AffixType type, ITree<String> errs) { if (ruleName == null) { - throw new NullPointerException("Rule name must not be null"); + errs.addChild("ERROR: Rule name must not be null"); + + return; } else if (ruleName.equals("")) { - throw new IllegalArgumentException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } else if(!rules.containsKey(ruleName)) { - String msg = String.format("Rule '%s' is not a valid rule name"); + String msg = String.format("ERROR: Rule '%s' is not a valid rule name"); - throw new IllegalArgumentException(msg); - } + errs.addChild(msg); - Set<CaseElement> elements = new HashSet<>(affixes.getSize()); - for(CaseElement affix : affixes) { - elements.add(affix); + return; } + + Set<CaseElement> elements = new HashSet<>(affixes); List<List<CaseElement>> affixLists = powerList(elements); FunctionalList<IPair<Integer, RuleCase>> newCases = new FunctionalList<>(); - IList<IPair<Integer, RuleCase>> caseList = rules.get(ruleName).getCases(); + for (IPair<Integer, RuleCase> ruleCase : caseList) { RuleCase cas = ruleCase.getRight(); for(List<CaseElement> affixList : affixLists) { - FunctionalList<CaseElement> newCase = new FunctionalList<>(); + List<CaseElement> newCase = new ArrayList<>(); if(type.isPrefix()) { for(CaseElement element : affixList) { @@ -263,12 +298,24 @@ public class RGrammarBuilder { } public void despaceRule(String ruleName) { + despaceRule(ruleName, new Tree<>(), false); + } + + public void despaceRule(String ruleName, ITree<String> errs, boolean doTrace) { if (ruleName == null) { - throw new NullPointerException("ruleName must not be null"); + errs.addChild("ERROR: Rule name must not be null"); + + return; } else if (ruleName.equals("")) { - throw new IllegalArgumentException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } else if (!rules.containsKey(ruleName)) { - throw new IllegalArgumentException(String.format("The rule '%s' doesn't exist", ruleName)); + String msg = String.format("ERROR: The rule '%s' doesn't exist", ruleName); + + errs.addChild(msg); + + return; } IList<IPair<Integer, RuleCase>> caseList = rules.get(ruleName).getCases(); @@ -279,42 +326,82 @@ public class RGrammarBuilder { newCaseList.add(new Pair<>(cse.getLeft(), new FlatRuleCase(cse.getRight().elementList))); } - trace("Despacing %d cases of rule %s", caseList.getSize(), ruleName); + if (doTrace) { + String msg = String.format("TRACE: Despacing %d cases of rule %s", caseList.getSize(), ruleName); + + errs.addChild(msg); + } rules.get(ruleName).replaceCases(newCaseList); } public void setWeight(String ruleName) { + setWeight(ruleName, new Tree<>()); + } + + public void setWeight(String ruleName, ITree<String> errs) { if (ruleName == null) { - throw new NullPointerException("ruleName must not be null"); + errs.addChild("ERROR: Rule name must not be null"); + + return; } else if (ruleName.equals("")) { - throw new IllegalArgumentException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } else if (!rules.containsKey(ruleName)) { - throw new IllegalArgumentException(String.format("The rule '%s' doesn't exist", ruleName)); + String msg = String.format("ERROR: The rule '%s' doesn't exist", ruleName); + + errs.addChild(msg); + + return; } rules.get(ruleName).prob = Rule.ProbType.NORMAL; } public void setRuleRecur(String ruleName, int recurLimit) { + setRuleRecur(ruleName, recurLimit, new Tree<>()); + } + + public void setRuleRecur(String ruleName, int recurLimit, ITree<String> errs) { if (ruleName == null) { - throw new NullPointerException("ruleName must not be null"); + errs.addChild("ERROR: Rule name must not be null"); + + return; } else if (ruleName.equals("")) { - throw new IllegalArgumentException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } else if (!rules.containsKey(ruleName)) { - throw new IllegalArgumentException(String.format("The rule '%s' doesn't exist", ruleName)); + String msg = String.format("ERROR: The rule '%s' doesn't exist", ruleName); + + errs.addChild(msg); + + return; } rules.get(ruleName).recurLimit = recurLimit; } public void setDescent(String ruleName, int descentFactor) { + setDescent(ruleName, descentFactor, new Tree<>()); + } + + public void setDescent(String ruleName, int descentFactor, ITree<String> errs) { if (ruleName == null) { - throw new NullPointerException("ruleName must not be null"); + errs.addChild("ERROR: Rule name must not be null"); + + return; } else if (ruleName.equals("")) { - throw new IllegalArgumentException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } else if (!rules.containsKey(ruleName)) { - throw new IllegalArgumentException(String.format("The rule '%s' doesn't exist", ruleName)); + String msg = String.format("ERROR: The rule '%s' doesn't exist", ruleName); + + errs.addChild(msg); + + return; } Rule rl = rules.get(ruleName); @@ -324,12 +411,23 @@ public class RGrammarBuilder { } public void setBinomial(String ruleName, int target, int bound, int trials) { + setBinomial(ruleName, target, bound, trials, new Tree<>()); + } + + public void setBinomial(String ruleName, int target, int bound, int trials, ITree<String> errs) { if (ruleName == null) { - throw new NullPointerException("ruleName must not be null"); + errs.addChild("ERROR: Rule name must not be null"); + + return; } else if (ruleName.equals("")) { - throw new IllegalArgumentException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } else if (!rules.containsKey(ruleName)) { - throw new IllegalArgumentException(String.format("The rule '%s' doesn't exist", ruleName)); + String msg = String.format("ERROR: The rule '%s' doesn't exist", ruleName); + errs.addChild(msg); + + return; } Rule rl = rules.get(ruleName); @@ -350,37 +448,67 @@ public class RGrammarBuilder { } public void rejectRule(String rule, String reject) { + rejectRule(rule, reject, new Tree<>()); + } + + public void rejectRule(String rule, String reject, ITree<String> errs) { if (rule == null) { - throw new NullPointerException("rule must not be null"); + errs.addChild("ERROR: Rule must not be null"); + + return; } else if(reject == null) { - throw new NullPointerException("reject must not be null"); + errs.addChild("ERROR: Reject must not be null"); + + return; } else if (rule.equals("")) { - throw new IllegalArgumentException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } else if(!rules.containsKey(rule)) { - throw new IllegalArgumentException(String.format("The rule '%s' doesn't exist", rule)); + String msg = String.format("ERROR: The rule '%s' doesn't exist", rule); + + errs.addChild(msg); + + return; } Rule rl = rules.get(rule); - rl.addRejection(reject); + rl.addRejection(reject, errs); } public void findReplaceRule(String rule, String find, String replace) { + findReplaceRule(rule, find, replace, new Tree<>()); + } + + public void findReplaceRule(String rule, String find, String replace, ITree<String> errs) { if (rule == null) { - throw new NullPointerException("rule must not be null"); + errs.addChild("ERROR: Rule must not be null"); + + return; } else if(find == null) { - throw new NullPointerException("find must not be null"); + errs.addChild("ERROR: Find must not be null"); + + return; } else if(replace == null) { - throw new NullPointerException("replace must not be null"); + errs.addChild("ERROR: Replace must not be null"); + + return; } else if (rule.equals("")) { - throw new IllegalArgumentException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return; } else if(!rules.containsKey(rule)) { - throw new IllegalArgumentException(String.format("The rule '%s' doesn't exist", rule)); + String msg = String.format("ERROR: The rule '%s' doesn't exist", rule); + + errs.addChild(msg); + + return; } Rule rl = rules.get(rule); - rl.addFindReplace(find, replace); + rl.addFindReplace(find, replace, errs); } /* diff --git a/src/main/java/bjc/rgens/parser/RGrammarParser.java b/src/main/java/bjc/rgens/parser/RGrammarParser.java index 94c9976..a1dce8a 100755 --- a/src/main/java/bjc/rgens/parser/RGrammarParser.java +++ b/src/main/java/bjc/rgens/parser/RGrammarParser.java @@ -2,17 +2,40 @@ package bjc.rgens.parser; import static bjc.rgens.parser.RGrammarLogging.warn; +import bjc.rgens.parser.elements.*; + +import bjc.utils.data.IPair; +import bjc.utils.data.ITree; +import bjc.utils.data.Pair; +import bjc.utils.data.Tree; + +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; + +import bjc.utils.funcutils.ListUtils; +import bjc.utils.funcutils.SetUtils; +import bjc.utils.funcutils.StringUtils; +import bjc.utils.funcutils.TriConsumer; + +import bjc.utils.ioutils.LevelSplitter; +import bjc.utils.ioutils.blocks.Block; +import bjc.utils.ioutils.blocks.BlockReader; +import bjc.utils.ioutils.blocks.SimpleBlockReader; + +import java.io.IOException; import java.io.Reader; import java.io.StringReader; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.LinkedList; import java.util.HashMap; import java.util.List; import java.util.Map; import bjc.rgens.parser.RGrammarBuilder.AffixType; -import bjc.rgens.parser.elements.CaseElement; -import bjc.rgens.parser.elements.ChanceCaseElement; -import bjc.rgens.parser.elements.SerialCaseElement; -import bjc.rgens.parser.elements.VariableDefCaseElement; + import bjc.utils.data.Pair; import bjc.utils.funcdata.FunctionalList; import bjc.utils.funcdata.IList; @@ -21,6 +44,10 @@ import bjc.utils.ioutils.LevelSplitter; import bjc.utils.ioutils.blocks.Block; import bjc.utils.ioutils.blocks.BlockReader; import bjc.utils.ioutils.blocks.SimpleBlockReader; + +import static bjc.rgens.parser.RGrammarLogging.*; +import static bjc.rgens.parser.RGrammarBuilder.AffixType; + /** * Reads {@link RGrammar} from a input stream. * @@ -48,244 +75,58 @@ public class RGrammarParser { /* Top-level block delimiter. */ private static final String TMPL_TOPLEVEL_BLOCK_DELIM = "\\R\\t{%d}\\.?\\R"; - /* Pragma impls. */ - private static Map<String, TriConsumer<String, RGrammarBuilder, Integer>> pragmas; - - /* Initialize pragmas. */ - static { - pragmas = new HashMap<>(); - - pragmas.put("initial-rule", (body, build, level) -> { - List<String> bits = LevelSplitter.def.levelSplit(body, " "); - - if (bits.size() != 1) { - String msg = "Must specify initial rule"; - throw new GrammarException(msg); - } - - build.setInitialRule(bits.get(0)); - }); - - pragmas.put("grammar-name", (body, build, level) -> { - List<String> bits = LevelSplitter.def.levelSplit(body, " "); - - if (bits.size() != 1) { - String msg = "Must specify grammar name"; - throw new GrammarException(msg); - } - - build.name = bits.get(0); - }); - - pragmas.put("despace-rule", (body, build, level) -> { - List<String> bits = LevelSplitter.def.levelSplit(body, " "); - - if (bits.size() < 1) { - throw new GrammarException("Must specify rules to despace"); - } - - for(String bit : bits) { - build.despaceRule(bit); - } - }); - - pragmas.put("export-rule", (body, build, level) -> { - List<String> exports = LevelSplitter.def.levelSplit(body, " "); - - if(exports.size() < 1) { - throw new GrammarException("Must specify rules to export"); - } - - for (String export : exports) { - build.addExport(export); - } - }); - - pragmas.put("recur-limit", (body, build, level) -> { - List<String> parts = LevelSplitter.def.levelSplit(body, " "); - - if(parts.size() != 2) { - throw new GrammarException("Recur-limit pragma takes two arguments: the name of the rule to set the limit for, and the new value of the limit"); - } - - if(!parts.get(1).matches("\\A\\d+\\Z")) { - throw new GrammarException("Limit value must be an integer"); - } - - build.setRuleRecur(parts.get(0), Integer.parseInt(parts.get(1))); - }); - - pragmas.put("enable-weight", (body, build, level) -> { - List<String> parts = LevelSplitter.def.levelSplit(body, " "); - - if(parts.size() != 1) { - throw new GrammarException("Enable-weight pragma takes one argument: the name of the rule to set the weight factor for"); - } - - build.setWeight(parts.get(0)); - }); - - pragmas.put("enable-descent", (body, build, level) -> { - List<String> parts = LevelSplitter.def.levelSplit(body, " "); - - if(parts.size() != 2) { - throw new GrammarException("Enable-descent pragma takes two arguments: the name of the rule to set the descent factor for, and the new value of the factor"); - } - - if(!parts.get(1).matches("\\A\\d+\\Z")) { - throw new GrammarException("Factor value must be an integer"); - } - - build.setDescent(parts.get(0), Integer.parseInt(parts.get(1))); - }); - - pragmas.put("enable-binomial", (body, build, level) -> { - // @NOTE 9/4/18 - // - // This can be kind of hard to read right off. Is there - // a format to put stuff in that looks better and is - // more readable? - List<String> parts = LevelSplitter.def.levelSplit(body, " "); - - if(parts.size() != 4) { - throw new GrammarException("Enable-descent pragma takes four arguments: the name of the rule to set the binomial factors for, and the three binomial parameters (target, bound trials)"); - } - - if(!parts.get(1).matches("\\A\\d+\\Z")) { - throw new GrammarException("Target value must be an integer"); - } - if(!parts.get(2).matches("\\A\\d+\\Z")) { - throw new GrammarException("Bound value must be an integer"); - } - if(!parts.get(3).matches("\\A\\d+\\Z")) { - throw new GrammarException("Trials value must be an integer"); - } - - build.setBinomial(parts.get(0), Integer.parseInt(parts.get(1)), Integer.parseInt(parts.get(2)), Integer.parseInt(parts.get(3))); - }); - - /* - * @NOTE 4/9/18 - * - * Consider if we want to replace this with something more akin - * to the `definer` feature from DiceLang. This will work fine - * in most cases, but there are some cases where you'd want the - * extra power. No examples are apparent at the moment. - */ - pragmas.put("find-replace-rule", (body, build, level) -> { - List<String> bits = LevelSplitter.def.levelSplit(body, " "); - - if(bits.size() != 3) { - throw new GrammarException("Regex-rule pragma takes three arguments: the name of the rule to process, then the find/replace pair to apply after the rule has been generated."); - } + private static void doAffixWith(String body, RGrammarBuilder build, int level, AffixType afxType, ITree<String> errs) { + int idx = body.indexOf(" "); - build.findReplaceRule(bits.get(0), bits.get(1), bits.get(2)); - }); + if (idx == -1) { + String fmt = "ERROR: Takes at least two arguments, the name of the rule to affix, then what to affix it with\n\tThis can be more than one token, to get them affixed as a group"; - pragmas.put("reject-rule", (body, build, level) -> { - List<String> bits = LevelSplitter.def.levelSplit(body, " "); + String msg = String.format(fmt, afxType.toString().toLowerCase()); - if(bits.size() != 3) { - throw new GrammarException("Reject-rule pragma takes two arguments: the name of the rule to process, then the rejection pattern to apply after the rule has been generated."); - } + errs.addChild(msg); - build.rejectRule(bits.get(0), bits.get(1)); - }); + return; + } - pragmas.put("prefix-with", (body, build, level) -> { - doAffixWith(body, build, level, AffixType.PREFIX); - }); + String rName = body.substring(0, idx); - pragmas.put("suffix-with", (body, build, level) -> { - doAffixWith(body, build, level, AffixType.SUFFIX); - }); + List<CaseElement> elms = new ArrayList<>(); - pragmas.put("circumfix-with", (body, build, level) -> { - doAffixWith(body, build, level, AffixType.CIRCUMFIX); - }); - /* - * @NOTE 9/4/18 - * - * Right now, we ignore additional elements to autovivify. Not - * sure yet if this is the desired behavior. - * - * As I see it, there are a couple of alternatives: - * - * 1) Continue what we're doing. This is simple, but seems - * somewhat inelegant. - * - * 2) Error if more than one is provided. Even simpler, but also - * seems inelegant. - * - * 3) Parse them independantly. Each element is treated as a - * seperate autovar. Seems simple, but may - * cause issues with mixing rule & nonrule - * variables, as well as naming. - * - * 4) Parse them together. Autovars are stored as cases instead - * of case elements. Also simple, but may have - * some odd corner cases, and I can't think of - * any cases where the additional power would - * be useful. - * - * - * - * - * - * - * As an additional aside, we currently error if we provide - * something that isn't a variable definition. This is because - * we pull the name for the auto-vivify variable from the - * element. If we go with option 4 above, the user will have to - * specify a name for the variable, and we should likely add - * some check when the variable is made live that it actually - * created the variable it said it would. - * - */ - pragmas.put("autovivify", (body, build, level) -> { - doAutoVar(body, build, level, false); - }); + parseElementString(body.substring(idx + 1), elms, errs); - pragmas.put("autovivify-rule", (body, build, level) -> { - doAutoVar(body, build, level, true); - }); + build.affixWith(rName, elms, afxType, errs); } - private static void doAffixWith(String body, RGrammarBuilder build, int level, AffixType afxType) { - int idx = body.indexOf(" "); + private static void doAutoVar(List<String> bits, RGrammarBuilder build, int level, boolean isRule, ITree<String> errs) { + if (bits.size() < 1) { + String msg = "Must specify name of variable and definition to autovivify"; - if (idx == -1) { - String msg = "Affixing pragma %s-with takes at least two arguments, the name of the rule to affix, then what to affix it with\n\tThis can be more than one token, to get them affixed as a group"; + errs.addChild(msg); - throw new GrammarException(String.format(msg, afxType.toString().toLowerCase())); + return; } - String rName = body.substring(0, idx); + String[] bitArr = bits.toArray(new String[0]); - IList<CaseElement> elms = parseElementString(body.substring(idx + 1)).getLeft(); + List<CaseElement> elmList = new ArrayList<>(); - build.affixWith(rName, elms, afxType); - } + parseElementString(bitArr, elmList, errs); + CaseElement elm = elmList.get(0); - private static void doAutoVar(String body, RGrammarBuilder build, int level, boolean isRule) { - List<String> bits = LevelSplitter.def.levelSplit(body, " "); + if (elmList.size() > 1) { + String msg = String.format("WARN: Ignoring %d additional elements for autovivify: %s", elmList.size(), elmList.subList(1, elmList.size())); - if (bits.size() < 1) { - String msg = "Must specify name of variable and definition to autovivify"; - throw new GrammarException(msg); - } - - String[] bitArr = bits.toArray(new String[0]); - - IList<CaseElement> elmList = parseElementString(bitArr).getLeft(); - CaseElement elm = elmList.first(); + errs.addChild(msg); - if (elmList.getSize() > 1) { - warn("Ignoring %d additional elements for autovivify: %s", elmList.getSize(), elmList.tail()); + return; } if (!(elm instanceof VariableDefCaseElement)) { - throw new GrammarException(String.format("Autovivify expression must be a variable defn. (expr. %s)", elm)); + String msg = String.format("ERROR: Autovivify expression must be a variable defn. (expr. %s)", elm); + + errs.addChild(msg); + + return; } { @@ -308,64 +149,80 @@ public class RGrammarParser { * @throws GrammarException * Thrown if the grammar has a syntax error. */ - public static RGrammar readGrammar(Reader is) throws GrammarException { + public static RGrammar readGrammar(Reader is, LoadOptions lopts, ITree<String> errs) throws GrammarException { String dlm = String.format(TMPL_TOPLEVEL_BLOCK_DELIM, 0); try (BlockReader reader = new SimpleBlockReader(dlm, is)) { if (!reader.hasNextBlock()) { - throw new GrammarException("At least one top-level block must be present"); + errs.addChild("At least one top-level block must be present"); + + return null; } - try { RGrammarBuilder build = new RGrammarBuilder(); for(Block block : reader) { - if(DEBUG) - System.err.printf("Handling top-level block (%s)\n", block); + if(lopts.doTrace) { + String msg = String.format("TRACE: Handling top-level block (%s)\n", block); + + errs.addChild(msg); + } + + String msg = String.format("INFO: Block %d (%d-%d) (offset %d, %d-%d)", block.blockNo, block.startLine, block.endLine, block.lineOffset, block.lineOffset + block.startLine, block.lineOffset + block.endLine); + + ITree<String> kid = new Tree<>(msg); + + handleBlock(build, block.contents, 0, block.startLine, lopts, kid); - handleBlock(build, block.contents, 0, block.startLine); + if (kid.size() > 0) errs.addChild(kid); } - if(LINES) - System.err.printf("%d ", reader.getBlock().endLine); + if(lopts.doTrace) { + errs.addChild(String.format("TRACE: Ended at line %d ", reader.getBlock().endLine)); + } return build.toRGrammar(); - } catch (GrammarException gex) { - String msg = String.format("Error in block (%s)", reader.getBlock()); - throw new GrammarException(msg, gex, gex.getRootMessage()); - } - } catch (Exception ex) { - throw new GrammarException("Unknown error handling block", ex, ex.getMessage()); + } catch (IOException ioex) { + String msg = String.format("ERROR: Unknown I/O error: %s", ioex.getMessage()); + + errs.addChild(msg); } + + return null; } - /* Throughout these, level indicates the nesting level of that construct. */ + /* + * Throughout these, level indicates the nesting level of that construct, + * and lineOffset indicates the total number of lines to adjust the block + * line numbers by. + */ /* Handles an arbitrary block. */ - private static void handleBlock(RGrammarBuilder build, String block, - int level, int lineOffset) throws GrammarException { + private static void handleBlock(RGrammarBuilder build, String block, int level, int lineOffset, LoadOptions lopts, ITree<String> errs) { /* Discard empty blocks. */ - if (block.equals("") || block.matches("\\R")) - return; + if (block.equals("") || block.matches("\\R")) return; int typeSep = block.indexOf(' '); if (typeSep == -1) { - throw new GrammarException( - "A block must start with a introducer, followed by a space, then the rest of the block"); + errs.addChild("ERROR: A block must start with a introducer, followed by a space, then the rest of the block"); } String blockType = block.substring(0, typeSep).trim(); if (blockType.equalsIgnoreCase("pragma")) { - handlePragmaBlock(block, build, level, lineOffset); + handlePragmaBlock(block, build, level, lineOffset, lopts, errs); } else if (blockType.startsWith("[")) { - handleRuleBlock(block, build, level, lineOffset); + handleRuleBlock(block, build, level, lineOffset, lopts, errs); } else if (blockType.equalsIgnoreCase("where")) { - handleWhereBlock(block, build, level, lineOffset); + handleWhereBlock(block, build, level, lineOffset, lopts, errs); } else if (blockType.startsWith("#")) { - if(DEBUG) - System.err.printf("Handled comment block (%s)\n", block); + if(lopts.doTrace) { + String msg = String.format("TRACE: Handled comment block (%s)\n", block); + + errs.addChild(msg); + } + /* * Comment block. * @@ -376,137 +233,368 @@ public class RGrammarParser { */ return; } else { - String msg = String.format("Unknown block type: '%s'", blockType); - throw new GrammarException(msg); + String msg = String.format("ERROR: Unknown block type: '%s'", blockType); + + errs.addChild(msg); } } /* Handle reading a block of pragmas. */ - private static void handlePragmaBlock(String block, RGrammarBuilder build, - int level, int lineOffset) throws GrammarException { + private static void handlePragmaBlock(String block, RGrammarBuilder build, int level, int lineOffset, LoadOptions lopts, ITree<String> errs) { String dlm = String.format(TMPL_PRAGMA_BLOCK_DELIM, level); + try (BlockReader pragmaReader = new SimpleBlockReader(dlm, new StringReader(block))) { - try { - for(Block pragma : pragmaReader) { - pragma.lineOffset = lineOffset; + for(Block pragma : pragmaReader) { + pragma.lineOffset = lineOffset; - if(DEBUG) - System.err.printf("Handled pragma block (%s)\n", pragma); + if(lopts.doTrace) { + System.err.printf("TRACE: Handled pragma block (%s)\n", pragma); + } - String pragmaContents = pragma.contents; + String pragmaContents = pragma.contents; - int pragmaSep = pragmaContents.indexOf(' '); + int pragmaSep = pragmaContents.indexOf(' '); - if (pragmaSep == -1) { - String msg = "A pragma invocation must consist of the word pragma, followed by a space, then the body of the pragma"; + if (pragmaSep == -1) { + String msg = "ERROR: A pragma invocation must consist of the word pragma, followed by a space, then the body of the pragma"; - throw new GrammarException(msg); - } + errs.addChild(msg); + return; + } - String pragmaLeader = pragmaContents.substring(0, pragmaSep); - String pragmaBody = pragmaContents.substring(pragmaSep + 1); + String pragmaLeader = pragmaContents.substring(0, pragmaSep); + String pragmaBody = pragmaContents.substring(pragmaSep + 1); - if (!pragmaLeader.equalsIgnoreCase("pragma")) { - String msg = String.format("Illegal line leader in pragma block: '%s'", pragmaLeader); + if (!pragmaLeader.equalsIgnoreCase("pragma")) { + String msg = String.format("ERROR: Illegal line leader in pragma block: '%s'", pragmaLeader); - throw new GrammarException(msg); - } - - handlePragma(pragmaBody, build, level, pragma.startLine + lineOffset); + errs.addChild(msg); + return; } - } catch (GrammarException gex) { - Block pragma = pragmaReader.getBlock(); - String msg = String.format("Error in pragma: (%s)", pragma); - throw new GrammarException(msg, gex, gex.getRootMessage()); + handlePragma(pragmaBody, build, level, pragma.startLine + lineOffset, lopts, errs); } - } catch (Exception ex) { - throw new GrammarException("Unknown error handling pragma block", ex, ex.getMessage()); + } catch (IOException ioex) { + String msg = String.format("ERROR: Unknown I/O error in pragma block: %s", ioex.getMessage()); + + errs.addChild(msg); } } /* Handle an individual pragma in a block. */ - private static void handlePragma(String pragma, RGrammarBuilder build, - int level, int lineOffset) throws GrammarException { + private static void handlePragma(String pragma, RGrammarBuilder build, int level, int lineOffset, LoadOptions lopts, ITree<String> errs) { int bodySep = pragma.indexOf(' '); - if (bodySep == -1) - bodySep = pragma.length(); + if (bodySep == -1) bodySep = pragma.length(); String pragmaName = pragma.substring(0, bodySep); String pragmaBody = pragma.substring(bodySep + 1); - if (pragmas.containsKey(pragmaName)) { - try { - if(DEBUG) - System.err.printf("Handled pragma '%s'\n", pragmaName); + if(lopts.doTrace) { + String msg = String.format("TRACE: Handled pragma '%s'\n", pragmaName); + + errs.addChild(msg); + } + + // Pragma bits + List<String> bits = StringUtils.levelSplit(pragmaBody, " "); + + String fmt = String.format("INFO: Pragma '%s'", pragmaName); + ITree<String> kid = new Tree<>(fmt); + + switch (pragmaName) { + case "initial-rule": + { + if (bits.size() != 1) { + kid.addChild("ERROR: Must specify initial rule"); - pragmas.get(pragmaName).accept(pragmaBody, build, level); - } catch (GrammarException gex) { - String msg = String.format("Error in pragma '%s'", pragmaName); + break; + } - throw new GrammarException(msg, gex); + build.setInitialRule(bits.get(0), kid); } - } else { - String msg = String.format("Unknown pragma '%s'", pragmaName); + break; + case "grammar-name": + { + if (bits.size() != 1) { + kid.addChild("ERROR: Must specify grammar name"); + + break; + } + + build.name = bits.get(0); + } + break; + case "despace-rule": + { + if (bits.size() < 1) { + kid.addChild("ERROR: Must specify at least one rule to despace"); + + break; + } + + for(String bit : bits) { + build.despaceRule(bit, kid, lopts.doTrace); + } + } + break; + case "export-rule": + { + if(bits.size() < 1) { + kid.addChild("ERROR: Must specify rules to export"); + + break; + } + + for (String export : bits) { + build.addExport(export); + } + } + break; + case "recur-limit": + { + if(bits.size() != 2) { + kid.addChild("ERROR: Takes two arguments: the name of the rule to set the limit for, and the new value of the limit"); + + break; + } + + if(!bits.get(1).matches("\\A\\d+\\Z")) { + kid.addChild("ERROR: Limit value must be an integer"); + + break; + } + + build.setRuleRecur(bits.get(0), Integer.parseInt(bits.get(1)), kid); + } + break; + case "enable-weight": + { + if(bits.size() != 1) { + kid.addChild("ERROR: Takes one argument: the name of the rule to enable standard weighting for"); + } + + build.setWeight(bits.get(0), kid); + } + break; + case "enable-descent": + { + if(bits.size() != 2) { + kid.addChild("ERROR: Takes two arguments: The name of the rule to set to descent mode, and the value of the descent factor"); + + break; + } + + if(!bits.get(1).matches("\\A\\d+\\Z")) { + kid.addChild("ERROR: Factor value must be an integer"); + + break; + } + + build.setDescent(bits.get(0), Integer.parseInt(bits.get(1)), kid); + } + break; + case "enable-binomial": + { + // @NOTE 9/4/18 + // + // This can be kind of hard to read right off. Is there + // a format to put stuff in that looks better and is + // more readable? + + if(bits.size() != 4) { + kid.addChild("ERROR: Takes four arguments: the name of the rule to set the binomial factors for, and the three binomial parameters (target, bound, trials) (target/bound chance of success)"); + + break; + } + + if(!bits.get(1).matches("\\A\\d+\\Z")) { + kid.addChild("ERROR: Target value must be an integer"); + + break; + } + + if(!bits.get(2).matches("\\A\\d+\\Z")) { + kid.addChild("ERROR: Bound value must be an integer"); + + break; + } + + if(!bits.get(3).matches("\\A\\d+\\Z")) { + kid.addChild("ERROR: Trials value must be an integer"); + + break; + } + + build.setBinomial(bits.get(0), Integer.parseInt(bits.get(1)), Integer.parseInt(bits.get(2)), Integer.parseInt(bits.get(3)), kid); + } + break; + case "find-replace-rule": + { + /* + * @NOTE 4/9/18 + * + * Consider if we want to replace this with something more akin + * to the `definer` feature from DiceLang. This will work fine + * in most cases, but there are some cases where you'd want the + * extra power. No examples are apparent at the moment. + */ + if(bits.size() != 3) { + kid.addChild("ERROR: Takes three arguments: the name of the rule to process, then the find/replace pair to apply after the rule has been generated."); + + break; + } + + build.findReplaceRule(bits.get(0), bits.get(1), bits.get(2), kid); + } + break; + case "reject-rule": + { + if(bits.size() != 2) { + kid.addChild("ERROR: Takes two arguments: the name of the rule to process, then the rejection pattern to apply after the rule has been generated."); - throw new GrammarException(msg); + break; + } + + build.rejectRule(bits.get(0), bits.get(1), kid); + } + break; + case "prefix-with": + { + doAffixWith(pragmaBody, build, level, AffixType.PREFIX, kid); + } + break; + case "suffix-with": + { + doAffixWith(pragmaBody, build, level, AffixType.SUFFIX, kid); + } + break; + case "circumfix-with": + { + doAffixWith(pragmaBody, build, level, AffixType.CIRCUMFIX, kid); + } + break; + /* + * @NOTE 9/4/18 + * + * Right now, we ignore additional elements to autovivify. Not + * sure yet if this is the desired behavior. + * + * As I see it, there are a couple of alternatives: + * + * 1) Continue what we're doing. This is simple, but seems + * somewhat inelegant. + * + * 2) Error if more than one is provided. Even simpler, but also + * seems inelegant. + * + * 3) Parse them independantly. Each element is treated as a + * seperate autovar. Seems simple, but may + * cause issues with mixing rule & nonrule + * variables, as well as naming. + * + * 4) Parse them together. Autovars are stored as cases instead + * of case elements. Also simple, but may have + * some odd corner cases, and I can't think of + * any cases where the additional power would + * be useful. + * + * + * + * + * + * + * As an additional aside, we currently error if we provide + * something that isn't a variable definition. This is because + * we pull the name for the auto-vivify variable from the + * element. If we go with option 4 above, the user will have to + * specify a name for the variable, and we should likely add + * some check when the variable is made live that it actually + * created the variable it said it would. + * + */ + case "autovivify": + { + doAutoVar(bits, build, level, false, kid); + } + break; + case "autovivify-rule": + { + doAutoVar(bits, build, level, true, kid); + } + break; + default: + { + String msg = String.format("ERROR: Unknown pragma '%s'", pragmaName); + + kid.addChild(msg); + } } + + if (kid.size() > 0) errs.addChild(kid); } /* Handle a block of a rule declaration and one or more cases. */ - private static void handleRuleBlock(String ruleBlock, RGrammarBuilder build, - int level, int lineOffset) throws GrammarException { + private static void handleRuleBlock(String ruleBlock, RGrammarBuilder build, int level, int lineOffset, LoadOptions lopts, ITree<String> errs) { String dlm = String.format(TMPL_RULEDECL_BLOCK_DELIM, level); + try (BlockReader ruleReader = new SimpleBlockReader(dlm, new StringReader(ruleBlock))) { - try { - if (ruleReader.hasNextBlock()) { - /* Rule with a declaration followed by multiple cases. */ - ruleReader.nextBlock(); - Block declBlock = ruleReader.getBlock(); - declBlock.lineOffset = lineOffset; + ITree<String> kid = new Tree<>(); - String declContents = declBlock.contents; - Rule rl = handleRuleDecl(build, declContents, lineOffset + declBlock.startLine); + if (ruleReader.hasNextBlock()) { + /* Rule with a declaration followed by multiple cases. */ + ruleReader.nextBlock(); + Block declBlock = ruleReader.getBlock(); + declBlock.lineOffset = lineOffset; - for(Block block : ruleReader) { - /* Ignore comment lines. */ - if(block.contents.trim().startsWith("#")) return; + String declContents = declBlock.contents; + Rule rl = handleRuleDecl(build, declContents, lineOffset + declBlock.startLine, kid); - handleRuleCase(block.contents, build, rl, block.startLine + lineOffset); - } - } else { - /* Rule with a declaration followed by a single case. */ - handleRuleDecl(build, ruleBlock, lineOffset); - } - } catch (GrammarException gex) { - String msg = String.format("Error in rule case (%s)", ruleReader.getBlock()); + // Error occured during rule processing + if (rl == null) return; + + for(Block block : ruleReader) { + /* Ignore comment lines. */ + if(block.contents.trim().startsWith("#")) return; - throw new GrammarException(msg, gex, gex.getRootMessage()); + handleRuleCase(block.contents, build, rl, block.startLine + lineOffset, kid); + } + } else { + /* Rule with a declaration followed by a single case. */ + handleRuleDecl(build, ruleBlock, lineOffset, kid); } - } catch (Exception ex) { - throw new GrammarException("Unknown error handling rule block", ex, ex.getMessage()); + + if (kid.size() > 0) errs.addChild(kid); + } catch (IOException ex) { + String msg = String.format("ERROR: Unknown error handling rule block (%s)",ex.getMessage()); + + errs.addChild(msg); } } /* Handle a rule declaration and its initial case. */ - private static Rule handleRuleDecl(RGrammarBuilder build, String declContents, int lineOffset) { + private static Rule handleRuleDecl(RGrammarBuilder build, String declContents, int lineOffset, ITree<String> errs) { int declSep = declContents.indexOf("\u2192"); if (declSep == -1) { /* * @NOTE * We should maybe remove support for the old - * syntax at some point. However, maybe we don't - * want to do so so as to make inputting grammars - * easier. + * syntax at some point. + * + * We don't want to do so so as to make inputting grammars easier, + * since that character is not easy to type on a normal keyboard, + * and takes 4 keystrokes in vim as composed to 1 for the normal + * one. */ declSep = declContents.indexOf(' '); if (declSep == -1) { String msg = "A rule must be given at least one case in its declaration, and seperated from that case by \u2192 or ' '"; - throw new GrammarException(msg); + errs.addChild(msg); + + return null; } } @@ -514,69 +602,78 @@ public class RGrammarParser { String ruleBody = declContents.substring(declSep + 1).trim(); if (ruleName.equals("")) { - throw new GrammarException("The empty string is not a valid rule name"); + errs.addChild("ERROR: The empty string is not a valid rule name"); + + return null; } - Rule rul = build.getOrCreateRule(ruleName); + errs.setHead("INFO: Rule " + ruleName); - handleRuleCase(ruleBody, build, rul, lineOffset); + Rule rul = build.getOrCreateRule(ruleName, errs); + + if (rul == null) return null; + + handleRuleCase(ruleBody, build, rul, lineOffset, errs); return rul; } /* Handle a single case of a rule. */ - private static void handleRuleCase(String cse, RGrammarBuilder build, Rule rul, int lineOffset) { - Pair<IList<CaseElement>, Integer> caseParts = parseElementString(cse); + private static void handleRuleCase(String cse, RGrammarBuilder build, Rule rul, int lineOffset, ITree<String> errs) { + List<CaseElement> elms = new ArrayList<>(); - rul.addCase(new NormalRuleCase(caseParts.getLeft()), caseParts.getRight()); + int weights = parseElementString(cse, elms, errs); + + rul.addCase(new NormalRuleCase(elms), weights); } /* Handle a where block (a block with local rules). */ - private static void handleWhereBlock(String block, RGrammarBuilder build, - int level, int lineOffset) throws GrammarException { + private static void handleWhereBlock(String block, RGrammarBuilder build, int level, int lineOffset, LoadOptions lopts, ITree<String> errs) { int nlIndex = block.indexOf("\\nin"); if (nlIndex == -1) { - throw new GrammarException("Where block must be a context followed by a body"); + errs.addChild("ERROR: Where block must be a context followed by a body"); + + return; } String trimBlock = block.substring(nlIndex).trim(); String whereDelim = String.format(TMPL_WHERE_BLOCK_DELIM, level); - try (BlockReader whereReader = new SimpleBlockReader(whereDelim, - new StringReader(trimBlock))) { - try { - Block whereCtx = whereReader.next(); - whereCtx.lineOffset = lineOffset; - - StringReader ctxReader = new StringReader(whereCtx.contents.trim()); - String ctxDelim = String.format(TMPL_TOPLEVEL_BLOCK_DELIM, level + 1); - - try (BlockReader bodyReader = new SimpleBlockReader(ctxDelim, ctxReader)) { - Block whereBody = whereReader.next(); - whereBody.lineOffset = lineOffset + whereCtx.startLine; - - System.err.printf("\tUNIMPLEMENTED WHERE:\n%s\n", whereBody.contents); - /** - * @TODO 10/11/17 Ben Culkin :WhereBlocks - * Implement where blocks. - * - * A where block has the context evaluated - * in a new context, and the body executed - * in that context. - */ - } - } catch (GrammarException gex) { - throw new GrammarException(String.format("Error in where block (%s)", - whereReader.getBlock()), gex, gex.getRootMessage()); + try (BlockReader whereReader = new SimpleBlockReader(whereDelim, new StringReader(trimBlock))) { + Block whereCtx = whereReader.next(); + whereCtx.lineOffset = lineOffset; + + StringReader ctxReader = new StringReader(whereCtx.contents.trim()); + String ctxDelim = String.format(TMPL_TOPLEVEL_BLOCK_DELIM, level + 1); + + try (BlockReader bodyReader = new SimpleBlockReader(ctxDelim, ctxReader)) { + @SuppressWarnings("unused") + Block whereBody = whereReader.next(); + whereBody.lineOffset = lineOffset + whereCtx.startLine; + + String msg = String.format("UNIMPLEMENTED WHERE:\n%s\n", whereBody.contents); + errs.addChild(msg); + /** + * @TODO 10/11/17 Ben Culkin :WhereBlocks + * Implement where blocks. + * + * A where block has the context evaluated + * in a new context, and the body executed + * in that context. + */ } - } catch (Exception ex) { - throw new GrammarException("Unknown error in where block", ex, ex.getMessage()); + } catch (IOException ioex) { + String msg = String.format("Unknown error in where block (%s)", ioex.getMessage()); } } - public static Pair<IList<CaseElement>, Integer> parseElementString(String cses) { + public static int parseElementString(String cses, List<CaseElement> elms) { + return parseElementString(cses, elms, new Tree<>()); + } + + public static int parseElementString(String cses, List<CaseElement> elms, ITree<String> errs) { /* * @NOTE * @@ -589,12 +686,14 @@ public class RGrammarParser { * return parseElementString(cseList.toArray(new String[0])); */ - return parseElementString(cses.split(" ")); + return parseElementString(cses.split(" "), elms, errs); } - public static Pair<IList<CaseElement>, Integer> parseElementString(String... cses) { - IList<CaseElement> caseParts = new FunctionalList<>(); + public static int parseElementString(String[] cses, List<CaseElement> elms) { + return parseElementString(cses, elms, new Tree<>()); + } + public static int parseElementString(String[] cses, List<CaseElement> caseParts, ITree<String> errs) { int weight = 1; int repCount = 1; @@ -644,7 +743,7 @@ public class RGrammarParser { * a setting on CaseElement, instead of having * their own CaseElement type. */ - CaseElement elm = caseParts.popLast(); + CaseElement elm = caseParts.remove(caseParts.size() - 1); if(repCount == 0) { /* Skip no-reps */ @@ -697,6 +796,6 @@ public class RGrammarParser { } } - return new Pair<>(caseParts, weight); + return weight; } } diff --git a/src/main/java/bjc/rgens/parser/RGrammarSet.java b/src/main/java/bjc/rgens/parser/RGrammarSet.java index f827e1f..ed09bdf 100755 --- a/src/main/java/bjc/rgens/parser/RGrammarSet.java +++ b/src/main/java/bjc/rgens/parser/RGrammarSet.java @@ -29,17 +29,20 @@ public class RGrammarSet { public Map<String, String> loadedFrom; /* @NOTE These are replaced by the logging setup */ - public static final boolean PERF = true; - public static final boolean DEBUG = true; + public static final boolean PERF = true; /** Create a new set of randomized grammars. */ public RGrammarSet() { + this(false); + } + + public RGrammarSet(boolean orderExports) { grammars = new HashMap<>(); // @NOTE // Swap which line is commented to toggle ordering of exports - //exportedRules = new TreeMap<>(); - exportedRules = new HashMap<>(); + if (orderExports) exportedRules = new TreeMap<>(); + else exportedRules = new HashMap<>(); loadedFrom = new HashMap<>(); @@ -78,8 +81,7 @@ public class RGrammarSet { exportedRules.put(export.name, export); - if(DEBUG) - debug("%s (%d cases) exported from %s", export.name, export.getCases().getSize(), grammarName); + debug("%s (%d cases) exported from %s", export.name, export.getCases().getSize(), grammarName); } /* Add exports to grammar. */ diff --git a/src/main/java/bjc/rgens/parser/RGrammarTest.java b/src/main/java/bjc/rgens/parser/RGrammarTest.java index 1e335bf..9559fd4 100755 --- a/src/main/java/bjc/rgens/parser/RGrammarTest.java +++ b/src/main/java/bjc/rgens/parser/RGrammarTest.java @@ -3,11 +3,17 @@ package bjc.rgens.parser; import static bjc.rgens.parser.RGrammarLogging.error; import static bjc.rgens.parser.RGrammarLogging.perf; +import bjc.utils.data.ITree; +import bjc.utils.data.Tree; + import java.io.IOException; + import java.net.URISyntaxException; import java.net.URL; + import java.nio.file.Path; import java.nio.file.Paths; + import java.util.Random; import bjc.rgens.parser.templates.GrammarTemplate; @@ -28,9 +34,24 @@ public class RGrammarTest { URL rsc = RGrammarTest.class.getResource("/server-config-sample.gcfg"); try { + LoadOptions lopts = new LoadOptions(); + + // Set up load options + lopts.doPerf = true; + lopts.doDebug = false; + lopts.doTrace = false; + + lopts.defName = "default"; + /* Load a grammar set. */ - Path cfgPath = Paths.get(rsc.toURI()); - ConfigSet cfgSet = ConfigLoader.fromConfigFile(cfgPath); + Path cfgPath = Paths.get(rsc.toURI()); + + String msg = String.format("INFO: Loading config file %s", cfgPath); + ITree<String> errTree = new Tree<>(msg); + + ConfigSet cfgSet = ConfigLoader.fromConfigFile(cfgPath, lopts, errTree); + + System.err.print(errTree); for(RGrammarSet gramSet : cfgSet.grammars.values()) { testGrammarSet(gramSet); diff --git a/src/main/java/bjc/rgens/parser/RGrammars.java b/src/main/java/bjc/rgens/parser/RGrammars.java index 9fe8dea..b049731 100755 --- a/src/main/java/bjc/rgens/parser/RGrammars.java +++ b/src/main/java/bjc/rgens/parser/RGrammars.java @@ -30,7 +30,12 @@ public class RGrammars { Path cfgPath = Paths.get(rsc); - cfgSet = ConfigLoader.fromConfigFile(cfgPath); + LoadOptions lopts = new LoadOptions(); + + lopts.doPerf = false; + lopts.defName = "default"; + + cfgSet = ConfigLoader.fromConfigFile(cfgPath, lopts); } catch (IOException | URISyntaxException ex) { RuntimeException rtex = new RuntimeException("Could not load grammars"); diff --git a/src/main/java/bjc/rgens/parser/RegexRuleCase.java b/src/main/java/bjc/rgens/parser/RegexRuleCase.java index 9522428..4d42ed6 100755 --- a/src/main/java/bjc/rgens/parser/RegexRuleCase.java +++ b/src/main/java/bjc/rgens/parser/RegexRuleCase.java @@ -1,7 +1,10 @@ package bjc.rgens.parser; import bjc.rgens.parser.elements.CaseElement; -import bjc.utils.funcdata.IList; + +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; /* * @TODO @@ -9,7 +12,7 @@ import bjc.utils.funcdata.IList; * Actually implement this */ public class RegexRuleCase extends RuleCase { - public RegexRuleCase(IList<CaseElement> elements) { + public RegexRuleCase(List<CaseElement> elements) { super(elements); } @@ -18,7 +21,7 @@ public class RegexRuleCase extends RuleCase { // TODO } - public RegexRuleCase withElements(IList<CaseElement> elements) { + public RegexRuleCase withElements(List<CaseElement> elements) { return new RegexRuleCase(elements); } } diff --git a/src/main/java/bjc/rgens/parser/Rule.java b/src/main/java/bjc/rgens/parser/Rule.java index 90b14f9..15e9b5f 100755 --- a/src/main/java/bjc/rgens/parser/Rule.java +++ b/src/main/java/bjc/rgens/parser/Rule.java @@ -3,6 +3,13 @@ package bjc.rgens.parser; import static bjc.rgens.parser.RGrammarLogging.fine; import static bjc.utils.data.IPair.pair; +import bjc.utils.data.IPair; +import bjc.utils.data.ITree; +import bjc.utils.data.Tree; +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; +import bjc.utils.gen.WeightedRandom; + import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -120,20 +127,32 @@ public class Rule { } public void addRejection(String reject) { + addRejection(reject, new Tree<>()); + } + + public void addRejection(String reject, ITree<String> errs) { try { Pattern.compile(reject); } catch (PatternSyntaxException psex) { - throw new GrammarException(String.format("String %s is not a valid regex for rejection", reject), psex); + String msg = String.format("ERROR: '%s' is not a valid regex for rejection (%s)", reject, psex.getMessage()); } rejectionPreds.add(reject); } public void addFindReplace(String find, String replace) { + addFindReplace(find, replace, new Tree<>()); + } + + public void addFindReplace(String find, String replace, ITree<String> errs) { try { Pattern.compile(find); } catch (PatternSyntaxException psex) { - throw new GrammarException(String.format("String %s is not a valid regex for finding", find), psex); + String msg = String.format("ERROR: '%s' is not a valid regex for finding (%s)", find, psex.getMessage()); + + errs.addChild(msg); + + return; } findReplaces.add(pair(find, replace)); diff --git a/src/main/java/bjc/rgens/parser/RuleCase.java b/src/main/java/bjc/rgens/parser/RuleCase.java index 33aea0c..dacb16e 100755 --- a/src/main/java/bjc/rgens/parser/RuleCase.java +++ b/src/main/java/bjc/rgens/parser/RuleCase.java @@ -1,7 +1,8 @@ package bjc.rgens.parser; import bjc.rgens.parser.elements.CaseElement; -import bjc.utils.funcdata.IList; + +import java.util.List; /* * @NOTE @@ -22,7 +23,7 @@ public abstract class RuleCase { public Rule belongsTo; - public IList<CaseElement> elementList; + public List<CaseElement> elementList; /** * Create a new case of the specified type that takes a element list @@ -32,7 +33,7 @@ public abstract class RuleCase { * The element list parameter of the case. * */ - protected RuleCase(IList<CaseElement> elements) { + protected RuleCase(List<CaseElement> elements) { elementList = elements; serial = nextSerial; @@ -41,7 +42,7 @@ public abstract class RuleCase { public abstract void generate(GenerationState state); - public abstract RuleCase withElements(IList<CaseElement> elements); + public abstract RuleCase withElements(List<CaseElement> elements); public String toString() { if(debugName != null) { diff --git a/src/main/java/bjc/rgens/parser/elements/InlineRuleCaseElement.java b/src/main/java/bjc/rgens/parser/elements/InlineRuleCaseElement.java index 82bc6ee..9385ded 100644 --- a/src/main/java/bjc/rgens/parser/elements/InlineRuleCaseElement.java +++ b/src/main/java/bjc/rgens/parser/elements/InlineRuleCaseElement.java @@ -2,15 +2,30 @@ package bjc.rgens.parser.elements; import bjc.rgens.parser.GenerationState; import bjc.rgens.parser.RGrammarParser; + import bjc.utils.data.IPair; import bjc.utils.funcdata.IList; + + +import bjc.utils.data.ITree; +import bjc.utils.data.Tree; +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcutils.StringUtils; + import bjc.utils.gen.WeightedRandom; import bjc.utils.ioutils.LevelSplitter; +import java.util.ArrayList; +import java.util.List; + public class InlineRuleCaseElement extends CaseElement { public final WeightedRandom<CaseElement> elements; public InlineRuleCaseElement(String... parts) { + this(new Tree<>(), parts); + } + + public InlineRuleCaseElement(ITree<String> errs, String... parts) { super(true); this.elements = new WeightedRandom<>(); @@ -24,10 +39,10 @@ public class InlineRuleCaseElement extends CaseElement { partArr = new String[] {part}; } - IPair<IList<CaseElement>, Integer> par = RGrammarParser.parseElementString(partArr); - int prob = par.getRight(); + List<CaseElement> elms = new ArrayList<>(); + int prob = RGrammarParser.parseElementString(partArr, elms, errs); - for(CaseElement elm : par.getLeft()) { + for(CaseElement elm : elms) { elements.addProbability(prob, elm); } } diff --git a/src/main/java/bjc/rgens/parser/elements/vars/VariableElement.java b/src/main/java/bjc/rgens/parser/elements/vars/VariableElement.java index 013f903..eb36af8 100644 --- a/src/main/java/bjc/rgens/parser/elements/vars/VariableElement.java +++ b/src/main/java/bjc/rgens/parser/elements/vars/VariableElement.java @@ -37,6 +37,7 @@ public abstract class VariableElement { for (String npart : parts) { // @HACK + // // This is so that inline refs to hypenized rule names // work. Not sure this is a good impl. strategy String part = npart.replaceAll("\\(|\\)", ""); diff --git a/src/main/java/bjc/rgens/parser/templates/GrammarTemplate.java b/src/main/java/bjc/rgens/parser/templates/GrammarTemplate.java index a57897c..ef0bf3a 100644 --- a/src/main/java/bjc/rgens/parser/templates/GrammarTemplate.java +++ b/src/main/java/bjc/rgens/parser/templates/GrammarTemplate.java @@ -1,5 +1,12 @@ package bjc.rgens.parser.templates; +import bjc.rgens.parser.ConfigSet; +import bjc.rgens.parser.GenerationState; +import bjc.rgens.parser.LoadOptions; + +import bjc.utils.data.ITree; +import bjc.utils.data.Tree; + import java.io.Reader; import java.util.ArrayList; import java.util.List; @@ -8,19 +15,48 @@ import java.util.Scanner; import bjc.rgens.parser.ConfigSet; import bjc.rgens.parser.GenerationState; +/** + * Represents a grammar template. + * + * @author Ben Culkin + */ public class GrammarTemplate { + /** + * The config set the template belongs to. + */ public ConfigSet belongsTo; + /** + * The name of the template. + */ public String name; + /** + * The elements in the template. + */ public final List<TemplateElement> elements; + /** + * Whether or not to do spacing of elements. + */ public boolean doSpacing = true; + /** + * Create a new grammar template. + * + * @param elements + * The elements that belong to the template. + */ public GrammarTemplate(List<TemplateElement> elements) { this.elements = elements; } + /** + * Generate the template. + * + * @param state + * The state for generating a template. + */ public void generate(GenerationState state) { for(TemplateElement element : elements) { element.generate(state); @@ -30,27 +66,43 @@ public class GrammarTemplate { } } - public static GrammarTemplate readTemplate(Reader rdr) { + /** + * Read a template from an input source. + * + * @param rdr + * The reader to get input from. + * + * @param errs + * The errors/information to generate during loading. + * + * @return The generated template. + */ + public static GrammarTemplate readTemplate(Reader rdr, ITree<String> errs) { List<TemplateElement> elements = new ArrayList<>(); GrammarTemplate template = new GrammarTemplate(elements); Scanner scn = new Scanner(rdr); scn.useDelimiter("\\R"); - //int lno = 0; + int lno = 0; while(scn.hasNextLine()) { String ln = scn.nextLine(); - //lno += 1; + lno += 1; + ITree<String> kid = new Tree<>(String.format("INFO: Line %d", lno)); switch(ln.charAt(0)) { case '#': // Ignore comments break; case '/': - handlePragma(elements, template, ln.substring(1)); + handlePragma(elements, template, ln.substring(1), kid); break; default: - handleLine(elements, template, ln); + handleLine(elements, template, ln, kid); + } + + if (kid.size() > 0) { + errs.addChild(kid); } } @@ -59,18 +111,26 @@ public class GrammarTemplate { return template; } - private static void handleLine(List<TemplateElement> elements, GrammarTemplate template, String ln) { + private static void handleLine(List<TemplateElement> elements, GrammarTemplate template, String ln, ITree<String> errs) { if(ln.matches("^.*?\\$@.+?@\\$.*$")) { /* * Handle live templates */ - elements.add(new LiveTemplateElement(ln)); + elements.add(new LiveTemplateElement(ln, errs)); } else { - elements.add(new LiteralTemplateElement(ln)); + elements.add(new LiteralTemplateElement(ln, errs)); } } - private static void handlePragma(List<TemplateElement> elements, GrammarTemplate template, String ln) { - // TODO + private static void handlePragma(List<TemplateElement> elements, GrammarTemplate template, String ln, ITree<String> errs) { + /* + * @TODO 2/8/2019 Ben Culkin :TemplatePragmas + * Implement template pragmas. + * + * Implement template pragmas. Mainly, this means that the 'choose' + * based ones need to be implemented based off of the provided sample + * template. + */ + errs.addChild("ERROR: Template pragmas are not yet implemented"); } } diff --git a/src/main/java/bjc/rgens/parser/templates/LiteralTemplateElement.java b/src/main/java/bjc/rgens/parser/templates/LiteralTemplateElement.java index ca4b32f..2f0a571 100644 --- a/src/main/java/bjc/rgens/parser/templates/LiteralTemplateElement.java +++ b/src/main/java/bjc/rgens/parser/templates/LiteralTemplateElement.java @@ -1,16 +1,33 @@ package bjc.rgens.parser.templates; +import bjc.utils.data.ITree; + import bjc.rgens.parser.GenerationState; +/** + * Represents a literal text element. + * + * @author Ben Culkin + */ public class LiteralTemplateElement extends TemplateElement { + /** + * The literal value of the element. + */ public final String val; - public LiteralTemplateElement(String val) { + /** + * Create a new literal template element. + * + * @param val + * The string to insert. + */ + public LiteralTemplateElement(String val, ITree<String> errs) { super(true); this.val = val; } + @Override public void generate(GenerationState state) { state.appendContents(val); } diff --git a/src/main/java/bjc/rgens/parser/templates/LiveTemplateElement.java b/src/main/java/bjc/rgens/parser/templates/LiveTemplateElement.java index 80c8315..81c0c47 100644 --- a/src/main/java/bjc/rgens/parser/templates/LiveTemplateElement.java +++ b/src/main/java/bjc/rgens/parser/templates/LiveTemplateElement.java @@ -6,6 +6,12 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import bjc.utils.data.BooleanToggle; +import bjc.utils.data.ITree; +import bjc.utils.data.Tree; +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; + import bjc.rgens.parser.GenerationState; import bjc.rgens.parser.RGrammarParser; import bjc.rgens.parser.elements.CaseElement; @@ -13,12 +19,30 @@ import bjc.rgens.parser.elements.LiteralCaseElement; import bjc.utils.data.BooleanToggle; import bjc.utils.funcdata.FunctionalList; +/** + * A template element that can contain rule elements. + * + * @author Ben Culkin. + */ public class LiveTemplateElement extends TemplateElement { + // Pattern for matching elements (any number of characters bracketed by '$@' and '@$') private static final Pattern INSERT_PAT = Pattern.compile("\\$@(.+?)@\\$"); + /** + * The sub-elements of this element. + */ public final List<List<CaseElement>> elements; - public LiveTemplateElement(String val) { + /** + * Create a new template element. + * + * @param val + * The string to parse this element from. + * + * @param errs + * A tree to add errors & information to. + */ + public LiveTemplateElement(String val, ITree<String> errs) { super(true); elements = new ArrayList<>(); @@ -30,10 +54,12 @@ public class LiveTemplateElement extends TemplateElement { mat.appendReplacement(sb, ""); String body = mat.group(1); - FunctionalList<CaseElement> elms = (FunctionalList<CaseElement>)RGrammarParser.parseElementString(body).getLeft(); + List<CaseElement> elms = new ArrayList<>(); + + int weight = RGrammarParser.parseElementString(body, elms, errs); elements.add(Arrays.asList(new LiteralCaseElement(sb.toString()))); - elements.add(elms.getInternal()); + elements.add(elms); sb = new StringBuffer(); } @@ -42,6 +68,7 @@ public class LiveTemplateElement extends TemplateElement { elements.add(Arrays.asList(new LiteralCaseElement(sb.toString()))); } + @Override public void generate(GenerationState state) { BooleanToggle bt = new BooleanToggle(false); diff --git a/src/main/java/bjc/rgens/text/markov/TextGenerator.java b/src/main/java/bjc/rgens/text/markov/TextGenerator.java index 47849f7..9ec7c47 100755 --- a/src/main/java/bjc/rgens/text/markov/TextGenerator.java +++ b/src/main/java/bjc/rgens/text/markov/TextGenerator.java @@ -42,10 +42,10 @@ public class TextGenerator { file = args[1]; } else { - System.out.println("\n" + "Usage: java TextGenerator k M file"); + System.out.println("\nUsage: java TextGenerator k M file"); System.out.println("where k is the markov order, M is the number"); System.out.println("of characters to be printed, and file is the"); - System.out.println("name of the file to print from. M may be left out." + "\n"); + System.out.println("name of the file to print from. M may be left out.\n"); System.exit(1); } |
