Como posso destacar uma sessão GAP interativa assim?

Como posso destacar uma sessão GAP interativa assim?

Suponha que eu queira compor uma sessão de tela em alguma linguagem de programação (GAP no meu caso, mas também poderia ser Python, BASH ou qualquer outra coisa com um prompt de leitura interativo). Então, na minha tela, posso ter algo assim:

gap> for i in [1..10] do
>   Print(i, ":");
> od;
1:2:3:4:5:6:7:8:9:10:
gap> 2^10;
1024
gap> 1/0;
Error, Rational operations: <divisor> must not be zero
not in any function at line 5 of *stdin*
you can replace <divisor> via 'return <divisor>;'
brk> 2^10;
1024
brk>

Ou seja, há um prompt de comando gap>, após o qual o usuário insere uma entrada multilinha. Depois disso, vem uma linha de saída e, em seguida, outro prompt gap>. Isso se repete até que um comando acione um erro, momento em que o prompt muda para brk>.

Eu gostaria de usar olistingspacote (ou outra coisa) para

  1. pinte os prompts gap>, >e brk>; digamos, azul e vermelho;
  2. destaque palavras-chave como for, doe od; digamos, tornando-os ousados;
  3. masapenasdestaque as palavras-chave nas linhas que começam com um prompt, portanto, não dentro da saída dos meus comandos.

O último ponto é o que está me causando problemas. Eu não consegui descobrir uma maneira de fazer isso. O resultado costuma ser bastante feio quando palavras como and, note insão destacadas como palavras-chave, por exemplo, em uma mensagem de erro.

Perguntas semelhantes foram feitas antes, mas daquelas que consegui encontrar, nenhuma perguntou exatamente isso, resp. recebi uma resposta que faz o que eu quero.

Para registro, aqui está a definição de linguagem que estou usando agora:

\lstdefinelinguagem{GAP}{%
    morekeywords=[2]{e,quebrar,continuar,fazer,elif,else,end,fail,false,fi,for,%
        função, se, em, local, mod, não, od, ou, rec, repetir, retornar, então, verdadeiro,%
        até, enquanto},%
    moredelim=[s][\color{azul}]{gap}{>},%
    moredelim=[s][\cor{vermelho}]{brk}{>},%
    %moredelim=*[l][\color{azul}]{gap>},%
    %moredelim=*[l][\color{vermelho}]{brk>},%
    sensível=verdadeiro,%
    mais comentário=[l]\#,%
    maisstring=[b]',%
    maisstring=[b]",%
    }%

PS: Para ilustrar o que quero, considere esta imagem mostrando como poderia ser meu exemplo acima (além do que descrevi acima, também configurei todas as entradas do usuário em itálico):

insira a descrição da imagem aqui

Responder1

Esta listingsresposta está com cerca de um ano de atraso, mas acredito que atende a todos os seus requisitos.

Editar: corrigiu um bug relacionado ao prompt de "palavras-chave" que ocorria no meio de uma linha.

Saída desejada

saída desejada

Resultado obtido

saída obtida

Código

\documentclass[a4paper]{article}
\usepackage{xcolor}
\usepackage{textcomp}
\usepackage{listings}

\makeatletter

% switch to remember whether a prompt was found on the current line
\newif\ifprompt@GAP@
% switch to flag whether a prompt `keyword' is an bona fide GAP prompt
\newif\iftoolateforprompt@GAP@

% style of GAP keywords
\definecolor{GAPkeywords}{RGB}{077,145,021}
\newcommand\GAPkeywordstyle{\color{GAPkeywords}}

% font shape of GAP input lines 
\newcommand\GAPinputfshape\slshape

\lstdefinelanguage{GAP}
{%
  basicstyle =\ttfamily,
  alsoletter=>,
  morekeywords=[2]{%
    and,break,continue,do,elif,else,end,fail,false,fi,for,function,if,in,%
    local,mod,not,od,or,rec,repeat,return,then,true,until,while,
  },%
  keywordstyle=[2]\Process@GAP@keywords,
  morekeywords=[3]{gap>,>},
  keywordstyle=[3]\Process@GAP@prompt{blue},
  morekeywords=[4]{brk>},
  keywordstyle=[4]\Process@GAP@prompt{red},
  sensitive=true,%
  upquote=true,% <--- for straight single quotes
  showstringspaces=false,
  morecomment=[l]\#,
  morestring=[b]',
  morestring=[b]",
}%

% only highlight keywords if a prompt has occured on the current line
\newcommand\Process@GAP@keywords{\ifprompt@GAP@\GAPkeywordstyle\fi}

\newcommand\Process@GAP@prompt[1]
{%
  \iftoolateforprompt@GAP@%
  \else%
      \color{#1}\upshape% customise the style of your > and gap> prompts here
      \global\prompt@GAP@true%
      \aftergroup\GAPinputfshape% we trigger slanted shape after > or gap>
  \fi%
}

% Hook into InitVarsEOL (some hook executed right after an EOL) to:
% - reset the \ifprompt@GAP@ and \iftoolateforprompt@GAP@ switches
% - reset the font shape to \upshape
\lst@AddToHook{InitVarsEOL}%
{%
  \global\prompt@GAP@false%
  \global\toolateforprompt@GAP@false%
  \upshape%
}

% Hook into PostOutput to signal that a GAP prompt
% can no occur on the current line
\lst@AddToHook{PostOutput}{\global\toolateforprompt@GAP@true}

\makeatother

\begin{document}
\begin{lstlisting}[language=GAP]
gap> for i in [1..10] do
>   Print(i, ":");
> od;
1:2:3:4:5:6:7:8:9:10:
gap> 2^10;
1024
gap> 1/0;
Error, Rational operations: <divisor> must not be zero
not in any function at line 5 of *stdin*
you can replace <divisor> via 'return <divisor>;'
brk> 2^10;
1024
brk>
\end{lstlisting}
\end{document}

Responder2

A única desvantagem é que processo palavras inteiras, não subpalavras. Portanto, od;é uma palavra separada de od. Isso pode ser superado, mas não neste MWE. Copiei a saída da sessão bruta (não formatada) para o arquivosessão.ine parti daí.

\documentclass[12pt]{article}
\makeatletter%
\let\protectededef\protected@edef
\makeatother%

\parindent 0in
\renewcommand{\encodingdefault}{T1}
\usepackage{color}
\usepackage{readarray}
\usepackage{verbatimbox}
\usepackage{ifthen}
\catcode`^=12
\definecolor{darkgreen}{rgb}{0,0.5,0}
\newcounter{rowindex}\newcounter{wordindex}%

\newcommand\displaysource[1]{%
  \sffamily%
  \readdef{#1}{\x}%
  \setcounter{rowindex}{0}%
  \whiledo{\value{rowindex} < \nrecords}{%
    \addtocounter{rowindex}{1}%
    \getargsC{\csname record\roman{rowindex}\endcsname}%
    \ifthenelse{\equal{\argi}{>}  \OR%
                \equal{\argi}{gap>}  \OR%
                \equal{\argi}{brk>}}%
                {\def\userin{\itshape}}{\def\userin{\upshape}}%
    \setcounter{wordindex}{0}%
    \whiledo{\value{wordindex} < \narg}{%
      \addtocounter{wordindex}{1}%
      \protectededef\thisword{\csname arg\roman{wordindex}\endcsname}%
      \ifthenelse{\equal{\thisword}{gap>}  \OR%
                  \equal{\thisword}{>}}%
        {%
          \upshape\textcolor{blue}{\thisword~}%
        }{%
          \ifthenelse{\equal{\thisword}{brk>}}%
            {%
              \textcolor{red}{brk>~}%
            }{%
              \userin%
              \ifthenelse{\equal{\thisword}{for}  \OR%
                          \equal{\thisword}{in}  \OR%
                          \equal{\thisword}{do}  \OR%
                          \equal{\thisword}{od}  \OR%
                          \equal{\thisword}{od;}}%
              {%
                \ifthenelse{\equal{\userin}{\itshape}}%
                  {%
                    \textcolor{darkgreen}{\thisword~}%
                  }{%
                    \thisword~%
                  }%
              }{%
                \thisword~%
              }
            }%
        }%
    }%
    \\%
  }%
  \rmfamily%
}
\begin{document}

\displaysource{session.in}

\end{document}

insira a descrição da imagem aqui

informação relacionada