Skip to content

Commit

Permalink
Group syntax entities by combinator precedence
Browse files Browse the repository at this point in the history
  • Loading branch information
frenic committed Apr 13, 2018
1 parent 8c7d16e commit 3efdd20
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 148 deletions.
8 changes: 4 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3554,7 +3554,7 @@ type BorderImageOutsetProperty<TLength> = Globals | TLength | string | number;

type BorderImageRepeatProperty = Globals | "repeat" | "round" | "space" | "stretch" | string;

type BorderImageSliceProperty = Globals | "fill" | string | number;
type BorderImageSliceProperty = Globals | string | number;

type BorderImageSourceProperty = Globals | "none" | string;

Expand Down Expand Up @@ -3894,7 +3894,7 @@ type MaskBorderOutsetProperty<TLength> = Globals | TLength | string | number;

type MaskBorderRepeatProperty = Globals | "repeat" | "round" | "space" | "stretch" | string;

type MaskBorderSliceProperty = Globals | "fill" | string | number;
type MaskBorderSliceProperty = Globals | string | number;

type MaskBorderSourceProperty = Globals | "none" | string;

Expand Down Expand Up @@ -4184,7 +4184,7 @@ type BorderBottomProperty<TLength> = Globals | BrWidth<TLength> | BrStyle | Colo

type BorderColorProperty = Globals | Color | string;

type BorderImageProperty = Globals | "fill" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
type BorderImageProperty = Globals | "none" | "repeat" | "round" | "space" | "stretch" | string | number;

type BorderInlineEndProperty<TLength> = Globals | BrWidth<TLength> | BrStyle | NamedColor | DeprecatedSystemColor | "currentcolor" | string;

Expand Down Expand Up @@ -4232,7 +4232,7 @@ type MarginProperty<TLength> = Globals | TLength | "auto" | string;

type MaskProperty<TLength> = Globals | MaskLayer<TLength> | string;

type MaskBorderProperty = Globals | "alpha" | "fill" | "luminance" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
type MaskBorderProperty = Globals | "alpha" | "luminance" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;

type OffsetProperty<TLength> = Globals | Position<TLength> | GeometryBox | "auto" | "none" | string;

Expand Down
8 changes: 4 additions & 4 deletions index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -3310,7 +3310,7 @@ type BorderImageOutsetProperty<TLength> = Globals | TLength | string | number;

type BorderImageRepeatProperty = Globals | "repeat" | "round" | "space" | "stretch" | string;

type BorderImageSliceProperty = Globals | "fill" | string | number;
type BorderImageSliceProperty = Globals | string | number;

type BorderImageSourceProperty = Globals | "none" | string;

Expand Down Expand Up @@ -3650,7 +3650,7 @@ type MaskBorderOutsetProperty<TLength> = Globals | TLength | string | number;

type MaskBorderRepeatProperty = Globals | "repeat" | "round" | "space" | "stretch" | string;

type MaskBorderSliceProperty = Globals | "fill" | string | number;
type MaskBorderSliceProperty = Globals | string | number;

type MaskBorderSourceProperty = Globals | "none" | string;

Expand Down Expand Up @@ -3940,7 +3940,7 @@ type BorderBottomProperty<TLength> = Globals | BrWidth<TLength> | BrStyle | Colo

type BorderColorProperty = Globals | Color | string;

type BorderImageProperty = Globals | "fill" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
type BorderImageProperty = Globals | "none" | "repeat" | "round" | "space" | "stretch" | string | number;

type BorderInlineEndProperty<TLength> = Globals | BrWidth<TLength> | BrStyle | NamedColor | DeprecatedSystemColor | "currentcolor" | string;

Expand Down Expand Up @@ -3988,7 +3988,7 @@ type MarginProperty<TLength> = Globals | TLength | "auto" | string;

type MaskProperty<TLength> = Globals | MaskLayer<TLength> | string;

type MaskBorderProperty = Globals | "alpha" | "fill" | "luminance" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
type MaskBorderProperty = Globals | "alpha" | "luminance" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;

type OffsetProperty<TLength> = Globals | Position<TLength> | GeometryBox | "auto" | "none" | string;

Expand Down
4 changes: 2 additions & 2 deletions src/compat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Combinator, combinatorData, Component, componentData, componentGroupData, Entity, EntityType } from './parser';
import { Combinator, combinators, Component, componentData, componentGroupData, Entity, EntityType } from './parser';

const importsCache: { [cssPath: string]: MDN.PropertiesCompat | null } = {};

Expand Down Expand Up @@ -108,7 +108,7 @@ export function compatSyntax(data: MDN.CompatData, entities: EntityType[]): Enti
const alternativeEntities: EntityType[] = [entity];

for (const keyword of alternatives) {
alternativeEntities.push(combinatorData(Combinator.SingleBar), componentData(Component.Keyword, keyword));
alternativeEntities.push(combinators[Combinator.SingleBar], componentData(Component.Keyword, keyword));
}

compatEntities.push(componentGroupData(alternativeEntities));
Expand Down
156 changes: 135 additions & 21 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ export enum Component {
Group,
}

// Higher number is higher precedence
export enum Combinator {
/** Components are mandatory and should appear in that order */
Juxtaposition,
Juxtaposition = 0,
/** Components are mandatory but may appear in any order */
DoubleAmpersand,
DoubleAmpersand = 1,
/** At least one of the components must be present, and they may appear in any order */
DoubleBar,
DoubleBar = 2,
/** Exactly one of the components must be present */
SingleBar,
SingleBar = 3,
}

export enum Multiplier {
Expand Down Expand Up @@ -72,7 +73,6 @@ export type ComponentType = INonGroupData | IGroupData;

export interface ICombinator {
entity: Entity.Combinator;
multiplier: MultiplierType | null;
combinator: Combinator;
}

Expand All @@ -81,7 +81,7 @@ export interface IFunction {
multiplier: MultiplierType | null;
}

interface IUnknown {
export interface IUnknown {
entity: Entity.Unknown;
multiplier: MultiplierType | null;
}
Expand All @@ -92,10 +92,29 @@ const REGEX_ENTITY = /(?:^|\s)((?:[\w]+\([^\)]*\))|[^\s*+?#!{]+)([*+?#!]|{(\d+),
const REGEX_DATA_TYPE = /^(<[^>]+>)/g;
const REGEX_KEYWORD = /^([\w-]+)/g;

export const combinators: { [key: number]: ICombinator } = {
[Combinator.Juxtaposition]: {
entity: Entity.Combinator,
combinator: Combinator.Juxtaposition,
},
[Combinator.DoubleAmpersand]: {
entity: Entity.Combinator,
combinator: Combinator.DoubleAmpersand,
},
[Combinator.DoubleBar]: {
entity: Entity.Combinator,
combinator: Combinator.DoubleBar,
},
[Combinator.SingleBar]: {
entity: Entity.Combinator,
combinator: Combinator.SingleBar,
},
};

export default function parse(syntax: string): EntityType[] {
const levels: EntityType[][] = [[]];
const deepestLevel = () => levels[levels.length - 1];
let previousMatchWasComponent = false;

let entityMatch: RegExpExecArray | null;
while ((entityMatch = REGEX_ENTITY.exec(syntax))) {
const [, value, ...rawMultiplier] = entityMatch;
Expand All @@ -104,27 +123,27 @@ export default function parse(syntax: string): EntityType[] {
previousMatchWasComponent = false;
continue;
} else if (value.indexOf('&&') === 0) {
deepestLevel().push(combinatorData(Combinator.DoubleAmpersand, multiplierData(rawMultiplier)));
deepestLevel().push(combinators[Combinator.DoubleAmpersand]);
previousMatchWasComponent = false;
continue;
} else if (value.indexOf('||') === 0) {
deepestLevel().push(combinatorData(Combinator.DoubleBar, multiplierData(rawMultiplier)));
deepestLevel().push(combinators[Combinator.DoubleBar]);
previousMatchWasComponent = false;
continue;
} else if (value.indexOf('|') === 0) {
deepestLevel().push(combinatorData(Combinator.SingleBar, multiplierData(rawMultiplier)));
deepestLevel().push(combinators[Combinator.SingleBar]);
previousMatchWasComponent = false;
continue;
} else if (value.indexOf(']') === 0) {
const definitions = levels.pop();
if (definitions) {
deepestLevel().push(componentGroupData(definitions, multiplierData(rawMultiplier)));
deepestLevel().push(componentGroupData(groupByPrecedence(definitions), multiplierData(rawMultiplier)));
}
previousMatchWasComponent = true;
continue;
} else {
if (previousMatchWasComponent === true) {
deepestLevel().push(combinatorData(Combinator.Juxtaposition));
if (previousMatchWasComponent) {
deepestLevel().push(combinators[Combinator.Juxtaposition]);
}

if (value.indexOf('[') === 0) {
Expand All @@ -149,15 +168,55 @@ export default function parse(syntax: string): EntityType[] {
deepestLevel().push({ entity: Entity.Unknown, multiplier: multiplierData(rawMultiplier) });
}

return levels[0];
function deepestLevel() {
return levels[levels.length - 1];
}

return groupByPrecedence(levels[0]);
}

export function combinatorData(combinator: Combinator, multiplier: MultiplierType | null = null): ICombinator {
return {
entity: Entity.Combinator,
combinator,
multiplier,
};
export function isComponent(entity: EntityType): entity is ComponentType {
return entity.entity === Entity.Component;
}

export function isCombinator(entity: EntityType): entity is ICombinator {
return entity.entity === Entity.Combinator;
}

export function isCurlyBracetMultiplier(multiplier: MultiplierType): multiplier is IMultiplierCurlyBracet {
return multiplier.sign === Multiplier.CurlyBracet;
}

export function isMandatoryMultiplied(multiplier: MultiplierType | null) {
return multiplier !== null && (isCurlyBracetMultiplier(multiplier) && multiplier.min > 1);
}

export function isOptionallyMultiplied(multiplier: MultiplierType | null) {
return (
multiplier !== null &&
((isCurlyBracetMultiplier(multiplier) && multiplier.min < multiplier.max && multiplier.max > 1) ||
multiplier.sign === Multiplier.Asterisk ||
multiplier.sign === Multiplier.PlusSign ||
multiplier.sign === Multiplier.HashMark ||
multiplier.sign === Multiplier.ExclamationPoint)
);
}

export function isMandatoryEntity(entity: EntityType) {
if (isCombinator(entity)) {
return entity === combinators[Combinator.DoubleAmpersand] || entity === combinators[Combinator.Juxtaposition];
}

if (entity.multiplier) {
return (
(isCurlyBracetMultiplier(entity.multiplier) && entity.multiplier.min > 0) ||
entity.multiplier.sign === Multiplier.PlusSign ||
entity.multiplier.sign === Multiplier.HashMark ||
entity.multiplier.sign === Multiplier.ExclamationPoint
);
}

return true;
}

export function componentData(
Expand Down Expand Up @@ -198,8 +257,63 @@ function multiplierData(raw: string[]): MultiplierType | null {
case '!':
return { sign: Multiplier.ExclamationPoint };
case '{':
return { sign: Multiplier.CurlyBracet, min: +raw[1], max: +raw[2] };
return { sign: Multiplier.CurlyBracet, min: Number(raw[1]), max: Number(raw[2]) };
default:
return null;
}
}

function groupByPrecedence(entities: EntityType[], precedence: number = Combinator.SingleBar): EntityType[] {
if (precedence < 0) {
// We've reached the lowest precedence possible
return entities;
}

const combinator = combinators[precedence];
const combinatorIndexes: number[] = [];

// Search for indexes where the combinator is used
for (let i = entities.indexOf(combinator); i > -1; i = entities.indexOf(combinator, i + 1)) {
combinatorIndexes.push(i);
}

const nextPrecedence = precedence - 1;

if (combinatorIndexes.length === 0) {
return groupByPrecedence(entities, nextPrecedence);
}

const groupedEntities: EntityType[] = [];

// Yes, what you see is correct: it's index of indexes
for (
let i = 0;
// Add one loop to finnish up the last entities
i < combinatorIndexes.length + 1;
i++
) {
const sectionEntities = entities.slice(
i > 0
? combinatorIndexes[i - 1] + 1
: // Slice from beginning
0,
i < combinatorIndexes.length
? combinatorIndexes[i]
: // Slice to end
entities.length,
);

// Only group if there's more than one entity in between
if (sectionEntities.length > 1) {
groupedEntities.push(componentGroupData(groupByPrecedence(sectionEntities, nextPrecedence)));
} else {
groupedEntities.push(...sectionEntities);
}

if (i < combinatorIndexes.length) {
groupedEntities.push(entities[combinatorIndexes[i]]);
}
}

return groupedEntities;
}
Loading

0 comments on commit 3efdd20

Please sign in to comment.