diff options
author | Norbert Preining <norbert@preining.info> | 2022-11-03 03:05:14 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2022-11-03 03:05:14 +0000 |
commit | c3c9975778ff2848e51f6debf733abe8ca930bd3 (patch) | |
tree | 034fcccacf40343842462b161df1764d3a7a41af /macros/latex-dev/base/ltluatex.dtx | |
parent | 2281af8ba8fd52341e706f3979712f3280fcfdad (diff) |
CTAN sync 202211030305
Diffstat (limited to 'macros/latex-dev/base/ltluatex.dtx')
-rw-r--r-- | macros/latex-dev/base/ltluatex.dtx | 224 |
1 files changed, 209 insertions, 15 deletions
diff --git a/macros/latex-dev/base/ltluatex.dtx b/macros/latex-dev/base/ltluatex.dtx index ab81c3620a..6c66a96be0 100644 --- a/macros/latex-dev/base/ltluatex.dtx +++ b/macros/latex-dev/base/ltluatex.dtx @@ -28,7 +28,7 @@ \ProvidesFile{ltluatex.dtx} %</driver> %<*tex> -[2022/08/13 v1.1y +[2022/10/03 v1.2a %</tex> %<plain> 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{} @@ -1288,8 +1316,120 @@ 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. +% +% \changes{v1.2a}{2022/10/03}{Add rules for callback ordering} +% \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 + meta[current.value.description] = nil + 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] = ... + 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 + realcallbacklist[name] = list + t[name] = list + return list + end +}) % \end{macrocode} % % Numerical codes for callback types, and name-to-value association (the @@ -1731,6 +1871,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 @@ -1751,10 +1892,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. @@ -1773,6 +1914,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} @@ -1780,7 +1922,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,25 +1929,76 @@ 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} +% \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) + 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(<callback>, <description_a>, <description_b>)" + ) + end + if relation == 'before' 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)} % \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) @@ -1823,7 +2015,7 @@ local function remove_from_callback(name, description) .. "remove_from_callback(<callback>, <description>)" ) end - local l = callbacklist[name] + local l = realcallbacklist[name] if not l then luatexbase_error( "No callback list for `" .. name .. "'\n") @@ -1851,6 +2043,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 +2069,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 +2091,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 +2105,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 |