diff options
Diffstat (limited to 'lib/python/qmk/c_parse.py')
| -rw-r--r-- | lib/python/qmk/c_parse.py | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py new file mode 100644 index 000000000..e41e271a4 --- /dev/null +++ b/lib/python/qmk/c_parse.py | |||
| @@ -0,0 +1,161 @@ | |||
| 1 | """Functions for working with config.h files. | ||
| 2 | """ | ||
| 3 | from pathlib import Path | ||
| 4 | |||
| 5 | from milc import cli | ||
| 6 | |||
| 7 | from qmk.comment_remover import comment_remover | ||
| 8 | |||
| 9 | default_key_entry = {'x': -1, 'y': 0, 'w': 1} | ||
| 10 | |||
| 11 | |||
| 12 | def c_source_files(dir_names): | ||
| 13 | """Returns a list of all *.c, *.h, and *.cpp files for a given list of directories | ||
| 14 | |||
| 15 | Args: | ||
| 16 | |||
| 17 | dir_names | ||
| 18 | List of directories relative to `qmk_firmware`. | ||
| 19 | """ | ||
| 20 | files = [] | ||
| 21 | for dir in dir_names: | ||
| 22 | files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp']) | ||
| 23 | return files | ||
| 24 | |||
| 25 | |||
| 26 | def find_layouts(file): | ||
| 27 | """Returns list of parsed LAYOUT preprocessor macros found in the supplied include file. | ||
| 28 | """ | ||
| 29 | file = Path(file) | ||
| 30 | aliases = {} # Populated with all `#define`s that aren't functions | ||
| 31 | parsed_layouts = {} | ||
| 32 | |||
| 33 | # Search the file for LAYOUT macros and aliases | ||
| 34 | file_contents = file.read_text() | ||
| 35 | file_contents = comment_remover(file_contents) | ||
| 36 | file_contents = file_contents.replace('\\\n', '') | ||
| 37 | |||
| 38 | for line in file_contents.split('\n'): | ||
| 39 | if line.startswith('#define') and '(' in line and 'LAYOUT' in line: | ||
| 40 | # We've found a LAYOUT macro | ||
| 41 | macro_name, layout, matrix = _parse_layout_macro(line.strip()) | ||
| 42 | |||
| 43 | # Reject bad macro names | ||
| 44 | if macro_name.startswith('LAYOUT_kc') or not macro_name.startswith('LAYOUT'): | ||
| 45 | continue | ||
| 46 | |||
| 47 | # Parse the matrix data | ||
| 48 | matrix_locations = _parse_matrix_locations(matrix, file, macro_name) | ||
| 49 | |||
| 50 | # Parse the layout entries into a basic structure | ||
| 51 | default_key_entry['x'] = -1 # Set to -1 so _default_key(key) will increment it to 0 | ||
| 52 | layout = layout.strip() | ||
| 53 | parsed_layout = [_default_key(key) for key in layout.split(',')] | ||
| 54 | |||
| 55 | for key in parsed_layout: | ||
| 56 | key['matrix'] = matrix_locations.get(key['label']) | ||
| 57 | |||
| 58 | parsed_layouts[macro_name] = { | ||
| 59 | 'key_count': len(parsed_layout), | ||
| 60 | 'layout': parsed_layout, | ||
| 61 | 'filename': str(file), | ||
| 62 | } | ||
| 63 | |||
| 64 | elif '#define' in line: | ||
| 65 | # Attempt to extract a new layout alias | ||
| 66 | try: | ||
| 67 | _, pp_macro_name, pp_macro_text = line.strip().split(' ', 2) | ||
| 68 | aliases[pp_macro_name] = pp_macro_text | ||
| 69 | except ValueError: | ||
| 70 | continue | ||
| 71 | |||
| 72 | # Populate our aliases | ||
| 73 | for alias, text in aliases.items(): | ||
| 74 | if text in parsed_layouts and 'KEYMAP' not in alias: | ||
| 75 | parsed_layouts[alias] = parsed_layouts[text] | ||
| 76 | |||
| 77 | return parsed_layouts | ||
| 78 | |||
| 79 | |||
| 80 | def parse_config_h_file(config_h_file, config_h=None): | ||
| 81 | """Extract defines from a config.h file. | ||
| 82 | """ | ||
| 83 | if not config_h: | ||
| 84 | config_h = {} | ||
| 85 | |||
| 86 | config_h_file = Path(config_h_file) | ||
| 87 | |||
| 88 | if config_h_file.exists(): | ||
| 89 | config_h_text = config_h_file.read_text() | ||
| 90 | config_h_text = config_h_text.replace('\\\n', '') | ||
| 91 | |||
| 92 | for linenum, line in enumerate(config_h_text.split('\n')): | ||
| 93 | line = line.strip() | ||
| 94 | |||
| 95 | if '//' in line: | ||
| 96 | line = line[:line.index('//')].strip() | ||
| 97 | |||
| 98 | if not line: | ||
| 99 | continue | ||
| 100 | |||
| 101 | line = line.split() | ||
| 102 | |||
| 103 | if line[0] == '#define': | ||
| 104 | if len(line) == 1: | ||
| 105 | cli.log.error('%s: Incomplete #define! On or around line %s' % (config_h_file, linenum)) | ||
| 106 | elif len(line) == 2: | ||
| 107 | config_h[line[1]] = True | ||
| 108 | else: | ||
| 109 | config_h[line[1]] = ' '.join(line[2:]) | ||
| 110 | |||
| 111 | elif line[0] == '#undef': | ||
| 112 | if len(line) == 2: | ||
| 113 | if line[1] in config_h: | ||
| 114 | if config_h[line[1]] is True: | ||
| 115 | del config_h[line[1]] | ||
| 116 | else: | ||
| 117 | config_h[line[1]] = False | ||
| 118 | else: | ||
| 119 | cli.log.error('%s: Incomplete #undef! On or around line %s' % (config_h_file, linenum)) | ||
| 120 | |||
| 121 | return config_h | ||
| 122 | |||
| 123 | |||
| 124 | def _default_key(label=None): | ||
| 125 | """Increment x and return a copy of the default_key_entry. | ||
| 126 | """ | ||
| 127 | default_key_entry['x'] += 1 | ||
| 128 | new_key = default_key_entry.copy() | ||
| 129 | |||
| 130 | if label: | ||
| 131 | new_key['label'] = label | ||
| 132 | |||
| 133 | return new_key | ||
| 134 | |||
| 135 | |||
| 136 | def _parse_layout_macro(layout_macro): | ||
| 137 | """Split the LAYOUT macro into its constituent parts | ||
| 138 | """ | ||
| 139 | layout_macro = layout_macro.replace('\\', '').replace(' ', '').replace('\t', '').replace('#define', '') | ||
| 140 | macro_name, layout = layout_macro.split('(', 1) | ||
| 141 | layout, matrix = layout.split(')', 1) | ||
| 142 | |||
| 143 | return macro_name, layout, matrix | ||
| 144 | |||
| 145 | |||
| 146 | def _parse_matrix_locations(matrix, file, macro_name): | ||
| 147 | """Parse raw matrix data into a dictionary keyed by the LAYOUT identifier. | ||
| 148 | """ | ||
| 149 | matrix_locations = {} | ||
| 150 | |||
| 151 | for row_num, row in enumerate(matrix.split('},{')): | ||
| 152 | if row.startswith('LAYOUT'): | ||
| 153 | cli.log.error('%s: %s: Nested layout macro detected. Matrix data not available!', file, macro_name) | ||
| 154 | break | ||
| 155 | |||
| 156 | row = row.replace('{', '').replace('}', '') | ||
| 157 | for col_num, identifier in enumerate(row.split(',')): | ||
| 158 | if identifier != 'KC_NO': | ||
| 159 | matrix_locations[identifier] = (row_num, col_num) | ||
| 160 | |||
| 161 | return matrix_locations | ||
