Skip to content

Commit 145b7b8

Browse files
authored
Fix formatting of strings containing control characters (#1543)
Since control characters like '\b' cannot be escaped directly in multi-line strings, we move them into string interpolations. E.g. "\n\b" is formatted as '' ${"\b"}'' This partially addresses the malformatting of the Prelude.Text.show example discovered in dhall-lang/dhall-lang#822. This sample string is now malformatted due to #1545 though.
1 parent 416160b commit 145b7b8

File tree

3 files changed

+53
-5
lines changed

3 files changed

+53
-5
lines changed

dhall/src/Dhall/Pretty/Internal.hs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,21 +1087,23 @@ prettyCharacterSet characterSet expression =
10871087
angles . map prettyAlternative . Dhall.Map.toList
10881088

10891089
prettyChunks :: Pretty a => Chunks Src a -> Doc Ann
1090-
prettyChunks (Chunks a b) =
1091-
if anyText (== '\n')
1092-
then
1090+
prettyChunks chunks@(Chunks a b)
1091+
| anyText (== '\n') =
10931092
if anyText (/= '\n')
10941093
then long
10951094
else Pretty.flatAlt long short
1096-
else short
1095+
| otherwise =
1096+
short
10971097
where
10981098
long =
10991099
Pretty.align
11001100
( literal "''" <> Pretty.hardline
11011101
<> Pretty.align
1102-
(foldMap prettyMultilineChunk a <> prettyMultilineText b)
1102+
(foldMap prettyMultilineChunk a' <> prettyMultilineText b')
11031103
<> literal "''"
11041104
)
1105+
where
1106+
Chunks a' b' = multilineChunks chunks
11051107

11061108
short =
11071109
literal "\"" <> foldMap prettyChunk a <> literal (prettyText b <> "\"")
@@ -1136,6 +1138,43 @@ prettyCharacterSet characterSet expression =
11361138

11371139
prettyText t = literal (Pretty.pretty (escapeText_ t))
11381140

1141+
1142+
-- | Prepare 'Chunks' for multi-line formatting by interpolating characters that
1143+
-- may not appear in multi-line strings directly.
1144+
--
1145+
-- >>> multilineChunks (Chunks [] "\n\NUL\b\f\t")
1146+
-- Chunks [("\n",TextLit (Chunks [] "\NUL\b\f"))] "\t"
1147+
multilineChunks :: Chunks Src a -> Chunks Src a
1148+
multilineChunks (Chunks as0 b0) = Chunks as1 b1
1149+
where
1150+
as1 = foldr f (map toPair bs) as0
1151+
1152+
(bs, b1) = splitOnPredicate predicate b0
1153+
1154+
predicate c = Data.Char.isControl c && c /= ' ' && c /= '\t' && c /= '\n'
1155+
1156+
f (t0, e) pairs = case splitOnPredicate predicate t0 of
1157+
(ts1, t1) -> map toPair ts1 ++ (t1, e) : pairs
1158+
1159+
toPair (t0, t1) = (t0, TextLit (Chunks [] t1))
1160+
1161+
-- | Split `Text` on a predicate, preserving all parts of the original string.
1162+
--
1163+
-- >>> splitOnPredicate (== 'x') ""
1164+
-- ([],"")
1165+
-- >>> splitOnPredicate (== 'x') " xx "
1166+
-- ([(" ","xx")]," ")
1167+
-- >>> splitOnPredicate (== 'x') "xx"
1168+
-- ([("","xx")],"")
1169+
--
1170+
-- prop> \(Fun _ p) s -> let {t = Text.pack s; (as, b) = splitOnPredicate p t} in foldMap (uncurry (<>)) as <> b == t
1171+
splitOnPredicate :: (Char -> Bool) -> Text -> ([(Text, Text)], Text)
1172+
splitOnPredicate p t = case Text.break p t of
1173+
(a, "") -> ([], a)
1174+
(a, b) -> case Text.span p b of
1175+
(c, d) -> case splitOnPredicate p d of
1176+
(e, f) -> ((a, c) : e, f)
1177+
11391178
-- | Pretty-print a value
11401179
pretty_ :: Pretty a => a -> Text
11411180
pretty_ = prettyToStrictText
@@ -1208,3 +1247,8 @@ layoutOpts :: Pretty.LayoutOptions
12081247
layoutOpts =
12091248
Pretty.defaultLayoutOptions
12101249
{ Pretty.layoutPageWidth = Pretty.AvailablePerLine 80 1.0 }
1250+
1251+
1252+
{- $setup
1253+
>>> import Test.QuickCheck (Fun(..))
1254+
-}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"\u0000 \$ \\ \n"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
''
2+
${"\u0000"} $ \
3+
''

0 commit comments

Comments
 (0)