pinsformat.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. #!/usr/bin/env python3
  2. #
  3. # Formatter script for pins_MYPINS.h files
  4. #
  5. # Usage: pinsformat.py [infile] [outfile]
  6. #
  7. # With no parameters convert STDIN to STDOUT
  8. #
  9. import sys, re
  10. do_log = False
  11. def logmsg(msg, line):
  12. if do_log: print(msg, line)
  13. col_comment = 50
  14. # String lpad / rpad
  15. def lpad(astr, fill, c=' '):
  16. if not fill: return astr
  17. need = fill - len(astr)
  18. return astr if need <= 0 else (need * c) + astr
  19. def rpad(astr, fill, c=' '):
  20. if not fill: return astr
  21. need = fill - len(astr)
  22. return astr if need <= 0 else astr + (need * c)
  23. # Concatenate a string, adding a space if necessary
  24. # to avoid merging two words
  25. def concat_with_space(s1, s2):
  26. if not s1.endswith(' ') and not s2.startswith(' '):
  27. s1 += ' '
  28. return s1 + s2
  29. # Pin patterns
  30. mpatt = [ r'-?\d{1,3}', r'P[A-I]\d+', r'P\d_\d+', r'Pin[A-Z]\d\b' ]
  31. mstr = '|'.join(mpatt)
  32. mexpr = [ re.compile(f'^{m}$') for m in mpatt ]
  33. # Corrsponding padding for each pattern
  34. ppad = [ 3, 4, 5, 5 ]
  35. # Match a define line
  36. definePinPatt = re.compile(rf'^\s*(//)?#define\s+[A-Z_][A-Z0-9_]+?_PIN\s+({mstr})\s*(//.*)?$')
  37. def format_pins(argv):
  38. src_file = 'stdin'
  39. dst_file = None
  40. scnt = 0
  41. for arg in argv:
  42. if arg == '-v':
  43. global do_log
  44. do_log = True
  45. elif scnt == 0:
  46. # Get a source file if specified. Default destination is the same file
  47. src_file = dst_file = arg
  48. scnt += 1
  49. elif scnt == 1:
  50. # Get destination file if specified
  51. dst_file = arg
  52. scnt += 1
  53. # No text to process yet
  54. file_text = ''
  55. if src_file == 'stdin':
  56. # If no source file specified read from STDIN
  57. file_text = sys.stdin.read()
  58. else:
  59. # Open and read the file src_file
  60. with open(src_file, 'r') as rf: file_text = rf.read()
  61. if len(file_text) == 0:
  62. print('No text to process')
  63. return
  64. # Read from file or STDIN until it terminates
  65. filtered = process_text(file_text)
  66. if dst_file:
  67. with open(dst_file, 'w') as wf: wf.write(filtered)
  68. else:
  69. print(filtered)
  70. # Find the pin pattern so non-pin defines can be skipped
  71. def get_pin_pattern(txt):
  72. r = ''
  73. m = 0
  74. match_count = [ 0, 0, 0, 0 ]
  75. # Find the most common matching pattern
  76. match_threshold = 5
  77. for line in txt.split('\n'):
  78. r = definePinPatt.match(line)
  79. if r == None: continue
  80. ind = -1
  81. for p in mexpr:
  82. ind += 1
  83. if not p.match(r[2]): continue
  84. match_count[ind] += 1
  85. if match_count[ind] >= match_threshold:
  86. return { 'match': mpatt[ind], 'pad':ppad[ind] }
  87. return None
  88. def process_text(txt):
  89. if len(txt) == 0: return '(no text)'
  90. patt = get_pin_pattern(txt)
  91. if patt == None: return txt
  92. pmatch = patt['match']
  93. pindefPatt = re.compile(rf'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+({pmatch})\s*(//.*)?$')
  94. noPinPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+(-1)\s*(//.*)?$')
  95. skipPatt1 = re.compile(r'^(\s*(//)?#define)\s+(AT90USB|USBCON|(BOARD|DAC|FLASH|HAS|IS|USE)_.+|.+_(ADDRESS|AVAILABLE|BAUDRATE|CLOCK|CONNECTION|DEFAULT|ERROR|EXTRUDERS|FREQ|ITEM|MKS_BASE_VERSION|MODULE|NAME|ONLY|ORIENTATION|PERIOD|RANGE|RATE|READ_RETRIES|SERIAL|SIZE|SPI|STATE|STEP|TIMER|VERSION))\s+(.+)\s*(//.*)?$')
  96. skipPatt2 = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+(0x[0-9A-Fa-f]+|\d+|.+[a-z].+)\s*(//.*)?$')
  97. skipPatt3 = re.compile(r'^\s*#e(lse|ndif)\b.*$')
  98. aliasPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+([A-Z_][A-Z0-9_()]+)\s*(//.*)?$')
  99. switchPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s*(//.*)?$')
  100. undefPatt = re.compile(r'^(\s*(//)?#undef)\s+([A-Z_][A-Z0-9_]+)\s*(//.*)?$')
  101. defPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+([-_\w]+)\s*(//.*)?$')
  102. condPatt = re.compile(r'^(\s*(//)?#(if|ifn?def|elif)(\s+\S+)*)\s+(//.*)$')
  103. commPatt = re.compile(r'^\s{20,}(//.*)?$')
  104. col_value_lj = col_comment - patt['pad'] - 2
  105. col_value_rj = col_comment - 3
  106. #
  107. # #define SKIP_ME
  108. #
  109. def trySkip1(d):
  110. if skipPatt1.match(d['line']) == None: return False
  111. logmsg("skip:", d['line'])
  112. return True
  113. #
  114. # #define MY_PIN [pin]
  115. #
  116. def tryPindef(d):
  117. line = d['line']
  118. r = pindefPatt.match(line)
  119. if r == None: return False
  120. logmsg("pin:", line)
  121. pinnum = r[4] if r[4][0] == 'P' else lpad(r[4], patt['pad'])
  122. line = f'{r[1]} {r[3]}'
  123. line = concat_with_space(rpad(line, col_value_lj), pinnum)
  124. if r[5]: line = rpad(line, col_comment) + r[5]
  125. d['line'] = line
  126. return True
  127. #
  128. # #define MY_PIN -1
  129. #
  130. def tryNoPin(d):
  131. line = d['line']
  132. r = noPinPatt.match(line)
  133. if r == None: return False
  134. logmsg("pin -1:", line)
  135. line = f'{r[1]} {r[3]}'
  136. line = concat_with_space(rpad(line, col_value_lj), '-1')
  137. if r[5]: line = rpad(line, col_comment) + r[5]
  138. d['line'] = line
  139. return True
  140. #
  141. # #define SKIP_ME_TOO
  142. #
  143. def trySkip2(d):
  144. if skipPatt2.match( d['line']) == None: return False
  145. logmsg("skip:", d['line'])
  146. return True
  147. #
  148. # #else|endif
  149. #
  150. def trySkip3(d):
  151. if skipPatt3.match( d['line']) == None: return False
  152. logmsg("skip:", d['line'])
  153. return True
  154. #
  155. # #define ALIAS OTHER
  156. #
  157. def tryAlias(d):
  158. line = d['line']
  159. r = aliasPatt.match(line)
  160. if r == None: return False
  161. logmsg("alias:", line)
  162. line = f'{r[1]} {r[3]}'
  163. line = concat_with_space(line, lpad(r[4], col_value_rj + 1 - len(line)))
  164. if r[5]: line = concat_with_space(rpad(line, col_comment), r[5])
  165. d['line'] = line
  166. return True
  167. #
  168. # #define SWITCH
  169. #
  170. def trySwitch(d):
  171. line = d['line']
  172. r = switchPatt.match(line)
  173. if r == None: return False
  174. logmsg("switch:", line)
  175. line = f'{r[1]} {r[3]}'
  176. if r[4]: line = concat_with_space(rpad(line, col_comment), r[4])
  177. d['line'] = line
  178. d['check_comment_next'] = True
  179. return True
  180. #
  181. # #define ...
  182. #
  183. def tryDef(d):
  184. line = d['line']
  185. r = defPatt.match(line)
  186. if r == None: return False
  187. logmsg("def:", line)
  188. line = f'{r[1]} {r[3]} '
  189. line = concat_with_space(line, lpad(r[4], col_value_rj + 1 - len(line)))
  190. if r[5]: line = rpad(line, col_comment - 1) + ' ' + r[5]
  191. d['line'] = line
  192. return True
  193. #
  194. # #undef ...
  195. #
  196. def tryUndef(d):
  197. line = d['line']
  198. r = undefPatt.match(line)
  199. if r == None: return False
  200. logmsg("undef:", line)
  201. line = f'{r[1]} {r[3]}'
  202. if r[4]: line = concat_with_space(rpad(line, col_comment), r[4])
  203. d['line'] = line
  204. return True
  205. #
  206. # #if|ifdef|ifndef|elif ...
  207. #
  208. def tryCond(d):
  209. line = d['line']
  210. r = condPatt.match(line)
  211. if r == None: return False
  212. logmsg("cond:", line)
  213. line = concat_with_space(rpad(r[1], col_comment), r[5])
  214. d['line'] = line
  215. d['check_comment_next'] = True
  216. return True
  217. out = ''
  218. wDict = { 'check_comment_next': False }
  219. # Transform each line and add it to the output
  220. for line in txt.split('\n'):
  221. wDict['line'] = line
  222. if wDict['check_comment_next']:
  223. r = commPatt.match(line)
  224. wDict['check_comment_next'] = (r != None)
  225. if wDict['check_comment_next']:
  226. # Comments in column 50
  227. line = rpad('', col_comment) + r[1]
  228. elif trySkip1(wDict): pass #define SKIP_ME
  229. elif tryPindef(wDict): pass #define MY_PIN [pin]
  230. elif tryNoPin(wDict): pass #define MY_PIN -1
  231. elif trySkip2(wDict): pass #define SKIP_ME_TOO
  232. elif trySkip3(wDict): pass #else|endif
  233. elif tryAlias(wDict): pass #define ALIAS OTHER
  234. elif trySwitch(wDict): pass #define SWITCH
  235. elif tryDef(wDict): pass #define ...
  236. elif tryUndef(wDict): pass #undef ...
  237. elif tryCond(wDict): pass #if|ifdef|ifndef|elif ...
  238. out += wDict['line'].rstrip() + '\n'
  239. return re.sub('\n\n$', '\n', re.sub(r'\n\n+', '\n\n', out))
  240. # Python standard startup for command line with arguments
  241. if __name__ == '__main__':
  242. format_pins(sys.argv[1:])