
Me gustaría saber si es posible utilizar el listings
paquete para resaltar expresiones regulares para que parezcan un objeto de cadena normal. Nota: La sintaxis de una expresión regular no está libre de contexto, por lo que no puedo esperar una solución que reconozca el 100% de los casos. De todos modos, por ejemplo, en Ruby me gustaría escribir
(/[a-z]+/)
y diga listings
que reconozca la expresión regular /[a-z]+/
usando el paréntesis izquierdo (
como punto de anclaje (para diferenciarlo de la división aritmética):
\lstset{moredelim=[s][\color{red}]{(/}{/}}
Pero esto hace que el paréntesis izquierdo sea coloreado, no solo la expresión regular en sí. Para comprender mejor el analizador modifiqué la declaración a:
\lstset{moredelim=[s][\color{red}\textcolor{black}{:macro:}]{(/}{/}}
Para mi sorpresa, sin embargo, ":macro:" se aplica acadade las fichas: (/
, [
, a
, -
, z
, ]
, y /
, detectadas pormoredelim
. Esperaba erróneamente que ":macro:" se hubiera aplicado alcompletoexpresión coincidente (/[a-z]+/
. Lo cual, por cierto, es la situación si cambio la declaración a su versión con doble estrella:
\lstset{moredelim=**[s][\color{red}\textcolor{black}{:macro:}]{(/}{/}}
Pero, en este caso, la expresión regular se analizará listings
coloreando palabras clave dentro de la expresión. Esto no es lo que quiero. Quiero usarlo (
solo como ancla para encontrar la expresión regular, luego quiero tratar el ancla por separado de la expresión regular (dándole un color diferente).
Para ser específico: quiero saber si puedo recoger solo el primer token ( moredelim
que contiene (/
) y tratarlo por separado de los demás tokens.
Aquí se proporciona un MWE para examinar el problema:
\documentclass{article}
\usepackage[english]{babel}
\usepackage{listings}
\usepackage{xcolor}
\lstnewenvironment{lstRuby}{
\lstset{
language={},
moredelim=[s][\color{red}\textcolor{black}{:macro:}]{(/}{/}
}
}{}
\begin{document}
\setlength{\parindent}{0pt}
\ttfamily
This is the code:
(/[a-z]+/)
This is what I want to achieve:
(\textcolor{red}{/[a-z]+/})
This is what I get using listings:
\begin{lstRuby}
(/[a-z]+/)
\end{lstRuby}
\end{document}
Respuesta1
Quizás esta respuesta ya no sea relevante para quien pregunta. Pero debido a que este problema fue bastante más difícil de resolver, realmente debería haberlo sido y puede ser relevante para otros, publicaré la respuesta de todos modos.
Como se menciona en la pregunta y los comentarios, la moredelim=**[s]
variante trata todo el texto delimitado como una unidad/grupo, lo que permite insertar texto arbitrario al inicio del grupo delimitado y también al final (hasta \aftergroup
). El problema con esto es que también se aplican otros estilos al texto entre los delimitadores. Por otro lado, moredelim=[s]
no aplica otros estilos sino que a su vez aplica el estilo del grupo delimitador a cada fragmento de caracteres de la misma clase (letra, otro, etc.). Esto evita que el texto se inserte sólo justo antes y después de los delimitadores.
No parece haber una manera fácil de lograr el efecto deseado, por lo que aquí tenemos que conectarnos con algunas partes internas. Las macros \lst@DelimOpen
y \lst@DelimClose
controlan las acciones cuando se encuentra un nuevo par de delimitadores en el listado. Entonces los redefinimos para instalar dos ganchos \@delim@open@hook
y \@delim@close@hook
. En estos podemos verificar qué estilo está actualmente activo (comparándolo con \lst@currstyle
) y, en función de eso, elegir las acciones adecuadas. Con este enfoque, se pueden utilizar varios delimitadores sofisticados en paralelo. Nota que \@delim@close@hook
se ejecutaantesse genera el último fragmento de caracteres, por lo que nuevamente necesitamos usar el \aftergroup
truco para mover la acción final después de los últimos caracteres en el grupo delimitado.
La implementación final utiliza moredelim=[is][\regexstyle]{(/}{/)}
para definir el estilo de las expresiones regulares, donde el i
campo elimina los delimitadores originales de la salida. \regexstyle
es el estilo real que se aplica a todo el texto interno de la expresión regular. Asegúrese de utilizar aquí una macro contenedora con un nombre inequívoco; de lo contrario, la prueba \lst@currstyle
puede generar resultados incorrectos. \regexstyle@start
y \regexstyle@end
son las macros que insertan el código en lugar del delimitador de inicio y fin original, respectivamente.
Aquí está el ejemplo completo:
\documentclass{article}
\usepackage{listings}
\usepackage{xcolor}
\lstnewenvironment{lstRuby}{%
\lstset{
language={},
moredelim=[is][\color{green}]{*}{*},
moredelim=[is][\regexstyle]{(/}{/)},
emphstyle=\color{blue},
emph={foo}
}%
}{}
\makeatletter
\let\orig@lst@DelimOpen=\lst@DelimOpen
\def\lst@DelimOpen#1#2#3#4#5#6\@empty{%
\orig@lst@DelimOpen{#1}{#2}{#3}{#4}{#5}{#6}\@empty
\@delim@open@hook
}
\let\orig@lst@DelimClose=\lst@DelimClose
\def\lst@DelimClose{%
\@delim@close@hook
\orig@lst@DelimClose
}
\def\@delim@open@hook{%
\def\@temp{\regexstyle}%
\ifx\lst@currstyle\@temp
\regexstyle@start
\fi
}
\def\@delim@close@hook{%
\def\@temp{\regexstyle}%
\ifx\lst@currstyle\@temp
\aftergroup\regexstyle@end
\fi
}
\def\regexstyle{\color{red}}
\def\regexstyle@start{({\regexstyle /}}
\def\regexstyle@end{{\regexstyle /})}
\makeatother
\begin{document}
\setlength{\parindent}{0pt}
\ttfamily
This is the code:
(/[a-z]+/)
This is what I want to achieve:
(\textcolor{red}{/[a-z]+/})
This is what I get using listings:
\begin{lstRuby}
text (/[a-z]+/) /[a-z]+/ *foo*
text foo (/foo|\/|foo/) *bar*
\end{lstRuby}
\end{document}