Haskell Stack Overflow 布局预处理器
module StackOverflow where -- yes, the source of this post compiles as is
如果您想先玩这个(向下 1/2) ,请跳至如何让它工作。
跳到我想要什么,如果我有点无聊,你只是想找出我正在寻求什么帮助。
TLDR 问题摘要:
- 我可以让 ghci 将文件名完成添加到
:so
我在我定义的命令中ghci.conf
吗? - 我是否可以以某种方式定义一个 ghci 命令来返回编译代码而不是返回 ghci 命令,或者 ghci 是否有更好的方法让我将 Haskell 代码插入为特定于文件扩展的预处理器,因此
:l
适用于.hs
和.lhs
像往常一样文件,但使用我的手写.so
文件预处理器?
背景:
.lhs
Haskell 在源文件中支持 literate 编程,有两种方式:
- LaTeX 风格
\begin{code}
和\end{code}
. - 鸟迹:代码以 开头
>
,其他都是注释。
代码和注释之间必须有一个空白行(以防止对>
.
Bird 跟踪规则听起来不像 StackOverflow 的代码块吗?
参考: 1. .ghci 手册
2. GHCi haskellwiki
3. Neil Mitchell 关于:{
.ghci:}
的博客
预处理器
我喜欢在文本编辑器中编写 SO 答案,并且我喜欢发布由有效代码组成的帖子,但最终会出现评论块或>
我必须在发布之前编辑掉的 s,这不那么有趣。
所以,我给自己写了一个预处理器。
- 如果我将一些 ghci 内容粘贴为代码块,它通常以
*
or开头:
。 - 如果该行完全空白,我不希望将其视为代码,否则我会意外出现 code-next-to-comment-line 错误,因为我看不到我不小心留在空白行上的 4 个空格。
- 如果前一行不是代码,那么这一行也不应该是,所以我们可以处理 StackOverflow 在代码块之外使用缩进来进行文本布局。
起初我们不知道(我不知道)这一行是代码还是文本:
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
命令,因为我以前尝试使用
let currentStackOverflowFile = ....
或currentStackOverflowFile <- return ...
没有让我到任何地方。
怎么做才能让它工作
现在我需要把它放在我的ghci.conf
文件中,即appdata/ghc/ghci.conf
按照说明
:{
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 将文件名完成添加到
:so
我在我定义的命令中ghci.conf
吗? - 我是否可以以某种方式定义一个 ghci 命令来返回编译代码而不是返回 ghci 命令,或者 ghci 是否有更好的方法让我将 Haskell 代码插入为特定于文件扩展的预处理器,因此
:l
适用于.hs
和.lhs
像往常一样文件,但使用我的手写.so
文件预处理器?