forked from fb55/nth-check
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(parse): Replace regex with hand-rolled parser (fb55#9)
Based on 9894c1d.
- Loading branch information
Showing
1 changed file
with
91 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,94 @@ | ||
module.exports = parse; | ||
|
||
//following http://www.w3.org/TR/css3-selectors/#nth-child-pseudo | ||
|
||
//[ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | ||
var re_nthElement = /^([+\-]?\d*n)?\s*(?:([+\-]?)\s*(\d+))?$/; | ||
|
||
/* | ||
parses a nth-check formula, returns an array of two numbers | ||
*/ | ||
function parse(formula){ | ||
formula = formula.trim().toLowerCase(); | ||
|
||
if(formula === "even"){ | ||
return [2, 0]; | ||
} else if(formula === "odd"){ | ||
return [2, 1]; | ||
} else { | ||
var parsed = formula.match(re_nthElement); | ||
|
||
if(!parsed){ | ||
throw new SyntaxError("n-th rule couldn't be parsed ('" + formula + "')"); | ||
} | ||
|
||
var a; | ||
|
||
if(parsed[1]){ | ||
a = parseInt(parsed[1], 10); | ||
if(isNaN(a)){ | ||
if(parsed[1].charAt(0) === "-") a = -1; | ||
else a = 1; | ||
} | ||
} else a = 0; | ||
|
||
return [ | ||
a, | ||
parsed[3] ? parseInt((parsed[2] || "") + parsed[3], 10) : 0 | ||
]; | ||
} | ||
// Following http://www.w3.org/TR/css3-selectors/#nth-child-pseudo | ||
|
||
// Whitespace as per https://www.w3.org/TR/selectors-3/#lex is " \t\r\n\f" | ||
const whitespace = new Set([9, 10, 12, 13, 32]); | ||
const ZERO = "0".charCodeAt(0); | ||
const NINE = "9".charCodeAt(0); | ||
|
||
/** | ||
* Parses an expression. | ||
* | ||
* @throws An `Error` if parsing fails. | ||
* @returns An array containing the integer step size and the integer offset of the nth rule. | ||
* @example nthCheck.parse("2n+3"); // returns [2, 3] | ||
*/ | ||
function parse(formula) { | ||
formula = formula.trim().toLowerCase(); | ||
|
||
if (formula === "even") { | ||
return [2, 0]; | ||
} else if (formula === "odd") { | ||
return [2, 1]; | ||
} | ||
|
||
// Parse [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | ||
|
||
let idx = 0; | ||
|
||
let a = 0; | ||
let sign = readSign(); | ||
let number = readNumber(); | ||
|
||
if (idx < formula.length && formula.charAt(idx) === "n") { | ||
idx++; | ||
a = sign * (number ?? 1); | ||
|
||
skipWhitespace(); | ||
|
||
if (idx < formula.length) { | ||
sign = readSign(); | ||
skipWhitespace(); | ||
number = readNumber(); | ||
} else { | ||
sign = number = 0; | ||
} | ||
} | ||
|
||
// Throw if there is anything else | ||
if (number === null || idx < formula.length) { | ||
throw new Error(`n-th rule couldn't be parsed ('${formula}')`); | ||
} | ||
|
||
return [a, sign * number]; | ||
|
||
function readSign() { | ||
if (formula.charAt(idx) === "-") { | ||
idx++; | ||
return -1; | ||
} | ||
|
||
if (formula.charAt(idx) === "+") { | ||
idx++; | ||
} | ||
|
||
return 1; | ||
} | ||
|
||
function readNumber() { | ||
const start = idx; | ||
let value = 0; | ||
|
||
while ( | ||
idx < formula.length && | ||
formula.charCodeAt(idx) >= ZERO && | ||
formula.charCodeAt(idx) <= NINE | ||
) { | ||
value = value * 10 + (formula.charCodeAt(idx) - ZERO); | ||
idx++; | ||
} | ||
|
||
// Return `null` if we didn't read anything. | ||
return idx === start ? null : value; | ||
} | ||
|
||
function skipWhitespace() { | ||
while ( | ||
idx < formula.length && | ||
whitespace.has(formula.charCodeAt(idx)) | ||
) { | ||
idx++; | ||
} | ||
} | ||
} |