I've made the following changes to PS LOOP and pushed them on a branch
called "loop" pending the fixing of the pretty-printer as we discussed
a couple weeks ago.

These changes capture some idioms that were common in our code. They
deviate from CL's LOOP (I could not bring myself to implement "BEING
THE HASH-KEYS OF") in ways that are appropriate for JS semantics. Since
PS LOOP was not fully compatible with CL's LOOP anyway, this is not much
of a loss.

(1) Added a FOR..OF clause that iterates through the keys of a JS object.
This translates to JS's "for k in obj" loops and corresponds roughly
to "loop for k being the hash-keys of" in CL.

  (ps (loop :for k :of obj :do (foo k)))

  "for (var k in obj) {
      foo(k);
  };"

To bind both key and value, you specify a pair. (This is an icky bit
of syntax but it feels like the least icky option of the ones I'm
aware of.)

  (ps (loop :for (k v) :of obj :do (foo k v)))

  "for (var k in obj) {
      var v = obj[k];
      foo(k, v);
  };"

Destructuring works on the value position just like they do everywhere
else, so you can unpack an array. (Note that in this example the key
variable isn't needed so we can put NIL in there.)

  (ps (loop :for (nil (v1 v2)) :of obj :do (foo v1 v2)))

  "for (var _js23095 in obj) {
      var _js23096 = obj[_js23095];
      var v1 = _js23096[0];
      var v2 = _js23096[1];
      foo(v1, v2);
  };"

... or named properties (by using keywords):

  (ps (loop :for (nil (:hello :world)) :of obj :do (foo hello world)))

  "for (var _js23097 in obj) {
      var var23099 = obj[_js23097];
      var hello23101 = var23099.hello;
      var world23102 = var23099.world;
      foo(hello23101, world23102);
  };"

Side note: To get the scoping right for parallel loop clauses is
tricky because LOOP orders things "step test step test" where JS's FOR
does "step step test test", meaning that the second step will be
evaluated even if the first test fails. It got trickier with the
introduction of FOR..OF, so I adopted a simpler implementation that
has some advantages (much tighter generated loop code) but also some
disadvantages: (a) it was a pain to support :INITIALLY clauses so I
dropped them (I'd be surprised if anyone ever used it), and (b)
complex loops sometimes require an extra temporary variable to track
whether the loop is on its first iteration or not.

(2) There is now a MAP..TO clause that works like COLLECT only instead
of building an array, builds an object mapping keys to values:

  (ps (loop :for str :in strs :map str :to (length str)))

  "(function () {
      var _js23109 = strs.length;
      var map23110 = {  };
      for (var _js23108 = 0; _js23108 < _js23109; _js23108 += 1) {
          var str = strs[_js23108];
          map23110[str] = str.length;
      };
      return map23110;
  })();"

(3) In FOR..ON, a BY term can now be an integer. For example, you can
say "by 2" instead of "by #'cddr" which makes little sense for JS arrays:

  (ps (loop :for (key val) :on pairs :by 2 :do (foo key val)))

  "for (var _js23094 = pairs; _js23094.length > 0; _js23094 = _js23094.slice(2)) {
      var key = _js23094[0];
      var val = _js23094[1];
      foo(key, val);
  };"

If BY doesn't get a number, it assumes it got a function:

  (ps (loop :for (key val) :on pairs :by blah :do (foo key val)))

  "for (var _js23112 = pairs; _js23112.length > 0; _js23112 = blah(_js23112)) {
      var key = _js23112[0];
      var val = _js23112[1];
      foo(key, val);
  };"

Daniel