packages feed

hydra-0.15.0: src/main/haskell/Hydra/Tools/Tabular.hs

-- | A loose collection of tools for working with tabular data, including CSVs.

module Hydra.Tools.Tabular (
  referenceCoder,
  tabularAdapter,
) where

import Hydra.Kernel
import Hydra.Tabular
import qualified Hydra.Show.Core as ShowCore

import qualified Control.Monad as CM
import qualified Data.List as L
import qualified Data.Map as M


-- | Consumes a map of field names to value coders, producing a record coder.
--   The record coder maps records with concrete values on the left, to records with references on the right, and vice versa.
--   Fields not designated in the map are unaffected; they are simply copied as-is in either direction.
--   For fields designated in the map, terms on the right *must* be variables.
referenceCoder :: (M.Map Name (Coder Term Name)) -> Coder Record Record
referenceCoder fieldCoders = Coder encode decode
  where
    encode cx (Record tname fields) = Record tname <$> CM.sequence (encodeField <$> fields)
      where
        encodeField field@(Field fname fterm) = case M.lookup fname fieldCoders of
          Just coder -> Field fname <$> (TermVariable <$> coderEncode coder cx fterm)
          Nothing -> return field
    decode cx (Record tname fields) = Record tname <$> CM.sequence (decodeField <$> fields)
      where
        decodeField field@(Field fname fterm) = case M.lookup fname fieldCoders of
          Just coder -> case fterm of
            TermVariable v -> Field fname <$> coderDecode coder cx v
            _ -> Left $ ErrorOther (OtherError $ "expected variable, found: " ++ ShowCore.term fterm)
          Nothing -> return field

-- | Consumes a type name and field types plus a cell-level coder, producing a record coder.
--   The record coder maps data rows on the left to records on the right, and vice versa.
tabularAdapter :: Name -> [FieldType] -> (Type -> Coder (Maybe v) Term) -> Coder (DataRow v) Record
tabularAdapter typeName fieldTypes cellAdapter = Coder encode decode
  where
    cellCoders = cellAdapter <$> (fieldTypeType <$> fieldTypes)
    encode cx (DataRow cells) = do
      values <- CM.zipWithM (\coder cell -> coderEncode coder cx cell) cellCoders cells
      let fields = L.zipWith Field (fieldTypeName <$> fieldTypes) values
      return $ Record typeName fields
    decode cx (Record tname fields) = do
      if (tname /= typeName)
        then Left $ ErrorOther (OtherError $ "expected record of type " ++ unName typeName ++ ", found record of type " ++ unName tname)
        else DataRow <$> CM.sequence (fieldDecoders cx <*> fields)
    fieldDecoders cx = L.zipWith (decodeField cx) fieldTypes cellCoders
      where
        decodeField cx fieldType coder field = if (fieldName field /= fieldTypeName fieldType)
          then Left $ ErrorOther (OtherError $ "expected field " ++ unName (fieldTypeName fieldType) ++ ", found field " ++ unName (fieldName field))
          else coderDecode coder cx (fieldTerm field)