From 006ce2651dda6a808a7455ec1db079217792e1a9 Mon Sep 17 00:00:00 2001 From: Max Mikhailov Date: Wed, 7 Jun 2017 16:31:35 +0300 Subject: [PATCH] special functions: add `boolean` and `if`, clean up `alpha` --- lib/less/functions/boolean.js | 15 ++++ lib/less/functions/index.js | 1 + lib/less/parser/parser.js | 107 +++++++++++++++--------- lib/less/tree/alpha.js | 28 ------- lib/less/tree/index.js | 1 - test/css/functions.css | 13 +++ test/less/errors/functions-2-alpha.less | 2 - test/less/errors/functions-2-alpha.txt | 3 - test/less/functions.less | 17 ++++ test/less/plugin/plugin-tree-nodes.js | 3 - 10 files changed, 112 insertions(+), 78 deletions(-) create mode 100644 lib/less/functions/boolean.js delete mode 100644 lib/less/tree/alpha.js delete mode 100644 test/less/errors/functions-2-alpha.less delete mode 100644 test/less/errors/functions-2-alpha.txt diff --git a/lib/less/functions/boolean.js b/lib/less/functions/boolean.js new file mode 100644 index 00000000..21254bc3 --- /dev/null +++ b/lib/less/functions/boolean.js @@ -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); + } +}); diff --git a/lib/less/functions/index.js b/lib/less/functions/index.js index 4b17556d..3b697d53 100644 --- a/lib/less/functions/index.js +++ b/lib/less/functions/index.js @@ -5,6 +5,7 @@ module.exports = function(environment) { }; // register functions + require("./boolean"); require("./default"); require("./color"); require("./color-blending"); diff --git a/lib/less/parser/parser.js b/lib/less/parser/parser.js index 1b97560b..52cf6489 100644 --- a/lib/less/parser/parser.js +++ b/lib/less/parser/parser.js @@ -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 + ')'); }, // diff --git a/lib/less/tree/alpha.js b/lib/less/tree/alpha.js deleted file mode 100644 index 82a4ce5e..00000000 --- a/lib/less/tree/alpha.js +++ /dev/null @@ -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; diff --git a/lib/less/tree/index.js b/lib/less/tree/index.js index f22cd4c3..7a6f46f0 100644 --- a/lib/less/tree/index.js +++ b/lib/less/tree/index.js @@ -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 diff --git a/test/css/functions.css b/test/css/functions.css index ab5df5bb..f4144ea2 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -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 */ +} diff --git a/test/less/errors/functions-2-alpha.less b/test/less/errors/functions-2-alpha.less deleted file mode 100644 index f00da078..00000000 --- a/test/less/errors/functions-2-alpha.less +++ /dev/null @@ -1,2 +0,0 @@ -@plugin "../plugin/plugin-tree-nodes"; -test-alpha(); \ No newline at end of file diff --git a/test/less/errors/functions-2-alpha.txt b/test/less/errors/functions-2-alpha.txt deleted file mode 100644 index 38a17273..00000000 --- a/test/less/errors/functions-2-alpha.txt +++ /dev/null @@ -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(); diff --git a/test/less/functions.less b/test/less/functions.less index 74a2b206..ad868293 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -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 */ +} diff --git a/test/less/plugin/plugin-tree-nodes.js b/test/less/plugin/plugin-tree-nodes.js index eec94ada..96c7760f 100644 --- a/test/less/plugin/plugin-tree-nodes.js +++ b/test/less/plugin/plugin-tree-nodes.js @@ -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"); },