user2039981
user2039981

Reputation:

Javascript - Replace using global modifier fails

This is my code:

str = str.replace(/(\+?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity))([%!]+)/g, function(m, $1,     $2) {
      return "(" + $1 + ")" + $2.replace(/%/g, ".percent()")
                                .replace(/!/g, ".fact()");
});
str = str.replace(/([+]?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity)|\)|\%|\!)\^([-+]?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity)|\()/g, function(m, $1, $2) {
    if ($1 == ")" && $2 == "(") {
        return (").pow(");
    }
    if ($1 == "%" && $2 == "(") {
        return ("%.pow(");
    }
    if ($1 == "!" && $2 == "(") {
        return ("!.pow(");
    }
    if ($1 == ")") {
        return (").pow(" + $2 + ")");
    }
    if ($1 == "%") {
        return ("%.pow(" + $2 + ")");
    }
    if ($1 == "!") {
        return ("!.pow(" + $2 + ")");
    }
    if ($2 == "(") {
        return ("(" + $1 + ").pow(");
    }
    return ("(" + $1 + ").pow(" + $2 + ")");
});

And the problem is that I want to replace all occurrences, which it won't do.

"2%" -> "(2).percent()", working
"2%%" -> "(2).percent()%", should be "(2).percent().percent()"

I use the g modifier so I can't see what the problem is, how do I fix this?

Upvotes: 0

Views: 156

Answers (1)

p.s.w.g
p.s.w.g

Reputation: 149068

The problem is that your pattern matches some number, "Infinity", or ), followed by a %, but the second % isn't preceded by any of those, so it simply isn't a match.

It looks like you were hoping that by matching a ) sign before the percent, it will pick up on the closing ) from your previous replacement and use that as part of the next match. The regular expression engine doesn't work like that. Once a match is found, it moves on to the next match.

In other words, the g modifier doesn't mean 'recursively substitute replacements until no match is found', it means, 'replace every match found in the original string'.

I'm sure there are other solutions, but you could try this:

str = str.replace(/(\+?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity))(%+)/g, function(m, $1, $2) {
  return "(" + $1 + ")" + new Array($2.length + 1).join(".percent()");
});

Given your updated question, you could do something like this:

str = str.replace(/(\+?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity))([%!]+)/g, function(m, $1, $2) {
  return "(" + $1 + ")" + $2.replace(/%/g, ".percent()")
                            .replace(/!/g, ".fact()");
});

Given your second update:

var re = /(\+?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity))((?:[%!]|\^[+-]?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity|\())*)/g;
str = str.replace(
  re,
  function(m, $1, $2) {
    return "(" + $1 + ")" + $2.replace(/\^([^(^%!]+)/g, ".pow($1)")
                              .replace(/\^\(/g, ".pow(")
                              .replace(/%/g, ".percent()")
                              .replace(/!/g, ".fact()");
  });

$('#go').click(function() {
  var str = $("#input").val();
  var re = /(\+?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity))((?:[%!]|\^[+-]?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity|\())*)/g;
  str = str.replace(
    re,
    function(m, $1, $2) {
      return "(" + $1 + ")" + $2.replace(/\^([^(^%!]+)/g, ".pow($1)")
                                .replace(/\^\(/g, ".pow(")
                                .replace(/%/g, ".percent()")
                                .replace(/!/g, ".fact()");
    });
    $("#output").text(str);
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="input" value="5!^3%" /><button id="go">Go</button>
<div id="output" />

However, I would caution you before you go much further down this road. It really seems like this problem would be best tackled with a proper parser. Check out jison for a good resource to help you build your parser.


I stand by my previous word of caution about the limited usefulness of regular expressions, but here's a somewhat simpler approach you might try, as long as you're sure the general format of your input is roughly correct, and you don't mind a few unnecessary parentheses:

str = str.replace(/[+-]?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity)/g, "($&)")
         .replace(/%/g, ".percent()")
         .replace(/!/g, ".fact()")
         .replace(/\^/g, ".pow");

$('#go').click(function() {
  var str = $("#input").val();
  str = str.replace(/[+-]?(?:(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|Infinity)/g, "($&)")
           .replace(/%/g, ".percent()")
           .replace(/!/g, ".fact()")
           .replace(/\^/g, ".pow");
  $("#output").text(str);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="input" value="(2)^2!" /><button id="go">Go</button>
<div id="output" />

Upvotes: 5

Related Questions