[latex3-commits] [git/LaTeX3-latex3-latex2e] callback_rules: Rule based callback management (325a6695)

Marcel Fabian Krüger tex at 2krueger.de
Wed May 18 23:53:16 CEST 2022


Repository : https://github.com/latex3/latex2e
On branch  : callback_rules
Link       : https://github.com/latex3/latex2e/commit/325a669560ad44bcf8e62dbd037203d5ab4ed8f2

>---------------------------------------------------------------

commit 325a669560ad44bcf8e62dbd037203d5ab4ed8f2
Author: Marcel Fabian Krüger <tex at 2krueger.de>
Date:   Mon May 16 20:00:21 2022 +0200

    Rule based callback management


>---------------------------------------------------------------

325a669560ad44bcf8e62dbd037203d5ab4ed8f2
 base/ltluatex.dtx                                  | 181 +++++++++++++++++++--
 ...tlb-latexrelease-rollback-2021-06-01.luatex.tlg |   2 +-
 ...tlb-latexrelease-rollback-2021-11-15.luatex.tlg |   2 +-
 base/testfiles/tlb-ltluatex-001.luatex.tlg         |   2 +-
 base/testfiles/tlb-mathcallbacks.luatex.tlg        |   6 +-
 5 files changed, 174 insertions(+), 19 deletions(-)

diff --git a/base/ltluatex.dtx b/base/ltluatex.dtx
index 18623fd6..20b55fcc 100644
--- a/base/ltluatex.dtx
+++ b/base/ltluatex.dtx
@@ -1288,8 +1288,83 @@ 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
+                list[post_pos] = list[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
@@ -1733,10 +1808,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}
 % If it is not a user defined callback use the primitive callback register.
 %    \begin{macrocode}
@@ -1752,7 +1827,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(
@@ -1760,19 +1834,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(<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)}
@@ -1794,7 +1917,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")
@@ -1822,6 +1945,7 @@ local function remove_from_callback(name, description)
     "Removing  `" .. description .. "' from `" .. name .. "'."
   )
   if #l == 0 then
+    realcallbacklist[name] = nil
     callbacklist[name] = nil
     if user_callbacks_defaults[name] == nil then
       callback_register(name, nil)
@@ -1841,12 +1965,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
@@ -1863,7 +1987,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")
@@ -1877,12 +2001,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
@@ -1936,6 +2061,36 @@ callback_register("mlist_to_hlist", function(head, display_type, need_penalties)
 end)
 %    \end{macrocode}
 % \end{macro}
+%
+% And finally some patching for the luatexbase priority argument:
+%    \begin{macrocode}
+local func = luatexbase.new_luafunction'ConstrainCallbacks at PatchLuatexbase'
+token.set_lua('ConstrainCallbacks at 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 c0ef4498..4cbe7bf5 100644
--- a/base/testfiles/tlb-latexrelease-rollback-2021-06-01.luatex.tlg
+++ b/base/testfiles/tlb-latexrelease-rollback-2021-06-01.luatex.tlg
@@ -717,7 +717,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 9f30e524..ced0e099 100644
--- a/base/testfiles/tlb-latexrelease-rollback-2021-11-15.luatex.tlg
+++ b/base/testfiles/tlb-latexrelease-rollback-2021-11-15.luatex.tlg
@@ -717,7 +717,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 53c7483d..9e4f3289 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:428: in field 'create_callback'
+^^I./ltluatex.lua:493: 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.luatex.tlg b/base/testfiles/tlb-mathcallbacks.luatex.tlg
index 1c373bbf..8690520c 100644
--- a/base/testfiles/tlb-mathcallbacks.luatex.tlg
+++ b/base/testfiles/tlb-mathcallbacks.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





More information about the latex3-commits mailing list.