123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- #!/usr/bin/env python
- # License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
- import os
- import subprocess
- import sys
- from collections import defaultdict
- from typing import Any, DefaultDict, Union
- if __name__ == '__main__' and not __package__:
- import __main__
- __main__.__package__ = 'gen'
- sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
- KeymapType = dict[str, tuple[str, Union[frozenset[str], str]]]
- def resolve_keys(keymap: KeymapType) -> DefaultDict[str, list[str]]:
- ans: DefaultDict[str, list[str]] = defaultdict(list)
- for ch, (attr, atype) in keymap.items():
- if isinstance(atype, str) and atype in ('int', 'uint'):
- q = atype
- else:
- q = 'flag'
- ans[q].append(ch)
- return ans
- def enum(keymap: KeymapType) -> str:
- lines = []
- for ch, (attr, atype) in keymap.items():
- lines.append(f"{attr}='{ch}'")
- return '''
- enum KEYS {{
- {}
- }};
- '''.format(',\n'.join(lines))
- def parse_key(keymap: KeymapType) -> str:
- lines = []
- for attr, atype in keymap.values():
- vs = atype.upper() if isinstance(atype, str) and atype in ('uint', 'int') else 'FLAG'
- lines.append(f'case {attr}: value_state = {vs}; break;')
- return ' \n'.join(lines)
- def parse_flag(keymap: KeymapType, type_map: dict[str, Any], command_class: str) -> str:
- lines = []
- for ch in type_map['flag']:
- attr, allowed_values = keymap[ch]
- q = ' && '.join(f"g.{attr} != '{x}'" for x in sorted(allowed_values))
- lines.append(f'''
- case {attr}: {{
- g.{attr} = parser_buf[pos++];
- if ({q}) {{
- REPORT_ERROR("Malformed {command_class} control block, unknown flag value for {attr}: 0x%x", g.{attr});
- return;
- }};
- }}
- break;
- ''')
- return ' \n'.join(lines)
- def parse_number(keymap: KeymapType) -> tuple[str, str]:
- int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int']
- uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint']
- return '; '.join(int_keys), '; '.join(uint_keys)
- def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any], payload_allowed: bool) -> str:
- def group(atype: str, conv: str) -> tuple[str, str]:
- flag_fmt, flag_attrs = [], []
- cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
- for ch in type_map[atype]:
- flag_fmt.append(f's{cv}')
- attr = keymap[ch][0]
- flag_attrs.append(f'"{attr}", {conv}g.{attr}')
- return ' '.join(flag_fmt), ', '.join(flag_attrs)
- flag_fmt, flag_attrs = group('flag', '')
- int_fmt, int_attrs = group('int', '(int)')
- uint_fmt, uint_attrs = group('uint', '(unsigned int)')
- fmt = f'{flag_fmt} {uint_fmt} {int_fmt}'
- if payload_allowed:
- ans = [f'REPORT_VA_COMMAND("K s {{{fmt} sI}} y#", self->window_id, "{report_name}", ']
- else:
- ans = [f'REPORT_VA_COMMAND("K s {{{fmt}}}", self->window_id, "{report_name}", ']
- ans.append(',\n '.join((flag_attrs, uint_attrs, int_attrs)))
- if payload_allowed:
- ans.append(', "payload_sz", g.payload_sz, parser_buf, g.payload_sz')
- ans.append(');')
- return '\n'.join(ans)
- def generate(
- function_name: str,
- callback_name: str,
- report_name: str,
- keymap: KeymapType,
- command_class: str,
- initial_key: str = 'a',
- payload_allowed: bool = True
- ) -> str:
- type_map = resolve_keys(keymap)
- keys_enum = enum(keymap)
- handle_key = parse_key(keymap)
- flag_keys = parse_flag(keymap, type_map, command_class)
- int_keys, uint_keys = parse_number(keymap)
- report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed)
- if payload_allowed:
- payload_after_value = "case ';': state = PAYLOAD; break;"
- payload = ', PAYLOAD'
- payload_case = f'''
- case PAYLOAD: {{
- sz = parser_buf_pos - pos;
- g.payload_sz = MAX(BUF_EXTRA, sz);
- if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) {{
- g.payload_sz = MAX(BUF_EXTRA, sz);
- REPORT_ERROR("Failed to parse {command_class} command payload with error: \
- invalid base64 data in chunk of size: %zu with output buffer size: %zu", sz, g.payload_sz); return; }}
- pos = parser_buf_pos;
- }}
- break;
- '''
- callback = f'{callback_name}(self->screen, &g, parser_buf)'
- else:
- payload_after_value = payload = payload_case = ''
- callback = f'{callback_name}(self->screen, &g)'
- return f'''
- #include "base64.h"
- static inline void
- {function_name}(PS *self, uint8_t *parser_buf, const size_t parser_buf_pos) {{
- unsigned int pos = 1;
- enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }};
- enum PARSER_STATES state = KEY, value_state = FLAG;
- static {command_class} g;
- unsigned int i, code;
- uint64_t lcode; int64_t accumulator;
- bool is_negative;
- memset(&g, 0, sizeof(g));
- size_t sz;
- {keys_enum}
- enum KEYS key = '{initial_key}';
- if (parser_buf[pos] == ';') state = AFTER_VALUE;
- while (pos < parser_buf_pos) {{
- switch(state) {{
- case KEY:
- key = parser_buf[pos++];
- state = EQUAL;
- switch(key) {{
- {handle_key}
- default:
- REPORT_ERROR("Malformed {command_class} control block, invalid key character: 0x%x", key);
- return;
- }}
- break;
- case EQUAL:
- if (parser_buf[pos++] != '=') {{
- REPORT_ERROR("Malformed {command_class} control block, no = after key, found: 0x%x instead", parser_buf[pos-1]);
- return;
- }}
- state = value_state;
- break;
- case FLAG:
- switch(key) {{
- {flag_keys}
- default:
- break;
- }}
- state = AFTER_VALUE;
- break;
- case INT:
- #define READ_UINT \\
- for (i = pos, accumulator=0; i < MIN(parser_buf_pos, pos + 10); i++) {{ \\
- int64_t n = parser_buf[i] - '0'; if (n < 0 || n > 9) break; \\
- accumulator += n * digit_multipliers[i - pos]; \\
- }} \\
- if (i == pos) {{ REPORT_ERROR("Malformed {command_class} control block, expecting an integer value for key: %c", key & 0xFF); return; }} \\
- lcode = accumulator / digit_multipliers[i - pos - 1]; pos = i; \\
- if (lcode > UINT32_MAX) {{ REPORT_ERROR("Malformed {command_class} control block, number is too large"); return; }} \\
- code = lcode;
- is_negative = false;
- if(parser_buf[pos] == '-') {{ is_negative = true; pos++; }}
- #define I(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break
- READ_UINT;
- switch(key) {{
- {int_keys};
- default: break;
- }}
- state = AFTER_VALUE;
- break;
- #undef I
- case UINT:
- READ_UINT;
- #define U(x) case x: g.x = code; break
- switch(key) {{
- {uint_keys};
- default: break;
- }}
- state = AFTER_VALUE;
- break;
- #undef U
- #undef READ_UINT
- case AFTER_VALUE:
- switch (parser_buf[pos++]) {{
- default:
- REPORT_ERROR("Malformed {command_class} control block, expecting a comma or semi-colon after a value, found: 0x%x",
- parser_buf[pos - 1]);
- return;
- case ',':
- state = KEY;
- break;
- {payload_after_value}
- }}
- break;
- {payload_case}
- }} // end switch
- }} // end while
- switch(state) {{
- case EQUAL:
- REPORT_ERROR("Malformed {command_class} control block, no = after key"); return;
- case INT:
- case UINT:
- REPORT_ERROR("Malformed {command_class} control block, expecting an integer value"); return;
- case FLAG:
- REPORT_ERROR("Malformed {command_class} control block, expecting a flag value"); return;
- default:
- break;
- }}
- {report_cmd}
- {callback};
- }}
- '''
- def write_header(text: str, path: str) -> None:
- with open(path, 'w') as f:
- print(f'// This file is generated by {os.path.basename(__file__)} do not edit!', file=f, end='\n\n')
- print('#pragma once', file=f)
- print(text, file=f)
- subprocess.check_call(['clang-format', '-i', path])
- def graphics_parser() -> None:
- flag = frozenset
- keymap: KeymapType = {
- 'a': ('action', flag('tTqpdfac')),
- 'd': ('delete_action', flag('aAiIcCfFnNpPqQrRxXyYzZ')),
- 't': ('transmission_type', flag('dfts')),
- 'o': ('compressed', flag('z')),
- 'f': ('format', 'uint'),
- 'm': ('more', 'uint'),
- 'i': ('id', 'uint'),
- 'I': ('image_number', 'uint'),
- 'p': ('placement_id', 'uint'),
- 'q': ('quiet', 'uint'),
- 'w': ('width', 'uint'),
- 'h': ('height', 'uint'),
- 'x': ('x_offset', 'uint'),
- 'y': ('y_offset', 'uint'),
- 'v': ('data_height', 'uint'),
- 's': ('data_width', 'uint'),
- 'S': ('data_sz', 'uint'),
- 'O': ('data_offset', 'uint'),
- 'c': ('num_cells', 'uint'),
- 'r': ('num_lines', 'uint'),
- 'X': ('cell_x_offset', 'uint'),
- 'Y': ('cell_y_offset', 'uint'),
- 'z': ('z_index', 'int'),
- 'C': ('cursor_movement', 'uint'),
- 'U': ('unicode_placement', 'uint'),
- 'P': ('parent_id', 'uint'),
- 'Q': ('parent_placement_id', 'uint'),
- 'H': ('offset_from_parent_x', 'int'),
- 'V': ('offset_from_parent_y', 'int'),
- }
- text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
- write_header(text, 'kitty/parse-graphics-command.h')
- def main(args: list[str]=sys.argv) -> None:
- graphics_parser()
- if __name__ == '__main__':
- import runpy
- m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
- m['main']([sys.executable, 'apc-parsers'])
|