[Tex/LaTex] Can the listings package highlight by regexp

listings

I'm trying to typeset SuperCollider code using the listings package.

Any "freestanding" identifiers that begin with a capital letter are class names, and should be highlighted; by "freestanding", I mean outside of strings or comments. I would be tickled pink if listings could let you define identifiers in terms of regexp, e.g. [A-Z][A-Za-z0-9_]* but the manual doesn't suggests that as a possibility.

SuperCollider has some 2300 classes… I really don't relish listing all of them literally in my preamble. I guess I could extract them by hand for each individual listing, but I'd rather not have to.

Is there a way to do this without a really massive morekeywords expression? (Perhaps using another package?)

Sample SuperCollider code:

p.clear;

~grains.addSpec(\tfreq, [1, 40, \exp]);
~grains.addSpec(\overlap, [0.1, 10, \exp]);
~grains.addSpec(\pos, [0, b.duration]);  // 3.43 is nice!
~grains.addSpec(\rate, [0.5, 2, \exp]);
~grains = { |tfreq = 25, overlap = 6, pan = 0, amp = 0.2, pos = 3.43,
   rate = 1|
   var trig = Impulse.ar(tfreq);
   TGrains.ar(2, trig, b, rate, pos, overlap / tfreq, pan, amp)
};
~grains.play;

Impulse and TGrains should be highlighted in blue. I have dozens of other listings using different capitalized keywords.

Best Answer

Edit: see https://tex.stackexchange.com/a/166159/21891 for a more complete listings language definition for the SuperCollider language.

Syntax highlighting of identifiers by regexp is not a feature of listings, but it is possible to parse identifiers in order to check whether they pass a certain test and then conditionally apply a style to them.

In the code below, all identifiers of the form [A-Z][A-Za-z0-9_]* are highlighted in a user-defined "class style". The class style below consists in boldface, green colour. You can customize the way classes are typeset by passing the appropriate declarations (e.g. \bfseries\color{red}) to the classstyle key, which I've defined for convenience.

If everything works as expected, you shouldn't have to specify your SuperCollider classes one by one anymore. Happy days :) Also, you can still define keywords starting by an uppercase letter; the keyword style will override the class style for those keywords.

References: This solution combines ideas laid out in Marco Daniel's answer, and David Carlisle's answer. The SuperCollider sample I used for this example is adapted from there.

enter image description here

\documentclass{article}

\usepackage[T1]{fontenc}
\usepackage{listings}
\usepackage{xcolor}

\renewcommand{\ttdefault}{pcr}

% patch to detect SuperCollider classes (i.e. identifiers starting by A-Z)
% and apply the corresponding class style
% ----- ugly internals -----
\makeatletter

% custom keys for controlling the styles of SuperCollider classes,
% symbols, and global variables
\lst@Key{classstyle}{}{\def\classstyle@supcol{#1}}

% local variables
\newcount\currentchar

\def\@testChar%
{%
  \ifnum\lst@mode=\lst@Pmode%
    % copy the first token in \the\lst@token to \@testChar
    \expandafter\splitfirstchar\expandafter{\the\lst@token}%
        % test for characters A through Z
        \currentchar=65
        \loop
          \expandafter\ifnum\expandafter`\@testChar=\currentchar%
            \let\lst@thestyle\classstyle@supcol%
            \let\iterate\relax
          \fi
          \advance \currentchar by 1
          \unless\ifnum\currentchar>90
        \repeat 
  \fi
  % ...but override by keyword style if a keyword is detected!
  \lsthk@DetectKeywords% 
}

% helper macros
\def\splitfirstchar#1{\@splitfirstchar#1\@nil}
\def\@splitfirstchar#1#2\@nil{\gdef\@testChar{#1}}

% apply patch to perform test on each identifier
\lst@AddToHook{Output}{\@ddedToOutput}
\let\@ddedToOutput\@testChar

\makeatother
% ----- end of ugly internals -----

% language definition
\lstdefinelanguage{SuperCollider}
{%
  alsoother     = @\$,
  morecomment   = **[l]{//},
  morecomment   = **[s]{/*}{*/},
  morestring    = **[s]{"}{"},
}[keywords,strings,comments]

% color definition
\definecolor{SCclasscolor}{RGB}{000,127,000}
\definecolor{SCcommentcolor}{RGB}{063,127,127}
\definecolor{SCstringcolor}{RGB}{186,034,034}
\colorlet{framerulecolor}{black}

% style definition
\lstdefinestyle{SupColSty}
{%
  language         = SuperCollider,
  basicstyle       = \ttfamily\footnotesize,
  stringstyle      = \color{SCstringcolor},
  commentstyle     = \color{SCcommentcolor}\itshape,
  classstyle       = \color{SCclasscolor}\bfseries,
  breaklines       = true,
  showstringspaces = false,
  frame            = single,
  rulecolor        = \color{framerulecolor},
}

% --- write source code to external file (for this example) ---
\usepackage{filecontents}
\begin{filecontents*}{Atari2600.scd}
/*
// Fredrik Olofsson

A quick demo of Fredrik Olofsson's Atari 2600 plugin, which can be downloaded from:


www.fredrikolofsson.com/pages/code-sc.html

This lovely 8-bit tune is based on an example in the helpfile.

*/

// Simple synth definition using the Atari2600 UGen:
(
SynthDef(\atari2600, {|out= 0, gate= 1, tone0= 5, 
      tone1= 8, freq0= 10, freq1= 20, amp= 1, pan= 0|
    var e, z;
    e= EnvGen.kr(Env.asr(0.01, amp, 0.05), gate, doneAction:2);
    z= Atari2600.ar(tone0, tone1, freq0, freq1, 15, 15);
    Out.ar(out, Pan2.ar(z*e, pan));
}).add;
)
"Test: Pseq SynthDef, etc. don't get highlighted as classes in strings"
// And a pattern to play it:
(
Pbind(
    \instrument, \atari2600,
    \dur, Pseq([0.25, 0.25, 0.25, 0.45], inf),
    \amp, 0.8,
    \tone0, Pseq([Pseq([2, 5], 32), Pseq([3, 5], 32)], inf),
    \tone1, 14,
    \freq0, Pseq([Pbrown(28, 31, 1, 32), 
                          Pbrown(23, 26, 3, 32)], inf),
    \freq1, Pseq([Pn(10, 16), Pn(11, 16)], inf)
).play
)
\end{filecontents*}

\begin{document}
\lstinputlisting[style=SupColSty]{Atari2600.scd}
\end{document}