diff options
Diffstat (limited to 'src/libexecline/el_substitute.c')
-rw-r--r-- | src/libexecline/el_substitute.c | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/src/libexecline/el_substitute.c b/src/libexecline/el_substitute.c new file mode 100644 index 0000000..a94c16d --- /dev/null +++ b/src/libexecline/el_substitute.c @@ -0,0 +1,179 @@ +/* ISC license. */ + +#include <skalibs/bytestr.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> +#include <execline/execline.h> + +typedef struct elsubsu_s elsubsu_t, *elsubsu_t_ref ; +struct elsubsu_s +{ + elsubst_t const *subst ; + unsigned int pos ; +} ; + +typedef struct subsuinfo_s subsuinfo_t, *subsuinfo_t_ref ; +struct subsuinfo_s +{ + stralloc dst ; + stralloc sa ; + genalloc list ; /* array of elsubsu_t */ + char const *values ; +} ; + +#define SUBSUINFO_ZERO { .dst = STRALLOC_ZERO, .sa = STRALLOC_ZERO, .list = GENALLOC_ZERO, .values = 0 } + +#define TEST 0x80 +#define MARK 0x40 +#define KEEPESC 0x20 +#define INCRESC 0x10 + +#define STATE 0x07 +#define INWORD 0x00 +#define INDOLL 0x01 +#define INDBR 0x02 +#define INVAR 0x03 +#define INVARBR 0x04 +#define ACCEPT 0x05 + +static int parseword (stralloc *sa, genalloc *list, char const *s, char const *vars, elsubst_t const *substs, unsigned int nsubst) +{ + static char const class[5] = "\0\\${}" ; + static unsigned char const table[6][5] = + { + { ACCEPT, ACCEPT, ACCEPT, ACCEPT | TEST, ACCEPT }, + { INWORD | KEEPESC | INCRESC, INWORD | INCRESC, INWORD | INCRESC, INWORD | TEST | INCRESC, INWORD | INCRESC }, + { INDOLL | KEEPESC, INDOLL, INDOLL, INDOLL | TEST, INDOLL }, + { INWORD, INDBR | KEEPESC, INWORD, INWORD | TEST, INWORD }, + { INWORD, INWORD, INWORD, INWORD | TEST, INWORD | TEST }, + { INWORD, INVAR | MARK | KEEPESC, INVARBR | MARK | KEEPESC, INVAR | KEEPESC, INVARBR | KEEPESC } + } ; + + unsigned int mark = 0, pos = 0, offset = 0, esc = 0, salen = sa->len, listlen = genalloc_len(elsubsu_t, list) ; + unsigned char state = INWORD ; + + while (state != ACCEPT) + { + int nopush = 0 ; + unsigned char c = table[byte_chr(class, 5, s[pos])][state] ; + + if (c & TEST) + { + unsigned int supp = (state == INVARBR) ; + unsigned int i = 0 ; + for (; i < nsubst ; i++) + { + if (!str_diffn(vars + substs[i].var, s + mark, pos - mark) && !vars[substs[i].var + pos - mark]) + { + sa->len -= esc >> 1 ; offset += esc >> 1 ; + if (esc & 1) + { + byte_copy(sa->s + mark - offset - 2 - supp, pos - mark + 1 + supp, sa->s + mark - offset + (esc>>1) - 1 - supp) ; + sa->len-- ; offset++ ; + } + else + { + elsubsu_t cur ; + cur.subst = substs + i ; + cur.pos = mark - offset - 1 - supp ; + if (!genalloc_append(elsubsu_t, list, &cur)) goto err ; + offset += sa->len - cur.pos ; + sa->len = cur.pos ; + if (supp) nopush = 1 ; + } + break ; + } + } + } + if (nopush) offset++ ; + else if (!stralloc_catb(sa, s+pos, 1)) goto err ; + if (c & MARK) mark = pos ; + if (!(c & KEEPESC)) esc = 0 ; + if (c & INCRESC) esc++ ; + state = c & STATE ; pos++ ; + } + sa->len-- ; + return (int)pos ; + +err: + sa->len = salen ; + list->len = listlen ; + return -1 ; +} + +static int substword (subsuinfo_t *info, unsigned int wordstart, unsigned int wordlen, unsigned int n, unsigned int offset) +{ + if (n < genalloc_len(elsubsu_t, &info->list)) + { + elsubsu_t *list = genalloc_s(elsubsu_t, &info->list) ; + char const *p = info->values + list[n].subst->value ; + unsigned int l = list[n].pos + offset ; + unsigned int dstbase = info->dst.len ; + unsigned int sabase = info->sa.len ; + unsigned int i = 0 ; + int nc = 0 ; + if (!stralloc_readyplus(&info->sa, l)) return -1 ; + stralloc_catb(&info->sa, info->sa.s + wordstart, l) ; + for ( ; i < list[n].subst->n ; i++) + { + int r ; + unsigned int plen = str_len(p) ; + info->sa.len = sabase + l ; + if (!stralloc_readyplus(&info->sa, plen + wordlen - l)) goto err ; + stralloc_catb(&info->sa, p, plen) ; + stralloc_catb(&info->sa, info->sa.s + wordstart + l, wordlen - l) ; + r = substword(info, sabase, info->sa.len - sabase, n+1, offset + plen) ; + if (r < 0) goto err ; + nc += r ; + p += plen+1 ; + } + return nc ; + err: + info->sa.len = sabase ; + info->dst.len = dstbase ; + return -1 ; + } + else + { + if (!stralloc_readyplus(&info->dst, wordlen+1)) return -1 ; + stralloc_catb(&info->dst, info->sa.s + wordstart, wordlen) ; + stralloc_0(&info->dst) ; + return 1 ; + } +} + +int el_substitute (stralloc *dst, char const *src, unsigned int len, char const *vars, char const *values, elsubst_t const *substs, unsigned int nsubst) +{ + subsuinfo_t info = SUBSUINFO_ZERO ; + unsigned int nc = 0 ; + unsigned int i = 0 ; + unsigned int dstbase = dst->len ; + int wasnull = !dst->s ; + info.dst = *dst ; + info.values = values ; + + while (i < len) + { + int r ; + genalloc_setlen(elsubsu_t, &info.list, 0) ; + info.sa.len = 0 ; + r = parseword(&info.sa, &info.list, src + i, vars, substs, nsubst) ; + if (r < 0) goto err ; + i += r ; + r = substword(&info, 0, info.sa.len, 0, 0) ; + if (r < 0) goto err ; + nc += r ; + } + genalloc_free(elsubsu_t, &info.list) ; + stralloc_free(&info.sa) ; + if (!wasnull) stralloc_free(dst) ; + *dst = info.dst ; + return (int)nc ; + +err : + genalloc_free(elsubsu_t, &info.list) ; + stralloc_free(&info.sa) ; + if (wasnull) stralloc_free(&info.dst) ; else info.dst.len = dstbase ; + *dst = info.dst ; + return -1 ; +} |