view compiler.js @ 251:2557ce4e671f

Fix a couple of compiler bugs. topenv was getting initialized in multiple places. This resulted in multiple copies of modules getting created which caused problems for macro expansion. Additionally, arguments were not being marked as declared during code generation so assigning to an argument that was not closed over generated invalid C code.
author Michael Pavone <pavone@retrodev.com>
date Fri, 11 Apr 2014 22:29:32 -0700
parents 60eff5f81d9a
children d6a4c9e7716e
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;
}

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

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
			self.ast = parser.parse(data.responseText);
			self.ast.populateSymbols(toplevel);
			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) {
					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' || 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 == 'llProperty:withType:') {
		if (this.args[0] instanceof symbol) {
			if ((this.args[1] instanceof symbol) || (this.args[1] instanceof funcall)) {
				symbols.defineLLProperty(this.args[0].name);
				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");
		}
	} else if (this.name == 'llMessage:withVars:andCode:') {
		if (this.args[0] instanceof symbol) {
			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')];