xtgettcap: switch from a table based internal structure, to a single char array

Having the builtin terminfo DB as a table, with one entry per
capability/value pair was ineffective memory-wise - we ended up adding
~20K to the final binary size.

This patch changes the internal representation from a table, to a
single NULL-separated (and NULL-terminated) char array:

  cap1-name \0 cap1-value \0 cap2-name \0 cap2-value \0

The capabilities are still sorted; to lookup a capability we need to
scan from the beginning until we either find a match, or until the
capabilities from the DB sort higher (lexicographically) than the
searched-for capability.

The terminfo char array is 3.3K - more better than before.
This commit is contained in:
Daniel Eklöf 2022-01-13 13:37:44 +01:00
parent 7258e0b005
commit 5a032c4c6f
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
2 changed files with 75 additions and 46 deletions

64
dcs.c
View file

@ -66,19 +66,52 @@ err:
UNITTEST
{
/* Verify table is sorted */
for (size_t i = 1; i < ALEN(terminfo_capabilities); i++) {
xassert(strcmp(terminfo_capabilities[i - 1].name,
terminfo_capabilities[i].name ) <= 0);
const char *p = terminfo_capabilities;
size_t left = sizeof(terminfo_capabilities);
const char *last_cap = NULL;
while (left > 0) {
const char *cap = p;
const char *val = cap + strlen(cap) + 1;
size_t size = strlen(cap) + 1 + strlen(val) + 1;;
xassert(size <= left);
p += size;
left -= size;
if (last_cap != NULL)
xassert(strcmp(last_cap, cap) < 0);
last_cap = cap;
}
}
static int
terminfo_entry_compar(const void *_key, const void *_entry)
static bool
lookup_capability(const char *name, const char **value)
{
const char *key = _key;
const struct foot_terminfo_entry *entry = _entry;
const char *p = terminfo_capabilities;
size_t left = sizeof(terminfo_capabilities);
return strcmp(key, entry->name);
while (left > 0) {
const char *cap = p;
const char *val = cap + strlen(cap) + 1;
size_t size = strlen(cap) + 1 + strlen(val) + 1;;
xassert(size <= left);
p += size;
left -= size;
int r = strcmp(cap, name);
if (r == 0) {
*value = val;
return true;
} else if (r > 0)
break;
}
*value = NULL;
return false;
}
static void
@ -88,18 +121,23 @@ xtgettcap_reply(struct terminal *term, const char *hex_cap_name, size_t len)
if (name == NULL)
goto err;
#if 0
const struct foot_terminfo_entry *entry =
bsearch(name, terminfo_capabilities, ALEN(terminfo_capabilities),
sizeof(*entry), &terminfo_entry_compar);
#endif
const char *value;
bool valid_capability = lookup_capability(name, &value);
xassert(!valid_capability || value != NULL);
LOG_DBG("XTGETTCAP: cap=%s (%.*s), value=%s",
name, (int)len, hex_cap_name,
entry != NULL ? entry->value : "<invalid>");
valid_capability ? value : "<invalid>");
if (entry == NULL)
if (!valid_capability)
goto err;
if (entry->value == NULL) {
if (value[0] == '\0') {
/* Boolean */
term_to_slave(term, "\033P1+r", 5);
term_to_slave(term, hex_cap_name, len);
@ -116,13 +154,13 @@ xtgettcap_reply(struct terminal *term, const char *hex_cap_name, size_t len)
5 + /* DCS 1 + r (\EP1+r) */
len + /* capability name, hex encoded */
1 + /* = */
strlen(entry->value) * 2 + /* capability value, hex encoded */
strlen(value) * 2 + /* capability value, hex encoded */
2 + /* ST (\E\\) */
1);
int idx = sprintf(reply, "\033P1+r%.*s=", (int)len, hex_cap_name);
for (const char *c = entry->value; *c != '\0'; c++) {
for (const char *c = value; *c != '\0'; c++) {
uint8_t nib1 = (uint8_t)*c >> 4;
uint8_t nib2 = (uint8_t)*c & 0xf;

View file

@ -49,7 +49,19 @@ class IntCapability(Capability):
class StringCapability(Capability):
pass
def __init__(self, name: str, value: str):
# Expand \E to literal ESC in non-parameterized capabilities
if '%' not in value:
value = re.sub(r'\\E([0-7])', r'\\033" "\1', value)
value = re.sub(r'\\E', r'\\033', value)
else:
# Need to double-escape \E in C string literals
value = value.replace('\\E', '\\\\E')
# Dont escape :
value = value.replace('\\:', ':')
super().__init__(name, value)
class Fragment:
@ -156,48 +168,27 @@ def main():
entry.add_capability(StringCapability('TN', target_entry_name))
entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel
target.write('#pragma once\n')
target.write('\n')
# target.write('enum terminfo_capability_type {\n')
# target.write(' TERMINFO_CAP_BOOL,\n')
# target.write(' TERMINFO_CAP_INT,\n')
# target.write(' TERMINFO_CAP_STRING,\n')
# target.write('};\n')
# target.write('\n')
target.write('struct foot_terminfo_entry {\n')
target.write(' const char *name;\n')
# target.write(' enum terminfo_capability_type type;\n')
target.write(' const char *value;\n')
target.write('};\n')
target.write('\n')
target.write(
'static const struct foot_terminfo_entry terminfo_capabilities[] = {\n')
terminfo_parts = []
for cap in sorted(entry.caps.values()):
name = cap.name
value = str(cap.value)
# Expand \E to literal ESC in non-parameterized capabilities
if '%' not in value:
value = re.sub(r'\\E([0-7])', r'\\033" "\1', value)
value = re.sub(r'\\E', r'\\033', value)
else:
# Need to double-escape \E in C string literals
value = value.replace('\\E', '\\\\E')
# Dont escape :
value = value.replace('\\:', ':')
# Do escape ‘“‘
# Escape ‘“‘
name = name.replace('"', '\"')
value = value.replace('"', '\"')
terminfo_parts.append(name)
if isinstance(cap, BoolCapability):
target.write(f' {{"{name}", NULL}},\n')
terminfo_parts.append('')
else:
target.write(f' {{"{name}", "{value}"}},\n')
terminfo_parts.append(value)
target.write('};\n')
terminfo = '\\0" "'.join(terminfo_parts)
target.write('#pragma once\n')
target.write('\n')
target.write(f'static const char terminfo_capabilities[] = "{terminfo}";')
target.write('\n')
if __name__ == '__main__':