2014-10-14 14 views
1

На этот раз я пытаюсь разобрать текстовый файл на [[String]] с использованием Parsec. Результат - это список, состоящий из списков, представляющих строки файла. Каждая строка представляет собой список, содержащий слова, которые могут быть разделены любым количеством пробелов (необязательно) запятыми и пробелами после запятых.Исчезающий конец строки с Parsec

Вот мой код, и он даже работает.

import Text.ParserCombinators.Parsec hiding (spaces) 
import Control.Applicative ((<$>)) 
import System.IO 
import System.Environment 

myParser :: Parser [[String]] 
myParser = 
    do x <- sepBy parseColl eol 
     eof 
     return x 

eol :: Parser String 
eol = try (string "\n\r") 
    <|> try (string "\r\n") 
    <|> string "\n" 
    <|> string "\r" 
    <?> "end of line" 

spaces :: Parser() 
spaces = skipMany (char ' ') >> return() 

parseColl :: Parser [String] 
parseColl = many parseItem 

parseItem :: Parser String 
parseItem = 
    do optional spaces 
     x <- many1 (noneOf " ,\n\r") 
     optional spaces 
     optional (char ',') 
     return x 

parseText :: String -> String 
parseText str = 
    case parse myParser "" str of 
     Left e -> "parser error: " ++ show e 
     Right x -> show x 

main :: IO() 
main = 
    do fileName <- head <$> getArgs 
     handle <- openFile fileName ReadMode 
     contents <- hGetContents handle 
     putStr $ parseText contents 
     hClose handle 

Тестовый файл:

это мой тестовый файл
это, линия, есть, отделенные, по, запятые
и это еще одна, линия

Результат:

[["this","is","my","test","file"], 
["this","line","is","separated","by","commas"], 
["and","this","is","another","line"], 
[]] -- well, this is a bit unexpected, but I can filter things 

Теперь, чтобы сделать мою жизнь более трудной, я хочу иметь возможность «сбежать» eol, если перед ней есть запятая ,, даже если запятая содержит пробелы. Так что это должно рассматриваться одна строка:

это, пространства может быть здесь
моя линия

Что лучшая стратегия (наиболее идиоматических и элегантный) реализовать этот синтаксис (без теряя способность игнорировать запятые внутри строки).

+0

Parsec отлично, но для чего-то такого простого, почему бы не использовать 'lines' и' words', а затем запустить вывод через то, что удаляет запятые и запятые, а затем фильтрует пустые строки? Иногда необходим полный анализ, но для чего-то такого простого, это выглядит излишним для меня. – jamshidh

+0

@jamshidh, вы правы, но это не настоящая программа. Это упрощенный пример. В моей реальной программе я не могу уйти с 'lines' и' words' ... – Mark

+0

Не определено ли parsec eof? https://hackage.haskell.org/package/parsec-3.0.0/docs/Text-ParserCombinators-Parsec-Combinator.html#v:eof – Arnon

ответ

2

На ум приходит пара решений ... Один легко, другой - средняя сложность.


среда Трудность решения заключается в определении itemSeparator быть запятая с последующим пробелом и lineSeparator быть «\ п» или «\ г» следует пробел .... Убедитесь в том, пропустите non '\ n', '\ r'-whitespace, но не дальше, в конце анализа элемента, так что самый следующий символ после элемента должен быть либо' \ n ',' \ r ', либо ',', который определяет, не возвращаясь, будет ли новый элемент или строка.

Затем с помощью sepBy1 для определения parseLine (IE-parseLine = parseItem sepBy1 parseItemSeparator) и endBy для определения parseFile (IE-parseFile = parseLine endBy parseLineSeparator).

Вам действительно нужно, чтобы sepBy1 с внутренней стороны, vs sepBy, иначе у вас будет список нулевых элементов, что вызывает бесконечный цикл во время разбора. endBy работает как sepBy, но позволяет дополнительному «\ п», «\ г» в конце файла ....


Легче всего было бы канонизировать вход, запустив его, хотя простое преобразование перед разбором. Вы можете написать функцию для удаления пробелов после запятой (используя dropWhile и isSpace) и, возможно, даже упростить различные случаи «\ n», «\ r» .... затем запустить вывод через упрощенный синтаксический анализатор.

Что-то вроде этого сделало бы трюк (это непроверено ....)

canonicalize::String->String 
canonicalize [] == [] 
canonicalize (',':rest) = ',':canonicalize (dropWhile isSpace rest) 
canonicalize ('\n':rest) = '\n':canonicalize (dropWhile isSpace rest) 
canonicalize ('\r':rest) = '\n':canonicalize (dropWhile isSpace rest) --all '\r' will become '\n' 
canonicalize (c:rest) = c:canonicalize rest 

Поскольку Haskell ленив, это преобразование будет работать на потоковых данных, как данные поступают в, так что это на самом деле ничего не тормозить на всех (в зависимости от того, насколько вы облегчите анализатор, он может даже скорость скорее всего ... Хотя, скорее всего, он будет близок к стирке)

Я не знаю, насколько сложным является полный вопрос, но, возможно, несколько правил, добавленных в функцию канонизации, на самом деле позволят вам использование lines и words в конце концов ....

+0

Решение, связанное с трансформацией до синтаксического анализа, неприемлемо, поскольку отчеты parsec о позиции ошибки синтаксического анализа станут неточными (в моей реальной программе это очень важно, извините). Другое решение также неприемлемо, потому что элементы в строке могут быть разделены следующими способами: 'item, item',' item item', 'item, item'. В конце строки * * могут быть пробелами, их тоже не должно быть. – Mark

+0

Soln 1 разрешает ноль или больше пробелов в конце строки .... Я пропустил элементы дела, разделенные запятой, но вы должны иметь возможность легко учитывать это, используя 'optional'. Вы правы в том, что позиция становится неточной для второго решения, если это важно, используйте модифицированную версию номер один (с опцией «0») – jamshidh

+0

Хорошо, я попытаюсь использовать ее, спасибо за ваш ответ. – Mark

1

Просто используйте optional spaces в parseColl, как это:

parseColl :: Parser [String] 
parseColl = optional spaces >> many parseItem 

parseItem :: Parser String 
parseItem = 
    do 
     x <- many1 (noneOf " ,\n\r") 
     optional spaces 
     optional (char ',') 
     return x 

Во-вторых, разделить разделитель из пункта

parseColl :: Parser [String] 
parseColl = do 
     optional spaces 
     items <- parseItem `sepBy` parseSeparator 
     optional spaces 
     return items 

parseItem :: Parser String 
parseItem = many1 $ noneOf " ,\n\r" 

parseSeparator = try (optional spaces >> char ',' >> optional spaces) <|> spaces 

В-третьих, мы заново немного eol и spaces:

eol :: Parser String 
eol = try (string "\n\r") 
    <|> string "\r\n" 
    <|> string "\n" 
    <|> string "\r" 
    <|> eof 
    <?> "end of line" 

spaces :: Parser() 
spaces = skipMany1 $ char ' ' 

parseColl :: Parser [String] 
parseColl = do 
     optional spaces 
     items <- parseItem `sepBy` parseSeparator 
     optional spaces 
     eol 
     return items 

Наконец, давайте перепишем myParser:

myParser = many parseColl 

 Смежные вопросы

  • Нет связанных вопросов^_^