SCargotPrintParse.hs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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" $ "()" ~=? pprintIt SNil
  55. , TestLabel "pretty print SAtom" $ "hi" ~=? pprintIt (SAtom (AIdent "hi"))
  56. , TestLabel "pretty print pair" $ "(hi . world)" ~=?
  57. pprintIt (SCons (SAtom (AIdent "hi")) (SAtom (AIdent "world")))
  58. , TestLabel "pretty print list of 1" $ "(hi)" ~=?
  59. pprintIt (SCons (SAtom (AIdent "hi")) SNil)
  60. , TestLabel "pretty print list of 2" $ "(hi world)" ~=?
  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)" ~=?
  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)" ~=?
  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)" ~=?
  77. pprintIt (SCons (SAtom (AIdent "hi"))
  78. (SCons (SAtom (AIdent "world"))
  79. (SCons (SAtom (AIdent "hallo"))
  80. SNil)))
  81. ]
  82. , TestLabel "unconstrained print" $
  83. let pprintIt = ucPrintSExpr Swing in TestList
  84. [ TestLabel "pretty print SNil" $ "()" ~=? pprintIt SNil
  85. , TestLabel "pretty print SAtom" $ "hi" ~=? pprintIt (SAtom (AIdent "hi"))
  86. , TestLabel "pretty print pair" $ "(hi . world)" ~=?
  87. pprintIt (SCons (SAtom (AIdent "hi")) (SAtom (AIdent "world")))
  88. , TestLabel "pretty print list of 1" $ "(hi)" ~=?
  89. pprintIt (SCons (SAtom (AIdent "hi")) SNil)
  90. , TestLabel "pretty print list of 2" $ "(hi world)" ~=?
  91. pprintIt (SCons (SAtom (AIdent "hi"))
  92. (SCons (SAtom (AIdent "world"))
  93. SNil))
  94. , TestLabel "pretty print list of 2 pairs" $
  95. "((hi . hallo)\n world\n . welt)" ~=?
  96. pprintIt (SCons (SCons (SAtom (AIdent "hi"))
  97. (SAtom (AIdent "hallo")))
  98. (SCons (SAtom (AIdent "world"))
  99. (SAtom (AIdent "welt"))))
  100. , TestLabel "pretty print list of 3 ending in a pair" $
  101. "(hi world hallo . welt)" ~=?
  102. pprintIt (SCons (SAtom (AIdent "hi"))
  103. (SCons (SAtom (AIdent "world"))
  104. (SCons (SAtom (AIdent "hallo"))
  105. (SAtom (AIdent "welt")))))
  106. , TestLabel "pretty print list of 3" $ "(hi world hallo)" ~=?
  107. pprintIt (SCons (SAtom (AIdent "hi"))
  108. (SCons (SAtom (AIdent "world"))
  109. (SCons (SAtom (AIdent "hallo"))
  110. SNil)))
  111. ]
  112. ]
  113. , TestLabel "round-trip" $ TestList $
  114. concatMap (\t -> map t srcs) $
  115. [ testParsePrint
  116. ]
  117. ]
  118. if errors counts + failures counts > 0
  119. then exitFailure
  120. else exitSuccess
  121. testParsePrint :: (String, T.Text) -> Test
  122. testParsePrint (n,s) = TestList
  123. [ testParseFlatPrint n s
  124. , testParseUnconstrainedPrint Swing n s
  125. , testParseUnconstrainedPrint Align n s
  126. , testParsePPrint 80 Swing n s
  127. , testParsePPrint 60 Swing n s
  128. , testParsePPrint 40 Swing n s
  129. , testParsePPrint 20 Swing n s
  130. , testParsePPrint 15 Swing n s
  131. , testParsePPrint 10 Swing n s
  132. , testParsePPrint 80 Align n s
  133. , testParsePPrint 40 Align n s
  134. , testParsePPrint 10 Align n s
  135. ]
  136. testParseFlatPrint testName src =
  137. testRoundTrip (testName <> " flat print")
  138. (fromRight (error "Failed parse") . parseSExpr)
  139. printSExpr
  140. stripAllText
  141. src
  142. testParseUnconstrainedPrint indentStyle testName src =
  143. testRoundTrip (testName <> " unconstrained print")
  144. (fromRight (error "Failed parse") . parseSExpr)
  145. (ucPrintSExpr indentStyle)
  146. stripAllText
  147. src
  148. testParsePPrint width indentStyle testName src =
  149. testRoundTrip (testName <> " pretty print")
  150. (fromRight (error "Failed parse") . parseSExpr)
  151. (pprintSExpr width indentStyle)
  152. stripAllText
  153. src
  154. stripAllText = T.unwords . concatMap T.words . T.lines
  155. testRoundTrip nm there back prep src = TestList
  156. [ TestLabel (nm <> " round trip") $
  157. TestCase $ (prep src) @=? (prep $ back $ there src)
  158. , TestLabel (nm <> " round trip twice") $
  159. TestCase $ (prep src) @=? (prep $ back $ there $ back $ there src)
  160. ]
  161. ------------------------------------------------------------------------
  162. data FAtom = AIdent String
  163. | AQuoted String
  164. | AString String
  165. | AInt Integer
  166. | ABV Int Integer
  167. deriving (Eq, Show)
  168. string :: String -> SExpr FAtom
  169. string = SAtom . AString
  170. -- | Lift an unquoted identifier.
  171. ident :: String -> SExpr FAtom
  172. ident = SAtom . AIdent
  173. -- | Lift a quoted identifier.
  174. quoted :: String -> SExpr FAtom
  175. quoted = SAtom . AQuoted
  176. -- | Lift an integer.
  177. int :: Integer -> SExpr FAtom
  178. int = SAtom . AInt
  179. printAtom :: FAtom -> T.Text
  180. printAtom a =
  181. case a of
  182. AIdent s -> T.pack s
  183. AQuoted s -> T.pack ('\'' : s)
  184. AString s -> T.pack (show s)
  185. AInt i -> T.pack (show i)
  186. ABV w val -> formatBV w val
  187. printSExpr :: SExpr FAtom -> T.Text
  188. printSExpr = encodeOne (flatPrint printAtom)
  189. pprintSExpr :: Int -> Indent -> SExpr FAtom -> T.Text
  190. pprintSExpr w i = encodeOne (setIndentStrategy (const i) $
  191. setMaxWidth w $
  192. setIndentAmount 1 $
  193. basicPrint printAtom)
  194. ucPrintSExpr :: Indent -> SExpr FAtom -> T.Text
  195. ucPrintSExpr i = encodeOne (setIndentStrategy (const i) $
  196. setIndentAmount 1 $
  197. unconstrainedPrint printAtom)
  198. getIdent :: FAtom -> Maybe String
  199. getIdent (AIdent s) = Just s
  200. getIdent _ = Nothing
  201. formatBV :: Int -> Integer -> T.Text
  202. formatBV w val = T.pack (prefix ++ printf fmt val)
  203. where
  204. (prefix, fmt)
  205. | w `rem` 4 == 0 = ("#x", "%0" ++ show (w `div` 4) ++ "x")
  206. | otherwise = ("#b", "%0" ++ show w ++ "b")
  207. parseIdent :: Parser String
  208. parseIdent = (:) <$> first <*> P.many rest
  209. where first = P.letter P.<|> P.oneOf "+-=<>_"
  210. rest = P.letter P.<|> P.digit P.<|> P.oneOf "+-=<>_"
  211. parseString :: Parser String
  212. parseString = do
  213. _ <- P.char '"'
  214. s <- P.many (P.noneOf ['"'])
  215. _ <- P.char '"'
  216. return s
  217. parseBV :: Parser (Int, Integer)
  218. parseBV = P.char '#' >> ((P.char 'b' >> parseBin) P.<|> (P.char 'x' >> parseHex))
  219. where parseBin = P.oneOf "10" >>= \d -> parseBin' (1, if d == '1' then 1 else 0)
  220. parseBin' :: (Int, Integer) -> Parser (Int, Integer)
  221. parseBin' (bits, x) = do
  222. P.optionMaybe (P.oneOf "10") >>= \case
  223. Just d -> parseBin' (bits + 1, x * 2 + (if d == '1' then 1 else 0))
  224. Nothing -> return (bits, x)
  225. parseHex = (\s -> (length s * 4, read ("0x" ++ s))) <$> P.many1 P.hexDigit
  226. parseAtom :: Parser FAtom
  227. parseAtom
  228. = AIdent <$> parseIdent
  229. P.<|> AQuoted <$> (P.char '\'' >> parseIdent)
  230. P.<|> AString <$> parseString
  231. P.<|> AInt . read <$> P.many1 P.digit
  232. P.<|> uncurry ABV <$> parseBV
  233. parserLL :: SExprParser FAtom (SExpr FAtom)
  234. parserLL = withLispComments (mkParser parseAtom)
  235. parseSExpr :: T.Text -> Either String (SExpr FAtom)
  236. parseSExpr = decodeOne parserLL