UNPKG

contracts-js

Version:

A contract library for JavaScript

1,412 lines (1,407 loc) 668 kB
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.sweet=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ /* Copyright (C) 2012 Tim Disney <tim@disnet.me> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 'use strict'; var _ = _dereq_('underscore'); var parser = _dereq_('./parser'); var syn = _dereq_('./syntax'); var se = _dereq_('./scopedEval'); var patternModule = _dereq_('./patterns'); var codegen = _dereq_('escodegen'); var assert = syn.assert; var throwSyntaxError = syn.throwSyntaxError; var throwSyntaxCaseError = syn.throwSyntaxCaseError; var SyntaxCaseError = syn.SyntaxCaseError; var unwrapSyntax = syn.unwrapSyntax; // used to export "private" methods for unit testing exports._test = {}; function StringMap(o) { this.__data = o || {}; } StringMap.prototype = { has: function (key) { return Object.prototype.hasOwnProperty.call(this.__data, key); }, get: function (key) { return this.has(key) ? this.__data[key] : void 0; }, set: function (key, value) { this.__data[key] = value; }, extend: function () { var args = _.map(_.toArray(arguments), function (x) { return x.__data; }); _.extend.apply(_, [this.__data].concat(args)); return this; } }; var scopedEval = se.scopedEval; var Rename = syn.Rename; var Mark = syn.Mark; var Def = syn.Def; var syntaxFromToken = syn.syntaxFromToken; var joinSyntax = syn.joinSyntax; var builtinMode = false; var expandCount = 0; var maxExpands; var push = Array.prototype.push; function remdup(mark, mlist) { if (mark === _.first(mlist)) { return _.rest(mlist, 1); } return [mark].concat(mlist); } // (CSyntax) -> [...Num] function marksof(ctx, stopName, originalName) { while (ctx) { if (ctx.constructor === Mark) { return remdup(ctx.mark, marksof(ctx.context, stopName, originalName)); } if (ctx.constructor === Def) { ctx = ctx.context; continue; } if (ctx.constructor === Rename) { if (stopName === originalName + '$' + ctx.name) { return []; } ctx = ctx.context; continue; } } return []; } function resolve(stx) { return resolveCtx(stx.token.value, stx.context, [], [], {}); } // This call memoizes intermediate results in the recursive invocation. // The scope of the memo cache is the resolve() call, so that multiple // resolve() calls don't walk all over each other, and memory used for // the memoization can be garbage collected. // // The memoization addresses issue #232. // // It looks like the memoization uses only the context and doesn't look // at originalName, stop_spine and stop_branch arguments. This is valid // because whenever in every recursive call operates on a "deeper" or // else a newly created context. Therefore the collection of // [originalName, stop_spine, stop_branch] can all be associated with a // unique context. This argument is easier to see in a recursive // rewrite of the resolveCtx function than with the while loop // optimization - https://gist.github.com/srikumarks/9847260 - where the // recursive steps always operate on a different context. // // This might make it seem that the resolution results can be stored on // the context object itself, but that would not work in general // because multiple resolve() calls will walk over each other's cache // results, which fails tests. So the memoization uses only a context's // unique instance numbers as the memoization key and is local to each // resolve() call. // // With this memoization, the time complexity of the resolveCtx call is // no longer exponential for the cases in issue #232. function resolveCtx(originalName, ctx, stop_spine, stop_branch, cache) { if (!ctx) { return originalName; } var key = ctx.instNum; return cache[key] || (cache[key] = resolveCtxFull(originalName, ctx, stop_spine, stop_branch, cache)); } // (Syntax) -> String function resolveCtxFull(originalName, ctx, stop_spine, stop_branch, cache) { while (true) { if (!ctx) { return originalName; } if (ctx.constructor === Mark) { ctx = ctx.context; continue; } if (ctx.constructor === Def) { if (stop_spine.indexOf(ctx.defctx) !== -1) { ctx = ctx.context; continue; } else { stop_branch = unionEl(stop_branch, ctx.defctx); ctx = renames(ctx.defctx, ctx.context, originalName); continue; } } if (ctx.constructor === Rename) { if (originalName === ctx.id.token.value) { var idName = resolveCtx(ctx.id.token.value, ctx.id.context, stop_branch, stop_branch, cache); var subName = resolveCtx(originalName, ctx.context, unionEl(stop_spine, ctx.def), stop_branch, cache); if (idName === subName) { var idMarks = marksof(ctx.id.context, originalName + '$' + ctx.name, originalName); var subMarks = marksof(ctx.context, originalName + '$' + ctx.name, originalName); if (arraysEqual(idMarks, subMarks)) { return originalName + '$' + ctx.name; } } } ctx = ctx.context; continue; } return originalName; } } function arraysEqual(a, b) { if (a.length !== b.length) { return false; } for (var i = 0; i < a.length; i++) { if (a[i] !== b[i]) { return false; } } return true; } function renames(defctx, oldctx, originalName) { var acc = oldctx; for (var i = 0; i < defctx.length; i++) { if (defctx[i].id.token.value === originalName) { acc = new Rename(defctx[i].id, defctx[i].name, acc, defctx); } } return acc; } function unionEl(arr, el) { if (arr.indexOf(el) === -1) { var res = arr.slice(0); res.push(el); return res; } return arr; } var nextFresh = 0; // fun () -> Num function fresh() { return nextFresh++; } // wraps the array of syntax objects in the delimiters given by the second argument // ([...CSyntax], CSyntax) -> [...CSyntax] function wrapDelim(towrap, delimSyntax) { assert(delimSyntax.token.type === parser.Token.Delimiter, 'expecting a delimiter token'); return syntaxFromToken({ type: parser.Token.Delimiter, value: delimSyntax.token.value, inner: towrap, range: delimSyntax.token.range, startLineNumber: delimSyntax.token.startLineNumber, lineStart: delimSyntax.token.lineStart }, delimSyntax); } // (CSyntax) -> [...CSyntax] function getParamIdentifiers(argSyntax) { if (argSyntax.token.type === parser.Token.Delimiter) { return _.filter(argSyntax.token.inner, function (stx) { return stx.token.value !== ','; }); } else if (argSyntax.token.type === parser.Token.Identifier) { return [argSyntax]; } else { assert(false, 'expecting a delimiter or a single identifier for function parameters'); } } function inherit(parent, child, methods) { var P = function () { }; P.prototype = parent.prototype; child.prototype = new P(); child.prototype.constructor = child; _.extend(child.prototype, methods); } // A TermTree is the core data structure for the macro expansion process. // It acts as a semi-structured representation of the syntax. function TermTree() { } TermTree.properties = []; TermTree.create = function () { return new TermTree(); }; TermTree.prototype = { 'isTermTree': true, 'destruct': function () { var self = this; return _.reduce(this.constructor.properties, function (acc, prop) { if (self[prop] && self[prop].isTermTree) { push.apply(acc, self[prop].destruct()); return acc; } else if (self[prop] && self[prop].token && self[prop].token.inner) { var src = self[prop].token; var keys = Object.keys(src); var newtok = {}; for (var i = 0, len = keys.length, key; i < len; i++) { key = keys[i]; newtok[key] = src[key]; } var clone = syntaxFromToken(newtok, self[prop]); clone.token.inner = _.reduce(clone.token.inner, function (acc$2, t) { if (t && t.isTermTree) { push.apply(acc$2, t.destruct()); return acc$2; } acc$2.push(t); return acc$2; }, []); acc.push(clone); return acc; } else if (Array.isArray(self[prop])) { var destArr = _.reduce(self[prop], function (acc$2, t) { if (t && t.isTermTree) { push.apply(acc$2, t.destruct()); return acc$2; } acc$2.push(t); return acc$2; }, []); push.apply(acc, destArr); return acc; } else if (self[prop]) { acc.push(self[prop]); return acc; } else { return acc; } }, []); }, 'addDefCtx': function (def) { var self = this; _.each(this.constructor.properties, function (prop) { if (Array.isArray(self[prop])) { self[prop] = _.map(self[prop], function (item) { return item.addDefCtx(def); }); } else if (self[prop]) { self[prop] = self[prop].addDefCtx(def); } }); return this; }, 'rename': function (id, name) { var self = this; _.each(this.constructor.properties, function (prop) { if (Array.isArray(self[prop])) { self[prop] = _.map(self[prop], function (item) { return item.rename(id, name); }); } else if (self[prop]) { self[prop] = self[prop].rename(id, name); } }); return this; } }; function EOF(eof) { this.eof = eof; } EOF.properties = ['eof']; EOF.create = function (eof) { return new EOF(eof); }; inherit(TermTree, EOF, { 'isEOF': true }); function Keyword(keyword) { this.keyword = keyword; } Keyword.properties = ['keyword']; Keyword.create = function (keyword) { return new Keyword(keyword); }; inherit(TermTree, Keyword, { 'isKeyword': true }); function Punc(punc) { this.punc = punc; } Punc.properties = ['punc']; Punc.create = function (punc) { return new Punc(punc); }; inherit(TermTree, Punc, { 'isPunc': true }); function Delimiter(delim) { this.delim = delim; } Delimiter.properties = ['delim']; Delimiter.create = function (delim) { return new Delimiter(delim); }; inherit(TermTree, Delimiter, { 'isDelimiter': true }); function LetMacro(name, body) { this.name = name; this.body = body; } LetMacro.properties = [ 'name', 'body' ]; LetMacro.create = function (name, body) { return new LetMacro(name, body); }; inherit(TermTree, LetMacro, { 'isLetMacro': true }); function Macro(name, body) { this.name = name; this.body = body; } Macro.properties = [ 'name', 'body' ]; Macro.create = function (name, body) { return new Macro(name, body); }; inherit(TermTree, Macro, { 'isMacro': true }); function AnonMacro(body) { this.body = body; } AnonMacro.properties = ['body']; AnonMacro.create = function (body) { return new AnonMacro(body); }; inherit(TermTree, AnonMacro, { 'isAnonMacro': true }); function OperatorDefinition(type, name, prec, assoc, body) { this.type = type; this.name = name; this.prec = prec; this.assoc = assoc; this.body = body; } OperatorDefinition.properties = [ 'type', 'name', 'prec', 'assoc', 'body' ]; OperatorDefinition.create = function (type, name, prec, assoc, body) { return new OperatorDefinition(type, name, prec, assoc, body); }; inherit(TermTree, OperatorDefinition, { 'isOperatorDefinition': true }); function Module(body, exports$2) { this.body = body; this.exports = exports$2; } Module.properties = [ 'body', 'exports' ]; Module.create = function (body, exports$2) { return new Module(body, exports$2); }; inherit(TermTree, Module, { 'isModule': true }); function Export(name) { this.name = name; } Export.properties = ['name']; Export.create = function (name) { return new Export(name); }; inherit(TermTree, Export, { 'isExport': true }); function VariableDeclaration(ident, eq, init, comma) { this.ident = ident; this.eq = eq; this.init = init; this.comma = comma; } VariableDeclaration.properties = [ 'ident', 'eq', 'init', 'comma' ]; VariableDeclaration.create = function (ident, eq, init, comma) { return new VariableDeclaration(ident, eq, init, comma); }; inherit(TermTree, VariableDeclaration, { 'isVariableDeclaration': true }); function Statement() { } Statement.properties = []; Statement.create = function () { return new Statement(); }; inherit(TermTree, Statement, { 'isStatement': true }); function Empty() { } Empty.properties = []; Empty.create = function () { return new Empty(); }; inherit(Statement, Empty, { 'isEmpty': true }); function CatchClause(keyword, params, body) { this.keyword = keyword; this.params = params; this.body = body; } CatchClause.properties = [ 'keyword', 'params', 'body' ]; CatchClause.create = function (keyword, params, body) { return new CatchClause(keyword, params, body); }; inherit(Statement, CatchClause, { 'isCatchClause': true }); function ForStatement(keyword, cond) { this.keyword = keyword; this.cond = cond; } ForStatement.properties = [ 'keyword', 'cond' ]; ForStatement.create = function (keyword, cond) { return new ForStatement(keyword, cond); }; inherit(Statement, ForStatement, { 'isForStatement': true }); function ReturnStatement(keyword, expr) { this.keyword = keyword; this.expr = expr; } ReturnStatement.properties = [ 'keyword', 'expr' ]; ReturnStatement.create = function (keyword, expr) { return new ReturnStatement(keyword, expr); }; inherit(Statement, ReturnStatement, { 'isReturnStatement': true, 'destruct': function () { var expr = this.expr.destruct(); // need to adjust the line numbers to make sure that the expr // starts on the same line as the return keyword. This might // not be the case if an operator or infix macro perturbed the // line numbers during expansion. expr = adjustLineContext(expr, this.keyword.keyword); return this.keyword.destruct().concat(expr); } }); function Expr() { } Expr.properties = []; Expr.create = function () { return new Expr(); }; inherit(Statement, Expr, { 'isExpr': true }); function UnaryOp(op, expr) { this.op = op; this.expr = expr; } UnaryOp.properties = [ 'op', 'expr' ]; UnaryOp.create = function (op, expr) { return new UnaryOp(op, expr); }; inherit(Expr, UnaryOp, { 'isUnaryOp': true }); function PostfixOp(expr, op) { this.expr = expr; this.op = op; } PostfixOp.properties = [ 'expr', 'op' ]; PostfixOp.create = function (expr, op) { return new PostfixOp(expr, op); }; inherit(Expr, PostfixOp, { 'isPostfixOp': true }); function BinOp(left, op, right) { this.left = left; this.op = op; this.right = right; } BinOp.properties = [ 'left', 'op', 'right' ]; BinOp.create = function (left, op, right) { return new BinOp(left, op, right); }; inherit(Expr, BinOp, { 'isBinOp': true }); function AssignmentExpression(left, op, right) { this.left = left; this.op = op; this.right = right; } AssignmentExpression.properties = [ 'left', 'op', 'right' ]; AssignmentExpression.create = function (left, op, right) { return new AssignmentExpression(left, op, right); }; inherit(Expr, AssignmentExpression, { 'isAssignmentExpression': true }); function ConditionalExpression(cond, question, tru, colon, fls) { this.cond = cond; this.question = question; this.tru = tru; this.colon = colon; this.fls = fls; } ConditionalExpression.properties = [ 'cond', 'question', 'tru', 'colon', 'fls' ]; ConditionalExpression.create = function (cond, question, tru, colon, fls) { return new ConditionalExpression(cond, question, tru, colon, fls); }; inherit(Expr, ConditionalExpression, { 'isConditionalExpression': true }); function NamedFun(keyword, star, name, params, body) { this.keyword = keyword; this.star = star; this.name = name; this.params = params; this.body = body; } NamedFun.properties = [ 'keyword', 'star', 'name', 'params', 'body' ]; NamedFun.create = function (keyword, star, name, params, body) { return new NamedFun(keyword, star, name, params, body); }; inherit(Expr, NamedFun, { 'isNamedFun': true }); function AnonFun(keyword, star, params, body) { this.keyword = keyword; this.star = star; this.params = params; this.body = body; } AnonFun.properties = [ 'keyword', 'star', 'params', 'body' ]; AnonFun.create = function (keyword, star, params, body) { return new AnonFun(keyword, star, params, body); }; inherit(Expr, AnonFun, { 'isAnonFun': true }); function ArrowFun(params, arrow, body) { this.params = params; this.arrow = arrow; this.body = body; } ArrowFun.properties = [ 'params', 'arrow', 'body' ]; ArrowFun.create = function (params, arrow, body) { return new ArrowFun(params, arrow, body); }; inherit(Expr, ArrowFun, { 'isArrowFun': true }); function ObjDotGet(left, dot, right) { this.left = left; this.dot = dot; this.right = right; } ObjDotGet.properties = [ 'left', 'dot', 'right' ]; ObjDotGet.create = function (left, dot, right) { return new ObjDotGet(left, dot, right); }; inherit(Expr, ObjDotGet, { 'isObjDotGet': true }); function ObjGet(left, right) { this.left = left; this.right = right; } ObjGet.properties = [ 'left', 'right' ]; ObjGet.create = function (left, right) { return new ObjGet(left, right); }; inherit(Expr, ObjGet, { 'isObjGet': true }); function Template(template) { this.template = template; } Template.properties = ['template']; Template.create = function (template) { return new Template(template); }; inherit(Expr, Template, { 'isTemplate': true }); function Call(fun, args) { this.fun = fun; this.args = args; } Call.properties = [ 'fun', 'args' ]; Call.create = function (fun, args) { return new Call(fun, args); }; inherit(Expr, Call, { 'isCall': true }); function PrimaryExpression() { } PrimaryExpression.properties = []; PrimaryExpression.create = function () { return new PrimaryExpression(); }; inherit(Expr, PrimaryExpression, { 'isPrimaryExpression': true }); function ThisExpression(keyword) { this.keyword = keyword; } ThisExpression.properties = ['keyword']; ThisExpression.create = function (keyword) { return new ThisExpression(keyword); }; inherit(PrimaryExpression, ThisExpression, { 'isThisExpression': true }); function Lit(lit) { this.lit = lit; } Lit.properties = ['lit']; Lit.create = function (lit) { return new Lit(lit); }; inherit(PrimaryExpression, Lit, { 'isLit': true }); function Block(body) { this.body = body; } Block.properties = ['body']; Block.create = function (body) { return new Block(body); }; inherit(PrimaryExpression, Block, { 'isBlock': true }); function ArrayLiteral(array) { this.array = array; } ArrayLiteral.properties = ['array']; ArrayLiteral.create = function (array) { return new ArrayLiteral(array); }; inherit(PrimaryExpression, ArrayLiteral, { 'isArrayLiteral': true }); function Id(id) { this.id = id; } Id.properties = ['id']; Id.create = function (id) { return new Id(id); }; inherit(PrimaryExpression, Id, { 'isId': true }); function Partial() { } Partial.properties = []; Partial.create = function () { return new Partial(); }; inherit(TermTree, Partial, { 'isPartial': true }); function PartialOperation(stx, left) { this.stx = stx; this.left = left; } PartialOperation.properties = [ 'stx', 'left' ]; PartialOperation.create = function (stx, left) { return new PartialOperation(stx, left); }; inherit(Partial, PartialOperation, { 'isPartialOperation': true }); function PartialExpression(stx, left, combine) { this.stx = stx; this.left = left; this.combine = combine; } PartialExpression.properties = [ 'stx', 'left', 'combine' ]; PartialExpression.create = function (stx, left, combine) { return new PartialExpression(stx, left, combine); }; inherit(Partial, PartialExpression, { 'isPartialExpression': true }); function BindingStatement(keyword, decls) { this.keyword = keyword; this.decls = decls; } BindingStatement.properties = [ 'keyword', 'decls' ]; BindingStatement.create = function (keyword, decls) { return new BindingStatement(keyword, decls); }; inherit(Statement, BindingStatement, { 'isBindingStatement': true, 'destruct': function () { return this.keyword.destruct().concat(_.reduce(this.decls, function (acc, decl) { push.apply(acc, decl.destruct()); return acc; }, [])); } }); function VariableStatement(keyword, decls) { this.keyword = keyword; this.decls = decls; } VariableStatement.properties = [ 'keyword', 'decls' ]; VariableStatement.create = function (keyword, decls) { return new VariableStatement(keyword, decls); }; inherit(BindingStatement, VariableStatement, { 'isVariableStatement': true }); function LetStatement(keyword, decls) { this.keyword = keyword; this.decls = decls; } LetStatement.properties = [ 'keyword', 'decls' ]; LetStatement.create = function (keyword, decls) { return new LetStatement(keyword, decls); }; inherit(BindingStatement, LetStatement, { 'isLetStatement': true }); function ConstStatement(keyword, decls) { this.keyword = keyword; this.decls = decls; } ConstStatement.properties = [ 'keyword', 'decls' ]; ConstStatement.create = function (keyword, decls) { return new ConstStatement(keyword, decls); }; inherit(BindingStatement, ConstStatement, { 'isConstStatement': true }); function ParenExpression(args, delim, commas) { this.args = args; this.delim = delim; this.commas = commas; } ParenExpression.properties = [ 'args', 'delim', 'commas' ]; ParenExpression.create = function (args, delim, commas) { return new ParenExpression(args, delim, commas); }; inherit(PrimaryExpression, ParenExpression, { 'isParenExpression': true, 'destruct': function () { var commas = this.commas.slice(); var src = this.delim.token; var keys = Object.keys(src); var newtok = {}; for (var i = 0, len = keys.length, key; i < len; i++) { key = keys[i]; newtok[key] = src[key]; } var delim = syntaxFromToken(newtok, this.delim); delim.token.inner = _.reduce(this.args, function (acc, term) { assert(term && term.isTermTree, 'expecting term trees in destruct of ParenExpression'); push.apply(acc, term.destruct()); // add all commas except for the last one if (commas.length > 0) { acc.push(commas.shift()); } return acc; }, []); return Delimiter.create(delim).destruct(); } }); function stxIsUnaryOp(stx) { var staticOperators = [ '+', '-', '~', '!', 'delete', 'void', 'typeof', 'yield', 'new', '++', '--' ]; return _.contains(staticOperators, unwrapSyntax(stx)); } function stxIsBinOp(stx) { var staticOperators = [ '+', '-', '*', '/', '%', '||', '&&', '|', '&', '^', '==', '!=', '===', '!==', '<', '>', '<=', '>=', 'in', 'instanceof', '<<', '>>', '>>>' ]; return _.contains(staticOperators, unwrapSyntax(stx)); } function getUnaryOpPrec(op) { var operatorPrecedence = { 'new': 16, '++': 15, '--': 15, '!': 14, '~': 14, '+': 14, '-': 14, 'typeof': 14, 'void': 14, 'delete': 14, 'yield': 2 }; return operatorPrecedence[op]; } function getBinaryOpPrec(op) { var operatorPrecedence = { '*': 13, '/': 13, '%': 13, '+': 12, '-': 12, '>>': 11, '<<': 11, '>>>': 11, '<': 10, '<=': 10, '>': 10, '>=': 10, 'in': 10, 'instanceof': 10, '==': 9, '!=': 9, '===': 9, '!==': 9, '&': 8, '^': 7, '|': 6, '&&': 5, '||': 4 }; return operatorPrecedence[op]; } function getBinaryOpAssoc(op) { var operatorAssoc = { '*': 'left', '/': 'left', '%': 'left', '+': 'left', '-': 'left', '>>': 'left', '<<': 'left', '>>>': 'left', '<': 'left', '<=': 'left', '>': 'left', '>=': 'left', 'in': 'left', 'instanceof': 'left', '==': 'left', '!=': 'left', '===': 'left', '!==': 'left', '&': 'left', '^': 'left', '|': 'left', '&&': 'left', '||': 'left' }; return operatorAssoc[op]; } function stxIsAssignOp(stx) { var staticOperators = [ '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=', '|=', '^=', '&=' ]; return _.contains(staticOperators, unwrapSyntax(stx)); } function enforestVarStatement(stx, context, varStx) { var decls = []; var rest = stx; var rhs; if (!rest.length) { throwSyntaxError('enforest', 'Unexpected end of input', varStx); } if (expandCount >= maxExpands) { return null; } while (rest.length) { if (rest[0].token.type === parser.Token.Identifier) { if (rest[1] && rest[1].token.type === parser.Token.Punctuator && rest[1].token.value === '=') { rhs = get_expression(rest.slice(2), context); if (rhs.result == null) { throwSyntaxError('enforest', 'Unexpected token', rhs.rest[0]); } if (rhs.rest[0] && rhs.rest[0].token.type === parser.Token.Punctuator && rhs.rest[0].token.value === ',') { decls.push(VariableDeclaration.create(rest[0], rest[1], rhs.result, rhs.rest[0])); rest = rhs.rest.slice(1); continue; } else { decls.push(VariableDeclaration.create(rest[0], rest[1], rhs.result, null)); rest = rhs.rest; break; } } else if (rest[1] && rest[1].token.type === parser.Token.Punctuator && rest[1].token.value === ',') { decls.push(VariableDeclaration.create(rest[0], null, null, rest[1])); rest = rest.slice(2); } else { decls.push(VariableDeclaration.create(rest[0], null, null, null)); rest = rest.slice(1); break; } } else { throwSyntaxError('enforest', 'Unexpected token', rest[0]); } } return { result: decls, rest: rest }; } function enforestAssignment(stx, context, left, prevStx, prevTerms) { var op = stx[0]; var rightStx = stx.slice(1); var opTerm = Punc.create(stx[0]); var opPrevStx = tagWithTerm(opTerm, [stx[0]]).concat(tagWithTerm(left, left.destruct().reverse()), prevStx); var opPrevTerms = [ opTerm, left ].concat(prevTerms); var opRes = enforest(rightStx, context, opPrevStx, opPrevTerms); if (opRes.result) { // Lookbehind was matched, so it may not even be a binop anymore. if (opRes.prevTerms.length < opPrevTerms.length) { return opRes; } var right = opRes.result; // only a binop if the right is a real expression // so 2+2++ will only match 2+2 if (right.isExpr) { var term = AssignmentExpression.create(left, op, right); return { result: term, rest: opRes.rest, prevStx: prevStx, prevTerms: prevTerms }; } } else { return opRes; } } function enforestParenExpression(parens, context) { var argRes, enforestedArgs = [], commas = []; var innerTokens = parens.expose().token.inner; while (innerTokens.length > 0) { argRes = enforest(innerTokens, context); if (!argRes.result || !argRes.result.isExpr) { return null; } enforestedArgs.push(argRes.result); innerTokens = argRes.rest; if (innerTokens[0] && innerTokens[0].token.value === ',') { // record the comma for later commas.push(innerTokens[0]); // but dump it for the next loop turn innerTokens = innerTokens.slice(1); } else { // either there are no more tokens or // they aren't a comma, either way we // are done with the loop break; } } return innerTokens.length ? null : ParenExpression.create(enforestedArgs, parens, commas); } function adjustLineContext(stx, original, current) { current = current || { lastLineNumber: stx[0].token.lineNumber || stx[0].token.startLineNumber, lineNumber: original.token.lineNumber }; return _.map(stx, function (stx$2) { if (stx$2.token.type === parser.Token.Delimiter) { // handle tokens with missing line info stx$2.token.startLineNumber = typeof stx$2.token.startLineNumber == 'undefined' ? original.token.lineNumber : stx$2.token.startLineNumber; stx$2.token.endLineNumber = typeof stx$2.token.endLineNumber == 'undefined' ? original.token.lineNumber : stx$2.token.endLineNumber; stx$2.token.startLineStart = typeof stx$2.token.startLineStart == 'undefined' ? original.token.lineStart : stx$2.token.startLineStart; stx$2.token.endLineStart = typeof stx$2.token.endLineStart == 'undefined' ? original.token.lineStart : stx$2.token.endLineStart; stx$2.token.startRange = typeof stx$2.token.startRange == 'undefined' ? original.token.range : stx$2.token.startRange; stx$2.token.endRange = typeof stx$2.token.endRange == 'undefined' ? original.token.range : stx$2.token.endRange; stx$2.token.sm_startLineNumber = typeof stx$2.token.sm_startLineNumber == 'undefined' ? stx$2.token.startLineNumber : stx$2.token.sm_startLineNumber; stx$2.token.sm_endLineNumber = typeof stx$2.token.sm_endLineNumber == 'undefined' ? stx$2.token.endLineNumber : stx$2.token.sm_endLineNumber; stx$2.token.sm_startLineStart = typeof stx$2.token.sm_startLineStart == 'undefined' ? stx$2.token.startLineStart : stx$2.token.sm_startLineStart; stx$2.token.sm_endLineStart = typeof stx$2.token.sm_endLineStart == 'undefined' ? stx$2.token.endLineStart : stx$2.token.sm_endLineStart; stx$2.token.sm_startRange = typeof stx$2.token.sm_startRange == 'undefined' ? stx$2.token.startRange : stx$2.token.sm_startRange; stx$2.token.sm_endRange = typeof stx$2.token.sm_endRange == 'undefined' ? stx$2.token.endRange : stx$2.token.sm_endRange; if (stx$2.token.startLineNumber !== current.lineNumber) { if (stx$2.token.startLineNumber !== current.lastLineNumber) { current.lineNumber++; current.lastLineNumber = stx$2.token.startLineNumber; stx$2.token.startLineNumber = current.lineNumber; } else { current.lastLineNumber = stx$2.token.startLineNumber; stx$2.token.startLineNumber = current.lineNumber; } } if (stx$2.token.inner.length > 0) { stx$2.token.inner = adjustLineContext(stx$2.token.inner, original, current); } return stx$2; } // handle tokens with missing line info stx$2.token.lineNumber = typeof stx$2.token.lineNumber == 'undefined' ? original.token.lineNumber : stx$2.token.lineNumber; stx$2.token.lineStart = typeof stx$2.token.lineStart == 'undefined' ? original.token.lineStart : stx$2.token.lineStart; stx$2.token.range = typeof stx$2.token.range == 'undefined' ? original.token.range : stx$2.token.range; // Only set the sourcemap line info once. Necessary because a single // syntax object can go through expansion multiple times. If at some point // we want to write an expansion stepper this might be a good place to store // intermediate expansion line info (ie push to a stack instead of // just write once). stx$2.token.sm_lineNumber = typeof stx$2.token.sm_lineNumber == 'undefined' ? stx$2.token.lineNumber : stx$2.token.sm_lineNumber; stx$2.token.sm_lineStart = typeof stx$2.token.sm_lineStart == 'undefined' ? stx$2.token.lineStart : stx$2.token.sm_lineStart; stx$2.token.sm_range = typeof stx$2.token.sm_range == 'undefined' ? stx$2.token.range.slice() : stx$2.token.sm_range; // move the line info to line up with the macro name // (line info starting from the macro name) if (stx$2.token.lineNumber !== current.lineNumber) { if (stx$2.token.lineNumber !== current.lastLineNumber) { current.lineNumber++; current.lastLineNumber = stx$2.token.lineNumber; stx$2.token.lineNumber = current.lineNumber; } else { current.lastLineNumber = stx$2.token.lineNumber; stx$2.token.lineNumber = current.lineNumber; } } return stx$2; }); } function getName(head, rest) { var idx = 0; var curr = head; var next = rest[idx]; var name = [head]; while (true) { if (next && (next.token.type === parser.Token.Punctuator || next.token.type === parser.Token.Identifier || next.token.type === parser.Token.Keyword) && (curr.token.sm_range && next.token.sm_range && curr.token.sm_range[1] === next.token.sm_range[0] || curr.token.range[1] === next.token.range[0])) { name.push(next); curr = next; next = rest[++idx]; } else { return name; } } } function getMacroInEnv(head, rest, env) { if (!(head.token.type === parser.Token.Identifier || head.token.type === parser.Token.Keyword || head.token.type === parser.Token.Punctuator)) { return null; } var name = getName(head, rest); // simple case, don't need to create a new syntax object if (name.length === 1) { if (env.names.get(unwrapSyntax(name[0]))) { var resolvedName = resolve(name[0]); if (env.has(resolvedName)) { return env.get(resolvedName); } } return null; } else { while (name.length > 0) { var nameStr = name.map(unwrapSyntax).join(''); if (env.names.get(nameStr)) { var nameStx = syn.makeIdent(nameStr, name[0]); var resolvedName = resolve(nameStx); if (env.has(resolvedName)) { return env.get(resolvedName); } } name.pop(); } return null; } } function nameInEnv(head, rest, env) { return getMacroInEnv(head, rest, env) !== null; } // This should only be used on things that can't be rebound except by // macros (puncs, keywords). function resolveFast(stx, env) { var name = unwrapSyntax(stx); return env.names.get(name) ? resolve(stx) : name; } function expandMacro(stx, context, opCtx, opType, macroObj) { // pull the macro transformer out the environment var head = stx[0]; var rest = stx.slice(1); macroObj = macroObj || getMacroInEnv(head, rest, context.env); var stxArg = rest.slice(macroObj.fullName.length - 1); var transformer; if (opType != null) { assert(opType === 'binary' || opType === 'unary', 'operator type should be either unary or binary: ' + opType); transformer = macroObj[opType].fn; } else { transformer = macroObj.fn; } // create a new mark to be used for the input to // the macro var newMark = fresh(); var transformerContext = makeExpanderContext(_.defaults({ mark: newMark }, context)); // apply the transformer var rt; try { rt = transformer([head].concat(stxArg), transformerContext, opCtx.prevStx, opCtx.prevTerms); } catch (e) { if (e instanceof SyntaxCaseError) { // add a nicer error for syntax case var nameStr = macroObj.fullName.map(function (stx$2) { return stx$2.token.value; }).join(''); if (opType != null) { var argumentString = '`' + stxArg.slice(0, 5).map(function (stx$2) { return stx$2.token.value; }).join(' ') + '...`'; throwSyntaxError('operator', 'Operator `' + nameStr + '` could not be matched with ' + argumentString, head); } else { var argumentString = '`' + stxArg.slice(0, 5).map(function (stx$2) { return stx$2.token.value; }).join(' ') + '...`'; throwSyntaxError('macro', 'Macro `' + nameStr + '` could not be matched with ' + argumentString, head); } } else { // just rethrow it throw e; } } if (!builtinMode && !macroObj.builtin) { expandCount++; } if (!Array.isArray(rt.result)) { throwSyntaxError('enforest', 'Macro must return a syntax array', stx[0]); } if (rt.result.length > 0) { var adjustedResult = adjustLineContext(rt.result, head); if (stx[0].token.leadingComments) { if (adjustedResult[0].token.leadingComments) { adjustedResult[0].token.leadingComments = adjustedResult[0].token.leadingComments.concat(head.token.leadingComments); } else { adjustedResult[0].token.leadingComments = head.token.leadingComments; } } rt.result = adjustedResult; } return rt; } function comparePrec(left, right, assoc) { if (assoc === 'left') { return left <= right; } return left < right; } // enforest the tokens, returns an object with the `result` TermTree and // the uninterpreted `rest` of the syntax function enforest(toks, context, prevStx, prevTerms) { assert(toks.length > 0, 'enforest assumes there are tokens to work with'); prevStx = prevStx || []; prevTerms = prevTerms || []; if (expandCount >= maxExpands) { return { result: null, rest: toks }; } function step(head, rest, opCtx) { var innerTokens; assert(Array.isArray(rest), 'result must at least be an empty array'); if (head.isTermTree) { var isCustomOp = false; var uopMacroObj; var uopSyntax; if (head.isPunc || head.isKeyword || head.isId) { if (head.isPunc) { uopSyntax = head.punc; } else if (head.isKeyword) { uopSyntax = head.keyword; } else if (head.isId) { uopSyntax = head.id; } uopMacroObj = getMacroInEnv(uopSyntax, rest, context.env); isCustomOp = uopMacroObj && uopMacroObj.isOp; } // look up once (we want to check multiple properties on bopMacroObj // without repeatedly calling getMacroInEnv) var bopMacroObj; if (rest[0] && rest[1]) { bopMacroObj = getMacroInEnv(rest[0], rest.slice(1), context.env); } // unary operator if (isCustomOp && uopMacroObj.unary || uopSyntax && stxIsUnaryOp(uopSyntax)) { var uopPrec; if (isCustomOp && uopMacroObj.unary) { uopPrec = uopMacroObj.unary.prec; } else { uopPrec = getUnaryOpPrec(unwrapSyntax(uopSyntax)); } var opRest = rest; var uopMacroName; if (uopMacroObj) { uopMacroName = [uopSyntax].concat(rest.slice(0, uopMacroObj.fullName.length - 1)); opRest = rest.slice(uopMacroObj.fullName.length - 1); } var leftLeft = opCtx.prevTerms[0] && opCtx.prevTerms[0].isPartial ? opCtx.prevTerms[0] : null; var unopTerm = PartialOperation.create(head, leftLeft); var unopPrevStx = tagWithTerm(unopTerm, head.destruct().reverse()).concat(opCtx.prevStx); var unopPrevTerms = [unopTerm].concat(opCtx.prevTerms); var unopOpCtx = _.extend({}, opCtx, { combine: function (t) { if (t.isExpr) { if (isCustomOp && uopMacroObj.unary) { var rt$2 = expandMacro(uopMacroName.concat(t.destruct()), context, opCtx, 'unary'); var newt = get_expression(rt$2.result, context); assert(newt.rest.length === 0, 'should never have left over syntax'); return opCtx.combine(newt.result); } return opCtx.combine(UnaryOp.create(uopSyntax, t)); } else { // not actually an expression so don't create // a UnaryOp term just return with the punctuator return opCtx.combine(head); } }, prec: uopPrec, prevStx: unopPrevStx, prevTerms: unopPrevTerms, op: unopTerm }); return step(opRest[0], opRest.slice(1), unopOpCtx); } // BinOp else if (head.isExpr && (rest[0] && rest[1] && (stxIsBinOp(rest[0]) && !bopMacroObj || bopMacroObj && bopMacroObj.isOp && bopMacroObj.binary))) { var opRes; var op = rest[0]; var left = head; var rightStx = rest.slice(1); var leftLeft = opCtx.prevTerms[0] && opCtx.prevTerms[0].isPartial ? opCtx.prevTerms[0] : null; var leftTerm = PartialExpression.create(head.destruct(), leftLeft, function () { return step(head, [], opCtx); }); var opTerm = PartialOperation.create(op, leftTerm); var opPrevStx = tagWithTerm(opTerm, [rest[0]]).concat(tagWithTerm(leftTerm, head.destruct()).reverse(), opCtx.prevStx); var opPrevTerms = [ opTerm, leftTerm ].concat(opCtx.prevTerms); var isCustomOp = bopMacroObj && bopMacroObj.isOp && bopMacroObj.binary; var bopPrec; var bopAssoc; if (isCustomOp && bopMacroObj.binary) { bopPrec = bopMacroObj.binary.prec; bopAssoc = bopMacroObj.binary.assoc; } else { bopPrec = getBinaryOpPrec(unwrapSyntax(op)); bopAssoc = getBinaryOpAssoc(unwrapSyntax(op)); } assert(bopPrec !== undefined, 'expecting a precedence for operator: ' + op); var newStack; if (comparePrec(bopPrec, opCtx.prec, bopAssoc)) { var bopCtx = opCtx; var combResult = opCtx.combine(head); if (opCtx.stack.length > 0) { return step(combResult.term, rest, opCtx.stack[0]); } left = combResult.term; newStack = opCtx.stack; opPrevStx = combResult.prevStx; opPrevTerms = combResult.prevTerms; } else { newStack = [opCtx].concat(opCtx.stack); } assert(opCtx.combine !== undefined, 'expecting a combine function'); var opRightStx = rightStx; var bopMacroName; if (isCustomOp) { bopMacroName = rest.slice(0, bopMacroObj.fullName.length); opRightStx = rightStx.slice(bopMacroObj.fullName.length - 1); } var bopOpCtx = _.extend({}, opCtx, { combine: function (right) { if (right.isExpr) { if (isCustomOp && bopMacroObj.binary) { var leftStx = left.destruct(); var rightStx$2 = right.destruct(); var rt$2 = expandMacro(bopMacroName.concat(syn.makeDelim('()', leftStx, leftStx[0]), syn.makeDelim('()', rightStx$2, rightStx$2[0])), context, opCtx, 'binary'); var newt = get_expression(rt$2.result, context); assert(newt.rest.length === 0, 'should never have left over syntax'); return { term: newt.result, prevStx: opPrevStx, prevTerms: opPrevTerms }; } return { term: BinOp.create(left, op, right), prevStx: opPrevStx, prevTerms: opPrevTerms }; } else { return { term: head, prevStx: opPrevStx, prevTerms: opPrevTerms }; } }, prec: bopPrec, op: opTerm, stack: newStack,