1 /*
2  * Copyright (c) 2004, Oracle and/or its affiliates. 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.tools.jstat;
27 
28 import java.io.*;
29 import 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  */
38 public 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 
Parser(String filename)90     public Parser(String filename) throws FileNotFoundException {
91         this.filename = filename;
92         Reader r = new BufferedReader(new FileReader(filename));
93     }
94 
Parser(Reader r)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      */
pushBack()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      */
nextToken()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      */
matchOne(Set keyWords)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      */
match(int ttype, String token)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      */
match(int ttype)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      */
match(char ttype)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      */
matchQuotedString()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      */
matchNumber()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      */
matchID()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      */
match(String token)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      */
isReservedWord(String word)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      */
isInfixOperator(char op)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      */
scaleStmt(ColumnFormat cf)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      */
alignStmt(ColumnFormat cf)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      */
headerStmt(ColumnFormat cf)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      */
widthStmt(ColumnFormat cf)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      */
formatStmt(ColumnFormat cf)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      */
primary()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      */
unary()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      */
multExpression()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      */
addExpression()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      */
expression()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      */
dataStmt(ColumnFormat cf)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      */
statementList(ColumnFormat cf)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      */
optionList(OptionFormat of)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      */
optionStmt()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      */
parse(String option)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 
parseOptions()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 
getOptionFormat()576     OptionFormat getOptionFormat() {
577        return optionFormat;
578     }
579 
log(boolean logging, String s)580     private void log(boolean logging, String s) {
581         if (logging) {
582             System.out.println(s);
583         }
584     }
585 }
586