@@ -103,101 +103,107 @@ function gte(i, y) {
103103function expand ( str , max , isTop ) {
104104 var expansions = [ ] ;
105105
106- var m = balanced ( '{' , '}' , str ) ;
107- if ( ! m || / \$ $ / . test ( m . pre ) ) return [ str ] ;
108-
109- var isNumericSequence = / ^ - ? \d + \. \. - ? \d + (?: \. \. - ? \d + ) ? $ / . test ( m . body ) ;
110- var isAlphaSequence = / ^ [ a - z A - Z ] \. \. [ a - z A - Z ] (?: \. \. - ? \d + ) ? $ / . test ( m . body ) ;
111- var isSequence = isNumericSequence || isAlphaSequence ;
112- var isOptions = m . body . indexOf ( ',' ) >= 0 ;
113- if ( ! isSequence && ! isOptions ) {
114- // {a},b }
115- if ( m . post . match ( / , (? ! , ) .* \} / ) ) {
116- str = m . pre + '{' + m . body + escClose + m . post ;
117- return expand ( str , max , true ) ;
106+ // The `{a},b }` rewrite below restarts expansion on a rewritten string with
107+ // the same `max` and `isTop = true`. Loop instead of recursing so a long run
108+ // of non-expanding `{}` groups can't exhaust the call stack.
109+ for ( ; ; ) {
110+ var m = balanced ( '{' , '}' , str ) ;
111+ if ( ! m || / \$ $ / . test ( m . pre ) ) return [ str ] ;
112+
113+ var isNumericSequence = / ^ - ? \d + \. \. - ? \d + (?: \. \. - ? \d + ) ? $ / . test ( m . body ) ;
114+ var isAlphaSequence = / ^ [ a - z A - Z ] \. \. [ a - z A - Z ] (?: \. \. - ? \d + ) ? $ / . test ( m . body ) ;
115+ var isSequence = isNumericSequence || isAlphaSequence ;
116+ var isOptions = m . body . indexOf ( ',' ) >= 0 ;
117+ if ( ! isSequence && ! isOptions ) {
118+ // {a},b }
119+ if ( m . post . match ( / , (? ! , ) .* \} / ) ) {
120+ str = m . pre + '{' + m . body + escClose + m . post ;
121+ isTop = true
122+ continue
123+ }
124+ return [ str ] ;
118125 }
119- return [ str ] ;
120- }
121126
122- var n ;
123- if ( isSequence ) {
124- n = m . body . split ( / \. \. / ) ;
125- } else {
126- n = parseCommaParts ( m . body ) ;
127- if ( n . length === 1 ) {
128- // x{{a,b}}y ==> x{a}y x{b}y
129- n = expand ( n [ 0 ] , max , false ) . map ( embrace ) ;
127+ var n ;
128+ if ( isSequence ) {
129+ n = m . body . split ( / \. \. / ) ;
130+ } else {
131+ n = parseCommaParts ( m . body ) ;
130132 if ( n . length === 1 ) {
131- var post = m . post . length
132- ? expand ( m . post , max , false )
133- : [ '' ] ;
134- return post . map ( function ( p ) {
135- return m . pre + n [ 0 ] + p ;
136- } ) ;
133+ // x{{a,b}}y ==> x{a}y x{b}y
134+ n = expand ( n [ 0 ] , max , false ) . map ( embrace ) ;
135+ if ( n . length === 1 ) {
136+ var post = m . post . length
137+ ? expand ( m . post , max , false )
138+ : [ '' ] ;
139+ return post . map ( function ( p ) {
140+ return m . pre + n [ 0 ] + p ;
141+ } ) ;
142+ }
137143 }
138144 }
139- }
140-
141- // at this point, n is the parts, and we know it's not a comma set
142- // with a single entry.
143145
144- // no need to expand pre, since it is guaranteed to be free of brace-sets
145- var pre = m . pre ;
146- var post = m . post . length
147- ? expand ( m . post , max , false )
148- : [ '' ] ;
149-
150- var N ;
151-
152- if ( isSequence ) {
153- var x = numeric ( n [ 0 ] ) ;
154- var y = numeric ( n [ 1 ] ) ;
155- var width = Math . max ( n [ 0 ] . length , n [ 1 ] . length )
156- var incr = n . length == 3
157- ? Math . max ( Math . abs ( numeric ( n [ 2 ] ) ) , 1 )
158- : 1 ;
159- var test = lte ;
160- var reverse = y < x ;
161- if ( reverse ) {
162- incr *= - 1 ;
163- test = gte ;
164- }
165- var pad = n . some ( isPadded ) ;
166-
167- N = [ ] ;
168-
169- for ( var i = x ; test ( i , y ) && N . length < max ; i += incr ) {
170- var c ;
171- if ( isAlphaSequence ) {
172- c = String . fromCharCode ( i ) ;
173- if ( c === '\\' )
174- c = '' ;
175- } else {
176- c = String ( i ) ;
177- if ( pad ) {
178- var need = width - c . length ;
179- if ( need > 0 ) {
180- var z = new Array ( need + 1 ) . join ( '0' ) ;
181- if ( i < 0 )
182- c = '-' + z + c . slice ( 1 ) ;
183- else
184- c = z + c ;
146+ // at this point, n is the parts, and we know it's not a comma set
147+ // with a single entry.
148+
149+ // no need to expand pre, since it is guaranteed to be free of brace-sets
150+ var pre = m . pre ;
151+ var post = m . post . length
152+ ? expand ( m . post , max , false )
153+ : [ '' ] ;
154+
155+ var N ;
156+
157+ if ( isSequence ) {
158+ var x = numeric ( n [ 0 ] ) ;
159+ var y = numeric ( n [ 1 ] ) ;
160+ var width = Math . max ( n [ 0 ] . length , n [ 1 ] . length )
161+ var incr = n . length == 3
162+ ? Math . max ( Math . abs ( numeric ( n [ 2 ] ) ) , 1 )
163+ : 1 ;
164+ var test = lte ;
165+ var reverse = y < x ;
166+ if ( reverse ) {
167+ incr *= - 1 ;
168+ test = gte ;
169+ }
170+ var pad = n . some ( isPadded ) ;
171+
172+ N = [ ] ;
173+
174+ for ( var i = x ; test ( i , y ) && N . length < max ; i += incr ) {
175+ var c ;
176+ if ( isAlphaSequence ) {
177+ c = String . fromCharCode ( i ) ;
178+ if ( c === '\\' )
179+ c = '' ;
180+ } else {
181+ c = String ( i ) ;
182+ if ( pad ) {
183+ var need = width - c . length ;
184+ if ( need > 0 ) {
185+ var z = new Array ( need + 1 ) . join ( '0' ) ;
186+ if ( i < 0 )
187+ c = '-' + z + c . slice ( 1 ) ;
188+ else
189+ c = z + c ;
190+ }
185191 }
186192 }
193+ N . push ( c ) ;
187194 }
188- N . push ( c ) ;
195+ } else {
196+ N = concatMap ( n , function ( el ) { return expand ( el , max , false ) } ) ;
189197 }
190- } else {
191- N = concatMap ( n , function ( el ) { return expand ( el , max , false ) } ) ;
192- }
193198
194- for ( var j = 0 ; j < N . length ; j ++ ) {
195- for ( var k = 0 ; k < post . length && expansions . length < max ; k ++ ) {
196- var expansion = pre + N [ j ] + post [ k ] ;
197- if ( ! isTop || isSequence || expansion )
198- expansions . push ( expansion ) ;
199+ for ( var j = 0 ; j < N . length ; j ++ ) {
200+ for ( var k = 0 ; k < post . length && expansions . length < max ; k ++ ) {
201+ var expansion = pre + N [ j ] + post [ k ] ;
202+ if ( ! isTop || isSequence || expansion )
203+ expansions . push ( expansion ) ;
204+ }
199205 }
200- }
201206
202- return expansions ;
207+ return expansions ;
208+ }
203209}
0 commit comments