haskell - mac - Enseñé a ghci a compilar mis publicaciones de StackOverflow. ¿Puedo hacerlo más impermeable?
stack haskell (1)
Preprocesador de diseño de desbordamiento de pila Haskell
module StackOverflow where -- yes, the source of this post compiles as is
Pase a Qué hacer para que funcione si quiere jugar con esto primero (1/2 camino hacia abajo).
Diríjase a Lo que me gustaría si hago un poco de tiempo y solo quiere saber qué ayuda estoy buscando.
TLDR Resumen de la pregunta:
- ¿Puedo hacer que ghci agregue la finalización del nombre de archivo al comando
:so
queghci.conf
en mighci.conf
? - ¿De alguna manera podría definir un comando ghci que devuelve 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 Haskell como un preprocesador específico de extensión de archivo, entonces
:l
trabajaría para archivos.hs
y.lhs
como de costumbre, pero ¿usar mi preprocesador escrito a mano para archivos.so
?
Fondo:
Haskell soporta 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 consiste en un código que funciona, pero que termina con bloques de comentarios o que tengo que editar antes de publicar, lo cual 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 sea tratada como código, porque de lo contrario obtengo errores accidentales de código al lado de la línea de comentario porque no puedo ver los 4 espacios que dejé accidentalmente en una línea en blanco.
- Si la línea anterior no era un código, esta línea tampoco debería ser así, así que podemos hacer frente al uso de sangría de StackOverflow para fines de diseño de texto fuera de los 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 del comando :rso
debido a mis intentos previos de usar let currentStackOverflowFile = ....
o currentStackOverflowFile <- return ...
no me han llevado a ninguna parte.
Qué hacer para que funcione
Ahora tengo que ponerlo en mi archivo ghci.conf
, es decir, en appdata/ghc/ghci.conf
según las instructions
:{
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 del archivo .lhs
intermedio.
Además, parece que ghci completa el nombre del archivo comenzando en la subcadena más corta de :load
que determina que realmente está load
, por lo que usa :lso
lugar de :so
que no lo engaña.
(No me gustaría volver a escribir mi código en C. Tampoco me gustaría volver a compilar ghci de la fuente).
Recordatorio de la pregunta TLDR:
- ¿Puedo hacer que ghci agregue la finalización del nombre de archivo al comando
:so
queghci.conf
en mighci.conf
? - ¿De alguna manera podría definir un comando ghci que devuelve 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 Haskell como un preprocesador específico de extensión de archivo, entonces
:l
trabajaría para archivos.hs
y.lhs
como de costumbre, pero ¿usar mi preprocesador escrito a mano para archivos.so
?
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. Luego solo usa :set -pgmL SO-preprocessor
en ghci.conf
.
Para el preprocesador literario estándar, ejecute el programa no unlit
, o use Distribution.Simple.PreProcess.Unlit
.
De esta forma, la finalización de la :load
y el nombre del archivo solo funcionan 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 dar salida a #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).