pogo
Version:
A readable, DSL friendly programming language that compiles to JavaScript
365 lines (292 loc) • 10.7 kB
JavaScript
var _ = require('underscore');
var errors = require('./errors');
var codegenUtils = require('../terms/codegenUtils');
var prototypeSource = require('../prototype');
exports.macros = function (terms) {
var macros = terms.macroDirectory();
var createOperator = function(term, name, args) {
return terms.operator(name[0], args);
};
var javaScriptOperators = [
'/',
'-',
'>=',
'!=',
'<=',
'<',
'>',
'|',
'&',
'||',
'&&',
'!',
'~',
'--',
'++',
'%',
'>>',
'>>>',
'<<'
];
_.each(javaScriptOperators, function(op) {
macros.addMacro([op], createOperator);
});
macros.addMacro(['=='], function (term, name, args) {
return terms.operator('===', args);
});
macros.addMacro(['^^'], function (term, name, args) {
return terms.operator('^', args);
});
macros.addMacro(['!='], function (term, name, args) {
return terms.operator('!==', args);
});
macros.addMacro(['in'], function (term, name, args) {
return terms.operator('in', args);
});
var constructorType = function (constructor) {
if (constructor.isVariable && constructor.variable.length == 1) {
var constructorName = constructor.variable[0];
switch (constructorName) {
case "String":
return "string";
case "Number":
return "number";
case "Boolean":
return "boolean";
}
}
};
macros.addMacro(['::'], function (term, name, args) {
var type = constructorType(args[1]);
if (type) {
return terms.typeof (args[0], type);
} else {
return terms.operator('instanceof', args);
}
});
macros.addMacro(['..'], function (term, name, args) {
return terms.range(args);
});
var matchMultiOperator = function (name) {
var firstOp = name[0];
for (var n = 1; n < name.length; n++) {
if (name[n] != firstOp) {
return;
}
}
return function (term, name, args) {
return terms.operator(name[0], args);
};
};
_.each(['+', '*'], function(op) {
macros.addWildCardMacro([op], matchMultiOperator);
});
var createIfExpression = function(term, name, args) {
var cases = [];
var errorMsg = 'arguments to if else in are incorrect, try:\n\nif (condition)\n then ()\nelse if (another condition)\n do this ()\nelse\n otherwise ()';
if (args.length < 2) {
return terms.errors.addTermWithMessage(term, errorMsg);
}
if ((name[name.length - 1] === 'else') !== (args.length % 2 === 1)) {
return terms.errors.addTermWithMessage(term, errorMsg);
}
for (var n = 0; n + 1 < args.length; n += 2) {
if (!isAny(args[n]) || !isClosureWithParameters(0)(args[n + 1])) {
return terms.errors.addTermWithMessage(term, errorMsg);
}
var body = args[n + 1].body;
cases.push({condition: args[n], body: body});
}
var elseBody;
if (args.length % 2 === 1) {
var body = args[args.length - 1].body;
elseBody = body;
}
return terms.ifExpression(cases, elseBody);
};
var matchIfMacro = function (name) {
if (/^if(ElseIf)*(Else)?$/.test(codegenUtils.concatName(name))) {
return createIfExpression;
}
};
macros.addWildCardMacro(['if'], matchIfMacro);
macros.addMacro(['promise'], function(term, name, args) {
return terms.newPromise({closure: args[0]});
});
macros.addMacro(['new'], function(term, name, args) {
var constructor;
if (args[0].isSubExpression) {
constructor = args[0].statements[0];
} else {
constructor = args[0];
}
return terms.newOperator(constructor);
});
var areValidArguments = function () {
var args = arguments[0];
var argValidators = Array.prototype.slice.call(arguments, 1);
if (args && args.length === argValidators.length) {
return _.all(_.zip(args, argValidators), function (argval) {
return argval[1](argval[0]);
});
} else {
return false;
}
};
var isClosureWithParameters = function (paramterCount) {
return function (arg) {
return arg.isClosure && arg.parameters.length === paramterCount;
};
};
var isAny = function (arg) {
return arg !== undefined;
};
var isDefinition = function (arg) {
return arg.isDefinition;
};
var createForEach = function (term, name, args) {
if (areValidArguments(args, isAny, isClosureWithParameters(1))) {
var collection = args[0];
var block = args[1];
var body = block.body;
var itemVariable = block.parameters[0];
return terms.forEach(collection, itemVariable, block.body);
} else {
return terms.errors.addTermWithMessage(term, 'arguments to for each in are incorrect, try:\n\nfor each @(item) in (items)\n do something with (item)');
}
};
macros.addMacro(['for', 'each', 'in'], createForEach);
macros.addMacro(['for', 'in'], function (term, name, args) {
if (areValidArguments(args, isAny, isClosureWithParameters(1))) {
var collection = args[0];
var block = args[1];
var iterator = block.parameters[0];
var body = block.body;
return terms.forIn(iterator, collection, block.body);
} else {
return terms.errors.addTermWithMessage(term, 'arguments to for in are incorrect, try:\n\nfor @(field) in (object)\n do something with (field)');
}
});
macros.addMacro(['for'], function(term, name, args) {
if (areValidArguments(args, isDefinition, isAny, isAny, isClosureWithParameters(0))) {
var init = args[0];
var test = args[1];
var incr = args[2];
if (!init)
return errors.addTermWithMessage(args[0], 'expected init, as in (n = 0. ...)');
if (!test)
return errors.addTermWithMessage(args[0], 'expected test, as in (... . n < 10. ...)');
if (!incr)
return errors.addTermWithMessage(args[0], 'expected incr, as in (... . ... . n = n + 1)');
return terms.forStatement(init, test, incr, args[3].body);
} else {
return terms.errors.addTermWithMessage(term, 'arguments to for are incorrect, try:\n\nfor (n = 0, n < 10, ++n)\n do something with (n)');
}
});
macros.addMacro(['while'], function(term, name, args) {
if (areValidArguments(args, isAny, isClosureWithParameters(0))) {
var test = args[0];
var statements = args[1].body;
return terms.whileStatement(test, statements);
} else {
return terms.errors.addTermWithMessage(term, 'arguments to while are incorrect, try:\n\nwhile (condition)\n do something ()');
}
});
macros.addMacro(['with'], function(term, name, args) {
if (areValidArguments(args, isAny, isClosureWithParameters(0))) {
return terms.withStatement(args[0], args[1].body);
} else {
return terms.errors.addTermWithMessage(term, 'arguments to with are incorrect, try:\n\nwith (object)\n do something with (field)');
}
});
macros.addMacro(['and'], function (term, name, args) {
return terms.operator('&&', args);
});
macros.addMacro(['or'], function (term, name, args) {
return terms.operator('||', args);
});
macros.addMacro(['not'], function (term, name, args) {
return terms.operator('!', args);
});
macros.addMacro(['return'], function(term, name, args) {
return terms.returnStatement(args && args[0]);
});
macros.addMacro(['throw'], function(term, name, args) {
if (areValidArguments(args, isAny)) {
return terms.throwStatement(args[0]);
} else {
return terms.errors.addTermWithMessage(term, 'arguments to throw are incorrect, try: @throw error');
}
});
macros.addMacro(['break'], function(term, name, args) {
return terms.breakStatement();
});
macros.addMacro(['continue'], function(term, name, args) {
return terms.continueStatement();
});
macros.addMacro(['try', 'catch'], function (term, name, args) {
if (areValidArguments(args, isClosureWithParameters(0), isAny, isClosureWithParameters(0))) {
var body = args[0].body;
var catchParameter = args[1];
var catchBody = args[2].body;
return terms.tryExpression(body, {catchBody: catchBody, catchParameter: catchParameter});
} else {
return terms.errors.addTermWithMessage(term, 'arguments to try catch are incorrect, try:\n\ntry\n something dangerous ()\ncatch (error)\n handle (error)');
}
});
macros.addMacro(['try', 'catch', 'finally'], function (term, name, args) {
if (areValidArguments(args, isClosureWithParameters(0), isAny, isClosureWithParameters(0), isClosureWithParameters(0))) {
var body = args[0].body;
var catchParameter = args[1];
var catchBody = args[2].body;
var finallyBody = args[3].body;
return terms.tryExpression(body, {catchBody: catchBody, catchParameter: catchParameter, finallyBody: finallyBody});
} else {
return terms.errors.addTermWithMessage(term, 'arguments to try catch finally are incorrect, try:\n\ntry\n something dangerous ()\ncatch (error)\n handle (error)\nfinally\n always do this ()');
}
});
macros.addMacro(['try', 'finally'], function (term, name, args) {
if (areValidArguments(args, isClosureWithParameters(0), isClosureWithParameters(0))) {
var body = args[0].body;
var finallyBody = args[1].body;
return terms.tryExpression(body, {finallyBody: finallyBody});
} else {
return terms.errors.addTermWithMessage(term, 'arguments to try finally are incorrect, try:\n\ntry\n something dangerous ()\nfinally\n always do this ()');
}
});
macros.addMacro(['nil'], function (term) {
return terms.nil();
});
macros.addMacro(['<-'], function (term) {
return terms.generator(term.functionArguments[0], term.functionArguments[1]);
});
function defineConstant(name, source) {
return terms.moduleConstants.defineAs(
[name],
terms.javascript(source.toString()),
{generated: false}
);
}
function functionMacro(name, source, dependents) {
macros.addMacro([name], function (term, _, args) {
if (dependents) {
dependents.forEach(function (dep) {
defineConstant(dep.name, dep.source);
});
}
var f = defineConstant(name, source);
if (args) {
return terms.functionCall(f, args, {couldBeMacro: false, options: true});
} else {
return f;
}
});
return {
name: name,
source: source
}
}
var protoConstant = functionMacro('prototype', prototypeSource._prototype);
functionMacro('prototypeExtending', prototypeSource.prototypeExtending, [protoConstant]);
return macros;
};