9

I am interested to know how listings can be customized to mimic the Pygments's Prolog lexer (i.e. make Prolog code represented by listings look the same as it is done with minted)

I previously asked this question: How to make a minted code listing centered on a page?

Jubobs commented on that question and encouraged me to post this question.

My previous question was answered by egreg, who provided the following example, and this is exactly how I want my code listing to look like in my report:

mintedListing

However, I need a lot of extra packages for this solution. So I am wondering if the same (or very similar) result can be achieved by customizing listings.

Jubobs already provided the following lstdefinestyle example:

\lstdefinestyle{myPrologstyle}
{
    language=Prolog,
    basicstyle = \ttfamily\color{blue},
    moredelim = [s][\color{black}]{(}{)},
    literate =
        {:-}{{\textcolor{black}{:-}}}2
        {,}{{\textcolor{black}{,}}}1
        {.}{{\textcolor{black}{.}}}1
}

This however is not sufficient enough for me. For example, the variables are not highlighted in the dark blue color as it is done in minted.

Here is another (more complete) example of how Prolog code is highlighted with minted:

\begin{minted}{prolog}
somePredicate(A, B) :-
    arbitraryPredicate(A, _, 1, 2),
    predicateWithAtom(someAtom),
    anotherPredicate(B, someAtom, myPredicate(A, _)),
    findall(X, ('testString'(X), myPredicate(A, X)), L1),
    member(A, L1),
    !.
\end{minted}

The snippet above produces the following output:

MoreCompleteExample

As can be seen from this example, atoms are highlighted red, anonymous variables (underscores) are highlighted green, and numbers are grey (or is it cyan?).

Does anyone know how I can customize listings the way I want? Keep in mind that I want my listing to be centered on the page. (See my other question if you want to know how I make my listing centered.)

6
  • Those Prolog atoms and variables... do they only ever occur within parentheses?
    – jub0bs
    Feb 19, 2014 at 14:18
  • @Jubobs Yes, they only occur within parentheses. Also I noticed I forget one more thing, a predicate can be inside the parentheses of another predicate. Such as this: predicate(anotherPredicate(X)). The anotherPredicate is then highlighted just as any other predicate (light blue) and not as an atom.
    – PJvG
    Feb 19, 2014 at 14:48
  • Ok. That complicates things. I suggest you edit your answer to cover all those cases. A complete list of specs would be good. Then I or other listings lovers will be able to figure out whether implementing such a style in listings is feasible.
    – jub0bs
    Feb 19, 2014 at 14:56
  • 1
    In order to properly highlight an identifier as an atom or as a predicate, you would have to look ahead and check whether a ( is found immediately after the identifier. If a ( is found, then the identifier is a predicate; otherwise, the identifier is an atom. In theory, you could declare all characters as "letters" in order to allow for such a look-ahead; however, that would seriously upset listings and keyword recognition would be, by and large, completely broken. In summary: no, that type of highlighting is not possible with listings.
    – jub0bs
    Mar 14, 2014 at 14:28
  • 2
    I think I (mostly) cracked it; have a look at my updated answer.
    – jub0bs
    Apr 13, 2014 at 18:31

1 Answer 1

9

Months later, after much contorsion, I've managed to put a viable (even if ugly) solution together... thereby making a liar out of myself -_-'

Automatic recognition of Prolog predicates, atoms and variables

The solution shown below automatically distinguishes (and highlights in a style very similar to Pygments') Prolog predicates, atoms and variables. No need to manually enter long lists of keywords! In a nutshell, the approach consists in the following:

  • Make listings treat the opening-parenthesis character as a "letter", in order to test whether that character occurs at the end of identifiers; if it does, then the identifier in question is a predicate.
  • Otherwise, test the first character of the identifier against [A-Z_]; if the test passes, the identifier is a variable.
  • Otherwise, the identifier is an atom.

For a comparison, see the screenshot below.

enter image description here

Improvement over Pygments' Prolog lexer

I noticed that Pygments indiscriminately highlights underscores in dark green, and highlights whatever word immediately follows that character as an atom... That doesn't make sense to me, because, according to the Wikipedia page on Prolog syntax, an identifier starting by an underscore is a variable, not an atom.

The approach shown below improves upon the Pygments Prolog lexer insofar as it highlights underscores differently in different contexts:

  • it correctly highlights identifiers starting by an underscore, but composed of at least two characters, as (non-anonymous) Prolog variables, and
  • it highlights lone underscores, i.e. Prolog anonymous variables, in a distinct style (dark green).

Correct me if I'm wrong, here; I'm no Prolog expert.

Missing feature

I didn't bother replicating the highlighting style for numbers; as other questions on the subject attest, that is notoriously difficult, so I'll pass on that, at least for now.

Code

\documentclass{article}

\usepackage[top=1in]{geometry}
\usepackage{textcomp}
\usepackage{listings}
%\usepackage{minted}      % (requires -shell-escape)
\usepackage{xcolor}
\usepackage{filecontents}

% --- ugly internals for language definition ---
%
\makeatletter

% initialisation of user macros
\newcommand\PrologPredicateStyle{}
\newcommand\PrologVarStyle{}
\newcommand\PrologAnonymVarStyle{}
\newcommand\PrologAtomStyle{}
\newcommand\PrologOtherStyle{}
\newcommand\PrologCommentStyle{}

% useful switches (to keep track of context)
\newif\ifpredicate@prolog@
\newif\ifwithinparens@prolog@

% save definition of underscore for test
\lst@SaveOutputDef{`_}\underscore@prolog

% local variables
\newcount\currentchar@prolog

\newcommand\@testChar@prolog%
{%
  % if we're in processing mode...
  \ifnum\lst@mode=\lst@Pmode%
    \detectTypeAndHighlight@prolog%
  \else
    % ... or within parentheses
    \ifwithinparens@prolog@%
      \detectTypeAndHighlight@prolog%
    \fi
  \fi
  % Some housekeeping...
  \global\predicate@prolog@false%
}

% helper macros
\newcommand\detectTypeAndHighlight@prolog
{%
  % First, assume that we have an atom.
  \def\lst@thestyle{\PrologAtomStyle}%
  % Test whether we have a predicate and modify the style accordingly.
  \ifpredicate@prolog@%
    \def\lst@thestyle{\PrologPredicateStyle}%
  \else
    % Test whether we have a predicate and modify the style accordingly.
    \expandafter\splitfirstchar@prolog\expandafter{\the\lst@token}%
    % Check whether the identifier starts by an underscore.
    \expandafter\ifx\@testChar@prolog\underscore@prolog%
      % Check whether the identifier is '_' (anonymous variable)
      \ifnum\lst@length=1%
        \let\lst@thestyle\PrologAnonymVarStyle%
      \else
        \let\lst@thestyle\PrologVarStyle%
      \fi
    \else
      % Check whether the identifier starts by a capital letter.
      \currentchar@prolog=65
      \loop
        \expandafter\ifnum\expandafter`\@testChar@prolog=\currentchar@prolog%
          \let\lst@thestyle\PrologVarStyle%
          \let\iterate\relax
        \fi
        \advance \currentchar@prolog by 1
        \unless\ifnum\currentchar@prolog>90
      \repeat
    \fi
  \fi
}
\newcommand\splitfirstchar@prolog{}
\def\splitfirstchar@prolog#1{\@splitfirstchar@prolog#1\relax}
\newcommand\@splitfirstchar@prolog{}
\def\@splitfirstchar@prolog#1#2\relax{\def\@testChar@prolog{#1}}

% helper macro for () delimiters
\def\beginlstdelim#1#2%
{%
  \def\endlstdelim{\PrologOtherStyle #2\egroup}%
  {\PrologOtherStyle #1}%
  \global\predicate@prolog@false%
  \withinparens@prolog@true%
  \bgroup\aftergroup\endlstdelim%
}

% language name
\newcommand\lang@prolog{Prolog-pretty}
% ``normalised'' language name
\expandafter\lst@NormedDef\expandafter\normlang@prolog%
  \expandafter{\lang@prolog}

% language definition
\expandafter\expandafter\expandafter\lstdefinelanguage\expandafter%
{\lang@prolog}
{%
  language            = Prolog,
  keywords            = {},      % reset all preset keywords
  showstringspaces    = false,
  alsoletter          = (,
  alsoother           = @$,
  moredelim           = **[is][\beginlstdelim{(}{)}]{(}{)},
  MoreSelectCharTable =
    \lst@DefSaveDef{`(}\opparen@prolog{\global\predicate@prolog@true\opparen@prolog},
}

% Hooking into listings to test each ``identifier''
\newcommand\@ddedToOutput@prolog\relax
\lst@AddToHook{Output}{\@ddedToOutput@prolog}

\lst@AddToHook{PreInit}
{%
  \ifx\lst@language\normlang@prolog%
    \let\@ddedToOutput@prolog\@testChar@prolog%
  \fi
}

\lst@AddToHook{DeInit}{\renewcommand\@ddedToOutput@prolog{}}

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


% --- definition of a custom style similar to that of Pygments ---
% custom colors
\definecolor{PrologPredicate}{RGB}{000,031,255}
\definecolor{PrologVar}      {RGB}{024,021,125}
\definecolor{PrologAnonymVar}{RGB}{000,127,000}
\definecolor{PrologAtom}     {RGB}{186,032,032}
\definecolor{PrologComment}  {RGB}{063,128,127}
\definecolor{PrologOther}    {RGB}{000,000,000}

% redefinition of user macros for Prolog style
\renewcommand\PrologPredicateStyle{\color{PrologPredicate}}
\renewcommand\PrologVarStyle{\color{PrologVar}}
\renewcommand\PrologAnonymVarStyle{\color{PrologAnonymVar}}
\renewcommand\PrologAtomStyle{\color{PrologAtom}}
\renewcommand\PrologCommentStyle{\itshape\color{PrologComment}}
\renewcommand\PrologOtherStyle{\color{PrologOther}}

% custom style definition 
\lstdefinestyle{Prolog-pygsty}
{
  language     = Prolog-pretty,
  upquote      = true,
  stringstyle  = \PrologAtomStyle,
  commentstyle = \PrologCommentStyle,
  literate     =
    {:-}{{\PrologOtherStyle :-}}2
    {,}{{\PrologOtherStyle ,}}1
    {.}{{\PrologOtherStyle .}}1
}

% global settings
\lstset
{
  captionpos = below,
  frame      = single,
  columns    = fullflexible,
  basicstyle = \ttfamily,
}

% write some sample code to an external file
\begin{filecontents*}{sample.pl}
somePredicate(_, B) :-
    arbitraryPredicate(A, _variable, 1, 2),
    predicateWithAtom(someAtom),
    anotherPredicate(B, someAtom, myPredicate(A, _)),
    findall(X, ('testString'(X), myPredicate(A, X)), L1),
    member(A, L1),
    !.
    /*
    block comment: blah blah blah
    */
    % to-end-of-line comment: blah blah blah
\end{filecontents*}


\begin{document}

\section{With \textsf{minted} / Pygments}
%\begin{listing}
%\inputminted[frame=single]{prolog}{sample.pl}
%\caption{Sample Prolog code}
%\end{listing}

\section{With \textsf{Listings}}
\setcounter{lstlisting}{1}
\lstinputlisting[
  style      = Prolog-pygsty,
  caption    = {Using my custom \textsf{listings} style},
]{sample.pl}

\lstinputlisting[
  language = Prolog,
  caption  = {Using \textsf{listings}' default settings for Prolog},
]{sample.pl}

\end{document}
17
  • 1
    Nice, but now atoms are not distinguished from variables... any id starting with capital letter is a variable, any id starting with lowercase letter is an atom (a constant), I guess the OP would like to differentiate between them...
    – Bordaigorl
    Feb 19, 2014 at 11:48
  • 1
    @Jubobs wow the answer you linked is really impressive! It would be nice if this mechanism could be integrated into the package since it is a quite common thing one might need (think of Haskell, Erlang...)
    – Bordaigorl
    Feb 19, 2014 at 13:40
  • 1
    @Jubobs very nice indeed! It is also very useful to see how one could hook into listings internals to modify its behaviour. I wanted to do something similar for Erlang's syntax module:call(...) highlighting module names differently and now I know where to start. Thanks a lot!
    – Bordaigorl
    Apr 14, 2014 at 7:45
  • 1
    @Jubobs Oh wow that's really great! Thanks for your answer! And about your remark on highlighting variables, it also puzzled me why Pygments highlights a variable starting with an underscore (anonymous variable) as an atom (with the exception for the underscore character itself). So I think your version is better on that part. However, it might be even better if you differentiate variables from anonymous variables, for example with dark green highlights for the entire anonymous variable. Thanks anyway, I didn't expect you would get back to this question. I have accepted your answer.
    – PJvG
    Apr 14, 2014 at 13:02
  • 1
    @PJvG According to this, variables starting by an underscore but whose names consist in more than that underscore are not anonymous variables. Wouldn't it make more sense to highlight only "free-standing" _ in dark green and to highlight variables such as _variable just like any other variable?
    – jub0bs
    Apr 14, 2014 at 14:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .