1

I'm having a little trouble with while loops in coffeescript.

The original function is as the following and the source is this answer:

// Return all pattern matches with captured groups
RegExp.prototype.execAll = function(string) {
    var match = null;
    var matches = new Array();
    while (match = this.exec(string)) {
        var matchArray = [];
        for (i in match) {
            if (parseInt(i) == i) {
                matchArray.push(match[i]);
            }
        }
        matches.push(matchArray);
    }
    return matches;
}

It works as intended. I have converted it via js2coffee and the coffee script is:

# Return all pattern matches with captured groups
RegExp::execAll = (string) ->

  matches = new Array()
  match = null
  while match = @exec(string) 
    matchArray = new Array()
    for i of match
      matchArray.push match[i]  if parseInt(i) is i
    matches.push matchArray
  matches

Which compiles to:

RegExp.prototype.execAll = function(string) {
  var i, match, matchArray, matches;
  matches = new Array();
  match = null;
  while (match = this.exec(string)) {
    matchArray = new Array();
    for (i in match) {
      if (parseInt(i) === i) {
        matchArray.push(match[i]);
      }
    }
    matches.push(matchArray);
  }
  return matches;
};

The result is:

[
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  []
]

I think this doesn't work because of the variable scope since the only difference I can see is this line:

RegExp.prototype.execAll = function(string) {
  var i, match, matchArray, matches;

versus the original:

RegExp.prototype.execAll = function(string) {
    var match = null;
    var matches = new Array();
    while (match = this.exec(string)) {
        var matchArray = [];

Notice how matchArray is scoped out... I couldn't find a way around this, but as I was researching I found these questions Were `do...while` loops left out of CoffeeScript...? and How do I declare a variable in a specific scope in coffeescript? and I'm pretty much out of ideas here... Any other way of matching all with regexp other than this while loop (by the way I have tried \gm flags and it still hits only one match)? Or is there a way of getting this scope right in coffee script?

The whole Coffee Script code is :

fs = require 'fs'

text = fs.readFileSync('test.md','utf8')

#console.log text

regex = /^(?:@@)(\w+):(.*.)/gm
# Return all pattern matches with captured groups
RegExp::execAll = (string) ->

  matches = new Array()
  match = null
  while match = @exec(string) 
    matchArray = new Array()
    for i of match
      matchArray.push match[i]  if parseInt(i) is i
    matches.push matchArray
  matches

res = regex.execAll text
#console.log regex.exec text
console.log (JSON.stringify(res, null, "  ") )

And the Javascript that works is this:

var fs, regex, res, text;

fs = require('fs');

text = fs.readFileSync('test.md', 'utf8');

regex = /^(?:@@)(\w+):(.*.)/gm;

// Return all pattern matches with captured groups
RegExp.prototype.execAll = function(string) {
    var match = null;
    var matches = new Array();
    while (match = this.exec(string)) {
        var matchArray = [];
        for (i in match) {
            if (parseInt(i) == i) {
                matchArray.push(match[i]);
            }
        }
        matches.push(matchArray);
    }
    return matches;
}

res = regex.execAll(text);

//console.log(regex.exec(text));

console.log(JSON.stringify(res, null, "  "));
Community
  • 1
  • 1
Logan
  • 10,649
  • 13
  • 41
  • 54
  • Just a note: On the result there are 9 empty arrays for 9 actual matches (those are filled with the working javascript code) – Logan Dec 29 '12 at 05:19

1 Answers1

7

The actual problem is not what you think it is. The actual problem is that the == compiled to ===. Here's how you fix it:

# Return all pattern matches with captured groups
RegExp::execAll = (string) ->
  matches = []
  while match = @exec string
    matches.push(group for group in match)
  matches

But you may be wondering why === doesn't work. Thus, I present to you:

Why === doesn't work (in this case)

parseInt(i) == i was supposed to determine if i is a string that represents an integer. You can try that in JavaScript on a few values and see that it works. parseInt(i) is a number, whereas i is a string. In JavaScript, if you're using == on a string and a number, it will implicitly coerce and the test will work as you might expect. This implicit coercion is often unexpected, so CoffeeScript disallows it from happening by having no equivalent of the == operator, translating == (and is) into ===, which first compares types. In that case, the number and the string will never be equal, so the if's condition is never met, so nothing is ever pushed onto the array.

icktoofay
  • 126,289
  • 21
  • 250
  • 231
  • I have just found out that if you do something like `if \`"string" == "string"\` then return true` you can have `==` signs. So between `\`here\`` you can write literal javascript – Logan Dec 31 '12 at 01:36
  • @Logan: You could indeed do that, but you might want to put the operands into variables first so you can use CoffeeScript syntax in the operands. Regardless, I think you'll agree that for this particular case, using an idiomatic translation is better than trying to literally translate it. – icktoofay Dec 31 '12 at 02:19
  • I agree with you on idiomatic translation. However I don't understand how to use CoffeeScript syntax in the operands by putting them into variables. Do you mean `if \`str == str2\` then return true` ? – Logan Dec 31 '12 at 02:45
  • @Logan: I'm saying if you wanted to use CoffeeScript syntax of the operands (like, say, `personA?.name == personB?.name`), but wanted to use `==` rather than `===` in the translated JavaScript, you might have to use the CoffeeScript `personAName = personA?.name; personBName = personB?.name; \`personAName == personBName\``. – icktoofay Dec 31 '12 at 02:49