首页 文章

我教ghci来编译我的StackOverflow帖子 . 我可以让它更光滑吗?

提问于
浏览
33

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 回答

  • 9

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

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

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

    GHCI按顺序将4个参数传递给预处理器: -h ,标签,源文件名和目标文件名 . 预处理器应该读取源并写入目标 . 标签用于输出 #line pragma . 如果不改变源的行数,则可以忽略它(即用 -- 注释或空行替换"comment"行) .

相关问题