11import { replaceValueToReplacer } from './replace.ts'
2+ import { binarySearch } from '@std/collections/unstable-binary-search'
23
34/**
45 * A contract fulfilled by both `RegExp`s and `Irregex`s. The `Irregex` class implements these properties and methods of
@@ -61,19 +62,21 @@ export abstract class Irregex<T = unknown> implements IrregexCompatible {
6162 * A list of matchers that should have their `lastIndex` property kept in sync with the parent `Irregex`'s
6263 * `lastIndex` property. Useful for keeping internally-used regexes (or other matchers) in sync.
6364 */
64- trackLastIndex ?: Pick < Matcher , ' lastIndex' > [ ]
65+ protected trackLastIndex ?: { lastIndex : number } [ ]
6566
6667 exec ( str : string ) : ( RegExpExecArray & T ) | null {
6768 const match = this . getMatch ( str )
6869 this . lastIndex = match ? match . index + match [ 0 ] . length : 0
6970 return match
7071 }
7172
72- #lastCached?: {
73- input : string
73+ static #MAX_CACHE_SIZE = 10
74+ #lastCached = new Map < string , {
7475 iterated : ( RegExpExecArray & T ) [ ]
7576 iterator : Iterator < RegExpExecArray & T >
76- }
77+ cursor : number
78+ indices : number [ ]
79+ } > ( )
7780
7881 /**
7982 * Convenience method for converting an iterator function to a match getter suitable for use with `getMatch`.
@@ -87,24 +90,61 @@ export abstract class Irregex<T = unknown> implements IrregexCompatible {
8790 str : string ,
8891 getter : ( this : this) => Iterable < RegExpExecArray & T > ,
8992 ) : ( RegExpExecArray & T ) | null {
90- if ( this . #lastCached?. input !== str ) {
93+ let lastCached = this . #lastCached. get ( str )
94+
95+ if ( lastCached == null ) {
9196 const iterator = getter . call ( this ) [ Symbol . iterator ] ( )
9297
93- this . #lastCached = {
94- input : str ,
95- iterated : [ ] ,
96- iterator,
98+ this . #lastCached. set (
99+ str ,
100+ lastCached = {
101+ iterated : [ ] ,
102+ iterator,
103+ cursor : 0 ,
104+ indices : [ ] ,
105+ } ,
106+ )
107+
108+ if ( this . #lastCached. size > Irregex . #MAX_CACHE_SIZE) {
109+ this . #lastCached. delete ( this . #lastCached. keys ( ) . next ( ) . value ! )
97110 }
98111 }
99112
100- for ( const x of this . #lastCached. iterated ) {
101- if ( x . index >= this . lastIndex ) return x
113+ if ( this . lastIndex === 0 ) lastCached . cursor = 0
114+
115+ // if we've already iterated past the current lastIndex, the result is already in the `iterated` array
116+ if ( ( lastCached . iterated . at ( - 1 ) ?. index ?? - 1 ) >= this . lastIndex ) {
117+ // check after the cursor first (assuming usually iterated sequentially)
118+ for ( let i = lastCached . cursor ; i < lastCached . iterated . length ; ++ i ) {
119+ const x = lastCached . iterated [ i ] !
120+ const prev = lastCached . iterated [ i - 1 ]
121+ if ( x . index >= this . lastIndex && ( prev ?. index ?? - 1 ) < this . lastIndex ) {
122+ lastCached . cursor = i
123+ return x
124+ }
125+ }
126+
127+ const { cursor } = lastCached
128+ lastCached . cursor = 0
129+
130+ // const indices = lastCached.iterated.map(x=>x.index).slice(0, cursor)
131+
132+ // throw '!'
133+
134+ const i = binarySearch (
135+ lastCached . indices . slice ( 0 , cursor ) ,
136+ this . lastIndex ,
137+ )
138+ return lastCached . iterated [ i < 0 ? ~ i : i ] !
102139 }
140+
103141 while ( true ) {
104- const next = this . # lastCached. iterator . next ( )
142+ const next = lastCached . iterator . next ( )
105143 if ( next . done ) return null
106144 const x = next . value
107- this . #lastCached. iterated . push ( x )
145+
146+ lastCached . cursor = lastCached . iterated . push ( x ) - 1
147+ lastCached . indices . push ( x . index )
108148 if ( x . index >= this . lastIndex ) return x
109149 }
110150 }
0 commit comments