apc_parsers.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. #!/usr/bin/env python
  2. # License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
  3. import os
  4. import subprocess
  5. import sys
  6. from collections import defaultdict
  7. from typing import Any, DefaultDict, Union
  8. if __name__ == '__main__' and not __package__:
  9. import __main__
  10. __main__.__package__ = 'gen'
  11. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  12. KeymapType = dict[str, tuple[str, Union[frozenset[str], str]]]
  13. def resolve_keys(keymap: KeymapType) -> DefaultDict[str, list[str]]:
  14. ans: DefaultDict[str, list[str]] = defaultdict(list)
  15. for ch, (attr, atype) in keymap.items():
  16. if isinstance(atype, str) and atype in ('int', 'uint'):
  17. q = atype
  18. else:
  19. q = 'flag'
  20. ans[q].append(ch)
  21. return ans
  22. def enum(keymap: KeymapType) -> str:
  23. lines = []
  24. for ch, (attr, atype) in keymap.items():
  25. lines.append(f"{attr}='{ch}'")
  26. return '''
  27. enum KEYS {{
  28. {}
  29. }};
  30. '''.format(',\n'.join(lines))
  31. def parse_key(keymap: KeymapType) -> str:
  32. lines = []
  33. for attr, atype in keymap.values():
  34. vs = atype.upper() if isinstance(atype, str) and atype in ('uint', 'int') else 'FLAG'
  35. lines.append(f'case {attr}: value_state = {vs}; break;')
  36. return ' \n'.join(lines)
  37. def parse_flag(keymap: KeymapType, type_map: dict[str, Any], command_class: str) -> str:
  38. lines = []
  39. for ch in type_map['flag']:
  40. attr, allowed_values = keymap[ch]
  41. q = ' && '.join(f"g.{attr} != '{x}'" for x in sorted(allowed_values))
  42. lines.append(f'''
  43. case {attr}: {{
  44. g.{attr} = parser_buf[pos++];
  45. if ({q}) {{
  46. REPORT_ERROR("Malformed {command_class} control block, unknown flag value for {attr}: 0x%x", g.{attr});
  47. return;
  48. }};
  49. }}
  50. break;
  51. ''')
  52. return ' \n'.join(lines)
  53. def parse_number(keymap: KeymapType) -> tuple[str, str]:
  54. int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int']
  55. uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint']
  56. return '; '.join(int_keys), '; '.join(uint_keys)
  57. def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any], payload_allowed: bool, payload_is_base64: bool) -> str:
  58. def group(atype: str, conv: str) -> tuple[str, str]:
  59. flag_fmt, flag_attrs = [], []
  60. cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
  61. for ch in type_map[atype]:
  62. flag_fmt.append(f's{cv}')
  63. attr = keymap[ch][0]
  64. flag_attrs.append(f'"{attr}", {conv}g.{attr}')
  65. return ' '.join(flag_fmt), ', '.join(flag_attrs)
  66. flag_fmt, flag_attrs = group('flag', '')
  67. int_fmt, int_attrs = group('int', '(int)')
  68. uint_fmt, uint_attrs = group('uint', '(unsigned int)')
  69. fmt = f'{flag_fmt} {uint_fmt} {int_fmt}'
  70. if payload_allowed:
  71. ans = [f'REPORT_VA_COMMAND("K s {{{fmt} ss#}}", self->window_id, "{report_name}",\n']
  72. else:
  73. ans = [f'REPORT_VA_COMMAND("K s {{{fmt}}}", self->window_id, "{report_name}",\n']
  74. if flag_attrs:
  75. ans.append(f'{flag_attrs},\n')
  76. if uint_attrs:
  77. ans.append(f'{uint_attrs},\n')
  78. if int_attrs:
  79. ans.append(f'{int_attrs},\n')
  80. if payload_allowed:
  81. if payload_is_base64:
  82. ans.append('"", (char*)parser_buf, g.payload_sz')
  83. else:
  84. ans.append('"", (char*)parser_buf + payload_start, g.payload_sz')
  85. ans.append(');')
  86. return '\n'.join(ans)
  87. def generate(
  88. function_name: str,
  89. callback_name: str,
  90. report_name: str,
  91. keymap: KeymapType,
  92. command_class: str,
  93. initial_key: str = 'a',
  94. payload_allowed: bool = True,
  95. payload_is_base64: bool = True,
  96. start_parsing_at: int = 1,
  97. field_sep: str = ',',
  98. ) -> str:
  99. type_map = resolve_keys(keymap)
  100. keys_enum = enum(keymap)
  101. handle_key = parse_key(keymap)
  102. flag_keys = parse_flag(keymap, type_map, command_class)
  103. int_keys, uint_keys = parse_number(keymap)
  104. report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed, payload_is_base64)
  105. extra_init = ''
  106. if payload_allowed:
  107. payload_after_value = "case ';': state = PAYLOAD; break;"
  108. payload = ', PAYLOAD'
  109. if payload_is_base64:
  110. payload_case = f'''
  111. case PAYLOAD: {{
  112. sz = parser_buf_pos - pos;
  113. g.payload_sz = MAX(BUF_EXTRA, sz);
  114. if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) {{
  115. g.payload_sz = MAX(BUF_EXTRA, sz);
  116. REPORT_ERROR("Failed to parse {command_class} command payload with error: \
  117. invalid base64 data in chunk of size: %zu with output buffer size: %zu", sz, g.payload_sz); return; }}
  118. pos = parser_buf_pos;
  119. }} break;
  120. '''
  121. callback = f'{callback_name}(self->screen, &g, parser_buf)'
  122. else:
  123. payload_case = '''
  124. case PAYLOAD: {
  125. sz = parser_buf_pos - pos;
  126. payload_start = pos;
  127. g.payload_sz = sz;
  128. pos = parser_buf_pos;
  129. } break;
  130. '''
  131. extra_init = 'size_t payload_start = 0;'
  132. callback = f'{callback_name}(self->screen, &g, parser_buf + payload_start)'
  133. else:
  134. payload_after_value = payload = payload_case = ''
  135. callback = f'{callback_name}(self->screen, &g)'
  136. return f'''
  137. #include "base64.h"
  138. static inline void
  139. {function_name}(PS *self, uint8_t *parser_buf, const size_t parser_buf_pos) {{
  140. unsigned int pos = {start_parsing_at};
  141. {extra_init}
  142. enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }};
  143. enum PARSER_STATES state = KEY, value_state = FLAG;
  144. {command_class} g = {{0}};
  145. unsigned int i, code;
  146. uint64_t lcode; int64_t accumulator;
  147. bool is_negative; (void)is_negative;
  148. size_t sz;
  149. {keys_enum}
  150. enum KEYS key = '{initial_key}';
  151. if (parser_buf[pos] == ';') state = AFTER_VALUE;
  152. while (pos < parser_buf_pos) {{
  153. switch(state) {{
  154. case KEY:
  155. key = parser_buf[pos++];
  156. state = EQUAL;
  157. switch(key) {{
  158. {handle_key}
  159. default:
  160. REPORT_ERROR("Malformed {command_class} control block, invalid key character: 0x%x", key);
  161. return;
  162. }}
  163. break;
  164. case EQUAL:
  165. if (parser_buf[pos++] != '=') {{
  166. REPORT_ERROR("Malformed {command_class} control block, no = after key, found: 0x%x instead", parser_buf[pos-1]);
  167. return;
  168. }}
  169. state = value_state;
  170. break;
  171. case FLAG:
  172. switch(key) {{
  173. {flag_keys}
  174. default:
  175. break;
  176. }}
  177. state = AFTER_VALUE;
  178. break;
  179. case INT:
  180. #define READ_UINT \\
  181. for (i = pos, accumulator=0; i < MIN(parser_buf_pos, pos + 10); i++) {{ \\
  182. int64_t n = parser_buf[i] - '0'; if (n < 0 || n > 9) break; \\
  183. accumulator += n * digit_multipliers[i - pos]; \\
  184. }} \\
  185. if (i == pos) {{ REPORT_ERROR("Malformed {command_class} control block, expecting an integer value for key: %c", key & 0xFF); return; }} \\
  186. lcode = accumulator / digit_multipliers[i - pos - 1]; pos = i; \\
  187. if (lcode > UINT32_MAX) {{ REPORT_ERROR("Malformed {command_class} control block, number is too large"); return; }} \\
  188. code = lcode;
  189. is_negative = false;
  190. if(parser_buf[pos] == '-') {{ is_negative = true; pos++; }}
  191. #define I(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break
  192. READ_UINT;
  193. switch(key) {{
  194. {int_keys};
  195. default: break;
  196. }}
  197. state = AFTER_VALUE;
  198. break;
  199. #undef I
  200. case UINT:
  201. READ_UINT;
  202. #define U(x) case x: g.x = code; break
  203. switch(key) {{
  204. {uint_keys};
  205. default: break;
  206. }}
  207. state = AFTER_VALUE;
  208. break;
  209. #undef U
  210. #undef READ_UINT
  211. case AFTER_VALUE:
  212. switch (parser_buf[pos++]) {{
  213. default:
  214. REPORT_ERROR("Malformed {command_class} control block, expecting a {field_sep} or semi-colon after a value, found: 0x%x",
  215. parser_buf[pos - 1]);
  216. return;
  217. case '{field_sep}':
  218. state = KEY;
  219. break;
  220. {payload_after_value}
  221. }}
  222. break;
  223. {payload_case}
  224. }} // end switch
  225. }} // end while
  226. switch(state) {{
  227. case EQUAL:
  228. REPORT_ERROR("Malformed {command_class} control block, no = after key"); return;
  229. case INT:
  230. case UINT:
  231. REPORT_ERROR("Malformed {command_class} control block, expecting an integer value"); return;
  232. case FLAG:
  233. REPORT_ERROR("Malformed {command_class} control block, expecting a flag value"); return;
  234. default:
  235. break;
  236. }}
  237. {report_cmd}
  238. {callback};
  239. }}
  240. '''
  241. def write_header(text: str, path: str) -> None:
  242. with open(path, 'w') as f:
  243. print(f'// This file is generated by {os.path.basename(__file__)} do not edit!', file=f, end='\n\n')
  244. print('#pragma once', file=f)
  245. print(text, file=f)
  246. subprocess.check_call(['clang-format', '-i', path])
  247. def parsers() -> None:
  248. flag = frozenset
  249. keymap: KeymapType = {
  250. 'a': ('action', flag('tTqpdfac')),
  251. 'd': ('delete_action', flag('aAiIcCfFnNpPqQrRxXyYzZ')),
  252. 't': ('transmission_type', flag('dfts')),
  253. 'o': ('compressed', flag('z')),
  254. 'f': ('format', 'uint'),
  255. 'm': ('more', 'uint'),
  256. 'i': ('id', 'uint'),
  257. 'I': ('image_number', 'uint'),
  258. 'p': ('placement_id', 'uint'),
  259. 'q': ('quiet', 'uint'),
  260. 'w': ('width', 'uint'),
  261. 'h': ('height', 'uint'),
  262. 'x': ('x_offset', 'uint'),
  263. 'y': ('y_offset', 'uint'),
  264. 'v': ('data_height', 'uint'),
  265. 's': ('data_width', 'uint'),
  266. 'S': ('data_sz', 'uint'),
  267. 'O': ('data_offset', 'uint'),
  268. 'c': ('num_cells', 'uint'),
  269. 'r': ('num_lines', 'uint'),
  270. 'X': ('cell_x_offset', 'uint'),
  271. 'Y': ('cell_y_offset', 'uint'),
  272. 'z': ('z_index', 'int'),
  273. 'C': ('cursor_movement', 'uint'),
  274. 'U': ('unicode_placement', 'uint'),
  275. 'P': ('parent_id', 'uint'),
  276. 'Q': ('parent_placement_id', 'uint'),
  277. 'H': ('offset_from_parent_x', 'int'),
  278. 'V': ('offset_from_parent_y', 'int'),
  279. }
  280. text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
  281. write_header(text, 'kitty/parse-graphics-command.h')
  282. keymap = {
  283. 'w': ('width', 'uint'),
  284. 's': ('scale', 'uint'),
  285. 'n': ('subscale_n', 'uint'),
  286. 'd': ('subscale_d', 'uint'),
  287. 'v': ('vertical_align', 'uint'),
  288. }
  289. text = generate(
  290. 'parse_multicell_code', 'screen_handle_multicell_command', 'multicell_command', keymap, 'MultiCellCommand',
  291. payload_is_base64=False, start_parsing_at=0, field_sep=':')
  292. write_header(text, 'kitty/parse-multicell-command.h')
  293. def main(args: list[str]=sys.argv) -> None:
  294. parsers()
  295. if __name__ == '__main__':
  296. import runpy
  297. m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
  298. m['main']([sys.executable, 'apc-parsers'])