view jsbackend.js @ 217:adad61ea2f3a

Switched to a less hacky implementation of hygiene and exposed more AST properties to macros
author Michael Pavone <pavone@retrodev.com>
date Sat, 21 Dec 2013 12:07:51 -0800
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;
};