FelixMcFelix/laughing-ironman

View on GitHub
src/Code Generator/SketchGen.js

Summary

Maintainability
C
1 day
Test Coverage
/* global MVM */
/*jshint sub: true */
var Sketch = Sketch || {};

/**
 * @classdesc Creates an instance of the SketchGen module. SketchGen takes a JSON tree output by the default Jison generated parser and outputs MVM bytecode used to drive computations and canvas operations.
 * @class Sketch.SketchGen
 * @public
 * @author FelixMcFelix (Kyle S.)
 */

Sketch.SketchGen = function(){
    var outBuffer = [];
    var programCounter = 0;
    var scopeStack = [];
    var stackPtr = 0;
    var functionStack = [];

    var DEBUG = false;

    var instructions = Sketch.bindInstructions(this);

    /**
     * Write a value to the next available code word.
     * @method Sketch.SketchGen#emit
     * @param {*} code - the code word or data value to be written into the next slot.
     * @returns {number} - the address which was just written to.
     * @public
     */
    this.emit = function(code){
        outBuffer.push(code);
        return programCounter++;
    };

    /**
     * Replace a code value at a given address.
     * @method Sketch.SketchGen#patch
     * @param {number} addr - the code address to be replaced.
     * @param {*} code - the value to write to the code store.
     * @returns void
     * @public
     */
    this.patch = function(addr, code){
        outBuffer[addr] = code;
    };

    /**
     * Return the current program counter value.
     * @method Sketch.SketchGen#pc
     * @returns {number} - the current program counter value.
     * @public
     */
    this.pc = function(){
        return programCounter;
    };

    /**
     * Interpret an AST node using the current code generator.
     * @method Sketch.SketchGen#interpretNode
     * @param {{type: number, arguments: *}} node - the AST node to be processed in the production of the current code store.
     * @param {*} opt - an optional parameter to be passed to the individual node handler function.
     * @returns {{type: string}}
     * @public
     */
    this.interpretNode = function(node, opt){
        if(Array.isArray(node)){
            node.forEach(this.interpretNode.bind(this));
        } else if(node === ""){
            return;
        } else{
            if(DEBUG){
                console.log("{\n"+Sketch.SketchGenNodes._rev[node.type]+",");
                console.log(node.arguments);
                console.log("}");
            }
            return instructions[node.type](node.arguments, opt);
        }
    };

    /**
     * Push a new program scope frame.
     * @method Sketch.SketchGen#scopePush
     * @param {boolean} [noEmit=false] - specifies whether push and pop commands should not be written to the program as a side effect. This should be true for function definitions.
     * @returns void
     * @public
     */
    this.scopePush = function(noEmit){
        scopeStack.push(new Sketch.SketchGen.ScopeStackFrame());
        stackPtr++;
        if(noEmit){
            return;
        }
        this.emit(MVM.opCodes.PUSHSC);
    };

    /**
     * Pop off the current program scope frame.
     * @method Sketch.SketchGen#scopePop
     * @param {boolean} [noEmit=false] - specifies whether push and pop commands should not be written to the program as a side effect. This should be true for function definitions.
     * @returns void
     * @public
     */
    this.scopePop = function(noEmit){
        scopeStack.pop();
        stackPtr--;
        if(noEmit){
            return;
        }
        this.emit(MVM.opCodes.POPSC);

        // TODO: Patch missed function calls (equivalent to hoisting).
        // TODO: Handle missed variable lookups in a different manner.
    };

    /**
     * Register a label in the current scope frame.
     * @method Sketch.SketchGen#scopeRegister
     * @param {string} label - the variable name to register.
     * @param {string} type - the data type that the variable will be declared with.
     * @param {object=} extra - any extra data that could be required when handling this label (for example, function definitions).
     * @returns void
     * @public
     */
    this.scopeRegister = function(label, type, extra){
        var curFrame = scopeStack[stackPtr];

        if (!curFrame.labelTable[label]){
            var destAddr = (type === "function") ? programCounter : curFrame.nextData++;
            curFrame.labelTable[label] = new Sketch.SketchGen.Label(destAddr, type, extra);
        } else {
            throw "Illegal attempt to redefine variable "+label+".";
        }
    };

    /**
     * Search for a reference to a label (a variable) within the program scope.
     * @method Sketch.SketchGen#scopeLookup
     * @param {string} label - the variable name to lookup.
     * @returns {{entry: Sketch.SketchGen.Label, stack: number}} - an object detailing the height of the referenced label, its address and its type as well as any extra data.
     * @public
     */

    this.scopeLookup = function(label){
        var stack = 0;
        var out = null;

        for(null; stackPtr-stack>=0; stack++){
            var frame = scopeStack[stackPtr-stack];
            var entry = frame.labelTable[label];
            if (entry){
                out = {entry: entry, stack: stack};
                break;
            }
        }

        if (out === null){
            throw "BAD LOOKUP.";
        }

        return out;
    };

    /**
     * Compile a Sketch program.
     * @method Sketch.SketchGen#interpret
     * @param {Object} program - an AST object generated by the Jison parser.
     * @returns number[] - an array of opcodes and literals to be parsed by MVM.
     * @public
     */
    this.interpret = function(program){
        this.cleanState();

        this.interpretNode({type: Sketch.SketchGenNodes["program"], arguments: program});

        var iaddr = null, raddr = null;

        try{
            var t = this.scopeLookup("init");
            if(t.entry.type === "function"){
                iaddr = t.entry.address;
            }
        } catch(e){}
        try{
            var d = this.scopeLookup("render");
            if(d.entry.type === "function"){
                raddr = d.entry.address;
            }
        } catch(e){}

        return {code: outBuffer, initAddr: iaddr, renderAddr: raddr};
    };

    /**
     * Reset the internal object state to allow onject reuse when compiling a new program.
     * @method Sketch.SketchGen#cleanSlate
     * @returns void
     * @public
     */
    this.cleanState = function(){
        outBuffer = [];
        programCounter = 0;
        scopeStack = [];
        scopeStack.push(new Sketch.SketchGen.ScopeStackFrame());
        stackPtr = 0;
        functionStack = [];
    };

    /**
     * Tell the generator that we are beginning a function definition, so that we can ensure that returns have the right type.
     * @method Sketch.SketchGen#beginFunction
     * @param {string} type - the return type of the function we are working on.
     * @returns void
     * @public
     */
    this.beginFunction = function(type){
        functionStack.push(type);
    };

    /**
     * Tell the generator that we are ending a function definition.
     * @method Sketch.SketchGen#endFunction
     * @returns void
     * @public
     */
    this.endFunction = function(){
        functionStack.pop();
    };

    /**
     * Request the type of the current function definition.
     * @method Sketch.SketchGen#currentFunctionType
     * @returns {string}
     * @public
     */
    this.currentFunctionType = function(){
        if(functionStack.length === 0){
            throw "Not currently defining a function, can't find its type!"
        }
        return functionStack[functionStack.length-1];
    };
}

/**
 * @classdesc Simple semantic class for use in the {@link Sketch.SketchGen} scope stack.
 * @class Sketch.SketchGen.ScopeStackFrame
 * @public
 * @author FelixMcFelix (Kyle S.)
 */
Sketch.SketchGen.ScopeStackFrame = function(){
    this.labelTable = {};
    this.nextData = 0;
};

/**
 * @classdesc Simple semantic class for use in the {@link Sketch.SketchGen.ScopeStackFrame} label table.
 * @class Sketch.SketchGen.Label
 * @public
 * @param {Number} addr - the address the label references within its data frame.
 * @param {String} type - the type of the variable represented by the label.
 * @param {Object} [extra] - any extra data (function parameters etc.) that must be known about the label.
 * @author FelixMcFelix (Kyle S.)
 */
Sketch.SketchGen.Label = function(addr, type, extra){
    this.address = addr;
    this.type = type;
    if(extra){
        this.extra = extra;
    }
};