<row><entry>Substring</entry><entry>substring('foobar',0,3)</entry><entry>Return the requested substring</entry></row>
<row><entry>Concat</entry><entry>concat('foo','bar')</entry><entry>Return the
concatenation of the strings</entry></row>
+<row><entry>Ifelse</entry><entry>ifelse('foo' == 'bar','us','them')</entry><entry>Return the branch value according to the condition</entry></row>
</tbody>
</tgroup>
</table>
concat('foo', 'bar') == 'foobar'
</screen>
</section>
+ <section>
+ <title>Ifelse</title>
+ The ifelse function "ifelse(cond, iftrue, ifelse)" returns the
+ "iftrue" or "ifelse" branch value following the boolean
+ condition "cond". For instance:
+ <screen>
+ ifelse(option[230].exists, option[230].hex, 'none')
+ </screen>
+ </section>
</section>
<note>
isc::eval::Token class and represents a certain expression primitive.
Currently supported tokens are:
- - isc::dhcp::TokenString -- represents a constant string, e.g. "MSFT";
+ - isc::dhcp::TokenString -- represents a constant string, e.g. "MSFT".
- isc::dhcp::TokenHexString -- represents a constant string, encoded as
- hex string, e.g. 0x666f6f which is actually "foo";
+ hex string, e.g. 0x666f6f which is actually "foo".
- isc::dhcp::TokenIpAddress -- represents a constant IP address, encoded as
a 4 or 16 byte binary string, e.g., 10.0.0.1 is 0x10000001.
- isc::dhcp::TokenOption -- represents an option in a packet, e.g.
- option[123].text;
+ option[123].text.
- isc::dhcp::TokenRelay4Option -- represents a sub-option inserted by the
DHCPv4 relay, e.g. relay[123].text or relay[123].hex
- isc::dhcp::TokenRelay6Option -- represents a sub-option inserted by
- isc::dhcp::TokenPkt6 -- represents a DHCPv6 packet field (message type
or transaction id).
- isc::dhcp::TokenRelay6Field -- represents a DHCPv6 relay information field.
- - isc::dhcp::TokenEqual -- represents the equal (==) operator;
- - isc::dhcp::TokenSubstring -- represents the substring(text, start, length) operator;
+ - isc::dhcp::TokenEqual -- represents the equal (==) operator.
+ - isc::dhcp::TokenSubstring -- represents the substring(text, start, length) operator.
- isc::dhcp::TokenConcat -- represents the concat operator which
concatenate two other tokens.
+ - isc::dhcp::TokenIfElse == represents the ifelse(cond, iftrue, ifelse) operator.
- isc::dhcp::TokenNot -- the logical not operator.
- isc::dhcp::TokenAnd -- the logical and (strict) operator.
- isc::dhcp::TokenOr -- the logical or (strict) operator (strict means
This debug message indicates that the given binary string is being pushed
onto the value stack. The string is displayed in hex.
+# For use with TokenIfElse
+
+% EVAL_DEBUG_IFELSE_FALSE Popping %1 (false) and %2, leaving %3
+This debug message indicates that the condition is false so
+the iftrue branch value is removed and the ifelse branch value
+is left on the value stack.
+
+% EVAL_DEBUG_IFELSE_TRUE Popping %1 (true) and %2, leaving %3
+This debug message indicates that the condition is true so
+the ifelse branch value is removed and the iftrue branch value
+is left on the value stack.
+
# For use with TokenIpAddress
% EVAL_DEBUG_IPADDRESS Pushing IPAddress %1
EXPECT_TRUE(conc);
}
+ /// @brief checks if the given token is an ifelse operator
+ void checkTokenIfElse(const TokenPtr& token) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenIfElse> alt =
+ boost::dynamic_pointer_cast<TokenIfElse>(token);
+ EXPECT_TRUE(alt);
+ }
+
/// @brief checks if the given expression raises the expected message
/// when it is parsed.
void checkError(const string& expr, const string& msg) {
checkTokenConcat(tmp3);
}
+// Test the parsing of an ifelse expression
+TEST_F(EvalContextTest, ifElse) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("ifelse('foo' == 'bar', 'us', 'them') == 'you'"));
+
+ ASSERT_EQ(8, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(2);
+ TokenPtr tmp2 = eval.expression.at(3);
+ TokenPtr tmp3 = eval.expression.at(4);
+ TokenPtr tmp4 = eval.expression.at(5);
+
+ checkTokenEq(tmp1);
+ checkTokenString(tmp2, "us");
+ checkTokenString(tmp3, "them");
+ checkTokenIfElse(tmp4);
+}
+
//
// Test some scanner error cases
TEST_F(EvalContextTest, scanErrors) {
"<string>:1.16: syntax error, unexpected ), expecting \",\"");
checkError("concat('foo','bar','') == 'foobar'",
"<string>:1.19: syntax error, unexpected \",\", expecting )");
+ checkError("ifelse('foo'=='bar','foo')",
+ "<string>:1.26: syntax error, unexpected ), expecting \",\"");
+ checkError("ifelse('foo'=='bar','foo','bar','')",
+ "<string>:1.32: syntax error, unexpected \",\", expecting )");
}
// Tests some type error cases
"<string>:1.8-10: syntax error, unexpected and, expecting ==");
checkError("'true' or 'false'",
"<string>:1.8-9: syntax error, unexpected or, expecting ==");
+ // Ifelse requires a boolean condition and string branches.
+ checkError("ifelse('foobar','foo','bar')",
+ "<string>:1.16: syntax error, unexpected \",\", expecting ==");
+ checkError("ifelse('foo'=='bar','foo'=='foo','bar')",
+ "<string>:1.26-27: syntax error, unexpected ==, "
+ "expecting \",\"");
+ checkError("ifelse('foo'=='bar','foo','bar'=='bar')",
+ "<string>:1.32-33: syntax error, unexpected ==, expecting )");
}
EvalContext::PARSER_STRING);
testExpressionNegative<EvalParseError>("pkt6.msgtype == 1", Option::V6,
EvalContext::PARSER_STRING);
+
+ // Check that ifelse works as expecting (it was added explicitely for
+ // the string evaluation).
+ testExpressionString(Option::V4,
+ "ifelse(option[100].exists,'foo','bar')", "foo");
+ testExpressionString(Option::V4,
+ "ifelse(option[200].exists,'foo','bar')", "bar");
}
};
EXPECT_TRUE(checkFile());
}
+// This test checks if a token representing an ifelse is able
+// to select the branch following the condition.
+TEST_F(TokenTest, ifElse) {
+ ASSERT_NO_THROW(t_.reset(new TokenIfElse()));
+
+ // Ifelse requires three values on the stack, try
+ // with 0, 1 and 2 all should throw an exception
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ values_.push("bar");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // The condition must be a boolean
+ values_.push("bar");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+
+ // Check if what it returns
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenIfElse()));
+ values_.push("true");
+ values_.push("foo");
+ values_.push("bar");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("foo", values_.top());
+
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenIfElse()));
+ values_.push("false");
+ values_.push("foo");
+ values_.push("bar");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("bar", values_.top());
+}
+
// This test checks if a token representing a not is able to
// negate a boolean value (with incorrectly built stack).
TEST_F(TokenTest, operatorNotInvalid) {
.arg(toHex(values.top()));
}
+void
+TokenIfElse::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+
+ if (values.size() < 3) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+ "3 values for ifelse, got " << values.size());
+ }
+
+ string iffalse = values.top();
+ values.pop();
+ string iftrue = values.top();
+ values.pop();
+ string cond = values.top();
+ values.pop();
+ bool val = toBool(cond);
+
+ if (val) {
+ values.push(iftrue);
+ } else {
+ values.push(iffalse);
+ }
+
+ // Log what we popped and pushed
+ if (val) {
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IFELSE_TRUE)
+ .arg('\'' + cond + '\'')
+ .arg(toHex(iffalse))
+ .arg(toHex(iftrue));
+ } else {
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IFELSE_FALSE)
+ .arg('\'' +cond + '\'')
+ .arg(toHex(iftrue))
+ .arg(toHex(iffalse));
+ }
+}
+
void
TokenNot::evaluate(Pkt& /*pkt*/, ValueStack& values) {
void evaluate(Pkt& pkt, ValueStack& values);
};
+/// @brief Token that represents an alternative
+///
+/// For example in the sub-expression "ifelse(cond, iftrue, iffalse)"
+/// the boolean "cond" expression is evaluated, if it is true then
+/// the "iftrue" value is returned else the "iffalse" value is returned.
+/// Please note that "iftrue" and "iffalse" must be plain string (vs. boolean)
+/// expressions and they are always evaluated. If you want a similar
+/// operator on boolean expressions it can be built from "and", "or" and
+/// "not" boolean operators.
+class TokenIfElse : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenIfElse() { }
+
+ /// @brief Alternative.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the
+ /// last three results. It does a simple string comparison on the
+ /// condition (third value on the stack) which is required to be
+ /// either "true" or "false", and leaves the second and first
+ /// value if the condition is "true" or "false".
+ ///
+ /// @throw EvalBadStack if there are less than 3 values on stack
+ /// @throw EvalTypeError if the third value (the condition) is not
+ /// either "true" or "false"
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (two items are removed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
/// @brief Token that represents logical negation operator
///
/// For example in the expression "not(option[vendor-class].text == 'MSF')"