Pregunta Enseñé a ghci a compilar mis publicaciones de StackOverflow. ¿Puedo hacerlo más impermeable?


Preprocesador de diseño de desbordamiento de pila Haskell

module StackOverflow where  -- yes, the source of this post compiles as is

Saltar a Qué hacer para que funcione si quieres jugar con esto primero (1/2 camino hacia abajo).
Saltar a Lo que me gustaría si calculo un poco y solo quieres saber qué ayuda estoy buscando.

TLDR Resumen de la pregunta:

  1. ¿Puedo obtener ghci para agregar la finalización del nombre de archivo al :so comando que definí en mi ghci.conf?
  2. ¿Podría de alguna manera definir un comando ghci que devuelva código para compilación en lugar de devolver un comando ghci, o ghci en su lugar tiene una mejor manera de conectar el código de Haskell como preprocesador específico de extensión de archivo, por lo :l trabajaría para .hs y .lhs archivos como de costumbre, pero use mi preprocesador manuscrito para .so archivos?

Fondo:

Haskell apoya la programación alfabetizada en .lhs archivos fuente, de dos maneras:

  • Estilo LaTeX \begin{code} y \end{code}.
  • Pistas de aves: el código comienza con >, cualquier otra cosa es un comentario.
    Debe haber una línea en blanco entre el código y los comentarios (para detener el uso indebido accidental trivial de >)

¿Las reglas de seguimiento de Bird no suenan de forma similar a los bloques de código de StackOverflow?

Referencias 1.  El manual .ghci 2.  GHCi haskellwiki 3.  Neil Mitchell bloguea sobre :{ y :} en .ghci 

El preprocesador

Me gusta escribir respuestas de SO en un editor de texto, y me gusta hacer una publicación que consta de código que funciona, pero terminan con bloques de comentarios o >s que tengo que editar antes de publicar, que es menos divertido.

Entonces, me escribí un pre-procesador.

  • Si he pegado algunas cosas ghci como un bloque de código, generalmente comienza con * o :.
  • Si la línea está completamente en blanco, no quiero que se trate como código, porque de lo contrario Obtengo errores accidentales de code-next-to-comment porque no puedo ver los 4 espacios accidentalmente dejado en una línea que de otro modo estaría en blanco.
  • Si la línea anterior no era un código, esta línea tampoco debería ser, por lo que podemos lidiar con StackOverflow uso de sangría para propósitos de diseño de texto fuera de bloques de código.

Al principio no sabemos (no sé) si esta línea es código o texto:

dunnoNow :: [String] -> [String]
dunnoNow [] = []
dunnoNow (line:lines)
  | all (==' ') line = line:dunnoNow lines     -- next line could be either
  | otherwise = let (first4,therest) = splitAt 4 line in 
     if first4 /="    "                 -- 
        || null therest                 -- so the next line won't ever crash
        || head therest `elem` "*:"     -- special chars that don't start lines of code.
     then line:knowNow False lines      -- this isn't code, so the next line isn't either
     else ('>':line):knowNow True lines -- this is code, add > and the next line has to be too

pero si lo sabemos, debemos mantenernos en el mismo modo hasta que lleguemos a una línea en blanco:

knowNow :: Bool -> [String] -> [String]
knowNow _ [] = []
knowNow itsCode (line:lines) 
  | all (==' ') line = line:dunnoNow lines
  | otherwise = (if itsCode then '>':line else line):knowNow itsCode lines

Hacer que ghci use el preprocesador

Ahora podemos tomar un nombre de módulo, preprocesar ese archivo y decirle a ghci que lo cargue:

loadso :: String -> IO String
loadso fn = fmap (unlines.dunnoNow.lines) (readFile $ fn++".so") -- so2bird each line
        >>= writeFile (fn++"_so.lhs")                     -- write to a new file
        >> return (":def! rso (\\_ -> return \":so "++ fn ++"\")\n:load "++fn++"_so.lhs")

He usado la redefinición silenciosa de :rso comando debido a mis intentos anteriores de usar let currentStackOverflowFile = .... o currentStackOverflowFile <- return ... no me llevo a ninguna parte

Qué hacer para que funcione

Ahora necesito ponerlo en mi ghci.conf archivo, es decir, en appdata/ghc/ghci.conf  según el instrucciones

:{
let dunnoNow [] = []
    dunnoNow (line:lines)
      | all (==' ') line = line:dunnoNow lines     -- next line could be either
      | otherwise = let (first4,therest) = splitAt 4 line in 
         if first4 /="    "                 -- 
            || null therest                 -- so the next line won't ever crash
            || head therest `elem` "*:"     -- special chars that don't start lines of code.
         then line:knowNow False lines      -- this isn't code, so the next line isn't either
         else ('>':line):knowNow True lines -- this is code, add > and the next line has to be too
    knowNow _ [] = []
    knowNow itsCode (line:lines) 
      | all (==' ') line = line:dunnoNow lines
      | otherwise = (if itsCode then '>':line else line):knowNow itsCode lines
    loadso fn = fmap (unlines.dunnoNow.lines) (readFile $ fn++".so") -- convert each line
        >>= writeFile (fn++"_so.lhs")                            -- write to a new file
        >> return (":def! rso (\\_ -> return \":so "++ fn ++"\")\n:load "++fn++"_so.lhs")
:}
:def so loadso

Uso

Ahora puedo guardar esta publicación completa en LiterateSo.so y hacer cosas encantadoras en ghci como

*Prelude> :so StackOverflow
[1 of 1] Compiling StackOverflow    ( StackOverflow_so.lhs, interpreted )
Ok, modules loaded: StackOverflow.

*StackOverflow> :rso
[1 of 1] Compiling StackOverflow    ( StackOverflow_so.lhs, interpreted )
Ok, modules loaded: StackOverflow.

*StackOverflow>

¡Hurra!

Lo que me gustaría:

Preferiría habilitar ghci para soportar esto más directamente. Sería bueno deshacerse de la intermedia .lhsarchivo.

Además, parece que ghci completa el nombre del archivo comenzando en la subcadena más corta de :load eso determina en realidad estás haciendo load, entonces usando :lso en lugar de :so no lo engaña

(Me gustaría no gustaría reescribir mi código en C. También lo haría no gustaría recompilar ghci de la fuente)

Recordatorio de la pregunta TLDR:

  1. ¿Puedo obtener ghci para agregar la finalización del nombre de archivo al :so comando que definí en mi ghci.conf?
  2. ¿Podría de alguna manera definir un comando ghci que devuelva código para compilación en lugar de devolver un comando ghci, o ghci en su lugar tiene una mejor manera de conectar el código de Haskell como preprocesador específico de extensión de archivo, por lo :l trabajaría para .hs y .lhs archivos como de costumbre, pero use mi preprocesador manuscrito para .so archivos?

32
2017-10-01 01:55


origen


Respuestas:


Yo trataría de hacer un preprocesador independiente que ejecute el código de preprocesamiento SO o el preprocesador literario estándar, dependiendo de la extensión del archivo. Entonces solo usa :set -pgmL SO-preprocessor en ghci.conf.

Para el preprocesador literario estándar, ejecuta el unlit programa o uso Distribution.Simple.PreProcess.Unlit.

De esta manera, :load y la finalización del nombre de archivo simplemente funciona normalmente.

GHCI pasa 4 argumentos al preprocesador, en orden: -h, la etiqueta, el nombre del archivo de origen y el nombre del archivo de destino. El preprocesador debe leer la fuente y escribir en el destino. La etiqueta se usa para imprimir #line pragmas. Puede ignorarlo si no altera el recuento de líneas de la fuente (es decir, reemplace las líneas de "comentarios" con -- comentarios o líneas en blanco).


9
2017-10-01 10:20