/*************************************************************************\
* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/

/* Author:  Marty Kraimer Date:    13JUL95*/

/*The routines in this module are serially reusable NOT reentrant*/

#include <ctype.h>
#include <epicsStdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "dbDefs.h"
#include "dbmf.h"
#include "ellLib.h"
#include "epicsPrint.h"
#include "epicsString.h"
#include "errMdef.h"
#include "freeList.h"
#include "gpHash.h"
#include "macLib.h"

#include "dbBase.h"
#include "dbFldTypes.h"
#include "dbStaticLib.h"
#include "dbStaticPvt.h"
#include "epicsExport.h"
#include "epicsAssert.h"
#include "link.h"
#include "special.h"
#include "iocInit.h"

/* This file is included from dbYacc.y
 * Duplicate some declarations to avoid warnings
 * from analysis tools that don't know about this.
 */
static int yyerror(char *str);
static long pvt_yy_parse(void);

/*global declarations*/
char *makeDbdDepends=0;

int dbRecordsOnceOnly=0;
epicsExportAddress(int,dbRecordsOnceOnly);

int dbBptNotMonotonic=0;
epicsExportAddress(int,dbBptNotMonotonic);

int dbQuietMacroWarnings=0;
epicsExportAddress(int,dbQuietMacroWarnings);

int dbRecordsAbcSorted=0;
epicsExportAddress(int,dbRecordsAbcSorted);

/*private routines */
static void yyerrorAbort(char *str);
static void allocTemp(void *pvoid);
static void *popFirstTemp(void);
static void *getLastTemp(void);
static int db_yyinput(char *buf,int max_size);
static void dbIncludePrint(void);
static void dbPathCmd(char *path);
static void dbAddPathCmd(char *path);
static void dbIncludeNew(char *include_file);
static void dbMenuHead(char *name);
static void dbMenuChoice(char *name,char *value);
static void dbMenuBody(void);

static void dbRecordtypeHead(char *name);
static void dbRecordtypeEmpty(void);
static void dbRecordtypeBody(void);
static void dbRecordtypeFieldHead(char *name,char *type);
static void dbRecordtypeFieldItem(char *name,char *value);
static short findOrAddGuiGroup(const char *name);

static void dbDevice(char *recordtype,char *linktype,
        char *dsetname,char *choicestring);
static void dbDriver(char *name);
static void dbLinkType(char *name, char *jlif_name);
static void dbRegistrar(char *name);
static void dbFunction(char *name);
static void dbVariable(char *name, char *type);

static void dbBreakHead(char *name);
static void dbBreakItem(char *value);
static void dbBreakBody(void);

static void dbRecordHead(char *recordType,char*name,int visible);
static void dbRecordField(char *name,char *value);
static void dbRecordBody(void);

/*private declarations*/
#define MY_BUFFER_SIZE 1024
static char *my_buffer=NULL;
static char *mac_input_buffer=NULL;
static char *my_buffer_ptr=NULL;
static MAC_HANDLE *macHandle = NULL;
typedef struct inputFile{
    ELLNODE     node;
    const char  *path;
    const char  *filename;
    FILE        *fp;
    int         line_num;
}inputFile;
static ELLLIST inputFileList = ELLLIST_INIT;

static inputFile *pinputFileNow = NULL;
/* The DBBASE most recently allocated/used by dbReadCOM() */
static DBBASE *savedPdbbase = NULL;

typedef struct tempListNode {
    ELLNODE     node;
    void        *item;
}tempListNode;

static ELLLIST tempList = ELLLIST_INIT;
static void *freeListPvt = NULL;
static int duplicate = FALSE;

static void yyerrorAbort(char *str)
{
    yyerror(str);
    yyAbort = TRUE;
}

static void allocTemp(void *pvoid)
{
    tempListNode        *ptempListNode;

    ptempListNode = freeListCalloc(freeListPvt);
    ptempListNode->item = pvoid;
    ellAdd(&tempList,&ptempListNode->node);
}

static void *popFirstTemp(void)
{
    tempListNode        *ptempListNode;
    void                *ptemp = NULL;

    ptempListNode = (tempListNode *)ellFirst(&tempList);
    if(ptempListNode) {
        ptemp = ptempListNode->item;
        ellDelete(&tempList,(ELLNODE *)ptempListNode);
        freeListFree(freeListPvt,ptempListNode);
    }
    return(ptemp);
}

static void *getLastTemp(void)
{
    tempListNode        *ptempListNode;

    ptempListNode = (tempListNode *)ellLast(&tempList);
    return(ptempListNode->item);
}

const char *dbOpenFile(DBBASE *pdbbase,const char *filename,FILE **fp)
{
    ELLLIST     *ppathList = (ELLLIST *)pdbbase->pathPvt;
    dbPathNode  *pdbPathNode;
    char        *fullfilename;

    *fp = 0;
    if (!filename) return 0;
    if (!ppathList || ellCount(ppathList) == 0 ||
        strchr(filename, '/') || strchr(filename, '\\')) {
        *fp = fopen(filename, "r");
        if (*fp && makeDbdDepends)
            fprintf(stdout, "%s:%s \n", makeDbdDepends, filename);
        return 0;
    }
    pdbPathNode = (dbPathNode *)ellFirst(ppathList);
    while (pdbPathNode) {
        fullfilename = dbMalloc(strlen(pdbPathNode->directory) +
            strlen(filename) + 2);
        strcpy(fullfilename, pdbPathNode->directory);
        strcat(fullfilename, "/");
        strcat(fullfilename, filename);
        *fp = fopen(fullfilename, "r");
        if (*fp && makeDbdDepends)
            fprintf(stdout, "%s:%s \n", makeDbdDepends, fullfilename);
        free(fullfilename);
        if (*fp) return pdbPathNode->directory;
        pdbPathNode = (dbPathNode *)ellNext(&pdbPathNode->node);
    }
    return 0;
}


static void freeInputFileList(void)
{
    inputFile *pinputFileNow;

    while((pinputFileNow=(inputFile *)ellFirst(&inputFileList))) {
        if(fclose(pinputFileNow->fp))
            fprintf(stderr, ERL_WARNING
                ": Error closing file '%s': %s\n",
                pinputFileNow->filename, strerror(errno));
        free((void *)pinputFileNow->filename);
        ellDelete(&inputFileList,(ELLNODE *)pinputFileNow);
        free(pinputFileNow);
    }
}

static
int cmp_dbRecordNode(const ELLNODE *lhs, const ELLNODE *rhs)
{
    dbRecordNode *LHS = (dbRecordNode*)lhs,
                 *RHS = (dbRecordNode*)rhs;

    return strcmp(LHS->recordname, RHS->recordname);
}

static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
        const char *path,const char *substitutions)
{
    long        status;
    inputFile   *pinputFile = NULL;
    char        *penv;
    char        **macPairs;

    if (ellCount(&tempList)) {
        fprintf(stderr, ERL_WARNING
            ": dbReadCOM: Parser stack dirty %d\n", ellCount(&tempList));
    }

    if (getIocState() != iocVoid) {
        status = -2;
        goto cleanup;
    }

    errlogInit(0); /* Initialize the errSymTable */

    if(*ppdbbase == 0) *ppdbbase = dbAllocBase();
    savedPdbbase = *ppdbbase;
    if(path && strlen(path)>0) {
        dbPath(savedPdbbase,path);
    } else {
        penv = getenv("EPICS_DB_INCLUDE_PATH");
        if(penv) {
            dbPath(savedPdbbase,penv);
        } else {
            dbPath(savedPdbbase,".");
        }
    }
    my_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char));
    freeListInitPvt(&freeListPvt,sizeof(tempListNode),100);
    if (substitutions == NULL)
        substitutions = "";
    if(macCreateHandle(&macHandle,NULL)) {
        fprintf(stderr, ERL_ERROR ": macCreateHandle failed\n");
        status = -1;
        goto cleanup;
    }
    macParseDefns(macHandle,substitutions,&macPairs);
    if(macPairs == NULL) {
        macDeleteHandle(macHandle);
        macHandle = NULL;
    } else {
        macInstallMacros(macHandle,macPairs);
        free(macPairs);
        mac_input_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char));
    }
    macSuppressWarning(macHandle,dbQuietMacroWarnings);
    pinputFile = dbCalloc(1,sizeof(inputFile));
    if (filename) {
        pinputFile->filename = macEnvExpand(filename);
    }
    if (!fp) {
        FILE *fp1 = 0;

        if (pinputFile->filename)
            pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp1);
        if (!pinputFile->filename || !fp1) {
            fprintf(stderr, ERL_ERROR
                ": Can't open file '%s'\n", pinputFile->filename);
            free((char*)pinputFile->filename);
            free(pinputFile);
            status = -1;
            goto cleanup;
        }
        pinputFile->fp = fp1;
    } else {
        pinputFile->fp = fp;
        fp = NULL;
    }
    pinputFile->line_num = 0;
    pinputFileNow = pinputFile;
    my_buffer[0] = '\0';
    my_buffer_ptr = my_buffer;
    ellAdd(&inputFileList,&pinputFile->node);
    status = pvt_yy_parse();

    if (ellCount(&tempList) && !yyAbort)
        fprintf(stderr, ERL_WARNING
            ": dbReadCOM: Parser stack dirty w/o error. %d\n",
            ellCount(&tempList));
    while (ellCount(&tempList))
        popFirstTemp(); /* Memory leak on parser failure */

    dbFreePath(savedPdbbase);
    if(!status) { /*add RTYP and VERS as an attribute */
        DBENTRY dbEntry;
        DBENTRY *pdbEntry = &dbEntry;
        long    localStatus;

        dbInitEntry(savedPdbbase,pdbEntry);
        localStatus = dbFirstRecordType(pdbEntry);
        while(!localStatus) {
            localStatus = dbPutRecordAttribute(pdbEntry,"RTYP",
                dbGetRecordTypeName(pdbEntry));
            if(!localStatus)  {
                localStatus = dbPutRecordAttribute(pdbEntry,"VERS",
                    "none specified");
            }
            if(localStatus) {
                fprintf(stderr,"dbPutRecordAttribute status %ld\n",status);
            } else {
                localStatus = dbNextRecordType(pdbEntry);
            }
        }
        dbFinishEntry(pdbEntry);
    }
cleanup:
    if(dbRecordsAbcSorted) {
        ELLNODE *cur;
        for(cur = ellFirst(&savedPdbbase->recordTypeList); cur; cur=ellNext(cur))
        {
            dbRecordType *rtype = CONTAINER(cur, dbRecordType, node);

            ellSortStable(&rtype->recList, &cmp_dbRecordNode);
        }
    }
    if(macHandle) macDeleteHandle(macHandle);
    macHandle = NULL;
    if(mac_input_buffer) free(mac_input_buffer);
    mac_input_buffer = NULL;
    if(freeListPvt) freeListCleanup(freeListPvt);
    freeListPvt = NULL;
    if(my_buffer) free(my_buffer);
    my_buffer = NULL;
    freeInputFileList();
    if(fp)
        fclose(fp);
    return(status);
}

long dbReadDatabase(DBBASE **ppdbbase, const char *filename,
    const char *path, const char *substitutions)
{
    return dbReadCOM(ppdbbase, filename, 0, path, substitutions);
}

long dbReadDatabaseFP(DBBASE **ppdbbase, FILE *fp,
    const char *path, const char *substitutions)
{
    return dbReadCOM(ppdbbase, 0, fp, path, substitutions);
}

static int db_yyinput(char *buf, int max_size)
{
    size_t  l,n;
    char        *fgetsRtn;

    if(yyAbort) return(0);
    if(*my_buffer_ptr==0) {
        while(TRUE) { /*until we get some input*/
            if(macHandle) {
                fgetsRtn = fgets(mac_input_buffer,MY_BUFFER_SIZE,
                        pinputFileNow->fp);
                if(fgetsRtn) {
                    int exp = macExpandString(macHandle,mac_input_buffer,
                        my_buffer,MY_BUFFER_SIZE);
                    if (exp < 0) {
                        fprintf(stderr, ERL_WARNING
                            ": '%s' line %d has undefined macros\n",
                            pinputFileNow->filename, pinputFileNow->line_num+1);
                    }
                }
            } else {
                fgetsRtn = fgets(my_buffer,MY_BUFFER_SIZE,pinputFileNow->fp);
            }
            if(fgetsRtn) break;
            if(fclose(pinputFileNow->fp))
                fprintf(stderr, ERL_WARNING
                    ": Error closing file '%s': %s\n",
                    pinputFileNow->filename, strerror(errno));
            free((void *)pinputFileNow->filename);
            ellDelete(&inputFileList,(ELLNODE *)pinputFileNow);
            free(pinputFileNow);
            pinputFileNow = (inputFile *)ellLast(&inputFileList);
            if(!pinputFileNow) return(0);
        }
        if(dbStaticDebug) fprintf(stderr,"%s",my_buffer);
        pinputFileNow->line_num++;
        my_buffer_ptr = &my_buffer[0];
    }
    l = strlen(my_buffer_ptr);
    n = (l<=max_size ? l : max_size);
    memcpy(buf,my_buffer_ptr,n);
    my_buffer_ptr += n;
    return (int)n;
}

static void dbIncludePrint(void)
{
    inputFile *pinputFile = pinputFileNow;

    while (pinputFile) {
        fprintf(stderr, " in");
        if (pinputFile->path)
            fprintf(stderr, " path \"%s\" ",pinputFile->path);
        if (pinputFile->filename) {
            fprintf(stderr, " file \"%s\"",pinputFile->filename);
        } else {
            fprintf(stderr, " standard input");
        }
        fprintf(stderr, " line %d\n",pinputFile->line_num);
        pinputFile = (inputFile *)ellPrevious(&pinputFile->node);
    }
    return;
}

static void dbPathCmd(char *path)
{
    dbPath(savedPdbbase,path);
}

static void dbAddPathCmd(char *path)
{
    dbAddPath(savedPdbbase,path);
}

static void dbIncludeNew(char *filename)
{
    inputFile   *pinputFile;
    FILE        *fp;

    pinputFile = dbCalloc(1,sizeof(inputFile));
    pinputFile->filename = macEnvExpand(filename);
    pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp);
    if (!fp) {
        fprintf(stderr, ERL_ERROR ": Can't open include file '%s'\n", filename);
        yyerror(NULL);
        free((void *)pinputFile->filename);
        free(pinputFile);
        return;
    }
    pinputFile->fp = fp;
    ellAdd(&inputFileList,&pinputFile->node);
    pinputFileNow = pinputFile;
}

static void dbMenuHead(char *name)
{
    dbMenu              *pdbMenu;
    GPHENTRY            *pgphentry;

    if (!*name) {
        yyerrorAbort("dbMenuHead: Menu name can't be empty");
        return;
    }
    pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->menuList);
    if(pgphentry) {
        duplicate = TRUE;
        return;
    }
    if(ellCount(&tempList)) yyerrorAbort("dbMenuHead: tempList not empty");
    pdbMenu = dbCalloc(1,sizeof(dbMenu));
    pdbMenu->name = epicsStrDup(name);
    allocTemp(pdbMenu);
}

static void dbMenuChoice(char *name,char *value)
{
    if (!*name) {
        yyerror("dbMenuChoice: Menu choice name can't be empty");
        return;
    }
    if(duplicate) return;
    allocTemp(epicsStrDup(name));
    allocTemp(epicsStrDup(value));
}

static void dbMenuBody(void)
{
    dbMenu              *pnewMenu;
    dbMenu              *pMenu;
    int                 nChoice;
    int                 i;
    GPHENTRY            *pgphentry;

    if(duplicate) {
        duplicate = FALSE;
        return;
    }
    pnewMenu = (dbMenu *)popFirstTemp();
    if(!pnewMenu)
        return;
    pnewMenu->nChoice = nChoice = ellCount(&tempList)/2;
    pnewMenu->papChoiceName = dbCalloc(pnewMenu->nChoice,sizeof(char *));
    pnewMenu->papChoiceValue = dbCalloc(pnewMenu->nChoice,sizeof(char *));
    for(i=0; i<nChoice; i++) {
        pnewMenu->papChoiceName[i] = (char *)popFirstTemp();
        pnewMenu->papChoiceValue[i] = (char *)popFirstTemp();
        if(!pnewMenu->papChoiceName[i] || !pnewMenu->papChoiceValue[i])
            return;
    }
    if(ellCount(&tempList)) yyerrorAbort("dbMenuBody: tempList not empty");
    /* Add menu in sorted order */
    pMenu = (dbMenu *)ellFirst(&savedPdbbase->menuList);
    while(pMenu && strcmp(pMenu->name,pnewMenu->name) >0 )
        pMenu = (dbMenu *)ellNext(&pMenu->node);
    if(pMenu)
        ellInsert(&savedPdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node);
    else
        ellAdd(&savedPdbbase->menuList,&pnewMenu->node);
    pgphentry = gphAdd(savedPdbbase->pgpHash,pnewMenu->name,&savedPdbbase->menuList);
    if(!pgphentry) {
        yyerrorAbort("gphAdd failed");
    } else {
        pgphentry->userPvt = pnewMenu;
    }
}

static void dbRecordtypeHead(char *name)
{
    dbRecordType        *pdbRecordType;
    GPHENTRY            *pgphentry;

    if (!*name) {
        yyerrorAbort("dbRecordtypeHead: Recordtype name can't be empty");
        return;
    }
    pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->recordTypeList);
    if(pgphentry) {
        duplicate = TRUE;
        return;
    }
    pdbRecordType = dbCalloc(1,sizeof(dbRecordType));
    pdbRecordType->name = epicsStrDup(name);
    if (savedPdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList);
    if(ellCount(&tempList))
        yyerrorAbort("dbRecordtypeHead tempList not empty");
    allocTemp(pdbRecordType);
}

static void dbRecordtypeFieldHead(char *name,char *type)
{
    dbFldDes            *pdbFldDes;
    int                 i;

    if (!*name) {
        yyerrorAbort("dbRecordtypeFieldHead: Field name can't be empty");
        return;
    }
    if(duplicate) return;
    pdbFldDes = dbCalloc(1,sizeof(dbFldDes));
    allocTemp(pdbFldDes);
    pdbFldDes->name = epicsStrDup(name);
    pdbFldDes->as_level = ASL1;
    pdbFldDes->isDevLink = strcmp(pdbFldDes->name, "INP")==0 ||
            strcmp(pdbFldDes->name, "OUT")==0;
    i = dbFindFieldType(type);
    if (i < 0)
        yyerrorAbort("Invalid Field Type");
    pdbFldDes->field_type = i;
}

static short findOrAddGuiGroup(const char *name)
{
    dbGuiGroup *pdbGuiGroup;
    GPHENTRY   *pgphentry;
    pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->guiGroupList);
    if (!pgphentry) {
        pdbGuiGroup = dbCalloc(1,sizeof(dbGuiGroup));
        pdbGuiGroup->name = epicsStrDup(name);
        ellAdd(&savedPdbbase->guiGroupList, &pdbGuiGroup->node);
        pdbGuiGroup->key = ellCount(&savedPdbbase->guiGroupList);
        pgphentry = gphAdd(savedPdbbase->pgpHash, pdbGuiGroup->name, &savedPdbbase->guiGroupList);
        pgphentry->userPvt = pdbGuiGroup;
    }
    return ((dbGuiGroup *)pgphentry->userPvt)->key;
}

static void dbRecordtypeFieldItem(char *name,char *value)
{
    dbFldDes            *pdbFldDes;

    if(duplicate) return;
    pdbFldDes = (dbFldDes *)getLastTemp();
    if(strcmp(name,"asl")==0) {
        if(strcmp(value,"ASL0")==0) {
            pdbFldDes->as_level = ASL0;
        } else if(strcmp(value,"ASL1")==0) {
            pdbFldDes->as_level = ASL1;
        } else {
            yyerror("Invalid 'asl' value, must be ASL0 or ASL1");
        }
        return;
    }
    if(strcmp(name,"initial")==0) {
        pdbFldDes->initial = epicsStrDup(value);
        return;
    }
    if(strcmp(name,"promptgroup")==0) {
        pdbFldDes->promptgroup = findOrAddGuiGroup(value);
        return;
    }
    if(strcmp(name,"prompt")==0) {
        pdbFldDes->prompt = epicsStrDup(value);
        return;
    }
    if(strcmp(name,"special")==0) {
        int     i;
        for(i=0; i<SPC_NTYPES; i++) {
            if(strcmp(value,pamapspcType[i].strvalue)==0) {
                pdbFldDes->special = pamapspcType[i].value;
                return;
            }
        }
        if(sscanf(value,"%hd",&pdbFldDes->special)==1) {
            return;
        }
        yyerror("Invalid 'special' value.");
        return;
    }
    if(strcmp(name,"pp")==0) {
        if((strcmp(value,"YES")==0) || (strcmp(value,"TRUE")==0)) {
            pdbFldDes->process_passive = TRUE;
        } else if((strcmp(value,"NO")==0) || (strcmp(value,"FALSE")==0)) {
            pdbFldDes->process_passive = FALSE;
        } else {
            yyerror("Invalid 'pp' value, must be YES/NO/TRUE/FALSE");
        }
        return;
    }
    if(strcmp(name,"interest")==0) {
        if(sscanf(value,"%hd",&pdbFldDes->interest)!=1)
            yyerror("Invalid 'interest' value, must be integer");
        return;
    }
    if(strcmp(name,"base")==0) {
        if(strcmp(value,"DECIMAL")==0) {
            pdbFldDes->base = CT_DECIMAL;
        } else if(strcmp(value,"HEX")==0) {
            pdbFldDes->base = CT_HEX;
        } else {
            yyerror("Invalid 'base' value, must be DECIMAL/HEX");
        }
        return;
    }
    if(strcmp(name,"size")==0) {
        if(sscanf(value,"%hd",&pdbFldDes->size)!=1)
            yyerror("Invalid 'size' value, must be integer");
        return;
    }
    if(strcmp(name,"extra")==0) {
        pdbFldDes->extra = epicsStrDup(value);
        return;
    }
    if(strcmp(name,"menu")==0) {
        pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(savedPdbbase,value);
        if(!savedPdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt)
            yyerrorAbort("menu not found");
        return;
    }
    if(strcmp(name,"prop")==0) {
        if(strcmp(value, "YES")==0)
            pdbFldDes->prop = 1;
        else
            pdbFldDes->prop = 0;
        return;
    }
}

static void dbRecordtypeCdef(char *text) {
    dbText              *pdbCdef;
    tempListNode        *ptempListNode;
    dbRecordType        *pdbRecordType;

    if (!savedPdbbase->loadCdefs || duplicate) return;
    ptempListNode = (tempListNode *)ellFirst(&tempList);
    pdbRecordType = ptempListNode->item;

    pdbCdef = dbCalloc(1,sizeof(dbText));
    if (text[0] == ' ') text++; /* strip leading space if present */
    pdbCdef->text = epicsStrDup(text);
    ellAdd(&pdbRecordType->cdefList, &pdbCdef->node);
    return;
}

static void dbRecordtypeEmpty(void)
{
    tempListNode *ptempListNode;
    dbRecordType *pdbRecordType;

    if (duplicate) {
        duplicate = FALSE;
        return;
    }

    ptempListNode = (tempListNode *)ellFirst(&tempList);
    pdbRecordType = ptempListNode->item;
    fprintf(stderr, ERL_ERROR
        ": Declaration of recordtype(%s) preceded full definition.\n",
        pdbRecordType->name);
    yyerrorAbort(NULL);
}

static void dbRecordtypeBody(void)
{
    dbRecordType        *pdbRecordType;
    dbFldDes            *pdbFldDes;
    int                 i,j,ilink;
    GPHENTRY            *pgphentry;
    int                 no_fields,no_prompt,no_links;
    dbfType             field_type;
    char                *psortFldNameTemp;
    short               psortFldIndTemp;
    char                **papsortFldName;
    short               *sortFldInd;

    if(duplicate) {
        duplicate = FALSE;
        return;
    }
    pdbRecordType= (dbRecordType *)popFirstTemp();
    if(!pdbRecordType)
        return;
    pdbRecordType->no_fields = no_fields = ellCount(&tempList);
    pdbRecordType->papFldDes = dbCalloc(no_fields,sizeof(dbFldDes *));
    pdbRecordType->papsortFldName = dbCalloc(no_fields,sizeof(char *));
    pdbRecordType->sortFldInd = dbCalloc(no_fields,sizeof(short));
    no_prompt = no_links = 0;
    for(i=0; i<no_fields; i++) {
        pdbFldDes = (dbFldDes *)popFirstTemp();
        if(!pdbFldDes)
            return;
        pdbFldDes->pdbRecordType = pdbRecordType;
        pdbFldDes->indRecordType = i;
        pdbRecordType->papFldDes[i] = pdbFldDes;
        if(pdbFldDes->promptgroup) no_prompt++;
        field_type = pdbFldDes->field_type;
        if((field_type>=DBF_INLINK) && (field_type<=DBF_FWDLINK))no_links++;
        if((field_type==DBF_STRING) && (pdbFldDes->size==0))
            fprintf(stderr, ERL_ERROR
                ": recordtype(%s).%s size not specified\n",
                pdbRecordType->name,pdbFldDes->name);
        if((field_type==DBF_NOACCESS) && (pdbFldDes->extra==0))
            fprintf(stderr, ERL_ERROR
                ": recordtype(%s).%s extra not specified\n",
                pdbRecordType->name,pdbFldDes->name);
    }
    if (ellCount(&tempList))
        yyerrorAbort("dbRecordtypeBody: tempList not empty");
    pdbRecordType->no_prompt = no_prompt;
    pdbRecordType->no_links = no_links;
    pdbRecordType->link_ind = dbCalloc(no_links,sizeof(short));
    ilink = 0;
    for(i=0; i<no_fields; i++) {
        pdbFldDes = pdbRecordType->papFldDes[i];
        /* if prompt is null make it a null string */
        if(!pdbFldDes->prompt) pdbFldDes->prompt = dbCalloc(1,sizeof(char));
        field_type = pdbFldDes->field_type;
        if((field_type>=DBF_INLINK) && (field_type<=DBF_FWDLINK))
            pdbRecordType->link_ind[ilink++] = i;
        if(strcmp(pdbFldDes->name,"VAL")==0) {
            pdbRecordType->pvalFldDes = pdbRecordType->papFldDes[i];
            pdbRecordType->indvalFlddes = i;
        }
        pdbRecordType->papsortFldName[i] = pdbFldDes->name;
        pdbRecordType->sortFldInd[i] = i;
    }
   /*Now sort fields. Sorry dumb sort algorithm */
   papsortFldName = pdbRecordType->papsortFldName;
   sortFldInd = pdbRecordType->sortFldInd;
   for(i=0; i<no_fields; i++) {
        for(j=i+1; j<no_fields; j++) {
            if(strcmp(papsortFldName[j],papsortFldName[i])<0 ) {
                psortFldNameTemp = papsortFldName[j];
                psortFldIndTemp = sortFldInd[j];
                papsortFldName[j] = papsortFldName[i];
                sortFldInd[j] = sortFldInd[i];
                papsortFldName[i] = psortFldNameTemp;
                sortFldInd[i] = psortFldIndTemp;
            }
        }
    }
    /*Initialize lists*/
    ellInit(&pdbRecordType->attributeList);
    ellInit(&pdbRecordType->recList);
    ellInit(&pdbRecordType->devList);
    pgphentry = gphAdd(savedPdbbase->pgpHash,pdbRecordType->name,
        &savedPdbbase->recordTypeList);
    if(!pgphentry) {
        yyerrorAbort("gphAdd failed");
    } else {
        pgphentry->userPvt = pdbRecordType;
    }
    ellAdd(&savedPdbbase->recordTypeList,&pdbRecordType->node);
}

static void dbDevice(char *recordtype,char *linktype,
        char *dsetname,char *choicestring)
{
    devSup              *pdevSup;
    dbRecordType        *pdbRecordType;
    GPHENTRY            *pgphentry;
    int                 i,link_type;
    pgphentry = gphFind(savedPdbbase->pgpHash,recordtype,&savedPdbbase->recordTypeList);
    if(!pgphentry) {
        fprintf(stderr, ERL_ERROR
            ": Record type '%s' not found for device '%s'\n",
            recordtype, choicestring);
        yyerror(NULL);
        return;
    }
    link_type=-1;
    for(i=0; i<LINK_NTYPES; i++) {
        if(strcmp(pamaplinkType[i].strvalue,linktype)==0) {
            link_type = pamaplinkType[i].value;
            break;
        }
    }
    if(link_type==-1) {
        fprintf(stderr, ERL_ERROR
            ": Bad link type '%s' for device '%s'\n",
            linktype, choicestring);
        yyerror(NULL);
        return;
    }
    pdbRecordType = (dbRecordType *)pgphentry->userPvt;
    pgphentry = gphFind(savedPdbbase->pgpHash,choicestring,&pdbRecordType->devList);
    if(pgphentry) {
        return;
    }
    pdevSup = dbCalloc(1,sizeof(devSup));
    pdevSup->name = epicsStrDup(dsetname);
    pdevSup->choice = epicsStrDup(choicestring);
    pdevSup->link_type = link_type;
    pgphentry = gphAdd(savedPdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList);
    if(!pgphentry) {
        yyerrorAbort("gphAdd failed");
    } else {
        pgphentry->userPvt = pdevSup;
    }
    ellAdd(&pdbRecordType->devList,&pdevSup->node);
}

static void dbDriver(char *name)
{
    drvSup      *pdrvSup;
    GPHENTRY    *pgphentry;

    if (!*name) {
        yyerrorAbort("dbDriver: Driver name can't be empty");
        return;
    }
    pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->drvList);
    if(pgphentry) {
        return;
    }
    pdrvSup = dbCalloc(1,sizeof(drvSup));
    pdrvSup->name = epicsStrDup(name);
    pgphentry = gphAdd(savedPdbbase->pgpHash,pdrvSup->name,&savedPdbbase->drvList);
    if(!pgphentry) {
        yyerrorAbort("gphAdd failed");
    }
    pgphentry->userPvt = pdrvSup;
    ellAdd(&savedPdbbase->drvList,&pdrvSup->node);
}

static void dbLinkType(char *name, char *jlif_name)
{
    linkSup *pLinkSup;
    GPHENTRY *pgphentry;

    pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->linkList);
    if (pgphentry) {
        return;
    }
    pLinkSup = dbCalloc(1,sizeof(linkSup));
    pLinkSup->name = epicsStrDup(name);
    pLinkSup->jlif_name = epicsStrDup(jlif_name);
    pgphentry = gphAdd(savedPdbbase->pgpHash, pLinkSup->name, &savedPdbbase->linkList);
    if (!pgphentry) {
        yyerrorAbort("gphAdd failed");
    }
    pgphentry->userPvt = pLinkSup;
    ellAdd(&savedPdbbase->linkList, &pLinkSup->node);
}

static void dbRegistrar(char *name)
{
    dbText      *ptext;
    GPHENTRY    *pgphentry;

    if (!*name) {
        yyerrorAbort("dbRegistrar: Registrar name can't be empty");
        return;
    }
    pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->registrarList);
    if(pgphentry) {
        return;
    }
    ptext = dbCalloc(1,sizeof(dbText));
    ptext->text = epicsStrDup(name);
    pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->registrarList);
    if(!pgphentry) {
        yyerrorAbort("gphAdd failed");
    }
    pgphentry->userPvt = ptext;
    ellAdd(&savedPdbbase->registrarList,&ptext->node);
}

static void dbFunction(char *name)
{
    dbText     *ptext;
    GPHENTRY   *pgphentry;

    if (!*name) {
        yyerrorAbort("dbFunction: Function name can't be empty");
        return;
    }
    pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->functionList);
    if(pgphentry) {
       return;
    }
    ptext = dbCalloc(1,sizeof(dbText));
    ptext->text = epicsStrDup(name);
    pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->functionList);
    if(!pgphentry) {
       yyerrorAbort("gphAdd failed");
    }
    pgphentry->userPvt = ptext;
    ellAdd(&savedPdbbase->functionList,&ptext->node);
}

static void dbVariable(char *name, char *type)
{
    dbVariableDef       *pvar;
    GPHENTRY            *pgphentry;

    if (!*name) {
        yyerrorAbort("dbVariable: Variable name can't be empty");
        return;
    }
    pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->variableList);
    if(pgphentry) {
        return;
    }
    pvar = dbCalloc(1,sizeof(dbVariableDef));
    pvar->name = epicsStrDup(name);
    pvar->type = epicsStrDup(type);
    pgphentry = gphAdd(savedPdbbase->pgpHash,pvar->name,&savedPdbbase->variableList);
    if(!pgphentry) {
        yyerrorAbort("gphAdd failed");
    }
    pgphentry->userPvt = pvar;
    ellAdd(&savedPdbbase->variableList,&pvar->node);
}

static void dbBreakHead(char *name)
{
    brkTable    *pbrkTable;
    GPHENTRY    *pgphentry;

    if (!*name) {
        yyerrorAbort("dbBreakHead: Breaktable name can't be empty");
        return;
    }
    pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->bptList);
    if(pgphentry) {
        duplicate = TRUE;
        return;
    }
    pbrkTable = dbCalloc(1,sizeof(brkTable));
    pbrkTable->name = epicsStrDup(name);
    if(ellCount(&tempList)) yyerrorAbort("dbBreakHead:tempList not empty");
    allocTemp(pbrkTable);
}

static void dbBreakItem(char *value)
{
    double dummy;
    if (duplicate) return;
    if (epicsScanDouble(value, &dummy) != 1) {
        yyerrorAbort("Non-numeric value in breaktable");
    }
    allocTemp(epicsStrDup(value));
}

static void dbBreakBody(void)
{
    brkTable            *pnewbrkTable;
    brkInt              *paBrkInt;
    brkTable            *pbrkTable;
    int                 number, down=0;
    int                 i;
    GPHENTRY            *pgphentry;

    if (duplicate) {
        duplicate = FALSE;
        return;
    }
    pnewbrkTable = (brkTable *)popFirstTemp();
    if(!pnewbrkTable)
        return;
    number = ellCount(&tempList);
    if (number % 2) {
        yyerrorAbort("breaktable: Raw value missing");
        return;
    }
    number /= 2;
    if (number < 2) {
        yyerrorAbort("breaktable: Must have at least two points!");
        return;
    }
    pnewbrkTable->number = number;
    pnewbrkTable->paBrkInt = paBrkInt = dbCalloc(number, sizeof(brkInt));
    for (i=0; i<number; i++) {
        char    *str;

        str = (char *)popFirstTemp();
        if(!str)
            return;
        (void) epicsScanDouble(str, &paBrkInt[i].raw);
        free(str);

        str = (char *)popFirstTemp();
        if(!str)
            return;
        (void) epicsScanDouble(str, &paBrkInt[i].eng);
        free(str);
    }
    /* Compute slopes */
    for (i=0; i<number-1; i++) {
        double slope =
          (paBrkInt[i+1].eng - paBrkInt[i].eng)/
          (paBrkInt[i+1].raw - paBrkInt[i].raw);
        if (!dbBptNotMonotonic && slope == 0) {
            yyerrorAbort("breaktable slope is zero");
            return;
        }
        if (i == 0) {
            down = (slope < 0);
        } else if (!dbBptNotMonotonic && down != (slope < 0)) {
            yyerrorAbort("breaktable slope changes sign");
            return;
        }
        paBrkInt[i].slope = slope;
    }
    /* Continue with last slope beyond the final point */
    paBrkInt[number-1].slope = paBrkInt[number-2].slope;
    /* Add brkTable in sorted order */
    pbrkTable = (brkTable *)ellFirst(&savedPdbbase->bptList);
    while (pbrkTable) {
        if (strcmp(pbrkTable->name, pnewbrkTable->name) > 0) {
            ellInsert(&savedPdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable),
                (ELLNODE *)pnewbrkTable);
            break;
        }
        pbrkTable = (brkTable *)ellNext(&pbrkTable->node);
    }
    if (!pbrkTable) ellAdd(&savedPdbbase->bptList, &pnewbrkTable->node);
    pgphentry = gphAdd(savedPdbbase->pgpHash,pnewbrkTable->name,&savedPdbbase->bptList);
    if (!pgphentry) {
        yyerrorAbort("dbBreakBody: gphAdd failed");
        return;
    }
    pgphentry->userPvt = pnewbrkTable;
}

static
int dbRecordNameValidate(const char *name)
{
    size_t i=0u;
    const char *pos = name;

    if (!*name) {
        yyerrorAbort(ERL_ERROR ": Record/Alias name can't be empty");
        return 1;
    }

    for(; *pos; i++, pos++) {
        unsigned char c = *pos;
        if(i==0) {
            /* first character restrictions */
            if(c=='-' || c=='+' || c=='[' || c=='{') {
                fprintf(stderr, ERL_WARNING
                    ": Record/Alias name '%s' should not begin with '%c'\n",
                    name, c);
            }
        }
        /* any character restrictions */
        if(c < ' ') {
            fprintf(stderr, ERL_WARNING
                ": Record/Alias name '%s' contains non-printable 0x%02x\n",
                 name, c);

        } else if(c==' ' || c=='\t' || c=='"' || c=='\'' || c=='.' || c=='$') {
            fprintf(stderr, ERL_ERROR
                ": Bad character '%c' in Record/Alias name \"%s\"\n",
                c, name);
            yyerrorAbort(NULL);
            return 1;
        }
    }

    return 0;
}

static void dbRecordHead(char *recordType, char *name, int visible)
{
    DBENTRY *pdbentry;
    long status;

    if(dbRecordNameValidate(name))
        return;

    pdbentry = dbAllocEntry(savedPdbbase);
    if (ellCount(&tempList))
        yyerrorAbort("dbRecordHead: tempList not empty");
    allocTemp(pdbentry);

    if (recordType[0] == '*' && recordType[1] == 0) {
        status = dbFindRecord(pdbentry, name);
        if (status == 0)
            return; /* done */
        fprintf(stderr, ERL_ERROR ": Record '%s' not found\n", name);
        yyerror(NULL);
        duplicate = TRUE;
        return;
    }

    if (recordType[0] == '#' && recordType[1] == 0) {
        status = dbFindRecord(pdbentry, name);
        if (status == 0) {
            dbDeleteRecord(pdbentry);
        } else {
            fprintf(stderr, ERL_WARNING
                ": Record '%s' not found, can't delete\n"
                "  at file '%s', line %d\n",
                name, pinputFileNow->filename, pinputFileNow->line_num);
        }
        popFirstTemp();
        dbFreeEntry(pdbentry);
        duplicate = TRUE;
        return;
    }
    status = dbFindRecordType(pdbentry, recordType);
    if (status) {
        fprintf(stderr, ERL_ERROR
            ": Record type '%s' for record '%s' not found\n",
            recordType, name);
        yyerrorAbort(NULL);
        return;
    }

    /*Duplicate records are ok if the same type */

    status = dbCreateRecord(pdbentry,name);
    if (status == S_dbLib_recExists) {
        if (strcmp(recordType, dbGetRecordTypeName(pdbentry)) != 0) {
            fprintf(stderr, ERL_ERROR
                ": %s record '%s' already exists, can't load %s record\n",
                recordType, name, dbGetRecordTypeName(pdbentry));
            yyerror(NULL);
            return;
        }
        else if (dbRecordsOnceOnly) {
            fprintf(stderr, ERL_ERROR
                ": Record '%s' already defined; dbRecordsOnceOnly is set,\n"
                "  so can't modify record.\n", name);
            yyerror(NULL);
            duplicate = TRUE;
        }
    }
    else if (status) {
        fprintf(stderr, ERL_ERROR
            ": Can't create %s record '%s'\n",
            recordType, name);
        yyerrorAbort(NULL);
    }

    if (visible)
        dbVisibleRecord(pdbentry);
}

/* For better suggestions for wrong field names
   the following array contains pairs of often
   confused fields. Thus, the number of elements
   must be even.
   For the last character, ranges like A-F are
   allowed as a shortcut. Pairs must have matching
   range size.
   If extending this map, please add only field names
   found in record types from base.
   Each array element (i.e. both sides of a pair)
   is tested against the faulty field name.
   The first match (considering ranges) where the
   other side of the pair is an existing field name
   (after adjusting for ranges) will be suggested
   as a replacement.
   If no such match is found, the suggestion falls
   back to weighted lexical similarity with existing
   field names.
*/

static const char* const dbFieldConfusionMap [] = {
    "INP","OUT",
    "DOL","INP",
    "ZNAM","ZRST",
    "ONAM","ONST",
    "INPA-J","DOL0-9",
    "INPK-P","DOLA-F",
    "INP0-9","INPA-J"
};
STATIC_ASSERT(NELEMENTS(dbFieldConfusionMap)%2==0);

static void dbRecordField(char *name,char *value)
{
    DBENTRY *pdbentry;
    tempListNode *ptempListNode;
    long status;

    if (duplicate) return;
    ptempListNode = (tempListNode *)ellFirst(&tempList);
    pdbentry = ptempListNode->item;
    status = dbFindField(pdbentry,name);
    if (status) {
        fprintf(stderr, ERL_ERROR
            ": %s record '%s' doesn't have a field '%s'\n",
            dbGetRecordTypeName(pdbentry), dbGetRecordName(pdbentry), name);
        if(dbGetRecordName(pdbentry)) {
            DBENTRY temp;
            const dbFldDes *bestFld = NULL;
            int i;
            dbCopyEntryContents(pdbentry, &temp);
            for(i = 0; i < NELEMENTS(dbFieldConfusionMap); i++) {
                const char* fieldname = dbFieldConfusionMap[i];
                const char* replacement = dbFieldConfusionMap[i^1]; /* swap even with odd indices */
                const char* guess = NULL;
                char buf[8]; /* no field name is so long */
                size_t l = strlen(fieldname);
                if (l >= 3 && fieldname[l-2] == '-' &&
                    strncmp(name, fieldname, l-3) == 0 &&
                    name[l-3] >= fieldname[l-3] &&
                    name[l-3] <= fieldname[l-1])
                {
                    /* range map (like XXXA-Z) */
                    size_t l2 = strlen(replacement);
                    strncpy(buf, replacement, sizeof(buf)-1);
                    buf[l2-3] += name[l-3] - fieldname[l-3];
                    buf[l2-2] = 0;
                    guess = buf;
                } else if (strcmp(name, fieldname) == 0) {
                    /* simple map */
                    guess = replacement;
                }
                if (guess && dbFindFieldPart(&temp, &guess) == 0) {
                    /* guessed field exists */
                    bestFld = temp.pflddes;
                    break;
                }
            }
            if (!bestFld) {
                /* no map found, use weighted lexical similarity
                   the weights are a bit arbitrary */
                double bestSim = -1.0;
                char quote = 0;
                if (*value == '"' || *value == '\'')
                    quote = *value++;
                for (status = dbFirstField(&temp, 0); !status; status = dbNextField(&temp, 0)) {
                    if (temp.pflddes->special == SPC_NOMOD ||
                        temp.pflddes->special == SPC_DBADDR) /* cannot be configured */
                        continue;
                    double sim = epicsStrSimilarity(name, temp.pflddes->name);
                    if (!temp.pflddes->promptgroup)
                        sim *= 0.5; /* no prompt: unlikely */
                    if (temp.pflddes->interest)
                        sim *= 1.0 - 0.1 * temp.pflddes->interest; /* 10% less likely per interest level */
                    if (sim == 0)
                        continue;
                    if (*value != quote) {
                        /* value given, check match to field type */
                        long status = 0;
                        char* end = &quote;

                        switch (temp.pflddes->field_type) {
                            epicsAny dummy;
                            case DBF_CHAR:
                                status = epicsParseInt8(value, &dummy.int8, 0, &end);
                                break;
                            case DBF_UCHAR:
                                status = epicsParseUInt8(value, &dummy.uInt8, 0, &end);
                                break;
                            case DBF_SHORT:
                                status = epicsParseInt16(value, &dummy.int16, 0, &end);
                                break;
                            case DBF_USHORT:
                            case DBF_ENUM:
                                status = epicsParseUInt16(value, &dummy.uInt16, 0, &end);
                                break;
                            case DBF_LONG:
                                status = epicsParseInt32(value, &dummy.int32, 0, &end);
                                break;
                            case DBF_ULONG:
                                status = epicsParseUInt32(value, &dummy.uInt32, 0, &end);
                                break;
                            case DBF_INT64:
                                status = epicsParseInt64(value, &dummy.int64, 0, &end);
                                break;
                            case DBF_UINT64:
                                status = epicsParseUInt64(value, &dummy.uInt64, 0, &end);
                                break;
                            case DBF_FLOAT:
                                status = epicsParseFloat(value, &dummy.float32, &end);
                                break;
                            case DBF_DOUBLE:
                                status = epicsParseDouble(value, &dummy.float64, &end);
                                break;
                            case DBF_MENU:
                            case DBF_DEVICE: {
                                char** choices;
                                int nChoice;
                                int choice;

                                if (temp.pflddes->field_type == DBF_MENU) {
                                    dbMenu* menu = (dbMenu*)temp.pflddes->ftPvt;
                                    choices = menu->papChoiceValue;
                                    nChoice = menu->nChoice;
                                } else {
                                    dbDeviceMenu* menu = (dbDeviceMenu*)temp.pflddes->ftPvt;
                                    choices = menu->papChoice;
                                    nChoice = menu->nChoice;
                                }
                                status = epicsParseUInt16(value, &dummy.uInt16, 0, &end);
                                if (!status && *end == quote && dummy.uInt16 < nChoice) {
                                    if (temp.pflddes->field_type == DBF_DEVICE)
                                        sim *= 0.5; /* numeric device type index is uncommon */
                                    break;
                                }
                                for (choice = 0; choice < nChoice; choice++) {
                                    size_t len = strlen(choices[choice]);
                                    end = value + len;
                                    if (strncmp(value, choices[choice], len) == 0 && *end == quote) {
                                        sim *= 1.5; /* boost for matching choice string */
                                        status = 0;
                                        break;
                                    }
                                }
                                if (choice == nChoice)
                                    status = S_stdlib_noConversion;
                                break;
                            }
                            default:
                                break;
                        }
                        if (status || *end != quote)
                            sim *= 0.1; /* value type does not match field type: unlikely */
                    }
                    if (sim > bestSim) {
                        bestSim = sim;
                        bestFld = temp.pflddes;
                    }
                }
            }
            dbFinishEntry(&temp);
            if (bestFld) {
                fprintf(stderr, "    Did you mean \"%s\"?", bestFld->name);
                if(bestFld->prompt)
                    fprintf(stderr, "  (%s)", bestFld->prompt);
                fprintf(stderr, "\n");
            }
        }
        yyerror(NULL);
        return;
    }
    if (pdbentry->indfield == 0) {
        fprintf(stderr, ERL_ERROR
            ": Can't set 'NAME' field of record '%s'\n",
            dbGetRecordName(pdbentry));
        yyerror(NULL);
        return;
    }

    if (*value == '"' || *value == '\'') {
        /* jsonSTRING values still have their quotes */
        value++;
        value[strlen(value) - 1] = 0;
        dbTranslateEscape(value, value);    /* in-place; safe & legal */
    }

    status = dbPutString(pdbentry,value);
    if (status) {
        char msg[128];

        errSymLookup(status, msg, sizeof(msg));
        fprintf(stderr, ERL_ERROR
            ": Can't set '%s.%s' to '%s' %s : %s\n",
            dbGetRecordName(pdbentry), name, value,
            pdbentry->message ? pdbentry->message : "", msg);
        dbPutStringSuggest(pdbentry, value);
        yyerror(NULL);
        return;
    }
}

static void dbRecordInfo(char *name, char *value)
{
    DBENTRY *pdbentry;
    tempListNode *ptempListNode;
    long status;

    if (!*name) {
        yyerrorAbort("dbRecordInfo: Info item name can't be empty");
        return;
    }
    if (duplicate) return;
    ptempListNode = (tempListNode *)ellFirst(&tempList);
    pdbentry = ptempListNode->item;

    if (*value == '"' || *value == '\'') {
        /* jsonSTRING values still have their quotes */
        value++;
        value[strlen(value) - 1] = 0;
        dbTranslateEscape(value, value);    /* in-place; safe & legal */
    }

    status = dbPutInfo(pdbentry,name,value);
    if (status) {
        fprintf(stderr, ERL_ERROR
            ": Can't set '%s' info(\"%s\") to '%s'\n",
            dbGetRecordName(pdbentry), name, value);
        yyerror(NULL);
        return;
    }
}

static long createAlias(DBENTRY *pdbentry, const char *alias)
{
    DBENTRY tempEntry;
    long status;
    dbRecordNode *precnode = pdbentry->precnode;

    if (precnode->aliasedRecnode) precnode = precnode->aliasedRecnode;
    dbInitEntry(pdbentry->pdbbase, &tempEntry);
    status = dbFindRecord(&tempEntry, alias);
    if (status == 0) {
        if (tempEntry.precnode->aliasedRecnode != precnode) {
            if (tempEntry.precnode->aliasedRecnode)
                fprintf(stderr, ERL_ERROR
                    ": Alias '%s' for record '%s' already aliases '%s'\n",
                    alias, dbGetRecordName(pdbentry),
                    tempEntry.precnode->aliasedRecnode->recordname);
            else
                fprintf(stderr, ERL_ERROR
                    ": Alias '%s' for record '%s' is already a record name.\n",
                    alias, dbGetRecordName(pdbentry));
            status = S_dbLib_recExists;
        } else if (dbRecordsOnceOnly) {
            fprintf(stderr, ERL_ERROR
                ": Alias '%s' already defined; dbRecordsOnceOnly is set.\n",
                alias);
            status = S_dbLib_recExists;
        }
    } else {
        status = dbCreateAlias(pdbentry, alias);
    }
    dbFinishEntry(&tempEntry);
    return status;
}

static void dbRecordAlias(char *name)
{
    DBENTRY *pdbentry;
    tempListNode *ptempListNode;
    if(dbRecordNameValidate(name))
        return;

    if (duplicate) return;
    ptempListNode = (tempListNode *)ellFirst(&tempList);
    pdbentry = ptempListNode->item;

    if (createAlias(pdbentry, name) != 0) {
        yyerror(NULL);
    }
}

static void dbAlias(char *name, char *alias)
{
    DBENTRY dbEntry;
    DBENTRY *pdbEntry = &dbEntry;

    if(dbRecordNameValidate(alias) || dbRecordNameValidate(name))
        return;

    dbInitEntry(savedPdbbase, pdbEntry);
    if (dbFindRecord(pdbEntry, name)) {
        fprintf(stderr, ERL_ERROR
            ": Alias '%s' names an unknown record '%s'\n",
            alias, name);
        yyerror(NULL);
    }
    else if (createAlias(pdbEntry, alias) != 0) {
        yyerror(NULL);
    }
    dbFinishEntry(pdbEntry);
}

static void dbRecordBody(void)
{
    DBENTRY *pdbentry;

    if (duplicate) {
        duplicate = FALSE;
        return;
    }
    pdbentry = (DBENTRY *)popFirstTemp();
    if (ellCount(&tempList))
        yyerrorAbort("dbRecordBody: tempList not empty");
    dbFreeEntry(pdbentry);
}
