2013-09-23 1 views
2

В настоящее время я пишу программу Haskell, где в какой-то момент мне нужно манипулировать кодом Haskell, который кто-то еще ввел в систему, добавив его с моей собственной информацией и функциями. Например, я, возможно, пожелает, чтобы найти все места, пользователь написал что-то вроде:Haskell Parenthesis Matching for Find and Replace

а = Shape (Rectangle 3 5)

И изменить эти записи с дополнительными данными (например, номер строки, они появились на или информацию о пользователе, который написал им:

а = TrackedShape (74 «Джон» (Прямоугольник 3 5))

для того, чтобы сделать это найти и заменить, я пробовал только с помощью регулярных выражений, но что во многих случаях они недостаточно выразительны, чтобы фиксировать все варианты использования. В частности, для примера выше мне понадобится t o захватить все, что содержится в конструкторе формы, и поэтому нужно знать, что представляют собой скобки.

Я также попытался выяснить, будет ли полезен синтаксический анализатор, такой как haskell-src-exts, но я не уверен. Похоже, что, хотя эти библиотеки могут быть полезны для разбора кода Haskell изначально, им не хватает возможности анализировать код, изменять дерево разбора, а затем изменять разобранный код назад в его первоначальную форму, сохраняя при этом структуру Оригинальный текст.

Есть ли библиотеки, которые были бы полезны для такого рода задач? Или, если это не так, есть ли какая-то функция, которую я мог бы написать, которая обеспечивала бы надежный поиск и замену для этого?

ответ

0

У haskell-src-exts есть принтер для кода: http://hackage.haskell.org/packages/archive/haskell-src-exts/1.14.0/doc/html/Language-Haskell-Exts-Pretty.html Когда вы говорите, сохраняя структуру, вы имеете в виду сохранение первоначальных мест разрыва строки?

Функция parseFile в этом модуле http://hackage.haskell.org/packages/archive/haskell-src-exts/1.14.0/doc/html/Language-Haskell-Exts-Annotated.html действительно дает вам исходную информацию об источнике, поэтому теоретически вы можете сохранить эту информацию во время манипуляции, а затем использовать ее в печати. Я подозреваю, что вам удастся избежать использования номеров строк, чтобы ввести разрывы строк (и следующий отступ) в соответствующих точках, причем расстояние между линиями относительно стандартно.

Одна из проблем заключается в том, что вы потеряете комментарии в источнике при его анализе. Единственный способ, с помощью которого я могу избежать этого, - разобрать его и попытаться использовать исходные исходные местоположения, чтобы решить, где изменить исходный файл (а не выплевывать его снова из разобранной формы). Но в целом код синтаксического анализа и сохранение всех комментариев (включая встроенные комментарии) в управляемой форме - довольно сложная задача, и вы часто не найдете библиотеки.

+0

Да, уточнить. Когда я говорю «сохраняющая структура», я имею в виду сохранение отступов, разрывов строк и т. Д. Замены, которые я делаю на экране, будут отображаться пользователю немедленно, поэтому я не хочу, чтобы он внезапно уничтожил все их отступы. Я хочу, чтобы это выглядело как * их * код, который, как оказалось, был дополнен –

+0

Возможно, вы пишете чрезвычайно простой парсер (используя parsec или attoparsec), который просто разбирает круглые скобки и текст. Затем вы можете применять выражения reqular к тому, что содержится в parens. – mhwombat

4

Когда вы используете Haskell и regexes, вы не можете, а затем доберитесь до Parsec! Я лично считаю, что это одна из лучших функций Haskell, которую я пропустил при работе на других языках, что разбор контекстно-свободных языков или даже контекстно-зависимых языков так же просто или даже проще, чем работа с регулярными выражениями.

import Text.ParserCombinators.Parsec 
import Control.Applicative ((<$>),(<*>),(*>),(<*)) 
import Control.Monad 
import Text.Printf 

-- Handy helper to concat successive parsers that return strings 
infixl 4 <++> 
f <++> g = (++) <$> f <*> g 

-- Parse a balanced number of brackets 
brackets :: Parser String 
brackets = string "(" <++> (join <$> many brackets) <++> string ")" <|> many1 (noneOf "()") 

-- Parser that will perform your example replace 
replaceShape :: Int -> String -> Parser String 
replaceShape line name = printf "TrackedShape (%i \"%s\" %s)" line name <$> (string "Shape " *> brackets) 

испытание в GHCi:

> parseTest (replaceShape 10 "John") "Shape (Rectangle 3 5)" 
"TrackedShape (10 \"John\" (Rectangle 3 5))" 

Код выше может выглядеть относительно непроницаемым, если вы еще не работали с аппликативными или даже Аппликативными анализаторами раньше. Однако, как обычные выражения, как только вы привыкнете к нему, это отличный способ работы. Есть много очень полных обучающих программ для парсеров, и узнайте, что у Haskell есть отличное объяснение Приложений.<$> - это инфиксная версия fmap, а <*> объединяет выражения в аналогичном (но менее мощном) способе до >>=. Таким образом, выражение (++) <$> f <*> g в нашем случае эквивалентно

do 
    a <- f 
    b <- g 
    return $ a ++ b 

В самом деле, функция могла бы быть написана таким образом (есть интерфейс Прикладной и Монада), но аппликативный стиль позволяет очень лаконичный, регулярное выражению, как анализаторы ,

Другие функции, используемые в примере, это <|>, который обеспечивает чередование (то есть разбор таким образом или таким образом. Таким образом, в примере скобок две альтернативы открывают новую скобку или анализируют что-то посередине, кронштейн.) и <* и *>, которые похожи на <*>, за исключением того, что они отбрасывают результат с одной из сторон. < указывает на результат, который сохраняется.

Обратите внимание, что я заметил, что реализована дополнительная функциональность для определения номеров строк, но Parsec может это сделать, поскольку моназа синтаксического анализа является на самом деле монадным трансформатором и поэтому может быть помещена поверх государственной монады, чтобы сохранить произвольную информацию во время разобрать. У Parsec очень богатый набор функций для таких вещей, поэтому это определенно инструмент, который вы хотите для этой работы.

+0

", потому что любая аппликация порождает Монаду" Э-э ... довольно уверен, что это не так. – singpolyma

+0

Упс, не так! Я действительно удалил комментарий, поскольку я не думаю, что это было особенно полезно в этом случае. –