LaTeX: to define a macro that needs only one expansion to get the result
%% | |
%% This is file 'ixquickmacro.sty'. | |
%% | |
%% Copyright (c) 2019 Takayuki YATO (aka. "ZR") | |
%% GitHub: https://github.com/zr-tex8r | |
%% Twitter: @zr_tex8r | |
%% | |
%% This package is distributed under the MIT License. | |
%% | |
%% package declaration | |
\NeedsTeXFormat{LaTeX2e} | |
\ProvidesPackage{ixquickmacro}[2019/12/31 v0.2] | |
\def\ixzqm@pkgname{ixquickmacro} | |
%--------------------------------------- environment check | |
% engine | |
\RequirePackage{iftex} | |
\RequireLuaTeX | |
\unless\ifprimitive\luadef | |
\PackageError\ixzqm@pkgname | |
{Your LuaTeX does not support \string\luadef}% | |
{Package loading is aborted.}% | |
\expandafter\endinput\fi\relax | |
%--------------------------------------- general | |
%% packages | |
\RequirePackage{etoolbox}[] | |
%% unique tokens | |
\def\ixzqm@uniq{\@gobble\ixzqm@uniq@}% vanishing | |
%% constants | |
\chardef\ixzqm@iconst=42 | |
%% variables | |
\newif\ifixzqm@ok | |
\newluafunction\ixzqm@lua@setglobal | |
\newluafunction\ixzqm@lua@prepare | |
\newluafunction\ixzqm@lua@declare | |
\let\ixzqm@probe\relax | |
\let\ixzqm@body\relax | |
\let\ixzqm@lua@target\relax | |
%--------------------------------------- Lua part | |
\begingroup | |
\escapechar\m@ne | |
\catcode10=12 \endlinechar=10 % | |
\directlua{ | |
local M = {}; xx = M | |
local functable = lua.get_functions_table() | |
local rnumber = luatexbase.registernumber | |
local create, put_next = token.create, token.put_next | |
local insert, unpack = table.insert, table.unpack | |
functable[rnumber('ixzqm@lua@setglobal')] = function() | |
M.global = (token.scan_int() > 0) | |
end | |
functable[rnumber('ixzqm@lua@prepare')] = function() | |
-- save prefix info | |
-- TODO: what to do with 'global'? | |
local probe = token.create("ixzqm@probe") | |
M.protected = probe.protected | |
-- check if the target token (CS) is definable | |
M.targetcs = token.get_next() | |
put_next(create('let'), M.targetcs, create('ixzqm@uniq'), | |
create('ifx'), M.targetcs, create('ixzqm@uniq'), | |
create('ixzqm@next'), M.targetcs, create('fi'), | |
create('ixzqm@fail')) | |
end | |
functable[rnumber("ixzqm@lua@declare")] = function() | |
local id = rnumber('ixzqm@lua@target') | |
local drive = create('ixzqm@drive') | |
local delegate = create('ixzqm@D/'..tostring(id)) | |
functable[id] = function() | |
put_next(drive, delegate) | |
token.scan_int() -- trigger full expansion | |
end | |
-- write out luadef statement | |
local t = {create('luadef'), M.targetcs, create('ixzqm@lua@target')} | |
if M.global then insert(t, 1, create('global')) end | |
if M.protected then insert(t, 1, create('protected')) end | |
put_next(unpack(t)) | |
end | |
}\endgroup% | |
%--------------------------------------- TeX part | |
%%<*> [\protected]\defquickmacro\CS{<body>} | |
\newrobustcmd*{\defquickmacro}{% | |
\luafunction\ixzqm@lua@setglobal\z@ | |
\ixzqm@def@quick@macro@a | |
} | |
\newrobustcmd*{\gdefquickmacro}{% | |
\luafunction\ixzqm@lua@setglobal\@ne | |
\ixzqm@def@quick@macro@a | |
} | |
\def\ixzqm@def@quick@macro@a#1#{% | |
% once pass through TeX processor | |
\ixzqm@def@quick@macro@b#1% | |
} | |
\def\ixzqm@def@quick@macro@b{% | |
\def\ixzqm@probe{}% dummy def for picking up macro prefixes | |
\let\ixzqm@next\ixzqm@def@quick@macro@c | |
\luafunction\ixzqm@lua@prepare | |
} | |
% invoked as: \ixzqm@def@quick@macro@c \CS \fi \ixzqm@fail | |
\@gobbletwo\if\if \def\ixzqm@def@quick@macro@c#1\fi#2{\fi | |
% pick up the macro body | |
\afterassignment\ixzqm@def@quick@macro@d | |
\def\ixzqm@body | |
} | |
\def\ixzqm@def@quick@macro@d{% | |
\newluafunction\ixzqm@lua@target | |
\global\cslet{ixzqm@D/\number\ixzqm@lua@target}\ixzqm@body | |
\luafunction\ixzqm@lua@declare | |
} | |
%% \ixzqm@fail | |
% Gobbles the macro body. | |
\def\ixzqm@fail{% | |
\afterassignment\ixzqm@fail@a | |
\gdef\ixzqm@probe | |
} | |
\def\ixzqm@fail@a{% | |
\global\let\ixzqm@probe\@empty | |
} | |
%% \ixzqm@drive\CS | |
% A execution of scan_int triggers the full expansion. | |
\def\ixzqm@drive#1{% | |
\expandafter\ixzqm@iconst\expanded{#1}% | |
} | |
%--------------------------------------- done | |
\endinput | |
%% EOF |
\documentclass{article} | |
\usepackage{ixquickmacro} | |
\makeatletter %!!!!!!!!!!!!!!!!!!!!!!!!! TeX code BEGIN | |
% to show the expanded forms | |
\def\xInspect#1{\typeout{% | |
once->\unexpanded\expandafter{#1}^^J% | |
twice->\unexpanded\expandafter\expandafter\expandafter{#1}^^J% | |
full->#1}} | |
%% \todayYMD *once-expands* to the desired token list. | |
\defquickmacro\todayYMD{% | |
% this code is "only fully-ecpandable" | |
\the\year/\two@digits\month/\two@digits\day} | |
%% \todayYMDp is truly unexpandable. | |
\protected\defquickmacro\todayYMDp{% | |
\the\year/\two@digits\month/\two@digits\day} | |
\makeatother %!!!!!!!!!!!!!!!!!!!!!!!!! TeX code END | |
\begin{document} | |
\xInspect{\todayYMD} | |
% once->2019/12/31 | |
% twice->2019/12/31 | |
% full->2019/12/31 | |
\stop |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
Usage
\defquickmacro\CS{<body>}
: Defines locally\CS
to be a token that once-expands to the “full expansion of<body>
”.\gdefquickmacro\CS{<body>}
: Ditto except that the definition is global.