view jsbackend.js @ 251:2557ce4e671f

Fix a couple of compiler bugs. topenv was getting initialized in multiple places. This resulted in multiple copies of modules getting created which caused problems for macro expansion. Additionally, arguments were not being marked as declared during code generation so assigning to an argument that was not closed over generated invalid C code.
author Michael Pavone <pavone@retrodev.com>
date Fri, 11 Apr 2014 22:29:32 -0700
parents a83989115028
children
line wrap: on
line source

var mainModule;
var modules = {};

function toobj(val)
{
	return (typeof val == "boolean") ? (val ? module_true : module_false) : val;
}

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.toJS = function(isReceiver) {
	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;
};

function escapeJSName(name)
{
	name = name.replace("_", "UN_").replace(":", "CN_").replace("!", "EX_").replace('?', 'QS_').replace('@', 'AT_');
	var reserved = {'true': true, 'false': true, 'this': true, 'if': true, 'else': true, 'NaN': true};
	if (name in reserved) {
		name = 's' + name;
	}
	return name;
}

symbol.prototype.toJS = function() {
	var name = this.cleanName();
	if (name == 'self') {
		return this.symbols.selfVar();
	}
	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';
		for (var i = 0; i < funinfo.depth; ++i) {
			pre += '.parent';
		}
	} else if (info.type == 'toplevel') {
		return toplevel.moduleVar(name);
	}
	return pre + escapeJSName(name);
}

intlit.prototype.toJS = function() {
	return this.val.toString();
}

floatlit.prototype.toJS = function() {
	return this.val.toString();
}

strlit.prototype.toJS = function() {
	return '"' + this.val.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r') + '"';
}

listlit.prototype.toJS = function() {
	var ret = '[';
	each(this.val, function(idx, el) {
		ret += (idx ? ', ' : '') + el.toJS();
	});
	return ret + ']';
}

funcall.prototype.toJS = 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 funinfo = this.symbols.find(name);
	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 = (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);
			if (args.length == 0) {
				return callee;
			} else {
				return callee + '(' + args.join(', ') + ')';
			}
		}
	}
	var ret = '';
	switch(funinfo.type)
	{
	case '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];
			args.splice(0, 1);
			if (args.length == 0) {
				var rJS = receiver.toJS(true);
				var callee = rJS + '.' + escapeJSName(name);
				
				return '(' + callee + ' instanceof Function ? ' + callee + '() : ' + callee + ')';
			}
		}
		ret = receiver.toJS(true) + '.';
		break;
	case 'parent':
		var receiver = new symbol('self', this.symbols);
		ret = receiver.toJS(true);
		for (var i = 0; i < funinfo.depth; ++i) {
			ret += '.parent';
		}
		break;
	}
	for (var i in args) {
		args[i] = args[i].toJS();
	}
	return ret + escapeJSName(name) + '(' + args.join(', ') + ')';
}

object.prototype.toJS = function() {
	var messages = this.messages;
	var compiled = [];
	var imports = []
	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 {
			var js = messages[i].toJSObject();
			if (js) {
				compiled.push(indent(js));
			}
		}
	}
	var pre = '';
	var post = '';
	for (var i = imports.length-1; i >= 0; i--) {
		pre += 'doImport(';
		post += ', ' + imports[i].src.toJS();
		if (imports[i].symbols) {
			post += ', ' + imports[i].symbols.toJS();
		}
		post += ')';
	}
	return pre+'{\n\tparent: ' + this.symbols.parentObject() + ',\n\t' + compiled.join(',\n\t') + '\n}'+post;
}

object.prototype.toJSModule = function() {
	this.populateSymbols(toplevel);
	return '(function () {\n\tvar module = ' + indent(this.toJS()) + ';\n\treturn module;\n})();'
}

lambda.prototype.toJS = function() {
	var args = this.args ? this.args.slice(0, this.args.length) : [];
	if (args.length && args[0].cleanName() == 'self') {
		args.splice(0, 1);
	}
	var exprs = this.expressions;
	for (var i in args) {
		args[i] = args[i].toJS();
	}
	var compiled = []
	for (var i in exprs) {
		var js = exprs[i].toJS();
		if (js) {
			compiled.push(indent(js));
		}
	}
	exprs = compiled;
	if (exprs.length) {
		exprs[exprs.length-1] = 'return ' + exprs[exprs.length-1] + ';';
	}
	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() + '();';
}

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) {
		prefix =  'var ';
	} else {
		switch (existing.type)
		{
		case 'self':
			var self = new symbol('self', this.symbols);
			prefix = self.toJS() + '.';
			break;
		case 'parent':
			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;
};