34

Haskell Stack Overflow 布局预处理器

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

如果您想先玩这个(向下 1/2) ,请跳至如何让它工作。
跳到我想要什么,如果我有点无聊,你只是想找出我正在寻求什么帮助。

TLDR 问题摘要:

  1. 我可以让 ghci 将文件名完成添加到:so我在我定义的命令中ghci.conf吗?
  2. 我是否可以以某种方式定义一个 ghci 命令来返回编译代码而不是返回 ghci 命令,或者 ghci 是否有更好的方法让我将 Haskell 代码插入为特定于文件扩展的预处理器,因此:l适用于.hs.lhs像往常一样文件,但使用我的手写.so文件预处理器?

背景:

.lhsHaskell 在源文件中支持 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 问题提醒:

  1. 我可以让 ghci 将文件名完成添加到:so我在我定义的命令中ghci.conf吗?
  2. 我是否可以以某种方式定义一个 ghci 命令来返回编译代码而不是返回 ghci 命令,或者 ghci 是否有更好的方法让我将 Haskell 代码插入为特定于文件扩展的预处理器,因此:l适用于.hs.lhs像往常一样文件,但使用我的手写.so文件预处理器?
4

1 回答 1

9

我会尝试制作一个独立的预处理器来运行 SO 预处理代码或标准的文学预处理器,具体取决于文件扩展名。然后只需使用:set -pgmL SO-preprocessorin ghci.conf

对于标准的文学预处理器,运行unlit程序,或使用Distribution.Simple.PreProcess.Unlit.

这样,:load文件名完成就可以正常工作。

GHCI 将 4 个参数依次传递给预处理器:-h标签、源文件名和目标文件名。预处理器应该读取源并写入目标。标签用于输出#linepragma。如果您不更改源代码的行数(即用--注释或空白行替换“注释”行),您可以忽略它。

于 2012-10-01T10:20:23.500 回答