4949 * - ^=: stringified value starts with
5050 * - $=: stringified value ends with
5151 * - *=: stringified value includes
52+ * - =/.../: regular expression matches
5253 *
5354 * - Examples (from "JSONPath examples" at reference link)
5455 * - .store.book[*].author
@@ -376,7 +377,7 @@ export class JSONPath {
376377 continue ;
377378 }
378379 if ( c0 === 0x27 /* ' */ ) {
379- const r = this . #consumeQuotedIdentifier ( query , i + 1 ) ;
380+ const r = this . #untilChar ( query , 0x27 /* ' */ , i + 1 )
380381 if ( r === undefined ) { return ; }
381382 keys . push ( r . s ) ;
382383 i = r . i ;
@@ -397,22 +398,27 @@ export class JSONPath {
397398 }
398399 return { s : keys . length === 1 ? keys [ 0 ] : keys , i } ;
399400 }
400- #consumeQuotedIdentifier( query , i ) {
401+ #consumeUnquotedIdentifier( query , i ) {
402+ const match = this . #reUnquotedIdentifier. exec ( query . slice ( i ) ) ;
403+ if ( match === null ) { return ; }
404+ return match [ 0 ] ;
405+ }
406+ #untilChar( query , targetCharCode , i ) {
401407 const len = query . length ;
402408 const parts = [ ] ;
403409 let beg = i , end = i ;
404410 for ( ; ; ) {
405411 if ( end === len ) { return ; }
406412 const c = query . charCodeAt ( end ) ;
407- if ( c === 0x27 /* ' */ ) {
413+ if ( c === targetCharCode ) {
408414 parts . push ( query . slice ( beg , end ) ) ;
409415 end += 1 ;
410416 break ;
411417 }
412418 if ( c === 0x5C /* \ */ && ( end + 1 ) < len ) {
413419 parts . push ( query . slice ( beg , end ) ) ;
414- const d = query . chatCodeAt ( end + 1 ) ;
415- if ( d === 0x27 || d === 0x5C ) {
420+ const d = query . charCodeAt ( end + 1 ) ;
421+ if ( d === targetCharCode || d === 0x5C ) {
416422 end += 1 ;
417423 beg = end ;
418424 }
@@ -421,12 +427,20 @@ export class JSONPath {
421427 }
422428 return { s : parts . join ( '' ) , i : end } ;
423429 }
424- #consumeUnquotedIdentifier( query , i ) {
425- const match = this . #reUnquotedIdentifier. exec ( query . slice ( i ) ) ;
426- if ( match === null ) { return ; }
427- return match [ 0 ] ;
428- }
429430 #compileExpr( query , step , i ) {
431+ if ( query . startsWith ( '=/' , i ) ) {
432+ const r = this . #untilChar( query , 0x2F /* / */ , i + 2 ) ;
433+ if ( r === undefined ) { return i ; }
434+ const match = / ^ [ i ] / . exec ( query . slice ( r . i ) ) ;
435+ try {
436+ step . rval = new RegExp ( r . s , match && match [ 0 ] || undefined ) ;
437+ } catch {
438+ return i ;
439+ }
440+ step . op = 're' ;
441+ if ( match ) { r . i += match [ 0 ] . length ; }
442+ return r . i ;
443+ }
430444 const match = this . #reExpr. exec ( query . slice ( i ) ) ;
431445 if ( match === null ) { return i ; }
432446 try {
@@ -466,6 +480,7 @@ export class JSONPath {
466480 case '^=' : outcome = `${ v } ` . startsWith ( step . rval ) === target ; break ;
467481 case '$=' : outcome = `${ v } ` . endsWith ( step . rval ) === target ; break ;
468482 case '*=' : outcome = `${ v } ` . includes ( step . rval ) === target ; break ;
483+ case 're' : outcome = step . rval . test ( `${ v } ` ) ; break ;
469484 default : outcome = hasOwn === target ; break ;
470485 }
471486 if ( outcome ) { return k ; }
0 commit comments