mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-01 05:16:05 +01:00
2017 lines
41 KiB
C
2017 lines
41 KiB
C
/*
|
|
* zutil.c - misc utilities
|
|
*
|
|
* This file is part of zsh, the Z shell.
|
|
*
|
|
* Copyright (c) 1999 Sven Wischnowsky
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, without written agreement and without
|
|
* license or royalty fees, to use, copy, modify, and distribute this
|
|
* software and to distribute modified versions of this software for any
|
|
* purpose, provided that the above copyright notice and the following
|
|
* two paragraphs appear in all copies of this software.
|
|
*
|
|
* In no event shall Sven Wischnowsky or the Zsh Development Group be liable
|
|
* to any party for direct, indirect, special, incidental, or consequential
|
|
* damages arising out of the use of this software and its documentation,
|
|
* even if Sven Wischnowsky and the Zsh Development Group have been advised of
|
|
* the possibility of such damage.
|
|
*
|
|
* Sven Wischnowsky and the Zsh Development Group specifically disclaim any
|
|
* warranties, including, but not limited to, the implied warranties of
|
|
* merchantability and fitness for a particular purpose. The software
|
|
* provided hereunder is on an "as is" basis, and Sven Wischnowsky and the
|
|
* Zsh Development Group have no obligation to provide maintenance,
|
|
* support, updates, enhancements, or modifications.
|
|
*
|
|
*/
|
|
|
|
#include "zutil.mdh"
|
|
#include "zutil.pro"
|
|
|
|
typedef struct {
|
|
char **match;
|
|
char **mbegin;
|
|
char **mend;
|
|
} MatchData;
|
|
|
|
static void
|
|
savematch(MatchData *m)
|
|
{
|
|
char **a;
|
|
|
|
queue_signals();
|
|
a = getaparam("match");
|
|
m->match = a ? zarrdup(a) : NULL;
|
|
a = getaparam("mbegin");
|
|
m->mbegin = a ? zarrdup(a) : NULL;
|
|
a = getaparam("mend");
|
|
m->mend = a ? zarrdup(a) : NULL;
|
|
unqueue_signals();
|
|
}
|
|
|
|
static void
|
|
restorematch(MatchData *m)
|
|
{
|
|
if (m->match)
|
|
setaparam("match", m->match);
|
|
else
|
|
unsetparam("match");
|
|
if (m->mbegin)
|
|
setaparam("mbegin", m->mbegin);
|
|
else
|
|
unsetparam("mbegin");
|
|
if (m->mend)
|
|
setaparam("mend", m->mend);
|
|
else
|
|
unsetparam("mend");
|
|
}
|
|
|
|
static void
|
|
freematch(MatchData *m)
|
|
{
|
|
if (m->match)
|
|
freearray(m->match);
|
|
if (m->mbegin)
|
|
freearray(m->mbegin);
|
|
if (m->mend)
|
|
freearray(m->mend);
|
|
}
|
|
|
|
/* Style stuff. */
|
|
|
|
typedef struct stypat *Stypat;
|
|
typedef struct style *Style;
|
|
|
|
/* A pattern and the styles for it. */
|
|
|
|
struct style {
|
|
struct hashnode node;
|
|
Stypat pats; /* patterns */
|
|
};
|
|
|
|
struct stypat {
|
|
Stypat next;
|
|
char *pat; /* pattern string */
|
|
Patprog prog; /* compiled pattern */
|
|
int weight; /* how specific is the pattern? */
|
|
Eprog eval; /* eval-on-retrieve? */
|
|
char **vals;
|
|
};
|
|
|
|
/* Hash table of styles and associated functions. */
|
|
|
|
static HashTable zstyletab;
|
|
|
|
/* Memory stuff. */
|
|
|
|
static void
|
|
freestylepatnode(Stypat p)
|
|
{
|
|
zsfree(p->pat);
|
|
freepatprog(p->prog);
|
|
if (p->vals)
|
|
freearray(p->vals);
|
|
if (p->eval)
|
|
freeeprog(p->eval);
|
|
zfree(p, sizeof(*p));
|
|
}
|
|
|
|
static void
|
|
freestylenode(HashNode hn)
|
|
{
|
|
Style s = (Style) hn;
|
|
Stypat p, pn;
|
|
|
|
p = s->pats;
|
|
while (p) {
|
|
pn = p->next;
|
|
freestylepatnode(p);
|
|
p = pn;
|
|
}
|
|
|
|
zsfree(s->node.nam);
|
|
zfree(s, sizeof(struct style));
|
|
}
|
|
|
|
/*
|
|
* Free the information for one of the patterns associated with
|
|
* a style.
|
|
*
|
|
* If the style s is passed, prev is the previous pattern in the list,
|
|
* found when scanning. We use this to update the list of patterns.
|
|
* If this results in their being no remaining patterns, the style
|
|
* itself is removed from the list of styles. This isn't optimised,
|
|
* since it's not a very frequent operation; we simply scan down the list
|
|
* to find the previous entry.
|
|
*/
|
|
static void
|
|
freestypat(Stypat p, Style s, Stypat prev)
|
|
{
|
|
if (s) {
|
|
if (prev)
|
|
prev->next = p->next;
|
|
else
|
|
s->pats = p->next;
|
|
}
|
|
|
|
freestylepatnode(p);
|
|
|
|
if (s && !s->pats) {
|
|
/* No patterns left, free style */
|
|
zstyletab->removenode(zstyletab, s->node.nam);
|
|
zsfree(s->node.nam);
|
|
zfree(s, sizeof(*s));
|
|
}
|
|
}
|
|
|
|
/* Pattern to match context when printing nodes */
|
|
|
|
static Patprog zstyle_contprog;
|
|
|
|
/*
|
|
* Print a node. Print flags as shown.
|
|
*/
|
|
enum {
|
|
ZSLIST_NONE,
|
|
ZSLIST_BASIC,
|
|
ZSLIST_SYNTAX,
|
|
};
|
|
|
|
static void
|
|
printstylenode(HashNode hn, int printflags)
|
|
{
|
|
Style s = (Style)hn;
|
|
Stypat p;
|
|
char **v;
|
|
|
|
if (printflags == ZSLIST_BASIC) {
|
|
quotedzputs(s->node.nam, stdout);
|
|
putchar('\n');
|
|
}
|
|
|
|
for (p = s->pats; p; p = p->next) {
|
|
if (zstyle_contprog && !pattry(zstyle_contprog, p->pat))
|
|
continue;
|
|
if (printflags == ZSLIST_BASIC)
|
|
printf("%s %s", (p->eval ? "(eval)" : " "), p->pat);
|
|
else {
|
|
printf("zstyle %s", (p->eval ? "-e " : ""));
|
|
quotedzputs(p->pat, stdout);
|
|
printf(" %s", s->node.nam);
|
|
}
|
|
for (v = p->vals; *v; v++) {
|
|
putchar(' ');
|
|
quotedzputs(*v, stdout);
|
|
}
|
|
putchar('\n');
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Scan the list for a particular pattern, maybe adding matches to
|
|
* the link list (heap memory). Value to be added as
|
|
* shown in enum
|
|
*/
|
|
static LinkList zstyle_list;
|
|
static char *zstyle_patname;
|
|
|
|
enum {
|
|
ZSPAT_NAME, /* Add style names for matched pattern to list */
|
|
ZSPAT_PAT, /* Add all patterns to list, doesn't use patname */
|
|
ZSPAT_REMOVE, /* Remove matched pattern, doesn't use list */
|
|
};
|
|
|
|
static void
|
|
scanpatstyles(HashNode hn, int spatflags)
|
|
{
|
|
Style s = (Style)hn;
|
|
Stypat p, q;
|
|
LinkNode n;
|
|
|
|
for (q = NULL, p = s->pats; p; q = p, p = p->next) {
|
|
switch (spatflags) {
|
|
case ZSPAT_NAME:
|
|
if (!strcmp(p->pat, zstyle_patname)) {
|
|
addlinknode(zstyle_list, s->node.nam);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case ZSPAT_PAT:
|
|
/* Check pattern isn't already there */
|
|
for (n = firstnode(zstyle_list); n; incnode(n))
|
|
if (!strcmp(p->pat, (char *) getdata(n)))
|
|
break;
|
|
if (!n)
|
|
addlinknode(zstyle_list, p->pat);
|
|
break;
|
|
|
|
case ZSPAT_REMOVE:
|
|
if (!strcmp(p->pat, zstyle_patname)) {
|
|
freestypat(p, s, q);
|
|
/*
|
|
* May remove link node itself; that's OK
|
|
* when scanning but we need to make sure
|
|
* we don't look at it any more.
|
|
*/
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static HashTable
|
|
newzstyletable(int size, char const *name)
|
|
{
|
|
HashTable ht;
|
|
ht = newhashtable(size, name, NULL);
|
|
|
|
ht->hash = hasher;
|
|
ht->emptytable = emptyhashtable;
|
|
ht->filltable = NULL;
|
|
ht->cmpnodes = strcmp;
|
|
ht->addnode = addhashnode;
|
|
/* DISABLED is not supported */
|
|
ht->getnode = gethashnode2;
|
|
ht->getnode2 = gethashnode2;
|
|
ht->removenode = removehashnode;
|
|
ht->disablenode = NULL;
|
|
ht->enablenode = NULL;
|
|
ht->freenode = freestylenode;
|
|
ht->printnode = printstylenode;
|
|
|
|
return ht;
|
|
}
|
|
|
|
/* Store a value for a style. */
|
|
|
|
static int
|
|
setstypat(Style s, char *pat, Patprog prog, char **vals, int eval)
|
|
{
|
|
int weight, tmp, first;
|
|
char *str;
|
|
Stypat p, q, qq;
|
|
Eprog eprog = NULL;
|
|
|
|
if (eval) {
|
|
int ef = errflag;
|
|
|
|
eprog = parse_string(zjoin(vals, ' ', 1), 0);
|
|
/* Keep any user interrupt error status */
|
|
errflag = ef | (errflag & ERRFLAG_INT);
|
|
|
|
if (!eprog)
|
|
{
|
|
freepatprog(prog);
|
|
return 1;
|
|
}
|
|
|
|
eprog = dupeprog(eprog, 0);
|
|
}
|
|
for (p = s->pats; p; p = p->next)
|
|
if (!strcmp(pat, p->pat)) {
|
|
|
|
/* Exists -> replace. */
|
|
|
|
if (p->vals)
|
|
freearray(p->vals);
|
|
if (p->eval)
|
|
freeeprog(p->eval);
|
|
p->vals = zarrdup(vals);
|
|
p->eval = eprog;
|
|
freepatprog(prog);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* New pattern. */
|
|
|
|
p = (Stypat) zalloc(sizeof(*p));
|
|
p->pat = ztrdup(pat);
|
|
p->prog = prog;
|
|
p->vals = zarrdup(vals);
|
|
p->eval = eprog;
|
|
p->next = NULL;
|
|
|
|
/* Calculate the weight. */
|
|
|
|
for (weight = 0, tmp = 2, first = 1, str = pat; *str; str++) {
|
|
if (first && *str == '*' && (!str[1] || str[1] == ':')) {
|
|
/* Only `*' in this component. */
|
|
tmp = 0;
|
|
continue;
|
|
}
|
|
first = 0;
|
|
|
|
if (*str == '(' || *str == '|' || *str == '*' || *str == '[' ||
|
|
*str == '<' || *str == '?' || *str == '#' || *str == '^')
|
|
/* Is pattern. */
|
|
tmp = 1;
|
|
|
|
if (*str == ':') {
|
|
/* Yet another component. */
|
|
|
|
first = 1;
|
|
weight += tmp;
|
|
tmp = 2;
|
|
}
|
|
}
|
|
p->weight = (weight += tmp);
|
|
|
|
for (qq = NULL, q = s->pats; q && q->weight >= weight;
|
|
qq = q, q = q->next);
|
|
|
|
p->next = q;
|
|
if (qq)
|
|
qq->next = p;
|
|
else
|
|
s->pats = p;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Add a new style. */
|
|
|
|
static Style
|
|
addstyle(char *name)
|
|
{
|
|
Style s = (Style) zshcalloc(sizeof(*s));
|
|
|
|
zstyletab->addnode(zstyletab, ztrdup(name), s);
|
|
|
|
return s;
|
|
}
|
|
|
|
static char **
|
|
evalstyle(Stypat p)
|
|
{
|
|
int ef = errflag;
|
|
char **ret, *str;
|
|
|
|
unsetparam("reply");
|
|
execode(p->eval, 1, 0, "style");
|
|
if (errflag) {
|
|
/* Keep any user interrupt error status */
|
|
errflag = ef | (errflag & ERRFLAG_INT);
|
|
return NULL;
|
|
}
|
|
errflag = ef | (errflag & ERRFLAG_INT);
|
|
|
|
queue_signals();
|
|
if ((ret = getaparam("reply")))
|
|
ret = arrdup(ret);
|
|
else if ((str = getsparam("reply"))) {
|
|
ret = (char **) hcalloc(2 * sizeof(char *));
|
|
ret[0] = dupstring(str);
|
|
}
|
|
unqueue_signals();
|
|
unsetparam("reply");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Look up a style for a context pattern. This does the matching. */
|
|
|
|
static char **
|
|
lookupstyle(char *ctxt, char *style)
|
|
{
|
|
Style s;
|
|
Stypat p;
|
|
char **found = NULL;
|
|
|
|
s = (Style)zstyletab->getnode2(zstyletab, style);
|
|
if (s) {
|
|
MatchData match;
|
|
savematch(&match);
|
|
for (p = s->pats; p; p = p->next)
|
|
if (pattry(p->prog, ctxt)) {
|
|
found = (p->eval ? evalstyle(p) : p->vals);
|
|
break;
|
|
}
|
|
restorematch(&match);
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
static int
|
|
bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
int min, max, n, add = 0, list = ZSLIST_NONE, eval = 0;
|
|
|
|
if (!args[0])
|
|
list = ZSLIST_BASIC;
|
|
else if (args[0][0] == '-') {
|
|
char oc;
|
|
|
|
if ((oc = args[0][1]) && oc != '-') {
|
|
if (args[0][2]) {
|
|
zwarnnam(nam, "invalid argument: %s", args[0]);
|
|
return 1;
|
|
}
|
|
if (oc == 'L') {
|
|
list = ZSLIST_SYNTAX;
|
|
args++;
|
|
} else if (oc == 'e') {
|
|
eval = add = 1;
|
|
args++;
|
|
}
|
|
} else {
|
|
add = 1;
|
|
args++;
|
|
}
|
|
} else
|
|
add = 1;
|
|
|
|
if (add) {
|
|
Style s;
|
|
Patprog prog;
|
|
char *pat;
|
|
|
|
if (arrlen_lt(args, 2)) {
|
|
zwarnnam(nam, "not enough arguments");
|
|
return 1;
|
|
}
|
|
pat = dupstring(args[0]);
|
|
tokenize(pat);
|
|
|
|
if (!(prog = patcompile(pat, PAT_ZDUP, NULL))) {
|
|
zwarnnam(nam, "invalid pattern: %s", args[0]);
|
|
return 1;
|
|
}
|
|
if (!(s = (Style)zstyletab->getnode2(zstyletab, args[1])))
|
|
s = addstyle(args[1]);
|
|
return setstypat(s, args[0], prog, args + 2, eval);
|
|
}
|
|
if (list) {
|
|
Style s;
|
|
char *context, *stylename;
|
|
|
|
switch (arrlen_ge(args, 3) ? 3 : arrlen(args)) {
|
|
case 2:
|
|
context = args[0];
|
|
stylename = args[1];
|
|
break;
|
|
|
|
case 1:
|
|
context = args[0];
|
|
stylename = NULL;
|
|
break;
|
|
|
|
case 0:
|
|
context = stylename = NULL;
|
|
break;
|
|
|
|
default:
|
|
zwarnnam(nam, "too many arguments");
|
|
return 1;
|
|
}
|
|
if (context) {
|
|
tokenize(context);
|
|
zstyle_contprog = patcompile(context, PAT_STATIC, NULL);
|
|
|
|
if (!zstyle_contprog)
|
|
return 1;
|
|
} else
|
|
zstyle_contprog = NULL;
|
|
|
|
if (stylename) {
|
|
s = (Style)zstyletab->getnode2(zstyletab, stylename);
|
|
if (!s)
|
|
return 1;
|
|
zstyletab->printnode(&s->node, list);
|
|
} else {
|
|
scanhashtable(zstyletab, 1, 0, 0,
|
|
zstyletab->printnode, list);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'd': min = 0; max = -1; break;
|
|
case 's': min = 3; max = 4; break;
|
|
case 'b': min = 3; max = 3; break;
|
|
case 'a': min = 3; max = 3; break;
|
|
case 't': min = 2; max = -1; break;
|
|
case 'T': min = 2; max = -1; break;
|
|
case 'm': min = 3; max = 3; break;
|
|
case 'g': min = 1; max = 3; break;
|
|
default:
|
|
zwarnnam(nam, "invalid option: %s", args[0]);
|
|
return 1;
|
|
}
|
|
n = arrlen(args) - 1;
|
|
if (n < min) {
|
|
zwarnnam(nam, "not enough arguments");
|
|
return 1;
|
|
} else if (max >= 0 && n > max) {
|
|
zwarnnam(nam, "too many arguments");
|
|
return 1;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'd':
|
|
{
|
|
Style s;
|
|
|
|
if (args[1]) {
|
|
if (args[2]) {
|
|
char *pat = args[1];
|
|
|
|
for (args += 2; *args; args++) {
|
|
if ((s = (Style)zstyletab->getnode2(zstyletab,
|
|
*args))) {
|
|
Stypat p, q;
|
|
|
|
for (q = NULL, p = s->pats; p;
|
|
q = p, p = p->next) {
|
|
if (!strcmp(p->pat, pat)) {
|
|
freestypat(p, s, q);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
zstyle_patname = args[1];
|
|
|
|
/* sorting not needed for deletion */
|
|
scanhashtable(zstyletab, 0, 0, 0, scanpatstyles,
|
|
ZSPAT_REMOVE);
|
|
}
|
|
} else
|
|
zstyletab->emptytable(zstyletab);
|
|
}
|
|
break;
|
|
case 's':
|
|
{
|
|
char **vals, *ret;
|
|
int val;
|
|
|
|
if ((vals = lookupstyle(args[1], args[2])) && vals[0]) {
|
|
ret = sepjoin(vals, (args[4] ? args[4] : " "), 0);
|
|
val = 0;
|
|
} else {
|
|
ret = ztrdup("");
|
|
val = 1;
|
|
}
|
|
setsparam(args[3], ret);
|
|
|
|
return val;
|
|
}
|
|
break;
|
|
case 'b':
|
|
{
|
|
char **vals, *ret;
|
|
int val;
|
|
|
|
if ((vals = lookupstyle(args[1], args[2])) &&
|
|
vals[0] && !vals[1] &&
|
|
(!strcmp(vals[0], "yes") ||
|
|
!strcmp(vals[0], "true") ||
|
|
!strcmp(vals[0], "on") ||
|
|
!strcmp(vals[0], "1"))) {
|
|
ret = "yes";
|
|
val = 0;
|
|
} else {
|
|
ret = "no";
|
|
val = 1;
|
|
}
|
|
setsparam(args[3], ztrdup(ret));
|
|
|
|
return val;
|
|
}
|
|
break;
|
|
case 'a':
|
|
{
|
|
char **vals, **ret;
|
|
int val;
|
|
|
|
if ((vals = lookupstyle(args[1], args[2]))) {
|
|
ret = zarrdup(vals);
|
|
val = 0;
|
|
} else {
|
|
char *dummy = NULL;
|
|
|
|
ret = zarrdup(&dummy);
|
|
val = 1;
|
|
}
|
|
setaparam(args[3], ret);
|
|
|
|
return val;
|
|
}
|
|
break;
|
|
case 't':
|
|
case 'T':
|
|
{
|
|
char **vals;
|
|
|
|
if ((vals = lookupstyle(args[1], args[2])) && vals[0]) {
|
|
if (args[3]) {
|
|
char **ap = args + 3, **p;
|
|
|
|
while (*ap) {
|
|
p = vals;
|
|
while (*p)
|
|
if (!strcmp(*ap, *p++))
|
|
return 0;
|
|
ap++;
|
|
}
|
|
return 1;
|
|
} else
|
|
return !(!strcmp(vals[0], "true") ||
|
|
!strcmp(vals[0], "yes") ||
|
|
!strcmp(vals[0], "on") ||
|
|
!strcmp(vals[0], "1"));
|
|
}
|
|
return (args[0][1] == 't' ? (vals ? 1 : 2) : 0);
|
|
}
|
|
break;
|
|
case 'm':
|
|
{
|
|
char **vals;
|
|
Patprog prog;
|
|
|
|
tokenize(args[3]);
|
|
|
|
if ((vals = lookupstyle(args[1], args[2])) &&
|
|
(prog = patcompile(args[3], PAT_STATIC, NULL))) {
|
|
while (*vals)
|
|
if (pattry(prog, *vals++))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
break;
|
|
case 'g':
|
|
{
|
|
int ret = 1;
|
|
Style s;
|
|
Stypat p;
|
|
|
|
zstyle_list = newlinklist();
|
|
|
|
if (args[2]) {
|
|
if (args[3]) {
|
|
if ((s = (Style)zstyletab->getnode2(zstyletab, args[3]))) {
|
|
for (p = s->pats; p; p = p->next) {
|
|
if (!strcmp(args[2], p->pat)) {
|
|
char **v = p->vals;
|
|
|
|
while (*v)
|
|
addlinknode(zstyle_list, *v++);
|
|
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
zstyle_patname = args[2];
|
|
scanhashtable(zstyletab, 1, 0, 0, scanpatstyles,
|
|
ZSPAT_NAME);
|
|
ret = 0;
|
|
}
|
|
} else {
|
|
scanhashtable(zstyletab, 1, 0, 0, scanpatstyles,
|
|
ZSPAT_PAT);
|
|
ret = 0;
|
|
}
|
|
set_list_array(args[1], zstyle_list);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Format stuff. */
|
|
|
|
/*
|
|
* One chunk of text, to allow recursive handling of ternary
|
|
* expressions in zformat -f output.
|
|
* instr The input string.
|
|
* specs The format specifiers, specs[c] is the string from c:string
|
|
* outp *outp is the start of the output string
|
|
* ousedp (*outp)[*ousedp] is where to write next
|
|
* olenp *olenp is the size allocated for *outp
|
|
* endchar Terminator character in addition to `\0' (may be '\0')
|
|
* skip If 1, don't output, just parse.
|
|
*/
|
|
static char *zformat_substring(char* instr, char **specs, char **outp,
|
|
int *ousedp, int *olenp, int endchar, int skip)
|
|
{
|
|
char *s;
|
|
|
|
for (s = instr; *s && *s != endchar; s++) {
|
|
if (*s == '%') {
|
|
int right, min = -1, max = -1, outl, testit;
|
|
char *spec, *start = s;
|
|
|
|
if ((right = (*++s == '-')))
|
|
s++;
|
|
|
|
if (idigit(*s)) {
|
|
for (min = 0; idigit(*s); s++)
|
|
min = (min * 10) + (int) STOUC(*s) - '0';
|
|
}
|
|
|
|
/* Ternary expressions */
|
|
testit = (STOUC(*s) == '(');
|
|
if (testit && s[1] == '-')
|
|
{
|
|
/* Allow %(-1... etc. */
|
|
right = 1;
|
|
s++;
|
|
}
|
|
if ((*s == '.' || testit) && idigit(s[1])) {
|
|
for (max = 0, s++; idigit(*s); s++)
|
|
max = (max * 10) + (int) STOUC(*s) - '0';
|
|
}
|
|
else if (testit)
|
|
s++;
|
|
|
|
if (testit && STOUC(*s)) {
|
|
int actval, testval, endcharl;
|
|
|
|
/*
|
|
* One one number is useful for ternary expressions.
|
|
* Remember to put the sign back.
|
|
*/
|
|
testval = (min >= 0) ? min : (max >= 0) ? max : 0;
|
|
if (right)
|
|
testval *= -1;
|
|
|
|
if (specs[STOUC(*s)])
|
|
actval = (int)mathevali(specs[STOUC(*s)]);
|
|
else
|
|
actval = 0;
|
|
/* zero means values are equal, i.e. true */
|
|
actval -= testval;
|
|
|
|
/* careful about premature end of string */
|
|
if (!(endcharl = *++s))
|
|
return NULL;
|
|
|
|
/*
|
|
* Either skip true text and output false text, or
|
|
* vice versa... unless we are already skipping.
|
|
*/
|
|
if (!(s = zformat_substring(s+1, specs, outp, ousedp,
|
|
olenp, endcharl, skip || actval)))
|
|
return NULL;
|
|
if (!(s = zformat_substring(s+1, specs, outp, ousedp,
|
|
olenp, ')', skip || !actval)))
|
|
return NULL;
|
|
} else if (skip) {
|
|
continue;
|
|
} else if ((spec = specs[STOUC(*s)])) {
|
|
int len;
|
|
|
|
if ((len = strlen(spec)) > max && max >= 0)
|
|
len = max;
|
|
outl = (min >= 0 ? (min > len ? min : len) : len);
|
|
|
|
if (*ousedp + outl >= *olenp) {
|
|
int nlen = *olenp + outl + 128;
|
|
char *tmp = (char *) zhalloc(nlen);
|
|
|
|
memcpy(tmp, *outp, *olenp);
|
|
*olenp = nlen;
|
|
*outp = tmp;
|
|
}
|
|
if (len >= outl) {
|
|
memcpy(*outp + *ousedp, spec, outl);
|
|
*ousedp += outl;
|
|
} else {
|
|
int diff = outl - len;
|
|
|
|
if (right) {
|
|
while (diff--)
|
|
(*outp)[(*ousedp)++] = ' ';
|
|
memcpy(*outp + *ousedp, spec, len);
|
|
*ousedp += len;
|
|
} else {
|
|
memcpy(*outp + *ousedp, spec, len);
|
|
*ousedp += len;
|
|
while (diff--)
|
|
(*outp)[(*ousedp)++] = ' ';
|
|
}
|
|
}
|
|
} else {
|
|
int len = s - start + 1;
|
|
|
|
if (*ousedp + len >= *olenp) {
|
|
int nlen = *olenp + len + 128;
|
|
char *tmp = (char *) zhalloc(nlen);
|
|
|
|
memcpy(tmp, *outp, *olenp);
|
|
*olenp = nlen;
|
|
*outp = tmp;
|
|
}
|
|
memcpy(*outp + *ousedp, start, len);
|
|
*ousedp += len;
|
|
}
|
|
} else {
|
|
if (skip)
|
|
continue;
|
|
if (*ousedp + 1 >= *olenp) {
|
|
char *tmp = (char *) zhalloc((*olenp) << 1);
|
|
|
|
memcpy(tmp, *outp, *olenp);
|
|
*olenp <<= 1;
|
|
*outp = tmp;
|
|
}
|
|
(*outp)[(*ousedp)++] = *s;
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static int
|
|
bin_zformat(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
char opt;
|
|
|
|
if (args[0][0] != '-' || !(opt = args[0][1]) || args[0][2]) {
|
|
zwarnnam(nam, "invalid argument: %s", args[0]);
|
|
return 1;
|
|
}
|
|
args++;
|
|
|
|
switch (opt) {
|
|
case 'f':
|
|
{
|
|
char **ap, *specs[256], *out;
|
|
int olen, oused = 0;
|
|
|
|
memset(specs, 0, 256 * sizeof(char *));
|
|
|
|
specs['%'] = "%";
|
|
specs[')'] = ")";
|
|
for (ap = args + 2; *ap; ap++) {
|
|
if (!ap[0][0] || ap[0][0] == '-' || ap[0][0] == '.' ||
|
|
idigit(ap[0][0]) || ap[0][1] != ':') {
|
|
zwarnnam(nam, "invalid argument: %s", *ap);
|
|
return 1;
|
|
}
|
|
specs[STOUC(ap[0][0])] = ap[0] + 2;
|
|
}
|
|
out = (char *) zhalloc(olen = 128);
|
|
|
|
zformat_substring(args[1], specs, &out, &oused, &olen, '\0', 0);
|
|
out[oused] = '\0';
|
|
|
|
setsparam(args[0], ztrdup(out));
|
|
return 0;
|
|
}
|
|
break;
|
|
case 'a':
|
|
{
|
|
char **ap, *cp;
|
|
int nbc = 0, colon = 0, pre = 0, suf = 0;
|
|
#ifdef MULTIBYTE_SUPPORT
|
|
int prechars = 0;
|
|
#endif /* MULTIBYTE_SUPPORT */
|
|
|
|
for (ap = args + 2; *ap; ap++) {
|
|
for (nbc = 0, cp = *ap; *cp && *cp != ':'; cp++)
|
|
if (*cp == '\\' && cp[1])
|
|
cp++, nbc++;
|
|
if (*cp == ':' && cp[1]) {
|
|
int d;
|
|
#ifdef MULTIBYTE_SUPPORT
|
|
int dchars = 0;
|
|
#endif /* MULTIBYTE_SUPPORT */
|
|
|
|
colon++;
|
|
if ((d = cp - *ap - nbc) > pre)
|
|
pre = d;
|
|
#ifdef MULTIBYTE_SUPPORT
|
|
if (isset(MULTIBYTE)) {
|
|
*cp = '\0';
|
|
dchars = MB_METASTRWIDTH(*ap) - nbc;
|
|
*cp = ':';
|
|
} else
|
|
dchars = d;
|
|
if (dchars > prechars)
|
|
prechars = dchars;
|
|
#endif /* MULTIBYTE_SUPPORT */
|
|
if ((d = strlen(cp + 1)) > suf)
|
|
suf = d;
|
|
}
|
|
}
|
|
{
|
|
int sl = strlen(args[1]);
|
|
VARARR(char, buf, pre + suf + sl + 1);
|
|
char **ret, **rp, *copy, *cpp, oldc;
|
|
|
|
ret = (char **) zalloc((arrlen(args + 2) + 1) *
|
|
sizeof(char *));
|
|
|
|
#ifndef MULTIBYTE_SUPPORT
|
|
memcpy(buf + pre, args[1], sl);
|
|
suf = pre + sl;
|
|
#endif /* MULTIBYTE_SUPPORT */
|
|
|
|
for (rp = ret, ap = args + 2; *ap; ap++) {
|
|
copy = dupstring(*ap);
|
|
for (cp = cpp = copy; *cp && *cp != ':'; cp++) {
|
|
if (*cp == '\\' && cp[1])
|
|
cp++;
|
|
*cpp++ = *cp;
|
|
}
|
|
oldc = *cpp;
|
|
*cpp = '\0';
|
|
if (((cpp == cp && oldc == ':') || *cp == ':') && cp[1]) {
|
|
#ifdef MULTIBYTE_SUPPORT
|
|
int rempad;
|
|
char *ptr;
|
|
memcpy(buf, copy, (cpp - copy));
|
|
*cp = '\0';
|
|
if (isset(MULTIBYTE))
|
|
rempad = prechars - MB_METASTRWIDTH(copy);
|
|
else
|
|
rempad = prechars - strlen(copy);
|
|
ptr = buf + (cpp - copy);
|
|
if (rempad)
|
|
memset(ptr, ' ', rempad);
|
|
ptr += rempad;
|
|
memcpy(ptr, args[1], sl);
|
|
ptr += sl;
|
|
strcpy(ptr, cp + 1);
|
|
#else /* MULTIBYTE_SUPPORT */
|
|
memset(buf, ' ', pre);
|
|
memcpy(buf, copy, (cpp - copy));
|
|
strcpy(buf + suf, cp + 1);
|
|
#endif /* MULTIBYTE_SUPPORT */
|
|
*rp++ = ztrdup(buf);
|
|
} else
|
|
*rp++ = ztrdup(copy);
|
|
}
|
|
*rp = NULL;
|
|
|
|
setaparam(args[0], ret);
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
zwarnnam(nam, "invalid option: -%c", opt);
|
|
return 1;
|
|
}
|
|
|
|
/* Zregexparse stuff. */
|
|
|
|
typedef struct {
|
|
int cutoff;
|
|
char *pattern;
|
|
Patprog patprog;
|
|
char *guard;
|
|
char *action;
|
|
LinkList branches;
|
|
} RParseState;
|
|
|
|
typedef struct {
|
|
RParseState *state;
|
|
LinkList actions;
|
|
} RParseBranch;
|
|
|
|
typedef struct {
|
|
LinkList nullacts;
|
|
LinkList in;
|
|
LinkList out;
|
|
} RParseResult;
|
|
|
|
static char **rparseargs;
|
|
static LinkList rparsestates;
|
|
|
|
static int rparsealt(RParseResult *result, jmp_buf *perr);
|
|
|
|
static void
|
|
connectstates(LinkList out, LinkList in)
|
|
{
|
|
LinkNode outnode, innode, ln;
|
|
|
|
for (outnode = firstnode(out); outnode; outnode = nextnode(outnode)) {
|
|
RParseBranch *outbranch = getdata(outnode);
|
|
|
|
for (innode = firstnode(in); innode; innode = nextnode(innode)) {
|
|
RParseBranch *inbranch = getdata(innode);
|
|
RParseBranch *br = hcalloc(sizeof(*br));
|
|
|
|
br->state = inbranch->state;
|
|
br->actions = newlinklist();
|
|
for (ln = firstnode(outbranch->actions); ln; ln = nextnode(ln))
|
|
addlinknode(br->actions, getdata(ln));
|
|
for (ln = firstnode(inbranch->actions); ln; ln = nextnode(ln))
|
|
addlinknode(br->actions, getdata(ln));
|
|
addlinknode(outbranch->state->branches, br);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
rparseelt(RParseResult *result, jmp_buf *perr)
|
|
{
|
|
int l;
|
|
char *s = *rparseargs;
|
|
|
|
if (!s)
|
|
return 1;
|
|
|
|
switch (s[0]) {
|
|
case '/': {
|
|
RParseState *st;
|
|
RParseBranch *br;
|
|
char *pattern, *lookahead;
|
|
int patternlen, lookaheadlen = 0;
|
|
|
|
l = strlen(s);
|
|
if (!((2 <= l && s[l - 1] == '/') ||
|
|
(3 <= l && s[l - 2] == '/' && (s[l - 1] == '+' ||
|
|
s[l - 1] == '-'))))
|
|
return 1;
|
|
st = hcalloc(sizeof(*st));
|
|
st->branches = newlinklist();
|
|
st->cutoff = s[l - 1];
|
|
if (s[l - 1] == '/') {
|
|
pattern = s + 1;
|
|
patternlen = l - 2;
|
|
} else {
|
|
pattern = s + 1;
|
|
patternlen = l - 3;
|
|
}
|
|
rparseargs++;
|
|
if ((s = *rparseargs) && s[0] == '%' &&
|
|
2 <= (l = strlen(s)) && s[l - 1] == '%') {
|
|
rparseargs++;
|
|
lookahead = s + 1;
|
|
lookaheadlen = l - 2;
|
|
} else {
|
|
lookahead = NULL;
|
|
}
|
|
if (patternlen == 2 && !strncmp(pattern, "[]", 2))
|
|
st->pattern = NULL;
|
|
else {
|
|
char *cp;
|
|
int l = patternlen + 12; /* (#b)((#B)...)...* */
|
|
if(lookahead)
|
|
l += lookaheadlen + 4; /* (#B)... */
|
|
cp = st->pattern = hcalloc(l);
|
|
strcpy(cp, "(#b)((#B)");
|
|
cp += 9;
|
|
strcpy(cp, pattern);
|
|
cp += patternlen;
|
|
strcpy(cp, ")");
|
|
cp += 1;
|
|
if (lookahead) {
|
|
strcpy(cp, "(#B)");
|
|
cp += 4;
|
|
strcpy(cp, lookahead);
|
|
cp += lookaheadlen;
|
|
}
|
|
strcpy(cp, "*");
|
|
}
|
|
st->patprog = NULL;
|
|
if ((s = *rparseargs) && *s == '-') {
|
|
rparseargs++;
|
|
l = strlen(s);
|
|
st->guard = hcalloc(l);
|
|
memcpy(st->guard, s + 1, l - 1);
|
|
st->guard[l - 1] = '\0';
|
|
} else
|
|
st->guard = NULL;
|
|
if ((s = *rparseargs) && *s == ':') {
|
|
rparseargs++;
|
|
l = strlen(s);
|
|
st->action = hcalloc(l);
|
|
memcpy(st->action, s + 1, l - 1);
|
|
st->action[l - 1] = '\0';
|
|
} else
|
|
st->action = NULL;
|
|
result->nullacts = NULL;
|
|
result->in = newlinklist();
|
|
br = hcalloc(sizeof(*br));
|
|
br->state = st;
|
|
br->actions = newlinklist();
|
|
addlinknode(result->in, br);
|
|
result->out = newlinklist();
|
|
br = hcalloc(sizeof(*br));
|
|
br->state = st;
|
|
br->actions = newlinklist();
|
|
addlinknode(result->out, br);
|
|
break;
|
|
}
|
|
case '(':
|
|
if (s[1])
|
|
return 1;
|
|
rparseargs++;
|
|
if (rparsealt(result, perr))
|
|
longjmp(*perr, 2);
|
|
s = *rparseargs;
|
|
if (!s || s[0] != ')' || s[1] != '\0')
|
|
longjmp(*perr, 2);
|
|
rparseargs++;
|
|
break;
|
|
default:
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rparseclo(RParseResult *result, jmp_buf *perr)
|
|
{
|
|
if (rparseelt(result, perr))
|
|
return 1;
|
|
|
|
if (*rparseargs && !strcmp(*rparseargs, "#")) {
|
|
rparseargs++;
|
|
while (*rparseargs && !strcmp(*rparseargs, "#"))
|
|
rparseargs++;
|
|
|
|
connectstates(result->out, result->in);
|
|
result->nullacts = newlinklist();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
prependactions(LinkList acts, LinkList branches)
|
|
{
|
|
LinkNode aln, bln;
|
|
|
|
for (bln = firstnode(branches); bln; bln = nextnode(bln)) {
|
|
RParseBranch *br = getdata(bln);
|
|
|
|
for (aln = lastnode(acts); aln != (LinkNode)acts; aln = prevnode(aln))
|
|
pushnode(br->actions, getdata(aln));
|
|
}
|
|
}
|
|
|
|
static void
|
|
appendactions(LinkList acts, LinkList branches)
|
|
{
|
|
LinkNode aln, bln;
|
|
for (bln = firstnode(branches); bln; bln = nextnode(bln)) {
|
|
RParseBranch *br = getdata(bln);
|
|
|
|
for (aln = firstnode(acts); aln; aln = nextnode(aln))
|
|
addlinknode(br->actions, getdata(aln));
|
|
}
|
|
}
|
|
|
|
static int
|
|
rparseseq(RParseResult *result, jmp_buf *perr)
|
|
{
|
|
int l;
|
|
char *s;
|
|
RParseResult sub;
|
|
|
|
result->nullacts = newlinklist();
|
|
result->in = newlinklist();
|
|
result->out = newlinklist();
|
|
|
|
while (1) {
|
|
if ((s = *rparseargs) && s[0] == '{' && s[(l = strlen(s)) - 1] == '}') {
|
|
char *action = hcalloc(l - 1);
|
|
LinkNode ln;
|
|
|
|
rparseargs++;
|
|
memcpy(action, s + 1, l - 2);
|
|
action[l - 2] = '\0';
|
|
if (result->nullacts)
|
|
addlinknode(result->nullacts, action);
|
|
for (ln = firstnode(result->out); ln; ln = nextnode(ln)) {
|
|
RParseBranch *br = getdata(ln);
|
|
addlinknode(br->actions, action);
|
|
}
|
|
}
|
|
else if (!rparseclo(&sub, perr)) {
|
|
connectstates(result->out, sub.in);
|
|
|
|
if (result->nullacts) {
|
|
prependactions(result->nullacts, sub.in);
|
|
insertlinklist(sub.in, lastnode(result->in), result->in);
|
|
}
|
|
if (sub.nullacts) {
|
|
appendactions(sub.nullacts, result->out);
|
|
insertlinklist(sub.out, lastnode(result->out), result->out);
|
|
} else
|
|
result->out = sub.out;
|
|
|
|
if (result->nullacts && sub.nullacts)
|
|
insertlinklist(sub.nullacts, lastnode(result->nullacts),
|
|
result->nullacts);
|
|
else
|
|
result->nullacts = NULL;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rparsealt(RParseResult *result, jmp_buf *perr)
|
|
{
|
|
RParseResult sub;
|
|
|
|
if (rparseseq(result, perr))
|
|
return 1;
|
|
|
|
while (*rparseargs && !strcmp(*rparseargs, "|")) {
|
|
rparseargs++;
|
|
if (rparseseq(&sub, perr))
|
|
longjmp(*perr, 2);
|
|
if (!result->nullacts && sub.nullacts)
|
|
result->nullacts = sub.nullacts;
|
|
|
|
insertlinklist(sub.in, lastnode(result->in), result->in);
|
|
insertlinklist(sub.out, lastnode(result->out), result->out);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rmatch(RParseResult *sm, char *subj, char *var1, char *var2, int comp)
|
|
{
|
|
LinkNode ln, lnn;
|
|
LinkList nexts;
|
|
LinkList nextslist;
|
|
RParseBranch *br;
|
|
RParseState *st = NULL;
|
|
int point1 = 0, point2 = 0;
|
|
|
|
setiparam(var1, point1);
|
|
setiparam(var2, point2);
|
|
|
|
if (!comp && !*subj && sm->nullacts) {
|
|
for (ln = firstnode(sm->nullacts); ln; ln = nextnode(ln)) {
|
|
char *action = getdata(ln);
|
|
|
|
if (action)
|
|
execstring(action, 1, 0, "zregexparse-action");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
nextslist = newlinklist();
|
|
nexts = sm->in;
|
|
addlinknode(nextslist, nexts);
|
|
do {
|
|
MatchData match1, match2;
|
|
|
|
savematch(&match1);
|
|
|
|
for (ln = firstnode(nexts); ln; ln = nextnode(ln)) {
|
|
int i;
|
|
RParseState *next;
|
|
|
|
br = getdata(ln);
|
|
next = br->state;
|
|
if (next->pattern && !next->patprog) {
|
|
tokenize(next->pattern);
|
|
if (!(next->patprog = patcompile(next->pattern, 0, NULL)))
|
|
return 3;
|
|
}
|
|
if (next->pattern && pattry(next->patprog, subj) &&
|
|
(!next->guard || (execstring(next->guard, 1, 0,
|
|
"zregexparse-guard"), !lastval))) {
|
|
LinkNode aln;
|
|
char **mend;
|
|
int len;
|
|
|
|
queue_signals();
|
|
mend = getaparam("mend");
|
|
len = atoi(mend[0]);
|
|
unqueue_signals();
|
|
|
|
for (i = len; i; i--)
|
|
if (*subj++ == Meta)
|
|
subj++;
|
|
|
|
savematch(&match2);
|
|
restorematch(&match1);
|
|
|
|
for (aln = firstnode(br->actions); aln; aln = nextnode(aln)) {
|
|
char *action = getdata(aln);
|
|
|
|
if (action)
|
|
execstring(action, 1, 0, "zregexparse-action");
|
|
}
|
|
restorematch(&match2);
|
|
|
|
point2 += len;
|
|
setiparam(var2, point2);
|
|
st = br->state;
|
|
nexts = st->branches;
|
|
if (next->cutoff == '-' || (next->cutoff == '/' && len)) {
|
|
nextslist = newlinklist();
|
|
point1 = point2;
|
|
setiparam(var1, point1);
|
|
}
|
|
addlinknode(nextslist, nexts);
|
|
break;
|
|
}
|
|
}
|
|
if (!ln)
|
|
freematch(&match1);
|
|
} while (ln);
|
|
|
|
if (!comp && !*subj)
|
|
for (ln = firstnode(sm->out); ln; ln = nextnode(ln)) {
|
|
br = getdata(ln);
|
|
if (br->state == st) {
|
|
for (ln = firstnode(br->actions); ln; ln = nextnode(ln)) {
|
|
char *action = getdata(ln);
|
|
|
|
if (action)
|
|
execstring(action, 1, 0, "zregexparse-action");
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
for (lnn = firstnode(nextslist); lnn; lnn = nextnode(lnn)) {
|
|
nexts = getdata(lnn);
|
|
for (ln = firstnode(nexts); ln; ln = nextnode(ln)) {
|
|
br = getdata(ln);
|
|
if (br->state->action)
|
|
execstring(br->state->action, 1, 0, "zregexparse-action");
|
|
}
|
|
}
|
|
return empty(nexts) ? 2 : 1;
|
|
}
|
|
|
|
/*
|
|
usage: zregexparse [-c] var1 var2 string regex...
|
|
status:
|
|
0: matched
|
|
1: unmatched (all next state candidates are failed)
|
|
2: unmatched (there is no next state candidates)
|
|
3: regex parse error
|
|
*/
|
|
|
|
static int
|
|
bin_zregexparse(char *nam, char **args, Options ops, UNUSED(int func))
|
|
{
|
|
int oldextendedglob = opts[EXTENDEDGLOB];
|
|
char *var1 = args[0];
|
|
char *var2 = args[1];
|
|
char *subj = args[2];
|
|
int ret;
|
|
jmp_buf rparseerr;
|
|
RParseResult result;
|
|
|
|
opts[EXTENDEDGLOB] = 1;
|
|
|
|
rparseargs = args + 3;
|
|
|
|
pushheap();
|
|
rparsestates = newlinklist();
|
|
if (setjmp(rparseerr) || rparsealt(&result, &rparseerr) || *rparseargs) {
|
|
if (*rparseargs)
|
|
zwarnnam(nam, "invalid regex : %s", *rparseargs);
|
|
else
|
|
zwarnnam(nam, "not enough regex arguments");
|
|
ret = 3;
|
|
} else
|
|
ret = 0;
|
|
|
|
if (!ret)
|
|
ret = rmatch(&result, subj, var1, var2, OPT_ISSET(ops,'c'));
|
|
popheap();
|
|
|
|
opts[EXTENDEDGLOB] = oldextendedglob;
|
|
return ret;
|
|
}
|
|
|
|
typedef struct zoptdesc *Zoptdesc;
|
|
typedef struct zoptarr *Zoptarr;
|
|
typedef struct zoptval *Zoptval;
|
|
|
|
struct zoptdesc {
|
|
Zoptdesc next;
|
|
char *name;
|
|
int flags;
|
|
Zoptarr arr;
|
|
Zoptval vals, last;
|
|
};
|
|
|
|
#define ZOF_ARG 1
|
|
#define ZOF_OPT 2
|
|
#define ZOF_MULT 4
|
|
#define ZOF_SAME 8
|
|
#define ZOF_MAP 16
|
|
#define ZOF_CYC 32
|
|
|
|
struct zoptarr {
|
|
Zoptarr next;
|
|
char *name;
|
|
Zoptval vals, last;
|
|
int num;
|
|
};
|
|
|
|
struct zoptval {
|
|
Zoptval next, onext;
|
|
char *name;
|
|
char *arg;
|
|
char *str;
|
|
};
|
|
|
|
static Zoptdesc opt_descs;
|
|
static Zoptarr opt_arrs;
|
|
|
|
static Zoptdesc
|
|
get_opt_desc(char *name)
|
|
{
|
|
Zoptdesc p;
|
|
|
|
for (p = opt_descs; p; p = p->next)
|
|
if (!strcmp(name, p->name))
|
|
return p;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Zoptdesc
|
|
lookup_opt(char *str)
|
|
{
|
|
Zoptdesc p;
|
|
|
|
for (p = opt_descs; p; p = p->next) {
|
|
if ((p->flags & ZOF_ARG) ? strpfx(p->name, str) : !strcmp(p->name, str))
|
|
return p;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static Zoptarr
|
|
get_opt_arr(char *name)
|
|
{
|
|
Zoptarr p;
|
|
|
|
for (p = opt_arrs; p; p = p->next)
|
|
if (!strcmp(name, p->name))
|
|
return p;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Zoptdesc
|
|
map_opt_desc(Zoptdesc start)
|
|
{
|
|
Zoptdesc map = NULL;
|
|
|
|
if (!start || !(start->flags & ZOF_MAP))
|
|
return start;
|
|
|
|
map = get_opt_desc(start->arr->name);
|
|
|
|
if (!map)
|
|
return start;
|
|
|
|
if (map == start) {
|
|
start->flags &= ~ZOF_MAP; /* optimize */
|
|
return start;
|
|
}
|
|
|
|
if (map->flags & ZOF_CYC)
|
|
return NULL;
|
|
|
|
start->flags |= ZOF_CYC;
|
|
map = map_opt_desc(map);
|
|
start->flags &= ~ZOF_CYC;
|
|
|
|
return map;
|
|
}
|
|
|
|
static void
|
|
add_opt_val(Zoptdesc d, char *arg)
|
|
{
|
|
Zoptval v = NULL;
|
|
char *n = dyncat("-", d->name);
|
|
int new = 0;
|
|
|
|
Zoptdesc map = map_opt_desc(d);
|
|
if (map)
|
|
d = map;
|
|
|
|
if (!(d->flags & ZOF_MULT))
|
|
v = d->vals;
|
|
if (!v) {
|
|
v = (Zoptval) zhalloc(sizeof(*v));
|
|
v->next = v->onext = NULL;
|
|
v->name = n;
|
|
new = 1;
|
|
}
|
|
v->arg = arg;
|
|
if ((d->flags & ZOF_ARG) && !(d->flags & (ZOF_OPT | ZOF_SAME))) {
|
|
v->str = NULL;
|
|
if (d->arr)
|
|
d->arr->num += (arg ? 2 : 1);
|
|
} else if (arg) {
|
|
char *s = (char *) zhalloc(strlen(d->name) + strlen(arg) + 2);
|
|
|
|
*s = '-';
|
|
strcpy(s + 1, d->name);
|
|
strcat(s, arg);
|
|
v->str = s;
|
|
if (d->arr)
|
|
d->arr->num += 1;
|
|
} else {
|
|
v->str = NULL;
|
|
if (d->arr)
|
|
d->arr->num += 1;
|
|
}
|
|
if (new) {
|
|
if (d->arr) {
|
|
if (d->arr->last)
|
|
d->arr->last->next = v;
|
|
else
|
|
d->arr->vals = v;
|
|
d->arr->last = v;
|
|
}
|
|
if (d->last)
|
|
d->last->onext = v;
|
|
else
|
|
d->vals = v;
|
|
d->last = v;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For "zparseopts -K -A assoc ..." this function copies the keys and
|
|
* values from the default and allocates the extra space for any parsed
|
|
* values having the same keys. If there are no new values, creates an
|
|
* empty array. Returns a pointer to the NULL element marking the end.
|
|
*
|
|
* aval = pointer to the newly allocated array
|
|
* assoc = name of the default hash param to copy
|
|
* keep = whether we need to make the copy at all
|
|
* num = count of new values to add space for
|
|
*/
|
|
static char **
|
|
zalloc_default_array(char ***aval, char *assoc, int keep, int num)
|
|
{
|
|
char **ap = 0;
|
|
|
|
*aval = 0;
|
|
if (keep && num) {
|
|
struct value vbuf;
|
|
Value v = fetchvalue(&vbuf, &assoc, 0,
|
|
SCANPM_WANTKEYS|SCANPM_WANTVALS|SCANPM_MATCHMANY);
|
|
if (v && v->isarr) {
|
|
char **dp, **dval = getarrvalue(v);
|
|
int dnum = (dval ? arrlen(dval) : 0) + 1;
|
|
*aval = (char **) zalloc(((num * 2) + dnum) * sizeof(char *));
|
|
for (ap = *aval, dp = dval; dp && *dp; dp++) {
|
|
*ap = (char *) zalloc(strlen(*dp) + 1);
|
|
strcpy(*ap++, *dp);
|
|
}
|
|
*ap = NULL;
|
|
}
|
|
}
|
|
if (!ap) {
|
|
ap = *aval = (char **) zalloc(((num * 2) + 1) * sizeof(char *));
|
|
*ap = NULL;
|
|
}
|
|
return ap;
|
|
}
|
|
|
|
static int
|
|
bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
char *o, *p, *n, **pp, **aval, **ap, *assoc = NULL, **cp, **np;
|
|
int del = 0, flags = 0, extract = 0, keep = 0;
|
|
Zoptdesc sopts[256], d;
|
|
Zoptarr a, defarr = NULL;
|
|
Zoptval v;
|
|
|
|
opt_descs = NULL;
|
|
opt_arrs = NULL;
|
|
memset(sopts, 0, 256 * sizeof(Zoptdesc));
|
|
|
|
while ((o = *args++)) {
|
|
if (*o == '-') {
|
|
switch (o[1]) {
|
|
case '\0':
|
|
o = NULL;
|
|
break;
|
|
case '-':
|
|
if (o[2])
|
|
args--;
|
|
/* else unreachable, default parsing removes "--" */
|
|
o = NULL;
|
|
break;
|
|
case 'D':
|
|
if (o[2]) {
|
|
args--;
|
|
o = NULL;
|
|
break;
|
|
}
|
|
del = 1;
|
|
break;
|
|
case 'E':
|
|
if (o[2]) {
|
|
args--;
|
|
o = NULL;
|
|
break;
|
|
}
|
|
extract = 1;
|
|
break;
|
|
case 'K':
|
|
if (o[2]) {
|
|
args--;
|
|
o = NULL;
|
|
break;
|
|
}
|
|
keep = 1;
|
|
break;
|
|
case 'M':
|
|
if (o[2]) {
|
|
args--;
|
|
o = NULL;
|
|
break;
|
|
}
|
|
flags |= ZOF_MAP;
|
|
break;
|
|
case 'a':
|
|
if (defarr) {
|
|
zwarnnam(nam, "default array given more than once");
|
|
return 1;
|
|
}
|
|
if (o[2])
|
|
n = o + 2;
|
|
else if (*args)
|
|
n = *args++;
|
|
else {
|
|
zwarnnam(nam, "missing array name");
|
|
return 1;
|
|
}
|
|
defarr = (Zoptarr) zhalloc(sizeof(*defarr));
|
|
defarr->name = n;
|
|
defarr->num = 0;
|
|
defarr->vals = defarr->last = NULL;
|
|
defarr->next = NULL;
|
|
opt_arrs = defarr;
|
|
break;
|
|
case 'A':
|
|
if (assoc) {
|
|
zwarnnam(nam, "associative array given more than once");
|
|
return 1;
|
|
}
|
|
if (o[2])
|
|
assoc = o + 2;
|
|
else if (*args)
|
|
assoc = *args++;
|
|
else {
|
|
zwarnnam(nam, "missing array name");
|
|
return 1;
|
|
}
|
|
break;
|
|
default:
|
|
/* Anything else is an option description */
|
|
args--;
|
|
o = NULL;
|
|
break;
|
|
}
|
|
if (!o) {
|
|
o = "";
|
|
break;
|
|
}
|
|
} else {
|
|
args--;
|
|
break;
|
|
}
|
|
}
|
|
if (!o) {
|
|
zwarnnam(nam, "missing option descriptions");
|
|
return 1;
|
|
}
|
|
while ((o = dupstring(*args++))) {
|
|
int f = 0;
|
|
if (!*o) {
|
|
zwarnnam(nam, "invalid option description: %s", o);
|
|
return 1;
|
|
}
|
|
for (p = o; *p; p++) {
|
|
if (*p == '\\' && p[1])
|
|
p++;
|
|
else if (p > o) { /* At least one character of option name */
|
|
if (*p == '+') {
|
|
f |= ZOF_MULT;
|
|
*p = '\0';
|
|
p++;
|
|
break;
|
|
} else if (*p == ':' || *p == '=')
|
|
break;
|
|
}
|
|
}
|
|
if (*p == ':') {
|
|
f |= ZOF_ARG;
|
|
*p = '\0';
|
|
if (*++p == ':') {
|
|
p++;
|
|
f |= ZOF_OPT;
|
|
}
|
|
if (*p == '-') {
|
|
p++;
|
|
f |= ZOF_SAME;
|
|
}
|
|
}
|
|
a = NULL;
|
|
if (*p == '=') {
|
|
*p++ = '\0';
|
|
f |= flags;
|
|
if (!(a = get_opt_arr(p))) {
|
|
a = (Zoptarr) zhalloc(sizeof(*a));
|
|
a->name = p;
|
|
a->num = 0;
|
|
a->vals = a->last = NULL;
|
|
a->next = opt_arrs;
|
|
opt_arrs = a;
|
|
}
|
|
} else if (*p) {
|
|
zwarnnam(nam, "invalid option description: %s", args[-1]);
|
|
return 1;
|
|
} else if (!(a = defarr) && !assoc) {
|
|
zwarnnam(nam, "no default array defined: %s", args[-1]);
|
|
return 1;
|
|
}
|
|
for (p = n = o; *p; p++) {
|
|
if (*p == '\\' && p[1])
|
|
p++;
|
|
*n++ = *p;
|
|
}
|
|
*n = '\0';
|
|
if (get_opt_desc(o)) {
|
|
zwarnnam(nam, "option defined more than once: %s", o);
|
|
return 1;
|
|
}
|
|
d = (Zoptdesc) zhalloc(sizeof(*d));
|
|
d->name = o;
|
|
d->flags = f;
|
|
d->arr = a;
|
|
d->next = opt_descs;
|
|
d->vals = d->last = NULL;
|
|
opt_descs = d;
|
|
if (!o[1])
|
|
sopts[STOUC(*o)] = d;
|
|
if ((flags & ZOF_MAP) && !map_opt_desc(d)) {
|
|
zwarnnam(nam, "cyclic option mapping: %s", args[-1]);
|
|
return 1;
|
|
}
|
|
}
|
|
np = cp = pp = ((extract && del) ? arrdup(pparams) : pparams);
|
|
for (; (o = *pp); pp++) {
|
|
if (*o != '-') {
|
|
if (extract) {
|
|
if (del)
|
|
*cp++ = o;
|
|
continue;
|
|
} else
|
|
break;
|
|
}
|
|
if (!o[1] || (o[1] == '-' && !o[2])) {
|
|
if (del && extract)
|
|
*cp++ = o;
|
|
pp++;
|
|
break;
|
|
}
|
|
if (!(d = lookup_opt(o + 1))) {
|
|
while (*++o) {
|
|
if (!(d = sopts[STOUC(*o)])) {
|
|
o = NULL;
|
|
break;
|
|
}
|
|
if (d->flags & ZOF_ARG) {
|
|
if (o[1]) {
|
|
add_opt_val(d, o + 1);
|
|
break;
|
|
} else if (!(d->flags & ZOF_OPT) ||
|
|
(pp[1] && pp[1][0] != '-')) {
|
|
if (!pp[1]) {
|
|
zwarnnam(nam, "missing argument for option: %s",
|
|
d->name);
|
|
return 1;
|
|
}
|
|
add_opt_val(d, *++pp);
|
|
} else
|
|
add_opt_val(d, NULL);
|
|
} else
|
|
add_opt_val(d, NULL);
|
|
}
|
|
if (!o) {
|
|
if (extract) {
|
|
if (del)
|
|
*cp++ = *pp;
|
|
continue;
|
|
} else
|
|
break;
|
|
}
|
|
} else {
|
|
if (d->flags & ZOF_ARG) {
|
|
char *e = o + strlen(d->name) + 1;
|
|
|
|
if (*e)
|
|
add_opt_val(d, e);
|
|
else if (!(d->flags & ZOF_OPT) ||
|
|
(pp[1] && pp[1][0] != '-')) {
|
|
if (!pp[1]) {
|
|
zwarnnam(nam, "missing argument for option: %s",
|
|
d->name);
|
|
return 1;
|
|
}
|
|
add_opt_val(d, *++pp);
|
|
} else
|
|
add_opt_val(d, NULL);
|
|
} else
|
|
add_opt_val(d, NULL);
|
|
}
|
|
}
|
|
|
|
if (flags & ZOF_MAP) {
|
|
for (d = opt_descs; d; d = d->next)
|
|
if (d->arr && !d->vals && (d->flags & ZOF_MAP)) {
|
|
if (d->arr->num == 0 && get_opt_desc(d->arr->name))
|
|
d->arr->num = -1; /* this is not a real array */
|
|
}
|
|
}
|
|
if (extract && del)
|
|
while (*pp)
|
|
*cp++ = *pp++;
|
|
|
|
for (a = opt_arrs; a; a = a->next) {
|
|
if (a->num >= 0 && (!keep || a->num)) {
|
|
aval = (char **) zalloc((a->num + 1) * sizeof(char *));
|
|
for (ap = aval, v = a->vals; v; ap++, v = v->next) {
|
|
if (v->str)
|
|
*ap = ztrdup(v->str);
|
|
else {
|
|
*ap = ztrdup(v->name);
|
|
if (v->arg)
|
|
*++ap = ztrdup(v->arg);
|
|
}
|
|
}
|
|
*ap = NULL;
|
|
setaparam(a->name, aval);
|
|
}
|
|
}
|
|
if (assoc) {
|
|
int num;
|
|
|
|
for (num = 0, d = opt_descs; d; d = d->next)
|
|
if (d->vals)
|
|
num++;
|
|
|
|
if (!keep || num) {
|
|
ap = zalloc_default_array(&aval, assoc, keep, num);
|
|
for (d = opt_descs; d; d = d->next) {
|
|
if (d->vals) {
|
|
*ap++ = n = (char *) zalloc(strlen(d->name) + 2);
|
|
*n = '-';
|
|
strcpy(n + 1, d->name);
|
|
|
|
for (num = 1, v = d->vals; v; v = v->onext) {
|
|
num += (v->arg ? strlen(v->arg) : 0);
|
|
if (v->next)
|
|
num++;
|
|
}
|
|
*ap++ = n = (char *) zalloc(num);
|
|
for (v = d->vals; v; v = v->onext) {
|
|
if (v->arg) {
|
|
strcpy(n, v->arg);
|
|
n += strlen(v->arg);
|
|
}
|
|
*n = ' ';
|
|
}
|
|
*n = '\0';
|
|
}
|
|
}
|
|
*ap = NULL;
|
|
sethparam(assoc, aval);
|
|
}
|
|
}
|
|
if (del) {
|
|
if (extract) {
|
|
*cp = NULL;
|
|
freearray(pparams);
|
|
pparams = zarrdup(np);
|
|
} else {
|
|
pp = zarrdup(pp);
|
|
freearray(pparams);
|
|
pparams = pp;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct builtin bintab[] = {
|
|
BUILTIN("zformat", 0, bin_zformat, 3, -1, 0, NULL, NULL),
|
|
BUILTIN("zparseopts", 0, bin_zparseopts, 1, -1, 0, NULL, NULL),
|
|
BUILTIN("zregexparse", 0, bin_zregexparse, 3, -1, 0, "c", NULL),
|
|
BUILTIN("zstyle", 0, bin_zstyle, 0, -1, 0, NULL, NULL),
|
|
};
|
|
|
|
static struct features module_features = {
|
|
bintab, sizeof(bintab)/sizeof(*bintab),
|
|
NULL, 0,
|
|
NULL, 0,
|
|
NULL, 0,
|
|
0
|
|
};
|
|
|
|
/**/
|
|
int
|
|
setup_(UNUSED(Module m))
|
|
{
|
|
zstyletab = newzstyletable(17, "zstyletab");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
features_(Module m, char ***features)
|
|
{
|
|
*features = featuresarray(m, &module_features);
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
enables_(Module m, int **enables)
|
|
{
|
|
return handlefeatures(m, &module_features, enables);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
boot_(UNUSED(Module m))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
cleanup_(Module m)
|
|
{
|
|
return setfeatureenables(m, &module_features, NULL);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
finish_(UNUSED(Module m))
|
|
{
|
|
deletehashtable(zstyletab);
|
|
|
|
return 0;
|
|
}
|