214 lines
6.3 KiB
C
214 lines
6.3 KiB
C
/*****************************************************************************
|
|
*
|
|
* string.c
|
|
*
|
|
* String builtin macros.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include "m4.h"
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* opSubstr
|
|
*
|
|
* Return the substring of $1 starting from $2 and continuing for
|
|
* $3 characters. If $3 is not supplied, then return the entire
|
|
* remainder of the string.
|
|
*
|
|
* If $2 is out of range, then nothing is returned.
|
|
*
|
|
* If $3 is a negative number, then treat it as zero.
|
|
*
|
|
* The extra ptokNil covers us in the case where $# is 1.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
DeclareOp(opSubstr)
|
|
{
|
|
if (ctokArgv) {
|
|
TOK tok;
|
|
ITCH itch = (ITCH)atTraditionalPtok(ptokArgv(2));
|
|
if (itch < ctchSPtok(ptokArgv(1))) {
|
|
CTCH ctch;
|
|
if (ctokArgv >= 3) {
|
|
ctch = atTraditionalPtok(ptokArgv(3));
|
|
if ((int)ctch < 0) {
|
|
ctch = 0;
|
|
}
|
|
} else {
|
|
ctch = ctchMax;
|
|
}
|
|
ctch = min(ctch, ctchSPtok(ptokArgv(1)) - itch);
|
|
Assert(itch + ctch <= ctchSPtok(ptokArgv(1)));
|
|
SetStaticPtokPtchCtch(&tok, ptchPtok(ptokArgv(1)) + itch, ctch);
|
|
PushPtok(&tok);
|
|
}
|
|
} else {
|
|
#ifdef STRICT_M4
|
|
Warn("wrong number of arguments to %P", ptokArgv(0));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* opIndex
|
|
*
|
|
* Return the zero-based location of the first occurrence of $2 in $1,
|
|
* or -1 if the substring does not appear. If there are multiple
|
|
* matches, the leftmost one is returned.
|
|
*
|
|
* The extra ptokNil covers us in the case where $# is 1.
|
|
*
|
|
* QUIRK! AT&T returns -1 if $1 and $2 are both null strings.
|
|
* GNU returns 0, which is what I do also.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/* SOMEDAY! -- need minimum and maximum arg count */
|
|
|
|
DeclareOp(opIndex)
|
|
{
|
|
if (ctokArgv) {
|
|
/*
|
|
* Note carefully: itch and itchMac need to be ints
|
|
* because itchMac can underflow if you try to search
|
|
* for a big string inside a small string.
|
|
*/
|
|
int itch;
|
|
int itchMac = ctchSPtok(ptokArgv(1)) - ctchSPtok(ptokArgv(2));
|
|
for (itch = 0; itch <= itchMac; itch++) {
|
|
if (fEqPtchPtchCtch(ptchPtok(ptokArgv(1)) + itch,
|
|
ptchPtok(ptokArgv(2)),
|
|
ctchSPtok(ptokArgv(2)))) {
|
|
PushAt(itch);
|
|
return;
|
|
}
|
|
}
|
|
PushAt(-1);
|
|
} else {
|
|
PushQuotedPtok(ptokArgv(0));
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* opTranslit
|
|
*
|
|
* For each character in the $1, look for a match in $2. If found,
|
|
* produce the corresponding character from $3. If there is no
|
|
* such character, then produce nothing.
|
|
*
|
|
* Note that the algorithm must be as specified, in order for
|
|
*
|
|
* translit(abc,ab,ba)
|
|
*
|
|
* to result in `bac'.
|
|
*
|
|
* We actually walk $1 backwards so we can push directly instead
|
|
* of having to build a temporary token. But the walking of $2
|
|
* must be in the forward direction, so that `translit(a,aa,bc)'
|
|
* results in `b' and not `c'.
|
|
*
|
|
* ptokNil saves us in the case where $# = 1.
|
|
*
|
|
* QUIRK! If given only one argument, AT&T emits $1 unchanged.
|
|
* GNU emits nothing! AT&T is obviously correct, so I side
|
|
* with them on this one.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
DeclareOp(opTranslit)
|
|
{
|
|
if (ctokArgv) {
|
|
ITCH itch1 = ctchArgv(1);
|
|
while ((int)--itch1 >= 0) {
|
|
TCH tch = ptchArgv(1)[itch1];
|
|
ITCH itch2;
|
|
for (itch2 = 0; itch2 < ctchArgv(2); itch2++) {
|
|
if (ptchArgv(2)[itch2] == tch) {
|
|
if (itch2 < ctchArgv(3)) {
|
|
PushTch(ptchArgv(3)[itch2]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (itch2 >= ctchArgv(2)) {
|
|
PushTch(tch);
|
|
}
|
|
}
|
|
} else {
|
|
PushQuotedPtok(ptokArgv(0));
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* opPatsubst
|
|
*
|
|
* Scan $1 for any occurrences of $2. If found, replace them with $3.
|
|
* If $3 is omitted, then the string is deleted.
|
|
*
|
|
* As a special case, if $2 is the null string, then $3 is inserted
|
|
* at the beginning of the string and between each character of $1.
|
|
*
|
|
* NOTE! This is a GNU extension.
|
|
*
|
|
* NOTE! GNU supports regular expressions for $2. We support only
|
|
* literal strings.
|
|
*
|
|
* NOTE! Scanning is required to be forwards, so we temporarily expand
|
|
* into the Exp hold, then pop it off when we're done.
|
|
*
|
|
* QUIRK! If given only one argument, GNU emits nothing!
|
|
* This is clearly wrong, so I emit $1.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
DeclareOp(opPatsubst)
|
|
{
|
|
if (ctokArgv) {
|
|
CTCH ctchSrc = ctchArgv(1);
|
|
PTCH ptchSrc = ptchArgv(1);
|
|
|
|
CTCH ctchPat = ctchArgv(2); /* ptokNil saves us here */
|
|
PTCH ptchPat = ptchArgv(2);
|
|
|
|
TOK tok;
|
|
OpenExpPtok(&tok);
|
|
|
|
while (ctchSrc >= ctchPat) {
|
|
if (fEqPtchPtchCtch(ptchPat, ptchSrc, ctchPat)) {
|
|
if (ctokArgv >= 3) {
|
|
AddExpPtok(ptokArgv(3));
|
|
}
|
|
if (ctchSrc == 0) {
|
|
AddExpTch(*ptchSrc);
|
|
ctchSrc--;
|
|
ptchSrc++;
|
|
} else {
|
|
ctchSrc -= ctchPat;
|
|
ptchSrc += ctchPat;
|
|
}
|
|
} else {
|
|
AddExpTch(*ptchSrc);
|
|
ctchSrc--;
|
|
ptchSrc++;
|
|
}
|
|
}
|
|
|
|
/* Flush out what's left of the string */
|
|
while (ctchSrc) {
|
|
AddExpTch(*ptchSrc);
|
|
ctchSrc--;
|
|
ptchSrc++;
|
|
}
|
|
|
|
CsopExpDopPdivPtok((DIVOP)PushZPtok, 0, &tok);
|
|
|
|
} else {
|
|
PushQuotedPtok(ptokArgv(0));
|
|
}
|
|
}
|