/* 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;
}
};