/*
Copyright Contributors to the OpenVDB Project
SPDX-License-Identifier: MPL-2.0

@file grammar/axlexer.l

@authors Nick Avramoussis, Richard Jones

@brief  OpenVDB AX Lexer
*/

%top{
    // Manually include MIN/MAX macro definitions - fixes warnings on
    // Windows with older version of flex
    #include <stdint.h>
}

%{
    #include "openvdb_ax/ast/Parse.h"
    #include "openvdb_ax/compiler/Logger.h"
    #include "axparser.h" /*generated by bison*/
    #include <openvdb/Platform.h>
    #include <cstdlib>
    #include <errno.h>

    /// @note  Bypasses conversion warnings in YY_CURRENT_BUFFER macro.
    ///        This is a bit over zealous as we only need to suppress
    ///        -Wnull-conversion
    OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN

    extern openvdb::ax::Logger* axlog;

    /// @note  Location tracking macro for axlloc token locations.
    ///   YY_USER_ACTION is called before any and each lexer action
    ///   is performed. Instead of manually tracking newlines, we
    ///   can simply scan for them in the current text held by axtext
    #define YY_USER_ACTION \
        assert(axlog); \
        axlloc.first_line = axlloc.last_line; \
        axlloc.first_column = axlloc.last_column; \
        for (int i = 0; axtext[i] != '\0'; i++) { \
            if (axtext[i] == '\n') { \
                axlloc.last_line++; \
                axlloc.last_column = 1; \
            } \
            else { \
                axlloc.last_column++; \
            } \
        }
%}

/* Option 'nounistd' suppresses the inclusion of the non-ANSI header
 * file unistd.h. This is necessary on Windows (perhaps we should be
 * doing this in a platform depedant way?). This also requires us to
 * completely disable parsing interactivity (which for now if fine
 * as we currently don't support that).
 */
%option nounistd

/* Option 'never-interactive' instructs flex to generate a scanner which
 * never considers its input interactive. Essentially disabled EOF calls
 * to isatty().
 */
%option never-interactive

/* Option 'noyywrap' indicates that when EOF is hit, yyin does not
 * automatically reset to another file.
 */
%option noyywrap

/* Options 'nounput' and 'noinput' are useful for interactive session
 * support. We don't support or require this.
 */
%option nounput noinput

/* Option 'prefix' creates a C++ lexer with the given prefix, so that
 * we can link with other flex-generated lexers in the same application
 * without name conflicts.
 */
%option prefix="ax"

/* Some handy macros which define constant tokens
 */

/* All whitespace bar new lines:
 *   \t tabs, \v vertical tabs, \r carriage return, \f form feed (page break)
 * https://www.regular-expressions.info/refcharacters.html
 */
WHITESPACE  [ \t\v\r\f]+
LETTER      [a-zA-Z]
DIGIT       [0-9]
E           [eE][-+]?{DIGIT}+
NEWLINE     [\n]
COMMENT     "//".*

%%

";"                         { return SEMICOLON; }
"@"                         { return AT; }
"$"                         { return DOLLAR; }

"="                         { return EQUALS; }
"+"                         { return PLUS; }
"-"                         { return MINUS; }
"*"                         { return MULTIPLY; }
"/"                         { return DIVIDE; }
"%"                         { return MODULO; }

"<<"                        { return SHIFTLEFT; }
">>"                        { return SHIFTRIGHT; }
"&"                         { return BITAND; }
"|"                         { return BITOR; }
"^"                         { return BITXOR; }
"~"                         { return BITNOT; }

"=="                        { return EQUALSEQUALS; }
"!="                        { return NOTEQUALS; }
">"                         { return MORETHAN; }
"<"                         { return LESSTHAN; }
">="                        { return MORETHANOREQUAL; }
"<="                        { return LESSTHANOREQUAL; }

"+="                        { return PLUSEQUALS; }
"-="                        { return MINUSEQUALS; }
"*="                        { return MULTIPLYEQUALS; }
"/="                        { return DIVIDEEQUALS; }
"%="                        { return MODULOEQUALS; }
"&="                        { return BITANDEQUALS; }
"^="                        { return BITXOREQUALS; }
"|="                        { return BITOREQUALS; }
"<<="                       { return SHIFTLEFTEQUALS; }
">>="                       { return SHIFTRIGHTEQUALS; }

"++"                        { return PLUSPLUS; }
"--"                        { return MINUSMINUS; }

"&&"                        { return AND; }
"||"                        { return OR; }
"!"                         { return NOT; }

","                         { return COMMA; }

"?"                         { return QUESTION; }
":"                         { return COLON; }

"("                         { return LPARENS; }
")"                         { return RPARENS; }
"{"                         { return LCURLY; }
"}"                         { return RCURLY; }
"["                         { return LSQUARE; }
"]"                         { return RSQUARE; }

".x"                        { return DOT_X; }
".y"                        { return DOT_Y; }
".z"                        { return DOT_Z; }
".r"                        { return DOT_X; }
".g"                        { return DOT_Y; }
".b"                        { return DOT_Z; }

"v@"                        { return V_AT; }
"f@"                        { return F_AT; }
"i@"                        { return I_AT; }
"s@"                        { return S_AT; }
"int16@"                    { return I16_AT; }

"v$"                        { return V_DOLLAR; }
"f$"                        { return F_DOLLAR; }
"i$"                        { return I_DOLLAR; }
"s$"                        { return S_DOLLAR; }

"if"                        { return IF; }
"else"                      { return ELSE; }
"true"                      { return TRUE; }
"false"                     { return FALSE; }
"return"                    { return RETURN; }
"for"                       { return FOR; }
"do"                        { return DO; }
"while"                     { return WHILE; }
"break"                     { return BREAK;}
"continue"                  { return CONTINUE;}

"bool"                      { return BOOL; }
"int"                       { return INT32; }
"int32"                     { return INT32; }
"int64"                     { return INT64; }
"float"                     { return FLOAT; }
"double"                    { return DOUBLE; }
"string"                    { return STRING; }

"vec2i"                     { return VEC2I; }
"vec2f"                     { return VEC2F; }
"vec2d"                     { return VEC2D; }
"vec3i"                     { return VEC3I; }
"vec3f"                     { return VEC3F; }
"vec3d"                     { return VEC3D; }
"vec4i"                     { return VEC4I; }
"vec4f"                     { return VEC4F; }
"vec4d"                     { return VEC4D; }

"mat3f"                     { return MAT3F; }
"mat3d"                     { return MAT3D; }
"mat4f"                     { return MAT4F; }
"mat4d"                     { return MAT4D; }

 /*Tokens to support VEX Syntax*/
"vector"                    { return VEC3F; } /*VEX SUPPORT TOKENS*/
"matrix"                    { return MAT4F; }
"matrix3"                   { return MAT3F; }
"3@"                        { return M3F_AT; }
"4@"                        { return M4F_AT; }

 /*Deprecated Tokens*/
"vectorint"                 {  axlog->warning("vectorint keyword is deprecated. use vec3i.",
                                    {axlloc.first_line, axlloc.first_column});
                                return VEC3I;
                            }
"vectorfloat"               {  axlog->warning("vectorfloat keyword is deprecated. use vec3f.",
                                    {axlloc.first_line, axlloc.first_column});
                                return VEC3F;
                            }
"vectordouble"              {  axlog->warning("vectordouble keyword is deprecated. use vec3d.",
                                    {axlloc.first_line, axlloc.first_column});
                                return VEC3D;
                            }
"short"                     {  axlog->warning("short local variables have been removed. use int, int32 or int64.",
                                    {axlloc.first_line, axlloc.first_column});
                                return INT32;
                            }
"long"                      {  axlog->warning("long keyword is deprecated. use int64.",
                                    {axlloc.first_line, axlloc.first_column});
                                return INT64;
                            }

 /* Reserved keywords */
"case"|"char"|"class"|"const"|"def"|"default" | \
"enum"|"extern"|"friend"|"function"|"inline"|"int16" |  \
"private"|"protected"|"signed"|"sizeof"|"static" | \
"struct"|"switch"|"template"|"this"|"typedef" | \
"uniform"|"union"|"unsigned"|"until"|"virtual" | \
"void"|"byte"|"flt"|"flt16"|"flt32"|"flt64"|"int8" | \
"half" {
    /* @todo: move this into parser */
    std::ostringstream os;
    os <<"\""<< axtext << "\" is a reserved keyword.";
    axlog->error(os.str(), {axlloc.first_line, axlloc.first_column});
}

{WHITESPACE}                { } /* ignore whitespace */
{COMMENT}                   { } /* ignore //-style one-line comments */
{NEWLINE}                   { } /* ignore newlines */

\"(\\.|[^\\"\n])*\"         {
                                // minus quotes, plus null terminator
                                char* dst = (char*)malloc(sizeof(char) * ((axleng-2) + 1));
                                memcpy(dst, axtext+1, axleng-2);
                                dst[axleng-2] = '\0';
                                axlval.string = dst;
                                return L_STRING;
                            }

{DIGIT}+s                   {
                                axlog->warning("s suffix is deprecated.", {axlloc.first_line, axlloc.first_column});
                                errno = 0;
                                axlval.index = uint64_t(std::strtoull(axtext, /*end*/nullptr, /*base*/10));
                                if (errno == ERANGE) {
                                    errno = 0;
                                    axlog->error("integer constant is too large to be represented:",
                                        {axlloc.first_line, axlloc.first_column});
                                }
                                return L_INT32;
                            }

{DIGIT}+                    {
                                errno = 0;
                                axlval.index = uint64_t(std::strtoull(axtext, /*end*/nullptr, /*base*/10));
                                if (errno == ERANGE) {
                                    errno = 0;
                                    axlog->error("integer constant is too large to be represented:",
                                        {axlloc.first_line, axlloc.first_column});
                                }

                                return L_INT32;
                            }

{DIGIT}+l                   {
                                errno = 0;
                                axlval.index = uint64_t(std::strtoull(axtext, /*end*/nullptr, /*base*/10));
                                if (errno == ERANGE) {
                                    errno = 0;
                                    axlog->error("integer constant is too large to be represented:",
                                        {axlloc.first_line, axlloc.first_column});
                                }
                                return L_INT64;
                            }

"."{DIGIT}+f         |
{DIGIT}+"."{DIGIT}*f |
{DIGIT}+("."{DIGIT}+)?{E}+f     {
                                    errno = 0;
                                    axlval.flt = static_cast<double>(std::strtof(axtext, /*end*/nullptr));
                                    if (errno == ERANGE) {
                                        errno = 0;
                                        if (std::isinf(axlval.flt)) {
                                            axlog->warning("floating point constant is too large for type float, "
                                                "will be converted to inf.", {axlloc.first_line, axlloc.first_column});
                                        }
                                        else if (axlval.flt == 0.0) {
                                            axlog->warning("floating point constant truncated to zero.",
                                                {axlloc.first_line, axlloc.first_column});
                                        }
                                        else {
                                            axlog->warning("floating point constant is too small for type float "
                                                "and may underflow.", {axlloc.first_line, axlloc.first_column});
                                        }
                                    }
                                    return L_FLOAT;
                                }

"."{DIGIT}+         |
{DIGIT}+"."{DIGIT}* |
{DIGIT}+("."{DIGIT}+)?{E}+      {
                                    errno = 0;
                                    axlval.flt = std::strtod(axtext, /*end*/nullptr);
                                    if (errno == ERANGE) {
                                        errno = 0;
                                        if (std::isinf(axlval.flt)) {
                                            axlog->warning("floating point constant is too large for type double, "
                                                "will be converted to inf.", {axlloc.first_line, axlloc.first_column});
                                        }
                                        else if (axlval.flt == 0.0) {
                                            axlog->warning("floating point constant truncated to zero.",
                                                {axlloc.first_line, axlloc.first_column});
                                        }
                                        else {
                                            axlog->warning("floating point constant is too small for type double "
                                                "and may underflow.", {axlloc.first_line, axlloc.first_column});
                                        }
                                    }
                                    return L_DOUBLE;
                                }

([_]|{LETTER})([_]|{LETTER}|{DIGIT})*   {
                                            axlval.string = strdup(axtext);
                                            return IDENTIFIER;
                                        }

.                           {
                                /* error on everything else */
                                /* @todo: move this into parser */
                                assert(axlog);
                                axlog->error("stray or invalid character.",
                                        {axlloc.first_line, axlloc.first_column});

                            }

%%

OPENVDB_NO_TYPE_CONVERSION_WARNING_END

