% \iffalse meta-comment % % Copyright 2009, 2010 by Élie Roux % Copyright 2010, 2011 by Manuel Pégourié-Gonnard % % 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 % %<*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 % %<*ignore> \fi % %<*driver> \documentclass{ltxdoc} \input lltxb-dtxstyle \begin{document} \DocInput{luatexbase-attr.dtx}% \end{document} % % \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.4 2011-05-24} % % \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}[2011/05/24 v0.4 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} % % Load \pk{luatexbase-compat}. % % \begin{macrocode} \begingroup\expandafter\expandafter\expandafter\endgroup \expandafter\ifx\csname RequirePackage\endcsname\relax \input luatexbase-compat.sty \else \RequirePackage{luatexbase-compat} \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} % % First load \pk{luatexbase-loader} (hence \pk{luatexbase-compat}), then % the supporting Lua module. We make sure \verb|luatex.sty| is loaded. % % \begin{macrocode} \begingroup\expandafter\expandafter\expandafter\endgroup \expandafter\ifx\csname RequirePackage\endcsname\relax \input luatexbase-loader.sty \input luatex.sty \else \RequirePackage{luatexbase-loader} \RequirePackage{luatex} \fi \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))}% \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} % \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% % % \end{macrocode} % % \subsection{Lua module} % % \begin{macrocode} %<*luamodule> --- locals local nodenew = 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 % \end{macrocode} % % We improvise a basic logging facility. % % \begin{macrocode} local reporter = function (log, category, ...) if log == true then texiowrite_nl("log", "("..category..") ") texiowrite("log", stringformat(...)) else texiowrite_nl("("..category..") ") texiowrite(stringformat(...)) end end local warning = function (...) reporter (false, "warning", ...) end ----- info = function (...) reporter (false, "info", ...) end local log = function (...) reporter (true, "log", ...) end % \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 \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 unset_attribute(name) 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. % % \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 % \end{macrocode} % % User whatsit allocation (experimental). % % \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} % % The 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 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 whatsit name, it is possible and even % encouraged to specify the name of the package that will be % using the 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 of subtype “user defined”. % Return values are said node and its id. % % \begin{macrocode} --- string -> string -> (node_t -> int) local new_user_whatsit = function (name, package) local id = new_user_whatsit_id(name, package) local wi = nodenew(whatsit_t, user_defined_t) wi.user_id = id return wi, id end luatexbase.new_user_whatsit = new_user_whatsit % \end{macrocode} % % If one knows the name of a 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 % whatsit in question. First return value is the whatsit name, the second % the package identifier it was registered with. % % We issue a warning and return empty strings in case the asked whatsit is % unregistered. % % \begin{macrocode} --- int | node -> (string, string) local get_user_whatsit_name = function (asked) local id if type(asked) == "number" then id = asked 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" (" texiowrite(table.concat(whatsit_list, "\n ")) texiowrite"))" 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} % % \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} %\input luatexbase-attr.sty %\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)} % %\bye %\stop % \end{macrocode} % % % \Finale \endinput