summaryrefslogtreecommitdiff
path: root/RGens/src/main/java/bjc/rgens/newparser/RGrammar.java
blob: 35fc356cdfc9a1bbf9039b44fa209d5a49542f17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package bjc.rgens.newparser;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Represents a randomized grammar.
 * 
 * @author EVE
 *
 */
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, RGrammar> importRules;

	private Set<String> exportRules;

	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 exportedRules
	 *                The set of imported rules to use.
	 */
	public void setImportedRules(Map<String, RGrammar> exportedRules) {
		importRules = exportedRules;
	}

	/**
	 * 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())) {
					RGrammar dst = importRules.get(elm.getLiteral());

					state.contents.append(dst.generate(elm.getLiteral()));
				} 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) {
		/*
		 * Passing null nulls our initial rule.
		 */
		if(initialRule == null) {
			this.initialRule = null;
			return;
		}

		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;
	}

	/**
	 * Gets the rules exported by this grammar.
	 * 
	 * The initial rule is exported by default if specified.
	 * 
	 * @return The rules exported by this grammar.
	 */
	public Set<Rule> getExportedRules() {
		Set<Rule> res = new HashSet<>();

		for(String rname : exportRules) {
			if(!rules.containsKey(rname)) {
				throw new GrammarException(String
						.format("No rule named '%s' local to this grammar found", initialRule));
			}

			res.add(rules.get(rname));
		}

		if(initialRule != null) {
			res.add(rules.get(initialRule));
		}

		return res;
	}

	/**
	 * Set the rules exported by this grammar.
	 * 
	 * @param exportRules
	 *                The rules exported by this grammar.
	 */
	public void setExportedRules(Set<String> exportRules) {
		this.exportRules = exportRules;
	}
}