source: imaps-frontend/node_modules/css-tree/lib/definition-syntax/parse.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 13.6 KB
Line 
1var Tokenizer = require('./tokenizer');
2var TAB = 9;
3var N = 10;
4var F = 12;
5var R = 13;
6var SPACE = 32;
7var EXCLAMATIONMARK = 33; // !
8var NUMBERSIGN = 35; // #
9var AMPERSAND = 38; // &
10var APOSTROPHE = 39; // '
11var LEFTPARENTHESIS = 40; // (
12var RIGHTPARENTHESIS = 41; // )
13var ASTERISK = 42; // *
14var PLUSSIGN = 43; // +
15var COMMA = 44; // ,
16var HYPERMINUS = 45; // -
17var LESSTHANSIGN = 60; // <
18var GREATERTHANSIGN = 62; // >
19var QUESTIONMARK = 63; // ?
20var COMMERCIALAT = 64; // @
21var LEFTSQUAREBRACKET = 91; // [
22var RIGHTSQUAREBRACKET = 93; // ]
23var LEFTCURLYBRACKET = 123; // {
24var VERTICALLINE = 124; // |
25var RIGHTCURLYBRACKET = 125; // }
26var INFINITY = 8734; // ∞
27var NAME_CHAR = createCharMap(function(ch) {
28 return /[a-zA-Z0-9\-]/.test(ch);
29});
30var COMBINATOR_PRECEDENCE = {
31 ' ': 1,
32 '&&': 2,
33 '||': 3,
34 '|': 4
35};
36
37function createCharMap(fn) {
38 var array = typeof Uint32Array === 'function' ? new Uint32Array(128) : new Array(128);
39 for (var i = 0; i < 128; i++) {
40 array[i] = fn(String.fromCharCode(i)) ? 1 : 0;
41 }
42 return array;
43}
44
45function scanSpaces(tokenizer) {
46 return tokenizer.substringToPos(
47 tokenizer.findWsEnd(tokenizer.pos)
48 );
49}
50
51function scanWord(tokenizer) {
52 var end = tokenizer.pos;
53
54 for (; end < tokenizer.str.length; end++) {
55 var code = tokenizer.str.charCodeAt(end);
56 if (code >= 128 || NAME_CHAR[code] === 0) {
57 break;
58 }
59 }
60
61 if (tokenizer.pos === end) {
62 tokenizer.error('Expect a keyword');
63 }
64
65 return tokenizer.substringToPos(end);
66}
67
68function scanNumber(tokenizer) {
69 var end = tokenizer.pos;
70
71 for (; end < tokenizer.str.length; end++) {
72 var code = tokenizer.str.charCodeAt(end);
73 if (code < 48 || code > 57) {
74 break;
75 }
76 }
77
78 if (tokenizer.pos === end) {
79 tokenizer.error('Expect a number');
80 }
81
82 return tokenizer.substringToPos(end);
83}
84
85function scanString(tokenizer) {
86 var end = tokenizer.str.indexOf('\'', tokenizer.pos + 1);
87
88 if (end === -1) {
89 tokenizer.pos = tokenizer.str.length;
90 tokenizer.error('Expect an apostrophe');
91 }
92
93 return tokenizer.substringToPos(end + 1);
94}
95
96function readMultiplierRange(tokenizer) {
97 var min = null;
98 var max = null;
99
100 tokenizer.eat(LEFTCURLYBRACKET);
101
102 min = scanNumber(tokenizer);
103
104 if (tokenizer.charCode() === COMMA) {
105 tokenizer.pos++;
106 if (tokenizer.charCode() !== RIGHTCURLYBRACKET) {
107 max = scanNumber(tokenizer);
108 }
109 } else {
110 max = min;
111 }
112
113 tokenizer.eat(RIGHTCURLYBRACKET);
114
115 return {
116 min: Number(min),
117 max: max ? Number(max) : 0
118 };
119}
120
121function readMultiplier(tokenizer) {
122 var range = null;
123 var comma = false;
124
125 switch (tokenizer.charCode()) {
126 case ASTERISK:
127 tokenizer.pos++;
128
129 range = {
130 min: 0,
131 max: 0
132 };
133
134 break;
135
136 case PLUSSIGN:
137 tokenizer.pos++;
138
139 range = {
140 min: 1,
141 max: 0
142 };
143
144 break;
145
146 case QUESTIONMARK:
147 tokenizer.pos++;
148
149 range = {
150 min: 0,
151 max: 1
152 };
153
154 break;
155
156 case NUMBERSIGN:
157 tokenizer.pos++;
158
159 comma = true;
160
161 if (tokenizer.charCode() === LEFTCURLYBRACKET) {
162 range = readMultiplierRange(tokenizer);
163 } else {
164 range = {
165 min: 1,
166 max: 0
167 };
168 }
169
170 break;
171
172 case LEFTCURLYBRACKET:
173 range = readMultiplierRange(tokenizer);
174 break;
175
176 default:
177 return null;
178 }
179
180 return {
181 type: 'Multiplier',
182 comma: comma,
183 min: range.min,
184 max: range.max,
185 term: null
186 };
187}
188
189function maybeMultiplied(tokenizer, node) {
190 var multiplier = readMultiplier(tokenizer);
191
192 if (multiplier !== null) {
193 multiplier.term = node;
194 return multiplier;
195 }
196
197 return node;
198}
199
200function maybeToken(tokenizer) {
201 var ch = tokenizer.peek();
202
203 if (ch === '') {
204 return null;
205 }
206
207 return {
208 type: 'Token',
209 value: ch
210 };
211}
212
213function readProperty(tokenizer) {
214 var name;
215
216 tokenizer.eat(LESSTHANSIGN);
217 tokenizer.eat(APOSTROPHE);
218
219 name = scanWord(tokenizer);
220
221 tokenizer.eat(APOSTROPHE);
222 tokenizer.eat(GREATERTHANSIGN);
223
224 return maybeMultiplied(tokenizer, {
225 type: 'Property',
226 name: name
227 });
228}
229
230// https://drafts.csswg.org/css-values-3/#numeric-ranges
231// 4.1. Range Restrictions and Range Definition Notation
232//
233// Range restrictions can be annotated in the numeric type notation using CSS bracketed
234// range notation—[min,max]—within the angle brackets, after the identifying keyword,
235// indicating a closed range between (and including) min and max.
236// For example, <integer [0, 10]> indicates an integer between 0 and 10, inclusive.
237function readTypeRange(tokenizer) {
238 // use null for Infinity to make AST format JSON serializable/deserializable
239 var min = null; // -Infinity
240 var max = null; // Infinity
241 var sign = 1;
242
243 tokenizer.eat(LEFTSQUAREBRACKET);
244
245 if (tokenizer.charCode() === HYPERMINUS) {
246 tokenizer.peek();
247 sign = -1;
248 }
249
250 if (sign == -1 && tokenizer.charCode() === INFINITY) {
251 tokenizer.peek();
252 } else {
253 min = sign * Number(scanNumber(tokenizer));
254 }
255
256 scanSpaces(tokenizer);
257 tokenizer.eat(COMMA);
258 scanSpaces(tokenizer);
259
260 if (tokenizer.charCode() === INFINITY) {
261 tokenizer.peek();
262 } else {
263 sign = 1;
264
265 if (tokenizer.charCode() === HYPERMINUS) {
266 tokenizer.peek();
267 sign = -1;
268 }
269
270 max = sign * Number(scanNumber(tokenizer));
271 }
272
273 tokenizer.eat(RIGHTSQUAREBRACKET);
274
275 // If no range is indicated, either by using the bracketed range notation
276 // or in the property description, then [−∞,∞] is assumed.
277 if (min === null && max === null) {
278 return null;
279 }
280
281 return {
282 type: 'Range',
283 min: min,
284 max: max
285 };
286}
287
288function readType(tokenizer) {
289 var name;
290 var opts = null;
291
292 tokenizer.eat(LESSTHANSIGN);
293 name = scanWord(tokenizer);
294
295 if (tokenizer.charCode() === LEFTPARENTHESIS &&
296 tokenizer.nextCharCode() === RIGHTPARENTHESIS) {
297 tokenizer.pos += 2;
298 name += '()';
299 }
300
301 if (tokenizer.charCodeAt(tokenizer.findWsEnd(tokenizer.pos)) === LEFTSQUAREBRACKET) {
302 scanSpaces(tokenizer);
303 opts = readTypeRange(tokenizer);
304 }
305
306 tokenizer.eat(GREATERTHANSIGN);
307
308 return maybeMultiplied(tokenizer, {
309 type: 'Type',
310 name: name,
311 opts: opts
312 });
313}
314
315function readKeywordOrFunction(tokenizer) {
316 var name;
317
318 name = scanWord(tokenizer);
319
320 if (tokenizer.charCode() === LEFTPARENTHESIS) {
321 tokenizer.pos++;
322
323 return {
324 type: 'Function',
325 name: name
326 };
327 }
328
329 return maybeMultiplied(tokenizer, {
330 type: 'Keyword',
331 name: name
332 });
333}
334
335function regroupTerms(terms, combinators) {
336 function createGroup(terms, combinator) {
337 return {
338 type: 'Group',
339 terms: terms,
340 combinator: combinator,
341 disallowEmpty: false,
342 explicit: false
343 };
344 }
345
346 combinators = Object.keys(combinators).sort(function(a, b) {
347 return COMBINATOR_PRECEDENCE[a] - COMBINATOR_PRECEDENCE[b];
348 });
349
350 while (combinators.length > 0) {
351 var combinator = combinators.shift();
352 for (var i = 0, subgroupStart = 0; i < terms.length; i++) {
353 var term = terms[i];
354 if (term.type === 'Combinator') {
355 if (term.value === combinator) {
356 if (subgroupStart === -1) {
357 subgroupStart = i - 1;
358 }
359 terms.splice(i, 1);
360 i--;
361 } else {
362 if (subgroupStart !== -1 && i - subgroupStart > 1) {
363 terms.splice(
364 subgroupStart,
365 i - subgroupStart,
366 createGroup(terms.slice(subgroupStart, i), combinator)
367 );
368 i = subgroupStart + 1;
369 }
370 subgroupStart = -1;
371 }
372 }
373 }
374
375 if (subgroupStart !== -1 && combinators.length) {
376 terms.splice(
377 subgroupStart,
378 i - subgroupStart,
379 createGroup(terms.slice(subgroupStart, i), combinator)
380 );
381 }
382 }
383
384 return combinator;
385}
386
387function readImplicitGroup(tokenizer) {
388 var terms = [];
389 var combinators = {};
390 var token;
391 var prevToken = null;
392 var prevTokenPos = tokenizer.pos;
393
394 while (token = peek(tokenizer)) {
395 if (token.type !== 'Spaces') {
396 if (token.type === 'Combinator') {
397 // check for combinator in group beginning and double combinator sequence
398 if (prevToken === null || prevToken.type === 'Combinator') {
399 tokenizer.pos = prevTokenPos;
400 tokenizer.error('Unexpected combinator');
401 }
402
403 combinators[token.value] = true;
404 } else if (prevToken !== null && prevToken.type !== 'Combinator') {
405 combinators[' '] = true; // a b
406 terms.push({
407 type: 'Combinator',
408 value: ' '
409 });
410 }
411
412 terms.push(token);
413 prevToken = token;
414 prevTokenPos = tokenizer.pos;
415 }
416 }
417
418 // check for combinator in group ending
419 if (prevToken !== null && prevToken.type === 'Combinator') {
420 tokenizer.pos -= prevTokenPos;
421 tokenizer.error('Unexpected combinator');
422 }
423
424 return {
425 type: 'Group',
426 terms: terms,
427 combinator: regroupTerms(terms, combinators) || ' ',
428 disallowEmpty: false,
429 explicit: false
430 };
431}
432
433function readGroup(tokenizer) {
434 var result;
435
436 tokenizer.eat(LEFTSQUAREBRACKET);
437 result = readImplicitGroup(tokenizer);
438 tokenizer.eat(RIGHTSQUAREBRACKET);
439
440 result.explicit = true;
441
442 if (tokenizer.charCode() === EXCLAMATIONMARK) {
443 tokenizer.pos++;
444 result.disallowEmpty = true;
445 }
446
447 return result;
448}
449
450function peek(tokenizer) {
451 var code = tokenizer.charCode();
452
453 if (code < 128 && NAME_CHAR[code] === 1) {
454 return readKeywordOrFunction(tokenizer);
455 }
456
457 switch (code) {
458 case RIGHTSQUAREBRACKET:
459 // don't eat, stop scan a group
460 break;
461
462 case LEFTSQUAREBRACKET:
463 return maybeMultiplied(tokenizer, readGroup(tokenizer));
464
465 case LESSTHANSIGN:
466 return tokenizer.nextCharCode() === APOSTROPHE
467 ? readProperty(tokenizer)
468 : readType(tokenizer);
469
470 case VERTICALLINE:
471 return {
472 type: 'Combinator',
473 value: tokenizer.substringToPos(
474 tokenizer.nextCharCode() === VERTICALLINE
475 ? tokenizer.pos + 2
476 : tokenizer.pos + 1
477 )
478 };
479
480 case AMPERSAND:
481 tokenizer.pos++;
482 tokenizer.eat(AMPERSAND);
483
484 return {
485 type: 'Combinator',
486 value: '&&'
487 };
488
489 case COMMA:
490 tokenizer.pos++;
491 return {
492 type: 'Comma'
493 };
494
495 case APOSTROPHE:
496 return maybeMultiplied(tokenizer, {
497 type: 'String',
498 value: scanString(tokenizer)
499 });
500
501 case SPACE:
502 case TAB:
503 case N:
504 case R:
505 case F:
506 return {
507 type: 'Spaces',
508 value: scanSpaces(tokenizer)
509 };
510
511 case COMMERCIALAT:
512 code = tokenizer.nextCharCode();
513
514 if (code < 128 && NAME_CHAR[code] === 1) {
515 tokenizer.pos++;
516 return {
517 type: 'AtKeyword',
518 name: scanWord(tokenizer)
519 };
520 }
521
522 return maybeToken(tokenizer);
523
524 case ASTERISK:
525 case PLUSSIGN:
526 case QUESTIONMARK:
527 case NUMBERSIGN:
528 case EXCLAMATIONMARK:
529 // prohibited tokens (used as a multiplier start)
530 break;
531
532 case LEFTCURLYBRACKET:
533 // LEFTCURLYBRACKET is allowed since mdn/data uses it w/o quoting
534 // check next char isn't a number, because it's likely a disjoined multiplier
535 code = tokenizer.nextCharCode();
536
537 if (code < 48 || code > 57) {
538 return maybeToken(tokenizer);
539 }
540
541 break;
542
543 default:
544 return maybeToken(tokenizer);
545 }
546}
547
548function parse(source) {
549 var tokenizer = new Tokenizer(source);
550 var result = readImplicitGroup(tokenizer);
551
552 if (tokenizer.pos !== source.length) {
553 tokenizer.error('Unexpected input');
554 }
555
556 // reduce redundant groups with single group term
557 if (result.terms.length === 1 && result.terms[0].type === 'Group') {
558 result = result.terms[0];
559 }
560
561 return result;
562}
563
564// warm up parse to elimitate code branches that never execute
565// fix soft deoptimizations (insufficient type feedback)
566parse('[a&&<b>#|<\'c\'>*||e() f{2} /,(% g#{1,2} h{2,})]!');
567
568module.exports = parse;
Note: See TracBrowser for help on using the repository browser.