Skip to content

Commit 313f2d1

Browse files
committed
Group syntax entities by combinator precedence
1 parent 0b206ec commit 313f2d1

2 files changed

Lines changed: 203 additions & 141 deletions

File tree

src/parser.ts

Lines changed: 135 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ export enum Component {
1111
Group,
1212
}
1313

14+
// Higher number is higher precedence
1415
export 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

2526
export enum Multiplier {
@@ -72,7 +73,6 @@ export type ComponentType = INonGroupData | IGroupData;
7273

7374
export 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+),
9292
const REGEX_DATA_TYPE = /^(<[^>]+>)/g;
9393
const REGEX_KEYWORD = /^([\w-]+)/g;
9494

95+
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+
95114
export 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,54 @@ 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-
function combinatorData(combinator: Combinator, multiplier: MultiplierType | null = null): ICombinator {
156-
return {
157-
entity: Entity.Combinator,
158-
combinator,
159-
multiplier,
160-
};
178+
export function isFunction(entity: EntityType): entity is IFunction {
179+
return entity.entity === Entity.Function;
180+
}
181+
182+
export function isComponent(entity: EntityType): entity is ComponentType {
183+
return entity.entity === Entity.Component;
184+
}
185+
186+
export function isCombinator(entity: EntityType): entity is ICombinator {
187+
return entity.entity === Entity.Combinator;
188+
}
189+
190+
export function isCurlyBracetMultiplier(multiplier: MultiplierType): multiplier is IMultiplierCurlyBracet {
191+
return multiplier.sign === Multiplier.CurlyBracet;
192+
}
193+
194+
export function isMultiplied(multiplier: MultiplierType) {
195+
return (
196+
(isCurlyBracetMultiplier(multiplier) && (multiplier.min > 1 || multiplier.max > 1)) ||
197+
multiplier.sign === Multiplier.Asterisk ||
198+
multiplier.sign === Multiplier.PlusSign ||
199+
multiplier.sign === Multiplier.HashMark ||
200+
multiplier.sign === Multiplier.ExclamationPoint
201+
);
202+
}
203+
204+
export function isMandatoryEntity(entity: EntityType) {
205+
if (isCombinator(entity)) {
206+
return entity === combinators[Combinator.DoubleAmpersand] || entity === combinators[Combinator.Juxtaposition];
207+
}
208+
209+
if (entity.multiplier) {
210+
return (
211+
(isCurlyBracetMultiplier(entity.multiplier) && entity.multiplier.min === 0) ||
212+
entity.multiplier.sign === Multiplier.PlusSign ||
213+
entity.multiplier.sign === Multiplier.HashMark ||
214+
entity.multiplier.sign === Multiplier.ExclamationPoint
215+
);
216+
}
217+
218+
return true;
161219
}
162220

163221
function componentData(
@@ -173,7 +231,7 @@ function componentData(
173231
};
174232
}
175233

176-
function componentGroupData(entities: EntityType[], multiplier: MultiplierType | null): ComponentType {
234+
function componentGroupData(entities: EntityType[], multiplier: MultiplierType | null = null): ComponentType {
177235
return {
178236
entity: Entity.Component,
179237
component: Component.Group,
@@ -198,8 +256,63 @@ function multiplierData(raw: string[]): MultiplierType | null {
198256
case '!':
199257
return { sign: Multiplier.ExclamationPoint };
200258
case '{':
201-
return { sign: Multiplier.CurlyBracet, min: +raw[1], max: +raw[2] };
259+
return { sign: Multiplier.CurlyBracet, min: Number(raw[1]), max: Number(raw[2]) };
202260
default:
203261
return null;
204262
}
205263
}
264+
265+
function groupByPrecedence(entities: EntityType[], precedence: number = Combinator.SingleBar): EntityType[] {
266+
if (precedence < 0) {
267+
// We've reached the lowest precedence possible
268+
return entities;
269+
}
270+
271+
const combinator = combinators[precedence];
272+
const combinatorIndexes: number[] = [];
273+
274+
// Search for indexes where the combinator is used
275+
for (let i = entities.indexOf(combinator); i > -1; i = entities.indexOf(combinator, i + 1)) {
276+
combinatorIndexes.push(i);
277+
}
278+
279+
const nextPrecedence = precedence - 1;
280+
281+
if (combinatorIndexes.length === 0) {
282+
return groupByPrecedence(entities, nextPrecedence);
283+
}
284+
285+
const groupedEntities: EntityType[] = [];
286+
287+
// Yes, what you see is correct: it's index of indexes
288+
for (
289+
let i = 0;
290+
// Add one loop to finnish up the last entities
291+
i < combinatorIndexes.length + 1;
292+
i++
293+
) {
294+
const sectionEntities = entities.slice(
295+
i > 0
296+
? combinatorIndexes[i - 1] + 1
297+
: // Slice from beginning
298+
0,
299+
i < combinatorIndexes.length
300+
? combinatorIndexes[i]
301+
: // Slice to end
302+
entities.length,
303+
);
304+
305+
// Only group if there's more than one entity in between
306+
if (sectionEntities.length > 1) {
307+
groupedEntities.push(componentGroupData(groupByPrecedence(sectionEntities, nextPrecedence)));
308+
} else {
309+
groupedEntities.push(...sectionEntities);
310+
}
311+
312+
if (i < combinatorIndexes.length) {
313+
groupedEntities.push(entities[combinatorIndexes[i]]);
314+
}
315+
}
316+
317+
return groupedEntities;
318+
}

0 commit comments

Comments
 (0)