{-
-- EPITECH PROJECT, 2023
-- GLaDOS
-- File description:
-- Environment
-}

module Scope
  ( ScopeMb (..),
    beginScope,
    clearScope,
    addVarToScope,
    getVarInScope,
    addVarsToScope,
    updateVar,
  )
where

import AST
import Stack

-- | Structure representing a member in a scope.
-- Can be a 'ScopeBegin' to mark the beginning of a scope or
-- a 'Variable' containing its name as a 'String' and its value as an 'Ast'
data ScopeMb
  = ScopeBegin Int
  | Variable String Ast Int
  deriving (ScopeMb -> ScopeMb -> Bool
(ScopeMb -> ScopeMb -> Bool)
-> (ScopeMb -> ScopeMb -> Bool) -> Eq ScopeMb
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ScopeMb -> ScopeMb -> Bool
== :: ScopeMb -> ScopeMb -> Bool
$c/= :: ScopeMb -> ScopeMb -> Bool
/= :: ScopeMb -> ScopeMb -> Bool
Eq, Int -> ScopeMb -> ShowS
[ScopeMb] -> ShowS
ScopeMb -> String
(Int -> ScopeMb -> ShowS)
-> (ScopeMb -> String) -> ([ScopeMb] -> ShowS) -> Show ScopeMb
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ScopeMb -> ShowS
showsPrec :: Int -> ScopeMb -> ShowS
$cshow :: ScopeMb -> String
show :: ScopeMb -> String
$cshowList :: [ScopeMb] -> ShowS
showList :: [ScopeMb] -> ShowS
Show)

-- | Get the depth of a member in a scope
getDepth :: [ScopeMb] -> Int
getDepth :: [ScopeMb] -> Int
getDepth [ScopeMb]
s = case [ScopeMb] -> Maybe ScopeMb
forall a. [a] -> Maybe a
top [ScopeMb]
s of
  Just (ScopeBegin Int
depth) -> Int
depth
  Just (Variable String
_ Ast
_ Int
depth) -> Int
depth
  Maybe ScopeMb
Nothing -> -Int
1

-- | Begin a new scope by adding a 'ScopeBegin' to the stack.
beginScope :: [ScopeMb] -> [ScopeMb]
beginScope :: [ScopeMb] -> [ScopeMb]
beginScope [ScopeMb]
s = [ScopeMb] -> ScopeMb -> [ScopeMb]
forall a. [a] -> a -> [a]
push [ScopeMb]
s (Int -> ScopeMb
ScopeBegin ([ScopeMb] -> Int
getDepth [ScopeMb]
s Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1))

-- | Clear the current scope
clearScope :: [ScopeMb] -> [ScopeMb]
clearScope :: [ScopeMb] -> [ScopeMb]
clearScope [] = []
clearScope [ScopeMb]
s = case [ScopeMb] -> (Maybe ScopeMb, [ScopeMb])
forall a. [a] -> (Maybe a, [a])
pop [ScopeMb]
s of
  (Maybe ScopeMb
Nothing, [ScopeMb]
s') -> [ScopeMb]
s'
  (Just (ScopeBegin Int
_), [ScopeMb]
s') -> [ScopeMb]
s'
  (Just ScopeMb
_, [ScopeMb]
s') -> [ScopeMb] -> [ScopeMb]
clearScope [ScopeMb]
s'

-- | Add a Variable to the stack with its name as a 'String'
-- and its value by an 'Ast'
addVarToScope :: [ScopeMb] -> String -> Ast -> [ScopeMb]
addVarToScope :: [ScopeMb] -> String -> Ast -> [ScopeMb]
addVarToScope [ScopeMb]
stack String
s Ast
v = [ScopeMb] -> ScopeMb -> [ScopeMb]
forall a. [a] -> a -> [a]
push [ScopeMb]
stack (String -> Ast -> Int -> ScopeMb
Variable String
s Ast
v (case [ScopeMb] -> Int
getDepth [ScopeMb]
stack of
  -1 -> Int
0
  Int
depth -> Int
depth
  ))

-- | Add multiple variables to the stack with their names as a 'String'
-- and their values as an 'Ast'
addVarsToScope :: [ScopeMb] -> [String] -> [Ast] -> [ScopeMb]
addVarsToScope :: [ScopeMb] -> [String] -> [Ast] -> [ScopeMb]
addVarsToScope [ScopeMb]
stack [] [Ast]
_ = [ScopeMb]
stack
addVarsToScope [ScopeMb]
stack [String]
_ [] = [ScopeMb]
stack
addVarsToScope [ScopeMb]
stack (String
s : [String]
xs1) (Ast
v : [Ast]
xs2) =
  [ScopeMb] -> ScopeMb -> [ScopeMb]
forall a. [a] -> a -> [a]
push
    ([ScopeMb] -> [String] -> [Ast] -> [ScopeMb]
addVarsToScope [ScopeMb]
stack [String]
xs1 [Ast]
xs2)
    (String -> Ast -> Int -> ScopeMb
Variable String
s Ast
v ([ScopeMb] -> Int
getDepth [ScopeMb]
stack))

seekAndUpdate :: [ScopeMb] -> Int -> String -> Ast -> [ScopeMb]
seekAndUpdate :: [ScopeMb] -> Int -> String -> Ast -> [ScopeMb]
seekAndUpdate (ScopeMb
h:[ScopeMb]
xs) Int
depth String
name Ast
ast
  | String -> Int -> ScopeMb -> Bool
isSearchedVar String
name Int
depth ScopeMb
h = String -> Ast -> Int -> ScopeMb
Variable String
name Ast
ast Int
depth ScopeMb -> [ScopeMb] -> [ScopeMb]
forall a. a -> [a] -> [a]
: [ScopeMb]
xs
  | Bool
otherwise = ScopeMb
h ScopeMb -> [ScopeMb] -> [ScopeMb]
forall a. a -> [a] -> [a]
: [ScopeMb] -> Int -> String -> Ast -> [ScopeMb]
seekAndUpdate [ScopeMb]
xs Int
depth String
name Ast
ast
seekAndUpdate [] Int
_ String
_ Ast
_ = []

-- | Update the given variable, don't do anything if it doesn't exist
updateVar :: [ScopeMb] -> String -> Ast -> [ScopeMb]
updateVar :: [ScopeMb] -> String -> Ast -> [ScopeMb]
updateVar [ScopeMb]
stack = [ScopeMb] -> Int -> String -> Ast -> [ScopeMb]
seekAndUpdate [ScopeMb]
stack ([ScopeMb] -> Int
getDepth [ScopeMb]
stack)

-- | Get the value contained in the variable given by name as a 'String',
-- return 'Nothing' if the variable don't exist or 'Just' its value
getVarInScope :: [ScopeMb] -> String -> Maybe Ast
getVarInScope :: [ScopeMb] -> String -> Maybe Ast
getVarInScope [ScopeMb]
stack String
s =
  ScopeMb -> Maybe Ast
getAst (ScopeMb -> Maybe Ast) -> Maybe ScopeMb -> Maybe Ast
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (ScopeMb -> Bool) -> [ScopeMb] -> Maybe ScopeMb
forall a. (a -> Bool) -> [a] -> Maybe a
seek (String -> Int -> ScopeMb -> Bool
isSearchedVar String
s ([ScopeMb] -> Int
getDepth [ScopeMb]
stack)) [ScopeMb]
stack

-- | Get the 'Ast' at a given 'ScopeMb'
getAst :: ScopeMb -> Maybe Ast
getAst :: ScopeMb -> Maybe Ast
getAst (Variable String
_ Ast
ast Int
_) = Ast -> Maybe Ast
forall a. a -> Maybe a
Just Ast
ast
getAst ScopeMb
_ = Maybe Ast
forall a. Maybe a
Nothing

-- | Return 'True' if the 'ScopeMb' is a Variable
-- by the name passed as parameter and if the variable is in the current scope
-- or global scope
isSearchedVar :: String -> Int -> ScopeMb -> Bool
isSearchedVar :: String -> Int -> ScopeMb -> Bool
isSearchedVar String
s2 Int
currentDepth (Variable String
s1 Ast
_ Int
depth) =
  String
s1 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
s2 Bool -> Bool -> Bool
&& (Int
depth Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 Bool -> Bool -> Bool
|| Int
depth Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
currentDepth)
isSearchedVar String
_ Int
_ ScopeMb
_ = Bool
False