Changeset View
Changeset View
Standalone View
Standalone View
contrib/mg/compile.c
- This file was added.
/* $NetBSD: compile.c,v 1.12 2017/05/04 09:46:30 roy Exp $ */ | |||||
/* | |||||
* Copyright (c) 2009, 2010, 2011 The NetBSD Foundation, Inc. | |||||
* | |||||
* This code is derived from software contributed to The NetBSD Foundation | |||||
* by Roy Marples. | |||||
* | |||||
* 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/cdefs.h> | |||||
#include <ctype.h> | |||||
#include <err.h> | |||||
#include <errno.h> | |||||
#include <limits.h> | |||||
#include <stdarg.h> | |||||
#include <stdlib.h> | |||||
#include <stdint.h> | |||||
#include <stdio.h> | |||||
#include <string.h> | |||||
#include "term_private.h" | |||||
#include "terminfo_term.h" | |||||
/* From NetBSD sys/arch/hpc/stand/include/machine/endian.h */ | |||||
static uint16_t | |||||
le16dec(const void *buf) | |||||
{ | |||||
const uint8_t *p = (const uint8_t *)buf; | |||||
return ((p[1] << 8) | p[0]); | |||||
} | |||||
static void | |||||
le16enc(void *buf, uint32_t u) | |||||
{ | |||||
uint8_t *p = (uint8_t *) buf; | |||||
p[0] = u & 0xff; | |||||
p[1] = ((unsigned)u >> 8) & 0xff; | |||||
} | |||||
static void | |||||
dowarn(int flags, const char *fmt, ...) | |||||
{ | |||||
va_list va; | |||||
errno = EINVAL; | |||||
if (flags & TIC_WARNING) { | |||||
va_start(va, fmt); | |||||
vwarnx(fmt, va); | |||||
va_end(va); | |||||
} | |||||
} | |||||
char * | |||||
_ti_grow_tbuf(TBUF *tbuf, size_t len) | |||||
{ | |||||
char *buf; | |||||
size_t l; | |||||
l = tbuf->bufpos + len; | |||||
if (l > tbuf->buflen) { | |||||
if (tbuf->buflen == 0) | |||||
buf = malloc(l); | |||||
else | |||||
buf = realloc(tbuf->buf, l); | |||||
if (buf == NULL) | |||||
return NULL; | |||||
tbuf->buf = buf; | |||||
tbuf->buflen = l; | |||||
} | |||||
return tbuf->buf; | |||||
} | |||||
char * | |||||
_ti_find_cap(TBUF *tbuf, char type, short ind) | |||||
{ | |||||
size_t n; | |||||
uint16_t num; | |||||
char *cap; | |||||
cap = tbuf->buf; | |||||
for (n = tbuf->entries; n > 0; n--) { | |||||
num = le16dec(cap); | |||||
cap += sizeof(uint16_t); | |||||
if ((short)num == ind) | |||||
return cap; | |||||
switch (type) { | |||||
case 'f': | |||||
cap++; | |||||
break; | |||||
case 'n': | |||||
cap += sizeof(uint16_t); | |||||
break; | |||||
case 's': | |||||
num = le16dec(cap); | |||||
cap += sizeof(uint16_t); | |||||
cap += num; | |||||
break; | |||||
} | |||||
} | |||||
errno = ESRCH; | |||||
return NULL; | |||||
} | |||||
char * | |||||
_ti_find_extra(TBUF *tbuf, const char *code) | |||||
{ | |||||
size_t n; | |||||
uint16_t num; | |||||
char *cap; | |||||
cap = tbuf->buf; | |||||
for (n = tbuf->entries; n > 0; n--) { | |||||
num = le16dec(cap); | |||||
cap += sizeof(uint16_t); | |||||
if (strcmp(cap, code) == 0) | |||||
return cap + num; | |||||
cap += num; | |||||
switch (*cap++) { | |||||
case 'f': | |||||
cap++; | |||||
break; | |||||
case 'n': | |||||
cap += sizeof(uint16_t); | |||||
break; | |||||
case 's': | |||||
num = le16dec(cap); | |||||
cap += sizeof(uint16_t); | |||||
cap += num; | |||||
break; | |||||
} | |||||
} | |||||
errno = ESRCH; | |||||
return NULL; | |||||
} | |||||
size_t | |||||
_ti_store_extra(TIC *tic, int wrn, char *id, char type, char flag, short num, | |||||
char *str, size_t strl, int flags) | |||||
{ | |||||
size_t l; | |||||
if (strcmp(id, "use") != 0) { | |||||
if (_ti_find_extra(&tic->extras, id) != NULL) | |||||
return 0; | |||||
if (!(flags & TIC_EXTRA)) { | |||||
if (wrn != 0) | |||||
dowarn(flags, "%s: %s: unknown capability", | |||||
tic->name, id); | |||||
return 0; | |||||
} | |||||
} | |||||
l = strlen(id) + 1; | |||||
if (l > UINT16_T_MAX) { | |||||
dowarn(flags, "%s: %s: cap name is too long", tic->name, id); | |||||
return 0; | |||||
} | |||||
if (!_ti_grow_tbuf(&tic->extras, | |||||
l + strl + (sizeof(uint16_t) * 2) + 1)) | |||||
return 0; | |||||
le16enc(tic->extras.buf + tic->extras.bufpos, (uint16_t)l); | |||||
tic->extras.bufpos += sizeof(uint16_t); | |||||
memcpy(tic->extras.buf + tic->extras.bufpos, id, l); | |||||
tic->extras.bufpos += l; | |||||
tic->extras.buf[tic->extras.bufpos++] = type; | |||||
switch (type) { | |||||
case 'f': | |||||
tic->extras.buf[tic->extras.bufpos++] = flag; | |||||
break; | |||||
case 'n': | |||||
le16enc(tic->extras.buf + tic->extras.bufpos, (uint16_t)num); | |||||
tic->extras.bufpos += sizeof(uint16_t); | |||||
break; | |||||
case 's': | |||||
le16enc(tic->extras.buf + tic->extras.bufpos, (uint16_t)strl); | |||||
tic->extras.bufpos += sizeof(uint16_t); | |||||
memcpy(tic->extras.buf + tic->extras.bufpos, str, strl); | |||||
tic->extras.bufpos += strl; | |||||
break; | |||||
} | |||||
tic->extras.entries++; | |||||
return 1; | |||||
} | |||||
ssize_t | |||||
_ti_flatten(uint8_t **buf, const TIC *tic) | |||||
{ | |||||
size_t buflen, len, alen, dlen; | |||||
uint8_t *cap; | |||||
len = strlen(tic->name) + 1; | |||||
if (tic->alias == NULL) | |||||
alen = 0; | |||||
else | |||||
alen = strlen(tic->alias) + 1; | |||||
if (tic->desc == NULL) | |||||
dlen = 0; | |||||
else | |||||
dlen = strlen(tic->desc) + 1; | |||||
buflen = sizeof(char) + | |||||
sizeof(uint16_t) + len + | |||||
sizeof(uint16_t) + alen + | |||||
sizeof(uint16_t) + dlen + | |||||
(sizeof(uint16_t) * 2) + tic->flags.bufpos + | |||||
(sizeof(uint16_t) * 2) + tic->nums.bufpos + | |||||
(sizeof(uint16_t) * 2) + tic->strs.bufpos + | |||||
(sizeof(uint16_t) * 2) + tic->extras.bufpos; | |||||
*buf = malloc(buflen); | |||||
if (*buf == NULL) | |||||
return -1; | |||||
cap = *buf; | |||||
*cap++ = 1; | |||||
le16enc(cap, (uint16_t)len); | |||||
cap += sizeof(uint16_t); | |||||
memcpy(cap, tic->name, len); | |||||
cap += len; | |||||
le16enc(cap, (uint16_t)alen); | |||||
cap += sizeof(uint16_t); | |||||
if (tic->alias != NULL) { | |||||
memcpy(cap, tic->alias, alen); | |||||
cap += alen; | |||||
} | |||||
le16enc(cap, (uint16_t)dlen); | |||||
cap += sizeof(uint16_t); | |||||
if (tic->desc != NULL) { | |||||
memcpy(cap, tic->desc, dlen); | |||||
cap += dlen; | |||||
} | |||||
if (tic->flags.entries == 0) { | |||||
le16enc(cap, 0); | |||||
cap += sizeof(uint16_t); | |||||
} else { | |||||
le16enc(cap, (uint16_t)(tic->flags.bufpos + sizeof(uint16_t))); | |||||
cap += sizeof(uint16_t); | |||||
le16enc(cap, (uint16_t)tic->flags.entries); | |||||
cap += sizeof(uint16_t); | |||||
memcpy(cap, tic->flags.buf, tic->flags.bufpos); | |||||
cap += tic->flags.bufpos; | |||||
} | |||||
if (tic->nums.entries == 0) { | |||||
le16enc(cap, 0); | |||||
cap += sizeof(uint16_t); | |||||
} else { | |||||
le16enc(cap, (uint16_t)(tic->nums.bufpos + sizeof(uint16_t))); | |||||
cap += sizeof(uint16_t); | |||||
le16enc(cap, (uint16_t)tic->nums.entries); | |||||
cap += sizeof(uint16_t); | |||||
memcpy(cap, tic->nums.buf, tic->nums.bufpos); | |||||
cap += tic->nums.bufpos; | |||||
} | |||||
if (tic->strs.entries == 0) { | |||||
le16enc(cap, 0); | |||||
cap += sizeof(uint16_t); | |||||
} else { | |||||
le16enc(cap, (uint16_t)(tic->strs.bufpos + sizeof(uint16_t))); | |||||
cap += sizeof(uint16_t); | |||||
le16enc(cap, (uint16_t)tic->strs.entries); | |||||
cap += sizeof(uint16_t); | |||||
memcpy(cap, tic->strs.buf, tic->strs.bufpos); | |||||
cap += tic->strs.bufpos; | |||||
} | |||||
if (tic->extras.entries == 0) { | |||||
le16enc(cap, 0); | |||||
cap += sizeof(uint16_t); | |||||
} else { | |||||
le16enc(cap, (uint16_t)(tic->extras.bufpos + sizeof(uint16_t))); | |||||
cap += sizeof(uint16_t); | |||||
le16enc(cap, (uint16_t)tic->extras.entries); | |||||
cap += sizeof(uint16_t); | |||||
memcpy(cap, tic->extras.buf, tic->extras.bufpos); | |||||
cap += tic->extras.bufpos; | |||||
} | |||||
return cap - *buf; | |||||
} | |||||
static int | |||||
encode_string(const char *term, const char *cap, TBUF *tbuf, const char *str, | |||||
int flags) | |||||
{ | |||||
int slash, i, num; | |||||
char ch, *p, *s, last; | |||||
if (_ti_grow_tbuf(tbuf, strlen(str) + 1) == NULL) | |||||
return -1; | |||||
p = s = tbuf->buf + tbuf->bufpos; | |||||
slash = 0; | |||||
last = '\0'; | |||||
/* Convert escape codes */ | |||||
while ((ch = *str++) != '\0') { | |||||
if (ch == '\n') { | |||||
/* Following a newline, strip leading whitespace from | |||||
* capability strings. */ | |||||
while (isspace((unsigned char)*str)) | |||||
str++; | |||||
continue; | |||||
} | |||||
if (slash == 0 && ch == '\\') { | |||||
slash = 1; | |||||
continue; | |||||
} | |||||
if (slash == 0) { | |||||
if (last != '%' && ch == '^') { | |||||
ch = *str++; | |||||
if (((unsigned char)ch) >= 128) | |||||
dowarn(flags, | |||||
"%s: %s: illegal ^ character", | |||||
term, cap); | |||||
if (ch == '\0') | |||||
break; | |||||
if (ch == '?') | |||||
ch = '\177'; | |||||
else if ((ch &= 037) == 0) | |||||
ch = (char)128; | |||||
} else if (!isprint((unsigned char)ch)) | |||||
dowarn(flags, | |||||
"%s: %s: unprintable character", | |||||
term, cap); | |||||
*p++ = ch; | |||||
last = ch; | |||||
continue; | |||||
} | |||||
slash = 0; | |||||
if (ch >= '0' && ch <= '7') { | |||||
num = ch - '0'; | |||||
for (i = 0; i < 2; i++) { | |||||
if (*str < '0' || *str > '7') { | |||||
if (isdigit((unsigned char)*str)) | |||||
dowarn(flags, | |||||
"%s: %s: non octal" | |||||
" digit", term, cap); | |||||
else | |||||
break; | |||||
} | |||||
num = num * 8 + *str++ - '0'; | |||||
} | |||||
if (num == 0) | |||||
num = 0200; | |||||
*p++ = (char)num; | |||||
continue; | |||||
} | |||||
switch (ch) { | |||||
case 'a': | |||||
*p++ = '\a'; | |||||
break; | |||||
case 'b': | |||||
*p++ = '\b'; | |||||
break; | |||||
case 'e': /* FALLTHROUGH */ | |||||
case 'E': | |||||
*p++ = '\033'; | |||||
break; | |||||
case 'f': | |||||
*p++ = '\014'; | |||||
break; | |||||
case 'l': /* FALLTHROUGH */ | |||||
case 'n': | |||||
*p++ = '\n'; | |||||
break; | |||||
case 'r': | |||||
*p++ = '\r'; | |||||
break; | |||||
case 's': | |||||
*p++ = ' '; | |||||
break; | |||||
case 't': | |||||
*p++ = '\t'; | |||||
break; | |||||
default: | |||||
/* We should warn here */ | |||||
case '^': | |||||
case ',': | |||||
case ':': | |||||
case '|': | |||||
*p++ = ch; | |||||
break; | |||||
} | |||||
last = ch; | |||||
} | |||||
*p++ = '\0'; | |||||
tbuf->bufpos += (size_t)(p - s); | |||||
return 0; | |||||
} | |||||
char * | |||||
_ti_get_token(char **cap, char sep) | |||||
{ | |||||
char esc, *token; | |||||
while (isspace((unsigned char)**cap)) | |||||
(*cap)++; | |||||
if (**cap == '\0') | |||||
return NULL; | |||||
/* We can't use stresep(3) as ^ we need two escape chars */ | |||||
esc = '\0'; | |||||
for (token = *cap; | |||||
**cap != '\0' && (esc != '\0' || **cap != sep); | |||||
(*cap)++) | |||||
{ | |||||
if (esc == '\0') { | |||||
if (**cap == '\\' || **cap == '^') | |||||
esc = **cap; | |||||
} else { | |||||
/* termcap /E/ is valid */ | |||||
if (sep == ':' && esc == '\\' && **cap == 'E') | |||||
esc = 'x'; | |||||
else | |||||
esc = '\0'; | |||||
} | |||||
} | |||||
if (**cap != '\0') | |||||
*(*cap)++ = '\0'; | |||||
return token; | |||||
} | |||||
TIC * | |||||
_ti_compile(char *cap, int flags) | |||||
{ | |||||
char *token, *p, *e, *name, *desc, *alias; | |||||
signed char flag; | |||||
long cnum; | |||||
short ind, num; | |||||
size_t len; | |||||
TBUF buf; | |||||
TIC *tic; | |||||
name = _ti_get_token(&cap, ','); | |||||
if (name == NULL) { | |||||
dowarn(flags, "no seperator found: %s", cap); | |||||
return NULL; | |||||
} | |||||
desc = strrchr(name, '|'); | |||||
if (desc != NULL) | |||||
*desc++ = '\0'; | |||||
alias = strchr(name, '|'); | |||||
if (alias != NULL) | |||||
*alias++ = '\0'; | |||||
tic = calloc(sizeof(*tic), 1); | |||||
if (tic == NULL) | |||||
return NULL; | |||||
buf.buf = NULL; | |||||
buf.buflen = 0; | |||||
tic->name = strdup(name); | |||||
if (tic->name == NULL) | |||||
goto error; | |||||
if (alias != NULL && flags & TIC_ALIAS) { | |||||
tic->alias = strdup(alias); | |||||
if (tic->alias == NULL) | |||||
goto error; | |||||
} | |||||
if (desc != NULL && flags & TIC_DESCRIPTION) { | |||||
tic->desc = strdup(desc); | |||||
if (tic->desc == NULL) | |||||
goto error; | |||||
} | |||||
for (token = _ti_get_token(&cap, ','); | |||||
token != NULL && *token != '\0'; | |||||
token = _ti_get_token(&cap, ',')) | |||||
{ | |||||
/* Skip commented caps */ | |||||
if (!(flags & TIC_COMMENT) && token[0] == '.') | |||||
continue; | |||||
/* Obsolete entries */ | |||||
if (token[0] == 'O' && token[1] == 'T') { | |||||
if (!(flags & TIC_EXTRA)) | |||||
continue; | |||||
token += 2; | |||||
} | |||||
/* str cap */ | |||||
p = strchr(token, '='); | |||||
if (p != NULL) { | |||||
*p++ = '\0'; | |||||
/* Don't use the string if we already have it */ | |||||
ind = (short)_ti_strindex(token); | |||||
if (ind != -1 && | |||||
_ti_find_cap(&tic->strs, 's', ind) != NULL) | |||||
continue; | |||||
/* Encode the string to our scratch buffer */ | |||||
buf.bufpos = 0; | |||||
if (encode_string(tic->name, token, | |||||
&buf, p, flags) == -1) | |||||
goto error; | |||||
if (buf.bufpos > UINT16_T_MAX) { | |||||
dowarn(flags, "%s: %s: string is too long", | |||||
tic->name, token); | |||||
continue; | |||||
} | |||||
if (!VALID_STRING(buf.buf)) { | |||||
dowarn(flags, "%s: %s: invalid string", | |||||
tic->name, token); | |||||
continue; | |||||
} | |||||
if (ind == -1) | |||||
_ti_store_extra(tic, 1, token, 's', -1, -2, | |||||
buf.buf, buf.bufpos, flags); | |||||
else { | |||||
if (!_ti_grow_tbuf(&tic->strs, | |||||
(sizeof(uint16_t) * 2) + buf.bufpos)) | |||||
goto error; | |||||
le16enc(tic->strs.buf + tic->strs.bufpos, (uint16_t)ind); | |||||
tic->strs.bufpos += sizeof(uint16_t); | |||||
le16enc(tic->strs.buf + tic->strs.bufpos, | |||||
(uint16_t)buf.bufpos); | |||||
tic->strs.bufpos += sizeof(uint16_t); | |||||
memcpy(tic->strs.buf + tic->strs.bufpos, | |||||
buf.buf, buf.bufpos); | |||||
tic->strs.bufpos += buf.bufpos; | |||||
tic->strs.entries++; | |||||
} | |||||
continue; | |||||
} | |||||
/* num cap */ | |||||
p = strchr(token, '#'); | |||||
if (p != NULL) { | |||||
*p++ = '\0'; | |||||
/* Don't use the number if we already have it */ | |||||
ind = (short)_ti_numindex(token); | |||||
if (ind != -1 && | |||||
_ti_find_cap(&tic->nums, 'n', ind) != NULL) | |||||
continue; | |||||
cnum = strtol(p, &e, 0); | |||||
if (*e != '\0') { | |||||
dowarn(flags, "%s: %s: not a number", | |||||
tic->name, token); | |||||
continue; | |||||
} | |||||
if (!VALID_NUMERIC(cnum)) { | |||||
dowarn(flags, "%s: %s: number out of range", | |||||
tic->name, token); | |||||
continue; | |||||
} | |||||
num = (short)cnum; | |||||
if (ind == -1) | |||||
_ti_store_extra(tic, 1, token, 'n', -1, | |||||
num, NULL, 0, flags); | |||||
else { | |||||
if (_ti_grow_tbuf(&tic->nums, | |||||
sizeof(uint16_t) * 2) == NULL) | |||||
goto error; | |||||
le16enc(tic->nums.buf + tic->nums.bufpos, | |||||
(uint16_t)ind); | |||||
tic->nums.bufpos += sizeof(uint16_t); | |||||
le16enc(tic->nums.buf + tic->nums.bufpos, | |||||
(uint16_t)num); | |||||
tic->nums.bufpos += sizeof(uint16_t); | |||||
tic->nums.entries++; | |||||
} | |||||
continue; | |||||
} | |||||
flag = 1; | |||||
len = strlen(token) - 1; | |||||
if (token[len] == '@') { | |||||
flag = CANCELLED_BOOLEAN; | |||||
token[len] = '\0'; | |||||
} | |||||
ind = (short)_ti_flagindex(token); | |||||
if (ind == -1 && flag == CANCELLED_BOOLEAN) { | |||||
if ((ind = (short)_ti_numindex(token)) != -1) { | |||||
if (_ti_find_cap(&tic->nums, 'n', ind) != NULL) | |||||
continue; | |||||
if (_ti_grow_tbuf(&tic->nums, | |||||
sizeof(uint16_t) * 2) == NULL) | |||||
goto error; | |||||
le16enc(tic->nums.buf + tic->nums.bufpos, | |||||
(uint16_t)ind); | |||||
tic->nums.bufpos += sizeof(uint16_t); | |||||
le16enc(tic->nums.buf + tic->nums.bufpos, | |||||
(uint16_t)CANCELLED_NUMERIC); | |||||
tic->nums.bufpos += sizeof(uint16_t); | |||||
tic->nums.entries++; | |||||
continue; | |||||
} else if ((ind = (short)_ti_strindex(token)) != -1) { | |||||
if (_ti_find_cap(&tic->strs, 's', ind) != NULL) | |||||
continue; | |||||
if (_ti_grow_tbuf(&tic->strs, | |||||
(sizeof(uint16_t) * 2) + 1) == NULL) | |||||
goto error; | |||||
le16enc(tic->strs.buf + tic->strs.bufpos, (uint16_t)ind); | |||||
tic->strs.bufpos += sizeof(uint16_t); | |||||
le16enc(tic->strs.buf + tic->strs.bufpos, 0); | |||||
tic->strs.bufpos += sizeof(uint16_t); | |||||
tic->strs.entries++; | |||||
continue; | |||||
} | |||||
} | |||||
if (ind == -1) | |||||
_ti_store_extra(tic, 1, token, 'f', flag, 0, NULL, 0, | |||||
flags); | |||||
else if (_ti_find_cap(&tic->flags, 'f', ind) == NULL) { | |||||
if (_ti_grow_tbuf(&tic->flags, sizeof(uint16_t) + 1) | |||||
== NULL) | |||||
goto error; | |||||
le16enc(tic->flags.buf + tic->flags.bufpos, | |||||
(uint16_t)ind); | |||||
tic->flags.bufpos += sizeof(uint16_t); | |||||
tic->flags.buf[tic->flags.bufpos++] = flag; | |||||
tic->flags.entries++; | |||||
} | |||||
} | |||||
free(buf.buf); | |||||
return tic; | |||||
error: | |||||
free(buf.buf); | |||||
_ti_freetic(tic); | |||||
return NULL; | |||||
} | |||||
void | |||||
_ti_freetic(TIC *tic) | |||||
{ | |||||
if (tic != NULL) { | |||||
free(tic->name); | |||||
free(tic->alias); | |||||
free(tic->desc); | |||||
free(tic->extras.buf); | |||||
free(tic->flags.buf); | |||||
free(tic->nums.buf); | |||||
free(tic->strs.buf); | |||||
free(tic); | |||||
} | |||||
} |