Path: blob/master/src/jdk.jcmd/share/classes/sun/tools/jstat/Parser.java
41159 views
/*1* Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.tools.jstat;2627import java.io.*;28import java.util.*;2930/**31* A class implementing a simple predictive parser for output format32* specification language for the jstat command.33*34* @author Brian Doherty35* @since 1.536*/37public class Parser {3839private static boolean pdebug = Boolean.getBoolean("jstat.parser.debug");40private static boolean ldebug = Boolean.getBoolean("jstat.lex.debug");4142private static final char OPENBLOCK = '{';43private static final char CLOSEBLOCK = '}';44private static final char DOUBLEQUOTE = '"';45private static final char PERCENT_CHAR = '%';46private static final char OPENPAREN = '(';47private static final char CLOSEPAREN = ')';4849private static final char OPERATOR_PLUS = '+';50private static final char OPERATOR_MINUS = '-';51private static final char OPERATOR_MULTIPLY = '*';52private static final char OPERATOR_DIVIDE = '/';5354private static final String OPTION = "option";55private static final String COLUMN = "column";56private static final String DATA = "data";57private static final String HEADER = "header";58private static final String WIDTH = "width";59private static final String FORMAT = "format";60private static final String ALIGN = "align";61private static final String SCALE = "scale";62private static final String REQUIRED = "required";6364private static final String START = OPTION;6566private static final Set<String> scaleKeyWords = Scale.keySet();67private static final Set<String> alignKeyWords = Alignment.keySet();68private static final Set<String> boolKeyWords = Set.of("true", "false");69private static String[] otherKeyWords = {70OPTION, COLUMN, DATA, HEADER, WIDTH, FORMAT, ALIGN, SCALE, REQUIRED71};7273private static char[] infixOps = {74OPERATOR_PLUS, OPERATOR_MINUS, OPERATOR_MULTIPLY, OPERATOR_DIVIDE75};7677private static char[] delimiters = {78OPENBLOCK, CLOSEBLOCK, PERCENT_CHAR, OPENPAREN, CLOSEPAREN79};808182private static Set<String> reservedWords;8384private StreamTokenizer st;85private String filename;86private Token lookahead;87private Token previous;88private int columnCount;89private OptionFormat optionFormat;9091public Parser(String filename) throws FileNotFoundException {92this.filename = filename;93Reader r = new BufferedReader(new FileReader(filename));94}9596public Parser(Reader r) {97st = new StreamTokenizer(r);9899// allow both c++ style comments100st.ordinaryChar('/');101st.wordChars('_','_');102st.slashSlashComments(true);103st.slashStarComments(true);104105reservedWords = new HashSet<String>();106for (int i = 0; i < otherKeyWords.length; i++) {107reservedWords.add(otherKeyWords[i]);108}109110for (int i = 0; i < delimiters.length; i++ ) {111st.ordinaryChar(delimiters[i]);112}113114for (int i = 0; i < infixOps.length; i++ ) {115st.ordinaryChar(infixOps[i]);116}117}118119/**120* push back the lookahead token and restore the lookahead token121* to the previous token.122*/123private void pushBack() {124lookahead = previous;125st.pushBack();126}127128/**129* retrieve the next token, placing the token value in the lookahead130* member variable, storing its previous value in the previous member131* variable.132*/133private void nextToken() throws ParserException, IOException {134int t = st.nextToken();135previous = lookahead;136lookahead = new Token(st.ttype, st.sval, st.nval);137log(ldebug, "lookahead = " + lookahead);138}139140/**141* match one of the token values in the given set of key words142* token is assumed to be of type TT_WORD, and the set is assumed143* to contain String objects.144*/145private Token matchOne(Set<String> keyWords) throws ParserException, IOException {146if ((lookahead.ttype == StreamTokenizer.TT_WORD)147&& keyWords.contains(lookahead.sval)) {148Token t = lookahead;149nextToken();150return t;151}152throw new SyntaxException(st.lineno(), keyWords, lookahead);153}154155/**156* match a token with TT_TYPE=type, and the token value is a given sequence157* of characters.158*/159private void match(int ttype, String token)160throws ParserException, IOException {161if (lookahead.ttype == ttype && lookahead.sval.compareTo(token) == 0) {162nextToken();163} else {164throw new SyntaxException(st.lineno(), new Token(ttype, token),165lookahead);166}167}168169/**170* match a token with TT_TYPE=type171*/172private void match(int ttype) throws ParserException, IOException {173if (lookahead.ttype == ttype) {174nextToken();175} else {176throw new SyntaxException(st.lineno(), new Token(ttype), lookahead);177}178}179180/**181* match a token with TT_TYPE=char, where the token value is the given char.182*/183private void match(char ttype) throws ParserException, IOException {184if (lookahead.ttype == (int)ttype) {185nextToken();186}187else {188throw new SyntaxException(st.lineno(), new Token((int)ttype),189lookahead);190}191}192193/**194* match a token with TT_TYPE='"', where the token value is a sequence195* of characters between matching quote characters.196*/197private void matchQuotedString() throws ParserException, IOException {198match(DOUBLEQUOTE);199}200201/**202* match a TT_NUMBER token that matches a parsed number value203*/204private void matchNumber() throws ParserException, IOException {205match(StreamTokenizer.TT_NUMBER);206}207208/**209* match a TT_WORD token that matches an arbitrary, not quoted token.210*/211private void matchID() throws ParserException, IOException {212match(StreamTokenizer.TT_WORD);213}214215/**216* match a TT_WORD token that matches the given string217*/218private void match(String token) throws ParserException, IOException {219match(StreamTokenizer.TT_WORD, token);220}221222/**223* determine if the given word is a reserved key word224*/225private boolean isReservedWord(String word) {226return reservedWords.contains(word);227}228229/**230* determine if the give work is a reserved key word231*/232private boolean isInfixOperator(char op) {233for (int i = 0; i < infixOps.length; i++) {234if (op == infixOps[i]) {235return true;236}237}238return false;239}240241/**242* scalestmt -> 'scale' scalespec243* scalespec -> <see above scaleTerminals array>244*/245private void scaleStmt(ColumnFormat cf)246throws ParserException, IOException {247match(SCALE);248Token t = matchOne(scaleKeyWords);249cf.setScale(Scale.toScale(t.sval));250String scaleString = t.sval;251log(pdebug, "Parsed: scale -> " + scaleString);252}253254/**255* alignstmt -> 'align' alignspec256* alignspec -> <see above alignTerminals array>257*/258private void alignStmt(ColumnFormat cf)259throws ParserException, IOException {260match(ALIGN);261Token t = matchOne(alignKeyWords);262cf.setAlignment(Alignment.toAlignment(t.sval));263String alignString = t.sval;264log(pdebug, "Parsed: align -> " + alignString);265}266267/**268* headerstmt -> 'header' quotedstring269*/270private void headerStmt(ColumnFormat cf)271throws ParserException, IOException {272match(HEADER);273String headerString = lookahead.sval;274matchQuotedString();275cf.setHeader(headerString);276log(pdebug, "Parsed: header -> " + headerString);277}278279/**280* widthstmt -> 'width' integer281*/282private void widthStmt(ColumnFormat cf)283throws ParserException, IOException {284match(WIDTH);285double width = lookahead.nval;286matchNumber();287cf.setWidth((int)width);288log(pdebug, "Parsed: width -> " + width );289}290291/**292* formatstmt -> 'format' quotedstring293*/294private void formatStmt(ColumnFormat cf)295throws ParserException, IOException {296match(FORMAT);297String formatString = lookahead.sval;298matchQuotedString();299cf.setFormat(formatString);300log(pdebug, "Parsed: format -> " + formatString);301}302303/**304* Primary -> Literal | Identifier | '(' Expression ')'305*/306private Expression primary() throws ParserException, IOException {307Expression e = null;308309switch (lookahead.ttype) {310case OPENPAREN:311match(OPENPAREN);312e = expression();313match(CLOSEPAREN);314break;315case StreamTokenizer.TT_WORD:316String s = lookahead.sval;317if (isReservedWord(s)) {318throw new SyntaxException(st.lineno(), "IDENTIFIER",319"Reserved Word: " + lookahead.sval);320}321matchID();322e = new Identifier(s);323log(pdebug, "Parsed: ID -> " + s);324break;325case StreamTokenizer.TT_NUMBER:326double literal = lookahead.nval;327matchNumber();328e = new Literal(Double.valueOf(literal));329log(pdebug, "Parsed: number -> " + literal);330break;331default:332throw new SyntaxException(st.lineno(), "IDENTIFIER", lookahead);333}334log(pdebug, "Parsed: primary -> " + e);335return e;336}337338/**339* Unary -> ('+'|'-') Unary | Primary340*/341private Expression unary() throws ParserException, IOException {342Expression e = null;343Operator op = null;344345while (true) {346switch (lookahead.ttype) {347case OPERATOR_PLUS:348match(OPERATOR_PLUS);349op = Operator.PLUS;350break;351case OPERATOR_MINUS:352match(OPERATOR_MINUS);353op = Operator.MINUS;354break;355default:356e = primary();357log(pdebug, "Parsed: unary -> " + e);358return e;359}360Expression e1 = new Expression();361e1.setOperator(op);362e1.setRight(e);363log(pdebug, "Parsed: unary -> " + e1);364e1.setLeft(new Literal(Double.valueOf(0)));365e = e1;366}367}368369/**370* MultExpression -> Unary (('*' | '/') Unary)*371*/372private Expression multExpression() throws ParserException, IOException {373Expression e = unary();374Operator op = null;375376while (true) {377switch (lookahead.ttype) {378case OPERATOR_MULTIPLY:379match(OPERATOR_MULTIPLY);380op = Operator.MULTIPLY;381break;382case OPERATOR_DIVIDE:383match(OPERATOR_DIVIDE);384op = Operator.DIVIDE;385break;386default:387log(pdebug, "Parsed: multExpression -> " + e);388return e;389}390Expression e1 = new Expression();391e1.setOperator(op);392e1.setLeft(e);393e1.setRight(unary());394e = e1;395log(pdebug, "Parsed: multExpression -> " + e);396}397}398399/**400* AddExpression -> MultExpression (('+' | '-') MultExpression)*401*/402private Expression addExpression() throws ParserException, IOException {403Expression e = multExpression();404Operator op = null;405406while (true) {407switch (lookahead.ttype) {408case OPERATOR_PLUS:409match(OPERATOR_PLUS);410op = Operator.PLUS;411break;412case OPERATOR_MINUS:413match(OPERATOR_MINUS);414op = Operator.MINUS;415break;416default:417log(pdebug, "Parsed: addExpression -> " + e);418return e;419}420Expression e1 = new Expression();421e1.setOperator(op);422e1.setLeft(e);423e1.setRight(multExpression());424e = e1;425log(pdebug, "Parsed: addExpression -> " + e);426}427}428429/**430* Expression -> AddExpression431*/432private Expression expression() throws ParserException, IOException {433Expression e = addExpression();434log(pdebug, "Parsed: expression -> " + e);435return e;436}437438/**439* datastmt -> 'data' expression440*/441private void dataStmt(ColumnFormat cf) throws ParserException, IOException {442match(DATA);443Expression e = expression();444cf.setExpression(e);445log(pdebug, "Parsed: data -> " + e);446}447448/**449* requiredstmt -> 'required' expression450*/451private void requiredStmt(ColumnFormat cf) throws ParserException, IOException {452match(REQUIRED);453Token t = matchOne(boolKeyWords);454cf.setRequired(Boolean.parseBoolean(t.sval));455log(pdebug, "Parsed: required -> " + cf.isRequired());456}457458/**459* statementlist -> optionalstmt statementlist460* optionalstmt -> 'data' expression461* 'header' quotedstring462* 'width' integer463* 'format' formatstring464* 'align' alignspec465* 'scale' scalespec466* 'required' boolean467*/468private void statementList(ColumnFormat cf)469throws ParserException, IOException {470while (true) {471if (lookahead.ttype != StreamTokenizer.TT_WORD) {472return;473}474475if (lookahead.sval.compareTo(DATA) == 0) {476dataStmt(cf);477} else if (lookahead.sval.compareTo(HEADER) == 0) {478headerStmt(cf);479} else if (lookahead.sval.compareTo(WIDTH) == 0) {480widthStmt(cf);481} else if (lookahead.sval.compareTo(FORMAT) == 0) {482formatStmt(cf);483} else if (lookahead.sval.compareTo(ALIGN) == 0) {484alignStmt(cf);485} else if (lookahead.sval.compareTo(SCALE) == 0) {486scaleStmt(cf);487} else if (lookahead.sval.compareTo(REQUIRED) == 0) {488requiredStmt(cf);489} else {490return;491}492}493}494495/**496* optionlist -> columspec optionlist497* null498* columspec -> 'column' '{' statementlist '}'499*/500private void optionList(OptionFormat of)501throws ParserException, IOException {502while (true) {503if (lookahead.ttype != StreamTokenizer.TT_WORD) {504return;505}506507match(COLUMN);508match(OPENBLOCK);509ColumnFormat cf = new ColumnFormat(columnCount++);510statementList(cf);511match(CLOSEBLOCK);512cf.validate();513of.addSubFormat(cf);514}515}516517/**518* optionstmt -> 'option' ID '{' optionlist '}'519*/520private OptionFormat optionStmt() throws ParserException, IOException {521match(OPTION);522String optionName=lookahead.sval;523matchID();524match(OPENBLOCK);525OptionFormat of = new OptionFormat(optionName);526optionList(of);527match(CLOSEBLOCK);528return of;529}530531/**532* parse the specification for the given option identifier533*/534public OptionFormat parse(String option)535throws ParserException, IOException {536nextToken();537538/*539* this search stops on the first occurance of an option540* statement with a name matching the given option. Any541* duplicate options are ignored.542*/543while (lookahead.ttype != StreamTokenizer.TT_EOF) {544// look for the start symbol545if ((lookahead.ttype != StreamTokenizer.TT_WORD)546|| (lookahead.sval.compareTo(START) != 0)) {547// skip tokens until a start symbol is found548nextToken();549continue;550}551552// check if the option name is the one we are interested in553match(START);554555if ((lookahead.ttype == StreamTokenizer.TT_WORD)556&& (lookahead.sval.compareTo(option) == 0)) {557// this is the one we are looking for, parse it558pushBack();559return optionStmt();560} else {561// not what we are looking for, start skipping tokens562nextToken();563}564}565return null;566}567568public Set<OptionFormat> parseOptions() throws ParserException, IOException {569Set<OptionFormat> options = new HashSet<OptionFormat>();570571nextToken();572573while (lookahead.ttype != StreamTokenizer.TT_EOF) {574// look for the start symbol575if ((lookahead.ttype != StreamTokenizer.TT_WORD)576|| (lookahead.sval.compareTo(START) != 0)) {577// skip tokens until a start symbol is found578nextToken();579continue;580}581582// note: if a duplicate option statement exists, then583// first one encountered is the chosen definition.584OptionFormat of = optionStmt();585options.add(of);586}587return options;588}589590OptionFormat getOptionFormat() {591return optionFormat;592}593594private void log(boolean logging, String s) {595if (logging) {596System.out.println(s);597}598}599}600601602