view cbackend.js @ 251:2557ce4e671f

Fix a couple of compiler bugs. topenv was getting initialized in multiple places. This resulted in multiple copies of modules getting created which caused problems for macro expansion. Additionally, arguments were not being marked as declared during code generation so assigning to an argument that was not closed over generated invalid C code.
author Michael Pavone <pavone@retrodev.com>
date Fri, 11 Apr 2014 22:29:32 -0700
parents c50f77de41d1
children d6a4c9e7716e
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 = {'+': 'ADD_', '-': 'SUB_', '*': 'MUL_', '/': 'DIV_', '%': 'MOD_',
	                '=': 'EQ_', '!=': 'NEQ_', '<': 'LT_', '>': 'GT_', '>=': 'GEQ_', '<=': 'LEQ_',
					'.': 'CAT_', '&&':'if', '||':'ifnot', '|': 'CONS_'};
	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, ' + (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.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 name;
	}
	if (info.type == 'toplevel') {
		return toplevel.moduleVar(name);
	} else if (info.type == 'self') {
		if (info.isll || !(info.def instanceof lambda)) {
			return 'self->' + name;
		} 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() {
	return 'make_float(' + this.val.toString() + ')';
}
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);
	if (this.receiver) {
		args.splice(0, 0, this.receiver);
	}
	var method = false;
	var funinfo = this.symbols.find(name);
	var start = 0;
	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 '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');
	}
};
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;
}

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 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) {
	this.imported.push(source);
	var importNum = imported.length - 1;
	if (symbols) {
		each(symbols, function(i, sym) {
			this.addMessage(sym.name, {
				vars: {},
				lines: [
					'return self->import_' + importNum + '->meta->meth_lookup[method_id & 0xF](method_id, num_args, 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'
		}
	}
	objdef += '} ' + this.name + ';\nobj_meta ' + this.name + '_meta;\n';
	return includes + objdef;
}

cObject.prototype.toCDef = function() {
	this.checkInitMsg();
	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 * 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';

			}
			metadef += this.name + '_slot_' + i;
		} else {
			metadef += 'no_impl';
		}
	}
	metadef += '}};\n';
	return slotdefs + 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) {
			if (messages[i].name == 'import:' && messages[i].args.length == 1) {
				me.addImport(false, 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(el);
				});
				me.addImport(importsyms, messages[i].args[1]);
			} else if(messages[i].name == 'llProperty:withType:' && messages[i].args.length == 2) {
				me.addProperty(messages[i].args[0].name, null, messages[i].args[1].toCTypeName());
			} else if(messages[i].name == 'llMessage:withVars:andCode:' && messages[i].args.length == 3) {
				var msgname = messages[i].args[0].name
				var rawvars = messages[i].args[1].expressions;
				var vars = {};
				for(var v in rawvars) {
					vars[rawvars[v].symbol.name] = rawvars[v].expression.toCTypeName();
				}
				me.addMessage(msgname, {
					vars: vars,
					lines: messages[i].args[2].toCLines(vars, true)
				});
			} else if(messages[i].name == 'includeSystemHeader:' && messages[i].args.length == 1) {
				me.addInclude("<" + messages[i].args[0].val + ">");
			} 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 makeInt(bits, unsigned)
{
	var typename = 'obj_' + (unsigned ? 'u' : '') + 'int' + bits;
	var intObj = new cObject(typename);
	intObj.addProperty('num', null, (unsigned ? 'u' : '') + 'int' + bits +'_t');
	addBinaryOp(intObj, 'ADD_', '+', typename);
	addBinaryOp(intObj, 'SUB_', '-', typename);
	addBinaryOp(intObj, 'MUL_', '*', typename);
	addBinaryOp(intObj, 'DIV_', '/', typename);
	addBinaryOp(intObj, 'MOD_', '%', typename);
	addBinaryOp(intObj, 'or', '|', typename);
	addBinaryOp(intObj, 'xor', '^', typename);
	addBinaryOp(intObj, 'and', '&', typename);
	addBinaryOp(intObj, 'lshift:by', '<<', typename);
	addBinaryOp(intObj, 'rshift:by', '>>', typename);
	addCompOp(intObj, 'LT_', '<', typename);
	addCompOp(intObj, 'GT_', '>', typename);
	addCompOp(intObj, 'EQ_', '==', typename);
	addCompOp(intObj, 'NEQ_', '!=', typename);
	addCompOp(intObj, 'GEQ_', '>=', typename);
	addCompOp(intObj, 'LEQ_', '<=', 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);'
		]
	});
	//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);'
					]
				});
			}
		}
	}

	return intObj;
}

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),
	        makeArray(), makeString(), makelambda()];
}

function addBuiltinModules(toplevel)
{
	var os = new cObject('mod_obj_os');
	os.addInclude('<sys/stat.h>');
	os.addInclude('<fcntl.h>');
	os.addInclude('<stdlib.h>');
	os.addInclude('<time.h>');
	os.addInclude('<unistd.h>');
	os.addMessage('write', {
		vars: {str: 'string *', intret: 'obj_int32 *', filedes: 'obj_int32 *'},
		lines: [
			'filedes = va_arg(args, obj_int32 *);',
			'str = va_arg(args, string *);',
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = write(filedes->num, str->data, str->bytes);',
			'return &(intret->header);'
		]
	});
	os.addMessage('read', {
		vars: {str: 'string *', size: 'obj_int32 *', filedes: 'obj_int32 *'},
		lines: [
			'filedes = va_arg(args, obj_int32 *);',
			'size = va_arg(args, obj_int32 *);',
			'str = (string *)make_object(&string_meta, NULL, 0);',
			'str->data = GC_MALLOC_ATOMIC(size->num + 1);',
			'str->len = str->bytes = read(filedes->num, str->data, size->num);',
			'if (str->bytes < 0) { str->bytes = str->len = 0; }',
			'str->data[str->bytes] = 0;',
			'return &(str->header);'
		]
	});
	os.addMessage('open', {
		vars: {str: 'string *', flags: 'obj_int32 *', filedes: 'obj_int32 *', perm: 'obj_int32 *'},
		lines: [
			'str = va_arg(args, string *);',
			'flags = va_arg(args, obj_int32 *);',
			'filedes = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'if (num_params == 3) {',
			'	filedes->num = open(str->data, flags->num);',
			'} else if (num_params == 4) {',
			'	perm = va_arg(args, obj_int32 *);',
			'	filedes->num = open(str->data, flags->num, perm->num);',
			'} else {',
			'	filedes->num = -1;',
			'}',
			'return &(filedes->header);'
		]
	});
	os.addMessage('close', {
		vars: {filedes: 'obj_int32 *', intret: 'obj_int32 *'},
		lines: [
			'filedes = va_arg(args, obj_int32 *);',
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = close(filedes->num);',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_RDONLY', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_RDONLY;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_WRONLY', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_WRONLY;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_RDWR', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_RDWR;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_CREAT', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_CREAT;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_APPEND', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_APPEND;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_TRUNC', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_TRUNC;',
			'return &(intret->header);'
		]
	});
	os.addMessage('rand', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = rand();',
			'return &(intret->header);'
		]
	});
	os.addMessage('rand64', {
		vars: {intret64: 'obj_int64 *'},
		lines: [
			'intret64 = (obj_int64 *)make_object(&obj_int64_meta, NULL, 0);',
			'intret64->num = (((int64_t)rand()) << 32 ) | rand();',
			'return &(intret64->header);'
		]
	});
	os.addMessage('srand', {
		vars: {oseed: 'object *', seed: 'obj_int32 *'},
		lines: [
			'oseed = va_arg(args, object *);',
			'seed = mcall(' + getMethodId("int32") + ', 1, oseed);',
			'srand(seed->num);',
			'return &(seed->header);'
		]
	});
	os.addMessage('time', {
		vars: {intret64: 'obj_int64 *'},
		lines: [
			'intret64 = (obj_int64 *)make_object(&obj_int64_meta, NULL, 0);',
			'intret64->num = time(NULL);',
			'return &(intret64->header);'
		]
	});
	os.addMessage('sleep', {
		vars: {osecs: 'object *', secs: 'obj_int32 *', intret: 'obj_int32 *'},
		lines: [
			'osecs = va_arg(args, object *);',
			'secs = mcall(' + getMethodId("int32") + ', 1, osecs);',
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = sleep(secs->num);',
			'return &(intret->header);'
		]
	});
	toplevel.names['os'] = os;
}

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

function processUsedToplevel(toplevel)
{
	var alwaysused = ['true', 'false', 'list'];
	var ret = '';
	var modulenum = 0;
	var visited = {};
	for (var i in alwaysused) {
		toplevel.used[alwaysused[i]] = true;
	}
	var newused = Object.keys(toplevel.used);
	var allused = newused;
	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);
			}
		}
	}

	for (var i = allused.length-1; i >= 0; i--) {
		var symbol = allused[i];
		debugprint('//---module', symbol, '(' + i +')--- compile');
		assignNames.push(symbol);
		ret += '\t' + toplevel.moduleVar(symbol) + ' = ' + toplevel.names[symbol].toC() + ';\n';
		assignNames.pop();
	}
	return ret;
}

function makeCProg(obj)
{
	forwarddec = toplevelcode = '';
	assignNames = [];
	var builtins = builtinTypes();
	for (var i in builtins) {
		forwarddec += builtins[i].toEarlyCDef();
		toplevelcode += builtins[i].toCDef();
	}
	addBuiltinModules(toplevel);
	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 ' + 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 != '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);
	}
	return prefix + this.symbol.toC() + ' = ' + 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) + ';']
};