Python 2.7, 794
I have been meaning to build a minifier for Python for a while, so this is a good opportunity to investigate the problem.
The program uses a mix of regular expression analysis, and Python parser operations. White space is minimised. Variable that are defined by the user are replaced by a single letter variable (which is not in use!). Finally the while True
statement is put on a diet.
The three test cases all verify as running correctly. I could imagine some pathological examples which could result in errors in the generated code but the algorithm should be robust under most circumstances.
Results
228 t1.py
128 t2.py
438 t3.py
794 total
Code
import sys
import re
from tokenize import generate_tokens
from token import tok_name
from keyword import iskeyword
wr = sys.stdout.write
def pyparse(text):
'Return [TYPE,TOKEN] pair list'
# Use KEYWORD,NAME,NUMBER,OP,STRING,NL,NEWLINE,COMMENT,INDENT,DEDENT
rawtokens = generate_tokens(text.readline)
tokens = [[tok_name[n], t] for n,t,p1,p2,dx in rawtokens]
for tpair in tokens:
if tpair[0] == 'NAME' and iskeyword(tpair[1]):
tpair[0] = 'KEYWORD'
return tokens
def finduservars(filename):
'Return a set of user variables that we can replace with a-zA-Z'
varset = set()
for line in open(filename):
line = line.strip()
match = re.match(r'def\s+(\w+)\s*\((.*)\)\s*:', line)
if match:
func, args = match.groups()
varset.add(func)
arglist = re.findall(r'(\w+|=)', args)
for a in arglist:
if a == '=':
break # keyword args follow - too hard to parse
varset.add(a)
continue
match = re.match(r'(\w+)\s*=.+', line)
if match:
assigned = match.group(1)
varset.add(assigned)
continue
return set(v for v in list(varset) if len(v) > 1)
filename = sys.argv[1]
tokenlist = pyparse(open(filename))
# Build map for var->char conversion:
varset = finduservars(filename)
singles = [text for tok,text in tokenlist if tok=='NAME' and len(text)==1]
allvar = [chr(n) for n in range(97,123)+range(65,91)]
charvar = [c for c in allvar if c not in singles]
varreplaced = list(varset)[:len(charvar)]
varmap = dict((v, charvar.pop(0)) for v in varreplaced)
prev = 'NONE'
indent = ['']
output = []
add = output.append
for tok, text in tokenlist:
if tok == 'NL':
continue
elif tok == 'INDENT':
indent.append( text.replace(' ', ' ') )
output[-1] = indent[-1]
elif tok == 'DEDENT':
indent.pop(-1)
output[-1] = indent[-1]
elif tok == 'NEWLINE':
add(text)
add(indent[-1])
elif tok in 'KEYWORD,NAME,NUMBER':
if prev in 'KEYWORD,NAME,NUMBER':
add(' ')
if tok == 'NAME':
if output[-2] == 'while' and text == 'True':
add('1') # common verbose idiom
else:
add(varmap.get(text, text))
else:
add(text)
else:
add(text)
prev = tok
wr(''.join(output))