blob: d8c55e828bf4c89dca40aff6307f48587d840d94 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.tools.jstat;
27
28import java.io.*;
29import java.util.*;
30
31/**
32 * A class implementing a simple predictive parser for output format
33 * specification language for the jstat command.
34 *
35 * @author Brian Doherty
36 * @since 1.5
37 */
38public class Parser {
39
40 private static boolean pdebug = Boolean.getBoolean("jstat.parser.debug");
41 private static boolean ldebug = Boolean.getBoolean("jstat.lex.debug");
42
43 private static final char OPENBLOCK = '{';
44 private static final char CLOSEBLOCK = '}';
45 private static final char DOUBLEQUOTE = '"';
46 private static final char PERCENT_CHAR = '%';
47 private static final char OPENPAREN = '(';
48 private static final char CLOSEPAREN = ')';
49
50 private static final char OPERATOR_PLUS = '+';
51 private static final char OPERATOR_MINUS = '-';
52 private static final char OPERATOR_MULTIPLY = '*';
53 private static final char OPERATOR_DIVIDE = '/';
54
55 private static final String OPTION = "option";
56 private static final String COLUMN = "column";
57 private static final String DATA = "data";
58 private static final String HEADER = "header";
59 private static final String WIDTH = "width";
60 private static final String FORMAT = "format";
61 private static final String ALIGN = "align";
62 private static final String SCALE = "scale";
63
64 private static final String START = OPTION;
65
66 private static final Set scaleKeyWords = Scale.keySet();
67 private static final Set alignKeyWords = Alignment.keySet();
68 private static String[] otherKeyWords = {
69 OPTION, COLUMN, DATA, HEADER, WIDTH, FORMAT, ALIGN, SCALE
70 };
71
72 private static char[] infixOps = {
73 OPERATOR_PLUS, OPERATOR_MINUS, OPERATOR_MULTIPLY, OPERATOR_DIVIDE
74 };
75
76 private static char[] delimiters = {
77 OPENBLOCK, CLOSEBLOCK, PERCENT_CHAR, OPENPAREN, CLOSEPAREN
78 };
79
80
81 private static Set<String> reservedWords;
82
83 private StreamTokenizer st;
84 private String filename;
85 private Token lookahead;
86 private Token previous;
87 private int columnCount;
88 private OptionFormat optionFormat;
89
90 public Parser(String filename) throws FileNotFoundException {
91 this.filename = filename;
92 Reader r = new BufferedReader(new FileReader(filename));
93 }
94
95 public Parser(Reader r) {
96 st = new StreamTokenizer(r);
97
98 // allow both c++ style comments
99 st.ordinaryChar('/');
100 st.wordChars('_','_');
101 st.slashSlashComments(true);
102 st.slashStarComments(true);
103
104 reservedWords = new HashSet<String>();
105 for (int i = 0; i < otherKeyWords.length; i++) {
106 reservedWords.add(otherKeyWords[i]);
107 }
108
109 for (int i = 0; i < delimiters.length; i++ ) {
110 st.ordinaryChar(delimiters[i]);
111 }
112
113 for (int i = 0; i < infixOps.length; i++ ) {
114 st.ordinaryChar(infixOps[i]);
115 }
116 }
117
118 /**
119 * push back the lookahead token and restore the lookahead token
120 * to the previous token.
121 */
122 private void pushBack() {
123 lookahead = previous;
124 st.pushBack();
125 }
126
127 /**
128 * retrieve the next token, placing the token value in the lookahead
129 * member variable, storing its previous value in the previous member
130 * variable.
131 */
132 private void nextToken() throws ParserException, IOException {
133 int t = st.nextToken();
134 previous = lookahead;
135 lookahead = new Token(st.ttype, st.sval, st.nval);
136 log(ldebug, "lookahead = " + lookahead);
137 }
138
139 /**
140 * match one of the token values in the given set of key words
141 * token is assumed to be of type TT_WORD, and the set is assumed
142 * to contain String objects.
143 */
144 private Token matchOne(Set keyWords) throws ParserException, IOException {
145 if ((lookahead.ttype == StreamTokenizer.TT_WORD)
146 && keyWords.contains(lookahead.sval)) {
147 Token t = lookahead;
148 nextToken();
149 return t;
150 }
151 throw new SyntaxException(st.lineno(), keyWords, lookahead);
152 }
153
154 /**
155 * match a token with TT_TYPE=type, and the token value is a given sequence
156 * of characters.
157 */
158 private void match(int ttype, String token)
159 throws ParserException, IOException {
160 if (lookahead.ttype == ttype && lookahead.sval.compareTo(token) == 0) {
161 nextToken();
162 } else {
163 throw new SyntaxException(st.lineno(), new Token(ttype, token),
164 lookahead);
165 }
166 }
167
168 /**
169 * match a token with TT_TYPE=type
170 */
171 private void match(int ttype) throws ParserException, IOException {
172 if (lookahead.ttype == ttype) {
173 nextToken();
174 } else {
175 throw new SyntaxException(st.lineno(), new Token(ttype), lookahead);
176 }
177 }
178
179 /**
180 * match a token with TT_TYPE=char, where the token value is the given char.
181 */
182 private void match(char ttype) throws ParserException, IOException {
183 if (lookahead.ttype == (int)ttype) {
184 nextToken();
185 }
186 else {
187 throw new SyntaxException(st.lineno(), new Token((int)ttype),
188 lookahead);
189 }
190 }
191
192 /**
193 * match a token with TT_TYPE='"', where the token value is a sequence
194 * of characters between matching quote characters.
195 */
196 private void matchQuotedString() throws ParserException, IOException {
197 match(DOUBLEQUOTE);
198 }
199
200 /**
201 * match a TT_NUMBER token that matches a parsed number value
202 */
203 private void matchNumber() throws ParserException, IOException {
204 match(StreamTokenizer.TT_NUMBER);
205 }
206
207 /**
208 * match a TT_WORD token that matches an arbitrary, not quoted token.
209 */
210 private void matchID() throws ParserException, IOException {
211 match(StreamTokenizer.TT_WORD);
212 }
213
214 /**
215 * match a TT_WORD token that matches the given string
216 */
217 private void match(String token) throws ParserException, IOException {
218 match(StreamTokenizer.TT_WORD, token);
219 }
220
221 /**
222 * determine if the given word is a reserved key word
223 */
224 private boolean isReservedWord(String word) {
225 return reservedWords.contains(word);
226 }
227
228 /**
229 * determine if the give work is a reserved key word
230 */
231 private boolean isInfixOperator(char op) {
232 for (int i = 0; i < infixOps.length; i++) {
233 if (op == infixOps[i]) {
234 return true;
235 }
236 }
237 return false;
238 }
239
240 /**
241 * scalestmt -> 'scale' scalespec
242 * scalespec -> <see above scaleTerminals array>
243 */
244 private void scaleStmt(ColumnFormat cf)
245 throws ParserException, IOException {
246 match(SCALE);
247 Token t = matchOne(scaleKeyWords);
248 cf.setScale(Scale.toScale(t.sval));
249 String scaleString = t.sval;
250 log(pdebug, "Parsed: scale -> " + scaleString);
251 }
252
253 /**
254 * alignstmt -> 'align' alignspec
255 * alignspec -> <see above alignTerminals array>
256 */
257 private void alignStmt(ColumnFormat cf)
258 throws ParserException, IOException {
259 match(ALIGN);
260 Token t = matchOne(alignKeyWords);
261 cf.setAlignment(Alignment.toAlignment(t.sval));
262 String alignString = t.sval;
263 log(pdebug, "Parsed: align -> " + alignString);
264 }
265
266 /**
267 * headerstmt -> 'header' quotedstring
268 */
269 private void headerStmt(ColumnFormat cf)
270 throws ParserException, IOException {
271 match(HEADER);
272 String headerString = lookahead.sval;
273 matchQuotedString();
274 cf.setHeader(headerString);
275 log(pdebug, "Parsed: header -> " + headerString);
276 }
277
278 /**
279 * widthstmt -> 'width' integer
280 */
281 private void widthStmt(ColumnFormat cf)
282 throws ParserException, IOException {
283 match(WIDTH);
284 double width = lookahead.nval;
285 matchNumber();
286 cf.setWidth((int)width);
287 log(pdebug, "Parsed: width -> " + width );
288 }
289
290 /**
291 * formatstmt -> 'format' quotedstring
292 */
293 private void formatStmt(ColumnFormat cf)
294 throws ParserException, IOException {
295 match(FORMAT);
296 String formatString = lookahead.sval;
297 matchQuotedString();
298 cf.setFormat(formatString);
299 log(pdebug, "Parsed: format -> " + formatString);
300 }
301
302 /**
303 * Primary -> Literal | Identifier | '(' Expression ')'
304 */
305 private Expression primary() throws ParserException, IOException {
306 Expression e = null;
307
308 switch (lookahead.ttype) {
309 case OPENPAREN:
310 match(OPENPAREN);
311 e = expression();
312 match(CLOSEPAREN);
313 break;
314 case StreamTokenizer.TT_WORD:
315 String s = lookahead.sval;
316 if (isReservedWord(s)) {
317 throw new SyntaxException(st.lineno(), "IDENTIFIER",
318 "Reserved Word: " + lookahead.sval);
319 }
320 matchID();
321 e = new Identifier(s);
322 log(pdebug, "Parsed: ID -> " + s);
323 break;
324 case StreamTokenizer.TT_NUMBER:
325 double literal = lookahead.nval;
326 matchNumber();
327 e = new Literal(new Double(literal));
328 log(pdebug, "Parsed: number -> " + literal);
329 break;
330 default:
331 throw new SyntaxException(st.lineno(), "IDENTIFIER", lookahead);
332 }
333 log(pdebug, "Parsed: primary -> " + e);
334 return e;
335 }
336
337 /**
338 * Unary -> ('+'|'-') Unary | Primary
339 */
340 private Expression unary() throws ParserException, IOException {
341 Expression e = null;
342 Operator op = null;
343
344 while (true) {
345 switch (lookahead.ttype) {
346 case OPERATOR_PLUS:
347 match(OPERATOR_PLUS);
348 op = Operator.PLUS;
349 break;
350 case OPERATOR_MINUS:
351 match(OPERATOR_MINUS);
352 op = Operator.MINUS;
353 break;
354 default:
355 e = primary();
356 log(pdebug, "Parsed: unary -> " + e);
357 return e;
358 }
359 Expression e1 = new Expression();
360 e1.setOperator(op);
361 e1.setRight(e);
362 log(pdebug, "Parsed: unary -> " + e1);
363 e1.setLeft(new Literal(new Double(0)));
364 e = e1;
365 }
366 }
367
368 /**
369 * MultExpression -> Unary (('*' | '/') Unary)*
370 */
371 private Expression multExpression() throws ParserException, IOException {
372 Expression e = unary();
373 Operator op = null;
374
375 while (true) {
376 switch (lookahead.ttype) {
377 case OPERATOR_MULTIPLY:
378 match(OPERATOR_MULTIPLY);
379 op = Operator.MULTIPLY;
380 break;
381 case OPERATOR_DIVIDE:
382 match(OPERATOR_DIVIDE);
383 op = Operator.DIVIDE;
384 break;
385 default:
386 log(pdebug, "Parsed: multExpression -> " + e);
387 return e;
388 }
389 Expression e1 = new Expression();
390 e1.setOperator(op);
391 e1.setLeft(e);
392 e1.setRight(unary());
393 e = e1;
394 log(pdebug, "Parsed: multExpression -> " + e);
395 }
396 }
397
398 /**
399 * AddExpression -> MultExpression (('+' | '-') MultExpression)*
400 */
401 private Expression addExpression() throws ParserException, IOException {
402 Expression e = multExpression();
403 Operator op = null;
404
405 while (true) {
406 switch (lookahead.ttype) {
407 case OPERATOR_PLUS:
408 match(OPERATOR_PLUS);
409 op = Operator.PLUS;
410 break;
411 case OPERATOR_MINUS:
412 match(OPERATOR_MINUS);
413 op = Operator.MINUS;
414 break;
415 default:
416 log(pdebug, "Parsed: addExpression -> " + e);
417 return e;
418 }
419 Expression e1 = new Expression();
420 e1.setOperator(op);
421 e1.setLeft(e);
422 e1.setRight(multExpression());
423 e = e1;
424 log(pdebug, "Parsed: addExpression -> " + e);
425 }
426 }
427
428 /**
429 * Expression -> AddExpression
430 */
431 private Expression expression() throws ParserException, IOException {
432 Expression e = addExpression();
433 log(pdebug, "Parsed: expression -> " + e);
434 return e;
435 }
436
437 /**
438 * datastmt -> 'data' expression
439 */
440 private void dataStmt(ColumnFormat cf) throws ParserException, IOException {
441 match(DATA);
442 Expression e = expression();
443 cf.setExpression(e);
444 log(pdebug, "Parsed: data -> " + e);
445 }
446
447 /**
448 * statementlist -> optionalstmt statementlist
449 * optionalstmt -> 'data' expression
450 * 'header' quotedstring
451 * 'width' integer
452 * 'format' formatstring
453 * 'align' alignspec
454 * 'scale' scalespec
455 */
456 private void statementList(ColumnFormat cf)
457 throws ParserException, IOException {
458 while (true) {
459 if (lookahead.ttype != StreamTokenizer.TT_WORD) {
460 return;
461 }
462
463 if (lookahead.sval.compareTo(DATA) == 0) {
464 dataStmt(cf);
465 } else if (lookahead.sval.compareTo(HEADER) == 0) {
466 headerStmt(cf);
467 } else if (lookahead.sval.compareTo(WIDTH) == 0) {
468 widthStmt(cf);
469 } else if (lookahead.sval.compareTo(FORMAT) == 0) {
470 formatStmt(cf);
471 } else if (lookahead.sval.compareTo(ALIGN) == 0) {
472 alignStmt(cf);
473 } else if (lookahead.sval.compareTo(SCALE) == 0) {
474 scaleStmt(cf);
475 } else {
476 return;
477 }
478 }
479 }
480
481 /**
482 * optionlist -> columspec optionlist
483 * null
484 * columspec -> 'column' '{' statementlist '}'
485 */
486 private void optionList(OptionFormat of)
487 throws ParserException, IOException {
488 while (true) {
489 if (lookahead.ttype != StreamTokenizer.TT_WORD) {
490 return;
491 }
492
493 match(COLUMN);
494 match(OPENBLOCK);
495 ColumnFormat cf = new ColumnFormat(columnCount++);
496 statementList(cf);
497 match(CLOSEBLOCK);
498 cf.validate();
499 of.addSubFormat(cf);
500 }
501 }
502
503 /**
504 * optionstmt -> 'option' ID '{' optionlist '}'
505 */
506 private OptionFormat optionStmt() throws ParserException, IOException {
507 match(OPTION);
508 String optionName=lookahead.sval;
509 matchID();
510 match(OPENBLOCK);
511 OptionFormat of = new OptionFormat(optionName);
512 optionList(of);
513 match(CLOSEBLOCK);
514 return of;
515 }
516
517 /**
518 * parse the specification for the given option identifier
519 */
520 public OptionFormat parse(String option)
521 throws ParserException, IOException {
522 nextToken();
523
524 /*
525 * this search stops on the first occurance of an option
526 * statement with a name matching the given option. Any
527 * duplicate options are ignored.
528 */
529 while (lookahead.ttype != StreamTokenizer.TT_EOF) {
530 // look for the start symbol
531 if ((lookahead.ttype != StreamTokenizer.TT_WORD)
532 || (lookahead.sval.compareTo(START) != 0)) {
533 // skip tokens until a start symbol is found
534 nextToken();
535 continue;
536 }
537
538 // check if the option name is the one we are interested in
539 match(START);
540
541 if ((lookahead.ttype == StreamTokenizer.TT_WORD)
542 && (lookahead.sval.compareTo(option) == 0)) {
543 // this is the one we are looking for, parse it
544 pushBack();
545 return optionStmt();
546 } else {
547 // not what we are looking for, start skipping tokens
548 nextToken();
549 }
550 }
551 return null;
552 }
553
554 public Set<OptionFormat> parseOptions() throws ParserException, IOException {
555 Set<OptionFormat> options = new HashSet<OptionFormat>();
556
557 nextToken();
558
559 while (lookahead.ttype != StreamTokenizer.TT_EOF) {
560 // look for the start symbol
561 if ((lookahead.ttype != StreamTokenizer.TT_WORD)
562 || (lookahead.sval.compareTo(START) != 0)) {
563 // skip tokens until a start symbol is found
564 nextToken();
565 continue;
566 }
567
568 // note: if a duplicate option statement exists, then
569 // first one encountered is the chosen definition.
570 OptionFormat of = optionStmt();
571 options.add(of);
572 }
573 return options;
574 }
575
576 OptionFormat getOptionFormat() {
577 return optionFormat;
578 }
579
580 private void log(boolean logging, String s) {
581 if (logging) {
582 System.out.println(s);
583 }
584 }
585}