{-
-- EPITECH PROJECT, 2025
-- Glados
-- File description:
-- Semantic Checker for Function Calls & Operators
-}

module AST.Semantics.CheckCall (checkCall, checkEqualityOp,checkTypeof,
    checkFFRead,
    checkFFWrite,
    checkOpen,
    checkClose,
    checkRead,
    checkInput,
    checkSizeof) where

import qualified Data.Text as DT
import qualified Data.Map.Strict as Map
import AST.Ast (Ast(..))
import AST.Semantics.Type
import Common.Type.Integer (IntValue(..))
import Data.Char (chr)
import Control.Monad (unless)
import Control.Applicative ((<|>))

-- | Type signature for the checkExpr function passed as argument.
-- Used to break the circular dependency between Check and CheckCall.
type CheckExprFn = CheckEnv -> Ast -> Either String Type

-- | Main entry point for checking calls and operators.
--
-- Dispatches the call to the appropriate handler. It first checks if the
-- function is a built-in operator (Math, Logic, Special). If not, it
-- attempts to resolve it as a user-defined function.
--
-- @param CheckExprFn The callback to verify arguments.
-- @param CheckEnv The current semantic environment.
-- @param Ast The function node (usually ASymbol).
-- @param [Ast] The list of arguments.
-- @return Either String Type The return type of the call or an error message.
checkCall :: CheckExprFn -> CheckEnv -> Ast -> [Ast] -> Either String Type
checkCall :: CheckExprFn -> CheckEnv -> Ast -> [Ast] -> Either String Type
checkCall CheckExprFn
checker CheckEnv
env (APos Int
_ Int
_ Ast
f) [Ast]
args = CheckExprFn -> CheckEnv -> Ast -> [Ast] -> Either String Type
checkCall CheckExprFn
checker CheckEnv
env Ast
f [Ast]
args
checkCall CheckExprFn
checker CheckEnv
env (ASymbol Text
name) [Ast]
args =
    case CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkBuiltinOp CheckExprFn
checker CheckEnv
env Text
name [Ast]
args of
        Just Either String Type
result -> Either String Type
result
        Maybe (Either String Type)
Nothing     -> CheckExprFn -> CheckEnv -> Text -> [Ast] -> Either String Type
checkUserFunc CheckExprFn
checker CheckEnv
env Text
name [Ast]
args
checkCall CheckExprFn
checker CheckEnv
env Ast
expr [Ast]
args = do
    funcType <- CheckExprFn
checker CheckEnv
env Ast
expr
    case funcType of
        TyFunc [Type]
expectedArgs Type
retType ->
            CheckExprFn
-> CheckEnv
-> Text
-> [Ast]
-> [Type]
-> Type
-> Either String Type
verifyFuncCall CheckExprFn
checker CheckEnv
env
                (String -> Text
DT.pack String
"<anonymous_call>") [Ast]
args [Type]
expectedArgs Type
retType
        Type
_ -> String -> Either String Type
forall a b. a -> Either a b
Left (String -> Either String Type) -> String -> Either String Type
forall a b. (a -> b) -> a -> b
$ String
"Error: expression is not a function ("
            String -> String -> String
forall a. [a] -> [a] -> [a]
++ Type -> String
typeToString Type
funcType String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
")"

-- | Main dispatcher for built-in operators and special functions.
--
-- Tries to match arithmetic, comparison, logic, or special internal calls
-- like 'attr_update'.
--
-- Returns 'Nothing' if the symbol is not a built-in operator.
checkBuiltinOp :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
               -> Maybe (Either String Type)
checkBuiltinOp :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkBuiltinOp CheckExprFn
c CheckEnv
e Text
n [Ast]
a =
    case CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkMathComp CheckExprFn
c CheckEnv
e Text
n [Ast]
a of
        Just Either String Type
res -> Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just Either String Type
res
        Maybe (Either String Type)
Nothing -> CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkLogicSpecial CheckExprFn
c CheckEnv
e Text
n [Ast]
a

-- | Sub-dispatcher for Arithmetic and Comparison operators.
--
-- Handles:
-- * Arithmetic: +, -, *, div, mod
-- * Comparison: <, >, <=, >=
--
-- Split from the main function to reduce cyclomatic complexity and line length.
checkMathComp :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
              -> Maybe (Either String Type)
checkMathComp :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkMathComp CheckExprFn
c CheckEnv
e Text
n [Ast]
a
    | Text
n Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
mathOps = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Type -> Type -> Either String Type
checkBinaryOp CheckExprFn
c CheckEnv
e Text
n [Ast]
a Type
TyInt Type
TyInt
    | Text
n Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
compOps = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Type -> Type -> Either String Type
checkBinaryOp CheckExprFn
c CheckEnv
e Text
n [Ast]
a Type
TyInt Type
TyBool
    | Bool
otherwise = Maybe (Either String Type)
forall a. Maybe a
Nothing
    where
        mathOps :: [Text]
mathOps = (String -> Text) -> [String] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map String -> Text
DT.pack [String
"+", String
"-", String
"*", String
"div", String
"mod"]
        compOps :: [Text]
compOps = (String -> Text) -> [String] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map String -> Text
DT.pack [String
"<", String
">", String
"<=", String
">="]

-- | Validates 'print'. Accepts 1 argument of ANY type. Returns Void.
checkPrint :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkPrint :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkPrint CheckExprFn
checker CheckEnv
env [Ast
arg] = do
    _ <- CheckExprFn
checker CheckEnv
env Ast
arg
    Right TyVoid
checkPrint CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"Function 'print' expects exactly 1 argument"

-- | Validates strict operators (===, !==). Always returns Bool.
-- Accepts any types (even incompatible ones).
checkStrictOp :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkStrictOp :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkStrictOp CheckExprFn
checker CheckEnv
env [Ast
left, Ast
right] = do
    _ <- CheckExprFn
checker CheckEnv
env Ast
left
    _ <- checker env right
    Right TyBool
checkStrictOp CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"Strict comparison operator expects 2 arguments"

-- | Sub-dispatcher for Logic and Strict Comparison operators.
--
-- This function handles boolean logic (&&, ||) and strict comparison (===, !==).
-- For other special keywords (like 'print' or 'eq?'), it delegates to 'checkKeywordFuncs'
-- to keep the function size small and manageable.
--
-- @param c (checker): The callback function to verify sub-expressions.
-- @param e (env): The current semantic environment.
-- @param n (name): The operator/function name (e.g. "&&", "===").
-- @param a (args): The list of arguments passed to the operator.
checkLogicSpecial :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
                  -> Maybe (Either String Type)
checkLogicSpecial :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkLogicSpecial CheckExprFn
c CheckEnv
e Text
n [Ast]
a
    | Text
n Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
logicOps = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Type -> Type -> Either String Type
checkBinaryOp CheckExprFn
c CheckEnv
e Text
n [Ast]
a Type
TyBool Type
TyBool
    | Text
n Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
strictOps = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkStrictOp CheckExprFn
c CheckEnv
e [Ast]
a
    | Bool
otherwise = CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkKeywordFuncs CheckExprFn
c CheckEnv
e Text
n [Ast]
a
    where
        logicOps :: [Text]
logicOps = (String -> Text) -> [String] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map String -> Text
DT.pack [String
"&&", String
"||"]
        strictOps :: [Text]
strictOps = (String -> Text) -> [String] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map String -> Text
DT.pack [String
"teq?", String
"tneq?"]

-- | Main dispatcher for named keyword functions.
--
-- This function acts as a router that delegates the verification to specific
-- sub-dispatchers based on the function category. It uses the (<|>) operator
-- to try each handler in sequence:
-- 1. Base Operators (equality, not, structs)
-- 2. System Functions (print, exit, typeof)
-- 3. IO Functions (files, input)
-- 4. Data Functions (casts, list ops)
--
-- Arguments:
--   c (checker) : The callback function to verify sub-expressions.
--   e (env)     : The current semantic environment.
--   n (name)    : The function/keyword name (e.g. "print", "open").
--   a (args)    : The list of AST arguments passed to the function.
--
-- Returns 'Just (Right Type)' if a match is found and valid,
-- 'Just (Left Error)' if a match is found but invalid,
-- or 'Nothing' if the symbol is not a keyword.
checkKeywordFuncs :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
                  -> Maybe (Either String Type)
checkKeywordFuncs :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkKeywordFuncs CheckExprFn
c CheckEnv
e Text
n [Ast]
a =
    CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkBaseOps CheckExprFn
c CheckEnv
e Text
n [Ast]
a Maybe (Either String Type)
-> Maybe (Either String Type) -> Maybe (Either String Type)
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|>
    CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkSystemOps CheckExprFn
c CheckEnv
e Text
n [Ast]
a Maybe (Either String Type)
-> Maybe (Either String Type) -> Maybe (Either String Type)
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|>
    CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkIOFuncs CheckExprFn
c CheckEnv
e Text
n [Ast]
a Maybe (Either String Type)
-> Maybe (Either String Type) -> Maybe (Either String Type)
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|>
    CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkDataFuncs CheckExprFn
c CheckEnv
e Text
n [Ast]
a

-- | Dispatcher for fundamental language operators.
--
-- Handles:
-- * Equality checks: 'eq?' (==), 'neq?' (!=)
-- * Boolean negation: '!'
-- * Internal structure updates: attr_update
checkBaseOps :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
             -> Maybe (Either String Type)
checkBaseOps :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkBaseOps CheckExprFn
c CheckEnv
e Text
n [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"eq?" Bool -> Bool -> Bool
|| Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"neq?" = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkEqualityOp CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"!"           = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Type -> Type -> Either String Type
checkUnaryOp CheckExprFn
c CheckEnv
e Text
n [Ast]
a Type
TyBool Type
TyBool
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"attr_update" = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkSetField CheckExprFn
c CheckEnv
e [Ast]
a
    | Bool
otherwise = Maybe (Either String Type)
forall a. Maybe a
Nothing

-- | Dispatcher for system utilities and introspection functions.
--
-- Handles:
-- * 'print': Standard output.
-- * 'exit': Terminate the program with a code.
-- * 'typeof': Runtime type inspection.
checkSystemOps :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
               -> Maybe (Either String Type)
checkSystemOps :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkSystemOps CheckExprFn
c CheckEnv
e Text
n [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"print"  = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkPrint CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"exit"   = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkExit CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"typeof" = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkTypeof CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"sizeof" = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkSizeof CheckExprFn
c CheckEnv
e [Ast]
a
    | Bool
otherwise = Maybe (Either String Type)
forall a. Maybe a
Nothing

-- | Dispatcher for Input/Output operations (File System & Console).
--
-- Handles:
-- * High-level File I/O: 'ffread' (read whole file), 'ffwrite' (write content).
-- * Low-level I/O: 'open' (get fd), 'close' (close fd), 'read' (read bytes),
--   'input' (read from stdin/fd).
checkIOFuncs :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
             -> Maybe (Either String Type)
checkIOFuncs :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkIOFuncs CheckExprFn
c CheckEnv
e Text
n [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"ffread"  = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkFFRead CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"ffwrite" = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkFFWrite CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"open"    = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkOpen CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"close"   = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkClose CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"read"    = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkRead CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"input"   = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkInput CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"write"   = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkWrite CheckExprFn
c CheckEnv
e [Ast]
a
    | Bool
otherwise = Maybe (Either String Type)
forall a. Maybe a
Nothing

-- | Dispatcher for Data functions: Casts and List operations.
checkDataFuncs :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
               -> Maybe (Either String Type)
checkDataFuncs :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Maybe (Either String Type)
checkDataFuncs CheckExprFn
c CheckEnv
e Text
n [Ast]
a
    | Text
n Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
castOps = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Type -> Type -> Either String Type
checkUnaryOp CheckExprFn
c CheckEnv
e Text
n [Ast]
a Type
TyInt Type
TyInt
    | Text
n Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
listOps = Either String Type -> Maybe (Either String Type)
forall a. a -> Maybe a
Just (Either String Type -> Maybe (Either String Type))
-> Either String Type -> Maybe (Either String Type)
forall a b. (a -> b) -> a -> b
$ CheckExprFn -> CheckEnv -> Text -> [Ast] -> Either String Type
checkListOps CheckExprFn
c CheckEnv
e Text
n [Ast]
a
    | Bool
otherwise = Maybe (Either String Type)
forall a. Maybe a
Nothing
    where
        castOps :: [Text]
castOps = (String -> Text) -> [String] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map String -> Text
DT.pack [ String
"int8", String
"uint8", String
"int16", String
"uint16"
                              , String
"int32", String
"uint32", String
"int64", String
"uint64"
                              , String
"char", String
"uchar" ]
        listOps :: [Text]
listOps = (String -> Text) -> [String] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map String -> Text
DT.pack [String
"cons", String
"head", String
"tail", String
"nth", String
"nth_update"]

-- | Dispatcher for List specific operations.
checkListOps :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
             -> Either String Type
checkListOps :: CheckExprFn -> CheckEnv -> Text -> [Ast] -> Either String Type
checkListOps CheckExprFn
c CheckEnv
e Text
n [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"cons" = CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkCons CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"head" = CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkHead CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"tail" = CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkTail CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"nth"  = CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkNth CheckExprFn
c CheckEnv
e [Ast]
a
    | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Text
DT.pack String
"nth_update" = CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkUpdate CheckExprFn
c CheckEnv
e [Ast]
a
    | Bool
otherwise = String -> Either String Type
forall a b. a -> Either a b
Left String
"Unknown list operator"

-- | Validates 'update(list, index, value)'.
-- Separates type retrieval from validation to fit coding style.
checkUpdate :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkUpdate :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkUpdate CheckExprFn
checker CheckEnv
env [Ast
lst, Ast
idx, Ast
val] = do
    tLst <- CheckExprFn
checker CheckEnv
env Ast
lst
    tIdx <- checker env idx
    tVal <- checker env val
    validateListUpdate tLst tIdx tVal
checkUpdate CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"update expects 3 arguments (list, index, value)"

-- | Helper to validate types for list update.
validateListUpdate :: Type -> Type -> Type -> Either String Type
validateListUpdate :: Type -> Type -> Type -> Either String Type
validateListUpdate (TyList Type
inner) Type
tIdx Type
tVal
    | Bool -> Bool
not (Type -> Type -> Bool
areTypesCompatible Type
TyInt Type
tIdx) =
        String -> Either String Type
forall a b. a -> Either a b
Left String
"update index must be an integer"
    | Type -> Type -> Bool
areTypesCompatible Type
inner Type
tVal = Type -> Either String Type
forall a b. b -> Either a b
Right (Type -> Type
TyList Type
inner)
    | Bool
otherwise = String -> Either String Type
forall a b. a -> Either a b
Left (String -> Either String Type) -> String -> Either String Type
forall a b. (a -> b) -> a -> b
$ String
"Type mismatch in list update: expected " String -> String -> String
forall a. [a] -> [a] -> [a]
++
                         Type -> String
typeToString Type
inner String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" but got " String -> String -> String
forall a. [a] -> [a] -> [a]
++
                         Type -> String
typeToString Type
tVal
validateListUpdate Type
_ Type
_ Type
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"update expects a list as first argument"

-- | Validates 'exit'. Expects 1 Int. Returns Void.
checkExit :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkExit :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkExit CheckExprFn
checker CheckEnv
env [Ast
arg] = do
    t <- CheckExprFn
checker CheckEnv
env Ast
arg
    unless (areTypesCompatible TyInt t) $ Left "exit expects an integer code"
    Right TyVoid
checkExit CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"exit expects 1 argument"

-- | Validates 'cons(elem, list)'. Returns 'list' type.
checkCons :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkCons :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkCons CheckExprFn
checker CheckEnv
env [Ast
el, Ast
lst] = do
    tEl <- CheckExprFn
checker CheckEnv
env Ast
el
    tLst <- checker env lst
    case tLst of
        TyList Type
inner -> if Type -> Type -> Bool
areTypesCompatible Type
inner Type
tEl
                        then Type -> Either String Type
forall a b. b -> Either a b
Right Type
tLst
                        else String -> Either String Type
forall a b. a -> Either a b
Left (String -> Either String Type) -> String -> Either String Type
forall a b. (a -> b) -> a -> b
$ String
"cons type mismatch: " String -> String -> String
forall a. [a] -> [a] -> [a]
++
                             Type -> String
typeToString Type
tEl String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" vs " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Type -> String
typeToString Type
inner
        Type
_ -> String -> Either String Type
forall a b. a -> Either a b
Left String
"cons expects a list as second argument"
checkCons CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"cons expects 2 arguments"

-- | Validates 'head(list)'. Returns inner type.
checkHead :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkHead :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkHead CheckExprFn
checker CheckEnv
env [Ast
lst] = do
    tLst <- CheckExprFn
checker CheckEnv
env Ast
lst
    case tLst of
        TyList Type
inner -> Type -> Either String Type
forall a b. b -> Either a b
Right Type
inner
        Type
_ -> String -> Either String Type
forall a b. a -> Either a b
Left String
"head expects a list"
checkHead CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"head expects 1 argument"

-- | Validates 'tail(list)'. Returns 'list' type.
checkTail :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkTail :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkTail CheckExprFn
checker CheckEnv
env [Ast
lst] = do
    tLst <- CheckExprFn
checker CheckEnv
env Ast
lst
    case tLst of
        TyList Type
_ -> Type -> Either String Type
forall a b. b -> Either a b
Right Type
tLst
        Type
_ -> String -> Either String Type
forall a b. a -> Either a b
Left String
"tail expects a list"
checkTail CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"tail expects 1 argument"

-- | Validates 'nth(list, index)'. Returns inner type.
checkNth :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkNth :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkNth CheckExprFn
checker CheckEnv
env [Ast
lst, Ast
idx] = do
    tLst <- CheckExprFn
checker CheckEnv
env Ast
lst
    tIdx <- checker env idx
    unless (areTypesCompatible TyInt tIdx) $ Left
        "nth index must be an integer"
    case tLst of
        TyList Type
inner -> Type -> Either String Type
forall a b. b -> Either a b
Right Type
inner
        Type
_ -> String -> Either String Type
forall a b. a -> Either a b
Left String
"nth expects a list as first argument"
checkNth CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"nth expects 2 arguments"

-- | Validates the special internal function 'attr_update'.
--
-- This function is generated by the Parser when assigning a value to a
-- structure field (e.g. `p.x = 10` becomes `attr_update(p, "x", 10)`).
--
-- It verifies that:
-- 1. The object is a structure.
-- 2. The field exists in the structure definition.
-- 3. The assigned value type matches the field type.
checkSetField :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkSetField :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkSetField CheckExprFn
checker CheckEnv
env [Ast
obj, Ast
fieldNameAst, Ast
val] = do
    objType <- CheckExprFn
checker CheckEnv
env Ast
obj
    case objType of
        TyStruct Text
sName -> do
            fieldName <- Ast -> Either String Text
extractStringFromAst Ast
fieldNameAst
            checkFieldInStruct checker env sName fieldName val
        Type
_ -> String -> Either String Type
forall a b. a -> Either a b
Left String
"attr_update expects a structure as first argument"
checkSetField CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"attr_update expects 3 arguments"

-- | Validate that a specific field exists within a structure.
--
-- If found, delegates to 'validateAssignment' for type checking.
checkFieldInStruct :: CheckExprFn -> CheckEnv -> DT.Text -> DT.Text -> Ast
                   -> Either String Type
checkFieldInStruct :: CheckExprFn
-> CheckEnv -> Text -> Text -> Ast -> Either String Type
checkFieldInStruct CheckExprFn
checker CheckEnv
env Text
sName Text
fieldName Ast
val = do
    def <- CheckEnv -> Text -> Either String StructDef
getStructDef CheckEnv
env Text
sName
    case Map.lookup fieldName (structFields def) of
        Just Type
expectedType ->
            CheckExprFn
-> CheckEnv -> Ast -> Type -> Text -> Text -> Either String Type
validateAssignment CheckExprFn
checker CheckEnv
env Ast
val Type
expectedType Text
fieldName Text
sName
        Maybe Type
Nothing -> String -> Either String Type
forall a b. a -> Either a b
Left (String -> Either String Type) -> String -> Either String Type
forall a b. (a -> b) -> a -> b
$ String
"Field '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
DT.unpack Text
fieldName String -> String -> String
forall a. [a] -> [a] -> [a]
++
                          String
"' not found in struct '" String -> String -> String
forall a. [a] -> [a] -> [a]
++
                          Text -> String
DT.unpack Text
sName String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"'"

-- | Final validation step for field assignment.
--
-- Checks if the value type is compatible with the field's expected type.
-- Returns the structure type itself on success (allowing expression chaining).
validateAssignment :: CheckExprFn -> CheckEnv -> Ast -> Type ->
    DT.Text -> DT.Text -> Either String Type
validateAssignment :: CheckExprFn
-> CheckEnv -> Ast -> Type -> Text -> Text -> Either String Type
validateAssignment CheckExprFn
checker CheckEnv
env Ast
val Type
expectedType Text
fieldName Text
sName = do
    valType <- CheckExprFn
checker CheckEnv
env Ast
val
    if areTypesCompatible expectedType valType
        then Right (TyStruct sName)
        else Left $ "Type mismatch in field assignment '" ++
                    DT.unpack fieldName ++ "'. Expected " ++
                    typeToString expectedType ++ ", got " ++
                    typeToString valType

-- | Extract a string (Text) from an AST node.
--
-- Expects an AList of AInteger(IChar), which is the format used by the parser
-- to transmit the field name string to the 'attr_update' function.
extractStringFromAst :: Ast -> Either String DT.Text
extractStringFromAst :: Ast -> Either String Text
extractStringFromAst (AList [Ast]
chars) =
    let extractChar :: Ast -> Maybe Char
extractChar (AInteger (IChar Int8
c)) = Char -> Maybe Char
forall a. a -> Maybe a
Just (Int -> Char
chr (Int8 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int8
c))
        extractChar Ast
_ = Maybe Char
forall a. Maybe a
Nothing
    in case (Ast -> Maybe Char) -> [Ast] -> Maybe String
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM Ast -> Maybe Char
extractChar [Ast]
chars of
        Just String
str -> Text -> Either String Text
forall a b. b -> Either a b
Right (String -> Text
DT.pack String
str)
        Maybe String
Nothing -> String -> Either String Text
forall a b. a -> Either a b
Left String
"Invalid string format in AST"
extractStringFromAst Ast
_ = String -> Either String Text
forall a b. a -> Either a b
Left String
"Invalid field name format in attr_update"

-- | Retrieve a structure definition from the environment.
getStructDef :: CheckEnv -> DT.Text -> Either String StructDef
getStructDef :: CheckEnv -> Text -> Either String StructDef
getStructDef CheckEnv
env Text
name = case Text -> Map Text StructDef -> Maybe StructDef
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
name (CheckEnv -> Map Text StructDef
envStructs CheckEnv
env) of
    Just StructDef
d -> StructDef -> Either String StructDef
forall a b. b -> Either a b
Right StructDef
d
    Maybe StructDef
Nothing -> String -> Either String StructDef
forall a b. a -> Either a b
Left (String -> Either String StructDef)
-> String -> Either String StructDef
forall a b. (a -> b) -> a -> b
$ String
"Error: Undefined struct '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
DT.unpack Text
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"'"

-- | Validates standard binary operators.
--
-- Checks that both arguments match the expected input type.
-- Returns the specified output type.
checkBinaryOp :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
              -> Type -> Type -> Either String Type
checkBinaryOp :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Type -> Type -> Either String Type
checkBinaryOp CheckExprFn
checker CheckEnv
env Text
op [Ast]
args Type
inType Type
outType =
    case [Ast]
args of
        [Ast
left, Ast
right] ->
                CheckExprFn
-> CheckEnv
-> Text
-> Ast
-> Ast
-> Type
-> Type
-> Either String Type
validateBinaryOp CheckExprFn
checker CheckEnv
env Text
op Ast
left Ast
right Type
inType Type
outType
        [Ast]
_ -> String -> Either String Type
forall a b. a -> Either a b
Left (String -> Either String Type) -> String -> Either String Type
forall a b. (a -> b) -> a -> b
$ String
"Operator '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
DT.unpack Text
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"' expects 2 arguments"

-- | Validate binary operands types.
validateBinaryOp :: CheckExprFn -> CheckEnv -> DT.Text -> Ast -> Ast
                 -> Type -> Type -> Either String Type
validateBinaryOp :: CheckExprFn
-> CheckEnv
-> Text
-> Ast
-> Ast
-> Type
-> Type
-> Either String Type
validateBinaryOp CheckExprFn
checker CheckEnv
env Text
op Ast
left Ast
right Type
inType Type
outType = do
    tLeft <- CheckExprFn
checker CheckEnv
env Ast
left
    tRight <- checker env right
    if areTypesCompatible tLeft inType && areTypesCompatible tRight inType
        then Right outType
        else Left $ binaryOpError op inType tLeft tRight

-- | Formats the error message for binary operators.
binaryOpError :: DT.Text -> Type -> Type -> Type -> String
binaryOpError :: Text -> Type -> Type -> Type -> String
binaryOpError Text
op Type
expected Type
actualLeft Type
actualRight =
    String
"Operator '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
DT.unpack Text
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"' expects (" String -> String -> String
forall a. [a] -> [a] -> [a]
++
    Type -> String
typeToString Type
expected String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
", " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Type -> String
typeToString Type
expected String -> String -> String
forall a. [a] -> [a] -> [a]
++
    String
") but got (" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Type -> String
typeToString Type
actualLeft String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
", " String -> String -> String
forall a. [a] -> [a] -> [a]
++
    Type -> String
typeToString Type
actualRight String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
")"

-- | Validates equality checks (==).
--
-- Unlike other binary ops, equality allows any type as long as
-- both operands have compatible types (e.g. int==int, bool==bool).
checkEqualityOp :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkEqualityOp :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkEqualityOp CheckExprFn
checker CheckEnv
env [Ast]
args =
    case [Ast]
args of
        [Ast
left, Ast
right] -> do
            _ <- CheckExprFn
checker CheckEnv
env Ast
left
            _ <- checker env right
            Right TyBool
        [Ast]
_ -> String -> Either String Type
forall a b. a -> Either a b
Left String
"Equality operator expects 2 arguments"

-- | Validates unary operators (like !).
checkUnaryOp :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
             -> Type -> Type -> Either String Type
checkUnaryOp :: CheckExprFn
-> CheckEnv -> Text -> [Ast] -> Type -> Type -> Either String Type
checkUnaryOp CheckExprFn
checker CheckEnv
env Text
op [Ast]
args Type
inType Type
outType =
    case [Ast]
args of
        [Ast
operand] -> do
            tOp <- CheckExprFn
checker CheckEnv
env Ast
operand
            if areTypesCompatible tOp inType
                then Right outType
                else Left $ "Operator '" ++ DT.unpack op ++ "' expects " ++
                            typeToString inType ++" but got "
                            ++ typeToString tOp
        [Ast]
_ -> String -> Either String Type
forall a b. a -> Either a b
Left (String -> Either String Type) -> String -> Either String Type
forall a b. (a -> b) -> a -> b
$ String
"Operator '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
DT.unpack Text
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"' expects 1 argument"

-- | Validates user-defined function calls.
--
-- Looks up the function in the environment and verifies:
-- The symbol exists and is a function.
-- The argument count matches.
-- Each argument type matches the function definition.
checkUserFunc :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast] -> Either String Type
checkUserFunc :: CheckExprFn -> CheckEnv -> Text -> [Ast] -> Either String Type
checkUserFunc CheckExprFn
checker CheckEnv
env Text
name [Ast]
args =
    case Text -> Map Text Type -> Maybe Type
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
name (CheckEnv -> Map Text Type
envVars CheckEnv
env) of
        Just (TyFunc [Type]
argTypes Type
retType) ->
            CheckExprFn
-> CheckEnv
-> Text
-> [Ast]
-> [Type]
-> Type
-> Either String Type
verifyFuncCall CheckExprFn
checker CheckEnv
env Text
name [Ast]
args [Type]
argTypes Type
retType
        Just Type
_ -> String -> Either String Type
forall a b. a -> Either a b
Left (String -> Either String Type) -> String -> Either String Type
forall a b. (a -> b) -> a -> b
$ String
"'" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
DT.unpack Text
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"' is not a function"
        Maybe Type
Nothing -> String -> Either String Type
forall a b. a -> Either a b
Left (String -> Either String Type) -> String -> Either String Type
forall a b. (a -> b) -> a -> b
$ String
"Undefined function '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
DT.unpack Text
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"'"

-- | Dispatches verification between argument count and argument types.
verifyFuncCall :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
               -> [Type] -> Type -> Either String Type
verifyFuncCall :: CheckExprFn
-> CheckEnv
-> Text
-> [Ast]
-> [Type]
-> Type
-> Either String Type
verifyFuncCall CheckExprFn
checker CheckEnv
env Text
name [Ast]
args [Type]
expected Type
ret
    | [Ast] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Ast]
args Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= [Type] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Type]
expected = Text -> [Type] -> [Ast] -> Either String Type
verifyArgCount Text
name [Type]
expected [Ast]
args
    | Bool
otherwise = CheckExprFn
-> CheckEnv
-> Text
-> [Ast]
-> [Type]
-> Type
-> Either String Type
verifyArgTypes CheckExprFn
checker CheckEnv
env Text
name [Ast]
args [Type]
expected Type
ret

-- | Checks if the number of arguments matches the definition.
verifyArgCount :: DT.Text -> [Type] -> [Ast] -> Either String Type
verifyArgCount :: Text -> [Type] -> [Ast] -> Either String Type
verifyArgCount Text
name [Type]
expected [Ast]
args =
    String -> Either String Type
forall a b. a -> Either a b
Left (String -> Either String Type) -> String -> Either String Type
forall a b. (a -> b) -> a -> b
$ String
"Function '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
DT.unpack Text
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"' expects " String -> String -> String
forall a. [a] -> [a] -> [a]
++
           Int -> String
forall a. Show a => a -> String
show ([Type] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Type]
expected) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" arguments but got "String -> String -> String
forall a. [a] -> [a] -> [a]
++
           Int -> String
forall a. Show a => a -> String
show ([Ast] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Ast]
args)

-- | Checks if the types of arguments match the definition.
verifyArgTypes :: CheckExprFn -> CheckEnv -> DT.Text -> [Ast]
               -> [Type] -> Type -> Either String Type
verifyArgTypes :: CheckExprFn
-> CheckEnv
-> Text
-> [Ast]
-> [Type]
-> Type
-> Either String Type
verifyArgTypes CheckExprFn
checker CheckEnv
env Text
name [Ast]
args [Type]
expected Type
ret = do
    checked <- (Ast -> Either String Type) -> [Ast] -> Either String [Type]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (CheckExprFn
checker CheckEnv
env) [Ast]
args
    let errors = ((Type, Type) -> Bool) -> [(Type, Type)] -> [(Type, Type)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(Type
expect, Type
act) -> Bool -> Bool
not (Type -> Type -> Bool
areTypesCompatible Type
expect Type
act))
                        ([Type] -> [Type] -> [(Type, Type)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Type]
expected [Type]
checked)
    if null errors
        then Right ret
        else Left $ "Argument type mismatch in call to '" ++
            DT.unpack name ++ "'"

-- | Validates 'typeof(arg)'.
--
-- Introspection function that returns the type of the argument as a string.
-- It accepts any valid expression type.
--
-- @param arg: Any expression.
-- @return: String (TyList TyInt) representing the type name (e.g., "int", "bool").
checkTypeof :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkTypeof :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkTypeof CheckExprFn
checker CheckEnv
env [Ast
arg] = do
    _ <- CheckExprFn
checker CheckEnv
env Ast
arg
    Right (TyList TyInt)
checkTypeof CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"typeof expects 1 argument"

-- | Validates 'ffread(path)'.
--
-- High-level File Read: Reads the entire content of a file.
--
-- @param path: String (TyList TyInt) representing the file path.
-- @return: [String] (TyList (TyList TyInt)), where each element is a line.
checkFFRead :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkFFRead :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkFFRead CheckExprFn
checker CheckEnv
env [Ast
path] = do
    tPath <- CheckExprFn
checker CheckEnv
env Ast
path
    case tPath of
        TyList Type
TyInt -> Type -> Either String Type
forall a b. b -> Either a b
Right (Type -> Type
TyList (Type -> Type
TyList Type
TyInt))
        Type
_ -> String -> Either String Type
forall a b. a -> Either a b
Left String
"ffread expects a string (path) as argument"
checkFFRead CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"ffread expects 1 argument"

-- | Validates 'ffwrite(path, content)'.
--
-- High-level File Write: Writes a list of strings to a file.
--
-- @param path: String (TyList TyInt) representing the file path.
-- @param content: [String] (TyList (TyList TyInt)) the lines to write.
-- @return: Bool (TyBool) indicating success or failure.
checkFFWrite :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkFFWrite :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkFFWrite CheckExprFn
checker CheckEnv
env [Ast
path, Ast
content] = do
    tPath <- CheckExprFn
checker CheckEnv
env Ast
path
    tContent <- checker env content
    
    case (tPath, tContent) of
        (TyList Type
TyInt, TyList (TyList Type
TyInt)) -> Type -> Either String Type
forall a b. b -> Either a b
Right Type
TyBool
        (Type, Type)
_ -> String -> Either String Type
forall a b. a -> Either a b
Left String
"ffwrite expects (path: string, content: [string])"
checkFFWrite CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"ffwrite expects 2 arguments"

-- | Validates 'open(path, mode)'.
--
-- Low-level Open: Opens a file and returns a file descriptor.
--
-- @param path: String (TyList TyInt) representing the file path.
-- @param mode: Int (TyInt) representing the access mode (e.g., 0 for read, 1 for write).
-- @return: Int (TyInt) the file descriptor (FD).
checkOpen :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkOpen :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkOpen CheckExprFn
checker CheckEnv
env [Ast
path, Ast
mode] = do
    tPath <- CheckExprFn
checker CheckEnv
env Ast
path
    tMode <- checker env mode
    if areTypesCompatible (TyList TyInt)
            tPath && areTypesCompatible TyInt tMode
        then Right TyInt
        else Left "open expects (path: string, mode: int)"
checkOpen CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"open expects 2 arguments"

-- | Validates 'close(fd)'.
--
-- Low-level Close: Closes an open file descriptor.
--
-- @param fd: Int (TyInt) the file descriptor to close.
-- @return: Int (TyInt) usually 0 for success.
checkClose :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkClose :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkClose CheckExprFn
checker CheckEnv
env [Ast
fd] = do
    tFd <- CheckExprFn
checker CheckEnv
env Ast
fd
    if areTypesCompatible TyInt tFd
        then Right TyInt
        else Left "close expects an integer file descriptor"
checkClose CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"close expects 1 argument"

-- | Validates 'read(fd, size)'.
--
-- Low-level Read: Reads a specific number of bytes/characters from a file descriptor.
--
-- @param fd: Int (TyInt) the file descriptor to read from.
-- @param size: Int (TyInt) the number of characters to read.
-- @return: String (TyList TyInt) containing the read data.
checkRead :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkRead :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkRead CheckExprFn
checker CheckEnv
env [Ast
fd, Ast
size] = do
    tFd <- CheckExprFn
checker CheckEnv
env Ast
fd
    tSize <- checker env size
    if areTypesCompatible TyInt tFd && areTypesCompatible TyInt tSize
        then Right (TyList TyInt)
        else Left "read expects (fd: int, size: int)"
checkRead CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"read expects 2 arguments"

-- | Validates 'input(fd)'.
--
-- Stream Input: Reads a line from a file descriptor (often used with 0 for stdin).
--
-- @param fd: Int (TyInt) the file descriptor to read from.
-- @return: String (TyList TyInt) containing the input line.
checkInput :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkInput :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkInput CheckExprFn
checker CheckEnv
env [Ast
fd] = do
    tFd <- CheckExprFn
checker CheckEnv
env Ast
fd
    if areTypesCompatible TyInt tFd
        then Right (TyList TyInt)
        else Left "input expects an integer file descriptor"
checkInput CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"input expects 1 argument"

-- | Validates 'write(fd, content)'.
--
-- Low-level Write: Writes content to a file descriptor.
--
-- @param fd: Int (TyInt) the file descriptor.
-- @param content: String/List ([char] or [int]) the data to write.
-- @return: Int (TyInt) usually the number of bytes written.
checkWrite :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkWrite :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkWrite CheckExprFn
checker CheckEnv
env [Ast
fd, Ast
content] = do
    tFd <- CheckExprFn
checker CheckEnv
env Ast
fd
    tContent <- checker env content
    unless (areTypesCompatible TyInt tFd) $ 
        Left "write expects an integer file descriptor (int) as first argument"
    case tContent of
        TyList Type
_ -> Type -> Either String Type
forall a b. b -> Either a b
Right Type
TyInt
        Type
_ -> String -> Either String Type
forall a b. a -> Either a b
Left String
"write expects a string or list ([char]) as second argument"
checkWrite CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"write expects 2 arguments (fd: int, content: [char])"

-- | Validates 'sizeof(arg)'.
--
-- Returns the size (or length) of the argument.
-- Accepts any type. Returns an Integer.
--
-- @param arg: Any expression.
-- @return: Int (TyInt).
checkSizeof :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkSizeof :: CheckExprFn -> CheckEnv -> [Ast] -> Either String Type
checkSizeof CheckExprFn
checker CheckEnv
env [Ast
arg] = do
    _ <- CheckExprFn
checker CheckEnv
env Ast
arg
    Right TyInt
checkSizeof CheckExprFn
_ CheckEnv
_ [Ast]
_ = String -> Either String Type
forall a b. a -> Either a b
Left String
"sizeof expects 1 argument"