godot-megaparsec 0.1.0.0 → 0.2.0.0
raw patch · 4 files changed
+126/−55 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Godot.Parser.Resource: instance Godot.Parser.Resource.HasDescriptor Godot.Parser.Resource.GdnsParsed Godot.Parser.Resource.GdnsDescriptor
- Godot.Parser.Resource: instance Godot.Parser.Resource.HasDescriptor Godot.Parser.Resource.TscnParsed Godot.Parser.Resource.TscnDescriptor
- Godot.Parser.Resource: instance Godot.Parser.Resource.HasFormat Godot.Parser.Resource.GdnsDescriptor GHC.Types.Int
- Godot.Parser.Resource: instance Godot.Parser.Resource.HasFormat Godot.Parser.Resource.TscnDescriptor GHC.Types.Int
- Godot.Parser.Resource: instance Godot.Parser.Resource.HasLoadSteps Godot.Parser.Resource.GdnsDescriptor GHC.Types.Int
- Godot.Parser.Resource: instance Godot.Parser.Resource.HasLoadSteps Godot.Parser.Resource.TscnDescriptor GHC.Types.Int
- Godot.Parser.Resource: instance Godot.Parser.Resource.HasSections Godot.Parser.Resource.GdnsParsed [Godot.Parser.Resource.GodotSection]
- Godot.Parser.Resource: instance Godot.Parser.Resource.HasSections Godot.Parser.Resource.TscnParsed [Godot.Parser.Resource.GodotSection]
- Godot.Parser.Resource: instance Godot.Parser.Resource.HasTy Godot.Parser.Resource.GdnsDescriptor Data.Text.Internal.Text
+ Godot.Parser.Resource: Other :: OtherParsed -> GodotParsed
+ Godot.Parser.Resource: OtherDescriptor :: Text -> HashMap Text GodotValue -> OtherDescriptor
+ Godot.Parser.Resource: OtherParsed :: OtherDescriptor -> [GodotSection] -> OtherParsed
+ Godot.Parser.Resource: [_otherDescriptorHeaderName] :: OtherDescriptor -> Text
+ Godot.Parser.Resource: [_otherDescriptorHeaders] :: OtherDescriptor -> HashMap Text GodotValue
+ Godot.Parser.Resource: [_otherParsedDescriptor] :: OtherParsed -> OtherDescriptor
+ Godot.Parser.Resource: [_otherParsedSections] :: OtherParsed -> [GodotSection]
+ Godot.Parser.Resource: data OtherDescriptor
+ Godot.Parser.Resource: data OtherParsed
+ Godot.Parser.Resource: godotParser :: Parser GodotParsed
+ Godot.Parser.Resource: instance GHC.Generics.Generic Godot.Parser.Resource.OtherDescriptor
+ Godot.Parser.Resource: instance GHC.Generics.Generic Godot.Parser.Resource.OtherParsed
+ Godot.Parser.Resource: instance GHC.Show.Show Godot.Parser.Resource.OtherDescriptor
+ Godot.Parser.Resource: instance GHC.Show.Show Godot.Parser.Resource.OtherParsed
+ Godot.Parser.Resource: otherParser :: Parser OtherParsed
+ Godot.Parser.Resource.Lens: class HasDescriptor s a | s -> a
+ Godot.Parser.Resource.Lens: class HasFormat s a | s -> a
+ Godot.Parser.Resource.Lens: class HasHeaderName s a | s -> a
+ Godot.Parser.Resource.Lens: class HasHeaders s a | s -> a
+ Godot.Parser.Resource.Lens: class HasLoadSteps s a | s -> a
+ Godot.Parser.Resource.Lens: class HasSections s a | s -> a
+ Godot.Parser.Resource.Lens: class HasTy s a | s -> a
+ Godot.Parser.Resource.Lens: descriptor :: HasDescriptor s a => Lens' s a
+ Godot.Parser.Resource.Lens: format :: HasFormat s a => Lens' s a
+ Godot.Parser.Resource.Lens: headerName :: HasHeaderName s a => Lens' s a
+ Godot.Parser.Resource.Lens: headers :: HasHeaders s a => Lens' s a
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasDescriptor Godot.Parser.Resource.GdnsParsed Godot.Parser.Resource.GdnsDescriptor
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasDescriptor Godot.Parser.Resource.OtherParsed Godot.Parser.Resource.OtherDescriptor
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasDescriptor Godot.Parser.Resource.TscnParsed Godot.Parser.Resource.TscnDescriptor
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasFormat Godot.Parser.Resource.GdnsDescriptor GHC.Types.Int
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasFormat Godot.Parser.Resource.TscnDescriptor GHC.Types.Int
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasHeaderName Godot.Parser.Resource.OtherDescriptor Data.Text.Internal.Text
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasHeaders Godot.Parser.Resource.OtherDescriptor (Data.HashMap.Internal.HashMap Data.Text.Internal.Text Godot.Parser.Resource.GodotValue)
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasLoadSteps Godot.Parser.Resource.GdnsDescriptor GHC.Types.Int
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasLoadSteps Godot.Parser.Resource.TscnDescriptor GHC.Types.Int
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasSections Godot.Parser.Resource.GdnsParsed [Godot.Parser.Resource.GodotSection]
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasSections Godot.Parser.Resource.OtherParsed [Godot.Parser.Resource.GodotSection]
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasSections Godot.Parser.Resource.TscnParsed [Godot.Parser.Resource.GodotSection]
+ Godot.Parser.Resource.Lens: instance Godot.Parser.Resource.Lens.HasTy Godot.Parser.Resource.GdnsDescriptor Data.Text.Internal.Text
+ Godot.Parser.Resource.Lens: loadSteps :: HasLoadSteps s a => Lens' s a
+ Godot.Parser.Resource.Lens: sections :: HasSections s a => Lens' s a
+ Godot.Parser.Resource.Lens: ty :: HasTy s a => Lens' s a
Files
- bench/Main.hs +5/−1
- godot-megaparsec.cabal +4/−2
- src/Godot/Parser/Resource.hs +89/−52
- src/Godot/Parser/Resource/Lens.hs +28/−0
bench/Main.hs view
@@ -13,7 +13,11 @@ main :: IO () main = do- let parseFile f = fromRight (error $ "Unable to parse " <> f) . P.parse tscnParser "" <$> T.readFile f+ let parseFile f =+ fromRight (error $ "Unable to parse " <> f) . P.parse godotParser ""+ <$> T.readFile f defaultMain [ bench "EntityMap" $ whnfIO (parseFile "bench/EntityMap.tscn") , bench "Geography" $ whnfIO (parseFile "bench/Geography.tscn")+ , bench "MarketActor" $ whnfIO (parseFile "bench/MarketActor.tscn")+ , bench "Other" $ whnfIO (parseFile "bench/Other.other") , bench "BigFile" $ whnfIO (parseFile "bench/BigFile.tscn")]
godot-megaparsec.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: godot-megaparsec-version: 0.1.0.0+version: 0.2.0.0 -- A short (one-line) description of the package. synopsis: Megaparsec parser for Godot `tscn` and `gdns` files.@@ -29,7 +29,9 @@ location: https://github.com/WinstonHartnett/godot-megaparsec.git library- exposed-modules: Godot.Parser.Resource+ exposed-modules:+ Godot.Parser.Resource+ Godot.Parser.Resource.Lens -- Modules included in this library but not exported. -- other-modules:
src/Godot/Parser/Resource.hs view
@@ -1,30 +1,39 @@-{-# LANGUAGE DeriveGeneric #-}+{-|+Module : Godot.Parser.Resource+Description : Megaparsec parser for the Godot resource file format.+Copyright : (c) Winston Hartnett, 2021+License : GPL-3+Maintainer : whartnett@gmail.com+Stability : experimental+Portability : POSIX -{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE MultiParamTypeClasses #-}+A parser for Godot resource file formats. Currently only supports auto-generated+`tscn` and `gdns` files.+-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-} module Godot.Parser.Resource (GodotValue(..) ,GodotSection(..) ,TscnDescriptor(..) ,TscnParsed(..)+ ,OtherDescriptor(..)+ ,OtherParsed(..) ,GdnsDescriptor(..) ,GdnsParsed(..) ,GodotParsed(..) ,tscnParser- ,gdnsParser) where+ ,gdnsParser+ ,otherParser+ ,godotParser) where import Control.Applicative ((<|>),liftA2)-import Control.Lens import Control.Monad (unless) import Data.Char (isAlphaNum,isDigit,isUpper) import Data.Either (fromRight)-import Data.Foldable (foldl') import Data.Functor (($>)) import qualified Data.HashMap.Lazy as M import qualified Data.HashSet as S@@ -35,6 +44,8 @@ import GHC.Generics (Generic) +import Prelude hiding (exponent)+ import qualified Text.Megaparsec as P import qualified Text.Megaparsec.Char as P import qualified Text.Megaparsec.Char.Lexer as P (decimal,signed)@@ -103,17 +114,19 @@ godotValueP = do nc <- T.head . P.stateInput <$> P.getParserState case nc of- '"' -> GodotString <$> godotStringP- '[' -> GodotArr <$> godotArrP- '{' -> GodotDict <$> godotDictP- 't' -> GodotBool <$> godotBoolP- 'f' -> GodotBool <$> godotBoolP- 'n' -> godotNullP+ '"' -> GodotString <$> godotStringP+ '[' -> GodotArr <$> godotArrP+ '{' -> GodotDict <$> godotDictP+ 't' -> GodotBool <$> godotBoolP+ 'f' -> GodotBool <$> godotBoolP+ 'n' -> godotNullP l | isUpper l || l == '@' -> GodotConstructor <$> godotConstructorP- _ -> P.try (GodotFloat <$> godotFloatP) <|> P.try (GodotInt <$> godotIntP)+ _ -> P.try (GodotFloat <$> godotFloatP) <|> P.try (GodotInt <$> godotIntP) -- | Values parsed from a Tscn file.+--+-- Constructors are `(constructor name, constructor args)`. data GodotValue = GodotConstructor (T.Text, [GodotValue]) | GodotInt Int@@ -125,8 +138,6 @@ | GodotNull deriving (Show,Generic,Eq) -makeLenses ''GodotValue- -- There aren't any lenses to unwrap sum types AFAIK :/ -- Surely there's a better way to do this. unGodotConstructor k = fmap (\(GodotConstructor (n, a)) -> (n, a)) . M.lookup k@@ -159,39 +170,45 @@ collectRest its = M.filterWithKey (\k _ -> k `S.member` S.fromList its) --- | As of Godot 3.3+-- | Godot resource section prefixed with a bracket-enclosed header, optionally+-- with body entries.+--+-- Header entries not specified in a record are accessed with the relevant `headers` field.+-- Likewise, body entries not specified are accessed with the `entries` field.+-- Note that explicitly specified section fields are not duplicated in `headers` and+-- `entries` fields. data GodotSection = ExtResourceSection { _extResourceSectionPath :: T.Text , _extResourceSectionTy :: T.Text , _extResourceSectionId :: Int- -- | Other header information+ -- | Other header information. , _extResourceSectionHeaders :: M.HashMap T.Text GodotValue- -- | Body of the configuration entry+ -- | Body of the configuration entry. , _extResourceSectionEntries :: M.HashMap T.Text GodotValue } | SubResourceSection { _subResourceSectionTy :: T.Text , _subResourceSectionId :: Int- -- | Other header information+ -- | Other header information. , _subResourceSectionHeaders :: M.HashMap T.Text GodotValue- -- | Body of the configuration entry+ -- | Body of the configuration entry. , _subResourceSectionEntries :: M.HashMap T.Text GodotValue } | NodeSection { _nodeSectionTy :: Maybe T.Text , _nodeSectionName :: T.Text- -- | If Nothing, then this node is the root+ -- | If `Nothing`, then this node is the root. , _nodeSectionParent :: Maybe T.Text- -- | Instance is GodotConstructor+ -- | Instance refers to an `ExtResource` ID, usually listed at the top of a file. , _nodeSectionInst :: Maybe Int , _nodeSectionInstPlaceholder :: Maybe T.Text , _nodeSectionOwner :: Maybe T.Text , _nodeSectionIndex :: Maybe Int , _nodeSectionGroups :: Maybe [T.Text]- -- | Other header information+ -- | Other header information. , _nodeSectionHeaders :: M.HashMap T.Text GodotValue- -- | Body of the configuration entry+ -- | Body of the configuration entry. , _nodeSectionEntries :: M.HashMap T.Text GodotValue } | ConnectionSection@@ -199,9 +216,9 @@ , _connectionSectionFrom :: T.Text , _connectionSectionTo :: T.Text , _connectionSectionMethod :: T.Text- -- | Other header information+ -- | Other header information. , _connectionSectionHeaders :: M.HashMap T.Text GodotValue- -- | Body of the configuration entry+ -- | Body of the configuration entry. , _connectionSectionEntries :: M.HashMap T.Text GodotValue } | ResourceSection@@ -216,8 +233,7 @@ } deriving (Show,Generic) -makeFields ''GodotSection-+-- | `tscn` file descriptor. data TscnDescriptor = TscnDescriptor { _tscnDescriptorLoadSteps :: Int@@ -225,8 +241,7 @@ } deriving (Show,Generic) -makeFields ''TscnDescriptor-+-- | Parsed `tscn` file. data TscnParsed = TscnParsed { _tscnParsedDescriptor :: TscnDescriptor@@ -234,8 +249,7 @@ } deriving (Show,Generic) -makeFields ''TscnParsed-+-- | `gdns` file descriptor. data GdnsDescriptor = GdnsDescriptor { _gdnsDescriptorTy :: T.Text@@ -244,8 +258,7 @@ } deriving (Show,Generic) -makeFields ''GdnsDescriptor-+-- | Parsed `gdns` file. data GdnsParsed = GdnsParsed { _gdnsParsedDescriptor :: GdnsDescriptor@@ -253,15 +266,29 @@ } deriving (Show,Generic) -makeFields ''GdnsParsed+-- | An unknown file descriptor.+data OtherDescriptor =+ OtherDescriptor+ { _otherDescriptorHeaderName :: T.Text+ , _otherDescriptorHeaders :: M.HashMap T.Text GodotValue+ }+ deriving (Show,Generic) +-- | An unknown file parsing result.+data OtherParsed =+ OtherParsed+ { _otherParsedDescriptor :: OtherDescriptor+ , _otherParsedSections :: [GodotSection]+ }+ deriving (Show,Generic)++-- | Parsed godot resource file. data GodotParsed = Tscn TscnParsed | Gdns GdnsParsed+ | Other OtherParsed deriving (Show,Generic) -makeFields ''GodotParsed- tscnHeaderKVP :: Parser (T.Text, GodotValue) tscnHeaderKVP = liftA2 (,) (P.takeWhileP Nothing (/= '=')) (P.char '=' *> godotValueP) @@ -337,6 +364,12 @@ (unGodotString' "from" kvs) (unGodotString' "to" kvs) (unGodotString' "method" kvs) (collectRest ["signal", "from", "to", "method"] kvs) bodyKvs) +-- | Parse an unspecified section.+otherP :: Parser GodotSection+otherP = do+ (headerName, headerKvs', bodyKvs) <- bodyAndKvs+ pure $ OtherSection headerName headerKvs' bodyKvs+ -- | Parse a `tscn` file. tscnParser :: Parser TscnParsed tscnParser = do@@ -345,17 +378,17 @@ format = unGodotInt' "format" kvs sectionP = P.choice- (map P.try [tscnConnectionP, tscnExtResourceP, tscnSubResourceP, tscnNodeP])+ (map P.try+ [tscnConnectionP, tscnExtResourceP, tscnSubResourceP, tscnNodeP, otherP]) sections <- P.manyTill sectionP P.eof pure $ TscnParsed (TscnDescriptor loadSteps format) sections -- | Parse a `[resource]` section.-resourceParser :: Parser GodotSection-resourceParser = do- (headerName, headerKvs, bodyKvs) <- bodyAndKvs- pure- $ ResourceSection (unGodotString "resource_name" bodyKvs)- (unGodotString "class_name" bodyKvs) (unGodotConstructor "library" bodyKvs)+resourceP :: Parser GodotSection+resourceP =+ headerWrapper "resource"+ (\_ bodyKvs -> ResourceSection (unGodotString "resource_name" bodyKvs)+ (unGodotString "class_name" bodyKvs) (unGodotConstructor "library" bodyKvs)) -- | Parse a `gdns` file. gdnsParser :: Parser GdnsParsed@@ -364,15 +397,19 @@ let ty = unGodotString' "type" kvs loadSteps = unGodotInt' "load_steps" kvs format = unGodotInt' "format" kvs- sectionP = P.choice (map P.try [tscnExtResourceP, resourceParser])+ sectionP = P.choice (map P.try [tscnExtResourceP, resourceP, otherP]) sections <- P.manyTill sectionP P.eof pure $ GdnsParsed (GdnsDescriptor ty loadSteps format) sections -otherParser :: Parser GodotSection+-- | Parse an unknown resource file.+otherParser :: Parser OtherParsed otherParser = do- (headerName, headerKvs, bodyKvs) <- bodyAndKvs- pure $ OtherSection headerName headerKvs bodyKvs+ hName <- P.char '[' *> P.takeWhile1P Nothing (/= ' ') <* P.char ' '+ hKvs <- headerKvs <* P.char ']' <* P.space+ sections <- P.manyTill otherP P.eof+ pure $ OtherParsed (OtherDescriptor hName hKvs) sections --- | Parse a resource file (currently only `tscn` and `gdns`).+-- | Parse some Godot resource file. godotParser :: Parser GodotParsed-godotParser = P.try (Tscn <$> tscnParser) <|> P.try (Gdns <$> gdnsParser)+godotParser =+ P.choice (map P.try [Tscn <$> tscnParser, Gdns <$> gdnsParser, Other <$> otherParser])
+ src/Godot/Parser/Resource/Lens.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TemplateHaskell #-}++module Godot.Parser.Resource.Lens where++import Control.Lens++import Godot.Parser.Resource++makeLenses ''GodotValue++makeFields ''GodotSection++makeFields ''TscnDescriptor++makeFields ''TscnParsed++makeFields ''GdnsDescriptor++makeFields ''GdnsParsed++makeFields ''OtherDescriptor++makeFields ''OtherParsed++makeFields ''GodotParsed