view cbackend.js @ 331:61f5b794d939

Breaking change: method call syntax now always uses the syntactic receiver as the actual receiver. This makes its behavior different from function call syntax, but solves some problems with methods being shadowed by local variables and the like.
author Michael Pavone <pavone@retrodev.com>
date Sat, 28 Mar 2015 14:21:04 -0700
parents eef8a5cea812
children 2a0463c46913
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('.', '_');
	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') + ';'
		]
	});
	intObj.addMessage('hash', {
		vars: {},
		lines: [
			'return &(self->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('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) + ';']
};