1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "core/css/parser/SizesCalcParser.h"
8 #include "core/css/MediaValues.h"
9 #include "core/css/parser/CSSParserToken.h"
13 SizesCalcParser::SizesCalcParser(CSSParserTokenIterator start, CSSParserTokenIterator end, PassRefPtr<MediaValues> mediaValues)
14 : m_mediaValues(mediaValues)
17 m_isValid = calcToReversePolishNotation(start, end) && calculate();
20 float SizesCalcParser::result() const
26 static bool operatorPriority(UChar cc, bool& highPriority)
28 if (cc == '+' || cc == '-')
30 else if (cc == '*' || cc == '/')
37 bool SizesCalcParser::handleOperator(Vector<CSSParserToken>& stack, const CSSParserToken& token)
39 // If the token is an operator, o1, then:
40 // while there is an operator token, o2, at the top of the stack, and
41 // either o1 is left-associative and its precedence is equal to that of o2,
42 // or o1 has precedence less than that of o2,
43 // pop o2 off the stack, onto the output queue;
44 // push o1 onto the stack.
45 bool stackOperatorPriority;
46 bool incomingOperatorPriority;
48 if (!operatorPriority(token.delimiter(), incomingOperatorPriority))
50 if (!stack.isEmpty() && stack.last().type() == DelimiterToken) {
51 if (!operatorPriority(stack.last().delimiter(), stackOperatorPriority))
53 if (!incomingOperatorPriority || stackOperatorPriority) {
54 appendOperator(stack.last());
62 void SizesCalcParser::appendNumber(const CSSParserToken& token)
65 value.value = token.numericValue();
66 m_valueList.append(value);
69 bool SizesCalcParser::appendLength(const CSSParserToken& token)
73 if (!m_mediaValues->computeLength(token.numericValue(), token.unitType(), result))
76 value.isLength = true;
77 m_valueList.append(value);
81 void SizesCalcParser::appendOperator(const CSSParserToken& token)
84 value.operation = token.delimiter();
85 m_valueList.append(value);
88 bool SizesCalcParser::calcToReversePolishNotation(CSSParserTokenIterator start, CSSParserTokenIterator end)
90 // This method implements the shunting yard algorithm, to turn the calc syntax into a reverse polish notation.
91 // http://en.wikipedia.org/wiki/Shunting-yard_algorithm
93 Vector<CSSParserToken> stack;
94 for (CSSParserTokenIterator it = start; it != end; ++it) {
95 CSSParserTokenType type = it->type();
101 if (!CSSPrimitiveValue::isLength(it->unitType()) || !appendLength(*it))
105 if (!handleOperator(stack, *it))
109 if (it->value() != "calc")
111 // "calc(" is the same as "("
112 case LeftParenthesisToken:
113 // If the token is a left parenthesis, then push it onto the stack.
116 case RightParenthesisToken:
117 // If the token is a right parenthesis:
118 // Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
119 while (!stack.isEmpty() && stack.last().type() != LeftParenthesisToken && stack.last().type() != FunctionToken) {
120 appendOperator(stack.last());
123 // If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
126 // Pop the left parenthesis from the stack, but not onto the output queue.
130 case WhitespaceToken:
136 case PercentageToken:
137 case UnicodeRangeToken:
143 case LeftBracketToken:
144 case RightBraceToken:
145 case RightBracketToken:
152 // When there are no more tokens to read:
153 // While there are still operator tokens in the stack:
154 while (!stack.isEmpty()) {
155 // If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
156 CSSParserTokenType type = stack.last().type();
157 if (type == LeftParenthesisToken || type == FunctionToken)
159 // Pop the operator onto the output queue.
160 appendOperator(stack.last());
166 static bool operateOnStack(Vector<SizesCalcValue>& stack, UChar operation)
168 if (stack.size() < 2)
170 SizesCalcValue rightOperand = stack.last();
172 SizesCalcValue leftOperand = stack.last();
177 if (rightOperand.isLength != leftOperand.isLength)
179 isLength = (rightOperand.isLength && leftOperand.isLength);
180 stack.append(SizesCalcValue(leftOperand.value + rightOperand.value, isLength));
183 if (rightOperand.isLength != leftOperand.isLength)
185 isLength = (rightOperand.isLength && leftOperand.isLength);
186 stack.append(SizesCalcValue(leftOperand.value - rightOperand.value, isLength));
189 if (rightOperand.isLength && leftOperand.isLength)
191 isLength = (rightOperand.isLength || leftOperand.isLength);
192 stack.append(SizesCalcValue(leftOperand.value * rightOperand.value, isLength));
195 if (rightOperand.isLength || rightOperand.value == 0)
197 stack.append(SizesCalcValue(leftOperand.value / rightOperand.value, leftOperand.isLength));
205 bool SizesCalcParser::calculate()
207 Vector<SizesCalcValue> stack;
208 for (const auto& value : m_valueList) {
209 if (value.operation == 0) {
212 if (!operateOnStack(stack, value.operation))
216 if (stack.size() == 1 && stack.last().isLength) {
217 m_result = std::max(clampTo<float>(stack.last().value), (float)0.0);