/* ScriptParser Copyright (C) 1999 Jochen Hoenicke. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package jode.obfuscator; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import @COLLECTIONS@.Collection; import @COLLECTIONS@.LinkedList; public class ScriptParser { static int NO_TOKEN = -2; static int EOF_TOKEN = -1; static int STRING_TOKEN = 0; static int NEW_TOKEN = 1; static int EQUALS_TOKEN = 2; static int COMMA_TOKEN = 3; static int OPENBRACE_TOKEN = 4; static int CLOSEBRACE_TOKEN = 5; static int IDENTIFIER_TOKEN = 6; static int NUMBER_TOKEN = 7; Scanner scanner; class Scanner { BufferedReader input; String value; String line; int column; int linenr; int pushback = NO_TOKEN; public Scanner(Reader i) { input = new BufferedReader(i); } public void readString() throws ParseException { StringBuffer val = new StringBuffer(); while (column < line.length()) { char c = line.charAt(column++); if (c == '"') { value = val.toString(); return; } if (c == '\\') { c = line.charAt(column++); switch (c) { case 'n': val.append('\n'); break; case 't': val.append('\t'); break; case 'r': val.append('\r'); break; case 'u': if (column+4 <= line.length()) { try { char uni = (char) Integer.parseInt (line.substring(column, column+4), 16); column += 4; val.append(uni); } catch (NumberFormatException ex) { throw new ParseException (linenr, "Invalid unicode escape character"); } } else throw new ParseException (linenr, "Invalid unicode escape character"); break; default: val.append(c); } } else val.append(c); } throw new ParseException(linenr, "String spans over multiple lines"); } public void readIdentifier() { int start = column-1; while (column < line.length() && Character.isUnicodeIdentifierPart(line.charAt(column))) column++; value = line.substring(start, column); } public void readNumber() { boolean hex = false; int start = column-1; /* special case for hex numbers */ if (line.charAt(start) == '0' && line.charAt(column) == 'x') { column++; hex = true; } while (column < line.length()) { char c = line.charAt(column); if (!Character.isDigit(c)) { if (!hex) break; if ((c < 'A' || c > 'F') && (c < 'a' || c > 'f')) break; } column++; } value = line.substring(start, column); } public void pushbackToken(int token) { if (pushback != NO_TOKEN) throw new IllegalStateException ("Can only handle one pushback"); pushback = token; } public int getToken() throws ParseException, IOException { if (pushback != NO_TOKEN) { int result = pushback; pushback = NO_TOKEN; return result; } value = null; while (true) { if (line == null) { line = input.readLine(); if (line == null) return EOF_TOKEN; linenr++; column = 0; } while (column < line.length()) { char c = line.charAt(column++); if (Character.isWhitespace(c)) continue; if (c == '#') // this is a comment, skip this line break; if (c == '=') return EQUALS_TOKEN; if (c == ',') return COMMA_TOKEN; if (c == '{') return OPENBRACE_TOKEN; if (c == '}') return CLOSEBRACE_TOKEN; if (c == '"') { readString(); return STRING_TOKEN; } if (Character.isDigit(c) || c == '+' || c == '-') { readNumber(); return NUMBER_TOKEN; } if (Character.isUnicodeIdentifierStart(c)) { readIdentifier(); if (value.equals("new")) return NEW_TOKEN; return IDENTIFIER_TOKEN; } throw new ParseException (linenr, "Illegal character `"+c+"'"); } line = null; } } public String getValue() { return value; } public int getLineNr() { return linenr; } } public ScriptParser(Reader reader) { this.scanner = new Scanner(reader); } public Object parseClass() throws ParseException, IOException { int linenr = scanner.getLineNr(); int token = scanner.getToken(); if (token != IDENTIFIER_TOKEN) throw new ParseException(linenr, "Class name expected"); Object instance; try { Class clazz = Class.forName("jode.obfuscator."+scanner.getValue()); instance = clazz.newInstance(); } catch (ClassNotFoundException ex) { throw new ParseException(scanner.getLineNr(), "Class `"+scanner.getValue() +"' not found"); } catch (Exception ex) { throw new ParseException(scanner.getLineNr(), "Class `"+scanner.getValue() +"' not valid: "+ex.getMessage()); } token = scanner.getToken(); if (token == OPENBRACE_TOKEN) { if (!(instance instanceof OptionHandler)) throw new ParseException (scanner.getLineNr(), "Class `"+instance.getClass().getName() +"' doesn't handle options."); parseOptions((OptionHandler) instance); if (scanner.getToken() != CLOSEBRACE_TOKEN) throw new ParseException(scanner.getLineNr(), "`}' expected"); } else scanner.pushbackToken(token); return instance; } public void parseOptions(OptionHandler optionHandler) throws ParseException, IOException { int token = scanner.getToken(); while (true) { if (token == EOF_TOKEN || token == CLOSEBRACE_TOKEN) { scanner.pushbackToken(token); return; } if (token != IDENTIFIER_TOKEN) throw new ParseException(scanner.getLineNr(), "identifier expected"); String ident = scanner.getValue(); if (scanner.getToken() != EQUALS_TOKEN) throw new ParseException(scanner.getLineNr(), "equal sign expected"); int linenr = scanner.getLineNr(); Collection values = new LinkedList(); do { token = scanner.getToken(); if (token == NEW_TOKEN) { values.add(parseClass()); } else if (token == STRING_TOKEN) { values.add(scanner.getValue()); } else if (token == NUMBER_TOKEN) { values.add(new Integer(scanner.getValue())); } token = scanner.getToken(); } while (token == COMMA_TOKEN); try { optionHandler.setOption(ident, values); } catch (IllegalArgumentException ex) { throw new ParseException(linenr, optionHandler.getClass().getName() +": "+ex.getMessage()); } catch (RuntimeException ex) { throw new ParseException(linenr, optionHandler.getClass().getName() +": Illegal value: " +ex.getClass().getName() +": "+ex.getMessage()); } } } }