Skip to content

Commit

Permalink
Rule based callback management
Browse files Browse the repository at this point in the history
  • Loading branch information
zauguin committed Oct 3, 2022
1 parent 9ac3e87 commit e7a79dc
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 26 deletions.
183 changes: 170 additions & 13 deletions base/ltluatex.dtx
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -1780,27 +1857,75 @@ 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(
"Cannot add second callback to exclusive function\n`" ..
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(<callback>, <description_a>, <description_b>)"
)
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)}
Expand All @@ -1823,7 +1948,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")
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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}
Expand Down
Expand Up @@ -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 ....
Expand Down
Expand Up @@ -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 ....
Expand Down
Expand Up @@ -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 ....
Expand Down
2 changes: 1 addition & 1 deletion base/testfiles/tlb-ltluatex-001.luatex.tlg
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions 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
12 changes: 6 additions & 6 deletions base/testfiles/tlb-mathcallbacks-002.luatex.tlg
Expand Up @@ -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
Expand Down

0 comments on commit e7a79dc

Please sign in to comment.