UNPKG

pogo

Version:

A readable, DSL friendly programming language that compiles to JavaScript

365 lines (292 loc) 10.7 kB
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; };