From 52dc714927182d8035b0488ecfa6f7c08a0222ca Mon Sep 17 00:00:00 2001 From: fredburger Date: Wed, 30 Oct 2013 08:11:22 +0100 Subject: [PATCH] New input chunker in parser.js. #1615 --- lib/less/parser.js | 206 +++++++++++------- .../errors/parse-error-missing-bracket.txt | 4 +- 2 files changed, 125 insertions(+), 85 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 732ba40a..1f5c445a 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -359,96 +359,136 @@ less.Parser = function Parser(env) { parser.imports.contents[env.currentFileInfo.filename] = str; // Split the input into chunks. - chunks = (function (chunks, input) { - var j = 0, - skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g, - comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g, - string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g, - level = 0, - match, - chunk = chunks[0], - inputLen = input.length, - inParam; + chunks = (function (input) { + var checkParenLevel = false; // todo: enable? - for (var i = 0, c, cc; i < inputLen;) { - skip.lastIndex = i; - if (match = skip.exec(input)) { - if (match.index === i) { - i += match[0].length; - chunk.push(match[0]); - } - } - c = input.charAt(i); - comment.lastIndex = string.lastIndex = i; + var len = input.length, level = 0, parenLevel = 0, + lastOpening, lastClosing, lastMultiComment, lastMultiCommentEndBrace, + chunks = [], emitFrom = 0, + k, ko, cc, cc2, matched; - if (match = string.exec(input)) { - if (match.index === i) { - i += match[0].length; - chunk.push(match[0]); - continue; - } - } - - if (!inParam && c === '/') { - cc = input.charAt(i + 1); - if (cc === '/' || cc === '*') { - if (match = comment.exec(input)) { - if (match.index === i) { - i += match[0].length; - chunk.push(match[0]); - continue; - } - } - } - } - - switch (c) { - case '{': - if (!inParam) { - level++; - chunk.push(c); - break; - } - /* falls through */ - case '}': - if (!inParam) { - level--; - chunk.push(c); - chunks[++j] = chunk = []; - break; - } - /* falls through */ - case '(': - if (!inParam) { - inParam = true; - chunk.push(c); - break; - } - /* falls through */ - case ')': - if (inParam) { - inParam = false; - chunk.push(c); - break; - } - /* falls through */ - default: - chunk.push(c); - } - - i++; - } - if (level !== 0) { + function fail(msg, index) { + index = index || k; error = new(LessError)({ - index: i-1, + index: index || k, type: 'Parse', - message: (level > 0) ? "missing closing `}`" : "missing opening `{`", + message: msg, filename: env.currentFileInfo.filename }, env); } - return chunks.map(function (c) { return c.join(''); }); - })([[]], str); + function emitChunk(force) { + var len = k - emitFrom; + if (((len < 512) && !force) || !len) { + return; + } + chunks.push(input.slice(emitFrom, k + 1)); + emitFrom = k + 1; + } + + for (k = 0; k < len; k++) { + cc = input.charCodeAt(k); + if (((cc >= 97) && (cc <= 122)) || (cc < 34)) { + // a-z or whitespace + continue; + } + + switch (cc) { + case 40: // ( + parenLevel++; continue; + case 41: // ) + parenLevel--; continue; + case 59: // ; + if (!parenLevel) { emitChunk(); } + continue; + case 123: // { + level++; lastOpening = k; continue; + case 125: // } + level--; lastClosing = k; + if (!level) { emitChunk(); } + continue; + case 92: // \ + if (k < len - 1) { k++; continue; } + fail("unescaped '\\'"); + break; + case 34: + case 39: + case 96: // ", ' and ` + matched = 0; + ko = k; + for (k = k + 1; k < len; k++) { + cc2 = input.charCodeAt(k); + if (cc2 > 96) { continue; } + if (cc2 == cc) { matched = 1; break; } + if (cc2 == 92) { // \ + if (k == len - 1) { + fail("unescaped '\\'"); + break; + } + k++; + } + } + if (matched) { continue; } + if (!error) { + fail("unmatched '" + String.fromCharCode(cc) + "'", ko); + } + break; + case 47: // /, check for comment + if (parenLevel || (k == len - 1)) { continue; } + cc2 = input.charCodeAt(k + 1); + if (cc2 == 47) { + // //, find lnfeed + for (k = k + 2; k < len; k++) { + cc2 = input.charCodeAt(k); + if ((cc2 <= 13) && ((cc2 == 10) || (cc2 == 13))) { break; } + } + } else if (cc2 == 42) { + // /*, find */ + lastMultiComment = ko = k; + for (k = k + 2; k < len - 1; k++) { + cc2 = input.charCodeAt(k); + if (cc2 == 125) { lastMultiCommentEndBrace = k; } + if (cc2 != 42) { continue; } + if (input.charCodeAt(k + 1) == 47) { break; } + } + if (k == len - 1) { + fail("missing closing `*/`", ko); + } + } + continue; + case 42: // *, check for unmatched */ + if ((k < len - 1) && (input.charCodeAt(k + 1) == 47)) { + fail("unmatched `/*`"); + break; + } + continue; + default: + continue; + } + + // only reaches here on error + break; + } + + if (!error) { + if (level !== 0) { + if (level > 0) { + if ((lastMultiComment > lastOpening) && (lastMultiCommentEndBrace > lastMultiComment)) { + fail("missing closing `}` or `*/`", lastOpening); + } else { + fail("missing closing `}`", lastOpening); + } + } else { + fail("missing opening `{`", lastClosing); + } + } else if (checkParenLevel && (parenLevel !== 0)) { + fail((parenLevel > 0) ? "missing closing `)`" : "missing opening `(`"); + } + } + + emitChunk(true); + return chunks; + })(str); current = chunks[0]; @@ -938,7 +978,7 @@ less.Parser = function Parser(env) { option = option && option[1]; extend = new(tree.Extend)(new(tree.Selector)(elements), option, index); - if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; } + if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; } } while($char(",")); diff --git a/test/less/errors/parse-error-missing-bracket.txt b/test/less/errors/parse-error-missing-bracket.txt index 0005f464..7db2716e 100644 --- a/test/less/errors/parse-error-missing-bracket.txt +++ b/test/less/errors/parse-error-missing-bracket.txt @@ -1,3 +1,3 @@ -ParseError: missing closing `}` in {path}parse-error-missing-bracket.less on line 3, column 1: +ParseError: missing closing `}` in {path}parse-error-missing-bracket.less on line 1, column 6: +1 body { 2 background-color: #fff; -3