summaryrefslogtreecommitdiff
path: root/RGens/src
diff options
context:
space:
mode:
Diffstat (limited to 'RGens/src')
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/CaseElement.java121
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RGrammar.java116
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RGrammarBuilder.java33
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RGrammarParser.java37
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RGrammarSet.java88
-rw-r--r--RGens/src/main/java/bjc/rgens/newparser/RGrammarTest.java34
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);
}
}