
Я хотел бы узнать, возможно ли использовать пакет listings
для подсветки регулярных выражений, чтобы они выглядели как обычный строковый объект. Примечание: синтаксис regexp не является контекстно-свободным, поэтому я не могу ожидать решения, которое распознает 100% случаев. В любом случае, например, в Ruby я хотел бы написать
(/[a-z]+/)
и сказать listings
распознавать регулярное выражение, /[a-z]+/
используя левую скобку (
в качестве опорной точки (чтобы отделить его от арифметического деления):
\lstset{moredelim=[s][\color{red}]{(/}{/}}
Но это делает левую скобку цветной, а не только само регулярное выражение. Чтобы лучше понять парсер, я изменил оператор следующим образом:
\lstset{moredelim=[s][\color{red}\textcolor{black}{:macro:}]{(/}{/}}
Однако, к моему удивлению, ":macro:" применяется ккаждыйиз токенов: (/
, [
, a
, -
, z
, ]
, и /
, обнаруженныхmoredelim
. Я ошибочно ожидал, что ":macro:" будет применен квесьсовпавшее выражение (/[a-z]+/
. Что, кстати, и произойдет, если я изменю выражение на его версию с двумя звездочками:
\lstset{moredelim=**[s][\color{red}\textcolor{black}{:macro:}]{(/}{/}}
Но в этом случае регулярное выражение будет проанализировано с помощью listings
- окрашивания ключевых слов внутри выражения. Это не то, что мне нужно. Я хочу использовать (
только как якорь для поиска регулярного выражения, после чего я хочу обрабатывать якорь отдельно от самого регулярного выражения (придавая ему другой цвет).
Если говорить конкретно: я хочу знать, могу ли я взять только первый токен moredelim
, содержащий (/
, и обработать его отдельно от других токенов.
MWE для изучения этого вопроса представлен здесь:
\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}
решение1
Этот ответ, возможно, больше не актуален для спрашивающего. Но поскольку эта проблема была гораздо сложнее в решении, чем должна была быть, и может быть актуальна для других, я все равно опубликую ответ.
Как упоминалось в вопросе и комментариях, moredelim=**[s]
вариант рассматривает весь текст с разделителями как одну единицу/группу, что позволяет вставлять произвольный текст в начале группы с разделителями, а также в конце (через \aftergroup
). Проблема в том, что другие стили применяются и к тексту между разделителями. С другой стороны, moredelim=[s]
не применяет другие стили, а в свою очередь применяет стиль группы разделителей к каждой группе символов того же класса (буква, другой и т. д.). Это предотвращает вставку текста только непосредственно перед и после разделителей.
Кажется, нет простого способа достичь желаемого эффекта, поэтому нам придется зацепиться за некоторые внутренние механизмы. Макросы \lst@DelimOpen
и \lst@DelimClose
управляют действиями, когда в листинге обнаруживается новая пара разделителей. Поэтому мы переопределяем их, чтобы установить два хука \@delim@open@hook
и \@delim@close@hook
. В них мы можем проверить, какой стиль в данный момент активен (сравнив с \lst@currstyle
), и на основе этого выбрать правильные действия. При таком подходе можно использовать несколько таких причудливых разделителей параллельно. Обратите внимание, что\@delim@close@hook
выполняетсядовыводится последняя часть символов, поэтому нам снова нужно использовать трюк, \aftergroup
чтобы переместить последнее действие после последних символов в разделенной группе.
Окончательная реализация использует moredelim=[is][\regexstyle]{(/}{/)}
для определения стиля для регулярных выражений, где i
поле удаляет исходные разделители из вывода. \regexstyle
— это фактический стиль, который применяется ко всему внутреннему тексту регулярного выражения. Обязательно используйте здесь однозначно названный макрос-оболочку, в противном случае проверка \lst@currstyle
может привести к неправильным результатам. \regexstyle@start
и \regexstyle@end
— это макросы, которые вставляют код вместо исходного начального и конечного разделителя соответственно.
Вот полный пример:
\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}