-- | An example showing how specifications involving structs (in particular, -- nested structs) are interpreted and how they are compiled to C using -- copilot-c99. {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} module Main where import qualified Prelude as P import Control.Monad (void, forM_) import Data.Proxy (Proxy(..)) import Data.Type.Equality (TestEquality(..), (:~:)(..)) import GHC.Generics (Generic) import GHC.TypeLits (sameSymbol) import Language.Copilot import Copilot.Compile.C99 -- | Definition for `Volts`. data Volts = Volts { numVolts :: Field "numVolts" Word16 , flag :: Field "flag" Bool } deriving Generic -- | `Struct` instance for `Volts`. instance Struct Volts where typeName = typeNameDefault toValues = toValuesDefault -- In order to run struct updates (as used in the "equalityStructUpdate" -- trigger below) in the Copilot interpreter, we must implement the -- `updateField` method. To do so, we must check to see if the supplied -- `Value` has a `Field` with the same name and type as a field in `Volts`. -- (Alternatively, we could define this as @updateField = updateFieldDefault@, -- but we demonstrate how to manually implement it below for educational -- purposes.) updateField volts (Value fieldTy (field :: Field fieldName a)) -- For each field in `Volts`, we must: -- -- 1. Check that the field names match using `sameSymbol`. Here, -- "numVolts" is the expected name, and `fieldName` is the actual name -- that is supplied as an argument to `updateField`. If the check -- succeeds, then the `sameSymbol` function will return `Just p`, where -- `p` is proof that the two names are the same. | Just Refl <- sameSymbol (Proxy @"numVolts") (Proxy @fieldName) -- 2. Check that the field types match using `testEquality`. Here, -- `Word16` is the expected type, and `fieldTy` is the actual type that -- is supplied as an argument. Again, `testEquality` will return `Just -- p` (where `p` is a proof) if the two are the same. , Just Refl <- testEquality Word16 fieldTy -- 3. If both of the checks above succeed, then we can update the field's -- value using a record update. = volts { numVolts = field } -- It is possible that the `Value` passed as an argument could correspond -- to any of the fields in `Volts`, so we must repeat this process for -- the `flag` field as well. | Just Refl <- sameSymbol (Proxy @fieldName) (Proxy @"flag") , Just Refl <- testEquality fieldTy Bool = volts { flag = field } -- If the supplied `Value` does not correspond to any field in `Volts`, -- then something went wrong in the Copilot interpreter. This case reports -- this as an error. | otherwise = error $ "Unexpected field: " P.++ show field -- | `Volts` instance for `Typed`. instance Typed Volts where typeOf = typeOfDefault data Battery = Battery { temp :: Field "temp" Word16 , volts :: Field "volts" (Array 10 Volts) , other :: Field "other" (Array 10 (Array 5 Word32)) } deriving Generic -- | `Battery` instance for `Struct`. instance Struct Battery where typeName = typeNameDefault toValues = toValuesDefault -- We implement `updateField` similarly to how we implement it in the -- `Struct Volts` instance above. (Alternatively, we could define this as -- @updateField = updateFieldDefault@, but we demonstrate how to manually -- implement it below for educational purposes.) updateField battery (Value fieldTy (field :: Field fieldName a)) | Just Refl <- sameSymbol (Proxy @fieldName) (Proxy @"temp") , Just Refl <- testEquality fieldTy Word16 = battery { temp = field } | Just Refl <- sameSymbol (Proxy @fieldName) (Proxy @"volts") -- Note that writing out the full `Type` for `Volts` is somewhat verbose, -- so we make use of the `Typed Volts` instance and write `typeOf @Volts` -- instead. , Just Refl <- testEquality fieldTy (Array @10 (typeOf @Volts)) = battery { volts = field } | Just Refl <- sameSymbol (Proxy @fieldName) (Proxy @"other") , Just Refl <- testEquality fieldTy (Array @10 (Array @5 Word32)) = battery { other = field } | otherwise = error $ "Unexpected field: " P.++ show field -- | `Battery` instance for `Typed`. instance Typed Battery where typeOf = typeOfDefault spec :: Spec spec = do let voltsValue :: Volts voltsValue = Volts { numVolts = Field 42 , flag = Field True } batteryValue :: Battery batteryValue = Battery { temp = Field 0 , volts = Field (array (replicate 10 voltsValue)) , other = Field (array (replicate 10 (array (replicate 5 0)))) } battery :: Stream Battery battery = extern "battery" (Just [batteryValue]) -- Check equality, indexing into nested structs and arrays. Note that this is -- trivial by equality. trigger "equalitySameIndex" ((((battery#volts) ! 0)#numVolts) == (((battery#volts) ! 0)#numVolts)) [arg battery] -- Same as previous example, but get a different array index (so should be -- false). trigger "equalityDifferentIndices" ((((battery#other) ! 2) ! 3) == (((battery#other) ! 2) ! 4)) [arg battery] -- Update a struct field, then check it for equality. let batteryTemp1, batteryTemp2 :: Stream Word16 batteryTemp1 = (battery ## temp =$ (+1))#temp batteryTemp2 = battery#temp + 1 trigger "equalityStructUpdate" (batteryTemp1 == batteryTemp2) [arg battery, arg batteryTemp1, arg batteryTemp2] main :: IO () main = do -- Run the specification using the Copilot interpreter. interpret 1 spec -- Compile the specification to C. spec' <- reify spec compile "structs" spec'