1

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?

5
  • This isn't related to your actual question, but as an aside - your code won't work anyway, because your parseXXX functions are missing a return. Commented Mar 27, 2022 at 16:12
  • @RobinZigmond That part of the code is fine. Look at what the map function is doing. Commented Mar 27, 2022 at 16:18
  • @SannatBhasin that part is not fine. The map returns functions that take query to transform(parser(query)), but if the parsers are defined as the OP has them, then this will always be transform(undefined). Commented Mar 27, 2022 at 16:23
  • @RobinZigmond My bad. I thought the map was substituting each element with an arrow function that had an output. Commented Mar 27, 2022 at 16:44
  • 1
    @RobinZigmond yeah, I just forgot to add the return statement in the stackoverflow answer, but it is in the code base. I'll update my question now. Commented Mar 27, 2022 at 16:59

2 Answers 2

3

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();
};
Sign up to request clarification or add additional context in comments.

Comments

1

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).

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.