diff options
| author | bjculkin <bjculkin@mix.wvu.edu> | 2017-03-22 17:10:50 -0400 |
|---|---|---|
| committer | bjculkin <bjculkin@mix.wvu.edu> | 2017-03-22 17:10:50 -0400 |
| commit | 9d06ef82f53e156334ba86fbfedbdf02eb93552f (patch) | |
| tree | c4d4d9c13a4c75580aa21974ebc80df26dd8cae3 | |
| parent | 3cd931c1317ebe49cf109673640e0b2d916f884d (diff) | |
Reimplement more old features
6 files changed, 346 insertions, 83 deletions
diff --git a/RGens/src/main/java/bjc/rgens/newparser/CaseElement.java b/RGens/src/main/java/bjc/rgens/newparser/CaseElement.java index 29cf6f7..362100f 100644 --- a/RGens/src/main/java/bjc/rgens/newparser/CaseElement.java +++ b/RGens/src/main/java/bjc/rgens/newparser/CaseElement.java @@ -1,8 +1,6 @@ package bjc.rgens.newparser; -import static bjc.rgens.newparser.CaseElement.ElementType.LITERAL; -import static bjc.rgens.newparser.CaseElement.ElementType.RANGE; -import static bjc.rgens.newparser.CaseElement.ElementType.RULEREF; +import static bjc.rgens.newparser.CaseElement.ElementType.*; /** * A element in a rule case. @@ -34,15 +32,15 @@ public class CaseElement { */ VARDEF, /** - * An element that represents a variable that stores the result of - * generating a rule. + * An element that represents a variable that stores the result + * of generating a rule. */ EXPVARDEF; } - private static final String SPECIAL_CASELEM = "\\{[^}]\\}"; - private static final String REFER_CASELEM = "\\[[^\\]]+\\]"; - private static final String RANGE_CASELM = "\\[\\d+\\.\\.\\d+\\]"; + private static final String SPECIAL_CASELEM = "\\{[^}]\\}"; + private static final String REFER_CASELEM = "\\[[^\\]]+\\]"; + private static final String RANGE_CASELM = "\\[\\d+\\.\\.\\d+\\]"; /** * The type of this element. @@ -52,8 +50,8 @@ public class CaseElement { /** * The literal string value of this element. * - * This means that it is a string whose value should always mean the same - * thing. + * This means that it is a string whose value should always mean the + * same thing. * * <h2>Used For</h2> * <dl> @@ -117,13 +115,13 @@ public class CaseElement { * Create a new case element. * * @param typ - * The type of this element. + * The type of this element. * * @throws IllegalArgumentException - * If the specified type needs parameters. + * If the specified type needs parameters. */ public CaseElement(ElementType typ) { - switch (typ) { + switch(typ) { case LITERAL: case RULEREF: throw new IllegalArgumentException("This type requires a string parameter"); @@ -143,16 +141,17 @@ public class CaseElement { * Create a new case element that has a single string value. * * @param typ - * The type of this element. + * The type of this element. * * @param val - * The string value of this element. + * The string value of this element. * * @throws IllegalArgumentException - * If the specified type doesn't take a single string parameter. + * If the specified type doesn't take a single string + * parameter. */ public CaseElement(ElementType typ, String val) { - switch (typ) { + switch(typ) { case LITERAL: case RULEREF: break; @@ -174,17 +173,18 @@ public class CaseElement { * Create a new case element that has two integer values. * * @param typ - * The type of this element. + * The type of this element. * @param first - * The first integer value for this element. + * The first integer value for this element. * @param second - * The second integer value for this element. + * The second integer value for this element. * * @throws IllegalArgumentException - * If the specified type doesn't take two integer parameters. + * If the specified type doesn't take two integer + * parameters. */ public CaseElement(ElementType typ, int first, int second) { - switch (typ) { + switch(typ) { case LITERAL: case RULEREF: throw new IllegalArgumentException("This type requires a string parameter"); @@ -207,17 +207,18 @@ public class CaseElement { * Create a new case element that has two string values. * * @param typ - * The type of this element. + * The type of this element. * @param name - * The first string value for this element. + * The first string value for this element. * @param def - * The second string value for this element. + * The second string value for this element. * * @throws IllegalArgumentException - * If the specified type doesn't take two string parameters. + * If the specified type doesn't take two string + * parameters. */ public CaseElement(ElementType typ, String name, String def) { - switch (typ) { + switch(typ) { case LITERAL: case RULEREF: throw new IllegalArgumentException("This type requires a string parameter"); @@ -242,10 +243,10 @@ public class CaseElement { * @return The literal string value for this element. * * @throws IllegalStateException - * If this type doesn't have a literal string value. + * If this type doesn't have a literal string value. */ public String getLiteral() { - switch (type) { + switch(type) { case LITERAL: case RULEREF: break; @@ -262,14 +263,15 @@ public class CaseElement { * @return The starting integer value for this element. * * @throws IllegalStateException - * If this type doesn't have a starting integer value. + * If this type doesn't have a starting integer value. */ public int getStart() { - switch (type) { + switch(type) { case RANGE: break; default: - throw new IllegalStateException(String.format("Type '%s' doesn't have a starting integer value", type)); + throw new IllegalStateException( + String.format("Type '%s' doesn't have a starting integer value", type)); } return start; @@ -281,14 +283,15 @@ public class CaseElement { * @return The ending integer value for this element. * * @throws IllegalStateException - * If this type doesn't have a ending integer value. + * If this type doesn't have a ending integer value. */ public int getEnd() { - switch (type) { + switch(type) { case RANGE: break; default: - throw new IllegalStateException(String.format("Type '%s' doesn't have a ending integer value", type)); + throw new IllegalStateException( + String.format("Type '%s' doesn't have a ending integer value", type)); } return end; @@ -300,10 +303,10 @@ public class CaseElement { * @return The variable name of this element. * * @throws IllegalStateException - * If the type doesn't have a variable name. + * If the type doesn't have a variable name. */ public String getName() { - switch (type) { + switch(type) { case VARDEF: case EXPVARDEF: break; @@ -320,10 +323,10 @@ public class CaseElement { * @return The variable definition of this element. * * @throws IllegalStateException - * If the type doesn't have a variable definition. + * If the type doesn't have a variable definition. */ public String getDefn() { - switch (type) { + switch(type) { case VARDEF: case EXPVARDEF: break; @@ -336,7 +339,7 @@ public class CaseElement { @Override public String toString() { - switch (type) { + switch(type) { case LITERAL: case RULEREF: return literalVal; @@ -355,34 +358,54 @@ public class CaseElement { * Create a case element from a string. * * @param csepart - * The string to convert. + * The string to convert. * * @return A case element representing the string. */ public static CaseElement createElement(String csepart) { - if (csepart == null) { + if(csepart == null) { throw new NullPointerException("Case part cannot be null"); } - if (csepart.matches(SPECIAL_CASELEM)) { + if(csepart.matches(SPECIAL_CASELEM)) { /* * Handle special cases. */ String specialBody = csepart.substring(1, csepart.length() - 1); - if (specialBody.matches("\\S+:=\\S+")) { + if(specialBody.matches("\\S+:=\\S+")) { /* * Handle expanding variable definitions. */ - } else if (specialBody.matches("\\S+=\\S+")) { + String[] parts = specialBody.split(":="); + + if(parts.length != 2) { + throw new GrammarException("Expanded variables must be a name and a definition," + + " seperated by :="); + } + + return new CaseElement(EXPVARDEF, parts[0], parts[1]); + } else if(specialBody.matches("\\S+=\\S+")) { /* * Handle regular variable definitions. */ + /* + * Handle expanding variable definitions. + */ + String[] parts = specialBody.split("="); + + if(parts.length != 2) { + throw new GrammarException("Variables must be a name and a definition," + + " seperated by ="); + } + + return new CaseElement(VARDEF, parts[0], parts[1]); } else { - throw new IllegalArgumentException(String.format("Unknown special case part '%s'", specialBody)); + throw new IllegalArgumentException( + String.format("Unknown special case part '%s'", specialBody)); } - } else if (csepart.matches(REFER_CASELEM)) { - if (csepart.matches(RANGE_CASELM)) { + } else if(csepart.matches(REFER_CASELEM)) { + if(csepart.matches(RANGE_CASELM)) { /* * Handle ranges */ @@ -398,7 +421,5 @@ public class CaseElement { } else { return new CaseElement(LITERAL, csepart); } - - throw new IllegalArgumentException(String.format("Unknown case part '%s'")); } }
\ No newline at end of file diff --git a/RGens/src/main/java/bjc/rgens/newparser/RGrammar.java b/RGens/src/main/java/bjc/rgens/newparser/RGrammar.java index c57cd1e..b7f53df 100644 --- a/RGens/src/main/java/bjc/rgens/newparser/RGrammar.java +++ b/RGens/src/main/java/bjc/rgens/newparser/RGrammar.java @@ -1,9 +1,12 @@ package bjc.rgens.newparser; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Represents a randomized grammar. @@ -16,12 +19,17 @@ public class RGrammar { public StringBuilder contents; public Random rnd; - public GenerationState(StringBuilder contents, Random rnd) { + public Map<String, String> vars; + + public GenerationState(StringBuilder contents, Random rnd, Map<String, String> vs) { this.contents = contents; this.rnd = rnd; + this.vars = vs; } } + private static Pattern NAMEVAR_PATTERN = Pattern.compile("\\$(\\w+)"); + private Map<String, Rule> rules; private Map<String, RGrammar> importRules; @@ -100,7 +108,9 @@ public class RGrammar { StringBuilder contents = new StringBuilder(); - generateCase(start, new GenerationState(contents, rnd)); + HashMap<String, String> scope = new HashMap<>(); + + generateCase(start, new GenerationState(contents, rnd, scope)); return contents.toString(); } @@ -147,6 +157,12 @@ public class RGrammar { state.contents.append(val); state.contents.append(" "); break; + case VARDEF: + generateVarDef(elm.getName(), elm.getDefn(), state); + break; + case EXPVARDEF: + generateExpVarDef(elm.getName(), elm.getDefn(), state); + break; default: throw new GrammarException(String.format("Unknown element type '%s'", elm.type)); } @@ -156,12 +172,98 @@ public class RGrammar { } /* + * Generate a expanding variable definition. + */ + private void generateExpVarDef(String name, String defn, GenerationState state) { + GenerationState newState = new GenerationState(new StringBuilder(), state.rnd, state.vars); + + if(rules.containsKey(defn)) { + RuleCase destCase = rules.get(defn).getCase(); + + generateCase(destCase, newState); + } else if(importRules.containsKey(defn)) { + RGrammar destGrammar = importRules.get(defn); + RuleCase destCase = destGrammar.rules.get(defn).getCase(); + + destGrammar.generateCase(destCase, newState); + } else { + throw new GrammarException(String.format("No rule '%s' defined", defn)); + } + + state.vars.put(name, newState.contents.toString()); + } + + /* + * Generate a variable definition. + */ + private void generateVarDef(String name, String defn, GenerationState state) { + state.vars.put(name, defn); + } + + /* * Generate a rule reference. */ private void generateRuleReference(CaseElement elm, GenerationState state) { String refersTo = elm.getLiteral(); - GenerationState newState = new GenerationState(new StringBuilder(), state.rnd); + GenerationState newState = new GenerationState(new StringBuilder(), state.rnd, state.vars); + + if(refersTo.contains("$")) { + /* + * Parse variables + */ + String refBody = refersTo.substring(1, refersTo.length() - 1); + + if(refBody.contains("-")) { + /* + * Handle dependant rule names. + */ + StringBuffer nameBuffer = new StringBuffer(); + + Matcher nameMatcher = NAMEVAR_PATTERN.matcher(refBody); + + while(nameMatcher.find()) { + String var = nameMatcher.group(1); + + if(!state.vars.containsKey(var)) { + throw new GrammarException(String.format("No variable '%s' defined")); + } + + String name = state.vars.get(var); + + if(name.contains(" ")) { + throw new GrammarException( + "Variables substituted into names cannot contain spaces"); + } else if(name.equals("")) { + throw new GrammarException( + "Variables substituted into names cannot be empty"); + } + + nameMatcher.appendReplacement(nameBuffer, name); + } + + nameMatcher.appendTail(nameBuffer); + + refersTo = nameBuffer.toString(); + } else { + /* + * Handle string references. + */ + if(refBody.equals("$")) { + throw new GrammarException("Cannot refer to unnamed variables"); + } + + String key = refBody.substring(1); + + if(!state.vars.containsKey(key)) { + throw new GrammarException(String.format("No variable '%s' defined", key)); + } + + state.contents.append(state.vars.get(key)); + } + + refersTo = refBody; + } if(rules.containsKey(refersTo)) { RuleCase cse = rules.get(refersTo).getCase(state.rnd); @@ -172,7 +274,7 @@ public class RGrammar { newState.contents.append(dst.generate(refersTo, state.rnd)); } else { - throw new GrammarException(String.format("No rule by name '%s' found", refersTo)); + throw new GrammarException(String.format("No rule '%s' defined", refersTo)); } if(refersTo.contains("+")) { @@ -214,7 +316,7 @@ public class RGrammar { throw new GrammarException("The empty string is not a valid rule name"); } else if(!rules.containsKey(initialRule)) { throw new GrammarException( - String.format("No rule named '%s' local to this grammar found.", initialRule)); + String.format("No rule '%s' local to this grammar defined.", initialRule)); } this.initialRule = initialRule; @@ -232,8 +334,8 @@ public class RGrammar { for(String rname : exportRules) { if(!rules.containsKey(rname)) { - throw new GrammarException(String - .format("No rule named '%s' local to this grammar found", initialRule)); + throw new GrammarException(String.format("No rule '%s' local to this grammar defined", + initialRule)); } res.add(rules.get(rname)); diff --git a/RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java b/RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java index 27a9bb3..edd7e80 100644 --- a/RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java +++ b/RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java @@ -154,8 +154,6 @@ public class RGrammarBuilder { throw new NullPointerException("init must not be null"); } else if(init.equals("")) { throw new IllegalArgumentException("The empty string is not a valid rule name"); - } else if(!rules.containsKey(init)) { - throw new IllegalArgumentException(String.format("No local rule named '%s' found", init)); } initialRule = init; @@ -176,8 +174,6 @@ public class RGrammarBuilder { throw new NullPointerException("Export name must not be null"); } else if(export.equals("")) { throw new NullPointerException("The empty string is not a valid rule name"); - } else if(!rules.containsKey(export)) { - throw new IllegalArgumentException(String.format("No local rule named '%s' found", export)); } exportedRules.add(export); @@ -201,8 +197,6 @@ public class RGrammarBuilder { throw new NullPointerException("Rule name must not be null"); } else if(ruleName.equals("")) { throw new IllegalArgumentException("The empty string is not a valid rule name"); - } else if(!rules.containsKey(ruleName)) { - throw new IllegalArgumentException(String.format("No local rule named '%s' found", ruleName)); } CaseElement element = CaseElement.createElement(suffix); @@ -211,4 +205,31 @@ public class RGrammarBuilder { ruleCase.getElements().add(element); } } + + /** + * Prefix a given case element to every case of a specific rule. + * + * @param ruleName + * The rule to prefix. + * + * @param prefix + * The prefix to add. + * + * @throws IllegalArgumentException + * If the rule name is either invalid or not defined by + * this grammar, or if the prefix is invalid. + */ + public void prefixWith(String ruleName, String prefix) { + if(ruleName == null) { + throw new NullPointerException("Rule name must not be null"); + } else if(ruleName.equals("")) { + throw new IllegalArgumentException("The empty string is not a valid rule name"); + } + + CaseElement element = CaseElement.createElement(prefix); + + for(RuleCase ruleCase : rules.get(ruleName).getCases()) { + ruleCase.getElements().add(element); + } + } } diff --git a/RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java b/RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java index dac397e..80e1c2f 100644 --- a/RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java +++ b/RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java @@ -57,6 +57,42 @@ public class RGrammarParser { build.addExport(export); } }); + + pragmas.put("suffix-with", (body, build, level) -> { + String[] parts = body.trim().split(" "); + + if(parts.length != 2) { + throw new GrammarException("Suffix-with pragma takes two arguments," + + " the name of the rule to suffix, then what to suffix it with"); + } else { + String name = parts[0]; + String suffix = parts[1]; + + if(name.equals("")) { + throw new GrammarException("The empty string is not a valid rule name"); + } + + build.suffixWith(name, suffix); + } + }); + + pragmas.put("prefix-with", (body, build, level) -> { + String[] parts = body.trim().split(" "); + + if(parts.length != 2) { + throw new GrammarException("Prefix-with pragma takes two arguments," + + " the name of the rule to prefix, then what to prefix it with"); + } else { + String name = parts[0]; + String prefix = parts[1]; + + if(name.equals("")) { + throw new GrammarException("The empty string is not a valid rule name"); + } + + build.prefixWith(name, prefix); + } + }); } /** @@ -294,6 +330,7 @@ public class RGrammarParser { /* * Handle a where block (a block with local rules). */ + @SuppressWarnings("unused") private void handleWhereBlock(String block, RGrammarBuilder build, int level) throws GrammarException { try(BlockReader whereReader = new BlockReader("", new StringReader(block))) { try { diff --git a/RGens/src/main/java/bjc/rgens/newparser/RGrammarSet.java b/RGens/src/main/java/bjc/rgens/newparser/RGrammarSet.java index e67e356..fbcd8f7 100644 --- a/RGens/src/main/java/bjc/rgens/newparser/RGrammarSet.java +++ b/RGens/src/main/java/bjc/rgens/newparser/RGrammarSet.java @@ -1,7 +1,13 @@ package bjc.rgens.newparser; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; +import java.util.Scanner; import java.util.Set; /** @@ -99,7 +105,7 @@ public class RGrammarSet { } else if(!exportedRules.containsKey(exportName)) { throw new IllegalArgumentException(String.format("No export with name '%s' found", exportName)); } - + return exportedRules.get(exportName); } @@ -120,4 +126,84 @@ public class RGrammarSet { public Set<String> getExportedRules() { return exportedRules.keySet(); } + + /** + * Load a grammar set from a configuration file. + * + * @param cfgFile + * The configuration file to load from. + * + * @return The grammar set created by the configuration file. + * + * @throws IOException + * If something goes wrong during configuration loading. + */ + public static RGrammarSet fromConfigFile(Path cfgFile) throws IOException { + RGrammarSet set = new RGrammarSet(); + + RGrammarParser parser = new RGrammarParser(); + + Path cfgParent = cfgFile.getParent(); + + try(Scanner scn = new Scanner(cfgFile)) { + /* + * Execute lines from the configuration file. + */ + while(scn.hasNextLine()) { + String ln = scn.nextLine().trim(); + + /* + * Ignore blank/comment lines. + */ + if(ln.equals("")) continue; + if(ln.startsWith("#")) continue; + + /* + * Handle mixed whitespace + */ + ln = ln.replaceAll("\\s+", " "); + + int nameIdx = ln.indexOf(" "); + + if(nameIdx == -1) { + throw new GrammarException("Must specify a name for a loaded grammar"); + } + + /* + * Name and path of grammar. + */ + String name = ln.substring(0, nameIdx); + Path path = Paths.get(ln.substring(nameIdx).trim()); + + /* + * Convert from configuration relative path. + */ + Path convPath = cfgParent.resolve(path); + + File fle = convPath.toFile(); + + if(fle.isDirectory()) { + /* + * TODO implement subset grammars + */ + throw new GrammarException("Sub-grammar sets aren't implemented yet"); + } else if(fle.getName().endsWith(".gram")) { + /* + * Load grammar files. + */ + try { + RGrammar gram = parser.readGrammar(new FileInputStream(fle)); + set.addGrammar(name, gram); + } catch(GrammarException gex) { + throw new GrammarException( + String.format("Error loading file '%s'", path), gex); + } + } else { + throw new GrammarException(String.format("Unrecognized file '%s'")); + } + } + } + + return set; + } } diff --git a/RGens/src/main/java/bjc/rgens/newparser/RGrammarTest.java b/RGens/src/main/java/bjc/rgens/newparser/RGrammarTest.java index 375e35f..14b29e7 100644 --- a/RGens/src/main/java/bjc/rgens/newparser/RGrammarTest.java +++ b/RGens/src/main/java/bjc/rgens/newparser/RGrammarTest.java @@ -1,6 +1,9 @@ package bjc.rgens.newparser; -import java.io.InputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; /** * Test for new grammar syntax. @@ -16,27 +19,20 @@ public class RGrammarTest { * Unused CLI args. */ public static void main(String[] args) { - InputStream stream = RGrammarTest.class.getResourceAsStream("/sample-grammars/college.gram"); + URL rsc = RGrammarTest.class.getResource("/server-config-sample.cfg"); - RGrammarSet grammarSet = new RGrammarSet(); + try { + RGrammarSet gramSet = RGrammarSet.fromConfigFile(Paths.get(rsc.toURI())); - RGrammarParser parse = new RGrammarParser(); + for(String exportName : gramSet.getExportedRules()) { + RGrammar grammar = gramSet.getExportSource(exportName); - RGrammar grammar = parse.readGrammar(stream); - - grammarSet.addGrammar("rpg", grammar); - - for(int i = 0; i < 10; i++) { - System.out.println(grammar.generate(null, null)); + grammar.generate(exportName); + } + } catch(IOException ioex) { + ioex.printStackTrace(); + } catch(URISyntaxException urisex) { + urisex.printStackTrace(); } - - System.out.println(); - System.out.println(); - - System.out.println("Formatted grammar: "); - - String formattedGrammar = RGrammarFormatter.formatGrammar(grammar); - - System.out.print(formattedGrammar); } } |
