Я научил ghci компилировать мои сообщения StackOverflow. Могу ли я сделать это slicker?

Препроцессор макета Haskell

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

Перейти к Что делать, чтобы заставить его работать, если вы хотите играть с этим первым (1/2 пути вниз).
Пропустите вниз до Что бы я хотел, если немного помахать, и вы просто хотите узнать, какую помощь я ищу.

TL;DR Вопрос резюме:

  • Могу ли я получить ghci для добавления завершения имени файла в команду :so, которую я определил в своем ghci.conf?
  • Могу ли я каким-то образом определить команду ghci, которая возвращает код для компиляции вместо того, чтобы возвращать команду ghci, или у ghci вместо этого есть лучший способ подключить код Haskell в качестве предварительный процессор с расширением файла, поэтому :l будет работать как обычно, для .hs и .lhs файлов, но использовать мой рукописный препроцессор для .so файлов?

История:

Haskell поддерживает грамотное программирование в исходных файлах .lhs двумя способами:

  • Стиль LaTeX \begin{code} и \end{code}.
  • Птицы: код начинается с >, все остальное - комментарий.
    Должна быть пустая строка между кодом и комментариями (чтобы остановить тривиальное случайное неправильное использование >).

Не звучат ли правила Bird Bird, похожие на блоки кода StackOverflow?

Ссылки: 1. Руководство .ghci   2. GHCi haskellwiki   3. Нейл Митчелл блоги о :{ и :} в .ghci

Препроцессор

Мне нравится писать ответы SO в текстовом редакторе, и мне нравится делать сообщение, состоящее из кода, который работает, но в итоге есть блоки комментариев или >, которые мне нужно отредактировать перед публикацией, что менее интересно.

Итак, я написал себе предварительный процессор.

  • Если я вставлял некоторый материал ghci в качестве кодового блока, он обычно начинается с * или :.
  • Если строка полностью пустая, я не хочу, чтобы она рассматривалась как код, потому что в противном случае Я получаю случайные ошибки кода, следующие за комментарием, потому что я не вижу 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 из исходного кода.)

Ответ на вопросник TL;DR:

  • Могу ли я получить ghci для добавления завершения имени файла в команду :so, которую я определил в своем ghci.conf?
  • Могу ли я каким-то образом определить команду ghci, которая возвращает код для компиляции вместо того, чтобы возвращать команду ghci, или у ghci вместо этого есть лучший способ подключить код Haskell в качестве предварительный процессор с расширением файла, поэтому :l будет работать как обычно, для .hs и .lhs файлов, но использовать мой рукописный препроцессор для .so файлов?

Ответ 1

Я попытался бы сделать автономный препроцессор, который запускает код предварительной обработки SO или стандартный литературный препроцессор, в зависимости от расширения файла. Затем просто используйте :set -pgmL SO-preprocessor в ghci.conf.

Для стандартного литературного препроцессора запустите программу unlit или используйте Distribution.Simple.PreProcess.Unlit.

Таким образом, :load и заполнение имени файла работают нормально.

GHCI передает 4 аргументам препроцессору, чтобы: -h, метка, имя исходного файла и имя целевого файла. Препроцессор должен прочитать источник и записать в пункт назначения. Метка используется для вывода #line прагм. Вы можете игнорировать его, если вы не изменяете количество строк источника (т.е. Заменяете строки комментариев комментарием -- или пустые строки).