From 843aeffa41741c7aa19ed133cce760d8cac7a243 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 25 Jun 2010 03:03:37 -0400 Subject: [PATCH] slice parsing --- lib/less/parser.js | 139 +++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 55 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 3cc0af4..6f09f9c 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -50,6 +50,9 @@ less.Parser = function Parser(env) { var input, // LeSS input string i, // current index in `input` j, // current chunk + temp, + memo, + chunk, furthest, // furthest index the parser has gone to chunks, // chunkified input current, // index of current chunk, in `input` @@ -84,11 +87,27 @@ less.Parser = function Parser(env) { } }; + function save(){ + temp = chunk; + memo = i; + current = i; + } + function restore() { + chunks[j] = chunk = temp; + i = memo; + current = i; + } + function sync() { + if (i > current) { + chunks[j] = chunk = chunk.slice(i - current); + current = i; + } + } // // Parse from a token, regexp or string, and move forward if match // function $(tok) { - var match, args, length, c, index, endIndex; + var match, args, length, c, index, endIndex, k; // // Non-terminal @@ -104,6 +123,7 @@ less.Parser = function Parser(env) { } else if (typeof(tok) === 'string') { match = input.charAt(i) === tok ? tok : null; length = 1; + sync (); // 1. We move to the next chunk, if necessary. // 2. Set the `lastIndex` to be relative @@ -114,14 +134,11 @@ less.Parser = function Parser(env) { // index, which we stored in [2]. // } else { - if (i >= current + chunks[j].length && - j < chunks.length - 1) { // 1. - current += chunks[j++].length; - } - tok.lastIndex = index = i - current; // 2. - match = tok.exec(chunks[j]); + sync (); - if (match && (match.index === index)) { // 3. + match = tok.exec(chunk); + + if (match) { // 3. length = match[0].length; } else { return } } @@ -133,13 +150,18 @@ less.Parser = function Parser(env) { // if (match) { i += length; - endIndex = current + chunks[j].length; + var mem = i; + endIndex = i + chunk.length - length; - while (i <= endIndex) { + while (i < endIndex) { c = input.charCodeAt(i); if (! (c === 32 || c === 10 || c === 9)) { break } i++; } + chunks[j] = chunk = chunk.slice(length + (i - mem)); + current = i; + + if (chunk.length === 0 && j < chunks.length - 1) { chunk = chunks[++j] } if(typeof(match) === 'string') { return match; @@ -152,21 +174,25 @@ less.Parser = function Parser(env) { // Same as $(), but don't change the state of the parser, // just return the match. function peek(tok) { - var match, index; - if (typeof(tok) === 'string') { return input.charAt(i) === tok; } else { - index = i - current; - tok.lastIndex = index; - - if ((match = tok.exec(chunks[j])) && - (match.index === index)) { - return match; + if (tok.test(chunk)) { + return true; + } else { + return false; } } } + function exec(tok) { + var match; + + if ((match = tok.exec(chunks[j]))) { + return match; + } + } + this.env = env || {}; // The optimization level dictates the thoroughness of the parser, @@ -246,6 +272,7 @@ less.Parser = function Parser(env) { chunks = [input]; } inputLength = input.length; + chunk = chunks[0]; // Start with the primary rule. // The whole syntax tree is held under a Ruleset node, @@ -387,7 +414,7 @@ less.Parser = function Parser(env) { while (node = $(this.mixin.definition) || $(this.rule) || $(this.ruleset) || $(this.mixin.call) || $(this.comment) || - $(/[\n\s]+/g) || $(this.directive)) { + $(/^[\n\s]+/) || $(this.directive)) { root.push(node); } return root; @@ -402,8 +429,8 @@ less.Parser = function Parser(env) { if (input.charAt(i) !== '/') return; if (input.charAt(i + 1) === '/') { - return new(tree.Comment)($(/\/\/.*/g), true); - } else if (comment = $(/\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/g)) { + return new(tree.Comment)($(/^\/\/.*/), true); + } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) { return new(tree.Comment)(comment); } }, @@ -421,7 +448,7 @@ less.Parser = function Parser(env) { var str; if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return; - if (str = $(/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/g)) { + if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) { return new(tree.Quoted)(str[0], str[1] || str[2]); } }, @@ -433,7 +460,7 @@ less.Parser = function Parser(env) { // keyword: function () { var k; - if (k = $(/[A-Za-z-]+/g)) { return new(tree.Keyword)(k) } + if (k = $(/^[A-Za-z-]+/)) { return new(tree.Keyword)(k) } }, // @@ -449,7 +476,7 @@ less.Parser = function Parser(env) { call: function () { var name, args; - if (! (name = $(/([\w-]+|%)\(/g))) return; + if (! (name = $(/^([\w-]+|%)\(/))) return; if (name[1].toLowerCase() === 'alpha') { return $(this.alpha) } @@ -484,8 +511,8 @@ less.Parser = function Parser(env) { url: function () { var value; - if (input.charAt(i) !== 'u' || !$(/url\(/g)) return; - value = $(this.entities.quoted) || $(/[-\w%@$\/.&=:;#+?]+/g); + if (input.charAt(i) !== 'u' || !$(/^url\(/)) return; + value = $(this.entities.quoted) || $(/^[-\w%@$\/.&=:;#+?]+/); if (! $(')')) throw new(Error)("missing closing ) for url()"); return new(tree.URL)(value.value ? value : new(tree.Anonymous)(value)); @@ -502,7 +529,7 @@ less.Parser = function Parser(env) { variable: function () { var name, index = i; - if (input.charAt(i) === '@' && (name = $(/@[\w-]+/g))) { + if (input.charAt(i) === '@' && (name = $(/^@[\w-]+/))) { return new(tree.Variable)(name, index); } }, @@ -517,7 +544,7 @@ less.Parser = function Parser(env) { color: function () { var rgb; - if (input.charAt(i) === '#' && (rgb = $(/#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/g))) { + if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) { return new(tree.Color)(rgb[1]); } }, @@ -531,7 +558,7 @@ less.Parser = function Parser(env) { var value, c = input.charCodeAt(i); if ((c > 57 || c < 45) || c === 47) return; - if (value = $(/(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm)?/g)) { + if (value = $(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm)?/)) { return new(tree.Dimension)(value[1], value[2]); } } @@ -545,7 +572,7 @@ less.Parser = function Parser(env) { variable: function () { var name; - if (input.charAt(i) === '@' && (name = $(/(@[\w-]+)\s*:/g))) { return name[1] } + if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] } }, // @@ -558,7 +585,7 @@ less.Parser = function Parser(env) { shorthand: function () { var a, b; - if (! peek(/[@\w.-]+\/[@\w.-]+/g)) return; + if (! peek(/^[@\w.-]+\/[@\w.-]+/)) return; if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) { return new(tree.Shorthand)(a, b); @@ -585,7 +612,7 @@ less.Parser = function Parser(env) { if (s !== '.' && s !== '#') { return } - while (e = $(/[#.][\w-]+/g)) { + while (e = $(/^[#.][\w-]+/)) { elements.push(new(tree.Element)(c, e)); c = $('>'); } @@ -618,12 +645,13 @@ less.Parser = function Parser(env) { definition: function () { var name, params = [], match, ruleset, param, value; - if (input.charAt(i) !== '.' || peek(/[^{]*(;|})/g)) return; + if (input.charAt(i) !== '.' || peek(/^[^{]*(;|})/)) return; - if (match = $(/([#.][\w-]+)\s*\(/g)) { + + if (match = $(/^([#.][\w-]+)\s*\(/)) { name = match[1]; - while (param = $(/@[\w-]+/g) || $(this.entities.literal) + while (param = $(/^@[\w-]+/) || $(this.entities.literal) || $(this.entities.keyword)) { // Variable if (param[0] === '@') { @@ -678,8 +706,8 @@ less.Parser = function Parser(env) { alpha: function () { var value; - if (! $(/opacity=/gi)) return; - if (value = $(/\d+/g) || $(this.entities.variable)) { + if (! $(/^opacity=/i)) return; + if (value = $(/^\d+/) || $(this.entities.variable)) { if (! $(')')) throw new(Error)("missing closing ) for alpha()"); return new(tree.Alpha)(value); } @@ -701,7 +729,7 @@ less.Parser = function Parser(env) { var e, t; c = $(this.combinator); - e = $(/[.#:]?[\w-]+/g) || $('*') || $(this.attribute) || $(/\([^)@]+\)/g); + e = $(/^[.#:]?[\w-]+/) || $('*') || $(this.attribute) || $(/^\([^)@]+\)/); if (e) { return new(tree.Element)(c, e) } }, @@ -753,16 +781,16 @@ less.Parser = function Parser(env) { if (elements.length > 0) { return new(tree.Selector)(elements) } }, tag: function () { - return $(/[a-zA-Z][a-zA-Z-]*[0-9]?/g) || $('*'); + return $(/^[a-zA-Z][a-zA-Z-]*[0-9]?/) || $('*'); }, attribute: function () { var attr = '', key, val, op; if (! $('[')) return; - if (key = $(/[a-z-]+/g) || $(this.entities.quoted)) { - if ((op = $(/[|~*$^]?=/g)) && - (val = $(this.entities.quoted) || $(/[\w-]+/g))) { + if (key = $(/^[a-z-]+/) || $(this.entities.quoted)) { + if ((op = $(/^[|~*$^]?=/)) && + (val = $(this.entities.quoted) || $(/^[\w-]+/))) { attr = [key, op, val.toCSS ? val.toCSS() : val].join(''); } else { attr = key } } @@ -788,9 +816,10 @@ less.Parser = function Parser(env) { // div, .class, body > p {...} // ruleset: function () { - var selectors = [], s, rules, match, memo = i; + var selectors = [], s, rules, match; + save(); - if (match = peek(/([.#: \w-]+)[\s\n]*\{/g)) { + if (match = exec(/^([.#: \w-]+)[\s\n]*\{/)) { i += match[0].length - 1; selectors = [new(tree.Selector)([new(tree.Element)(null, match[1])])]; } else { @@ -806,17 +835,17 @@ less.Parser = function Parser(env) { } else { // Backtrack furthest = i; - i = memo; + restore(); } }, rule: function () { var value, c = input.charAt(i); - var memo = i; + save(); if (c === '.' || c === '#' || c === '&') { return } if (name = $(this.variable) || $(this.property)) { - if ((name.charAt(0) != '@') && (match = peek(/([^@+\/*(;{}-]*);/g))) { + if ((name.charAt(0) != '@') && (match = exec(/^([^@+\/*(;{}-]*);/))) { i += match[0].length - 1; value = new(tree.Anonymous)(match[1]); } else if (name === "font") { @@ -829,7 +858,7 @@ less.Parser = function Parser(env) { return new(tree.Rule)(name, value, memo); } else { furthest = i; - i = memo; + restore(); } } }, @@ -846,7 +875,7 @@ less.Parser = function Parser(env) { // "import": function () { var path; - if ($(/@import\s+/g) && + if ($(/^@import\s+/) && (path = $(this.entities.quoted) || $(this.entities.url)) && $(';')) { return new(tree.Import)(path, imports); @@ -865,12 +894,12 @@ less.Parser = function Parser(env) { if (value = $(this['import'])) { return value; - } else if (name = $(/@media|@page/g)) { - types = $(/[^{]+/g).trim(); + } else if (name = $(/^@media|@page/)) { + types = $(/^[^{]+/).trim(); if (rules = $(this.block)) { return new(tree.Directive)(name + " " + types, rules); } - } else if (name = $(/@[-a-z]+/g)) { + } else if (name = $(/^@[-a-z]+/)) { if (name === '@font-face') { if (rules = $(this.block)) { return new(tree.Directive)(name, rules); @@ -920,7 +949,7 @@ less.Parser = function Parser(env) { }, important: function () { if (input.charAt(i) === '!') { - return $(/!\s*important/g); + return $(/^!\s*important/); } }, sub: function () { @@ -942,7 +971,7 @@ less.Parser = function Parser(env) { addition: function () { var m, a, op, operation; if (m = $(this.multiplication)) { - while ((op = $(/[-+]\s+/g) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) && + while ((op = $(/^[-+]\s+/) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) && (a = $(this.multiplication))) { operation = new(tree.Operation)(op, [operation || m, a]); } @@ -979,7 +1008,7 @@ less.Parser = function Parser(env) { property: function () { var name; - if (name = $(/(\*?-?[-a-z_0-9]+)\s*:/g)) { + if (name = $(/^(\*?-?[-a-z_0-9]+)\s*:/)) { return name[1]; } }