-- This file is part of Goatee. -- -- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- -- Goatee is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU Affero General Public License for more details. -- -- You should have received a copy of the GNU Affero General Public License -- along with Goatee. If not, see . {-# LANGUAGE CPP, TemplateHaskell #-} {-# OPTIONS_HADDOCK hide #-} -- | Core property-related data types, and some Template Haskell declarations -- for defining property metadata. -- -- Import "Game.Goatee.Lib.Property" rather than importing this module. module Game.Goatee.Lib.Property.Base ( -- * Properties Property (..), -- * Property metadata PropertyType (..), Descriptor (..), ValuedDescriptor (..), AnyDescriptor (..), AnyValuedDescriptor (..), AnyCoordListDescriptor, PropertyInfo, ValuedPropertyInfo (ValuedPropertyInfo), -- * (Internal) Property metadata declaration defProperty, defValuedProperty, ) where import Game.Goatee.Lib.Property.Value (PropertyValueType(..), nonePvt) import Game.Goatee.Lib.Renderer import Game.Goatee.Lib.Types import Language.Haskell.TH ( Info (DataConI), DecsQ, Name, Type (AppT), appE, appT, caseE, conE, conP, conT, lam1E, match, mkName, newName, normalB, recP, reify, sigD, stringE, valD, varE, varP, wildP, ) import Text.ParserCombinators.Parsec (Parser) -- | An SGF property that gives a node meaning. A property is /known/ if its -- meaning is defined by the SGF specification, and /unknown/ otherwise. Known -- properties each have their own data constructors. Unknown properties are -- represented by the 'UnknownProperty' data constructor. data Property = -- Move properties. B (Maybe Coord) -- ^ Black move (nothing iff pass). | KO -- ^ Execute move unconditionally (even if illegal). | MN Integer -- ^ Assign move number. | W (Maybe Coord) -- ^ White move (nothing iff pass). -- Setup properties. | AB CoordList -- ^ Assign black stones. | AE CoordList -- ^ Assign empty stones. | AW CoordList -- ^ Assign white stones. | PL Color -- ^ Player to play. -- Node annotation properties. | C Text -- ^ Comment. | DM DoubleValue -- ^ Even position. | GB DoubleValue -- ^ Good for black. | GW DoubleValue -- ^ Good for white. | HO DoubleValue -- ^ Hotspot. | N SimpleText -- ^ Node name. | UC DoubleValue -- ^ Unclear position. | V RealValue -- ^ Node value. -- Move annotation properties. | BM DoubleValue -- ^ Bad move. | DO -- ^ Doubtful move. | IT -- ^ Interesting move. | TE DoubleValue -- ^ Tesuji. -- Markup properties. | AR ArrowList -- ^ Arrows. | CR CoordList -- ^ Mark points with circles. | DD CoordList -- ^ Dim points. | LB LabelList -- ^ Label points with text. | LN LineList -- ^ Lines. | MA CoordList -- ^ Mark points with 'X's. | SL CoordList -- ^ Mark points as selected. | SQ CoordList -- ^ Mark points with squares. | TR CoordList -- ^ Mark points with trianges. -- Root properties. | AP SimpleText SimpleText -- ^ Application info. | CA SimpleText -- ^ Charset for SimpleText and Text. | FF Int -- ^ File format version. | GM Int -- ^ Game (must be 1 = Go). | ST VariationMode -- ^ Variation display format. | SZ Int Int -- ^ Board size, columns then rows. -- Game info properties. | AN SimpleText -- ^ Name of annotator. | BR SimpleText -- ^ Rank of black player. | BT SimpleText -- ^ Name of black team. | CP SimpleText -- ^ Copyright info. | DT SimpleText -- ^ Dates played. | EV SimpleText -- ^ Event name. | GC Text -- ^ Game comment, or background, or summary. | GN SimpleText -- ^ Game name. | ON SimpleText -- ^ Information about the opening. | OT SimpleText -- ^ The method used for overtime. | PB SimpleText -- ^ Name of black player. | PC SimpleText -- ^ Where the game was played. | PW SimpleText -- ^ Name of white player. | RE GameResult -- ^ Result of the game. | RO SimpleText -- ^ Round info. | RU Ruleset -- ^ Ruleset used. | SO SimpleText -- ^ Source of the game. | TM RealValue -- ^ Time limit, in seconds. | US SimpleText -- ^ Name of user or program who entered the game. | WR SimpleText -- ^ Rank of white player. | WT SimpleText -- ^ Name of white team. -- Timing properties. | BL RealValue -- ^ Black time left. | OB Int -- ^ Black moves left in byo-yomi period. | OW Int -- ^ White moves left in byo-yomi period. | WL RealValue -- ^ White time left. -- Miscellaneous properties. -- TODO FG property. -- TODO PM property. | VW CoordList -- ^ Set viewing region. -- Go-specific properties. | HA Int -- ^ Handicap stones (>=2). | KM RealValue -- ^ Komi. | TB CoordList -- ^ Black territory. | TW CoordList -- ^ White territory. | UnknownProperty String UnknownPropertyValue -- TODO Game info, timing, and miscellaneous properties. -- Also in functions below. deriving (Eq, Show) -- | The property types that SGF uses to group properties. data PropertyType = MoveProperty -- ^ Cannot mix with setup nodes. | SetupProperty -- ^ Cannot mix with move nodes. | RootProperty -- ^ May only appear in root nodes. | GameInfoProperty -- ^ At most one on any path. | GeneralProperty -- ^ May appear anywhere in the game tree. deriving (Eq, Show) -- | A class for types that contain metadata about a 'Property'. The main -- instance of this class is 'Property' itself; 'Property's can be treated as -- though they have metadata directly. When referring to a property in general -- rather than a specific instance, use the values of 'PropertyInfo' and -- 'ValuedPropertyInfo'. -- -- See also 'ValuedDescriptor'. class Descriptor a where -- | Returns the name of the property, as used in SGF files. propertyName :: a -> String -- | Returns the type of the property, as specified by the SGF spec. propertyType :: a -> PropertyType -- | Returns whether the value of the given property is inherited from the -- lowest ancestor specifying the property, when the property is not set on a -- node itself. propertyInherited :: a -> Bool -- | Returns whether the given property has the type of a descriptor. propertyPredicate :: a -> Property -> Bool -- | A parser of property values in SGF format (e.g. @"[ab]"@ for a property -- that takes a point). propertyValueParser :: a -> Parser Property -- | A renderer property values to SGF format (e.g. @B (Just (1,2))@ renders -- to @"[ab]"@). propertyValueRenderer :: a -> Property -> Render () -- | A renderer for displaying property values in a UI. Displays the value in -- a human-readable format. propertyValueRendererPretty :: a -> Property -> Render () -- | A class for 'Descriptor's of properties that also contain values. class (Descriptor a, Eq v) => ValuedDescriptor v a | a -> v where -- | Extracts the value from a property of the given type. Behaviour is -- undefined if the property is not of the given type. propertyValue :: a -> Property -> v -- | Builds a property from a given value. propertyBuilder :: a -> v -> Property -- | An existential type for any property descriptor. 'AnyDescriptor' has a -- 'Descriptor' instance, so there is no need to extract the value with a -- pattern match before using 'Descriptor' methods. data AnyDescriptor = forall a. Descriptor a => AnyDescriptor a instance Descriptor AnyDescriptor where propertyName (AnyDescriptor d) = propertyName d propertyType (AnyDescriptor d) = propertyType d propertyInherited (AnyDescriptor d) = propertyInherited d propertyPredicate (AnyDescriptor d) = propertyPredicate d propertyValueParser (AnyDescriptor d) = propertyValueParser d propertyValueRenderer (AnyDescriptor d) = propertyValueRenderer d propertyValueRendererPretty (AnyDescriptor d) = propertyValueRendererPretty d -- | An existential type for any descriptor of a property that holds a value of -- a specific type. Has instances for 'Descriptor' and 'ValuedDescriptor', -- similar to 'AnyDescriptor'. data AnyValuedDescriptor v = forall a. ValuedDescriptor v a => AnyValuedDescriptor a instance Descriptor (AnyValuedDescriptor v) where propertyName (AnyValuedDescriptor d) = propertyName d propertyType (AnyValuedDescriptor d) = propertyType d propertyInherited (AnyValuedDescriptor d) = propertyInherited d propertyPredicate (AnyValuedDescriptor d) = propertyPredicate d propertyValueParser (AnyValuedDescriptor d) = propertyValueParser d propertyValueRenderer (AnyValuedDescriptor d) = propertyValueRenderer d propertyValueRendererPretty (AnyValuedDescriptor d) = propertyValueRendererPretty d instance Eq v => ValuedDescriptor v (AnyValuedDescriptor v) where propertyValue (AnyValuedDescriptor d) = propertyValue d propertyBuilder (AnyValuedDescriptor d) = propertyBuilder d type AnyCoordListDescriptor = AnyValuedDescriptor CoordList -- | Metadata for a property that does not contain a value. Corresponds to a -- nullary data constructor of 'Property'. data PropertyInfo = PropertyInfo { propertyInfoName :: String -- ^ The SGF textual name for the property. , propertyInfoInstance :: Property -- ^ The single instance of the property. , propertyInfoType :: PropertyType -- ^ The SGF property type. , propertyInfoInherited :: Bool -- ^ Whether the property is inherited. } instance Descriptor PropertyInfo where propertyName = propertyInfoName propertyType = propertyInfoType propertyInherited = propertyInfoInherited propertyPredicate = (==) . propertyInfoInstance propertyValueParser descriptor = propertyInfoInstance descriptor <$ pvtParser nonePvt propertyValueRenderer _ _ = pvtRenderer nonePvt () propertyValueRendererPretty _ _ = pvtRendererPretty nonePvt () -- | Metadata for a property that contains a value. Corresponds to a -- non-nullary data constructor of 'Property'. data ValuedPropertyInfo v = ValuedPropertyInfo { valuedPropertyInfoName :: String -- ^ The SGF textual name for the property (also the name of the data -- constructor). , valuedPropertyInfoType :: PropertyType -- ^ The SGF property type. , valuedPropertyInfoInherited :: Bool -- ^ Whether the property is inherited. , valuedPropertyInfoPredicate :: Property -> Bool -- ^ A predicate that matches predicates to which this 'ValuedPropertyInfo' -- applies. , valuedPropertyInfoValueType :: PropertyValueType v -- ^ Metadata about the type of the property's value. , valuedPropertyInfoValue :: Property -> v -- ^ A function that extracts values from properties to which this -- 'ValuedPropertyInfo' applies. It is invalid to call this function with a -- different type of property. , valuedPropertyInfoBuilder :: v -> Property -- ^ A function that builds a property containing a value. } instance Descriptor (ValuedPropertyInfo v) where propertyName = valuedPropertyInfoName propertyType = valuedPropertyInfoType propertyInherited = valuedPropertyInfoInherited propertyPredicate = valuedPropertyInfoPredicate propertyValueParser descriptor = fmap (valuedPropertyInfoBuilder descriptor) $ pvtParser $ valuedPropertyInfoValueType descriptor propertyValueRenderer descriptor property = pvtRenderer (valuedPropertyInfoValueType descriptor) $ valuedPropertyInfoValue descriptor property propertyValueRendererPretty descriptor property = pvtRendererPretty (valuedPropertyInfoValueType descriptor) $ valuedPropertyInfoValue descriptor property instance Eq v => ValuedDescriptor v (ValuedPropertyInfo v) where propertyValue = valuedPropertyInfoValue propertyBuilder = valuedPropertyInfoBuilder -- | Internal to this module, do not use outside. Template Haskell function to -- declare a property that does not contain a value. -- -- > $(defProperty "KO" 'MoveProperty False) -- -- This example declares a @propertyKO :: 'PropertyInfo'@ that is a -- 'MoveProperty' and is not inherited. defProperty :: String -- ^ The SGF textual name of the property. -> Name -- ^ The name of the 'PropertyType'. -> Bool -- ^ Whether the property is inherited. -> DecsQ defProperty name propType inherited = do let propName = mkName name varName = mkName $ "property" ++ name sequence [ sigD varName $ conT $ mkName "PropertyInfo", valD (varP varName) (normalB [| PropertyInfo name $(conE propName) $(conE propType) inherited |]) [] ] -- | Internal to this module, do not use outside. Template Haskell function to -- declare a property that contains a value. -- -- > $(defValuedProperty "B" 'MoveProperty False 'maybeCoordPrinter) -- -- This example declares a @propertyB :: 'ValuedPropertyInfo' (Maybe 'Coord')@ -- that is a 'MoveProperty' and is not inherited. The value type is -- automatically inferred. defValuedProperty :: String -> Name -> Bool -> Name -> DecsQ defValuedProperty name propType inherited valueType = do let propName = mkName name varName = mkName $ "property" ++ name foo <- newName "foo" bar <- newName "bar" #if MIN_VERSION_template_haskell(2,11,0) DataConI _ (AppT (AppT _ haskellValueType) _) _ <- reify propName #else DataConI _ (AppT (AppT _ haskellValueType) _) _ _ <- reify propName #endif sequence [ sigD varName $ appT (conT ''ValuedPropertyInfo) $ return haskellValueType, valD (varP varName) (normalB [| ValuedPropertyInfo name $(conE propType) inherited $(lam1E (varP foo) $ caseE (varE foo) [match (recP propName []) (normalB $ conE $ mkName "True") [], match wildP (normalB $ conE $ mkName "False") []]) $(varE valueType) $(lam1E (varP foo) $ caseE (varE foo) [match (conP propName [varP bar]) (normalB $ varE bar) [], match wildP (normalB [| error $ "Property value getter for " ++ $(stringE name) ++ " applied to " ++ show $(varE foo) ++ "." |]) []]) $(lam1E (varP foo) $ appE (conE propName) (varE foo)) |]) [] ]