## root/libutil/gtagsop.c(119.html)

/* [<][>][^][v][top][bottom][index][help] */

## DEFINITIONS

This source file includes following definitions.

1. compare_path
2. compare_lineno
3. compare_tags
4. seekto
5. dbname
6. gtags_open
7. gtags_put
8. gtags_delete
9. gtags_first
10. gtags_next
11. gtags_close
12. flush_pool
13. segment_read

    /*
     * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2005, 2006,
     *      2007 Tama Communications Corporation
     *
     * This file is part of GNU GLOBAL.
     *
     * This program 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 3 of the License, or
     * (at your option) any later version.
     *
     * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
     */

    #ifdef HAVE_CONFIG_H
    #include <config.h>
    #endif
    #include <assert.h>
    #include <ctype.h>
    #include <stdio.h>
    #ifdef STDC_HEADERS
    #include <stdlib.h>
    #endif
    #ifdef HAVE_STRING_H
    #include <string.h>
    #else
    #include <strings.h>
    #endif
    #ifdef HAVE_UNISTD_H
    #include <unistd.h>
    #endif

    #include "char.h"
    #include "checkalloc.h"
    #include "conf.h"
    #include "compress.h"
    #include "dbop.h"
    #include "die.h"
    #include "format.h"
    #include "gparam.h"
    #include "gtagsop.h"
    #include "locatestring.h"
    #include "makepath.h"
    #include "path.h"
    #include "gpathop.h"
    #include "split.h"
    #include "strbuf.h"
    #include "strhash.h"
    #include "strlimcpy.h"
    #include "strmake.h"
    #include "varray.h"

    #define HASHBUCKETS     2048

    static int compare_path(const void *, const void *);
    static int compare_lineno(const void *, const void *);
    static int compare_tags(const void *, const void *);
    static const char *seekto(const char *, int);
    static void flush_pool(GTOP *);
    static void segment_read(GTOP *);

    /*
     * compare_path: compare function for sorting path names.
     */
    static int
    compare_path(const void *s1, const void *s2)
    {
            return strcmp(*(char **)s1, *(char **)s2);
    }
    /*
     * compare_lineno: compare function for sorting line number.
     */
    static int
    compare_lineno(const void *s1, const void *s2)
    {
            return *(const int *)s1 - *(const int *)s2;
    }
    /*
     * compare_tags: compare function for sorting tags.
     */
    static int
    compare_tags(const void *v1, const void *v2)
    {
            const GTP *e1 = v1, *e2 = v2;
            int ret;

            if ((ret = strcmp(e1->path, e2->path)) != 0)
                    return ret;
            return e1->lineno - e2->lineno;
    }
    /*
     * seekto: seek to the specified item of tag record.
     *
     * Usage:
     *           0         1          2
     * tagline = <file id> <tag name> <line number>
     *
     * <file id>     = seekto(tagline, SEEKTO_FILEID);
     * <tag name>    = seekto(tagline, SEEKTO_TAGNAME);
     * <line number> = seekto(tagline, SEEKTO_LINENO);
     */
    #define SEEKTO_FILEID   0
    #define SEEKTO_TAGNAME  1
    #define SEEKTO_LINENO   2

    static const char *
    seekto(const char *string, int n)
    {
            const char *p = string;
            while (n--) {
                    p = strchr(p, ' ');
                    if (p == NULL)
                            return NULL;
                    p++;
            }
            return p;
    }
    /*
     * Tag format
     *
     * [Specification of format version 4]
     *
     * Standard format (for GTAGS)
     *
     *         <file id> <tag name> <line number> <line image>
     *
     *                 * Separator is single blank.
     *
     *         [example]
     *         +------------------------------------
     *         |110 func 10 int func(int a)
     *         |110 func 30 func(int a1, int a2)
     *
     *         Line image might be compressed (GTAGS_COMPRESS).
     *
     * With compact option for GRTAGS, GSYMS (GTAGS_COMPACT)
     *
     *         <file id> <tag name> <line number>,...
     *
     *                 * Separator is single blank.
     *
     *         [example]
     *         +------------------------------------
     *         |110 func 10,30
     *
     *         Line numbers are sorted in a line.
     *
     * [Specification of format version 5]
     *
     * Standard format for GTAGS
     *
     *         <file id> <tag name> <line number> <line image>
     *
     *                 * Separator is single blank.
     *
     *         [example]
     *         +------------------------------------
     *         |110 func 10 int func(int a)
     *         |110 func 30 func(int a1, int a2)
     *
     *         Line image might be compressed (GTAGS_COMPRESS).
     *         Tag name might be compressed (GTAGS_COMPNAME).
     *
     * With compact option for GRTAGS, GSYMS (GTAGS_COMPACT)
     *
     *         <file id> <tag name> <line number>,...
     *
     *                 * Separator is single blank.
     *
     *         [example]
     *         +------------------------------------
     *         |110 func 10,20
     *
     *         Each line number might be expressed as difference from the previous
     *         line number except for the head (GTAGS_COMPLINE).
     *           ex: 10,3,2 means '10 13 15'.
     *         In addition,successive line numbers are expressed as a range.
     *           ex: 10-3 means '10 11 12 13'.
     *
     * [Description]
     *
     * o Standard format is applied to GTAGS, and compact format is applied
     *   to GRTAGS and GSYMS by default.
     * o Above two formats are same to the first line number. So, we can use
     *   common function to sort them.
     * o Separator is single blank.
     *   This decrease disk space used a little, and make it easy to parse
     *   tag record.
     * o Use file id instead of path name.
     *   This allows blanks in path name at least in tag files.
     * o Put file id at the head of tag record.
     *   We can access file id without string processing.
     *   This is advantageous for deleting tag record when incremental updating.
     *
     * [Concept of format version]
     *
     * Since GLOBAL's tag files are machine independent, they can be distributed
     * apart from GLOBAL itself. For example, if some network file system available,
     * client may execute global using server's tag files. In this case, both
     * GLOBAL are not necessarily the same version. So, we should assume that
     * older version of GLOBAL might access the tag files which generated
     * by new GLOBAL. To deal in such case, we decided to buried a version number
     * to both global(1) and tag files. The conclete procedure is like follows:
     *
     * 1. Gtags(1) bury the version number in tag files.
     * 2. Global(1) pick up the version number from a tag file. If the number
     *    is larger than its acceptable version number then global give up work
     *    any more and display error message.
     * 3. If version number is not found then it assumes version 1.
     *
     * [History of format version]
     *
     * GLOBAL-1.0 - 1.8     no idea about format version.
     * GLOBAL-1.9 - 2.24    understand format version.
     *                      support format version 1 (default).
     *                      if (format > 1) then print error message.
     * GLOBAL-3.0 - 4.5     support format version 1 and 2.
     *                      if (format > 2) then print error message.
     * GLOBAL-4.5.1 - 4.8.7 support format version 1, 2 and 3.
     *                      if (format > 3) then print error message.
     * GLOBAL-5.0 - 5.3     support format version only 4.
     *                      if (format !=  4) then print error message.
     * GLOBAL-5.4 -         support format version 4 and 5
     *                      if (format > 5 || format < 4) then print error message.
     *
     * In GLOBAL-5.0, we threw away the compatibility with the past formats.
     * Though we could continue the support for older formats, it seemed
     * not to be worthy. Because keeping maintaining the all formats hinders
     * new optimization and the function addition in the future.
     * Instead, the following error messages are displayed in a wrong usage.
     *       [older global and new tag file]
     *       $ global -x main
     *       GTAGS seems new format. Please install the latest GLOBAL.
     *       [new global and older tag file]
     *       $ global -x main
     *       GTAGS seems older format. Please remake tag files.
     */
    static int upper_bound_version = 5;     /* acceptable format version (upper bound) */
    static int lower_bound_version = 4;     /* acceptable format version (lower bound) */
    static const char *tagslist[] = {"GPATH", "GTAGS", "GRTAGS", "GSYMS"};
    /*
     * dbname: return db name
     *
     *      i)      db      0: GPATH, 1: GTAGS, 2: GRTAGS, 3: GSYMS
     *      r)              dbname
     */
    const char *
    dbname(int db)
    {
            assert(db >= 0 && db < GTAGLIM);
            return tagslist[db];
    }
    /*
     * gtags_open: open global tag.
     *
     *      i)      dbpath  dbpath directory
     *      i)      root    root directory (needed when compact format)
     *      i)      db      GTAGS, GRTAGS, GSYMS
     *      i)      mode    GTAGS_READ: read only
     *                      GTAGS_CREATE: create tag
     *                      GTAGS_MODIFY: modify tag
     *      r)              GTOP structure
     *
     * when error occurred, gtagopen doesn't return.
     */
    GTOP *
    gtags_open(const char *dbpath, const char *root, int db, int mode)
    {
            GTOP *gtop;
            int dbmode;

            gtop = (GTOP *)check_calloc(sizeof(GTOP), 1);
            gtop->db = db;
            gtop->mode = mode;
            gtop->format_version = 4;
            /*
             * Open tag file allowing duplicate records.
             */
            switch (gtop->mode) {
            case GTAGS_READ:
                    dbmode = 0;
                    break;
            case GTAGS_CREATE:
                    dbmode = 1;
                    break;
            case GTAGS_MODIFY:
                    dbmode = 2;
                    break;
            default:
                    assert(0);
            }
            gtop->dbop = dbop_open(makepath(dbpath, dbname(db), NULL), dbmode, 0644, DBOP_DUP);
            if (gtop->dbop == NULL) {
                    if (dbmode == 1)
                            die("cannot make %s.", dbname(db));
                    die("%s not found.", dbname(db));
            }
            if (gtop->mode == GTAGS_CREATE) {
                    /*
                     * Decide format.
                     */
                    gtop->format = 0;
                    gtop->format_version = 5;
                    if (gtop->db == GTAGS)
                            gtop->format |= GTAGS_COMPRESS;
                    gtop->format |= GTAGS_COMPNAME;
                    if (gtop->db == GRTAGS || gtop->db == GSYMS) {
                            gtop->format |= GTAGS_COMPACT;
                            gtop->format |= GTAGS_COMPLINE;
                    }
                    if (gtop->format & GTAGS_COMPACT)
                            dbop_putoption(gtop->dbop, COMPACTKEY, NULL);
                    if (gtop->format & GTAGS_COMPRESS) {
                            dbop_putoption(gtop->dbop, COMPRESSKEY, DEFAULT_ABBREVIATION);
                            abbrev_open(DEFAULT_ABBREVIATION);
                    }
                    if (gtop->format & GTAGS_COMPLINE)
                            dbop_putoption(gtop->dbop, COMPLINEKEY, NULL);
                    if (gtop->format & GTAGS_COMPNAME)
                            dbop_putoption(gtop->dbop, COMPNAMEKEY, NULL);
                    dbop_putversion(gtop->dbop, gtop->format_version);
            } else {
                    /*
                     * recognize format version of GTAGS. 'format version record'
                     * is saved as a META record in GTAGS and GRTAGS.
                     * if 'format version record' is not found, it's assumed
                     * version 1.
                     */
                    const char *p;
                    /*
                     * check format version.
                     */
                    gtop->format_version = dbop_getversion(gtop->dbop);
                    if (gtop->format_version > upper_bound_version)
                            die("%s seems new format. Please install the latest GLOBAL.", dbname(gtop->db));
                    else if (gtop->format_version < lower_bound_version)
                            die("%s seems older format. Please remake tag files.", dbname(gtop->db));
                    gtop->format = 0;
                    if (dbop_getoption(gtop->dbop, COMPACTKEY) != NULL)
                            gtop->format |= GTAGS_COMPACT;
                    if ((p = dbop_getoption(gtop->dbop, COMPRESSKEY)) != NULL) {
                            abbrev_open(p);
                            gtop->format |= GTAGS_COMPRESS;
                    }
                    if (dbop_getoption(gtop->dbop, COMPLINEKEY) != NULL)
                            gtop->format |= GTAGS_COMPLINE;
                    if (dbop_getoption(gtop->dbop, COMPNAMEKEY) != NULL)
                            gtop->format |= GTAGS_COMPNAME;
            }
            if (gpath_open(dbpath, dbmode) < 0) {
                    if (dbmode == 1)
                            die("cannot create GPATH.");
                    else
                            die("GPATH not found.");
            }
            if (gtop->mode != GTAGS_READ)
                    gtop->sb = strbuf_open(0);      /* This buffer is used for working area. */
            /*
             * Stuff for compact format.
             */
            if (gtop->format & GTAGS_COMPACT) {
                    assert(root != NULL);
                    strlimcpy(gtop->root, root, sizeof(gtop->root));
                    if (gtop->mode != GTAGS_READ)
                            gtop->path_hash = strhash_open(HASHBUCKETS);
            }
            return gtop;
    }
    /*
     * gtags_put: put tag record with packing.
     *
     *      i)      gtop    descripter of GTOP
     *      i)      key     tag name
     *      i)      ctags_x tag line (ctags -x format)
     */
    void
    gtags_put(GTOP *gtop, const char *key, const char *ctags_x)     /* virtually const */
    {
            SPLIT ptable;

            if (split((char *)ctags_x, 4, &ptable) != 4) {
                    recover(&ptable);
                    die("illegal tag format.\n'%s'", ctags_x);
            }
            if (gtop->format & GTAGS_COMPACT) {
                    int *lno;
                    struct sh_entry *entry;
                    /*
                     * Flush the pool when path is changed.
                     * Line numbers in the pool will be sorted and duplicated
                     * records will be combined.
                     *
                     * pool    "funcA"   | 1| 3| 7|23|11| 2|...
                     *           v
                     * output  funcA 33 1,2,3,7,11,23...
                     */
                    if (gtop->cur_path[0] && strcmp(gtop->cur_path, ptable.part[PART_PATH].start)) {
                            flush_pool(gtop);
                            strhash_reset(gtop->path_hash);
                    }
                    strlimcpy(gtop->cur_path, ptable.part[PART_PATH].start, sizeof(gtop->cur_path));
                    /*
                     * Register each record into the pool.
                     *
                     * Pool image:
                     *
                     * tagname   lno
                     * ------------------------------
                     * "funcA"   | 1| 3| 7|23|11| 2|...
                     * "funcB"   |34| 2| 5|66| 3|...
                     * ...
                     */
                    entry = strhash_assign(gtop->path_hash, key, 1);
                    if (entry->value == NULL)
                            entry->value = varray_open(sizeof(int), 100);
                    lno = varray_append((VARRAY *)entry->value);
                    *lno = atoi(ptable.part[PART_LNO].start);
            } else {
                    const char *s_fid = gpath_path2fid(ptable.part[PART_PATH].start, NULL);

                    if (s_fid == NULL)
                            die("GPATH is corrupted.('%s' not found)", ptable.part[PART_PATH].start);
                    strbuf_reset(gtop->sb);
                    strbuf_puts(gtop->sb, s_fid);
                    strbuf_putc(gtop->sb, ' ');
                    if (gtop->format & GTAGS_COMPNAME)
                            strbuf_puts(gtop->sb, compress(ptable.part[PART_TAG].start, key));
                    else
                            strbuf_puts(gtop->sb, ptable.part[PART_TAG].start);
                    strbuf_putc(gtop->sb, ' ');
                    strbuf_puts(gtop->sb, ptable.part[PART_LNO].start);
                    strbuf_putc(gtop->sb, ' ');
                    strbuf_puts(gtop->sb, gtop->format & GTAGS_COMPRESS ?
                            compress(ptable.part[PART_LINE].start, key) :
                            ptable.part[PART_LINE].start);
                    dbop_put(gtop->dbop, key, strbuf_value(gtop->sb));
            }
            recover(&ptable);
    }
    /*
     * gtags_delete: delete records belong to set of fid.
     *
     *      i)      gtop    GTOP structure
     *      i)      deleteset bit array of fid
     */
    void
    gtags_delete(GTOP *gtop, IDSET *deleteset)
    {
            const char *tagline;
            int fid;

            for (tagline = dbop_first(gtop->dbop, NULL, NULL, 0); tagline; tagline = dbop_next(gtop->dbop)) {
                    /*
                     * Extract path from the tag line.
                     */
                    fid = atoi(tagline);
                    /*
                     * If the file id exists in the deleteset, delete the tagline.
                     */
                    if (idset_contains(deleteset, fid))
                            dbop_delete(gtop->dbop, NULL);
            }
    }
    /*
     * gtags_first: return first record
     *
     *      i)      gtop    GTOP structure
     *      i)      pattern tag name
     *              o may be regular expression
     *              o may be NULL
     *      i)      flags   GTOP_PREFIX     prefix read
     *                      GTOP_KEY        read key only
     *                      GTOP_PATH       read path only
     *                      GTOP_NOREGEX    don't use regular expression.
     *                      GTOP_IGNORECASE ignore case distinction.
     *                      GTOP_BASICREGEX use basic regular expression.
     *                      GTOP_NOSORT     don't sort
     *      r)              record
     */
    GTP *
    gtags_first(GTOP *gtop, const char *pattern, int flags)
    {
            int dbflags = 0;
            int regflags = 0;
            char prefix[IDENTLEN+1];
            static regex_t reg;
            regex_t *preg = &reg;
            const char *key = NULL;
            const char *tagline;

            /* Settlement for last time if any */
            if (gtop->path_hash) {
                    strhash_close(gtop->path_hash);
                    gtop->path_hash = NULL;
            }
            if (gtop->path_array) {
                    free(gtop->path_array);
                    gtop->path_array = NULL;
            }

            gtop->flags = flags;
            if (flags & GTOP_PREFIX && pattern != NULL)
                    dbflags |= DBOP_PREFIX;
            if (flags & GTOP_KEY)
                    dbflags |= DBOP_KEY;

            if (!(flags & GTOP_BASICREGEX))
                    regflags |= REG_EXTENDED;
            if (flags & GTOP_IGNORECASE)
                    regflags |= REG_ICASE;
            if (gtop->format & GTAGS_COMPACT)
                    gtop->fp = NULL;
            /*
             * Get key and compiled regular expression for dbop_xxxx().
             */
            if (flags & GTOP_NOREGEX) {
                    key = pattern;
                    preg = NULL;
            } else if (pattern == NULL || !strcmp(pattern, ".*")) {
                    /*
                     * Since the regular expression '.*' matches to any record,
                     * we take sequential read method.
                     */
                    key = NULL;
                    preg = NULL;
            } else if (isregex(pattern) && regcomp(preg, pattern, regflags) == 0) {
                    const char *p;
                    /*
                     * If the pattern include '^' + some non regular expression
                     * characters like '^aaa[0-9]', we take prefix read method
                     * with the non regular expression part as the prefix.
                     */
                    if (!(flags & GTOP_IGNORECASE) && *pattern == '^' && *(p = pattern + 1) && !isregexchar(*p)) {
                            int i = 0;

                            while (*p && !isregexchar(*p) && i < IDENTLEN)
                                    prefix[i++] = *p++;
                            prefix[i] = '\0';
                            key = prefix;
                            dbflags |= DBOP_PREFIX;
                    } else {
                            key = NULL;
                    }
            } else {
                    key = pattern;
                    preg = NULL;
            }
            /*
             * If GTOP_PATH is set, at first, we collect all path names in a pool and
             * sort them. gtags_first() and gtags_next() returns one of the pool.
             */
            if (gtop->flags & GTOP_PATH) {
                    struct sh_entry *entry;
                    char *p;
                    const char *cp;
                    unsigned long i;

                    gtop->path_hash = strhash_open(HASHBUCKETS);
                    /*
                     * Pool path names.
                     *
                     * fid          path name
                     * +--------------------------
                     * |100         ./aaa/a.c
                     * |105         ./aaa/b.c
                     *  ...
                     */
                    for (tagline = dbop_first(gtop->dbop, key, preg, dbflags);
                         tagline != NULL;
                         tagline = dbop_next(gtop->dbop))
                    {
                            /* extract file id */
                            p = locatestring(tagline, " ", MATCH_FIRST);
                            if (p == NULL)
                                    die("Illegal tag record. '%s'\n", tagline);
                            *p = '\0';
                            entry = strhash_assign(gtop->path_hash, tagline, 1);
                            /* new entry: get path name and set. */
                            if (entry->value == NULL) {
                                    cp = gpath_fid2path(tagline, NULL);
                                    if (cp == NULL)
                                            die("GPATH is corrupted.(file id '%s' not found)", tagline);
                                    entry->value = strhash_strdup(gtop->path_hash, cp, 0);
                            }
                    }
                    /*
                     * Sort path names.
                     *
                     * fid          path name       path_array (sort)
                     * +--------------------------  +---+
                     * |100         ./aaa/a.c <-------* |
                     * |105         ./aaa/b.c <-------* |
                     *  ...                         ...
                     */
                    gtop->path_array = (char **)check_malloc(gtop->path_hash->entries * sizeof(char *));
                    i = 0;
                    for (entry = strhash_first(gtop->path_hash); entry != NULL; entry = strhash_next(gtop->path_hash))
                            gtop->path_array[i++] = entry->value;
                    if (i != gtop->path_hash->entries)
                            die("Something is wrong. 'i = %lu, entries = %lu'" , i, gtop->path_hash->entries);
                    if (!(gtop->flags & GTOP_NOSORT))
                            qsort(gtop->path_array, gtop->path_hash->entries, sizeof(char *), compare_path);
                    gtop->path_count = gtop->path_hash->entries;
                    gtop->path_index = 0;

                    if (gtop->path_index >= gtop->path_count)
                            return NULL;
                    gtop->gtp.path = gtop->path_array[gtop->path_index++];
                    return &gtop->gtp;
            } else if (gtop->flags & GTOP_KEY) {
                    return ((gtop->gtp.tag = dbop_first(gtop->dbop, key, preg, dbflags)) == NULL)
                            ? NULL : &gtop->gtp;
            } else {
                    if (gtop->vb == NULL)
                            gtop->vb = varray_open(sizeof(GTP), 200);
                    else
                            varray_reset(gtop->vb);
                    if (gtop->segment_pool == NULL)
                            gtop->segment_pool = pool_open();
                    else
                            pool_reset(gtop->segment_pool);
                    if (gtop->path_hash == NULL)
                            gtop->path_hash = strhash_open(HASHBUCKETS);
                    else
                            strhash_reset(gtop->path_hash);
                    tagline = dbop_first(gtop->dbop, key, preg, dbflags);
                    if (tagline == NULL)
                            return NULL;
                    /*
                     * Dbop_next() wil read the same record again.
                     */
                    dbop_unread(gtop->dbop);
                    /*
                     * Read a tag segment with sorting.
                     */
                    segment_read(gtop);
                    return  &gtop->gtp_array[gtop->gtp_index++];
            }
    }
    /*
     * gtags_next: return next record.
     *
     *      i)      gtop    GTOP structure
     *      r)              record
     *                      NULL end of tag
     */
    GTP *
    gtags_next(GTOP *gtop)
    {
            if (gtop->flags & GTOP_PATH) {
                    if (gtop->path_index >= gtop->path_count)
                            return NULL;
                    gtop->gtp.path = gtop->path_array[gtop->path_index++];
                    return &gtop->gtp;
            } else if (gtop->flags & GTOP_KEY) {
                    return ((gtop->gtp.tag = dbop_next(gtop->dbop)) == NULL)
                            ? NULL : &gtop->gtp;
            } else {
                    /*
                     * End of segment.
                     * Reset resources and read new segment again.
                     */
                    if (gtop->gtp_index >= gtop->gtp_count) {
                            varray_reset(gtop->vb);
                            pool_reset(gtop->segment_pool);
                            /* strhash_reset(gtop->path_hash); */
                            segment_read(gtop);
                    }
                    if (gtop->gtp_index >= gtop->gtp_count)
                            return NULL;
                    return &gtop->gtp_array[gtop->gtp_index++];
            }
    }
    /*
     * gtags_close: close tag file
     *
     *      i)      gtop    GTOP structure
     */
    void
    gtags_close(GTOP *gtop)
    {
            if (gtop->format & GTAGS_COMPACT && gtop->fp != NULL)
                    fclose(gtop->fp);
            if (gtop->format & GTAGS_COMPRESS)
                    abbrev_close();
            if (gtop->format & GTAGS_COMPACT && gtop->cur_path[0])
                    flush_pool(gtop);
            if (gtop->segment_pool)
                    pool_close(gtop->segment_pool);
            if (gtop->path_array)
                    free(gtop->path_array);
            if (gtop->sb)
                    strbuf_close(gtop->sb);
            if (gtop->vb)
                    varray_close(gtop->vb);
            if (gtop->path_hash)
                    strhash_close(gtop->path_hash);
            gpath_close();
            dbop_close(gtop->dbop);
            free(gtop);
    }
    /*
     * flush_pool: flush the pool and write is as compact format.
     *
     *      i)      gtop    descripter of GTOP
     */
    static void
    flush_pool(GTOP *gtop)
    {
            struct sh_entry *entry;
            const char *s_fid = gpath_path2fid(gtop->cur_path, NULL);
            int header_offset;
            int i, last;

            if (s_fid == NULL)
                    die("GPATH is corrupted.('%s' not found)", gtop->cur_path);
            /*
             * Write records as compact format and free line number table
             * for each entry in the pool.
             */
            for (entry = strhash_first(gtop->path_hash); entry; entry = strhash_next(gtop->path_hash)) {
                    VARRAY *vb = (VARRAY *)entry->value;
                    int *lno_array = varray_assign(vb, 0, 0);
                    const char *key = entry->name;

                    /*
                     * extract method when class method definition.
                     *
                     * Ex: Class::method(...)
                     *
                     * key  = 'method'
                     * data = 'Class::method  103 ./class.cpp ...'
                     */
                    if (gtop->flags & GTAGS_EXTRACTMETHOD) {
                            if ((key = locatestring(entry->name, ".", MATCH_LAST)) != NULL)
                                    key++;
                            else if ((key = locatestring(entry->name, "::", MATCH_LAST)) != NULL)
                                    key += 2;
                            else
                                    key = entry->name;
                    }
                    /* Sort line number table */
                    qsort(lno_array, vb->length, sizeof(int), compare_lineno);

                    strbuf_reset(gtop->sb);
                    strbuf_puts(gtop->sb, s_fid);
                    strbuf_putc(gtop->sb, ' ');
                    if (gtop->format & GTAGS_COMPNAME) {
                            strbuf_puts(gtop->sb, compress(entry->name, key));
                    } else {
                            strbuf_puts(gtop->sb, entry->name);
                    }
                    strbuf_putc(gtop->sb, ' ');
                    header_offset = strbuf_getlen(gtop->sb);
                    /*
                     * If GTAGS_COMPLINE flag is set, each line number is expressed as the
                     * difference from the previous line number except for the head.
                     * GTAGS_COMPLINE is set by default in format version 5.
                     */
                    if (gtop->format & GTAGS_COMPLINE) {
                            int cont = 0;

                            last = 0;                       /* line 0 doesn't exist */
                            for (i = 0; i < vb->length; i++) {
                                    int n = lno_array[i];

                                    if (n == last)
                                            continue;
                                    if (last > 0 && n == last + 1) {
                                            if (!cont) {
                                                    /*
                                                     * Don't use range expression at the head.
                                                     */
                                                    if (strbuf_getlen(gtop->sb) == header_offset)
                                                            strbuf_putn(gtop->sb, n);
                                                    else
                                                            cont = last;
                                            }
                                    } else {
                                            /*
                                             * Range expression. ex: 10-2 means 10 11 12
                                             */
                                            if (cont) {
                                                    strbuf_putc(gtop->sb, '-');
                                                    strbuf_putn(gtop->sb, last - cont);
                                                    cont = 0;
                                            }
                                            if (strbuf_getlen(gtop->sb) > header_offset) {
                                                    strbuf_putc(gtop->sb, ',');
                                                    strbuf_putn(gtop->sb, n - last);
                                            } else {
                                                    strbuf_putn(gtop->sb, n);
                                            }
                                            if (strbuf_getlen(gtop->sb) > DBOP_PAGESIZE / 4) {
                                                    dbop_put(gtop->dbop, key, strbuf_value(gtop->sb));
                                                    strbuf_setlen(gtop->sb, header_offset);
                                            }
                                    }
                                    last = n;
                            }
                            if (cont) {
                                    strbuf_putc(gtop->sb, '-');
                                    strbuf_putn(gtop->sb, last - cont);
                            }
                    } else {
                            /*
                             * This code is to support older format (version 4).
                             */
                            last = 0;                       /* line 0 doesn't exist */
                            for (i = 0; i < vb->length; i++) {
                                    int n = lno_array[i];

                                    if (n == last)
                                            continue;
                                    if (strbuf_getlen(gtop->sb) > header_offset)
                                            strbuf_putc(gtop->sb, ',');
                                    strbuf_putn(gtop->sb, n);
                                    if (strbuf_getlen(gtop->sb) > DBOP_PAGESIZE / 4) {
                                            dbop_put(gtop->dbop, key, strbuf_value(gtop->sb));
                                            strbuf_setlen(gtop->sb, header_offset);
                                    }
                                    last = n;
                            }
                    }
                    if (strbuf_getlen(gtop->sb) > header_offset)
                            dbop_put(gtop->dbop, key, strbuf_value(gtop->sb));
                    /* Free line number table */
                    varray_close(vb);
            }
    }
    /*
     * Read a tag segment with sorting.
     *
     *      i)      gtop    GTOP structure
     *      o)      gtop->gtp_array         segment table
     *      o)      gtop->gtp_count         segment table size
     *      o)      gtop->gtp_index         segment table index (initial value = 0)
     *      o)      gtop->cur_tagname       current tag name
     *
     * A segment is a set of tag records which have same tag name.
     * This function read a segment from tag file, sort it and put it on segment table.
     * This function can treat both of standard format and compact format.
     *
     * Sorting is done by three keys.
     *      1st key: tag name
     *      2nd key: file name
     *      3rd key: line number
     * Since all records in a segment have same tag name, you need not think about 1st key.
     */
    void
    segment_read(GTOP *gtop)
    {
            const char *tagline, *fid, *path, *lineno;
            GTP *gtp;
            struct sh_entry *sh;

            /*
             * Save tag lines.
             */
            gtop->cur_tagname[0] = '\0';
            while ((tagline = dbop_next(gtop->dbop)) != NULL) {
                    /*
                     * get tag name and line number.
                     *
                     * tagline = <file id> <tag name> <line number>
                     */
                    if (gtop->cur_tagname[0] == '\0') {
                            strlimcpy(gtop->cur_tagname, gtop->dbop->lastkey, sizeof(gtop->cur_tagname));
                    } else if (strcmp(gtop->cur_tagname, gtop->dbop->lastkey) != 0) {
                            /*
                             * Dbop_next() wil read the same record again.
                             */
                            dbop_unread(gtop->dbop);
                            break;
                    }
                    gtp = varray_append(gtop->vb);
                    gtp->tagline = pool_strdup(gtop->segment_pool, tagline, 0);
                    gtp->tag = (const char *)gtop->cur_tagname;
                    /*
                     * convert fid into hashed path name to save memory.
                     */
                    fid = (const char *)strmake(tagline, " ");
                    path = gpath_fid2path(fid, NULL);
                    if (path == NULL)
                            die("gtags_first: path not found. (fid=%s)", fid);
                    sh = strhash_assign(gtop->path_hash, path, 1);
                    gtp->path = sh->name;
                    lineno = seekto(gtp->tagline, SEEKTO_LINENO);
                    if (lineno == NULL)
                            die("illegal tag record.\n%s", tagline);
                    gtp->lineno = atoi(lineno);
            }
            /*
             * Sort tag lines.
             */
            gtop->gtp_array = varray_assign(gtop->vb, 0, 0);
            gtop->gtp_count = gtop->vb->length;
            gtop->gtp_index = 0;
            if (!(gtop->flags & GTOP_NOSORT))
                    qsort(gtop->gtp_array, gtop->gtp_count, sizeof(GTP), compare_tags);
    }

/* [<][>][^][v][top][bottom][index][help] */