586 lines
18 KiB
Java
586 lines
18 KiB
Java
/*
|
|
* Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package sun.tools.jstat;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* A class implementing a simple predictive parser for output format
|
|
* specification language for the jstat command.
|
|
*
|
|
* @author Brian Doherty
|
|
* @since 1.5
|
|
*/
|
|
public class Parser {
|
|
|
|
private static boolean pdebug = Boolean.getBoolean("jstat.parser.debug");
|
|
private static boolean ldebug = Boolean.getBoolean("jstat.lex.debug");
|
|
|
|
private static final char OPENBLOCK = '{';
|
|
private static final char CLOSEBLOCK = '}';
|
|
private static final char DOUBLEQUOTE = '"';
|
|
private static final char PERCENT_CHAR = '%';
|
|
private static final char OPENPAREN = '(';
|
|
private static final char CLOSEPAREN = ')';
|
|
|
|
private static final char OPERATOR_PLUS = '+';
|
|
private static final char OPERATOR_MINUS = '-';
|
|
private static final char OPERATOR_MULTIPLY = '*';
|
|
private static final char OPERATOR_DIVIDE = '/';
|
|
|
|
private static final String OPTION = "option";
|
|
private static final String COLUMN = "column";
|
|
private static final String DATA = "data";
|
|
private static final String HEADER = "header";
|
|
private static final String WIDTH = "width";
|
|
private static final String FORMAT = "format";
|
|
private static final String ALIGN = "align";
|
|
private static final String SCALE = "scale";
|
|
|
|
private static final String START = OPTION;
|
|
|
|
private static final Set scaleKeyWords = Scale.keySet();
|
|
private static final Set alignKeyWords = Alignment.keySet();
|
|
private static String[] otherKeyWords = {
|
|
OPTION, COLUMN, DATA, HEADER, WIDTH, FORMAT, ALIGN, SCALE
|
|
};
|
|
|
|
private static char[] infixOps = {
|
|
OPERATOR_PLUS, OPERATOR_MINUS, OPERATOR_MULTIPLY, OPERATOR_DIVIDE
|
|
};
|
|
|
|
private static char[] delimiters = {
|
|
OPENBLOCK, CLOSEBLOCK, PERCENT_CHAR, OPENPAREN, CLOSEPAREN
|
|
};
|
|
|
|
|
|
private static Set<String> reservedWords;
|
|
|
|
private StreamTokenizer st;
|
|
private String filename;
|
|
private Token lookahead;
|
|
private Token previous;
|
|
private int columnCount;
|
|
private OptionFormat optionFormat;
|
|
|
|
public Parser(String filename) throws FileNotFoundException {
|
|
this.filename = filename;
|
|
Reader r = new BufferedReader(new FileReader(filename));
|
|
}
|
|
|
|
public Parser(Reader r) {
|
|
st = new StreamTokenizer(r);
|
|
|
|
// allow both c++ style comments
|
|
st.ordinaryChar('/');
|
|
st.wordChars('_','_');
|
|
st.slashSlashComments(true);
|
|
st.slashStarComments(true);
|
|
|
|
reservedWords = new HashSet<String>();
|
|
for (int i = 0; i < otherKeyWords.length; i++) {
|
|
reservedWords.add(otherKeyWords[i]);
|
|
}
|
|
|
|
for (int i = 0; i < delimiters.length; i++ ) {
|
|
st.ordinaryChar(delimiters[i]);
|
|
}
|
|
|
|
for (int i = 0; i < infixOps.length; i++ ) {
|
|
st.ordinaryChar(infixOps[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* push back the lookahead token and restore the lookahead token
|
|
* to the previous token.
|
|
*/
|
|
private void pushBack() {
|
|
lookahead = previous;
|
|
st.pushBack();
|
|
}
|
|
|
|
/**
|
|
* retrieve the next token, placing the token value in the lookahead
|
|
* member variable, storing its previous value in the previous member
|
|
* variable.
|
|
*/
|
|
private void nextToken() throws ParserException, IOException {
|
|
int t = st.nextToken();
|
|
previous = lookahead;
|
|
lookahead = new Token(st.ttype, st.sval, st.nval);
|
|
log(ldebug, "lookahead = " + lookahead);
|
|
}
|
|
|
|
/**
|
|
* match one of the token values in the given set of key words
|
|
* token is assumed to be of type TT_WORD, and the set is assumed
|
|
* to contain String objects.
|
|
*/
|
|
private Token matchOne(Set keyWords) throws ParserException, IOException {
|
|
if ((lookahead.ttype == StreamTokenizer.TT_WORD)
|
|
&& keyWords.contains(lookahead.sval)) {
|
|
Token t = lookahead;
|
|
nextToken();
|
|
return t;
|
|
}
|
|
throw new SyntaxException(st.lineno(), keyWords, lookahead);
|
|
}
|
|
|
|
/**
|
|
* match a token with TT_TYPE=type, and the token value is a given sequence
|
|
* of characters.
|
|
*/
|
|
private void match(int ttype, String token)
|
|
throws ParserException, IOException {
|
|
if (lookahead.ttype == ttype && lookahead.sval.compareTo(token) == 0) {
|
|
nextToken();
|
|
} else {
|
|
throw new SyntaxException(st.lineno(), new Token(ttype, token),
|
|
lookahead);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* match a token with TT_TYPE=type
|
|
*/
|
|
private void match(int ttype) throws ParserException, IOException {
|
|
if (lookahead.ttype == ttype) {
|
|
nextToken();
|
|
} else {
|
|
throw new SyntaxException(st.lineno(), new Token(ttype), lookahead);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* match a token with TT_TYPE=char, where the token value is the given char.
|
|
*/
|
|
private void match(char ttype) throws ParserException, IOException {
|
|
if (lookahead.ttype == (int)ttype) {
|
|
nextToken();
|
|
}
|
|
else {
|
|
throw new SyntaxException(st.lineno(), new Token((int)ttype),
|
|
lookahead);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* match a token with TT_TYPE='"', where the token value is a sequence
|
|
* of characters between matching quote characters.
|
|
*/
|
|
private void matchQuotedString() throws ParserException, IOException {
|
|
match(DOUBLEQUOTE);
|
|
}
|
|
|
|
/**
|
|
* match a TT_NUMBER token that matches a parsed number value
|
|
*/
|
|
private void matchNumber() throws ParserException, IOException {
|
|
match(StreamTokenizer.TT_NUMBER);
|
|
}
|
|
|
|
/**
|
|
* match a TT_WORD token that matches an arbitrary, not quoted token.
|
|
*/
|
|
private void matchID() throws ParserException, IOException {
|
|
match(StreamTokenizer.TT_WORD);
|
|
}
|
|
|
|
/**
|
|
* match a TT_WORD token that matches the given string
|
|
*/
|
|
private void match(String token) throws ParserException, IOException {
|
|
match(StreamTokenizer.TT_WORD, token);
|
|
}
|
|
|
|
/**
|
|
* determine if the given word is a reserved key word
|
|
*/
|
|
private boolean isReservedWord(String word) {
|
|
return reservedWords.contains(word);
|
|
}
|
|
|
|
/**
|
|
* determine if the give work is a reserved key word
|
|
*/
|
|
private boolean isInfixOperator(char op) {
|
|
for (int i = 0; i < infixOps.length; i++) {
|
|
if (op == infixOps[i]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* scalestmt -> 'scale' scalespec
|
|
* scalespec -> <see above scaleTerminals array>
|
|
*/
|
|
private void scaleStmt(ColumnFormat cf)
|
|
throws ParserException, IOException {
|
|
match(SCALE);
|
|
Token t = matchOne(scaleKeyWords);
|
|
cf.setScale(Scale.toScale(t.sval));
|
|
String scaleString = t.sval;
|
|
log(pdebug, "Parsed: scale -> " + scaleString);
|
|
}
|
|
|
|
/**
|
|
* alignstmt -> 'align' alignspec
|
|
* alignspec -> <see above alignTerminals array>
|
|
*/
|
|
private void alignStmt(ColumnFormat cf)
|
|
throws ParserException, IOException {
|
|
match(ALIGN);
|
|
Token t = matchOne(alignKeyWords);
|
|
cf.setAlignment(Alignment.toAlignment(t.sval));
|
|
String alignString = t.sval;
|
|
log(pdebug, "Parsed: align -> " + alignString);
|
|
}
|
|
|
|
/**
|
|
* headerstmt -> 'header' quotedstring
|
|
*/
|
|
private void headerStmt(ColumnFormat cf)
|
|
throws ParserException, IOException {
|
|
match(HEADER);
|
|
String headerString = lookahead.sval;
|
|
matchQuotedString();
|
|
cf.setHeader(headerString);
|
|
log(pdebug, "Parsed: header -> " + headerString);
|
|
}
|
|
|
|
/**
|
|
* widthstmt -> 'width' integer
|
|
*/
|
|
private void widthStmt(ColumnFormat cf)
|
|
throws ParserException, IOException {
|
|
match(WIDTH);
|
|
double width = lookahead.nval;
|
|
matchNumber();
|
|
cf.setWidth((int)width);
|
|
log(pdebug, "Parsed: width -> " + width );
|
|
}
|
|
|
|
/**
|
|
* formatstmt -> 'format' quotedstring
|
|
*/
|
|
private void formatStmt(ColumnFormat cf)
|
|
throws ParserException, IOException {
|
|
match(FORMAT);
|
|
String formatString = lookahead.sval;
|
|
matchQuotedString();
|
|
cf.setFormat(formatString);
|
|
log(pdebug, "Parsed: format -> " + formatString);
|
|
}
|
|
|
|
/**
|
|
* Primary -> Literal | Identifier | '(' Expression ')'
|
|
*/
|
|
private Expression primary() throws ParserException, IOException {
|
|
Expression e = null;
|
|
|
|
switch (lookahead.ttype) {
|
|
case OPENPAREN:
|
|
match(OPENPAREN);
|
|
e = expression();
|
|
match(CLOSEPAREN);
|
|
break;
|
|
case StreamTokenizer.TT_WORD:
|
|
String s = lookahead.sval;
|
|
if (isReservedWord(s)) {
|
|
throw new SyntaxException(st.lineno(), "IDENTIFIER",
|
|
"Reserved Word: " + lookahead.sval);
|
|
}
|
|
matchID();
|
|
e = new Identifier(s);
|
|
log(pdebug, "Parsed: ID -> " + s);
|
|
break;
|
|
case StreamTokenizer.TT_NUMBER:
|
|
double literal = lookahead.nval;
|
|
matchNumber();
|
|
e = new Literal(new Double(literal));
|
|
log(pdebug, "Parsed: number -> " + literal);
|
|
break;
|
|
default:
|
|
throw new SyntaxException(st.lineno(), "IDENTIFIER", lookahead);
|
|
}
|
|
log(pdebug, "Parsed: primary -> " + e);
|
|
return e;
|
|
}
|
|
|
|
/**
|
|
* Unary -> ('+'|'-') Unary | Primary
|
|
*/
|
|
private Expression unary() throws ParserException, IOException {
|
|
Expression e = null;
|
|
Operator op = null;
|
|
|
|
while (true) {
|
|
switch (lookahead.ttype) {
|
|
case OPERATOR_PLUS:
|
|
match(OPERATOR_PLUS);
|
|
op = Operator.PLUS;
|
|
break;
|
|
case OPERATOR_MINUS:
|
|
match(OPERATOR_MINUS);
|
|
op = Operator.MINUS;
|
|
break;
|
|
default:
|
|
e = primary();
|
|
log(pdebug, "Parsed: unary -> " + e);
|
|
return e;
|
|
}
|
|
Expression e1 = new Expression();
|
|
e1.setOperator(op);
|
|
e1.setRight(e);
|
|
log(pdebug, "Parsed: unary -> " + e1);
|
|
e1.setLeft(new Literal(new Double(0)));
|
|
e = e1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* MultExpression -> Unary (('*' | '/') Unary)*
|
|
*/
|
|
private Expression multExpression() throws ParserException, IOException {
|
|
Expression e = unary();
|
|
Operator op = null;
|
|
|
|
while (true) {
|
|
switch (lookahead.ttype) {
|
|
case OPERATOR_MULTIPLY:
|
|
match(OPERATOR_MULTIPLY);
|
|
op = Operator.MULTIPLY;
|
|
break;
|
|
case OPERATOR_DIVIDE:
|
|
match(OPERATOR_DIVIDE);
|
|
op = Operator.DIVIDE;
|
|
break;
|
|
default:
|
|
log(pdebug, "Parsed: multExpression -> " + e);
|
|
return e;
|
|
}
|
|
Expression e1 = new Expression();
|
|
e1.setOperator(op);
|
|
e1.setLeft(e);
|
|
e1.setRight(unary());
|
|
e = e1;
|
|
log(pdebug, "Parsed: multExpression -> " + e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AddExpression -> MultExpression (('+' | '-') MultExpression)*
|
|
*/
|
|
private Expression addExpression() throws ParserException, IOException {
|
|
Expression e = multExpression();
|
|
Operator op = null;
|
|
|
|
while (true) {
|
|
switch (lookahead.ttype) {
|
|
case OPERATOR_PLUS:
|
|
match(OPERATOR_PLUS);
|
|
op = Operator.PLUS;
|
|
break;
|
|
case OPERATOR_MINUS:
|
|
match(OPERATOR_MINUS);
|
|
op = Operator.MINUS;
|
|
break;
|
|
default:
|
|
log(pdebug, "Parsed: addExpression -> " + e);
|
|
return e;
|
|
}
|
|
Expression e1 = new Expression();
|
|
e1.setOperator(op);
|
|
e1.setLeft(e);
|
|
e1.setRight(multExpression());
|
|
e = e1;
|
|
log(pdebug, "Parsed: addExpression -> " + e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Expression -> AddExpression
|
|
*/
|
|
private Expression expression() throws ParserException, IOException {
|
|
Expression e = addExpression();
|
|
log(pdebug, "Parsed: expression -> " + e);
|
|
return e;
|
|
}
|
|
|
|
/**
|
|
* datastmt -> 'data' expression
|
|
*/
|
|
private void dataStmt(ColumnFormat cf) throws ParserException, IOException {
|
|
match(DATA);
|
|
Expression e = expression();
|
|
cf.setExpression(e);
|
|
log(pdebug, "Parsed: data -> " + e);
|
|
}
|
|
|
|
/**
|
|
* statementlist -> optionalstmt statementlist
|
|
* optionalstmt -> 'data' expression
|
|
* 'header' quotedstring
|
|
* 'width' integer
|
|
* 'format' formatstring
|
|
* 'align' alignspec
|
|
* 'scale' scalespec
|
|
*/
|
|
private void statementList(ColumnFormat cf)
|
|
throws ParserException, IOException {
|
|
while (true) {
|
|
if (lookahead.ttype != StreamTokenizer.TT_WORD) {
|
|
return;
|
|
}
|
|
|
|
if (lookahead.sval.compareTo(DATA) == 0) {
|
|
dataStmt(cf);
|
|
} else if (lookahead.sval.compareTo(HEADER) == 0) {
|
|
headerStmt(cf);
|
|
} else if (lookahead.sval.compareTo(WIDTH) == 0) {
|
|
widthStmt(cf);
|
|
} else if (lookahead.sval.compareTo(FORMAT) == 0) {
|
|
formatStmt(cf);
|
|
} else if (lookahead.sval.compareTo(ALIGN) == 0) {
|
|
alignStmt(cf);
|
|
} else if (lookahead.sval.compareTo(SCALE) == 0) {
|
|
scaleStmt(cf);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* optionlist -> columspec optionlist
|
|
* null
|
|
* columspec -> 'column' '{' statementlist '}'
|
|
*/
|
|
private void optionList(OptionFormat of)
|
|
throws ParserException, IOException {
|
|
while (true) {
|
|
if (lookahead.ttype != StreamTokenizer.TT_WORD) {
|
|
return;
|
|
}
|
|
|
|
match(COLUMN);
|
|
match(OPENBLOCK);
|
|
ColumnFormat cf = new ColumnFormat(columnCount++);
|
|
statementList(cf);
|
|
match(CLOSEBLOCK);
|
|
cf.validate();
|
|
of.addSubFormat(cf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* optionstmt -> 'option' ID '{' optionlist '}'
|
|
*/
|
|
private OptionFormat optionStmt() throws ParserException, IOException {
|
|
match(OPTION);
|
|
String optionName=lookahead.sval;
|
|
matchID();
|
|
match(OPENBLOCK);
|
|
OptionFormat of = new OptionFormat(optionName);
|
|
optionList(of);
|
|
match(CLOSEBLOCK);
|
|
return of;
|
|
}
|
|
|
|
/**
|
|
* parse the specification for the given option identifier
|
|
*/
|
|
public OptionFormat parse(String option)
|
|
throws ParserException, IOException {
|
|
nextToken();
|
|
|
|
/*
|
|
* this search stops on the first occurance of an option
|
|
* statement with a name matching the given option. Any
|
|
* duplicate options are ignored.
|
|
*/
|
|
while (lookahead.ttype != StreamTokenizer.TT_EOF) {
|
|
// look for the start symbol
|
|
if ((lookahead.ttype != StreamTokenizer.TT_WORD)
|
|
|| (lookahead.sval.compareTo(START) != 0)) {
|
|
// skip tokens until a start symbol is found
|
|
nextToken();
|
|
continue;
|
|
}
|
|
|
|
// check if the option name is the one we are interested in
|
|
match(START);
|
|
|
|
if ((lookahead.ttype == StreamTokenizer.TT_WORD)
|
|
&& (lookahead.sval.compareTo(option) == 0)) {
|
|
// this is the one we are looking for, parse it
|
|
pushBack();
|
|
return optionStmt();
|
|
} else {
|
|
// not what we are looking for, start skipping tokens
|
|
nextToken();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public Set<OptionFormat> parseOptions() throws ParserException, IOException {
|
|
Set<OptionFormat> options = new HashSet<OptionFormat>();
|
|
|
|
nextToken();
|
|
|
|
while (lookahead.ttype != StreamTokenizer.TT_EOF) {
|
|
// look for the start symbol
|
|
if ((lookahead.ttype != StreamTokenizer.TT_WORD)
|
|
|| (lookahead.sval.compareTo(START) != 0)) {
|
|
// skip tokens until a start symbol is found
|
|
nextToken();
|
|
continue;
|
|
}
|
|
|
|
// note: if a duplicate option statement exists, then
|
|
// first one encountered is the chosen definition.
|
|
OptionFormat of = optionStmt();
|
|
options.add(of);
|
|
}
|
|
return options;
|
|
}
|
|
|
|
OptionFormat getOptionFormat() {
|
|
return optionFormat;
|
|
}
|
|
|
|
private void log(boolean logging, String s) {
|
|
if (logging) {
|
|
System.out.println(s);
|
|
}
|
|
}
|
|
}
|