autohit.creator.compiler.SimCompiler.java Source code

Java tutorial

Introduction

Here is the source code for autohit.creator.compiler.SimCompiler.java

Source

/**
 * AUTOHIT 2003
 * Copyright Erich P Gatejen (c) 1989,1997,2003,2004
 * 
 * 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 of the License, 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; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Additional license information can be found in the documentation.
 * @author Erich P Gatejen
 */
package autohit.creator.compiler;

//import org.apache.commons.collections.ExtendedProperties;
import java.util.HashMap;
import java.util.ListIterator;
import java.util.Stack;

import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import autohit.common.AutohitErrorCodes;
import autohit.common.NOPair;
import autohit.common.NVPair;
import autohit.creator.SimLanguage;
import autohit.server.SystemContext;
import autohit.vm.VMExecutableWrapper;
import autohit.vm.i.VMICall;
import autohit.vm.i.VMIMethod;
import autohit.vm.i.VMIClear;
import autohit.vm.i.VMIEval;
import autohit.vm.i.VMIExec;
import autohit.vm.i.VMIFault;
import autohit.vm.i.VMIFetch;
import autohit.vm.i.VMIGoto;
import autohit.vm.i.VMIIf;
import autohit.vm.i.VMIJump;
import autohit.vm.i.VMILoad;
import autohit.vm.i.VMIMath;
import autohit.vm.i.VMIMerge;
import autohit.vm.i.VMINew;
import autohit.vm.i.VMINop;
import autohit.vm.i.VMIRScope;
import autohit.vm.i.VMIReduce;
import autohit.vm.i.VMIRight;
import autohit.vm.i.VMIScope;
import autohit.vm.i.VMIStore;
import autohit.vm.i.VMISubr;
import autohit.vm.i.VMIAssert;

/**
 * This is the a Sim compiler.  It will compile xml documents that conform
 * to the "%autohit.dtd.location%/sim.dtd" dtd into a Sim object.
 * <p>
 * WARNING!!!  For the compiler to work, the sim.dtd must be at the location
 * specified in the system property autohit.dtd.location.
 * <p>
 * The private methods translate Token() matches the textual tokens, as defined in the
 * dtd to numerics that are used by the rest of the compiler.  Any new tokens
 * need to be added to that method.
 * <p>
 * I can think of several more elegant approaches to making this compiler, but until the
 * XML parsers settle a bit, I'm not going to bother...  
 * <p>
 * IMPORTANT NOTE!!!!  The XML parser WITH DTD ensure instructions are
 * coded in the proper order.  ALL of the code below assumes this!!!
 * If instructions come out of order or in disallowed places 
 * (like a seek outside of a get), I GUARANTEE you'll get a runaway 
 * compiler...
 * <p>
 * Logging will be done to the autohit.creator.sim namespace.  It is set for
 * pretty and 
 *
 * @author Erich P. Gatejen
 * @version 1.1
 * <i>Version History</i>
 * <code>EPG - Initial - 14Apr03<br>
 * EPG - Update to add goto and references - 13Jul03</code> 
 * 
 */
public class SimCompiler extends XmlCompiler implements SimLanguage {

    private final static String DTD_NAME = "sim.dtd";
    private final static String MY_TYPE_OF_EXEC = "sim1";

    // We'll do subtractive compares for IF.  This might change.

    // Lexical elements and attriutes

    // parse tokens

    /**
     *  The work-in-progress object code
     */
    protected VMExecutableWrapper ob;

    /**
     *  Symbol table for lookups
     */
    protected HashMap symboltable;

    /**
     *  Stack for fixups
     */
    protected Stack fixupstack;

    /**
     *  Local name - UID
     */
    private String localname = "UNKNOWN";

    /**
     *  Default Constructor.  This is the only and default constructor.
     *
     *  @throws Exception any exception invalidates the compiler.
     */
    public SimCompiler() throws Exception {
        super();

    }

    /**
     *  Constructor.  This is the only and default constructor.
     *
     *  @throws Exception any exception invalidates the compiler.
     */
    public SimCompiler(SystemContext sc) throws Exception {
        super(DTD_NAME, sc);
    }

    /**
     * Compile the xml tree into an VMExecutable object.
     * 
     * We will create a new log for each run, so that we can uniquely
     * identify them.
     *
     * @param xd   A parsed XML document.
     * @return a reference to the target object, in this case it will be a VMExecutableWrapper, or null if it failed.
     * @see autohit.vm.VMExecutableWrapper
     */
    public Object build(Document xd) {

        int idx;
        NodeList rootChildren;
        Element itemTree = null;
        Element codeTree = null;
        int numNodes;
        Node scratchNode;
        String scratchString;

        // Any exception or verification check aborts the compile
        try {

            // Ok, build our working Sim object
            ob = new VMExecutableWrapper();
            ob.create();

            // Create our symbol table and fixup stack
            symboltable = new HashMap();
            fixupstack = new Stack();

            // set defaults attributes
            ob.exec.major = 0;

            // set any default attributes
            ob.exec.type = MY_TYPE_OF_EXEC;
            ob.exec.minor = 0;
            ob.exec.output = null; // assume there is nothign to return

            // Get the root element and normalize
            Element root = (Element) xd.getDocumentElement();
            root.normalize();

            // Peal out the <info> and <code> sub-trees
            rootChildren = (NodeList) root.getChildNodes();
            numNodes = rootChildren.getLength();
            while (numNodes > 0) {
                scratchNode = rootChildren.item(numNodes - 1);
                if (scratchNode instanceof Element) {
                    scratchString = scratchNode.getNodeName();
                    if (scratchString.charAt(0) == 'i') {
                        itemTree = (Element) scratchNode;
                    } else if (scratchString.charAt(0) == 'c') {
                        codeTree = (Element) scratchNode;
                    }
                }
                numNodes--;
            }

            if (itemTree == null) {
                runtimeError("Missing infomation <info> block.");
            }

            if (codeTree == null) {
                runtimeError("Missing infomation <code> block.");
                throw new Exception();
            }

            // Deal with the <info> tree
            NodeList itemTreeChildren = itemTree.getChildNodes();
            for (idx = 0; idx < itemTreeChildren.getLength(); idx++) {
                scratchNode = itemTreeChildren.item(idx);

                // pull only Elements
                if (scratchNode instanceof Element) {
                    processItem((Element) scratchNode);
                }

            }

            // Deal with the <code> tree
            // Basicall, I'm gonna go wtih recursion.  I don't think it should
            // get very deep.   
            try {
                processCode(codeTree);

                // Put a NOP on the end of the executable
                ob.emit(new VMINop());
                ob.clean();

                // fixup goto symbols
                ListIterator li = fixupstack.listIterator();
                VMIGoto jcandidate;
                NOPair nocandidate;
                Integer currentgoto;
                while (li.hasNext()) {
                    nocandidate = (NOPair) li.next();
                    if (symboltable.containsKey(nocandidate.n)) {
                        jcandidate = (VMIGoto) nocandidate.o;
                        currentgoto = (Integer) symboltable.get(nocandidate.n);
                        jcandidate.t = currentgoto.intValue();
                        runtimeDebug("Fixup GOTO for label=" + nocandidate.n + " target=" + jcandidate.t);
                    } else {
                        runtimeError("Broken GOTO.  No label for " + nocandidate.n + ".");
                    }
                }

            } catch (Exception e) {
                // an otherwise uncaught exception.  A runaway compiler...
                runtimeError("FATAL ERROR.  Runaway compilation errors.  Stopping compile.");
                ob = null;
            }

        } catch (Exception e) {
            myLog.error("CRITICAL ERROR encountered.  Stopping compile of " + localname + ".  " + e.toString(),
                    AutohitErrorCodes.CODE_COMPILE_ERROR);
            myLog.error(e.toString());
            ob = null; // leave the objectCode as null;
        }

        // ditch data as it falls out of scope
        symboltable = null;
        fixupstack = null;

        // clean up logs
        int err = numberErrors();
        runtimeLog.error("Total errors for " + localname + " : " + err);
        runtimeLog.warning("Total errors for " + localname + " : " + numberWarnings());
        if (err > 0) {
            runtimeLog.info("COMPILE FAILED  " + localname + " DUE TO ERRORS.");
            ob = null;
        }
        return ob;
    }

    /**
     *  Processes info section tags. 
     */
    private void processItem(Element en) throws Exception {
        String tempText;
        String name;
        //runtimeDebug("ENTER --- " + en.getTagName());
        name = en.getTagName().toLowerCase();
        NodeList itemTreeChildren;
        int idx;
        Node sNode;

        // Parse the token and call a handler
        if (name.charAt(0) == 'n') {
            if (name.charAt(1) == 'a') {
                //NAME
                tempText = getText(en.getFirstChild());
                if (tempText == null) {
                    runtimeError("Empty <name> tag.");
                } else {
                    ob.exec.name = tempText;
                    runtimeDebug("TAG <name> name= " + ob.exec.name);
                }
                // optional uid attribute
                if (en.hasAttribute(ATTR_UID)) {
                    ob.exec.uid = en.getAttribute(ATTR_UID);
                    localname = ob.exec.uid;
                    runtimeDebug("TAG <name> uid= " + ob.exec.uid);
                }
            } else {
                // NOTE - optional
                tempText = getText(en.getFirstChild());
                ob.exec.note = tempText;
            }
        } else if (name.charAt(0) == 'v') {
            // VERSION
            ob.exec.major = 0;
            try {
                if (en.hasAttribute(ATTR_VERSIONNUMBER)) {
                    tempText = en.getAttribute(ATTR_VERSIONNUMBER);
                    ob.exec.major = Integer.parseInt(tempText);
                    runtimeDebug("TAG <version> num= " + ob.exec.major);
                } else {
                    runtimeError("ERROR: TAG<version> Attr num not present.");
                }
            } catch (Exception e) {
                runtimeError("ERROR: TAG<version> Could not parse value for Attr num.");
            }

        } else if (name.charAt(0) == 'i') {
            if (name.charAt(1) == 'o') {
                // IO - Recurse with it's children.
                itemTreeChildren = en.getChildNodes();
                for (idx = 0; idx < itemTreeChildren.getLength(); idx++) {
                    sNode = itemTreeChildren.item(idx);
                    if (sNode instanceof Element) {
                        processItem((Element) sNode);
                    }
                }
            } else {
                // INPUT - NOT SUPPORTED
            }

        } else if (name.charAt(0) == 'b') {
            // BUFFER - NOT SUPPORTED

        } else if (name.charAt(0) == 'o') {
            ob.exec.output = new NVPair();
            // OUTPUT - Specifies the output variable
            ob.exec.output.name = en.getAttribute(ATTR_NAME);
            if (en.hasAttribute(ATTR_TYPE)) {
                ob.exec.output.value = en.getAttribute(ATTR_TYPE);
            }

        } else {
            runtimeError("Software Detected Fault: creator.compiler.SimCompiler.processItem().  The textual token ["
                    + en.getNodeName()
                    + "] should have NOT reached this code. Check the XML DTD associated with the SimCompiler.");
            throw (new Exception("Software Detected Fault in creator.compiler.SimCompiler.processItem()."));
        }

        //DEBUG
        //runtimeDebug("EXIT --- " + en.getTagName());
    }

    /**
     *  Every raw element will enter here.  Bascially, it dispatches it to it's specific
     *  handler.  It will do so for every child in the passed node. 
     */
    private void processCode(Element en) throws Exception {

        Element child;
        String name;
        int idx;
        Node scratchNode;
        NodeList itemTreeChildren = en.getChildNodes();

        //DEBUG
        //runtimeDebug("ENTER --- " + en.getTagName());

        for (idx = 0; idx < itemTreeChildren.getLength(); idx++) {

            scratchNode = itemTreeChildren.item(idx);

            // Only process elements
            if (!(scratchNode instanceof Element))
                continue;

            // Clean it
            child = (Element) scratchNode;
            name = child.getTagName().toLowerCase();

            // Parse the token and call a handler
            // Just not enough tokens to justify a state translation
            if (name.charAt(0) == 'a') {
                handleAssert(child);
            } else if (name.charAt(0) == 'b') {
                if (name.charAt(1) == 'l') {
                    handleBlock(child);
                } else {
                    handleBuffer(child);
                }
            } else if (name.charAt(0) == 'c') {
                handleCall(child);
            } else if (name.charAt(0) == 'e') {
                handleExec(child);
            } else if (name.charAt(0) == 'f') {
                handleFor(child);
            } else if (name.charAt(0) == 'g') {
                handleGoto(child);
            } else if (name.charAt(0) == 'i') {
                if (name.charAt(1) == 'n') {
                    handleInput(child);
                } else {
                    handleIf(child);
                }
            } else if (name.charAt(0) == 'l') {
                handleLabel(child);
            } else if (name.charAt(0) == 'm') {
                if (name.charAt(1) == 'e') {
                    handleMethod(child);
                } else {
                    handleMath(child);
                }
            } else if (name.charAt(0) == 's') {
                if (name.charAt(1) == 'e') {
                    handleSet(child);
                } else {
                    handleSubroutine(child);
                }
            } else if (name.charAt(0) == 'w') {
                handleWhile(child);
            } else if (name.charAt(0) == 'r') {
                handleReturn(child);
            } else {
                runtimeError("Software Detected Fault: autohit.creator.compiler.SimCompiler().  The textual token ["
                        + en.getNodeName()
                        + "] should have NOT reached this code. Check the XML DTD associated with the SimCompiler.");
                throw (new Exception("Software Detected Fault in creator.compiler.SimCompiler.processItem()."));
            }
        }
        //DEBUG
        //runtimeDebug("EXIT --- " + en.getTagName());
    }

    // == ===============================================================================
    // == =                         TOKEN HANDLERS                                      =
    // == ===============================================================================

    /**
     *  handle a block.  easy enough.  just scope it!.
     *  MICROCODE
     *  1- i.scope
     *   2- ANY
     *   3- i.rscope
     */
    private void handleBlock(Element en) {

        //runtimeDebug("handleBlock.");

        // 1- i.scope
        this.emitScope();

        // 2- ANY
        try {
            processCode(en);

        } catch (Exception e) {
            // Stop an error unravelling here....    
        }

        // 3- i.rscope
        this.emitRScope();
    }

    /**
     *  handle a Buffer.
     *  MICROCODE
     *  1- if (clear exists)   i.clear(name), ALREADY = true
     *   2- if (eval exists)   i.eval(eval), i.merge(name) 
     *  3-    else if (value exists) i.left(value), i.merge(name)
     *  4-    else if (buffer exists)i.reduce(buffer), i.merge(name)
     *  5-    else CLEAR = true         
     *  6- if (cdata exists)           i.load(cdata), i.merge(name)
     *  7-      else if (CLEAR true, ALREADY false)      i.clear(name)
     */
    private void handleBuffer(Element en) {

        String name = en.getAttribute(ATTR_NAME);
        String cdata;
        boolean clearFlag = false;
        boolean clearAlready = false;

        //runtimeDebug("handleBuffer.  name=" + name);

        // 1- if (clear exists)   i.clear(name)
        if (en.hasAttribute(ATTR_CLEAR)) {
            this.emitClear(name);
            clearAlready = true;
        }

        // 2- if (eval exists)   i.eval(eval), i.merge(name) 
        if (en.hasAttribute(ATTR_EVALUATOR)) {
            this.emitEval(en.getAttribute(ATTR_EVALUATOR));
            this.emitMerge(en.getAttribute(ATTR_NAME));

            // 3-    else if (value exists) i.load(value), i.merge(name)    
        } else if (en.hasAttribute(ATTR_VALUE)) {
            this.emitLoad(en.getAttribute(ATTR_VALUE));
            this.emitMerge(en.getAttribute(ATTR_NAME));

            // 4- else if (buffer exists)i.reduce(buffer), i.merge(name) 
        } else if (en.hasAttribute(ATTR_BUFFER)) {
            this.emitReduce(en.getAttribute(ATTR_BUFFER));
            this.emitMerge(en.getAttribute(ATTR_NAME));

            // 5-    else          i.clear(name), done
        } else {
            clearFlag = true;
        }

        // 6- if (cdata exists)    i.load(cdata), i.merge(name)
        try {
            cdata = getText(en.getFirstChild());
        } catch (Exception e) {
            cdata = null;
        }

        if (cdata != null) {
            this.emitLoad(cdata);
            this.emitMerge(en.getAttribute(ATTR_NAME));

            // 7- else if (CLEAR true, ALREADY false)      i.clear(name)
        } else if ((clearFlag == true) && (clearAlready == false)) {
            this.emitClear(name);
        }
    }

    /**
     *  handle call.
     *  MICROCODE
     * 1- i.scope
     * 2- (SET)*
     * 3- i.call(name)
     * 4- i.rscope
     * 5- if (result exist) i.store(result)
     */
    private void handleCall(Element en) {

        String name = en.getAttribute(ATTR_NAME);

        runtimeDebug("handleCall.  call=" + name);

        // 1- i.scope
        this.emitScope();

        // 2- (SET)*
        try {
            // recurse into the children
            processCode(en);

            // 3- i.call(name)
            this.emitCall(name);

        } catch (Exception e) {
            //   Stop an error unravelling here.  Close the scope and move on
            runtimeError("ERROR handleCall.  Broken call." + name);
            runtimeError(e.toString());
            this.emitRScope();
            return;
        }

        // 4- i.rscope
        this.emitRScope();

        // 5- if (result exist) i.store(result)
        if (en.hasAttribute(ATTR_RESULT)) {
            this.emitStore(en.getAttribute(ATTR_RESULT));
        }
    }

    /**
     *  handle call.
     *  MICROCODE
     * 1- i.scope
     * 2- (SET)*
     * 3- i.eval(name)
     * 4- i.method(method)
     * 5- i.rscope
     * 6- if (result exist) i.store(result)
     */
    private void handleMethod(Element en) {

        String name = en.getAttribute(ATTR_NAME);
        String method = en.getAttribute(ATTR_METHOD);

        runtimeDebug("handleMethod.  name=" + name + " method=" + method);

        // 1- i.scope
        this.emitScope();

        // 2- (SET)*
        try {
            // recurse into the children
            processCode(en);

            // 3- i.call(name)
            this.emitEval(name);

            // 4- i.call(name)
            this.emitMethod(method);

        } catch (Exception e) {
            //   Stop an error unravelling here.  Close the scope and move on
            runtimeError("ERROR handleMethod.  Broken call." + name);
            runtimeError(e.toString());
            this.emitRScope();
            return;
        }

        // 5- i.rscope
        this.emitRScope();

        // 5- if (result exist) i.store(result)
        if (en.hasAttribute(ATTR_RESULT)) {
            this.emitStore(en.getAttribute(ATTR_RESULT));
        }
    }

    /**
     *  handle exec.
     *  MICROCODE
     * 1- i.scope
     * 2- (INPUT)*
     * 3- i.exec(name)
     * 4- i.rscope
     * 5- if (result exist) i.store(result)
     */
    private void handleExec(Element en) {

        String name = en.getAttribute(ATTR_NAME);

        runtimeDebug("handleExec.  exec=" + name);

        // 1- i.scope
        this.emitScope();

        // 2- (INPUT)*
        try {
            // recurse into the children
            processCode(en);

            // 3- i.call(name)
            this.emitExec(name);

        } catch (Exception e) {
            //   Stop an error unravelling here.  Close the scope and move on
            runtimeError("ERROR handleExec.  Broken exec." + name);
            runtimeError(e.toString());
            this.emitRScope();
            return;
        }

        // 4- i.rscope
        this.emitRScope();

        // 5- if (result exist) i.store(result)
        if (en.hasAttribute(ATTR_RESULT)) {
            this.emitStore(en.getAttribute(ATTR_RESULT));
        }

    }

    /**
     *  handle a FOR
     *  MICROCODE
     * 1-  TOP:   i.scope
     * 2-       if (eval exists)   i.eval(eval)
     * 3-          else if (value exists) i.load(value)
     * 4-         else          !!ERROR   
     * 5-       i.new(count)
     * 6-  LOOP:   i.if(DONE)
     * 7-  DO:   (ANY)*
     * 8-       i.load("1")
     * 9-       i.right   
     * 10-       i.fetch(count)
     * 11-      i.math("-")      // LEFT RESULT will stay in LEFT
     * 12-      i.store(count)      
     * 13-      i.jump(LOOP)      
     * 14- DONE:   i.rscope
     */
    private void handleFor(Element en) {

        int loopstop;

        VMIIf myif;
        VMIJump myjump;

        //runtimeDebug("handleFor");

        // 1- i.scope
        this.emitScope();

        // 2- if (eval exists)   i.eval(eval)
        if (en.hasAttribute(ATTR_EVALUATOR)) {
            this.emitEval(en.getAttribute(ATTR_EVALUATOR));

            // 3-   else if (value exists) i.load(value)    
        } else if (en.hasAttribute(ATTR_VALUE)) {
            this.emitLoad(en.getAttribute(ATTR_VALUE));

            // 4-   else          !!ERROR   
        } else {
            runtimeError("ERROR Broken for.  No count value defined.");
            this.emitLoad(en.getAttribute(LITERAL_ZERO));
            // use 0 so we can finish compile
        }

        // 5-    i.new(count)
        this.emitNew(en.getAttribute("count"));

        // 6-  LOOP:   i.if(DONE)
        loopstop = ob.nextIP(); // loop stop points to #6
        myif = new VMIIf();
        // FIXUP the IF later
        ob.emit(myif);
        runtimeDebug("EMIT(" + loopstop + ") i.if target= FIXUP LATER");

        // 7-  DO:   (ANY)*
        try {
            processCode(en);
        } catch (Exception e) {
            //   Stop an error unravelling here.  Close the scope and move on
            runtimeError("ERROR Broken FOR inner.");
            runtimeError(e.toString());
        }

        // 8-  i.load(LITERAL_ONE)
        this.emitLoad(LITERAL_ONE);

        // 9-  i.right
        this.emitRight();

        // 10-  i.fetch(count)
        this.emitFetch(en.getAttribute(ATTR_COUNT));

        // 11-   i.math("-")
        this.emitMath(MINUS_OPERATION);

        // 12-  i.store(count)
        this.emitStore(en.getAttribute(ATTR_COUNT));

        // 13-   i.jump(LOOP)
        this.emitJump(loopstop);

        // 14- DONE:   i.rscope
        // First fixup the jump
        myif.t = ob.nextIP();
        runtimeDebug("FIXUP IF=" + myif.t);
        this.emitRScope();
    }

    /**
     *  handle a Set
     *  MICROCODE
     * 1- if (eval exists)         i.eval(eval)
     * 2-     else if (ref exists)   i.fetch(ref)
     * 3-    else if (value exists) i.load(value)
     * 4-    else (buffer exists)   i.reduce(buffer)
     * 5-    else                !!ERROR   
     * 6- if (new exists)         i.new(name)
     * 7-    else               i.store(name)
     */
    private void handleSet(Element en) {

        String name = en.getAttribute(ATTR_NAME);

        //runtimeDebug("handleSet  name=" + name);

        // 1- if (eval exists)         i.eval(eval)
        if (en.hasAttribute(ATTR_EVALUATOR)) {
            this.emitEval(en.getAttribute(ATTR_EVALUATOR));

            // 2- else if (ref exists)   i.fetch(ref)   
        } else if (en.hasAttribute(ATTR_REFERENCE)) {
            this.emitFetch(en.getAttribute(ATTR_REFERENCE));

            // 3- else if (value exists) i.load(value)    
        } else if (en.hasAttribute(ATTR_VALUE)) {
            this.emitLoad(en.getAttribute(ATTR_VALUE));

            // 4- else (buffer exists)   i.reduce(buffer) 
        } else if (en.hasAttribute(ATTR_BUFFER)) {
            this.emitReduce(en.getAttribute(ATTR_BUFFER));

            // 5- else                !!ERROR   
        } else {
            runtimeError("ERROR.  No source given for SET.");
            return;
        }

        // 6- if (new exists)         i.new(name)
        if (en.hasAttribute(ATTR_NEW)) {
            this.emitNew(name);

            // 7-    else      i.store(name)
        } else {
            this.emitStore(name);
        }
    }

    /**
     *  handle a Input
     *  MICROCODE
     * 1- if (eval exists)         i.eval(eval)
     * 2-    else if (value exists) i.load(value)
     * 3-    else (buffer exists)   i.reduce(buffer)
     * 4-    else                !!ERROR   
     * 5- i.new(name)
     */
    private void handleInput(Element en) {

        String name = en.getAttribute(ATTR_NAME);

        //runtimeDebug("handleInput  name=" + name);

        // 1- if (eval exists)         i.eval(eval)
        if (en.hasAttribute(ATTR_EVALUATOR)) {
            this.emitEval(en.getAttribute(ATTR_EVALUATOR));

            // 2- else if (value exists) i.load(value)    
        } else if (en.hasAttribute(ATTR_VALUE)) {
            this.emitLoad(en.getAttribute(ATTR_VALUE));

            // 3- else (buffer exists)   i.reduce(buffer) 
        } else if (en.hasAttribute(ATTR_BUFFER)) {
            this.emitReduce(en.getAttribute(ATTR_BUFFER));

            // 4- else                !!ERROR   
        } else {
            runtimeError("ERROR.  No source given for INPUT.");
            return;
        }

        // 5- i.new(name)
        this.emitNew(name);
    }

    /**
     *  handle goto
     *  Emit the goto.  Put it in the fixup stack.
     *  MICROCODE
     *  1- i.goto(label)
     */
    private void handleGoto(Element en) {

        String label = en.getAttribute(ATTR_LABEL);

        //runtimeDebug("handleGoto  label=" + label);

        // 1- i.goto(label)
        fixupstack.push(new NOPair(label, (Object) this.emitGoto(0)));
    }

    /**
     *  handle a IF
     * <code>MICROCODE
     * 1-     if (oper exists)   OP = oper
     * 2-        else         OP = '=' (default)
     * 3-       if (eval exists)         i.eval(eval)
     * 4-          else if (value exists) i.load(value)
     * 5-        else                !!ERROR   
     * 6-       i.right
     * 7-       i.fetch(item)
     * 8-       i.math(operation)
     * 9-      i.if(OUTER,OP)
     * 10- INNER:   i.scope
     * 11-     (ANY)*
     * 12-    i.rscope
     * 13- OUTER:   i.nop </code>
     */
    private void handleIf(Element en) {

        VMIIf myif;
        int opthang = EQ; // default is =

        //runtimeDebug("handleIf");

        // 1- if (oper exists)   OP = oper
        if (en.hasAttribute(ATTR_OPERATOR)) {
            String ops = en.getAttribute(ATTR_OPERATOR);
            if (ops.equals(EQ_STRING)) {
                opthang = EQ;
            } else if (ops.equals(LT_STRING)) {
                opthang = LT;
            } else if (ops.equals(GT_STRING)) {
                opthang = GT;
            } else if (ops.equals(NOT_STRING)) {
                opthang = NOT;
            } else {
                runtimeWarning("WARNING If has unrecognized operation.  Using default \"eq\"");
                opthang = EQ;
            }
        }
        //   2-   else         OP = '=' (default)
        // Implied since it is the default.

        // 3- if (eval exists)          i.eval(eval)
        if (en.hasAttribute(ATTR_EVALUATOR)) {
            this.emitEval(en.getAttribute(ATTR_EVALUATOR));

            // 4- else if (value exists) i.load(value)    
        } else if (en.hasAttribute(ATTR_VALUE)) {
            this.emitLoad(en.getAttribute(ATTR_VALUE));

            // 5- else !!ERROR   
        } else {
            runtimeError("ERROR Broken IF.  No right value defined defined.");
            this.emitLoad(en.getAttribute(LITERAL_ZERO));
            // use 0 so we can finish compile
        }

        // 6- i.right
        this.emitRight();

        // 7- i.fetch(item)
        this.emitFetch(en.getAttribute(ATTR_ITEM));

        // 8- i.math(operation)
        this.emitMath(IF_OPERATION);

        // 9- i.if(OUTER, OP)
        myif = new VMIIf();
        myif.operFlag = opthang;
        ob.emit(myif);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.if target= FIXUP LATER");

        // 10- INNER:   i.scope
        this.emitScope();

        // 11- (ANY)*
        try {
            processCode(en);
        } catch (Exception e) {
            //   Stop an error unravelling here.  Close the scope and move on
            runtimeError("ERROR Broken IF inner.");
            runtimeError(e.toString());
        }

        // 12- i.rscope
        this.emitRScope();

        // 13- OUTER:   i.nop
        // First fixup the if.  Put a NOP for safety, so there will always be a target
        myif.t = ob.nextIP();
        runtimeDebug("FIXUP if target=" + myif.t);
        this.emitNOP();
    }

    /**
     *  handle a ASSERT
     * <code>MICROCODE
     * 
     * 1-    if (oper NOT exists)   OP = NOT
      * 2-    else                 OP = EQ
      * 3-    i.fetch(item)
     * 4-    i.assert(OUTER,OP)
     * 5- INNER:  i.scope
     * 6-    (ANY)*
     * 7-    i.rscope
     * 8- OUTER:   i.nop
     * </code>
     */
    private void handleAssert(Element en) {

        VMIAssert myassert;
        int opthang = EQ; // default is =

        //runtimeDebug("handleAssert");

        // 1- if (oper exists)   OP = oper
        if (en.hasAttribute(ATTR_OPERATOR)) {
            String ops = en.getAttribute(ATTR_OPERATOR);
            if (ops.equals(EQ_STRING)) {
                opthang = EQ;
            } else if (ops.equals(NOT_STRING)) {
                opthang = NOT;
            } else {
                runtimeWarning("WARNING If has unrecognized operation.  Using default \"eq\"");
                opthang = EQ;
            }
        }
        //   2-   else         OP = '=' (default)
        // Implied since it is the default.

        //  3-   i.fetch(item)
        this.emitFetch(en.getAttribute(ATTR_ITEM));

        //  4-    i.assert(OUTER,OP)
        myassert = new VMIAssert();
        myassert.operFlag = opthang;
        ob.emit(myassert);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.assert target= FIXUP LATER");

        // 5- INNER:  i.scope
        this.emitScope();

        // 6- (ANY)*
        try {
            processCode(en);
        } catch (Exception e) {
            //   Stop an error unravelling here.  Close the scope and move on
            runtimeError("ERROR Broken ASSERT inner.");
            runtimeError(e.toString());
        }

        // 7- i.rscope
        this.emitRScope();

        // 8- OUTER:   i.nop
        // First fixup the assert.  Put a NOP for safety, so there will always be a target
        myassert.t = ob.nextIP();
        runtimeDebug("FIXUP assert target=" + myassert.t);
        this.emitNOP();
    }

    /**
     *  handle subroutine.
     *  MICROCODE
     * 1- i.scope
     * 2- (SET)*
     * 3- i.subr(name)
     * 4- i.rscope
     * 5- if (result exist) i.store(result)
     */
    private void handleSubroutine(Element en) {

        String name = en.getAttribute(ATTR_NAME);

        runtimeDebug("handleSubroutine.  name=" + name);

        // 1- i.scope
        this.emitScope();

        // 2- (SET)*
        try {
            // recurse into the children
            processCode(en);

            // 3- i.call(name)
            this.emitSubr(name);

        } catch (Exception e) {
            //   Stop an error unravelling here.  Close the scope and move on
            runtimeError("ERROR handleCall.  Broken call." + name);
            runtimeError(e.toString());
            ob.emit(new VMIRScope());
            return;
        }

        // 4- i.rscope
        this.emitRScope();

        // 5- if (result exist) i.store(result)
        if (en.hasAttribute(ATTR_RESULT)) {
            this.emitStore(en.getAttribute(ATTR_RESULT));
        }
    }

    /**
     *  handle a label
     *  MICROCODE
     * 1- {mark label}
     */
    private void handleLabel(Element en) {

        String name = en.getAttribute(ATTR_NAME);

        //runtimeDebug("handleLabel.  name=" + name);

        // 1- {mark label}.  Put it in the symboltable with current IP
        // See if there is a duplicate.
        if (symboltable.containsKey(name)) {
            runtimeError("Duplicate label declared.  label=" + name);
        } else {
            symboltable.put(name, new Integer(ob.nextIP()));
        }
    }

    /**
     *  handle a WHILE
     *  MICROCODE
     * 1-    i.scope
     * 2- DO:   i.load(value)
     * 3-    i.right
     * 4-   i.fetch(name)
        * 5-    i.math("=")
     * 6-    i.if(DONE)
     * 7-   (ANY)*
     * 8-    i.jump(DO)
     * 9- DONE:   i.rscope
     */
    private void handleWhile(Element en) {

        int loopdo;
        VMIIf myif;
        VMIJump myjump;

        //runtimeDebug("handleWhile");

        // 1- i.scope
        this.emitScope();

        // 2- DO:   i.load(value)
        loopdo = ob.nextIP(); // DO points at #2
        this.emitLoad(en.getAttribute(ATTR_VALUE));

        // 3-    i.right
        this.emitRight();

        // 4-   i.fetch(name)
        this.emitFetch(en.getAttribute(ATTR_NAME));

        // 5-    i.math("=")
        this.emitMath(EQ_OPERATION);

        // 6-    i.if(DONE)
        myif = new VMIIf();
        ob.emit(myif);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.if target= FIXUP LATER");

        // 7- (ANY)*
        try {
            processCode(en);
        } catch (Exception e) {
            //   Stop an error unravelling here.  Close the scope and move on
            runtimeError("ERROR Broken FOR inner.");
            runtimeError(e.toString());
        }

        // 8-    i.jump(DO)
        myjump = new VMIJump();
        myjump.t = loopdo;
        ob.emit(myjump);
        runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.jump target=" + loopdo);

        // 9- DONE:   i.rscope
        // First fixup the if
        myif.t = ob.nextIP();
        this.emitRScope();
    }

    /**
     *  handle a MATH
     *  MICROCODE
     * 1- if (eval exists)         i.eval(eval)
     * 2-    else if (value exists) i.load(value)
     * 3-    else                !!ERROR   
     * 4- i.right()
     * 5- i.fetch(left)
     * 6- i.math(oper)
     * 7- if (output exists)      i.store(output)
     * 8-    else                i.store(left)
     */
    private void handleMath(Element en) {

        //runtimeDebug("handleMath");

        // 1- if (eval exists)      i.eval(eval)
        if (en.hasAttribute(ATTR_EVALUATOR)) {
            this.emitEval(en.getAttribute(ATTR_EVALUATOR));

            // 2- else if (value exists) i.load(value)    
        } else if (en.hasAttribute(ATTR_VALUE)) {
            this.emitLoad(en.getAttribute(ATTR_VALUE));

            // 3- else          !!ERROR   
        } else {
            runtimeError("ERROR Broken Math.  No right value defined.");
            this.emitLoad(LITERAL_ZERO);
            // use 0 so we can finish compile
        }

        // 4- i.right
        this.emitRight();

        // 5- i.fetch(item)
        this.emitFetch(en.getAttribute(ATTR_LEFT));

        // 6- i.math(operation)
        this.emitMath(en.getAttribute(ATTR_OPERATOR));

        // 7- if (output exists)      i.store(output)
        if (en.hasAttribute(ATTR_OUTPUT)) {
            this.emitStore(en.getAttribute(ATTR_OUTPUT));

            // else                i.store(left)
        } else {
            this.emitStore(en.getAttribute(ATTR_LEFT));
        }

    }

    /**
     *  handle a return.  It just emits a fault
     *  MICROCODE
     * 1- i.fault
     */
    private void handleReturn(Element en) {

        //runtimeDebug("handleReturn");

        // 1- i.fault
        this.emitFault();
    }

    // == ===============================================================================
    // == =                             HELPERS                                         =
    // == ===============================================================================

    /**
     *  Valid string.
     * 
     *  Checks if the reference is not null and the string contains characters.
     *
     *  @param s the string to check.
     *  @return true if valid, otherwise false
     */
    private boolean isValid(String s) {
        if ((s == null) || (s.length() < 1))
            return false;
        else
            return true;
    }

    /**
     *  Get the text out of an XML node.
     *
     *  @param cdn XML node.
     *  @return the text.
     */
    private String getText(Node cdn) {

        try {
            if ((cdn.getNodeType() == Node.TEXT_NODE) || (cdn.getNodeType() != Node.CDATA_SECTION_NODE)) {
                CharacterData cdnc = (CharacterData) cdn;
                return cdnc.getData();
            }
        } catch (Exception e) {
        } // ignore.  re are returning empty anyway
        return null;
    }

    /**
     *  Get the text out of an XML node.
     *
     *  @param where where it was bad.
     *  @param which which attribute was bad.
     *  @param tag  which tag.
     *  @return the text.
     */
    private void errBadAttribute(String where, String which, String tag) {
        runtimeError("ERROR: Invalid '" + which + "' attribute for <" + tag + ">.  @" + where);
    }

    /**
     *  emitClear
     */
    private void emitClear(String name) {
        VMIClear ic;
        ic = new VMIClear();
        ic.t = name;
        ob.emit(ic);
        //runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.clear name= " + name);
    }

    /**
     *  emitEval
     */
    private void emitEval(String eval) {
        VMIEval ic;
        ic = new VMIEval();
        ic.e = eval;
        ob.emit(ic);
        //runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.eval eval= " + eval);
    }

    /**
     *  emitReduce
     */
    private void emitReduce(String buffer) {
        VMIReduce ic;
        ic = new VMIReduce();
        ic.b = buffer;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.reduce name= " + buffer);
    }

    /**
     *  emitReduce
     */
    private void emitLoad(String value) {
        VMILoad ic;
        ic = new VMILoad();
        ic.l = value;
        ob.emit(ic);
        //runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.load value= " + value);
    }

    /**
     *  emitMerge
     */
    private void emitMerge(String buffer) {
        VMIMerge ic;
        ic = new VMIMerge();
        ic.b = buffer;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.merge buffer= " + buffer);
    }

    /**
     *  emitCall
     */
    private void emitCall(String target) {
        VMICall ic;
        ic = new VMICall();
        ic.t = target;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.call target= " + target);
    }

    /**
     *  emitMethod
     */
    private void emitMethod(String m) {
        VMIMethod ic;
        ic = new VMIMethod();
        ic.m = m;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.method method=" + m);
    }

    /**
     *  emitSubr
     */
    private void emitSubr(String target) {
        VMISubr ic;
        ic = new VMISubr();
        ic.t = target;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.subr target= " + target);
    }

    /**
     *  emitExec
     */
    private void emitExec(String target) {
        VMIExec ic;
        ic = new VMIExec();
        ic.c = target;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.exec target= " + target);
    }

    /**
     *  emitNew
     */
    private void emitNew(String var) {
        VMINew ic;
        ic = new VMINew();
        ic.v = var;
        ob.emit(ic);
        //runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.new variable= " + var);
    }

    /**
     *  emitRight
     */
    private void emitRight() {
        ob.emit(new VMIRight());
        //runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.right");
    }

    /**
     *  emitFetch
     */
    private void emitFetch(String var) {
        VMIFetch ic;
        ic = new VMIFetch();
        ic.v = var;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.fetch variable= " + var);
    }

    /**
     *  emitMath
     */
    private void emitMath(String oper) {
        VMIMath ic;
        ic = new VMIMath();
        ic.o = oper;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.math operation= " + oper);
    }

    /**
     *  emitStore
     */
    private void emitStore(String var) {
        VMIStore ic;
        ic = new VMIStore();
        ic.v = var;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.store variable= " + var);
    }

    /**
     *  emitJump
     */
    private VMIJump emitJump(int target) {
        VMIJump ic;
        ic = new VMIJump();
        ic.t = target;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.Jump target= " + target);
        return ic;
    }

    /**
     *  emitGoto
     */
    private VMIGoto emitGoto(int target) {
        VMIGoto ic;
        ic = new VMIGoto();
        ic.t = target;
        ob.emit(ic);
        //runtimeDebug(
        //   "EMIT(" + (ob.nextIP() - 1) + ") i.goto target= " + target);
        return ic;
    }

    /**
     *  emitScope
     */
    private void emitScope() {
        ob.emit(new VMIScope());
        //runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.scope");
    }

    /**
     *  emitRScope
     */
    private void emitRScope() {
        ob.emit(new VMIRScope());
        //runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.rscope");
    }

    /**
     *  emitNOP
     */
    private void emitNOP() {
        ob.emit(new VMINop());
        //runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.nop");
    }

    /**
     *  emitNOP
     */
    private void emitFault() {
        ob.emit(new VMIFault());
        //runtimeDebug("EMIT(" + (ob.nextIP() - 1) + ") i.fault");
    }

}