I am writing a language parser in javascript and went with the functional approach of constructing nested functions to increase scalability.
My code currently looks like the following
const parseDate = (query) => {
return query.match(/stuff/)
}
const parseAuthor = (query) => {
return query.match(/stuff/)
}
const parseTags = (query) => {
return query.match(/stuff/)
}
//...
const transform = (match) => {
const target = match?.[0]?.trim()
// some more post processing that modifies "target"
return target;
}
const parsers = [parseAuthor, parseTags, parseReviewer, parseSearch, parseAfter].map(parser => {
return (query) => transform(parser(query))
})
I am trying to creplace function parseDate
in the array with another function that will take the output of parseDate
and turn it into ISO format.
parsers[4] = (query) => {
return new Date(parsers[4](query)).toISOString();
};
This causes a RangeError: Maximum call stack size exceeded
which I assume is coming from the fact that javascript is constructing a recursive function that takes the output of the old parseDate function and runs it again through the new function, and that output to the same function ... and so on.
That is not what I want. I just want to replace the parsers[4] function with a new one.
I tried duplicating the function but had no luck and getting the same error
parsers[4] = (query) => {
return new Date(parsers[4].bind({})(query)).toISOString();
};
How do we exactly do this?
CodePudding user response:
Since you are adding a function to the list (which is not automatically evaluated while being assigned), your reference to object at the 4th index inside parsers
will point to the current state of parsers when executing, which makes it a self reference (leading to an infinite recursion loop causing the stack size to explode).
You could simply use parseDate
itself if you have a reference to it or store the current object in parsers[4]
in a temporary variable before using it:
var temp = parsers[4]
parsers[4] = (query) => {
return new Date(temp(query)).toISOString();
};
CodePudding user response:
The problem with either form of the definition you're trying to provide is that you're defining a function - parsers[4]
- that unconditionally calls itself. Try reading it through and pretending you're the Javascript engine - in order to compute parsers[4](query)
, you're going to need to first compute parsers[4](query)
and so on, infinitely. (The use of .bind
doesn't fix that all - yes you're making a new reference to the same function object, but in order to execute that function it still needs to reference the same function you're defining.)
As for how to solve it - there are quite a few ways I can think of. Probably the most simple-minded - but perfectly good enough if you're just doing this once - is to make a temporary copy:
const oldParser = parsers[4];
parsers[4] = (query) => {
return new Date(oldParser(query)).toISOString();
};
You could also write this as a "decorator" or function transformation:
const transformFunction = (func) => (query) => {
return new Date(func(query)).toISOString();
};
parsers[4] = transformFunction(parsers[4]);
Note that the above doesn't lead to infinite recursion - or indeed any recursion at all - even though it may look similar to the original. That's because the original was referring to parsers[4]
while executing, while this only refers to it once, when defining the new function. It will simply, under the covers, store a reference to the old function (which I've labelled func
), much as the first approach did.
One advantage of this is that, if you needed to transform the whole array in the same way, you could then do it as simply as parsers = parsers.map(transformFunction)
.