/* Lexical analyzer.
   Copyright (C) 2025 Free Software Foundation, Inc.

GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

%{
#define YY_NO_UNISTD_H
#define YY_USER_ACTION this->location.columns(yyleng);

#include <sstream>
#include "parser.hh"

#undef YY_DECL
#define YY_DECL yy::parser::symbol_type elna::boot::lexer::lex(elna::boot::driver& driver)
#define yyterminate() return yy::parser::make_YYEOF(this->location)
%}

%option c++ noyywrap never-interactive
%option yyclass="elna::boot::lexer"

%x IN_COMMENT

%%
%{
    this->location.step();
%}

<IN_COMMENT>{
\*\)                    BEGIN(INITIAL);
[^*\n]+                 ; /* Eat comment in chunks. */
\*                      ; /* Eat the lone star. */
\n+                     {
                            this->location.lines(yyleng);
                            this->location.step();
                        }
}
\(\*                    BEGIN(IN_COMMENT);
[ \t\r]                 {
                            this->location.step();
                        }
\n+                     {
                            this->location.lines(yyleng);
                        }
if                      {
                            return yy::parser::make_IF(this->location);
                        }
then                    {
                            return yy::parser::make_THEN(this->location);
                        }
else                    {
                            return yy::parser::make_ELSE(this->location);
                        }
elsif                   {
                            return yy::parser::make_ELSIF(this->location);
                        }
while                   {
                            return yy::parser::make_WHILE(this->location);
                        }
do                      {
                            return yy::parser::make_DO(this->location);
                        }
proc                    {
                            return yy::parser::make_PROCEDURE(this->location);
                        }
begin                   {
                            return yy::parser::make_BEGIN_BLOCK(this->location);
                        }
end                     {
                            return yy::parser::make_END_BLOCK(this->location);
                        }
extern                  {
                            return yy::parser::make_EXTERN(this->location);
                        }
const                   {
                            return yy::parser::make_CONST(this->location);
                        }
var                     {
                            return yy::parser::make_VAR(this->location);
                        }
type                    {
                            return yy::parser::make_TYPE(this->location);
                        }
record                  {
                            return yy::parser::make_RECORD(this->location);
                        }
union                   {
                            return yy::parser::make_UNION(this->location);
                        }
true                    {
                            return yy::parser::make_BOOLEAN(true, this->location);
                        }
false                   {
                            return yy::parser::make_BOOLEAN(false, this->location);
                        }
nil                     {
                            return yy::parser::make_NIL(this->location);
                        }
and                     {
                            return yy::parser::make_AND(this->location);
                        }
xor                     {
                            return yy::parser::make_XOR(this->location);
                        }
or                      {
                            return yy::parser::make_OR(this->location);
                        }
not                     {
                            return yy::parser::make_NOT(this->location);
                        }
return                  {
                            return yy::parser::make_RETURN(this->location);
                        }
cast                    {
                            return yy::parser::make_CAST(this->location);
                        }
defer                   {
                            return yy::parser::make_DEFER(this->location);
                        }
[A-Za-z_][A-Za-z0-9_]*  {
                            return yy::parser::make_IDENTIFIER(yytext, this->location);
                        }
#[A-Za-z_][A-Za-z0-9_]* {
                            return yy::parser::make_TRAIT(yytext + 1, this->location);
                        }
[0-9]+u                 {
                            return yy::parser::make_WORD(strtoul(yytext, NULL, 10), this->location);
                        }
[0-9]+                  {
                            return yy::parser::make_INTEGER(strtol(yytext, NULL, 10), this->location);
                        }
[0-9]+\.[0-9]           {
                            return yy::parser::make_FLOAT(strtof(yytext, NULL), this->location);
                        }
'[[:print:]]'           {
                            if (yytext[1] == '\\' || yytext[1] == '\'')
                            {
                                REJECT;
                            }
                            else
                            {
                                return yy::parser::make_CHARACTER(std::string(yytext, 1, 1), this->location);
                            }
                        }
'\\x[0-9a-fA-F]{1,2}'   {
                            char character = static_cast<char>(std::stoi(yytext + 3, nullptr, 16));

                            return yy::parser::make_CHARACTER(std::string(&character, 1), this->location);
                        }
'\\[0nabtfrv\\'"?]'     {
                            char escape = elna::boot::escape_char(yytext[2]);
                            if (escape == escape_invalid_char)
                            {
                                REJECT;
                            }
                            return yy::parser::make_CHARACTER(std::string(&escape, 1), this->location);
                        }
\"[[:print:]]*\"        {
                            std::string result;
                            const char *current_position = yytext + 1;

                            while (*current_position != '\0')
                            {
                                if (*current_position == '\\' && *(current_position + 1) == 'x')
                                {
                                    current_position += 2;

                                    std::size_t processed;
                                    char character = static_cast<char>(std::stoi(current_position, &processed, 16));
                                    if (processed == 0)
                                    {
                                        REJECT;
                                    }
                                    else
                                    {
                                        current_position += processed - 1;
                                        result.push_back(character);
                                    }
                                }
                                else if (*current_position == '\\')
                                {
                                    ++current_position;

                                    char escape = elna::boot::escape_char(*current_position);
                                    if (escape == elna::boot::escape_invalid_char)
                                    {
                                        REJECT;
                                    }
                                    result.push_back(escape);
                                }
                                else
                                {
                                    result.push_back(*current_position);
                                }
                                ++current_position;
                            }
                            result.pop_back();
                            return yy::parser::make_STRING(result, this->location);
                        }
\(                      {
                            return yy::parser::make_LEFT_PAREN(this->location);
                        }
\)                      {
                            return yy::parser::make_RIGHT_PAREN(this->location);
                        }
\[                      {
                            return yy::parser::make_LEFT_SQUARE(this->location);
                        }
\]                      {
                            return yy::parser::make_RIGHT_SQUARE(this->location);
                        }
\<\<                    {
                            return yy::parser::make_SHIFT_LEFT(this->location);
                        }
\>\>                    {
                            return yy::parser::make_SHIFT_RIGHT(this->location);
                        }
\>=                     {
                            return yy::parser::make_GREATER_EQUAL(this->location);
                        }
\<=                     {
                            return yy::parser::make_LESS_EQUAL(this->location);
                        }
\>                      {
                            return yy::parser::make_GREATER_THAN(this->location);
                        }
\<                      {
                            return yy::parser::make_LESS_THAN(this->location);
                        }
\<\>                    {
                            return yy::parser::make_NOT_EQUAL(this->location);
                        }
=                       {
                            return yy::parser::make_EQUALS(this->location);
                        }
;                       {
                            return yy::parser::make_SEMICOLON(this->location);
                        }
\.                      {
                            return yy::parser::make_DOT(this->location);
                        }
,                       {
                            return yy::parser::make_COMMA(this->location);
                        }
\+                      {
                            return yy::parser::make_PLUS(this->location);
                        }
\->                     {
                            return yy::parser::make_ARROW(this->location);
                        }
\-                      {
                            return yy::parser::make_MINUS(this->location);
                        }
\*                      {
                            return yy::parser::make_MULTIPLICATION(this->location);
                        }
\/                      {
                            return yy::parser::make_DIVISION(this->location);
                        }
%                       {
                            return yy::parser::make_REMAINDER(this->location);
                        }
:=                      {
                            return yy::parser::make_ASSIGNMENT(this->location);
                        }
:                       {
                            return yy::parser::make_COLON(this->location);
                        }
\^                      {
                            return yy::parser::make_HAT(this->location);
                        }
@                       {
                            return yy::parser::make_AT(this->location);
                        }
!                       {
                            return yy::parser::make_EXCLAMATION(this->location);
                        }
.                       {
                            std::stringstream ss;

                            ss << "Illegal character 0x" << std::hex << static_cast<unsigned int>(yytext[0]);
                            driver.error(this->location, ss.str());
                        }
%%