/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.tools.template; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StreamTokenizer; import java.io.StringReader; import java.io.StringWriter; import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.Vector; /** * Generates output files given a template. Run from command line. * * Command line format: * java GenerateFromTemplate [-debug] <input> <output> {<var>=<value>} * * NOTE: the command line variable feature is VERY powerful, but also * VERY dangerous. Make sure command line variable names don't * overlap with ANY text that might be found in the template except * actual substitution points! * * Supported commands: * [a] means 0 or 1 occurences of a * {a} means 0 or more occurences of a * * $$$$ INCLUDE <filename> * Inserts the contents of a specified file at the current point in * the template. NOTE: variable bindings propagate into the included * file, which may result in some unexpected substitutions. * * $$$$ FOREACH <var> <filename> [<field> <bgn>[..<end>]] ... $$$$ END * Loops over a file with a fixed record structure (description below). * If both <bgn> and <end> are specified, the loop is over records * with <field> value between <bgn> and <end> (inclusive) (it is * assumed that there is only one record with <field> value <bgn> or * <field> value <end>; <bgn> must precede <end>). If only <bgn> is * specified, the loop is over all records with <field> value equal * to <bgn>. * * $$$$ LOOP <var> { <value> } ... $$$$ END * Loops over a given list of values. * Numeric ranges (<num>..<num>) can also be used. * <var>.INDEX can be used to count iterations (0-indexed) * * $$$$ COUNT <var> { <value> } ... $$$$ END * Defines the variable to contain the number of elements in a given * list of values. * Numeric ranges (<num>..<num>) can also be used. * * $$$$ LET <var> <expr> ... $$$$ END * Defines the variable to have a given value until the matching END * (think functional). * NOTE: a variable can't be reassigned inside the block. * NOTE: operator precedence is NOT the same as in Java: * '|' and '^' have the same precedence as '+' and '-' * '&' has the same precedence as '*', '/' and '-' * '~' has the same precedence as unary '-' * * The expression has the following grammar: * LetExpr ::= StrExpr * StrExpr ::= StrExpr '`' String // concatenation * | String * String ::= @UPPER(StrExpr) // to upper case * | @LOWER(StrExpr) // to lower case * | @PAD(StrExpr, Expr, "str") // pad to given length * // with given string * | @SUBST(StrExpr, "str", "str") // substitute chars * // from string 1 * // with chars from * // string 2 * | @IF(Cond, StrExpr, StrExpr) // conditional eval. * | "str" * | str * | Expr // arith. expression * Cond ::= Expr '<' Expr // less * | Expr '<=' Expr // less or equal * | Expr '>' Expr // greater * | Expr '>=' Expr // greater or equal * | Expr '==' Expr // equal * | Expr '!=' Expr // not equal * Expr ::= Expr + Term // addition * | Expr - Term // subtraction * | Expr | Term // bitwise OR * | Expr ^ Term // bitwise XOR * | Term * Term ::= Term * Factor // multiplication * | Term / Factor // (integer) division * | Term % Factor // (integer) remainder * | Term & Factor // bitwise AND * | Factor * Factor ::= @LENGTH(StrExpr) // string length * | (Expr) // precedence * | - Factor // negation * | ~ Factor // bitwise NOT * | int * * $$$$ SPLIT "<value>" "<sep>" { <var> } ... $$$$ END * Splits a given value into tokens using given separators, and places * each token into the corresponding variable. If there are fewer * variables than tokens, the extra tokens are ignored. If there are more * variables than tokens, the extra variables get an empty value (""). * * $$$$ JOIN <var> "<sep>" { <value> } ... $$$$ END * Joins a given list of values using a given separator into the variable. * * $$$$ EVAL ... $$$$ END * Evaluates the result of template generation inside the block as * a template. * * $$$$ IF <cond> ... [ $$$$ ELSE ... ] $$$$ END * Conditionally generates enclosed templates. * <cond> is one of the following: * <value> == <value> String equality * <value> != <value> String inequality * <value> > <value> Alphabetically greater * <value> >= <value> Alphabetically greater or equal * <value> < <value> Alphabetically less * <value> <= <value> Alphabetically less or equal * <value> =~ <value> Contains as substring * <value> IN { <value> } List membership * <value> eq <value> Numeric equality (integers) * <value> ne <value> Numeric inequality (integers) * <value> gt <value> Numerically greater * <value> ge <value> Numerically greater or equal * <value> lt <value> Numerically less * <value> le <value> Numerically less or equal * * The FOREACH file has the following format: * <record structure>\n * { <record>\n } * * where <record structure> is * { { <field name> }\n } * terminated by a blank line * * Long lines can be extended by placing a backslash ("\") as the last * character of a line. The next line is appended to the end of the * current line. * NOTE: Lines are appended AS IS: leading spaces become part of the line. * if you wish the line to be broken use a double backslash ("\\") * * Lines starting with "#" are considered comments and skipped. * * On substitution, <FOREACH var>.<field name> is substituted by a value * of the field named <field name> in the current record of the FOREACH file. * A special field INDEX will contain the current record number. */ public class GenerateFromTemplate { static boolean DEBUG = false; static String inDir; static final String templateMarker = "$$$$"; static final String commentMarker = "#"; static final String indexField = "INDEX"; /** * Enumeration of commands that can be generated */ enum Command { END, FOREACH, LOOP, IF, ELSE, SPLIT, JOIN, EVAL, LET, COUNT, INCLUDE } static final int MAX_DATA_SIZE = 65536; LineNumberReader in; PrintWriter out; String[] vars; String[] vals; String params; /** * Main. * * @params args arguments from command line */ public static void main(String[] args) throws IOException { if (args.length < 2) { System.out.println("Usage: java GenerateFromTemplate [-debug] <input> <output> {<var>=<value>}"); return; } int argc = args.length; if (args[0].equals("-debug")) { DEBUG = true; argc--; System.arraycopy(args, 1, args, 0, argc); } // When driven from ant (on AIX), there's a problem keeping tokens that // are separated by blanks from being split into separate tokens when // when the java command is forked. Each token should contain an "=". // Verify this and reassemble them if they don't. int limit = argc; argc = 2; for (int i = 2; i < limit; i++) { if (args[i].indexOf("=") < 0) args[ argc-1 ] = args[ argc-1 ] + " " + args[ i ]; else { args[ argc++ ] = args[ i ]; } } FileInputStream inStream = null; FileOutputStream outStream = null; if (DEBUG) System.out.println("in:"+args[0]+"\nout:"+args[1]); try { if (args[0].indexOf(File.separator) != -1) inDir = args[0].substring(0, args[0].lastIndexOf(File.separator)+1); else inDir = ""; if (DEBUG) System.out.println("inDir="+inDir); inStream = new FileInputStream(args[0]); outStream = new FileOutputStream(args[1]); String[] vars = new String[argc-2]; String[] vals = new String[argc-2]; for (int i = 2; i < argc; i++) { String arg = args[i]; int pos = arg.indexOf("="); vars[i-2] = arg.substring(0, pos); vals[i-2] = arg.substring(pos+1); if (DEBUG) System.out.println(vars[i-2]+" = "+vals[i-2]); } GenerateFromTemplate gft = new GenerateFromTemplate(inStream, outStream); gft.setSubst(vars, vals); gft.generateOutputFromTemplate(); // } catch (IOException e) { // System.out.println("An error occurred: "+e); } finally { try { inStream.close(); outStream.close(); } catch (Exception e) {} } } GenerateFromTemplate(InputStream inStream, OutputStream outStream) { // setup i/o in = new LineNumberReader(new InputStreamReader(inStream)); out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outStream))); } void setSubst(String[] vr, String[] vl) { vars = vr; vals = vl; } String readLine() throws IOException { String inLine = in.readLine(); if (inLine != null) for (int i = 0; i < vars.length; i++) inLine = substitute(inLine, vars[i], vals[i]); return inLine; } void generateOutputFromTemplate() throws IOException { try { String inLine; // loop over strings for (inLine = readLine(); inLine != null; inLine = readLine()) { if (DEBUG) System.out.println("from input:"+inLine); if (!isTemplateLine(inLine)) { if (DEBUG) System.out.println("not template line, continuing..."); out.print(inLine+"\n"); continue; } Vector<Object> region = buildTemplateRegion(inLine); processTemplateRegion(region); } } finally { out.flush(); } } boolean isTemplateLine(String line) { StringTokenizer st = new StringTokenizer(line, " \t"); return st.hasMoreTokens() && st.nextToken().equals(templateMarker); } Vector<Object> buildTemplateRegion(String inLine) throws IOException { Vector<Object> region = new Vector<Object>(); region.addElement(inLine); Command command = getTemplateCommand(inLine); if (DEBUG) System.out.println("template command #"+command); switch (command) { case FOREACH: case LOOP: case SPLIT: case JOIN: case EVAL: case LET: case COUNT: buildLoopRegion(region); break; case IF: buildCondRegion(region); break; case INCLUDE: buildIncludeRegion(region); break; } return region; } void buildLoopRegion(Vector<Object> region) throws IOException { for (;;) { String inLine = readLine(); if (inLine == null) throw new IOException("Unexpected end of file"); if (isTemplateLine(inLine)) { Command command = getTemplateCommand(inLine); if (command == Command.END) break; region.addElement(buildTemplateRegion(inLine)); } else { if (DEBUG) System.out.println("adding line to region :"+inLine); region.addElement(inLine); } } } void buildCondRegion(Vector<Object> region) throws IOException { Vector<Object> intern = new Vector<Object>(); for (;;) { String inLine = readLine(); if (inLine == null) throw new IOException("Unexpected end of file"); if (isTemplateLine(inLine)) { Command command = getTemplateCommand(inLine); if (command == Command.END) { region.addElement(intern); break; } else if (command == Command.ELSE) { region.addElement(intern); intern = new Vector<Object>(); } else { intern.addElement(buildTemplateRegion(inLine)); } } else { if (DEBUG) System.out.println("adding line to region :"+inLine); intern.addElement(inLine); } } } void buildIncludeRegion(Vector<Object> region) throws IOException { QuotedStringTokenizer pst = new QuotedStringTokenizer(params); if (!pst.hasMoreTokens()) throw new IOException("Missing filename in INCLUDE"); String file_name = pst.nextToken(); LineNumberReader old_in = in; try { in = new LineNumberReader(new FileReader(file_name)); } catch (java.io.FileNotFoundException e) { in = new LineNumberReader(new FileReader(inDir + file_name)); } String inLine; // loop over strings in the file for (inLine = readLine(); inLine != null; inLine = readLine()) { if (isTemplateLine(inLine)) { region.addElement(buildTemplateRegion(inLine)); } else { if (DEBUG) System.out.println("adding line to region :"+inLine); region.addElement(inLine); } } in = old_in; } Command getTemplateCommand(String line) throws IOException { int startMatch = line.indexOf(templateMarker) + templateMarker.length() + 1; int endMatch = line.indexOf(' ', startMatch); if (endMatch < 0) { endMatch = line.length(); } if (DEBUG) System.out.println("getting template command :"+line.substring(startMatch)); Command i; try { i = Enum.valueOf(Command.class, line.substring(startMatch, endMatch)); } catch (IllegalArgumentException e) { IOException newE = new IOException("Invalid command"); newE.initCause(e); throw newE; } params = line.substring(endMatch); if (DEBUG) System.out.println("command is " + i + ". params ="+params); return i; } void processTemplateRegion(Vector<Object> region) throws IOException { String inLine = (String)region.elementAt(0); Command command = getTemplateCommand(inLine); switch (command) { case FOREACH: processForeachRegion(region); break; case LOOP: processLoopRegion(region); break; case COUNT: processCountRegion(region); break; case SPLIT: processSplitRegion(region); break; case JOIN: processJoinRegion(region); break; case LET: processLetRegion(region); break; case EVAL: processEvalRegion(region); break; case IF: processCondRegion(region); break; case INCLUDE: processIncludeRegion(region); break; } } private String getNextLine(BufferedReader data) throws IOException { String line = data.readLine(); if (line == null || line.length() == 0) return line; while (line.startsWith(commentMarker)) { line = data.readLine(); } while (line.endsWith("\\")) { if (line.endsWith("\\\\")) { line = line.substring(0, line.length()-2) + "\n" + data.readLine(); } else { line = line.substring(0, line.length()-1) + data.readLine(); } } return line; } void processForeachRegion(Vector region) throws IOException { // get var name and data file name QuotedStringTokenizer pst = new QuotedStringTokenizer(params); if (!pst.hasMoreTokens()) throw new IOException("Missing variable in FOREACH"); String var_name = pst.nextToken(); if (!pst.hasMoreTokens()) throw new IOException("Missing filename in FOREACH"); String file_name = pst.nextToken(); String select = null; String start = null; String end = null; boolean inRange = false; if (pst.hasMoreTokens()) { select = pst.nextToken(); if (!pst.hasMoreTokens()) throw new IOException("Missing field value in FOREACH"); String fval = pst.nextToken(); int dotdot = fval.indexOf(".."); if (dotdot != -1 && dotdot == fval.lastIndexOf("..")) { start = fval.substring(0, dotdot); end = fval.substring(dotdot+2); } else { start = fval; } } if (DEBUG) System.out.println("doing foreach with varname "+var_name+ " on data file :"+file_name); if (DEBUG && select != null) { System.out.print(" selecting records with "+select); if (end == null) System.out.println(" equal to \""+start+"\""); else System.out.println(" between \""+start+"\" and \""+end+"\""); } // open data file BufferedReader data; try { data = new BufferedReader(new FileReader(file_name)); } catch (java.io.FileNotFoundException e) { data = new BufferedReader(new FileReader(inDir + file_name)); } // read field information Vector<String> fields_v = new Vector<String>(); Vector<Integer> fpl_v = new Vector<Integer>(); for (String inLine = getNextLine(data); (inLine != null && inLine.length() != 0); inLine = getNextLine(data)) { StringTokenizer st = new StringTokenizer(inLine); fpl_v.addElement(st.countTokens()); while (st.hasMoreTokens()) { String tok = st.nextToken(); if (DEBUG) System.out.println("read field "+fields_v.size()+" :"+tok); fields_v.addElement(tok); } } fields_v.addElement(indexField); // convert to arrays for faster access int[] fieldsPerLine = new int[fpl_v.size()]; for (int i = 0; i < fieldsPerLine.length; i++) fieldsPerLine[i] = fpl_v.elementAt(i); String[] fields = fields_v.toArray(new String[0]); // Count through data file. dataFileLoop: for (int curField = 0; ; curField++) { // Read in all fields int i = 0; String[] fieldData = new String[fields.length]; for (int aFieldsPerLine : fieldsPerLine) { String line = getNextLine(data); if (line == null) break dataFileLoop; if (aFieldsPerLine == 1) { if (DEBUG) System.out.println("read field " + fields[i] + " :" + line); fieldData[i++] = line; } else { if (DEBUG) System.out.println("reading " + aFieldsPerLine + " fields"); StringTokenizer st = new StringTokenizer(line); try { for (int k = 0; k < aFieldsPerLine; k++) { String tok = st.nextToken(); if (DEBUG) System.out.println("read field " + fields[i] + ": " + tok); fieldData[i++] = tok; } } catch (NoSuchElementException x) { throw new IOException("Missing field " + fields[i]); } } } if (fieldsPerLine.length != 1) getNextLine(data); // skip empty line. fieldData[i++] = Integer.toString(curField); if (select != null) { for (int j = 0; j < fields.length; j++) { if (DEBUG) System.out.println("checking if select is field "+fields[j]); if (select.equals(fields[j])) { String value = fieldData[j]; if (value.equals(start)) inRange = true; else if (end == null) inRange = false; else if (value.equals(end)) end = null; if (DEBUG) System.out.println("record in range; including"); break; } } if (!inRange) break; } // Count through each line in region. for (int j = 1; j < region.size(); j++) { try { String currentLine = (String) region.elementAt(j); String result = substitute(currentLine, var_name, fields, fieldData); out.print(result+"\n"); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> oldRegion = (Vector<Object>)region.elementAt(j); Vector<Object> newRegion = substituteInRegion(oldRegion, var_name, fields, fieldData); processTemplateRegion(newRegion); } } // for j } // for curField data.close(); } void processLoopRegion(Vector region) throws IOException { // get var name and data values if (DEBUG) System.out.println("params=\""+params+"\""); QuotedStringTokenizer pst = new QuotedStringTokenizer(params); if (!pst.hasMoreTokens()) throw new IOException("Missing var name in LOOP"); String var_name = pst.nextToken(); Vector<String> valvec = new Vector<String>(); while (pst.hasMoreTokens()) { String v_i = pst.nextToken(); int dotdot = v_i.indexOf(".."); if (dotdot != -1 && dotdot == v_i.lastIndexOf("..")) { int start = Integer.parseInt(v_i.substring(0, dotdot)); int end = Integer.parseInt(v_i.substring(dotdot+2)); for (int j = start; j <= end; j++) valvec.addElement(Integer.toString(j)); } else valvec.addElement(v_i); } String[] values = new String[valvec.size()]; for (int i = 0; i < values.length; i++) values[i] = valvec.elementAt(i); if (DEBUG) System.out.println("doing loop with varname "+var_name+ " on values :"+ params.substring(var_name.length()+1)); // Loop through data values. for (int curValue = 0; curValue < values.length; curValue++) { // Count through each line in region. for (int j = 1; j < region.size(); j++) { try { String currentLine = (String) region.elementAt(j); // String result = substitute(currentLine, var_name, values[curValue]); String result = substitute(currentLine, var_name+"."+indexField, Integer.toString(curValue)); result = substitute(result, var_name, values[curValue]); out.print(result+"\n"); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> oldRegion = (Vector<Object>) region.elementAt(j); // Vector newRegion = substituteInRegion(oldRegion, var_name, // values[curValue]); Vector<Object> newRegion = substituteInRegion(oldRegion, var_name+"."+indexField, Integer.toString(curValue)); newRegion = substituteInRegion(newRegion, var_name, values[curValue]); processTemplateRegion(newRegion); } } // for j } // for currentValue } void processCountRegion(Vector region) throws IOException { // get var name and data values if (DEBUG) System.out.println("params=\""+params+"\""); QuotedStringTokenizer pst = new QuotedStringTokenizer(params); if (!pst.hasMoreTokens()) throw new IOException("Missing var name in COUNT"); String var_name = pst.nextToken(); int count = 0; while (pst.hasMoreTokens()) { String v_i = pst.nextToken(); int dotdot = v_i.indexOf(".."); if (dotdot != -1 && dotdot == v_i.lastIndexOf("..")) { int start = Integer.parseInt(v_i.substring(0, dotdot)); int end = Integer.parseInt(v_i.substring(dotdot+2)); for (int j = start; j <= end; j++) count++; } else count++; } String value = Integer.toString(count); if (DEBUG) System.out.println("doing count with varname "+var_name+ " on values :"+ params.substring(var_name.length()+1)+ "; count="+value); // Count through each line in region. for (int j = 1; j < region.size(); j++) { try { String currentLine = (String) region.elementAt(j); String result = substitute(currentLine, var_name, value); out.print(result+"\n"); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> oldRegion = (Vector<Object>) region.elementAt(j); Vector<Object> newRegion = substituteInRegion(oldRegion, var_name, value); processTemplateRegion(newRegion); } } // for j } private String parseArg(StreamTokenizer st) throws IOException { if (st.nextToken() != '(') throw new IOException("Missing '('"); String ret = evalStrExpr(st); if (st.nextToken() != ')') throw new IOException("Missing ')'"); return ret; } private String evalUpper(StreamTokenizer st) throws IOException { return parseArg(st).toUpperCase(); } private String evalLower(StreamTokenizer st) throws IOException { return parseArg(st).toLowerCase(); } private String evalPad(StreamTokenizer st) throws IOException { if (st.nextToken() != '(') throw new IOException("Missing '('"); StringBuilder val = new StringBuilder(evalStrExpr(st)); int unpaddedSize = val.length(); if (st.nextToken() != ',') throw new IOException("Missing ','"); int len = evalExpr(st); if (st.nextToken() != ',') throw new IOException("Missing ','"); if (st.nextToken() != '"') throw new IOException("Invalid string"); String pad = st.sval; if (st.nextToken() != ')') throw new IOException("Missing ')'"); while (val.length() < len) val.append(pad); val.setLength(Math.max(len, unpaddedSize)); return val.toString(); } private String evalSubst(StreamTokenizer st) throws IOException { if (st.nextToken() != '(') throw new IOException("Missing '('"); StringBuilder val = new StringBuilder(evalStrExpr(st)); if (st.nextToken() != ',') throw new IOException("Missing ','"); if (st.nextToken() != '"') throw new IOException("Invalid string"); String oldc = st.sval; if (st.nextToken() != ',') throw new IOException("Missing ','"); if (st.nextToken() != '"') throw new IOException("Invalid string"); String newc = st.sval; if (st.nextToken() != ')') throw new IOException("Missing ')'"); for (int i = 0; i < val.length(); i++) { int l = oldc.indexOf(val.charAt(i)); if (l != -1) val.setCharAt(i, newc.charAt(l)); } return val.toString(); } private boolean evalCond(StreamTokenizer st) throws IOException { int val = evalExpr(st); int token = st.nextToken(); switch (token) { case '>': if (st.nextToken() == '=') return val >= evalExpr(st); else { st.pushBack(); return val > evalExpr(st); } case '<': if (st.nextToken() == '=') return val <= evalExpr(st); else { st.pushBack(); return val < evalExpr(st); } case '=': if (st.nextToken() != '=') throw new IOException("Invalid token"); return val == evalExpr(st); case '!': if (st.nextToken() != '=') throw new IOException("Invalid token"); return val != evalExpr(st); default: throw new IOException("Invalid token"); } } private String evalIf(StreamTokenizer st) throws IOException { if (st.nextToken() != '(') throw new IOException("Missing '('"); boolean cond = evalCond(st); if (st.nextToken() != ',') throw new IOException("Missing ','"); String valtrue = evalStrExpr(st); if (st.nextToken() != ',') throw new IOException("Missing ','"); String valfalse = evalStrExpr(st); if (st.nextToken() != ')') throw new IOException("Missing ')'"); if (cond) return valtrue; return valfalse; } private int evalLength(StreamTokenizer st) throws IOException { return parseArg(st).length(); } private int evalFactor(StreamTokenizer st) throws IOException { int tok = st.nextToken(); switch (tok) { case StreamTokenizer.TT_NUMBER: return (int) st.nval; case '-': return -evalFactor(st); case '~': return ~evalFactor(st); case '(': int val = evalExpr(st); if (st.nextToken() != ')') throw new IOException("Mismatched parentheses"); return val; case StreamTokenizer.TT_WORD: if (st.sval.equals("@LENGTH")) return evalLength(st); else throw new IOException("Invalid token: "+tok); default: throw new IOException("Invalid token: "+tok); } } private int evalTerm(StreamTokenizer st) throws IOException { int val = evalFactor(st); int token = st.nextToken(); while (token == '*' || token == '/' || token == '%' || token == '&') { int t = evalFactor(st); switch (token) { case '*': val *= t; break; case '/': val /= t; break; case '%': val %= t; break; case '&': val &= t; break; default: throw new IOException("Invalid token"); } token = st.nextToken(); } st.pushBack(); return val; } private int evalExpr(StreamTokenizer st) throws IOException { int val = evalTerm(st); int token = st.nextToken(); while (token == '+' || token == '-' || token == '|' || token == '^') { int t = evalTerm(st); switch (token) { case '+': val += t; break; case '-': val -= t; break; case '|': val |= t; break; case '^': val ^= t; break; default: throw new IOException("Invalid token"); } token = st.nextToken(); } st.pushBack(); return val; } private String evalString(StreamTokenizer st) throws IOException { int token = st.nextToken(); switch (token) { case StreamTokenizer.TT_WORD: if (st.sval.equals("@UPPER")) return evalUpper(st); else if (st.sval.equals("@LOWER")) return evalLower(st); else if (st.sval.equals("@PAD")) return evalPad(st); else if (st.sval.equals("@SUBST")) return evalSubst(st); else if (st.sval.equals("@IF")) return evalIf(st); else if (st.sval.equals("@LENGTH")) break; // in Factor else return st.sval; case '"': return st.sval; default: break; } st.pushBack(); return Integer.toString(evalExpr(st)); } private String evalStrExpr(StreamTokenizer st) throws IOException { StringBuilder val = new StringBuilder(evalString(st)); int token = st.nextToken(); while (token == '`') { val.append(evalString(st)); token = st.nextToken(); } st.pushBack(); return val.toString(); } private String evalLet(StreamTokenizer st) throws IOException { String val = evalStrExpr(st); if (st.nextToken() != StreamTokenizer.TT_EOF) throw new IOException("Extra input: '"+st.ttype+"'"); return val; } void processLetRegion(Vector region) throws IOException { // get var name and data values if (DEBUG) System.out.println("params=\""+params+"\""); StringReader sr = new StringReader(params); StreamTokenizer pst = new StreamTokenizer(sr); // pst.resetSyntax(); pst.parseNumbers(); pst.whitespaceChars(0, ' '); // pst.wordChars('a', 'z'); pst.wordChars('A', 'Z'); // pst.wordChars(128 + 32, 255); // pst.quoteChar('"'); pst.quoteChar('\''); pst.ordinaryChar('-'); pst.ordinaryChar('/'); pst.ordinaryChar('*'); pst.wordChars('@','@'); pst.wordChars('_', '_'); int tok = pst.nextToken(); if (tok != StreamTokenizer.TT_WORD) throw new IOException("Missing var name in LET"); String var_name = pst.sval; if (pst.nextToken() == StreamTokenizer.TT_EOF) throw new IOException("Missing value in LET"); pst.pushBack(); String value = evalLet(pst); if (DEBUG) System.out.println("doing let with varname "+var_name+ " and value=\""+value+"\""); // Execute region assigning data value. // Count through each line in region. for (int j = 1; j < region.size(); j++) { try { String currentLine = (String) region.elementAt(j); String result = substitute(currentLine, var_name, value); out.print(result+"\n"); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> oldRegion = (Vector) region.elementAt(j); Vector<Object> newRegion = substituteInRegion(oldRegion, var_name, value); processTemplateRegion(newRegion); } } // for j } void processJoinRegion(Vector<Object> region) throws IOException { // get var name and data values if (DEBUG) System.out.println("params=\""+params+"\""); QuotedStringTokenizer pst = new QuotedStringTokenizer(params); if (!pst.hasMoreTokens()) throw new IOException("Missing var name in JOIN"); String var_name = pst.nextToken(); if (!pst.hasMoreTokens()) throw new IOException("Missing separators in JOIN"); String sep = pst.nextToken(); int numValues = pst.countTokens(); String value = ""; if (pst.hasMoreTokens()) { value = pst.nextToken(); for (int i = 1; i < numValues; i++) value += sep + pst.nextToken(); } if (DEBUG) System.out.println("doing join with varname "+var_name+ " and value=\""+value+"\""); // Execute region assigning data value. // Count through each line in region. for (int j = 1; j < region.size(); j++) { try { String currentLine = (String) region.elementAt(j); String result = substitute(currentLine, var_name, value); out.print(result+"\n"); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> oldRegion = (Vector<Object>) region.elementAt(j); Vector<Object> newRegion = substituteInRegion(oldRegion, var_name, value); processTemplateRegion(newRegion); } } // for j } void processSplitRegion(Vector<Object> region) throws IOException { // get data value and var names if (DEBUG) System.out.println("params=\""+params+"\""); QuotedStringTokenizer pst = new QuotedStringTokenizer(params); if (!pst.hasMoreTokens()) throw new IOException("Missing value in SPLIT"); String value = pst.nextToken(); if (!pst.hasMoreTokens()) throw new IOException("Missing separators in SPLIT"); String sep = pst.nextToken(); if (!pst.hasMoreTokens()) throw new IOException("Missing variables in SPLIT"); int numVars = pst.countTokens(); String[] var_names = new String[numVars]; for (int i = 0; i < numVars; i++) var_names[i] = pst.nextToken(); StringTokenizer vst = new StringTokenizer(value, sep); String[] values = new String[numVars]; for (int i = 0; i < numVars; i++) if (vst.hasMoreTokens()) values[i] = vst.nextToken(); else values[i] = ""; if (DEBUG) System.out.println("doing split with value \""+value+ "\" to vars :"+ params.substring(value.length()+3)); // Count through each line in region. for (int j = 1; j < region.size(); j++) { try { String result = (String) region.elementAt(j); // Loop through vars. for (int curVar = 0; curVar < var_names.length; curVar++) result = substitute(result, var_names[curVar], values[curVar]); out.print(result+"\n"); } catch (ClassCastException e) { Vector<Object> newRegion = (Vector<Object>) region.elementAt(j); // Loop through vars. for (int curVar = 0; curVar < var_names.length; curVar++) newRegion = substituteInRegion(newRegion, var_names[curVar], values[curVar]); processTemplateRegion(newRegion); } } // for j } void processEvalRegion(Vector<Object> region) throws IOException { if (DEBUG) System.out.println("doing eval"); PrintWriter old_out = out; StringWriter sw = new StringWriter(); out = new PrintWriter(sw); // Count through each line in region. for (int j = 1; j < region.size(); j++) { try { String currentLine = (String) region.elementAt(j); out.print(currentLine+"\n"); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> tmpRegion = (Vector<Object>) region.elementAt(j); processTemplateRegion(tmpRegion); } } // for j out = old_out; if (DEBUG) System.out.println("doing eval: evaluating\n"+sw); LineNumberReader old_in = in; in = new LineNumberReader(new StringReader(sw.toString())); String inLine; // loop over strings in the newly created region for (inLine = readLine(); inLine != null; inLine = readLine()) { if (DEBUG) System.out.println("from input:"+inLine); if (!isTemplateLine(inLine)) { if (DEBUG) System.out.println("not template line, continuing..."); out.print(inLine+"\n"); continue; } Vector<Object> newRegion = buildTemplateRegion(inLine); processTemplateRegion(newRegion); } in = old_in; } boolean evaluateConditional(String arg, String op, String[] value) { boolean retval = false; op = op.intern(); if (op == "==") retval = arg.equals(value[0]); else if (op == "!=") retval = !arg.equals(value[0]); else if (op == "<") retval = arg.compareTo(value[0]) < 0; else if (op == "<=") retval = arg.compareTo(value[0]) <= 0; else if (op == ">") retval = arg.compareTo(value[0]) > 0; else if (op == ">=") retval = arg.compareTo(value[0]) >= 0; else if (op == "=~") retval = arg.indexOf(value[0]) >= 0; else if (op == "IN") { for (int i = 0; i < value.length && !retval; i++) if (arg.equals(value[i])) retval = true; } else if (op == "eq") retval = Integer.parseInt(arg) == Integer.parseInt(value[0]); else if (op == "ne") retval = Integer.parseInt(arg) != Integer.parseInt(value[0]); else if (op == "lt") retval = Integer.parseInt(arg) < Integer.parseInt(value[0]); else if (op == "le") retval = Integer.parseInt(arg) <= Integer.parseInt(value[0]); else if (op == "gt") retval = Integer.parseInt(arg) > Integer.parseInt(value[0]); else if (op == "ge") retval = Integer.parseInt(arg) >= Integer.parseInt(value[0]); return retval; } void processCondRegion(Vector region) throws IOException { // get var name, operation and data value QuotedStringTokenizer pst = new QuotedStringTokenizer(params); if (!pst.hasMoreTokens()) throw new IOException("Missing argument in IF"); String arg = pst.nextToken(); if (!pst.hasMoreTokens()) throw new IOException("Missing operation in IF"); String op = pst.nextToken(); String[] value = new String[pst.countTokens()]; for (int i = 0; i < value.length; i++) value[i] = pst.nextToken(); if (DEBUG) { if (value.length > 0) System.out.println("doing conditional "+arg+" "+op+" "+value[0]); else System.out.println("doing conditional "+arg+" "+op+" <NO ARGUMENT TOKEN FOUND>"); } // Evaluate conditional. Vector newRegion = (Vector) region.elementAt(1); if (!evaluateConditional(arg, op, value)) { if (region.size() > 2) newRegion = (Vector) region.elementAt(2); else newRegion = new Vector(); if (DEBUG) System.out.println("condition is false"); } else { if (DEBUG) System.out.println("condition is true"); } // Count through each line in region. for (int j = 0; j < newRegion.size(); j++) { try { String currentLine = (String) newRegion.elementAt(j); out.print(currentLine+"\n"); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> tmpRegion = (Vector<Object>) newRegion.elementAt(j); processTemplateRegion(tmpRegion); } } // for j } void processIncludeRegion(Vector<Object> region) throws IOException { // Count through each line in region. for (int j = 1; j < region.size(); j++) { try { String result = (String) region.elementAt(j); out.print(result+"\n"); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> newRegion = (Vector<Object>)region.elementAt(j); processTemplateRegion(newRegion); } } // for j } Vector<Object> substituteInRegion(Vector<Object> region, String var, String[] fields, String[] fieldData) throws IOException { Vector<Object> newRegion = new Vector<Object>(region.size()); for (int i = 0; i < region.size(); i++) { Object el = region.elementAt(i); try { String s = (String) el; String r = substitute(s, var, fields, fieldData); newRegion.addElement(r); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> s = (Vector<Object>)el; Vector<Object> r = substituteInRegion(s, var, fields, fieldData); newRegion.addElement(r); } } return newRegion; } Vector<Object> substituteInRegion(Vector<Object> region, String var, String value) throws IOException { Vector<Object> newRegion = new Vector<Object>(region.size()); for (int i = 0; i < region.size(); i++) { Object el = region.elementAt(i); try { String s = (String) el; String r = substitute(s, var, value); newRegion.addElement((Object)r); } catch (ClassCastException e) { @SuppressWarnings("unchecked") // Suppress complaints that we are casting to an erased type Vector<Object> s = (Vector<Object>)el; Vector<Object> r = substituteInRegion(s, var, value); newRegion.addElement(r); } } return newRegion; } String substitute(String input, String var, String[] fields, String[] fieldData) throws IOException { StringBuilder out = new StringBuilder(); int varlen = var.length(); int oidx = 0; for (;;) { if (DEBUG) System.out.println("checking for occurrence of "+var+ " in :"+input.substring(oidx)); int idx = input.indexOf(var, oidx); if (idx == -1) break; // Write stuff inbetween last variable and current variable out.append(input.substring(oidx, idx)); idx += varlen; if (input.charAt(idx) != '.') throw new IOException("no field"); idx++; // Find which field this is int idx_save = idx; for (int i = 0; i < fields.length; i++) { String fld = fields[i]; int flen = fld.length(); if (DEBUG) System.out.println("checking if it is field "+fld); if (input.regionMatches(idx, fld, 0, flen)) { String value = fieldData[i]; if (DEBUG) System.out.println("field matches. outputting data :"+ value); out.append(value); idx += flen; break; } } if (idx == idx_save) throw new IOException("unknown field"); oidx = idx; } if (DEBUG) System.out.println("no more variables left on this line"); out.append(input.substring(oidx)); return out.toString(); } String substitute(String input, String var, String value) throws IOException { StringBuilder out = new StringBuilder(); int varlen = var.length(); int oidx = 0; for (;;) { int idx = input.indexOf(var, oidx); if (idx == -1) break; out.append(input.substring(oidx, idx)); idx += varlen; out.append(value); oidx = idx; } out.append(input.substring(oidx)); return out.toString(); } }