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