@@ -147,9 +147,15 @@ parseGenericPackageDescription' lexWarnings fs = do
147
147
-- Sections
148
148
let gpd = emptyGpd & L. packageDescription .~ pd
149
149
150
+ -- Common stanzas
151
+ sectionFields' <-
152
+ if specVersion pd >= mkVersion [2 , 1 ]
153
+ then spliceCommonStanzas sectionFields
154
+ else pure sectionFields
155
+
150
156
-- elif conditional is accepted if spec version is >= 2.1
151
157
let hasElif = if specVersion pd >= mkVersion [2 ,1 ] then HasElif else NoElif
152
- execStateT (goSections hasElif sectionFields) gpd
158
+ execStateT (goSections hasElif sectionFields' ) gpd
153
159
where
154
160
emptyGpd :: GenericPackageDescription
155
161
emptyGpd = GenericPackageDescription emptyPackageDescription [] Nothing [] [] [] [] []
@@ -261,6 +267,7 @@ goSections hasElif = traverse_ process
261
267
parseWarning pos PWTUnknownSection $ " Ignoring section: " ++ show name
262
268
263
269
parseName :: Position -> [SectionArg Position ] -> SectionParser String
270
+ -- TODO: use strict parser
264
271
parseName pos args = case args of
265
272
[SecArgName _pos secName] ->
266
273
pure $ fromUTF8BS secName
@@ -274,6 +281,20 @@ parseName pos args = case args of
274
281
lift $ parseFailure pos $ " Invalid name " ++ show args
275
282
pure " "
276
283
284
+ parseCommonName :: Position -> [SectionArg Position ] -> ParseResult String
285
+ parseCommonName pos args = case args of
286
+ [SecArgName _pos secName] ->
287
+ pure $ fromUTF8BS secName
288
+ [SecArgStr _pos secName] ->
289
+ pure $ fromUTF8BS secName
290
+ [] -> do
291
+ parseFailure pos $ " name required"
292
+ pure " "
293
+ _ -> do
294
+ -- TODO: pretty print args
295
+ parseFailure pos $ " Invalid name " ++ show args
296
+ pure " "
297
+
277
298
parseUnqualComponentName :: Position -> [SectionArg Position ] -> SectionParser UnqualComponentName
278
299
parseUnqualComponentName pos args = mkUnqualComponentName <$> parseName pos args
279
300
@@ -366,6 +387,80 @@ When/if we re-implement the parser to support formatting preservging roundtrip
366
387
with new AST, this all need to be rewritten.
367
388
-}
368
389
390
+ -------------------------------------------------------------------------------
391
+ -- Common stanzas
392
+ -------------------------------------------------------------------------------
393
+
394
+ -- | Splice common stanzas.
395
+ --
396
+ -- The approach resembles CPP: @common@ sections define blocks, which are
397
+ -- then spliced in place of @include@ sections.
398
+ --
399
+ -- == Example
400
+ --
401
+ -- @
402
+ -- common deps
403
+ -- build-depends: base ^>= 4.10
404
+ --
405
+ -- test-suite tests
406
+ -- type: exitcode-stdio-1.0
407
+ -- include deps
408
+ -- main-is: Main.hs
409
+ -- @
410
+ --
411
+ -- is transformed into
412
+ --
413
+ -- @
414
+ -- test-suite tests
415
+ -- type: exitcode-stdio-1.0
416
+ -- build-depends: base ^>= 4.10
417
+ -- main-is: Main.hs
418
+ -- @
419
+ --
420
+ -- Pros of this approach is its simplicity. Drawbacks are
421
+ --
422
+ -- * common stanza is parsed multiple times. We could /compile/ common stanzas
423
+ -- to e.g. @'BuildInfo' -> 'BuildInfo'@, but application of it in
424
+ -- 'FieldGrammarParser' approach will be impossible in *between* of other fields.
425
+ -- Applying common stanzas after 'parseCondTree' might be surprising!
426
+ -- Note how @build-depends@ are spliced in the example above.
427
+ --
428
+ -- /TODO:/ we could warn about unused stanzas.
429
+ --
430
+ spliceCommonStanzas :: [Field Position ] -> ParseResult [Field Position ]
431
+ spliceCommonStanzas = go Map. empty
432
+ where
433
+ go :: Map String [Field Position ] -> [Field Position ] -> ParseResult [Field Position ]
434
+ go _ [] = pure []
435
+ go common (f@ Field {} : fields) = (f : ) <$> go common fields
436
+ go common (Section (Name pos name) args secFields : fields) | name == " common" = do
437
+ commonName <- parseCommonName pos args
438
+ go (Map. insert commonName secFields common) fields
439
+ go common (Section name args secFields : fields) = do
440
+ secFields' <- splice common secFields
441
+ fields' <- go common fields
442
+ pure (Section name args secFields' : fields')
443
+
444
+ splice :: Map String [Field Position ] -> [Field Position ] -> ParseResult [Field Position ]
445
+ splice common = sgo where
446
+ sgo [] = pure []
447
+ sgo (f@ Field {} : fields) = (f : ) <$> sgo fields
448
+ sgo (Section (Name pos name) args secFields : fields) | name == " include" = do
449
+ unless (null secFields) $
450
+ parseFailure pos " Non-empty include stanza"
451
+
452
+ commonName <- parseCommonName pos args
453
+ case Map. lookup commonName common of
454
+ Nothing -> do
455
+ parseFailure pos $ " Undefined common stanza included: " ++ commonName
456
+ sgo fields
457
+ Just secFields' -> do
458
+ (secFields' ++ ) <$> sgo fields
459
+ sgo (Section name args secFields : fields) = do
460
+ secFields' <- sgo secFields
461
+ fields' <- sgo fields
462
+ pure (Section name args secFields' : fields')
463
+
369
464
-------------------------------------------------------------------------------
370
465
-- Old syntax
371
466
-------------------------------------------------------------------------------
0 commit comments