#!/usr/bin/env waul-1.2 # Sequence comprehensions | Spencer Tipping # Licensed under the terms of the MIT source code license # Introduction. # Caterwaul pre-1.0 had a module called 'seq' that provided a finite and an infinite sequence class and localized operator overloading to make them easier to use. Using wrapper classes was both # unnecessary (since most sequence operations were done inside the seq[] macro anyway) and problematic, as it required the user to remember to cast sequences back into arrays and such. It also # reduced runtime performance and created a lot of unnecessary copying. # Caterwaul 1.0 streamlines the seq[] macro by removing the sequence classes and operating directly on arrays or array-like things. Not everything in Javascript is an array, but I'm going to # pretend that everything is (or at least looks like one) and rely on the [i] and .length properties. This allows the sequence library to (1) have a very thin design, and (2) compile down to # tight loops without function calls. # Distributive property. # The seq[] modifier distributes across several operators. They are: # | 1. Ternary ?: # 2. Short-circuit && and || # 3. Parentheses # It won't cross a square-bracket or invocation boundary, however. This includes distributing over array elements and [] dereferencing. You can cause it to cross an array boundary by prefixing # the array with ~ (which should be familiar, as it is the same syntax that's used to cause function bodies to be interpreted in sequence context). For instance: # | [1, 2, 3, X] -seq // <- X is interpreted in regular Javascript context # ~[1, 2, 3, X] -seq // <- X is interpreted in sequence context # Notation. # The notation is mostly a superset of the pre-1.0 sequence notation. Operators that have the same functionality as before (others are reserved for future meanings, but probably won't do what # they used to): # | * = map e.g. [1, 2, 3] *[x + 1] |seq -> [2, 3, 4] # *! = each e.g. [1, 2, 3] *![console.log(x)] |seq -> [1, 2, 3] (and logs 1, 2, 3) # / = foldl e.g. [1, 2, 3] /[x - next] |seq -> -4 # /! = foldr e.g. [1, 2, 3] /![x - next] |seq -> 2 # % = filter e.g. [1, 2, 3] %[x & 1] |seq -> [1, 3] # %! = filter-not e.g. [1, 2, 3] %![x & 1] |seq -> [2] # + = concatenate e.g. [1, 2, 3] + [4, 5] |seq -> [1, 2, 3, 4, 5] # | = exists e.g. [1, 2, 3] |[x === 2] |seq -> true # |! = not-exists e.g. [1, 2, 3] |![x >= 4] |seq -> true # Note that ^ has higher precedence than |, so we can use it in a sequence comprehension without interfering with the |seq macro (so long as the |seq macro is placed on the right). # Modifiers. # Modifiers are unary operators that come after the primary operator. These have the same (or similar) functionality as before: # | ~ = interpret something in sequence context e.g. [[1], [2], [3]] *~[x *[x + 1]] |seq -> [[2], [3], [4]] # x = rename the variable from 'x' e.g. [1, 2, 3] *y[y + 1] |seq -> [2, 3, 4] # Here, 'x' means any identifier. Caterwaul 1.0 introduces some new stuff. The map function now has a new variant, *~!. Filter also supports this variant. Like other operators, they support # variable renaming and sequence context. You can do this by putting those modifiers after the *~!; for instance, xs *~!~[exp] interprets 'exp' in sequence context. Similarly, *~!y[exp] uses # 'y' rather than 'x'. # | *~! = flatmap e.g. [1, 2, 3] *~![[x, x + 1]] |seq -> [1, 2, 2, 3, 3, 4] # %~! = map/filter e.g. [1, 2, 3] %~![x & 1 && x + 1] |seq -> [2, 4] # /~! = unfold e.g. 1 /~![x < 5 ? x + 1 : null] |seq -> [1, 2, 3, 4, 5] # |~! = right-exists e.g. [1, 2, 3] |~![x & 1] |seq -> 3 # Variables. # All of the variables from before are still available and the naming is still mostly the same. Each block has access to 'x', which is the immediate element. 'xi' is the index, and 'x0' is the # alternative element for folds. Because all sequences are finite, a new variable 'xl' is available -- this is the total number of elements in the source sequence. The sequence object is no # longer accessible because there may not be a concrete sequence. (I'm leaving room for cross-operation optimizations in the future.) The renaming is done exactly as before: # | [1, 2, 3] *[x + 1] |seq -> [2, 3, 4] # [1, 2, 3] *y[y + 1] |seq -> [2, 3, 4] # [1, 2, 3] *[xi] |seq -> [0, 1, 2] # [1, 2, 3] *foo[fooi] |seq -> [0, 1, 2] # Word operators. # Some operators are designed to work with objects, just like in prior versions. However, the precedence has been changed to improve ergonomics. For example, it's uncommon to use objects as an # intermediate form because all of the sequence operators are built around arrays. Similarly, it's very common to unpack objects immediately before using them. Therefore the unpack operators # should be very high precedence and the pack operator should have very low precedence: # | {foo: 'bar'} /keys |seq -> ['foo'] # {foo: 'bar'} /values |seq -> ['bar'] # {foo: 'bar'} /pairs |seq -> [['foo', 'bar']] # {foo: 'bar'} /pairs |object |seq -> {foo: 'bar'} # {foo: 'bar'} /pairs |mobject |seq -> {foo: ['bar']} # Note that unlike regular modifiers you can't use a variety of operators with each word. Each one is defined for just one form. I may change this in the future, but I'm reluctant to start # with it because it would remove a lot of syntactic flexibility. # Update: After using this in the field, I've found that the low-precedence |object form is kind of a pill. Now the sequence library supports several variants, /object, -object, and |object. # The same is true of mobject, introduced in Caterwaul 1.2. # Prefixes. # New in Caterwaul 1.0.3 is the ability to specify the scope of operation for sequence macros. For instance, you might want to operate on one of several types of data. Normally the sequence # macro assumes arrays, but you may want to modify a unary operator such as *[] to transform an object's keys or values. Prefixes let you do this. # | o %k*[x.substr(1)] -seq (equivalent to o /pairs *[[x[0].substr(1), x[1]]] -object -seq) # o %v*[x.split(/a/)] -seq (equivalent to o /pairs *[[x[0], x[1].split(/a/)]] -object -seq) # Prefixes are generally faster than manual unpacking and repacking. However, some operations (e.g. fold and its variants) don't work with prefixes. The reason is that it's unclear what to do # with the values that correspond to a folded key, for instance. (Imagine what this would mean: o %k/[x + x0] -seq) The following operators can be used with prefixes: # | * = map # *! = each <- returns the original object # % = filter <- removes key/value pairs # %! = filter-not # %~! = map-filter <- changes some key-value pairs, removes others # These operators support the standard set of modifiers, including ~ prefixing and variable renaming. However, indexing variables such as xi and xl are unavailable because no temporary arrays # are constructed. # The following operators cannot be used with prefixes because it's difficult to imagine what purpose they would serve: # | *~! = flatmap # / = foldl # /! = foldr # /~! = unfold # None of the binary operators (e.g. +, -, ^, etc) can be used with prefixes because of precedence. Any prefix would bind more strongly to the left operand than it would to the binary # operator, which would disrupt the syntax tree. # Folding prefixes. # New in Caterwaul 1.1 is the ability to specify fold prefixes. This allows you to specify the initial element of a fold: # | xs /[0][x0 + x*x] -seq (sum the squares of each element) # xs /~[[]][x0 + [x, x + 1]] -seq (equivalent to xs *~![[x, x + 1]] -seq) # From 1.1.5 onwards, these fold prefixes can be used with other operators as well. For example: # | 1 /~![xi < 10][x + 1] -seq (return the array [1, 2, ..., 9]: the first block conditionalizes the unfold) # xs %~![x < 0][-x] -seq (return an array of the negation of all negative elements in the first) # xs *~![xi < 10][f(x)] -seq (return the tenth composition of f over x) # Function promotion. # Caterwaul 1.1 also adds support for implicit function promotion of sequence block expressions: # | f(x) = x + 1 # seq in [1, 2, 3] *f # seq in [-1, 0, 1] %f # You can use this to make method calls, which will remain bound to the original object: # | xs *foo.bar -seq (equivalent to xs *[foo.bar(x)] -seq) # xs *(bar + bif).baz -seq (equivalent to xs *[(bar + bif).baz(x)] -seq) # The only restriction is that you can't use a bracketed expression as the last operator; otherwise it will be interpreted as a block. You also can't invoke a promoted function in sequence # context, since it is unclear what the intent would be. # Calling convention. # All functions you promote will always be called with these arguments, in this order: # | f(x, x0, xi, xl) # This may seem strange, since x0 may or may not be defined. I chose this setup to simplify code generation, even if it is a bit redundant. If x0 isn't provided by the current operator, then # its value will be undefined. # Scope wrapping. # Normally sequences use thin compilation; that is, the body of each sequence element is inserted directly into a for-loop. This increases performance by eliminating a function call, but it # has the usual caveats about variable sharing. For instance: # | fs = [1, 2, 3] *[delay in x] -seq # fs[0]() -> 3 (counterintuitive) # fs[1]() -> 3 (counterintuitive) # fs[2]() -> 3 (expected) # The problem is that all three closures get the same value of 'x', which is a single lexically-scoped variable. To fix this, caterwaul 1.1 introduces the unary + modifier on blocks. This # wraps them in a closure to give each iteration its own lexical scope: # | fs = [1, 2, 3] *+[delay in x] -seq # fs[0]() -> 1 # fs[1]() -> 2 # fs[2]() -> 3 # Numbers. # Caterwaul 1.0 removes support for the infinite stream of naturals (fun though it was), since all sequences are now assumed to be finite and are strictly evaluated. So the only macros # available are n[] and ni[], which generate finite sequences of evenly-spaced numbers. The only difference between n[] and ni[] is that ni[] uses an inclusive upper bound, whereas n[] is # exclusive. # | n[1, 10] -seq -> [1, 2, 3, 4, 5, 6, 7, 8, 9] # | ni[1, 10] -seq -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # n[10] -seq -> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # ni[10] -seq -> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # n[0, 10, 2] -seq -> [0, 2, 4, 6, 8] # ni[0, 10, 2] -seq -> [0, 2, 4, 6, 8, 10] # Slicing. # There are two reasons you might want to slice something. One is that you're legitimately taking a subsequence of the original thing; in that case, you can invoke the .slice() method # manually. The more interesting case is when you want to promote a non-array into an array. This is such a common thing to do (and has so much typing overhead) that I've dedicated a shorthand # to it: # | +xs -seq, where [xs = arguments] -> Array.prototype.slice.call(arguments) # Generated code. # Previously the code was factored into separate methods that took callback functions. (Basically the traditional map/filter/each arrangement in functional languages.) However, now the library # optimizes the methods out of the picture. This means that now we manage all of the dataflow between the different sequence operators. I thought about allocating gensym variables -- one for # each temporary result -- but this means that the temporary results won't be garbage-collected until the entire sequence comprehension is complete. So instead it generates really gnarly code, # with each dependent sequence listed in the for-loop variable initialization. # Luckily this won't matter because, like, there aren't any bugs or anything ;) # Type closure. # Caterwaul 1.1.3 includes a modification that makes the sequence library closed over types. Suppose you've got a special collection type that you want to use instead of arrays. A sequence # operation will assume that your collection type implements .length and [i], but any maps or flat maps that you do will return new instances of your type rather than generalizing to a regular # array. For example: # | xs = new my_sequence(); # ys = xs *f -seq; # ys.constructor === xs.constructor // -> true # In order for this to work, your sequence classes need to implement a nullary constructor that creates an empty instance. They should also implement a variadic push() method. # Note that this is a breaking change! The fix is to prepend sequence variables with '+' (see 'Slicing' above). This breaks any code that relies on the seq library taking care of Arguments # objects by promoting them into arrays. # Portability. # The seq library is theoretically portable to syntaxes besides JS, but you'll probably want to do some aggressive preprocessing if you do this. It assumes a lot about operator precedence and # such (from a design perspective). caterwaul.module('std.seq', 'js js_literals words', function ($) { $.seq(caterwaul_function) = caterwaul_function -se [it.modifiers.seq(match) = seq_expand.call(seq_expand, anon_pattern.replace({_x: match._expression})) -re- this(it) /when.it] -where [anon_pattern = anon('S[_x]'.qs), seq_expand = $($.alternatives(operator_macros.concat(word_macros)))], where [anon = $.anonymizer('S'), rule(p, e) = $.rereplacer(anon(p), e.constructor === $.syntax ? anon(e) : e), operator_macros = [rule('S[_x]'.qs, '_x'.qs), rule('S[_xs + _ys]'.qs, concat), // Distributive property rule('S[(_x)]'.qs, '(S[_x])'.qs), rule('S[_x[_y]]'.qs, 'S[_x][_y]'.qs), rule('S[_xs(_ys)]'.qs, 'S[_xs](_ys)'.qs), rule('S[[_x]]'.qs, '[_x]'.qs), rule('S[_x, _y]'.qs, 'S[_x], S[_y]'.qs), rule('S[_xs._p]'.qs, 'S[_xs]._p'.qs), rule('S[~[_x]]'.qs, '[S[_x]]'.qs), // ~ modifier on arrays rule('S[~_xs(_ys)]'.qs, 'S[_xs](S[_ys])'.qs), // ~ modifier on function calls rule('S[_x ? _y : _z]'.qs, '(S[_x]) ? (S[_y]) : (S[_z])'.qs), rule('S[_x && _y]'.qs, '(S[_x]) && (S[_y])'.qs), rule('S[_x || _y]'.qs, '(S[_x]) || (S[_y])'.qs), // Unary seq operators rule('S[+_xs]'.qs, 'Array.prototype.slice.call((_xs))'.qs), rule('S[_xs %_thing]'.qs, handle_filter_forms), rule('S[_xs *_thing]'.qs, handle_map_forms), rule('S[_xs /_thing]'.qs, handle_fold_forms), rule('S[_xs |_thing]'.qs, handle_exists_forms), rule('S[_xs %k*_thing]'.qs, handle_kmap_forms), rule('S[_xs %v*_thing]'.qs, handle_vmap_forms), rule('S[_xs %k%_thing]'.qs, handle_kfilter_forms), rule('S[_xs %v%_thing]'.qs, handle_vfilter_forms)] -where [// High-level form specializations unrecognized(reason) = raise [new Error(reason)], use_form(form, xs, body, init, vars) = form ? form.replace({_f: body, _init: init}).replace($.merge({_s: xs}, vars)) : unrecognized('unsupported sequence operator or modifiers used on #{body}'), operator_case(forms)(match) = parse_modifiers(match._thing, use(forms.normal, forms.inormal), use(forms.bang, forms.ibang), use(forms.tbang, forms.itbang)) -where [xs = match._xs, expander = this, form_function(form)(body, vars) = use_form(form, xs, body, null, vars), iform_function(form)(body, init, vars) = use_form(form, xs, body, init, vars), use(form, iform)(body) = parse_body(body, expander, form_function(form), iform_function(iform))], handle_map_forms = operator_case({normal: map, bang: each, tbang: flatmap, itbang: iterate}), handle_filter_forms = operator_case({normal: filter, bang: filter_not, tbang: map_filter, itbang: imap_filter}), handle_fold_forms = operator_case({normal: foldl, bang: foldr, tbang: unfold, inormal: ifoldl, ibang: ifoldr, itbang: iunfold}), handle_kmap_forms = operator_case({normal: kmap, bang: keach}), handle_kfilter_forms = operator_case({normal: kfilter, bang: kfilter_not, tbang: kmap_filter}), handle_vmap_forms = operator_case({normal: vmap, bang: veach}), handle_vfilter_forms = operator_case({normal: vfilter, bang: vfilter_not, tbang: vmap_filter}), handle_exists_forms = operator_case({normal: exists, bang: not_exists, tbang: r_exists}), // Body parsing block = anon('[_x]'.qs), block_with_variable = anon('_var@0[_x]'.qs), block_with_init = anon('[_init][_x]'.qs), block_with_variable_and_init = anon('_var@0[_init][_x]'.qs), block_with_closure = anon('+_x'.qs), block_with_seq = anon('~_x'.qs), standard_names = {_x: 'x', _x0: 'x0', _xi: 'xi', _xl: 'xl', _xs: 'xs', _xr: 'xr'}, prefixed_names(p) = {_x: p , _x0: '#{p}0', _xi: '#{p}i', _xl: '#{p}l', _xs: '#{p}s', _xr: '#{p}r'}, function_promotion = anon('(_f).call({_x0: _x0, _xi: _xi, _xl: _xl, _xs: _xs, _xr: _xr}, _x)'.qs), promote_function(f) = function_promotion.replace({_f: f}), closure_wrapper = anon('(function (_x, _x0, _xi, _xl, _xs, _xr) {return _f}).call(this, _x, _x0, _xi, _xl, _xs, _xr)'.qs), close_body(vars, f) = closure_wrapper.replace(vars).replace({_f: f}), seq_pattern = anon('S[_x]'.qs), promote_seq(f) = seq_pattern.replace({_x: f}), parse_body(tree, expand, normal, init) = ((r = block_with_seq.match(tree)) ? parse_body(r._x, expand, sequence_context_normal, sequence_context_init) : (r = block_with_closure.match(tree)) ? parse_body(r._x, expand, wrapping_normal, wrapping_init) : (r = block_with_variable_and_init.match(tree)) ? init(r._x, r._init, prefixed_names(r._var)) : (r = block_with_init.match(tree)) ? init(r._x, r._init, standard_names) : (r = block_with_variable.match(tree)) ? normal(r._x, prefixed_names(r._var)) : (r = block.match(tree)) ? normal(r._x, standard_names) : normal(promote_function(tree), standard_names)) -where [in_sequence_context(f) = expand.call(expand, promote_seq(f)), sequence_context_normal(f, names) = normal(in_sequence_context(f), names), sequence_context_init(f, init_expression, names) = init (in_sequence_context(f), init_expression, names), wrapping_normal(f, names) = normal(close_body(names, f), names), wrapping_init(f, init_expression, names) = init (close_body(names, f), init_expression, names), r = null], // Modifier parsing tbang_modifier = anon('~!_x'.qs), bang_modifier = anon('!_x'.qs), parse_modifiers(tree, normal, bang, tbang) = ((result = tbang_modifier.match(tree)) ? tbang(result._x) : (result = bang_modifier.match(tree)) ? bang(result._x) : normal(tree)) -where [result = null]] -where [// Setup for form definitions (see below) loop_anon = $.anonymizer('x', 'y', 'i', 'j', 'l', 'lj', 'r', 'o', 'k'), scope = anon('(function (_xs) {var _x, _x0, _xi, _xl, _xr; _body}).call(this, S[_s])'.qs), scoped(t) = scope.replace({_body: t}), form(x) = x /!anon /!scoped /!loop_anon, // Form definitions map = form('for (var _xr = new _xs.constructor(), _xi = 0, _xl = _xs.length; _xi < _xl; ++_xi) _x = _xs[_xi], _xr.push((_f)); return _xr'.qs), each = form('for (var _xi = 0, _xl = _xs.length; _xi < _xl; ++_xi) _x = _xs[_xi], (_f); return _xs'.qs), flatmap = form('for (var _xr = new _xs.constructor(), _xi = 0, _xl = _xs.length; _xi < _xl; ++_xi) _x = _xs[_xi], _xr.push.apply(_xr, Array.prototype.slice.call((_f))); return _xr'.qs), iterate = form('for (var _x = _xs, _xi = 0, _x0, _xl; _x0 = (_init); ++_xi) _x = (_f); return _x'.qs), filter = form('for (var _xr = new _xs.constructor(), _xi = 0, _xl = _xs.length, _x0; _xi < _xl; ++_xi) _x = _xs[_xi], (_f) && _xr.push(_x); return _xr'.qs), filter_not = form('for (var _xr = new _xs.constructor(), _xi = 0, _xl = _xs.length, _x0; _xi < _xl; ++_xi) _x = _xs[_xi], (_f) || _xr.push(_x); return _xr'.qs), map_filter = form('for (var _xr = new _xs.constructor(), _xi = 0, _xl = _xs.length, _x0, _y; _xi < _xl; ++_xi) _x = _xs[_xi], (_y = (_f)) && _xr.push(_y); return _xr'.qs), imap_filter = form('for (var _xr = new _xs.constructor(), _xi = 0, _xl = _xs.length, _x0; _xi < _xl; ++_xi) _x = _xs[_xi], (_x0 = (_init)) && _xr.push(_f); return _xr'.qs), foldl = form('for (var _x0 = _xs[0], _xi = 1, _xl = _xs.length; _xi < _xl; ++_xi) _x = _xs[_xi], _x0 = (_f); return _x0'.qs), foldr = form('for (var _xl = _xs.length, _xi = _xl - 2, _x0 = _xs[_xl - 1]; _xi >= 0; --_xi) _x = _xs[_xi], _x0 = (_f); return _x0'.qs), unfold = form('for (var _xr = [], _x = _xs, _xi = 0; _x !== null; ++_xi) _xr.push(_x), _x = (_f); return _xr'.qs), ifoldl = form('for (var _x0 = (_init), _xi = 0, _xl = _xs.length; _xi < _xl; ++_xi) _x = _xs[_xi], _x0 = (_f); return _x0'.qs), ifoldr = form('for (var _xl = _xs.length - 1, _xi = _xl, _x0 = (_init); _xi >= 0; --_xi) _x = _xs[_xi], _x0 = (_f); return _x0'.qs), iunfold = form('for (var _xr = [], _x = _xs, _xi = 0, _x0; _x0 = (_init); ++_xi) _xr.push(_x), _x = (_f); return _xr'.qs), exists = form('for (var _x = _xs[0], _xi = 0, _xl = _xs.length, x; _xi < _xl; ++_xi) {_x = _xs[_xi]; if (x = (_f)) return x} return false'.qs), not_exists = form('for (var _x = _xs[0], _xi = 0, _xl = _xs.length, x; _xi < _xl; ++_xi) {_x = _xs[_xi]; if (x = (_f)) return false} return true'.qs), r_exists = form('for (var _xl = _xs.length, _xi = _xl - 1, _x = _xs[_xi], x; _xi >= 0; --_xi) {_x = _xs[_xi]; if (x = (_f)) return x} return false'.qs), concat = anon('(S[_xs]).concat((S[_ys]))'.qs), kmap = form('var _xr = new _xs.constructor(); for (var _x in _xs) if (Object.prototype.hasOwnProperty.call(_xs, _x)) _xr[_f] = _xs[_x]; return _xr'.qs), keach = form(' for (var _x in _xs) if (Object.prototype.hasOwnProperty.call(_xs, _x)) _f; return _xs'.qs), kfilter = form('var _xr = new _xs.constructor(); for (var _x in _xs) if (Object.prototype.hasOwnProperty.call(_xs, _x) && (_f)) _xr[_x] = _xs[_x]; return _xr'.qs), kfilter_not = form('var _xr = new _xs.constructor(); for (var _x in _xs) if (Object.prototype.hasOwnProperty.call(_xs, _x) && ! (_f)) _xr[_x] = _xs[_x]; return _xr'.qs), kmap_filter = form('var _xr = new _xs.constructor(), x; for (var _x in _xs) if (Object.prototype.hasOwnProperty.call(_xs, _x) && (x = (_f))) _xr[x] = _xs[_x]; return _xr'.qs), vmap = form('var _xr = new _xs.constructor(); for (var k in _xs) if (Object.prototype.hasOwnProperty.call(_xs, k)) _x = _xs[k], _xr[k] = (_f); return _xr'.qs), veach = form(' for (var k in _xs) if (Object.prototype.hasOwnProperty.call(_xs, k)) _x = _xs[k], _f; return _xs'.qs), vfilter = form('var _xr = new _xs.constructor(); for (var k in _xs) if (Object.prototype.hasOwnProperty.call(_xs, k)) _x = _xs[k], (_f) && (_xr[k] = _x); return _xr'.qs), vfilter_not = form('var _xr = new _xs.constructor(); for (var k in _xs) if (Object.prototype.hasOwnProperty.call(_xs, k)) _x = _xs[k], (_f) || (_xr[k] = _x); return _xr'.qs), vmap_filter = form('var _xr = new _xs.constructor(), x; for (var k in _xs) if (Object.prototype.hasOwnProperty.call(_xs, k)) _x = _xs[k], x = (_f), x && (_xr[k] = x); return _xr'.qs)], word_macros = [rule('S[n[_u]]'.qs, n), rule('S[ni[_u]]'.qs, ni), rule('S[n[_l, _u]]'.qs, n), rule('S[ni[_l, _u]]'.qs, ni), rule('S[n[_l, _u, _step]]'.qs, n), rule('S[ni[_l, _u, _step]]'.qs, ni), rule('S[_o /keys]'.qs, keys), rule('S[_o |object]'.qs, object), rule('S[_o /mobject'.qs, mobject), rule('S[_o /values]'.qs, values), rule('S[_o -object]'.qs, object), rule('S[_o -mobject'.qs, mobject), rule('S[_o /pairs]'.qs, pairs), rule('S[_o /object]'.qs, object), rule('S[_o |mobject'.qs, mobject)] -where [n(match) = n_pattern .replace($.merge({_l: '0', _step: '1'}, match)), ni(match) = ni_pattern.replace($.merge({_l: '0', _step: '1'}, match)), n_pattern = anon('(function (i, u, s) {if ((u - i) * s <= 0) return []; for (var r = [], d = u - i; d > 0 ? i < u : i > u; i += s) r.push(i); return r})((_l), (_u), (_step))'.qs), ni_pattern = anon('(function (i, u, s) {if ((u - i) * s <= 0) return []; for (var r = [], d = u - i; d > 0 ? i <= u : i >= u; i += s) r.push(i); return r})((_l), (_u), (_step))'.qs), scope = anon('(function (o) {_body}).call(this, (S[_o]))'.qs), scoped(t) = scope.replace({_body: t}), form(p) = "tree.replace(_)".qf -where [tree = scoped(anon(p))], keys = form('var ks = []; for (var k in o) Object.prototype.hasOwnProperty.call(o, k) && ks.push(k); return ks'.qs), values = form('var vs = []; for (var k in o) Object.prototype.hasOwnProperty.call(o, k) && vs.push(o[k]); return vs'.qs), pairs = form('var ps = []; for (var k in o) Object.prototype.hasOwnProperty.call(o, k) && ps.push([k, o[k]]); return ps'.qs), object = form('for (var r = {}, i = 0, l = o.length, x; i < l; ++i) x = o[i], r[x[0]] = x[1]; return r'.qs), mobject = form('for (var r = {}, i = 0, l = o.length, x; i < l; ++i) x = o[i], (r[x[0]] || (r[x[0]] = [])).push(x[1]); return r'.qs)]]}); # Generated by SDoc