diff --git a/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g b/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g index 72a4a9c9..62dd2bbf 100644 --- a/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g +++ b/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g @@ -692,7 +692,7 @@ hexColor ; function - : (a=IDENT | a=PERCENT) LPAREN b=functionParameter RPAREN -> ^(TERM_FUNCTION $a $b*) + : (a=IDENT | a=PERCENT) LPAREN b=functionParameter? RPAREN -> ^(TERM_FUNCTION $a $b*) ; functionParameter diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java b/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java index ba7a5c9b..dc310645 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java @@ -1,5 +1,5 @@ package com.github.sommeri.less4j.core.ast; public enum ASTCssNodeType { - UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, FUNCTION, MEDIA, COMMENT, DECLARATIONS_BODY, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION, ESCAPED_SELECTOR, ESCAPED_VALUE, INTERPOLABLE_NAME, FIXED_NAME_PART, VARIABLE_NAME_PART, KEYFRAMES, KEYFRAMES_NAME, KEYFRAMES_BODY, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MA, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE + UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, FUNCTION, MEDIA, COMMENT, DECLARATIONS_BODY, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION, ESCAPED_SELECTOR, ESCAPED_VALUE, INTERPOLABLE_NAME, FIXED_NAME_PART, VARIABLE_NAME_PART, KEYFRAMES, KEYFRAMES_NAME, KEYFRAMES_BODY, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MA, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE, ANONYMOUS, EMPTY_EXPRESSION } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/AnonymousExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/AnonymousExpression.java new file mode 100644 index 00000000..b75be232 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/ast/AnonymousExpression.java @@ -0,0 +1,37 @@ +package com.github.sommeri.less4j.core.ast; + +import java.util.Collections; +import java.util.List; + +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; + +public class AnonymousExpression extends Expression { + + private String value; + + public AnonymousExpression(HiddenTokenAwareTree token, String value) { + super(token); + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public List getChilds() { + return Collections.emptyList(); + } + + @Override + public ASTCssNodeType getType() { + return ASTCssNodeType.ANONYMOUS; + } + + @Override + public AnonymousExpression clone() { + AnonymousExpression result = (AnonymousExpression) super.clone(); + return result; + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java index e6608797..43bbfd71 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ColorExpression.java @@ -1,5 +1,6 @@ package com.github.sommeri.less4j.core.ast; +import java.awt.Color; import java.util.Collections; import java.util.List; @@ -7,23 +8,27 @@ public class ColorExpression extends Expression { - private String value; - private int red; - private int green; - private int blue; + protected String value; + protected double red; + protected double green; + protected double blue; public ColorExpression(HiddenTokenAwareTree token, String value) { super(token); setValue(value); } - public ColorExpression(HiddenTokenAwareTree token, int red, int green, int blue) { + public ColorExpression(HiddenTokenAwareTree token, double red, double green, double blue) { super(token); this.red = red; this.green = green; this.blue = blue; this.value = encode(red, green, blue); } + + public ColorExpression(HiddenTokenAwareTree token, Color color) { + this(token, color.getRed(), color.getGreen(), color.getBlue()); + } public String getValue() { return value; @@ -36,18 +41,22 @@ public void setValue(String value) { blue=decode(value, 2); } - public int getRed() { + public double getRed() { return red; } - public int getGreen() { + public double getGreen() { return green; } - public int getBlue() { + public double getBlue() { return blue; } + public double getAlpha() { + return 1.0f; + } + private int decode(String color, int i) { if (color.length()<7) { String substring = color.substring(i+1, i+2); @@ -57,15 +66,15 @@ private int decode(String color, int i) { return Integer.parseInt(color.substring(i*2+1, i*2+3), 16); } - private String encode(int red, int green, int blue) { + private String encode(double red, double green, double blue) { return "#" + toHex(red) + toHex(green) + toHex(blue); } - private String toHex(int color) { + protected String toHex(double color) { String prefix = ""; if (color<16) prefix = "0"; - return prefix + Integer.toHexString(color); + return prefix + Integer.toHexString((int) Math.round(color)); } @Override @@ -87,4 +96,52 @@ public String toString() { public ColorExpression clone() { return (ColorExpression) super.clone(); } + + public String toARGB() { + return "#FF" + toHex(red) + toHex(green) + toHex(blue); + } + + public Color toColor() { + return new Color((int)Math.round(this.red), (int)Math.round(this.green), (int)Math.round(this.blue)); + } + + public static class ColorWithAlphaExpression extends ColorExpression { + + /** + * Alpha in the range 0-1. + */ + private double alpha; + + public ColorWithAlphaExpression(HiddenTokenAwareTree token, double red, double green, double blue, double alpha) { + super(token, red, green, blue); + this.alpha = alpha; + if (alpha != 1.0) { + this.value = encode(red, green, blue, alpha); + } + } + + public ColorWithAlphaExpression(HiddenTokenAwareTree token, Color color) { + this(token, color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() / 255.0f); + } + + @Override + public double getAlpha() { + return alpha; + } + + protected String encode(double red, double green, double blue, double alpha) { + return "rgba(" + Math.round(red) + ", " + Math.round(green) + ", " + Math.round(blue) + ", " + alpha + ")"; + } + + @Override + public String toARGB() { + return "#" + toHex(Math.round(alpha * 255)) + toHex(red) + toHex(green) + toHex(blue); + } + + @Override + public Color toColor() { + return new Color((int)Math.round(this.red), (int)Math.round(this.green), (int)Math.round(this.blue), Math.round(this.alpha * 255)); + } + + } } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/Declaration.java b/src/main/java/com/github/sommeri/less4j/core/ast/Declaration.java index a4130d99..4540fa2a 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/Declaration.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/Declaration.java @@ -63,6 +63,10 @@ public List getChilds() { public boolean isFontDeclaration() { return getName()!=null? getName().toLowerCase().equals("font") : false; } + + public boolean isFilterDeclaration() { + return getName()!=null? getName().toLowerCase().equals("filter") : false; + } @Override public String toString() { diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/EmptyExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/EmptyExpression.java new file mode 100644 index 00000000..12be4fd5 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/ast/EmptyExpression.java @@ -0,0 +1,24 @@ +package com.github.sommeri.less4j.core.ast; + +import java.util.Collections; +import java.util.List; + +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; + +public class EmptyExpression extends Expression { + + public EmptyExpression(HiddenTokenAwareTree token) { + super(token); + } + + @Override + public List getChilds() { + return Collections.emptyList(); + } + + @Override + public ASTCssNodeType getType() { + return ASTCssNodeType.EMPTY_EXPRESSION; + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java b/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java index 08b5d13f..6911d618 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/NumberExpression.java @@ -1,7 +1,9 @@ package com.github.sommeri.less4j.core.ast; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; @@ -94,11 +96,102 @@ public boolean hasOriginalString() { public ASTCssNodeType getType() { return ASTCssNodeType.NUMBER; } + + public NumberExpression convertTo(String targetUnit) { + Map conversions = new HashMap(); + for (Dimension dim : Dimension.values()) { + if (dim.getConversions().containsKey(targetUnit)) { + conversions.put(dim, targetUnit); + } + } + + return convertTo(conversions); + } + + public NumberExpression convertTo(Map conversions) { + double value = getValueAsDouble(); + String unit = getSuffix(); + Dimension dimension = getDimension(); + + String targetUnit = conversions.get(dimension); + if (targetUnit == null) { + /* No conversion requested for this type of number */ + return this; + } + + Map group = dimension.getConversions(); + + Number multiplier = group.get(unit); + Number divisor = group.get(targetUnit); + + if (multiplier != null && divisor != null) { + return new NumberExpression(getUnderlyingStructure(), value * multiplier.doubleValue() / divisor.doubleValue(), targetUnit, null, getDimension()); + } else { + throw new IllegalArgumentException("Conversion between " + unit + " and " + targetUnit + " is not supported."); + } + } public enum Dimension { NUMBER, PERCENTAGE, LENGTH, EMS, EXS, ANGLE, TIME, FREQ, REPEATER, UNKNOWN; - } + private Map conversions; + + public static Dimension forSuffix(String suffix) { + if (suffix.equals("%")) { + return PERCENTAGE; + } else if (suffix.equals("px") || suffix.equals("cm") || suffix.equals("mm") || suffix.equals("in") || suffix.equals("pt") || suffix.equals("pc")) { + return LENGTH; + } else if (suffix.equals("em")) { + return EMS; + } else if (suffix.equals("ex")) { + return EXS; + } else if (suffix.equals("deg") || suffix.equals("rad") || suffix.equals("grad")) { + return ANGLE; + } else if (suffix.equals("ms") || suffix.equals("s")) { + return TIME; + } else if (suffix.equals("khz") || suffix.equals("hz")) { + return FREQ; + } else { + return UNKNOWN; + } + } + + public Map getConversions() { + if (conversions != null) + return conversions; + + HashMap result = new HashMap(); + switch (this) { + case LENGTH: + result.put("m", 1); + result.put("cm", 0.01); + result.put("mm", 0.001); + result.put("in", 0.0254); + result.put("pt", 0.0254 / 72); + result.put("pc", 0.0254 / 72 * 12); + break; + + case TIME: + result.put("s", 1); + result.put("ms", 0.001); + break; + + case ANGLE: + result.put("rad", 1.0 / (2 * Math.PI)); + result.put("deg", 1.0 / 360); + result.put("grad", 1.0 / 400); + result.put("turn", 1); + break; + + default: + break; + } + + this.conversions = result; + return result; + } + } + @Override public String toString() { if (originalString != null) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java b/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java index f7886785..d17ddd3c 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java @@ -142,7 +142,7 @@ private void evaluateExpressions(ASTCssNode node) { } private void evaluateInDeclaration(Declaration node) { - if (!node.isFontDeclaration()) { + if (!node.isFontDeclaration() && !node.isFilterDeclaration()) { evaluateExpressions(node); return; } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractFunction.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractFunction.java new file mode 100644 index 00000000..091e3b53 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractFunction.java @@ -0,0 +1,24 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import com.github.sommeri.less4j.core.ast.NumberExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; + +abstract class AbstractFunction implements Function { + + static double scaled(NumberExpression n, int size) { + if (n.getDimension() == Dimension.PERCENTAGE) { + return n.getValueAsDouble() * size / 100; + } else { + return number(n); + } + } + + static double number(NumberExpression n) { + if (n.getDimension() == Dimension.PERCENTAGE) { + return n.getValueAsDouble() / 100; + } else { + return n.getValueAsDouble(); + } + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java new file mode 100644 index 00000000..9735a97e --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/AbstractMultiParameterFunction.java @@ -0,0 +1,67 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import java.util.Collections; +import java.util.List; + +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.ComposedExpression; +import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.FaultyExpression; +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; + +abstract class AbstractMultiParameterFunction extends AbstractFunction { + + @Override + public Expression evaluate(Expression parameters, ProblemsHandler problemsHandler) { + List splitParameters; + + if (getMinParameters() == 1 && parameters.getType() != ASTCssNodeType.COMPOSED_EXPRESSION) { + splitParameters = Collections.singletonList(parameters); + } else if (parameters.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { + splitParameters = ((ComposedExpression) parameters).splitByComma(); + } else { + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getName(), getMinParameters()); + return new FaultyExpression(parameters); + } + + if (splitParameters.size() >= getMinParameters() && splitParameters.size() <= getMaxParameters()) { + /* Validate */ + boolean valid = true; + for (int i = 0; i < splitParameters.size(); i++) { + if (!validateParameter(splitParameters.get(i), i, problemsHandler)) { + valid = false; + } + } + + if (valid) { + return evaluate(splitParameters, problemsHandler, parameters.getUnderlyingStructure()); + } else { + return new FaultyExpression(parameters); + } + } else { + problemsHandler.wrongNumberOfArgumentsToFunction(parameters, getName(), getMinParameters()); + return new FaultyExpression(parameters); + } + } + + protected abstract Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); + + protected abstract int getMinParameters(); + + protected abstract int getMaxParameters(); + + protected abstract boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler); + + protected boolean validateParameter(Expression parameter, ASTCssNodeType expected, ProblemsHandler problemsHandler) { + if (parameter.getType() != expected) { + problemsHandler.wrongArgumentTypeToFunction(parameter, getName(), expected, parameter.getType()); + return false; + } else { + return true; + } + } + + protected abstract String getName(); + +} \ No newline at end of file diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java new file mode 100644 index 00000000..202a375b --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ColorFunctions.java @@ -0,0 +1,1141 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.AnonymousExpression; +import com.github.sommeri.less4j.core.ast.ColorExpression; +import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.FunctionExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; + +public class ColorFunctions implements FunctionsPackage { + + protected static final String RGB = "rgb"; + protected static final String RGBA = "rgba"; + protected static final String ARGB = "argb"; + protected static final String HSL = "hsl"; + protected static final String HSLA = "hsla"; + protected static final String HSV = "hsv"; + protected static final String HSVA = "hsva"; + + protected static final String HUE = "hue"; + protected static final String SATURATION = "saturation"; + protected static final String LIGHTNESS = "lightness"; + protected static final String RED = "red"; + protected static final String GREEN = "green"; + protected static final String BLUE = "blue"; + protected static final String ALPHA = "alpha"; + protected static final String LUMA = "luma"; + + protected static final String SATURATE = "saturate"; + protected static final String DESATURATE = "desaturate"; + protected static final String LIGHTEN = "lighten"; + protected static final String DARKEN = "darken"; + protected static final String FADEIN = "fadein"; + protected static final String FADEOUT = "fadeout"; + protected static final String FADE = "fade"; + protected static final String SPIN = "spin"; + protected static final String MIX = "mix"; + protected static final String GREYSCALE = "greyscale"; + protected static final String CONTRAST = "contrast"; + + protected static final String MULTIPLY = "multiply"; + protected static final String SCREEN = "screen"; + protected static final String OVERLAY = "overlay"; + protected static final String SOFTLIGHT = "softlight"; + protected static final String HARDLIGHT = "hardlight"; + protected static final String DIFFERENCE = "difference"; + protected static final String EXCLUSION = "exclusion"; + protected static final String AVERAGE = "average"; + protected static final String NEGATION = "negation"; + + protected static final String TINT = "tint"; + protected static final String SHADE = "shade"; + + private static Map FUNCTIONS = new HashMap(); + static { + FUNCTIONS.put(RGB, new RGB()); + FUNCTIONS.put(RGBA, new RGBA()); + FUNCTIONS.put(ARGB, new ARGB()); + FUNCTIONS.put(HSL, new HSL()); + FUNCTIONS.put(HSLA, new HSLA()); + FUNCTIONS.put(HSV, new HSV()); + FUNCTIONS.put(HSVA, new HSVA()); + + FUNCTIONS.put(HUE, new Hue()); + FUNCTIONS.put(SATURATION, new Saturation()); + FUNCTIONS.put(LIGHTNESS, new Lightness()); + FUNCTIONS.put(RED, new Red()); + FUNCTIONS.put(GREEN, new Green()); + FUNCTIONS.put(BLUE, new Blue()); + FUNCTIONS.put(ALPHA, new Alpha()); + FUNCTIONS.put(LUMA, new Luma()); + + FUNCTIONS.put(SATURATE, new Saturate()); + FUNCTIONS.put(DESATURATE, new Desaturate()); + FUNCTIONS.put(LIGHTEN, new Lighten()); + FUNCTIONS.put(DARKEN, new Darken()); + FUNCTIONS.put(FADEIN, new FadeIn()); + FUNCTIONS.put(FADEOUT, new FadeOut()); + FUNCTIONS.put(FADE, new Fade()); + FUNCTIONS.put(SPIN, new Spin()); + FUNCTIONS.put(MIX, new Mix()); + FUNCTIONS.put(GREYSCALE, new Greyscale()); + FUNCTIONS.put(CONTRAST, new Contrast()); + + FUNCTIONS.put(MULTIPLY, new Multiply()); + FUNCTIONS.put(SCREEN, new Screen()); + FUNCTIONS.put(OVERLAY, new Overlay()); + FUNCTIONS.put(SOFTLIGHT, new Softlight()); + FUNCTIONS.put(HARDLIGHT, new Hardlight()); + FUNCTIONS.put(DIFFERENCE, new Difference()); + FUNCTIONS.put(EXCLUSION, new Exclusion()); + FUNCTIONS.put(AVERAGE, new Average()); + FUNCTIONS.put(NEGATION, new Negation()); + + FUNCTIONS.put(TINT, new Tint()); + FUNCTIONS.put(SHADE, new Shade()); + } + + private final ProblemsHandler problemsHandler; + + public ColorFunctions(ProblemsHandler problemsHandler) { + this.problemsHandler = problemsHandler; + } + + /* + * (non-Javadoc) + * + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage# + * canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public boolean canEvaluate(FunctionExpression input, Expression parameters) { + return FUNCTIONS.containsKey(input.getName()); + } + + /* + * (non-Javadoc) + * + * @see + * com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate + * (com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public Expression evaluate(FunctionExpression input, Expression parameters) { + if (!canEvaluate(input, parameters)) + return input; + + Function function = FUNCTIONS.get(input.getName()); + return function.evaluate(parameters, problemsHandler); + } + +} + +class RGB extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), token); + } + + private Expression evaluate(NumberExpression r, NumberExpression g, NumberExpression b, HiddenTokenAwareTree token) { + return rgb(scaled(r, 255), scaled(g, 255), scaled(b, 255), token); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 3; + } + + @Override + protected int getMaxParameters() { + return 3; + } + + @Override + protected String getName() { + return ColorFunctions.RGB; + } + +} + +class RGBA extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), + (NumberExpression) parameters.get(3), token); + } + + private Expression evaluate(NumberExpression r, NumberExpression g, NumberExpression b, NumberExpression a, HiddenTokenAwareTree token) { + return rgba(scaled(r, 255), scaled(g, 255), scaled(b, 255), number(a), token); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 4; + } + + @Override + protected int getMaxParameters() { + return 4; + } + + @Override + protected String getName() { + return ColorFunctions.RGBA; + } + +} + +class HSL extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), token); + } + + private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, HiddenTokenAwareTree token) { + return hsla(new HSLAValue(number(h), number(s), number(l)), token); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 3; + } + + @Override + protected int getMaxParameters() { + return 3; + } + + @Override + protected String getName() { + return ColorFunctions.HSL; + } + +} + +class HSLA extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), + (NumberExpression) parameters.get(3), token); + } + + private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression l, NumberExpression a, HiddenTokenAwareTree token) { + return hsla(new HSLAValue(number(h), number(s), number(l), number(a)), token); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 4; + } + + @Override + protected int getMaxParameters() { + return 4; + } + + @Override + protected String getName() { + return ColorFunctions.HSLA; + } + +} + +class HSV extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), token); + } + + private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression v, HiddenTokenAwareTree token) { + return hsva(number(h), number(s), number(v), 1.0, token); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 3; + } + + @Override + protected int getMaxParameters() { + return 3; + } + + @Override + protected String getName() { + return ColorFunctions.HSV; + } + +} + +class HSVA extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression) parameters.get(0), (NumberExpression) parameters.get(1), (NumberExpression) parameters.get(2), + (NumberExpression) parameters.get(3), token); + } + + private Expression evaluate(NumberExpression h, NumberExpression s, NumberExpression v, NumberExpression a, HiddenTokenAwareTree token) { + return hsva(number(h), number(s), number(v), number(a), token); + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected int getMinParameters() { + return 4; + } + + @Override + protected int getMaxParameters() { + return 4; + } + + @Override + protected String getName() { + return ColorFunctions.HSVA; + } + +} + +class ARGB extends AbstractColorFunction { + + @Override + protected Expression evaluate(List parameters, ProblemsHandler problemHandler, HiddenTokenAwareTree token) { + return new AnonymousExpression(token, ((ColorExpression) parameters.get(0)).toARGB()); + } + + @Override + protected int getMaxParameters() { + return 1; + } + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + } + + @Override + protected String getName() { + return ColorFunctions.ARGB; + } + +} + +class Hue extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + HSLAValue hsla = toHSLA(color); + return new NumberExpression(token, Double.valueOf(Math.round(hsla.h)), "", null, Dimension.NUMBER); + } + + @Override + protected String getName() { + return ColorFunctions.HUE; + } + +} + +class Saturation extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + HSLAValue hsla = toHSLA(color); + return new NumberExpression(token, Double.valueOf(Math.round(hsla.s * 100)), "%", null, Dimension.PERCENTAGE); + } + + @Override + protected String getName() { + return ColorFunctions.SATURATION; + } + +} + +class Lightness extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + HSLAValue hsla = toHSLA(color); + return new NumberExpression(token, Double.valueOf(Math.round(hsla.l * 100)), "%", null, Dimension.PERCENTAGE); + } + + @Override + protected String getName() { + return ColorFunctions.LIGHTNESS; + } + +} + +class Red extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Double.valueOf(color.getRed()), "", null, Dimension.NUMBER); + } + + @Override + protected String getName() { + return ColorFunctions.RED; + } + +} + +class Green extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Double.valueOf(color.getGreen()), "", null, Dimension.NUMBER); + } + + @Override + protected String getName() { + return ColorFunctions.GREEN; + } + +} + +class Blue extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Double.valueOf(color.getBlue()), "", null, Dimension.NUMBER); + } + + @Override + protected String getName() { + return ColorFunctions.BLUE; + } + +} + +class Alpha extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Double.valueOf(color.getAlpha()), "", null, Dimension.NUMBER); + } + + @Override + protected String getName() { + return ColorFunctions.ALPHA; + } + +} + +class Luma extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + double luma = (Math + .round((0.2126 * (color.getRed() / 255.0) + 0.7152 * (color.getGreen() / 255.0) + 0.0722 * (color.getBlue() / 255.0)) + * color.getAlpha() * 100)); + + return new NumberExpression(token, Double.valueOf(luma), "%", null, Dimension.PERCENTAGE); + } + + @Override + protected String getName() { + return ColorFunctions.LUMA; + } + +} + +abstract class AbstractColorOperationFunction extends AbstractColorFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((ColorExpression) splitParameters.get(0), problemsHandler, token); + } + + protected abstract Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 1; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + } + +} + +class Saturate extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.s += amount.getValueAsDouble() / 100.0f; + hsla.s = clamp(hsla.s); + } + + @Override + protected String getName() { + return ColorFunctions.SATURATE; + } + +} + +class Desaturate extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.s -= amount.getValueAsDouble() / 100.0f; + hsla.s = clamp(hsla.s); + } + + @Override + protected String getName() { + return ColorFunctions.DESATURATE; + } + +} + +class Lighten extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.l += amount.getValueAsDouble() / 100.0f; + hsla.l = clamp(hsla.l); + } + + @Override + protected String getName() { + return ColorFunctions.LIGHTEN; + } + +} + +class Darken extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.l -= amount.getValueAsDouble() / 100.0f; + hsla.l = clamp(hsla.l); + } + + @Override + protected String getName() { + return ColorFunctions.DARKEN; + } + +} + +class FadeIn extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.a += amount.getValueAsDouble() / 100.0f; + hsla.a = clamp(hsla.a); + } + + @Override + protected String getName() { + return ColorFunctions.FADEIN; + } + +} + +class FadeOut extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.a -= amount.getValueAsDouble() / 100.0f; + hsla.a = clamp(hsla.a); + } + + @Override + protected String getName() { + return ColorFunctions.FADEOUT; + } + +} + +class Fade extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, HSLAValue hsla) { + hsla.a = (amount.getValueAsDouble() / 100.0f); + hsla.a = clamp(hsla.a); + } + + @Override + protected String getName() { + return ColorFunctions.FADE; + } + +} + +class Spin extends AbstractColorHSLAmountFunction { + + @Override + protected void apply(NumberExpression amount, HSLAValue hsla) { + double hue = ((hsla.h + amount.getValueAsDouble()) % 360); + hsla.h = hue < 0 ? 360 + hue : hue; + } + + @Override + protected String getName() { + return ColorFunctions.SPIN; + } + +} + +// +// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein +// http://sass-lang.com +// +class Mix extends AbstractColorFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + ColorExpression color1 = (ColorExpression) splitParameters.get(0); + ColorExpression color2 = (ColorExpression) splitParameters.get(1); + NumberExpression weight = splitParameters.size() > 2 ? (NumberExpression) splitParameters.get(2) : null; + + if (weight == null) { + weight = new NumberExpression(token, Double.valueOf(50), "%", null, Dimension.PERCENTAGE); + } + + return mix(color1, color2, weight, token); + } + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 3; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + case 1: + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + case 2: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + return false; + } + + @Override + protected String getName() { + return ColorFunctions.MIX; + } + +} + +class Greyscale extends AbstractColorOperationFunction { + + @Override + protected Expression evaluate(ColorExpression color, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + HSLAValue hsla = toHSLA(color); + hsla.s = 0; + return hsla(hsla, token); + } + + @Override + protected String getName() { + return ColorFunctions.GREYSCALE; + } + +} + +class Contrast extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + /* Contrast needs to support an invalid first parameter to succeed in less.js test cases. + * I think this is in order to support filter: rules so may not be a good idea. + * We return null to ColorFunctions which will in turn return the input, so in effect we change + * nothing. + */ + if (splitParameters.get(0).getType() != ASTCssNodeType.COLOR_EXPRESSION) + return null; + + ColorExpression color = (ColorExpression) splitParameters.get(0); + ColorExpression dark = (ColorExpression) (splitParameters.size() > 1 ? splitParameters.get(1) : new ColorExpression(token, 0, 0, 0)); + ColorExpression light = (ColorExpression) (splitParameters.size() > 2 ? splitParameters.get(2) : new ColorExpression(token, 255, 255, 255)); + NumberExpression threshold = (NumberExpression) (splitParameters.size() > 3 ? splitParameters.get(3) : new NumberExpression(token, 43.0, "%", null, Dimension.PERCENTAGE)); + double thresholdValue = AbstractColorFunction.number(threshold); + + if (((0.2126 * (color.getRed()/255) + 0.7152 * (color.getGreen()/255) + 0.0722 * (color.getBlue()/255)) * color.getAlpha()) < thresholdValue) { + return light; + } else { + return dark; + } + } + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 4; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + /* Contrast needs to support an invalid first parameter to succeed in less.js test cases. + * I think this is in order to support filter: rules so may not be a good idea. + */ + return true; + case 1: + case 2: + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + case 3: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + return false; + } + + @Override + protected String getName() { + return ColorFunctions.CONTRAST; + } + +} + +class Multiply extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return a * b / 255.0; + } + + @Override + protected String getName() { + return ColorFunctions.MULTIPLY; + } + +} + +class Screen extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return 255 - (255 - a) * (255 - b) / 255; + } + + @Override + protected String getName() { + return ColorFunctions.SCREEN; + } + +} + +class Overlay extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return a < 128 ? 2 * a * b / 255 : 255 - 2 * (255 - a) * (255 - b) / 255; + } + + @Override + protected String getName() { + return ColorFunctions.OVERLAY; + } + +} + +class Softlight extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + double t = b * a / 255; + return t + a * (255 - (255 - a) * (255 - b) / 255 - t) / 255; + } + + @Override + protected String getName() { + return ColorFunctions.SOFTLIGHT; + } + +} + +class Hardlight extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return b < 128 ? 2 * b * a / 255 : 255 - 2 * (255 - b) * (255 - a) / 255; + } + + @Override + protected String getName() { + return ColorFunctions.HARDLIGHT; + } + +} + +class Difference extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return Math.abs(a - b); + } + + @Override + protected String getName() { + return ColorFunctions.DIFFERENCE; + } + +} + +class Exclusion extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return a + b * (255 - a - a) / 255; + } + + @Override + protected String getName() { + return ColorFunctions.EXCLUSION; + } + +} + +class Average extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return (a + b) / 2; + } + + @Override + protected String getName() { + return ColorFunctions.AVERAGE; + } + +} + +class Negation extends AbstractSimpleColorBlendFunction { + + @Override + protected double evaluate(double a, double b) { + return 255 - Math.abs(255 - b - a); + } + + @Override + protected String getName() { + return ColorFunctions.NEGATION; + } + +} + +class Tint extends AbstractColorAmountFunction { + + @Override + protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { + return mix(rgb(255, 255, 255, token), color, amount, token); + } + + @Override + protected String getName() { + return ColorFunctions.TINT; + } + +} + +class Shade extends AbstractColorAmountFunction { + + @Override + protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { + return mix(rgb(0, 0, 0, token), color, amount, token); + } + + @Override + protected String getName() { + return ColorFunctions.SHADE; + } + +} + +abstract class AbstractSimpleColorBlendFunction extends AbstractColorBlendFunction { + + @Override + protected Expression evaluate(ColorExpression color1, ColorExpression color2, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return rgb(evaluate(color1.getRed(), color2.getRed()), + evaluate(color1.getGreen(), color2.getGreen()), + evaluate(color1.getBlue(), color2.getBlue()), token); + } + + protected abstract double evaluate(double a, double b); + +} + +abstract class AbstractColorBlendFunction extends AbstractColorFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((ColorExpression)splitParameters.get(0), (ColorExpression)splitParameters.get(1), problemsHandler, token); + } + + protected abstract Expression evaluate(ColorExpression color1, ColorExpression color2, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + } + +} + +abstract class AbstractColorFunction extends AbstractMultiParameterFunction { + + static double clamp(double val) { + return Math.min(1, Math.max(0, val)); + } + + static ColorExpression rgb(double r, double g, double b, HiddenTokenAwareTree token) { + return new ColorExpression(token, r, g, b); + } + + static ColorExpression rgba(double r, double g, double b, double a, HiddenTokenAwareTree token) { + return new ColorExpression.ColorWithAlphaExpression(token, r, g, b, a); + } + + static ColorExpression hsla(HSLAValue hsla, HiddenTokenAwareTree token) { + double h = (hsla.h % 360.0) / 360.0, s = hsla.s, l = hsla.l, a = hsla.a; + + double m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; + double m1 = l * 2 - m2; + + return rgba(hue(h + 1.0 / 3.0, m1, m2) * 255, hue(h, + m1, m2) * 255, hue(h - 1.0 / 3.0, m1, m2) * 255, a, token); + } + + private static double hue(double h, double m1, double m2) { + h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); + if (h * 6 < 1) + return m1 + (m2 - m1) * h * 6; + else if (h * 2 < 1) + return m2; + else if (h * 3 < 2) + return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6; + else + return m1; + } + + static final int[][] hsvaPerm = new int[][] { + new int[] { 0, 3, 1 }, + new int[] { 2, 0, 1 }, + new int[] { 1, 0, 3 }, + new int[] { 1, 2, 0 }, + new int[] { 3, 1, 0 }, + new int[] { 0, 1, 2 } + }; + + static ColorExpression hsva(double h, double s, double v, double a, HiddenTokenAwareTree token) { + h = ((h % 360) / 360) * 360; + + int i = (int) Math.floor((h / 60) % 6); + double f = (h / 60) - i; + + double[] vs = new double[] { v, + v * (1 - s), + v * (1 - f * s), + v * (1 - (1 - f) * s) + }; + + return rgba(vs[hsvaPerm[i][0]] * 255, vs[hsvaPerm[i][1]] * 255, vs[hsvaPerm[i][2]] * 255, a, token); + } + + /** + * Mix + * @param color1 + * @param color2 + * @param weight number 0-100. + * @return + */ + protected static Expression mix(ColorExpression color1, ColorExpression color2, NumberExpression weight, HiddenTokenAwareTree token) { + double p = weight.getValueAsDouble() / 100.0; + double w = p * 2 - 1; + double a = color1.getAlpha() - color2.getAlpha(); + + double w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + double w2 = 1 - w1; + + return rgba(color1.getRed() * w1 + color2.getRed() * w2, + color1.getGreen() * w1 + color2.getGreen() * w2, + color1.getBlue() * w1 + color2.getBlue() * w2, + color1.getAlpha() * p + color2.getAlpha() * (1 - p), token); + } + + static HSLAValue toHSLA(ColorExpression color) { + double r = color.getRed() / 255.0, g = color.getGreen() / 255.0, b = color.getBlue() / 255.0, a = color.getAlpha(); + + double max = Math.max(r, Math.max(g, b)), min = Math.min(r, Math.min(g, b)); + double h, s, l = (max + min) / 2, d = max - min; + + if (max == min) { + h = s = 0; + } else { + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + if (max == r) { + h = (g - b) / d + (g < b ? 6 : 0); + } else if (max == g) { + h = (b - r) / d + 2; + } else { + h = (r - g) / d + 4; + } + + h /= 6; + } + + return new HSLAValue((h * 360), s, l, a); + } + +} + +class HSLAValue { + public double h, s, l, a; + + public HSLAValue() { + super(); + } + + public HSLAValue(double h, double s, double l, double a) { + super(); + this.h = h; + this.s = s; + this.l = l; + this.a = a; + } + + public HSLAValue(double h, double s, double l) { + super(); + this.h = h; + this.s = s; + this.l = l; + this.a = 1.0f; + } +} + +abstract class AbstractColorAmountFunction extends AbstractColorFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + ColorExpression color = (ColorExpression) splitParameters.get(0); + NumberExpression amount = (NumberExpression) splitParameters.get(1); + + return evaluate(color, amount, token); + } + + protected abstract Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token); + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + return validateParameter(parameter, ASTCssNodeType.COLOR_EXPRESSION, problemsHandler); + case 1: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + return false; + } + +} + +abstract class AbstractColorHSLAmountFunction extends AbstractColorAmountFunction { + + @Override + protected Expression evaluate(ColorExpression color, NumberExpression amount, HiddenTokenAwareTree token) { + HSLAValue hsla = toHSLA(color); + + apply(amount, hsla); + + return hsla(hsla, token); + } + + /** + * Apply the amount to the given hsla array. + * + * @param amount + * @param hsla + */ + protected abstract void apply(NumberExpression amount, HSLAValue hsla); + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java index 4db9fda8..ef14b930 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java @@ -60,6 +60,9 @@ public ExpressionEvaluator(Scope scope, ProblemsHandler problemsHandler) { colorsCalculator = new ColorsCalculator(problemsHandler); functions.add(new MathFunctions(problemsHandler)); functions.add(new StringFunctions(problemsHandler)); + functions.add(new ColorFunctions(problemsHandler)); + functions.add(new MiscFunctions(problemsHandler)); + functions.add(new TypeFunctions(problemsHandler)); functions.add(new UnknownFunctions(problemsHandler)); } @@ -173,6 +176,7 @@ public Expression evaluate(Expression input) { case COLOR_EXPRESSION: case NUMBER: case FAULTY_EXPRESSION: + case EMPTY_EXPRESSION: return input; default: diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java index e57225fd..608b29b0 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MathFunctions.java @@ -1,6 +1,7 @@ package com.github.sommeri.less4j.core.compiler.expressions; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.github.sommeri.less4j.core.ast.ASTCssNodeType; @@ -19,6 +20,17 @@ public class MathFunctions implements FunctionsPackage { protected static final String ROUND = "round"; protected static final String FLOOR = "floor"; protected static final String CEIL = "ceil"; + protected static final String SQRT = "sqrt"; + protected static final String ABS = "abs"; + protected static final String TAN = "tan"; + protected static final String SIN = "sin"; + protected static final String COS = "cos"; + protected static final String MOD = "mod"; + protected static final String POW = "pow"; + protected static final String ATAN = "atan"; + protected static final String ASIN = "asin"; + protected static final String ACOS = "acos"; + protected static final String PI = "pi"; private static Map FUNCTIONS = new HashMap(); static { @@ -26,6 +38,17 @@ public class MathFunctions implements FunctionsPackage { FUNCTIONS.put(FLOOR, new Floor()); FUNCTIONS.put(CEIL, new Ceil()); FUNCTIONS.put(ROUND, new Round()); + FUNCTIONS.put(SQRT, new Sqrt()); + FUNCTIONS.put(ABS, new Abs()); + FUNCTIONS.put(TAN, new Tan()); + FUNCTIONS.put(SIN, new Sin()); + FUNCTIONS.put(COS, new Cos()); + FUNCTIONS.put(MOD, new Mod()); + FUNCTIONS.put(POW, new Pow()); + FUNCTIONS.put(ATAN, new Atan()); + FUNCTIONS.put(ASIN, new Asin()); + FUNCTIONS.put(ACOS, new Acos()); + FUNCTIONS.put(PI, new Pi()); } private final ProblemsHandler problemsHandler; @@ -94,7 +117,7 @@ private Expression createResult(Double originalValue, HiddenTokenAwareTree paren } -abstract class RoundingFunction implements Function { +abstract class AbstractSingleValueMathFunction implements Function { @Override public final Expression evaluate(Expression iParameter, ProblemsHandler problemsHandler) { @@ -114,17 +137,31 @@ public final Expression evaluate(Expression iParameter, ProblemsHandler problems return calc(parentToken, oValue, suffix, dimension); } + + protected Expression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { + return new NumberExpression(parentToken, calc(oValue, suffix, dimension), resultSuffix(suffix, dimension), + null, resultDimension(suffix, dimension)); + } + + protected String resultSuffix(String suffix, Dimension dimension) { + return suffix; + } + + protected Dimension resultDimension(String suffix, Dimension dimension) { + return dimension; + } protected abstract String getName(); - protected abstract Expression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension); + + protected abstract double calc(double d, String suffix, Dimension dimension); } -class Floor extends RoundingFunction { +class Floor extends AbstractSingleValueMathFunction { @Override - protected Expression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { - return new NumberExpression(parentToken, Math.floor(oValue), suffix, null, dimension); + protected double calc(double d, String suffix, Dimension dimension) { + return Math.floor(d); } @Override @@ -133,11 +170,11 @@ protected String getName() { } } -class Ceil extends RoundingFunction { +class Ceil extends AbstractSingleValueMathFunction { @Override - protected NumberExpression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { - return new NumberExpression(parentToken, Math.ceil(oValue), suffix, null, dimension); + protected double calc(double d, String suffix, Dimension dimension) { + return Math.ceil(d); } @Override @@ -146,16 +183,328 @@ protected String getName() { } } -class Round extends RoundingFunction { +class Sqrt extends AbstractSingleValueMathFunction { @Override - protected Expression calc(HiddenTokenAwareTree parentToken, Double oValue, String suffix, Dimension dimension) { - return new NumberExpression(parentToken, (double)Math.round(oValue), suffix, null, dimension); + protected String getName() { + return MathFunctions.SQRT; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.sqrt(d); + } + +} + +class Abs extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.ABS; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.abs(d); + } + +} + +class Tan extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.TAN; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + if (suffix.equalsIgnoreCase("deg")) { + d = Math.toRadians(d); + } else if (suffix.equalsIgnoreCase("grad")) { + d *= Math.PI / 200; + } + return Math.tan(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return ""; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.NUMBER; + } + +} + +class Atan extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.ATAN; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.atan(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return "rad"; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.ANGLE; + } + +} + +class Sin extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.SIN; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + if (suffix.equalsIgnoreCase("deg")) { + d = Math.toRadians(d); + } else if (suffix.equalsIgnoreCase("grad")) { + d *= Math.PI / 200; + } + return Math.sin(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return ""; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.NUMBER; + } + +} + +class Asin extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.ASIN; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.asin(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return "rad"; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.ANGLE; + } + +} + +class Cos extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.COS; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + if (suffix.equalsIgnoreCase("deg")) { + d = Math.toRadians(d); + } else if (suffix.equalsIgnoreCase("grad")) { + d *= Math.PI / 200; + } + return Math.cos(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return ""; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.NUMBER; + } + +} + +class Acos extends AbstractSingleValueMathFunction { + + @Override + protected String getName() { + return MathFunctions.ACOS; + } + + @Override + protected double calc(double d, String suffix, Dimension dimension) { + return Math.acos(d); + } + + @Override + protected String resultSuffix(String suffix, Dimension dimension) { + return "rad"; + } + + @Override + protected Dimension resultDimension(String suffix, Dimension dimension) { + return Dimension.ANGLE; + } + +} + +abstract class AbtractMultiParameterMathFunction extends AbstractMultiParameterFunction { + + @Override + protected boolean validateParameter(Expression parameter, ASTCssNodeType expected, ProblemsHandler problemsHandler) { + if (expected == ASTCssNodeType.NUMBER && parameter.getType() != expected) { + /* There is a special problemsHandler method for reporting math functions not getting numbers. */ + problemsHandler.mathFunctionParameterNotANumber(getName(), parameter); + return false; + } else { + return super.validateParameter(parameter, expected, problemsHandler); + } + } + +} + +class Mod extends AbtractMultiParameterMathFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + NumberExpression a = (NumberExpression) splitParameters.get(0); + NumberExpression b = (NumberExpression) splitParameters.get(1); + return new NumberExpression(token, a.getValueAsDouble() % b.getValueAsDouble(), a.getSuffix(), null, a.getDimension()); + } + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + + @Override + protected String getName() { + return MathFunctions.MOD; + } + +} + +class Pow extends AbstractTwoValueMathFunction { + + @Override + protected Expression evaluate(NumberExpression a, NumberExpression b, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return new NumberExpression(token, Math.pow(a.getValueAsDouble(), b.getValueAsDouble()), a.getSuffix(), null, a.getDimension()); + } + + @Override + protected String getName() { + return MathFunctions.POW; + } + +} + +abstract class AbstractTwoValueMathFunction extends AbtractMultiParameterMathFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + return evaluate((NumberExpression)splitParameters.get(0), (NumberExpression)splitParameters.get(1), problemsHandler, token); + } + + protected abstract Expression evaluate(NumberExpression a, NumberExpression b, ProblemsHandler problemsHandler, HiddenTokenAwareTree token); + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + +} + +class Round extends AbtractMultiParameterMathFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree parentToken) { + NumberExpression parameter = (NumberExpression) splitParameters.get(0); + Double oValue = parameter.getValueAsDouble(); + String suffix = parameter.getSuffix(); + Dimension dimension = parameter.getDimension(); + + if (oValue.isInfinite() || oValue.isNaN()) + return new NumberExpression(parentToken, oValue, suffix, null, dimension); + + NumberExpression fraction = (NumberExpression) (splitParameters.size() > 1 ? splitParameters.get(1) : null); + if (fraction != null) { + double pow = Math.pow(10, fraction.getValueAsDouble()); + oValue = Math.round(oValue * pow) / pow; + } else { + oValue = (double) Math.round(oValue); + } + return new NumberExpression(parentToken, oValue, suffix, null, dimension); + } + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); } @Override protected String getName() { return MathFunctions.ROUND; } + } +class Pi extends AbstractFunction { + + @Override + public Expression evaluate(Expression parameters, ProblemsHandler problemsHandler) { + return new NumberExpression(parameters.getUnderlyingStructure(), Math.PI, "", null, Dimension.NUMBER); + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java new file mode 100644 index 00000000..1aca2d36 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MiscFunctions.java @@ -0,0 +1,240 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.ColorExpression; +import com.github.sommeri.less4j.core.ast.ComposedExpression; +import com.github.sommeri.less4j.core.ast.CssString; +import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.FunctionExpression; +import com.github.sommeri.less4j.core.ast.IdentifierExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; + +public class MiscFunctions implements FunctionsPackage { + + protected static final String COLOR = "color"; + protected static final String UNIT = "unit"; + protected static final String CONVERT = "convert"; + protected static final String EXTRACT = "extract"; + + private static Map FUNCTIONS = new HashMap(); + static { + FUNCTIONS.put(COLOR, new Color()); + FUNCTIONS.put(UNIT, new Unit()); + FUNCTIONS.put(CONVERT, new Convert()); + FUNCTIONS.put(EXTRACT, new Extract()); + } + + private final ProblemsHandler problemsHandler; + + public MiscFunctions(ProblemsHandler problemsHandler) { + this.problemsHandler = problemsHandler; + } + + /* + * (non-Javadoc) + * + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage# + * canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public boolean canEvaluate(FunctionExpression input, Expression parameters) { + return FUNCTIONS.containsKey(input.getName()); + } + + /* + * (non-Javadoc) + * + * @see + * com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate + * (com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public Expression evaluate(FunctionExpression input, Expression parameters) { + if (!canEvaluate(input, parameters)) + return input; + + Function function = FUNCTIONS.get(input.getName()); + return function.evaluate(parameters, problemsHandler); + } + +} + +class Color extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + CssString string = (CssString) splitParameters.get(0); + return new ColorExpression(token, string.getValue()); + } + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 1; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + return validateParameter(parameter, ASTCssNodeType.STRING_EXPRESSION, problemsHandler); + } + + @Override + protected String getName() { + return MiscFunctions.COLOR; + } + +} + +class Unit extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + NumberExpression dimension = (NumberExpression) splitParameters.get(0); + IdentifierExpression unit = splitParameters.size() > 1 ? (IdentifierExpression) splitParameters.get(1) : null; + + String newSuffix; + Dimension newDimension; + if (unit != null) { + newSuffix = unit.getValue(); + newDimension = Dimension.forSuffix(newSuffix); + } else { + newSuffix = ""; + newDimension = Dimension.NUMBER; + } + + return new NumberExpression(token, dimension.getValueAsDouble(), newSuffix, null, newDimension); + } + + @Override + protected int getMinParameters() { + return 1; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + case 1: + return validateParameter(parameter, ASTCssNodeType.IDENTIFIER_EXPRESSION, problemsHandler); + } + return false; + } + + @Override + protected String getName() { + return MiscFunctions.UNIT; + } + +} + +class Convert extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + NumberExpression value = (NumberExpression) splitParameters.get(0); + IdentifierExpression unit = (IdentifierExpression) splitParameters.get(1); + return value.convertTo(unit.getValue()); + } + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + case 1: + return validateParameter(parameter, ASTCssNodeType.IDENTIFIER_EXPRESSION, problemsHandler); + } + return false; + } + + @Override + protected String getName() { + return MiscFunctions.CONVERT; + } + +} + +class Extract extends AbstractMultiParameterFunction { + + @Override + protected Expression evaluate(List splitParameters, ProblemsHandler problemsHandler, HiddenTokenAwareTree token) { + List values = collect((ComposedExpression) splitParameters.get(0)); + NumberExpression index = (NumberExpression) splitParameters.get(1); + return values.get(index.getValueAsDouble().intValue() - 1); + } + + private List collect(ComposedExpression values) { + List result = new ArrayList(); + Expression left = values.getLeft(); + Expression right = values.getRight(); + + if (left.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { + result.addAll(collect((ComposedExpression) left)); + } else { + result.add(left); + } + if (right.getType() == ASTCssNodeType.COMPOSED_EXPRESSION) { + result.addAll(collect((ComposedExpression) right)); + } else { + result.add(right); + } + return result; + } + + @Override + protected int getMinParameters() { + return 2; + } + + @Override + protected int getMaxParameters() { + return 2; + } + + @Override + protected boolean validateParameter(Expression parameter, int position, ProblemsHandler problemsHandler) { + switch (position) { + case 0: + return validateParameter(parameter, ASTCssNodeType.COMPOSED_EXPRESSION, problemsHandler); + case 1: + return validateParameter(parameter, ASTCssNodeType.NUMBER, problemsHandler); + } + return false; + } + + @Override + protected String getName() { + return MiscFunctions.EXTRACT; + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java new file mode 100644 index 00000000..a001cec4 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java @@ -0,0 +1,148 @@ +package com.github.sommeri.less4j.core.compiler.expressions; + +import java.util.HashMap; +import java.util.Map; + +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.FunctionExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression; +import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; + +public class TypeFunctions implements FunctionsPackage { + + protected static final String ISCOLOR = "iscolor"; + protected static final String ISKEYWORD = "iskeyword"; + protected static final String ISNUMBER = "isnumber"; + protected static final String ISSTRING = "isstring"; + protected static final String ISPIXEL = "ispixel"; + protected static final String ISPERCENTAGE = "ispercentage"; + protected static final String ISEM = "isem"; + + private static Map FUNCTIONS = new HashMap(); + static { + FUNCTIONS.put(ISCOLOR, new IsColor()); + FUNCTIONS.put(ISKEYWORD, new IsKeyword()); + FUNCTIONS.put(ISNUMBER, new IsNumber()); + FUNCTIONS.put(ISSTRING, new IsString()); + FUNCTIONS.put(ISPIXEL, new IsPixel()); + FUNCTIONS.put(ISPERCENTAGE, new IsPercentage()); + FUNCTIONS.put(ISEM, new IsEm()); + } + + private final ProblemsHandler problemsHandler; + + public TypeFunctions(ProblemsHandler problemsHandler) { + this.problemsHandler = problemsHandler; + } + + /* + * (non-Javadoc) + * + * @see com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage# + * canEvaluate(com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public boolean canEvaluate(FunctionExpression input, Expression parameters) { + return FUNCTIONS.containsKey(input.getName()); + } + + /* + * (non-Javadoc) + * + * @see + * com.github.sommeri.less4j.core.compiler.expressions.FunctionsPackage#evaluate + * (com.github.sommeri.less4j.core.ast.FunctionExpression, + * com.github.sommeri.less4j.core.ast.Expression) + */ + @Override + public Expression evaluate(FunctionExpression input, Expression parameters) { + if (!canEvaluate(input, parameters)) + return input; + + Function function = FUNCTIONS.get(input.getName()); + return function.evaluate(parameters, problemsHandler); + } + +} + +class IsColor extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.COLOR_EXPRESSION; + } + +} + +class IsKeyword extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.IDENTIFIER_EXPRESSION; + } + +} + +class IsNumber extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.NUMBER; + } + +} + +class IsString extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.STRING_EXPRESSION; + } + +} + +class IsPixel extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.NUMBER && ((NumberExpression)parameter).getSuffix().equalsIgnoreCase("px"); + } + +} + +class IsPercentage extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.NUMBER && ((NumberExpression)parameter).getDimension() == Dimension.PERCENTAGE; + } + +} + +class IsEm extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.NUMBER && ((NumberExpression)parameter).getSuffix().equalsIgnoreCase("em"); + } + +} + + +abstract class AbstractTypeFunction extends AbstractFunction { + + @Override + public Expression evaluate(Expression parameter, ProblemsHandler problemsHandler) { + if (checkType(parameter)) { + return new NumberExpression(parameter.getUnderlyingStructure(), 1.0, "", "true", Dimension.UNKNOWN); + } else { + return new NumberExpression(parameter.getUnderlyingStructure(), 0.0, "", "false", Dimension.UNKNOWN); + } + } + + protected abstract boolean checkType(Expression parameter); + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/parser/TermBuilder.java b/src/main/java/com/github/sommeri/less4j/core/parser/TermBuilder.java index 97430a59..2e267766 100644 --- a/src/main/java/com/github/sommeri/less4j/core/parser/TermBuilder.java +++ b/src/main/java/com/github/sommeri/less4j/core/parser/TermBuilder.java @@ -6,6 +6,7 @@ import com.github.sommeri.less4j.core.problems.BugHappened; import com.github.sommeri.less4j.core.ast.ColorExpression; import com.github.sommeri.less4j.core.ast.CssString; +import com.github.sommeri.less4j.core.ast.EmptyExpression; import com.github.sommeri.less4j.core.ast.EscapedValue; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.FunctionExpression; @@ -216,6 +217,12 @@ private Expression extractUrlParameter(HiddenTokenAwareTree token, String text) private FunctionExpression buildFromNormalFunction(HiddenTokenAwareTree token, HiddenTokenAwareTree actual) { List children = actual.getChildren(); String name = children.get(0).getText(); + + if (children.size() == 1) { + /* No arguments to the function */ + return new FunctionExpression(token, name, new EmptyExpression(token)); + } + HiddenTokenAwareTree parameterNode = children.get(1); if (parameterNode.getType() != LessLexer.OPEQ) { diff --git a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java index e2590740..7e6e1135 100644 --- a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java +++ b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java @@ -4,6 +4,7 @@ import com.github.sommeri.less4j.LessCompiler.Problem; import com.github.sommeri.less4j.core.ast.ASTCssNode; +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; import com.github.sommeri.less4j.core.ast.ArgumentDeclaration; import com.github.sommeri.less4j.core.ast.ComparisonExpressionOperator; import com.github.sommeri.less4j.core.ast.EscapedSelector; @@ -71,6 +72,14 @@ public void unsupportedKeyframesMember(ASTCssNode errorNode) { public void errFormatWrongFirstParameter(Expression param) { collector.addError(new CompilationError(param, "First argument of format function must be either string or escaped value.")); } + + public void wrongNumberOfArgumentsToFunction(Expression param, String function, int expectedArguments) { + collector.addError(new CompilationError(param, "Wrong number of arguments to function '" + function + "', should be " + expectedArguments + ".")); + } + + public void wrongArgumentTypeToFunction(Expression param, String function, ASTCssNodeType expected, ASTCssNodeType received) { + collector.addError(new CompilationError(param, "Wrong argument type to function '" + function + "', expected " + expected + " saw " + received + ".")); + } public void variablesCycle(List cycle) { collector.addError(new CompilationError(cycle.get(0), "Cyclic references among variables: " + printer.toVariablesString(cycle))); diff --git a/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java b/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java index 000976d2..b0487445 100644 --- a/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java +++ b/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java @@ -8,6 +8,7 @@ import com.github.sommeri.less4j.core.ExtendedStringBuilder; import com.github.sommeri.less4j.core.NotACssException; import com.github.sommeri.less4j.core.ast.ASTCssNode; +import com.github.sommeri.less4j.core.ast.AnonymousExpression; import com.github.sommeri.less4j.core.ast.Body; import com.github.sommeri.less4j.core.ast.CharsetDeclaration; import com.github.sommeri.less4j.core.ast.ColorExpression; @@ -214,6 +215,9 @@ public boolean switchOnType(ASTCssNode node) { case IMPORT: return appendImport((Import) node); + + case ANONYMOUS: + return appendAnonymous((AnonymousExpression) node); case ESCAPED_SELECTOR: case PARENTHESES_EXPRESSION: @@ -227,6 +231,11 @@ public boolean switchOnType(ASTCssNode node) { throw new IllegalStateException("Unknown: " + node.getType() + " " + node.getSourceLine() + ":" + node.getCharPositionInSourceLine()); } } + + private boolean appendAnonymous(AnonymousExpression node) { + builder.append(node.getValue()); + return true; + } private boolean appendImport(Import node) { builder.append("@import").ensureSeparator(); diff --git a/src/test/java/com/github/sommeri/less4j/compiler/CssOptimizationsTest.java b/src/test/java/com/github/sommeri/less4j/compiler/CssOptimizationsTest.java index c62bd387..01a5a047 100644 --- a/src/test/java/com/github/sommeri/less4j/compiler/CssOptimizationsTest.java +++ b/src/test/java/com/github/sommeri/less4j/compiler/CssOptimizationsTest.java @@ -9,7 +9,7 @@ public class CssOptimizationsTest extends BasicFeaturesTest { - private static final String standardCases = "src/test/resources/compile-basic-features/functions/"; + private static final String standardCases = "src/test/resources/compile-basic-features/css-optimizations/"; public CssOptimizationsTest(File inputFile, File outputFile, String testName) { super(inputFile, outputFile, testName); diff --git a/src/test/java/com/github/sommeri/less4j/compiler/FunctionsTest.java b/src/test/java/com/github/sommeri/less4j/compiler/FunctionsTest.java index 2ea83ac4..51f4b0ee 100644 --- a/src/test/java/com/github/sommeri/less4j/compiler/FunctionsTest.java +++ b/src/test/java/com/github/sommeri/less4j/compiler/FunctionsTest.java @@ -9,7 +9,7 @@ public class FunctionsTest extends BasicFeaturesTest { - private static final String standardCases = "src/test/resources/compile-basic-features/css-optimizations/"; + private static final String standardCases = "src/test/resources/compile-basic-features/functions/"; public FunctionsTest(File inputFile, File outputFile, String testName) { super(inputFile, outputFile, testName); diff --git a/src/test/resources/compile-basic-features/functions/colors.css b/src/test/resources/compile-basic-features/functions/colors.css new file mode 100644 index 00000000..b4516425 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/colors.css @@ -0,0 +1,58 @@ +#yelow #short { + color: #fea; +} +#yelow #long { + color: #ffeeaa; +} +#yelow #rgba { + color: rgba(255, 238, 170, 0.1); +} +#yelow #argb { + color: #1affeeaa; +} +#blue #short { + color: #00f; +} +#blue #long { + color: #0000ff; +} +#blue #rgba { + color: rgba(0, 0, 255, 0.1); +} +#blue #argb { + color: #1a0000ff; +} +#alpha #hsla { + color: rgba(61, 45, 41, 0.6); +} +#overflow .a { + color: #000000; +} +#overflow .b { + color: #ffffff; +} +#overflow .c { + color: #ffffff; +} +#overflow .d { + color: #00ff00; +} +#grey { + color: #c8c8c8; +} +#808080 { + color: #808080; +} +#00ff00 { + color: #00ff00; +} +.lightenblue { + color: #3333ff; +} +.darkenblue { + color: #0000cc; +} +.unknowncolors { + color: blue2; + border: 2px solid superred; +} diff --git a/src/test/resources/compile-basic-features/functions/colors.less b/src/test/resources/compile-basic-features/functions/colors.less new file mode 100644 index 00000000..3f808625 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/colors.less @@ -0,0 +1,65 @@ +#yelow { + #short { + color: #fea; + } + #long { + color: #ffeeaa; + } + #rgba { + color: rgba(255, 238, 170, 0.1); + } + #argb { + color: argb(rgba(255, 238, 170, 0.1)); + } +} + +#blue { + #short { + color: #00f; + } + #long { + color: #0000ff; + } + #rgba { + color: rgba(0, 0, 255, 0.1); + } + #argb { + color: argb(rgba(0, 0, 255, 0.1)); + } +} + +#alpha #hsla { + color: hsla(11, 20%, 20%, 0.6); +} + +#overflow { + .a { color: #111111 - #444444; } // #000000 + .b { color: #eee + #fff; } // #ffffff + .c { color: #aaa * 3; } // #ffffff + .d { color: #00ee00 + #009900; } // #00ff00 +} + +#grey { + color: rgb(200, 200, 200); +} + +#808080 { + color: hsl(50, 0%, 50%); +} + +#00ff00 { + color: hsl(120, 100%, 50%); +} + +.lightenblue { + color: lighten(blue, 10%); +} + +.darkenblue { + color: darken(blue, 10%); +} + +.unknowncolors { + color: blue2; + border: 2px solid superred; +} diff --git a/src/test/resources/compile-basic-features/functions/functions-color.css b/src/test/resources/compile-basic-features/functions/functions-color.css new file mode 100644 index 00000000..a1237c75 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/functions-color.css @@ -0,0 +1,84 @@ +#built-in { + escaped: -Some::weird(#thing, y); + lighten: #ffcccc; + darken: #330000; + saturate: #203c31; + desaturate: #29332f; + greyscale: #2e2e2e; + spin-p: #bf6a40; + spin-n: #bf4055; + luma-white: 100%; + luma-black: 0%; + luma-black-alpha: 0%; + luma-red: 21%; + luma-green: 72%; + luma-blue: 7%; + luma-yellow: 93%; + luma-cyan: 79%; + luma-white-alpha: 50%; + contrast-filter: contrast(30%); + contrast-white: #000000; + contrast-black: #ffffff; + contrast-red: #ffffff; + contrast-green: #000000; + contrast-blue: #ffffff; + contrast-yellow: #000000; + contrast-cyan: #000000; + contrast-light: #111111; + contrast-dark: #eeeeee; + contrast-light-thresh: #111111; + contrast-dark-thresh: #eeeeee; + contrast-high-thresh: #eeeeee; + contrast-low-thresh: #111111; + contrast: #000000; + contrast2: #ffffff; + contrast3: #dddddd; + contrast4: #000000; + contrast5: #000000; + contrast6: #000000; + contrast7: #ffffff; + contrast8: #ffffff; + format: "rgb(32, 128, 64)"; + format-string: "hello world"; + format-multiple: "hello earth 2"; + format-url-encode: "red is %23ff0000"; + eformat: rgb(32, 128, 64); + hue: 98; + saturation: 12%; + lightness: 95%; + red: 255; + green: 255; + blue: 255; + alpha: 0.5; + luma: 65%; + mix: #800080; + mix2: rgba(75, 25, 0, 0.75); + color: #ff0011; + color2: #aaa; + tint: #898989; + tint-full: #ffffff; + tint-percent: #898989; + shade: #686868; + shade-full: #000000; + shade-percent: #686868; + hsv: #4d2926; + hsva: rgba(77, 40, 38, 0.2); + mix: #ff3300; + mix-0: #ffff00; + mix-100: #ff0000; + mix-weightless: #ff8000; +} +#alpha { + alpha: rgba(153, 94, 51, 0.6); +} +#blendmodes { + multiply: #ed0000; + screen: #f600f6; + overlay: #ed0000; + softlight: #ff0000; + hardlight: #0000ed; + difference: #f600f6; + exclusion: #f600f6; + average: #7b007b; + negation: #d73131; +} diff --git a/src/test/resources/compile-basic-features/functions/functions-color.less b/src/test/resources/compile-basic-features/functions/functions-color.less new file mode 100644 index 00000000..87052485 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/functions-color.less @@ -0,0 +1,90 @@ +#built-in { + @r: 32; + escaped: e("-Some::weird(#thing, y)"); + lighten: lighten(#ff0000, 40%); + darken: darken(#ff0000, 40%); + saturate: saturate(#29332f, 20%); + desaturate: desaturate(#203c31, 20%); + greyscale: greyscale(#203c31); + spin-p: spin(hsl(340, 50%, 50%), 40); + spin-n: spin(hsl(30, 50%, 50%), -40); + luma-white: luma(#fff); + luma-black: luma(#000); + luma-black-alpha: luma(rgba(0,0,0,0.5)); + luma-red: luma(#ff0000); + luma-green: luma(#00ff00); + luma-blue: luma(#0000ff); + luma-yellow: luma(#ffff00); + luma-cyan: luma(#00ffff); + luma-white-alpha: luma(rgba(255,255,255,0.5)); + contrast-filter: contrast(30%); + contrast-white: contrast(#fff); + contrast-black: contrast(#000); + contrast-red: contrast(#ff0000); + contrast-green: contrast(#00ff00); + contrast-blue: contrast(#0000ff); + contrast-yellow: contrast(#ffff00); + contrast-cyan: contrast(#00ffff); + contrast-light: contrast(#fff, #111111, #eeeeee); + contrast-dark: contrast(#000, #111111, #eeeeee); + contrast-light-thresh: contrast(#fff, #111111, #eeeeee, 0.5); + contrast-dark-thresh: contrast(#000, #111111, #eeeeee, 0.5); + contrast-high-thresh: contrast(#555, #111111, #eeeeee, 0.6); + contrast-low-thresh: contrast(#555, #111111, #eeeeee, 0.1); + contrast: contrast(#aaaaaa); + contrast2: contrast(#222222, #101010); + contrast3: contrast(#222222, #101010, #dddddd); + contrast4: contrast(hsl(90, 100%, 50%),#000000,#ffffff,40%); + contrast5: contrast(hsl(90, 100%, 50%),#000000,#ffffff,60%); + contrast6: contrast(hsl(90, 100%, 50%),#000000,#ffffff,80%); + contrast7: contrast(hsl(90, 100%, 50%),#000000,#ffffff,90%); + contrast8: contrast(hsl(90, 100%, 50%),#000000,#ffffff,100%); + format: %("rgb(%d, %d, %d)", @r, 128, 64); + format-string: %("hello %s", "world"); + format-multiple: %("hello %s %d", "earth", 2); + format-url-encode: %('red is %A', #ff0000); + eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64)); + + hue: hue(hsl(98, 12%, 95%)); + saturation: saturation(hsl(98, 12%, 95%)); + lightness: lightness(hsl(98, 12%, 95%)); + red: red(#f00); + green: green(#0f0); + blue: blue(#00f); + alpha: alpha(rgba(10, 20, 30, 0.5)); + luma: luma(rgb(100, 200, 30)); + mix: mix(#ff0000, #0000ff, 50%); + mix2: mix(rgba(100,0,0,1.0), rgba(0,100,0,0.5), 50%); + color: color("#ff0011"); + color2: color("#aaa"); + tint: tint(#777777, 13); + tint-full: tint(#777777, 100); + tint-percent: tint(#777777, 13%); + shade: shade(#777777, 13); + shade-full: shade(#777777, 100); + shade-percent: shade(#777777, 13%); + + hsv: hsv(5, 50%, 30%); + hsva: hsva(3, 50%, 30%, 0.2); + + mix: mix(#ff0000, #ffff00, 80); + mix-0: mix(#ff0000, #ffff00, 0); + mix-100: mix(#ff0000, #ffff00, 100); + mix-weightless: mix(#ff0000, #ffff00); +} + +#alpha { + alpha: darken(hsla(25, 50%, 50%, 0.6), 10%); +} + +#blendmodes { + multiply: multiply(#f60000, #f60000); + screen: screen(#f60000, #0000f6); + overlay: overlay(#f60000, #0000f6); + softlight: softlight(#f60000, #ffffff); + hardlight: hardlight(#f60000, #0000f6); + difference: difference(#f60000, #0000f6); + exclusion: exclusion(#f60000, #0000f6); + average: average(#f60000, #0000f6); + negation: negation(#f60000, #313131); +} diff --git a/src/test/resources/compile-basic-features/functions/functions.css b/src/test/resources/compile-basic-features/functions/functions.css new file mode 100644 index 00000000..c2404ac7 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/functions.css @@ -0,0 +1,112 @@ +#functions { + height: undefined("self"); + background: linear-gradient(#000, #fff); +} +#built-in { + escaped: -Some::weird(#thing, y); + lighten: #ffcccc; + darken: #330000; + saturate: #203c31; + desaturate: #29332f; + greyscale: #2e2e2e; + spin-p: #bf6a40; + spin-n: #bf4055; + luma-white: 100%; + luma-black: 0%; + luma-black-alpha: 0%; + luma-red: 21%; + luma-green: 72%; + luma-blue: 7%; + luma-yellow: 93%; + luma-cyan: 79%; + luma-white-alpha: 50%; + contrast-filter: contrast(30%); + contrast-white: #000000; + contrast-black: #ffffff; + contrast-red: #ffffff; + contrast-green: #000000; + contrast-blue: #ffffff; + contrast-yellow: #000000; + contrast-cyan: #000000; + contrast-light: #111111; + contrast-dark: #eeeeee; + contrast-light-thresh: #111111; + contrast-dark-thresh: #eeeeee; + contrast-high-thresh: #eeeeee; + contrast-low-thresh: #111111; + format: "rgb(32, 128, 64)"; + format-string: "hello world"; + format-multiple: "hello earth 2"; + format-url-encode: "red is %23ff0000"; + eformat: rgb(32, 128, 64); + unitless: 12; + unit: 14em; + hue: 98; + saturation: 12%; + lightness: 95%; + red: 255; + green: 255; + blue: 255; + rounded: 11; + rounded-two: 10.67; + roundedpx: 3px; + roundedpx-three: 3.333px; + rounded-percentage: 10%; + ceil: 11px; + floor: 12px; + sqrt: 5px; + pi: 3.141592653589793; + mod: 2m; + abs: 4%; + tan: 0.8390996311772799; + sin: 0.17364817766693033; + cos: 0.8438539587324921; + atan: 0.1rad; + atan: 34deg; + atan: 45.00000000000001deg; + pow: 64px; + pow: 64; + pow: 27; + percentage: 20%; + color: #ff0011; + tint: #898989; + tint-full: #ffffff; + tint-percent: #898989; + shade: #686868; + shade-full: #000000; + shade-percent: #686868; + hsv: #4d2926; + hsva: rgba(77, 40, 38, 0.2); + mix: #ff3300; + mix-0: #ffff00; + mix-100: #ff0000; + mix-weightless: #ff8000; +} +#built-in .is-a { + color: true; + color1: true; + color2: true; + keyword: true; + number: true; + string: true; + pixel: true; + percent: true; + em: true; +} +#alpha { + alpha: rgba(153, 94, 51, 0.6); +} +#blendmodes { + multiply: #ed0000; + screen: #f600f6; + overlay: #ed0000; + softlight: #ff0000; + hardlight: #0000ed; + difference: #f600f6; + exclusion: #f600f6; + average: #7b007b; + negation: #d73131; +} +#extract { + result: 3 2 1 C B A; +} diff --git a/src/test/resources/compile-basic-features/functions/functions.less b/src/test/resources/compile-basic-features/functions/functions.less new file mode 100644 index 00000000..0bd7a8a8 --- /dev/null +++ b/src/test/resources/compile-basic-features/functions/functions.less @@ -0,0 +1,125 @@ +#functions { + @var: 10; + @colors: #000, #fff; + height: undefined("self"); + background: linear-gradient(@colors); +} + +#built-in { + @r: 32; + escaped: e("-Some::weird(#thing, y)"); + lighten: lighten(#ff0000, 40%); + darken: darken(#ff0000, 40%); + saturate: saturate(#29332f, 20%); + desaturate: desaturate(#203c31, 20%); + greyscale: greyscale(#203c31); + spin-p: spin(hsl(340, 50%, 50%), 40); + spin-n: spin(hsl(30, 50%, 50%), -40); + luma-white: luma(#fff); + luma-black: luma(#000); + luma-black-alpha: luma(rgba(0,0,0,0.5)); + luma-red: luma(#ff0000); + luma-green: luma(#00ff00); + luma-blue: luma(#0000ff); + luma-yellow: luma(#ffff00); + luma-cyan: luma(#00ffff); + luma-white-alpha: luma(rgba(255,255,255,0.5)); + contrast-filter: contrast(30%); + contrast-white: contrast(#fff); + contrast-black: contrast(#000); + contrast-red: contrast(#ff0000); + contrast-green: contrast(#00ff00); + contrast-blue: contrast(#0000ff); + contrast-yellow: contrast(#ffff00); + contrast-cyan: contrast(#00ffff); + contrast-light: contrast(#fff, #111111, #eeeeee); + contrast-dark: contrast(#000, #111111, #eeeeee); + contrast-light-thresh: contrast(#fff, #111111, #eeeeee, 0.5); + contrast-dark-thresh: contrast(#000, #111111, #eeeeee, 0.5); + contrast-high-thresh: contrast(#555, #111111, #eeeeee, 0.6); + contrast-low-thresh: contrast(#555, #111111, #eeeeee, 0.1); + format: %("rgb(%d, %d, %d)", @r, 128, 64); + format-string: %("hello %s", "world"); + format-multiple: %("hello %s %d", "earth", 2); + format-url-encode: %('red is %A', #ff0000); + eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64)); + + unitless: unit(12px); + unit: unit(13px + 1px, em); + + hue: hue(hsl(98, 12%, 95%)); + saturation: saturation(hsl(98, 12%, 95%)); + lightness: lightness(hsl(98, 12%, 95%)); + red: red(#f00); + green: green(#0f0); + blue: blue(#00f); + rounded: round(@r/3); + rounded-two: round(@r/3, 2); + roundedpx: round(10px / 3); + roundedpx-three: round(10px / 3, 3); + rounded-percentage: round(10.2%); + ceil: ceil(10.1px); + floor: floor(12.9px); + sqrt: sqrt(25px); + pi: pi(); + mod: mod(13m, 11cm); // could take into account units, doesn't at the moment + abs: abs(-4%); + tan: tan(40deg); + sin: sin(10deg); + cos: cos(12); + atan: atan(tan(0.1rad)); + atan: convert(acos(cos(34deg)), deg); + atan: convert(acos(cos(50grad)), deg); + pow: pow(8px, 2); + pow: pow(4, 3); + pow: pow(3, 3em); + percentage: percentage(10px / 50); + color: color("#ff0011"); + tint: tint(#777777, 13); + tint-full: tint(#777777, 100); + tint-percent: tint(#777777, 13%); + shade: shade(#777777, 13); + shade-full: shade(#777777, 100); + shade-percent: shade(#777777, 13%); + + hsv: hsv(5, 50%, 30%); + hsva: hsva(3, 50%, 30%, 0.2); + + mix: mix(#ff0000, #ffff00, 80); + mix-0: mix(#ff0000, #ffff00, 0); + mix-100: mix(#ff0000, #ffff00, 100); + mix-weightless: mix(#ff0000, #ffff00); + + .is-a { + color: iscolor(#ddd); + color1: iscolor(red); + color2: iscolor(rgb(0, 0, 0)); + keyword: iskeyword(hello); + number: isnumber(32); + string: isstring("hello"); + pixel: ispixel(32px); + percent: ispercentage(32%); + em: isem(32em); + } +} + +#alpha { + alpha: darken(hsla(25, 50%, 50%, 0.6), 10%); +} + +#blendmodes { + multiply: multiply(#f60000, #f60000); + screen: screen(#f60000, #0000f6); + overlay: overlay(#f60000, #0000f6); + softlight: softlight(#f60000, #ffffff); + hardlight: hardlight(#f60000, #0000f6); + difference: difference(#f60000, #0000f6); + exclusion: exclusion(#f60000, #0000f6); + average: average(#f60000, #0000f6); + negation: negation(#f60000, #313131); +} + +#extract { + @anon: A B C 1 2 3; + result: extract(@anon, 6) extract(@anon, 5) extract(@anon, 4) extract(@anon, 3) extract(@anon, 2) extract(@anon, 1); +} diff --git a/src/test/resources/compile-basic-features/mixins/mixins-list-argument.css b/src/test/resources/compile-basic-features/mixins/mixins-list-argument.css index 4585287d..c80e639b 100644 --- a/src/test/resources/compile-basic-features/mixins/mixins-list-argument.css +++ b/src/test/resources/compile-basic-features/mixins/mixins-list-argument.css @@ -4,5 +4,5 @@ content2: 4% identifier "hello"; } #gradientBarUsePlace { - text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); }