@@ -34,7 +34,13 @@ class Lexer {
3434 * If the stack is empty, we are not in a function call. Remember that
3535 * function arguments can use arbitrarily nested in parentheses.
3636 */
37- public parenStack : number [ ] = [ ] ;
37+ public funcCallStack : number [ ] = [ ] ;
38+
39+ /**
40+ * A stack of parentheses and square brackets used to check for balanced
41+ * brackets.
42+ */
43+ public bracketStack : Array < [ string , number ] > = [ ] ;
3844
3945 /** Tokens resulting from tokenizing a JSONPath query. */
4046 public tokens : Token [ ] = [ ] ;
@@ -205,12 +211,26 @@ export function tokenize(
205211) : Token [ ] {
206212 const [ lexer , tokens ] = lex ( environment , path ) ;
207213 lexer . run ( ) ;
214+
215+ // If there's an error, it will be the last token with kind set to ERROR.
208216 if ( tokens . length && tokens [ tokens . length - 1 ] . kind === TokenKind . ERROR ) {
209217 throw new JSONPathSyntaxError (
210218 tokens [ tokens . length - 1 ] . value ,
211219 tokens [ tokens . length - 1 ] ,
212220 ) ;
213221 }
222+
223+ // If the bracket stack is not empty, we hav unbalanced brackets.
224+ // This might not be reachable.
225+ if ( lexer . bracketStack . length !== 0 ) {
226+ const [ ch , index ] = lexer . bracketStack [ lexer . bracketStack . length - 1 ] ;
227+ const msg = "unbalanced brackets" ;
228+ throw new JSONPathSyntaxError (
229+ msg ,
230+ new Token ( TokenKind . ERROR , ch , index , path ) ,
231+ ) ;
232+ }
233+
214234 return tokens ;
215235}
216236
@@ -242,6 +262,7 @@ function lexSegment(l: Lexer): StateFn | null {
242262 }
243263 return lexDotSelector ;
244264 case "[" :
265+ l . bracketStack . push ( [ "[" , l . start ] ) ;
245266 l . emit ( TokenKind . LBRACKET ) ;
246267 return lexInsideBracketedSelection ;
247268 default :
@@ -300,6 +321,7 @@ function lexDescendantSelection(l: Lexer): StateFn | null {
300321 l . emit ( TokenKind . WILD ) ;
301322 return lexSegment ;
302323 case "[" :
324+ l . bracketStack . push ( [ "[" , l . start ] ) ;
303325 l . emit ( TokenKind . LBRACKET ) ;
304326 return lexInsideBracketedSelection ;
305327 default :
@@ -391,6 +413,16 @@ function lexInsideBracketedSelection(l: Lexer): StateFn | null {
391413 const ch = l . next ( ) ;
392414 switch ( ch ) {
393415 case "]" :
416+ if (
417+ l . bracketStack . length === 0 ||
418+ l . bracketStack [ l . bracketStack . length - 1 ] [ 0 ] !== "["
419+ ) {
420+ l . backup ( ) ;
421+ l . error ( "unbalanced brackets" ) ;
422+ return null ;
423+ }
424+
425+ l . bracketStack . pop ( ) ;
394426 l . emit ( TokenKind . RBRACKET ) ;
395427 return lexSegment ;
396428 case "" :
@@ -432,36 +464,44 @@ function lexInsideFilter(l: Lexer): StateFn | null {
432464 return null ;
433465 case "]" :
434466 l . filterLevel -= 1 ;
435- if ( l . parenStack . length === 1 ) {
436- l . error ( "unbalanced parentheses" ) ;
437- return null ;
438- }
439467 l . backup ( ) ;
440468 return lexInsideBracketedSelection ;
441469 case "," :
442470 l . emit ( TokenKind . COMMA ) ;
443471 // If we have unbalanced parens, we are inside a function call and a
444472 // comma separates arguments. Otherwise a comma separates selectors.
445- if ( l . parenStack . length ) continue ;
473+ if ( l . funcCallStack . length ) continue ;
446474 l . filterLevel -= 1 ;
447475 return lexInsideBracketedSelection ;
448476 case "'" :
449477 return lexSingleQuoteStringInsideFilterExpression ;
450478 case '"' :
451479 return lexDoubleQuoteStringInsideFilterExpression ;
452480 case "(" :
481+ l . bracketStack . push ( [ "(" , l . start ] ) ;
453482 l . emit ( TokenKind . LPAREN ) ;
454483 // Are we in a function call? If so, a function argument contains parens.
455- if ( l . parenStack . length ) l . parenStack [ l . parenStack . length - 1 ] += 1 ;
484+ if ( l . funcCallStack . length )
485+ l . funcCallStack [ l . funcCallStack . length - 1 ] += 1 ;
456486 continue ;
457487 case ")" :
488+ if (
489+ l . bracketStack . length === 0 ||
490+ l . bracketStack [ l . bracketStack . length - 1 ] [ 0 ] !== "("
491+ ) {
492+ l . backup ( ) ;
493+ l . error ( "unbalanced brackets" ) ;
494+ return null ;
495+ }
496+
497+ l . bracketStack . pop ( ) ;
458498 l . emit ( TokenKind . RPAREN ) ;
459499 // Are we closing a function call or a parenthesized expression?
460- if ( l . parenStack . length ) {
461- if ( l . parenStack [ l . parenStack . length - 1 ] === 1 ) {
462- l . parenStack . pop ( ) ;
500+ if ( l . funcCallStack . length ) {
501+ if ( l . funcCallStack [ l . funcCallStack . length - 1 ] === 1 ) {
502+ l . funcCallStack . pop ( ) ;
463503 } else {
464- l . parenStack [ l . parenStack . length - 1 ] -= 1 ;
504+ l . funcCallStack [ l . funcCallStack . length - 1 ] -= 1 ;
465505 }
466506 }
467507 continue ;
@@ -562,8 +602,9 @@ function lexInsideFilter(l: Lexer): StateFn | null {
562602 // functions
563603 if ( l . acceptMatchRun ( functionNamePattern ) && l . peek ( ) === "(" ) {
564604 // Keep track of parentheses for this function call.
565- l . parenStack . push ( 1 ) ;
605+ l . funcCallStack . push ( 1 ) ;
566606 l . emit ( TokenKind . FUNCTION ) ;
607+ l . bracketStack . push ( [ "(" , l . start ] ) ;
567608 l . next ( ) ;
568609 l . ignore ( ) ;
569610 continue ;
0 commit comments