summaryrefslogblamecommitdiff
path: root/docgen/eolian_utils.lua
blob: 29d2967118e01eea305981e69addbe058b461046 (plain) (tree)
1
2
3
4
5
6
7
8



                                       

                                      

            






















                                                    
                                                     


                            
                                            

































                                                         






















                                                                                   
                                                               























                                                     













                                                      

                                                                           








































































































































































                                                                           
                            




                                  
                                                  










                                       
                             

                                                        









                                            






                                                    
                                                 









                                                      
                                                   







                                                


                                                                  
                                  


                                         
                                                      
















                                                  

                         









                                                                      


                                          














                                                              


                                          












































                                                                                            

                                  




















                                                              



















                                                             

                                             





































































































































                                                                                                  










                                                                   
                                                               







                                    









                                     

                                      





                                         



           


                                                           
 
                                                                        
                                       






                                                           
           





                                             
           
       
                                                              

   
                                                                    

                               
                                                               

                                     
                                                               


       









                                                               









                                                       
                               

   
                                            


                                         
                                                      



                                               
       
                                                          


























































                                                                                                                   




                                           

           















                                                    
















































                                                                             
                               




















                                                     
        
local ffi = require("ffi")
local eolian = require("eolian")
local keyref = require("docgen.keyref")

local tonum = ffi.tonumber or tonumber

local M = {}

local func_type_str = {
    [eolian.function_type.PROPERTY] = "property",
    [eolian.function_type.PROP_GET] = "property",
    [eolian.function_type.PROP_SET] = "property",
    [eolian.function_type.METHOD] = "method"
}

M.tok_ref_resolve = function(tok, eos, root)
    local tp, d1, d2 = tok:ref_resolve(eos)
    local ret = {}
    for tokp in d1:name_get():gmatch("[^%.]+") do
        ret[#ret + 1] = tokp:lower()
    end
    if tp == eolian.object_type.FUNCTION then
        ret[#ret + 1] = func_type_str[d2:type_get()]
        ret[#ret + 1] = d2:name_get():lower()
    elseif tp == eolian.object_type.EVENT then
        ret[#ret + 1] = "event"
        ret[#ret + 1] = d2:name_get():lower()
    end
    if root ~= nil then
        ret[#ret + 1] = not not root
    end
    return ret, d1:is_beta() or (d2 and d2:is_beta())
end

M.obj_id_get = function(obj)
    return tonum(ffi.cast("uintptr_t", obj))
end

M.obj_nspaces_get = function(obj, root)
    local tbl = obj:namespaces_get():to_array()
    for i = 1, #tbl do
        tbl[i] = tbl[i]:lower()
    end
    tbl[#tbl + 1] = obj:short_name_get():lower()
    if root ~= nil then
        tbl[#tbl + 1] = not not root
    end
    return tbl
end

M.event_nspaces_get = function(obj, cl, root)
    local tbl = M.obj_nspaces_get(cl)
    tbl[#tbl + 1] = "event"
    tbl[#tbl + 1] = obj:name_get():lower():gsub(",", "_")
    if root ~= nil then
        tbl[#tbl + 1] = not not root
    end
    return tbl
end

M.func_nspaces_get = function(obj, cl, root)
    local tbl = M.obj_nspaces_get(cl)
    tbl[#tbl + 1] = func_type_str[obj:type_get()]
    tbl[#tbl + 1] = obj:name_get():lower()
    if root ~= nil then
        tbl[#tbl + 1] = not not root
    end
    return tbl
end

local class_type_str = {
    [eolian.class_type.REGULAR] = "class",
    [eolian.class_type.ABSTRACT] = "class",
    [eolian.class_type.MIXIN] = "mixin",
    [eolian.class_type.INTERFACE] = "interface"
}

M.class_type_str_get = function(cl)
    return class_type_str[cl:type_get()]
end

local param_dirs = {
    [eolian.parameter_dir.IN] = "in",
    [eolian.parameter_dir.OUT] = "out",
    [eolian.parameter_dir.INOUT] = "inout"
}

M.param_get_dir_name = function(param)
    return assert(param_dirs[param:direction_get()], "unknown parameter direction")
end

M.type_cstr_get = function(tp, suffix)
    tp = tp or "void"
    local ct = (type(tp) == "string") and tp or tp:c_type_get()
    if not suffix then
        return ct
    end
    if ct:sub(#ct) == "*" then
        return ct .. suffix
    else
        return ct .. " " .. suffix
    end
end

local serialize_type
local serialize_tdecl
local serialize_tdecl_c
local serialize_var
local serialize_var_c

local wrap_type_attrs = function(tp, str)
    if tp:is_const() then
        str = "const(" .. str .. ")"
    end
    -- TODO: implement new ownership system into docs
    --if tp:is_own() then
    --    str = "own(" .. str .. ")"
    --end
    if tp:is_ptr() then
        str = "ptr(" .. str .. ")"
    end
    return str
end

serialize_type = function(tp)
    local tpt = tp:type_get()
    if tpt == eolian.type_type.UNKNOWN then
        error("unknown type: " .. tp:name_get())
    elseif tpt == eolian.type_type.VOID then
        return wrap_type_attrs(tp, "void")
    elseif tpt == eolian.type_type.UNDEFINED then
        return wrap_type_attrs(tp, "__undefined_type")
    elseif tpt == eolian.type_type.REGULAR or tpt == eolian.type_type.CLASS
        or tpt == eolian.type_type.ERROR then
        local stp = tp:base_type_get()
        if stp then
            local stypes = {}
            while stp do
                stypes[#stypes + 1] = serialize_type(stp)
                stp = stp:next_type_get()
            end
            return wrap_type_attrs(tp, tp:name_get() .. "<"
                .. table.concat(stypes, ", ") .. ">")
        end
        return wrap_type_attrs(tp, tp:name_get())
    end
    error("unhandled type type: " .. tpt)
end

local add_typedecl_attrs = function(tp, buf)
    if tp:is_extern() then
        buf[#buf + 1] = "@extern "
    end
    local ffunc = tp:free_func_get()
    if ffunc then
        buf[#buf + 1] = "@free("
        buf[#buf + 1] = ffunc
        buf[#buf + 1] = ") "
    end
end

serialize_tdecl = function(tp)
    local tpt = tp:type_get()
    if tpt == eolian.typedecl_type.UNKNOWN then
        error("unknown typedecl: " .. tp:name_get())
    elseif tpt == eolian.typedecl_type.STRUCT or
           tpt == eolian.typedecl_type.STRUCT_OPAQUE then
        local buf = { "struct " }
        add_typedecl_attrs(tp, buf)
        buf[#buf + 1] = tp:name_get()
        if tpt == eolian.typedecl_type.STRUCT_OPAQUE then
            buf[#buf + 1] = ";"
            return table.concat(buf)
        end
        local fields = tp:struct_fields_get():to_array()
        if #fields == 0 then
            buf[#buf + 1] = " {}"
            return table.concat(buf)
        end
        buf[#buf + 1] = " {\n"
        for i, fld in ipairs(fields) do
            buf[#buf + 1] = "    "
            buf[#buf + 1] = fld:name_get()
            buf[#buf + 1] = ": "
            buf[#buf + 1] = serialize_type(fld:type_get())
            buf[#buf + 1] = ";\n"
        end
        buf[#buf + 1] = "}"
        return table.concat(buf)
    elseif tpt == eolian.typedecl_type.ENUM then
        local buf = { "enum " }
        add_typedecl_attrs(tp, buf)
        buf[#buf + 1] = tp:name_get()
        local fields = tp:enum_fields_get():to_array()
        if #fields == 0 then
            buf[#buf + 1] = " {}"
            return table.concat(buf)
        end
        buf[#buf + 1] = " {\n"
        for i, fld in ipairs(fields) do
            buf[#buf + 1] = "    "
            buf[#buf + 1] = fld:name_get()
            local val = fld:value_get()
            if val then
                buf[#buf + 1] = ": "
                buf[#buf + 1] = val:serialize()
            end
            if i == #fields then
                buf[#buf + 1] = "\n"
            else
                buf[#buf + 1] = ",\n"
            end
        end
        buf[#buf + 1] = "}"
        return table.concat(buf)
    elseif tpt == eolian.typedecl_type.ALIAS then
        local buf = { "type " }
        add_typedecl_attrs(tp, buf)
        buf[#buf + 1] = tp:name_get()
        buf[#buf + 1] = ": "
        buf[#buf + 1] = serialize_type(tp:base_type_get())
        buf[#buf + 1] = ";"
        return table.concat(buf)
    elseif tpt == eolian.typedecl_type.FUNCTION_POINTER then
        return "TODO"
    end
    error("unhandled typedecl type: " .. tpt)
end

serialize_tdecl_c = function(tp, ns)
    local tpt = tp:type_get()
    if tpt == eolian.typedecl_type.UNKNOWN then
        error("unknown typedecl: " .. tp:name_get())
    elseif tpt == eolian.typedecl_type.STRUCT or
           tpt == eolian.typedecl_type.STRUCT_OPAQUE then
        local buf = { "typedef struct " }
        local fulln = tp:name_get():gsub("%.", "_");
        keyref.add(fulln, ns, "c")
        buf[#buf + 1] = "_" .. fulln;
        if tpt == eolian.typedecl_type.STRUCT_OPAQUE then
            buf[#buf + 1] = " " .. fulln .. ";"
            return table.concat(buf)
        end
        local fields = tp:struct_fields_get():to_array()
        if #fields == 0 then
            buf[#buf + 1] = " {} " .. fulln .. ";"
            return table.concat(buf)
        end
        buf[#buf + 1] = " {\n"
        for i, fld in ipairs(fields) do
            buf[#buf + 1] = "    "
            buf[#buf + 1] = M.type_cstr_get(fld:type_get(), fld:name_get())
            buf[#buf + 1] = ";\n"
        end
        buf[#buf + 1] = "} " .. fulln .. ";"
        return table.concat(buf)
    elseif tpt == eolian.typedecl_type.ENUM then
        local buf = { "typedef enum" }
        local fulln = tp:name_get():gsub("%.", "_");
        keyref.add(fulln, ns, "c")
        local fields = tp:enum_fields_get():to_array()
        if #fields == 0 then
            buf[#buf + 1] = " {} " .. fulln .. ";"
            return table.concat(buf)
        end
        buf[#buf + 1] = " {\n"
        for i, fld in ipairs(fields) do
            buf[#buf + 1] = "    "
            local cn = fld:c_name_get()
            buf[#buf + 1] = cn
            keyref.add(cn, ns, "c")
            local val = fld:value_get()
            if val then
                buf[#buf + 1] = " = "
                local ev = val:eval(eolian.expression_mask.INT)
                local lit = ev:to_literal()
                buf[#buf + 1] = lit
                local ser = val:serialize()
                if ser and ser ~= lit then
                    buf[#buf + 1] = " /* " .. ser .. " */"
                end
            end
            if i == #fields then
                buf[#buf + 1] = "\n"
            else
                buf[#buf + 1] = ",\n"
            end
        end
        buf[#buf + 1] = "} " .. fulln .. ";"
        return table.concat(buf)
    elseif tpt == eolian.typedecl_type.ALIAS then
        local fulln = tp:name_get():gsub("%.", "_");
        keyref.add(fulln, ns, "c")
        return "typedef "
            .. M.type_cstr_get(tp:base_type_get(), fulln) .. ";"
    elseif tpt == eolian.typedecl_type.FUNCTION_POINTER then
        return "TODO"
    end
    error("unhandled typedecl type: " .. tpt)
end

serialize_var = function(var)
    local buf = {}
    buf[#buf + 1] = "const "
    if var:is_extern() then
        buf[#buf + 1] = "@extern "
    end
    buf[#buf + 1] = var:name_get()
    buf[#buf + 1] = ": "
    buf[#buf + 1] = serialize_type(var:type_get())
    local val = var:value_get()
    if val then
        buf[#buf + 1] = " = "
        buf[#buf + 1] = val:serialize()
    end
    buf[#buf + 1] = ";"
    return table.concat(buf)
end

serialize_var_c = function(var, ns)
    local buf = {}
    local bt = var:type_get()
    local fulln = var:name_get():gsub("%.", "_"):upper()
    keyref.add(fulln, ns, "c")
    buf[#buf + 1] = "#define "
    buf[#buf + 1] = fulln
    buf[#buf + 1] = " "
    local val = var:value_get()
    local vt = val:eval()
    local lv = vt:to_literal()
    local sv = val:serialize()
    buf[#buf + 1] = lv
    if lv ~= sv then
        buf[#buf + 1] = "/* " .. sv .. " */"
    end
    return table.concat(buf)
end

local sert = {
    [eolian.object_type.TYPE] = serialize_type,
    [eolian.object_type.TYPEDECL] = serialize_tdecl,
    [eolian.object_type.CONSTANT] = serialize_var
}

M.obj_serialize = function(obj)
    local f = sert[obj:object_get():type_get()]
    assert(f, "cannot serialize object")
    return f(obj)
end

local sertc = {
    [eolian.object_type.TYPEDECL] = serialize_tdecl_c,
    [eolian.object_type.CONSTANT] = serialize_var_c
}

M.obj_serialize_c = function(obj, ns)
    local f = sertc[obj:object_get():type_get()]
    assert(f, "cannot serialize object")
    return f(obj, ns)
end

local gen_cparam = function(par, out)
    local part = par:type_get()
    out = out or (par:direction_get() == eolian.parameter_dir.OUT)
    local tstr = part:c_type_get()
    if out then
        tstr = M.type_cstr_get(tstr, "*")
    end
    return M.type_cstr_get(tstr, par:short_name_get())
end

local get_func_csig_part = function(cn, tp)
    if not tp then
        return "void " .. cn
    end
    return M.type_cstr_get(tp, cn)
end

M.function_serialize_c = function(f, ftype)
    ftype = ftype or eolian.function_type.METHOD
    assert(ftype ~= eolian.function_type.PROPERTY)

    local cn = f:full_c_name_get(ftype)
    local rtype = f:return_type_get(ftype)

    local fparam = "Eo *obj"
    if f:is_static() then
        fparam = nil
    elseif f:is_const() or ftype == eolian.function_type.PROP_GET then
        fparam = "const Eo *obj"
    end

    if f:type_get() == eolian.function_type.METHOD then
        local pars = f:parameters_get():to_array()
        local cnrt = get_func_csig_part(cn, rtype)
        for i = 1, #pars do
            pars[i] = gen_cparam(pars[i])
        end
        if fparam then
            table.insert(pars, 1, fparam);
        end
        return cnrt .. "(" .. table.concat(pars, ", ") .. ");"
    end

    local keys = f:property_keys_get(ftype):to_array()
    local vals = f:property_values_get(ftype):to_array()

    if ftype == eolian.function_type.PROP_SET then
        local cnrt = get_func_csig_part(cn, rtype)
        local pars = {}
        for i, par in ipairs(keys) do
            pars[#pars + 1] = gen_cparam(par)
        end
        for i, par in ipairs(vals) do
            pars[#pars + 1] = gen_cparam(par)
        end
        if fparam then
            table.insert(pars, 1, fparam);
        end
        return cnrt .. "(" .. table.concat(pars, ", ") .. ");"
    end

    -- getters
    local cnrt
    if not rtype then
        if #vals == 1 then
            cnrt = get_func_csig_part(cn, vals[1]:type_get())
            table.remove(vals, 1)
        else
            cnrt = get_func_csig_part(cn)
        end
    else
        cnrt = get_func_csig_part(cn, rtype)
    end
    local pars = {}
    for i, par in ipairs(keys) do
        pars[#pars + 1] = gen_cparam(par)
    end
    for i, par in ipairs(vals) do
        pars[#pars + 1] = gen_cparam(par, true)
    end
    table.insert(pars, 1, fparam);
    return cnrt .. "(" .. table.concat(pars, ", ") .. ");"
end

local gen_func_namesig = function(fn, cl, buf, isprop, isget, isset)
    if isprop then
        buf[#buf + 1] = "@property "
    end
    buf[#buf + 1] = fn:name_get()
    buf[#buf + 1] = " "
    if fn:is_beta() then
        buf[#buf + 1] = "@beta "
    end
    if not isprop then
        if fn:scope_get(eolian.function_type.METHOD) == eolian.object_scope.PROTECTED then
            buf[#buf + 1] = "@protected "
        end
    elseif isget and isset then
        if fn:scope_get(eolian.function_type.PROP_GET) == eolian.object_scope.PROTECTED and
           fn:scope_get(eolian.function_type.PROP_SET) == eolian.object_scope.PROTECTED then
            buf[#buf + 1] = "@protected "
        end
    end
    if fn:is_static() then
        buf[#buf + 1] = "@static "
    end
    if fn:is_const() then
        buf[#buf + 1] = "@const "
    end
end

local gen_func_param = function(fp, buf, nodir)
    -- TODO: default value
    buf[#buf + 1] = "        "
    if not nodir then
        buf[#buf + 1] = "@" .. M.param_get_dir_name(fp) .. " "
    end
    buf[#buf + 1] = fp:name_get()
    buf[#buf + 1] = ": "
    buf[#buf + 1] = M.obj_serialize(fp:type_get())
    local dval = fp:default_value_get()
    if dval then
        buf[#buf + 1] = " ("
        buf[#buf + 1] = dval:serialize()
        buf[#buf + 1] = ")"
    end
    if fp:is_optional() then
        buf[#buf + 1] = " @optional"
    end
    buf[#buf + 1] = ";\n"
end

local gen_func_return = function(fp, ftype, buf, indent)
    local rett = fp:return_type_get(ftype)
    if not rett then
        return
    end
    buf[#buf + 1] = indent and ("    "):rep(indent) or "    "
    buf[#buf + 1] = "return: "
    buf[#buf + 1] = M.obj_serialize(rett)
    local dval = fp:return_default_value_get(ftype)
    if dval then
        buf[#buf + 1] = " ("
        buf[#buf + 1] = dval:serialize()
        buf[#buf + 1] = ")"
    end
    if not fp:return_allow_unused(ftype) then
        buf[#buf + 1] = " @no_unused"
    end
    buf[#buf + 1] = ";\n"
end

M.method_serialize = function(fn, cl)
    local buf = {}
    gen_func_namesig(fn, cl, buf, false, false, false)

    local fimp = fn:implement_get()

    if fimp:is_pure_virtual(eolian.function_type.METHOD) then
        buf[#buf + 1] = "@pure_virtual "
    end
    buf[#buf + 1] = "{"
    local params = fn:parameters_get():to_array()
    local rtp = fn:return_type_get(eolian.function_type.METHOD)
    if #params == 0 and not rtp then
        buf[#buf + 1] = "}"
        return table.concat(buf)
    end
    buf[#buf + 1] = "\n"
    if #params > 0 then
        buf[#buf + 1] = "    params {\n"
        for i, fp in ipairs(params) do
            gen_func_param(fp, buf)
        end
        buf[#buf + 1] = "    }\n"
    end
    gen_func_return(fn, eolian.function_type.METHOD, buf)
    buf[#buf + 1] = "}"
    return table.concat(buf)
end

local eovals_check_same = function(a1, a2)
    if #a1 ~= #a2 then return false end
    for i, v in ipairs(a1) do
        if v ~= a2[i] then return false end
    end
    return true
end

local gen_prop_keyvals = function(tbl, kword, buf, indent)
    local ind = indent and ("    "):rep(indent) or "    "
    if #tbl == 0 then return end
    buf[#buf + 1] = "    "
    buf[#buf + 1] = ind
    buf[#buf + 1] = kword
    buf[#buf + 1] = " {\n"
    for i, v in ipairs(tbl) do
        buf[#buf + 1] = ind
        gen_func_param(v, buf, true)
    end
    buf[#buf + 1] = "    "
    buf[#buf + 1] = ind
    buf[#buf + 1] = "}\n"
end

M.property_serialize = function(fn, cl)
    local buf = {}
    local fnt = fn:type_get()
    local isget = (fnt == eolian.function_type.PROPERTY or fnt == eolian.function_type.PROP_GET)
    local isset = (fnt == eolian.function_type.PROPERTY or fnt == eolian.function_type.PROP_SET)
    gen_func_namesig(fn, cl, buf, true, isget, isset)

    local pimp = fn:implement_get()

    local gvirt = pimp:is_pure_virtual(eolian.function_type.PROP_GET)
    local svirt = pimp:is_pure_virtual(eolian.function_type.PROP_SET)

    if (not isget or gvirt) and (not isset or svirt) then
        buf[#buf + 1] = "@pure_virtual "
    end

    local gkeys = isget and fn:property_keys_get(eolian.function_type.PROP_GET):to_array() or {}
    local skeys = isset and fn:property_keys_get(eolian.function_type.PROP_SET):to_array() or {}
    local gvals = isget and fn:property_values_get(eolian.function_type.PROP_GET):to_array() or {}
    local svals = isget and fn:property_values_get(eolian.function_type.PROP_SET):to_array() or {}
    local grtt = isget and fn:return_type_get(eolian.function_type.PROP_GET) or nil
    local srtt = isset and fn:return_type_get(eolian.function_type.PROP_SET) or nil

    local keys_same = eovals_check_same(gkeys, skeys)
    local vals_same = eovals_check_same(gvals, svals)

    buf[#buf + 1] = "{\n"

    if isget then
        buf[#buf + 1] = "    get "
        if fn:scope_get(eolian.function_type.PROP_GET) == eolian.object_scope.PROTECTED and
           fn:scope_get(eolian.function_type.PROP_SET) ~= eolian.object_scope.PROTECTED then
            buf[#buf + 1] = "@protected "
        end
        buf[#buf + 1] = "{"
        if (#gkeys == 0 or keys_same) and (#gvals == 0 or vals_same) and
           (not grtt or grtt == srtt) then
            buf[#buf + 1] = "}\n"
        else
            buf[#buf + 1] = "\n"
            if not keys_same then gen_prop_keyvals(gkeys, "keys", buf) end
            if not vals_same then gen_prop_keyvals(gvals, "values", buf) end
            if grtt ~= srtt then
                gen_func_return(fn, eolian.function_type.PROP_GET, buf, 2)
            end
            buf[#buf + 1] = "    }\n"
        end
    end

    if isset then
        buf[#buf + 1] = "    set "
        if fn:scope_get(eolian.function_type.PROP_SET) == eolian.object_scope.PROTECTED and
           fn:scope_get(eolian.function_type.PROP_GET) ~= eolian.object_scope.PROTECTED then
            buf[#buf + 1] = "@protected "
        end
        buf[#buf + 1] = "{"
        if (#skeys == 0 or keys_same) and (#svals == 0 or vals_same) and
           (not srtt or grtt == srtt) then
            buf[#buf + 1] = "}\n"
        else
            buf[#buf + 1] = "\n"
            if not keys_same then gen_prop_keyvals(skeys, "keys", buf) end
            if not vals_same then gen_prop_keyvals(svals, "values", buf) end
            if grtt ~= srtt then
                gen_func_return(fn, eolian.function_type.PROP_SET, buf, 2)
            end
            buf[#buf + 1] = "    }\n"
        end
    end

    if keys_same then gen_prop_keyvals(gkeys, "keys", buf, 0) end
    if vals_same then gen_prop_keyvals(gvals, "values", buf, 0) end

    buf[#buf + 1] = "}"
    return table.concat(buf)
end

M.impl_is_overridden = function(obj, cl)
    return obj:class_get() ~= cl
end

M.impl_fallback_doc_get = function(obj)
    local ig, is = obj:is_prop_get(), obj:is_prop_set()
    if ig and not is then
        return obj:documentation_get(eolian.function_type.PROP_GET)
    elseif is and not ig then
        return obj:documentation_get(eolian.function_type.PROP_SET)
    end
    return obj:documentation_get(eolian.function_type.PROPERTY)
end

local revh = {}

M.class_children_get = function(cl)
    return revh[cl:name_get()] or {}
end

local build_child = function(icl, cl)
    local icln = icl:name_get()
    local t = revh[icln]
    if not t then
        t = {}
        revh[icln] = t
    end
    t[#t + 1] = cl
end

M.build_class_children = function(eos)
    for cl in eos:classes_get() do
        local pcl = cl:parent_get()
        if pcl then
            build_child(pcl, cl)
        end
        for icl in cl:extensions_get() do
            build_child(icl, cl)
        end
    end
end

-- finds all stuff that is callable on a class, respecting
-- overrides and not duplicating, does a depth-first search
local find_callables

local callable_cb = function(pcl, omeths, events, written, no_overrides)
    for impl in pcl:implements_get() do
        if not no_overrides or impl:class_get() == pcl then
            local func = impl:function_get()
            local fid = M.obj_id_get(func)
            if not written[fid] then
                omeths[#omeths + 1] = { pcl, impl }
                written[fid] = true
            end
        end
    end
    for ev in pcl:events_get() do
        local evid = ev:name_get()
        if not written[evid] then
            events[#events + 1] = { pcl, ev }
            written[evid] = true
        end
    end
    find_callables(pcl, omeths, events, written, no_overrides)
end

find_callables = function(cl, omeths, events, written, no_overrides)
    local pcl = cl:parent_get()
    if pcl then
        callable_cb(pcl, omeths, events, written, no_overrides)
    end
    for pcl in cl:extensions_get() do
        callable_cb(pcl, omeths, events, written, no_overrides)
    end
end

M.impl_has_doc = function(impl)
    if impl:documentation_get(eolian.function_type.PROP_GET) or
       impl:documentation_get(eolian.function_type.PROP_SET) or
       impl:documentation_get(eolian.function_type.UNRESOLVED)
    then
        return true
    end
    return false
end

local has_custom_doc = function(impl, cl, no_overrides)
    -- we don't care, let it pass
    if not no_overrides then
        return true
    end
    -- defined in this class, always has its own doc
    if impl:class_get() == cl then
        return true
    end
    -- otherwise check if this has *any* part of doc...
    return M.impl_has_doc(impl)
end

M.callables_get = function(cl, no_overrides)
    local written = {}
    local meths, omeths, evs = {}, {}, {}
    for impl in cl:implements_get() do
        if has_custom_doc(impl, cl, no_overrides) then
            local ifunc = impl:function_get()
            written[M.obj_id_get(ifunc)] = true
            meths[#meths + 1] = { cl, impl }
        end
    end
    find_callables(cl, omeths, evs, written, no_overrides)
    return meths, omeths, evs
end

M.sorted_funclist_get = function(tcl, tbl)
    if #tbl == 0 then
        return
    end
    local nt = {}
    for i, implt in ipairs(tbl) do
        local cl, impl = unpack(implt)
        local func = impl:function_get()

        local wt = {}
        wt[0] = cl
        wt[1] = func
        wt[2] = impl

        nt[#nt + 1] = wt
    end

    local get_best_scope = function(f)
        local ft = f:type_get()
        if ft == eolian.function_type.PROPERTY then
            local fs1, fs2 = f:scope_get(eolian.function_type.PROP_GET), f:scope_get(eolian.function_type.PROP_SET)
            if fs1 == eolian.object_scope.PUBLIC or fs2 == eolian.object_scope.PUBLIC then
                return eolian.object_scope.PUBLIC
            elseif fs1 == eolian.object_scope.PROTECTED or fs2 == eolian.object_scope.PROTECTED then
                return eolian.object_scope.PROTECTED
            else
                return eolian.object_scope.PRIVATE
            end
        else
            return f:scope_get(ft)
        end
    end
    table.sort(nt, function(v1, v2)
        local cl1, cl2 = v1[0], v2[0]
        if cl1 ~= cl2 then
            return cl1:name_get() < cl2:name_get()
        end

        local f1, f2 = v1[1], v2[1]
        local f1s, f2s = get_best_scope(f1), get_best_scope(f2)
        if f1s ~= f2s then
            if f1s ~= eolian.object_scope.PROTECED then
                -- public funcs go first, private funcs go last
                return f1s == eolian.object_scope.PUBLIC
            else
                -- protected funcs go second
                return f2s == eolian.object_scope.PRIVATE
            end
        end
        return f1:name_get() < f2:name_get()
    end)

    return nt
end

local find_parent_impl

local parent_impl_cb = function(pcl, fulln)
    for impl in pcl:implements_get() do
        if impl:name_get() == fulln then
            return impl, pcl
        end
    end
    local pimpl, pcl = find_parent_impl(fulln, pcl)
    if pimpl then
        return pimpl, pcl
    end
end

find_parent_impl = function(fulln, cl)
    local pcl = cl:parent_get()
    if pcl then
        local rimp, rcl = parent_impl_cb(pcl, fulln)
        if rimp then return rimp, rcl end
    end
    for pcl in cl:extensions_get() do
        local rimp, rcl = parent_impl_cb(pcl, fulln)
        if rimp then return rimp, rcl end
    end
    return nil, cl
end

M.parent_impl_get = function(fulln, cl)
    return find_parent_impl(fulln, cl)
end

local find_parent_doc
find_parent_doc = function(fulln, cl, ftype)
    local pimpl, pcl = find_parent_impl(fulln, cl)
    if not pimpl then
        return nil
    end
    local pdoc = pimpl:documentation_get(ftype)
    if not pdoc then
        return find_parent_doc(fulln, pcl, ftype)
    end
    return pdoc
end

M.parent_doc_get = function(impl, cl, ftype)
    local doc = impl:documentation_get(ftype)
    if doc then
        return doc
    end
    if not M.impl_is_overridden(impl, cl) then
        return nil
    end
    local pimp = impl:function_get():implement_get()
    if (ftype == eolian.function_type.PROP_GET and not pimp:is_prop_get()) or
       (ftype == eolian.function_type.PROP_SET and not pimp:is_prop_set())
    then
        return nil
    end
    return find_parent_doc(impl:name_get(), cl, ftype)
end

local impls_of = {}

local get_all_impls_of
get_all_impls_of = function(tbl, cl, fn, got)
    local cfn = cl:name_get()
    if got[cfn] then
        return
    end
    got[cfn] = true
    for imp in cl:implements_get() do
        local ofn = imp:function_get()
        if ofn == fn then
            tbl[#tbl + 1] = imp
            break
        end
    end
    for i, icl in ipairs(M.class_children_get(cl)) do
        get_all_impls_of(tbl, icl, fn, got)
    end
end

M.impls_of_get = function(fn)
    local fnn = fn:name_get()
    local ocl = fn:implement_get():class_get()
    local onm = ocl:name_get() .. "." .. fnn
    local imps = impls_of[onm]
    if not imps then
        imps = {}
        impls_of[onm] = imps
        get_all_impls_of(imps, ocl, fn, {})
    end
    return imps
end

return M