changeset 126:a2d2d8e09291

Merge
author Mike Pavone <pavone@retrodev.com>
date Mon, 05 Aug 2013 23:37:17 -0700
parents 6f8d868e8da0 (current diff) 1157639353e7 (diff)
children 2b25d0ce2946
files modules/sets.tp
diffstat 24 files changed, 1744 insertions(+), 405 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TASKS	Mon Aug 05 23:37:17 2013 -0700
@@ -0,0 +1,16 @@
+Improve string library
+Add basic UTF-8 support
+Implement import: in C backend
+Implement route:via
+Implement a hash dict based on hash set implementation
+Rejigger how built-in type/modules are handled slightly so we can add array new: which will return an array pre-allocated to a certain size
+Implement immutable objects
+Implement lists
+Add support for actors
+Check for breakage in Javascript backend and fix it
+Port all library stuff from C backend to Javascript backend
+Fix block comments in grammar
+Add dict literals to grammar and compiler
+Re-write compiler in TP
+Finish type system design
+Implement type system
\ No newline at end of file
--- a/cbackend.js	Mon Aug 05 23:36:18 2013 -0700
+++ b/cbackend.js	Mon Aug 05 23:37:17 2013 -0700
@@ -7,43 +7,33 @@
 {
 	if (!(methodName in methodIds)) {
 		methodIds[methodName] = nextmethodId++;
-		
+
 	}
 	return methodIds[methodName];
 }
 
-function importSym(obj, src, key)
+function getOpMethodName(opname)
 {
-	if(!(key in src)) {
-		throw new Error(key +' not found in source object for import');
+	var optoMeth = {'+': 'ADD_', '-': 'SUB_', '*': 'MUL_', '/': 'DIV_', '%': 'MOD_', '=': 'EQ_', '!=': 'NEQ_', '<': 'LT_', '>': 'GT_', '>=': 'GEQ_', '<=': 'LEQ_', '.': 'CAT_', '&&':'if', '||':'ifnot'};
+	if (opname in optoMeth) {
+		return optoMeth[opname];
+	} else {
+		return opname;
 	}
-	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';
+	var method = getOpMethodName(this.op);
+	return 'mcall(' + getMethodId(method) + '/* operator ' + method + ' */, 2, (object *)' + this.left.toC() + ', ' + this.right.toC() + ')\n';
 };
+op.prototype.toCLLExpr = function(vars) {
+	var opmap = {'=': '==', 'xor': '^'};
+	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)
 {
@@ -60,7 +50,7 @@
 	var pre = '';
 	switch(info.type) {
 	case 'self':
-		
+
 		pre = (new symbol('self', symbols)).toC() + '->';
 		break;
 	case 'parent':
@@ -108,6 +98,40 @@
 	}
 	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 = {};
 
@@ -119,10 +143,22 @@
 	}
 	return '((object *)&int32_' + 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;
@@ -134,7 +170,13 @@
 		declaredStrings[this.val] = nextStringId++;
 	}
 	return '((object *)&str_' + declaredStrings[this.val] + ')';
-}
+};
+strlit.prototype.toCLLExpr = function(vars) {
+	return '"' + this.val.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\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;
@@ -162,6 +204,8 @@
 		} else {
 			throw new Error("Unexpected AST type for foreign:");
 		}
+	} else if(name == 'llProperty:withType' || name == 'llProperty:withVars:andCode') {
+		return null;
 	}
 	var args = this.args.slice(0, this.args.length);
 	if (this.receiver) {
@@ -170,7 +214,7 @@
 	var method = false;
 	var funinfo = this.symbols.find(name);
 	var start = 0;
-	if (!funinfo || funinfo.def instanceof setter) {
+	if (!funinfo || funinfo.def instanceof setter || funinfo.type == 'toplevel') {
 		method = true;
 	} else {
 		switch(funinfo.type)
@@ -202,12 +246,122 @@
 	}
 	var callpart;
 	if (method) {
-		callpart = 'mcall(' + getMethodId(name) + '/* ' + name + ' */';
+		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 {
 		callpart = 'ccall(' + (new symbol(name, this.symbols)).toC();
 	}
 	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;
+	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) {
+			return this.receiver.toCLLExpr(vars) + '->' + this.name;
+		} else if (this.args.length == 1 && name[name.length-1] == '!') {
+			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 '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;
@@ -275,7 +429,7 @@
 		this.addMessage('_init', {
 			vars: {},
 			lines: init
-		});		
+		});
 		this.initmsgadded = true;
 	}
 }
@@ -316,7 +470,7 @@
 			}
 			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\t' + this.slots[i][0][1] + '\n' +
 					'\t}\n' +
 					'\treturn no_impl(method_id, num_params, (object *)self, args);\n}\n';
 			} else {
@@ -327,7 +481,7 @@
 				}
 				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 {
@@ -355,11 +509,15 @@
 
 var nextobject = 0;
 
-object.prototype.toC = function() {
+
+object.prototype.toCObject = function() {
 	var messages = this.messages;
 	var values = [];
-	var imports = []
-	var me = new cObject('object_' + nextobject++);
+	var imports = [];
+	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() + ' * ');
@@ -381,16 +539,34 @@
 					importsyms.push(new strlit(el.name));
 				});
 				imports.push({symbols: new listlit(importsyms), src: 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 {
-				throw new Error('Only import and import:from calls allowed in object context');
+
+				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.toC();
-}
+	return me;
+};
+
+object.prototype.toC = function() {
+	return this.toCObject().toC();
+};
 
 var toplevelcode;
 var forwarddec;
@@ -444,10 +620,16 @@
 			'str = (string *)make_object(&string_meta, NULL, 0);',
 			'str->data = GC_MALLOC(12);',
 			'sprintf(str->data, "%d", self->num);',
-			'str->length = str->bytes = strlen(str->data);',
+			'str->len = str->bytes = strlen(str->data);',
 			'return &(str->header);'
 		]
 	});
+	int32.addMessage('isInteger?', {
+		vars: {},
+		lines: [
+			'return ' + toplevel.moduleVar('true') + ';'
+		]
+	});
 	int32.addMessage('hash', {
 		vars: {},
 		lines: [
@@ -459,169 +641,20 @@
 
 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 = GC_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;
+	var arrayfile = toplevel.names['array'];
+	var ast = parseFile(arrayfile.path + '/' + arrayfile.file);
+	ast.name = 'array';
+	ast.populateSymbols(toplevel);
+	return ast.toCObject();
 }
 
 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 = GC_MALLOC_ATOMIC(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);'
-		]
-	});
-	string.addMessage('int32', {
-		vars: {intret: 'obj_int32 *'},
-		lines: [
-			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
-			'intret->num = atoi(self->data);',
-			'return &(intret->header);'
-		]
-	});
-	string.addMessage('hash', {
-		vars: {intret: 'obj_int32 *', i: 'uint32_t'},
-		lines: [
-			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
-			'intret->num = 0;',
-			'if (self->bytes) {',
-			'	intret->num = self->data[0] << 7;',
-			'	for (i = 0; i < self->bytes; i++) {',
-			'		intret->num = (1000003 * intret->num) ^ self->data[i];',
-			'	}',
-			'	intret->num = intret->num ^ self->bytes;',
-			'}',
-			'return &(intret->header);'
-		]
-	});
-	return string;
+	var arrayfile = toplevel.names['string'];
+	var ast = parseFile(arrayfile.path + '/' + arrayfile.file);
+	ast.name = 'string';
+	ast.populateSymbols(toplevel);
+	return ast.toCObject();
 }
 
 function makelambda()
@@ -753,31 +786,21 @@
 	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 visited = {};
+	for (var i in alwaysused) {
+		toplevel.used[alwaysused[i]] = true;
+	}
 	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');
@@ -794,13 +817,10 @@
 			}
 		}
 	}
-	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');
+		debugprint('//---module', symbol, '(' + i +')--- compile');
 		ret += '\t' + toplevel.moduleVar(symbol) + ' = ' + toplevel.names[symbol].toC() + ';\n';
 	}
 	return ret;
@@ -808,8 +828,8 @@
 
 function makeCProg(obj)
 {
+	forwarddec = toplevelcode = '';
 	var builtins = builtinTypes();
-	forwarddec = toplevelcode = '';
 	for (var i in builtins) {
 		forwarddec += builtins[i].toEarlyCDef();
 		toplevelcode += builtins[i].toCDef();
@@ -833,15 +853,13 @@
 	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);
+	debugprint('//', this.name);
 	if (Object.keys(this.symbols.closedover).length) {
-		this.symbols.envtype = 'lambda_' + mynum + '_env';
+		this.symbols.envtype = this.name + '_env';
+		forwarddec += 'typedef struct ' + this.symbols.envtype + '  ' + this.symbols.envtype + ';\n'
 	}
 	if (this.selftype) {
 		this.symbols.defineVar('self', this.selftype);
@@ -854,7 +872,7 @@
 	}
 	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 = []
@@ -868,9 +886,9 @@
 	if (exprs.length) {
 		exprs[exprs.length-1] = 'return (object *)(' + exprs[exprs.length-1] + ');';
 	}
-	
+
 	if (Object.keys(this.symbols.closedover).length) {
-		forwarddec += 'typedef struct lambda_' + mynum + '_env {\n';
+		forwarddec += 'struct ' + this.name + '_env {\n';
 		if (this.symbols.needsParentEnv) {
 			forwarddec += '\tstruct ' + this.symbols.parentEnvType() + ' * parent;\n';
 		}
@@ -881,18 +899,19 @@
 				forwarddec += '\tobject * ' + escapeCName(varname) + ';\n';
 			}
 		}
-		forwarddec += '} lambda_' + mynum + '_env;\n'
-		
-		var myenvinit = '\tlambda_' + mynum + '_env * myenv = GC_MALLOC(sizeof(lambda_' + mynum + '_env));\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 = 'lambda_' + mynum + '_env';
+		this.symbols.envtype = this.name + '_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';
+	forwarddec += 'object *' + this.name + ' (' + this.symbols.parentEnvType() + ' * env, uint32_t num_args, ...);\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') {
@@ -900,11 +919,10 @@
 		} 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 {
@@ -914,11 +932,11 @@
 			} else {
 				var envvar = 'myenv';
 			}
-			debugprint('//lambda_' + mynum, 'has envvar:', envvar, 'num vars closed over:', Object.keys(this.symbols.closedover).length);
+			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 lambda_obj_' + mynum + ' = {{&lambda_meta, NULL}, NULL, lambda_' + mynum + '};\n';
-			return '((object *)&lambda_obj_' + mynum + ')';
+		} else {
+			toplevelcode += 'lambda ' + this.name + '_obj = {{&lambda_meta, NULL}, NULL, lambda_' + mynum + '};\n';
+			return '((object *)&' + this.name + '_obj)';
 		}
 	}
 };
@@ -932,6 +950,31 @@
 };
 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() {
@@ -971,7 +1014,7 @@
 				paramget += escaped + ' =  va_arg(args, object *); ';
 			}
 		}
-		cobj.addMessage(this.symbol.name, {
+		cobj.addMessage(getOpMethodName(this.symbol.name), {
 			vars: messagevars,
 			lines: [paramget + 'return ' + val + '(' + (cobj.hasenv ? 'self->env' : 'NULL') + ', ' + params.length + (params.length ? ', ' : '') + params.join(', ') + ');']
 		});
@@ -982,3 +1025,6 @@
 		}
 	}
 };
+assignment.prototype.toCLines = function(vars, needsreturn) {
+	return [(needsreturn ? 'return ' : '') + this.symbol.toCLLExpr(vars) + ' = ' + this.expression.toCLLExpr(vars) + ';']
+};
--- a/compiler.js	Mon Aug 05 23:36:18 2013 -0700
+++ b/compiler.js	Mon Aug 05 23:37:17 2013 -0700
@@ -11,23 +11,57 @@
 	this.file = file;
 }
 
+modulefile.prototype.populateSymbols = function (toplevel) {
+	if (!this.ast) {
+		this.ast = parseFile(this.path + '/' + this.file);
+		this.ast.populateSymbols(toplevel);
+	}
+};
+
+modulefile.prototype.popuplateSymbolsAsync = function(toplevel, whenDone) {
+	if (!this.ast) {
+		var self = this;
+		get(this.path + '/' + this.file, function(data) {
+			self.ast = parser.parse(data.responseText);
+			self.ast.populateSymbols(toplevel);
+			whenDone();
+		});
+	} else {
+		whenDone();
+	}
+};
+
+function getfileshtml(path, data, names)
+{
+	var fakeEl = newEl("div", {
+		innerHTML: data.response
+	});
+	each(qall('a', fakeEl), function(idx, a) {
+		var tpidx = a.textContent.indexOf('.tp');
+		var modname = a.textContent.substr(0, tpidx);
+		if (tpidx > -1) {
+			names[modname] = new modulefile(path, modname + '.tp');
+		}
+	});
+}
+
 var toplevel = new topsymbols([]);
 function topsymbols(moduledirs)
 {
 	this.names = null;
 	this.used = {};
 	this.nextmodulenum = 0;
+	this.onready = null;
 	var self = this;
 	if (typeof window === "object") {
-		get('/src/', function(data) {
-			self.names = {};
-			var fakeEl = newEl("div", {
-				innerHTML: data.response
-			});
-			each(qall('a', fakeEl), function(idx, a) {
-				var tpidx = a.textContent.indexOf('.tp');
-				if (tpidx > -1) {
-					self.names[a.textContent.substr(0, tpidx)] = true;
+		get('/modules/', function(data) {
+			var names = {}
+			getfileshtml('/modules', data, names);
+			get('/src/', function(data) {
+				getfileshtml('/src', data, names);
+				self.names = names;
+				if (self.onready) {
+					self.onready();
 				}
 			});
 		});
@@ -57,10 +91,10 @@
 		};
 	}
 	return null;
-}
+};
 topsymbols.prototype.getEnvType = function() {
 	return 'void';
-}
+};
 topsymbols.prototype.moduleVar = function(name) {
 	if (!(name in this.names)) {
 		throw new Error('symbol ' + name + ' not found at toplevel');
@@ -72,17 +106,33 @@
 		this.names[name].modulevar = 'module_' + this.nextmodulenum++
 	}
 	return this.names[name].modulevar;
-}
+};
+topsymbols.prototype.onReady = function(fun) {
+	if (this.names) {
+		fun();
+		return;
+	}
+	if (!this.onready) {
+		this.onready = fun;
+	} else {
+		var oldready = this.onready;
+		this.onready = function() {
+			oldready();
+			fun();
+		};
+	}
+};
 
 function osymbols(parent)
 {
 	this.parent = parent;
 	this.names = {};
+	this.llnames = {};
 	this.needsenv = false;
 	this.typename = null;
 	this.needsparent = false;
 }
-osymbols.prototype.find = function(name, nestedcall) {
+osymbols.prototype.find = function(name, nestedcall, allowll) {
 	debugprint('//osymbols.find', name + ', exists?:', name in this.names, ', nested?:', nestedcall);
 	if (name in this.names) {
 		if (this.names[name] instanceof funcall && this.names[name].name == 'foreign:') {
@@ -93,11 +143,18 @@
 		}
 		var ret = {
 			type: 'self',
+			isll: false,
 			def: this.names[name],
 			selftype: this.typename
 		};
+	} else if(allowll && name in this.llnames) {
+		return {
+			type: 'self',
+			isll: true,
+			selftype: this.typename
+		};
 	} else if(this.parent) {
-		var ret = this.parent.find(name, nestedcall);
+		var ret = this.parent.find(name, nestedcall, allowll);
 		if (ret) {
 			if(ret.type == 'self') {
 				ret.type = 'parent';
@@ -119,6 +176,9 @@
 osymbols.prototype.defineMsg = function(name, def) {
 	this.names[name] = def;
 }
+osymbols.prototype.defineLLProperty = function(name) {
+	this.llnames[name] = true;
+}
 osymbols.prototype.parentObject = function() {
 	if (!this.parent) {
 		return 'null';
@@ -160,7 +220,7 @@
 	this.envtype = 'void';
 	this.needsParentEnv = false;
 }
-lsymbols.prototype.find = function(name, nestedcall) {
+lsymbols.prototype.find = function(name, nestedcall, allowll) {
 	debugprint('//lsymbols.find', name + ', exists?:', name in this.names, ', nested?:', nestedcall);
 	if (name in this.names) {
 		if (this.names[name] instanceof funcall && this.names[name].name == 'foreign:') {
@@ -187,7 +247,7 @@
 			}
 		}
 	} else if(this.parent) {
-		var ret = this.parent.find(name, true);
+		var ret = this.parent.find(name, true, allowll);
 		if (ret) {
 			if (ret.type == 'closedover') {
 				ret.type = 'upvar';
@@ -262,22 +322,17 @@
 
 function toobj(val)
 {
-	switch(typeof val)
-	{
-	case 'boolean':
-		if(val) {
-			return mainModule.strue;
-		} else {
-			return mainModule.sfalse;
-		}
-	case 'number':
-		return mainModule.snumber(val);
-	}
-	throw new Error("can't make val into object");
+	return (typeof val == "boolean") ? (val ? module_true : module_false) : val;
 }
 
 op.prototype.populateSymbols = function(symbols, isReceiver) {
 	this.left.populateSymbols(symbols);
+	if (this.op == '&&' || this.op == '||') {
+		//&& and || are syntactic sugar for if and ifnot with
+		//the second argument transformed into a lambda to
+		//achieve short-circuit evalutation
+		this.right = new lambda([], [this.right]);
+	}
 	this.right.populateSymbols(symbols);
 };
 
@@ -311,12 +366,39 @@
 }
 
 funcall.prototype.populateSymbols = function(symbols) {
+	var isll = false;
 	if (this.name == 'foreign:') {
 		if ((this.args[0] instanceof lambda) || (this.args[0] instanceof object) || (this.args[0] instanceof symbol)) {
 			return;
 		} else {
 			throw new Error("Unexpected AST type for foreign:");
 		}
+	} else if (this.name == 'llProperty:withType:') {
+		if (this.args[0] instanceof symbol) {
+			if ((this.args[1] instanceof symbol) || (this.args[1] instanceof funcall)) {
+				symbols.defineLLProperty(this.args[0].name);
+				return;
+			} else {
+				throw new Error("Second argument to llProperty:withType: must be a symbol or funcall");
+			}
+		} else {
+			throw new Error("First argument to llProperty:withType: must be a symbol");
+		}
+	} else if (this.name == 'llMessage:withVars:andCode:') {
+		if (this.args[0] instanceof symbol) {
+			if (this.args[1] instanceof lambda) {
+				if (this.args[2] instanceof lambda) {
+					symbols.defineMsg(this.args[0].name, this.args[2]);
+					isll = true;
+				} else {
+					throw new Error("Third argument to llMessage:withVars:andCode: must be a lambda");
+				}
+			} else {
+				throw new Error("Second argument to llMessage:withVars:andCode: must be a lambda");
+			}
+		} else {
+			throw new Error("First argument to llMessage:withVars:andCode: must be a symbol");
+		}
 	}
 	this.symbols = symbols;
 	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
@@ -325,26 +407,29 @@
 		symbols.find('self');
 	}
 	for (var i in this.args) {
-		this.args[i].populateSymbols(symbols);
+		this.args[i].populateSymbols(symbols, undefined, isll);
 	}
 	if (this.receiver) {
-		this.receiver.populateSymbols(symbols);
+		this.receiver.populateSymbols(symbols, undefined, isll);
 	}
 }
 
 funcall.prototype.populateSymbolsObject = function(symbols) {
-	this.populateSymbols(symbols.parent);
+	this.populateSymbols(symbols);
 }
 
 object.prototype.populateSymbols = function(symbols) {
-	symbols = new osymbols(symbols);
+	var symbols = new osymbols(symbols);
 	for (var i in this.messages) {
 		this.messages[i].populateSymbolsObject(symbols);
 	}
 	this.symbols = symbols;
 }
-
-lambda.prototype.populateSymbols = function(symbols, isobject) {
+var lambdanum = 0;
+lambda.prototype.populateSymbols = function(symbols, isobject, isll) {
+	if (!isll) {
+		this.name = 'lambda_' + lambdanum++;
+	}
 	var args = this.args ? this.args.slice(0, this.args.length) : [];
 	var exprs = this.expressions;
 	var symbols = new lsymbols(symbols);
@@ -394,3 +479,4 @@
 	this.fun = fun;
 }
 getter.prototype.args = [new symbol('self')];
+
--- a/editor.css	Mon Aug 05 23:36:18 2013 -0700
+++ b/editor.css	Mon Aug 05 23:37:17 2013 -0700
@@ -44,7 +44,12 @@
 
 #src .selected
 {
-	background-color: yellow;
+	background-color: #FFFFB0;
+}
+
+#src .selectParent
+{
+	background-color: #E0E0E0;
 }
 
 #editor
@@ -78,7 +83,7 @@
 	box-sizing: border-box;
 	-moz-box-sizing: border-box;
 	-webkit-box-sizing: border-box;
-	height: 50%;
+	height: 33%;
 	margin: 0;
 	overflow: auto;
 }
@@ -95,12 +100,12 @@
 	border-bottom-width: 0px;
 }
 
-#editor #operators
+#editor #operators, #editor #literals
 {
 	display: none;
 }
 
-#editor .showops > #operators
+#editor .showops > #operators, #editor .showlit > #literals
 {
 	display: block;
 }
@@ -118,6 +123,11 @@
 	cursor: pointer;
 }
 
+#nav > li
+{
+	min-width: 20mm;
+}
+
 /* AST Nodes */
 #src span
 {
@@ -153,6 +163,11 @@
 	display: block;
 }
 
+.varname:after
+{
+	content: ' <-';
+}
+
 .lambda
 {
 	display: inline;
@@ -200,6 +215,36 @@
 	color: #FF1030;
 }
 
+.string:before, .string:after
+{
+	content: '"';
+}
+
+.listlit:before
+{
+	content: '[';
+}
+
+.listlit:after, .arraylit:after
+{
+	content: ']';
+}
+
+.arraylit:before
+{
+	content: '#[';
+}
+
+.funpart
+{
+	color: #108030;
+}
+
+.args
+{
+	color: #1030C0;
+}
+
 .op > div
 {
 	display: inline-block;
--- a/editor.js	Mon Aug 05 23:36:18 2013 -0700
+++ b/editor.js	Mon Aug 05 23:37:17 2013 -0700
@@ -1,122 +1,203 @@
 
 
-object.prototype.toHTML = function(node) {
+object.prototype.toHTML = function(node, up) {
+	this.up = up;
+	var astNode = this;
 	var el = newEl('div', {
-		className: 'object'
+		className: 'object',
+		onclick: function(event) {
+			main_module.objectClick(this, astNode, event);
+		}
 	});
+	this.domNode = el;
 	node.appendChild(el);
 	for (var i in this.messages) {
-		this.messages[i].toHTML(el);
+		this.messages[i].toHTML(el, this);
 	}
 };
 
-lambda.prototype.toHTML = function(node) {
+lambda.prototype.toHTML = function(node, up) {
+	this.up = up
 	var astNode = this;
 	var el = newEl('div', {
 		className: 'lambda',
 		onclick: function(event) {
-			mainModule.lambdaClick(this, astNode, event);
+			main_module.lambdaClick(this, astNode, event);
 		}
 	});
+	this.domNode = el;
 	var args = newEl('div', {
 		className: 'args'
 	});
 	for (var i in this.args) {
-		this.args[i].toHTML(args);
+		this.args[i].toHTML(args, this);
 	}
 	var body = newEl('div', {
 		className: 'lambdabody'
 	});
 	for (var i in this.expressions) {
-		this.expressions[i].toHTML(body);
+		this.expressions[i].toHTML(body, this);
 	}
 	el.appendChild(args);
 	el.appendChild(body);
 	node.appendChild(el);
 };
 
-assignment.prototype.toHTML = function(node) {
+assignment.prototype.toHTML = function(node, up) {
+	this.up = up;
+	var astNode = this;
 	var base = newEl('div', {
-		className: 'assignment'
+		className: 'assignment',
+		onclick: function(event) {
+			main_module.assignClick(this, astNode, event);
+		}
 	});
 	var varName = newEl('span', {
-		textContent: this.symbol.name + ' <-'
+		textContent: this.symbol.name,
+		className: 'varname'
 	});
+	this.domNode = base;
 	base.appendChild(varName);
 	node.appendChild(base);
-	this.expression.toHTML(base);
+	this.expression.toHTML(base, this);
 };
 
-op.prototype.toHTML = function(node) {
+op.prototype.toHTML = function(node, up) {
+	this.up = up;
+	var astNode = this;
 	var base = newEl('span', {
-		className: 'op'
+		className: 'op',
+		onclick: function(event) {
+			main_module.opClick(this, astNode, event);
+		}
 	});
-	this.left.toHTML(base);
+	this.domNode = base;
+	this.left.toHTML(base, this);
 	base.appendChild(newEl('span', {
 		textContent: this.op,
 		className: 'opname'
 	}));
-	this.right.toHTML(base);
+	if (this.op == '&&' || this.op == '||') {
+		this.right.expressions[0].toHTML(base, this);
+	} else {
+		this.right.toHTML(base, this);
+	}
 	node.appendChild(base);
 };
 
-intlit.prototype.toHTML = function(node) {
-	node.appendChild(newEl('span', {
+intlit.prototype.toHTML = function(node, up) {
+	this.up = up;
+	var astNode = this;
+	this.domNode = newEl('span', {
 		className: 'integer',
-		textContent: this.val
-	}));
+		textContent: this.val,
+		onclick: function(event) {
+			main_module.scalarClick(this, astNode, event);
+		}
+	});
+	node.appendChild(this.domNode);
 };
 
-floatlit.prototype.toHTML = function(node) {
-	node.appendChild(newEl('span', {
+floatlit.prototype.toHTML = function(node, up) {
+	this.up = up;
+	var astNode = this;
+	this.domNode = newEl('span', {
 		className: 'float',
-		textContent: this.val
-	}));
+		textContent: this.val,
+		onclick: function(event) {
+			main_module.scalarClick(this, astNode, event);
+		}
+	});
+	node.appendChild(this.domNode);
 };
 
-strlit.prototype.toHTML = function(node) {
-	node.appendChild(newEl('span', {
+strlit.prototype.toHTML = function(node, up) {
+	this.up = up;
+	var astNode = this;
+	this.domNode = newEl('span', {
 		className: 'string',
 		contentEditable: 'true',
-		textContent: this.val
-	}));
+		textContent: this.val,
+		onclick: function(event) {
+			main_module.scalarClick(this, astNode, event);
+		}
+	});
+	node.appendChild(this.domNode);
 };
 
-funcall.prototype.toHTML = function(node) {
+listlit.prototype.toHTML = function(node, up) {
+	this.up = up;
+	this.domNode = newEl('span', {
+		className: 'list',
+	});
+	for (var i = 0; i < this.val.length; i++) {
+		this.val[i].toHTML(this.domNode, this);
+	}
+	node.appendChild(this.domNode);
+};
+
+arraylit.prototype.toHTML = function(node, up) {
+	this.up = up;
+	this.domNode = newEl('span', {
+		className: 'array',
+	});
+	for (var i = 0; i < this.val.length; i++) {
+		this.val[i].toHTML(this.domNode, this);
+	}
+	node.appendChild(this.domNode);
+};
+
+funcall.prototype.toHTML = function(node, up) {
+	this.up = up;
 	var astNode = this;
 	var base = newEl('div', {
-		className: 'funcall'
+		className: 'funcall',
+		onclick: function(event) {
+			main_module.funClick(this, astNode, event);
+		}
 	});
+	this.domNode = base;
 	if (this.receiver) {
-		this.receiver.toHTML(base);
+		this.receiver.toHTML(base, this);
 	}
 	var parts = this.name.split(':');
 	for (var i in parts ) {
 		if(parts[i]) {
 			base.appendChild(newEl('span', {
 				textContent: parts[i] + (this.receiver && parts.length == 1 ? '' : ':'),
-				className: 'funpart',
-				onclick: function(event) {
-					mainModule.funClick(this, astNode, event);
-				}}));
+				className: 'funpart'
+				}));
 			if (this.args[i]) {
-				this.args[i].toHTML(base);
+				this.args[i].toHTML(base, this);
 			}
 		}
 	}
 	for (; i < this.args.length; i++) {
-		this.args[i].toHTML(base);
+		this.args[i].toHTML(base, this);
 	}
 	node.appendChild(base);
 };
 
-symbol.prototype.toHTML = function(node) {
+symbol.prototype.toHTML = function(node, up) {
+	this.up = up;
 	var astNode = this;
-	node.appendChild(newEl('span', {
+	this.domNode = newEl('span', {
 		className: 'symbol',
 		textContent: this.name,
 		onclick: function(event) {
-			mainModule.symbolClick(this, astNode, event);
+			main_module.symbolClick(this, astNode, event);
 		}
-	}));
+	})
+	node.appendChild(this.domNode);
 }
+
+function getEl(from, idx)
+{
+	return from[idx];
+}
+
+function setEl(to, idx, val)
+{
+	to[idx] = val;
+	return to;
+}
--- a/index.html	Mon Aug 05 23:36:18 2013 -0700
+++ b/index.html	Mon Aug 05 23:37:17 2013 -0700
@@ -21,7 +21,7 @@
 	<div id="editor">
 		<div class="controls">
 			<ul id="inscope"></ul>
-		</div><div id="src"></div><div class="controls">
+		</div><div id="src"></div><div class="controls showops">
 			<ul id="operators">
 				<li>&lt;</li>
 				<li>&gt;</li>
@@ -32,7 +32,22 @@
 				<li>&&</li>
 				<li>||</li>
 			</ul>
-			<ul><li id="ops_button">operators</li></ul>
+			<ul id="literals">
+				<li>object</li>
+				<li>list</li>
+				<li>array</li>
+				<li>string</li>
+				<li>number</li>
+				<li>new symbol</li>
+			</ul>
+			<ul>
+				<li id="ops_button">operators</li>
+				<li id="lit_button">literals</li>
+			</ul>
+			<ul id="nav">
+				<li id="out">outwards</li><li id="in">inwards</li>
+				<li id="prev">previous</li><li id="next">next</li>
+			</ul>
 		</div>
 	</div>
 </body>
--- a/jsbackend.js	Mon Aug 05 23:36:18 2013 -0700
+++ b/jsbackend.js	Mon Aug 05 23:37:17 2013 -0700
@@ -3,18 +3,7 @@
 
 function toobj(val)
 {
-	switch(typeof val)
-	{
-	case 'boolean':
-		if(val) {
-			return mainModule.strue;
-		} else {
-			return mainModule.sfalse;
-		}
-	case 'number':
-		return mainModule.snumber(val);
-	}
-	throw new Error("can't make val into object");
+	return (typeof val == "boolean") ? (val ? module_true : module_false) : val;
 }
 
 function importSym(obj, src, key)
@@ -45,9 +34,13 @@
 }
 
 op.prototype.toJS = function(isReceiver) {
-	var ret = '(' + this.left.toJS() +' '+ (this.op == '=' ? '==' : this.op) +' '+ this.right.toJS() + ')';
-	if (isReceiver) {
-		ret = 'toobj' + ret;
+	if (this.op == '&&') {
+		var ret = 'toobj(' + this.left.toJS() + ').sif(' + this.right.toJS() + ')';
+	} else if(this.op == '||') {
+		var ret = 'toobj(' + this.left.toJS() + ').ifnot(' + this.right.toJS() + ')';
+	} else {
+		var opmap = {'=': '==', '.': '+'};
+		var ret = '(' + this.left.toJS() +' '+ (this.op in opmap ? opmap[this.op] : this.op) +' '+ this.right.toJS() + ')';
 	}
 	return ret;
 };
@@ -80,8 +73,7 @@
 			pre += '.parent';
 		}
 	} else if (info.type == 'toplevel') {
-		pre = 'modules.';
-		modules[name] = false;
+		return toplevel.moduleVar(name);
 	}
 	return pre + escapeJSName(name);
 }
@@ -122,22 +114,21 @@
 		args.splice(0, 0, this.receiver);
 	}
 	var funinfo = this.symbols.find(name);
-	if (!funinfo || funinfo.def instanceof setter) {
+	if (!funinfo || funinfo.def instanceof setter || funinfo.type == 'toplevel') {
 		var receiver = args[0];
 		args.splice(0, 1);
 		for (var i in args) {
 			args[i] = args[i].toJS();
 		}
-		var rJS = receiver.toJS(true);
+		var rJS = (funinfo ? '' : 'toobj(') + receiver.toJS(true) + (funinfo ? '' : ')') ;
 		if ((name[name.length-1] == '!' && args.length == 1) || (funinfo && funinfo.def instanceof setter)) {
 			return  '(' + rJS + '.' + escapeJSName(name.substr(0, name.length-1))  + ' = ' + args[0] + ', ' + rJS + ')';
 		} else {
 			var callee = rJS + '.' + escapeJSName(name);
-			var callCode = callee + '(' + args.join(', ') + ')';
 			if (args.length == 0) {
-				return '(' + callee + ' instanceof Function ? ' + callCode + ' : ' + callee + ')';
+				return callee;
 			} else {
-				return callCode;
+				return callee + '(' + args.join(', ') + ')';
 			}
 		}
 	}
@@ -145,7 +136,7 @@
 	switch(funinfo.type)
 	{
 	case 'self':
-		if (args.length < funinfo.def.args.length || funinfo.def.args[0].name != 'self') {
+		if (args.length < funinfo.def.args.length || funinfo.def.args.length == 0 || funinfo.def.args[0].name != 'self') {
 			var receiver = new symbol('self', this.symbols);
 		} else {
 			var receiver = args[0];
@@ -160,7 +151,8 @@
 		ret = receiver.toJS(true) + '.';
 		break;
 	case 'parent':
-		ret = 'this';
+		var receiver = new symbol('self', this.symbols);
+		ret = receiver.toJS(true);
 		for (var i = 0; i < funinfo.depth; ++i) {
 			ret += '.parent';
 		}
@@ -214,7 +206,7 @@
 
 object.prototype.toJSModule = function() {
 	this.populateSymbols(toplevel);
-	return '(function () {\n\tvar module = ' + indent(this.toJS()) + ';\n\treturn module;\n})'
+	return '(function () {\n\tvar module = ' + indent(this.toJS()) + ';\n\treturn module;\n})();'
 }
 
 lambda.prototype.toJS = function() {
@@ -239,40 +231,162 @@
 	}
 	return 'function (' + args.join(', ') + ') {\n\t' + (this.symbols.needsSelfVar ? 'var self = this;\n\t' : '') + exprs.join(';\n\t') + '\n}'
 };
+lambda.prototype.nonSelfArgs = function() {
+	var args = this.args ? this.args.slice(0, this.args.length) : [];
+	if (args.length && args[0].cleanName() == 'self') {
+		args.splice(0, 1);
+	}
+	return args;
+};
 lambda.prototype.toJSModule = function() {
 	this.populateSymbols(toplevel);
-	return this.toJS();
+	return this.toJS() + '();';
+}
+
+modulefile.prototype.toJSModule = function(){
+	return this.ast.toJSModule();
+};
+
+function processUsedToplevelJS(toplevel)
+{	
+	var alwaysused = ['true', 'false'];
+	var ret = '';
+	var modulenum = 0;
+	var visited = {};
+	for (var i = 0; i < alwaysused.length; i++) {
+		toplevel.used[alwaysused[i]] = true;
+	}
+	var newused = Object.keys(toplevel.used);
+	var allused = newused;
+	while (newused.length) {
+		for (var i = 0; i < newused.length; i++) {
+			console.log(i, newused[i]);
+			toplevel.names[newused[i]].populateSymbols(toplevel);
+			visited[newused[i]] = true;
+		}
+		newused = [];
+		for (var symbol in toplevel.used) {
+			if (!(symbol in visited)) {
+				newused.push(symbol);
+				allused.push(symbol);
+			}
+		}
+	}
+		
+	for (var i = allused.length-1; i >= 0; i--) {
+		var symbol = allused[i];
+		ret += 'var ' + toplevel.moduleVar(symbol) + ' = ' + toplevel.names[symbol].toJSModule() + '\n';
+	}
+	return ret;
+}
+
+function asyncProcessTopLevelJS(toplevel, whenDone)
+{
+	var alwaysused = ['true', 'false'];
+	var ret = '';
+	var modulenum = 0;
+	var visited = {};
+	for (var i = 0; i < alwaysused.length; i++) {
+		toplevel.used[alwaysused[i]] = true;
+	}
+	var newused = Object.keys(toplevel.used);
+	var allused = newused;
+	var i = -1;
+	var handler = function() {
+		i++;
+		while(newused.length)
+		{
+			if (i < newused.length) {
+				visited[newused[i]] = true;
+				toplevel.names[newused[i]].popuplateSymbolsAsync(toplevel, handler);
+				return;
+			} else {
+				newused = [];
+				for (var symbol in toplevel.used) {
+					if (!(symbol in visited)) {
+						newused.push(symbol);
+						allused.push(symbol);
+					}
+				}
+				i = 0;
+			}
+		}
+		whenDone();
+	};
+	handler();
+}
+
+function makeJSProg(mainmodule)
+{
+	return processUsedToplevelJS(toplevel) + 'main_module = ' + mainmodule.toJSModule() + '\n' +
+	'Number.prototype.__defineGetter__("string", function() { return "" + this; });\n' +
+	'String.prototype.__defineGetter__("string", function() { return this; });\n' +
+	'String.prototype.__defineGetter__("print", function() { write(this); });\n' +
+	'Object.defineProperty(Array.prototype, "foreach", {value: function(action) { var ret = module_false; for (var i = 0; i < this.length; i++) { ret = action(i, this[i]) }; return ret; }});\n' +
+	'Function.prototype.whileCN_do = function(action) { var ret = module_false; while(toobj(this()) == module_true) { ret = action(); } return ret; };\n' +
+	'module_true.valueOf = function() { return true; }\n' +
+	'module_false.valueOf = function() { return false; }\n' +
+	'function toobj(val) {\n' + 
+	'	return (typeof val == "boolean") ? (val ? module_true : module_false) : val;\n' + 
+	'}\n' + 
+	'var m = main_module.main;\n' + 
+	'if (m instanceof Function) {\n' + 
+	'	m(arguments);\n' +
+	'}\n';
 }
 
 assignment.prototype.toJS = function() {
 	var existing = this.symbols.find(this.symbol.name);
 	var prefix = '';
-	if (!existing) {
+	/*if (!existing) {
 		prefix =  'var ';
 	} else {
 		switch (existing.type)
 		{
 		case 'self':
-			prefix = 'this.';
+			var self = new symbol('self', this.symbols);
+			prefix = self.toJS() + '.';
 			break;
 		case 'parent':
-			prefix = 'this.';
+			var self = new symbol('self', this.symbols);
+			prefix = self.toJS() + '.';
 			for (var i = 0; i < existing.depth; ++i) {
 				prefix += 'parent.';
 			}
 			break;
 		}
-	}
+	}*/
 	var val = this.expression.toJS();
 	if (val === null) {
 		return null;
 	}
+	if ((existing.type == 'local' || existing.type == 'closedover') && !existing.isdeclared) {
+		prefix = 'var ';
+		this.symbols.declareVar(this.symbol.name);
+	}
 	return prefix + this.symbol.toJS() + ' = ' + val;
 };
+function removeInitialFunction(str)
+{
+	var f = 'function';
+	str = str.trim();
+	if (str.substr(0, f.length) == f) {
+		return str.substr(f.length);
+	}
+	return str;
+}
 assignment.prototype.toJSObject = function() {
 	var val = this.expression.toJS();
 	if (val === null) {
 		return null;
 	}
+	if (this.expression instanceof lambda) {
+		var args =  this.expression.nonSelfArgs();
+		if (args.length == 0) {
+			return 'get ' + escapeJSName(this.symbol.name) + removeInitialFunction(val);
+		} else if(args.length == 1 && this.symbol.name[this.symbol.name.length-1] == '!') {
+			return 'set ' + escapeJSName(this.symbol.name.substr(0, this.symbol.name.length-1)) + removeInitialFunction(val);
+		}
+	}
 	return escapeJSName(this.symbol.name) + ': ' + val;
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/array.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -0,0 +1,102 @@
+#{
+	llProperty: size withType: uint32_t
+	llProperty: storage withType: uint32_t
+	llProperty: data withType: ((object ptr) ptr)
+	llMessage: get withVars: {
+		index <- obj_int32 ptr
+	} andCode: :index {
+		if: (index num) >= 0 && (index num) < size {
+			(self data) get: (index num)
+		} else: {
+			false
+		}
+	}
+	
+	llMessage: set withVars: {
+		index <- obj_int32 ptr
+		value <- object ptr
+	} andCode: :index value {
+		if: (index num) >= 0 && (index num) < size {
+			data set: (index num) value
+		}
+		self
+	}
+	
+	llMessage: foreach withVars: {
+		clos <- lambda ptr
+		i <- uint32_t
+		index <- obj_int32 ptr
+	} andCode: :clos {
+		i <- 0
+		while: { i < size } do: {
+			index <- make_object: (addr_of: obj_int32_meta) NULL 0
+			index num!: i
+			ccall: clos 2 index (data get: i)
+			i <- i + 1
+		}
+		self
+	}
+	
+	llMessage: append withVars: {
+		value <- object ptr
+		tmp <- (object ptr) ptr
+	} andCode: :value {
+		if: storage = size {
+			storage <- storage * 2
+			tmp <- GC_REALLOC: data storage * (sizeof: (object ptr))
+			if: (not: tmp) {
+				fputs: "Failed to increase array size\n" stderr
+				exit: 1
+			}
+			data <- tmp
+		}
+		data set: size value
+		size <- size + 1
+		self
+	}
+	
+	llMessage: length withVars: {
+		intret <- obj_int32 ptr
+	} andCode: {
+		intret <- make_object: (addr_of: obj_int32_meta) NULL 0
+		intret num!: size
+		intret
+	}
+	
+	fold:with <- :acc :fun {
+		foreach: self :idx el {
+			fun: acc el
+		}
+	}
+	
+	foldr:with <- :acc :fun {
+		idx <- length - 1
+		while: {idx >= 0} do: {
+			fun: acc (get: idx)
+		}
+	}
+	
+	map <- :fun {
+		new <- #[]
+		foreach: self :idx el {
+			new append: (fun: el)
+		}
+	}
+	
+	find:withDefault <- :pred :default{
+		idx <- 0
+		l <- length
+		ret <- default
+		while: {idx < l} do: {
+			v <- get: idx
+			if: (pred: v) {
+				ret <- #{
+					key <- idx
+					value <- v
+				}
+				idx <- l
+			}
+		}
+		ret
+	}
+}
--- a/modules/dict.tp	Mon Aug 05 23:36:18 2013 -0700
+++ b/modules/dict.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -42,6 +42,13 @@
 					(els get: idx) val
 				}
 			}
+			
+			foreach <- :l {
+				foreach: els :idx el {
+					l: (el key) (el val)
+				}
+			}
+
 		}
 	}
 
--- a/modules/false.tp	Mon Aug 05 23:36:18 2013 -0700
+++ b/modules/false.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -2,6 +2,9 @@
 	if <- :self trueblock {
 		self
 	}
+	ifnot <- :self falseblock {
+		falseblock:
+	}
 	if:else <- :self trueblock :elseblock {
 		elseblock:
 	}
--- a/modules/sets.tp	Mon Aug 05 23:36:18 2013 -0700
+++ b/modules/sets.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -3,6 +3,8 @@
 		empty <- #{
 			empty? <- { true }
 		}
+		size <- 0
+		hashdiffs <- #[0]
 		#{
 			buckets <- #[empty empty empty empty]
 			size <- 0
@@ -10,11 +12,12 @@
 				hv <- object hash
 				
 				notdone <- true
-				hashes <- #[hv (hv + 1) (hv - 1)]
+				
+				basehash <- hv
 				i <- 0
 				ret <- false
-				while: { if: notdone { i < 3 } } do: {
-					hv <- hashes get: i
+				while: { if: notdone { i < (hashdiffs length) } } do: {
+					hv <- basehash + (hashdiffs get: i)
 					trunc <- hv % (buckets length)
 					if: trunc < 0 { trunc <- 0 - trunc }
 					bucketval <- (buckets get: trunc)	
@@ -42,10 +45,10 @@
 					}
 				}
 				notdone <- true
-				hashes <- #[hv (hv + 1) (hv - 1)]
+				basehash <- hv
 				i <- 0
-				while: { if: notdone { i < 3 } } do: {
-					hv <- hashes get: i
+				while: { if: notdone { i < (hashdiffs length) } } do: {
+					hv <- basehash + (hashdiffs get: i)
 					trunc <- hv % (buckets length)
 					if: trunc < 0 { trunc <- 0 - trunc }
 					bucketval <- (buckets get: trunc)	
@@ -61,7 +64,13 @@
 					i <- i + 1
 				}
 				if: notdone {
-					newsize <- (buckets length) * 3
+					newsize <- (buckets length) * 3 + 1
+					lastdiff <- hashdiffs get: ((hashdiffs length) - 1)
+					if: lastdiff <= 0 {
+						hashdiffs append: ((0 - lastdiff) + 1)
+					} else: {
+						hashdiffs append: (0 - lastdiff)
+					}
 					newbucks <- #[]
 					newbucks resize: newsize
 					while: { (newbucks length) < newsize } do: {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/string.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -0,0 +1,96 @@
+#{
+	llProperty: len withType: uint32_t
+	llProperty: bytes withType: uint32_t
+	llProperty: data withType: (char ptr)
+	
+	llMessage: length withVars: {
+		intret <- (obj_int32 ptr)
+	} andCode: {
+		intret <- make_object: (addr_of: obj_int32_meta) NULL 0
+		intret num!: len
+		intret
+	}
+	
+	llMessage: byte_length withVars: {
+		intret <- (obj_int32 ptr)
+	} andCode: {
+		intret <- make_object: (addr_of: obj_int32_meta) NULL 0
+		intret num!: bytes
+		intret
+	}
+	
+	llMessage: EQ_ withVars: {
+		argb <- (string ptr)
+	} andCode: :argb {
+		if: len = (argb len) && bytes = (argb bytes) && (not: (memcmp: data (argb data) bytes)) {
+			true
+		}
+	}
+	
+	llMessage: NEQ_ withVars: {
+		argb <- (string ptr)
+	} andCode: :argb {
+		if: len != (argb len) || bytes != (argb bytes) || (memcmp: data (argb data) bytes) {
+			true
+		}
+	}
+	
+	llMessage: print withVars: {} andCode: {
+		fwrite: data 1 bytes stdout
+		self
+	}
+	
+	llMessage: string withVars: {} andCode: {
+		self
+	}
+	
+	llMessage: CAT_ withVars: {
+		argbo <- (object ptr)
+		argb <- (string ptr)
+		out <- (string ptr)
+	} andCode: :argbo {
+		argb <- mcall: string 1 argbo
+		out <- make_object: (addr_of: string_meta) NULL 0
+		out bytes!: bytes + (argb bytes)
+		out len!: len + (argb len)
+		out data!: (GC_MALLOC_ATOMIC: (out bytes) + 1)
+		memcpy: (out data) data bytes
+		memcpy: (out data) + bytes (argb data) (argb bytes) + 1
+		out
+	}
+	
+	llMessage: byte withVars: {
+		index <- (obj_int32 ptr)
+		intret <- (obj_int32 ptr)
+	} andCode: :index {
+		intret <- make_object: (addr_of: obj_int32_meta) NULL 0
+		intret num!: (if: (index num) < bytes { data get: (index num) } else: {0})
+		intret
+	}
+	
+	llMessage: int32 withVars: {
+		intret <- (obj_int32 ptr)
+	} andCode: {
+		intret  <- make_object: (addr_of: obj_int32_meta) NULL 0
+		intret num!: (atoi: data)
+		intret
+	}
+	
+	llMessage: hash withVars: {
+		intret <- (obj_int32 ptr)
+		i <- uint32_t
+	} andCode: {
+		intret <- make_object: (addr_of: obj_int32_meta) NULL 0
+		intret num!: 0
+		if: bytes {
+			intret num!: (data get: 0) * 128
+			i <- 0
+			while: { i < bytes } do: {
+				intret num!: (1000003 * (intret num)) xor (data get: i)
+				i <- i + 1
+			}
+			intret num!: (intret num) xor bytes
+		}
+		intret
+	}
+}
--- a/modules/true.tp	Mon Aug 05 23:36:18 2013 -0700
+++ b/modules/true.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -2,6 +2,9 @@
 	if <- :self trueblock {
 		trueblock:
 	}
+	ifnot <- :self falseblock {
+		self
+	}
 	if:else <- :self trueblock :elseblock {
 		trueblock:
 	}
--- a/mquery.js	Mon Aug 05 23:36:18 2013 -0700
+++ b/mquery.js	Mon Aug 05 23:37:17 2013 -0700
@@ -1,6 +1,6 @@
 function each(container, fun)
 {
-	if (container instanceof Array) {
+	if (container instanceof Array || container instanceof HTMLCollection || container instanceof NodeList) {
 		for (var i = 0; i < container.length; i++) {
 			fun(i, container[i]);
 		}
@@ -68,6 +68,11 @@
 	} 
 }
 
+function hasClass(el, classname)
+{
+	return el.className == classname || el.className.split(' ').indexOf(classname) > -1
+}
+
 function ajax(method, url, data, onSuccess, onFail, onOthers)
 {
 	var req;
@@ -126,3 +131,14 @@
 	return parent;
 }
 
+function bubble(el, event, handler)
+{
+	el.addEventListener(event, handler, false);
+}
+
+function capture(el, event, handler)
+{
+	el.addEventListener(event, handler, true);
+}
+
+
--- a/parser.js	Mon Aug 05 23:36:18 2013 -0700
+++ b/parser.js	Mon Aug 05 23:37:17 2013 -0700
@@ -50,6 +50,7 @@
 function object(messages)
 {
 	this.messages = messages;
+	this.name = null;
 }
 
 function lambda(args, expressions)
@@ -69,30 +70,33 @@
 	return node instanceof lambda;
 }
 
-var grammar = 
+var grammar =
 'start = ws module:(object / lambda) ws { return module; };' +
 'ws = ([ \\t\\n\\r] / "//" [^\\n]* "\\n")*;' +
 'hws = ([ \\t] / "/*" ([^*] / "*" ! "/")* "*/" )*;' +
 'expr = e:(funcall / methcall / opexpr) ws { return e; };' +
-'opexpr = left:addsub pieces:(hws ("<=" / ">=" / "<" / ">" / "=" / "!=") hws addsub)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
-'addsub = left:muldiv pieces:(hws ("+"/"-"/".") hws muldiv)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
+'opexpr = left:compareop pieces:(hws ("&&" / "||") hws compareop)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
+'compareop = left:addsub pieces:(hws ("<=" / ">=" / "<" / ">" / "=" / "!=") hws addsub)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
+'addsub = left:muldiv pieces:(hws ("+"/"-"/"xor"/".") hws muldiv)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
 'muldiv = left:primlitsym pieces:(hws ("*"/"/"/"%") hws primlitsym)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
 'primlitsym = hws val:(float / hex / binary / int / string / symbol / object / array / list / lambda / "(" ws expr:expr hws ")" { return expr; }) { return val; };' +
-'symbol = chars:[a-zA-Z_!?@]+ trailing:(":"? [a-zA-Z_!?@0-9])* ! ":" { for (var i in trailing) { trailing[i] = trailing[i].join(""); } return new symbol(chars.join("") + trailing.join("")); };' +
+'symbol = chars:[a-zA-Z_!?@]+ trailing:(":"? [a-zA-Z_!?@0-9])* ! ":" { for (var i = 0; i < trailing.length; i++) { trailing[i] = trailing[i].join(""); } return new symbol(chars.join("") + trailing.join("")); };' +
 'float = digits:[0-9]+ "." decimals:[0-9]+ { return new floatlit(parseFloat(digits.join("") + "." + decimals.join(""))); };' +
 'binary = "0b" digits:[01]+ { return new intlit(parseInt(digits.join(""), 2)); };' +
 'hex = "0x" digits:[0-9a-fA-F]+ { return new intlit(parseInt(digits.join(""), 16)); };' +
 'int = sign:"-"? digits:[0-9]+ { return new intlit(parseInt(sign + digits.join(""), 10)); };' +
 'string = "\\"" text:(strpart/escape)* "\\"" { return new strlit(text.join("")); };' +
-'strpart = text:[^\\"\\\\]+ { return text.join(""); };' + 
+'strpart = text:[^\\"\\\\]+ { return text.join(""); };' +
 'escape = "\\\\" char:[nt\\"r\\\\] { if (char == "n") { return "\\n"; } if (char == "r") { return "\\r"; } return char; };' +
-'object = "#{" ws messages:(assignment / funcall)* "}" { return new object(messages); };' +
+'object = "#{" ws messages:(assignment / funexpr)* "}" { return new object(messages); };' +
 'array = "#[" ws els:opexpr* "]" { return new arraylit(els); };' +
 'list = "[" ws els:opexpr* "]" { return new listlit(els); };' +
-'assignment = ws sym:symbol hws "<-" expr:expr ws { return new assignment(sym, expr); }' +
+'opsym = name:("&&" / "||" / "<=" / ">=" / "<" / ">" / "=" / "!=" / "+" / "-" / "." / "*" / "/" / "%") { return new symbol(name); };' +
+'assignment = ws sym:(symbol / opsym) hws "<-" expr:expr ws { return new assignment(sym, expr); }' +
 'lambda = args:((& ":") argname+  )? "{" ws exprs:(assignment / expr)* "}" { return new lambda(args[1], exprs); };' +
 'argname = init:":"? chars:[a-zA-Z_!?@]+ trailing:[a-zA-Z_!?@0-9]* hws { return new symbol(init + chars.join("") + trailing.join("")); };' +
-'funcall = hws parts: funcallpart+ { var fun = ""; var args = []; for (var i in parts) { fun += parts[i].name; args = args.concat(parts[i].args); } return new funcall(fun, args); };' +
+'funexpr = f: funcall ws { return f; };' +
+'funcall = hws parts: funcallpart+ { var fun = ""; var args = []; for (var i = 0; i < parts.length; i++) { fun += parts[i].name; args = args.concat(parts[i].args); } return new funcall(fun, args); };' +
 'funcallpart = fun:funpart args:opexpr* hws { return { name: fun, args: args}; };' +
 'funpart = chars:[a-zA-Z_!?@]+ middle:[a-zA-Z_!?@0-9]* ":" & [ \\t\\n\\r] { return chars.join("") + middle.join("") + ":"; };' +
 'methcall = receiver:opexpr hws info:methcallrest { info.receiver = receiver; return info; };' +
--- a/runtime/progfoot.inc	Mon Aug 05 23:36:18 2013 -0700
+++ b/runtime/progfoot.inc	Mon Aug 05 23:37:17 2013 -0700
@@ -43,7 +43,7 @@
 	for (i = 0; i < argc; ++i) {
 		arg = (string *)make_object(&string_meta, NULL, 0);
 		arg->data = argv[i];
-		arg->bytes = arg->length = strlen(argv[i]);
+		arg->bytes = arg->len = strlen(argv[i]);
 		arr->data[i] = &(arg->header);
 	}
 	object * ret = mcall(METHOD_ID_MAIN, 2, mainModule(), &(arr->header));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/samples/logical.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -0,0 +1,28 @@
+#{
+
+	foo <- {
+		print: "foo\n"
+		true
+	}
+	
+	bar <- {
+		print: "bar\n"
+		false
+	}
+	
+	baz <- {
+		print: "baz\n"
+		true
+	}
+	
+	qux <- {
+		print: "shouldn't be printed\n"
+		true
+	}
+	
+	
+	main <- {
+		foo && bar || (baz || qux)
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/samples/oper_impl.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -0,0 +1,20 @@
+#{
+	pair <- :a b {
+		#{
+			first <- a
+			second <- b
+			+ <- :other {
+				pair: first + (other first) second + (other second)
+			}
+		}
+	}
+
+	main <- {
+		foo <- pair: 5 7
+		bar <- pair: 9 23
+		baz <- foo + bar
+		print: ((baz first) string) . "\n"
+		print: ((baz second) string) . "\n"
+		0
+	}
+}
--- a/samples/testarray.tp	Mon Aug 05 23:36:18 2013 -0700
+++ b/samples/testarray.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -12,7 +12,7 @@
 		bar append: 30
 		bar append: 28
 		bar append: 42
-		(sum: foo) + (sum: bar)
+		print: "" . ((sum: foo) + (sum: bar)) . "\n"
 	}
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/samples/testdict.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -0,0 +1,11 @@
+#{
+	main <- {
+		foo <- dict linear:
+		foo set: "key1" "val1"
+		foo set: "key2" "val2"
+		foreach: foo :k :v {
+			print: k . ", " . v . "\n"
+		}
+
+	}
+}
--- a/scripttags.js	Mon Aug 05 23:36:18 2013 -0700
+++ b/scripttags.js	Mon Aug 05 23:37:17 2013 -0700
@@ -2,8 +2,12 @@
 function compileAndRun(src)
 {
 	var ast = parser.parse(src);
-	var js = ast.toJSModule();
-	mainModule = eval(js)();
+	asyncProcessTopLevelJS(toplevel, function() {
+		var js = makeJSProg(ast);
+		eval(js);
+	});
+	/*.toJSModule();
+	mainModule = eval(js);
 	if (mainModule.strue) {
 		each(mainModule.strue, function(key, val) {
 			if(val instanceof Function) {
@@ -13,18 +17,25 @@
 			}
 		});
 	}
-	mainModule.main();
+	mainModule.main();*/
 }
 
 onReady(function() {
-	var tags = qall('script[type="text/tabletprog"]');
-	for (var i = 0; i < tags.length; ++i) {
-		if (tags[i].src) {
-			get(tags[i].src, function(req) {
-				compileAndRun(req.responseText);
-			});
-		} else {
-			compileAndRun(tags[i].innerHTML);
+	toplevel.onReady( function() {
+		var tags = qall('script[type="text/tabletprog"]');
+
+		for (var i = 0; i < tags.length; ++i) {
+			if (tags[i].src) {
+				(function() {
+					var src = tags[i].src;
+					get(src, function(req) {
+						console.log('Compiling ' + src);
+						compileAndRun(req.responseText);
+					});
+				})();
+			} else {
+				compileAndRun(tags[i].innerHTML);
+			}
 		}
-	}
+	});
 });
--- a/src/editor.tp	Mon Aug 05 23:36:18 2013 -0700
+++ b/src/editor.tp	Mon Aug 05 23:37:17 2013 -0700
@@ -5,7 +5,10 @@
 each <- foreign: :iterable fun {}
 addClass <- foreign: :node className {}
 removeClass <- foreign: :node className {}
+hasClass <- foreign: :node className {}
 get <- foreign: :url onSuccess onFail onOther {}
+getEl <- foreign: :from idx {}
+setEl <- foreign: :to idx val {}
 newEl <- foreign: :tagname props {}
 
 //TP Parser
@@ -24,9 +27,62 @@
 }
 
 //kernel definitions
-import: kernel
+//import: kernel
+
+filter <- :arr pred {
+	output <- arr slice: 0 0
+	each: arr :idx el {
+		if: (pred: el) {
+			output push: el
+		} else: {}
+	}
+	output
+}
 
 //editor code
+selection <- #{
+	valid? <- false
+}
+
+setSelection:withInNode <- :astnode :innode {
+	fakeEvent <- #{
+		stopPropagation <- :Blah {
+		}
+	}
+	selection <- #{
+		valid? <- true
+		in <- {
+			(innode domNode) onclick: fakeEvent
+		}
+		out <- {
+			fakeEvent <- #{
+				stopPropagation <- :Blah {
+				}
+			}
+			((astnode up) domNode) onclick: fakeEvent
+		}
+	}
+}
+
+setSelection <- :astnode {
+	fakeEvent <- #{
+		stopPropagation <- :Blah {
+		}
+	}
+	selection <- #{
+		valid? <- true
+		in <- {
+		}
+		out <- {
+			fakeEvent <- #{
+				stopPropagation <- :Blah {
+				}
+			}
+			((astnode up) domNode) onclick: fakeEvent
+		}
+	}
+}
+
 editFile <- :path {
 	get: path :request {
 		addClass: (q: "body") "editorMode"
@@ -59,9 +115,9 @@
 
 selectParent <- :node {
 	each: (qall: ".selectParent") :idx el {
-		removeClass: el "selected"
+		removeClass: el "selectParent"
 	}
-	addClass: (node parentNode) "selectParent"
+	addClass: node "selectParent"
 }
 
 popInscope:onClick <- :syms :handler {
@@ -70,34 +126,146 @@
 	each: syms :idx key {
 		inscope appendChild: (newEl: "li" #{
 			textContent <- key
-			onclick <- { handler: key }
+			onclick <- :Event { handler: key }
 		})
 	}
 }
 
+scalarClick <- :domnode astnode event {
+	selectNode: domnode
+	setSelection: astnode
+	event stopPropagation: (foreign: undefined)
+	//TODO: set focus
+}
+
 symbolClick <- :domnode astnode event {
-	console log: astnode
 	selectNode: domnode
-	popInscope: ((astnode symbols) allSymbols) onClick: :key {
+	popInscope: ((astnode symbols) allSymbols: (foreign: undefined)) onClick: :key {
 		domnode textContent!: key
 		astnode name!: key
 	}
-	event stopPropagation
+	setSelection: astnode
+	event stopPropagation: (foreign: undefined)
+}
+
+assignClick <- :domnode astnode event {
+	selectParent: domnode
+	selectQuery: ".selectParent > .varname" in: domnode
+	popInscope: ((astnode symbols) allSymbols: (foreign: undefined)) onClick: :key {
+		(domnode firstChild) textContent!: key
+		(astnode symbol) name!: key
+	}
+	setSelection: astnode withInNode: (astnode expression)
+	event stopPropagation: (foreign: undefined)
+}
+
+opClick <- :domnode astnode event {
+	selectParent: domnode
+	selectQuery: ".selectParent > .opname" in: domnode
+	showOps
+	setSelection: astnode withInNode: (astnode left)
+	event stopPropagation: (foreign: undefined)
 }
 
 funClick <- :domnode astnode event {
 	selectParent: domnode
-	selectQuery: ".selectParent > .funpart" in: (domnode parentNode)
+	selectQuery: ".selectParent > .funpart" in: domnode
 	symtable <- astnode symbols
-	syms <- filter: (symtable allSymbols) :sym {
+	syms <- filter: (symtable allSymbols: (foreign: undefined)) :sym {
 		isLambda: ((symtable find: sym) def)
 	}
-	popInscope: syms onClick: {}
-	event stopPropagation
+	inner <- if: (astnode receiver) != (foreign: null) {
+		astnode receiver
+	} else: {
+		(astnode args) getEl: 0
+	}
+	setSelection: astnode withInNode: inner
+	popInscope: syms onClick: :key {
+		astnode name!: key
+		parts <- key split: ":"
+		nodes <- []
+		each: (domnode children) :idx val{
+			nodes push: val
+		}
+		partIdx <- 0
+		nodeIdx <- 0
+		lastWasNamePart <- true
+		while: { partIdx < (parts length) || nodeIdx < (nodes length) } do: {
+			if: nodeIdx < (nodes length) {
+				node <-getEl: nodes nodeIdx
+				nodeIdx <- nodeIdx + 1
+				if: (hasClass: node "funpart") {
+					if: partIdx < (parts length) {
+						postfix <- if: partIdx = 0 && nodeIdx = 2 && (parts length) = 1 && (nodes length) = 2 { "" } else: { ":" }
+						t <- (getEl: parts partIdx)
+						node textContent!: (getEl: parts partIdx) . postfix
+						partIdx <- partIdx + 1
+					} else: {
+						domnode removeChild: node
+					}
+					lastWasNamePart <- true
+				} else: {
+					if: (not: lastWasNamePart) && partIdx < (parts length) && nodeIdx > 0 {
+						domnode insertBefore: (newEl: "span" #{
+							className <- "funpart selected"
+							textContent <- (getEl: parts partIdx) . ":"
+						}) node
+						partIdx <- partIdx + 1
+					}
+					lastWasNamePart <- false
+				}
+			} else: {
+				console log: "part: " . (getEl: parts partIdx)
+				domnode appendChild: (newEl: "span" #{
+					className <- "funpart selected"
+					textContent <- (getEl: parts partIdx) . ":"
+				})
+				partIdx <- partIdx + 1
+			}
+		}
+	}
+	event stopPropagation: (foreign: undefined)
 }
 
 lambdaClick <- :domnode astnode event {
-	symbolClick: domnode astnode event
+	selectNode: domnode
+	popInscope: ((astnode symbols) allSymbols: (foreign: undefined)) onClick: :key {
+		console log: "foooobar!"
+	}
+	inner <- if: ((astnode args) length) > 0 {
+		(astnode args) getEl: 0
+	} else: {
+		(astnode expressions) getEl: 0
+	}
+	setSelection: astnode withInNode: inner
+	event stopPropagation: (foreign: undefined)
+}
+
+objectClick <- :domnode astnode event {
+	selectNode: domnode
+	popInscope: ((astnode symbols) allSymbols: (foreign: undefined)) onClick: :key {
+		console log: "fooobar!"
+	}
+	setSelection: astnode withInNode: ((astnode messages) getEl: 0)
+	event stopPropagation: (foreign: undefined)
+}
+
+visible <- "showops"
+
+showOps <- {
+	each: (qall: ".controls") :idx el {
+		removeClass: el visible
+		addClass: el "showops"
+	}
+	visible <- "showops"
+}
+
+showLit <- {
+	each: (qall: ".controls") :idx el {
+		removeClass: el visible
+		addClass: el "showlit"
+	}
+	visible <- "showlit"
 }
 
 main <- {
@@ -124,7 +292,7 @@
 			}
 		}
 	}
-	
+
 	//bind handlers for editor buttons
 	each: (qall: ".controls li") :idx el {
 		el onclick!: :event {
@@ -133,15 +301,30 @@
 		}
 	}
 	(q: "#ops_button") onclick!: :event {
-		each: (qall: ".controls") :idx el {
-			addClass: el "showops"
+		showOps
+	}
+	(q: "#lit_button") onclick!: :event {
+		showLit
+	}
+
+	(q: "#in") onclick!: :event {
+		console log: "inwards"
+		if: (selection valid?) {
+			selection in
 		}
 	}
-	
+
+	(q: "#out") onclick!: :event {
+		console log: "outwards"
+		if: (selection valid?) {
+			selection out
+		}
+	}
+
 	path <- (window location) pathname
 	if: (path indexOf: "/edit/") = 0 {
 		editFile: (path substr: 5)
-	} else: {}
+	}
 }
 
 }
--- a/tpc.js	Mon Aug 05 23:36:18 2013 -0700
+++ b/tpc.js	Mon Aug 05 23:37:17 2013 -0700
@@ -6,12 +6,14 @@
 var includes = ['.'];
 var basedir = '';
 var debugmode = false;
+var backend = 'C';
 for (var i = 0; i < arguments.length; i++) {
 	switch (argtype) {
 	case 'normal':
 		switch (arguments[i]) {
 		case '-basedir':
 		case '-i':
+		case '-backend':
 			argtype = arguments[i];
 			break;
 		case '-compilerdebug':
@@ -38,6 +40,10 @@
 		includes.push(arguments[i]);
 		argtype = 'normal';
 		break;
+	case '-backend':
+		backend = arguments[i];
+		argtype = 'normal';
+		break;
 	}
 }
 if (argtype != 'normal') {
@@ -50,7 +56,7 @@
 	quit(1);
 }
 includes.push(basedir + 'modules');
-compileFile(file, basedir, includes, debugmode);
+compileFile(file, basedir, includes, debugmode, backend);
 
 
 function parseFile(filename)
@@ -78,19 +84,35 @@
 }
 
 
-function compileFile(filename, basedir, includes, debugmode)
+function compileFile(filename, basedir, includes, debugmode, backend)
 {
 	
 	load(basedir + 'peg.js');
 	PEG = module.exports;
 	load(basedir + 'parser.js');
 	load(basedir + 'compiler.js');
-	load(basedir + 'cbackend.js');
+	if (backend == 'C') {
+		load(basedir + 'cbackend.js');
+	} else {
+		load(basedir + 'jsbackend.js');
+	}
+	
 	var parsed = parseFile(filename);
 	if (debugmode) {
 		debugprint = print;
 	}
 	toplevel = new topsymbols(includes);
-	var c = parsed.toCModule();
+	switch(backend)
+	{
+	case 'C':
+		var c = parsed.toCModule();
+		break;
+	case 'JS':
+		var c = makeJSProg(parsed);
+		break;
+	default:
+		print('Backend', backend, ' not recognized');
+		quit(1);
+	}
 	print(c);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/types.js	Mon Aug 05 23:37:17 2013 -0700
@@ -0,0 +1,421 @@
+function anytype() {};
+anytype.prototype.satisfiedBy = function(type) {
+	return true;
+};
+anytype.prototype.str = function(indent) {
+	if (indent === undefined) {
+		indent = '';
+	}
+	return indent + 'any\n';
+};
+anytype.prototype.id = 0;
+anytype.prototype.callable = true;
+anytype.prototype.replaceParams = function() { return this; };
+var any = new anytype();
+
+function typeparam(name, base)
+{
+	this.name = name;
+	this.base = base;
+	this.callable = base.callable;
+}
+typeparam.prototype.replaceParams = function(paramtypes) {
+	if (!(this.name in paramtypes)) {
+		return this;
+	}
+	if (!this.base.satisfiedBy(paramtypes[this.name])) {
+		throw new Error('param ' + this.name + ' has base type ' + this.base.str() + ' which is not satisfied by ' + paramtypes[this.name].str());
+	}
+	return paramtypes[this.name];
+};
+typeparam.prototype.satisfiedBy = function(type) {
+	return this.base.satisfiedBy(type);
+};
+typeparam.prototype.str = function(indent) {
+	return indent + 'param ' + this.name + '\n';
+};
+
+var nexttypeid = 1;
+
+function objecttype()
+{
+	this.messages = {};
+	this.id = nexttypeid++;
+	this.typeparams = [];
+	this.satisfies_cache = {};
+	this.satisfies_cache[this.id] = true;
+}
+
+objecttype.prototype.callable = false;
+
+objecttype.prototype.addMessage = function(name, type)
+{
+	this.messages[name] = type;
+};
+
+objecttype.prototype.satisfiedBy = function(type) {
+	if (type.id in this.satisfies_cache) {
+		return this.satisfies_cache[type.id];
+	}
+	//temporarily set cache entry to true to prevent infinite recursion
+	this.satisfies_cache[type.id] = true;
+	var ret = true;
+	if (type.messages === undefined) {
+		ret = false;
+	}
+	if (ret) {
+		for (var msgname in this.messages) {
+			if (!(msgname in type.messages) || !this.messages[msgname].satisfiedBy(type.messages[msgname])) {
+				ret = false;
+				break;
+			}
+		}
+	}
+	this.satisfies_cache[type.id] = ret;
+	return ret;
+};
+
+objecttype.prototype.str = function(indent) {
+	if (indent === undefined) {
+		indent = '';
+	}
+	if (indent.length > 6) {
+		return 'max depth reached\n';
+	}
+	var newindent = indent + '\t';
+	var childindent = newindent + '\t'
+	var ret = indent + 'objectype {\n';
+	for (var msgname in this.messages) {
+		ret += newindent + msgname + ':\n' + this.messages[msgname].str(childindent)
+	}
+	return ret + indent + '}\n';
+};
+
+objecttype.prototype.replaceParams = function(paramtypes, visited) {
+	if (visited === undefined) {
+		visited = {};
+	}
+	if (this.id in visited) {
+		return visited[this.id];
+	}
+	var me = visited[this.id] = this.clone();
+	for (var msgname in this.messages) {
+		me.messages[msgname] = me.messages[msgname].replaceParams(paramtypes, visited);
+	}
+	return me;
+};
+
+objecttype.prototype.clone = function() {
+	var clone = new objecttype();
+	for (var msgname in this.messages) {
+		clone.messages[msgname] = this.messages[msgname];
+	}
+	clone.typeparams = this.typeparams;
+	return clone;
+};
+
+function lambdatype()
+{
+	this.id = nexttypeid++;
+	this.messages = {};
+	this.params = [];
+	this.paramlu = {};
+	this.typeparams = [];
+	this.returntype = any;
+	this.satisfies_cache = {};
+	this.satisfies_cache[this.id] = true;
+}
+
+lambdatype.prototype.callable = true;
+
+lambdatype.prototype.addParam = function(name) {
+	this.paramlu[name] = this.params.length;
+	this.params.push(any);
+};
+
+lambdatype.prototype.paramType = function(name, type) {
+	this.params[this.paramlu[name]] = type;
+};
+
+lambdatype.prototype.addMessage = function(name, type)
+{
+	this.messages[name] = type;
+};
+
+lambdatype.prototype.satisfiedBy = function(type) {
+	if (type.id in this.satisfies_cache) {
+		return this.satisfies_cache[type.id];
+	}
+	//temporarily set cache entry to true to prevent infinite recursion
+	this.satisfies_cache[type.id] = true;
+	var ret = true;
+	if (!(type.callable) || this.params.length != type.params.length) {
+		ret = false;
+	}
+	if (ret) {
+		for (var i in this.params) {
+			if (i >= type.params.length || !type.params[i].satisfiedBy(this.params[i])) {
+				ret = false;
+				break;
+			}
+		}
+	}
+	if (ret) {
+		for (var msgname in this.messages) {
+			if (!(msgname in type.messages) || !this.messages[msgname].satisfiedBy(type.messages[msgname])) {
+				ret = false;
+				break;
+			}
+		}
+	}
+	ret = ret && this.returntype.satisfiedBy(type.returntype);
+	this.satisfies_cache[type.id] = ret;
+	return ret;
+}
+
+lambdatype.prototype.str = function(indent) {
+	if (indent === undefined) {
+		indent = '';
+	}
+	if (indent.length > 6) {
+		return 'max depth reached\n';
+	}
+	var newindent = indent + '\t';
+	var childindent = newindent + '\t'
+	var ret = indent + 'lambdatype {\n' + newindent + 'params: {\n';
+	for (var i = 0; i < this.params.length; i++) {
+		ret += childindent + i + ':\n' + this.params[i].str(childindent + '\t');
+	}
+	ret += newindent + '}\n' + newindent + 'returntype:\n' + this.returntype.str(childindent);
+	
+	for (var msgname in this.messages) {
+		ret += newindent + msgname + ':\n' + this.messages[msgname].str(childindent)
+	}
+	return ret + indent + '}\n';
+};
+
+lambdatype.prototype.replaceParams = function(paramtypes, visited) {
+	if (visited === undefined) {
+		visited = {};
+	}
+	if (this.id in visited) {
+		return visited[this.id];
+	}
+	var me = visited[this.id] = this.clone();
+	for (var msgname in me.messages) {
+		me.messages[msgname] = me.messages[msgname].replaceParams(paramtypes, visited);
+	}
+	for (var i in me.params) {
+		me.params[i] = me.params[i].replaceParams(paramtypes, visited);
+	}
+	me.returntype = me.returntype.replaceParams(paramtypes, visited);
+	return me;
+};
+
+lambdatype.prototype.clone = function() {
+	var clone = new lambdatype();
+	for (var msgname in this.messages) {
+		clone.messages[msgname] = this.messages[msgname];
+	}
+	clone.paramlu = this.paramlu;
+	clone.params = this.params.slice(0, this.params.length);
+	clone.returntype = this.returntype;
+	clone.typeparams = this.typeparams;
+	return clone;
+};
+
+function uniontype(a, b)
+{
+	this.a = a;
+	this.b = b;
+	this.id = nexttypeid++;
+	this.satisfies_cache = null;
+}
+
+uniontype.prototype = {
+	lazyinit: function() {
+		if (this.satisfies_cache == null) {
+			this.satisfies_cache = {};
+			this.satisfies_cache[this.id] = true;
+			this._messages = {};
+			if (this.a.messages !== undefined && this.b.messages !== undefined) {
+				for (var msgname in this.a.messages) {
+					if (msgname in this.b.messages) {
+						this._messages[msgname] = mkunion(this.a.messages[msgname], this.b.messages[msgname]);
+					}
+				}
+			}
+			this._callable = false;
+			if (this.a.callable && this.b.callable && this.a.params.length == this.b.params.length) {
+				this._callable = true;
+				this._params = [];
+				for (var i = 0; i < this.a.params.length; i++) {
+					this._params.push(mkunion(this.a.params[i], this.b.params[i]));
+				}
+				this._returntype = mkunion(this.a.returntype, this.b.returntype);
+			}
+		}
+	},
+	satisfiedBy: function(type)
+	{
+		this.lazyinit();
+		if (type.id in this.satisfies_cache) {
+			return this.satisfies_cache[type.id];
+		}
+		this.satisfies_cache[type.id] = true;
+		var ret = this.a.satisfiedBy(type) || this.b.satisfiedBy(type);
+		this.satisfies_cache[type.id] = ret;
+		return ret;
+	},
+	str: function(indent)
+	{
+		if (indent === undefined) {
+			indent = '';
+		}
+		if (indent.length > 6) {
+			return 'max depth reached\n';
+		}
+		return indent + 'Union {\n\t' + indent + this.a.str(indent+'\t') + '\t' + indent + this.b.str(indent+'\t') + indent + '}\n';
+	},
+	replaceParams: function(paramtypes, visited) {
+		if (visited === undefined) {
+			visited = {};
+		}
+		if (this.id in visited) {
+			return visited[this.id];
+		}
+		var me = visited[this.id] = this.clone();
+		me.a = me.a.replaceParams(paramtypes, visited);
+		me.b = me.b.replaceParams(paramtypes, visited);
+		return me;
+	},
+	clone: function() {
+		return new uniontype(this.a, this.b);
+	},
+	get messages() {
+		this.lazyinit();
+		return this._messages;
+	},
+	get params() {
+		this.lazyinit();
+		return this._params;
+	},
+	get returntype() {
+		this.lazyinit();
+		return this._returntype;
+	},
+	get callable() {
+		this.lazyinit();
+		return this._callable;
+	}
+};
+
+
+function mkunion(a, b)
+{
+	//if b is a subtype of a, then a | b is equivalent to a
+	if (a.satisfiedBy(b)) {
+		return a;
+	}
+	//if a is a subtype of b, then a | b is equivalent to b
+	if (b.satisfiedBy(a)) {
+		return b;
+	}
+	return new uniontype(a, b);
+}
+
+function withtparams(type, params)
+{
+	this.type = type;
+	this.params = params;
+	this.replaced = false;
+}
+
+withtparams.prototype = {
+	satisfiedBy: function(type) {
+		this.lazyinit();
+		return this.type.satisfiedBy(type);
+	},
+	str: function(indent) {
+		if (indent === undefined) {
+			indent = '';
+		}
+		if (indent.length > 6) {
+			return 'max depth reached\n';
+		}
+		return this.type.str(indent) + indent + '<' + this.params.map(function(p) { return p.str(indent); }).join(', ') + '>';
+	},
+	replaceParams: function(paramtypes) {
+		var replaced = false;
+		for (var i in this.params) {
+			var newp = this.params[i].replaceParams(paramtypes);
+			if (newp != this.params[i]) {
+				replaced = true;
+				this.params[i] = newp;
+			}
+		}
+		return this;
+	},
+	lazyinit: function() {
+		if (!this.replaced) {
+			var childptypes = {};
+			for (var i in this.type.typeparams) {
+				childptypes[this.type.typeparams[i]] = this.params[i]
+			}
+			this.type = this.type.replaceParams(childptypes, {});
+			this.replaced = true;
+		}
+	},
+	get messages() {
+		this.lazyinit();
+		return this.type.messages;
+	}
+};
+
+function typetest()
+{
+	var foo = new objecttype();
+	var msgtype = new lambdatype();
+	msgtype.addParam('fo');
+	msgtype.returntype = foo;
+	foo.addMessage('bar', msgtype);
+	var baz = new objecttype();
+	var msgtype2 = new lambdatype();
+	msgtype2.addParam('fo');
+	msgtype2.paramType('fo', foo);
+	msgtype2.returntype = baz;
+	baz.addMessage('bar', msgtype2);
+	baz.addMessage('qux', msgtype);
+	var shizzle = new objecttype();
+	shizzle.addMessage('bar', msgtype);
+	shizzle.addMessage('boo', msgtype);
+	return {foo: foo, baz: baz, shizzle: shizzle};
+}
+
+function paramtypetest()
+{
+	var empty = new objecttype();
+	var tlnode = new objecttype();
+	tlnode.typeparams = ['T'];
+	var t = new typeparam('T', any);
+	var q = new typeparam('Q', any);
+	var head = new lambdatype();
+	head.returntype = t;
+	tlnode.addMessage('head', head);
+	var tail = new lambdatype();
+	var econs = new lambdatype();
+	econs.addParam('val');
+	econs.typeparams = ['Q'];
+	econs.paramType('val', q);
+	econs.returntype = new withtparams(tlnode, [q]);
+	empty.addMessage('cons', econs);
+	tail.returntype = new uniontype(new withtparams(tlnode, [t]), empty);
+	tlnode.addMessage('tail', tail);
+	var cons = new lambdatype();
+	cons.addParam('val');
+	cons.paramType('val', t);
+	cons.returntype = new withtparams(tlnode, [t]);
+	tlnode.addMessage('cons', cons);
+	return {empty: empty, tlnode: tlnode};
+}
+