-
Notifications
You must be signed in to change notification settings - Fork 0
/
ASTParse.hs
202 lines (171 loc) · 5.87 KB
/
ASTParse.hs
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
module ASTParse where
import Lib.Parser
import Lib.Monads
import AST
import Data.List
import Control.Applicative
import Control.Arrow (first, second)
{- A parser for the AST.
-
- Either call:
- ``unsafeParseProg inputString``
- or
- ``parseProg inputString``
-
- to parse a program.
-
- Known issues:
- - It is a bit slow on large inputs...
-
- This is provided FOR CONVENIENCE ONLY and may or may not have bugs
- in it.
-}
unsafeParseProg :: String -> Prog String String
unsafeParseProg inp = case parseProg inp of
Right prog -> prog
Left err -> error err
parseProg :: String -> Either String (Prog String String)
parseProg inp = Prog <$> parseFuns inp
---------------------------------------------------------
junk :: Parser ()
junk = (localDefRowCol (const (-1,-1))
$ firstParse
$ many (comment <|> multilineComment <|> spaces)) >> return ()
comment :: Parser ()
comment = (string "--" >> many (sat (/='\n')) >> return ())
multilineComment :: Parser ()
multilineComment = (string "{-" >> many (sat (const True)) >> string "-}" >> return ())
token :: Parser a -> Parser a
token pa = pa <* junk
symbol :: String -> Parser String
symbol str = string str <* junk
natural :: Parser Int
natural = nat <* junk
keywords :: [String]
keywords = ["let", "in", "fun", "if", "then", "else", "end", "not"]
identifier :: Parser String
identifier = token $ do
c <- lower
cs <- firstParse (many (alphaNum <|> char '_'))
let ident = c:cs
if ident `notElem` keywords
then return ident
else empty
parseFuns :: String -> Either String [Fun String String]
parseFuns inp = case runParser pa inp of
[Right (vals, (_, ""))] -> return vals
[Right (_, (loc, rst))] -> Left $ concat
[ "Could not parse input at "
, "row "
, show (fst loc)
, " and column "
, show (snd loc)
, " with inputs:\n"
, rst
]
[Left err] -> Left $ err
[] -> Left $ "Invalid parse"
as -> Left $ "An unknown error occured. Here are all the parse attempts: " ++ show as
where
pa = junk >> offsideMany fun
fun :: Parser (Fun String String)
fun = do
symbol "fun" <?> "Expected 'fun' keyword at start of function declaration."
fname <- identifier <?> "Expected function identifier after 'fun' keyword."
symbol "(" <?> concat
[ "Expected '(' after function identifier before arguments. "
, "Perhaps you meant to write: ``"
, "fun " ++ fname ++ "(<arguments>) = <function body>``"
]
args <- (do
arg <- identifier
args <- firstParse (many (symbol "," >> identifier))
return $ arg:args
) <|> return []
symbol ")" <?> "Expected ')' after function arguments before function definition."
symbol "=" <?> "Expected '=' sign after arguments."
expr' <- expr
return $ Fun (fname, args, expr')
expr :: Parser (Exp String String)
expr = chainl1 expr0 ((symbol "+" >> pure ADD) <|> (symbol "-" >> pure SUB))
expr0 :: Parser (Exp String String)
expr0 = chainl1 expr1 ((symbol "*" >> pure MUL) <|> (symbol "/" >> pure DIV))
expr1 :: Parser (Exp String String)
expr1 = firstParse $ (CONST <$> natural)
<|> varfun
<|> cond
<|> letbind
<|> neg
<|> surrounded (symbol "(") expr (symbol ")")
neg :: Parser (Exp String String)
neg = do
symbol "-"
expr' <- expr
return $ NEG expr'
cond :: Parser (Exp String String)
cond = do
symbol "if"
bexpr' <- bexpr
symbol "then" <?> "Expected 'then'."
expr' <- expr
symbol "else" <?> "Expected 'else'."
expr'' <- expr
return $ COND bexpr' expr' expr''
data AppParse
= VarParse
| ArgedFunParse [Exp String String]
varfun :: Parser (Exp String String)
varfun = do
f' <- fmap f $ do
ident <- identifier
-- A very cheap hack to help give informative error messages for some cases when you
-- forget that function application isn't space...
idents' <- many (identifier <|> (show <$> natural) <|> (show <$> (symbol "-" >> natural )))
if null idents'
then return ident
else Parser $ lift $ lift $ throwError $ concat
[ "Illegal expression: ``"
, intercalate " " $ ident : idents'
, "``. Perhaps you meant to use function application, so write: "
, ident ++ "(" ++ intercalate "," idents' ++ ")"
]
argparse <- argparse
return $ f' argparse
where
f name VarParse = VAR name
f name (ArgedFunParse args) = APP name args
argparse :: Parser AppParse
argparse =
(symbol "(" >> argedfunparse)
<|> varparse
where
argedfunparse = (do
expr' <- expr
exprs' <- many $ do
symbol ","
expr <?> "Expected comma seperated arguments in function call."
symbol ")"
return $ ArgedFunParse $ expr' : exprs'
)
<|> (symbol ")" >> return (ArgedFunParse []))
varparse = return VarParse
letbind :: Parser (Exp String String)
letbind = do
symbol "let"
funs <- offsideSome fun
symbol "in" <?> "Expected 'in' keyword to seperate let declarations and body"
expr' <- expr
symbol "end" <?> "Expected 'end' keyword after function let statement"
return $ LET funs expr'
bexpr :: Parser (BExp String String)
bexpr = chainr1 bterm0 (symbol "||" >> pure OR)
where
bterm0 :: Parser (BExp String String)
bterm0 = chainr1 bterm1 (symbol "&&" >> pure AND)
bterm1 :: Parser (BExp String String)
bterm1 =
(symbol "not" >> surrounded (symbol "(") bexpr (symbol ")"))
<|> (do l <- expr ; _ <- symbol "<" ; r <- expr ; return $ Lt l r)
<|> (do l <- expr ; _ <- symbol ">" ; r <- expr ; return $ Gt l r)
<|> (do l <- expr ; _ <- symbol "==" ; r <- expr ; return $ Eq l r)
<|> surrounded (symbol "(") bexpr (symbol ")")