]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/var.c
ae777fbea83ea765dd09208279bdc7f52919cdf2
[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     } while (rc > 0);
694     tokenbuf_free(&tmp);
695     return (p - begin);
696
697     error_return:
698     tokenbuf_free(&tmp);
699     tokenbuf_free(result);
700     return rc;
701 }
702
703 /* parse expression or variable */
704 static int 
705 parse_exptext_or_variable(
706     var_t *var, var_parse_t *ctx,
707     const char *begin, const char *end,
708     tokenbuf_t *result)
709 {
710     const char *p = begin;
711     tokenbuf_t tmp;
712     int rc;
713
714     tokenbuf_init(result);
715     tokenbuf_init(&tmp);
716     if (begin == end)
717         return 0;
718     do {
719         /* try to parse expression text */
720         rc = parse_exptext(var, ctx, p, end);
721         if (rc < 0)
722             goto error_return;
723         if (rc > 0) {
724             if (!tokenbuf_append(result, p, rc)) {
725                 rc = VAR_ERR_OUT_OF_MEMORY;
726                 goto error_return;
727             }
728             p += rc;
729         }
730
731         /* try to parse variable construct */
732         rc = parse_variable(var, ctx, p, end, &tmp);
733         if (rc < 0)
734             goto error_return;
735         if (rc > 0) {
736             p += rc;
737             if (!tokenbuf_append
738                 (result, tmp.begin, tmp.end - tmp.begin)) {
739                 rc = VAR_ERR_OUT_OF_MEMORY;
740                 goto error_return;
741             }
742         }
743     } while (rc > 0);
744
745     tokenbuf_free(&tmp);
746     return (p - begin);
747
748     error_return:
749     tokenbuf_free(&tmp);
750     tokenbuf_free(result);
751     return rc;
752 }
753
754 /* parse substitution text or variable */
755 static int 
756 parse_substext_or_variable(
757     var_t *var, var_parse_t *ctx,
758     const char *begin, const char *end,
759     tokenbuf_t *result)
760 {
761     const char *p = begin;
762     tokenbuf_t tmp;
763     int rc;
764
765     tokenbuf_init(result);
766     tokenbuf_init(&tmp);
767     if (begin == end)
768         return 0;
769     do {
770         /* try to parse substitution text */
771         rc = parse_substext(var, ctx, p, end);
772         if (rc < 0)
773             goto error_return;
774         if (rc > 0) {
775             if (!tokenbuf_append(result, p, rc)) {
776                 rc = VAR_ERR_OUT_OF_MEMORY;
777                 goto error_return;
778             }
779             p += rc;
780         }
781
782         /* try to parse substitution text */
783         rc = parse_variable(var, ctx, p, end, &tmp);
784         if (rc < 0)
785             goto error_return;
786         if (rc > 0) {
787             p += rc;
788             if (!tokenbuf_append
789                 (result, tmp.begin, tmp.end - tmp.begin)) {
790                 rc = VAR_ERR_OUT_OF_MEMORY;
791                 goto error_return;
792             }
793         }
794     } while (rc > 0);
795
796     tokenbuf_free(&tmp);
797     return (p - begin);
798
799     error_return:
800     tokenbuf_free(&tmp);
801     tokenbuf_free(result);
802     return rc;
803 }
804
805 /* parse class description */
806 static int 
807 parse_class_description(
808     var_t *var, var_parse_t *ctx,
809     tokenbuf_t *src, tokenbuf_t *dst)
810 {
811     unsigned char c, d;
812     const char *p; 
813
814     p = src->begin;
815     while (p != src->end) {
816         if ((src->end - p) >= 3 && p[1] == '-') {
817             if (*p > p[2])
818                 return VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC;
819             for (c = *p, d = p[2]; c <= d; ++c) {
820                 if (!tokenbuf_append(dst, (char *)&c, 1))
821                     return VAR_ERR_OUT_OF_MEMORY;
822             }
823             p += 3;
824         } else {
825             if (!tokenbuf_append(dst, p, 1))
826                 return VAR_ERR_OUT_OF_MEMORY;
827             p++;
828         }
829     }
830     return VAR_OK;
831 }
832
833 /* parse regex replace part */
834 static int 
835 parse_regex_replace(
836     var_t *var, var_parse_t *ctx,
837     const char *data, 
838     tokenbuf_t *orig,
839     regmatch_t *pmatch, 
840     tokenbuf_t *expanded)
841 {
842     const char *p;
843     int i;
844
845     p = orig->begin;
846     tokenbuf_init(expanded);
847
848     while (p != orig->end) {
849         if (*p == '\\') {
850             if (orig->end - p <= 1) {
851                 tokenbuf_free(expanded);
852                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
853             }
854             p++;
855             if (*p == '\\') {
856                 if (!tokenbuf_append(expanded, p, 1)) {
857                     tokenbuf_free(expanded);
858                     return VAR_ERR_OUT_OF_MEMORY;
859                 }
860                 p++;
861                 continue;
862             }
863             if (!isdigit((int)*p)) {
864                 tokenbuf_free(expanded);
865                 return VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE;
866             }
867             i = (*p - '0');
868             p++;
869             if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1) {
870                 tokenbuf_free(expanded);
871                 return VAR_ERR_SUBMATCH_OUT_OF_RANGE;
872             }
873             if (!tokenbuf_append(expanded, data + pmatch[i].rm_so,
874                                  pmatch[i].rm_eo - pmatch[i].rm_so)) {
875                 tokenbuf_free(expanded);
876                 return VAR_ERR_OUT_OF_MEMORY;
877             }
878         } else {
879             if (!tokenbuf_append(expanded, p, 1)) {
880                 tokenbuf_free(expanded);
881                 return VAR_ERR_OUT_OF_MEMORY;
882             }
883             p++;
884         }
885     }
886
887     return VAR_OK;
888 }
889
890 /* operation: transpose */
891 static int 
892 op_transpose(
893     var_t *var, var_parse_t *ctx,
894     tokenbuf_t *data, 
895     tokenbuf_t *search,
896     tokenbuf_t *replace)
897 {
898     tokenbuf_t srcclass, dstclass;
899     const char *p;
900     int rc;
901     int i;
902
903     tokenbuf_init(&srcclass);
904     tokenbuf_init(&dstclass);
905     if ((rc = parse_class_description(var, ctx, search, &srcclass)) != VAR_OK)
906         goto error_return;
907     if ((rc = parse_class_description(var, ctx, replace, &dstclass)) != VAR_OK)
908         goto error_return;
909     if (srcclass.begin == srcclass.end) {
910         rc = VAR_ERR_EMPTY_TRANSPOSE_CLASS;
911         goto error_return;
912     }
913     if ((srcclass.end - srcclass.begin) != (dstclass.end - dstclass.begin)) {
914         rc = VAR_ERR_TRANSPOSE_CLASSES_MISMATCH;
915         goto error_return;
916     }
917     if (data->buffer_size == 0) {
918         tokenbuf_t tmp;
919         if (!tokenbuf_assign(&tmp, data->begin, data->end - data->begin)) {
920             rc = VAR_ERR_OUT_OF_MEMORY;
921             goto error_return;
922         }
923         tokenbuf_move(&tmp, data);
924     }
925     for (p = data->begin; p != data->end; ++p) {
926         for (i = 0; i <= (srcclass.end - srcclass.begin); ++i) {
927             if (*p == srcclass.begin[i]) {
928                 *((char *)p) = dstclass.begin[i];
929                 break;
930             }
931         }
932     }
933     tokenbuf_free(&srcclass);
934     tokenbuf_free(&dstclass);
935     return VAR_OK;
936
937     error_return:
938     tokenbuf_free(search);
939     tokenbuf_free(replace);
940     tokenbuf_free(&srcclass);
941     tokenbuf_free(&dstclass);
942     return rc;
943 }
944
945 /* operation: search & replace */
946 static int 
947 op_search_and_replace(
948     var_t *var, var_parse_t *ctx,
949     tokenbuf_t *data, 
950     tokenbuf_t *search,
951     tokenbuf_t *replace, 
952     tokenbuf_t *flags)
953 {
954     tokenbuf_t tmp;
955     const char *p;
956     int case_insensitive = 0;
957     int multiline = 0;
958     int global = 0;
959     int no_regex = 0;
960     int rc;
961
962     if (search->begin == search->end)
963         return VAR_ERR_EMPTY_SEARCH_STRING;
964
965     for (p = flags->begin; p != flags->end; p++) {
966         switch (tolower(*p)) {
967             case 'm':
968                 multiline = 1;
969                 break;
970             case 'i':
971                 case_insensitive = 1;
972                 break;
973             case 'g':
974                 global = 1;
975                 break;
976             case 't':
977                 no_regex = 1;
978                 break;
979             default:
980                 return VAR_ERR_UNKNOWN_REPLACE_FLAG;
981         }
982     }
983
984     if (no_regex) {
985         /* plain text pattern based operation */
986         tokenbuf_init(&tmp);
987         for (p = data->begin; p != data->end;) {
988             if (case_insensitive)
989                 rc = strncasecmp(p, search->begin, search->end - search->begin);
990             else
991                 rc = strncmp(p, search->begin, search->end - search->begin);
992             if (rc != 0) {
993                 /* not matched, copy character */
994                 if (!tokenbuf_append(&tmp, p, 1)) {
995                     tokenbuf_free(&tmp);
996                     return VAR_ERR_OUT_OF_MEMORY;
997                 }
998                 p++;
999             } else {
1000                 /* matched, copy replacement string */
1001                 tokenbuf_merge(&tmp, replace);
1002                 p += (search->end - search->begin);
1003                 if (!global) {
1004                     /* append remaining text */
1005                     if (!tokenbuf_append(&tmp, p, data->end - p)) {
1006                         tokenbuf_free(&tmp);
1007                         return VAR_ERR_OUT_OF_MEMORY;
1008                     }
1009                     break;
1010                 }
1011             }
1012         }
1013         tokenbuf_free(data);
1014         tokenbuf_move(&tmp, data);
1015     } else {
1016         /* regular expression pattern based operation */
1017         tokenbuf_t mydata;
1018         tokenbuf_t myreplace;
1019         regex_t preg;
1020         regmatch_t pmatch[10];
1021         int regexec_flag;
1022
1023         /* copy pattern and data to own buffer to make sure they are EOS-terminated */
1024         if (!tokenbuf_assign(&tmp, search->begin, search->end - search->begin))
1025             return VAR_ERR_OUT_OF_MEMORY;
1026         if (!tokenbuf_assign(&mydata, data->begin, data->end - data->begin)) {
1027             tokenbuf_free(&tmp);
1028             return VAR_ERR_OUT_OF_MEMORY;
1029         }
1030
1031         /* compile the pattern. */
1032         rc = regcomp(&preg, tmp.begin, 
1033                      (  REG_EXTENDED
1034                       | (multiline ? REG_NEWLINE : 0)
1035                       | (case_insensitive ? REG_ICASE : 0)));
1036         tokenbuf_free(&tmp);
1037         if (rc != 0) {
1038             tokenbuf_free(&mydata);
1039             return VAR_ERR_INVALID_REGEX_IN_REPLACE;
1040         }
1041
1042         /* match the pattern and create the result string in the tmp buffer */
1043         tokenbuf_append(&tmp, "", 0);
1044         for (p = mydata.begin; p < mydata.end; ) {
1045             if (p == mydata.begin || p[-1] == '\n')
1046                 regexec_flag = 0;
1047             else
1048                 regexec_flag = REG_NOTBOL;
1049             rc = regexec(&preg, p, sizeof(pmatch) / sizeof(regmatch_t), pmatch, regexec_flag);
1050             if (rc != 0) {
1051                 /* no (more) matching */
1052                 tokenbuf_append(&tmp, p, mydata.end - p);
1053                 break;
1054             }
1055             else if (   multiline 
1056                      && (p + pmatch[0].rm_so) == mydata.end 
1057                      && (pmatch[0].rm_eo - pmatch[0].rm_so) == 0) {
1058                 /* special case: found empty pattern (usually /^/ or /$/ only)
1059                    in multi-line at end of data (after the last newline) */
1060                 tokenbuf_append(&tmp, p, mydata.end - p);
1061                 break;
1062             }
1063             else {
1064                 /* append prolog string */
1065                 if (!tokenbuf_append(&tmp, p, pmatch[0].rm_so)) {
1066                     regfree(&preg);
1067                     tokenbuf_free(&tmp);
1068                     tokenbuf_free(&mydata);
1069                     return VAR_ERR_OUT_OF_MEMORY;
1070                 }
1071                 /* create replace string */
1072                 rc = parse_regex_replace(var, ctx, p, replace, pmatch, &myreplace);
1073                 if (rc != VAR_OK) {
1074                     regfree(&preg);
1075                     tokenbuf_free(&tmp);
1076                     tokenbuf_free(&mydata);
1077                     return rc;
1078                 }
1079                 /* append replace string */
1080                 if (!tokenbuf_append(&tmp, myreplace.begin, myreplace.end - myreplace.begin)) {
1081                     regfree(&preg);
1082                     tokenbuf_free(&tmp);
1083                     tokenbuf_free(&mydata);
1084                     tokenbuf_free(&myreplace);
1085                     return VAR_ERR_OUT_OF_MEMORY;
1086                 }
1087                 tokenbuf_free(&myreplace);
1088                 /* skip now processed data */
1089                 p += pmatch[0].rm_eo;
1090                 /* if pattern matched an empty part (think about
1091                    anchor-only regular expressions like /^/ or /$/) we
1092                    skip the next character to make sure we do not enter
1093                    an infinitive loop in matching */
1094                 if ((pmatch[0].rm_eo - pmatch[0].rm_so) == 0) {
1095                     if (p >= mydata.end)
1096                         break;
1097                     if (!tokenbuf_append(&tmp, p, 1)) {
1098                         regfree(&preg);
1099                         tokenbuf_free(&tmp);
1100                         tokenbuf_free(&mydata);
1101                         return VAR_ERR_OUT_OF_MEMORY;
1102                     }
1103                     p++;
1104                 }
1105                 /* append prolog string and stop processing if we 
1106                    do not perform the search & replace globally */
1107                 if (!global) {
1108                     if (!tokenbuf_append(&tmp, p, mydata.end - p)) {
1109                         regfree(&preg);
1110                         tokenbuf_free(&tmp);
1111                         tokenbuf_free(&mydata);
1112                         return VAR_ERR_OUT_OF_MEMORY;
1113                     }
1114                     break;
1115                 }
1116             }
1117         }
1118         regfree(&preg);
1119         tokenbuf_free(data);
1120         tokenbuf_move(&tmp, data);
1121         tokenbuf_free(&mydata);
1122     }
1123
1124     return VAR_OK;
1125 }
1126
1127 /* operation: offset substring */
1128 static int 
1129 op_offset(
1130     var_t *var, var_parse_t *ctx,
1131     tokenbuf_t *data, 
1132     int num1,
1133     int num2, 
1134     int isrange)
1135 {
1136     tokenbuf_t res;
1137     const char *p;
1138
1139     /* determine begin of result string */
1140     if ((data->end - data->begin) < num1)
1141         return VAR_ERR_OFFSET_OUT_OF_BOUNDS;
1142     p = data->begin + num1;
1143
1144     /* if num2 is zero, we copy the rest from there. */
1145     if (num2 == 0) {
1146         if (!tokenbuf_assign(&res, p, data->end - p))
1147             return VAR_ERR_OUT_OF_MEMORY;
1148     } else {
1149         /* ok, then use num2. */
1150         if (isrange) {
1151             if ((p + num2) > data->end)
1152                 return VAR_ERR_RANGE_OUT_OF_BOUNDS;
1153             if (!tokenbuf_assign(&res, p, num2))
1154                 return VAR_ERR_OUT_OF_MEMORY;
1155         } else {
1156             if (num2 < num1)
1157                 return VAR_ERR_OFFSET_LOGIC;
1158             if ((data->begin + num2) > data->end)
1159                 return VAR_ERR_RANGE_OUT_OF_BOUNDS;
1160             if (!tokenbuf_assign(&res, p, num2 - num1 + 1))
1161                 return VAR_ERR_OUT_OF_MEMORY;
1162         }
1163     }
1164     tokenbuf_free(data);
1165     tokenbuf_move(&res, data);
1166     return VAR_OK;
1167 }
1168
1169 /* operation: padding */
1170 static int 
1171 op_padding(
1172     var_t *var, var_parse_t *ctx,
1173     tokenbuf_t *data, 
1174     int width, 
1175     tokenbuf_t *fill,
1176     char position)
1177 {
1178     tokenbuf_t result;
1179     int i;
1180
1181     if (fill->begin == fill->end)
1182         return VAR_ERR_EMPTY_PADDING_FILL_STRING;
1183     tokenbuf_init(&result);
1184     if (position == 'l') {
1185         /* left padding */
1186         i = width - (data->end - data->begin);
1187         if (i > 0) {
1188             i = i / (fill->end - fill->begin);
1189             while (i > 0) {
1190                 if (!tokenbuf_append(data, fill->begin, fill->end - fill->begin))
1191                     return VAR_ERR_OUT_OF_MEMORY;
1192                 i--;
1193             }
1194             i = (width - (data->end - data->begin)) % (fill->end - fill->begin);
1195             if (!tokenbuf_append(data, fill->begin, i))
1196                 return VAR_ERR_OUT_OF_MEMORY;
1197         }
1198     } else if (position == 'r') {
1199         /* right padding */
1200         i = width - (data->end - data->begin);
1201         if (i > 0) {
1202             i = i / (fill->end - fill->begin);
1203             while (i > 0) {
1204                 if (!tokenbuf_append(&result, fill->begin, fill->end - fill->begin)) {
1205                     tokenbuf_free(&result);
1206                     return VAR_ERR_OUT_OF_MEMORY;
1207                 }
1208                 i--;
1209             }
1210             i = (width - (data->end - data->begin)) % (fill->end - fill->begin);
1211             if (!tokenbuf_append(&result, fill->begin, i)) {
1212                 tokenbuf_free(&result);
1213                 return VAR_ERR_OUT_OF_MEMORY;
1214             }
1215             if (!tokenbuf_append(&result, data->begin, data->end - data->begin)) {
1216                 tokenbuf_free(&result);
1217                 return VAR_ERR_OUT_OF_MEMORY;
1218             }
1219             /* move string from temporary buffer to data buffer */
1220             tokenbuf_free(data);
1221             tokenbuf_move(&result, data);
1222         }
1223     } else if (position == 'c') {
1224         /* centered padding */
1225         i = (width - (data->end - data->begin)) / 2;
1226         if (i > 0) {
1227             /* create the prefix */
1228             i = i / (fill->end - fill->begin);
1229             while (i > 0) {
1230                 if (!tokenbuf_append(&result, fill->begin, fill->end - fill->begin)) {
1231                     tokenbuf_free(&result);
1232                     return VAR_ERR_OUT_OF_MEMORY;
1233                 }
1234                 i--;
1235             }
1236             i = ((width - (data->end - data->begin)) / 2)
1237                 % (fill->end - fill->begin);
1238             if (!tokenbuf_append(&result, fill->begin, i)) {
1239                 tokenbuf_free(&result);
1240                 return VAR_ERR_OUT_OF_MEMORY;
1241             }
1242             /* append the actual data string */
1243             if (!tokenbuf_append(&result, data->begin, data->end - data->begin)) {
1244                 tokenbuf_free(&result);
1245                 return VAR_ERR_OUT_OF_MEMORY;
1246             }
1247             /* append the suffix */
1248             i = width - (result.end - result.begin);
1249             i = i / (fill->end - fill->begin);
1250             while (i > 0) {
1251                 if (!tokenbuf_append(&result, fill->begin, fill->end - fill->begin)) {
1252                     tokenbuf_free(&result);
1253                     return VAR_ERR_OUT_OF_MEMORY;
1254                 }
1255                 i--;
1256             }
1257             i = width - (result.end - result.begin);
1258             if (!tokenbuf_append(&result, fill->begin, i)) {
1259                 tokenbuf_free(&result);
1260                 return VAR_ERR_OUT_OF_MEMORY;
1261             }
1262             /* move string from temporary buffer to data buffer */
1263             tokenbuf_free(data);
1264             tokenbuf_move(&result, data);
1265         }
1266     }
1267     return VAR_OK;
1268 }
1269
1270 /* parse an integer number ("123") */
1271 static int 
1272 parse_integer(
1273     var_t *var, var_parse_t *ctx,
1274     const char *begin, const char *end,
1275     int *result)
1276 {
1277     const char *p;
1278     int num;
1279
1280     p = begin;
1281     num = 0;
1282     while (isdigit(*p) && p != end) {
1283         num *= 10;
1284         num += (*p - '0');
1285         p++;
1286     }
1287     if (result != NULL)
1288         *result = num;
1289     return (p - begin);
1290 }
1291
1292 /* parse an operation (":x...") */
1293 static int 
1294 parse_operation(
1295     var_t *var, var_parse_t *ctx,
1296     const char *begin, const char *end,
1297     tokenbuf_t *data)
1298 {
1299     const char *p;
1300     tokenbuf_t tmptokbuf;
1301     tokenbuf_t search, replace, flags;
1302     tokenbuf_t number1, number2;
1303     int num1, num2;
1304     int isrange;
1305     int rc;
1306     char *ptr;
1307
1308     /* initialization */
1309     tokenbuf_init(&tmptokbuf);
1310     tokenbuf_init(&search);
1311     tokenbuf_init(&replace);
1312     tokenbuf_init(&flags);
1313     tokenbuf_init(&number1);
1314     tokenbuf_init(&number2);
1315     p = begin;
1316     if (p == end)
1317         return 0;
1318
1319     /* dispatch through the first operation character */
1320     switch (tolower(*p)) {
1321         case 'l': {
1322             /* turn value to lowercase. */
1323             if (data->begin != NULL) {
1324                 /* if the buffer does not live in an allocated buffer,
1325                    we have to copy it before modifying the contents. */
1326                 if (data->buffer_size == 0) {
1327                     if (!tokenbuf_assign(data, data->begin, data->end - data->begin)) {
1328                         rc = VAR_ERR_OUT_OF_MEMORY;
1329                         goto error_return;
1330                     }
1331                 }
1332                 /* convert value */
1333                 for (ptr = (char *)data->begin; ptr != data->end; ptr++)
1334                     *ptr = (char)tolower((int)(*ptr));
1335             }
1336             p++;
1337             break;
1338         }
1339         case 'u': {
1340             /* turn value to uppercase. */
1341             if (data->begin != NULL) {
1342                 /* if the buffer does not live in an allocated buffer,
1343                    we have to copy it before modifying the contents. */
1344                 if (data->buffer_size == 0) {
1345                     if (!tokenbuf_assign(data, data->begin, data->end - data->begin)) {
1346                         rc = VAR_ERR_OUT_OF_MEMORY;
1347                         goto error_return;
1348                     }
1349                 }
1350                 /* convert value */
1351                 for (ptr = (char *)data->begin; ptr != data->end; ptr++)
1352                     *ptr = (char)toupper((int)(*ptr));
1353             }
1354             p++;
1355             break;
1356         }
1357         case 'o': {
1358             /* cut out substring of value. */
1359             p++;
1360             rc = parse_integer(var, ctx, p, end, &num1);
1361             if (rc == 0) {
1362                 rc = VAR_ERR_MISSING_START_OFFSET;
1363                 goto error_return;
1364             }
1365             else if (rc < 0)
1366                 goto error_return;
1367             p += rc;
1368             if (*p == ',') {
1369                 isrange = 0;
1370                 p++;
1371             } else if (*p == '-') {
1372                 isrange = 1;
1373                 p++;
1374             } else {
1375                 rc = VAR_ERR_INVALID_OFFSET_DELIMITER;
1376                 goto error_return;
1377             }
1378             rc = parse_integer(var, ctx, p, end, &num2);
1379             p += rc;
1380             if (data->begin != NULL) {
1381                 rc = op_offset(var, ctx, data, num1, num2, isrange);
1382                 if (rc < 0)
1383                     goto error_return;
1384             }
1385             break;
1386         }
1387         case '#': {
1388             /* determine length of the value */
1389             if (data->begin != NULL) {
1390                 char buf[((sizeof(int)*8)/3)+10]; /* sufficient size: <#bits> x log_10(2) + safety */
1391                 sprintf(buf, "%d", (int)(data->end - data->begin));
1392                 tokenbuf_free(data);
1393                 if (!tokenbuf_assign(data, buf, strlen(buf))) {
1394                     rc = VAR_ERR_OUT_OF_MEMORY;
1395                     goto error_return;
1396                 }
1397             }
1398             p++;
1399             break;
1400         }
1401         case '-': {
1402             /* substitute parameter if data is empty */
1403             p++;
1404             rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
1405             if (rc < 0)
1406                 goto error_return;
1407             if (rc == 0) {
1408                 rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
1409                 goto error_return;
1410             }
1411             p += rc;
1412             if (tokenbuf_isundef(data))
1413                 tokenbuf_move(&tmptokbuf, data);
1414             else if (tokenbuf_isempty(data)) {
1415                 tokenbuf_free(data);
1416                 tokenbuf_move(&tmptokbuf, data);
1417             }
1418             break;
1419         }
1420         case '*': {
1421             /* substitute empty string if data is not empty, parameter otherwise. */
1422             p++;
1423             rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
1424             if (rc < 0)
1425                 goto error_return;
1426             if (rc == 0) {
1427                 rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
1428                 goto error_return;
1429             }
1430             p += rc;
1431             if (data->begin != NULL) {
1432                 if (data->begin == data->end) {
1433                     tokenbuf_free(data);
1434                     tokenbuf_move(&tmptokbuf, data);
1435                 } else {
1436                     tokenbuf_free(data);
1437                     data->begin = data->end = "";
1438                     data->buffer_size = 0;
1439                 }
1440             }
1441             break;
1442         }
1443         case '+': {
1444             /* substitute parameter if data is not empty. */
1445             p++;
1446             rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
1447             if (rc < 0)
1448                 goto error_return;
1449             if (rc == 0) {
1450                 rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
1451                 goto error_return;
1452             }
1453             p += rc;
1454             if (data->begin != NULL && data->begin != data->end) {
1455                 tokenbuf_free(data);
1456                 tokenbuf_move(&tmptokbuf, data);
1457             }
1458             break;
1459         }
1460         case 's': {
1461             /* search and replace. */
1462             p++;
1463             if (*p != '/')
1464                 return VAR_ERR_MALFORMATTED_REPLACE;
1465             p++;
1466             rc = parse_pattern(var, ctx, p, end);
1467             if (rc < 0)
1468                 goto error_return;
1469             tokenbuf_set(&search, p, p + rc, 0);
1470             p += rc;
1471             if (*p != '/') {
1472                 rc = VAR_ERR_MALFORMATTED_REPLACE;
1473                 goto error_return;
1474             }
1475             p++;
1476             rc = parse_substext_or_variable(var, ctx, p, end, &replace);
1477             if (rc < 0)
1478                 goto error_return;
1479             p += rc;
1480             if (*p != '/') {
1481                 rc = VAR_ERR_MALFORMATTED_REPLACE;
1482                 goto error_return;
1483             }
1484             p++;
1485             rc = parse_exptext(var, ctx, p, end);
1486             if (rc < 0)
1487                 goto error_return;
1488             tokenbuf_set(&flags, p, p + rc, 0);
1489             p += rc;
1490             if (data->begin != NULL) {
1491                 rc = op_search_and_replace(var, ctx, data, &search, &replace, &flags);
1492                 if (rc < 0)
1493                     goto error_return;
1494             }
1495             break;
1496         }
1497         case 'y': {
1498             /* transpose characters from class A to class B. */
1499             p++;
1500             if (*p != '/')
1501                 return VAR_ERR_MALFORMATTED_TRANSPOSE;
1502             p++;
1503             rc = parse_substext_or_variable(var, ctx, p, end, &search);
1504             if (rc < 0)
1505                 goto error_return;
1506             p += rc;
1507             if (*p != '/') {
1508                 rc = VAR_ERR_MALFORMATTED_TRANSPOSE;
1509                 goto error_return;
1510             }
1511             p++;
1512             rc = parse_substext_or_variable(var, ctx, p, end, &replace);
1513             if (rc < 0)
1514                 goto error_return;
1515             p += rc;
1516             if (*p != '/') {
1517                 rc = VAR_ERR_MALFORMATTED_TRANSPOSE;
1518                 goto error_return;
1519             } else
1520                 p++;
1521             if (data->begin) {
1522                 rc = op_transpose(var, ctx, data, &search, &replace);
1523                 if (rc < 0)
1524                     goto error_return;
1525             }
1526             break;
1527         }
1528         case 'p': {
1529             /* padding. */
1530             p++;
1531             if (*p != '/')
1532                 return VAR_ERR_MALFORMATTED_PADDING;
1533             p++;
1534             rc = parse_integer(var, ctx, p, end, &num1);
1535             if (rc == 0) {
1536                 rc = VAR_ERR_MISSING_PADDING_WIDTH;
1537                 goto error_return;
1538             }
1539             p += rc;
1540             if (*p != '/') {
1541                 rc = VAR_ERR_MALFORMATTED_PADDING;
1542                 goto error_return;
1543             }
1544             p++;
1545             rc = parse_substext_or_variable(var, ctx, p, end, &replace);
1546             if (rc < 0)
1547                 goto error_return;
1548             p += rc;
1549             if (*p != '/') {
1550                 rc = VAR_ERR_MALFORMATTED_PADDING;
1551                 goto error_return;
1552             }
1553             p++;
1554             if (*p != 'l' && *p != 'c' && *p != 'r') {
1555                 rc = VAR_ERR_MALFORMATTED_PADDING;
1556                 goto error_return;
1557             }
1558             p++;
1559             if (data->begin) {
1560                 rc = op_padding(var, ctx, data, num1, &replace, p[-1]);
1561                 if (rc < 0)
1562                     goto error_return;
1563             }
1564             break;
1565         }
1566         case '%': {
1567             /* operation callback function */
1568             const char *op_ptr;
1569             int op_len;
1570             const char *arg_ptr;
1571             int arg_len;
1572             const char *val_ptr;
1573             int val_len;
1574             const char *out_ptr;
1575             int out_len; 
1576             int out_size;
1577             tokenbuf_t args;
1578
1579             p++;
1580             rc = parse_name(var, ctx, p, end);
1581             if (rc < 0)
1582                 goto error_return;
1583             op_ptr = p;
1584             op_len = rc;
1585             p += rc;
1586             if (*p == '(') {
1587                 p++;
1588                 tokenbuf_init(&args);
1589                 rc = parse_opargtext_or_variable(var, ctx, p, end, &args);
1590                 if (rc < 0)
1591                     goto error_return;
1592                 p += rc;
1593                 arg_ptr = args.begin;
1594                 arg_len = args.end - args.begin;
1595                 if (*p != ')') {
1596                     rc = VAR_ERR_MALFORMED_OPERATION_ARGUMENTS;
1597                     goto error_return;
1598                 }
1599                 p++;
1600             }
1601             else {
1602                 arg_ptr = NULL;
1603                 arg_len = 0;
1604             }
1605             val_ptr = data->begin;
1606             val_len = data->end - data->begin;
1607
1608             if (data->begin != NULL && var->cb_operation_fct != NULL) {
1609                 /* call operation callback function */
1610                 rc = (*var->cb_operation_fct)(var, var->cb_operation_ctx,
1611                                               op_ptr, op_len,
1612                                               arg_ptr, arg_len,
1613                                               val_ptr, val_len,
1614                                               &out_ptr, &out_len, &out_size);
1615                 if (rc < 0)
1616                     goto error_return;
1617                 tokenbuf_free(data);
1618                 tokenbuf_set(data, out_ptr, out_ptr+out_len, out_size);
1619             }
1620             break;
1621         }
1622         default:
1623             return VAR_ERR_UNKNOWN_COMMAND_CHAR;
1624     }
1625
1626     /* return successfully */
1627     tokenbuf_free(&tmptokbuf);
1628     tokenbuf_free(&search);
1629     tokenbuf_free(&replace);
1630     tokenbuf_free(&flags);
1631     tokenbuf_free(&number1);
1632     tokenbuf_free(&number2);
1633     return (p - begin);
1634
1635     /* return with an error */
1636     error_return:
1637     tokenbuf_free(data);
1638     tokenbuf_free(&tmptokbuf);
1639     tokenbuf_free(&search);
1640     tokenbuf_free(&replace);
1641     tokenbuf_free(&flags);
1642     tokenbuf_free(&number1);
1643     tokenbuf_free(&number2);
1644     return rc;
1645 }
1646
1647 /* parse numerical expression operand */
1648 static int 
1649 parse_numexp_operand(
1650     var_t *var, var_parse_t *ctx,
1651     const char *begin, const char *end, 
1652     int *result, int *failed)
1653 {
1654     const char *p;
1655     tokenbuf_t tmp;
1656     int rc;
1657     var_parse_t myctx;
1658
1659     /* initialization */
1660     p = begin;
1661     tokenbuf_init(&tmp);
1662     if (p == end)
1663         return VAR_ERR_INCOMPLETE_INDEX_SPEC;
1664
1665     /* parse opening numerical expression */
1666     if (*p == '(') {
1667         /* parse inner numerical expression */
1668         rc = parse_numexp(var, ctx, ++p, end, result, failed);
1669         if (rc < 0)
1670             return rc;
1671         p += rc;
1672         if (p == end)
1673             return VAR_ERR_INCOMPLETE_INDEX_SPEC;
1674         /* parse closing parenthesis */
1675         if (*p != ')')
1676             return VAR_ERR_UNCLOSED_BRACKET_IN_INDEX;
1677         p++;
1678     }
1679     /* parse contained variable */
1680     else if (*p == var->syntax.delim_init) {
1681         /* parse variable with forced expansion */
1682         ctx = var_parse_push(ctx, &myctx);
1683         ctx->force_expand = 1;
1684         rc = parse_variable(var, ctx, p, end, &tmp);
1685         ctx = var_parse_pop(ctx);
1686
1687         if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
1688             *failed = 1;
1689             /* parse variable without forced expansion */
1690             ctx = var_parse_push(ctx, &myctx);
1691             ctx->force_expand = 0;
1692             rc = parse_variable(var, ctx, p, end, &tmp);
1693             ctx = var_parse_pop(ctx);
1694             if (rc < 0)
1695                 return rc;
1696             p += rc;
1697             *result = 0;
1698         }
1699         else if (rc < 0)
1700             return rc;
1701         else {
1702             p += rc;
1703             /* parse remaining numerical expression */
1704             rc = parse_numexp(var, ctx, tmp.begin, tmp.end, result, failed);
1705             tokenbuf_free(&tmp);
1706             if (rc < 0)
1707                 return rc;
1708         }
1709     }
1710     /* parse relative index mark ("#") */
1711     else if (   var->syntax.index_mark != EOS 
1712              && *p == var->syntax.index_mark) {
1713         p++;
1714         *result = ctx->index_this;
1715         if (ctx->rel_lookup_flag)
1716             ctx->rel_lookup_cnt++;
1717     }
1718     /* parse plain integer number */
1719     else if (isdigit(*p)) {
1720         rc = parse_integer(var, ctx, p, end, result);
1721         p += rc;
1722     }
1723     /* parse signed positive integer number */
1724     else if (*p == '+') {
1725         if ((end - p) > 1 && isdigit(p[1])) {
1726             p++;
1727             rc = parse_integer(var, ctx, p, end, result);
1728             p += rc;
1729         }
1730         else
1731             return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1732     }
1733     /* parse signed negative integer number */
1734     else if (*p == '-') {
1735         if (end - p > 1 && isdigit(p[1])) {
1736             p++;
1737             rc = parse_integer(var, ctx, p, end, result);
1738             *result = -(*result);
1739             p += rc;
1740         }
1741         else
1742             return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1743     }
1744     /* else we failed to parse anything reasonable */
1745     else
1746         return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1747
1748     return (p - begin);
1749 }
1750
1751 /* parse numerical expression ("x+y") */
1752 static int 
1753 parse_numexp(
1754     var_t *var, var_parse_t *ctx,
1755     const char *begin, const char *end, 
1756     int *result, int *failed)
1757 {
1758     const char *p;
1759     char op;
1760     int right;
1761     int rc;
1762
1763     /* initialization */
1764     p = begin;
1765     if (p == end)
1766         return VAR_ERR_INCOMPLETE_INDEX_SPEC;
1767
1768     /* parse left numerical operand */
1769     rc = parse_numexp_operand(var, ctx, p, end, result, failed);
1770     if (rc < 0)
1771         return rc;
1772     p += rc;
1773
1774     /* parse numerical operator */
1775     while (p != end) {
1776         if (*p == '+' || *p == '-') {
1777             op = *p++;
1778             /* recursively parse right operand (light binding) */
1779             rc = parse_numexp(var, ctx, p, end, &right, failed);
1780             if (rc < 0)
1781                 return rc;
1782             p += rc;
1783             if (op == '+')
1784                 *result = (*result + right);
1785             else
1786                 *result = (*result - right);
1787         }
1788         else if (*p == '*' || *p == '/' || *p == '%') {
1789             op = *p++;
1790             /* recursively parse right operand (string binding) */
1791             rc = parse_numexp_operand(var, ctx, p, end, &right, failed);
1792             if (rc < 0)
1793                 return rc;
1794             p += rc;
1795             if (op == '*')
1796                 *result = (*result * right);
1797             else if (op == '/') {
1798                 if (right == 0) {
1799                     if (*failed)
1800                         *result = 0;
1801                     else
1802                         return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
1803                 }
1804                 else
1805                     *result = (*result / right);
1806             }
1807             else if (op == '%') {
1808                 if (right == 0) {
1809                     if (*failed)
1810                         *result = 0;
1811                     else
1812                         return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
1813                 }
1814                 else
1815                     *result = (*result % right);
1816             }
1817         }
1818         else
1819             break;
1820     }
1821
1822     /* return amount of parsed input */
1823     return (p - begin);
1824 }
1825
1826 /* parse variable name ("abc") */
1827 static int 
1828 parse_name(
1829     var_t *var, var_parse_t *ctx,
1830     const char *begin, const char *end)
1831 {
1832     const char *p;
1833
1834     /* parse as long as name class characters are found */
1835     for (p = begin; p != end && var->syntax_nameclass[(int)(*p)]; p++)
1836         ;
1837     return (p - begin);
1838 }
1839
1840 /* lookup a variable value through the callback function */
1841 static int 
1842 lookup_value(
1843     var_t *var, var_parse_t *ctx,
1844     const char  *var_ptr, int  var_len, int var_inc, int var_idx,
1845     const char **val_ptr, int *val_len, int *val_size)
1846 {
1847     char buf[1];
1848     int rc;
1849
1850     /* pass through to original callback */
1851     rc = (*var->cb_value_fct)(var, var->cb_value_ctx,
1852                               var_ptr, var_len, var_inc, var_idx, 
1853                               val_ptr, val_len, val_size);
1854
1855     /* convert undefined variable into empty variable if relative
1856        lookups are counted. This is the case inside an active loop
1857        construct if no limits are given. There the parse_input()
1858        has to proceed until all variables have undefined values. 
1859        This trick here allows it to determine this case. */
1860     if (ctx->rel_lookup_flag && rc == VAR_ERR_UNDEFINED_VARIABLE) {
1861         ctx->rel_lookup_cnt--;
1862         buf[0] = EOS;
1863         *val_ptr  = buf;
1864         *val_len  = 0;
1865         *val_size = 0;
1866         return VAR_OK;
1867     }
1868
1869     return rc;
1870 }
1871
1872 /* parse complex variable construct ("${name...}") */
1873 static int 
1874 parse_variable_complex(
1875     var_t *var, var_parse_t *ctx,
1876     const char *begin, const char *end,
1877     tokenbuf_t *result)
1878 {
1879     const char *p;
1880     const char *data;
1881     int len, buffer_size;
1882     int failed = 0;
1883     int rc;
1884     int idx = 0;
1885     int inc;   
1886     tokenbuf_t name;
1887     tokenbuf_t tmp;
1888
1889     /* initializations */
1890     p = begin;
1891     tokenbuf_init(&name);
1892     tokenbuf_init(&tmp);
1893     tokenbuf_init(result);
1894
1895     /* parse open delimiter */
1896     if (p == end || *p != var->syntax.delim_open)
1897         return 0;
1898     p++;
1899     if (p == end)
1900         return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
1901
1902     /* parse name of variable to expand. The name may consist of an
1903        arbitrary number of variable name character and contained variable
1904        constructs. */
1905     do {
1906         /* parse a variable name */
1907         rc = parse_name(var, ctx, p, end);
1908         if (rc < 0)
1909             goto error_return;
1910         if (rc > 0) {
1911             if (!tokenbuf_append(&name, p, rc)) {
1912                 rc = VAR_ERR_OUT_OF_MEMORY;
1913                 goto error_return;
1914             }
1915             p += rc;
1916         }
1917
1918         /* parse an (embedded) variable */
1919         rc = parse_variable(var, ctx, p, end, &tmp);
1920         if (rc < 0)
1921             goto error_return;
1922         if (rc > 0) {
1923             if (!tokenbuf_merge(&name, &tmp)) {
1924                 rc = VAR_ERR_OUT_OF_MEMORY;
1925                 goto error_return;
1926             }
1927             p += rc;
1928         }
1929     } while (rc > 0);
1930
1931     /* we must have the complete expanded variable name now, 
1932        so make sure we really do. */
1933     if (name.begin == name.end) {
1934         if (ctx->force_expand) {
1935             rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
1936             goto error_return;
1937         }
1938         else {
1939             /* If no force_expand is requested, we have to back-off.
1940                We're not sure whether our approach here is 100% correct,
1941                because it _could_ have side-effects according to Peter
1942                Simons, but as far as we know and tried it, it is
1943                correct. But be warned -- RSE */
1944             tokenbuf_set(result, begin - 1, p, 0);
1945             goto goahead;
1946         }
1947     }
1948
1949     /* parse an optional index specification */
1950     if (   var->syntax.index_open != EOS
1951         && *p == var->syntax.index_open) {
1952         p++;
1953         rc = parse_numexp(var, ctx, p, end, &idx, &failed);
1954         if (rc < 0)
1955             goto error_return;
1956         if (rc == 0) {
1957             rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
1958             goto error_return;
1959         }
1960         p += rc;
1961         if (p == end) {
1962             rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
1963             goto error_return;
1964         }
1965         if (*p != var->syntax.index_close) {
1966             rc = VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1967             goto error_return;
1968         }
1969         p++;
1970     }
1971
1972     /* parse end of variable construct or start of post-operations */
1973     if (p == end || (*p != var->syntax.delim_close && *p != ':' && *p != '+')) {
1974         rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
1975         goto error_return;
1976     }
1977     inc = (*p == '+');                /* increment variable */
1978     p++;
1979
1980     /* lookup the variable value now */
1981     if (failed) {
1982         tokenbuf_set(result, begin - 1, p, 0);
1983     } else {
1984         rc = lookup_value(var, ctx,
1985                           name.begin, name.end-name.begin, inc, idx,
1986                           &data, &len, &buffer_size);
1987         if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
1988             tokenbuf_init(result); /* delayed handling of undefined variable */
1989         } else if (rc < 0) {
1990             goto error_return;
1991         } else {
1992             /* the preliminary result is the raw value of the variable.
1993                This may be modified by the operations that may follow. */
1994             tokenbuf_set(result, data, data + len, buffer_size);
1995         }
1996     }
1997
1998     /* parse optional post-operations */
1999     goahead:
2000     if (p[-1] == ':') {
2001         tokenbuf_free(&tmp);
2002         tokenbuf_init(&tmp);
2003         p--;
2004         while (p != end && *p == ':') {
2005             p++;
2006             if (!failed)
2007                 rc = parse_operation(var, ctx, p, end, result);
2008             else
2009                 rc = parse_operation(var, ctx, p, end, &tmp);
2010             if (rc < 0)
2011                 goto error_return;
2012             p += rc;
2013             if (failed)
2014                 result->end += rc;
2015         }
2016         if (p == end || *p != var->syntax.delim_close) {
2017             rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
2018             goto error_return;
2019         }
2020         p++;
2021         if (failed)
2022             result->end++;
2023     } else if (p[-1] == '+') {
2024        p++;
2025     }
2026
2027     /* lazy handling of undefined variable */
2028     if (!failed && tokenbuf_isundef(result)) {
2029         if (ctx->force_expand) {
2030             rc = VAR_ERR_UNDEFINED_VARIABLE;
2031             goto error_return;
2032         }
2033         else
2034             tokenbuf_set(result, begin - 1, p, 0);
2035     }
2036
2037     /* return successfully */
2038     tokenbuf_free(&name);
2039     tokenbuf_free(&tmp);
2040     return (p - begin);
2041
2042     /* return with an error */
2043     error_return:
2044     tokenbuf_free(&name);
2045     tokenbuf_free(&tmp);
2046     tokenbuf_free(result);
2047     return rc;
2048 }
2049
2050 /* parse variable construct ("$name" or "${name...}") */
2051 static int 
2052 parse_variable(
2053     var_t *var, var_parse_t *ctx,
2054     const char *begin, const char *end,
2055     tokenbuf_t *result)
2056 {
2057     const char *p;
2058     const char *data;
2059     int len, buffer_size;
2060     int rc, rc2;
2061     int inc;
2062
2063     /* initialization */
2064     p = begin;
2065     tokenbuf_init(result);
2066
2067     /* parse init delimiter */
2068     if (p == end || *p != var->syntax.delim_init)
2069         return 0;
2070     p++;
2071     if (p == end)
2072         return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
2073
2074     /* parse a simple variable name. 
2075        (if this fails, we're try to parse a complex variable construct) */
2076     rc = parse_name(var, ctx, p, end);
2077     if (rc < 0)
2078         return rc;
2079     if (rc > 0) {
2080         inc = (p[rc] == '+');
2081         rc2 = lookup_value(var, ctx, p, rc, inc, 0, &data, &len, &buffer_size);
2082         if (rc2 == VAR_ERR_UNDEFINED_VARIABLE && !ctx->force_expand) {
2083             tokenbuf_set(result, begin, begin + 1 + rc, 0);
2084             return (1 + rc);
2085         }
2086         if (rc2 < 0)
2087             return rc2;
2088         tokenbuf_set(result, data, data + len, buffer_size);
2089         return (1 + rc);
2090     }
2091
2092     /* parse a complex variable construct (else case) */
2093     rc = parse_variable_complex(var, ctx, p, end, result);
2094     if (rc > 0)
2095         rc++;
2096     return rc;
2097 }
2098
2099 /* parse loop construct limits ("[...]{b,s,e}") */
2100 static var_rc_t 
2101 parse_looplimits(
2102     var_t *var, var_parse_t *ctx,
2103     const char *begin, const char *end,
2104     int *start, int *step, int *stop, int *open_stop)
2105 {
2106     const char *p;
2107     int rc;
2108     int failed;
2109
2110     /* initialization */
2111     p = begin;
2112
2113     /* we are happy if nothing is to left to parse */
2114     if (p == end)
2115         return VAR_OK;
2116
2117     /* parse start delimiter */
2118     if (*p != var->syntax.delim_open)
2119         return VAR_OK;
2120     p++;
2121
2122     /* parse loop start value */
2123     failed = 0;
2124     rc = parse_numexp(var, ctx, p, end, start, &failed);
2125     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
2126         *start = 0; /* use default */
2127     else if (rc < 0)
2128         return (var_rc_t)rc;
2129     else
2130         p += rc;
2131     if (failed)
2132         return VAR_ERR_UNDEFINED_VARIABLE;
2133
2134     /* parse separator */
2135     if (*p != ',')
2136         return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2137     p++;
2138
2139     /* parse loop step value */
2140     failed = 0;
2141     rc = parse_numexp(var, ctx, p, end, step, &failed);
2142     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
2143         *step = 1; /* use default */
2144     else if (rc < 0)
2145         return (var_rc_t)rc;
2146     else
2147         p += rc;
2148     if (failed)
2149         return VAR_ERR_UNDEFINED_VARIABLE;
2150
2151     /* parse separator */
2152     if (*p != ',') {
2153         /* if not found, parse end delimiter */
2154         if (*p != var->syntax.delim_close)
2155             return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2156         p++;
2157
2158         /* shift step value to stop value */
2159         *stop = *step;
2160         *step = 1;
2161
2162         /* determine whether loop end is open */
2163         if (rc > 0)
2164             *open_stop = 0;
2165         else
2166             *open_stop = 1;
2167         return (var_rc_t)(p - begin);
2168     }
2169     p++;
2170
2171     /* parse loop stop value */
2172     failed = 0;
2173     rc = parse_numexp(var, ctx, p, end, stop, &failed);
2174     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC) {
2175         *stop = 0; /* use default */
2176         *open_stop = 1;
2177     }
2178     else if (rc < 0)
2179         return (var_rc_t)rc;
2180     else {
2181         *open_stop = 0;
2182         p += rc;
2183     }
2184     if (failed)
2185         return VAR_ERR_UNDEFINED_VARIABLE;
2186
2187     /* parse end delimiter */
2188     if (*p != var->syntax.delim_close)
2189         return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2190     p++;
2191
2192     /* return amount of parsed input */
2193     return (var_rc_t)(p - begin);
2194 }
2195
2196 /* parse plain text */
2197 static int 
2198 parse_text(
2199     var_t *var, var_parse_t *ctx,
2200     const char *begin, const char *end)
2201 {
2202     const char *p;
2203
2204     /* parse until delim_init (variable construct) 
2205        or index_open (loop construct) is found */
2206     for (p = begin; p != end; p++) {
2207         if (*p == var->syntax.escape) {
2208             p++; /* skip next character */
2209             if (p == end)
2210                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
2211         }
2212         else if (*p == var->syntax.delim_init)
2213             break;
2214         else if (   var->syntax.index_open != EOS
2215                  && (   *p == var->syntax.index_open 
2216                      || *p == var->syntax.index_close))
2217             break;
2218     }
2219     return (p - begin);
2220 }
2221
2222 /* expand input in general */
2223 static var_rc_t 
2224 parse_input(
2225     var_t *var, var_parse_t *ctx,
2226     const char *begin, const char *end,
2227     tokenbuf_t *output, int recursion_level)
2228 {
2229     const char *p;
2230     int rc, rc2;
2231     tokenbuf_t result;
2232     int start, step, stop, open_stop;
2233     int i;
2234     int output_backup;
2235     int rel_lookup_cnt;
2236     int loop_limit_length;
2237     var_parse_t myctx;
2238
2239     /* initialization */
2240     p = begin;
2241
2242     do {
2243         /* try to parse a loop construct */
2244         if (   p != end 
2245             && var->syntax.index_open != EOS 
2246             && *p == var->syntax.index_open) {
2247             p++;
2248
2249             /* loop preparation */
2250             loop_limit_length = -1;
2251             rel_lookup_cnt = ctx->rel_lookup_cnt;
2252             open_stop = 1;
2253             rc = 0;
2254             start = 0;
2255             step  = 1;
2256             stop  = 0;
2257             output_backup = 0;
2258
2259             /* iterate over loop construct, either as long as there is
2260                (still) nothing known about the limit, or there is an open
2261                (=unknown) limit stop and there are still defined variables
2262                or there is a stop limit known and it is still not reached */
2263             re_loop:
2264             for (i = start;
2265                  (   (   open_stop 
2266                       && (   loop_limit_length < 0 
2267                           || rel_lookup_cnt > ctx->rel_lookup_cnt)) 
2268                   || (   !open_stop 
2269                       && i <= stop)                                );
2270                  i += step) {
2271
2272                 /* remember current output end for restoring */
2273                 output_backup = (output->end - output->begin);
2274
2275                 /* open temporary context for recursion */ 
2276                 ctx = var_parse_push(ctx, &myctx);
2277                 ctx->force_expand    = 1;
2278                 ctx->rel_lookup_flag = 1;
2279                 ctx->index_this      = i;
2280
2281                 /* recursive parse input through ourself */
2282                 rc = parse_input(var, ctx, p, end, 
2283                                  output, recursion_level+1);
2284
2285                 /* retrieve info and close temporary context */
2286                 rel_lookup_cnt = ctx->rel_lookup_cnt;
2287                 ctx = var_parse_pop(ctx);
2288
2289                 /* error handling */
2290                 if (rc < 0)
2291                     goto error_return;
2292
2293                 /* make sure the loop construct is closed */
2294                 if (p[rc] != var->syntax.index_close) {
2295                     rc = VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT;
2296                     goto error_return;
2297                 }
2298
2299                 /* try to parse loop construct limit specification */
2300                 if (loop_limit_length < 0) {
2301                     rc2 = parse_looplimits(var, ctx, p+rc+1, end, 
2302                                            &start, &step, &stop, &open_stop);
2303                     if (rc2 < 0)
2304                         goto error_return;
2305                     else if (rc2 == 0)
2306                         loop_limit_length = 0;
2307                     else if (rc2 > 0) {
2308                         loop_limit_length = rc2;
2309                         /* restart loop from scratch */
2310                         output->end = (output->begin + output_backup);
2311                         goto re_loop;
2312                     }
2313                 }
2314             }
2315
2316             /* if stop value is open, restore to the output end
2317                because the last iteration was just to determine the loop
2318                termination and its result has to be discarded */
2319             if (open_stop)
2320                 output->end = (output->begin + output_backup);
2321
2322             /* skip parsed loop construct */
2323             p += rc;
2324             p++;
2325             p += loop_limit_length;
2326
2327             continue;
2328         }
2329
2330         /* try to parse plain text */
2331         rc = parse_text(var, ctx, p, end);
2332         if (rc > 0) {
2333             if (!tokenbuf_append(output, p, rc)) {
2334                 rc = VAR_ERR_OUT_OF_MEMORY;
2335                 goto error_return;
2336             }
2337             p += rc;
2338             continue;
2339         } else if (rc < 0)
2340             goto error_return;
2341
2342         /* try to parse a variable construct */
2343         tokenbuf_init(&result);
2344         rc = parse_variable(var, ctx, p, end, &result);
2345         if (rc > 0) {
2346             if (!tokenbuf_merge(output, &result)) {
2347                 tokenbuf_free(&result);
2348                 rc = VAR_ERR_OUT_OF_MEMORY;
2349                 goto error_return;
2350             }
2351             tokenbuf_free(&result);
2352             p += rc;
2353             continue;
2354         }    
2355         tokenbuf_free(&result);
2356         if (rc < 0)
2357             goto error_return;
2358
2359     } while (p != end && rc > 0);
2360
2361     /* We do not know whether this really could happen, but because we
2362        are paranoid, report an error at the outer most parsing level if
2363        there is still any input. Because this would mean that we are no
2364        longer able to parse the remaining input as a loop construct, a
2365        text or a variable construct. This would be very strange, but
2366        could perhaps happen in case of configuration errors!?... */
2367     if (recursion_level == 0 && p != end) {
2368         rc = VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE;
2369         goto error_return;
2370     }
2371
2372     /* return amount of parsed text */
2373     return (var_rc_t)(p - begin);
2374
2375     /* return with an error where as a special case the output begin is
2376        set to the input begin and the output end to the last input parsing
2377        position. */
2378     error_return:
2379     tokenbuf_free(output);
2380     tokenbuf_set(output, begin, p, 0);
2381     return (var_rc_t)rc;
2382 }
2383
2384 /* 
2385 **
2386 **  ==== APPLICATION PROGRAMMING INTERFACE (API) ====
2387 **
2388 */
2389
2390 /* create variable expansion context */
2391 var_rc_t 
2392 var_create(
2393     var_t **pvar)
2394 {
2395     var_t *var;
2396
2397     if (pvar == NULL)
2398         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2399     if ((var = (var_t *)malloc(sizeof(var_t))) == NULL)
2400         return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2401     memset(var, 0, sizeof(var));
2402     var_config(var, VAR_CONFIG_SYNTAX, &var_syntax_default);
2403     *pvar = var;
2404     return VAR_OK;
2405 }
2406
2407 /* destroy variable expansion context */
2408 var_rc_t 
2409 var_destroy(
2410     var_t *var)
2411 {
2412     if (var == NULL)
2413         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2414     free(var);
2415     return VAR_OK;
2416 }
2417
2418 /* configure variable expansion context */
2419 var_rc_t 
2420 var_config(
2421     var_t *var, 
2422     var_config_t mode, 
2423     ...)
2424 {
2425     va_list ap;
2426     var_rc_t rc;
2427
2428     if (var == NULL)
2429         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2430     va_start(ap, mode);
2431     switch (mode) {
2432         case VAR_CONFIG_SYNTAX: {
2433             var_syntax_t *s;
2434             s = (var_syntax_t *)va_arg(ap, void *);
2435             if (s == NULL)
2436                 return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2437             var->syntax.escape      = s->escape;
2438             var->syntax.delim_init  = s->delim_init;
2439             var->syntax.delim_open  = s->delim_open;
2440             var->syntax.delim_close = s->delim_close;
2441             var->syntax.index_open  = s->index_open;
2442             var->syntax.index_close = s->index_close;
2443             var->syntax.index_mark  = s->index_mark;
2444             var->syntax.name_chars  = NULL; /* unused internally */
2445             if ((rc = expand_character_class(s->name_chars, var->syntax_nameclass)) != VAR_OK)
2446                 return VAR_RC(rc);
2447             if (   var->syntax_nameclass[(int)var->syntax.delim_init] 
2448                 || var->syntax_nameclass[(int)var->syntax.delim_open]
2449                 || var->syntax_nameclass[(int)var->syntax.delim_close]
2450                 || var->syntax_nameclass[(int)var->syntax.escape])
2451                 return VAR_RC(VAR_ERR_INVALID_CONFIGURATION);
2452             break;
2453         }
2454         case VAR_CONFIG_CB_VALUE: {
2455             var_cb_value_t fct;
2456             void *ctx;
2457             fct = (var_cb_value_t)va_arg(ap, void *);
2458             ctx = (void *)va_arg(ap, void *);
2459             var->cb_value_fct = fct;
2460             var->cb_value_ctx = ctx;
2461             break;
2462         }
2463         case VAR_CONFIG_CB_OPERATION: {
2464             var_cb_operation_t fct;
2465             void *ctx;
2466             fct = (var_cb_operation_t)va_arg(ap, void *);
2467             ctx = (void *)va_arg(ap, void *);
2468             var->cb_operation_fct = fct;
2469             var->cb_operation_ctx = ctx;
2470             break;
2471         }
2472         default:
2473             return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2474     }
2475     va_end(ap);
2476     return VAR_OK;
2477 }
2478
2479 /* perform unescape operation on a buffer */
2480 var_rc_t 
2481 var_unescape(
2482     var_t *var, 
2483     const char *src, int srclen, 
2484     char *dst, int dstlen, 
2485     int all)
2486 {
2487     const char *end;
2488     var_rc_t rc;
2489
2490     if (var == NULL || src == NULL || dst == NULL)
2491         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2492     end = src + srclen;
2493     while (src < end) {
2494         if (*src == '\\') {
2495             if (++src == end)
2496                 return VAR_RC(VAR_ERR_INCOMPLETE_NAMED_CHARACTER);
2497             switch (*src) {
2498                 case '\\':
2499                     if (!all) {
2500                         *dst++ = '\\';
2501                     }
2502                     *dst++ = '\\';
2503                     break;
2504                 case 'n':
2505                     *dst++ = '\n';
2506                     break;
2507                 case 't':
2508                     *dst++ = '\t';
2509                     break;
2510                 case 'r':
2511                     *dst++ = '\r';
2512                     break;
2513                 case 'x':
2514                     ++src;
2515                     if ((rc = expand_hex(&src, &dst, end)) != VAR_OK)
2516                         return VAR_RC(rc);
2517                     break;
2518                 case '0': case '1': case '2': case '3': case '4':
2519                 case '5': case '6': case '7': case '8': case '9':
2520                     if (   end - src >= 3 
2521                         && isdigit((int)src[1]) 
2522                         && isdigit((int)src[2])) {
2523                         if ((rc = expand_octal(&src, &dst, end)) != 0)
2524                             return VAR_RC(rc);
2525                         break;
2526                     }
2527                 default:
2528                     if (!all) {
2529                         *dst++ = '\\';
2530                     }
2531                     *dst++ = *src;
2532             }
2533             ++src;
2534         } else
2535             *dst++ = *src++;
2536     }
2537     *dst = EOS;
2538     return VAR_OK;
2539 }
2540
2541 /* perform expand operation on a buffer */
2542 var_rc_t 
2543 var_expand(
2544     var_t *var, 
2545     const char *src_ptr, int src_len, 
2546     char **dst_ptr, int *dst_len, 
2547     int force_expand)
2548 {
2549     var_parse_t ctx;
2550     tokenbuf_t output;
2551     var_rc_t rc;
2552
2553     /* argument sanity checks */
2554     if (var == NULL || src_ptr == NULL || src_len == 0 || dst_ptr == NULL)
2555         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2556
2557     /* prepare internal expansion context */
2558     ctx.lower           = NULL;
2559     ctx.force_expand    = force_expand;
2560     ctx.rel_lookup_flag = 0;
2561     ctx.rel_lookup_cnt  = 0;
2562     ctx.index_this      = 0;
2563
2564     /* start the parsing */
2565     tokenbuf_init(&output);
2566     rc = parse_input(var, &ctx, src_ptr, src_ptr+src_len, &output, 0);
2567
2568     /* post-processing */
2569     if (rc >= 0) {
2570         /* always EOS-terminate output for convinience reasons 
2571            but do not count the EOS-terminator in the length */
2572         if (!tokenbuf_append(&output, "\0", 1)) {
2573             tokenbuf_free(&output);
2574             return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2575         }
2576         output.end--;
2577
2578         /* provide result */
2579         *dst_ptr = (char *)output.begin;
2580         if (dst_len != NULL)
2581             *dst_len = (output.end - output.begin);
2582         rc = VAR_OK;
2583     }
2584     else {
2585         /* provide result */
2586         if (dst_len != NULL)
2587             *dst_len = (output.end - output.begin);
2588     }
2589
2590     return VAR_RC(rc);
2591 }
2592
2593 /* format and expand a string */
2594 var_rc_t 
2595 var_formatv(
2596     var_t *var, 
2597     char **dst_ptr, int force_expand, 
2598     const char *fmt, va_list ap)
2599 {
2600     var_rc_t rc;
2601     va_list apbak;
2602     char *cpBuf;
2603     int nBuf;
2604
2605     /* argument sanity checks */
2606     if (var == NULL || dst_ptr == NULL || fmt == NULL)
2607         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2608
2609     /* determine formatting buffer length */
2610     apbak = ap;
2611     nBuf = var_mvsnprintf(NULL, 0, fmt, ap);
2612     ap = apbak;
2613     if (nBuf == -1)
2614         return VAR_RC(VAR_ERR_FORMATTING_FAILURE);
2615
2616     /* perform formatting */
2617     if ((cpBuf = (char *)malloc(nBuf+1)) == NULL)
2618         return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2619     nBuf = var_mvsnprintf(cpBuf, nBuf+1, fmt, ap);
2620     if (nBuf == -1) {
2621         free(cpBuf);
2622         return VAR_RC(VAR_ERR_FORMATTING_FAILURE);
2623     }
2624
2625     /* perform expansion */
2626     if ((rc = var_expand(var, cpBuf, nBuf, dst_ptr, NULL, force_expand)) != VAR_OK) {
2627         free(cpBuf);
2628         return VAR_RC(rc);
2629     }
2630
2631     /* cleanup */
2632     free(cpBuf);
2633
2634     return VAR_OK;
2635 }
2636
2637 /* format and expand a string */
2638 var_rc_t 
2639 var_format(
2640     var_t *var, 
2641     char **dst_ptr, int force_expand, 
2642     const char *fmt, ...)
2643 {
2644     var_rc_t rc;
2645     va_list ap;
2646
2647     /* argument sanity checks */
2648     if (var == NULL || dst_ptr == NULL || fmt == NULL)
2649         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2650
2651     va_start(ap, fmt);
2652     rc = var_formatv(var, dst_ptr, force_expand, fmt, ap);
2653     va_end(ap);
2654
2655     return VAR_RC(rc);
2656 }
2657
2658 /* var_rc_t to string mapping table */
2659 static const char *var_errors[] = {
2660     "everything ok",                                           /* VAR_OK = 0 */
2661     "incomplete named character",                              /* VAR_ERR_INCOMPLETE_NAMED_CHARACTER */
2662     "incomplete hexadecimal value",                            /* VAR_ERR_INCOMPLETE_HEX */
2663     "invalid hexadecimal value",                               /* VAR_ERR_INVALID_HEX */
2664     "octal value too large",                                   /* VAR_ERR_OCTAL_TOO_LARGE */
2665     "invalid octal value",                                     /* VAR_ERR_INVALID_OCTAL */
2666     "incomplete octal value",                                  /* VAR_ERR_INCOMPLETE_OCTAL */
2667     "incomplete grouped hexadecimal value",                    /* VAR_ERR_INCOMPLETE_GROUPED_HEX */
2668     "incorrect character class specification",                 /* VAR_ERR_INCORRECT_CLASS_SPEC */
2669     "invalid expansion configuration",                         /* VAR_ERR_INVALID_CONFIGURATION */
2670     "out of memory",                                           /* VAR_ERR_OUT_OF_MEMORY */
2671     "incomplete variable specification",                       /* VAR_ERR_INCOMPLETE_VARIABLE_SPEC */
2672     "undefined variable",                                      /* VAR_ERR_UNDEFINED_VARIABLE */
2673     "input is neither text nor variable",                      /* VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE */
2674     "unknown command character in variable",                   /* VAR_ERR_UNKNOWN_COMMAND_CHAR */
2675     "malformatted search and replace operation",               /* VAR_ERR_MALFORMATTED_REPLACE */
2676     "unknown flag in search and replace operation",            /* VAR_ERR_UNKNOWN_REPLACE_FLAG */
2677     "invalid regex in search and replace operation",           /* VAR_ERR_INVALID_REGEX_IN_REPLACE */
2678     "missing parameter in command",                            /* VAR_ERR_MISSING_PARAMETER_IN_COMMAND */
2679     "empty search string in search and replace operation",     /* VAR_ERR_EMPTY_SEARCH_STRING */
2680     "start offset missing in cut operation",                   /* VAR_ERR_MISSING_START_OFFSET */
2681     "offsets in cut operation delimited by unknown character", /* VAR_ERR_INVALID_OFFSET_DELIMITER */
2682     "range out of bounds in cut operation",                    /* VAR_ERR_RANGE_OUT_OF_BOUNDS */
2683     "offset out of bounds in cut operation",                   /* VAR_ERR_OFFSET_OUT_OF_BOUNDS */
2684     "logic error in cut operation",                            /* VAR_ERR_OFFSET_LOGIC */
2685     "malformatted transpose operation",                        /* VAR_ERR_MALFORMATTED_TRANSPOSE */
2686     "source and target class mismatch in transpose operation", /* VAR_ERR_TRANSPOSE_CLASSES_MISMATCH */
2687     "empty character class in transpose operation",            /* VAR_ERR_EMPTY_TRANSPOSE_CLASS */
2688     "incorrect character class in transpose operation",        /* VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC */
2689     "malformatted padding operation",                          /* VAR_ERR_MALFORMATTED_PADDING */
2690     "width parameter missing in padding operation",            /* VAR_ERR_MISSING_PADDING_WIDTH */
2691     "fill string missing in padding operation",                /* VAR_ERR_EMPTY_PADDING_FILL_STRING */
2692     "unknown quoted pair in search and replace operation",     /* VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE */
2693     "sub-matching reference out of range",                     /* VAR_ERR_SUBMATCH_OUT_OF_RANGE */
2694     "invalid argument",                                        /* VAR_ERR_INVALID_ARGUMENT */
2695     "incomplete quoted pair",                                  /* VAR_ERR_INCOMPLETE_QUOTED_PAIR */
2696     "lookup function does not support variable arrays",        /* VAR_ERR_ARRAY_LOOKUPS_ARE_UNSUPPORTED */
2697     "index of array variable contains an invalid character",   /* VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC */
2698     "index of array variable is incomplete",                   /* VAR_ERR_INCOMPLETE_INDEX_SPEC */
2699     "bracket expression in array variable's index not closed", /* VAR_ERR_UNCLOSED_BRACKET_IN_INDEX */
2700     "division by zero error in index specification",           /* VAR_ERR_DIVISION_BY_ZERO_IN_INDEX */
2701     "unterminated loop construct",                             /* VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT */
2702     "invalid character in loop limits",                        /* VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS */
2703     "malformed operation argument list",                       /* VAR_ERR_MALFORMED_OPERATION_ARGUMENTS */
2704     "undefined operation",                                     /* VAR_ERR_UNDEFINED_OPERATION */
2705     "formatting failure"                                       /* VAR_ERR_FORMATTING_FAILURE */
2706 };
2707
2708 /* translate a return code into its corresponding descriptive text */
2709 char *var_strerror(var_t *var, var_rc_t rc)
2710 {
2711     char *str;
2712     rc = (var_rc_t)(0 - rc);
2713     if (rc < 0 || rc >= (int)sizeof(var_errors) / (int)sizeof(char *)) {
2714         str = "unknown error";
2715     } else {
2716         str = (char *)var_errors[rc];
2717     }
2718     return str;
2719 }