CSSCalculationValue.cpp   [plain text]


/*
 * Copyright (C) 2011, 2012 Google Inc. All rights reserved.
 * Copyright (C) 2014 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "CSSCalculationValue.h"

#include "CSSParser.h"
#include "CSSParserTokenRange.h"
#include "CSSPrimitiveValueMappings.h"
#include "StyleResolver.h"
#include <wtf/MathExtras.h>
#include <wtf/text/StringBuilder.h>

static const int maxExpressionDepth = 100;

enum ParseState {
    OK,
    TooDeep,
    NoMoreTokens
};

namespace WebCore {

static RefPtr<CSSCalcExpressionNode> createCSS(const CalcExpressionNode&, const RenderStyle&);
static RefPtr<CSSCalcExpressionNode> createCSS(const Length&, const RenderStyle&);

static CalculationCategory unitCategory(CSSPrimitiveValue::UnitType type)
{
    switch (type) {
    case CSSPrimitiveValue::CSS_NUMBER:
        return CalcNumber;
    case CSSPrimitiveValue::CSS_EMS:
    case CSSPrimitiveValue::CSS_EXS:
    case CSSPrimitiveValue::CSS_PX:
    case CSSPrimitiveValue::CSS_CM:
    case CSSPrimitiveValue::CSS_MM:
    case CSSPrimitiveValue::CSS_IN:
    case CSSPrimitiveValue::CSS_PT:
    case CSSPrimitiveValue::CSS_PC:
    case CSSPrimitiveValue::CSS_REMS:
    case CSSPrimitiveValue::CSS_CHS:
    case CSSPrimitiveValue::CSS_VW:
    case CSSPrimitiveValue::CSS_VH:
    case CSSPrimitiveValue::CSS_VMIN:
    case CSSPrimitiveValue::CSS_VMAX:
        return CalcLength;
    case CSSPrimitiveValue::CSS_PERCENTAGE:
        return CalcPercent;
    case CSSPrimitiveValue::CSS_DEG:
    case CSSPrimitiveValue::CSS_RAD:
    case CSSPrimitiveValue::CSS_GRAD:
    case CSSPrimitiveValue::CSS_TURN:
        return CalcAngle;
    case CSSPrimitiveValue::CSS_MS:
    case CSSPrimitiveValue::CSS_S:
        return CalcTime;
    case CSSPrimitiveValue::CSS_HZ:
    case CSSPrimitiveValue::CSS_KHZ:
        return CalcFrequency;
    default:
        return CalcOther;
    }
}

static bool hasDoubleValue(CSSPrimitiveValue::UnitType type)
{
    switch (type) {
    case CSSPrimitiveValue::CSS_FR:
    case CSSPrimitiveValue::CSS_NUMBER:
    case CSSPrimitiveValue::CSS_PERCENTAGE:
    case CSSPrimitiveValue::CSS_EMS:
    case CSSPrimitiveValue::CSS_EXS:
    case CSSPrimitiveValue::CSS_CHS:
    case CSSPrimitiveValue::CSS_REMS:
    case CSSPrimitiveValue::CSS_PX:
    case CSSPrimitiveValue::CSS_CM:
    case CSSPrimitiveValue::CSS_MM:
    case CSSPrimitiveValue::CSS_IN:
    case CSSPrimitiveValue::CSS_PT:
    case CSSPrimitiveValue::CSS_PC:
    case CSSPrimitiveValue::CSS_DEG:
    case CSSPrimitiveValue::CSS_RAD:
    case CSSPrimitiveValue::CSS_GRAD:
    case CSSPrimitiveValue::CSS_TURN:
    case CSSPrimitiveValue::CSS_MS:
    case CSSPrimitiveValue::CSS_S:
    case CSSPrimitiveValue::CSS_HZ:
    case CSSPrimitiveValue::CSS_KHZ:
    case CSSPrimitiveValue::CSS_DIMENSION:
    case CSSPrimitiveValue::CSS_VW:
    case CSSPrimitiveValue::CSS_VH:
    case CSSPrimitiveValue::CSS_VMIN:
    case CSSPrimitiveValue::CSS_VMAX:
    case CSSPrimitiveValue::CSS_DPPX:
    case CSSPrimitiveValue::CSS_DPI:
    case CSSPrimitiveValue::CSS_DPCM:
        return true;
    case CSSPrimitiveValue::CSS_UNKNOWN:
    case CSSPrimitiveValue::CSS_STRING:
    case CSSPrimitiveValue::CSS_FONT_FAMILY:
    case CSSPrimitiveValue::CSS_URI:
    case CSSPrimitiveValue::CSS_IDENT:
    case CSSPrimitiveValue::CSS_ATTR:
    case CSSPrimitiveValue::CSS_COUNTER:
    case CSSPrimitiveValue::CSS_RECT:
    case CSSPrimitiveValue::CSS_RGBCOLOR:
    case CSSPrimitiveValue::CSS_PAIR:
    case CSSPrimitiveValue::CSS_UNICODE_RANGE:
    case CSSPrimitiveValue::CSS_COUNTER_NAME:
    case CSSPrimitiveValue::CSS_SHAPE:
    case CSSPrimitiveValue::CSS_QUAD:
    case CSSPrimitiveValue::CSS_QUIRKY_EMS:
    case CSSPrimitiveValue::CSS_CALC:
    case CSSPrimitiveValue::CSS_CALC_PERCENTAGE_WITH_NUMBER:
    case CSSPrimitiveValue::CSS_CALC_PERCENTAGE_WITH_LENGTH:
    case CSSPrimitiveValue::CSS_PROPERTY_ID:
    case CSSPrimitiveValue::CSS_VALUE_ID:
#if ENABLE(DASHBOARD_SUPPORT)
    case CSSPrimitiveValue::CSS_DASHBOARD_REGION:
#endif
        return false;
    };
    ASSERT_NOT_REACHED();
    return false;
}

static String buildCssText(const String& expression)
{
    StringBuilder result;
    result.appendLiteral("calc");
    bool expressionHasSingleTerm = expression[0] != '(';
    if (expressionHasSingleTerm)
        result.append('(');
    result.append(expression);
    if (expressionHasSingleTerm)
        result.append(')');
    return result.toString();
}

String CSSCalcValue::customCSSText() const
{
    return buildCssText(m_expression->customCSSText());
}

bool CSSCalcValue::equals(const CSSCalcValue& other) const
{
    return compareCSSValue(m_expression, other.m_expression);
}

inline double CSSCalcValue::clampToPermittedRange(double value) const
{
    return m_shouldClampToNonNegative && value < 0 ? 0 : value;
}

double CSSCalcValue::doubleValue() const
{
    return clampToPermittedRange(m_expression->doubleValue());
}

double CSSCalcValue::computeLengthPx(const CSSToLengthConversionData& conversionData) const
{
    return clampToPermittedRange(m_expression->computeLengthPx(conversionData));
}

class CSSCalcPrimitiveValue final : public CSSCalcExpressionNode {
    WTF_MAKE_FAST_ALLOCATED;
public:
    static Ref<CSSCalcPrimitiveValue> create(Ref<CSSPrimitiveValue>&& value, bool isInteger)
    {
        return adoptRef(*new CSSCalcPrimitiveValue(WTFMove(value), isInteger));
    }

    static RefPtr<CSSCalcPrimitiveValue> create(double value, CSSPrimitiveValue::UnitType type, bool isInteger)
    {
        if (!std::isfinite(value))
            return nullptr;
        return adoptRef(new CSSCalcPrimitiveValue(CSSPrimitiveValue::create(value, type), isInteger));
    }

private:
    bool isZero() const final
    {
        return !m_value->doubleValue();
    }

    String customCSSText() const final
    {
        return m_value->cssText();
    }

    std::unique_ptr<CalcExpressionNode> createCalcExpression(const CSSToLengthConversionData& conversionData) const final
    {
        switch (category()) {
        case CalcNumber:
            return std::make_unique<CalcExpressionNumber>(m_value->floatValue());
        case CalcLength:
            return std::make_unique<CalcExpressionLength>(Length(m_value->computeLength<float>(conversionData), WebCore::Fixed));
        case CalcPercent:
        case CalcPercentLength: {
            return std::make_unique<CalcExpressionLength>(m_value->convertToLength<FixedFloatConversion | PercentConversion>(conversionData));
        }
        // Only types that could be part of a Length expression can be converted
        // to a CalcExpressionNode. CalcPercentNumber makes no sense as a Length.
        case CalcPercentNumber:
        case CalcAngle:
        case CalcTime:
        case CalcFrequency:
        case CalcOther:
            ASSERT_NOT_REACHED();
        }
        ASSERT_NOT_REACHED();
        return nullptr;
    }

    double doubleValue() const final
    {
        if (hasDoubleValue(primitiveType()))
            return m_value->doubleValue();
        ASSERT_NOT_REACHED();
        return 0;
    }

    double computeLengthPx(const CSSToLengthConversionData& conversionData) const final
    {
        switch (category()) {
        case CalcLength:
            return m_value->computeLength<double>(conversionData);
        case CalcPercent:
        case CalcNumber:
            return m_value->doubleValue();
        case CalcPercentLength:
        case CalcPercentNumber:
        case CalcAngle:
        case CalcTime:
        case CalcFrequency:
        case CalcOther:
            ASSERT_NOT_REACHED();
            break;
        }
        ASSERT_NOT_REACHED();
        return 0;
    }

    bool equals(const CSSCalcExpressionNode& other) const final
    {
        if (type() != other.type())
            return false;

        return compareCSSValue(m_value, static_cast<const CSSCalcPrimitiveValue&>(other).m_value);
    }

    Type type() const final { return CssCalcPrimitiveValue; }
    CSSPrimitiveValue::UnitType primitiveType() const final
    {
        return CSSPrimitiveValue::UnitType(m_value->primitiveType());
    }

private:
    explicit CSSCalcPrimitiveValue(Ref<CSSPrimitiveValue>&& value, bool isInteger)
        : CSSCalcExpressionNode(unitCategory((CSSPrimitiveValue::UnitType)value->primitiveType()), isInteger)
        , m_value(WTFMove(value))
    {
    }

    Ref<CSSPrimitiveValue> m_value;
};

static const CalculationCategory addSubtractResult[CalcAngle][CalcAngle] = {
//    CalcNumber         CalcLength         CalcPercent        CalcPercentNumber  CalcPercentLength
    { CalcNumber,        CalcOther,         CalcPercentNumber, CalcPercentNumber, CalcOther }, //         CalcNumber
    { CalcOther,         CalcLength,        CalcPercentLength, CalcOther,         CalcPercentLength }, // CalcLength
    { CalcPercentNumber, CalcPercentLength, CalcPercent,       CalcPercentNumber, CalcPercentLength }, // CalcPercent
    { CalcPercentNumber, CalcOther,         CalcPercentNumber, CalcPercentNumber, CalcOther }, //         CalcPercentNumber
    { CalcOther,         CalcPercentLength, CalcPercentLength, CalcOther,         CalcPercentLength }, // CalcPercentLength
};

static CalculationCategory determineCategory(const CSSCalcExpressionNode& leftSide, const CSSCalcExpressionNode& rightSide, CalcOperator op)
{
    CalculationCategory leftCategory = leftSide.category();
    CalculationCategory rightCategory = rightSide.category();
    ASSERT(leftCategory < CalcOther);
    ASSERT(rightCategory < CalcOther);

    switch (op) {
    case CalcAdd:
    case CalcSubtract:
        if (leftCategory < CalcAngle && rightCategory < CalcAngle)
            return addSubtractResult[leftCategory][rightCategory];
        if (leftCategory == rightCategory)
            return leftCategory;
        return CalcOther;
    case CalcMultiply:
        if (leftCategory != CalcNumber && rightCategory != CalcNumber)
            return CalcOther;
        return leftCategory == CalcNumber ? rightCategory : leftCategory;
    case CalcDivide:
        if (rightCategory != CalcNumber || rightSide.isZero())
            return CalcOther;
        return leftCategory;
    case CalcMin:
    case CalcMax:
        ASSERT_NOT_REACHED();
        return CalcOther;
    }

    ASSERT_NOT_REACHED();
    return CalcOther;
}

static CalculationCategory resolvedTypeForMinOrMax(CalculationCategory category, CalculationCategory destinationCategory)
{
    switch (category) {
    case CalcNumber:
    case CalcLength:
    case CalcPercentNumber:
    case CalcPercentLength:
    case CalcAngle:
    case CalcTime:
    case CalcFrequency:
    case CalcOther:
        return category;

    case CalcPercent:
        if (destinationCategory == CalcLength)
            return CalcPercentLength;
        if (destinationCategory == CalcNumber)
            return CalcPercentNumber;
        return category;
    }

    return CalcOther;
}

static inline bool isIntegerResult(CalcOperator op, const CSSCalcExpressionNode& leftSide, const CSSCalcExpressionNode& rightSide)
{
    // Performs W3C spec's type checking for calc integers.
    // http://www.w3.org/TR/css3-values/#calc-type-checking
    return op != CalcDivide && leftSide.isInteger() && rightSide.isInteger();
}

static inline bool isIntegerResult(CalcOperator op, const Vector<Ref<CSSCalcExpressionNode>>& nodes)
{
    // Performs W3C spec's type checking for calc integers.
    // http://www.w3.org/TR/css3-values/#calc-type-checking
    if (op == CalcDivide)
        return false;

    for (auto& node : nodes) {
        if (!node->isInteger())
            return false;
    }

    return true;
}

static bool isSamePair(CalculationCategory a, CalculationCategory b, CalculationCategory x, CalculationCategory y)
{
    return (a == x && b == y) || (a == y && b == x);
}

class CSSCalcOperation final : public CSSCalcExpressionNode {
    WTF_MAKE_FAST_ALLOCATED;
public:
    static RefPtr<CSSCalcOperation> create(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide)
    {
        if (!leftSide || !rightSide)
            return nullptr;

        ASSERT(leftSide->category() < CalcOther);
        ASSERT(rightSide->category() < CalcOther);

        auto newCategory = determineCategory(*leftSide, *rightSide, op);
        if (newCategory == CalcOther)
            return nullptr;

        return adoptRef(new CSSCalcOperation(newCategory, op, leftSide.releaseNonNull(), rightSide.releaseNonNull()));
    }

    static RefPtr<CSSCalcOperation> createMinOrMax(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values, CalculationCategory destinationCategory)
    {
        ASSERT(op == CalcMin || op == CalcMax);

        std::optional<CalculationCategory> category = std::nullopt;
        for (auto& value : values) {
            auto valueCategory = resolvedTypeForMinOrMax(value->category(), destinationCategory);

            ASSERT(valueCategory < CalcOther);
            if (!category) {
                if (valueCategory == CalcOther)
                    return nullptr;
                category = valueCategory;
            }

            if (category != valueCategory) {
                if (isSamePair(category.value(), valueCategory, CalcLength, CalcPercentLength)) {
                    category = CalcPercentLength;
                    continue;
                }
                if (isSamePair(category.value(), valueCategory, CalcNumber, CalcPercentNumber)) {
                    category = CalcPercentNumber;
                    continue;
                }
                return nullptr;
            }
        }

        return adoptRef(new CSSCalcOperation(category.value(), op, WTFMove(values)));
    }

    static RefPtr<CSSCalcExpressionNode> createSimplified(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide)
    {
        if (!leftSide || !rightSide)
            return nullptr;

        auto leftCategory = leftSide->category();
        auto rightCategory = rightSide->category();
        ASSERT(leftCategory < CalcOther);
        ASSERT(rightCategory < CalcOther);

        bool isInteger = isIntegerResult(op, *leftSide, *rightSide);

        // Simplify numbers.
        if (leftCategory == CalcNumber && rightCategory == CalcNumber) {
            CSSPrimitiveValue::UnitType evaluationType = CSSPrimitiveValue::CSS_NUMBER;
            return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftSide->doubleValue(), rightSide->doubleValue() }), evaluationType, isInteger);
        }

        // Simplify addition and subtraction between same types.
        if (op == CalcAdd || op == CalcSubtract) {
            if (leftCategory == rightSide->category()) {
                CSSPrimitiveValue::UnitType leftType = leftSide->primitiveType();
                if (hasDoubleValue(leftType)) {
                    CSSPrimitiveValue::UnitType rightType = rightSide->primitiveType();
                    if (leftType == rightType)
                        return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftSide->doubleValue(), rightSide->doubleValue() }), leftType, isInteger);
                    CSSPrimitiveValue::UnitCategory leftUnitCategory = CSSPrimitiveValue::unitCategory(leftType);
                    if (leftUnitCategory != CSSPrimitiveValue::UOther && leftUnitCategory == CSSPrimitiveValue::unitCategory(rightType)) {
                        CSSPrimitiveValue::UnitType canonicalType = CSSPrimitiveValue::canonicalUnitTypeForCategory(leftUnitCategory);
                        if (canonicalType != CSSPrimitiveValue::CSS_UNKNOWN) {
                            double leftValue = leftSide->doubleValue() * CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(leftType);
                            double rightValue = rightSide->doubleValue() * CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(rightType);
                            return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftValue, rightValue }), canonicalType, isInteger);
                        }
                    }
                }
            }
        } else {
            // Simplify multiplying or dividing by a number for simplifiable types.
            ASSERT(op == CalcMultiply || op == CalcDivide);
            auto* numberSide = getNumberSide(*leftSide, *rightSide);
            if (!numberSide)
                return create(op, leftSide.releaseNonNull(), rightSide.releaseNonNull());
            if (numberSide == leftSide && op == CalcDivide)
                return nullptr;
            auto& otherSide = leftSide == numberSide ? *rightSide : *leftSide;

            double number = numberSide->doubleValue();
            if (!std::isfinite(number))
                return nullptr;
            if (op == CalcDivide && !number)
                return nullptr;

            auto otherType = otherSide.primitiveType();
            if (hasDoubleValue(otherType))
                return CSSCalcPrimitiveValue::create(evaluateOperator(op, { otherSide.doubleValue(), number }), otherType, isInteger);
        }

        return create(op, leftSide.releaseNonNull(), rightSide.releaseNonNull());
    }

private:
    bool isZero() const final
    {
        return !doubleValue();
    }

    std::unique_ptr<CalcExpressionNode> createCalcExpression(const CSSToLengthConversionData& conversionData) const final
    {
        Vector<std::unique_ptr<CalcExpressionNode>> nodes;
        nodes.reserveInitialCapacity(m_children.size());

        for (auto& child : m_children) {
            auto node = child->createCalcExpression(conversionData);
            if (!node)
                return nullptr;
            nodes.uncheckedAppend(WTFMove(node));
        }
        return std::make_unique<CalcExpressionOperation>(WTFMove(nodes), m_operator);
    }

    double doubleValue() const final
    {
        Vector<double> doubleValues;
        for (auto& child : m_children)
            doubleValues.append(child->doubleValue());
        return evaluate(doubleValues);
    }

    double computeLengthPx(const CSSToLengthConversionData& conversionData) const final
    {
        Vector<double> doubleValues;
        for (auto& child : m_children)
            doubleValues.append(child->computeLengthPx(conversionData));
        return evaluate(doubleValues);
    }

    static String buildCssText(Vector<String> childExpressions, CalcOperator op)
    {
        StringBuilder result;
        result.append('(');
        switch (op) {
        case CalcAdd:
        case CalcSubtract:
        case CalcMultiply:
        case CalcDivide:
            ASSERT(childExpressions.size() == 2);
            result.append(childExpressions[0]);
            result.append(' ');
            result.append(static_cast<char>(op));
            result.append(' ');
            result.append(childExpressions[1]);
            break;
        case CalcMin:
        case CalcMax:
            ASSERT(!childExpressions.isEmpty());
            const char* functionName = op == CalcMin ? "min(" : "max(";
            result.append(functionName);
            result.append(childExpressions[0]);
            for (size_t i = 1; i < childExpressions.size(); ++i) {
                result.append(',');
                result.append(' ');
                result.append(childExpressions[i]);
            }
            result.append(')');
        }
        result.append(')');

        return result.toString();
    }

    String customCSSText() const final
    {
        Vector<String> cssTexts;
        for (auto& child : m_children)
            cssTexts.append(child->customCSSText());
        return buildCssText(cssTexts, m_operator);
    }

    bool equals(const CSSCalcExpressionNode& exp) const final
    {
        if (type() != exp.type())
            return false;

        const CSSCalcOperation& other = static_cast<const CSSCalcOperation&>(exp);

        if (m_children.size() != other.m_children.size() || m_operator != other.m_operator)
            return false;

        for (size_t i = 0; i < m_children.size(); ++i) {
            if (!compareCSSValue(m_children[i], other.m_children[i]))
                return false;
        }
        return true;
    }

    Type type() const final { return CssCalcOperation; }

    CSSPrimitiveValue::UnitType primitiveType() const final
    {
        switch (category()) {
        case CalcNumber:
#if !ASSERT_DISABLED
            for (auto& child : m_children)
                ASSERT(child->category() == CalcNumber);
#endif
            return CSSPrimitiveValue::CSS_NUMBER;
        case CalcLength:
        case CalcPercent: {
            if (m_children.isEmpty())
                return CSSPrimitiveValue::CSS_UNKNOWN;
            if (m_children.size() == 2) {
                if (m_children[0]->category() == CalcNumber)
                    return m_children[1]->primitiveType();
                if (m_children[1]->category() == CalcNumber)
                    return m_children[0]->primitiveType();
            }
            CSSPrimitiveValue::UnitType firstType = m_children[0]->primitiveType();
            for (auto& child : m_children) {
                if (firstType != child->primitiveType())
                    return CSSPrimitiveValue::CSS_UNKNOWN;
            }
            return firstType;
        }
        case CalcAngle:
            return CSSPrimitiveValue::CSS_DEG;
        case CalcTime:
            return CSSPrimitiveValue::CSS_MS;
        case CalcFrequency:
            return CSSPrimitiveValue::CSS_HZ;
        case CalcPercentLength:
        case CalcPercentNumber:
        case CalcOther:
            return CSSPrimitiveValue::CSS_UNKNOWN;
        }
        ASSERT_NOT_REACHED();
        return CSSPrimitiveValue::CSS_UNKNOWN;
    }

    CSSCalcOperation(CalculationCategory category, CalcOperator op, Ref<CSSCalcExpressionNode>&& leftSide, Ref<CSSCalcExpressionNode>&& rightSide)
        : CSSCalcExpressionNode(category, isIntegerResult(op, leftSide.get(), rightSide.get()))
        , m_operator(op)
    {
        m_children.append(WTFMove(leftSide));
        m_children.append(WTFMove(rightSide));
    }

    CSSCalcOperation(CalculationCategory category, CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& children)
        : CSSCalcExpressionNode(category, isIntegerResult(op, children))
        , m_children(WTFMove(children))
        , m_operator(op)
    {
    }

    static CSSCalcExpressionNode* getNumberSide(CSSCalcExpressionNode& leftSide, CSSCalcExpressionNode& rightSide)
    {
        if (leftSide.category() == CalcNumber)
            return &leftSide;
        if (rightSide.category() == CalcNumber)
            return &rightSide;
        return nullptr;
    }

    double evaluate(const Vector<double>& children) const
    {
        return evaluateOperator(m_operator, children);
    }

    static double evaluateOperator(CalcOperator op, const Vector<double>& children)
    {
        switch (op) {
        case CalcAdd:
            ASSERT(children.size() == 2);
            return children[0] + children[1];
        case CalcSubtract:
            ASSERT(children.size() == 2);
            return children[0] - children[1];
        case CalcMultiply:
            ASSERT(children.size() == 2);
            return children[0] * children[1];
        case CalcDivide:
            ASSERT(children.size() == 1 || children.size() == 2);
            if (children.size() == 1)
                return std::numeric_limits<double>::quiet_NaN();
            return children[0] / children[1];
        case CalcMin: {
            if (children.isEmpty())
                return std::numeric_limits<double>::quiet_NaN();
            double minimum = children[0];
            for (auto child : children)
                minimum = std::min(minimum, child);
            return minimum;
        }
        case CalcMax: {
            if (children.isEmpty())
                return std::numeric_limits<double>::quiet_NaN();
            double maximum = children[0];
            for (auto child : children)
                maximum = std::max(maximum, child);
            return maximum;
        }
        }
        ASSERT_NOT_REACHED();
        return 0;
    }

    Vector<Ref<CSSCalcExpressionNode>> m_children;
    const CalcOperator m_operator;
};

static ParseState checkDepthAndIndex(int* depth, CSSParserTokenRange tokens)
{
    (*depth)++;
    if (tokens.atEnd())
        return NoMoreTokens;
    if (*depth > maxExpressionDepth)
        return TooDeep;
    return OK;
}

class CSSCalcExpressionNodeParser {
public:
    explicit CSSCalcExpressionNodeParser(CalculationCategory destinationCategory)
        : m_destinationCategory(destinationCategory)
    { }

    RefPtr<CSSCalcExpressionNode> parseCalc(CSSParserTokenRange tokens, CSSValueID function)
    {
        Value result;
        tokens.consumeWhitespace();
        bool ok = false;
        if (function == CSSValueCalc || function == CSSValueWebkitCalc)
            ok = parseValueExpression(tokens, 0, &result);
        else if (function == CSSValueMin || function == CSSValueMax)
            ok = parseMinMaxExpression(tokens, function, 0, &result);
        if (!ok || !tokens.atEnd())
            return nullptr;
        return result.value;
    }
    
private:
    struct Value {
        RefPtr<CSSCalcExpressionNode> value;
    };
    
    char operatorValue(const CSSParserToken& token)
    {
        if (token.type() == DelimiterToken)
            return token.delimiter();
        return 0;
    }
    
    bool parseValue(CSSParserTokenRange& tokens, Value* result)
    {
        CSSParserToken token = tokens.consumeIncludingWhitespace();
        if (!(token.type() == NumberToken || token.type() == PercentageToken || token.type() == DimensionToken))
            return false;
        
        CSSPrimitiveValue::UnitType type = token.unitType();
        if (unitCategory(type) == CalcOther)
            return false;
        
        bool isInteger = token.numericValueType() == IntegerValueType || (token.numericValueType() == NumberValueType && token.numericValue() == trunc(token.numericValue()));
        result->value = CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(token.numericValue(), type), isInteger);
        
        return true;
    }
    
    bool parseValueTerm(CSSParserTokenRange& tokens, int depth, Value* result)
    {
        if (checkDepthAndIndex(&depth, tokens) != OK)
            return false;

        auto functionId = tokens.peek().functionId();
        
        if (tokens.peek().type() == LeftParenthesisToken || functionId == CSSValueCalc) {
            CSSParserTokenRange innerRange = tokens.consumeBlock();
            tokens.consumeWhitespace();
            innerRange.consumeWhitespace();
            return parseValueExpression(innerRange, depth, result);
        }

        if (functionId == CSSValueMax || functionId == CSSValueMin) {
            CSSParserTokenRange innerRange = tokens.consumeBlock();
            tokens.consumeWhitespace();
            innerRange.consumeWhitespace();
            return parseMinMaxExpression(innerRange, functionId, depth, result);
        }
        
        return parseValue(tokens, result);
    }
    
    bool parseValueMultiplicativeExpression(CSSParserTokenRange& tokens, int depth, Value* result)
    {
        if (checkDepthAndIndex(&depth, tokens) != OK)
            return false;
        
        if (!parseValueTerm(tokens, depth, result))
            return false;
        
        while (!tokens.atEnd()) {
            char operatorCharacter = operatorValue(tokens.peek());
            if (operatorCharacter != CalcMultiply && operatorCharacter != CalcDivide)
                break;
            tokens.consumeIncludingWhitespace();
            
            Value rhs;
            if (!parseValueTerm(tokens, depth, &rhs))
                return false;
            
            result->value = CSSCalcOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));

            if (!result->value)
                return false;
        }
        
        return true;
    }
    
    bool parseAdditiveValueExpression(CSSParserTokenRange& tokens, int depth, Value* result)
    {
        if (checkDepthAndIndex(&depth, tokens) != OK)
            return false;
        
        if (!parseValueMultiplicativeExpression(tokens, depth, result))
            return false;
        
        while (!tokens.atEnd()) {
            char operatorCharacter = operatorValue(tokens.peek());
            if (operatorCharacter != CalcAdd && operatorCharacter != CalcSubtract)
                break;
            if ((&tokens.peek() - 1)->type() != WhitespaceToken)
                return false; // calc(1px+ 2px) is invalid
            tokens.consume();
            if (tokens.peek().type() != WhitespaceToken)
                return false; // calc(1px +2px) is invalid
            tokens.consumeIncludingWhitespace();
            
            Value rhs;
            if (!parseValueMultiplicativeExpression(tokens, depth, &rhs))
                return false;
            
            result->value = CSSCalcOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
            if (!result->value)
                return false;
        }
        
        return true;
    }

    bool parseMinMaxExpression(CSSParserTokenRange& tokens, CSSValueID minMaxFunction, int depth, Value* result)
    {
        if (checkDepthAndIndex(&depth, tokens) != OK)
            return false;

        CalcOperator op = (minMaxFunction == CSSValueMin) ? CalcMin : CalcMax;

        Value value;
        if (!parseValueExpression(tokens, depth, &value))
            return false;

        Vector<Ref<CSSCalcExpressionNode>> nodes;
        nodes.append(value.value.releaseNonNull());

        while (!tokens.atEnd()) {
            tokens.consumeWhitespace();
            if (tokens.consume().type() != CommaToken)
                return false;
            tokens.consumeWhitespace();

            if (!parseValueExpression(tokens, depth, &value))
                return false;

            nodes.append(value.value.releaseNonNull());
        }

        result->value = CSSCalcOperation::createMinOrMax(op, WTFMove(nodes), m_destinationCategory);
        return result->value;
    }

    bool parseValueExpression(CSSParserTokenRange& tokens, int depth, Value* result)
    {
        return parseAdditiveValueExpression(tokens, depth, result);
    }

    CalculationCategory m_destinationCategory;
};

static inline RefPtr<CSSCalcOperation> createBlendHalf(const Length& length, const RenderStyle& style, float progress)
{
    return CSSCalcOperation::create(CalcMultiply, createCSS(length, style),
        CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(progress, CSSPrimitiveValue::CSS_NUMBER), !progress || progress == 1));
}

static RefPtr<CSSCalcExpressionNode> createCSS(const CalcExpressionNode& node, const RenderStyle& style)
{
    switch (node.type()) {
    case CalcExpressionNodeNumber: {
        float value = toCalcExpressionNumber(node).value();
        return CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(value, CSSPrimitiveValue::CSS_NUMBER), value == std::trunc(value));
    }
    case CalcExpressionNodeLength:
        return createCSS(toCalcExpressionLength(node).length(), style);
    case CalcExpressionNodeOperation: {
        auto& operationNode = toCalcExpressionOperation(node);
        auto& operationChildren = operationNode.children();
        CalcOperator op = operationNode.getOperator();
        if (op == CalcMin || op == CalcMax) {
            Vector<Ref<CSSCalcExpressionNode>> values;
            values.reserveInitialCapacity(operationChildren.size());
            for (auto& child : operationChildren) {
                auto cssNode = createCSS(*child, style);
                if (!cssNode)
                    return nullptr;
                values.uncheckedAppend(*cssNode);
            }
            return CSSCalcOperation::createMinOrMax(operationNode.getOperator(), WTFMove(values), CalcOther);
        }

        if (operationChildren.size() == 2)
            return CSSCalcOperation::create(operationNode.getOperator(), createCSS(*operationChildren[0], style), createCSS(*operationChildren[1], style));

        return nullptr;
    }
    case CalcExpressionNodeBlendLength: {
        // FIXME: (http://webkit.org/b/122036) Create a CSSCalcExpressionNode equivalent of CalcExpressionBlendLength.
        auto& blend = toCalcExpressionBlendLength(node);
        float progress = blend.progress();
        return CSSCalcOperation::create(CalcAdd, createBlendHalf(blend.from(), style, 1 - progress), createBlendHalf(blend.to(), style, progress));
    }
    case CalcExpressionNodeUndefined:
        ASSERT_NOT_REACHED();
    }
    return nullptr;
}

static RefPtr<CSSCalcExpressionNode> createCSS(const Length& length, const RenderStyle& style)
{
    switch (length.type()) {
    case Percent:
    case Fixed:
        return CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(length, style), length.value() == trunc(length.value()));
    case Calculated:
        return createCSS(length.calculationValue().expression(), style);
    case Auto:
    case Intrinsic:
    case MinIntrinsic:
    case MinContent:
    case MaxContent:
    case FillAvailable:
    case FitContent:
    case Relative:
    case Undefined:
        ASSERT_NOT_REACHED();
    }
    return nullptr;
}

RefPtr<CSSCalcValue> CSSCalcValue::create(CSSValueID function, const CSSParserTokenRange& tokens, CalculationCategory destinationCategory, ValueRange range)
{
    CSSCalcExpressionNodeParser parser(destinationCategory);
    auto expression = parser.parseCalc(tokens, function);
    if (!expression)
        return nullptr;
    return adoptRef(new CSSCalcValue(expression.releaseNonNull(), range != ValueRangeAll));
}
    
RefPtr<CSSCalcValue> CSSCalcValue::create(const CalculationValue& value, const RenderStyle& style)
{
    auto expression = createCSS(value.expression(), style);
    if (!expression)
        return nullptr;
    return adoptRef(new CSSCalcValue(expression.releaseNonNull(), value.shouldClampToNonNegative()));
}

} // namespace WebCore