Created
September 22, 2012 10:48
-
-
Save phi-gamma/3765816 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
% 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 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 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment