From 99f6ebc940e504927ea38d6600f8ca9f893667bb Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Mon, 13 Aug 2012 22:40:06 +0000 Subject: new (semi-old) latex package xpeek (13aug12) git-svn-id: svn://tug.org/texlive/trunk@27393 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf-dist/source/latex/xpeek/xpeek.dtx | 582 +++++++++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 Master/texmf-dist/source/latex/xpeek/xpeek.dtx (limited to 'Master/texmf-dist/source/latex/xpeek') diff --git a/Master/texmf-dist/source/latex/xpeek/xpeek.dtx b/Master/texmf-dist/source/latex/xpeek/xpeek.dtx new file mode 100644 index 00000000000..5eca346e2bc --- /dev/null +++ b/Master/texmf-dist/source/latex/xpeek/xpeek.dtx @@ -0,0 +1,582 @@ +% \iffalse meta-comment +% !TeX program = pdflatex +% !TeX encoding = utf-8 +% !TeX spellcheck = en_US +%<*internal> +\iffalse +% +%<*internal> +\fi +\def\nameofplainTeX{plain} +\ifx\fmtname\nameofplainTeX\else + \expandafter\begingroup +\fi +% +%<*install> +\input l3docstrip.tex +\keepsilent +\askforoverwritefalse +\preamble +---------------------------------------------------------------- + +xpeek: Define commands that peek ahead in the input stream + +Copyright © 2012 by Joel C. Salomon. + +This work consists of the file xpeek.dtx, + and the derived files xpeek.ins, + xpeek.pdf, & + xpeek.sty. +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is at ‹http://www.latex-project.org/lppl.txt›. + +This work is “maintained” (per LPPL maintenance status) by +Joel C. Salomon ‹joelcsalomon@gmail.com›. + +---------------------------------------------------------------- +\endpreamble +\postamble +\endpostamble +\usedir{tex/latex/xpeek} +\generate{ + \file{\jobname.sty}{\from{\jobname.dtx}{package}} +} +% +%\endbatchfile +%<*internal> +\usedir{source/latex/xpeek} +\generate{ + \file{\jobname.ins}{\from{\jobname.dtx}{install}} +} +\ifx\fmtname\nameofplainTeX + \expandafter\endbatchfile +\else + \expandafter\endgroup +\fi +% +%<*driver> +\pdfobjcompresslevel=0 +\RequirePackage[utf8]{inputenc} +\documentclass[full]{l3doc} +\usepackage{xpeek} +\usepackage{xspace} +\usepackage[all]{foreign} + +^^A Set up formatting options + +^^A Format URLs surrounded by guillemets +\DeclareUrlCommand\url + {\def\UrlLeft##1\UrlRight{\text{‹}\href{##1}{##1}\text{›}}} +\DeclareUrlCommand\email + {\def\UrlLeft##1\UrlRight{\text{‹}\href{mailto:##1}{##1}\text{›}}} +\urlstyle{same} + +^^A Make Unicode curly quotes (“”) behave correctly +\AtBeginDocument{ + \sfcode\csname\encodingdefault\string\textquotedblright\endcsname=0 +} +\xspaceaddexceptions{”} + +^^A Enable line continuations in `syntax` environments +\NewDocumentCommand \fixsyntaxlines {} + {\def\^^M{\unskip\space\ignorespaces}} + +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \fi +% +% \changes{Version 0.1}{2012/08/09}{First useful version} +% +% \GetFileInfo{\jobname.sty} +% +% \DoNotIndex +% { +% \ProvidesExplPackage, \RequirePackage, \NewDocumentCommand, +% \bool_new:N, \bool_set_true:N, \bool_set_false:N, \bool_if:NTF, +% \cs_new_protected:Npn, +% \peek_after:Nw, +% \prg_new_protected_conditional:Npnn, +% \prg_return_true:, \prg_return_false:, +% \tl_new:N, \tl_clear:N, \tl_set:Nn, +% \tl_put_right:Nn, +% \tl_map_inline:Nn, \tl_map_break:, +% \token_if_eq_charcode:NNT, +% } +% +% \NewDocumentCommand \releasenote {} +% {This file describes release \fileversion, last revised \filedate.} +% \title{The \pkg{\jobname} package\thanks\releasenote} +% +% \urldef{\urlxthinsp}{\url}{http://tex.stackexchange.com/a/59542/2966} +% \urldef{\urlxflw}{\url}{http://tex.stackexchange.com/q/63568/2966} +% \urldef{\urlxign}{\url}{http://tex.stackexchange.com/q/63971/2966} +% \urldef{\urlltxl} +% {\url}{http://thread.gmane.org/gmane.comp.tex.latex.latex3/2894} +% \NewDocumentCommand \acknote {} +% {The original code is due to Enrico “egreg” Gregorio, +% based on an answer he gave to a question of mine on +% \href{http://tex.stackexchange.com}{\TeX.SX}; +% see \urlxthinsp. +% Enrico, Joseph Wright, Bruno Le Floch, \& Clemens Niederberger +% helped iron out the implementation; +% Bruno also wrote the initial version of the documentation +% and the near-final version of much of the code. +% See \urlxflw, \urlxign, \& \urlltxl.} +% \author{Joel C. Salomon\\\email{joelcsalomon@gmail.com}\thanks\acknote} +% \date{Released \filedate} +% +% \maketitle +% +% \begin{abstract} +% The \pkg{\jobname} package provides functions +% for defining \& managing commands which, +% like the familiar \cs{xspace}, +% “peek” ahead at what follows them in the input stream. +% They compare this token against a stored list +% and choose an action depending on whether there was a match. +% \end{abstract} +% +% \tableofcontents +% +% \begin{documentation} +% +% \section{Introduction} +% +% This package provides functions for defining \cs{xspace}-like commands. +% Such commands “peek” ahead into the input stream +% to see what follows their invocations; +% their behavior varies depending on what it is that comes next. +% +% Probably the most common \LaTeX\ command of this sort is \cs{textit}. +% It looks ahead at the token following its argument +% and decides whether to insert italic correction afterward. +% Table~\ref{tab:itcor} compares the use of \cs{textit} +% against using the font-switch command \cs{itshape} +% with \& without explicit italic correction. +% \begin{table}\centering +% \begin{tabular}{lll} +% |\textit{half}hearted| & \textit{half}hearted & (correct)\\ +% |{\itshape half}hearted| & {\itshape half}hearted & (overlap)\\ +% |{\itshape half\/}hearted| & {\itshape half\/}hearted & (correct)\\ +% ^^A There’s a bug in the package `trace` which breaks suppression +% ^^A of italic correction; see latex3/svn-mirror#96 for details. +% ^^A As a temporary work-around, include `\nocorr` where needed. +% |by \textit{half}.| & by \textit{half\nocorr}. & (correct)\\ +% |by {\itshape half}.| & by {\itshape half}. & (correct)\\ +% |by {\itshape half\/}.| & by {\itshape half\/}. & (too wide)\\ +% \end{tabular} +% \caption{Automatic italic correction with \cs{textit}} +% \label{tab:itcor} +% \end{table} +% Notice how \cs{textit} seems to just “do the right thing” +% when it comes to inserting or omitting italic correction. +% +% It is, unfortunately, not hard to confuse \cs{textit}. +% As an example, consider the following code: +% \begin{quote} +% |\NewDocumentCommand \naif {} {\textit{na\"\i f}}|\\ +% …\\ +% |He was a true \naif.| +% \end{quote} +% This yields +% \begin{quote} +% \NewDocumentCommand \naif {} {\textit{na\"\i f\nocorr}} +% He was a true \naif. +% \end{quote} +% So far, so good. +% +% But using this macro in middle of a sentence +% may produce results the naïve user does not expect. +% For example, try +% \begin{quote} +% |He was such a \naif he expected this example to work.| +% \end{quote} +% yielding +% \begin{quote} +% \NewDocumentCommand \naif {} {\textit{na\"\i f}} +% He was such a \naif he expected this example to work. +% \end{quote} +% Oops. +% We forgot that command words eat the spaces that follow them. +% +% This is usually the point where \TeX perts explain +% about following command words with explicit spaces, +% \eg, writing “|such a \naif\ he |…”, +% or following such commands with an empty group, +% “|such a \naif{} he |…”, \eg. +% +% Others, more pragmatically-inclined, +% will mention the \pkg{xspace} package. +% This is the sort of use-case it was developed for, after all, +% so let’s try it: +% \begin{quote} +% |\NewDocumentCommand \naif {} {\textit{na\"\i f}\xspace}|\\ +% …\\ +% |He was such a \naif he expected this example to work.| +% \end{quote} +% This produces +% \begin{quote} +% \NewDocumentCommand \naif {} {\textit{na\"\i f}\xspace} +% He was such a \naif he expected this example to work. +% \end{quote} +% Even “such a \foreign{naïf}” can get the right result sometimes. +% +% But you see, there’s a kinda hitch.^^A +% \footnote{Cue Malcolm Reynolds: “Don’t complicate things.”} +% Recall the first time we used the macro: +% \begin{quote} +% |He was a true \naif.| +% \end{quote} +% Now it yields this: +% \begin{quote} +% \NewDocumentCommand \naif {} {\textit{na\"\i f}\xspace} +% He was a true \naif. +% \end{quote} +% Notice that ugly space between the \textit{f} and the period? +% That’s italic correction being added where it doesn’t belong. +% +% The trouble lies in how \LaTeX\ implements the \cs{textit} logic. +% The macro is clever enough to look ahead, +% seeking for short punctuation (\eg, the comma \& period) +% which should not be preceded by italic correction. +% What the macro finds first, though, +% is that invocation of \cs{xspace} we added. +% That’s not one of the “no italic correction” tokens, +% so the correction gets added and the sentence looks ugly. +% +% That’s where this package comes in. +% +% (As a side note, Phillip G. Ratcliffe’s \pkg{foreign} package, +% bundled with both MiK\TeX\ and \TeX~Live +% and available from CTAN at \url{http://ctan.org/pkg/foreign}, +% is an excellent tool for this sort of foreign-word macro +% and was one of the inspirations for this package.) +% +% Commands generated via the tools the \pkg{xpeek} package provides +% can peek ahead in the input stream, +% like \cs{textit} and \cs{xspace}, +% checking whether the next token is on their match-list. +% What \pkg{xpeek} adds is +% the ability to ignore certain tokens during that peek-ahead. +% These ignored tokens are not lost; +% the scan-ahead routine saves them +% and they are restored when the command executes. +% +% \section{Documentation} +% +% At this point in the development of \pkg{xpeek}, +% only one set of follower-dependent functions is defined. +% Later versions will provide tools for defining such function sets. +% +% \subsection{Match- \& Ignore-Lists} +% +% \begin{variable}{\g_xpeek_matchlist_tl, \g_xpeek_ignorelist_tl} +% \begin{syntax} +% \cs{tl_gput_right:Nn} \cs{g_xpeek_\meta{list}_tl} \marg{tokens} +% \cs{tl_gremove_all:Nn} \cs{g_xpeek_\meta{list}_tl} \marg{tokens} +% \end{syntax} +% These two token-lists are the heart of \pkg{xpeek}. +% +% The \textit{match-list} is similar to \pkg{xspace}’s exceptions list +% or \LaTeX’s \cs{nocorrlist} list for suppressing italic correction. +% +% The functionality enabled by the \emph{ignore-list}, +% on the other hand, +% is the \foreign{raison d’être} of this package. +% For example, +% if \LaTeX’s \cs{textit} had an ignore-list +% and the token \cs{xspace} were in that list, +% then the definition +% \begin{quote} +% |\NewDocumentCommand \naif {} {\textit{na\"\i f}\xspace}| +% \end{quote} +% from the example above +% would have worked properly: +% \cs{textit} would have ignored the \cs{xspace}, +% looked past it \& seen the period, +% and therefore omitted italic correction. +% When that was done, \cs{xspace} would execute; +% it too would see the period, +% and therefore would not insert a space. +% \end{variable} +% +% \subsection{Peek-ahead} +% +% \begin{function}{\xpeek_after:nw} +% \begin{syntax} +% \cs{xpeek_after:nw} \marg{code} \meta{token} +% \end{syntax} +% Similar to \pkg{expl3}’s \cs{peek_after:Nw}, +% this sets the test variable \cs{l_peek_token} to \meta{token} +% then executes the \marg{code}. +% The \meta{token} remains in the input stream. +% \end{function} +% +% \subsection{Collecting Ignored Tokens} +% +% \begin{function}{\xpeek_collect_do:nn} +% \begin{syntax}\fixsyntaxlines +% \cs{xpeek_collect_do:nn} \marg{ignore-list} \marg{operation} \ +% \meta{the rest of the input-stream} +% \end{syntax} +% This collects all leading tokens in the input stream +% which are in the ignore-list, +% and passes them as an argument to the operation. +% For example, the invocation +% \begin{quote} +% |\xpeek_collect_do:nn { abc } { \foo \bar } caada| +% \end{quote} +% will collect the leading “\texttt{caa}” from the input stream +% and pass those tokens as an argument to +% the operation “|\foo \bar|”. +% \Ie, the code above is equivalent to this: +% \begin{quote} +% |\foo \bar { caa } da| +% \end{quote} +% (Note that it’s \cs{bar} \emph{within} the \marg{operation} +% that gets the collected tokens as an argument.) +% \end{function} +% +% \subsection{Searching Through Token-Lists} +% +% \begin{function}{\xpeek_if_in:NNTF} +% \begin{syntax}\fixsyntaxlines +% \cs{xpeek_if_in:NNTF} \cs{\meta{haystack}} \cs{\meta{needle}} \ +% \marg{true-code} \marg{false-code} +% \end{syntax} +% Among the variants of \cs{tl_if_in:Nn(TF)} defined in \pkg{expl3}, +% the form \cs{tl_if_in:NNTF} is missing. +% But that’s the form needed in this package, +% in constructions like this: +% \begin{quote} +% |\xpeek_if_in:NNTF \g_xpeek_matchlist_tl \l_peek_token|\\ +% | |\marg{true-code} \marg{false-code} +% \end{quote} +% So we define it ourselves. +% \end{function} +% +% \section{Example} +% +% As a demonstration, +% here’s an example of using the \pkg{xpeek} facilities +% to implement correct automatic italic correction \ala \cs{textit}. +% +% Set the match-list equal to \LaTeX’s default \cs{nocorrlist}. +% Only \cs{xspace} is needed in the ignore-list: +% \begin{quote} +% |\tl_gset:Nn \g_xpeek_matchlist_tl { , . }|\\ +% |\tl_gset:Nn \g_xpeek_ignorelist_tl { \xspace }| +% \end{quote} +% (Note that this does not require \cs{xspace} to be defined; +% it merely ensure that if it is defined, +% the macro will work correctly.) +% +% Declare the new italicization command \cs{xit}. +% It calls \cs{xpeek_collect_do:nn} to collect +% any instances of \cs{xspace} that might follow the invocation. +% The \marg{code} argument to \cs{xpeek_collect_do:nn} +% is where the italicization happens. +% Before that ends, though, +% the code checks whether \cs{l_peek_token} is in the ignore-list +% and includes italic correction, or not, appropriately. +% The last command within the \marg{code} +% is what will get the collected tokens as an argument. +% Since we want to keep \cs{xspace} if it’s there, +% we finish up the code with \cs{use:n}, +% which simply puts its argument into the input stream. +% \begin{quote} +% |\NewDocumentCommand \xit {m}|\\ +% | {|\\ +% | \xpeek_collect_do:nn \g_xpeek_ignorelist_tl|\\ +% | {|\\ +% | \group_begin:|\\ +% | \itshape #1|\\ +% | \xpeek_if_in:NNTF \g_xpeek_matchlist_tl \l_peek_token|\\ +% | { } { \/ }|\\ +% | \group_end:|\\ +% | \use:n|\\ +% | }|\\ +% | }|\\ +% \end{quote} +% +% Finally, define the command we tried to make work in the introduction, +% and see whether we’ve made it work. +% \begin{quote} +% |\NewDocumentCommand \naif {} {\xit{na\"\i f}\xspace}|\\ +% |He was a true \naif.|\\ +% |He was such a \naif he expected this example to work.| +% \end{quote} +% After all this, the code above yields this result: +% \begin{quote} +% \ExplSyntaxOn +% \tl_gset:Nn \g_xpeek_matchlist_tl { , . } +% \tl_gset:Nn \g_xpeek_ignorelist_tl { \xspace } +% \NewDocumentCommand \xit {m} +% { +% \xpeek_collect_do:nn \g_xpeek_ignorelist_tl +% { +% \group_begin: +% \itshape #1 +% \xpeek_if_in:NNTF \g_xpeek_matchlist_tl \l_peek_token +% { } { \/ } +% \group_end: +% \use:n +% } +% } +% \ExplSyntaxOff +% \NewDocumentCommand \naif {} {\xit{na\"\i f}\xspace} +% He was a true \naif. +% He was such a \naif he expected this example to work. +% \end{quote} +% And work it does. +% +% \end{documentation} +% +% \begin{implementation} +% +% \section{\pkg{\jobname} Implementation} +% +% \begin{macrocode} +%<*package> +% \end{macrocode} +% +% \begin{macrocode} +%<@@=xpeek> +% \end{macrocode} +% +% \begin{macrocode} +\RequirePackage{expl3, xparse} +% \end{macrocode} +% +% \begin{macrocode} +\ProvidesExplPackage + {xpeek} {2012/08/09} {0.1} + {Define commands that peek ahead in the input stream} +% \end{macrocode} +% +% \subsection{Match- \& Ignore-Lists} +% +% \begin{variable}{\g_xpeek_matchlist_tl, \g_xpeek_ignorelist_tl} +% Define the lists. +% \begin{macrocode} +\tl_new:N \g_xpeek_matchlist_tl +\tl_new:N \g_xpeek_ignorelist_tl +% \end{macrocode} +% \end{variable} +% +% \subsection{Peek-ahead} +% +% \begin{macro}[internal]{\xpeek_after:nw} +% \begin{variable}[aux]{\l_@@_code_tl} +% Define a function (AKA “token-list”) to hold the code passed in. +% \begin{macrocode} +\tl_new:N \l_@@_code_tl +% \end{macrocode} +% Store the argument in \cs{l_@@_code_tl}, +% then defer to \cs{peek_after:Nw} +% \begin{macrocode} +\cs_new_protected:Npn \xpeek_after:nw #1 + { + \tl_set:Nn \l_@@_code_tl {#1} + \peek_after:Nw \l_@@_code_tl + } +% \end{macrocode} +% \end{variable} +% \end{macro} +% +% \subsection{Collecting Ignored Tokens} +% +% \begin{macro}{\xpeek_collect_do:nn} +% \begin{macro}[aux]{\@@_collect_do:nnn} +% \begin{variable}[aux]{\l_@@_collected_tokens_tl} +% \begin{arguments} +% \item Tokens to collect from the input stream +% \item The operation to be performed on the collected tokens +% \item The next token on the input stream +% (for \cs{@@_collect_do:nnn}). +% \end{arguments}\noindent +% Define a list to save collected tokens in. +% \begin{macrocode} +\tl_new:N \l_@@_collected_tokens_tl +% \end{macrocode} +% Clear list and defer to auxiliary function. +% \begin{macrocode} +\cs_new_protected:Npn \xpeek_collect_do:nn #1#2 + { + \tl_clear:N \l_@@_collected_tokens_tl + \@@_collect_do:nnn {#1} {#2} {} + } +% \end{macrocode} +% Recursively consume tokens from the input stream +% so long as they match the collect-list, +% then apply the operation to the collected tokens. +% The last matching token will not yet have been collected, +% so pass it to the operation as well. +% (If there were no matching tokens, +% |#3| will be the empty argument +% that was passed by \cs{xpeek_collect_do:nn}.) +% \begin{macrocode} +\cs_new_protected:Npn \@@_collect_do:nnn #1#2#3 + { + \xpeek_after:nw + { + \xpeek_if_in:NNTF #1 \l_peek_token + { + \tl_put_right:Nn \l_@@_collected_tokens_tl {#3} + \@@_collect_do:nnn {#1} {#2} + } + { + #2 { \l_@@_collected_tokens_tl #3 } + } + } + } +% \end{macrocode} +% \end{variable} +% \end{macro} +% \end{macro} +% +% \subsection{Searching Through Token-Lists} +% +% \begin{macro}{\xpeek_if_in:NNTF} +% \begin{variable}[aux]{\l_@@_bool} +% \begin{arguments} +% \item Token-list to search through (the “haystack”) +% \item Token to search for (the “needle”) +% \end{arguments}\noindent +% Define an internal flag. +% \begin{macrocode} +\bool_new:N \l_@@_bool +% \end{macrocode} +% Map the “haystack”, searching for the “needle”. +% \begin{macrocode} +\prg_new_protected_conditional:Npnn \xpeek_if_in:NN #1#2 { TF } + { + \bool_set_false:N \l_@@_bool + \tl_map_inline:Nn #1 + { + \token_if_eq_charcode:NNT #2 ##1 + { + \bool_set_true:N \l_@@_bool + \tl_map_break: + } + } + \bool_if:NTF \l_@@_bool + { \prg_return_true: } { \prg_return_false: } + } +% \end{macrocode} +% \end{variable} +% \end{macro} +% +% \begin{macrocode} +% +% \end{macrocode} +% +% \end{implementation} +% +% \PrintChanges +% +% \PrintIndex -- cgit v1.2.3