module StackOverflow where -- yes, the source of this post compiles as is
Skip down to What to do to get it working if you want to play with this first (1/2 way down).
Skip down to What I would like if I witter on a bit and you just want to find out what help I'm seeking.
:so
command I defined in my ghci.conf
?:l
would work for .hs
and .lhs
files as usual, but use my handwritten preprocessor for .so
files?Haskell supports literate programming in .lhs
source files, two ways:
\begin{code}
and \end{code}
.>
, anything else is a comment.>
).Don't Bird tracks rules sound similar to StackOverflow's code blocks?
References: 1. The .ghci manual 2. GHCi haskellwiki 3. Neil Mitchell blogs about :{
and :}
in .ghci
I like writing SO answers in a text editor, and I like to make a post that consists of code that works, but end up with comment blocks or >
s that I have to edit out before posting, which is less fun.
So, I wrote myself a pre-processor.
*
or :
.At first we don't know (I don't know) whether this line is code or text:
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
but if we know, we should keep in the same mode until we hit a blank line:
knowNow :: Bool -> [String] -> [String] knowNow _ [] = [] knowNow itsCode (line:lines) | all (==' ') line = line:dunnoNow lines | otherwise = (if itsCode then '>':line else line):knowNow itsCode lines
Now we can take a module name, preprocess that file, and tell ghci to load it:
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")
I've used silently redefining the :rso
command becuase my previous attemts to use let currentStackOverflowFile = ....
or currentStackOverflowFile <- return ...
didn't get me anywhere.
Now I need to put it in my ghci.conf
file, i.e. in appdata/ghc/ghci.conf
as per the 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
Now I can save this entire post in LiterateSo.so
and do lovely things in ghci like
*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>
Hooray!
I would prefer to enable ghci to support this more directly. It would be nice to get rid of the intermediate .lhs
file.
Also, it seems ghci does filename completion starting at the shortest substring of :load
that determines you're actually doing load
, so using :lso
instead of :so
doesn't fool it.
(I would not like to rewrite my code in C. I also would not like to recompile ghci from source.)
:so
command I defined in my ghci.conf
?:l
would work for .hs
and .lhs
files as usual, but use my handwritten preprocessor for .so
files?I would try to make a standalone preprocessor that runs SO preprocessing code or the standard literary preprocessor, depending on file extension. Then just use :set -pgmL SO-preprocessor
in ghci.conf
.
For the standard literary preprocessor, run the unlit
program, or use Distribution.Simple.PreProcess.Unlit
.
This way, :load
and filename completion just work normally.
GHCI passes 4 arguments to the preprocessor, in order: -h
, the label, the source file name, and the destination file name. The preprocessor should read the source and write to the destination. The label is used to output #line
pragmas. You can ignore it if you don't alter the line count of the source (i.e. replace "comment" lines with --
comments or blank lines).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With