123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- #!/usr/bin/env python3
- #
- # Formatter script for pins_MYPINS.h files
- #
- # Usage: pinsformat.py [infile] [outfile]
- #
- # With no parameters convert STDIN to STDOUT
- #
- import sys, re
- do_log = False
- def logmsg(msg, line):
- if do_log: print(msg, line)
- col_comment = 50
- # String lpad / rpad
- def lpad(astr, fill, c=' '):
- if not fill: return astr
- need = fill - len(astr)
- return astr if need <= 0 else (need * c) + astr
- def rpad(astr, fill, c=' '):
- if not fill: return astr
- need = fill - len(astr)
- return astr if need <= 0 else astr + (need * c)
- # Concatenate a string, adding a space if necessary
- # to avoid merging two words
- def concat_with_space(s1, s2):
- if not s1.endswith(' ') and not s2.startswith(' '):
- s1 += ' '
- return s1 + s2
- # Pin patterns
- mpatt = [ r'-?\d{1,3}', r'P[A-I]\d+', r'P\d_\d+', r'Pin[A-Z]\d\b' ]
- mstr = '|'.join(mpatt)
- mexpr = [ re.compile(f'^{m}$') for m in mpatt ]
- # Corrsponding padding for each pattern
- ppad = [ 3, 4, 5, 5 ]
- # Match a define line
- definePinPatt = re.compile(rf'^\s*(//)?#define\s+[A-Z_][A-Z0-9_]+?_PIN\s+({mstr})\s*(//.*)?$')
- def format_pins(argv):
- src_file = 'stdin'
- dst_file = None
- scnt = 0
- for arg in argv:
- if arg == '-v':
- global do_log
- do_log = True
- elif scnt == 0:
- # Get a source file if specified. Default destination is the same file
- src_file = dst_file = arg
- scnt += 1
- elif scnt == 1:
- # Get destination file if specified
- dst_file = arg
- scnt += 1
- # No text to process yet
- file_text = ''
- if src_file == 'stdin':
- # If no source file specified read from STDIN
- file_text = sys.stdin.read()
- else:
- # Open and read the file src_file
- with open(src_file, 'r') as rf: file_text = rf.read()
- if len(file_text) == 0:
- print('No text to process')
- return
- # Read from file or STDIN until it terminates
- filtered = process_text(file_text)
- if dst_file:
- with open(dst_file, 'w') as wf: wf.write(filtered)
- else:
- print(filtered)
- # Find the pin pattern so non-pin defines can be skipped
- def get_pin_pattern(txt):
- r = ''
- m = 0
- match_count = [ 0, 0, 0, 0 ]
- # Find the most common matching pattern
- match_threshold = 5
- for line in txt.split('\n'):
- r = definePinPatt.match(line)
- if r == None: continue
- ind = -1
- for p in mexpr:
- ind += 1
- if not p.match(r[2]): continue
- match_count[ind] += 1
- if match_count[ind] >= match_threshold:
- return { 'match': mpatt[ind], 'pad':ppad[ind] }
- return None
- def process_text(txt):
- if len(txt) == 0: return '(no text)'
- patt = get_pin_pattern(txt)
- if patt == None: return txt
- pmatch = patt['match']
- pindefPatt = re.compile(rf'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+({pmatch})\s*(//.*)?$')
- noPinPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+(-1)\s*(//.*)?$')
- 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*(//.*)?$')
- skipPatt2 = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+(0x[0-9A-Fa-f]+|\d+|.+[a-z].+)\s*(//.*)?$')
- skipPatt3 = re.compile(r'^\s*#e(lse|ndif)\b.*$')
- aliasPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+([A-Z_][A-Z0-9_()]+)\s*(//.*)?$')
- switchPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s*(//.*)?$')
- undefPatt = re.compile(r'^(\s*(//)?#undef)\s+([A-Z_][A-Z0-9_]+)\s*(//.*)?$')
- defPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+([-_\w]+)\s*(//.*)?$')
- condPatt = re.compile(r'^(\s*(//)?#(if|ifn?def|elif)(\s+\S+)*)\s+(//.*)$')
- commPatt = re.compile(r'^\s{20,}(//.*)?$')
- col_value_lj = col_comment - patt['pad'] - 2
- col_value_rj = col_comment - 3
- #
- # #define SKIP_ME
- #
- def trySkip1(d):
- if skipPatt1.match(d['line']) == None: return False
- logmsg("skip:", d['line'])
- return True
- #
- # #define MY_PIN [pin]
- #
- def tryPindef(d):
- line = d['line']
- r = pindefPatt.match(line)
- if r == None: return False
- logmsg("pin:", line)
- pinnum = r[4] if r[4][0] == 'P' else lpad(r[4], patt['pad'])
- line = f'{r[1]} {r[3]}'
- line = concat_with_space(rpad(line, col_value_lj), pinnum)
- if r[5]: line = rpad(line, col_comment) + r[5]
- d['line'] = line
- return True
- #
- # #define MY_PIN -1
- #
- def tryNoPin(d):
- line = d['line']
- r = noPinPatt.match(line)
- if r == None: return False
- logmsg("pin -1:", line)
- line = f'{r[1]} {r[3]}'
- line = concat_with_space(rpad(line, col_value_lj), '-1')
- if r[5]: line = rpad(line, col_comment) + r[5]
- d['line'] = line
- return True
- #
- # #define SKIP_ME_TOO
- #
- def trySkip2(d):
- if skipPatt2.match( d['line']) == None: return False
- logmsg("skip:", d['line'])
- return True
- #
- # #else|endif
- #
- def trySkip3(d):
- if skipPatt3.match( d['line']) == None: return False
- logmsg("skip:", d['line'])
- return True
- #
- # #define ALIAS OTHER
- #
- def tryAlias(d):
- line = d['line']
- r = aliasPatt.match(line)
- if r == None: return False
- logmsg("alias:", line)
- line = f'{r[1]} {r[3]}'
- line = concat_with_space(line, lpad(r[4], col_value_rj + 1 - len(line)))
- if r[5]: line = concat_with_space(rpad(line, col_comment), r[5])
- d['line'] = line
- return True
- #
- # #define SWITCH
- #
- def trySwitch(d):
- line = d['line']
- r = switchPatt.match(line)
- if r == None: return False
- logmsg("switch:", line)
- line = f'{r[1]} {r[3]}'
- if r[4]: line = concat_with_space(rpad(line, col_comment), r[4])
- d['line'] = line
- d['check_comment_next'] = True
- return True
- #
- # #define ...
- #
- def tryDef(d):
- line = d['line']
- r = defPatt.match(line)
- if r == None: return False
- logmsg("def:", line)
- line = f'{r[1]} {r[3]} '
- line = concat_with_space(line, lpad(r[4], col_value_rj + 1 - len(line)))
- if r[5]: line = rpad(line, col_comment - 1) + ' ' + r[5]
- d['line'] = line
- return True
- #
- # #undef ...
- #
- def tryUndef(d):
- line = d['line']
- r = undefPatt.match(line)
- if r == None: return False
- logmsg("undef:", line)
- line = f'{r[1]} {r[3]}'
- if r[4]: line = concat_with_space(rpad(line, col_comment), r[4])
- d['line'] = line
- return True
- #
- # #if|ifdef|ifndef|elif ...
- #
- def tryCond(d):
- line = d['line']
- r = condPatt.match(line)
- if r == None: return False
- logmsg("cond:", line)
- line = concat_with_space(rpad(r[1], col_comment), r[5])
- d['line'] = line
- d['check_comment_next'] = True
- return True
- out = ''
- wDict = { 'check_comment_next': False }
- # Transform each line and add it to the output
- for line in txt.split('\n'):
- wDict['line'] = line
- if wDict['check_comment_next']:
- r = commPatt.match(line)
- wDict['check_comment_next'] = (r != None)
- if wDict['check_comment_next']:
- # Comments in column 50
- line = rpad('', col_comment) + r[1]
- elif trySkip1(wDict): pass #define SKIP_ME
- elif tryPindef(wDict): pass #define MY_PIN [pin]
- elif tryNoPin(wDict): pass #define MY_PIN -1
- elif trySkip2(wDict): pass #define SKIP_ME_TOO
- elif trySkip3(wDict): pass #else|endif
- elif tryAlias(wDict): pass #define ALIAS OTHER
- elif trySwitch(wDict): pass #define SWITCH
- elif tryDef(wDict): pass #define ...
- elif tryUndef(wDict): pass #undef ...
- elif tryCond(wDict): pass #if|ifdef|ifndef|elif ...
- out += wDict['line'].rstrip() + '\n'
- return re.sub('\n\n$', '\n', re.sub(r'\n\n+', '\n\n', out))
- # Python standard startup for command line with arguments
- if __name__ == '__main__':
- format_pins(sys.argv[1:])
|