Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

detached rulesets #1859

Merged
merged 16 commits into from
Feb 19, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ tree:
- <%= build.lib %>/tree/color.js
- <%= build.lib %>/tree/comment.js
- <%= build.lib %>/tree/condition.js
- <%= build.lib %>/tree/detached-ruleset.js
- <%= build.lib %>/tree/dimension.js
- <%= build.lib %>/tree/directive.js
- <%= build.lib %>/tree/element.js
Expand All @@ -143,6 +144,7 @@ tree:
- <%= build.lib %>/tree/quoted.js
- <%= build.lib %>/tree/rule.js
- <%= build.lib %>/tree/ruleset.js
- <%= build.lib %>/tree/ruleset-call.js
- <%= build.lib %>/tree/selector.js
- <%= build.lib %>/tree/unicode-descriptor.js
- <%= build.lib %>/tree/url.js
Expand Down
2 changes: 2 additions & 0 deletions lib/less/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ var less = {

require('./tree/color');
require('./tree/directive');
require('./tree/detached-ruleset');
require('./tree/operation');
require('./tree/dimension');
require('./tree/keyword');
Expand All @@ -120,6 +121,7 @@ require('./tree/media');
require('./tree/unicode-descriptor');
require('./tree/negative');
require('./tree/extend');
require('./tree/ruleset-call');


var isUrlRe = /^(?:https?:)?\/\//i;
Expand Down
131 changes: 103 additions & 28 deletions lib/less/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ less.Parser = function Parser(env) {
var input, // LeSS input string
i, // current index in `input`
j, // current chunk
temp, // temporarily holds a chunk's state, for backtracking
memo, // temporarily holds `i`, when backtracking
saveStack = [], // holds state for backtracking
furthest, // furthest index the parser has gone to
chunks, // chunkified input
current, // current chunk
Expand Down Expand Up @@ -111,8 +110,9 @@ less.Parser = function Parser(env) {
}
};

function save() { temp = current; memo = currentPos = i; }
function restore() { current = temp; currentPos = i = memo; }
function save() { currentPos = i; saveStack.push( { current: current, i: i, j: j }); }
function restore() { var state = saveStack.pop(); current = state.current; currentPos = i = state.i; j = state.j; }
function forget() { saveStack.pop(); }

function sync() {
if (i > currentPos) {
Expand Down Expand Up @@ -424,7 +424,7 @@ less.Parser = function Parser(env) {
if (--level < 0) {
return fail("missing opening `{`");
}
if (!level) { emitChunk(); }
if (!level && !parenLevel) { emitChunk(); }
continue;
case 92: // \
if (parserCurrentIndex < len - 1) { parserCurrentIndex++; continue; }
Expand Down Expand Up @@ -729,14 +729,17 @@ less.Parser = function Parser(env) {
while (current)
{
node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() ||
mixin.call() || this.comment() || this.directive();
mixin.call() || this.comment() || this.rulesetCall() || this.directive();
if (node) {
root.push(node);
} else {
if (!($re(/^[\s\n]+/) || $re(/^;+/))) {
break;
}
}
if (peekChar('}')) {
break;
}
}

return root;
Expand Down Expand Up @@ -1027,6 +1030,19 @@ less.Parser = function Parser(env) {
if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*:/))) { return name[1]; }
},

//
// The variable part of a variable definition. Used in the `rule` parser
//
// @fink();
//
rulesetCall: function () {
var name;

if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*\(\s*\)\s*;/))) {
return new tree.RulesetCall(name[1]);
}
},

//
// extend syntax - used to extend selectors
//
Expand Down Expand Up @@ -1112,6 +1128,7 @@ less.Parser = function Parser(env) {
}

if (parsers.end()) {
forget();
return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
}
}
Expand All @@ -1124,9 +1141,11 @@ less.Parser = function Parser(env) {
expressions = [], argsSemiColon = [], argsComma = [],
isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg;

save();

while (true) {
if (isCall) {
arg = parsers.expression();
arg = parsers.detachedRuleset() || parsers.expression();
} else {
parsers.comments();
if (input.charAt(i) === '.' && $re(/^\.{3}/)) {
Expand Down Expand Up @@ -1154,7 +1173,7 @@ less.Parser = function Parser(env) {

if (isCall) {
// Variable
if (arg.value.length == 1) {
if (arg.value && arg.value.length == 1) {
val = arg.value[0];
}
} else {
Expand All @@ -1169,7 +1188,21 @@ less.Parser = function Parser(env) {
}
expressionContainsNamed = true;
}
value = expect(parsers.expression);

// we do not support setting a ruleset as a default variable - it doesn't make sense
// However if we do want to add it, there is nothing blocking it, just don't error
// and remove isCall dependency below
value = (isCall && parsers.detachedRuleset()) || parsers.expression();

if (!value) {
if (isCall) {
error("could not understand value for named argument");
} else {
restore();
returner.args = [];
return returner;
}
}
nameLoop = (name = val.name);
} else if (!isCall && $re(/^\.{3}/)) {
returner.variadic = true;
Expand Down Expand Up @@ -1214,6 +1247,7 @@ less.Parser = function Parser(env) {
}
}

forget();
returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
return returner;
},
Expand Down Expand Up @@ -1254,10 +1288,14 @@ less.Parser = function Parser(env) {
variadic = argInfo.variadic;

// .mixincall("@{a}");
// looks a bit like a mixin definition.. so we have to be nice and restore
// looks a bit like a mixin definition..
// also
// .mixincall(@a: {rule: set;});
// so we have to be nice and restore
if (!$char(')')) {
furthest = i;
restore();
return;
}

parsers.comments();
Expand All @@ -1269,10 +1307,13 @@ less.Parser = function Parser(env) {
ruleset = parsers.block();

if (ruleset) {
forget();
return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
} else {
restore();
}
} else {
forget();
}
}
},
Expand Down Expand Up @@ -1336,10 +1377,16 @@ less.Parser = function Parser(env) {
this.entities.variableCurly();

if (! e) {
save();
if ($char('(')) {
if ((v = this.selector()) && $char(')')) {
e = new(tree.Paren)(v);
forget();
} else {
restore();
}
} else {
forget();
}
}

Expand Down Expand Up @@ -1442,6 +1489,22 @@ less.Parser = function Parser(env) {
}
},

blockRuleset: function() {
var block = this.block();

if (block) {
block = new tree.Ruleset(null, block);
}
return block;
},

detachedRuleset: function() {
var blockRuleset = this.blockRuleset();
if (blockRuleset) {
return new tree.DetachedRuleset(blockRuleset);
}
},

//
// div, .class, body > p {...}
//
Expand Down Expand Up @@ -1472,6 +1535,7 @@ less.Parser = function Parser(env) {
}

if (selectors && (rules = this.block())) {
forget();
var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports);
if (env.dumpLineNumbers) {
ruleset.debugInfo = debugInfo;
Expand All @@ -1484,35 +1548,47 @@ less.Parser = function Parser(env) {
}
},
rule: function (tryAnonymous) {
var name, value, c = input.charAt(i), important, merge;
save();
var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable;

if (c === '.' || c === '#' || c === '&') { return; }

save();

name = this.variable() || this.ruleProperty();
if (name) {
// prefer to try to parse first if its a variable or we are compressing
// but always fallback on the other one
value = !tryAnonymous && (env.compress || (name.charAt && (name.charAt(0) === '@'))) ?
(this.value() || this.anonymousValue()) :
(this.anonymousValue() || this.value());

important = this.important();
isVariable = typeof name === "string";

// a name returned by this.ruleProperty() is always an array of the form:
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
// where each item is a tree.Keyword or tree.Variable
merge = name.pop && name.pop().value;
if (isVariable) {
value = this.detachedRuleset();
}

if (!value) {
// prefer to try to parse first if its a variable or we are compressing
// but always fallback on the other one
value = !tryAnonymous && (env.compress || isVariable) ?
(this.value() || this.anonymousValue()) :
(this.anonymousValue() || this.value());

important = this.important();

// a name returned by this.ruleProperty() is always an array of the form:
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
// where each item is a tree.Keyword or tree.Variable
merge = !isVariable && name.pop().value;
}

if (value && this.end()) {
return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo);
forget();
return new (tree.Rule)(name, value, important, merge, startOfRule, env.currentFileInfo);
} else {
furthest = i;
restore();
if (value && !tryAnonymous) {
return this.rule(true);
}
}
} else {
forget();
}
},
anonymousValue: function () {
Expand Down Expand Up @@ -1546,6 +1622,7 @@ less.Parser = function Parser(env) {
if (dir && (path = this.entities.quoted() || this.entities.url())) {
features = this.mediaFeatures();
if ($char(';')) {
forget();
features = features && new(tree.Value)(features);
return new(tree.Import)(path, features, options, index, env.currentFileInfo);
}
Expand Down Expand Up @@ -1742,13 +1819,11 @@ less.Parser = function Parser(env) {
}

if (hasBlock) {
rules = this.block();
if (rules) {
rules = new(tree.Ruleset)(null, rules);
}
rules = this.blockRuleset();
}

if (rules || (!hasBlock && value && $char(';'))) {
forget();
return new(tree.Directive)(name, value, rules, index, env.currentFileInfo,
env.dumpLineNumbers ? getDebugInfo(index, input, env) : null);
}
Expand Down
20 changes: 20 additions & 0 deletions lib/less/tree/detached-ruleset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
(function (tree) {

tree.DetachedRuleset = function (ruleset, frames) {
this.ruleset = ruleset;
this.frames = frames;
};
tree.DetachedRuleset.prototype = {
type: "DetachedRuleset",
accept: function (visitor) {
this.ruleset = visitor.visit(this.ruleset);
},
eval: function (env) {
var frames = this.frames || env.frames.slice(0);
return new tree.DetachedRuleset(this.ruleset, frames);
},
callEval: function (env) {
return this.ruleset.eval(this.frames ? new(tree.evalEnv)(env, this.frames.concat(env.frames)) : env);
}
};
})(require('../tree'));
2 changes: 2 additions & 0 deletions lib/less/tree/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ tree.Media.prototype = {
}
},
bubbleSelectors: function (selectors) {
if (!selectors)
return;
this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])];
}
};
Expand Down
13 changes: 8 additions & 5 deletions lib/less/tree/mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ tree.mixin.Call.prototype = {
mixin.originalRuleset = mixins[m].originalRuleset || mixins[m];
}
Array.prototype.push.apply(
rules, mixin.eval(env, args, this.important).rules);
rules, mixin.evalCall(env, args, this.important).rules);
} catch (e) {
throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
}
Expand Down Expand Up @@ -150,7 +150,7 @@ tree.mixin.Call.prototype = {
}
};

tree.mixin.Definition = function (name, params, rules, condition, variadic) {
tree.mixin.Definition = function (name, params, rules, condition, variadic, frames) {
this.name = name;
this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])];
this.params = params;
Expand All @@ -164,7 +164,7 @@ tree.mixin.Definition = function (name, params, rules, condition, variadic) {
else { return count; }
}, 0);
this.parent = tree.Ruleset.prototype;
this.frames = [];
this.frames = frames;
};
tree.mixin.Definition.prototype = {
type: "MixinDefinition",
Expand Down Expand Up @@ -258,9 +258,12 @@ tree.mixin.Definition.prototype = {

return frame;
},
eval: function (env, args, important) {
eval: function (env) {
return new tree.mixin.Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || env.frames.slice(0));
},
evalCall: function (env, args, important) {
var _arguments = [],
mixinFrames = this.frames.concat(env.frames),
mixinFrames = this.frames ? this.frames.concat(env.frames) : env.frames,
frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments),
rules, ruleset;

Expand Down
Loading