% permitloadlib=true

%D \module
%D   [       file=libs-imp-foreign,
%D        version=2021.03.10,
%D          title=\CONTEXT\ Extra Modules,
%D       subtitle=Basic FFI,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

%D This module was added as a side track of a user wanting to run a library that is
%D not (has support) built in. In order to identify issues I wondered if we could
%D have an additional feature to the already present optional libraries (of course
%D with the usual loadlib protection. Keep in mind that loading libraries creates a
%D dependency and an api can change. And in a long term program like any \TEX\
%D program we don't want that (at least I don't want users to be forced to install
%D lots of additional source code and dependencies in order to compile \LUAMETATEX\
%D successfully.
%D
%D So, I looked around for alternatives to ffi and ran into the (stable since 5
%D years) alien module (by Fabio Mascarenhas) that uses the rather portable libffi
%D but it does have some dependencies. For callbacks you need to set some parameters
%D normally dealt with when configuring and compiling which does creates a
%D dependency. So, in the end I just took its keyword driven approach but wrapped it
%D in alternative code that more matches other modules and assumes \LUA\ 5.4.
%D
%D Additional features like arrays and structs will be implemented when we need them
%D using modern \LUA\ 5.4 features (string packing, toclose, etc). For now I
%D consider all this an experiment and will pick up the thread when I have an
%D example.
%D
%D So, how far do we go? I think as soon as a library becomes more complex, say with
%D multi|-|dimensional arrays one should just write a proper interface. So we limit
%D ourselves here. One problem with more complex datastructures is that it opens the
%D door to abuse thanks to uncontrolled memory access.

\registerctxluafile{libs-imp-foreign}{autosuffix}

\continueifinputfile{libs-imp-foreign.mkxl}

%D The difference in performance is not that significant because the time spent in
%D the called function is the bottleneck here.

\usemodule[article-basic] \setupbodyfont[8pt] \noheaderandfooterlines

\starttext

\startluacode

    local NC, BC, NR = context.NC, context.BC, context.NR

    function document.identify(kpse,set_program_name,find_file)
        context.starttabulate { "|l|lp|" }
            BC() context.type("kpse")             NC() context.typ(tostring(kpse))             NC() NR()
            BC() context.type("set_program_name") NC() context.typ(tostring(set_program_name)) NC() NR()
            BC() context.type("find_file")        NC() context.typ(tostring(find_file))        NC() NR()
        context.stoptabulate()
    end

    function document.lookup(find_file,filename,filetype,present,n)

        local c = os.clock()
        for i=1,n do
            if find_file(filename,filetype,present) then
                -- okay
            end
        end
        c = os.clock() - c

        context.starttabulate()
            BC() context("asked") NC() context.type(filename)                             NC() NR()
            BC() context("found") NC() context.type(find_file(filename,filetype,present)) NC() NR()
            if n > 0 then
                BC() context("times")   NC() context(n)            NC() NR()
                BC() context("seconds") NC() context(" %0.3f",c)   NC() NR()
                BC() context("lookup")  NC() context(" %0.6f",c/n) NC() NR()
            end
        context.stoptabulate()
    end
\stopluacode

\starttitle[title=kpse via foreign]

\startluacode

    local foreign = optional.loaded.foreign

    local kplib = (os.platform == "win64" and "kpathsea*w64")
               or (os.platform == "win32" and "kpathsea*w32")
               or "libkpathsea"

    local kpse  = foreign.load(kplib)

    local set_program_name = kpse:register {
        name      = "kpse_set_program_name",
        arguments = { "string", "string" }
    }

    local find_file = kpse:register {
        name      = "kpse_find_file",
        arguments = { "string", "int", "int" },
        result    = "string",
    }
    local path_expand = kpse:register {
        name      = "kpse_path_expand",
        arguments = { "string" },
        result    = "string",
    }
    local all_path_search = kpse:register {
        name      = "kpse_all_path_search",
        arguments = { "string", "string" },
        result    = "pointer",
        finalizer = function(p)
            return foreign.totable(p,"string") -- unknown n, so NULL terminated
        end
    }

 -- print(kpse)
 -- print(set_program_name)
 -- print(kpse:registered("kpse_set_program_name"))
 -- print(kpse:available("kpse_set_program_name"))
 -- print(kpse:available("set_program_name"))
 -- print(kpse:available("kpse_find_file"))

 -- inspect(kpse:registered ())
 -- inspect(foreign.types())
 -- inspect(foreign.abivalues())

    local set_program_name = kpse:registered("kpse_set_program_name")
    local find_file        = kpse:registered("kpse_find_file")

    set_program_name("luatex","luatex")

    document.lookup(find_file, "libs-imp-foreign.mkxl", 26, 0, 100000)
    document.lookup(find_file, "oeps.tex",              26, 0,  10000)
    document.lookup(find_file, "metafun.mp",            16, 0,   5000)
    document.lookup(find_file, "logo10.afm",             4, 0,   2500)

    document.identify(kpse, set_program_name, find_file)

 -- set_program_name("pdftex","pdftex")
 --
 -- local t = path_expand("$TEXINPUTS")
 -- local p = all_path_search(t,"oeps.tex")
 --
 -- inspect(t)
 -- inspect(p)

\stopluacode

\stoptitle

\registerctxluafile{libs-imp-kpse}{autosuffix}

\starttitle[title=kpse via optional / string]

\startluacode

    local kpse = optional.loaded.kpse

    local set_program_name = kpse.set_program_name
    local find_file        = kpse.find_file

    kpse.set_program_name("luatex")

    document.lookup(find_file, "libs-imp-foreign.mkxl", "tex", false, 100000)
    document.lookup(find_file, "oeps.tex",              "tex", false,  10000)
    document.lookup(find_file, "metafun.mp",            "mp",  false,   5000)
    document.lookup(find_file, "logo10.afm",            "afm", false,   2500)

    document.identify(kpse, set_program_name, find_file)

\stopluacode

\stoptitle

\starttitle[title=kpse via optional / number]

\startluacode

    local kpse = optional.loaded.kpse

    local set_program_name = kpse.set_program_name
    local find_file        = kpse.find_file

    kpse.set_program_name("luatex")

    document.lookup(find_file, "libs-imp-foreign.mkxl", 26, false, 100000)
    document.lookup(find_file, "oeps.tex",              26, false,  10000)
    document.lookup(find_file, "metafun.mp",            16, false,   5000)
    document.lookup(find_file, "logo10.afm",             4, false,   2500)

    document.identify(kpse, set_program_name, find_file)

\stopluacode

\stoptitle

\stoptext
