]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/var.c
Fix counter bugs reported by Chris Allen
[bacula/bacula] / bacula / src / lib / var.c
1 /*
2 **  OSSP var - Variable Expansion
3 **  Copyright (c) 2001-2002 Ralf S. Engelschall <rse@engelschall.com>
4 **  Copyright (c) 2001-2002 The OSSP Project (http://www.ossp.org/)
5 **  Copyright (c) 2001-2002 Cable & Wireless Deutschland (http://www.cw.com/de/)
6 **
7 **  This file is part of OSSP var, a variable expansion
8 **  library which can be found at http://www.ossp.org/pkg/lib/var/.
9 **
10 **  Permission to use, copy, modify, and distribute this software for
11 **  any purpose with or without fee is hereby granted, provided that
12 **  the above copyright notice and this permission notice appear in all
13 **  copies.
14 **
15 **  For disclaimer see below.
16 */
17 /*
18  *  Adapted by Kern Sibbald to Bacula June 2003
19  */
20 /*
21    Copyright (C) 2000-2003 Kern Sibbald and John Walker
22
23    This program is free software; you can redistribute it and/or
24    modify it under the terms of the GNU General Public License as
25    published by the Free Software Foundation; either version 2 of
26    the License, or (at your option) any later version.
27
28    This program is distributed in the hope that it will be useful,
29    but WITHOUT ANY WARRANTY; without even the implied warranty of
30    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
31    General Public License for more details.
32
33    You should have received a copy of the GNU General Public
34    License along with this program; if not, write to the Free
35    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
36    MA 02111-1307, USA.
37
38  */
39
40
41 #include "bacula.h"
42 #if defined(HAVE_PCREPOSIX)
43 #  include <pcreposix.h>
44 #else
45 #  include <regex.h>
46 #endif
47 #include "var.h"
48
49 /* support for OSSP ex based exception throwing */
50 #ifdef WITH_EX
51 #include "ex.h"
52 #define VAR_RC(rv) \
53     (  (rv) != VAR_OK && (ex_catching && !ex_shielding) \
54      ? (ex_throw(var_id, NULL, (rv)), (rv)) : (rv) )
55 #else
56 #define VAR_RC(rv) (rv)
57 #endif /* WITH_EX */
58
59 #ifndef EOS
60 #define EOS '\0'
61 #endif
62
63 /*
64 **
65 **  ==== INTERNAL DATA STRUCTURES ====
66 **
67 */
68
69 typedef char char_class_t[256]; /* 256 == 2 ^ sizeof(unsigned char)*8 */
70
71 /* the external context structure */
72 struct var_st {
73     var_syntax_t         syntax;
74     char_class_t         syntax_nameclass;
75     var_cb_value_t       cb_value_fct;
76     void                *cb_value_ctx;
77     var_cb_operation_t   cb_operation_fct;
78     void                *cb_operation_ctx;
79 };
80
81 /* the internal expansion context structure */
82 struct var_parse_st {
83     struct var_parse_st *lower;
84     int                  force_expand;
85     int                  rel_lookup_flag;
86     int                  rel_lookup_cnt;
87     int                  index_this;
88 };
89 typedef struct var_parse_st var_parse_t;
90
91 /* the default syntax configuration */
92 static const var_syntax_t var_syntax_default = {
93     '\\',         /* escape */
94     '$',          /* delim_init */
95     '{',          /* delim_open */
96     '}',          /* delim_close */
97     '[',          /* index_open */
98     ']',          /* index_close */
99     '#',          /* index_mark */
100     "a-zA-Z0-9_"  /* name_chars */
101 };
102
103 /*
104 **
105 **  ==== FORMATTING FUNCTIONS ====
106 **
107 */
108
109 /* minimal output-independent vprintf(3) variant which supports %{c,s,d,%} only */
110 static int 
111 var_mvxprintf(
112     int (*output)(void *ctx, const char *buffer, int bufsize), void *ctx, 
113     const char *format, va_list ap)
114 {
115     /* sufficient integer buffer: <available-bits> x log_10(2) + safety */
116     char ibuf[((sizeof(int)*8)/3)+10]; 
117     char *cp;
118     char c;
119     int d;
120     int n;
121     int bytes;
122
123     if (format == NULL || ap == NULL)
124         return -1;
125     bytes = 0;
126     while (*format != '\0') {
127         if (*format == '%') {
128             c = *(format+1);
129             if (c == '%') {
130                 /* expand "%%" */
131                 cp = &c;
132                 n = sizeof(char);
133             }
134             else if (c == 'c') {
135                 /* expand "%c" */
136                 c = (char)va_arg(ap, int);
137                 cp = &c;
138                 n = sizeof(char);
139             }
140             else if (c == 's') {
141                 /* expand "%s" */
142                 if ((cp = (char *)va_arg(ap, char *)) == NULL)
143                     cp = "(null)";
144                 n = strlen(cp);
145             }
146             else if (c == 'd') {
147                 /* expand "%d" */
148                 d = (int)va_arg(ap, int);
149                 bsnprintf(ibuf, sizeof(ibuf), "%d", d); /* explicitly secure */
150                 cp = ibuf;
151                 n = strlen(cp);
152             }
153             else {
154                 /* any other "%X" */
155                 cp = (char *)format;
156                 n  = 2;
157             }
158             format += 2;
159         }
160         else {
161             /* plain text */
162             cp = (char *)format;
163             if ((format = strchr(cp, '%')) == NULL)
164                 format = strchr(cp, '\0');
165             n = format - cp;
166         }
167         /* perform output operation */
168         if (output != NULL)
169             if ((n = output(ctx, cp, n)) == -1)
170                 break;
171         bytes += n;
172     }
173     return bytes;
174 }
175
176 /* output callback function context for var_mvsnprintf() */
177 typedef struct {
178     char *bufptr;
179     int buflen;
180 } var_mvsnprintf_cb_t;
181
182 /* output callback function for var_mvsnprintf() */
183 static int 
184 var_mvsnprintf_cb(
185     void *_ctx, 
186     const char *buffer, int bufsize)
187 {
188     var_mvsnprintf_cb_t *ctx = (var_mvsnprintf_cb_t *)_ctx;
189
190     if (bufsize > ctx->buflen)
191         return -1;
192     memcpy(ctx->bufptr, buffer, bufsize);
193     ctx->bufptr += bufsize;
194     ctx->buflen -= bufsize;
195     return bufsize;
196 }
197
198 /* minimal vsnprintf(3) variant which supports %{c,s,d} only */
199 static int 
200 var_mvsnprintf(
201     char *buffer, int bufsize, 
202     const char *format, va_list ap)
203 {
204     int n;
205     var_mvsnprintf_cb_t ctx;
206
207     if (format == NULL || ap == NULL)
208         return -1;
209     if (buffer != NULL && bufsize == 0)
210         return -1;
211     if (buffer == NULL) 
212         /* just determine output length */
213         n = var_mvxprintf(NULL, NULL, format, ap);
214     else {
215         /* perform real output */
216         ctx.bufptr = buffer;
217         ctx.buflen = bufsize;
218         n = var_mvxprintf(var_mvsnprintf_cb, &ctx, format, ap);
219         if (n != -1 && ctx.buflen == 0)
220             n = -1;
221         if (n != -1)
222             *(ctx.bufptr) = '\0';
223     }
224     return n;
225 }
226
227 /*
228 **
229 **  ==== PARSE CONTEXT FUNCTIONS ====
230 **
231 */
232
233 static var_parse_t *
234 var_parse_push(
235     var_parse_t *lower, var_parse_t *upper)
236 {
237     if (upper == NULL)
238         return NULL;
239     memcpy(upper, lower, sizeof(var_parse_t));
240     upper->lower = lower;
241     return upper;
242 }
243
244 static var_parse_t *
245 var_parse_pop(
246     var_parse_t *upper)
247 {
248     if (upper == NULL)
249         return NULL;
250     return upper->lower;
251 }
252
253 /*
254 **
255 **  ==== TOKEN BUFFER FUNCTIONS ====
256 **
257 */
258
259 #define TOKENBUF_INITIAL_BUFSIZE 64
260
261 typedef struct {
262     const char *begin;
263     const char *end;
264     int buffer_size;
265 } tokenbuf_t;
266
267 static void 
268 tokenbuf_init(
269     tokenbuf_t *buf)
270 {
271     buf->begin = NULL;
272     buf->end = NULL;
273     buf->buffer_size = 0;
274     return;
275 }
276
277 static int 
278 tokenbuf_isundef(
279     tokenbuf_t *buf)
280 {
281     if (buf->begin == NULL && buf->end == NULL)
282         return 1;
283     return 0;
284 }
285
286 static int 
287 tokenbuf_isempty(
288     tokenbuf_t *buf)
289 {
290     if (buf->begin == buf->end)
291         return 1;
292     return 0;
293 }
294
295 static void 
296 tokenbuf_set(
297     tokenbuf_t *buf, const char *begin, const char *end, int buffer_size)
298 {
299     buf->begin = begin;
300     buf->end = end;
301     buf->buffer_size = buffer_size;
302     return;
303 }
304
305 static void 
306 tokenbuf_move(
307     tokenbuf_t *src, tokenbuf_t *dst)
308 {
309     dst->begin = src->begin;
310     dst->end = src->end;
311     dst->buffer_size = src->buffer_size;
312     tokenbuf_init(src);
313     return;
314 }
315
316 static int 
317 tokenbuf_assign(
318     tokenbuf_t *buf, const char *data, int len)
319 {
320     char *p;
321
322     if ((p = (char *)malloc(len + 1)) == NULL)
323         return 0;
324     memcpy(p, data, len);
325     buf->begin = p;
326     buf->end = p + len;
327     buf->buffer_size = len + 1;
328     *((char *)(buf->end)) = EOS;
329     return 1;
330 }
331
332 static int 
333 tokenbuf_append(
334     tokenbuf_t *output, const char *data, int len)
335 {
336     char *new_buffer;
337     int new_size;
338     char *tmp;
339
340     /* Is the tokenbuffer initialized at all? If not, allocate a
341        standard-sized buffer to begin with. */
342     if (output->begin == NULL) {
343         if ((output->begin = output->end = (const char *)malloc(TOKENBUF_INITIAL_BUFSIZE)) == NULL)
344             return 0;
345         output->buffer_size = TOKENBUF_INITIAL_BUFSIZE;
346     }
347
348     /* does the token contain text, but no buffer has been allocated yet? */
349     if (output->buffer_size == 0) {
350         /* check whether data borders to output. If, we can append
351            simly by increasing the end pointer. */
352         if (output->end == data) {
353             output->end += len;
354             return 1;
355         }
356         /* ok, so copy the contents of output into an allocated buffer
357            so that we can append that way. */
358         if ((tmp = (char *)malloc(output->end - output->begin + len + 1)) == NULL)
359             return 0;
360         memcpy(tmp, output->begin, output->end - output->begin);
361         output->buffer_size = output->end - output->begin;
362         output->begin = tmp;
363         output->end = tmp + output->buffer_size;
364         output->buffer_size += len + 1;
365     }
366
367     /* does the token fit into the current buffer? If not, realloc a
368        larger buffer that fits. */
369     if ((output->buffer_size - (output->end - output->begin)) <= len) {
370         new_size = output->buffer_size;
371         do {
372             new_size *= 2;
373         } while ((new_size - (output->end - output->begin)) <= len);
374         if ((new_buffer = (char *)realloc((char *)output->begin, new_size)) == NULL)
375             return 0;
376         output->end = new_buffer + (output->end - output->begin);
377         output->begin = new_buffer;
378         output->buffer_size = new_size;
379     }
380
381     /* append the data at the end of the current buffer. */
382     if (len > 0)
383         memcpy((char *)output->end, data, len);
384     output->end += len;
385     *((char *)output->end) = EOS;
386     return 1;
387 }
388
389 static int 
390 tokenbuf_merge(
391     tokenbuf_t *output, tokenbuf_t *input)
392 {
393     return tokenbuf_append(output, input->begin, input->end - input->begin);
394 }
395
396 static void 
397 tokenbuf_free(
398     tokenbuf_t *buf)
399 {
400     if (buf->begin != NULL && buf->buffer_size > 0)
401         free((char *)buf->begin);
402     buf->begin = buf->end = NULL;
403     buf->buffer_size = 0;
404     return;
405 }
406
407 /*
408 **
409 **  ==== CHARACTER CLASS EXPANSION ====
410 **
411 */
412
413 static void 
414 expand_range(char a, char b, char_class_t chrclass)
415 {
416     do {
417         chrclass[(int)a] = 1;
418     } while (++a <= b);
419     return;
420 }
421
422 static var_rc_t 
423 expand_character_class(const char *desc, char_class_t chrclass)
424 {
425     int i;
426
427     /* clear the class array. */
428     for (i = 0; i < 256; ++i)
429         chrclass[i] = 0;
430
431     /* walk through class description and set appropriate entries in array */
432     while (*desc != EOS) {
433         if (desc[1] == '-' && desc[2] != EOS) {
434             if (desc[0] > desc[2])
435                 return VAR_ERR_INCORRECT_CLASS_SPEC;
436             expand_range(desc[0], desc[2], chrclass);
437             desc += 3;
438         } else {
439             chrclass[(int) *desc] = 1;
440             desc++;
441         }
442     }
443     return VAR_OK;
444 }
445
446 /*
447 **
448 **  ==== ESCAPE SEQUENCE EXPANSION FUNCTIONS ====
449 **
450 */
451
452 static int 
453 expand_isoct(
454     char c)
455 {
456     if (c >= '0' && c <= '7')
457         return 1;
458     else
459         return 0;
460 }
461
462 static var_rc_t 
463 expand_octal(
464     const char **src, char **dst, const char *end)
465 {
466     unsigned char c;
467
468     if (end - *src < 3)
469         return VAR_ERR_INCOMPLETE_OCTAL;
470     if (   !expand_isoct(**src) 
471         || !expand_isoct((*src)[1]) 
472         || !expand_isoct((*src)[2]))
473         return VAR_ERR_INVALID_OCTAL;
474
475     c = **src - '0';
476     if (c > 3)
477         return VAR_ERR_OCTAL_TOO_LARGE;
478     c *= 8;
479     (*src)++;
480
481     c += **src - '0';
482     c *= 8;
483     (*src)++;
484
485     c += **src - '0';
486
487     **dst = (char) c;
488     (*dst)++;
489     return VAR_OK;
490 }
491
492 static int 
493 expand_ishex(
494     char c)
495 {
496     if ((c >= '0' && c <= '9') ||
497         (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
498         return 1;
499     else
500         return 0;
501 }
502
503 static var_rc_t 
504 expand_simple_hex(
505     const char **src, char **dst, const char *end)
506 {
507     unsigned char c = 0;
508
509     if (end - *src < 2)
510         return VAR_ERR_INCOMPLETE_HEX;
511     if (   !expand_ishex(**src) 
512         || !expand_ishex((*src)[1]))
513         return VAR_ERR_INVALID_HEX;
514
515     if (**src >= '0' && **src <= '9')
516         c = **src - '0';
517     else if (c >= 'a' && c <= 'f')
518         c = **src - 'a' + 10;
519     else if (c >= 'A' && c <= 'F')
520         c = **src - 'A' + 10;
521
522     c = c << 4;
523     (*src)++;
524
525     if (**src >= '0' && **src <= '9')
526         c += **src - '0';
527     else if (**src >= 'a' && **src <= 'f')
528         c += **src - 'a' + 10;
529     else if (**src >= 'A' && **src <= 'F')
530         c += **src - 'A' + 10;
531
532     **dst = (char) c;
533     (*dst)++;
534     return VAR_OK;
535 }
536
537 static var_rc_t 
538 expand_grouped_hex(
539     const char **src, char **dst, const char *end)
540 {
541     var_rc_t rc;
542
543     while (*src < end && **src != '}') {
544         if ((rc = expand_simple_hex(src, dst, end)) != VAR_OK)
545             return rc;
546         (*src)++;
547     }
548     if (*src == end)
549         return VAR_ERR_INCOMPLETE_GROUPED_HEX;
550
551     return VAR_OK;
552 }
553
554 static var_rc_t 
555 expand_hex(
556     const char **src, char **dst, const char *end)
557 {
558     if (*src == end)
559         return VAR_ERR_INCOMPLETE_HEX;
560     if (**src == '{') {
561         (*src)++;
562         return expand_grouped_hex(src, dst, end);
563     } else
564         return expand_simple_hex(src, dst, end);
565 }
566
567 /* 
568 **
569 **  ==== RECURSIVE-DESCEND VARIABLE EXPANSION PARSER ====
570 **
571 */
572
573 /* forward declarations */
574 static int parse_variable(var_t *var, var_parse_t *ctx, const char *begin, const char *end, tokenbuf_t *result);
575 static int parse_numexp (var_t *var, var_parse_t *ctx, const char *begin, const char *end, int *result, int *failed);
576 static int parse_name   (var_t *var, var_parse_t *ctx, const char *begin, const char *end);
577
578 /* parse pattern text */
579 static int 
580 parse_pattern(
581     var_t *var, var_parse_t *ctx,
582     const char *begin, const char *end)
583 {
584     const char *p;
585
586     /* parse until '/' */
587     for (p = begin; p != end && *p != '/'; p++) {
588         if (*p == var->syntax.escape) {
589             if (p + 1 == end)
590                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
591             p++;
592         }
593     }
594     return (p - begin);
595 }
596
597 /* parse substitution text */
598 static int 
599 parse_substext(
600     var_t *var, var_parse_t *ctx,
601     const char *begin, const char *end)
602 {
603     const char *p;
604
605     /* parse until delim_init or '/' */
606     for (p = begin; p != end && *p != var->syntax.delim_init && *p != '/'; p++) {
607         if (*p == var->syntax.escape) {
608             if (p + 1 == end)
609                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
610             p++;
611         }
612     }
613     return (p - begin);
614 }
615
616 /* parse expression text */
617 static int 
618 parse_exptext(
619     var_t *var, var_parse_t *ctx,
620     const char *begin, const char *end)
621 {
622     const char *p;
623
624     /* parse until delim_init or delim_close or ':' */
625     for (p = begin;     p != end
626                     && *p != var->syntax.delim_init
627                     && *p != var->syntax.delim_close
628                     && *p != ':'; p++) {
629         if (*p == var->syntax.escape) {
630             if (p + 1 == end)
631                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
632             p++;
633         }
634     }
635     return (p - begin);
636 }
637
638 /* parse opertion argument text */
639 static int 
640 parse_opargtext(
641     var_t *var, var_parse_t *ctx,
642     const char *begin, const char *end)
643 {
644     const char *p;
645
646     /* parse until delim_init or ')' */
647     for (p = begin; p != end && *p != var->syntax.delim_init && *p != ')'; p++) {
648         if (*p == var->syntax.escape) {
649             if (p + 1 == end)
650                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
651             p++;
652         }
653     }
654     return (p - begin);
655 }
656
657 static int 
658 parse_opargtext_or_variable(
659     var_t *var, var_parse_t *ctx,
660     const char *begin, const char *end,
661     tokenbuf_t *result)
662 {
663     const char *p;
664     tokenbuf_t tmp;
665     int rc;
666
667     tokenbuf_init(result);
668     tokenbuf_init(&tmp);
669     p = begin;
670     if (p == end)
671         return 0;
672     do {
673         rc = parse_opargtext(var, ctx, p, end);
674         if (rc < 0)
675             goto error_return;
676         if (rc > 0) {
677             if (!tokenbuf_append(result, p, rc)) {
678                 rc = VAR_ERR_OUT_OF_MEMORY;
679                 goto error_return;
680             }
681             p += rc;
682         }
683         rc = parse_variable(var, ctx, p, end, &tmp);
684         if (rc < 0)
685             goto error_return;
686         if (rc > 0) {
687             p += rc;
688             if (!tokenbuf_merge(result, &tmp)) {
689                 rc = VAR_ERR_OUT_OF_MEMORY;
690                 goto error_return;
691             }
692         }
693         tokenbuf_free(&tmp);          /* KES 11/9/2003 */
694     } while (rc > 0);
695     tokenbuf_free(&tmp);
696     return (p - begin);
697
698 error_return:
699     tokenbuf_free(&tmp);
700     tokenbuf_free(result);
701     return rc;
702 }
703
704 /* parse expression or variable */
705 static int 
706 parse_exptext_or_variable(
707     var_t *var, var_parse_t *ctx,
708     const char *begin, const char *end,
709     tokenbuf_t *result)
710 {
711     const char *p = begin;
712     tokenbuf_t tmp;
713     int rc;
714
715     tokenbuf_init(result);
716     tokenbuf_init(&tmp);
717     if (begin == end)
718         return 0;
719     do {
720         /* try to parse expression text */
721         rc = parse_exptext(var, ctx, p, end);
722         if (rc < 0)
723             goto error_return;
724         if (rc > 0) {
725             if (!tokenbuf_append(result, p, rc)) {
726                 rc = VAR_ERR_OUT_OF_MEMORY;
727                 goto error_return;
728             }
729             p += rc;
730         }
731
732         /* try to parse variable construct */
733         rc = parse_variable(var, ctx, p, end, &tmp);
734         if (rc < 0)
735             goto error_return;
736         if (rc > 0) {
737             p += rc;
738             if (!tokenbuf_append
739                 (result, tmp.begin, tmp.end - tmp.begin)) {
740                 rc = VAR_ERR_OUT_OF_MEMORY;
741                 goto error_return;
742             }
743         }
744         tokenbuf_free(&tmp);          /* KES 11/9/2003 */
745     } while (rc > 0);
746
747     tokenbuf_free(&tmp);
748     return (p - begin);
749
750 error_return:
751     tokenbuf_free(&tmp);
752     tokenbuf_free(result);
753     return rc;
754 }
755
756 /* parse substitution text or variable */
757 static int 
758 parse_substext_or_variable(
759     var_t *var, var_parse_t *ctx,
760     const char *begin, const char *end,
761     tokenbuf_t *result)
762 {
763     const char *p = begin;
764     tokenbuf_t tmp;
765     int rc;
766
767     tokenbuf_init(result);
768     tokenbuf_init(&tmp);
769     if (begin == end)
770         return 0;
771     do {
772         /* try to parse substitution text */
773         rc = parse_substext(var, ctx, p, end);
774         if (rc < 0)
775             goto error_return;
776         if (rc > 0) {
777             if (!tokenbuf_append(result, p, rc)) {
778                 rc = VAR_ERR_OUT_OF_MEMORY;
779                 goto error_return;
780             }
781             p += rc;
782         }
783
784         /* try to parse substitution text */
785         rc = parse_variable(var, ctx, p, end, &tmp);
786         if (rc < 0)
787             goto error_return;
788         if (rc > 0) {
789             p += rc;
790             if (!tokenbuf_append
791                 (result, tmp.begin, tmp.end - tmp.begin)) {
792                 rc = VAR_ERR_OUT_OF_MEMORY;
793                 goto error_return;
794             }
795         }
796         tokenbuf_free(&tmp);          /* KES 11/9/2003 */
797     } while (rc > 0);
798
799     tokenbuf_free(&tmp);
800     return (p - begin);
801
802 error_return:
803     tokenbuf_free(&tmp);
804     tokenbuf_free(result);
805     return rc;
806 }
807
808 /* parse class description */
809 static int 
810 parse_class_description(
811     var_t *var, var_parse_t *ctx,
812     tokenbuf_t *src, tokenbuf_t *dst)
813 {
814     unsigned char c, d;
815     const char *p; 
816
817     p = src->begin;
818     while (p != src->end) {
819         if ((src->end - p) >= 3 && p[1] == '-') {
820             if (*p > p[2])
821                 return VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC;
822             for (c = *p, d = p[2]; c <= d; ++c) {
823                 if (!tokenbuf_append(dst, (char *)&c, 1))
824                     return VAR_ERR_OUT_OF_MEMORY;
825             }
826             p += 3;
827         } else {
828             if (!tokenbuf_append(dst, p, 1))
829                 return VAR_ERR_OUT_OF_MEMORY;
830             p++;
831         }
832     }
833     return VAR_OK;
834 }
835
836 /* parse regex replace part */
837 static int 
838 parse_regex_replace(
839     var_t *var, var_parse_t *ctx,
840     const char *data, 
841     tokenbuf_t *orig,
842     regmatch_t *pmatch, 
843     tokenbuf_t *expanded)
844 {
845     const char *p;
846     int i;
847
848     p = orig->begin;
849     tokenbuf_init(expanded);
850
851     while (p != orig->end) {
852         if (*p == '\\') {
853             if (orig->end - p <= 1) {
854                 tokenbuf_free(expanded);
855                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
856             }
857             p++;
858             if (*p == '\\') {
859                 if (!tokenbuf_append(expanded, p, 1)) {
860                     tokenbuf_free(expanded);
861                     return VAR_ERR_OUT_OF_MEMORY;
862                 }
863                 p++;
864                 continue;
865             }
866             if (!isdigit((int)*p)) {
867                 tokenbuf_free(expanded);
868                 return VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE;
869             }
870             i = (*p - '0');
871             p++;
872             if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1) {
873                 tokenbuf_free(expanded);
874                 return VAR_ERR_SUBMATCH_OUT_OF_RANGE;
875             }
876             if (!tokenbuf_append(expanded, data + pmatch[i].rm_so,
877                                  pmatch[i].rm_eo - pmatch[i].rm_so)) {
878                 tokenbuf_free(expanded);
879                 return VAR_ERR_OUT_OF_MEMORY;
880             }
881         } else {
882             if (!tokenbuf_append(expanded, p, 1)) {
883                 tokenbuf_free(expanded);
884                 return VAR_ERR_OUT_OF_MEMORY;
885             }
886             p++;
887         }
888     }
889
890     return VAR_OK;
891 }
892
893 /* operation: transpose */
894 static int 
895 op_transpose(
896     var_t *var, var_parse_t *ctx,
897     tokenbuf_t *data, 
898     tokenbuf_t *search,
899     tokenbuf_t *replace)
900 {
901     tokenbuf_t srcclass, dstclass;
902     const char *p;
903     int rc;
904     int i;
905
906     tokenbuf_init(&srcclass);
907     tokenbuf_init(&dstclass);
908     if ((rc = parse_class_description(var, ctx, search, &srcclass)) != VAR_OK)
909         goto error_return;
910     if ((rc = parse_class_description(var, ctx, replace, &dstclass)) != VAR_OK)
911         goto error_return;
912     if (srcclass.begin == srcclass.end) {
913         rc = VAR_ERR_EMPTY_TRANSPOSE_CLASS;
914         goto error_return;
915     }
916     if ((srcclass.end - srcclass.begin) != (dstclass.end - dstclass.begin)) {
917         rc = VAR_ERR_TRANSPOSE_CLASSES_MISMATCH;
918         goto error_return;
919     }
920     if (data->buffer_size == 0) {
921         tokenbuf_t tmp;
922         if (!tokenbuf_assign(&tmp, data->begin, data->end - data->begin)) {
923             rc = VAR_ERR_OUT_OF_MEMORY;
924             goto error_return;
925         }
926         tokenbuf_move(&tmp, data);
927     }
928     for (p = data->begin; p != data->end; ++p) {
929         for (i = 0; i <= (srcclass.end - srcclass.begin); ++i) {
930             if (*p == srcclass.begin[i]) {
931                 *((char *)p) = dstclass.begin[i];
932                 break;
933             }
934         }
935     }
936     tokenbuf_free(&srcclass);
937     tokenbuf_free(&dstclass);
938     return VAR_OK;
939
940 error_return:
941     tokenbuf_free(search);
942     tokenbuf_free(replace);
943     tokenbuf_free(&srcclass);
944     tokenbuf_free(&dstclass);
945     return rc;
946 }
947
948 /* operation: search & replace */
949 static int 
950 op_search_and_replace(
951     var_t *var, var_parse_t *ctx,
952     tokenbuf_t *data, 
953     tokenbuf_t *search,
954     tokenbuf_t *replace, 
955     tokenbuf_t *flags)
956 {
957     tokenbuf_t tmp;
958     const char *p;
959     int case_insensitive = 0;
960     int multiline = 0;
961     int global = 0;
962     int no_regex = 0;
963     int rc;
964
965     if (search->begin == search->end)
966         return VAR_ERR_EMPTY_SEARCH_STRING;
967
968     for (p = flags->begin; p != flags->end; p++) {
969         switch (tolower(*p)) {
970             case 'm':
971                 multiline = 1;
972                 break;
973             case 'i':
974                 case_insensitive = 1;
975                 break;
976             case 'g':
977                 global = 1;
978                 break;
979             case 't':
980                 no_regex = 1;
981                 break;
982             default:
983                 return VAR_ERR_UNKNOWN_REPLACE_FLAG;
984         }
985     }
986
987     if (no_regex) {
988         /* plain text pattern based operation */
989         tokenbuf_init(&tmp);
990         for (p = data->begin; p != data->end;) {
991             if (case_insensitive)
992                 rc = strncasecmp(p, search->begin, search->end - search->begin);
993             else
994                 rc = strncmp(p, search->begin, search->end - search->begin);
995             if (rc != 0) {
996                 /* not matched, copy character */
997                 if (!tokenbuf_append(&tmp, p, 1)) {
998                     tokenbuf_free(&tmp);
999                     return VAR_ERR_OUT_OF_MEMORY;
1000                 }
1001                 p++;
1002             } else {
1003                 /* matched, copy replacement string */
1004                 tokenbuf_merge(&tmp, replace);
1005                 p += (search->end - search->begin);
1006                 if (!global) {
1007                     /* append remaining text */
1008                     if (!tokenbuf_append(&tmp, p, data->end - p)) {
1009                         tokenbuf_free(&tmp);
1010                         return VAR_ERR_OUT_OF_MEMORY;
1011                     }
1012                     break;
1013                 }
1014             }
1015         }
1016         tokenbuf_free(data);
1017         tokenbuf_move(&tmp, data);
1018     } else {
1019         /* regular expression pattern based operation */
1020         tokenbuf_t mydata;
1021         tokenbuf_t myreplace;
1022         regex_t preg;
1023         regmatch_t pmatch[10];
1024         int regexec_flag;
1025
1026         /* copy pattern and data to own buffer to make sure they are EOS-terminated */
1027         if (!tokenbuf_assign(&tmp, search->begin, search->end - search->begin))
1028             return VAR_ERR_OUT_OF_MEMORY;
1029         if (!tokenbuf_assign(&mydata, data->begin, data->end - data->begin)) {
1030             tokenbuf_free(&tmp);
1031             return VAR_ERR_OUT_OF_MEMORY;
1032         }
1033
1034         /* compile the pattern. */
1035         rc = regcomp(&preg, tmp.begin, 
1036                      (  REG_EXTENDED
1037                       | (multiline ? REG_NEWLINE : 0)
1038                       | (case_insensitive ? REG_ICASE : 0)));
1039         tokenbuf_free(&tmp);
1040         if (rc != 0) {
1041             tokenbuf_free(&mydata);
1042             return VAR_ERR_INVALID_REGEX_IN_REPLACE;
1043         }
1044
1045         /* match the pattern and create the result string in the tmp buffer */
1046         tokenbuf_append(&tmp, "", 0);
1047         for (p = mydata.begin; p < mydata.end; ) {
1048             if (p == mydata.begin || p[-1] == '\n')
1049                 regexec_flag = 0;
1050             else
1051                 regexec_flag = REG_NOTBOL;
1052             rc = regexec(&preg, p, sizeof(pmatch) / sizeof(regmatch_t), pmatch, regexec_flag);
1053             if (rc != 0) {
1054                 /* no (more) matching */
1055                 tokenbuf_append(&tmp, p, mydata.end - p);
1056                 break;
1057             }
1058             else if (   multiline 
1059                      && (p + pmatch[0].rm_so) == mydata.end 
1060                      && (pmatch[0].rm_eo - pmatch[0].rm_so) == 0) {
1061                 /* special case: found empty pattern (usually /^/ or /$/ only)
1062                    in multi-line at end of data (after the last newline) */
1063                 tokenbuf_append(&tmp, p, mydata.end - p);
1064                 break;
1065             }
1066             else {
1067                 /* append prolog string */
1068                 if (!tokenbuf_append(&tmp, p, pmatch[0].rm_so)) {
1069                     regfree(&preg);
1070                     tokenbuf_free(&tmp);
1071                     tokenbuf_free(&mydata);
1072                     return VAR_ERR_OUT_OF_MEMORY;
1073                 }
1074                 /* create replace string */
1075                 rc = parse_regex_replace(var, ctx, p, replace, pmatch, &myreplace);
1076                 if (rc != VAR_OK) {
1077                     regfree(&preg);
1078                     tokenbuf_free(&tmp);
1079                     tokenbuf_free(&mydata);
1080                     return rc;
1081                 }
1082                 /* append replace string */
1083                 if (!tokenbuf_append(&tmp, myreplace.begin, myreplace.end - myreplace.begin)) {
1084                     regfree(&preg);
1085                     tokenbuf_free(&tmp);
1086                     tokenbuf_free(&mydata);
1087                     tokenbuf_free(&myreplace);
1088                     return VAR_ERR_OUT_OF_MEMORY;
1089                 }
1090                 tokenbuf_free(&myreplace);
1091                 /* skip now processed data */
1092                 p += pmatch[0].rm_eo;
1093                 /* if pattern matched an empty part (think about
1094                    anchor-only regular expressions like /^/ or /$/) we
1095                    skip the next character to make sure we do not enter
1096                    an infinitive loop in matching */
1097                 if ((pmatch[0].rm_eo - pmatch[0].rm_so) == 0) {
1098                     if (p >= mydata.end)
1099                         break;
1100                     if (!tokenbuf_append(&tmp, p, 1)) {
1101                         regfree(&preg);
1102                         tokenbuf_free(&tmp);
1103                         tokenbuf_free(&mydata);
1104                         return VAR_ERR_OUT_OF_MEMORY;
1105                     }
1106                     p++;
1107                 }
1108                 /* append prolog string and stop processing if we 
1109                    do not perform the search & replace globally */
1110                 if (!global) {
1111                     if (!tokenbuf_append(&tmp, p, mydata.end - p)) {
1112                         regfree(&preg);
1113                         tokenbuf_free(&tmp);
1114                         tokenbuf_free(&mydata);
1115                         return VAR_ERR_OUT_OF_MEMORY;
1116                     }
1117                     break;
1118                 }
1119             }
1120         }
1121         regfree(&preg);
1122         tokenbuf_free(data);
1123         tokenbuf_move(&tmp, data);
1124         tokenbuf_free(&mydata);
1125     }
1126
1127     return VAR_OK;
1128 }
1129
1130 /* operation: offset substring */
1131 static int 
1132 op_offset(
1133     var_t *var, var_parse_t *ctx,
1134     tokenbuf_t *data, 
1135     int num1,
1136     int num2, 
1137     int isrange)
1138 {
1139     tokenbuf_t res;
1140     const char *p;
1141
1142     /* determine begin of result string */
1143     if ((data->end - data->begin) < num1)
1144         return VAR_ERR_OFFSET_OUT_OF_BOUNDS;
1145     p = data->begin + num1;
1146
1147     /* if num2 is zero, we copy the rest from there. */
1148     if (num2 == 0) {
1149         if (!tokenbuf_assign(&res, p, data->end - p))
1150             return VAR_ERR_OUT_OF_MEMORY;
1151     } else {
1152         /* ok, then use num2. */
1153         if (isrange) {
1154             if ((p + num2) > data->end)
1155                 return VAR_ERR_RANGE_OUT_OF_BOUNDS;
1156             if (!tokenbuf_assign(&res, p, num2))
1157                 return VAR_ERR_OUT_OF_MEMORY;
1158         } else {
1159             if (num2 < num1)
1160                 return VAR_ERR_OFFSET_LOGIC;
1161             if ((data->begin + num2) > data->end)
1162                 return VAR_ERR_RANGE_OUT_OF_BOUNDS;
1163             if (!tokenbuf_assign(&res, p, num2 - num1 + 1))
1164                 return VAR_ERR_OUT_OF_MEMORY;
1165         }
1166     }
1167     tokenbuf_free(data);
1168     tokenbuf_move(&res, data);
1169     return VAR_OK;
1170 }
1171
1172 /* operation: padding */
1173 static int 
1174 op_padding(
1175     var_t *var, var_parse_t *ctx,
1176     tokenbuf_t *data, 
1177     int width, 
1178     tokenbuf_t *fill,
1179     char position)
1180 {
1181     tokenbuf_t result;
1182     int i;
1183
1184     if (fill->begin == fill->end)
1185         return VAR_ERR_EMPTY_PADDING_FILL_STRING;
1186     tokenbuf_init(&result);
1187     if (position == 'l') {
1188         /* left padding */
1189         i = width - (data->end - data->begin);
1190         if (i > 0) {
1191             i = i / (fill->end - fill->begin);
1192             while (i > 0) {
1193                 if (!tokenbuf_append(data, fill->begin, fill->end - fill->begin))
1194                     return VAR_ERR_OUT_OF_MEMORY;
1195                 i--;
1196             }
1197             i = (width - (data->end - data->begin)) % (fill->end - fill->begin);
1198             if (!tokenbuf_append(data, fill->begin, i))
1199                 return VAR_ERR_OUT_OF_MEMORY;
1200         }
1201     } else if (position == 'r') {
1202         /* right padding */
1203         i = width - (data->end - data->begin);
1204         if (i > 0) {
1205             i = i / (fill->end - fill->begin);
1206             while (i > 0) {
1207                 if (!tokenbuf_append(&result, fill->begin, fill->end - fill->begin)) {
1208                     tokenbuf_free(&result);
1209                     return VAR_ERR_OUT_OF_MEMORY;
1210                 }
1211                 i--;
1212             }
1213             i = (width - (data->end - data->begin)) % (fill->end - fill->begin);
1214             if (!tokenbuf_append(&result, fill->begin, i)) {
1215                 tokenbuf_free(&result);
1216                 return VAR_ERR_OUT_OF_MEMORY;
1217             }
1218             if (!tokenbuf_append(&result, data->begin, data->end - data->begin)) {
1219                 tokenbuf_free(&result);
1220                 return VAR_ERR_OUT_OF_MEMORY;
1221             }
1222             /* move string from temporary buffer to data buffer */
1223             tokenbuf_free(data);
1224             tokenbuf_move(&result, data);
1225         }
1226     } else if (position == 'c') {
1227         /* centered padding */
1228         i = (width - (data->end - data->begin)) / 2;
1229         if (i > 0) {
1230             /* create the prefix */
1231             i = i / (fill->end - fill->begin);
1232             while (i > 0) {
1233                 if (!tokenbuf_append(&result, fill->begin, fill->end - fill->begin)) {
1234                     tokenbuf_free(&result);
1235                     return VAR_ERR_OUT_OF_MEMORY;
1236                 }
1237                 i--;
1238             }
1239             i = ((width - (data->end - data->begin)) / 2)
1240                 % (fill->end - fill->begin);
1241             if (!tokenbuf_append(&result, fill->begin, i)) {
1242                 tokenbuf_free(&result);
1243                 return VAR_ERR_OUT_OF_MEMORY;
1244             }
1245             /* append the actual data string */
1246             if (!tokenbuf_append(&result, data->begin, data->end - data->begin)) {
1247                 tokenbuf_free(&result);
1248                 return VAR_ERR_OUT_OF_MEMORY;
1249             }
1250             /* append the suffix */
1251             i = width - (result.end - result.begin);
1252             i = i / (fill->end - fill->begin);
1253             while (i > 0) {
1254                 if (!tokenbuf_append(&result, fill->begin, fill->end - fill->begin)) {
1255                     tokenbuf_free(&result);
1256                     return VAR_ERR_OUT_OF_MEMORY;
1257                 }
1258                 i--;
1259             }
1260             i = width - (result.end - result.begin);
1261             if (!tokenbuf_append(&result, fill->begin, i)) {
1262                 tokenbuf_free(&result);
1263                 return VAR_ERR_OUT_OF_MEMORY;
1264             }
1265             /* move string from temporary buffer to data buffer */
1266             tokenbuf_free(data);
1267             tokenbuf_move(&result, data);
1268         }
1269     }
1270     return VAR_OK;
1271 }
1272
1273 /* parse an integer number ("123") */
1274 static int 
1275 parse_integer(
1276     var_t *var, var_parse_t *ctx,
1277     const char *begin, const char *end,
1278     int *result)
1279 {
1280     const char *p;
1281     int num;
1282
1283     p = begin;
1284     num = 0;
1285     while (isdigit(*p) && p != end) {
1286         num *= 10;
1287         num += (*p - '0');
1288         p++;
1289     }
1290     if (result != NULL)
1291         *result = num;
1292     return (p - begin);
1293 }
1294
1295 /* parse an operation (":x...") */
1296 static int 
1297 parse_operation(
1298     var_t *var, var_parse_t *ctx,
1299     const char *begin, const char *end,
1300     tokenbuf_t *data)
1301 {
1302     const char *p;
1303     tokenbuf_t tmptokbuf;
1304     tokenbuf_t search, replace, flags;
1305     tokenbuf_t number1, number2;
1306     int num1, num2;
1307     int isrange;
1308     int rc;
1309     char *ptr;
1310
1311     /* initialization */
1312     tokenbuf_init(&tmptokbuf);
1313     tokenbuf_init(&search);
1314     tokenbuf_init(&replace);
1315     tokenbuf_init(&flags);
1316     tokenbuf_init(&number1);
1317     tokenbuf_init(&number2);
1318     p = begin;
1319     if (p == end)
1320         return 0;
1321
1322     /* dispatch through the first operation character */
1323     switch (tolower(*p)) {
1324         case 'l': {
1325             /* turn value to lowercase. */
1326             if (data->begin != NULL) {
1327                 /* if the buffer does not live in an allocated buffer,
1328                    we have to copy it before modifying the contents. */
1329                 if (data->buffer_size == 0) {
1330                     if (!tokenbuf_assign(data, data->begin, data->end - data->begin)) {
1331                         rc = VAR_ERR_OUT_OF_MEMORY;
1332                         goto error_return;
1333                     }
1334                 }
1335                 /* convert value */
1336                 for (ptr = (char *)data->begin; ptr != data->end; ptr++)
1337                     *ptr = (char)tolower((int)(*ptr));
1338             }
1339             p++;
1340             break;
1341         }
1342         case 'u': {
1343             /* turn value to uppercase. */
1344             if (data->begin != NULL) {
1345                 /* if the buffer does not live in an allocated buffer,
1346                    we have to copy it before modifying the contents. */
1347                 if (data->buffer_size == 0) {
1348                     if (!tokenbuf_assign(data, data->begin, data->end - data->begin)) {
1349                         rc = VAR_ERR_OUT_OF_MEMORY;
1350                         goto error_return;
1351                     }
1352                 }
1353                 /* convert value */
1354                 for (ptr = (char *)data->begin; ptr != data->end; ptr++)
1355                     *ptr = (char)toupper((int)(*ptr));
1356             }
1357             p++;
1358             break;
1359         }
1360         case 'o': {
1361             /* cut out substring of value. */
1362             p++;
1363             rc = parse_integer(var, ctx, p, end, &num1);
1364             if (rc == 0) {
1365                 rc = VAR_ERR_MISSING_START_OFFSET;
1366                 goto error_return;
1367             }
1368             else if (rc < 0)
1369                 goto error_return;
1370             p += rc;
1371             if (*p == ',') {
1372                 isrange = 0;
1373                 p++;
1374             } else if (*p == '-') {
1375                 isrange = 1;
1376                 p++;
1377             } else {
1378                 rc = VAR_ERR_INVALID_OFFSET_DELIMITER;
1379                 goto error_return;
1380             }
1381             rc = parse_integer(var, ctx, p, end, &num2);
1382             p += rc;
1383             if (data->begin != NULL) {
1384                 rc = op_offset(var, ctx, data, num1, num2, isrange);
1385                 if (rc < 0)
1386                     goto error_return;
1387             }
1388             break;
1389         }
1390         case '#': {
1391             /* determine length of the value */
1392             if (data->begin != NULL) {
1393                 char buf[((sizeof(int)*8)/3)+10]; /* sufficient size: <#bits> x log_10(2) + safety */
1394                 sprintf(buf, "%d", (int)(data->end - data->begin));
1395                 tokenbuf_free(data);
1396                 if (!tokenbuf_assign(data, buf, strlen(buf))) {
1397                     rc = VAR_ERR_OUT_OF_MEMORY;
1398                     goto error_return;
1399                 }
1400             }
1401             p++;
1402             break;
1403         }
1404         case '-': {
1405             /* substitute parameter if data is empty */
1406             p++;
1407             rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
1408             if (rc < 0)
1409                 goto error_return;
1410             if (rc == 0) {
1411                 rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
1412                 goto error_return;
1413             }
1414             p += rc;
1415             if (tokenbuf_isundef(data))
1416                 tokenbuf_move(&tmptokbuf, data);
1417             else if (tokenbuf_isempty(data)) {
1418                 tokenbuf_free(data);
1419                 tokenbuf_move(&tmptokbuf, data);
1420             }
1421             break;
1422         }
1423         case '*': {
1424             /* substitute empty string if data is not empty, parameter otherwise. */
1425             p++;
1426             rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
1427             if (rc < 0)
1428                 goto error_return;
1429             if (rc == 0) {
1430                 rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
1431                 goto error_return;
1432             }
1433             p += rc;
1434             if (data->begin != NULL) {
1435                 if (data->begin == data->end) {
1436                     tokenbuf_free(data);
1437                     tokenbuf_move(&tmptokbuf, data);
1438                 } else {
1439                     tokenbuf_free(data);
1440                     data->begin = data->end = "";
1441                     data->buffer_size = 0;
1442                 }
1443             }
1444             break;
1445         }
1446         case '+': {
1447             /* substitute parameter if data is not empty. */
1448             p++;
1449             rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
1450             if (rc < 0)
1451                 goto error_return;
1452             if (rc == 0) {
1453                 rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
1454                 goto error_return;
1455             }
1456             p += rc;
1457             if (data->begin != NULL && data->begin != data->end) {
1458                 tokenbuf_free(data);
1459                 tokenbuf_move(&tmptokbuf, data);
1460             }
1461             break;
1462         }
1463         case 's': {
1464             /* search and replace. */
1465             p++;
1466             if (*p != '/')
1467                 return VAR_ERR_MALFORMATTED_REPLACE;
1468             p++;
1469             rc = parse_pattern(var, ctx, p, end);
1470             if (rc < 0)
1471                 goto error_return;
1472             tokenbuf_set(&search, p, p + rc, 0);
1473             p += rc;
1474             if (*p != '/') {
1475                 rc = VAR_ERR_MALFORMATTED_REPLACE;
1476                 goto error_return;
1477             }
1478             p++;
1479             rc = parse_substext_or_variable(var, ctx, p, end, &replace);
1480             if (rc < 0)
1481                 goto error_return;
1482             p += rc;
1483             if (*p != '/') {
1484                 rc = VAR_ERR_MALFORMATTED_REPLACE;
1485                 goto error_return;
1486             }
1487             p++;
1488             rc = parse_exptext(var, ctx, p, end);
1489             if (rc < 0)
1490                 goto error_return;
1491             tokenbuf_set(&flags, p, p + rc, 0);
1492             p += rc;
1493             if (data->begin != NULL) {
1494                 rc = op_search_and_replace(var, ctx, data, &search, &replace, &flags);
1495                 if (rc < 0)
1496                     goto error_return;
1497             }
1498             break;
1499         }
1500         case 'y': {
1501             /* transpose characters from class A to class B. */
1502             p++;
1503             if (*p != '/')
1504                 return VAR_ERR_MALFORMATTED_TRANSPOSE;
1505             p++;
1506             rc = parse_substext_or_variable(var, ctx, p, end, &search);
1507             if (rc < 0)
1508                 goto error_return;
1509             p += rc;
1510             if (*p != '/') {
1511                 rc = VAR_ERR_MALFORMATTED_TRANSPOSE;
1512                 goto error_return;
1513             }
1514             p++;
1515             rc = parse_substext_or_variable(var, ctx, p, end, &replace);
1516             if (rc < 0)
1517                 goto error_return;
1518             p += rc;
1519             if (*p != '/') {
1520                 rc = VAR_ERR_MALFORMATTED_TRANSPOSE;
1521                 goto error_return;
1522             } else
1523                 p++;
1524             if (data->begin) {
1525                 rc = op_transpose(var, ctx, data, &search, &replace);
1526                 if (rc < 0)
1527                     goto error_return;
1528             }
1529             break;
1530         }
1531         case 'p': {
1532             /* padding. */
1533             p++;
1534             if (*p != '/')
1535                 return VAR_ERR_MALFORMATTED_PADDING;
1536             p++;
1537             rc = parse_integer(var, ctx, p, end, &num1);
1538             if (rc == 0) {
1539                 rc = VAR_ERR_MISSING_PADDING_WIDTH;
1540                 goto error_return;
1541             }
1542             p += rc;
1543             if (*p != '/') {
1544                 rc = VAR_ERR_MALFORMATTED_PADDING;
1545                 goto error_return;
1546             }
1547             p++;
1548             rc = parse_substext_or_variable(var, ctx, p, end, &replace);
1549             if (rc < 0)
1550                 goto error_return;
1551             p += rc;
1552             if (*p != '/') {
1553                 rc = VAR_ERR_MALFORMATTED_PADDING;
1554                 goto error_return;
1555             }
1556             p++;
1557             if (*p != 'l' && *p != 'c' && *p != 'r') {
1558                 rc = VAR_ERR_MALFORMATTED_PADDING;
1559                 goto error_return;
1560             }
1561             p++;
1562             if (data->begin) {
1563                 rc = op_padding(var, ctx, data, num1, &replace, p[-1]);
1564                 if (rc < 0)
1565                     goto error_return;
1566             }
1567             break;
1568         }
1569         case '%': {
1570             /* operation callback function */
1571             const char *op_ptr;
1572             int op_len;
1573             const char *arg_ptr;
1574             int arg_len;
1575             const char *val_ptr;
1576             int val_len;
1577             const char *out_ptr;
1578             int out_len; 
1579             int out_size;
1580             tokenbuf_t args;
1581
1582             p++;
1583             rc = parse_name(var, ctx, p, end);
1584             if (rc < 0)
1585                 goto error_return;
1586             op_ptr = p;
1587             op_len = rc;
1588             p += rc;
1589             if (*p == '(') {
1590                 p++;
1591                 tokenbuf_init(&args);
1592                 rc = parse_opargtext_or_variable(var, ctx, p, end, &args);
1593                 if (rc < 0)
1594                     goto error_return;
1595                 p += rc;
1596                 arg_ptr = args.begin;
1597                 arg_len = args.end - args.begin;
1598                 if (*p != ')') {
1599                     rc = VAR_ERR_MALFORMED_OPERATION_ARGUMENTS;
1600                     goto error_return;
1601                 }
1602                 p++;
1603             }
1604             else {
1605                 arg_ptr = NULL;
1606                 arg_len = 0;
1607             }
1608             val_ptr = data->begin;
1609             val_len = data->end - data->begin;
1610
1611             if (data->begin != NULL && var->cb_operation_fct != NULL) {
1612                 /* call operation callback function */
1613                 rc = (*var->cb_operation_fct)(var, var->cb_operation_ctx,
1614                                               op_ptr, op_len,
1615                                               arg_ptr, arg_len,
1616                                               val_ptr, val_len,
1617                                               &out_ptr, &out_len, &out_size);
1618                 if (rc < 0)
1619                     goto error_return;
1620                 tokenbuf_free(data);
1621                 tokenbuf_set(data, out_ptr, out_ptr+out_len, out_size);
1622             }
1623             break;
1624         }
1625         default:
1626             return VAR_ERR_UNKNOWN_COMMAND_CHAR;
1627     }
1628
1629     /* return successfully */
1630     tokenbuf_free(&tmptokbuf);
1631     tokenbuf_free(&search);
1632     tokenbuf_free(&replace);
1633     tokenbuf_free(&flags);
1634     tokenbuf_free(&number1);
1635     tokenbuf_free(&number2);
1636     return (p - begin);
1637
1638     /* return with an error */
1639 error_return:
1640     tokenbuf_free(data);
1641     tokenbuf_free(&tmptokbuf);
1642     tokenbuf_free(&search);
1643     tokenbuf_free(&replace);
1644     tokenbuf_free(&flags);
1645     tokenbuf_free(&number1);
1646     tokenbuf_free(&number2);
1647     return rc;
1648 }
1649
1650 /* parse numerical expression operand */
1651 static int 
1652 parse_numexp_operand(
1653     var_t *var, var_parse_t *ctx,
1654     const char *begin, const char *end, 
1655     int *result, int *failed)
1656 {
1657     const char *p;
1658     tokenbuf_t tmp;
1659     int rc;
1660     var_parse_t myctx;
1661
1662     /* initialization */
1663     p = begin;
1664     tokenbuf_init(&tmp);
1665     if (p == end)
1666         return VAR_ERR_INCOMPLETE_INDEX_SPEC;
1667
1668     /* parse opening numerical expression */
1669     if (*p == '(') {
1670         /* parse inner numerical expression */
1671         rc = parse_numexp(var, ctx, ++p, end, result, failed);
1672         if (rc < 0)
1673             return rc;
1674         p += rc;
1675         if (p == end)
1676             return VAR_ERR_INCOMPLETE_INDEX_SPEC;
1677         /* parse closing parenthesis */
1678         if (*p != ')')
1679             return VAR_ERR_UNCLOSED_BRACKET_IN_INDEX;
1680         p++;
1681     }
1682     /* parse contained variable */
1683     else if (*p == var->syntax.delim_init) {
1684         /* parse variable with forced expansion */
1685         ctx = var_parse_push(ctx, &myctx);
1686         ctx->force_expand = 1;
1687         rc = parse_variable(var, ctx, p, end, &tmp);
1688         ctx = var_parse_pop(ctx);
1689
1690         if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
1691             *failed = 1;
1692             /* parse variable without forced expansion */
1693             ctx = var_parse_push(ctx, &myctx);
1694             ctx->force_expand = 0;
1695             rc = parse_variable(var, ctx, p, end, &tmp);
1696             ctx = var_parse_pop(ctx);
1697             if (rc < 0)
1698                 return rc;
1699             p += rc;
1700             *result = 0;
1701             tokenbuf_free(&tmp);      /* KES 11/9/2003 */
1702         } else if (rc < 0) {
1703             return rc;
1704         } else {
1705             p += rc;
1706             /* parse remaining numerical expression */
1707             rc = parse_numexp(var, ctx, tmp.begin, tmp.end, result, failed);
1708             tokenbuf_free(&tmp);
1709             if (rc < 0)
1710                 return rc;
1711         }
1712     }
1713     /* parse relative index mark ("#") */
1714     else if (   var->syntax.index_mark != EOS 
1715              && *p == var->syntax.index_mark) {
1716         p++;
1717         *result = ctx->index_this;
1718         if (ctx->rel_lookup_flag)
1719             ctx->rel_lookup_cnt++;
1720     }
1721     /* parse plain integer number */
1722     else if (isdigit(*p)) {
1723         rc = parse_integer(var, ctx, p, end, result);
1724         p += rc;
1725     }
1726     /* parse signed positive integer number */
1727     else if (*p == '+') {
1728         if ((end - p) > 1 && isdigit(p[1])) {
1729             p++;
1730             rc = parse_integer(var, ctx, p, end, result);
1731             p += rc;
1732         }
1733         else
1734             return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1735     }
1736     /* parse signed negative integer number */
1737     else if (*p == '-') {
1738         if (end - p > 1 && isdigit(p[1])) {
1739             p++;
1740             rc = parse_integer(var, ctx, p, end, result);
1741             *result = -(*result);
1742             p += rc;
1743         }
1744         else
1745             return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1746     }
1747     /* else we failed to parse anything reasonable */
1748     else
1749         return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1750
1751     return (p - begin);
1752 }
1753
1754 /* parse numerical expression ("x+y") */
1755 static int 
1756 parse_numexp(
1757     var_t *var, var_parse_t *ctx,
1758     const char *begin, const char *end, 
1759     int *result, int *failed)
1760 {
1761     const char *p;
1762     char op;
1763     int right;
1764     int rc;
1765
1766     /* initialization */
1767     p = begin;
1768     if (p == end)
1769         return VAR_ERR_INCOMPLETE_INDEX_SPEC;
1770
1771     /* parse left numerical operand */
1772     rc = parse_numexp_operand(var, ctx, p, end, result, failed);
1773     if (rc < 0)
1774         return rc;
1775     p += rc;
1776
1777     /* parse numerical operator */
1778     while (p != end) {
1779         if (*p == '+' || *p == '-') {
1780             op = *p++;
1781             /* recursively parse right operand (light binding) */
1782             rc = parse_numexp(var, ctx, p, end, &right, failed);
1783             if (rc < 0)
1784                 return rc;
1785             p += rc;
1786             if (op == '+')
1787                 *result = (*result + right);
1788             else
1789                 *result = (*result - right);
1790         }
1791         else if (*p == '*' || *p == '/' || *p == '%') {
1792             op = *p++;
1793             /* recursively parse right operand (string binding) */
1794             rc = parse_numexp_operand(var, ctx, p, end, &right, failed);
1795             if (rc < 0)
1796                 return rc;
1797             p += rc;
1798             if (op == '*')
1799                 *result = (*result * right);
1800             else if (op == '/') {
1801                 if (right == 0) {
1802                     if (*failed)
1803                         *result = 0;
1804                     else
1805                         return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
1806                 }
1807                 else
1808                     *result = (*result / right);
1809             }
1810             else if (op == '%') {
1811                 if (right == 0) {
1812                     if (*failed)
1813                         *result = 0;
1814                     else
1815                         return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
1816                 }
1817                 else
1818                     *result = (*result % right);
1819             }
1820         }
1821         else
1822             break;
1823     }
1824
1825     /* return amount of parsed input */
1826     return (p - begin);
1827 }
1828
1829 /* parse variable name ("abc") */
1830 static int 
1831 parse_name(
1832     var_t *var, var_parse_t *ctx,
1833     const char *begin, const char *end)
1834 {
1835     const char *p;
1836
1837     /* parse as long as name class characters are found */
1838     for (p = begin; p != end && var->syntax_nameclass[(int)(*p)]; p++)
1839         ;
1840     return (p - begin);
1841 }
1842
1843 /* lookup a variable value through the callback function */
1844 static int 
1845 lookup_value(
1846     var_t *var, var_parse_t *ctx,
1847     const char  *var_ptr, int  var_len, int var_inc, int var_idx,
1848     const char **val_ptr, int *val_len, int *val_size)
1849 {
1850     char buf[1];
1851     int rc;
1852
1853     /* pass through to original callback */
1854     rc = (*var->cb_value_fct)(var, var->cb_value_ctx,
1855                               var_ptr, var_len, var_inc, var_idx, 
1856                               val_ptr, val_len, val_size);
1857
1858     /* convert undefined variable into empty variable if relative
1859        lookups are counted. This is the case inside an active loop
1860        construct if no limits are given. There the parse_input()
1861        has to proceed until all variables have undefined values. 
1862        This trick here allows it to determine this case. */
1863     if (ctx->rel_lookup_flag && rc == VAR_ERR_UNDEFINED_VARIABLE) {
1864         ctx->rel_lookup_cnt--;
1865         buf[0] = EOS;
1866         *val_ptr  = buf;
1867         *val_len  = 0;
1868         *val_size = 0;
1869         return VAR_OK;
1870     }
1871
1872     return rc;
1873 }
1874
1875 /* parse complex variable construct ("${name...}") */
1876 static int 
1877 parse_variable_complex(
1878     var_t *var, var_parse_t *ctx,
1879     const char *begin, const char *end,
1880     tokenbuf_t *result)
1881 {
1882     const char *p;
1883     const char *data;
1884     int len, buffer_size;
1885     int failed = 0;
1886     int rc;
1887     int idx = 0;
1888     int inc;   
1889     tokenbuf_t name;
1890     tokenbuf_t tmp;
1891
1892     /* initializations */
1893     p = begin;
1894     tokenbuf_init(&name);
1895     tokenbuf_init(&tmp);
1896     tokenbuf_init(result);
1897
1898     /* parse open delimiter */
1899     if (p == end || *p != var->syntax.delim_open)
1900         return 0;
1901     p++;
1902     if (p == end)
1903         return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
1904
1905     /* parse name of variable to expand. The name may consist of an
1906        arbitrary number of variable name character and contained variable
1907        constructs. */
1908     do {
1909         /* parse a variable name */
1910         rc = parse_name(var, ctx, p, end);
1911         if (rc < 0)
1912             goto error_return;
1913         if (rc > 0) {
1914             if (!tokenbuf_append(&name, p, rc)) {
1915                 rc = VAR_ERR_OUT_OF_MEMORY;
1916                 goto error_return;
1917             }
1918             p += rc;
1919         }
1920
1921         /* parse an (embedded) variable */
1922         rc = parse_variable(var, ctx, p, end, &tmp);
1923         if (rc < 0)
1924             goto error_return;
1925         if (rc > 0) {
1926             if (!tokenbuf_merge(&name, &tmp)) {
1927                 rc = VAR_ERR_OUT_OF_MEMORY;
1928                 goto error_return;
1929             }
1930             p += rc;
1931         }
1932         tokenbuf_free(&tmp);          /* KES 11/9/2003 */
1933     } while (rc > 0);
1934
1935     /* we must have the complete expanded variable name now, 
1936        so make sure we really do. */
1937     if (name.begin == name.end) {
1938         if (ctx->force_expand) {
1939             rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
1940             goto error_return;
1941         }
1942         else {
1943             /* If no force_expand is requested, we have to back-off.
1944                We're not sure whether our approach here is 100% correct,
1945                because it _could_ have side-effects according to Peter
1946                Simons, but as far as we know and tried it, it is
1947                correct. But be warned -- RSE */
1948             tokenbuf_set(result, begin - 1, p, 0);
1949             goto goahead;
1950         }
1951     }
1952
1953     /* parse an optional index specification */
1954     if (   var->syntax.index_open != EOS
1955         && *p == var->syntax.index_open) {
1956         p++;
1957         rc = parse_numexp(var, ctx, p, end, &idx, &failed);
1958         if (rc < 0)
1959             goto error_return;
1960         if (rc == 0) {
1961             rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
1962             goto error_return;
1963         }
1964         p += rc;
1965         if (p == end) {
1966             rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
1967             goto error_return;
1968         }
1969         if (*p != var->syntax.index_close) {
1970             rc = VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1971             goto error_return;
1972         }
1973         p++;
1974     }
1975
1976     /* parse end of variable construct or start of post-operations */
1977     if (p == end || (*p != var->syntax.delim_close && *p != ':' && *p != '+')) {
1978         rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
1979         goto error_return;
1980     }
1981     inc = (*p == '+');                /* increment variable */
1982     p++;
1983
1984     /* lookup the variable value now */
1985     if (failed) {
1986         tokenbuf_set(result, begin - 1, p, 0);
1987     } else {
1988         rc = lookup_value(var, ctx,
1989                           name.begin, name.end-name.begin, inc, idx,
1990                           &data, &len, &buffer_size);
1991         if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
1992             tokenbuf_init(result); /* delayed handling of undefined variable */
1993         } else if (rc < 0) {
1994             goto error_return;
1995         } else {
1996             /* the preliminary result is the raw value of the variable.
1997                This may be modified by the operations that may follow. */
1998             tokenbuf_set(result, data, data + len, buffer_size);
1999         }
2000     }
2001
2002     /* parse optional post-operations */
2003 goahead:
2004     if (p[-1] == ':') {
2005         tokenbuf_free(&tmp);
2006         tokenbuf_init(&tmp);
2007         p--;
2008         while (p != end && *p == ':') {
2009             p++;
2010             if (!failed)
2011                 rc = parse_operation(var, ctx, p, end, result);
2012             else
2013                 rc = parse_operation(var, ctx, p, end, &tmp);
2014             if (rc < 0)
2015                 goto error_return;
2016             p += rc;
2017             if (failed)
2018                 result->end += rc;
2019         }
2020         if (p == end || *p != var->syntax.delim_close) {
2021             rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
2022             goto error_return;
2023         }
2024         p++;
2025         if (failed)
2026             result->end++;
2027     } else if (p[-1] == '+') {
2028        p++;
2029     }
2030
2031     /* lazy handling of undefined variable */
2032     if (!failed && tokenbuf_isundef(result)) {
2033         if (ctx->force_expand) {
2034             rc = VAR_ERR_UNDEFINED_VARIABLE;
2035             goto error_return;
2036         } else {
2037             tokenbuf_set(result, begin - 1, p, 0);
2038         }
2039     }
2040
2041     /* return successfully */
2042     tokenbuf_free(&name);
2043     tokenbuf_free(&tmp);
2044     return (p - begin);
2045
2046     /* return with an error */
2047 error_return:
2048     tokenbuf_free(&name);
2049     tokenbuf_free(&tmp);
2050     tokenbuf_free(result);
2051     return rc;
2052 }
2053
2054 /* parse variable construct ("$name" or "${name...}") */
2055 static int 
2056 parse_variable(
2057     var_t *var, var_parse_t *ctx,
2058     const char *begin, const char *end,
2059     tokenbuf_t *result)
2060 {
2061     const char *p;
2062     const char *data;
2063     int len, buffer_size;
2064     int rc, rc2;
2065     int inc;
2066
2067     /* initialization */
2068     p = begin;
2069     tokenbuf_init(result);
2070
2071     /* parse init delimiter */
2072     if (p == end || *p != var->syntax.delim_init)
2073         return 0;
2074     p++;
2075     if (p == end)
2076         return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
2077
2078     /* parse a simple variable name. 
2079        (if this fails, we're try to parse a complex variable construct) */
2080     rc = parse_name(var, ctx, p, end);
2081     if (rc < 0)
2082         return rc;
2083     if (rc > 0) {
2084         inc = (p[rc] == '+');
2085         rc2 = lookup_value(var, ctx, p, rc, inc, 0, &data, &len, &buffer_size);
2086         if (rc2 == VAR_ERR_UNDEFINED_VARIABLE && !ctx->force_expand) {
2087             tokenbuf_set(result, begin, begin + 1 + rc, 0);
2088             return (1 + rc);
2089         }
2090         if (rc2 < 0)
2091             return rc2;
2092         tokenbuf_set(result, data, data + len, buffer_size);
2093         return (1 + rc);
2094     }
2095
2096     /* parse a complex variable construct (else case) */
2097     rc = parse_variable_complex(var, ctx, p, end, result);
2098     if (rc > 0)
2099         rc++;
2100     return rc;
2101 }
2102
2103 /* parse loop construct limits ("[...]{b,s,e}") */
2104 static var_rc_t 
2105 parse_looplimits(
2106     var_t *var, var_parse_t *ctx,
2107     const char *begin, const char *end,
2108     int *start, int *step, int *stop, int *open_stop)
2109 {
2110     const char *p;
2111     int rc;
2112     int failed;
2113
2114     /* initialization */
2115     p = begin;
2116
2117     /* we are happy if nothing is to left to parse */
2118     if (p == end)
2119         return VAR_OK;
2120
2121     /* parse start delimiter */
2122     if (*p != var->syntax.delim_open)
2123         return VAR_OK;
2124     p++;
2125
2126     /* parse loop start value */
2127     failed = 0;
2128     rc = parse_numexp(var, ctx, p, end, start, &failed);
2129     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
2130         *start = 0; /* use default */
2131     else if (rc < 0)
2132         return (var_rc_t)rc;
2133     else
2134         p += rc;
2135     if (failed)
2136         return VAR_ERR_UNDEFINED_VARIABLE;
2137
2138     /* parse separator */
2139     if (*p != ',')
2140         return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2141     p++;
2142
2143     /* parse loop step value */
2144     failed = 0;
2145     rc = parse_numexp(var, ctx, p, end, step, &failed);
2146     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
2147         *step = 1; /* use default */
2148     else if (rc < 0)
2149         return (var_rc_t)rc;
2150     else
2151         p += rc;
2152     if (failed)
2153         return VAR_ERR_UNDEFINED_VARIABLE;
2154
2155     /* parse separator */
2156     if (*p != ',') {
2157         /* if not found, parse end delimiter */
2158         if (*p != var->syntax.delim_close)
2159             return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2160         p++;
2161
2162         /* shift step value to stop value */
2163         *stop = *step;
2164         *step = 1;
2165
2166         /* determine whether loop end is open */
2167         if (rc > 0)
2168             *open_stop = 0;
2169         else
2170             *open_stop = 1;
2171         return (var_rc_t)(p - begin);
2172     }
2173     p++;
2174
2175     /* parse loop stop value */
2176     failed = 0;
2177     rc = parse_numexp(var, ctx, p, end, stop, &failed);
2178     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC) {
2179         *stop = 0; /* use default */
2180         *open_stop = 1;
2181     }
2182     else if (rc < 0)
2183         return (var_rc_t)rc;
2184     else {
2185         *open_stop = 0;
2186         p += rc;
2187     }
2188     if (failed)
2189         return VAR_ERR_UNDEFINED_VARIABLE;
2190
2191     /* parse end delimiter */
2192     if (*p != var->syntax.delim_close)
2193         return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2194     p++;
2195
2196     /* return amount of parsed input */
2197     return (var_rc_t)(p - begin);
2198 }
2199
2200 /* parse plain text */
2201 static int 
2202 parse_text(
2203     var_t *var, var_parse_t *ctx,
2204     const char *begin, const char *end)
2205 {
2206     const char *p;
2207
2208     /* parse until delim_init (variable construct) 
2209        or index_open (loop construct) is found */
2210     for (p = begin; p != end; p++) {
2211         if (*p == var->syntax.escape) {
2212             p++; /* skip next character */
2213             if (p == end)
2214                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
2215         }
2216         else if (*p == var->syntax.delim_init)
2217             break;
2218         else if (   var->syntax.index_open != EOS
2219                  && (   *p == var->syntax.index_open 
2220                      || *p == var->syntax.index_close))
2221             break;
2222     }
2223     return (p - begin);
2224 }
2225
2226 /* expand input in general */
2227 static var_rc_t 
2228 parse_input(
2229     var_t *var, var_parse_t *ctx,
2230     const char *begin, const char *end,
2231     tokenbuf_t *output, int recursion_level)
2232 {
2233     const char *p;
2234     int rc, rc2;
2235     tokenbuf_t result;
2236     int start, step, stop, open_stop;
2237     int i;
2238     int output_backup;
2239     int rel_lookup_cnt;
2240     int loop_limit_length;
2241     var_parse_t myctx;
2242
2243     /* initialization */
2244     p = begin;
2245
2246     do {
2247         /* try to parse a loop construct */
2248         if (   p != end 
2249             && var->syntax.index_open != EOS 
2250             && *p == var->syntax.index_open) {
2251             p++;
2252
2253             /* loop preparation */
2254             loop_limit_length = -1;
2255             rel_lookup_cnt = ctx->rel_lookup_cnt;
2256             open_stop = 1;
2257             rc = 0;
2258             start = 0;
2259             step  = 1;
2260             stop  = 0;
2261             output_backup = 0;
2262
2263             /* iterate over loop construct, either as long as there is
2264                (still) nothing known about the limit, or there is an open
2265                (=unknown) limit stop and there are still defined variables
2266                or there is a stop limit known and it is still not reached */
2267             re_loop:
2268             for (i = start;
2269                  (   (   open_stop 
2270                       && (   loop_limit_length < 0 
2271                           || rel_lookup_cnt > ctx->rel_lookup_cnt)) 
2272                   || (   !open_stop 
2273                       && i <= stop)                                );
2274                  i += step) {
2275
2276                 /* remember current output end for restoring */
2277                 output_backup = (output->end - output->begin);
2278
2279                 /* open temporary context for recursion */ 
2280                 ctx = var_parse_push(ctx, &myctx);
2281                 ctx->force_expand    = 1;
2282                 ctx->rel_lookup_flag = 1;
2283                 ctx->index_this      = i;
2284
2285                 /* recursive parse input through ourself */
2286                 rc = parse_input(var, ctx, p, end, 
2287                                  output, recursion_level+1);
2288
2289                 /* retrieve info and close temporary context */
2290                 rel_lookup_cnt = ctx->rel_lookup_cnt;
2291                 ctx = var_parse_pop(ctx);
2292
2293                 /* error handling */
2294                 if (rc < 0)
2295                     goto error_return;
2296
2297                 /* make sure the loop construct is closed */
2298                 if (p[rc] != var->syntax.index_close) {
2299                     rc = VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT;
2300                     goto error_return;
2301                 }
2302
2303                 /* try to parse loop construct limit specification */
2304                 if (loop_limit_length < 0) {
2305                     rc2 = parse_looplimits(var, ctx, p+rc+1, end, 
2306                                            &start, &step, &stop, &open_stop);
2307                     if (rc2 < 0)
2308                         goto error_return;
2309                     else if (rc2 == 0)
2310                         loop_limit_length = 0;
2311                     else if (rc2 > 0) {
2312                         loop_limit_length = rc2;
2313                         /* restart loop from scratch */
2314                         output->end = (output->begin + output_backup);
2315                         goto re_loop;
2316                     }
2317                 }
2318             }
2319
2320             /* if stop value is open, restore to the output end
2321                because the last iteration was just to determine the loop
2322                termination and its result has to be discarded */
2323             if (open_stop)
2324                 output->end = (output->begin + output_backup);
2325
2326             /* skip parsed loop construct */
2327             p += rc;
2328             p++;
2329             p += loop_limit_length;
2330
2331             continue;
2332         }
2333
2334         /* try to parse plain text */
2335         rc = parse_text(var, ctx, p, end);
2336         if (rc > 0) {
2337             if (!tokenbuf_append(output, p, rc)) {
2338                 rc = VAR_ERR_OUT_OF_MEMORY;
2339                 goto error_return;
2340             }
2341             p += rc;
2342             continue;
2343         } else if (rc < 0)
2344             goto error_return;
2345
2346         /* try to parse a variable construct */
2347         tokenbuf_init(&result);
2348         rc = parse_variable(var, ctx, p, end, &result);
2349         if (rc > 0) {
2350             if (!tokenbuf_merge(output, &result)) {
2351                 tokenbuf_free(&result);
2352                 rc = VAR_ERR_OUT_OF_MEMORY;
2353                 goto error_return;
2354             }
2355             tokenbuf_free(&result);
2356             p += rc;
2357             continue;
2358         }    
2359         tokenbuf_free(&result);
2360         if (rc < 0)
2361             goto error_return;
2362
2363     } while (p != end && rc > 0);
2364
2365     /* We do not know whether this really could happen, but because we
2366        are paranoid, report an error at the outer most parsing level if
2367        there is still any input. Because this would mean that we are no
2368        longer able to parse the remaining input as a loop construct, a
2369        text or a variable construct. This would be very strange, but
2370        could perhaps happen in case of configuration errors!?... */
2371     if (recursion_level == 0 && p != end) {
2372         rc = VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE;
2373         goto error_return;
2374     }
2375
2376     /* return amount of parsed text */
2377     return (var_rc_t)(p - begin);
2378
2379     /* return with an error where as a special case the output begin is
2380        set to the input begin and the output end to the last input parsing
2381        position. */
2382     error_return:
2383     tokenbuf_free(output);
2384     tokenbuf_set(output, begin, p, 0);
2385     return (var_rc_t)rc;
2386 }
2387
2388 /* 
2389 **
2390 **  ==== APPLICATION PROGRAMMING INTERFACE (API) ====
2391 **
2392 */
2393
2394 /* create variable expansion context */
2395 var_rc_t 
2396 var_create(
2397     var_t **pvar)
2398 {
2399     var_t *var;
2400
2401     if (pvar == NULL)
2402         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2403     if ((var = (var_t *)malloc(sizeof(var_t))) == NULL)
2404         return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2405     memset(var, 0, sizeof(var));
2406     var_config(var, VAR_CONFIG_SYNTAX, &var_syntax_default);
2407     *pvar = var;
2408     return VAR_OK;
2409 }
2410
2411 /* destroy variable expansion context */
2412 var_rc_t 
2413 var_destroy(
2414     var_t *var)
2415 {
2416     if (var == NULL)
2417         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2418     free(var);
2419     return VAR_OK;
2420 }
2421
2422 /* configure variable expansion context */
2423 var_rc_t 
2424 var_config(
2425     var_t *var, 
2426     var_config_t mode, 
2427     ...)
2428 {
2429     va_list ap;
2430     var_rc_t rc;
2431
2432     if (var == NULL)
2433         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2434     va_start(ap, mode);
2435     switch (mode) {
2436         case VAR_CONFIG_SYNTAX: {
2437             var_syntax_t *s;
2438             s = (var_syntax_t *)va_arg(ap, void *);
2439             if (s == NULL)
2440                 return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2441             var->syntax.escape      = s->escape;
2442             var->syntax.delim_init  = s->delim_init;
2443             var->syntax.delim_open  = s->delim_open;
2444             var->syntax.delim_close = s->delim_close;
2445             var->syntax.index_open  = s->index_open;
2446             var->syntax.index_close = s->index_close;
2447             var->syntax.index_mark  = s->index_mark;
2448             var->syntax.name_chars  = NULL; /* unused internally */
2449             if ((rc = expand_character_class(s->name_chars, var->syntax_nameclass)) != VAR_OK)
2450                 return VAR_RC(rc);
2451             if (   var->syntax_nameclass[(int)var->syntax.delim_init] 
2452                 || var->syntax_nameclass[(int)var->syntax.delim_open]
2453                 || var->syntax_nameclass[(int)var->syntax.delim_close]
2454                 || var->syntax_nameclass[(int)var->syntax.escape])
2455                 return VAR_RC(VAR_ERR_INVALID_CONFIGURATION);
2456             break;
2457         }
2458         case VAR_CONFIG_CB_VALUE: {
2459             var_cb_value_t fct;
2460             void *ctx;
2461             fct = (var_cb_value_t)va_arg(ap, void *);
2462             ctx = (void *)va_arg(ap, void *);
2463             var->cb_value_fct = fct;
2464             var->cb_value_ctx = ctx;
2465             break;
2466         }
2467         case VAR_CONFIG_CB_OPERATION: {
2468             var_cb_operation_t fct;
2469             void *ctx;
2470             fct = (var_cb_operation_t)va_arg(ap, void *);
2471             ctx = (void *)va_arg(ap, void *);
2472             var->cb_operation_fct = fct;
2473             var->cb_operation_ctx = ctx;
2474             break;
2475         }
2476         default:
2477             return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2478     }
2479     va_end(ap);
2480     return VAR_OK;
2481 }
2482
2483 /* perform unescape operation on a buffer */
2484 var_rc_t 
2485 var_unescape(
2486     var_t *var, 
2487     const char *src, int srclen, 
2488     char *dst, int dstlen, 
2489     int all)
2490 {
2491     const char *end;
2492     var_rc_t rc;
2493
2494     if (var == NULL || src == NULL || dst == NULL)
2495         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2496     end = src + srclen;
2497     while (src < end) {
2498         if (*src == '\\') {
2499             if (++src == end)
2500                 return VAR_RC(VAR_ERR_INCOMPLETE_NAMED_CHARACTER);
2501             switch (*src) {
2502                 case '\\':
2503                     if (!all) {
2504                         *dst++ = '\\';
2505                     }
2506                     *dst++ = '\\';
2507                     break;
2508                 case 'n':
2509                     *dst++ = '\n';
2510                     break;
2511                 case 't':
2512                     *dst++ = '\t';
2513                     break;
2514                 case 'r':
2515                     *dst++ = '\r';
2516                     break;
2517                 case 'x':
2518                     ++src;
2519                     if ((rc = expand_hex(&src, &dst, end)) != VAR_OK)
2520                         return VAR_RC(rc);
2521                     break;
2522                 case '0': case '1': case '2': case '3': case '4':
2523                 case '5': case '6': case '7': case '8': case '9':
2524                     if (   end - src >= 3 
2525                         && isdigit((int)src[1]) 
2526                         && isdigit((int)src[2])) {
2527                         if ((rc = expand_octal(&src, &dst, end)) != 0)
2528                             return VAR_RC(rc);
2529                         break;
2530                     }
2531                 default:
2532                     if (!all) {
2533                         *dst++ = '\\';
2534                     }
2535                     *dst++ = *src;
2536             }
2537             ++src;
2538         } else
2539             *dst++ = *src++;
2540     }
2541     *dst = EOS;
2542     return VAR_OK;
2543 }
2544
2545 /* perform expand operation on a buffer */
2546 var_rc_t 
2547 var_expand(
2548     var_t *var, 
2549     const char *src_ptr, int src_len, 
2550     char **dst_ptr, int *dst_len, 
2551     int force_expand)
2552 {
2553     var_parse_t ctx;
2554     tokenbuf_t output;
2555     var_rc_t rc;
2556
2557     /* argument sanity checks */
2558     if (var == NULL || src_ptr == NULL || src_len == 0 || dst_ptr == NULL)
2559         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2560
2561     /* prepare internal expansion context */
2562     ctx.lower           = NULL;
2563     ctx.force_expand    = force_expand;
2564     ctx.rel_lookup_flag = 0;
2565     ctx.rel_lookup_cnt  = 0;
2566     ctx.index_this      = 0;
2567
2568     /* start the parsing */
2569     tokenbuf_init(&output);
2570     rc = parse_input(var, &ctx, src_ptr, src_ptr+src_len, &output, 0);
2571
2572     /* post-processing */
2573     if (rc >= 0) {
2574         /* always EOS-terminate output for convinience reasons 
2575            but do not count the EOS-terminator in the length */
2576         if (!tokenbuf_append(&output, "\0", 1)) {
2577             tokenbuf_free(&output);
2578             return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2579         }
2580         output.end--;
2581
2582         /* provide result */
2583         *dst_ptr = (char *)output.begin;
2584         if (dst_len != NULL)
2585             *dst_len = (output.end - output.begin);
2586         rc = VAR_OK;
2587     }
2588     else {
2589         /* provide result */
2590         if (dst_len != NULL)
2591             *dst_len = (output.end - output.begin);
2592     }
2593
2594     return VAR_RC(rc);
2595 }
2596
2597 /* format and expand a string */
2598 var_rc_t 
2599 var_formatv(
2600     var_t *var, 
2601     char **dst_ptr, int force_expand, 
2602     const char *fmt, va_list ap)
2603 {
2604     var_rc_t rc;
2605     va_list apbak;
2606     char *cpBuf;
2607     int nBuf;
2608
2609     /* argument sanity checks */
2610     if (var == NULL || dst_ptr == NULL || fmt == NULL)
2611         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2612
2613     /* determine formatting buffer length */
2614     apbak = ap;
2615     nBuf = var_mvsnprintf(NULL, 0, fmt, ap);
2616     ap = apbak;
2617     if (nBuf == -1)
2618         return VAR_RC(VAR_ERR_FORMATTING_FAILURE);
2619
2620     /* perform formatting */
2621     if ((cpBuf = (char *)malloc(nBuf+1)) == NULL)
2622         return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2623     nBuf = var_mvsnprintf(cpBuf, nBuf+1, fmt, ap);
2624     if (nBuf == -1) {
2625         free(cpBuf);
2626         return VAR_RC(VAR_ERR_FORMATTING_FAILURE);
2627     }
2628
2629     /* perform expansion */
2630     if ((rc = var_expand(var, cpBuf, nBuf, dst_ptr, NULL, force_expand)) != VAR_OK) {
2631         free(cpBuf);
2632         return VAR_RC(rc);
2633     }
2634
2635     /* cleanup */
2636     free(cpBuf);
2637
2638     return VAR_OK;
2639 }
2640
2641 /* format and expand a string */
2642 var_rc_t 
2643 var_format(
2644     var_t *var, 
2645     char **dst_ptr, int force_expand, 
2646     const char *fmt, ...)
2647 {
2648     var_rc_t rc;
2649     va_list ap;
2650
2651     /* argument sanity checks */
2652     if (var == NULL || dst_ptr == NULL || fmt == NULL)
2653         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2654
2655     va_start(ap, fmt);
2656     rc = var_formatv(var, dst_ptr, force_expand, fmt, ap);
2657     va_end(ap);
2658
2659     return VAR_RC(rc);
2660 }
2661
2662 /* var_rc_t to string mapping table */
2663 static const char *var_errors[] = {
2664     "everything ok",                                           /* VAR_OK = 0 */
2665     "incomplete named character",                              /* VAR_ERR_INCOMPLETE_NAMED_CHARACTER */
2666     "incomplete hexadecimal value",                            /* VAR_ERR_INCOMPLETE_HEX */
2667     "invalid hexadecimal value",                               /* VAR_ERR_INVALID_HEX */
2668     "octal value too large",                                   /* VAR_ERR_OCTAL_TOO_LARGE */
2669     "invalid octal value",                                     /* VAR_ERR_INVALID_OCTAL */
2670     "incomplete octal value",                                  /* VAR_ERR_INCOMPLETE_OCTAL */
2671     "incomplete grouped hexadecimal value",                    /* VAR_ERR_INCOMPLETE_GROUPED_HEX */
2672     "incorrect character class specification",                 /* VAR_ERR_INCORRECT_CLASS_SPEC */
2673     "invalid expansion configuration",                         /* VAR_ERR_INVALID_CONFIGURATION */
2674     "out of memory",                                           /* VAR_ERR_OUT_OF_MEMORY */
2675     "incomplete variable specification",                       /* VAR_ERR_INCOMPLETE_VARIABLE_SPEC */
2676     "undefined variable",                                      /* VAR_ERR_UNDEFINED_VARIABLE */
2677     "input is neither text nor variable",                      /* VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE */
2678     "unknown command character in variable",                   /* VAR_ERR_UNKNOWN_COMMAND_CHAR */
2679     "malformatted search and replace operation",               /* VAR_ERR_MALFORMATTED_REPLACE */
2680     "unknown flag in search and replace operation",            /* VAR_ERR_UNKNOWN_REPLACE_FLAG */
2681     "invalid regex in search and replace operation",           /* VAR_ERR_INVALID_REGEX_IN_REPLACE */
2682     "missing parameter in command",                            /* VAR_ERR_MISSING_PARAMETER_IN_COMMAND */
2683     "empty search string in search and replace operation",     /* VAR_ERR_EMPTY_SEARCH_STRING */
2684     "start offset missing in cut operation",                   /* VAR_ERR_MISSING_START_OFFSET */
2685     "offsets in cut operation delimited by unknown character", /* VAR_ERR_INVALID_OFFSET_DELIMITER */
2686     "range out of bounds in cut operation",                    /* VAR_ERR_RANGE_OUT_OF_BOUNDS */
2687     "offset out of bounds in cut operation",                   /* VAR_ERR_OFFSET_OUT_OF_BOUNDS */
2688     "logic error in cut operation",                            /* VAR_ERR_OFFSET_LOGIC */
2689     "malformatted transpose operation",                        /* VAR_ERR_MALFORMATTED_TRANSPOSE */
2690     "source and target class mismatch in transpose operation", /* VAR_ERR_TRANSPOSE_CLASSES_MISMATCH */
2691     "empty character class in transpose operation",            /* VAR_ERR_EMPTY_TRANSPOSE_CLASS */
2692     "incorrect character class in transpose operation",        /* VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC */
2693     "malformatted padding operation",                          /* VAR_ERR_MALFORMATTED_PADDING */
2694     "width parameter missing in padding operation",            /* VAR_ERR_MISSING_PADDING_WIDTH */
2695     "fill string missing in padding operation",                /* VAR_ERR_EMPTY_PADDING_FILL_STRING */
2696     "unknown quoted pair in search and replace operation",     /* VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE */
2697     "sub-matching reference out of range",                     /* VAR_ERR_SUBMATCH_OUT_OF_RANGE */
2698     "invalid argument",                                        /* VAR_ERR_INVALID_ARGUMENT */
2699     "incomplete quoted pair",                                  /* VAR_ERR_INCOMPLETE_QUOTED_PAIR */
2700     "lookup function does not support variable arrays",        /* VAR_ERR_ARRAY_LOOKUPS_ARE_UNSUPPORTED */
2701     "index of array variable contains an invalid character",   /* VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC */
2702     "index of array variable is incomplete",                   /* VAR_ERR_INCOMPLETE_INDEX_SPEC */
2703     "bracket expression in array variable's index not closed", /* VAR_ERR_UNCLOSED_BRACKET_IN_INDEX */
2704     "division by zero error in index specification",           /* VAR_ERR_DIVISION_BY_ZERO_IN_INDEX */
2705     "unterminated loop construct",                             /* VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT */
2706     "invalid character in loop limits",                        /* VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS */
2707     "malformed operation argument list",                       /* VAR_ERR_MALFORMED_OPERATION_ARGUMENTS */
2708     "undefined operation",                                     /* VAR_ERR_UNDEFINED_OPERATION */
2709     "formatting failure"                                       /* VAR_ERR_FORMATTING_FAILURE */
2710 };
2711
2712 /* translate a return code into its corresponding descriptive text */
2713 char *var_strerror(var_t *var, var_rc_t rc)
2714 {
2715     char *str;
2716     rc = (var_rc_t)(0 - rc);
2717     if (rc < 0 || rc >= (int)sizeof(var_errors) / (int)sizeof(char *)) {
2718         str = "unknown error";
2719     } else {
2720         str = (char *)var_errors[rc];
2721     }
2722     return str;
2723 }