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 }