// Scintilla source code edit control
/** @file LexVHDL.cxx
 ** Lexer for VHDL
 ** Written by Phil Reid,
 ** Based on:
 **  - The Verilog Lexer by Avi Yegudin
 **  - The Fortran Lexer by Chuan-jian Shen
 **  - The C++ lexer by Neil Hodgson
 **/
// Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ctype.h>

#include <string>
#include <string_view>

#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"

#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "LexerModule.h"

using namespace Lexilla;

namespace {

static void ColouriseVHDLDoc(
  Sci_PositionU startPos,
  Sci_Position length,
  int initStyle,
  WordList *keywordlists[],
  Accessor &styler);


/***************************************/
inline bool IsAWordChar(const int ch) {
  return IsAlphaNumeric(ch) || ch == '.' || ch == '_';
}

/***************************************/
inline bool IsAWordStart(const int ch) {
  return IsAlphaNumeric(ch) || ch == '_';
}

/***************************************/
inline bool IsABlank(unsigned int ch) {
  return (ch == ' ') || (ch == 0x09) || (ch == 0x0b) ;
}

/***************************************/
void ColouriseVHDLDoc(
  Sci_PositionU startPos,
  Sci_Position length,
  int initStyle,
  WordList *keywordlists[],
  Accessor &styler)
{
  WordList &Keywords   = *keywordlists[0];
  WordList &Operators  = *keywordlists[1];
  WordList &Attributes = *keywordlists[2];
  WordList &Functions  = *keywordlists[3];
  WordList &Packages   = *keywordlists[4];
  WordList &Types      = *keywordlists[5];
  WordList &User       = *keywordlists[6];

  StyleContext sc(startPos, length, initStyle, styler);
  bool isExtendedId = false;    // true when parsing an extended identifier

  while (sc.More())
  {
    bool advance = true;

    // Determine if the current state should terminate.
    if (sc.state == SCE_VHDL_OPERATOR) {
      sc.SetState(SCE_VHDL_DEFAULT);
    } else if (sc.state == SCE_VHDL_NUMBER) {
      if (!IsAWordChar(sc.ch) && (sc.ch != '#')) {
        sc.SetState(SCE_VHDL_DEFAULT);
      }
    } else if (sc.state == SCE_VHDL_IDENTIFIER) {
      if (!isExtendedId && (!IsAWordChar(sc.ch) || (sc.ch == '.'))) {
        char s[100];
        sc.GetCurrentLowered(s, sizeof(s));
        if (Keywords.InList(s)) {
          sc.ChangeState(SCE_VHDL_KEYWORD);
        } else if (Operators.InList(s)) {
          sc.ChangeState(SCE_VHDL_STDOPERATOR);
        } else if (Attributes.InList(s)) {
          sc.ChangeState(SCE_VHDL_ATTRIBUTE);
        } else if (Functions.InList(s)) {
          sc.ChangeState(SCE_VHDL_STDFUNCTION);
        } else if (Packages.InList(s)) {
          sc.ChangeState(SCE_VHDL_STDPACKAGE);
        } else if (Types.InList(s)) {
          sc.ChangeState(SCE_VHDL_STDTYPE);
        } else if (User.InList(s)) {
          sc.ChangeState(SCE_VHDL_USERWORD);
        }
        sc.SetState(SCE_VHDL_DEFAULT);
      } else if (isExtendedId && ((sc.ch == '\\') || sc.atLineEnd)) {
        // extended identifiers are terminated by backslash, check for end of line in case we have invalid syntax
        isExtendedId = false;
        sc.ForwardSetState(SCE_VHDL_DEFAULT);
        advance = false;
      }
    } else if (sc.state == SCE_VHDL_COMMENT || sc.state == SCE_VHDL_COMMENTLINEBANG) {
      if (sc.atLineEnd) {
        sc.SetState(SCE_VHDL_DEFAULT);
      }
    } else if (sc.state == SCE_VHDL_STRING) {
      if (sc.ch == '"') {
        advance = false;
        sc.Forward();
        if (sc.ch == '"')
          sc.Forward();
        else
          sc.SetState(SCE_VHDL_DEFAULT);
      } else if (sc.atLineEnd) {
        advance = false;
        sc.ChangeState(SCE_VHDL_STRINGEOL);
        sc.ForwardSetState(SCE_VHDL_DEFAULT);
      }
    } else if (sc.state == SCE_VHDL_BLOCK_COMMENT){
      if(sc.ch == '*' && sc.chNext == '/'){
        advance = false;
        sc.Forward();
        sc.ForwardSetState(SCE_VHDL_DEFAULT);
      }
    }

    // Determine if a new state should be entered.
    if (sc.state == SCE_VHDL_DEFAULT) {
      if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
        sc.SetState(SCE_VHDL_NUMBER);
      } else if (IsAWordStart(sc.ch)) {
        sc.SetState(SCE_VHDL_IDENTIFIER);
      } else if (sc.Match('-', '-')) {
        if (sc.Match("--!"))  // Nice to have a different comment style
          sc.SetState(SCE_VHDL_COMMENTLINEBANG);
        else
          sc.SetState(SCE_VHDL_COMMENT);
      } else if (sc.Match('/', '*')){
        sc.SetState(SCE_VHDL_BLOCK_COMMENT);
      } else if (sc.ch == '"') {
        sc.SetState(SCE_VHDL_STRING);
      } else if (sc.ch == '\'') {
        if (sc.GetRelative(2) == '\''){
          if (sc.chNext != '(' || sc.GetRelative(4) != '\''){
            // Can only be a character literal
            sc.SetState(SCE_VHDL_STRING);
            sc.Forward();
            sc.Forward();
            sc.ForwardSetState(SCE_VHDL_DEFAULT);
            advance = false;
          } // else can be a tick or a character literal, need more context, eg.: identifier'('x')
        } // else can only be a tick
      } else if (sc.ch == '\\') {
        isExtendedId = true;
        sc.SetState(SCE_VHDL_IDENTIFIER);
      } else if (isoperator(static_cast<char>(sc.ch))) {
        sc.SetState(SCE_VHDL_OPERATOR);
      }
    }

    if (advance)
      sc.Forward();
  }
  sc.Complete();
}
//=============================================================================
bool IsCommentLine(Sci_Position line, Accessor &styler) {
  Sci_Position pos = styler.LineStart(line);
  Sci_Position eol_pos = styler.LineStart(line + 1) - 1;
  for (Sci_Position i = pos; i < eol_pos; i++) {
    char ch = styler[i];
    char chNext = styler[i+1];
    if ((ch == '-') && (chNext == '-'))
      return true;
    else if (ch != ' ' && ch != '\t')
      return false;
  }
  return false;
}
bool IsCommentBlockStart(Sci_Position line, Accessor &styler)
{
  Sci_Position pos = styler.LineStart(line);
  Sci_Position eol_pos = styler.LineStart(line + 1) - 1;
  for (Sci_Position i = pos; i < eol_pos; i++) {
    char ch = styler[i];
    char chNext = styler[i+1];
    char style = styler.StyleAt(i);
    if ((style == SCE_VHDL_BLOCK_COMMENT) && (ch == '/') && (chNext == '*'))
      return true;
  }
  return false;
}

bool IsCommentBlockEnd(Sci_Position line, Accessor &styler)
{
  Sci_Position pos = styler.LineStart(line);
  Sci_Position eol_pos = styler.LineStart(line + 1) - 1;

  for (Sci_Position i = pos; i < eol_pos; i++) {
    char ch = styler[i];
    char chNext = styler[i+1];
    char style = styler.StyleAt(i);
    if ((style == SCE_VHDL_BLOCK_COMMENT) && (ch == '*') && (chNext == '/'))
      return true;
  }
  return false;
}

bool IsCommentStyle(char style)
{
  return style == SCE_VHDL_BLOCK_COMMENT || style == SCE_VHDL_COMMENT || style == SCE_VHDL_COMMENTLINEBANG;
}

//=============================================================================
// Folding the code
void FoldNoBoxVHDLDoc(
  Sci_PositionU startPos,
  Sci_Position length,
  int,
  Accessor &styler)
{
  // Decided it would be smarter to have the lexer have all keywords included. Therefore I
  // don't check if the style for the keywords that I use to adjust the levels.
  char words[] =
    "architecture begin block case component else elsif end entity for generate loop package process record then "
    "procedure protected function when units";
  WordList keywords;
  keywords.Set(words);

  bool foldComment      = styler.GetPropertyInt("fold.comment", 1) != 0;
  bool foldCompact      = styler.GetPropertyInt("fold.compact", 1) != 0;
  bool foldAtElse       = styler.GetPropertyInt("fold.at.else", 1) != 0;
  bool foldAtBegin      = styler.GetPropertyInt("fold.at.Begin", 1) != 0;
  bool foldAtParenthese = styler.GetPropertyInt("fold.at.Parenthese", 1) != 0;
  //bool foldAtWhen       = styler.GetPropertyInt("fold.at.When", 1) != 0;  //< fold at when in case statements

  int  visibleChars     = 0;
  Sci_PositionU endPos   = startPos + length;

  Sci_Position lineCurrent       = styler.GetLine(startPos);
  int levelCurrent      = SC_FOLDLEVELBASE;
  if(lineCurrent > 0)
    levelCurrent        = styler.LevelAt(lineCurrent-1) >> 16;
  //int levelMinCurrent   = levelCurrent;
  int levelMinCurrentElse = levelCurrent;   ///< Used for folding at 'else'
  int levelMinCurrentBegin = levelCurrent;  ///< Used for folding at 'begin'
  int levelNext         = levelCurrent;

  /***************************************/
  Sci_Position lastStart         = 0;
  char prevWord[32]     = "";

  /***************************************/
  // Find prev word
  // The logic for going up or down a level depends on a the previous keyword
  // This code could be cleaned up.
  Sci_Position end = 0;
  Sci_PositionU j;
  for(j = startPos; j>0; j--)
  {
    char ch       = styler.SafeGetCharAt(j);
    char chPrev   = styler.SafeGetCharAt(j-1);
    int style     = styler.StyleAt(j);
    int stylePrev = styler.StyleAt(j-1);
    if ((!IsCommentStyle(style)) && (stylePrev != SCE_VHDL_STRING))
    {
      if(IsAWordChar(chPrev) && !IsAWordChar(ch))
      {
        end = j-1;
      }
    }
    if ((!IsCommentStyle(style)) && (style != SCE_VHDL_STRING))
    {
      if(!IsAWordChar(chPrev) && IsAWordStart(ch) && (end != 0))
      {
        char s[32];
        Sci_PositionU k;
        for(k=0; (k<31 ) && (k<end-j+1 ); k++) {
          s[k] = MakeLowerCase(styler[j+k]);
        }
        s[k] = '\0';

        if(keywords.InList(s)) {
          strcpy(prevWord, s);
          break;
        }
      }
    }
  }
  for(j=j+static_cast<Sci_PositionU>(strlen(prevWord)); j<endPos; j++)
  {
    char ch       = styler.SafeGetCharAt(j);
    int style     = styler.StyleAt(j);
    if ((!IsCommentStyle(style)) && (style != SCE_VHDL_STRING))
    {
      if((ch == ';') && (strcmp(prevWord, "end") == 0))
      {
        strcpy(prevWord, ";");
      }
    }
  }

  char  chNext          = styler[startPos];
  char  chPrev          = '\0';
  char  chNextNonBlank;
  int   styleNext       = styler.StyleAt(startPos);
  //Platform::DebugPrintf("Line[%04d] Prev[%20s] ************************* Level[%x]\n", lineCurrent+1, prevWord, levelCurrent);

  /***************************************/
  for (Sci_PositionU i = startPos; i < endPos; i++)
  {
    char ch         = chNext;
    chNext          = styler.SafeGetCharAt(i + 1);
    chPrev          = styler.SafeGetCharAt(i - 1);
    chNextNonBlank  = chNext;
    Sci_PositionU j  = i+1;
    while(IsABlank(chNextNonBlank) && j<endPos)
    {
      j ++ ;
      chNextNonBlank = styler.SafeGetCharAt(j);
    }
    int style           = styleNext;
    styleNext       = styler.StyleAt(i + 1);
    bool atEOL      = (ch == '\r' && chNext != '\n') || (ch == '\n');

    if (foldComment && atEOL)
    {
      if(IsCommentLine(lineCurrent, styler))
      {
        if(!IsCommentLine(lineCurrent-1, styler) && IsCommentLine(lineCurrent+1, styler))
        {
          levelNext++;
        }
        else if(IsCommentLine(lineCurrent-1, styler) && !IsCommentLine(lineCurrent+1, styler))
        {
          levelNext--;
        }
      }
      else
      {
        if (IsCommentBlockStart(lineCurrent, styler) && !IsCommentBlockEnd(lineCurrent, styler))
        {
          levelNext++;
        }
        else if (IsCommentBlockEnd(lineCurrent, styler) && !IsCommentBlockStart(lineCurrent, styler))
        {
          levelNext--;
        }
      }
    }

    if ((style == SCE_VHDL_OPERATOR) && foldAtParenthese)
    {
      if(ch == '(') {
        levelNext++;
      } else if (ch == ')') {
        levelNext--;
      }
    }

    if ((!IsCommentStyle(style)) && (style != SCE_VHDL_STRING))
    {
      if((ch == ';') && (strcmp(prevWord, "end") == 0))
      {
        strcpy(prevWord, ";");
      }

      if(!IsAWordChar(chPrev) && IsAWordStart(ch))
      {
        lastStart = i;
      }

      if(IsAWordChar(ch) && !IsAWordChar(chNext)) {
        char s[32];
        Sci_PositionU k;
        for(k=0; (k<31 ) && (k<i-lastStart+1 ); k++) {
          s[k] = MakeLowerCase(styler[lastStart+k]);
        }
        s[k] = '\0';

        if(keywords.InList(s))
        {
          if (
            strcmp(s, "architecture") == 0  ||
            strcmp(s, "case") == 0          ||
            strcmp(s, "block") == 0         ||
            strcmp(s, "loop") == 0          ||
            strcmp(s, "package") ==0        ||
            strcmp(s, "process") == 0       ||
            strcmp(s, "protected") == 0     ||
            strcmp(s, "record") == 0        ||
            strcmp(s, "then") == 0          ||
            strcmp(s, "units") == 0)
          {
            if (strcmp(prevWord, "end") != 0)
            {
              if (levelMinCurrentElse > levelNext) {
                levelMinCurrentElse = levelNext;
              }
              levelNext++;
            }
          } else if (strcmp(s, "generate") == 0){
            if (strcmp(prevWord, "end") != 0 &&
                strcmp(prevWord, "else") != 0 && // vhdl08 else generate
                strcmp(prevWord, "case") != 0)   // vhdl08 case generate
            {
              if (levelMinCurrentElse > levelNext) {
                levelMinCurrentElse = levelNext;
              }
              levelNext++;
            }
          } else if (
            strcmp(s, "component") == 0      ||
            strcmp(s, "entity") == 0         ||
            strcmp(s, "configuration") == 0 )
          {
            if (strcmp(prevWord, "end") != 0)
            { // check for instantiated unit by backward searching for the colon.
              Sci_PositionU pos = lastStart;
              char chAtPos=0, styleAtPos;
              do{// skip white spaces
                if(!pos)
                  break;
                pos--;
                styleAtPos = styler.StyleAt(pos);
                chAtPos = styler.SafeGetCharAt(pos);
              }while(pos &&
                     (chAtPos == ' ' || chAtPos == '\t' ||
                      chAtPos == '\n' || chAtPos == '\r' ||
                      IsCommentStyle(styleAtPos)));

              // check for a colon (':') before the instantiated units "entity", "component" or "configuration". Don't fold thereafter.
              if (chAtPos != ':')
              {
                if (levelMinCurrentElse > levelNext) {
                  levelMinCurrentElse = levelNext;
                }
                levelNext++;
              }
            }
          } else if (
            strcmp(s, "procedure") == 0     ||
            strcmp(s, "function") == 0)
          {
            if (strcmp(prevWord, "end") != 0) // check for "end procedure" etc.
            { // This code checks to see if the procedure / function is a definition within a "package"
              // rather than the actual code in the body.
              int BracketLevel = 0;
              for(Sci_Position pos=i+1; pos<styler.Length(); pos++)
              {
                int styleAtPos = styler.StyleAt(pos);
                char chAtPos = styler.SafeGetCharAt(pos);
                if(chAtPos == '(') BracketLevel++;
                if(chAtPos == ')') BracketLevel--;
                if(
                  (BracketLevel == 0) &&
                  (!IsCommentStyle(styleAtPos)) &&
                  (styleAtPos != SCE_VHDL_STRING) &&
                  !iswordchar(styler.SafeGetCharAt(pos-1)) &&
                  (chAtPos|' ')=='i' && (styler.SafeGetCharAt(pos+1)|' ')=='s' &&
                  !iswordchar(styler.SafeGetCharAt(pos+2)))
                {
                  if (levelMinCurrentElse > levelNext) {
                    levelMinCurrentElse = levelNext;
                  }
                  levelNext++;
                  break;
                }
                if((BracketLevel == 0) && (chAtPos == ';'))
                {
                  break;
                }
              }
            }

          } else if (strcmp(s, "end") == 0) {
            levelNext--;
          }  else if(strcmp(s, "elsif") == 0) { // elsif is followed by then or generate so folding occurs correctly
            levelNext--;
          } else if (strcmp(s, "else") == 0) {
            if(strcmp(prevWord, "when") != 0)  // ignore a <= x when y else z;
            {
              levelMinCurrentElse = levelNext - 1;  // VHDL else is all on its own so just dec. the min level
            }
          } else if(
            ((strcmp(s, "begin") == 0) && (strcmp(prevWord, "architecture") == 0)) ||
            ((strcmp(s, "begin") == 0) && (strcmp(prevWord, "function") == 0)) ||
            ((strcmp(s, "begin") == 0) && (strcmp(prevWord, "procedure") == 0)) ||
            ((strcmp(s, "begin") == 0) && (strcmp(prevWord, "generate") == 0)))
          {
            levelMinCurrentBegin = levelNext - 1;
          }
          //Platform::DebugPrintf("Line[%04d] Prev[%20s] Cur[%20s] Level[%x]\n", lineCurrent+1, prevWord, s, levelCurrent);
          strcpy(prevWord, s);
        }
      }
    }
    if (atEOL) {
      int levelUse = levelCurrent;

      if (foldAtElse && (levelMinCurrentElse < levelUse)) {
        levelUse = levelMinCurrentElse;
      }
      if (foldAtBegin && (levelMinCurrentBegin < levelUse)) {
        levelUse = levelMinCurrentBegin;
      }
      int lev = levelUse | levelNext << 16;
      if (visibleChars == 0 && foldCompact)
        lev |= SC_FOLDLEVELWHITEFLAG;

      if (levelUse < levelNext)
        lev |= SC_FOLDLEVELHEADERFLAG;
      if (lev != styler.LevelAt(lineCurrent)) {
        styler.SetLevel(lineCurrent, lev);
      }
      //Platform::DebugPrintf("Line[%04d] ---------------------------------------------------- Level[%x]\n", lineCurrent+1, levelCurrent);
      lineCurrent++;
      levelCurrent = levelNext;
      //levelMinCurrent = levelCurrent;
      levelMinCurrentElse = levelCurrent;
      levelMinCurrentBegin = levelCurrent;
      visibleChars = 0;
    }
    /***************************************/
    if (!isspacechar(ch)) visibleChars++;
  }

  /***************************************/
//  Platform::DebugPrintf("Line[%04d] ---------------------------------------------------- Level[%x]\n", lineCurrent+1, levelCurrent);
}

//=============================================================================
void FoldVHDLDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *[],
                       Accessor &styler) {
  FoldNoBoxVHDLDoc(startPos, length, initStyle, styler);
}

//=============================================================================
const char * const VHDLWordLists[] = {
            "Keywords",
            "Operators",
            "Attributes",
            "Standard Functions",
            "Standard Packages",
            "Standard Types",
            "User Words",
            0,
        };

}

extern const LexerModule lmVHDL(SCLEX_VHDL, ColouriseVHDLDoc, "vhdl", FoldVHDLDoc, VHDLWordLists);


// Keyword:
//    access after alias all architecture array assert attribute begin block body buffer bus case component
//    configuration constant disconnect downto else elsif end entity exit file for function generate generic
//    group guarded if impure in inertial inout is label library linkage literal loop map new next null of
//    on open others out package port postponed procedure process pure range record register reject report
//    return select severity shared signal subtype then to transport type unaffected units until use variable
//    wait when while with
//
// Operators:
//    abs and mod nand nor not or rem rol ror sla sll sra srl xnor xor
//
// Attributes:
//    left right low high ascending image value pos val succ pred leftof rightof base range reverse_range
//    length delayed stable quiet transaction event active last_event last_active last_value driving
//    driving_value simple_name path_name instance_name
//
// Std Functions:
//    now readline read writeline write endfile resolved to_bit to_bitvector to_stdulogic to_stdlogicvector
//    to_stdulogicvector to_x01 to_x01z to_UX01 rising_edge falling_edge is_x shift_left shift_right rotate_left
//    rotate_right resize to_integer to_unsigned to_signed std_match to_01
//
// Std Packages:
//    std ieee work standard textio std_logic_1164 std_logic_arith std_logic_misc std_logic_signed
//    std_logic_textio std_logic_unsigned numeric_bit numeric_std math_complex math_real vital_primitives
//    vital_timing
//
// Std Types:
//    boolean bit character severity_level integer real time delay_length natural positive string bit_vector
//    file_open_kind file_open_status line text side width std_ulogic std_ulogic_vector std_logic
//    std_logic_vector X01 X01Z UX01 UX01Z unsigned signed
//

