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.
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}
Here is a way; I vastly prefer this to setting basewidth
, because it doesn't box each character and doesn't need to guess whether the monospaced font uses half em character widths.
\documentclass[a4paper]{report}
\usepackage{listings}
\begin{document}
\lstset{
basicstyle=\ttfamily,
columns=fullflexible,
keepspaces=true,
}
\verb|basicstyle=\ttfamily, columns=fullflexible, keepspaces=true|
\begin{lstlisting}
: paxos.learn ( addr n v -- Ethernet packet )
2 paxos.pack32 ( addr n v -- addr payload )
paxos.eth_type.learn ( addr payload -- addr payload ethtype )
swap paxos.eth_packet ; ( addr payload ethtype -- ethernet_packet )
\end{lstlisting}
This is verbatim (and what I want):
\begin{verbatim}
: paxos.learn ( addr n v -- Ethernet packet )
2 paxos.pack32 ( addr n v -- addr payload )
paxos.eth_type.learn ( addr payload -- addr payload ethtype )
swap paxos.eth_packet ; ( addr payload ethtype -- ethernet_packet )
\end{verbatim}
\end{document}
Best Answer
I was able to replicate it pretty closely using
listings
.Ultimately, the part that made it tricky is that you have redundant commands in your
\lstset
(and presumably also where you defined keywords for Javascript): for example, the colors you provided for different keyword styles were overridden by others that were already defined.I started with a
listings
language definition for Javascript, which I got from this TeX.sx post; however, we'll be modifying the colors, so I removed the definitions we won't use.Then, we define the style, which comes from setting the colors (and italics for comments) in
keywordstyle
,commentstyle
, etc., along with a few spacing adjustments. I always alphabetize my\lstset
call so that I can quickly figure out whether I've already added a command rather than duplicating it.Finally,
minted
highlights the numbers in gray. This TeX.sx answer shows us how to do it inlistings
:Here's my whole working example.