packages feed

dynobud-1.9.0.0: src/Dyno/ExportCStruct.hs

-- todo(greg):
-- a better name for this module would be Dyno.TechnicalDebt
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}

module Dyno.ExportCStruct
       ( CStructExporter
       , runCStructExporter
       , putTypedef
       , exportTypedef
       , exportCData
       , exportNames
       ) where

import Control.Lens ( (^.) )
import Control.Monad.State.Lazy ( State )
import qualified Control.Monad.State.Lazy as State
import qualified Data.Map as M
import Data.List
import Data.Proxy ( Proxy(..) )
import Text.Printf ( printf )
import Text.Read ( readMaybe )

import Accessors
       ( Lookup, AccessorTree(..), Field(..)
       , accessors, flatten, sameFieldType )
import Dyno.Vectorize ( Vectorize, vlength )

runCStructExporter :: State CStructExporter a -> (a, String)
runCStructExporter action =
  case State.runState action (CStructExporter (M.empty, [], [])) of
    (ret, CStructExporter (_, typedefs, [])) -> (ret, unlines (intercalate [""] (reverse typedefs)))
    (_, CStructExporter (_, _, stack)) ->
      error $ "runCStructExporter: stack is not empty!!:\n" ++ unlines stack

data CStructExporter = CStructExporter (M.Map String Fields, [[String]], [String])

data Fields where
  Fields :: [(String, AccessorTree a)] -> Fields

write :: String -> State CStructExporter ()
write str =
  State.modify $
  \(CStructExporter (set, typedefs, outs)) -> CStructExporter (set, typedefs, str: outs)

typedefStruct :: String -> [(String, AccessorTree a)] -> State CStructExporter ()
typedefStruct typeName fields = do
  write "typedef struct {"
  mapM_ (uncurry writeCField) fields
  write $ "} " ++ typeName ++ ";"

  CStructExporter (set, typedefs, currentStack) <- State.get
  State.put $ CStructExporter (set, reverse currentStack : typedefs, [])

sameFields :: [(String, AccessorTree a)] -> [(String, AccessorTree b)] -> Bool
sameFields xs ys
  | length xs /= length ys = False
  | otherwise = all (uncurry same) (zip xs ys)
  where
    same (nx, fx) (ny, fy) = (nx == ny) && sameTree fx fy

sameTree :: AccessorTree a -> AccessorTree b -> Bool
sameTree (Data (x0, x1) fx) (Data (y0, y1) fy) = x0 == y0 && x1 == y1 && sameFields fx fy
sameTree (Field fx) (Field fy) = sameFieldType fx fy
sameTree _ _ = False

typedefStructIfMissing :: String -> [(String, AccessorTree a)] -> State CStructExporter String
typedefStructIfMissing typeName fields = do
  CStructExporter (set0, typedefs, currentStack) <- State.get
  case M.lookup typeName set0 of
   -- haven't seen this type name yet, use it
   Nothing -> do
     State.put (CStructExporter (M.insert typeName (Fields fields) set0, typedefs, []))
     typedefStruct typeName fields
     State.modify $
       \(CStructExporter (set, structs, _)) -> CStructExporter (set, structs, currentStack)
     return typeName
   -- have seen this type name already, check if it's the one we have seen already
   Just (Fields fields0)
     -- yeah it's the one we have seen already
     | sameFields fields0 fields -> return typeName
     -- uh oh, same name but different type, lets modify the name
     | otherwise -> typedefStructIfMissing newTypeName fields
     where
       -- ideally this would sort types by complexity and give simple ones simple names
       newTypeName = typeName ++ "_"
--       newTypeName =
--         error $
--         printf "got two types with the same name: %s\nnew type:\n%s\nold type:\n%s\n"
--         (show typeName)
--         (show fields)
--         (show fields0)


parseVecName :: String -> Maybe Int
parseVecName ('V':'e':'c':' ':k) = readMaybe k
parseVecName _ = Nothing

writeCField :: String -> AccessorTree a -> State CStructExporter ()
writeCField fieldName (Field f) =
  write $ printf "  %s %s;" (primitiveName f) fieldName
writeCField fieldName (Data (typeName0, _) fields) = case parseVecName typeName0 of
  Nothing -> do
    typeName <- typedefStructIfMissing typeName0 fields
    write $ printf "  %s %s;" typeName fieldName
  Just k -> do -- handle Vecs as arrays
    childtype <- case fields of
      [] -> error "writeCField: Vec child has no children"
      ((_, Field f):_) -> return (primitiveName f)
      ((_, Data (typeName0',_) childfields):_) ->
        typedefStructIfMissing typeName0' childfields
    write $ printf "  %s %s[%d];" childtype fieldName k


primitiveName :: Field a -> String
primitiveName (FieldDouble _) = "double"
primitiveName (FieldInt _   ) = "int64_t"
primitiveName (FieldFloat _ ) = "float"
primitiveName (FieldString _) = error "writeCField: strings can't be struct fields :("
primitiveName (FieldBool _  ) = error "writeCField: bools can't be struct fields :("
primitiveName FieldSorry    =
  error "writeCField: found a GetSorry (generic-accessors doesn't support a type)"



-- | convenience function to export only one struct
putTypedef :: forall a . Lookup a => Proxy a -> State CStructExporter String
putTypedef _ =
  case handleM33 (accessors :: AccessorTree a) of
    (Data (typeName, _) fields) -> typedefStructIfMissing typeName fields
    (Field _) -> error "putStruct: accessors got Field instead of Data"

-- | convenience function to export only one struct
exportTypedef :: Lookup a => Proxy a -> String
exportTypedef = snd . runCStructExporter . putTypedef

-- | Export data as a C struct.
-- If a string with a variable name is given, the variable is declared.
exportCData :: forall a . Lookup a => Int -> Maybe String -> a -> String
exportCData spaces0 maybeVarName theData = case (acc, maybeVarName) of
  (Data (typeName,_) fields, Nothing) -> exportStructData theData typeName spaces fields
  (Data (typeName,_) fields, Just varName) ->
    printf "%s%s %s = {\n%s;" spaces typeName varName
    (exportStructData theData typeName (spaces ++ "  ") fields)
  (Field _, _) -> error "exportStructData: accessors got Field instead of Data"
  where
    spaces = replicate spaces0 ' '
    acc = handleM33 accessors

handleM33 :: AccessorTree a -> AccessorTree a
handleM33 r@(Field _) = r
handleM33 (Data ("V3","V3")
           [ ("x", Data ("V3","V3") [ ("x", Field field0)
                                    , ("y", Field field1)
                                    , ("z", Field field2)
                                    ])
           , ("y", Data ("V3","V3") [ ("x", Field field3)
                                    , ("y", Field field4)
                                    , ("z", Field field5)
                                    ])
           , ("z", Data ("V3","V3") [ ("x", Field field6)
                                    , ("y", Field field7)
                                    , ("z", Field field8)
                                    ])
           ]) = r
  where
    r = Data ("M33","M33")
        [ ("xx", Field field0)
        , ("xy", Field field1)
        , ("xz", Field field2)
        , ("yx", Field field3)
        , ("yy", Field field4)
        , ("yz", Field field5)
        , ("zx", Field field6)
        , ("zy", Field field7)
        , ("zz", Field field8)
        ]
handleM33 (Data name fields) = Data name $ map (\(n,at) -> (n, handleM33 at)) fields

exportStructData :: forall a . a -> String -> String -> [(String, AccessorTree a)] -> String
exportStructData theData comment spaces fields =
  spaces ++ intercalate (",\n"++spaces) (map exportField' fields)
  ++ "\n"++ spaces ++ "} /* " ++ comment ++ " */"
  where
    exportField' :: (String, AccessorTree a) -> String
    exportField' (n,t) = exportField theData (Just n) spaces t

toString :: a -> Field a -> String
toString theData (FieldDouble f) = case show (theData ^. f) of
  "Infinity" -> "INFINITY"
  "-Infinity" -> "-INFINITY"
  r -> r
toString theData (FieldFloat f) = show (theData ^. f)
toString theData (FieldInt f) = show (theData ^. f)
toString theData (FieldBool f) = show (fromEnum (theData ^. f))
toString _ (FieldString _) = "NAN"
toString _ FieldSorry = "NAN"

exportField :: a -> Maybe String -> String -> AccessorTree a -> String
exportField theData (Just fieldName) _ (Field f) =
  printf ".%s = %s" fieldName (toString theData f)
exportField theData Nothing _ (Field f) = toString theData f
exportField theData mfieldName spaces (Data (typeName,_) subfields) =
  nameEq ++ "\n" ++ fields
  where
    nameEq = case mfieldName of
      Just fieldName -> printf ".%s = {" fieldName
      Nothing -> "{"

    comment = case mfieldName of
      Just fieldName -> fieldName ++ " (" ++ typeName ++ ")"
      Nothing -> typeName

    fields :: String
    fields = case parseVecName typeName of
      Nothing -> exportStructData theData comment (spaces ++ "  ") subfields
      Just _ ->
        spaces ++ "  "
        ++ intercalate (",\n"++(spaces++"  "))
           (map (exportField theData Nothing (spaces++"  ") . snd) subfields)
        ++ "\n"++spaces++"  } /* " ++ comment ++ " */"

exportNames :: forall f . (Vectorize f, Lookup (f ())) => Proxy f -> String -> (String, String)
exportNames _ functionName = (src, prototype)
  where
    src =
      unlines
      [ prototype ++ " {"
      , printf "  static const char names[%d][%d] = {%s};"
        n maxLen (intercalate "," (map show names))
      , "  return names[k];"
      , "}"
      ]
    prototype = printf "const char * %s(const int k)" functionName
    maxLen = 1 + maximum (map length names)
    n
      | length names == vlength (Proxy :: Proxy f) = vlength (Proxy :: Proxy f)
      | otherwise = error "exportNames: length mismatch"
    names :: [String]
    names = map (\(name, _) -> name) $ flatten $
            handleM33 (accessors :: AccessorTree (f ()))