-
Notifications
You must be signed in to change notification settings - Fork 27
/
Parser.hs
643 lines (594 loc) · 21.4 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
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
module Nirum.Parser ( Parser
, ParseError
, aliasTypeDeclaration
, annotation
, annotationSet
, docs
, enumTypeDeclaration
, file
, handleNameDuplication
, identifier
, imports
, listModifier
, mapModifier
, method
, module'
, modulePath
, name
, optionModifier
, parse
, parseFile
, recordTypeDeclaration
, serviceDeclaration
, setModifier
, typeDeclaration
, typeExpression
, typeExpressionWithoutOptionModifier
, typeIdentifier
, unboxedTypeDeclaration
, unionTypeDeclaration
) where
import Control.Applicative ((<$>))
import Control.Monad (join, void)
import Data.List (foldl1')
import Prelude hiding (readFile)
import Data.Set (elems)
import qualified Data.Text as T
import Data.Text.IO (readFile)
import Text.Megaparsec ( Token
, choice
, eof
, many
, manyTill
, notFollowedBy
, option
, optional
, runParser
, sepBy1
, sepEndBy
, sepEndBy1
, skipMany
, skipSome
, try
, (<|>)
, (<?>)
)
import Text.Megaparsec.Char ( char
, eol
, noneOf
, spaceChar
, string
, string'
)
import qualified Text.Megaparsec.Error as E
import Text.Megaparsec.Text (Parser)
import Text.Megaparsec.Lexer (charLiteral)
import qualified Nirum.Constructs.Annotation as A
import Nirum.Constructs.Declaration (Declaration)
import Nirum.Constructs.Docs (Docs (Docs))
import Nirum.Constructs.DeclarationSet ( DeclarationSet
, NameDuplication ( BehindNameDuplication
, FacialNameDuplication
)
, empty
, fromList
)
import Nirum.Constructs.Identifier ( Identifier
, identifierRule
, reservedKeywords
, toString
)
import Nirum.Constructs.Module (Module (Module))
import Nirum.Constructs.ModulePath (ModulePath (ModulePath, ModuleName))
import Nirum.Constructs.Name (Name (Name))
import Nirum.Constructs.Service ( Method (Method)
, Parameter (Parameter)
, Service (Service)
)
import Nirum.Constructs.TypeDeclaration ( EnumMember (EnumMember)
, Field (Field)
, Tag (Tag)
, Type ( Alias
, EnumType
, RecordType
, UnboxedType
, UnionType
)
, TypeDeclaration ( Import
, ServiceDeclaration
, TypeDeclaration
, serviceAnnotations
, typeAnnotations
)
)
import Nirum.Constructs.TypeExpression ( TypeExpression ( ListModifier
, MapModifier
, OptionModifier
, SetModifier
, TypeIdentifier
)
)
type ParseError = E.ParseError (Token T.Text) E.Dec
comment :: Parser ()
comment = string "//" >> void (many $ noneOf ("\n" :: String)) <?> "comment"
spaces :: Parser ()
spaces = skipMany $ void spaceChar <|> comment
spaces1 :: Parser ()
spaces1 = skipSome $ void spaceChar <|> comment
identifier :: Parser Identifier
identifier =
quotedIdentifier <|> bareIdentifier <?> "identifier"
where
keywords :: Parser String
keywords = foldl1' (<|>) $ map (string' . toString) $ elems reservedKeywords
bareIdentifier :: Parser Identifier
bareIdentifier = do
notFollowedBy keywords
identifierRule
quotedIdentifier :: Parser Identifier
quotedIdentifier = do
char '`'
identifier' <- identifierRule
char '`'
return identifier'
name :: Parser Name
name = do
facialName <- identifier <?> "facial name"
behindName <- option facialName $ try $ do
spaces
char '/'
spaces
identifier <?> "behind name"
return $ Name facialName behindName
annotation :: Parser A.Annotation
annotation = do
char '@'
spaces
name' <- identifier
spaces
metadata <- optional $ do
char '('
spaces
m <- optional ((char '"' >> manyTill charLiteral (char '"'))
<?> "annotation metadata")
spaces
char ')'
return m
let metadata' = T.pack <$> join metadata
return $ A.Annotation name' metadata'
annotationSet :: Parser A.AnnotationSet
annotationSet = do
annotations <- many $ do
spaces
a <- annotation
spaces
return a
case A.fromList annotations of
Right annotations' -> return annotations'
Left (A.AnnotationNameDuplication _) -> fail "annotation name duplicate"
typeExpression :: Parser TypeExpression
typeExpression =
try optionModifier <|> typeExpressionWithoutOptionModifier
<?> "type expression"
typeExpressionWithoutOptionModifier :: Parser TypeExpression
typeExpressionWithoutOptionModifier =
try setModifier <|> listModifier <|> mapModifier <|> typeIdentifier
typeIdentifier :: Parser TypeExpression
typeIdentifier = do
typeIdentifier' <- identifier <?> "type identifier"
return $ TypeIdentifier typeIdentifier'
optionModifier :: Parser TypeExpression
optionModifier = do
expr <- typeExpressionWithoutOptionModifier
spaces
char '?'
return $ OptionModifier expr
setModifier :: Parser TypeExpression
setModifier = do
char '{'
spaces
expr <- typeExpression <?> "element type of set type"
spaces
char '}'
return $ SetModifier expr
listModifier :: Parser TypeExpression
listModifier = do
char '['
spaces
expr <- typeExpression <?> "element type of list type"
spaces
char ']'
return $ ListModifier expr
mapModifier :: Parser TypeExpression
mapModifier = do
char '{'
spaces
key <- typeExpression <?> "key type of map type"
spaces
char ':'
spaces
value <- typeExpression <?> "value type of map type"
spaces
char '}'
return $ MapModifier key value
docs :: Parser Docs
docs = do
comments <- sepEndBy1 (do
char '#'
void $ optional $ char ' '
line <- many $ noneOf ("\r\n" :: String)
return $ T.pack line
) (eol >> spaces) <?> "comments"
return $ Docs $ T.unlines comments
annotationsWithDocs :: Monad m
=> A.AnnotationSet
-> Maybe Docs
-> m A.AnnotationSet
annotationsWithDocs set' (Just docs') = A.insertDocs docs' set'
annotationsWithDocs set' Nothing = return set'
aliasTypeDeclaration :: Parser TypeDeclaration
aliasTypeDeclaration = do
annotationSet' <- annotationSet <?> "type alias annotations"
string' "type" <?> "type alias keyword"
spaces
typename <- identifier <?> "alias type name"
let name' = Name typename typename
spaces
char '='
spaces
canonicalType <- typeExpression <?> "canonical type of alias"
spaces
char ';'
docs' <- optional $ try $ spaces >> (docs <?> "type alias docs")
annotationSet'' <- annotationsWithDocs annotationSet' docs'
return $ TypeDeclaration name' (Alias canonicalType) annotationSet''
unboxedTypeDeclaration :: Parser TypeDeclaration
unboxedTypeDeclaration = do
annotationSet' <- annotationSet <?> "unboxed type annotations"
string' "unboxed" <?> "unboxed type keyword"
spaces
typename <- identifier <?> "unboxed type name"
let name' = Name typename typename
spaces
char '('
spaces
innerType <- typeExpression <?> "inner type of unboxed type"
spaces
char ')'
spaces
char ';'
docs' <- optional $ try $ spaces >> (docs <?> "unboxed type docs")
annotationSet'' <- annotationsWithDocs annotationSet' docs'
return $ TypeDeclaration name' (UnboxedType innerType) annotationSet''
enumMember :: Parser EnumMember
enumMember = do
annotationSet' <- annotationSet <?> "enum member annotations"
spaces
memberName <- name <?> "enum member name"
spaces
docs' <- optional $ do
d <- docs <?> "enum member docs"
spaces
return d
annotationSet'' <- annotationsWithDocs annotationSet' docs'
return $ EnumMember memberName annotationSet''
handleNameDuplication :: Declaration a
=> String -> [a]
-> (DeclarationSet a -> Parser b)
-> Parser b
handleNameDuplication label declarations cont =
case fromList declarations of
Left (BehindNameDuplication (Name _ bname)) ->
fail ("the behind " ++ label ++ " name `" ++ toString bname ++
"` is duplicated")
Left (FacialNameDuplication (Name fname _)) ->
fail ("the facial " ++ label ++ " name `" ++ toString fname ++
"` is duplicated")
Right set -> cont set
enumTypeDeclaration :: Parser TypeDeclaration
enumTypeDeclaration = do
annotationSet' <- annotationSet <?> "enum type annotations"
string "enum" <?> "enum keyword"
spaces
typename <- name <?> "enum type name"
spaces
frontDocs <- optional $ do
d <- docs <?> "enum type docs"
spaces
return d
char '='
spaces
docs' <- case frontDocs of
d@(Just _) -> return d
Nothing -> optional $ do
d <- docs <?> "enum type docs"
spaces
return d
annotationSet'' <- annotationsWithDocs annotationSet' docs'
members <- (enumMember `sepBy1` (spaces >> char '|' >> spaces))
<?> "enum members"
case fromList members of
Left (BehindNameDuplication (Name _ bname)) ->
fail ("the behind member name `" ++ toString bname ++
"` is duplicated")
Left (FacialNameDuplication (Name fname _)) ->
fail ("the facial member name `" ++ toString fname ++
"` is duplicated")
Right memberSet -> do
spaces
char ';'
return $ TypeDeclaration typename (EnumType memberSet)
annotationSet''
fieldsOrParameters :: forall a . (String, String)
-> (Name -> TypeExpression -> A.AnnotationSet -> a)
-> Parser [a]
fieldsOrParameters (label, pluralLabel) make = do
annotationSet' <- annotationSet <?> (label ++ " annotations")
spaces
type' <- typeExpression <?> (label ++ " type")
spaces1
name' <- name <?> (label ++ " name")
spaces
let makeWithDocs = make name' type' . A.union annotationSet'
. annotationsFromDocs
followedByComma makeWithDocs <|> do
d <- optional docs' <?> (label ++ " docs")
return [makeWithDocs d]
where
recur :: Parser [a]
recur = fieldsOrParameters (label, pluralLabel) make
followedByComma :: (Maybe Docs -> a) -> Parser [a]
followedByComma makeWithDocs = do
char ','
spaces
d <- optional docs' <?> (label ++ " docs")
rest <- option [] recur <?> ("rest of " ++ pluralLabel)
return $ makeWithDocs d : rest
docs' :: Parser Docs
docs' = do
d <- docs <?> (label ++ " docs")
spaces
return d
annotationsFromDocs :: Maybe Docs -> A.AnnotationSet
annotationsFromDocs Nothing = A.empty
annotationsFromDocs (Just d) = A.singleton $ A.docs d
fields :: Parser [Field]
fields = fieldsOrParameters ("label", "labels") Field
fieldSet :: Parser (DeclarationSet Field)
fieldSet = do
fields' <- fields <?> "fields"
handleNameDuplication "field" fields' return
recordTypeDeclaration :: Parser TypeDeclaration
recordTypeDeclaration = do
annotationSet' <- annotationSet <?> "record type annotations"
string "record" <?> "record keyword"
spaces
typename <- name <?> "record type name"
spaces
char '('
spaces
docs' <- optional $ do
d <- docs <?> "record type docs"
spaces
return d
fields' <- fieldSet <?> "record fields"
spaces
char ')'
spaces
char ';'
annotationSet'' <- annotationsWithDocs annotationSet' docs'
return $ TypeDeclaration typename (RecordType fields') annotationSet''
tag :: Parser Tag
tag = do
annotationSet' <- annotationSet <?> "union tag annotations"
spaces
tagName <- name <?> "union tag name"
spaces
paren <- optional $ char '('
fields' <- case paren of
Just _ -> do
spaces
f <- fieldSet <?> "union tag fields"
spaces
char ')'
return f
Nothing -> return empty
docs' <- optional $ do
d <- docs <?> "union tag docs"
spaces
return d
annotationSet'' <- annotationsWithDocs annotationSet' docs'
return $ Tag tagName fields' annotationSet''
unionTypeDeclaration :: Parser TypeDeclaration
unionTypeDeclaration = do
annotationSet' <- annotationSet <?> "union type annotations"
string "union" <?> "union keyword"
spaces
typename <- name <?> "union type name"
spaces
docs' <- optional $ do
d <- docs <?> "union type docs"
spaces
return d
char '='
spaces
tags' <- (tag `sepBy1` try (spaces >> char '|' >> spaces))
<?> "union tags"
spaces
char ';'
annotationSet'' <- annotationsWithDocs annotationSet' docs'
handleNameDuplication "tag" tags' $ \ tagSet ->
return $ TypeDeclaration typename (UnionType tagSet) annotationSet''
typeDeclaration :: Parser TypeDeclaration
typeDeclaration = do
-- Preconsume the common prefix (annotations) to disambiguate
-- the continued branches of parsers.
spaces
annotationSet' <- annotationSet <?> "type annotations"
spaces
typeDecl <- choice
[ unless' ["union", "record", "enum", "unboxed"] aliasTypeDeclaration
, unless' ["union", "record", "enum"] unboxedTypeDeclaration
, unless' ["union", "record"] enumTypeDeclaration
, unless' ["union"] recordTypeDeclaration
, unionTypeDeclaration
] <?> "type declaration (e.g. enum, record, unboxed, union)"
-- In theory, though it preconsumes annotationSet' before parsing typeDecl
-- so that typeDecl itself has no annotations, to prepare for an
-- unlikely situation (that I bet it'll never happen)
-- unite the preconsumed annotationSet' with typeDecl's annotations
-- (that must be empty).
let annotations = A.union annotationSet' $ typeAnnotations typeDecl
return $ typeDecl { typeAnnotations = annotations }
where
unless' :: [String] -> Parser a -> Parser a
unless' [] _ = fail "no candidates" -- Must never happen
unless' [s] p = notFollowedBy (string s) >> p
unless' (x : xs) p = notFollowedBy (string x) >> unless' xs p
parameters :: Parser [Parameter]
parameters = fieldsOrParameters ("parameter", "parameters") Parameter
parameterSet :: Parser (DeclarationSet Parameter)
parameterSet = option empty $ try $ do
params <- parameters <?> "method parameters"
handleNameDuplication "parameter" params return
method :: Parser Method
method = do
annotationSet' <- annotationSet <?> "service method annotation"
returnType <- typeExpression <?> "method return type"
spaces1
methodName <- name <?> "method name"
spaces
char '('
spaces
docs' <- optional $ do
d <- docs <?> "method docs"
spaces
return d
params <- parameterSet
spaces
char ')'
spaces
errorType <- optional $ do
string "throws" <?> "throws keyword"
spaces
e <- typeExpression <?> "method error type"
spaces
return e
annotationSet'' <- annotationsWithDocs annotationSet' docs'
return $ Method methodName params returnType errorType annotationSet''
methods :: Parser [Method]
methods = method `sepEndBy` try (spaces >> char ',' >> spaces)
methodSet :: Parser (DeclarationSet Method)
methodSet = do
methods' <- methods <?> "service methods"
handleNameDuplication "method" methods' return
serviceDeclaration :: Parser TypeDeclaration
serviceDeclaration = do
annotationSet' <- annotationSet <?> "service annotation"
string "service" <?> "service keyword"
spaces
serviceName <- name <?> "service name"
spaces
char '('
spaces
docs' <- optional $ do
d <- docs <?> "service docs"
spaces
return d
methods' <- methodSet <?> "service methods"
spaces
char ')'
spaces
char ';'
annotationSet'' <- annotationsWithDocs annotationSet' docs'
return $ ServiceDeclaration serviceName (Service methods') annotationSet''
modulePath :: Parser ModulePath
modulePath = do
idents <- sepBy1 (identifier <?> "module identifier")
(try (spaces >> char '.' >> spaces))
<?> "module path"
case makePath idents of
Nothing -> fail "module path cannot be empty"
Just path -> return path
where
makePath :: [Identifier] -> Maybe ModulePath
makePath = foldl f Nothing
f :: Maybe ModulePath -> Identifier -> Maybe ModulePath
f Nothing i = Just $ ModuleName i
f (Just p) i = Just $ ModulePath p i
importName :: Parser (Identifier, A.AnnotationSet)
importName = do
aSet <- annotationSet <?> "import annotations"
spaces
iName <- identifier <?> "name to import"
return (iName, aSet)
imports :: Parser [TypeDeclaration]
imports = do
string' "import" <?> "import keyword"
spaces
path <- modulePath <?> "module path"
spaces
char '('
spaces
idents <- sepBy1 importName
(spaces >> char ',' >> spaces)
<?> "names to import"
spaces
char ')'
spaces
char ';'
return [Import path ident aSet | (ident, aSet) <- idents]
module' :: Parser Module
module' = do
spaces
docs' <- optional $ do
d <- docs <?> "module docs"
spaces
return d
spaces
importLists <- many $ do
importList <- imports
spaces
return importList
types <- many $ do
typeDecl <- do
-- Preconsume the common prefix (annotations) to disambiguate
-- the continued branches of parsers.
spaces
annotationSet' <- annotationSet <?> "annotations"
spaces
decl <- choice [ notFollowedBy (string "service") >> typeDeclaration
, serviceDeclaration <?> "service declaration"
]
-- In theory, though it preconsumes annotationSet' before parsing
-- decl so that decl itself has no annotations, to prepare for an
-- unlikely situation (that I bet it'll never happen)
-- unite the preconsumed annotationSet' with decl's annotations
-- (that must be empty).
return $ case decl of
TypeDeclaration { typeAnnotations = set } ->
decl { typeAnnotations = A.union annotationSet' set }
ServiceDeclaration { serviceAnnotations = set } ->
decl { serviceAnnotations = A.union annotationSet' set }
_ -> decl -- Never happen!
spaces
return typeDecl
handleNameDuplication "type" (types ++ [i | l <- importLists, i <- l]) $
\ typeSet -> return $ Module typeSet docs'
file :: Parser Module
file = do
mod' <- module'
eof
return mod'
parse :: FilePath -- ^ Source path (although it's only used for error message)
-> T.Text -- ^ Input source code
-> Either ParseError Module
parse = runParser file
parseFile :: FilePath -- ^ Source path
-> IO (Either ParseError Module)
parseFile path = do
code <- readFile path
return $ runParser file path code
{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}