TEX (INITEX), 372 bytes
\catcode`~13\let~\catcode~`{1~`}2~`#6~`\ 12~`|13\let|\expandafter\def\z{}\def\f#1{#1}\def\i#1{}\def\b#1#2{\ifx#2\e|||\i|\i\else|\f\fi{\ifcat#1a|\f\else|||\b|||#2|\i\fi}{\ifcat#2a #1#2|\b|#2\else|\b|#1\fi}}\def\t{~`011~`111~`211~`311~`411~`511~`611~`711~`811~`911~`\|12~`\~12~`\#12~`\{12~`\}12~127=12~`\%12~`\\12\read16to\x\edef\r{|\b\x\e}\immediate\write16{|\i\r\z}\end}\t
For example, the program being saved in a file named "codegolf.tex", provided with input ??a_%,1!=\z#@
, initex codegolf.tex
outputs:
This is TeX, Version 3.14159265 (TeX Live 2019/dev/Debian) (INITEX)
(./codegolf.tex
\x=??a_%,1!=\z#@
a1 1z
)
No pages of output.
Transcript written on codegolf.log.
In the case of a degraded input, for example the strings ??
, ?a
, or a?
, this implementation does not print any pair.
Explanation
Basis for a TEX program
\read16
Per this Meta answer, the input of a program written in TEX should come from standard input, which is the in register number 16.
\immediate\write16
The TEX Codegolf community prefers to output to DVI or PDF, because it is simpler than writing to the terminal. However, this requires a valid \output
routine, which either requires a format (with more category codes to alter1) or is longer to define. So I write to the terminal. (\message
could also have been used, but I don't like how the output is surrounded by TEX chat on the same line).
Because I output to the terminal, I also feel that no DVI file should get produced, so \immediate
is required2.
\end
This is a complete program, so it must end with \end
to run cleanly.
Category codes
Running on alphanumerics and ignoring punctuation is a trivial matter for TEX. All I have to do is to customize the category codes of those characters.
For this subject, I will use category code 11 ("letter") for characters that need to be blurred, and category code 12 ("other") for characters that are to be dropped. That choice comes from the defaults of (ini)TEX, where letters are already set to 11 and most of the other characters to 12.
\catcode`~13\let~\catcode
To save bytes, I make ~ an active character and set its meaning to \catcode
. In the rest of the explanation, that alias will be ungolfed.
\catcode`{=1 \catcode`}=2 \catcode`#=6 \catcode`\ =12
These are required for INITEX to interpret my program correctly: { is now the begin-group character, } the end-group, # the macro argument, and ␣ is considered a normal character (so that the first spurious space of output can be removed). These choices are widely used in the TEX world and do not cost bytes3.
\catcode`|=13 \let|=\expandafter
This is an useful alias for later.
\def\t{\catcode`0=11 ... \end}
\t
This is a TEX design pattern that fools TEX's eyes. Everything that is written in the \todo
macro definition will get a category code assigned, and this category code will not be reconsidered when TeX expands the macro. Most notably:
\catcode`\\=12
This makes the escape character \ a normal character when read from the user input.
\read16to\x \edef\r{|\b\x\e} \immediate\write16{|\i\r\z} \end
When reading the macro definition, TEX has decided that these were control words, and TEX will not reconsider this choice after the macro has been expanded, even if \ has lost its special meaning in the meantime.
\catcode127=12
The DEL character is contained in ASCII, so it should be valid input per the subject's rules. But in order to change its category code to 12, the DEL character has to be written, and the most efficient4 way to input it in TEX is to name it by its code point.
\catcode`0=11 ... \catcode`\\=12
Here are three groups of category code assignments: make the digits like letters, revert the changes made at the beginning of the program, and cancel other INITEX defaults (DEL, %, \, but not the new line because the subject emits the hypothesis:)
Your input will be a single-line string
The blurring command
Because I opted for terminal output, and because \write
does not executes its argument (only expands it), I am forced to write the \blur
macro as fully expandable.
\def\z{} \def\firstofone#1{#1} \def\ignore#1{}
As a prolog, I define three macros, which are commonly used in various fully-expandable design patterns. The first one is the empty macro, the second one is the identity macro, and the third one is the zero macro.
\def\blur#1#2{ ... }
The \blur
macro is recursive5. It takes two characters at a time (because the rules say that the input is longer than two). Its definition is made of three parts.
\ifx#2\e \expandafter\expandafter \expandafter\ignore \expandafter\ignore
\else \expandafter\firstofone
\fi
is the recursion condition: if the end-of-string marker (the undefined command \e
6) is found, ignore the two other parts. (More precisely, drop the unused portion of the conditional \else...\fi
at \expandafter
level 0, then ignore the second part at \expandafter
level 1, then ignore the third part at \expandafter
level 2.) Otherwise, close the conditional and interpret the second part.
\ifcat#1a \expandafter\firstofone
\else \expandafter\expandafter \expandafter\blur
\expandafter\expandafter \expandafter#2
\expandafter \ignore
\fi
handles the first character. If it is a letter or digit, go analysing a second character. Otherwise, skip the remaining of the conditional, ignore the third part, and call \blur
recursively with #1 being dropped and #2 as the first character7 (an other one will get extracted from input).
\ifcat#2a ␣#1#2 \expandafter\blur \expandafter#2
\else \expandafter\blur \expandafter#1
\fi
handles the second character. If it is a letter or digit, output a space then the first part of the blur, then forget about #1 and call \blur
recursively on #27. Otherwise, forget about it and call \blur
recursively on #17.
Calling the blurring command on real input
\read16 to \x
will store the input into an (expandable) macro \x
.
\expandafter\blur \x \e
This is how the \blur
macro is called. Because \blur
acts on tokens, the input contained in \x
must be expanded first. The end-of-string marker follows.
\edef\result{\expandafter\blur \x \e}
The result of the expansion, including the whitespace at front, is stored into the \result
macro.
\expandafter\ignore \result \z
Inside the \write16
command, the \result
macro is first expanded. Then, \ignore
eliminates the first token of output: the first space if the output is non-empty, or the \z
macro call if the output is empty. In the first case, the \z
macro will survive; but \write16
will finally expand it, and its expansion is a no-op. This is the most efficient way of handling the “no trailing space” requirement, even if it requires 24 bytes (the definition and call of \z
and \return
); suppressing a space at the end of the output would have been nearly impossible.
☡ Footnotes
$, &, ^, _, NUL, SPC, TAB, ~ for plain TEX; 5 bytes each = 40 bytes.
Without \immediate
, \write
adds its content to a whatsit box, thus generating a page of output.
Because there are only three category code changes, it is not worth putting them in a group to save the reverting.
Decimal: 127=
(4); Hexadecimal: "7F=
(4, harder to read); Octal: '199=
(5); Escape sequence: ``\^^?
(5, plus \catcode`^7
and revert)
It is even tail-recursive, which in TEX has the meaning of "it does not add tokens after the recursive call", and by doing so is more memory-friendly.
Because all input other than my program does not get interpreted, the \e
command cannot be defined later.
This would fail if #2 was a simple group of arguments, but fortunately the input cannot contain simple groups, because no character has category code 1 nor 2.