SCargotPrintParse.hs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. {-# LANGUAGE OverloadedStrings #-}
  2. {-# LANGUAGE LambdaCase #-}
  3. module Main where
  4. import Data.Either
  5. import Data.SCargot
  6. import Data.SCargot.Comments
  7. import Data.SCargot.Repr
  8. import Data.Semigroup
  9. import qualified Data.Text as T
  10. import qualified Data.Text.IO as TIO
  11. import System.Exit
  12. import Test.HUnit
  13. import Text.Parsec as P
  14. import Text.Parsec.Text (Parser)
  15. import Text.Printf ( printf )
  16. main = do
  17. putStrLn "Parsing a large S-expression"
  18. srcs <- mapM (\n -> (,) n <$> TIO.readFile n) [ "test/small-sample.sexp"
  19. , "test/med-sample.sexp"
  20. , "test/med2-sample.sexp"
  21. , "test/big-sample.sexp"
  22. ]
  23. counts <- runTestTT $ TestList
  24. [ TestLabel "basic checks" $ TestList
  25. [ TestLabel "flat print" $ TestList
  26. [ TestLabel "flatprint SNil" $ "()" ~=? printSExpr SNil
  27. , TestLabel "flatprint SAtom" $ "hi" ~=? printSExpr (SAtom (AIdent "hi"))
  28. , TestLabel "flatprint pair" $ "(hi . world)" ~=?
  29. printSExpr (SCons (SAtom (AIdent "hi")) (SAtom (AIdent "world")))
  30. , TestLabel "flatprint list of 1" $ "(hi)" ~=?
  31. printSExpr (SCons (SAtom (AIdent "hi")) SNil)
  32. , TestLabel "flatprint list of 2" $ "(hi world)" ~=?
  33. printSExpr (SCons (SAtom (AIdent "hi"))
  34. (SCons (SAtom (AIdent "world"))
  35. SNil))
  36. , TestLabel "flatprint list of 2 pairs" $ "((hi . hallo) (world . welt))" ~=?
  37. printSExpr (SCons (SCons (SAtom (AIdent "hi"))
  38. (SAtom (AIdent "hallo")))
  39. (SCons (SAtom (AIdent "world"))
  40. (SAtom (AIdent "welt"))))
  41. , TestLabel "flatprint list of 3 ending in a pair" $ "(hi world (hallo . welt))" ~=?
  42. printSExpr (SCons (SAtom (AIdent "hi"))
  43. (SCons (SAtom (AIdent "world"))
  44. (SCons (SAtom (AIdent "hallo"))
  45. (SAtom (AIdent "welt")))))
  46. , TestLabel "flatprint list of 3" $ "(hi world hallo)" ~=?
  47. printSExpr (SCons (SAtom (AIdent "hi"))
  48. (SCons (SAtom (AIdent "world"))
  49. (SCons (SAtom (AIdent "hallo"))
  50. SNil)))
  51. ]
  52. , TestLabel "pretty print" $
  53. let pprintIt = pprintSExpr 40 Swing in TestList
  54. [ TestLabel "pretty print SNil" $ "()\n" ~=? pprintIt SNil
  55. , TestLabel "pretty print SAtom" $ "hi\n" ~=? pprintIt (SAtom (AIdent "hi"))
  56. , TestLabel "pretty print pair" $ "(hi . world)\n" ~=?
  57. pprintIt (SCons (SAtom (AIdent "hi")) (SAtom (AIdent "world")))
  58. , TestLabel "pretty print list of 1" $ "(hi)\n" ~=?
  59. pprintIt (SCons (SAtom (AIdent "hi")) SNil)
  60. , TestLabel "pretty print list of 2" $ "(hi world)\n" ~=?
  61. pprintIt (SCons (SAtom (AIdent "hi"))
  62. (SCons (SAtom (AIdent "world"))
  63. SNil))
  64. , TestLabel "pretty print list of 2 pairs" $
  65. "((hi . hallo) (world . welt))\n" ~=?
  66. pprintIt (SCons (SCons (SAtom (AIdent "hi"))
  67. (SAtom (AIdent "hallo")))
  68. (SCons (SAtom (AIdent "world"))
  69. (SAtom (AIdent "welt"))))
  70. , TestLabel "pretty print list of 3 ending in a pair" $
  71. "(hi world (hallo . welt))\n" ~=?
  72. pprintIt (SCons (SAtom (AIdent "hi"))
  73. (SCons (SAtom (AIdent "world"))
  74. (SCons (SAtom (AIdent "hallo"))
  75. (SAtom (AIdent "welt")))))
  76. , TestLabel "pretty print list of 3" $ "(hi world hallo)\n" ~=?
  77. pprintIt (SCons (SAtom (AIdent "hi"))
  78. (SCons (SAtom (AIdent "world"))
  79. (SCons (SAtom (AIdent "hallo"))
  80. SNil)))
  81. ]
  82. ]
  83. , TestLabel "round-trip" $ TestList $
  84. concatMap (\t -> map t srcs) $
  85. [ testParsePrint
  86. ]
  87. ]
  88. if errors counts + failures counts > 0
  89. then exitFailure
  90. else exitSuccess
  91. testParsePrint :: (String, T.Text) -> Test
  92. testParsePrint (n,s) = TestList
  93. [ testParseFlatPrint n s
  94. , testParsePPrint 80 Swing n s
  95. , testParsePPrint 60 Swing n s
  96. , testParsePPrint 40 Swing n s
  97. , testParsePPrint 20 Swing n s
  98. , testParsePPrint 15 Swing n s
  99. , testParsePPrint 10 Swing n s
  100. , testParsePPrint 80 Align n s
  101. , testParsePPrint 40 Align n s
  102. , testParsePPrint 10 Align n s
  103. ]
  104. testParseFlatPrint testName src =
  105. testRoundTrip (testName <> " flat print")
  106. (fromRight (error "Failed parse") . parseSExpr)
  107. printSExpr
  108. stripAllText
  109. src
  110. testParsePPrint width indentStyle testName src =
  111. testRoundTrip (testName <> " pretty print")
  112. (fromRight (error "Failed parse") . parseSExpr)
  113. (pprintSExpr width indentStyle)
  114. stripAllText
  115. src
  116. stripAllText = T.unwords . concatMap T.words . T.lines
  117. testRoundTrip nm there back prep src = TestList
  118. [ TestLabel (nm <> " round trip") $
  119. TestCase $ (prep src) @=? (prep $ back $ there src)
  120. , TestLabel (nm <> " round trip twice") $
  121. TestCase $ (prep src) @=? (prep $ back $ there $ back $ there src)
  122. ]
  123. ------------------------------------------------------------------------
  124. data FAtom = AIdent String
  125. | AQuoted String
  126. | AString String
  127. | AInt Integer
  128. | ABV Int Integer
  129. deriving (Eq, Show)
  130. string :: String -> SExpr FAtom
  131. string = SAtom . AString
  132. -- | Lift an unquoted identifier.
  133. ident :: String -> SExpr FAtom
  134. ident = SAtom . AIdent
  135. -- | Lift a quoted identifier.
  136. quoted :: String -> SExpr FAtom
  137. quoted = SAtom . AQuoted
  138. -- | Lift an integer.
  139. int :: Integer -> SExpr FAtom
  140. int = SAtom . AInt
  141. printAtom :: FAtom -> T.Text
  142. printAtom a =
  143. case a of
  144. AIdent s -> T.pack s
  145. AQuoted s -> T.pack ('\'' : s)
  146. AString s -> T.pack (show s)
  147. AInt i -> T.pack (show i)
  148. ABV w val -> formatBV w val
  149. printSExpr :: SExpr FAtom -> T.Text
  150. printSExpr = encodeOne (flatPrint printAtom)
  151. pprintSExpr :: Int -> Indent -> SExpr FAtom -> T.Text
  152. pprintSExpr w i = encodeOne (setIndentStrategy (const i) $
  153. setMaxWidth w $
  154. setIndentAmount 1 $
  155. basicPrint printAtom)
  156. getIdent :: FAtom -> Maybe String
  157. getIdent (AIdent s) = Just s
  158. getIdent _ = Nothing
  159. formatBV :: Int -> Integer -> T.Text
  160. formatBV w val = T.pack (prefix ++ printf fmt val)
  161. where
  162. (prefix, fmt)
  163. | w `rem` 4 == 0 = ("#x", "%0" ++ show (w `div` 4) ++ "x")
  164. | otherwise = ("#b", "%0" ++ show w ++ "b")
  165. parseIdent :: Parser String
  166. parseIdent = (:) <$> first <*> P.many rest
  167. where first = P.letter P.<|> P.oneOf "+-=<>_"
  168. rest = P.letter P.<|> P.digit P.<|> P.oneOf "+-=<>_"
  169. parseString :: Parser String
  170. parseString = do
  171. _ <- P.char '"'
  172. s <- P.many (P.noneOf ['"'])
  173. _ <- P.char '"'
  174. return s
  175. parseBV :: Parser (Int, Integer)
  176. parseBV = P.char '#' >> ((P.char 'b' >> parseBin) P.<|> (P.char 'x' >> parseHex))
  177. where parseBin = P.oneOf "10" >>= \d -> parseBin' (1, if d == '1' then 1 else 0)
  178. parseBin' :: (Int, Integer) -> Parser (Int, Integer)
  179. parseBin' (bits, x) = do
  180. P.optionMaybe (P.oneOf "10") >>= \case
  181. Just d -> parseBin' (bits + 1, x * 2 + (if d == '1' then 1 else 0))
  182. Nothing -> return (bits, x)
  183. parseHex = (\s -> (length s * 4, read ("0x" ++ s))) <$> P.many1 P.hexDigit
  184. parseAtom :: Parser FAtom
  185. parseAtom
  186. = AIdent <$> parseIdent
  187. P.<|> AQuoted <$> (P.char '\'' >> parseIdent)
  188. P.<|> AString <$> parseString
  189. P.<|> AInt . read <$> P.many1 P.digit
  190. P.<|> uncurry ABV <$> parseBV
  191. parserLL :: SExprParser FAtom (SExpr FAtom)
  192. parserLL = withLispComments (mkParser parseAtom)
  193. parseSExpr :: T.Text -> Either String (SExpr FAtom)
  194. parseSExpr = decodeOne parserLL