Python 2.6
I tried to implement a basic Python minifier which does the following things:
- Removing unnecessary whitespace
- Removing unnecessary newlines
- Removing comments
- Shortening variable names
As others stated, the winning criteria (shortest output when run through itself) does not make a whole lot of sense for a minifier if you want to start with a non-golfed solution.
Original (2591 characters)
# imports
import keyword, tokenize, sys, token, itertools
# vars
line = ""
out = [] ; block = []
idx = 0 ; previdx = 0 ; prevdirection = 0
varnames = set() ; imports = set()
t = tokenize.generate_tokens(sys.stdin.readline)
newline = '\n'
# settings
replace_varnames = True
while 1:
try:
toknum, tokval, _, _, _ = t.next()
iskwd = tokval in keyword.kwlist
if toknum == 1:
if ''.join(line).lstrip().startswith("import "):
imports.add(tokval)
else:
if not (iskwd or line[-1]=="." or tokval in __builtins__.__dict__.keys() or tokval.startswith("_") or len(tokval) <=2 or tokval in imports):
varnames.add(tokval)
if toknum == 53:
continue
if toknum == 4:
block.append(' '.join(x for x in ("".join(l for l in line if l)).strip().split()))
line = []
if toknum == 5 or toknum == 6:
if out and out[-1] != newline:
out.append(newline)
previdx = idx
idx += (-1,1)[toknum==5]
indenting = previdx < idx
dedenting = previdx > idx
prefix = " " * previdx
base = ";".join(l for l in block)
if dedenting and prevdirection == 1:
out.pop()
out.append(base)
else:
if len(block) > 1:
if indenting:
out.append(prefix + ";".join(l for l in block[:-1]))
out.append(newline)
out.append("".join(prefix + l for l in block[-1:]))
else:
out.append(prefix + base)
elif len(block) == 1:
out.append(prefix + base)
block = []
prevdirection = [-1,1][indenting]
space_if_needed = [""," "][iskwd and tokval not in ("else","try")]
line += space_if_needed + tokval + space_if_needed
except StopIteration:
out.append(" " * idx + ";".join(l for l in block))
break
total_out=''.join(out)+newline
if replace_varnames:
wrappers = [";"," ", ":", "=", "(", "[", ")", "]", ".",",","*","+","-","/","<",">","!","\n"]
wrl = list(itertools.permutations(wrappers,2)) + zip(wrappers, wrappers)
for i, e in enumerate(varnames):
# print i, e, "v"+str(i)
for p, a in wrl:
total_out = total_out.replace(p+e+a,p+"v"+str(i)+a)
print total_out[0:-1]
Minified (by itself), 1388 characters (a 46 % reduction)
import keyword,tokenize,sys,token,itertools;v16="";v20=[];v19=[];v4=0;v15=0;v17=0;v10=set();v6=set();t=tokenize.generate_tokens(sys.stdin.readline);v8='\n';v13=True
while 1:
try:
v18,v9,_,_,_=t.next();v2=v9 in keyword.kwlist
if v18==1:
if ''.join(v16).lstrip().startswith("import "):v6.add(v9)
else:
if not (v2 or v16[-1]=="." or v9 in __builtins__.__dict__.keys() or v9.startswith("_") or len(v9)<=2 or v9 in v6):v10.add(v9)
if v18==53:continue
if v18==4:v19.append(' '.join(x for x in ("".join(l for l in v16 if l)).strip().split()));v16=[]
if v18==5 or v18==6:
if v20 and v20[-1]!=v8:v20.append(v8)
v15=v4;v4+=(-1,1)[v18==5];v5=v15<v4;v12=v15>v4;v11=" "*v15;v14=";".join(l for l in v19)
if v12 and v17==1:v20.pop();v20.append(v14)
else:
if len(v19)>1:
if v5:v20.append(v11+";".join(l for l in v19[:-1]));v20.append(v8);v20.append("".join(v11+l for l in v19[-1:]))
else:v20.append(v11+v14)
elif len(v19)==1:v20.append(v11+v14)
v19=[];v17=[-1,1][v5]
v3=[""," "][v2 and v9 not in ("else","try")];v16+=v3+v9+v3
except StopIteration:v20.append(" "*v4+";".join(l for l in v19));break
v1=''.join(v20)+v8
if v13:
v0=[";"," ",":","=","(","[",")","]",".",",","*","+","-","/","<",">","!","\n"];v7=list(itertools.permutations(v0,2))+zip(v0,v0)
for i,e in enumerate(v10):
for p,a in v7:v1=v1.replace(p+e+a,p+"v"+str(i)+a)
print v1[0:-1]
Known limitations:
- Keyword arguments are problematic when variable name shortening is on.
- The code is ugly and can probably break some of the more dynamic Python constructs. It does work fine for itself (and other simple scripts) though.