parserlib: cascade layers

+ simplify Parser.ACTIONS
This commit is contained in:
tophf 2022-02-04 00:43:08 +03:00
parent 9b0db09b6c
commit 421526d60b

View File

@ -1243,6 +1243,7 @@ self.parserlib = (() => {
FONT_FACE_SYM: {text: '@font-face'}, FONT_FACE_SYM: {text: '@font-face'},
IMPORT_SYM: {text: '@import'}, IMPORT_SYM: {text: '@import'},
KEYFRAMES_SYM: {text: ['@keyframes', '@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes']}, KEYFRAMES_SYM: {text: ['@keyframes', '@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes']},
LAYER_SYM: {text: '@layer'},
MEDIA_SYM: {text: '@media'}, MEDIA_SYM: {text: '@media'},
NAMESPACE_SYM: {text: '@namespace'}, NAMESPACE_SYM: {text: '@namespace'},
PAGE_SYM: {text: '@page'}, PAGE_SYM: {text: '@page'},
@ -3442,6 +3443,8 @@ self.parserlib = (() => {
//#endregion //#endregion
//#region Parser //#region Parser
const ParserRoute = {};
class Parser extends EventTarget { class Parser extends EventTarget {
/** /**
* @param {Object} [options] * @param {Object} [options]
@ -3477,15 +3480,53 @@ self.parserlib = (() => {
super.fire(event); super.fire(event);
} }
/**
* @layer <layer-name>#;
* @layer <layer-name>? { <stylesheet> };
*/
_layer(start) {
const stream = this._tokenStream;
const ids = [];
let t, val;
do {
this._ws();
if ((t = stream.get(true)).type === Tokens.IDENT) {
ids.push(t.value);
this._ws();
t = stream.get(true);
}
if ((val = t.value) === '{') {
if (ids[1]) this.fire({type: 'error', message: '@layer block cannot have multiple ids'}, start);
this._layerBlock(start, true, ids[0]);
return;
}
} while (val === ',');
if (val !== ';') stream.mustMatch(Tokens.SEMICOLON);
this.fire({type: 'layer', ids}, start);
this._ws();
}
_layerBlock(start, inBlock, id) {
if (!inBlock) {
this._ws();
id = this._tokenStream.match(Tokens.IDENT);
this._tokenStream.mustMatch(Tokens.LBRACE);
}
this.fire({type: 'startlayer', id: id || null}, start);
this._rulesetBlock(start);
this.fire('endlayer');
this._ws();
}
_stylesheet() { _stylesheet() {
const stream = this._tokenStream; const stream = this._tokenStream;
this.fire('startstylesheet'); this.fire('startstylesheet');
this._sheetGlobals(); this._sheetGlobals();
const {topDocOnly} = this.options; const {topDocOnly} = this.options;
const allowedActions = topDocOnly ? Parser.ACTIONS.topDoc : Parser.ACTIONS.stylesheet; const allowedActions = topDocOnly ? ParserRoute.topDoc : ParserRoute.stylesheet;
for (let tt, token; (tt = (token = stream.get(true)).type); this._skipCruft()) { for (let tt, token; (tt = (token = stream.get(true)).type); this._skipCruft()) {
try { try {
const action = allowedActions.get(tt); const action = allowedActions[tt];
if (action) { if (action) {
action.call(this, token); action.call(this, token);
continue; continue;
@ -3520,6 +3561,7 @@ self.parserlib = (() => {
this._skipCruft(); this._skipCruft();
for (const [type, fn, max = Infinity] of [ for (const [type, fn, max = Infinity] of [
[Tokens.CHARSET_SYM, this._charset, 1], [Tokens.CHARSET_SYM, this._charset, 1],
[Tokens.LAYER_SYM, this._layer],
[Tokens.IMPORT_SYM, this._import], [Tokens.IMPORT_SYM, this._import],
[Tokens.NAMESPACE_SYM, this._namespace], [Tokens.NAMESPACE_SYM, this._namespace],
]) { ]) {
@ -3538,13 +3580,28 @@ self.parserlib = (() => {
} }
_import(start) { _import(start) {
let t, layer;
const stream = this._tokenStream; const stream = this._tokenStream;
const token = stream.mustMatch(TT.stringUri); t = stream.mustMatch(TT.stringUri);
const uri = token.uri || token.value.replace(/^["']|["']$/g, ''); const uri = t.uri || t.value.replace(/^["']|["']$/g, '');
this._ws(); this._ws();
t = stream.get(true);
if (/^layer(\()?$/i.test(t.value)) {
layer = RegExp.$1 ? stream.mustMatch(Tokens.IDENT) : '';
if (layer) stream.mustMatch(Tokens.RPAREN);
this._ws();
t = stream.get(true);
}
if (lowerCmp('supports(', t.value)) {
this._ws();
if (!this._declaration()) this._supportsCondition();
stream.mustMatch(Tokens.RPAREN);
} else {
stream.unget();
}
const media = this._mediaQueryList(); const media = this._mediaQueryList();
stream.mustMatch(Tokens.SEMICOLON); stream.mustMatch(Tokens.SEMICOLON);
this.fire({type: 'import', media, uri}, start); this.fire({type: 'import', layer, media, uri}, start);
this._ws(); this._ws();
} }
@ -3566,16 +3623,7 @@ self.parserlib = (() => {
this._supportsCondition(); this._supportsCondition();
stream.mustMatch(Tokens.LBRACE); stream.mustMatch(Tokens.LBRACE);
this.fire('startsupports', start); this.fire('startsupports', start);
this._ws(); this._rulesetBlock(start);
for (;; stream.skipComment()) {
const action = Parser.ACTIONS.supports.get(stream.peek());
if (action) {
action.call(this, stream.get(true));
} else if (!this._ruleset()) {
break;
}
}
stream.mustMatch(Tokens.RBRACE);
this.fire('endsupports'); this.fire('endsupports');
this._ws(); this._ws();
} }
@ -3642,11 +3690,7 @@ self.parserlib = (() => {
type: 'startmedia', type: 'startmedia',
media: mediaList, media: mediaList,
}, start); }, start);
this._ws(); this._rulesetBlock(start);
let action;
do action = Parser.ACTIONS.media.get(stream.peek());
while (action ? action.call(this, stream.get(true)) || true : this._ruleset());
stream.mustMatch(Tokens.RBRACE);
this.fire({ this.fire({
type: 'endmedia', type: 'endmedia',
media: mediaList, media: mediaList,
@ -3788,16 +3832,13 @@ self.parserlib = (() => {
this.fire({type: 'startdocument', functions, prefix}, start); this.fire({type: 'startdocument', functions, prefix}, start);
if (this.options.topDocOnly) { if (this.options.topDocOnly) {
stream.readDeclValue({stopOn: '}'}); stream.readDeclValue({stopOn: '}'});
stream.mustMatch(Tokens.RBRACE);
} else { } else {
/* We allow @import and such inside document sections because the final generated CSS for /* We allow @import and such inside document sections because the final generated CSS for
* a given page may be valid e.g. if this section is the first one that matched the URL */ * a given page may be valid e.g. if this section is the first one that matched the URL */
this._sheetGlobals(); this._sheetGlobals();
this._ws(); this._rulesetBlock(start);
let action;
do action = Parser.ACTIONS.document.get(stream.peek());
while (action ? action.call(this, stream.get(true)) || true : this._ruleset());
} }
stream.mustMatch(Tokens.RBRACE);
this.fire({type: 'enddocument', functions, prefix}); this.fire({type: 'enddocument', functions, prefix});
this._ws(); this._ws();
} }
@ -3879,6 +3920,20 @@ self.parserlib = (() => {
} }
} }
/** @param {parserlib.Token} start */
_rulesetBlock(start) {
const stream = this._tokenStream;
const map = ParserRoute[start.type];
this._ws();
while (true) {
const fn = map[stream.LT(1).type];
if (fn) fn.call(this, stream.get(true));
else if (!this._ruleset()) break;
stream.skipComment();
}
stream.mustMatch(Tokens.RBRACE);
}
_selectorsGroup() { _selectorsGroup() {
const stream = this._tokenStream; const stream = this._tokenStream;
const selectors = []; const selectors = [];
@ -3949,7 +4004,7 @@ self.parserlib = (() => {
} }
while (true) { while (true) {
const token = stream.get(true); const token = stream.get(true);
const action = Parser.ACTIONS.simpleSelectorSequence.get(token.type); const action = ParserRoute.simpleSelectorSequence[token.type];
const component = action ? action.call(this, token) : (stream.unget(), 0); const component = action ? action.call(this, token) : (stream.unget(), 0);
if (!component) break; if (!component) break;
modifiers.push(component); modifiers.push(component);
@ -4543,77 +4598,38 @@ self.parserlib = (() => {
Object.assign(Parser.prototype, TYPES); Object.assign(Parser.prototype, TYPES);
Parser.prototype._readWhitespace = Parser.prototype._ws; Parser.prototype._readWhitespace = Parser.prototype._ws;
const symDocument = [Tokens.DOCUMENT_SYM, Parser.prototype._document]; ParserRoute[Tokens.DOCUMENT_SYM] =
const symDocMisplaced = [Tokens.DOCUMENT_SYM, Parser.prototype._documentMisplaced]; ParserRoute[Tokens.LAYER_SYM] =
const symFontFace = [Tokens.FONT_FACE_SYM, Parser.prototype._fontFace]; ParserRoute[Tokens.MEDIA_SYM] =
const symKeyframes = [Tokens.KEYFRAMES_SYM, Parser.prototype._keyframes]; ParserRoute[Tokens.SUPPORTS_SYM] = {
const symMedia = [Tokens.MEDIA_SYM, Parser.prototype._media]; [Tokens.DOCUMENT_SYM]: Parser.prototype._documentMisplaced,
const symPage = [Tokens.PAGE_SYM, Parser.prototype._page]; [Tokens.FONT_FACE_SYM]: Parser.prototype._fontFace,
const symSupports = [Tokens.SUPPORTS_SYM, Parser.prototype._supports]; [Tokens.KEYFRAMES_SYM]: Parser.prototype._keyframes,
const symUnknown = [Tokens.UNKNOWN_SYM, Parser.prototype._unknownSym]; [Tokens.LAYER_SYM]: Parser.prototype._layerBlock,
const symViewport = [Tokens.VIEWPORT_SYM, Parser.prototype._viewport]; [Tokens.MEDIA_SYM]: Parser.prototype._media,
[Tokens.PAGE_SYM]: Parser.prototype._page,
Parser.ACTIONS = { [Tokens.SUPPORTS_SYM]: Parser.prototype._supports,
[Tokens.UNKNOWN_SYM]: Parser.prototype._unknownSym,
stylesheet: new Map([ [Tokens.VIEWPORT_SYM]: Parser.prototype._viewport,
symMedia, };
symDocument, ParserRoute.stylesheet = Object.assign({}, ParserRoute[Tokens.DOCUMENT_SYM], {
symSupports, [Tokens.DOCUMENT_SYM]: Parser.prototype._document,
symPage, [Tokens.S]: Parser.prototype._ws,
symFontFace, });
symKeyframes, ParserRoute.topDoc = {
symViewport, [Tokens.DOCUMENT_SYM]: Parser.prototype._document,
symUnknown, [Tokens.UNKNOWN_SYM]: Parser.prototype._unknownSym,
[Tokens.S, Parser.prototype._ws], [Tokens.S]: Parser.prototype._ws,
]), };
ParserRoute.simpleSelectorSequence = {
topDoc: new Map([ [Tokens.HASH]: Parser.prototype._hash,
symDocument, [Tokens.DOT]: Parser.prototype._class,
symUnknown, [Tokens.LBRACKET]: Parser.prototype._attrib,
[Tokens.S, Parser.prototype._ws], [Tokens.COLON]: Parser.prototype._pseudo,
]), [Tokens.IS]: Parser.prototype._is,
[Tokens.ANY]: Parser.prototype._is,
document: new Map([ [Tokens.WHERE]: Parser.prototype._is,
symMedia, [Tokens.NOT]: Parser.prototype._negation,
symDocMisplaced,
symSupports,
symPage,
symFontFace,
symViewport,
symKeyframes,
symUnknown,
]),
supports: new Map([
symKeyframes,
symMedia,
symSupports,
symDocMisplaced,
symViewport,
symUnknown,
]),
media: new Map([
symKeyframes,
symMedia,
symDocMisplaced,
symSupports,
symPage,
symFontFace,
symViewport,
symUnknown,
]),
simpleSelectorSequence: new Map([
[Tokens.HASH, Parser.prototype._hash],
[Tokens.DOT, Parser.prototype._class],
[Tokens.LBRACKET, Parser.prototype._attrib],
[Tokens.COLON, Parser.prototype._pseudo],
[Tokens.IS, Parser.prototype._is],
[Tokens.ANY, Parser.prototype._is],
[Tokens.WHERE, Parser.prototype._is],
[Tokens.NOT, Parser.prototype._negation],
]),
}; };
//#endregion //#endregion