Я научил 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
прагм. Вы можете игнорировать его, если вы не изменяете количество строк источника (т.е. Заменяете строки комментариев комментарием --
или пустые строки).