-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathParser.hs
More file actions
152 lines (119 loc) · 5.17 KB
/
Copy pathParser.hs
File metadata and controls
152 lines (119 loc) · 5.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
module Parser (runParser, parseCmd, parseInput) where
import Cmd
import Data.Maybe
import Data.Char
import Control.Applicative
-- We begin by defining the type of parsers for a type:
newtype Parser tok a = Parser { runParser :: [tok] -> Maybe (a,[tok]) }
-- The idea is that a value of type Parser tok a is something that
-- takes a string of tokens as input, and tries to parse a prefix of the
-- input as a value of type a. If it succeeds, it returns "Just" of a
-- value of type (a,[tok]), where the second component is the suffix of
-- remaining tokens. Otherwise it returns "Nothing".
-- Or in other words, adapting a poem by Graham Hutton,
-- A parser for things
-- Is a function from strings
-- To maybe a pair
-- Of a thing and a string!
-- Anyways, we will use the fact that for any type `tok` of tokens,
-- `Parser tok` defines a monad.
instance Monad (Parser tok) where
-- return :: a -> Parser tok a
return x = Parser (\ts -> Just (x,ts))
-- (>>=) :: Parser a -> (a -> Parser tok b) -> Parser tok b
p >>= f = Parser (\ts -> case runParser p ts of
Nothing -> Nothing
Just (x,ts') -> runParser (f x) ts')
-- We add some boilerplate code to derive Functor and Applicative
-- instances from the Monad instance
instance Functor (Parser tok) where
fmap f p = p >>= \x -> return (f x)
instance Applicative (Parser tok) where
pure = return
pf <*> p = pf >>= \f -> p >>= \x -> return (f x)
-- Note that the type Parser tok a is isomorphic to StateT [tok] Maybe a,
-- and we could have defined it that way to automatically derive all
-- these type class instances. But we prefer to do it for ourselves.
-- We also define an Alternative instance, which makes it convenient
-- to write backtracking parsers.
instance Alternative (Parser tok) where
-- empty :: Parser tok a
empty = Parser (\ts -> Nothing)
-- (<|>) :: Parser tok a -> Parser tok a -> Parser tok a
p1 <|> p2 = Parser (\ts -> case runParser p1 ts of
Just (x,ts') -> Just (x,ts')
Nothing -> runParser p2 ts)
-- The idea is that "empty" is a parser that always fails, while
-- p1 <|> p2 is a parser that first tries to parse a string of tokens using p1,
-- and if that fails tries parsing the same string using p2.
-- Now we define parsers for various kinds of basic stuff.
-- The "token" parser just reads one token of the input and returns it.
-- Note there must be at least one token for item to succeed.
token :: Parser tok tok
token = Parser $ \ts -> case ts of
[] -> Nothing
(t:ts') -> Just (t,ts')
-- The "sat p" parser matches a token satisfying the predicate p.
sat :: (tok -> Bool) -> Parser tok tok
sat p = do
t <- token
if p t then return t else empty
-- Now we move onto our actual example of interest.
-- Our parsers will assume that the input string has already been split
-- up into a space-separated list of words, and thus use `String` as the
-- basic token type from now on.
-- It will be useful to have a parser that consumes a token matching a
-- specific string and ignoring case. This is achieved by "match s".
match :: String -> Parser String String
match s = sat (\s' -> map toLower s == map toLower s')
-- We parse English number words as numbers (restricted to numbers
-- between one and nine).
number :: Parser String Int
number = do
(match "one" >> return 1) <|> (match "two" >> return 2) <|>
(match "three" >> return 3) <|> (match "four" >> return 4) <|>
(match "five" >> return 5) <|> (match "six" >> return 6) <|>
(match "seven" >> return 7) <|> (match "eight" >> return 8) <|>
(match "nine" >> return 9)
-- parseCmd is our general-purpose parser for commands, which can be
-- either climbing commands, meditation commands, or quitting.
parseCmd :: Parser String Cmd
parseCmd = parseClimb <|> parseQuit <|> parseShow <|> parseTeleport <|> parseWait
-- Parse a climbing command.
parseClimb :: Parser String Cmd
parseClimb = do
match "climb" <|> match "go"
(match "down" >> return Go_Down) <|>
(match "left" >> return Go_Left) <|>
(match "right" >> return Go_Right)
-- Parse a meditation command.
{-parseMeditate :: Parser String Cmd
parseMeditate = do
match "meditate"
match "for"
n <- number
if n == 1 then match "second" else match "seconds"
return (Meditate n)-}
-- Parse a quit command
parseQuit :: Parser String Cmd
parseQuit = do
match "quit" <|> match "q"
return Quit
parseShow :: Parser String Cmd
parseShow = do
match "show" <|> match "s"
return Show
parseTeleport :: Parser String Cmd
parseTeleport = do
match "teleport"
return Teleport
parseWait :: Parser String Cmd
parseWait = do
match "wait"
return Wait
-- Finally, we export a function that runs a parser on the entire input string, broken up into words.
-- This function runs in any MonadFail monad, to deal with the possiblity of failure.
parseInput :: MonadFail m => Parser String a -> String -> m a
parseInput p s = case runParser p (words s) of
Just (x,ts') -> if null ts' then return x else fail "parseInput: some tokens left"
Nothing -> fail "parseInput: failed to parse"