uglify/squish.uglify.lua

1
local llex = require "llex"
2
 
3
local base_char = 128;
4
local keywords = { "and", "break", "do", "else", "elseif",
5
    "end", "false", "for", "function", "if",
6
        "in", "local", "nil", "not", "or", "repeat",
7
            "return", "then", "true", "until", "while" }
8
 
9
function uglify_file(infile_fn, outfile_fn)
10
	local infile, err = io.open(infile_fn);
11
	if not infile then
12
		print_err("Can't open input file for reading: "..tostring(err));
13
		return;
14
	end
15
	
16
	local outfile, err = io.open(outfile_fn..".uglified", "w+b");
17
	if not outfile then
18
		print_err("Can't open output file for writing: "..tostring(err));
19
		return;
20
	end
21
	
22
	local data = infile:read("*a");
23
	infile:close();
24
	
25
	local shebang, newdata = data:match("^(#.-\n)(.+)$");
26
	local code = newdata or data;
27
	if shebang then
28
		outfile:write(shebang)
29
	end
30
 
31
	
32
	while base_char + #keywords <= 255 and code:find("["..string.char(base_char).."-"..string.char(base_char+#keywords-1).."]") do
33
		base_char = base_char + 1;
34
	end
35
	if base_char + #keywords > 255 then
36
		-- Sorry, can't uglify this file :(
37
		-- We /could/ use a multi-byte marker, but that would complicate
38
		-- things and lower the compression ratio (there are quite a few 
39
		-- 2-letter keywords)
40
		outfile:write(code);
41
		outfile:close();
42
		os.rename(outfile_fn..".uglified", outfile_fn);
43
		return;
44
	end
45
 
46
	local keyword_map_to_char = {}
47
	for i, keyword in ipairs(keywords) do
48
		keyword_map_to_char[keyword] = string.char(base_char + i);
49
	end
50
 
51
	-- Write loadstring and open string
52
	local maxequals = 0;
53
	data:gsub("(=+)", function (equals_string) maxequals = math.max(maxequals, #equals_string); end);
54
	
55
	-- Go lexer!
56
	llex.init(code, "@"..infile_fn);
57
	llex.llex()
58
	local seminfo = llex.seminfo;
59
	
60
	if opts.uglify_level == "full" and base_char+#keywords < 255 then
61
		-- Find longest TK_NAME and TK_STRING tokens
62
		local scores = {};
63
		for k,v in ipairs(llex.tok) do
64
			if v == "TK_NAME" or v == "TK_STRING" then
65
				local key = string.format("%q,%q", v, seminfo[k]);
66
				if not scores[key] then
67
					scores[key] = { type = v, value = seminfo[k], count = 0 };
68
					scores[#scores+1] = scores[key];
69
				end
70
				scores[key].count = scores[key].count + 1;
71
			end
72
		end
73
		for i=1,#scores do
74
			local v = scores[i];
75
			v.score = (v.count)*(#v.value-1)- #string.format("%q", v.value) - 1;
76
		end
77
		table.sort(scores, function (a, b) return a.score > b.score; end);
78
		local free_space = 255-(base_char+#keywords);
79
		for i=free_space+1,#scores do
80
			scores[i] = nil; -- Drop any over the limit
81
		end
82
	
83
		local base_keywords_len = #keywords;
84
		for k,v in ipairs(scores) do
85
			if v.score > 0 then
86
				table.insert(keywords, v.value);
87
				keyword_map_to_char[v.value] = string.char(base_char+base_keywords_len+k);
88
			end
89
		end
90
	end
91
	
92
	outfile:write("local base_char,keywords=", tostring(base_char), ",{");
93
	for _, keyword in ipairs(keywords) do
94
		outfile:write(string.format("%q", keyword), ',');
95
	end
96
	outfile:write[[}; function prettify(code) return code:gsub("["..string.char(base_char).."-"..string.char(base_char+#keywords).."]", 
97
	function (c) return keywords[c:byte()-base_char]; end) end ]]
98
	
99
	outfile:write [[return setfenv(assert(loadstring(prettify]]
100
	outfile:write("[", string.rep("=", maxequals+1), "[");
101
	
102
	-- Write code, substituting tokens as we go
103
	for k,v in ipairs(llex.tok) do
104
		if v == "TK_KEYWORD" or v == "TK_NAME" or v == "TK_STRING" then
105
			local keyword_char = keyword_map_to_char[seminfo[k]];
106
			if keyword_char then
107
				outfile:write(keyword_char);
108
			else -- Those who think Lua shouldn't have 'continue, fix this please :)
109
				outfile:write(seminfo[k]);
110
			end
111
		else
112
			outfile:write(seminfo[k]);
113
		end
114
	end
115
 
116
	-- Close string/functions	
117
	outfile:write("]", string.rep("=", maxequals+1), "]");
118
	outfile:write(", '@", outfile_fn,"')), getfenv())()");
119
	outfile:close();
120
	os.rename(outfile_fn..".uglified", outfile_fn);
121
end
122
 
123
if opts.uglify then
124
	print_info("Uglifying "..out_fn.."...");
125
	uglify_file(out_fn, out_fn);
126
	print_info("OK!");
127
end