% \iffalse meta-comment % % Written in 2009, 2010 by Manuel Pégourié-Gonnard and Élie Roux. % % % % 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 % %<*install> \input docstrip.tex \keepsilent \askforoverwritefalse \let\MetaPrefix\relax \preamble Copyright (C) 2009 by Elie Roux 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 % %<*ignore> \fi % %<*driver> \documentclass{ltxdoc} \input{lltxb-dtxstyle} \begin{document} \DocInput{luatexbase-mcb.dtx}% \end{document} % % \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{2010/05/27 v0.2a} % \author{% % Manuel P\'egouri\'e-Gonnard \\ \email{mpg@elzevir.fr} \and % \'Elie Roux \\ \email{elie.roux@telecom-bretagne.eu}} % % \maketitle % % \begin{abstract} % The primary feature of this package is to allow many functions to be % registered in the same callback. Depending of the type of the callback, the % functions will be combined in some way when the callback is called. Functions % are provided for addition and removal of individual functions from a % callback's list, with a priority system.\par % Additionally, you can create new callbacks that will be handled the same way % as predefined callbacks, except that they must be called explicitely. % \end{abstract} % % \tableofcontents % % \section{Documentation} % % \subsection{Managing functions in callbacks} % % 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 |callback.register| is that is registers only one function % in a callback. This package solves the problem by disabling % |callback.register| and providing a new interface allowing many functions to % be registered in a single callback. % % The way the functions are combined together depends on % the type of the callback. There are currently 4 types of callback, depending % on the signature of the functions that can be registered in it: % \begin{description} % \item[list] functions taking a list of nodes and returning a boolean and % possibly a new head: (TODO); % \item[data] functions taking datas and returning it modified: the functions % are called in order and passed the return value of the previous function as % an argument, the return value is that of the last function; % \item[simple] functions that don't return anything: they are called in % order, all with the same argument; % \item[first] functions with more complex signatures; functions in this type % of callback are \emph{not} combined: only the first one (according to % priorities) is executed. % \end{itemize} % % To add a function to a callback, use: % \begin{verbatim} % luatexbase.add_to_callback(name, func, description, priority) % \end{verbatim} % The first argument is the name of the callback, the second is a function, % the third one is a string used to identify the function later, and the % optional priority is a postive integer, representing the rank of the % function in the list of functions to be executing for this callback. So, % |1| is the highest priority. If no priority is specified, the function is % appended to the list, that is, its priority is the one of the last function % plus one. % % The priority system is intended to help resolving conflicts between packages % competing on the same callback, but it cannot solve every possible issue. If % two packages request priority |1| on the same callback, then the last one % loaded will win. % % To remove a function from a callback, use: % \begin{verbatim} % luatexbase.remove_from_callback(name, description) % \end{verbatim} % The first argument must be the name of the callback, and the second one the % description used when adding the function to this callback. % % % \subsection{Creating new callbacks} % % This package also privides a way to create and call new callbacks, in % addition to the default Lua\TeX\ callbacks. % See comments in the implementation section for details. % % \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/05/27 v0.2a Callback management for LuaTeX] % \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 % % \end{macrocode} % % \subsection{Lua module} % % \begin{macrocode} %<*lua> % \end{macrocode} % % \subsubsection{Module identification} % % \begin{macrocode} module('luatexbase', package.seeall) local err, warning, info = luatexbase.provides_module({ name = "luamcallbacks", version = 0.2, date = "2010/05/12", description = "register several functions in a callback", author = "Hans Hagen, Elie Roux and Manuel Pegourie-Gonnard", copyright = "Hans Hagen, Elie Roux and Manuel Pegourie-Gonnard", license = "CC0", }) % \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} % % Numerical codes for callback types. % % \begin{macrocode} local list, data, first, simple = 1, 2, 3, 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\nin 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} % % Add a function to a callback. First check arguments. % % \begin{macrocode} function add_to_callback (name,func,description,priority) if type(func) ~= "function" then return err("unable to add function:\nno proper function passed") end if not name or name == "" then err("unable to add function:\nno proper callback name passed") return elseif not callbacktypes[name] then err("unable to add function:\n'%s' is not a valid callback", name) return end if not description or description == "" then err("unable to add function to '%s':\nno proper description passed", name) return end if priority_in_callback(name, description) then err("function '%s' already registered\nin callback '%s'", description, name) return end % \end{macrocode} % % Then test if this callback is already in use. If not, initialise its list % and register the proper handler. % % \begin{macrocode} 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 % \end{macrocode} % % Actually register the function. % % \begin{macrocode} 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 table.insert(l,priority,f) % \end{macrocode} % % Keep user informed. % % \begin{macrocode} if callbacktypes[name] == first and (priority ~= 1 or #l ~= 0) then warning("several callbacks registered in callback '%s',\n" .."only the first function will be active.", name) end info("inserting function '%s'\nat position %s in callback list\nfor '%s'", description, priority, name) end % \end{macrocode} % % Remove a function from a callback. First check arguments. % % \begin{macrocode} function remove_from_callback (name, description) if not name or name == "" then err("unable to remove function:\nno proper callback name passed") return elseif not callbacktypes[name] then err("unable to remove function:\n'%s' is not a valid callback", name) return end if not description or description == "" then err( "unable to remove function from '%s':\nno proper description passed", name) return end local l = callbacklist[name] if not l then err("no callback list for '%s'",name) return end % \end{macrocode} % % Then loop over the callback's function list until we find a matching % entry. Remove it and check if the list gets empty: if so, unregister the % callback unless it is user-defined. % % \begin{macrocode} local index = false for k,v in ipairs(l) do if v.description == description then index = k break end end if not index then err("unable to remove function '%s'\nfrom '%s'", description, name) return end table.remove(l, index) info("removing function '%s'\nfrom '%s'", description, name) if table.maxn(l) == 0 then callbacklist[name] = nil if not lua_callbacks_defaults[name] then internalregister(name, nil) end end return 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:\nno proper callback name passed") return elseif not callbacktypes[name] then err("reset error: '%s'\nis 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 info("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:\nno proper name passed", name) return nil end if not ctype or not default then err("unable to create callback '%s':\n" .."callbacktype or default function not specified", name) return nil end if callbacktypes[name] then err("unable to create callback '%s':\ncallback already exists", name) return nil end local temp = str_to_type(ctype) if not temp then err("unable to create callback '%s':\ntype '%s' undefined", name, ctype) return nil end info("creating new callback '%s'", name) 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:\nno proper name passed", name) return nil end if not lua_callbacks_defaults[name] then err("unable to call lua callback '%s':\nunknown 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 false end local l = callbacklist[name] if not l then return false end for p, f in pairs(l) do if f.description == description then return p end end return false end % \end{macrocode} % % Finally, overwrite |callback.register| so that bails out in error. % % \begin{macrocode} callback.register = function () err("function callback.register has been trapped,\n" .."please use luatexbase.add_to_callback instead.") end % \end{macrocode} % % That's all folks! % % \begin{macrocode} % % \end{macrocode} % % \section{Test files} % % A few basic tests for Plain and LaTeX. % % \begin{macrocode} %\input luatexbase-mcb.sty %\RequirePackage{luatexbase-mcb} %<*testplain,testlatex> \catcode 64 11 \luatexbase@directlua{ local function sample(head,...) return head, true end local prio = luatexbase.priority_in_callback luatexbase.add_to_callback("hpack_filter", sample, "sample function one", 1) luatexbase.add_to_callback("hpack_filter", sample, "sample function two", 2) luatexbase.add_to_callback("hpack_filter", sample, "sample function three", 1) assert(prio("hpack_filter", "sample function three")) luatexbase.remove_from_callback("hpack_filter", "sample function three") assert(not prio("hpack_filter", "sample function three")) luatexbase.reset_callback("hpack_filter") assert(not prio("hpack_filter", "sample function one")) local function data_one(s) texio.write_nl("I'm data 1 whith argument: "..s) return s end local function data_two(s) texio.write_nl("I'm data 2 whith argument: "..s) return s end local function data_three(s) texio.write_nl("I'm data 3 whith argument: "..s) return s end luatexbase.create_callback("fooback", "data", data_one) luatexbase.call_callback("fooback", "default") luatexbase.add_to_callback("fooback", data_two, "my sample function two", 2) luatexbase.add_to_callback("fooback", data_three, "my sample function three", 1) luatexbase.call_callback("fooback", "all") luatexbase.remove_from_callback("fooback", "my sample function three") luatexbase.call_callback("fooback", "all but three") luatexbase.reset_callback("fooback") luatexbase.call_callback("fooback", "default") } % %\bye %\stop % \end{macrocode} % % \Finale \endinput