head	1.1;
access;
symbols;
locks; strict;
comment	@# @;


1.1
date	2021.09.30.22.01.03;	author agc;	state Exp;
branches;
next	;
commitid	5QRZudtjftD6K1bD;


desc
@@


1.1
log
@Update buffer gap routines to the latest API and functionality - 20210930.

The API has been consolidated around 4 functions:

1. gap_new() - returns a new (opaque) buffer gap structure
2. gap_dispose() - dispose of a previously allocated buffer gap structure
3. gap_exec() - perform operations on the buffer gap which expect an
   integer return value
4. gap_exec_string() - perform operations on the buffer gap which return a byte
   array (typically return buffer contents, or the buffer name)

All of the "verbs" are described in the libgap.3 manual page, found in the
dist directory. The main.c test program, again found in the dist directory,
gives 7 worked examples of how to use the functionality to achieve desired ends,
and there are 7 test input files, along with expected results, which are
provided as a regression test suite - accessible using "make t" from the top
level directory.

The buffer gap routines use a copy of the embedded agcre regexp library to
provide bounded (non-exponential time) results for regexp searching forwards
and backwards - for more information, see the agcre directory at the same level
as this code, or the source code in dist/agcre-embed.[ch].

Also move to a more usual reachover infrastructure
@
text
@/*-
 * Copyright (c) 2021 Alistair Crooks <agc@@NetBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <sys/types.h>
#include <sys/stat.h>

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "agcre-embed.h"
#include "gap.h"

/* this represents a single change */
typedef struct chg_t {
	char		 type;		/* type of change */
	uint64_t	 off;		/* offset where it happened */
	char		*s;		/* text */
	size_t		 size;		/* bytes in text */
} chg_t;

/* an editing buffer gap */
struct gap_t {
	uint64_t		 lhsc;		/* # of bytes to left */
	uint64_t		 rhsc;		/* # of bytes to right */
	uint64_t		 lhsnl;		/* # of \n bytes to left */
	uint64_t		 rhsnl;		/* # of \n bytes to right */
	uint64_t		 size;		/* allocated size in bytes of v */
	char			*v;		/* the text */
	char			*name;		/* file name */
	uint32_t		 dirty;		/* file has been changed */
	uint32_t		 chgc;		/* # of changes */
	uint32_t		 chgmax;	/* allocated size of change array */
	chg_t			*chgs;		/* change array */
	size_t			 mc;		/* size of match vector */
	size_t			 allocmc;	/* allocated size of match vector */
	agcre_regmatch_t	*m;		/* match vector */
};

static const int	begin = 1;
static const int	wrap = 1;
static const int	end = 0;
static const int	anchor = 1;

static const int	lhs = -1;
static const int	rhs = 1;

static const uint32_t	maxlen = 32;

/* hashing function */
static uint32_t
djbhash(const char *s)
{
	const char	*from = s;
	uint32_t         hash;

	for (hash = 5381; *s && (uint32_t)(s - from) < maxlen ; ) {
		hash = hash * 33 + *s++;
	}
	return hash + (hash >> 5);
}

/* find subscript of rhs */
static inline uint64_t
rhssub(uint64_t size, uint64_t off)
{
	return size - 1 - off;
}

/* add a new change to the list */
static int
chg_new(gap_t *gap, char type, const char *s, size_t cc)
{
	chg_t	*chg;

	if (gap->chgc == gap->chgmax) {
		chg = realloc(gap->chgs, (gap->chgmax + 32) * sizeof(*gap->chgs));
		if (chg == NULL) {
			return 0;
		}
		memset(&chg[gap->chgc], 0x0, 32 * sizeof(*gap->chgs));
		gap->chgs = chg;
		gap->chgmax += 32;
	}
	chg = &gap->chgs[gap->chgc++];
	chg->type = type;
	chg->off = gap->lhsc;
	chg->size = cc;
	if ((chg->s = calloc(1, cc)) == NULL) {
		return 0;
	}
	memcpy(chg->s, s, cc);
	return 1;
}

/* seek around via bytes */
static int
byteseek(gap_t *gap, int64_t n)
{
	int64_t	i;

	if (n < 0) {
		if (n + (int64_t)gap->lhsc < 0) {
			return 0;
		}
		for (i = 0 ; i > n ; --i) {
			if ((gap->v[rhssub(gap->size, ++gap->rhsc)] = gap->v[--gap->lhsc]) == '\n') {
				gap->rhsnl += 1;
				gap->lhsnl -= 1;
			}
		}
	} else {
		if (n > (int64_t)gap->rhsc) {
			return 0;
		}
		for (i = 0 ; i < n ; i++) {
			if ((gap->v[gap->lhsc++] = gap->v[rhssub(gap->size, gap->rhsc--)]) == '\n') {
				gap->lhsnl += 1;
				gap->rhsnl -= 1;
			}
		}
	}
	return 1;
}

/* seek relatively to the line number - 0-based */
static int
lineseek(gap_t *gap, int64_t n)
{
	int64_t	fline;

	if ((fline = gap->lhsnl + n) < 0 || fline >= (int64_t)(gap->lhsnl + gap->rhsnl)) {
		return 0;
	}
	if (n < 0) {
		while ((int64_t)gap->lhsnl >= fline) {
			if (!byteseek(gap, -1)) {
				break;
			}
		}
		/* we're now on the \n char */
		if (gap->lhsc > 0) {
			byteseek(gap, 1);
		}
	} else {
		while ((int64_t)gap->lhsnl < fline) {
			if (!byteseek(gap, 1)) {
				break;
			}
		}
		/* we're now on the \n char */
		if (gap->rhsc > 0) {
			byteseek(gap, 1);
		}
	}
	return 1;
}

/* insert bytes */
static int
insertbytes(gap_t *gap, const char *s, size_t cc, int newchg)
{
	uint64_t	 newsize;
	size_t		 i;
	char		*v;

	if (newchg && !chg_new(gap, 'i', s, cc)) {
		return 0;
	}
	if ((newsize = gap->lhsc + gap->rhsc + cc + 120) >= gap->size) {
		if ((v = realloc(gap->v, newsize)) == NULL) {
			return 0;
		}
		memmove(&v[rhssub(newsize, gap->rhsc)], &gap->v[rhssub(gap->size, gap->rhsc)], gap->rhsc);
		gap->v = v;
		gap->size = newsize;
	}
	for (i = 0 ; i < cc ; i++) {
		if ((gap->v[gap->lhsc++] = s[i]) == '\n') {
			gap->lhsnl += 1;
		}
	}
	return 1;
}

/* delete bytes */
static int
deletebytes(gap_t *gap, int64_t n, int newchg)
{
	int64_t	i;

	if (newchg && !chg_new(gap, 'd', &gap->v[rhssub(gap->size, gap->rhsc)], n)) {
		return 0;
	}
	if (n > (int64_t)gap->rhsc) {
		return 0;
	}
	for (i = 0 ; i < n ; i++) {
		if (gap->v[rhssub(gap->size, gap->rhsc--)] == '\n') {
			gap->rhsnl -= 1;
		}
	}
	return 1;
}

/* repeat a change */
static int
chg_redo(gap_t *gap)
{
	chg_t	*chg;

	if (gap->chgc == 0) {
		return 0;
	}
	chg = &gap->chgs[gap->chgc - 1];
	switch(chg->type) {
	case 'd':
		return deletebytes(gap, chg->size, 1);
	case 'i':
		return insertbytes(gap, chg->s, chg->size, 1);
	}
	return 0;
}

/* undo a change */
static int
chg_undo(gap_t *gap)
{
	chg_t	*chg;

	if (gap->chgc == 0) {
		return 0;
	}
	chg = &gap->chgs[gap->chgc - 1];
	if (!byteseek(gap, chg->off - gap->lhsc)) {
		return 0;
	}
	switch(chg->type) {
	case 'd':
		return insertbytes(gap, chg->s, chg->size, 1);
	case 'i':
		return deletebytes(gap, chg->size, 1);
	}
	return 0;
}

/* search for a regexp */
static int
regexsearch(gap_t *gap, const char *s, ssize_t size, int direction, int wrapsearch, int first, uint32_t anchoring)
{
	agcre_regmatch_t	*newm;
	agcre_regex_t		 r;
	uint32_t		 flags;
	int64_t			 target;
	int64_t			 rhsc;
	size_t			 allocmc;
	int			 found;

	if (size == -1) {
		size = strlen(s);
	}
	r.re_endp = &s[size];
	if (agcre_regcomp(&r, s, AGCRE_REG_EXTENDED | AGCRE_REG_PEND | anchoring) != 0) {
		return 0;
	}
	found = 0;
	if (r.re_nsub + 1 > gap->allocmc) {
		allocmc = (r.re_nsub + 1) + 5;
		if ((newm = realloc(gap->m, allocmc * sizeof(*newm))) == NULL) {
			goto regexsearchdone;
		}
		gap->m = newm;
		gap->allocmc = allocmc;
	}
	gap->mc = r.re_nsub + 1;
	flags = AGCRE_REG_STARTEND;
	if (anchoring) {
		flags |= AGCRE_REG_ANCHOR;
	}
	if (direction == rhs) {
		rhsc = rhssub(gap->size, gap->rhsc);
		gap->m[0].rm_so = rhsc + 1;
		gap->m[0].rm_eo = gap->size;
		if (agcre_regexec(&r, gap->v, gap->mc, gap->m, flags) == 0) {
			found = 1;
			target = (first) ? gap->m[0].rm_so - rhsc : gap->m[0].rm_eo - rhsc;
			byteseek(gap, target);
			goto regexsearchdone;
		}
		gap->m[0].rm_so = 0;
		gap->m[0].rm_eo = gap->lhsc;
		if (wrapsearch && agcre_regexec(&r, gap->v, gap->mc, gap->m, flags) == 0) {
			found = 1;
			target = (first) ? gap->m[0].rm_so - gap->lhsc : gap->m[0].rm_eo - gap->lhsc;
			byteseek(gap, target);
			goto regexsearchdone;
		}
	} else {
		gap->m[0].rm_so = 0;
		gap->m[0].rm_eo = gap->lhsc;
		if (agcre_rev_regexec(&r, gap->v, gap->mc, gap->m, flags) == 0) {
			found = 1;
			target = (first) ? gap->m[0].rm_so - gap->lhsc : gap->m[0].rm_eo - gap->lhsc;
			byteseek(gap, target);
			goto regexsearchdone;
		}
		rhsc = rhssub(gap->size, gap->rhsc);
		gap->m[0].rm_so = rhsc + 1;
		gap->m[0].rm_eo = gap->size;
		if (wrapsearch && agcre_rev_regexec(&r, gap->v, gap->mc, gap->m, flags) == 0) {
			found = 1;
			target = (first) ? gap->m[0].rm_so - rhsc : gap->m[0].rm_eo - rhsc;
			byteseek(gap, target);
			goto regexsearchdone;
		}
	}
regexsearchdone:
	agcre_regfree(&r);
	return found;
}

/* read a file into allocated memory */
static char *
getfilecontents(FILE *fp, size_t *n)
{
	struct stat	 st;
	size_t		 cc;
	size_t		 rc;
	char		*s;

	fstat(fileno(fp), &st);
	*n = (size_t)st.st_size;
	if ((s = calloc(1, *n)) == NULL) {
		return NULL;
	}
	for (cc = 0 ; (rc = fread(&s[cc], 1, *n - cc, fp)) > 0 ; cc += rc) {
	}
	if (cc != *n) {
		/* all or nothing */
		free(s);
		return NULL;
	}
	return s;
}

/* read a file into the buffer in the current position */
static int
readfile(gap_t *gap, const char *f, size_t cc)
{
	size_t	 size;
	char	 name[1024];
	FILE	*fp;
	char	*s;
	int	 ok;

	s = NULL;
	fp = NULL;
	ok = 0;
	snprintf(name, sizeof(name), "%.*s", (int)cc, f);
	if ((fp = fopen(name, "r")) == NULL) {
		goto readfiledone;
	}
	if ((s = getfilecontents(fp, &size)) == NULL) {
		goto readfiledone;
	}
	if (!insertbytes(gap, s, size, 1)) {
		goto readfiledone;
	}
	if (gap->name == NULL) {
		gap->name = strdup(name);
	}
	byteseek(gap, (int64_t)-size);
	ok = 1;
readfiledone:
	free(s);
	fclose(fp);
	return ok;
}

/* write a file from the buffer onto the fs */
static int
writefile(gap_t *gap, const char *f, size_t fsize)
{
	struct stat	 st;
	int64_t		 cc;
	size_t		 wc;
	mode_t		 mode;
	char		 wname[2048];
	char		 name[1024];
	FILE		*fp;
	int		 fd;
	int		 ok;

	fp = NULL;
	ok = 0;
	if (f == NULL) {
		snprintf(name, sizeof(name), "%s", gap->name);
	} else {
		snprintf(name, sizeof(wname), "%.*s", (int)fsize, f);
	}
	snprintf(wname, sizeof(wname), "%s.XXXXXXXX", name);
	if ((fd = mkstemp(wname)) < 0) {
		return 0;
	}
	if ((fp = fdopen(fd, "w")) == NULL) {
		goto writefiledone;
	}
	for (cc = 0 ; cc < (int64_t)gap->lhsc ; cc += wc) {
		if ((wc = fwrite(&gap->v[cc], 1, gap->lhsc - cc, fp)) == 0) {
			break;
		}
	}
	if (cc != (int64_t)gap->lhsc) {
		goto writefiledone;
	}
	for (cc = gap->rhsc - 1 ; cc >= 0 ; cc -= wc) {
		if ((wc = fwrite(&gap->v[rhssub(gap->size, cc)], 1, gap->rhsc - 1 - cc, fp)) == 0) {
			break;
		}
	}
	if (cc != (int64_t)gap->rhsc) {
		goto writefiledone;
	}
	mode = (stat(name, &st) < 0) ? 0666 : (st.st_mode & 07777);
	fchmod(fileno(fp), mode);
	sync();
	sync();
	sync();
	fclose(fp);
	if (rename(wname, name) < 0) {
		unlink(wname);
		goto writefiledone;
	}
	ok = 1;
writefiledone:
	fclose(fp);
	return ok;
}

/* move to absolute beginning of line */
static int
bol(gap_t *gap)
{
	for ( ; gap->lhsc > 0 && gap->v[gap->lhsc - 1] != '\n' ; ) {
		byteseek(gap, -1);
	}
	return 1;
}

/* move to end of line functionality */
static int
eol(gap_t *gap)
{
	for ( ; gap->rhsc > 0 && gap->v[rhssub(gap->size, gap->rhsc - 1)] != '\n' ; ) {
		byteseek(gap, 1);
	}
	return 1;
}

/* got to lbol */
static int
lbol(gap_t *gap)
{
	int	ch;

	bol(gap);
	while (gap->rhsc > 0) {
		ch = gap->v[rhssub(gap->size, gap->rhsc)];
		if (ch != '\t' && ch != ' ') {
			break;
		}
		byteseek(gap, 1);
	}
	return 1;
}

/* find a character in the current line */
static int
charfind(gap_t *gap, const char *s, size_t size, int direction, int target, int wrapping)
{
	char	*from;
	char	*nl;
	char	*p;
	int	 ch;

	ch = s[0];
	if (direction == rhs) {
		from = p = &gap->v[rhssub(gap->size, gap->rhsc - 1)];
		nl = (wrapping) ? NULL : memchr(p, '\n', gap->rhsc - 1);
		if (nl == NULL) {
			nl = &gap->v[gap->size];
		}
		for ( ; p != nl ; p++) {
			if (*p == ch) {
				return byteseek(gap, (int64_t)(p - from) + target);
			}
		}
	} else {
		from = p = &gap->v[gap->lhsc - 1];
		nl = (wrapping) ? NULL : memrchr(gap->v, '\n', gap->lhsc);
		if (nl == NULL) {
			nl = gap->v;
		}
		for ( ; p != nl ; --p) {
			if (*p == ch) {
				return byteseek(gap, (int64_t)-(from - p) - target);
			}
		}
	}
	return 0;
}

/********************************************************************/

/* create a new gap */
gap_t *
gap_new(void)
{
	gap_t	*gap;

	if ((gap = calloc(1, sizeof(*gap))) != NULL) {
		if ((gap->v = calloc(2048, sizeof(*gap->v))) == NULL) {
			free(gap);
			return NULL;
		}
		gap->size = 2048;
	}
	return gap;
}

/* dispose of text in the gap */
int
gap_dispose(gap_t **gap)
{
	if (gap && *gap) {
		free((*gap)->v);
		free((*gap)->name);
		free(*gap);
		*gap = NULL;
		return 1;
	}
	return 0;
}

/* execute a command, numeric return value */
int64_t
gap_exec(gap_t *gap, const char *info, int64_t n, const void *v, size_t size)
{
	if (gap == NULL || info == NULL) {
		return 0;
	}
	switch(djbhash(info)) {
	case /* "WORD-lhs-begin" */ 0xa6d14777:
		return regexsearch(gap, "(\\S+\\s*|\\s+)", -1, lhs, !wrap, begin, anchor);
	case /* "WORD-rhs-begin" */ 0x5f9342ad:
		return regexsearch(gap, "(\\S+\\s*|\\s+)", -1, rhs, !wrap, end, anchor);
	case /* "WORD-rhs-end" */ 0x2a832132:
		return regexsearch(gap, "(\\s*\\S+|\\s+)", -1, rhs, !wrap, end, anchor);
	case /* "blank-line-lhs" */ 0x85b67fa5:
		return regexsearch(gap, "\n\n", -1, lhs, !wrap, begin, !anchor);
	case /* "blank-line-rhs" */ 0x85b699f7:
		return regexsearch(gap, "\n\n", -1, rhs, !wrap, begin, !anchor);
	case /* "bol" */ 0xbe4a596:
		return bol(gap);
	case /* "delete" */ 0x47a09b:
		if (!deletebytes(gap, n, 1)) {
			return 0;
		}
		return 1;
	case /* "dirty?" */ 0x95ec98:
		return gap->dirty;
	case /* "eol" */ 0xbe4b2bf:
		return eol(gap);
	case /* "find-char-lhs-line" */ 0x4eaf2785:
		if (v == NULL || size < 1) {
			return 0;
		}
		return charfind(gap, v, size, lhs, 1, !wrap);
	case /* "find-char-lhs-wrap" */ 0x4eb585be:
		if (v == NULL || size < 1) {
			return 0;
		}
		return charfind(gap, v, size, lhs, 1, wrap);
	case /* "find-char-rhs-line" */ 0xdc867cb6:
		if (v == NULL || size < 1) {
			return 0;
		}
		return charfind(gap, v, size, rhs, 1, !wrap);
	case /* "find-char-rhs-wrap" */ 0xdc8cdaee:
		if (v == NULL || size < 1) {
			return 0;
		}
		return charfind(gap, v, size, rhs, 1, wrap);
	case /* "get-char" */ 0x2bde6b9:
		return (gap->rhsc == 0) ? 0 : gap->v[rhssub(gap->size, gap->rhsc)];
	case /* "lbol" */ 0x807ec7ce:
		return lbol(gap);
	case /* "lhsc" */ 0x807ee29f:
		return gap->lhsc;
	case /* "lhsnl" */ 0x105b3874:
		return gap->lhsnl;
	case /* "line-seek-cur" */ 0xd6d89507:
		return lineseek(gap, n);
	case /* "line-seek-end" */ 0xd6d89cd1:
		return lineseek(gap, gap->lhsnl + gap->rhsnl + n);
	case /* "line-seek-set" */ 0xd6d8d919:
		return lineseek(gap, n - gap->lhsnl);
	case /* "insert" */ 0x4faa2ae:
		if (v == NULL) {
			return 0;
		}
		return insertbytes(gap, v, size, 1);
	case /* "para-lhs" */ 0x14d4f340:
		return regexsearch(gap, "^\\{\n", -1, lhs, !wrap, begin, !anchor);
	case /* "para-rhs" */ 0x14d50d93:
		return regexsearch(gap, "^\\{\n", -1, rhs, !wrap, begin, !anchor);
	case /* "read-file" */ 0x1ff4ae6f:
		return readfile(gap, v, size);
	case /* "redo" */ 0x8082381c:
		return chg_redo(gap);
	case /* "regex-lhs-wrap-begin" */ 0x9051a90f:
		if (v == NULL) {
			return 0;
		}
		return regexsearch(gap, v, size, lhs, wrap, begin, !anchor);
	case /* "regex-rhs-wrap-begin" */ 0xf6ef0da3:
		if (v == NULL) {
			return 0;
		}
		return regexsearch(gap, v, size, rhs, wrap, begin, !anchor);
	case /* "regex-rhs-eof-end" */ 0xe17a8241:
		if (v == NULL) {
			return 0;
		}
		return regexsearch(gap, v, size, rhs, wrap, end, !anchor);
	case /* "rhsc" */ 0x80824737:
		return gap->rhsc;
	case /* "rhsnl" */ 0x10cb3012:
		return gap->rhsnl;
	case /* "seek-cur" */ 0xf6bda6b7:
		return byteseek(gap, n);
	case /* "seek-end" */ 0xf6bdae80:
		return byteseek(gap, gap->lhsc + gap->rhsc + n);
	case /* "seek-set" */ 0xf6bdeac9:
		return byteseek(gap, n - gap->lhsc);
	case /* "set-clean" */ 0xd4010721:
		gap->dirty = 0;
		return 1;
	case /* "set-name" */ 0xe72ac5b:
		if (v == NULL) {
			return 0;
		}
		asprintf(&gap->name, "%.*s", (int)size, (const char *)v);
		return 1;
	case /* "undo" */ 0x808411e3:
		return chg_undo(gap);
	case /* "word-lhs-begin" */ 0xc5ab55bb:
		return regexsearch(gap, "(\\w+\\s*|[[:punct:]]+\\s*|\\s+)", -1, lhs, !wrap, begin, anchor);
	case /* "word-rhs-begin" */ 0x7e6d50f1:
		return regexsearch(gap, "(\\w+\\s*|[[:punct:]]+\\s*|\\s+)", -1, rhs, !wrap, end, anchor);
	case /* "word-rhs-end" */ 0x43cce76:
		return regexsearch(gap, "(\\s*\\w+|\\s*[[:punct:]]+|\\s+)", -1, rhs, !wrap, end, anchor);
	case /* "write-file" */ 0xb2d1c629:
		return writefile(gap, v, size);
	}
	return 0;
}

/* execute a string, string return value (in allcoated memory, caller frees) */
char *
gap_exec_string(gap_t *gap, const char *info, size_t *n)
{
	size_t	 cc;
	char	*s;

	if (gap == NULL || info == NULL) {
		return NULL;
	}
	switch(djbhash(info)) {
	case /* "lhs?" */ 0x807ee27a:
		cc = gap->lhsc;
		s = calloc(1, cc + 1);
		memcpy(s, gap->v, cc);
		s[cc] = 0x0;
		if (n != NULL) {
			*n = cc;
		}
		return s;
	case /* "name?" */ 0x107c79ef:
		if (gap->name == NULL) {
			return NULL;
		}
		if (n != NULL) {
			*n = strlen(gap->name);
		}
		return strdup(gap->name);
	case /* "rhs?" */ 0x80824712:
		cc = gap->rhsc;
		s = calloc(1, cc + 1);
		memcpy(s, &gap->v[rhssub(gap->size, gap->rhsc)], cc);
		s[cc] = 0x0;
		if (n != NULL) {
			*n = cc;
		}
		return s;
	}
	return NULL;
}
@
