view parser.js @ 7:8af72f11714e

Initial version of FFI
author Mike Pavone <pavone@retrodev.com>
date Wed, 21 Mar 2012 20:12:12 -0700
parents 554602d4cbc6
children 04ae32e91598
line wrap: on
line source

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

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

function osymbols(parent)
{
	this.parent = parent;
	this.names = {};
	this.lastname = null;
}
osymbols.prototype.find = function(name) {
	if (name in this.names) {
		if (this.names[name] instanceof funcall && this.names[name].name == 'foreign:') {
			return {
				type: 'foreign',
				def: this.names[name]
			};
		}
		return {
			type: 'self',
			def: this.names[name],
		};
	} else if(this.parent) {
		var ret = this.parent.find(name);
		if (ret) {
			if(ret.type == 'self') {
				ret.type = 'parent';
				ret.depth = 1;
			} else if(ret.type == 'parent') {
				ret.depth++;
			}
		}
		return ret;
	}
	return null;
};
osymbols.prototype.defineMsg = function(name, def) {
	this.lastname = name;
	this.names[name] = def;
}
osymbols.prototype.parentObject = function() {
	if (!this.parent) {
		return 'null';
	}
	return 'this';
}

function lsymbols(parent)
{
	this.parent = parent;
	this.names = {};
	this.lastname = null;
	this.needsSelfVar = false;
}
lsymbols.prototype.find = function(name) {
	if (name in this.names) {
		if (this.names[name] instanceof funcall && this.names[name].name == 'foreign:') {
			return {
				type: 'foreign',
				def: this.names[name]
			};
		}
		return {
			type: 'local',
			def: this.names[name]
		};
	} else if(this.parent) {
		var ret = this.parent.find(name);
		if (ret && ret.type == 'local') {
			ret.type = 'upvar';
		}
		return ret;
	}
	return null;
};
lsymbols.prototype.defineVar = function(name, def) {
	this.lastname = name;
	this.names[name] = def;
};
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;
	}
};

function op(left, op, right)
{
	this.left = left;
	this.op = op;
	this.right = right;
}
op.prototype.toJS = function(symbols, isReceiver) {
	var ret = '(' + this.left.toJS(symbols) +' '+ (this.op == '=' ? '==' : this.op) +' '+ this.right.toJS(symbols) + ')';
	if (isReceiver) {
		ret = 'toobj' + ret;
	}
	return ret;
};

function symbol(name)
{
	this.name = name;
}
symbol.prototype.toJS = function(symbols) {
	var name = this.cleanName();
	if (name == 'self') {
		return symbols.selfVar();
	}
	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.cleanName = function() {
	return this.name[0] == ':' ? this.name.substr(1) : this.name;
}

function intlit(val)
{
	this.val = val;
}
intlit.prototype.toJS = function(symbols) {
	return this.val.toString();
}

function floatlit(val)
{
	this.val = val;
}
floatlit.prototype.toJS = function(symbols) {
	return this.val.toString();
}

function strlit(val)
{
	this.val = val;
}
strlit.prototype.toJS = function(symbols) {
	console.log('string:', this.val);
	return '"' + this.val.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r') + '"';
}

function funcall(name, args)
{
	this.name = name;
	this.args = args;
	this.receiver = null;
}
funcall.prototype.toJS = function(symbols) {
	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
	var args = this.args.slice(0, this.args.length);
	if (this.receiver) {
		args.splice(0, 0, this.receiver);
	}
	var funinfo = symbols.find(name);
	if (!funinfo) {
		var receiver = args[0];
		args.splice(0, 1);
		for (var i in args) {
			args[i] = args[i].toJS(symbols);
		}
		return receiver.toJS(symbols, true) + '.' + (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')';
	}
	switch(funinfo.type)
	{
	case 'self':
		if (args.length < funinfo.def.args.length || funinfo.def.args[0].name != 'self') {
			var receiver = new symbol('self');
		} else {
			var receiver = args[0];
			args.splice(0, 1);
		}
		for (var i in args) {
			args[i] = args[i].toJS(symbols);
		}
		return receiver.toJS(symbols, true) + '.' + (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')';
	case 'parent':
		var ret = 'this';
		for (var i = 0; i < funinfo.depth; ++i) {
			ret += '.parent';
		}
		for (var i in args) {
			args[i] = args[i].toJS(symbols);
		}
		ret += (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')';
		return ret;
	case 'local':
	case 'upvar':
	case 'foreign':
		for (var i in args) {
			args[i] = args[i].toJS(symbols);
		}
		return (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')';
	}
}

function object(messages)
{
	this.messages = messages;
}
object.prototype.toJS = function(symbols) {
	var messages = this.messages;
	symbols = new osymbols(symbols);
	var compiled = []
	for (var i in messages) {
		var js = messages[i].toJSObject(symbols);
		if (js) {
			compiled.push(indent(js));
		}
	}
	return '{\n\tparent: ' + symbols.parentObject() + ',\n\t' + compiled.join(',\n\t') + '\n}';
}

object.prototype.toJSModule = function() {
	return '(function () {\n\tvar module = ' + indent(this.toJS(null)) + ';\n\treturn module;\n})'
}

function lambda(args, expressions)
{
	this.args = args;
	this.expressions = expressions;
}
lambda.prototype.toJS = function(symbols) {
	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;
	symbols = new lsymbols(symbols);
	for (var i in args) {
		symbols.defineVar(args[i].cleanName(), null);
		args[i] = args[i].toJS(symbols);
	}
	var compiled = []
	for (var i in exprs) {
		var js = exprs[i].toJS(symbols);
		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' + (symbols.needsSelfVar ? 'var self = this;\n\t' : '') + exprs.join(';\n\t') + '\n}'
};
lambda.prototype.toJSModule = lambda.prototype.toJS

function assignment(sym, expr)
{
	this.symbol = sym;
	this.expression = expr;
}
assignment.prototype.toJS = function(symbols) {
	var existing = symbols.find(this.symbol.name);
	var prefix = '';
	if (!existing) {
		symbols.defineVar(this.symbol.name, this.expression);
		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;
		}
	}
	if (this.expression instanceof funcall && this.expression.name == 'foreign:') {
		return null;
	}
	return prefix + this.symbol.toJS(symbols) + ' = ' + this.expression.toJS(symbols);
};
assignment.prototype.toJSObject = function(symbols) {
	symbols.defineMsg(this.symbol.name, this.expression);
	if (this.expression instanceof funcall && this.expression.name == 'foreign:') {
		return null;
	}
	return this.symbol.toJS(symbols) + ': ' + this.expression.toJS(symbols);
};

var grammar = 
'start = ws module:(object / lambda) ws { return module; };' +
'ws = ([ \\t\\n\\r] / "//" [^\\n]* "\\n")*;' +
'hws = ([ \\t] / "/*" ([^*] / "*" ! "/")* "*/" )*;' +
'expr = e:(funcall / methcall / opexpr) ws { return e; };' +
'opexpr = left:addsub hws opn:("<=" / ">=" / "<" / ">" / "=") hws right:opexpr { return new op(left, opn, right); } / addsub;' +
'addsub = left:muldiv hws opn:("+"/"-") hws right:addsub { return new op(left, opn, right); } / muldiv;'+
'muldiv = left:primlitsym hws opn:("*"/"/") hws right:muldiv { return new op(left, opn, right); } / primlitsym;'+
'primlitsym = hws val:(float / int / string / symbol / object / lambda / "(" expr:expr hws ")" { return expr; }) { return val; };' +
'symbol = chars:[a-zA-Z_!?@]+ trailing:(":"? [a-zA-Z_!?@0-9])* ! ":" { for (var i in trailing) { trailing[i] = trailing[i].join(""); } return new symbol(chars.join("") + trailing.join("")); };' +
'float = digits:[0-9]+ "." decimals:[0-9]+ { return new floatlit(parseFloat(digits.join("") + "." + decimals.join(""))); };' +
'int = digits:[0-9]+ { return new intlit(parseInt(digits.join(""), 10)); };' +
'string = "\\"" text:[^\\"]* "\\"" { return new strlit(text.join("")); };' +
'object = "#{" ws messages:assignment* "}" { return new object(messages); };' +
'assignment = hws sym:symbol hws "<-" expr:expr ws { return new assignment(sym, expr); }' +
'lambda = args:((& ":") argname+  )? "{" ws exprs:(assignment / expr)* "}" { return new lambda(args[1], exprs); };' +
'argname = init:":"? chars:[a-zA-Z_!?@]+ trailing:[a-zA-Z_!?@0-9]* hws { return new symbol(init + chars.join("") + trailing.join("")); };' +
'funcall = hws parts: funcallpart+ { var fun = ""; var args = []; for (var i in parts) { fun += parts[i].name; args = args.concat(parts[i].args); } return new funcall(fun, args); };' +
'funcallpart = fun:funpart args:opexpr* hws { return { name: fun, args: args}; };' +
'funpart = chars:[a-zA-Z_!?@]+ middle:[a-zA-Z_!?@0-9]* ":" & [ \\t\\n\\r] { return chars.join("") + middle.join("") + ":"; };' +
'methcall = receiver:opexpr hws info:methcallrest { info.receiver = receiver; return info; };' +
'methcallrest = funcall / unarymeth;' +
'unarymeth = name:symbol { return new funcall(name.name, []); };';
var parser = PEG.buildParser(grammar);

//var parser = PEG.buildParser('start = expr; expr = int; int = digits:[0-9]+ { return parseInt(digits.join(""), 10); }');
onReady(function() {
	q('#parse').onclick = function() {
		var text = q('textarea').value;
		try {
			var parsed = parser.parse(text);
			q('pre').innerHTML = text + "\n\n" + JSON.stringify(parsed);
			console.log(parsed);
		} catch(e) {
			q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column;
		}
	}
	q('#tojs').onclick = function() {
		var text = q('textarea').value;
		//try {
			var parsed = parser.parse(text);
			var js = parsed.toJSModule();
			q('pre').innerHTML = js;
			console.log(parsed);
		/*} catch(e) {
			q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column;
		}*/
	}
	q('#run').onclick = function() {
		var text = q('textarea').value;
		//try {
			var parsed = parser.parse(text);
			var js = parsed.toJSModule();
			mainModule = eval(js)();
			q('pre').innerHTML = mainModule.main();
		/*} catch(e) {
			q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column;
		}*/
	}
});