2 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
5 *******************************************************************************
\r
6 * Copyright (C) 2002-2009, International Business Machines Corporation and *
\r
7 * others. All Rights Reserved. *
\r
8 *******************************************************************************
\r
10 package com.ibm.icu.dev.test.util;
\r
12 import java.util.ArrayList;
\r
13 import java.util.Map;
\r
14 import java.util.Set;
\r
15 import java.util.HashMap;
\r
16 import java.util.HashSet;
\r
17 import java.util.Iterator;
\r
19 import com.ibm.icu.text.UnicodeSet;
\r
20 import java.util.Random;
\r
23 private Map map = new HashMap();
\r
24 private Set variables = new HashSet();
\r
25 private Pick pick = null;
\r
26 private Pick.Target target = null;
\r
27 private Tokenizer t;
\r
28 private Quoter quoter;
\r
29 private Random random;
\r
31 public String next() {
\r
32 return target.next();
\r
35 public String getInternal() {
\r
36 return pick.getInternal(0, new HashSet());
\r
40 + "weight = integer '%';"
\r
41 + "range = '{' integer (',' integer?)? '}' weight*;"
\r
43 + "star = '*' weight*;"
\r
44 + "plus = '+' weight*;"
\r
45 + "maybe = '?' weight?;"
\r
46 + "quantifier = range | star | maybe | plus;"
\r
47 + "core = string | unicodeSet | '(' alternation ')';"
\r
48 + "sequence = (core quantifier*)+;"
\r
49 + "alternation = sequence (weight? ('|' sequence weight?)+)?;"
\r
50 + "rule = string '=' alternation;";
\r
53 * Match 0 or more times
\r
54 + Match 1 or more times
\r
55 ? Match 1 or 0 times
\r
56 {n} Match exactly n times
\r
57 {n,} Match at least n times
\r
58 {n,m} Match at least n but not more than m times
\r
64 public BNF(Random random, Quoter quoter) {
\r
65 this.random = random;
\r
66 this.quoter = quoter;
\r
67 t = new Tokenizer();
\r
70 public BNF addRules(String rules) {
\r
71 t.setSource(rules);
\r
74 return this; // for chaining
\r
77 public BNF complete() {
\r
78 // check that the rules match the variables, except for $root in rules
\r
79 Set ruleSet = map.keySet();
\r
81 variables.add("$root");
\r
82 variables.addAll(t.getLookedUpItems());
\r
83 if (!ruleSet.equals(variables)) {
\r
84 String msg = showDiff(variables, ruleSet);
\r
85 if (msg.length() != 0) msg = "Error: Missing definitions for: " + msg;
\r
86 String temp = showDiff(ruleSet, variables);
\r
87 if (temp.length() != 0) temp = "Warning: Defined but not used: " + temp;
\r
88 if (msg.length() == 0) msg = temp;
\r
89 else if (temp.length() != 0) {
\r
90 msg = msg + "; " + temp;
\r
95 if (!ruleSet.equals(variables)) {
\r
96 String msg = showDiff(variables, ruleSet);
\r
97 if (msg.length() != 0) msg = "Missing definitions for: " + msg;
\r
98 String temp = showDiff(ruleSet, variables);
\r
99 if (temp.length() != 0) temp = "Defined but not used: " + temp;
\r
100 if (msg.length() == 0) msg = temp;
\r
101 else if (temp.length() != 0) {
\r
102 msg = msg + "; " + temp;
\r
107 // replace variables by definitions
\r
108 Iterator it = ruleSet.iterator();
\r
109 while (it.hasNext()) {
\r
110 String key = (String) it.next();
\r
111 Pick expression = (Pick) map.get(key);
\r
112 Iterator it2 = ruleSet.iterator();
\r
113 if (false && key.equals("$crlf")) {
\r
114 System.out.println("debug") ;
\r
116 while (it2.hasNext()) {
\r
117 Object key2 = it2.next();
\r
118 if (key.equals(key2)) continue;
\r
119 Pick expression2 = (Pick) map.get(key2);
\r
120 expression2.replace(key, expression);
\r
123 pick = (Pick) map.get("$root");
\r
124 target = Pick.Target.make(pick, random, quoter);
\r
125 // TODO remove temp collections
\r
129 String showDiff(Set a, Set b) {
\r
130 Set temp = new HashSet();
\r
133 if (temp.size() == 0) return "";
\r
134 StringBuffer buffer = new StringBuffer();
\r
135 Iterator it = temp.iterator();
\r
136 while (it.hasNext()) {
\r
137 if (buffer.length() != 0) buffer.append(", ");
\r
138 buffer.append(it.next().toString());
\r
140 return buffer.toString();
\r
143 void error(String msg) {
\r
144 throw new IllegalArgumentException(msg
\r
145 + "\r\n" + t.toString());
\r
148 private boolean addRule() {
\r
149 int type = t.next();
\r
150 if (type == Tokenizer.DONE) return false;
\r
151 if (type != Tokenizer.STRING) error("missing weight");
\r
152 String s = t.getString();
\r
153 if (s.length() == 0 || s.charAt(0) != '$') error("missing $ in variable");
\r
154 if (t.next() != '=') error("missing =");
\r
155 int startBody = t.index;
\r
156 Pick rule = getAlternation();
\r
157 if (rule == null) error("missing expression");
\r
158 t.addSymbol(s, t.getSource(), startBody, t.index);
\r
159 if (t.next() != ';') error("missing ;");
\r
160 return addPick(s, rule);
\r
163 protected boolean addPick(String s, Pick rule) {
\r
164 Object temp = map.get(s);
\r
165 if (temp != null) error("duplicate variable");
\r
166 if (rule.name == null) rule.name(s);
\r
171 public BNF addSet(String variable, UnicodeSet set) {
\r
173 String body = set.toString();
\r
174 t.addSymbol(variable, body, 0, body.length());
\r
175 addPick(variable, Pick.codePoint(set));
\r
180 int maxRepeat = 99;
\r
182 Pick qualify(Pick item) {
\r
184 int type = t.next();
\r
187 return new Pick.Quote(item);
\r
189 return new Pick.Morph(item);
\r
191 int weight = getWeight();
\r
192 if (weight == NO_WEIGHT) weight = 50;
\r
193 weights = new int[] {100-weight, weight};
\r
194 return Pick.repeat(0, 1, weights, item);
\r
196 weights = getWeights();
\r
197 return Pick.repeat(1, maxRepeat, weights, item);
\r
199 weights = getWeights();
\r
200 return Pick.repeat(1, maxRepeat, weights, item);
\r
202 if (t.next() != Tokenizer.NUMBER) error("missing number");
\r
203 int start = (int) t.getNumber();
\r
209 if (type == Tokenizer.NUMBER) {
\r
210 end = (int)t.getNumber();
\r
214 if (type != '}') error("missing }");
\r
215 weights = getWeights();
\r
216 return Pick.repeat(start, end, weights, item);
\r
223 int token = t.next();
\r
224 if (token == Tokenizer.STRING) {
\r
225 String s = t.getString();
\r
226 if (s.charAt(0) == '$') variables.add(s);
\r
227 return Pick.string(s);
\r
229 if (token == Tokenizer.UNICODESET) {
\r
230 return Pick.codePoint(t.getUnicodeSet());
\r
232 if (token != '(') {
\r
236 Pick temp = getAlternation();
\r
238 if (token != ')') error("missing )");
\r
242 Pick getSequence() {
\r
243 Pick.Sequence result = null;
\r
246 Pick item = getCore();
\r
247 if (item == null) {
\r
248 if (result != null) return result;
\r
249 if (last != null) return last;
\r
250 error("missing item");
\r
252 // qualify it as many times as possible
\r
256 item = qualify(item);
\r
257 } while (item != oldItem);
\r
259 if (last == null) {
\r
262 if (result == null) result = Pick.makeSequence().and2(last);
\r
263 result = result.and2(item);
\r
268 // for simplicity, we just use recursive descent
\r
269 Pick getAlternation() {
\r
270 Pick.Alternation result = null;
\r
272 int lastWeight = NO_WEIGHT;
\r
274 Pick temp = getSequence();
\r
275 if (temp == null) error("empty alternation");
\r
276 int weight = getWeight();
\r
277 if (weight == NO_WEIGHT) weight = 1;
\r
278 if (last == null) {
\r
280 lastWeight = weight;
\r
282 if (result == null) result = Pick.makeAlternation().or2(lastWeight, last);
\r
283 result = result.or2(weight, temp);
\r
285 int token = t.next();
\r
286 if (token != '|') {
\r
288 if (result != null) return result;
\r
289 if (last != null) return last;
\r
294 private static final int NO_WEIGHT = Integer.MIN_VALUE;
\r
298 int token = t.next();
\r
299 if (token != Tokenizer.NUMBER) {
\r
303 weight = (int)t.getNumber();
\r
305 if (token != '%') error("missing %");
\r
309 int[] getWeights() {
\r
310 ArrayList list = new ArrayList();
\r
312 int weight = getWeight();
\r
313 if (weight == NO_WEIGHT) break;
\r
314 list.add(new Integer(weight));
\r
316 if (list.size() == 0) return null;
\r
317 int[] result = new int[list.size()];
\r
318 for (int i = 0; i < list.size(); ++i) {
\r
319 result[i] = ((Integer)list.get(i)).intValue();
\r
324 public int getMaxRepeat() {
\r
328 public BNF setMaxRepeat(int maxRepeat) {
\r
329 this.maxRepeat = maxRepeat;
\r