From e7a79dc958275bc6f97d554ea39e720f9f1598eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Fabian=20Kr=C3=BCger?= Date: Mon, 16 May 2022 20:00:21 +0200 Subject: [PATCH 1/6] Rule based callback management --- base/ltluatex.dtx | 183 ++++++++++++++++-- ...atexrelease-rollback-2021-06-01.luatex.tlg | 2 +- ...atexrelease-rollback-2021-11-15.luatex.tlg | 2 +- ...atexrelease-rollback-2022-06-01.luatex.tlg | 2 +- base/testfiles/tlb-ltluatex-001.luatex.tlg | 2 +- .../tlb-mathcallbacks-001.luatex.tlg | 6 +- .../tlb-mathcallbacks-002.luatex.tlg | 12 +- 7 files changed, 183 insertions(+), 26 deletions(-) diff --git a/base/ltluatex.dtx b/base/ltluatex.dtx index ab81c3620..37b930508 100644 --- a/base/ltluatex.dtx +++ b/base/ltluatex.dtx @@ -1288,8 +1288,85 @@ luatexbase.new_luafunction = new_luafunction % actual function as |func| and the identifying description as |description|. % Only callbacks with a non-empty list of functions have an entry in this % list. -% \begin{macrocode} -local callbacklist = callbacklist or { } +% +% Actually there are two tables: |realcallbacklist| directly contains the entries +% as described above while |callbacklist| only directly contains the already sorted +% entries. Other entries can be queried through |callbacklist| too which triggers a +% resort. +% +% Additionally |callbackrules| describes the ordering constraints: It contains two +% element tables with the descriptions of the constrained callback implementations. +% It can additionally contain a |type| entry indicating the kind of rule. A missing +% value indicates a normal ordering contraint. +% \begin{macrocode} +local realcallbacklist = {} +local callbackrules = {} +local callbacklist = setmetatable({}, { + __index = function(t, name) + local list = realcallbacklist[name] + local rules = callbackrules[name] + if list and rules then + local meta = {} + for i, entry in ipairs(list) do + local t = {value = entry, count = 0, pos = i} + meta[entry.description], list[i] = t, t + end + local count = #list + local pos = count + for i, rule in ipairs(rules) do + local rule = rules[i] + local pre, post = meta[rule[1]], meta[rule[2]] + if pre and post then + if rule.type then + if not rule.hidden then + assert(rule.type == 'incompatible-warning' and luatexbase_warning + or rule.type == 'incompatible-error' and luatexbase_error)( + "Incompatible functions \"" .. rule[1] .. "\" and \"" .. rule[2] + .. "\" specified for callback \"" .. name .. "\".") + rule.hidden = true + end + else + local post_count = post.count + post.count = post_count+1 + if post_count == 0 then + local post_pos = post.pos + if post_pos ~= pos then + local new_post_pos = list[pos] + new_post_pos.pos = post_pos + list[post_pos] = new_post_pos + end + list[pos] = nil + pos = pos - 1 + end + pre[#pre+1] = post + end + end + end + for i=1, count do -- The actual sort begins + local current = list[i] + if current then + for j, cur in ipairs(current) do + local count = cur.count + if count == 1 then + pos = pos + 1 + list[pos] = cur + else + cur.count = count - 1 + end + end + list[i] = current.value + else + -- Cycle occured. TODO: Show cycle for debugging + -- list[i] = ... + error'Cycle occured' + end + end + end + realcallbacklist[name] = list + t[name] = list + return list + end +}) % \end{macrocode} % % Numerical codes for callback types, and name-to-value association (the @@ -1751,10 +1828,10 @@ local function add_to_callback(name, func, description) % 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] + local l = realcallbacklist[name] if l == nil then l = { } - callbacklist[name] = l + realcallbacklist[name] = l % \end{macrocode} % \changes{v1.1y}{2022/08/13}{Adapted code for shared\_callbacks} % Handle count for shared engine callbacks. @@ -1780,7 +1857,6 @@ local function add_to_callback(name, func, description) func = func, description = description, } - local priority = #l + 1 if callbacktypes[name] == exclusive then if #l == 1 then luatexbase_error( @@ -1788,19 +1864,68 @@ local function add_to_callback(name, func, description) name .. "'") end end - table.insert(l, priority, f) + table.insert(l, f) + callbacklist[name] = nil % \end{macrocode} % Keep user informed. % \begin{macrocode} luatexbase_log( - "Inserting `" .. description .. "' at position " - .. priority .. " in `" .. name .. "'." + "Inserting `" .. description .. "' in `" .. name .. "'." ) end luatexbase.add_to_callback = add_to_callback % \end{macrocode} % \end{macro} % +% \begin{macro}{declare_callback_rule} +% Add an ordering constraint between two callback implementations +% \begin{macrocode} +local function declare_callback_rule(name, desc1, desc2, relation) + if not callbacktypes[name] or + not desc1 or not desc2 or + desc1 == "" or desc2 == "" then + luatexbase_error( + "Unable to create ordering constraint. " + .. "Correct usage:\n" + .. "declare_callback_rule(, , )" + ) + end + if relation == 'before' or not relation then + relation = nil + elseif relation == 'after' then + desc2, desc1 = desc1, desc2 + relation = nil + elseif relation == 'incompatible-warning' or relation == 'incompatible-error' then + elseif relation == 'unrelated' then + else + luatexbase_error( + "Unknown relation type in declare_callback_rule" + ) + end + callbacklist[name] = nil + local rules = callbackrules[name] + if rules then + for i, rule in ipairs(rules) do + if rule[1] == desc1 and rule[2] == desc2 or rule[1] == desc2 and rule[2] == desc1 then + if relation == 'unrelated' then + table.remove(rules, i) + else + rule[1], rule[2], rule.type = desc1, desc2, relation + end + return + end + end + if relation ~= 'unrelated' then + rules[#rules + 1] = {desc1, desc2, type = relation} + end + elseif relation ~= 'unrelated' then + callbackrules[name] = {{desc1, desc2, type = relation}} + end +end +luatexbase.declare_callback_rule = declare_callback_rule +% \end{macrocode} +% \end{macro} +% % \begin{macro}{remove_from_callback} % \changes{v1.0a}{2015/09/24}{Function added} % \changes{v1.0k}{2015/12/02}{adjust initialization of cb local (PHG)} @@ -1823,7 +1948,7 @@ local function remove_from_callback(name, description) .. "remove_from_callback(, )" ) end - local l = callbacklist[name] + local l = realcallbacklist[name] if not l then luatexbase_error( "No callback list for `" .. name .. "'\n") @@ -1851,6 +1976,7 @@ local function remove_from_callback(name, description) "Removing `" .. description .. "' from `" .. name .. "'." ) if #l == 0 then + realcallbacklist[name] = nil callbacklist[name] = nil local shared = shared_callbacks[name] if shared then @@ -1876,12 +2002,12 @@ luatexbase.remove_from_callback = remove_from_callback local function in_callback(name, description) if not name or name == "" - or not callbacklist[name] + or not realcallbacklist[name] or not callbacktypes[name] or not description then return false end - for _, i in pairs(callbacklist[name]) do + for _, i in pairs(realcallbacklist[name]) do if i.description == description then return true end @@ -1898,7 +2024,7 @@ luatexbase.in_callback = in_callback % this functionality. % \begin{macrocode} local function disable_callback(name) - if(callbacklist[name] == nil) then + if(realcallbacklist[name] == nil) then callback_register(name, false) else luatexbase_error("Callback list for " .. name .. " not empty") @@ -1912,12 +2038,13 @@ luatexbase.disable_callback = disable_callback % \changes{v1.0a}{2015/09/24}{Function added} % \changes{v1.0h}{2015/11/27}{Match test in in-callback latex/4445} % List the descriptions of functions registered for the given callback. +% This will sort the list if necessary. % \begin{macrocode} local function callback_descriptions (name) local d = {} if not name or name == "" - or not callbacklist[name] + or not realcallbacklist[name] or not callbacktypes[name] then return d @@ -1975,6 +2102,36 @@ function shared_callbacks.mlist_to_hlist.handler(head, display_type, need_penalt end % \end{macrocode} % \end{macro} +% +% And finally some patching for the luatexbase priority argument: +% \begin{macrocode} +local func = luatexbase.new_luafunction'ConstrainCallbacks@PatchLuatexbase' +token.set_lua('ConstrainCallbacks@PatchLuatexbase', func, 'protected') +lua.get_functions_table()[func] = function() + luatexbase.base_add_to_callback = add_to_callback + function luatexbase.add_to_callback(name,fun,description,priority) + if not priority then + return add_to_callback(name, fun, description) + end + local descriptions = luatexbase.callback_descriptions(name) +% if not priority then +% priority = #descriptions + 1 +% end + if luatexbase.callbacktypes[name] == 3 then + if priority == 1 and #descriptions==1 then + luatexbase.module_warning("luatexbase", + "resetting exclusive callback: " .. name) + luatexbase.reset_callback(name) + end + else + for i, desc in ipairs(descriptions) do + declare_callback_rule(name, desc, description, i < priority and 'before' or 'after') + end + end + return add_to_callback(name,fun,description) + end +end +% \end{macrocode} % \endgroup % % \begin{macrocode} diff --git a/base/testfiles/tlb-latexrelease-rollback-2021-06-01.luatex.tlg b/base/testfiles/tlb-latexrelease-rollback-2021-06-01.luatex.tlg index ad493ca71..038c43c56 100644 --- a/base/testfiles/tlb-latexrelease-rollback-2021-06-01.luatex.tlg +++ b/base/testfiles/tlb-latexrelease-rollback-2021-06-01.luatex.tlg @@ -730,7 +730,7 @@ Already applied: [....-..-..] Start of XeTeX class allocator on input line .... Already applied: [....-..-..] Start of XeTeX class allocator on input line .... Removing `tracingstacklevels' from `input_level_string'. Applying: [....-..-..] Lua trace_stack_levels function on input line .... -Inserting `tracingstacklevels' at position 1 in `input_level_string'. +Inserting `tracingstacklevels' in `input_level_string'. Already applied: [....-..-..] Lua trace_stack_levels function on input line .... Applying: [....-..-..] XeTeX character classes on input line .... Already applied: [....-..-..] XeTeX character classes on input line .... diff --git a/base/testfiles/tlb-latexrelease-rollback-2021-11-15.luatex.tlg b/base/testfiles/tlb-latexrelease-rollback-2021-11-15.luatex.tlg index a6455f5dc..14eec4fc4 100644 --- a/base/testfiles/tlb-latexrelease-rollback-2021-11-15.luatex.tlg +++ b/base/testfiles/tlb-latexrelease-rollback-2021-11-15.luatex.tlg @@ -730,7 +730,7 @@ Already applied: [....-..-..] Start of XeTeX class allocator on input line .... Already applied: [....-..-..] Start of XeTeX class allocator on input line .... Removing `tracingstacklevels' from `input_level_string'. Applying: [....-..-..] Lua trace_stack_levels function on input line .... -Inserting `tracingstacklevels' at position 1 in `input_level_string'. +Inserting `tracingstacklevels' in `input_level_string'. Already applied: [....-..-..] Lua trace_stack_levels function on input line .... Applying: [....-..-..] XeTeX character classes on input line .... Already applied: [....-..-..] XeTeX character classes on input line .... diff --git a/base/testfiles/tlb-latexrelease-rollback-2022-06-01.luatex.tlg b/base/testfiles/tlb-latexrelease-rollback-2022-06-01.luatex.tlg index c504eeb3a..ef3fc201f 100644 --- a/base/testfiles/tlb-latexrelease-rollback-2022-06-01.luatex.tlg +++ b/base/testfiles/tlb-latexrelease-rollback-2022-06-01.luatex.tlg @@ -755,7 +755,7 @@ Already applied: [....-..-..] Start of XeTeX class allocator on input line .... Already applied: [....-..-..] Start of XeTeX class allocator on input line .... Removing `tracingstacklevels' from `input_level_string'. Applying: [....-..-..] Lua trace_stack_levels function on input line .... -Inserting `tracingstacklevels' at position 1 in `input_level_string'. +Inserting `tracingstacklevels' in `input_level_string'. Already applied: [....-..-..] Lua trace_stack_levels function on input line .... Applying: [....-..-..] XeTeX character classes on input line .... Already applied: [....-..-..] XeTeX character classes on input line .... diff --git a/base/testfiles/tlb-ltluatex-001.luatex.tlg b/base/testfiles/tlb-ltluatex-001.luatex.tlg index 719637d9c..afd437d00 100644 --- a/base/testfiles/tlb-ltluatex-001.luatex.tlg +++ b/base/testfiles/tlb-ltluatex-001.luatex.tlg @@ -7,7 +7,7 @@ stack traceback: ^^I[C]: in function 'error' ^^I./ltluatex.lua:110: in upvalue 'module_error' ^^I./ltluatex.lua:117: in upvalue 'luatexbase_error' -^^I./ltluatex.lua:430: in field 'create_callback' +^^I./ltluatex.lua:495: in field 'create_callback' ^^I[\directlua]:1: in main chunk. l. ...} The lua interpreter ran into a problem, so the diff --git a/base/testfiles/tlb-mathcallbacks-001.luatex.tlg b/base/testfiles/tlb-mathcallbacks-001.luatex.tlg index 1c373bbf4..8690520c0 100644 --- a/base/testfiles/tlb-mathcallbacks-001.luatex.tlg +++ b/base/testfiles/tlb-mathcallbacks-001.luatex.tlg @@ -1,16 +1,16 @@ This is a generated file for the LaTeX2e validation system. Don't change this file in any respect. -Inserting `test' at position 1 in `pre_mlist_to_hlist_filter'. +Inserting `test' in `pre_mlist_to_hlist_filter'. LaTeX Font Info: External font `cmex10' loaded for size (Font) <7> on input line .... LaTeX Font Info: External font `cmex10' loaded for size (Font) <5> on input line .... pre_mlist_to_hlist_filter called -Inserting `test' at position 1 in `mlist_to_hlist'. +Inserting `test' in `mlist_to_hlist'. pre_mlist_to_hlist_filter called mlist_to_hlist called Removing `test' from `mlist_to_hlist'. pre_mlist_to_hlist_filter called -Inserting `test' at position 1 in `mlist_to_hlist'. +Inserting `test' in `mlist_to_hlist'. pre_mlist_to_hlist_filter called new mlist_to_hlist called diff --git a/base/testfiles/tlb-mathcallbacks-002.luatex.tlg b/base/testfiles/tlb-mathcallbacks-002.luatex.tlg index d4cf21bac..876237cc9 100644 --- a/base/testfiles/tlb-mathcallbacks-002.luatex.tlg +++ b/base/testfiles/tlb-mathcallbacks-002.luatex.tlg @@ -2,23 +2,23 @@ This is a generated file for the LaTeX2e validation system. Don't change this file in any respect. Removing `l3build.shift' from `post_mlist_to_hlist_filter'. false -Inserting `filter' at position 1 in `pre_mlist_to_hlist_filter'. +Inserting `filter' in `pre_mlist_to_hlist_filter'. true Removing `filter' from `pre_mlist_to_hlist_filter'. false -Inserting `filter' at position 1 in `pre_mlist_to_hlist_filter'. -Inserting `filter' at position 1 in `post_mlist_to_hlist_filter'. +Inserting `filter' in `pre_mlist_to_hlist_filter'. +Inserting `filter' in `post_mlist_to_hlist_filter'. true Removing `filter' from `pre_mlist_to_hlist_filter'. true Removing `filter' from `post_mlist_to_hlist_filter'. false -Inserting `filter' at position 1 in `mlist_to_hlist'. +Inserting `filter' in `mlist_to_hlist'. true Removing `filter' from `mlist_to_hlist'. false -Inserting `filter' at position 1 in `mlist_to_hlist'. -Inserting `filter' at position 1 in `post_mlist_to_hlist_filter'. +Inserting `filter' in `mlist_to_hlist'. +Inserting `filter' in `post_mlist_to_hlist_filter'. true Removing `filter' from `mlist_to_hlist'. true From e27a15f67eedd975ac7400de19706e1389e08e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Fabian=20Kr=C3=BCger?= Date: Fri, 27 May 2022 17:27:58 +0200 Subject: [PATCH 2/6] Change rule interface as discussed --- base/ltluatex.dtx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/ltluatex.dtx b/base/ltluatex.dtx index 37b930508..98a62681b 100644 --- a/base/ltluatex.dtx +++ b/base/ltluatex.dtx @@ -1880,7 +1880,7 @@ luatexbase.add_to_callback = add_to_callback % \begin{macro}{declare_callback_rule} % Add an ordering constraint between two callback implementations % \begin{macrocode} -local function declare_callback_rule(name, desc1, desc2, relation) +local function declare_callback_rule(name, desc1, relation, desc2) if not callbacktypes[name] or not desc1 or not desc2 or desc1 == "" or desc2 == "" then @@ -1890,7 +1890,7 @@ local function declare_callback_rule(name, desc1, desc2, relation) .. "declare_callback_rule(, , )" ) end - if relation == 'before' or not relation then + if relation == 'before' then relation = nil elseif relation == 'after' then desc2, desc1 = desc1, desc2 From 94b8dac0df090d22ccb9fbae19494fb0e6d29ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Fabian=20Kr=C3=BCger?= Date: Sat, 13 Aug 2022 14:53:27 +0200 Subject: [PATCH 3/6] Remove luatexbase change --- base/ltluatex.dtx | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/base/ltluatex.dtx b/base/ltluatex.dtx index 98a62681b..c38b943d1 100644 --- a/base/ltluatex.dtx +++ b/base/ltluatex.dtx @@ -2102,36 +2102,6 @@ function shared_callbacks.mlist_to_hlist.handler(head, display_type, need_penalt end % \end{macrocode} % \end{macro} -% -% And finally some patching for the luatexbase priority argument: -% \begin{macrocode} -local func = luatexbase.new_luafunction'ConstrainCallbacks@PatchLuatexbase' -token.set_lua('ConstrainCallbacks@PatchLuatexbase', func, 'protected') -lua.get_functions_table()[func] = function() - luatexbase.base_add_to_callback = add_to_callback - function luatexbase.add_to_callback(name,fun,description,priority) - if not priority then - return add_to_callback(name, fun, description) - end - local descriptions = luatexbase.callback_descriptions(name) -% if not priority then -% priority = #descriptions + 1 -% end - if luatexbase.callbacktypes[name] == 3 then - if priority == 1 and #descriptions==1 then - luatexbase.module_warning("luatexbase", - "resetting exclusive callback: " .. name) - luatexbase.reset_callback(name) - end - else - for i, desc in ipairs(descriptions) do - declare_callback_rule(name, desc, description, i < priority and 'before' or 'after') - end - end - return add_to_callback(name,fun,description) - end -end -% \end{macrocode} % \endgroup % % \begin{macrocode} From 8fbed5ba246f3464b53fb0e090442cca277769b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Fabian=20Kr=C3=BCger?= Date: Sun, 14 Aug 2022 21:54:21 +0200 Subject: [PATCH 4/6] Add documentation --- base/changes.txt | 5 +++++ base/doc/ltnews36.tex | 30 +++++++++++++++++++++++++++++- base/ltluatex.dtx | 38 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/base/changes.txt b/base/changes.txt index 874426197..102f31e4e 100644 --- a/base/changes.txt +++ b/base/changes.txt @@ -6,6 +6,11 @@ completeness or accuracy and it contains some references to files that are not part of the distribution. ================================================================================ +2022-10-03 Marcel Krüger + + * ltluatex.dtx: + Add ordering rules for callback handlers + 2022-09-20 Frank Mittelbach * ltfssdcl.dtx (section{Interface Commands}): diff --git a/base/doc/ltnews36.tex b/base/doc/ltnews36.tex index 55de98628..0ed38263d 100644 --- a/base/doc/ltnews36.tex +++ b/base/doc/ltnews36.tex @@ -281,7 +281,6 @@ \subsection{Improve \pkg{l3docstrip} integration into \pkg{docstrip}} \githubissue{903} - \subsection{Improve font series handling with incorrect \texttt{.fd} files} By convention, the font series value is supposed to contain no @@ -304,6 +303,35 @@ \subsection{Improve font series handling with incorrect \texttt{.fd} files} \githubissue{918} +\subsection{Rule based ordering for \LuaTeX\ callback handlers} + +In \hologo{LuaLaTeX} the callback handlers used to be called in the order +they are registered in, but this was often rather fragile. +It depends a lot on the load order and any attempts to enforce a +different order required to unregister and reregister the handlers to +be reordered. Additionally even if some ordering constraints where +enforced that way another package loaded later could accidentally +overwrite it. + +To improve this, we now order the callback handlers based on ordering +rules similar to the hook rules. + +When registering a callback which should run before or after another +callback, \verb+luatexbase.declare_callback_rule+ can now be used to +record this ordering constraint. +For example +\begin{verbatim} + luatexbase.add_to_callback('pre_shaping_filter', my_handler, 'my_name') + luatexbase.declare_callback_rule('pre_shaping_filter', 'my_name', 'before', 'other_name') +\end{verbatim} +will ensure that \verb+my_handler+ will always be called before the +handler registered as \verb+other_name+. + +This also means that the order in which callbacks are registered no +longer implicitly defines an order. +Code which relied on this implicit order should now define the order +rules explicitly. + \section{Bug fixes} diff --git a/base/ltluatex.dtx b/base/ltluatex.dtx index c38b943d1..8be179de9 100644 --- a/base/ltluatex.dtx +++ b/base/ltluatex.dtx @@ -28,7 +28,7 @@ \ProvidesFile{ltluatex.dtx} % %<*tex> -[2022/08/13 v1.1y +[2022/10/03 v1.2a % % LuaTeX support for plain TeX (core) %<*tex> @@ -380,7 +380,7 @@ % % \noindent % \DescribeMacro{create_callback} -% |luatexbase.create_callback(|\meta{name},meta{type},\meta{default}|)| +% |luatexbase.create_callback(|\meta{name},\meta{type},\meta{default}|)| % Defines a user defined callback. The last argument is a default % function or |false|. % @@ -389,6 +389,34 @@ % |luatexbase.call_callback(|\meta{name},\ldots|)| % Calls a user defined callback with the supplied arguments. % +% \noindent +% \DescribeMacro{declare_callback_rule} +% |luatexbase.declare_callback_rule(|\meta{name}, \meta{first}, \meta{relation}, \meta{second}|)| +% Adds an ordering constraint between two callback functions for callback \meta{name}. +% +% The kind of constraint added depends on \meta{relation}: +% \begin{description} +% \item[before] The callback function with description \meta{first} will be +% executed before the function with description \meta{second}. +% \item[after] The callback function with description \meta{first} will be +% executed after the function with description \meta{second}. +% \item[incompatible-warning] When both a callback function with description \meta{first} +% and with description \meta{second} is registered, then a warning is printed when +% the callback is executed. +% \item[incompatible-error] When both a callback function with description \meta{first} +% and with description \meta{second} is registered, then an error is printed when +% the callback is executed. +% \item[unrelated] Any previously declared callback rule between \meta{first} +% and \meta{second} gets disabled. +% \end{description} +% Every call to \texttt{declare_callback_rule} with a specific callback \meta{name} +% and descriptions \meta{first} and \meta{second} overwrites all previous calls with +% same callback and descriptions. +% +% The callback functions do not have to be registered yet when the functions is called. +% Ony the constraints for which both callback descriptions refer to callbacks +% registered at the time the callback is called will have an effect. +% % \endgroup % % \MaybeStop{} @@ -1298,6 +1326,8 @@ luatexbase.new_luafunction = new_luafunction % element tables with the descriptions of the constrained callback implementations. % It can additionally contain a |type| entry indicating the kind of rule. A missing % value indicates a normal ordering contraint. +% +% \changes{v1.2a}{2022/10/03}{Add rules for callback ordering} % \begin{macrocode} local realcallbacklist = {} local callbackrules = {} @@ -1808,6 +1838,7 @@ luatexbase.call_callback=call_callback % \changes{v1.0a}{2015/09/24}{Function added} % Add a function to a callback. First check arguments. % \changes{v1.0k}{2015/12/02}{Give more specific error messages (PHG)} +% \changes{v1.2a}{2022/10/03}{Add rules for callback ordering} % \begin{macrocode} local function add_to_callback(name, func, description) if not name or name == "" then @@ -1850,6 +1881,7 @@ local function add_to_callback(name, func, description) end end % \end{macrocode} +% \changes{v1.2a}{2022/10/03}{Add rules for callback ordering} % Actually register the function and give an error if more than one % |exclusive| one is registered. % \begin{macrocode} @@ -1878,6 +1910,7 @@ luatexbase.add_to_callback = add_to_callback % \end{macro} % % \begin{macro}{declare_callback_rule} +% \changes{v1.2a}{2022/10/03}{Add function} % Add an ordering constraint between two callback implementations % \begin{macrocode} local function declare_callback_rule(name, desc1, relation, desc2) @@ -1932,6 +1965,7 @@ luatexbase.declare_callback_rule = declare_callback_rule % \changes{v1.0k}{2015/12/02}{Give more specific error messages (PHG)} % \changes{v1.1m}{2020/03/07}{Do not call callback.register for user-defined callbacks} % \changes{v1.1y}{2022/08/13}{Adapted code for shared\_callbacks} +% \changes{v1.2a}{2022/10/03}{Add rules for callback ordering} % Remove a function from a callback. First check arguments. % \begin{macrocode} local function remove_from_callback(name, description) From a1babd33761e7e63dcd6793da9412b85ca0aea40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Fabian=20Kr=C3=BCger?= Date: Sun, 4 Sep 2022 14:14:22 +0200 Subject: [PATCH 5/6] Better debuging and error recovery for cycle detection --- base/ltluatex.dtx | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/base/ltluatex.dtx b/base/ltluatex.dtx index 8be179de9..6c66a96be 100644 --- a/base/ltluatex.dtx +++ b/base/ltluatex.dtx @@ -1375,6 +1375,7 @@ local callbacklist = setmetatable({}, { for i=1, count do -- The actual sort begins local current = list[i] if current then + meta[current.value.description] = nil for j, cur in ipairs(current) do local count = cur.count if count == 1 then @@ -1388,7 +1389,39 @@ local callbacklist = setmetatable({}, { else -- Cycle occured. TODO: Show cycle for debugging -- list[i] = ... - error'Cycle occured' + local remaining = {} + for name, entry in next, meta do + local value = entry.value + list[#list + 1] = entry.value + remaining[#remaining + 1] = name + end + table.sort(remaining) + local first_name = remaining[1] + for j, name in ipairs(remaining) do + local entry = meta[name] + list[i + j - 1] = entry.value + for _, post_entry in ipairs(entry) do + local post_name = post_entry.value.description + if not remaining[post_name] then + remaining[post_name] = name + end + end + end + local cycle = {first_name} + local index = 1 + local last_name = first_name + repeat + cycle[last_name] = index + last_name = remaining[last_name] + index = index + 1 + cycle[index] = last_name + until cycle[last_name] + local length = index - cycle[last_name] + 1 + table.move(cycle, cycle[last_name], index, 1) + for i=2, length//2 do + cycle[i], cycle[length + 1 - i] = cycle[length + 1 - i], cycle[i] + end + error('Cycle occured at ' .. table.concat(cycle, ' -> ', 1, length)) end end end From 925a098970e1e7998083e4307a1294a91f05d449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Fabian=20Kr=C3=BCger?= Date: Sun, 4 Sep 2022 14:17:19 +0200 Subject: [PATCH 6/6] Add testfile --- base/testfiles/tlb-callbacks-002.luatex.tlg | 14 ++++++ base/testfiles/tlb-callbacks-002.lvt | 48 +++++++++++++++++++++ base/testfiles/tlb-callbacks-002.tlg | 3 ++ base/testfiles/tlb-ltluatex-001.luatex.tlg | 2 +- 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 base/testfiles/tlb-callbacks-002.luatex.tlg create mode 100644 base/testfiles/tlb-callbacks-002.lvt create mode 100644 base/testfiles/tlb-callbacks-002.tlg diff --git a/base/testfiles/tlb-callbacks-002.luatex.tlg b/base/testfiles/tlb-callbacks-002.luatex.tlg new file mode 100644 index 000000000..61d211b66 --- /dev/null +++ b/base/testfiles/tlb-callbacks-002.luatex.tlg @@ -0,0 +1,14 @@ +This is a generated file for the LaTeX2e validation system. +Don't change this file in any respect. + LuaTeX Callback ordering test +Inserting `first' in `testcallback'. +Inserting `second' in `testcallback'. +Inserting `third' in `testcallback'. +Inserting `forth' in `testcallback'. +Inserting `fifth' in `testcallback'. +First try: fifth second third forth first +Second try: fifth third forth second first +Third try: fifth forth third second first +Cycle: +./ltluatex.lua:335: Cycle occured at second -> forth -> third -> second +Recovered: fifth second forth third first diff --git a/base/testfiles/tlb-callbacks-002.lvt b/base/testfiles/tlb-callbacks-002.lvt new file mode 100644 index 000000000..3afe3a47c --- /dev/null +++ b/base/testfiles/tlb-callbacks-002.lvt @@ -0,0 +1,48 @@ +\input{test2e} +\START +\typeout{^^J LuaTeX Callback ordering test^^J} +\ifx\directlua\undefined +\expandafter \END +\fi + +\directlua{ +% +% current ltluatex user-callbacks + luatexbase.create_callback('testcallback', 'data') +% + local call_order + local function add_handler(name) + luatexbase.add_to_callback('testcallback', function() + texio.write('term and log', ' ' .. name) + end, name) + end + local function run(header) + texio.write_nl('term and log', header .. ':') + luatexbase.call_callback('testcallback') + end +% Add a rule before the handlers + luatexbase.declare_callback_rule('testcallback', 'third', 'before', 'first') + add_handler'first' + add_handler'second' + add_handler'third' + add_handler'forth' + add_handler'fifth' + run'First try' +% And add rules afterwards + luatexbase.declare_callback_rule('testcallback', 'third', 'before', 'second') + luatexbase.declare_callback_rule('testcallback', 'fifth', 'before', 'forth') + luatexbase.declare_callback_rule('testcallback', 'fifth', 'before', 'first') + luatexbase.declare_callback_rule('testcallback', 'first', 'after', 'second') + run'Second try' + luatexbase.declare_callback_rule('testcallback', 'third', 'before', 'forth') + luatexbase.declare_callback_rule('testcallback', 'second', 'after', 'third') + luatexbase.declare_callback_rule('testcallback', 'third', 'after', 'forth') + run'Third try' + luatexbase.declare_callback_rule('testcallback', 'second', 'before', 'forth') + local _, msg = pcall(run, 'Cycle') + texio.write_nl('term and log', msg) + luatexbase.declare_callback_rule('testcallback', 'third', 'unrelated', 'second') + run'Recovered' +} + +\END diff --git a/base/testfiles/tlb-callbacks-002.tlg b/base/testfiles/tlb-callbacks-002.tlg new file mode 100644 index 000000000..7b020a4c0 --- /dev/null +++ b/base/testfiles/tlb-callbacks-002.tlg @@ -0,0 +1,3 @@ +This is a generated file for the LaTeX2e validation system. +Don't change this file in any respect. + LuaTeX Callback ordering test diff --git a/base/testfiles/tlb-ltluatex-001.luatex.tlg b/base/testfiles/tlb-ltluatex-001.luatex.tlg index afd437d00..4730d580b 100644 --- a/base/testfiles/tlb-ltluatex-001.luatex.tlg +++ b/base/testfiles/tlb-ltluatex-001.luatex.tlg @@ -7,7 +7,7 @@ stack traceback: ^^I[C]: in function 'error' ^^I./ltluatex.lua:110: in upvalue 'module_error' ^^I./ltluatex.lua:117: in upvalue 'luatexbase_error' -^^I./ltluatex.lua:495: in field 'create_callback' +^^I./ltluatex.lua:530: in field 'create_callback' ^^I[\directlua]:1: in main chunk. l. ...} The lua interpreter ran into a problem, so the