-
Notifications
You must be signed in to change notification settings - Fork 17
/
Parser.hs
422 lines (350 loc) · 13 KB
/
Parser.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
{-# OPTIONS_GHC -fno-warn-hi-shadowing
-fno-warn-name-shadowing
-fno-warn-unused-do-bind #-}
{-# LANGUAGE LambdaCase, TupleSections #-}
module Language.Lua.Annotated.Parser
( parseText
, parseFile
, stat
, exp
, chunk
) where
import Prelude hiding (exp, LT, GT, EQ)
import Language.Lua.Annotated.Lexer
import Language.Lua.Annotated.Syntax
import Language.Lua.Token
import Text.Parsec hiding (string)
import Text.Parsec.LTok
import Control.Applicative ((<*), (<$>), (<*>))
import Control.Monad (liftM)
-- | Runs Lua lexer before parsing. Use @parseText stat@ to parse
-- statements, and @parseText exp@ to parse expressions.
parseText :: Parser a -> String -> Either ParseError a
parseText p s = parse p "<string>" (llex s)
-- | Parse a Lua file. You can use @parseText chunk@ to parse a file from a string.
parseFile :: FilePath -> IO (Either ParseError (Block SourcePos))
parseFile path = parse chunk path . llex <$> readFile path
parens :: Monad m => ParsecT [LTok] u m a -> ParsecT [LTok] u m a
parens = between (tok LTokLParen) (tok LTokRParen)
brackets :: Monad m => ParsecT [LTok] u m a -> ParsecT [LTok] u m a
brackets = between (tok LTokLBracket) (tok LTokRBracket)
name :: Parser (Name SourcePos)
name = do
pos <- getPosition
str <- anyIdent
return $ Name pos str
data PrimaryExp a
= PName a (Name a)
| PParen a (Exp a)
deriving (Show, Eq)
data SuffixedExp a
= SuffixedExp a (PrimaryExp a) [SuffixExp a]
deriving (Show, Eq)
data SuffixExp a
= SSelect a (Name a)
| SSelectExp a (Exp a)
| SSelectMethod a (Name a) (FunArg a)
| SFunCall a (FunArg a)
deriving (Show, Eq)
primaryExp :: Parser (PrimaryExp SourcePos)
primaryExp = do
pos <- getPosition
PName pos <$> name <|> PParen pos <$> parens exp
suffixedExp :: Parser (SuffixedExp SourcePos)
suffixedExp = SuffixedExp <$> getPosition <*> primaryExp <*> many suffixExp
suffixExp :: Parser (SuffixExp SourcePos)
suffixExp = selectName <|> selectExp <|> selectMethod <|> funarg
where selectName = SSelect <$> getPosition <*> (tok LTokDot >> name)
selectExp = SSelectExp <$> getPosition <*> brackets exp
selectMethod = do
pos <- getPosition
tok LTokColon
SSelectMethod pos <$> name <*> funArg
funarg = SFunCall <$> getPosition <*> funArg
sexpToPexp :: SuffixedExp SourcePos -> PrefixExp SourcePos
sexpToPexp (SuffixedExp _ t r) = case r of
[] -> t'
(SSelect pos sname:xs) -> iter xs (PEVar pos (SelectName pos t' sname))
(SSelectExp pos sexp:xs) -> iter xs (PEVar pos (Select pos t' sexp))
(SSelectMethod pos mname args:xs) -> iter xs (PEFunCall pos (MethodCall pos t' mname args))
(SFunCall pos args:xs) -> iter xs (PEFunCall pos (NormalFunCall pos t' args))
where t' :: PrefixExp SourcePos
t' = case t of
PName pos name -> PEVar pos (VarName pos name)
PParen pos exp -> Paren pos exp
iter :: [SuffixExp SourcePos] -> PrefixExp SourcePos -> PrefixExp SourcePos
iter [] pe = pe
iter (SSelect pos sname:xs) pe = iter xs (PEVar pos (SelectName pos pe sname))
iter (SSelectExp pos sexp:xs) pe = iter xs (PEVar pos (Select pos pe sexp))
iter (SSelectMethod pos mname args:xs) pe = iter xs (PEFunCall pos (MethodCall pos pe mname args))
iter (SFunCall pos args:xs) pe = iter xs (PEFunCall pos (NormalFunCall pos pe args))
-- TODO: improve error messages.
sexpToVar :: SuffixedExp SourcePos -> Parser (Var SourcePos)
sexpToVar (SuffixedExp pos (PName _ name) []) = return (VarName pos name)
sexpToVar (SuffixedExp _ _ []) = fail "syntax error"
sexpToVar sexp = case sexpToPexp sexp of
PEVar _ var -> return var
_ -> fail "syntax error"
sexpToFunCall :: SuffixedExp SourcePos -> Parser (FunCall SourcePos)
sexpToFunCall (SuffixedExp _ _ []) = fail "syntax error"
sexpToFunCall sexp = case sexpToPexp sexp of
PEFunCall _ funcall -> return funcall
_ -> fail "syntax error"
var :: Parser (Var SourcePos)
var = suffixedExp >>= sexpToVar
funCall :: Parser (FunCall SourcePos)
funCall = suffixedExp >>= sexpToFunCall
funArg :: Parser (FunArg SourcePos)
funArg = tableArg <|> stringArg <|> arglist
where tableArg = TableArg <$> getPosition <*> table
stringArg = StringArg <$> getPosition <*> stringlit
arglist = do
pos <- getPosition
parens (do exps <- exp `sepBy` tok LTokComma
return $ Args pos exps)
funBody :: Parser (FunBody SourcePos)
funBody = do
pos <- getPosition
(params, vararg) <- arglist
body <- block
tok LTokEnd
return $ FunBody pos params vararg body
where lastarg = do
pos <- getPosition
arg <- optionMaybe (tok LTokEllipsis <|> tok LTokComma)
case arg of
Just LTokEllipsis -> return (Just pos)
_ -> return Nothing
arglist = parens $ do
vars <- name `sepEndBy` tok LTokComma
vararg <- lastarg
return (vars, vararg)
block :: Parser (Block SourcePos)
block = do
pos <- getPosition
stats <- many stat
ret <- optionMaybe retstat
return $ Block pos stats ret
retstat :: Parser [Exp SourcePos]
retstat = do
tok LTokReturn
exps <- exp `sepBy` tok LTokComma
optional (tok LTokSemic)
return exps
tableField :: Parser (TableField SourcePos)
tableField = choice [ expField, try namedField, field ]
where expField :: Parser (TableField SourcePos)
expField = do
pos <- getPosition
e1 <- brackets exp
tok LTokAssign
e2 <- exp
return $ ExpField pos e1 e2
namedField :: Parser (TableField SourcePos)
namedField = do
pos <- getPosition
name' <- name
tok LTokAssign
val <- exp
return $ NamedField pos name' val
field :: Parser (TableField SourcePos)
field = Field <$> getPosition <*> exp
table :: Parser (Table SourcePos)
table = do
pos <- getPosition
between (tok LTokLBrace)
(tok LTokRBrace)
(do fields <- tableField `sepEndBy` fieldSep
return $ Table pos fields)
where fieldSep = tok LTokComma <|> tok LTokSemic
-----------------------------------------------------------------------
---- Expressions
nilExp, boolExp, numberExp, stringExp, varargExp, fundefExp,
prefixexpExp, tableconstExp, exp :: Parser (Exp SourcePos)
nilExp = (Nil <$> getPosition) <* tok LTokNil
boolExp = do
pos <- getPosition
tOrF <- tok LTokTrue <|> tok LTokFalse
return $ Bool pos (tOrF == LTokTrue)
numberExp = Number <$> getPosition <*> number
stringExp = String <$> getPosition <*> stringlit
varargExp = (Vararg <$> getPosition) <* tok LTokEllipsis
fundefExp = do
pos <- getPosition
tok LTokFunction
body <- funBody
return $ EFunDef pos (FunDef (ann body) body)
prefixexpExp = PrefixExp <$> getPosition <*> liftM sexpToPexp suffixedExp
tableconstExp = TableConst <$> getPosition <*> table
type Binop' = Exp SourcePos -> Exp SourcePos -> Exp SourcePos
type Unop' = Exp SourcePos -> Exp SourcePos
binop :: Parser (Binop', Int, Int)
binop = do
pos <- getPosition
choice
[ tok LTokPlus >> return (Binop pos (Add pos), 10, 10)
, tok LTokMinus >> return (Binop pos (Sub pos), 10, 10)
, tok LTokStar >> return (Binop pos (Mul pos), 11, 11)
, tok LTokSlash >> return (Binop pos (Div pos), 11, 11)
, tok LTokExp >> return (Binop pos (Exp pos), 14, 13)
, tok LTokPercent >> return (Binop pos (Mod pos), 11, 11)
, tok LTokDDot >> return (Binop pos (Concat pos), 9, 8)
, tok LTokLT >> return (Binop pos (LT pos), 3, 3)
, tok LTokLEq >> return (Binop pos (LTE pos), 3, 3)
, tok LTokGT >> return (Binop pos (GT pos), 3, 3)
, tok LTokGEq >> return (Binop pos (GTE pos), 3, 3)
, tok LTokEqual >> return (Binop pos (EQ pos), 3, 3)
, tok LTokNotequal >> return (Binop pos (NEQ pos), 3, 3)
, tok LTokAnd >> return (Binop pos (And pos), 2, 2)
, tok LTokOr >> return (Binop pos (Or pos), 1, 1)
]
unop :: Parser (Unop', Int)
unop = do
pos <- getPosition
unopTok <- choice
[ tok LTokMinus >> return Neg
, tok LTokNot >> return Not
, tok LTokSh >> return Len
]
return (Unop pos (unopTok pos), 12)
subexp :: Int -> Parser (Exp SourcePos, Maybe (Binop', Int, Int))
subexp limit = do
(e1, bop) <- optionMaybe unop >>=
\case Nothing -> (, Nothing) <$> simpleExp
Just (uop, uopPri) -> do
(e1, bop) <- subexp uopPri
return (uop e1, bop)
maybe (optionMaybe binop) (return . Just) bop >>= loop limit e1
where
loop _ e1 Nothing = return (e1, Nothing)
loop limit e1 (Just b@(bop, bopPriL, bopPriR))
| bopPriL > limit = do
(e2, nextOp) <- subexp bopPriR
loop limit (bop e1 e2) nextOp
| otherwise = return (e1, Just b)
simpleExp :: Parser (Exp SourcePos)
simpleExp = choice [ nilExp, boolExp, numberExp, stringExp, varargExp,
fundefExp, prefixexpExp, tableconstExp ]
-- | Expression parser.
exp = fst <$> subexp 0
-----------------------------------------------------------------------
---- Statements
emptyStat, assignStat, funCallStat, labelStat, breakStat, gotoStat,
doStat, whileStat, repeatStat, ifStat, forRangeStat, forInStat,
funAssignStat, localFunAssignStat, localAssignStat, stat :: Parser (Stat SourcePos)
emptyStat = (EmptyStat <$> getPosition) <* tok LTokSemic
assignStat = do
pos <- getPosition
vars <- var `sepBy` tok LTokComma
tok LTokAssign
exps <- exp `sepBy` tok LTokComma
return $ Assign pos vars exps
funCallStat = FunCall <$> getPosition <*> funCall
labelStat = Label <$> getPosition <*> label
where label = between (tok LTokDColon) (tok LTokDColon) name
breakStat = (Break <$> getPosition) <* tok LTokBreak
gotoStat = Goto <$> getPosition <*> (tok LTokGoto >> name)
doStat = Do <$> getPosition <*> between (tok LTokDo) (tok LTokEnd) block
whileStat = do
pos <- getPosition
between (tok LTokWhile)
(tok LTokEnd)
(do cond <- exp
tok LTokDo
body <- block
return $ While pos cond body)
repeatStat = do
pos <- getPosition
tok LTokRepeat
body <- block
tok LTokUntil
cond <- exp
return $ Repeat pos body cond
ifStat = do
pos <- getPosition
between (tok LTokIf)
(tok LTokEnd)
(do f <- ifPart
conds <- many elseifPart
l <- optionMaybe elsePart
return $ If pos (f:conds) l)
where ifPart :: Parser (Exp SourcePos, Block SourcePos)
ifPart = cond
elseifPart :: Parser (Exp SourcePos, Block SourcePos)
elseifPart = tok LTokElseIf >> cond
cond :: Parser (Exp SourcePos, Block SourcePos)
cond = do
cond <- exp
tok LTokThen
body <- block
return (cond, body)
elsePart :: Parser (Block SourcePos)
elsePart = tok LTokElse >> block
forRangeStat = do
pos <- getPosition
between (tok LTokFor)
(tok LTokEnd)
(do name' <- name
tok LTokAssign
start <- exp
tok LTokComma
end <- exp
range <- optionMaybe $ tok LTokComma >> exp
tok LTokDo
body <- block
return $ ForRange pos name' start end range body)
forInStat = do
pos <- getPosition
between (tok LTokFor)
(tok LTokEnd)
(do names <- name `sepBy` tok LTokComma
tok LTokIn
exps <- exp `sepBy` tok LTokComma
tok LTokDo
body <- block
return $ ForIn pos names exps body)
funAssignStat = do
pos <- getPosition
tok LTokFunction
name' <- funName
body <- funBody
return $ FunAssign pos name' body
where funName :: Parser (FunName SourcePos)
funName = FunName <$> getPosition
<*> name
<*> many (tok LTokDot >> name)
<*> optionMaybe (tok LTokColon >> name)
localFunAssignStat = do
pos <- getPosition
tok LTokLocal
tok LTokFunction
name' <- name
body <- funBody
return $ LocalFunAssign pos name' body
localAssignStat = do
pos <- getPosition
tok LTokLocal
names <- name `sepBy` tok LTokComma
rest <- optionMaybe $ tok LTokAssign >> exp `sepBy` tok LTokComma
return $ LocalAssign pos names rest
-- | Statement parser.
stat =
choice [ emptyStat
, try assignStat
, try funCallStat
, labelStat
, breakStat
, gotoStat
, doStat
, whileStat
, repeatStat
, ifStat
, try forRangeStat
, forInStat
, funAssignStat
, try localFunAssignStat
, localAssignStat
]
-- | Lua file parser.
chunk :: Parser (Block SourcePos)
chunk = block <* tok LTokEof