% \iffalse meta-comment
%
%% File: l3luatex.dtx
%
% Copyright (C) 2010-2026 The LaTeX Project
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
% license or (at your option) any later version.  The latest version
% of this license is in the file
%
%    https://www.latex-project.org/lppl.txt
%
% This file is part of the "l3kernel bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver>
\documentclass[full,kernel]{l3doc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{^^A
%   The \pkg{l3luatex} module\\ \LuaTeX-specific functions^^A
% }
%
% \author{^^A
%  The \LaTeX{} Project\thanks
%    {^^A
%      E-mail:
%        \href{mailto:latex-team@latex-project.org}
%          {latex-team@latex-project.org}^^A
%    }^^A
% }
%
% \date{Released 2026-07-20}
%
% \maketitle
%
% \begin{documentation}
%
% The \LuaTeX{} engine provides access to the \Lua{} programming language,
% and with it access to the \enquote{internals} of \TeX{}. In order to use
% this within the framework provided here, a family of functions is
% available. When used with \pdfTeX{}, \pTeX{}, \upTeX{} or \XeTeX{} these raise an
% error: use \cs{sys_if_engine_luatex:T} to avoid this. Details on using
% \Lua{} with the \LuaTeX{} engine are given in the \LuaTeX{} manual.
%
% \section{Breaking out to \Lua{}}
%
% \begin{function}[EXP, added = 2018-06-18]{\lua_now:n, \lua_now:e}
%   \begin{syntax}
%     \cs{lua_now:n} \Arg{token list}
%   \end{syntax}
%   The \meta{token list} is first tokenized by \TeX{}, which includes
%   converting line ends to spaces in the usual \TeX{} manner and which
%   respects currently-applicable \TeX{} category codes. The resulting
%   \meta{\Lua{} input} is passed to the \Lua{} interpreter for processing.
%   Each \cs{lua_now:n} block is treated by \Lua{} as a separate chunk.
%   The \Lua{} interpreter executes the \meta{\Lua{} input} immediately,
%   and in an expandable manner.
%   \begin{texnote}
%     \cs{lua_now:e} is a macro wrapper around \tn{directlua}: when
%     \LuaTeX{} is in use two expansions are required to yield the
%     result of the \Lua{} code.
%   \end{texnote}
% \end{function}
%
% \begin{function}[added = 2018-06-18]{\lua_shipout_e:n, \lua_shipout:n}
%   \begin{syntax}
%     \cs{lua_shipout:n} \Arg{token list}
%   \end{syntax}
%   The \meta{token list} is first tokenized by \TeX{}, which includes
%   converting line ends to spaces in the usual \TeX{} manner and which
%   respects currently-applicable \TeX{} category codes.  The resulting
%   \meta{\Lua{} input} is passed to the \Lua{} interpreter when the
%   current page is finalized (i.e., at shipout).  Each
%   \cs{lua_shipout:n} block is treated by \Lua{} as a separate chunk.
%   The \Lua{} interpreter will execute the \meta{\Lua{} input} during the
%   page-building routine: no \TeX{} expansion of the \meta{\Lua{} input}
%   will occur at this stage.
%
%   In the case of the \cs{lua_shipout_e:n} version the input is fully
%   expanded by \TeX{} in an \texttt{e}-type manner during the shipout
%   operation.
%   \begin{texnote}
%     At a \TeX{} level, the \meta{\Lua{} input} is stored as a
%     \enquote{whatsit}.
%   \end{texnote}
% \end{function}
%
% \begin{function}[EXP, added = 2015-06-29]{\lua_escape:n, \lua_escape:e}
%   \begin{syntax}
%     \cs{lua_escape:n} \Arg{token list}
%   \end{syntax}
%   Converts the \meta{token list} such that it can safely be passed to
%   \Lua{}: embedded backslashes, double and single quotes, and newlines
%   and carriage returns are escaped. This is done by prepending an extra
%   token consisting of a backslash with category code~$12$, and for the line
%   endings, converting them to |\n| and |\r|, respectively.
%   \begin{texnote}
%     \cs{lua_escape:e} is a macro wrapper around \tn{luaescapestring}:
%     when \LuaTeX{} is in use two expansions are required to yield the
%     result of the \Lua{} code.
%   \end{texnote}
% \end{function}
%
% \begin{function}[added = 2022-05-14]{\lua_load_module:n}
%   \begin{syntax}
%     \cs{lua_load_module:n} \Arg{Lua module name}
%   \end{syntax}
%   Loads a Lua module into the Lua interpreter.
%
%   \cs{lua_now:n} passes its \Arg{token list} argument to the Lua interpreter
%   as a single line, with characters interpreted under the current catcode
%   régime. These two facts mean that \cs{lua_now:n} rarely behaves as expected
%   for larger pieces of code. Therefore, package authors should \textbf{not}
%   write significant amounts of Lua code in the arguments to \cs{lua_now:n}.
%   Instead, it is strongly recommended that they write the majorty of their Lua
%   code in a separate file, and then load it using \cs{lua_load_module:n}.
%   \begin{texnote}
%     This is a wrapper around the Lua call |require '|\meta{module}|'|.
%   \end{texnote}
% \end{function}
%
% \section{Lua interfaces}
%
% As well as interfaces for \TeX{}, there are a small number of Lua functions
% provided here.
%
% \begin{function}{ltx.utils}
%   Most public interfaces provided by the module are stored within the
%   |ltx.utils| table.
% \end{function}
%
% \begin{function}{ltx.utils.filedump}
%   \begin{syntax}
%     \meta{dump}| = ltx.utils.filedump(|\meta{file}|,|\meta{offset}|,|\meta{length}|)| \\
%   \end{syntax}
%   Returns the uppercase hexadecimal representation of the content of the
%   \meta{file} read as bytes. If the \meta{length} is given, only this part
%   of the file is returned; similarly, one may specify the \meta{offset} from
%   the start of the file. If the \meta{length} is not given, the entire file
%   is read starting at the \meta{offset}.
% \end{function}
%
% \begin{function}{ltx.utils.filemd5sum}
%   \begin{syntax}
%     \meta{hash}| = ltx.utils.filemd5sum(|\meta{file}|)| \\
%   \end{syntax}
%   Returns the MD5 sum of the file contents read as bytes; note that
%   the result will depend on the nature of the line endings used in the file,
%   in contrast to normal \TeX{} behavior. If the \meta{file} is not found,
%   nothing is returned with \emph{no error raised}.
% \end{function}
%
% \begin{function}{ltx.utils.filemoddate}
%   \begin{syntax}
%     \meta{date}| = ltx.utils.filemoddate(|\meta{file}|)| \\
%   \end{syntax}
%   Returns the date/time of last modification of the \meta{file} in the
%   format
%   \begin{quote}
%     |D:|\meta{year}\meta{month}\meta{day}\meta{hour}\meta{minute}^^A
%     \meta{second}\meta{offset}
%   \end{quote}
%   where the latter may be |Z| (UTC) or
%   \meta{plus-minus}\meta{hours}|'|\meta{minutes}|'|. If the \meta{file} is
%   not found, nothing is returned with \emph{no error raised}.
% \end{function}
%
% \begin{function}{ltx.utils.filesize}
%   \begin{syntax}
%     |size = ltx.utils.filesize(|\meta{file}|)| \\
%   \end{syntax}
%   Returns the size of the \meta{file} in bytes. If the \meta{file} is not
%   found, nothing is returned with \emph{no error raised}.
% \end{function}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3luatex} implementation}
%
%    \begin{macrocode}
%<@@=lua>
%    \end{macrocode}
%
% \subsection{Breaking out to \Lua{}}
%
%    \begin{macrocode}
%<*code>
%    \end{macrocode}
%
% \begin{macro}[EXP]{\@@_escape:n, \@@_now:n, \@@_shipout:n}
%   Copies of primitives.
%    \begin{macrocode}
\cs_new_eq:NN \@@_escape:n  \tex_luaescapestring:D
\cs_new_eq:NN \@@_now:n     \tex_directlua:D
\cs_new_eq:NN \@@_shipout:n \tex_latelua:D
%    \end{macrocode}
% \end{macro}
%
% These functions are set up in \pkg{l3str} for bootstrapping: we want to
% replace them with a \enquote{proper} version at this stage, so clean up.
%    \begin{macrocode}
\cs_undefine:N \lua_escape:e
\cs_undefine:N \lua_now:e
%    \end{macrocode}
%
% \begin{macro}[EXP]{\lua_now:n, \lua_now:e}
% \begin{macro}{\lua_shipout_e:n, \lua_shipout:n}
% \begin{macro}[EXP]{\lua_escape:n, \lua_escape:e}
%   Wrappers around the primitives.
%    \begin{macrocode}
\cs_new:Npn \lua_now:e #1 { \@@_now:n {#1} }
\cs_new:Npn \lua_now:n #1 { \lua_now:e { \exp_not:n {#1} } }
\cs_new_protected:Npn \lua_shipout_e:n #1 { \@@_shipout:n {#1} }
\cs_new_protected:Npn \lua_shipout:n #1
  { \lua_shipout_e:n { \exp_not:n {#1} } }
\cs_new:Npn \lua_escape:e #1 { \@@_escape:n {#1} }
\cs_new:Npn \lua_escape:n #1 { \lua_escape:e { \exp_not:n {#1} } }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\lua_load_module:n}
%   Wrapper around |require'|\meta{module}|'|.
%    \begin{macrocode}
\str_new:N \l_@@_err_msg_str
\cs_new_protected:Npn \lua_load_module:n #1
  {
    \bool_if:nF { \@@_load_module_p:n { #1 } }
      {
        \msg_error:nnnV
          { luatex } { module-not-found } { #1 } \l_@@_err_msg_str
      }
  }
%    \end{macrocode}
% \end{macro}
%
%   As with engines other than \LuaTeX{}
%   these have to be macros, we give them the same status in all cases.
%   When \LuaTeX{} is not in use, simply give an error message/
%    \begin{macrocode}
\sys_if_engine_luatex:F
  {
    \clist_map_inline:nn
      {
        \lua_escape:n , \lua_escape:e ,
        \lua_now:n , \lua_now:e
      }
      {
        \cs_gset:Npn #1 ##1
          {
            \msg_expandable_error:nnn
              { luatex } { luatex-required } { #1 }
          }
      }
    \clist_map_inline:nn
      { \lua_shipout_e:n , \lua_shipout:n, \lua_load_module:n }
      {
        \cs_gset_protected:Npn #1 ##1
          {
            \msg_error:nnn
              { luatex } { luatex-required } { #1 }
          }
      }
  }
%    \end{macrocode}
% \subsection{Messages}
%
%    \begin{macrocode}
\msg_new:nnnn { luatex } { luatex-required }
  { LuaTeX~engine~not~in~use!~Ignoring~#1. }
  {
    The~feature~you~are~using~is~only~available~
    with~the~LuaTeX~engine.~LaTeX3~ignored~'#1'.
  }
\msg_new:nnnn { luatex } { module-not-found }
  { Lua~module~`#1'~not~found. }
  {
    The~file~`#1.lua'~could~not~be~found.~Please~ensure~
    that~the~file~was~properly~installed~and~that~the~
    filename~database~is~current. \\ \\
    The~Lua~loader~provided~this~additional~information: \\
    #2
  }
\prop_gput:Nnn \g_msg_module_name_prop { luatex } { LaTeX }
\prop_gput:Nnn \g_msg_module_type_prop { luatex } { }
%    \end{macrocode}
%
%    \begin{macrocode}
%</code>
%    \end{macrocode}
%
% \subsection{\Lua{} functions for internal use}
%
%    \begin{macrocode}
%<*lua>
%    \end{macrocode}
%
% Most of the emulation of \pdfTeX{} here is based heavily on Heiko Oberdiek's
% \pkg{pdftexcmds} package.
%
% \begin{macro}{ltx.utils}
%   Create a table for the kernel's own use.
%    \begin{macrocode}
ltx = ltx or {utils={}}
ltx.utils = ltx.utils or { }
local ltxutils = ltx.utils
%    \end{macrocode}
% \end{macro}
%
%   Local copies of global tables.
%    \begin{macrocode}
local io       = io
local kpse     = kpse
local lfs      = lfs
local math     = math
local md5      = md5
local os       = os
local string   = string
local tex      = tex
local texio    = texio
local tonumber = tonumber
local token    = token
%    \end{macrocode}
%
%   Local copies of standard functions.
%    \begin{macrocode}
local abs        = math.abs
local byte       = string.byte
local floor      = math.floor
local format     = string.format
local gsub       = string.gsub
local lfs_attr   = lfs.attributes
local open       = io.open
local os_date    = os.date
local setcatcode = tex.setcatcode
local sprint     = tex.sprint
local cprint     = tex.cprint
local write      = tex.write
local write_nl   = texio.write_nl
local utf8_char  = utf8.char
local package_loaded    = package.loaded
local package_searchers = package.searchers
local table_concat      = table.concat

local scan_csname   = token.scancsname or token.scan_csname
local scan_int      = token.scan_int or token.scan_integer
local scan_string   = token.scanstring or token.scan_string
local scan_keyword  = token.scankeyword or token.scan_keyword
local scan_argument = token.scanargument or token.scan_argument
local get_next      = token.scannext or token.get_next
local put_next      = token.putnext or token.put_next
local token_create  = token.create
local get_macro     = token.getmacro or token.get_macro
local get_protected = token.getprotected or token.get_protected
local get_csname    = token.getcsname or token.get_csname
local token_new     = token.new
local set_macro     = token.setmacro or token.set_macro

local active_prefix = status.luatex_engine == 'luametatex' and status.getconstants().active_character_namespace or utf8.char(0xFFFF)

local lbrace, rbrace = token_create(byte'{'), token_create(byte'}')
%    \end{macrocode}
%
%   Since token.create only returns useful values after the tokens
%   has been added to TeX's hash table, we define a variant which
%   defines it first if necessary.
%    \begin{macrocode}
local token_create_safe
do
  local is_defined = token.is_defined
  local set_char   = token.set_char or tex.chardef
  local runtoks    = tex.runtoks
  local let_token  = token_create'let'

  function token_create_safe(s)
    local orig_token = token_create(s)
    if is_defined(s, true) then
      return orig_token
    end
    set_char(s, 0)
    local new_token = token_create(s)
    runtoks(function()
      put_next(let_token, new_token, orig_token)
    end)
    return new_token
  end
end

local true_tok     = token_create_safe'prg_return_true:'
local false_tok    = token_create_safe'prg_return_false:'
%    \end{macrocode}
% In Con\TeX{}t lmtx \texttt{token.command_id} does not exist,
% but it can easily be emulated with Con\TeX{}t's \texttt{tokens.commands}.
%    \begin{macrocode}
local command_id   = token.command_id
if not command_id and tokens and tokens.commands then
  local id_map = tokens.commands
  function command_id(name)
    return id_map[name]
  end
end
%    \end{macrocode}
%
%   Deal with Con\TeX{}t: doesn't use |kpse| library.
%    \begin{macrocode}
local kpse_find = (resolvers and resolvers.findfile) or kpse.find_file
%    \end{macrocode}
%
% \begin{macro}[int]{escapehex}
%   An internal auxiliary to convert a string to the matching hex escape.
%   This works on a byte basis: extension to handled UTF-8 input is
%   covered in \pkg{pdftexcmds} but is not currently required here.
%    \begin{macrocode}
local function escapehex(str)
  return (gsub(str, ".",
    function (ch) return format("%02X", byte(ch)) end))
end
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{ltx.utils.filedump}
%   Similar comments here to the next function: read the file in binary mode
%   to avoid any line-end weirdness.
%    \begin{macrocode}
local function filedump(name,offset,length)
  local file = kpse_find(name,"tex",true)
  if not file then return end
  local f = open(file,"rb")
  if not f then return end
  if offset and offset > 0 then
    f:seek("set", offset)
  end
  local data = f:read(length or 'a')
  f:close()
  return escapehex(data)
end
ltxutils.filedump = filedump
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{md5.HEX}
% Hash a string and return the hash in uppercase hexadecimal format.
% In some engines, this is built-in. For traditional \LuaTeX{}, the conversion
% to hexadecimal has to be done by us.
%    \begin{macrocode}
local md5_HEX = md5.HEX
if not md5_HEX then
  local md5_sum = md5.sum
  function md5_HEX(data)
    return escapehex(md5_sum(data))
  end
  md5.HEX = md5_HEX
end
%    \end{macrocode}
% \end{macro}
% \begin{macro}{ltx.utils.filemd5sum}
%   Read an entire file and hash it: the hash function itself is a built-in.
%   As Lua is byte-based there is no work needed here in terms of UTF-8
%   (see \pkg{pdftexcmds} and how it handles strings that have passed through
%   \LuaTeX{}). The file is read in binary mode so that no line ending
%   normalization occurs.
%    \begin{macrocode}
local function filemd5sum(name)
  local file = kpse_find(name, "tex", true) if not file then return end
  local f = open(file, "rb") if not f then return end

  local data = f:read("*a")
  f:close()
  return md5_HEX(data)
end
ltxutils.filemd5sum = filemd5sum
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{ltx.utils.filemoddate}
%   There are two cases: If the C standard library is C99 compliant,
%   we can use |%z| to get the timezone in almost the right format.
%   We only have to add primes and replace a zero or missing offset
%   with |Z|.
%
%   Of course this would be boring, so Windows does things differently.
%   There we have to manually calculate the offset.
%   See procedure \texttt{makepdftime} in \texttt{utils.c} of
%   \pdfTeX{}.
%    \begin{macrocode}
local filemoddate
if os_date'%z':match'^[+-]%d%d%d%d$' then
  local pattern = lpeg.Cs(16 *
      (lpeg.Cg(lpeg.S'+-' * '0000' * lpeg.Cc'Z')
    + 3 * lpeg.Cc"'" * 2 * lpeg.Cc"'"
    + lpeg.Cc'Z')
  * -1)
  function filemoddate(name)
    local file = kpse_find(name, "tex", true)
    if not file then return end
    local date = lfs_attr(file, "modification")
    if not date then return end
    return pattern:match(os_date("D:%Y%m%d%H%M%S%z", date))
  end
else
  local function filemoddate(name)
    local file = kpse_find(name, "tex", true)
    if not file then return end
    local date = lfs_attr(file, "modification")
    if not date then return end
    local d = os_date("*t", date)
    local u = os_date("!*t", date)
    local off = 60 * (d.hour - u.hour) + d.min - u.min
    if d.year ~= u.year then
      if d.year > u.year then
        off = off + 1440
      else
        off = off - 1440
      end
    elseif d.yday ~= u.yday then
      if d.yday > u.yday then
        off = off + 1440
      else
        off = off - 1440
      end
    end
    local timezone
    if off == 0 then
      timezone = "Z"
    else
      if off < 0 then
        timezone = "-"
        off = -off
      else
        timezone = "+"
      end
      timezone = format("%s%02d'%02d'", timezone, hours // 60, hours % 60)
    end
    return format("D:%04d%02d%02d%02d%02d%02d%s",
        d.year, d.month, d.day, d.hour, d.min, d.sec, timezone)
  end
end
ltxutils.filemoddate = filemoddate
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{ltx.utils.filesize}
%   A simple disk lookup.
%    \begin{macrocode}
local function filesize(name)
  local file = kpse_find(name, "tex", true)
  if file then
    local size = lfs_attr(file, "size")
    if size then
      return size
    end
  end
end
ltxutils.filesize = filesize
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{luacmd}
% An internal function for defining control sequences form Lua which behave
% like primitives. This acts as a wrapper around |token.set_lua| which accepts
% a function instead of an index into the functions table.
%    \begin{macrocode}
local luacmd do
  local set_lua = token.setlua or token.set_lua
  local undefined_cs = command_id'undefined_cs'

  if not context and not luatexbase then require'ltluatex' end
  if luatexbase then
    local new_luafunction = luatexbase.new_luafunction
    local functions = lua.get_functions_table()
    function luacmd(name, func, ...)
      local id
      local tok = token_create(name)
      if tok.command == undefined_cs then
        id = new_luafunction(name)
        set_lua(name, id, ...)
      else
        id = tok.index or tok.mode
      end
      functions[id] = func
    end
  elseif context then
    local register = context.functions.register
    local functions = context.functions.known
    function luacmd(name, func, ...)
      local tok = token_create(name)
      if tok.command == undefined_cs then
        set_lua(name, register(func), ...)
      else
        functions[tok.index or tok.mode] = func
      end
    end
  end
end
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{try_require}
%   Loads a Lua module. This function loads the module similarly to the standard
%   Lua global function |require|, with a few differences. On success,
%   |try_require| returns |true, module|. If the module cannot be found, it
%   returns |false, err_msg|. If the module is found, but something goes wrong
%   when loading it, the function throws an error.
%    \begin{macrocode}
local function try_require(name)
  if package_loaded[name] then
    return true, package_loaded[name]
  end

  local failure_details = {}
  for _, searcher in ipairs(package_searchers) do
    local loader, data = searcher(name)
    if type(loader) == 'function' then
      package_loaded[name] = loader(name, data) or true
      return true, package_loaded[name]
    elseif type(loader) == 'string' then
      failure_details[#failure_details + 1] = loader
    end
  end

  return false, table_concat(failure_details, '\n')
end
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_load_module_p:n}
%   Check to see if we can load a module using |require|. If we
%   can load the module, then we load it immediately. Otherwise, we save the
%   error message in |\l_@@_err_msg_str|.
%    \begin{macrocode}
local char_given   = command_id'char_given'
local c_true_bool  = token_create(1, char_given)
local c_false_bool = token_create(0, char_given)
local c_str_cctab  = token_create('c_str_cctab').mode

luacmd('@@_load_module_p:n', function()
  local success, result = try_require(scan_string())
  if success then
    set_macro(c_str_cctab, 'l_@@_err_msg_str', '')
    put_next(c_true_bool)
  else
    set_macro(c_str_cctab, 'l_@@_err_msg_str', result)
    put_next(c_false_bool)
  end
end)
%    \end{macrocode}
% \end{macro}
%
% \subsection{Preserving iniTeX Lua data for runs}
%
% The Lua state is not dumped when a format is written, therefore any Lua
% variables filled doing format building need to be restored in order to
% be accessible during normal runs.
%
% We provide some kernel-internal helpers for this. They will only be available if
% \texttt{luatexbase} is available. This is not a big restriction though, because
% Con\TeX{}t (which does not use \texttt{luatexbase}) does not load \pkg{expl3}
% in the format.
%
%    \begin{macrocode}
local register_luadata, get_luadata

if luatexbase then
  local register = token_create'@expl@luadata@bytecode'.index
%    \end{macrocode}
%
% \begin{macro}[int]{register_luadata}
% \texttt{register_luadata} is only available during format generation.
% It accept a string which uniquely identifies the data object and has to be
% provided to retrieve it later. Additionally it accepts a function which is
% called in the \texttt{pre_dump} callback and which has to return a string that
% evaluates to a valid Lua object to be preserved. Note that format generation
% does not necessarily mean the first run, because some packages such as
% \pkg{mylatexformat} generates a format based on an existing format.
%    \begin{macrocode}
  if status.ini_version then
    local luadata, luadata_order = {}, {}

    function register_luadata(name, func)
      if luadata[name] then
        error(format("LaTeX error: data name %q already in use", name))
      end
      luadata[name] = func
      luadata_order[#luadata_order + 1] = func and name
    end
%    \end{macrocode}
% \end{macro}
%
% The actual work is done in \texttt{pre_dump}. The \texttt{luadata_order} is used
% to ensure that the order is consistent over multiple runs.
%    \begin{macrocode}
    luatexbase.add_to_callback("pre_dump", function()
      if next(luadata) then
        local str = "return {"
        for i=1, #luadata_order do
          local name = luadata_order[i]
          str = format('%s[%q]=%s,', str, name, luadata[name]())
        end
        lua.bytecode[register] = assert(load(str .. "}"))
      end
    end, "ltx.luadata")
  end
%    \end{macrocode}
%
% \begin{macro}[int]{get_luadata}
% \texttt{get_luadata} is only available if data should be restored.
% It accept the identifier which was used when the data object was registered and
% returns the associated object. Every object can only be retrieved once.
% Note that it is possible for both \texttt{register_luadata} and \texttt{get_luadata}
% to be available, for example while compiling a precompiled preamble.
% In such a case, data must be registered again to be kept in the next run.
%    \begin{macrocode}
  local luadata = lua.bytecode[register]
  if luadata then
    lua.bytecode[register] = nil
    luadata = luadata()
    function get_luadata(name)
      if not luadata then return end
      local data = luadata[name]
      luadata[name] = nil
      return data
    end
  end
end
%    \end{macrocode}
% \end{macro}
%
% \subsection{Parsing Unicode data files}
%
% \begin{macro}[int]{\__kernel_codepoint_data:wn}
% \begin{macro}[int]{\__codepoint_nfd:w}
% This provides a macro to access a Unicode data entry. The first argument is a
% unbraced number, the second a string identifying the kind of data to read.
% The supported kinds match the data tables supported by \cs\__kernel_codepoint_data:nn
% in other engines.
%    \begin{macrocode}
do
  if get_luadata then
    local saved_unidata = get_luadata'lua-uni-data'
    if saved_unidata then saved_unidata() end
  end
  if register_luadata then
    register_luadata('lua-uni-data', function()
      return string.format("load(%q, nil, 'b')", string.dump(require'lua-uni-data-preload'.generate_bytecode(), true))
    end)
  end

  local uni_data = require'lua-uni-data'
  local tables, decomposition_mapping = uni_data.tables, uni_data.misc.decomposition_mapping

  luacmd('__kernel_codepoint_data:wn', function()
    local cp = scan_int()
    local table = scan_argument()
    return sprint(-2, tostring(tables[table][cp]))
  end)

  luacmd('__codepoint_nfd:w', function()
    local cp = scan_int()
    local decomposed = decomposition_mapping[cp]
    if decomposed then
      for i=1,2 do
        if decomposed[i] then
          sprint(-2, lbrace, tostring(decomposed[i]), rbrace)
        else
          sprint(-2, lbrace, rbrace)
        end
      end
    else
      return sprint(-2, lbrace, tostring(cp), rbrace, lbrace, rbrace)
    end
  end)
end
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
%    \begin{macrocode}
%</lua>
%    \end{macrocode}
%
%\end{implementation}
%
%\PrintIndex
