#include "config.h"
#include <sys/queue.h>
#include <sys/time.h>
#include <bitstring.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../common/common.h"
#include "../vi/vi.h"
#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
#define SUB_FIRST 0x01
#define SUB_MUSTSETR 0x02
static int re_conv(SCR *, char **, size_t *, int *);
static int re_sub(SCR *, char *, char **, size_t *, size_t *, regmatch_t [10]);
static int re_tag_conv(SCR *, char **, size_t *, int *);
static int s(SCR *, EXCMD *, char *, regex_t *, u_int);
int
ex_s(SCR *sp, EXCMD *cmdp)
{
regex_t *re;
size_t blen, len;
u_int flags;
int delim;
char *bp, *ptrn, *rep, *p, *t;
if (cmdp->argc == 0)
goto subagain;
for (p = cmdp->argv[0]->bp,
len = cmdp->argv[0]->len; len > 0; --len, ++p) {
if (!isblank(*p))
break;
}
if (len == 0)
subagain: return (ex_subagain(sp, cmdp));
delim = *p++;
if (isalnum(delim) || delim == '\\')
return (s(sp, cmdp, p, &sp->subre_c, SUB_MUSTSETR));
sp->c_suffix = sp->g_suffix = 0;
for (ptrn = t = p;;) {
if (p[0] == '\0' || p[0] == delim) {
if (p[0] == delim)
++p;
*t = '\0';
break;
}
if (p[0] == '\\') {
if (p[1] == delim)
++p;
else if (p[1] == '\\')
*t++ = *p++;
}
*t++ = *p++;
}
if (*ptrn == '\0') {
if (sp->re == NULL) {
ex_emsg(sp, NULL, EXM_NOPREVRE);
return (1);
}
if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,
sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH))
return (1);
flags = 0;
} else {
if (re_compile(sp, ptrn, t - ptrn,
&sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH))
return (1);
if (re_compile(sp, ptrn, t - ptrn,
&sp->subre, &sp->subre_len, &sp->subre_c, RE_C_SUBST))
return (1);
flags = SUB_FIRST;
sp->searchdir = FORWARD;
}
re = &sp->re_c;
if (p[0] == '\0' || p[0] == delim) {
if (p[0] == delim)
++p;
free(sp->repl);
sp->repl = NULL;
sp->repl_len = 0;
} else if (p[0] == '%' && (p[1] == '\0' || p[1] == delim))
p += p[1] == delim ? 2 : 1;
else {
for (rep = p, len = 0;
p[0] != '\0' && p[0] != delim; ++p, ++len)
if (p[0] == '~')
len += sp->repl_len;
GET_SPACE_RET(sp, bp, blen, len);
for (t = bp, len = 0, p = rep;;) {
if (p[0] == '\0' || p[0] == delim) {
if (p[0] == delim)
++p;
break;
}
if (p[0] == '\\') {
if (p[1] == delim)
++p;
else if (p[1] == '\\') {
*t++ = *p++;
++len;
} else if (p[1] == '~') {
++p;
if (!O_ISSET(sp, O_MAGIC))
goto tilde;
}
} else if (p[0] == '~' && O_ISSET(sp, O_MAGIC)) {
tilde: ++p;
memcpy(t, sp->repl, sp->repl_len);
t += sp->repl_len;
len += sp->repl_len;
continue;
}
*t++ = *p++;
++len;
}
if ((sp->repl_len = len) != 0) {
free(sp->repl);
if ((sp->repl = malloc(len)) == NULL) {
msgq(sp, M_SYSERR, NULL);
FREE_SPACE(sp, bp, blen);
return (1);
}
memcpy(sp->repl, bp, len);
}
FREE_SPACE(sp, bp, blen);
}
return (s(sp, cmdp, p, re, flags));
}
int
ex_subagain(SCR *sp, EXCMD *cmdp)
{
if (sp->subre == NULL) {
ex_emsg(sp, NULL, EXM_NOPREVRE);
return (1);
}
if (!F_ISSET(sp, SC_RE_SUBST) && re_compile(sp,
sp->subre, sp->subre_len, NULL, NULL, &sp->subre_c, RE_C_SUBST))
return (1);
return (s(sp,
cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->subre_c, 0));
}
int
ex_subtilde(SCR *sp, EXCMD *cmdp)
{
if (sp->re == NULL) {
ex_emsg(sp, NULL, EXM_NOPREVRE);
return (1);
}
if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,
sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH))
return (1);
return (s(sp,
cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->re_c, 0));
}
#define NEEDNEWLINE(sp) { \
if ((sp)->newl_len == (sp)->newl_cnt) { \
(sp)->newl_len += 25; \
REALLOCARRAY((sp), (sp)->newl, \
(sp)->newl_len, sizeof(size_t)); \
if ((sp)->newl == NULL) { \
(sp)->newl_len = 0; \
return (1); \
} \
} \
}
#define BUILD(sp, l, len) { \
if (lbclen + (len) > lblen) { \
lblen += MAXIMUM(lbclen + (len), 256); \
REALLOC((sp), lb, lblen); \
if (lb == NULL) { \
lbclen = 0; \
return (1); \
} \
} \
memcpy(lb + lbclen, (l), (len)); \
lbclen += (len); \
}
#define NEEDSP(sp, len, pnt) { \
if (lbclen + (len) > lblen) { \
lblen += MAXIMUM(lbclen + (len), 256); \
REALLOC((sp), lb, lblen); \
if (lb == NULL) { \
lbclen = 0; \
return (1); \
} \
(pnt) = lb + lbclen; \
} \
}
static int
s(SCR *sp, EXCMD *cmdp, char *s, regex_t *re, u_int flags)
{
EVENT ev;
MARK from, to;
TEXTH tiq;
recno_t elno, lno, slno;
regmatch_t match[10];
size_t blen, cnt, last, lbclen, lblen, len, llen;
size_t offset, saved_offset, scno;
int lflag, nflag, pflag, rflag;
int didsub, do_eol_match, eflags, nempty, eval;
int linechanged, matched, quit, rval;
unsigned long ul;
char *bp, *lb;
NEEDFILE(sp, cmdp);
slno = sp->lno;
scno = sp->cno;
if (!O_ISSET(sp, O_EDCOMPATIBLE))
sp->c_suffix = sp->g_suffix = 0;
lflag = nflag = pflag = rflag = 0;
if (s == NULL)
goto noargs;
for (lno = OOBLNO; *s != '\0'; ++s)
switch (*s) {
case ' ':
case '\t':
continue;
case '+':
++cmdp->flagoff;
break;
case '-':
--cmdp->flagoff;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (lno != OOBLNO)
goto usage;
errno = 0;
if ((ul = strtoul(s, &s, 10)) >= UINT_MAX)
errno = ERANGE;
if (*s == '\0')
--s;
if (errno == ERANGE) {
if (ul >= UINT_MAX)
msgq(sp, M_ERR, "Count overflow");
else
msgq(sp, M_SYSERR, NULL);
return (1);
}
lno = (recno_t)ul;
cmdp->addr1.lno = cmdp->addr2.lno;
cmdp->addr2.lno += lno - 1;
if (!db_exist(sp, cmdp->addr2.lno) &&
db_last(sp, &cmdp->addr2.lno))
return (1);
break;
case '#':
nflag = 1;
break;
case 'c':
sp->c_suffix = !sp->c_suffix;
if (F_ISSET(sp, SC_EX)) {
memset(&tiq, 0, sizeof(TEXTH));
TAILQ_INIT(&tiq);
}
break;
case 'g':
sp->g_suffix = !sp->g_suffix;
break;
case 'l':
lflag = 1;
break;
case 'p':
pflag = 1;
break;
case 'r':
if (LF_ISSET(SUB_FIRST)) {
msgq(sp, M_ERR,
"Regular expression specified; r flag meaningless");
return (1);
}
if (!F_ISSET(sp, SC_RE_SEARCH)) {
ex_emsg(sp, NULL, EXM_NOPREVRE);
return (1);
}
rflag = 1;
re = &sp->re_c;
break;
default:
goto usage;
}
if (*s != '\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) {
usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
return (1);
}
noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) {
msgq(sp, M_ERR,
"The #, l and p flags may not be combined with the c flag in vi mode");
return (1);
}
bp = lb = NULL;
blen = lbclen = lblen = 0;
for (matched = quit = 0, lno = cmdp->addr1.lno,
elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) {
if (INTERRUPTED(sp))
break;
if (db_get(sp, lno, DBG_FATAL, &s, &llen))
goto err;
if (sp->c_suffix) {
if (bp == NULL) {
GET_SPACE_RET(sp, bp, blen, llen);
} else
ADD_SPACE_RET(sp, bp, blen, llen);
memcpy(bp, s, llen);
s = bp;
}
offset = 0;
len = llen;
lbclen = 0;
nempty = -1;
didsub = linechanged = 0;
do_eol_match = 1;
eflags = REG_STARTEND;
nextmatch: match[0].rm_so = offset;
match[0].rm_eo = llen;
eval = regexec(re, (char *)s, 10, match, eflags);
if (eval == REG_NOMATCH)
goto endmatch;
if (eval != 0) {
re_error(sp, eval, re);
goto err;
}
matched = 1;
eflags |= REG_NOTBOL;
if (match[0].rm_so == nempty && match[0].rm_eo == nempty) {
nempty = -1;
if (len == 0)
goto endmatch;
BUILD(sp, s + offset, 1)
++offset;
--len;
goto nextmatch;
}
if (sp->c_suffix) {
from.lno = to.lno = lno;
from.cno = match[0].rm_so;
to.cno = match[0].rm_eo;
if (llen == 0)
from.cno = to.cno = 0;
if (F_ISSET(sp, SC_VI)) {
if (to.cno >= llen)
to.cno = llen - 1;
if (from.cno >= llen)
from.cno = llen - 1;
sp->lno = from.lno;
sp->cno = from.cno;
if (vs_refresh(sp, 1))
goto err;
vs_update(sp, "Confirm change? [n]", NULL);
if (v_event_get(sp, &ev, 0, 0))
goto err;
switch (ev.e_event) {
case E_CHARACTER:
break;
case E_EOF:
case E_ERR:
case E_INTERRUPT:
goto lquit;
default:
v_event_err(sp, &ev);
goto lquit;
}
} else {
const int flags =
O_ISSET(sp, O_NUMBER) ? E_C_HASH : 0;
if (ex_print(sp, cmdp, &from, &to, flags) ||
ex_scprint(sp, &from, &to))
goto lquit;
if (ex_txt(sp, &tiq, 0, TXT_CR))
goto err;
ev.e_c = TAILQ_FIRST(&tiq)->lb[0];
}
switch (ev.e_c) {
case CH_YES:
break;
default:
case CH_NO:
didsub = 0;
BUILD(sp, s + offset, match[0].rm_eo - offset);
goto skip;
case CH_QUIT:
lquit: quit = 1;
F_SET(sp->gp, G_INTERRUPTED);
goto endmatch;
}
}
sp->lno = lno;
sp->cno = match[0].rm_so;
BUILD(sp, s + offset, match[0].rm_so - offset);
didsub = 1;
if (re_sub(sp, s, &lb, &lbclen, &lblen, match))
goto err;
linechanged = 1;
skip: offset = match[0].rm_eo;
len = llen - match[0].rm_eo;
nempty = match[0].rm_eo;
if (didsub && sp->c_suffix && sp->g_suffix) {
saved_offset = lbclen;
if (len)
BUILD(sp, s + offset, len)
offset = saved_offset;
last = 0;
if (sp->newl_cnt) {
for (cnt = 0;
cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) {
if (db_insert(sp, lno,
lb + last, sp->newl[cnt] - last))
goto err;
last = sp->newl[cnt] + 1;
++sp->rptlines[L_ADDED];
}
lbclen -= last;
offset -= last;
sp->newl_cnt = 0;
}
if (db_set(sp, lno, lb + last, lbclen))
goto err;
if (db_get(sp, lno, DBG_FATAL, &s, &llen))
goto err;
ADD_SPACE_RET(sp, bp, blen, llen)
memcpy(bp, s, llen);
s = bp;
len = llen - offset;
lbclen = 0;
BUILD(sp, s, offset);
if (!do_eol_match)
goto endmatch;
if (offset == len) {
do_eol_match = 0;
eflags |= REG_NOTEOL;
}
goto nextmatch;
}
if (sp->g_suffix && do_eol_match) {
if (len == 0) {
do_eol_match = 0;
eflags |= REG_NOTEOL;
}
goto nextmatch;
}
endmatch: if (!linechanged)
continue;
if (len)
BUILD(sp, s + offset, len)
last = 0;
if (sp->newl_cnt) {
for (cnt = 0;
cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) {
if (db_insert(sp,
lno, lb + last, sp->newl[cnt] - last))
goto err;
last = sp->newl[cnt] + 1;
++sp->rptlines[L_ADDED];
}
lbclen -= last;
sp->newl_cnt = 0;
}
if (db_set(sp, lno, lb + last, lbclen))
goto err;
if (sp->rptlchange != lno) {
sp->rptlchange = lno;
++sp->rptlines[L_CHANGED];
}
if (lflag || nflag || pflag) {
from.lno = to.lno = lno;
from.cno = to.cno = 0;
if (lflag)
(void)ex_print(sp, cmdp, &from, &to, E_C_LIST);
if (nflag)
(void)ex_print(sp, cmdp, &from, &to, E_C_HASH);
if (pflag)
(void)ex_print(sp, cmdp, &from, &to, E_C_PRINT);
}
}
if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) {
sp->cno = 0;
(void)nonblank(sp, sp->lno, &sp->cno);
}
rval = 0;
if (!matched) {
if (!F_ISSET(sp, SC_EX_GLOBAL)) {
msgq(sp, M_ERR, "No match found");
goto err;
}
} else if (!lflag && !nflag && !pflag)
F_SET(cmdp, E_AUTOPRINT);
if (0) {
err: rval = 1;
}
if (bp != NULL)
FREE_SPACE(sp, bp, blen);
free(lb);
return (rval);
}
int
re_compile(SCR *sp, char *ptrn, size_t plen, char **ptrnp, size_t *lenp,
regex_t *rep, u_int flags)
{
size_t len;
int reflags, replaced, rval;
char *p;
reflags = 0;
if (!LF_ISSET(RE_C_TAG)) {
if (O_ISSET(sp, O_EXTENDED))
reflags |= REG_EXTENDED;
if (O_ISSET(sp, O_IGNORECASE))
reflags |= REG_ICASE;
if (O_ISSET(sp, O_ICLOWER)) {
for (p = ptrn, len = plen; len > 0; ++p, --len)
if (isupper(*p))
break;
if (len == 0)
reflags |= REG_ICASE;
}
}
if (LF_ISSET(RE_C_SEARCH) && F_ISSET(sp, SC_RE_SEARCH)) {
regfree(&sp->re_c);
F_CLR(sp, SC_RE_SEARCH);
}
if (LF_ISSET(RE_C_SUBST) && F_ISSET(sp, SC_RE_SUBST)) {
regfree(&sp->subre_c);
F_CLR(sp, SC_RE_SUBST);
}
if (ptrnp != NULL) {
if (LF_ISSET(RE_C_TAG)) {
if (re_tag_conv(sp, &ptrn, &plen, &replaced))
return (1);
} else
if (re_conv(sp, &ptrn, &plen, &replaced))
return (1);
free(*ptrnp);
*ptrnp = NULL;
if (lenp != NULL)
*lenp = plen;
MALLOC(sp, *ptrnp, plen + 1);
if (*ptrnp != NULL) {
memcpy(*ptrnp, ptrn, plen);
(*ptrnp)[plen] = '\0';
}
if (replaced)
FREE_SPACE(sp, ptrn, 0);
if (*ptrnp == NULL)
return (1);
ptrn = *ptrnp;
}
if ((rval = regcomp(rep, ptrn, reflags)) != 0) {
if (!LF_ISSET(RE_C_SILENT))
re_error(sp, rval, rep);
return (1);
}
if (LF_ISSET(RE_C_SEARCH))
F_SET(sp, SC_RE_SEARCH);
if (LF_ISSET(RE_C_SUBST))
F_SET(sp, SC_RE_SUBST);
return (0);
}
static int
re_conv(SCR *sp, char **ptrnp, size_t *plenp, int *replacedp)
{
size_t blen, len, needlen;
int magic;
char *bp, *p, *t;
magic = 0;
for (p = *ptrnp, len = *plenp, needlen = 0; len > 0; ++p, --len)
switch (*p) {
case '\\':
if (len > 1) {
--len;
switch (*++p) {
case '~':
if (!O_ISSET(sp, O_MAGIC)) {
magic = 1;
needlen += sp->repl_len;
}
break;
case '.':
case '[':
case '*':
if (!O_ISSET(sp, O_MAGIC)) {
magic = 1;
needlen += 1;
}
break;
default:
needlen += 2;
}
} else
needlen += 1;
break;
case '~':
if (O_ISSET(sp, O_MAGIC)) {
magic = 1;
needlen += sp->repl_len;
}
break;
case '.':
case '[':
case '*':
if (!O_ISSET(sp, O_MAGIC)) {
magic = 1;
needlen += 2;
}
break;
default:
needlen += 1;
break;
}
if (!magic) {
*replacedp = 0;
return (0);
}
*replacedp = 1;
GET_SPACE_RET(sp, bp, blen, needlen);
for (p = *ptrnp, len = *plenp, t = bp; len > 0; ++p, --len)
switch (*p) {
case '\\':
if (len > 1) {
--len;
switch (*++p) {
case '~':
if (O_ISSET(sp, O_MAGIC))
*t++ = '~';
else {
memcpy(t,
sp->repl, sp->repl_len);
t += sp->repl_len;
}
break;
case '.':
case '[':
case '*':
if (O_ISSET(sp, O_MAGIC))
*t++ = '\\';
*t++ = *p;
break;
default:
*t++ = '\\';
*t++ = *p;
}
} else
*t++ = '\\';
break;
case '~':
if (O_ISSET(sp, O_MAGIC)) {
memcpy(t, sp->repl, sp->repl_len);
t += sp->repl_len;
} else
*t++ = '~';
break;
case '.':
case '[':
case '*':
if (!O_ISSET(sp, O_MAGIC))
*t++ = '\\';
*t++ = *p;
break;
default:
*t++ = *p;
break;
}
*ptrnp = bp;
*plenp = t - bp;
return (0);
}
static int
re_tag_conv(SCR *sp, char **ptrnp, size_t *plenp, int *replacedp)
{
size_t blen, len;
int lastdollar;
char *bp, *p, *t;
len = *plenp;
*replacedp = 1;
GET_SPACE_RET(sp, bp, blen, len * 2);
p = *ptrnp;
t = bp;
if (len > 0 && (p[len - 1] == '/' || p[len - 1] == '?'))
--len;
if (len > 0 && p[len - 1] == '$') {
--len;
lastdollar = 1;
} else
lastdollar = 0;
if (len > 0 && (p[0] == '/' || p[0] == '?')) {
++p;
--len;
}
if (p[0] == '^') {
*t++ = *p++;
--len;
}
for (; len > 0; --len) {
if (p[0] == '\\' && (p[1] == '/' || p[1] == '?')) {
++p;
--len;
} else if (strchr("^.[]$*", p[0]))
*t++ = '\\';
*t++ = *p++;
if (len == 0)
break;
}
if (lastdollar)
*t++ = '$';
*ptrnp = bp;
*plenp = t - bp;
return (0);
}
void
re_error(SCR *sp, int errcode, regex_t *preg)
{
size_t s;
char *oe;
s = regerror(errcode, preg, "", 0);
if ((oe = malloc(s)) == NULL)
msgq(sp, M_SYSERR, NULL);
else {
(void)regerror(errcode, preg, oe, s);
msgq(sp, M_ERR, "RE error: %s", oe);
free(oe);
}
}
static int
re_sub(SCR *sp, char *ip, char **lbp, size_t *lbclenp, size_t *lblenp,
regmatch_t match[10])
{
enum { C_NOTSET, C_LOWER, C_ONELOWER, C_ONEUPPER, C_UPPER } conv;
size_t lbclen, lblen;
size_t mlen;
size_t rpl;
char *rp;
int ch;
int no;
char *p, *t;
char *lb;
lb = *lbp;
lbclen = *lbclenp;
lblen = *lblenp;
#define OUTCH(ch, nltrans) { \
CHAR_T __ch = (ch); \
u_int __value = KEY_VAL(sp, __ch); \
if ((nltrans) && (__value == K_CR || __value == K_NL)) { \
NEEDNEWLINE(sp); \
sp->newl[sp->newl_cnt++] = lbclen; \
} else if (conv != C_NOTSET) { \
switch (conv) { \
case C_ONELOWER: \
conv = C_NOTSET; \
\
case C_LOWER: \
if (isupper(__ch)) \
__ch = tolower(__ch); \
break; \
case C_ONEUPPER: \
conv = C_NOTSET; \
\
case C_UPPER: \
if (islower(__ch)) \
__ch = toupper(__ch); \
break; \
default: \
abort(); \
} \
} \
NEEDSP(sp, 1, p); \
*p++ = __ch; \
++lbclen; \
}
conv = C_NOTSET;
for (rp = sp->repl, rpl = sp->repl_len, p = lb + lbclen; rpl--;) {
switch (ch = *rp++) {
case '&':
if (O_ISSET(sp, O_MAGIC)) {
no = 0;
goto subzero;
}
break;
case '\\':
if (rpl == 0)
break;
--rpl;
switch (ch = *rp) {
case '&':
++rp;
if (!O_ISSET(sp, O_MAGIC)) {
no = 0;
goto subzero;
}
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
no = *rp++ - '0';
subzero: if (match[no].rm_so == -1 ||
match[no].rm_eo == -1)
break;
mlen = match[no].rm_eo - match[no].rm_so;
for (t = ip + match[no].rm_so; mlen--; ++t)
OUTCH(*t, 0);
continue;
case 'e':
case 'E':
++rp;
conv = C_NOTSET;
continue;
case 'l':
++rp;
conv = C_ONELOWER;
continue;
case 'L':
++rp;
conv = C_LOWER;
continue;
case 'u':
++rp;
conv = C_ONEUPPER;
continue;
case 'U':
++rp;
conv = C_UPPER;
continue;
default:
++rp;
break;
}
}
OUTCH(ch, 1);
}
*lbp = lb;
*lbclenp = lbclen;
*lblenp = lblen;
return (0);
}