001: /*
002: * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.tools.jstat;
027:
028: import java.io.*;
029: import java.util.*;
030:
031: /**
032: * A class implementing a simple predictive parser for output format
033: * specification language for the jstat command.
034: *
035: * @author Brian Doherty
036: * @version 1.9, 05/09/07
037: * @since 1.5
038: */
039: public class Parser {
040:
041: private static boolean pdebug = Boolean
042: .getBoolean("jstat.parser.debug");
043: private static boolean ldebug = Boolean
044: .getBoolean("jstat.lex.debug");
045:
046: private static final char OPENBLOCK = '{';
047: private static final char CLOSEBLOCK = '}';
048: private static final char DOUBLEQUOTE = '"';
049: private static final char PERCENT_CHAR = '%';
050: private static final char OPENPAREN = '(';
051: private static final char CLOSEPAREN = ')';
052:
053: private static final char OPERATOR_PLUS = '+';
054: private static final char OPERATOR_MINUS = '-';
055: private static final char OPERATOR_MULTIPLY = '*';
056: private static final char OPERATOR_DIVIDE = '/';
057:
058: private static final String OPTION = "option";
059: private static final String COLUMN = "column";
060: private static final String DATA = "data";
061: private static final String HEADER = "header";
062: private static final String WIDTH = "width";
063: private static final String FORMAT = "format";
064: private static final String ALIGN = "align";
065: private static final String SCALE = "scale";
066:
067: private static final String START = OPTION;
068:
069: private static final Set scaleKeyWords = Scale.keySet();
070: private static final Set alignKeyWords = Alignment.keySet();
071: private static String[] otherKeyWords = { OPTION, COLUMN, DATA,
072: HEADER, WIDTH, FORMAT, ALIGN, SCALE };
073:
074: private static char[] infixOps = { OPERATOR_PLUS, OPERATOR_MINUS,
075: OPERATOR_MULTIPLY, OPERATOR_DIVIDE };
076:
077: private static char[] delimiters = { OPENBLOCK, CLOSEBLOCK,
078: PERCENT_CHAR, OPENPAREN, CLOSEPAREN };
079:
080: private static Set<String> reservedWords;
081:
082: private StreamTokenizer st;
083: private String filename;
084: private Token lookahead;
085: private Token previous;
086: private int columnCount;
087: private OptionFormat optionFormat;
088:
089: public Parser(String filename) throws FileNotFoundException {
090: this .filename = filename;
091: Reader r = new BufferedReader(new FileReader(filename));
092: }
093:
094: public Parser(Reader r) {
095: st = new StreamTokenizer(r);
096:
097: // allow both c++ style comments
098: st.ordinaryChar('/');
099: st.wordChars('_', '_');
100: st.slashSlashComments(true);
101: st.slashStarComments(true);
102:
103: reservedWords = new HashSet<String>();
104: for (int i = 0; i < otherKeyWords.length; i++) {
105: reservedWords.add(otherKeyWords[i]);
106: }
107:
108: for (int i = 0; i < delimiters.length; i++) {
109: st.ordinaryChar(delimiters[i]);
110: }
111:
112: for (int i = 0; i < infixOps.length; i++) {
113: st.ordinaryChar(infixOps[i]);
114: }
115: }
116:
117: /**
118: * push back the lookahead token and restore the lookahead token
119: * to the previous token.
120: */
121: private void pushBack() {
122: lookahead = previous;
123: st.pushBack();
124: }
125:
126: /**
127: * retrieve the next token, placing the token value in the lookahead
128: * member variable, storing its previous value in the previous member
129: * variable.
130: */
131: private void nextToken() throws ParserException, IOException {
132: int t = st.nextToken();
133: previous = lookahead;
134: lookahead = new Token(st.ttype, st.sval, st.nval);
135: log(ldebug, "lookahead = " + lookahead);
136: }
137:
138: /**
139: * match one of the token values in the given set of key words
140: * token is assumed to be of type TT_WORD, and the set is assumed
141: * to contain String objects.
142: */
143: private Token matchOne(Set keyWords) throws ParserException,
144: 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) throws ParserException,
159: IOException {
160: if (lookahead.ttype == ttype
161: && lookahead.sval.compareTo(token) == 0) {
162: nextToken();
163: } else {
164: throw new SyntaxException(st.lineno(), new Token(ttype,
165: token), lookahead);
166: }
167: }
168:
169: /**
170: * match a token with TT_TYPE=type
171: */
172: private void match(int ttype) throws ParserException, IOException {
173: if (lookahead.ttype == ttype) {
174: nextToken();
175: } else {
176: throw new SyntaxException(st.lineno(), new Token(ttype),
177: lookahead);
178: }
179: }
180:
181: /**
182: * match a token with TT_TYPE=char, where the token value is the given char.
183: */
184: private void match(char ttype) throws ParserException, IOException {
185: if (lookahead.ttype == (int) ttype) {
186: nextToken();
187: } else {
188: throw new SyntaxException(st.lineno(), new Token(
189: (int) ttype), lookahead);
190: }
191: }
192:
193: /**
194: * match a token with TT_TYPE='"', where the token value is a sequence
195: * of characters between matching quote characters.
196: */
197: private void matchQuotedString() throws ParserException,
198: IOException {
199: match(DOUBLEQUOTE);
200: }
201:
202: /**
203: * match a TT_NUMBER token that matches a parsed number value
204: */
205: private void matchNumber() throws ParserException, IOException {
206: match(StreamTokenizer.TT_NUMBER);
207: }
208:
209: /**
210: * match a TT_WORD token that matches an arbitrary, not quoted token.
211: */
212: private void matchID() throws ParserException, IOException {
213: match(StreamTokenizer.TT_WORD);
214: }
215:
216: /**
217: * match a TT_WORD token that matches the given string
218: */
219: private void match(String token) throws ParserException,
220: IOException {
221: match(StreamTokenizer.TT_WORD, token);
222: }
223:
224: /**
225: * determine if the given word is a reserved key word
226: */
227: private boolean isReservedWord(String word) {
228: return reservedWords.contains(word);
229: }
230:
231: /**
232: * determine if the give work is a reserved key word
233: */
234: private boolean isInfixOperator(char op) {
235: for (int i = 0; i < infixOps.length; i++) {
236: if (op == infixOps[i]) {
237: return true;
238: }
239: }
240: return false;
241: }
242:
243: /**
244: * scalestmt -> 'scale' scalespec
245: * scalespec -> <see above scaleTerminals array>
246: */
247: private void scaleStmt(ColumnFormat cf) throws ParserException,
248: IOException {
249: match(SCALE);
250: Token t = matchOne(scaleKeyWords);
251: cf.setScale(Scale.toScale(t.sval));
252: String scaleString = t.sval;
253: log(pdebug, "Parsed: scale -> " + scaleString);
254: }
255:
256: /**
257: * alignstmt -> 'align' alignspec
258: * alignspec -> <see above alignTerminals array>
259: */
260: private void alignStmt(ColumnFormat cf) throws ParserException,
261: IOException {
262: match(ALIGN);
263: Token t = matchOne(alignKeyWords);
264: cf.setAlignment(Alignment.toAlignment(t.sval));
265: String alignString = t.sval;
266: log(pdebug, "Parsed: align -> " + alignString);
267: }
268:
269: /**
270: * headerstmt -> 'header' quotedstring
271: */
272: private void headerStmt(ColumnFormat cf) throws ParserException,
273: IOException {
274: match(HEADER);
275: String headerString = lookahead.sval;
276: matchQuotedString();
277: cf.setHeader(headerString);
278: log(pdebug, "Parsed: header -> " + headerString);
279: }
280:
281: /**
282: * widthstmt -> 'width' integer
283: */
284: private void widthStmt(ColumnFormat cf) throws ParserException,
285: IOException {
286: match(WIDTH);
287: double width = lookahead.nval;
288: matchNumber();
289: cf.setWidth((int) width);
290: log(pdebug, "Parsed: width -> " + width);
291: }
292:
293: /**
294: * formatstmt -> 'format' quotedstring
295: */
296: private void formatStmt(ColumnFormat cf) throws ParserException,
297: IOException {
298: match(FORMAT);
299: String formatString = lookahead.sval;
300: matchQuotedString();
301: cf.setFormat(formatString);
302: log(pdebug, "Parsed: format -> " + formatString);
303: }
304:
305: /**
306: * Primary -> Literal | Identifier | '(' Expression ')'
307: */
308: private Expression primary() throws ParserException, IOException {
309: Expression e = null;
310:
311: switch (lookahead.ttype) {
312: case OPENPAREN:
313: match(OPENPAREN);
314: e = expression();
315: match(CLOSEPAREN);
316: break;
317: case StreamTokenizer.TT_WORD:
318: String s = lookahead.sval;
319: if (isReservedWord(s)) {
320: throw new SyntaxException(st.lineno(), "IDENTIFIER",
321: "Reserved Word: " + lookahead.sval);
322: }
323: matchID();
324: e = new Identifier(s);
325: log(pdebug, "Parsed: ID -> " + s);
326: break;
327: case StreamTokenizer.TT_NUMBER:
328: double literal = lookahead.nval;
329: matchNumber();
330: e = new Literal(new Double(literal));
331: log(pdebug, "Parsed: number -> " + literal);
332: break;
333: default:
334: throw new SyntaxException(st.lineno(), "IDENTIFIER",
335: lookahead);
336: }
337: log(pdebug, "Parsed: primary -> " + e);
338: return e;
339: }
340:
341: /**
342: * Unary -> ('+'|'-') Unary | Primary
343: */
344: private Expression unary() throws ParserException, IOException {
345: Expression e = null;
346: Operator op = null;
347:
348: while (true) {
349: switch (lookahead.ttype) {
350: case OPERATOR_PLUS:
351: match(OPERATOR_PLUS);
352: op = Operator.PLUS;
353: break;
354: case OPERATOR_MINUS:
355: match(OPERATOR_MINUS);
356: op = Operator.MINUS;
357: break;
358: default:
359: e = primary();
360: log(pdebug, "Parsed: unary -> " + e);
361: return e;
362: }
363: Expression e1 = new Expression();
364: e1.setOperator(op);
365: e1.setRight(e);
366: log(pdebug, "Parsed: unary -> " + e1);
367: e1.setLeft(new Literal(new Double(0)));
368: e = e1;
369: }
370: }
371:
372: /**
373: * MultExpression -> Unary (('*' | '/') Unary)*
374: */
375: private Expression multExpression() throws ParserException,
376: IOException {
377: Expression e = unary();
378: Operator op = null;
379:
380: while (true) {
381: switch (lookahead.ttype) {
382: case OPERATOR_MULTIPLY:
383: match(OPERATOR_MULTIPLY);
384: op = Operator.MULTIPLY;
385: break;
386: case OPERATOR_DIVIDE:
387: match(OPERATOR_DIVIDE);
388: op = Operator.DIVIDE;
389: break;
390: default:
391: log(pdebug, "Parsed: multExpression -> " + e);
392: return e;
393: }
394: Expression e1 = new Expression();
395: e1.setOperator(op);
396: e1.setLeft(e);
397: e1.setRight(unary());
398: e = e1;
399: log(pdebug, "Parsed: multExpression -> " + e);
400: }
401: }
402:
403: /**
404: * AddExpression -> MultExpression (('+' | '-') MultExpression)*
405: */
406: private Expression addExpression() throws ParserException,
407: IOException {
408: Expression e = multExpression();
409: Operator op = null;
410:
411: while (true) {
412: switch (lookahead.ttype) {
413: case OPERATOR_PLUS:
414: match(OPERATOR_PLUS);
415: op = Operator.PLUS;
416: break;
417: case OPERATOR_MINUS:
418: match(OPERATOR_MINUS);
419: op = Operator.MINUS;
420: break;
421: default:
422: log(pdebug, "Parsed: addExpression -> " + e);
423: return e;
424: }
425: Expression e1 = new Expression();
426: e1.setOperator(op);
427: e1.setLeft(e);
428: e1.setRight(multExpression());
429: e = e1;
430: log(pdebug, "Parsed: addExpression -> " + e);
431: }
432: }
433:
434: /**
435: * Expression -> AddExpression
436: */
437: private Expression expression() throws ParserException, IOException {
438: Expression e = addExpression();
439: log(pdebug, "Parsed: expression -> " + e);
440: return e;
441: }
442:
443: /**
444: * datastmt -> 'data' expression
445: */
446: private void dataStmt(ColumnFormat cf) throws ParserException,
447: IOException {
448: match(DATA);
449: Expression e = expression();
450: cf.setExpression(e);
451: log(pdebug, "Parsed: data -> " + e);
452: }
453:
454: /**
455: * statementlist -> optionalstmt statementlist
456: * optionalstmt -> 'data' expression
457: * 'header' quotedstring
458: * 'width' integer
459: * 'format' formatstring
460: * 'align' alignspec
461: * 'scale' scalespec
462: */
463: private void statementList(ColumnFormat cf) throws ParserException,
464: IOException {
465: while (true) {
466: if (lookahead.ttype != StreamTokenizer.TT_WORD) {
467: return;
468: }
469:
470: if (lookahead.sval.compareTo(DATA) == 0) {
471: dataStmt(cf);
472: } else if (lookahead.sval.compareTo(HEADER) == 0) {
473: headerStmt(cf);
474: } else if (lookahead.sval.compareTo(WIDTH) == 0) {
475: widthStmt(cf);
476: } else if (lookahead.sval.compareTo(FORMAT) == 0) {
477: formatStmt(cf);
478: } else if (lookahead.sval.compareTo(ALIGN) == 0) {
479: alignStmt(cf);
480: } else if (lookahead.sval.compareTo(SCALE) == 0) {
481: scaleStmt(cf);
482: } else {
483: return;
484: }
485: }
486: }
487:
488: /**
489: * optionlist -> columspec optionlist
490: * null
491: * columspec -> 'column' '{' statementlist '}'
492: */
493: private void optionList(OptionFormat of) throws ParserException,
494: IOException {
495: while (true) {
496: if (lookahead.ttype != StreamTokenizer.TT_WORD) {
497: return;
498: }
499:
500: match(COLUMN);
501: match(OPENBLOCK);
502: ColumnFormat cf = new ColumnFormat(columnCount++);
503: statementList(cf);
504: match(CLOSEBLOCK);
505: cf.validate();
506: of.addSubFormat(cf);
507: }
508: }
509:
510: /**
511: * optionstmt -> 'option' ID '{' optionlist '}'
512: */
513: private OptionFormat optionStmt() throws ParserException,
514: IOException {
515: match(OPTION);
516: String optionName = lookahead.sval;
517: matchID();
518: match(OPENBLOCK);
519: OptionFormat of = new OptionFormat(optionName);
520: optionList(of);
521: match(CLOSEBLOCK);
522: return of;
523: }
524:
525: /**
526: * parse the specification for the given option identifier
527: */
528: public OptionFormat parse(String option) throws ParserException,
529: IOException {
530: nextToken();
531:
532: /*
533: * this search stops on the first occurance of an option
534: * statement with a name matching the given option. Any
535: * duplicate options are ignored.
536: */
537: while (lookahead.ttype != StreamTokenizer.TT_EOF) {
538: // look for the start symbol
539: if ((lookahead.ttype != StreamTokenizer.TT_WORD)
540: || (lookahead.sval.compareTo(START) != 0)) {
541: // skip tokens until a start symbol is found
542: nextToken();
543: continue;
544: }
545:
546: // check if the option name is the one we are interested in
547: match(START);
548:
549: if ((lookahead.ttype == StreamTokenizer.TT_WORD)
550: && (lookahead.sval.compareTo(option) == 0)) {
551: // this is the one we are looking for, parse it
552: pushBack();
553: return optionStmt();
554: } else {
555: // not what we are looking for, start skipping tokens
556: nextToken();
557: }
558: }
559: return null;
560: }
561:
562: public Set<OptionFormat> parseOptions() throws ParserException,
563: IOException {
564: Set<OptionFormat> options = new HashSet<OptionFormat>();
565:
566: nextToken();
567:
568: while (lookahead.ttype != StreamTokenizer.TT_EOF) {
569: // look for the start symbol
570: if ((lookahead.ttype != StreamTokenizer.TT_WORD)
571: || (lookahead.sval.compareTo(START) != 0)) {
572: // skip tokens until a start symbol is found
573: nextToken();
574: continue;
575: }
576:
577: // note: if a duplicate option statement exists, then
578: // first one encountered is the chosen definition.
579: OptionFormat of = optionStmt();
580: options.add(of);
581: }
582: return options;
583: }
584:
585: OptionFormat getOptionFormat() {
586: return optionFormat;
587: }
588:
589: private void log(boolean logging, String s) {
590: if (logging) {
591: System.out.println(s);
592: }
593: }
594: }
|