Haskell Stack Overflow布局预处理器
module StackOverflow where -- yes, the source of this post compiles as is
如果你想先玩这个(1/2路向下),请跳到 What to do to get it working .
如果我稍微停下来,你会跳到 What I would like ,你只想找到我正在寻求的帮助 .
TLDR问题摘要:
-
我可以获得ghci将文件名完成添加到我在
ghci.conf
中定义的:so
命令吗? -
我可以以某种方式定义一个ghci命令来返回编译代码而不是返回一个ghci命令,或者ghci有一个更好的方法让我将Haskell代码作为特定于文件扩展名的预处理器插入,所以
:l
会工作像往常一样使用.hs
和.lhs
文件,但是使用我的手写预处理器来处理.so
文件?
背景:
Haskell支持 .lhs
源文件中的文字编程,有两种方式:
-
LaTeX style
\begin{code}
和\end{code}
. -
Bird跟踪:代码以
>
开头,其他任何内容都是评论 .
代码和注释之间必须有一个空行(以防止意外误用>
) .
Bird不跟踪类似于StackOverflow代码块的规则声音吗?
参考文献: 1. The .ghci manual 2. GHCi haskellwiki 3. Neil Mitchell blogs about : in .ghci
预处理器
我喜欢在文本编辑器中编写SO答案,我喜欢发表一个包含有效代码的帖子,但最后会发布评论块或 >
,我必须在发布前编辑,这样不那么有趣 .
所以,我自己写了一个预处理器 .
-
如果我在代码块中粘贴了一些ghci,它通常以
*
或:
开头 . -
如果该行完全空白,我不会看到我在空白行上意外遗留的4个空格 .
-
如果前一行不是代码,则该行不应使用缩进来进行代码块之外的文本布局 .
起初我们不知道(dunno)这行是代码还是文本:
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
但如果我们知道,我们应该保持相同的模式,直到我们打出一个空白行:
knowNow :: Bool -> [String] -> [String]
knowNow _ [] = []
knowNow itsCode (line:lines)
| all (==' ') line = line:dunnoNow lines
| otherwise = (if itsCode then '>':line else line):knowNow itsCode lines
让ghci使用预处理器
现在我们可以获取模块名称,预处理该文件,并告诉ghci加载它:
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")
我曾经默默地重新定义了 :rso
命令,因为我以前的attemts使用 let currentStackOverflowFile = ....
或 currentStackOverflowFile <- return ...
没有让我到任何地方 .
如何使其发挥作用
现在我需要将它放在我的 ghci.conf
文件中,即在 appdata/ghc/ghci.conf
中按照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
用法
现在我可以在 LiterateSo.so
中保存这整篇文章并在ghci中做一些可爱的事情
*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>
万岁!
我想要什么:
我更愿意让ghci更直接地支持它 . 摆脱中间 .lhs
文件会很好 .
此外,似乎ghci文件名完成从 :load
的最短子字符串开始,它确定你实际上正在做 load
,所以使用 :lso
而不是 :so
并不会欺骗它 .
(我不想在C中重写我的代码 . 我也不想从源代码重新编译ghci . )
TLDR问题提醒:
-
我可以获得ghci将文件名完成添加到我在
ghci.conf
中定义的:so
命令吗? -
我可以以某种方式定义一个ghci命令来返回编译代码而不是返回一个ghci命令,或者ghci是否有更好的方法让我将Haskell代码作为特定于文件扩展名的预处理器插入,所以
:l
会工作像往常一样.hs
和.lhs
文件,但是使用我的手写预处理程序来处理.so
文件?
1 回答
我会尝试制作一个运行SO预处理代码或标准文学预处理器的独立预处理器,具体取决于文件扩展名 . 然后在
ghci.conf
中使用:set -pgmL SO-preprocessor
.对于标准文学预处理器,运行
unlit
程序,或使用Distribution.Simple.PreProcess.Unlit
.这样,
:load
和文件名完成就可以正常工作了 .GHCI按顺序将4个参数传递给预处理器:
-h
,标签,源文件名和目标文件名 . 预处理器应该读取源并写入目标 . 标签用于输出#line
pragma . 如果不改变源的行数,则可以忽略它(即用--
注释或空行替换"comment"行) .