# How to make a macro which displays different text the last time it appears within a paragraph in ConTeXt?

I have a simple macro which displays the letter “A” in the document:

\define\displayletter{A}

This is some text. \displayletter
This is some more text. \displayletter This is other text.
This is also some text. \displayletter


When compiled, this will print:

This is some text. A This is some more text. A This is other text. This is also some text. A


In some paragraphs, I need to have the last occurrence of \displayletter to instead display the letter “Z”.

There might be various solutions to this, but I think that a macro, such as this, placed around each paragraph can control whether or not the last letter appears as an “A” or “Z”:

\displayparagraph{0}{
This is some text. \displayletter
This is some more text. \displayletter This is other text.
This is also some text. \displayletter
}


If #1 is equal to 0, then, every \displayletter in enclosed paragraph should be displayed as an “A”. This displays:

This is some text. A This is some more text. A This is other text. This is also some text. A


If #1 is greater than 0, then the last occurrence of \displayletter within the enclosed paragraph should be displayed as a “Z”, but all other previous occurrences of \displayletter within the paragraph remain as an “A”. This displays:

This is some text. A This is some more text. A This is other text. This is also some text. Z


How can I make the last occurrence of a macro within another macro display a different letter?

-

Below is my approach to this but it modifies the user interface a bit so that it looks more Context-ish. (You can skip the implementation and start reading the code with the section “user interface”.)

EDIT: The code has been updated to reflect new functionality added with version 2012-09-21 of ConTeXt. No hacks necessary anymore ;-) Also, the rule final is now a synonym for last, following a suggestion by Aditya. If you are stuck with an older MkIV, the earlier version has been archived as a gist.

There are two things going on.

1. Paragraphs are counted and occurrences of the macro are stored.
2. On macro invocation, a set of conditions is evaluated to determine the output.

You will need to define a condition set and hook it into a macro. The set is defined via \definesubstitutionset. It takes a key-value list that maps conditions to output:

\definesubstitutionset [mysub] [1=a,2=b,default=x,final=y]


The line above means that in a given paragraph, the first time the letter a will be printed, the second time b, the last time y and for all other occurrences x.

Now this substitution set must be assigned to a macro. Create a new one using \definesubstitute and use it as the parameter substitutionset:

\definesubstitute [displayfoo] [substitutionset=mysub]


(Also, try the style and color keys!) Now you can use the macro \displayfoo and the content it actually typesets will depend on the conditions specified.

% macros=mkvi
\unprotect

%% 1. Declare a counter and increment it with every paragraph start.
\newcount\parcount \parcount0

\startluacode
local context       = context
local jobdatasets   = job.datasets
local tablefastcopy = table.fastcopy
-- 2. Retrieve the state of the previous run. NEW: Datasets make this
--    go smooth from the Lua end since version 2012-09-21.
local paragraph_info = jobdatasets.getdata(
"substitute",
"paragraph_info")

local occurrences, substitution_sets = { }, { }
documentdata.occurrences = occurrences

-- 3. (Ab)use the table to emulate a switch statement.
local do_display_letter = function (n, old_n, branches)
local s
if not old_n then -- paragraph count changed
s = branches.default
elseif n == old_n then
s = branches.last
else
s = branches[n] or branches.default
end
context(s)
end

-- 4. Bookkeeping: store away the number of times the macro is called
--    per paragraph. Then, continue with the response function.
local substitute = function (sub_id, set_id, parcount)
local previous = paragraph_info and paragraph_info[sub_id]
local current  = occurrences[sub_id]
current[parcount] = current[parcount] or 0
current[parcount] = current[parcount]  + 1
do_display_letter(
current[parcount],
previous and previous[parcount],
substitution_sets[set_id])
end

-- 5. Define a function that stores the counters of the current pass
--    in the .tuc file. The setter requires both a namespace
--    (“substitute”) and a tag (“paragraph_info”), so as to emulate
--    the usage of \type{\definedataset}.
local storeinfo = function ( )
jobdatasets.setdata {
name = "substitute",
tag  = "paragraph_info",
data = occurrences,
}
end

-- 6. Code for key-value parser in Lua. Makes it easy to define
--    substitutions using setups.
local lpeg = require "lpeg"

local C, Cc, Cf, Cg, Ct = lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct

local P, S, V, lpegmatch = lpeg.P, lpeg.S, lpeg.V, lpeg.match

local p_args = P{
"args",
args           = Cf(Ct"" * (V"kv_pair" + V"emptyline")^0, rawset),
kv_pair        = Cg(V"key"
* V"separator"
* (V"value" * V"final"
+ V"empty"))
* V"rest_of_line"^-1
,
key            = V"whitespace"^0 * C(V"key_char"^1),
key_char       = (1 - V"whitespace" - V"eol" - V"equals")^1,
separator      = V"whitespace"^0 * V"equals" * V"whitespace"^0,
empty          = V"whitespace"^0 * V"comma" * V"rest_of_line"^-1
* Cc(false)
,
value          = C((V"balanced" + (1 - V"final"))^1),
final          = V"whitespace"^0 * V"comma" + V"rest_of_string",
rest_of_string = V"whitespace"^0
* V"eol_comment"^-1
* V"eol"^0
* V"eof"
,
rest_of_line   = V"whitespace"^0 * V"eol_comment"^-1 * V"eol",
eol_comment    = V"comment_string" * (1 - (V"eol" + V"eof"))^0,
comment_string = V"lua_comment" + V"TeX_comment",
TeX_comment    = V"percent",
lua_comment    = V"double_dash",
emptyline      = V"rest_of_line",

balanced       = V"balanced_brk" + V"balanced_brc",
balanced_brk   = V"lbrk"
* (V"balanced" + (1 - V"rbrk"))^0
* V"rbrk"
,
balanced_brc   = V"lbrc"
* (V"balanced" + (1 - V"rbrc"))^0
* V"rbrc"
,
-- Terminals
eol            = P"\n\r" + P"\r\n" + P"\n" + P"\r",
eof            = -P(1),
whitespace     = S" \t\v",
equals         = P"=",
dot            = P".",
comma          = P",",
dash           = P"-",    double_dash  = V"dash" * V"dash",
percent        = P"%",
lbrk           = P"[",    rbrk         = P"]",
lbrc           = P"{",    rbrc         = P"}",
}

local defaults = { -- EDIT: defaults are a tad more helpful
last    = [[{\bold RULE “last” MISSING}]],
default = [[{\bold RULE “default” MISSING}]],
}

-- 7. The substitution sets are stored in a Lua by their id. If it’s
--    an integer, it will be stored on the array part. The special
--    conditions “default” and “last” are stored on the hash.
local define_substitutionset = function (set_id, parent, raw)
local current = parent and tablefastcopy(substitution_sets[parent])
or { }
local args = lpegmatch(p_args, raw)
--- sanitize arguments to have numbers on the array part
for k, v in next, args do
k = tonumber(k) or k
current[k] = v
end
--- make “.final” a synonym for “.last”
current.last = current.last or current.final
--- substitute defaults for missing mandatory rules
for k, v in next, defaults do current[k] = current[k] or v end
substitution_sets[set_id] = current
end

commands.conditional_substitution = substitute
commands.store_substitution_info  = storeinfo
commands.define_substitutionset   = define_substitutionset
\stopluacode

%% 8. Substitutions are assigned using \type{\definesubstitutionset},
%%    which supports inheritance.
\def\definesubstitutionset{%
\dotripleempty\define_substitutionset_indeed%
}

\def\define_substitutionset_indeed[#id][#second][#third]{%
\ifthirdargument% inherit
\ctxcommand{define_substitutionset(
\!!bs#id\!!es,
\!!bs#second\!!es,
\!!bs\detokenize{#third}\!!es)}%
\else% new definition
\ctxcommand{define_substitutionset(
\!!bs#id\!!es,
false,
\!!bs\detokenize{#second}\!!es)}%
\fi%
}

%% 9. For the actual macro generator we rely on the generic namespace
%%    functionality.

\installnamespace {substitute}
\installdefinehandler        \????substitute {substitute}
\????substitute
\installsetuphandler         \????substitute {substitute}
\installparameterhandler     \????substitute {substitute}
\installstyleandcolorhandler \????substitute {substitute}

\appendtoks
\ctxlua{documentdata.occurrences["\currentsubstitute"] = { }}
\setuevalue{\currentsubstitute}%
{\substitute_direct[\currentsubstitute]}%
\to \everydefinesubstitute

\unexpanded\def\substitute_direct[#id]{%
\edef\currentsubstitute{#id}%
\dosingleempty\substitute_direct_indeed%
}

\def\substitute_direct_indeed[#setups]{%
\bgroup%
\iffirstargument\setupcurrentsubstitute[#setups]\fi
\usesubstitutestyleandcolor\c!style\c!color%% I♥ConTeXt
\ctxcommand{conditional_substitution(
\!!bs\currentsubstitute\!!es,
\!!bs\substituteparameter{substitutionset}\!!es,
\the\parcount)}%
\egroup%
}

%% 10. Store the counters in the .tuc file on exit.
\appendtoks \ctxcommand{store_substitution_info()} \to \everybye

\protect

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%                          user interface                           %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Usage:
%%  First, define one or more substitution sets. These map occurrences
%%         to output. Integers as key are interpreted as “use this for
%%         the $n$-th occurrence. Two keys are special: “last” for the
%%         last occurrence in a paragraph. “default” for any other
%%         occurrence that is not explicitly defined.
%%         {\em EDIT}: “final” has been made a synonym for “last”.
%%
%%
%%  Second, define a substitution macro. These behave just as normal
%%          ConTeXt macro generators including inheritance. They also
%%          have corresponding \setup<id>. Just because it’s no effort
%%          to implement, the “substitute” macros also respect “style”
%%          and “color” parameters.

\definesubstitutionset [1] [default=A,last=A]
\definesubstitutionset [2] [1] [last=Z]

\definesubstitute [displayletter] [substitutionset=1,style=bold]

\definesubstitutionset [yetanotherset] [
1=Initial occurrence.,
3=Kilroy was here.,
last=Last occurrence.,
default=Ordinary occurrence.,
]

\definesubstitute [displaywhatever] [
substitutionset=yetanotherset,
style=smallcaps,
]

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%                              testing                              %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\starttext

foo \displayletter\
bar \displayletter\
baz \displayletter\

foo \displayletter[substitutionset=2]
bar \displayletter[substitutionset=2]
baz \displayletter[substitutionset=2]

foo \displaywhatever\
bar \displaywhatever\
baz \displaywhatever\

\stoptext \endinput

-
It is better to use last as a key rather than final because last is already used as a key by other commands (and therefore you get automatic multilingual interface if you use \c!last, etc. –  Aditya Sep 19 '12 at 6:01
+1 for putting so much effort into this and for providing an excellent answer one can learn a lot from. –  Marco Sep 19 '12 at 11:21

This would be my approach to solve the problem. I was inspired by the comprehensive answer of @phg to try it in a more direct way. Because I don't know much about ConTeXt I give a LuaLatex example. But the main idea is implemented in a Lua function so I think it doesn't matter.

\documentclass{article}
\usepackage{filecontents}

\begin{filecontents*}{luaFunctions.lua}

function ReplaceLetter(text)
counter = 0

-- count the occurrences of '[displayletter]'
_, count = string.gsub(text, "%[displayletter%]", "")

-- define the replacements
replacements={[1]="\\textbf{A}",[2]="\\textbf{B}",[count]="\\textbf{Z}",["default"]="\\textbf{D}"}

-- replace all '[displayletter]'
out = string.gsub(text, "%[displayletter%]",
function(word)
counter = counter + 1
return replacements[counter] or replacements["default"]
end)

tex.print(out)
end

\end{filecontents*}

\directlua{dofile("luaFunctions.lua")}
\def\displayparagraph#1{\directlua{ReplaceLetter("#1")}}

\begin{document}
\displayparagraph{%
Lorem ipsum dolor sit amet, [displayletter] consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim [displayletter] ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis [displayletter] aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. [displayletter] Excepteur
sint occaecat cupidatat [displayletter] non proident, sunt in culpa qui
officia deserunt [displayletter] mollit anim id est laborum.}
\end{document}


-