
Gostaria de saber se é possível usar o listings
pacote para destacar expressões regulares de forma que pareçam um objeto string normal. Nota: A sintaxe de uma regexp não é livre de contexto, portanto não posso esperar uma solução que reconheça 100% dos casos. De qualquer forma, por exemplo, em Ruby eu gostaria de escrever
(/[a-z]+/)
e diga listings
para reconhecer o regexp /[a-z]+/
usando o parêntese esquerdo (
como ponto de ancoragem (para separá-lo da divisão aritmética):
\lstset{moredelim=[s][\color{red}]{(/}{/}}
Mas isso torna o parêntese esquerdo colorido - não apenas o regexp em si. Para entender melhor o analisador, modifiquei a instrução para:
\lstset{moredelim=[s][\color{red}\textcolor{black}{:macro:}]{(/}{/}}
Para minha surpresa, entretanto, ":macro:" é aplicado acadados tokens: (/
, [
, a
, -
, z
, ]
, e /
, localizado por moredelim
. Eu esperava erroneamente que ":macro:" teria sido aplicado aointeiroexpressão correspondente (/[a-z]+/
. Qual, aliás, é a situação se eu mudar a afirmação para sua versão com estrela dupla:
\lstset{moredelim=**[s][\color{red}\textcolor{black}{:macro:}]{(/}{/}}
Mas, neste caso, o regexp será analisado listings
- colorindo palavras-chave dentro da expressão. Não é isso que eu quero. Quero usar (
apenas como âncora para encontrar a regexp, depois quero tratar a âncora separadamente da própria regexp (dando uma cor diferente).
Para ser mais específico: quero saber se posso pegar apenas o primeiro token em moredelim
- contendo o (/
- e tratá-lo separadamente dos outros tokens.
Um MWE para examinar o problema é fornecido aqui:
\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}
Responder1
Esta resposta talvez não seja mais relevante para quem pergunta. Mas como esse problema foi muito mais difícil de resolver do que deveria ter sido e pode ser relevante para outras pessoas, postarei a resposta de qualquer maneira.
Conforme mencionado na pergunta e nos comentários, a moredelim=**[s]
variante trata todo o texto delimitado como uma unidade/grupo, o que permite a inserção de texto arbitrário no início do grupo delimitado e também no final (até \aftergroup
). O problema é que outros estilos também são aplicados ao texto entre os delimitadores. Por outro lado, moredelim=[s]
não aplica outros estilos, mas por sua vez aplica o estilo de grupo delimitador a cada bloco de caracteres da mesma classe (letra, outros etc.). Isso evita que o texto seja inserido apenas antes e depois dos delimitadores.
Não parece haver uma maneira fácil de alcançar o efeito desejado, então temos que nos concentrar em alguns aspectos internos aqui. As macros \lst@DelimOpen
e \lst@DelimClose
controlam as ações para quando um novo par de delimitadores for encontrado na listagem. Então, nós os redefinimos para instalar dois ganchos \@delim@open@hook
e \@delim@close@hook
. Nestes podemos verificar qual estilo está ativo no momento (comparando com \lst@currstyle
) e com base nisso escolher as ações adequadas. Com esta abordagem, vários delimitadores sofisticados podem ser usados em paralelo. Observe que \@delim@close@hook
é executadoanteso último pedaço de caracteres é gerado, então novamente precisamos usar o \aftergroup
truque para mover a ação final após os últimos caracteres do grupo delimitado.
A implementação final usa moredelim=[is][\regexstyle]{(/}{/)}
para definir o estilo das regexes, onde o i
campo remove os delimitadores originais da saída. \regexstyle
é o estilo real aplicado a todo o texto interno da regex. Certifique-se de usar uma macro wrapper com nome inequívoco aqui, caso contrário, o teste \lst@currstyle
poderá levar a resultados errados. \regexstyle@start
e \regexstyle@end
são as macros que inserem o código no lugar do delimitador inicial e final original, respectivamente.
Aqui está o exemplo 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}