From 89668d36167846e002d0f6dcdc1034b5fee44ce3 Mon Sep 17 00:00:00 2001 From: "Benjamin J. Culkin" Date: Thu, 11 Oct 2018 22:11:20 -0300 Subject: Update --- src/main/java/bjc/rgens/parser/ConfigLoader.java | 5 +---- src/main/java/bjc/rgens/parser/ConfigSet.java | 15 +++++++++++++-- src/main/java/bjc/rgens/parser/RGrammarSet.java | 14 ++++++++------ .../bjc/rgens/parser/elements/vars/VariableElement.java | 1 + 4 files changed, 23 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/main/java/bjc/rgens/parser/ConfigLoader.java b/src/main/java/bjc/rgens/parser/ConfigLoader.java index 97ca257..269a45b 100644 --- a/src/main/java/bjc/rgens/parser/ConfigLoader.java +++ b/src/main/java/bjc/rgens/parser/ConfigLoader.java @@ -31,10 +31,7 @@ public class ConfigLoader { ConfigSet 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"; + RGrammarSet set = cfgSet.createGSet("default"); long startCFGTime = System.nanoTime(); 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 grammars; + public final Map grammars; public final Map templates; - public final Map subconfigs; + public final Map 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/RGrammarSet.java b/src/main/java/bjc/rgens/parser/RGrammarSet.java index 30f08a4..56ed1c8 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 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/elements/vars/VariableElement.java b/src/main/java/bjc/rgens/parser/elements/vars/VariableElement.java index 2193b3c..77eb151 100644 --- a/src/main/java/bjc/rgens/parser/elements/vars/VariableElement.java +++ b/src/main/java/bjc/rgens/parser/elements/vars/VariableElement.java @@ -38,6 +38,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("\\(|\\)", ""); -- cgit v1.2.3 From 8c289f05ca36c3def6a4e4ab2414b7469c03339e Mon Sep 17 00:00:00 2001 From: "Benjamin J. Culkin" Date: Sun, 21 Jul 2019 16:24:47 -0300 Subject: Refactor front-end error-handling This refactors the front-end to use a tree for capturing errors, instead of throwing exceptions. This has the benefit that you will receive notifications about all of the error messages you have, instead of only the first. I'm a bit fuzzy on the details, since it's been a while since I wrote these changes. --- src/main/java/bjc/rgens/parser/ConfigLoader.java | 333 ++++++--- src/main/java/bjc/rgens/parser/FlatRuleCase.java | 8 +- src/main/java/bjc/rgens/parser/LoadOptions.java | 50 ++ src/main/java/bjc/rgens/parser/NormalRuleCase.java | 6 +- src/main/java/bjc/rgens/parser/RGrammar.java | 17 +- .../java/bjc/rgens/parser/RGrammarBuilder.java | 232 ++++-- src/main/java/bjc/rgens/parser/RGrammarParser.java | 798 ++++++++++++--------- src/main/java/bjc/rgens/parser/RGrammarTest.java | 25 +- src/main/java/bjc/rgens/parser/RGrammars.java | 7 +- src/main/java/bjc/rgens/parser/RegexRuleCase.java | 6 +- src/main/java/bjc/rgens/parser/Rule.java | 18 +- src/main/java/bjc/rgens/parser/RuleCase.java | 9 +- .../parser/elements/InlineRuleCaseElement.java | 17 +- .../rgens/parser/templates/GrammarTemplate.java | 74 +- .../parser/templates/LiteralTemplateElement.java | 19 +- .../parser/templates/LiveTemplateElement.java | 30 +- 16 files changed, 1081 insertions(+), 568 deletions(-) create mode 100644 src/main/java/bjc/rgens/parser/LoadOptions.java (limited to 'src') diff --git a/src/main/java/bjc/rgens/parser/ConfigLoader.java b/src/main/java/bjc/rgens/parser/ConfigLoader.java index 269a45b..c282f8a 100644 --- a/src/main/java/bjc/rgens/parser/ConfigLoader.java +++ b/src/main/java/bjc/rgens/parser/ConfigLoader.java @@ -1,19 +1,33 @@ package bjc.rgens.parser; +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.BufferedReader; +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 static bjc.rgens.parser.RGrammarLogging.*; +/** + * Class that performs loading of grammar sets from config files. + * + * @author Ben Culkin + */ public class ConfigLoader { /** * Load a grammar set from a configuration file. @@ -27,57 +41,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 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 errs) throws IOException { + lopts.cfgFile = cfgFile; + lopts.cfgSet = new ConfigSet(); /* The grammar set we're parsing into. */ - RGrammarSet set = cfgSet.createGSet("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 = StringUtils.levelSplit(ln, " ").toArray(new String[0]); + ITree 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()); - - error(gex, "Line %s of config set %s (%s)", lno, cfgFile, gex.getRootMessage()); - gex.printStackTrace(); + String fmt = String.format("ERROR: Unknown config line type %s", type); - System.out.println(); - System.out.println(); + header.addChild(fmt); + } } + + errs.addChild(header); } } @@ -85,57 +118,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); - return cfgSet; + errs.addChild(fmt); + } + + 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 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 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]); @@ -144,63 +192,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 dirItr = new QueuedIterator<>(dirPath.toFile().listFiles()); + + ITree header = new Tree<>(String.format("INFO: Bulk-loading files from directory '%s'", lopts.parent)); - doLoadGrammar(rdr, null, cfgSet, set, dirPath, normFle); - } else if(fleName.endsWith(".gtpl")) { - BufferedReader rdr = Files.newBufferedReader(normFle); + while (dirItr.hasNext()) { + File curFile = dirItr.next(); - doLoadTemplate(rdr, null, cfgSet, set, dirPath); - } else if(fleName.endsWith(".class")) { - // Ignore these + String fName = curFile.toString(); + + ITree 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 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; } @@ -211,11 +278,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 @@ -227,9 +297,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 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]); @@ -238,39 +312,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.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 template file. */ + Reader rdr = new FileReader(convPath.toFile()); + + String fmt = String.format("INFO: Loading template '%s' from '%s'", name, convPath); + ITree 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 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; } @@ -281,22 +364,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 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]); @@ -305,23 +395,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 { + /* Load grammar file. */ long startFileTime = System.nanoTime(); - 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()); + Reader rdr = new FileReader(convPath.toFile()); + + ITree 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/FlatRuleCase.java b/src/main/java/bjc/rgens/parser/FlatRuleCase.java index 4bbd1cc..ac6f554 100644 --- a/src/main/java/bjc/rgens/parser/FlatRuleCase.java +++ b/src/main/java/bjc/rgens/parser/FlatRuleCase.java @@ -1,11 +1,11 @@ package bjc.rgens.parser; -import bjc.utils.funcdata.IList; - import bjc.rgens.parser.elements.CaseElement; +import java.util.List; + public class FlatRuleCase extends RuleCase { - public FlatRuleCase(IList elms) { + public FlatRuleCase(List elms) { super(elms); } @@ -16,7 +16,7 @@ public class FlatRuleCase extends RuleCase { } } - public FlatRuleCase withElements(IList elms) { + public FlatRuleCase withElements(List 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 7fee169..8fca86d 100644 --- a/src/main/java/bjc/rgens/parser/NormalRuleCase.java +++ b/src/main/java/bjc/rgens/parser/NormalRuleCase.java @@ -1,11 +1,11 @@ package bjc.rgens.parser; -import bjc.utils.funcdata.IList; +import java.util.List; import bjc.rgens.parser.elements.CaseElement; public class NormalRuleCase extends RuleCase { - public NormalRuleCase(IList elms) { + public NormalRuleCase(List elms) { super(elms); } @@ -20,7 +20,7 @@ public class NormalRuleCase extends RuleCase { } } - public NormalRuleCase withElements(IList elms) { + public NormalRuleCase withElements(List 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 20ce320..018f2f0 100755 --- a/src/main/java/bjc/rgens/parser/RGrammar.java +++ b/src/main/java/bjc/rgens/parser/RGrammar.java @@ -1,7 +1,9 @@ package bjc.rgens.parser; 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; @@ -291,18 +293,27 @@ public class RGrammar { * initial rule. */ public void setInitialRule(String initRule) { + setInitialRule(initRule, new Tree<>()); + } + + public void setInitialRule(String initRule, ITree 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); + + errs.addChild(msg); - throw new GrammarException(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 535d818..358c6a1 100755 --- a/src/main/java/bjc/rgens/parser/RGrammarBuilder.java +++ b/src/main/java/bjc/rgens/parser/RGrammarBuilder.java @@ -4,9 +4,13 @@ import bjc.rgens.parser.elements.CaseElement; import bjc.rgens.parser.elements.VariableDefCaseElement; 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; @@ -59,10 +63,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 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); @@ -116,10 +129,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 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; @@ -135,10 +155,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 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); @@ -157,8 +185,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 suffixes) { - affixWith(ruleName, suffixes, AffixType.SUFFIX); + public void suffixWith(String ruleName, List suffixes) { + affixWith(ruleName, suffixes, AffixType.SUFFIX, new Tree<>()); } @@ -175,8 +203,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 prefixes) { - affixWith(ruleName, prefixes, AffixType.PREFIX); + public void prefixWith(String ruleName, List prefixes) { + affixWith(ruleName, prefixes, AffixType.PREFIX, new Tree<>()); } /** @@ -192,8 +220,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 prefixes) { - affixWith(ruleName, prefixes, AffixType.CIRCUMFIX); + public void circumfixWith(String ruleName, List prefixes) { + affixWith(ruleName, prefixes, AffixType.CIRCUMFIX, new Tree<>()); } public static enum AffixType { @@ -210,32 +238,39 @@ public class RGrammarBuilder { } } - public void affixWith(String ruleName, IList affixes, AffixType type) { + public void affixWith(String ruleName, List affixes, AffixType type) { + affixWith(ruleName, affixes, type, new Tree<>()); + } + + public void affixWith(String ruleName, List affixes, AffixType type, ITree 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 elements = new HashSet<>(affixes.getSize()); - for(CaseElement affix : affixes) { - elements.add(affix); + return; } + + Set elements = new HashSet<>(affixes); List> affixLists = powerList(elements); FunctionalList> newCases = new FunctionalList<>(); - IList> caseList = rules.get(ruleName).getCases(); + for (IPair ruleCase : caseList) { RuleCase cas = ruleCase.getRight(); for(List affixList : affixLists) { - FunctionalList newCase = new FunctionalList<>(); + List newCase = new ArrayList<>(); if(type.isPrefix()) { for(CaseElement element : affixList) { @@ -265,12 +300,24 @@ public class RGrammarBuilder { } public void despaceRule(String ruleName) { + despaceRule(ruleName, new Tree<>(), false); + } + + public void despaceRule(String ruleName, ITree 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> caseList = rules.get(ruleName).getCases(); @@ -281,42 +328,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 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 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 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); @@ -326,12 +413,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 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); @@ -352,37 +450,67 @@ public class RGrammarBuilder { } public void rejectRule(String rule, String reject) { + rejectRule(rule, reject, new Tree<>()); + } + + public void rejectRule(String rule, String reject, ITree 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 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 a95cefc..dc4d82d 100755 --- a/src/main/java/bjc/rgens/parser/RGrammarParser.java +++ b/src/main/java/bjc/rgens/parser/RGrammarParser.java @@ -3,19 +3,27 @@ package bjc.rgens.parser; 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; @@ -26,6 +34,7 @@ import java.util.Set; import static bjc.rgens.parser.RGrammarLogging.*; import static bjc.rgens.parser.RGrammarBuilder.AffixType; + /** * Reads {@link RGrammar} from a input stream. * @@ -53,244 +62,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> pragmas; - - /* Initialize pragmas. */ - static { - pragmas = new HashMap<>(); - - pragmas.put("initial-rule", (body, build, level) -> { - List bits = StringUtils.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 bits = StringUtils.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 bits = StringUtils.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 exports = StringUtils.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 parts = StringUtils.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 parts = StringUtils.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 parts = StringUtils.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 parts = StringUtils.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 bits = StringUtils.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 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 bits = StringUtils.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 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 bits, RGrammarBuilder build, int level, boolean isRule, ITree 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); - - IList elms = parseElementString(body.substring(idx + 1)).getLeft(); - - build.affixWith(rName, elms, afxType); - } + String[] bitArr = bits.toArray(new String[0]); - private static void doAutoVar(String body, RGrammarBuilder build, int level, boolean isRule) { - List bits = StringUtils.levelSplit(body, " "); + List elmList = new ArrayList<>(); - if (bits.size() < 1) { - String msg = "Must specify name of variable and definition to autovivify"; - throw new GrammarException(msg); - } + parseElementString(bitArr, elmList, errs); + CaseElement elm = elmList.get(0); - String[] bitArr = bits.toArray(new String[0]); + if (elmList.size() > 1) { + String msg = String.format("WARN: Ignoring %d additional elements for autovivify: %s", elmList.size(), elmList.subList(1, elmList.size())); - IList 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; } { @@ -313,64 +136,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 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 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 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. * @@ -381,137 +220,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 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; - if(DEBUG) - System.err.printf("Handled pragma block (%s)\n", pragma); + try (BlockReader pragmaReader = new SimpleBlockReader(dlm, new StringReader(block))) { + for(Block pragma : pragmaReader) { + pragma.lineOffset = lineOffset; - String pragmaContents = pragma.contents; + if(lopts.doTrace) { + System.err.printf("TRACE: Handled pragma block (%s)\n", pragma); + } - int pragmaSep = pragmaContents.indexOf(' '); + String pragmaContents = pragma.contents; - if (pragmaSep == -1) { - String msg = "A pragma invocation must consist of the word pragma, followed by a space, then the body of the pragma"; + int pragmaSep = pragmaContents.indexOf(' '); - throw new GrammarException(msg); - } + 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"; - String pragmaLeader = pragmaContents.substring(0, pragmaSep); - String pragmaBody = pragmaContents.substring(pragmaSep + 1); + errs.addChild(msg); + return; + } - if (!pragmaLeader.equalsIgnoreCase("pragma")) { - String msg = String.format("Illegal line leader in pragma block: '%s'", pragmaLeader); + String pragmaLeader = pragmaContents.substring(0, pragmaSep); + String pragmaBody = pragmaContents.substring(pragmaSep + 1); - throw new GrammarException(msg); - } + if (!pragmaLeader.equalsIgnoreCase("pragma")) { + String msg = String.format("ERROR: Illegal line leader in pragma block: '%s'", pragmaLeader); - 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 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 bits = StringUtils.levelSplit(pragmaBody, " "); + + String fmt = String.format("INFO: Pragma '%s'", pragmaName); + ITree kid = new Tree<>(fmt); - pragmas.get(pragmaName).accept(pragmaBody, build, level); - } catch (GrammarException gex) { - String msg = String.format("Error in pragma '%s'", pragmaName); + switch (pragmaName) { + case "initial-rule": + { + if (bits.size() != 1) { + kid.addChild("ERROR: Must specify initial rule"); - throw new GrammarException(msg, gex); + break; + } + + 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."); - throw new GrammarException(msg); + 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."); + + 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 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 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 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; } } @@ -519,70 +589,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); + + Rule rul = build.getOrCreateRule(ruleName, errs); - handleRuleCase(ruleBody, build, rul, lineOffset); + 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, Integer> caseParts = parseElementString(cse); + private static void handleRuleCase(String cse, RGrammarBuilder build, Rule rul, int lineOffset, ITree errs) { + List elms = new ArrayList<>(); + + int weights = parseElementString(cse, elms, errs); - rul.addCase(new NormalRuleCase(caseParts.getLeft()), caseParts.getRight()); + 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 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)) { - @SuppressWarnings("unused") - 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, Integer> parseElementString(String cses) { + public static int parseElementString(String cses, List elms) { + return parseElementString(cses, elms, new Tree<>()); + } + + public static int parseElementString(String cses, List elms, ITree errs) { /* * @NOTE * @@ -595,12 +673,14 @@ public class RGrammarParser { * return parseElementString(cseList.toArray(new String[0])); */ - return parseElementString(cses.split(" ")); + return parseElementString(cses.split(" "), elms, errs); } - public static Pair, Integer> parseElementString(String... cses) { - IList caseParts = new FunctionalList<>(); + public static int parseElementString(String[] cses, List elms) { + return parseElementString(cses, elms, new Tree<>()); + } + public static int parseElementString(String[] cses, List caseParts, ITree errs) { int weight = 1; int repCount = 1; @@ -650,7 +730,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 */ @@ -703,6 +783,6 @@ public class RGrammarParser { } } - return new Pair<>(caseParts, weight); + return weight; } } diff --git a/src/main/java/bjc/rgens/parser/RGrammarTest.java b/src/main/java/bjc/rgens/parser/RGrammarTest.java index 788823a..36c87de 100755 --- a/src/main/java/bjc/rgens/parser/RGrammarTest.java +++ b/src/main/java/bjc/rgens/parser/RGrammarTest.java @@ -2,11 +2,17 @@ package bjc.rgens.parser; import bjc.rgens.parser.templates.GrammarTemplate; +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 static bjc.rgens.parser.RGrammarLogging.*; @@ -27,9 +33,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 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 cc31bad..51d9fe9 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 3c57489..1712d57 100755 --- a/src/main/java/bjc/rgens/parser/RegexRuleCase.java +++ b/src/main/java/bjc/rgens/parser/RegexRuleCase.java @@ -1,8 +1,8 @@ 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; @@ -12,7 +12,7 @@ import java.util.regex.PatternSyntaxException; * Actually implement this */ public class RegexRuleCase extends RuleCase { - public RegexRuleCase(IList elements) { + public RegexRuleCase(List elements) { super(elements); } @@ -21,7 +21,7 @@ public class RegexRuleCase extends RuleCase { } - public RegexRuleCase withElements(IList elements) { + public RegexRuleCase withElements(List 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 1a74352..377da9e 100755 --- a/src/main/java/bjc/rgens/parser/Rule.java +++ b/src/main/java/bjc/rgens/parser/Rule.java @@ -1,6 +1,8 @@ package bjc.rgens.parser; 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; @@ -121,20 +123,32 @@ public class Rule { } public void addRejection(String reject) { + addRejection(reject, new Tree<>()); + } + + public void addRejection(String reject, ITree 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 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 elementList; + public List 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 elements) { + protected RuleCase(List elements) { elementList = elements; serial = nextSerial; @@ -41,7 +42,7 @@ public abstract class RuleCase { public abstract void generate(GenerationState state); - public abstract RuleCase withElements(IList elements); + public abstract RuleCase withElements(List 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 917dd33..269cc4f 100644 --- a/src/main/java/bjc/rgens/parser/elements/InlineRuleCaseElement.java +++ b/src/main/java/bjc/rgens/parser/elements/InlineRuleCaseElement.java @@ -3,16 +3,23 @@ package bjc.rgens.parser.elements; import bjc.rgens.parser.GenerationState; import bjc.rgens.parser.RGrammarParser; -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.funcutils.StringUtils; import bjc.utils.gen.WeightedRandom; +import java.util.ArrayList; +import java.util.List; + public class InlineRuleCaseElement extends CaseElement { public final WeightedRandom elements; public InlineRuleCaseElement(String... parts) { + this(new Tree<>(), parts); + } + + public InlineRuleCaseElement(ITree errs, String... parts) { super(true); this.elements = new WeightedRandom<>(); @@ -26,10 +33,10 @@ public class InlineRuleCaseElement extends CaseElement { partArr = new String[] {part}; } - IPair, Integer> par = RGrammarParser.parseElementString(partArr); - int prob = par.getRight(); + List 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/templates/GrammarTemplate.java b/src/main/java/bjc/rgens/parser/templates/GrammarTemplate.java index 8a99188..64db166 100644 --- a/src/main/java/bjc/rgens/parser/templates/GrammarTemplate.java +++ b/src/main/java/bjc/rgens/parser/templates/GrammarTemplate.java @@ -2,25 +2,58 @@ 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; import java.util.Scanner; +/** + * 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 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 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,7 +63,18 @@ 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 errs) { List elements = new ArrayList<>(); GrammarTemplate template = new GrammarTemplate(elements); @@ -42,34 +86,46 @@ public class GrammarTemplate { String ln = scn.nextLine(); lno += 1; + ITree 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); + } + } return template; } - private static void handleLine(List elements, GrammarTemplate template, String ln) { + private static void handleLine(List elements, GrammarTemplate template, String ln, ITree 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 elements, GrammarTemplate template, String ln) { - + private static void handlePragma(List elements, GrammarTemplate template, String ln, ITree 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 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 8dbde05..0aa3bd0 100644 --- a/src/main/java/bjc/rgens/parser/templates/LiveTemplateElement.java +++ b/src/main/java/bjc/rgens/parser/templates/LiveTemplateElement.java @@ -1,7 +1,10 @@ package bjc.rgens.parser.templates; 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; @@ -14,12 +17,30 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * 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> 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 errs) { super(true); elements = new ArrayList<>(); @@ -31,10 +52,12 @@ public class LiveTemplateElement extends TemplateElement { mat.appendReplacement(sb, ""); String body = mat.group(1); - FunctionalList elms = (FunctionalList)RGrammarParser.parseElementString(body).getLeft(); + List 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(); } @@ -43,6 +66,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); -- cgit v1.2.3