Merge pull request #3079 from less/special-funcs

Initial support for custom parsed functions (`boolean`, `if` etc.)
This commit is contained in:
Max Mikhailov
2017-06-07 18:57:38 +02:00
committed by GitHub
10 changed files with 112 additions and 78 deletions

View File

@@ -0,0 +1,15 @@
var functionRegistry = require("./function-registry"),
Anonymous = require("../tree/anonymous"),
Keyword = require("../tree/keyword");
functionRegistry.addMultiple({
boolean: function(condition) {
return condition ? Keyword.True : Keyword.False;
},
'if': function(condition, trueValue, falseValue) {
return condition ? trueValue
: (falseValue || new Anonymous);
}
});

View File

@@ -5,6 +5,7 @@ module.exports = function(environment) {
};
// register functions
require("./boolean");
require("./default");
require("./color");
require("./color-blending");

View File

@@ -383,13 +383,10 @@ var Parser = function Parser(context, imports, fileInfo) {
//
// rgb(255, 0, 255)
//
// We also try to catch IE's `alpha()`, but let the `alpha` parser
// deal with the details.
//
// The arguments are parsed with the `entities.arguments` parser.
//
call: function () {
var name, nameLC, args, alpha, index = parserInput.i;
var name, args, func, index = parserInput.i;
// http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
if (parserInput.peek(/^url\(/i)) {
@@ -399,20 +396,22 @@ var Parser = function Parser(context, imports, fileInfo) {
parserInput.save();
name = parserInput.$re(/^([\w-]+|%|progid:[\w\.]+)\(/);
if (!name) { parserInput.forget(); return; }
if (!name) {
parserInput.forget();
return;
}
name = name[1];
nameLC = name.toLowerCase();
if (nameLC === 'alpha') {
alpha = parsers.alpha();
if (alpha) {
func = this.customFuncCall(name);
if (func) {
args = func.parse();
if (args && func.stop) {
parserInput.forget();
return alpha;
return args;
}
}
args = this.arguments();
args = this.arguments(args);
if (!parserInput.$char(')')) {
parserInput.restore("Could not parse call arguments or missing ')'");
@@ -422,47 +421,72 @@ var Parser = function Parser(context, imports, fileInfo) {
parserInput.forget();
return new(tree.Call)(name, args, index, fileInfo);
},
arguments: function () {
var argsSemiColon = [], argsComma = [],
expressions = [],
isSemiColonSeparated, value, arg;
//
// Parsing rules for functions with non-standard args, e.g.:
//
// boolean(not(2 > 1))
//
// This is a quick prototype, to be modified/improved when
// more custom-parsed funcs come (e.g. `selector(...)`)
//
customFuncCall: function (name) {
/* Ideally the table is to be moved out of here for faster perf.,
but it's quite tricky since it relies on all these `parsers`
and `expect` available only here */
return {
alpha: f(parsers.ieAlpha, true),
boolean: f(condition),
'if': f(condition)
}[name.toLowerCase()];
function f(parse, stop) {
return {
parse: parse, // parsing function
stop: stop // when true - stop after parse() and return its result,
// otherwise continue for plain args
};
}
function condition() {
return [expect(parsers.condition, 'expected condition')];
}
},
arguments: function (prevArgs) {
var argsComma = prevArgs || [],
argsSemiColon = [],
isSemiColonSeparated, value;
parserInput.save();
while (true) {
if (prevArgs) {
prevArgs = false;
} else {
value = parsers.detachedRuleset() || this.assignment() || parsers.expression();
if (!value) {
break;
}
arg = parsers.detachedRuleset() || this.assignment() || parsers.expression();
if (value.value && value.value.length == 1) {
value = value.value[0];
}
if (!arg) {
break;
argsComma.push(value);
}
value = arg;
if (arg.value && arg.value.length == 1) {
value = arg.value[0];
}
if (value) {
expressions.push(value);
}
argsComma.push(value);
if (parserInput.$char(',')) {
continue;
}
if (parserInput.$char(';') || isSemiColonSeparated) {
isSemiColonSeparated = true;
if (expressions.length > 1) {
value = new(tree.Value)(expressions);
}
value = (argsComma.length < 1) ? argsComma[0]
: new tree.Value(argsComma);
argsSemiColon.push(value);
expressions = [];
argsComma = [];
}
}
@@ -1019,17 +1043,18 @@ var Parser = function Parser(context, imports, fileInfo) {
//
// alpha(opacity=88)
//
alpha: function () {
ieAlpha: function () {
var value;
// http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
if (!parserInput.$re(/^opacity=/i)) { return; }
value = parserInput.$re(/^\d+/);
if (!value) {
value = expect(this.entities.variable, "Could not parse alpha");
value = expect(parsers.entities.variable, "Could not parse alpha");
value = '@{' + value.name.slice(1) + '}';
}
expectChar(')');
return new(tree.Alpha)(value);
return new tree.Quoted('', 'alpha(opacity=' + value + ')');
},
//

View File

@@ -1,28 +0,0 @@
var Node = require("./node");
var Alpha = function (val) {
this.value = val;
};
Alpha.prototype = new Node();
Alpha.prototype.type = "Alpha";
Alpha.prototype.accept = function (visitor) {
this.value = visitor.visit(this.value);
};
Alpha.prototype.eval = function (context) {
if (this.value.eval) { return new Alpha(this.value.eval(context)); }
return this;
};
Alpha.prototype.genCSS = function (context, output) {
output.add("alpha(opacity=");
if (this.value.genCSS) {
this.value.genCSS(context, output);
} else {
output.add(this.value);
}
output.add(")");
};
module.exports = Alpha;

View File

@@ -1,7 +1,6 @@
var tree = Object.create(null);
tree.Node = require('./node');
tree.Alpha = require('./alpha');
tree.Color = require('./color');
tree.AtRule = require('./atrule');
// Backwards compatibility

View File

@@ -199,3 +199,16 @@
html {
color: #8080ff;
}
#boolean {
a: true;
b: false;
c: false;
}
#if {
a: 1;
b: 2;
c: 3;
e: ;
f: 6;
/* results in void */
}

View File

@@ -1,2 +0,0 @@
@plugin "../plugin/plugin-tree-nodes";
test-alpha();

View File

@@ -1,3 +0,0 @@
SyntaxError: Alpha node returned by a function is not valid here in {path}functions-2-alpha.less on line 2, column 1:
1 @plugin "../plugin/plugin-tree-nodes";
2 test-alpha();

View File

@@ -231,3 +231,20 @@ html {
color: mix(blue, @color2, 50%);
}
#boolean {
a: boolean(not(2 < 1));
b: boolean(not(2 > 1) and (true));
c: boolean(not(boolean((true))));
}
#if {
a: if(not(false), 1, 2);
b: if(not(true), 1, 2);
@1: if(not(false), {c: 3}, {d: 4}); @1();
e: if(not(true), 5);
@f: boolean((3 = 4));
f: if(not(@f), 6);
if((false), {g: 7}); /* results in void */
}

View File

@@ -32,9 +32,6 @@ functions.addMultiple({
return true;
},
// These cause root errors
"test-alpha": function() {
return less.Alpha(30);
},
"test-assignment": function() {
return less.Assignment("bird", "robin");
},