c_parse.py 5.8 KB

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