ffi-reflect icon indicating copy to clipboard operation
ffi-reflect copied to clipboard

cdata serializer with reflect

Open sonoro1234 opened this issue 2 months ago • 2 comments

I have implemented a cdata serializer with reflect that I wish to share. It recreates the table of init values used in ffi.new I can be used as:

local str = cdataser(cdata)
print(str)
print(loadstring("local ffi = require'ffi';"..str))
print(loadstring("local ffi = require'ffi';return "..str)())

It needs the necessary cdefs already present in LuaJIT to work but it seems no problem as they were already present when saving. It can be used as a plug in a more general serializer for tables.

this is the cdataser.lua code

local ffi = require "ffi"
local reflect = require "reflect"

local function tb2st(t)
    if type(t)~= "table" then
        return tostring(t)
    else
        local str = {"{"}
        for i,v in ipairs(t) do
            table.insert(str,tb2st(v))
            table.insert(str, ",")
        end
        if #str > 1 then table.remove(str) end
        table.insert(str, "}")
        return table.concat(str)
    end
end

local function valor(base,ind,name)
    --print("valor",tb2st(ind),name)
    local field = base
    for i,vv in ipairs(ind) do field = field[vv] end
    field = field[name]
    return field
end
local function valorarray(base,ind,name,nels)
    local field = valor(base,ind,name)
    local t = {}
    for i=0,nels-1 do t[i+1] = field[i] end
    return t
end
local function mergetab(t,t2,ti,ti2)
    if ti2.what == "union" then
        t2 = ti.transparent and t2[1] or {t2[1]}
        table.insert(t,t2)
    else --struct
        if ti.transparent then
            for i,v in ipairs(t2) do table.insert(t,v) end
        else
            table.insert(t,t2)
        end
    end
end
local function cd2tab(cd,ct,ind,nelems)
  local t = {}
  ind = ind or {}
  for ti in ct:members() do
    if ti.what == "struct" or ti.what == "union" then
        if not ti.transparent then table.insert(ind,ti.name) end
        local t2 = cd2tab(cd,ti,ind,nelems)
        if not ti.transparent then table.remove(ind) end
        mergetab(t,t2,ti,ti)
    else
        assert(ti.what == "field")
        if ti.type.what == "struct" or ti.type.what == "union" then
            if not ti.transparent then table.insert(ind,ti.name) end
            local t2 = cd2tab(cd,ti.type,ind,nelems)
            if not ti.transparent then table.remove(ind) end
            mergetab(t,t2,ti,ti.type)
        elseif ti.type.what == "array" then
            local nels = ti.type.vla and nelems or (ti.type.size/ti.type.element_type.size)
            local t2 = valorarray(cd,ind,ti.name,nels)
            table.insert(t,t2)
        else
            table.insert(t,valor(cd,ind,ti.name))
        end
    end
  end
  return ct.what=="union" and {t[1]} or t
end

local function valorarray1(cd,nels,ti)
    local tk = ti.element_type.what
    local t = {}
    for i=0,nels-1 do
        local val = cd[i]
        if tk == "struct" or tk == "union" then 
            val = cd2tab(cd[i],ti.element_type,{})
        end
        t[i+1] = val 
    end
    return t
end
local function genstr(tystr,vals,nelem)
    if nelem then
        return table.concat{"ffi.new(\"",tystr,"\",",nelem,",",tb2st(vals),")"}
    else
        return table.concat{"ffi.new(\"",tystr,"\",",tb2st(vals),")"}
    end
end
local function cdataser(cd)
    local ty = ffi.typeof(cd)
    local len = ffi.sizeof(cd)
    local s0 = ffi.sizeof(ty,0)
    local s1 = ffi.sizeof(ty,1)
    local tystr = tostring(ty):sub(7, -2)
    local is_vla, nelem
    if s0 ~= s1 then
        is_vla = true
        local el_size = s1 - s0
        nelem = (len - s0)/el_size
    end
    local ti = reflect.typeof(ty)
    if ti.what == "struct" or ti.what == "union" then
        local t = cd2tab(cd,ti,{},nelem)
        return genstr(tystr,t,nelem)
    elseif ti.what == "array" then
        assert(ti.vla == is_vla)
        local nels = is_vla and nelem or ti.size/ti.element_type.size
        local vals = valorarray1(cd,nels,ti)
        return genstr(tystr,vals,nelem)
    else
        return genstr(tystr,tonumber(cd))
    end
end

return cdataser

feedback welcome

sonoro1234 avatar Dec 23 '24 15:12 sonoro1234