kilim.tools.Asm.java Source code

Java tutorial

Introduction

Here is the source code for kilim.tools.Asm.java

Source

/* Copyright (c) 2006, Sriram Srinivasan
 *
 * You may distribute this software under the terms of the license 
 * specified in the file "License"
 */

package kilim.tools;

import static kilim.Constants.D_DOUBLE;
import static kilim.Constants.D_LONG;
import static kilim.Constants.LDC2_W;
import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
import static org.objectweb.asm.Opcodes.ACC_ENUM;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
import static org.objectweb.asm.Opcodes.ACC_NATIVE;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_STRICT;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ACC_SYNCHRONIZED;
import static org.objectweb.asm.Opcodes.ACC_TRANSIENT;
import static org.objectweb.asm.Opcodes.ACC_VOLATILE;
import static org.objectweb.asm.Opcodes.NEWARRAY;
import static org.objectweb.asm.Opcodes.T_BOOLEAN;
import static org.objectweb.asm.Opcodes.T_BYTE;
import static org.objectweb.asm.Opcodes.T_CHAR;
import static org.objectweb.asm.Opcodes.T_DOUBLE;
import static org.objectweb.asm.Opcodes.T_FLOAT;
import static org.objectweb.asm.Opcodes.T_INT;
import static org.objectweb.asm.Opcodes.T_LONG;
import static org.objectweb.asm.Opcodes.T_SHORT;
import static org.objectweb.asm.Opcodes.V1_5;
import static org.objectweb.asm.Opcodes.V1_6;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import me.jor.util.Log4jUtil;

import org.apache.commons.logging.Log;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

/**
 * This is a replacement for the jasmin bytecode assembler and uses the same
 * syntax. The main reason for writing it is that jasmin (v 2.1 at the time of
 * writing) didn't correcly support annotations. That is, the annotations
 * inserted by jasmin don't show up in java.lang.reflect.Method, even though the
 * annotations are in the class file. It was easier to write this tool than to
 * release a separate fix for jasmin.
 * <pre>
 * Usage: java kilim.tools.Asm [options] <.j file(s)>
 * Options:
 *      -d <dir> : output directory (default: '.')
 *      -q       : quiet            (default: verbose)
 *      -nf      : no stack frames  (default: compute stack frames)
 * </pre>
 * If stack frames are requested (default), the version of the class file is V1_6, otherwise
 * it is V1_5.
 * 
 * @author sriram srinivasan (sriram@malhar.net)
 */
public class Asm {
    private static final Log log = Log4jUtil.getLog(Asm.class);
    static boolean quiet = false;
    static String outputDir = ".";
    static Pattern wsPattern = Pattern.compile("\\s+");
    static Pattern commentPattern = Pattern.compile("^;.*$| ;[^\"]*");

    private boolean eofOK = false;
    private ClassWriter cv;
    private MethodVisitor mv;
    private int maxLocals = 1;
    private int maxStack = 1;
    private HashSet<String> declaredLabels = new HashSet<String>();
    private HashMap<String, Label> labels = new HashMap<String, Label>();
    private String className;
    private String methodName;
    private String fileName;
    private Line line, bufferedLine;
    private Matcher lastMatch = null; // for error context
    private Pattern lastPattern = null;

    private LineNumberReader reader;

    static HashMap<String, Integer> modifiers = new HashMap<String, Integer>();

    static {
        modifiers.put("public", ACC_PUBLIC);
        modifiers.put("private", ACC_PRIVATE);
        modifiers.put("protected", ACC_PROTECTED);
        modifiers.put("static", ACC_STATIC);
        modifiers.put("final", ACC_FINAL);
        modifiers.put("super", ACC_SUPER);
        modifiers.put("synchronized", ACC_SYNCHRONIZED);
        modifiers.put("volatile", ACC_VOLATILE);
        modifiers.put("transient", ACC_TRANSIENT);
        modifiers.put("native", ACC_NATIVE);
        modifiers.put("interface", ACC_INTERFACE);
        modifiers.put("abstract", ACC_ABSTRACT);
        modifiers.put("strict", ACC_STRICT);
        modifiers.put("enum", ACC_ENUM);
    }

    public static void main(String[] args) throws IOException {
        List<String> files = parseArgs(args);
        for (String arg : files) {
            if (!quiet) {
                log.info("Asm: " + arg);
            }
            new Asm(arg).write();
        }
    }

    public Asm(String afileName) throws IOException {
        fileName = afileName;
        reader = new LineNumberReader(new FileReader(fileName));
        cv = new ClassWriter(computeFrames ? ClassWriter.COMPUTE_FRAMES : 0);
        try {
            parseClass();
        } catch (EOF eof) {
            if (!eofOK) {
                log.error("Premature end of file: " + fileName);
                System.exit(1);
            }
        } catch (AsmException e) {
            log.error(e);
            System.exit(1);
        } catch (RuntimeException e) {
            log.warn("File: " + fileName);
            if (methodName != null) {
                log.warn("Method: " + methodName);
            }
            log.warn("");
            log.warn("Line " + line);
            log.warn("Last pattern match: " + lastPattern);
            throw e;
        }
    }

    // .class public final Foo/Bar/Baz 
    private static String classNamePatternStr = "[\\w/$]+";
    private static String modifierPatternStr = "public|private|protected|static|final|synchronized|volatile|transient|native|abstract|strict| ";
    private static Pattern classPattern = Pattern
            .compile("\\.(class|interface) ((" + modifierPatternStr + ")*)(" + classNamePatternStr + ")$");

    private void parseClass() {
        readLine();
        // match class declaration
        int acc = 0;
        if (!lineMatch(classPattern)) {
            err("Expected .class or .interface declaration");
        }

        if (line.startsWith(".interface")) {
            acc = ACC_INTERFACE;
        }

        acc |= parseModifiers(group(2));
        className = group(4);
        String superClassName = parseSuper();
        String[] interfaces = parseInterfaces();
        cv.visit((computeFrames ? V1_6 : V1_5), acc, className, null, superClassName, interfaces);

        parseClassBody();

        eofOK = true;
    }

    private int parseModifiers(String s) {
        if (s == null)
            return 0;
        s = s.trim();
        if (s.equals(""))
            return 0;
        int acc = 0;
        for (String modifier : split(wsPattern, s)) {
            if (!modifiers.containsKey(modifier)) {
                err("Modifier " + modifier + " not recognized");
            }
            acc |= modifiers.get(modifier);
        }
        return acc;
    }

    private static Pattern superPattern = Pattern.compile("\\.super (" + classNamePatternStr + ")$");

    private String parseSuper() {
        readLine();
        if (!lineMatch(superPattern)) {
            err("Expected .super <superclass>");
        }
        return group(1);
    }

    private static Pattern implementsPattern = Pattern.compile("\\.implements +(" + classNamePatternStr + ")$");

    private String[] parseInterfaces() {
        StringList interfaces = new StringList();
        while (true) {
            readLine();
            if (!lineMatch(implementsPattern)) {
                putBackLine();
                return interfaces.toArray();
            }
            interfaces.add(group(1));
        }
    }

    private void parseClassBody() {
        while (true) {
            readLine(); // this breaks out of the loop upon EOF
            if (lineMatch(fieldPattern)) {
                parseField();
            } else if (lineMatch(methodPattern)) {
                parseMethod();
            } else if (lineMatch(annotationPattern)) {
                readLine();
                if (!line.startsWith(".end annotation")) {
                    err(".end annotation not present");
                }
            } else {
                err("Expected field, method or annotation in class body");
            }
        }
    }

    // The field declaration "public final String fileName = "foobar" is declared as
    // .field public final  fieldName [[Ljava/lang/String; = "foobar" 
    // .field (modifier)* name type (= constval)?
    private static String namePatternStr = "[$\\w]+";
    private static String descPatternStr = "[$\\[\\w/;]+";
    private static Pattern fieldPattern = Pattern.compile(".field +((" + modifierPatternStr + ")*) +("
            + namePatternStr + ") +(" + descPatternStr + ") *(= *(.*))?");

    private void parseField() {
        String name = group(3);
        String desc = group(4);
        String valueStr = group(6);
        Object value = valueStr == null ? null
                : parseValue(valueStr, (desc.equals(D_DOUBLE) || desc.equals(D_LONG)));
        cv.visitField(parseModifiers(group(1)), name, // field name
                desc, null, // no signature 
                value);
    }

    // A method declaration for
    //   private static final Object[][]foobar(int, long, boolean)
    // is specified as
    //   .method private final static foobar(IJZ)[[Ljava/lang/Object; 
    //   .method <init>(IJZ)
    private static String methodNamePatternStr = "[<>\\w]+"; // 
    private static Pattern methodPattern = Pattern
            .compile(".method +((" + modifierPatternStr + ")*) (" + methodNamePatternStr + ") *([(][^\\s]+)");

    private void parseMethod() {
        eofOK = false;
        methodName = group(3);
        int acc = parseModifiers(group(1));
        String desc = group(4);

        String[] exceptions = parseMethodExceptions();
        mv = cv.visitMethod(acc, methodName, desc, // method desc
                null, // signature
                exceptions);

        parseMethodBody();
        eofOK = true;
    }

    private static Pattern throwsPattern = Pattern.compile("^ *\\.throws +(" + classNamePatternStr + ")");

    private String[] parseMethodExceptions() {
        StringList l = new StringList();
        while (true) {
            readLine();
            if (!lineMatch(throwsPattern)) {
                putBackLine();
                return l.toArray();
            }
            l.add(group(1));
        }
    }

    private void parseMethodBody() {
        labels.clear();
        declaredLabels.clear();
        mv.visitCode();
        while (true) {
            readLine();
            if (line.startsWith(".end method")) {
                break;
            } else if (line.startsWith(".")) {
                parseMethodDirective();
            } else if (lineMatch(labelPattern)) {
                parseLabel();
            } else {
                parseInstructions();
            }
        }
        checkLabelDeclarations();
        mv.visitMaxs(maxStack, maxLocals);
        mv.visitEnd();
    }

    private static Pattern labelPattern = Pattern.compile("^(\\w+) *: *$");

    private void parseLabel() {
        String str = group(1);
        if (declaredLabels.contains(str)) {
            err("Duplicate label " + str);
        } else {
            declaredLabels.add(str);
            Label l = getLabel(str);
            mv.visitLabel(l);
        }
    }

    private void checkLabelDeclarations() {
        for (String key : labels.keySet()) {
            if (!declaredLabels.contains(key)) {
                throw new AsmException("Label " + key + " not declared in " + methodName);
            }
        }
    }

    static Pattern localsPattern = Pattern.compile(".limit +locals +([0-9]+)");
    static Pattern stackPattern = Pattern.compile(".limit +stack +([0-9]+)");
    static Pattern catchPattern = Pattern
            .compile(".catch +(" + classNamePatternStr + ") +from +([\\w]+) +to +([\\w]+) +using +([\\w]+)");
    static Pattern annotationPattern = Pattern.compile(".annotation +((visible) )?([\\w/;]+)");

    private void parseMethodDirective() {
        if (lineMatch(localsPattern)) {
            maxLocals = parseInt(group(1));
        } else if (lineMatch(stackPattern)) {
            maxStack = parseInt(group(1));
        } else if (lineMatch(catchPattern)) {
            String exceptionType = group(1);
            if (exceptionType.equals("all")) {
                exceptionType = null;
            }
            Label fromLabel = getLabel(group(2));
            Label toLabel = getLabel(group(3));
            Label usingLabel = getLabel(group(4));
            mv.visitTryCatchBlock(fromLabel, toLabel, usingLabel, exceptionType);
        } else if (lineMatch(annotationPattern)) {
            parseAnnotation();
        } else if (!quiet) {
            log.error("Directive ignored: " + line);
        }
    }

    private void parseAnnotation() {
        String s = group(2);
        boolean visible = s == null ? false : s.equals("visible");
        String desc = group(3);
        mv.visitAnnotation(desc, visible);
        readLine();
        if (!line.startsWith(".end annotation")) {
            err(".end annotation not present");
        }
    }

    static String opcodeStrs[] = { "nop", "aconst_null", "iconst_m1", "iconst_0", "iconst_1", "iconst_2",
            "iconst_3", "iconst_4", "iconst_5", "lconst_0", "lconst_1", "fconst_0", "fconst_1", "fconst_2",
            "dconst_0", "dconst_1", "bipush", "sipush", "ldc", "ldc_w", "ldc2_w", "iload", "lload", "fload",
            "dload", "aload", "iload_0", "iload_1", "iload_2", "iload_3", "lload_0", "lload_1", "lload_2",
            "lload_3", "fload_0", "fload_1", "fload_2", "fload_3", "dload_0", "dload_1", "dload_2", "dload_3",
            "aload_0", "aload_1", "aload_2", "aload_3", "iaload", "laload", "faload", "daload", "aaload", "baload",
            "caload", "saload", "istore", "lstore", "fstore", "dstore", "astore", "istore_0", "istore_1",
            "istore_2", "istore_3", "lstore_0", "lstore_1", "lstore_2", "lstore_3", "fstore_0", "fstore_1",
            "fstore_2", "fstore_3", "dstore_0", "dstore_1", "dstore_2", "dstore_3", "astore_0", "astore_1",
            "astore_2", "astore_3", "iastore", "lastore", "fastore", "dastore", "aastore", "bastore", "castore",
            "sastore", "pop", "pop2", "dup", "dup_x1", "dup_x2", "dup2", "dup2_x1", "dup2_x2", "swap", "iadd",
            "ladd", "fadd", "dadd", "isub", "lsub", "fsub", "dsub", "imul", "lmul", "fmul", "dmul", "idiv", "ldiv",
            "fdiv", "ddiv", "irem", "lrem", "frem", "drem", "ineg", "lneg", "fneg", "dneg", "ishl", "lshl", "ishr",
            "lshr", "iushr", "lushr", "iand", "land", "ior", "lor", "ixor", "lxor", "iinc", "i2l", "i2f", "i2d",
            "l2i", "l2f", "l2d", "f2i", "f2l", "f2d", "d2i", "d2l", "d2f", "i2b", "i2c", "i2s", "lcmp", "fcmpl",
            "fcmpg", "dcmpl", "dcmpg", "ifeq", "ifne", "iflt", "ifge", "ifgt", "ifle", "if_icmpeq", "if_icmpne",
            "if_icmplt", "if_icmpge", "if_icmpgt", "if_icmple", "if_acmpeq", "if_acmpne", "goto", "jsr", "ret",
            "tableswitch", "lookupswitch", "ireturn", "lreturn", "freturn", "dreturn", "areturn", "return",
            "getstatic", "putstatic", "getfield", "putfield", "invokevirtual", "invokespecial", "invokestatic",
            "invokeinterface", "unused", "new", "newarray", "anewarray", "arraylength", "athrow", "checkcast",
            "instanceof", "monitorenter", "monitorexit", "wide", "multianewarray", "ifnull", "ifnonnull", "goto_w",
            "jsr_w" };

    private static boolean computeFrames = true;

    private final static HashMap<String, Integer> opcodeMap = new HashMap<String, Integer>();
    private final static byte[] visitTypes;
    private final static int INSN = 0;
    private final static int VAR = 1;
    private final static int LDC = 2;
    private final static int JUMP = 3;
    private final static int TABLESWITCH = 4;
    private final static int LOOKUPSWITCH = 5;
    private final static int FIELD = 6;
    private final static int METHOD = 7;
    private final static int TYPE = 8;
    private final static int MULTIANEWARRAY = 9;
    private final static int INT = 10;
    private final static int IINC = 11;

    static {
        for (int i = 0; i < opcodeStrs.length; i++) {
            opcodeMap.put(opcodeStrs[i], i);
        }
        opcodeMap.put("invokenonvirtual", opcodeMap.get("invokespecial"));

        // Generated the table from asm.Opcode
        visitTypes = new byte[] { INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN,
                INSN, INSN, INSN, INT, INT, LDC, LDC, LDC, VAR, VAR, VAR, VAR, VAR, INSN, INSN, INSN, INSN, INSN,
                INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN,
                INSN, INSN, INSN, INSN, INSN, INSN, INSN, VAR, VAR, VAR, VAR, VAR, INSN, INSN, INSN, INSN, INSN,
                INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN,
                INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN,
                INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN,
                INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN,
                INSN, INSN, INSN, INSN, IINC, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN,
                INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, INSN, JUMP, JUMP, JUMP, JUMP, JUMP, JUMP, JUMP,
                JUMP, JUMP, JUMP, JUMP, JUMP, JUMP, JUMP, JUMP, JUMP, VAR, TABLESWITCH, LOOKUPSWITCH, INSN, INSN,
                INSN, INSN, INSN, INSN, FIELD, FIELD, FIELD, FIELD, METHOD, METHOD, METHOD, METHOD, INSN, TYPE, INT,
                TYPE, INSN, INSN, TYPE, TYPE, INSN, INSN, INSN, MULTIANEWARRAY, JUMP, JUMP, JUMP, JUMP };
    }

    static final Pattern insnPattern = Pattern.compile("(\\w+)( +(.*))?");
    static final Pattern quotedPattern = Pattern.compile("(.*)");
    static final Pattern casePattern = Pattern.compile("(\\w+) *: *(\\w+)");
    static final Pattern methodInvokePattern = Pattern.compile("(" + classNamePatternStr + ")[/.]("
            + methodNamePatternStr + ") *([(].*?[)]" + descPatternStr + ") *(, *\\d+)?");
    static final Pattern fieldSpecPattern = Pattern.compile("([\\w/.$]+)[/.]([\\w$]+) +([^\\s]+)");

    private void parseInstructions() {
        // line read in parseMethodBody()
        if (!lineMatch(insnPattern)) {
            err("Instruction is not well-formed");
        }
        String insn = group(1);
        String operand = null;
        if (groupCount() == 3) {
            operand = group(3);
            if (operand != null) {
                operand = operand.trim();
            }
        }
        if (!opcodeMap.containsKey(insn)) {
            err("Instruction " + insn + " not recognized");
        }
        int opcode = opcodeMap.get(insn);
        switch (visitTypes[opcode]) {
        case INSN:
            mv.visitInsn(opcode);
            break;
        case VAR:
            mv.visitVarInsn(opcode, parseInt(operand));
            break;
        case LDC:
            mv.visitLdcInsn(parseValue(operand, (opcode == LDC2_W)));
            break;
        case JUMP:
            Label l = getLabel(operand);
            mv.visitJumpInsn(opcode, l);
            break;

        case TABLESWITCH: {
            int min = parseInt(operand);
            ArrayList<Label> labelList = new ArrayList<Label>(10);
            Label defLabel = null;
            while (true) {
                readLine();
                if (line.startsWith("default")) {
                    lineMatch(casePattern);
                    defLabel = getLabel(group(2));
                    break;
                } else {
                    labelList.add(getLabel(line.s));
                }
            }
            Label[] labels = labelList.toArray(new Label[labelList.size()]);
            int max = min + labels.length - 1;
            mv.visitTableSwitchInsn(min, max, defLabel, labels);
            break;
        }
        case LOOKUPSWITCH: {
            ArrayList<Integer> keyList = new ArrayList<Integer>(10);
            ArrayList<Label> labelList = new ArrayList<Label>(10);
            Label defLabel = null;
            while (true) {
                readLine();

                if (lineMatch(casePattern)) {
                    Label lab = getLabel(group(2));
                    String keystr = group(1);
                    if (keystr.equals("default")) {
                        defLabel = lab;
                        break;
                    } else {
                        int key = parseInt(keystr);
                        keyList.add(key);
                        labelList.add(lab);
                    }
                } else {
                    err("Ill-formed switch instruction");
                }
            }
            Label[] labels = labelList.toArray(new Label[labelList.size()]);
            int[] keys = new int[keyList.size()];
            for (int i = 0; i < keys.length; i++) {
                keys[i] = keyList.get(i);
            }
            mv.visitLookupSwitchInsn(defLabel, keys, labels);
            break;
        }
        case FIELD: {
            // getstatic foo/bar/Baz/fieldName I
            if (operand == null || !match(operand, fieldSpecPattern)) {
                err("Expected field access of the form foo/Bar/fieldName I");
            }
            String owner = group(1);
            String name = group(2);
            String desc = group(3);
            mv.visitFieldInsn(opcode, owner, name, desc);
            break;
        }
        case METHOD: {
            // invokevirtual foo/bar/Baz/methodName(IJZ)V
            if (operand == null || !match(operand, methodInvokePattern)) {
                err("Expected method invocation of the form /foo/Bar/methodName(IJ)V");
            }
            String owner = group(1);
            String name = group(2);
            String desc = group(3);
            mv.visitMethodInsn(opcode, owner, name, desc);
            break;
        }
        case TYPE:
            opcheck("expected type", operand);
            mv.visitTypeInsn(opcode, operand);
            break;

        case MULTIANEWARRAY: {
            opcheck("expected array type and dimensions", operand);
            String words[] = split(wsPattern, operand);
            mv.visitMultiANewArrayInsn(words[0], parseInt(words[1]));
            break;
        }

        case INT: {
            int op = -1;
            if (opcode == NEWARRAY) {
                if (operand.equals("boolean")) {
                    op = T_BOOLEAN;
                } else if (operand.equals("char")) {
                    op = T_CHAR;
                } else if (operand.equals("float")) {
                    op = T_FLOAT;
                } else if (operand.equals("double")) {
                    op = T_DOUBLE;
                } else if (operand.equals("byte")) {
                    op = T_BYTE;
                } else if (operand.equals("short")) {
                    op = T_SHORT;
                } else if (operand.equals("int")) {
                    op = T_INT;
                } else if (operand.equals("long")) {
                    op = T_LONG;
                } else {
                    err("Unknown type for newarray: " + operand);
                }
            } else {
                op = parseInt(operand);
            }

            mv.visitIntInsn(opcode, op);
            break;
        }

        case IINC: {
            opcheck("Expected iinc <var> <inc amount>", operand);
            String words[] = split(wsPattern, operand);
            int var = parseInt(words[0]);
            int increment = parseInt(words[1]);
            mv.visitIincInsn(var, increment);
            break;
        }

        default:
            err("INTERNAL ERROR: UNKNOWN TYPE OF INSTRUCTION");
        }
    }

    private void opcheck(String errMessage, String operand) {
        if (operand == null) {
            err(errMessage);
        }
    }

    private Object parseValue(String s, boolean isDoubleWord) {
        Object ret = null;
        if (s == null) {
            err("Expected constant value ");
        }
        if (s.startsWith("\"")) {
            if (isDoubleWord) {
                err("long or double value expected instead of string");
            }
            if (s.charAt(s.length() - 1) != '"') {
                err("Ill-formed string");
            }
            ret = s.substring(1, s.length() - 1);
        } else if (s.startsWith("L")) {
            // class name
            ret = Type.getType(s);
        } else {
            if (s.indexOf('.') == -1) {
                if (isDoubleWord) {
                    ret = (Long) parseLong(s);
                } else {
                    ret = (Integer) parseInt(s);
                }
            } else {
                if (isDoubleWord) {
                    ret = (Double) parseDouble(s);
                } else {
                    ret = (Float) parseFloat(s);
                }
            }
        }
        return ret;
    }

    int parseInt(String s) {
        if (s == null) {
            err("Expected integer");
        }
        try {
            return Integer.parseInt(s.trim());
        } catch (NumberFormatException nfe) {
            err("Expected integer value, got " + s);
        }
        return 0;
    }

    long parseLong(String s) {
        if (s == null) {
            err("Expected long");
        }
        try {
            return Long.parseLong(s.trim());
        } catch (NumberFormatException nfe) {
            err("Expected long value, got " + s);
        }
        return 0L;
    }

    float parseFloat(String s) {
        if (s == null) {
            err("Expected float");
        }
        try {
            return Float.parseFloat(s);
        } catch (NumberFormatException nfe) {
            err("Expected float, got " + s);
        }
        return 0.0f;
    }

    double parseDouble(String s) {
        if (s == null) {
            err("Expected float");
        }
        try {
            return Double.parseDouble(s);
        } catch (NumberFormatException nfe) {
            err("Expected double, got " + s);
        }
        return 0.0;
    }

    Label getLabel(String s) {
        if (s == null) {
            err("Expected label string");
        }
        Label ret = labels.get(s);
        if (ret == null) {
            ret = new Label();
            labels.put(s, ret);
        }
        return ret;
    }

    private void err(String s) {
        String msg = String.format("%s: %d: %s\n", fileName, line.n, s);
        msg += line.s;
        throw new AsmException(msg);
    }

    // return the next non-empty line after stripping comments
    private Line readLine() {
        if (bufferedLine != null) {
            line = bufferedLine;
            bufferedLine = null;
            return line;
        }
        while (true) {
            Line l = getLine();
            String s = l.s.trim();
            s = commentPattern.matcher(s).replaceAll("");
            if (s.length() > 0) {
                l.s = s;
                line = l;
                return l;
            }
        }
    }

    private void putBackLine() {
        bufferedLine = line;
    }

    boolean eofSeen = false;

    private Line getLine() {
        if (eofSeen) {
            throw new EOF();
        }
        try {
            String s = reader.readLine();
            if (s == null) {
                eofSeen = true;
                s = "";
            }
            return new Line(reader.getLineNumber(), s);
        } catch (IOException ioe) {
            ioe.printStackTrace();
            throw new EOF();
        }
    }

    boolean match(String s, Pattern p) {
        lastMatch = p.matcher(s);
        lastPattern = p;
        return lastMatch.find();
    }

    boolean lineMatch(Pattern p) {
        lastMatch = p.matcher(line.s);
        lastPattern = p;
        return lastMatch.find();
    }

    String group(int i) {
        String ret = lastMatch.group(i);
        return ret;
    }

    int groupCount() {
        return lastMatch.groupCount();
    }

    static String[] split(Pattern p, String s) {
        return p.split(s);
    }

    private void write() throws IOException {
        String dir = outputDir + "/" + getDirName(className);
        mkdir(dir);
        String fileName = outputDir + '/' + className + ".class";
        FileOutputStream fos = new FileOutputStream(fileName);
        fos.write(cv.toByteArray());
        fos.close();
        log.info("Wrote: " + fileName);
    }

    private static void mkdir(String dir) throws IOException {
        File f = new File(dir);
        if (!f.exists()) {
            if (!f.mkdirs()) {
                throw new IOException("Unable to create directory: " + dir);
            }
        }
    }

    private static String getDirName(String className) {
        int end = className.lastIndexOf('/');
        return (end == -1) ? "" : className.substring(0, end);
    }

    private static List<String> parseArgs(String[] args) {
        ArrayList<String> ret = new ArrayList<String>(args.length);
        for (int i = 0; i < args.length; i++) {
            String arg = args[i];
            if (arg.equals("-d")) {
                outputDir = args[++i];
            } else if (arg.equals("-q")) {
                quiet = true;
            } else if (arg.equals("-nf")) {
                computeFrames = false;
            } else {
                ret.add(arg);
            }
        }
        return ret;
    }

}

@SuppressWarnings("serial")
class EOF extends RuntimeException {
}

class Line {
    int n;
    String s;

    Line(int num, String str) {
        n = num;
        s = str;
    }

    public String toString() {
        return String.format("%4d: %s\n", n, s);
    }

    public boolean startsWith(String str) {
        return s.startsWith(str);
    }
}

@SuppressWarnings("serial")
class StringList extends ArrayList<String> {
    public String[] toArray() {
        String[] ret = new String[size()];
        return this.toArray(ret);
    }
}

@SuppressWarnings("serial")
class AsmException extends RuntimeException {
    public AsmException(String s) {
        super(s);
    }
}