Browse Source

[master] Merged trac4233 (new concat string function to classification expression)

Francis Dupont 9 years ago
parent
commit
eec10b436b

+ 6 - 1
ChangeLog

@@ -1,8 +1,13 @@
+1080.	[func]		fdupont
+	Added a concat function in classification which concatenates two
+	strings. 
+	(Trac #4233, git ...)
+
 1079.	[func]		fdupont
 	Added Not, And and Or logical operators, parentheses around
 	logical expressions and option[code].exist logical predicate
 	(to check the presence of an empty option).
-	(Trac #4231, git xxx)
+	(Trac #4231, git 8e01dbe2fe2d8c97f89c20f5bb1d03748a2432e0)
 
 1078.	[func]		tomek
 	Client classification in DHCPv4 has been enhanced. It is now

+ 9 - 0
doc/guide/classify.xml

@@ -224,6 +224,7 @@ sub-option with code "code" from the Relay Agent Information option
 <row><entry>And</entry> <entry>('foo' == 'bar') and ('bar' == 'foo')</entry><entry>Logical and</entry></row>
 <row><entry>Or</entry> <entry>('foo' == 'bar') or ('bar' == 'foo')</entry><entry>Logical or</entry></row>
 <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>
           </tbody>
           </tgroup>
         </table>
@@ -261,6 +262,14 @@ sub-option with code "code" from the Relay Agent Information option
         substring('foobar', 10, 2) == ''
           </screen>
       </section>
+      <section>
+        <title>Concat</title>
+        The concat function "concat(string1, string2)" returns the
+        concatenation of its two arguments. For instance:
+          <screen>
+        concat('foo', 'bar') == 'foobar'
+          </screen>
+       </section>
     </section>
 
   <note>

+ 139 - 128
src/lib/eval/lexer.cc

@@ -460,8 +460,8 @@ static void yy_fatal_error (yyconst char msg[]  );
 	(yy_c_buf_p) = yy_cp;
 
 /* %% [4.0] data tables for the DFA and the user's section 1 definitions go here */
-#define YY_NUM_RULES 25
-#define YY_END_OF_BUFFER 26
+#define YY_NUM_RULES 26
+#define YY_END_OF_BUFFER 27
 /* This struct is not used in this scanner,
    but its presence is necessary. */
 struct yy_trans_info
@@ -469,33 +469,35 @@ struct yy_trans_info
 	flex_int32_t yy_verify;
 	flex_int32_t yy_nxt;
 	};
-static yyconst flex_int16_t yy_acclist[119] =
+static yyconst flex_int16_t yy_acclist[128] =
     {   0,
-       26,   24,   25,    1,   24,   25,    2,   25,   24,   25,
-       19,   24,   25,   20,   24,   25,   23,   24,   25,   24,
-       25,   18,   24,   25,    5,   24,   25,    5,   24,   25,
-       24,   25,   24,   25,16390,   21,   24,   25,   22,   24,
-       25,   24,   25,16390,   24,   25,16390,   24,   25,16390,
-       24,   25,16390,   24,   25,16390,   24,   25,16390,   24,
-       25,16390,   24,   25,16390,    1,    2,    3,    5,    7,
-    16390, 8198,16390,16390,16390,16390,16390,16390,   17,16390,
-    16390,16390,16390,    4,   14,16390,   16,16390,16390,   10,
-    16390,   15,16390,16390,16390,16390,16390,16390,16390,16390,
-
-    16390,    9,16390,16390,16390,16390,16390,   11,16390,    8,
-    16390,   13,16390,16390,16390,16390,   12,16390
+       27,   25,   26,    1,   25,   26,    2,   26,   25,   26,
+       20,   25,   26,   21,   25,   26,   24,   25,   26,   25,
+       26,   19,   25,   26,    5,   25,   26,    5,   25,   26,
+       25,   26,   25,   26,16390,   22,   25,   26,   23,   25,
+       26,   25,   26,16390,   25,   26,16390,   25,   26,16390,
+       25,   26,16390,   25,   26,16390,   25,   26,16390,   25,
+       26,16390,   25,   26,16390,   25,   26,16390,    1,    2,
+        3,    5,    7,16390, 8198,16390,16390,16390,16390,16390,
+    16390,16390,   18,16390,16390,16390,16390,    4,   14,16390,
+       17,16390,16390,16390,   10,16390,   16,16390,16390,16390,
+
+    16390,16390,16390,16390,16390,16390,16390,    9,16390,16390,
+    16390,16390,16390,16390,   15,16390,   11,16390,    8,16390,
+       12,16390,16390,16390,16390,   13,16390
     } ;
 
-static yyconst flex_int16_t yy_accept[76] =
+static yyconst flex_int16_t yy_accept[82] =
     {   0,
         1,    1,    1,    2,    4,    7,    9,   11,   14,   17,
        20,   22,   25,   28,   31,   33,   36,   39,   42,   45,
-       48,   51,   54,   57,   60,   63,   66,   67,   68,   68,
-       69,   70,   70,   71,   71,   71,   72,   73,   74,   75,
-       76,   77,   78,   79,   81,   82,   83,   84,   85,   87,
-       89,   90,   92,   94,   95,   96,   97,   98,   99,  100,
-      101,  102,  104,  105,  106,  107,  108,  110,  112,  114,
-      115,  116,  117,  119,  119
+       48,   51,   54,   57,   60,   63,   66,   69,   70,   71,
+       71,   72,   73,   73,   74,   74,   74,   75,   76,   77,
+       78,   79,   80,   81,   82,   83,   85,   86,   87,   88,
+       89,   91,   93,   94,   95,   97,   99,  100,  101,  102,
+      103,  104,  105,  106,  107,  108,  110,  111,  112,  113,
+      114,  115,  117,  119,  121,  123,  124,  125,  126,  128,
+      128
     } ;
 
 static yyconst flex_int32_t yy_ec[256] =
@@ -509,11 +511,11 @@ static yyconst flex_int32_t yy_ec[256] =
        13,    1,    1,    1,   14,   14,   14,   14,   14,   14,
        15,   15,   15,   15,   15,   15,   15,   15,   15,   15,
        15,   15,   15,   15,   15,   15,   15,   16,   15,   15,
-       17,    1,   18,    1,   19,    1,   20,   21,   14,   22,
+       17,    1,   18,    1,   19,    1,   20,   21,   22,   23,
 
-       23,   14,   24,   25,   26,   15,   15,   27,   15,   28,
-       29,   30,   15,   31,   32,   33,   34,   15,   15,   35,
-       36,   15,    1,    1,    1,    1,    1,    1,    1,    1,
+       24,   14,   25,   26,   27,   15,   15,   28,   15,   29,
+       30,   31,   15,   32,   33,   34,   35,   15,   15,   36,
+       37,   15,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
@@ -530,114 +532,118 @@ static yyconst flex_int32_t yy_ec[256] =
         1,    1,    1,    1,    1
     } ;
 
-static yyconst flex_int32_t yy_meta[37] =
+static yyconst flex_int32_t yy_meta[38] =
     {   0,
         1,    2,    3,    1,    1,    1,    1,    2,    1,    4,
         4,    4,    1,    4,    2,    2,    1,    2,    2,    4,
-        4,    4,    4,    2,    2,    2,    2,    2,    2,    2,
-        2,    2,    2,    2,    2,    2
+        4,    4,    4,    4,    2,    2,    2,    2,    2,    2,
+        2,    2,    2,    2,    2,    2,    2
     } ;
 
-static yyconst flex_int16_t yy_base[78] =
+static yyconst flex_int16_t yy_base[84] =
     {   0,
-        0,    0,  113,  205,  108,  101,   98,  205,  205,  205,
-       27,  205,   30,   33,   87,   45,  205,  205,   64,   22,
-       28,   31,   43,   52,   34,   58,   82,   66,   50,  205,
-       66,    0,  205,   85,   87,   66,  205,   68,   79,   71,
-       81,   84,   91,   93,   95,  104,   99,    0,  101,  108,
-      110,  112,  116,  119,  121,  123,  125,  129,  132,  136,
-      138,  140,  142,  148,  161,  150,  152,  155,  157,  164,
-      159,  169,  167,  205,  197,  200,   48
+        0,    0,  202,  219,  167,  155,  138,  219,  219,  219,
+       28,  219,   31,   34,   94,   46,  219,  219,   66,   22,
+       27,   29,   31,   44,   42,   51,   53,  103,   80,   74,
+      219,   77,    0,  219,   88,   90,   68,  219,   70,   81,
+       72,   84,   86,   92,   95,   74,   88,  104,   99,    0,
+      102,  108,  112,  110,  116,  118,  121,  131,  124,  127,
+      134,  136,  138,  143,  145,  147,  149,  153,  156,  175,
+      160,  162,  165,  167,  169,  172,  177,  182,  180,  219,
+      211,  214,   58
     } ;
 
-static yyconst flex_int16_t yy_def[78] =
+static yyconst flex_int16_t yy_def[84] =
     {   0,
-       74,    1,   74,   74,   74,   74,   75,   74,   74,   74,
-       74,   74,   74,   74,   74,   76,   74,   74,   76,   19,
-       19,   19,   19,   19,   19,   19,   74,   74,   75,   74,
-       74,   77,   74,   74,   19,   19,   74,   19,   19,   19,
-       19,   19,   19,   19,   19,   19,   19,   77,   19,   19,
+       80,    1,   80,   80,   80,   80,   81,   80,   80,   80,
+       80,   80,   80,   80,   80,   82,   80,   80,   82,   19,
+       19,   19,   19,   19,   19,   19,   19,   80,   80,   81,
+       80,   80,   83,   80,   80,   19,   19,   80,   19,   19,
+       19,   19,   19,   19,   19,   19,   19,   19,   19,   83,
        19,   19,   19,   19,   19,   19,   19,   19,   19,   19,
        19,   19,   19,   19,   19,   19,   19,   19,   19,   19,
-       19,   19,   19,    0,   74,   74,   74
+       19,   19,   19,   19,   19,   19,   19,   19,   19,    0,
+       80,   80,   80
     } ;
 
-static yyconst flex_int16_t yy_nxt[242] =
+static yyconst flex_int16_t yy_nxt[257] =
     {   0,
         4,    5,    6,    7,    8,    9,   10,   11,   12,   13,
        14,   14,   15,   16,   16,   16,   17,   18,    4,   19,
-       16,   16,   20,   16,   21,   16,   16,   22,   23,   16,
-       24,   25,   26,   16,   16,   16,   31,   31,   31,   31,
-       31,   31,   31,   31,   31,   32,   34,   34,   36,   36,
-       41,   48,   35,   30,   36,   36,   40,   36,   36,   42,
-       36,   36,   37,   35,   32,   34,   34,   46,   28,   36,
-       36,   35,   43,   44,   45,   31,   31,   31,   36,   36,
-       47,   37,   35,   27,   36,   36,   34,   34,   74,   74,
-       38,   39,   36,   36,   49,   36,   51,   36,   36,   33,
-
-       50,   30,   37,   28,   74,   36,   36,   36,   36,   27,
-       36,   36,   74,   36,   36,   52,   53,   36,   36,   36,
-       36,   55,   36,   54,   56,   36,   36,   36,   36,   74,
-       36,   36,   74,   57,   36,   36,   36,   36,   36,   36,
-       60,   58,   36,   36,   59,   36,   36,   36,   36,   36,
-       36,   36,   36,   74,   61,   36,   36,   62,   36,   36,
-       64,   63,   36,   36,   36,   36,   36,   36,   36,   36,
-       66,   65,   69,   67,   36,   68,   36,   36,   36,   36,
-       70,   36,   36,   36,   36,   36,   72,   36,   36,   71,
-       36,   36,   73,   36,   36,   36,   36,   29,   29,   74,
-
-       29,   36,   36,   36,    3,   74,   74,   74,   74,   74,
-       74,   74,   74,   74,   74,   74,   74,   74,   74,   74,
-       74,   74,   74,   74,   74,   74,   74,   74,   74,   74,
-       74,   74,   74,   74,   74,   74,   74,   74,   74,   74,
-       74
+       16,   20,   16,   21,   16,   22,   16,   16,   23,   24,
+       16,   25,   26,   27,   16,   16,   16,   32,   32,   32,
+       32,   32,   32,   32,   32,   32,   33,   35,   35,   37,
+       37,   41,   43,   36,   37,   37,   37,   37,   37,   37,
+       44,   50,   42,   38,   36,   47,   33,   35,   35,   37,
+       37,   37,   37,   36,   45,   46,   49,   31,   37,   37,
+       37,   37,   29,   38,   36,   48,   32,   32,   32,   35,
+       35,   80,   80,   39,   40,   37,   37,   51,   37,   37,
+
+       53,   37,   37,   52,   28,   38,   34,   80,   37,   37,
+       54,   37,   37,   37,   37,   58,   37,   37,   37,   37,
+       37,   55,   37,   37,   59,   56,   37,   37,   57,   37,
+       37,   37,   37,   61,   60,   37,   37,   37,   37,   37,
+       37,   31,   62,   37,   37,   37,   37,   63,   37,   37,
+       64,   37,   37,   67,   37,   37,   65,   29,   37,   37,
+       66,   37,   37,   37,   37,   37,   37,   69,   28,   68,
+       37,   37,   37,   37,   37,   37,   37,   37,   71,   70,
+       37,   37,   72,   37,   74,   73,   75,   37,   37,   37,
+       37,   76,   37,   37,   37,   37,   37,   37,   77,   37,
+
+       37,   80,   37,   37,   37,   78,   79,   37,   37,   37,
+       37,   30,   30,   80,   30,   37,   37,   37,    3,   80,
+       80,   80,   80,   80,   80,   80,   80,   80,   80,   80,
+       80,   80,   80,   80,   80,   80,   80,   80,   80,   80,
+       80,   80,   80,   80,   80,   80,   80,   80,   80,   80,
+       80,   80,   80,   80,   80,   80
     } ;
 
-static yyconst flex_int16_t yy_chk[242] =
+static yyconst flex_int16_t yy_chk[257] =
     {   0,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    1,    1,    1,    1,    1,   11,   11,   11,   13,
-       13,   13,   14,   14,   14,   13,   16,   16,   20,   20,
-       21,   77,   16,   29,   21,   21,   20,   22,   22,   22,
-       25,   25,   16,   16,   13,   19,   19,   25,   28,   23,
-       23,   19,   23,   23,   24,   31,   31,   31,   24,   24,
-       26,   19,   19,   27,   26,   26,   34,   34,   35,   35,
-       19,   19,   36,   36,   38,   38,   40,   40,   40,   15,
-
-       39,    7,   34,    6,   35,   39,   39,   41,   41,    5,
-       42,   42,    3,   35,   35,   41,   42,   43,   43,   44,
-       44,   45,   45,   43,   46,   47,   47,   49,   49,    0,
-       46,   46,    0,   47,   50,   50,   51,   51,   52,   52,
-       55,   51,   53,   53,   54,   54,   54,   55,   55,   56,
-       56,   57,   57,    0,   56,   58,   58,   57,   59,   59,
-       59,   58,   60,   60,   61,   61,   62,   62,   63,   63,
-       61,   60,   65,   63,   64,   64,   66,   66,   67,   67,
-       66,   68,   68,   69,   69,   71,   71,   65,   65,   70,
-       70,   70,   72,   73,   73,   72,   72,   75,   75,    0,
-
-       75,   76,   76,   76,   74,   74,   74,   74,   74,   74,
-       74,   74,   74,   74,   74,   74,   74,   74,   74,   74,
-       74,   74,   74,   74,   74,   74,   74,   74,   74,   74,
-       74,   74,   74,   74,   74,   74,   74,   74,   74,   74,
-       74
+        1,    1,    1,    1,    1,    1,    1,   11,   11,   11,
+       13,   13,   13,   14,   14,   14,   13,   16,   16,   20,
+       20,   20,   22,   16,   21,   21,   22,   22,   23,   23,
+       23,   83,   21,   16,   16,   25,   13,   19,   19,   25,
+       25,   24,   24,   19,   24,   24,   27,   30,   26,   26,
+       27,   27,   29,   19,   19,   26,   32,   32,   32,   35,
+       35,   36,   36,   19,   19,   37,   37,   39,   39,   41,
+
+       41,   46,   46,   40,   28,   35,   15,   36,   40,   40,
+       42,   42,   42,   43,   43,   47,   47,   36,   36,   44,
+       44,   43,   45,   45,   48,   44,   49,   49,   45,   51,
+       51,   48,   48,   53,   49,   52,   52,   54,   54,   53,
+       53,    7,   54,   55,   55,   56,   56,   57,   57,   57,
+       58,   59,   59,   61,   60,   60,   59,    6,   58,   58,
+       60,   61,   61,   62,   62,   63,   63,   63,    5,   62,
+       64,   64,   65,   65,   66,   66,   67,   67,   65,   64,
+       68,   68,   67,   69,   69,   68,   70,   71,   71,   72,
+       72,   71,   73,   73,   74,   74,   75,   75,   76,   76,
+
+       76,    3,   70,   70,   77,   77,   78,   79,   79,   78,
+       78,   81,   81,    0,   81,   82,   82,   82,   80,   80,
+       80,   80,   80,   80,   80,   80,   80,   80,   80,   80,
+       80,   80,   80,   80,   80,   80,   80,   80,   80,   80,
+       80,   80,   80,   80,   80,   80,   80,   80,   80,   80,
+       80,   80,   80,   80,   80,   80
     } ;
 
 /* Table of booleans, true if rule could match eol. */
-static yyconst flex_int32_t yy_rule_can_match_eol[26] =
+static yyconst flex_int32_t yy_rule_can_match_eol[27] =
     {   0,
 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
-    0, 0, 0, 0, 0, 0,     };
+    0, 0, 0, 0, 0, 0, 0,     };
 
 extern int yy_flex_debug;
 int yy_flex_debug = 1;
 
-static yyconst flex_int16_t yy_rule_linenum[25] =
+static yyconst flex_int16_t yy_rule_linenum[26] =
     {   0,
        78,   82,   88,   98,  104,  118,  125,  126,  127,  128,
       129,  130,  131,  132,  133,  134,  135,  136,  137,  138,
-      139,  140,  141,  143
+      139,  140,  141,  142,  144
     } ;
 
 static yy_state_type *yy_state_buf=0, *yy_state_ptr=0;
@@ -714,7 +720,7 @@ static isc::eval::location loc;
 // by moving it ahead by yyleng bytes. yyleng specifies the length of the
 // currently matched token.
 #define YY_USER_ACTION  loc.columns(yyleng);
-#line 718 "lexer.cc"
+#line 724 "lexer.cc"
 
 #define INITIAL 0
 
@@ -962,7 +968,7 @@ YY_DECL
     loc.step();
 
 
-#line 966 "lexer.cc"
+#line 972 "lexer.cc"
 
 	if ( !(yy_init) )
 		{
@@ -1030,14 +1036,14 @@ yy_match:
 			while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 				{
 				yy_current_state = (int) yy_def[yy_current_state];
-				if ( yy_current_state >= 75 )
+				if ( yy_current_state >= 81 )
 					yy_c = yy_meta[(unsigned int) yy_c];
 				}
 			yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
 			*(yy_state_ptr)++ = yy_current_state;
 			++yy_cp;
 			}
-		while ( yy_current_state != 74 );
+		while ( yy_current_state != 80 );
 
 yy_find_action:
 /* %% [10.0] code to find the action number goes here */
@@ -1101,13 +1107,13 @@ do_action:	/* This label is used only to access EOF actions. */
 			{
 			if ( yy_act == 0 )
 				fprintf( stderr, "--scanner backing up\n" );
-			else if ( yy_act < 25 )
+			else if ( yy_act < 26 )
 				fprintf( stderr, "--accepting rule at line %ld (\"%s\")\n",
 				         (long)yy_rule_linenum[yy_act], yytext );
-			else if ( yy_act == 25 )
+			else if ( yy_act == 26 )
 				fprintf( stderr, "--accepting default rule (\"%s\")\n",
 				         yytext );
-			else if ( yy_act == 26 )
+			else if ( yy_act == 27 )
 				fprintf( stderr, "--(end of buffer or a NUL)\n" );
 			else
 				fprintf( stderr, "--EOF (start condition %d)\n", YY_START );
@@ -1212,12 +1218,12 @@ return isc::eval::EvalParser::make_EXISTS(loc);
 case 12:
 YY_RULE_SETUP
 #line 130 "lexer.ll"
-return isc::eval::EvalParser::make_SUBSTRING(loc);
+return isc::eval::EvalParser::make_RELAY4(loc);
 	YY_BREAK
 case 13:
 YY_RULE_SETUP
 #line 131 "lexer.ll"
-return isc::eval::EvalParser::make_RELAY4(loc);
+return isc::eval::EvalParser::make_SUBSTRING(loc);
 	YY_BREAK
 case 14:
 YY_RULE_SETUP
@@ -1227,63 +1233,68 @@ return isc::eval::EvalParser::make_ALL(loc);
 case 15:
 YY_RULE_SETUP
 #line 133 "lexer.ll"
-return isc::eval::EvalParser::make_NOT(loc);
+return isc::eval::EvalParser::make_CONCAT(loc);
 	YY_BREAK
 case 16:
 YY_RULE_SETUP
 #line 134 "lexer.ll"
-return isc::eval::EvalParser::make_AND(loc);
+return isc::eval::EvalParser::make_NOT(loc);
 	YY_BREAK
 case 17:
 YY_RULE_SETUP
 #line 135 "lexer.ll"
-return isc::eval::EvalParser::make_OR(loc);
+return isc::eval::EvalParser::make_AND(loc);
 	YY_BREAK
 case 18:
 YY_RULE_SETUP
 #line 136 "lexer.ll"
-return isc::eval::EvalParser::make_DOT(loc);
+return isc::eval::EvalParser::make_OR(loc);
 	YY_BREAK
 case 19:
 YY_RULE_SETUP
 #line 137 "lexer.ll"
-return isc::eval::EvalParser::make_LPAREN(loc);
+return isc::eval::EvalParser::make_DOT(loc);
 	YY_BREAK
 case 20:
 YY_RULE_SETUP
 #line 138 "lexer.ll"
-return isc::eval::EvalParser::make_RPAREN(loc);
+return isc::eval::EvalParser::make_LPAREN(loc);
 	YY_BREAK
 case 21:
 YY_RULE_SETUP
 #line 139 "lexer.ll"
-return isc::eval::EvalParser::make_LBRACKET(loc);
+return isc::eval::EvalParser::make_RPAREN(loc);
 	YY_BREAK
 case 22:
 YY_RULE_SETUP
 #line 140 "lexer.ll"
-return isc::eval::EvalParser::make_RBRACKET(loc);
+return isc::eval::EvalParser::make_LBRACKET(loc);
 	YY_BREAK
 case 23:
 YY_RULE_SETUP
 #line 141 "lexer.ll"
-return isc::eval::EvalParser::make_COMA(loc);
+return isc::eval::EvalParser::make_RBRACKET(loc);
 	YY_BREAK
 case 24:
 YY_RULE_SETUP
-#line 143 "lexer.ll"
+#line 142 "lexer.ll"
+return isc::eval::EvalParser::make_COMA(loc);
+	YY_BREAK
+case 25:
+YY_RULE_SETUP
+#line 144 "lexer.ll"
 driver.error (loc, "Invalid character: " + std::string(yytext));
 	YY_BREAK
 case YY_STATE_EOF(INITIAL):
-#line 144 "lexer.ll"
+#line 145 "lexer.ll"
 return isc::eval::EvalParser::make_END(loc);
 	YY_BREAK
-case 25:
+case 26:
 YY_RULE_SETUP
-#line 145 "lexer.ll"
+#line 146 "lexer.ll"
 ECHO;
 	YY_BREAK
-#line 1287 "lexer.cc"
+#line 1298 "lexer.cc"
 
 	case YY_END_OF_BUFFER:
 		{
@@ -1563,7 +1574,7 @@ static int yy_get_next_buffer (void)
 		while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 			{
 			yy_current_state = (int) yy_def[yy_current_state];
-			if ( yy_current_state >= 75 )
+			if ( yy_current_state >= 81 )
 				yy_c = yy_meta[(unsigned int) yy_c];
 			}
 		yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
@@ -1591,11 +1602,11 @@ static int yy_get_next_buffer (void)
 	while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 		{
 		yy_current_state = (int) yy_def[yy_current_state];
-		if ( yy_current_state >= 75 )
+		if ( yy_current_state >= 81 )
 			yy_c = yy_meta[(unsigned int) yy_c];
 		}
 	yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
-	yy_is_jam = (yy_current_state == 74);
+	yy_is_jam = (yy_current_state == 80);
 	if ( ! yy_is_jam )
 		*(yy_state_ptr)++ = yy_current_state;
 
@@ -2353,7 +2364,7 @@ void yyfree (void * ptr )
 
 /* %ok-for-header */
 
-#line 145 "lexer.ll"
+#line 146 "lexer.ll"
 
 
 

+ 2 - 1
src/lib/eval/lexer.ll

@@ -127,9 +127,10 @@ blank [ \t]
 "text"      return isc::eval::EvalParser::make_TEXT(loc);
 "hex"       return isc::eval::EvalParser::make_HEX(loc);
 "exists"    return isc::eval::EvalParser::make_EXISTS(loc);
-"substring" return isc::eval::EvalParser::make_SUBSTRING(loc);
 "relay4"    return isc::eval::EvalParser::make_RELAY4(loc);
+"substring" return isc::eval::EvalParser::make_SUBSTRING(loc);
 "all"       return isc::eval::EvalParser::make_ALL(loc);
+"concat"    return isc::eval::EvalParser::make_CONCAT(loc);
 "not"       return isc::eval::EvalParser::make_NOT(loc);
 "and"       return isc::eval::EvalParser::make_AND(loc);
 "or"        return isc::eval::EvalParser::make_OR(loc);

+ 1 - 1
src/lib/eval/location.hh

@@ -1,4 +1,4 @@
-// Generated 20160219
+// Generated at 20160219
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Locations for Bison parsers in C++

+ 100 - 99
src/lib/eval/parser.cc

@@ -255,11 +255,10 @@ namespace isc { namespace eval {
         value.move< TokenOption::RepresentationType > (that.value);
         break;
 
-      case 20: // "constant string"
-      case 21: // "integer"
-      case 22: // "constant hexstring"
-      case 23: // "option name"
-      case 24: // TOKEN
+      case 21: // "constant string"
+      case 22: // "integer"
+      case 23: // "constant hexstring"
+      case 24: // "option name"
         value.move< std::string > (that.value);
         break;
 
@@ -286,11 +285,10 @@ namespace isc { namespace eval {
         value.copy< TokenOption::RepresentationType > (that.value);
         break;
 
-      case 20: // "constant string"
-      case 21: // "integer"
-      case 22: // "constant hexstring"
-      case 23: // "option name"
-      case 24: // TOKEN
+      case 21: // "constant string"
+      case 22: // "integer"
+      case 23: // "constant hexstring"
+      case 24: // "option name"
         value.copy< std::string > (that.value);
         break;
 
@@ -334,53 +332,46 @@ namespace isc { namespace eval {
         << yysym.location << ": ";
     switch (yytype)
     {
-            case 20: // "constant string"
+            case 21: // "constant string"
 
 #line 71 "parser.yy" // lalr1.cc:636
         { yyoutput << yysym.value.template as< std::string > (); }
-#line 342 "parser.cc" // lalr1.cc:636
+#line 340 "parser.cc" // lalr1.cc:636
         break;
 
-      case 21: // "integer"
+      case 22: // "integer"
 
 #line 71 "parser.yy" // lalr1.cc:636
         { yyoutput << yysym.value.template as< std::string > (); }
-#line 349 "parser.cc" // lalr1.cc:636
+#line 347 "parser.cc" // lalr1.cc:636
         break;
 
-      case 22: // "constant hexstring"
+      case 23: // "constant hexstring"
 
 #line 71 "parser.yy" // lalr1.cc:636
         { yyoutput << yysym.value.template as< std::string > (); }
-#line 356 "parser.cc" // lalr1.cc:636
+#line 354 "parser.cc" // lalr1.cc:636
         break;
 
-      case 23: // "option name"
+      case 24: // "option name"
 
 #line 71 "parser.yy" // lalr1.cc:636
         { yyoutput << yysym.value.template as< std::string > (); }
-#line 363 "parser.cc" // lalr1.cc:636
-        break;
-
-      case 24: // TOKEN
-
-#line 71 "parser.yy" // lalr1.cc:636
-        { yyoutput << yysym.value.template as< std::string > (); }
-#line 370 "parser.cc" // lalr1.cc:636
+#line 361 "parser.cc" // lalr1.cc:636
         break;
 
       case 29: // option_code
 
 #line 71 "parser.yy" // lalr1.cc:636
         { yyoutput << yysym.value.template as< uint16_t > (); }
-#line 377 "parser.cc" // lalr1.cc:636
+#line 368 "parser.cc" // lalr1.cc:636
         break;
 
       case 30: // option_repr_type
 
 #line 71 "parser.yy" // lalr1.cc:636
         { yyoutput << yysym.value.template as< TokenOption::RepresentationType > (); }
-#line 384 "parser.cc" // lalr1.cc:636
+#line 375 "parser.cc" // lalr1.cc:636
         break;
 
 
@@ -584,11 +575,10 @@ namespace isc { namespace eval {
         yylhs.value.build< TokenOption::RepresentationType > ();
         break;
 
-      case 20: // "constant string"
-      case 21: // "integer"
-      case 22: // "constant hexstring"
-      case 23: // "option name"
-      case 24: // TOKEN
+      case 21: // "constant string"
+      case 22: // "integer"
+      case 23: // "constant hexstring"
+      case 24: // "option name"
         yylhs.value.build< std::string > ();
         break;
 
@@ -619,7 +609,7 @@ namespace isc { namespace eval {
                     TokenPtr neg(new TokenNot());
                     ctx.expression.push_back(neg);
                 }
-#line 623 "parser.cc" // lalr1.cc:859
+#line 613 "parser.cc" // lalr1.cc:859
     break;
 
   case 5:
@@ -628,7 +618,7 @@ namespace isc { namespace eval {
                     TokenPtr neg(new TokenAnd());
                     ctx.expression.push_back(neg);
                 }
-#line 632 "parser.cc" // lalr1.cc:859
+#line 622 "parser.cc" // lalr1.cc:859
     break;
 
   case 6:
@@ -637,7 +627,7 @@ namespace isc { namespace eval {
                     TokenPtr neg(new TokenOr());
                     ctx.expression.push_back(neg);
                 }
-#line 641 "parser.cc" // lalr1.cc:859
+#line 631 "parser.cc" // lalr1.cc:859
     break;
 
   case 7:
@@ -646,7 +636,7 @@ namespace isc { namespace eval {
                     TokenPtr eq(new TokenEqual());
                     ctx.expression.push_back(eq);
                 }
-#line 650 "parser.cc" // lalr1.cc:859
+#line 640 "parser.cc" // lalr1.cc:859
     break;
 
   case 8:
@@ -655,7 +645,7 @@ namespace isc { namespace eval {
                     TokenPtr opt(new TokenOption(yystack_[3].value.as< uint16_t > (), TokenOption::EXISTS));
                     ctx.expression.push_back(opt);
                 }
-#line 659 "parser.cc" // lalr1.cc:859
+#line 649 "parser.cc" // lalr1.cc:859
     break;
 
   case 9:
@@ -664,7 +654,7 @@ namespace isc { namespace eval {
                       TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ()));
                       ctx.expression.push_back(str);
                   }
-#line 668 "parser.cc" // lalr1.cc:859
+#line 658 "parser.cc" // lalr1.cc:859
     break;
 
   case 10:
@@ -673,7 +663,7 @@ namespace isc { namespace eval {
                       TokenPtr hex(new TokenHexString(yystack_[0].value.as< std::string > ()));
                       ctx.expression.push_back(hex);
                   }
-#line 677 "parser.cc" // lalr1.cc:859
+#line 667 "parser.cc" // lalr1.cc:859
     break;
 
   case 11:
@@ -682,7 +672,7 @@ namespace isc { namespace eval {
                       TokenPtr opt(new TokenOption(yystack_[3].value.as< uint16_t > (), yystack_[0].value.as< TokenOption::RepresentationType > ()));
                       ctx.expression.push_back(opt);
                   }
-#line 686 "parser.cc" // lalr1.cc:859
+#line 676 "parser.cc" // lalr1.cc:859
     break;
 
   case 12:
@@ -706,7 +696,7 @@ namespace isc { namespace eval {
                          error(yystack_[5].location, "relay4 can only be used in DHCPv4.");
                      }
                   }
-#line 710 "parser.cc" // lalr1.cc:859
+#line 700 "parser.cc" // lalr1.cc:859
     break;
 
   case 13:
@@ -715,70 +705,79 @@ namespace isc { namespace eval {
                       TokenPtr sub(new TokenSubstring());
                       ctx.expression.push_back(sub);
                   }
-#line 719 "parser.cc" // lalr1.cc:859
+#line 709 "parser.cc" // lalr1.cc:859
+    break;
+
+  case 14:
+#line 152 "parser.yy" // lalr1.cc:859
+    {
+                      TokenPtr conc(new TokenConcat());
+                      ctx.expression.push_back(conc);
+                  }
+#line 718 "parser.cc" // lalr1.cc:859
     break;
 
   case 15:
-#line 156 "parser.yy" // lalr1.cc:859
+#line 159 "parser.yy" // lalr1.cc:859
     {
                      yylhs.value.as< uint16_t > () = ctx.convertOptionCode(yystack_[0].value.as< std::string > (), yystack_[0].location);
                  }
-#line 727 "parser.cc" // lalr1.cc:859
+#line 726 "parser.cc" // lalr1.cc:859
     break;
 
   case 16:
-#line 160 "parser.yy" // lalr1.cc:859
+#line 163 "parser.yy" // lalr1.cc:859
     {
                      yylhs.value.as< uint16_t > () = ctx.convertOptionName(yystack_[0].value.as< std::string > (), yystack_[0].location);
                  }
-#line 735 "parser.cc" // lalr1.cc:859
+#line 734 "parser.cc" // lalr1.cc:859
     break;
 
   case 17:
-#line 166 "parser.yy" // lalr1.cc:859
+#line 169 "parser.yy" // lalr1.cc:859
     {
                           yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::TEXTUAL;
                       }
-#line 743 "parser.cc" // lalr1.cc:859
+#line 742 "parser.cc" // lalr1.cc:859
     break;
 
   case 18:
-#line 170 "parser.yy" // lalr1.cc:859
+#line 173 "parser.yy" // lalr1.cc:859
     {
                           yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::HEXADECIMAL;
                       }
-#line 751 "parser.cc" // lalr1.cc:859
+#line 750 "parser.cc" // lalr1.cc:859
     break;
 
   case 19:
-#line 176 "parser.yy" // lalr1.cc:859
+#line 179 "parser.yy" // lalr1.cc:859
     {
                      TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ()));
                      ctx.expression.push_back(str);
                  }
-#line 760 "parser.cc" // lalr1.cc:859
+#line 759 "parser.cc" // lalr1.cc:859
     break;
 
   case 20:
-#line 183 "parser.yy" // lalr1.cc:859
+#line 186 "parser.yy" // lalr1.cc:859
     {
                       TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ()));
                       ctx.expression.push_back(str);
                   }
-#line 769 "parser.cc" // lalr1.cc:859
+#line 768 "parser.cc" // lalr1.cc:859
     break;
 
   case 21:
-#line 188 "parser.yy" // lalr1.cc:859
+#line 191 "parser.yy" // lalr1.cc:859
     {
                      TokenPtr str(new TokenString("all"));
                      ctx.expression.push_back(str);
                  }
-#line 778 "parser.cc" // lalr1.cc:859
+#line 777 "parser.cc" // lalr1.cc:859
     break;
 
 
-#line 782 "parser.cc" // lalr1.cc:859
+#line 781 "parser.cc" // lalr1.cc:859
             default:
               break;
             }
@@ -1033,75 +1032,77 @@ namespace isc { namespace eval {
   }
 
 
-  const signed char EvalParser::yypact_ninf_ = -11;
+  const signed char EvalParser::yypact_ninf_ = -14;
 
   const signed char EvalParser::yytable_ninf_ = -1;
 
   const signed char
   EvalParser::yypact_[] =
   {
-      -4,    -8,     9,    -4,    12,    -4,   -11,   -11,   -11,    29,
-       7,    37,    16,    -1,   -11,    16,     0,   -11,    -4,    -4,
-      -1,   -11,   -11,    23,    25,    26,    28,   -11,   -11,    38,
-     -11,    30,    16,    31,    32,    22,    34,   -11,    33,    27,
-     -11,   -11,   -11,   -11,    35,    11,   -11,    27,   -11,   -11,
-      39,   -11
+      -2,   -12,    -4,     3,    -2,    14,    -2,   -14,   -14,    39,
+      33,    40,    16,     6,     6,   -14,    16,    17,   -14,    -2,
+      -2,     6,   -14,   -14,    25,    27,    28,    31,    29,   -14,
+     -14,    42,   -14,    36,    16,    26,     6,    37,    24,    34,
+     -14,    41,    35,    18,   -14,   -14,   -14,   -14,    43,     0,
+     -14,   -14,    18,   -14,   -14,    38,   -14
   };
 
   const unsigned char
   EvalParser::yydefact_[] =
   {
-       0,     0,     0,     0,     0,     0,     9,    10,    14,     0,
-       2,     0,     0,     0,     4,     0,     0,     1,     0,     0,
-       0,    15,    16,     0,     0,     0,     0,     3,     5,     6,
-       7,     0,     0,     0,     0,     0,     0,    19,     0,     0,
-      17,    18,     8,    11,     0,     0,    12,     0,    21,    20,
-       0,    13
+       0,     0,     0,     0,     0,     0,     0,     9,    10,     0,
+       2,     0,     0,     0,     0,     4,     0,     0,     1,     0,
+       0,     0,    15,    16,     0,     0,     0,     0,     0,     3,
+       5,     6,     7,     0,     0,     0,     0,     0,     0,     0,
+      19,     0,     0,     0,    17,    18,     8,    11,     0,     0,
+      14,    12,     0,    21,    20,     0,    13
   };
 
   const signed char
   EvalParser::yypgoto_[] =
   {
-     -11,   -11,     8,    15,   -10,    18,   -11,   -11
+     -14,   -14,    12,   -13,   -10,    19,   -14,   -14
   };
 
   const signed char
   EvalParser::yydefgoto_[] =
   {
-      -1,     9,    10,    11,    23,    43,    38,    50
+      -1,     9,    10,    11,    24,    47,    41,    55
   };
 
   const unsigned char
   EvalParser::yytable_[] =
   {
-       1,     2,     3,    24,     2,    26,     4,    18,    19,     4,
-      12,    14,     5,    16,    18,    19,     6,    27,     7,     6,
-       8,     7,    36,     8,    48,    13,    28,    29,    25,    17,
-      15,    40,    49,    41,    42,    30,    40,    21,    41,    22,
-      20,    33,    31,    32,    35,    18,    39,    34,    45,    47,
-       0,     0,    37,    44,     0,     0,    51,    46
+      26,    27,     1,     2,     3,     4,    28,    12,    32,     5,
+      25,     2,     3,    13,    53,     6,    15,     5,    17,     7,
+      14,     8,    54,    42,    39,    19,    20,     7,    44,     8,
+      45,    30,    31,    16,    44,    29,    45,    46,    22,    18,
+      23,    19,    20,    21,    35,    33,    34,    36,    40,    37,
+      19,    38,    43,    50,    48,     0,    56,    49,    52,     0,
+       0,     0,    51
   };
 
   const signed char
   EvalParser::yycheck_[] =
   {
-       4,     5,     6,     4,     5,    15,    10,     7,     8,    10,
-      18,     3,    16,     5,     7,     8,    20,    17,    22,    20,
-      24,    22,    32,    24,    13,    16,    18,    19,    13,     0,
-      18,     9,    21,    11,    12,    20,     9,    21,    11,    23,
-       3,    15,    19,    18,    14,     7,    14,    19,    15,    14,
-      -1,    -1,    21,    19,    -1,    -1,    17,    39
+      13,    14,     4,     5,     6,     7,    16,    19,    21,    11,
+       4,     5,     6,    17,    14,    17,     4,    11,     6,    21,
+      17,    23,    22,    36,    34,     8,     9,    21,    10,    23,
+      12,    19,    20,    19,    10,    18,    12,    13,    22,     0,
+      24,     8,     9,     3,    16,    20,    19,    16,    22,    20,
+       8,    15,    15,    18,    20,    -1,    18,    16,    15,    -1,
+      -1,    -1,    43
   };
 
   const unsigned char
   EvalParser::yystos_[] =
   {
-       0,     4,     5,     6,    10,    16,    20,    22,    24,    26,
-      27,    28,    18,    16,    27,    18,    27,     0,     7,     8,
-       3,    21,    23,    29,     4,    28,    29,    17,    27,    27,
-      28,    19,    18,    15,    19,    14,    29,    21,    31,    14,
-       9,    11,    12,    30,    19,    15,    30,    14,    13,    21,
-      32,    17
+       0,     4,     5,     6,     7,    11,    17,    21,    23,    26,
+      27,    28,    19,    17,    17,    27,    19,    27,     0,     8,
+       9,     3,    22,    24,    29,     4,    28,    28,    29,    18,
+      27,    27,    28,    20,    19,    16,    16,    20,    15,    29,
+      22,    31,    28,    15,    10,    12,    13,    30,    20,    16,
+      18,    30,    15,    14,    22,    32,    18
   };
 
   const unsigned char
@@ -1116,7 +1117,7 @@ namespace isc { namespace eval {
   EvalParser::yyr2_[] =
   {
        0,     2,     1,     3,     2,     3,     3,     3,     6,     1,
-       1,     6,     6,     8,     1,     1,     1,     1,     1,     1,
+       1,     6,     6,     8,     6,     1,     1,     1,     1,     1,
        1,     1
   };
 
@@ -1128,10 +1129,10 @@ namespace isc { namespace eval {
   const EvalParser::yytname_[] =
   {
   "\"end of file\"", "error", "$undefined", "\"==\"", "\"option\"",
-  "\"substring\"", "\"not\"", "\"and\"", "\"or\"", "\"text\"",
-  "\"relay4\"", "\"hex\"", "\"exists\"", "\"all\"", "\".\"", "\",\"",
-  "\"(\"", "\")\"", "\"[\"", "\"]\"", "\"constant string\"", "\"integer\"",
-  "\"constant hexstring\"", "\"option name\"", "TOKEN", "$accept",
+  "\"substring\"", "\"concat\"", "\"not\"", "\"and\"", "\"or\"",
+  "\"text\"", "\"relay4\"", "\"hex\"", "\"exists\"", "\"all\"", "\".\"",
+  "\",\"", "\"(\"", "\")\"", "\"[\"", "\"]\"", "\"constant string\"",
+  "\"integer\"", "\"constant hexstring\"", "\"option name\"", "$accept",
   "expression", "bool_expr", "string_expr", "option_code",
   "option_repr_type", "start_expr", "length_expr", YY_NULLPTR
   };
@@ -1141,8 +1142,8 @@ namespace isc { namespace eval {
   EvalParser::yyrline_[] =
   {
        0,    80,    80,    83,    84,    89,    94,    99,   104,   111,
-     116,   121,   126,   146,   151,   155,   159,   165,   169,   175,
-     182,   187
+     116,   121,   126,   146,   151,   158,   162,   168,   172,   178,
+     185,   190
   };
 
   // Print the state stack on the debug stream.
@@ -1177,8 +1178,8 @@ namespace isc { namespace eval {
 
 #line 13 "parser.yy" // lalr1.cc:1167
 } } // isc::eval
-#line 1181 "parser.cc" // lalr1.cc:1167
-#line 194 "parser.yy" // lalr1.cc:1168
+#line 1182 "parser.cc" // lalr1.cc:1167
+#line 197 "parser.yy" // lalr1.cc:1168
 
 void
 isc::eval::EvalParser::error(const location_type& loc,

+ 48 - 53
src/lib/eval/parser.h

@@ -302,7 +302,6 @@ namespace isc { namespace eval {
       // "integer"
       // "constant hexstring"
       // "option name"
-      // TOKEN
       char dummy2[sizeof(std::string)];
 
       // option_code
@@ -333,25 +332,25 @@ namespace isc { namespace eval {
         TOKEN_EQUAL = 258,
         TOKEN_OPTION = 259,
         TOKEN_SUBSTRING = 260,
-        TOKEN_NOT = 261,
-        TOKEN_AND = 262,
-        TOKEN_OR = 263,
-        TOKEN_TEXT = 264,
-        TOKEN_RELAY4 = 265,
-        TOKEN_HEX = 266,
-        TOKEN_EXISTS = 267,
-        TOKEN_ALL = 268,
-        TOKEN_DOT = 269,
-        TOKEN_COMA = 270,
-        TOKEN_LPAREN = 271,
-        TOKEN_RPAREN = 272,
-        TOKEN_LBRACKET = 273,
-        TOKEN_RBRACKET = 274,
-        TOKEN_STRING = 275,
-        TOKEN_INTEGER = 276,
-        TOKEN_HEXSTRING = 277,
-        TOKEN_OPTION_NAME = 278,
-        TOKEN_TOKEN = 279
+        TOKEN_CONCAT = 261,
+        TOKEN_NOT = 262,
+        TOKEN_AND = 263,
+        TOKEN_OR = 264,
+        TOKEN_TEXT = 265,
+        TOKEN_RELAY4 = 266,
+        TOKEN_HEX = 267,
+        TOKEN_EXISTS = 268,
+        TOKEN_ALL = 269,
+        TOKEN_DOT = 270,
+        TOKEN_COMA = 271,
+        TOKEN_LPAREN = 272,
+        TOKEN_RPAREN = 273,
+        TOKEN_LBRACKET = 274,
+        TOKEN_RBRACKET = 275,
+        TOKEN_STRING = 276,
+        TOKEN_INTEGER = 277,
+        TOKEN_HEXSTRING = 278,
+        TOKEN_OPTION_NAME = 279
       };
     };
 
@@ -480,6 +479,10 @@ namespace isc { namespace eval {
 
     static inline
     symbol_type
+    make_CONCAT (const location_type& l);
+
+    static inline
+    symbol_type
     make_NOT (const location_type& l);
 
     static inline
@@ -550,10 +553,6 @@ namespace isc { namespace eval {
     symbol_type
     make_OPTION_NAME (const std::string& v, const location_type& l);
 
-    static inline
-    symbol_type
-    make_TOKEN (const std::string& v, const location_type& l);
-
 
     /// Build a parser object.
     EvalParser (EvalContext& ctx_yyarg);
@@ -759,9 +758,9 @@ namespace isc { namespace eval {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 57,     ///< Last index in yytable_.
+      yylast_ = 62,     ///< Last index in yytable_.
       yynnts_ = 8,  ///< Number of nonterminal symbols.
-      yyfinal_ = 17, ///< Termination state number.
+      yyfinal_ = 18, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
       yyntokens_ = 25  ///< Number of tokens.
@@ -847,11 +846,10 @@ namespace isc { namespace eval {
         value.copy< TokenOption::RepresentationType > (other.value);
         break;
 
-      case 20: // "constant string"
-      case 21: // "integer"
-      case 22: // "constant hexstring"
-      case 23: // "option name"
-      case 24: // TOKEN
+      case 21: // "constant string"
+      case 22: // "integer"
+      case 23: // "constant hexstring"
+      case 24: // "option name"
         value.copy< std::string > (other.value);
         break;
 
@@ -880,11 +878,10 @@ namespace isc { namespace eval {
         value.copy< TokenOption::RepresentationType > (v);
         break;
 
-      case 20: // "constant string"
-      case 21: // "integer"
-      case 22: // "constant hexstring"
-      case 23: // "option name"
-      case 24: // TOKEN
+      case 21: // "constant string"
+      case 22: // "integer"
+      case 23: // "constant hexstring"
+      case 24: // "option name"
         value.copy< std::string > (v);
         break;
 
@@ -958,11 +955,10 @@ namespace isc { namespace eval {
         value.template destroy< TokenOption::RepresentationType > ();
         break;
 
-      case 20: // "constant string"
-      case 21: // "integer"
-      case 22: // "constant hexstring"
-      case 23: // "option name"
-      case 24: // TOKEN
+      case 21: // "constant string"
+      case 22: // "integer"
+      case 23: // "constant hexstring"
+      case 24: // "option name"
         value.template destroy< std::string > ();
         break;
 
@@ -997,11 +993,10 @@ namespace isc { namespace eval {
         value.move< TokenOption::RepresentationType > (s.value);
         break;
 
-      case 20: // "constant string"
-      case 21: // "integer"
-      case 22: // "constant hexstring"
-      case 23: // "option name"
-      case 24: // TOKEN
+      case 21: // "constant string"
+      case 22: // "integer"
+      case 23: // "constant hexstring"
+      case 24: // "option name"
         value.move< std::string > (s.value);
         break;
 
@@ -1096,6 +1091,12 @@ namespace isc { namespace eval {
   }
 
   EvalParser::symbol_type
+  EvalParser::make_CONCAT (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_CONCAT, l);
+  }
+
+  EvalParser::symbol_type
   EvalParser::make_NOT (const location_type& l)
   {
     return symbol_type (token::TOKEN_NOT, l);
@@ -1203,16 +1204,10 @@ namespace isc { namespace eval {
     return symbol_type (token::TOKEN_OPTION_NAME, v, l);
   }
 
-  EvalParser::symbol_type
-  EvalParser::make_TOKEN (const std::string& v, const location_type& l)
-  {
-    return symbol_type (token::TOKEN_TOKEN, v, l);
-  }
-
 
 #line 13 "parser.yy" // lalr1.cc:392
 } } // isc::eval
-#line 1216 "parser.h" // lalr1.cc:392
+#line 1211 "parser.h" // lalr1.cc:392
 
 
 

+ 6 - 3
src/lib/eval/parser.yy

@@ -39,6 +39,7 @@ using namespace isc::eval;
   EQUAL "=="
   OPTION "option"
   SUBSTRING "substring"
+  CONCAT "concat"
   NOT "not"
   AND "and"
   OR "or"
@@ -59,7 +60,6 @@ using namespace isc::eval;
 %token <std::string> INTEGER "integer"
 %token <std::string> HEXSTRING "constant hexstring"
 %token <std::string> OPTION_NAME "option name"
-%token <std::string> TOKEN
 
 %type <uint16_t> option_code
 %type <TokenOption::RepresentationType> option_repr_type
@@ -148,8 +148,11 @@ string_expr : STRING
                       TokenPtr sub(new TokenSubstring());
                       ctx.expression.push_back(sub);
                   }
-            | TOKEN
-                // Temporary unused token to avoid explicit but long errors
+            | CONCAT "(" string_expr "," string_expr ")"
+                  {
+                      TokenPtr conc(new TokenConcat());
+                      ctx.expression.push_back(conc);
+                  }
             ;
 
 option_code : INTEGER

+ 1 - 1
src/lib/eval/position.hh

@@ -1,4 +1,4 @@
-// Generated 20160219
+// Generated at 20160219
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Positions for Bison parsers in C++

+ 1 - 1
src/lib/eval/stack.hh

@@ -1,4 +1,4 @@
-// Generated 20160219
+// Generated at 20160219
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Stack handling for Bison parsers in C++

+ 62 - 27
src/lib/eval/tests/context_unittest.cc

@@ -81,14 +81,6 @@ public:
         EXPECT_EQ(expected_code, opt->getCode());
     }
 
-    /// @brief checks if the given token is a substring operator
-    void checkTokenSubstring(const TokenPtr& token) {
-        ASSERT_TRUE(token);
-        boost::shared_ptr<TokenSubstring> sub =
-            boost::dynamic_pointer_cast<TokenSubstring>(token);
-        EXPECT_TRUE(sub);
-    }
-
     /// @brief check if the given token is relay4 with the expected code
     void checkTokenRelay4(const TokenPtr& token, uint16_t code) {
         ASSERT_TRUE(token);
@@ -101,6 +93,22 @@ public:
         }
     }
 
+    /// @brief checks if the given token is a substring operator
+    void checkTokenSubstring(const TokenPtr& token) {
+        ASSERT_TRUE(token);
+        boost::shared_ptr<TokenSubstring> sub =
+            boost::dynamic_pointer_cast<TokenSubstring>(token);
+        EXPECT_TRUE(sub);
+    }
+
+    /// @brief checks if the given token is a concat operator
+    void checkTokenConcat(const TokenPtr& token) {
+        ASSERT_TRUE(token);
+        boost::shared_ptr<TokenConcat> conc =
+            boost::dynamic_pointer_cast<TokenConcat>(token);
+        EXPECT_TRUE(conc);
+    }
+
     /// @brief checks if the given expression raises the expected message
     /// when it is parsed.
     void checkError(const string& expr, const string& msg) {
@@ -275,6 +283,33 @@ TEST_F(EvalContextTest, optionHex) {
     checkTokenOption(eval.expression.at(0), 123);
 }
 
+// This test checks that the relay[code].hex can be used in expressions.
+TEST_F(EvalContextTest, relay4Option) {
+
+    EvalContext eval(Option::V4);
+    EXPECT_NO_THROW(parsed_ =
+                    eval.parseString("relay4[13].hex == 'thirteen'"));
+    EXPECT_TRUE(parsed_);
+    ASSERT_EQ(3, eval.expression.size());
+
+    TokenPtr tmp1 = eval.expression.at(0);
+    TokenPtr tmp2 = eval.expression.at(1);
+    TokenPtr tmp3 = eval.expression.at(2);
+
+    checkTokenRelay4(tmp1, 13);
+    checkTokenString(tmp2, "thirteen");
+    checkTokenEq(tmp3);
+}
+
+// Verify that relay4[13] is not usable in v6
+// There will be a separate relay accessor for v6.
+TEST_F(EvalContextTest, relay4Error) {
+    universe_ = Option::V6;
+
+    checkError("relay4[13].hex == 'thirteen'",
+               "<string>:1.1-6: relay4 can only be used in DHCPv4.");
+}
+
 // Test parsing of logical operators
 TEST_F(EvalContextTest, logicalOps) {
     // option.exists
@@ -402,31 +437,23 @@ TEST_F(EvalContextTest, substring) {
     checkTokenSubstring(tmp4);
 }
 
-// This test checks that the relay[code].hex can be used in expressions.
-TEST_F(EvalContextTest, relay4Option) {
-
+// Test the parsing of a concat expression
+TEST_F(EvalContextTest, concat) {
     EvalContext eval(Option::V4);
+
     EXPECT_NO_THROW(parsed_ =
-                    eval.parseString("relay4[13].hex == 'thirteen'"));
+        eval.parseString("concat('foo','bar') == 'foobar'"));
     EXPECT_TRUE(parsed_);
-    ASSERT_EQ(3, eval.expression.size());
+
+    ASSERT_EQ(5, eval.expression.size());
 
     TokenPtr tmp1 = eval.expression.at(0);
     TokenPtr tmp2 = eval.expression.at(1);
     TokenPtr tmp3 = eval.expression.at(2);
 
-    checkTokenRelay4(tmp1, 13);
-    checkTokenString(tmp2, "thirteen");
-    checkTokenEq(tmp3);
-}
-
-// Verify that relay4[13] is not usable in v6
-// There will be a separate relay accessor for v6.
-TEST_F(EvalContextTest, relay4Error) {
-    universe_ = Option::V6;
-
-    checkError("relay4[13].hex == 'thirteen'",
-               "<string>:1.1-6: relay4 can only be used in DHCPv4.");
+    checkTokenString(tmp1, "foo");
+    checkTokenString(tmp2, "bar");
+    checkTokenConcat(tmp3);
 }
 
 // Test some scanner error cases
@@ -439,6 +466,7 @@ TEST_F(EvalContextTest, scanErrors) {
     checkError("subtring", "<string>:1.1: Invalid character: s");
     checkError("foo", "<string>:1.1: Invalid character: f");
     checkError(" bar", "<string>:1.2: Invalid character: b");
+    checkError("relay[12].hex == 'foo'", "<string>:1.1: Invalid character: r");
 }
 
 // Tests some scanner/parser error cases
@@ -546,8 +574,7 @@ TEST_F(EvalContextTest, parseErrors) {
                "<string>:1.19-20: syntax error, unexpected ==, "
                "expecting end of file");
     checkError("substring('foobar') == 'f'",
-               "<string>:1.19: syntax error, "
-               "unexpected ), expecting \",\"");
+               "<string>:1.19: syntax error, unexpected ), expecting \",\"");
     checkError("substring('foobar',3) == 'bar'",
                "<string>:1.21: syntax error, unexpected ), expecting \",\"");
     checkError("substring('foobar','3',3) == 'bar'",
@@ -555,6 +582,10 @@ TEST_F(EvalContextTest, parseErrors) {
                "expecting integer");
     checkError("substring('foobar',1,a) == 'foo'",
                "<string>:1.22: Invalid character: a");
+    checkError("concat('foobar') == 'f'",
+               "<string>:1.16: syntax error, unexpected ), expecting \",\"");
+    checkError("concat('foo','bar','') == 'foobar'",
+               "<string>:1.19: syntax error, unexpected \",\", expecting )");
 }
 
 // Tests some type error cases
@@ -568,6 +599,10 @@ TEST_F(EvalContextTest, typeErrors) {
     checkError("substring('foobar',0x32,1) == 'foo'",
                "<string>:1.20-23: syntax error, unexpected constant "
                "hexstring, expecting integer");
+    checkError("concat('foo',3) == 'foo3'",
+               "<string>:1.14: syntax error, unexpected integer");
+    checkError("concat(3,'foo') == '3foo'",
+               "<string>:1.8: syntax error, unexpected integer");
     checkError("('foo' == 'bar') == 'false'",
                "<string>:1.18-19: syntax error, unexpected ==, "
                "expecting end of file");

+ 131 - 109
src/lib/eval/tests/token_unittest.cc

@@ -341,7 +341,7 @@ TEST_F(TokenTest, optionHexString4) {
 }
 
 // This test checks if a token representing an option value is able to check
-// the existence ofthe option from an IPv4 packet.
+// the existence of the option from an IPv4 packet.
 TEST_F(TokenTest, optionExistsString4) {
     TokenPtr found;
     TokenPtr not_found;
@@ -420,7 +420,7 @@ TEST_F(TokenTest, optionHexString6) {
 }
 
 // This test checks if a token representing an option value is able to check
-// the existence ofthe option from an IPv6 packet.
+// the existence of the option from an IPv6 packet.
 TEST_F(TokenTest, optionExistsString6) {
     TokenPtr found;
     TokenPtr not_found;
@@ -441,6 +441,109 @@ TEST_F(TokenTest, optionExistsString6) {
     EXPECT_EQ("true", values_.top());
 }
 
+// This test checks that the existing relay option can be found.
+TEST_F(TokenTest, relayOption) {
+
+    // Insert relay option with sub-options 1 and 13
+    insertRelay4Option();
+
+    // Creating the token should be safe.
+    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));
+
+    // We should be able to evaluate it.
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+    // we should have one value on the stack
+    ASSERT_EQ(1, values_.size());
+
+    // The option should be found and relay[13] should evaluate to the
+    // content of that sub-option, i.e. "thirteen"
+    EXPECT_EQ("thirteen", values_.top());
+}
+
+// This test checks that the code properly handles cases when
+// there is a RAI option, but there's no requested sub-option.
+TEST_F(TokenTest, relayOptionNoSuboption) {
+
+    // Insert relay option with sub-options 1 and 13
+    insertRelay4Option();
+
+    // Creating the token should be safe.
+    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(15, TokenOption::TEXTUAL)));
+
+    // We should be able to evaluate it.
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+    // we should have one value on the stack
+    ASSERT_EQ(1, values_.size());
+
+    // The option should NOT be found (there is no sub-option 15),
+    // so the expression should evaluate to ""
+    EXPECT_EQ("", values_.top());
+}
+
+// This test checks that the code properly handles cases when
+// there's no RAI option at all.
+TEST_F(TokenTest, relayOptionNoRai) {
+
+    // We didn't call insertRelay4Option(), so there's no RAI option.
+
+    // Creating the token should be safe.
+    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));
+
+    // We should be able to evaluate it.
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+    // we should have one value on the stack
+    ASSERT_EQ(1, values_.size());
+
+    // The option should NOT be found (there is no sub-option 13),
+    // so the expression should evaluate to ""
+    EXPECT_EQ("", values_.top());
+}
+
+// This test checks that only the RAI is searched for the requested
+// sub-option.
+TEST_F(TokenTest, relayRAIOnly) {
+
+    // Insert relay option with sub-options 1 and 13
+    insertRelay4Option();
+
+    // Add options 13 and 70 to the packet.
+    OptionPtr opt13(new OptionString(Option::V4, 13, "THIRTEEN"));
+    OptionPtr opt70(new OptionString(Option::V4, 70, "SEVENTY"));
+    pkt4_->addOption(opt13);
+    pkt4_->addOption(opt70);
+
+    // The situation is as follows:
+    // Packet:
+    //  - option 13 (containing "THIRTEEN")
+    //  - option 82 (rai)
+    //      - option 1 (containing "one")
+    //      - option 13 (containing "thirteen")
+
+    // Let's try to get option 13. It should get the one from RAI
+    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("thirteen", values_.top());
+
+    // Try to get option 1. It should get the one from RAI
+    clearStack();
+    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(1, TokenOption::TEXTUAL)));
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("one", values_.top());
+
+    // Try to get option 70. It should fail, as there's no such
+    // sub option in RAI.
+    clearStack();
+    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(70, TokenOption::TEXTUAL)));
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("", values_.top());
+}
+
 // This test checks if a token representing an == operator is able to
 // compare two values (with incorrectly built stack).
 TEST_F(TokenTest, optionEqualInvalid) {
@@ -490,7 +593,7 @@ TEST_F(TokenTest, optionEqualTrue) {
 
 // This test checks if a token representing a not is able to
 // negate a boolean value (with incorrectly built stack).
-TEST_F(TokenTest, optionNotInvalid) {
+TEST_F(TokenTest, operatorNotInvalid) {
 
     ASSERT_NO_THROW(t_.reset(new TokenNot()));
 
@@ -504,7 +607,7 @@ TEST_F(TokenTest, optionNotInvalid) {
 
 // This test checks if a token representing a not operator is able to
 // negate a boolean value.
-TEST_F(TokenTest, optionNot) {
+TEST_F(TokenTest, operatorNot) {
 
     ASSERT_NO_THROW(t_.reset(new TokenNot()));
 
@@ -523,7 +626,7 @@ TEST_F(TokenTest, optionNot) {
 
 // This test checks if a token representing an and is able to
 // conjugate two values (with incorrectly built stack).
-TEST_F(TokenTest, optionAndInvalid) {
+TEST_F(TokenTest, operatorAndInvalid) {
 
     ASSERT_NO_THROW(t_.reset(new TokenAnd()));
 
@@ -547,7 +650,7 @@ TEST_F(TokenTest, optionAndInvalid) {
 
 // This test checks if a token representing an and operator is able to
 // conjugate false with another logical
-TEST_F(TokenTest, optionAndFalse) {
+TEST_F(TokenTest, operatorAndFalse) {
 
     ASSERT_NO_THROW(t_.reset(new TokenAnd()));
 
@@ -574,7 +677,7 @@ TEST_F(TokenTest, optionAndFalse) {
 
 // This test checks if a token representing an and is able to
 // conjugate two true values.
-TEST_F(TokenTest, optionAndTrue) {
+TEST_F(TokenTest, operatorAndTrue) {
 
     ASSERT_NO_THROW(t_.reset(new TokenAnd()));
 
@@ -589,7 +692,7 @@ TEST_F(TokenTest, optionAndTrue) {
 
 // This test checks if a token representing an or is able to
 // combinate two values (with incorrectly built stack).
-TEST_F(TokenTest, optionOrInvalid) {
+TEST_F(TokenTest, operatorOrInvalid) {
 
     ASSERT_NO_THROW(t_.reset(new TokenOr()));
 
@@ -613,7 +716,7 @@ TEST_F(TokenTest, optionOrInvalid) {
 
 // This test checks if a token representing an or is able to
 // conjugate two false values.
-TEST_F(TokenTest, optionOrFalse) {
+TEST_F(TokenTest, operatorOrFalse) {
 
     ASSERT_NO_THROW(t_.reset(new TokenOr()));
 
@@ -628,7 +731,7 @@ TEST_F(TokenTest, optionOrFalse) {
 
 // This test checks if a token representing an == operator is able to
 // conjugate true with another logical
-TEST_F(TokenTest, optionOrTrue) {
+TEST_F(TokenTest, operatorOrTrue) {
 
     ASSERT_NO_THROW(t_.reset(new TokenOr()));
 
@@ -655,7 +758,7 @@ TEST_F(TokenTest, optionOrTrue) {
 
 };
 
-// This test checks if an a token representing a substring request
+// This test checks if a token representing a substring request
 // throws an exception if there aren't enough values on the stack.
 // The stack from the top is: length, start, string.
 // The actual packet is not used.
@@ -663,7 +766,7 @@ TEST_F(TokenTest, substringNotEnoughValues) {
     ASSERT_NO_THROW(t_.reset(new TokenSubstring()));
 
     // Subsring requires three values on the stack, try
-    // with 0, 1 and 2 all should thorw an exception
+    // with 0, 1 and 2 all should throw an exception
     EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
 
     values_.push("");
@@ -790,7 +893,7 @@ TEST_F(TokenTest, substringEquals) {
 
     // The substring values
     // Subsring requires three values on the stack, try
-    // with 0, 1 and 2 all should thorw an exception
+    // with 0, 1 and 2 all should throw an exception
     values_.push("foobar");
     values_.push("1");
     values_.push("4");
@@ -813,7 +916,7 @@ TEST_F(TokenTest, substringEquals) {
 
     // The substring values
     // Subsring requires three values on the stack, try
-    // with 0, 1 and 2 all should thorw an exception
+    // with 0, 1 and 2 all should throw an exception
     values_.push("foobar");
     values_.push("1");
     values_.push("4");
@@ -829,105 +932,24 @@ TEST_F(TokenTest, substringEquals) {
 
 }
 
-// This test checks that the existing relay option can be found.
-TEST_F(TokenTest, relayOption) {
-
-    // Insert relay option with sub-options 1 and 13
-    insertRelay4Option();
-
-    // Creating the token should be safe.
-    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));
-
-    // We should be able to evaluate it.
-    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
-
-    // we should have one value on the stack
-    ASSERT_EQ(1, values_.size());
-
-    // The option should be found and relay[13] should evaluate to the
-    // content of that sub-option, i.e. "thirteen"
-    EXPECT_EQ("thirteen", values_.top());
-}
-
-// This test checks that the code properly handles cases when
-// there is a RAI option, but there's no requested sub-option.
-TEST_F(TokenTest, relayOptionNoSuboption) {
-
-    // Insert relay option with sub-options 1 and 13
-    insertRelay4Option();
-
-    // Creating the token should be safe.
-    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(15, TokenOption::TEXTUAL)));
-
-    // We should be able to evaluate it.
-    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
-
-    // we should have one value on the stack
-    ASSERT_EQ(1, values_.size());
-
-    // The option should NOT be found (there is no sub-option 15),
-    // so the expression should evaluate to ""
-    EXPECT_EQ("", values_.top());
-}
-
-// This test checks that the code properly handles cases when
-// there's no RAI option at all.
-TEST_F(TokenTest, relayOptionNoRai) {
-
-    // We didn't call insertRelay4Option(), so there's no RAI option.
-
-    // Creating the token should be safe.
-    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));
-
-    // We should be able to evaluate it.
-    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
-
-    // we should have one value on the stack
-    ASSERT_EQ(1, values_.size());
-
-    // The option should NOT be found (there is no sub-option 13),
-    // so the expression should evaluate to ""
-    EXPECT_EQ("", values_.top());
-}
-
-// This test checks that only the RAI is searched for the requested
-// sub-option.
-TEST_F(TokenTest, relayRAIOnly) {
-
-    // Insert relay option with sub-options 1 and 13
-    insertRelay4Option();
-
-    // Add options 13 and 70 to the packet.
-    OptionPtr opt13(new OptionString(Option::V4, 13, "THIRTEEN"));
-    OptionPtr opt70(new OptionString(Option::V4, 70, "SEVENTY"));
-    pkt4_->addOption(opt13);
-    pkt4_->addOption(opt70);
+// This test checks if a token representing a concat request
+// throws an exception if there aren't enough values on the stack.
+// The actual packet is not used.
+TEST_F(TokenTest, concat) {
+    ASSERT_NO_THROW(t_.reset(new TokenConcat()));
 
-    // The situation is as follows:
-    // Packet:
-    //  - option 13 (containing "THIRTEEN")
-    //  - option 82 (rai)
-    //      - option 1 (containing "one")
-    //      - option 13 (containing "thirteen")
+    // Concat requires two values on the stack, try
+    // with 0 and 1 both should throw an exception
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
 
-    // Let's try to get option 13. It should get the one from RAI
-    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));
-    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
-    ASSERT_EQ(1, values_.size());
-    EXPECT_EQ("thirteen", values_.top());
+    values_.push("foo");
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
 
-    // Try to get option 1. It should get the one from RAI
-    clearStack();
-    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(1, TokenOption::TEXTUAL)));
+    // Two should work
+    values_.push("bar");
     EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
-    ASSERT_EQ(1, values_.size());
-    EXPECT_EQ("one", values_.top());
 
-    // Try to get option 70. It should fail, as there's no such
-    // sub option in RAI.
-    clearStack();
-    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(70, TokenOption::TEXTUAL)));
-    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+    // Check the result
     ASSERT_EQ(1, values_.size());
-    EXPECT_EQ("", values_.top());
+    EXPECT_EQ("foobar", values_.top());
 }

+ 29 - 12
src/lib/eval/token.cc

@@ -84,6 +84,23 @@ TokenOption::evaluate(const Pkt& pkt, ValueStack& values) {
     values.push(opt_str);
 }
 
+TokenRelay4Option::TokenRelay4Option(const uint16_t option_code,
+                                     const RepresentationType& rep_type)
+    :TokenOption(option_code, rep_type) {
+}
+
+OptionPtr TokenRelay4Option::getOption(const Pkt& pkt) {
+
+    // Check if there is Relay Agent Option.
+    OptionPtr rai = pkt.getOption(DHO_DHCP_AGENT_OPTIONS);
+    if (!rai) {
+        return (OptionPtr());
+    }
+
+    // If there is, try to return its suboption
+    return (rai->getOption(option_code_));
+}
+
 void
 TokenEqual::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
 
@@ -178,21 +195,21 @@ TokenSubstring::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
     values.push(string_str.substr(start_pos, length));
 }
 
-TokenRelay4Option::TokenRelay4Option(const uint16_t option_code,
-                                     const RepresentationType& rep_type)
-    :TokenOption(option_code, rep_type) {
-}
-
-OptionPtr TokenRelay4Option::getOption(const Pkt& pkt) {
+void
+TokenConcat::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
 
-    // Check if there is Relay Agent Option.
-    OptionPtr rai = pkt.getOption(DHO_DHCP_AGENT_OPTIONS);
-    if (!rai) {
-        return (OptionPtr());
+    if (values.size() < 2) {
+        isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+                  "2 values for concat, got " << values.size());
     }
 
-    // If there is, try to return its suboption
-    return (rai->getOption(option_code_));
+    string op1 = values.top();
+    values.pop();
+    string op2 = values.top();
+    values.pop(); // Dammit, std::stack interface is awkward.
+
+    // The top of the stack was evaluated last so this is the right order
+    values.push(op2 + op1);
 }
 
 void

+ 45 - 22
src/lib/eval/token.h

@@ -219,6 +219,34 @@ protected:
     RepresentationType representation_type_; ///< Representation type.
 };
 
+/// @brief Represents a sub-option inserted by the DHCPv4 relay.
+///
+/// DHCPv4 relays insert sub-options in option 82. This token attempts to extract
+/// such sub-options. Note in DHCPv6 it is radically different (possibly
+/// many encapsulation levels), thus there are separate classes for v4 and v6.
+///
+/// This token can represent the following expressions:
+/// relay[13].text - Textual representation of sub-option 13 in RAI (option 82)
+/// relay[13].hex  - Binary representation of sub-option 13 in RAI (option 82)
+/// relay[vendor-class].text - Text representation of sub-option X in RAI (option 82)
+/// relay[vendor-class].hex - Binary representation of sub-option X in RAI (option 82)
+class TokenRelay4Option : public TokenOption {
+public:
+
+    /// @brief Constructor for extracting sub-option from RAI (option 82)
+    ///
+    /// @param option_code code of the requested sub-option
+    /// @param rep_type code representation (currently .hex and .text are supported)
+    TokenRelay4Option(const uint16_t option_code,
+                      const RepresentationType& rep_type);
+
+protected:
+    /// @brief Attempts to obtain specified sub-option of option 82 from the packet
+    /// @param pkt DHCPv4 packet (that hopefully contains option 82)
+    /// @return found sub-option from option 82
+    virtual OptionPtr getOption(const Pkt& pkt);
+};
+
 /// @brief Token that represents equality operator (compares two other tokens)
 ///
 /// For example in the expression option[vendor-class].text == "MSFT"
@@ -300,32 +328,27 @@ public:
     void evaluate(const Pkt& pkt, ValueStack& values);
 };
 
-/// @brief Represents a sub-option inserted by the DHCPv4 relay.
+/// @brief Token that represents concat operator (concatenates two other tokens)
 ///
-/// DHCPv4 relays insert sub-options in option 82. This token attempts to extract
-/// such sub-options. Note in DHCPv6 it is radically different (possibly
-/// many encapsulation levels), thus there are separate classes for v4 and v6.
-///
-/// This token can represent the following expressions:
-/// relay[13].text - Textual representation of sub-option 13 in RAI (option 82)
-/// relay[13].hex  - Binary representation of sub-option 13 in RAI (option 82)
-/// relay[vendor-class].text - Text representation of sub-option X in RAI (option 82)
-/// relay[vendor-class].hex - Binary representation of sub-option X in RAI (option 82)
-class TokenRelay4Option : public TokenOption {
+/// For example in the sub-expression "concat('foo','bar')" the result
+/// of the evaluation is "foobar"
+class TokenConcat : public Token {
 public:
+    /// @brief Constructor (does nothing)
+    TokenConcat() {}
 
-    /// @brief Constructor for extracting sub-option from RAI (option 82)
+    /// @brief Concatenate two values.
     ///
-    /// @param option_code code of the requested sub-option
-    /// @param rep_type code representation (currently .hex and .text are supported)
-    TokenRelay4Option(const uint16_t option_code,
-                      const RepresentationType& rep_type);
-
-protected:
-    /// @brief Attempts to obtain specified sub-option of option 82 from the packet
-    /// @param pkt DHCPv4 packet (that hopefully contains option 82)
-    /// @return found sub-option from option 82
-    virtual OptionPtr getOption(const Pkt& pkt);
+    /// Evaluation does not use packet information, but rather consumes the last
+    /// two parameters. It does a simple string concatenation. It requires
+    /// at least two parameters to be present on stack.
+    ///
+    /// @throw EvalBadStack if there are less than 2 values on stack
+    ///
+    /// @param pkt (unused)
+    /// @param values - stack of values (2 arguments will be popped, 1 result
+    ///        will be pushed)
+    void evaluate(const Pkt& pkt, ValueStack& values);
 };
 
 /// @brief Token that represents logical negation operator