view compiler.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 eef8a5cea812
children
line wrap: on
line source

var debugprint = function() {};

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

var currentModule = null;
function modulefile(path, file)
{
	this.path = path;
	this.file = file;
	this.dependencies = {};
}

modulefile.prototype.populateSymbols = function (toplevel) {
	if (!this.ast) {
		currentModule = this;
		this.ast = parseFile(this.path + '/' + this.file).macroexpand(toplevel.topenv);
		this.ast.populateSymbols(toplevel);
		currentModule = null;
	}
};

modulefile.prototype.popuplateSymbolsAsync = function(toplevel, whenDone) {
	if (!this.ast) {
		var self = this;
		get(this.path + '/' + this.file, function(data) {
      //TODO: macro expand in the async case
			currentModule = this;
			self.ast = parser.parse(data.responseText);
			self.ast.populateSymbols(toplevel);
			currentModule = null;
			whenDone();
		});
	} else {
		whenDone();
	}
};

function getfileshtml(path, data, names)
{
	var fakeEl = newEl("div", {
		innerHTML: data.response
	});
	each(qall('a', fakeEl), function(idx, a) {
		var tpidx = a.textContent.indexOf('.tp');
		var modname = a.textContent.substr(0, tpidx);
		if (tpidx > -1) {
			names[modname] = new modulefile(path, modname + '.tp');
		}
	});
}

var toplevel = null;//new topsymbols([]);
function topsymbols(moduledirs, top)
{
	if (!top) {
		top = new topenv(moduledirs);
	}
	this.topenv = top;
	this.moduledirs = moduledirs;
	this.names = null;
	this.used = {};
	this.nextmodulenum = 0;
	this.onready = null;
	var self = this;
	if (typeof window === "object") {
		get('/modules/', function(data) {
			var names = {}
			getfileshtml('/modules', data, names);
			get('/src/', function(data) {
				getfileshtml('/src', data, names);
				self.names = names;
				if (self.onready) {
					self.onready();
				}
			});
		});
	} 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) {
					var moduleName = results[i].substr(0, tpidx);
					if (!(moduleName in this.names)) {
						this.names[moduleName] = 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) {
		if (currentModule) {
			currentModule.dependencies[name] = true;
		}
		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' || name == 'list') {
		return 'module_' + name;
	}
	if (!this.names[name].modulevar) {
		this.names[name].modulevar = 'module_' + this.nextmodulenum++
	}
	return this.names[name].modulevar;
};
topsymbols.prototype.onReady = function(fun) {
	if (this.names) {
		fun();
		return;
	}
	if (!this.onready) {
		this.onready = fun;
	} else {
		var oldready = this.onready;
		this.onready = function() {
			oldready();
			fun();
		};
	}
};

function osymbols(parent)
{
	this.parent = parent;
	this.names = {};
	this.llnames = {};
	this.needsenv = false;
	this.typename = null;
	this.needsparent = false;
}
osymbols.prototype.find = function(name, nestedcall, allowll) {
	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',
			isll: false,
			def: this.names[name],
			selftype: this.typename
		};
	} else if(allowll && name in this.llnames) {
		return {
			type: 'self',
			isll: true,
			selftype: this.typename
		};
	} else if(this.parent) {
		var ret = this.parent.find(name, nestedcall, allowll);
		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.defineLLProperty = function(name) {
	this.llnames[name] = true;
}
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, allowll) {
	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 && !(name in this.closedover)) {
				debugprint('//symbol', name, 'is now closed over');
				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, allowll);
		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)
{
	return (typeof val == "boolean") ? (val ? module_true : module_false) : val;
}

op.prototype.populateSymbols = function(symbols, isReceiver) {
	this.left.populateSymbols(symbols);
	if (this.op == '&&' || this.op == '||') {
		//&& and || are syntactic sugar for if and ifnot with
		//the second argument transformed into a lambda to
		//achieve short-circuit evalutation
		this.right = new lambda([], [this.right]);
	}
	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) {
	var isll = false;
	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:");
		}
	} else if (this.name == 'import:from:' && symbols instanceof osymbols  && this.args.length == 2) {
		each(this.args[0].val, function(i, el) {
			if (!(el instanceof symbol)) {
				throw new Error('Names in import:from statement must be symbols');
			}
			symbols.defineMsg(el.name, new lambda([], []));
		});
	} else if (this.name == 'llProperty:withType:') {
		if (this.args[0] instanceof symbol) {
			if ((this.args[1] instanceof symbol) || (this.args[1] instanceof funcall)) {
				symbols.defineLLProperty(this.args[0] instanceof symbol ? this.args[0].name : this.args[0].val);
				return;
			} else {
				throw new Error("Second argument to llProperty:withType: must be a symbol or funcall");
			}
		} else {
			throw new Error("First argument to llProperty:withType: must be a symbol or string");
		}
	} else if (this.name == 'llMessage:withVars:andCode:' && symbols instanceof osymbols) {
		if (this.args[0] instanceof symbol || this.args[0] instanceof strlit) {
			if (this.args[1] instanceof lambda) {
				if (this.args[2] instanceof lambda) {

					symbols.defineMsg(this.args[0].name, this.args[2]);
					isll = true;
				} else {
					throw new Error("Third argument to llMessage:withVars:andCode: must be a lambda");
				}
			} else {
				throw new Error("Second argument to llMessage:withVars:andCode: must be a lambda");
			}
		} else {
			throw new Error("First argument to llMessage:withVars:andCode: must be a symbol");
		}
	}
	this.symbols = symbols;
	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
	var funinfo = symbols.find(name);
	if (funinfo && (funinfo.type == 'self' || funinfo.type == 'parent')) {
		symbols.find('self');
	}
	for (var i in this.args) {
		this.args[i].populateSymbols(symbols, undefined, isll);
	}
	if (this.receiver) {
		this.receiver.populateSymbols(symbols, undefined, isll);
	}
}

funcall.prototype.defineSymbolsObject = function(symbols) {
}

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

object.prototype.populateSymbols = function(symbols) {
	var symbols = new osymbols(symbols);
	for (var i in this.messages) {
		this.messages[i].defineSymbolsObject(symbols);
	}
	for (var i in this.messages) {
		this.messages[i].populateSymbolsObject(symbols);
	}
	this.symbols = symbols;
}
var lambdanum = 0;
lambda.prototype.populateSymbols = function(symbols, isobject, isll) {
	if (!isll) {
		this.name = 'lambda_' + lambdanum++;
	}
	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 || existing.type == 'toplevel') {
		symbols.defineVar(this.symbol.name, this.expression);
	}
	this.symbol.populateSymbols(symbols);
	this.expression.populateSymbols(symbols);
	this.symbols = symbols;
};
assignment.prototype.defineSymbolsObject = 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));
	}
};
assignment.prototype.populateSymbolsObject = function(symbols) {
	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')];