% \iffalse meta-comment
%
% Copyright 2009, 2010 by Élie Roux <elie.roux@telecom-bretagne.eu>
% Copyright 2010, 2011 by Manuel Pégourié-Gonnard <mpg@elzevir.fr>
% Copyright 2013 by Philipp Gesang <philipp.gesang@alumni.uni-heidelberg.de>
%
% This work is under the CC0 license.
%
% This work consists of the main source file luatexbase-attr.dtx
% and the derived files
%    luatexbase-attr.sty attr.lua
%    test-regs-plain.tex test-regs-latex.tex
%
% Unpacking:
%    tex luatexbase-attr.dtx
% Documentation:
%    pdflatex luatexbase-attr.dtx
%
%<*ignore>
\begingroup
  \def\x{LaTeX2e}%
\expandafter\endgroup
\ifcase 0\ifx\install y1\fi\expandafter
         \ifx\csname processbatchFile\endcsname\relax\else1\fi
         \ifx\fmtname\x\else 1\fi\relax
\else\csname fi\endcsname
%</ignore>
%<*install>
\input docstrip.tex

\keepsilent
\askforoverwritefalse

\let\MetaPrefix\relax

\preamble

See the aforementioned source file(s) for copyright and licensing information.

\endpreamble

\let\MetaPrefix\DoubleperCent

\generate{%
  \usedir{tex/luatex/luatexbase}%
  \file{luatexbase-attr.sty}{\from{luatexbase-attr.dtx}{texpackage}}%
}

\generate{%
  \usedir{doc/luatex/luatexbase}%
  \file{test-attr-plain.tex}{\from{luatexbase-attr.dtx}{testplain}}%
  \file{test-attr-latex.tex}{\from{luatexbase-attr.dtx}{testlatex}}%
}

\def\MetaPrefix{-- }

\def\luapostamble{%
  \MetaPrefix^^J%
  \MetaPrefix\space End of File `\outFileName'.%
}

\def\currentpostamble{\luapostamble}%

\generate{%
  \usedir{tex/luatex/luatexbase}%
  \file{attr.lua}{\from{luatexbase-attr.dtx}{luamodule}}%
}

\obeyspaces
\Msg{************************************************************************}
\Msg{*}
\Msg{* To finish the installation you have to move the following}
\Msg{* files into a directory searched by TeX:}
\Msg{*}
\Msg{*     luatexbase-attr.sty attr.lua}
\Msg{*}
\Msg{* Happy TeXing!}
\Msg{*}
\Msg{************************************************************************}

\endbatchfile
%</install>
%<*ignore>
\fi
%</ignore>
%<*driver>
\documentclass{ltxdoc}
\input lltxb-dtxstyle
\begin{document}
  \DocInput{luatexbase-attr.dtx}%
\end{document}
%</driver>
% \fi
%
% \CheckSum{0}
%
% \CharacterTable
%  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%   Lower-case    \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%   Digits        \0\1\2\3\4\5\6\7\8\9
%   Exclamation   \!     Double quote  \"     Hash (number) \#
%   Dollar        \$     Percent       \%     Ampersand     \&
%   Acute accent  \'     Left paren    \(     Right paren   \)
%   Asterisk      \*     Plus          \+     Comma         \,
%   Minus         \-     Point         \.     Solidus       \/
%   Colon         \:     Semicolon     \;     Less than     \<
%   Equals        \=     Greater than  \>     Question mark \?
%   Commercial at \@     Left bracket  \[     Backslash     \\
%   Right bracket \]     Circumflex    \^     Underscore    \_
%   Grave accent  \`     Left brace    \{     Vertical bar  \|
%   Right brace   \}     Tilde         \~}
%
% \pkdate{luatexbase-attr}{v0.6 2013-05-11}
%
% \maketitle
%
% \begin{abstract}
% In addition to the registers existing in \tex and \etex, \luatex introduces
% a new concept: attributes. This package takes care of attribute allocation
% just like Plain \tex and \latex do for other registers, and also provides a
% Lua interface.
% \end{abstract}
%
% \tableofcontents
%
% \section{Documentation}
%
% \subsection{\tex interface}
%
% The main macro defined here is |\newluatexattribute|. It behaves in the same
% way as |\newcount|. There are also two helper macros: |\setluatexattibute|
% sets an attribute's value (locally, but you can use |\global| in front of
% it). |\unsetluatexattribute| unsets an attribute by giving it a special
% value, depending on \luatex's version; you should always use this macro
% in order to be sure the correct special value for your version of \luatex is
% used.
%
% Due to the intended use of attributes, it makes no sense to locally
% allocate an attribute the way you can locally allocate a counter using
% \file{etex.sty}'s |\loccount|, so no corresponding macro is defined.
%
% \subsection{Lua interface}
%
% The various Lua functions for manipulating attributes use a number to
% designate the attribute. Hence, package writers need a way to know the
% number of the attribute associated to |\fooattr| assuming it was defined
% using |\newluatexattribute\fooattr|, something that \luatex currently
% doesn't support (you can get the current value of the associated attribute
% as |tex.attribute.fooattr|, but not the attribute number).
%
% There are several ways to work around this. For example, it is possible to
% extract the number at any time from the |\meaning| of |\fooattr|.
% Alternatively, one could look at |\the\allocationnumber| just after the
% definition of |\fooattr| and remember it in a Lua variable. For your
% convenience, this is automatically done by |\newluatexattribute|: the number
% is remembered in a dedicated Lua table so that you can get it as
% |luatexbase.attributes.fooattr| (mind the absence of backslash here) at any
% time.
%
% Also, two Lua functions are provided that are analogous to the above \tex
% macros (actually, the macros are wrappers around the functions):
% |luatexbase.new_attribute|\parg{name} allocates a new attribute, without
% defining a corresponding \tex control sequence (only an entry in
% |luatexbase.attributes| is created. It usually returns the number of the
% allocated attribute. If room is missing, it raises an error, unless the
% second argument (optional) is not false, in which case it returns -1.
%
% |luatexbase.unset_attribute|\parg{name} unsets an existing attribute.
%
%    \section{Implementation}
%
%    \subsection{\tex package}
%
%    \begin{macrocode}
%<*texpackage>
%    \end{macrocode}
%
%    \subsubsection{Preliminaries}
%
%    Catcode defenses and reload protection.
%
%    \begin{macrocode}
\begingroup\catcode61\catcode48\catcode32=10\relax% = and space
  \catcode123 1 % {
  \catcode125 2 % }
  \catcode 35 6 % #
  \toks0\expandafter{\expandafter\endlinechar\the\endlinechar}%
  \edef\x{\endlinechar13}%
  \def\y#1 #2 {%
    \toks0\expandafter{\the\toks0 \catcode#1 \the\catcode#1}%
    \edef\x{\x \catcode#1 #2}}%
  \y  13  5 % carriage return
  \y  61 12 % =
  \y  32 10 % space
  \y 123  1 % {
  \y 125  2 % }
  \y  35  6 % #
  \y  64 11 % @ (letter)
  \y  10 12 % new line ^^J
  \y  34 12 % "
  \y  39 12 % '
  \y  40 12 % (
  \y  41 12 % )
  \y  44 12 % ,
  \y  45 12 % -
  \y  46 12 % .
  \y  47 12 % /
  \y  58 12 % :
  \y  60 12 % <
  \y  62 12 % >
  \y  91 12 % [
  \y  93 12 % ]
  \y  94  7 % ^
  \y  95  8 % _
  \y  96 12 % `
  \toks0\expandafter{\the\toks0 \relax\noexpand\endinput}%
  \edef\y#1{\noexpand\expandafter\endgroup%
    \noexpand\ifx#1\relax \edef#1{\the\toks0}\x\relax%
    \noexpand\else \noexpand\expandafter\noexpand\endinput%
    \noexpand\fi}%
\expandafter\y\csname luatexbase@attr@sty@endinput\endcsname%
%    \end{macrocode}
%
%    Package declaration.
%
%    \begin{macrocode}
\begingroup
  \expandafter\ifx\csname ProvidesPackage\endcsname\relax
    \def\x#1[#2]{\immediate\write16{Package: #1 #2}}
  \else
    \let\x\ProvidesPackage
  \fi
\expandafter\endgroup
\x{luatexbase-attr}[2013/05/11 v0.6 Attributes allocation for LuaTeX]
%    \end{macrocode}
%
%    Make sure \luatex is used.
%
%    \begin{macrocode}
\begingroup\expandafter\expandafter\expandafter\endgroup
\expandafter\ifx\csname RequirePackage\endcsname\relax
  \input ifluatex.sty
\else
  \RequirePackage{ifluatex}
\fi
\ifluatex\else
  \begingroup
    \expandafter\ifx\csname PackageError\endcsname\relax
      \def\x#1#2#3{\begingroup \newlinechar10
        \errhelp{#3}\errmessage{Package #1 error: #2}\endgroup}
    \else
      \let\x\PackageError
    \fi
  \expandafter\endgroup
  \x{luatexbase-attr}{LuaTeX is required for this package. Aborting.}{%
    This package can only be used with the LuaTeX engine^^J%
    (command `lualatex' or `luatex').^^J%
    Package loading has been stopped to prevent additional errors.}
  \expandafter\luatexbase@attr@sty@endinput%
\fi
%    \end{macrocode}
%
%    \subsubsection{Primitives needed}
%
%    First load \pk{luatexbase-modutils} (hence \pk{luatexbase-loader}
%    and \pk{luatexbase-compat}), and make sure \pk{luatex.sty} is loaded too.
%
%    \begin{macrocode}
\begingroup\expandafter\expandafter\expandafter\endgroup
\expandafter\ifx\csname RequirePackage\endcsname\relax
  \input luatexbase-modutils.sty
  \input luatex.sty
\else
  \RequirePackage{luatexbase-modutils}
  \RequirePackage{luatex}
\fi
%    \end{macrocode}
%
%    Make sure the primitives we need are available.
%
%    \begin{macrocode}
\luatexbase@ensure@primitive{luaescapestring}
\luatexbase@ensure@primitive{attributedef}
\luatexbase@ensure@primitive{attribute}
%    \end{macrocode}
%
%    \subsubsection{Load supporting Lua module}
%
%    \begin{macrocode}
\luatexbase@directlua{require('luatexbase.attr')}
%    \end{macrocode}
%
%    \subsection{User macros}
%
%    The allocation macro is merely a wrapper around the Lua function, but
%    handles error and logging in \tex, for consistency with other allocation
%    macros.
%
%    \begin{macrocode}
\def\newluatexattribute#1{%
  \begingroup\escapechar\m@ne \expandafter\expandafter\expandafter
  \endgroup                   \expandafter\expandafter\expandafter
  \allocationnumber           \luatexbase@directlua{tex.write(
    luatexbase.new_attribute("\luatexluaescapestring{\string#1}", true))}\relax%
  \ifnum\allocationnumber>\m@ne
    \global\luatexattributedef#1=\allocationnumber
    \wlog{\string#1=\string\luatexattribute\the\allocationnumber}%
  \else
    \errmessage{No room for a new \string\attribute}%
  \fi
  \global\unsetluatexattribute#1% if you move this line up a little, bad
  % things happen, see #20
  }
%    \end{macrocode}
%
%    Helper macro |\unsetluatexattribute|.
%
%    \begin{macrocode}
\newcount\lltxb@attr@unsetvalue
\lltxb@attr@unsetvalue=\ifnum\luatexversion<37 -1\else -2147483647\fi\relax
\def\unsetluatexattribute#1{%
  #1\lltxb@attr@unsetvalue}
%    \end{macrocode}
%
%    And now the trivial helper macro.
%
%    \begin{macrocode}
\def\setluatexattribute#1#2{%
  #1=\numexpr#2\relax}
%    \end{macrocode}
%
%    That's all folks!
%
%    \begin{macrocode}
\luatexbase@attr@sty@endinput%
%</texpackage>
%    \end{macrocode}
%
%    \subsection{Lua module}
%
%    \begin{macrocode}
%<*luamodule>
--- locals
local copynode          = node.copy
local newnode           = node.new
local nodesubtype       = node.subtype
local nodetype          = node.id
local stringfind        = string.find
local stringformat      = string.format
local tableunpack       = unpack or table.unpack
local texiowrite_nl     = texio.write_nl
local texiowrite        = texio.write
--- luatex internal types
local whatsit_t         = nodetype"whatsit"
local user_defined_t    = nodesubtype"user_defined"
local unassociated      = "__unassociated"
luatexbase              = luatexbase or { }
local luatexbase        = luatexbase
local err, warning, info, log = luatexbase.provides_module({
    name          = "luatexbase-attr",
    version       = 0.6,
    date          = "2013/05/11",
    description   = "Attributes allocation for LuaTeX",
    author        = "Elie Roux, Manuel Pegourie-Gonnard and Philipp Gesang",
    copyright     = "Elie Roux, Manuel Pegourie-Gonnard and Philipp Gesang",
    license       = "CC0",
})
%    \end{macrocode}
%
%    This table holds the values of the allocated attributes, indexed by name.
%
%    \begin{macrocode}
luatexbase.attributes   = luatexbase.attributes or { }
local attributes        = luatexbase.attributes
%    \end{macrocode}
%
%    Scoping: we use locals for the attribute functions.
%
%    \begin{macrocode}
local new_attribute
local unset_attribute
%    \end{macrocode}
%
%    In the \luatex ecosystem there are currently two functions that create a
%    new attribute.
%    One is in |oberdiek| bundle, the other is this one. We will hack a little
%    in order to make them compatible. The other function uses
%    |LuT@AllocAttribute| as attribute counter, we will keep it in sync with
%    ours. A possible problem might also appear: the other function starts
%    attribute allocation at 0, which will break luaotfload. We output an
%    error if a new attribute has already been allocated with number 0.
%
%    \begin{macrocode}
local luatex_sty_counter = 'LuT@AllocAttribute'
if tex.count[luatex_sty_counter] then
  if tex.count[luatex_sty_counter] > -1 then
    error("luatexbase error: Attribute 0 has already been set by the \\newattribute "
        .."macro from luatex.sty, not belonging to this package. This makes "
        .."luaotfload unusable. Please report to the maintainer of luatex.sty.")
  else
    tex.count[luatex_sty_counter] = 0
  end
end
%    \end{macrocode}
%
%    The allocation function. Unlike other registers, allocate starting from 1.
%    Some code (e.~g., font handling coming from Con\tex{}t) behaves strangely
%    with \verb+\attribute0+ set, and since there is plenty of room here, it
%    doesn't seem bad to ``lose'' one item in order to avoid this problem.
%
%    \begin{macrocode}
local last_alloc = 0
function new_attribute(name, silent)
    if last_alloc >= 65535 then
        if silent then
            return -1
        else
            error("No room for a new \\attribute", 1)
        end
    end
    local lsc = tex.count[luatex_sty_counter]
    if lsc and lsc > last_alloc then
      last_alloc = lsc
    end
    last_alloc = last_alloc + 1
    if lsc then
      tex.setcount('global', luatex_sty_counter, last_alloc)
    end
    attributes[name] = last_alloc
    if not silent then
        log('luatexbase.attributes[%q] = %d', name, last_alloc)
    end
    return last_alloc
end
luatexbase.new_attribute = new_attribute
%    \end{macrocode}
%
%    Unset an attribute the correct way depending on \luatex's version.
%    The constant \verb+unset_value+ can be retrieved by calling
%    \verb+get_unset_value()+ to apply to nodes.
%
%    \begin{macrocode}
local unset_value = (luatexbase.luatexversion < 37) and -1 or -2147483647
function unset_attribute(name)
    tex.setattribute(attributes[name], unset_value)
end
luatexbase.unset_attribute = unset_attribute
luatexbase.get_unset_value = function () return unset_value end
%    \end{macrocode}
%
%   Allocation of user-defined whatsit nodes (experimental).
%   User-defined whatsit nodes (or user whatsits) are ignored by the
%   \luatex engine. They can thus be used to store information in
%   node lists without doing any harm. User whatsits can be
%   distinguished by an id that is stored in node field |user_id|.
%
%    \begin{macrocode}
--- cf. luatexref-t.pdf, sect. 8.1.4.25
local user_whatsits       = {    --- (package, (name, id hash)) hash
    __unassociated = { },        --- those without package name
}
local whatsit_ids         = { }  --- (id, (name * package)) hash
local whatsit_cap         = 2^53 --- Lua numbers are doubles
local current_whatsit     = 0
local anonymous_whatsits  = 0
local anonymous_prefix    = "anon"
%    \end{macrocode}
%
%    User whatsit allocation is split into two functions:
%    \verb|new_user_whatsit_id| registers a new id (an integer)
%    and returns it. It is up to the user what he actually does
%    with the return value.
%
%    Registering user whatsits without a name, though supported, is
%    not exactly good style. In these cases we generate a name
%    from a counter.
%
%    In addition to the user whatsit name, it is possible and even
%    encouraged to specify the name of the package that will be
%    using the user whatsit as the second argument.
%
%    \begin{macrocode}
--- string -> string -> int
local new_user_whatsit_id = function (name, package)
    if name then
        if not package then
            package = unassociated
        end
    else -- anonymous
        anonymous_whatsits = anonymous_whatsits + 1
        warning("defining anonymous user whatsit no. %d", anonymous_whatsits)
        warning("dear package authors, please name your whatsits!")
        package = unassociated
        name    = anonymous_prefix .. tostring(anonymous_whatsits)
    end

    local whatsitdata = user_whatsits[package]
    if not whatsitdata then
        whatsitdata             = { }
        user_whatsits[package]  = whatsitdata
    end

    local id = whatsitdata[name]
    if id then --- warning
        warning("replacing whatsit %s:%s (%d)", package, name, id)
    else --- new id
        current_whatsit     = current_whatsit + 1
        if current_whatsit >= whatsit_cap then
            warning("maximum of %d integral user whatsit ids reached",
                whatsit_cap)
            warning("further whatsit allocation may be inconsistent")
        end
        id                  = current_whatsit
        whatsitdata[name]   = id
        whatsit_ids[id]     = { name, package }
    end
    log("new user-defined whatsit %d (%s:%s)", id, package, name)
    return id
end
luatexbase.new_user_whatsit_id = new_user_whatsit_id
%    \end{macrocode}
%
%    \verb|new_user_whatsit| first registers a new id and then also
%    creates the corresponding whatsit node of subtype “user-defined”.
%    We return a nullary function that delivers copies of the whatsit.
%
%    Alternatively, the first argument can be a whatsit node that
%    will then be used as prototype. Note that in this case a
%    \emph{copy} of the prototype will be stored in the closure,
%    eliminating side-effects.
%
%    \begin{macrocode}
--- (string | node_t) -> string -> ((unit -> node_t) * int)
local new_user_whatsit = function (req, package)
    local id, whatsit
    if type(req) == "string" then
        id              = new_user_whatsit_id(req, package)
        whatsit         = newnode(whatsit_t, user_defined_t)
        whatsit.user_id = id
    elseif req.id == whatsit_t and req.subtype == user_defined_t then
        id      = req.user_id
        whatsit = copynode(req)
        if not whatsit_ids[id] then
            warning("whatsit id %d unregistered; "
                    .. "inconsistencies may arise", id)
        end
    end
    return function () return copynode(whatsit) end, id
end
luatexbase.new_user_whatsit         = new_user_whatsit
%    \end{macrocode}
%
%    If one knows the name of a user whatsit, its corresponding id
%    can be retrieved by means of \verb|get_user_whatsit_id|.
%
%    \begin{macrocode}
--- string -> string -> int
local get_user_whatsit_id = function (name, package)
    if not package then
        package = unassociated
    end
    return user_whatsits[package][name]
end
luatexbase.get_user_whatsit_id = get_user_whatsit_id
%    \end{macrocode}
%
%    The inverse lookup is also possible via \verb|get_user_whatsit_name|.
%    Here it finally becomes obvious why it is beneficial to supply a package
%    name -- it adds information about who created and might be relying on the
%    user whatsit in question. First return value is the user whatsit name, the
%    second the package identifier it was registered with.
%
%    We issue a warning and return empty strings in case the argument
%    doesn't correspond to a registered user whatsit id.
%
%    \begin{macrocode}
--- int | fun | node -> (string, string)
local get_user_whatsit_name = function (asked)
    local id
    if type(asked) == "number" then
        id = asked
    elseif type(asked) == "function" then
        --- node generator
        local n = asked()
        id = n.user_id
    else --- node
        id = asked.user_id
    end
    local metadata = whatsit_ids[id]
    if not metadata then -- unknown
        warning("whatsit id %d unregistered; inconsistencies may arise", id)
        return "", ""
    end
    return tableunpack(metadata)
end
luatexbase.get_user_whatsit_name = get_user_whatsit_name
%    \end{macrocode}
%
%    For the curious as well as the cautious who are interesting in
%    what they are dealing with, we add a function that outputs the
%    current allocation status to the terminal.
%
%    \begin{macrocode}
--- string -> unit
local dump_registered_whatsits = function (asked_package)
    local whatsit_list = { }
    if asked_package then
        local whatsitdata = user_whatsits[asked_package]
        if not whatsitdata then
            error("(no user whatsits registered for package %s)",
                  asked_package)
            return
        end
        texiowrite_nl("(user whatsit allocation stats for " .. asked_package)
        for name, id in next, whatsitdata do
            whatsit_list[#whatsit_list+1] =
                stringformat("(%s:%s %d)", asked_package, name, id)
        end
    else
        texiowrite_nl("(user whatsit allocation stats")
        texiowrite_nl(stringformat(" ((total %d)\n  (anonymous %d))",
            current_whatsit, anonymous_whatsits))
        for package, whatsitdata in next, user_whatsits do
            for name, id in next, whatsitdata do
                whatsit_list[#whatsit_list+1] =
                    stringformat("(%s:%s %d)", package, name, id)
            end
        end
    end

    texiowrite_nl" ("
    --- in an attempt to be clever the texio.write* functions
    --- mess up line breaking, so concatenation is unusable ...
    local first = true
    for i=1, #whatsit_list do
        if first then
            first = false
        else -- indent
            texiowrite_nl"  "
        end
        texiowrite(whatsit_list[i])
    end
    texiowrite"))\n"
end
luatexbase.dump_registered_whatsits = dump_registered_whatsits
%    \end{macrocode}
%    Lastly, we define a couple synonyms for convenience.
%    \begin{macrocode}
luatexbase.newattribute            = new_attribute
luatexbase.newuserwhatsit          = new_user_whatsit
luatexbase.newuserwhatsitid        = new_user_whatsit_id
luatexbase.getuserwhatsitid        = get_user_whatsit_id
luatexbase.getuserwhatsitname      = get_user_whatsit_name
luatexbase.dumpregisteredwhatsits  = dump_registered_whatsits
%    \end{macrocode}
%
%    \begin{macrocode}
%</luamodule>
%    \end{macrocode}
%
%    \section{Test files}
%
%    The tests done are very basic: we just make sure that the package loads
%    correctly and the macros don't generate any error, under both \latex and
%    Plain \tex. We also check that the attribute's number is remembered well,
%    independently of the current value of |\escapechar|.
%
%    \begin{macrocode}
%<testplain>\input luatexbase-attr.sty
%<testlatex>\RequirePackage{luatexbase-attr}
%<*testplain,testlatex>
\newluatexattribute\testattr
\setluatexattribute\testattr{1}
\ifnum\testattr=1 \else \ERROR \fi
\unsetluatexattribute\testattr
\ifnum\testattr<0 \else \ERROR \fi
\catcode64 11
\luatexbase@directlua{assert(luatexbase.attributes.testattr)}
\luatexbase@directlua{luatexbase.new_attribute('luatestattr')}
\luatexbase@directlua{assert(luatexbase.attributes.luatestattr)}
\begingroup
\escapechar64
\newluatexattribute\anotherattr
\endgroup
\setluatexattribute\anotherattr{1}
\luatexbase@directlua{assert(luatexbase.attributes.anotherattr)}
%</testplain,testlatex>
%<testplain>\bye
%<testlatex>\stop
%    \end{macrocode}
%
%
% \Finale
\endinput
