有了该listings
软件包,我想将整数从字符串、注释和关键字中着色。为数字着色的问题有 到过 问 一些 次。有些答案处理注释、字符串或裸词中出现的数字,但当关键字包含数字时,没有给出解决方案。这很烦人,因为 C 和 C++ 有很多类型包含数字,例如uint32_t
。
这是一个小例子。
\documentclass{standalone}
\usepackage{xcolor}
\usepackage{listings}
\definecolor{darkgreen}{rgb}{0.18,0.54,0.34}
\definecolor{maroon}{rgb}{0.64,0.16,0.16}
\definecolor{darkpink}{rgb}{0.75,0.25,0.5}
\lstdefinestyle{myc++}{
language=[ISO]C++,
keywordstyle=\color{darkgreen}\bfseries,
commentstyle=\color{blue}\textit,
stringstyle=\color{darkpink}\ttfamily,
%
literate=*
{0}{{{\textcolor{red}0}}}1
{1}{{{\textcolor{red}1}}}1
{2}{{{\textcolor{red}2}}}1
{3}{{{\textcolor{red}3}}}1
{4}{{{\textcolor{red}4}}}1
{5}{{{\textcolor{red}5}}}1
{6}{{{\textcolor{red}6}}}1
{7}{{{\textcolor{red}7}}}1
{8}{{{\textcolor{red}8}}}1
{9}{{{\textcolor{red}9}}}1,
escapeinside={|}{|},
%
morekeywords={int32_t}
}
\begin{document}
\begin{lstlisting}[style=myc++]
// In comment: 42
int32_t in_keyword = 80085;
char *in_identifier|2| = "And in string 1337\n";
\end{lstlisting}
\end{document}
结果如下:
literate
避免对注释或字符串内的数字进行着色的星号。escapeinside
避免对有特殊标记的裸字内的数字进行着色。但我还没有找到如何int32_t
对关键字进行着色的方法。
答案1
我知道这个问题已经存在三年了,但我目前正在处理同样的问题。我搜索了互联网,相同或密切相关的问题已经被问了很多次,但没有 100% 让我满意的答案,因为我正在寻找一个像这里所期望的解决方案。因为我找到了一个虽然不完美但相当不错的解决方案,我想分享它以防其他人在这里遇到。但总的来说,我同意 @Jubobs 的观点,在列表中这样做真的很麻烦。
所以基本上我所做的就是使用 basicstyle 属性将所有内容涂成我想要的数字颜色。然后我像往常一样使用 keywordstyle、commentsytle 和 stringstlye 属性为关键字、注释和字符串着色。现在的诀窍是使用 identifierstyle 为所有变量名称涂上实际的基本颜色。关于基本样式剩下的就是所有数字以及所有运算符、括号等,例如 (+ - * ... 之后,您显然只需使用基本颜色为运算符、括号等着色即可获得结果。这可以通过 literate 属性完成。
我将从我现在创建的 Julia 样式中添加几行代码来说明我的意思。请注意,在我的例子中,我想用与字符串相同的颜色来为数字着色,但显然您可以简单地为该工作创建一个新的颜色。
% julia language definition
\lstdefinelanguage{julia}{%
morekeywords=[1]{end,export,finally},%
morekeywords=[2]{true,false,ARGS},%
morekeywords=[3]{ANY,AbstractArray,AbstractChannel},%
morecomment=[l]{\#},%
morecomment=[n]{\#=}{=\#},%
morestring=[b]{"},%
morestring=[m]{'},%
}[keywords,comments,strings]
% defining the colors for
\definecolor{jlbase}{HTML}{444444} % julia's base color
\definecolor{jlkeyword}{HTML}{444444} % julia's keywords
\definecolor{jlliteral}{HTML}{78A960} % julia's literals
\definecolor{jlbuiltin}{HTML}{397300} % julia's built-ins
\definecolor{jlcomment}{HTML}{888888} % julia's comments
\definecolor{jlstring}{HTML}{880000} % julia's strings
% basic font
\def\lstbasicfont{\color{jlstring}\fontfamily{pcr}\selectfont\scriptsize}
% defining the styles for
\lstset{keywordstyle={[1]\color{jlkeyword}\bfseries}} % julia's keywords
\lstset{keywordstyle={[2]\color{jlliteral}}} % julia's literals
\lstset{keywordstyle={[3]\color{jlbuiltin}}} % julia's built-ins
\lstset{commentstyle={\color{jlcomment}}} % julia's comments
\lstset{stringstyle={\color{jlstring}}} % julia's strings
\lstset{identifierstyle={\color{jlbase}}} % variables
% coloring the operators with the basecolor
% and . in floating point numbers with the desired number color
\lstset{extendedchars=false}
\lstset{literate=*
{\\}{{{\color{jlbase}\textbackslash{}}}}{1} {\{}{{{\color{jlbase}\{}}}{1} {\}}{{{\color{jlbase}\}}}}{1}
{!}{{{\color{jlbase}!}}}{1} {\%}{{{\color{jlbase}\%}}}{1} {&}{{{\color{jlbase}\&}}}{1}
{(}{{{\color{jlbase}(}}}{1} {)}{{{\color{jlbase})}}}{1} {*}{{{\color{jlbase}*}}}{1}
{+}{{{\color{jlbase}+}}}{1} {,}{{{\color{jlbase},}}}{1} {-}{{{\color{jlbase}-}}}{1}
{.}{{{\color{jlbase}.}}}{1} {/}{{{\color{jlbase}/}}}{1} {:}{{{\color{jlbase}:}}}{1}
{;}{{{\color{jlbase};}}}{1} {<}{{{\color{jlbase}<}}}{1} {=}{{{\color{jlbase}=}}}{1}
{>}{{{\color{jlbase}>}}}{1} {?}{{{\color{jlbase}?}}}{1} {[}{{{\color{jlbase}[}}}{1}
{]}{{{\color{jlbase}]}}}{1} {^}{{{\color{jlbase}\^{}}}}{1} {|}{{{\color{jlbase}|}}}{1}
{~}{{{\color{jlbase}\textasciitilde{}}}}{1}
{.0}{{{\color{jlstring}.0}}}{2} {.1}{{{\color{jlstring}.1}}}{2} {.2}{{{\color{jlstring}.2}}}{2}
{.3}{{{\color{jlstring}.3}}}{2} {.4}{{{\color{jlstring}.4}}}{2} {.5}{{{\color{jlstring}.5}}}{2}
{.6}{{{\color{jlstring}.6}}}{2} {.7}{{{\color{jlstring}.7}}}{2} {.8}{{{\color{jlstring}.8}}}{2}
{.9}{{{\color{jlstring}.9}}}{2}}
% activating the julia style
\lstset{language=julia}
好的,我猜这就是你所认为的 MWE:
\documentclass{standalone}
\usepackage{xcolor}
\usepackage{listings}
\definecolor{darkgreen}{rgb}{0.18,0.54,0.34}
\definecolor{maroon}{rgb}{0.64,0.16,0.16}
\definecolor{darkpink}{rgb}{0.75,0.25,0.5}
\lstdefinestyle{myc++}{
language=[ISO]C++,
keywordstyle=\color{darkgreen}\bfseries,
commentstyle=\color{blue}\textit,
stringstyle=\color{darkpink}\ttfamily,
basicstyle={\color{red}},
identifierstyle={\color{black}},
%
literate=*
{*}{{{\color{black}*}}}{1}
{=}{{{\color{black}=}}}{1}
{;}{{{\color{black};}}}{1}
{.7}{{{\color{red}.7}}}{2},%
%
morekeywords={int32_t}
}
\begin{document}
\begin{lstlisting}[style=myc++]
// In comment: 42
int32_t in_keyword = 80085.7;
char *in_identifier2 = "And in string 1337\n";
\end{lstlisting}
\end{document}
请注意,使用此方法您甚至不需要转义 in_identifier2 中的 2。
答案2
我认为,到目前为止,各个线程中提出的解决方案的一个缺点是它们严重依赖于该literate
功能,这非常具有侵入性,可能会破坏其他代码元素的处理。因此,这里有另一种方法,虽然还远远不够完美,但可以很好地在现有的语言和样式定义之上使用。
基本思想
首先介绍一下它如何listings
解析语法元素。当它尝试解析标识符时,它会寻找它认为是信字符,然后是其他几个信或数字s。所有这些都被合并成一个标识符标记,然后进行进一步处理,例如关键字检测。每当一个数字或者其他发现不属于标识符名称的字符,它由以下所有字符组成数字沙其他s 合并为一个序列,然后将其作为一组字符输出。
现在我们的想法是简单地加入到这个输出过程中,并识别出所有构成合法数字文字的字符序列。只要找到这样的数字,我们就应用一种特殊的数字样式,否则我们就保持输出原样。
实施说明
对于这种非标识符序列的特殊处理,listings
已经提供了一个名为的钩子OutputOther
,我们将使用它来预处理这些序列(存储在中\lst@token
)。
为了解析数字,我们需要一种方法,能够以不同于仅仅查找字符的方式处理至少减号-
,因为listings
这些是通过\lst@um-
宏内部处理的。相反,宏的文字副本由这个答案提供被使用。此外,连字符的输出listings
以一个宏作为前缀,该宏用于\lst@nolig
防止 TeX 在连续出现多个连字符时构建连字符。对于数字,连字符/减号只能出现在数字的开头,我们只需要测试序列的第一个标记\lst@nolig
并在必要时丢弃它。
其余的实现非常简单。为了便于定制,定义了两个新listings
选项parsenumbers=(true|false)
和numbersstyle={...}
(不要与已经存在的numberstyle
选项混淆!)。前者启用/禁用数字解析例程,后者设置要应用的样式。我们还会在解析数字之前检查是否允许模式更改(\lst@ifmode
为 false),这样数字样式就不会应用于非星号分隔符。
如下面的例子所示,这个解决方案在许多标准情况下工作得很好,但它至少在以下情况下失败了:
- 对于像 这样的序列
<identifier><plus/minus><integer>
,因为listings
在标识符后会中断解析,并将加号/减号和整数视为一个有符号数字。这是一个常见的词法分析问题。 - 对于科学计数法中的浮点文字,例如
0.12e3
,解析过程将被视为e
标识符,从而破坏整个数字组。我看不出有什么好的解决办法。作为一种解决方法,e
可以将其设为数字字符,但这会破坏以字母 开头的标识符/关键字的解析e
。 - 如果将数字用作
literate
字符,解析整个数字组也会失败。使用+
和-
作为识字字符在一定程度上是可行的。
带有示例文档的代码
\documentclass{article}
\usepackage{listings}
\usepackage{xcolor}
\makeatletter
%%% Copied from https://tex.stackexchange.com/a/500690/23765
% Some conditional tests
\def\@genericif#1{#1\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}
\def\@ifdigit#1{\@genericif{\ifnum1<1\noexpand#1\relax}}
\def\@ifempty#1{\@genericif{\if\relax\detokenize{#1}\relax}}
% The main parsing macros
\def\parse@num#1{%
\@ifempty{#1}%
{\parse@num@false}%
{\@genericif{\parsesign}%
{\parse@num@sign#1{}\@end}%
{\parse@num@dig#1{}\@end}%
}%
}
% Parse sign
\def\parse@num@sign#1#2\@end{%
\@genericif{\ifx\parse@num@minus#1}%
{\@ifempty{#2}{\parse@num@false}{\parse@num@dig#2\@end}}%
{\@genericif{\ifx\parse@num@plus#1}%
{\@ifempty{#2}{\parse@num@false}{\parse@num@dig#2\@end}}%
{\parse@num@dig#1#2\@end}%
}%
}
% Parse first digit
\def\parse@num@dig#1#2\@end{%
\@ifdigit{#1}%
{\@ifempty{#2}{\parse@num@true}{\parse@num@digs#2\@end}}%
{\parse@num@false}%
}
% Parse optional following digits
\def\parse@num@digs#1#2\@end{%
\@ifdigit{#1}{%
\@ifempty{#2}%
{\parse@num@true}%
{\parse@num@digs#2\@end}%
}{%
\@genericif{\parsefloat}{%
\@genericif{\ifx\parse@num@point#1}%
{\@ifempty{#2}{\parse@num@false}{\parse@num@decs#2\@end}}%
{\parse@num@false}%
}{\parse@num@false}%
}%
}
% Parse decimal places
\def\parse@num@decs#1#2\@end{%
\@ifdigit{#1}{%
\@ifempty{#2}%
{\parse@num@true}%
{\parse@num@decs#2\@end}%
}{\parse@num@false}%
}
% User interface
\newcommand\ifnumber[4][]{%
\begingroup
\let\parsesign=\iftrue
\let\parsefloat=\iftrue
\let\parse@num@minus=-%
\let\parse@num@plus=+%
\let\parse@num@point=.%
#1%
\def\parse@num@true{\endgroup#3}%
\def\parse@num@false{\endgroup#4}%
\parse@num{#2}%
}
%%% Additions to the listings package
\lst@Key{numbersstyle}{}{\def\lst@numbersstyle{#1}}
\lst@Key{parsenumbers}{false}[t]{\lstKV@SetIf{#1}\lst@ifparsenumbers}
\lst@AddToHook{OutputOther}{%
\lst@ifparsenumbers
% Only if mode changes are not prohibited
\lst@ifmode\else
\expandafter\@hook@ifnumber\the\lst@token\@end
{\let\lst@thestyle=\lst@numbersstyle}%
{}%
\fi
\fi
}
\def\@hook@ifnumber#1#2\@end{%
\@genericif{\ifx\lst@nolig#1}%
{\@hook@ifnumber@{#2}}%
{\@hook@ifnumber@{#1#2}}%
}
\def\@hook@ifnumber@{%
\ifnumber[\expandafter\let\expandafter\parse@num@minus\csname lst@um-\endcsname]%
}
\makeatother
%%% Example document
\lstset{
basicstyle = \ttfamily,
identifierstyle = \color{blue},
keywordstyle = \color{green!80!black},
keywords = {foo},
moredelim = [il][]{**},
moredelim = [l][\color{gray}]{/},
morestring = [d][\color{gray}]{"},
morestring = *[d][\color{gray}\itshape]{!},
morestring = **[d][\color{gray}\itshape]{?},
% Apply new number coloring routine
parsenumbers = true,
numbersstyle = {\color{magenta}}
}
\begin{document}
\begin{lstlisting}
**good cases:
foo foo123 _123
123 12.345 .123
-123 -12.345 -.123
+123 +12.345 +.123
+++1 1++2++3 ---1
1-2 12 - 34 1+2
**problematic cases:
i+1 0.1e12 i-1
**delimiters:
/ foo 123 line mode
" foo 123 " **delimited
! foo 123 ! **inner styles
? foo 123 ? **cumulative styles
\end{lstlisting}
\lstset{
literate = {+}{{\textcolor{green}{\char`\+}}}1
{-}{{\textcolor{red}{\char`\-}}}1
{num}{123}3
}
\begin{lstlisting}
**literate cases:
-123 -12.345 -.123
+123 +12.345 +.123
++1+ 1++2--3 --1-
1-2 12 - 34 1+2
num num+num -num
\end{lstlisting}
\end{document}