diff --git a/grammer/GrammarMain.java b/grammer/GrammarMain.java new file mode 100644 index 0000000..86c8c34 --- /dev/null +++ b/grammer/GrammarMain.java @@ -0,0 +1,63 @@ +// Stuart Reges +// 3/10/04 +// +// GrammarMain contains a main program that prompts a user for the name of a +// grammar file and then gives the user the opportunity to generate random +// versions of various elements of the grammar. + +import java.io.*; +import java.util.*; + +public class GrammarMain { + public static void main(String[] args) throws FileNotFoundException { + Scanner console = new Scanner(System.in); + System.out.println("Welcome to the cs145 random sentence generator."); + System.out.println(); + + // open grammar file + System.out.print("What is the name of the grammar file? "); + String fileName = console.nextLine(); + Scanner input = new Scanner(new File(fileName)); + + // read the grammar file and construct the grammar solver + List grammar = new ArrayList(); + while (input.hasNextLine()) + grammar.add(input.nextLine()); + GrammarSolver solver = + new GrammarSolver(Collections.unmodifiableList(grammar)); + + showResults(console, solver); + } + + // pre : console open for console reading, solver initialized + // post: allows the user to repeatedly pick a grammar element to generate + public static void showResults(Scanner console, GrammarSolver solver) { + for(;;) { + System.out.println(); + System.out.println("Available symbols to generate are:"); + System.out.println(solver.getSymbols()); + System.out.print("What do you want generated (return to quit)? "); + String target = console.nextLine(); + if (target.length() == 0) + break; + if (!solver.grammarContains(target)) + System.out.println("Illegal symbol"); + else { + System.out.print("How many do you want me to generate? "); + if (!console.hasNextInt()) + System.out.println("that's not an integer"); + else { + int number = console.nextInt(); + if (number < 0) + System.out.println("no negatives allowed"); + else { + String[] answers = solver.generate(target, number); + for (int i = 0; i < number; i++) + System.out.println(answers[i]); + } + } + console.nextLine(); // to position to next line + } + } + } +} diff --git a/grammer/GrammarSolver.java b/grammer/GrammarSolver.java new file mode 100644 index 0000000..7575f95 --- /dev/null +++ b/grammer/GrammarSolver.java @@ -0,0 +1,158 @@ +/** +* +* Matt Jensen +* CS145 - Lab 5 +* 5/30/19 +* +*/ + +import java.util.*; +import java.util.stream.Collectors; + +public class GrammarSolver { + + private Map> grammarMap; + private List grammarList; + + // sets grammar of object. + public GrammarSolver(List grammarList) { + this.grammarList = grammarList; + this.grammarMap = new TreeMap>(); + + // add all lines to grammer. + for(String entry : grammarList) { + if( this.isValidEntry(entry) ) { // throws exception if illegal. + this.addEntry(entry); + } + } + } + + /** + * + * Public Methods + * + */ + + // publicly evaluate grammar + public String[] generate(String symbol, int times) { + String[] result = new String[times]; + for( int i = 0; i < result.length; i++) { + result[i] = this.generate(symbol); + } + return result; + + } + + // map of available grammars. + public Map> getGrammars() { + return this.grammarMap; + } + + // list of grammars. + public List getGrammarList() { + return this.grammarList; + } + + // all the keys/nonterminals of the grammer + public String getSymbols() { + return this.getGrammars().keySet().toString(); + } + + // grammer contains a terminal key. + public boolean grammarContains(String key) { + return this.getGrammars().keySet().contains(key); + } + + /** + * + * Private Helper Methods + * + */ + + // recursively evaluate grammar + private String generate(String symbol) { + String result = ""; + Set symbols = this.getGrammars().keySet(); + + // base case + if(this.grammarContains(symbol) != true ) { + return ""; + } + String rule = this.getRandomTerminal(symbol); + + // apply the rules + for(String subRule : GrammarSolver.splitRule(rule)){ + if( ! result.isEmpty()) { + result += " "; // keeps leading whitespace off. + } + if( this.grammarContains(subRule) ) { // test if a subrule is a nonterminal. + result += this.generate(subRule); // evaluates nonterminal rule and appends it. + } else { + result += subRule; + } + } + return result.trim(); + } + + // add grammar to grammars. + private void addEntry(String entry) { + String nonterminal = GrammarSolver.nonTerminal(entry); + List rules = GrammarSolver.terminals(entry); + this.getGrammars().put(nonterminal, rules); + } + + // line of a grammar is valid. + private boolean isValidEntry(String entry) { + // errors if bad colon count + int colonCount = entry.length() - entry.replace(":", "").length(); + if( colonCount != 1) { + throw new IllegalArgumentException("does not contain single colon"); + } + + // errors if duplicate + String nonterminal = GrammarSolver.nonTerminal(entry); + if( this.getGrammars().keySet().contains(nonterminal) ) { + throw new IllegalArgumentException("duplicate non-terminal detected"); + } + return true; + } + + // random element of a non-terminal's rules. + private String getRandomTerminal(String symbol) { + Random random = new Random(); + List rules = this.getGrammars().get(symbol); + int index = random.nextInt(rules.size()); + return rules.get(index); + } + + + + /** + * + * Static Methods + * + */ + + // splits a string of rules at the whitespaces. + private static String[] splitRule(String rule) { + String[] split = rule.split("[ \t]"); + return split; + } + + // pre: no whitespace. + // pre: non-empty. + public static String nonTerminal(String entry) { + return entry.substring(0, entry.indexOf(':')); + } + + // extracts terminals from a string. + public static List terminals(String entry) { + List rules = new ArrayList(); + entry = entry.substring(entry.indexOf(':') + 1, entry.length()); + String[] exploded = entry.split("\\|"); + for(int i = 0; i < exploded.length; i++) { + rules.add(exploded[i]); + } + return rules; + } +} diff --git a/grammer/GrammarTest.java b/grammer/GrammarTest.java new file mode 100644 index 0000000..7b387ab --- /dev/null +++ b/grammer/GrammarTest.java @@ -0,0 +1,206 @@ +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertArrayEquals; +import org.junit.Test; +import java.util.*; + +public class GrammarTest { + + @Test + public void testMixedNonAndTerminals() { + List grammar = new ArrayList(); + grammar.add(": |"); + grammar.add(":Test"); + grammar.add(":"); + GrammarSolver solver = new GrammarSolver(grammar); + String[] result = solver.generate("", 1); + String[] expected = new String[1]; + Arrays.fill(expected, "Test"); + assertArrayEquals(expected, result); + } + @Test(expected = IllegalArgumentException.class) + public void testGenerateNonTermDoesntExist() { + List grammar = new ArrayList(); + grammar.add(":Test|:Other"); + GrammarSolver solver = new GrammarSolver(grammar); + solver.generate("", 1); + } + + @Test(expected = IllegalArgumentException.class) + public void testGenerateTimesInvalid() { + List grammar = new ArrayList(); + grammar.add(":Test|:Other"); + GrammarSolver solver = new GrammarSolver(grammar); + solver.generate("", 0); + solver.generate("", -1); + } + + @Test + public void testGenerate() { + String[] expected = new String[2]; + Arrays.fill(expected, "Test"); + + List grammar = new ArrayList(); + grammar.add(":Test"); + GrammarSolver solver = new GrammarSolver(grammar); + + String[] result = solver.generate("", 2); + assertArrayEquals(expected, result); + } + + @Test + public void testMultipleRulePaths() { + String[] expected = new String[3]; + Arrays.fill(expected, "Test Nest"); + + List grammar = new ArrayList(); + grammar.add(": "); + grammar.add(":Test"); + grammar.add(":Nest"); + GrammarSolver solver = new GrammarSolver(grammar); + + String[] result = solver.generate("", 3); + assertArrayEquals(expected, result); + } + @Test + public void testMultipleSymbolsAsRules() { + String[] expected = new String[3]; + Arrays.fill(expected, "Nest Nest"); + + List grammar = new ArrayList(); + grammar.add(": "); + grammar.add(":"); + grammar.add(":Nest"); + GrammarSolver solver = new GrammarSolver(grammar); + + String[] result = solver.generate("", 3); + assertArrayEquals(expected, result); + } + + @Test + public void testGenerateNestedTwice() { + String[] expected = new String[3]; + Arrays.fill(expected, "Nest"); + + List grammar = new ArrayList(); + grammar.add(":"); + grammar.add(":"); + grammar.add(":Nest"); + GrammarSolver solver = new GrammarSolver(grammar); + + String[] result = solver.generate("", 3); + assertArrayEquals(expected, result); + } + @Test + public void testGenerateNested() { + String[] expected = new String[2]; + Arrays.fill(expected, "Nest"); + + List grammar = new ArrayList(); + grammar.add(":"); + grammar.add(":Nest"); + GrammarSolver solver = new GrammarSolver(grammar); + + String[] result = solver.generate("", 2); + assertArrayEquals(expected, result); + } + @Test + public void testGrammarListImmutable() { + List grammar = new ArrayList(); + grammar.add(":Test"); + GrammarSolver solver = new GrammarSolver(grammar); + String expected = grammar.get(0); + String actual = solver.getGrammarList().get(0); + assertEquals("got back different than instantiation.", expected, actual); + assertArrayEquals("different grammars.", grammar.toArray(), solver.getGrammarList().toArray()); + } + @Test + public void testGrammarListImmutableWithPipe() { + List grammar = new ArrayList(); + grammar.add(":Test|Other"); + GrammarSolver solver = new GrammarSolver(grammar); + assertArrayEquals("different grammars.", grammar.toArray(), solver.getGrammarList().toArray()); + } + + @Test + public void testNonTerminalExtract() { + String entry = ":Test"; + String expected = ""; + String actual = GrammarSolver.nonTerminal(entry); + assertEquals("failure - extraction failed", expected, actual); + } + @Test + public void testRuleExtractContent() { + String entry = ":Test"; + List expected = new ArrayList(); + expected.add("Test"); + List actual = GrammarSolver.terminals(entry); + assertArrayEquals("failure - rule extraction failed", expected.toArray(), actual.toArray()); + } + @Test + public void testRuleCount() { + String entry = ":Test"; + List expected = new ArrayList(); + expected.add("Test"); + List actual = GrammarSolver.terminals(entry); + assertEquals("rule count different", expected.size(), actual.size()); + } + + @Test(expected = IllegalArgumentException.class) + public void testNoColon() { + List grammar = new ArrayList(); + grammar.add("test"); + GrammarSolver solver = new GrammarSolver(grammar); + } + + @Test(expected = IllegalArgumentException.class) + public void testDuplicateGrammar() { + List grammar = new ArrayList(); + grammar.add(":Test|Other"); + grammar.add(":Test|Other"); + GrammarSolver solver = new GrammarSolver(grammar); + } + + @Test(expected = IllegalArgumentException.class) + public void testGrammarContainsSingleColon() { + List grammar = new ArrayList(); + grammar.add(":Test|:Other"); + GrammarSolver solver = new GrammarSolver(grammar); + } + + @Test + public void testGrammarContains() { + List grammar = new ArrayList(); + grammar.add(":Test"); + GrammarSolver solver = new GrammarSolver(grammar); + assertEquals(true, solver.grammarContains("")); + assertEquals(false, solver.grammarContains("a")); + assertEquals(false, solver.grammarContains("")); + } + @Test + public void testGrammarContainsCaseInsensitive() { + List grammar = new ArrayList(); + grammar.add(":Test"); + GrammarSolver solver = new GrammarSolver(grammar); + assertEquals(true, solver.grammarContains("")); + assertEquals(false, solver.grammarContains("")); + } + + @Test + public void testGetSymbols() { + List grammar = new ArrayList(); + grammar.add(":Test"); + grammar.add(":Test|Other"); + GrammarSolver solver = new GrammarSolver(grammar); + assertEquals("[, ]", solver.getSymbols()); + } + @Test + public void testGetSymbolsSorted() { + List grammar = new ArrayList(); + grammar.add(":Test|Other"); + grammar.add(":Test"); + grammar.add(":Test|Other"); + GrammarSolver solver = new GrammarSolver(grammar); + assertEquals("[, , ]", solver.getSymbols()); + } +} diff --git a/grammer/sentence.txt b/grammer/sentence.txt new file mode 100644 index 0000000..315321c --- /dev/null +++ b/grammer/sentence.txt @@ -0,0 +1,10 @@ +: +: | +:John|Jane|Sally|Spot|Fred|Elmo +:| +:big|fat|green|wonderful|faulty|subliminal|pretentious +:the|a +:dog|cat|man|university|father|mother|child|television +: | +:hit|honored|kissed|helped +:died|collapsed|laughed|wept \ No newline at end of file diff --git a/grammer/sentence2.txt b/grammer/sentence2.txt new file mode 100644 index 0000000..06d1af7 --- /dev/null +++ b/grammer/sentence2.txt @@ -0,0 +1,5 @@ +E: T | E OP T +T: x | y | 42 | 0 | 1 | 92 | ( E ) | F1 ( E ) | - T | F2 ( E , E ) +OP: + | - | * | % | / +F1: sin | cos| tan |sqrt | abs +F2:max |min | pow \ No newline at end of file diff --git a/grammer/spec.png b/grammer/spec.png new file mode 100644 index 0000000..5fb16f3 Binary files /dev/null and b/grammer/spec.png differ