Added AIO

This commit is contained in:
2024-02-08 18:34:50 -05:00
parent 613a9e16f9
commit 07fde21fe6
23 changed files with 6917 additions and 0 deletions

1287
AIO_Server/AIO.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
LuaSrcDiet License
------------------
LuaSrcDiet is licensed under the terms of the MIT license reproduced
below. This means that LuaSrcDiet is free software and can be used for
both academic and commercial purposes at absolutely no cost.
Parts of LuaSrcDiet is based on Lua 5 code. See COPYRIGHT_Lua51
(Lua 5.1.3) for Lua 5 license information.
For details and rationale, see http://www.lua.org/license.html .
===============================================================================
Copyright (C) 2005-2008 Kein-Hong Man <khman@users.sf.net>
Lua 5.1.3 Copyright (C) 1994-2008 Lua.org, PUC-Rio.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================================================
(end of COPYRIGHT)

View File

@@ -0,0 +1,34 @@
Lua License
-----------
Lua is licensed under the terms of the MIT license reproduced below.
This means that Lua is free software and can be used for both academic
and commercial purposes at absolutely no cost.
For details and rationale, see http://www.lua.org/license.html .
===============================================================================
Copyright (C) 1994-2008 Lua.org, PUC-Rio.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================================================
(end of COPYRIGHT)

View File

@@ -0,0 +1,809 @@
#!/usr/bin/env lua
--[[--------------------------------------------------------------------
LuaSrcDiet
Compresses Lua source code by removing unnecessary characters.
For Lua 5.1.x source code.
Copyright (c) 2008 Kein-Hong Man <khman@users.sf.net>
The COPYRIGHT file describes the conditions
under which this software may be distributed.
See the ChangeLog for more information.
----------------------------------------------------------------------]]
--[[--------------------------------------------------------------------
-- NOTES:
-- * Remember to update version and date information below (MSG_TITLE)
-- * TODO: to implement pcall() to properly handle lexer etc. errors
-- * TODO: verify token stream or double-check binary chunk?
-- * TODO: need some automatic testing for a semblance of sanity
-- * TODO: the plugin module is highly experimental and unstable
----------------------------------------------------------------------]]
-- standard libraries, functions
local string = string
local math = math
local table = table
local require = require
local print = print
local sub = string.sub
local gmatch = string.gmatch
-- support modules
local llex = require "llex"
local lparser = require "lparser"
local optlex = require "optlex"
local optparser = require "optparser"
local plugin
--[[--------------------------------------------------------------------
-- messages and textual data
----------------------------------------------------------------------]]
local MSG_TITLE = [[
LuaSrcDiet: Puts your Lua 5.1 source code on a diet
Version 0.11.2 (20080608) Copyright (c) 2005-2008 Kein-Hong Man
The COPYRIGHT file describes the conditions under which this
software may be distributed.
]]
local MSG_USAGE = [[
usage: LuaSrcDiet [options] [filenames]
example:
>LuaSrcDiet myscript.lua -o myscript_.lua
options:
-v, --version prints version information
-h, --help prints usage information
-o <file> specify file name to write output
-s <suffix> suffix for output files (default '_')
--keep <msg> keep block comment with <msg> inside
--plugin <module> run <module> in plugin/ directory
- stop handling arguments
(optimization levels)
--none all optimizations off (normalizes EOLs only)
--basic lexer-based optimizations only
--maximum maximize reduction of source
(informational)
--quiet process files quietly
--read-only read file and print token stats only
--dump-lexer dump raw tokens from lexer to stdout
--dump-parser dump variable tracking tables from parser
--details extra info (strings, numbers, locals)
features (to disable, insert 'no' prefix like --noopt-comments):
%s
default settings:
%s]]
------------------------------------------------------------------------
-- optimization options, for ease of switching on and off
-- * positive to enable optimization, negative (no) to disable
-- * these options should follow --opt-* and --noopt-* style for now
------------------------------------------------------------------------
local OPTION = [[
--opt-comments,'remove comments and block comments'
--opt-whitespace,'remove whitespace excluding EOLs'
--opt-emptylines,'remove empty lines'
--opt-eols,'all above, plus remove unnecessary EOLs'
--opt-strings,'optimize strings and long strings'
--opt-numbers,'optimize numbers'
--opt-locals,'optimize local variable names'
--opt-entropy,'tries to reduce symbol entropy of locals'
]]
-- preset configuration
local DEFAULT_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--opt-numbers --opt-locals
]]
-- override configurations: MUST explicitly enable/disable everything
local BASIC_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--noopt-eols --noopt-strings --noopt-numbers
--noopt-locals
]]
local MAXIMUM_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--opt-eols --opt-strings --opt-numbers
--opt-locals --opt-entropy
]]
local NONE_CONFIG = [[
--noopt-comments --noopt-whitespace --noopt-emptylines
--noopt-eols --noopt-strings --noopt-numbers
--noopt-locals
]]
local DEFAULT_SUFFIX = "_" -- default suffix for file renaming
local PLUGIN_SUFFIX = "plugin/" -- relative location of plugins
--[[--------------------------------------------------------------------
-- startup and initialize option list handling
----------------------------------------------------------------------]]
-- simple error message handler; change to error if traceback wanted
local function die(msg)
print("LuaSrcDiet: "..msg); os.exit()
end
--die = error--DEBUG
--if not string.match(_VERSION, "5.1", 1, 1) then -- sanity check
-- die("requires Lua 5.1 to run")
--end
------------------------------------------------------------------------
-- prepares text for list of optimizations, prepare lookup table
------------------------------------------------------------------------
local MSG_OPTIONS = ""
do
local WIDTH = 24
local o = {}
for op, desc in gmatch(OPTION, "%s*([^,]+),'([^']+)'") do
local msg = " "..op
msg = msg..string.rep(" ", WIDTH - #msg)..desc.."\n"
MSG_OPTIONS = MSG_OPTIONS..msg
o[op] = true
o["--no"..sub(op, 3)] = true
end
OPTION = o -- replace OPTION with lookup table
end
MSG_USAGE = string.format(MSG_USAGE, MSG_OPTIONS, DEFAULT_CONFIG)
------------------------------------------------------------------------
-- global variable initialization, option set handling
------------------------------------------------------------------------
local suffix = DEFAULT_SUFFIX -- file suffix
local option = {} -- program options
local stat_c, stat_l -- statistics tables
-- function to set option lookup table based on a text list of options
-- note: additional forced settings for --opt-eols is done in optlex.lua
local function set_options(CONFIG)
for op in gmatch(CONFIG, "(%-%-%S+)") do
if sub(op, 3, 4) == "no" and -- handle negative options
OPTION["--"..sub(op, 5)] then
option[sub(op, 5)] = false
else
option[sub(op, 3)] = true
end
end
end
--[[--------------------------------------------------------------------
-- support functions
----------------------------------------------------------------------]]
-- list of token types, parser-significant types are up to TTYPE_GRAMMAR
-- while the rest are not used by parsers; arranged for stats display
local TTYPES = {
"TK_KEYWORD", "TK_NAME", "TK_NUMBER", -- grammar
"TK_STRING", "TK_LSTRING", "TK_OP",
"TK_EOS",
"TK_COMMENT", "TK_LCOMMENT", -- non-grammar
"TK_EOL", "TK_SPACE",
}
local TTYPE_GRAMMAR = 7
local EOLTYPES = { -- EOL names for token dump
["\n"] = "LF", ["\r"] = "CR",
["\n\r"] = "LFCR", ["\r\n"] = "CRLF",
}
------------------------------------------------------------------------
-- read source code from file
------------------------------------------------------------------------
local function load_file(fname)
local INF = io.open(fname, "rb")
if not INF then die("cannot open \""..fname.."\" for reading") end
local dat = INF:read("*a")
if not dat then die("cannot read from \""..fname.."\"") end
INF:close()
print("loadf ", type(dat))
return dat
end
------------------------------------------------------------------------
-- save source code to file
------------------------------------------------------------------------
local function save_file(fname, dat)
local OUTF = io.open(fname, "wb")
if not OUTF then die("cannot open \""..fname.."\" for writing") end
local status = OUTF:write(dat)
if not status then die("cannot write to \""..fname.."\"") end
OUTF:close()
print("loadf ", type(dat))
end
------------------------------------------------------------------------
-- functions to deal with statistics
------------------------------------------------------------------------
-- initialize statistics table
local function stat_init()
stat_c, stat_l = {}, {}
for i = 1, #TTYPES do
local ttype = TTYPES[i]
stat_c[ttype], stat_l[ttype] = 0, 0
end
end
-- add a token to statistics table
local function stat_add(tok, seminfo)
stat_c[tok] = stat_c[tok] + 1
stat_l[tok] = stat_l[tok] + #seminfo
end
-- do totals for statistics table, return average table
local function stat_calc()
local function avg(c, l) -- safe average function
if c == 0 then return 0 end
return l / c
end
local stat_a = {}
local c, l = 0, 0
for i = 1, TTYPE_GRAMMAR do -- total grammar tokens
local ttype = TTYPES[i]
c = c + stat_c[ttype]; l = l + stat_l[ttype]
end
stat_c.TOTAL_TOK, stat_l.TOTAL_TOK = c, l
stat_a.TOTAL_TOK = avg(c, l)
c, l = 0, 0
for i = 1, #TTYPES do -- total all tokens
local ttype = TTYPES[i]
c = c + stat_c[ttype]; l = l + stat_l[ttype]
stat_a[ttype] = avg(stat_c[ttype], stat_l[ttype])
end
stat_c.TOTAL_ALL, stat_l.TOTAL_ALL = c, l
stat_a.TOTAL_ALL = avg(c, l)
return stat_a
end
--[[--------------------------------------------------------------------
-- main tasks
----------------------------------------------------------------------]]
------------------------------------------------------------------------
-- a simple token dumper, minimal translation of seminfo data
------------------------------------------------------------------------
local function dump_tokens(srcfl)
--------------------------------------------------------------------
-- load file and process source input into tokens
--------------------------------------------------------------------
local z = load_file(srcfl)
llex.init(z)
llex.llex()
local toklist, seminfolist = llex.tok, llex.seminfo
--------------------------------------------------------------------
-- display output
--------------------------------------------------------------------
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
if tok == "TK_OP" and string.byte(seminfo) < 32 then
seminfo = "(".. string.byte(seminfo)..")"
elseif tok == "TK_EOL" then
seminfo = EOLTYPES[seminfo]
else
seminfo = "'"..seminfo.."'"
end
print(tok.." "..seminfo)
end--for
end
----------------------------------------------------------------------
-- parser dump; dump globalinfo and localinfo tables
----------------------------------------------------------------------
local function dump_parser(srcfl)
local print = print
--------------------------------------------------------------------
-- load file and process source input into tokens
--------------------------------------------------------------------
local z = load_file(srcfl)
llex.init(z)
llex.llex()
local toklist, seminfolist, toklnlist
= llex.tok, llex.seminfo, llex.tokln
--------------------------------------------------------------------
-- do parser optimization here
--------------------------------------------------------------------
lparser.init(toklist, seminfolist, toklnlist)
local globalinfo, localinfo = lparser.parser()
--------------------------------------------------------------------
-- display output
--------------------------------------------------------------------
local hl = string.rep("-", 72)
print("*** Local/Global Variable Tracker Tables ***")
print(hl.."\n GLOBALS\n"..hl)
-- global tables have a list of xref numbers only
for i = 1, #globalinfo do
local obj = globalinfo[i]
local msg = "("..i..") '"..obj.name.."' -> "
local xref = obj.xref
for j = 1, #xref do msg = msg..xref[j].." " end
print(msg)
end
-- local tables have xref numbers and a few other special
-- numbers that are specially named: decl (declaration xref),
-- act (activation xref), rem (removal xref)
print(hl.."\n LOCALS (decl=declared act=activated rem=removed)\n"..hl)
for i = 1, #localinfo do
local obj = localinfo[i]
local msg = "("..i..") '"..obj.name.."' decl:"..obj.decl..
" act:"..obj.act.." rem:"..obj.rem
if obj.isself then
msg = msg.." isself"
end
msg = msg.." -> "
local xref = obj.xref
for j = 1, #xref do msg = msg..xref[j].." " end
print(msg)
end
print(hl.."\n")
end
------------------------------------------------------------------------
-- reads source file(s) and reports some statistics
------------------------------------------------------------------------
local function read_only(srcfl)
local print = print
--------------------------------------------------------------------
-- load file and process source input into tokens
--------------------------------------------------------------------
local z = load_file(srcfl)
llex.init(z)
llex.llex()
local toklist, seminfolist = llex.tok, llex.seminfo
print(MSG_TITLE)
print("Statistics for: "..srcfl.."\n")
--------------------------------------------------------------------
-- collect statistics
--------------------------------------------------------------------
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat_a = stat_calc()
--------------------------------------------------------------------
-- display output
--------------------------------------------------------------------
local fmt = string.format
local function figures(tt)
return stat_c[tt], stat_l[tt], stat_a[tt]
end
local tabf1, tabf2 = "%-16s%8s%8s%10s", "%-16s%8d%8d%10.2f"
local hl = string.rep("-", 42)
print(fmt(tabf1, "Lexical", "Input", "Input", "Input"))
print(fmt(tabf1, "Elements", "Count", "Bytes", "Average"))
print(hl)
for i = 1, #TTYPES do
local ttype = TTYPES[i]
print(fmt(tabf2, ttype, figures(ttype)))
if ttype == "TK_EOS" then print(hl) end
end
print(hl)
print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
print(hl)
print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
print(hl.."\n")
end
------------------------------------------------------------------------
-- process source file(s), write output and reports some statistics
------------------------------------------------------------------------
local function process_file(srcfl, destfl)
local function print(...) -- handle quiet option
if option.QUIET then return end
_G.print(...)
end
if plugin and plugin.init then -- plugin init
option.EXIT = false
plugin.init(option, srcfl, destfl)
if option.EXIT then return end
end
print(MSG_TITLE) -- title message
--------------------------------------------------------------------
-- load file and process source input into tokens
--------------------------------------------------------------------
local z = load_file(srcfl)
if plugin and plugin.post_load then -- plugin post-load
z = plugin.post_load(z) or z
if option.EXIT then return end
end
llex.init(z)
llex.llex()
local toklist, seminfolist, toklnlist
= llex.tok, llex.seminfo, llex.tokln
if plugin and plugin.post_lex then -- plugin post-lex
plugin.post_lex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
--------------------------------------------------------------------
-- collect 'before' statistics
--------------------------------------------------------------------
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat1_a = stat_calc()
local stat1_c, stat1_l = stat_c, stat_l
--------------------------------------------------------------------
-- do parser optimization here
--------------------------------------------------------------------
if option["opt-locals"] then
optparser.print = print -- hack
lparser.init(toklist, seminfolist, toklnlist)
local globalinfo, localinfo = lparser.parser()
if plugin and plugin.post_parse then -- plugin post-parse
plugin.post_parse(globalinfo, localinfo)
if option.EXIT then return end
end
optparser.optimize(option, toklist, seminfolist, globalinfo, localinfo)
if plugin and plugin.post_optparse then -- plugin post-optparse
plugin.post_optparse()
if option.EXIT then return end
end
end
--------------------------------------------------------------------
-- do lexer optimization here, save output file
--------------------------------------------------------------------
optlex.print = print -- hack
toklist, seminfolist, toklnlist
= optlex.optimize(option, toklist, seminfolist, toklnlist)
if plugin and plugin.post_optlex then -- plugin post-optlex
plugin.post_optlex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
local dat = table.concat(seminfolist)
-- depending on options selected, embedded EOLs in long strings and
-- long comments may not have been translated to \n, tack a warning
if string.find(dat, "\r\n", 1, 1) or
string.find(dat, "\n\r", 1, 1) then
optlex.warn.mixedeol = true
end
-- save optimized source stream to output file
save_file(destfl, dat)
--------------------------------------------------------------------
-- collect 'after' statistics
--------------------------------------------------------------------
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat_a = stat_calc()
--------------------------------------------------------------------
-- display output
--------------------------------------------------------------------
-- print("Statistics for: "..srcfl.." -> "..destfl.."\n")
-- local fmt = string.format
-- local function figures(tt)
-- return stat1_c[tt], stat1_l[tt], stat1_a[tt],
-- stat_c[tt], stat_l[tt], stat_a[tt]
-- end
-- local tabf1, tabf2 = "%-16s%8s%8s%10s%8s%8s%10s",
-- "%-16s%8d%8d%10.2f%8d%8d%10.2f"
-- local hl = string.rep("-", 68)
-- print("*** lexer-based optimizations summary ***\n"..hl)
-- print(fmt(tabf1, "Lexical",
-- "Input", "Input", "Input",
-- "Output", "Output", "Output"))
-- print(fmt(tabf1, "Elements",
-- "Count", "Bytes", "Average",
-- "Count", "Bytes", "Average"))
-- print(hl)
-- for i = 1, #TTYPES do
-- local ttype = TTYPES[i]
-- print(fmt(tabf2, ttype, figures(ttype)))
-- if ttype == "TK_EOS" then print(hl) end
-- end
-- print(hl)
-- print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
-- print(hl)
-- print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
-- print(hl)
--------------------------------------------------------------------
-- report warning flags from optimizing process
--------------------------------------------------------------------
if optlex.warn.lstring then
print("* WARNING: "..optlex.warn.lstring)
elseif optlex.warn.mixedeol then
print("* WARNING: ".."output still contains some CRLF or LFCR line endings")
end
print()
end
local function process_code(code, config)
option.QUIET = true
if config == 1 then
set_options(DEFAULT_CONFIG)
elseif config == 2 then
set_options(BASIC_CONFIG)
elseif config == 3 then
set_options(MAXIMUM_CONFIG)
else
set_options(NONE_CONFIG)
end
local function print(...) -- handle quiet option
if option.QUIET then return end
_G.print(...)
end
-- if plugin and plugin.init then -- plugin init
-- option.EXIT = false
-- plugin.init(option, srcfl, destfl)
-- if option.EXIT then return end
-- end
print(MSG_TITLE) -- title message
--------------------------------------------------------------------
-- load file and process source input into tokens
--------------------------------------------------------------------
local z = code -- load_file(srcfl)
if plugin and plugin.post_load then -- plugin post-load
z = plugin.post_load(z) or z
if option.EXIT then return end
end
llex.init(z)
llex.llex()
local toklist, seminfolist, toklnlist
= llex.tok, llex.seminfo, llex.tokln
if plugin and plugin.post_lex then -- plugin post-lex
plugin.post_lex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
--------------------------------------------------------------------
-- collect 'before' statistics
--------------------------------------------------------------------
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat1_a = stat_calc()
local stat1_c, stat1_l = stat_c, stat_l
--------------------------------------------------------------------
-- do parser optimization here
--------------------------------------------------------------------
if option["opt-locals"] then
optparser.print = print -- hack
lparser.init(toklist, seminfolist, toklnlist)
local globalinfo, localinfo = lparser.parser()
if plugin and plugin.post_parse then -- plugin post-parse
plugin.post_parse(globalinfo, localinfo)
if option.EXIT then return end
end
optparser.optimize(option, toklist, seminfolist, globalinfo, localinfo)
if plugin and plugin.post_optparse then -- plugin post-optparse
plugin.post_optparse()
if option.EXIT then return end
end
end
--------------------------------------------------------------------
-- do lexer optimization here, save output file
--------------------------------------------------------------------
optlex.print = print -- hack
toklist, seminfolist, toklnlist
= optlex.optimize(option, toklist, seminfolist, toklnlist)
if plugin and plugin.post_optlex then -- plugin post-optlex
plugin.post_optlex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
local dat = table.concat(seminfolist)
-- depending on options selected, embedded EOLs in long strings and
-- long comments may not have been translated to \n, tack a warning
if string.find(dat, "\r\n", 1, 1) or
string.find(dat, "\n\r", 1, 1) then
optlex.warn.mixedeol = true
end
-- save optimized source stream to output file
-- save_file(destfl, dat)
--------------------------------------------------------------------
-- collect 'after' statistics
--------------------------------------------------------------------
-- stat_init()
-- for i = 1, #toklist do
-- local tok, seminfo = toklist[i], seminfolist[i]
-- stat_add(tok, seminfo)
-- end--for
-- local stat_a = stat_calc()
--------------------------------------------------------------------
-- display output
--------------------------------------------------------------------
-- print("Statistics for: "..srcfl.." -> "..destfl.."\n")
-- local fmt = string.format
-- local function figures(tt)
-- return stat1_c[tt], stat1_l[tt], stat1_a[tt],
-- stat_c[tt], stat_l[tt], stat_a[tt]
-- end
-- local tabf1, tabf2 = "%-16s%8s%8s%10s%8s%8s%10s",
-- "%-16s%8d%8d%10.2f%8d%8d%10.2f"
-- local hl = string.rep("-", 68)
-- print("*** lexer-based optimizations summary ***\n"..hl)
-- print(fmt(tabf1, "Lexical",
-- "Input", "Input", "Input",
-- "Output", "Output", "Output"))
-- print(fmt(tabf1, "Elements",
-- "Count", "Bytes", "Average",
-- "Count", "Bytes", "Average"))
-- print(hl)
-- for i = 1, #TTYPES do
-- local ttype = TTYPES[i]
-- print(fmt(tabf2, ttype, figures(ttype)))
-- if ttype == "TK_EOS" then print(hl) end
-- end
-- print(hl)
-- print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
-- print(hl)
-- print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
-- print(hl)
--------------------------------------------------------------------
-- report warning flags from optimizing process
--------------------------------------------------------------------
if optlex.warn.lstring then
print("* WARNING: "..optlex.warn.lstring)
elseif optlex.warn.mixedeol then
print("* WARNING: ".."output still contains some CRLF or LFCR line endings")
end
print()
return dat
end
--[[--------------------------------------------------------------------
-- main functions
----------------------------------------------------------------------]]
local arg = {...} -- program arguments
local fspec = {}
set_options(DEFAULT_CONFIG) -- set to default options at beginning
------------------------------------------------------------------------
-- per-file handling, ship off to tasks
------------------------------------------------------------------------
local function do_files(fspec)
for _, srcfl in ipairs(fspec) do
local destfl
------------------------------------------------------------------
-- find and replace extension for filenames
------------------------------------------------------------------
local extb, exte = string.find(srcfl, "%.[^%.%\\%/]*$")
local basename, extension = srcfl, ""
if extb and extb > 1 then
basename = sub(srcfl, 1, extb - 1)
extension = sub(srcfl, extb, exte)
end
destfl = basename..suffix..extension
if #fspec == 1 and option.OUTPUT_FILE then
destfl = option.OUTPUT_FILE
end
if srcfl == destfl then
die("output filename identical to input filename")
end
------------------------------------------------------------------
-- perform requested operations
------------------------------------------------------------------
if option.DUMP_LEXER then
dump_tokens(srcfl)
elseif option.DUMP_PARSER then
dump_parser(srcfl)
elseif option.READ_ONLY then
read_only(srcfl)
else
process_file(srcfl, destfl)
end
end--for
end
------------------------------------------------------------------------
-- main function (entry point is after this definition)
------------------------------------------------------------------------
local function main()
local argn, i = #arg, 1
if argn == 0 then
option.HELP = true
end
--------------------------------------------------------------------
-- handle arguments
--------------------------------------------------------------------
while i <= argn do
local o, p = arg[i], arg[i + 1]
local dash = string.match(o, "^%-%-?")
if dash == "-" then -- single-dash options
if o == "-h" then
option.HELP = true; break
elseif o == "-v" then
option.VERSION = true; break
elseif o == "-s" then
if not p then die("-s option needs suffix specification") end
suffix = p
i = i + 1
elseif o == "-o" then
if not p then die("-o option needs a file name") end
option.OUTPUT_FILE = p
i = i + 1
elseif o == "-" then
break -- ignore rest of args
else
die("unrecognized option "..o)
end
elseif dash == "--" then -- double-dash options
if o == "--help" then
option.HELP = true; break
elseif o == "--version" then
option.VERSION = true; break
elseif o == "--keep" then
if not p then die("--keep option needs a string to match for") end
option.KEEP = p
i = i + 1
elseif o == "--plugin" then
if not p then die("--plugin option needs a module name") end
if option.PLUGIN then die("only one plugin can be specified") end
option.PLUGIN = p
plugin = require(PLUGIN_SUFFIX..p)
i = i + 1
elseif o == "--quiet" then
option.QUIET = true
elseif o == "--read-only" then
option.READ_ONLY = true
elseif o == "--basic" then
set_options(BASIC_CONFIG)
elseif o == "--maximum" then
set_options(MAXIMUM_CONFIG)
elseif o == "--none" then
set_options(NONE_CONFIG)
elseif o == "--dump-lexer" then
option.DUMP_LEXER = true
elseif o == "--dump-parser" then
option.DUMP_PARSER = true
elseif o == "--details" then
option.DETAILS = true
elseif OPTION[o] then -- lookup optimization options
set_options(o)
else
die("unrecognized option "..o)
end
else
fspec[#fspec + 1] = o -- potential filename
end
i = i + 1
end--while
if option.HELP then
print(MSG_TITLE..MSG_USAGE); return true
elseif option.VERSION then
print(MSG_TITLE); return true
end
if #fspec > 0 then
if #fspec > 1 and option.OUTPUT_FILE then
die("with -o, only one source file can be specified")
end
do_files(fspec)
return true
else
die("nothing to do!")
end
end
-- entry point -> main() -> do_files()
-- if not main() then
-- die("Please run with option -h or --help for usage information")
-- end
-- end of script
return process_code

View File

@@ -0,0 +1,193 @@
LuaSrcDiet
Compresses Lua source code by removing unnecessary characters.
Copyright (c) 2005-2008 Kein-Hong Man <khman@users.sf.net>
The COPYRIGHT file describes the conditions
under which this software may be distributed.
http://luaforge.net/projects/luasrcdiet/
http://luasrcdiet.luaforge.net/
--
For the older unmaintained version of LuaSrcDiet for Lua 5.0.2 sources,
please see the 5.0/README file.
--
PREVIEW NOTES
See also: http://luasrcdiet.luaforge.net/
The 0.11.0 release of LuaSrcDiet has a local variable name optimizer.
Local variable names are renamed into the shortest possible names. In
addition, variable names are reused whenever possible, reducing the
number of unique variable names. Several hundred local variable names
can be reduced into 53 or less unique names, which allows all locals
to be single-character in length.
The local variable name optimizer uses a full parser of Lua 5.1 source
code, thus it can rename all local variables, including function
parameters. It should handle the implicit "self" parameter gracefully.
The optimizer needs more testing, but is already able to optimize the
LuaSrcDiet sources itself and generate correct Lua output.
String and number token optimizations are also performed, apart from the
usual whitespace, line ending and comment removal. Numbers can switch
between different formats. Strings can be simplified and can switch
delimiters between " or ' characters.
Most options can also be enabled or disabled separately, for maximum
flexibility. If you need to keep a copyright message in the optimized
output, the --keep option can keep block comments that contain a certain
string.
For samples, see the sample/ directory. Performance statistics can be
found in the sample/statistics.txt file. Preliminary test samples for
strings and numbers can also be found in the sample/ directory.
Priority for future work:
(a) automatic tests for lexer/parser optimizations
(b) integrity checking, token stream check or binary chunk check
--
INTRODUCTION
...
WARNING! Locals optimization does NOT have support for 'arg' vararg
functions (LUA_COMPAT_VARARG).
--
WHAT'S NEW
Major changes for version 0.11.2 (see the ChangeLog as well):
* improved local variable name allocation, more efficient now
* added experimental --plugin option with an example plugin script
* added a SLOC plugin to count SLOC for Lua 5.1 source files
* added a HTML plugin to see globals and locals marked
Major changes for version 0.11.1 (see the ChangeLog as well):
* --detail option for more string, number and local variable info
* fixed a local rename bug that generates names that are keywords
* added explanatory notes on local variable optimization
* added --opt-entropy option for locals to reduce symbol entropy
Major changes for version 0.11.0 (see the ChangeLog as well):
* Local variable name optimization.
* Many options and sample output added.
Major changes for version 0.10.2 (see the ChangeLog as well):
* Aggressive optimizations for string and number tokens.
* Minor bug fixes.
Major changes for version 0.10.1 (see the ChangeLog as well):
* Totally rewritten for Lua 5.1.x.
--
USAGE OPTIONS
...
Example of summary data display:
Statistics for: LuaSrcDiet.lua -> sample/LuaSrcDiet.lua
*** local variable optimization summary ***
----------------------------------------------------------
Variable Unique Decl. Token Size Average
Types Names Count Count Bytes Bytes
----------------------------------------------------------
Global 10 0 19 95 5.00
----------------------------------------------------------
Local (in) 88 153 683 3340 4.89
TOTAL (in) 98 153 702 3435 4.89
----------------------------------------------------------
Local (out) 32 153 683 683 1.00
TOTAL (out) 42 153 702 778 1.11
----------------------------------------------------------
*** lexer-based optimizations summary ***
--------------------------------------------------------------------
Lexical Input Input Input Output Output Output
Elements Count Bytes Average Count Bytes Average
--------------------------------------------------------------------
TK_KEYWORD 374 1531 4.09 374 1531 4.09
TK_NAME 795 3963 4.98 795 1306 1.64
TK_NUMBER 54 59 1.09 54 59 1.09
TK_STRING 152 1725 11.35 152 1717 11.30
TK_LSTRING 7 1976 282.29 7 1976 282.29
TK_OP 997 1092 1.10 997 1092 1.10
TK_EOS 1 0 0.00 1 0 0.00
--------------------------------------------------------------------
TK_COMMENT 140 6884 49.17 1 18 18.00
TK_LCOMMENT 7 1723 246.14 0 0 0.00
TK_EOL 543 543 1.00 197 197 1.00
TK_SPACE 1270 2465 1.94 263 263 1.00
--------------------------------------------------------------------
Total Elements 4340 21961 5.06 2841 8159 2.87
--------------------------------------------------------------------
Total Tokens 2380 10346 4.35 2380 7681 3.23
--------------------------------------------------------------------
--
USING LUASRCDIET
...
Please see the command line help or see sample/Makefile for examples.
This is experimental software and nothing has been done yet on a proper
installation scheme for use with normal work. A thousand apologies...
--
CODE SIZE REDUCTION
...
--
OTHER OPTIONS
...
--
BEHAVIOUR NOTES
* embedded line endings in strings and long strings always
normalized to LF
* will not optimize trailing spaces in long strings, only warns
* scientific notation generated in number optimzation is not in
canonical form, this may or may not be a bad thing, so feedback
is welcome
--
ACKNOWLEDGEMENTS
Thanks to the LuaForge people for hosting this.
Developed on SciTE http://www.scintilla.org/. Two thumbs up.
--
FEEDBACK
Feedback and contributions are welcome. Your name will be acknowledged,
as long as you are willing to comply with COPYRIGHT. If your material is
self-contained, you can retain a copyright notice for those material in
your own name, as long as you use the same Lua 5/MIT-style copyright.
My alternative e-mail address is: keinhong AT gmail DOT com
Enjoy!!
Kein-Hong Man (esq.)
Kuala Lumpur
Malaysia 20080603

View File

@@ -0,0 +1,355 @@
--[[--------------------------------------------------------------------
base.llex.lua: Lua 5.1 lexical analyzer in Lua
This file is part of LuaSrcDiet, based on Yueliang material.
Copyright (c) 2008 Kein-Hong Man <khman@users.sf.net>
The COPYRIGHT file describes the conditions
under which this software may be distributed.
See the ChangeLog for more information.
----------------------------------------------------------------------]]
--[[--------------------------------------------------------------------
-- NOTES:
-- * This is a version of the native 5.1.x lexer from Yueliang 0.4.0,
-- with significant modifications to handle LuaSrcDiet's needs:
-- (1) base.llex.error is an optional error function handler
-- (2) base.seminfo for strings include their delimiters and no
-- translation operations are performed on them
-- * ADDED shbang handling has been added to support executable scripts
-- * NO localized decimal point replacement magic
-- * NO limit to number of lines
-- * NO support for compatible long strings (LUA_COMPAT_LSTR)
-- * Please read technotes.txt for more technical details.
----------------------------------------------------------------------]]
local base = {}
-- local base = _G
-- local string = require "string"
-- module "base.llex"
local find = string.find
local match = string.match
local sub = string.sub
----------------------------------------------------------------------
-- initialize keyword list, variables
----------------------------------------------------------------------
local kw = {}
for v in string.gmatch([[
and break do else elseif end false for function if in
local nil not or repeat return then true until while]], "%S+") do
kw[v] = true
end
-- NOTE: see init() for module variables (externally visible):
-- base.tok, base.seminfo, base.tokln
local z, -- source stream
sourceid, -- name of source
I, -- position of lexer
buff, -- buffer for strings
ln -- line number
----------------------------------------------------------------------
-- add information to token listing
----------------------------------------------------------------------
local function addtoken(token, info)
local i = #base.tok + 1
base.tok[i] = token
base.seminfo[i] = info
base.tokln[i] = ln
end
----------------------------------------------------------------------
-- handles line number incrementation and end-of-line characters
----------------------------------------------------------------------
local function inclinenumber(i, is_tok)
local sub = sub
local old = sub(z, i, i)
i = i + 1 -- skip '\n' or '\r'
local c = sub(z, i, i)
if (c == "\n" or c == "\r") and (c ~= old) then
i = i + 1 -- skip '\n\r' or '\r\n'
old = old..c
end
if is_tok then addtoken("TK_EOL", old) end
ln = ln + 1
I = i
return i
end
----------------------------------------------------------------------
-- initialize lexer for given source _z and source name _sourceid
----------------------------------------------------------------------
function base.init(_z, _sourceid)
z = _z -- source
sourceid = _sourceid -- name of source
I = 1 -- lexer's position in source
ln = 1 -- line number
base.tok = {} -- lexed token list*
base.seminfo = {} -- lexed semantic information list*
base.tokln = {} -- line numbers for messages*
-- (*) externally visible thru' module
--------------------------------------------------------------------
-- initial processing (shbang handling)
--------------------------------------------------------------------
local p, _, q, r = find(z, "^(#[^\r\n]*)(\r?\n?)")
if p then -- skip first line
I = I + #q
addtoken("TK_COMMENT", q)
if #r > 0 then inclinenumber(I, true) end
end
end
----------------------------------------------------------------------
-- returns a chunk name or id, no truncation for long names
----------------------------------------------------------------------
function base.chunkid()
if sourceid and match(sourceid, "^[=@]") then
return sub(sourceid, 2) -- remove first char
end
return "[string]"
end
----------------------------------------------------------------------
-- formats error message and throws error
-- * a simplified version, does not report what token was responsible
----------------------------------------------------------------------
function base.errorline(s, line)
local e = error or base.error
e(string.format("%s:%d: %s", base.chunkid(), line or ln, s))
end
------------------------------------------------------------------------
-- count separators ("=") in a long string delimiter
------------------------------------------------------------------------
local function skip_sep(i)
local sub = sub
local s = sub(z, i, i)
i = i + 1
local count = #match(z, "=*", i) -- note, take the length
i = i + count
I = i
return (sub(z, i, i) == s) and count or (-count) - 1
end
----------------------------------------------------------------------
-- reads a long string or long comment
----------------------------------------------------------------------
local function read_long_string(is_str, sep)
local i = I + 1 -- skip 2nd '['
local sub = sub
local c = sub(z, i, i)
if c == "\r" or c == "\n" then -- string starts with a newline?
i = inclinenumber(i) -- skip it
end
local j = i
while true do
local p, q, r = find(z, "([\r\n%]])", i) -- (long range)
if not p then
base.errorline(is_str and "unfinished long string" or
"unfinished long comment")
end
i = p
if r == "]" then -- delimiter test
if skip_sep(i) == sep then
buff = sub(z, buff, I)
I = I + 1 -- skip 2nd ']'
return buff
end
i = I
else -- newline
buff = buff.."\n"
i = inclinenumber(i)
end
end--while
end
----------------------------------------------------------------------
-- reads a string
----------------------------------------------------------------------
local function read_string(del)
local i = I
local find = find
local sub = sub
while true do
local p, q, r = find(z, "([\n\r\\\"\'])", i) -- (long range)
if p then
if r == "\n" or r == "\r" then
base.errorline("unfinished string")
end
i = p
if r == "\\" then -- handle escapes
i = i + 1
r = sub(z, i, i)
if r == "" then break end -- (EOZ error)
p = find("abfnrtv\n\r", r, 1, true)
------------------------------------------------------
if p then -- special escapes
if p > 7 then
i = inclinenumber(i)
else
i = i + 1
end
------------------------------------------------------
elseif find(r, "%D") then -- other non-digits
i = i + 1
------------------------------------------------------
else -- \xxx sequence
local p, q, s = find(z, "^(%d%d?%d?)", i)
i = q + 1
if s + 1 > 256 then -- UCHAR_MAX
base.errorline("escape sequence too large")
end
------------------------------------------------------
end--if p
else
i = i + 1
if r == del then -- ending delimiter
I = i
return sub(z, buff, i - 1) -- return string
end
end--if r
else
break -- (error)
end--if p
end--while
base.errorline("unfinished string")
end
------------------------------------------------------------------------
-- main lexer function
------------------------------------------------------------------------
function base.llex()
local find = find
local match = match
while true do--outer
local i = I
-- inner loop allows break to be used to nicely section tests
while true do--inner
----------------------------------------------------------------
local p, _, r = find(z, "^([_%a][_%w]*)", i)
if p then
I = i + #r
if kw[r] then
addtoken("TK_KEYWORD", r) -- reserved word (keyword)
else
addtoken("TK_NAME", r) -- identifier
end
break -- (continue)
end
----------------------------------------------------------------
local p, _, r = find(z, "^(%.?)%d", i)
if p then -- numeral
if r == "." then i = i + 1 end
local _, q, r = find(z, "^%d*[%.%d]*([eE]?)", i)
i = q + 1
if #r == 1 then -- optional exponent
if match(z, "^[%+%-]", i) then -- optional sign
i = i + 1
end
end
local _, q = find(z, "^[_%w]*", i)
I = q + 1
local v = sub(z, p, q) -- string equivalent
if not tonumber(v) then -- handles hex test also
base.errorline("malformed number")
end
addtoken("TK_NUMBER", v)
break -- (continue)
end
----------------------------------------------------------------
local p, q, r, t = find(z, "^((%s)[ \t\v\f]*)", i)
if p then
if t == "\n" or t == "\r" then -- newline
inclinenumber(i, true)
else
I = q + 1 -- whitespace
addtoken("TK_SPACE", r)
end
break -- (continue)
end
----------------------------------------------------------------
local r = match(z, "^%p", i)
if r then
buff = i
local p = find("-[\"\'.=<>~", r, 1, true)
if p then
-- two-level if block for punctuation/symbols
--------------------------------------------------------
if p <= 2 then
if p == 1 then -- minus
local c = match(z, "^%-%-(%[?)", i)
if c then
i = i + 2
local sep = -1
if c == "[" then
sep = skip_sep(i)
end
if sep >= 0 then -- long comment
addtoken("TK_LCOMMENT", read_long_string(false, sep))
else -- short comment
I = find(z, "[\n\r]", i) or (#z + 1)
addtoken("TK_COMMENT", sub(z, buff, I - 1))
end
break -- (continue)
end
-- (fall through for "-")
else -- [ or long string
local sep = skip_sep(i)
if sep >= 0 then
addtoken("TK_LSTRING", read_long_string(true, sep))
elseif sep == -1 then
addtoken("TK_OP", "[")
else
base.errorline("invalid long string delimiter")
end
break -- (continue)
end
--------------------------------------------------------
elseif p <= 5 then
if p < 5 then -- strings
I = i + 1
addtoken("TK_STRING", read_string(r))
break -- (continue)
end
r = match(z, "^%.%.?%.?", i) -- .|..|... dots
-- (fall through)
--------------------------------------------------------
else -- relational
r = match(z, "^%p=?", i)
-- (fall through)
end
end
I = i + #r
addtoken("TK_OP", r) -- for other symbols, fall through
break -- (continue)
end
----------------------------------------------------------------
local r = sub(z, i, i)
if r ~= "" then
I = i + 1
addtoken("TK_OP", r) -- other single-char tokens
break
end
addtoken("TK_EOS", "") -- end of stream,
return base -- exit here
----------------------------------------------------------------
end--while inner
end--while outer
end
return base

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,837 @@
--[[--------------------------------------------------------------------
optlex.lua: does lexer-based optimizations
This file is part of LuaSrcDiet.
Copyright (c) 2008 Kein-Hong Man <khman@users.sf.net>
The COPYRIGHT file describes the conditions
under which this software may be distributed.
See the ChangeLog for more information.
----------------------------------------------------------------------]]
--[[--------------------------------------------------------------------
-- NOTES:
-- * For more lexer-based optimization ideas, see the TODO items or
-- look at technotes.txt.
-- * TODO: general string delimiter conversion optimizer
-- * TODO: (numbers) warn if overly significant digit
----------------------------------------------------------------------]]
local base = {}
-- local base = _G
-- local string = require "string"
-- module "optlex"
local match = string.match
local sub = string.sub
local find = string.find
local rep = string.rep
local print = print
local tostring = tostring
local tonumber = tonumber
------------------------------------------------------------------------
-- variables and data structures
------------------------------------------------------------------------
-- error function, can override by setting own function into module
local error = error
base.warn = {} -- table for warning flags
local stoks, sinfos, stoklns -- source lists
local is_realtoken = { -- significant (grammar) tokens
TK_KEYWORD = true,
TK_NAME = true,
TK_NUMBER = true,
TK_STRING = true,
TK_LSTRING = true,
TK_OP = true,
TK_EOS = true,
}
local is_faketoken = { -- whitespace (non-grammar) tokens
TK_COMMENT = true,
TK_LCOMMENT = true,
TK_EOL = true,
TK_SPACE = true,
}
local opt_details -- for extra information
------------------------------------------------------------------------
-- true if current token is at the start of a line
-- * skips over deleted tokens via recursion
------------------------------------------------------------------------
local function atlinestart(i)
local tok = stoks[i - 1]
if i <= 1 or tok == "TK_EOL" then
return true
elseif tok == "" then
return atlinestart(i - 1)
end
return false
end
------------------------------------------------------------------------
-- true if current token is at the end of a line
-- * skips over deleted tokens via recursion
------------------------------------------------------------------------
local function atlineend(i)
local tok = stoks[i + 1]
if i >= #stoks or tok == "TK_EOL" or tok == "TK_EOS" then
return true
elseif tok == "" then
return atlineend(i + 1)
end
return false
end
------------------------------------------------------------------------
-- counts comment EOLs inside a long comment
-- * in order to keep line numbering, EOLs need to be reinserted
------------------------------------------------------------------------
local function commenteols(lcomment)
local sep = #match(lcomment, "^%-%-%[=*%[")
local z = sub(lcomment, sep + 1, -(sep - 1)) -- remove delims
local i, c = 1, 0
while true do
local p, q, r, s = find(z, "([\r\n])([\r\n]?)", i)
if not p then break end -- if no matches, done
i = p + 1
c = c + 1
if #s > 0 and r ~= s then -- skip CRLF or LFCR
i = i + 1
end
end
return c
end
------------------------------------------------------------------------
-- compares two tokens (i, j) and returns the whitespace required
-- * important! see technotes.txt for more information
-- * only two grammar/real tokens are being considered
-- * if "", no separation is needed
-- * if " ", then at least one whitespace (or EOL) is required
------------------------------------------------------------------------
local function checkpair(i, j)
local match = match
local t1, t2 = stoks[i], stoks[j]
--------------------------------------------------------------------
if t1 == "TK_STRING" or t1 == "TK_LSTRING" or
t2 == "TK_STRING" or t2 == "TK_LSTRING" then
return ""
--------------------------------------------------------------------
elseif t1 == "TK_OP" or t2 == "TK_OP" then
if (t1 == "TK_OP" and (t2 == "TK_KEYWORD" or t2 == "TK_NAME")) or
(t2 == "TK_OP" and (t1 == "TK_KEYWORD" or t1 == "TK_NAME")) then
return ""
end
if t1 == "TK_OP" and t2 == "TK_OP" then
-- for TK_OP/TK_OP pairs, see notes in technotes.txt
local op, op2 = sinfos[i], sinfos[j]
if (match(op, "^%.%.?$") and match(op2, "^%.")) or
(match(op, "^[~=<>]$") and op2 == "=") or
(op == "[" and (op2 == "[" or op2 == "=")) then
return " "
end
return ""
end
-- "TK_OP" + "TK_NUMBER" case
local op = sinfos[i]
if t2 == "TK_OP" then op = sinfos[j] end
if match(op, "^%.%.?%.?$") then
return " "
end
return ""
--------------------------------------------------------------------
else-- "TK_KEYWORD" | "TK_NAME" | "TK_NUMBER" then
return " "
--------------------------------------------------------------------
end
end
------------------------------------------------------------------------
-- repack tokens, removing deletions caused by optimization process
------------------------------------------------------------------------
local function repack_tokens()
local dtoks, dinfos, dtoklns = {}, {}, {}
local j = 1
for i = 1, #stoks do
local tok = stoks[i]
if tok ~= "" then
dtoks[j], dinfos[j], dtoklns[j] = tok, sinfos[i], stoklns[i]
j = j + 1
end
end
stoks, sinfos, stoklns = dtoks, dinfos, dtoklns
end
------------------------------------------------------------------------
-- number optimization
-- * optimization using string formatting functions is one way of doing
-- this, but here, we consider all cases and handle them separately
-- (possibly an idiotic approach...)
-- * scientific notation being generated is not in canonical form, this
-- may or may not be a bad thing, feedback welcome
-- * note: intermediate portions need to fit into a normal number range
-- * optimizations can be divided based on number patterns:
-- * hexadecimal:
-- (1) no need to remove leading zeros, just skip to (2)
-- (2) convert to integer if size equal or smaller
-- * change if equal size -> lose the 'x' to reduce entropy
-- (3) number is then processed as an integer
-- (4) note: does not make 0[xX] consistent
-- * integer:
-- (1) note: includes anything with trailing ".", ".0", ...
-- (2) remove useless fractional part, if present, e.g. 123.000
-- (3) remove leading zeros, e.g. 000123
-- (4) switch to scientific if shorter, e.g. 123000 -> 123e3
-- * with fraction:
-- (1) split into digits dot digits
-- (2) if no integer portion, take as zero (can omit later)
-- (3) handle degenerate .000 case, after which the fractional part
-- must be non-zero (if zero, it's matched as an integer)
-- (4) remove trailing zeros for fractional portion
-- (5) p.q where p > 0 and q > 0 cannot be shortened any more
-- (6) otherwise p == 0 and the form is .q, e.g. .000123
-- (7) if scientific shorter, convert, e.g. .000123 -> 123e-6
-- * scientific:
-- (1) split into (digits dot digits) [eE] ([+-] digits)
-- (2) if significand has ".", shift it out so it becomes an integer
-- (3) if significand is zero, just use zero
-- (4) remove leading zeros for significand
-- (5) shift out trailing zeros for significand
-- (6) examine exponent and determine which format is best:
-- integer, with fraction, scientific
------------------------------------------------------------------------
local function do_number(i)
local before = sinfos[i] -- 'before'
local z = before -- working representation
local y -- 'after', if better
--------------------------------------------------------------------
if match(z, "^0[xX]") then -- hexadecimal number
local v = tostring(tonumber(z))
if #v <= #z then
z = v -- change to integer, AND continue
else
return -- no change; stick to hex
end
end
--------------------------------------------------------------------
if match(z, "^%d+%.?0*$") then -- integer or has useless frac
z = match(z, "^(%d+)%.?0*$") -- int portion only
if z + 0 > 0 then
z = match(z, "^0*([1-9]%d*)$") -- remove leading zeros
local v = #match(z, "0*$")
local nv = tostring(v)
if v > #nv + 1 then -- scientific is shorter
z = sub(z, 1, #z - v).."e"..nv
end
y = z
else
y = "0" -- basic zero
end
--------------------------------------------------------------------
elseif not match(z, "[eE]") then -- number with fraction part
local p, q = match(z, "^(%d*)%.(%d+)$") -- split
if p == "" then p = 0 end -- int part zero
if q + 0 == 0 and p == 0 then
y = "0" -- degenerate .000 case
else
-- now, q > 0 holds and p is a number
local v = #match(q, "0*$") -- remove trailing zeros
if v > 0 then
q = sub(q, 1, #q - v)
end
-- if p > 0, nothing else we can do to simplify p.q case
if p + 0 > 0 then
y = p.."."..q
else
y = "."..q -- tentative, e.g. .000123
local v = #match(q, "^0*") -- # leading spaces
local w = #q - v -- # significant digits
local nv = tostring(#q)
-- e.g. compare 123e-6 versus .000123
if w + 2 + #nv < 1 + #q then
y = sub(q, -w).."e-"..nv
end
end
end
--------------------------------------------------------------------
else -- scientific number
local sig, ex = match(z, "^([^eE]+)[eE]([%+%-]?%d+)$")
ex = tonumber(ex)
-- if got ".", shift out fractional portion of significand
local p, q = match(sig, "^(%d*)%.(%d*)$")
if p then
ex = ex - #q
sig = p..q
end
if sig + 0 == 0 then
y = "0" -- basic zero
else
local v = #match(sig, "^0*") -- remove leading zeros
sig = sub(sig, v + 1)
v = #match(sig, "0*$") -- shift out trailing zeros
if v > 0 then
sig = sub(sig, 1, #sig - v)
ex = ex + v
end
-- examine exponent and determine which format is best
local nex = tostring(ex)
if ex == 0 then -- it's just an integer
y = sig
elseif ex > 0 and (ex <= 1 + #nex) then -- a number
y = sig..rep("0", ex)
elseif ex < 0 and (ex >= -#sig) then -- fraction, e.g. .123
v = #sig + ex
y = sub(sig, 1, v).."."..sub(sig, v + 1)
elseif ex < 0 and (#nex >= -ex - #sig) then
-- e.g. compare 1234e-5 versus .01234
-- gives: #sig + 1 + #nex >= 1 + (-ex - #sig) + #sig
-- -> #nex >= -ex - #sig
v = -ex - #sig
y = "."..rep("0", v)..sig
else -- non-canonical scientific representation
y = sig.."e"..ex
end
end--if sig
end
--------------------------------------------------------------------
if y and y ~= sinfos[i] then
if opt_details then
print("<number> (line "..stoklns[i]..") "..sinfos[i].." -> "..y)
opt_details = opt_details + 1
end
sinfos[i] = y
end
end
------------------------------------------------------------------------
-- string optimization
-- * note: works on well-formed strings only!
-- * optimizations on characters can be summarized as follows:
-- \a\b\f\n\r\t\v -- no change
-- \\ -- no change
-- \"\' -- depends on delim, other can remove \
-- \[\] -- remove \
-- \<char> -- general escape, remove \
-- \<eol> -- normalize the EOL only
-- \ddd -- if \a\b\f\n\r\t\v, change to latter
-- if other < ascii 32, keep ddd but zap leading zeros
-- if >= ascii 32, translate it into the literal, then also
-- do escapes for \\,\",\' cases
-- <other> -- no change
-- * switch delimiters if string becomes shorter
------------------------------------------------------------------------
local function do_string(I)
local info = sinfos[I]
local delim = sub(info, 1, 1) -- delimiter used
local ndelim = (delim == "'") and '"' or "'" -- opposite " <-> '
local z = sub(info, 2, -2) -- actual string
local i = 1
local c_delim, c_ndelim = 0, 0 -- "/' counts
--------------------------------------------------------------------
while i <= #z do
local c = sub(z, i, i)
----------------------------------------------------------------
if c == "\\" then -- escaped stuff
local j = i + 1
local d = sub(z, j, j)
local p = find("abfnrtv\\\n\r\"\'0123456789", d, 1, true)
------------------------------------------------------------
if not p then -- \<char> -- remove \
z = sub(z, 1, i - 1)..sub(z, j)
i = i + 1
------------------------------------------------------------
elseif p <= 8 then -- \a\b\f\n\r\t\v\\
i = i + 2 -- no change
------------------------------------------------------------
elseif p <= 10 then -- \<eol> -- normalize EOL
local eol = sub(z, j, j + 1)
if eol == "\r\n" or eol == "\n\r" then
z = sub(z, 1, i).."\n"..sub(z, j + 2)
elseif p == 10 then -- \r case
z = sub(z, 1, i).."\n"..sub(z, j + 1)
end
i = i + 2
------------------------------------------------------------
elseif p <= 12 then -- \"\' -- remove \ for ndelim
if d == delim then
c_delim = c_delim + 1
i = i + 2
else
c_ndelim = c_ndelim + 1
z = sub(z, 1, i - 1)..sub(z, j)
i = i + 1
end
------------------------------------------------------------
else -- \ddd -- various steps
local s = match(z, "^(%d%d?%d?)", j)
j = i + 1 + #s -- skip to location
local cv = s + 0
local cc = string.char(cv)
local p = find("\a\b\f\n\r\t\v", cc, 1, true)
if p then -- special escapes
s = "\\"..sub("abfnrtv", p, p)
elseif cv < 32 then -- normalized \ddd
s = "\\"..cv
elseif cc == delim then -- \<delim>
s = "\\"..cc
c_delim = c_delim + 1
elseif cc == "\\" then -- \\
s = "\\\\"
else -- literal character
s = cc
if cc == ndelim then
c_ndelim = c_ndelim + 1
end
end
z = sub(z, 1, i - 1)..s..sub(z, j)
i = i + #s
------------------------------------------------------------
end--if p
----------------------------------------------------------------
else-- c ~= "\\" -- <other> -- no change
i = i + 1
if c == ndelim then -- count ndelim, for switching delimiters
c_ndelim = c_ndelim + 1
end
----------------------------------------------------------------
end--if c
end--while
--------------------------------------------------------------------
-- switching delimiters, a long-winded derivation:
-- (1) delim takes 2+2*c_delim bytes, ndelim takes c_ndelim bytes
-- (2) delim becomes c_delim bytes, ndelim becomes 2+2*c_ndelim bytes
-- simplifying the condition (1)>(2) --> c_delim > c_ndelim
if c_delim > c_ndelim then
i = 1
while i <= #z do
local p, q, r = find(z, "([\'\"])", i)
if not p then break end
if r == delim then -- \<delim> -> <delim>
z = sub(z, 1, p - 2)..sub(z, p)
i = p
else-- r == ndelim -- <ndelim> -> \<ndelim>
z = sub(z, 1, p - 1).."\\"..sub(z, p)
i = p + 2
end
end--while
delim = ndelim -- actually change delimiters
end
--------------------------------------------------------------------
z = delim..z..delim
if z ~= sinfos[I] then
if opt_details then
print("<string> (line "..stoklns[I]..") "..sinfos[I].." -> "..z)
opt_details = opt_details + 1
end
sinfos[I] = z
end
end
------------------------------------------------------------------------
-- long string optimization
-- * note: warning flagged if trailing whitespace found, not trimmed
-- * remove first optional newline
-- * normalize embedded newlines
-- * reduce '=' separators in delimiters if possible
------------------------------------------------------------------------
local function do_lstring(I)
local info = sinfos[I]
local delim1 = match(info, "^%[=*%[") -- cut out delimiters
local sep = #delim1
local delim2 = sub(info, -sep, -1)
local z = sub(info, sep + 1, -(sep + 1)) -- lstring without delims
local y = ""
local i = 1
--------------------------------------------------------------------
while true do
local p, q, r, s = find(z, "([\r\n])([\r\n]?)", i)
-- deal with a single line
local ln
if not p then
ln = sub(z, i)
elseif p >= i then
ln = sub(z, i, p - 1)
end
if ln ~= "" then
-- flag a warning if there are trailing spaces, won't base.optimize!
if match(ln, "%s+$") then
warn.lstring = "trailing whitespace in long string near line "..stoklns[I]
end
y = y..ln
end
if not p then -- done if no more EOLs
break
end
-- deal with line endings, normalize them
i = p + 1
if p then
if #s > 0 and r ~= s then -- skip CRLF or LFCR
i = i + 1
end
-- skip first newline, which can be safely deleted
if not(i == 1 and i == p) then
y = y.."\n"
end
end
end--while
--------------------------------------------------------------------
-- handle possible deletion of one or more '=' separators
if sep >= 3 then
local chk, okay = sep - 1
-- loop to test ending delimiter with less of '=' down to zero
while chk >= 2 do
local delim = "%]"..rep("=", chk - 2).."%]"
if not match(y, delim) then okay = chk end
chk = chk - 1
end
if okay then -- change delimiters
sep = rep("=", okay - 2)
delim1, delim2 = "["..sep.."[", "]"..sep.."]"
end
end
--------------------------------------------------------------------
sinfos[I] = delim1..y..delim2
end
------------------------------------------------------------------------
-- long comment optimization
-- * note: does not remove first optional newline
-- * trim trailing whitespace
-- * normalize embedded newlines
-- * reduce '=' separators in delimiters if possible
------------------------------------------------------------------------
local function do_lcomment(I)
local info = sinfos[I]
local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters
local sep = #delim1
local delim2 = sub(info, -sep, -1)
local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims
local y = ""
local i = 1
--------------------------------------------------------------------
while true do
local p, q, r, s = find(z, "([\r\n])([\r\n]?)", i)
-- deal with a single line, extract and check trailing whitespace
local ln
if not p then
ln = sub(z, i)
elseif p >= i then
ln = sub(z, i, p - 1)
end
if ln ~= "" then
-- trim trailing whitespace if non-empty line
local ws = match(ln, "%s*$")
if #ws > 0 then ln = sub(ln, 1, -(ws + 1)) end
y = y..ln
end
if not p then -- done if no more EOLs
break
end
-- deal with line endings, normalize them
i = p + 1
if p then
if #s > 0 and r ~= s then -- skip CRLF or LFCR
i = i + 1
end
y = y.."\n"
end
end--while
--------------------------------------------------------------------
-- handle possible deletion of one or more '=' separators
sep = sep - 2
if sep >= 3 then
local chk, okay = sep - 1
-- loop to test ending delimiter with less of '=' down to zero
while chk >= 2 do
local delim = "%]"..rep("=", chk - 2).."%]"
if not match(y, delim) then okay = chk end
chk = chk - 1
end
if okay then -- change delimiters
sep = rep("=", okay - 2)
delim1, delim2 = "--["..sep.."[", "]"..sep.."]"
end
end
--------------------------------------------------------------------
sinfos[I] = delim1..y..delim2
end
------------------------------------------------------------------------
-- short comment optimization
-- * trim trailing whitespace
------------------------------------------------------------------------
local function do_comment(i)
local info = sinfos[i]
local ws = match(info, "%s*$") -- just look from end of string
if #ws > 0 then
info = sub(info, 1, -(ws + 1)) -- trim trailing whitespace
end
sinfos[i] = info
end
------------------------------------------------------------------------
-- returns true if string found in long comment
-- * this is a feature to keep copyright or license texts
------------------------------------------------------------------------
local function keep_lcomment(opt_keep, info)
if not opt_keep then return false end -- option not set
local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters
local sep = #delim1
local delim2 = sub(info, -sep, -1)
local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims
if find(z, opt_keep, 1, true) then -- try to match
return true
end
end
------------------------------------------------------------------------
-- main entry point
-- * currently, lexer processing has 2 passes
-- * processing is done on a line-oriented basis, which is easier to
-- grok due to the next point...
-- * since there are various options that can be enabled or disabled,
-- processing is a little messy or convoluted
------------------------------------------------------------------------
function base.optimize(option, toklist, semlist, toklnlist)
--------------------------------------------------------------------
-- set option flags
--------------------------------------------------------------------
local opt_comments = option["opt-comments"]
local opt_whitespace = option["opt-whitespace"]
local opt_emptylines = option["opt-emptylines"]
local opt_eols = option["opt-eols"]
local opt_strings = option["opt-strings"]
local opt_numbers = option["opt-numbers"]
local opt_keep = option.KEEP
opt_details = option.DETAILS and 0 -- upvalues for details display
print = print or base.print
if opt_eols then -- forced settings, otherwise won't work properly
opt_comments = true
opt_whitespace = true
opt_emptylines = true
end
--------------------------------------------------------------------
-- variable initialization
--------------------------------------------------------------------
stoks, sinfos, stoklns -- set source lists
= toklist, semlist, toklnlist
local i = 1 -- token position
local tok, info -- current token
local prev -- position of last grammar token
-- on same line (for TK_SPACE stuff)
--------------------------------------------------------------------
-- changes a token, info pair
--------------------------------------------------------------------
local function settoken(tok, info, I)
I = I or i
stoks[I] = tok or ""
sinfos[I] = info or ""
end
--------------------------------------------------------------------
-- processing loop (PASS 1)
--------------------------------------------------------------------
while true do
tok, info = stoks[i], sinfos[i]
----------------------------------------------------------------
local atstart = atlinestart(i) -- set line begin flag
if atstart then prev = nil end
----------------------------------------------------------------
if tok == "TK_EOS" then -- end of stream/pass
break
----------------------------------------------------------------
elseif tok == "TK_KEYWORD" or -- keywords, identifiers,
tok == "TK_NAME" or -- operators
tok == "TK_OP" then
-- TK_KEYWORD and TK_OP can't be optimized without a big
-- optimization framework; it would be more of an optimizing
-- compiler, not a source code compressor
-- TK_NAME that are locals needs parser to analyze/base.optimize
prev = i
----------------------------------------------------------------
elseif tok == "TK_NUMBER" then -- numbers
if opt_numbers then
do_number(i) -- base.optimize
end
prev = i
----------------------------------------------------------------
elseif tok == "TK_STRING" or -- strings, long strings
tok == "TK_LSTRING" then
if opt_strings then
if tok == "TK_STRING" then
do_string(i) -- base.optimize
else
do_lstring(i) -- base.optimize
end
end
prev = i
----------------------------------------------------------------
elseif tok == "TK_COMMENT" then -- short comments
if opt_comments then
if i == 1 and sub(info, 1, 1) == "#" then
-- keep shbang comment, trim whitespace
do_comment(i)
else
-- safe to delete, as a TK_EOL (or TK_EOS) always follows
settoken() -- remove entirely
end
elseif opt_whitespace then -- trim whitespace only
do_comment(i)
end
----------------------------------------------------------------
elseif tok == "TK_LCOMMENT" then -- long comments
if keep_lcomment(opt_keep, info) then
------------------------------------------------------------
-- if --keep, we keep a long comment if <msg> is found;
-- this is a feature to keep copyright or license texts
if opt_whitespace then -- trim whitespace only
do_lcomment(i)
end
prev = i
elseif opt_comments then
local eols = commenteols(info)
------------------------------------------------------------
-- prepare opt_emptylines case first, if a disposable token
-- follows, current one is safe to dump, else keep a space;
-- it is implied that the operation is safe for '-', because
-- current is a TK_LCOMMENT, and must be separate from a '-'
if is_faketoken[stoks[i + 1]] then
settoken() -- remove entirely
tok = ""
else
settoken("TK_SPACE", " ")
end
------------------------------------------------------------
-- if there are embedded EOLs to keep and opt_emptylines is
-- disabled, then switch the token into one or more EOLs
if not opt_emptylines and eols > 0 then
settoken("TK_EOL", rep("\n", eols))
end
------------------------------------------------------------
-- if optimizing whitespaces, force reinterpretation of the
-- token to give a chance for the space to be optimized away
if opt_whitespace and tok ~= "" then
i = i - 1 -- to reinterpret
end
------------------------------------------------------------
else -- disabled case
if opt_whitespace then -- trim whitespace only
do_lcomment(i)
end
prev = i
end
----------------------------------------------------------------
elseif tok == "TK_EOL" then -- line endings
if atstart and opt_emptylines then
settoken() -- remove entirely
elseif info == "\r\n" or info == "\n\r" then
-- normalize the rest of the EOLs for CRLF/LFCR only
-- (note that TK_LCOMMENT can change into several EOLs)
settoken("TK_EOL", "\n")
end
----------------------------------------------------------------
elseif tok == "TK_SPACE" then -- whitespace
if opt_whitespace then
if atstart or atlineend(i) then
-- delete leading and trailing whitespace
settoken() -- remove entirely
else
------------------------------------------------------------
-- at this point, since leading whitespace have been removed,
-- there should be a either a real token or a TK_LCOMMENT
-- prior to hitting this whitespace; the TK_LCOMMENT case
-- only happens if opt_comments is disabled; so prev ~= nil
local ptok = stoks[prev]
if ptok == "TK_LCOMMENT" then
-- previous TK_LCOMMENT can abut with anything
settoken() -- remove entirely
else
-- prev must be a grammar token; consecutive TK_SPACE
-- tokens is impossible when optimizing whitespace
local ntok = stoks[i + 1]
if is_faketoken[ntok] then
-- handle special case where a '-' cannot abut with
-- either a short comment or a long comment
if (ntok == "TK_COMMENT" or ntok == "TK_LCOMMENT") and
ptok == "TK_OP" and sinfos[prev] == "-" then
-- keep token
else
settoken() -- remove entirely
end
else--is_realtoken
-- check a pair of grammar tokens, if can abut, then
-- delete space token entirely, otherwise keep one space
local s = checkpair(prev, i + 1)
if s == "" then
settoken() -- remove entirely
else
settoken("TK_SPACE", " ")
end
end
end
------------------------------------------------------------
end
end
----------------------------------------------------------------
else
error("unidentified token encountered")
end
----------------------------------------------------------------
i = i + 1
end--while
repack_tokens()
--------------------------------------------------------------------
-- processing loop (PASS 2)
--------------------------------------------------------------------
if opt_eols then
i = 1
-- aggressive EOL removal only works with most non-grammar tokens
-- optimized away because it is a rather simple scheme -- basically
-- it just checks 'real' token pairs around EOLs
if stoks[1] == "TK_COMMENT" then
-- first comment still existing must be shbang, skip whole line
i = 3
end
while true do
tok, info = stoks[i], sinfos[i]
--------------------------------------------------------------
if tok == "TK_EOS" then -- end of stream/pass
break
--------------------------------------------------------------
elseif tok == "TK_EOL" then -- consider each TK_EOL
local t1, t2 = stoks[i - 1], stoks[i + 1]
if is_realtoken[t1] and is_realtoken[t2] then -- sanity check
local s = checkpair(i - 1, i + 1)
if s == "" then
settoken() -- remove entirely
end
end
end--if tok
--------------------------------------------------------------
i = i + 1
end--while
repack_tokens()
end
--------------------------------------------------------------------
if opt_details and opt_details > 0 then print() end -- spacing
return stoks, sinfos, stoklns
end
return base

View File

@@ -0,0 +1,566 @@
--[[--------------------------------------------------------------------
optparser.lua: does parser-based optimizations
This file is part of LuaSrcDiet.
Copyright (c) 2008 Kein-Hong Man <khman@users.sf.net>
The COPYRIGHT file describes the conditions
under which this software may be distributed.
See the ChangeLog for more information.
----------------------------------------------------------------------]]
--[[--------------------------------------------------------------------
-- NOTES:
-- * For more parser-based optimization ideas, see the TODO items or
-- look at technotes.txt.
-- * The processing load is quite significant, but since this is an
-- off-line text processor, I believe we can wait a few seconds.
-- * TODO: might process "local a,a,a" wrongly... need tests!
-- * TODO: remove position handling if overlapped locals (rem < 0)
-- needs more study, to check behaviour
-- * TODO: there are probably better ways to do allocation, e.g. by
-- choosing better methods to sort and pick locals...
-- * TODO: we don't need 53*63 two-letter identifiers; we can make
-- do with significantly less depending on how many that are really
-- needed and improve entropy; e.g. 13 needed -> choose 4*4 instead
----------------------------------------------------------------------]]
local base = {}
-- local base = _G
-- local string = require "string"
-- local table = require "table"
-- module "optparser"
----------------------------------------------------------------------
-- Letter frequencies for reducing symbol entropy (fixed version)
-- * Might help a wee bit when the output file is compressed
-- * See Wikipedia: http://en.wikipedia.org/wiki/Letter_frequencies
-- * We use letter frequencies according to a Linotype keyboard, plus
-- the underscore, and both lower case and upper case letters.
-- * The arrangement below (LC, underscore, %d, UC) is arbitrary.
-- * This is certainly not optimal, but is quick-and-dirty and the
-- process has no significant overhead
----------------------------------------------------------------------
local LETTERS = "etaoinshrdlucmfwypvbgkqjxz_ETAOINSHRDLUCMFWYPVBGKQJXZ"
local ALPHANUM = "etaoinshrdlucmfwypvbgkqjxz_0123456789ETAOINSHRDLUCMFWYPVBGKQJXZ"
-- names or identifiers that must be skipped
-- * the first two lines are for keywords
local SKIP_NAME = {}
for v in string.gmatch([[
and break do else elseif end false for function if in
local nil not or repeat return then true until while
self]], "%S+") do
SKIP_NAME[v] = true
end
------------------------------------------------------------------------
-- variables and data structures
------------------------------------------------------------------------
local toklist, seminfolist, -- token lists
globalinfo, localinfo, -- variable information tables
globaluniq, localuniq, -- unique name tables
var_new, -- index of new variable names
varlist -- list of output variables
----------------------------------------------------------------------
-- preprocess information table to get lists of unique names
----------------------------------------------------------------------
local function preprocess(infotable)
local uniqtable = {}
for i = 1, #infotable do -- enumerate info table
local obj = infotable[i]
local name = obj.name
--------------------------------------------------------------------
if not uniqtable[name] then -- not found, start an entry
uniqtable[name] = {
decl = 0, token = 0, size = 0,
}
end
--------------------------------------------------------------------
local uniq = uniqtable[name] -- count declarations, tokens, size
uniq.decl = uniq.decl + 1
local xref = obj.xref
local xcount = #xref
uniq.token = uniq.token + xcount
uniq.size = uniq.size + xcount * #name
--------------------------------------------------------------------
if obj.decl then -- if local table, create first,last pairs
obj.id = i
obj.xcount = xcount
if xcount > 1 then -- if ==1, means local never accessed
obj.first = xref[2]
obj.last = xref[xcount]
end
--------------------------------------------------------------------
else -- if global table, add a back ref
uniq.id = i
end
--------------------------------------------------------------------
end--for
return uniqtable
end
----------------------------------------------------------------------
-- calculate actual symbol frequencies, in order to reduce entropy
-- * this may help further reduce the size of compressed sources
-- * note that since parsing optimizations is put before lexing
-- optimizations, the frequency table is not exact!
-- * yes, this will miss --keep block comments too...
----------------------------------------------------------------------
local function recalc_for_entropy(option)
local byte = string.byte
local char = string.char
-- table of token classes to accept in calculating symbol frequency
local ACCEPT = {
TK_KEYWORD = true, TK_NAME = true, TK_NUMBER = true,
TK_STRING = true, TK_LSTRING = true,
}
if not option["opt-comments"] then
ACCEPT.TK_COMMENT = true
ACCEPT.TK_LCOMMENT = true
end
--------------------------------------------------------------------
-- create a new table and remove any original locals by filtering
--------------------------------------------------------------------
local filtered = {}
for i = 1, #toklist do
filtered[i] = seminfolist[i]
end
for i = 1, #localinfo do -- enumerate local info table
local obj = localinfo[i]
local xref = obj.xref
for j = 1, obj.xcount do
local p = xref[j]
filtered[p] = "" -- remove locals
end
end
--------------------------------------------------------------------
local freq = {} -- reset symbol frequency table
for i = 0, 255 do freq[i] = 0 end
for i = 1, #toklist do -- gather symbol frequency
local tok, info = toklist[i], filtered[i]
if ACCEPT[tok] then
for j = 1, #info do
local c = byte(info, j)
freq[c] = freq[c] + 1
end
end--if
end--for
--------------------------------------------------------------------
-- function to re-sort symbols according to actual frequencies
--------------------------------------------------------------------
local function resort(symbols)
local symlist = {}
for i = 1, #symbols do -- prepare table to sort
local c = byte(symbols, i)
symlist[i] = { c = c, freq = freq[c], }
end
table.sort(symlist, -- sort selected symbols
function(v1, v2)
return v1.freq > v2.freq
end
)
local charlist = {} -- reconstitute the string
for i = 1, #symlist do
charlist[i] = char(symlist[i].c)
end
return table.concat(charlist)
end
--------------------------------------------------------------------
LETTERS = resort(LETTERS) -- change letter arrangement
ALPHANUM = resort(ALPHANUM)
end
----------------------------------------------------------------------
-- returns a string containing a new local variable name to use, and
-- a flag indicating whether it collides with a global variable
-- * trapping keywords and other names like 'self' is done elsewhere
----------------------------------------------------------------------
local function new_var_name()
local var
local cletters, calphanum = #LETTERS, #ALPHANUM
local v = var_new
if v < cletters then -- single char
v = v + 1
var = string.sub(LETTERS, v, v)
else -- longer names
local range, sz = cletters, 1 -- calculate # chars fit
repeat
v = v - range
range = range * calphanum
sz = sz + 1
until range > v
local n = v % cletters -- left side cycles faster
v = (v - n) / cletters -- do first char first
n = n + 1
var = string.sub(LETTERS, n, n)
while sz > 1 do
local m = v % calphanum
v = (v - m) / calphanum
m = m + 1
var = var..string.sub(ALPHANUM, m, m)
sz = sz - 1
end
end
var_new = var_new + 1
return var, globaluniq[var] ~= nil
end
----------------------------------------------------------------------
-- calculate and print some statistics
-- * probably better in main source, put here for now
----------------------------------------------------------------------
local function stats_summary(globaluniq, localuniq, afteruniq, option)
local fmt = string.format
local opt_details = option.DETAILS
local uniq_g , uniq_li, uniq_lo, uniq_ti, uniq_to, -- stats needed
decl_g, decl_li, decl_lo, decl_ti, decl_to,
token_g, token_li, token_lo, token_ti, token_to,
size_g, size_li, size_lo, size_ti, size_to
= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
local function avg(c, l) -- safe average function
if c == 0 then return 0 end
return l / c
end
--------------------------------------------------------------------
-- collect statistics (note: globals do not have declarations!)
--------------------------------------------------------------------
for name, uniq in pairs(globaluniq) do
uniq_g = uniq_g + 1
token_g = token_g + uniq.token
size_g = size_g + uniq.size
end
for name, uniq in pairs(localuniq) do
uniq_li = uniq_li + 1
decl_li = decl_li + uniq.decl
token_li = token_li + uniq.token
size_li = size_li + uniq.size
end
for name, uniq in pairs(afteruniq) do
uniq_lo = uniq_lo + 1
decl_lo = decl_lo + uniq.decl
token_lo = token_lo + uniq.token
size_lo = size_lo + uniq.size
end
uniq_ti = uniq_g + uniq_li
decl_ti = decl_g + decl_li
token_ti = token_g + token_li
size_ti = size_g + size_li
uniq_to = uniq_g + uniq_lo
decl_to = decl_g + decl_lo
token_to = token_g + token_lo
size_to = size_g + size_lo
--------------------------------------------------------------------
-- detailed stats: global list
--------------------------------------------------------------------
if opt_details then
local sorted = {} -- sort table of unique global names by size
for name, uniq in pairs(globaluniq) do
uniq.name = name
sorted[#sorted + 1] = uniq
end
table.sort(sorted,
function(v1, v2)
return v1.size > v2.size
end
)
local tabf1, tabf2 = "%8s%8s%10s %s", "%8d%8d%10.2f %s"
local hl = string.rep("-", 44)
print("*** global variable list (sorted by size) ***\n"..hl)
print(fmt(tabf1, "Token", "Input", "Input", "Global"))
print(fmt(tabf1, "Count", "Bytes", "Average", "Name"))
print(hl)
for i = 1, #sorted do
local uniq = sorted[i]
print(fmt(tabf2, uniq.token, uniq.size, avg(uniq.token, uniq.size), uniq.name))
end
print(hl)
print(fmt(tabf2, token_g, size_g, avg(token_g, size_g), "TOTAL"))
print(hl.."\n")
--------------------------------------------------------------------
-- detailed stats: local list
--------------------------------------------------------------------
local tabf1, tabf2 = "%8s%8s%8s%10s%8s%10s %s", "%8d%8d%8d%10.2f%8d%10.2f %s"
local hl = string.rep("-", 70)
print("*** local variable list (sorted by allocation order) ***\n"..hl)
print(fmt(tabf1, "Decl.", "Token", "Input", "Input", "Output", "Output", "Global"))
print(fmt(tabf1, "Count", "Count", "Bytes", "Average", "Bytes", "Average", "Name"))
print(hl)
for i = 1, #varlist do -- iterate according to order assigned
local name = varlist[i]
local uniq = afteruniq[name]
local old_t, old_s = 0, 0
for j = 1, #localinfo do -- find corresponding old names and calculate
local obj = localinfo[j]
if obj.name == name then
old_t = old_t + obj.xcount
old_s = old_s + obj.xcount * #obj.oldname
end
end
print(fmt(tabf2, uniq.decl, uniq.token, old_s, avg(old_t, old_s),
uniq.size, avg(uniq.token, uniq.size), name))
end
print(hl)
print(fmt(tabf2, decl_lo, token_lo, size_li, avg(token_li, size_li),
size_lo, avg(token_lo, size_lo), "TOTAL"))
print(hl.."\n")
end--if opt_details
--------------------------------------------------------------------
-- display output
--------------------------------------------------------------------
-- local tabf1, tabf2 = "%-16s%8s%8s%8s%8s%10s", "%-16s%8d%8d%8d%8d%10.2f"
-- local hl = string.rep("-", 58)
-- print("*** local variable optimization summary ***\n"..hl)
-- print(fmt(tabf1, "Variable", "Unique", "Decl.", "Token", "Size", "Average"))
-- print(fmt(tabf1, "Types", "Names", "Count", "Count", "Bytes", "Bytes"))
-- print(hl)
-- print(fmt(tabf2, "Global", uniq_g, decl_g, token_g, size_g, avg(token_g, size_g)))
-- print(hl)
-- print(fmt(tabf2, "Local (in)", uniq_li, decl_li, token_li, size_li, avg(token_li, size_li)))
-- print(fmt(tabf2, "TOTAL (in)", uniq_ti, decl_ti, token_ti, size_ti, avg(token_ti, size_ti)))
-- print(hl)
-- print(fmt(tabf2, "Local (out)", uniq_lo, decl_lo, token_lo, size_lo, avg(token_lo, size_lo)))
-- print(fmt(tabf2, "TOTAL (out)", uniq_to, decl_to, token_to, size_to, avg(token_to, size_to)))
-- print(hl.."\n")
end
----------------------------------------------------------------------
-- main entry point
-- * does only local variable optimization for now
----------------------------------------------------------------------
function base.optimize(option, _toklist, _seminfolist, _globalinfo, _localinfo)
-- set tables
toklist, seminfolist, globalinfo, localinfo
= _toklist, _seminfolist, _globalinfo, _localinfo
var_new = 0 -- reset variable name allocator
varlist = {}
------------------------------------------------------------------
-- preprocess global/local tables, handle entropy reduction
------------------------------------------------------------------
globaluniq = preprocess(globalinfo)
localuniq = preprocess(localinfo)
if option["opt-entropy"] then -- for entropy improvement
recalc_for_entropy(option)
end
------------------------------------------------------------------
-- build initial declared object table, then sort according to
-- token count, this might help assign more tokens to more common
-- variable names such as 'e' thus possibly reducing entropy
-- * an object knows its localinfo index via its 'id' field
-- * special handling for "self" special local (parameter) here
------------------------------------------------------------------
local object = {}
for i = 1, #localinfo do
object[i] = localinfo[i]
end
table.sort(object, -- sort largest first
function(v1, v2)
return v1.xcount > v2.xcount
end
)
------------------------------------------------------------------
-- the special "self" function parameters must be preserved
-- * the allocator below will never use "self", so it is safe to
-- keep those implicit declarations as-is
------------------------------------------------------------------
local temp, j, gotself = {}, 1, false
for i = 1, #object do
local obj = object[i]
if not obj.isself then
temp[j] = obj
j = j + 1
else
gotself = true
end
end
object = temp
------------------------------------------------------------------
-- a simple first-come first-served heuristic name allocator,
-- note that this is in no way optimal...
-- * each object is a local variable declaration plus existence
-- * the aim is to assign short names to as many tokens as possible,
-- so the following tries to maximize name reuse
-- * note that we preserve sort order
------------------------------------------------------------------
local nobject = #object
while nobject > 0 do
local varname, gcollide
repeat
varname, gcollide = new_var_name() -- collect a variable name
until not SKIP_NAME[varname] -- skip all special names
varlist[#varlist + 1] = varname -- keep a list
local oleft = nobject
------------------------------------------------------------------
-- if variable name collides with an existing global, the name
-- cannot be used by a local when the name is accessed as a global
-- during which the local is alive (between 'act' to 'rem'), so
-- we drop objects that collides with the corresponding global
------------------------------------------------------------------
if gcollide then
-- find the xref table of the global
local gref = globalinfo[globaluniq[varname].id].xref
local ngref = #gref
-- enumerate for all current objects; all are valid at this point
for i = 1, nobject do
local obj = object[i]
local act, rem = obj.act, obj.rem -- 'live' range of local
-- if rem < 0, it is a -id to a local that had the same name
-- so follow rem to extend it; does this make sense?
while rem < 0 do
rem = localinfo[-rem].rem
end
local drop
for j = 1, ngref do
local p = gref[j]
if p >= act and p <= rem then drop = true end -- in range?
end
if drop then
obj.skip = true
oleft = oleft - 1
end
end--for
end--if gcollide
------------------------------------------------------------------
-- now the first unassigned local (since it's sorted) will be the
-- one with the most tokens to rename, so we set this one and then
-- eliminate all others that collides, then any locals that left
-- can then reuse the same variable name; this is repeated until
-- all local declaration that can use this name is assigned
-- * the criteria for local-local reuse/collision is:
-- A is the local with a name already assigned
-- B is the unassigned local under consideration
-- => anytime A is accessed, it cannot be when B is 'live'
-- => to speed up things, we have first/last accesses noted
------------------------------------------------------------------
while oleft > 0 do
local i = 1
while object[i].skip do -- scan for first object
i = i + 1
end
------------------------------------------------------------------
-- first object is free for assignment of the variable name
-- [first,last] gives the access range for collision checking
------------------------------------------------------------------
oleft = oleft - 1
local obja = object[i]
i = i + 1
obja.newname = varname
obja.skip = true
obja.done = true
local first, last = obja.first, obja.last
local xref = obja.xref
------------------------------------------------------------------
-- then, scan all the rest and drop those colliding
-- if A was never accessed then it'll never collide with anything
-- otherwise trivial skip if:
-- * B was activated after A's last access (last < act)
-- * B was removed before A's first access (first > rem)
-- if not, see detailed skip below...
------------------------------------------------------------------
if first and oleft > 0 then -- must have at least 1 access
local scanleft = oleft
while scanleft > 0 do
while object[i].skip do -- next valid object
i = i + 1
end
scanleft = scanleft - 1
local objb = object[i]
i = i + 1
local act, rem = objb.act, objb.rem -- live range of B
-- if rem < 0, extend range of rem thru' following local
while rem < 0 do
rem = localinfo[-rem].rem
end
--------------------------------------------------------
if not(last < act or first > rem) then -- possible collision
--------------------------------------------------------
-- B is activated later than A or at the same statement,
-- this means for no collision, A cannot be accessed when B
-- is alive, since B overrides A (or is a peer)
--------------------------------------------------------
if act >= obja.act then
for j = 1, obja.xcount do -- ... then check every access
local p = xref[j]
if p >= act and p <= rem then -- A accessed when B live!
oleft = oleft - 1
objb.skip = true
break
end
end--for
--------------------------------------------------------
-- A is activated later than B, this means for no collision,
-- A's access is okay since it overrides B, but B's last
-- access need to be earlier than A's activation time
--------------------------------------------------------
else
if objb.last and objb.last >= obja.act then
oleft = oleft - 1
objb.skip = true
end
end
end
--------------------------------------------------------
if oleft == 0 then break end
end
end--if first
------------------------------------------------------------------
end--while
------------------------------------------------------------------
-- after assigning all possible locals to one variable name, the
-- unassigned locals/objects have the skip field reset and the table
-- is compacted, to hopefully reduce iteration time
------------------------------------------------------------------
local temp, j = {}, 1
for i = 1, nobject do
local obj = object[i]
if not obj.done then
obj.skip = false
temp[j] = obj
j = j + 1
end
end
object = temp -- new compacted object table
nobject = #object -- objects left to process
------------------------------------------------------------------
end--while
------------------------------------------------------------------
-- after assigning all locals with new variable names, we can
-- patch in the new names, and reprocess to get 'after' stats
------------------------------------------------------------------
for i = 1, #localinfo do -- enumerate all locals
local obj = localinfo[i]
local xref = obj.xref
if obj.newname then -- if got new name, patch it in
for j = 1, obj.xcount do
local p = xref[j] -- xrefs indexes the token list
seminfolist[p] = obj.newname
end
obj.name, obj.oldname -- adjust names
= obj.newname, obj.name
else
obj.oldname = obj.name -- for cases like 'self'
end
end
------------------------------------------------------------------
-- deal with statistics output
------------------------------------------------------------------
if gotself then -- add 'self' to end of list
varlist[#varlist + 1] = "self"
end
local afteruniq = preprocess(localinfo)
stats_summary(globaluniq, localuniq, afteruniq, option)
------------------------------------------------------------------
end
return base

View File

@@ -0,0 +1,361 @@
Tech Notes for LuaSrcDiet
=========================
The following are notes on the optimization process for easy reference.
Miscellaneous Ideas, TODO Stuff
===============================
Other Ideas:
(a) remove unused locals that can be removed in the source
(b) remove declarations using nil
(c) remove table constructor elements using nil
(d) extra optional semicolon removal
(e) extra comma or semicolon removal in table constructors
(f) special number forms: using ^ and * to shorten constants: 1^16
(g) simple declaration of locals that can be merged: local a,b,c,d
(h) warn of opportunity for using a local to zap a bunch of globals
(i) warn of trailing whitespace in strings or long strings
(j) spaces to tabs in comments/long comments/long strings
(k) convert long strings to normal strings, vice versa
(l) simplify logical or relational operator expression
Modified Lexer Output
=====================
The lexer is works almost exactly like llex.c in 'normal' Lua. Instead
of returning one token on each call, the lexer processes the entire
string (from an entire file) and returns. Two lists (tokens and semantic
information items) are set up in the module for use by the caller.
For maximum flexibility during processing, the lexer returns non-grammar
lexical elements as tokens too. Non-grammar elements, such as comments,
whitespace, line endings, are classified along with 'normal' tokens. The
lexer classifies 7 kinds of grammar tokens and 4 kinds of non-grammar
tokens:
---------------------------------------------------------------------
TOKEN CLASS DESCRIPTION
---------------------------------------------------------------------
"TK_KEYWORD" keywords
"TK_NAME" identifiers
"TK_NUMBER" numbers (unconverted, kept in original form)
"TK_STRING" strings (no translation is done, includes delimiters)
"TK_LSTRING" long strings (no translation is done, includes delimiters)
"TK_OP" operators and punctuation (most single-char, some double)
"TK_EOS" end-of-stream (there is only one for each file/stream)
---------------------------------------------------------------------
"TK_SPACE" whitespace (generally, spaces, \t, \v and \f)
"TK_COMMENT" comments (includes delimiters, also includes special
first line shbang, which is handled specially in the
optimizer)
"TK_LCOMMENT" block comments (includes delimiters)
"TK_EOL" end-of-lines (excludes those embedded in strings)
---------------------------------------------------------------------
Table for Lexer-Based Optimizations
===================================
We aim to keep lexer-based optimizations free of parser considerations,
i.e. we allow for generalized optimization of token sequences. The table
below considers the requirements for all combinations of significant
tokens. Other tokens are whitespace-like. Comments can be considered to
be a special kind of whitespace, e.g. a short comment needs to have a
following EOL token, if we do not want to optimize away short comments.
FIRST SECOND TOKEN
TOKEN |
| V
V Keyword Name Number String LString Oper
--------------------------------------------------------
Keyword [S] [S] [S] 0 0 0
Name [S] [S] [S] 0 0 0
Number [S] [S] [S] 0 0 [1]
String 0 0 0 0 0 0
LString 0 0 0 0 0 0
Oper 0 0 [1] 0 0 [2]
--------------------------------------------------------
[S] = need at least one whitespace (set as either a space or keep EOL)
[1] = need a space if operator is a '.', all others okay; a '+' or '-'
is used as part of a floating-point spec, but there does not
appear to be any way of creating a float by joining with number
with a a '+' or '-' plus another number, because an 'e' has to
be somewhere in the first token, this can't be done
[2] = normally there cannot be consecutive operators, but we plan to
allow for generalized optimization of token sequences, i.e. even
sequences that are grammatically illegal; so disallow adjacent
operators if:
(a) the first is in [=<>] and the second is '='
(b) disallow dot sequences to be adjacent, but "..." first okay
(c) disallow '[' followed by '=' or '[' (not optimal)
Also, a minus '-' cannot preceed a Comment or LComment, because comments
start with a '--' prefix. Apart from that, all Comment or LComment
tokens can be set abut with a real token.
Sequence of Lexer-Based Optimization Process
============================================
*** TODO ***
Description of Locals Optimization
==================================
VARIABLE TRACKING AND LOCAL VARIABLE RENAMING
(A) TK_NAME token class considerations
--------------------------------------
A TK_NAME token means a number of things, and some of these cannot be
renamed. We are interested in the use of TK_NAME in the following:
(a) global variable access
(b) local variable declaration, includes local statements, local
functions, function parameters, implicit "self" local, etc.
(c) local variable access, including upvalue access
TK_NAME is also used in parts of grammar as constant strings -- these
tokens cannot be optimized without user assistance. These include:
(d) as the key in key=value pairs in table construction
(e) as field or method names in a:b or a.b syntax forms
For local variable name optimization, we do not need to consider (d) and
(e), and while global variables cannot be renamed (since we do not have
support for user assistance), they need to be considered as part of
Lua's variable access scheme.
(B) Lifetime of a local variable
--------------------------------
Take the following example:
local string, table = string, table
In the example, the two locals are assigned the values of the globals
with the same names. Therefore, when Lua encounters the declaration
portion:
local string, table
The parser cannot immediately make the two local variable available to
following code. In the parser and code generator, locals are inactive
when its entry is created. They are activated only when the function
adjustlocalvars() is called to activate the appropriate local variables.
In the example, the two local variables are activated only after the
whole statement has been parsed, that is, after the last "table" token.
Hence, the statement works as expected. Also, once the two local
variables goes out of scope, removevars() is called to deactivate them,
allowing other variables of the same name to become visible again.
Another example worth mentioning is:
local a, a, a, = 1, 2, 3
The above will assign 3 to 'a'.
Thus, when optimizing local variable names, (1) we need to consider
accesses of global variable names affecting the namespace, (2) for the
local variable names themselves, we need to consider when they are
declared, activated and removed, and (3) within the 'live' time of
locals, we need to know when they are accessed (since locals that are
never accessed don't really matter.)
(C) Local variable tracking
---------------------------
Every local variable declaration is considered an object to be renamed.
From the parser, we have the original name of the local variable, the
token positions for declaration, activation and removal, and the token
position for all the TK_NAME tokens which references this local. All
instances of the implicit "self" local variable are flagged as such.
In addition to local variable information, all global variable accesses
are tabled, one object entry for one name, and each object has a
corresponding list of token positions for the TK_NAME tokens, which is
where the global variables were accessed.
The key criteria is: Our act of renaming cannot change the visibility of
any of these locals and globals at the time they are accessed.
Of course, if every variable has a unique name, then there is no need
for a name allocation algorithm, as there will be no conflict. But, in
order to maximize utilization of short identifier names, we want to
reuse the names as much as possible. In addition, fewer names will
likely reduce symbol entropy and may slightly improve compressibility of
the source code.
(D) Name allocation theory
--------------------------
To understand the renaming algorithm, first we need to establish how
different local and global variables can operate happily without
interfering with each other.
Consider three objects, local object A, local object B and global object
G. A and B involve declaration, activation and removal, and within the
period it is active, there may be zero or more accesses of the local.
For G, there are only global variable accesses to look into.
Assume that we have assigned a new name to A and we wish to consider its
effects on other locals and globals, for which we choose B and G as
examples. We assume local B has not been assigned a new name as we
expect our algorithm to take care of collisions.
A's lifetime is something like this:
Decl Act Rem
+ +-------------------------------+
-------------------------------------------------
where 'Decl' is the time of declaration, 'Act' is the time of
activation, and 'Rem' is the time of removal. Between 'Act' and 'Rem',
the local is alive or 'live' and Lua can see it if its corresponding
TK_NAME identifier comes up.
Decl Act Rem
+ +-------------------------------+
-------------------------------------------------
* * * *
(1) (2) (3) (4)
Recall that the key criteria is to not change the visibility of globals
and locals. Consider local and global accesses at (1), (2), (3) and (4).
A global G of the same name as A will only collide at (3), where Lua
will see A and not G. Since G must be accessed at (3) according to what
the parser says, and we cannot modify the positions of 'Decl', 'Act' and
'Rem', it follows that A cannot have the same name as G.
Decl Act Rem
+ +-----------------------+
---------------------------------
(1)+ +---+ (2)+ +---+ (3)+ +---+ (4)+ +---+
--------- --------- --------- ---------
For the case of A and B having the same names and colliding, consider
the cases for which B is at (1), (2), (3) or (4) in the above.
(1) and (4) means that A and B are completely isolated from each other,
hence in the two cases, A and B can safely use the same variable names.
To be specific, since we have assigned A, B is considered completely
isolated from A if B's Activation-to-Removal period is isolated from
the time of A's first access to last access, meaning B's active time
will never affect any of A's accesses.
For (2) and (3), we have two cases where we need to consider which one
has been activated first. For (2), B is active before A, so A cannot
impose on B. But A's accesses are valid while B is active, since A can
override B. For no collision in the case of (2), we simply need to
ensure that the last access of B occurs before A is activated.
For (3), B is activated before A, hence B can override A's accesses. For
no collision, all of A's accesses cannot happen while B is active. Thus
position (3) follows the "A is never accessed when B is active" rule in
a general way. Local variables of a child function are in the position
of (3). To illustrate, the local B can use the same name as local A and
live in a child function or block scope if each time A is accessed, Lua
sees A and not B. So we have to check all accesses of A and see whether
they collide with the active period of B. Now if A was never accessed,
then B can be active anywhere.
The above appears to resolve all sorts of cases where the active times
of A and B overlap. If there is a more simple scheme, do let me know.
Note that in the above, the allocator does not need to know how locals
are separated according to function prototypes. Perhaps the allocator
can be simplified if knowledge of function structure is utilized.
(E) Name allocation algorithm
-----------------------------
To begin with, the name generator is mostly separate from the name
allocation algorithm. The name generator returns the next shortest name
for the algorithm to apply to local variables. To attempt to reduce
symbol entropy (which benefit compression algorithms), the name
generator follows English frequent letter usage. Later, there may be an
option to calculate an actual symbol entropy table from the input data.
Since there are 53 one-character identifiers and (53*63-4) two-character
identifiers (minus a few keywords), there isn't a pressing need to
optimally maximize name reuse. Sample files show that we can eventually
get 20 tokens per identifier name, thus a source file can have over 1000
local variable tokens that are all single character in length.
In theory, we should need no more than 260 local identifiers by default.
Why? Since LUAI_MAXVARS is 200 and LUAI_MAXUPVALUES is 60, at any block
scope, there can be at most (LUAI_MAXVARS + LUAI_MAXUPVALUES) locals
referenced, or 260. Also, those from outer scopes not referenced in
inner scopes can reuse identifiers. The net effect of this is that a
local variable name allocation method should not allocate more than 260
identifier names for locals.
The current algorithm is a simple first-come first-served scheme:
(a) One local object that use the most tokens is named first.
(b) Any other non-conflicting locals with respect to the first
object are assigned the same name.
(c) Assigned locals are removed and the procedure is repeated for
objects that have not been assigned new names. (a) to (c)
repeats until no local objects are left.
In addition, there are a few extra issues to take care of:
(d) Implicit "self" locals that have been flagged as such are
already "assigned to" and so they are left unmodified.
(e) The name generator skips "self" to avoid conflicts. This
is not optimal but it is unlikely a script will use so many
local variables as to reach "self".
(f) Keywords are also skipped for the name generator.
(g) Global name conflict resolution.
For (g), global name conflict resolution is handled just after the new
name is generated. The name can still be used for some locals even if it
conflicts with other locals. To remove conflicts, global variable
accesses for the particular identifier name is checked. Any local
variables that are active when a global access is made is marked to be
skipped. The rest of the local objects can then use that name.
The algorithm has special handling for locals that use the same name in
the same scope. For example:
local foo = 10 -- (1)
...
local foo = 20 -- (2)
...
print(e)
Since we are considering name visibility, the first 'foo' does not
really cease to exist when the second 'foo' is declared, because if we
were to make that assumption, and the first 'foo' is removed before (2),
then I should be able to use 'e' as the name for the first 'foo' and
after (2), it should not conflict with variables in the outer scope
with the same name. To illustrate:
local e = 10 -- 'foo ' renamed to 'e'
...
local t = 20 -- error if we assumed 'e' removed here
...
print(e)
Since 'e' is a global in the example, we now have an error as the
name as been taken over by a local. Thus, the first 'foo' local must
have its active time extend to the end of the current scope. If there
is no conflict between the first and second 'foo', the algorithm may
still assign the same names to them.
The current fix to deal with the above chains local objects in order to
find the removal position. Practically, the parser can be modified to
simplify this.
END.

View File

@@ -0,0 +1,20 @@
Copyright (c) 2014 Robin Wellner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,97 @@
Smallfolk
=========
Smallfolk is a reasonably fast, robust, richtly-featured table serialization
library for Lua. It was specifically written to allow complex data structures
to be loaded from unsafe sources for [LÖVE](http://love2d.org/) games, but can
be used anywhere.
You use, distribute and extend Smallfolk under the terms of the MIT license.
Usage
-----
Smallfolk is very simple and easy to use:
```lua
local smallfolk = require 'smallfolk'
print(smallfolk.dumps({"Hello", world = true}))
print(smallfolk.loads('{"foo":"bar"}').foo)
-- prints:
-- {"Hello","world":t}
-- bar
```
Fast
----
Using Serpent's benchmark code, Smallfolk's serialization speed is comparable
to that of Ser (and Ser is 33% faster than Serpent).
It should be noted that deserialization is much slower in Smallfolk than in
most other serialization libraries, because it parses the input itself instead
of handing it over to Lua. However, if you use LuaJIT this difference is much
less, and it is not noticable for small outputs. By default, Smallfolk rejects
inputs that are too large, to prevent DOS attacks.
Robust
------
Sometimes you have strange, non-euclidean geometries in your table
constructions. It happens, I don't judge. Smallfolk can deal with that, where
some other serialization libraries (or anything that produces JSON) cry "Iä!
Iä! Cthulhu fhtagn!" and give up &mdash; or worse, silently produce incorrect
data.
```lua
local smallfolk = require 'smallfolk'
local cthulhu = {{}, {}, {}}
cthulhu.fhtagn = cthulhu
cthulhu[1][cthulhu[2]] = cthulhu[3]
cthulhu[2][cthulhu[1]] = cthulhu[2]
cthulhu[3][cthulhu[3]] = cthulhu
print(smallfolk.dumps(cthulhu))
-- prints:
-- {{{@2:@3}:{@4:@1}},@3,@4,"fhtagn":@1}
```
Secure
------
Smallfolk doesn't run arbitrary Lua code, so you can safely use it when you
want to read data from an untrusted source.
Compact
-------
Smallfolk creates really small output files compared to something like Ser when
it encounters a lot of non-tree-like data, by using numbered references rather
than item assignment.
Tested
------
Check out `tests.lua` to see how Smallfolk behaves with all kinds of inputs.
Reference
---------
###`smallfolk.dumps(object)`
Returns an 8-bit string representation of `object`. Throws an error if `object`
contains any types that cannot be serialised (userdata, functions and threads).
###`smallfolk.loads(string[, maxsize=10000])`
Returns an object whose representation would be `string`. If the length of
`string` is larger than `maxsize`, no deserialization is attempted and instead
an error is thrown. If `string` is not a valid representation of any object,
an error is thrown.
See also
--------
* [Ser](https://github.com/gvx/Ser): for trusted-source serialization
* [Lady](https://github.com/gvx/Lady): for trusted-source savegames

View File

@@ -0,0 +1,218 @@
local M = {}
Smallfolk = M
local expect_object, dump_object
local error, tostring, pairs, type, floor, huge, concat = error, tostring, pairs, type, math.floor, math.huge, table.concat
local dump_type = {}
function dump_type:string(nmemo, memo, acc)
local nacc = #acc
acc[nacc + 1] = '"'
acc[nacc + 2] = self:gsub('"', '""')
acc[nacc + 3] = '"'
return nmemo
end
function dump_type:number(nmemo, memo, acc)
acc[#acc + 1] = ("%.17g"):format(self)
return nmemo
end
function dump_type:table(nmemo, memo, acc)
--[[
if memo[self] then
acc[#acc + 1] = '@'
acc[#acc + 1] = tostring(memo[self])
return nmemo
end
nmemo = nmemo + 1
]]
memo[self] = nmemo
acc[#acc + 1] = '{'
local nself = #self
for i = 1, nself do -- don't use ipairs here, we need the gaps
nmemo = dump_object(self[i], nmemo, memo, acc)
acc[#acc + 1] = ','
end
for k, v in pairs(self) do
if type(k) ~= 'number' or floor(k) ~= k or k < 1 or k > nself then
nmemo = dump_object(k, nmemo, memo, acc)
acc[#acc + 1] = ':'
nmemo = dump_object(v, nmemo, memo, acc)
acc[#acc + 1] = ','
end
end
acc[#acc] = acc[#acc] == '{' and '{}' or '}'
return nmemo
end
function dump_object(object, nmemo, memo, acc)
if object == true then
acc[#acc + 1] = 't'
elseif object == false then
acc[#acc + 1] = 'f'
elseif object == nil then
acc[#acc + 1] = 'n'
elseif object ~= object then
if (''..object):sub(1,1) == '-' then
acc[#acc + 1] = 'N'
else
acc[#acc + 1] = 'Q'
end
elseif object == huge then
acc[#acc + 1] = 'I'
elseif object == -huge then
acc[#acc + 1] = 'i'
else
local t = type(object)
if not dump_type[t] then
error('cannot dump type ' .. t)
end
return dump_type[t](object, nmemo, memo, acc)
end
return nmemo
end
function M.dumps(object)
local nmemo = 0
local memo = {}
local acc = {}
dump_object(object, nmemo, memo, acc)
return concat(acc)
end
local function invalid(i)
error('invalid input at position ' .. i)
end
local nonzero_digit = {['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true, ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true}
local is_digit = {['0'] = true, ['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true, ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true}
local function expect_number(string, start)
local i = start
local head = string:sub(i, i)
if head == '-' then
i = i + 1
head = string:sub(i, i)
end
if nonzero_digit[head] then
repeat
i = i + 1
head = string:sub(i, i)
until not is_digit[head]
elseif head == '0' then
i = i + 1
head = string:sub(i, i)
else
invalid(i)
end
if head == '.' then
local oldi = i
repeat
i = i + 1
head = string:sub(i, i)
until not is_digit[head]
if i == oldi + 1 then
invalid(i)
end
end
if head == 'e' or head == 'E' then
i = i + 1
head = string:sub(i, i)
if head == '+' or head == '-' then
i = i + 1
head = string:sub(i, i)
end
if not is_digit[head] then
invalid(i)
end
repeat
i = i + 1
head = string:sub(i, i)
until not is_digit[head]
end
return tonumber(string:sub(start, i - 1)), i
end
local expect_object_head = {
t = function(string, i) return true, i end,
f = function(string, i) return false, i end,
n = function(string, i) return nil, i end,
Q = function(string, i) return -(0/0), i end,
N = function(string, i) return 0/0, i end,
I = function(string, i) return 1/0, i end,
i = function(string, i) return -1/0, i end,
['"'] = function(string, i)
local nexti = i - 1
repeat
nexti = string:find('"', nexti + 1, true) + 1
until string:sub(nexti, nexti) ~= '"'
return string:sub(i, nexti - 2):gsub('""', '"'), nexti
end,
['0'] = function(string, i)
return expect_number(string, i - 1)
end,
['{'] = function(string, i, tables)
local nt, k, v = {}
local j = 1
tables[#tables + 1] = nt
if string:sub(i, i) == '}' then
return nt, i + 1
end
while true do
k, i = expect_object(string, i, tables)
if string:sub(i, i) == ':' then
v, i = expect_object(string, i + 1, tables)
nt[k] = v
else
nt[j] = k
j = j + 1
end
local head = string:sub(i, i)
if head == ',' then
i = i + 1
elseif head == '}' then
return nt, i + 1
else
invalid(i)
end
end
end,
--[[
['@'] = function(string, i, tables)
local match = string:match('^%d+', i)
local ref = tonumber(match)
if tables[ref] then
return tables[ref], i + #match
end
invalid(i)
end,
]]
}
expect_object_head['1'] = expect_object_head['0']
expect_object_head['2'] = expect_object_head['0']
expect_object_head['3'] = expect_object_head['0']
expect_object_head['4'] = expect_object_head['0']
expect_object_head['5'] = expect_object_head['0']
expect_object_head['6'] = expect_object_head['0']
expect_object_head['7'] = expect_object_head['0']
expect_object_head['8'] = expect_object_head['0']
expect_object_head['9'] = expect_object_head['0']
expect_object_head['-'] = expect_object_head['0']
expect_object_head['.'] = expect_object_head['0']
expect_object = function(string, i, tables)
local head = string:sub(i, i)
if expect_object_head[head] then
return expect_object_head[head](string, i + 1, tables)
end
invalid(i)
end
function M.loads(string, maxsize)
if #string > (maxsize or 10000) then
error 'input too large'
end
return (expect_object(string, 1, {}))
end
return M

View File

@@ -0,0 +1,25 @@
lua-digest-crc32lua License
===============================================================================
Copyright (C) 2008, David Manura.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================================================

View File

@@ -0,0 +1,207 @@
--[[
LUA MODULE
digest.crc32 - CRC-32 checksum implemented entirely in Lua.
SYNOPSIS
local CRC = require 'digest.crc32lua'
print(CRC.crc32 'test') --> 0xD87F7E0C or -662733300
assert(CRC.crc32('st', CRC.crc32('te')) == CRC.crc32 'test')
DESCRIPTION
This can be used to compute CRC-32 checksums on strings.
This is similar to [1-2].
API
Note: in the functions below, checksums are 32-bit integers stored in
numbers. The number format currently depends on the bit
implementation--see DESIGN NOTES below.
CRC.crc32_byte(byte [, crc]) --> rcrc
Returns CRC-32 checksum `rcrc` of byte `byte` (number 0..255) appended to
a string with CRC-32 checksum `crc`. `crc` defaults to 0 (empty string)
if omitted.
CRC.crc32_string(s, crc) --> bcrc
Returns CRC-32 checksum `rcrc` of string `s` appended to
a string with CRC-32 checksum `crc`. `crc` defaults to 0 (empty string)
if omitted.
CRC.crc32(o, crc) --> bcrc
This invokes `crc32_byte` if `o` is a byte or `crc32_string` if `o`
is a string.
CRC.bit
This contains the underlying bit library used by the module. It
should be considered a read-only copy.
DESIGN NOTES
Currently, this module exposes the underlying bit array implementation in CRC
checksums returned. In BitOp, bit arrays are 32-bit signed integer numbers
(may be negative). In Lua 5.2 'bit32' and 'bit.numberlua', bit arrays are
32-bit unsigned integer numbers (non-negative). This is subject to change
in the future but is currently done due to (unconfirmed) performance
implications.
On platforms with 64-bit numbers, one way to normalize CRC
checksums to be unsigned is to do `crcvalue % 2^32`,
The name of this module is inspired by Perl `Digest::CRC*`.
DEPENDENCIES
Requires one of the following bit libraries:
BitOp "bit" -- bitop.luajit.org -- This is included in LuaJIT and also available
for Lua 5.1/5.2. This provides the fastest performance in LuaJIT.
Lua 5.2 "bit32" -- www.lua.org/manual/5.2 -- This is provided in Lua 5.2
and is preferred in 5.2 (unless "bit" also happens to be installed).
"bit.numberlua" (>=000.003) -- https://github.com/davidm/lua-bit-numberlua
This is slowest and used as a last resort.
It is only a few times slower than "bit32" though.
DOWNLOAD/INSTALLATION
If using LuaRocks:
luarocks install lua-digest-crc32lua
Otherwise, download <https://github.com/davidm/lua-digest-crc32lua/zipball/master>.
Alternately, if using git:
git clone git://github.com/davidm/lua-digest-crc32lua.git
cd lua-digest-crc32lua
Optionally unpack:
./util.mk
or unpack and install in LuaRocks:
./util.mk install
REFERENCES
[1] http://www.axlradius.com/freestuff/CRC32.java
[2] http://www.gamedev.net/reference/articles/article1941.asp
[3] http://java.sun.com/j2se/1.5.0/docs/api/java/util/zip/CRC32.html
[4] http://www.dsource.org/projects/tango/docs/current/tango.io.digest.Crc32.html
[5] http://pydoc.org/1.5.2/zlib.html#-crc32
[6] http://www.python.org/doc/2.5.2/lib/module-binascii.html
LICENSE
(c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
(end license)
--]]
local M = {_TYPE='module', _NAME='digest.crc32', _VERSION='0.3.20111128'}
local type = type
local require = require
local setmetatable = setmetatable
--[[
Requires the first module listed that exists, else raises like `require`.
If a non-string is encountered, it is returned.
Second return value is module name loaded (or '').
--]]
local function requireany(...)
local errs = {}
for _,name in ipairs{...} do
if type(name) ~= 'string' then return name, '' end
local ok, mod = pcall(require, name)
if ok then return mod, name end
errs[#errs+1] = mod
end
error(table.concat(errs, '\n'), 2)
end
local bit, name_ = requireany('bit', 'bit32', 'bit.numberlua', 'bit53')
local bxor = bit.bxor
local bnot = bit.bnot
local band = bit.band
local rshift = bit.rshift
-- CRC-32-IEEE 802.3 (V.42)
local POLY = 0xEDB88320
-- Memoize function pattern (like http://lua-users.org/wiki/FuncTables ).
local function memoize(f)
local mt = {}
local t = setmetatable({}, mt)
function mt:__index(k)
local v = f(k); t[k] = v
return v
end
return t
end
-- CRC table.
local crc_table = memoize(function(i)
local crc = i
for _=1,8 do
local b = band(crc, 1)
crc = rshift(crc, 1)
if b == 1 then crc = bxor(crc, POLY) end
end
return crc
end)
function M.crc32_byte(byte, crc)
crc = bnot(crc or 0)
local v1 = rshift(crc, 8)
local v2 = crc_table[bxor(crc % 256, byte)]
return bnot(bxor(v1, v2))
end
local M_crc32_byte = M.crc32_byte
function M.crc32_string(s, crc)
crc = crc or 0
for i=1,#s do
crc = M_crc32_byte(s:byte(i), crc)
end
return crc
end
local M_crc32_string = M.crc32_string
function M.crc32(s, crc)
if type(s) == 'string' then
return M_crc32_string(s, crc)
else
return M_crc32_byte(s, crc)
end
end
M.bit = bit -- bit library used
return M

127
AIO_Server/LibCompress.lua Normal file
View File

@@ -0,0 +1,127 @@
-- LibCompress.lua
--
-- Authors: jjsheets and Galmok of European Stormrage (Horde)
-- Email: sheets.jeff@gmail.com and galmok@gmail.com
-- Licence: GPL version 2 (General Public License)
--
-- Hacked severely by Taehl (SelfMadeSpirit@gmail.com)
----------------------------------------------------------------------------------
assert(not TLibCompress, "LibCompress already loaded. Possibly loading different versions of LibCompress")
TLibCompress = {}
local assert = assert
local type = type
local unpack = unpack or table.unpack
local tconcat = table.concat
local schar = string.char
local ssub = string.sub
local sbyte = string.byte
local mmodf = math.modf
local floor = floor or math.floor
local function encode(x)
local bytes = {}
local xmod
repeat
x, xmod = mmodf(x/255)
xmod = xmod * 255
bytes[#bytes + 1] = xmod
until x <= 0
if #bytes == 1 and bytes[1] > 0 and bytes[1] < 250 then
return schar(bytes[1])
else
for i = 1, #bytes do bytes[i] = bytes[i] + 1 end
return schar(256 - #bytes, unpack(bytes))
end
end
local function decode(ss,i)
i = i or 1
local a = sbyte(ss,i,i)
if a > 249 then
local r = 0
a = 256 - a
for n = i+a, i+1, -1 do
r = r * 255 + sbyte(ss,n,n) - 1
end
return r, a + 1
else
return a, 1
end
end
function TLibCompress.CompressLZW(uncompressed)
assert(type(uncompressed) == 'string')
local result = {'\222'}
local ressize = 1
local w = ''
local dict = {}
local dict_size = 256
for i = 0, 255 do
dict[schar(i)] = i
end
for i = 1, #uncompressed do
local c = ssub(uncompressed,i,i)
local wc = w..c
if dict[wc] then
w = wc
else
dict[wc] = dict_size
dict_size = dict_size +1
local r = encode(dict[w])
ressize = ressize + #r
result[#result + 1] = r
w = c
end
end
if w then
local r = encode(dict[w])
ressize = ressize + #r
result[#result + 1] = r
end
if (#uncompressed+1) > ressize then
return tconcat(result)
else
return '\1'..uncompressed
end
end
function TLibCompress.DecompressLZW(compressed)
assert(type(compressed) == 'string')
local UC
UC, compressed = ssub(compressed,1,1), ssub(compressed, 2)
if UC == '\1' then
return compressed
end
if UC ~= "\222" then
return nil, "Can only decompress LZW compressed data ("..tostring(UC)..")"
end
local dict_size = 256
local dict = {}
for i = 0, 255 do
dict[i] = schar(i)
end
local result = {}
local t = 1
local delta, k
k, delta = decode(compressed,t)
t = t + delta
result[#result+1] = dict[k]
local w = dict[k]
local entry
local csize = #compressed
while t <= csize do
k, delta = decode(compressed,t)
t = t + delta
entry = dict[k] or (w..ssub(w,1,1))
result[#result+1] = entry
dict[dict_size] = w..ssub(entry,1,1)
dict_size = dict_size + 1
w = entry
end
return tconcat(result)
end
return TLibCompress

29
AIO_Server/bit53.lua Normal file
View File

@@ -0,0 +1,29 @@
-- Provides compatibility for scripts using bit libs for lua versions < 5.3
-- Using load to avoid errors when having this file in earlier lua sources than 5.3
-- check that lua version is higher or equal to 5.3
local MIN_LUA_VER = 5.3
if tonumber(_VERSION:match("%d+%.?%d*")) >= MIN_LUA_VER then
return assert(assert(load( [[
local bit53 = {}
function bit53.band(a,b)
return a&b
end
function bit53.bor(a,b)
return a|b
end
function bit53.bxor(a,b)
return a~b
end
function bit53.bnot(a)
return ~a
end
function bit53.lshift(a, b)
return a<<b
end
function bit53.rshift(a, b)
return a>>b
end
return bit53
]], "bit53" ))())
end

41
AIO_Server/lualzw-zeros/.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# Compiled Lua sources
luac.out
# luarocks build files
*.src.rock
*.zip
*.tar.gz
# Object files
*.o
*.os
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,72 @@
# lualzw
A relatively fast LZW compression algorithm in pure lua
# encoding and decoding
Lossless compression for any text. The more repetition in the text, the better.
16 bit encoding is used. So each 8 bit character is encoded as 16 bit.
This means that the dictionary size is 65280.
Any special characters like `äöå` that are represented with multiple characters are supported. The special characters are split up into single characters that are then encoded and decoded.
While compressing, the algorithm checks if the result size gets over the input. If it does, then the input is not compressed and the algorithm returns the input prematurely as the compressed result.
The `zeros` branch contains a version that does not add additional null `\0` characters to the input when encoding. Any existing null characters in input string are preserved as nulls however so make sure your input does not contain nulls.
# usage
```lua
local lualzw = require("lualzw")
local input = "foofoofoofoofoofoofoofoofoo"
local compressed = assert(lualzw.compress(input))
local decompressed = assert(lualzw.decompress(compressed))
assert(input == decompressed)
```
# errors
Returns nil and an error message when the algorithm fails to compress or decompress.
# speed
Times are in seconds.
Both have the same generated input.
The values are an average of 10 tries.
Note that compressing random generated inputs results usually in bigger result than original. In these cases the algorithms do not compress and return input instead and thus compression result is 100% of input.
lualzw is at an advantage in cases where compression cannot be done as it stops prematurely and LibCompress does not.
Also lualzw is at an advantage in cases where compression can be done as it has a larger dictionary in use.
Input: 1000000 random generated bytes converted into string
algorithm|compress|decompress|result % of input
---------|--------|----------|-------------
lualzw|0.6622|0.0003|100
LibCompress|2.1983|0.0024|100
Input: 1000000 random generated bytes in ASCII range converted into string
algorithm|compress|decompress|result % of input
---------|--------|----------|-------------
lualzw|0.812|0.0022|100
LibCompress|1.782|0.0007|100
Input: 1000000 random generated repeating bytes converted into string
algorithm|compress|decompress|result % of input
---------|--------|----------|-------------
lualzw|0.3975|0.0262|4.5001
LibCompress|0.3907|0.0264|6.6997
Input: 1000000 of same character
algorithm|compress|decompress|result % of input
---------|--------|----------|-------------
lualzw|0.7045|0.0026|0.2829
LibCompress|0.6418|0.0038|0.4241
Input: "ymn32h8hm8ekrwjkrn9f" repeated 50000 times. In total 1000000 bytes
algorithm|compress|decompress|result % of input
---------|--------|----------|-------------
lualzw|0.4788|0.0088|1.2629
LibCompress|0.4426|0.0093|1.8905

View File

@@ -0,0 +1,166 @@
--[[
MIT License
Copyright (c) 2016 Rochet2
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local char = string.char
local type = type
local select = select
local sub = string.sub
local tconcat = table.concat
local basedictcompress = {}
local basedictdecompress = {}
for i = 0, 255 do
local ic, iic = char(i), char(i, 1)
basedictcompress[ic] = iic
basedictdecompress[iic] = ic
end
local function dictAddA(str, dict, a, b)
if a >= 256 then
a, b = 1, b+1
if b >= 256 then
dict = {}
b = 2
end
end
dict[str] = char(a,b)
a = a+1
return dict, a, b
end
local function compress(input)
if type(input) ~= "string" then
return nil, "string expected, got "..type(input)
end
local len = #input
if len <= 1 then
return "u"..input
end
local dict = {}
local a, b = 1, 2
local result = {"c"}
local resultlen = 1
local n = 2
local word = ""
for i = 1, len do
local c = sub(input, i, i)
local wc = word..c
if not (basedictcompress[wc] or dict[wc]) then
local write = basedictcompress[word] or dict[word]
if not write then
return nil, "algorithm error, could not fetch word"
end
result[n] = write
resultlen = resultlen + #write
n = n+1
if len <= resultlen then
return "u"..input
end
dict, a, b = dictAddA(wc, dict, a, b)
word = c
else
word = wc
end
end
result[n] = basedictcompress[word] or dict[word]
resultlen = resultlen+#result[n]
n = n+1
if len <= resultlen then
return "u"..input
end
return tconcat(result)
end
local function dictAddB(str, dict, a, b)
if a >= 256 then
a, b = 1, b+1
if b >= 256 then
dict = {}
b = 2
end
end
dict[char(a,b)] = str
a = a+1
return dict, a, b
end
local function decompress(input)
if type(input) ~= "string" then
return nil, "string expected, got "..type(input)
end
if #input < 1 then
return nil, "invalid input - not a compressed string"
end
local control = sub(input, 1, 1)
if control == "u" then
return sub(input, 2)
elseif control ~= "c" then
return nil, "invalid input - not a compressed string"
end
input = sub(input, 2)
local len = #input
if len < 2 then
return nil, "invalid input - not a compressed string"
end
local dict = {}
local a, b = 1, 2
local result = {}
local n = 1
local last = sub(input, 1, 2)
result[n] = basedictdecompress[last] or dict[last]
n = n+1
for i = 3, len, 2 do
local code = sub(input, i, i+1)
local lastStr = basedictdecompress[last] or dict[last]
if not lastStr then
return nil, "could not find last from dict. Invalid input?"
end
local toAdd = basedictdecompress[code] or dict[code]
if toAdd then
result[n] = toAdd
n = n+1
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
else
local tmp = lastStr..sub(lastStr, 1, 1)
result[n] = tmp
n = n+1
dict, a, b = dictAddB(tmp, dict, a, b)
end
last = code
end
return tconcat(result)
end
lualzw = {
compress = compress,
decompress = decompress,
}
return lualzw

83
AIO_Server/queue.lua Normal file
View File

@@ -0,0 +1,83 @@
local Queue = {}
function Queue.__index(que, key)
return Queue[key]
end
function NewQueue()
local t = {first = 0, last = -1}
setmetatable(t, Queue)
return t
end
function Queue.pushleft(que, value)
local first = que.first - 1
que.first = first
que[first] = value
return first
end
function Queue.pushright(que, value)
local last = que.last + 1
que.last = last
que[last] = value
return last
end
function Queue.popleft(que)
local first = que.first
if first > que.last then error("que is empty") end
local value = que[first]
que[first] = nil -- to allow garbage collection
que.first = first + 1
return value
end
function Queue.popright(que)
local last = que.last
if que.first > last then error("que is empty") end
local value = que[last]
que[last] = nil -- to allow garbage collection
que.last = last - 1
return value
end
function Queue.peekleft(que)
return que[que.first]
end
function Queue.peekright(que)
return que[que.last]
end
function Queue.empty(que)
return que.last < que.first
end
function Queue.size(que)
return que.last - que.first + 1
end
function Queue.clear(que)
local l, r = self:getrange()
for i = l, r do
que[idx] = nil
end
que.first, que.last = 0, -1
end
function Queue.get(que, idx)
if idx < que.first or idx > que.last then
return
end
return que[idx]
end
function Queue.getrange(que)
return que.first, que.last
end
function Queue.gettable(que)
return que
end
return NewQueue

View File

@@ -0,0 +1,35 @@
const bonusAuraId = 600000;
const bonusAreaId = 3817;
const teleportData = {
map: 571,
x: 5496.157227,
y: 4725.473633,
z: -194.177444,
o: 2.009775,
};
function exitPlayer(player): boolean {
if (player.GetAreaId() === bonusAreaId && !player.HasAura(bonusAuraId)) {
player.Teleport(teleportData.map, teleportData.x, teleportData.y, teleportData.z, teleportData.o);
return true;
}
return false;
}
const checkAura = (eventId: number, delay: number, repeats: number, player: Player): void => {
if(exitPlayer(player)){
player.RemoveEventById(eventId);
}
}
const openRift: player_event_on_update_zone = (event: number, player: Player) => {
// Exit player should not be here
if(!exitPlayer(player)){
player.RegisterEvent(checkAura, 60000, 11);
}
}
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_UPDATE_ZONE, (...args) => openRift(...args));