-1

I want to parse some data that's in a string format. Anything enclosed in parenthesis in the string to parse should be replaced with itself run through a function. This is what I want:

function foo(str) {
    return parseInt(str) + 1; // Example function, not actually what the function will be
}

function parse(str) {
    // everything in str that is enclosed in parenthesis should be replaced with itself ran through foo();

    // Example
    // Input: "My name is foo and I am (0) year old."
    // Output: "My name is foo and I am 1 year old."
    // "(0)" has been replaced with the result of foo("0")
}

I have thought up a couple bad workarounds, but I want something more robust. For example:

function parse(str) {
    // Input: "My name is foo and I am (0) year old."
    str = str.replaceAll("(", "${foo('");
    str = str.replaceAll(")", "')}");
    str = "`" + str + "`"
    // Here str will be "`My name is foo and I am ${foo(0)} year old.`"
    // And I can use eval() or something to treat it like I've typed that
}

This, however, is kind of a bad way of doing it. EDIT: I tested it, it works, but it is quite vulnerable.

I can't think of anything else and I'm not very good with RegEx. (although I'd accept a solution using it)

  • Do you want to extract the name "foo" from the string and use that as the name of the function to call? – Andrew Morton Jun 01 '23 at 17:28
  • Is the matched content in the string will always be a string or could be anything else? – Pøziel Jun 01 '23 at 17:28
  • @AndrewMorton: no--the function will always be the same. maybe i should have used a different name. – Sidney Welsh Jun 01 '23 at 17:30
  • @Pøziel: the content passed to foo() here is the string "0", foo() pareses it into a number. Let's say we want to pass the function a string and if it needs a different data type it can interpret that – Sidney Welsh Jun 01 '23 at 17:32
  • In that case, it sounds like you just needed to know the name of the way to do it to be able to find documentation, or an example such as [Replacing string match with a callback function](https://riptutorial.com/javascript/example/8421/replacing-string-match-with-a-callback-function). – Andrew Morton Jun 01 '23 at 17:34
  • This sort of thing? [Playground Link](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAlmADgV1gXhgIgLIE8ZgCGAtgKbwQwBmIIMhYAJjAJL3EwAUADAJQy5ShAE4wQAG0YA6LgA55vTAFgAUKqrIwwKHHDUGwXACVSicYWClOCFFABcMaMIQBzADQxhp85YBim4AdOADcHJ1d+NAA+RyhnMBd+AG9VGDTPUihkYTB4JFQpLzMLKwB6AB1OTikAKgB+XnLeUvcuAH03YMiYop9Sfy0Q3l5VAF9VdQCdPRoQTggwuIiYFJV0jKycrkQRCFIWMCh5-gBqGABGXikoEABlJYTOflLSmAAjUmBCZD2M4r8AjBiD9YF5NrlwgkxhM1CpQJAJKQpOIQC5OFQDMZvCVrPkoB5ZsMgA) – spender Jun 01 '23 at 17:43
  • `const replacer = (_, v) => +v + 1; const result = str.replaceAll(/\((\d+)\)/g, replacer);` – epascarello Jun 01 '23 at 18:09

3 Answers3

1

You could used a full matching pattern, and wrap the resulting string in back-ticks so it's evaluated as a string literal.

const foo = (value) => +value + 1;

const parse = (str) =>
  '`' +
  str.replace(/\((\w+)\)/g, (_, v) => `\${foo(${v})}`) +
  '`';

// Source: https://stackoverflow.com/a/29182787/1762224
console.log(eval(parse('My name is foo and I am (0) year old.')));

The only problem with this is that the pattern to match between the parenthesis seems to be any kind of value. This could pose problems down the road. Just tweak the regular expression as-needed.


If you don't want to use eval, pass a callback:

const foo = (value) => +value + 1;

const parse = (str, callback) =>
  str.replace(/\((\w+)\)/g, (_, v) => callback(v));

// Source: https://stackoverflow.com/a/29182787/1762224
console.log(parse('My name is foo and I am (0) year old.', foo));
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • I mean, this works, but it's basically a tidier way of writing what I already did. I was wondering if there was a simpler answer without eval(). Thanks! – Sidney Welsh Jun 01 '23 at 17:39
  • Also, your code doesn’t work for my specific instance. I need to pass a string to the function foo() (which actually operates on a string), and for some reason this code doesn't like it when I use non-numeric characters in the input. – Sidney Welsh Jun 01 '23 at 17:49
  • @SidneyWelsh that's because your `foo` function only returns an integer value. You need to update your question to include various scenarios. This code is not a one-size fits all. – Mr. Polywhirl Jun 01 '23 at 18:00
0

Here's what I would do. I would match the string with a RegEx that would match anything inside parenthesis in the string. With that, I would then use str.replaceAll() to replace the matched string with the result of the foo() function.

const regex = /\((\d*)\)/gm;

function foo(str) {
    return parseInt(str) + 1;
}

function parse(str) {
  
  // Loop all match the regex find in the string
  let m;
  while ((m = regex.exec(str)) !== null) {
    
    // This is necessary to avoid infinite loops with zero-width matches
    if (m.index === regex.lastIndex) {
        regex.lastIndex++;
    }
    
    // Replace all instance of the match with the operation of the match
    str = str.replaceAll(m[0], foo(m[1]))
  }
  return str;
}

let p = parse('My name is foo and I am (0) year old and I want (54) apples');

// The result will be: My name is foo and I am 1 year old and I want 55 apples

With that, you won't need to use eval() as it potentially pose a risk for your application.

I hope that would work for you. If I missed anything, tell me, I will edit my answer.

Pøziel
  • 199
  • 10
  • It worked. I needed to modify the regex slightly to work with strings (I replaced \d with [^)]) but other than that it was perfect. Thanks! – Sidney Welsh Jun 01 '23 at 18:03
  • Is it because you want the result in paranthesis to not only match number? If so, you are probably best with this RegEx: `\((.+?)\)`. It is more clean as it will match anything in paranthesis as lazy as possible. – Pøziel Jun 01 '23 at 18:10
  • The problem with that (I tried) is that it matches the closing parenthesis as well, causing problems. With `/\{([^}]*)\}/gm` it works better. – Sidney Welsh Jun 01 '23 at 18:13
0

See if it works for you:

function foo(str) {
  return parseInt(str) + 1;
}

function parse(str) {
  return str.replace(/\((\d+)\)/g, (_, num) => foo(num));
}

This Regex pattern matches every number in a string that is inside a parenthesis, put them in the 1st capturing grouping and pass all of them in foo().