/* sml3_regex.c: regulaere Ausdruecke */

/* Copyright 2012-2017 Kurt Nienhaus
 *
 * This file is part of libsammel3.
 * libsammel3 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.
 * libsammel3 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with libsammel3.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#include <regex.h>
#include "config.h"
#include "sml3_fehler.h"
#include "sml3_util.h"
#include "sml3_regex.h"

char * SML3_rexquot(const char *);
struct SML3_rexpatt * SML3_rexcomp(const char *, int);
void SML3_rexcompfree(struct SML3_rexpatt *);
char * SML3_rexsuche(const char *, char **, const void *, int, struct SML3_rexsub *);
char * SML3_rexstr(const char *, char **, const void *, int, size_t *);
struct SML3_rexrep SML3_rexmakerep(const void *, int, void *, char * (*)(struct SML3_rexsub *, void *), void *);
char * SML3_rexersetze(const char *, ...);

static int estr_add(char **, size_t *, size_t *, size_t, const char *, const char *);
static int estr_repl(char **, size_t *, size_t *, size_t, struct SML3_rexrep *, struct SML3_rexsub *);


/* estr_add [thread-sicher]:
 * String zu 1.Arg hinzufuegen
 * 1.Arg: Adresse auf String, an den hinzugefuegt wird
 * 2.Arg: Adresse auf Allokationsgroesse 1.Arg
 * 3.Arg: Adresse auf Startposition 1.Arg
 * 4.Arg: Blockgroesse fuer Allokation
 * 5.+6.Arg: hinzuzufuegender String (von - bis-exkl.)
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: ENOMEM = Allokationsfehler
 */
static int
estr_add(char **estr, size_t *eallo, size_t *epos, size_t block, const char *panf, const char *pend)
{
  size_t slen, sallo;

  if (panf == NULL) { return 0; }
  if (pend == NULL) { pend = panf + strlen(panf); }
  if (pend <= panf) { return 0; }

  slen = pend - panf;
  sallo = *epos + slen + 1;
  sallo += block - (sallo % block);
  if (sallo > *eallo) {
    *estr = realloc(*estr, sallo);
    if (*estr == NULL) { SML3_fehlernew(ENOMEM, "realloc: %s", SML3_strerror(errno)); return -1; }
    *eallo = sallo;
  }
  memmove(*estr + *epos, panf, slen);
  (*estr)[*epos + slen] = '\0';
  *epos += slen;

  return 0;
} /* Ende estr_add */


/* estr_repl [Ersetzestring statt -funktion: thread-sicher]:
 * Daten aus Ersetzestring bzw. -funktion zu 1.Arg hinzufuegen
 * 1.Arg: Adresse auf String, an den hinzugefuegt wird
 * 2.Arg: Adresse auf Allokationsgroesse 1.Arg
 * 3.Arg: Adresse auf Startposition 1.Arg
 * 4.Arg: Blockgroesse
 * 5.Arg: Adresse auf Unterausdruecke
 * 6.Arg: Adresse auf struct fuer Suche + Ersetzen
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: ENOMEM = Allokationsfehler
 *                  weitere aus Ersetzefunktion
 */
static int
estr_repl(char **estr, size_t *eallo, size_t *epos, size_t block, struct SML3_rexrep *reps, struct SML3_rexsub *subpatt)
{
  size_t slen;
  char *rets;

  if (reps->sflag & SML3_REXFLAG_FUNK) {  /* Ersetzefunktion */
    char * (*ersfk)(struct SML3_rexsub *, void *) = reps->e.replfunk;
    rets = ersfk(subpatt, reps->param);
    if (rets == NULL) { SML3_fehleradd(NULL); return -1; }
    slen = strlen(rets);
    if (slen > 0 && estr_add(estr, eallo, epos, block, rets, rets + slen) < 0) {
      SML3_fehleradd(NULL);
      free(rets);
      return -1;
    }
    free(rets);

  } else if (reps->e.replace != NULL) {  /* Ersetzestring */
    char *ptr;
    int snr;
    rets = (char *)reps->e.replace;
    while ((ptr = strchr(rets, '\\')) != NULL) {  /* Klammerreferenz suchen: "\"<1-2 Ziffern> */
      /* vorige Zeichen (rets bis ptr-exkl.) uebertragen */
      slen = (size_t)(ptr - rets);
      if (slen > 0 && estr_add(estr, eallo, epos, block, rets, rets + slen) < 0) {
        SML3_fehleradd(NULL);
        return -1;
      }
      /* Klammerreferenznummer ermitteln */
      rets = ptr + 1;
      if (rets[0] < '0' || rets[0] > '9') {  /* keine Klammerreferenz */
        if (estr_add(estr, eallo, epos, block, rets - 1, rets) < 0) {
          SML3_fehleradd(NULL);
          return -1;
        }
        continue;
      }
      snr = rets[0] - '0';
      if (rets[1] >= '0' && rets[1] <= '9') {  /* zweistellige Klammerreferenz */
        snr *= 10;
        snr += rets[1] - '0';
        rets++;
      }
      if (snr > 0 && snr <= subpatt->anzsub) {  /* Klammerreferenz vorhanden */
        const char *ptvon, *ptbis;
        ptvon = subpatt->sptr + subpatt->sub[snr - 1].von;
        ptbis = subpatt->sptr + subpatt->sub[snr - 1].bis;
        if (ptbis > ptvon && estr_add(estr, eallo, epos, block, ptvon, ptbis) < 0) {
          SML3_fehleradd(NULL);
          return -1;
        }
      }
      rets++;
    }
    /* restliche Zeichen uebertragen */
    slen = strlen(rets);
    if (slen > 0 && estr_add(estr, eallo, epos, block, rets, rets + slen) < 0) {
      SML3_fehleradd(NULL);
      return -1;
    }
  }

  return 0;
} /* Ende estr_repl */


/* SML3_rexquot [thread-sicher]:
 * quotet alle Metazeichen im Suchpattern
 * 1.Arg: Suchpattern
 * Rueckgabe: alloziertes gequotetes Suchpattern
 */
char *
SML3_rexquot(const char *spatt)
{
  char *qpatt;
  size_t qsize, slen;
  const char *svon, *sbis;

  qpatt = SML3_malloc(sizeof(char));
  qsize = 1;
  qpatt[qsize - 1] = '\0';

  if (spatt == NULL || *spatt == '\0') { return qpatt; }

  for (svon = sbis = spatt; *sbis != '\0'; sbis++) {
    if (strchr("^.[$()|*+?{\\", (int)*sbis) != NULL) {
      slen = (size_t)(sbis - svon);
      qpatt = SML3_realloc(qpatt, sizeof(char) * (qsize + slen + 2));
      if (slen > 0) { memmove(qpatt + qsize - 1, svon, slen); qsize += slen; }
      qpatt[qsize - 1] = '\\';
      qsize++;
      qpatt[qsize - 1] = *sbis;
      qsize++;
      qpatt[qsize - 1] = '\0';
      svon = sbis + 1;
    }
  }
  slen = (size_t)(sbis - svon);
  if (slen > 0) {
    qpatt = SML3_realloc(qpatt, sizeof(char) * (qsize + slen));
    memmove(qpatt + qsize - 1, svon, slen);
    qsize += slen;
    qpatt[qsize - 1] = '\0';
  }

  return qpatt;
} /* Ende SML3_rexquot */


/* SML3_rexcomp [thread-sicher]:
 * compiliert Suchpattern
 * 1.Arg: Suchpattern
 * 2.Arg: Suchpatternflag geodert (SML3_REXFLAG_?)
 * Rueckgabe: alloziertes compiliertes Suchpattern
 *            oder NULL = Fehler
 * SML3-errno-Wert: EINVAL  = Fehler Uebergabeparameter
 *                  ENOMEM  = Allokationsfehler
 *                  ENOEXEC = Compilierungsfehler
 */
struct SML3_rexpatt *
SML3_rexcomp(const char *spatt, int sflag)
{
  struct SML3_rexpatt *rpatt;
  char *qpatt;

  if (spatt == NULL || *spatt == '\0') {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    return NULL;
  }

  rpatt = malloc(sizeof(*rpatt));
  if (rpatt == NULL) { SML3_fehlernew(ENOMEM, "malloc: %s", SML3_strerror(errno)); return NULL; }
  rpatt->cpatt = malloc(sizeof(regex_t));
  if (rpatt->cpatt == NULL) { SML3_fehlernew(ENOMEM, "malloc: %s", SML3_strerror(errno)); free(rpatt); return NULL; }

  rpatt->sflag = sflag & (SML3_REXFLAG_Q | SML3_REXFLAG_I | SML3_REXFLAG_N);

  qpatt = NULL;
  if (rpatt->sflag & SML3_REXFLAG_Q) {
    qpatt = SML3_rexquot(spatt);
  }

  sflag = REG_EXTENDED;
  if (rpatt->sflag & SML3_REXFLAG_I) { sflag |= REG_ICASE; }
  if (rpatt->sflag & SML3_REXFLAG_N) { sflag |= REG_NEWLINE; }

  sflag = regcomp((regex_t *)rpatt->cpatt, qpatt == NULL ? spatt : qpatt, sflag);
  if (qpatt != NULL) { free(qpatt); }
  if (sflag != 0) {
    char berr[512];
    regerror(sflag, (regex_t *)rpatt->cpatt, berr, sizeof(berr));
    SML3_fehlernew(ENOEXEC, "regcomp: %s", berr);
    free(rpatt->cpatt);
    free(rpatt);
    return NULL;
  }

  return rpatt;
} /* Ende SML3_rexcomp */


/* SML3_rexcompfree [thread-sicher]:
 * dealloziert compiliertes Suchpattern
 * 1.Arg: compiliertes Suchpattern
 */
void
SML3_rexcompfree(struct SML3_rexpatt *rpatt)
{
  if (rpatt == NULL) { return; }
  if (rpatt->cpatt != NULL) { regfree(rpatt->cpatt); free(rpatt->cpatt); }
  free(rpatt);
} /* Ende SML3_rexcompfree */


/* SML3_rexsuche [thread-sicher]:
 * sucht nach erstem bzw. letztem Vorkommen eines reg. Ausdrucks
 * 1.Arg: zu durchsuchender String
 * 2.Arg: fuer Rueckgabe Startpointer im 1.Arg fuer weitere Suche
 *        (darf Adresse auf 1.Arg sein)
 *        oder NULL
 * 3.Arg: Suchpattern (als String oder schon compiliert)
 * 4.Arg: Suchpatternflag geodert (SML3_REXFLAG_*)
 *         - als String: SML3_REXFLAG_Q, SML3_REXFLAG_I, SML3_REXFLAG_N
 *         - schon compiliert: SML3_REXFLAG_COMP
 *         - ausserdem: SML3_REXFLAG_GLOBAL = letzten Treffer finden
 * 5.Arg: Adresse fuer Rueckgabe Unterausdruecke oder NULL
 * Rueckgabe: Pointer auf Beginn gefundenen Treffer
 *            (Achtung: falls gleich 2.Arg -> Endlosschleife)
 *            oder NULL = kein Treffer (SML3-errno-Wert = 0)
 *                        bzw. Fehler (SML3-errno-Wert != 0)
 * SML3-errno-Wert: 0       = kein Treffer
 *                  EINVAL  = Fehler Uebergabeparameter
 *                  ENOMEM  = Allokationsfehler
 *                  ENOEXEC = Compilierungsfehler
 *
 * Der Rueckgabe-Pointer ist gleich ((5.Arg)->sptr + (5.Arg)->von)
 * Der Rueckgabe-Startpointer des 2.Arg ist gleich ((5.Arg)->sptr + (5.Arg)->bis)
 */
char *
SML3_rexsuche(const char *string, char **nstr, const void *vpatt, int sflag, struct SML3_rexsub *subpatt)
{
  const char *stringvorne;
  struct SML3_rexpatt *rpatt;
  regmatch_t rmat[1 + SML3_REXSUBMAX], rmrk[1 + SML3_REXSUBMAX];
  size_t strtpos;
  char *tstr;

  if (nstr == NULL) { nstr = &tstr; }
  if (subpatt != NULL) { memset(subpatt, 0, sizeof(*subpatt)); }
  if (string == NULL) { SML3_fehlernew(0, NULL); *nstr = NULL; return NULL; }
  if (vpatt == NULL) {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    *nstr = NULL;
    return NULL;
  }

  /* compilieren */
  if (!(sflag & SML3_REXFLAG_COMP)) {
    rpatt = SML3_rexcomp((const char *)vpatt, sflag);
    if (rpatt == NULL) { SML3_fehleradd(NULL); *nstr = NULL; return NULL; }
  } else {
    rpatt = (struct SML3_rexpatt *)vpatt;
  }

  memset(rmrk, 0, sizeof(rmrk));
  rmrk[0].rm_so = -1;
  stringvorne = string;
  strtpos = 0;

  while (regexec(rpatt->cpatt, string, 1 + SML3_REXSUBMAX, rmat, 0) == 0) {
    memmove(rmrk, rmat, sizeof(rmat));
    strtpos = string - stringvorne;
    string += (size_t)rmat[0].rm_eo;
    if (rpatt->sflag & SML3_REXFLAG_N && *string == '\n') { string++; }
    if (*string == '\0') { break; }
    if (!(sflag & SML3_REXFLAG_GLOBAL)) { break; }
    if (rmat[0].rm_eo == 0) { string++; }  /* Endlosschleife verhindern */
  }
  if (!(sflag & SML3_REXFLAG_COMP)) { SML3_rexcompfree(rpatt); }

  *nstr = (char *)string;
  if (rmrk[0].rm_so == -1) { SML3_fehlernew(0, NULL); return NULL; }

  if (subpatt != NULL) {
    int i1;
    subpatt->sptr = stringvorne;
    subpatt->von = strtpos + rmrk[0].rm_so;
    subpatt->bis = strtpos + rmrk[0].rm_eo;
    for (i1 = 1; i1 <= SML3_REXSUBMAX; i1++) {
      if (rmrk[i1].rm_so >= 0) { subpatt->anzsub = i1; }
    }
    for (i1 = 0; i1 < subpatt->anzsub; i1++) {
      if (rmrk[i1 + 1].rm_so >= 0) {
        subpatt->sub[i1].von = strtpos + rmrk[i1 + 1].rm_so;
        subpatt->sub[i1].bis = strtpos + rmrk[i1 + 1].rm_eo;
      } else {  /* Unterausdruck leer */
        subpatt->sub[i1].von = strtpos;
        subpatt->sub[i1].bis = strtpos;
      }
    }
  }

  return (char *)(stringvorne + strtpos + rmrk[0].rm_so);
} /* Ende SML3_rexsuche */


/* SML3_rexstr [thread-sicher]:
 * wie SML3_rexsuche(): sucht nach erstem bzw. letztem Vorkommen eines reg. Ausdrucks
 * 1.Arg: zu durchsuchender String
 * 2.Arg: fuer Rueckgabe Startpointer im 1.Arg fuer weitere Suche
 *        (darf Adresse auf 1.Arg sein)
 *        oder NULL
 * 3.Arg: Suchpattern (als String oder schon compiliert)
 * 4.Arg: Suchpatternflag geodert (SML3_REXFLAG_*)
 *         - als String: SML3_REXFLAG_Q, SML3_REXFLAG_I, SML3_REXFLAG_N
 *         - schon compiliert: SML3_REXFLAG_COMP
 *         - ausserdem: SML3_REXFLAG_GLOBAL = letzten Treffer finden
 * 5.Arg: Adresse fuer Rueckgabe Laenge gefundener Treffer oder NULL
 * Rueckgabe: Pointer auf Beginn gefundenen Treffer
 *            (Achtung: falls gleich 2.Arg -> Endlosschleife)
 *            oder NULL = kein Treffer (SML3-errno-Wert = 0)
 *                        bzw. Fehler (SML3-errno-Wert != 0)
 * SML3-errno-Wert: 0       = kein Treffer
 *                  EINVAL  = Fehler Uebergabeparameter
 *                  ENOMEM  = Allokationsfehler
 *                  ENOEXEC = Compilierungsfehler
 */
char *
SML3_rexstr(const char *string, char **nstr, const void *vpatt, int sflag, size_t *sublen)
{
  char *retp;
  struct SML3_rexsub subpatt;

  if (sublen != NULL) { *sublen = 0; }
  retp = SML3_rexsuche(string, nstr, vpatt, sflag, &subpatt);
  if (retp == NULL) {
    if (SML3_fehlererrno() != 0) { SML3_fehleradd(NULL); }
    return NULL;
  }
  if (sublen != NULL) { *sublen = subpatt.bis - subpatt.von; }
  return retp;
} /* Ende SML3_rexstr */


/* SML3_rexmakerep [reentrant]:
 * erstellt Parameter fuer Suchen + Ersetzen mit SML3_rexersetze()
 * 1.Arg: Suchausdruck (evtl. schon compiliert)
 * 2.Arg: Suchpatternflag geodert (SML3_REXFLAG_*)
 * 3.Arg: Ersetzestring
 * 4.Arg: Ersetzefunktion
 * 5.Arg: 2.Parameter fuer Ersetzefunktion oder NULL
 */
struct SML3_rexrep
SML3_rexmakerep(const void *spatt, int sflag, void *replace, char * (*replfunk)(struct SML3_rexsub *, void *), void *param)
{
  struct SML3_rexrep reps;
  reps.spatt = spatt;
  if ((sflag & SML3_REXFLAG_FUNK) && replfunk != NULL) {
    reps.e.replfunk = replfunk;
  } else {
    reps.e.replace = replace;
    sflag &= ~SML3_REXFLAG_FUNK;
  }
  reps.sflag = sflag;
  reps.param = param;
  return reps;
} /* Ende SML3_rexmakerep */


/* SML3_rexersetze [ohne Ersetzefunktion: thread-sicher]:
 * ersetzt ersten oder alle Vorkommen eines oder mehrerer reg. Ausdruecke
 * 1.Arg: zu durchsuchender String
 * weitere Arg: Parameter fuer Suchen + Ersetzen (struct SML3_rexrep)
 *              uebergeben durch
 *              - SML3_REXREP(P1, P2, P3):
 *                  P1: Suchausdruck (evtl. schon compiliert)
 *                  P2: Suchpatternflag geodert (SML3_REXFLAG_*)
 *                  P3: Ersetzestring
 *              - SML3_REXREPFUNK(P1, P2, P3, P4)
 *                  P1: Suchausdruck (evtl. schon compiliert)
 *                  P2: Suchpatternflag geodert (SML3_REXFLAG_*)
 *                  P3: Ersetzefunktion: char * (*)(struct SML3_rexsub *, void *)
 *                  P4: 2.Parameter fuer Ersetzefunktion oder NULL
 *                (Die Ersetzefunktion erhaelt:
 *                 - den aktuellen struct SML3_rexsub als Pointer
 *                 - den uebergebenen 2.Parameter (P4)
 *                 Sie muss zurueckgeben:
 *                 - einen allozierten Ersetze-String oder NULL = Fehler
 *                )
 *              beendet mit
 *              - SML3_REXEND
 * Rueckgabe: allozierter ersetzter String
 *            oder NULL = keine Ersetzung (SML3-errno-Wert = 0)
 *                        bzw. Fehler (SML3-errno-Wert != 0)
 * SML3-errno-Wert: 0       = keine Ersetzung
 *                  EINVAL  = Fehler Uebergabeparameter
 *                  ENOMEM  = Allokationsfehler
 *                  ENOEXEC = Compilierungsfehler
 */
char *
SML3_rexersetze(const char *string, ...)
{
  static const size_t block = 4096;
  va_list ap;
  struct SML3_rexrep reps;
  struct SML3_rexpatt *rpatt;
  struct SML3_rexsub subpatt;
  char *mstr, *nstr, *sstr, *estr, *fstr;
  size_t epos, eallo, s1;

  if (string == NULL) { SML3_fehlernew(0, NULL); return NULL; }

  va_start(ap, string);
  sstr = (char *)string;
  mstr = NULL;
  for (;;) {
    reps = va_arg(ap, struct SML3_rexrep);
    if (reps.spatt == NULL) { break; }  /* Ende */

    /* ersetzten String allozieren */
    estr = malloc(sizeof(char) * block);
    if (estr == NULL) { SML3_fehlernew(ENOMEM, "malloc: %s", SML3_strerror(errno));; return NULL; }
    eallo = block;
    epos = 0;

    /* wegen evtl. Wiederholung compilieren */
    if (!(reps.sflag & SML3_REXFLAG_COMP)) {
      rpatt = SML3_rexcomp((const char *)reps.spatt, reps.sflag);
      if (rpatt == NULL) { SML3_fehleradd(NULL); va_end(ap); free(estr); return NULL; }
    } else {
      rpatt = (struct SML3_rexpatt *)reps.spatt;
    }

    /* suchen + ersetzen */
    do {
      fstr = SML3_rexsuche(sstr, &nstr, rpatt, (reps.sflag | SML3_REXFLAG_COMP) & ~SML3_REXFLAG_GLOBAL, &subpatt);
      if (fstr == NULL) {
        if (SML3_fehlererrno() != 0) {
          SML3_fehleradd(NULL);
          va_end(ap);
          if (estr != NULL) { free(estr); }
          if (!(reps.sflag & SML3_REXFLAG_COMP)) { SML3_rexcompfree(rpatt); }
          return NULL;
        }
        sstr = nstr;
        break;
      }

      /* vorige Zeichen (sstr bis fstr-exkl.) uebertragen */
      if (fstr > sstr) {
        if (estr_add(&estr, &eallo, &epos, block, sstr, fstr) < 0) {
          SML3_fehleradd(NULL);
          va_end(ap);
          if (estr != NULL) { free(estr); }
          if (!(reps.sflag & SML3_REXFLAG_COMP)) { SML3_rexcompfree(rpatt); }
          return NULL;
        }
      }
      sstr = nstr;

      /* Daten aus Ersetzestring bzw. -funktion uebertragen */
      if (estr_repl(&estr, &eallo, &epos, block, &reps, &subpatt) < 0) {
        SML3_fehleradd(NULL);
        va_end(ap);
        if (estr != NULL) { free(estr); }
        if (!(reps.sflag & SML3_REXFLAG_COMP)) { SML3_rexcompfree(rpatt); }
        return NULL;
      }

      /* Daten zwischen subpatt.sptr + subpatt.bis und sstr-exkl. uebertragen */
      if (subpatt.sptr + subpatt.bis < sstr) {
        if (estr_add(&estr, &eallo, &epos, block, subpatt.sptr + subpatt.bis, sstr) < 0) {
          SML3_fehleradd(NULL);
          va_end(ap);
          if (estr != NULL) { free(estr); }
          if (!(reps.sflag & SML3_REXFLAG_COMP)) { SML3_rexcompfree(rpatt); }
          return NULL;
        }
      }

      if (fstr == sstr) {  /* Endlosschleife verhindern */
        if (!(reps.sflag & SML3_REXFLAG_GLOBAL)) { break; }
        if (*sstr == '\0') { break; }
        if (estr_add(&estr, &eallo, &epos, block, sstr, sstr + 1) < 0) {
          SML3_fehleradd(NULL);
          va_end(ap);
          if (estr != NULL) { free(estr); }
          if (!(reps.sflag & SML3_REXFLAG_COMP)) { SML3_rexcompfree(rpatt); }
          return NULL;
        }
        sstr++;
      }
    } while (reps.sflag & SML3_REXFLAG_GLOBAL);

    if (!(reps.sflag & SML3_REXFLAG_COMP)) { SML3_rexcompfree(rpatt); }

    if (epos == 0 && ((mstr == NULL && sstr == string) || (mstr != NULL && sstr == mstr))) {
      /* keine Datenaenderung */
      free(estr);
    } else {
      /* restliche Daten uebertragen */
      s1 = strlen(sstr);
      if (s1 > 0) {
        if (estr_add(&estr, &eallo, &epos, block, sstr, sstr + s1) < 0) {
          SML3_fehleradd(NULL);
          va_end(ap);
          if (estr != NULL) { free(estr); }
          return NULL;
        }
      }
      if (mstr != NULL) { free(mstr); }
      sstr = mstr = estr;
    }
  }
  va_end(ap);

  if (mstr == NULL) { SML3_fehlernew(0, NULL); }  /* keine Ersetzung */
  return mstr;
} /* Ende SML3_rexersetze */
