view jsbackend.js @ 69:ba032565c7a5

Fix handling of variable style access to self and parent object messages defined with lambdas. Improve test case for this bug to include parent object access as well as self object access.
author Mike Pavone <pavone@retrodev.com>
date Sat, 14 Jul 2012 19:24:04 -0700
parents 668f533e5284
children 926b65fe92b4
line wrap: on
line source

var mainModule;
var modules = {};

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");
}

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) {
	var ret = '(' + this.left.toJS() +' '+ (this.op == '=' ? '==' : this.op) +' '+ this.right.toJS() + ')';
	if (isReceiver) {
		ret = 'toobj' + ret;
	}
	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') {
		pre = 'modules.';
		modules[name] = false;
	}
	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) {
		var receiver = args[0];
		args.splice(0, 1);
		for (var i in args) {
			args[i] = args[i].toJS();
		}
		var rJS = receiver.toJS(true);
		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 + ')';
			} else {
				return callCode;
			}
		}
	}
	var ret = '';
	switch(funinfo.type)
	{
	case 'self':
		if (args.length < funinfo.def.args.length || 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':
		ret = 'this';
		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.toJSModule = function() {
	this.populateSymbols(toplevel);
	return this.toJS();
}

assignment.prototype.toJS = function() {
	var existing = this.symbols.find(this.symbol.name);
	var prefix = '';
	if (!existing) {
		prefix =  'var ';
	} else {
		switch (existing.type)
		{
		case 'self':
			prefix = 'this.';
			break;
		case 'parent':
			prefix = 'this.';
			for (var i = 0; i < existing.depth; ++i) {
				prefix += 'parent.';
			}
			break;
		}
	}
	var val = this.expression.toJS();
	if (val === null) {
		return null;
	}
	return prefix + this.symbol.toJS() + ' = ' + val;
};
assignment.prototype.toJSObject = function() {
	var val = this.expression.toJS();
	if (val === null) {
		return null;
	}
	return escapeJSName(this.symbol.name) + ': ' + val;
};