var chunker = require('./chunker');
module.exports = function() {
var input, // LeSS input string j, // current chunk saveStack = [], // holds state for backtracking furthest, // furthest index the parser has gone to furthestPossibleErrorMessage,// if this is furthest we got to, this is the probably cause chunks, // chunkified input current, // current chunk currentPos, // index of current chunk, in `input` parserInput = {}; var CHARCODE_SPACE = 32, CHARCODE_TAB = 9, CHARCODE_LF = 10, CHARCODE_CR = 13, CHARCODE_PLUS = 43, CHARCODE_COMMA = 44, CHARCODE_FORWARD_SLASH = 47, CHARCODE_9 = 57; function skipWhitespace(length) { var oldi = parserInput.i, oldj = j, curr = parserInput.i - currentPos, endIndex = parserInput.i + current.length - curr, mem = (parserInput.i += length), inp = input, c, nextChar, comment; for (; parserInput.i < endIndex; parserInput.i++) { c = inp.charCodeAt(parserInput.i); if (parserInput.autoCommentAbsorb && c === CHARCODE_FORWARD_SLASH) { nextChar = inp.charAt(parserInput.i + 1); if (nextChar === '/') { comment = {index: parserInput.i, isLineComment: true}; var nextNewLine = inp.indexOf("\n", parserInput.i + 2); if (nextNewLine < 0) { nextNewLine = endIndex; } parserInput.i = nextNewLine; comment.text = inp.substr(comment.i, parserInput.i - comment.i); parserInput.commentStore.push(comment); continue; } else if (nextChar === '*') { var nextStarSlash = inp.indexOf("*/", parserInput.i + 2); if (nextStarSlash >= 0) { comment = { index: parserInput.i, text: inp.substr(parserInput.i, nextStarSlash + 2 - parserInput.i), isLineComment: false }; parserInput.i += comment.text.length - 1; parserInput.commentStore.push(comment); continue; } } break; } if ((c !== CHARCODE_SPACE) && (c !== CHARCODE_LF) && (c !== CHARCODE_TAB) && (c !== CHARCODE_CR)) { break; } } current = current.slice(length + parserInput.i - mem + curr); currentPos = parserInput.i; if (!current.length) { if (j < chunks.length - 1) { current = chunks[++j]; skipWhitespace(0); // skip space at the beginning of a chunk return true; // things changed } parserInput.finished = true; } return oldi !== parserInput.i || oldj !== j; } parserInput.save = function() { currentPos = parserInput.i; saveStack.push( { current: current, i: parserInput.i, j: j }); }; parserInput.restore = function(possibleErrorMessage) { if (parserInput.i > furthest || (parserInput.i === furthest && possibleErrorMessage && !furthestPossibleErrorMessage)) { furthest = parserInput.i; furthestPossibleErrorMessage = possibleErrorMessage; } var state = saveStack.pop(); current = state.current; currentPos = parserInput.i = state.i; j = state.j; }; parserInput.forget = function() { saveStack.pop(); }; parserInput.isWhitespace = function (offset) { var pos = parserInput.i + (offset || 0), code = input.charCodeAt(pos); return (code === CHARCODE_SPACE || code === CHARCODE_CR || code === CHARCODE_TAB || code === CHARCODE_LF); }; // Specialization of $(tok) parserInput.$re = function(tok) { if (parserInput.i > currentPos) { current = current.slice(parserInput.i - currentPos); currentPos = parserInput.i; } var m = tok.exec(current); if (!m) { return null; } skipWhitespace(m[0].length); if (typeof m === "string") { return m; } return m.length === 1 ? m[0] : m; }; parserInput.$char = function(tok) { if (input.charAt(parserInput.i) !== tok) { return null; } skipWhitespace(1); return tok; }; parserInput.$str = function(tok) { var tokLength = tok.length; // https://jsperf.com/string-startswith/21 for (var i = 0; i < tokLength; i++) { if (input.charAt(parserInput.i + i) !== tok.charAt(i)) { return null; } } skipWhitespace(tokLength); return tok; }; parserInput.$quoted = function() { var startChar = input.charAt(parserInput.i); if (startChar !== "'" && startChar !== '"') { return; } var length = input.length, currentPosition = parserInput.i; for (var i = 1; i + currentPosition < length; i++) { var nextChar = input.charAt(i + currentPosition); switch(nextChar) { case "\\": i++; continue; case "\r": case "\n": break; case startChar: var str = input.substr(currentPosition, i + 1); skipWhitespace(i + 1); return str; default: } } return null; }; parserInput.autoCommentAbsorb = true; parserInput.commentStore = []; parserInput.finished = false; // Same as $(), but don't change the state of the parser, // just return the match. parserInput.peek = function(tok) { if (typeof tok === 'string') { // https://jsperf.com/string-startswith/21 for (var i = 0; i < tok.length; i++) { if (input.charAt(parserInput.i + i) !== tok.charAt(i)) { return false; } } return true; } else { return tok.test(current); } }; // Specialization of peek() // TODO remove or change some currentChar calls to peekChar parserInput.peekChar = function(tok) { return input.charAt(parserInput.i) === tok; }; parserInput.currentChar = function() { return input.charAt(parserInput.i); }; parserInput.getInput = function() { return input; }; parserInput.peekNotNumeric = function() { var c = input.charCodeAt(parserInput.i); //Is the first char of the dimension 0-9, '.', '+' or '-' return (c > CHARCODE_9 || c < CHARCODE_PLUS) || c === CHARCODE_FORWARD_SLASH || c === CHARCODE_COMMA; }; parserInput.start = function(str, chunkInput, failFunction) { input = str; parserInput.i = j = currentPos = furthest = 0; // chunking apparantly makes things quicker (but my tests indicate // it might actually make things slower in node at least) // and it is a non-perfect parse - it can't recognise // unquoted urls, meaning it can't distinguish comments // meaning comments with quotes or {}() in them get 'counted' // and then lead to parse errors. // In addition if the chunking chunks in the wrong place we might // not be able to parse a parser statement in one go // this is officially deprecated but can be switched on via an option // in the case it causes too much performance issues. if (chunkInput) { chunks = chunker(str, failFunction); } else { chunks = [str]; } current = chunks[0]; skipWhitespace(0); }; parserInput.end = function() { var message, isFinished = parserInput.i >= input.length; if (parserInput.i < furthest) { message = furthestPossibleErrorMessage; parserInput.i = furthest; } return { isFinished: isFinished, furthest: parserInput.i, furthestPossibleErrorMessage: message, furthestReachedEnd: parserInput.i >= input.length - 1, furthestChar: input[parserInput.i] }; }; return parserInput;
};