view cbackend.js @ 32:64f1d516fbfd

Tiny bit of work on closures
author Mike Pavone <pavone@retrodev.com>
date Sat, 07 Jul 2012 17:03:01 -0700
parents 668f533e5284
children a10f1b049193
line wrap: on
line source

var mainModule;
var modules = {};

var nextmethodId = 0;
var methodIds = {};
function getMethodId(methodName)
{
	if (!(methodName in methodIds)) {
		methodIds[methodName] = nextmethodId++;
		
	}
	return methodIds[methodName];
}

function importSym(obj, src, key)
{
	if(!(key in src)) {
		throw new Error(key +' not found in source object for import');
	}
	if(key in obj) {
		throw new Error(key +' already exists in target object for import')
	}
	obj[key] = src[key];
}

function doImport(obj, src, symlist)
{
	if (symlist === undefined) {
		each(src, function(key,val) {
			if (key != 'parent') {
				importSym(obj, src, key);
			}
		});
	} else {
		for (var i = 0; i < symlist.length; ++i) {
			importSym(obj, src, symlist[i]);
		}
	}
	return obj;
}

op.prototype.toC = function(isReceiver) {
	var optoMeth = {'+': 'ADD_', '-': 'SUB_', '*': 'MUL_', '/': 'DIV_', '=': 'EQ_', '!=': 'NEQ_', '<': 'LT_', '>': 'GT_', '>=': 'GEQ_', '<=': 'LEQ_'};
	var method = optoMeth[this.op];
	var ret = '(params[0] = ' + this.left.toC() + ',\n';
	ret += 'params[1] = ' + this.right.toC() + ',\n';
	ret += 'mcall(' + getMethodId(method) + ', 2, params))\n';
	return ret;
};

function escapeCName(name)
{
	name = name.replace("_", "UN_").replace(":", "CN_").replace("!", "EX_").replace('?', 'QS_').replace('@', 'AT_');
	name = 'tp_' + name;
	return name;
}

symbol.prototype.toC = function() {
	var name = this.cleanName();
	if (name == 'self') {
		return name;
	}
	var info = this.symbols.find(name);
	if (!info) {
		throw new Error('symbol ' + name + ' not found');
	}
	var pre = '';
	if (info.type == 'self') {
		pre = this.symbols.selfVar() + '->';
	} else if(info.type == 'parent') {
		pre = this.symbols.selfVar() + '->';
		for (var i = 0; i < info.depth; ++i) {
			pre += 'parent->';
		}
	} else if(info.type == 'upvar') {
		pre = 'env->';
		for (var i = 1; i < info.depth; ++i) {
			pre += 'parent->';
		}
	} else if (info.type == 'toplevel') {
		pre = 'modules.';
		modules[name] = false;
	}
	return pre + escapeCName(name);
}

intlit.prototype.toC = function() {
	var str = this.val.toString();
	toplevelcode += 'obj_int32 int32_' + str + ' = {{&obj_int32_meta, NULL}, ' + str + '};\n';
	return '((object *)&int32_' + str + ')';
}

floatlit.prototype.toC = function() {
	return 'make_float(' + this.val.toString() + ')';
}

strlit.prototype.toC = function() {
	return 'make_str("' + this.val.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r') + '")';
}

listlit.prototype.toC = function() {
	var ret = 'make_list(';
	each(this.val, function(idx, el) {
		ret += (idx ? ', ' : '') + el.toC();
	});
	return ret + ')';
}

funcall.prototype.toC = function() {
	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
	if (name == 'foreign') {
		if ((this.args[0] instanceof lambda) || (this.args[0] instanceof object)) {
			return null;
		} else if(this.args[0] instanceof symbol) {
			return this.args[0].name;
		} else {
			throw new Error("Unexpected AST type for foreign:");
		}
	}
	var args = this.args.slice(0, this.args.length);
	if (this.receiver) {
		args.splice(0, 0, this.receiver);
	}
		var method = false;
	var funinfo = this.symbols.find(name);
	if (!funinfo || funinfo.def instanceof setter) {
		method = true;
	} else {
		switch(funinfo.type)
		{
		case 'self':
			if (args.length < funinfo.def.args.length || funinfo.def.args[0].name != 'self') {
				args.splice(0, 0, new symbol('self', this.symbols));
			} else {
				args.splice(0, 1);
			}
			method = true;
			break;
		case 'parent':
			ret = 'self';
			for (var i = 0; i < funinfo.depth; ++i) {
				ret += '->parent';
			}
			break;
		}
	}
	for (var i in args) {
		args[i] = 'params[' + i + '] = ' + args[i].toC() + ',\n';
	}
	var callpart;
	if (method) {
		callpart = 'mcall(' + getMethodId(name);
	} else {
		callpart = 'ccall(' + escapeCName(name);
	}
	return '(' + args.join('') + callpart + ', ' + args.length + ', ' + 'params))';
}

function cObject(name) {
	this.name = name;
	this.slots = {};
	this.properties = [];
	this.values = [];
}

cObject.prototype.addMessage = function(msgname, implementation) {
	var methodid = getMethodId(msgname);
	var trunc = methodid & 0xF;
	if (!(trunc in this.slots)) {
		this.slots[trunc] = [];
	}
	this.slots[trunc].push([methodid, implementation, msgname]);
}

cObject.prototype.addProperty = function(propname, value, type) {
	if (type != undefined) {
		this.properties.push([propname, type]);
	} else {
		var escaped = escapeCName(propname);
		this.addMessage(propname, 'return self->' + escaped + ';');
		this.addMessage(propname + '!', 'self->' + escaped + ' = params[1]; return params[0];');
		this.properties.push(escaped);
		this.values.push(value);
	}
}

cObject.prototype.toEarlyCDef = function() {
	var objdef =  'typedef struct {\n\tobject header;\n';
	for (var i in this.properties) {
		if (this.properties[i] instanceof Array) {
			objdef += '\t' + this.properties[i][1] + ' ' + this.properties[i][0] + ';\n';
		} else {
			objdef += '\tobject * ' + this.properties[i] + ';\n'
		}
	}
	objdef += '} ' + this.name + ';\nobj_meta ' + this.name + '_meta;\n';
	return objdef;
}

cObject.prototype.toCDef = function() {
	var slotdefs = '';
	var metadef = 'obj_meta ' + this.name + '_meta = {sizeof(' + this.name +'), {';
	for (var i = 0; i < 16; i++) {
		if (i) {
			metadef += ', ';
		}
		if (i in this.slots) {
			slotdefs += 'object * ' + this.name + '_slot_' + i + '(uint32_t method_id, uint32_t num_params, object ** params) {\n\t' +
				this.name + ' *self = (' + this.name + ' *)params[0];';
			if (this.slots[i].length == 1) {
				slotdefs += '\tif (method_id == ' + this.slots[i][0][0] + ') { /* ' + this.slots[i][0][2] + '*/\n' +
					'\t\t' + this.slots[i][0][1] + '\n' + 
					'\t}\n' +
					'\treturn no_impl(method_id, num_params, params);\n}\n';
			} else {
				slotdefs += '\tswitch(method_id) {\n';
				for (j in this.slots[i]) {
					slotdefs += '\t\tcase ' + this.slots[i][j][0] + ': /* ' + this.slots[i][j][2] + '*/\n' +
						'\t\t\t' + this.slots[i][j][1] + '\n';
				}
				slotdefs += '\t\tdefault:\n\treturn no_impl(method_id, num_params, params);\n}\n';
			}
			metadef += this.name + '_slot_' + i;
		} else {
			metadef += 'no_impl';
		}
	}
	metadef += '}};\n';
	return slotdefs + metadef;
}

cObject.prototype.toCInstance = function() {
	return 'make_object(&' + this.name + '_meta, NULL, ' + this.values.length + (this.values.length ? ', ' : '') + this.values.join(', ') + ')';
}

var nextobject = 0;

object.prototype.toC = function() {
	var messages = this.messages;
	var values = [];
	var imports = []
	var me = new cObject('object_' + nextobject++);
	for (var i in messages) {
		if (messages[i] instanceof funcall) {
			if (messages[i].name == 'import:' && messages[i].args.length == 1) {
				imports.push({symbols: false, src: messages[i].args[0]});
			} else if(messages[i].name == 'import:from:' && messages[i].args.length == 2) {
				var importsyms = [];
				each(messages[i].args[0].val, function(i, el) {
					if (!(el instanceof symbol)) {
						throw new Error('Names in import:from statement must be symbols');
					}
					importsyms.push(new strlit(el.name));
				});
				imports.push({symbols: new listlit(importsyms), src: messages[i].args[1]});
			} else {
				throw new Error('Only import and import:from calls allowed in object context');
			}
		} else {
			messages[i].toCObject(me);
		}
	}
	forwarddec += me.toEarlyCDef();
	toplevelcode += me.toCDef();
	return me.toCInstance();
}

var toplevelcode;
var forwarddec;

function makeCProg(obj)
{
	var int32 = new cObject('obj_int32');
	int32.addProperty('num', null, 'int32_t');
	int32.addMessage('ADD_', 'params[0] = make_object(&obj_int32_meta, NULL, 0); ((obj_int32 *)params[0])->num = self->num + ((obj_int32 *)params[1])->num; return params[0];');
	int32.addMessage('SUB_', 'params[0] = make_object(&obj_int32_meta, NULL, 0); ((obj_int32 *)params[0])->num = self->num - ((obj_int32 *)params[1])->num; return params[0];');
	forwarddec = toplevelcode = '';
	forwarddec += int32.toEarlyCDef();
	toplevelcode += int32.toCDef();
	obj.populateSymbols(toplevel);
	var rest = 'object * mainModule() {\n\treturn ' + obj.toC() + ';\n}\n';
	return '#include "runtime/proghead.inc"\n#define METHOD_ID_MAIN ' + getMethodId('main') + '\n' + forwarddec + toplevelcode + rest + '#include "runtime/progfoot.inc"\n';
}

object.prototype.toCModule = function() {
	return makeCProg(this);
}

var lambdanum = 0;

lambda.prototype.toC = function() {
	var args = this.args ? this.args.slice(0, this.args.length) : [];
	var exprs = this.expressions;
	if (this.selftype) {
		if (args[0] && args[0].cleanName() == 'self') {
			args.splice(0, 1);
		}
		var offset = 1;
	} else {
		var offset = 0;
	}
	for (var i = 0; i < args.length; ++i) {
		args[i] = '\tobject * ' + args[i].toC() + ' = params[' + (offset + i) + '];\n';
	}
	var compiled = []
	for (var i in exprs) {
		var js = exprs[i].toC();
		if (js) {
			compiled.push(indent(js));
		}
	}
	exprs = compiled;
	if (exprs.length) {
		exprs[exprs.length-1] = 'return ' + exprs[exprs.length-1] + ';';
	}
	var mynum = lambdanum++;
	toplevelcode +=  'object * lambda_' + mynum + ' (void * env, uint32_t num_args, object ** params) {\n';
	if (this.selftype) {
		toplevelcode += '\t' + this.selftype + ' * self = (' + this.selftype + ' *)params[0];\n';
	}
	toplevelcode += args.join('') + exprs.join(';\n\t') + '\n}\n';
		
	toplevelcode += 'closure lambda_obj_' + mynum + ' = {{&lambda_meta, NULL}, NULL, lambda_' + mynum + '};\n';
	return '((object *)&lambda_obj_' + mynum + ')';
};
lambda.prototype.toCObject = function(typename) {
	this.selftype = typename;
	return this.toC();
};
lambda.prototype.toCModule = function() {
	return makeCProg(this);
}

assignment.prototype.toC = function() {
	var existing = this.symbols.find(this.symbol.name);
	var prefix = '';
	if (!existing) {
		prefix =  'object * ';
	} else {
		switch (existing.type)
		{
		case 'self':
			prefix = 'self->';
			break;
		case 'parent':
			prefix = 'self->header.';
			for (var i = 0; i < existing.depth; ++i) {
				prefix += 'parent->';
			}
			break;
		}
	}
	var val = this.expression.toC();
	if (val === null) {
		return null;
	}
	return prefix + this.symbol.toC() + ' = ' + val;
};
assignment.prototype.toCObject = function(cobj) {
	if (this.expression.toCObject) {
		var val = this.expression.toCObject(cobj.name);
	} else {
		var val = this.expression.toC();
	}
	if (val === null) {
		return;
	}
	if (this.expression instanceof lambda) {
		cobj.addMessage(this.symbol.name, 'return ccall(' + val + ', num_params, params);');
	} else {
		cobj.addProperty(this.symbol.name, val);
	}
};