Files
wnsrc/lua/pac3/libraries/string_stream.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

671 lines
18 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local math_huge = math.huge
local math_frexp = math.frexp
local math_ldexp = math.ldexp
local math_floor = math.floor
local math_min = math.min
local math_max = math.max
local bit_rshift = bit.rshift
--- StringStream type
-- @name StringStream
-- @class type
-- @libtbl ss_methods
local ss_methods = {}
local ss_meta = {
__index = ss_methods,
__metatable = "StringStream",
__tostring = function(self)
return string.format("Stringstream [%u,%u]", self:tell(), self:size())
end
}
local ss_methods_big = setmetatable({},{__index=ss_methods})
local ss_meta_big = {
__index = ss_methods_big,
__metatable = "StringStream",
__tostring = function(self)
return string.format("Stringstream [%u,%u]", self:tell(), self:size())
end
}
local function StringStream(stream, i, endian)
local ret = setmetatable({
index = 1,
subindex = 1
}, ss_meta)
if stream~=nil then
assert(isstring(stream), "stream must be a string")
ret:write(stream)
if i~=nil then
assert(isnumber(i), "i must be a number")
ret:seek(i)
else
ret:seek(1)
end
end
if endian~=nil then
assert(isstring(endian), "endian must be a string")
ret:setEndian(endian)
end
return ret
end
--Credit https://stackoverflow.com/users/903234/rpfeltz
--Bugfixes and IEEE754Double credit to me
local function PackIEEE754Float(number)
if number == 0 then
return 0x00, 0x00, 0x00, 0x00
elseif number == math_huge then
return 0x00, 0x00, 0x80, 0x7F
elseif number == -math_huge then
return 0x00, 0x00, 0x80, 0xFF
elseif number ~= number then
return 0x00, 0x00, 0xC0, 0xFF
else
local sign = 0x00
if number < 0 then
sign = 0x80
number = -number
end
local mantissa, exponent = math_frexp(number)
exponent = exponent + 0x7F
if exponent <= 0 then
mantissa = math_ldexp(mantissa, exponent - 1)
exponent = 0
elseif exponent > 0 then
if exponent >= 0xFF then
return 0x00, 0x00, 0x80, sign + 0x7F
elseif exponent == 1 then
exponent = 0
else
mantissa = mantissa * 2 - 1
exponent = exponent - 1
end
end
mantissa = math_floor(math_ldexp(mantissa, 23) + 0.5)
return mantissa % 0x100,
bit_rshift(mantissa, 8) % 0x100,
(exponent % 2) * 0x80 + bit_rshift(mantissa, 16),
sign + bit_rshift(exponent, 1)
end
end
local function UnpackIEEE754Float(b4, b3, b2, b1)
local exponent = (b1 % 0x80) * 0x02 + bit_rshift(b2, 7)
local mantissa = math_ldexp(((b2 % 0x80) * 0x100 + b3) * 0x100 + b4, -23)
if exponent == 0xFF then
if mantissa > 0 then
return 0 / 0
else
if b1 >= 0x80 then
return -math_huge
else
return math_huge
end
end
elseif exponent > 0 then
mantissa = mantissa + 1
else
exponent = exponent + 1
end
if b1 >= 0x80 then
mantissa = -mantissa
end
return math_ldexp(mantissa, exponent - 0x7F)
end
local function PackIEEE754Double(number)
if number == 0 then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
elseif number == math_huge then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F
elseif number == -math_huge then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF
elseif number ~= number then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF
else
local sign = 0x00
if number < 0 then
sign = 0x80
number = -number
end
local mantissa, exponent = math_frexp(number)
exponent = exponent + 0x3FF
if exponent <= 0 then
mantissa = math_ldexp(mantissa, exponent - 1)
exponent = 0
elseif exponent > 0 then
if exponent >= 0x7FF then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, sign + 0x7F
elseif exponent == 1 then
exponent = 0
else
mantissa = mantissa * 2 - 1
exponent = exponent - 1
end
end
mantissa = math_floor(math_ldexp(mantissa, 52) + 0.5)
return mantissa % 0x100,
math_floor(mantissa / 0x100) % 0x100, --can only rshift up to 32 bit numbers. mantissa is too big
math_floor(mantissa / 0x10000) % 0x100,
math_floor(mantissa / 0x1000000) % 0x100,
math_floor(mantissa / 0x100000000) % 0x100,
math_floor(mantissa / 0x10000000000) % 0x100,
(exponent % 0x10) * 0x10 + math_floor(mantissa / 0x1000000000000),
sign + bit_rshift(exponent, 4)
end
end
local function UnpackIEEE754Double(b8, b7, b6, b5, b4, b3, b2, b1)
local exponent = (b1 % 0x80) * 0x10 + bit_rshift(b2, 4)
local mantissa = math_ldexp(((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8, -52)
if exponent == 0x7FF then
if mantissa > 0 then
return 0 / 0
else
if b1 >= 0x80 then
return -math_huge
else
return math_huge
end
end
elseif exponent > 0 then
mantissa = mantissa + 1
else
exponent = exponent + 1
end
if b1 >= 0x80 then
mantissa = -mantissa
end
return math_ldexp(mantissa, exponent - 0x3FF)
end
--- Sets the endianness of the string stream
--@param endian The endianness of number types. "big" or "little" (default "little")
function ss_methods:setEndian(endian)
if endian == "little" then
debug.setmetatable(self, ss_meta)
elseif endian == "big" then
debug.setmetatable(self, ss_meta_big)
else
error("Invalid endian specified", 2)
end
end
--- Writes the given string and advances the buffer pointer.
--@param data A string of data to write
function ss_methods:write(data)
if self.index > #self then -- Most often case
self[self.index] = data
self.index = self.index + 1
self.subindex = 1
else
local i = 1
local length = #data
while length > 0 do
if self.index > #self then -- End of buffer
self[self.index] = string.sub(data, i)
self.index = self.index + 1
self.subindex = 1
break
else
local cur = self[self.index]
local sublength = math_min(#cur - self.subindex + 1, length)
self[self.index] = string.sub(cur,1,self.subindex-1) .. string.sub(data,i,i+sublength-1) .. string.sub(cur,self.subindex+sublength)
length = length - sublength
i = i + sublength
if length > 0 then
self.index = self.index + 1
self.subindex = 1
else
self.subindex = self.subindex + sublength
end
end
end
end
end
--- Reads the specified number of bytes from the buffer and advances the buffer pointer.
--@param length How many bytes to read
--@return A string containing the bytes
function ss_methods:read(length)
local ret = {}
while length > 0 do
local cur = self[self.index]
if cur then
if self.subindex == 1 and length >= #cur then
ret[#ret+1] = cur
self.index = self.index + 1
length = length - #cur
else
local sublength = math_min(#cur - self.subindex + 1, length)
ret[#ret+1] = string.sub(cur, self.subindex, self.subindex + sublength - 1)
length = length - sublength
if length > 0 then
self.index = self.index + 1
self.subindex = 1
else
self.subindex = self.subindex + sublength
end
end
else
break
end
end
return table.concat(ret)
end
--- Sets internal pointer to i. The position will be clamped to [1, buffersize+1]
--@param i The position
function ss_methods:seek(pos)
if pos < 1 then error("Index must be 1 or greater", 2) end
self.index = #self+1
self.subindex = 1
local length = 0
for i, v in ipairs(self) do
length = length + #v
if length >= pos then
self.index = i
self.subindex = pos - (length - #v)
break
end
end
end
--- Move the internal pointer by amount i
--@param length The offset
function ss_methods:skip(length)
while length>0 do
local cur = self[self.index]
if cur then
local sublength = math_min(#cur - self.subindex + 1, length)
length = length - sublength
self.subindex = self.subindex + sublength
if self.subindex>#cur then
self.index = self.index + 1
self.subindex = 1
end
else
self.index = #self.index + 1
self.subindex = 1
break
end
end
while length<0 do
local cur = self[self.index]
if cur then
local sublength = math_max(-self.subindex, length)
length = length - sublength
self.subindex = self.subindex + sublength
if self.subindex<1 then
self.index = self.index - 1
self.subindex = self[self.index] and #self[self.index] or 1
end
else
self.index = 1
self.subindex = 1
break
end
end
end
--- Returns the internal position of the byte reader.
--@return The buffer position
function ss_methods:tell()
local length = 0
for i=1, self.index-1 do
length = length + #self[i]
end
return length + self.subindex
end
--- Tells the size of the byte stream.
--@return The buffer size
function ss_methods:size()
local length = 0
for i, v in ipairs(self) do
length = length + #v
end
return length
end
--- Reads an unsigned 8-bit (one byte) integer from the byte stream and advances the buffer pointer.
--@return The uint8 at this position
function ss_methods:readUInt8()
return string.byte(self:read(1))
end
function ss_methods_big:readUInt8()
return string.byte(self:read(1))
end
--- Reads an unsigned 16 bit (two byte) integer from the byte stream and advances the buffer pointer.
--@return The uint16 at this position
function ss_methods:readUInt16()
local a,b = string.byte(self:read(2), 1, 2)
return b * 0x100 + a
end
function ss_methods_big:readUInt16()
local a,b = string.byte(self:read(2), 1, 2)
return a * 0x100 + b
end
--- Reads an unsigned 32 bit (four byte) integer from the byte stream and advances the buffer pointer.
--@return The uint32 at this position
function ss_methods:readUInt32()
local a,b,c,d = string.byte(self:read(4), 1, 4)
return d * 0x1000000 + c * 0x10000 + b * 0x100 + a
end
function ss_methods_big:readUInt32()
local a,b,c,d = string.byte(self:read(4), 1, 4)
return a * 0x1000000 + b * 0x10000 + c * 0x100 + d
end
--- Reads a signed 8-bit (one byte) integer from the byte stream and advances the buffer pointer.
--@return The int8 at this position
function ss_methods:readInt8()
local x = self:readUInt8()
if x>=0x80 then x = x - 0x100 end
return x
end
--- Reads a signed 16-bit (two byte) integer from the byte stream and advances the buffer pointer.
--@return The int16 at this position
function ss_methods:readInt16()
local x = self:readUInt16()
if x>=0x8000 then x = x - 0x10000 end
return x
end
--- Reads a signed 32-bit (four byte) integer from the byte stream and advances the buffer pointer.
--@return The int32 at this position
function ss_methods:readInt32()
local x = self:readUInt32()
if x>=0x80000000 then x = x - 0x100000000 end
return x
end
--- Reads a 4 byte IEEE754 float from the byte stream and advances the buffer pointer.
--@return The float32 at this position
function ss_methods:readFloat()
return UnpackIEEE754Float(string.byte(self:read(4), 1, 4))
end
function ss_methods_big:readFloat()
local a,b,c,d = string.byte(self:read(4), 1, 4)
return UnpackIEEE754Float(d, c, b, a)
end
--- Reads a 8 byte IEEE754 double from the byte stream and advances the buffer pointer.
--@return The double at this position
function ss_methods:readDouble()
return UnpackIEEE754Double(string.byte(self:read(8), 1, 8))
end
function ss_methods_big:readDouble()
local a,b,c,d,e,f,g,h = string.byte(self:read(8), 1, 8)
return UnpackIEEE754Double(h, g, f, e, d, c, b, a)
end
--- Reads until the given byte and advances the buffer pointer.
--@param byte The byte to read until (in number form)
--@return The string of bytes read
function ss_methods:readUntil(byte)
byte = string.char(byte)
local ret = {}
for i=self.index, #self do
local cur = self[self.index]
local find = string.find(cur, byte, self.subindex, true)
if find then
ret[#ret+1] = string.sub(cur, self.subindex, find)
self.subindex = find+1
if self.subindex > #cur then
self.index = self.index + 1
self.subindex = 1
end
break
else
if self.subindex == 1 then
ret[#ret+1] = cur
else
ret[#ret+1] = string.sub(cur, self.subindex)
end
self.index = self.index + 1
self.subindex = 1
end
end
return table.concat(ret)
end
--- returns a null terminated string, reads until "\x00" and advances the buffer pointer.
--@return The string of bytes read
function ss_methods:readString()
local s = self:readUntil(0)
return string.sub(s, 1, #s-1)
end
--- Writes a byte to the buffer and advances the buffer pointer.
--@param x An int8 to write
function ss_methods:writeInt8(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x100 end
self:write(string.char(x%0x100))
end
--- Writes a short to the buffer and advances the buffer pointer.
--@param x An int16 to write
function ss_methods:writeInt16(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x10000 end
self:write(string.char(x%0x100, bit_rshift(x, 8)%0x100))
end
function ss_methods_big:writeInt16(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x10000 end
self:write(bit_rshift(x, 8)%0x100, string.char(x%0x100))
end
--- Writes an int to the buffer and advances the buffer pointer.
--@param x An int32 to write
function ss_methods:writeInt32(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x100000000 end
self:write(string.char(x%0x100, bit_rshift(x, 8)%0x100, bit_rshift(x, 16)%0x100, bit_rshift(x, 24)%0x100))
end
function ss_methods_big:writeInt32(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x100000000 end
self:write(string.char(bit_rshift(x, 24)%0x100, bit_rshift(x, 16)%0x100, bit_rshift(x, 8)%0x100), x%0x100)
end
--- Writes a 4 byte IEEE754 float to the byte stream and advances the buffer pointer.
--@param x The float to write
function ss_methods:writeFloat(x)
self:write(string.char(PackIEEE754Float(x)))
end
function ss_methods_big:writeFloat(x)
local a,b,c,d = PackIEEE754Float(x)
self:write(string.char(d,c,b,a))
end
--- Writes a 8 byte IEEE754 double to the byte stream and advances the buffer pointer.
--@param x The double to write
function ss_methods:writeDouble(x)
self:write(string.char(PackIEEE754Double(x)))
end
function ss_methods_big:writeDouble(x)
local a,b,c,d,e,f,g,h = PackIEEE754Double(x)
self:write(string.char(h,g,f,e,d,c,b,a))
end
--- Writes a string to the buffer putting a null at the end and advances the buffer pointer.
--@param string The string of bytes to write
function ss_methods:writeString(string)
self:write(string)
self:write("\0")
end
--- Returns the buffer as a string
--@return The buffer as a string
function ss_methods:getString()
return table.concat(self)
end
do
do
function ss_methods:writeBool(b)
self:writeInt8(b and 1 or 0)
end
function ss_methods:readBool()
return self:readInt8() == 1
end
end
do
function ss_methods:writeVector(val)
self:writeDouble(val.x)
self:writeDouble(val.y)
self:writeDouble(val.z)
end
function ss_methods:readVector()
local x = self:readDouble()
local y = self:readDouble()
local z = self:readDouble()
return Vector(x,y,z)
end
end
do
function ss_methods:writeAngle(val)
self:writeDouble(val.p)
self:writeDouble(val.y)
self:writeDouble(val.r)
end
function ss_methods:readAngle()
local x = self:readDouble()
local y = self:readDouble()
local z = self:readDouble()
return Angle(x,y,z)
end
end
do
function ss_methods:writeColor(val)
self:writeDouble(val.r)
self:writeDouble(val.g)
self:writeDouble(val.b)
self:writeDouble(val.a)
end
function ss_methods:readColor()
local r = self:readDouble()
local g = self:readDouble()
local b = self:readDouble()
local a = self:readDouble()
return Color(r,g,b,a)
end
end
do
function ss_methods:writeEntity(val)
self:writeInt32(val:EntIndex())
end
function ss_methods:readEntity()
return Entity(self:readInt32())
end
end
function ss_methods:writeTable(tab)
for k, v in pairs( tab ) do
self:writeType( k )
self:writeType( v )
end
self:writeType( nil )
end
function ss_methods:readTable()
local tab = {}
while true do
local k = self:readType()
if k == nil then
return tab
end
tab[k] = self:readType()
end
end
local write_functions = {
[TYPE_NIL] = function(s, t, v) s:writeInt8( t ) end,
[TYPE_STRING] = function(s, t, v) s:writeInt8( t ) s:writeString( v ) end,
[TYPE_NUMBER] = function(s, t, v) s:writeInt8( t ) s:writeDouble( v ) end,
[TYPE_TABLE] = function(s, t, v) s:writeInt8( t ) s:writeTable( v ) end,
[TYPE_BOOL] = function(s, t, v) s:writeInt8( t ) s:writeBool( v ) end,
[TYPE_VECTOR] = function(s, t, v) s:writeInt8( t ) s:writeVector( v ) end,
[TYPE_ANGLE] = function(s, t, v) s:writeInt8( t ) s:writeAngle( v ) end,
[TYPE_COLOR] = function(s, t, v) s:writeInt8( t ) s:writeColor( v ) end,
[TYPE_ENTITY] = function(s, t, v) s:writeInt8( t ) s:writeEntity( v ) end,
}
function ss_methods:writeType( v )
local typeid = nil
if IsColor(v) then
typeid = TYPE_COLOR
else
typeid = TypeID(v)
end
local func = write_functions[typeid]
if func then
return func(self, typeid, v)
end
error("StringStream:writeType: Couldn't write " .. type(v) .. " (type " .. typeid .. ")")
end
local read_functions = {
[TYPE_NIL] = function(s) return nil end,
[TYPE_STRING] = function(s) return s:readString() end,
[TYPE_NUMBER] = function(s) return s:readDouble() end,
[TYPE_TABLE] = function(s) return s:readTable() end,
[TYPE_BOOL] = function(s) return s:readBool() end,
[TYPE_VECTOR] = function(s) return s:readVector() end,
[TYPE_ANGLE] = function(s) return s:readAngle() end,
[TYPE_COLOR] = function(s) return s:readColor() end,
[TYPE_ENTITY] = function(s) return s:readEntity() end,
}
function ss_methods:readType( typeid )
typeid = typeid or self:readUInt8(8)
local func = read_functions[typeid]
if func then
return func(self)
end
error("StringStream:readType: Couldn't read type " .. tostring(typeid))
end
end
return StringStream