]> code.delx.au - gnu-emacs-elpa/commitdiff
Restore microooptimized, add more benchmarks, add tests.
authorJackson Ray Hamilton <jackson@jacksonrayhamilton.com>
Sat, 22 Nov 2014 21:09:50 +0000 (13:09 -0800)
committerJackson Ray Hamilton <jackson@jacksonrayhamilton.com>
Sat, 22 Nov 2014 21:09:50 +0000 (13:09 -0800)
benchmark/scenarios.js
scopifier-microoptimized.js [new file with mode: 0644]
test/.jslintrc
test/fixtures/monad.js [deleted file]
test/fixtures/vow.js [new file with mode: 0644]
test/fixtures/vow.json [new file with mode: 0644]
test/scopifier.js [deleted file]
test/specs.js [new file with mode: 0644]

index f2fa38667f5673d8578483973352be2d532819c5..879da5c21c7d5b12f48ff7dc2b678c9c31e4874b 100644 (file)
@@ -3,6 +3,7 @@
 var fs = require('fs'),
     path = require('path'),
     scopifier = require('../scopifier'),
+    scopifierMicrooptimized = require('../scopifier-microoptimized'),
 
     jqueryPath = path.join(__dirname, 'fixtures', 'jquery-2.1.1.js'),
     lodashPath = path.join(__dirname, 'fixtures', 'lodash-2.4.1.js'),
@@ -19,21 +20,18 @@ suite('scopifier', function () {
             next(error);
         });
     });
-
     before(function (next) {
         fs.readFile(lodashPath, 'utf8', function (error, contents) {
             lodash = contents;
             next(error);
         });
     });
-
     before(function (next) {
         fs.readFile(asyncPath, 'utf8', function (error, contents) {
             async = contents;
             next(error);
         });
     });
-
     before(function (next) {
         fs.readFile(mkdirpPath, 'utf8', function (error, contents) {
             mkdirp = contents;
@@ -41,20 +39,23 @@ suite('scopifier', function () {
         });
     });
 
-    bench('jquery', function () {
-        scopifier(jquery);
-    });
-
-    bench('lodash', function () {
-        scopifier(lodash);
-    });
-
-    bench('async', function () {
-        scopifier(async);
-    });
-
-    bench('mkdirp', function () {
-        scopifier(mkdirp);
+    [scopifier, scopifierMicrooptimized].forEach(function (scopifier, index) {
+        var message = '';
+        if (index === 1) {
+            message = ' (microoptimized)';
+        }
+        bench('jquery' + message, function () {
+            scopifier(jquery);
+        });
+        bench('lodash' + message, function () {
+            scopifier(lodash);
+        });
+        bench('async' + message, function () {
+            scopifier(async);
+        });
+        bench('mkdirp' + message, function () {
+            scopifier(mkdirp);
+        });
     });
 
 });
diff --git a/scopifier-microoptimized.js b/scopifier-microoptimized.js
new file mode 100644 (file)
index 0000000..1ef067d
--- /dev/null
@@ -0,0 +1,123 @@
+'use strict';
+
+var escope = require('escope'),
+    esprima = require('esprima');
+
+// Given code, returns an array of `[level, start, end]' tokens for
+// context-coloring.
+module.exports = function (code) {
+    var analyzedScopes,
+        ast,
+        comment,
+        comments,
+        definition,
+        definitions,
+        i,
+        isDefined,
+        j,
+        k,
+        mappedDefinitions,
+        range,
+        reference,
+        references,
+        scope,
+        scopes = [],
+        symbols = [],
+        variable;
+
+    // Gracefully handle parse errors by doing nothing.
+    try {
+        ast = esprima.parse(code, {
+            comment: true,
+            range: true
+        });
+        analyzedScopes = escope.analyze(ast).scopes;
+    } catch (error) {
+        process.exit(1);
+    }
+
+    for (i = 0; i < analyzedScopes.length; i += 1) {
+        scope = analyzedScopes[i];
+        // Having its level set implies it was already annotated.
+        if (scope.level === undefined) {
+            if (scope.upper) {
+                if (scope.upper.functionExpressionScope) {
+                    // Pretend function expression scope doesn't exist.
+                    scope.level = scope.upper.level;
+                    scope.variables = scope.upper.variables.concat(scope.variables);
+                } else {
+                    scope.level = scope.upper.level + 1;
+                }
+            } else {
+                // Base case.
+                scope.level = 0;
+            }
+            // We've only given the scope a level for posterity's sake. We're
+            // done now.
+            if (!scope.functionExpressionScope) {
+                range = scope.block.range;
+                scopes.push([
+                    scope.level,
+                    range[0] + 1,
+                    range[1] + 1
+                ]);
+                definitions = [];
+                for (j = 0; j < scope.variables.length; j += 1) {
+                    variable = scope.variables[j];
+                    mappedDefinitions = [];
+                    for (k = 0; k < variable.defs.length; k += 1) {
+                        definition = variable.defs[k];
+                        range = definition.name.range;
+                        mappedDefinitions.push([
+                            scope.level,
+                            range[0] + 1,
+                            range[1] + 1
+                        ]);
+                    }
+                    Array.prototype.push.apply(definitions, mappedDefinitions);
+                }
+                references = [];
+                for (j = 0; j < scope.references.length; j += 1) {
+                    reference = scope.references[j];
+                    range = reference.identifier.range;
+                    isDefined = false;
+                    // Determine if a definition already exists for the
+                    // range. (escope detects variables twice if they are
+                    // declared and initialized simultaneously; this filters
+                    // them.)
+                    for (k = 0; k < definitions.length; k += 1) {
+                        definition = definitions[k];
+                        if (definition[1] === range[0] + 1 &&
+                                definition[2] === range[1] + 1) {
+                            isDefined = true;
+                            break;
+                        }
+                    }
+                    if (!isDefined) {
+                        references.push([
+                            // Handle global references too.
+                            reference.resolved ? reference.resolved.scope.level : 0,
+                            range[0] + 1,
+                            range[1] + 1
+                        ]);
+                    }
+                }
+                Array.prototype.push.apply(symbols, definitions);
+                Array.prototype.push.apply(symbols, references);
+            }
+        }
+    }
+
+    comments = [];
+    for (i = 0; i < ast.comments.length; i += 1) {
+        comment = ast.comments[i];
+        range = comment.range;
+        comments.push([
+            -1,
+            range[0] + 1,
+            range[1] + 1
+        ]);
+    }
+
+    return scopes.concat(symbols).concat(comments);
+};
index 12021144608945bfcf07757e0af8765a86679da8..539f3570dc5e676ac17768f052e4d0c9dd31cdc8 100644 (file)
@@ -1,5 +1,9 @@
 {
   "predef": [
+    "after",
+    "afterEach",
+    "before",
+    "beforeEach",
     "describe",
     "it"
   ]
diff --git a/test/fixtures/monad.js b/test/fixtures/monad.js
deleted file mode 100644 (file)
index 6366fad..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/* A monad. */
-function MONAD() {
-    abc = 3;
-    return function unit(value) {
-        // Some details.
-        var monad = Object.create(null);
-        monad.bind = function (func) {
-            return func(value);
-        };
-        return monad;
-    };
-}
diff --git a/test/fixtures/vow.js b/test/fixtures/vow.js
new file mode 100644 (file)
index 0000000..374853e
--- /dev/null
@@ -0,0 +1,270 @@
+// vow.js
+// Douglas Crockford
+// 2013-09-20
+
+// Public Domain
+
+/*global setImmediate */
+
+
+var VOW = (function () {
+    'use strict';
+
+// The VOW object contains a .make function that is used to make vows.
+// It may also contain other useful functions.
+// In some mythologies, 'VOW' is called 'deferrer'.
+
+    function enlighten(queue, fate) {
+
+// enlighten is a helper function of herald and .when. It schedules the
+// processing of all of the resolution functions in either the keepers queue
+// or the breakers queue in later turns with the promise's fate.
+
+        queue.forEach(function (func) {
+            setImmediate(func, fate);
+        });
+    }
+
+    return {
+        make: function make() {
+
+// The make function makes new vows. A vow contains a promise object and the
+// two resolution functions (break and keep) that determine the fate of the
+// promise.
+
+            var breakers = [],          // .when's broken queue
+                fate,                   // The promise's ultimate value
+                keepers = [],           // .when's kept queue
+                status = 'pending';     // 'broken', 'kept', or 'pending'
+
+            function enqueue(
+                resolution, // 'keep' or 'break'
+                func,       // A function that was registered with .when
+                vow         // A vow that provides the resolution functions
+            ) {
+
+// enqueue is a helper function used by .when. It will append a function to
+// either the keepers queue or the breakers queue.
+
+                var queue = resolution === 'keep' ? keepers : breakers;
+                queue[queue.length] = typeof func !== 'function'
+
+// If func is not a function, push the resolver so that the value passes to
+// the next cascaded .when.
+
+                    ? vow[resolution]
+
+// If the func is a function, push a function that calls func with a value.
+// The result can be a promise, or not a promise, or an exception.
+
+                    : function (value) {
+                        try {
+                            var result = func(value);
+
+// If the result is a promise, then register our resolver with that promise.
+
+                            if (result && result.is_promise === true) {
+                                result.when(vow.keep, vow.break);
+
+// But if it is not a promise, then use the result to resolve our promise.
+
+                            } else {
+                                vow.keep(result);
+                            }
+
+// But if func throws an exception, then break our promise.
+
+                        } catch (e) {
+                            vow.break(e);
+                        }
+                    };
+            }
+
+            function herald(state, value, queue) {
+
+// The herald function is a helper function of break and keep.
+// It seals the promise's fate, updates its status, enlightens
+// one of the queues, and empties both queues.
+
+                if (status !== 'pending') {
+                    throw "overpromise";
+                }
+                fate = value;
+                status = state;
+                enlighten(queue, fate);
+                keepers.length = 0;
+                breakers.length = 0;
+            }
+
+// Construct and return the vow object.
+
+            return {
+                'break': function (value) {
+
+// The break method breaks the promise.
+
+                    herald('broken', value, breakers);
+                },
+                keep: function keep(value) {
+
+// The keep method keeps the promise.
+
+                    herald('kept', value, keepers);
+                },
+                promise: {
+
+// The promise is an object with a .when method.
+
+                    is_promise: true,
+
+// The .when method is the promise monad's bind. The .when method can take two
+// optional functions. One of those functions may be called, depending on the
+// promise's resolution. Both could be called if the the kept function throws.
+
+                    when: function (kept, broken) {
+
+// Make a new vow. Return the new promise.
+
+                        var vow = make();
+                        switch (status) {
+
+// If this promise is still pending, then enqueue both kept and broken.
+
+                        case 'pending':
+                            enqueue('keep',  kept,   vow);
+                            enqueue('break', broken, vow);
+                            break;
+
+// If the promise has already been kept, then enqueue only the kept function,
+// and enlighten it.
+
+                        case 'kept':
+                            enqueue('keep', kept, vow);
+                            enlighten(keepers, fate);
+                            break;
+
+// If the promise has already been broken, then enqueue only the broken
+// function, and enlighten it.
+
+                        case 'broken':
+                            enqueue('break', broken, vow);
+                            enlighten(breakers, fate);
+                            break;
+                        }
+                        return vow.promise;
+                    }
+                }
+            };
+        },
+        every: function every(array) {
+
+// The every function takes an array of promises and returns a promise that
+// will deliver an array of results only if every promise is kept.
+
+            var remaining = array.length, results = [], vow = VOW.make();
+
+            if (!remaining) {
+                vow.break(array);
+            } else {
+                array.forEach(function (promise, i) {
+                    promise.when(function (value) {
+                        results[i] = value;
+                        remaining -= 1;
+                        if (remaining === 0) {
+                            vow.keep(results);
+                        }
+                    }, function (reason) {
+                        remaining = NaN;
+                        vow.break(reason);
+                    });
+                });
+            }
+            return vow.promise;
+        },
+        first: function first(array) {
+
+// The first function takes an array of promises and returns a promise to
+// deliver the first observed kept promise, or a broken promise if all of
+// the promises are broken.
+
+            var found = false, remaining = array.length, vow = VOW.make();
+
+            function check() {
+                remaining -= 1;
+                if (remaining === 0 && !found) {
+                    vow.break();
+                }
+            }
+
+            if (remaining === 0) {
+                vow.break(array);
+            } else {
+                array.forEach(function (promise) {
+                    promise.when(function (value) {
+                        if (!found) {
+                            found = true;
+                            vow.keep(value);
+                        }
+                        check();
+                    }, check);
+                });
+            }
+            return vow.promise;
+        },
+        any: function any(array) {
+
+// The any function takes an array of promises and returns a promise that
+// will deliver a possibly sparse array of results of any kept promises.
+// The result will contain an undefined element for each broken promise.
+
+            var remaining = array.length, results = [], vow = VOW.make();
+
+            function check() {
+                remaining -= 1;
+                if (remaining === 0) {
+                    vow.keep(results);
+                }
+            }
+
+            if (!remaining) {
+                vow.keep(results);
+            } else {
+                array.forEach(function (promise, i) {
+                    promise.when(function (value) {
+                        results[i] = value;
+                        check();
+                    }, check);
+                });
+            }
+            return vow.promise;
+        },
+        kept: function (value) {
+
+// Returns a new kept promise.
+
+            var vow = VOW.make();
+            vow.keep(value);
+            return vow.promise;
+        },
+        broken: function (reason) {
+
+// Returns a new broken promise.
+
+            var vow = VOW.make();
+            vow.break(reason);
+            return vow.promise;
+        }
+    };
+}());
+
+
+// If your system does not have setImmediate, then simulate it with setTimeout.
+
+if (typeof setImmediate !== 'function') {
+    setImmediate = function setImmediate(func, param) {
+        'use strict';
+        return setTimeout(function () {
+            func(param);
+        }, 0);
+    };
+}
diff --git a/test/fixtures/vow.json b/test/fixtures/vow.json
new file mode 100644 (file)
index 0000000..8eb2d6d
--- /dev/null
@@ -0,0 +1 @@
+[[0,92,8470],[1,103,8174],[2,311,656],[3,583,648],[2,685,5093],[3,1155,2682],[4,1964,2667],[5,2566,2645],[3,2696,3201],[3,3290,3423],[3,3447,3579],[3,3961,5050],[2,5110,5975],[3,5469,5917],[4,5526,5770],[4,5772,5897],[2,5992,6931],[3,6282,6446],[3,6568,6873],[4,6622,6846],[2,6946,7814],[3,7277,7437],[3,7555,7756],[4,7612,7729],[2,7830,7986],[2,8004,8165],[1,8323,8467],[2,8408,8456],[0,96,99],[0,8273,8285],[0,8308,8320],[1,320,329],[2,330,335],[2,337,341],[2,569,574],[3,593,597],[0,613,625],[3,626,630],[2,632,636],[2,694,698],[2,886,894],[2,950,954],[2,1022,1029],[2,1084,1090],[2,1164,1171],[2,2705,2711],[3,1189,1199],[3,1238,1242],[3,1311,1314],[3,1536,1541],[2,1568,1575],[2,1578,1586],[3,1544,1554],[3,1604,1609],[3,1610,1615],[3,1633,1637],[3,1781,1784],[3,1785,1795],[4,1974,1979],[4,2045,2051],[3,2054,2058],[4,2059,2064],[4,2178,2184],[4,2188,2194],[4,2250,2256],[3,2262,2265],[3,2272,2275],[4,2439,2445],[3,2430,2433],[5,2573,2574],[5,2616,2617],[3,2606,2609],[3,2712,2717],[3,2719,2724],[3,2726,2731],[2,2930,2936],[2,3029,3033],[3,3036,3041],[2,3059,3065],[3,3068,3073],[1,3091,3100],[3,3101,3106],[2,3108,3112],[2,3131,3138],[2,3167,3175],[3,3300,3305],[2,3371,3377],[3,3388,3393],[2,3395,3403],[3,3456,3460],[3,3461,3466],[2,3530,3536],[3,3545,3550],[2,3552,3559],[3,3971,3975],[3,3977,3983],[3,4060,4063],[2,4066,4070],[2,4106,4112],[2,4258,4265],[3,4275,4279],[3,4283,4286],[2,4317,4324],[3,4334,4340],[3,4342,4345],[2,4549,4556],[3,4565,4569],[3,4571,4574],[1,4605,4614],[2,4615,4622],[2,4624,4628],[2,4838,4845],[3,4855,4861],[3,4863,4866],[1,4897,4906],[2,4907,4915],[2,4917,4921],[3,5016,5019],[2,5119,5124],[2,5125,5130],[2,5295,5304],[2,5321,5328],[2,5335,5338],[2,5307,5312],[0,5341,5344],[2,5371,5380],[2,5410,5415],[2,5400,5403],[2,5455,5460],[2,5953,5956],[3,5479,5486],[3,5488,5489],[3,5513,5520],[4,5536,5541],[4,5582,5587],[2,5569,5576],[3,5577,5578],[2,5613,5622],[2,5657,5666],[2,5713,5720],[2,5704,5707],[4,5782,5788],[2,5816,5825],[0,5828,5831],[4,5867,5873],[2,5857,5860],[2,6001,6006],[2,6007,6012],[2,6210,6215],[2,6225,6234],[2,6251,6254],[2,6291,6296],[2,6237,6242],[0,6257,6260],[2,6464,6473],[2,6509,6514],[2,6499,6502],[2,6554,6559],[2,6909,6912],[2,6317,6326],[2,6353,6362],[2,6373,6378],[2,6402,6405],[3,6578,6585],[2,6848,6853],[3,6609,6616],[4,6632,6637],[2,6670,6675],[2,6707,6712],[4,6758,6763],[2,6749,6752],[2,6816,6821],[2,6955,6958],[2,6959,6964],[2,7206,7215],[2,7232,7239],[2,7246,7249],[2,7286,7291],[2,7218,7223],[0,7252,7255],[2,7456,7465],[2,7494,7501],[2,7485,7488],[2,7541,7546],[2,7792,7795],[2,7312,7321],[2,7348,7357],[2,7396,7403],[2,7387,7390],[3,7565,7572],[3,7574,7575],[2,7731,7736],[3,7599,7606],[4,7622,7627],[4,7668,7673],[2,7655,7662],[3,7663,7664],[2,7699,7704],[2,7840,7845],[2,7898,7901],[0,7904,7907],[2,7937,7942],[2,7928,7931],[2,7964,7967],[2,8014,8020],[2,8075,8078],[0,8081,8084],[2,8115,8121],[2,8105,8108],[2,8143,8146],[1,8332,8344],[1,8345,8349],[1,8351,8356],[0,8397,8407],[1,8434,8438],[1,8439,8444],[-1,1,10],[-1,11,31],[-1,32,45],[-1,47,63],[-1,65,89],[-1,136,206],[-1,207,253],[-1,254,305],[-1,346,417],[-1,418,494],[-1,495,559],[-1,704,780],[-1,781,856],[-1,857,868],[-1,910,933],[-1,974,1005],[-1,1046,1067],[-1,1108,1141],[-1,1201,1221],[-1,1250,1294],[-1,1323,1370],[-1,1388,1463],[-1,1464,1514],[-1,1654,1729],[-1,1730,1757],[-1,1798,1873],[-1,1874,1940],[-1,2068,2144],[-1,2285,2359],[-1,2479,2538],[-1,2736,2798],[-1,2799,2861],[-1,2862,2908],[-1,3203,3242],[-1,3310,3349],[-1,3471,3508],[-1,3609,3657],[-1,3698,3776],[-1,3777,3854],[-1,3855,3933],[-1,3988,4030],[-1,4117,4188],[-1,4384,4461],[-1,4462,4482],[-1,4667,4738],[-1,4739,4769],[-1,5135,5210],[-1,5211,5277],[-1,6017,6090],[-1,6091,6164],[-1,6165,6192],[-1,6969,7042],[-1,7043,7115],[-1,7116,7188],[-1,7850,7880],[-1,8025,8057],[-1,8181,8260]]
diff --git a/test/scopifier.js b/test/scopifier.js
deleted file mode 100644 (file)
index 3f87ef4..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-
-var assert = require('assert');
-
-describe('scopifier', function () {
-
-    it('should work', function () {
-        assert.strictEqual(true, true);
-    });
-
-});
diff --git a/test/specs.js b/test/specs.js
new file mode 100644 (file)
index 0000000..c015ddb
--- /dev/null
@@ -0,0 +1,53 @@
+'use strict';
+
+var assert = require('assert'),
+    fs = require('fs'),
+    path = require('path'),
+
+    scopifier = require('../scopifier'),
+    scopifierMicrooptimized = require('../scopifier-microoptimized'),
+
+    inputPath = path.join(__dirname, 'fixtures', 'vow.js'),
+    outputPath = path.join(__dirname, 'fixtures', 'vow.json');
+
+describe('scopifier', function () {
+
+    var input, output;
+
+    before(function (done) {
+        fs.readFile(inputPath, 'utf8', function (error, contents) {
+            if (error) {
+                done(error);
+                return;
+            }
+            input = contents;
+            done();
+        });
+    });
+
+    before(function (done) {
+        fs.readFile(outputPath, 'utf8', function (readError, contents) {
+            if (readError) {
+                done(readError);
+                return;
+            }
+            try {
+                output = JSON.parse(contents);
+            } catch (parseError) {
+                done(parseError);
+            }
+            done();
+        });
+    });
+
+    [scopifier, scopifierMicrooptimized].forEach(function (scopifier, index) {
+        var message = '';
+        if (index === 1) {
+            message = ' (microoptimized)';
+        }
+        it('should work' + message, function () {
+            assert.deepEqual(scopifier(input), output);
+        });
+    });
+
+});