csv-parser / src / CSVQLEval.hs
CSVQLEval.hs
Raw
module CSVQLEval where
import CSVQLTokens
import CSVQLGrammar
import CSVQLTypes

type VariableEnvironment = [(String, E)]
type State = (VariableEnvironment, E)
type BuiltIn = State -> IO State

-- evaluate a CSVQL expression
evalExpr :: [(String, BuiltIn)] -> BuiltInTypeMap -> State -> IO State

-- return base types
evalExpr _ _ (vars, Null) = return (vars, Null)
evalExpr _ _ (vars, String x) = return (vars, String x)
evalExpr _ _ (vars, Int x) = return (vars, Int x)
evalExpr _ _ (vars, Bool x) = return (vars, Bool x)

--evaluate compound types
evalExpr funcs fMap (vars, Table arity rows) = do
    (vars', arityEvaluated) <- evalExpr funcs fMap (vars, arity)
    (vars'', rowsEvaluated) <- evalExpr funcs fMap (vars', rows)
    return (vars'', Table arityEvaluated rowsEvaluated)
evalExpr funcs fMap (vars, Row x) = do
    (vars', evaluated) <- evalExpr funcs fMap (vars, x)
    return (vars', Row evaluated)
evalExpr funcs fMap (vars, List x) = do
    (vars', evaluated) <- evalExpr funcs fMap (vars, x)
    return (vars', List evaluated)
evalExpr funcs fMap (vars, ListBody exp1 exp2) = do
    (vars', eval1) <- evalExpr funcs fMap (vars, exp1)
    (vars'', eval2) <- evalExpr funcs fMap (vars', exp2)
    return (vars'', ListBody eval1 eval2)

-- return value assigned to variable or just expression of type Var if not found
evalExpr funcs fMap (vars, Var x) = case varLookup vars x of
    Just exp -> return (vars, exp)
    Nothing -> return (vars, Var x)

-- handle semicolons (subsequent expressions)
evalExpr funcs fMap (vars, Semi exp1 exp2) = do
    -- only log state changes from first expression, return doesn't matter
    (newGamma, _) <- evalExpr funcs fMap (vars, exp1)
    evalExpr funcs fMap (newGamma, exp2)

-- assign a variable and add it to the environment (or overwrite it if it already exists)
evalExpr funcs fMap (vars, Assign varName expr) = do
    (newVars, evaluated) <- evalExpr funcs fMap (vars, expr)
    assignExpr varName (newVars, evaluated)

-- call a function
evalExpr funcs fMap (vars, Func funcName arguments) = do
    (vars' , args) <- evalExpr funcs fMap (vars, arguments)
    case funcLookup funcs funcName of
        Just func -> func (vars', args)
        Nothing -> error $ "function " ++ funcName ++ " not found"

-- evaluates not function
evalExpr funcs fMap (vars, Not expr) = do
    (vars', evaluatedExpr) <- evalExpr funcs fMap (vars, expr)
    return (vars', CSVQLEval.not evaluatedExpr)

-- evaluates indexing
evalExpr funcs fMap (vars, Index expr1 expr2) = do
    (vars', expr1') <- evalExpr funcs fMap (vars, expr1)
    (vars'', expr2') <- evalExpr funcs fMap (vars', expr2)
    return (vars'', CSVQLEval.index expr1' expr2')

-- evaluates equality
evalExpr funcs fMap (vars, Equals expr1 expr2) = do
    (_, expr1') <- evalExpr funcs fMap (vars, expr1)
    (_, expr2') <- evalExpr funcs fMap (vars, expr2)
    return (vars, equator expr1' expr2')

evalExpr funcs fMap (vars, LessThan expr1 expr2) = do
    (_, expr1') <- evalExpr funcs fMap (vars, expr1)
    (_, expr2') <- evalExpr funcs fMap (vars, expr2)
    return (vars, lessThan expr1' expr2')

evalExpr funcs fMap (vars, GreaterThan expr1 expr2) = do
    (_, expr1') <- evalExpr funcs fMap (vars, expr1)
    (_, expr2') <- evalExpr funcs fMap (vars, expr2)
    return (vars, greaterThan expr1' expr2')

-- evaluates or
evalExpr funcs fMap (vars, Or expr1 expr2) = do
    (_, expr1') <- evalExpr funcs fMap (vars, expr1)
    (_, expr2') <- evalExpr funcs fMap (vars, expr2)
    return (vars, or' expr1' expr2')
        where or' :: E -> E -> E
              or' (Bool True) _ = Bool True
              or' _ (Bool True) = Bool True
              or' _ _ = Bool False

-- evaluates and
evalExpr funcs fMap (vars, And expr1 expr2) = do
    (_, expr1') <- evalExpr funcs fMap (vars, expr1)
    (_, expr2') <- evalExpr funcs fMap (vars, expr2)
    return (vars, and' expr1' expr2')
        where and' :: E -> E -> E
              and' (Bool True) (Bool True) = Bool True
              and' _ _ = Bool False


-- addition for quick combination of rows
evalExpr funcs fMap (vars, Plus expr1 expr2) = do
    (_, expr1') <- evalExpr funcs fMap (vars, expr1)
    (_, expr2') <- evalExpr funcs fMap (vars, expr2)
    return (vars, addExpr expr1' expr2')

-- for loop
evalExpr funcs fMap (vars, For varName iterable body) = forExpr funcs fMap vars varName iterable body

-- handle subfunctions
evalExpr funcs fMap (vars, Sub parentName childName args) = subExpr funcs fMap vars parentName childName args

evalExpr funcs fMap (vars, If condition result) = do
    (_, cond) <- evalExpr funcs fMap (vars, condition)
    case cond of
        (Bool True) -> evalExpr funcs fMap (vars, result)
        _ -> return (vars, Null)

evalExpr funcs fMap (vars, IfElse condition result1 result2) = do
    (_, cond) <- evalExpr funcs fMap (vars, condition)
    case cond of
        (Bool True) -> evalExpr funcs fMap (vars, result1)
        (Bool False) -> evalExpr funcs fMap (vars, result2)
        _ -> error "boolean not given as condition in if statement"

addExpr :: E -> E -> E
addExpr (Row e1) (Row e2) = appendToList e1 e2
addExpr (Int x) (Int y) = Int (x + y)

subExpr :: [(String, BuiltIn)] -> BuiltInTypeMap -> VariableEnvironment -> String -> String -> E -> IO State
subExpr funcs fMap vars parentName childName args = do
    case varLookup vars parentName of
        Just parent -> case funcLookup funcs childName of
                Just func -> do
                    let pType = getType parent [] fMap
                    case fInputTypeLookup fMap childName of
                        Just fType -> 
                            let contains :: UnTypes -> [UnTypes] -> Bool
                                contains _ [] = False
                                contains y (x:xs) | y == x = True
                                                  | otherwise = contains y xs
                                in
                            if contains pType fType
                                then func (vars, ListBody parent (ListBody (String parentName) args))
                                else error "function not relevant to that type"
                        Nothing -> error "function not in type map"
                Nothing -> error "function doesn't exist"
        Nothing -> error "variable doesn't exist"

-- assign/overwrite a variable
assignExpr :: String -> State -> IO State
assignExpr varName (vars, exp) = do
    case varLookup vars varName of
        Nothing -> return ((varName, exp):vars, Null)
        _ -> do
            let newVars = removeVar vars [] varName
            return ((varName, exp):newVars, Null)

-- extract arguments from iterable and call for loop
forExpr :: [(String, BuiltIn)] -> BuiltInTypeMap -> VariableEnvironment -> String -> String -> E -> IO State
forExpr funcs fMap vars varName iterableName body = do
    case varLookup vars iterableName of 
        Just exp2 -> do
            case exp2 of
                (List x) -> loopOver funcs fMap vars varName x body
                (Table width Null) -> return (vars, Null)
                (Table width rows) -> loopOver funcs fMap vars varName rows body
                (Row x) -> loopOver funcs fMap vars varName x body
        Nothing -> error ("iterable not found in variable " ++ varName)

loopOver :: [(String, BuiltIn)] -> BuiltInTypeMap -> VariableEnvironment -> String -> E -> E -> IO State
loopOver funcs fMap vars varName (ListBody current next) body = do
    -- add variable to environment, evaluate, then remove and call next loop
    (vars', evaluated) <- evalExpr funcs fMap ((varName, current):vars, body)
    let vars'' = removeVar vars' [] varName
    loopOver funcs fMap vars'' varName next body
-- final loop
loopOver funcs fMap vars varName current body = do
    (vars', exp3) <- evalExpr funcs fMap ((varName, current):vars, body)
    let vars'' = removeVar vars' [] varName
    return (vars'', exp3)

-- remove variable from var environment (if it exists)
removeVar :: VariableEnvironment -> VariableEnvironment -> String -> VariableEnvironment
removeVar ((name, exp):old) acc toRemove | name == toRemove = old ++ acc
                                         | otherwise = removeVar old ((name, exp):acc) toRemove
removeVar [] acc toRemove = acc

-- find variable in environment
varLookup :: VariableEnvironment -> String -> Maybe E
varLookup [] _ = Nothing
varLookup ((x, exp):gamma) y | x == y = Just exp
                             | otherwise = varLookup gamma y

-- find function in given list
funcLookup :: [(String, a -> b)] -> String -> Maybe (a -> b)
funcLookup [] _ = Nothing
funcLookup ((x, func):funcs) y | x == y = Just func
                               | otherwise = funcLookup funcs y

--compares two expressions, returns true if they are equal
equator :: E -> E -> E
equator e1 e2 = Bool (e1 == e2)

lessThan :: E -> E -> E
lessThan (Int e1) (Int e2) = Bool (e1 < e2)
lessThan _ _ = error "ERROR: Comparing size of non-int value"

greaterThan :: E -> E -> E
greaterThan (Int e1) (Int e2) = Bool (e1 > e2)
greaterThan _ _ = error "ERROR: Comparing size of non-int value"

--negates an expression of type bool
not :: E -> E
not (Bool True) = Bool False
not (Bool _) = Bool True
not _ = error "error negating input, can only negate bools"

-- Sorts a given list or table with bubble sort, is able to sort via rows
sort :: E -> E
sort a@(ListBody _ _) = sort (List a)
sort a@(List e) = List (sortStep e (lengthList a - 1))
    where sortStep :: E -> Int -> E
          sortStep e lim | lim > 0 = sortStep (sortTill e lim) (lim - 1)
                         | otherwise = e
          sortTill :: E -> Int -> E
          sortTill e 0 = e
          sortTill (ListBody e1 (ListBody e2 e3)) lim = ListBody (maxExpr e1 e2) (sortTill (ListBody (minExpr e1 e2) e3) (lim-1))
          sortTill (ListBody e1 e2) lim = ListBody (maxExpr e1 e2) (sortTill (minExpr e1 e2) (lim-1))
sort (Table a rows) = Table a rows'
    where (List rows') = (sort rows)
sort (Row a) = (List (Row a))
sort e = error "parsed expression is not list or table"

-- uses haskells comparison system to compare expressions and return the min expression, both expressions must be of the same type
-- works with all data types except tables
-- string priority is in the order A->a->B->b->C...z
minExpr :: E -> E -> E
minExpr (String e1) (String e2) = String (max e1 e2)
minExpr (Int e1) (Int e2) = Int (min e1 e2)
minExpr (Bool e1) (Bool e2) = Bool (min e1 e2)
minExpr (Row e1) (Row e2) = Row (minExpr e1 e2)
minExpr (List e1) (List e2) = List (minExpr e1 e2)
minExpr (ListBody e11 e12) (ListBody e21 e22) | fromBool $ equator e11 e21 = ListBody e11 (minExpr e12 e22)
                                              | fromBool $ equator e11 (minExpr e11 e21) = ListBody e11 e12
                                              | otherwise = ListBody e21 e22
                                                where fromBool :: E -> Bool
                                                      fromBool (Bool e) = e
minExpr _ _ = error "ERROR: failure to compare expressions, ensure both are of the same type"

-- takes two expressions and returns the larger one
maxExpr :: E -> E -> E
maxExpr (String e1) (String e2) = String (min e1 e2)
maxExpr (Int e1) (Int e2) = Int (max e1 e2)
maxExpr (Bool e1) (Bool e2) = Bool (max e1 e2)
maxExpr (Row e1) (Row e2) = Row (maxExpr e1 e2)
maxExpr (List e1) (List e2) = List (maxExpr e1 e2)
maxExpr (ListBody e11 e12) (ListBody e21 e22) | fromBool $ equator e11 e21 = ListBody e11 (maxExpr e12 e22)
                                              | fromBool $ equator e11 (maxExpr e11 e21) = ListBody e11 e12
                                              | otherwise = ListBody e21 e22
                                                where fromBool :: E -> Bool
                                                      fromBool (Bool e) = e
maxExpr _ _ = error "ERROR: failure to comapre expressions, ensure both are of the same type"

-- takes a list expression and returns the length as an int
lengthList :: E -> Int
lengthList lBody@(ListBody e1 e2) = lengthList (List lBody)
lengthList (Row e) = lengthList (List e)
lengthList (List e) = lengthList' e
    where lengthList' :: E -> Int
          lengthList' (ListBody e1 e2) = 1 + lengthList' e2
          lengthList' e = 1
lengthList (Table _ Null) = 0
lengthList (Table _ e) = lengthList (List e)

-- takes an iterable structure and an integer in expression form and returns the element at given index
index :: E -> E -> E
index (List e1) (Int e2) = indexList e1 e2
index (Row e1@(ListBody _ _)) (Int e2) = indexList e1 e2
index (Row e1) (Int 0) = e1
index (String e1) (Int e2) = index' e1 e2
    where index' :: String -> Int -> E
          index' [] i = error "index out of bounds"
          index' xs 0 = String [head xs]
          index' (x:xs) i = index' xs (i-1)
index (Table _ Null) (Int _) = error "index out of bounds, table empty"
index (Table _ e1@(ListBody _ _)) (Int e2) = indexList e1 e2
index (Table _ e@(Row _)) (Int 0) = e
index (Table _ (Row _)) (Int _) = error "index out of bounds"
index (Row e1) (Int _) = error "index out of bounds"
index e1 e2 = error ("index error, either index is not integer or non iterable structure inputted")

-- DO NOT USE
-- used in index function to index rows and lists
indexList :: E -> Int -> E
indexList (ListBody e1 e2) 0 = e1
indexList e 0 = e
indexList (ListBody e1 e2) i = indexList e2 (i-1)
indexList e i = error "index out of bounds"

-- takes basic types and turns them into a string
eToString :: E -> E
eToString (String x) = String x
eToString (Int x) = String (show x)
eToString (Bool x) = String (show x)
eToString (List e) = eToString e
eToString (ListBody e1 e2) = ListBody (eToString e1) (eToString e2)
eToString (Row e) = Row (eToString e)
eToString (Table x e) = Table x (eToString e)
eToString e = error $ show e ++ " eToString"

-- checks if the iterable expression is empty
eIsEmpty :: E -> E
eIsEmpty Null = Bool True
eIsEmpty (Table _ Null) = Bool True
eIsEmpty (Table _ _) = Bool False
eIsEmpty (List e) = eIsEmpty e
eIsEmpty (ListBody _ _) = Bool False
eIsEmpty (Row e) = eIsEmpty e
eIsEmpty (String "") = Bool True
eIsEmpty (String _) = Bool False
eIsEmpty (Int _) = Bool False
eIsEmpty (Bool _) = Bool False

-- appends two lists together
appendToList :: E -> E -> E
appendToList (List x) z = appendToList' x z
appendToList (ListBody x y) z = ListBody x (appendToList' y z)
appendToList x y | getVarType x == getVarType y = ListBody x y
                 | otherwise = error "list types do not match"
appendToList' :: E -> E -> E
appendToList' (ListBody x y) z = ListBody x (appendToList' y z)
appendToList' x y | getVarType x == getVarType y = ListBody x y
                  | getVarType x == getVarType (removeList y) = ListBody x (removeList y)
                  | otherwise = error "list types do not match"
                  where removeList :: E -> E
                        removeList (List x) = x
                        removeList x = x

-- checks if expressions match the given type
-- lists return true if it is checked against the type (Iterable (TypeList _))
checkType :: UnTypes -> E -> Bool
checkType (Iterable (TypeList x)) (List a) = checkType x a
checkType x (ListBody a b) = checkType x a && checkType x b
checkType TypeNull Null = True
checkType TypeInt (Int _) = True
checkType TypeString (String _) = True
checkType TypeBool (Bool _) = True
checkType (Iterable TypeRow) (Row _) = True
checkType (Iterable TypeTable) (Table _ _) = True
checkType _ _ = False

-- returns the type of the variable parsed
-- IS UNRELIABLE WITH LISTS, it will return the type of the list with the first element found, use with checkType to ensure uniformality
getVarType :: E -> UnTypes
getVarType (List a) = Iterable (TypeList (getVarType a))
getVarType (ListBody a _) = getVarType a
getVarType (Int _) = TypeInt
getVarType (String _) = TypeString
getVarType (Bool _) = TypeBool
getVarType (Row _) = Iterable TypeRow
getVarType (Table _ _) = Iterable TypeTable
getVarType Null = TypeNull