1

I'm trying to transform an uppercase KEYWORD into \textsc{Keyword} in Listings. I'd like something of the form

\lstdefinestyle{mystyle}
{
    language = ML,
    basicstyle = {\ttfamily},
    keywordstyle = [2]{\scAfterTitlecase}, % magic macro desired here!
    morekeywords = [2]{SOME,NONE,GREATER,EQUAL,LESS},
}

Ideally, if \scAfterTitlecase could be defined, the listing would "eat in" a code snippet SOME x and "spit out" a typeset result "as if" it were {\sc Some} x (for example).

Note: I realize that \sc will not preserve the \ttfamily basic style, and that is intentional. I am not trying to have small cap teletype letters.

Generalized Problem

Looking ahead, I realize I am going to be typesetting SNAKE_CASE_UPPERCASE_KEYWORDS, and I would love to figure out some way to similarly transform them to be typeset "small caps Pascal cased" (e.g., SNAKE_CASE_UPPER would be transformed into {\sc SnakeCaseUpper}).

Complete Example

I want to type in LaTeX something like this:

\documentclass{article}
\usepackage{listings}

\lstset{ language=ML, basicstyle = {\ttfamily}, keywordstyle = [2]{\scAfterTitlecase}, % magic macro desired here! morekeywords = [2]{SOME,NONE,GREATER,EQUAL,LESS,STRETCH_GOAL}, } \begin{document} \begin{lstlisting} signature STRETCH_GOAL = sig val will_work : bool; val issued : bool option; end;

fun giveRaise (SOME true) = true | giveRaise _ = false; \end{lstlisting} \end{document}

I want that lstlistings environment to be typeset as if I wrote:

signature \textsc{StretchGoal} = sig
  val will_work : bool;
  val issued : bool option;
end;

fun giveRaise (\textsc{Some} true) = true | giveRaise _ = false;

I want a magic \scAfterTitlecase macro which would have the same effect as if I could somehow transform the morekeywords = [2]{Some, None, StretchGoal, ...}. The reason why I don't want to just change the keywords by hand is because I'm loading actual Standard ML code.

Edit 1 (Jan 13, 2022): As a first stab, I managed to write a macro which can take a SCREAMING_SNAKE_CASE argument and convert it to a PascalCase string:

\usepackage{mfirstuc}
\makeatletter
\def\@Screaming@Snake@To@Pascal@Case#1_#2\end@Screaming@Snake@To@Pascal@Case{%
  \makefirstuc{\MakeLowercase{#1}}%
\ifx#2\@empty%
\else\ignorespaces\@Screaming@Snake@To@Pascal@Case#2%
\end@Screaming@Snake@To@Pascal@Case%
\fi}

\newcommand\ScreamingSnakeToPascalCase[1]{\ifx@empty#1% \else% @Screaming@Snake@To@Pascal@Case#1_@empty\end@Screaming@Snake@To@Pascal@Case% \fi} \makeatother

This works fine. Then I tried writing a "no argument" version of this macro (as discussed in the thread Change syntax of macro, to go inside braces). The complete, uh, "working" minimal example:

\documentclass{article}
\usepackage{mfirstuc}
\makeatletter
\def\@Screaming@Snake@To@Pascal@Case#1_#2\end@Screaming@Snake@To@Pascal@Case{%
  \makefirstuc{\MakeLowercase{#1}}%
\ifx#2\@empty%
\else\ignorespaces\@Screaming@Snake@To@Pascal@Case#2%
\end@Screaming@Snake@To@Pascal@Case%
\fi}

\newcommand\ScreamingSnakeToPascalCase[1]{\ifx@empty#1% \else% @Screaming@Snake@To@Pascal@Case#1_@empty\end@Screaming@Snake@To@Pascal@Case% \fi} \makeatother

\newcommand{\noarg}{% % close the group \egroup
% the first \expandafter removes \iftrue % the second \expandafter removes \else \expandafter\expandafter\expandafter \ScreamingSnakeToPascalCase\iftrue\expandafter{\else}\fi }

\lstset{ language=ML, alsoletter={_}, basicstyle = {\ttfamily}, keywordstyle = {[2]\noarg}, morekeywords = [2]{SOME,NONE,GREATER,EQUAL,LESS,STRETCH_GOAL}, } \begin{document}

{\noarg COUGH_DROP_MAY_NOT_WORK_GOOD} % This is typeset fine % ...if I remove the lstlisting environment below

\textsc{\ScreamingSnakeToPascalCase{SPAM_EGGS_BUTTER}} % also works fine

% now, this lstlisting fails... \begin{lstlisting} signature STRETCH_GOAL = sig val will_work : bool; val issued : bool option; end;

fun giveRaise (SOME true) = true | giveRaise _ = false; \end{lstlisting} \end{document}

LaTeX is not happy, compaining:

! Extra \else.
\@makefirstuc ...i \@gls@domfirstuc \fi \fi \else 
                                                  \glsmakefirstuc {#1}\fi \fi 
l.44 signature STRETCH_GOAL 
                            = sig
? 

So, one step forward, albeit very small.

Alex Nelson
  • 1,060
  • Can you provide a complete example rather than a code snippet? – Steven B. Segletes Jul 27 '21 at 23:10
  • @StevenB.Segletes I'm not sure you need anything more than the SOME x transformed into the \textsc{Some} x, but I have added a complete example anyways. – Alex Nelson Jul 28 '21 at 02:10
  • Just for clarification: Your desired \scAfterTitlecase needs to 1) split a keyword into its component words by underscores _ (if there are any), and then remove the underscores; 2) in each component words, capitalize the first letter while making all other letters lowercase; and 3) typeset the entire keyword in cap+small cap. Is my understanding correct? – Ruixi Zhang Jul 28 '21 at 03:25
  • @RuixiZhang Yes, you are correct in describing the desired macro. [I may be hasty in calling step (2) "title casing", since you are correct in describing it as "Capitalize the first letter while making all other letters lowercase".] – Alex Nelson Jul 29 '21 at 14:05
  • @RuixiZhang The real challenge is to make the macro work with listings. It's not hard using some regular expressions (or other similar hackery), but making it work with listings, ah, that's the challenge! – Alex Nelson Jul 29 '21 at 16:23

1 Answers1

0

I hacked together a solution like this one. The only subtlety is that every TeX macro executed will cause a horizontal skip in Listings, so I need an \unskip to undo this.

But this will correctly change SCREAMING_SNAKE_CASE into \textsc{ScreamingSnakeCase} within a Listings environment.

\documentclass{article}
\usepackage{mfirstuc}
\makeatletter

% I need to do something like % https://tex.stackexchange.com/a/448770/14751 % to transform the "\lst@um_" back to "_"

\def@Screaming@Snake@To@Pascal@Case#1_#2\end@Screaming@Snake@To@Pascal@Case{% \makefirstuc{\MakeLowercase{#1}}% \ifx#2@empty\else\ignorespaces@Screaming@Snake@To@Pascal@Case#2\end@Screaming@Snake@To@Pascal@Case\fi}

\newcommand\ScreamingSnakeToPascalCase[1]{% \ifx@empty#1\else{\normalfont\scshape@Screaming@Snake@To@Pascal@Case#1_@empty\end@Screaming@Snake@To@Pascal@Case}\fi}

% https://tex.stackexchange.com/questions/439396/listings-highlight-a-prefixed-keyword-starting-with-a-single-quote \makeatother

\usepackage{listings}

\makeatletter

\def@setlststyle{% \unskip\edef\lt@temp{\unskip\noexpand\ScreamingSnakeToPascalCase{\expandafter\the\lst@token\unskip\relax\unskip}}% \unskip\global\lst@token=\expandafter{\lt@temp}% \unskip\the\lst@token }

\begingroup \catcode`_=11

\gdef\scsty#1{ \unskip\edef\lsttokens{\the\lst@token} \unskip\global\lst@token={} \unskip\expandafter\replaceUM\lsttokens\noexpand\lst@um_\relax@end \unskip\unskip@setlststyle }

\gdef\appendall#1@endAppend{\unskip \global\lst@token=\expandafter{\the\lst@token#1} } \gdef\replaceUM#1\lst@um_#2@end{\unskip \if\relax\detokenize{#1}\relax \else \ifx\relax#2 \appendall#1@endAppend \else \appendall#1_@endAppend \replaceUM#2@end \fi \fi } \endgroup

\makeatother

\lstset{ % language=ML, % columns = fixed, columns=flexible, % columns=fullflexible, alsoletter={_}, basicstyle = {\ttfamily}, keywordstyle = [2]\scsty, morekeywords = [2]{SOME,NONE,GREATER,EQUAL,LESS,STRETCH_GOAL}, keywordstyle = {\bf}, morekeywords={abstype,and,andalso,as,case,do,datatype,else,end,% eqtype,exception,fn,fun,functor,handle,if,in,include,infix,% infixr,let,local,nonfix,of,op,open,orelse,raise,rec,sharing,sig,% signature,struct,structure,then,type,val,with,withtype,while},% sensitive,% morecomment=[n]{(}{)},% morestring=[d]"% } \begin{document}

\begin{lstlisting} signature STRETCH_GOAL = sig val will_work : bool; val issued : bool option; end;

fun giveRaise (SOME true) = true | giveRaise _ = false; \end{lstlisting} \end{document}

Alex Nelson
  • 1,060