summaryrefslogtreecommitdiff
path: root/RGens/src
diff options
context:
space:
mode:
authorbjculkin <bjculkin@mix.wvu.edu>2017-03-21 14:04:17 -0400
committerbjculkin <bjculkin@mix.wvu.edu>2017-03-21 14:04:17 -0400
commite279644fc59f46916c20b0b4f941fd37b4f07675 (patch)
tree7782b229f6482039ad0739079807294c14928085 /RGens/src
parentba7e0a2c1efe4203f766f3192b8852c3bb7d3369 (diff)
Finish basic implementation
This finishes a basic implementation. It now handles literal strings and rule references, allowing it to handle basic grammars.
Diffstat (limited to 'RGens/src')
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/CaseElement.java102
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RGrammar.java150
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java143
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java58
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RGrammarTest.java27
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/Rule.java50
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RuleCase.java95
7 files changed, 596 insertions, 29 deletions
diff --git a/RGens/src/main/java/bjc/rgens/newparser/CaseElement.java b/RGens/src/main/java/bjc/rgens/newparser/CaseElement.java
new file mode 100644
index 0000000..fe088b8
--- /dev/null
+++ b/RGens/src/main/java/bjc/rgens/newparser/CaseElement.java
@@ -0,0 +1,102 @@
+package bjc.rgens.newparser;
+
+/**
+ * A element in a rule case.
+ *
+ * @author EVE
+ */
+public class CaseElement {
+ /**
+ * The possible types of an element.
+ *
+ * @author EVE
+ *
+ */
+ public static enum ElementType {
+ /**
+ * A element that represents a literal string.
+ */
+ LITERAL,
+ /**
+ * A element that represents a rule reference.
+ */
+ RULEREF;
+ }
+
+ /**
+ * The type of this element.
+ */
+ public final CaseElement.ElementType type;
+
+ /**
+ * The literal string value of this element.
+ *
+ * This means that it is a string whose value should always mean the
+ * same thing.
+ *
+ * <h2>Used For</h2>
+ * <dl>
+ * <dt>LITERAL</dt>
+ * <dd>The string this element represents</dd>
+ * <dt>RULEREF</dt>
+ * <dd>The name of the rule this element references</dd>
+ * </dl>
+ */
+ private String literalVal;
+
+ /**
+ * Create a new case element.
+ *
+ * @param typ
+ * The type of this element.
+ *
+ * @throws IllegalArgumentException
+ * If the specified type needs parameters.
+ */
+ public CaseElement(CaseElement.ElementType typ) {
+ switch(typ) {
+ case LITERAL:
+ case RULEREF:
+ throw new IllegalArgumentException("This type requires a string parameter.");
+ default:
+ break;
+ }
+ type = typ;
+ }
+
+ /**
+ * Create a new case element that has a single string value.
+ *
+ * @param typ
+ * The type of this element.
+ *
+ * @param val
+ * The string value of this element.
+ *
+ * @throws IllegalArgumentException
+ * If the specified type doesn't take a single string
+ * parameter.
+ */
+ public CaseElement(CaseElement.ElementType typ, String val) {
+ switch(typ) {
+ case LITERAL:
+ case RULEREF:
+ break;
+ default:
+ throw new IllegalArgumentException("This type doesn't have a string parameter.");
+ }
+
+ type = typ;
+
+ literalVal = val;
+ }
+
+ /**
+ * Get the literal string value for this element.
+ *
+ * @return The literal string value for this element.
+ */
+ public String getLiteral() {
+ return literalVal;
+ }
+} \ 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 64385e9..4adcbe8 100644
--- a/RGens/src/main/java/bjc/rgens/newparser/RGrammar.java
+++ b/RGens/src/main/java/bjc/rgens/newparser/RGrammar.java
@@ -1,5 +1,8 @@
package bjc.rgens.newparser;
+import java.util.List;
+import java.util.Map;
+
/**
* Represents a randomized grammar.
*
@@ -7,5 +10,152 @@ package bjc.rgens.newparser;
*
*/
public class RGrammar {
+ private static class GenerationState {
+ public StringBuilder contents;
+
+ public GenerationState(StringBuilder contents) {
+ this.contents = contents;
+ }
+ }
+
+ private Map<String, Rule> rules;
+
+ private Map<String, Rule> importRules;
+
+ private String initialRule;
+
+ /**
+ * Create a new randomized grammar using the specified set of rules.
+ *
+ * @param rules
+ * The rules to use.
+ */
+ public RGrammar(Map<String, Rule> rules) {
+ this.rules = rules;
+ }
+
+ /**
+ * Sets the imported rules to use.
+ *
+ * Imported rules are checked for rule definitions after local
+ * definitions are checked.
+ *
+ * @param importedRules
+ * The set of imported rules to use.
+ */
+ public void setImportedRules(Map<String, Rule> importedRules) {
+ importRules = importedRules;
+ }
+
+ /**
+ * Generate a string from this grammar, starting from the specified
+ * rule.
+ *
+ * @param startRule
+ * The rule to start generating at, or null to use the
+ * initial rule for this grammar.
+ *
+ * @return A possible string from the grammar.
+ */
+ public String generate(String startRule) {
+ String fromRule = startRule;
+
+ if(startRule == null) {
+ if(initialRule == null) {
+ throw new GrammarException(
+ "Must specify a start rule for grammars with no initial rule");
+ } else {
+ fromRule = initialRule;
+ }
+ } else {
+ if(startRule.equals("")) {
+ throw new GrammarException("The empty string is not a valid rule name");
+ }
+ }
+
+ RuleCase start = rules.get(fromRule).getCase();
+
+ StringBuilder contents = new StringBuilder();
+
+ generateCase(start, new GenerationState(contents));
+
+ return contents.toString();
+ }
+
+ /*
+ * Generate a rule case.
+ */
+ private void generateCase(RuleCase start, GenerationState state) {
+ try {
+ switch(start.type) {
+ case NORMAL:
+ for(CaseElement elm : start.getElements()) {
+ generateElement(elm, state);
+ }
+ break;
+ default:
+ throw new GrammarException(String.format("Unknown case type '%s'", start.type));
+ }
+ } catch(GrammarException gex) {
+ throw new GrammarException(String.format("Error in generating case (%s)", start), gex);
+ }
+ }
+
+ /*
+ * Generate a case element.
+ */
+ private void generateElement(CaseElement elm, GenerationState state) {
+ try {
+ switch(elm.type) {
+ case LITERAL:
+ state.contents.append(elm.getLiteral() + " ");
+ break;
+ case RULEREF:
+ if(rules.containsKey(elm.getLiteral())) {
+ RuleCase cse = rules.get(elm.getLiteral()).getCase();
+
+ generateCase(cse, state);
+ } else if(importRules.containsKey(elm.getLiteral())) {
+ RuleCase cse = importRules.get(elm.getLiteral()).getCase();
+
+ generateCase(cse, state);
+ } else {
+ throw new GrammarException(
+ String.format("No rule by name '%s' found", elm.getLiteral()));
+ }
+ break;
+ default:
+ throw new GrammarException(String.format("Unknown element type '%s'", elm.type));
+ }
+ } catch(GrammarException gex) {
+ throw new GrammarException(String.format("Error in generating case element (%s)", elm), gex);
+ }
+ }
+
+ /**
+ * Get the initial rule of this grammar.
+ *
+ * @return The initial rule of this grammar.
+ */
+ public String getInitialRule() {
+ return initialRule;
+ }
+
+ /**
+ * Set the initial rule of this grammar.
+ *
+ * @param initialRule
+ * The initial rule of this grammar, or null to say there
+ * is no initial rule.
+ */
+ public void setInitialRule(String initialRule) {
+ if(initialRule.equals("")) {
+ 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));
+ }
+ this.initialRule = initialRule;
+ }
}
diff --git a/RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java b/RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java
index fdf2433..7549847 100644
--- a/RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java
+++ b/RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java
@@ -1,5 +1,14 @@
package bjc.rgens.newparser;
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static bjc.rgens.newparser.CaseElement.ElementType.*;
+import static bjc.rgens.newparser.RuleCase.CaseType.*;
+
/**
* Construct randomized grammars piece by piece.
*
@@ -7,45 +16,139 @@ package bjc.rgens.newparser;
*
*/
public class RGrammarBuilder {
+ private Map<String, Rule> rules;
+
+ private Rule currRule;
+
+ private IList<CaseElement> currentCase;
+
+ private String initialRule;
+
/**
- * Sets the rule currently being built.
+ * Create a new randomized grammar builder.
+ */
+ public RGrammarBuilder() {
+ rules = new HashMap<>();
+
+ currentCase = new FunctionalList<>();
+ }
+
+ /**
+ * Starts a rule with the provided name.
+ *
+ * If the rule already exists, it will be opened for adding additional
+ * cases instead.
+ *
+ * @param rName
+ * The name of the rule currently being built.
*
- * @param rName The name of the rule currently being built.
+ * @throws IllegalArgumentException
+ * If the rule name is the empty string.
*/
- public void setCurrentRule(String rName) {
- /*
- * TODO implement.
- */
+ public void startRule(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");
+ }
+
+ currRule = new Rule(rName);
}
-
+
/**
* Convert this builder into a grammar.
*
* @return The grammar built by this builder
*/
public RGrammar toRGrammar() {
- /*
- * TODO implement.
- */
- return null;
+ RGrammar grammar = new RGrammar(rules);
+
+ grammar.setInitialRule(initialRule);
+
+ return grammar;
}
+ /**
+ * Adds a case part to this rule.
+ *
+ * <h2>Case part formatting</h2>
+ * <dl>
+ * <dt>Rule Reference</dt>
+ * <dd>Rule references are marked by being surrounded with square
+ * brackets (the square brackets are part of the rule's name)</dd>
+ * <dt>Literal Strings</dt>
+ * <dd>Literal strings are the default case part type.</dd>
+ * </dl>
+ *
+ * @param csepart
+ */
public void addCasePart(String csepart) {
- // TODO Auto-generated method stub
+ if(csepart.matches("\\[[^\\]]+\\]")) {
+ currentCase.add(new CaseElement(RULEREF, csepart));
+ } else {
+ currentCase.add(new CaseElement(LITERAL, csepart));
+ }
}
- public void endRule() {
- // TODO Auto-generated method stub
-
+ /**
+ * Finalizes editing a rule.
+ *
+ * Saves the rule to the rule map.
+ *
+ * @throws IllegalStateException
+ * Must be invoked while a rule is being edited.
+ */
+ public void finishRule() {
+ if(currRule == null) {
+ throw new IllegalStateException("Must start a rule before finishing one");
+ }
+
+ rules.put(currRule.ruleName, currRule);
}
+ /**
+ * Finishes the current case being edited.
+ *
+ * @throws IllegalStateException
+ * Must be invoked while a rule is being edited.
+ */
public void finishCase() {
- // TODO Auto-generated method stub
-
+ if(currRule == null) {
+ throw new IllegalStateException("Must start a rule before finishing a case");
+ }
+
+ currRule.addCase(new RuleCase(NORMAL, currentCase));
+
+ currentCase = new FunctionalList<>();
+ }
+
+ /**
+ * Begins a case for the current rule.
+ *
+ * @throws IllegalStateException
+ * Must be invoked while a rule is being edited.
+ */
+ public void beginCase() {
+ if(currRule == null) {
+ throw new IllegalStateException("Must start a rule before adding cases");
+ }
}
- public void startWhereBlock(String string) {
- // TODO Auto-generated method stub
-
+ /**
+ * Set the initial rule of the grammar.
+ *
+ * @param init
+ * The initial rule of the grammar.
+ */
+ public void setInitialRule(String init) {
+ if(init == null) {
+ 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 rule named '%s' found", init));
+ }
+
+ initialRule = init;
}
}
diff --git a/RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java b/RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java
index 9e55736..b74c5f7 100644
--- a/RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java
+++ b/RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java
@@ -6,11 +6,9 @@ import bjc.utils.parserutils.BlockReader.Block;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
-import java.util.Scanner;
/**
* Reads {@link RGrammar} from a input stream.
@@ -40,6 +38,17 @@ public class RGrammarParser {
*/
static {
pragmas = new HashMap<>();
+
+ pragmas.put("initial-rule", (body, build, level) -> {
+ int sep = body.indexOf(' ');
+
+ if(sep != -1) {
+ throw new GrammarException(
+ "Initial-rule pragma takes only one argument, the name of the initial rule");
+ }
+
+ build.setInitialRule(body);
+ });
}
/**
@@ -85,6 +94,12 @@ public class RGrammarParser {
* Handles an arbitrary block.
*/
private void handleBlock(RGrammarBuilder build, String block, int level) throws GrammarException {
+ /*
+ * Discard empty blocks
+ */
+ if(block.equals("")) return;
+ if(block.equals("\n")) return;
+
int typeSep = block.indexOf(' ');
if(typeSep == -1) {
@@ -100,6 +115,11 @@ public class RGrammarParser {
handleRuleBlock(block, build, level);
} else if(blockType.equalsIgnoreCase("where")) {
handleWhereBlock(block, build, level);
+ } else if(blockType.equalsIgnoreCase("#")) {
+ /*
+ * Comment block.
+ */
+ return;
} else {
throw new GrammarException(String.format("Unknown block type: '%s'", blockType));
}
@@ -124,7 +144,7 @@ public class RGrammarParser {
}
String pragmaLeader = pragmaContents.substring(0, pragmaSep);
- String pragmaBody = pragmaContents.substring(pragmaSep);
+ String pragmaBody = pragmaContents.substring(pragmaSep + 1);
if(!pragmaLeader.equalsIgnoreCase("pragma")) {
throw new GrammarException(
@@ -153,7 +173,7 @@ public class RGrammarParser {
if(bodySep == -1) bodySep = pragma.length();
String pragmaName = pragma.substring(0, bodySep);
- String pragmaBody = pragma.substring(bodySep);
+ String pragmaBody = pragma.substring(bodySep + 1);
if(pragmas.containsKey(pragmaName)) {
pragmas.get(pragmaName).accept(pragmaBody, build, level);
@@ -183,12 +203,16 @@ public class RGrammarParser {
ruleReader.forEachBlock((block) -> {
handleRuleCase(block.contents, build);
});
+
+ build.finishRule();
} else {
/*
* Rule with a declaration followed by a
* single case.
*/
handleRuleDecl(build, ruleBlock);
+
+ build.finishRule();
}
} catch(GrammarException gex) {
throw new GrammarException(
@@ -206,18 +230,29 @@ public class RGrammarParser {
int declSep = declContents.indexOf("\u2192");
if(declSep == -1) {
- throw new GrammarException("A rule must be given at least one case in its declaration, and"
- + "seperated from that case by \u2192");
+ /*
+ * The old syntax allowed it to work with just a space.
+ */
+ declSep = declContents.indexOf(' ');
+
+ if(declSep == -1) {
+ throw new GrammarException(
+ "A rule must be given at least one case in its declaration, and"
+ + "seperated from that case by \u2192");
+ }
+
+ System.out.println(
+ "WARNING: Rule declarations must now be seperated from the initial case by \u2192.");
}
String ruleName = declContents.substring(0, declSep).trim();
- String ruleBody = declContents.substring(declSep).trim();
+ String ruleBody = declContents.substring(declSep + 1).trim();
if(ruleName.equals("")) {
throw new GrammarException("The empty string is not a valid rule name");
}
- build.setCurrentRule(ruleName);
+ build.startRule(ruleName);
handleRuleCase(ruleBody, build);
}
@@ -226,6 +261,8 @@ public class RGrammarParser {
* Handle a single case of a rule.
*/
private void handleRuleCase(String cse, RGrammarBuilder build) {
+ build.beginCase();
+
for(String csepart : cse.split(" ")) {
build.addCasePart(csepart);
}
@@ -236,10 +273,13 @@ 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 {
-
+ /*
+ * TODO decide syntax for where blocks.
+ */
} catch(GrammarException gex) {
throw new GrammarException(
String.format("Error in where block (%s)", whereReader.getBlock()),
diff --git a/RGens/src/main/java/bjc/rgens/newparser/RGrammarTest.java b/RGens/src/main/java/bjc/rgens/newparser/RGrammarTest.java
new file mode 100644
index 0000000..483542e
--- /dev/null
+++ b/RGens/src/main/java/bjc/rgens/newparser/RGrammarTest.java
@@ -0,0 +1,27 @@
+package bjc.rgens.newparser;
+
+import java.io.InputStream;
+
+/**
+ * Test for new grammar syntax.
+ *
+ * @author EVE
+ *
+ */
+public class RGrammarTest {
+ /**
+ * Main method.
+ *
+ * @param args
+ * Unused CLI args.
+ */
+ public static void main(String[] args) {
+ InputStream stream = RGrammarTest.class.getResourceAsStream("/sample-grammars/insults.gram");
+
+ RGrammarParser parse = new RGrammarParser();
+
+ RGrammar grammar = parse.readGrammar(stream);
+
+ System.out.println(grammar.generate(null));
+ }
+}
diff --git a/RGens/src/main/java/bjc/rgens/newparser/Rule.java b/RGens/src/main/java/bjc/rgens/newparser/Rule.java
new file mode 100644
index 0000000..c479c87
--- /dev/null
+++ b/RGens/src/main/java/bjc/rgens/newparser/Rule.java
@@ -0,0 +1,50 @@
+package bjc.rgens.newparser;
+
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+/**
+ * A rule in a randomized grammar.
+ *
+ * @author EVE
+ *
+ */
+public class Rule {
+ /**
+ * The name of this grammar rule.
+ */
+ public final String ruleName;
+
+ private IList<RuleCase> ruleCases;
+
+ /**
+ * Create a new grammar rule.
+ *
+ * @param ruleName
+ * The name of the grammar rule.
+ */
+ public Rule(String ruleName) {
+ this.ruleName = ruleName;
+
+ ruleCases = new FunctionalList<>();
+ }
+
+ /**
+ * Adds a case to the rule.
+ *
+ * @param cse
+ * The case to add.
+ */
+ public void addCase(RuleCase cse) {
+ ruleCases.add(cse);
+ }
+
+ /**
+ * Get a random case from this rule.
+ *
+ * @return A random case from this rule.
+ */
+ public RuleCase getCase() {
+ return ruleCases.randItem();
+ }
+} \ No newline at end of file
diff --git a/RGens/src/main/java/bjc/rgens/newparser/RuleCase.java b/RGens/src/main/java/bjc/rgens/newparser/RuleCase.java
new file mode 100644
index 0000000..e919cb8
--- /dev/null
+++ b/RGens/src/main/java/bjc/rgens/newparser/RuleCase.java
@@ -0,0 +1,95 @@
+package bjc.rgens.newparser;
+
+import bjc.utils.funcdata.IList;
+
+/**
+ * A case in a rule in a randomized grammar.
+ *
+ * @author EVE
+ */
+public class RuleCase {
+ /**
+ * The possible types of a case.
+ *
+ * @author EVE
+ *
+ */
+ public static enum CaseType {
+ /**
+ * A normal case, composed from a list of elementList.
+ */
+ NORMAL;
+ }
+
+ /**
+ * The type of this case.
+ */
+ public final CaseType type;
+
+ /**
+ * The list of element values for this case.
+ *
+ * <h2>Used For</h2>
+ * <dl>
+ * <dt>NORMAL</dt>
+ * <dd>Used as the list of elementList the rule is composed of.</dd>
+ * </dl>
+ */
+ private IList<CaseElement> elementList;
+
+ /**
+ * Create a new case of the specified type.
+ *
+ * @param typ
+ * The type of case to create.
+ *
+ * @throws IllegalArgumentException
+ * If the type requires parameters.
+ */
+ public RuleCase(CaseType typ) {
+ switch(typ) {
+ case NORMAL:
+ throw new IllegalArgumentException("This type requires an element list parameter");
+ default:
+ break;
+ }
+
+ type = typ;
+ }
+
+ /**
+ * Create a new case of the specified type that takes a element list
+ * parameter.
+ *
+ * @param typ
+ * The type of case to create.
+ *
+ * @param elements
+ * The element list parameter of the case.
+ *
+ * @throws IllegalArgumentException
+ * If this type doesn't take a element list parameter.
+ */
+ public RuleCase(CaseType typ, IList<CaseElement> elements) {
+ switch(typ) {
+ case NORMAL:
+ break;
+ default:
+ throw new IllegalArgumentException("This type doesn't have a element list parameter");
+ }
+
+ type = typ;
+
+ elementList = elements;
+ }
+
+ /**
+ * Get the element list value of this type.
+ *
+ * @return The element list value of this case, or null if this type
+ * doesn't have one.
+ */
+ public IList<CaseElement> getElements() {
+ return elementList;
+ }
+} \ No newline at end of file