view cbackend.js @ 69:ba032565c7a5

Fix handling of variable style access to self and parent object messages defined with lambdas. Improve test case for this bug to include parent object access as well as self object access.
author Mike Pavone <pavone@retrodev.com>
date Sat, 14 Jul 2012 19:24:04 -0700
parents 3a169ebb3224
children ab6f24d6945d
line wrap: on
line source

var mainModule;
var modules = {};

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

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

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

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

function escapeCName(name)
{
	if (name == 'self') {
		return name;
	}
	name = name.replace("_", "UN_").replace(":", "CN_").replace("!", "EX_").replace('?', 'QS_').replace('@', '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');
	}
	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);
}

var declaredInts = {};

intlit.prototype.toC = function() {
	var str = this.val < 0 ? 'neg_' + (0-this.val).toString() : this.val.toString();
	if (!(this.val in declaredInts)) {
		toplevelcode += 'obj_int32 int32_' + str + ' = {{&obj_int32_meta, NULL}, ' + this.val.toString() + '};\n';
		declaredInts[this.val] = true;
	}
	return '((object *)&int32_' + str + ')';
}

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

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('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r') + '"};\n';
		declaredStrings[this.val] = nextStringId++;
	}
	return '((object *)&str_' + declaredStrings[this.val] + ')';
}

listlit.prototype.toC = function() {
	var ret = 'make_list(' + this.val.length;
	for (var i = 0; i < this.val.length; 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:");
		}
	}
	var args = this.args.slice(0, this.args.length);
	if (this.receiver) {
		args.splice(0, 0, this.receiver);
	}
	var method = false;
	var funinfo = this.symbols.find(name);
	if (!funinfo || funinfo.def instanceof setter) {
		method = true;
	} else {
		switch(funinfo.type)
		{
		case 'self':
			
			if (args.length < funinfo.def.args.length || !funinfo.def.args.length || funinfo.def.args[0].name != 'self') {
				args.splice(0, 0, new symbol('self', this.symbols));
			} else {
				args.splice(0, 1);
			}
			method = true;
			break;
		case 'parent':
			ret = 'self';
			for (var i = 0; i < funinfo.depth; ++i) {
				ret += '->parent';
			}
			break;
		}
	}
	for (var i in args) {
		args[i] = ', ' + args[i].toC();
	}
	var callpart;
	if (method) {
		callpart = 'mcall(' + getMethodId(name) + '/* ' + name + ' */';
	} else {
		callpart = 'ccall(' + (new symbol(name, this.symbols)).toC();
	}
	return callpart + ', ' + args.length + 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.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) {
			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]) {
				slotdefs += '\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.toC = function() {
	var messages = this.messages;
	var values = [];
	var imports = []
	var me = new cObject('object_' + nextobject++);
	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) {
				imports.push({symbols: false, src: messages[i].args[0]});
			} else if(messages[i].name == 'import:from:' && messages[i].args.length == 2) {
				var importsyms = [];
				each(messages[i].args[0].val, function(i, el) {
					if (!(el instanceof symbol)) {
						throw new Error('Names in import:from statement must be symbols');
					}
					importsyms.push(new strlit(el.name));
				});
				imports.push({symbols: new listlit(importsyms), src: messages[i].args[1]});
			} else {
				throw new Error('Only import and import:from calls allowed in object context');
			}
		} else {
			messages[i].toCObject(me);
		}
	}

	return me.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 makeInt32()
{
	var int32 = new cObject('obj_int32');
	int32.addProperty('num', null, 'int32_t');
	addBinaryOp(int32, 'ADD_', '+', 'obj_int32');
	addBinaryOp(int32, 'SUB_', '-', 'obj_int32');
	addBinaryOp(int32, 'MUL_', '*', 'obj_int32');
	addBinaryOp(int32, 'DIV_', '/', 'obj_int32');
	addBinaryOp(int32, 'MOD_', '%', 'obj_int32');
	addCompOp(int32, 'LT_', '<', 'obj_int32');
	addCompOp(int32, 'GT_', '>', 'obj_int32');
	addCompOp(int32, 'EQ_', '==', 'obj_int32');
	addCompOp(int32, 'NEQ_', '!=', 'obj_int32');
	addCompOp(int32, 'GEQ_', '>=', 'obj_int32');
	addCompOp(int32, 'LEQ_', '<=', 'obj_int32');
	int32.addInclude('<string.h>');
	int32.addMessage('string', {
		vars: {str: 'string *'},
		lines: [
			'str = (string *)make_object(&string_meta, NULL, 0);',
			'str->data = malloc(12);',
			'sprintf(str->data, "%d", self->num);',
			'str->length = str->bytes = strlen(str->data);',
			'return &(str->header);'
		]
	});
	return int32;
}

function makeArray()
{
	var array = new cObject('array');
	array.addProperty('size', null, 'uint32_t');
	array.addProperty('storage', null, 'uint32_t');
	array.addProperty('data', null, 'object **');
	array.addMessage('get', {
		vars: {index: 'obj_int32 *'},
		lines: [
			'index = va_arg(args, obj_int32 *);',
			'if (index->num >= 0 && index->num < self->size) {',
			'	return self->data[index->num];',
			'}',
			'return ' + toplevel.moduleVar('false') + ';'
		]
	});
	array.addMessage('set', {
		vars: {index: 'obj_int32 *'},
		lines: [
			'index = va_arg(args, obj_int32 *);',
			'if (index->num >= 0 && index->num < self->size) {',
			'	self->data[index->num] = va_arg(args, object *);',
			'}',
			'return &(self->header);'
		]
	});
	array.addMessage('foreach', {
		vars: {index: 'obj_int32 *', i: 'int32_t', clos: 'lambda *'},
		lines: [
			'clos = va_arg(args, lambda *);',
			'for (i = 0; i < self->size; i++) {',
			'	index = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'	index->num = i;',
			'	ccall(clos, 2, index, self->data[i]);',
			'}',
			'return &(self->header);'
		]
	});
	array.addMessage('append', {
		vars: {tmp: 'object **'},
		lines: [
			'if (self->storage == self->size) {',
			'	self->storage *= 2;',
			'	tmp = realloc(self->data, self->storage * sizeof(object *));',
			'	if (!tmp) {',
			'		fputs("Failed to increase array size\\n", stderr);',
			'		exit(1);',
			'	}',
			'	self->data = tmp;',
			'}',
			'self->data[self->size++] = va_arg(args, object *);',
			'return &(self->header);'
		]
	});
	array.addMessage('length', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = self->size;',
			'return &(intret->header);'
		]
	});
	return array;
}

function makeString()
{
	var string = new cObject('string');
	string.addProperty('length', null, 'uint32_t');
	string.addProperty('bytes', null, 'uint32_t');
	string.addProperty('data', null, 'char *');
	string.addMessage('length', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = self->length;',
			'return &(intret->header);'
		]
	});
	string.addMessage('byte_length', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = self->bytes;',
			'return &(intret->header);'
		]
	});
	string.addMessage('EQ_', {
		vars: {argb: 'string *'},
		lines: [
			'argb = va_arg(args, string *);',
			'if (self->length == argb->length && self->bytes == argb->bytes && !memcmp(self->data, argb->data, self->bytes)) {',
			'	return ' + toplevel.moduleVar('true') + ';',
			'}',
			'return ' + toplevel.moduleVar('false') + ';',
		]
	});
	string.addMessage('NEQ_', {
		vars: {argb: 'string *'},
		lines: [
			'argb = va_arg(args, string *);',
			'if (self->length != argb->length || self->bytes != argb->bytes || memcmp(self->data, argb->data, self->bytes)) {',
			'	return ' + toplevel.moduleVar('true') + ';',
			'}',
			'return ' + toplevel.moduleVar('false') + ';',
		]
	});
	string.addMessage('print', {
		vars: {},
		lines: [
			'fwrite(self->data, 1, self->bytes, stdout);',
			'return &(self->header);'
		]
	});
	string.addMessage('string', {
		vars: {},
		lines: [ 'return &(self->header);' ]
	});
	string.addMessage('CAT_', {
		vars: {argbo: 'object *', argb: 'string *', out: 'string *'},
		lines: [
			'argbo = va_arg(args, object *);',
			'argb = (string *)mcall(' + getMethodId('string') + ', 1, argbo);',
			'out = (string *)make_object(&string_meta, NULL, 0);',
			'out->bytes = self->bytes + argb->bytes;',
			'out->length = self->length + argb->length;',
			'out->data = malloc(out->bytes+1);',
			'memcpy(out->data, self->data, self->bytes);',
			'memcpy(out->data + self->bytes, argb->data, argb->bytes + 1);',
			'return &(out->header);'
		]
	});
	string.addMessage('byte', {
		vars: {index: 'obj_int32 *', intret: 'obj_int32 *'},
		lines: [
			'index = va_arg(args, obj_int32 *);',
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = index->num < self->bytes ? self->data[index->num] : 0;',
			'return &(intret->header);'
		]
	});
	return string;
}

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

function addBuiltinModules(toplevel)
{
	var os = new cObject('mod_obj_os');
	os.addInclude('<sys/stat.h>');
	os.addInclude('<fcntl.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 = malloc(size->num + 1);',
			'str->length = str->bytes = read(filedes->num, str->data, size->num);',
			'if (str->bytes < 0) { str->bytes = str->length = 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);'
		]
	});
	toplevel.names['os'] = os;
}

modulefile.prototype.populateSymbols = function (toplevel) {
	if (!this.ast) {
		this.ast = parseFile(this.path + '/' + this.file);
		this.ast.populateSymbols(toplevel);
	}
};

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

function processUsedToplevel(toplevel)
{	
	var alwaysused = ['true', 'false'];
	var ret = '';
	var modulenum = 0;
	var newused = Object.keys(toplevel.used);
	var allused = newused;
	var visited = {};
	for (var i in alwaysused) {
		forwarddec += 'object * ' + toplevel.moduleVar(alwaysused[i]) + ';\n';
		toplevel.names[alwaysused[i]].populateSymbols(toplevel);
		visited[alwaysused[i]] = true;
	}
	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 in alwaysused) {
		allused.push(alwaysused[i]);
	}
		
	for (var i = allused.length-1; i >= 0; i--) {
		var symbol = allused[i];
		debugprint('//---module', symbol, '--- compile');
		ret += '\t' + toplevel.moduleVar(symbol) + ' = ' + toplevel.names[symbol].toC() + ';\n';
	}
	return ret;
}

function makeCProg(obj)
{
	var builtins = builtinTypes();
	forwarddec = toplevelcode = '';
	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';
	return '#include "runtime/proghead.inc"\n' +
		'#define METHOD_ID_MAIN ' + getMethodId('main') + '\n' +
		forwarddec + toplevelcode + rest + '#include "runtime/progfoot.inc"\n';
}

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

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

var lambdanum = 0;

lambda.prototype.toC = function() {
	var args = this.args ? this.args.slice(0, this.args.length) : [];
	var exprs = this.expressions;
	var mynum = lambdanum++;
	debugprint('//lambda', mynum);
	if (Object.keys(this.symbols.closedover).length) {
		this.symbols.envtype = 'lambda_' + mynum + '_env';
	}
	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();
		
		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));
		}
	}
	exprs = compiled;
	if (exprs.length) {
		exprs[exprs.length-1] = 'return ' + exprs[exprs.length-1] + ';';
	}
	
	if (Object.keys(this.symbols.closedover).length) {
		forwarddec += 'typedef struct lambda_' + mynum + '_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 += '} lambda_' + mynum + '_env;\n'
		
		var myenvinit = '\tlambda_' + mynum + '_env * myenv = malloc(sizeof(lambda_' + mynum + '_env));\n';
		if (this.symbols.needsParentEnv) {
			myenvinit += '\tmyenv->parent = env;\n';
		}
		this.symbols.envtype = 'lambda_' + mynum + '_env';
	} else {
		var myenvinit = '';
	}
	
	toplevelcode +=  'object * lambda_' + mynum + ' (' + 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';
	this.name = 'lambda_' + mynum;
	
	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('//lambda_' + mynum, 'has envvar:', envvar, 'num vars closed over:', Object.keys(this.symbols.closedover).length);
			return 'make_lambda(' + envvar + ', (closure_func)' + this.name + ')';
		} else {	
			toplevelcode += 'lambda lambda_obj_' + mynum + ' = {{&lambda_meta, NULL}, NULL, lambda_' + mynum + '};\n';
			return '((object *)&lambda_obj_' + mynum + ')';
		}
	}
};
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);
}

assignment.prototype.toC = function() {
	debugprint('//assignment', this.symbol.name);
	var existing = this.symbols.find(this.symbol.name);
	var prefix = '';
	var val = this.expression.toC();
	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);
	if (this.expression.toCObject) {
		var val = this.expression.toCObject(cobj.name);
	} else {
		var val = this.expression.toC();
	}
	if (val === null) {
		return;
	}
	if (this.expression instanceof lambda) {
		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(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 = self;');
		}
	}
};