diff Libs/AceSerializer-3.0/AceSerializer-3.0.lua @ 57:01b63b8ed811 v21

total rewrite to version 21
author yellowfive
date Fri, 05 Jun 2015 11:05:15 -0700
parents
children e635cd648e01
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Libs/AceSerializer-3.0/AceSerializer-3.0.lua	Fri Jun 05 11:05:15 2015 -0700
@@ -0,0 +1,283 @@
+--- **AceSerializer-3.0** can serialize any variable (except functions or userdata) into a string format,
+-- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially 
+-- very large numbers or floating point numbers, and table structures. The only caveat currently is, that multiple
+-- references to the same table will be send individually.
+--
+-- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by 
+-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
+-- and can be accessed directly, without having to explicitly call AceSerializer itself.\\
+-- It is recommended to embed AceSerializer, otherwise you'll have to specify a custom `self` on all calls you
+-- make into AceSerializer.
+-- @class file
+-- @name AceSerializer-3.0
+-- @release $Id: AceSerializer-3.0.lua 1038 2011-10-03 01:39:58Z mikk $
+local MAJOR,MINOR = "AceSerializer-3.0", 4
+local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not AceSerializer then return end
+
+-- Lua APIs
+local strbyte, strchar, gsub, gmatch, format = string.byte, string.char, string.gsub, string.gmatch, string.format
+local assert, error, pcall = assert, error, pcall
+local type, tostring, tonumber = type, tostring, tonumber
+local pairs, select, frexp = pairs, select, math.frexp
+local tconcat = table.concat
+
+-- quick copies of string representations of wonky numbers
+local inf = math.huge
+
+local serNaN  -- can't do this in 4.3, see ace3 ticket 268
+local serInf = tostring(inf)
+local serNegInf = tostring(-inf)
+
+
+-- Serialization functions
+
+local function SerializeStringHelper(ch)	-- Used by SerializeValue for strings
+	-- We use \126 ("~") as an escape character for all nonprints plus a few more
+	local n = strbyte(ch)
+	if n==30 then           -- v3 / ticket 115: catch a nonprint that ends up being "~^" when encoded... DOH
+		return "\126\122"
+	elseif n<=32 then 			-- nonprint + space
+		return "\126"..strchar(n+64)
+	elseif n==94 then		-- value separator 
+		return "\126\125"
+	elseif n==126 then		-- our own escape character
+		return "\126\124"
+	elseif n==127 then		-- nonprint (DEL)
+		return "\126\123"
+	else
+		assert(false)	-- can't be reached if caller uses a sane regex
+	end
+end
+
+local function SerializeValue(v, res, nres)
+	-- We use "^" as a value separator, followed by one byte for type indicator
+	local t=type(v)
+	
+	if t=="string" then		-- ^S = string (escaped to remove nonprints, "^"s, etc)
+		res[nres+1] = "^S"
+		res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper)
+		nres=nres+2
+	
+	elseif t=="number" then	-- ^N = number (just tostring()ed) or ^F (float components)
+		local str = tostring(v)
+		if tonumber(str)==v  --[[not in 4.3 or str==serNaN]] or str==serInf or str==serNegInf then
+			-- translates just fine, transmit as-is
+			res[nres+1] = "^N"
+			res[nres+2] = str
+			nres=nres+2
+		else
+			local m,e = frexp(v)
+			res[nres+1] = "^F"
+			res[nres+2] = format("%.0f",m*2^53)	-- force mantissa to become integer (it's originally 0.5--0.9999)
+			res[nres+3] = "^f"
+			res[nres+4] = tostring(e-53)	-- adjust exponent to counteract mantissa manipulation
+			nres=nres+4
+		end
+	
+	elseif t=="table" then	-- ^T...^t = table (list of key,value pairs)
+		nres=nres+1
+		res[nres] = "^T"
+		for k,v in pairs(v) do
+			nres = SerializeValue(k, res, nres)
+			nres = SerializeValue(v, res, nres)
+		end
+		nres=nres+1
+		res[nres] = "^t"
+	
+	elseif t=="boolean" then	-- ^B = true, ^b = false
+		nres=nres+1
+		if v then
+			res[nres] = "^B"	-- true
+		else
+			res[nres] = "^b"	-- false
+		end
+	
+	elseif t=="nil" then		-- ^Z = nil (zero, "N" was taken :P)
+		nres=nres+1
+		res[nres] = "^Z"
+	
+	else
+		error(MAJOR..": Cannot serialize a value of type '"..t.."'")	-- can't produce error on right level, this is wildly recursive
+	end
+	
+	return nres
+end
+
+
+
+local serializeTbl = { "^1" }	-- "^1" = Hi, I'm data serialized by AceSerializer protocol rev 1
+
+--- Serialize the data passed into the function.
+-- Takes a list of values (strings, numbers, booleans, nils, tables)
+-- and returns it in serialized form (a string).\\
+-- May throw errors on invalid data types.
+-- @param ... List of values to serialize
+-- @return The data in its serialized form (string)
+function AceSerializer:Serialize(...)
+	local nres = 1
+	
+	for i=1,select("#", ...) do
+		local v = select(i, ...)
+		nres = SerializeValue(v, serializeTbl, nres)
+	end
+	
+	serializeTbl[nres+1] = "^^"	-- "^^" = End of serialized data
+	
+	return tconcat(serializeTbl, "", 1, nres+1)
+end
+
+-- Deserialization functions
+local function DeserializeStringHelper(escape)
+	if escape<"~\122" then
+		return strchar(strbyte(escape,2,2)-64)
+	elseif escape=="~\122" then	-- v3 / ticket 115: special case encode since 30+64=94 ("^") - OOPS.
+		return "\030"
+	elseif escape=="~\123" then
+		return "\127"
+	elseif escape=="~\124" then
+		return "\126"
+	elseif escape=="~\125" then
+		return "\94"
+	end
+	error("DeserializeStringHelper got called for '"..escape.."'?!?")  -- can't be reached unless regex is screwed up
+end
+
+local function DeserializeNumberHelper(number)
+	--[[ not in 4.3 if number == serNaN then
+		return 0/0
+	else]]if number == serNegInf then
+		return -inf
+	elseif number == serInf then
+		return inf
+	else
+		return tonumber(number)
+	end
+end
+
+-- DeserializeValue: worker function for :Deserialize()
+-- It works in two modes:
+--   Main (top-level) mode: Deserialize a list of values and return them all
+--   Recursive (table) mode: Deserialize only a single value (_may_ of course be another table with lots of subvalues in it)
+--
+-- The function _always_ works recursively due to having to build a list of values to return
+--
+-- Callers are expected to pcall(DeserializeValue) to trap errors
+
+local function DeserializeValue(iter,single,ctl,data)
+
+	if not single then
+		ctl,data = iter()
+	end
+
+	if not ctl then 
+		error("Supplied data misses AceSerializer terminator ('^^')")
+	end	
+
+	if ctl=="^^" then
+		-- ignore extraneous data
+		return
+	end
+
+	local res
+	
+	if ctl=="^S" then
+		res = gsub(data, "~.", DeserializeStringHelper)
+	elseif ctl=="^N" then
+		res = DeserializeNumberHelper(data)
+		if not res then
+			error("Invalid serialized number: '"..tostring(data).."'")
+		end
+	elseif ctl=="^F" then     -- ^F<mantissa>^f<exponent>
+		local ctl2,e = iter()
+		if ctl2~="^f" then
+			error("Invalid serialized floating-point number, expected '^f', not '"..tostring(ctl2).."'")
+		end
+		local m=tonumber(data)
+		e=tonumber(e)
+		if not (m and e) then
+			error("Invalid serialized floating-point number, expected mantissa and exponent, got '"..tostring(m).."' and '"..tostring(e).."'")
+		end
+		res = m*(2^e)
+	elseif ctl=="^B" then	-- yeah yeah ignore data portion
+		res = true
+	elseif ctl=="^b" then   -- yeah yeah ignore data portion
+		res = false
+	elseif ctl=="^Z" then	-- yeah yeah ignore data portion
+		res = nil
+	elseif ctl=="^T" then
+		-- ignore ^T's data, future extensibility?
+		res = {}
+		local k,v
+		while true do
+			ctl,data = iter()
+			if ctl=="^t" then break end	-- ignore ^t's data
+			k = DeserializeValue(iter,true,ctl,data)
+			if k==nil then 
+				error("Invalid AceSerializer table format (no table end marker)")
+			end
+			ctl,data = iter()
+			v = DeserializeValue(iter,true,ctl,data)
+			if v==nil then
+				error("Invalid AceSerializer table format (no table end marker)")
+			end
+			res[k]=v
+		end
+	else
+		error("Invalid AceSerializer control code '"..ctl.."'")
+	end
+	
+	if not single then
+		return res,DeserializeValue(iter)
+	else
+		return res
+	end
+end
+
+--- Deserializes the data into its original values.
+-- Accepts serialized data, ignoring all control characters and whitespace.
+-- @param str The serialized data (from :Serialize)
+-- @return true followed by a list of values, OR false followed by an error message
+function AceSerializer:Deserialize(str)
+	str = gsub(str, "[%c ]", "")	-- ignore all control characters; nice for embedding in email and stuff
+
+	local iter = gmatch(str, "(^.)([^^]*)")	-- Any ^x followed by string of non-^
+	local ctl,data = iter()
+	if not ctl or ctl~="^1" then
+		-- we purposefully ignore the data portion of the start code, it can be used as an extension mechanism
+		return false, "Supplied data is not AceSerializer data (rev 1)"
+	end
+
+	return pcall(DeserializeValue, iter)
+end
+
+
+----------------------------------------
+-- Base library stuff
+----------------------------------------
+
+AceSerializer.internals = {	-- for test scripts
+	SerializeValue = SerializeValue,
+	SerializeStringHelper = SerializeStringHelper,
+}
+
+local mixins = {
+	"Serialize",
+	"Deserialize",
+}
+
+AceSerializer.embeds = AceSerializer.embeds or {}
+
+function AceSerializer:Embed(target)
+	for k, v in pairs(mixins) do
+		target[v] = self[v]
+	end
+	self.embeds[target] = true
+	return target
+end
+
+-- Update embeds
+for target, v in pairs(AceSerializer.embeds) do
+	AceSerializer:Embed(target)
+end
\ No newline at end of file