Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I recently posted this answer to How to highlight all words of the form [0-9][A-Za-z0-9]* immediately following an equal sign?. It works, but it suffers from (a lot) of code duplication.

What I have at the moment

More specifically, I have to use a line of the form

\lst@DefSaveDef{`<char>}\jubobs@<char>{\jubobs@<char>\foo}

no fewer than 52 times, for <char> in A-Z and a-z. For information, \lst@DefSaveDef is a listings internal macro with the following syntax:

\lst@DefSaveDef{<charcode>}<some-macro>{<replacement-text>}

Using my newfound understanding of \expandafter, I've managed to somewhat reduce the code by defining a macro that takes a letter as argument and performs the same thing as the line show above:

\makeatletter
\newcommand\addtoletterdef[1]
{%
  \expandafter\expandafter\expandafter\lst@DefSaveDef%
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\expandafter`%
  \expandafter\expandafter\expandafter#1%
  \expandafter\expandafter\expandafter}%
  \expandafter\csname jubobs@#1\expandafter\endcsname%
  \expandafter{\csname jubobs@#1\endcsname\foo}%
}
\makeatother

However, that didn't help much in reducing code duplication: I still have to invoke \addtoletterdef 52 times... ;...(

What I want

I would like to define a macro similar to my \addtoletterdef but that would accept a charcode instead of a character. That way, it would be easy to loop through charcodes 65 to 90 (A-Z) and 97 to 122 (a-z) and perform all those 52 operations without any code duplication.

I've got a feeling that the answer lies in using the \begingroup\lccode trick, but that trick is very new to me, and I'm far from mastering it.

How can I redefine \addtoletterdef to accept a charcode instead of a character?


MWE:

\documentclass{article}

\usepackage{listings}

% dummy macro
\def\foo{foo}

\makeatletter
\newcommand\addtoletterdef[1]
{%
  \expandafter\expandafter\expandafter\lst@DefSaveDef%
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\expandafter`%
  \expandafter\expandafter\expandafter#1%
  \expandafter\expandafter\expandafter}%
  \expandafter\csname jubobs@#1\expandafter\endcsname%
  \expandafter{\csname jubobs@#1\endcsname\foo}%
}
\makeatother

\lstdefinestyle{mycode}
{
  language=C,
  SelectCharTable=
      \addtoletterdef{A}
      % ...
      \addtoletterdef{Z}
      \addtoletterdef{a}
      % ...
      \addtoletterdef{z}
}

\begin{document}
\begin{lstlisting}[style=mycode]
a1 = 6.12234Z
a2 = Z1324124
\end{lstlisting}
\end{document}
share|improve this question
2  
So you are doing lot of digging with listings. It is really good. I see many packages (like matlab prettifier) as the out come. Keep it up and all the best :) –  Harish Kumar Mar 8 at 23:39
    
@HarishKumar Thanks for the encouragement! I really appreciate it. For the record, it's not that I love Matlab so much; it's just a good case study to learn about the listings package, and further, compiler construction. –  Jubobs Mar 8 at 23:43

2 Answers 2

up vote 13 down vote accepted
+250

You can build your \lstdefine in pieces, adding to a token register:

\documentclass{article}

\usepackage{xcolor}
\usepackage{textcomp}
\usepackage{listings}

\makeatletter

\newif\iffirstchar\firstchartrue
\newif\ifstartedbyadigit
\newif\ifprecededbyequalsign

\newcommand\processletter
{%
  \ifnum\lst@mode=\lst@Pmode%
    \iffirstchar%
        \global\startedbyadigitfalse%
      \fi
      \global\firstcharfalse%
    \fi
}

\newcommand\processdigit
{%
  \ifnum\lst@mode=\lst@Pmode%
      \iffirstchar%
        \global\startedbyadigittrue%
      \fi
      \global\firstcharfalse%
  \fi
}

\lst@AddToHook{OutputOther}%
{%
  \lst@IfLastOtherOneOf{=}
    {\global\precededbyequalsigntrue}
    {}%
}

\lst@AddToHook{Output}%
{%
  \ifprecededbyequalsign%
      \ifstartedbyadigit%
        \def\lst@thestyle{\color{orange}}%
      \fi
    \fi
  \global\firstchartrue%
  \global\startedbyadigitfalse%
  \global\precededbyequalsignfalse%
}


\definecolor{Code}{rgb}{0,0,0}
\definecolor{Keywords}{rgb}{255,0,0}
\definecolor{Strings}{rgb}{255,0,255}
\definecolor{Comments}{rgb}{0,0,255}
\definecolor{Numbers}{rgb}{255,128,0}

\newtoks\jubo@toks
\jubo@toks={
  language=C,
  commentstyle=\color{Comments}\slshape,
  stringstyle=\color{Strings},
  keywordstyle={\color{Keywords}\bfseries},
  alsoletter=0123456789,
  SelectCharTable=%
}
\def\add@savedef#1#2{%
  \begingroup\lccode`?=#1\relax
  \lowercase{\endgroup
  \edef\@temp{%
    \noexpand\lst@DefSaveDef{\number#1}%
    \expandafter\noexpand\csname lsts@?\endcsname{%
      \expandafter\noexpand\csname lsts@?\endcsname\noexpand#2}%
  }}%
  \jubo@toks=\expandafter{\the\expandafter\jubo@toks\@temp}%
}
\count@=`0
\loop
  \add@savedef\count@\processdigit
  \ifnum\count@<`9
  \advance\count@\@ne
\repeat
\count@=`A
\loop
  \add@savedef\count@\processletter
  \ifnum\count@<`Z
  \advance\count@\@ne
\repeat
\count@=`a
\loop
  \add@savedef\count@\processletter
  \ifnum\count@<`z
  \advance\count@\@ne
\repeat
%\showthe\jubo@toks % for debugging
\begingroup\edef\x{\endgroup
  \noexpand\lstdefinestyle{mycode}{\the\jubo@toks}
}\x

\makeatother

\begin{document}

\begin{lstlisting}[style=mycode]
int main ()
{
    printf("foo 3a a3")
    //this is an example
    a1 = 0;
    a2 = a1;
    a3 = 16hxFF;
    a4 = 16 + a1;
    // sanity check
    a5 =+ 16hFF /* 16hFF is not highlighted because not it's
                   immediately preceded by an equal sign */
    return 0;
}
\end{lstlisting}

\end{document}

enter image description here

Just for checking, here's the value of \jubo@toks prior to step 6

language=C, commentstyle=\color {Comments}\slshape , stringstyle=\color {Str
ings}, keywordstyle={\color {Keywords}\bfseries }, alsoletter=0123456789, Selec
tCharTable=\lst@DefSaveDef {48}\lsts@0 {\lsts@0 \processdigit }\lst@DefSaveDef 
{49}\lsts@1 {\lsts@1 \processdigit }\lst@DefSaveDef {50}\lsts@2 {\lsts@2 \proce
ssdigit }\lst@DefSaveDef {51}\lsts@3 {\lsts@3 \processdigit }\lst@DefSaveDef {5
2}\lsts@4 {\lsts@4 \processdigit }\lst@DefSaveDef {53}\lsts@5 {\lsts@5 \process
digit }\lst@DefSaveDef {54}\lsts@6 {\lsts@6 \processdigit }\lst@DefSaveDef {55}
\lsts@7 {\lsts@7 \processdigit }\lst@DefSaveDef {56}\lsts@8 {\lsts@8 \processdi
git }\lst@DefSaveDef {57}\lsts@9 {\lsts@9 \processdigit }\lst@DefSaveDef {65}\l
sts@A {\lsts@A \processletter }\lst@DefSaveDef {66}\lsts@B {\lsts@B \processlet
ter }\lst@DefSaveDef {67}\lsts@C {\lsts@C \processletter }\lst@DefSaveDef {68}\
lsts@D {\lsts@D \processletter }\lst@DefSaveDef {69}\lsts@E {\lsts@E \processle
tter }\lst@DefSaveDef {70}\lsts@F {\lsts@F \processletter }\lst@DefSaveDef {71}
\lsts@G {\lsts@G \processletter }\lst@DefSaveDef {72}\lsts@H {\lsts@H \processl
etter }\lst@DefSaveDef {73}\lsts@I {\lsts@I \processletter }\lst@DefSaveDef {74
}\lsts@J {\lsts@J \processletter }\lst@DefSaveDef {75}\lsts@K {\lsts@K \process
letter }\lst@DefSaveDef {76}\lsts@L {\lsts@L \processletter }\lst@DefSaveDef {7
7}\lsts@M {\lsts@M \processletter }\lst@DefSaveDef {78}\lsts@N {\lsts@N \proces
sletter }\lst@DefSaveDef {79}\lsts@O {\lsts@O \processletter }\lst@DefSaveDef {
80}\lsts@P {\lsts@P \processletter }\lst@DefSaveDef {81}\lsts@Q {\lsts@Q \proce
ssletter }\lst@DefSaveDef {82}\lsts@R {\lsts@R \processletter }\lst@DefSaveDef 
{83}\lsts@S {\lsts@S \processletter }\lst@DefSaveDef {84}\lsts@T {\lsts@T \proc
essletter }\lst@DefSaveDef {85}\lsts@U {\lsts@U \processletter }\lst@DefSaveDef
 {86}\lsts@V {\lsts@V \processletter }\lst@DefSaveDef {87}\lsts@W {\lsts@W \pro
cessletter }\lst@DefSaveDef {88}\lsts@X {\lsts@X \processletter }\lst@DefSaveDe
f {89}\lsts@Y {\lsts@Y \processletter }\lst@DefSaveDef {90}\lsts@Z {\lsts@Z \pr
ocessletter }\lst@DefSaveDef {97}\lsts@a {\lsts@a \processletter }\lst@DefSaveD
ef {98}\lsts@b {\lsts@b \processletter }\lst@DefSaveDef {99}\lsts@c {\lsts@c \p
rocessletter }\lst@DefSaveDef {100}\lsts@d {\lsts@d \processletter }\lst@DefSav
eDef {101}\lsts@e {\lsts@e \processletter }\lst@DefSaveDef {102}\lsts@f {\lsts@
f \processletter }\lst@DefSaveDef {103}\lsts@g {\lsts@g \processletter }\lst@De
fSaveDef {104}\lsts@h {\lsts@h \processletter }\lst@DefSaveDef {105}\lsts@i {\l
sts@i \processletter }\lst@DefSaveDef {106}\lsts@j {\lsts@j \processletter }\ls
t@DefSaveDef {107}\lsts@k {\lsts@k \processletter }\lst@DefSaveDef {108}\lsts@l
 {\lsts@l \processletter }\lst@DefSaveDef {109}\lsts@m {\lsts@m \processletter 
}\lst@DefSaveDef {110}\lsts@n {\lsts@n \processletter }\lst@DefSaveDef {111}\ls
ts@o {\lsts@o \processletter }\lst@DefSaveDef {112}\lsts@p {\lsts@p \processlet
ter }\lst@DefSaveDef {113}\lsts@q {\lsts@q \processletter }\lst@DefSaveDef {114
}\lsts@r {\lsts@r \processletter }\lst@DefSaveDef {115}\lsts@s {\lsts@s \proces
sletter }\lst@DefSaveDef {116}\lsts@t {\lsts@t \processletter }\lst@DefSaveDef 
{117}\lsts@u {\lsts@u \processletter }\lst@DefSaveDef {118}\lsts@v {\lsts@v \pr
ocessletter }\lst@DefSaveDef {119}\lsts@w {\lsts@w \processletter }\lst@DefSave
Def {120}\lsts@x {\lsts@x \processletter }\lst@DefSaveDef {121}\lsts@y {\lsts@y
 \processletter }\lst@DefSaveDef {122}\lsts@z {\lsts@z \processletter }.
share|improve this answer
    
I'll have to carefully study your approach to understand it but, as always, everything checks out. Thanks 62 times! This is worth a bounty! –  Jubobs Mar 8 at 19:04
1  
\toks@ should be replaced by a permanent token register assigned via \newtoks, see the answer of a followup question. –  Heiko Oberdiek Jun 14 at 18:01
1  
@HeikoOberdiek Your wish is my command. ;-) –  egreg Jun 14 at 18:06

It is definitely worthwile to study the loops in egreg's answer, but you can also use existing tools:

loop for listing

The code copies all the set-up from your review, and then it uses \xintApplyInline to do the loops (non-expandably, it goes through here; else one could use\xintApplyUnbraced which prepares expandably the totality of the material before re-injecting it in the token stream, but not needed here).

Update: I notice only now that there were some extra \expandafter in the copied-pasted \addtoletterdef from the OP. One didn't notice them because when \csname is used the macro has meaning \relax hence the second level of \expandafter's did nothing bad anyhow. (and I also remove some unnecessary %'s)

\documentclass{article}
\usepackage{xinttools}% for expandable and non-expandable loops
\usepackage{listings}
\usepackage{xcolor}
\usepackage{textcomp}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% TAKEN OVER VERBATIM http://tex.stackexchange.com/a/164297/4686
%% (and egreg's answer)

\definecolor{Code}{rgb}{0,0,0}
\definecolor{Keywords}{rgb}{255,0,0}
\definecolor{Strings}{rgb}{255,0,255}
\definecolor{Comments}{rgb}{0,0,255}
\definecolor{Numbers}{rgb}{255,128,0}

\makeatletter

\newif\iffirstchar\firstchartrue
\newif\ifstartedbyadigit
\newif\ifprecededbyequalsign

\lst@AddToHook{OutputOther}%
{%
  \lst@IfLastOtherOneOf{=}
    {\global\precededbyequalsigntrue}
    {}%
}

\lst@AddToHook{Output}%
{%
  \ifprecededbyequalsign
      \ifstartedbyadigit
        \def\lst@thestyle{\color{orange}}%
      \fi
    \fi
  \global\firstchartrue
  \global\startedbyadigitfalse
  \global\precededbyequalsignfalse
}

\newcommand\processletter
{%
  \ifnum\lst@mode=\lst@Pmode
    \iffirstchar%
        \global\startedbyadigitfalse
      \fi
      \global\firstcharfalse
    \fi
}
\newcommand\processdigit
{%
  \ifnum\lst@mode=\lst@Pmode
      \iffirstchar
        \global\startedbyadigittrue
      \fi
      \global\firstcharfalse
  \fi
}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%% \addtoletterdef. Some \expandafter's removed!!

\newcommand\addtoletterdef[2]
{%
  \expandafter\lst@DefSaveDef
  \expandafter{%
  \expandafter`%
  \expandafter#2%
  \expandafter}%
  \csname jubobs@#2\expandafter\endcsname
  \expandafter{\csname jubobs@#2\endcsname #1}%
}
\makeatother

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% USE OF \xintApplyUnbraced to loop over letters or digits

\lstdefinestyle{mycode}
{
  language=C,
  commentstyle=\color{Comments}\slshape,
  stringstyle=\color{Strings},
  keywordstyle={\color{Keywords}\bfseries},
  alsoletter=0123456789,
  SelectCharTable=%
      \xintApplyInline{\addtoletterdef\processdigit}{0123456789}%
      \xintApplyInline{\addtoletterdef\processletter}
      {abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ}%
}

%% this is it!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


\begin{document}\thispagestyle{empty}
\begin{lstlisting}[style=mycode]
int main ()
{
    printf("foo 3a a3")
    //this is an example
    a1 = 0;
    a2 = a1;
    a3 = 16hxFF;
    a4 = 16 + a1;
    // sanity check
    a5 =+ 16hFF /* 16hFF is not highlighted because not it's
                   immediately preceded by an equal sign */
    return 0;
}
\end{lstlisting}
\end{document}
share|improve this answer
    
I had never heard of the xinttools package, but I'm going to check it out. Thanks! –  Jubobs Mar 8 at 19:27
    
The idea of the answer is a bit different from egreg's: there is no preparation of a token list, rather all the 62 instances of \addtoletterdef are created "live", so to speak. Turns out one can do this non-expandably, hence I used \xintApplyInline (non expandability here basically means that \xintApplyInline does some definitions of its own in the process; for example the \loop of LaTeX as used by egreg is also non-expandable as it makes a definition of \iterate). It is also possible but not needed here to use \xintApplyUnbraced which is an expandable iteration. –  jfbu Mar 8 at 19:49
    
@Jubobs what... you never heard of xinttools of \xintFor fame ?!?! .. with all the shameless propaganda I have been doing here for months now... ;-) –  jfbu Mar 8 at 20:15
2  
You need to take the propanganda up a notch ;-p –  Jubobs Mar 8 at 20:16

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.