%{
/*
* ===========================================================================
* Copyright (C) 2025 the OpenMoHAA team
* 
* This file is part of OpenMoHAA source code.
* 
* OpenMoHAA source code 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 2 of the License,
* or (at your option) any later version.
* 
* OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
* ===========================================================================
*
*
* yyLexer.*: FLEX Lexical grammar for MoHScript.
*/

#include "scriptcompiler.h"
#include "./yyParser.hpp"

#include <stdio.h>

void fprintf2(FILE * f, const char *format, ...)
{
    va_list     va;
    static char buffer[4200];

    va_start(va, format);
    vsprintf(buffer, format, va);
    va_end(va);

    gi.Printf(buffer);
}

#define fprintf fprintf2

const char  *start_ptr;
const char  *in_ptr;
extern int   prev_yylex;
extern int   out_pos;
extern int   success_pos;
parseStage_e parseStage;

extern yyparsedata parsedata;

void yyllocset(YYLTYPE * loc, uint32_t off)
{
    success_pos    = out_pos - yyleng + off;
    loc->sourcePos = success_pos;
    parsedata.pos  = success_pos;
}

void yyreducepos(uint32_t off)
{
    out_pos -= off;
}

#define YYLEX(n)               \
    {                          \
        yyllocset(&yylloc, 0); \
        prev_yylex = n;        \
        return n;              \
    }
#define YYLEXOFF(n, off)         \
    {                            \
        yyllocset(&yylloc, off); \
        prev_yylex = n;          \
        return n;                \
    }

#define YY_USER_ACTION                   \
    {                                    \
        out_pos += yyleng - yy_more_len; \
        yylloc.sourcePos = out_pos;      \
        parsedata.pos    = out_pos;      \
    }

#define YY_FATAL_ERROR(n) yylexerror(n)

void yylexerror(const char *msg)
{
    gi.DPrintf("%s\n%s", msg, yytext);
    assert(0);
}

static void TextEscapeValue(char *str, size_t len)
{
    char *to = parsetree_malloc(len + 1);

    yylval.s.val.stringValue = to;

    while (len) {
        if (*str == '\\') {
            if (len == 1) {
                break;
            }

            if (str[1] == 'n') {
                *to = '\n';
                to++;
            } else if (str[1] == 't') {
                *to = '\t';
                to++;
            } else if (str[1] == '"') {
                *to = '\"';
                to++;
            } else {
                *to = str[1];
                to++;
            }

            len -= 2;
            str += 2;
        } else {
            *to = *str;
            to++;
            len--;
            str++;
        }
    }

    *to = 0;
}

static void TextValue(char *str, size_t len)
{
    char *s = parsetree_malloc(len + 1);
    strncpy(s, str, len);
    s[len]                   = 0;
    yylval.s.val.stringValue = s;
}

static bool UseField(void)
{
    return prev_yylex == TOKEN_PERIOD || prev_yylex == TOKEN_DOLLAR;
}

#define YY_INPUT(buf, result, max_size)  \
    {                                    \
        char c;                          \
        int  n;                          \
                                         \
        c = '*';                         \
        for (n = 0; n < max_size; n++) { \
            c = *in_ptr++;               \
            if (!c || c == '\n') {       \
                break;                   \
            }                            \
                                         \
            buf[n] = c;                  \
        }                                \
                                         \
        if (c == '\n') {                 \
            buf[n++] = c;                \
        } else if (!c) {                 \
            in_ptr--;                    \
        }                                \
                                         \
        result = n;                      \
    }

%}

/*%option debug*/

%option warn nodefault

%option never-interactive
%option yylineno

%x SCRIPT
%x C_COMMENT
%x C_LINE_COMMENT
%x VARIABLES
%x IDENTIFIER

string          ([^\\\"\r\n]|\\.)*
identifier      [^\{\}\(\)\[\]\r\n\,:; \t]
nonexpr         [0-9a-zA-Z_\"'?@#`\.\x80-\xff]
nonnumeric      [a-zA-Z_\"'?@#`\x80-\xff]
alphanum        [a-zA-Z0-9_]+
varname         [a-zA-Z0-9_\"$\\]+

%%

"/*"                            { BEGIN(C_COMMENT); }
<C_COMMENT>"*/"                 { BEGIN(INITIAL); }
<C_COMMENT>\n                   { ; }
<C_COMMENT>.                    { ; }
"*/"                            { Compiler.CompileError( parsedata.pos - yyleng, "'*/' found outside of comment" ); }

\\[\r\n]+                       { ; }
"//"[^\r\n]*                    { if(prev_yylex != TOKEN_EOL) YYLEX(TOKEN_EOL); }

<VARIABLES>"size"                           { BEGIN(INITIAL); YYLEX(TOKEN_SIZE); }
<VARIABLES>[ \t]*\./([0-9]*[^0-9[:space:]]) { YYLEX(TOKEN_PERIOD); }
<VARIABLES>\"{string}\"                     { BEGIN(INITIAL); TextEscapeValue(yytext + 1, strlen( yytext ) - 2 ); YYLEX(TOKEN_STRING); }
<VARIABLES>{varname}                        {
                                                TextEscapeValue(yytext, strlen(yytext));
                                                YYLEX(TOKEN_IDENTIFIER);
                                            }
<VARIABLES>[ \t\r\n]                        {
                                                BEGIN(INITIAL);
                                                unput(yytext[yyleng - 1]);
                                                yyreducepos(1);
                                            }
<VARIABLES>.                                {
                                                BEGIN(INITIAL);
                                                unput(yytext[yyleng - 1]);
                                                yyreducepos(1);
                                            }

\"{string}\"{nonexpr}           {
                                    BEGIN(IDENTIFIER);
                                    yymore();
                                }

\"{string}\"                    { TextEscapeValue(yytext + 1, yyleng - 2); YYLEX(TOKEN_STRING); }

"?"                             { YYLEX(TOKEN_TERNARY); }
"if"                            { YYLEX(TOKEN_IF); }
"else"                          { YYLEX(TOKEN_ELSE); }
"while"                         { YYLEX(TOKEN_WHILE); }
"for"                           { YYLEX(TOKEN_FOR); }
"do"                            { YYLEX(TOKEN_DO); }

"game"                          { BEGIN(VARIABLES); yylval.s.val = node1_(method_game); YYLEX(TOKEN_LISTENER); }
"group"                         { BEGIN(VARIABLES); yylval.s.val = node1_(method_group); YYLEX(TOKEN_LISTENER); }
"level"                         { BEGIN(VARIABLES); yylval.s.val = node1_(method_level); YYLEX(TOKEN_LISTENER); }
"local"                         { BEGIN(VARIABLES); yylval.s.val = node1_(method_local); YYLEX(TOKEN_LISTENER); }
"parm"                          { BEGIN(VARIABLES); yylval.s.val = node1_(method_parm); YYLEX(TOKEN_LISTENER); }
"owner"                         { BEGIN(VARIABLES); yylval.s.val = node1_(method_owner); YYLEX(TOKEN_LISTENER); }
"self"                          { BEGIN(VARIABLES); yylval.s.val = node1_(method_self); YYLEX(TOKEN_LISTENER); }

"{"                             { parsedata.braces_count++; YYLEX(TOKEN_LEFT_BRACES); }
"}"                             { parsedata.braces_count--; YYLEX(TOKEN_RIGHT_BRACES); }
"("                             { YYLEX(TOKEN_LEFT_BRACKET); }
")"                             { BEGIN(VARIABLES); YYLEX(TOKEN_RIGHT_BRACKET); }
"["                             { YYLEX(TOKEN_LEFT_SQUARE_BRACKET); }
"]"                             { BEGIN(VARIABLES); YYLEX(TOKEN_RIGHT_SQUARE_BRACKET); }

"="                             { YYLEX(TOKEN_ASSIGNMENT); }
":"                             { YYLEX(TOKEN_COLON); }
"::"                            { YYLEX(TOKEN_DOUBLE_COLON); }
";"                             { YYLEX(TOKEN_SEMICOLON); }

"=="                            { YYLEX(TOKEN_EQUALITY); }
"ifequal"                       { YYLEX(TOKEN_EQUALITY); }
"ifstrequal"                    { YYLEX(TOKEN_EQUALITY); }
"||"                            { YYLEX(TOKEN_LOGICAL_OR); }
"&&"                            { YYLEX(TOKEN_LOGICAL_AND); }

"|"                             { YYLEX(TOKEN_BITWISE_OR); }
"^"                             { YYLEX(TOKEN_BITWISE_EXCL_OR); }
"&"                             { YYLEX(TOKEN_BITWISE_AND); }
"!="                            { YYLEX(TOKEN_INEQUALITY); }
"ifnotequal"                    { YYLEX(TOKEN_INEQUALITY); }
"ifstrnotequal"                 { YYLEX(TOKEN_INEQUALITY); }
"<"                             { YYLEX(TOKEN_LESS_THAN); }
"ifless"                        { YYLEX(TOKEN_LESS_THAN); }
">"                             { YYLEX(TOKEN_GREATER_THAN); }
"ifgreater"                     { YYLEX(TOKEN_GREATER_THAN); }
"<="                            { YYLEX(TOKEN_LESS_THAN_OR_EQUAL); }
"iflessequal"                   { YYLEX(TOKEN_LESS_THAN_OR_EQUAL); }
">="                            { YYLEX(TOKEN_GREATER_THAN_OR_EQUAL); }
"ifgreaterequal"                { YYLEX(TOKEN_GREATER_THAN_OR_EQUAL); }
[ \t]"-"                        { YYLEX(TOKEN_NEG); }

"+"                             { YYLEX(TOKEN_PLUS); }
"+="                            { YYLEX(TOKEN_PLUS_EQUALS); }
"++"|[ \t]"++"                  { YYLEX(TOKEN_INCREMENT); }
"-"|"-"[ \t]|[ \t]"-"[ \t]      { YYLEX(TOKEN_MINUS); }
"-="                            { YYLEX(TOKEN_MINUS_EQUALS); }
[ \t]"-="                       { YYLEX(TOKEN_MINUS_EQUALS); }
"--"|[ \t]"--"                  { YYLEX(TOKEN_DECREMENT); }
"*"                             { YYLEX(TOKEN_MULTIPLY); }
"*="                            { YYLEX(TOKEN_MULTIPLY_EQUALS); }
"/"                             { YYLEX(TOKEN_DIVIDE); }
"/="                            { YYLEX(TOKEN_DIVIDE_EQUALS); }
"%"                             { YYLEX(TOKEN_MODULUS); }
"%="                            { YYLEX(TOKEN_MODULUS_EQUALS); }
"<<"                            { YYLEX(TOKEN_SHIFT_LEFT); }
"<<="                           { YYLEX(TOKEN_SHIFT_LEFT_EQUALS); }
">>"                            { YYLEX(TOKEN_SHIFT_RIGHT); }
">>="                           { YYLEX(TOKEN_SHIFT_RIGHT_EQUALS); }
"&="                            { YYLEX(TOKEN_AND_EQUALS); }
"^="                            { YYLEX(TOKEN_EXCL_OR_EQUALS); }
"|="                            { YYLEX(TOKEN_OR_EQUALS); }
[$]+                            { BEGIN( VARIABLES ); YYLEX(TOKEN_DOLLAR); }
"!"                             { YYLEX(TOKEN_NOT); }
"~"                             { YYLEX(TOKEN_COMPLEMENT); }

"."                             { YYLEX(TOKEN_PERIOD); }

","                             { YYLEX(TOKEN_COMMA); }

"NULL"                          { YYLEX(TOKEN_NULL); }
"NIL"                           { YYLEX(TOKEN_NIL); }

"try"                           { YYLEX(TOKEN_TRY); }
"catch"                         { YYLEX(TOKEN_CATCH); }
"switch"                        { YYLEX(TOKEN_SWITCH); }

"case"                          { YYLEX(TOKEN_CASE); }
"break"                         { YYLEX(TOKEN_BREAK); }
"continue"                      { YYLEX(TOKEN_CONTINUE); }

"makearray"|"makeArray"         { YYLEX(TOKEN_MAKEARRAY); }
"endarray"|"endArray"           { YYLEX(TOKEN_ENDARRAY); }

[\r\n]+                         { if (prev_yylex != TOKEN_EOL) YYLEX(TOKEN_EOL); }
[ \t]                           { ; }

[0-9]+                                  {
                                            char* p = nullptr;
                                            yylval.s.val.intValue = std::strtol(yytext, &p, 10);
                                            YYLEX(TOKEN_INTEGER);
                                        }

[0-9\.]+{nonnumeric}                    {
                                            BEGIN(IDENTIFIER);
                                            yymore();
                                        }

[0-9\.]+|[0-9\.]+("e+"|"e-")+[0-9\.]    {
                                            char* p = nullptr;
                                            yylval.s.val.floatValue = std::strtof(yytext, &p);
                                            YYLEX(TOKEN_FLOAT);
                                        }

<IDENTIFIER>{identifier}*               {
                                            BEGIN(INITIAL);
                                            TextEscapeValue(yytext, yyleng);
                                            YYLEX(TOKEN_IDENTIFIER);
                                        }
<IDENTIFIER>[ \t\r\n]                   {
                                            BEGIN(INITIAL);
                                            unput(yytext[yyleng - 1]);
                                            yyreducepos(1);
                                            TextEscapeValue(yytext, yyleng - 1);
                                            YYLEXOFF(TOKEN_IDENTIFIER, 1);
                                        }
<IDENTIFIER>.                           {
                                            BEGIN(INITIAL);
                                            unput(yytext[yyleng - 1]);
                                            yyreducepos(1);
                                            TextEscapeValue(yytext, yyleng - 1);
                                            YYLEXOFF(TOKEN_IDENTIFIER, 1);
                                        }

{identifier}                    {
                                    BEGIN(IDENTIFIER);
                                    yymore();
                                }

[a-zA-Z0-9_]+                   {
                                    BEGIN(IDENTIFIER);
                                    yymore();
                                }

<SCRIPT>[a-zA-Z0-9]+            { BEGIN(INITIAL); }

.                               { YY_FATAL_ERROR("bad token:\n"); }

%{

#undef fprintf

%}

%%

//
// Implements yywrap to always append a newline to the source
//
int yywrap(void)
{
    if (parseStage == PS_TYPE) {
        parseStage  = PS_BODY;
        in_ptr      = start_ptr;
        out_pos     = 0;
        success_pos = 0;
        return 0;
    }

    if (parseStage == PS_BODY) {
        if (YY_START == C_COMMENT) {
            Compiler.CompileError(success_pos, "unexpected end of file found in comment");
            return 1;
        }

        parseStage = PS_BODY_END;
        in_ptr     = "\n";
        return 0;
    }

    return 1;
}

void yy_init_script()
{
    BEGIN(SCRIPT);
}
