/* sml3_formel.c: Formel- und Stringberechnung */

/* 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 <strings.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <math.h>
#include "config.h"
#include "sml3_fehler.h"
#include "sml3_util.h"
#include "sml3_support.h"
#include "sml3_gummi.h"
#include "sml3_hash.h"
#include "sml3_escapeseq.h"
#include "sml3_regex.h"
#include "sml3_latin.h"
#include "sml3_formel.h"

#undef DEBUG_BOOLTEST

#define DEZ_PUNKT  "."  /* Dezimaltrenner */

struct teilvar {
  int aktiv;           /* 0 = inaktiv, 1 = ${VAR:offset:length} */
  int offset, length;  /* offset beginnt ab 0 */
};

struct s_op1 {  /* Einer-Operatoren */
  char z;   /* Operatorzeichen */
  enum {    /* Name als Bitfeld */
    op1_null = 0,        /* kein Operator oder Vorzeichen + */
    op1_minus = 1,       /* Vorzeichen - */
    op1_negation = 2,    /* Negation ! */
    op1_einerkompl = 4   /* Einer-Komplement ~ */
  } e;
};
static struct s_op1 op1_st[] = {
  { '+', op1_null },
  { '-', op1_minus },
  { '!', op1_negation },
  { '~', op1_einerkompl },
  { '\0', op1_null }  /* letzte Zeile: z = 0 */
};

#define OP2_MAXPRIO 5  /* hoechste Prioritaet der Zweier-Operatoren */
struct s_op2 {  /* Zweier-Operatoren */
  char z[4];      /* Operatorstring */
  int prio;       /* Prioritaet (1 bis OP2_MAXPRIO) */
  enum e_op2 {       /* Name */
    op2_null,        /* (kein Operator) - Prioritaet 1 */
    op2_btw_and,     /* & - Prioritaet 2 */
    op2_btw_or,      /* | - Prioritaet 2 */
    op2_btw_xor,     /* ^ - Prioritaet 2 */
    op2_shift_up,    /* << - Prioritaet 3 */
    op2_shift_down,  /* >> - Prioritaet 3 */
    op2_add,         /* + - Prioritaet 4 */
    op2_sub,         /* - - Prioritaet 4 */
    op2_mupl,        /* * - Prioritaet 5 */
    op2_div,         /* / - Prioritaet 5 */
    op2_modulo       /* % - Prioritaet 5 */
  } e;
};
static struct s_op2 op2_st[] = {
  { "*", 5, op2_mupl },
  { "/", 5, op2_div },
  { "%", 5, op2_modulo },
  { "+", 4, op2_add },
  { "-", 4, op2_sub },
  { ">>", 3, op2_shift_down },
  { "<<", 3, op2_shift_up },
  { "^", 2, op2_btw_xor },
  { "|", 2, op2_btw_or },
  { "&", 2, op2_btw_and },
  { "", 1, op2_null }  /* letzte Zeile: z = leer */
};

struct flintno {  /* Ergebnis eines (rekursiv ermittelten) Formelausdrucks */
  enum e_op2 operator;
  char typ;             /* 'i' = Integer, 'f' = double */
  union {
    SML3_int64 i;         /* Formelwert, wenn typ = 'i' */
    double f;             /* Formelwert, wenn typ = 'f' */
  } wert;
};

struct sct_liste {  /* Parameterliste fuer Funktionen */
  struct sct_liste *next;
  char data[];  /* struct flintno */
};

struct s_booltest {  /* Struct fuer Bool-Test */
  struct s_booltest *next;  /* naechstes Struct-Element */
  enum {        /* Unter-Struct-Typ */
    is_down = 1,  /* DOWN-Struct */
    is_up,        /* UP-Struct */
    is_wert,      /* BoolWert-Struct */
    is_op         /* BoolOp-Struct */
  } t;
  union {  /* Auswahl fuer ein Unter-Struct */
    struct {  /* DOWN-Struct */
      struct s_booltest **skip;  /* Adresse auf Struct hinter zugehoerigem UP-Struct */
    } down;
    struct {  /* BoolWert-Struct */
      int ezch;  /* Endezeichen oder 0 */
      enum {    /* Gleichheitsoperator innerhalb Booloperator */
        /* numerisch */
        btn_num_von = 1,   /* Start numerisch */
          btn_eq,            /* "-eq" */
          btn_ne,            /* "-ne" */
          btn_ge,            /* "-ge" */
          btn_le,            /* "-le" */
          btn_gt,            /* "-gt" */
          btn_lt,            /* "-lt" */
        btn_num_bis,       /* Ende numerisch */
        /* Text+RegEx */
        btr_re,            /* "=~" */
        btr_rn,            /* "!~" */
        /* Text */
        btt_text_von,      /* Start Text */
          /* = */
          btt_eq,            /* "=" */
          btt_eqi,           /* "={i}" (case-insensitive) */
          btt_equ,           /* "={u}" (UTF-8) */
          btt_equi,          /* "={ui}" oder "={iu}" (UTF-8 case-insensitive) */
          /* != */
          btt_ne,            /* "!=" */
          btt_nei,           /* "!={i}" (case-insensitive) */
          btt_neu,           /* "!={u}" (UTF-8) */
          btt_neui,          /* "!={ui}" oder "!={iu}" (UTF-8 case-insensitive) */
          /* >= */
          btt_ge,            /* ">=" */
          btt_gei,           /* ">={i}" (case-insensitive) */
          btt_geu,           /* ">={u}" (UTF-8) */
          btt_geui,          /* ">={ui}" oder ">={iu}" (UTF-8 case-insensitive) */
          /* <= */
          btt_le,            /* "<=" */
          btt_lei,           /* "<={i}" (case-insensitive) */
          btt_leu,           /* "<={u}" (UTF-8) */
          btt_leui,          /* "<={ui}" oder "<={iu}" (UTF-8 case-insensitive) */
          /* > */
          btt_gt,            /* ">" */
          btt_gti,           /* ">{i}" (case-insensitive) */
          btt_gtu,           /* ">{u}" (UTF-8) */
          btt_gtui,          /* ">{ui}" oder ">{iu}" (UTF-8 case-insensitive) */
          /* < */
          btt_lt,            /* "<" */
          btt_lti,           /* "<{i}" (case-insensitive) */
          btt_ltu,           /* "<{u}" (UTF-8) */
          btt_ltui,          /* "<{ui}" oder "<{iu}" (UTF-8 case-insensitive) */
        btt_text_bis,      /* Ende Text */
      } g;
      const char *p1von, *p1bis;  /* erster Booltest-Text (p1bis=exkl.) */
      const char *p2von, *p2bis;  /* zweiter Booltest-Text (p2bis=exkl.), falls vorhanden */
    } wert;
    struct {  /* BoolOp-Struct */
      enum {    /* Booloperator */
        btb_or = 1,  /* ODER-Operator (||) */
        btb_and      /* UND-Operator (&&) */
      } b;
    } op;
  } u;
};


const char * SML3_formel_expandvar(struct SML3_formel_vhash *, struct SML3_hashelem **, const char *, const char *, int);
const char * SML3_formel_expandvalue(struct SML3_formel_vhash *, struct SML3_gummi *, size_t *, const char *, const char *);
const char * SML3_formel_rechne(struct SML3_formel_vhash *, SML3_int64 *, double *, int *, const char *, const char *);
const char * SML3_formel_expandstring(struct SML3_formel_vhash *, struct SML3_gummi *, size_t *, int, const char *, const char *);
const char * SML3_formel_booltest(struct SML3_formel_vhash *, int, int, const char *, const char *, int *);
struct SML3_formel_vhash * SML3_formel_ebene_new(int);
void SML3_formel_ebene_free(struct SML3_formel_vhash **);
int SML3_formel_ebene_down(struct SML3_formel_vhash *, struct SML3_hash *);
int SML3_formel_ebene_up(struct SML3_formel_vhash *);

static struct SML3_hash * hsfunk(struct SML3_formel_vhash *, const char *, char **);
static void varget_subhash(struct SML3_gummi *, size_t *, struct SML3_hash **, int);
static int vartest_zeichen(struct SML3_gummi *, size_t *, const char **);
static int vartest_hash(struct SML3_gummi *, size_t *, struct SML3_hash **, struct SML3_formel_vhash *, int *, const char **);
static int vartest_varsub(int *, struct SML3_gummi *, size_t *, struct SML3_hash **, int, const char *, struct SML3_formel_vhash *, int *, const char **, const char *, int, struct teilvar *);
static int vartest_dimbeg(int *, struct SML3_gummi *, size_t *, struct SML3_hash **, const char **, int *, struct SML3_formel_vhash *, int);
static int vartest_bksl(struct SML3_gummi *, size_t *, const char **, const char *, int);
static int vartest_vardim(struct SML3_gummi *, size_t *, struct SML3_formel_vhash *, const char **, const char *);
static int vartest_dimend(int *, const char **);
static const char * expand_var(struct SML3_formel_vhash *, struct SML3_hashelem **, const char *, const char *, int, struct teilvar *);
static void expand_subvar(int *, struct SML3_formel_vhash *, struct SML3_gummi *, size_t *, struct SML3_hash **, const char *, const char *, int *, int);
static const char * hole_teilwert(struct SML3_gummi *, const char *, size_t *, struct teilvar *);
static const char * baue_teilvar(struct SML3_formel_vhash *, const char *, const char *, int, struct teilvar *);
static const char * formel_rechne_rek(struct SML3_formel_vhash *, struct flintno *, int, const char *, const char *);
static const char * formel_wert(struct SML3_formel_vhash *, struct flintno *, int, const char *, const char *);
static int formel_rechnefunktion(const char *, struct flintno *, struct sct_liste *);
static void formel_rechnestack(struct flintno *, int *, int);
static const char * booltest_aufbau(struct s_booltest ***, int, struct SML3_formel_vhash *, const char *, const char *, int);
#ifdef DEBUG_BOOLTEST
static void booltest_show(struct s_booltest *, int);
#endif /* DEBUG_BOOLTEST */
static int booltest_exec(struct s_booltest *, struct SML3_formel_vhash *, int);
static void booltest_free(struct s_booltest *);
static int operator_ui(const char **, const char *);
static int utf8cmp(struct SML3_gummi *, struct SML3_gummi *, int);


/* Funktion zum Hash-Ermitteln */
static struct SML3_hash *
hsfunk(struct SML3_formel_vhash *vhptr, const char *hkenn, char **nvar)
{
  struct SML3_hashelem *he1;

  /* 1.Alternative: hkenn ist Referenzname (z.B. "#name", aber ohne das "#") */
  if (nvar != NULL) {
    int eb_pos;
    char *refvar = NULL;
    if (hkenn == NULL || *hkenn == '\0') { return NULL; }
    /* Hashelement aus hs_ref fuer Referenznamen erhalten */
    for (eb_pos = vhptr->eb_pos; eb_pos > 0; eb_pos--) {
      he1 = SML3_hashget(vhptr->ebene[eb_pos].hs_ref, hkenn, strlen(hkenn) + 1);
      if (he1 == NULL) { return NULL; }
      /* referenzierten Variablennamen fuer Referenznamen aus Hashelement erhalten */
      refvar = (char *)SML3_hashelem_valget(he1, NULL);
      if (refvar == NULL || *refvar == '\0') { return NULL; }
      if (*refvar != '#') { break; }
      /* hier ist referenzierter Variablenname wieder ein Referenzname: erneut aufloesen */
      hkenn = (const char *)refvar + 1;  /* '#' ueberspringen */
    }
    if (eb_pos == 0) { return NULL; }  /* falls oberste Ebene, kann es keine Referenzen geben */
    /* Rueckgabe setzen */
    *nvar = SML3_strdup(refvar);  /* referenzierter Variablenname */
    return vhptr->ebene[eb_pos - 1].hs_lokal;  /* Rueckgabe ist lokales Hash aus ermittelter Ebene minus 1 */
  }

  /* 2.Alternative: hkenn ist Hashkenner (z.B. "VAR#", aber ohne das "#"; oder NULL = kein Hashkenner) */
  if (hkenn == NULL || *hkenn == '\0') { return vhptr->ebene[vhptr->eb_pos].hs_lokal; }  /* aktuelles lokales Hash zurueckgeben */
  return SML3_hashsubset(vhptr->hs_global, hkenn, strlen(hkenn) + 1);
} /* Ende hsfunk */


/* varget_subhash [thread-sicher]:
 * gibt im 3.Arg Subhash zurueck
 * 1.+2.Arg: Key + Size fuer Subhash
 * 3.Arg: fuer Rueckgabe Subhash (oder NULL, falls nicht vorhanden)
 * 4.Arg: ob Subhash anlegen, falls nicht vorhanden
 */
static void
varget_subhash(struct SML3_gummi *gwert, size_t *gpos, struct SML3_hash **hsvar, int create)
{
  struct SML3_hash *hstmp;
  if (*hsvar == NULL) { return; }
  SML3_gumcpy(gwert, *gpos, "");
  hstmp = SML3_hashsubget(*hsvar, SML3_gumgetval(gwert), *gpos + 1);
  if (hstmp == NULL && create > 0) {  /* Subhash anlegen */
    hstmp = SML3_hashsubset(*hsvar, SML3_gumgetval(gwert), *gpos + 1);
  }
  *hsvar = hstmp;
} /* Ende varget_subhash */


/* vartest_zeichen [thread-sicher]:
 * testet auf gueltiges Zeichen fuer Variablennamen, setzt es in 1.+2.Arg
 * 1.+2.Arg: fuer Aufbau Variablennamen
 * 3.Arg: Adresse auf aktuelle Position des zu parsenden Strings (wird aktualisiert)
 * Rueckgabe: 2 = Variablennamen-Ende, 1 = OK, 0 = kein gueltiges Zeichen fuer diesen Test
 */
static int
vartest_zeichen(struct SML3_gummi *gwert, size_t *gpos, const char **panf)
{
  if (((unsigned char)**panf >= 'A' && (unsigned char)**panf <= 'Z')
      || ((unsigned char)**panf >= 'a' && (unsigned char)**panf <= 'z')
      || ((unsigned char)**panf >= '0' && (unsigned char)**panf <= '9')
      || **panf == '_') {
    SML3_gummove(gwert, *gpos, *panf, 1);
    (*gpos)++;
    (*panf)++;
    return 1;
  }
  return 0;
} /* Ende vartest_zeichen */


/* vartest_hash [thread-sicher]:
 * testet auf Hash-Zeichen fuer Variablennamen, aktiviert Hash
 * 1.+2.Arg: fuer Aufbau Variablennamen
 * 3.Arg: fuer Rueckgabe aktuelles Hash
 * 4.Arg: Struct fuer die Variablen-Hashes
 * 5.Arg: fuer Rueckgabe, ob Hashkenner leer ist (bei Treffer)
 * 6.Arg: Adresse auf aktuelle Position des zu parsenden Strings (wird aktualisiert)
 * Rueckgabe: 2 = Variablennamen-Ende, 1 = OK, 0 = kein gueltiges Zeichen fuer diesen Test
 */
static int
vartest_hash(struct SML3_gummi *gwert, size_t *gpos, struct SML3_hash **hsvar, struct SML3_formel_vhash *vhptr, int *hzleer, const char **panf)
{
  if (**panf == '#') {
    SML3_gumcpy(gwert, *gpos, "");
    if (*gpos == 0) { *hzleer = 1; } else { *hzleer = 0; }
    *hsvar = hsfunk(vhptr, SML3_gumgetval(gwert), NULL);
    *SML3_gumgetval(gwert) = '\0'; *gpos = 0;
    (*panf)++;
    return 1;
  }
  return 0;
} /* Ende vartest_hash */


/* vartest_varsub [thread-sicher]:
 * testet auf Variablenwert im Variablennamen, fuegt ihn zum Variablennamen hinzu
 * 1.Arg: Adresse auf Parser-Position des Variablennamens (wird aktualisiert)
 * 2.+3.Arg: fuer Aufbau Variablennamen
 * 4.Arg: fuer Rueckgabe aktuelles Hash
 * 5.Arg: ob Variable geklammert
 * 6.Arg: Beginn zu parsender String (ohne Klammerung)
 * 7.Arg: Struct fuer die Variablen-Hashes
 * 8.Arg: fuer Rueckgabe, ob Hashkenner leer ist
 * 9.Arg: Adresse auf aktuelle Position des zu parsenden Strings (wird aktualisiert)
 * 10.Arg: Endepointer Gesamtstring (exkl.)
 * 11.Arg: ob Subhash anlegen, falls nicht vorhanden
 * 12.Arg: fuer Ermittlung Variablenname: NULL
 *         fuer Ermittlung Variablenwert: Informationsstruct fuer Variablenwertermittlung
 * Rueckgabe: 2 = Variablennamen-Ende, 1 = OK, 0 = kein gueltiges Zeichen fuer diesen Test, -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
static int
vartest_varsub(int *dimpos, struct SML3_gummi *gwert, size_t *gpos, struct SML3_hash **hsvar, int vklam, const char *pvorn, struct SML3_formel_vhash *vhptr, int *hzleer, const char **panf, const char *pend, int create, struct teilvar *teilvar_s)
{
  if (**panf == '$') {
    struct teilvar tv1;
    struct SML3_hashelem *he1;
    const char *pnext;

    if (*dimpos == 1 && teilvar_s != NULL && !vklam && *panf > pvorn) { return 2; }  /* Ende */

    /* Variablenwert erhalten, an gwert anhaengen und evtl. auswerten */
    if ((pnext = expand_var(vhptr, &he1, *panf + 1, pend, 0, &tv1)) == NULL) { return -1; }
    if (he1 != NULL) {
      const char *psub;
      size_t slen;
      struct SML3_gummi gsubwert;

      SML3_guminit(&gsubwert, 128);
      psub = SML3_hashelem_valget(he1, &slen);
      psub = hole_teilwert(&gsubwert, psub, &slen, &tv1);
      if (psub != NULL && slen > 0) {
        if (*dimpos == 1 && *psub == '[' && *gpos == 0) {
          SML3_gumdest(&gsubwert);
          SML3_fehlernew(EINVAL, "variable with dimensions but without name");
          return -1;
        }
        if (*dimpos == 3 && *psub != '[') { SML3_gumdest(&gsubwert); return 2; }  /* Ende */
        expand_subvar(dimpos, vhptr, gwert, gpos, hsvar, psub, psub + slen, hzleer, create);
        if (*dimpos < 0) { SML3_fehleradd(NULL); SML3_gumdest(&gsubwert); return -1; }
      }
      SML3_gumdest(&gsubwert);
    }
    *panf = pnext;
    return 1;
  }
  return 0;
} /* Ende vartest_varsub */


/* vartest_dimbeg [thread-sicher]:
 * testet auf Dimensionsbeginn im Variablennamen, aktualisiert Hash
 * 1.Arg: Adresse auf Parser-Position des Variablennamens (wird aktualisiert)
 * 2.+3.Arg: fuer Aufbau Variablennamen
 * 4.Arg: fuer Rueckgabe aktuelles Hash
 * 5.Arg: Adresse auf aktuelle Position des zu parsenden Strings (wird aktualisiert)
 * 6.Arg: fuer Ueber- und Rueckgabe, ob Hashkenner leer ist
 * 7.Arg: Struct fuer die Variablen-Hashes
 * 8.Arg: ob Subhash anlegen, falls nicht vorhanden
 * Rueckgabe: 2 = Variablennamen-Ende, 1 = OK, 0 = kein gueltiges Zeichen fuer diesen Test, -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
static int
vartest_dimbeg(int *dimpos, struct SML3_gummi *gwert, size_t *gpos, struct SML3_hash **hsvar, const char **panf, int *hzleer, struct SML3_formel_vhash *vhptr, int create)
{
  if (**panf == '[') {
    if (*dimpos == 1 && *gpos == 0 && create >= 0) {
      SML3_fehlernew(EINVAL, "variable with dimensions but without name");
      return -1;
    }

    if (*dimpos == 1 && hzleer != NULL && *hzleer && *gpos > 0) {  /* #<name> */
      char *nvar = NULL;
      SML3_gumcpy(gwert, *gpos, "");
      *hsvar = hsfunk(vhptr, SML3_gumgetval(gwert), &nvar);
      if (nvar != NULL) {
        *SML3_gumgetval(gwert) = '\0'; *gpos = 0;
        *hzleer = 0;
        expand_subvar(dimpos, vhptr, gwert, gpos, hsvar, nvar, nvar + strlen(nvar), hzleer, create);
        if (*dimpos < 0) { SML3_fehleradd(NULL); free(nvar); return -1; }
        free(nvar);
      } else {
        *SML3_gumgetval(gwert) = '\0'; *gpos = 0;
      }
      if (*gpos == 0 && create >= 0) {
        SML3_fehlernew(EINVAL, "reference-variable with dimensions but without name");
        return -1;
      }
    }

    /* Variablennamen bestehend nur aus Ziffern verbieten */
    if (*dimpos == 1 && *gpos > 0 && strspn(SML3_gumgetval(gwert), "0123456789") == *gpos) {
      *SML3_gumgetval(gwert) = '\0'; *gpos = 0;
      if (create >= 0) {
        SML3_fehlernew(EINVAL, "variable with dimensions but with invalid name");
        return -1;
      }
    }

    varget_subhash(gwert, gpos, hsvar, create);
    *SML3_gumgetval(gwert) = '\0'; *gpos = 0;
    (*panf)++;
    *dimpos = 2;
    return 1;
  }
  return 0;
} /* Ende vartest_dimbeg */


/* vartest_bksl [thread-sicher]:
 * testet auf Backslash im Variablennamen, quotet (']', '$', '\') oder ignoriert es
 * 1.+2.Arg: fuer Aufbau Variablennamen
 * 3.Arg: Adresse auf aktuelle Position des zu parsenden Strings (wird aktualisiert)
 * 4.Arg: Endepointer Gesamtstring (exkl.)
 * 5.Arg: ob '$' auch quotbares Zeichen
 * Rueckgabe: 2 = Variablennamen-Ende, 1 = OK, 0 = kein gueltiges Zeichen fuer diesen Test
 */
static int
vartest_bksl(struct SML3_gummi *gwert, size_t *gpos, const char **panf, const char *pend, int obdollar)
{
  if (**panf == '\\') {
    if (*panf < pend - 1 && ((*panf)[1] == ']' || (obdollar && (*panf)[1] == '$') || (*panf)[1] == '\\')) {
      /* Quotung */
      SML3_gummove(gwert, *gpos, *panf + 1, 1);
      (*gpos)++;
      (*panf) += 2;
    } else {  /* ignorieren */
      (*panf)++;
    }
    return 1;
  }
  return 0;
} /* Ende vartest_bksl */


/* vartest_vardim [thread-sicher]:
 * testet auf Variable in einer Dimension, fuegt Wert zum Variablennamen hinzu
 * 1.+2.Arg: fuer Aufbau Variablennamen
 * 3.Arg: Struct fuer die Variablen-Hashes
 * 4.Arg: Adresse auf aktuelle Position des zu parsenden Strings (wird aktualisiert)
 * 5.Arg: Endepointer Gesamtstring (exkl.)
 * Rueckgabe: 2 = Variablennamen-Ende, 1 = OK, 0 = kein gueltiges Zeichen fuer diesen Test, -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
static int
vartest_vardim(struct SML3_gummi *gwert, size_t *gpos, struct SML3_formel_vhash *vhptr, const char **panf, const char *pend)
{
  if (**panf == '$') {
    struct teilvar tv1;
    struct SML3_hashelem *he1;

    /* Variablenwert erhalten und an gwert anhaengen */
    if ((*panf = expand_var(vhptr, &he1, *panf + 1, pend, 0, &tv1)) == NULL) { return -1; }
    if (he1 != NULL) {
      const char *psub, *kp1;
      size_t slen;
      struct SML3_gummi gsubwert;

      SML3_guminit(&gsubwert, 128);
      psub = SML3_hashelem_valget(he1, &slen);
      psub = hole_teilwert(&gsubwert, psub, &slen, &tv1);
      if (psub != NULL) {
        if (slen > 0 && (kp1 = memchr(psub, '\0', slen)) != NULL) { slen = (size_t)(kp1 - psub); }
        if (slen > 0) {
          SML3_gummove(gwert, *gpos, psub, slen);
          *gpos += slen;
          SML3_gumcpy(gwert, *gpos, "");
        }
      }
      SML3_gumdest(&gsubwert);
    }
    return 1;
  }
  return 0;
} /* Ende vartest_vardim */


/* vartest_dimend [reentrant]:
 * testet auf Dimensionsende im Variablennamen
 * 1.Arg: Adresse auf Parser-Position des Variablennamens (wird aktualisiert)
 * 2.Arg: Adresse auf aktuelle Position des zu parsenden Strings (wird aktualisiert)
 * Rueckgabe: 2 = Variablennamen-Ende, 1 = OK, 0 = kein gueltiges Zeichen fuer diesen Test
 */
static int
vartest_dimend(int *dimpos, const char **panf)
{
  if (**panf == ']') {
    (*panf)++;
    *dimpos = 3;
    return 1;
  }
  return 0;
} /* Ende vartest_dimend */


/* expand_var [thread-sicher]:
 * rekursive Funktion zum Variablen-/Feld-Erhalten
 * 1.Arg: Struct fuer die Variablen-Hashes
 * 2.Arg: fuer Rueckgabe Hash-Element der Variable bzw. des Hashs
 * 3.Arg: Startpointer Variablen-/Feld-Name
 *        (normale Variable: Beginn auf Variablennamen, Feldvariable: Beginn auf Feldzeichen '@')
 * 4.Arg: Endepointer Gesamtstring (exkl.)
 * 5.Arg: ob Hash-Element anlegen, falls nicht vorhanden (0 = nein, 1 = ja, -1 = nein (will nur Variablenende haben))
 * 6.Arg: fuer Ermittlung Variablenname: NULL
 *        fuer Ermittlung Variablenwert: Informationsstruct fuer Variablenwertermittlung
 * Rueckgabe: Pointer auf Zeichen hinter Variablenname oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
static const char *
expand_var(struct SML3_formel_vhash *vhptr, struct SML3_hashelem **heret, const char *panf, const char *pend, int create, struct teilvar *teilvar_s)
{
  const char *pmrk, *pvorn;
  int dimpos, obfeld, vklam, terg, hzleer, obhkenn;
  size_t gpos;
  struct SML3_gummi gwert;
  struct SML3_hash *hsvar, *hstmp;

  pmrk = panf;
  *heret = NULL;
  if (teilvar_s != NULL) { teilvar_s->aktiv = 0; }
  hsvar = hsfunk(vhptr, NULL, NULL);

  /* Feldvariable? */
  if (panf < pend && *panf == '@') {
    obfeld = 1;
    if (teilvar_s != NULL) { return panf; }  /* $@... nicht sinnvoll */
    panf++;
  } else {
    obfeld = 0;
  }

  /* Variablenklammerung? */
  if (panf < pend && *panf == '{') {
    vklam = 1;
    panf++;
  } else {
    vklam = 0;
  }

  SML3_guminit(&gwert, 128); gpos = 0;

  /* Variablenname parsen */
  hzleer = 0;
  pvorn = panf;
  dimpos = 1;
  obhkenn = 0;
  while (panf < pend) {

    if (dimpos == 1) {  /* Variablenname vor den Dimensionen */

      /* auf Variablen-Zeichen testen */
      terg = vartest_zeichen(&gwert, &gpos, &panf);
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* auf Variablen-Hashzeichen testen */
      terg = vartest_hash(&gwert, &gpos, &hsvar, vhptr, &hzleer, &panf);
      if (terg == 2) { obhkenn = 1; break; }
      if (terg == 1) { obhkenn = 1; continue; }

      /* auf Variablen-Variable testen */
      terg = vartest_varsub(&dimpos, &gwert, &gpos, &hsvar, vklam, pvorn, vhptr, &hzleer, &panf, pend, create, teilvar_s);
      if (terg < 0) { SML3_fehleradd(NULL); goto _err1; }
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* auf Variablen-Dimensionsbeginn testen */
      terg = vartest_dimbeg(&dimpos, &gwert, &gpos, &hsvar, &panf, &hzleer, vhptr, create);
      if (terg < 0) { SML3_fehleradd(NULL); goto _err1; }
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* ungueltiges Zeichen = Ende */
      break;

    } else if (dimpos == 2) {  /* Variablenname in den Dimensionen */

      /* auf Variablen-Backslash testen */
      terg = vartest_bksl(&gwert, &gpos, &panf, pend, 1);
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* auf Variablen-DimVariable testen */
      terg = vartest_vardim(&gwert, &gpos, vhptr, &panf, pend);
      if (terg < 0) { SML3_fehleradd(NULL); goto _err1; }
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* auf Variablen-Dimensionsende testen */
      terg = vartest_dimend(&dimpos, &panf);
      if (terg < 0) { SML3_fehleradd(NULL); goto _err1; }
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* sonstiges Zeichen */
      if (*panf != '\0') {  /* nur Nicht-Nuller-Zeichen speichern */
        SML3_gummove(&gwert, gpos, panf, 1);
        gpos++;
      }
      panf++;

    } else {  /* Variablenname zwischen den Dimensionen */

      /* auf Variablen-Variable testen */
      terg = vartest_varsub(&dimpos, &gwert, &gpos, &hsvar, vklam, pvorn, vhptr, NULL, &panf, pend, create, teilvar_s);
      if (terg < 0) { SML3_fehleradd(NULL); goto _err1; }
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* auf Variablen-Dimensionsbeginn testen */
      terg = vartest_dimbeg(&dimpos, &gwert, &gpos, &hsvar, &panf, NULL, NULL, create);
      if (terg < 0) { SML3_fehleradd(NULL); goto _err1; }
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* ungueltiges Zeichen = Ende */
      break;
    }
  }
  SML3_gumcpy(&gwert, gpos, "");

  if (dimpos == 1 && hzleer && gpos > 0) {  /* #<name> */
    char *nvar = NULL;
    hsvar = hsfunk(vhptr, SML3_gumgetval(&gwert), &nvar);
    if (nvar != NULL) {
      *SML3_gumgetval(&gwert) = '\0'; gpos = 0;
      hzleer = 0;
      expand_subvar(&dimpos, vhptr, &gwert, &gpos, &hsvar, nvar, nvar + strlen(nvar), &hzleer, create);
      if (dimpos < 0) { SML3_fehleradd(NULL); free(nvar); goto _err1; }
      free(nvar);
    } else {
      *SML3_gumgetval(&gwert) = '\0'; gpos = 0;
    }
  }

  /* Variablennamen bestehend nur aus Ziffern verbieten */
  if (dimpos == 1 && gpos > 0 && strspn(SML3_gumgetval(&gwert), "0123456789") == gpos) {
    *SML3_gumgetval(&gwert) = '\0'; gpos = 0;
  }

  if (dimpos == 2) {
    SML3_fehlernew(EINVAL, "missing ']': (%.*s)", (int)(panf - pmrk), pmrk);
    goto _err1;
  }

  /* Rueckgabe-Hash-Element erhalten */
  if (hsvar != NULL && gpos > 0) {
    if (obfeld == 1) {
      hstmp = SML3_hashsubget(hsvar, SML3_gumgetval(&gwert), gpos + 1);
      if (hstmp != NULL) { *heret = SML3_hashparentelem(hstmp); } else { *heret = NULL; }
    } else {
      *heret = SML3_hashget(hsvar, SML3_gumgetval(&gwert), gpos + 1);
    }
    if (*heret == NULL && create > 0) {
      if (obfeld == 1) {
        hstmp = SML3_hashsubset(hsvar, SML3_gumgetval(&gwert), gpos + 1);
        *heret = SML3_hashparentelem(hstmp);
      } else {
        *heret = SML3_hashset(hsvar, SML3_gumgetval(&gwert), gpos + 1);
      }
    }
  } else if (hsvar != NULL && obhkenn) {  /* Hash vom Hashkenner, z.B. @ABC# */
    *heret = SML3_hashparentelem(hsvar);
  }

  SML3_gumdest(&gwert);

  /* Variablenwert vom Feld? */
  if (*heret != NULL && teilvar_s != NULL && SML3_hashelem_typ_is_hash(*heret)) {
    SML3_fehlernew(EINVAL, "variable is a subhash: (%.*s)", (int)(panf - pmrk), pmrk);
    return NULL;
  }

  /* Ende Variablenklammerung pruefen */
  if (vklam) {
    if (panf >= pend) {
      SML3_fehlernew(EINVAL, "missing '}': (%.*s)", (int)(panf - pmrk), pmrk);
      return NULL;
    }
    if (*panf != '}') {
      panf = baue_teilvar(vhptr, panf, pend, create, teilvar_s);
      if (panf == NULL) { SML3_fehleradd("in \"%.*s\"", (int)(panf - pmrk), pmrk); return NULL; }
    } else {
      panf++;
    }
  }

  if (*heret == NULL && create > 0) { SML3_fehlernew(EINVAL, "could not create hash element"); return NULL; }

  return panf;

_err1:
  SML3_gumdest(&gwert);
  return NULL;
} /* Ende expand_var */


/* expand_subvar [thread-sicher]:
 * Unter-Funktion zum Parsen eines Variablennamens
 * 1.Arg: Wert + Rueckgabe = Position in der Variablen
 *        Rueckgabe:  Position in der Variablen:
 *                    1 = Variablenname
 *                    2 = innerhalb einer Dimension
 *                    3 = am Ende einer Dimension
 *                   -1 = Fehler
 * 2.Arg: Struct fuer die Variablen-Hashes
 * 3.Arg: fuer Rueckgabe Variablenname oder letzten Dimensionswert
 * 4.Arg: fuer Rueckgabe Anzahl Zeichen im 3.Arg
 * 5.Arg: fuer Rueckgabe aktuelles Unter-Hash
 * 6.Arg: Startpointer Variablenname
 * 7.Arg: Endepointer Variablenname (exkl.)
 * 8.Arg: fuer Rueckgabe, ob Hashkenner leer ist
 * 9.Arg: ob Hash-Element anlegen, falls nicht vorhanden (0 = nein, 1 = ja, -1 = nein (will nur Variablenende haben))
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
static void
expand_subvar(int *dimpp, struct SML3_formel_vhash *vhptr, struct SML3_gummi *gwert, size_t *gpos, struct SML3_hash **hsvar, const char *panf, const char *pend, int *hzleer, int create)
{
  int dimpos, terg;
  const char *pmrk;

  pmrk = panf;
  dimpos = *dimpp;
  *dimpp = -1;

  while (panf < pend) {
    if (dimpos == 1) {  /* Variablenname vor den Dimensionen */

      /* auf Variablen-Zeichen testen */
      terg = vartest_zeichen(gwert, gpos, &panf);
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* auf Variablen-Hashzeichen testen */
      terg = vartest_hash(gwert, gpos, hsvar, vhptr, hzleer, &panf);
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* auf Variablen-Dimensionsbeginn testen */
      terg = vartest_dimbeg(&dimpos, gwert, gpos, hsvar, &panf, hzleer, vhptr, create);
      if (terg < 0) { SML3_fehleradd(NULL); return; }
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* ungueltiges Zeichen = Ende */
      break;

    } else if (dimpos == 2) {  /* Variablenname in den Dimensionen */

      /* auf Variablen-Backslash testen */
      terg = vartest_bksl(gwert, gpos, &panf, pend, 0);
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* auf Variablen-Dimensionsende testen */
      terg = vartest_dimend(&dimpos, &panf);
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* sonstiges Zeichen */
      if (*panf != '\0') {
        SML3_gummove(gwert, *gpos, panf, 1);
        (*gpos)++;
      }
      panf++;

    } else {  /* Variablenname zwischen den Dimensionen */

      /* auf Variablen-Dimensionsbeginn testen */
      terg = vartest_dimbeg(&dimpos, gwert, gpos, hsvar, &panf, NULL, NULL, create);
      if (terg < 0) { SML3_fehleradd(NULL); return; }
      if (terg == 2) { break; }
      if (terg == 1) { continue; }

      /* ungueltiges Zeichen = Ende */
      break;
    }
  }
  SML3_gumcpy(gwert, *gpos, "");

  if (dimpos == 1 && hzleer != NULL && *hzleer && *gpos > 0) {  /* #<name> nicht erlaubt, muss von Funktion zum Hash-Ermitteln aufgeloest werden */
    SML3_fehlernew(EINVAL, "subvariable: %s not allowed: (%.*s)", SML3_gumgetval(gwert), (int)(panf - pmrk), pmrk);
    return;
  }

  /* Variablennamen bestehend nur aus Ziffern verbieten */
  if (dimpos == 1 && *gpos > 0 && strspn(SML3_gumgetval(gwert), "0123456789") == *gpos) {
    *SML3_gumgetval(gwert) = '\0'; *gpos = 0;
  }

  if (dimpos == 2) {  /* innerhalb einer Dimension */
    SML3_fehlernew(EINVAL, "subvariable: missing ']': (%.*s)", (int)(panf - pmrk), pmrk);
    return;
  }

  if (panf == pend - 1 && *panf == '\0') { panf++; }
  if (panf < pend) {
    int cmax = ((pend - panf) > 32 ? 32 : (pend - panf));
    SML3_fehlernew(EINVAL, "subvariable name (%.*s) contains invalid character: (%.*s)", (int)(panf - pmrk + 1), pmrk, cmax, panf);
    return;
  }
  *dimpp = dimpos;
} /* Ende expand_subvar */


/* hole_teilwert [thread-sicher]:
 * gibt gewuenschten Teilwert der Variablen zurueck
 * 1.Arg: fuer Rueckgabe Teilwert
 * 2.Arg: Variablenwert
 * 3.Arg: Anzahl Zeichen im 2.Arg
 *        und fuer Rueckgabe Anzahl Zeichen Teilwert
 * 4.Arg: durch baue_teilvar() erhaltenes Struct
 * Rueckgabe: Pointer auf Teilwert (auf 1.Arg oder 2.Arg) oder NULL = kein Wert
 */
static const char *
hole_teilwert(struct SML3_gummi *rgm, const char *wert, size_t *wsize, struct teilvar *teilvar_s)
{
  if (wsize == NULL || wert == NULL) { return wert; }
  while (*wsize > 0 && wert[*wsize - 1] == '\0') { (*wsize)--; }
  if (*wsize == 0 || rgm == NULL || teilvar_s == NULL || teilvar_s->aktiv == 0) { return wert; }
  *SML3_gumgetval(rgm) = '\0';

  if (teilvar_s->aktiv == 1) {  /* var:offset:length */
    if (teilvar_s->offset < 0) { teilvar_s->offset = 0; }
    if (teilvar_s->offset >= (int)*wsize) { *wsize = 0; return NULL; }
    if (teilvar_s->length < 0) { *wsize = 0; return NULL; }
    if (teilvar_s->length == 0 || teilvar_s->offset + teilvar_s->length > (int)*wsize) {
      teilvar_s->length = *wsize - teilvar_s->offset;
    }
    SML3_gummove(rgm, 0, wert + teilvar_s->offset, teilvar_s->length);
    *wsize = teilvar_s->length;
    if (SML3_gumgetval(rgm)[teilvar_s->length - 1] != '\0') {
      SML3_gumcpy(rgm, teilvar_s->length, "");
    } else {
      (*wsize)--;
    }
  } else {
    *wsize = 0;
    return NULL;
  }

  return (const char *)SML3_gumgetval(rgm);
} /* Ende hole_teilwert */


/* baue_teilvar [thread-sicher]:
 * ermittelt gewuenschten Teil vom Variablenwert
 * 1.Arg: Struct fuer die Variablen-Hashes
 * 2.Arg: Startpointer auf Teilzeichen der Variablen (oder '}')
 * 3.Arg: Endepointer Gesamtstring (exkl.)
 * 4.Arg: ob Hash-Element anlegen, falls nicht vorhanden (0 = nein, 1 = ja, -1 = nein (will nur Variablenende haben))
 * 5.Arg: Adresse auf aufzubauendes Struct
 * Rueckgabe: Pointer auf Zeichen hinter Variablenname oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
static const char *
baue_teilvar(struct SML3_formel_vhash *vhptr, const char *panf, const char *pend, int create, struct teilvar *teilvar_s)
{
  if (panf >= pend) {
    SML3_fehlernew(EINVAL, "missing '}'");
    return NULL;
  }

  if (*panf == ':') {  /* var:offset[:length] */
    unsigned int zahl[2] = { 0, 0 };
    int idx = 0;
    panf++;
    while (panf < pend) {
      if (*panf == '}') { break; }
      if (*panf == ':') {
        if (idx == 0) { idx = 1; panf++; continue; } else { break; }
      }
      if ((unsigned char)*panf >= '0' && (unsigned char)*panf <= '9') {
        zahl[idx] *= 10;
        zahl[idx] += (*panf - '0');
        panf++;
      } else if (*panf == '$') {  /* Variablenwert erhalten */
        struct teilvar tv1, *ptv1;
        struct SML3_hashelem *he1;
        if (teilvar_s != NULL) { ptv1 = &tv1; } else { ptv1 = NULL; }
        if ((panf = expand_var(vhptr, &he1, panf + 1, pend, (create < 0 ? create : 0), ptv1)) == NULL) {
          SML3_fehleradd(NULL);
          return NULL;
        }
        if (ptv1 != NULL && he1 != NULL) {
          const char *psub;
          size_t slen;
          struct SML3_gummi gtmp = SML3_GUM_INITIALIZER;
          psub = SML3_hashelem_valget(he1, &slen);
          psub = hole_teilwert(&gtmp, psub, &slen, ptv1);
          if (psub != NULL && slen > 0 && psub[slen - 1] == '\0') { slen--; }
          if (psub == NULL) { slen = 0; }
          while (slen > 0) {
            if ((unsigned char)*psub >= '0' && (unsigned char)*psub <= '9') {
              zahl[idx] *= 10;
              zahl[idx] += (*psub - '0');
            } else {
              SML3_fehlernew(EINVAL, "no number: %c", *psub);
              SML3_gumdest(&gtmp);
              return NULL;
            }
            psub++; slen--;
          }
          SML3_gumdest(&gtmp);
        }
      } else {
        SML3_fehlernew(EINVAL, "no number: %c", *panf);
        return NULL;
      }
    }
    if (panf == pend || *panf != '}') {
      if (panf == pend) {
        SML3_fehlernew(EINVAL, "missing '}'");
      } else {
        SML3_fehlernew(EINVAL, "missing '}', found '%c'", *panf);
      }
      return NULL;
    }
    if (teilvar_s != NULL) {
      teilvar_s->aktiv = 1;
      teilvar_s->offset = zahl[0];
      teilvar_s->length = zahl[1];
    }

  } else if (*panf != '}') {
    SML3_fehlernew(EINVAL, "missing '}', found '%c'", *panf);
    return NULL;
  }

  return ++panf;
} /* Ende baue_teilvar */


/* formel_rechne_rek [thread-sicher]:
 * errechnet Formel rekursiv
 * 1.Arg: Struct fuer die Variablen-Hashes
 * 2.Arg: fuer Rueckgabe Formelwert
 * 3.Arg: gewuenschte Kommastellenanzahl je Einzelwert oder -1 = egal
 * 4.Arg: Startpointer Formel
 * 5.Arg: Endepointer Gesamtstring (exkl.)
 * Rueckgabe: Pointer auf Zeichen hinter Formel oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ERANGE = Wert einer Funktion nicht darstellbar
 */
static const char *
formel_rechne_rek(struct SML3_formel_vhash *vhptr, struct flintno *fln, int prez, const char *panf, const char *pend)
{
  struct flintno flstack[OP2_MAXPRIO];
  int flstackpos, i1, i2;
  const char *pmrk;

  flstackpos = 0;
  flstack[flstackpos].operator = op2_null;
  flstack[flstackpos].typ = 'i';
  flstack[flstackpos].wert.i = 0;
  pmrk = NULL;

  /* parsen */
  while (panf <= pend) {
    /* Leerzeichen */
    while (panf < pend) {
      if (*panf != ' ' && *panf != '\t') { break; }
      panf++;
    }
    if (pmrk == NULL) { pmrk = panf; }
    if (panf == pend || *panf == ')' || *panf == ',') {
      /* Stringende, Klammer-zu oder Funktions-Argument-Komma: Ende oder Fehler */
      if (pmrk == panf) { break; }  /* Formel leer */
      if (panf == pend) {
        SML3_fehlernew(EINVAL, "invalid character at EOF in formel: \"%.*s\"", (int)(panf - pmrk), pmrk);
      } else {
        SML3_fehlernew(EINVAL, "invalid character '%c' in formel: \"%.*s\"", *panf, (int)(panf - pmrk + 1), pmrk);
      }
      return NULL;
    }

    /* Wert */
    if ((panf = formel_wert(vhptr, &flstack[flstackpos], prez, panf, pend)) == NULL) {
      SML3_fehleradd(NULL);
      return NULL;
    }

    /* Leerzeichen */
    while (panf < pend) {
      if (*panf != ' ' && *panf != '\t') { break; }
      panf++;
    }
    /* Stringende, Klammer-zu oder Funktions-Argument-Komma: Ende */
    if (panf == pend || *panf == ')' || *panf == ',') { break; }

    /* Zweier-Operator */
    for (i1 = 0; *op2_st[i1].z != '\0'; i1++) {
      size_t slen = strlen(op2_st[i1].z);
      if ((size_t)(pend - panf) >= slen && strncmp(panf, op2_st[i1].z, slen) == 0) {
        panf += slen;
        break;
      }
    }
    if (*op2_st[i1].z == '\0') {  /* kein gueltiger Operator */
      if (*panf == '\0') {
        SML3_fehlernew(EINVAL, "EOF in formel: \"%.*s\"", (int)(panf - pmrk + 1), pmrk);
      } else {
        SML3_fehlernew(EINVAL, "invalid operator '%c' in formel: \"%.*s\"", *panf, (int)(panf - pmrk + 1), pmrk);
      }
      return NULL;
    }
_frr1:
    for (i2 = 0; *op2_st[i2].z != '\0'; i2++) {
      if (flstack[flstackpos].operator == op2_st[i2].e) { break; }
    }
    if (op2_st[i1].prio <= op2_st[i2].prio) {  /* gefundener Op <= gestackter Op */
      formel_rechnestack(flstack, &flstackpos, prez);  /* Stackberechnung */
      goto _frr1;  /* neue Pruefung, da veraenderter gestackter Op */
    }

    flstackpos++;
    flstack[flstackpos].operator = op2_st[i1].e;
  }

  /* Stack abarbeiten */
  while (flstackpos > 0) {
    formel_rechnestack(flstack, &flstackpos, prez);  /* Stackberechnung */
  }
  memmove(fln, &flstack[0], sizeof(struct flintno));

  return panf;
} /* Ende formel_rechne_rek */


/* formel_wert [thread-sicher]:
 * errechnet Formel-Wert
 * 1.Arg: Struct fuer die Variablen-Hashes
 * 2.Arg: fuer Rueckgabe Formelwert
 * 3.Arg: gewuenschte Kommastellenanzahl je Einzelwert oder -1 = egal
 * 4.Arg: Startpointer Formel
 * 5.Arg: Endepointer Gesamtstring (exkl.)
 * Rueckgabe: Pointer auf Zeichen hinter Formel oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ERANGE = Wert einer Funktion nicht darstellbar
 */
static const char *
formel_wert(struct SML3_formel_vhash *vhptr, struct flintno *fln, int prez, const char *panf, const char *pend)
{
  unsigned int vorz;
  int i1, obneg;

  /* Einer-Operatoren auswerten */
  vorz = 0; obneg = 0;
  for (; panf < pend; panf++) {
    for (i1 = 0; op1_st[i1].z != '\0'; i1++) {
      if (*panf == op1_st[i1].z) {
        if (op1_st[i1].e != op1_null) {
          if (vorz & op1_st[i1].e) { vorz &= ~op1_st[i1].e; } else { vorz |= op1_st[i1].e; }
          if (op1_st[i1].e == op1_negation) { obneg = 1; }
        }
        break;
      }
    }
    if (op1_st[i1].z == '\0') { break; }
  }
  if (panf >= pend) { SML3_fehlernew(EINVAL, "no formel value at EOF"); return NULL; }

  /* Wert ermitteln */
  if ((*panf >= 'a' && *panf <= 'z') || (*panf >= 'A' && *panf <= 'Z') || *panf == '(') {
    /* Funktion oder Klammer */
    char funktion[64];
    struct sct_liste *sctl, **sctp, *sctm;
    for (i1 = 0; panf < pend; panf++, i1++) {
      if ((*panf >= 'a' && *panf <= 'z') || (*panf >= 'A' && *panf <= 'Z')
         || (*panf >= '0' && *panf <= '9')) { continue; }
      break;
    }
    *funktion = '\0';
    if (i1 > 0) { snprintf(funktion, sizeof(funktion), "%.*s", i1, panf - i1); }
    if (*panf != '(') {
      SML3_fehlernew(EINVAL, "function without bracket: %s", funktion);
      return NULL;
    }
    sctp = &sctl;
    for (;;) {  /* je Argument der Funktion */
      *sctp = SML3_calloc(1, sizeof(struct sct_liste) + sizeof(struct flintno));
      (*sctp)->next = NULL;
      panf++;
      if ((panf = formel_rechne_rek(vhptr, (struct flintno *)(*sctp)->data, prez, panf, pend)) == NULL) {
        while (sctl != NULL) { sctm = sctl->next; free(sctl); sctl = sctm; }
        return NULL;
      }
      if ((*funktion == '\0' && *panf != ')') || (*funktion != '\0' && *panf != ')' && *panf != ',')) {
        SML3_fehlernew(EINVAL, "missing closing bracket");
        while (sctl != NULL) { sctm = sctl->next; free(sctl); sctl = sctm; }
        return NULL;
      }
      if (*panf == ')') { panf++; break; }
      sctp = &(*sctp)->next;
    }
    if (formel_rechnefunktion(funktion, fln, sctl) < 0) {
      SML3_fehleradd(NULL);
      while (sctl != NULL) { sctm = sctl->next; free(sctl); sctl = sctm; }
      return NULL;
    }
    while (sctl != NULL) { sctm = sctl->next; free(sctl); sctl = sctm; }

  } else if ((*panf >= '0' && *panf <= '9') || *panf == DEZ_PUNKT[0]) {
    /* Zahl */
    char buf[128];
    int bpos, punkt;
    fln->typ = 'i';
    fln->wert.i = 0;
    for (bpos = punkt = 0; panf < pend; panf++) {
      if (*panf >= '0' && *panf <= '9') {
        buf[bpos] = *panf;
      } else if (!punkt && *panf == DEZ_PUNKT[0]) {
        buf[bpos] = *panf;
        punkt = 1;
      } else {
        break;
      }
      if (++bpos == (int)sizeof(buf)) {
        if (!punkt) {
          SML3_fehlernew(EINVAL, "formel value too large: %.*s", bpos, buf);
          return NULL;
        }
        bpos--;
        break;
      }
    }
    for (; panf < pend; panf++) {
      if (*panf >= '0' && *panf <= '9') { continue; }
      break;
    }
    buf[bpos] = '\0';
    if (punkt) {
      fln->wert.f = atof(buf);
      fln->typ = 'f';
    } else {
      fln->wert.i = SML3_strtoint64(buf, NULL, 10);
      fln->typ = 'i';
    }

  } else if (*panf == '$') {
    /* Variable */
    struct SML3_gummi gm;
    size_t rsize;
    char *rptr;
    SML3_guminit(&gm, 32);
    panf = SML3_formel_expandvalue(vhptr, &gm, &rsize, panf, pend);
    if (panf == NULL) { SML3_fehleradd(NULL); SML3_gumdest(&gm); return NULL; }
    rptr = SML3_gumgetval(&gm);
    if (rsize > 0 && rptr[rsize - 1] == '\0') { rsize--; }
    if (rsize > 0 && strspn(rptr, "-0123456789" DEZ_PUNKT) != rsize) {
      SML3_fehlernew(EINVAL, "invalid formel variable value: %.*s", (int)rsize, rptr);
      SML3_gumdest(&gm);
      return NULL;
    }
    if (strchr(rptr, DEZ_PUNKT[0]) != NULL) {
      fln->wert.f = atof(rptr);
      fln->typ = 'f';
    } else {
      fln->wert.i = SML3_strtoint64(rptr, NULL, 10);
      fln->typ = 'i';
    }
    SML3_gumdest(&gm);

  } else {
    SML3_fehlernew(EINVAL, "invalid formel value: '%c'", *panf);
    return NULL;
  }

  /* Einer-Operatoren anwenden */
  if (vorz & op1_minus) {
    if (fln->typ == 'f') { fln->wert.f = -fln->wert.f; } else { fln->wert.i = -fln->wert.i; }
  }
  if ((vorz & op1_negation) || obneg) {
    if (fln->wert.i == 0) { fln->wert.i = 1; } else { fln->wert.i = 0; }
    fln->typ = 'i';
  }
  if (vorz & op1_einerkompl) {
    if (fln->typ == 'f') { fln->wert.i = (SML3_int64)fln->wert.f; fln->typ = 'i'; }
    fln->wert.i = ~fln->wert.i;
  }

  /* Nachkommastellenanpassung */
  if (fln->typ == 'f' && prez >= 0) {
    double dplus;
    if (prez == 0) {
      if (fln->wert.i < 0) { dplus = -.5; } else { dplus = .5; }
      fln->wert.i = (SML3_int64)(fln->wert.f + dplus);
      fln->typ = 'i';
    } else {
      char buf[128];
      if (fln->wert.f < 0.) { dplus = -.0000000001; } else { dplus = .0000000001; }
      snprintf(buf, sizeof(buf), "%.*f", prez, fln->wert.f + dplus);
      fln->wert.f = atof(buf);
    }
  }

  return panf;
} /* Ende formel_wert */


/* formel_rechnefunktion [thread-sicher]:
 * errechnet math. Funktion
 * 1.Arg: Funktionsname
 * 2.Arg: fuer Rueckgabe Formelwert
 * 3.Arg: Formelwert-Liste der Funktionsargumente
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ERANGE = Wert einer Funktion nicht darstellbar
 */
static int
formel_rechnefunktion(const char *funktion, struct flintno *fln, struct sct_liste *liste)
{
  struct sct_liste *lptr;
  int anzarg;
  double wd;
  struct flintno *fptr[4];

  for (anzarg = 0, lptr = liste; lptr != NULL; lptr = lptr->next) { anzarg++; }

  if (strcmp(funktion, "sqrt") == 0) {  /* sqrt() */
    if (anzarg != 1) {
      SML3_fehlernew(EINVAL, "function %s: wrong number of args: %d", funktion, anzarg);
      return -1;
    }
    fptr[0] = (struct flintno *)liste->data;
    errno = 0;
    wd = sqrt(fptr[0]->typ == 'i' ? (double)fptr[0]->wert.i : fptr[0]->wert.f);
    if (errno != 0) {
      SML3_fehlernew(ERANGE, "function %s: %s", funktion, SML3_strerror(errno));
      return -1;
    }
    fln->typ = 'f'; fln->wert.f = wd;

  } else if (strcmp(funktion, "pow") == 0) {  /* pow() */
    if (anzarg != 2) {
      SML3_fehlernew(EINVAL, "function %s: wrong number of args: %d", funktion, anzarg);
      return -1;
    }
    lptr = liste; fptr[0] = (struct flintno *)lptr->data;
    lptr = lptr->next; fptr[1] = (struct flintno *)lptr->data;
    errno = 0;
    wd = pow(fptr[0]->typ == 'i' ? (double)fptr[0]->wert.i : fptr[0]->wert.f,
             fptr[1]->typ == 'i' ? (double)fptr[1]->wert.i : fptr[1]->wert.f);
    if (errno != 0) {
      SML3_fehlernew(ERANGE, "function %s: %s", funktion, SML3_strerror(errno));
      return -1;
    }
    fln->typ = 'f'; fln->wert.f = wd;

/* FIXME weitere */

  } else {  /* letzten Wert nehmen (Kommaoperator) */
    for (lptr = liste; lptr != NULL; lptr = lptr->next) {
      fptr[0] = (struct flintno *)lptr->data;
      if (fptr[0]->typ == 'f') {
        fln->typ = 'f'; fln->wert.f = fptr[0]->wert.f;
      } else {
        fln->typ = 'i'; fln->wert.i = fptr[0]->wert.i;
      }
    }
  }

  return 0;
} /* Ende formel_rechnefunktion */


/* formel_rechnestack [thread-sicher]:
 * rechnet beide zuletzt in den Stack gelegte Werte zusammen
 * 1.Arg: Stack
 * 2.Arg: Adresse auf letzte Stackposition (wird aktualisiert)
 * 3.Arg: gewuenschte Kommastellenanzahl je Einzelwert oder -1 = egal
 */
static void
formel_rechnestack(struct flintno *flstack, int *flstackpos, int prez)
{
  if (*flstackpos < 1) { return; }

  switch(flstack[*flstackpos].operator) {

    case op2_btw_and:
      if (flstack[*flstackpos].typ == 'f') {
        flstack[*flstackpos].wert.i = (SML3_int64)flstack[*flstackpos].wert.f;
        flstack[*flstackpos].typ = 'i';
      }
      if (flstack[*flstackpos - 1].typ == 'f') {
        flstack[*flstackpos - 1].wert.i = (SML3_int64)flstack[*flstackpos - 1].wert.f;
        flstack[*flstackpos - 1].typ = 'i';
      }
      flstack[*flstackpos - 1].wert.i =
        flstack[*flstackpos - 1].wert.i & flstack[*flstackpos].wert.i;
      break;

    case op2_btw_or:
      if (flstack[*flstackpos].typ == 'f') {
        flstack[*flstackpos].wert.i = (SML3_int64)flstack[*flstackpos].wert.f;
        flstack[*flstackpos].typ = 'i';
      }
      if (flstack[*flstackpos - 1].typ == 'f') {
        flstack[*flstackpos - 1].wert.i = (SML3_int64)flstack[*flstackpos - 1].wert.f;
        flstack[*flstackpos - 1].typ = 'i';
      }
      flstack[*flstackpos - 1].wert.i =
        flstack[*flstackpos - 1].wert.i | flstack[*flstackpos].wert.i;
      break;

    case op2_btw_xor:
      if (flstack[*flstackpos].typ == 'f') {
        flstack[*flstackpos].wert.i = (SML3_int64)flstack[*flstackpos].wert.f;
        flstack[*flstackpos].typ = 'i';
      }
      if (flstack[*flstackpos - 1].typ == 'f') {
        flstack[*flstackpos - 1].wert.i = (SML3_int64)flstack[*flstackpos - 1].wert.f;
        flstack[*flstackpos - 1].typ = 'i';
      }
      flstack[*flstackpos - 1].wert.i =
        flstack[*flstackpos - 1].wert.i ^ flstack[*flstackpos].wert.i;
      break;

    case op2_shift_up:
      if (flstack[*flstackpos].typ == 'f') {
        flstack[*flstackpos].wert.i = (SML3_int64)flstack[*flstackpos].wert.f;
        flstack[*flstackpos].typ = 'i';
      }
      if (flstack[*flstackpos - 1].typ == 'f') {
        flstack[*flstackpos - 1].wert.i = (SML3_int64)flstack[*flstackpos - 1].wert.f;
        flstack[*flstackpos - 1].typ = 'i';
      }
      flstack[*flstackpos - 1].wert.i =
        flstack[*flstackpos - 1].wert.i << flstack[*flstackpos].wert.i;
      break;

    case op2_shift_down:
      if (flstack[*flstackpos].typ == 'f') {
        flstack[*flstackpos].wert.i = (SML3_int64)flstack[*flstackpos].wert.f;
        flstack[*flstackpos].typ = 'i';
      }
      if (flstack[*flstackpos - 1].typ == 'f') {
        flstack[*flstackpos - 1].wert.i = (SML3_int64)flstack[*flstackpos - 1].wert.f;
        flstack[*flstackpos - 1].typ = 'i';
      }
      flstack[*flstackpos - 1].wert.i =
        flstack[*flstackpos - 1].wert.i >> flstack[*flstackpos].wert.i;
      break;

    case op2_add:
      if (flstack[*flstackpos].typ == 'f') {
        if (flstack[*flstackpos - 1].typ == 'f') {
          flstack[*flstackpos - 1].wert.f =
            flstack[*flstackpos - 1].wert.f + flstack[*flstackpos].wert.f;
        } else {
          flstack[*flstackpos - 1].wert.f =
            (double)flstack[*flstackpos - 1].wert.i + flstack[*flstackpos].wert.f;
          flstack[*flstackpos - 1].typ = 'f';
        }
      } else {
        if (flstack[*flstackpos - 1].typ == 'f') {
          flstack[*flstackpos - 1].wert.f =
            flstack[*flstackpos - 1].wert.f + (double)flstack[*flstackpos].wert.i;
        } else {
          flstack[*flstackpos - 1].wert.i =
            flstack[*flstackpos - 1].wert.i + flstack[*flstackpos].wert.i;
        }
      }
      break;

    case op2_sub:
      if (flstack[*flstackpos].typ == 'f') {
        if (flstack[*flstackpos - 1].typ == 'f') {
          flstack[*flstackpos - 1].wert.f =
            flstack[*flstackpos - 1].wert.f - flstack[*flstackpos].wert.f;
        } else {
          flstack[*flstackpos - 1].wert.f =
            (double)flstack[*flstackpos - 1].wert.i - flstack[*flstackpos].wert.f;
          flstack[*flstackpos - 1].typ = 'f';
        }
      } else {
        if (flstack[*flstackpos - 1].typ == 'f') {
          flstack[*flstackpos - 1].wert.f =
            flstack[*flstackpos - 1].wert.f - (double)flstack[*flstackpos].wert.i;
        } else {
          flstack[*flstackpos - 1].wert.i =
            flstack[*flstackpos - 1].wert.i - flstack[*flstackpos].wert.i;
        }
      }
      break;

    case op2_mupl:
      if (flstack[*flstackpos].typ == 'f') {
        if (flstack[*flstackpos - 1].typ == 'f') {
          flstack[*flstackpos - 1].wert.f =
            flstack[*flstackpos - 1].wert.f * flstack[*flstackpos].wert.f;
        } else {
          flstack[*flstackpos - 1].wert.f =
            (double)flstack[*flstackpos - 1].wert.i * flstack[*flstackpos].wert.f;
          flstack[*flstackpos - 1].typ = 'f';
        }
      } else {
        if (flstack[*flstackpos - 1].typ == 'f') {
          flstack[*flstackpos - 1].wert.f =
            flstack[*flstackpos - 1].wert.f * (double)flstack[*flstackpos].wert.i;
        } else {
          flstack[*flstackpos - 1].wert.i =
            flstack[*flstackpos - 1].wert.i * flstack[*flstackpos].wert.i;
        }
      }
      break;

    case op2_div:
      if (flstack[*flstackpos].typ == 'f') {
        if (flstack[*flstackpos - 1].typ == 'f') {
          flstack[*flstackpos - 1].wert.f =
            flstack[*flstackpos - 1].wert.f / flstack[*flstackpos].wert.f;
        } else {
          flstack[*flstackpos - 1].wert.f =
            (double)flstack[*flstackpos - 1].wert.i / flstack[*flstackpos].wert.f;
          flstack[*flstackpos - 1].typ = 'f';
        }
      } else {
        if (flstack[*flstackpos - 1].typ == 'f') {
          flstack[*flstackpos - 1].wert.f =
            flstack[*flstackpos - 1].wert.f / (double)flstack[*flstackpos].wert.i;
        } else {
          flstack[*flstackpos - 1].wert.i =
            flstack[*flstackpos - 1].wert.i / flstack[*flstackpos].wert.i;
        }
      }
      break;

    case op2_modulo:
      if (flstack[*flstackpos].typ == 'f') {
        flstack[*flstackpos].wert.i = (SML3_int64)flstack[*flstackpos].wert.f;
        flstack[*flstackpos].typ = 'i';
      }
      if (flstack[*flstackpos - 1].typ == 'f') {
        flstack[*flstackpos - 1].wert.i = (SML3_int64)flstack[*flstackpos - 1].wert.f;
        flstack[*flstackpos - 1].typ = 'i';
      }
      flstack[*flstackpos - 1].wert.i =
        flstack[*flstackpos - 1].wert.i % flstack[*flstackpos].wert.i;
      break;

    default:
      break;
  }

  /* Nachkommastellenanpassung */
  if (flstack[*flstackpos - 1].typ == 'f' && prez >= 0) {
    double dplus;
    if (prez == 0) {
      if (flstack[*flstackpos - 1].wert.i < 0) { dplus = -.5; } else { dplus = .5; }
      flstack[*flstackpos - 1].wert.i = (SML3_int64)(flstack[*flstackpos - 1].wert.f + dplus);
      flstack[*flstackpos - 1].typ = 'i';
    } else {
      char buf[128];
      if (flstack[*flstackpos - 1].wert.f < 0.) { dplus = -.0000000001; } else { dplus = .0000000001; }
      snprintf(buf, sizeof(buf), "%.*f", prez, flstack[*flstackpos - 1].wert.f + dplus);
      flstack[*flstackpos - 1].wert.f = atof(buf);
    }
  }

  (*flstackpos)--;
} /* Ende formel_rechnestack */


/* booltest_aufbau [thread-sicher]:
 * baut Booltest-Struct-Liste auf (rekursiv)
 * 1.Arg: Adresse auf Adresse auf Pointer fuer naechstes zu setzendes Booltest-Struct
 * 2.Arg: Endezeichen oder 0 = keins
 * 3.Arg: Struct fuer die Variablen-Hashes
 * 4.Arg: Startpointer Bool-Test
 * 5.Arg: Endepointer Gesamtstring (exkl.)
 * 6.Arg: ob Ermittlung
 * Rueckgabe: Pointer auf Zeichen dahinter oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
static const char *
booltest_aufbau(struct s_booltest ***bnext, int ezch, struct SML3_formel_vhash *vhptr, const char *panf, const char *pend, int obmtl)
{
  const char *pmrk;
  char ez;
  int chbeg, klamstack;

_bt_redo:
  **bnext = SML3_calloc(1, sizeof(struct s_booltest));

  /* Anfangsleerzeichen ueberspringen */
  while (panf < pend) {
    if (!isspace((int)(unsigned char)*panf)) { break; }
    panf++;
  }

  pmrk = panf;
  ez = (char)ezch;
  chbeg = 0;

  /* erstes Zeichen unterscheiden */
  if (panf == pend || (ez != 0 && *panf == ez)) {  /* Ende bzw. Endezeichen */
    (**bnext)->t = is_wert;
    (**bnext)->u.wert.ezch = ezch;
    *bnext = &(**bnext)->next;
    return panf;
  }
  if (*panf == ')') {  /* UP-Struct */
    (**bnext)->t = is_wert;
    (**bnext)->u.wert.ezch = ezch;
    *bnext = &(**bnext)->next;
    **bnext = SML3_calloc(1, sizeof(struct s_booltest));
    (**bnext)->t = is_up;
    *bnext = &(**bnext)->next;
    return panf;
  }
  if (*panf == '(') {  /* DOWN-Struct */
    struct s_booltest **bmrk;
    (**bnext)->t = is_down;
    bmrk = *bnext;
    /* rekursiv aufrufen */
    *bnext = &(**bnext)->next;
    panf = booltest_aufbau(bnext, ezch, vhptr, panf + 1, pend, obmtl);
    if (panf == NULL) { return NULL; }
    /* Ende-Klammer vorhanden? */
    if (panf == pend || *panf != ')') {
      if (panf == pend) {
        SML3_fehlernew(EINVAL, "invalid character at EOF in boolexpression: \"%.*s\"", (int)(panf - pmrk), pmrk);
      } else {
        SML3_fehlernew(EINVAL, "invalid character '%c' in boolexpression: \"%.*s\"", *panf, (int)(panf - pmrk + 1), pmrk);
      }
      return NULL;
    }
    panf++;
    (*bmrk)->u.down.skip = *bnext;
    /* gueltiger Boolausdruck oder etwa Formel-Klammer? */
    while (panf < pend) {  /* Anfangsleerzeichen ueberspringen */
      if (!isspace((int)(unsigned char)*panf)) { break; }
      panf++;
    }
    if (panf == pend || (ez != 0 && *panf == ez)) { return panf; }  /* Ende bzw. Endezeichen */
    if (*panf == ')') {  /* UP-Struct */
      **bnext = SML3_calloc(1, sizeof(struct s_booltest));
      (**bnext)->t = is_up;
      *bnext = &(**bnext)->next;
      return panf;
    }
    if (panf <= pend - 2 && strncmp(panf, "||", 2) == 0) {  /* BoolOp-ODER */
      **bnext = SML3_calloc(1, sizeof(struct s_booltest));
      (**bnext)->t = is_op;
      (**bnext)->u.op.b = btb_or;
      *bnext = &(**bnext)->next;
      panf += 2;
      goto _bt_redo;
    } else if (panf <= pend - 2 && strncmp(panf, "&&", 2) == 0) {  /* BoolOp-UND */
      **bnext = SML3_calloc(1, sizeof(struct s_booltest));
      (**bnext)->t = is_op;
      (**bnext)->u.op.b = btb_and;
      *bnext = &(**bnext)->next;
      panf += 2;
      goto _bt_redo;
    } else {  /* kein gueltiger Boolausdruck, umwandeln in Text ohne Child-Begrenzer */
      panf = pmrk;
      booltest_free((*bmrk)->next);
      memset(*bmrk, 0, sizeof(struct s_booltest));
      *bnext= bmrk;
    }
  }

  (**bnext)->t = is_wert;
  (**bnext)->u.wert.ezch = ezch;
  (**bnext)->u.wert.p1von = panf;
  if (ez != 0 && panf <= pend - 2 && panf[0] == '\\' && panf[1] == ez) {  /* Child-Begrenzer */
    chbeg = 1;
    panf += 2;
  }
  klamstack = 0;

  for (;panf < pend;) {
    if (panf <= pend - 2 && panf[0] == '\\') {  /* Quotung */
      if (panf[1] == '\\') { panf += 2; continue; }  /* echtes Backslash */
      if (ez != 0 && panf[1] == ez) { chbeg = !chbeg; panf += 2; continue; }  /* Child-Begrenzer */
      panf += 2;
      continue;
    }
    if (*panf == '$') {  /* Variable ueberspringen */
      struct SML3_hashelem *he1;
      if ((panf = expand_var(vhptr, &he1, panf + 1, pend, obmtl ? 0 : -1, NULL)) == NULL) {
        SML3_fehleradd(NULL);
        return NULL;
      }
      continue;
    }
    if (chbeg) { panf++; continue; }  /* innerhalb Child-Begrenzer */

    if (ez != 0 && *panf == ez) {  /* Ende bzw. Endezeichen */
      if ((**bnext)->u.wert.g != 0) {
        (**bnext)->u.wert.p2bis = panf;
      } else {
        (**bnext)->u.wert.p1bis = panf;
      }
      *bnext = &(**bnext)->next;
      return panf;
    }

    if (*panf == ')') {
      if (klamstack > 0) {  /* Klammer-Stack-POP */
        klamstack--;
        panf++;
        continue;
      } else {  /* UP-Struct */
        if ((**bnext)->u.wert.g != 0) {
          (**bnext)->u.wert.p2bis = panf;
        } else {
          (**bnext)->u.wert.p1bis = panf;
        }
        *bnext = &(**bnext)->next;
        **bnext = SML3_calloc(1, sizeof(struct s_booltest));
        (**bnext)->t = is_up;
        *bnext = &(**bnext)->next;
        return panf;
      }
    }

    if (*panf == '(') { klamstack++; panf++; continue; }  /* Klammer-Stack-PUSH */

    if (klamstack > 0) { panf++; continue; }  /* keine weiteren Pruefungen */

    if ((**bnext)->u.wert.g == 0) {  /* Gleichheitsoperator pruefen */
      const char *pv1 = panf;
      if (panf <= pend - 3 && strncmp(panf, "-eq", 3) == 0) {  /* num.gleich: '-eq' */
        (**bnext)->u.wert.g = btn_eq;
        panf += 3;
      } else if (panf <= pend - 3 && strncmp(panf, "-ne", 3) == 0) {  /* num.ungleich: '-ne' */
        (**bnext)->u.wert.g = btn_ne;
        panf += 3;
      } else if (panf <= pend - 3 && strncmp(panf, "-ge", 3) == 0) {  /* num.groessergleich: '-ge' */
        (**bnext)->u.wert.g = btn_ge;
        panf += 3;
      } else if (panf <= pend - 3 && strncmp(panf, "-le", 3) == 0) {  /* num.kleinergleich: '-le' */
        (**bnext)->u.wert.g = btn_le;
        panf += 3;
      } else if (panf <= pend - 3 && strncmp(panf, "-gt", 3) == 0) {  /* num.groesser: '-gt' */
        (**bnext)->u.wert.g = btn_gt;
        panf += 3;
      } else if (panf <= pend - 3 && strncmp(panf, "-lt", 3) == 0) {  /* num.kleiner: '-lt' */
        (**bnext)->u.wert.g = btn_lt;
        panf += 3;
      } else if (panf <= pend - 2 && strncmp(panf, "=~", 2) == 0) {  /* regulaer gleich: '=~' */
        (**bnext)->u.wert.g = btr_re;
        panf += 2;
      } else if (panf <= pend - 2 && strncmp(panf, "!~", 2) == 0) {  /* regulaer ungleich: '!~' */
        (**bnext)->u.wert.g = btr_rn;
        panf += 2;
      } else if (panf <= pend - 2 && strncmp(panf, "==", 2) == 0) {  /* gleich: '==' */
        panf += 2;
        (**bnext)->u.wert.g = btt_eq + operator_ui(&panf, pend);
      } else if (panf <= pend - 1 && strncmp(panf, "=", 1) == 0) {  /* gleich: '=' */
        panf++;
        (**bnext)->u.wert.g = btt_eq + operator_ui(&panf, pend);
      } else if (panf <= pend - 2 && strncmp(panf, "!=", 2) == 0) {  /* ungleich: '!=' */
        panf += 2;
        (**bnext)->u.wert.g = btt_ne + operator_ui(&panf, pend);
      } else if (panf <= pend - 2 && strncmp(panf, ">=", 2) == 0) {  /* groessergleich: '>=' */
        panf += 2;
        (**bnext)->u.wert.g = btt_ge + operator_ui(&panf, pend);
      } else if (panf <= pend - 2 && strncmp(panf, "<=", 2) == 0) {  /* kleinergleich: '<=' */
        panf += 2;
        (**bnext)->u.wert.g = btt_le + operator_ui(&panf, pend);
      } else if (panf <= pend - 1 && strncmp(panf, ">", 1) == 0) {  /* groesser: '>' */
        panf++;
        (**bnext)->u.wert.g = btt_gt + operator_ui(&panf, pend);
      } else if (panf <= pend - 1 && strncmp(panf, "<", 1) == 0) {  /* kleiner: '<' */
        panf++;
        (**bnext)->u.wert.g = btt_lt + operator_ui(&panf, pend);
      }

      if (pv1 < panf) {
        (**bnext)->u.wert.p1bis = pv1;
        (**bnext)->u.wert.p2von = panf;
        continue;
      }
    }

    /* Booloperatoren pruefen */
    if (panf <= pend - 2 && strncmp(panf, "||", 2) == 0) {  /* BoolOp-ODER */
      if ((**bnext)->u.wert.g != 0) {
        (**bnext)->u.wert.p2bis = panf;
      } else {
        (**bnext)->u.wert.p1bis = panf;
      }
      *bnext = &(**bnext)->next;
      **bnext = SML3_calloc(1, sizeof(struct s_booltest));
      (**bnext)->t = is_op;
      (**bnext)->u.op.b = btb_or;
      *bnext = &(**bnext)->next;
      panf += 2;
      goto _bt_redo;
    }
    if (panf <= pend - 2 && strncmp(panf, "&&", 2) == 0) {  /* BoolOp-UND */
      if ((**bnext)->u.wert.g != 0) {
        (**bnext)->u.wert.p2bis = panf;
      } else {
        (**bnext)->u.wert.p1bis = panf;
      }
      *bnext = &(**bnext)->next;
      **bnext = SML3_calloc(1, sizeof(struct s_booltest));
      (**bnext)->t = is_op;
      (**bnext)->u.op.b = btb_and;
      *bnext = &(**bnext)->next;
      panf += 2;
      goto _bt_redo;
    }

    panf++;
  }

  if (chbeg) {
    SML3_fehlernew(EINVAL, "missing closing \\%c at EOF in boolexpression: \"%.*s\"", ez, (int)(panf - pmrk), pmrk);
    return NULL;
  }
  if (klamstack > 0) {
    SML3_fehlernew(EINVAL, "missing closing bracket ')' at EOF in boolexpression: \"%.*s\"", (int)(panf - pmrk), pmrk);
    return NULL;
  }

  if ((**bnext)->u.wert.g != 0) {
    (**bnext)->u.wert.p2bis = panf;
  } else {
    (**bnext)->u.wert.p1bis = panf;
  }
  *bnext = &(**bnext)->next;

  return panf;
} /* Ende booltest_aufbau */


#ifdef DEBUG_BOOLTEST
/* booltest_show [thread-sicher]:
 * zeigt Booltest-Struct-Liste an (rekursiv)
 * 1.Arg: Pointer auf Start-Booltest-Struct
 * 2.Arg: rekursive Tiefe
 */
static void
booltest_show(struct s_booltest *bstart, int tiefe)
{
  while (bstart != NULL) {

    if (bstart->t == is_down) {
      printf("%*s- DOWN %p: Skip = %p\n", tiefe, "", bstart, *bstart->u.down.skip);
      booltest_show(bstart->next, tiefe + 2);
      bstart = *bstart->u.down.skip;

    } else if (bstart->t == is_up) {
      printf("%*s- UP %p\n", tiefe, "", bstart);
      return;

    } else if (bstart->t == is_wert) {
      printf("%*s- Wert %p: Gleichheit = %d\n", tiefe, "", bstart, bstart->u.wert.g);
      if (bstart->u.wert.p1von != NULL) {
        printf("%*s  P1 = <%.*s>\n", tiefe, "", (int)(bstart->u.wert.p1bis - bstart->u.wert.p1von), bstart->u.wert.p1von);
      }
      if (bstart->u.wert.p2von != NULL) {
        printf("%*s  P2 = <%.*s>\n", tiefe, "", (int)(bstart->u.wert.p2bis - bstart->u.wert.p2von), bstart->u.wert.p2von);
      }
      bstart = bstart->next;

    } else if (bstart->t == is_op) {
      printf("%*s- BoolOp %p: Bool = %d\n", tiefe, "", bstart, bstart->u.op.b);
      bstart = bstart->next;

    } else {
      printf("%*s- ? %p\n", tiefe, "", bstart);
      bstart = bstart->next;
    }
  }
} /* Ende booltest_show */
#endif /* DEBUG_BOOLTEST */


/* booltest_exec [thread-sicher]:
 * fuehrt Booltest-Struct-Liste aus (rekursiv)
 * 1.Arg: Pointer auf Start-Booltest-Struct
 * 2.Arg: Struct fuer die Variablen-Hashes
 * 3.Arg: falls Formel: gewuenschte Kommastellenanzahl je Einzelwert oder -1 = egal
 * Rueckgabe: 1 = wahr, 0 = falsch, -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
static int
booltest_exec(struct s_booltest *bstart, struct SML3_formel_vhash *vhptr, int prez)
{
  int ergbool = 0;

  while (bstart != NULL) {

    if (bstart->t == is_down) {
      ergbool = booltest_exec(bstart->next, vhptr, prez);
      if (ergbool < 0) { return -1; }
      bstart = *bstart->u.down.skip;

    } else if (bstart->t == is_up) {
      return ergbool;

    } else if (bstart->t == is_wert) {
      if (bstart->u.wert.g == 0
          || bstart->u.wert.g == btr_re
          || bstart->u.wert.g == btr_rn
          || (bstart->u.wert.g > btt_text_von && bstart->u.wert.g < btt_text_bis)) {  /* Text */
        struct SML3_gummi gm1, gm2;
        size_t gmpos;

        SML3_guminit(&gm1, 128);
        if (bstart->u.wert.p1von != NULL && bstart->u.wert.p1bis != NULL) {
          const char *pnext;
          gmpos = 0;
          pnext = SML3_formel_expandstring(vhptr, &gm1, &gmpos, -bstart->u.wert.ezch, bstart->u.wert.p1von, bstart->u.wert.p1bis);
          if (pnext == NULL) { SML3_fehleradd(NULL); SML3_gumdest(&gm1); return -1; }
          if (pnext != bstart->u.wert.p1bis) {
            int cmax = ((bstart->u.wert.p1bis - pnext) > 32 ? 32 : (bstart->u.wert.p1bis - pnext));
            SML3_fehlernew(EINVAL, "string-expand invalid at \"%.*s\"", cmax, pnext);
            SML3_gumdest(&gm1);
            return -1;
          }
        }

        SML3_guminit(&gm2, 128);
        if (bstart->u.wert.g != 0 && bstart->u.wert.p2von != NULL && bstart->u.wert.p2bis != NULL) {
          const char *pnext;
          gmpos = 0;
          pnext = SML3_formel_expandstring(vhptr, &gm2, &gmpos, -bstart->u.wert.ezch, bstart->u.wert.p2von, bstart->u.wert.p2bis);
          if (pnext == NULL) { SML3_fehleradd(NULL); SML3_gumdest(&gm1); SML3_gumdest(&gm2); return -1; }
          if (pnext != bstart->u.wert.p2bis) {
            int cmax = ((bstart->u.wert.p2bis - pnext) > 32 ? 32 : (bstart->u.wert.p2bis - pnext));
            SML3_fehlernew(EINVAL, "string-expand invalid at \"%.*s\"", cmax, pnext);
            SML3_gumdest(&gm1); SML3_gumdest(&gm2);
            return -1;
          }
        }

        ergbool = 0;
        /* Text+RegEx */
        if (bstart->u.wert.g == btr_re) {
          if (SML3_rexstr(SML3_gumgetval(&gm1), NULL, SML3_gumgetval(&gm2), SML3_REXFLAG_I, NULL) != NULL) { ergbool = 1; }
        } else if (bstart->u.wert.g == btr_rn) {
          if (SML3_rexstr(SML3_gumgetval(&gm1), NULL, SML3_gumgetval(&gm2), SML3_REXFLAG_I, NULL) == NULL) { ergbool = 1; }
        /* Text */
        } else if (bstart->u.wert.g >= btt_eq && bstart->u.wert.g <= btt_equi) {  /* = */
          switch(bstart->u.wert.g) {
            case btt_eq:
              if (strcmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) == 0) { ergbool = 1; }
              break;
            case btt_eqi:
              if (strcasecmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) == 0) { ergbool = 1; }
              break;
            case btt_equ:
              if (utf8cmp(&gm1, &gm2, 0) == 0) { ergbool = 1; }
              break;
            case btt_equi:
              if (utf8cmp(&gm1, &gm2, 1) == 0) { ergbool = 1; }
              break;
            default:
              break;
          }
        } else if (bstart->u.wert.g >= btt_ne && bstart->u.wert.g <= btt_neui) {  /* != */
          switch(bstart->u.wert.g) {
            case btt_ne:
              if (strcmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) != 0) { ergbool = 1; }
              break;
            case btt_nei:
              if (strcasecmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) != 0) { ergbool = 1; }
              break;
            case btt_neu:
              if (utf8cmp(&gm1, &gm2, 0) != 0) { ergbool = 1; }
              break;
            case btt_neui:
              if (utf8cmp(&gm1, &gm2, 1) != 0) { ergbool = 1; }
              break;
            default:
              break;
          }
        } else if (bstart->u.wert.g >= btt_ge && bstart->u.wert.g <= btt_geui) {  /* >= */
          switch(bstart->u.wert.g) {
            case btt_ge:
              if (strcmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) >= 0) { ergbool = 1; }
              break;
            case btt_gei:
              if (strcasecmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) >= 0) { ergbool = 1; }
              break;
            case btt_geu:
              if (utf8cmp(&gm1, &gm2, 0) >= 0) { ergbool = 1; }
              break;
            case btt_geui:
              if (utf8cmp(&gm1, &gm2, 1) >= 0) { ergbool = 1; }
              break;
            default:
              break;
          }
        } else if (bstart->u.wert.g >= btt_le && bstart->u.wert.g <= btt_leui) {  /* <= */
          switch(bstart->u.wert.g) {
            case btt_le:
              if (strcmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) <= 0) { ergbool = 1; }
              break;
            case btt_lei:
              if (strcasecmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) <= 0) { ergbool = 1; }
              break;
            case btt_leu:
              if (utf8cmp(&gm1, &gm2, 0) <= 0) { ergbool = 1; }
              break;
            case btt_leui:
              if (utf8cmp(&gm1, &gm2, 1) <= 0) { ergbool = 1; }
              break;
            default:
              break;
          }
        } else if (bstart->u.wert.g >= btt_gt && bstart->u.wert.g <= btt_gtui) {  /* > */
          switch(bstart->u.wert.g) {
            case btt_gt:
              if (strcmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) > 0) { ergbool = 1; }
              break;
            case btt_gti:
              if (strcasecmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) > 0) { ergbool = 1; }
              break;
            case btt_gtu:
              if (utf8cmp(&gm1, &gm2, 0) > 0) { ergbool = 1; }
              break;
            case btt_gtui:
              if (utf8cmp(&gm1, &gm2, 1) > 0) { ergbool = 1; }
              break;
            default:
              break;
          }
        } else if (bstart->u.wert.g >= btt_lt && bstart->u.wert.g <= btt_ltui) {  /* < */
          switch(bstart->u.wert.g) {
            case btt_lt:
              if (strcmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) < 0) { ergbool = 1; }
              break;
            case btt_lti:
              if (strcasecmp(SML3_gumgetval(&gm1), SML3_gumgetval(&gm2)) < 0) { ergbool = 1; }
              break;
            case btt_ltu:
              if (utf8cmp(&gm1, &gm2, 0) < 0) { ergbool = 1; }
              break;
            case btt_ltui:
              if (utf8cmp(&gm1, &gm2, 1) < 0) { ergbool = 1; }
              break;
            default:
              break;
          }
        /* ansonsten */
        } else {
          if (*SML3_gumgetval(&gm1) != '\0') { ergbool = 1; }
        }
#ifdef DEBUG_BOOLTEST
        printf("String-Vergleich = %d: <%s> <BoolOp=%d> <%s>\n", ergbool, SML3_gumgetval(&gm1), bstart->u.wert.g, SML3_gumgetval(&gm2));
#endif /* DEBUG_BOOLTEST */
        SML3_gumdest(&gm1);
        SML3_gumdest(&gm2);

      } else if (bstart->u.wert.g == btn_eq
                 || bstart->u.wert.g == btn_ne
                 || bstart->u.wert.g == btn_ge
                 || bstart->u.wert.g == btn_le
                 || bstart->u.wert.g == btn_gt
                 || bstart->u.wert.g == btn_lt) {  /* numerisch */
        const double mindw = .000000001;
        SML3_int64 iw1 = 0, iw2 = 0;
        double dw1 = .0, dw2 = .0;
        int prez1 = prez, prez2 = prez;

        if (bstart->u.wert.p1von != NULL && bstart->u.wert.p1bis != NULL) {
          const char *pnext;
          pnext = SML3_formel_rechne(vhptr, &iw1, &dw1, &prez1, bstart->u.wert.p1von, bstart->u.wert.p1bis);
          if (pnext == NULL) { SML3_fehleradd(NULL); return -1; }
          if (pnext != bstart->u.wert.p1bis) {
            int cmax = ((bstart->u.wert.p1bis - pnext) > 32 ? 32 : (bstart->u.wert.p1bis - pnext));
            SML3_fehlernew(EINVAL, "formel-expand invalid at \"%.*s\"", cmax, pnext);
            return -1;
          }
        } else {
          prez1 = 0;
        }

        if (bstart->u.wert.p2von != NULL && bstart->u.wert.p2bis != NULL) {
          const char *pnext;
          pnext = SML3_formel_rechne(vhptr, &iw2, &dw2, &prez2, bstart->u.wert.p2von, bstart->u.wert.p2bis);
          if (pnext == NULL) { SML3_fehleradd(NULL); return -1; }
          if (pnext != bstart->u.wert.p2bis) {
            int cmax = ((bstart->u.wert.p2bis - pnext) > 32 ? 32 : (bstart->u.wert.p2bis - pnext));
            SML3_fehlernew(EINVAL, "formel-expand invalid at \"%.*s\"", cmax, pnext);
            return -1;
          }
        } else {
          prez2 = 0;
        }

        if (prez1 == 0 && prez2 == 1) {
          prez1 = 1;
          dw1 = (double)iw1;
        } else if (prez1 == 1 && prez2 == 0) {
          prez2 = 1;
          dw2 = (double)iw2;
        }

        ergbool = 0;
        if (bstart->u.wert.g == btn_eq) {
          if (prez1 == 1) {
            double d1 = dw1 - dw2;
            if (d1 < mindw && d1 > -mindw) { ergbool = 1; }
          } else {
            ergbool = !!(iw1 == iw2);
          }
        } else if (bstart->u.wert.g == btn_ne) {
          if (prez1 == 1) {
            double d1 = dw1 - dw2;
            if (d1 >= mindw || d1 <= -mindw) { ergbool = 1; }
          } else {
            ergbool = !!(iw1 != iw2);
          }
        } else if (bstart->u.wert.g == btn_ge) {
          if (prez1 == 1) {
            double d1 = dw1 - dw2;
            if (d1 > -mindw) { ergbool = 1; }
          } else {
            ergbool = !!(iw1 >= iw2);
          }
        } else if (bstart->u.wert.g == btn_le) {
          if (prez1 == 1) {
            double d1 = dw1 - dw2;
            if (d1 < mindw) { ergbool = 1; }
          } else {
            ergbool = !!(iw1 <= iw2);
          }
        } else if (bstart->u.wert.g == btn_gt) {
          if (prez1 == 1) {
            double d1 = dw1 - dw2;
            if (d1 >= mindw) { ergbool = 1; }
          } else {
            ergbool = !!(iw1 > iw2);
          }
        } else if (bstart->u.wert.g == btn_lt) {
          if (prez1 == 1) {
            double d1 = dw1 - dw2;
            if (d1 <= -mindw) { ergbool = 1; }
          } else {
            ergbool = !!(iw1 < iw2);
          }
        }
#ifdef DEBUG_BOOLTEST
        if (prez1 == 1) {
          printf("Formel-Vergleich = %d: <%.9f> <BoolOp=%d> <%.9f>\n", ergbool, dw1, bstart->u.wert.g, dw2);
        } else {
          printf("Formel-Vergleich = %d: <%" SMLINT64_FORMAT "d> <BoolOp=%d> <%" SMLINT64_FORMAT "d>\n", ergbool, iw1, bstart->u.wert.g, iw2);
        }
#endif /* DEBUG_BOOLTEST */
      }
      bstart = bstart->next;

    } else if (bstart->t == is_op) {
      int boolop = bstart->u.op.b;
      bstart = bstart->next;
      if (bstart == NULL) { break; }
      if (boolop == btb_or) {  /* BoolOp-ODER */
        if (ergbool) {  /* ueberspringen */
          if (bstart->t == is_down) {
            bstart = *bstart->u.down.skip;
          } else {
            bstart = bstart->next;
          }
        }
      } else if (boolop == btb_and) {  /* BoolOp-UND */
        if (!ergbool) {  /* ueberspringen */
          if (bstart->t == is_down) {
            bstart = *bstart->u.down.skip;
          } else {
            bstart = bstart->next;
          }
        }
      }

    } else {
      bstart = bstart->next;
    }
  }

  return ergbool;
} /* Ende booltest_exec */


/* booltest_free [thread-sicher]:
 * gibt Booltest-Struct-Liste frei
 * 1.Arg: Start-Pointer fuer freizugebende Booltest-Struct-Liste
 */
static void
booltest_free(struct s_booltest *bstart)
{
  struct s_booltest *bnext;
  for (;bstart != NULL;) {
    bnext = bstart->next;
    free(bstart);
    bstart = bnext;
  }
} /* Ende booltest_free */


/* prueft, ob Operator ICase- oder UTF8-Anhaengsel hat: {[ui]}, gibt zu addierende Zahl an Grundoperator zurueck */
static int
operator_ui(const char **panf, const char *pend)
{
  int retw = 0;

  if (panf == NULL || *panf == NULL || pend == NULL) { return 0; }
  if (*panf > pend - 3) { return 0; }
  if ((*panf)[0] != '{') { return 0; }

  if ((*panf)[1] == 'i' || (*panf)[1] == 'I') { retw |= 1; }
  if ((*panf)[1] == 'u' || (*panf)[1] == 'U') { retw |= 2; }

  if ((*panf)[2] == '}') { (*panf) += 3; return retw; }
  if (*panf > pend - 4) { return 0; }

  if ((*panf)[2] == 'i' || (*panf)[2] == 'I') { retw |= 1; }
  if ((*panf)[2] == 'u' || (*panf)[2] == 'U') { retw |= 2; }

  if ((*panf)[3] == '}') { (*panf) += 4; return retw; }

  return 0;
} /* Ende operator_ui */


/* vergleicht 2 UTF8-Strings, konvertiert evtl. zuvor zu UTF-8 */
static int
utf8cmp(struct SML3_gummi *gm1, struct SML3_gummi *gm2, int icase)
{
  char *pt1, *pt2;
  size_t slen;

  pt1 = SML3_gumgetval(gm1);
  pt2 = SML3_gumgetval(gm2);
  if (pt1 == NULL && pt2 == NULL) { return 0; }
  if (pt1 == NULL) { return (*pt2 == '\0' ? 0 : -1); }
  if (pt2 == NULL) { return (*pt1 == '\0' ? 0 : 1); }

  slen = strlen(pt1);
  if (!SML3_latin_isutf8(pt1, slen)) {
    struct SML3_gummi gmtmp = SML3_GUM_INITIALIZER;
    SML3_latin_convert(SML3_LATIN_INPUT_ISO | SML3_LATIN_OUTPUT_UTF8, pt1, &slen, &gmtmp, 0);
    SML3_gumswapgum(gm1, &gmtmp);
    SML3_gumdest(&gmtmp);
    pt1 = SML3_gumgetval(gm1);
  }

  slen = strlen(pt2);
  if (!SML3_latin_isutf8(pt2, slen)) {
    struct SML3_gummi gmtmp = SML3_GUM_INITIALIZER;
    SML3_latin_convert(SML3_LATIN_INPUT_ISO | SML3_LATIN_OUTPUT_UTF8, pt2, &slen, &gmtmp, 0);
    SML3_gumswapgum(gm2, &gmtmp);
    SML3_gumdest(&gmtmp);
    pt2 = SML3_gumgetval(gm2);
  }

  return SML3_latin_strcmp(pt1, pt2, icase);
} /* Ende utf8cmp */


/* SML3_formel_expandvar [thread-sicher]:
 * loest Variable auf, setzt deren Hash-Element in 2.Arg
 * 1.Arg: Struct fuer die Variablen-Hashes (struct SML3_formel_vhash)
 * 2.Arg: fuer Rueckgabe Hash-Element der auszuwertenden Variable,
 *        falls Variable ein Feld ist, wird Hash-Element eines Subhashs zurueckgegeben
 * 3.Arg: Startpointer Variablenname
 *        (normale Variable: Beginn auf Variablennamen, Feldvariable: Beginn auf Feldzeichen '@')
 * 4.Arg: Endepointer Gesamtstring (exkl.) oder NULL = bis Stringende
 * 5.Arg: ob Hash-Element anlegen, falls nicht vorhanden (0 = nein, 1 = ja, -1 = nein (will nur Variablenende haben))
 * Rueckgabe: Pointer auf Zeichen hinter Variablenname oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *
 * Variablennamen und ihre Dimensionswerte koennen nicht binaer sein,
 * sie haben jeweils zum Schluss ein 0-Zeichen.
 *
 * Soll das Hash-Element eines Subhashs fuer einen Hashkenner zurueckgegeben werden,
 * wird im 2.Arg der Hashkenner mit '#' als Feldvariable uebergeben,
 * z.B.:  um das Hash, in dem alle Variablen von "ABC#" liegen, zu erhalten,
 *        wird im 2.Arg uebergeben: "@ABC#"
 */
const char *
SML3_formel_expandvar(struct SML3_formel_vhash *vhptr, struct SML3_hashelem **heret, const char *panf, const char *pend, int create)
{
  const char *pret;

  if (heret == NULL || panf == NULL) {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    return NULL;
  }

  if (pend == NULL) { pend = panf + strlen(panf); }
  *heret = NULL;

  if ((pret = expand_var(vhptr, heret, panf, pend, create, NULL)) == NULL) {
    int cmax = ((pend - panf) > 32 ? 32 : (pend - panf));
    SML3_fehleradd("expanding \"%.*s...\"", cmax, panf);
    *heret = NULL;
    return NULL;
  }
  return pret;
} /* Ende SML3_formel_expandvar */


/* SML3_formel_expandvalue [thread-sicher]:
 * ermittelt Variablenwert, setzt ihn in 2.+3.Arg
 * 1.Arg: Struct fuer die Variablen-Hashes (struct SML3_formel_vhash)
 * 2.Arg: fuer Rueckgabe Variablenwert
 * 3.Arg: fuer Rueckgabe Anzahl Zeichen im 2.Arg
 * 4.Arg: Startpointer Variablenname (Beginn auf Variablenzeichen '$')
 * 5.Arg: Endepointer Gesamtstring (exkl.) oder NULL = bis Stringende
 * Rueckgabe: Pointer auf Zeichen hinter Variablenname oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
const char *
SML3_formel_expandvalue(struct SML3_formel_vhash *vhptr, struct SML3_gummi *retgm, size_t *retsize, const char *panf, const char *pend)
{
  const char *pret;
  struct SML3_hashelem *he1;
  struct teilvar tv1;

  if (retgm == NULL || panf == NULL) {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    return NULL;
  }

  if (pend == NULL) { pend = panf + strlen(panf); }
  *SML3_gumgetval(retgm) = '\0';
  if (retsize != NULL) { *retsize = 0; }

  if (panf >= pend || *panf != '$') { return panf; }

  if ((pret = expand_var(vhptr, &he1, panf + 1, pend, 0, &tv1)) == NULL) {
    int cmax = ((pend - panf) > 32 ? 32 : (pend - panf));
    SML3_fehleradd("expanding \"%.*s...\"", cmax, panf);
    return NULL;
  }
  if (he1 != NULL) {
    const char *psub;
    size_t slen;
    psub = SML3_hashelem_valget(he1, &slen);
    psub = hole_teilwert(retgm, psub, &slen, &tv1);
    if (psub != NULL && slen > 0) {
      SML3_gummove(retgm, 0, psub, slen);
      SML3_gumcpy(retgm, slen, "");
    } else {
      *SML3_gumgetval(retgm) = '\0'; slen = 0;
    }
    if (retsize != NULL) { *retsize = slen; }
  }
  return pret;
} /* Ende SML3_formel_expandvalue */


/* SML3_formel_rechne [thread-sicher]:
 * errechnet Formel
 * Eine Formel besteht aus:
 *  - Zahlen:
 *    - Ganzzahlen
 *    - Bruchzahlen mit '.'
 *  - Variablen: 
 *    - Wert = String: repraesentiert Zahl
 *  - Operatoren:
 *    - Einer-Ops: Vorzeichen: + -
 *                 Negation: !
 *                 Einer-Komplement: ~
 *    - Zweier-Ops: Bitweise: & | ^   (niedrigste Prioritaet)
 *                  Shift: << >>      (naechsthoehere Prioritaet)
 *                  Strich: + -       (naechsthoehere Prioritaet)
 *                  Punkt: * / %      (hoechste Prioritaet)
 *  - Klammern
 *  - mathematischen Funktionen (z.B. sqrt())
 * 1.Arg: Struct fuer die Variablen-Hashes (struct SML3_formel_vhash)
 * 2.Arg: fuer Rueckgabe Formelwert als SML3_int64
 * 3.Arg: fuer Rueckgabe Formelwert als double
 * 4.Arg: Wert: gewuenschte Kommastellenanzahl je Einzelwert (0 - 9) oder -1 = egal
 *        Rueckgabe: 0 = Formelwert ist Ganzzahl, 1 = keine Ganzzahl
 * 5.Arg: Startpointer Formel
 * 6.Arg: Endepointer Gesamtstring (exkl.) oder NULL = bis Stringende
 * Rueckgabe: Pointer auf Zeichen hinter Formel oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ERANGE = Wert einer Funktion nicht darstellbar
 */
const char *
SML3_formel_rechne(struct SML3_formel_vhash *vhptr, SML3_int64 *rint, double *rfloat, int *prez, const char *panf, const char *pend)
{
  const char *pret;
  int prezdummy = -1;
  struct flintno fln;

  if (panf == NULL) {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    return NULL;
  }

  if (pend == NULL) { pend = panf + strlen(panf); }
  if (rint != NULL) { *rint = 0; }
  if (rfloat != NULL) { *rfloat = 0.; }
  if (panf >= pend) {
    if (prez != NULL) { *prez = 0; }
    return panf;
  }
  if (prez == NULL) { prez = &prezdummy; }
  if (*prez > 9) { *prez = 9; }

  fln.typ = 'i'; fln.wert.i = 0;
  if ((pret = formel_rechne_rek(vhptr, &fln, *prez, panf, pend)) == NULL) {
    int cmax = ((pend - panf) > 32 ? 32 : (pend - panf));
    SML3_fehleradd("calculating \"%.*s...\"", cmax, panf);
    return NULL;
  }
  if (fln.typ == 'f') {
    if (rfloat != NULL) { *rfloat = fln.wert.f; }
    if (rint != NULL) { *rint = (SML3_int64)fln.wert.f; }
    *prez = 1;
  } else {
    if (rfloat != NULL) { *rfloat = (double)fln.wert.i; }
    if (rint != NULL) { *rint = fln.wert.i; }
    *prez = 0;
  }

  return pret;
} /* Ende SML3_formel_rechne */


/* SML3_formel_expandstring [thread-sicher]:
 * ermittelt String (loest Variablen auf), setzt ihn in 2.+3.Arg.
 * Ermittlung geht bis zum nicht gequoteten Endezeichen, falls gesetzt,
 * maximal aber bis zum Endepointer Gesamtstring. Quotung mit Backslash.
 * Verarbeitet Escapesequenzen!
 * 1.Arg: Struct fuer die Variablen-Hashes (struct SML3_formel_vhash)
 * 2.Arg: fuer Rueckgabe ermittelten String
 * 3.Arg: Startposition im 2.Arg (wird aktualisiert)
 * 4.Arg: Endezeichen oder 0 = keins
 *        Falls 0 (kein Endezeichen), werden mehrere Spaces durch eins ersetzt
 *        und am Anfang und Ende entfernt,
 *        Falls 256 (auch kein Endezeichen), bleiben die Spaces
 *        (intern: falls < 0: kein Endezeichen, aber negiertes Zeichen
 *         fuer Sub-Endezeichen gequotet)
 * 5.Arg: Startpointer String
 * 6.Arg: Endepointer Gesamtstring (exkl.) oder NULL = bis Stringende
 * Rueckgabe: Pointer auf Zeichen hinter Stringende (z.B. Endezeichen)
 *            oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
const char *
SML3_formel_expandstring(struct SML3_formel_vhash *vhptr, struct SML3_gummi *retgm, size_t *retsize, int ezch, const char *panf, const char *pend)
{
  const char *pret, *pmrk;
  char ez, subez, rz;
  size_t eanz, s1, rsiz, stsiz;
  int zszieh, zsnicht;

  if (retgm == NULL || panf == NULL) {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    return NULL;
  }

  if (pend == NULL) { pend = panf + strlen(panf); }
  *SML3_gumgetval(retgm) = '\0';
  if (retsize != NULL) { stsiz = *retsize; } else { stsiz = 0; }

  if (panf >= pend) { return panf; }

  pmrk = panf;
  if (ezch == 256) { ezch = 0; zsnicht = 1; } else { zsnicht = 0; }
  if (ezch < 0) {
    ez = 0;
    subez = (char)-ezch;
  } else {
    ez = (char)ezch;
    subez = 0;
  }
  if (ez == 0) { zszieh = 1; } else { zszieh = 0; }
  rsiz = 0;

  for (pret = panf; pret < pend;) {

    if (*pret == '\\') {  /* Quotung */
      if (pret > panf) {  /* vorige Zeichen uebertragen */
        s1 = (size_t)(pret - panf);
        SML3_gummove(retgm, stsiz + rsiz, panf, s1);
        rsiz += s1;
        panf = pret;
      }

      if (subez != 0 && pret < pend - 1 && pret[1] == subez) {
        /* Sub-Endezeichen, Stringposition aktualisieren */
        zszieh = !zszieh;
        pret += 2;
        panf = pret;
      } else {
        /* Escape-Zeichen anhaengen und Stringposition aktualisieren */
        eanz = SML3_escapeseq_next(pret, (size_t)(pend - pret), &rz);
        if (eanz == 0) { break; }  /* unvollstaendige Escape-Sequenz */
        SML3_gummove(retgm, stsiz + rsiz, &rz, 1);
        rsiz++;
        pret += eanz;
        panf = pret;
      }

    } else if (*pret == '$') {  /* Variable */
      struct SML3_gummi gm = SML3_GUM_INITIALIZER;
      char *gptr;

      if (pret > panf) {  /* vorige Zeichen uebertragen */
        s1 = (size_t)(pret - panf);
        SML3_gummove(retgm, stsiz + rsiz, panf, s1);
        rsiz += s1;
        panf = pret;
      }

      panf = SML3_formel_expandvalue(vhptr, &gm, &eanz, pret, pend);
      if (panf == NULL) {
        int cmax = ((pend - pmrk) > 32 ? 32 : (pend - pmrk));
        SML3_fehleradd("string expanding \"%.*s...\"", cmax, pmrk);
        SML3_gumdest(&gm);
        return NULL;
      }
      gptr = SML3_gumgetval(&gm);
      if (eanz > 0 && gptr[eanz - 1] == '\0') { eanz--; }
      if (eanz > 0) {
        SML3_gummove(retgm, stsiz + rsiz, gptr, eanz);
        rsiz += eanz;
      }
      SML3_gumdest(&gm);
      pret = panf;

    } else if (ez != 0 && *pret == ez) {  /* Endezeichen */
      break;

    } else if (zszieh && !zsnicht && isspace((int)(unsigned char)*pret)) {
      pret++;
      if (pret != pmrk + 1) {  /* nicht am Anfang: vorige Zeichen und Whitespace uebertragen */
        s1 = (size_t)(pret - panf);
        SML3_gummove(retgm, stsiz + rsiz, panf, s1);
        rsiz += s1;
        panf = pret;
      }
      while (pret < pend) {
        if (!isspace((int)(unsigned char)*pret)) { break; }
        pret++;
      }
      if (pret == pend && rsiz > 0) { rsiz--; }  /* Whitespace am Ende entfernen */
      panf = pret;
    } else {
      pret++;
    }
  }

  if (subez != 0 && !zszieh) {
    int cmax = ((pend - pmrk) > 32 ? 32 : (pend - pmrk));
    SML3_fehlernew(EINVAL, "missing closing \\%c at EOF in string: \"%.*s...\"", subez, cmax, pmrk);
    return NULL;
  }

  if (pret > panf) {  /* vorige Zeichen uebertragen */
    s1 = (size_t)(pret - panf);
    SML3_gummove(retgm, stsiz + rsiz, panf, s1);
    rsiz += s1;
    panf = pret;
  }

  SML3_gumcpy(retgm, stsiz + rsiz, "");
  if (retsize != NULL) { *retsize = stsiz + rsiz; }

  return panf;
} /* Ende SML3_formel_expandstring */


/* SML3_formel_booltest [thread-sicher]:
 * testet Bool-Ausdruck,
 * - miteinander verglichen werden koennen:
 *   - Formeln, Vergleichsoperatoren sind:
 *     -eq  (gleich)
 *     -ne  (ungleich)
 *     -ge  (groesser gleich)
 *     -le  (kleiner gleich)
 *     -gt  (groesser)
 *     -lt  (kleiner)
 *   - Strings, Vergleichsoperatoren sind:
 *     =~  (reg. Ausdruck: enthaelt (case-insensitive))
 *     !~  (reg. Ausdruck: enthaelt nicht (case-insensitive))
 *     =   (gleich)
 *     !=  (ungleich)
 *     >=  (groesser gleich)
 *     <=  (kleiner gleich)
 *     >   (groesser)
 *     <   (kleiner)
 *   - die Stringvergleichoperatoren ohne reg. Ausdruck koennen mit Zusatz sein
 *     - {i}  = Vergleich case-insensitive
 *     - {u}  = Vergleich von 2 UTF-8 Strings (werden evtl. vorher konvertiert)
 *     - {ui} = Vergleich case-insensitive von 2 UTF-8 Strings (werden evtl. vorher konvertiert)
 *       {iu} = Vergleich case-insensitive von 2 UTF-8 Strings (werden evtl. vorher konvertiert)
 *     (Bsp: "Text1" >={ui} "Text2")
 * - einzelne Vergleiche koennen mit boolschem UND und ODER verbunden werden:
 *     &&  (UND)
 *     ||  (ODER)
 * - geklammert werden die Vergleiche mit '(' und ')'
 * 1.Arg: Struct fuer die Variablen-Hashes (struct SML3_formel_vhash)
 * 2.Arg: falls Formel: gewuenschte Kommastellenanzahl je Einzelwert oder -1 = egal
 * 3.Arg: Endezeichen oder 0 = keins
 * 4.Arg: Startpointer String
 * 5.Arg: Endepointer Gesamtstring (exkl.) oder NULL = bis Stringende
 * 6.Arg: fuer Rueckgabe, ob wahr (=1) oder falsch (=0)
 *        oder NULL = nicht ermitteln
 * Rueckgabe: Pointer auf Zeichen hinter Bool-Ausdruck oder NULL = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 */
const char *
SML3_formel_booltest(struct SML3_formel_vhash *vhptr, int prez, int ezch, const char *panf, const char *pend, int *istrue)
{
  struct s_booltest bstart, **bnext;
  const char *pmrk, *pendm;

  if (istrue != NULL) { *istrue = 0; }

  if (panf == NULL) {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    return NULL;
  }

  pendm = pend;
  if (pendm == NULL) { pendm = panf + strlen(panf); }
  bnext = &bstart.next;
  pmrk = panf;

  panf = booltest_aufbau(&bnext, ezch, vhptr, panf, pendm, istrue == NULL ? 0 : 1);
  if (panf == NULL) {
    int cmax = ((pendm - pmrk) > 32 ? 32 : (pendm - pmrk));
    SML3_fehleradd("testing \"%.*s...\"", cmax, pmrk);
    return NULL;
  }

  if (ezch != 0 && (panf == pendm || *panf != ezch)) {
    int cmax = ((pendm - pmrk) > 32 ? 32 : (pendm - pmrk));
    SML3_fehlernew(EINVAL, "missing closing '%c' at EOF, testing \"%.*s...\"", ezch, cmax, pmrk);
    return NULL;
  } else if (ezch == 0 && pend != NULL && panf != pend) {
    int cmax = ((pendm - pmrk) > 32 ? 32 : (pendm - pmrk));
    SML3_fehlernew(EINVAL, "invalid characters before EOF: \"%s\", testing \"%.*s...\"", panf, cmax, pmrk);
    return NULL;
  }

#ifdef DEBUG_BOOLTEST
  booltest_show(bstart.next, 0);
#endif /* DEBUG_BOOLTEST */

  if (istrue != NULL) {
    int ergbool = booltest_exec(bstart.next, vhptr, prez);
    if (ergbool < 0) {
      int cmax = ((pendm - pmrk) > 32 ? 32 : (pendm - pmrk));
      SML3_fehleradd("testing \"%.*s...\"", cmax, pmrk);
      return NULL;
    }
    *istrue = !!ergbool;
  }

  booltest_free(bstart.next);

  return panf;
} /* Ende SML3_formel_booltest */


/* SML3_formel_ebene_new [thread-sicher]:
 * gibt neues Struct fuer die Variablen-Hashes zurueck
 * 1.Arg: maximale Anzahl Ebenen
 * Rueckgabe: Struct fuer die Variablen-Hashes
 */
struct SML3_formel_vhash *
SML3_formel_ebene_new(int maxebene)
{
  struct SML3_formel_vhash *vhptr;
  int i1;

  if (maxebene < 1) { maxebene = 1; }
  if (maxebene > 1024) { maxebene = 1024; }

  vhptr = SML3_calloc(1, sizeof(*vhptr));
  vhptr->hs_global = SML3_hashnew(NULL, 0);
  vhptr->eb_max = maxebene;
  vhptr->eb_pos = 0;
  vhptr->ebene = SML3_calloc(maxebene, sizeof(*vhptr->ebene));
  for (i1 = 0; i1 < maxebene; i1++) {
    vhptr->ebene[i1].hs_lokal = SML3_hashnew(NULL, 0);
    vhptr->ebene[i1].hs_ref = SML3_hashnew(NULL, 0);
  }

  return vhptr;
} /* Ende SML3_formel_ebene_new */


/* SML3_formel_ebene_free [thread-sicher]:
 * gibt Struct fuer die Variablen-Hashes frei
 * 1.Arg: Adresse auf Struct fuer die Variablen-Hashes
 */
void
SML3_formel_ebene_free(struct SML3_formel_vhash **vhpptr)
{
  int i1;

  if (vhpptr == NULL || *vhpptr == NULL) { return; }
  SML3_hashfree(&(*vhpptr)->hs_global);
  for (i1 = 0; i1 < (*vhpptr)->eb_max; i1++) {
    SML3_hashfree(&(*vhpptr)->ebene[i1].hs_lokal);
    SML3_hashfree(&(*vhpptr)->ebene[i1].hs_ref);
  }
  free((*vhpptr)->ebene);
  free(*vhpptr);
  *vhpptr = NULL;
} /* Ende SML3_formel_ebene_free */


/* SML3_formel_ebene_down [thread-sicher]:
 * geht eine Ebene hinab
 * 1.Arg: Struct fuer die Variablen-Hashes
 * 2.Arg: Hash mit Referenzvariablen: <Referenzname> = <referenzierter Variablenname>
 *                                    (z.B.:  "#name" = "D[1][2]")
 *        oder NULL
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOSPC = tiefste Ebene bereits erreicht
 */
int
SML3_formel_ebene_down(struct SML3_formel_vhash *vhptr, struct SML3_hash *hsref)
{
  if (vhptr == NULL) { SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL)); return -1; }
  if (vhptr->eb_pos >= vhptr->eb_max - 1) { SML3_fehlernew(ENOSPC, "bottom ebene %d already reached", vhptr->eb_max); return -1; }

  if (hsref != NULL) {
    struct SML3_hashelem *he1, *he2;
    char *pkey, *pval;
    struct SML3_gummi gm1 = SML3_GUM_INITIALIZER;
    size_t gmpos = 0;

    for (he1 = SML3_hashlist(hsref, NULL, 0); he1 != NULL; he1 = SML3_hashlist(hsref, he1, 0)) {
      pkey = (char *)SML3_hashelem_keyget(he1, NULL);
      pval = (char *)SML3_hashelem_valget(he1, NULL);
      if (pkey[0] != '#' || pkey[1] == '\0' || *pval == '\0') { continue; }
      pkey++;  /* '#' ueberspringen, soll nicht im Key enthalten sein */
      if (strspn(pkey, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_") != strlen(pkey)) { continue; }
      SML3_gumcpy(&gm1, 0, ""); gmpos = 0;
      if (SML3_formel_expandstring(vhptr, &gm1, &gmpos, 0, pval, NULL) == NULL) { continue; }
      /* Referenzvariablenhash setzen mit: <Referenzvariable ohne '#'> = <expandierter Value = referenzierter Variablenname> */
      pval = SML3_gumgetval(&gm1);
      if (*pval == '@') { pval++; gmpos--; }  /* Feldzeichen "@" entfernen */
      he2 = SML3_hashset(vhptr->ebene[vhptr->eb_pos + 1].hs_ref, pkey, strlen(pkey) + 1);  /* in naechsttieferer Ebene setzen */
      SML3_hashelem_valset(he2, pval, gmpos + 1);
    }

    SML3_gumdest(&gm1);
  }

  vhptr->eb_pos++;
  return 0;
} /* Ende SML3_formel_ebene_down */


/* SML3_formel_ebene_up [thread-sicher]:
 * geht eine Ebene hinauf
 * 1.Arg: Struct fuer die Variablen-Hashes
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOSPC = hoechste Ebene bereits erreicht
 */
int
SML3_formel_ebene_up(struct SML3_formel_vhash *vhptr)
{
  if (vhptr == NULL) { SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL)); return -1; }
  if (vhptr->eb_pos <= 0) { SML3_fehlernew(ENOSPC, "top ebene already reached"); return -1; }
  
  SML3_hashclear(vhptr->ebene[vhptr->eb_pos].hs_lokal);
  SML3_hashclear(vhptr->ebene[vhptr->eb_pos].hs_ref);

  vhptr->eb_pos--;
  return 0;
} /* Ende SML3_formel_ebene_up */
