1
+ module Text.Parsing.Indent (
2
+ -- $doc
3
+ -- * Types
4
+ IndentParser , runIndent ,
5
+ -- * Blocks
6
+ withBlock , withBlock' , block , block1 ,
7
+ -- * Indentation Checking
8
+ indented , indented' , sameLine , sameOrIndented , checkIndent , withPos ,
9
+ -- * Paired characters
10
+ -- indentBrackets, indentAngles, indentBraces, indentParens,
11
+ -- * Line Fold Chaining
12
+ -- | Any chain using these combinators must used with 'withPos'
13
+ indentAp , (<+/>), indentNoAp , (<-/>), indentMany , (<*/>), indentOp , (<?/>), Optional (..)
14
+ ) where
15
+
16
+ import Prelude (class Monad , Unit , id , ap , const , ($), flip , unit , pure , (==), bind , (<=))
17
+ import Data.List (List (..), many )
18
+ import Data.Maybe (Maybe (..))
19
+
20
+ import Control.Alt ((<|>))
21
+ import Control.Apply ((*>), lift2 )
22
+ import Control.Monad.Trans (lift )
23
+ import Control.Monad.State (State , evalState )
24
+ import Control.Monad.State.Trans (get , put )
25
+ import Data.Either (Either (..))
26
+
27
+ import Text.Parsing.Parser (ParseError , ParserT (..), PState (..), fail )
28
+ import Text.Parsing.Parser.Combinators
29
+ import Text.Parsing.Parser.Pos (Position (..), initialPos )
30
+ import Text.Parsing.Parser.String (string , oneOf )
31
+
32
+ -- $doc
33
+ -- This is purescript-port of Text.Parsing.Indent
34
+ -- https://hackage.haskell.org/package/indents-0.3.3/docs/Text-Parsec-Indent.html, 05.07.2016
35
+
36
+ -- A module to construct indentation aware parsers. Many programming
37
+ -- language have indentation based syntax rules e.g. python and Haskell.
38
+ -- This module exports combinators to create such parsers.
39
+ --
40
+ -- The input source can be thought of as a list of tokens. Abstractly
41
+ -- each token occurs at a line and a column and has a width. The column
42
+ -- number of a token measures is indentation. If t1 and t2 are two tokens
43
+ -- then we say that indentation of t1 is more than t2 if the column
44
+ -- number of occurrence of t1 is greater than that of t2.
45
+ --
46
+ -- Currently this module supports two kind of indentation based syntactic
47
+ -- structures which we now describe:
48
+ --
49
+ -- [Block] --A block of indentation /c/ is a sequence of tokens with
50
+ -- indentation at least /c/. Examples for a block is a where clause of
51
+ -- Haskell with no explicit braces.
52
+ --
53
+ -- [Line fold] A line fold starting at line /l/ and indentation /c/ is a
54
+ -- sequence of tokens that start at line /l/ and possibly continue to
55
+ -- subsequent lines as long as the indentation is greater than /c/. Such
56
+ -- a sequence of lines need to be /folded/ to a single line. An example
57
+ -- is MIME headers. Line folding based binding separation is used in
58
+ -- Haskell as well.
59
+
60
+ -- | Indentation sensitive parser type. Usually @ m @ will
61
+ -- be @ Identity @ as with any @ ParserT @
62
+ type IndentParser s a = ParserT s (State Position ) a
63
+
64
+
65
+ -- | @ getPosition @ returns current position
66
+ -- should probably be added to Text.Parsing.Parser.Pos
67
+ getPosition :: forall m s . (Monad m ) => ParserT s m Position
68
+ getPosition = ParserT $ \(PState { input: (i :: s ), position: (pos :: Position )}) -> pure {input: (i :: s ), result: (Right (pos :: Position )) :: Either ParseError Position , consumed : false , position : (pos :: Position )}
69
+
70
+ -- | simple helper function to avoid typ-problems with MonadState instance
71
+ get' :: forall s . IndentParser s Position
72
+ get' = do
73
+ g <- lift get
74
+ pure g
75
+
76
+ -- | simple helper function to avoid typ-problems with MonadState instance
77
+ put' :: forall s . Position -> IndentParser s Unit
78
+ put' p = lift (put p)
79
+
80
+ sourceColumn :: Position -> Int
81
+ sourceColumn (Position {line: _, column: c}) = c
82
+
83
+ sourceLine :: Position -> Int
84
+ sourceLine (Position {line: l, column: _}) = l
85
+
86
+ setSourceLine :: Position -> Int -> Position
87
+ setSourceLine (Position {line: _, column: c}) l = Position {line: l, column: c}
88
+
89
+ biAp :: forall a b c . (a -> b ) -> (b -> b -> c ) -> a -> a -> c
90
+ biAp f c v1 v2 = c (f v1) (f v2)
91
+
92
+ -- | @ many1 @ should prabably be inside Text.Parsing.Parser.Combinators
93
+ many1 :: forall s m a . (Monad m ) => ParserT s m a -> ParserT s m (List a )
94
+ many1 p = lift2 Cons p (many p)
95
+
96
+ symbol :: forall m . (Monad m ) => String -> ParserT String m String
97
+ symbol name = (many $ oneOf [' ' ,' \t ' ]) *> (string name)
98
+
99
+ -- --------------------------------------------------------------------
100
+
101
+ -- | @ 'withBlock' f a p @ parses @ a @
102
+ -- followed by an indented block of @ p @
103
+ -- combining them with @ f @
104
+ withBlock :: forall a b c s . (a -> List b -> c ) -> IndentParser s a -> IndentParser s b -> IndentParser s c
105
+ withBlock f a p = withPos $ do
106
+ r1 <- a
107
+ r <- optionMaybe $ indented *> block p
108
+ case r of
109
+ Nothing -> pure (f r1 Nil )
110
+ Just r2 -> pure (f r1 r2)
111
+
112
+ -- | Like 'withBlock', but throws away initial parse result
113
+ withBlock' :: forall a b s . IndentParser s a -> IndentParser s b -> IndentParser s (List b )
114
+ withBlock' = withBlock (flip const)
115
+
116
+ -- | Parses only when indented past the level of the reference
117
+ indented :: forall s . IndentParser s Unit
118
+ indented = do
119
+ pos <- getPosition
120
+ s <- get'
121
+ if biAp sourceColumn (<=) pos s then fail " not indented" else do
122
+ put' $ setSourceLine s (sourceLine pos)
123
+ pure unit
124
+
125
+ -- | same as 'indented', but does not change internal state
126
+ indented' :: forall s . IndentParser s Unit
127
+ indented' = do
128
+ pos <- getPosition
129
+ s <- get'
130
+ if biAp sourceColumn (<=) pos s then fail " not indented" else pure unit
131
+
132
+ -- | Parses only when indented past the level of the reference or on the same line
133
+ sameOrIndented :: forall s . IndentParser s Unit
134
+ sameOrIndented = sameLine <|> indented
135
+
136
+ -- | Parses only on the same line as the reference
137
+ sameLine :: forall s . IndentParser s Unit
138
+ sameLine = do
139
+ pos <- getPosition
140
+ s <- get'
141
+ if biAp sourceLine (==) pos s then pure unit else fail " over one line"
142
+
143
+ -- | Parses a block of lines at the same indentation level
144
+ block1 :: forall s a . IndentParser s a -> IndentParser s (List a )
145
+ block1 p = withPos $ do
146
+ r <- many1 $ checkIndent *> p
147
+ pure r
148
+
149
+ -- | Parses a block of lines at the same indentation level , empty Blocks allowed
150
+ block :: forall s a . IndentParser s a -> IndentParser s (List a )
151
+ block p = withPos $ do
152
+ r <- many $ checkIndent *> p
153
+ pure r
154
+
155
+ -- | Parses using the current location for indentation reference
156
+ withPos :: forall s a . IndentParser s a -> IndentParser s a
157
+ withPos x = do
158
+ a <- get'
159
+ p <- getPosition
160
+ r <- put' p *> x
161
+ put' a *> pure r
162
+
163
+ -- | Ensures the current indentation level matches that of the reference
164
+ checkIndent :: forall s . IndentParser s Unit
165
+ checkIndent = do
166
+ s <- get'
167
+ p <- getPosition
168
+ if biAp sourceColumn (==) p s then pure unit else fail " indentation doesn't match"
169
+
170
+ -- | Run the result of an indentation sensitive parse
171
+ runIndent :: forall a . State Position a -> a
172
+ runIndent = flip evalState initialPos
173
+
174
+ -- | '<+/>' is to indentation sensitive parsers what 'ap' is to monads
175
+ indentAp :: forall s a b . IndentParser s (a -> b ) -> IndentParser s a -> IndentParser s b
176
+ indentAp a b = ap a $ sameOrIndented *> b
177
+
178
+ infixl 9 indentAp as <+/>
179
+
180
+ -- | Like '<+/>' but doesn't apply the function to the parsed value
181
+ indentNoAp :: forall s a b . IndentParser s a -> IndentParser s b -> IndentParser s a
182
+ indentNoAp a b = lift2 const a $ sameOrIndented *> b
183
+
184
+ infixl 10 indentNoAp as <-/>
185
+
186
+ -- | Like '<+/>' but applies the second parser many times
187
+ indentMany :: forall s a b . IndentParser s (List a -> b ) -> IndentParser s a -> IndentParser s b
188
+ indentMany a b = ap a (many (sameOrIndented *> b))
189
+
190
+ infixl 11 indentMany as <*/>
191
+
192
+ -- | Datatype used to optional parsing
193
+ data Optional s a = Opt a (IndentParser s a )
194
+
195
+ -- | Like '<+/>' but applies the second parser optionally using the 'Optional' datatype
196
+ indentOp :: forall s a b . IndentParser s (a -> b ) -> (Optional s a ) -> IndentParser s b
197
+ indentOp a (Opt b c) = ap a (option b (sameOrIndented *> c))
198
+
199
+ infixl 12 indentOp as <?/>
200
+
201
+ -- | parses with surrounding brackets
202
+ indentBrackets :: forall a . IndentParser String a -> IndentParser String a
203
+ indentBrackets p = withPos $ pure id <- /> symbol " [" <+/> p <- /> symbol " ]"
204
+
205
+ -- | parses with surrounding angle brackets
206
+ indentAngles :: forall a . IndentParser String a -> IndentParser String a
207
+ indentAngles p = withPos $ pure id <- /> symbol " <" <+/> p <- /> symbol " >"
208
+
209
+ -- | parses with surrounding braces
210
+ indentBraces :: forall a . IndentParser String a -> IndentParser String a
211
+ indentBraces p = withPos $ pure id <- /> symbol " {" <+/> p <- /> symbol " }"
212
+
213
+ -- | parses with surrounding parentheses
214
+ indentParens :: forall a . IndentParser String a -> IndentParser String a
215
+ indentParens p = withPos $ pure id <- /> symbol " (" <+/> p <- /> symbol " )"
0 commit comments