diff options
| author | Benjamin J. Culkin <bjculkin@mix.wvu.edu> | 2019-07-21 16:24:47 -0300 |
|---|---|---|
| committer | Benjamin J. Culkin <bjculkin@mix.wvu.edu> | 2019-07-21 16:24:47 -0300 |
| commit | 8c289f05ca36c3def6a4e4ab2414b7469c03339e (patch) | |
| tree | 9e97b6ea73b94ad831258b9bede0136a43530a39 /src/main/java/bjc/rgens/parser/RGrammarParser.java | |
| parent | 89668d36167846e002d0f6dcdc1034b5fee44ce3 (diff) | |
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.
Diffstat (limited to 'src/main/java/bjc/rgens/parser/RGrammarParser.java')
| -rwxr-xr-x | src/main/java/bjc/rgens/parser/RGrammarParser.java | 798 |
1 files changed, 439 insertions, 359 deletions
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<String, TriConsumer<String, RGrammarBuilder, Integer>> pragmas; - - /* Initialize pragmas. */ - static { - pragmas = new HashMap<>(); - - pragmas.put("initial-rule", (body, build, level) -> { - List<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<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 = 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<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); - - IList<CaseElement> 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<String> bits = StringUtils.levelSplit(body, " "); + List<CaseElement> 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<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; } { @@ -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<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. * @@ -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<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; - 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<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); - 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<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; } } @@ -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<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<>(); + + 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<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)) { - @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<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 * @@ -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<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; @@ -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; } } |
