MINOR: sample: make sample_parse_expr() able to return an end pointer

When an end pointer is passed, instead of complaining that a comma is
missing after a keyword, sample_parse_expr() will silently return the
pointer to the current location into this return pointer so that the
caller can continue its parsing. This will be used by more complex
expressions which embed sample expressions, and may even permit to
embed sample expressions into arguments of other expressions.
diff --git a/src/acl.c b/src/acl.c
index 4b8a6d4..1e32271 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -321,7 +321,7 @@
 		 * so, we retrieve a completely parsed expression with args and
 		 * convs already done.
 		 */
-		smp = sample_parse_expr((char **)args, &idx, file, line, err, al);
+		smp = sample_parse_expr((char **)args, &idx, file, line, err, al, NULL);
 		if (!smp) {
 			memprintf(err, "%s in ACL expression '%s'", *err, *args);
 			goto out_return;
diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c
index 6d9c1f5..3c2978c 100644
--- a/src/cfgparse-listen.c
+++ b/src/cfgparse-listen.c
@@ -1727,7 +1727,7 @@
 		}
 
 		curproxy->conf.args.ctx = ARGC_STK;
-		expr = sample_parse_expr(args, &myidx, file, linenum, &errmsg, &curproxy->conf.args);
+		expr = sample_parse_expr(args, &myidx, file, linenum, &errmsg, &curproxy->conf.args, NULL);
 		if (!expr) {
 			ha_alert("parsing [%s:%d] : '%s': %s\n", file, linenum, args[0], errmsg);
 			err_code |= ERR_ALERT | ERR_FATAL;
diff --git a/src/dns.c b/src/dns.c
index f823018..86147a4 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -2572,7 +2572,7 @@
 
 	cur_arg = cur_arg + 1;
 
-	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
+	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
 	if (!expr)
 		goto do_resolve_parse_error;
 
diff --git a/src/flt_spoe.c b/src/flt_spoe.c
index 4e448dd..a78e615 100644
--- a/src/flt_spoe.c
+++ b/src/flt_spoe.c
@@ -3968,7 +3968,7 @@
 			}
 			arg->expr = sample_parse_expr((char*[]){delim, NULL},
 						      &idx, file, linenum, &errmsg,
-						      &curproxy->conf.args);
+						      &curproxy->conf.args, NULL);
 			if (arg->expr == NULL) {
 				ha_alert("parsing [%s:%d] : '%s': %s.\n", file, linenum, args[0], errmsg);
 				err_code |= ERR_ALERT | ERR_FATAL;
diff --git a/src/http_act.c b/src/http_act.c
index 4c0c5c4..7185014 100644
--- a/src/http_act.c
+++ b/src/http_act.c
@@ -558,7 +558,7 @@
 	}
 
 	cur_arg = *orig_arg;
-	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
+	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
 	if (!expr)
 		return ACT_RET_PRS_ERR;
 
@@ -743,7 +743,7 @@
 	}
 
 	cur_arg = *orig_arg;
-	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
+	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
 	if (!expr)
 		return ACT_RET_PRS_ERR;
 
@@ -1730,7 +1730,7 @@
 
 	cur_arg = *orig_arg;
 	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
-				 err, &px->conf.args);
+				 err, &px->conf.args, NULL);
 	if (!expr)
 		return ACT_RET_PRS_ERR;
 
diff --git a/src/log.c b/src/log.c
index 23e6c47..51d684d 100644
--- a/src/log.c
+++ b/src/log.c
@@ -488,7 +488,7 @@
 	cmd[1] = "";
 	cmd_arg = 0;
 
-	expr = sample_parse_expr(cmd, &cmd_arg, curpx->conf.args.file, curpx->conf.args.line, err, &curpx->conf.args);
+	expr = sample_parse_expr(cmd, &cmd_arg, curpx->conf.args.file, curpx->conf.args.line, err, &curpx->conf.args, NULL);
 	if (!expr) {
 		memprintf(err, "failed to parse sample expression <%s> : %s", text, *err);
 		goto error_free;
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 7668ec2..8486863 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -1287,7 +1287,7 @@
 	unsigned int where;
 
 	cur_arg = *orig_arg;
-	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
+	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
 	if (!expr)
 		return ACT_RET_PRS_ERR;
 
diff --git a/src/queue.c b/src/queue.c
index 1bb08a5..9fd3e9d 100644
--- a/src/queue.c
+++ b/src/queue.c
@@ -544,7 +544,7 @@
 	unsigned int where = 0;
 
 	rule->arg.expr = sample_parse_expr((char **)args, arg, px->conf.args.file,
-	                                   px->conf.args.line, err, &px->conf.args);
+	                                   px->conf.args.line, err, &px->conf.args, NULL);
 	if (!rule->arg.expr)
 		return ACT_RET_PRS_ERR;
 
@@ -572,7 +572,7 @@
 	unsigned int where = 0;
 
 	rule->arg.expr = sample_parse_expr((char **)args, arg, px->conf.args.file,
-	                                   px->conf.args.line, err, &px->conf.args);
+	                                   px->conf.args.line, err, &px->conf.args, NULL);
 	if (!rule->arg.expr)
 		return ACT_RET_PRS_ERR;
 
diff --git a/src/sample.c b/src/sample.c
index d82451f..ef0f7b0 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -824,8 +824,11 @@
  *        fetch keyword followed by format conversion keywords.
  * Returns a pointer on allocated sample expression structure.
  * The caller must have set al->ctx.
+ * If <endptr> is non-nul, it will be set to the first unparsed character
+ * (which may be the final '\0') on success. If it is nul, the expression
+ * must be properly terminated by a '\0' otherwise an error is reported.
  */
-struct sample_expr *sample_parse_expr(char **str, int *idx, const char *file, int line, char **err_msg, struct arg_list *al)
+struct sample_expr *sample_parse_expr(char **str, int *idx, const char *file, int line, char **err_msg, struct arg_list *al, char **endptr)
 {
 	const char *begw; /* beginning of word */
 	const char *endw; /* end of word */
@@ -917,6 +920,10 @@
 		int argcnt;
 
 		if (*endt && *endt != ',') {
+			if (endptr) {
+				/* end found, let's stop here */
+				break;
+			}
 			if (ckw)
 				memprintf(err_msg, "missing comma after converter '%s'", ckw);
 			else
@@ -949,8 +956,10 @@
 		conv = find_sample_conv(begw, endw - begw);
 		if (!conv) {
 			/* we found an isolated keyword that we don't know, it's not ours */
-			if (begw == str[*idx])
+			if (begw == str[*idx]) {
+				endt = begw;
 				break;
+			}
 			memprintf(err_msg, "unknown converter '%s'", ckw);
 			goto out_error;
 		}
@@ -996,6 +1005,11 @@
 		}
 	}
 
+	if (endptr) {
+		/* end found, let's stop here */
+		*endptr = (char *)endt;
+	}
+
  out:
 	free(fkw);
 	free(ckw);
diff --git a/src/server.c b/src/server.c
index d7cb97d..959fb22 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1538,7 +1538,7 @@
 	idx = 0;
 	px->conf.args.ctx = ARGC_SRV;
 
-	return sample_parse_expr((char **)args, &idx, file, linenum, err, &px->conf.args);
+	return sample_parse_expr((char **)args, &idx, file, linenum, err, &px->conf.args, NULL);
 }
 
 static int server_parse_sni_expr(struct server *newsrv, struct proxy *px, char **err)
diff --git a/src/stick_table.c b/src/stick_table.c
index 1e7d4f3..9937f11 100644
--- a/src/stick_table.c
+++ b/src/stick_table.c
@@ -2118,7 +2118,7 @@
 	rule->arg.gpt.value = strtol(args[*arg], &error, 10);
 	if (*error != '\0') {
 		rule->arg.gpt.expr = sample_parse_expr((char **)args, arg, px->conf.args.file,
-		                                       px->conf.args.line, err, &px->conf.args);
+		                                       px->conf.args.line, err, &px->conf.args, NULL);
 		if (!rule->arg.gpt.expr)
 			return ACT_RET_PRS_ERR;
 
diff --git a/src/tcp_rules.c b/src/tcp_rules.c
index 7eb5659..fc8b4ae 100644
--- a/src/tcp_rules.c
+++ b/src/tcp_rules.c
@@ -766,7 +766,7 @@
 		arg++;
 
 		curpx->conf.args.ctx = ARGC_CAP;
-		expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args);
+		expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args, NULL);
 		if (!expr) {
 			memprintf(err,
 			          "'%s %s %s' : %s",
@@ -847,7 +847,7 @@
 		}
 
 		curpx->conf.args.ctx = ARGC_TRK;
-		expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args);
+		expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args, NULL);
 		if (!expr) {
 			memprintf(err,
 			          "'%s %s %s' : %s",
diff --git a/src/vars.c b/src/vars.c
index ff6baf5..63e2103 100644
--- a/src/vars.c
+++ b/src/vars.c
@@ -749,7 +749,7 @@
 	kw_name = args[*arg-1];
 
 	rule->arg.vars.expr = sample_parse_expr((char **)args, arg, px->conf.args.file,
-	                                        px->conf.args.line, err, &px->conf.args);
+	                                        px->conf.args.line, err, &px->conf.args, NULL);
 	if (!rule->arg.vars.expr)
 		return ACT_RET_PRS_ERR;