view compiler.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 3a169ebb3224
children 8a9b96888b7d
line wrap: on
line source

var debugprint = function() {};

function indent(str)
{
	return str.split('\n').join('\n\t');
}

function modulefile(path, file)
{
	this.path = path;
	this.file = file;
}

var toplevel = new topsymbols([]);
function topsymbols(moduledirs)
{
	this.names = null;
	this.used = {};
	this.nextmodulenum = 0;
	var self = this;
	if (typeof window === "object") {
		get('/src/', function(data) {
			self.names = {};
			var fakeEl = newEl("div", {
				innerHTML: data.response
			});
			each(qall('a', fakeEl), function(idx, a) {
				var tpidx = a.textContent.indexOf('.tp');
				if (tpidx > -1) {
					self.names[a.textContent.substr(0, tpidx)] = true;
				}
			});
		});
	} else {
		this.names = {};
		for (var dirnum in moduledirs) {
			var results = os.system("ls", [moduledirs[dirnum]]).split('\n');
			for (var i in results) {
				var tpidx = results[i].indexOf('.tp')
				if (tpidx > 0 && tpidx == results[i].length - 3) {
					this.names[results[i].substr(0, tpidx)] = new modulefile(moduledirs[dirnum], results[i]);
				}
			}
		}
	}
}
topsymbols.prototype.find = function(name) {
	debugprint('//topsymbols.find', name, name in this.names);
	if (!this.names) {
		throw new Error('data not ready');
	}
	if (name in this.names) {
		this.used[name] = true;
		return {
			type: 'toplevel',
			def: this.names[name]
		};
	}
	return null;
}
topsymbols.prototype.getEnvType = function() {
	return 'void';
}
topsymbols.prototype.moduleVar = function(name) {
	if (!(name in this.names)) {
		throw new Error('symbol ' + name + ' not found at toplevel');
	}
	if (name == 'true' || name == 'false') {
		return 'module_' + name;
	}
	if (!this.names[name].modulevar) {
		this.names[name].modulevar = 'module_' + this.nextmodulenum++
	}
	return this.names[name].modulevar;
}

function osymbols(parent)
{
	this.parent = parent;
	this.names = {};
	this.needsenv = false;
	this.typename = null;
	this.needsparent = false;
}
osymbols.prototype.find = function(name, nestedcall) {
	debugprint('//osymbols.find', name + ', exists?:', name in this.names, ', nested?:', nestedcall);
	if (name in this.names) {
		if (this.names[name] instanceof funcall && this.names[name].name == 'foreign:') {
			return {
				type: 'foreign',
				def: this.names[name]
			};
		}
		var ret = {
			type: 'self',
			def: this.names[name],
			selftype: this.typename
		};
	} else if(this.parent) {
		var ret = this.parent.find(name, nestedcall);
		if (ret) {
			if(ret.type == 'self') {
				ret.type = 'parent';
				ret.depth = 1;
				this.needsparent = true;
			} else if(ret.type == 'parent') {
				this.needsparent = true;
				ret.depth++;
			} else if(ret.type == 'closedover' || ret.type == 'upvar') {
				this.needsenv = true;
			}
		}
	} else {
		return null;
	}
	debugprint('\t//symbol type:', ret ? ret.type : 'null');
	return ret;
};
osymbols.prototype.defineMsg = function(name, def) {
	this.names[name] = def;
}
osymbols.prototype.parentObject = function() {
	if (!this.parent) {
		return 'null';
	}
	return 'this';
}
osymbols.prototype.allSymbols = function(curlist, cursyms) {
	if (curlist === undefined) {
		curlist = [];
		cursyms = {};
	}
	var keys = Object.keys(this.names).sort();
	for (var i in keys) {
		if (!(keys[i] in cursyms)) {
			curlist.push(keys[i]);
			cursyms[keys[i]] = true;
		}
	}
	if (this.parent) {
		return this.parent.allSymbols(curlist, cursyms);
	}
	return curlist;
}
osymbols.prototype.getEnvType = function() {
	return this.parent.getEnvType();
}
osymbols.prototype.envVar = function() {
	return this.parent.envVar();
}

function lsymbols(parent)
{
	this.parent = parent;
	this.names = {};
	this.closedover = {};
	this.declared = {};
	this.needsSelfVar = false;
	this.passthruenv = false;
	this.envtype = 'void';
	this.needsParentEnv = false;
}
lsymbols.prototype.find = function(name, nestedcall) {
	debugprint('//lsymbols.find', name + ', exists?:', name in this.names, ', nested?:', nestedcall);
	if (name in this.names) {
		if (this.names[name] instanceof funcall && this.names[name].name == 'foreign:') {
			var ret = {
				type: 'foreign',
				def: this.names[name]
			};
		} else {
			if (nestedcall) {
				this.closedover[name] = true;
				this.passthruenv = false;
			} 
			if (name in this.closedover) {
				var ret = {
					type: 'closedover',
					def: this.names[name]
				};
			} else {
				var ret = {
					type: 'local',
					def: this.names[name],
					isdeclared: (name in this.declared)
				};
			}
		}
	} else if(this.parent) {
		var ret = this.parent.find(name, true);
		if (ret) {
			if (ret.type == 'closedover') {
				ret.type = 'upvar';
				ret.depth = 0;
			}
			if (ret.type == 'upvar') {
				if (Object.keys(this.closedover).length) {
					ret.depth++;
					ret.startdepth = 1;
					this.needsParentEnv = true;
				} else {
					this.passthruenv = true;
					ret.startdepth = 0;
					/*if (ret.depth == 0) {
						ret.depth = 1;
					}*/
				}
			}
		}
	} else {
		return null;
	}
	var type = ret ? ret.type : 'null';
	debugprint('\t//symbol type:', type , type == 'upvar' ? 'depth: ' + ret.depth : '');
	return ret;
};
lsymbols.prototype.defineVar = function(name, def) {
	this.names[name] = def;
};
lsymbols.prototype.declareVar = function(name) {
	this.declared[name] = true;
}
lsymbols.prototype.selfVar = function() {
	if (this.parent && this.parent instanceof lsymbols) {
		this.parent.needsSelf();
		return 'self';
	} else {
		return 'this';
	}
};
lsymbols.prototype.needsSelf = function() {
	if (this.parent && this.parent instanceof lsymbols) {
		this.parent.needsSelf();
	} else {
		this.needsSelfVar = true;
	}
};
lsymbols.prototype.allSymbols = osymbols.prototype.allSymbols;
lsymbols.prototype.parentEnvType = function() {
	if (!this.parent) {
		return 'void';
	}
	return this.parent.getEnvType();
};
lsymbols.prototype.getEnvType = function() {
	if (this.passthruenv) {
		return this.parent.getEnvType();
	} else {
		return this.envtype;
	}
}
lsymbols.prototype.envVar = function() {
	if (Object.keys(this.closedover).length) {
		return 'myenv';
	} else if(this.passthruenv) {
		return 'env';
	}
	return null;
}

var mainModule;

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

op.prototype.populateSymbols = function(symbols, isReceiver) {
	this.left.populateSymbols(symbols);
	this.right.populateSymbols(symbols);
};

symbol.prototype.populateSymbols = function(symbols) {
	this.symbols = symbols;
	var ret = symbols.find(this.cleanName());
	if (ret && (ret.type == 'self' || ret.type == 'parent')) {
		symbols.find('self');
	}
}

intlit.prototype.populateSymbols = function(symbols) {
}

floatlit.prototype.populateSymbols = function(symbols) {
}

strlit.prototype.populateSymbols = function(symbols) {
}

listlit.prototype.populateSymbols = function(symbols) {
	for (var i = 0; i < this.val.length; i++) {
		this.val[i].populateSymbols(symbols);
	}
}

arraylit.prototype.populateSymbols = function(symbols) {
	for (var i = 0; i < this.val.length; i++) {
		this.val[i].populateSymbols(symbols);
	}
}

funcall.prototype.populateSymbols = function(symbols) {
	if (this.name == 'foreign:') {
		if ((this.args[0] instanceof lambda) || (this.args[0] instanceof object) || (this.args[0] instanceof symbol)) {
			return;
		} else {
			throw new Error("Unexpected AST type for foreign:");
		}
	}
	this.symbols = symbols;
	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
	symbols.find(name)
	for (var i in this.args) {
		this.args[i].populateSymbols(symbols);
	}
	if (this.receiver) {
		this.receiver.populateSymbols(symbols);
	}
}

funcall.prototype.populateSymbolsObject = function(symbols) {
	this.populateSymbols(symbols.parent);
}

object.prototype.populateSymbols = function(symbols) {
	symbols = new osymbols(symbols);
	for (var i in this.messages) {
		this.messages[i].populateSymbolsObject(symbols);
	}
	this.symbols = symbols;
}

lambda.prototype.populateSymbols = function(symbols, isobject) {
	var args = this.args ? this.args.slice(0, this.args.length) : [];
	var exprs = this.expressions;
	var symbols = new lsymbols(symbols);
	for (var i in args) {
		symbols.defineVar(args[i].cleanName(), null);
		args[i].populateSymbols(symbols);
	}
	if (isobject && (!args.length || args[0].cleanName() != 'self')) {
		symbols.defineVar('self', null);
	}
	for (var i in exprs) {
		exprs[i].populateSymbols(symbols);
	}
	this.symbols = symbols;
};

assignment.prototype.populateSymbols = function(symbols) {
	debugprint('//assignment', this.symbol.name, 'populateSymbols');
	var existing = symbols.find(this.symbol.name);
	if (!existing) {
		symbols.defineVar(this.symbol.name, this.expression);
	}
	this.symbol.populateSymbols(symbols);
	this.expression.populateSymbols(symbols);
	this.symbols = symbols;
};
assignment.prototype.populateSymbolsObject = function(symbols) {
	debugprint('//messagedef', this.symbol.name, 'populateSymbols');
	if (this.expression instanceof lambda || (this.expression instanceof funcall & this.expression.name == 'foreign:')) {
		symbols.defineMsg(this.symbol.name, this.expression);
	} else {
		symbols.defineMsg(this.symbol.name, new getter(null));
		symbols.defineMsg(this.symbol.name + '!', new setter(null));
	}
	this.symbol.populateSymbols(symbols);
	this.expression.populateSymbols(symbols, true);
	this.symbols = symbols;
};

function setter(fun)
{
	this.fun = fun;
}
setter.prototype.args = [new symbol('self'), new symbol('newval')];
function getter(fun)
{
	this.fun = fun;
}
getter.prototype.args = [new symbol('self')];