Browse Source

Reformat essentially all of Prosidy (#2)

Reformat essentially all of Prosidy

Co-authored-by: Alex Feldman-Crough <alex@fldcr.com>
Reviewed-on: prosidy/prosidy#2
chris-martin-patch-1
alex 1 year ago
parent
commit
bdf69baa43
  1. 14
      src/Prosidy.hs
  2. 4
      src/Prosidy/Optics.hs
  3. 45
      src/Prosidy/Optics/Internal.hs
  4. 26
      src/Prosidy/Optics/Source.hs
  5. 81
      src/Prosidy/Optics/Types.hs
  6. 27
      src/Prosidy/Parse.hs
  7. 201
      src/Prosidy/Source.hs
  8. 202
      src/Prosidy/Types.hs
  9. 41
      src/Prosidy/Types/Assoc.hs
  10. 85
      src/Prosidy/Types/Key.hs
  11. 45
      src/Prosidy/Types/Series.hs
  12. 39
      src/Prosidy/Types/Set.hs

14
src/Prosidy.hs

@ -7,7 +7,13 @@ Maintainer : alex@fldcr.com
-}
module Prosidy (module X) where
import Prosidy.Source as X (Source, Location, Line, Column, Offset)
import Prosidy.Optics as X
import Prosidy.Parse as X
import Prosidy.Types as X
import Prosidy.Source as X
( Source
, Location
, Line
, Column
, Offset
)
import Prosidy.Optics as X
import Prosidy.Parse as X
import Prosidy.Types as X

4
src/Prosidy/Optics.hs

@ -7,5 +7,5 @@ Maintainer : alex@fldcr.com
-}
module Prosidy.Optics (module X) where
import Prosidy.Optics.Source as X
import Prosidy.Optics.Types as X
import Prosidy.Optics.Source as X
import Prosidy.Optics.Types as X

45
src/Prosidy/Optics/Internal.hs

@ -7,13 +7,14 @@ Maintainer : alex@fldcr.com
-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}
module Prosidy.Optics.Internal
( module Prosidy.Optics.Internal
, Profunctor(..)
, Choice(..)
, Strong(..)
, Contravariant(..)
) where
module Prosidy.Optics.Internal
( module Prosidy.Optics.Internal
, Profunctor(..)
, Choice(..)
, Strong(..)
, Contravariant(..)
)
where
import Data.Profunctor ( Profunctor(..)
, Choice(..)
@ -25,23 +26,25 @@ import Data.Monoid ( First(..)
)
import Data.Functor.Identity ( Identity(..) )
import Data.Tagged ( Tagged(..) )
import Data.Functor.Contravariant (Contravariant(..))
import Data.Functor.Contravariant ( Contravariant(..) )
type Optic p f s t a b = p a (f b) -> p s (f t)
type Iso s t a b = forall p f. (Profunctor p, Functor f) => Optic p f s t a b
type Lens s t a b = forall p f. (Strong p, Functor f) => Optic p f s t a b
type Prism s t a b = forall p f. (Choice p, Applicative f) => Optic p f s t a b
type Affine s t a b = forall p f. (Choice p, Strong p, Applicative f) => Optic p f s t a b
type Traversal s t a b = forall f. (Applicative f) => Optic (->) f s t a b
type Iso s t a b = forall p f . (Profunctor p, Functor f) => Optic p f s t a b
type Lens s t a b = forall p f . (Strong p, Functor f) => Optic p f s t a b
type Prism s t a b
= forall p f . (Choice p, Applicative f) => Optic p f s t a b
type Affine s t a b
= forall p f . (Choice p, Strong p, Applicative f) => Optic p f s t a b
type Traversal s t a b = forall f . (Applicative f) => Optic (->) f s t a b
type Optic' p f s a = Optic p f s s a a
type Iso' s a = Iso s s a a
type Lens' s a = Lens s s a a
type Prism' s a = Prism s s a a
type Affine' s a = Affine s s a a
type Iso' s a = Iso s s a a
type Lens' s a = Lens s s a a
type Prism' s a = Prism s s a a
type Affine' s a = Affine s s a a
type Traversal' s a = Traversal s s a a
type Getter s a = forall f. (Functor f, Contravariant f) => Optic' (->) f s a
type Getter s a = forall f . (Functor f, Contravariant f) => Optic' (->) f s a
iso :: (s -> a) -> (b -> t) -> Iso s t a b
iso get set = dimap get (fmap set)
@ -55,9 +58,7 @@ lens get set = dimap into outof . second'
{-# INLINE lens #-}
prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b
prism set get = dimap get rhs . right'
where
rhs = either pure (fmap set)
prism set get = dimap get rhs . right' where rhs = either pure (fmap set)
{-# INLINE prism #-}
prism' :: (b -> s) -> (s -> Maybe a) -> Prism s s a b
@ -70,7 +71,7 @@ prism' set get = dimap lhs rhs . right'
affine :: (s -> Either t a) -> (s -> b -> t) -> Affine s t a b
affine get set = dimap lhs rhs . right' . second'
where
lhs x = fmap (x,) $ get x
lhs x = fmap (x, ) $ get x
rhs = either pure (\(x, f) -> set x <$> f)
{-# INLINE affine #-}

26
src/Prosidy/Optics/Source.hs

@ -16,11 +16,12 @@ module Prosidy.Optics.Source
-- * Conversion utilities
, sparse
) where
)
where
import Prosidy.Types
import Prosidy.Source
import Prosidy.Optics.Internal
import Prosidy.Types
import Prosidy.Source
import Prosidy.Optics.Internal
-- | A classy optic for selecting the 'Location' from a value. Note that
-- 'location' is affine: a 'Location' can't be attached to a value which does
@ -34,27 +35,31 @@ instance HasLocation Location where
{-# INLINE location #-}
instance HasLocation (Tag a) where
location = affine' tagLocation (\d l -> d{tagLocation = Just l})
location = affine' tagLocation (\d l -> d { tagLocation = Just l })
{-# INLINE location #-}
instance HasLocation (Region a) where
location = affine' regionLocation (\d l -> d{regionLocation = Just l})
location = affine' regionLocation (\d l -> d { regionLocation = Just l })
{-# INLINE location #-}
instance HasLocation Paragraph where
location = affine' paragraphLocation (\d l -> d{paragraphLocation = Just l})
location =
affine' paragraphLocation (\d l -> d { paragraphLocation = Just l })
{-# INLINE location #-}
-- | Focus on the 'Offset' from a value parsed from a source file. If the
-- 'Offset' is modified, note that the resulting 'column' and 'line' will /also/ be
-- modified as they are denormalizations of this value.
offset :: HasLocation l => Affine' l Offset
offset = location . sparse . lens sparseLocationOffset (\sl x -> sl{sparseLocationOffset=x})
offset = location . sparse . lens
sparseLocationOffset
(\sl x -> sl { sparseLocationOffset = x })
{-# INLINE offset #-}
-- | Fetch the 'Column' from a value parsed from a source file. Modifications
-- are not allowed as the 'offset' and 'line' may become inconsistent.
column :: (HasLocation l, Contravariant f, Applicative f) => Optic' (->) f l Column
column
:: (HasLocation l, Contravariant f, Applicative f) => Optic' (->) f l Column
column = location . to locationColumn
{-# INLINE column #-}
@ -66,7 +71,8 @@ line = location . to locationLine
-- | Fetch the 'Source' a value was parsed from. Modifications are not allowed
-- as the 'line', 'offset', and 'column' may become inconsistent.
source :: (HasLocation l, Contravariant f, Applicative f) => Optic' (->) f l Source
source
:: (HasLocation l, Contravariant f, Applicative f) => Optic' (->) f l Source
source = location . to locationSource
{-# INLINE source #-}

81
src/Prosidy/Optics/Types.hs

@ -8,7 +8,7 @@ Maintainer : alex@fldcr.com
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
module Prosidy.Optics.Types
module Prosidy.Optics.Types
( -- * Classy optics
-- ** Items with 'Metadata'
HasMetadata(..)
@ -29,27 +29,36 @@ module Prosidy.Optics.Types
, _Text
, _Break
-- * Optics on common types
, key
, key
, _Assoc
, _NonEmpty
, _Series
, _SeriesNE
, _Set
) where
import Prosidy.Types
import Prosidy.Types.Assoc (toHashMap, fromHashMap)
import Prosidy.Types.Series (toSeq, fromSeq, toSeqNE, fromSeqNE)
import Prosidy.Types.Set (toHashSet, fromHashSet)
import Prosidy.Optics.Internal
import Data.Text (Text)
import Data.Sequence (Seq)
import Data.HashMap.Strict (HashMap)
import Data.HashSet (HashSet)
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
)
where
import Prosidy.Types
import Prosidy.Types.Assoc ( toHashMap
, fromHashMap
)
import Prosidy.Types.Series ( toSeq
, fromSeq
, toSeqNE
, fromSeqNE
)
import Prosidy.Types.Set ( toHashSet
, fromHashSet
)
import Prosidy.Optics.Internal
import Data.Text ( Text )
import Data.Sequence ( Seq )
import Data.HashMap.Strict ( HashMap )
import Data.HashSet ( HashSet )
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
-------------------------------------------------------------------------------
-- | A classy optic for focusing on items with 'Metadata', including 'Tag's,
@ -58,15 +67,15 @@ class HasMetadata t where
metadata :: Lens' t Metadata
instance HasMetadata Document where
metadata = lens documentMetadata (\d m -> d{documentMetadata = m})
metadata = lens documentMetadata (\d m -> d { documentMetadata = m })
{-# INLINE metadata #-}
instance HasMetadata (Tag a) where
metadata = lens tagMetadata (\d m -> d{tagMetadata = m})
metadata = lens tagMetadata (\d m -> d { tagMetadata = m })
{-# INLINE metadata #-}
instance HasMetadata (Region a) where
metadata = lens regionMetadata (\d m -> d{regionMetadata = m})
metadata = lens regionMetadata (\d m -> d { regionMetadata = m })
{-# INLINE metadata #-}
instance HasMetadata Metadata where
@ -75,8 +84,9 @@ instance HasMetadata Metadata where
-- | Fetch all properties from items which contain metadata.
properties :: HasMetadata m => Lens' m (Set Key)
properties = metadata . lens metadataProperties (\m p -> m{metadataProperties = p})
{-# INLINEABLE properties #-}
properties =
metadata . lens metadataProperties (\m p -> m { metadataProperties = p })
{-# INLINABLE properties #-}
{-# SPECIALIZE INLINE properties :: Lens' Metadata (Set Key) #-}
{-# SPECIALIZE INLINE properties :: Lens' Document (Set Key) #-}
{-# SPECIALIZE INLINE properties :: Lens' (Tag a) (Set Key) #-}
@ -84,7 +94,8 @@ properties = metadata . lens metadataProperties (\m p -> m{metadataProperties =
-- | Fetch all settings defined on items which contain metadata.
settings :: HasMetadata m => Lens' m (Assoc Key Text)
settings = metadata . lens metadataSettings (\m s -> m{metadataSettings = s})
settings =
metadata . lens metadataSettings (\m s -> m { metadataSettings = s })
{-# INLINABLE settings #-}
{-# SPECIALIZE INLINE settings :: Lens' Metadata (Assoc Key Text) #-}
{-# SPECIALIZE INLINE settings :: Lens' Document (Assoc Key Text) #-}
@ -95,14 +106,16 @@ settings = metadata . lens metadataSettings (\m s -> m{metadataSettings = s})
-- optic as a setter will add a property if set to 'True' and remove the
-- property when set to 'False'.
hasProperty :: HasMetadata m => Key -> Lens' m Bool
hasProperty k = properties . _Set . lens (HS.member k)
hasProperty k = properties . _Set . lens
(HS.member k)
(\hs b -> (if b then HS.insert else HS.delete) k hs)
{-# INLINE hasProperty #-}
-- | Select a setting from an item attached to metadata. Returns 'Nothing' if
-- no value is set.
atSetting :: HasMetadata m => Key -> Lens' m (Maybe Text)
atSetting k = settings . _Assoc . lens (HM.lookup k)
atSetting k = settings . _Assoc . lens
(HM.lookup k)
(\hm x -> maybe (HM.delete k) (HM.insert k) x hm)
{-# INLINE atSetting #-}
@ -117,32 +130,30 @@ class HasContent t where
instance HasContent Document where
type Content Document = Series Block
content = lens documentContent (\d c -> d{documentContent = c})
content = lens documentContent (\d c -> d { documentContent = c })
{-# INLINE content #-}
instance HasContent (Tag a) where
type Content (Tag a) = a
content = lens tagContent (\t c -> t{tagContent = c})
content = lens tagContent (\t c -> t { tagContent = c })
{-# INLINE content #-}
instance HasContent (Region a) where
type Content (Region a) = a
content = lens regionContent (\t c -> t{regionContent = c})
content = lens regionContent (\t c -> t { regionContent = c })
{-# INLINE content #-}
instance HasContent Paragraph where
type Content Paragraph = SeriesNE Inline
content = lens paragraphContent (\t c -> t{paragraphContent = c})
content = lens paragraphContent (\t c -> t { paragraphContent = c })
{-# INLINE content #-}
-------------------------------------------------------------------------------
-- | Focus on the inner 'Region' of 'Tag's with a name. This can be used to
-- filter 'Tag's to a specific subset for manipulation.
tagged :: Key -> Prism' (Tag a) (Region a)
tagged k = prism' (regionToTag k) $ \tag ->
if tagName tag == k
then Just $ tagToRegion tag
else Nothing
tagged k = prism' (regionToTag k)
$ \tag -> if tagName tag == k then Just $ tagToRegion tag else Nothing
{-# INLINE tagged #-}
-------------------------------------------------------------------------------
@ -155,7 +166,7 @@ _BlockTag = prism' BlockTag $ \case
-- | Focus only on paragraphs'
_BlockParagraph :: Prism' Block Paragraph
_BlockParagraph = prism' BlockParagraph $ \case
BlockParagraph p -> Just p
BlockParagraph p -> Just p
_ -> Nothing
-- | Focus only on literal tags.
@ -211,4 +222,4 @@ _SeriesNE = prism' toSeqNE fromSeqNE
-- | An isomorphism between Prosidy's 'Set' wrapper and 'HashSet'.
_Set :: Iso (Set a) (Set b) (HashSet a) (HashSet b)
_Set = iso toHashSet fromHashSet
{-# INLINE _Set #-}
{-# INLINE _Set #-}

27
src/Prosidy/Parse.hs

@ -24,16 +24,23 @@ module Prosidy.Parse
)
where
import Prosidy.Compat
import Prelude hiding (fail)
import Prosidy.Compat
import Prelude hiding ( fail )
import Prosidy.Types
import Prosidy.Source
import Prosidy.Types.Key (isValidKeyHead, isValidKeyTail, unsafeMakeKey)
import Prosidy.Types.Series (fromSeqNE, toSeqNE, fromSeq)
import Prosidy.Types.Key ( isValidKeyHead
, isValidKeyTail
, unsafeMakeKey
)
import Prosidy.Types.Series ( fromSeqNE
, toSeqNE
, fromSeq
)
import Text.Megaparsec hiding ( token
, sourceName )
import Text.Megaparsec hiding ( token
, sourceName
)
import Text.Megaparsec.Char ( char
, string
)
@ -208,10 +215,7 @@ literalBody :: P () -> P Text
literalBody end = do
literalLines <- manyTill literalLine (try $ skipSpaces *> end)
emptyLines
pure
$ Text.Lazy.toStrict
. Text.Lazy.intercalate "\n"
$ literalLines
pure $ Text.Lazy.toStrict . Text.Lazy.intercalate "\n" $ literalLines
literalLine :: P Text.Lazy.Text
literalLine = do
@ -440,8 +444,7 @@ annotateSource :: P (Maybe Location -> a) -> P a
annotateSource (P (ReaderT r)) = P . ReaderT $ \src -> do
offset <- Offset . fromIntegral <$> getOffset
result <- r src
sourceLoc <- maybe (fail sourceLocationError) pure
$ getLocation offset src
sourceLoc <- maybe (fail sourceLocationError) pure $ getLocation offset src
pure . result $ Just sourceLoc
sourceLocationError :: String

201
src/Prosidy/Source.hs

@ -14,43 +14,47 @@ Maintainer : alex@fldcr.com
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
module Prosidy.Source
( Source(..)
, Location
, SparseLocation(..)
, LineMap
, Offset(..)
, Line(..)
, Column(..)
, locationSource
, locationColumn
, locationLine
, locationOffset
, makeSource
, getSourceLine
, getLocation
, enrichLocation
, stripLocation
, lineOffsets
, lineToOffset
, offsetToLine
) where
import Data.Hashable (Hashable(..))
import Data.Vector.Unboxed (Vector, MVector, Unbox)
import Data.Text (Text)
import GHC.Generics (Generic)
import Control.DeepSeq (NFData)
import Data.Binary (Binary(..))
import Data.Aeson (ToJSON(..), FromJSON(..))
import Control.Monad (guard)
import qualified Data.Text as T
import qualified Data.Vector.Unboxed as V
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Generic.Mutable as VGM
module Prosidy.Source
( Source(..)
, Location
, SparseLocation(..)
, LineMap
, Offset(..)
, Line(..)
, Column(..)
, locationSource
, locationColumn
, locationLine
, locationOffset
, makeSource
, getSourceLine
, getLocation
, enrichLocation
, stripLocation
, lineOffsets
, lineToOffset
, offsetToLine
)
where
import Data.Hashable ( Hashable(..) )
import Data.Vector.Unboxed ( Vector
, MVector
, Unbox
)
import Data.Text ( Text )
import GHC.Generics ( Generic )
import Control.DeepSeq ( NFData )
import Data.Binary ( Binary(..) )
import Data.Aeson ( ToJSON(..)
, FromJSON(..)
)
import Control.Monad ( guard )
import qualified Data.Text as T
import qualified Data.Vector.Unboxed as V
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Generic.Mutable as VGM
-- | Information about Prosidy source file.
--
@ -82,14 +86,11 @@ makeSource :: String -> Text -> Source
makeSource name body = Source name body lineMap
where
lineMap = case T.foldl' lineMapFold (1, '\0', []) $ body of
(_, _, acc) -> LineMap . V.fromList . reverse $ acc
(_, _, acc) -> LineMap . V.fromList . reverse $ acc
lineMapFold (ix, prev, acc) ch
| ch == '\n' && prev == '\r'
= (succ ix, ch, Offset ix : drop 1 acc)
| ch == '\n' || ch == '\r'
= (succ ix, ch, Offset ix : acc)
| otherwise
= (succ ix, ch, acc)
| ch == '\n' && prev == '\r' = (succ ix, ch, Offset ix : drop 1 acc)
| ch == '\n' || ch == '\r' = (succ ix, ch, Offset ix : acc)
| otherwise = (succ ix, ch, acc)
-- | Convert an 'Offset' into a 'Location'.
getLocation :: Offset -> Source -> Maybe Location
@ -100,14 +101,11 @@ getLocation offset src = do
-- | Fetch a single line from a source.
getSourceLine :: Line -> Source -> Maybe Text
getSourceLine line source = do
start <- fromEnum <$> lineToOffset line lineMap
let end = fromEnum <$> lineToOffset (succ line) lineMap
Just
. maybe id (T.take . (flip (-) start)) end
. T.drop start
$ sourceText source
where
lineMap = sourceLineMap source
start <- fromEnum <$> lineToOffset line lineMap
let end = fromEnum <$> lineToOffset (succ line) lineMap
Just . maybe id (T.take . (flip (-) start)) end . T.drop start $ sourceText
source
where lineMap = sourceLineMap source
-- | A location in a 'Source'. The line and column numbers of this type are not
-- attached to this type; convert to a 'Location' to access those values.
@ -137,28 +135,25 @@ data Location = Location
-- | Add lazily computed line and column number information to a
-- 'SparseLocation'.
enrichLocation :: SparseLocation -> Location
enrichLocation sl = Location
{ locationSource = source
, locationOffset = offset
, locationLine = line
, locationColumn = column
}
enrichLocation sl = Location { locationSource = source
, locationOffset = offset
, locationLine = line
, locationColumn = column
}
where
source = sparseLocationSource sl
lineMap = sourceLineMap source
offset@(~(Offset offsetN)) = sparseLocationOffset sl
line = offsetToLine offset lineMap
column =
case lineToOffset line lineMap of
column = case lineToOffset line lineMap of
Just (Offset n) -> Column (offsetN - n)
Nothing -> Column 0
-- | Remove line and column number information from a 'Location'.
stripLocation :: Location -> SparseLocation
stripLocation l = SparseLocation
{ sparseLocationSource = locationSource l
, sparseLocationOffset = locationOffset l
}
stripLocation l = SparseLocation { sparseLocationSource = locationSource l
, sparseLocationOffset = locationOffset l
}
-- | A dense vector containing offsets poiting to the start of each line. That
-- is, the starting position of the third line of a file can be found at
@ -172,8 +167,7 @@ instance Binary LineMap where
put (LineMap v) = put (V.toList v)
instance Hashable LineMap where
hashWithSalt salt (LineMap v) =
V.foldl' hashWithSalt salt v
hashWithSalt salt (LineMap v) = V.foldl' hashWithSalt salt v
-- | Convert a 'LineMap' into a list of 'Offset's, corresponding to the first
-- character of a line. Note that the initial offset is omitted-- the offset at
@ -184,26 +178,27 @@ lineOffsets (LineMap v) = V.toList v
-- | Fetch the 'Offset' for the given 'Line'. Evaluates to 'Nothing' if the
-- given 'Line' does not appear in the LineMap
lineToOffset :: Line -> LineMap -> Maybe Offset
lineToOffset (Line 0) _ = Just $ Offset 0
lineToOffset (Line 0 ) _ = Just $ Offset 0
lineToOffset (Line nth) (LineMap xs) = xs V.!? fromIntegral (pred nth)
-- | Fetch the 'Line' number for a given 'Offset'. Newlines will be attributed
-- the line that they terminate, rather than the line started immediately
-- afterwards.
offsetToLine :: Offset -> LineMap -> Line
offsetToLine offset (LineMap xs) = Line . fromIntegral $ go Nothing 0 (V.length xs)
offsetToLine offset (LineMap xs) = Line . fromIntegral $ go Nothing
0
(V.length xs)
where
go result min max
| min >= max = maybe 0 succ result
| otherwise =
let
nthIndex = ((max - min) `div` 2) + min
nthOffset = xs V.! nthIndex
in
case nthOffset `compare` offset of
EQ -> succ nthIndex
LT -> go (Just nthIndex) (nthIndex + 1) max
GT -> go result min nthIndex
| min >= max
= maybe 0 succ result
| otherwise
= let nthIndex = ((max - min) `div` 2) + min
nthOffset = xs V.! nthIndex
in case nthOffset `compare` offset of
EQ -> succ nthIndex
LT -> go (Just nthIndex) (nthIndex + 1) max
GT -> go result min nthIndex
-- | A line number.
--
@ -229,55 +224,45 @@ newtype Offset = Offset Word
newtype instance MVector s Offset = MV_Offset (MVector s Word)
instance VGM.MVector MVector Offset where
basicLength (MV_Offset m) =
VGM.basicLength m
{-# INLINE basicLength #-}
basicLength (MV_Offset m) = VGM.basicLength m
{-# INLINE basicLength #-}
basicUnsafeSlice ix len (MV_Offset m) =
MV_Offset $ VGM.basicUnsafeSlice ix len m
{-# INLINE basicUnsafeSlice #-}
basicUnsafeSlice ix len (MV_Offset m) =
MV_Offset $ VGM.basicUnsafeSlice ix len m
{-# INLINE basicUnsafeSlice #-}
basicOverlaps (MV_Offset x) (MV_Offset y) =
VGM.basicOverlaps x y
{-# INLINE basicOverlaps #-}
basicOverlaps (MV_Offset x) (MV_Offset y) = VGM.basicOverlaps x y
{-# INLINE basicOverlaps #-}
basicUnsafeNew len =
MV_Offset <$> VGM.basicUnsafeNew len
{-# INLINE basicUnsafeNew #-}
basicUnsafeNew len = MV_Offset <$> VGM.basicUnsafeNew len
{-# INLINE basicUnsafeNew #-}
basicInitialize (MV_Offset v) =
VGM.basicInitialize v
{-# INLINE basicInitialize #-}
basicInitialize (MV_Offset v) = VGM.basicInitialize v
{-# INLINE basicInitialize #-}
basicUnsafeRead (MV_Offset v) =
fmap Offset <$> VGM.basicUnsafeRead v
{-# INLINE basicUnsafeRead #-}
basicUnsafeRead (MV_Offset v) = fmap Offset <$> VGM.basicUnsafeRead v
{-# INLINE basicUnsafeRead #-}
basicUnsafeWrite (MV_Offset v) ix (Offset w) =
VGM.basicUnsafeWrite v ix w
{-# INLINE basicUnsafeWrite #-}
basicUnsafeWrite (MV_Offset v) ix (Offset w) = VGM.basicUnsafeWrite v ix w
{-# INLINE basicUnsafeWrite #-}
newtype instance Vector Offset = V_Offset (Vector Word)
instance VG.Vector Vector Offset where
basicUnsafeFreeze (MV_Offset v) =
V_Offset <$> VG.basicUnsafeFreeze v
basicUnsafeFreeze (MV_Offset v) = V_Offset <$> VG.basicUnsafeFreeze v
{-# INLINE basicUnsafeFreeze #-}
basicUnsafeThaw (V_Offset v) =
MV_Offset <$> VG.basicUnsafeThaw v
basicUnsafeThaw (V_Offset v) = MV_Offset <$> VG.basicUnsafeThaw v
{-# INLINE basicUnsafeThaw #-}
basicLength (V_Offset v) =
VG.basicLength v
basicLength (V_Offset v) = VG.basicLength v
{-# INLINE basicLength #-}
basicUnsafeSlice ix len (V_Offset v) =
V_Offset $ VG.basicUnsafeSlice ix len v
{-# INLINE basicUnsafeSlice #-}
basicUnsafeIndexM (V_Offset v) ix =
Offset <$> VG.basicUnsafeIndexM v ix
basicUnsafeIndexM (V_Offset v) ix = Offset <$> VG.basicUnsafeIndexM v ix
{-# INLINE basicUnsafeIndexM #-}
instance Unbox Offset where
instance Unbox Offset where

202
src/Prosidy/Types.hs

@ -18,7 +18,7 @@ Maintainer : alex@fldcr.com
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
module Prosidy.Types
module Prosidy.Types
( -- * Documents
Document(..)
, documentToRegion
@ -39,23 +39,42 @@ module Prosidy.Types
, Metadata(..)
, Region(..)
-- * Utility wrappers
, module X
) where
import Prosidy.Types.Assoc as X (Assoc(..))
import Prosidy.Types.Key as X (Key, KeyError(..), InvalidCharacter, makeKey, rawKey)
import Prosidy.Types.Series as X (Series(..), SeriesNE)
import Prosidy.Types.Set as X (Set(..))
import Prosidy.Source (Location)
import Data.Text (Text)
import GHC.Generics (Generic)
import Control.DeepSeq (NFData)
import Data.Binary (Binary)
import Data.Hashable (Hashable)
import Data.Aeson (ToJSON(..), FromJSON(..), withObject, (.:), (.=), object, pairs)
import qualified Data.Aeson as Aeson
, module X
)
where
import Prosidy.Types.Assoc as X
( Assoc(..) )
import Prosidy.Types.Key as X
( Key
, KeyError(..)
, InvalidCharacter
, makeKey
, rawKey
)
import Prosidy.Types.Series as X
( Series(..)
, SeriesNE
)
import Prosidy.Types.Set as X
( Set(..) )
import Prosidy.Source ( Location )
import Data.Text ( Text )
import GHC.Generics ( Generic )
import Control.DeepSeq ( NFData )
import Data.Binary ( Binary )
import Data.Hashable ( Hashable )
import Data.Aeson ( ToJSON(..)
, FromJSON(..)
, withObject
, (.:)
, (.=)
, object
, pairs
)
import qualified Data.Aeson as Aeson
-------------------------------------------------------------------------------
-- | A sum type enumerating allowed types inside of a block context.
@ -77,39 +96,33 @@ instance FromJSON Block where
"literal" -> BlockLiteral <$> o .: "value"
_ -> fail $ "unknown tag subtype: " <> show subtype
"paragraph" -> BlockParagraph <$> o .: "value"
_ -> fail $ "unknown block type: " <> show ty
_ -> fail $ "unknown block type: " <> show ty
instance ToJSON Block where
toEncoding b = pairs . mconcat $ case b of
BlockLiteral t ->
BlockLiteral t ->
[ "type" .= ("tag" :: Text)
, "subtype" .= ("literal" :: Text)
, "value" .= t
]
BlockParagraph p ->
[ "type" .= ("paragraph" :: Text)
, "value" .= p
, "subtype" .= ("literal" :: Text)
, "value" .= t
]
BlockTag t ->
BlockParagraph p -> ["type" .= ("paragraph" :: Text), "value" .= p]
BlockTag t ->
[ "type" .= ("tag" :: Text)
, "subtype" .= ("block" :: Text)
, "value" .= t
, "subtype" .= ("block" :: Text)
, "value" .= t
]
toJSON b = object $ case b of
BlockLiteral t ->
BlockLiteral t ->
[ "type" .= ("tag" :: Text)
, "subtype" .= ("literal" :: Text)
, "value" .= t
]
BlockParagraph p ->
[ "type" .= ("paragraph" :: Text)
, "value" .= p
, "subtype" .= ("literal" :: Text)
, "value" .= t
]
BlockTag t ->
BlockParagraph p -> ["type" .= ("paragraph" :: Text), "value" .= p]
BlockTag t ->
[ "type" .= ("tag" :: Text)
, "subtype" .= ("block" :: Text)
, "value" .= t
, "subtype" .= ("block" :: Text)
, "value" .= t
]
-------------------------------------------------------------------------------
@ -122,20 +135,14 @@ data Document = Document
deriving anyclass (Hashable, NFData, Binary)
instance FromJSON Document where
parseJSON = withObject "Document" $ \o -> Document
<$> o .: "metadata"
<*> o .: "content"
parseJSON = withObject "Document"
$ \o -> Document <$> o .: "metadata" <*> o .: "content"
instance ToJSON Document where
toEncoding (Document md ct) = pairs $ mconcat
[ "metadata" .= md
, "content" .= ct
]
toEncoding (Document md ct) =
pairs $ mconcat ["metadata" .= md, "content" .= ct]
toJSON (Document md ct) = object
[ "metadata" .= md
, "content" .= ct
]
toJSON (Document md ct) = object ["metadata" .= md, "content" .= ct]
-- | Convert a 'Document' to a 'Region'. The resulting 'Region' will never have
-- a 'Location' attached.
@ -169,39 +176,28 @@ instance FromJSON Inline where
ty <- o .: "type"
case ty :: Text of
"break" -> pure Break
"tag" -> InlineTag <$> o .: "value"
"tag" -> InlineTag <$> o .: "value"
"text" -> InlineText <$> o .: "value"
_ -> fail $ "unknown inline type: " <> show ty
instance ToJSON Inline where
toEncoding i = pairs . mconcat $ case i of
Break ->
[ "type" .= ("break" :: Text)
, "value" .= Aeson.Null
]
InlineTag t ->
[ "type" .= ("tag" :: Text)
Break -> ["type" .= ("break" :: Text), "value" .= Aeson.Null]
InlineTag t ->
[ "type" .= ("tag" :: Text)
, "subtype" .= ("inline" :: Text)
, "value" .= t
]
InlineText t ->
[ "type" .= ("text" :: Text)
, "value" .= t
]
InlineText t -> ["type" .= ("text" :: Text), "value" .= t]
toJSON i = object $ case i of
Break ->
[ "type" .= ("break" :: Text)
]
InlineTag t ->
[ "type" .= ("tag" :: Text)
Break -> ["type" .= ("break" :: Text)]
InlineTag t ->
[ "type" .= ("tag" :: Text)
, "subtype" .= ("inline" :: Text)
, "value" .= t
]
InlineText t ->
[ "type" .= ("text" :: Text)
, "value" .= t
]
InlineText t -> ["type" .= ("text" :: Text), "value" .= t]
-------------------------------------------------------------------------------
-- | A set of properties and settings, associated with a 'Region'.
@ -209,7 +205,7 @@ instance ToJSON Inline where
-- The namespaces of properties and settings are distinct; a property can share
-- a name with a setting without conflict.
data Metadata = Metadata
{ metadataProperties :: Set Key
{ metadataProperties :: Set Key
-- ^ Properties are a set of 'Key's with no associated value.
, metadataSettings :: Assoc Key Text
-- ^ Settings are 'Key's with an attached value.
@ -221,30 +217,23 @@ instance Monoid Metadata where
mempty = Metadata mempty mempty
instance Semigroup Metadata where
Metadata p1 s1 <> Metadata p2 s2 =
Metadata (p1 <> p2) (s1 <> s2)
Metadata p1 s1 <> Metadata p2 s2 = Metadata (p1 <> p2) (s1 <> s2)
instance FromJSON Metadata where
parseJSON = withObject "Metadata" $ \o -> Metadata
<$> o .: "properties"
<*> o .: "settings"
parseJSON = withObject "Metadata"
$ \o -> Metadata <$> o .: "properties" <*> o .: "settings"
instance ToJSON Metadata where
toEncoding (Metadata ps ss) = pairs $ mconcat
[ "properties" .= ps
, "settings" .= ss
]
toEncoding (Metadata ps ss) =
pairs $ mconcat ["properties" .= ps, "settings" .= ss]
toJSON (Metadata ps ss) = object
[ "properties" .= ps
, "settings" .= ss
]
toJSON (Metadata ps ss) = object ["properties" .= ps, "settings" .= ss]
-------------------------------------------------------------------------------
-- | A non-empty collection of 'Inline' items. A 'Paragraph' represents the
-- border between block and inline contexts. All ancestors of a paragraph are
-- block items or a document, and all children are inline items.
data Paragraph = Paragraph
data Paragraph = Paragraph
{ paragraphContent :: SeriesNE Inline
, paragraphLocation :: Maybe Location
}
@ -256,7 +245,7 @@ instance FromJSON Paragraph where
instance ToJSON Paragraph where
toEncoding (Paragraph s _) = toEncoding s
toJSON (Paragraph s _) = toJSON s
toJSON (Paragraph s _) = toJSON s
-------------------------------------------------------------------------------
-- | An untagged structural grouping of items with type @a@. Regions do not
@ -270,10 +259,7 @@ data Region a = Region
deriving anyclass (Hashable, NFData, Binary)
instance ToJSON a => ToJSON (Region a) where
toJSON (Region md ct _) = Aeson.object
[ "metadata" .= md
, "content" .= ct
]
toJSON (Region md ct _) = Aeson.object ["metadata" .= md, "content" .= ct]
-------------------------------------------------------------------------------
-- | A 'Region', annotated with a tag name.
@ -287,32 +273,30 @@ data Tag a = Tag
deriving anyclass (Hashable, NFData, Binary)
instance FromJSON a => FromJSON (Tag a) where
parseJSON = withObject "Tag" $ \o -> Tag
<$> o .: "name"
<*> o .: "metadata"
<*> o .: "content"
<*> pure Nothing
parseJSON = withObject "Tag" $ \o ->
Tag
<$> o
.: "name"
<*> o
.: "metadata"
<*> o
.: "content"
<*> pure Nothing
instance ToJSON a => ToJSON (Tag a) where
toEncoding (Tag nm md ct _) = pairs $ mconcat
[ "name" .= nm
, "metadata" .= md
, "content" .= ct
]
toJSON (Tag nm md ct _) = object
[ "name" .= nm
, "metadata" .= md
, "content" .= ct
]
toEncoding (Tag nm md ct _) =
pairs $ mconcat ["name" .= nm, "metadata" .= md, "content" .= ct]
toJSON (Tag nm md ct _) =
object ["name" .= nm, "metadata" .= md, "content" .= ct]
-- | A 'Tag' containing zero or more 'Block' items.
-- Specified in Prosidy source with the @#-@ sigil.
type BlockTag = Tag (Series Block)
type BlockTag = Tag (Series Block)
-- | A 'Tag' containing zero or more 'Inline' items.
-- Specified in Prosidy source with the @#@ sigil.
type InlineTag = Tag (Series Inline)
type InlineTag = Tag (Series Inline)
-- | A 'Tag' containing a single plain-text item.
-- Specified in Prosidy source with the @#=@ sigil.
@ -324,4 +308,4 @@ tagToRegion (Tag _ md ct loc) = Region md ct loc
-- | Convert a 'Region' to a 'Tag' by providing a tag name.
regionToTag :: Key -> Region a -> Tag a
regionToTag name (Region md ct loc) = Tag name md ct loc
regionToTag name (Region md ct loc) = Tag name md ct loc

41
src/Prosidy/Types/Assoc.hs

@ -8,21 +8,18 @@ Maintainer : alex@fldcr.com
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Prosidy.Types.Assoc
( Assoc(..)
, asHashMap
, fromHashMap
, toHashMap
) where
import Data.HashMap.Strict (HashMap)
import GHC.Generics (Generic)
import Data.Aeson (ToJSON(..), FromJSON(..))
import Control.DeepSeq (NFData)
import Data.Binary (Binary(..))
import Data.Hashable (Hashable(..))
import qualified Data.HashMap.Strict as HM
module Prosidy.Types.Assoc (Assoc(..), asHashMap, fromHashMap, toHashMap) where
import Data.HashMap.Strict ( HashMap )
import GHC.Generics ( Generic )
import Data.Aeson ( ToJSON(..)
, FromJSON(..)
)
import Control.DeepSeq ( NFData )
import Data.Binary ( Binary(..) )
import Data.Hashable ( Hashable(..) )
import qualified Data.HashMap.Strict as HM
-- | An associative mapping of keys to values.
--
@ -35,15 +32,17 @@ newtype Assoc k v = Assoc (HashMap k v)
deriving newtype (Eq, Foldable, Functor, Show, ToJSON, FromJSON, NFData, Semigroup, Monoid, Hashable)
instance (Eq k, Hashable k, Binary k, Binary v) => Binary (Assoc k v) where
get =
Assoc . HM.fromList <$> get
get = Assoc . HM.fromList <$> get
put (Assoc hm) =
put $ HM.toList hm
put (Assoc hm) = put $ HM.toList hm
-- | Given a function which operates on a 'HashMap', return a function which
-- performs an equivalent transfromation on an 'Assoc'.
asHashMap :: Functor f => (HashMap k v -> f (HashMap k' v')) -> Assoc k v -> f (Assoc k' v')
asHashMap
:: Functor f
=> (HashMap k v -> f (HashMap k' v'))
-> Assoc k v
-> f (Assoc k' v')
asHashMap f (Assoc a) = Assoc <$> f a
-- | Convert a 'HashMap' to an 'Assoc'.
@ -52,4 +51,4 @@ fromHashMap = Assoc
-- | Convert an 'Assoc' to a 'HashMap'.
toHashMap :: Assoc k v -> HashMap k v
toHashMap (Assoc hm) = hm
toHashMap (Assoc hm) = hm

85
src/Prosidy/Types/Key.hs

@ -23,7 +23,7 @@ Maintainer : alex@fldcr.com
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE QuasiQuotes #-}
module Prosidy.Types.Key
module Prosidy.Types.Key
( -- * The 'Key' type.
Key
-- * Creating 'Key's and unwrapping them
@ -36,23 +36,30 @@ module Prosidy.Types.Key
-- * Errors
, KeyError(..)
, InvalidCharacter(..)
) where
)
where
import Data.Text (Text)
import Data.Aeson (ToJSON(..), ToJSONKey(..), FromJSON(..), FromJSONKey(..))
import GHC.Generics (Generic)
import Control.DeepSeq (NFData)
import Data.Binary (Binary)
import Data.Hashable (Hashable)
import Data.String (IsString(..))
import Data.Foldable (for_)
import Control.Monad (unless)
import Control.Exception (Exception(..), throw)
import Data.Text ( Text )
import Data.Aeson ( ToJSON(..)
, ToJSONKey(..)
, FromJSON(..)
, FromJSONKey(..)
)
import GHC.Generics ( Generic )
import Control.DeepSeq ( NFData )
import Data.Binary ( Binary )
import Data.Hashable ( Hashable )
import Data.String ( IsString(..) )
import Data.Foldable ( for_ )
import Control.Monad ( unless )
import Control.Exception ( Exception(..)
, throw
)
import qualified Data.Aeson as Aeson
import qualified Data.Char as Char
import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.Aeson as Aeson
import qualified Data.Char as Char
import qualified Data.Set as Set
import qualified Data.Text as Text
-- | A 'Key' is an identifier used in tags, properties, and setting names.
newtype Key = Key Text
@ -70,22 +77,27 @@ instance FromJSON Key where
either (fail . displayException) pure $ makeKey text
instance FromJSONKey Key where
fromJSONKey = Aeson.FromJSONKeyTextParser $
either (fail . displayException) pure . makeKey
fromJSONKey =
Aeson.FromJSONKeyTextParser
$ either (fail . displayException) pure
. makeKey
-- | Create a new 'Key', checking its validity.
makeKey :: Text -> Either KeyError Key
makeKey rawText = case Text.unpack rawText of
[] ->
Left EmptyKeyError
[] -> Left EmptyKeyError
keyHead : keyTail
| isValidKeyHead keyHead -> do
for_ (zip [1..] keyTail) $ \(ix, ch) ->
unless (isValidKeyTail ch) $
Left . InvalidCharacterError $ InvalidCharacter rawText ix ch
Right $ Key rawText
| otherwise ->
Left . InvalidCharacterError $ InvalidCharacter rawText 0 keyHead
| isValidKeyHead keyHead -> do
for_ (zip [1 ..] keyTail) $ \(ix, ch) ->
unless (isValidKeyTail ch)
$ Left
. InvalidCharacterError
$ InvalidCharacter rawText ix ch
Right $ Key rawText
| otherwise -> Left . InvalidCharacterError $ InvalidCharacter
rawText
0
keyHead
-- | Create a new 'Key' /without/ performing any checks.
unsafeMakeKey :: Text -> Key
@ -106,7 +118,7 @@ isValidKeyHead = (||) <$> Char.isAlphaNum <*> (== '_')
isValidKeyTail :: Char -> Bool
isValidKeyTail = not . invalid
where
invalid = (||) <$> Char.isSpace <*> (`Set.member` reserved)
invalid = (||) <$> Char.isSpace <*> (`Set.member` reserved)
reserved = Set.fromList "\\#{}[]:=,"
-- | Errors returned when creating invalid keys.
@ -132,11 +144,12 @@ data InvalidCharacter = InvalidCharacter
instance Exception KeyError where
displayException EmptyKeyError =
"Cannot create a Key with a length of zero."
displayException (InvalidCharacterError (InvalidCharacter text nth ch)) = unwords
[ "Cannot create a Key named " <> show text <> ":"
, "the character"
, show ch
, "at index"
, show nth
, "is not allowed."
]
displayException (InvalidCharacterError (InvalidCharacter text nth ch)) =
unwords
[ "Cannot create a Key named " <> show text <> ":"
, "the character"
, show ch
, "at index"
, show nth
, "is not allowed."
]

45
src/Prosidy/Types/Series.hs

@ -8,7 +8,7 @@ Maintainer : alex@fldcr.com
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Prosidy.Types.Series
module Prosidy.Types.Series
( -- * Possibly empty collections
Series(..)
, asSeq
@ -18,18 +18,23 @@ module Prosidy.Types.Series
, SeriesNE
, fromSeqNE
, toSeqNE
) where
import Data.Sequence (Seq)
import GHC.Generics (Generic)
import Data.Aeson (ToJSON(..), FromJSON(..))
import Control.DeepSeq (NFData)
import Data.Binary (Binary(..))
import Data.Hashable (Hashable(..))
import Data.Foldable (toList, foldl')
import Control.Monad (guard)
import qualified Data.Sequence as Seq
)
where
import Data.Sequence ( Seq )
import GHC.Generics ( Generic )
import Data.Aeson ( ToJSON(..)
, FromJSON(..)
)
import Control.DeepSeq ( NFData )
import Data.Binary ( Binary(..) )
import Data.Hashable ( Hashable(..) )
import Data.Foldable ( toList
, foldl'
)
import Control.Monad ( guard )
import qualified Data.Sequence as Seq
-- | A newtype wrapper around a sequential collection.
--
@ -47,8 +52,7 @@ instance Binary a => Binary (Series a) where
{-# INLINE put #-}
instance Hashable a => Hashable (Series a) where
hashWithSalt salt (Series xs) =
foldl' hashWithSalt salt xs
hashWithSalt salt (Series xs) = foldl' hashWithSalt salt xs
instance Traversable Series where
traverse f (Series xs) = Series <$> traverse f xs
@ -59,7 +63,11 @@ newtype SeriesNE a = SeriesNE (Seq a)
deriving newtype (Eq, Foldable, Functor, Applicative, ToJSON, NFData, Semigroup, Monoid)
instance Binary a => Binary (SeriesNE a) where
get = maybe (error "SeriesNE must be non-empty") id . fromSeqNE . Seq.fromList <$> get
get =
maybe (error "SeriesNE must be non-empty") id
. fromSeqNE
. Seq.fromList
<$> get
{-# INLINE get #-}
put (SeriesNE xs) = put $ toList xs
@ -72,8 +80,7 @@ instance FromJSON a => FromJSON (SeriesNE a) where
pure $ SeriesNE inner
instance Hashable a => Hashable (SeriesNE a) where
hashWithSalt salt (SeriesNE xs) =
foldl' hashWithSalt salt xs
hashWithSalt salt (SeriesNE xs) = foldl' hashWithSalt salt xs
instance Traversable SeriesNE where
traverse f (SeriesNE xs) = SeriesNE <$> traverse f xs
@ -99,4 +106,4 @@ fromSeqNE s | otherwise = Just (SeriesNE s)
-- | Convert a 'SeriesNE' into a 'Seq'. The returned 'Seq' is guarenteed to
-- always contain at least one element.
toSeqNE :: SeriesNE a -> Seq a
toSeqNE (SeriesNE a) = a
toSeqNE (SeriesNE a) = a

39
src/Prosidy/Types/Set.hs

@ -9,21 +9,20 @@ Maintainer : alex@fldcr.com
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
module Prosidy.Types.Set
( Set(..)
, asHashSet
, fromHashSet
, toHashSet
) where
import Data.HashSet (HashSet)
import GHC.Generics (Generic)
import Data.Aeson (FromJSONKey, ToJSONKey, ToJSON(..), FromJSON(..))
import Control.DeepSeq (NFData)
import Data.Binary (Binary(..))
import Data.Hashable (Hashable(..))
import qualified Data.HashSet as HS
import qualified Data.HashMap.Strict as HM
module Prosidy.Types.Set (Set(..), asHashSet, fromHashSet, toHashSet) where
import Data.HashSet ( HashSet )
import GHC.Generics ( Generic )
import Data.Aeson ( FromJSONKey
, ToJSONKey
, ToJSON(..)
, FromJSON(..)
)
import Control.DeepSeq ( NFData )
import Data.Binary ( Binary(..) )
import Data.Hashable ( Hashable(..) )
import qualified Data.HashSet as HS
import qualified Data.HashMap.Strict as HM
-- | A newtype wrapper around an unordered collection of unique elements.
--
@ -42,11 +41,9 @@ instance (Hashable a, Eq a, FromJSONKey a) => FromJSON (Set a) where
pure . Set . HM.keysSet $ HM.filter id m
instance (Eq a, Hashable a, Binary a) => Binary (Set a) where
get =
Set . HS.fromList <$> get
put (Set s) =
put $ HS.toList s
get = Set . HS.fromList <$> get
put (Set s) = put $ HS.toList s
-- | Given a function which operates on 'HashSet's, return a function which
-- performs the same operation on a 'Set'.
@ -59,4 +56,4 @@ toHashSet (Set s) = s
-- | Convert a 'HashSet' to a 'Set'.
fromHashSet :: HashSet a -> Set a
fromHashSet = Set
fromHashSet = Set
Loading…
Cancel
Save