【问题标题】:How do I parse S-expressions into a data structure in Haskell?如何将 S 表达式解析为 Haskell 中的数据结构?
【发布时间】:2018-10-11 07:29:37
【问题描述】:

我是 Haskell 的新手,需要一些指导。

挑战:获取 S 表达式并将其解析为记录。

我成功的地方:我可以获取一个文件并将其读入一个已解析的字符串。 然而,使用将文本解析为 DFA s.t

 let 
        toDFA :: [L.Text] -> EntryDFA
        toDFA t =
           let [q,a,d,s,f] = t
           in EntryDFA { 
               state = read q
              ,alpha = read a
              ,delta = read d
              ,start = read s
              ,final = read f }

返回此错误:

• Couldn't match type ‘L.Text’ with ‘[Char]’
  Expected type: String
    Actual type: L.Text

必须有更惯用的方法。

【问题讨论】:

  • 您的完整代码似乎缺少导入。此外,您的 do-block 似乎以 let 结尾,这是 AFAIK 不允许的。你为什么要使用Text?有一个 splitOn 适用于 split 包中 Data.List.Split 中的字符串。我不认为使用 read 的方式会起作用。
  • @JonasDuregård Text 绝对是读取源文件的正确类型。
  • 在 Haskell 中解析通常有两种方法:解析器组合器和解析器生成器。一旦您知道这些关键字,Google 应该会从那里引导您完成接下来的几个步骤。我认为,关于这两种技术的教程都太大了,不能成为 SO 的好话题。
  • @Cubic 1) 这似乎有点过于简单了。 2)此代码似乎没有将源文件读取为Text
  • 我正在使用适当的导入。我没有包括它们,所以完整的代码有点误导。我道歉。我真的在寻找一种方法来解决字符串和文本之间的问题,或者如何解决错误代码。据我了解,Text 是处理源文件的最佳方式。

标签: parsing haskell


【解决方案1】:

read 是一个类型为Read a => String -> a 的偏函数,解析失败时会抛出异常。通常你想避免它(如果你有一个字符串,请使用readMaybe)。 StringL.Text 是不同的类型,这就是您收到错误的原因。

您的示例代码在trans-func 之后缺少一个额外的)

我正在使用 Megaparsec 包,它提供了一种使用解析器组合器的简单方法。库的作者写了更长的教程here

基本思想是Parser a 是可以解析a 类型的值的类型。在Text.Megaparsec 中,您可以使用几个函数(parseparseMaybe 等)在“字符串”数据类型(例如String 或严格/惰性Text)上“运行”解析器.

当您对IO 使用do 表示法时,它的意思是“一个接一个地做”。类似地,您可以将do 符号与Parser 一起使用,意思是“先解析这一件事,然后再解析下一件事”。

p1 *> p2表示运行解析器p1,运行p2并返回运行p2的结果。 p1 <* p2 表示运行解析器p1,运行p2 并返回运行p1 的结果。您还可以在 Hoogle 上查找文档,以防您无法理解某些内容。

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns    #-}

-- In practice, many of these imports would be unqualified, but I've
-- opted for explicitness for clarity.
import Control.Applicative (empty, many, some, (<*), (*>))
import Control.Exception (try, IOException)
import Data.Maybe (fromMaybe)
import Data.Set (Set)
import Data.Text (Text)

import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import qualified Text.Megaparsec as MP
import qualified Text.Megaparsec.Char as MPC
import qualified Text.Megaparsec.Char.Lexer as MPCL

type Q = Text
type E = Char

data EntryDFA = EntryDFA
  { state :: Set Q
  , alpha :: Set E
  , delta :: Set (Q,E,Q)
  , start :: Q
  , final :: Set Q
  } deriving Show

inputFile = "foo.sexp"

main :: IO ()
main = do
  -- read file and check for exception instead of checking if
  -- it exists and then trying to read it
  result <- try (TIO.readFile inputFile)
  case result of
    Left e -> print (e :: IOException)
    Right txt -> do
      case MP.parse dfaParser inputFile txt of
        Left e -> print e
        Right dfa -> print dfa

type Parser = MP.Parsec () Text

-- There are no comments in the S-exprs, so leave those empty
spaceConsumer :: Parser ()
spaceConsumer = MPCL.space MPC.space1 empty empty

symbol :: Text -> Parser Text
symbol txt = MPCL.symbol spaceConsumer txt

parens :: Parser a -> Parser a
parens p = MP.between (symbol "(") (symbol ")") p

setP :: Ord a => Parser a -> Parser (Set a)
setP p = do
  items <- parens (p `MP.sepBy1` (symbol ","))
  return (Set.fromList items)

pair :: Parser a -> Parser b -> Parser (a, b)
pair p1 p2 = parens $ do
  x1 <- p1
  x2 <- symbol "," *> p2
  return (x1, x2)

stateP :: Parser Text
stateP = do
  c <- MPC.letterChar
  cs <- many MPC.alphaNumChar
  return (T.pack (c:cs))

dfaParser :: Parser EntryDFA
dfaParser = do
  () <- spaceConsumer
  (_, state) <- pair (symbol "states") (setP stateP)
  (_, alpha) <- pair (symbol "alpha") (setP alphaP)
  (_, delta) <- pair (symbol "trans-func") (setP transFuncP)
  (_, start) <- pair (symbol "start") valP
  (_, final) <- pair (symbol "final") (setP valP)
  return (EntryDFA {state, alpha, delta, start, final})
  where
    alphaP :: Parser Char
    alphaP = MPC.letterChar <* spaceConsumer
    transFuncP :: Parser (Text, Char, Text)
    transFuncP = parens $ do
      s1 <- stateP
      a <- symbol "," *> alphaP
      s2 <- symbol "," *> stateP
      return (s1, a, s2)
    valP :: Parser Text
    valP = fmap T.pack (some MPC.digitChar)

【讨论】:

  • 谢谢。这正是我正在寻找的答案。我熟悉解析,只是不在 Haskell 中。来自 Python 和 C++,数据类型的处理是我最大的僵局。我的很多问题都与实现、语法等有关。显式使用 String 给我留下了当 q 是解析的 [Char] 时读取 q 会失败的问题,其中 [[Char]] 看起来像 [\"states q\", \"字母 e\", ... ]。并且使用 Lazy.Text,这似乎是处理消费字符串的正确方法,会导致 IO 问题和上述错误。 Hoogle 很棒。再次感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-05
  • 1970-01-01
相关资源
最近更新 更多