1 /* 2 * Copyright (c) 2004, 2018, 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 private static final String REQUIRED = "required"; 64 65 private static final String START = OPTION; 66 67 private static final Set<String> scaleKeyWords = Scale.keySet(); 68 private static final Set<String> alignKeyWords = Alignment.keySet(); 69 private static final Set<String> boolKeyWords = Set.of("true", "false"); 70 private static String[] otherKeyWords = { 71 OPTION, COLUMN, DATA, HEADER, WIDTH, FORMAT, ALIGN, SCALE, REQUIRED 72 }; 73 74 private static char[] infixOps = { 75 OPERATOR_PLUS, OPERATOR_MINUS, OPERATOR_MULTIPLY, OPERATOR_DIVIDE 76 }; 77 78 private static char[] delimiters = { 79 OPENBLOCK, CLOSEBLOCK, PERCENT_CHAR, OPENPAREN, CLOSEPAREN 80 }; 81 82 83 private static Set<String> reservedWords; 84 85 private StreamTokenizer st; 86 private String filename; 87 private Token lookahead; 88 private Token previous; 89 private int columnCount; 90 private OptionFormat optionFormat; 91 92 public Parser(String filename) throws FileNotFoundException { 93 this.filename = filename; 94 Reader r = new BufferedReader(new FileReader(filename)); 95 } 96 97 public Parser(Reader r) { 98 st = new StreamTokenizer(r); 99 100 // allow both c++ style comments 101 st.ordinaryChar('/'); 102 st.wordChars('_','_'); 103 st.slashSlashComments(true); 104 st.slashStarComments(true); 105 106 reservedWords = new HashSet<String>(); 107 for (int i = 0; i < otherKeyWords.length; i++) { 108 reservedWords.add(otherKeyWords[i]); 109 } 110 111 for (int i = 0; i < delimiters.length; i++ ) { 112 st.ordinaryChar(delimiters[i]); 113 } 114 115 for (int i = 0; i < infixOps.length; i++ ) { 116 st.ordinaryChar(infixOps[i]); 117 } 118 } 119 120 /** 121 * push back the lookahead token and restore the lookahead token 122 * to the previous token. 123 */ 124 private void pushBack() { 125 lookahead = previous; 126 st.pushBack(); 127 } 128 129 /** 130 * retrieve the next token, placing the token value in the lookahead 131 * member variable, storing its previous value in the previous member 132 * variable. 133 */ 134 private void nextToken() throws ParserException, IOException { 135 int t = st.nextToken(); 136 previous = lookahead; 137 lookahead = new Token(st.ttype, st.sval, st.nval); 138 log(ldebug, "lookahead = " + lookahead); 139 } 140 141 /** 142 * match one of the token values in the given set of key words 143 * token is assumed to be of type TT_WORD, and the set is assumed 144 * to contain String objects. 145 */ 146 private Token matchOne(Set<String> keyWords) throws ParserException, IOException { 147 if ((lookahead.ttype == StreamTokenizer.TT_WORD) 148 && keyWords.contains(lookahead.sval)) { 149 Token t = lookahead; 150 nextToken(); 151 return t; 152 } 153 throw new SyntaxException(st.lineno(), keyWords, lookahead); 154 } 155 156 /** 157 * match a token with TT_TYPE=type, and the token value is a given sequence 158 * of characters. 159 */ 160 private void match(int ttype, String token) 161 throws ParserException, IOException { 162 if (lookahead.ttype == ttype && lookahead.sval.compareTo(token) == 0) { 163 nextToken(); 164 } else { 165 throw new SyntaxException(st.lineno(), new Token(ttype, token), 166 lookahead); 167 } 168 } 169 170 /** 171 * match a token with TT_TYPE=type 172 */ 173 private void match(int ttype) throws ParserException, IOException { 174 if (lookahead.ttype == ttype) { 175 nextToken(); 176 } else { 177 throw new SyntaxException(st.lineno(), new Token(ttype), 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 } 188 else { 189 throw new SyntaxException(st.lineno(), new Token((int)ttype), 190 lookahead); 191 } 192 } 193 194 /** 195 * match a token with TT_TYPE='"', where the token value is a sequence 196 * of characters between matching quote characters. 197 */ 198 private void matchQuotedString() throws ParserException, 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, IOException { 220 match(StreamTokenizer.TT_WORD, token); 221 } 222 223 /** 224 * determine if the given word is a reserved key word 225 */ 226 private boolean isReservedWord(String word) { 227 return reservedWords.contains(word); 228 } 229 230 /** 231 * determine if the give work is a reserved key word 232 */ 233 private boolean isInfixOperator(char op) { 234 for (int i = 0; i < infixOps.length; i++) { 235 if (op == infixOps[i]) { 236 return true; 237 } 238 } 239 return false; 240 } 241 242 /** 243 * scalestmt -> 'scale' scalespec 244 * scalespec -> <see above scaleTerminals array> 245 */ 246 private void scaleStmt(ColumnFormat cf) 247 throws ParserException, IOException { 248 match(SCALE); 249 Token t = matchOne(scaleKeyWords); 250 cf.setScale(Scale.toScale(t.sval)); 251 String scaleString = t.sval; 252 log(pdebug, "Parsed: scale -> " + scaleString); 253 } 254 255 /** 256 * alignstmt -> 'align' alignspec 257 * alignspec -> <see above alignTerminals array> 258 */ 259 private void alignStmt(ColumnFormat cf) 260 throws ParserException, IOException { 261 match(ALIGN); 262 Token t = matchOne(alignKeyWords); 263 cf.setAlignment(Alignment.toAlignment(t.sval)); 264 String alignString = t.sval; 265 log(pdebug, "Parsed: align -> " + alignString); 266 } 267 268 /** 269 * headerstmt -> 'header' quotedstring 270 */ 271 private void headerStmt(ColumnFormat cf) 272 throws ParserException, IOException { 273 match(HEADER); 274 String headerString = lookahead.sval; 275 matchQuotedString(); 276 cf.setHeader(headerString); 277 log(pdebug, "Parsed: header -> " + headerString); 278 } 279 280 /** 281 * widthstmt -> 'width' integer 282 */ 283 private void widthStmt(ColumnFormat cf) 284 throws ParserException, IOException { 285 match(WIDTH); 286 double width = lookahead.nval; 287 matchNumber(); 288 cf.setWidth((int)width); 289 log(pdebug, "Parsed: width -> " + width ); 290 } 291 292 /** 293 * formatstmt -> 'format' quotedstring 294 */ 295 private void formatStmt(ColumnFormat cf) 296 throws ParserException, IOException { 297 match(FORMAT); 298 String formatString = lookahead.sval; 299 matchQuotedString(); 300 cf.setFormat(formatString); 301 log(pdebug, "Parsed: format -> " + formatString); 302 } 303 304 /** 305 * Primary -> Literal | Identifier | '(' Expression ')' 306 */ 307 private Expression primary() throws ParserException, IOException { 308 Expression e = null; 309 310 switch (lookahead.ttype) { 311 case OPENPAREN: 312 match(OPENPAREN); 313 e = expression(); 314 match(CLOSEPAREN); 315 break; 316 case StreamTokenizer.TT_WORD: 317 String s = lookahead.sval; 318 if (isReservedWord(s)) { 319 throw new SyntaxException(st.lineno(), "IDENTIFIER", 320 "Reserved Word: " + lookahead.sval); 321 } 322 matchID(); 323 e = new Identifier(s); 324 log(pdebug, "Parsed: ID -> " + s); 325 break; 326 case StreamTokenizer.TT_NUMBER: 327 double literal = lookahead.nval; 328 matchNumber(); 329 e = new Literal(Double.valueOf(literal)); 330 log(pdebug, "Parsed: number -> " + literal); 331 break; 332 default: 333 throw new SyntaxException(st.lineno(), "IDENTIFIER", lookahead); 334 } 335 log(pdebug, "Parsed: primary -> " + e); 336 return e; 337 } 338 339 /** 340 * Unary -> ('+'|'-') Unary | Primary 341 */ 342 private Expression unary() throws ParserException, IOException { 343 Expression e = null; 344 Operator op = null; 345 346 while (true) { 347 switch (lookahead.ttype) { 348 case OPERATOR_PLUS: 349 match(OPERATOR_PLUS); 350 op = Operator.PLUS; 351 break; 352 case OPERATOR_MINUS: 353 match(OPERATOR_MINUS); 354 op = Operator.MINUS; 355 break; 356 default: 357 e = primary(); 358 log(pdebug, "Parsed: unary -> " + e); 359 return e; 360 } 361 Expression e1 = new Expression(); 362 e1.setOperator(op); 363 e1.setRight(e); 364 log(pdebug, "Parsed: unary -> " + e1); 365 e1.setLeft(new Literal(Double.valueOf(0))); 366 e = e1; 367 } 368 } 369 370 /** 371 * MultExpression -> Unary (('*' | '/') Unary)* 372 */ 373 private Expression multExpression() throws ParserException, IOException { 374 Expression e = unary(); 375 Operator op = null; 376 377 while (true) { 378 switch (lookahead.ttype) { 379 case OPERATOR_MULTIPLY: 380 match(OPERATOR_MULTIPLY); 381 op = Operator.MULTIPLY; 382 break; 383 case OPERATOR_DIVIDE: 384 match(OPERATOR_DIVIDE); 385 op = Operator.DIVIDE; 386 break; 387 default: 388 log(pdebug, "Parsed: multExpression -> " + e); 389 return e; 390 } 391 Expression e1 = new Expression(); 392 e1.setOperator(op); 393 e1.setLeft(e); 394 e1.setRight(unary()); 395 e = e1; 396 log(pdebug, "Parsed: multExpression -> " + e); 397 } 398 } 399 400 /** 401 * AddExpression -> MultExpression (('+' | '-') MultExpression)* 402 */ 403 private Expression addExpression() throws ParserException, IOException { 404 Expression e = multExpression(); 405 Operator op = null; 406 407 while (true) { 408 switch (lookahead.ttype) { 409 case OPERATOR_PLUS: 410 match(OPERATOR_PLUS); 411 op = Operator.PLUS; 412 break; 413 case OPERATOR_MINUS: 414 match(OPERATOR_MINUS); 415 op = Operator.MINUS; 416 break; 417 default: 418 log(pdebug, "Parsed: addExpression -> " + e); 419 return e; 420 } 421 Expression e1 = new Expression(); 422 e1.setOperator(op); 423 e1.setLeft(e); 424 e1.setRight(multExpression()); 425 e = e1; 426 log(pdebug, "Parsed: addExpression -> " + e); 427 } 428 } 429 430 /** 431 * Expression -> AddExpression 432 */ 433 private Expression expression() throws ParserException, IOException { 434 Expression e = addExpression(); 435 log(pdebug, "Parsed: expression -> " + e); 436 return e; 437 } 438 439 /** 440 * datastmt -> 'data' expression 441 */ 442 private void dataStmt(ColumnFormat cf) throws ParserException, IOException { 443 match(DATA); 444 Expression e = expression(); 445 cf.setExpression(e); 446 log(pdebug, "Parsed: data -> " + e); 447 } 448 449 /** 450 * requiredstmt -> 'required' expression 451 */ 452 private void requiredStmt(ColumnFormat cf) throws ParserException, IOException { 453 match(REQUIRED); 454 Token t = matchOne(boolKeyWords); 455 cf.setRequired(Boolean.parseBoolean(t.sval)); 456 log(pdebug, "Parsed: required -> " + cf.isRequired()); 457 } 458 459 /** 460 * statementlist -> optionalstmt statementlist 461 * optionalstmt -> 'data' expression 462 * 'header' quotedstring 463 * 'width' integer 464 * 'format' formatstring 465 * 'align' alignspec 466 * 'scale' scalespec 467 * 'required' boolean 468 */ 469 private void statementList(ColumnFormat cf) 470 throws ParserException, IOException { 471 while (true) { 472 if (lookahead.ttype != StreamTokenizer.TT_WORD) { 473 return; 474 } 475 476 if (lookahead.sval.compareTo(DATA) == 0) { 477 dataStmt(cf); 478 } else if (lookahead.sval.compareTo(HEADER) == 0) { 479 headerStmt(cf); 480 } else if (lookahead.sval.compareTo(WIDTH) == 0) { 481 widthStmt(cf); 482 } else if (lookahead.sval.compareTo(FORMAT) == 0) { 483 formatStmt(cf); 484 } else if (lookahead.sval.compareTo(ALIGN) == 0) { 485 alignStmt(cf); 486 } else if (lookahead.sval.compareTo(SCALE) == 0) { 487 scaleStmt(cf); 488 } else if (lookahead.sval.compareTo(REQUIRED) == 0) { 489 requiredStmt(cf); 490 } else { 491 return; 492 } 493 } 494 } 495 496 /** 497 * optionlist -> columspec optionlist 498 * null 499 * columspec -> 'column' '{' statementlist '}' 500 */ 501 private void optionList(OptionFormat of) 502 throws ParserException, IOException { 503 while (true) { 504 if (lookahead.ttype != StreamTokenizer.TT_WORD) { 505 return; 506 } 507 508 match(COLUMN); 509 match(OPENBLOCK); 510 ColumnFormat cf = new ColumnFormat(columnCount++); 511 statementList(cf); 512 match(CLOSEBLOCK); 513 cf.validate(); 514 of.addSubFormat(cf); 515 } 516 } 517 518 /** 519 * optionstmt -> 'option' ID '{' optionlist '}' 520 */ 521 private OptionFormat optionStmt() throws ParserException, IOException { 522 match(OPTION); 523 String optionName=lookahead.sval; 524 matchID(); 525 match(OPENBLOCK); 526 OptionFormat of = new OptionFormat(optionName); 527 optionList(of); 528 match(CLOSEBLOCK); 529 return of; 530 } 531 532 /** 533 * parse the specification for the given option identifier 534 */ 535 public OptionFormat parse(String option) 536 throws ParserException, IOException { 537 nextToken(); 538 539 /* 540 * this search stops on the first occurance of an option 541 * statement with a name matching the given option. Any 542 * duplicate options are ignored. 543 */ 544 while (lookahead.ttype != StreamTokenizer.TT_EOF) { 545 // look for the start symbol 546 if ((lookahead.ttype != StreamTokenizer.TT_WORD) 547 || (lookahead.sval.compareTo(START) != 0)) { 548 // skip tokens until a start symbol is found 549 nextToken(); 550 continue; 551 } 552 553 // check if the option name is the one we are interested in 554 match(START); 555 556 if ((lookahead.ttype == StreamTokenizer.TT_WORD) 557 && (lookahead.sval.compareTo(option) == 0)) { 558 // this is the one we are looking for, parse it 559 pushBack(); 560 return optionStmt(); 561 } else { 562 // not what we are looking for, start skipping tokens 563 nextToken(); 564 } 565 } 566 return null; 567 } 568 569 public Set<OptionFormat> parseOptions() throws ParserException, IOException { 570 Set<OptionFormat> options = new HashSet<OptionFormat>(); 571 572 nextToken(); 573 574 while (lookahead.ttype != StreamTokenizer.TT_EOF) { 575 // look for the start symbol 576 if ((lookahead.ttype != StreamTokenizer.TT_WORD) 577 || (lookahead.sval.compareTo(START) != 0)) { 578 // skip tokens until a start symbol is found 579 nextToken(); 580 continue; 581 } 582 583 // note: if a duplicate option statement exists, then 584 // first one encountered is the chosen definition. 585 OptionFormat of = optionStmt(); 586 options.add(of); 587 } 588 return options; 589 } 590 591 OptionFormat getOptionFormat() { 592 return optionFormat; 593 } 594 595 private void log(boolean logging, String s) { 596 if (logging) { 597 System.out.println(s); 598 } 599 } 600 }