view jsbackend.js @ 331:61f5b794d939

Breaking change: method call syntax now always uses the syntactic receiver as the actual receiver. This makes its behavior different from function call syntax, but solves some problems with methods being shadowed by local variables and the like.
author Michael Pavone <pavone@retrodev.com>
date Sat, 28 Mar 2015 14:21:04 -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;
};