Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
e170fc8
feat: implement comment parser
leana8959 Sep 25, 2025
6624ebf
hide internal comment manipulating functions until API is stable
leana8959 Jun 12, 2026
47e8c45
fix lint issues
leana8959 Jun 12, 2026
c8ab7f2
Retain comments in field parser
leana8959 Jul 20, 2026
f29a92f
document types and their methods
leana8959 Jul 27, 2026
6ea1499
rewrite mapComments and mapCommentData using lens
leana8959 Jul 27, 2026
8ff46fd
remove unused instances
leana8959 Jul 27, 2026
968eace
remove strict either
leana8959 Jul 27, 2026
47570de
run forumolu
leana8959 Jul 27, 2026
18e8202
remove use of Functor in commentsAfter
leana8959 Jul 28, 2026
6463836
don't expose cabal-parsec-debug flag
leana8959 Jul 29, 2026
c599c87
fix pragma in testsuite
leana8959 Aug 11, 2026
1e24ac2
clean up pragmas, reduce diff, reword wonky comments
leana8959 Aug 14, 2026
9993557
add ascii flowchart of the lexer
leana8959 Aug 17, 2026
e0d67b9
implement basic idea of field transformation
leana8959 Jul 1, 2026
5c56bf9
Merge branch 'exact-pp-leana' into transform-fields
leana8959 Aug 17, 2026
a65156d
replace Newtype class with Coerce class
leana8959 Aug 17, 2026
c3dafbd
fix unterminated pragma
leana8959 Aug 17, 2026
b21a4da
fix newtype class in parser tests
leana8959 Aug 17, 2026
28902d8
add a pass to turn name to lower case
leana8959 Aug 17, 2026
b3e55f1
accept casing in concrete parsing
leana8959 Aug 17, 2026
3c8c107
document the reason of orFallback
leana8959 Aug 17, 2026
f8d5fd0
add comment handling in modification functions
leana8959 Aug 17, 2026
777b11a
update comment handling in test suite
leana8959 Aug 17, 2026
e76eb1e
add comment in example
leana8959 Aug 17, 2026
ccda102
add export list to transform module
leana8959 Aug 18, 2026
04742a0
make modifyValueList partial
leana8959 Aug 18, 2026
10772d5
make modifyValueAtomAla partial
leana8959 Aug 18, 2026
dbbffad
allow using partial modification function in modify focuses
leana8959 Aug 18, 2026
de0d96b
add instances to fix test
leana8959 Aug 18, 2026
81aeb3e
update todos
leana8959 Aug 18, 2026
af418ce
remove project files
leana8959 Aug 18, 2026
855be0c
add todos
leana8959 Aug 18, 2026
4ea6ff7
add export list for ExactPretty
leana8959 Aug 18, 2026
9a6011d
add proper position shifting logic in new field
leana8959 Aug 18, 2026
5e3f24d
fix colonPos calculation
leana8959 Aug 18, 2026
6974966
remove todo
leana8959 Aug 18, 2026
43ad1b3
add a basic add section function
leana8959 Aug 18, 2026
6b0974d
make prependValueListBS partial
leana8959 Aug 18, 2026
c1e2828
add a function to prepend function list
leana8959 Aug 18, 2026
bae4047
move code around
leana8959 Aug 18, 2026
8e724e2
clean up imports
leana8959 Aug 18, 2026
c80115c
implement example formatter to test out the idea
leana8959 Aug 20, 2026
cae82ce
add a function to run the formatter
leana8959 Aug 20, 2026
30ae685
run the build-depends formatter as an example
leana8959 Aug 20, 2026
635d9e1
add shared context to edit functions
leana8959 Aug 20, 2026
9b493f2
move code around, remove dead code
leana8959 Aug 20, 2026
4f02cf8
implement sectionArgBS
leana8959 Aug 20, 2026
5b1e003
refactor using monad instance
leana8959 Aug 20, 2026
e510c6c
run formatter
leana8959 Aug 20, 2026
9ec34ae
liftA2 is cool
leana8959 Aug 20, 2026
617f14d
move parsec orphan instance to its module
leana8959 Aug 20, 2026
ee8d9d3
update todo on labels
leana8959 Aug 20, 2026
4ee6831
reuse transform functions to format
leana8959 Aug 20, 2026
684ee41
proper formatting example with multiple sections
leana8959 Aug 20, 2026
25cd0eb
add modifyConfitionConfVar
leana8959 Aug 26, 2026
9ed5478
document position handling
leana8959 Aug 26, 2026
566eeb9
run formatter
leana8959 Aug 26, 2026
8e4ed38
implement relative algebra
leana8959 Aug 28, 2026
3224c5f
checkpoint
leana8959 Aug 28, 2026
3d61c52
stash
leana8959 Aug 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Cabal-syntax/Cabal-syntax.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ build-type: Simple
extra-doc-files:
README.md ChangeLog.md

flag cabal-parsec-debug
description: Enable debug build for the cabal field lexer/parser.
default: False
manual: True

source-repository head
type: git
location: https://github.com/haskell/cabal/
Expand Down Expand Up @@ -47,6 +52,8 @@ library
-- See also https://github.com/ekmett/transformers-compat/issues/35
, transformers (>= 0.3 && < 0.4) || (>=0.4.1.0 && <0.7)

, pretty-simple

ghc-options:
-Wall
-fno-ignore-asserts
Expand All @@ -60,11 +67,17 @@ library
if impl(ghc >= 8.0) && impl(ghc < 8.8)
ghc-options: -Wnoncanonical-monadfail-instances

if flag(cabal-parsec-debug)
CPP-Options: -DCABAL_PARSEC_DEBUG
build-depends:
vector

build-tool-depends:
-- https://github.com/haskell/alex/issues/288
alex:alex < 3.5.4.1 || > 3.5.4.1

exposed-modules:
Distribution.Annotation
Distribution.Backpack
Distribution.CabalSpecVersion
Distribution.Compat.Binary
Expand All @@ -83,14 +96,19 @@ library
Distribution.FieldGrammar.Newtypes
Distribution.FieldGrammar.Parsec
Distribution.FieldGrammar.Pretty
Distribution.FieldGrammar.Format
Distribution.Fields
Distribution.Fields.ConfVar
Distribution.Fields.Field
Distribution.Fields.Field.Lens
Distribution.Fields.Field.Relative
Distribution.Fields.Lexer
Distribution.Fields.LexerMonad
Distribution.Fields.Transform
Distribution.Fields.ParseResult
Distribution.Fields.Parser
Distribution.Fields.Pretty
Distribution.Fields.ExactPretty
Distribution.InstalledPackageInfo
Distribution.License
Distribution.ModuleName
Expand All @@ -99,13 +117,15 @@ library
Distribution.PackageDescription.Configuration
Distribution.PackageDescription.FieldGrammar
Distribution.PackageDescription.Parsec
Distribution.PackageDescription.Format
Distribution.PackageDescription.PrettyPrint
Distribution.PackageDescription.Quirks
Distribution.PackageDescription.Utils
Distribution.Parsec
Distribution.Parsec.Error
Distribution.Parsec.FieldLineStream
Distribution.Parsec.Position
Distribution.Parsec.Position.Lens
Distribution.Parsec.Warning
Distribution.Parsec.Source
Distribution.Pretty
Expand Down
31 changes: 31 additions & 0 deletions Cabal-syntax/src/Distribution/Annotation.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{-# LANGUAGE DeriveFunctor #-}

Check warning on line 1 in Cabal-syntax/src/Distribution/Annotation.hs

View workflow job for this annotation

GitHub Actions / hlint

Warning in module Distribution.Annotation: Unused LANGUAGE pragma ▫︎ Found: "{-# LANGUAGE DeriveFunctor #-}" ▫︎ Note: Extension DeriveFunctor is implied by DeriveTraversable
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE UndecidableInstances #-}

module Distribution.Annotation where

import Data.List (sortOn)
import Data.Ord (Down (..))
import Distribution.Parsec
import Distribution.Parsec.Position

Check warning on line 10 in Cabal-syntax/src/Distribution/Annotation.hs

View workflow job for this annotation

GitHub Actions / Doctest Cabal

The import of ‘Distribution.Parsec.Position’ is redundant

-- | Designates the source position within a joined 'FieldLineStream'.
-- Inclusive in the start position, exclusive in the ending position.
data SrcSpan = SrcSpan {-# UNPACK #-} !Position {-# UNPACK #-} !Position
deriving (Show, Eq, Ord)

data Located a = MkLocated {getSrcSpan :: !SrcSpan, unLocated :: !a}
deriving (Show, Functor, Foldable, Traversable)

sortBySrcSpanAsc :: [Located a] -> [Located a]
sortBySrcSpanAsc = sortOn getSrcSpan

sortBySrcSpanDes :: [Located a] -> [Located a]
sortBySrcSpanDes = sortOn (Down . getSrcSpan)

instance Parsec a => Parsec (Located a) where
parsec = do
begin <- getPosition
x <- parsec
end <- getPosition
pure (MkLocated (SrcSpan begin end) x)
8 changes: 6 additions & 2 deletions Cabal-syntax/src/Distribution/FieldGrammar.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ module Distribution.FieldGrammar
, PrettyFieldGrammar
, PrettyFieldGrammar'
, prettyFieldGrammar
, FormatterFieldGrammar
, formatFieldGrammar

-- * Auxiliary
, (^^^)
Expand All @@ -37,6 +39,7 @@ import Prelude ()
import qualified Data.Map.Strict as Map

import Distribution.FieldGrammar.Class
import Distribution.FieldGrammar.Format
import Distribution.FieldGrammar.Newtypes
import Distribution.FieldGrammar.Parsec
import Distribution.FieldGrammar.Pretty
Expand Down Expand Up @@ -85,7 +88,7 @@ partitionFields = finalize . foldl' f (PS mempty mempty mempty)
| otherwise = (fs, reverse (reverse s : ss))

f :: PS ann -> Field ann -> PS ann
f (PS fs s ss) (Field (Name ann name) fss) =
f (PS fs s ss) (Field _ (Name ann name) fss) =
PS (Map.insertWith (flip (++)) name [MkNamelessField ann fss] fs) [] ss'
where
ss'
Expand All @@ -95,10 +98,11 @@ partitionFields = finalize . foldl' f (PS mempty mempty mempty)
PS fs (MkSection name sargs sfields : s) ss

-- | Take all fields from the front.
-- Returns a tuple containing the comments, nameless fields, and sections
takeFields :: [Field ann] -> (Fields ann, [Field ann])
takeFields = finalize . spanMaybe match
where
finalize (fs, rest) = (Map.fromListWith (flip (++)) fs, rest)

match (Field (Name ann name) fs) = Just (name, [MkNamelessField ann fs])
match (Field _ (Name ann name) fs) = Just (name, [MkNamelessField ann fs])
match _ = Nothing
98 changes: 98 additions & 0 deletions Cabal-syntax/src/Distribution/FieldGrammar/Format.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE LambdaCase #-}

Check warning on line 2 in Cabal-syntax/src/Distribution/FieldGrammar/Format.hs

View workflow job for this annotation

GitHub Actions / hlint

Warning in module Distribution.FieldGrammar.Format: Unused LANGUAGE pragma ▫︎ Found: "{-# LANGUAGE LambdaCase #-}"
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE UndecidableInstances #-}

-- | This is a field formatter directed by
module Distribution.FieldGrammar.Format where

import Distribution.FieldGrammar.Class
import Distribution.Fields.Field
import Distribution.Fields.Transform
import Distribution.Parsec
import Distribution.Pretty

import Data.Coerce
import Data.Kind

import Data.Map (Map)
import qualified Data.Map as M
import Distribution.CabalSpecVersion
import Distribution.Compat.Lens

data FormatterFieldGrammar s a = FormatterFG
{ fieldGrammarFormatter
:: CabalSpecVersion
-> Map FieldName ([FieldLine (WithComments Position)] -> [FieldLine (WithComments Position)])
}
deriving (Functor)

formatFieldGrammar :: CabalSpecVersion -> FormatterFieldGrammar s a -> Field (WithComments Position) -> Field (WithComments Position)
formatFieldGrammar spec (FormatterFG versionedFormatter) = go
where
go section@(Section{}) = section
go (Field colonPos fname fls) =
let newField = case versionedFormatter spec M.!? getName fname of
Just f -> Field colonPos fname (f fls)
Nothing -> Field colonPos fname fls
in newField

-- TODO(leana8959): how to do this properly
class (Pretty a, Parsec a) => Formattable a
instance (Pretty a, Parsec a) => Formattable a

instance Applicative (FormatterFieldGrammar s) where
pure _ = FormatterFG $ \_ -> M.empty
FormatterFG x <*> FormatterFG y = FormatterFG (x <> y)

-- | Naive implementation, doesn't put comments back.
formatFieldLines
:: forall (a :: Type)
. (Parsec a, Pretty a)
=> (CabalSpecVersion -> [FieldLine (WithComments Position)] -> [FieldLine (WithComments Position)])
formatFieldLines = formatFieldLinesAla @a @a

-- | Naive implementation, doesn't put comments back.
formatFieldLinesAla
:: forall (b :: Type) (a :: Type)
. (Coercible a b, Parsec b, Pretty b)
=> (CabalSpecVersion -> [FieldLine (WithComments Position)] -> [FieldLine (WithComments Position)])
formatFieldLinesAla spec fls = case modifyValueAtomAla @b @a Just spec fls of
EditOk ok -> ok
EditUnchanged u -> u
EditErr _ -> error $ "formatting failed"

Check warning on line 63 in Cabal-syntax/src/Distribution/FieldGrammar/Format.hs

View workflow job for this annotation

GitHub Actions / hlint

Suggestion in formatFieldLinesAla in module Distribution.FieldGrammar.Format: Redundant $ ▫︎ Found: "error $ \"formatting failed\"" ▫︎ Perhaps: "error \"formatting failed\""

instance FieldGrammar Formattable FormatterFieldGrammar where
uniqueFieldAla
:: forall b a s proxy. (Formattable b, Coercible a b) => FieldName -> proxy a b -> ALens' s a -> FormatterFieldGrammar s a
uniqueFieldAla fn _ _extract = FormatterFG $ \spec -> M.singleton fn $ formatFieldLinesAla @b @a spec

booleanFieldDef fn _ _def = FormatterFG $ \spec -> M.singleton fn $ formatFieldLines @Bool spec

optionalFieldAla
:: forall b a s proxy. (Formattable b, Coercible a b) => FieldName -> proxy a b -> ALens' s (Maybe a) -> FormatterFieldGrammar s (Maybe a)
optionalFieldAla fn _ _extract = FormatterFG $ \spec -> M.singleton fn $ formatFieldLinesAla @b @a spec

optionalFieldDefAla
:: forall b a s proxy. (Formattable b, Coercible a b) => FieldName -> proxy a b -> ALens' s a -> a -> FormatterFieldGrammar s a
optionalFieldDefAla fn _ _extract _def = FormatterFG $ \spec -> M.singleton fn $ formatFieldLinesAla @b @a spec

monoidalFieldAla
:: forall b a proxy s. (Formattable b, Coercible a b) => FieldName -> proxy a b -> ALens' s a -> FormatterFieldGrammar s a
monoidalFieldAla fn _ _extract = FormatterFG $ \spec -> M.singleton fn $ formatFieldLinesAla @b @a spec

-- Nothing to format?
freeTextField _fn _ = FormatterFG $ \_ -> M.empty
freeTextFieldDef _fn _ = FormatterFG $ \_ -> M.empty
freeTextFieldDefST _fn _ = FormatterFG $ \_ -> M.empty
prefixedFields _fn _ = FormatterFG $ \_ -> M.empty

knownField _ = FormatterFG $ \_ -> M.empty
hiddenField _ = FormatterFG $ \_ -> M.empty

deprecatedSince _ _ = id
removedIn _ _ = id
availableSince _ _ = id
availableSinceWarn _ = id

blurFieldGrammar _ (FormatterFG formatter) = FormatterFG formatter
24 changes: 24 additions & 0 deletions Cabal-syntax/src/Distribution/FieldGrammar/Newtypes.hs
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,13 @@
)
import Text.PrettyPrint (Doc, comma, fsep, punctuate, text, vcat)

import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8

Check warning on line 70 in Cabal-syntax/src/Distribution/FieldGrammar/Newtypes.hs

View workflow job for this annotation

GitHub Actions / Doctest Cabal

The qualified import of ‘Data.ByteString.Char8’ is redundant
import Data.Coerce (Coercible, coerce)
import qualified Data.List.NonEmpty as NE
import qualified Data.Set as Set
import qualified Distribution.Compat.CharParsing as P
import Distribution.Fields.ExactPretty (LineEnding, lineEndingChar)

Check warning on line 75 in Cabal-syntax/src/Distribution/FieldGrammar/Newtypes.hs

View workflow job for this annotation

GitHub Actions / Doctest Cabal

The import of ‘Distribution.Fields.ExactPretty’ is redundant
import qualified Distribution.SPDX as SPDX

-- | Vertical list with commas. Displayed with 'vcat'
Expand All @@ -88,12 +91,21 @@
data NoCommaFSep = NoCommaFSep

class Sep sep where
-- | Detect whether a string is considered valid separator.
-- Used in exactprint modification.
isSeparator :: Proxy sep -> Char -> Bool

sepToChar :: Proxy sep -> BS.ByteString

prettySep :: Proxy sep -> [Doc] -> Doc

parseSep :: CabalParsing m => Proxy sep -> m a -> m [a]
parseSepNE :: CabalParsing m => Proxy sep -> m a -> m (NonEmpty a)

instance Sep CommaVCat where
isSeparator _ = (== ',')
sepToChar _ = ",\n"

prettySep _ = vcat . punctuate comma
parseSep _ p = do
v <- askCabalSpecVersion
Expand All @@ -102,6 +114,9 @@
v <- askCabalSpecVersion
if v >= CabalSpecV2_2 then parsecLeadingCommaNonEmpty p else parsecCommaNonEmpty p
instance Sep CommaFSep where
isSeparator _ = (== ',')
sepToChar _ = ","

prettySep _ = fsep . punctuate comma
parseSep _ p = do
v <- askCabalSpecVersion
Expand All @@ -110,18 +125,27 @@
v <- askCabalSpecVersion
if v >= CabalSpecV2_2 then parsecLeadingCommaNonEmpty p else parsecCommaNonEmpty p
instance Sep VCat where
isSeparator _ c = c == ',' || isSpace c
sepToChar _ = "\n"

prettySep _ = vcat
parseSep _ p = do
v <- askCabalSpecVersion
if v >= CabalSpecV3_0 then parsecLeadingOptCommaList p else parsecOptCommaList p
parseSepNE _ p = NE.some1 (p <* P.spaces)
instance Sep FSep where
isSeparator _ c = c == ',' || isSpace c
sepToChar _ = " "

prettySep _ = fsep
parseSep _ p = do
v <- askCabalSpecVersion
if v >= CabalSpecV3_0 then parsecLeadingOptCommaList p else parsecOptCommaList p
parseSepNE _ p = NE.some1 (p <* P.spaces)
instance Sep NoCommaFSep where
isSeparator _ = isSpace
sepToChar _ = " "

prettySep _ = fsep
parseSep _ p = many (p <* P.spaces)
parseSepNE _ p = NE.some1 (p <* P.spaces)
Expand Down
Loading
Loading