Create a gist now

Instantly share code, notes, and snippets.

% macros=mkvi
\unprotect
%% 1. Declare a counter and increment it with every paragraph start.
\newcount\parcount \parcount0
\appendtoks \advance\parcount1 \to \everypar
\startluacode
local context = context
local tablefastcopy = table.fastcopy
-- 2. Retrieve the state of the previous run.
local paragraph_info = job.variables.collected.paragraph_info
local occurrences = { "bar" } -- hack: expects something on the array part for saving‽
local 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.final
else
s = branches[n] or branches.default or "DEFAULT VALUE MISSING!"
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.
local storeinfo = function ( )
job.variables.tobesaved.paragraph_info = 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 = { final = "", default = "" }
-- 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 “final” 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)
for k, v in next, args do
k = tonumber(k) or k
current[k] = v
end
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: “final” for the
%% last occurrence in a paragraph. “default” for any other
%% occurrence that is not explicitly defined.
%%
%% 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,final=A]
\definesubstitutionset [2] [1] [final=Z]
\definesubstitute [displayletter] [substitutionset=1,style=bold]
\definesubstitutionset [yetanotherset] [
1=Initial occurrence.,
3=Kilroy was here.,
final=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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment