{- 
-- EPITECH PROJECT, 2025
-- Glados
-- File description:
-- Lexer.hs
-}

{-|
Module      : Parser.Lexer
Description : Lexical analysis tools and basic parsers.
Stability   : stable

This module defines the basic building blocks for the parser, including:
- Space consumers (handling whitespace and comments)
- Lexeme wrappers
- Symbol and keyword parsers
- Reserved words management
-}
module Parser.Lexer (
    Parser,
    sc,
    lexeme,
    symbol,
    pKeyword,
    pIdentifier,
    parens,
    braces,
    semicolon,
    comma,
    colon,
    withPos
) where

import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
import Data.Void
import qualified Data.Text as DT
import AST.Ast (Ast(..))

-- | The standard Parser type for this project.
--
-- It uses Void as the custom error component (no custom errors)
-- and DT.Text as the input stream type.
type Parser = Parsec Void DT.Text

-- | Space Consumer.
--
-- Consumes whitespace and ignores comments.
-- Supported comment styles:
--
-- * Line comments starting with //
-- * Block comments enclosed in /* ... */
sc :: Parser ()
sc :: Parser ()
sc = Parser () -> Parser () -> Parser () -> Parser ()
forall e s (m :: * -> *).
MonadParsec e s m =>
m () -> m () -> m () -> m ()
L.space
    Parser ()
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
m ()
space1
    (Tokens Text -> Parser ()
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Tokens s -> m ()
L.skipLineComment (String -> Text
DT.pack String
"//"))
    (Tokens Text -> Tokens Text -> Parser ()
forall e s (m :: * -> *).
MonadParsec e s m =>
Tokens s -> Tokens s -> m ()
L.skipBlockComment (String -> Text
DT.pack String
"/*") (String -> Text
DT.pack String
"*/"))

-- | Wrapper for lexemes.
--
-- Consumes trailing whitespace after parsing the given parser 'p'.
-- This is essential for the "trailing whitespace" convention used in this parser.
lexeme :: Parser a -> Parser a
lexeme :: forall a. Parser a -> Parser a
lexeme = Parser ()
-> ParsecT Void Text Identity a -> ParsecT Void Text Identity a
forall e s (m :: * -> *) a. MonadParsec e s m => m () -> m a -> m a
L.lexeme Parser ()
sc

-- | Parse a fixed string as a symbol.
--
-- Consumes the string exactly as matches, followed by whitespace.
symbol :: DT.Text -> Parser DT.Text
symbol :: Text -> Parser Text
symbol = Parser ()
-> Tokens Text -> ParsecT Void Text Identity (Tokens Text)
forall e s (m :: * -> *).
MonadParsec e s m =>
m () -> Tokens s -> m (Tokens s)
L.symbol Parser ()
sc

-- | Parse content between parentheses (...).
parens :: Parser a -> Parser a
parens :: forall a. Parser a -> Parser a
parens = Parser Text
-> Parser Text
-> ParsecT Void Text Identity a
-> ParsecT Void Text Identity a
forall (m :: * -> *) open close a.
Applicative m =>
m open -> m close -> m a -> m a
between (Text -> Parser Text
symbol (String -> Text
DT.pack String
"(")) (Text -> Parser Text
symbol (String -> Text
DT.pack String
")"))

-- | Parse content between braces {...}.
braces :: Parser a -> Parser a
braces :: forall a. Parser a -> Parser a
braces = Parser Text
-> Parser Text
-> ParsecT Void Text Identity a
-> ParsecT Void Text Identity a
forall (m :: * -> *) open close a.
Applicative m =>
m open -> m close -> m a -> m a
between (Text -> Parser Text
symbol (String -> Text
DT.pack String
"{")) (Text -> Parser Text
symbol (String -> Text
DT.pack String
"}"))

-- | Parse a semicolon ; symbol.
semicolon :: Parser DT.Text
semicolon :: Parser Text
semicolon = Text -> Parser Text
symbol (String -> Text
DT.pack String
";")

-- | Parse a comma , symbol.
comma :: Parser DT.Text
comma :: Parser Text
comma = Text -> Parser Text
symbol (String -> Text
DT.pack String
",")

-- | Parse a colon : symbol.
colon :: Parser DT.Text
colon :: Parser Text
colon = Text -> Parser Text
symbol (String -> Text
DT.pack String
":")

-- | Wraps a parser with position information.
--
-- Captures the current source position (line, column) before running the parser 'p',
-- and wraps the resulting AST node in an 'APos' constructor.
-- This allows error reporting to pinpoint the exact location of semantic errors later.
--
-- @param Parser Ast The parser to wrap.
-- @return Parser Ast A parser that returns an APos node containing the original result.
withPos :: Parser Ast -> Parser Ast
withPos :: Parser Ast -> Parser Ast
withPos Parser Ast
p = do
    pos <- ParsecT Void Text Identity SourcePos
forall s e (m :: * -> *).
(TraversableStream s, MonadParsec e s m) =>
m SourcePos
getSourcePos
    node <- p
    return (APos (unPos (sourceLine pos)) (unPos (sourceColumn pos)) node)

-- | List of reserved keywords in the language.
--
-- These words cannot be used as variable or function identifiers.
reservedWords :: [DT.Text]
reservedWords :: [Text]
reservedWords = (String -> Text) -> [String] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map String -> Text
DT.pack [String
"func", String
"ret", String
"if", String
"else", String
"int",
        String
"bool", String
"void", String
"while", String
"lambda", String
"break"]

-- | Parse a specific keyword.
--
-- Ensures that the keyword is not followed by an alphanumeric character
-- to prevent partial matches (e.g., matching "if" inside "ifelse").
pKeyword :: DT.Text -> Parser DT.Text
pKeyword :: Text -> Parser Text
pKeyword Text
w = Parser Text -> Parser Text
forall a. Parser a -> Parser a
lexeme (Tokens Text -> ParsecT Void Text Identity (Tokens Text)
forall e s (m :: * -> *).
MonadParsec e s m =>
Tokens s -> m (Tokens s)
string Text
Tokens Text
w Parser Text -> Parser () -> Parser Text
forall a b.
ParsecT Void Text Identity a
-> ParsecT Void Text Identity b -> ParsecT Void Text Identity a
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* ParsecT Void Text Identity Char -> Parser ()
forall a. ParsecT Void Text Identity a -> Parser ()
forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m ()
notFollowedBy ParsecT Void Text Identity Char
ParsecT Void Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
m (Token s)
alphaNumChar)

-- | Parse a valid identifier.
--
-- An identifier must start with a letter or underscore, followed by
-- alphanumeric characters or underscores.
--
-- It fails if the parsed identifier is one of the 'reservedWords'.
pIdentifier :: Parser DT.Text
pIdentifier :: Parser Text
pIdentifier = (Parser Text -> Parser Text
forall a. Parser a -> Parser a
lexeme (Parser Text -> Parser Text) -> Parser Text -> Parser Text
forall a b. (a -> b) -> a -> b
$ do
    first <- ParsecT Void Text Identity Char
ParsecT Void Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
m (Token s)
letterChar ParsecT Void Text Identity Char
-> ParsecT Void Text Identity Char
-> ParsecT Void Text Identity Char
forall a.
ParsecT Void Text Identity a
-> ParsecT Void Text Identity a -> ParsecT Void Text Identity a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Token Text -> ParsecT Void Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
'_'
    rest <- many (alphaNumChar <|> char '_')
    let name = String -> Text
DT.pack (Char
first Char -> String -> String
forall a. a -> [a] -> [a]
: String
rest)
    if name `elem` reservedWords
        then fail $
            "Keyword '" ++ DT.unpack name ++ "' cannot be an identifier"
        else return name) Parser Text -> String -> Parser Text
forall e s (m :: * -> *) a.
MonadParsec e s m =>
m a -> String -> m a
<?> String
"identifier"