c_parse.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. """Functions for working with config.h files.
  2. """
  3. from pathlib import Path
  4. import re
  5. from milc import cli
  6. from qmk.comment_remover import comment_remover
  7. default_key_entry = {'x': -1, 'y': 0, 'w': 1}
  8. single_comment_regex = re.compile(r' */[/*].*$')
  9. multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)
  10. def strip_line_comment(string):
  11. """Removes comments from a single line string.
  12. """
  13. return single_comment_regex.sub('', string)
  14. def strip_multiline_comment(string):
  15. """Removes comments from a single line string.
  16. """
  17. return multi_comment_regex.sub('', string)
  18. def c_source_files(dir_names):
  19. """Returns a list of all *.c, *.h, and *.cpp files for a given list of directories
  20. Args:
  21. dir_names
  22. List of directories relative to `qmk_firmware`.
  23. """
  24. files = []
  25. for dir in dir_names:
  26. files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp'])
  27. return files
  28. def find_layouts(file):
  29. """Returns list of parsed LAYOUT preprocessor macros found in the supplied include file.
  30. """
  31. file = Path(file)
  32. aliases = {} # Populated with all `#define`s that aren't functions
  33. parsed_layouts = {}
  34. # Search the file for LAYOUT macros and aliases
  35. file_contents = file.read_text()
  36. file_contents = comment_remover(file_contents)
  37. file_contents = file_contents.replace('\\\n', '')
  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. # Reject bad macro names
  43. if macro_name.startswith('LAYOUT_kc') or not macro_name.startswith('LAYOUT'):
  44. continue
  45. # Parse the matrix data
  46. matrix_locations = _parse_matrix_locations(matrix, file, macro_name)
  47. # Parse the layout entries into a basic structure
  48. default_key_entry['x'] = -1 # Set to -1 so _default_key(key) will increment it to 0
  49. layout = layout.strip()
  50. parsed_layout = [_default_key(key) for key in layout.split(',')]
  51. for key in parsed_layout:
  52. if key['label'] in matrix_locations:
  53. key['matrix'] = matrix_locations[key['label']]
  54. parsed_layouts[macro_name] = {
  55. 'key_count': len(parsed_layout),
  56. 'layout': parsed_layout,
  57. 'filename': str(file),
  58. }
  59. elif '#define' in line:
  60. # Attempt to extract a new layout alias
  61. try:
  62. _, pp_macro_name, pp_macro_text = line.strip().split(' ', 2)
  63. aliases[pp_macro_name] = pp_macro_text
  64. except ValueError:
  65. continue
  66. # Populate our aliases
  67. for alias, text in aliases.items():
  68. if text in parsed_layouts and 'KEYMAP' not in alias:
  69. parsed_layouts[alias] = parsed_layouts[text]
  70. return parsed_layouts
  71. def parse_config_h_file(config_h_file, config_h=None):
  72. """Extract defines from a config.h file.
  73. """
  74. if not config_h:
  75. config_h = {}
  76. config_h_file = Path(config_h_file)
  77. if config_h_file.exists():
  78. config_h_text = config_h_file.read_text()
  79. config_h_text = config_h_text.replace('\\\n', '')
  80. config_h_text = strip_multiline_comment(config_h_text)
  81. for linenum, line in enumerate(config_h_text.split('\n')):
  82. line = strip_line_comment(line).strip()
  83. if not line:
  84. continue
  85. line = line.split()
  86. if line[0] == '#define':
  87. if len(line) == 1:
  88. cli.log.error('%s: Incomplete #define! On or around line %s' % (config_h_file, linenum))
  89. elif len(line) == 2:
  90. config_h[line[1]] = True
  91. else:
  92. config_h[line[1]] = ' '.join(line[2:])
  93. elif line[0] == '#undef':
  94. if len(line) == 2:
  95. if line[1] in config_h:
  96. if config_h[line[1]] is True:
  97. del config_h[line[1]]
  98. else:
  99. config_h[line[1]] = False
  100. else:
  101. cli.log.error('%s: Incomplete #undef! On or around line %s' % (config_h_file, linenum))
  102. return config_h
  103. def _default_key(label=None):
  104. """Increment x and return a copy of the default_key_entry.
  105. """
  106. default_key_entry['x'] += 1
  107. new_key = default_key_entry.copy()
  108. if label:
  109. new_key['label'] = label
  110. return new_key
  111. def _parse_layout_macro(layout_macro):
  112. """Split the LAYOUT macro into its constituent parts
  113. """
  114. layout_macro = layout_macro.replace('\\', '').replace(' ', '').replace('\t', '').replace('#define', '')
  115. macro_name, layout = layout_macro.split('(', 1)
  116. layout, matrix = layout.split(')', 1)
  117. return macro_name, layout, matrix
  118. def _parse_matrix_locations(matrix, file, macro_name):
  119. """Parse raw matrix data into a dictionary keyed by the LAYOUT identifier.
  120. """
  121. matrix_locations = {}
  122. for row_num, row in enumerate(matrix.split('},{')):
  123. if row.startswith('LAYOUT'):
  124. cli.log.error('%s: %s: Nested layout macro detected. Matrix data not available!', file, macro_name)
  125. break
  126. row = row.replace('{', '').replace('}', '')
  127. for col_num, identifier in enumerate(row.split(',')):
  128. if identifier != 'KC_NO':
  129. matrix_locations[identifier] = [row_num, col_num]
  130. return matrix_locations