Checkpoint on unnecessary Inventory complexity.
authordsc <david.schoonover@gmail.com>
Wed, 9 Feb 2011 23:32:49 +0000 (15:32 -0800)
committerdsc <david.schoonover@gmail.com>
Wed, 9 Feb 2011 23:32:49 +0000 (15:32 -0800)
26 files changed:
lib/jsparse.js [new file with mode: 0755]
src/Y/class.cjs
src/Y/modules/y.event.cjs
src/Y/modules/y.polyevent.cjs [new file with mode: 0644]
src/Y/types/array.cjs
src/Y/types/function.cjs
src/Y/types/string.cjs
src/evt.cjs
src/ezl/layer/index.cjs
src/ezl/layer/layer.cjs
src/ezl/layer/layerable.cjs [new file with mode: 0644]
src/tanks/game.cjs
src/tanks/globals.js
src/tanks/index.js
src/tanks/inventory/bag.cjs
src/tanks/inventory/bagbag.cjs
src/tanks/inventory/belt.cjs
src/tanks/inventory/container.cjs
src/tanks/inventory/index.cjs [new file with mode: 0644]
src/tanks/inventory/inventory.cjs
src/tanks/inventory/requirements/indexer.cjs [new file with mode: 0644]
src/tanks/inventory/requirements/requirements.cjs [new file with mode: 0644]
src/tanks/mixins/inventoried.cjs
src/tanks/thing/item.cjs
src/tanks/thing/player.cjs
src/tanks/ui/inventory/containerui.cjs

diff --git a/lib/jsparse.js b/lib/jsparse.js
new file mode 100755 (executable)
index 0000000..260c3c1
--- /dev/null
@@ -0,0 +1,653 @@
+// Copyright (C) 2007 Chris Double.\r
+// \r
+// Redistribution and use in source and binary forms, with or without\r
+// modification, are permitted provided that the following conditions are met:\r
+// \r
+// 1. Redistributions of source code must retain the above copyright notice,\r
+//    this list of conditions and the following disclaimer.\r
+// \r
+// 2. Redistributions in binary form must reproduce the above copyright notice,\r
+//    this list of conditions and the following disclaimer in the documentation\r
+//    and/or other materials provided with the distribution.\r
+// \r
+// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,\r
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\r
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\r
+// DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;\r
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+//\r
+function identity(x) {\r
+    return x;\r
+}\r
+\r
+function foldl(f, initial, seq) {\r
+    for(var i=0; i< seq.length; ++i) \r
+        initial = f(initial, seq[i]);\r
+    return initial;\r
+}\r
+\r
+var memoize = true;\r
+\r
+function ParseState(input, index) {\r
+    this.input = input;\r
+    this.index = index || 0;\r
+    this.length = input.length - this.index;\r
+    this.cache = { };\r
+    return this;\r
+}\r
+\r
+ParseState.prototype.from = function(index) {\r
+    var r = new ParseState(this.input, this.index + index);\r
+    r.cache = this.cache;\r
+    r.length = this.length - index;\r
+    return r;\r
+}\r
+\r
+ParseState.prototype.substring = function(start, end) {\r
+    return this.input.substring(start + this.index, (end || this.length) + this.index);\r
+}\r
+\r
+ParseState.prototype.trimLeft = function() {\r
+    var s = this.substring(0);\r
+    var m = s.match(/^\s+/);\r
+    return m ? this.from(m[0].length) : this;\r
+}\r
+\r
+ParseState.prototype.at = function(index) {\r
+    return this.input.charAt(this.index + index);\r
+}\r
+\r
+ParseState.prototype.toString = function() {\r
+    return 'PS"' + this.substring(0) + '"'; \r
+}\r
+\r
+ParseState.prototype.getCached = function(pid) {\r
+    if(!memoize)\r
+        return false;\r
+\r
+    var p = this.cache[pid];\r
+    if(p) \r
+        return p[this.index];\r
+    else\r
+        return false;\r
+}\r
+\r
+ParseState.prototype.putCached = function(pid, cached) {\r
+    if(!memoize)\r
+        return false;\r
+\r
+    var p = this.cache[pid];\r
+    if(p)\r
+        p[this.index] = cached;\r
+    else {\r
+        p = this.cache[pid] = { };\r
+        p[this.index] = cached;\r
+    }\r
+}\r
+\r
+function ps(str) {\r
+    return new ParseState(str);\r
+}\r
+\r
+// 'r' is the remaining string to be parsed.\r
+// 'matched' is the portion of the string that\r
+// was successfully matched by the parser.\r
+// 'ast' is the AST returned by the successfull parse.\r
+function make_result(r, matched, ast) {\r
+        return { remaining: r, matched: matched, ast: ast };\r
+}\r
+\r
+var parser_id = 0;\r
+                \r
+// 'token' is a parser combinator that given a string, returns a parser\r
+// that parses that string value. The AST contains the string that was parsed.\r
+function token(s) {\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+\r
+        var r = state.length >= s.length && state.substring(0,s.length) == s;\r
+        if(r) \r
+            cached = { remaining: state.from(s.length), matched: s, ast: s };\r
+        else\r
+            cached = false;\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    };\r
+}\r
+\r
+// Like 'token' but for a single character. Returns a parser that given a string\r
+// containing a single character, parses that character value.\r
+function ch(c) {\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+        var r = state.length >= 1 && state.at(0) == c;\r
+        if(r) \r
+            cached = { remaining: state.from(1), matched: c, ast: c };\r
+        else\r
+            cached = false;\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    };\r
+}\r
+\r
+// 'range' is a parser combinator that returns a single character parser\r
+// (similar to 'ch'). It parses single characters that are in the inclusive\r
+// range of the 'lower' and 'upper' bounds ("a" to "z" for example).\r
+function range(lower, upper) {\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+        \r
+        if(state.length < 1) \r
+            cached = false;\r
+        else {\r
+            var ch = state.at(0);\r
+            if(ch >= lower && ch <= upper) \r
+                cached = { remaining: state.from(1), matched: ch, ast: ch };\r
+            else\r
+                cached = false;\r
+        }\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    };\r
+}\r
+\r
+// Helper function to convert string literals to token parsers\r
+// and perform other implicit parser conversions.\r
+function toParser(p) {\r
+    return (typeof(p) == "string") ? token(p) : p;\r
+}\r
+\r
+// Parser combinator that returns a parser that\r
+// skips whitespace before applying parser.\r
+function whitespace(p) {\r
+    var p = toParser(p);\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+\r
+        cached = p(state.trimLeft());\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    };\r
+}\r
+\r
+// Parser combinator that passes the AST generated from the parser 'p' \r
+// to the function 'f'. The result of 'f' is used as the AST in the result.\r
+function action(p, f) {\r
+    var p = toParser(p);\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached) \r
+            return cached;\r
+\r
+        var x = p(state);\r
+        if(x) {\r
+            x.ast = f(x.ast);\r
+            cached = x;\r
+        }\r
+        else {\r
+            cached = false;\r
+        }\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    };\r
+}\r
+\r
+// Given a parser that produces an array as an ast, returns a\r
+// parser that produces an ast with the array joined by a separator.\r
+function join_action(p, sep) {\r
+    return action(p, function(ast) { return ast.join(sep); });\r
+}\r
+\r
+// Given an ast of the form [ Expression, [ a, b, ...] ], convert to\r
+// [ [ [ Expression [ a ] ] b ] ... ]\r
+// This is used for handling left recursive entries in the grammar. e.g.\r
+// MemberExpression:\r
+//   PrimaryExpression\r
+//   FunctionExpression\r
+//   MemberExpression [ Expression ]\r
+//   MemberExpression . Identifier\r
+//   new MemberExpression Arguments \r
+function left_factor(ast) {\r
+    return foldl(function(v, action) { \r
+                     return [ v, action ]; \r
+                 }, \r
+                 ast[0], \r
+                 ast[1]);\r
+}\r
+\r
+// Return a parser that left factors the ast result of the original\r
+// parser.\r
+function left_factor_action(p) {\r
+    return action(p, left_factor);\r
+}\r
+\r
+// 'negate' will negate a single character parser. So given 'ch("a")' it will successfully\r
+// parse any character except for 'a'. Or 'negate(range("a", "z"))' will successfully parse\r
+// anything except the lowercase characters a-z.\r
+function negate(p) {\r
+    var p = toParser(p);\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+\r
+        if(state.length >= 1) {\r
+            var r = p(state);\r
+            if(!r) \r
+                cached =  make_result(state.from(1), state.at(0), state.at(0));\r
+            else\r
+                cached = false;\r
+        }\r
+        else {\r
+            cached = false;\r
+        }\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    };\r
+}\r
+\r
+// 'end_p' is a parser that is successful if the input string is empty (ie. end of parse).\r
+function end_p(state) {\r
+    if(state.length == 0) \r
+        return make_result(state, undefined, undefined);\r
+    else\r
+        return false;\r
+}\r
+\r
+// 'nothing_p' is a parser that always fails.\r
+function nothing_p(state) {\r
+    return false;\r
+}\r
+\r
+// 'sequence' is a parser combinator that processes a number of parsers in sequence.\r
+// It can take any number of arguments, each one being a parser. The parser that 'sequence'\r
+// returns succeeds if all the parsers in the sequence succeeds. It fails if any of them fail.\r
+function sequence() {\r
+    var parsers = [];\r
+    for(var i = 0; i < arguments.length; ++i) \r
+        parsers.push(toParser(arguments[i]));            \r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached) {\r
+            return cached;\r
+        }\r
+\r
+        var ast = [];\r
+        var matched = "";\r
+        var i;\r
+        for(i=0; i< parsers.length; ++i) {\r
+            var parser = parsers[i];        \r
+            var result = parser(state);\r
+            if(result) {\r
+                state = result.remaining;\r
+                if(result.ast != undefined) {\r
+                    ast.push(result.ast);\r
+                    matched = matched + result.matched;\r
+                }\r
+            }\r
+            else {\r
+                break;\r
+            }\r
+        }\r
+        if(i == parsers.length) {\r
+            cached = make_result(state, matched, ast);\r
+        }\r
+        else \r
+            cached = false;\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    };\r
+}\r
+\r
+// Like sequence, but ignores whitespace between individual parsers.\r
+function wsequence() {\r
+    var parsers = [];\r
+    for(var i=0; i < arguments.length; ++i) {\r
+        parsers.push(whitespace(toParser(arguments[i])));\r
+    }\r
+    return sequence.apply(null, parsers);       \r
+}\r
+\r
+// 'choice' is a parser combinator that provides a choice between other parsers.\r
+// It takes any number of parsers as arguments and returns a parser that will try\r
+// each of the given parsers in order. The first one that succeeds results in a \r
+// successfull parse. It fails if all parsers fail.\r
+function choice() {\r
+    var parsers = [];\r
+    for(var i = 0; i < arguments.length; ++i) \r
+        parsers.push(toParser(arguments[i]));            \r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached) {\r
+            return cached;\r
+        }\r
+        var i;\r
+        for(i=0; i< parsers.length; ++i) {\r
+            var parser=parsers[i];\r
+            var result = parser(state);\r
+            if(result) {\r
+                break;\r
+            }\r
+        }        \r
+        if(i == parsers.length)\r
+            cached = false;\r
+        else\r
+            cached = result;\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+\r
+// 'butnot' is a parser combinator that takes two parsers, 'p1' and 'p2'. \r
+// It returns a parser that succeeds if 'p1' matches and 'p2' does not, or\r
+// 'p1' matches and the matched text is longer that p2's.\r
+// Useful for things like: butnot(IdentifierName, ReservedWord)\r
+function butnot(p1,p2) {\r
+    var p1 = toParser(p1);\r
+    var p2 = toParser(p2);\r
+    var pid = parser_id++;\r
+    \r
+    // match a but not b. if both match and b's matched text is shorter\r
+    // than a's, a failed match is made\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+        \r
+        var br = p2(state);\r
+        if(!br) {\r
+            cached = p1(state);\r
+        } else {\r
+            var ar = p1(state);\r
+            if(ar.matched.length > br.matched.length)\r
+                cached = ar;\r
+            else\r
+                cached = false;\r
+        }\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+\r
+// 'difference' is a parser combinator that takes two parsers, 'p1' and 'p2'. \r
+// It returns a parser that succeeds if 'p1' matches and 'p2' does not. If\r
+// both match then if p2's matched text is shorter than p1's it is successfull.\r
+function difference(p1,p2) {\r
+    var p1 = toParser(p1);\r
+    var p2 = toParser(p2);\r
+    var pid = parser_id++;\r
+    \r
+    // match a but not b. if both match and b's matched text is shorter\r
+    // than a's, a successfull match is made\r
+    return function(state) {\r
+        var savedState = sate;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+\r
+        var br = p2(state);\r
+        if(!br) {\r
+            cached = p1(state);\r
+        } else {\r
+            var ar = p1(state);\r
+            if(ar.matched.length >= br.matched.length)\r
+                cached = br;\r
+            else\r
+                cached = ar;\r
+        }\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+\r
+\r
+// 'xor' is a parser combinator that takes two parsers, 'p1' and 'p2'. \r
+// It returns a parser that succeeds if 'p1' or 'p2' match but fails if\r
+// they both match.\r
+function xor(p1, p2) {\r
+    var p1 = toParser(p1);\r
+    var p2 = toParser(p2);\r
+    var pid = parser_id++;\r
+\r
+    // match a or b but not both\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+\r
+        var ar = p1(state);\r
+        var br = p2(state);\r
+        if(ar && br)\r
+            cached = false;\r
+        else\r
+            cached = ar || br;\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+\r
+// A parser combinator that takes one parser. It returns a parser that\r
+// looks for zero or more matches of the original parser.\r
+function repeat0(p) {\r
+    var p = toParser(p);\r
+    var pid = parser_id++;\r
+    \r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached) {\r
+            return cached;\r
+        }\r
+\r
+        var ast = [];\r
+        var matched = "";\r
+        var result;\r
+        while(result = p(state)) {\r
+            ast.push(result.ast);\r
+            matched = matched + result.matched;\r
+            if(result.remaining.index == state.index)\r
+                break;\r
+            state = result.remaining;                        \r
+        }                \r
+        cached = make_result(state, matched, ast);                \r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+\r
+// A parser combinator that takes one parser. It returns a parser that\r
+// looks for one or more matches of the original parser.\r
+function repeat1(p) {\r
+    var p = toParser(p);\r
+    var pid = parser_id++;\r
+\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+\r
+        var ast = [];\r
+        var matched = "";\r
+        var result= p(state);\r
+        if(!result) \r
+            cached = false;\r
+        else {        \r
+            while(result) {\r
+                ast.push(result.ast);\r
+                matched = matched + result.matched;\r
+                if(result.remaining.index == state.index)\r
+                    break;\r
+                state = result.remaining;                        \r
+                result = p(state);\r
+            }                \r
+            cached = make_result(state, matched, ast);                \r
+        }\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+\r
+// A parser combinator that takes one parser. It returns a parser that\r
+// matches zero or one matches of the original parser.\r
+function optional(p) {\r
+    var p = toParser(p);\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;\r
+        var r = p(state);\r
+        cached = r || make_result(state, "", false);                                \r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+\r
+// A parser combinator that ensures that the given parser succeeds but\r
+// ignores its result. This can be useful for parsing literals that you\r
+// don't want to appear in the ast. eg:\r
+// sequence(expect("("), Number, expect(")")) => ast: Number\r
+function expect(p) {\r
+    return action(p, function(ast) { return undefined; });\r
+}\r
+\r
+function chain(p, s, f) {\r
+    var p = toParser(p);\r
+\r
+    return action(sequence(p, repeat0(action(sequence(s, p), f))),\r
+                  function(ast) { return [ast[0]].concat(ast[1]); });\r
+}\r
+\r
+// A parser combinator to do left chaining and evaluation. Like 'chain', it expects a parser\r
+// for an item and for a seperator. The seperator parser's AST result should be a function\r
+// of the form: function(lhs,rhs) { return x; }\r
+// Where 'x' is the result of applying some operation to the lhs and rhs AST's from the item\r
+// parser.\r
+function chainl(p, s) {\r
+    var p = toParser(p);\r
+    return action(sequence(p, repeat0(sequence(s, p))),\r
+                  function(ast) {\r
+                      return foldl(function(v, action) { return action[0](v, action[1]); }, ast[0], ast[1]);\r
+                  });\r
+}\r
+\r
+// A parser combinator that returns a parser that matches lists of things. The parser to \r
+// match the list item and the parser to match the seperator need to \r
+// be provided. The AST is the array of matched items.\r
+function list(p, s) {\r
+    return chain(p, s, function(ast) { return ast[1]; });\r
+}\r
+\r
+// Like list, but ignores whitespace between individual parsers.\r
+function wlist() {\r
+    var parsers = [];\r
+    for(var i=0; i < arguments.length; ++i) {\r
+        parsers.push(whitespace(arguments[i]));\r
+    }\r
+    return list.apply(null, parsers);       \r
+}\r
+\r
+// A parser that always returns a zero length match\r
+function epsilon_p(state) {\r
+    return make_result(state, "", undefined);\r
+}\r
+\r
+// Allows attaching of a function anywhere in the grammer. If the function returns\r
+// true then parse succeeds otherwise it fails. Can be used for testing if a symbol\r
+// is in the symbol table, etc.\r
+function semantic(f) {\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;       \r
+        cached = f() ? make_result(state, "", undefined) : false;\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+\r
+// The `and` predicate asserts that a certain conditional\r
+// syntax is satisfied before evaluating another production. Eg:\r
+// sequence(and("0"), oct_p)\r
+// (if a leading zero, then parse octal)\r
+// It succeeds if 'p' succeeds and fails if 'p' fails. It never \r
+// consume any input however, and doesn't put anything in the resulting\r
+// AST.\r
+function and(p) {\r
+    var p = toParser(p);\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;       \r
+        var r = p(state);\r
+        cached = r ? make_result(state, "", undefined) : false;\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+                        \r
+// The opposite of 'and'. It fails if 'p' succeeds and succeeds if\r
+// 'p' fails. It never consumes any input. This combined with 'and' can\r
+// be used for 'lookahead' and disambiguation of cases.\r
+//\r
+// Compare:\r
+// sequence("a",choice("+","++"),"b")\r
+//   parses a+b\r
+//   but not a++b because the + matches the first part and peg's don't\r
+//   backtrack to other choice options if they succeed but later things fail.\r
+//\r
+// sequence("a",choice(sequence("+", not("+")),"++"),"b")\r
+//    parses a+b\r
+//    parses a++b\r
+//\r
+function not(p) {\r
+    var p = toParser(p);\r
+    var pid = parser_id++;\r
+    return function(state) {\r
+        var savedState = state;\r
+        var cached = savedState.getCached(pid);\r
+        if(cached)\r
+            return cached;       \r
+        cached = p(state) ? false : make_result(state, "", undefined);\r
+        savedState.putCached(pid, cached);\r
+        return cached;\r
+    }\r
+}\r
+\r
index 65b7d9d..c70ec20 100644 (file)
@@ -109,6 +109,7 @@ function Class(className, Parent, members) {
         var k = classStatics[i];
         NewClass[k] = ClassFactory[k];
     }
+    NewClass.instantiate = instantiate.partial(NewClass);
     
     // Copy parent methods, then add new instance methods
     for (var k in parentMembers)
index 0135273..3cb8030 100644 (file)
@@ -113,23 +113,22 @@ Y.YObject.subclass('Event', {
     },
     
     emit : function emit(evtname, trigger, data){
-        var q = this.queues[evtname]
+        var evt, A = arguments
+        ,   q = this.queues[evtname]
         ,   L = q ? q.length : 0
         ;
-        if ( !L )
-            return this;
-        
-        q = q._o.slice(0);
-        var A = arguments
-        ,   target = this.target
-        ,   evt = new Event(evtname, target, trigger, data)
-        ,   method = (A.length > 3 ? 'apply' : 'call')
-        ,   args   = (A.length > 3 ? [evt].concat(Y(A,3)) : evt)
-        ;
-        for (var i=0, fn=q[i]; i<L; fn=q[++i])
-            fn[method](target, args);
+        if ( L ) {
+            q = q._o.slice(0);
+            var target = this.target
+            ,   evt = new Event(evtname, target, trigger, data)
+            ,   method = (A.length > 3 ? 'apply' : 'call')
+            ,   args   = (A.length > 3 ? [evt].concat(Y(A,3)) : evt)
+            ;
+            for (var i=0, fn=q[i]; i<L; fn=q[++i])
+                fn[method](target, args);
+        }
         
-        if (this.parent && !evt._stopped)
+        if (this.parent && !(evt && evt._stopped) )
             this.parent.emit.apply(this.parent, A);
         
         return evt;
@@ -141,20 +140,6 @@ Y.YObject.subclass('Event', {
     },
     
     /**
-     * Dispatches the given event to all listeners in the appropriate queue.
-     * Listeners are invoked with the event, and with context set to the target.
-     * XXX: does not handle degenerate or recursive event dispatch
-     */
-    dispatchEvent : function dispatchEvent(evt){
-        var queue = this.queues[evt.type];
-        if (queue && queue.length)
-            queue.invoke('call', evt.target, evt);
-        if (this.parent)
-            this.parent.emit(evt.type, evt.trigger, evt.data);
-        return evt;
-    },
-    
-    /**
      * Decorates object with bound methods to act as a delegate of this hub.
      */
     decorate : function decorate(delegate){
@@ -165,6 +150,9 @@ Y.YObject.subclass('Event', {
         return delegate;
     }
 }
+
+
+,   GLOBAL_ID = 0
 ,
 
 Emitter =
@@ -172,6 +160,9 @@ exports['Emitter'] =
 Y.YObject.subclass('Emitter', 
     Y.extend({
         'init' : function(target, parent){
+            if (this.__id__ === undefined)
+                this.__id__ = GLOBAL_ID++;
+            
             this.queues = {};
             
             this.target = target || this;
@@ -187,7 +178,7 @@ methods.on = methods.addListener;
 Emitter.methods = methods;
 
 // Global Event Hub
-Emitter.global = new Emitter()
+Emitter.global = new Emitter();
 Emitter.global.decorate(exports);
 
 Y['event'] = exports;
diff --git a/src/Y/modules/y.polyevent.cjs b/src/Y/modules/y.polyevent.cjs
new file mode 100644 (file)
index 0000000..14cd527
--- /dev/null
@@ -0,0 +1,244 @@
+var Y = require('Y').Y
+,   event = require('Y/modules/y.event')
+,   Event = event.Event
+,   Emitter = event.Emitter
+,
+
+/**
+ * A simple event.
+ * TODO: Detect jQuery event objects.
+ * TODO: If DOM event, wrap with consistent API (use jQuery if present).
+ */
+PolyEvent =
+exports['PolyEvent'] =
+Event.subclass('PolyEvent', {
+    _stopped : false,   // whether propagation is stopped
+    
+    type    : 'event',  // event type
+    target  : null,     // target of the emitter
+    trigger : null,     // object which caused this event
+    data    : null,     // event data
+    
+    
+    
+    init : function init( type, target, trigger, data ){
+        data = data || {};
+        for (var k in data) this[k] = data[k];
+        this.data = this._o = data;
+        
+        this.type = type;
+        this.target = target || trigger;
+        this.trigger = trigger || target;
+    },
+    
+    has : function has(){
+        
+    },
+    
+    hasNoun : function hasNoun(noun){
+        
+    },
+    
+    hasVerb : function hasVerb(verb){
+        
+    },
+    
+    toString: function(){ return "PolyEvent("+this.type+")"; }
+})
+
+/**
+ * A simple multicaster.
+ */
+, methods = {
+    
+    emit : function emit(evtname, trigger, data){
+        var q = this.queues[evtname]
+        ,   L = q ? q.length : 0
+        ;
+        if ( !L )
+            return this;
+        
+        q = q._o.slice(0);
+        var A = arguments
+        ,   target = this.target
+        ,   evt = new PolyEvent(evtname, target, trigger, data)
+        ,   method = (A.length > 3 ? 'apply' : 'call')
+        ,   args   = (A.length > 3 ? [evt].concat(Y(A,3)) : evt)
+        ;
+        for (var i=0, fn=q[i]; i<L; fn=q[++i])
+            fn[method](target, args);
+        
+        if (this.parent && !evt._stopped)
+            this.parent.emit.apply(this.parent, A);
+        
+        return evt;
+    },
+    
+    /**
+     * @param {String} evt Event string to normalize and expand.
+     * @return {PolyEvent...}
+     */
+    getMatchingEvents : function getMatchingEvents(evt){
+        
+    },
+    
+    /**
+     * Decorates object with bound methods to act as a delegate of this hub.
+     */
+    decorate : function decorate(delegate){
+        if (!delegate)
+            return delegate;
+        
+        Emitter.fn.decorate.call(this, delegate);
+        for (var k in methods)
+            delegate[k] = methods[k].bind(this);
+        return delegate;
+    }
+}
+,
+
+PolyEmitter =
+exports['PolyEmitter'] =
+Emitter.subclass('PolyEmitter', 
+    Y.extend({
+        'init' : function(target, parent){
+            Emitter.init.call(this, target, parent);
+        }
+    }, methods) )
+;
+PolyEmitter.methods = methods;
+
+// Global Event Hub
+PolyEmitter.global = new PolyEmitter();
+PolyEmitter.global.decorate(exports);
+
+
+// Helper Functions
+
+var _match_cache = {};
+
+/**
+ * Returns an array of all events which match the given event.
+ * @param event
+ */     
+function getMatchingEvents(event, base_event) {
+    if ( !_match_cache[event] ) {
+        
+        // Segregate event groups in a manner worthy of strict scrutiny
+        var seg = event.split('.').reduce(
+            function(seg, token){
+                if (!base_event)
+                    base_event = token;
+                if (token.indexOf('-') !== -1)
+                    seg.special.push(token);
+                else
+                    seg.plain.push(token);
+                return seg;
+            }, { plain:[], special:[] });
+        
+        // Calculate the correct powerset for each event-group
+        var evtset;
+        if ( !seg.special.length )
+            evtset = powerset( seg.plain ).map(sorter).map(joiner);
+        else {
+            evtset = propertyset( seg.special ).reduce(
+                function(acc, t) {
+                    return acc.concat(
+                        powerset( seg.plain.concat([ t ]) ).map(sorter).map(joiner) );
+                }, new Y.YArray());
+        }
+        // Remove invalid permutations and duplicates
+        _match_cache[event] = evtset
+            .filter(function(v) { 
+                return v.indexOf(base_event) === 0;
+            })
+            .unique()
+            .reverse();
+        
+        // Always notify 'all'
+        _match_cache[event].push('all');
+    }
+    
+    // console.log("getMatchingEvents( event="+event+", base_event="+base_event+" ) --> [ "+_match_cache[event].join(",\n  ")+" ]");
+    return _match_cache[event];
+}
+
+/**
+ * Calculates the set of all unique subsets of S (without regard for order) of size k (ie, combination (S k).
+ * @param {Array} S A collection.
+ * @param {Number} k Length of combinations to calculate.
+ * @return {Array} Array of sub-Arrays of S.
+ */
+function comb(S, k){
+    if ( !S || k <= 0 )
+        return new Y.YArray();
+    
+    if (k === 1)
+        return Y(S).map(mkArray);
+    
+    return S.reduce(function nComb(acc, v, i){
+        var subsets = comb(S.slice(i+1), k-1);
+        return acc.concat( subsets.invoke('concat', [v]) );
+    }, new Y.YArray());
+}
+function mkArray(v){ return new Y.YArray([v]); }
+
+/**
+ * Calculates the powerset of S (set of all subsets, P(S)).
+ * @param {Array} S A collection.
+ * @param {Number} [n] Optional upper-bound on subset size. Defaults to the length of S.
+ * @return {Array} Array of sub-Arrays of S.
+ */
+function powerset(S, n){
+    n = (n < 0 || n === undefined) ? S.length : (!S ? 0 : n);
+    for (var P = new Y.YArray(), i=1; i <= n; ++i)
+        P.extend( comb(S,i) );
+    return P;
+}
+
+/**
+ * @param {Array} T Array of Event-part Arrays.
+ * @return {Array}
+ */
+function propertyset(T){
+    var ps = new Y.YArray();
+    if ( !T || !T.length )
+        return ps;
+    
+    return T.reduce(function(acc, t){ 
+                return acc.extend(t.split('-'), t);
+            }, ps)
+        .concat(T, powerset(T).map(joiner))
+        .unique();
+}
+
+var _prefixes = [ 'state', 'core', 'chrome', 'video', 'share' ]
+,   _suffixes = [ 'user', 'auto' ]
+;
+function cmp( a, b ) {
+    var a_suffix = _suffixes.indexOf(a) !== -1
+    ,   b_suffix = _suffixes.indexOf(b) !== -1
+    ,   a_prefix = _prefixes.indexOf(a) !== -1
+    ,   b_prefix = _prefixes.indexOf(b) !== -1
+    ;
+    if (a_prefix === b_prefix && a_suffix === b_suffix)
+        return 0;
+    else if (a_prefix || b_suffix)
+        return -1;
+    else
+        return 1;
+}
+
+function shove(A, v){ A.push(v); return A; }
+function sorter(A){ A.sort(cmp); return A; }
+function joiner(A){ return A.join('.'); }
+
+function unique(A){
+    return A.reduce(function(acc, v) {
+        return (acc.indexOf(v) === -1) ? shove(acc, v) : acc;
+    }, Y([]));
+}
+
+
+Y.extend( exports, { comb:comb, powerset:powerset, propertyset:propertyset, unique:unique, getMatchingEvents:getMatchingEvents });
+Y.extend( Y['event'], exports );
index 6b3a9eb..011cb36 100644 (file)
@@ -17,7 +17,8 @@ YCollection.subclass('YArray', function(YArray){
     // Add YArray to the things that count as arrays
     type.isArray.types.push(YArray);
     
-    var newYArray = YArray.instantiate.bind(YArray);
+    var newYArray = YArray.instantiate;
+    // var newYArray = YArray.instantiate.bind(YArray);
     
     proxy({ target:YArray, donor:_Array, context:'_o',
         names:'indexOf lastIndexOf shift join'.split(' ') });
@@ -30,14 +31,19 @@ YCollection.subclass('YArray', function(YArray){
     proxy({ target:YArray, donor:_Array, context:'_o', wrap:newYArray,
         names:['slice'] });
     
+    function unwrapY(o){
+        return (o instanceof YArray ? o.end() : o);
+    }
+    
     
     
     this['init'] =
     function initYArray(o){
-        if (o instanceof YArray)
-            this._o = o._o;
-        else
-            this._o = o || [];
+        this._o = (
+            ( o === undefined     ? []   :
+            ( o instanceof Array  ? o    :
+            ( o instanceof YArray ? o._o :
+                [o] )))); // lisp strikes back!
     };
     
     this['get'] =
@@ -105,27 +111,6 @@ YCollection.subclass('YArray', function(YArray){
                 return v;
     };
     
-    /**
-     * Intersects this YArray with another collection, returning a new YArray.
-     * The membership test uses Y(a).has(), so it is possible to intersect collections of different types.
-     * For YArray and YObject, .has() uses strict equality (===) via .indexOf().
-     * 
-     * @param {Array|Object|YCollection} a Comparison collection.
-     * @return {YArray} A new YArray of all elements in {this} found in the supplied collection.
-     * 
-     *      var foo = /foo/;
-     *      var A = [foo, 'A', 1, 2, 3, 'C', /foo/];
-     *      var B = [foo, 'B', 3, 'A', 1, /foo/];
-     *      var I = Y(A).intersect(B);
-     *      I.toString() === "YArray([/foo/,A,1,3])"; // true
-     *      I.get(0) === foo; // true
-     */
-    this['intersect'] =
-    function intersect(a){
-        var A = Y(a);
-        return this.filter(A.has, A);
-    };
-    
     this['clear'] =
     function clear(){
         var A = this._o;
@@ -134,10 +119,6 @@ YCollection.subclass('YArray', function(YArray){
         return this;
     };
     
-    function unwrapY(o){
-        return (o instanceof YArray ? o.end() : o);
-    }
-    
     this['concat'] =
     function concat( donor ){
         var A = this._o;
@@ -234,5 +215,55 @@ YCollection.subclass('YArray', function(YArray){
         }, Y({}), this );
     };
     
+    /**
+     * Set Intersection (A ^ B)
+     * Intersects this YArray with another collection, returning a new YArray.
+     * The membership test uses Y(a).has(), so it is possible to intersect collections of different types.
+     * For YArray and YObject, .has() uses strict equality (===) via .indexOf().
+     * 
+     * @param {Array|Object|YCollection} a Comparison collection.
+     * @return {YArray} A new YArray of all elements in {this} found in the supplied collection.
+     * 
+     *      var foo = /foo/;
+     *      var A = [foo, 'A', 1, 2, 3, 'C', /foo/];
+     *      var B = [foo, 'B', 3, 'A', 1, /foo/];
+     *      var I = Y(A).intersect(B);
+     *      I.toString() === "YArray([/foo/,A,1,3])"; // true
+     *      I.get(0) === foo; // true
+     */
+    this['intersect'] =
+    function intersect(a){
+        var A = Y(a);
+        return this.filter(A.has, A);
+    };
+    
+    /**
+     * Set Union (A v B)
+     * @param {Array|Object|YCollection} a Comparison collection.
+     * @return {YArray} A new YArray of all elements in both collections, but without duplicates.
+     */
+    this['union'] =
+    function union(a){
+        return this.concat(a).unique();
+    };
+    
+    /**
+     * Set Difference (A - B)
+     * @param {Array|Object|YCollection} a Comparison collection.
+     * @return {YArray} A new YArray of only elements in this not in supplied collection.
+     */
+    this['difference'] =
+    function difference(a){
+        var A = Y(a);
+        return this.filter(Y(A.has).compose(op.not), A);
+    };
+    
+    // Symmetric Difference
+    this['xor'] =
+    function xor(a){
+        return this.difference(a).concat( Y(a).difference(this) );
+    };
+    
+    
     return this;
 });
index 22143a7..28e4e93 100644 (file)
@@ -123,11 +123,14 @@ function genericize(fn) {
 
 
 
-function _composer(x,fn){ return fn.call(this, x); }
+
 function compose(f,g){
     var fns = slice.call(arguments).map(_Function.toFunction);
-    return function(){
-        return fns.reduce(_composer, slice.call(arguments), this);
+    return function(x){
+        var self = this;
+        return fns.reduce(function (x, fn){
+            return fn.call(self, x);
+        }, x);
     };
 }
 
index 676a1a9..5f8b808 100644 (file)
@@ -12,7 +12,8 @@ var YCollection = require('Y/types/collection').YCollection
 exports['YString'] =
 YCollection.subclass('YString', function(YString){
     
-    var newYString = YString.instantiate.bind(YString);
+    // var newYString = YString.instantiate.bind(YString);
+    var newYString = YString.instantiate;
     proxy({ target:YString, donor:String, context:'_o', wrap:newYString,
         names:'slice substr substring concat replace toLowerCase toUpperCase'.split(' ') });
     proxy({ target:YString, donor:String, context:'_o',
index 83a0242..d8c1880 100644 (file)
@@ -28,7 +28,7 @@ var Y       = require('Y').Y
 ,   Emitter = require('Y/modules/y.event').Emitter
 ,   unwrap  = require('Y/types/function').unwrap
 
-,   isFunction      = Y.isFunction
+// ,   isFunction      = Y.isFunction
 ,   KNOWN_CLASSES   = Class.KNOWN_CLASSES = exports['KNOWN_CLASSES'] = {}
 
 ,   P        = 'prototype'
@@ -89,7 +89,7 @@ function ConstructorTemplate() {
         }, instance);
         
         var initialise = instance.__initialise__;
-        if ( isFunction(initialise) )
+        if ( typeof initialise == 'function' )
             initialise.apply(instance, args);
         
         cls.emit('created', instance, {
@@ -120,7 +120,7 @@ function createInitialise(cls){
             Y.bindAll(instance, binds);
         
         var init = cls.fn.init;
-        if ( isFunction(init) ) {
+        if ( typeof init == 'function' ) {
             var result = init.apply(instance, arguments);
             if (result) instance = result; // XXX: I think this needs to go away
         }
@@ -165,7 +165,7 @@ function Class(className, Parent, members){
     ,   parentMembers = {}
     ;
     
-    if ( !members && !isFunction(Parent) ) {
+    if ( !members && !(typeof Parent == 'function') ) {
         members = Parent;
         Parent  = null;
     }
@@ -176,7 +176,7 @@ function Class(className, Parent, members){
         Parent = Object;
     
     // Parent is the prototype
-    if ( !isFunction(Parent) ) {
+    if ( !(typeof Parent == 'function') ) {
         SuperClass = getProto(Parent).constructor || Object;
         prototype = Parent;
         
@@ -258,7 +258,7 @@ function Class(className, Parent, members){
         }
     
     // Either invoke body constructor...
-    if ( isFunction(members) ) {
+    if ( typeof members == 'function' ) {
         // body fn is responsible for calling mixin, attaching statics, etc
         members.call(prototype, NewClass);
     
@@ -275,7 +275,7 @@ function Class(className, Parent, members){
     // Notify mixins to let them finish up any customization
     if (mixins.length)
         mixins.forEach(function(mxn){
-            if ( mxn && isFunction(mxn.emit) )
+            if ( mxn && (typeof mxn.emit == 'function') )
                 mxn.emit('mixin', NewClass, { 'mixin':mxn, 'cls':NewClass });
         });
     
@@ -355,18 +355,10 @@ new Class('Mixin', Class, {
         },
         
         /**
+         * @param {String} key
          * @return The merged pairs at key taken from all bases.
          */
-        aggregate : function aggregate(key){
-            return this.__bases__
-                .clone()
-                .unshift(this)
-                .reverse()
-                .reduce(function(acc, base){
-                    var proto = (base instanceof Function ? base.prototype : base);
-                    return Y.extend(acc, proto[key]);
-                }, {});
-        }
+        aggregate : Y(aggregate).methodize()
         
     }