Reputation: 1989
I am trying to write a JavaScript replaceAll()
with a RegEx that will replace every value of x
with period*x
, as long as it is within Math.sin()
or Math.cos()
or Math.tan()
I tried this:
let fx = 'Math.tan(Math.sin(Math.cos(x)*x)*x)';
const periodRegEx = /(Math.(sin|cos|tan)\(.*?)(x)([^\)].*?)(\))/gi;
// capture group with 'Math.sin(' or 'Math.cos(' or 'Math.tan('
// capture group with 'x'
// capture group with any character except for ')'
// capture group with ')'
let newFx = fx.replaceAll(periodRegEx,('period*'+\2));
But that is getting me an `illegal escape sequence error. This:
let newFx = fx.replaceAll(periodRegEx,('period*'+'\2'));
Is giving me nothing, and this:
let newFx = fx.replaceAll(periodRegEx,('period*'+$2));
Is giving me a $2 not defined
error.
What I am looking for is this as the output of the replaceAll()
:
'Math.tan(Math.sin(Math.cos(period*x)*period*x)*period*x)'
Upvotes: 2
Views: 99
Reputation: 3353
Doing this with a single regex expression is going to be difficult, but you can accomplish it with a recursive replace function that will handle the matches one at a time, applying placeholders, then loop back through the array of matches in reverse.
let fx = 'Math.tan(Math.sin(Math.cos(x)*x)*x)';
let matches = []
function replaceX(str){
const rgxmatch = /Math\.(?:sin|cos|tan)\([^)(]*x[^)(]*\)/.exec(str),
match = rgxmatch ? rgxmatch[0] : null
if(match){
matches.push(match.replace(/\bx\b/g, 'period*x'))
return replaceX(str.replace(match, `_PLACEHOLDER_${matches.length}`))
}
else {
return fillInMatches(str)
}
}
function fillInMatches(str){
let len = matches.length
while(len > 0){
str = str.replace(`_PLACEHOLDER_${len}`, matches[len-1])
--len
}
return str
}
const newStr = replaceX(fx)
console.log(newStr)
Upvotes: 0
Reputation: 91
It is most likely unfeasible to achieve what you need with RegEx-only oneliner, having in mind that:
A simple solution in plain JS without additional libraries would be to extract parts of expression that start with a trig function call, perform replacement only on them and concatenate back with the remainder of the formula.
Note that due to (3) also using /\((.*?x[^\)]*?)\)/
to extract function argument will not always be sufficient.
One example of such solution would be:
let fx = 'Math.tan(Math.sin(Math.cos((x-1)*x)*x)*x)*(x+1)+Math.sin(x)';
let newFx = periodSubstitute(fx,'x','period*x');
console.log(newFx)
function periodSubstitute(formula, variable, replacement) {
const trigFunctionRegex = /Math.(sin|cos|tan)\(/;
let trigFunction = trigFunctionRegex.exec(formula);
if (trigFunction === null)
return formula;
else {
let start = formula.indexOf(trigFunction[0]) + trigFunction[0].length;
let end = closingParenthesis(formula, start);
let substitute = formula.substring(start, end).replaceAll(variable, replacement);
return formula.substring(0, start) + substitute + periodSubstitute(formula.substring(end), variable, replacement);
}
}
function closingParenthesis(formula, from) {
let open = 0;
for (let index = from; index < formula.length; index++) {
const char = formula[index];
if (char == '(')
open++;
if (char == ')') {
if (open == 0)
return index;
else
open--;
}
}
return 0;
}
Upvotes: 2
Reputation: 23761
If your code is JS (seems) you can parse and replace it with acorn and generate back JS code with astring:
let fx = 'Math.tan(Math.sin(Math.cos(x)*x)*x)';
const parsed = acorn.Parser.parse(fx, {ecmaVersion: 'es6'});
const replace = (node, inside = false) => {
if(node.callee?.object?.name === 'Math' && ['tan', 'cos', 'sin'].includes(node.callee?.property?.name)){
replace(node.arguments, true);
} else if(node.name === 'x' && inside){
node.name = 'period * x';
} else if(Array.isArray(node)){
node.forEach(node => replace(node, inside));
} else if(typeof node === 'object' && node){
for(const k in node){
replace(node[k], inside);
}
}
}
replace(parsed);
const code = astring.generate(parsed);
console.log(code)
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/8.11.3/acorn.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/astring.min.js"></script>
Upvotes: 0