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<String> scaleKeyWords = Scale.keySet();
  67     private static final Set<String> 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<String> 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(Double.valueOf(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(Double.valueOf(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 }