File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\bnf\Bnf.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.bnf;
8
9 import java.io.ByteArrayInputStream;
10 import java.io.IOException;
11 import java.io.InputStreamReader;
12 import java.io.Reader;
13 import java.sql.ResultSet;
14 import java.sql.SQLException;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.StringTokenizer;
18 import org.h2.server.web.DbContextRule;
19 import org.h2.tools.Csv;
20 import org.h2.util.New;
21 import org.h2.util.StringUtils;
22 import org.h2.util.Utils;
23
24 /**
25 * This class can read a file that is similar to BNF (Backus-Naur form).
26 * It is made specially to support SQL grammar.
27 */
28 public class Bnf {
29
30 /**
31 * The rule map. The key is lowercase, and all spaces
32 * are replaces with underscore.
33 */
34 private final HashMap<String, RuleHead> ruleMap = New.hashMap();
35 private String syntax;
36 private String currentToken;
37 private String[] tokens;
38 private char firstChar;
39 private int index;
40 private Rule lastRepeat;
41 private ArrayList<RuleHead> statements;
42 private String currentTopic;
43
44 /**
45 * Create an instance using the grammar specified in the CSV file.
46 *
47 * @param csv if not specified, the help.csv is used
48 * @return a new instance
49 */
50 public static Bnf getInstance(Reader csv) throws SQLException, IOException {
51 Bnf bnf = new Bnf();
52 if (csv == null) {
53 byte[] data = Utils.getResource("/org/h2/res/help.csv");
54 csv = new InputStreamReader(new ByteArrayInputStream(data));
55 }
56 bnf.parse(csv);
57 return bnf;
58 }
59
60 private void addFixedRule(String name, int fixedType) {
61 Rule rule = new RuleFixed(fixedType);
62 addRule(name, "Fixed", rule);
63 }
64
65 private RuleHead addRule(String topic, String section, Rule rule) {
66 RuleHead head = new RuleHead(section, topic, rule);
67 String key = StringUtils.toLowerEnglish(topic.trim().replace(' ', '_'));
68 if (ruleMap.get(key) != null) {
69 throw new AssertionError("already exists: " + topic);
70 }
71 ruleMap.put(key, head);
72 return head;
73 }
74
75 private void parse(Reader reader) throws SQLException, IOException {
76 Rule functions = null;
77 statements = New.arrayList();
78 Csv csv = new Csv();
79 csv.setLineCommentCharacter('#');
80 ResultSet rs = csv.read(reader, null);
81 while (rs.next()) {
82 String section = rs.getString("SECTION").trim();
83 if (section.startsWith("System")) {
84 continue;
85 }
86 String topic = rs.getString("TOPIC");
87 syntax = rs.getString("SYNTAX").trim();
88 currentTopic = section;
89 tokens = tokenize();
90 index = 0;
91 Rule rule = parseRule();
92 if (section.startsWith("Command")) {
93 rule = new RuleList(rule, new RuleElement(";\n\n", currentTopic), false);
94 }
95 RuleHead head = addRule(topic, section, rule);
96 if (section.startsWith("Function")) {
97 if (functions == null) {
98 functions = rule;
99 } else {
100 functions = new RuleList(rule, functions, true);
101 }
102 } else if (section.startsWith("Commands")) {
103 statements.add(head);
104 }
105 }
106 addRule("@func@", "Function", functions);
107 addFixedRule("@ymd@", RuleFixed.YMD);
108 addFixedRule("@hms@", RuleFixed.HMS);
109 addFixedRule("@nanos@", RuleFixed.NANOS);
110 addFixedRule("anything_except_single_quote", RuleFixed.ANY_EXCEPT_SINGLE_QUOTE);
111 addFixedRule("anything_except_double_quote", RuleFixed.ANY_EXCEPT_DOUBLE_QUOTE);
112 addFixedRule("anything_until_end_of_line", RuleFixed.ANY_UNTIL_EOL);
113 addFixedRule("anything_until_end_comment", RuleFixed.ANY_UNTIL_END);
114 addFixedRule("anything_except_two_dollar_signs", RuleFixed.ANY_EXCEPT_2_DOLLAR);
115 addFixedRule("anything", RuleFixed.ANY_WORD);
116 addFixedRule("@hex_start@", RuleFixed.HEX_START);
117 addFixedRule("@concat@", RuleFixed.CONCAT);
118 addFixedRule("@az_@", RuleFixed.AZ_UNDERSCORE);
119 addFixedRule("@af@", RuleFixed.AF);
120 addFixedRule("@digit@", RuleFixed.DIGIT);
121 addFixedRule("@open_bracket@", RuleFixed.OPEN_BRACKET);
122 addFixedRule("@close_bracket@", RuleFixed.CLOSE_BRACKET);
123 }
124
125 /**
126 * Parse the syntax and let the rule call the visitor.
127 *
128 * @param visitor the visitor
129 * @param s the syntax to parse
130 */
131 public void visit(BnfVisitor visitor, String s) {
132 this.syntax = s;
133 tokens = tokenize();
134 index = 0;
135 Rule rule = parseRule();
136 rule.setLinks(ruleMap);
137 rule.accept(visitor);
138 }
139
140 /**
141 * Convert convert ruleLink to rule_link.
142 *
143 * @param token the token
144 * @return the rule map key
145 */
146 public static String getRuleMapKey(String token) {
147 StringBuilder buff = new StringBuilder();
148 for (char ch : token.toCharArray()) {
149 if (Character.isUpperCase(ch)) {
150 buff.append('_').append(Character.toLowerCase(ch));
151 } else {
152 buff.append(ch);
153 }
154 }
155 return buff.toString();
156 }
157
158 /**
159 * Get the rule head for the given title.
160 *
161 * @param title the title
162 * @return the rule head, or null
163 */
164 public RuleHead getRuleHead(String title) {
165 return ruleMap.get(title);
166 }
167
168 private Rule parseRule() {
169 read();
170 return parseOr();
171 }
172
173 private Rule parseOr() {
174 Rule r = parseList();
175 if (firstChar == '|') {
176 read();
177 r = new RuleList(r, parseOr(), true);
178 }
179 lastRepeat = r;
180 return r;
181 }
182
183 private Rule parseList() {
184 Rule r = parseToken();
185 if (firstChar != '|' && firstChar != ']' && firstChar != '}' && firstChar != 0) {
186 r = new RuleList(r, parseList(), false);
187 }
188 lastRepeat = r;
189 return r;
190 }
191
192 private Rule parseToken() {
193 Rule r;
194 if ((firstChar >= 'A' && firstChar <= 'Z') || (firstChar >= 'a' && firstChar <= 'z')) {
195 // r = new RuleElement(currentToken+ " syntax:" + syntax);
196 r = new RuleElement(currentToken, currentTopic);
197 } else if (firstChar == '[') {
198 read();
199 Rule r2 = parseOr();
200 r = new RuleOptional(r2);
201 if (firstChar != ']') {
202 throw new AssertionError("expected ], got " + currentToken + " syntax:" + syntax);
203 }
204 } else if (firstChar == '{') {
205 read();
206 r = parseOr();
207 if (firstChar != '}') {
208 throw new AssertionError("expected }, got " + currentToken + " syntax:" + syntax);
209 }
210 } else if ("@commaDots@".equals(currentToken)) {
211 r = new RuleList(new RuleElement(",", currentTopic), lastRepeat, false);
212 r = new RuleRepeat(r, true);
213 } else if ("@dots@".equals(currentToken)) {
214 r = new RuleRepeat(lastRepeat, false);
215 } else {
216 r = new RuleElement(currentToken, currentTopic);
217 }
218 lastRepeat = r;
219 read();
220 return r;
221 }
222
223 private void read() {
224 if (index < tokens.length) {
225 currentToken = tokens[index++];
226 firstChar = currentToken.charAt(0);
227 } else {
228 currentToken = "";
229 firstChar = 0;
230 }
231 }
232
233 private String[] tokenize() {
234 ArrayList<String> list = New.arrayList();
235 syntax = StringUtils.replaceAll(syntax, "yyyy-MM-dd", "@ymd@");
236 syntax = StringUtils.replaceAll(syntax, "hh:mm:ss", "@hms@");
237 syntax = StringUtils.replaceAll(syntax, "nnnnnnnnn", "@nanos@");
238 syntax = StringUtils.replaceAll(syntax, "function", "@func@");
239 syntax = StringUtils.replaceAll(syntax, "0x", "@hexStart@");
240 syntax = StringUtils.replaceAll(syntax, ",...", "@commaDots@");
241 syntax = StringUtils.replaceAll(syntax, "...", "@dots@");
242 syntax = StringUtils.replaceAll(syntax, "||", "@concat@");
243 syntax = StringUtils.replaceAll(syntax, "a-z|_", "@az_@");
244 syntax = StringUtils.replaceAll(syntax, "A-Z|_", "@az_@");
245 syntax = StringUtils.replaceAll(syntax, "A-F", "@af@");
246 syntax = StringUtils.replaceAll(syntax, "0-9", "@digit@");
247 syntax = StringUtils.replaceAll(syntax, "'['", "@openBracket@");
248 syntax = StringUtils.replaceAll(syntax, "']'", "@closeBracket@");
249 StringTokenizer tokenizer = getTokenizer(syntax);
250 while (tokenizer.hasMoreTokens()) {
251 String s = tokenizer.nextToken();
252 // avoid duplicate strings
253 s = StringUtils.cache(s);
254 if (s.length() == 1) {
255 if (" \r\n".indexOf(s.charAt(0)) >= 0) {
256 continue;
257 }
258 }
259 list.add(s);
260 }
261 return list.toArray(new String[list.size()]);
262 }
263
264 /**
265 * Get the list of tokens that can follow.
266 * This is the main autocomplete method.
267 * The returned map for the query 'S' may look like this:
268 * <pre>
269 * key: 1#SELECT, value: ELECT
270 * key: 1#SET, value: ET
271 * </pre>
272 *
273 * @param query the start of the statement
274 * @return the map of possible token types / tokens
275 */
276 public HashMap<String, String> getNextTokenList(String query) {
277 Sentence sentence = new Sentence();
278 sentence.setQuery(query);
279 try {
280 for (RuleHead head : statements) {
281 if (!head.getSection().startsWith("Commands")) {
282 continue;
283 }
284 sentence.start();
285 if (head.getRule().autoComplete(sentence)) {
286 break;
287 }
288 }
289 } catch (IllegalStateException e) {
290 // ignore
291 }
292 return sentence.getNext();
293 }
294
295 /**
296 * Cross-link all statements with each other.
297 * This method is called after updating the topics.
298 */
299 public void linkStatements() {
300 for (RuleHead r : ruleMap.values()) {
301 r.getRule().setLinks(ruleMap);
302 }
303 }
304
305 /**
306 * Update a topic with a context specific rule.
307 * This is used for autocomplete support.
308 *
309 * @param topic the topic
310 * @param rule the database context rule
311 */
312 public void updateTopic(String topic, DbContextRule rule) {
313 topic = StringUtils.toLowerEnglish(topic);
314 RuleHead head = ruleMap.get(topic);
315 if (head == null) {
316 head = new RuleHead("db", topic, rule);
317 ruleMap.put(topic, head);
318 statements.add(head);
319 } else {
320 head.setRule(rule);
321 }
322 }
323
324 /**
325 * Get the list of possible statements.
326 *
327 * @return the list of statements
328 */
329 public ArrayList<RuleHead> getStatements() {
330 return statements;
331 }
332
333 /**
334 * Get the tokenizer for the given syntax.
335 *
336 * @param s the syntax
337 * @return the tokenizer
338 */
339 public static StringTokenizer getTokenizer(String s) {
340 return new StringTokenizer(s, " [](){}|.,\r\n<>:-+*/=<\">!'$", true);
341 }
342
343 }
344