From bcffc807b1b049d6091a4e1c676eb49a2fc46f14 Mon Sep 17 00:00:00 2001 From: Bruno Jouhier Date: Sat, 7 Mar 2015 00:41:52 +0100 Subject: [PATCH] issue #254 - removed old transform files and renamed --- lib/callbacks/transform-esprima.js | 2000 -------------------------- lib/callbacks/transform.js | 1290 ++++++++++------- lib/fibers-fast/transform-esprima.js | 378 ----- lib/fibers-fast/transform.js | 229 +-- lib/fibers/transform-esprima.js | 664 --------- lib/fibers/transform.js | 381 ++--- 6 files changed, 1059 insertions(+), 3883 deletions(-) delete mode 100644 lib/callbacks/transform-esprima.js delete mode 100644 lib/fibers-fast/transform-esprima.js delete mode 100644 lib/fibers/transform-esprima.js diff --git a/lib/callbacks/transform-esprima.js b/lib/callbacks/transform-esprima.js deleted file mode 100644 index 518351cf..00000000 --- a/lib/callbacks/transform-esprima.js +++ /dev/null @@ -1,2000 +0,0 @@ -/** - * Copyright (c) 2011 Bruno Jouhier - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ -/// Activate doc gen later !doc -/// -/// # Transformation engine (callback mode) -/// -/// `var transform = require('streamline/lib/callbacks/transform')` -/// -if (typeof exports !== 'undefined') { - var esprima = require('esprima'); - var escodegen = require('escodegen'); -} else { - // see later -}(function(exports) { - "use strict"; - /// * `version = transform.version` - /// current version of the transformation algorithm. - exports.version = require("../version").version + " (callbacks - esprima)"; - var Syntax = esprima.Syntax; - - // ES6 forms that we don't transform yet - // ArrowFunctionExpression = 'ArrowFunctionExpression', - // ClassBody = 'ClassBody', - // ClassDeclaration = 'ClassDeclaration', - // ClassExpression = 'ClassExpression', - // MethodDefinition = 'MethodDefinition', - - // ES5 node types that we don't use: - // CatchClause: catch clause inside TryStatement - // DebuggerStatement: debugger - // EmptyStatement: ; - // ObjectExpression: object initializer - // Property: prop: inside ObjectExpression - // WithStatement - - - function _assert(cond) { - if (!cond) throw new Error("Assertion failed!") - } - - /* - * Utility functions - */ - - function originalLine(options, line, col) { - if (!options.prevMap) return line || 0; - // Work around a bug in CoffeeScript's source maps; column number 0 is faulty. - if (col == null) col = 1000; - var r = options.prevMap.originalPositionFor({ line: line, column: col }).line - return r == null ? line || 0 : r; - } - - function originalCol(options, line, col) { - if (!options.prevMap) return col || 0; - return options.prevMap.originalPositionFor({ line: line, column: col }).column || 0; - } - - function _node(ref, type, init) { - var n = { - _scope: ref && ref._scope, - _async: ref && ref._async, - type: type, - loc: ref && ref.loc, - range: ref && ref.range, - }; - if (Array.isArray(init)) throw new Error("INTERNAL ERROR: children in esprima!"); - if (init) Object.keys(init).forEach(function(k) { - n[k] = init[k]; - }); - return n; - } - - function _isFunction(node) { - return node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression'; - } - - function isDot(node) { - return node.type === 'MemberExpression' && !node.computed; - } - - function isIndex(node) { - return node.type === 'MemberExpression' && node.computed; - } - - function _identifier(name) { - return { - type: 'Identifier', - name: name, - }; - } - - function _declarator(name, init) { - return { - _scope: init && init._scope, - type: 'VariableDeclarator', - id: _identifier(name), - init: init, - }; - } - - function _literal(val) { - return { - type: 'Literal', - value: val, - }; - } - - function _return(node) { - return { - type: 'ReturnStatement', - _scope: node._scope, - argument: node - }; - } - - function _semicolon(node) { - var stmt = _node(node, 'ExpressionStatement'); - stmt.expression = node; - return stmt; - } - - function _safeName(precious, name) { - if (name.substring(0, 2) === '__') while (precious[name]) name += 'A'; - return name; - } - // cosmetic stuff: template logic generates nested blocks. Flatten them. - - function _flatten(node) { - if (node.type == 'BlockStatement' || node.type == 'Program') { - do { - var found = false; - var body = []; - node.body.forEach(function(child) { - if (child._isFunctionReference || (child.type == 'ExpressionStatement' && (child.expression == null || child.expression._isFunction))) return; // eliminate empty statement and dummy function node; - node._async |= child._async; - if (child.type == 'BlockStatement' || child.type == 'Program') { - body = body.concat(child.body); - found = true; - } else body.push(child); - }) - node.body = body; - } - while (found); - } - return node; - } - - // generic helper to traverse parse tree - // if doAll is true, fn is called on every property, otherwise only on sub-nodes - // if clone object is passed, values returned by fn are assigned to clone properties - - function _propagate(node, fn, doAll, clone) { - var result = clone ? clone : node; - for (var prop in node) { - if (node.hasOwnProperty(prop) && prop[0] != '_') { - var child = node[prop]; - if (child != null) { - if (Array.isArray(child)) { - if (clone) result[prop] = (child = [].concat(child)); - var undef = false; - for (var i = 0; i < child.length; i++) { - if (doAll || (child[i] && child[i].type)) { - child[i] = fn(child[i], node); - undef |= typeof child[i] === "undefined" - } - } - if (undef) { - result[prop] = child.filter(function(elt) { - return typeof elt !== "undefined"; - }); - } - } else { - if (doAll || (child && child.type)) result[prop] = fn(child, node); - - } - } else if (child === null) { - result[prop] = null; - } - } - } - return result; - } - - // clones the tree rooted at node. - - function _clone(node) { - var lastId = 0; - var clones = {}; // target property creates cycles - - function cloneOne(child) { - if (!child || !child.type) return child; - var cloneId = child._cloneId; - if (!cloneId) cloneId = (child._cloneId = ++lastId); - var clone = clones[cloneId]; - if (clone) return clone; - clones[cloneId] = (clone = { - _cloneId: cloneId - }); - return _propagate(child, cloneOne, true, clone); - } - - return _propagate(node, cloneOne, true, {}); - } - - /* - * Utility class to generate parse trees from code templates - */ - - function Template(pass, str, isExpression, createScope) { - // parser the function and set the root - var _root = esprima.parse("function _t(){" + str + "}").body[0].body.body; - if (_root.length == 1) _root = _root[0]; - else _root = _node(_root[0], 'BlockStatement', { - body: _root, - }); - // if template is an expression rather than a full statement, go one more step down - //if (isExpression) - // _root = _root.expression; - // generates a parse tree from a template by substituting bindings. - this.generate = function(scopeNode, bindings) { - var scope = scopeNode._scope; - var loc = scopeNode.loc; - _assert(scope != null); - bindings = bindings || {}; - var fn = null; - - function gen(node) { - if (node && typeof node === 'object' && node.type != 'Program' && node.type != 'BlockStatement') node._pass = pass; - if (_isFunction(node) && createScope) { - _assert(fn == null); - fn = node; - } - if (!node || !node.type) { - if (node == "_") return scope.options.callback; - // not a parse node - replace if it is a name that matches a binding - if (typeof node === "string") { - if (node[0] === "$") return bindings[node]; - return _safeName(scope.options.precious, node); - } - return node; - } - node._scope = scope; - // if node is ident; statement ('ExpressionStatement') or ident expression, try to match with binding - var ident = node.type == 'ExpressionStatement' ? node.expression : node; - if (ident && ident.type == 'Identifier' && ident.name[0] === "$") { - return typeof bindings[ident.name] === 'string' ? _identifier(bindings[ident.name]) : bindings[ident.name]; - } else { - // recurse through sub nodes - node = _propagate(node, function(child) { - child = gen(child); - // propagate async flag like analyze phase - if (child && (child._async || (child === scope.options.callback && createScope)) && !_isFunction(node)) node._async = true; - return child; - }, true); - node = _flatten(node); - return node; - } - } - - function _changeScope(node, parent) { - if (_isFunction(node)) return node; - node._scope = scope; - return _propagate(node, _changeScope); - } - - // generate - var result = gen(_clone(_root)); - if (fn) { - // parser drops parenthesized flag (because of return) - fn.parenthesized = true; - var scope = new Scope(fn.body, fn._scope.options); - scope.name = fn._scope.name; - scope.line = fn._scope.line; - scope.last = fn._scope.last; - _assert(fn.params[0].name === fn._scope.options.callback); - scope.cbIndex = 0; - - _propagate(fn, _changeScope); - } - result = isExpression ? result.argument : result; - result.loc = loc; - return result; - } - this.root = isExpression ? _root.argument : _root; // for simplify pass - } - - /* - * Utility to generate names of intermediate variables - */ - - function Scope(script, options) { - //this.script = script; - this.line = 0; - this.last = 0; - this.vars = []; - this.functions = []; - this.options = options; - this.cbIndex = -1; - this.isAsync = function() { - return this.cbIndex >= 0; - } - } - - function _genId(node) { - return _safeName(node._scope.options.precious, "__" + ++node._scope.last); - } - - /* - * Preliminary pass: mark source nodes so we can map line numbers - * Also eliminate _fast_ syntax - */ - function _removeFast(node, options) { - function _isMarker(node) { - return node.type === 'Identifier' && node.name === options.callback; - } - function _isStar(node) { - return node.type === 'CallExpression' && _isMarker(node.callee) && node.arguments.length === 2; - } - // ~_ -> _ - if (node.type === 'UnaryExpression' && node.operator === '~' && _isMarker(node.argument)) { - options.needsTransform = true; - return node.argument; - } - // [_] -> _ (with multiple marker) - if (node.type === 'ArrayExpression' && node.elements.length === 1 && _isMarker(node.elements[0])) { - options.needsTransform = true; - node.elements[0]._returnArray = true; - return node.elements[0]; - } - // _ >> x -> x - if (node.type === 'BinaryExpression' && node.operator === '>>' && _isMarker(node.left)) { - options.needsTransform = true; - return node.right; - } - // _ << x -> x - if (node.type === 'BinaryExpression' && node.operator === '<<' && _isMarker(node.left)) { - options.needsTransform = true; - return node.right; - } - // !_ -> false - if (node.type === 'UnaryExpression' && node.operator === '!' && _isMarker(node.argument)) { - options.needsTransform = true; - node.type = 'Literal'; - node.value = false; - node.raw = "false"; - delete node.argument; - return node; - } - // void _ -> null - if (node.type === 'UnaryExpression' && node.operator === 'void' && _isMarker(node.argument)) { - options.needsTransform = true; - node.type = 'Literal'; - node.value = null; - node.raw = "null"; - delete node.argument; - return node; - } - if (_isStar(node)) { - node._isStar = true; - options.needsTransform = true; - node.callee.name = _safeName(options.precious, "__rt") + ".streamlinify" - return node; - } - return node; - } - - function _markSource(node, options) { - function _markOne(node) { - if (typeof node.name === 'string') options.precious[node.name] = true; - node.params && node.params.forEach(function(param) { - options.precious[param.name] = true; - }); - node._isSourceNode = true; - _propagate(node, function(child) { - child = _removeFast(child, options); - _markOne(child); - return child; - }); - } - - _markOne(node); - } - - /* - * Canonicalization pass: wrap top level script if async - */ - - function _isScriptAsync(script, options) { - var async = false; - - function _doIt(node, parent) { - switch (node.type) { - case 'FunctionDeclaration': - case 'FunctionExpression': - // do not propagate into functions - return node; - case 'Identifier': - if (node.name == options.callback) { - async = true; - } else { // propagate only if async is still false - _propagate(node, _doIt); - } - return node; - case 'CallExpression': - // special hack for coffeescript top level closure - var fn = node.callee, - args = node.arguments, - ident; - if (isDot(fn) && (ident = fn.property).name === "call" // - && (fn = fn.object).type === 'FunctionExpression' && fn.params.length === 0 // - && !fn.id && args.length === 1 // - && args[0].type === 'ThisExpression') { - _propagate(fn.body, _doIt); - return node; - } - // fall through - default: - // do not propagate if async has been found - if (!async) { - _propagate(node, _doIt); - } - return node; - } - } - _propagate(script, _doIt); - if (async && options.verbose) console.log("WARNING: async calls found at top level in " + script.filename); - return async; - } - - var _rootTemplate = new Template("root", - // define as string on one line to get lineno = 1 - "(function main(_){ $script }).call(this, __trap);"); - - function _canonTopLevelScript(script, options) { - script._scope = new Scope(script, options); - if (_isScriptAsync(script, options)) return _rootTemplate.generate(script, { - $script: script - }); - else return script; - } - - /* - * Scope canonicalization pass: - * Set _scope on all nodes - * Set _async on all nodes that contain an async marker - * Move vars and functions to beginning of scope. - * Replace this by __this. - * Set _breaks flag on all statements that end with return, throw or break - */ - var _assignTemplate = new Template("canon", "$lhs = $rhs;"); - - // try to give a meaningful name to an anonymous func - - function _guessName(node, parent) { - function _sanitize(name) { - // replace all invalid chars by '_o_' - name = ('' + name).replace(/[^A-Z0-9_$]/ig, '_o_'); - // add '_o_' prefix if name is empty or starts with a digit - return name && !/^\d/.test(name) ? name : '_o_' + name; - } - var id = _genId(node), - n, nn; - if (parent.type === 'Identifier') return _sanitize(parent.name) + id; - if (parent.type === 'AssignmentExpression') { - n = parent.left; - var s = ""; - while ((isDot(n) && (nn = n.property).type === 'Identifier') || (isIndex(n) && (nn = n.property).type === 'Literal')) { - s = s ? (nn.name || nn.value) + "_" + s : (nn.name || nn.value); - n = n.object; - } - if (n.type === 'Identifier') s = s ? n.name + "_" + s : n.value; - if (s) return _sanitize(s) + id; - } else if (parent.type == 'Property') { - n = parent.key; - if (n.type === 'Identifier' || n.type === 'Literal') return _sanitize(n.name || n.value) + id; - } - return id; - } - - function _canonScopes(node, options) { - function _doIt(node, parent) { - var scope = parent._scope; - node._scope = scope; - var async = scope.isAsync(); - if (!async && !_isFunction(node)) { - if (node.type === 'Identifier' && node.name === options.callback && !parent._isStar) { - throw new Error(node.filename + ": Function contains async calls but does not have _ parameter: " + node.name + " at line " + (node.loc && node.loc.start.line)); - } - return _propagate(node, _doIt); - } - - if (node.type === 'TryStatement') node._async = true; - switch (node.type) { - case 'FunctionDeclaration': - case 'FunctionExpression': - var result = node; - var cbIndex = node.params.reduce(function(index, param, i) { - if (param.name != options.callback) return index; - if (index < 0) return i; - else throw new Error("duplicate _ parameter"); - }, -1); - if (cbIndex >= 0) { - // handle coffeescript fat arrow method definition (issue #141) - if (_isFatArrow(node)) return node; - // handl coffeescript default params (issue #218) - if (_hasDefaultCallback(node, options)) { - // converted function is not async any more - cbIndex = -1; - } else { - // should rename options -> context because transform writes into it. - options.needsTransform = true; - // assign names to anonymous functions (for futures) - if (!node.id) node.id = _identifier(_guessName(node, parent)); - } - } - // if function is a statement, move it away - if (async && (parent.type === 'Program' || parent.type === 'BlockStatement')) { - scope.functions.push(node); - result = undefined; - } - // create new scope for the body - var bodyScope = new Scope(node.body, options); - node.body._scope = bodyScope; - bodyScope.name = node.id && node.id.name; - bodyScope.cbIndex = cbIndex; - bodyScope.line = node.loc && node.loc.start.line; - node.body = _propagate(node.body, _doIt); - // insert declarations at beginning of body - if (cbIndex >= 0) bodyScope.functions.push(_literal("BEGIN_BODY")); // will be removed later - node.body.body = bodyScope.functions.concat(node.body.body); - if (bodyScope.hasThis && !node._inhibitThis) { - bodyScope.vars.push(_declarator(_safeName(options.precious, "__this"), _node(node, 'ThisExpression'))); - } - if (bodyScope.hasArguments && !node._inhibitArguments) { - bodyScope.vars.push(_declarator(_safeName(options.precious, "__arguments"), _identifier("arguments"))); - } - if (bodyScope.vars.length > 0) { - node.body.body.splice(0, 0, _node(node, 'VariableDeclaration', { - kind: 'var', // will see later about preserving const, ... - declarations: bodyScope.vars, - })); - } - // do not set _async flag - return result; - case 'VariableDeclaration': - var declarations = node.declarations.map(function(child) { - if (!scope.vars.some(function(elt) { - return elt.id.name == child.id.name; - })) { - scope.vars.push(_declarator(child.id.name, null)); - } - if (!child.init) return null; - child = _assignTemplate.generate(parent, { - $lhs: _identifier(child.id.name), - $rhs: child.init - }); - if (parent.type === 'ForStatement') child = child.expression; - return child; - }).filter(function(child) { - return child != null; - }); - if (declarations.length == 0) { - // leave variable if `for (var x in y)` - return parent.type === 'ForInStatement' // - ? node.declarations[node.declarations.length - 1].id : undefined; - } - var result; - if (parent.type == 'BlockStatement' || parent.type === 'Program') { - result = _node(parent, 'BlockStatement', { - body: declarations, - }); - } else { - result = _node(parent, 'SequenceExpression', { - expressions: declarations, - }); - } - result = _propagate(result, _doIt); - parent._async |= result._async; - return result; - case 'ThisExpression': - scope.hasThis = true; - return _identifier(_safeName(options.precious, "__this")); - case 'Identifier': - if (node.name === "arguments") { - scope.hasArguments = true; - return _identifier(_safeName(options.precious, "__arguments")); - } - node = _propagate(node, _doIt); - node._async |= node.name === options.callback; - if (node._async && !(parent.arguments) && // func(_) is ok - !(parent.type === 'Property' && node === parent.key) && // { _: 1 } is ok - !(isDot(parent) && node === parent.property)) - throw new Error("invalid usage of '_'") - parent._async |= node._async; - return node; - case 'NewExpression': - var cbIndex = node.arguments.reduce(function(index, arg, i) { - if (arg.type !== 'Identifier' || arg.name !== options.callback) return index; - if (index < 0) return i; - else throw new Error("duplicate _ argument"); - }, -1); - if (cbIndex >= 0) { - var constr = _node(node, 'CallExpression', { - callee: _identifier(_safeName(options.precious, '__construct')), - arguments: [node.callee, _literal(cbIndex)] - }); - node = _node(node, 'CallExpression', { - callee: constr, - arguments: node.arguments - }); - } - node = _propagate(node, _doIt); - parent._async |= node._async; - return node; - case 'CallExpression': - _convertThen(node, options); - _convertCoffeeScriptCalls(node, options); - _convertApply(node, options); - // fall through - default: - if (node.type === 'SwitchCase') { - // wrap consequent into a block, to reuse block logic in subsequent steps - if (node.consequent.length !== 1 || node.consequent[0].type !== 'BlockStatement') { - node.consequent = [_node(node, 'BlockStatement', { - body: node.consequent, - })]; - } - if (node.test == null) parent.hasDefault = true; - } - node = _propagate(node, _doIt); - _setBreaks(node); - parent._async |= node._async; - return node; - } - } - return _propagate(node, _doIt); - } - - function _convertThen(node, options) { - // promise.then(_, _) -> __pthen(promise, _) - var fn = node.callee; - var args = node.arguments; - if (isDot(fn) && args.length === 2 // - && args[0].type === 'Identifier' && args[0].name === options.callback - && args[1].type === 'Identifier' && args[1].name === options.callback) { - node.arguments = [fn.object, _literal(fn.property.name), args[1]]; - fn.type = 'Identifier'; - fn.name = "__pthen"; - } - } - - function _convertCoffeeScriptCalls(node, options) { - // takes care of anonymous functions inserted by - // CoffeeScript compiler - var fn = node.callee; - var args = node.arguments; - if (fn.type === 'FunctionExpression' && fn.params.length === 0 && !fn.id && args.length == 0) { - // (function() { ... })() - // --> (function(_) { ... })(_) - fn._noFuture = true; - fn.id = _identifier("___closure"); - fn.params = [_identifier(options.callback)]; - node.arguments = [_identifier(options.callback)]; - } else if (isDot(fn)) { - var ident = fn.property; - fn = fn.object; - if (fn.type === 'FunctionExpression' && fn.params.length === 0 && !fn.id && ident.type === 'Identifier') { - if (ident.name === "call" && args.length === 1 && args[0].type === 'ThisExpression') { - // (function() { ... }).call(this) - // --> (function(_) { ... })(_) - node.callee = fn; - fn._noFuture = true; - fn.id = _identifier("___closure"); - fn.params = [_identifier(options.callback)]; - node.arguments = [_identifier(options.callback)]; - node._scope.hasThis = true; - fn._inhibitThis = true; - } else if (ident.name === "apply" && args.length === 2 && args[0].type === 'ThisExpression' // - && args[1].type === 'Identifier' && args[1].name === "arguments") { - // (function() { ... }).apply(this, arguments) - // --> (function(_) { ... })(_) - node.callee = fn; - fn._noFuture = true; - fn.id = _identifier("___closure"); - fn.params = [_identifier(options.callback)]; - node.arguments = [_identifier(options.callback)]; - node._scope.hasThis = true; - node._scope.hasArguments = true; - fn._inhibitThis = true; - fn._inhibitArguments = true; - } else if (ident.name === "call" && args.length === 1 && args[0].type === 'Identifier' && args[0].name === '_this') { - // (function() { ... }).call(_this) - // --> (function(_) { ... }).call(_this, _) - fn._noFuture = true; - fn.id = _identifier("___closure"); - fn.params.push(_identifier(options.callback)); - args.push(_identifier(options.callback)); - } - } - } - } - - function _isFatArrow(node) { - //this.method = function(_) { - // return Test.prototype.method.apply(_this, arguments); - //}; - // Params may vary but so we only test body. - if (node.body.body.length !== 1) return false; - var n = node.body.body[0]; - if (n.type !== 'ReturnStatement' || !n.argument) return false; - n = n.argument; - if (n.type !== 'CallExpression') return false; - var args = n.arguments; - var target = n.callee; - if (args.length !== 2 || args[0].name !== '_this' || args[1].name !== 'arguments') return false; - if (!isDot(target) || target.property.name !== 'apply') return false; - target = target.object; - if (!isDot(target)) return false; - target = target.object; - if (!isDot(target) || target.property.name !== 'prototype') return false; - target = target.object; - if (target.type !== 'Identifier') return false; - // Got it. Params are useless so nuke them - node.params = []; - return true; - } - - function _hasDefaultCallback(node, options) { - // function(a, b, _) { - // if (a == null) { a = ... } - // if (_ == null) { _ = ... } - // - // - // becomes - // - // function(a, b, cb) { - // var args = Array.prototype.slice.call(arguments, 0); - // if (a == null) { args[0] = ... } - // if (cb == null) { args[2] = ... } - // (function(a, b, _) { - // - // }).apply(this, args); - var indexes = []; - var paramI = -1; - var skip = 0; - for (var i = 0; i < node.body.body.length; i++) { - var child = node.body. - body[i]; - if (i === 0 && child.type === 'VariableDeclaration') { - skip = 1; - continue; - } - if (child.type !== 'IfStatement') return false; - if (child.test.type !== 'BinaryExpression' && child.test.operator != '==') return false; - var ident = child.test.left; - if (ident.type !== 'Identifier') return false; - if (child.test.right.type !== 'Literal' || child.test.right.value !== null) return false; - if (!child.consequent.body || child.consequent.body.length !== 1) return false; - var assign = child.consequent.body[0]; - if (assign.type !== 'ExpressionStatement') return false; - assign = assign.expression; - if (assign.type !== 'AssignmentExpression') return false; - if (assign.left.type !== 'Identifier') return false; - if (assign.left.name !== ident.name) return false; - // we got a candidate - let us find the param - while (++paramI < node.params.length) { - if (ident.name === node.params[paramI].name) break; - } - if (paramI === node.params.length) return false; - indexes.push(paramI); - if (ident.name === options.callback) { - // got it - var originalParams = node.params.slice(0); - var cb = _safeName(options.precious, 'cb'); - // need to clone args because arguments is not a true array and its length is not bumped - // if we assigned the callback beyond arguments.length - var args = _safeName(options.precious, 'args'); - node.params[paramI] = _identifier(cb); - for (var k = 0; k < indexes.length; k++) { - // chain has been verified above - var ifn = node.body.body[skip + k]; - if (k === indexes.length - 1) ifn.test.left.name = cb; - var lhs = ifn.consequent.body[0].expression.left; - // too lazy to create real tree - fake it with identifier - lhs.name = args + "[" + indexes[k] + "]"; - } - node._async = false; - var remain = node.body.body; - node.body.body = remain.splice(0, paramI); - // ugly hack to insert args initializer - node.body.body.splice(0, 0, _identifier("var " + args + " = Array.prototype.slice.call(arguments, 0);")); - node.body.body.push(_node(node, 'ReturnStatement', { - argument: _node(node, 'CallExpression', { - callee: _node(node, 'MemberExpression', { - object: _node(node, 'FunctionExpression', { - params: originalParams, - body: _node(node, 'BlockStatement', { body: remain }), - parenthesized: true, - }), - property: _identifier("apply"), - }), - arguments: [_identifier("this"), _identifier(args)], - }), - })); - return true; - } - } - // we did not find it - return false; - } - - function _convertApply(node, options) { - // f.apply(this, arguments) -> __apply(_, f, __this, __arguments, cbIndex) - var dot = node.callee; - var args = node.arguments; - if (isDot(dot)) { - var ident = dot.property; - if (ident.type === 'Identifier' && ident.name === "apply" && args.length === 2 // - && args[0].type === 'ThisExpression' && args[1].type === 'Identifier' && args[1].name === "arguments") { - var f = dot.object; - node.callee = _identifier('__apply'); - node.arguments = [_identifier(options.callback), f, _identifier('__this'), _identifier('__arguments'), _literal(node._scope.cbIndex)]; - node._scope.hasThis = true; - node._scope.hasArguments = true; - } - } - } - - var _switchVarTemplate = new Template("canon", "{ var $v = true; }"); - var _switchIfTemplate = new Template("canon", "if ($v) { $block; }"); - - function _setBreaks(node) { - switch (node.type) { - case 'IfStatement': - node._breaks = node.consequent._breaks && node.alternate && node.alternate._breaks; - break; - case 'SwitchStatement': - if (!node.hasDefault && node._async) { - node.cases.push(_node(node, 'SwitchCase', { - consequent: [_node(node, 'BlockStatement', { - body: [_node(node, 'BreakStatement')], - })], - })); - } - for (var i = 0; i < node.cases.length; i++) { - var stmts = node.cases[i]; - if (node._async && stmts.consequent[0].body.length > 0 && !stmts._breaks) { - if (i === node.cases.length - 1) { - stmts.consequent[0].body.push(_node(node, 'BreakStatement')); - stmts._breaks = true; - } else { - // we rewrite: - // case A: no_break_A - // case B: no_break_B - // case C: breaking_C - // - // as: - // case A: var __A = true; - // case B: var __B = true; - // case C: - // if (__A) no_break_A - // if (__B) no_break_B - // breaking_C - var v = _identifier(_genId(node)); - var body = stmts.consequent[0]; - node.cases[i].consequent = [_switchVarTemplate.generate(node.cases[i], { - $v: v, - })]; - var ifStmt = _switchIfTemplate.generate(node.cases[i], { - $v: v, - $block: body, - }); - node.cases[i + 1].consequent[0].body.splice(0, 0, ifStmt); - } - } - } - break; - case 'TryStatement': - node._breaks = node.block._breaks && node.handlers.length && node.handlers[0].body._breaks; - break; - case 'BlockStatement': - case 'Program': - node.body.forEach(function(child) { - node._breaks |= child._breaks; - }); - break; - case 'SwitchCase': - if (node.consequent.length !== 1 || node.consequent[0].type !== 'BlockStatement') throw new Error("internal error: SwitchCase not wrapped: " + node.consequent.length); - node._breaks |= node.consequent[0]._breaks; - break; - case 'ReturnStatement': - case 'ThrowStatement': - case 'BreakStatement': - node._breaks = true; - break; - } - } - - /* - * Flow canonicalization pass: - * Converts all loops to FOR format - * Converts lazy expressions - * Splits try/catch/finally - * Wraps isolated statements into blocks - */ - - function _statementify(exp) { - if (!exp) return exp; - var block = _node(exp, 'BlockStatement', { - body: [] - }); - - function uncomma(node) { - if (node.type === 'SequenceExpression') { - node.expressions.forEach(uncomma); - } else { - block.body.push(node.type == 'ExpressionStatement' ? node : _semicolon(node)); - } - } - uncomma(exp); - return block; - - } - - function _blockify(node) { - if (!node || node.type == 'BlockStatement') return node; - if (node.type == 'SequenceExpression') return _statementify(node); - var block = _node(node, 'BlockStatement', { - body: [node] - }); - block._async = node._async; - return block; - } - - var _flowsTemplates = { - WHILE: new Template("flows", "{" + // - " for (; $test;) {" + // - " $body;" + // - " }" + // - "}"), - - DO: new Template("flows", "{" + // - " var $firstTime = true;" + // - " for (; $firstTime || $test;) {" + // - " $firstTime = false;" + // - " $body;" + // - " }" + // - "}"), - - FOR: new Template("flows", "{" + // - " $init;" + // - " for (; $test; $update) {" + // - " $body;" + // - " }" + // - "}"), - - FOR_IN: new Template("flows", "{" + // - " var $array = __forIn($object);" + // - " var $i = 0;" + // - " for (; $i < $array.length;) {" + // - " $iter = $array[$i++];" + // - " $body;" + // - " }" + // - "}"), - - TRY: new Template("flows", "" + // - "try {" + // - " try { $try; }" + // - " catch ($ex) { $catch; }" + // - "}" + // - "finally { $finally; }"), - - AND: new Template("flows", "" + // - "return (function $name(_){" + // - " var $v = $op1;" + // - " if (!$v) {" + // - " return $v;" + // - " }" + // - " return $op2;" + // - "})(_)", true, true), - - OR: new Template("flows", "" + // - "return (function $name(_){" + // - " var $v = $op1;" + // - " if ($v) {" + // - " return $v;" + // - " }" + // - " return $op2;" + // - "})(_)", true, true), - - HOOK: new Template("flows", "" + // - "return (function $name(_){" + // - " var $v = $test;" + // - " if ($v) {" + // - " return $true;" + // - " }" + // - " return $false;" + // - "})(_);", true, true), - - COMMA: new Template("flows", "" + // - "return (function $name(_){" + // - " $body;" + // - " return $result;" + // - "})(_);", true, true), - - CONDITION: new Template("flows", "" + // - "return (function $name(_){" + // - " return $test;" + // - "})(_);", true, true), - - UPDATE: new Template("flows", "" + // - "return (function $name(_){" + // - " $update;" + // - "})(_);", true, true) - }; - - function _canonFlows(node, options) { - var targets = {}; - function _doIt(node, parent, force) { - var scope = node._scope; - function withTarget(node, label, isLoop, fn) { - label = label || ''; - var breakTarget = targets['break_' + label]; - var continueTarget = targets['continue_' + label]; - targets['break_' + label] = node; - if (isLoop) targets['continue_' + label] = node; - var result = fn(); - targets['break_' + label] = breakTarget; - targets['continue_' + label] = continueTarget; - return result; - } - - function _doAsyncFor(node) { - // extra pass to wrap async test and update - if (node.test && node.test._async && node.test.type !== 'CallExpression') node.test = _flowsTemplates.CONDITION.generate(node, { - $name: "__$" + node._scope.name, - $test: _doIt(node.test, node, true), - }); - if (node.update && node.update._async) node.update = _flowsTemplates.UPDATE.generate(node, { - $name: "__$" + node._scope.name, - $update: _statementify(node.update) - }); - } - if (node.type == 'ForStatement' && node._pass === "flows") _doAsyncFor(node); - if (!scope || !scope.isAsync() || (!force && node._pass === "flows")) return _propagate(node, _doIt); - - switch (node.type) { - case 'IfStatement': - node.consequent = _blockify(node.consequent); - node.alternate = _blockify(node.alternate); - break; - case 'SwitchStatement': - return withTarget(node, null, false, function() { - if (node._async) { - var def = node.cases.filter(function(n) { - return n.test == null - })[0]; - if (!def) { - def = _node(node, 'SwitchCase', { - consequent: [_node(node, 'BlockStatement', { - body: [], - })], - }); - node.cases.push(def); - } - if (!def._breaks) { - def.consequent[0].body.push(_node(node, 'BreakStatement')); - } - } - return _propagate(node, _doIt); - }); - case 'WhileStatement': - node.body = _blockify(node.body); - return withTarget(node, null, true, function() { - if (node._async) { - node = _flowsTemplates.WHILE.generate(node, { - $test: node.test, - $body: node.body - }); - } - return _propagate(node, _doIt); - }); - case 'DoWhileStatement': - node.body = _blockify(node.body); - return withTarget(node, null, true, function() { - if (node._async) { - node = _flowsTemplates.DO.generate(node, { - $firstTime: _identifier(_genId(node)), - $test: node.test, - $body: node.body - }); - } - return _propagate(node, _doIt); - }); - case 'ForStatement': - node.test = node.test || _literal(1); - node.body = _blockify(node.body); - return withTarget(node, null, true, function() { - if (node._async) { - if (node.init) { - node = _flowsTemplates.FOR.generate(node, { - $init: _statementify(node.init), - $test: node.test, - $update: node.update, - $body: node.body - }); - } else { - if (node._pass !== "flows") { - node._pass = "flows"; - _doAsyncFor(node); - } - } - } - return _propagate(node, _doIt); - }); - case 'ForInStatement': - node.body = _blockify(node.body); - return withTarget(node, null, true, function() { - if (node._async) { - if (node.left.type != 'Identifier') { - throw new Error("unsupported 'for ... in' syntax: type=" + node.left.type); - } - node = _flowsTemplates.FOR_IN.generate(node, { - $array: _identifier(_genId(node)), - $i: _identifier(_genId(node)), - $object: node.right, - $iter: node.left, - $body: node.body - }); - } - return _propagate(node, _doIt); - }); - case 'TryStatement': - if (node.block && node.handlers.length && node.finalizer) { - node = _flowsTemplates.TRY.generate(node, { - $try: node.block, - $catch: node.handlers[0].body, - $ex: node.handlers[0].param, - $finally: node.finalizer - }) - } - break; - case 'LogicalExpression': - if (node._async) { - node = _flowsTemplates[node.operator === '&&' ? 'AND' : 'OR'].generate(node, { - $name: "__$" + node._scope.name, - $v: _identifier(_genId(node)), - $op1: node.left, - $op2: node.right, - }); - } - break; - case 'ConditionalExpression': - if (node._async) { - node = _flowsTemplates.HOOK.generate(node, { - $name: "__$" + node._scope.name, - $v: _identifier(_genId(node)), - $test: node.test, - $true: node.consequent, - $false: node.alternate, - }); - } - break; - - case 'SequenceExpression': - if (node._async) { - node = _flowsTemplates.COMMA.generate(node, { - $name: "__$" + node._scope.name, - $body: _node(node, 'BlockStatement', { - body: node.expressions.slice(0, node.expressions.length - 1).map(_semicolon), - }), - $result: node.expressions[node.expressions.length - 1] - }); - } - break; - case 'LabeledStatement': - return withTarget(node, node.label.name, true, function() { - return _propagate(node, _doIt); - }); - case 'BreakStatement': - var target = targets['break_' + (node.label ? node.label.name : '')]; - if (!target) throw new Error("internal error: break target not set"); - node._async = target._async; - break; - case 'ContinueStatement': - var target = targets['continue_' + (node.label ? node.label.name : '')]; - if (!target) throw new Error("internal error: continue target not set"); - node._async = target._async; - break; - } - return _propagate(node, _doIt); - } - return _propagate(node, _doIt); - } - - /* - * Disassembly pass - */ - - function _split(node, prop) { - var exp = node[prop]; - if (!exp || !exp._async) return node; - var id = _genId(node); - var v = _declarator(id, exp); - node[prop] = _identifier(id); - return _node(node, 'BlockStatement', { - body: [_node(node, 'VariableDeclaration', { - kind: 'var', // see later - declarations: [v], - }), node] - }); - } - - function _disassemble(node, options) { - function _disassembleIt(node, parent, noResult) { - if (!node._async) return _propagate(node, _scanIt); - node = _propagate(node, _disassembleIt); - if (node.type === 'CallExpression') { - if (node.callee.type === 'Identifier' && node.callee.name.indexOf('__wrap') == 0) { - node._isWrapper = true; - return node; - } - var args = node.arguments; - if (args.some(function(arg) { - return (arg.type === 'Identifier' && arg.name === options.callback) || arg._isWrapper; - })) { - if (noResult) { - node._scope.disassembly.push(_statementify(node)); - return; - } else { - if (parent.type == 'Identifier' && parent.name.indexOf('__') === 0) { - // don't generate another ID, use the parent one - node._skipDisassembly = true; - return node; - } - var id = _genId(node); - var v = _declarator(id, node); - node = _node(node, 'VariableDeclaration', { - kind: 'var', // fix later - declarations: [v] - }); - node._scope.disassembly.push(node); - return _identifier(id); - } - } - } - return node; - } - - function _scanIt(node, parent) { - var scope = node._scope; - if (!scope || !scope.isAsync() || !node._async) return _propagate(node, _scanIt); - switch (node.type) { - case 'IfStatement': - node = _split(node, "test"); - break; - case 'SwitchStatement': - node = _split(node, "discriminant"); - break; - case 'ForStatement': - break; - case 'ReturnStatement': - node = _split(node, "argument"); - break; - case 'ThrowStatement': - node = _split(node, "argument"); - break; - case 'VariableDeclaration': - _assert(node.declarations.length === 1); - var decl = node.declarations[0]; - scope.disassembly = []; - decl.init = _disassembleIt(decl.init, decl); - node._async = decl.init._skipDisassembly; - scope.disassembly.push(node); - return _node(parent, 'BlockStatement', { - body: scope.disassembly, - }); - case 'ExpressionStatement': - scope.disassembly = []; - node.expression = _disassembleIt(node.expression, node, true); - if (node.expression) { - node._async = false; - scope.disassembly.push(node); - } - return _node(parent, 'BlockStatement', { - body: scope.disassembly, - }); - } - return _propagate(node, _scanIt); - } - return _propagate(node, _scanIt); - - } - - /* - * Transformation pass - introducing callbacks - */ - var _cbTemplates = { - FUNCTION: new Template("cb", "{" + // - " $decls;" + // - " var __frame = { name: $fname, line: $line };" + // - " return __func(_, this, arguments, $fn, $index, __frame, function $name(){" + // - " $body;" + // - " _();" + // - " });" + // - "}"), - - FUNCTION_INTERNAL: new Template("cb", "{ $decls; $body; _(); }"), - - RETURN: new Template("cb", "return _(null, $value);"), - - RETURN_UNDEFINED: new Template("cb", "return _(null);"), - - THROW: new Template("cb", "return _($exception);"), - - IF: new Template("cb", "" + // - "return (function $name(__then){" + // - " if ($test) { $then; __then(); }" + // - " else { $else; __then(); }" + // - "})(function $name(){ $tail; });"), - - SWITCH: new Template("cb", "" + // - "return (function $name(__break){" + // - " $statement;" + // - "})(function $name(){ $tail; });"), - - LABEL: new Template("cb", "" + // - "$statement;" + // - "$tail;"), - - BREAK: new Template("cb", "return __break();"), - - LABELLED_BREAK: new Template("cb", "return $break();"), - - CONTINUE: new Template("cb", "" + // - "while (__more) { __loop(); } __more = true;" + // - "return;"), - - LABELLED_CONTINUE: new Template("cb", "" + // - "while ($more.get()) { $loop(); } $more.set(true);" + // - "return;"), - - LOOP1: new Template("cb", "" + // - "if ($v) {" + // - " $body;" + // - " while (__more) { __loop(); } __more = true;" + // - "}" + // - "else { __break(); }"), - - // LOOP2 is in temp pass so that it gets transformed if update is async - LOOP2: new Template("temp", "var $v = $test; $loop1;"), - - LOOP2_UPDATE: new Template("temp", "" + // - "if ($beenHere) { $update; } else { $beenHere = true; }" + // - "var $v = $test; $loop1;"), - - FOR: new Template("cb", "" + // - "return (function ___(__break){" + // - " var __more;" + // - " var __loop = __cb(_, __frame, 0, 0, function $name(){" + // - " __more = false;" + // - " $loop2" + // - " });" + // - " do { __loop(); } while (__more); __more = true;" + // - "})(function $name(){ $tail;});"), - - LABELLED_FOR: new Template("cb", "" + // - "return (function ___(__break){" + // - " var __more, $more = { get: function() { return __more; }, set: function(v) { __more = v; }};" + // - " var __loop = __cb(_, __frame, 0, 0, function $name(){" + // - " var $break = __break, $loop = __loop;" + // - " __more = false;" + // - " $loop2" + // - " });" + // - " do { __loop(); } while (__more); __more = true;" + // - "})(function $name(){ $tail;});"), - - FOR_UPDATE: new Template("cb", "" + // - "var $beenHere = false;" + // - "return (function ___(__break){" + // - " var __more;" + // - " var __loop = __cb(_, __frame, 0, 0, function $name(){" + // - " __more = false;" + // - " $loop2" + // - " });" + // - " do { __loop(); } while (__more); __more = true;" + // - "})(function $name(){ $tail; });"), - - LABELLED_FOR_UPDATE: new Template("cb", "" + // - "var $beenHere = false;" + // - "return (function ___(__break){" + // - " var __more, $more = { get: function() { return __more; }, set: function(v) { __more = v; }};" + // - " var __loop = __cb(_, __frame, 0, 0, function $name(){" + // - " var $break = __break, $loop = __loop;" + // - " __more = false;" + // - " $loop2" + // - " });" + // - " do { __loop(); } while (__more); __more = true;" + // - "})(function $name(){ $tail; });"), - - CATCH: new Template("cb", "" + // - "return (function ___(__then){" + // - " (function ___(_){" + // - " __tryCatch(_, function $name(){ $try; __then(); });" + // - " })(function ___($ex, __result){" + // - " __catch(function $name(){" + // - " if ($ex) { $catch; __then(); }" + // - " else { _(null, __result); }" + // - " }, _);" + // - " });" + // - "})(function ___(){" + // - " __tryCatch(_, function $name(){ $tail; });" + // - "});"), - - FINALLY: new Template("cb", "" + // - "return (function ___(__then){" + // - " (function ___(_){" + // - " __tryCatch(_, function $name(){ $try; _(null, null, true); });" + // - " })(function ___(__e, __r, __cont){" + // - " (function ___(__then){" + // - " __tryCatch(_, function $name(){ $finally; __then(); });" + // - " })(function ___(){" + // - " __tryCatch(_, function ___(){" + // - " if (__cont) __then(); else _(__e, __r);" + // - " });" + // - " })" + // - " });" + // - "})(function ___(){" + // - " __tryCatch(_, function $name(){ $tail; });" + // - "});"), - - CALL_VOID: new Template("cb", "return __cb(_, __frame, $offset, $col, function $name(){ $tail; }, true, $returnArray)", true), - - CALL_TMP: new Template("cb", "return __cb(_, __frame, $offset, $col, function ___(__0, $result){ $tail }, true, $returnArray)", true), - - CALL_RESULT: new Template("cb", "" + // - "return __cb(_, __frame, $offset, $col, function $name(__0, $v){" + // - " var $result = $v;" + // - " $tail" + // - "}, true, $returnArray)", true) - }; - - function _callbackify(node, options) { - var label; - function _scanIt(node, parent) { - node = _flatten(node); - if (!node._scope || !node._scope.isAsync() || node._pass === "cb") return _propagate(node, _scanIt); - switch (node.type) { - case 'Program': - case 'BlockStatement': - if (node.type === 'Program' || parent.type === 'FunctionExpression' || parent.type === 'FunctionDeclaration') { - if (parent._pass !== "cb") { - // isolate the leading decls from the body because 'use strict' - // do not allow hoisted functions inside try/catch - var decls; - for (var cut = 0; cut < node.body.length; cut++) { - var child = node.body[cut]; - if (child.type === 'Literal' && child.value === "BEGIN_BODY") { - decls = node.body.splice(0, cut); - node.body.splice(0, 1); - break; - } - } - var template = parent._noFuture || parent._pass === "flows" ? _cbTemplates.FUNCTION_INTERNAL : _cbTemplates.FUNCTION; - node = template.generate(node, { - $fn: parent.id.name, - //node._scope.name ? _identifier(node._scope.name) : _node(node, NULL), - $name: "__$" + node._scope.name, - $fname: _literal(parent.id.name), - $line: _literal(originalLine(options, node._scope.line)), - $index: _literal(node._scope.cbIndex), - $decls: _node(node, 'BlockStatement', { - body: decls || [] - }), - $body: node - }); - } - //node.type = 'Program'; - } - // continue with block restructure - for (var i = 0; i < node.body.length; i++) { - node.body[i] = _restructureIt(node, i); - } - return node; - } - return _propagate(node, _scanIt); - } - - function _extractTail(parent, i) { - return _node(parent, 'BlockStatement', { - body: parent.body.splice(i + 1, parent.body.length - i - 1) - }); - } - - function _restructureIt(parent, i) { - var node = parent.body[i]; - if (node._pass === "cb") return _propagate(node, _scanIt); - switch (node.type) { - case 'ReturnStatement': - _extractTail(parent, i); - var template = node.argument ? _cbTemplates.RETURN : _cbTemplates.RETURN_UNDEFINED; - node = template.generate(node, { - $value: node.argument - }); - break; - case 'ThrowStatement': - _extractTail(parent, i); - node = _cbTemplates.THROW.generate(node, { - $exception: node.argument - }); - break; - case 'BreakStatement': - if (!node._async) break; - _extractTail(parent, i); - if (node.label) { - node = _cbTemplates.LABELLED_BREAK.generate(node, { - $break: _safeName(options.precious, '__break__' + node.label.name) - }); - } else { - node = _cbTemplates.BREAK.generate(node, {}); - } - break; - case 'ContinueStatement': - if (!node._async) break; - _extractTail(parent, i); - if (node.label) { - node = _cbTemplates.LABELLED_CONTINUE.generate(node, { - $loop: _safeName(options.precious, '__loop__' + node.label.name), - $more: _safeName(options.precious, '__more__' + node.label.name), - }); - } else { - node = _cbTemplates.CONTINUE.generate(node, {}); - } - break; - case 'TryStatement': - var tail = _extractTail(parent, i); - if (node.handlers.length) { - node = _cbTemplates.CATCH.generate(node, { - $name: "__$" + node._scope.name, - $try: node.block, - $catch: node.handlers[0].body, - $ex: node.handlers[0].param, - $tail: tail - }); - } else { - node = _cbTemplates.FINALLY.generate(node, { - $name: "__$" + node._scope.name, - $try: node.block, - $finally: node.finalizer, - $tail: tail - }); - } - break; - default: - if (node._async) { - var tail = _extractTail(parent, i); - switch (node.type) { - case 'IfStatement': - node = _cbTemplates.IF.generate(node, { - $name: "__$" + node._scope.name, - $test: node.test, - $then: node.consequent, - $else: node.alternate || _node(node, 'BlockStatement', { - body: [] - }), - $tail: tail - }); - break; - case 'SwitchStatement': - node._pass = "cb"; // avoid infinite recursion - node = _cbTemplates.SWITCH.generate(node, { - $name: "__$" + node._scope.name, - $statement: node, - $tail: tail - }); - break; - case 'LabeledStatement': - var l = label; - label = node.label.name; - node = _cbTemplates.LABEL.generate(node, { - $name: "__$" + node._scope.name, - $statement: node.body, - $tail: tail - }); - node = _scanIt(node, parent); - label = l; - return node; - case 'ForStatement': - var v = _identifier(_genId(node)); - var loop1 = _cbTemplates.LOOP1.generate(node, { - $v: v, - $body: node.body, - }); - var update = node.update; - var beenHere = update && _identifier(_genId(node)); - var loop2 = (update ? _cbTemplates.LOOP2_UPDATE : _cbTemplates.LOOP2).generate(node, { - $v: v, - $test: node.test, - $beenHere: beenHere, - $update: _statementify(update), - $loop1: loop1 - }); - node = (update - ? (label ? _cbTemplates.LABELLED_FOR_UPDATE : _cbTemplates.FOR_UPDATE) - : (label ? _cbTemplates.LABELLED_FOR : _cbTemplates.FOR)).generate(node, { - $name: "__$" + node._scope.name, - $loop: _identifier(_safeName(options.precious, '__loop__' + label)), - $break: _identifier(_safeName(options.precious, '__break__' + label)), - $more: _identifier(_safeName(options.precious, '__more__' + label)), - $beenHere: beenHere, - $loop2: loop2, - $tail: tail - - }); - break; - case 'VariableDeclaration': - _assert(node.declarations.length == 1); - var decl = node.declarations[0]; - _assert(decl.type === 'VariableDeclarator'); - var call = decl.init; - decl.init = null; - _assert(call && call.type === 'CallExpression'); - return _restructureCall(call, tail, decl.id.name); - case 'ExpressionStatement': - var call = node.expression; - _assert(call.type === 'CallExpression') - return _restructureCall(call, tail); - default: - throw new Error("internal error: bad node type: " + node.type + ": " + escodegen.generate(node)); - } - } - } - return _scanIt(node, parent); - - function _restructureCall(node, tail, result) { - var args = node.arguments; - - function _cbIndex(args) { - return args.reduce(function(index, arg, i) { - if ((arg.type == 'Identifier' && arg.name === options.callback) || arg._isWrapper) return i; - else return index; - }, -1); - } - var i = _cbIndex(args); - _assert(i >= 0); - var returnArray = args[i]._returnArray; - if (args[i]._isWrapper) { - args = args[i].arguments; - i = _cbIndex(args); - } - // find the appropriate node for this call: - // e.g. for "a.b(_)", find the node "b" - var identifier = node.callee; - while (isDot(identifier)) { - identifier = identifier.property; - } - var bol = options.source.lastIndexOf('\n', identifier.start) + 1; - var col = identifier.start - bol; - args[i] = (result ? result.indexOf('__') === 0 ? _cbTemplates.CALL_TMP : _cbTemplates.CALL_RESULT : _cbTemplates.CALL_VOID).generate(node, { - $v: _genId(node), - $frameName: _literal(node._scope.name), - $offset: _literal(Math.max(originalLine(options, identifier.loc && identifier.loc.start.line, col) - originalLine(options, node._scope.line), 0)), - $col: _literal(originalCol(options, identifier.loc && identifier.loc.start.column, col)), - $name: "__$" + node._scope.name, - $returnArray: _node(node, 'Literal', { - value: !!returnArray, - }), - $result: _identifier(result), - $tail: tail - }); - node = _propagate(node, _scanIt); - - var stmt = _node(node, 'ReturnStatement', { - argument: node - }); - stmt._pass = "cb"; - return stmt; - } - } - return _propagate(node, _scanIt); - } - - /* - * Simplify pass - introducing callbacks - */ - - function _checkUsed(val, used) { - if (typeof val === "string" && val.substring(0, 2) === "__") used[val] = true; - } - - - var _optims = { - function__0$fn: new Template("simplify", "return function ___(__0) { $fn(); }", true).root, - function$return: new Template("simplify", "return function $fn1() { return $fn2(); }", true).root, - function__0$arg1return_null$arg2: new Template("simplify", "return function ___(__0, $arg1) { var $arg2 = $arg3; return _(null, $arg4); }", true).root, - __cb__: new Template("simplify", "return __cb(_, $frameVar, $line, $col, _)", true).root, - __cbt__: new Template("simplify", "return __cb(_, $frameVar, $line, $col, _, true, false)", true).root, - function$fn: new Template("simplify", "return function $fn1() { $fn2(); }", true).root, - closure: new Template("simplify", "return (function ___closure(_){ $body; })(__cb(_,$frameVar,$line,$col,function $fnName(){_();},true,false))", true).root, - safeParam: new Template("simplify", "return (function $fnName($param){ $body; })(function $fnName(){_();})", true).root, - } - - function _simplify(node, options, used) { - if (node._simplified) return node; - node._simplified = true; - // eliminate extra braces on switch cases - if (node.type === 'SwitchCase') { - if (node.consequent.length === 1 && node.consequent[0].type === 'BlockStatement') // - node.consequent = node.consequent[0].body; - } - - _propagate(node, function(child) { - return _simplify(child, options, used) - }); - _checkUsed(node.name, used); - - function _match(prop, v1, v2, result) { - var ignored = ["loc", "range", "raw"]; - if (prop.indexOf('_') == 0 || ignored.indexOf(prop) >= 0) return true; - if (v1 == v2) { - //console.log("MATCHING1: " + v1); - return true; - } - if (v1 == null || v2 == null) { - // ignore difference between null and empty array - if (prop == "body" && v1 && v1.length === 0) return true; - return false; - } - if (Array.isArray(v1)) { - if (v1.length != v2.length) return false; - for (var i = 0; i < v1.length; i++) { - if (!_match(prop, v1[i], v2[i], result)) return false; - } - return true; - } - if (v1.type === 'Identifier' && v1.name[0] === "$" && typeof v2.value === "number") { - //console.log("MATCHING2: " + v1.name + " with " + v2.value); - result[v1.name] = v2.value; - return true; - } - if (typeof v1 == "string" && v1[0] == "$" && typeof v2 == "string") { - //console.log("MATCHING3: " + v1 + " with " + v2); - result[v1] = v2; - return true; - } - if (v1.type) { - var exp; - if (v1.type == 'BlockStatement' && v1.body[0] && (exp = v1.body[0].expression) && typeof exp.name == "string" && exp.name[0] == '$') { - result[exp.name] = v2; - return true; - } - if (v1.type != v2.type) return false; - if (v1.type == 'Identifier' && v1.name == '$') { - result[v1.name] = v2.name; - return true; - } - - for (var prop in v1) { - if (v1.hasOwnProperty(prop)) { - if (!_match(prop, v1[prop], v2[prop], result)) return false; - } - } - return true; - } - return false; - } - - var result = {}; - if (_match("", _optims.function__0$fn, node, result)) return _identifier(result.$fn); - if (_match("", _optims.function$return, node, result) && (result.$fn1 === '___' || result.$fn1.indexOf('__$') === 0) && (result.$fn2 === '__break')) return _identifier(result.$fn2); - if (_match("", _optims.function__0$arg1return_null$arg2, node, result) && result.$arg1 == result.$arg3 && result.$arg2 == result.$arg4) return _identifier("_"); - if (options.optimize && _match("", _optims.__cb__, node, result)) return _identifier("_"); - if (options.optimize && _match("", _optims.__cbt__, node, result)) return _identifier("_"); - if (_match("", _optims.function$fn, node, result) && (result.$fn1 === '___' || result.$fn1.indexOf('__$') === 0) && (result.$fn2 === '__then' || result.$fn2 === '__loop')) return _identifier(result.$fn2); - if (_match("", _optims.closure, node, result)) { node.arguments[0] = _identifier("_"); } - if (_match("", _optims.safeParam, node, result) && (result.$param === '__then' || result.$param === '__break')) node.arguments[0] = _identifier("_"); - _flatten(node); - return node; - } - - function _extend(obj, other) { - for (var i in other) { - obj[i] = other[i]; - } - return obj; - } - - function _cl(obj) { - return _extend({}, obj); - } - - var visit = 0; - function dump(obj) { - function fix(obj) { - if (!obj || typeof obj !== 'object') return '' + obj; - if (obj._visited === visit) return ""; - obj._visited = visit; - if (Array.isArray(obj)) return obj.map(fix); - return Object.keys(obj).filter(function(k) { - return !/^(_|loc)/.test(k) || k === '_async'; - }).reduce(function(r, k) { - r[k] = fix(obj[k]); - return r; - }, {}); - } - visit++; - return JSON.stringify(fix(obj), null, ' '); - } - - function addNewlines(node) { - var line = 1, l = 1; - function doIt(obj, insideBlock) { - if (!obj) return; - if (obj.loc) l = Math.max(obj.loc.start.line, l); - if (obj.type && insideBlock && line < l) { - obj.leadingComments = new Array(l - line).join('#').split('#'); - line = l; - } - if (Array.isArray(obj)) return obj.forEach(function(o) { - doIt(o, insideBlock); - }); - if (!obj.type) return; - var isBlock = obj.type === 'BlockStatement' || obj.type === 'SwitchCase'; - Object.keys(obj).forEach(function(k) { - var v = obj[k]; - doIt(v, isBlock); - }); - } - doIt(node, false); - } - - /// * `transformed = transform.transform(source, options)` - /// Transforms streamline source. - /// The following `options` may be specified: - /// * `sourceName` identifies source (stack traces, transformation errors) - /// * `lines` controls line mapping - // Undocumented options: - // * (obsolete) `callback` alternative identifier if `_` is already used - // * (internal) `noHelpers` disables generation of helper functions (`__cb`, etc.) - // * (internal) `optimize` optimizes transform (but misses stack frames) - exports.transform = function(source, options) { - try { - source = source.replace(/\r\n/g, "\n"); - options = options ? _extend({}, options) : {}; // clone to isolate options set at file level - var sourceOptions = /streamline\.options\s*=\s*(\{.*\})/.exec(source); - if (sourceOptions) { - _extend(options, JSON.parse(sourceOptions[1])); - } - options.source = source; - options.callback = options.callback || "_"; - options.lines = options.lines || "preserve"; - options.precious = {}; // identifiers found inside source - //console.log("TRANSFORMING " + options.sourceName) - //console.log("source=" + source); - // esprima does not like return at top level so we wrap into a function - // also \n is needed before } in case last line ends on a // comment - source = "function dummy(){" + source + "\n}"; - var node = esprima.parse(source, { - loc: true, - range: true, - }); - node = node.body[0].body; - if (node.type !== 'BlockStatement') throw new Error("source wrapper error: " + node.type); - //console.log(JSON.stringify(node, null, ' ')); - var strict = node.body[0] && node.body[0].expression && node.body[0].expression.value == "use strict"; - strict && node.body.splice(0, 1); - _markSource(node, options); - //console.log("tree=" + node); - node = _canonTopLevelScript(node, options); - //console.log("CANONTOPLEVEL=" + escodegen.generate(node)); - node = _canonScopes(node, options); - //console.log("CANONSCOPES=" + escodegen.generate(node)); - if (!options.needsTransform) return options.source; // original source! - node = _canonFlows(node, options); - //console.log("CANONFLOWS=" + escodegen.generate(node)); - node = _disassemble(node, options); - //console.log("DISASSEMBLE=" + escodegen.generate(node)) - node = _callbackify(node, options); - //console.error(dump(node)); - //console.log("CALLBACKIFY=" + escodegen.generate(node)) - var used = {}; - node = _simplify(node, options, used); - //fixRanges(node); - addNewlines(node); - var result = escodegen.generate(node, { - comment: true, - }); - // remove curly braces around generated source - result = result[0] === '{' ? result.substring(1, result.length - 1) : result; - // turn comments into newlines - //result = result.replace(/\n\s*/g, ' ').replace(/\/\*undefined\*\//g, '\n'); - result = result.split(/\/\*undefined\*\/\n/).map(function(s) { - return s.replace(/\n\s*/g, ' '); - }).join('\n'); - - // add helpers at beginning so that __g is initialized before any other code - if (!options.noHelpers) { - var s = exports.helpersSource(options, used, strict); - if (options.lines == "sourcemap") { - result.prepend(s); - } else { - result = s + result; - } - } - //console.log("result=" + result); - //console.log("TRANSFORMED " + options.sourceName + ": " + result.length) - return result; - } catch (err) { - var message = "error streamlining " + (options.sourceName || 'source') + ": " + err.message; - if (err.source && err.cursor) { - var line = 1; - for (var i = 0; i < err.cursor; i++) { - if (err.source[i] === "\n") line += 1; - } - message += " on line " + line; - } else if (err.stack) { - message += "\nSTACK:\n" + err.stack; - } - throw new Error(message); - } - } - // hack to fix #123 - exports.transform.version = exports.version; - - function _trim(fn) { - return fn.toString().replace(/\s+/g, " "); - } - - function include(mod, modules) { - var source = modules + "['" + mod + "']=(mod={exports:{}});"; - source += "(function(module, exports){"; - var req = require; // prevents client side require from getting fs as a dependency - source += req('fs').readFileSync(__dirname + '/../' + mod + '.js', 'utf8').replace(/(\/\/[^"\n]*\n|\/\*[\s\S]*?\*\/|\n)[ \t]*/g, ""); - source += "})(mod, mod.exports);"; - return source; - } - - function requireRuntime(options) { - var req = "require"; - if (!options.standalone) return req + "('" + (options.internal ? '..' : 'streamline/lib') + "/callbacks/runtime').runtime(__filename, " + !!options.oldStyleFutures + ")"; - var modules = _safeName(options.precious, "__modules"); - var s = "(function(){var " + modules + "={},mod;"; - s += "function require(p){var m=" + modules + "[p.substring(3)]; return m && m.exports};"; - s += include('globals', modules); - s += include('util/future', modules); - s += include('callbacks/runtime', modules); - if (['funnel', 'forEach_', 'map_', 'filter_', 'every_', 'some_', 'reduce_', 'reduceRight_', 'sort_', 'apply_'].some(function(name) { - return options.precious[name]; - })) s += include('callbacks/builtins', modules); - s += "return " + modules + "['callbacks/runtime'].exports.runtime('" + options.sourceName + "', " + !!options.oldStyleFutures + ");"; - s += "})()"; - return s; - } - - // Undocumented (internal) - exports.helpersSource = function(options, used, strict) { - var srcName = "" + options.sourceName; // + "_.js"; - var i = srcName.indexOf('node_modules/'); - if (i == -1 && typeof process === 'object' && typeof process.cwd === 'function') i = process.cwd().length; - srcName = i >= 0 ? srcName.substring(i + 13) : srcName; - var sep = options.lines == "preserve" ? " " : "\n"; - strict = strict ? '"use strict";' + sep : ""; - var s = sep + strict; - var keys = ['__g', '__func', '__cb', '__future', '__propagate', '__trap', '__catch', '__tryCatch', '__forIn', '__apply', '__construct', '__setEF', '__pthen']; - var __rt = _safeName(options.precious, "__rt"); - s += "var " + __rt + "=" + requireRuntime(options); - keys.forEach(function(key) { - var k = _safeName(options.precious, key); - if (used[k]) s += "," + k + "=" + __rt + "." + key; - }); - s += ";" + sep; - if (options.promise) { - var arg = typeof options.promise === "string" ? "'" + options.promise + "'" : "true"; - s += _safeName(options.precious, "__rt.__g") + ".setPromise(" + arg + ");" + sep; - } - return s; - } -})(typeof exports !== 'undefined' ? exports : (window.Streamline = window.Streamline || {})); diff --git a/lib/callbacks/transform.js b/lib/callbacks/transform.js index 8bd63398..518351cf 100644 --- a/lib/callbacks/transform.js +++ b/lib/callbacks/transform.js @@ -1,4 +1,3 @@ -module.exports = require('./transform-esprima'); /** * Copyright (c) 2011 Bruno Jouhier * @@ -23,103 +22,121 @@ module.exports = require('./transform-esprima'); * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ -/// !doc +/// Activate doc gen later !doc /// /// # Transformation engine (callback mode) /// /// `var transform = require('streamline/lib/callbacks/transform')` /// if (typeof exports !== 'undefined') { - var Narcissus = require('../../deps/narcissus'); - var format = require('./format').format; + var esprima = require('esprima'); + var escodegen = require('escodegen'); } else { - var format = Streamline.format; + // see later }(function(exports) { - //"use strict"; + "use strict"; /// * `version = transform.version` /// current version of the transformation algorithm. - exports.version = require("../version").version + " (callbacks)"; + exports.version = require("../version").version + " (callbacks - esprima)"; + var Syntax = esprima.Syntax; - var parse = Narcissus.parser.parse; - var pp = Narcissus.decompiler.pp; - var definitions = Narcissus.definitions; + // ES6 forms that we don't transform yet + // ArrowFunctionExpression = 'ArrowFunctionExpression', + // ClassBody = 'ClassBody', + // ClassDeclaration = 'ClassDeclaration', + // ClassExpression = 'ClassExpression', + // MethodDefinition = 'MethodDefinition', + + // ES5 node types that we don't use: + // CatchClause: catch clause inside TryStatement + // DebuggerStatement: debugger + // EmptyStatement: ; + // ObjectExpression: object initializer + // Property: prop: inside ObjectExpression + // WithStatement - eval(definitions.consts.replace(/const /g, "var ")); function _assert(cond) { if (!cond) throw new Error("Assertion failed!") } - function _tag(node) { - if (!node || !node.type) return "*NOT_A_NODE*"; - var t = definitions.tokens[node.type]; - return /^\W/.test(t) ? definitions.opTypeNames[t] : t.toUpperCase(); - } - /* * Utility functions */ function originalLine(options, line, col) { - if (!options.prevMap) return line; + if (!options.prevMap) return line || 0; // Work around a bug in CoffeeScript's source maps; column number 0 is faulty. if (col == null) col = 1000; var r = options.prevMap.originalPositionFor({ line: line, column: col }).line - return r == null ? line : r; + return r == null ? line || 0 : r; } function originalCol(options, line, col) { - if (!options.prevMap) return col; + if (!options.prevMap) return col || 0; return options.prevMap.originalPositionFor({ line: line, column: col }).column || 0; } - function _node(ref, type, children) { + function _node(ref, type, init) { var n = { _scope: ref && ref._scope, _async: ref && ref._async, type: type, + loc: ref && ref.loc, + range: ref && ref.range, }; - if (Array.isArray(children)) n.children = children; - else if (children) Object.keys(children).forEach(function(k) { - n[k] = children[k]; + if (Array.isArray(init)) throw new Error("INTERNAL ERROR: children in esprima!"); + if (init) Object.keys(init).forEach(function(k) { + n[k] = init[k]; }); return n; } - function _identifier(name, initializer) { + function _isFunction(node) { + return node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression'; + } + + function isDot(node) { + return node.type === 'MemberExpression' && !node.computed; + } + + function isIndex(node) { + return node.type === 'MemberExpression' && node.computed; + } + + function _identifier(name) { return { - _scope: initializer && initializer._scope, - type: IDENTIFIER, + type: 'Identifier', name: name, - value: name, - initializer: initializer }; } - function _number(val) { + function _declarator(name, init) { return { - type: NUMBER, - value: val + _scope: init && init._scope, + type: 'VariableDeclarator', + id: _identifier(name), + init: init, }; } - function _string(val) { + function _literal(val) { return { - type: STRING, - value: val + type: 'Literal', + value: val, }; } function _return(node) { return { - type: RETURN, + type: 'ReturnStatement', _scope: node._scope, - value: node + argument: node }; } function _semicolon(node) { - var stmt = _node(node, SEMICOLON); + var stmt = _node(node, 'ExpressionStatement'); stmt.expression = node; return stmt; } @@ -131,19 +148,19 @@ if (typeof exports !== 'undefined') { // cosmetic stuff: template logic generates nested blocks. Flatten them. function _flatten(node) { - if (node.type == BLOCK || node.type == SCRIPT) { + if (node.type == 'BlockStatement' || node.type == 'Program') { do { var found = false; - var children = []; - node.children.forEach(function(child) { - if (child._isFunctionReference || (child.type == SEMICOLON && (child.expression == null || child.expression._isFunction))) return; // eliminate empty statement and dummy function node; + var body = []; + node.body.forEach(function(child) { + if (child._isFunctionReference || (child.type == 'ExpressionStatement' && (child.expression == null || child.expression._isFunction))) return; // eliminate empty statement and dummy function node; node._async |= child._async; - if (child.type == BLOCK || child.type == SCRIPT) { - children = children.concat(child.children); + if (child.type == 'BlockStatement' || child.type == 'Program') { + body = body.concat(child.body); found = true; - } else children.push(child); + } else body.push(child); }) - node.children = children; + node.body = body; } while (found); } @@ -157,9 +174,7 @@ if (typeof exports !== 'undefined') { function _propagate(node, fn, doAll, clone) { var result = clone ? clone : node; for (var prop in node) { - // funDecls and expDecls are aliases to children - // target property creates loop (see Node.prototype.toString) - if (node.hasOwnProperty(prop) && prop.indexOf("Decls") < 0 && (doAll || prop != 'target') && prop[0] != '_') { + if (node.hasOwnProperty(prop) && prop[0] != '_') { var child = node[prop]; if (child != null) { if (Array.isArray(child)) { @@ -180,6 +195,8 @@ if (typeof exports !== 'undefined') { if (doAll || (child && child.type)) result[prop] = fn(child, node); } + } else if (child === null) { + result[prop] = null; } } } @@ -213,23 +230,25 @@ if (typeof exports !== 'undefined') { function Template(pass, str, isExpression, createScope) { // parser the function and set the root - var _root = parse("function _t(){" + str + "}").children[0].body; - if (_root.children.length == 1) _root = _root.children[0]; - else _root = _node(_root.children[0], BLOCK, _root.children); - + var _root = esprima.parse("function _t(){" + str + "}").body[0].body.body; + if (_root.length == 1) _root = _root[0]; + else _root = _node(_root[0], 'BlockStatement', { + body: _root, + }); // if template is an expression rather than a full statement, go one more step down //if (isExpression) // _root = _root.expression; // generates a parse tree from a template by substituting bindings. this.generate = function(scopeNode, bindings) { var scope = scopeNode._scope; + var loc = scopeNode.loc; _assert(scope != null); bindings = bindings || {}; var fn = null; function gen(node) { - if (node.type != SCRIPT && node.type != BLOCK) node._pass = pass; - if (node.type == FUNCTION && createScope) { + if (node && typeof node === 'object' && node.type != 'Program' && node.type != 'BlockStatement') node._pass = pass; + if (_isFunction(node) && createScope) { _assert(fn == null); fn = node; } @@ -243,22 +262,16 @@ if (typeof exports !== 'undefined') { return node; } node._scope = scope; - // if node is ident; statement (SEMICOLON) or ident expression, try to match with binding - var ident = node.type == SEMICOLON ? node.expression : node; - if (ident && ident.type == IDENTIFIER && ident.value[0] === "$") { - var result = bindings[ident.value]; - // transfer initializer if there is one - if (ident.initializer) { - result.initializer = gen(ident.initializer); - if (result.initializer._async) result._async = true; - } - return result; + // if node is ident; statement ('ExpressionStatement') or ident expression, try to match with binding + var ident = node.type == 'ExpressionStatement' ? node.expression : node; + if (ident && ident.type == 'Identifier' && ident.name[0] === "$") { + return typeof bindings[ident.name] === 'string' ? _identifier(bindings[ident.name]) : bindings[ident.name]; } else { // recurse through sub nodes node = _propagate(node, function(child) { child = gen(child); // propagate async flag like analyze phase - if (child && (child._async || (child === scope.options.callback && createScope)) && node.type !== FUNCTION) node._async = true; + if (child && (child._async || (child === scope.options.callback && createScope)) && !_isFunction(node)) node._async = true; return child; }, true); node = _flatten(node); @@ -266,6 +279,12 @@ if (typeof exports !== 'undefined') { } } + function _changeScope(node, parent) { + if (_isFunction(node)) return node; + node._scope = scope; + return _propagate(node, _changeScope); + } + // generate var result = gen(_clone(_root)); if (fn) { @@ -275,19 +294,16 @@ if (typeof exports !== 'undefined') { scope.name = fn._scope.name; scope.line = fn._scope.line; scope.last = fn._scope.last; - _assert(fn.params[0] === fn._scope.options.callback); + _assert(fn.params[0].name === fn._scope.options.callback); scope.cbIndex = 0; - function _changeScope(node, parent) { - if (node.type == FUNCTION) return node; - node._scope = scope; - return _propagate(node, _changeScope); - } _propagate(fn, _changeScope); } - return isExpression ? result.value : result; + result = isExpression ? result.argument : result; + result.loc = loc; + return result; } - this.root = isExpression ? _root.value : _root; // for simplify pass + this.root = isExpression ? _root.argument : _root; // for simplify pass } /* @@ -295,7 +311,7 @@ if (typeof exports !== 'undefined') { */ function Scope(script, options) { - this.script = script; + //this.script = script; this.line = 0; this.last = 0; this.vars = []; @@ -317,50 +333,54 @@ if (typeof exports !== 'undefined') { */ function _removeFast(node, options) { function _isMarker(node) { - return node.type === IDENTIFIER && node.value === options.callback; + return node.type === 'Identifier' && node.name === options.callback; } function _isStar(node) { - return node.type === CALL && _isMarker(node.children[0]) && node.children[1].children.length === 2; + return node.type === 'CallExpression' && _isMarker(node.callee) && node.arguments.length === 2; } // ~_ -> _ - if (node.type === BITWISE_NOT && _isMarker(node.children[0])) { + if (node.type === 'UnaryExpression' && node.operator === '~' && _isMarker(node.argument)) { options.needsTransform = true; - return node.children[0]; + return node.argument; } // [_] -> _ (with multiple marker) - if (node.type === ARRAY_INIT && node.children.length === 1 && _isMarker(node.children[0])) { + if (node.type === 'ArrayExpression' && node.elements.length === 1 && _isMarker(node.elements[0])) { options.needsTransform = true; - node.children[0]._returnArray = true; - return node.children[0]; + node.elements[0]._returnArray = true; + return node.elements[0]; } // _ >> x -> x - if (node.type === RSH && _isMarker(node.children[0])) { + if (node.type === 'BinaryExpression' && node.operator === '>>' && _isMarker(node.left)) { options.needsTransform = true; - return node.children[1]; + return node.right; } // _ << x -> x - if (node.type === LSH && _isMarker(node.children[0])) { + if (node.type === 'BinaryExpression' && node.operator === '<<' && _isMarker(node.left)) { options.needsTransform = true; - return node.children[1]; + return node.right; } // !_ -> false - if (node.type === NOT && _isMarker(node.children[0])) { + if (node.type === 'UnaryExpression' && node.operator === '!' && _isMarker(node.argument)) { options.needsTransform = true; - node.type = FALSE; - node.children = []; + node.type = 'Literal'; + node.value = false; + node.raw = "false"; + delete node.argument; return node; } // void _ -> null - if (node.type === VOID && _isMarker(node.children[0])) { + if (node.type === 'UnaryExpression' && node.operator === 'void' && _isMarker(node.argument)) { options.needsTransform = true; - node.type = NULL; - node.children = []; + node.type = 'Literal'; + node.value = null; + node.raw = "null"; + delete node.argument; return node; } if (_isStar(node)) { node._isStar = true; options.needsTransform = true; - node.children[0].value = _safeName(options.precious, "__rt") + ".streamlinify" + node.callee.name = _safeName(options.precious, "__rt") + ".streamlinify" return node; } return node; @@ -368,9 +388,9 @@ if (typeof exports !== 'undefined') { function _markSource(node, options) { function _markOne(node) { - if (typeof node.value === 'string') options.precious[node.value] = true; + if (typeof node.name === 'string') options.precious[node.name] = true; node.params && node.params.forEach(function(param) { - options.precious[param] = true; + options.precious[param.name] = true; }); node._isSourceNode = true; _propagate(node, function(child) { @@ -392,22 +412,26 @@ if (typeof exports !== 'undefined') { function _doIt(node, parent) { switch (node.type) { - case FUNCTION: + case 'FunctionDeclaration': + case 'FunctionExpression': // do not propagate into functions return node; - case IDENTIFIER: - if (node.value == options.callback) { + case 'Identifier': + if (node.name == options.callback) { async = true; } else { // propagate only if async is still false _propagate(node, _doIt); } return node; - case CALL: + case 'CallExpression': // special hack for coffeescript top level closure - var fn = node.children[0], - args = node.children[1], + var fn = node.callee, + args = node.arguments, ident; - if (fn.type === DOT && (ident = fn.children[1]).value === "call" && (fn = fn.children[0]).type === FUNCTION && fn.params.length === 0 && !fn.name && args.children.length === 1 && args.children[0].type === THIS) { + if (isDot(fn) && (ident = fn.property).name === "call" // + && (fn = fn.object).type === 'FunctionExpression' && fn.params.length === 0 // + && !fn.id && args.length === 1 // + && args[0].type === 'ThisExpression') { _propagate(fn.body, _doIt); return node; } @@ -452,25 +476,25 @@ if (typeof exports !== 'undefined') { function _guessName(node, parent) { function _sanitize(name) { // replace all invalid chars by '_o_' - name = name.replace(/[^A-Z0-9_$]/ig, '_o_'); + name = ('' + name).replace(/[^A-Z0-9_$]/ig, '_o_'); // add '_o_' prefix if name is empty or starts with a digit return name && !/^\d/.test(name) ? name : '_o_' + name; } var id = _genId(node), n, nn; - if (parent.type === IDENTIFIER) return _sanitize(parent.value) + id; - if (parent.type === ASSIGN) { - n = parent.children[0]; + if (parent.type === 'Identifier') return _sanitize(parent.name) + id; + if (parent.type === 'AssignmentExpression') { + n = parent.left; var s = ""; - while ((n.type === DOT && (nn = n.children[1]).type === IDENTIFIER) || (n.type === INDEX && (nn = n.children[1]).type === STRING)) { - s = s ? nn.value + "_" + s : nn.value; - n = n.children[0]; + while ((isDot(n) && (nn = n.property).type === 'Identifier') || (isIndex(n) && (nn = n.property).type === 'Literal')) { + s = s ? (nn.name || nn.value) + "_" + s : (nn.name || nn.value); + n = n.object; } - if (n.type === IDENTIFIER) s = s ? n.value + "_" + s : n.value; + if (n.type === 'Identifier') s = s ? n.name + "_" + s : n.value; if (s) return _sanitize(s) + id; - } else if (parent.type == PROPERTY_INIT) { - n = parent.children[0]; - if (n.type === IDENTIFIER || n.type === STRING) return _sanitize(n.value) + id; + } else if (parent.type == 'Property') { + n = parent.key; + if (n.type === 'Identifier' || n.type === 'Literal') return _sanitize(n.name || n.value) + id; } return id; } @@ -480,19 +504,20 @@ if (typeof exports !== 'undefined') { var scope = parent._scope; node._scope = scope; var async = scope.isAsync(); - if (!async && node.type !== FUNCTION) { - if (node.type === IDENTIFIER && node.value === options.callback && !parent._isStar) { - throw new Error(node.filename + ": Function contains async calls but does not have _ parameter: " + node.name + " at line " + node.lineno); + if (!async && !_isFunction(node)) { + if (node.type === 'Identifier' && node.name === options.callback && !parent._isStar) { + throw new Error(node.filename + ": Function contains async calls but does not have _ parameter: " + node.name + " at line " + (node.loc && node.loc.start.line)); } return _propagate(node, _doIt); } - if (node.type === TRY) node._async = true; + if (node.type === 'TryStatement') node._async = true; switch (node.type) { - case FUNCTION: + case 'FunctionDeclaration': + case 'FunctionExpression': var result = node; var cbIndex = node.params.reduce(function(index, param, i) { - if (param != options.callback) return index; + if (param.name != options.callback) return index; if (index < 0) return i; else throw new Error("duplicate _ parameter"); }, -1); @@ -507,100 +532,123 @@ if (typeof exports !== 'undefined') { // should rename options -> context because transform writes into it. options.needsTransform = true; // assign names to anonymous functions (for futures) - if (!node.name) node.name = _guessName(node, parent); + if (!node.id) node.id = _identifier(_guessName(node, parent)); } } // if function is a statement, move it away - if (async && (parent.type === SCRIPT || parent.type === BLOCK)) { + if (async && (parent.type === 'Program' || parent.type === 'BlockStatement')) { scope.functions.push(node); result = undefined; } // create new scope for the body var bodyScope = new Scope(node.body, options); node.body._scope = bodyScope; - bodyScope.name = node.name; + bodyScope.name = node.id && node.id.name; bodyScope.cbIndex = cbIndex; - bodyScope.line = node.lineno; + bodyScope.line = node.loc && node.loc.start.line; node.body = _propagate(node.body, _doIt); // insert declarations at beginning of body - if (cbIndex >= 0) bodyScope.functions.push(_string("BEGIN_BODY")); // will be removed later - node.body.children = bodyScope.functions.concat(node.body.children); + if (cbIndex >= 0) bodyScope.functions.push(_literal("BEGIN_BODY")); // will be removed later + node.body.body = bodyScope.functions.concat(node.body.body); if (bodyScope.hasThis && !node._inhibitThis) { - bodyScope.vars.push(_identifier(_safeName(options.precious, "__this"), _node(node, THIS))); + bodyScope.vars.push(_declarator(_safeName(options.precious, "__this"), _node(node, 'ThisExpression'))); } if (bodyScope.hasArguments && !node._inhibitArguments) { - bodyScope.vars.push(_identifier(_safeName(options.precious, "__arguments"), _identifier("arguments"))); + bodyScope.vars.push(_declarator(_safeName(options.precious, "__arguments"), _identifier("arguments"))); } if (bodyScope.vars.length > 0) { - node.body.children.splice(0, 0, _node(node, VAR, bodyScope.vars)); + node.body.body.splice(0, 0, _node(node, 'VariableDeclaration', { + kind: 'var', // will see later about preserving const, ... + declarations: bodyScope.vars, + })); } // do not set _async flag return result; - case VAR: - var children = node.children.map(function(child) { + case 'VariableDeclaration': + var declarations = node.declarations.map(function(child) { if (!scope.vars.some(function(elt) { - return elt.value == child.value; + return elt.id.name == child.id.name; })) { - scope.vars.push(_identifier(child.value)); + scope.vars.push(_declarator(child.id.name, null)); } - if (!child.initializer) return null; + if (!child.init) return null; child = _assignTemplate.generate(parent, { - $lhs: _identifier(child.value), - $rhs: child.initializer + $lhs: _identifier(child.id.name), + $rhs: child.init }); - if (parent.type === FOR) child = child.expression; + if (parent.type === 'ForStatement') child = child.expression; return child; }).filter(function(child) { return child != null; }); - if (children.length == 0) { - return; + if (declarations.length == 0) { + // leave variable if `for (var x in y)` + return parent.type === 'ForInStatement' // + ? node.declarations[node.declarations.length - 1].id : undefined; + } + var result; + if (parent.type == 'BlockStatement' || parent.type === 'Program') { + result = _node(parent, 'BlockStatement', { + body: declarations, + }); + } else { + result = _node(parent, 'SequenceExpression', { + expressions: declarations, + }); } - var type = parent.type == BLOCK || parent.type === SCRIPT ? BLOCK : COMMA; - var result = _node(parent, type, children); result = _propagate(result, _doIt); parent._async |= result._async; return result; - case THIS: + case 'ThisExpression': scope.hasThis = true; return _identifier(_safeName(options.precious, "__this")); - case IDENTIFIER: - if (node.value === "arguments") { + case 'Identifier': + if (node.name === "arguments") { scope.hasArguments = true; - //if (!options.ninja) throw new Error("To use 'arguments' inside streamlined function, read the doc and set the 'ninja' option"); return _identifier(_safeName(options.precious, "__arguments")); } node = _propagate(node, _doIt); - node._async |= node.value === options.callback; - if (node._async && !parent.isArgsList && // func(_) is ok - !(parent.type === PROPERTY_INIT && node === parent.children[0]) && // { _: 1 } is ok - !(parent.type === DOT && node === parent.children[1])) + node._async |= node.name === options.callback; + if (node._async && !(parent.arguments) && // func(_) is ok + !(parent.type === 'Property' && node === parent.key) && // { _: 1 } is ok + !(isDot(parent) && node === parent.property)) throw new Error("invalid usage of '_'") parent._async |= node._async; return node; - case NEW_WITH_ARGS: - var cbIndex = node.children[1].children.reduce(function(index, arg, i) { - if (arg.type !== IDENTIFIER || arg.value !== options.callback) return index; + case 'NewExpression': + var cbIndex = node.arguments.reduce(function(index, arg, i) { + if (arg.type !== 'Identifier' || arg.name !== options.callback) return index; if (index < 0) return i; else throw new Error("duplicate _ argument"); }, -1); if (cbIndex >= 0) { - var constr = _node(node, CALL, [_identifier(_safeName(options.precious, '__construct')), _node(node, LIST, [node.children[0], _number(cbIndex)])]); - node = _node(node, CALL, [constr, node.children[1]]); + var constr = _node(node, 'CallExpression', { + callee: _identifier(_safeName(options.precious, '__construct')), + arguments: [node.callee, _literal(cbIndex)] + }); + node = _node(node, 'CallExpression', { + callee: constr, + arguments: node.arguments + }); } - node.children[1].isArgsList = true; node = _propagate(node, _doIt); parent._async |= node._async; return node; - case CALL: - node.children[1].isArgsList = true; + case 'CallExpression': _convertThen(node, options); _convertCoffeeScriptCalls(node, options); _convertApply(node, options); - node.children[1].isArgsList = true; // fall through default: - // todo: set breaks flag + if (node.type === 'SwitchCase') { + // wrap consequent into a block, to reuse block logic in subsequent steps + if (node.consequent.length !== 1 || node.consequent[0].type !== 'BlockStatement') { + node.consequent = [_node(node, 'BlockStatement', { + body: node.consequent, + })]; + } + if (node.test == null) parent.hasDefault = true; + } node = _propagate(node, _doIt); _setBreaks(node); parent._async |= node._async; @@ -612,62 +660,63 @@ if (typeof exports !== 'undefined') { function _convertThen(node, options) { // promise.then(_, _) -> __pthen(promise, _) - var fn = node.children[0]; - var args = node.children[1]; - if (fn.type === DOT && args.children.length === 2 // - && args.children[0].type === IDENTIFIER && args.children[0].value === options.callback - && args.children[1].type === IDENTIFIER && args.children[1].value === options.callback) { - args.children = [fn.children[0], _string(fn.children[1].value), args.children[1]]; - fn.type = IDENTIFIER; - fn.value = "__pthen"; + var fn = node.callee; + var args = node.arguments; + if (isDot(fn) && args.length === 2 // + && args[0].type === 'Identifier' && args[0].name === options.callback + && args[1].type === 'Identifier' && args[1].name === options.callback) { + node.arguments = [fn.object, _literal(fn.property.name), args[1]]; + fn.type = 'Identifier'; + fn.name = "__pthen"; } } function _convertCoffeeScriptCalls(node, options) { // takes care of anonymous functions inserted by // CoffeeScript compiler - var fn = node.children[0]; - var args = node.children[1]; - if (fn.type === FUNCTION && fn.params.length === 0 && !fn.name && args.children.length == 0) { + var fn = node.callee; + var args = node.arguments; + if (fn.type === 'FunctionExpression' && fn.params.length === 0 && !fn.id && args.length == 0) { // (function() { ... })() // --> (function(_) { ... })(_) fn._noFuture = true; - fn.name = "___closure"; - fn.params = [options.callback]; - args.children = [_identifier(options.callback)]; - } else if (fn.type === DOT) { - var ident = fn.children[1]; - fn = fn.children[0]; - if (fn.type === FUNCTION && fn.params.length === 0 && !fn.name && ident.type === IDENTIFIER) { - if (ident.value === "call" && args.children.length === 1 && args.children[0].type === THIS) { + fn.id = _identifier("___closure"); + fn.params = [_identifier(options.callback)]; + node.arguments = [_identifier(options.callback)]; + } else if (isDot(fn)) { + var ident = fn.property; + fn = fn.object; + if (fn.type === 'FunctionExpression' && fn.params.length === 0 && !fn.id && ident.type === 'Identifier') { + if (ident.name === "call" && args.length === 1 && args[0].type === 'ThisExpression') { // (function() { ... }).call(this) // --> (function(_) { ... })(_) - node.children[0] = fn; + node.callee = fn; fn._noFuture = true; - fn.name = "___closure"; - fn.params = [options.callback]; - args.children = [_identifier(options.callback)]; + fn.id = _identifier("___closure"); + fn.params = [_identifier(options.callback)]; + node.arguments = [_identifier(options.callback)]; node._scope.hasThis = true; fn._inhibitThis = true; - } else if (ident.value === "apply" && args.children.length === 2 && args.children[0].type === THIS && args.children[1].type === IDENTIFIER && args.children[1].value === "arguments") { + } else if (ident.name === "apply" && args.length === 2 && args[0].type === 'ThisExpression' // + && args[1].type === 'Identifier' && args[1].name === "arguments") { // (function() { ... }).apply(this, arguments) // --> (function(_) { ... })(_) - node.children[0] = fn; + node.callee = fn; fn._noFuture = true; - fn.name = "___closure"; - fn.params = [options.callback]; - args.children = [_identifier(options.callback)]; + fn.id = _identifier("___closure"); + fn.params = [_identifier(options.callback)]; + node.arguments = [_identifier(options.callback)]; node._scope.hasThis = true; node._scope.hasArguments = true; fn._inhibitThis = true; fn._inhibitArguments = true; - } else if (ident.value === "call" && args.children.length === 1 && args.children[0].type === IDENTIFIER && args.children[0].value === '_this') { + } else if (ident.name === "call" && args.length === 1 && args[0].type === 'Identifier' && args[0].name === '_this') { // (function() { ... }).call(_this) // --> (function(_) { ... }).call(_this, _) fn._noFuture = true; - fn.name = "___closure"; - fn.params.push(options.callback); - args.children.push(_identifier(options.callback)); + fn.id = _identifier("___closure"); + fn.params.push(_identifier(options.callback)); + args.push(_identifier(options.callback)); } } } @@ -678,21 +727,21 @@ if (typeof exports !== 'undefined') { // return Test.prototype.method.apply(_this, arguments); //}; // Params may vary but so we only test body. - if (node.body.children.length !== 1) return false; - var n = node.body.children[0]; - if (n.type !== RETURN || !n.value) return false; - n = n.value; - if (n.type !== CALL) return false; - var args = n.children[1].children; - var target = n.children[0]; - if (args.length !== 2 || args[0].value !== '_this' || args[1].value !== 'arguments') return false; - if (target.type !== DOT || target.children[1].value !== 'apply') return false; - target = target.children[0]; - if (target.type !== DOT || target.children[1].type !== IDENTIFIER) return false; - target = target.children[0]; - if (target.type !== DOT || target.children[1].value !== 'prototype') return false; - target = target.children[0]; - if (target.type !== IDENTIFIER) return false; + if (node.body.body.length !== 1) return false; + var n = node.body.body[0]; + if (n.type !== 'ReturnStatement' || !n.argument) return false; + n = n.argument; + if (n.type !== 'CallExpression') return false; + var args = n.arguments; + var target = n.callee; + if (args.length !== 2 || args[0].name !== '_this' || args[1].name !== 'arguments') return false; + if (!isDot(target) || target.property.name !== 'apply') return false; + target = target.object; + if (!isDot(target)) return false; + target = target.object; + if (!isDot(target) || target.property.name !== 'prototype') return false; + target = target.object; + if (target.type !== 'Identifier') return false; // Got it. Params are useless so nuke them node.params = []; return true; @@ -713,85 +762,86 @@ if (typeof exports !== 'undefined') { // (function(a, b, _) { // // }).apply(this, args); - var indexes = []; - var paramI = -1; - var skip = 0; - for (var i = 0; i < node.body.children.length; i++) { - var child = node.body.children[i]; - if (i === 0 && child.type === VAR) { skip = 1; continue; } - if (child.type !== IF) return false; - if (child.condition.type !== EQ) return false; - var ident = child.condition.children[0]; - if (ident.type !== IDENTIFIER) return false; - if (child.condition.children[1].type !== NULL) return false; - if (!child.thenPart.children || child.thenPart.children.length !== 1) return false; - var assign = child.thenPart.children[0]; - if (assign.type !== SEMICOLON) return false; - assign = assign.expression; - if (assign.type !== ASSIGN) - if (assign.children[0].type !== IDENTIFIER) return false; - if (assign.children[0].value !== ident.value) return false; - // we got a candidate - let us find the param - while (++paramI < node.params.length) { - if (ident.value === node.params[paramI]) break; - } - if (paramI === node.params.length) return false; - indexes.push(paramI); - if (ident.value === options.callback) { - // got it - var originalParams = node.params.slice(0); - var cb = _safeName(options.precious, 'cb'); - // need to clone args because arguments is not a true array and its length is not bumped - // if we assigned the callback beyond arguments.length - var args = _safeName(options.precious, 'args'); - node.params[paramI] = cb; - for (var k = 0; k < indexes.length; k++) { - // chain has been verified above - var ifn = node.body.children[skip + k]; - if (k === indexes.length - 1) ifn.condition.children[0].value = cb; - var lhs = ifn.thenPart.children[0].expression.children[0]; - // too lazy to create real tree - fake it with identifier - lhs.value = args + "[" + indexes[k] + "]"; - } - node._async = false; - var remain = node.body.children; - node.body.children = remain.splice(0, paramI); - // ugly hack to insert args initializer - node.body.children.splice(0, 0, _identifier("var " + args + " = Array.prototype.slice.call(arguments, 0);")); - node.body.children.push(_node(node, RETURN, { - value: _node(node, CALL, [ - _node(node, DOT, [ - _node(node, FUNCTION, { - params: originalParams, - body: _node(node, SCRIPT, remain), - parenthesized: true, - lineno: node.lineno, - }), - _node(node, IDENTIFIER, { - value: "apply", - lineno: node.lineno, - }), - ]), - _node(node, LIST, [_identifier("this"), _identifier(args)]) - ]) - })); - return true; - } - } - // we did not find it - return false; - } + var indexes = []; + var paramI = -1; + var skip = 0; + for (var i = 0; i < node.body.body.length; i++) { + var child = node.body. + body[i]; + if (i === 0 && child.type === 'VariableDeclaration') { + skip = 1; + continue; + } + if (child.type !== 'IfStatement') return false; + if (child.test.type !== 'BinaryExpression' && child.test.operator != '==') return false; + var ident = child.test.left; + if (ident.type !== 'Identifier') return false; + if (child.test.right.type !== 'Literal' || child.test.right.value !== null) return false; + if (!child.consequent.body || child.consequent.body.length !== 1) return false; + var assign = child.consequent.body[0]; + if (assign.type !== 'ExpressionStatement') return false; + assign = assign.expression; + if (assign.type !== 'AssignmentExpression') return false; + if (assign.left.type !== 'Identifier') return false; + if (assign.left.name !== ident.name) return false; + // we got a candidate - let us find the param + while (++paramI < node.params.length) { + if (ident.name === node.params[paramI].name) break; + } + if (paramI === node.params.length) return false; + indexes.push(paramI); + if (ident.name === options.callback) { + // got it + var originalParams = node.params.slice(0); + var cb = _safeName(options.precious, 'cb'); + // need to clone args because arguments is not a true array and its length is not bumped + // if we assigned the callback beyond arguments.length + var args = _safeName(options.precious, 'args'); + node.params[paramI] = _identifier(cb); + for (var k = 0; k < indexes.length; k++) { + // chain has been verified above + var ifn = node.body.body[skip + k]; + if (k === indexes.length - 1) ifn.test.left.name = cb; + var lhs = ifn.consequent.body[0].expression.left; + // too lazy to create real tree - fake it with identifier + lhs.name = args + "[" + indexes[k] + "]"; + } + node._async = false; + var remain = node.body.body; + node.body.body = remain.splice(0, paramI); + // ugly hack to insert args initializer + node.body.body.splice(0, 0, _identifier("var " + args + " = Array.prototype.slice.call(arguments, 0);")); + node.body.body.push(_node(node, 'ReturnStatement', { + argument: _node(node, 'CallExpression', { + callee: _node(node, 'MemberExpression', { + object: _node(node, 'FunctionExpression', { + params: originalParams, + body: _node(node, 'BlockStatement', { body: remain }), + parenthesized: true, + }), + property: _identifier("apply"), + }), + arguments: [_identifier("this"), _identifier(args)], + }), + })); + return true; + } + } + // we did not find it + return false; + } function _convertApply(node, options) { // f.apply(this, arguments) -> __apply(_, f, __this, __arguments, cbIndex) - var dot = node.children[0]; - var args = node.children[1]; - if (dot.type === DOT) { - var ident = dot.children[1]; - if (ident.type === IDENTIFIER && ident.value === "apply" && args.children.length === 2 && args.children[0].type === THIS && args.children[1].type === IDENTIFIER && args.children[1].value === "arguments") { - var f = dot.children[0]; - node.children[0] = _identifier('__apply'); - args.children = [_identifier(options.callback), f, _identifier('__this'), _identifier('__arguments'), _number(node._scope.cbIndex)]; + var dot = node.callee; + var args = node.arguments; + if (isDot(dot)) { + var ident = dot.property; + if (ident.type === 'Identifier' && ident.name === "apply" && args.length === 2 // + && args[0].type === 'ThisExpression' && args[1].type === 'Identifier' && args[1].name === "arguments") { + var f = dot.object; + node.callee = _identifier('__apply'); + node.arguments = [_identifier(options.callback), f, _identifier('__this'), _identifier('__arguments'), _literal(node._scope.cbIndex)]; node._scope.hasThis = true; node._scope.hasArguments = true; } @@ -803,21 +853,22 @@ if (typeof exports !== 'undefined') { function _setBreaks(node) { switch (node.type) { - case IF: - node._breaks = node.thenPart._breaks && node.elsePart && node.elsePart._breaks; + case 'IfStatement': + node._breaks = node.consequent._breaks && node.alternate && node.alternate._breaks; break; - case SWITCH: + case 'SwitchStatement': + if (!node.hasDefault && node._async) { + node.cases.push(_node(node, 'SwitchCase', { + consequent: [_node(node, 'BlockStatement', { + body: [_node(node, 'BreakStatement')], + })], + })); + } for (var i = 0; i < node.cases.length; i++) { - var stmts = node.cases[i].statements; - if (node._async && stmts.children.length > 0 && !stmts._breaks) { - // narcissus has the strange idea of inserting an empty default after last case. - // If we detect this and if the last case is not terminated by a break, we do not consider it an error - // and we just fix it by adding a break. - if (i == node.cases.length - 2 && node.cases[i + 1].type === DEFAULT && node.cases[i + 1].statements.children.length === 1 && node.cases[i + 1].statements.children[0].type === SEMICOLON && node.cases[i + 1].statements.children[0].expression == null) { - stmts.children.push(_node(node, BREAK)); - stmts._breaks = true; - } else if (i === node.cases.length - 1) { - stmts.children.push(_node(node, BREAK)); + var stmts = node.cases[i]; + if (node._async && stmts.consequent[0].body.length > 0 && !stmts._breaks) { + if (i === node.cases.length - 1) { + stmts.consequent[0].body.push(_node(node, 'BreakStatement')); stmts._breaks = true; } else { // we rewrite: @@ -833,30 +884,35 @@ if (typeof exports !== 'undefined') { // if (__B) no_break_B // breaking_C var v = _identifier(_genId(node)); - node.cases[i].statements = _switchVarTemplate.generate(node.cases[i], { + var body = stmts.consequent[0]; + node.cases[i].consequent = [_switchVarTemplate.generate(node.cases[i], { $v: v, - }); + })]; var ifStmt = _switchIfTemplate.generate(node.cases[i], { $v: v, - $block: stmts, + $block: body, }); - node.cases[i + 1].statements.children.splice(0, 0, ifStmt); + node.cases[i + 1].consequent[0].body.splice(0, 0, ifStmt); } } } break; - case TRY: - node._breaks = node.tryBlock._breaks && node.catchClauses[0] && node.catchClauses[0].block._breaks; + case 'TryStatement': + node._breaks = node.block._breaks && node.handlers.length && node.handlers[0].body._breaks; break; - case BLOCK: - case SCRIPT: - node.children.forEach(function(child) { + case 'BlockStatement': + case 'Program': + node.body.forEach(function(child) { node._breaks |= child._breaks; }); break; - case RETURN: - case THROW: - case BREAK: + case 'SwitchCase': + if (node.consequent.length !== 1 || node.consequent[0].type !== 'BlockStatement') throw new Error("internal error: SwitchCase not wrapped: " + node.consequent.length); + node._breaks |= node.consequent[0]._breaks; + break; + case 'ReturnStatement': + case 'ThrowStatement': + case 'BreakStatement': node._breaks = true; break; } @@ -872,13 +928,15 @@ if (typeof exports !== 'undefined') { function _statementify(exp) { if (!exp) return exp; - var block = _node(exp, BLOCK, []); + var block = _node(exp, 'BlockStatement', { + body: [] + }); function uncomma(node) { - if (node.type === COMMA) { - node.children.forEach(uncomma); + if (node.type === 'SequenceExpression') { + node.expressions.forEach(uncomma); } else { - block.children.push(node.type == SEMICOLON ? node : _semicolon(node)); + block.body.push(node.type == 'ExpressionStatement' ? node : _semicolon(node)); } } uncomma(exp); @@ -887,31 +945,33 @@ if (typeof exports !== 'undefined') { } function _blockify(node) { - if (!node || node.type == BLOCK) return node; - if (node.type == COMMA) return _statementify(node); - var block = _node(node, BLOCK, [node]); + if (!node || node.type == 'BlockStatement') return node; + if (node.type == 'SequenceExpression') return _statementify(node); + var block = _node(node, 'BlockStatement', { + body: [node] + }); block._async = node._async; return block; } var _flowsTemplates = { WHILE: new Template("flows", "{" + // - " for (; $condition;) {" + // + " for (; $test;) {" + // " $body;" + // " }" + // "}"), DO: new Template("flows", "{" + // " var $firstTime = true;" + // - " for (; $firstTime || $condition;) {" + // + " for (; $firstTime || $test;) {" + // " $firstTime = false;" + // " $body;" + // " }" + // "}"), FOR: new Template("flows", "{" + // - " $setup;" + // - " for (; $condition; $update) {" + // + " $init;" + // + " for (; $test; $update) {" + // " $body;" + // " }" + // "}"), @@ -952,7 +1012,7 @@ if (typeof exports !== 'undefined') { HOOK: new Template("flows", "" + // "return (function $name(_){" + // - " var $v = $condition;" + // + " var $v = $test;" + // " if ($v) {" + // " return $true;" + // " }" + // @@ -967,7 +1027,7 @@ if (typeof exports !== 'undefined') { CONDITION: new Template("flows", "" + // "return (function $name(_){" + // - " return $condition;" + // + " return $test;" + // "})(_);", true, true), UPDATE: new Template("flows", "" + // @@ -977,138 +1037,178 @@ if (typeof exports !== 'undefined') { }; function _canonFlows(node, options) { + var targets = {}; function _doIt(node, parent, force) { var scope = node._scope; + function withTarget(node, label, isLoop, fn) { + label = label || ''; + var breakTarget = targets['break_' + label]; + var continueTarget = targets['continue_' + label]; + targets['break_' + label] = node; + if (isLoop) targets['continue_' + label] = node; + var result = fn(); + targets['break_' + label] = breakTarget; + targets['continue_' + label] = continueTarget; + return result; + } function _doAsyncFor(node) { - // extra pass to wrap async condition and update - if (node.condition && node.condition._async && node.condition.type !== CALL) node.condition = _flowsTemplates.CONDITION.generate(node, { + // extra pass to wrap async test and update + if (node.test && node.test._async && node.test.type !== 'CallExpression') node.test = _flowsTemplates.CONDITION.generate(node, { $name: "__$" + node._scope.name, - $condition: _doIt(node.condition, node, true), + $test: _doIt(node.test, node, true), }); if (node.update && node.update._async) node.update = _flowsTemplates.UPDATE.generate(node, { $name: "__$" + node._scope.name, $update: _statementify(node.update) }); } - if (node.type == FOR && node._pass === "flows") _doAsyncFor(node); + if (node.type == 'ForStatement' && node._pass === "flows") _doAsyncFor(node); if (!scope || !scope.isAsync() || (!force && node._pass === "flows")) return _propagate(node, _doIt); switch (node.type) { - case IF: - node.thenPart = _blockify(node.thenPart); - node.elsePart = _blockify(node.elsePart); + case 'IfStatement': + node.consequent = _blockify(node.consequent); + node.alternate = _blockify(node.alternate); break; - case SWITCH: - if (node._async) { - var def = node.cases.filter(function(n) { - return n.type == DEFAULT - })[0]; - if (!def) { - def = _node(node, DEFAULT); - def.statements = _node(node, BLOCK, []); - node.cases.push(def); - } - if (!def._breaks) { - def.statements.children.push(_node(node, BREAK)) + case 'SwitchStatement': + return withTarget(node, null, false, function() { + if (node._async) { + var def = node.cases.filter(function(n) { + return n.test == null + })[0]; + if (!def) { + def = _node(node, 'SwitchCase', { + consequent: [_node(node, 'BlockStatement', { + body: [], + })], + }); + node.cases.push(def); + } + if (!def._breaks) { + def.consequent[0].body.push(_node(node, 'BreakStatement')); + } } - } - break; - case WHILE: - node.body = _blockify(node.body); - if (node._async) { - node = _flowsTemplates.WHILE.generate(node, { - $condition: node.condition, - $body: node.body - }); - } - break; - case DO: + return _propagate(node, _doIt); + }); + case 'WhileStatement': node.body = _blockify(node.body); - if (node._async) { - node = _flowsTemplates.DO.generate(node, { - $firstTime: _identifier(_genId(node)), - $condition: node.condition, - $body: node.body - }); - } - break; - case FOR: - node.condition = node.condition || _number(1); + return withTarget(node, null, true, function() { + if (node._async) { + node = _flowsTemplates.WHILE.generate(node, { + $test: node.test, + $body: node.body + }); + } + return _propagate(node, _doIt); + }); + case 'DoWhileStatement': node.body = _blockify(node.body); - if (node._async) { - if (node.setup) { - node = _flowsTemplates.FOR.generate(node, { - $setup: _statementify(node.setup), - $condition: node.condition, - $update: node.update, + return withTarget(node, null, true, function() { + if (node._async) { + node = _flowsTemplates.DO.generate(node, { + $firstTime: _identifier(_genId(node)), + $test: node.test, $body: node.body }); - } else { - if (node._pass !== "flows") { - node._pass = "flows"; - _doAsyncFor(node); + } + return _propagate(node, _doIt); + }); + case 'ForStatement': + node.test = node.test || _literal(1); + node.body = _blockify(node.body); + return withTarget(node, null, true, function() { + if (node._async) { + if (node.init) { + node = _flowsTemplates.FOR.generate(node, { + $init: _statementify(node.init), + $test: node.test, + $update: node.update, + $body: node.body + }); + } else { + if (node._pass !== "flows") { + node._pass = "flows"; + _doAsyncFor(node); + } } } - } - break; - case FOR_IN: + return _propagate(node, _doIt); + }); + case 'ForInStatement': node.body = _blockify(node.body); - if (node._async) { - if (node.iterator.type != IDENTIFIER) { - throw new Error("unsupported 'for ... in' syntax: type=" + _tag(node.iterator)); + return withTarget(node, null, true, function() { + if (node._async) { + if (node.left.type != 'Identifier') { + throw new Error("unsupported 'for ... in' syntax: type=" + node.left.type); + } + node = _flowsTemplates.FOR_IN.generate(node, { + $array: _identifier(_genId(node)), + $i: _identifier(_genId(node)), + $object: node.right, + $iter: node.left, + $body: node.body + }); } - node = _flowsTemplates.FOR_IN.generate(node, { - $array: _identifier(_genId(node)), - $i: _identifier(_genId(node)), - $object: node.object, - $iter: node.iterator, - $body: node.body - }); - } - break; - case TRY: - if (node.tryBlock && node.catchClauses[0] && node.finallyBlock) { + return _propagate(node, _doIt); + }); + case 'TryStatement': + if (node.block && node.handlers.length && node.finalizer) { node = _flowsTemplates.TRY.generate(node, { - $try: node.tryBlock, - $catch: node.catchClauses[0].block, - $ex: node.catchClauses[0].varName, - $finally: node.finallyBlock + $try: node.block, + $catch: node.handlers[0].body, + $ex: node.handlers[0].param, + $finally: node.finalizer }) } break; - case AND: - case OR: + case 'LogicalExpression': if (node._async) { - node = _flowsTemplates[_tag(node)].generate(node, { + node = _flowsTemplates[node.operator === '&&' ? 'AND' : 'OR'].generate(node, { $name: "__$" + node._scope.name, $v: _identifier(_genId(node)), - $op1: node.children[0], - $op2: node.children[1] + $op1: node.left, + $op2: node.right, }); } break; - case HOOK: + case 'ConditionalExpression': if (node._async) { node = _flowsTemplates.HOOK.generate(node, { $name: "__$" + node._scope.name, $v: _identifier(_genId(node)), - $condition: node.children[0], - $true: node.children[1], - $false: node.children[2] + $test: node.test, + $true: node.consequent, + $false: node.alternate, }); } break; - case COMMA: + case 'SequenceExpression': if (node._async) { node = _flowsTemplates.COMMA.generate(node, { $name: "__$" + node._scope.name, - $body: _node(node, BLOCK, node.children.slice(0, node.children.length - 1).map(_semicolon)), - $result: node.children[node.children.length - 1] + $body: _node(node, 'BlockStatement', { + body: node.expressions.slice(0, node.expressions.length - 1).map(_semicolon), + }), + $result: node.expressions[node.expressions.length - 1] }); } break; + case 'LabeledStatement': + return withTarget(node, node.label.name, true, function() { + return _propagate(node, _doIt); + }); + case 'BreakStatement': + var target = targets['break_' + (node.label ? node.label.name : '')]; + if (!target) throw new Error("internal error: break target not set"); + node._async = target._async; + break; + case 'ContinueStatement': + var target = targets['continue_' + (node.label ? node.label.name : '')]; + if (!target) throw new Error("internal error: continue target not set"); + node._async = target._async; + break; } return _propagate(node, _doIt); } @@ -1123,36 +1223,44 @@ if (typeof exports !== 'undefined') { var exp = node[prop]; if (!exp || !exp._async) return node; var id = _genId(node); - var v = _identifier(id, exp); + var v = _declarator(id, exp); node[prop] = _identifier(id); - return _node(node, BLOCK, [_node(node, VAR, [v]), node]); + return _node(node, 'BlockStatement', { + body: [_node(node, 'VariableDeclaration', { + kind: 'var', // see later + declarations: [v], + }), node] + }); } function _disassemble(node, options) { function _disassembleIt(node, parent, noResult) { if (!node._async) return _propagate(node, _scanIt); node = _propagate(node, _disassembleIt); - if (node.type === CALL) { - if (node.children[0].type === IDENTIFIER && node.children[0].value.indexOf('__wrap') == 0) { + if (node.type === 'CallExpression') { + if (node.callee.type === 'Identifier' && node.callee.name.indexOf('__wrap') == 0) { node._isWrapper = true; return node; } - var args = node.children[1]; - if (args.children.some(function(arg) { - return (arg.type === IDENTIFIER && arg.value === options.callback) || arg._isWrapper; + var args = node.arguments; + if (args.some(function(arg) { + return (arg.type === 'Identifier' && arg.name === options.callback) || arg._isWrapper; })) { if (noResult) { node._scope.disassembly.push(_statementify(node)); return; } else { - if (parent.type == IDENTIFIER && parent.value.indexOf('__') === 0) { + if (parent.type == 'Identifier' && parent.name.indexOf('__') === 0) { // don't generate another ID, use the parent one node._skipDisassembly = true; return node; } var id = _genId(node); - var v = _identifier(id, node); - node = _node(node, VAR, [v]); + var v = _declarator(id, node); + node = _node(node, 'VariableDeclaration', { + kind: 'var', // fix later + declarations: [v] + }); node._scope.disassembly.push(node); return _identifier(id); } @@ -1165,36 +1273,40 @@ if (typeof exports !== 'undefined') { var scope = node._scope; if (!scope || !scope.isAsync() || !node._async) return _propagate(node, _scanIt); switch (node.type) { - case IF: - node = _split(node, "condition"); + case 'IfStatement': + node = _split(node, "test"); break; - case SWITCH: + case 'SwitchStatement': node = _split(node, "discriminant"); break; - case FOR: + case 'ForStatement': break; - case RETURN: - node = _split(node, "value"); + case 'ReturnStatement': + node = _split(node, "argument"); break; - case THROW: - node = _split(node, "exception"); + case 'ThrowStatement': + node = _split(node, "argument"); break; - case VAR: - _assert(node.children.length === 1); - var ident = node.children[0]; + case 'VariableDeclaration': + _assert(node.declarations.length === 1); + var decl = node.declarations[0]; scope.disassembly = []; - ident.initializer = _disassembleIt(ident.initializer, ident); - node._async = ident.initializer._skipDisassembly; + decl.init = _disassembleIt(decl.init, decl); + node._async = decl.init._skipDisassembly; scope.disassembly.push(node); - return _node(parent, BLOCK, scope.disassembly); - case SEMICOLON: + return _node(parent, 'BlockStatement', { + body: scope.disassembly, + }); + case 'ExpressionStatement': scope.disassembly = []; node.expression = _disassembleIt(node.expression, node, true); if (node.expression) { node._async = false; scope.disassembly.push(node); } - return _node(parent, BLOCK, scope.disassembly); + return _node(parent, 'BlockStatement', { + body: scope.disassembly, + }); } return _propagate(node, _scanIt); } @@ -1225,7 +1337,7 @@ if (typeof exports !== 'undefined') { IF: new Template("cb", "" + // "return (function $name(__then){" + // - " if ($condition) { $then; __then(); }" + // + " if ($test) { $then; __then(); }" + // " else { $else; __then(); }" + // "})(function $name(){ $tail; });"), @@ -1258,11 +1370,11 @@ if (typeof exports !== 'undefined') { "else { __break(); }"), // LOOP2 is in temp pass so that it gets transformed if update is async - LOOP2: new Template("temp", "var $v = $condition; $loop1;"), + LOOP2: new Template("temp", "var $v = $test; $loop1;"), LOOP2_UPDATE: new Template("temp", "" + // "if ($beenHere) { $update; } else { $beenHere = true; }" + // - "var $v = $condition; $loop1;"), + "var $v = $test; $loop1;"), FOR: new Template("cb", "" + // "return (function ___(__break){" + // @@ -1353,40 +1465,43 @@ if (typeof exports !== 'undefined') { function _callbackify(node, options) { var label; function _scanIt(node, parent) { - //console.log("CBIT: " + _tag(node) + " " + pp(node)) node = _flatten(node); if (!node._scope || !node._scope.isAsync() || node._pass === "cb") return _propagate(node, _scanIt); switch (node.type) { - case SCRIPT: - if (parent._pass !== "cb") { - // isolate the leading decls from the body because 'use strict' - // do not allow hoisted functions inside try/catch - var decls; - for (var cut = 0; cut < node.children.length; cut++) { - var child = node.children[cut]; - if (child.type === STRING && child.value === "BEGIN_BODY") { - decls = node.children.splice(0, cut); - node.children.splice(0, 1); - break; + case 'Program': + case 'BlockStatement': + if (node.type === 'Program' || parent.type === 'FunctionExpression' || parent.type === 'FunctionDeclaration') { + if (parent._pass !== "cb") { + // isolate the leading decls from the body because 'use strict' + // do not allow hoisted functions inside try/catch + var decls; + for (var cut = 0; cut < node.body.length; cut++) { + var child = node.body[cut]; + if (child.type === 'Literal' && child.value === "BEGIN_BODY") { + decls = node.body.splice(0, cut); + node.body.splice(0, 1); + break; + } } + var template = parent._noFuture || parent._pass === "flows" ? _cbTemplates.FUNCTION_INTERNAL : _cbTemplates.FUNCTION; + node = template.generate(node, { + $fn: parent.id.name, + //node._scope.name ? _identifier(node._scope.name) : _node(node, NULL), + $name: "__$" + node._scope.name, + $fname: _literal(parent.id.name), + $line: _literal(originalLine(options, node._scope.line)), + $index: _literal(node._scope.cbIndex), + $decls: _node(node, 'BlockStatement', { + body: decls || [] + }), + $body: node + }); } - var template = parent._noFuture || parent._pass === "flows" ? _cbTemplates.FUNCTION_INTERNAL : _cbTemplates.FUNCTION; - node = template.generate(node, { - $fn: parent.name, - //node._scope.name ? _identifier(node._scope.name) : _node(node, NULL), - $name: "__$" + node._scope.name, - $fname: _string(parent.name), - $line: _number(originalLine(options, node._scope.line)), - $index: _number(node._scope.cbIndex), - $decls: _node(node, BLOCK, decls || []), - $body: node - }); + //node.type = 'Program'; } - node.type = SCRIPT; // continue with block restructure - case BLOCK: - for (var i = 0; i < node.children.length; i++) { - node.children[i] = _restructureIt(node, i); + for (var i = 0; i < node.body.length; i++) { + node.body[i] = _restructureIt(node, i); } return node; } @@ -1394,69 +1509,66 @@ if (typeof exports !== 'undefined') { } function _extractTail(parent, i) { - return _node(parent, BLOCK, parent.children.splice(i + 1, parent.children.length - i - 1)); + return _node(parent, 'BlockStatement', { + body: parent.body.splice(i + 1, parent.body.length - i - 1) + }); } function _restructureIt(parent, i) { - var node = parent.children[i]; + var node = parent.body[i]; if (node._pass === "cb") return _propagate(node, _scanIt); - //console.log("RESTRUCTUREIT: " + _tag(node) + " " + pp(node)) switch (node.type) { - case RETURN: + case 'ReturnStatement': _extractTail(parent, i); - var template = node.value ? _cbTemplates.RETURN : _cbTemplates.RETURN_UNDEFINED; + var template = node.argument ? _cbTemplates.RETURN : _cbTemplates.RETURN_UNDEFINED; node = template.generate(node, { - $value: node.value + $value: node.argument }); break; - case THROW: + case 'ThrowStatement': _extractTail(parent, i); node = _cbTemplates.THROW.generate(node, { - $exception: node.exception + $exception: node.argument }); break; - case BREAK: - if (node.target && !node.target._async) { - break; - } + case 'BreakStatement': + if (!node._async) break; _extractTail(parent, i); if (node.label) { node = _cbTemplates.LABELLED_BREAK.generate(node, { - $break: _safeName(options.precious, '__break__' + node.label) + $break: _safeName(options.precious, '__break__' + node.label.name) }); } else { node = _cbTemplates.BREAK.generate(node, {}); } break; - case CONTINUE: - if (node.target && !node.target._async) { - break; - } + case 'ContinueStatement': + if (!node._async) break; _extractTail(parent, i); if (node.label) { node = _cbTemplates.LABELLED_CONTINUE.generate(node, { - $loop: _safeName(options.precious, '__loop__' + node.label), - $more: _safeName(options.precious, '__more__' + node.label), + $loop: _safeName(options.precious, '__loop__' + node.label.name), + $more: _safeName(options.precious, '__more__' + node.label.name), }); } else { node = _cbTemplates.CONTINUE.generate(node, {}); } break; - case TRY: + case 'TryStatement': var tail = _extractTail(parent, i); - if (node.catchClauses[0]) { + if (node.handlers.length) { node = _cbTemplates.CATCH.generate(node, { $name: "__$" + node._scope.name, - $try: node.tryBlock, - $catch: node.catchClauses[0].block, - $ex: node.catchClauses[0].varName, + $try: node.block, + $catch: node.handlers[0].body, + $ex: node.handlers[0].param, $tail: tail }); } else { node = _cbTemplates.FINALLY.generate(node, { $name: "__$" + node._scope.name, - $try: node.tryBlock, - $finally: node.finallyBlock, + $try: node.block, + $finally: node.finalizer, $tail: tail }); } @@ -1465,16 +1577,18 @@ if (typeof exports !== 'undefined') { if (node._async) { var tail = _extractTail(parent, i); switch (node.type) { - case IF: + case 'IfStatement': node = _cbTemplates.IF.generate(node, { $name: "__$" + node._scope.name, - $condition: node.condition, - $then: node.thenPart, - $else: node.elsePart || _node(node, BLOCK, []), + $test: node.test, + $then: node.consequent, + $else: node.alternate || _node(node, 'BlockStatement', { + body: [] + }), $tail: tail }); break; - case SWITCH: + case 'SwitchStatement': node._pass = "cb"; // avoid infinite recursion node = _cbTemplates.SWITCH.generate(node, { $name: "__$" + node._scope.name, @@ -1482,18 +1596,18 @@ if (typeof exports !== 'undefined') { $tail: tail }); break; - case LABEL: + case 'LabeledStatement': var l = label; - label = node.label; + label = node.label.name; node = _cbTemplates.LABEL.generate(node, { $name: "__$" + node._scope.name, - $statement: node.statement, + $statement: node.body, $tail: tail }); node = _scanIt(node, parent); label = l; return node; - case FOR: + case 'ForStatement': var v = _identifier(_genId(node)); var loop1 = _cbTemplates.LOOP1.generate(node, { $v: v, @@ -1503,7 +1617,7 @@ if (typeof exports !== 'undefined') { var beenHere = update && _identifier(_genId(node)); var loop2 = (update ? _cbTemplates.LOOP2_UPDATE : _cbTemplates.LOOP2).generate(node, { $v: v, - $condition: node.condition, + $test: node.test, $beenHere: beenHere, $update: _statementify(update), $loop1: loop1 @@ -1521,63 +1635,66 @@ if (typeof exports !== 'undefined') { }); break; - case VAR: - _assert(node.children.length == 1); - var ident = node.children[0]; - _assert(ident.type === IDENTIFIER); - var call = ident.initializer; - delete ident.initializer; - _assert(call && call.type === CALL); - return _restructureCall(call, tail, ident.value); - case SEMICOLON: + case 'VariableDeclaration': + _assert(node.declarations.length == 1); + var decl = node.declarations[0]; + _assert(decl.type === 'VariableDeclarator'); + var call = decl.init; + decl.init = null; + _assert(call && call.type === 'CallExpression'); + return _restructureCall(call, tail, decl.id.name); + case 'ExpressionStatement': var call = node.expression; - _assert(call.type === CALL) + _assert(call.type === 'CallExpression') return _restructureCall(call, tail); default: - throw new Error("internal error: bad node type: " + _tag(node) + ": " + pp(node)); + throw new Error("internal error: bad node type: " + node.type + ": " + escodegen.generate(node)); } } } return _scanIt(node, parent); function _restructureCall(node, tail, result) { - var args = node.children[1]; + var args = node.arguments; function _cbIndex(args) { - return args.children.reduce(function(index, arg, i) { - if ((arg.type == IDENTIFIER && arg.value === options.callback) || arg._isWrapper) return i; + return args.reduce(function(index, arg, i) { + if ((arg.type == 'Identifier' && arg.name === options.callback) || arg._isWrapper) return i; else return index; }, -1); } var i = _cbIndex(args); _assert(i >= 0); - var returnArray = args.children[i]._returnArray; - if (args.children[i]._isWrapper) { - args = args.children[i].children[1]; + var returnArray = args[i]._returnArray; + if (args[i]._isWrapper) { + args = args[i].arguments; i = _cbIndex(args); } // find the appropriate node for this call: // e.g. for "a.b(_)", find the node "b" - var identifier = node.children[0]; - while (identifier.type == DOT) { - identifier = identifier.children[1]; + var identifier = node.callee; + while (isDot(identifier)) { + identifier = identifier.property; } var bol = options.source.lastIndexOf('\n', identifier.start) + 1; var col = identifier.start - bol; - args.children[i] = (result ? result.indexOf('__') === 0 ? _cbTemplates.CALL_TMP : _cbTemplates.CALL_RESULT : _cbTemplates.CALL_VOID).generate(node, { + args[i] = (result ? result.indexOf('__') === 0 ? _cbTemplates.CALL_TMP : _cbTemplates.CALL_RESULT : _cbTemplates.CALL_VOID).generate(node, { $v: _genId(node), - $frameName: _string(node._scope.name), - $offset: _number(originalLine(options, identifier.lineno, col) - originalLine(options, node._scope.line)), - $col: _number(originalCol(options, identifier.lineno, col)), + $frameName: _literal(node._scope.name), + $offset: _literal(Math.max(originalLine(options, identifier.loc && identifier.loc.start.line, col) - originalLine(options, node._scope.line), 0)), + $col: _literal(originalCol(options, identifier.loc && identifier.loc.start.column, col)), $name: "__$" + node._scope.name, - $returnArray: returnArray, - $result: result, + $returnArray: _node(node, 'Literal', { + value: !!returnArray, + }), + $result: _identifier(result), $tail: tail }); node = _propagate(node, _scanIt); - var stmt = _node(node, RETURN, []); - stmt.value = node; + var stmt = _node(node, 'ReturnStatement', { + argument: node + }); stmt._pass = "cb"; return stmt; } @@ -1597,29 +1714,38 @@ if (typeof exports !== 'undefined') { var _optims = { function__0$fn: new Template("simplify", "return function ___(__0) { $fn(); }", true).root, function$return: new Template("simplify", "return function $fn1() { return $fn2(); }", true).root, - function__0$arg1return_null$arg2: new Template("simplify", "return function ___(__0, $arg1) { return _(null, $arg2); }", true).root, + function__0$arg1return_null$arg2: new Template("simplify", "return function ___(__0, $arg1) { var $arg2 = $arg3; return _(null, $arg4); }", true).root, __cb__: new Template("simplify", "return __cb(_, $frameVar, $line, $col, _)", true).root, - __cbt__: new Template("simplify", "return __cb(_, $frameVar, $line, $col, _, true)", true).root, + __cbt__: new Template("simplify", "return __cb(_, $frameVar, $line, $col, _, true, false)", true).root, function$fn: new Template("simplify", "return function $fn1() { $fn2(); }", true).root, - closure: new Template("simplify", "return (function ___closure(_){ $body; })(__cb(_,$frameVar,$line,$col,function $fnName(){_();},true))", true).root, + closure: new Template("simplify", "return (function ___closure(_){ $body; })(__cb(_,$frameVar,$line,$col,function $fnName(){_();},true,false))", true).root, safeParam: new Template("simplify", "return (function $fnName($param){ $body; })(function $fnName(){_();})", true).root, } function _simplify(node, options, used) { if (node._simplified) return node; node._simplified = true; + // eliminate extra braces on switch cases + if (node.type === 'SwitchCase') { + if (node.consequent.length === 1 && node.consequent[0].type === 'BlockStatement') // + node.consequent = node.consequent[0].body; + } + _propagate(node, function(child) { return _simplify(child, options, used) }); - _checkUsed(node.value, used); + _checkUsed(node.name, used); function _match(prop, v1, v2, result) { - var ignored = ["parenthesized", "lineno", "start", "end", "tokenizer", "hasReturnWithValue"]; + var ignored = ["loc", "range", "raw"]; if (prop.indexOf('_') == 0 || ignored.indexOf(prop) >= 0) return true; - if (v1 == v2) return true; + if (v1 == v2) { + //console.log("MATCHING1: " + v1); + return true; + } if (v1 == null || v2 == null) { // ignore difference between null and empty array - if (prop == "children" && v1 && v1.length === 0) return true; + if (prop == "body" && v1 && v1.length === 0) return true; return false; } if (Array.isArray(v1)) { @@ -1629,28 +1755,30 @@ if (typeof exports !== 'undefined') { } return true; } - if (v1.type === IDENTIFIER && v1.value[0] === "$" && v2.type === NUMBER) { - result[v1.value] = v2.value; + if (v1.type === 'Identifier' && v1.name[0] === "$" && typeof v2.value === "number") { + //console.log("MATCHING2: " + v1.name + " with " + v2.value); + result[v1.name] = v2.value; return true; } if (typeof v1 == "string" && v1[0] == "$" && typeof v2 == "string") { + //console.log("MATCHING3: " + v1 + " with " + v2); result[v1] = v2; return true; } if (v1.type) { var exp; - if (v1.type == SCRIPT && v1.children[0] && (exp = v1.children[0].expression) && typeof exp.value == "string" && exp.value[0] == '$') { - result[exp.value] = v2; + if (v1.type == 'BlockStatement' && v1.body[0] && (exp = v1.body[0].expression) && typeof exp.name == "string" && exp.name[0] == '$') { + result[exp.name] = v2; return true; } if (v1.type != v2.type) return false; - if (v1.type == IDENTIFIER && v1.value == '$') { - result[v1.value] = v2.value; + if (v1.type == 'Identifier' && v1.name == '$') { + result[v1.name] = v2.name; return true; } for (var prop in v1) { - if (v1.hasOwnProperty(prop) && prop.indexOf("Decls") < 0 && prop != "target") { + if (v1.hasOwnProperty(prop)) { if (!_match(prop, v1[prop], v2[prop], result)) return false; } } @@ -1662,12 +1790,12 @@ if (typeof exports !== 'undefined') { var result = {}; if (_match("", _optims.function__0$fn, node, result)) return _identifier(result.$fn); if (_match("", _optims.function$return, node, result) && (result.$fn1 === '___' || result.$fn1.indexOf('__$') === 0) && (result.$fn2 === '__break')) return _identifier(result.$fn2); - if (_match("", _optims.function__0$arg1return_null$arg2, node, result) && result.$arg1 == result.$arg2) return _identifier("_"); + if (_match("", _optims.function__0$arg1return_null$arg2, node, result) && result.$arg1 == result.$arg3 && result.$arg2 == result.$arg4) return _identifier("_"); if (options.optimize && _match("", _optims.__cb__, node, result)) return _identifier("_"); if (options.optimize && _match("", _optims.__cbt__, node, result)) return _identifier("_"); if (_match("", _optims.function$fn, node, result) && (result.$fn1 === '___' || result.$fn1.indexOf('__$') === 0) && (result.$fn2 === '__then' || result.$fn2 === '__loop')) return _identifier(result.$fn2); - if (_match("", _optims.closure, node, result)) node.children[1] = _identifier("_"); - if (_match("", _optims.safeParam, node, result) && (result.$param === '__then' || result.$param === '__break')) node.children[1] = _identifier("_"); + if (_match("", _optims.closure, node, result)) { node.arguments[0] = _identifier("_"); } + if (_match("", _optims.safeParam, node, result) && (result.$param === '__then' || result.$param === '__break')) node.arguments[0] = _identifier("_"); _flatten(node); return node; } @@ -1683,6 +1811,46 @@ if (typeof exports !== 'undefined') { return _extend({}, obj); } + var visit = 0; + function dump(obj) { + function fix(obj) { + if (!obj || typeof obj !== 'object') return '' + obj; + if (obj._visited === visit) return ""; + obj._visited = visit; + if (Array.isArray(obj)) return obj.map(fix); + return Object.keys(obj).filter(function(k) { + return !/^(_|loc)/.test(k) || k === '_async'; + }).reduce(function(r, k) { + r[k] = fix(obj[k]); + return r; + }, {}); + } + visit++; + return JSON.stringify(fix(obj), null, ' '); + } + + function addNewlines(node) { + var line = 1, l = 1; + function doIt(obj, insideBlock) { + if (!obj) return; + if (obj.loc) l = Math.max(obj.loc.start.line, l); + if (obj.type && insideBlock && line < l) { + obj.leadingComments = new Array(l - line).join('#').split('#'); + line = l; + } + if (Array.isArray(obj)) return obj.forEach(function(o) { + doIt(o, insideBlock); + }); + if (!obj.type) return; + var isBlock = obj.type === 'BlockStatement' || obj.type === 'SwitchCase'; + Object.keys(obj).forEach(function(k) { + var v = obj[k]; + doIt(v, isBlock); + }); + } + doIt(node, false); + } + /// * `transformed = transform.transform(source, options)` /// Transforms streamline source. /// The following `options` may be specified: @@ -1706,26 +1874,46 @@ if (typeof exports !== 'undefined') { options.precious = {}; // identifiers found inside source //console.log("TRANSFORMING " + options.sourceName) //console.log("source=" + source); - var node = parse(source + "\n", options.sourceName); // final newline avoids infinite loop if unterminated string literal at the end - var strict = node.children[0] && node.children[0].expression && node.children[0].expression.value == "use strict"; - strict && node.children.splice(0, 1); + // esprima does not like return at top level so we wrap into a function + // also \n is needed before } in case last line ends on a // comment + source = "function dummy(){" + source + "\n}"; + var node = esprima.parse(source, { + loc: true, + range: true, + }); + node = node.body[0].body; + if (node.type !== 'BlockStatement') throw new Error("source wrapper error: " + node.type); + //console.log(JSON.stringify(node, null, ' ')); + var strict = node.body[0] && node.body[0].expression && node.body[0].expression.value == "use strict"; + strict && node.body.splice(0, 1); _markSource(node, options); //console.log("tree=" + node); node = _canonTopLevelScript(node, options); - //console.log("CANONTOPLEVEL=" + pp(node)); + //console.log("CANONTOPLEVEL=" + escodegen.generate(node)); node = _canonScopes(node, options); - //console.log("CANONSCOPES=" + pp(node)); - if (!options.needsTransform) return source; + //console.log("CANONSCOPES=" + escodegen.generate(node)); + if (!options.needsTransform) return options.source; // original source! node = _canonFlows(node, options); - //console.log("CANONFLOWS=" + pp(node)); + //console.log("CANONFLOWS=" + escodegen.generate(node)); node = _disassemble(node, options); - //console.log("DISASSEMBLE=" + pp(node)) + //console.log("DISASSEMBLE=" + escodegen.generate(node)) node = _callbackify(node, options); - //console.log("CALLBACKIFY=" + pp(node)) + //console.error(dump(node)); + //console.log("CALLBACKIFY=" + escodegen.generate(node)) var used = {}; node = _simplify(node, options, used); - - var result = format(node, options.lines); + //fixRanges(node); + addNewlines(node); + var result = escodegen.generate(node, { + comment: true, + }); + // remove curly braces around generated source + result = result[0] === '{' ? result.substring(1, result.length - 1) : result; + // turn comments into newlines + //result = result.replace(/\n\s*/g, ' ').replace(/\/\*undefined\*\//g, '\n'); + result = result.split(/\/\*undefined\*\/\n/).map(function(s) { + return s.replace(/\n\s*/g, ' '); + }).join('\n'); // add helpers at beginning so that __g is initialized before any other code if (!options.noHelpers) { diff --git a/lib/fibers-fast/transform-esprima.js b/lib/fibers-fast/transform-esprima.js deleted file mode 100644 index c9d4b19c..00000000 --- a/lib/fibers-fast/transform-esprima.js +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright 2011 Marcel Laverdet -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -"use strict"; -if (typeof exports !== 'undefined') { - var esprima = require('esprima'); - var sourceMap = require('../util/source-map'); -} -(function(exports) { - exports.transform = transform; - exports.version = require("../version").version + " (fibers-fast --esprima)"; - // hack to fix #123 - exports.transform.version = exports.version; - -var Walker = require('../fibers/walker-esprima'); - -function aggressiveReplace(source) { - // arr.forEach_(_, function(_, item) {...}) --> arr.forEach(function(item, __, _) {...}) - // The __ parameter captures index param (2 chars so that replace preserves length) - // we append _ to callback args list because transform needs async marker on callback. - function testArg(arg) { - return new RegExp('\\b' + arg + '\\b').test(source) ? null : arg; - } - function unusedIdent() { - var arg; - if (arg = testArg('__')) return arg; - for (var i = 0; i < 26 && !arg; i++) { - if (arg = testArg(String.fromCharCode(0x41) + '_')) return arg; - if (arg = testArg('_' + String.fromCharCode(0x41))) return arg; - if (arg = testArg(String.fromCharCode(0x61) + '_')) return arg; - if (arg = testArg('_' + String.fromCharCode(0x61))) return arg; - } - return null; - } - var arg = unusedIdent(); - if (!arg) { - console.log("cannot apply aggressive optimization!!"); - return source; - } else { - return source.replace(/\.(forEach|map|filter|every|some|reduce)_\(_, function\(_, ([^)]+)\)/g, function(all, name, params) { - return '.' + name + '(function(' + params + ', ' + arg + ', _)'; - }); - } -} - -/** - * Transforms code to be streamliney. Line numbers are not maintained, but could be if I could - * figure out how to do it with uglifyjs. - */ -function transform(source, options) { - source = source.replace(/\r\n/g, "\n"); - options = options || {}; - if (options.aggressive) source = aggressiveReplace(source); - var callback = options.callback || '_'; - var didRewrite = 0; - var position = 0; - var buffer = ''; - var async = false; - - function isAsyncArg(arg) { - return arg.type === 'UnaryExpression' && arg.operator === '~' && arg.argument.type === 'Identifier' && arg.argument.name === callback; - } - function isArrayArg(arg) { - return arg.type === 'ArrayExpression' && arg.elements.length === 1 && arg.elements[0].type === 'Identifier' && arg.elements[0].name === callback; - } - function isLShiftArg(arg) { - return arg.type === 'BinaryExpression' && arg.operator === '<<' && // - arg.left.type === 'Identifier' && arg.left.name === callback; - } - function isRShiftArg(arg) { - return arg.type === 'BinaryExpression' && arg.operator === '>>' && // - arg.left.type === 'Identifier' && arg.left.name === callback; - } - function isFutureArg(arg) { - return arg.type === 'UnaryExpression' && (arg.operator === '!' || arg.operator === 'void') // - && arg.argument.type === 'Identifier' && arg.argument.name === callback; - } - function isAsyncParam(param) { - return param === callback || (param.type === 'Identifier' && param.name === callback); - } - /** - * Finds the index of the callback param in an argument list, -1 if not found. - */ - function getCallback(args, testFn, lineno) { - var idx = -1; - for (var ii = 0; ii < args.length; ++ii) { - if (testFn(args[ii])) { - if (idx === -1) { - idx = ii; - } else { - lineno = lineno || args[ii].loc.start.line; - throw new Error('Callback argument used more than once in function call on line '+ lineno); - } - } - } - return idx; - } - - /** - * Adds to `buffer` everything that hasn't been rendered so far. - */ - function catchup(end) { - if (end < position || end === undefined) { - throw new Error('Invalid catchup, '+ position+ ' to '+ end); - } - buffer += source.substring(position, end); - position = end; - } - - function skipTo(pos) { - buffer += source.substring(position, pos).replace(/\S/g, ''); - position = pos; - } - - function startsWith(str, start, pat) { - return str.substring(start, start + pat.length) === pat; - } - - function endsWith(str, end, pat) { - return str.substring(end - pat.length, end) === pat; - } - - var walk = Walker({ - Function: function(name, args, body) { - // Open this function - if (name === callback) { - throw new Error('Invalid usage of callback on line '+ this.loc.start.line); - } - var idx = getCallback(args, isAsyncParam, this.loc.start.line); - catchup(this.body.range[0] + 1); - var oldAsync = async; - async = idx !== -1 || this.forceAsync; - walk(this.body); - async = oldAsync; - }, - CallExpression: function(expr, args) { - if (expr.type === 'Identifier' && expr.name === callback && args.length === 2) { - catchup(expr.range[0]); - buffer += 'fstreamline__.star'; - skipTo(expr.range[1]); - args.map(walk); - ++didRewrite; - return; - } - if (expr.type === 'MemberExpression' && !expr.computed && args.length === 2 && args[0].type === 'Identifier' && args[0].name === callback // - && args[1].type === 'Identifier' && args[1].name === callback) { - if (!async) throw error(this.loc.start.line, "Function contains async calls but does not have _ parameter"); - catchup(this.range[0]); - buffer += ('fstreamline__.then.call(this,'); - skipTo(expr.object.range[0]); - walk(expr.object); - catchup(expr.object.range[1]); - buffer += (', "' + expr.property.name + '", _)'); - skipTo(this.range[1]); - ++didRewrite; - return; - } - var idxFast = getCallback(args, isAsyncParam); - var idx = getCallback(args, isAsyncArg); - var idxArray = getCallback(args, isArrayArg); - if (idx === -1) idx = idxArray; - if ((idxFast !== -1 || idx !== -1) && !async) throw new Error("Function contains async calls but does not have _ parameter on line " + this.loc.start.line); - function walkExpr() { - skipTo(expr.range[0]); - if (expr.type === 'MemberExpression') { - if (!expr.computed) { - // Method call: foo.bar(_) - walk(expr.object); - catchup(expr.object.range[1]); - buffer += ', '+ JSON.stringify(expr.property.name); - } else { - // Dynamic method call: foo[bar](_) - walk(expr.object); - catchup(expr.object.range[1]); - buffer += ', '; - skipTo(expr.property.range[0]); - walk(expr.property); - catchup(expr.property.range[1]); - } - skipTo(expr.range[1]); - } else { - // Function call - buffer += 'null, '; - walk(expr); - catchup(expr.range[1]); - } - } - if (idx !== -1) { - // Rewrite streamlined calls - // issue #108: process between expr.range[0] and last arg end rather than this.range[0]/end - catchup(this.range[0]); - buffer += 'fstreamline__.invoke('; - walkExpr(); - // Render arguments - buffer += ', ['; - skipTo(args[0].range[0]); - for (var ii = 0; ii < args.length; ++ii) { - catchup(args[ii].range[0]); - if (ii !== idx) { - walk(args[ii]); - catchup(args[ii].range[1]); - } else { - buffer += '_'; - skipTo(args[ii].range[1]); - } - } - var options = idx; - if (idxArray !== -1) options = '{ callbackIndex: ' + idx + ', returnArray: true }'; - buffer += '], '+ options + ')'; - skipTo(this.range[1]); - ++didRewrite; - } else if ((idx = getCallback(args, isRShiftArg)) !== -1) { - catchup(this.range[0]); - skipTo(expr.range[0]); - if (expr.type === 'MemberExpression' && !expr.computed) { - buffer += 'fstreamline__.createBound('; - walk(expr.object); - catchup(expr.object.range[1]); - buffer += ",'" + expr.property.name + "'"; - skipTo(expr.range[1]); - } else { - buffer += 'fstreamline__.create('; - walk(expr); - catchup(expr.range[1]); - } - buffer += ',' + idx + ')('; - for (var ii = 0; ii < args.length; ++ii) { - skipTo(args[ii].range[0]); - if (ii > 0) buffer += ', '; - if (ii !== idx) { - walk(args[ii]); - catchup(args[ii].range[1]); - } else { - var arg = args[ii].right; - skipTo(arg.range[0]); - walk(arg); - catchup(arg.range[1]); - skipTo(args[ii].range[1]); - } - } - buffer += ')'; - skipTo(this.range[1]); - ++didRewrite; - } else if ((idx = getCallback(args, isLShiftArg)) !== -1) { - catchup(this.range[0]); - skipTo(expr.range[0]); - walk(expr); - catchup(expr.range[1]); - buffer += '('; - for (var ii = 0; ii < args.length; ++ii) { - skipTo(args[ii].range[0]); - if (ii > 0) buffer += ', '; - if (ii !== idx) { - walk(args[ii]); - catchup(args[ii].range[1]); - } else { - var arg = args[ii].right; - if (arg.type !== 'FunctionExpression') throw new Error("Expected function after _ << "); - var idx2 = getCallback(arg.params, isAsyncParam); - if (idx2 === -1) throw new Error("Expected async function after _ << "); - buffer += 'fstreamline__.create('; - skipTo(arg.range[0]); - walk(arg); - catchup(arg.range[1]); - buffer += ',' + idx2 + ', true)' - skipTo(args[ii].range[1]); - } - } - buffer += ')'; - skipTo(this.range[1]); - ++didRewrite; - } else if ((idx = getCallback(args, isFutureArg)) !== -1) { - var isPromise = args[idx].operator === "void"; - catchup(this.range[0]); - buffer += 'fstreamline__.' + (isPromise ? 'promise' : 'spin') + '('; - walkExpr(); - buffer += ', ['; - for (var ii = 0; ii < args.length; ++ii) { - skipTo(args[ii].range[0]); - if (ii > 0) buffer += ', '; - if (ii !== idx) { - walk(args[ii]); - catchup(args[ii].range[1]); - } else { - catchup(args[ii].range[0]); - buffer += isPromise ? 'null' : 'false'; - skipTo(args[ii].range[1]); - } - } - catchup(args[args.length - 1].range[1]); - buffer += '], '+ idx + ')'; - skipTo(this.range[1]); - ++didRewrite; - } else { - var paren = 0; - if (source[this.range[0]] === '(' && source[this.range[0] + 1] === '(' && - source[this.range[1] - 1] === ')' && source[this.range[1] - 2] === ')') { - paren = 1; - } - if (startsWith(source, this.range[0] + paren, '(function() {')) { - // handle coffeescript wrappers: set the forceAsync flag - // so that we don't get an error about _ being used inside non async function - if (endsWith(source, this.range[1] - paren, '})()')) { - expr.forceAsync = async; - } - if (endsWith(source, this.range[1] - paren, '}).call(this)') // - || endsWith(source, this.range[1] - paren, '}).call(_this)') // - || endsWith(source, this.range[1] - paren, '}).apply(this, arguments)')) { - expr.object.forceAsync = async; - } - } - walk(expr); - args.map(walk); - } - }, - Property: function() { - // Dont't walk the property key, because that's an identifier and it will be clobbered, per - // the below code - walk(this.value); - }, - MemberExpression: function() { - // See comment above for Property - walk(this.object); - if (this.computed) walk(this.property) - }, - UnaryExpression: function(operator, argument) { - if (operator === '!' && argument.type === 'Identifier' && argument.name === callback) { - catchup(this.range[0]); - buffer += 'null'; - skipTo(this.range[1]); - } else { - walk(argument); - } - }, - }); - - // Walk parsed source, rendering along the way - var originalSource = source; - var optStr = "(function(){"; - if (options.promise) { - var arg = typeof options.promise === "string" ? "'" + options.promise + "'" : "true"; - optStr += "fstreamline__.globals.setPromise(" + arg + ");"; - } - optStr += "})();"; - source = 'var fstreamline__ = require("' + (options.internal ? '..' : 'streamline/lib') + '/fibers-fast/runtime");' + optStr + ' (function(_) { ' + // - source + // - '\n})(_ >> function(err) {\n' + // - ' if (err) throw err;\n' + // - '});\n'; - var parsed = esprima.parse(source, { - loc: true, - range: true, - }); - walk(parsed); - buffer += source.substring(position); - - if (didRewrite > 0) { - return buffer; - } else { - return originalSource; - } -} -})(typeof exports !== 'undefined' ? exports : (window.Streamline = window.Streamline || {})); diff --git a/lib/fibers-fast/transform.js b/lib/fibers-fast/transform.js index 0e37382f..c9d4b19c 100644 --- a/lib/fibers-fast/transform.js +++ b/lib/fibers-fast/transform.js @@ -17,18 +17,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. "use strict"; -return module.exports = require('./transform-esprima'); if (typeof exports !== 'undefined') { - var Narcissus = require('../../deps/narcissus'); + var esprima = require('esprima'); + var sourceMap = require('../util/source-map'); } (function(exports) { exports.transform = transform; - exports.version = require("../version").version + " (fibers-fast)"; + exports.version = require("../version").version + " (fibers-fast --esprima)"; // hack to fix #123 exports.transform.version = exports.version; -var t = Narcissus.definitions.tokenIds; -var Walker = require('../fibers/walker'); +var Walker = require('../fibers/walker-esprima'); function aggressiveReplace(source) { // arr.forEach_(_, function(_, item) {...}) --> arr.forEach(function(item, __, _) {...}) @@ -74,22 +73,25 @@ function transform(source, options) { var async = false; function isAsyncArg(arg) { - return arg.type === t.BITWISE_NOT && arg.children[0].type === t.IDENTIFIER && arg.children[0].value === callback; + return arg.type === 'UnaryExpression' && arg.operator === '~' && arg.argument.type === 'Identifier' && arg.argument.name === callback; } function isArrayArg(arg) { - return arg.type === t.ARRAY_INIT && arg.children.length === 1 && arg.children[0].type === t.IDENTIFIER && arg.children[0].value === callback; + return arg.type === 'ArrayExpression' && arg.elements.length === 1 && arg.elements[0].type === 'Identifier' && arg.elements[0].name === callback; } function isLShiftArg(arg) { - return arg.type === t.LSH && arg.children[0].type === t.IDENTIFIER && arg.children[0].value === callback; + return arg.type === 'BinaryExpression' && arg.operator === '<<' && // + arg.left.type === 'Identifier' && arg.left.name === callback; } function isRShiftArg(arg) { - return arg.type === t.RSH && arg.children[0].type === t.IDENTIFIER && arg.children[0].value === callback; + return arg.type === 'BinaryExpression' && arg.operator === '>>' && // + arg.left.type === 'Identifier' && arg.left.name === callback; } function isFutureArg(arg) { - return (arg.type === t.NOT || arg.type === t.VOID) && arg.children[0].type === t.IDENTIFIER && arg.children[0].value === callback; + return arg.type === 'UnaryExpression' && (arg.operator === '!' || arg.operator === 'void') // + && arg.argument.type === 'Identifier' && arg.argument.name === callback; } function isAsyncParam(param) { - return param === callback || (param.type === t.IDENTIFIER && param.value === callback); + return param === callback || (param.type === 'Identifier' && param.name === callback); } /** * Finds the index of the callback param in an argument list, -1 if not found. @@ -101,7 +103,7 @@ function transform(source, options) { if (idx === -1) { idx = ii; } else { - lineno = lineno || args[ii].lineno; + lineno = lineno || args[ii].loc.start.line; throw new Error('Callback argument used more than once in function call on line '+ lineno); } } @@ -134,37 +136,37 @@ function transform(source, options) { } var walk = Walker({ - 'function': function(name, args, body) { + Function: function(name, args, body) { // Open this function if (name === callback) { - throw new Error('Invalid usage of callback on line '+ this.lineno); + throw new Error('Invalid usage of callback on line '+ this.loc.start.line); } - var idx = getCallback(args, isAsyncParam, this.lineno); - catchup(this.body.start + 1); + var idx = getCallback(args, isAsyncParam, this.loc.start.line); + catchup(this.body.range[0] + 1); var oldAsync = async; async = idx !== -1 || this.forceAsync; walk(this.body); async = oldAsync; }, - 'call': function(expr, args) { - if (expr.type === t.IDENTIFIER && expr.value === '_' && args.length === 2) { - catchup(expr.start); + CallExpression: function(expr, args) { + if (expr.type === 'Identifier' && expr.name === callback && args.length === 2) { + catchup(expr.range[0]); buffer += 'fstreamline__.star'; - skipTo(expr.end); + skipTo(expr.range[1]); args.map(walk); ++didRewrite; return; } - if (expr.type === t.DOT && args.length === 2 && args[0].type === t.IDENTIFIER && args[0].value === callback // - && args[1].type === t.IDENTIFIER && args[1].value === callback) { - if (!async) throw error(this.lineno, "Function contains async calls but does not have _ parameter"); - catchup(this.start); - skipTo(expr.children[0].start); + if (expr.type === 'MemberExpression' && !expr.computed && args.length === 2 && args[0].type === 'Identifier' && args[0].name === callback // + && args[1].type === 'Identifier' && args[1].name === callback) { + if (!async) throw error(this.loc.start.line, "Function contains async calls but does not have _ parameter"); + catchup(this.range[0]); buffer += ('fstreamline__.then.call(this,'); - walk(expr.children[0]); - catchup(expr.children[0].end); - buffer += (', "' + expr.children[1].value + '", _'); - skipTo(args[1].end); + skipTo(expr.object.range[0]); + walk(expr.object); + catchup(expr.object.range[1]); + buffer += (', "' + expr.property.name + '", _)'); + skipTo(this.range[1]); ++didRewrite; return; } @@ -172,163 +174,177 @@ function transform(source, options) { var idx = getCallback(args, isAsyncArg); var idxArray = getCallback(args, isArrayArg); if (idx === -1) idx = idxArray; - if ((idxFast !== -1 || idx !== -1) && !async) throw new Error("Function contains async calls but does not have _ parameter on line " + this.lineno); + if ((idxFast !== -1 || idx !== -1) && !async) throw new Error("Function contains async calls but does not have _ parameter on line " + this.loc.start.line); function walkExpr() { - if (expr.type === t.DOT) { - // Method call: foo.bar(_) - walk(expr.children[0]); - catchup(expr.children[0].end); - buffer += ', '+ JSON.stringify(expr.children[1].value); - } else if (expr.type === t.INDEX) { - // Dynamic method call: foo[bar](_) - walk(expr.children[0]); - catchup(expr.children[0].end); - buffer += ', '; - skipTo(expr.children[1].start); - walk(expr.children[1]); - catchup(expr.children[1].end); + skipTo(expr.range[0]); + if (expr.type === 'MemberExpression') { + if (!expr.computed) { + // Method call: foo.bar(_) + walk(expr.object); + catchup(expr.object.range[1]); + buffer += ', '+ JSON.stringify(expr.property.name); + } else { + // Dynamic method call: foo[bar](_) + walk(expr.object); + catchup(expr.object.range[1]); + buffer += ', '; + skipTo(expr.property.range[0]); + walk(expr.property); + catchup(expr.property.range[1]); + } + skipTo(expr.range[1]); } else { // Function call buffer += 'null, '; walk(expr); - catchup(expr.end); + catchup(expr.range[1]); } } if (idx !== -1) { // Rewrite streamlined calls - // issue #108: process between expr.start and last arg end rather than this.start/end - catchup(expr.start); + // issue #108: process between expr.range[0] and last arg end rather than this.range[0]/end + catchup(this.range[0]); buffer += 'fstreamline__.invoke('; walkExpr(); // Render arguments buffer += ', ['; - skipTo(args[0].start); + skipTo(args[0].range[0]); for (var ii = 0; ii < args.length; ++ii) { - catchup(args[ii].start); + catchup(args[ii].range[0]); if (ii !== idx) { walk(args[ii]); - catchup(args[ii].end); + catchup(args[ii].range[1]); } else { buffer += '_'; - skipTo(args[ii].end); + skipTo(args[ii].range[1]); } } - catchup(args[args.length - 1].end); var options = idx; if (idxArray !== -1) options = '{ callbackIndex: ' + idx + ', returnArray: true }'; - buffer += '], '+ options; + buffer += '], '+ options + ')'; + skipTo(this.range[1]); ++didRewrite; } else if ((idx = getCallback(args, isRShiftArg)) !== -1) { - catchup(expr.start); - if (expr.type === t.DOT) { + catchup(this.range[0]); + skipTo(expr.range[0]); + if (expr.type === 'MemberExpression' && !expr.computed) { buffer += 'fstreamline__.createBound('; - walk(expr.children[0]); - catchup(expr.children[0].end); - buffer += ",'" + expr.children[1].value + "'"; - skipTo(expr.end); + walk(expr.object); + catchup(expr.object.range[1]); + buffer += ",'" + expr.property.name + "'"; + skipTo(expr.range[1]); } else { buffer += 'fstreamline__.create('; walk(expr); + catchup(expr.range[1]); } - catchup(expr.end); - buffer += ',' + idx + ')'; + buffer += ',' + idx + ')('; for (var ii = 0; ii < args.length; ++ii) { - catchup(args[ii].start); + skipTo(args[ii].range[0]); + if (ii > 0) buffer += ', '; if (ii !== idx) { walk(args[ii]); - catchup(args[ii].end); + catchup(args[ii].range[1]); } else { - var arg = args[ii].children[1]; - skipTo(arg.start); + var arg = args[ii].right; + skipTo(arg.range[0]); walk(arg); - catchup(arg.end); - skipTo(args[ii].end); + catchup(arg.range[1]); + skipTo(args[ii].range[1]); } } + buffer += ')'; + skipTo(this.range[1]); ++didRewrite; } else if ((idx = getCallback(args, isLShiftArg)) !== -1) { - catchup(expr.start); + catchup(this.range[0]); + skipTo(expr.range[0]); walk(expr); - catchup(expr.end); + catchup(expr.range[1]); + buffer += '('; for (var ii = 0; ii < args.length; ++ii) { - catchup(args[ii].start); + skipTo(args[ii].range[0]); + if (ii > 0) buffer += ', '; if (ii !== idx) { walk(args[ii]); - catchup(args[ii].end); + catchup(args[ii].range[1]); } else { - var arg = args[ii].children[1]; - if (arg.type !== t.FUNCTION) throw new Error("Expected function after _ << "); + var arg = args[ii].right; + if (arg.type !== 'FunctionExpression') throw new Error("Expected function after _ << "); var idx2 = getCallback(arg.params, isAsyncParam); if (idx2 === -1) throw new Error("Expected async function after _ << "); - buffer += 'fstreamline__.create('; - skipTo(arg.start); + buffer += 'fstreamline__.create('; + skipTo(arg.range[0]); walk(arg); - catchup(arg.end); - buffer += ',' + idx2 + ', true)' - skipTo(args[ii].end); + catchup(arg.range[1]); + buffer += ',' + idx2 + ', true)' + skipTo(args[ii].range[1]); } } + buffer += ')'; + skipTo(this.range[1]); ++didRewrite; } else if ((idx = getCallback(args, isFutureArg)) !== -1) { - var isPromise = args[idx].type === t.VOID; - catchup(this.start); + var isPromise = args[idx].operator === "void"; + catchup(this.range[0]); buffer += 'fstreamline__.' + (isPromise ? 'promise' : 'spin') + '('; walkExpr(); buffer += ', ['; - skipTo(args[0].start); for (var ii = 0; ii < args.length; ++ii) { - catchup(args[ii].start); + skipTo(args[ii].range[0]); + if (ii > 0) buffer += ', '; if (ii !== idx) { walk(args[ii]); - catchup(args[ii].end); + catchup(args[ii].range[1]); } else { - catchup(args[ii].start); + catchup(args[ii].range[0]); buffer += isPromise ? 'null' : 'false'; - skipTo(args[ii].end); + skipTo(args[ii].range[1]); } } - catchup(args[args.length - 1].end); + catchup(args[args.length - 1].range[1]); buffer += '], '+ idx + ')'; - skipTo(this.end); + skipTo(this.range[1]); ++didRewrite; } else { var paren = 0; - if (source[this.start] === '(' && source[this.start + 1] === '(' && - source[this.end - 1] === ')' && source[this.end - 2] === ')') { + if (source[this.range[0]] === '(' && source[this.range[0] + 1] === '(' && + source[this.range[1] - 1] === ')' && source[this.range[1] - 2] === ')') { paren = 1; } - if (startsWith(source, this.start + paren, '(function() {')) { + if (startsWith(source, this.range[0] + paren, '(function() {')) { // handle coffeescript wrappers: set the forceAsync flag // so that we don't get an error about _ being used inside non async function - if (endsWith(source, this.end - paren, '})()')) { + if (endsWith(source, this.range[1] - paren, '})()')) { expr.forceAsync = async; } - if (endsWith(source, this.end - paren, '}).call(this)') // - || endsWith(source, this.end - paren, '}).call(_this)') // - || endsWith(source, this.end - paren, '}).apply(this, arguments)')) { - expr.children[0].forceAsync = async; + if (endsWith(source, this.range[1] - paren, '}).call(this)') // + || endsWith(source, this.range[1] - paren, '}).call(_this)') // + || endsWith(source, this.range[1] - paren, '}).apply(this, arguments)')) { + expr.object.forceAsync = async; } } walk(expr); args.map(walk); } }, - 'property_init': function() { + Property: function() { // Dont't walk the property key, because that's an identifier and it will be clobbered, per // the below code - walk(this.children[1]); + walk(this.value); }, - 'dot': function() { - // See comment above for propery_init - walk(this.children[0]); + MemberExpression: function() { + // See comment above for Property + walk(this.object); + if (this.computed) walk(this.property) }, - 'not': function(value) { - if (value.type === t.IDENTIFIER && value.value === callback) { - catchup(this.start); + UnaryExpression: function(operator, argument) { + if (operator === '!' && argument.type === 'Identifier' && argument.name === callback) { + catchup(this.range[0]); buffer += 'null'; - skipTo(this.end); + skipTo(this.range[1]); } else { - walk(value); + walk(argument); } }, }); @@ -346,7 +362,10 @@ function transform(source, options) { '\n})(_ >> function(err) {\n' + // ' if (err) throw err;\n' + // '});\n'; - var parsed = Narcissus.parser.parse(source, options.sourceName); + var parsed = esprima.parse(source, { + loc: true, + range: true, + }); walk(parsed); buffer += source.substring(position); diff --git a/lib/fibers/transform-esprima.js b/lib/fibers/transform-esprima.js deleted file mode 100644 index 42a35143..00000000 --- a/lib/fibers/transform-esprima.js +++ /dev/null @@ -1,664 +0,0 @@ -// Copyright 2011 Marcel Laverdet -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -"use strict"; -if (typeof exports !== 'undefined') { - var esprima = require('esprima'); - var sourceMap = require('../util/source-map'); -} -(function(exports) { - exports.transform = transform; - exports.version = require("../version").version + " (fibers --esprima)"; - // hack to fix #123 - exports.transform.version = exports.version; - -var Walker = require('../fibers/walker-esprima'); - -// TODO ensure `foo(_)` calls have a bounding fiber. streamline is smart enough to allow this: -// ~function() { foo(_) }(); -// and disallow this: -// foo(function() { foo(_) }); - -/** - * Finds all variables which have been declared locally in this function. - */ -function getLocals(fn, recurse) { - var names = Object.create(null); - function decl() { - var vars = this.declarations; - for (var ii = 0; ii < vars.length; ++ii) { - names[vars[ii].id.name] = vars[ii].id.name; - vars[ii].init && walk(vars[ii].init); - } - } - var walk = Walker({ - Function: function(name, args, body) { - if (this.type !== 'FunctionExpression') { - names[this.id.name] = this.id.name; - } - // Don't walk further by default - if (recurse) { - for (var ii in getLocals(this, true)) { - names[ii] = ii; - } - } - }, - VariableDeclaration: decl, - }); - fn.body.body.map(walk); - for (var ii = 0; ii < fn.params; ++ii) { - names[fn.params[ii].name] = fn.params[ii].name; - } - if (fn.id && fn.type === 'FunctionExpression') { - names[fn.id.name] = fn.id.name; - } - return names; -} - -/** - * Create a new object inheriting from `base` and extended by `vector` - */ -function chain(base, vector) { - var obj = Object.create(base); - for (var ii in vector) { - obj[ii] = vector[ii]; - } - return obj; -} - -/** - * Transforms code to be streamliney. Line numbers are not maintained, but could be if I could - * figure out how to do it with uglifyjs. - */ -function transform(source, options) { - source = source.replace(/\r\n/g, "\n"); - options = options || {}; - var callback = options.callback || '_'; - var didRewrite = 0; - var position = 0; - var buffer = new sourceMap.SourceNode(); - var scope = Object.create(null); - var streamlined = Object.create(null); - var verboten = Object.create(null); - var async = false; - var finallies = 0; - var allIdentifiers; - - var originalSource = source; - var optStr = "(function(){"; - if (options.oldStyleFutures) optStr += "fstreamline__.globals.oldStyleFutures = true; "; - if (options.promise) { - var arg = typeof options.promise === "string" ? "'" + options.promise + "'" : "true"; - optStr += "fstreamline__.globals.setPromise(" + arg + ");"; - } - optStr += "})();"; - var prelude = 'var fstreamline__ = require("' + (options.internal ? '..' : 'streamline/lib') + '/fibers/runtime"); ' + optStr + '(function(_) { '; - var postlude = - '\n}.call(this, function(err) {\n'+ - ' if (err) throw err;\n'+ - '}));\n'; - - function error(lineno, message) { - return new Error(options.sourceName + ":" + lineno + ": " + message); - } - /** - * Walks a parse tree and finds all functions which have been declared as streamline functions. - * Also returns variables which are always streamlined functions. Note that this does not enter - * new function scopes. - */ - function getStreamlinedDeclarations(fn) { - var declared = Object.create(null); - var exprs = Object.create(null); - var not = Object.create(null); - var lines = Object.create(null); - - var walk = Walker({ - Function: function(name, args, body) { - if (this.type !== 'FunctionExpression') { - var idx = getCallback(args); - (idx === -1 ? not : declared)[name] = getCallbackDefault(args, body) || idx; - lines[name] = this.loc.start.line; - } - // Don't walk further - }, - VariableDeclarator: function(name, initializer) { - if (!initializer) { - return; - } - if (initializer.type === 'FunctionExpression') { - (getCallback(initializer.params) === -1 ? not : exprs)[name] = true; - } else { - not[name] = true; - } - lines[name] = this.loc.start.line; - walk(initializer); - }, - AssignmentExpression: function(left, right) { - var name = left.type === 'Identifier' && left.name; - if (name) { - if (right.type === 'FunctionExpression') { - (getCallback(right.params) === -1 ? not : exprs)[name] = true; - } else { - not[name] = true; - } - lines[name] = left.loc.start.line; - } - walk(right); - }, - }); - fn.body.body.map(walk); - for (var ii in declared) { - exprs[ii] = true; - } - for (var ii in not) { - delete exprs[ii]; - } - return { - declared: declared, - strict: exprs, - lines: lines, - }; - } - - /** - * Finds the index of the callback param in an argument list, -1 if not found. - */ - function getCallback(args, lineno) { - var idx = -1; - for (var ii = 0; ii < args.length; ++ii) { - if (args[ii] === callback || (args[ii].type === 'Identifier' && args[ii].name === callback) || // - (args[ii].type === 'UnaryExpression' && args[ii].operator === '~' && args[ii].argument.type === 'Identifier' && args[ii].argument.name === callback) || - (args[ii].type === 'ArrayExpression' && args[ii].elements.length === 1 && args[ii].elements[0].type === 'Identifier' && args[ii].elements[0].name === callback)) { - if (idx === -1) { - idx = ii; - } else { - lineno = lineno || args[ii].loc.start.line; - throw error(lineno, 'Callback argument used more than once in function call '); - } - } - } - return idx; - } - - /** - * Figure out line number information. - */ - var newlines = (function() { - var r = [-1]; - for (var i = source.indexOf("\n"); i >= 0; i = source.indexOf("\n", i+1)) { - r.push(i); - } - return r; - })(); - var lines = source.split("\n"); - // Find the greatest index `i` such that arr[i] < val. - // (Slightly different from the C++ lower_bound function, doesn't allow equality.) - function lower_bound(arr, val) { - var lo = 0, hi = newlines.length; - while ((hi - lo) > 1) { - var mid = Math.floor((hi+lo)/2); - if (newlines[mid] >= val) { - hi = mid; - } else { - lo = mid; - } - } - return lo; - } - function substr(start, end) { - var orig = start - prelude.length; - if (orig < 0 || (end - prelude.length) > originalSource.length) { - return source.substring(start, end); - } - // Binary search to find the line number. - var line = lower_bound(newlines, orig); - var col = orig - (newlines[line] + 1); - return new sourceMap.SourceNode( - line + 1, // Lines are 1-indexed. - col, // Columns are 0-indexed. - options.sourceName, - source.substring(start, end) - ); - } - - /** - * Adds to `buffer` everything that hasn't been rendered so far. - */ - function catchup(end) { - function go(to) { - if (to < position || to === undefined) { - throw new Error('BUG: Invalid catchup, '+ position+ ' to '+ to); - } - buffer.add(substr(position, to)); - position = to; - } - if (end < position || end === undefined) { - throw new Error('Invalid catchup, '+ position+ ' to '+ end); - } - while (position < end) { - var s = source.substring(position); - var m = s.match(/^\s/); - if (m) { - buffer.add(s[0]); - position++; - } else if (!(m = s.match(/^\S[\s\S]*?\b/)) || m[0].length > (end - position)) { - go(end); - } else { - go(position + m[0].length); - } - } - } - - function skipTo(pos) { - buffer.add(source.substring(position, pos).replace(/\S/g, '')); - position = pos; - } - - function startsWith(str, start, pat) { - return str.substring(start, start + pat.length) === pat; - } - - function endsWith(str, end, pat) { - return str.substring(end - pat.length, end) === pat; - } - - function getCallbackDefault(params, body) { - var paramI = -1; - for (var i = 0; i < body.length; i++) { - var child = body[i]; - if (i === 0 && child.type === 'VariableDeclaration') { continue; } - if (child.type !== 'IfStatement') return null; - if (child.test.type !== 'BinaryExpression' || child.test.operator !== '==') return null; - var ident = child.test.left; - if (ident.type !== 'Identifier') return null; - if (child.test.right.type !== 'Literal' || child.test.right.value !== null) return false; - if (!child.consequent.body || child.consequent.body.length !== 1) return false; - var assign = child.consequent.body[0]; - if (assign.type !== 'ExpressionStatement') return false; - assign = assign.expression; - if (assign.type !== 'AssignmentExpression') return false; - if (assign.left.type !== 'Identifier') return false; - if (assign.left.name !== ident.name) return false; - // we got a candidate - let us find the param - while (++paramI < params.length) { - if (ident.name === params[paramI].name) break; - } - if (paramI === params.length) return null; - if (ident.name === callback) { - body.splice(i, 1); // remove it from body - var def = assign.right; - return '{callbackIndex:' + paramI + ",callbackDefault:function(){ return " + // - source.substring(def.range[0], def.range[1]) + ';}}' - } - } - // we did not find it - return null; - } - - var walk = Walker({ - Function: function(name, args, body) { - // Open this function - if (name === callback) { - throw error(this.loc.start.line, 'Invalid usage of callback'); - } - catchup(this.range[0]); - var idx = getCallback(args, this.loc.start.line), opts; - if (idx !== -1 && this.type === 'FunctionExpression') { - buffer.add('fstreamline__.create('); - opts = getCallbackDefault(args, body) || idx; - ++didRewrite; - } - catchup(this.body.range[0] + 1); - // keep "use strict"; and similar directives at beginning of block - while (body[0] && body[0].type === 'ExpressionStatement' && body[0].expression.type === 'Literal' && typeof body[0].expression.value === 'string') { - catchup(body[0].range[1]); - body.splice(0, 1); - } - - // Analyze the scope of this function for locals and streamlined functions - // We need locals to avoid renaming collisions with streamlined functions, and the streamlined - // functions let us optimize `invoke`. - var locals = getLocals(this); - var localStreamlined = getStreamlinedDeclarations(this); - var oldScope = scope; - var oldStreamlined = streamlined; - var oldVerboten = verboten; - var oldAsync = async; - var oldFinallies = finallies; - async = idx !== -1 || this.forceAsync; - finallies = 0; - scope = chain(scope, locals); - streamlined = chain(streamlined, localStreamlined.strict); - verboten = chain(verboten); - for (var ii in locals) { - if (!localStreamlined.strict[ii]) { - streamlined[ii] = false; - } - verboten[ii] = false; - } - if (idx !== -1 && this.type === 'FunctionExpression' && name) { - // Can't use a streamline'd function by name from within that function - verboten[name] = true; - } - - // Hoist streamlined functions - var hoisted = false; - for (var ii in localStreamlined.declared) { - var fragment = '_', len = 1; - while (scope[ii+ fragment] || allIdentifiers[ii+ fragment]) { - fragment = Array(++len + 1).join('_'); - } - scope[ii] = ii+ fragment; - if (!hoisted) { - buffer.add('var '); - hoisted = true; - } else { - buffer.add(', '); - } - buffer.add(ii+ fragment+ ' = fstreamline__.create(' + ii+ ', ' + localStreamlined.declared[ii] + ',__filename,' + localStreamlined.lines[ii] + ')'); - ++didRewrite; - } - if (hoisted) { - buffer.add(';'); - } - - // Close up the function - body.map(walk); - catchup(this.range[1]); - if (idx !== -1 && this.type === 'FunctionExpression') { - buffer.add(', '+ opts + ',__filename,' + this.loc.start.line + ')'); - } - - // Reset scopes - scope = oldScope; - streamlined = oldStreamlined; - verboten = oldVerboten; - async = oldAsync; - finallies = oldFinallies; - }, - CallExpression: function(expr, args) { - if (expr.type === 'Identifier' && expr.name === '_' && args.length === 2) { - catchup(this.range[0]); - buffer.add('fstreamline__.streamlinify('); - skipTo(args[0].range[0]); - args.map(walk); - catchup(args[1].range[1]); - buffer.add(')'); - skipTo(this.range[1]); - ++didRewrite; - return; - } - if (expr.type === 'MemberExpression' && args.length === 2 && args[0].type === 'Identifier' && args[0].name === callback // - && args[1].type === 'Identifier' && args[1].name === callback) { - if (!async) throw error(this.loc.start.line, "Function contains async calls but does not have _ parameter"); - catchup(this.range[0]); - skipTo(expr.object.range[0]); - buffer.add('fstreamline__.then.call(this,'); - walk(expr.object); - catchup(expr.object.range[1]); - buffer.add(', "' + expr.property.name + '", _)'); - skipTo(this.range[1]); - ++didRewrite; - return; - } - var idx = getCallback(args); - if (idx !== -1 && !async) throw error(this.loc.start.line, "Function contains async calls but does not have _ parameter"); - if (idx !== -1 && expr.type === 'Identifier' && streamlined[expr.name]) { - // Optimized streamline callback. We know this call is to a streamlined function so we can - // just inline it. - catchup(this.range[0]); - if (scope[expr.name] === expr.name) { - // In this case `expr` was declared with a function expression instead of a function - // declaration, so the original function is no longer around. - catchup(expr.range[0]); - buffer.add('('); - catchup(expr.range[1]); - buffer.add('.fstreamlineFunction || 0)'); - } else { - if (true) { // TODO: enable this only for flame graphs - catchup(expr.range[0]); - buffer.add('('); - catchup(expr.range[1]); - // _ postfix is important - buffer.add('_.fstreamlineFunction || 0)'); - } else { - catchup(expr.range[1]); - } - } - buffer.add('('); - for (var ii = 0; ii < args.length; ++ii) { - skipTo(args[ii].range[0]); - if (ii > 0) buffer.add(', '); - if (ii !== idx) { - walk(args[ii]); - catchup(args[ii].range[1]); - } else { - buffer.add('_'); - skipTo(args[ii].range[1]); - } - } - skipTo(this.range[1]); - buffer.add(')'); - } else if (idx !== -1) { - // Rewrite streamlined calls - // issue #108: process between expr.range[0] and last arg end rather than this.range[0]/end - catchup(this.range[0]); - buffer.add('fstreamline__.invoke('); - skipTo(expr.range[0]) - if (expr.type === 'MemberExpression') { - skipTo(expr.object.range[0]); - walk(expr.object); - catchup(expr.object.range[1]); - if (!expr.computed) { - // Method call: foo.bar(_) - buffer.add(', '+ JSON.stringify(expr.property.name)); - } else { - // Dynamic method call: foo[bar](_) - buffer.add(', '); - skipTo(expr.property.range[0]); - walk(expr.property); - catchup(expr.property.range[1]); - } - skipTo(expr.range[1]); - } else { - // Function call - buffer.add('null, '); - walk(expr); - } - catchup(expr.range[1]); - skipTo(args[0].range[0]); - // Render arguments - buffer.add(', ['); - for (var ii = 0; ii < args.length; ++ii) { - skipTo(args[ii].range[0]); - if (ii > 0) buffer.add(', '); - if (ii !== idx) { - walk(args[ii]); - catchup(args[ii].range[1]); - } else { - buffer.add('_'); - skipTo(args[ii].range[1]); - } - } - var options = idx; - if (args[idx].type === 'ArrayExpression') options = '{ callbackIndex: ' + idx + ', returnArray: true }'; - buffer.add('], ' + options + ')'); - skipTo(this.range[1]); - ++didRewrite; - } else { - var paren = 0; - if (source[this.range[0]] === '(' && source[this.range[0] + 1] === '(' && - source[this.range[1] - 1] === ')' && source[this.range[1] - 2] === ')') { - paren = 1; - } - if (startsWith(source, this.range[0] + paren, '(function() {')) { - // handle coffeescript wrappers: set the forceAsync flag - // so that we don't get an error about _ being used inside non async function - if (endsWith(source, this.range[1] - paren, '})()')) { - expr.forceAsync = async; - } - if (endsWith(source, this.range[1] - paren, '}).call(this)') // - || endsWith(source, this.range[1] - paren, '}).call(_this)') // - || endsWith(source, this.range[1] - paren, '}).apply(this, arguments)')) { - expr.object.forceAsync = async; - } - } - walk(expr); - args.map(walk); - } - }, - Identifier: function(name) { - if (name === callback) { - throw error(this.loc.start.line, 'Invalid usage of callback'); - } else if (verboten[name]) { - throw error(this.loc.start.line, 'Invalid use of indentifier `'+ name+ '`'); - } - if (scope[name]) { - catchup(this.range[0]); - buffer.add(scope[name]); - skipTo(this.range[1]); - } else { - // catchup to end will deal with all sort of oddities, like object initializer keys that are - // parsed as identifiers but need to be quoted. - catchup(this.range[1]); - } - }, - Property: function() { - // Dont't walk the property key, because that's an identifier and it will be clobbered, per - // the below code - walk(this.value); - }, - MemberExpression: function() { - // See comment above for propery_init - walk(this.object); - if (this.computed) walk(this.property) - }, - NewExpression: function(expr, args) { - var idx = getCallback(args); - if (idx !== -1) { - // assumes that this is a streamlined function! - catchup(this.range[0]); - skipTo(expr.range[0]); // skip new keyword - buffer.add(" fstreamline__.construct("); - walk(expr); - catchup(expr.range[1]); - buffer.add("," + idx + ")("); - // process arguments to avoid 'invalid usage of callback' error - for (var ii = 0; ii < args.length; ++ii) { - skipTo(args[ii].range[0]); - if (ii > 0) buffer.add(', '); - if (ii !== idx) { - walk(args[ii]); - catchup(args[ii].range[1]); - } else { - buffer.add('_'); - skipTo(args[ii].range[1]); - } - } - buffer.add(")"); - skipTo(this.range[1]); - } else { - walk(expr); - args.map(walk); - } - }, - ReturnStatement: function(argument) { - argument && walk(argument); - fixASI(this); - }, - ThrowStatement: function(argument) { - argument && walk(argument); - fixASI(this); - }, - YieldStatement: function(argument) { - argument && walk(argument); - fixASI(this); - }, - UnaryExpression: function(operator, argument) { - if (operator === '!' || operator === 'void') { - if (argument.type === 'Identifier' && argument.name === callback) { - catchup(this.range[0]); - buffer.add(operator === '!' ? 'false' : 'null'); - skipTo(this.range[1]); - } else { - walk(argument); - } - } else { - walk(argument); - } - }, - TryStatement: function(block, handlers, finalizer) { - walk(block); - handlers.map(walk); - finalizer && walk(finalizer); - }, - ExpressionStatement: function(expression) { - expression && walk(expression); - fixASI(this); - }, - BinaryExpression: function(operator, left, right) { - if (operator === '<<' || operator === '>>') { - walkShift.call(this, left, right); - } else { - walk(left); - walk(right); - } - }, - VariableDeclaration: function(declarations) { - declarations && declarations.map(walk); - if (this.eligibleForASI) fixASI(this); - }, - }); - - // take care of ASI, in case transformation parenthesized next statement - function fixASI(node) { - catchup(node.range[1]); - if (buffer.lastChar() !== ';') buffer.add(';'); - } - - function walkShift(left, right) { - if (left.type === 'Identifier' && left.name === callback) { - catchup(left.range[0]); - skipTo(source.indexOf(this.operator, left.range[1]) + 2); - - walk(right); - ++didRewrite; - } else { - walk(left); - walk(right); - } - } - - // Walk parsed source, rendering along the way - source = prelude + source + postlude; - var parsed = esprima.parse(source, { - loc: true, - range: true, - }); - allIdentifiers = getLocals(parsed.body[2].expression.callee.object, true); - - walk(parsed); - catchup(source.length); - - if (didRewrite > 0) { - return buffer; - } else { - return originalSource; - } -} -})(typeof exports !== 'undefined' ? exports : (window.Streamline = window.Streamline || {})); diff --git a/lib/fibers/transform.js b/lib/fibers/transform.js index f25dbac1..42a35143 100644 --- a/lib/fibers/transform.js +++ b/lib/fibers/transform.js @@ -17,19 +17,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. "use strict"; -return module.exports = require('./transform-esprima'); if (typeof exports !== 'undefined') { - var Narcissus = require('../../deps/narcissus'); + var esprima = require('esprima'); var sourceMap = require('../util/source-map'); } (function(exports) { exports.transform = transform; - exports.version = require("../version").version + " (fibers)"; + exports.version = require("../version").version + " (fibers --esprima)"; // hack to fix #123 exports.transform.version = exports.version; -var t = Narcissus.definitions.tokenIds; -var Walker = require('../fibers/walker'); +var Walker = require('../fibers/walker-esprima'); // TODO ensure `foo(_)` calls have a bounding fiber. streamline is smart enough to allow this: // ~function() { foo(_) }(); @@ -42,15 +40,16 @@ var Walker = require('../fibers/walker'); function getLocals(fn, recurse) { var names = Object.create(null); function decl() { - var vars = this.children; + var vars = this.declarations; for (var ii = 0; ii < vars.length; ++ii) { - names[vars[ii].name] = vars[ii].name; + names[vars[ii].id.name] = vars[ii].id.name; + vars[ii].init && walk(vars[ii].init); } } var walk = Walker({ - 'function': function(name, args, body) { - if (this.functionForm !== 1) { - names[this.name] = this.name; + Function: function(name, args, body) { + if (this.type !== 'FunctionExpression') { + names[this.id.name] = this.id.name; } // Don't walk further by default if (recurse) { @@ -59,16 +58,14 @@ function getLocals(fn, recurse) { } } }, - 'var': decl, - 'const': decl, - 'let': decl, + VariableDeclaration: decl, }); - fn.body.children.map(walk); + fn.body.body.map(walk); for (var ii = 0; ii < fn.params; ++ii) { - names[fn.params[ii]] = fn.params[ii]; + names[fn.params[ii].name] = fn.params[ii].name; } - if (fn.name && fn.functionForm === 1) { - names[fn.name] = fn.name; + if (fn.id && fn.type === 'FunctionExpression') { + names[fn.id.name] = fn.id.name; } return names; } @@ -129,42 +126,42 @@ function transform(source, options) { var exprs = Object.create(null); var not = Object.create(null); var lines = Object.create(null); + var walk = Walker({ - 'function': function(name, args, body) { - if (this.functionForm !== 1) { + Function: function(name, args, body) { + if (this.type !== 'FunctionExpression') { var idx = getCallback(args); (idx === -1 ? not : declared)[name] = getCallbackDefault(args, body) || idx; - lines[name] = this.lineno; + lines[name] = this.loc.start.line; } // Don't walk further }, - 'identifier': function(name, initializer) { + VariableDeclarator: function(name, initializer) { if (!initializer) { return; } - if (initializer.type === t.FUNCTION) { + if (initializer.type === 'FunctionExpression') { (getCallback(initializer.params) === -1 ? not : exprs)[name] = true; } else { not[name] = true; } - lines[name] = this.lineno; + lines[name] = this.loc.start.line; walk(initializer); }, - 'assign': function() { - var name = this.children[0].type === t.IDENTIFIER && this.children[0].value; + AssignmentExpression: function(left, right) { + var name = left.type === 'Identifier' && left.name; if (name) { - var expr = this.children[1]; - if (expr.type === t.FUNCTION) { - (getCallback(expr.params) === -1 ? not : exprs)[name] = true; + if (right.type === 'FunctionExpression') { + (getCallback(right.params) === -1 ? not : exprs)[name] = true; } else { not[name] = true; } - lines[name] = this.children[0].lineno; + lines[name] = left.loc.start.line; } - walk(this.children[1]); + walk(right); }, }); - fn.body.children.map(walk); + fn.body.body.map(walk); for (var ii in declared) { exprs[ii] = true; } @@ -184,13 +181,13 @@ function transform(source, options) { function getCallback(args, lineno) { var idx = -1; for (var ii = 0; ii < args.length; ++ii) { - if (args[ii] === callback || (args[ii].type === t.IDENTIFIER && args[ii].value === callback) || // - (args[ii].type === t.BITWISE_NOT && args[ii].children[0].type === t.IDENTIFIER && args[ii].children[0].value === callback) || - (args[ii].type === t.ARRAY_INIT && args[ii].children.length === 1 && args[ii].children[0].type === t.IDENTIFIER && args[ii].children[0].value === callback)) { + if (args[ii] === callback || (args[ii].type === 'Identifier' && args[ii].name === callback) || // + (args[ii].type === 'UnaryExpression' && args[ii].operator === '~' && args[ii].argument.type === 'Identifier' && args[ii].argument.name === callback) || + (args[ii].type === 'ArrayExpression' && args[ii].elements.length === 1 && args[ii].elements[0].type === 'Identifier' && args[ii].elements[0].name === callback)) { if (idx === -1) { idx = ii; } else { - lineno = lineno || args[ii].lineno; + lineno = lineno || args[ii].loc.start.line; throw error(lineno, 'Callback argument used more than once in function call '); } } @@ -284,28 +281,29 @@ function transform(source, options) { var paramI = -1; for (var i = 0; i < body.length; i++) { var child = body[i]; - if (i === 0 && child.type === t.VAR) { continue; } - if (child.type !== t.IF) return null; - if (child.condition.type !== t.EQ) return null; - var ident = child.condition.children[0]; - if (ident.type !== t.IDENTIFIER) return null; - if (child.condition.children[1].type !== t. NULL) return null; - if (!child.thenPart.children || child.thenPart.children.length !== 1) return null; - var assign = child.thenPart.children[0]; - if (assign.type !== t.SEMICOLON) return null; + if (i === 0 && child.type === 'VariableDeclaration') { continue; } + if (child.type !== 'IfStatement') return null; + if (child.test.type !== 'BinaryExpression' || child.test.operator !== '==') return null; + var ident = child.test.left; + if (ident.type !== 'Identifier') return null; + if (child.test.right.type !== 'Literal' || child.test.right.value !== null) return false; + if (!child.consequent.body || child.consequent.body.length !== 1) return false; + var assign = child.consequent.body[0]; + if (assign.type !== 'ExpressionStatement') return false; assign = assign.expression; - if (assign.type !== t.ASSIGN) if (assign.children[0].type !== t.IDENTIFIER) return null; - if (assign.children[0].value !== ident.value) return null; + if (assign.type !== 'AssignmentExpression') return false; + if (assign.left.type !== 'Identifier') return false; + if (assign.left.name !== ident.name) return false; // we got a candidate - let us find the param while (++paramI < params.length) { - if (ident.value === params[paramI]) break; + if (ident.name === params[paramI].name) break; } if (paramI === params.length) return null; - if (ident.value === callback) { + if (ident.name === callback) { body.splice(i, 1); // remove it from body - var def = assign.children[1]; + var def = assign.right; return '{callbackIndex:' + paramI + ",callbackDefault:function(){ return " + // - source.substring(def.start, def.end) + ';}}' + source.substring(def.range[0], def.range[1]) + ';}}' } } // we did not find it @@ -313,22 +311,22 @@ function transform(source, options) { } var walk = Walker({ - 'function': function(name, args, body) { + Function: function(name, args, body) { // Open this function if (name === callback) { - throw error(this.lineno, 'Invalid usage of callback'); + throw error(this.loc.start.line, 'Invalid usage of callback'); } - catchup(this.start); - var idx = getCallback(args, this.lineno), opts; - if (idx !== -1 && this.functionForm === 1) { + catchup(this.range[0]); + var idx = getCallback(args, this.loc.start.line), opts; + if (idx !== -1 && this.type === 'FunctionExpression') { buffer.add('fstreamline__.create('); opts = getCallbackDefault(args, body) || idx; ++didRewrite; } - catchup(this.body.start + 1); + catchup(this.body.range[0] + 1); // keep "use strict"; and similar directives at beginning of block - while (body[0] && body[0].type === t.SEMICOLON && body[0].expression.type === t.STRING) { - catchup(body[0].end); + while (body[0] && body[0].type === 'ExpressionStatement' && body[0].expression.type === 'Literal' && typeof body[0].expression.value === 'string') { + catchup(body[0].range[1]); body.splice(0, 1); } @@ -353,7 +351,7 @@ function transform(source, options) { } verboten[ii] = false; } - if (idx !== -1 && this.functionForm === 1 && name) { + if (idx !== -1 && this.type === 'FunctionExpression' && name) { // Can't use a streamline'd function by name from within that function verboten[name] = true; } @@ -381,9 +379,9 @@ function transform(source, options) { // Close up the function body.map(walk); - catchup(this.end); - if (idx !== -1 && this.functionForm === 1) { - buffer.add(', '+ opts + ',__filename,' + this.lineno + ')'); + catchup(this.range[1]); + if (idx !== -1 && this.type === 'FunctionExpression') { + buffer.add(', '+ opts + ',__filename,' + this.loc.start.line + ')'); } // Reset scopes @@ -393,254 +391,267 @@ function transform(source, options) { async = oldAsync; finallies = oldFinallies; }, - 'call': function(expr, args) { - if (expr.type === t.IDENTIFIER && expr.value === '_' && args.length === 2) { - catchup(this.start); + CallExpression: function(expr, args) { + if (expr.type === 'Identifier' && expr.name === '_' && args.length === 2) { + catchup(this.range[0]); buffer.add('fstreamline__.streamlinify('); - skipTo(args[0].start); + skipTo(args[0].range[0]); args.map(walk); - catchup(args[1].end); + catchup(args[1].range[1]); buffer.add(')'); - skipTo(this.end); + skipTo(this.range[1]); ++didRewrite; return; } - if (expr.type === t.DOT && args.length === 2 && args[0].type === t.IDENTIFIER && args[0].value === callback // - && args[1].type === t.IDENTIFIER && args[1].value === callback) { - if (!async) throw error(this.lineno, "Function contains async calls but does not have _ parameter"); - catchup(this.start); - skipTo(expr.children[0].start); + if (expr.type === 'MemberExpression' && args.length === 2 && args[0].type === 'Identifier' && args[0].name === callback // + && args[1].type === 'Identifier' && args[1].name === callback) { + if (!async) throw error(this.loc.start.line, "Function contains async calls but does not have _ parameter"); + catchup(this.range[0]); + skipTo(expr.object.range[0]); buffer.add('fstreamline__.then.call(this,'); - walk(expr.children[0]); - catchup(expr.children[0].end); - buffer.add(', "' + expr.children[1].value + '", _'); - skipTo(args[1].end); + walk(expr.object); + catchup(expr.object.range[1]); + buffer.add(', "' + expr.property.name + '", _)'); + skipTo(this.range[1]); ++didRewrite; return; } var idx = getCallback(args); - if (idx !== -1 && !async) throw error(this.lineno, "Function contains async calls but does not have _ parameter"); - if (idx !== -1 && expr.type === t.IDENTIFIER && streamlined[expr.value]) { + if (idx !== -1 && !async) throw error(this.loc.start.line, "Function contains async calls but does not have _ parameter"); + if (idx !== -1 && expr.type === 'Identifier' && streamlined[expr.name]) { // Optimized streamline callback. We know this call is to a streamlined function so we can // just inline it. - catchup(this.start); - if (scope[expr.value] === expr.value) { + catchup(this.range[0]); + if (scope[expr.name] === expr.name) { // In this case `expr` was declared with a function expression instead of a function // declaration, so the original function is no longer around. - catchup(expr.start); + catchup(expr.range[0]); buffer.add('('); - catchup(expr.end); + catchup(expr.range[1]); buffer.add('.fstreamlineFunction || 0)'); } else { if (true) { // TODO: enable this only for flame graphs - catchup(expr.start); + catchup(expr.range[0]); buffer.add('('); - catchup(expr.end); + catchup(expr.range[1]); // _ postfix is important buffer.add('_.fstreamlineFunction || 0)'); } else { - catchup(expr.end); + catchup(expr.range[1]); } } + buffer.add('('); for (var ii = 0; ii < args.length; ++ii) { - catchup(args[ii].start); + skipTo(args[ii].range[0]); + if (ii > 0) buffer.add(', '); if (ii !== idx) { walk(args[ii]); - catchup(args[ii].end); + catchup(args[ii].range[1]); } else { buffer.add('_'); - skipTo(args[ii].end); + skipTo(args[ii].range[1]); } } + skipTo(this.range[1]); + buffer.add(')'); } else if (idx !== -1) { // Rewrite streamlined calls - // issue #108: process between expr.start and last arg end rather than this.start/end - catchup(expr.start); + // issue #108: process between expr.range[0] and last arg end rather than this.range[0]/end + catchup(this.range[0]); buffer.add('fstreamline__.invoke('); - if (expr.type === t.DOT) { - // Method call: foo.bar(_) - walk(expr.children[0]); - catchup(expr.children[0].end); - buffer.add(', '+ JSON.stringify(expr.children[1].value)); - } else if (expr.type === t.INDEX) { - // Dynamic method call: foo[bar](_) - walk(expr.children[0]); - catchup(expr.children[0].end); - buffer.add(', '); - skipTo(expr.children[1].start); - walk(expr.children[1]); - catchup(expr.children[1].end); + skipTo(expr.range[0]) + if (expr.type === 'MemberExpression') { + skipTo(expr.object.range[0]); + walk(expr.object); + catchup(expr.object.range[1]); + if (!expr.computed) { + // Method call: foo.bar(_) + buffer.add(', '+ JSON.stringify(expr.property.name)); + } else { + // Dynamic method call: foo[bar](_) + buffer.add(', '); + skipTo(expr.property.range[0]); + walk(expr.property); + catchup(expr.property.range[1]); + } + skipTo(expr.range[1]); } else { // Function call buffer.add('null, '); walk(expr); - catchup(expr.end); } + catchup(expr.range[1]); + skipTo(args[0].range[0]); // Render arguments buffer.add(', ['); - skipTo(args[0].start); for (var ii = 0; ii < args.length; ++ii) { - catchup(args[ii].start); + skipTo(args[ii].range[0]); + if (ii > 0) buffer.add(', '); if (ii !== idx) { walk(args[ii]); - catchup(args[ii].end); + catchup(args[ii].range[1]); } else { buffer.add('_'); - skipTo(args[ii].end); + skipTo(args[ii].range[1]); } } - catchup(args[args.length - 1].end); var options = idx; - if (args[idx].type === t.ARRAY_INIT) options = '{ callbackIndex: ' + idx + ', returnArray: true }'; - buffer.add('], ' + options); + if (args[idx].type === 'ArrayExpression') options = '{ callbackIndex: ' + idx + ', returnArray: true }'; + buffer.add('], ' + options + ')'); + skipTo(this.range[1]); ++didRewrite; } else { var paren = 0; - if (source[this.start] === '(' && source[this.start + 1] === '(' && - source[this.end - 1] === ')' && source[this.end - 2] === ')') { + if (source[this.range[0]] === '(' && source[this.range[0] + 1] === '(' && + source[this.range[1] - 1] === ')' && source[this.range[1] - 2] === ')') { paren = 1; } - if (startsWith(source, this.start + paren, '(function() {')) { + if (startsWith(source, this.range[0] + paren, '(function() {')) { // handle coffeescript wrappers: set the forceAsync flag // so that we don't get an error about _ being used inside non async function - if (endsWith(source, this.end - paren, '})()')) { + if (endsWith(source, this.range[1] - paren, '})()')) { expr.forceAsync = async; } - if (endsWith(source, this.end - paren, '}).call(this)') // - || endsWith(source, this.end - paren, '}).call(_this)') // - || endsWith(source, this.end - paren, '}).apply(this, arguments)')) { - expr.children[0].forceAsync = async; + if (endsWith(source, this.range[1] - paren, '}).call(this)') // + || endsWith(source, this.range[1] - paren, '}).call(_this)') // + || endsWith(source, this.range[1] - paren, '}).apply(this, arguments)')) { + expr.object.forceAsync = async; } } walk(expr); args.map(walk); } }, - 'identifier': function(name, initializer) { + Identifier: function(name) { if (name === callback) { - throw error(this.lineno, 'Invalid usage of callback'); + throw error(this.loc.start.line, 'Invalid usage of callback'); } else if (verboten[name]) { - throw error(this.lineno, 'Invalid use of indentifier `'+ name+ '`'); + throw error(this.loc.start.line, 'Invalid use of indentifier `'+ name+ '`'); } if (scope[name]) { - var paren = this.parenthesized ? 1 : 0; - catchup(this.start + paren); + catchup(this.range[0]); buffer.add(scope[name]); - skipTo(this.end - paren); + skipTo(this.range[1]); } else { // catchup to end will deal with all sort of oddities, like object initializer keys that are // parsed as identifiers but need to be quoted. - catchup(this.end); + catchup(this.range[1]); } - initializer && walk(initializer); }, - 'property_init': function() { + Property: function() { // Dont't walk the property key, because that's an identifier and it will be clobbered, per // the below code - walk(this.children[1]); + walk(this.value); }, - 'dot': function() { + MemberExpression: function() { // See comment above for propery_init - walk(this.children[0]); + walk(this.object); + if (this.computed) walk(this.property) }, - 'new_with_args': function(expr, args) { + NewExpression: function(expr, args) { var idx = getCallback(args); if (idx !== -1) { // assumes that this is a streamlined function! - catchup(this.start); - skipTo(expr.start); // skip new keyword + catchup(this.range[0]); + skipTo(expr.range[0]); // skip new keyword buffer.add(" fstreamline__.construct("); walk(expr); - catchup(expr.end); - buffer.add("," + idx + ")"); + catchup(expr.range[1]); + buffer.add("," + idx + ")("); // process arguments to avoid 'invalid usage of callback' error for (var ii = 0; ii < args.length; ++ii) { - catchup(args[ii].start); + skipTo(args[ii].range[0]); + if (ii > 0) buffer.add(', '); if (ii !== idx) { walk(args[ii]); - catchup(args[ii].end); + catchup(args[ii].range[1]); } else { buffer.add('_'); - skipTo(args[ii].end); + skipTo(args[ii].range[1]); } } + buffer.add(")"); + skipTo(this.range[1]); } else { walk(expr); args.map(walk); } }, - 'return': function(value) { - value && walk(value); + ReturnStatement: function(argument) { + argument && walk(argument); fixASI(this); }, - 'throw': function(exception) { - exception && walk(exception); + ThrowStatement: function(argument) { + argument && walk(argument); fixASI(this); }, - 'yield': function(value) { - value && walk(value); + YieldStatement: function(argument) { + argument && walk(argument); fixASI(this); }, - 'not': function(value) { - if (value.type === t.IDENTIFIER && value.value === callback) { - catchup(this.start); - buffer.add('false'); - skipTo(this.end); - } else { - walk(value); - } - }, - 'void': function(value) { - if (value.type === t.IDENTIFIER && value.value === callback) { - catchup(this.start); - buffer.add('null'); - skipTo(this.end); + UnaryExpression: function(operator, argument) { + if (operator === '!' || operator === 'void') { + if (argument.type === 'Identifier' && argument.name === callback) { + catchup(this.range[0]); + buffer.add(operator === '!' ? 'false' : 'null'); + skipTo(this.range[1]); + } else { + walk(argument); + } } else { - walk(value); + walk(argument); } }, - 'try': function(tryBlock, catchClauses, finallyBlock) { - walk(tryBlock); - catchClauses.map(walk); - finallyBlock && walk(finallyBlock); + TryStatement: function(block, handlers, finalizer) { + walk(block); + handlers.map(walk); + finalizer && walk(finalizer); }, - 'semicolon': function(expression) { + ExpressionStatement: function(expression) { expression && walk(expression); fixASI(this); }, - 'rsh': walkShift, - 'lsh': walkShift, - 'let': fixVarASI, - 'var': fixVarASI, - 'const': fixVarASI, + BinaryExpression: function(operator, left, right) { + if (operator === '<<' || operator === '>>') { + walkShift.call(this, left, right); + } else { + walk(left); + walk(right); + } + }, + VariableDeclaration: function(declarations) { + declarations && declarations.map(walk); + if (this.eligibleForASI) fixASI(this); + }, }); // take care of ASI, in case transformation parenthesized next statement function fixASI(node) { - catchup(node.end); + catchup(node.range[1]); if (buffer.lastChar() !== ';') buffer.add(';'); } - function fixVarASI() { - this.children && this.children.map(walk); - if (this.eligibleForASI) fixASI(this); - } - function walkShift() { - var args = this.children; - if (args[0].type === t.IDENTIFIER && args[0].value === callback) { - catchup(args[0].start); - skipTo(args[1].start); - walk(args[1]); + function walkShift(left, right) { + if (left.type === 'Identifier' && left.name === callback) { + catchup(left.range[0]); + skipTo(source.indexOf(this.operator, left.range[1]) + 2); + + walk(right); ++didRewrite; } else { - args.map(walk); + walk(left); + walk(right); } } // Walk parsed source, rendering along the way source = prelude + source + postlude; - var parsed = Narcissus.parser.parse(source, options.sourceName); - allIdentifiers = getLocals(parsed.children[2].expression.children[0].children[0], true); + var parsed = esprima.parse(source, { + loc: true, + range: true, + }); + allIdentifiers = getLocals(parsed.body[2].expression.callee.object, true); + walk(parsed); catchup(source.length);