% \iffalse meta-comment
%
% Written in 2009, 2010 by Manuel Pégourié-Gonnard and Élie Roux.
%     <mpg@elzevir.fr>
%     <elie.roux@telecom-bretagne.eu>
%
% This work is under the CC0 license.
%
% This work consists of the main source file luatexbase-mcb.dtx
% and the derived files
%    luatexbase-mcb.sty, mcb.lua, luatexbase-mcb.pdf,
%    test-mcb-plain.tex test-mcb-latex.tex
%
% Unpacking:
%    tex luatexbase-mcb.dtx
% Documentation:
%    pdflatex luatexbase-mcb.dtx
%
%<*ignore>
\begingroup
  \def\x{LaTeX2e}%
\expandafter\endgroup
\ifcase 0\ifx\install y1\fi\expandafter
         \ifx\csname processbatchFile\endcsname\relax\else1\fi
         \ifx\fmtname\x\else 1\fi\relax
\else\csname fi\endcsname
%</ignore>
%<*install>
\input docstrip.tex

\keepsilent
\askforoverwritefalse

\let\MetaPrefix\relax

\preamble

Copyright (C) 2009 by Elie Roux <elie.roux@telecom-bretagne.eu>

This work is under the CC0 license.
See source file '\inFileName' for details.

\endpreamble

\let\MetaPrefix\DoubleperCent

\generate{%
  \usedir{tex/luatex/luatexbase}%
  \file{luatexbase-mcb.sty}{\from{luatexbase-mcb.dtx}{texpackage}}%
}

\generate{%
  \usedir{doc/luatex/luatexbase}%
  \file{test-mcb-plain.tex}{\from{luatexbase-mcb.dtx}{testplain}}%
  \file{test-mcb-latex.tex}{\from{luatexbase-mcb.dtx}{testlatex}}%
}

\def\MetaPrefix{-- }

\def\luapostamble{%
  \MetaPrefix^^J%
  \MetaPrefix\space End of File `\outFileName'.%
}

\def\currentpostamble{\luapostamble}%

\generate{%
  \usedir{tex/luatex/luatexbase}%
  \file{mcb.lua}{\from{luatexbase-mcb.dtx}{lua}}%
}

\obeyspaces
\Msg{************************************************************************}
\Msg{*}
\Msg{* To finish the installation you have to move the following}
\Msg{* files into a directory searched by TeX:}
\Msg{*}
\Msg{*     luatexbase-mcb.sty mcb.lua}
\Msg{*}
\Msg{* Happy TeXing!}
\Msg{*}
\Msg{************************************************************************}

\endbatchfile
%</install>
%<*ignore>
\fi
%</ignore>
%<*driver>
\documentclass{ltxdoc}
\input{lltxb-dtxstyle}
\begin{document}
  \DocInput{luatexbase-mcb.dtx}%
\end{document}
%</driver>
% \fi
%
% \CheckSum{0}
%
% \CharacterTable
%  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%   Lower-case    \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%   Digits        \0\1\2\3\4\5\6\7\8\9
%   Exclamation   \!     Double quote  \"     Hash (number) \#
%   Dollar        \$     Percent       \%     Ampersand     \&
%   Acute accent  \'     Left paren    \(     Right paren   \)
%   Asterisk      \*     Plus          \+     Comma         \,
%   Minus         \-     Point         \.     Solidus       \/
%   Colon         \:     Semicolon     \;     Less than     \<
%   Equals        \=     Greater than  \>     Question mark \?
%   Commercial at \@     Left bracket  \[     Backslash     \\
%   Right bracket \]     Circumflex    \^     Underscore    \_
%   Grave accent  \`     Left brace    \{     Vertical bar  \|
%   Right brace   \}     Tilde         \~}
%
% \title{The \textsf{luatexbase-mcb} package}
% \date{2009/09/18 v0.93}
% \author{%
%   Manuel P\'egouri\'e-Gonnard \\ \email{mpg@elzevir.fr} \and
%   \'Elie Roux \\ \email{elie.roux@telecom-bretagne.eu}}
%
% \maketitle
%
% \begin{abstract}
% This package manages the callback adding and removing, by adding
% \texttt{callback.add} and \texttt{callback.remove}, and overwriting
% \texttt{callback.register}. It also allows to create and call new callbacks.
% For an introduction on this package (among others), please refer to the
% document \texttt{luatextra-reference.pdf}.
% \par\textbf{Warning.} Currently assumes that \textsf{luatexbase-modutils}
% has been previously loaded. (This is a temporary limitation.)
% \end{abstract}
%
% \tableofcontents
%
% \section{Documentation}
%
% Lua\TeX\ provides an extremely interesting feature, named callbacks. It
% allows to call some lua functions at some points of the \TeX\ algorithm (a
% \emph{callback}), like when \TeX\ breaks likes, puts vertical spaces, etc.
% The Lua\TeX\ core offers a function called \texttt{callback.register} that
% enables to register a function in a callback.
%
% The problem with \texttt{callback.register} is that is registers only one
% function in a callback. For a lot of callbacks it can be common to have
% several packages registering their function in a callback, and thus it is
% impossible with them to be compatible with each other.
%
% This package solves this problem by adding mainly one new function
% \texttt{callback.\\add} that adds a function in a callback. With this
% function it is possible for packages to register their function in a
% callback without overwriting the functions of the other packages.
%
% The functions are called in a certain order, and when a package registers a
% callback it can assign a priority to its function. Conflicts can still
% remain even with the priority mechanism, for example in the case where two
% packages want to have the highest priority. In these cases the packages have
% to solve the conflicts themselves.
%
% This package also privides a way to create and call new callbacks, in
% addition to the default Lua\TeX\ callbacks.
%
% \subsubsection*{Limitations}
%
% This package only works for callbacks where it's safe to add multiple
% functions without changing the functions' signatures. There are callbacks,
% though, where registering several functions is not possible without changing
% the function's signatures, like for example the readers callbacks. These
% callbacks take a filename and give the datas in it. One solution would be to
% change the functions' signature to open it when the function is the first,
% and to take the datas and modify them eventually if they are called after
% the first. But it seems rather fragile and useless, so it's not implemented.
% With these callbacks, in this package we simply execute the first function
% in the list.
%
% Other callbacks in this case are \texttt{define\_font} and
% \texttt{open\_read\_file}. There is though a solution for several packages
% to use these callbacks, see the implementation of \texttt{luatextra}.
%
%    \section{Implementation}
%
%    \subsection{\tex package}
%
%    \begin{macrocode}
%<*texpackage>
%    \end{macrocode}
%
%    \subsubsection{Preliminaries}
%
%    Reload protection, especially for \plaintex.
%
%    \begin{macrocode}
                \csname lltxb@mcb@loaded\endcsname
\expandafter\let\csname lltxb@mcb@loaded\endcsname\endinput
%    \end{macrocode}
%
%    Catcode defenses.
%
%    \begin{macrocode}
\begingroup
  \catcode123 1 % {
  \catcode125 2 % }
  \catcode 35 6 % #
  \toks0{}%
  \def\x{}%
  \def\y#1 #2 {%
    \toks0\expandafter{\the\toks0 \catcode#1 \the\catcode#1}%
    \edef\x{\x \catcode#1 #2}}%
  \y 123 1  % {
  \y 125 2  % }
  \y  35 6  % #
  \y  10 12 % ^^J
  \y  34 12 % "
  \y  36 3  % $ $
  \y  39 12 % '
  \y  40 12 % (
  \y  41 12 % )
  \y  42 12 % *
  \y  43 12 % +
  \y  44 12 % ,
  \y  45 12 % -
  \y  46 12 % .
  \y  47 12 % /
  \y  60 12 % <
  \y  61 12 % =
  \y  64 11 % @ (letter)
  \y  62 12 % >
  \y  95 12 % _ (other)
  \y  96 12 % `
  \edef\y#1{\endgroup\edef#1{\the\toks0\relax}\x}%
\expandafter\y\csname lltxb@mcb@AtEnd\endcsname
%    \end{macrocode}
%
%    Package declaration.
%
%    \begin{macrocode}
\begingroup
  \expandafter\ifx\csname ProvidesPackage\endcsname\relax
    \def\x#1[#2]{\immediate\write16{Package: #1 #2}}
  \else
    \let\x\ProvidesPackage
  \fi
\expandafter\endgroup
\x{luatexbase-mcb}[2010/09/11 v0.93  Callback management for LuaTeX  (mpg)]
%    \end{macrocode}
%
%    Make sure \luatex is used.
%
%    \begin{macrocode}
\begingroup\expandafter\expandafter\expandafter\endgroup
\expandafter\ifx\csname RequirePackage\endcsname\relax
  \input ifluatex.sty
\else
  \RequirePackage{ifluatex}
\fi
\ifluatex\else
  \begingroup
    \expandafter\ifx\csname PackageWarningNoLine\endcsname\relax
      \def\x#1#2{\begingroup\newlinechar10
        \immediate\write16{Package #1 warning: #2}\endgroup}
    \else
      \let\x\PackageWarningNoLine
    \fi
  \expandafter\endgroup
  \x{luatexbase-mcb}{LuaTeX is required for this package. Aborting.}
  \lltxb@mcb@AtEnd
  \expandafter\endinput
\fi
%    \end{macrocode}
%
%    \subsubsection{Load supporting Lua module}
%
%    First load \pk{luatexbase-loader} (hence \pk{luatexbase-compat}), then
%    the supporting Lua module.
%
%    \begin{macrocode}
\begingroup\expandafter\expandafter\expandafter\endgroup
\expandafter\ifx\csname RequirePackage\endcsname\relax
  \input luatexbase-modutils.sty
\else
  \RequirePackage{luatexbase-modutils}
\fi
\luatexbase@directlua{require('luatexbase.mcb')}
%    \end{macrocode}
%
%    That's all folks!
%
%    \begin{macrocode}
\lltxb@mcb@AtEnd
%</texpackage>
%    \end{macrocode}
%
%    \subsection{Lua module}
%
%    \begin{macrocode}
%<*lua>
%    \end{macrocode}
%
%    \subsubsection{Module identification}
%
%    \begin{macrocode}
module('luatexbase', package.seeall)
luatexbase.provides_module({
    name          = "luamcallbacks",
    version       = 0.93,
    date          = "2009/09/18",
    description   = "register several functions in a callback",
    author        = "Hans Hagen, Elie Roux and Manuel Pégourie-Gonnard",
    copyright     = "Hans Hagen, Elie Roux and Manuel Pégourie-Gonnard",
    license       = "CC0",
})
%    \end{macrocode}
%
%    Shortcuts for error functions.
%
%    \begin{macrocode}
local log = log or function(...)
  luatexbase.module_log('luamcallbacks', string.format(...))
end
local info = info or function(...)
  luatexbase.module_info('luamcallbacks', string.format(...))
end
local warning = warning or function(...)
  luatexbase.module_warning('luamcallbacks', string.format(...))
end
local err = err or function(...)
  luatexbase.module_error('luamcallbacks', string.format(...))
end
%    \end{macrocode}
%
%    \subsubsection{Initialisations}
%
%    \texttt{callbacklist} is the main list, that contains the callbacks as
%    keys and a table of the registered functions a values.
%
%    \begin{macrocode}
local callbacklist = callbacklist or { }
%    \end{macrocode}
%
%    A table with the default functions of the created callbacks. See
%    \texttt{create} for further informations.
%
%    \begin{macrocode}
local lua_callbacks_defaults = { }
%    \end{macrocode}
%
%    There are 4 types of callback:
%    \begin{itemize}
%    \item the ones taking a list of nodes and returning a boolean and
%    eventually a new head (\texttt{list})
%    \item the ones taking datas and returning the modified ones
%    (\texttt{data})
%    \item the ones that can't have multiple functions registered in them
%    (\texttt{first})
%    \item the ones for functions that don't return anything (\texttt{simple})
%    \end{itemize}
%
%    \begin{macrocode}
local list = 1
local data = 2
local first = 3
local simple = 4
%    \end{macrocode}
%
%    \texttt{callbacktypes} is the list that contains the callbacks as keys
%    and the type (list or data) as values.
%
%    \begin{macrocode}
local callbacktypes = callbacktypes or {
  buildpage_filter = simple,
  token_filter = first,
  pre_output_filter = list,
  hpack_filter = list,
  process_input_buffer = data,
  mlist_to_hlist = list,
  vpack_filter = list,
  define_font = first,
  open_read_file = first,
  linebreak_filter = list,
  post_linebreak_filter = list,
  pre_linebreak_filter = list,
  start_page_number = simple,
  stop_page_number = simple,
  start_run = simple,
  show_error_hook = simple,
  stop_run = simple,
  hyphenate = simple,
  ligaturing = simple,
  kerning = data,
  find_write_file = first,
  find_read_file = first,
  find_vf_file = data,
  find_map_file = data,
  find_format_file = data,
  find_opentype_file = data,
  find_output_file = data,
  find_truetype_file = data,
  find_type1_file = data,
  find_data_file = data,
  find_pk_file = data,
  find_font_file = data,
  find_image_file = data,
  find_ocp_file = data,
  find_sfd_file = data,
  find_enc_file = data,
  read_sfd_file = first,
  read_map_file = first,
  read_pk_file = first,
  read_enc_file = first,
  read_vf_file = first,
  read_ocp_file = first,
  read_opentype_file = first,
  read_truetype_file = first,
  read_font_file = first,
  read_type1_file = first,
  read_data_file = first,
}
%    \end{macrocode}
%
%    In Lua\TeX\ version 0.43, a new callback called |process_output_buffer|
%    appeared, so we enable it. Test the version using the compat package for,
%    well, compatibility.
%
%    \begin{macrocode}
if luatexbase.luatexversion > 42 then
    callbacktypes["process_output_buffer"] = data
end
%    \end{macrocode}
%
%    As we overwrite \texttt{callback.register}, we save it as
%    \texttt{internalregister}.
%
%    \begin{macrocode}
local internalregister = internalregister or callback.register
%    \end{macrocode}
%
%    \subsubsection{Unsorted stuff}
%
%    A simple function we'll use later to understand the arguments of the
%    \texttt{create} function. It takes a string and returns the type
%    corresponding to the string or nil.
%
%    \begin{macrocode}
local function str_to_type(str)
    if str == 'list' then
        return list
    elseif str == 'data' then
        return data
    elseif str == 'first' then
        return first
    elseif str == 'simple' then
        return simple
    else
        return nil
    end
end
%    \end{macrocode}
%
%     This function and the following ones are only internal. This one is the
%     handler for the first type of callbacks: the ones that take a list head
%     and return true, false, or a new list head.
%
%    \begin{macrocode}
-- local
function listhandler (name)
    return function(head,...)
        local l = callbacklist[name]
        if l then
            local done = true
            for _, f in ipairs(l) do
                -- the returned value is either true or a new head plus true
                rtv1, rtv2 = f.func(head,...)
                if type(rtv1) == 'boolean' then
                    done = rtv1
                elseif type (rtv1) == 'userdata' then
                    head = rtv1
                end
                if type(rtv2) == 'boolean'  then
                    done = rtv2
                elseif type(rtv2) == 'userdata' then
                    head = rtv2
                end
                if done == false then
                    err("function \"%s\" returned false in callback '%s'",
                      f.description, name)
                end
            end
            return head, done
        else
            return head, false
        end
    end
end
%    \end{macrocode}
%
%     The handler for callbacks taking datas and returning modified ones.
%
%    \begin{macrocode}
local function datahandler (name)
    return function(data,...)
        local l = callbacklist[name]
        if l then
            for _, f in ipairs(l) do
                data = f.func(data,...)
            end
        end
        return data
    end
end
%    \end{macrocode}
%
%     This function is for the handlers that don't support more than one
%     functions in them. In this case we only call the first function of the
%     list.
%
%    \begin{macrocode}
local function firsthandler (name)
    return function(...)
        local l = callbacklist[name]
        if l then
            local f = l[1].func
            return f(...)
        else
            return nil, false
        end
    end
end
%    \end{macrocode}
%
%     Handler for simple functions that don't return anything.
%
%    \begin{macrocode}
local function simplehandler (name)
    return function(...)
        local l = callbacklist[name]
        if l then
            for _, f in ipairs(l) do
                f.func(...)
            end
        end
    end
end
%    \end{macrocode}
%
%    \subsubsection{Public functions}
%
%    The main function. The signature is \texttt{add (name,
%    func, description, priority)} with \texttt{name} being the name of the
%    callback in which the function is added; \texttt{func} is the added
%    function; \texttt{description} is a small character string describing the
%    function, and \texttt{priority} an optional argument describing the
%    priority the function will have.
%
%    The functions for a callbacks are added in a list (in
%    \texttt{callbacklist\\.callbackname}). If they have no
%    priority or a high priority number, they will be added at the end of the
%    list, and will be called after the others. If they have a low priority
%    number, the will be added at the beginning of the list and will be called
%    before the others.
%
%    Something that must be made clear, is that there is absolutely no
%    solution for packages conflicts: if two packages want the top priority on
%    a certain callback, they will have to decide the priority they will give
%    to their function themselves. Most of the time, the priority is not needed.
%
%    \begin{macrocode}
function add_to_callback (name,func,description,priority)
    if type(func) ~= "function" then
        err("unable to add function, no proper function passed")
        return
    end
    if not name or name == "" then
        err("unable to add function, no proper callback name passed")
        return
    elseif not callbacktypes[name] then
        err("unable to add function, '%s' is not a valid callback", name)
        return
    end
    if not description or description == "" then
        err("unable to add function to '%s', no proper description passed",
          name)
        return
    end
    if priority_in_callback(name, description) ~= 0 then
        warning("function '%s' already registered in callback '%s'",
          description, name)
    end
    local l = callbacklist[name]
    if not l then
        l = {}
        callbacklist[name] = l
        if not lua_callbacks_defaults[name] then
            if callbacktypes[name] == list then
                internalregister(name, listhandler(name))
            elseif callbacktypes[name] == data then
                internalregister(name, datahandler(name))
            elseif callbacktypes[name] == simple then
                internalregister(name, simplehandler(name))
            elseif callbacktypes[name] == first then
                internalregister(name, firsthandler(name))
            else
                err("unknown callback type")
            end
        end
    end
    local f = {
        func = func,
        description = description,
    }
    priority = tonumber(priority)
    if not priority or priority > #l then
        priority = #l+1
    elseif priority < 1 then
        priority = 1
    end
    if callbacktypes[name] == first and (priority ~= 1 or #l ~= 0) then
        warning("several callbacks registered in callback '%s', "
        .."only the first function will be active.", name)
    end
    table.insert(l,priority,f)
    log("inserting function '%s' at position %s in callback list for '%s'",
      description, priority, name)
end
%    \end{macrocode}
%
%    The function that removes a function from a callback. The signature is
%    \texttt{remove (name, description)} with \texttt{name} being
%    the name of callbacks, and description the description passed to
%    \texttt{add}.
%
%    \begin{macrocode}
function remove_from_callback (name, description)
    if not name or name == "" then
        err("unable to remove function, no proper callback name passed")
        return
    elseif not callbacktypes[name] then
        err("unable to remove function, '%s' is not a valid callback", name)
        return
    end
    if not description or description == "" then
        err(
          "unable to remove function from '%s', no proper description passed",
          name)
        return
    end
    local l = callbacklist[name]
    if not l then
        err("no callback list for '%s'",name)
        return
    end
    for k,v in ipairs(l) do
        if v.description == description then
            table.remove(l,k)
            log("removing function '%s' from '%s'",description,name)
            if not next(l) then
              callbacklist[name] = nil
              if not lua_callbacks_defaults[name] then
                internalregister(name, nil)
              end
            end
            return
        end
    end
    warning("unable to remove function '%s' from '%s'",description,name)
end
%    \end{macrocode}
%
%    This function removes all the functions registered in a callback.
%
%    \begin{macrocode}
function reset_callback (name)
    if not name or name == "" then
        err("unable to reset, no proper callback name passed")
        return
    elseif not callbacktypes[name] then
        err("reset error, '%s' is not a valid callback", name)
        return
    end
    if not lua_callbacks_defaults[name] then
        internalregister(name, nil)
    end
    local l = callbacklist[name]
    if l then
        log("resetting callback list '%s'",name)
        callbacklist[name] = nil
    end
end
%    \end{macrocode}
%
%    This first function creates a new callback. The signature is
%    \texttt{create(name, ctype, default)} where \texttt{name} is the name of
%    the new callback to create, \texttt{ctype} is the type of callback, and
%    \texttt{default} is the default function to call if no function is
%    registered in this callback.
%
%    The created callback will behave the same way Lua\TeX\ callbacks do, you
%    can add and remove functions in it. The difference is that the callback
%    is not automatically called, the package developer creating a new
%    callback must also call it, see next function.
%
%    \begin{macrocode}
function create_callback(name, ctype, default)
    if not name then
        err("unable to call callback, no proper name passed", name)
        return nil
    end
    if not ctype or not default then
        err("unable to create callback '%s': "
        .."callbacktype or default function not specified", name)
        return nil
    end
    if callbacktypes[name] then
        err("unable to create callback '%s', callback already exists", name)
        return nil
    end
    local temp = str_to_type(ctype)
    if not temp then
        err("unable to create callback '%s', type '%s' undefined", name, ctype)
        return nil
    end
    ctype = temp
    lua_callbacks_defaults[name] = default
    callbacktypes[name] = ctype
end
%    \end{macrocode}
%
%    This function calls a callback. It can only call a callback created by
%    the \texttt{create} function.
%
%    \begin{macrocode}
function call_callback(name, ...)
    if not name then
        err("unable to call callback, no proper name passed", name)
        return nil
    end
    if not lua_callbacks_defaults[name] then
        err("unable to call lua callback '%s', unknown callback", name)
        return nil
    end
    local l = callbacklist[name]
    local f
    if not l then
        f = lua_callbacks_defaults[name]
    else
        if callbacktypes[name] == list then
            f = listhandler(name)
        elseif callbacktypes[name] == data then
            f = datahandler(name)
        elseif callbacktypes[name] == simple then
            f = simplehandler(name)
        elseif callbacktypes[name] == first then
            f = firsthandler(name)
        else
            err("unknown callback type")
        end
    end
    return f(...)
end
%    \end{macrocode}
%
%    This function tells if a function has already been registered in a
%    callback, and gives its current priority. The arguments are the name of
%    the callback and the description of the function. If it has already been
%    registered, it gives its priority, and if not it returns false.
%
%    \begin{macrocode}
function priority_in_callback (name, description)
    if not name or name == ""
            or not callbacktypes[name]
            or not description then
        return 0
    end
    local l = callbacklist[name]
    if not l then return 0 end
    for p, f in pairs(l) do
        if f.description == description then
            return p
        end
    end
    return 0
end
%    \end{macrocode}
%
%    Finally we
%    overwrite \texttt{callback.register} so that it outputs an error.
%
%    \begin{macrocode}
callback.register = function ()
err("function callback.register has been deleted by luamcallbacks, "
.."please use callback.add instead.")
end
%    \end{macrocode}
%
%    That's all folks!
%
%    \begin{macrocode}
%</lua>
%    \end{macrocode}
%
%    \section{Test files}
%
%    A few basic tests for Plain and LaTeX.
%
%    \begin{macrocode}
%<testplain>\input luatexbase-mcb.sty
%<testlatex>\RequirePackage{luatexbase-mcb}
%<*testplain,testlatex>
\catcode 64 11
\luatexbase@directlua{
  local function one(head,...)
      texio.write_nl("I'm number 1")
      return head, true
  end

  local function two(head,...)
      texio.write_nl("I'm number 2")
      return head, true
  end

  local function three(head,...)
      texio.write_nl("I'm number 3")
      return head, true
  end

  luatexbase.add_to_callback("hpack_filter",one,"my sample function one",1)
  luatexbase.add_to_callback("hpack_filter",two,"my sample function two",2)
  luatexbase.add_to_callback("hpack_filter",three,"my sample function three",1)

  luatexbase.remove_from_callback("hpack_filter","my sample function three")
}
%</testplain,testlatex>
%<testplain>\bye
%<testlatex>\stop
%    \end{macrocode}
%
% \Finale
\endinput
