diff --git a/lib/less/chunker.js b/lib/less/chunker.js new file mode 100644 index 000000000..0d1388201 --- /dev/null +++ b/lib/less/chunker.js @@ -0,0 +1,123 @@ +var LessError = require('./less-error.js'); + +// Split the input into chunks. +module.exports = function (parser, input, env) { + var len = input.length, level = 0, parenLevel = 0, + lastOpening, lastOpeningParen, lastMultiComment, lastMultiCommentEndBrace, + chunks = [], emitFrom = 0, + parserCurrentIndex, currentChunkStartIndex, cc, cc2, matched; + + function fail(msg, index) { + throw new(LessError)(parser, { + index: index || parserCurrentIndex, + type: 'Parse', + message: msg, + filename: env.currentFileInfo.filename + }, env); + } + + function emitChunk(force) { + var len = parserCurrentIndex - emitFrom; + if (((len < 512) && !force) || !len) { + return; + } + chunks.push(input.slice(emitFrom, parserCurrentIndex + 1)); + emitFrom = parserCurrentIndex + 1; + } + + for (parserCurrentIndex = 0; parserCurrentIndex < len; parserCurrentIndex++) { + cc = input.charCodeAt(parserCurrentIndex); + if (((cc >= 97) && (cc <= 122)) || (cc < 34)) { + // a-z or whitespace + continue; + } + + switch (cc) { + case 40: // ( + parenLevel++; + lastOpeningParen = parserCurrentIndex; + continue; + case 41: // ) + if (--parenLevel < 0) { + return fail("missing opening `(`"); + } + continue; + case 59: // ; + if (!parenLevel) { emitChunk(); } + continue; + case 123: // { + level++; + lastOpening = parserCurrentIndex; + continue; + case 125: // } + if (--level < 0) { + return fail("missing opening `{`"); + } + if (!level && !parenLevel) { emitChunk(); } + continue; + case 92: // \ + if (parserCurrentIndex < len - 1) { parserCurrentIndex++; continue; } + return fail("unescaped `\\`"); + case 34: + case 39: + case 96: // ", ' and ` + matched = 0; + currentChunkStartIndex = parserCurrentIndex; + for (parserCurrentIndex = parserCurrentIndex + 1; parserCurrentIndex < len; parserCurrentIndex++) { + cc2 = input.charCodeAt(parserCurrentIndex); + if (cc2 > 96) { continue; } + if (cc2 == cc) { matched = 1; break; } + if (cc2 == 92) { // \ + if (parserCurrentIndex == len - 1) { + return fail("unescaped `\\`"); + } + parserCurrentIndex++; + } + } + if (matched) { continue; } + return fail("unmatched `" + String.fromCharCode(cc) + "`", currentChunkStartIndex); + case 47: // /, check for comment + if (parenLevel || (parserCurrentIndex == len - 1)) { continue; } + cc2 = input.charCodeAt(parserCurrentIndex + 1); + if (cc2 == 47) { + // //, find lnfeed + for (parserCurrentIndex = parserCurrentIndex + 2; parserCurrentIndex < len; parserCurrentIndex++) { + cc2 = input.charCodeAt(parserCurrentIndex); + if ((cc2 <= 13) && ((cc2 == 10) || (cc2 == 13))) { break; } + } + } else if (cc2 == 42) { + // /*, find */ + lastMultiComment = currentChunkStartIndex = parserCurrentIndex; + for (parserCurrentIndex = parserCurrentIndex + 2; parserCurrentIndex < len - 1; parserCurrentIndex++) { + cc2 = input.charCodeAt(parserCurrentIndex); + if (cc2 == 125) { lastMultiCommentEndBrace = parserCurrentIndex; } + if (cc2 != 42) { continue; } + if (input.charCodeAt(parserCurrentIndex + 1) == 47) { break; } + } + if (parserCurrentIndex == len - 1) { + return fail("missing closing `*/`", currentChunkStartIndex); + } + parserCurrentIndex++; + } + continue; + case 42: // *, check for unmatched */ + if ((parserCurrentIndex < len - 1) && (input.charCodeAt(parserCurrentIndex + 1) == 47)) { + return fail("unmatched `/*`"); + } + continue; + } + } + + if (level !== 0) { + if ((lastMultiComment > lastOpening) && (lastMultiCommentEndBrace > lastMultiComment)) { + return fail("missing closing `}` or `*/`", lastOpening); + } else { + return fail("missing closing `}`", lastOpening); + } + } else if (parenLevel !== 0) { + return fail("missing closing `)`", lastOpeningParen); + } + + emitChunk(true); + return chunks; +}; \ No newline at end of file diff --git a/lib/less/less-error.js b/lib/less/less-error.js new file mode 100644 index 000000000..750bad806 --- /dev/null +++ b/lib/less/less-error.js @@ -0,0 +1,27 @@ + +var LessError = module.exports = function LessError(parser, e, env) { + var input = parser.getInput(e, env), + loc = parser.getLocation(e.index, input), + line = loc.line, + col = loc.column, + callLine = e.call && parser.getLocation(e.call, input).line, + lines = input.split('\n'); + + this.type = e.type || 'Syntax'; + this.message = e.message; + this.filename = e.filename || env.currentFileInfo.filename; + this.index = e.index; + this.line = typeof(line) === 'number' ? line + 1 : null; + this.callLine = callLine + 1; + this.callExtract = lines[callLine]; + this.stack = e.stack; + this.column = col; + this.extract = [ + lines[line - 1], + lines[line], + lines[line + 1] + ]; +}; + +LessError.prototype = new Error(); +LessError.prototype.constructor = LessError; diff --git a/lib/less/parser.js b/lib/less/parser.js index b9be06c5d..735102f5c 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1,3 +1,6 @@ +var chunker = require('./chunker.js'); +var LessError = require('./less-error.js'); + module.exports = function(less, tree, visitor) { // // less.js - parser @@ -320,33 +323,6 @@ var Parser = function Parser(env) { }; } - function LessError(e, env) { - var input = getInput(e, env), - loc = getLocation(e.index, input), - line = loc.line, - col = loc.column, - callLine = e.call && getLocation(e.call, input).line, - lines = input.split('\n'); - - this.type = e.type || 'Syntax'; - this.message = e.message; - this.filename = e.filename || env.currentFileInfo.filename; - this.index = e.index; - this.line = typeof(line) === 'number' ? line + 1 : null; - this.callLine = callLine + 1; - this.callExtract = lines[callLine]; - this.stack = e.stack; - this.column = col; - this.extract = [ - lines[line - 1], - lines[line], - lines[line + 1] - ]; - } - - LessError.prototype = new Error(); - LessError.prototype.constructor = LessError; - this.env = env = env || {}; // @@ -379,130 +355,10 @@ var Parser = function Parser(env) { input = str = preText + str.replace(/^\uFEFF/, '') + modifyVars; parser.imports.contents[env.currentFileInfo.filename] = str; - // Split the input into chunks. - chunks = (function (input) { - var len = input.length, level = 0, parenLevel = 0, - lastOpening, lastOpeningParen, lastMultiComment, lastMultiCommentEndBrace, - chunks = [], emitFrom = 0, - parserCurrentIndex, currentChunkStartIndex, cc, cc2, matched; - - function fail(msg, index) { - error = new(LessError)({ - index: index || parserCurrentIndex, - type: 'Parse', - message: msg, - filename: env.currentFileInfo.filename - }, env); - } - - function emitChunk(force) { - var len = parserCurrentIndex - emitFrom; - if (((len < 512) && !force) || !len) { - return; - } - chunks.push(input.slice(emitFrom, parserCurrentIndex + 1)); - emitFrom = parserCurrentIndex + 1; - } - - for (parserCurrentIndex = 0; parserCurrentIndex < len; parserCurrentIndex++) { - cc = input.charCodeAt(parserCurrentIndex); - if (((cc >= 97) && (cc <= 122)) || (cc < 34)) { - // a-z or whitespace - continue; - } - - switch (cc) { - case 40: // ( - parenLevel++; - lastOpeningParen = parserCurrentIndex; - continue; - case 41: // ) - if (--parenLevel < 0) { - return fail("missing opening `(`"); - } - continue; - case 59: // ; - if (!parenLevel) { emitChunk(); } - continue; - case 123: // { - level++; - lastOpening = parserCurrentIndex; - continue; - case 125: // } - if (--level < 0) { - return fail("missing opening `{`"); - } - if (!level && !parenLevel) { emitChunk(); } - continue; - case 92: // \ - if (parserCurrentIndex < len - 1) { parserCurrentIndex++; continue; } - return fail("unescaped `\\`"); - case 34: - case 39: - case 96: // ", ' and ` - matched = 0; - currentChunkStartIndex = parserCurrentIndex; - for (parserCurrentIndex = parserCurrentIndex + 1; parserCurrentIndex < len; parserCurrentIndex++) { - cc2 = input.charCodeAt(parserCurrentIndex); - if (cc2 > 96) { continue; } - if (cc2 == cc) { matched = 1; break; } - if (cc2 == 92) { // \ - if (parserCurrentIndex == len - 1) { - return fail("unescaped `\\`"); - } - parserCurrentIndex++; - } - } - if (matched) { continue; } - return fail("unmatched `" + String.fromCharCode(cc) + "`", currentChunkStartIndex); - case 47: // /, check for comment - if (parenLevel || (parserCurrentIndex == len - 1)) { continue; } - cc2 = input.charCodeAt(parserCurrentIndex + 1); - if (cc2 == 47) { - // //, find lnfeed - for (parserCurrentIndex = parserCurrentIndex + 2; parserCurrentIndex < len; parserCurrentIndex++) { - cc2 = input.charCodeAt(parserCurrentIndex); - if ((cc2 <= 13) && ((cc2 == 10) || (cc2 == 13))) { break; } - } - } else if (cc2 == 42) { - // /*, find */ - lastMultiComment = currentChunkStartIndex = parserCurrentIndex; - for (parserCurrentIndex = parserCurrentIndex + 2; parserCurrentIndex < len - 1; parserCurrentIndex++) { - cc2 = input.charCodeAt(parserCurrentIndex); - if (cc2 == 125) { lastMultiCommentEndBrace = parserCurrentIndex; } - if (cc2 != 42) { continue; } - if (input.charCodeAt(parserCurrentIndex + 1) == 47) { break; } - } - if (parserCurrentIndex == len - 1) { - return fail("missing closing `*/`", currentChunkStartIndex); - } - parserCurrentIndex++; - } - continue; - case 42: // *, check for unmatched */ - if ((parserCurrentIndex < len - 1) && (input.charCodeAt(parserCurrentIndex + 1) == 47)) { - return fail("unmatched `/*`"); - } - continue; - } - } - - if (level !== 0) { - if ((lastMultiComment > lastOpening) && (lastMultiCommentEndBrace > lastMultiComment)) { - return fail("missing closing `}` or `*/`", lastOpening); - } else { - return fail("missing closing `}`", lastOpening); - } - } else if (parenLevel !== 0) { - return fail("missing closing `)`", lastOpeningParen); - } - - emitChunk(true); - return chunks; - })(str); - - if (error) { - return callback(new(LessError)(error, env)); + try { + chunks = chunker(parser, str, env); + } catch (ex) { + return callback(new LessError(parser, ex, env)); } current = chunks[0]; @@ -516,7 +372,7 @@ var Parser = function Parser(env) { root.root = true; root.firstRoot = true; } catch (e) { - return callback(new(LessError)(e, env)); + return callback(new LessError(parser, e, env)); } root.toCSS = (function (evaluate) { @@ -609,7 +465,7 @@ var Parser = function Parser(env) { strictUnits: Boolean(options.strictUnits), numPrecision: 8}); } catch (e) { - throw new(LessError)(e, env); + throw new LessError(parser, e, env); } var CleanCSS = less.environment.getCleanCSS(); @@ -668,7 +524,7 @@ var Parser = function Parser(env) { if (e) { if (!(e instanceof LessError)) { - e = new(LessError)(e, env); + e = new LessError(parser, e, env); } return callback(e); @@ -2053,6 +1909,10 @@ var Parser = function Parser(env) { } } }; + + parser.getInput = getInput; + parser.getLocation = getLocation; + return parser; }; Parser.serializeVars = function(vars) {