mathjs: Indicating expression location for error

When evaluating with multiple expressions and one of them has an error:

math.evaluate(["1+1","2+"])

The error is:

SyntaxError: Unexpected end of expression (char 3)

I would like to know which is the expression with the error: SyntaxError: Unexpected end of expression (expr 2, char 3)

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 15 (6 by maintainers)

Most upvoted comments

Is it possible to return both the correct results and the errors on the return array similar to this #2318 (comment)?

Well, that is an interesting question. The code internally is throwing errors. So the usual behavior would be for those errors to propagate outside of mathjs – I mean, that’s what “throwing an error” is all about. So while it’s absolutely possible and desirable to improve the data in an error, imagine the client that can’t afford for there to be any errors and absolutely wants to catch the first one before executing any further? So once that first exception is thrown and not caught within the mathjs package, there is no opportunity to continue evaluating the remaining expressions.

Hence, I think that all mathjs could do is offer an ‘evaluateAll’ convenience method that catches errors for you around every expression – basically recreate the loop that Jos shows in https://github.com/josdejong/mathjs/issues/2318#issuecomment-925861448 but in a standardized mathjs method – or possibly (?) add an options argument to evaluate() that would take say ‘{catch: true}’ to tell evaluate to wrap each evaluation in a try and if it catches one, return the error object instead of the result. I think @josdejong would have to weigh in on any of these possibilities.

Thanks so much for your interest in this issue, @zishiwu123. The point in parse.js that you have identified has to do with looping over the clauses in the object syntax {x: 1, y: 2} rather than the different expressions in a call of evaluate on an array of expressions.

The loop you are looking for – actually there are two instances – are the deepmaps in src/expression/function/evaluate.js, near the end where it deals with Array or Matrix arguments. There is where you’d have to capture the index information and create a way that an eventual error could access it.

Finally, my personal suggestion is that the expression index annotation should only appear when the error occurs in one of these array or matrix calls, so that the errors from single-expression calls are not cluttered with index information that doesn’t add anything – when you evaluate a single expression, there really is no “index”.

Yes, I think the consensus here is to extend the “state” of parsing to allow to record which expression was being evaluated in an array call to evaluate (but not burden the message in an ordinary single-expression call). A PR to that end would, I believe be welcome.

I think it would be useful to indicate in which expression the error was found like described by @zishiwu123.

Maybe if the expression has multiple lines the error could indicate the line and character. This might not be as important because it could also be extracted and converted by the user.

It seems reasonable to me not to implement evaluateAll but just improve the exception thrown to know where the error was found.

When evaluating with multiple expressions and one of them has an error:

math.evaluate(["1+1","2+"])

The error is:

SyntaxError: Unexpected end of expression (char 3)

I would like to know which is the expression with the error: SyntaxError: Unexpected end of expression (expr 2, char 3)

Research The error message is generated by: https://github.com/josdejong/mathjs/blob/4f83b786ea420b4095073dc97f3321d1dd768147/src/expression/parse.js#L1769-L1775

Here are the contents of the state object passed into createSyntaxError() for the first example @dvd101x posted:

{
  extraNodes: {},
  expression: '2+',
  comment: '',
  index: 2,
  token: '',
  tokenType: 1,
  nestingLevel: 0,
  conditionalLevel: null
}

Proposed Solution The state object tracks the current evaluated expression "2+", which is where the error occurs. But it doesn’t track the index of the current expression (let’s call it expressionIndex) being evaluated from the array of expressions ["1+1","2+"] that was passed into math.evaluate. If we want to know expressionIndex, I think we need to add it as an additional property to the state object and increment it every time this line in parseObject() is executed: https://github.com/josdejong/mathjs/blob/4f83b786ea420b4095073dc97f3321d1dd768147/src/expression/parse.js#L1660

The existing index property in the state object tracks the index of the current character in the current expression. This is different from the proposed expressionIndex property.

Questions Besides occurring in parseObject(), the check for condition state.token === ',' occurs 5 other times, all in parse.js. I am not very familiar with the code base but I would like to help out with this issue. So far my questions are:

  1. Does the method I proposed for parseObject() need to be applied [in the same way / in a different way /not at all] in the other 5 occurrences of the condition state.token === ','?
  2. Do we want to enforce a consistent style of error messages? If so, should it be one of the formats proposed by @dvd101x?

Expression 1 - SyntaxError: Unexpected end of expression (char 5)

SyntaxError: Unexpected end of expression (char 5) (expr 1)

Expression[1] SyntaxError: Unexpected end of expression (char 5)

Yes indeed, thanks for sharing a clear example David👍

You can evaluate the expressions one by one:

const expressions = ["1+1","2+"]

expressions.forEach((expression, index) => {
  try {
    const res = math.evaluate(expression)
  } catch (err) {
    // do something here... 
    console.error(index, err)
  }  
})