aboutsummaryrefslogtreecommitdiff
path: root/lib/python/qmk/c_parse.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python/qmk/c_parse.py')
-rw-r--r--lib/python/qmk/c_parse.py161
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"""
3from pathlib import Path
4
5from milc import cli
6
7from qmk.comment_remover import comment_remover
8
9default_key_entry = {'x': -1, 'y': 0, 'w': 1}
10
11
12def 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
26def 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
80def 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
124def _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
136def _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
146def _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