0

Given an input string, I need to find and replace this:

funccall(x, y, z, w)

With this:

call(x, y, 0, z, w)

Everything between the commas, as well between a comma and a bracket, is unknown (i.e., may be different on each input). This includes the values of x, y, z and w, as well as the amount of spaces in between.

But the funccall part is constant (i.e., that's the actual substring that I should be looking for), and the fact that I need to insert 0, after the 2nd comma is also constant.

Here is my method:

function fix(str) {
    const index0 = str.indexOf('funccall');
    if (index0 >= 0) {
        const index1 = str.indexOf(',', str.indexOf(',', index0) + 1);
        const part0 = str.substring(0, index0);
        const part1 = str.substring(index0 + 'func'.length, index1) + ', 0';
        const part2 = str.substring(index1);
        return part0 + part1 + part2;
    }
    return str;
}

I was really hoping for a one-liner solution (or at least something close to a one-liner).

halfer
  • 19,824
  • 17
  • 99
  • 186
goodvibration
  • 5,980
  • 4
  • 28
  • 61
  • 2
    “*I was really hoping for a one-liner solution*” - do remember that readability is a feature, not a bug (as clever as one-line solutions look, they often need to be commented with explanations so you, or your, colleagues can later understand what it’s doing, and how) :) – David Thomas Jun 27 '19 at 09:47

2 Answers2

2

If you can count on the input string not containing commas outside of the argument separators, you can use a regular expression instead - match the x, y, part, and the z, w part, and put a 0, in the middle:

const fix = str => str.replace(
  /(funccall\((?:[^,]+,){2})([^,]+,[^,]+\))/g,
  (_, g1, g2) => `${g1} 0,${g2}`
);

console.log(fix('funccall(x, y, z, w)'));

The pattern is composed of 2 groups:

  • (funccall\((?:[^,]+,){2}):
    • funccall\( - Match funccall followed by (
    • (?:[^,]+,){2}) - Match (anything but a comma, followed by a comma) twice
  • ([^,]+,[^,]+\)):
    • ([^,]+, - Match anything but a comma, followed by a comma
    • [^,]+ - Followed by anything but a comma (final argument has no trailing comma)
    • \) - Followed by )
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 3
    You cannot use regex to do this reliably. For instance, `funccall(x, y * foo(1, 2), z, w)` breaks it. – T.J. Crowder Jun 27 '19 at 09:47
  • Please do not delete your answer, I will likely use it (after testing it of course). My overall expression is not expected to be that complicated. It's essentially a string which represents a function call with 4 input arguments, and a known function name. – goodvibration Jun 27 '19 at 09:52
  • 1
    @goodvibration - I used to say that about doing the same thing to modify HTML. Every single time I thought "Oh, my example is simple enough there won't be a problem"...there was a problem. :-) Now I just [remind myself not to do that](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags#1732454). – T.J. Crowder Jun 27 '19 at 09:55
1

Everything between the commas, as well between a comma and a bracket, is unknown (i.e., may be different on each input).

and

I was really hoping for a one-liner solution (or at least something close to a one-liner).

There isn't one, JavaScript (I'm assuming the input string is JavaScript) is too complex for that. You're going to get regular expression "one-liner" answers. They will be wrong. You need to balance parens, braces, square brackets, single quotes, double quotes, and backticks (at least); and you need to handle comments (including multi-line comments); etc., etc., etc.

You need a JavaScript parser for this. There are several available that can be used with Node.js, such as @babel/parser (the parser used by Babel) and Esprima.

In the tree the parser provides, you'd look for a function call with the name funccall. The node in the tree (in a good parser) will tell you the starting and ending locations within the string of that call's source text, and of course have nested nodes for all of the individual parts of this.

It will not be as complex as you may think.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • @RokoC.Buljan - **LOL** – T.J. Crowder Jun 27 '19 at 09:49
  • 1
    Even my own solution will not necessarily work (I now understand) if, for example, `x` is replace with `someFunc(a, b)`. – goodvibration Jun 27 '19 at 09:52
  • @goodvibration - Exactly. And of course, even that's relatively simple. Consider `funccall(x, y * array.reduce((acc, x) => { /*...complex function logic...*/ return acc;}, 0), z, w)` :-) – T.J. Crowder Jun 27 '19 at 09:53
  • 1
    "In the tree the parser provides, you'd look for a function call with the name `funccall`". OK, this is an awesome idea. I just found the place in the code (a node module which I fetch via `npm install`) where that tree is generated. I will look further into this. I believe that it will allow me to insert my `0` parameter right before this tree is converted into a code string. Thank you for that!!! – goodvibration Jun 27 '19 at 10:07