view cbackend.js @ 349:60292f131de9

Make map actually work right on hashmaps
author Michael Pavone <pavone@retrodev.com>
date Fri, 10 Apr 2015 01:19:10 -0700
parents 7279e21dad68
children 06dceff348ea
line wrap: on
line source

var mainModule;
var modules = {};

var methodIds = {};
var methodNames = [];
var assignNames;
function getMethodId(methodName)
{
	if (!(methodName in methodIds)) {
		methodIds[methodName] = methodNames.length;
		methodNames.push(methodName);
	}
	return methodIds[methodName];
}

function getOpMethodName(opname)
{
	var optoMeth = {'&&':'if', '||':'ifnot'};
	if (opname in optoMeth) {
		return optoMeth[opname];
	} else {
		return opname;
	}
}

op.prototype.toC = function(isReceiver) {
	var method = getOpMethodName(this.op);
	if (this.op == '|') {
		return 'mcall(' + getMethodId(method) + '/* operator ' + method + ' */, 2, (object *)' + this.right.toC() + ', ' + this.left.toC() + ')\n';
	} else {
		return 'mcall(' + getMethodId(method) + '/* operator ' + method + ' */, 2, (object *)' + this.left.toC() + ', ' + this.right.toC() + ')\n';
	}
};
op.prototype.toCLLExpr = function(vars) {
	var opmap = {'=': '==', 'xor': '^', 'or': '|', 'and': '&'};
	return this.left.toCLLExpr(vars) + (this.op in opmap ? opmap[this.op] : this.op) + this.right.toCLLExpr(vars);
};
op.prototype.toCLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toCLLExpr(vars) + ';'];
};


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

function getSymbolPrefix(info, symbols)
{
	var pre = '';
	switch(info.type) {
	case 'self':

		pre = (new symbol('self', symbols)).toC() + '->';
		break;
	case 'parent':
		pre = (new symbol('self', symbols)).toC() + '->header.';
		for (var i = 0; i < info.depth; ++i) {
			pre += (i ? '->' : '') + 'parent';
		}
		pre = '((' + info.selftype + ' *)' + pre + ')->';
		break;
	case 'upvar':
		pre = 'env->';
		for (var i = info.startdepth; i < info.depth; ++i) {
			pre += 'parent->';
		}
		break;
	case 'recupvar':
		if (info.subtype == 'object') {
			pre = 'self->env->';
		} else {
			//TODO: fill this case in if necessary
		}
		pre += getSymbolPrefix(info.parent);
	case 'closedover':
		pre = 'myenv->';
	}
	return pre;
}

symbol.prototype.toC = function() {
	var name = this.cleanName();
	var info = this.symbols.find(name);
	if (!info) {
		throw new Error('symbol ' + name + ' not found in ' + assignNames.join('<-'));
	}
	if (info.type == 'toplevel') {
		return toplevel.moduleVar(name);
	} else if (info.type == 'self' && info.def instanceof lambda) {
		return 'mcall(' + getMethodId(name) + '/* ' + name + ' */, 1, (object *)' + (new symbol('self', this.symbols)).toC() + ')';
	} else if (info.type == 'parent' && info.def instanceof lambda) {
		var obj = (new symbol('self', this.symbols)).toC() + '->header.';
		for (var i = 0; i < info.depth; ++i) {
			obj += (i ? '->' : '') + 'parent';
		}
		return 'mcall(' + getMethodId(name) + '/* ' + name + ' */, 1, ' + obj + ')';
	}
	return getSymbolPrefix(info, this.symbols) + escapeCName(name);
}
symbol.prototype.toCTypeName = function() {
	return this.cleanName();
};
symbol.prototype.cSafeName = function() {
	var name = this.cleanName();
	if (name[0] >= "0" && name[0] <= "9") {
		name = '_tp_' + name;
	}
	return name;
};
symbol.prototype.toCLLExpr = function(vars) {
	var name = this.cleanName();
	if (name in vars) {
		return name;
	}
	if (name == 'self') {
		return 'self';
	}
	var info = this.symbols.find(name, false, true);
	var symbols = this.symbols;
	while (info && info.type == 'local') {
		symbols = symbols.parent;
		info = symbols.find(name, false, true);
	}
	if (!info) {
		return this.cSafeName();
	}
	if (info.type == 'toplevel') {
		return toplevel.moduleVar(name);
	} else if (info.type == 'self') {
		if (info.isll || !(info.def instanceof lambda)) {
			return 'self->' + this.cSafeName();
		} else {
			return 'mcall(' + getMethodId(name) + '/* ' + name + ' */, 1, self)';
		}
	}
	throw new Error('Unsupported reference type ' + info.type + ' for variable ' + name);
};
symbol.prototype.toCLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toCLLExpr(vars) + ';' ];
};

var declaredInts = {};

intlit.prototype.toC = function() {
	var intType = (this.unsigned ? 'u' : '') + 'int' + this.bits;
	var str =  intType + '_' + (this.val < 0 ? 'neg_' + (0-this.val).toString() : this.val.toString());
	if (!(str in declaredInts)) {
		toplevelcode += 'obj_' + intType + ' ' + str + ' = {{&obj_' + intType + '_meta, NULL}, ' + this.val.toString() + '};\n';
		declaredInts[str] = true;
	}
	return '((object *)&' + str + ')';
}
intlit.prototype.toCLLExpr = function(vars) {
	return this.val.toString();
};
intlit.prototype.toCLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toCLLExpr(vars) + ';' ];
};

floatlit.prototype.toC = function() {
	var floatType = this.bits == 32 ? 'float' : 'double';
	var str =  floatType + '_' + (this.val < 0 ? 'neg_' + (0-this.val).toString() : this.val.toString()).replace('.', '_').replace('-', 'neg');
	if (!(str in declaredInts)) {
		toplevelcode += 'obj_float' + this.bits + ' ' + str + ' = {{&obj_float' + this.bits + '_meta, NULL}, ' + this.val.toString() + '};\n';
		declaredInts[str] = true;
	}
	return '((object *)&' + str + ')';
}
floatlit.prototype.toCLLExpr = function(vars) {
	return this.val.toString();
};
floatlit.prototype.toCLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toCLLExpr(vars) + ';' ];
};

var declaredStrings = {};
var nextStringId = 0;

strlit.prototype.toC = function() {
	if (!(this.val in declaredStrings)) {
		//TODO: get the proper byte length
		toplevelcode += 'string str_' + nextStringId + ' = {{&string_meta, NULL}, ' + this.val.length + ', ' + this.val.length + ', "' + this.val.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r') + '"};\n';
		declaredStrings[this.val] = nextStringId++;
	}
	return '((object *)&str_' + declaredStrings[this.val] + ')';
};
strlit.prototype.toCLLExpr = function(vars) {
	return '"' + this.val.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r') + '"';
};
strlit.prototype.toCLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toCLLExpr(vars) +';' ];
};

listlit.prototype.toC = function() {
	var ret = 'make_list(' + this.val.length;
	for (var i = this.val.length-1; i >= 0; i--) {
		ret += ', ' + this.val[i].toC();
	}
	return ret + ')';
}

arraylit.prototype.toC = function() {
	var ret = 'make_array(' + this.val.length;
	for (var i = 0; i < this.val.length; i++) {
		ret += ', ' + this.val[i].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:");
		}
	} else if(name == 'llProperty:withType' || name == 'llProperty:withVars:andCode' || name == 'quote') {
		return null;
	}
	var args = this.args.slice(0, this.args.length);
	var method = false;
	var start = 0;
	if (this.receiver) {
		args.splice(0, 0, this.receiver);
		method = true;
	} else {
		var funinfo = this.symbols.find(name);
		if (!funinfo || funinfo.def instanceof setter || funinfo.type == 'toplevel') {
			method = true;
		} else {
			switch(funinfo.type)
			{
			case 'self':
			case 'parent':
				var defargs = funinfo.def.args.length;
				if (!defargs || funinfo.def.args[0].cleanName() != 'self') {
					defargs ++
				}
				if (args.length < defargs) {
					if (funinfo.type == 'self') {
						args.splice(0, 0, new symbol('self', this.symbols));
					} else {
						var obj = (new symbol('self', this.symbols)).toC() + '->header.';
						for (var i = 0; i < funinfo.depth; ++i) {
							obj += (i ? '->' : '') + 'parent';
						}
						args.splice(0, 0, ', ' + obj);
						start = 1;
					}
				} else if(!(args[0] instanceof symbol) || args[0].name != 'self') {
					funinfo = null;
				}
				method = true;
				break;
			}
		}
	}
	for (var i = start; i < args.length; i++) {
		args[i] = ', ' + (!i && method ? '(object *)(' : '') + args[i].toC() + (!i && method ? ')' : '');
	}
	var callpart;
	if (method) {
		if (funinfo && funinfo.type == 'self' && funinfo.def.name) {
			callpart =  funinfo.def.name + '(' + (funinfo.def.symbols.parent.needsenv ? (new symbol('self', this.symbols)).toC() + '->env' : 'NULL' );
		} else {
			callpart = 'mcall(' + getMethodId(name) + '/* ' + name + ' */';
		}
	} else {
		var closVar = (new symbol(name, this.symbols)).toC();
		callpart = '((lambda *)' + closVar + ')->func(((lambda *)' + closVar + ')->env';
	}
	return callpart + ', ' + args.length + args.join('') + ')';
};
funcall.prototype.toCTypeName = function() {
	switch(this.name)
	{
	case 'const:':
	case 'const':
		var receiver = this.receiver ? this.receiver : this.args[0];
		return  'const ' + receiver.toCTypeName();
		break;
	case 'ptr:':
	case 'ptr':
		var receiver = this.receiver ? this.receiver : this.args[0];
		return receiver.toCTypeName() + ' *';
		break;
	case 'struct:':
	case 'struct':
		var receiver = this.receiver ? this.receiver : this.args[0];
		return 'struct ' + receiver.toCTypeName();
		break;
	case 'funptr:':
	case 'funptr':
		var rettype;
		var firstArg;
		if (this.receiver) {
			rettype = this.receiver;
			firstArg = 0;
		} else {
			rettype = this.args[0];
			firstArg = 1;
		}
		var argtypes = '';
		for (var i = firstArg; i < this.args.length; i++) {
			if (argtypes) {
				argtypes += ', '
			}
			argtypes += this.args[i].toCTypeName();
		}
		return [rettype.toCTypeName() + '(*', ')(' + argtypes + ')'];
		break;
	default:
		throw new Error('invalid use of funcall expression where a C type name is expected: ' + this.name);
	}
};
funcall.prototype.toCLines = function(vars, needsreturn) {
	var lines = [];
	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
	var args = this.args.slice(0, this.args.length);
	if (this.receiver) {
		args.splice(0, 0, [this.receiver]);
	}
	switch(name)
	{
	case 'if':
		lines.push('if (' + this.args[0].toCLLExpr(vars) + ') {');
		var blines = this.args[1].toCLines(vars, needsreturn);
		for (var i in blines) {
			lines.push('\t' + blines[i]);
		}
		if (needsreturn) {
			lines.push('} else {');
			lines.push('\t return module_false;');
			lines.push('}');
		} else {
			lines.push('}');
		}
		break;
	case 'if:else':
		lines.push('if (' + this.args[0].toCLLExpr(vars) + ') {');
		var blines = this.args[1].toCLines(vars, needsreturn);
		for (var i in blines) {
			lines.push('\t' + blines[i]);
		}
		lines.push('} else {');
		blines = this.args[2].toCLines(vars, needsreturn);
		for (var i in blines) {
			lines.push('\t' + blines[i]);
		}
		lines.push('}');
		break;
	case 'while:do':
		if (needsreturn) {
			throw new Error("while:do can't be last statement in llMessage code block");
		}
		lines.push('while (' + this.args[0].toCLLExpr(vars) + ') {');
		var blines = this.args[1].toCLines(vars);
		for (var i in blines) {
			lines.push('\t' + blines[i]);
		}
		lines.push('}');
		break;
	default:
		lines.push( (needsreturn ? 'return (object *)' : '') + this.toCLLExpr(vars) + ';');
	}
	return lines;
};

funcall.prototype.toCLLExpr = function(vars) {
	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
	var args = this.args.slice(0, this.args.length);
	if (this.receiver) {
		if(this.args.length == 0) {
			if (this.receiver instanceof symbol && this.receiver.name in vars && vars[this.receiver.name].substr(-1) != "*") {
				return this.receiver.toCLLExpr(vars) + '.' + this.name;
			} else {
				return this.receiver.toCLLExpr(vars) + '->' + this.name;
			}
		} else if (this.args.length == 1 && name[name.length-1] == '!') {
			if (this.receiver instanceof symbol && this.receiver.name in vars && vars[this.receiver.name].substr(-1) != "*") {
				return this.receiver.toCLLExpr(vars) + '.' + this.name.substr(0, name.length-1) + ' = ' + args[0].toCLLExpr(vars);
			} else {
				return this.receiver.toCLLExpr(vars) + '->' + this.name.substr(0, name.length-1) + ' = ' + args[0].toCLLExpr(vars);
			}
		} else {
			args.splice(0, 0, this.receiver);
		}
	}
	switch(name)
	{
	case 'if':
		return '((' + args[0].toCLLExpr(vars) + ') ? (' + args[1].toCLLExpr(vars) + ') : 0)';
	case 'if:else':
		return '((' + args[0].toCLLExpr(vars) + ') ? (' + args[1].toCLLExpr(vars) + ') : (' + args[2].toCLLExpr(vars) + '))';
	case 'while:do':
		throw new Error('while:do not allowed in expression context in llMessage block');
	case 'addr_of':
		return '(&(' + args[0].toCLLExpr(vars) + '))';
	case 'sizeof':
		return 'sizeof(' + args[0].toCTypeName() + ')';
	case 'get':
		return args[0].toCLLExpr(vars) + '[' + args[1].toCLLExpr(vars) + ']';
	case 'set':
		return args[0].toCLLExpr(vars) + '[' + args[1].toCLLExpr(vars) + '] = ' + args[2].toCLLExpr(vars);
	case 'not':
		return '!(' + args[0].toCLLExpr(vars) + ')';
	case 'castTo':
		return '((' + args[1].toCTypeName() + ')(' + args[0].toCLLExpr(vars) + '))';
	case 'mcall':
		if (args[0] instanceof symbol) {
			args[0] = new intlit(getMethodId(args[0].name));
		}
	default:
		for (var i in args) {
			args[i] = args[i].toCLLExpr(vars);
		}
		return name + '(' + args.join(', ') + ')';
	}
};

function cObject(name) {
	this.name = name;
	this.slots = {};
	this.properties = [];
	this.values = [];
	this.slotvars = {};
	this.includes = {};
	this.parent = 'NULL';
	this.init = [];
	this.initmsgadded = false;
	this.imported = [];
}

cObject.prototype.addInclude = function(includefile) {
	this.includes[includefile] = true;
}

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, '\t\t' + implementation.lines.join('\n\t\t') + '\n', msgname]);
	if (!(trunc in this.slotvars)) {
		this.slotvars[trunc] = {};
	}
	for (var varname in implementation.vars) {
		this.slotvars[trunc][varname] = implementation.vars[varname];
	}
}

cObject.prototype.addProperty = function(propname, value, type) {
	if (type != undefined) {
		this.properties.push([propname, type]);
		if (value !== null) {
			this.values.push(value);
		}
	} else {
		var escaped = escapeCName(propname);
		this.addMessage(propname, {
			vars: {},
			lines: [
				'return (object *)self->' + escaped + ';'
		]});
		this.addMessage(propname + '!', {
			vars: {},
			lines: [
				'self->' + escaped + ' = va_arg(args, object *);',
				'return (object *)self;'
		]});
		this.properties.push(escaped);
		this.values.push(value);
	}
}

cObject.prototype.addInit = function(statement) {
	this.init.push(statement);
};

cObject.prototype.addImport = function(symbols, source) {
	var importNum = this.imported.indexOf(source);
	if (importNum < 0) {
		var importNum = this.imported.length;
		this.imported.push(source);
		this.addInit('self->import_' + importNum + ' = ' + source.toC() + ';');
	}
	if (symbols) {
		var self = this;
		each(symbols, function(i, sym) {
			self.addMessage(sym.name, {
				vars: {},
				lines: [
					'return self->import_' + importNum + '->meta->meth_lookup[method_id & 0xF](method_id, num_params, self->import_' + importNum + ', args);'
				]
			});
		});
	} else {
		//TODO: handle proxying for unimplemented methods
	}
};

cObject.prototype.checkInitMsg = function() {
	if (!this.initmsgadded && this.init.length) {
		var init = this.init.slice(0, this.init.length);
		init.push('return (object *)self;');
		this.addMessage('_init', {
			vars: {},
			lines: init
		});
		this.initmsgadded = true;
	}
}

cObject.prototype.populateSymbols = function() {};

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

cObject.prototype.toCDef = function() {
	this.checkInitMsg();
	var slotdefs = '';
	var methlists = '';
	var methdict = '}, {'
	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 * oself, va_list args) {\n\t' +
				this.name + ' *self = (' + this.name + ' *)oself;\n';
			for (var varname in this.slotvars[i]) {
				if (this.slotvars[i][varname] instanceof Array) {
					slotdefs += '/*foo*/\t' + this.slotvars[i][varname][0] + ' ' + varname + this.slotvars[i][varname][1] + ';\n';
				} else {
					slotdefs += '/*bar*/\t' + this.slotvars[i][varname] + ' ' + varname + ';\n';
				}
			}
			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, (object *)self, args);\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' +
					'\t\t\treturn no_impl(method_id, num_params, (object *)self, args);\n\t}\n}\n';

			}
			methlists += 'uint32_t ' + this.name + '_methods_' + i + '[] = {'
			for (j in this.slots[i]) {
				methlists += this.slots[i][j][0] + ', ';
			}
			methlists += 'LAST_METHOD};';
			metadef += this.name + '_slot_' + i;
			methdict += (i ? ', ' : '') + this.name + '_methods_' + i;
		} else {
			metadef += 'no_impl';
			methdict += (i ? ', ' : '') + 'NULL';
		}
	}
	metadef += methdict + '}};\n';
	return slotdefs + methlists + metadef;
}

cObject.prototype.toCInstance = function() {
	this.checkInitMsg();
	var ret = 'make_object(&' + this.name + '_meta, ' + this.parent + ', ' + this.values.length + (this.values.length ? ', ' : '') + this.values.join(', ') + ')';
	if (this.initmsgadded) {
		ret = 'mcall(' + getMethodId('_init') + ', 1, ' + ret + ')'
	}
	return ret;
}

cObject.prototype.toC = function() {
	forwarddec += this.toEarlyCDef();
	toplevelcode += this.toCDef();
	return this.toCInstance();
}

var nextobject = 0;


object.prototype.toCObject = function() {
	var messages = this.messages;
	if (!this.name) {
		this.name = 'object_' + nextobject++;
	}
	var me = new cObject(this.name);
	this.symbols.typename = me.name;
	if (this.symbols.needsenv) {
		me.addProperty('env', this.symbols.envVar(), 'struct ' + this.symbols.getEnvType() + ' * ');
		me.hasenv = true;
	}
	if (this.symbols.needsparent && !(this.symbols.parent instanceof osymbols)) {
		me.parent = '(object *)(' + (new symbol('self', this.symbols.parent)).toC() + ')';
	}
	for (var i in messages) {
		if (messages[i] instanceof funcall) {
			var msgname = messages[i].name;
			if (msgname[msgname.length-1] == ':') {
				msgname = msgname.substr(0, msgname.length-1);
			}
			if (msgname == 'import' && messages[i].args.length == 1) {
				me.addImport(false, messages[i].args[0]);
			} else if(msgname == '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(el);
				});
				me.addImport(importsyms, messages[i].args[1]);
			} else if(msgname == 'llProperty:withType' && messages[i].args.length == 2) {
				me.addProperty(messages[i].args[0].name, null, messages[i].args[1].toCTypeName());
			} else if(msgname == 'llMessage:withVars:andCode' && messages[i].args.length == 3) {
				if (messages[i].args[0] instanceof symbol) {
					var msgname = messages[i].args[0].name;
				} else if (messages[i].args[0] instanceof strlit) {
					var msgname = messages[i].args[0].val;
				} else {
					throw new Error('First argument to llMessage:withVars:andCode must be a symbol or string');
				}
				var rawvars = messages[i].args[1].expressions;
				var vars = {};
				for(var v in rawvars) {
					vars[rawvars[v].symbol.cSafeName()] = rawvars[v].expression.toCTypeName();
				}
				me.addMessage(msgname, {
					vars: vars,
					lines: messages[i].args[2].toCLines(vars, true)
				});
			} else if(msgname == 'includeSystemHeader' && messages[i].args.length == 1) {
				if (messages[i].args[0] instanceof strlit) {
					me.addInclude("<" + messages[i].args[0].val + ">");
				} else if(messages[i].args[0] instanceof symbol) {
					me.addInclude(messages[i].args[0].name);
				} else {
					throw new Error('Argument to includeSystemHeader must be a string literal or symbol');
				}
			} else {

				throw new Error('Only import and import:from calls allowed in object context. ' + messages[i].name + 'with ' + messages[i].args.length + ' arguments found instead.');
			}
		} else {
			messages[i].toCObject(me);
		}
	}

	return me;
};

object.prototype.toC = function() {
	return this.toCObject().toC();
};

var toplevelcode;
var forwarddec;

function addBinaryOp(cobject, opname, cop, objtype)
{
	cobject.addMessage(opname, {
		vars: {ret: objtype + ' *', argb: objtype +' *'},
		lines: [
			'argb = va_arg(args, ' + objtype + ' *);',
			'ret = (' + objtype + ' *)make_object(&' + objtype + '_meta, NULL, 0);',
			'ret->num = self->num ' + cop + ' argb->num;',
			'return &(ret->header);'
		]
	});
}

function addCompOp(cobject, opname, cop, objtype)
{
	cobject.addMessage(opname, {
		vars: {argb: objtype + ' *'},
		lines: [
			'argb = va_arg(args, ' + objtype + ' *);',
			'if (self->num ' + cop + ' argb->num) {',
			'	return ' + toplevel.moduleVar('true') + ';',
			'}',
			'return ' + toplevel.moduleVar('false') + ';',
		]
	});
}

function addNumberOps(obj, typename)
{
	addBinaryOp(obj, '+', '+', typename);
	addBinaryOp(obj, '-', '-', typename);
	addBinaryOp(obj, '*', '*', typename);
	addBinaryOp(obj, '/', '/', typename);
	addCompOp(obj, '<', '<', typename);
	addCompOp(obj, '>', '>', typename);
	addCompOp(obj, '=', '==', typename);
	addCompOp(obj, '!=', '!=', typename);
	addCompOp(obj, '>=', '>=', typename);
	addCompOp(obj, '<=', '<=', typename);

	obj.addMessage('jsonEncode', {
		vars: {},
		lines: [
			'return mcall(' + getMethodId('string') + ', 1, &self->header);'
		]
	});
}

function makeInt(bits, unsigned)
{
	var typename = 'obj_' + (unsigned ? 'u' : '') + 'int' + bits;
	var intObj = new cObject(typename);
	intObj.addProperty('num', null, (unsigned ? 'u' : '') + 'int' + bits +'_t');
	addNumberOps(intObj, typename);
	addBinaryOp(intObj, '%', '%', typename);
	addBinaryOp(intObj, 'or', '|', typename);
	addBinaryOp(intObj, 'xor', '^', typename);
	addBinaryOp(intObj, 'and', '&', typename);
	addBinaryOp(intObj, 'lshift:by', '<<', typename);
	addBinaryOp(intObj, 'rshift:by', '>>', typename);

	intObj.addInclude('<string.h>');
	//-9223372036854775808
	//01234567890123456789
	intObj.addMessage('string', {
		vars: {str: 'string *'},
		lines: [
			'str = (string *)make_object(&string_meta, NULL, 0);',
			'str->data = GC_MALLOC(' + (bits == 64 ? 21 : 12) + ');',
			'sprintf(str->data, "%' + (bits == 64 ? 'l' : '') + (unsigned ? 'u' : 'd') + '", self->num);',
			'str->len = str->bytes = strlen(str->data);',
			'return &(str->header);'
		]
	});
	intObj.addMessage('utf8', {
		vars: {str: 'string *', norm: 'uint32_t'},
		lines: [
			'str = (string *)make_object(&string_meta, NULL, 0);',
			'str->len = 1;',
			'norm = self->num;',
			'if (norm < 0x80) {',
			'	str->bytes = 1;',
			'	str->data = GC_MALLOC(2);',
			'	str->data[0] = norm;',
			'} else if(norm < 0x800) {',
			'	str->bytes = 2;',
			'	str->data = GC_MALLOC(3);',
			'	str->data[0] = 0xC0 | norm >> 6;',
			'	str->data[1] = 0x80 | (norm & 0x3F);',
			'} else if(norm < 0x10000) {',
			'	str->bytes = 3;',
			'	str->data = GC_MALLOC(4);',
			'	str->data[0] = 0xE0 | norm >> 12;',
			'	str->data[1] = 0x80 | ((norm >> 6) & 0x3F);',
			'	str->data[2] = 0x80 | (norm & 0x3F);',
			'} else if(norm < 0x10FFFF) {',
			'	str->bytes = 4;',
			'	str->data = GC_MALLOC(5);',
			'	str->data[0] = 0xF0 | norm >> 18;',
			'	str->data[1] = 0x80 | ((norm >> 12) & 0x3F);',
			'	str->data[2] = 0x80 | ((norm >> 6) & 0x3F);',
			'	str->data[3] = 0x80 | (norm & 0x3F);',
			'} else {',
			'	str->len = str->bytes = 0;',
			'	str->data = GC_MALLOC(1);',
			'}',
			'str->data[str->bytes] = 0;',
			'return &(str->header);'
		]
	});
	//7FFFFFFFFFFFFFFF
	//01234567890123456789
	intObj.addMessage('hex', {
		vars: {str: 'string *'},
		lines: [
			'str = (string *)make_object(&string_meta, NULL, 0);',
			'str->data = GC_MALLOC(' + (bits == 64 ? 17 : 9) + ');',
			'sprintf(str->data, "%' + (bits == 64 ? 'l' : '') +'X", self->num);',
			'str->len = str->bytes = strlen(str->data);',
			'return &(str->header);'
		]
	});
	intObj.addMessage('isInteger?', {
		vars: {},
		lines: [
			'return ' + toplevel.moduleVar('true') + ';'
		]
	});
	if (bits == 32 && !unsigned) {
		intObj.addMessage('hash', {
			vars: {},
			lines:  [
				'return &(self->header);'
			]
		});
	} else {
		intObj.addMessage('hash', {
			vars: {
				int32ret: 'obj_int32 *'
			},
			lines:  [
				'int32ret = make_object(&obj_int32_meta, NULL, 0);',
				bits < 64 ? 'int32ret->num = self->num;' : 'int32ret->num = self->num ^ (self->num >> 32);',
				'return &(int32ret->header);'
			]
		});
	}
	intObj.addMessage('signed?', {
		vars: {},
		lines: [
			'return ' + toplevel.moduleVar(unsigned ? 'false' : 'true') + ';'
		]
	});
	var sizes = [8, 16, 32, 64];
	var destunsigned = [false, true];
	for (var i = 0; i < sizes.length; i++) {
		size = sizes[i];
		for (var j = 0; j < destunsigned.length; j++) {
			uns = destunsigned[j];
			if (uns == unsigned && size == bits) {
				intObj.addMessage((uns ? 'u' : '') + 'int' + size, {
					vars: {},
					lines: [
						'return &(self->header);'
					]
				});
			} else {
				var retType = 'obj_' + (uns ? 'u' : '') + 'int' + size;
				var retName = 'ret' + (uns ? 'u' : '') + size;
				var vars = {};
				vars[retName] = retType + ' *';
				intObj.addMessage((uns ? 'u' : '') + 'int' + size, {

					vars: vars,
					lines: [
						retName + ' = ('+retType+' *)make_object(&' + retType +'_meta, NULL, 0);',
						retName + '->num = self->num;',
						'return &(' + retName + '->header);'
					]
				});
			}
		}
		if (size > 16) {
			var retType = 'obj_float' + size;
			var retName = 'retfloat' + size;
			var vars = {};
			vars[retName] = retType + ' *';
			intObj.addMessage((size > bits ? 'f' : 'truncF') + size, {
				vars: vars,
				lines: [
					retName + ' = make_object(&' + retType + '_meta, NULL, 0);',
					retName + '->num = self->num;',
					'return &(' + retName + '->header);'
				]
			})
		}
	}

	return intObj;
}

function makeFloat(bits)
{
	var typename = 'obj_float' + bits;
	var floatObj = new cObject(typename);
	floatObj.addProperty('num', null, bits == 32 ? 'float' : 'double');
	addNumberOps(floatObj, typename);

	floatObj.addInclude('<string.h>');
	floatObj.addMessage('string', {
		vars: {str: 'string *'},
		lines: [
			'str = (string *)make_object(&string_meta, NULL, 0);',
			//probably overkill, but lets play it safe for now
			'str->data = GC_MALLOC(128);',
			'sprintf(str->data, "%f", self->num);',
			'str->len = str->bytes = strlen(str->data);',
			'return &(str->header);'
		]
	});
	
	floatObj.addMessage('hash', {
		vars: {
			intptr: 'int32_t *',
			int32ret: 'obj_int32 *'
		},
		lines: [
			'intptr = (int32_t *)&self->num;',
			'int32ret = make_object(&obj_int32_meta, NULL, 0);',
			'int32ret->num = *intptr' + (size == 64 ? ' ^ intptr[1];' : ';'),
			'return &(int32ret->header);'
		]
	});
	
	floatObj.addMessage('f' + bits, {
		vars: {},
		lines: [
			'return &(self->header);'
		]
	});

	floatObj.addMessage('f' + (bits == 32 ? 64 : 32), {
		vars: {trans: 'obj_float' + (bits == 32 ? 64 : 32) + ' *'},
		lines: [
			'trans = make_object(&obj_float' + (bits == 32 ? 64 : 32) + '_meta, NULL, 0);',
			'trans->num = self->num;',
			'return &(trans->header);'
		]
	});
	var sizes = [8, 16, 32, 64];
	var prefixes = ['U', ''];
	for (var i = 0; i < sizes.length; i++) {
		for (var j = 0; j < prefixes.length; j++) {
			var ret = 'ret' + i + j;
			var vars = {};
			var retType = 'obj_' + prefixes[j].toLowerCase() + 'int' + sizes[i];
			vars[ret] = retType + ' *';
			floatObj.addMessage('trunc' + prefixes[j] + 'Int' + sizes[i], {
				vars: vars,
				lines: [
					ret + ' = make_object(&' + retType + '_meta, NULL, 0);',
					ret + '->num = self->num;',
					'return &(' + ret + '->header);'
				]
			});
		}
	}
	return floatObj;
}

function makeCPointer()
{
	var cptr = new cObject('cpointer');
	cptr.addProperty('val', null, 'void *');
	//cpointer:
	//1234567890
	cptr.addMessage('string', {
		vars: {ret: 'string *'},
		lines: [
			'ret = make_object(&string_meta, NULL, 0);',
			//22 digits for worst case sensible representation
			//10 chars for prefix
			//4 for slop
			//1 for null terminator
			'ret->data = GC_MALLOC_ATOMIC(22+10+4+1);',
			'snprintf(ret->data, 22+10+4, "cpointer: %p", self->val);',
			'ret->data[22+10+4] = 0;',
			'return (object *)ret;'
		]
	});
	return cptr;
}

function makeArray()
{
	var arrayfile = toplevel.names['array'];
	var ast = parseFile(arrayfile.path + '/' + arrayfile.file);
	ast.name = 'array';
	ast.populateSymbols(toplevel);
	return ast.toCObject();
}

function makeString()
{
	var arrayfile = toplevel.names['string'];
	var ast = parseFile(arrayfile.path + '/' + arrayfile.file);
	ast.name = 'string';
	ast.populateSymbols(toplevel);
	return ast.toCObject();
}

function makelambda()
{
	var clos = new cObject('lambda');
	clos.addProperty('env', null, 'void *');
	clos.addProperty('func', null, 'closure_func');
	clos.addMessage('while:do', {
		vars: {action: 'lambda *', ret: 'object *'},
		lines: [
			'action = va_arg(args, lambda *);',
			'ret = ' + toplevel.moduleVar('true') + ';',
			'while(' + toplevel.moduleVar('true') + ' == ccall(self, 0)) {',
			'	ccall(action, 0);',
			'}',
			'return ret;'
		]
	});
	return clos;
}

function builtinTypes()
{
	return [makeInt(64, false), makeInt(32, false), makeInt(16, false), makeInt(8, false),
	        makeInt(64, true) , makeInt(32, true),  makeInt(16, true),  makeInt(8, true),
	        makeFloat(32), makeFloat(64), makeArray(), makeString(), makelambda(), makeCPointer()];
}

modulefile.prototype.toC = function(){
	return this.ast.toCModuleInstance();
};

function processUsedToplevel(toplevel)
{
	var alwaysused = ['true', 'false', 'list'];
	var ret = '';
	var modulenum = 0;
	var visited = {};
	var newused = alwaysused;//Object.keys(toplevel.used);
	var allused = newused;
	print('//newused = ' + newused.join(', '));
	while (newused.length) {
		for (var i in newused) {
			debugprint('//---module', newused[i], '--- populate symbols');
			forwarddec += 'object * ' + toplevel.moduleVar(newused[i]) + ';\n';
			toplevel.names[newused[i]].populateSymbols(toplevel);
			visited[newused[i]] = true;
		}
		newused = [];
		for (var symbol in toplevel.used) {
			if (!(symbol in visited)) {
				debugprint('//found new usage of module', symbol);
				newused.push(symbol);
				allused.push(symbol);
			}
		}
	}
	var compiled = {};
	//true and false depend on each other, but not at module init time
	//force them to have no dependencies
	toplevel.names['true'].dependencies = {};
	toplevel.names['false'].dependencies = {};
	//string and object can be a problem as well and is safe to init them before other things
	toplevel.names['string'].dependencies = {};
	toplevel.names['object'].dependencies = {};

	while (allused.length) {
		var nextused = [];
		var minUnmet = allused.length;
		var minUnmetIdx = -1;
		for (var i = 0; i < allused.length; i++) {
			var symbol = allused[i];
			var module = toplevel.names[symbol];
			var unmet = 0;
			for (var dependency in module.dependencies) {
				if (!(dependency in compiled) && dependency != symbol) {
					unmet++;
				}
			}
			if (unmet) {
				nextused.push(symbol);
				if (unmet < minUnmet) {
					minUnmet = unmet;
					minUnmetIdx = i;
				}
			} else {
				debugprint('//---module', symbol, '(' + i +')--- compile');
				debugprint('//    dependencies: ' + Object.keys(toplevel.names[symbol].dependencies).join(', '));
				assignNames.push(symbol);
				ret += '\t' + toplevel.moduleVar(symbol) + ' = ' + toplevel.names[symbol].toC() + ';\n';
				assignNames.pop();
				compiled[symbol] = true;
			}
		}
		if (nextused.length == allused.length) {
			var symbol = nextused[minUnmetIdx];
			print('//WARNING: circular module dependency detected, compiling ' + symbol + ' with dependencies ' + Object.keys(toplevel.names[symbol].dependencies).join(', '));
			print('//         but only these dependencies are met: ' + Object.keys(compiled).join(', '));
			debugprint('//---module', symbol, '(' + i +')--- compile');
			assignNames.push(symbol);
			ret += '\t' + toplevel.moduleVar(symbol) + ' = ' + toplevel.names[symbol].toC() + ';\n';
			assignNames.pop();
			compiled[symbol] = true;
			nextused[minUnmetIdx] = nextused[nextused.length-1];
			nextused.pop();
		}
		allused = nextused;
	}
	return ret;
}

function makeCProg(obj)
{
	forwarddec = toplevelcode = '';
	assignNames = [];
	var builtins = builtinTypes();
	for (var i in builtins) {
		forwarddec += builtins[i].toEarlyCDef();
		toplevelcode += builtins[i].toCDef();
	}
	debugprint('//------POPULATING SYMBOLS-----');
	obj.populateSymbols(toplevel);
	var moduleinit = processUsedToplevel(toplevel);
	debugprint('//------COMPILING AST-----');
	var rest = 'object * mainModule() {\n' + moduleinit + '\tmain_module = ' + obj.toCModuleInstance() + ';\n\treturn main_module;\n}\n';
	var mnames = 'char * methodNames[] = {\n';
	for (var i = 0; i < methodNames.length; i++) {
		mnames += '\t"' + methodNames[i].replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n") + '",\n';
	}
	mnames += '};\n';
	return '#include "runtime/proghead.inc"\n' +
		'#define METHOD_ID_MAIN ' + getMethodId('main') + '\n' +
		'#define METHOD_ID_EMPTY ' + getMethodId('empty') + '\n' +
		'#define METHOD_ID_CONS ' + getMethodId(getOpMethodName('|')) + '\n' +
		mnames + forwarddec + toplevelcode + rest + '#include "runtime/progfoot.inc"\n';
}

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

object.prototype.toCModuleInstance = function() {
	return this.toC();
}

lambda.prototype.toC = function() {
	var args = this.args ? this.args.slice(0, this.args.length) : [];
	var exprs = this.expressions;
	var assignPath = assignNames.join('<-');
	debugprint('//', this.name, assignPath);
	var addedTypeDef = false;
	if (Object.keys(this.symbols.closedover).length) {
		this.symbols.envtype = this.name + '_env';
		forwarddec += 'typedef struct ' + this.symbols.envtype + '  ' + this.symbols.envtype + ';\n'
		var addedTypeDef = true;
	}
	if (this.selftype) {
		this.symbols.defineVar('self', 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) {
		var argname = args[i].toC();
		this.symbols.declareVar(args[i].cleanName());
		args[i] = (argname.indexOf('->') < 0 ? '\tobject * ' : '\t') + argname + ' = va_arg(args, object *);\n';
	}
	var compiled = []
	for (var i in exprs) {
		var js = exprs[i].toC();
		if (js) {
			compiled.push(indent(js));
		}
	}
	if (compiled.length) {
		if (exprs[exprs.length - 1] instanceof assignment) {
			compiled.push('return (object *)' + exprs[exprs.length - 1].symbol.toC() + ';');
		} else {
			compiled[compiled.length-1] = 'return (object *)(' + compiled[compiled.length-1] + ');';
		}
	}
	exprs = compiled;

	if (Object.keys(this.symbols.closedover).length) {
		if (!addedTypeDef) {
			for (var key in this.symbols.closedover) {
				print(key, ": ", this.symbols.closedover[key]);
			}
			throw new Error('this.symbols.closedover is not empty, but it was when compilation of ' + this.name + ' "' + assignPath + '" started');
		}
		forwarddec += 'struct ' + this.name + '_env {\n';
		if (this.symbols.needsParentEnv) {
			forwarddec += '\tstruct ' + this.symbols.parentEnvType() + ' * parent;\n';
		}
		for (var varname in this.symbols.closedover) {
			if (varname == 'self' && this.selftype) {
				forwarddec += '\tstruct ' + this.selftype + ' * self;\n';
			} else {
				forwarddec += '\tobject * ' + escapeCName(varname) + ';\n';
			}
		}
		forwarddec += '};\n'

		var myenvinit = '\t' + this.name + '_env * myenv = GC_MALLOC(sizeof(' + this.name + '_env));\n';
		if (this.symbols.needsParentEnv) {
			myenvinit += '\tmyenv->parent = env;\n';
		}
		this.symbols.envtype = this.name + '_env';
	} else {
		var myenvinit = '';
	}
	forwarddec += 'object *' + this.name + ' (' + this.symbols.parentEnvType() + ' * env, uint32_t num_args, ...);\n';

	toplevelcode += '//' + assignPath + "\n";
	toplevelcode +=  'object * ' + this.name + ' ( ' + this.symbols.parentEnvType() + ' * env, uint32_t num_args, ...) {\n\tva_list args;\n' + myenvinit + '\tva_start(args, num_args);\n';
	if (this.selftype) {
		var selfvar = (new symbol('self', this.symbols)).toC();
		if (selfvar == 'self') {
			toplevelcode += '\t' + this.selftype + ' * self = va_arg(args, ' + this.selftype + ' *);\n';
		} else {
			toplevelcode += '\t' + selfvar  + ' = va_arg(args, ' + this.selftype + ' *);\n';
		}

	}
	toplevelcode += args.join('') + '\tva_end(args);\n' + exprs.join(';\n\t') + '\n}\n';

	if (this.selftype) {
		return this.name;
	} else {
		if (this.symbols.parentEnvType() != 'void') {
			if (this.symbols.parent.passthruenv) {
				var envvar = 'env';
			} else {
				var envvar = 'myenv';
			}
			debugprint('//' + this.name, 'has envvar:', envvar, 'num vars closed over:', Object.keys(this.symbols.closedover).length);
			return 'make_lambda(' + envvar + ', (closure_func)' + this.name + ')';
		} else {
			toplevelcode += 'lambda ' + this.name + '_obj = {{&lambda_meta, NULL}, NULL, ' + this.name + '};\n';
			return '((object *)&' + this.name + '_obj)';
		}
	}
};
lambda.prototype.toCModuleInstance = function() {
	this.toC();
	return this.name + '(NULL, 0)';
};
lambda.prototype.toCObject = function(typename) {
	this.selftype = typename;
	return this.toC();
};
lambda.prototype.toCModule = function() {
	return makeCProg(this);
};
lambda.prototype.toCLines = function(vars, needsreturn) {
	var lines = [];
	for (var i in this.args) {
		var name = this.args[i].name;
		if (name[0] == ':') {
			name = name.substr(1);
		}
		if (name[0] >= "0" && name[0] <= "9") {
                name = '_tp_' + name;
        }
		if(name != 'self') {
			lines.push(name + ' = va_arg(args, ' + vars[name] + ');');
		}
	}
	for (var i in this.expressions) {
		var exprlines = this.expressions[i].toCLines(vars, needsreturn && i == this.expressions.length - 1);
		for (var j in exprlines) {
			lines.push('\t' + exprlines[j]);
		}
	}
	return lines;
}
lambda.prototype.toCLLExpr = function(vars) {
	if (this.expressions.length != 1) {
		throw new Error('lambda in expression context must have a single statement in llMessage block');
	}
	return this.expressions[0].toCLLExpr(vars);
}

assignment.prototype.toC = function() {
	debugprint('//assignment', this.symbol.name);
	var existing = this.symbols.find(this.symbol.name);
	var prefix = '';
	assignNames.push(this.symbol.name);
	var val = this.expression.toC();
	assignNames.pop(this.symbol.name);
	if (val === null) {
		return null;
	}
	if (existing.type == 'local' && !existing.isdeclared) {
		prefix = 'object *';
		this.symbols.declareVar(this.symbol.name);
		debugprint('//declared var', this.symbol.name);
	}
	var cast = '';
	if (this.symbol.name == 'self') {
		//ugly hack alert
		cast = '(void *)';
	} else {
		cast = '(object *)';
	}
	return prefix + this.symbol.toC() + ' = ' + cast + val;
};
assignment.prototype.toCObject = function(cobj) {
	debugprint('//message definition', this.symbol.name);
	assignNames.push('#' + this.symbol.name);
	if (this.expression.toCObject) {
		var val = this.expression.toCObject(cobj.name);
	} else {
		var val = this.expression.toC();
	}
	assignNames.pop();
	if (val === null) {
		return;
	}
	if (this.expression instanceof lambda) {
		var params = ['((object *)self)'];
		var paramget = '';
		var messagevars = {};
		for (var i in this.expression.args) {
			var escaped = escapeCName(this.expression.args[i].cleanName());
			if (escaped != 'self') {
				messagevars[escaped] = 'object *';
				params.push(escaped);
				paramget += escaped + ' =  va_arg(args, object *); ';
			}
		}
		cobj.addMessage(getOpMethodName(this.symbol.name), {
			vars: messagevars,
			lines: [paramget + 'return ' + val + '(' + (cobj.hasenv ? 'self->env' : 'NULL') + ', ' + params.length + (params.length ? ', ' : '') + params.join(', ') + ');']
		});
	} else {
		cobj.addProperty(this.symbol.name, val);
		if (this.expression instanceof object && this.expression.symbols.needsparent) {
			cobj.addInit('self->' + escapeCName(this.symbol.name) + '->parent = (object *)self;');
		}
	}
};
assignment.prototype.toCLines = function(vars, needsreturn) {
	return [(needsreturn ? 'return ' : '') + this.symbol.toCLLExpr(vars) + ' = ' + this.expression.toCLLExpr(vars) + ';']
};