packages feed

libasterix-0.14.0: src/Asterix/Schema.hs

-- |
-- Module: Asterix.Schema
--
-- This module defines generic asterix structures which can be used as types
-- and as values. This imposes some complexity, mainly because of the fact that
-- in haskell, all type information is erased during program compilation. In
-- other words: normally types are only important when writting and compiling
-- the code, not when running it. Whatever is required at runtime must be
-- explicitely "created" during compile phase.
--
-- A standard approach to convert type level information to the value level
-- equivalent is to use typeclasses and instances. Regular function don't work
-- in this case.
--
-- An approach taken in this library is to have:
--  - generic structures from this module, for type or value level usecases;
--  - generated file where complete asterix specs are defined as types;
--  - conversion rules (as typeclasses and instances) from this module to have
--    runtime equivalent of complete asterix specs at value level too;
--
-- Naming convention:
--
-- * GSomething ... generic definition of 'Something'
--    It is indexed by 'Usecase' type parameter.
--
-- * TSomething ... type level definition of 'Something', defined as:
--    type TSomething = GSomething 'TypeLevel
--
-- * VSomething ... value level definition of 'Something', defined as:
--    type VSomething = GSomething 'ValueLevel
--
-- The type level definitions (TSomething) are generated in a separate module.
-- The 'schema' function from Schema typeclass converts from
-- TSomething -> VSomething, such that any specification part can also be
-- used at runtime.

{-# LANGUAGE DataKinds              #-}
{-# LANGUAGE PolyKinds              #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE UndecidableInstances   #-}

module Asterix.Schema
( module Asterix.Schema
, module Data.Proxy
) where

import           Data.Proxy
import           Data.Text
import           Data.Type.Bool
import           Data.Type.Equality
import           Data.Type.Ord
import           GHC.TypeLits

-- | Type or value level usecase.
data Usecase
    = TypeLevel
    | ValueLevel

-- | Generic Int is 'Nat' or 'Integer'.
type family GInt (u :: Usecase) = r | r -> u where
    GInt 'TypeLevel = Nat
    GInt 'ValueLevel = Int

type TInt = GInt 'TypeLevel
type VInt = GInt 'ValueLevel

-- | Integer in the interval [0..7].
newtype Int8 = Int8 { unInt8 :: Int }
    deriving (Show, Eq)

class KnownNat8 (n :: Nat) where
    natVal8 :: Proxy n -> Int8

instance (KnownNat n, n <= 7) => KnownNat8 n where
    natVal8 = Int8 . fromIntegral . natVal

-- | Generic Text is 'Symbol' or 'Text'.
type family GText (u :: Usecase) = r | r -> u where
    GText 'TypeLevel = Symbol
    GText 'ValueLevel = Text

type TText = GText 'TypeLevel
type VText = GText 'ValueLevel

-- Generic asterix schema

-- | Rule.
data GRule (u :: Usecase) t
    = GContextFree t
    | GDependent [[GText u]] t [([GInt u], t)]
deriving instance Show t => Show (GRule 'ValueLevel t)

type TRule = GRule 'TypeLevel
type VRule = GRule 'ValueLevel

-- | String type.
data GStringType
    = GStringAscii
    | GStringICAO
    | GStringOctal
    deriving Show

type TStringType = GStringType
type VStringType = GStringType

-- | Signed or unsigned.
data GSignedness
    = GSigned
    | GUnsigned
    deriving Show

type TSignedness = GSignedness
type VSignedness = GSignedness

-- | Plus or minus.
data GPlusMinus
    = GPlus
    | GMinus
    deriving Show

type TPlusMinus = GPlusMinus
type VPlusMinus = GPlusMinus

-- | Positive or negative integer numbers.
data GZ (u :: Usecase) = GZ GPlusMinus (GInt u)
deriving instance Show (GZ 'ValueLevel)

type TZ = GZ 'TypeLevel
type VZ = GZ 'ValueLevel

-- | A number without rounding errors.
data GNumber (u :: Usecase)
    = GNumInt (GZ u)
    | GNumDiv (GNumber u) (GNumber u)
    | GNumPow (GZ u) (GZ u)
deriving instance Show (GNumber 'ValueLevel)

type TNumber = GNumber 'TypeLevel
type VNumber = GNumber 'ValueLevel

-- | BDS type.
data GBdsType (u :: Usecase)
    = GBdsWithAddress
    | GBdsAt (Maybe (GInt u))
deriving instance Show (GBdsType 'ValueLevel)

type TBdsType = GBdsType 'TypeLevel
type VBdsType = GBdsType 'ValueLevel

-- | Type of content.
data GContent (u :: Usecase)
    = GContentRaw
    | GContentTable [(GInt u, GText u)]
    | GContentString GStringType
    | GContentInteger GSignedness
    | GContentQuantity GSignedness (GNumber u) (GText u)
    | GContentBds (GBdsType u)
deriving instance Show (GContent 'ValueLevel)

type TContent = GContent 'TypeLevel
type VContent = GContent 'ValueLevel

-- | Repetitive type.
data GRepetitiveType (u :: Usecase)
    = GRepetitiveRegular (GInt u)
    | GRepetitiveFx
deriving instance Show (GRepetitiveType 'ValueLevel)

type TRepetitiveType = GRepetitiveType 'TypeLevel
type VRepetitiveType = GRepetitiveType 'ValueLevel

-- | Explicit type.
data GExplicitType
    = GReservedExpansion
    | GSpecialPurpose
    deriving Show

type TExplicitType = GExplicitType
type VExplicitType = GExplicitType

-- | Variation.
data GVariation (u :: Usecase)
    = GElement (GInt u) (GInt u) (GRule u (GContent u)) -- offset size
    | GGroup (GInt u) [GItem u] -- offset items
    | GExtended [Maybe (GItem u)]
    | GRepetitive (GRepetitiveType u) (GVariation u)
    | GExplicit (Maybe GExplicitType)
    | GCompound [Maybe (GNonSpare u)]
deriving instance Show (GVariation 'ValueLevel)

type TVariation = GVariation 'TypeLevel
type VVariation = GVariation 'ValueLevel

-- | NonSpare.
data GNonSpare (u :: Usecase)
    = GNonSpare (GText u) (GText u) (GRule u (GVariation u)) -- name, title
deriving instance Show (GNonSpare 'ValueLevel)

type TNonSpare = GNonSpare 'TypeLevel
type VNonSpare = GNonSpare 'ValueLevel

-- | Item.
data GItem (u :: Usecase)
    = GSpare (GInt u) (GInt u) -- offset size
    | GItem (GNonSpare u)
deriving instance Show (GItem 'ValueLevel)

type TItem = GItem 'TypeLevel
type VItem = GItem 'ValueLevel

-- | UAP Item.
data GUapItem (u :: Usecase)
    = GUapItem (GNonSpare u)
    | GUapItemSpare
    | GUapItemRFS
deriving instance Show (GUapItem 'ValueLevel)

type TUapItem = GUapItem 'TypeLevel
type VUapItem = GUapItem 'ValueLevel

-- | Record.
newtype GRecord (u :: Usecase) = GRecord [GUapItem u]
deriving instance Show (GRecord 'ValueLevel)

type TRecord = GRecord 'TypeLevel
type VRecord = GRecord 'ValueLevel

-- | UAP selector.
data GUapSelector (u :: Usecase) = GUapSelector [GText u] [(GInt u, GText u)]
deriving instance Show (GUapSelector 'ValueLevel)

type TUapSelector = GUapSelector 'TypeLevel
type VUapSelector = GUapSelector 'ValueLevel

-- | User application profile.
data GUap (u :: Usecase)
    = GUap (GRecord u)
    | GUaps [(GText u, GRecord u)] (Maybe (GUapSelector u))
deriving instance Show (GUap 'ValueLevel)

type TUap = GUap 'TypeLevel
type VUap = GUap 'ValueLevel

-- | Datablock.
data GDatablock (u ::  Usecase) = GDatablock (GInt u) (GUap u) -- cat uap
deriving instance Show (GDatablock 'ValueLevel)

type TDatablock = GDatablock 'TypeLevel
type VDatablock = GDatablock 'ValueLevel

-- | Expansion.
data GExpansion (u :: Usecase) = GExpansion (Maybe (GInt u)) [Maybe (GNonSpare u)]
deriving instance Show (GExpansion 'ValueLevel)

type TExpansion = GExpansion 'TypeLevel
type VExpansion = GExpansion 'ValueLevel

-- | Edition.
data GEdition (u :: Usecase) = GEdition (GInt u) (GInt u) -- major, minor
deriving instance Show (GEdition 'ValueLevel)

type TEdition = GEdition 'TypeLevel
type VEdition = GEdition 'ValueLevel

deriving instance Eq VEdition
instance Ord VEdition where
    compare (GEdition a1 b1) (GEdition a2 b2)
        = compare a1 a2
       <> compare b1 b2

-- | Toplevel asterix structure.
data GAsterix (u :: Usecase)
    = GAsterixBasic (GInt u) (GEdition u) (GUap u) -- cat
    | GAsterixExpansion (GInt u) (GEdition u) (GExpansion u) -- cat
deriving instance Show (GAsterix 'ValueLevel)

type TAsterix = GAsterix 'TypeLevel
type VAsterix = GAsterix 'ValueLevel

-- | Convert structures from type level 't' to value level 'a',
-- with 'schema' function.
class Schema t a where
    schema :: Proxy t -> a

instance KnownNat n => Schema n Int where
    schema _ = fromIntegral $ natVal @n Proxy

instance KnownNat8 n => Schema n Int8 where
    schema _ = natVal8 @n Proxy

instance KnownSymbol s => Schema s Text where
    schema _ = Data.Text.pack $ symbolVal @s Proxy

instance Schema 'Nothing (Maybe a) where
    schema _ = Nothing

instance
    ( Schema t a
    ) => Schema ('Just t) (Maybe a) where
    schema _ = Just (schema @t Proxy)

instance Schema '[] [a] where
    schema _ = []

instance
    ( Schema t a
    , Schema ts [a]
    ) => Schema (t ': ts) [a] where
    schema _ = schema @t Proxy : schema @ts Proxy

instance
    ( Schema t1 a1
    , Schema t2 a2
    ) => Schema '(t1, t2) (a1, a2) where
    schema _ = (schema @t1 Proxy, schema @t2 Proxy)

instance
    ( Schema t (f 'ValueLevel)
    ) => Schema
        ('GContextFree (t :: f 'TypeLevel))
        (GRule 'ValueLevel (f 'ValueLevel)) where
    schema _ = GContextFree (schema @t Proxy)

instance
    ( Schema lst1 [[Text]]
    , Schema t (f 'ValueLevel)
    , Schema lst2 [([Int], f ValueLevel)]
    ) => Schema
        ('GDependent lst1 (t :: f 'TypeLevel) lst2)
        (GRule 'ValueLevel (f 'ValueLevel)) where
    schema _= GDependent
        (schema @lst1 Proxy)
        (schema @t Proxy)
        (schema @lst2 Proxy)

instance Schema 'GStringAscii GStringType where
    schema _ = GStringAscii

instance Schema 'GStringICAO GStringType where
    schema _ = GStringICAO

instance Schema 'GStringOctal GStringType where
    schema _ = GStringOctal

instance Schema 'GSigned GSignedness where
    schema _ = GSigned

instance Schema 'GUnsigned GSignedness where
    schema _ = GUnsigned

instance Schema 'GPlus GPlusMinus where
    schema _ = GPlus

instance Schema 'GMinus GPlusMinus where
    schema _ = GMinus

instance
    ( Schema pm GPlusMinus
    , Schema n Int
    ) => Schema ('GZ pm n) VZ where
    schema _ = GZ (schema @pm Proxy) (schema @n Proxy)

instance
    ( Schema z VZ
    ) => Schema ('GNumInt z) VNumber where
    schema _ = GNumInt (schema @z Proxy)

instance
    ( Schema n1 (GNumber ValueLevel)
    , Schema n2 (GNumber ValueLevel)
    ) => Schema ('GNumDiv n1 n2) VNumber where
    schema _ = GNumDiv (schema @n1 Proxy) (schema @n2 Proxy)

instance
    ( Schema a (GZ ValueLevel)
    , Schema b (GZ ValueLevel)
    ) => Schema ('GNumPow a b) VNumber where
    schema _ = GNumPow (schema @a Proxy) (schema @b Proxy)

instance Schema 'GBdsWithAddress VBdsType where
    schema _ = GBdsWithAddress

instance
    ( Schema ma (Maybe Int)
    ) => Schema ('GBdsAt ma) VBdsType where
    schema _ = GBdsAt (schema @ma Proxy)

instance Schema 'GContentRaw VContent where
    schema _ = GContentRaw

instance
    ( Schema lst [(Int, Text)]
    ) => Schema ('GContentTable lst) VContent where
    schema _ = GContentTable (schema @lst @[(Int, Text)] Proxy)

instance
    ( Schema st GStringType
    ) => Schema ('GContentString st) VContent where
    schema _ = GContentString (schema @st Proxy)

instance
    ( Schema sig GSignedness
    ) => Schema ('GContentInteger sig) VContent where
    schema _ = GContentInteger (schema @sig Proxy)

instance
    ( Schema sig GSignedness
    , Schema lsb (GNumber ValueLevel)
    , Schema unit Text
    ) => Schema ('GContentQuantity sig lsb unit) VContent where
    schema _ = GContentQuantity
        (schema @sig Proxy)
        (schema @lsb Proxy)
        (schema @unit Proxy)

instance
    ( Schema bt (GBdsType ValueLevel)
    ) => Schema ('GContentBds bt) VContent where
    schema _ = GContentBds (schema @bt Proxy)

instance
    ( Schema n Int
    ) => Schema ('GRepetitiveRegular n) VRepetitiveType where
    schema _ = GRepetitiveRegular (schema @n Proxy)

instance Schema 'GRepetitiveFx VRepetitiveType where
    schema _ = GRepetitiveFx

instance Schema 'GReservedExpansion GExplicitType where
    schema _ = GReservedExpansion

instance Schema 'GSpecialPurpose GExplicitType where
    schema _ = GSpecialPurpose

instance
    ( Schema o Int8
    , Schema n Int
    , Schema rule (VRule VContent)
    ) => Schema ('GElement o n rule) VVariation where
    schema _ = GElement
        (unInt8 $ schema @o Proxy)
        (schema @n Proxy)
        (schema @rule Proxy)

instance
    ( Schema o Int8
    , Schema ts [VItem]
    ) => Schema ('GGroup o ts) VVariation where
    schema _ = GGroup (unInt8 $ schema @o Proxy) (schema @ts Proxy)

instance
    ( Schema lst [Maybe VItem]
    ) => Schema ('GExtended lst) VVariation where
    schema _ = GExtended (schema @lst Proxy)

instance
    ( Schema rt VRepetitiveType
    , Schema var VVariation
    ) => Schema ('GRepetitive rt var) VVariation where
    schema _ = GRepetitive (schema @rt Proxy) (schema @var Proxy)

instance
    ( Schema met (Maybe GExplicitType)
    ) => Schema ('GExplicit met) VVariation where
    schema _ = GExplicit (schema @met Proxy)

instance
    ( Schema lst [Maybe VNonSpare]
    ) => Schema ('GCompound lst) VVariation where
    schema _ = GCompound (schema @lst Proxy)

instance
    ( Schema name Text
    , Schema title Text
    , Schema rule (VRule VVariation)
    ) => Schema ('GNonSpare name title rule) VNonSpare where
    schema _ = GNonSpare
        (schema @name Proxy)
        (schema @title Proxy)
        (schema @rule Proxy)

instance
    ( Schema o Int8
    , Schema n Int
    ) => Schema ('GSpare o n) VItem where
    schema _ = GSpare (unInt8 $ schema @o Proxy) (schema @n Proxy)

instance
    ( Schema nsp VNonSpare
    ) => Schema ('GItem nsp) VItem where
    schema _ = GItem (schema @nsp Proxy)

instance
    ( Schema nsp VNonSpare
    ) => Schema ('GUapItem nsp) VUapItem where
    schema _ = GUapItem (schema @nsp Proxy)

instance Schema 'GUapItemSpare VUapItem where
    schema _ = GUapItemSpare

instance Schema 'GUapItemRFS VUapItem where
    schema _ = GUapItemRFS

instance
    ( Schema lst [VUapItem]
    ) => Schema ('GRecord lst) VRecord where
    schema _ = GRecord (schema @lst Proxy)

instance
    ( Schema iname [Text]
    , Schema lst [(Int, Text)]
    ) => Schema ('GUapSelector iname lst) VUapSelector where
    schema _ = GUapSelector (schema @iname Proxy) (schema @lst Proxy)

instance
    ( Schema rec VRecord
    ) => Schema ('GUap rec) VUap where
    schema _ = GUap (schema @rec Proxy)

instance
    ( Schema lst [(Text, VRecord)]
    , Schema msel (Maybe VUapSelector)
    ) => Schema ('GUaps lst msel) VUap where
    schema _ = GUaps (schema @lst Proxy) (schema @msel Proxy)

instance
    ( Schema cat Int
    , Schema uap VUap
    ) => Schema ('GDatablock cat uap) VDatablock where
    schema _ = GDatablock (schema @cat Proxy) (schema @uap Proxy)

instance
    ( Schema mn (Maybe Int)
    , Schema lst [Maybe VNonSpare]
    ) => Schema ('GExpansion mn lst) VExpansion where
    schema _ = GExpansion (schema @mn Proxy) (schema @lst Proxy)

instance
    ( Schema a Int
    , Schema b Int
    ) => Schema ('GEdition a b) VEdition where
    schema _ = GEdition (schema @a Proxy) (schema @b Proxy)

instance
    ( Schema cat Int
    , Schema ed VEdition
    , Schema uap VUap
    ) => Schema ('GAsterixBasic cat ed uap) VAsterix where
    schema _ = GAsterixBasic
        (schema @cat Proxy)
        (schema @ed Proxy)
        (schema @uap Proxy)

instance
    ( name ~ VText
    , uap ~ 'GUaps lst msel
    , Schema lst [(VText, VRecord)]
    ) => Schema ('GAsterixBasic cat ed uap) [(name, VRecord)] where
    schema _ = schema @lst Proxy

instance
    ( Schema cat Int
    , Schema ed VEdition
    , Schema exp VExpansion
    ) => Schema ('GAsterixExpansion cat ed exp) VAsterix where
    schema _ = GAsterixExpansion
        (schema @cat Proxy)
        (schema @ed Proxy)
        (schema @exp Proxy)

-- | TypeError wrapper.
type Unspecified (t :: k)
    = TypeError ('Text "Unspecified " :<>: ShowType t)

-- | Name is not defined.
type NotDefined (name :: Symbol)
    = TypeError ('Text "Not defined: " :<>: 'Text name)

-- | Type level lookup.
type family Lookup (name :: Symbol) (lst :: [(Symbol, k)]) :: k where
    Lookup name '[] = NotDefined name
    Lookup name1 ( '(name2, uap) ': ts) = If
        (name1 == name2)
        uap
        (Lookup name1 ts)

-- | Statically known bit sizes.
type BitSizeOf :: k -> Nat
type family BitSizeOf t where
    BitSizeOf '[] = 0
    BitSizeOf (t ': ts) = BitSizeOf t + BitSizeOf ts
    BitSizeOf ('GElement o n rc) = n
    BitSizeOf ('GGroup o lst) = BitSizeOf lst
    BitSizeOf ('GContextFree t) = BitSizeOf t
    BitSizeOf ('GDependent lst1 t lst2) = BitSizeOf t
    BitSizeOf ('GSpare o n) = n
    BitSizeOf ('GNonSpare name title rv) = BitSizeOf rv
    BitSizeOf ('GItem nsp) = BitSizeOf nsp
    BitSizeOf other = Unspecified other

-- | Extract category.
type CategoryOf :: k -> Nat
type family CategoryOf t where
    CategoryOf ('GAsterixBasic cat ed uap) = cat
    CategoryOf ('GDatablock cat uap) = cat
    CategoryOf other = Unspecified other

-- | Extract record type from single uap basic asterix.
type RecordOf :: TAsterix -> TRecord
type family RecordOf t where
    RecordOf ('GAsterixBasic cat ed ('GUap rec)) = rec
    RecordOf other = Unspecified other

-- | Extract record type from multiple uap basic asterix.
type RecordOfUap :: TAsterix -> Symbol -> TRecord
type family RecordOfUap t name where
    RecordOfUap ('GAsterixBasic cat ed ('GUaps lst sel)) name
        = Lookup name lst
    RecordOfUap other name = Unspecified other

-- | Get Datablock type.
type family DatablockOf (t :: TAsterix) :: TDatablock where
    DatablockOf ('GAsterixBasic cat ed uap) = 'GDatablock cat uap
    DatablockOf other = Unspecified other

-- | Get Expansion type.
type family ExpansionOf (t :: GAsterix u) :: GExpansion u where
    ExpansionOf ('GAsterixExpansion cat ed exp) = exp
    ExpansionOf other = Unspecified other

-- | Extract first group of extension.
type family ExtendedFirstGroup (lst :: [Maybe a]) :: [a] where
    ExtendedFirstGroup '[] = '[]
    ExtendedFirstGroup ('Nothing ': ts) = '[]
    ExtendedFirstGroup ('Just t ': ts) = t ': ExtendedFirstGroup ts

-- | Does extended end with Fx?
type family ExtendedTrailingFx (lst :: [Maybe a]) :: Bool where
    ExtendedTrailingFx '[] = 'False
    ExtendedTrailingFx ('Nothing ': ts) = 'True
    ExtendedTrailingFx ('Just t ': ts) = ExtendedTrailingFx ts

-- | Remaining extended items.
type family ExtendedRemainingItems (lst :: [Maybe a]) :: [Maybe a] where
    ExtendedRemainingItems '[] = '[]
    ExtendedRemainingItems ('Nothing ': ts) = ts
    ExtendedRemainingItems ('Just t ': ts) = ExtendedRemainingItems ts

-- | Extract record type from datablock type.
type family TypeOfRecord (t :: TDatablock) :: TRecord where
    TypeOfRecord ('GDatablock cat ('GUap rec)) = rec
    TypeOfRecord db = TypeError ('Text "Not defined")

-- | Does this datablock contain multiple UAPs?
type family IsMultiUap (t :: GDatablock u) :: Bool where
    IsMultiUap ('GDatablock cat ('GUap rec)) = 'False
    IsMultiUap ('GDatablock cat ('GUaps lst sel)) = 'True

-- | Find UAP name, based on given record.
type family UapName r lst :: Symbol where
    UapName r '[] = TypeError ('Text "Wrong record" :<>: ShowType r)
    UapName r1 ( '(name, r2) ': ts) = If
        (r1 == r2)
        name
        (UapName r1 ts)

-- | Find group subitem by name.
type family LookupGroup (name :: Symbol) (lst :: [TItem]) :: Nat where
    LookupGroup name '[] = NotDefined name
    LookupGroup name ('GSpare o n ': ts) = 1 + LookupGroup name ts
    LookupGroup name ('GItem ('GNonSpare name1 title rv) ': ts) = If
        (name1 == name) 0 (1 + LookupGroup name ts)

-- | Find extended subitem by name.
type family LookupExtended (name :: Symbol) (lst :: [Maybe TItem]) :: Nat where
    LookupExtended name '[] = NotDefined name
    LookupExtended name ('Nothing ': ts) = 1 + LookupExtended name ts
    LookupExtended name ('Just ('GSpare o n) ': ts) = 1 + LookupExtended name ts
    LookupExtended name ('Just ('GItem ('GNonSpare name1 title rv)) ': ts) = If
        (name1 == name) 0 (1 + LookupExtended name ts)

-- | Find compound subitem by name.
type family LookupCompound (name :: Symbol) (lst :: [Maybe TNonSpare]) :: Nat where
    LookupCompound name '[] = NotDefined name
    LookupCompound name ('Nothing ': ts) = 1 + LookupCompound name ts
    LookupCompound name ('Just ('GNonSpare name1 title rv) ': ts) = If
        (name1 == name) 0 (1 + LookupCompound name ts)

-- | Find record subitem by name.
type family LookupRecord (name :: Symbol) (lst :: [TUapItem]) :: Nat where
    LookupRecord name '[] = NotDefined name
    LookupRecord name ('GUapItem ('GNonSpare name1 title rv) ': ts) = If
        (name1 == name) 0 (1 + LookupRecord name ts)
    LookupRecord name ('GUapItemSpare ': ts) = 1 + LookupRecord name ts
    LookupRecord name ('GUapItemRFS ': ts) = 1 + LookupRecord name ts

-- | Extract NonSpares from uap item listing.
type family RecordNonSpares (t :: [TUapItem]) :: [TNonSpare]
  where
    RecordNonSpares '[] = '[]
    RecordNonSpares ('GUapItem nsp ': ts) = nsp ': RecordNonSpares ts
    RecordNonSpares ('GUapItemSpare ': ts) = RecordNonSpares ts
    RecordNonSpares ('GUapItemRFS ': ts) = RecordNonSpares ts

-- | Extract NonSpares from item listing.
type family ItemNonSpares (t :: [TItem]) :: [TNonSpare]
  where
    ItemNonSpares '[] = '[]
    ItemNonSpares ('GSpare o2 n2 ': ts) = ItemNonSpares ts
    ItemNonSpares ('GItem nsp ': ts) = nsp ': ItemNonSpares ts

-- | Type level list filtering.
type family FilterMaybe (lst :: [Maybe a]) :: [a] where
    FilterMaybe '[] = '[]
    FilterMaybe ('Nothing ': ts) = FilterMaybe ts
    FilterMaybe ('Just t ': ts) = t ': FilterMaybe ts

-- | Prepend name.
type family PrependName t where
    PrependName '[] = '[]
    PrependName ('GNonSpare name title rv ': ts)
        = '(name, 'GNonSpare name title rv) ': PrependName ts

-- | Overloaded type level function to extract subtype from parent, based on name.
type (~>) :: k -> Symbol -> TNonSpare
type family (~>) t name where
    (~>) ('GGroup o lst) name
        = Lookup name (PrependName (ItemNonSpares lst))
    (~>) ('GExtended lst) name
        = Lookup name (PrependName (ItemNonSpares (FilterMaybe lst)))
    (~>) ('GRepetitive rt var) name
        = var ~> name
    (~>) ('GCompound lst) name
        = Lookup name (PrependName (FilterMaybe lst))
    (~>) ('GNonSpare pname title ('GContextFree ('GGroup o lst))) name
        = 'GGroup o lst ~> name
    (~>) ('GNonSpare pname title ('GContextFree ('GExtended lst))) name
        = 'GExtended lst ~> name
    (~>) ('GNonSpare pname title ('GContextFree ('GRepetitive rt var))) name
        = 'GRepetitive rt var ~> name
    (~>) ('GNonSpare pname title ('GContextFree ('GCompound lst))) name
        = 'GCompound lst ~> name
    (~>) ('GRecord lst) name
        = Lookup name (PrependName (RecordNonSpares lst))
    (~>) ('GExpansion mn lst) name
        = Lookup name (PrependName (FilterMaybe lst))
    (~>) ('GAsterixBasic cat ed ('GUap ('GRecord lst))) name
        = Lookup name (PrependName (RecordNonSpares lst))
    (~>) ('GAsterixExpansion cat ed ('GExpansion mn lst)) name
        = 'GExpansion mn lst ~> name
    (~>) k name = TypeError ('Text "Undefined argument: " :<>: ShowType k)

-- | Extract dependent rule.
type family DepRule2 lst ix where
    DepRule2 ('(ix1, t) ': ts) ix = If (ix1 == ix) t (DepRule2 ts ix)
    DepRule2 t ix = TypeError ('Text "Undefined rule: " :<>: ShowType ix)
type family DepRule t ix where
    DepRule ('GNonSpare name title ('GDependent lst1 d lst2)) ix = DepRule2 lst2 ix

-- | All of the given types in a non-empty list must be the same,
-- return that type. Usage example:
-- type TSacSic = SameType '[ Cat034 ~> "010", Cat048 ~> "010"]
type family SameType (t :: [k]) :: k where
    SameType '[] = TypeError ('Text "Empty list")
    SameType (t ': '[]) = t
    SameType (t1 ': t2 ': ts) = If
        (t1 == t2)
        (SameType (t2 ': ts))
        (TypeError ('Text "Type mismatch"))