0

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?

1
  • While 2% matches, a standalone % doesn't match. Even if it did match % alone, the replacement would be "2%%" -> "(2).percent()%.percent()" Either qualify the % in the alternation |(?=\%) or take it out of the alternation block and make the entire block optional. Same goes for the other regex. Commented Oct 21, 2014 at 19:29

1 Answer 1

5

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" />

Sign up to request clarification or add additional context in comments.

14 Comments

Sorry, I have edited my question, since your solution might interract with my real code. I actually have both a factorial and percent function, so I need my code working for example: 3!!%!%
Awesome! And aswell, edited it again sry xD I also have a pow ^ function
@Murplyx Well it seems like this is getting more and more complex. What would happen if you have 5!^3%? is that (5).fact().pow(3).percent() or (5).fact().pow((3).percent())? Should 1^2^3 become (1).pow(2).pow(3) or (1).pow((2).pow(3))? This is stretching the limit of the usefulness of regular expressions. I'd recommend you take some time to consider if you what you really need is a proper parser.
No, it should never either !, % or ^ add additional parentheses, it should only stack them 1^2^3 -> (1).pow(2).pow(3), 5!^3% -> (5).fact().pow(3).percent()
2^-3 -> (2)^-(3), should be "(2).pow(-3)"
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.