Как сделать подпарсер с Parsec? - PullRequest
0 голосов
/ 30 января 2019

Я хотел бы проанализировать несколько списков команд с отступом или форматированием в виде массива с Parsec.Например, мои списки будут формироваться следующим образом:

Command1 arg1 arg2       Command1 arg1 arg2         Command1 arg1 arg2
Command2 arg1                                       Command3 arg1 arg2 arg3
                         Command3 arg1 arg2 arg3
                                                    Command4
Command3 arg1 arg2 arg3  Command2 arg1
                         Command4
Command4
Command5 arg1                                       Command2 arg1

Эти команды должны анализироваться столбец за столбцом с изменениями состояния в синтаксическом анализаторе.

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

Я проверил API библиотеки Parsec, но не нашел функции для этого.

Я рассмотрел использование runParser, но эта функция извлекала только результаты синтаксического анализатора, а не его состояние.

Я также рассмотрел создание функции, вдохновленной runParsecT и mkPT для создания собственного парсера, но конструкторы ParsecT или initialPos недоступны (не экспортируются библиотекой)

Возможно ли этозапустить подпарсер внутри парсера с помощью Parsec?

Если нет, может ли библиотека, такая как мегапарсек , решить мою проблему?

Ответы [ 2 ]

0 голосов
/ 30 января 2019

В качестве отправной точки, самый простой ответ на вопрос «Как создать подпарсер» использует монадное связывание, аппликативное <*>, альтернативное <|> и комбинаторы, предоставляемые библиотекой.Предполагая, что каждая команда принадлежит одному типу (как в ответе Ханса Крюгера) и с произвольным числом столбцов, приведенный ниже может быть хорошим шаблоном.

import Text.Parsec
import Text.Parsec.Char
import Data.List(transpose)

cmdFileParser :: Parsec s u [[CommandType]] 
cmdFileParser = sepBy sepParser cmdLineParser
   where
     sepParser = newline --From Text.Parsec.Char

cmdLineParser :: Parsec s u [CommandType]
cmdLineParser = sepBy sepParser cmdParser
   where
     sepParser = tab


cmdParser :: Parsec s u CommandType
cmdParser =   parseCommand1
              <|> parseCommand2
              <|> parseCommand3 
              <|> etc 

Затем, после анализа, транспонируйте[[CommandType]] для группировки команд по столбцу

main = do
  ...
  let ret = runParser cmdFileParser 
                       "debug string telling what was parsed" 
                       stringToParse
  case ret of
    Left e -> putStrLn "wasn't parsed"
    Right cmds -> doSomethingWith (transpose cmds)

Я бы сказал, что вышеприведенный типичный подход.Есть варианты конечно.Например, если вы знаете, что должно быть только три столбца, вы могли бы вместо приведенных выше cmdLineParser указать ниже

cmdLineParser :: Parsec s u (CommandType,CommandType,CommandType)
cmdLineParser = (\a b c -> (a,b,c)) <$> ct <*> ct <*> cmdParser
   where
     ct = cmdParser <* tab

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

cmdParser :: Parsec s u String
cmdParser = many (noneOf "\n\t")

Хотя, будьте осторожны при использовании вышеупомянутого.Я был сожжен в моем использовании many раньше, где это занимает слишком много или всегда удается.Так что я не уверен, что эта точная формулировка даст вам командную строку.Кроме того, если вы просто проанализируете эту команду как строку, а затем повторите команду в вашем main, вы будете анализировать дважды!

0 голосов
/ 30 января 2019

Не полный ответ, более уточняющий вопрос:

Нужно ли составлять список строк?Я бы предпочел проанализировать входные данные и преобразовать их в более специальный тип данных.Таким образом, вы можете использовать гарантии типа haskell.

Я бы начал с определения типа данных для моих команд:

data Command = Command1 Argtype1 
               | Command2 Argtype2
               | Command3 Argtype1 Argtype2

data Argtype1 = Arg1 | Arg2 | ArgX
data Argtype2 = Arg2_1 | Arg2_2 

После этого вы можете проанализировать ввод и поместить его в типы данных.

В конце синтаксического анализа вы можете mappend результаты (то есть для добавления списков спереди с помощью операции (:)).

В итоге вы получите тип данных [Command].С этим вы можете работать дальше.

Для разбора текста вы можете следить за введением в мегапарсек пакета по адресу (https://markkarpov.com/megaparsec/parsing-simple-imperative-language.html)


Или вы имеете в виду что-то совершенно иное? каждая строка (содержащая некоторые команды) в целом должна быть один вход конечного автомата, и конечный автомат изменяется относительно команд? Тогда мне интересно, почему конечный автомат долженбыть реализован как парсер.

...