@@ -11,15 +11,16 @@ export enum Component {
1111 Group ,
1212}
1313
14+ // Higher number is higher precedence
1415export enum Combinator {
1516 /** Components are mandatory and should appear in that order */
16- Juxtaposition ,
17+ Juxtaposition = 0 ,
1718 /** Components are mandatory but may appear in any order */
18- DoubleAmpersand ,
19+ DoubleAmpersand = 1 ,
1920 /** At least one of the components must be present, and they may appear in any order */
20- DoubleBar ,
21+ DoubleBar = 2 ,
2122 /** Exactly one of the components must be present */
22- SingleBar ,
23+ SingleBar = 3 ,
2324}
2425
2526export enum Multiplier {
@@ -72,7 +73,6 @@ export type ComponentType = INonGroupData | IGroupData;
7273
7374export interface ICombinator {
7475 entity : Entity . Combinator ;
75- multiplier : MultiplierType | null ;
7676 combinator : Combinator ;
7777}
7878
@@ -81,7 +81,7 @@ export interface IFunction {
8181 multiplier : MultiplierType | null ;
8282}
8383
84- interface IUnknown {
84+ export interface IUnknown {
8585 entity : Entity . Unknown ;
8686 multiplier : MultiplierType | null ;
8787}
@@ -92,10 +92,29 @@ const REGEX_ENTITY = /(?:^|\s)((?:[\w]+\([^\)]*\))|[^\s*+?#!{]+)([*+?#!]|{(\d+),
9292const REGEX_DATA_TYPE = / ^ ( < [ ^ > ] + > ) / g;
9393const REGEX_KEYWORD = / ^ ( [ \w - ] + ) / g;
9494
95+ export const combinators : { [ key : number ] : ICombinator } = {
96+ [ Combinator . Juxtaposition ] : {
97+ entity : Entity . Combinator ,
98+ combinator : Combinator . Juxtaposition ,
99+ } ,
100+ [ Combinator . DoubleAmpersand ] : {
101+ entity : Entity . Combinator ,
102+ combinator : Combinator . DoubleAmpersand ,
103+ } ,
104+ [ Combinator . DoubleBar ] : {
105+ entity : Entity . Combinator ,
106+ combinator : Combinator . DoubleBar ,
107+ } ,
108+ [ Combinator . SingleBar ] : {
109+ entity : Entity . Combinator ,
110+ combinator : Combinator . SingleBar ,
111+ } ,
112+ } ;
113+
95114export default function parse ( syntax : string ) : EntityType [ ] {
96115 const levels : EntityType [ ] [ ] = [ [ ] ] ;
97- const deepestLevel = ( ) => levels [ levels . length - 1 ] ;
98116 let previousMatchWasComponent = false ;
117+
99118 let entityMatch : RegExpExecArray | null ;
100119 while ( ( entityMatch = REGEX_ENTITY . exec ( syntax ) ) ) {
101120 const [ , value , ...rawMultiplier ] = entityMatch ;
@@ -104,27 +123,27 @@ export default function parse(syntax: string): EntityType[] {
104123 previousMatchWasComponent = false ;
105124 continue ;
106125 } else if ( value . indexOf ( '&&' ) === 0 ) {
107- deepestLevel ( ) . push ( combinatorData ( Combinator . DoubleAmpersand , multiplierData ( rawMultiplier ) ) ) ;
126+ deepestLevel ( ) . push ( combinators [ Combinator . DoubleAmpersand ] ) ;
108127 previousMatchWasComponent = false ;
109128 continue ;
110129 } else if ( value . indexOf ( '||' ) === 0 ) {
111- deepestLevel ( ) . push ( combinatorData ( Combinator . DoubleBar , multiplierData ( rawMultiplier ) ) ) ;
130+ deepestLevel ( ) . push ( combinators [ Combinator . DoubleBar ] ) ;
112131 previousMatchWasComponent = false ;
113132 continue ;
114133 } else if ( value . indexOf ( '|' ) === 0 ) {
115- deepestLevel ( ) . push ( combinatorData ( Combinator . SingleBar , multiplierData ( rawMultiplier ) ) ) ;
134+ deepestLevel ( ) . push ( combinators [ Combinator . SingleBar ] ) ;
116135 previousMatchWasComponent = false ;
117136 continue ;
118137 } else if ( value . indexOf ( ']' ) === 0 ) {
119138 const definitions = levels . pop ( ) ;
120139 if ( definitions ) {
121- deepestLevel ( ) . push ( componentGroupData ( definitions , multiplierData ( rawMultiplier ) ) ) ;
140+ deepestLevel ( ) . push ( componentGroupData ( groupByPrecedence ( definitions ) , multiplierData ( rawMultiplier ) ) ) ;
122141 }
123142 previousMatchWasComponent = true ;
124143 continue ;
125144 } else {
126- if ( previousMatchWasComponent === true ) {
127- deepestLevel ( ) . push ( combinatorData ( Combinator . Juxtaposition ) ) ;
145+ if ( previousMatchWasComponent ) {
146+ deepestLevel ( ) . push ( combinators [ Combinator . Juxtaposition ] ) ;
128147 }
129148
130149 if ( value . indexOf ( '[' ) === 0 ) {
@@ -149,15 +168,55 @@ export default function parse(syntax: string): EntityType[] {
149168 deepestLevel ( ) . push ( { entity : Entity . Unknown , multiplier : multiplierData ( rawMultiplier ) } ) ;
150169 }
151170
152- return levels [ 0 ] ;
171+ function deepestLevel ( ) {
172+ return levels [ levels . length - 1 ] ;
173+ }
174+
175+ return groupByPrecedence ( levels [ 0 ] ) ;
153176}
154177
155- export function combinatorData ( combinator : Combinator , multiplier : MultiplierType | null = null ) : ICombinator {
156- return {
157- entity : Entity . Combinator ,
158- combinator,
159- multiplier,
160- } ;
178+ export function isComponent ( entity : EntityType ) : entity is ComponentType {
179+ return entity . entity === Entity . Component ;
180+ }
181+
182+ export function isCombinator ( entity : EntityType ) : entity is ICombinator {
183+ return entity . entity === Entity . Combinator ;
184+ }
185+
186+ export function isCurlyBracetMultiplier ( multiplier : MultiplierType ) : multiplier is IMultiplierCurlyBracet {
187+ return multiplier . sign === Multiplier . CurlyBracet ;
188+ }
189+
190+ export function isMandatoryMultiplied ( multiplier : MultiplierType | null ) {
191+ return multiplier !== null && ( isCurlyBracetMultiplier ( multiplier ) && multiplier . min > 1 ) ;
192+ }
193+
194+ export function isOptionallyMultiplied ( multiplier : MultiplierType | null ) {
195+ return (
196+ multiplier !== null &&
197+ ( ( isCurlyBracetMultiplier ( multiplier ) && multiplier . min < multiplier . max && multiplier . max > 1 ) ||
198+ multiplier . sign === Multiplier . Asterisk ||
199+ multiplier . sign === Multiplier . PlusSign ||
200+ multiplier . sign === Multiplier . HashMark ||
201+ multiplier . sign === Multiplier . ExclamationPoint )
202+ ) ;
203+ }
204+
205+ export function isMandatoryEntity ( entity : EntityType ) {
206+ if ( isCombinator ( entity ) ) {
207+ return entity === combinators [ Combinator . DoubleAmpersand ] || entity === combinators [ Combinator . Juxtaposition ] ;
208+ }
209+
210+ if ( entity . multiplier ) {
211+ return (
212+ ( isCurlyBracetMultiplier ( entity . multiplier ) && entity . multiplier . min > 0 ) ||
213+ entity . multiplier . sign === Multiplier . PlusSign ||
214+ entity . multiplier . sign === Multiplier . HashMark ||
215+ entity . multiplier . sign === Multiplier . ExclamationPoint
216+ ) ;
217+ }
218+
219+ return true ;
161220}
162221
163222export function componentData (
@@ -198,8 +257,63 @@ function multiplierData(raw: string[]): MultiplierType | null {
198257 case '!' :
199258 return { sign : Multiplier . ExclamationPoint } ;
200259 case '{' :
201- return { sign : Multiplier . CurlyBracet , min : + raw [ 1 ] , max : + raw [ 2 ] } ;
260+ return { sign : Multiplier . CurlyBracet , min : Number ( raw [ 1 ] ) , max : Number ( raw [ 2 ] ) } ;
202261 default :
203262 return null ;
204263 }
205264}
265+
266+ function groupByPrecedence ( entities : EntityType [ ] , precedence : number = Combinator . SingleBar ) : EntityType [ ] {
267+ if ( precedence < 0 ) {
268+ // We've reached the lowest precedence possible
269+ return entities ;
270+ }
271+
272+ const combinator = combinators [ precedence ] ;
273+ const combinatorIndexes : number [ ] = [ ] ;
274+
275+ // Search for indexes where the combinator is used
276+ for ( let i = entities . indexOf ( combinator ) ; i > - 1 ; i = entities . indexOf ( combinator , i + 1 ) ) {
277+ combinatorIndexes . push ( i ) ;
278+ }
279+
280+ const nextPrecedence = precedence - 1 ;
281+
282+ if ( combinatorIndexes . length === 0 ) {
283+ return groupByPrecedence ( entities , nextPrecedence ) ;
284+ }
285+
286+ const groupedEntities : EntityType [ ] = [ ] ;
287+
288+ // Yes, what you see is correct: it's index of indexes
289+ for (
290+ let i = 0 ;
291+ // Add one loop to finnish up the last entities
292+ i < combinatorIndexes . length + 1 ;
293+ i ++
294+ ) {
295+ const sectionEntities = entities . slice (
296+ i > 0
297+ ? combinatorIndexes [ i - 1 ] + 1
298+ : // Slice from beginning
299+ 0 ,
300+ i < combinatorIndexes . length
301+ ? combinatorIndexes [ i ]
302+ : // Slice to end
303+ entities . length ,
304+ ) ;
305+
306+ // Only group if there's more than one entity in between
307+ if ( sectionEntities . length > 1 ) {
308+ groupedEntities . push ( componentGroupData ( groupByPrecedence ( sectionEntities , nextPrecedence ) ) ) ;
309+ } else {
310+ groupedEntities . push ( ...sectionEntities ) ;
311+ }
312+
313+ if ( i < combinatorIndexes . length ) {
314+ groupedEntities . push ( entities [ combinatorIndexes [ i ] ] ) ;
315+ }
316+ }
317+
318+ return groupedEntities ;
319+ }
0 commit comments