/* global Sketch */
/* global MVM */
//HELPERS
var createNode = function(type, args){
return {
type: Sketch.SketchGenNodes[type],
arguments: args
};
};
var boolNegateNode = function(node){
return createNode("negate", node);
};
var resolveType = function(typeObj){
if(typeObj.type === "ident"){
typeObj = typeObj.data.entry
}
return typeObj
};
var loadAndOperate = function(context, nodes, operand){
var types = [];
for(var i = 0; i< nodes.length; i++){
var n = context.interpretNode(nodes[i]);
// if(n.type === "ident"){
// types.push(n.data.entry);
// } else{
// types.push(n);
// }
types.push(resolveType(n));
}
var o = Sketch.SketchGenOperandTable.lookup(operand, types);
context.emit(o.value.code);
var d = o.value;
if(o.extra){
d.extra = o.extra;
}
return d;
};
var primitive = function(context, value, type){
context.emit(MVM.opCodes.LOADC);
context.emit(value);
return {type: type};
};
var assignmentOperand = function(context, nodes, operandNode){
context.interpretNode(createNode("assign", [nodes[0], createNode(operandNode, [nodes[0], nodes[1]])]));
};
var increment = function(context, nodes, value){
context.interpretNode(nodes[0]);
assignmentOperand(context, [nodes[0], createNode("num", value)], "addition");
return Sketch.SketchGenOperandTable.lookup((value>0)?"++":"--", [Sketch.SketchGenNodes._rev[nodes[0]]]).value;
};
/**
* Table of unbound functions used in code generation.
* These correspond to keys in {@link Sketch.SketchGenNodes}, and MUST be bound to an instance of {@link Sketch.SketchGen} to function.
* @constant Sketch.SketchGen.SketchGenInstr
* @author FelixMcFelix (Kyle S.)
*/
Sketch.SketchGenInstr = [];
Sketch.addInstruction = function(key, func){
Sketch.SketchGenInstr[Sketch.SketchGenNodes[key]] = func;
};
//CONVENTION: All functions return an object with their return type. This is how we do type checking.
/*
Sketch.addInstruction("template", function(args){
var type;
return type;
});
*/
//Program header.
Sketch.addInstruction("program", function(args){
this.interpretNode(args);
this.emit(MVM.opCodes.EXIT);
});
//Program Structure
Sketch.addInstruction("block", function(args, noCodes){
//HAS NO TYPE - ORGANISATIONAL TYPE
this.scopePush(noCodes);
this.interpretNode(args);
this.scopePop(noCodes);
return {type: null}
});
Sketch.addInstruction("function", function(args){
//args[0] = name, args[1] = decls[], args[2] = type, args[3] = block
//We need to extract info, and then transform the tree to place decls inside the block.
this.beginFunction(args[2]);
this.emit(MVM.opCodes.JUMP);
var patchme = this.emit(0xff);
//Extract the amount of parameters and their types - names are unimportant for the table.
var pTypes = [];
args[1].forEach(function(curr){
pTypes.push(curr.arguments[0]);
});
this.scopeRegister(args[0], "function", {returnType: args[2], paramTypes: pTypes});
this.interpretNode(createNode("block", [args[1], args[3].arguments]), true);
//All functions return a null value for their type automatically.
//This allows runoff at the end, implicit zero return,
//and makes my life easier.
var defaultRet = Sketch.sketchGenDefaultReturns[args[2]];
if(defaultRet === null){
this.emit(MVM.opCodes.RETURN);
} else{
this.interpretNode(createNode(args[2], defaultRet));
this.emit(MVM.opCodes.RETURNVAL);
}
this.patch(patchme, this.pc());
this.endFunction();
return {type: "function"};
});
Sketch.addInstruction("func_call", function(args){
//args[0] = name, args[1] = params[]
//Lookup name, check for function type.
//Compare param types, count while accessing them.
//Check return type against
var dat = this.scopeLookup(args[0]);
if(dat.entry.type !== "function"){
throw "Tried to call "+args[0]+" as though it were a function - it is a "+dat.type+"!";
}
if(args[1].length === undefined){
args[1].length = 0;
}
if(dat.entry.extra.paramTypes.length !== args[1].length){
throw "Parameter length mismatch.";
}
for(var i = 0; i<args[1].length; i++){
var t1 = resolveType(this.interpretNode(args[1][i])).type;
var t2 = dat.entry.extra.paramTypes[i];
if (t1 !== t2){
throw "Type mismatch on parameter "+i+" of call to "+args[0]+": EXPECTED "+t2+", not"+t1+".";
}
}
this.emit(MVM.opCodes.CALL);
this.emit(dat.stack);
this.emit(dat.entry.address);
this.emit(args[1].length);
return {type: dat.entry.extra.returnType};
});
Sketch.addInstruction("return", function(args){
if(args === null){
this.emit(MVM.opCodes.RETURN);
} else{
var t1 = resolveType(this.interpretNode(args)).type;
this.emit(MVM.opCodes.RETURNVAL);
var t2 = this.currentFunctionType();
if (t1 !== t2){
throw "ERROR: expected return type of "+t2+", given "+t1+".";
}
}
return {type: null};
});
Sketch.addInstruction("if", function(args){
//args is an array of the other classes.
//each returns an object with property "patch", the address to patch with the end
var patches = [];
var t = this;
args.forEach(function(c){
var k = t.interpretNode(c);
patches.push(k.patch);
});
var end = this.pc();
patches.forEach(function(c){
if(c !== null){
t.patch(c, end);
}
});
return {type: null};
});
Sketch.addInstruction("else_if", function(args){
//args[0] is the expression to test.
//args[1] is the block.
var t1 = this.interpretNode(args[0]);
if(resolveType(t1).type !== "bool"){
throw "Expressions in an if-else block must be boolean type (true or false).";
}
this.emit(MVM.opCodes.JUMPF);
var patch1 = this.emit(0xFF);
var t2 = this.interpretNode(args[1]);
this.emit(MVM.opCodes.JUMP);
t2.patch = this.emit(0xFF);
this.patch(patch1, this.pc());
return t2;
});
Sketch.addInstruction("else", function(args){
//args should just be a block;
var d = this.interpretNode(args);
d.patch = null;
return d;
});
//Variable declaration and assignment
Sketch.addInstruction("variable_decl", function(args){
this.interpretNode(args);
});
Sketch.addInstruction("variable_decl_assign", function(args){
this.interpretNode(args[0]);
this.interpretNode(createNode("assign", [createNode("ident", args[0].arguments[1]), args[1]]));
});
Sketch.addInstruction("decl", function(args){
this.scopeRegister(args[1],args[0]);
return args[0];
});
Sketch.addInstruction("assign", function(args){
var left = this.interpretNode(args[0], true);
var right = this.interpretNode(args[1]);
if(left.type !== "ident"){
throw "ERROR: non-identity type on left side of assignment operator.";
}
if(resolveType(right).type !== resolveType(left).type){
console.log("Ltype: "+resolveType(left).type+", Rtype: "+resolveType(right).type);
throw "ERROR: right side of assignment does not match type of identifier.";
}
if(right.extra){
left.data.entry.extra = right.extra;
}
this.emit(MVM.opCodes.STORER);
this.emit(left.data.stack);
this.emit(left.data.entry.address);
return right;
});
//Arithmetic Instructions
Sketch.addInstruction("addition", function(args){
return loadAndOperate(this, args, "+");
});
Sketch.addInstruction("subtraction", function(args){
return loadAndOperate(this, args, "-");
});
Sketch.addInstruction("multiplication", function(args){
return loadAndOperate(this, args, "*");
});
Sketch.addInstruction("division", function(args){
return loadAndOperate(this, args, "/");
});
Sketch.addInstruction("modulo", function(args){
return loadAndOperate(this, args, "%");
});
Sketch.addInstruction("increment", function(args){
return increment(this, args, 1);
});
Sketch.addInstruction("decrement", function(args){
return increment(this, args, -1);
});
Sketch.addInstruction("unary_minus", function(args){
return this.interpretNode(createNode("multiplication", [args, createNode("num", [-1])]));
});
//Arithmetic assignment Instructions.
Sketch.addInstruction("add_assign", function(args){
return assignmentOperand(this, args, "addition");
});
Sketch.addInstruction("sub_assign", function(args){
return assignmentOperand(this, args, "subtraction");
});
Sketch.addInstruction("mul_assign", function(args){
return assignmentOperand(this, args, "multiplication");
});
Sketch.addInstruction("div_assign", function(args){
return assignmentOperand(this, args, "division");
});
Sketch.addInstruction("mod_assign", function(args){
return assignmentOperand(this, args, "modulo");
});
//Graphical operands
Sketch.addInstruction("colour", function(args){
return loadAndOperate(this, args, "~");
});
Sketch.addInstruction("translate", function(args){
return loadAndOperate(this, args, "->");
});
//Logical Instructions
Sketch.addInstruction("and", function(args){
return loadAndOperate(this, args, "&&");
});
Sketch.addInstruction("or", function(args){
return loadAndOperate(this, args, "||");
});
Sketch.addInstruction("equal", function(args){
return loadAndOperate(this, args, "?=");
});
Sketch.addInstruction("not_equal", function(args){
return this.interpretNode(boolNegateNode(createNode("equal", args)));
});
Sketch.addInstruction("negate", function(args){
return loadAndOperate(this, [args], "!");
});
Sketch.addInstruction("less_than", function(args){
return loadAndOperate(this, args, "?<");
});
Sketch.addInstruction("greater_than", function(args){
return loadAndOperate(this, args, "?>");
});
Sketch.addInstruction("less_than_or_equal", function(args){
return this.interpretNode(boolNegateNode(createNode("greater_than", args)));
});
Sketch.addInstruction("greater_than_or_equal", function(args){
return this.interpretNode(boolNegateNode(createNode("less_than", args)));
});
//Literals and identifiers.
Sketch.addInstruction("num", function(args){
return primitive(this, args, "num");
});
Sketch.addInstruction("ident", function(args, noaccess){
var d = this.scopeLookup(args);
if(!noaccess){
this.emit(MVM.opCodes.LOADR);
this.emit(d.stack);
this.emit(d.entry.address);
}
return {type: "ident", data: d};
});
Sketch.addInstruction("bool", function(args){
return primitive(this, args, "bool");
});
Sketch.addInstruction("point", function(args){
var size = args.length;
if(size){
//Okay, all elements must be num.
args.forEach(function(curr){
var t = this.interpretNode(curr);
if(t.type !== "num" && t.data.entry.type !== "num"){
throw "Tried to place a non-numeric value into a point type.";
}
}.bind(this));
this.emit(MVM.opCodes.AGGR);
this.emit(size);
} else{
throw "Can't define a zero-size point!";
}
return {type: "point", extra: {size: size}};
});
Sketch.addInstruction("width", function(){
this.emit(MVM.opCodes.WIDTH);
return {type: "num"};
});
Sketch.addInstruction("height", function(){
this.emit(MVM.opCodes.HEIGHT);
return {type: "num"};
});
//Render instructions.
Sketch.addInstruction("draw", function(args){
return loadAndOperate(this, [args], "draw");
});
Sketch.addInstruction("clear", function(){
primitive(this, null, null);
this.emit(MVM.opCodes.CLEAR);
return {type: null};
});
Sketch.addInstruction("clear_colour", function(args){
return loadAndOperate(this, [args], "clear");
});
Sketch.bindInstructions = function(sketchgen){
var out = [];
for (var i = 0; i < Sketch.SketchGenInstr.length; i++){
out[i] = Sketch.SketchGenInstr[i].bind(sketchgen);
}
return out;
};