packages feed

project-m36-1.2.0: src/lib/ProjectM36/AtomFunctions/Primitive.hs

module ProjectM36.AtomFunctions.Primitive where
import ProjectM36.Base
import ProjectM36.Relation (relFold, oneTuple)
import ProjectM36.Tuple
import ProjectM36.AtomFunctionError
import ProjectM36.AtomFunction
import ProjectM36.AtomType
import qualified Data.HashSet as HS
import Control.Monad
import qualified Data.UUID as U
import qualified Data.Text as T
import qualified Data.Attoparsec.Text as APT
import Data.Scientific

primitiveAtomFunctions :: AtomFunctions
primitiveAtomFunctions = HS.fromList [
  --match on any relation type
  Function { funcName = "add",
             funcType = [IntegerAtomType, IntegerAtomType, IntegerAtomType],
             funcBody = body (\case
                                 IntegerAtom i1:IntegerAtom i2:_ -> pure (IntegerAtom (i1 + i2))
                                 _ -> Left AtomFunctionTypeMismatchError),
             funcACL = ()},
    Function { funcName = "abs",
               funcType = [IntegerAtomType, IntegerAtomType],
               funcBody = body (\case
                                   IntegerAtom i:_ -> pure $ IntegerAtom (abs i)
                                   _ -> Left AtomFunctionTypeMismatchError
                               ),
               funcACL = ()
             },
    Function { funcName = "id",
               funcType = [TypeVariableType "a", TypeVariableType "a"],
               funcBody = body (\case
                                   x:_ -> pure x
                                   _ -> Left AtomFunctionTypeMismatchError
                               ),
               funcACL = ()},    
    Function { funcName = "sum",
               funcType = foldAtomFuncType IntegerAtomType IntegerAtomType,
               funcBody = body $ relationFoldFunc relationSum,
               funcACL = ()               
             },
    Function { funcName = "count",
               funcType = [anyRelationAtomType,
                           IntegerAtomType],
               funcBody = body $ relationAtomFunc relationCount,
               funcACL = ()               
             },
    Function { funcName = "max",
               funcType = foldAtomFuncType IntegerAtomType IntegerAtomType,
               funcBody = body $ relationFoldFunc relationMax,
               funcACL = ()               
             },
    Function { funcName = "min",
               funcType = foldAtomFuncType IntegerAtomType IntegerAtomType,
               funcBody = body $ relationFoldFunc relationMin,
               funcACL = ()               
             },
    Function { funcName = "mean",
               funcType = foldAtomFuncType IntegerAtomType IntegerAtomType,
               funcBody = body $ relationFoldFunc relationMean,
               funcACL = ()               
             },
    Function { funcName = "eq",
               funcType = [TypeVariableType "a", TypeVariableType "a", BoolAtomType],
               funcBody = body $ \case
                                         [i1,i2] -> pure (BoolAtom (i1 == i2))
                                         _ -> Left AtomFunctionTypeMismatchError,
               funcACL = ()               
             },
    Function { funcName = "lt",
               funcType = [IntegerAtomType, IntegerAtomType, BoolAtomType],
               funcBody = body $ integerAtomFuncLessThan False,
               funcACL = ()
             },
    Function { funcName = "lte",
               funcType = [IntegerAtomType, IntegerAtomType, BoolAtomType],
               funcBody = body $ integerAtomFuncLessThan True,
               funcACL = ()
               },
    Function { funcName = "gte",
               funcType = [IntegerAtomType, IntegerAtomType, BoolAtomType],
               funcBody = body $ integerAtomFuncLessThan False >=> boolAtomNot,
               funcACL = ()
             },
    Function { funcName = "gt",
               funcType = [IntegerAtomType, IntegerAtomType, BoolAtomType],
               funcBody = body $ integerAtomFuncLessThan True >=> boolAtomNot,
               funcACL = ()
             },
    Function { funcName = "not",
               funcType = [BoolAtomType, BoolAtomType],
               funcBody = body $ \case
                                         [b] -> boolAtomNot b
                                         _ -> Left AtomFunctionTypeMismatchError,
               funcACL = ()               
             },
    Function { funcName = "int",
               funcType = [IntegerAtomType, IntAtomType],
               funcBody =
                 body $ \case
                                [IntegerAtom v] ->
                                  if v < fromIntegral (maxBound :: Int) then
                                    pure (IntAtom (fromIntegral v))
                                  else
                                    Left InvalidIntBoundError
                                _ -> Left AtomFunctionTypeMismatchError,
               funcACL = ()               
             },
    Function { funcName = "integer",
               funcType = [IntAtomType, IntegerAtomType],
               funcBody = body $ \case
                 [IntAtom v] -> Right $ IntegerAtom $ fromIntegral v
                 _ -> Left AtomFunctionTypeMismatchError,
               funcACL = ()               
             },
    Function { funcName = "uuid",
               funcType = [TextAtomType, UUIDAtomType],
               funcBody = body $ \case
                 [TextAtom v] ->
                   let mUUID = U.fromString (T.unpack v) in
                     case mUUID of
                       Just u -> pure $ UUIDAtom u
                       Nothing -> Left $ InvalidUUIDString v
                 _ -> Left AtomFunctionTypeMismatchError,
               funcACL = ()               
             },
    Function { funcName = "and",
               funcType = [BoolAtomType, BoolAtomType, BoolAtomType],
               funcBody = body $ \case
                 [BoolAtom b1, BoolAtom b2] ->
                   Right $ BoolAtom (b1 && b2)
                 _ -> Left AtomFunctionTypeMismatchError,
               funcACL = ()               
             },
    Function { funcName = "or",
               funcType = [BoolAtomType, BoolAtomType, BoolAtomType],
               funcBody = body $ \case
                 [BoolAtom b1, BoolAtom b2] ->
                   Right $ BoolAtom (b1 || b2)
                 _ -> Left AtomFunctionTypeMismatchError,
               funcACL = ()               
             },
    Function { funcName = "increment",
               funcType = [IntegerAtomType, IntegerAtomType],
               funcBody = body $ \case
                 [IntegerAtom i] -> pure (IntegerAtom (i+1))
                 _ -> Left AtomFunctionTypeMismatchError,
               funcACL = ()               
             },
    Function { funcName = "text_length",
               funcType = [TextAtomType, IntegerAtomType],
               funcBody = body $ \case
                 [TextAtom t] -> pure (IntegerAtom (toInteger (T.length t)))
                 _ -> Left AtomFunctionTypeMismatchError,
               funcACL = ()
             }
    
  ] <> scientificAtomFunctions
  where
    body = FunctionBuiltInBody
    relationAtomFunc f [RelationAtom rel] = f rel
    relationAtomFunc _ _ = Left AtomFunctionTypeMismatchError
    relationFoldFunc f [SubrelationFoldAtom rel subAttr] = f rel subAttr
    relationFoldFunc _ _ = Left AtomFunctionTypeMismatchError
                         
integerAtomFuncLessThan :: Bool -> [Atom] -> Either AtomFunctionError Atom
integerAtomFuncLessThan equality (IntegerAtom i1:IntegerAtom i2:_) = pure (BoolAtom (i1 `op` i2))
  where
    op = if equality then (<=) else (<)
integerAtomFuncLessThan _ _= pure (BoolAtom False)

boolAtomNot :: Atom -> Either AtomFunctionError Atom
boolAtomNot (BoolAtom b) = pure (BoolAtom (not b))
boolAtomNot _ = error "boolAtomNot called on non-Bool atom"

--used by sum atom function
relationSum :: Relation -> AttributeName -> Either AtomFunctionError Atom
relationSum relIn subAttr = pure (IntegerAtom (relFold (\tupIn acc -> acc + newVal tupIn) 0 relIn))
  where
    --extract Integer from Atom
    newVal tupIn =
      case atomForAttributeName subAttr tupIn of
        Left err -> error (show err)
        Right atom -> castInteger atom
    
relationCount :: Relation -> Either AtomFunctionError Atom
relationCount relIn = pure (IntegerAtom (relFold (\_ acc -> acc + 1) (0::Integer) relIn))

relationMax :: Relation -> AttributeName -> Either AtomFunctionError Atom
relationMax relIn subAttr = case oneTuple relIn of
    Nothing -> Left AtomFunctionEmptyRelationError
    Just oneTup -> pure (IntegerAtom (relFold (\tupIn acc -> max acc (newVal tupIn)) (newVal oneTup) relIn))
  where
    newVal tupIn =
      case atomForAttributeName subAttr tupIn of
        Left err -> error (show err)
        Right atom -> castInteger atom

relationMin :: Relation -> AttributeName -> Either AtomFunctionError Atom
relationMin relIn subAttr = case oneTuple relIn of 
  Nothing -> Left AtomFunctionEmptyRelationError
  Just oneTup -> pure (IntegerAtom (relFold (\tupIn acc -> min acc (newVal tupIn)) (newVal oneTup) relIn))
  where
    newVal tupIn =
      case atomForAttributeName subAttr tupIn of
        Left err -> error (show err)
        Right atom -> castInteger atom


relationMean :: Relation -> AttributeName -> Either AtomFunctionError Atom
relationMean relIn subAttr = case oneTuple relIn of
  Nothing -> Left AtomFunctionEmptyRelationError
  Just _oneTup -> do
    let (sum'', count') = relFold (\tupIn (sum', count) -> (sum' + newVal tupIn, count + 1)) (0, 0) relIn
        newVal tupIn =
          case atomForAttributeName subAttr tupIn of
            Left err -> error (show err)
            Right atom -> castInteger atom
    pure (IntegerAtom (sum'' `div` count'))
    

castInt :: Atom -> Int
castInt (IntAtom i) = i
castInt _ = error "attempted to cast non-IntAtom to Int"

castInteger :: Atom -> Integer
castInteger (IntegerAtom i) = i 
castInteger _ = error "attempted to cast non-IntegerAtom to Integer"

scientificAtomFunctions :: AtomFunctions
scientificAtomFunctions = HS.fromList [
  Function { funcName = "read_scientific",
             funcType = [TextAtomType, ScientificAtomType],
             funcBody = body $ \case
               TextAtom t:_ ->
                 case APT.parseOnly (APT.scientific <* APT.endOfInput) t of
                   Left err -> Left (AtomFunctionParseError err)
                   Right sci -> pure (ScientificAtom sci)
               _ -> Left AtomFunctionTypeMismatchError,
             funcACL = ()             
           },
  Function { funcName = "scientific",
             funcType = [IntegerAtomType, IntAtomType, ScientificAtomType],
             funcBody = body $ \case
               [IntegerAtom c,IntAtom e] -> pure (ScientificAtom $ scientific c e)
               _ -> Left AtomFunctionTypeMismatchError,
             funcACL = ()             
           },
  Function { funcName = "scientific_add",
             funcType = binaryFuncType,
             funcBody = binaryFuncBody (+),
             funcACL = ()             
           },
  Function { funcName = "scientific_subtract",
             funcType = binaryFuncType,
             funcBody = binaryFuncBody (-),
             funcACL = ()             
           },
  Function { funcName = "scientific_multiply",
             funcType = binaryFuncType,
             funcBody = binaryFuncBody (*),
             funcACL = ()             
           },
  Function { funcName = "scientific_divide",
             funcType = binaryFuncType,
             funcBody = binaryFuncBody (/),
             funcACL = ()
           }
  ]
  where body = FunctionBuiltInBody
        binaryFuncType = [ScientificAtomType, ScientificAtomType, ScientificAtomType]
        binaryFuncBody op = body $ \case
          [ScientificAtom s1, ScientificAtom s2] -> pure (ScientificAtom (s1 `op` s2))
          _ -> Left AtomFunctionTypeMismatchError