local source = [[
local str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
local function split(s, delimiter)
local t = {}
(s..delimiter):gsub("(.-)"..delimiter, function(c)
table.insert(t, c)
end)
return t
end
for _, s in ipairs(split(str, " ")) do
print(s)
end
]]
local minified = require("wsminify")(source)
print(minified)
print("---")
loadstring(minified)()
--[[
wsminify
A whitespace minifier made with boatbomber's Lua(u) lexer
Lua Lexer (https://devforum.roblox.com/t/183115)
the original Penlight Lexer module:
https://github.com/stevedonovan/Penlight
Authors:
stevedonovan <https://github.com/stevedonovan> ----------- Original Penlight lexer author
ryanjmulder <https://github.com/ryanjmulder> ------------- Penlight lexer contributer
mpeterv <https://github.com/mpeterv> --------------------- Penlight lexer contributer
Tieske <https://github.com/Tieske> ----------------------- Penlight lexer contributer
boatbomber <https://github.com/boatbomber> --------------- Roblox port, added builtin token, added patterns for incomplete syntax, bug fixes, behavior changes, token optimization
Sleitnick <https://github.com/Sleitnick> ----------------- Roblox optimizations
howmanysmall <https://github.com/howmanysmall> ----------- Lua + Roblox optimizations
boatbomber <https://github.com/boatbomber> --------------- Added lexer.navigator() for non-sequential reads
]]
-- Ported lexer
-- Also minified with this, except I had to add an escape on one of the
-- patterns (which had ]] and conflicted the multi-line string)
local lexer={} local Prefix,Suffix,Cleaner="^[ \t\n\0\a\b\v\f\r]*","[ \t\n\0\a\b\v\f\r]*","[ \t\n\0\a\b\v\f\r]+" local NUMBER_A="0x[%da-fA-F]+" local NUMBER_B="%d+%.?%d*[eE][%+%-]?%d+" local NUMBER_C="%d+[%._]?[%d_eE]*" local OPERATORS="[:;<>/~%*%(%)%-=,{}%.#%^%+%%]+" local BRACKETS="[%[%]]+" local IDEN="[%a_][%w_]*" local STRING_EMPTY="(['\"])%1" local STRING_PLAIN=[=[(['"])[%w%p \t\v\b\f\r\a]-([^%\]%1)]=] local STRING_INCOMP_A="(['\"]).-\n" local STRING_INCOMP_B="(['\"])[^\n]*" local STRING_MULTI="%[(=*)%[.-%]%1%]" local STRING_MULTI_INCOMP="%[=*%[.-.*" local COMMENT_MULTI="%-%-%[(=*)%[.-%]%1%]" local COMMENT_MULTI_INCOMP="%-%-%[=*%[.-.*" local COMMENT_PLAIN="%-%-.-\n" local COMMENT_INCOMP="%-%-.*" local TABLE_EMPTY={} local lua_keyword={["and"]= true ,["break"]= true ,["do"]= true ,["else"]= true ,["elseif"]= true ,["end"]= true ,["false"]= true ,["for"]= true ,["function"]= true ,["if"]= true ,["in"]= true ,["local"]= true ,["nil"]= true ,["not"]= true ,["while"]= true ,["or"]= true ,["repeat"]= true ,["return"]= true ,["then"]= true ,["true"]= true ,["self"]= true ,["until"]= true ,["continue"]= true ,["plugin"]= true ,} local lua_builtin={["assert"]= true ;["collectgarbage"]= true ;["error"]= true ;["getfenv"]= true ;["getmetatable"]= true ;["ipairs"]= true ;["loadstring"]= true ;["newproxy"]= true ;["next"]= true ;["pairs"]= true ;["pcall"]= true ;["print"]= true ;["rawequal"]= true ;["rawget"]= true ;["rawset"]= true ;["select"]= true ;["setfenv"]= true ;["setmetatable"]= true ;["tonumber"]= true ;["tostring"]= true ;["type"]= true ;["unpack"]= true ;["xpcall"]= true ;["_G"]= true ;["_VERSION"]= true ;["bit32"]= true ;["coroutine"]= true ;["debug"]= true ;["math"]= true ;["os"]= true ;["string"]= true ;["table"]= true ;["utf8"]= true ;["delay"]= true ;["elapsedTime"]= true ;["gcinfo"]= true ;["require"]= true ;["settings"]= true ;["spawn"]= true ;["tick"]= true ;["time"]= true ;["typeof"]= true ;["UserSettings"]= true ;["wait"]= true ;["warn"]= true ;["ypcall"]= true ;["Enum"]= true ;["game"]= true ;["shared"]= true ;["script"]= true ;["workspace"]= true ;["Axes"]= true ;["BrickColor"]= true ;["CellId"]= true ;["CFrame"]= true ;["Color3"]= true ;["ColorSequence"]= true ;["ColorSequenceKeypoint"]= true ;["DateTime"]= true ;["DockWidgetPluginGuiInfo"]= true ;["Faces"]= true ;["Instance"]= true ;["NumberRange"]= true ;["NumberSequence"]= true ;["NumberSequenceKeypoint"]= true ;["PathWaypoint"]= true ;["PhysicalProperties"]= true ;["PluginDrag"]= true ;["Random"]= true ;["Ray"]= true ;["Rect"]= true ;["Region3"]= true ;["Region3int16"]= true ;["TweenInfo"]= true ;["UDim"]= true ;["UDim2"]= true ;["Vector2"]= true ;["Vector2int16"]= true ;["Vector3"]= true ;["Vector3int16"]= true ;} local function idump(tok) return coroutine.yield("iden",tok) end local function odump(tok) return coroutine.yield("operator",tok) end local function ndump(tok) return coroutine.yield("number",tok) end local function sdump(tok) return coroutine.yield("string",tok) end local function cdump(tok) return coroutine.yield("comment",tok) end local function lua_vdump(tok) local cleanTok=string.gsub(tok,Cleaner,"") if lua_keyword[cleanTok] then return coroutine.yield("keyword",tok) elseif lua_builtin[cleanTok] then return coroutine.yield("builtin",tok) else return coroutine.yield("iden",tok) end end local lua_matches={{Prefix..IDEN..Suffix,lua_vdump},{Prefix..NUMBER_A..Suffix,ndump},{Prefix..NUMBER_B..Suffix,ndump},{Prefix..NUMBER_C..Suffix,ndump},{Prefix..STRING_EMPTY..Suffix,sdump},{Prefix..STRING_PLAIN..Suffix,sdump},{Prefix..STRING_INCOMP_A..Suffix,sdump},{Prefix..STRING_INCOMP_B..Suffix,sdump},{Prefix..STRING_MULTI..Suffix,sdump},{Prefix..STRING_MULTI_INCOMP..Suffix,sdump},{Prefix..COMMENT_MULTI..Suffix,cdump},{Prefix..COMMENT_MULTI_INCOMP..Suffix,cdump},{Prefix..COMMENT_PLAIN..Suffix,cdump},{Prefix..COMMENT_INCOMP..Suffix,cdump},{Prefix..OPERATORS..Suffix,odump},{Prefix..BRACKETS..Suffix,odump},{"^.",idump}} function lexer.scan(s) local startTime=os.clock()lexer.finished= false local function lex(first_arg) local line_nr=0 local sz=#s local idx=1 local function handle_requests(res) while res do local tp=type(res) if tp=="table" then res=coroutine.yield("","") for _,t in ipairs(res) do res=coroutine.yield(t[1],t[2]) end elseif tp=="string" then local i1,i2=string.find(s,res,idx) if i1 then idx=i2+1res=coroutine.yield("",string.sub(s,i1,i2)) else res=coroutine.yield("","")idx=sz+1 end else res=coroutine.yield(line_nr,idx) end end end handle_requests(first_arg)line_nr=1 while true do if idx>sz then while true do handle_requests(coroutine.yield()) end end for _,m in ipairs(lua_matches) do local findres={} local i1,i2=string.find(s,m[1],idx)findres[1],findres[2]=i1,i2 if i1 then local tok=string.sub(s,i1,i2)idx=i2+1lexer.finished=idx>sz local res=m[2](tok,findres) if string.find(tok,"\n") then local _,newlines=string.gsub(tok,"\n",TABLE_EMPTY)line_nr=line_nr+newlines end handle_requests(res) break end end end end return coroutine.wrap(lex) end
-- Transformers
local function trim(s)
local r, _ = s:gsub("^%s+",""):gsub("%s-$","")
return r
end
local function blank()
return ""
end
local function padend(s)
return trim(s).." "
end
local function pad(s)
return " "..trim(s).." "
end
-- Lookup a token's transformer
local transform = {
["string"] = trim;
["operator"] = trim;
["number"] = trim;
["iden"] = trim;
["builtin"] = trim;
["space"] = blank;
["comment"] = blank;
["keyword"] = function(s, previous)
s = trim(s)
return (
previous == nil -- Beginning of string
or (previous[1]~="keyword" and previous[1]~="iden") -- Not a keyword or variable
or (previous[2] and previous[2]:sub(previous[2]:len(),-1) == " ") -- Already has a space
) and padend(s) or pad(s)
end
}
return function(s)
local minified = ""
local previous
for token, src in lexer.scan(s) do
local transformed = transform[token] and transform[token](src, previous) or src
minified = minified..transformed
previous = {token, transformed}
end
return minified
end