From 5a032c4c6f6517e664ac7b920c3cfae8d5a25b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 13 Jan 2022 13:37:44 +0100 Subject: [PATCH] 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. --- dcs.c | 64 ++++++++++++++++++++++------ scripts/generate-builtin-terminfo.py | 57 +++++++++++-------------- 2 files changed, 75 insertions(+), 46 deletions(-) diff --git a/dcs.c b/dcs.c index 07fec5c7..1a388863 100644 --- a/dcs.c +++ b/dcs.c @@ -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 : ""); + valid_capability ? value : ""); - 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; diff --git a/scripts/generate-builtin-terminfo.py b/scripts/generate-builtin-terminfo.py index 9c8807bf..c8d3be4b 100755 --- a/scripts/generate-builtin-terminfo.py +++ b/scripts/generate-builtin-terminfo.py @@ -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') + + # Don’t 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') - - # Don’t 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__':