/* * subst.c - Substitution rules * * Copyright 2012 by Werner Almesberger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #include "util.h" #include "vstring.h" #include "lang.h" #include "subst.h" /* ----- Rule set construction --------------------------------------------- */ static struct subst *alloc_subst(enum subst_type type) { struct subst *sub; sub = alloc_type(struct subst); sub->type = type; sub->next = NULL; return sub; } static char *prepare_re(const char *re) { char *res = NULL; int res_len = 0; append_char(&res, &res_len, '^'); append(&res, &res_len, re); append_char(&res, &res_len, '$'); return res; } struct subst *subst_match(const char *src, const char *re) { char error[1000]; struct subst *sub; char *tmp; int err; sub = alloc_subst(st_match); sub->u.match.src = src; tmp = prepare_re(re); err = regcomp(&sub->u.match.re, tmp, REG_EXTENDED); free(tmp); if (err) { regerror(err, &sub->u.match.re, error, sizeof(error)); yyerrorf("%s", error); } return sub; } static void end_chunk(struct chunk ***last, const char *start, const char *s) { struct chunk *c; if (s == start) return; c = alloc_type(struct chunk); c->type = ct_string; c->u.s = stralloc_n(start, s-start);; c->unit = NULL; c->next = NULL; **last = c; *last = &c->next; } static const char *parse_var(struct chunk *c, const char *s) { const char *t; int braced; if (!*s) yyerror("trailing dollar sign"); braced = *s == '{'; if (braced) s++; t = s; while (*t) { if (braced && *t == '}') break; if (s == t && *t == '$') { t++; break; } if (!isalnum(*t)) break; t++; } if (s == t) yyerror("invalid variable name"); if (braced && !*t) yyerror("unterminated variable name"); if (isdigit(*s)) { if (t != s+1 || *s == '0') yyerror("invalid variable name"); c->type = ct_sub; c->u.sub = *s-'0'; } else if (isalnum(*s)) { char *tmp; c->type = ct_var; tmp = stralloc_n(s, t-s); c->u.var = unique(tmp); free(tmp); } else { c->type = ct_sub; c->u.sub = 0; } if (*t != '#') { if (braced) { assert(*t == '}'); t++; } return t; } s = ++t; while (*t) { if (braced && *t == '}') break; if (!braced && t != s) break; t++; } if (s == t) yyerror("invalid unit"); c->unit = stralloc_n(s, t-s); if (braced) { if (!*t) yyerror("unterminated unit"); t++; } return t; } static struct chunk *parse_pattern(const char *s) { struct chunk *res = NULL, **last = &res; struct chunk *c; const char *start = s; while (*s) { if (*s == '\\') { if (!s[1]) yyerror("trailing backslash"); end_chunk(&last, start, s); start = s+1; s += 2; continue; } if (*s != '$') { s++; continue; } end_chunk(&last, start, s); c = alloc_type(struct chunk); c->unit = NULL; c->next = NULL; *last = c; last = &c->next; start = s = parse_var(c, s+1); } end_chunk(&last, start, s); return res; } struct subst *subst_assign(const char *dst, const char *pat) { struct subst *sub; sub = alloc_subst(st_assign); sub->u.assign.dst = dst; sub->u.assign.pat = parse_pattern(pat); return sub; } struct subst *subst_end(void) { return alloc_subst(st_end); } struct subst *subst_break(const char *block) { struct subst *sub; sub = alloc_subst(st_break); sub->u.tmp = block; return sub; } struct subst *subst_again(const char *block) { struct subst *sub; sub = alloc_subst(st_again); sub->u.tmp = block; return sub; } /* ----- Jump resolution --------------------------------------------------- */ struct parent { const struct subst *sub; const struct parent *parent; }; static const struct subst *resolve_jump(const char *name, const struct parent *parent) { while (parent) { assert(parent->sub->type == st_match); if (!strcmp(name, parent->sub->u.match.src)) return parent->sub; parent = parent->parent; } yyerrorf("cannot find \"%s\"", name); } static void recurse_fin(struct subst *sub, const struct parent *parent) { struct parent next = { .sub = sub, .parent = parent, }; while (sub) { switch (sub->type) { case st_match: recurse_fin(sub->u.match.block, &next); case st_assign: case st_end: break; case st_break: case st_again: sub->u.jump = resolve_jump(sub->u.tmp, parent); break; default: abort(); } sub = sub->next; } } void subst_finalize(struct subst *sub) { recurse_fin(sub, NULL); } /* ----- Dumping ----------------------------------------------------------- */ #define INDENT 4 static void dump_chunks(FILE *file, const struct chunk *c) { while (c) { switch (c->type) { case ct_string: fprintf(file, "%s", c->u.s); break; case ct_var: fprintf(file, "${%s", c->u.var); break; case ct_sub: if (c->u.sub) fprintf(file, "${%d", c->u.sub); else fprintf(file, "${$"); break; default: abort(); } if (c->type != ct_string) { if (c->unit) fprintf(file, "#%s", c->unit); fprintf(file, "}"); } c = c->next; } } static void recurse_dump(FILE *file, const struct subst *sub, int level) { while (sub) { fprintf(file, "%*s", INDENT*level, ""); switch (sub->type) { case st_match: fprintf(file, "%s=RE {\n", sub->u.match.src); recurse_dump(file, sub->u.match.block, level+1); fprintf(file, "%*s}\n", INDENT*level, ""); break; case st_assign: fprintf(file, "%s=", sub->u.assign.dst); dump_chunks(file, sub->u.assign.pat); fprintf(file, "\n"); break; case st_end: fprintf(file, "end\n"); break; case st_break: fprintf(file, "break %s\n", sub->u.jump->u.match.src); break; case st_again: fprintf(file, "again %s\n", sub->u.jump->u.match.src); break; default: abort(); } sub = sub->next; } } void subst_dump(FILE *file, const struct subst *sub) { recurse_dump(file, sub, 0); }