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