Skip to content

Commit 74e477a

Browse files
authored
Merge pull request #35 from starkstark/master
added Indent.purs
2 parents da38585 + 051ca05 commit 74e477a

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed

src/Text/Parsing/Parser/Indent.purs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
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

Comments
 (0)