/* --- Copyright Jonathan Meyer 1997. All rights reserved. -----------------
 > File:        jasmin/src/jasmin/ClassFile.java
 > Purpose:     Uses a parser and the JAS package to create Java class files
 > Author:      Jonathan Meyer, 10 July 1996
 */

package jasmin;


/*
 * This class is a bit monolithic, and should probably be converted into
 * several smaller classes. However, for this specific application,
 * its acceptable.
 *
 */

import jas.*;
import java.io.*;
import java.util.*;

/**
 * A ClassFile object is used to represent the binary data that makes up a
 * Java class file - it also serves as the public
 * API to the Jasmin assembler, though users should beware: the API
 * is likely to change in future versions (though its such a small API
 * at the moment that changes are likely to have only a small impact).<p>
 *
 * To assemble a file, you first construct a jasmin.ClassFile object, then
 * call readJasmin() to read in the contents of a Jasmin assembly file, then
 * call write() to write out the binary representation of the class file.<p>
 *
 * There are a few other utility methods as well. See Main.java for an example
 * which uses all of the public methods provided in ClassFile.
 *
 * @author Jonathan Meyer
 * @version 1.05, 8 Feb 1997
 */
public class ClassFile {

    private static final boolean PARSER_DEBUG = false;

    // state info for the class being built
    String filename;
    ClassEnv class_env;
    String class_name;
    String source_name;
    Scanner scanner;

    // state info for the current method being defined
    ExceptAttr except_attr;
    Catchtable catch_table;
    LocalVarTableAttr var_table;
    LocalVarTypeTableAttr vtype_table;
    LineTableAttr line_table;
    CodeAttr  code;
    Method cur_method;
    Var    cur_field;
    Hashtable labels;
    StackMap stackmap;
    VerifyFrame verifyframe;
    Annotation cur_annotation;

    int line_label_count, line_num, stack_map_label_count;
    boolean auto_number, class_header;
    Insn buffered_insn;

    // state info for lookupswitch and tableswitch instructions
    Vector switch_vec;
    int low_value;
    int high_value;


    static final String BGN_METHOD = "jasmin_reserved_bgnmethod:";
    static final String END_METHOD = "jasmin_reserved_endmethod:";

    // number of errors reported in a file.
    int errors;

    //
    // Error reporting method
    //
    void report_error(String msg) { report_error(msg, false); }

    void report_error(String msg, boolean BadIntVal) {
        if(Main.DEBUG)
            throw new RuntimeException();

        errors++;

        // Print out filename/linenumber/message
        System.err.print(filename + ":");
        if (scanner == null)
            System.err.println(" " + msg + ".");
        else {
            String dia_line;
            int    dia_linnum, dia_charpos;

            if (BadIntVal && scanner.char_num >= 0) {
              dia_line    = scanner.int_line;
              dia_linnum  = scanner.int_line_num;
              dia_charpos = scanner.int_char_num;
            } else {
              dia_line    = scanner.line.toString();
              dia_linnum  = scanner.line_num;
              dia_charpos = scanner.char_num;
            }
            System.err.println(dia_linnum + ": " + msg + ".");
            if (scanner.char_num >= 0) {
                System.err.println(dia_line);

                // Print out where on the line the scanner got to
                int i;
                for (i = 0; i < dia_charpos; i++) {
                    if (dia_line.charAt(i) == '\t') {
                        System.err.print("\t");
                    } else {
                        System.err.print(" ");
                    }
                }
                System.err.println("^");
            }
        }
    }

    //
    // called by the .source directive
    //
    void setSource(String name) {
        source_name = name;
    }

    //
    // called by the .bytecode directive
    //
    void setVersion(Number version) {
        String str = version.toString();
        int idx = str.indexOf(".");
        if(!(version instanceof Float) || (idx == -1))
            report_error("invalid bytecode version number : " + str);
        class_env.setVersion(Short.parseShort(str.substring(0,idx)),
                             Short.parseShort(str.substring(idx+1,str.length())));
    }

    //
    // called by the .class directive
    //
    void setClass(String name, short acc) {
        class_name = name;
        class_env.setClass(new ClassCP(name));
        class_env.setClassAccess(acc);
        class_header = true;
    }

    //
    // called by the .super directive
    //
    void setSuperClass(String name) {
        class_env.setSuperClass(new ClassCP(name));
    }

    //
    // called by the .implements directive
    //
    void addInterface(String name) {
        class_env.addInterface(new ClassCP(name));
    }


    //
    // called by the .debug directive
    //
    void setSourceDebugExtension(String str) {
        class_env.setSourceDebugExtension(str);
    }


    //
    // called by the .enclosing directive
    //
    void setEnclosingMethod(String str) {
        try {
            if(str.indexOf("(") != -1) { // full method description
                String[] split = ScannerUtils.splitClassMethodSignature(str);
                class_env.setEnclosingMethod(split[0], split[1], split[2]);
            }
            else                         // just a class name
                class_env.setEnclosingMethod(str, null, null);
        } catch(IllegalArgumentException e) {
            report_error(e.toString());
        }
    }

    // parser diagnostics
    private static void opened_annotation(String fld) throws jasError
    { throw new jasError("Skipped .end annotation in " + fld); }

    private static void outside(String dir) throws jasError
    { throw new jasError("illegal use of " +dir+
                 " outside of method/field definition or class header"); }

    //
    // called at end of jasmin-header (resolve class variables)
    //
    public void endHeader() throws jasError
      {
        if(cur_annotation != null)
          opened_annotation("class header");

        class_env.endHeader();  // resolve annotations
        class_header = false;
      }

    //
    // called by the .signature directive
    //
    void setSignature(String str) throws jasError
    {
      SignatureAttr sig = new SignatureAttr(str);
      if (cur_method != null) {
        cur_method.setSignature(sig);
      } else if(cur_field != null) {
        cur_field.setSignature(sig);
      } else if(class_header) {
        class_env.setSignature(str);
      } else {
        outside(".signature");
      }
    }

    //
    // called by the .deprecated directive
    //
    //
    void setDeprecated() throws jasError
    {
      DeprecatedAttr depr = new DeprecatedAttr();
      if ( cur_method != null ) {
        cur_method.setDeprecated(depr);
      } else if(cur_field != null) {
        cur_field.setDeprecated(depr);
      } else if(class_header) {
        class_env.setDeprecated(depr);
      } else {
        outside(".deprecated");
      }
    }

    //
    // called by the .attribute directive
    //
    void addGenericAttr(String name, String file)
        throws IOException, jasError
    {
      GenericAttr gattr = new GenericAttr(name, file);
      if (cur_method != null) {
        flushInsnBuffer();
        if(code != null)  code.addGenericAttr(gattr);
        else cur_method.addGenericAttr(gattr);
      } else if(cur_field != null) {
        cur_field.addGenericAttr(gattr);
      } else if (class_header) {
        class_env.addGenericAttr(gattr);
      } else {
        outside(".attribute");
      }
    }

    //
    // called by the .inner directive
    //
    //
    public void addInner(short iacc, String name, String inner, String outer)
    { class_env.addInnerClass(iacc, name, inner, outer); }

    //
    // procedure group for annotation description
    //
    private static void annotation_internal() throws jasError
    { throw new jasError("logic error in .annotation parsing"); }

    void addAnnotation() throws jasError
    {
      if(cur_method == null) Annotation.ParserError();
      cur_annotation = cur_method.addAnnotation();
    }

    void addAnnotation(boolean visible, String clname, int paramnum)
        throws jasError
    {
      if(cur_method == null) Annotation.ParserError();
      cur_annotation = cur_method.addAnnotation(visible, clname, paramnum);
    }

    void addAnnotation(boolean visible, String clname) throws jasError
    {
      if (cur_method != null) {
        cur_annotation = cur_method.addAnnotation(visible, clname);
      } else if(cur_field != null) {
        cur_annotation = cur_field.addAnnotation(visible, clname);
      } else if (class_header) {
        cur_annotation = class_env.addAnnotation(visible, clname);
      } else {
        outside(".annotation");
      }
    }

    void addAnnotationField(String name, String type, String add)
        throws jasError
    {
      if(cur_annotation == null) annotation_internal();
      cur_annotation.addField(name, type, add);
    }

    void addAnnotationValue(Object value) throws jasError
    {
      if(cur_annotation == null) annotation_internal();
      cur_annotation.addValue(value);
    }

    void nestAnnotation() throws jasError
    {
      if(cur_annotation == null) annotation_internal();
      cur_annotation = cur_annotation.nestAnnotation();
    }

    void endAnnotation() throws jasError
    {
      if(cur_annotation == null)
        throw new jasError(".end annotation without .annotation");
      cur_annotation = cur_annotation.endAnnotation();
    }

    // called by the .field directive to begin 'prompted' field
    //
    void beginField(short access, String name, String desc, Object value)
        throws jasError
    {
      ConstAttr ca = null;

      if (value != null) {
        CP cp;
        // create a constant pool entry for the initial value
        if (value instanceof Integer) {
          cp = new IntegerCP(((Integer)value).intValue());
        } else if (value instanceof Float) {
          cp = new FloatCP(((Float)value).floatValue());
        } else if (value instanceof Double) {
          cp = new DoubleCP(((Double)value).doubleValue());
        } else if (value instanceof Long) {
          cp = new LongCP(((Long)value).longValue());
        } else if (value instanceof String) {
          cp = new StringCP((String)value);
        } else {
          throw new jasError("usupported value type");
        }
        ca = new ConstAttr(cp);
      }
      cur_field = new Var(access, new AsciiCP(name), new AsciiCP(desc), ca);
    }

    //
    // called by the .end field directive to end 'prompted' field
    //
    void endField() throws jasError {
      if (cur_field == null)
        throw new jasError(".end field without .field");

      if (cur_annotation != null)
          opened_annotation("field");

      class_env.addField(cur_field);
      cur_field = null;
    }
    //
    // called by the .field directive
    //
    void addField(short access, String name, String desc,
                                String sig, Object value) throws jasError {
        beginField(access, name, desc, value);
        if (sig != null) cur_field.setSignature(new SignatureAttr(sig));
        endField();
    }

    //
    // called by the .method directive to start the definition for a method
    //
    void newMethod(String name, String descriptor, int access) {
        // set method state variables
        labels      = new Hashtable();
        code        = null;
        except_attr = null;
        catch_table = null;
        var_table   = null;
        vtype_table = null;
        line_table  = null;
        line_label_count  = 0;
        stack_map_label_count = 0;
        stackmap = null;
        verifyframe = null;
        cur_method = new Method((short)access, new AsciiCP(name),
                                new AsciiCP(descriptor));
    }


    //
    // called by the .end method directive to end the definition for a method
    //
    void endMethod() throws jasError {
        if (cur_method == null)
            throw new jasError(".end method without .method");

        if (cur_annotation != null)
            opened_annotation("method");

        if (code != null) {

            plantLabel(END_METHOD);
            flushInsnBuffer();

            if (catch_table != null) {
                code.setCatchtable(catch_table);
            }

            if (var_table != null) {
                code.setLocalVarTable(var_table);
            }
            if (vtype_table != null) {
                code.setLocalVarTypeTable(vtype_table);
            }
            if (line_table != null) {
                code.setLineTable(line_table);
            }
            if (stackmap != null) {
                code.setStackMap(stackmap);
            }
        }
        cur_method.setCode(code, except_attr);
        class_env.addMethod(cur_method);

        // clear method state variables
        cur_method  = null;
        code        = null;
        labels      = null;
        catch_table = null;
        line_table  = null;
        var_table   = null;
        vtype_table = null;
        stackmap    = null;
        verifyframe = null;
    }

// get last stackmap locals vector
    private Vector prevStack(int count) throws jasError {
        Vector prev = null;
        if(stackmap != null)
            prev = stackmap.getLastFrame(count);
        return prev;
    }

// define a new stack map frame (possible with copy previous)
    void beginStack(boolean copy) throws jasError {
        Vector prev = null;
        if(copy)
            prev = prevStack(0);
        verifyframe = new VerifyFrame(prev);
    }

// define a new stack map frame and copy 'n' previous
// (type-independet) elements
    void beginStack(int n) throws jasError {
        if(n <= 0)
            throw new jasError("Invalid counter", true);
        verifyframe = new VerifyFrame(prevStack(n));
    }

// define the offset of the current stack map frame
    void plantStackOffset(int n) {
        try {
            verifyframe.setOffset(n);
        } catch(jasError e) {
            report_error(e.toString());
        }
    }

    void plantStackOffset(String label) throws jasError {
        Label l = getLabel(label);
        try {
            verifyframe.setOffset(l);
        } catch(jasError e) {
            report_error(e.toString());
        }
    }

// add a local variable item to the current stack map frame
    void plantStackLocals(String item, String val) {
        try {
            verifyframe.addLocalsItem(item, val);
        } catch(jasError e) {
            report_error(e.toString());
        }
    }

// add a stack item element to the current stack map frame
    void plantStackStack(String item, String val) {
        try {
            verifyframe.addStackItem(item, val);
        } catch(jasError e) {
            report_error(e.toString());
        }
    }

// add the current stack map frame to the current stack map attribute
    void endStack() {
        if(!verifyframe.haveOffset()) {
            String l = "jasmin_reserved_SM:" + (stack_map_label_count++);
            try {
                plantLabel(l);
                verifyframe.setOffset(getLabel(l));
            } catch(jasError e) {
                report_error(e.toString());
            }
        }
        if(stackmap==null)
            stackmap = new StackMap(class_env);

        stackmap.addFrame(verifyframe);
        verifyframe = null; // PARANOYA
    }


    //
    // plant routines - these use addInsn to add instructions to the
    //                  code for the current method.
    //

    //
    // used for instructions that take no arguments
    //
    void plant(String name) throws jasError {
        InsnInfo insn = InsnInfo.get(name);
        autoNumber();
        flushInsnBuffer();

        if (insn.args.equals("")) {
            bufferInsn(new Insn(insn.opcode));
        } else {
            throw new jasError("Missing arguments for instruction " + name);
        }
    }

    //
    // used for relative branch targets (ie $+5, $-12, ...)
    //
    void plantRelativeGoto(String name, int val) throws jasError {
        InsnInfo insn = InsnInfo.get(name);
        if (insn.args.equals("label")) {
            bufferInsn(new Insn(insn.opcode, val, true));
        } else {  // forward the request for "normal" instructions
            plant(name, val);
        }
    }


    //
    // used for iinc
    //
    void plant(String name, int v1, int v2)
        throws jasError
    {
        InsnInfo insn = InsnInfo.get(name);
        autoNumber();
        flushInsnBuffer();

        if (insn.args.equalsIgnoreCase("ii")) {
            bufferInsn(new IincInsn(v1, v2, insn.args.charAt(0) == 'I'));
        } else {
            throw new jasError("Bad arguments for instruction " + name);
        }
    }

    //
    // used for instructions that take an integer parameter
    // (branches are part of this, the int is converted to a label)
    //
    void plant(String name, int val) throws jasError {
        InsnInfo insn = InsnInfo.get(name);
        CodeAttr code = _getCode();
        autoNumber();
        flushInsnBuffer();

        if (insn.args.equalsIgnoreCase("i")) {
            bufferInsn(new Insn(insn.opcode, val, insn.args.charAt(0) == 'I'));
        } else if (insn.args.equals("constant")) {
            bufferInsn(new Insn(insn.opcode, new IntegerCP(val)));
        } else if (insn.args.equals("bigconstant")) {
            bufferInsn(new Insn(insn.opcode, new LongCP(val)));
        } else if (insn.args.equals("label")) {
            plant(name, String.valueOf(val));        // the target is not signed
                                                     // assume it is a label
        } else {
            throw new jasError("Bad arguments for instruction " + name);
        }
    }

    //
    // used for ldc and other instructions that take a numeric argument
    //
    void plant(String name, Number val) throws jasError {
        InsnInfo insn = InsnInfo.get(name);
        CodeAttr code = _getCode();
        autoNumber();
        flushInsnBuffer();

        if (insn.args.equalsIgnoreCase("i") && (val instanceof Integer)) {
            bufferInsn(new Insn(insn.opcode, val.intValue(), insn.args.charAt(0) == 'I'));
        } else if (insn.args.equals("constant")) {
            if (val instanceof Integer || val instanceof Long) {
                bufferInsn(new Insn(insn.opcode,
                             new IntegerCP(val.intValue())));
            } else if (val instanceof Float || val instanceof Double) {
                bufferInsn(new Insn(insn.opcode,
                             new FloatCP(val.floatValue())));
            }
        } else if (insn.args.equals("bigconstant")) {
            if (val instanceof Integer || val instanceof Long) {
                bufferInsn(new Insn(insn.opcode,
                             new LongCP(val.longValue())));
            } else if (val instanceof Float || val instanceof Double) {
                bufferInsn(new Insn(insn.opcode,
                             new DoubleCP(val.doubleValue())));
            }
        } else {
            throw new jasError("Bad arguments for instruction " + name);
        }
    }

    //
    // used for ldc <quoted-string>
    //
    void plantString(String name, String val) throws jasError {
        InsnInfo insn = InsnInfo.get(name);
        autoNumber();
        flushInsnBuffer();

        if (insn.args.equals("constant")) {
            bufferInsn(new Insn(insn.opcode, new StringCP(val)));
        } else {
            throw new jasError("Bad arguments for instruction " + name);
        }
    }

    //
    // used for invokeinterface and multianewarray
    //
    void plant(String name, String val, int nargs)
            throws jasError
    {
        InsnInfo insn = InsnInfo.get(name);
        CodeAttr code = _getCode();
        autoNumber();
        flushInsnBuffer();

        if (insn.args.equals("interface")) {
            String split[] = ScannerUtils.splitClassMethodSignature(val);
            bufferInsn(new InvokeinterfaceInsn(
                         new InterfaceCP(split[0], split[1],
                         split[2]), nargs));

        } else if (insn.args.equals("marray")) {
            bufferInsn(new MultiarrayInsn(new ClassCP(val), nargs));
        } else {
            throw new jasError("Bad arguments for instruction " + name);
        }
    }

    //
    // used for instructions that take a word as a parameter
    // (e.g. branches, newarray, invokemethod)
    //
    void plant(String name, String val) throws jasError {
        InsnInfo insn = InsnInfo.get(name);
        CodeAttr code = _getCode();
        autoNumber();
        flushInsnBuffer();

        if (insn.args.equals("method")) {
            String split[] = ScannerUtils.splitClassMethodSignature(val);
            bufferInsn(new Insn(insn.opcode,
                         new MethodCP(split[0], split[1], split[2])));
        } else if (insn.args.equals("constant")) {
            bufferInsn(new Insn(insn.opcode, new ClassCP(val)));
        } else if (insn.args.equals("atype")) {
            int atype = 0;
            if (val.equals("boolean")) {
                atype = 4;
            } else if (val.equals("char")) {
                atype = 5;
            } else if (val.equals("float")) {
                atype = 6;
            } else if (val.equals("double")) {
                atype = 7;
            } else if (val.equals("byte")) {
                atype = 8;
            } else if (val.equals("short")) {
                atype = 9;
            } else if (val.equals("int")) {
                atype = 10;
            } else if (val.equals("long")) {
                atype = 11;
            } else {
                throw new jasError("Bad array type: " + name);
            }
            bufferInsn(new Insn(insn.opcode, atype, false));
        } else if (insn.args.indexOf("label")>=0) {
            bufferInsn(new Insn(insn.opcode, getLabel(val), scanner.line_num));
        } else if (insn.args.equals("class")) {
            bufferInsn(new Insn(insn.opcode, new ClassCP(val)));
        } else {
            throw new jasError("(gloups)Bad arguments for instruction " + name);
        }
    }

    //
    // used for instructions that take a field and a signature as parameters
    // (e.g. getstatic, putstatic)
    //
    void plant(String name, String v1, String v2)
            throws jasError
    {
        InsnInfo info = InsnInfo.get(name);
        CodeAttr code = _getCode();
        autoNumber();
        flushInsnBuffer();

        if (info.args.equals("field")) {
            String split[] = ScannerUtils.splitClassField(v1);
            if(split[1] == null)
                throw new jasError("can't extract field from "+v1);
            bufferInsn(new Insn(info.opcode,
                         new FieldCP(split[0], split[1], v2)));
        } else {
            throw new jasError("Bad arguments for instruction " + name);
        }
    }

    //
    // Lookupswitch instruction
    //
    void newLookupswitch() throws jasError {
        switch_vec = new Vector();
        autoNumber();
    };

    void addLookupswitch(int val, String label)
            throws jasError {
        switch_vec.addElement(new Integer(val));
        switch_vec.addElement(new LabelOrOffset(getLabel(label)));
    };

    void addLookupswitch(int val, int offset)
            throws jasError {
        switch_vec.addElement(new Integer(val));
        switch_vec.addElement(new LabelOrOffset(offset));
    };

    void endLookupswitch(String deflabel) throws jasError {
        flushInsnBuffer();
        Object[] offlabs = checkLookupswitch();
        int[] offsets = (int[])offlabs[0];
        LabelOrOffset[] labels = (LabelOrOffset[])offlabs[1];
        LabelOrOffset defl = new LabelOrOffset(getLabel(deflabel));
        bufferInsn(new LookupswitchInsn(defl, offsets, labels));
    }

    void endLookupswitch(int defoffset) throws jasError {
        flushInsnBuffer();
        Object[] offlabs = checkLookupswitch();
        int[] offsets = (int[])offlabs[0];
        LabelOrOffset[] labels = (LabelOrOffset[])offlabs[1];
        bufferInsn(new LookupswitchInsn(new LabelOrOffset(defoffset),
                          offsets, labels));
    }


    //
    // called by both endLookupswitch() methods
    //
    private Object[] checkLookupswitch() {
        int n = switch_vec.size() >> 1;
        int offsets[] = new int[n];
        LabelOrOffset labels[] = new LabelOrOffset[n];
        Enumeration e = switch_vec.elements();
        int i = 0;
        while (e.hasMoreElements()) {
            offsets[i] = ((Integer)e.nextElement()).intValue();
            labels[i] = (LabelOrOffset)e.nextElement();
            i++;
        }
        Object result[] = { offsets, labels };
        return result;
    }


    //
    // Tableswitch instruction
    //
    void newTableswitch(int lowval) throws jasError {
        newTableswitch(lowval, -1);
    };

    void newTableswitch(int lowval, int hival) throws jasError {
        switch_vec = new Vector();
        low_value = lowval;
        high_value = hival;
        autoNumber();
    };

    void addTableswitch(String label) throws jasError {
        switch_vec.addElement(new LabelOrOffset(getLabel(label)));
    };

    void addTableswitch(int offset) throws jasError {
        switch_vec.addElement(new LabelOrOffset(offset));
    };

    void endTableswitch(String deflabel) throws jasError {
        flushInsnBuffer();
        LabelOrOffset labels[] = checkTableswitch();
        bufferInsn(new TableswitchInsn(low_value,
                           low_value+labels.length-1,
                           new LabelOrOffset(getLabel(deflabel)),
                           labels));
    }

    void endTableswitch(int defoffset) throws jasError {
        flushInsnBuffer();
        LabelOrOffset labels[] = checkTableswitch();
        bufferInsn(new TableswitchInsn(low_value,
             low_value+labels.length-1, new LabelOrOffset(defoffset), labels));
    }


    //
    // called by both endTableswitch() methods
    //
    private LabelOrOffset[] checkTableswitch() {
        int n = switch_vec.size();
        LabelOrOffset labels[] = new LabelOrOffset[n];
        Enumeration e = switch_vec.elements();
        int i = 0;
        while (e.hasMoreElements()) {
            labels[i] = (LabelOrOffset)e.nextElement();
            i++;
        }
        if (high_value != -1 && (high_value != low_value + n - 1)) {
            report_error("tableswitch - given incorrect value for <high>");
        }
        return labels;
    }


    // Used by the parser to tell ClassFile what the line number
    // for the next statement is. ClassFile's autoNumber mechanism
    // uses this info.
    void setLine(int l) { line_num = l; }

    //
    // If auto_number is true, output debugging line number table
    // for Jasmin assembly instructions.

    //
    void autoNumber() throws jasError {
        if (auto_number) {
            // use the line number of the last token
            addLineInfo(line_num);
        }
    }

    //
    // Label management
    //

    //
    // gets the Label object for a label, creating it if it doesn't exist
    //
    Label getLabel(String name) throws jasError {

        // check that we are inside of a method definition
        if (cur_method == null) {
            throw new jasError( "illegal use of label outside of method definition");
        }

        Label lab = (Label)labels.get(name);
        if (lab == null) {
            lab = new Label(name);
            labels.put(name, lab);
        }
        return lab;
    }

    //
    // defines a label
    //
    void plantLabel(String name) throws jasError {
    //    System.out.println("planting label "+name);
        try {
            Integer.parseInt(name);
          // the label is a number, we must add it *before* the buffered insn
          // this is the reason to use a buffer instructions
            _getCode().addInsn(getLabel(name));
            flushInsnBuffer();
        } catch(NumberFormatException e) {
          // traditional label (word), add it *after* the buffered insn
            flushInsnBuffer();
            bufferInsn(getLabel(name));
        }
    }

// if there is an instruction in the buffer, add it to the code
// and put the new instruction in the buffer
    void bufferInsn(Insn i) throws jasError{
        if(i == null)
            throw new jasError("trying to buffer a null instruction");
        if(buffered_insn != null)
            flushInsnBuffer();
        buffered_insn = i;
    }

// if the buffer is not empty, add the instruction to the code array
    void flushInsnBuffer() throws jasError {
        if(buffered_insn != null) {
            _getCode().addInsn(buffered_insn);
            buffered_insn = null;
        }
    }

    //
    // used by the .var directive
    //
    void addVar(String startLab, String endLab,
                String name, String desc, String sign, int var_num)
               throws jasError {
        if (startLab == null) {
            startLab = BGN_METHOD;
        }

        if (endLab == null) {
            endLab = END_METHOD;
        }
        Label slab, elab;
        slab = getLabel(startLab);
        elab = getLabel(endLab);

        if (var_table == null) {
            var_table = new LocalVarTableAttr();
        }

        var_table.addEntry(new LocalVarEntry(slab, elab, name, desc, var_num));

        if (sign != null) {
            if (vtype_table == null) {
              vtype_table = new LocalVarTypeTableAttr();
            }

            vtype_table.addEntry(new LocalVarEntry(slab, elab, name, sign, var_num));
        }
    }

    void addVar(int startOffset, int endOffset, String name,
                  String desc, String sign, int var_num) throws jasError {
        if (var_table == null) {
            var_table = new LocalVarTableAttr();
        }
        var_table.addEntry(new LocalVarEntry(startOffset, endOffset,
                           name, desc, var_num));

        if (sign != null) {
            if (vtype_table == null) {
              vtype_table = new LocalVarTypeTableAttr();
            }

            vtype_table.addEntry(new LocalVarEntry(startOffset, endOffset,
                                 name, sign, var_num));
        }
    }

    //
    // used by .line directive
    //
    void addLineInfo(int line_num) throws jasError {
        String l = "jasmin_reserved_L:" + (line_label_count++);
        if (line_table == null) {
            line_table = new LineTableAttr();
        }
        plantLabel(l);
        line_table.addEntry(getLabel(l), line_num);
    }

    void addLine(int line_num) throws jasError {
        if (!auto_number) {
            addLineInfo(line_num);
        }
    }

    //
    // used by the .throws directive
    //
    void addThrow(String name) throws jasError {

        // check that we are inside of a method definition
        if (cur_method == null) {
            throw new jasError( "illegal use of .throw outside of method definition");
        }

        if (except_attr == null) {
            except_attr = new ExceptAttr();
        }
        except_attr.addException(new ClassCP(name));
    }

    //
    // used by the .catch directive
    //
    void addCatch(String name, String start_lab, String end_lab,
                                String branch_lab) throws jasError {
        ClassCP class_cp = checkCatch(name);

        catch_table.addEntry(getLabel(start_lab), getLabel(end_lab),
                             getLabel(branch_lab), class_cp);
    }


    void addCatch(String name, int start_off, int end_off,
                                int branch_off) throws jasError {
        ClassCP class_cp = checkCatch(name);

        catch_table.addEntry(start_off, end_off, branch_off, class_cp);
    }

    short checkLimit(int v) throws jasError {
        if(v < 0 || v > 65535)
            throw new jasError("Illegal limit value", true);
        return (short)v;
    }

    //
    // used by the .limit stack directive
    //
    void setStackSize(int v) throws jasError {
        _getCode().setStackSize(checkLimit(v));
    }

    //
    // used by the .limit vars directive
    //
    void setVarSize(int v) throws jasError {
        _getCode().setVarSize(checkLimit(v));
    }

    // --- Private stuff ---


    //
    // verifications made by both addCatch() methods
    //
    private ClassCP checkCatch(String name) throws jasError {
        ClassCP class_cp;
        // check that we are inside of a method definition
        if (cur_method == null) {
            throw new jasError( "illegal use of .catch outside of method definition");
        }

        if (catch_table == null) {
            catch_table = new Catchtable();
        }

        if (name.equals("all")) {
            class_cp = null;
        } else {
            class_cp = new ClassCP(name);
        }
        return class_cp;
    }


    //
    // returns the code block, creating it if it doesn't exist
    //
    CodeAttr _getCode() throws jasError {

        // check that we are inside of a method definition
        if (cur_method == null) {
            throw new jasError("illegal use of instruction outside of method definition");
        }

        if (code == null) {
            code = new CodeAttr();
            plantLabel(BGN_METHOD);
        }

        return (code);
    }


    // PUBLIC API TO JASMIN:

    /** Makes a new ClassFile object, used to represent a Java class file.
      * You can then use readJasmin to read in a class file stored in
      * Jasmin assembly format.
      */

    public ClassFile() {}

    /**
      * Parses a Jasmin file, converting it internally into a binary
      * representation.
      * If something goes wrong, this throws one of
      * an IOException, or a jasError, or one of a few other exceptions.
      * I'll tie this down more formally in the next version.
      *
      * @param input is the stream containing the Jasmin assembly code for the
      *        class.
      *
      * @param name is the name of the stream. This name will be
      *        concatenated to error messages printed to System.err.
      *
      * @param numberLines true if you want Jasmin to generate line
      *        numbers automatically, based on the assembly source, or
      *        false if you are using the ".line" directive and don't
      *        want Jasmin to help out.
      */
    public void readJasmin(Reader input, String name,
                           boolean numberLines)
                   throws IOException, Exception {
        // initialize variables for error reporting
        errors = 0;
        filename = name;
        source_name = name;

        //initialize local-frame variables
        cur_method = null;
        cur_field = null;
        cur_annotation = null;
        class_header = false;

        // if numberLines is true, we output LineTableAttr's that indicate what line
        // numbers the Jasmin code falls on.
        auto_number = numberLines;

        // Parse the input file
        class_env = new ClassEnv();

        scanner = new Scanner(input);
        parser parse_obj = new parser(this, scanner);

        if (PARSER_DEBUG) {
            // for debugging
            parse_obj.debug_parse();
        } else {
            parse_obj.parse();
        }
    }

    /**
     * Returns the number of warnings/errors encountered while parsing a file.
     * 0 if everything went OK.
     */
    public int errorCount() {
        return errors;
    }

    /**
     * Returns the name of the class in the file (i.e. the string given to
     * the .class parameter in Jasmin)
     *
     */
    public String getClassName() {
        return class_name;
    }

    /**
     * Writes the binary data for the class represented by this ClassFile
     * object to the specified
     * output stream, using the Java Class File format. Throws either an
     * IOException or a jasError if something goes wrong.
     */
    public void write(OutputStream outp) throws IOException, jasError {
        class_env.setSource(source_name);
        class_env.write(new DataOutputStream(outp));
    }
};

/* --- Revision History ---------------------------------------------------
--- Iouri Kharon, Aug 10 2006
    Added 'Wide' prefix support to some instructions

--- Iouri Kharon, Feb 17 2006
    Added extended syntax for .stack

--- Iouri Kharon, Dec 30 2005
    Added multiline .field, directive .inner and .anotation

--- Iouri Kharon, Dec 20 2005
    Added LocalVariableTypeTable support in addVar methods (called by .var)

--- Daniel Reynaud, Oct 22 2005
    Added setSourceDebugExtension() method (called by .debug)
    Added setEnclosingMethod() method (called by .enclosing)

--- Daniel Reynaud, Oct 19 2005
    Added setVersion() method (called by .bytecode)
    Changed BGN_METHOD, END_METHOD and line number label to avoid collision

--- Jonathan Meyer, April 11 1997
    Fixed bug where source_name was not being set in class_env.

--- Jonathan Meyer, Mar 1 1997
    Renamed "Jasmin" class "ClassFile".

--- Jonathan Meyer, Feb 8 1997
    Converted to non-static. Made a public API. Split off InsnInfo to a
    separate file.

--- Jonathan Meyer, Oct 1 1996
    Added addInterface method, used by the .implements directive.

--- Jonathan Meyer, July 25 1996
    Added setLine and line_num, to fix problem with autoNumber.
    Added report_error method.

--- Jonathan Meyer, July 24 1996 added version constant.
*/
