| ... |
... |
@@ -6,10 +6,6 @@ module GHC.Parser.String ( |
|
6
|
6
|
lexString,
|
|
7
|
7
|
lexMultilineString,
|
|
8
|
8
|
|
|
9
|
|
- -- * StringMeta
|
|
10
|
|
- StringMeta (..),
|
|
11
|
|
- defaultStrMeta,
|
|
12
|
|
-
|
|
13
|
9
|
-- * Unicode smart quote helpers
|
|
14
|
10
|
isDoubleSmartQuote,
|
|
15
|
11
|
isSingleSmartQuote,
|
| ... |
... |
@@ -20,7 +16,6 @@ import GHC.Prelude hiding (getChar) |
|
20
|
16
|
import Control.Arrow ((>>>))
|
|
21
|
17
|
import Control.Monad (when)
|
|
22
|
18
|
import Data.Char (chr, ord)
|
|
23
|
|
-import Data.Data (Data)
|
|
24
|
19
|
import qualified Data.Foldable1 as Foldable1
|
|
25
|
20
|
import qualified Data.List.NonEmpty as NonEmpty
|
|
26
|
21
|
import Data.Maybe (listToMaybe, mapMaybe)
|
| ... |
... |
@@ -37,19 +32,13 @@ import GHC.Parser.CharClass ( |
|
37
|
32
|
)
|
|
38
|
33
|
import GHC.Parser.Errors.Types (LexErr (..))
|
|
39
|
34
|
import GHC.Utils.Panic (panic)
|
|
40
|
|
-import Language.Haskell.Syntax.Module.Name (ModuleName)
|
|
41
|
35
|
|
|
42
|
36
|
type BufPos = Int
|
|
43
|
37
|
data StringLexError = StringLexError LexErr BufPos
|
|
44
|
38
|
deriving (Show, Eq)
|
|
45
|
39
|
|
|
46
|
40
|
lexString :: Int -> StringBuffer -> Either StringLexError String
|
|
47
|
|
-lexString = lexStringWith processChars processChars
|
|
48
|
|
- where
|
|
49
|
|
- processChars :: HasChar c => [c] -> Either (c, LexErr) [c]
|
|
50
|
|
- processChars =
|
|
51
|
|
- collapseGaps
|
|
52
|
|
- >>> resolveEscapes
|
|
|
41
|
+lexString = lexStringWith (fromRight . processCharsSingle)
|
|
53
|
42
|
|
|
54
|
43
|
-- -----------------------------------------------------------------------------
|
|
55
|
44
|
-- Lexing interface
|
| ... |
... |
@@ -72,26 +61,27 @@ So what we'll do is do two passes. The first pass is optimistic; just convert |
|
72
|
61
|
to a plain String and process it. If this results in an error, we do a second
|
|
73
|
62
|
pass, this time where each character is annotated with its position. Now, the
|
|
74
|
63
|
error has all the information it needs.
|
|
75
|
|
-
|
|
76
|
|
-Ideally, lexStringWith would take a single (forall c. HasChar c => ...) function,
|
|
77
|
|
-but to help the specializer, we pass it in twice to concretize it for the two
|
|
78
|
|
-types we actually use.
|
|
79
|
64
|
-}
|
|
80
|
65
|
|
|
81
|
66
|
-- | See Note [Lexing strings]
|
|
82
|
|
-lexStringWith ::
|
|
83
|
|
- ([Char] -> Either (Char, LexErr) [Char])
|
|
84
|
|
- -> ([CharPos] -> Either (CharPos, LexErr) [CharPos])
|
|
85
|
|
- -> Int
|
|
86
|
|
- -> StringBuffer
|
|
87
|
|
- -> Either StringLexError String
|
|
88
|
|
-lexStringWith processChars processCharsPos len buf =
|
|
|
67
|
+lexStringWith :: (String -> Maybe String) -> Int -> StringBuffer -> Either StringLexError String
|
|
|
68
|
+lexStringWith processChars len buf =
|
|
89
|
69
|
case processChars $ bufferChars buf len of
|
|
90
|
|
- Right s -> Right s
|
|
91
|
|
- Left _ ->
|
|
92
|
|
- case processCharsPos $ bufferLocatedChars buf len of
|
|
93
|
|
- Right _ -> panic "expected lex error on second pass"
|
|
94
|
|
- Left ((_, pos), e) -> Left $ StringLexError e pos
|
|
|
70
|
+ Just s -> Right s
|
|
|
71
|
+ Nothing -> do
|
|
|
72
|
+ validateString len buf -- should return Left
|
|
|
73
|
+ panic "expected lex error on second pass"
|
|
|
74
|
+
|
|
|
75
|
+-- | Find any lexical errors in the string.
|
|
|
76
|
+--
|
|
|
77
|
+-- Can validate both single- and multi-line strings, since multi-line strings
|
|
|
78
|
+-- have the same validation logic as single-line strings, and none of the
|
|
|
79
|
+-- multi-line string processing steps affect the validity of the string.
|
|
|
80
|
+validateString :: Int -> StringBuffer -> Either StringLexError ()
|
|
|
81
|
+validateString len buf =
|
|
|
82
|
+ case processCharsSingle $ bufferLocatedChars buf len of
|
|
|
83
|
+ Right _ -> Right ()
|
|
|
84
|
+ Left ((_, pos), e) -> Left $ StringLexError e pos
|
|
95
|
85
|
|
|
96
|
86
|
class HasChar c where
|
|
97
|
87
|
getChar :: c -> Char
|
| ... |
... |
@@ -128,6 +118,11 @@ bufferLocatedChars initialBuf len = go initialBuf |
|
128
|
118
|
-- -----------------------------------------------------------------------------
|
|
129
|
119
|
-- Lexing phases
|
|
130
|
120
|
|
|
|
121
|
+processCharsSingle :: HasChar c => [c] -> Either (c, LexErr) [c]
|
|
|
122
|
+processCharsSingle =
|
|
|
123
|
+ collapseGaps
|
|
|
124
|
+ >>> resolveEscapes
|
|
|
125
|
+
|
|
131
|
126
|
-- | Collapse all string gaps in the given input.
|
|
132
|
127
|
--
|
|
133
|
128
|
-- Iterates through the input in `go` until we encounter a backslash. The
|
| ... |
... |
@@ -185,6 +180,9 @@ resolveEscapes = go nilOL |
|
185
|
180
|
Left (c, e) -> Left (c, e)
|
|
186
|
181
|
c : cs -> go (acc `snocOL` c) cs
|
|
187
|
182
|
|
|
|
183
|
+resolveEscapesMaybe :: HasChar c => [c] -> Maybe [c]
|
|
|
184
|
+resolveEscapesMaybe = fromRight . resolveEscapes
|
|
|
185
|
+
|
|
188
|
186
|
-- -----------------------------------------------------------------------------
|
|
189
|
187
|
-- Escape characters
|
|
190
|
188
|
|
| ... |
... |
@@ -284,20 +282,7 @@ isSingleSmartQuote = \case |
|
284
|
282
|
_ -> False
|
|
285
|
283
|
|
|
286
|
284
|
-- -----------------------------------------------------------------------------
|
|
287
|
|
--- StringMeta
|
|
288
|
|
-
|
|
289
|
|
-data StringMeta = StringMeta
|
|
290
|
|
- { strMetaMultiline :: Bool
|
|
291
|
|
- , strMetaQualified :: Maybe ModuleName
|
|
292
|
|
- }
|
|
293
|
|
- deriving (Show, Data)
|
|
294
|
|
-
|
|
295
|
|
-defaultStrMeta :: StringMeta
|
|
296
|
|
-defaultStrMeta =
|
|
297
|
|
- StringMeta
|
|
298
|
|
- { strMetaMultiline = False
|
|
299
|
|
- , strMetaQualified = Nothing
|
|
300
|
|
- }
|
|
|
285
|
+-- QualifiedStrings
|
|
301
|
286
|
|
|
302
|
287
|
{- Note [Implementation of QualifiedStrings]
|
|
303
|
288
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... |
... |
@@ -353,88 +338,60 @@ to standard string literals for overlap checking, ensuring that |
|
353
|
338
|
-- and rejoining lines, and instead manually find newline characters,
|
|
354
|
339
|
-- for performance.
|
|
355
|
340
|
lexMultilineString :: Int -> StringBuffer -> Either StringLexError String
|
|
356
|
|
-lexMultilineString = lexStringWith processChars processChars
|
|
|
341
|
+lexMultilineString = lexStringWith processCharsMulti
|
|
|
342
|
+
|
|
|
343
|
+processCharsMulti :: String -> Maybe String
|
|
|
344
|
+processCharsMulti =
|
|
|
345
|
+ collapseGaps -- Step 1
|
|
|
346
|
+ >>> normalizeEOL
|
|
|
347
|
+ >>> expandLeadingTabs -- Step 3
|
|
|
348
|
+ >>> rmCommonWhitespacePrefix -- Step 4
|
|
|
349
|
+ >>> collapseOnlyWsLines -- Step 5
|
|
|
350
|
+ >>> rmFirstNewline -- Step 7a
|
|
|
351
|
+ >>> rmLastNewline -- Step 7b
|
|
|
352
|
+ >>> resolveEscapesMaybe -- Step 8
|
|
|
353
|
+
|
|
|
354
|
+-- Normalize line endings to LF. The spec dictates that lines should be
|
|
|
355
|
+-- split on newline characters and rejoined with ``\n``. But because we
|
|
|
356
|
+-- aren't actually splitting/rejoining, we'll manually normalize here
|
|
|
357
|
+normalizeEOL :: String -> String
|
|
|
358
|
+normalizeEOL = go
|
|
357
|
359
|
where
|
|
358
|
|
- processChars :: HasChar c => [c] -> Either (c, LexErr) [c]
|
|
359
|
|
- processChars =
|
|
360
|
|
- collapseGaps -- Step 1
|
|
361
|
|
- >>> normalizeEOL
|
|
362
|
|
- >>> expandLeadingTabs -- Step 3
|
|
363
|
|
- >>> rmCommonWhitespacePrefix -- Step 4
|
|
364
|
|
- >>> collapseOnlyWsLines -- Step 5
|
|
365
|
|
- >>> rmFirstNewline -- Step 7a
|
|
366
|
|
- >>> rmLastNewline -- Step 7b
|
|
367
|
|
- >>> resolveEscapes -- Step 8
|
|
368
|
|
-
|
|
369
|
|
- -- Normalize line endings to LF. The spec dictates that lines should be
|
|
370
|
|
- -- split on newline characters and rejoined with ``\n``. But because we
|
|
371
|
|
- -- aren't actually splitting/rejoining, we'll manually normalize here
|
|
372
|
|
- normalizeEOL :: HasChar c => [c] -> [c]
|
|
373
|
|
- normalizeEOL =
|
|
374
|
|
- let go = \case
|
|
375
|
|
- Char '\r' : c@(Char '\n') : cs -> c : go cs
|
|
376
|
|
- c@(Char '\r') : cs -> setChar '\n' c : go cs
|
|
377
|
|
- c@(Char '\f') : cs -> setChar '\n' c : go cs
|
|
378
|
|
- c : cs -> c : go cs
|
|
379
|
|
- [] -> []
|
|
380
|
|
- in go
|
|
381
|
|
-
|
|
382
|
|
- -- expands all tabs, since the lexer will verify that tabs can only appear
|
|
383
|
|
- -- as leading indentation
|
|
384
|
|
- expandLeadingTabs :: HasChar c => [c] -> [c]
|
|
385
|
|
- expandLeadingTabs =
|
|
386
|
|
- let go !col = \case
|
|
387
|
|
- c@(Char '\t') : cs ->
|
|
388
|
|
- let fill = 8 - (col `mod` 8)
|
|
389
|
|
- in replicate fill (setChar ' ' c) ++ go (col + fill) cs
|
|
390
|
|
- c : cs -> c : go (if getChar c == '\n' then 0 else col + 1) cs
|
|
391
|
|
- [] -> []
|
|
392
|
|
- in go 0
|
|
|
360
|
+ go = \case
|
|
|
361
|
+ Char '\r' : c@(Char '\n') : cs -> c : go cs
|
|
|
362
|
+ c@(Char '\r') : cs -> setChar '\n' c : go cs
|
|
|
363
|
+ c@(Char '\f') : cs -> setChar '\n' c : go cs
|
|
|
364
|
+ c : cs -> c : go cs
|
|
|
365
|
+ [] -> []
|
|
|
366
|
+
|
|
|
367
|
+-- | Expands all tabs blindly, since the lexer will verify that tabs can only appear
|
|
|
368
|
+-- as leading indentation
|
|
|
369
|
+expandLeadingTabs :: String -> String
|
|
|
370
|
+expandLeadingTabs = go 0
|
|
|
371
|
+ where
|
|
|
372
|
+ go !col = \case
|
|
|
373
|
+ '\t' : cs ->
|
|
|
374
|
+ let fill = 8 - (col `mod` 8)
|
|
|
375
|
+ in replicate fill ' ' ++ go (col + fill) cs
|
|
|
376
|
+ c : cs -> c : go (if c == '\n' then 0 else col + 1) cs
|
|
|
377
|
+ [] -> []
|
|
|
378
|
+
|
|
|
379
|
+rmCommonWhitespacePrefix :: String -> String
|
|
|
380
|
+rmCommonWhitespacePrefix cs0 = go cs0
|
|
|
381
|
+ where
|
|
|
382
|
+ commonWSPrefix = getCommonWsPrefix cs0
|
|
393
|
383
|
|
|
394
|
|
- rmCommonWhitespacePrefix :: HasChar c => [c] -> [c]
|
|
395
|
|
- rmCommonWhitespacePrefix cs0 =
|
|
396
|
|
- let commonWSPrefix = getCommonWsPrefix (map getChar cs0)
|
|
397
|
|
- go = \case
|
|
398
|
|
- c@(Char '\n') : cs -> c : go (dropLine commonWSPrefix cs)
|
|
399
|
|
- c : cs -> c : go cs
|
|
400
|
|
- [] -> []
|
|
401
|
|
- -- drop x characters from the string, or up to a newline, whichever
|
|
402
|
|
- -- comes first
|
|
403
|
|
- dropLine !x = \case
|
|
404
|
|
- cs | x <= 0 -> cs
|
|
405
|
|
- cs@(Char '\n' : _) -> cs
|
|
406
|
|
- _ : cs -> dropLine (x - 1) cs
|
|
407
|
|
- [] -> []
|
|
408
|
|
- in go cs0
|
|
409
|
|
-
|
|
410
|
|
- collapseOnlyWsLines :: HasChar c => [c] -> [c]
|
|
411
|
|
- collapseOnlyWsLines =
|
|
412
|
|
- let go = \case
|
|
413
|
|
- c@(Char '\n') : cs | Just cs' <- checkAllWs cs -> c : go cs'
|
|
414
|
|
- c : cs -> c : go cs
|
|
415
|
|
- [] -> []
|
|
416
|
|
- checkAllWs = \case
|
|
417
|
|
- -- got all the way to a newline or the end of the string, return
|
|
418
|
|
- cs@(Char '\n' : _) -> Just cs
|
|
419
|
|
- cs@[] -> Just cs
|
|
420
|
|
- -- found whitespace, continue
|
|
421
|
|
- Char c : cs | is_space c -> checkAllWs cs
|
|
422
|
|
- -- anything else, stop
|
|
423
|
|
- _ -> Nothing
|
|
424
|
|
- in go
|
|
425
|
|
-
|
|
426
|
|
- rmFirstNewline :: HasChar c => [c] -> [c]
|
|
427
|
|
- rmFirstNewline = \case
|
|
428
|
|
- Char '\n' : cs -> cs
|
|
429
|
|
- cs -> cs
|
|
430
|
|
-
|
|
431
|
|
- rmLastNewline :: HasChar c => [c] -> [c]
|
|
432
|
|
- rmLastNewline =
|
|
433
|
|
- let go = \case
|
|
434
|
|
- [] -> []
|
|
435
|
|
- [Char '\n'] -> []
|
|
436
|
|
- c : cs -> c : go cs
|
|
437
|
|
- in go
|
|
|
384
|
+ go = \case
|
|
|
385
|
+ c@'\n' : cs -> c : go (dropLine commonWSPrefix cs)
|
|
|
386
|
+ c : cs -> c : go cs
|
|
|
387
|
+ [] -> []
|
|
|
388
|
+
|
|
|
389
|
+ -- drop x characters from the string, or up to a newline, whichever comes first
|
|
|
390
|
+ dropLine !x = \case
|
|
|
391
|
+ cs | x <= 0 -> cs
|
|
|
392
|
+ cs@('\n' : _) -> cs
|
|
|
393
|
+ _ : cs -> dropLine (x - 1) cs
|
|
|
394
|
+ [] -> []
|
|
438
|
395
|
|
|
439
|
396
|
-- | See step 4 in Note [Multiline string literals]
|
|
440
|
397
|
--
|
| ... |
... |
@@ -450,6 +407,36 @@ getCommonWsPrefix s = |
|
450
|
407
|
. drop 1 -- ignore first line in calculation
|
|
451
|
408
|
$ lines s
|
|
452
|
409
|
|
|
|
410
|
+collapseOnlyWsLines :: String -> String
|
|
|
411
|
+collapseOnlyWsLines = go
|
|
|
412
|
+ where
|
|
|
413
|
+ go = \case
|
|
|
414
|
+ c@'\n' : cs | Just cs' <- checkAllWs cs -> c : go cs'
|
|
|
415
|
+ c : cs -> c : go cs
|
|
|
416
|
+ [] -> []
|
|
|
417
|
+
|
|
|
418
|
+ checkAllWs = \case
|
|
|
419
|
+ -- got all the way to a newline or the end of the string, return
|
|
|
420
|
+ cs@('\n' : _) -> Just cs
|
|
|
421
|
+ cs@[] -> Just cs
|
|
|
422
|
+ -- found whitespace, continue
|
|
|
423
|
+ c : cs | is_space c -> checkAllWs cs
|
|
|
424
|
+ -- anything else, stop
|
|
|
425
|
+ _ -> Nothing
|
|
|
426
|
+
|
|
|
427
|
+rmFirstNewline :: String -> String
|
|
|
428
|
+rmFirstNewline = \case
|
|
|
429
|
+ '\n' : cs -> cs
|
|
|
430
|
+ cs -> cs
|
|
|
431
|
+
|
|
|
432
|
+rmLastNewline :: String -> String
|
|
|
433
|
+rmLastNewline = go
|
|
|
434
|
+ where
|
|
|
435
|
+ go = \case
|
|
|
436
|
+ [] -> []
|
|
|
437
|
+ ['\n'] -> []
|
|
|
438
|
+ c : cs -> c : go cs
|
|
|
439
|
+
|
|
453
|
440
|
{-
|
|
454
|
441
|
Note [Multiline string literals]
|
|
455
|
442
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... |
... |
@@ -488,3 +475,9 @@ It's more precisely defined with the following algorithm: |
|
488
|
475
|
* Lines with only whitespace characters
|
|
489
|
476
|
3. Calculate the longest prefix of whitespace shared by all lines in the remaining list
|
|
490
|
477
|
-}
|
|
|
478
|
+
|
|
|
479
|
+-- -----------------------------------------------------------------------------
|
|
|
480
|
+-- Other utilities
|
|
|
481
|
+
|
|
|
482
|
+fromRight :: Either e a -> Maybe a
|
|
|
483
|
+fromRight = either (const Nothing) Just |