Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ISC Open Source Projects
Kea
Commits
373d2d91
Commit
373d2d91
authored
Sep 28, 2017
by
Francis Dupont
Browse files
[5363] Added ifelse operator
parent
f0d1ab76
Changes
8
Hide whitespace changes
Inline
Side-by-side
doc/guide/classify.xml
View file @
373d2d91
...
...
@@ -579,6 +579,7 @@
<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>
...
...
@@ -624,6 +625,15 @@ concatenation of the strings</entry></row>
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>
...
...
src/lib/eval/eval.dox
View file @
373d2d91
...
...
@@ -132,13 +132,13 @@ instantiated with the appropriate value and put onto the expression vector.
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
...
...
@@ -149,10 +149,11 @@ instantiated with the appropriate value and put onto the expression vector.
- 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
...
...
src/lib/eval/eval_messages.mes
View file @
373d2d91
...
...
@@ -34,6 +34,18 @@ the value stack. The strings are displayed in hex.
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
...
...
src/lib/eval/tests/context_unittest.cc
View file @
373d2d91
...
...
@@ -406,6 +406,14 @@ public:
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
)
{
...
...
@@ -1209,6 +1217,26 @@ TEST_F(EvalContextTest, concat) {
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
)
{
...
...
@@ -1358,6 +1386,10 @@ TEST_F(EvalContextTest, parseErrors) {
"<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
...
...
@@ -1388,6 +1420,14 @@ TEST_F(EvalContextTest, typeErrors) {
"<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 )"
);
}
...
...
src/lib/eval/tests/evaluate_unittest.cc
View file @
373d2d91
...
...
@@ -490,6 +490,13 @@ TEST_F(ExpressionsTest, evaluateString) {
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"
);
}
};
src/lib/eval/tests/token_unittest.cc
View file @
373d2d91
...
...
@@ -1954,6 +1954,45 @@ TEST_F(TokenTest, concat) {
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
)
{
...
...
src/lib/eval/token.cc
View file @
373d2d91
...
...
@@ -591,6 +591,42 @@ TokenConcat::evaluate(Pkt& /*pkt*/, ValueStack& values) {
.
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
)
{
...
...
src/lib/eval/token.h
View file @
373d2d91
...
...
@@ -694,6 +694,37 @@ public:
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')"
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment