changeset 31:668f533e5284

Add initial version of C backend
author Mike Pavone <pavone@retrodev.com>
date Sat, 07 Jul 2012 16:48:36 -0700
parents 608eb70fe261
children 64f1d516fbfd
files cbackend.js compiler.js jsbackend.js mquery.js runtime/object.c runtime/object.h runtime/progfoot.inc runtime/proghead.inc src/editor.tp testparse.html testparse.js
diffstat 11 files changed, 519 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cbackend.js	Sat Jul 07 16:48:36 2012 -0700
@@ -0,0 +1,368 @@
+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_', '=': 'EQ_', '!=': 'NEQ_', '<': 'LT_', '>': 'GT_', '>=': 'GEQ_', '<=': 'LEQ_'};
+	var method = optoMeth[this.op];
+	var ret = '(params[0] = ' + this.left.toC() + ',\n';
+	ret += 'params[1] = ' + this.right.toC() + ',\n';
+	ret += 'mcall(' + getMethodId(method) + ', 2, params))\n';
+	return ret;
+};
+
+function escapeCName(name)
+{
+	name = name.replace("_", "UN_").replace(":", "CN_").replace("!", "EX_").replace('?', 'QS_').replace('@', 'AT_');
+	name = 'tp_' + name;
+	return name;
+}
+
+symbol.prototype.toC = function() {
+	var name = this.cleanName();
+	if (name == 'self') {
+		return name;
+	}
+	var info = this.symbols.find(name);
+	if (!info) {
+		throw new Error('symbol ' + name + ' not found');
+	}
+	var pre = '';
+	if (info.type == 'self') {
+		pre = this.symbols.selfVar() + '->';
+	} else if(info.type == 'parent') {
+		pre = this.symbols.selfVar();
+		for (var i = 0; i < funinfo.depth; ++i) {
+			pre += '->parent';
+		}
+	} else if (info.type == 'toplevel') {
+		pre = 'modules.';
+		modules[name] = false;
+	}
+	return pre + escapeCName(name);
+}
+
+intlit.prototype.toC = function() {
+	var str = this.val.toString();
+	toplevelcode += 'obj_int32 int32_' + str + ' = {{&obj_int32_meta, NULL}, ' + str + '};\n';
+	return '((object *)&int32_' + str + ')';
+}
+
+floatlit.prototype.toC = function() {
+	return 'make_float(' + this.val.toString() + ')';
+}
+
+strlit.prototype.toC = function() {
+	return 'make_str("' + this.val.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r') + '")';
+}
+
+listlit.prototype.toC = function() {
+	var ret = 'make_list(';
+	each(this.val, function(idx, el) {
+		ret += (idx ? ', ' : '') + el.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[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] = 'params[' + i + '] = ' + args[i].toC() + ',\n';
+	}
+	var callpart;
+	if (method) {
+		callpart = 'mcall(' + getMethodId(name);
+	} else {
+		callpart = 'ccall(' + escapeCName(name);
+	}
+	return '(' + args.join('') + callpart + ', ' + args.length + ', ' + 'params))';
+}
+
+function cObject(name) {
+	this.name = name;
+	this.slots = {};
+	this.properties = [];
+	this.values = [];
+}
+
+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, implementation, msgname]);
+}
+
+cObject.prototype.addProperty = function(propname, value, type) {
+	if (type != undefined) {
+		this.properties.push([propname, type]);
+	} else {
+		var escaped = escapeCName(propname);
+		this.addMessage(propname, 'return self->' + escaped + ';');
+		this.addMessage(propname + '!', 'self->' + escaped + ' = params[1]; return params[0];');
+		this.properties.push(escaped);
+		this.values.push(value);
+	}
+}
+
+cObject.prototype.toEarlyCDef = function() {
+	var objdef =  'typedef struct {\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 objdef;
+}
+
+cObject.prototype.toCDef = function() {
+	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 ** params) {\n\t' +
+				this.name + ' *self = (' + this.name + ' *)params[0];';
+			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, params);\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\treturn no_impl(method_id, num_params, params);\n}\n';
+			}
+			metadef += this.name + '_slot_' + i;
+		} else {
+			metadef += 'no_impl';
+		}
+	}
+	metadef += '}};\n';
+	return slotdefs + metadef;
+}
+
+cObject.prototype.toCInstance = function() {
+	return 'make_object(&' + this.name + '_meta, NULL, ' + this.values.length + (this.values.length ? ', ' : '') + this.values.join(', ') + ')';
+}
+
+var nextobject = 0;
+
+object.prototype.toC = function() {
+	var messages = this.messages;
+	var values = [];
+	var imports = []
+	var me = new cObject('object_' + nextobject++);
+	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);
+		}
+	}
+	forwarddec += me.toEarlyCDef();
+	toplevelcode += me.toCDef();
+	return me.toCInstance();
+}
+
+var toplevelcode;
+var forwarddec;
+
+function makeCProg(obj)
+{
+	var int32 = new cObject('obj_int32');
+	int32.addProperty('num', null, 'int32_t');
+	int32.addMessage('ADD_', 'params[0] = make_object(&obj_int32_meta, NULL, 0); ((obj_int32 *)params[0])->num = self->num + ((obj_int32 *)params[1])->num; return params[0];');
+	int32.addMessage('SUB_', 'params[0] = make_object(&obj_int32_meta, NULL, 0); ((obj_int32 *)params[0])->num = self->num - ((obj_int32 *)params[1])->num; return params[0];');
+	forwarddec = toplevelcode = '';
+	forwarddec += int32.toEarlyCDef();
+	toplevelcode += int32.toCDef();
+	obj.populateSymbols(toplevel);
+	var rest = 'object * mainModule() {\n\treturn ' + obj.toC() + ';\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);
+}
+
+var lambdanum = 0;
+
+lambda.prototype.toC = function() {
+	var args = this.args ? this.args.slice(0, this.args.length) : [];
+	var exprs = this.expressions;
+	if (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) {
+		args[i] = '\tobject * ' + args[i].toC() + ' = params[' + (offset + i) + '];\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] + ';';
+	}
+	var mynum = lambdanum++;
+	toplevelcode +=  'object * lambda_' + mynum + ' (void * env, uint32_t num_args, object ** params) {\n';
+	if (this.selftype) {
+		toplevelcode += '\t' + this.selftype + ' * self = (' + this.selftype + ' *)params[0];\n';
+	}
+	toplevelcode += args.join('') + exprs.join(';\n\t') + '\n}\n';
+		
+	toplevelcode += 'closure lambda_obj_' + mynum + ' = {{&lambda_meta, NULL}, NULL, lambda_' + mynum + '};\n';
+	return '((object *)&lambda_obj_' + mynum + ')';
+};
+lambda.prototype.toCObject = function(typename) {
+	this.selftype = typename;
+	return this.toC();
+};
+lambda.prototype.toCModule = function() {
+	return makeCProg(this);
+}
+
+assignment.prototype.toC = function() {
+	var existing = this.symbols.find(this.symbol.name);
+	var prefix = '';
+	if (!existing) {
+		prefix =  'object * ';
+	} else {
+		switch (existing.type)
+		{
+		case 'self':
+			prefix = 'self->';
+			break;
+		case 'parent':
+			prefix = 'self->header.';
+			for (var i = 0; i < existing.depth; ++i) {
+				prefix += 'parent->';
+			}
+			break;
+		}
+	}
+	var val = this.expression.toC();
+	if (val === null) {
+		return null;
+	}
+	return prefix + this.symbol.toC() + ' = ' + val;
+};
+assignment.prototype.toCObject = function(cobj) {
+	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) {
+		cobj.addMessage(this.symbol.name, 'return ccall(' + val + ', num_params, params);');
+	} else {
+		cobj.addProperty(this.symbol.name, val);
+	}
+};
--- a/compiler.js	Thu Apr 19 19:20:21 2012 -0700
+++ b/compiler.js	Sat Jul 07 16:48:36 2012 -0700
@@ -3,14 +3,32 @@
 	return str.split('\n').join('\n\t');
 }
 
+var toplevel = new topsymbols();
 function topsymbols()
 {
 	this.names = null;
+	var self = this;
+	get('/src/', function(data) {
+		console.log(data);
+		self.names = {};
+		var fakeEl = newEl("div", {
+			innerHTML: data.response
+		});
+		console.log(fakeEl);
+		each(qall('a', fakeEl), function(idx, a) {
+			var tpidx = a.textContent.indexOf('.tp');
+			if (tpidx > -1) {
+				self.names[a.textContent.substr(0, tpidx)] = true;
+			}
+		});
+	});
 }
 topsymbols.prototype.find = function(name) {
+	console.log(this.names);
 	if (!this.names) {
-		
+		throw new Error('data not ready');
 	}
+	console.log('toplevel', name);
 	if (name in this.names) {
 		return {
 			type: 'toplevel',
@@ -98,8 +116,13 @@
 		};
 	} else if(this.parent) {
 		var ret = this.parent.find(name);
-		if (ret && ret.type == 'local') {
-			ret.type = 'upvar';
+		if (ret) {
+			if (ret.type == 'local') {
+				ret.type = 'upvar';
+				ret.depth = 1;
+			} else if (ret.type == 'upvar') {
+				ret.depth++;
+			}
 		}
 		return ret;
 	}
--- a/jsbackend.js	Thu Apr 19 19:20:21 2012 -0700
+++ b/jsbackend.js	Sat Jul 07 16:48:36 2012 -0700
@@ -1,4 +1,5 @@
 var mainModule;
+var modules = {};
 
 function toobj(val)
 {
@@ -78,6 +79,9 @@
 		for (var i = 0; i < funinfo.depth; ++i) {
 			pre += '.parent';
 		}
+	} else if (info.type == 'toplevel') {
+		pre = 'modules.';
+		modules[name] = false;
 	}
 	return pre + escapeJSName(name);
 }
@@ -209,7 +213,7 @@
 }
 
 object.prototype.toJSModule = function() {
-	this.populateSymbols(null);
+	this.populateSymbols(toplevel);
 	return '(function () {\n\tvar module = ' + indent(this.toJS()) + ';\n\treturn module;\n})'
 }
 
@@ -236,7 +240,7 @@
 	return 'function (' + args.join(', ') + ') {\n\t' + (this.symbols.needsSelfVar ? 'var self = this;\n\t' : '') + exprs.join(';\n\t') + '\n}'
 };
 lambda.prototype.toJSModule = function() {
-	this.populateSymbols(null);
+	this.populateSymbols(toplevel);
 	return this.toJS();
 }
 
--- a/mquery.js	Thu Apr 19 19:20:21 2012 -0700
+++ b/mquery.js	Sat Jul 07 16:48:36 2012 -0700
@@ -119,3 +119,10 @@
 	return el;
 }
 
+function setText(parent, text)
+{
+	parent.innerHTML = '';
+	parent.appendChild(document.createTextNode(text));
+	return parent;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/object.c	Sat Jul 07 16:48:36 2012 -0700
@@ -0,0 +1,21 @@
+#include "object.h"
+#include <stdarg.h>
+#include <stdlib.h>
+
+object * make_object(obj_meta * meta, void * parent, int num_props, ...)
+{
+	va_list args;
+	object * newobj = malloc(meta->size);
+	newobj->meta = meta;
+	newobj->parent = parent;
+	
+	va_start(args, num_props);
+	object ** curprop = ((object **)(newobj + 1));
+	for (; num_props > 0; num_props--)
+	{
+		*curprop = va_arg(args, object *);
+	}
+	va_end(args);
+	return newobj;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/object.h	Sat Jul 07 16:48:36 2012 -0700
@@ -0,0 +1,36 @@
+#ifndef OBJECT_H_
+#define OBJECT_H_
+
+#include <stdint.h>
+
+typedef struct obj_meta obj_meta;
+
+typedef struct object
+{
+	obj_meta * meta;
+	struct object * parent;
+} object;
+
+typedef object * (*method)(uint32_t method_id, uint32_t num_args, object **);
+
+typedef object * (*closure_func)(void *, uint32_t, object **);
+
+typedef struct closure
+{
+	object header;
+	void * env;
+	closure_func func;
+} closure;
+
+struct obj_meta
+{
+	uint32_t size;
+	method   meth_lookup[16];
+};
+
+#define mcall(method_id, num_args, args) (args[0])->meta->meth_lookup[method_id & 0xF](method_id, num_args, args)
+#define ccall(clos, num_args, args) (((closure *)clos)->func(((closure *)clos)->env, num_args, args))
+
+object * make_object(obj_meta * meta, void * parent, int num_props, ...);
+
+#endif //OBJECT_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/progfoot.inc	Sat Jul 07 16:48:36 2012 -0700
@@ -0,0 +1,23 @@
+
+int main(int argc, char ** argv)
+{
+	object * params[64];
+	params[0] = mainModule();
+	object * ret = mcall(METHOD_ID_MAIN, 1, params);
+	printf("%p:%p\n", ret->meta, &obj_int32_meta);
+	if (ret->meta == &obj_int32_meta) {
+		obj_int32 * reti32 = (obj_int32 *) ret;
+		printf("%d\n", reti32->num);
+	} else if(ret->meta == &lambda_meta) {
+		puts("returned lambda????");
+	} else {
+		int i = 0;
+		for(; i < 16; ++i) {
+			if (ret->meta->meth_lookup[i] != &no_impl) {
+				printf("slot %d is set\n", i);
+			}
+		}
+	}
+	return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/proghead.inc	Sat Jul 07 16:48:36 2012 -0700
@@ -0,0 +1,18 @@
+#include "object.h"
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+object * no_impl(uint32_t method_id, uint32_t num_args, object ** params)
+{
+	printf("method %d is not implemented on object\n", method_id);
+	exit(0);
+	return NULL;
+}
+
+obj_meta lambda_meta = {
+	sizeof(closure), 
+	{no_impl, no_impl, no_impl, no_impl, no_impl, no_impl, no_impl, no_impl, 
+	 no_impl, no_impl, no_impl, no_impl, no_impl, no_impl, no_impl, no_impl}
+};
+
--- a/src/editor.tp	Thu Apr 19 19:20:21 2012 -0700
+++ b/src/editor.tp	Sat Jul 07 16:48:36 2012 -0700
@@ -24,27 +24,7 @@
 }
 
 //kernel definitions
-true <- #{
-  if:else <- :self trueblock :elseblock {
-    trueblock:
-  }
-}
-
-false <- #{
-  if:else <- :self trueblock :elseblock {
-    elseblock:
-  }
-}
-
-filter <- :arr pred {
-	output <- arr slice: 0 0
-	each: arr :idx el {
-		if: (pred: el) {
-			output push: el
-		} else: {}
-	}
-	output
-}
+import: kernel
 
 //editor code
 editFile <- :path {
--- a/testparse.html	Thu Apr 19 19:20:21 2012 -0700
+++ b/testparse.html	Sat Jul 07 16:48:36 2012 -0700
@@ -7,12 +7,14 @@
 	<script src="parser.js"></script>
 	<script src="compiler.js"></script>
 	<script src="jsbackend.js"></script>
+	<script src="cbackend.js"></script>
 	<script src="testparse.js"></script>
 </head>
 <body style="height: 100%">
 	<textarea style="width: 90%; height: 50%; display: block; margin: 0 auto;"></textarea>
 	<input id="parse" type="button" value="Parse!" style="width: 90%;display: block; margin: 0 auto;">
 	<input id="tojs" type="button" value="To Javascript!" style="width: 90%;display: block; margin: 0 auto;">
+	<input id="toc" type="button" value="To C!" style="width: 90%; display: block; margin: 0 auto;">
 	<input id="run" type="button" value="Run!" style="width: 90%;display: block; margin: 0 auto;">
 	<pre>
 	</pre>
--- a/testparse.js	Thu Apr 19 19:20:21 2012 -0700
+++ b/testparse.js	Sat Jul 07 16:48:36 2012 -0700
@@ -21,6 +21,17 @@
 			q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column;
 		}*/
 	}
+	q('#toc').onclick = function() {
+		var text = q('textarea').value;
+		//try {
+			var parsed = parser.parse(text);
+			var c = parsed.toCModule();
+			setText(q('pre'), c);
+			console.log(parsed);
+		/*} catch(e) {
+			q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column;
+		}*/
+	}
 	q('#run').onclick = function() {
 		var text = q('textarea').value;
 		//try {