Logo Search packages:      
Sourcecode: codeblocks version File versions  Download package

LexPerl.cxx

Go to the documentation of this file.
// Scintilla source code edit control
/** @file LexPerl.cxx
 ** Lexer for Perl.
 **/
// Copyright 1998-2008 by Neil Hodgson <neilh@scintilla.org>
// Lexical analysis fixes by Kein-Hong Man <mkh@pl.jaring.my>
// The License.txt file describes the conditions under which this software may be distributed.

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

#include "Platform.h"

#include "PropSet.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "KeyWords.h"
#include "Scintilla.h"
#include "SciLexer.h"
#include "CharacterSet.h"

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif

// Info for HERE document handling from perldata.pod (reformatted):
// ----------------------------------------------------------------
// A line-oriented form of quoting is based on the shell ``here-doc'' syntax.
// Following a << you specify a string to terminate the quoted material, and
// all lines following the current line down to the terminating string are
// the value of the item.
// * The terminating string may be either an identifier (a word), or some
//   quoted text.
// * If quoted, the type of quotes you use determines the treatment of the
//   text, just as in regular quoting.
// * An unquoted identifier works like double quotes.
// * There must be no space between the << and the identifier.
//   (If you put a space it will be treated as a null identifier,
//    which is valid, and matches the first empty line.)
//   (This is deprecated, -w warns of this syntax)
// * The terminating string must appear by itself (unquoted and
//   with no surrounding whitespace) on the terminating line.

#define HERE_DELIM_MAX 256          // maximum length of HERE doc delimiter

#define PERLNUM_BINARY        1     // order is significant: 1-4 cannot have a dot
#define PERLNUM_HEX                 2
#define PERLNUM_OCTAL         3
#define PERLNUM_FLOAT_EXP     4     // exponent part only
#define PERLNUM_DECIMAL       5     // 1-5 are numbers; 6-7 are strings
#define PERLNUM_VECTOR        6
#define PERLNUM_V_VECTOR      7
#define PERLNUM_BAD                 8

#define BACK_NONE       0     // lookback state for bareword disambiguation:
#define BACK_OPERATOR   1     // whitespace/comments are insignificant
#define BACK_KEYWORD    2     // operators/keywords are needed for disambiguation

static bool isPerlKeyword(unsigned int start, unsigned int end, WordList &keywords, Accessor &styler)
{
      // old-style keyword matcher; needed because GetCurrent() needs
      // current segment to be committed, but we may abandon early...
      char s[100];
      unsigned int i, len = end - start;
      if (len > 30) { len = 30; }
      for (i = 0; i < len; i++, start++) s[i] = styler[start];
      s[i] = '\0';
      return keywords.InList(s);
}

static int disambiguateBareword(Accessor &styler, unsigned int bk, unsigned int fw,
                                int backFlag, unsigned int backPos, unsigned int endPos)
{
      // identifiers are recognized by Perl as barewords under some
      // conditions, the following attempts to do the disambiguation
      // by looking backward and forward; result in 2 LSB
      int result = 0;
      bool moreback = false;        // true if passed newline/comments
      bool brace = false;                 // true if opening brace found
      // if BACK_NONE, neither operator nor keyword, so skip test
      if (backFlag == BACK_NONE)
            return result;
      // first look backwards past whitespace/comments to set EOL flag
      // (some disambiguation patterns must be on a single line)
      if (backPos <= static_cast<unsigned int>(styler.LineStart(styler.GetLine(bk))))
            moreback = true;
      // look backwards at last significant lexed item for disambiguation
      bk = backPos - 1;
      int ch = static_cast<unsigned char>(styler.SafeGetCharAt(bk));
      if (ch == '{' && !moreback) {
            // {bareword: possible variable spec
            brace = true;
      } else if ((ch == '&' && styler.SafeGetCharAt(bk - 1) != '&')
                  // &bareword: subroutine call
                     || styler.Match(bk - 1, "->")
                  // ->bareword: part of variable spec
                     || styler.Match(bk - 2, "sub")) {
                  // sub bareword: subroutine declaration
                  // (implied BACK_KEYWORD, no keywords end in 'sub'!)
            result |= 1;
      }
      // next, scan forward after word past tab/spaces only;
      // if ch isn't one of '[{(,' we can skip the test
      if ((ch == '{' || ch == '(' || ch == '['|| ch == ',')
            && fw < endPos) {
            while (ch = static_cast<unsigned char>(styler.SafeGetCharAt(fw)),
                     IsASpaceOrTab(ch) && fw < endPos) {
                  fw++;
            }
            if ((ch == '}' && brace)
                  // {bareword}: variable spec
                  || styler.Match(fw, "=>")) {
                  // [{(, bareword=>: hash literal
                  result |= 2;
            }
      }
      return result;
}

static void skipWhitespaceComment(Accessor &styler, unsigned int &p)
{
      // when backtracking, we need to skip whitespace and comments
      int style;
      while ((p > 0) && (style = styler.StyleAt(p),
               style == SCE_PL_DEFAULT || style == SCE_PL_COMMENTLINE))
            p--;
}

static int styleBeforeBracePair(Accessor &styler, unsigned int bk)
{
      // backtrack to find open '{' corresponding to a '}', balanced
      // return significant style to be tested for '/' disambiguation
      int braceCount = 1;
      if (bk == 0)
            return SCE_PL_DEFAULT;
      while (--bk > 0) {
            if (styler.StyleAt(bk) == SCE_PL_OPERATOR) {
                  int bkch = static_cast<unsigned char>(styler.SafeGetCharAt(bk));
                  if (bkch == ';') {      // early out
                        break;
                  } else if (bkch == '}') {
                        braceCount++;
                  } else if (bkch == '{') {
                        if (--braceCount == 0) break;
                  }
            }
      }
      if (bk > 0 && braceCount == 0) {
            // balanced { found, bk > 0, skip more whitespace/comments
            bk--;
            skipWhitespaceComment(styler, bk);
            return styler.StyleAt(bk);
      }
      return SCE_PL_DEFAULT;
}

static int styleCheckIdentifier(Accessor &styler, unsigned int bk)
{
      // backtrack to classify sub-styles of identifier under test
      // return sub-style to be tested for '/' disambiguation
      if (styler.SafeGetCharAt(bk) == '>')      // inputsymbol, like <foo>
            return 1;
      // backtrack to check for possible "->" or "::" before identifier
      while (bk > 0 && styler.StyleAt(bk) == SCE_PL_IDENTIFIER) {
            bk--;
      }
      while (bk > 0) {
            int bkstyle = styler.StyleAt(bk);
            if (bkstyle == SCE_PL_DEFAULT
                  || bkstyle == SCE_PL_COMMENTLINE) {
                  // skip whitespace, comments
            } else if (bkstyle == SCE_PL_OPERATOR) {
                  // test for "->" and "::"
                  if (styler.Match(bk - 1, "->") || styler.Match(bk - 1, "::"))
                        return 2;
            } else
                  return 3;   // bare identifier
            bk--;
      }
      return 0;
}

static int inputsymbolScan(Accessor &styler, unsigned int pos, unsigned int endPos)
{
      // looks forward for matching > on same line; a bit ugly
      unsigned int fw = pos;
      while (++fw < endPos) {
            int fwch = static_cast<unsigned char>(styler.SafeGetCharAt(fw));
            if (fwch == '\r' || fwch == '\n') {
                  return 0;
            } else if (fwch == '>') {
                  if (styler.Match(fw - 2, "<=>"))    // '<=>' case
                        return 0;
                  return fw - pos;
            }
      }
      return 0;
}

static int podLineScan(Accessor &styler, unsigned int &pos, unsigned int endPos)
{
      // forward scan the current line to classify line for POD style
      int state = -1;
      while (pos <= endPos) {
            int ch = static_cast<unsigned char>(styler.SafeGetCharAt(pos));
            if (ch == '\n' || ch == '\r' || pos >= endPos) {
                  if (ch == '\r' && styler.SafeGetCharAt(pos + 1) == '\n') pos++;
                  break;
            }
            if (IsASpaceOrTab(ch)) {      // whitespace, take note
                  if (state == -1)
                        state = SCE_PL_DEFAULT;
            } else if (state == SCE_PL_DEFAULT) {     // verbatim POD line
                  state = SCE_PL_POD_VERB;
            } else if (state != SCE_PL_POD_VERB) {    // regular POD line
                  state = SCE_PL_POD;
            }
            pos++;
      }
      if (state == -1)
            state = SCE_PL_DEFAULT;
      return state;
}

static bool styleCheckSubPrototype(Accessor &styler, unsigned int bk)
{
      // backtrack to identify if we're starting a subroutine prototype
      // we also need to ignore whitespace/comments:
      // 'sub' [whitespace|comment] <identifier> [whitespace|comment]
      styler.Flush();
      skipWhitespaceComment(styler, bk);
      if (bk == 0 || styler.StyleAt(bk) != SCE_PL_IDENTIFIER)     // check identifier
            return false;
      while (bk > 0 && (styler.StyleAt(bk) == SCE_PL_IDENTIFIER)) {
            bk--;
      }
      skipWhitespaceComment(styler, bk);
      if (bk < 2 || styler.StyleAt(bk) != SCE_PL_WORD // check "sub" keyword
            || !styler.Match(bk - 2, "sub"))    // assume suffix is unique!
            return false;
      return true;
}

static bool isMatch(const char *sref, char *s)
{
      // match per-line delimiter - must kill trailing CR if CRLF
      int i = strlen(s);
      if (i != 0 && s[i - 1] == '\r')
            s[i - 1] = '\0';
      return (strcmp(sref, s) == 0);
}

static int actualNumStyle(int numberStyle) {
      if (numberStyle == PERLNUM_VECTOR || numberStyle == PERLNUM_V_VECTOR) {
            return SCE_PL_STRING;
      } else if (numberStyle == PERLNUM_BAD) {
            return SCE_PL_ERROR;
      }
      return SCE_PL_NUMBER;
}

static int opposite(int ch) {
      if (ch == '(') return ')';
      if (ch == '[') return ']';
      if (ch == '{') return '}';
      if (ch == '<') return '>';
      return ch;
}

static void ColourisePerlDoc(unsigned int startPos, int length, int initStyle,
                             WordList *keywordlists[], Accessor &styler) {

      WordList &keywords = *keywordlists[0];

      // keywords that forces /PATTERN/ at all times; should track vim's behaviour
      WordList reWords;
      reWords.Set("elsif if split while");

      // charset classes
      CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true);
      CharacterSet setWord(CharacterSet::setAlphaNum, "_", 0x80, true);
      CharacterSet setSingleCharOp(CharacterSet::setNone, "rwxoRWXOezsfdlpSbctugkTBMAC");
      // lexing of "%*</" operators is non-trivial; these are missing in the set below
      CharacterSet setPerlOperator(CharacterSet::setNone, "^&\\()-+=|{}[]:;>,?!.~");
      CharacterSet setQDelim(CharacterSet::setNone, "qrwx");
      CharacterSet setModifiers(CharacterSet::setAlpha);
      CharacterSet setPreferRE(CharacterSet::setNone, "*/<%");
      // setArray and setHash also accepts chars for special vars like $_,
      // which are then truncated when the next char does not match setVar
      CharacterSet setVar(CharacterSet::setAlphaNum, "#$_'", 0x80, true);
      CharacterSet setArray(CharacterSet::setAlpha, "#$_+-", 0x80, true);
      CharacterSet setHash(CharacterSet::setAlpha, "#$_!^+-", 0x80, true);
      CharacterSet &setPOD = setModifiers;
      CharacterSet setNonHereDoc(CharacterSet::setDigits, "=$@");
      CharacterSet setHereDocDelim(CharacterSet::setAlphaNum, "_");
      CharacterSet setSubPrototype(CharacterSet::setNone, "\\[$@%&*];");
      // for format identifiers
      CharacterSet setFormatStart(CharacterSet::setAlpha, "_=");
      CharacterSet &setFormat = setHereDocDelim;

      // Lexer for perl often has to backtrack to start of current style to determine
      // which characters are being used as quotes, how deeply nested is the
      // start position and what the termination string is for HERE documents.

      class HereDocCls {      // Class to manage HERE doc sequence
      public:
            int State;        // 0: '<<' encountered
                                    // 1: collect the delimiter
                                    // 2: here doc text (lines after the delimiter)
            int Quote;        // the char after '<<'
            bool Quoted;            // true if Quote in ('\'','"','`')
            int DelimiterLength;    // strlen(Delimiter)
            char *Delimiter;  // the Delimiter, 256: sizeof PL_tokenbuf
            HereDocCls() {
                  State = 0;
                  Quote = 0;
                  Quoted = false;
                  DelimiterLength = 0;
                  Delimiter = new char[HERE_DELIM_MAX];
                  Delimiter[0] = '\0';
            }
            void Append(int ch) {
                  Delimiter[DelimiterLength++] = static_cast<char>(ch);
                  Delimiter[DelimiterLength] = '\0';
            }
            ~HereDocCls() {
                  delete []Delimiter;
            }
      };
      HereDocCls HereDoc;           // TODO: FIFO for stacked here-docs

      class QuoteCls {  // Class to manage quote pairs
            public:
            int Rep;
            int Count;
            int Up, Down;
            QuoteCls() {
                  this->New(1);
            }
            void New(int r = 1) {
                  Rep   = r;
                  Count = 0;
                  Up    = '\0';
                  Down  = '\0';
            }
            void Open(int u) {
                  Count++;
                  Up    = u;
                  Down  = opposite(Up);
            }
      };
      QuoteCls Quote;

      // additional state for number lexing
      int numState = PERLNUM_DECIMAL;
      int dotCount = 0;

      unsigned int endPos = startPos + length;

      // Backtrack to beginning of style if required...
      // If in a long distance lexical state, backtrack to find quote characters.
      // Includes strings (may be multi-line), numbers (additional state), format
      // bodies, as well as POD sections.
      if (initStyle == SCE_PL_HERE_Q
            || initStyle == SCE_PL_HERE_QQ
            || initStyle == SCE_PL_HERE_QX
            || initStyle == SCE_PL_FORMAT
      ) {
            int delim = (initStyle == SCE_PL_FORMAT) ? SCE_PL_FORMAT_IDENT:SCE_PL_HERE_DELIM;
            while ((startPos > 1) && (styler.StyleAt(startPos) != delim)) {
                  startPos--;
            }
            startPos = styler.LineStart(styler.GetLine(startPos));
            initStyle = styler.StyleAt(startPos - 1);
      }
      if (initStyle == SCE_PL_STRING_Q
            || initStyle == SCE_PL_STRING_QQ
            || initStyle == SCE_PL_STRING_QX
            || initStyle == SCE_PL_STRING_QR
            || initStyle == SCE_PL_STRING_QW
            || initStyle == SCE_PL_REGEX
            || initStyle == SCE_PL_REGSUBST
            || initStyle == SCE_PL_STRING
            || initStyle == SCE_PL_BACKTICKS
            || initStyle == SCE_PL_CHARACTER
            || initStyle == SCE_PL_NUMBER
            || initStyle == SCE_PL_IDENTIFIER
            || initStyle == SCE_PL_ERROR
            || initStyle == SCE_PL_SUB_PROTOTYPE
      ) {
            while ((startPos > 1) && (styler.StyleAt(startPos - 1) == initStyle)) {
                  startPos--;
            }
            initStyle = SCE_PL_DEFAULT;
      } else if (initStyle == SCE_PL_POD
                     || initStyle == SCE_PL_POD_VERB
      ) {
            // POD backtracking finds preceeding blank lines and goes back past them
            int ln = styler.GetLine(startPos);
            if (ln > 0) {
                  initStyle = styler.StyleAt(styler.LineStart(--ln));
                  if (initStyle == SCE_PL_POD || initStyle == SCE_PL_POD_VERB) {
                        while (ln > 0 && styler.GetLineState(ln) == SCE_PL_DEFAULT)
                              ln--;
                  }
                  startPos = styler.LineStart(++ln);
                  initStyle = styler.StyleAt(startPos - 1);
            } else {
                  startPos = 0;
                  initStyle = SCE_PL_DEFAULT;
            }
      }

      // backFlag, backPos are additional state to aid identifier corner cases.
      // Look backwards past whitespace and comments in order to detect either
      // operator or keyword. Later updated as we go along.
      int backFlag = BACK_NONE;
      unsigned int backPos = startPos;
      if (backPos > 0) {
            backPos--;
            skipWhitespaceComment(styler, backPos);
            if (styler.StyleAt(backPos) == SCE_PL_OPERATOR)
                  backFlag = BACK_OPERATOR;
            else if (styler.StyleAt(backPos) == SCE_PL_WORD)
                  backFlag = BACK_KEYWORD;
            backPos++;
      }

      StyleContext sc(startPos, endPos - startPos, initStyle, styler, static_cast<char>(STYLE_MAX));

      for (; sc.More(); sc.Forward()) {

            // Determine if the current state should terminate.
            switch (sc.state) {
                  case SCE_PL_OPERATOR:
                        sc.SetState(SCE_PL_DEFAULT);
                        backFlag = BACK_OPERATOR;
                        backPos = sc.currentPos;
                        break;
                  case SCE_PL_IDENTIFIER:       // identifier, bareword, inputsymbol
                        if ((!setWord.Contains(sc.ch) && sc.ch != '\'')
                              || sc.Match('.', '.')
                              || sc.chPrev == '>') {  // end of inputsymbol
                              sc.SetState(SCE_PL_DEFAULT);
                        }
                        break;
                  case SCE_PL_WORD:       // keyword, plus special cases
                        if (!setWord.Contains(sc.ch)) {
                              char s[100];
                              sc.GetCurrent(s, sizeof(s));
                              if ((strcmp(s, "__DATA__") == 0) || (strcmp(s, "__END__") == 0)) {
                                    sc.ChangeState(SCE_PL_DATASECTION);
                              } else {
                                    if ((strcmp(s, "format") == 0)) {
                                          sc.SetState(SCE_PL_FORMAT_IDENT);
                                          HereDoc.State = 0;
                                    } else {
                                          sc.SetState(SCE_PL_DEFAULT);
                                    }
                                    backFlag = BACK_KEYWORD;
                                    backPos = sc.currentPos;
                              }
                        }
                        break;
                  case SCE_PL_SCALAR:
                  case SCE_PL_ARRAY:
                  case SCE_PL_HASH:
                  case SCE_PL_SYMBOLTABLE:
                        if (sc.Match(':', ':')) {     // skip ::
                              sc.Forward();
                        } else if (!setVar.Contains(sc.ch)) {
                              if (sc.LengthCurrent() == 1) {
                                    // Special variable: $(, $_ etc.
                                    sc.Forward();
                              }
                              sc.SetState(SCE_PL_DEFAULT);
                        }
                        break;
                  case SCE_PL_NUMBER:
                        // if no early break, number style is terminated at "(go through)"
                        if (sc.ch == '.') {
                              if (sc.chNext == '.') {
                                    // double dot is always an operator (go through)
                              } else if (numState <= PERLNUM_FLOAT_EXP) {
                                    // non-decimal number or float exponent, consume next dot
                                    sc.SetState(SCE_PL_OPERATOR);
                                    break;
                              } else {    // decimal or vectors allows dots
                                    dotCount++;
                                    if (numState == PERLNUM_DECIMAL) {
                                          if (dotCount <= 1)      // number with one dot in it
                                                break;
                                          if (IsADigit(sc.chNext)) {    // really a vector
                                                numState = PERLNUM_VECTOR;
                                                break;
                                          }
                                          // number then dot (go through)
                                    } else if (IsADigit(sc.chNext))     // vectors
                                          break;
                                    // vector then dot (go through)
                              }
                        } else if (sc.ch == '_') {
                              // permissive underscoring for number and vector literals
                              break;
                        } else if (numState == PERLNUM_DECIMAL) {
                              if (sc.ch == 'E' || sc.ch == 'e') { // exponent, sign
                                    numState = PERLNUM_FLOAT_EXP;
                                    if (sc.chNext == '+' || sc.chNext == '-') {
                                          sc.Forward();
                                    }
                                    break;
                              } else if (IsADigit(sc.ch))
                                    break;
                              // number then word (go through)
                        } else if (numState == PERLNUM_HEX) {
                              if (IsADigit(sc.ch, 16))
                                    break;
                        } else if (numState == PERLNUM_VECTOR || numState == PERLNUM_V_VECTOR) {
                              if (IsADigit(sc.ch))    // vector
                                    break;
                              if (setWord.Contains(sc.ch) && dotCount == 0) { // change to word
                                    sc.ChangeState(SCE_PL_IDENTIFIER);
                                    break;
                              }
                              // vector then word (go through)
                        } else if (IsADigit(sc.ch)) {
                              if (numState == PERLNUM_FLOAT_EXP) {
                                    break;
                              } else if (numState == PERLNUM_OCTAL) {
                                    if (sc.ch <= '7') break;
                              } else if (numState == PERLNUM_BINARY) {
                                    if (sc.ch <= '1') break;
                              }
                              // mark invalid octal, binary numbers (go through)
                              numState = PERLNUM_BAD;
                              break;
                        }
                        // complete current number or vector
                        sc.ChangeState(actualNumStyle(numState));
                        sc.SetState(SCE_PL_DEFAULT);
                        break;
                  case SCE_PL_COMMENTLINE:
                        if (sc.atLineEnd) {
                              sc.SetState(SCE_PL_DEFAULT);
                        }
                        break;
                  case SCE_PL_HERE_DELIM:
                        if (HereDoc.State == 0) { // '<<' encountered
                              int delim_ch = sc.chNext;
                              int ws_skip = 0;
                              HereDoc.State = 1;      // pre-init HERE doc class
                              HereDoc.Quote = sc.chNext;
                              HereDoc.Quoted = false;
                              HereDoc.DelimiterLength = 0;
                              HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
                              if (IsASpaceOrTab(delim_ch)) {
                                    // skip whitespace; legal only for quoted delimiters
                                    unsigned int i = sc.currentPos + 1;
                                    while ((i < endPos) && IsASpaceOrTab(delim_ch)) {
                                          i++;
                                          delim_ch = static_cast<unsigned char>(styler.SafeGetCharAt(i));
                                    }
                                    ws_skip = i - sc.currentPos - 1;
                              }
                              if (delim_ch == '\'' || delim_ch == '"' || delim_ch == '`') {
                                    // a quoted here-doc delimiter; skip any whitespace
                                    sc.Forward(ws_skip + 1);
                                    HereDoc.Quote = delim_ch;
                                    HereDoc.Quoted = true;
                              } else if ((ws_skip == 0 && setNonHereDoc.Contains(sc.chNext))
                                             || ws_skip > 0) {
                                    // left shift << or <<= operator cases
                                    // restore position if operator
                                    sc.ChangeState(SCE_PL_OPERATOR);
                                    sc.ForwardSetState(SCE_PL_DEFAULT);
                                    backFlag = BACK_OPERATOR;
                                    backPos = sc.currentPos;
                                    HereDoc.State = 0;
                              } else {
                                    // specially handle initial '\' for identifier
                                    if (ws_skip == 0 && HereDoc.Quote == '\\')
                                          sc.Forward();
                                    // an unquoted here-doc delimiter, no special handling
                                    // (cannot be prefixed by spaces/tabs), or
                                    // symbols terminates; deprecated zero-length delimiter
                              }
                        } else if (HereDoc.State == 1) { // collect the delimiter
                              backFlag = BACK_NONE;
                              if (HereDoc.Quoted) { // a quoted here-doc delimiter
                                    if (sc.ch == HereDoc.Quote) { // closing quote => end of delimiter
                                          sc.ForwardSetState(SCE_PL_DEFAULT);
                                    } else if (!sc.atLineEnd) {
                                          if (sc.Match('\\', static_cast<char>(HereDoc.Quote))) { // escaped quote
                                                sc.Forward();
                                          }
                                          if (sc.ch != '\r') {    // skip CR if CRLF
                                                HereDoc.Append(sc.ch);
                                          }
                                    }
                              } else { // an unquoted here-doc delimiter
                                    if (setHereDocDelim.Contains(sc.ch)) {
                                          HereDoc.Append(sc.ch);
                                    } else {
                                          sc.SetState(SCE_PL_DEFAULT);
                                    }
                              }
                              if (HereDoc.DelimiterLength >= HERE_DELIM_MAX - 1) {
                                    sc.SetState(SCE_PL_ERROR);
                                    HereDoc.State = 0;
                              }
                        }
                        break;
                  case SCE_PL_HERE_Q:
                  case SCE_PL_HERE_QQ:
                  case SCE_PL_HERE_QX: {
                        // also implies HereDoc.State == 2
                        sc.Complete();
                        while (!sc.atLineEnd)
                              sc.Forward();
                        char s[HERE_DELIM_MAX];
                        sc.GetCurrent(s, sizeof(s));
                        if (isMatch(HereDoc.Delimiter, s)) {
                              sc.SetState(SCE_PL_DEFAULT);
                              backFlag = BACK_NONE;
                              HereDoc.State = 0;
                        }
                        } break;
                  case SCE_PL_POD:
                  case SCE_PL_POD_VERB: {
                        unsigned int fw = sc.currentPos;
                        int ln = styler.GetLine(fw);
                        if (sc.atLineStart && sc.Match("=cut")) { // end of POD
                              sc.SetState(SCE_PL_POD);
                              sc.Forward(4);
                              sc.SetState(SCE_PL_DEFAULT);
                              styler.SetLineState(ln, SCE_PL_POD);
                              break;
                        }
                        int pod = podLineScan(styler, fw, endPos);      // classify POD line
                        styler.SetLineState(ln, pod);
                        if (pod == SCE_PL_DEFAULT) {
                              if (sc.state == SCE_PL_POD_VERB) {
                                    unsigned int fw2 = fw;
                                    while (fw2 <= endPos && pod == SCE_PL_DEFAULT) {
                                          fw = fw2++; // penultimate line (last blank line)
                                          pod = podLineScan(styler, fw2, endPos);
                                          styler.SetLineState(styler.GetLine(fw2), pod);
                                    }
                                    if (pod == SCE_PL_POD) {      // truncate verbatim POD early
                                          sc.SetState(SCE_PL_POD);
                                    } else
                                          fw = fw2;
                              } else
                                    pod = SCE_PL_POD;
                        } else {
                              if (pod == SCE_PL_POD_VERB    // still part of current paragraph
                                  && (styler.GetLineState(ln - 1) == SCE_PL_POD)) {
                                    pod = SCE_PL_POD;
                                    styler.SetLineState(ln, pod);
                              } else if (pod == SCE_PL_POD
                                             && (styler.GetLineState(ln - 1) == SCE_PL_POD_VERB)) {
                                    pod = SCE_PL_POD_VERB;
                                    styler.SetLineState(ln, pod);
                              }
                              sc.SetState(pod);
                        }
                        sc.Forward(fw - sc.currentPos);     // commit style
                        } break;
                  case SCE_PL_REGEX:
                  case SCE_PL_STRING_QR:
                        if (Quote.Rep <= 0) {
                              if (!setModifiers.Contains(sc.ch))
                                    sc.SetState(SCE_PL_DEFAULT);
                        } else if (!Quote.Up && !IsASpace(sc.ch)) {
                              Quote.Open(sc.ch);
                        } else if (sc.ch == '\\' && Quote.Up != '\\') {
                              sc.Forward();
                        } else if (sc.ch == Quote.Down) {
                              Quote.Count--;
                              if (Quote.Count == 0)
                                    Quote.Rep--;
                        } else if (sc.ch == Quote.Up) {
                              Quote.Count++;
                        }
                        break;
                  case SCE_PL_REGSUBST:
                        if (Quote.Rep <= 0) {
                              if (!setModifiers.Contains(sc.ch))
                                    sc.SetState(SCE_PL_DEFAULT);
                        } else if (!Quote.Up && !IsASpace(sc.ch)) {
                              Quote.Open(sc.ch);
                        } else if (sc.ch == '\\' && Quote.Up != '\\') {
                              sc.Forward();
                        } else if (Quote.Count == 0 && Quote.Rep == 1) {
                              // We matched something like s(...) or tr{...}, Perl 5.10
                              // appears to allow almost any character for use as the
                              // next delimiters. Whitespace and comments are accepted in
                              // between, but we'll limit to whitespace here.
                              // For '#', if no whitespace in between, it's a delimiter.
                              if (IsASpace(sc.ch)) {
                                    // Keep going
                              } else if (sc.ch == '#' && IsASpaceOrTab(sc.chPrev)) {
                                    sc.SetState(SCE_PL_DEFAULT);
                              } else {
                                    Quote.Open(sc.ch);
                              }
                        } else if (sc.ch == Quote.Down) {
                              Quote.Count--;
                              if (Quote.Count == 0)
                                    Quote.Rep--;
                              if (Quote.Up == Quote.Down)
                                    Quote.Count++;
                        } else if (sc.ch == Quote.Up) {
                              Quote.Count++;
                        }
                        break;
                  case SCE_PL_STRING_Q:
                  case SCE_PL_STRING_QQ:
                  case SCE_PL_STRING_QX:
                  case SCE_PL_STRING_QW:
                  case SCE_PL_STRING:
                  case SCE_PL_CHARACTER:
                  case SCE_PL_BACKTICKS:
                        if (!Quote.Down && !IsASpace(sc.ch)) {
                              Quote.Open(sc.ch);
                        } else if (sc.ch == '\\' && Quote.Up != '\\') {
                              sc.Forward();
                        } else if (sc.ch == Quote.Down) {
                              Quote.Count--;
                              if (Quote.Count == 0)
                                    sc.ForwardSetState(SCE_PL_DEFAULT);
                        } else if (sc.ch == Quote.Up) {
                              Quote.Count++;
                        }
                        break;
                  case SCE_PL_SUB_PROTOTYPE: {
                        int i = 0;
                        // forward scan; must all be valid proto characters
                        while (setSubPrototype.Contains(sc.GetRelative(i)))
                              i++;
                        if (sc.GetRelative(i) == ')') {     // valid sub prototype
                              sc.Forward(i);
                              sc.ForwardSetState(SCE_PL_DEFAULT);
                        } else {
                              // abandon prototype, restart from '('
                              sc.ChangeState(SCE_PL_OPERATOR);
                              sc.SetState(SCE_PL_DEFAULT);
                        }
                        } break;
                  case SCE_PL_FORMAT: {
                        sc.Complete();
                        while (!sc.atLineEnd)
                              sc.Forward();
                        char s[10];
                        sc.GetCurrent(s, sizeof(s));
                        if (isMatch(".", s))
                              sc.SetState(SCE_PL_DEFAULT);
                        } break;
                  case SCE_PL_ERROR:
                        break;
            }
            // Needed for specific continuation styles (one follows the other)
            switch (sc.state) {
                  // continued from SCE_PL_WORD
                  case SCE_PL_FORMAT_IDENT:
                        // occupies HereDoc state 3 to avoid clashing with HERE docs
                        if (IsASpaceOrTab(sc.ch)) {         // skip whitespace
                              sc.ChangeState(SCE_PL_DEFAULT);
                              while (IsASpaceOrTab(sc.ch) && !sc.atLineEnd)
                                    sc.Forward();
                              sc.SetState(SCE_PL_FORMAT_IDENT);
                        }
                        if (setFormatStart.Contains(sc.ch)) {     // identifier or '='
                              if (sc.ch != '=') {
                                    do {
                                          sc.Forward();
                                    } while (setFormat.Contains(sc.ch));
                              }
                              while (IsASpaceOrTab(sc.ch) && !sc.atLineEnd)
                                    sc.Forward();
                              if (sc.ch == '=') {
                                    sc.ForwardSetState(SCE_PL_DEFAULT);
                                    HereDoc.State = 3;
                              } else {
                                    // invalid indentifier; inexact fallback, but hey
                                    sc.ChangeState(SCE_PL_IDENTIFIER);
                                    sc.SetState(SCE_PL_DEFAULT);
                              }
                        } else {
                              sc.ChangeState(SCE_PL_DEFAULT);     // invalid indentifier
                        }
                        backFlag = BACK_NONE;
                        break;
            }

            // Must check end of HereDoc states here before default state is handled
            if (HereDoc.State == 1 && sc.atLineEnd) {
                  // Begin of here-doc (the line after the here-doc delimiter):
                  // Lexically, the here-doc starts from the next line after the >>, but the
                  // first line of here-doc seem to follow the style of the last EOL sequence
                  int st_new = SCE_PL_HERE_QQ;
                  HereDoc.State = 2;
                  if (HereDoc.Quoted) {
                        if (sc.state == SCE_PL_HERE_DELIM) {
                              // Missing quote at end of string! We are stricter than perl.
                              // Colour here-doc anyway while marking this bit as an error.
                              sc.ChangeState(SCE_PL_ERROR);
                        }
                        switch (HereDoc.Quote) {
                              case '\'': st_new = SCE_PL_HERE_Q ; break;
                              case '"' : st_new = SCE_PL_HERE_QQ; break;
                              case '`' : st_new = SCE_PL_HERE_QX; break;
                        }
                  } else {
                        if (HereDoc.Quote == '\\')
                              st_new = SCE_PL_HERE_Q;
                  }
                  sc.SetState(st_new);
            }
            if (HereDoc.State == 3 && sc.atLineEnd) {
                  // Start of format body.
                  HereDoc.State = 0;
                  sc.SetState(SCE_PL_FORMAT);
            }

            // Determine if a new state should be entered.
            if (sc.state == SCE_PL_DEFAULT) {
                  if (IsADigit(sc.ch) ||
                        (IsADigit(sc.chNext) && (sc.ch == '.' || sc.ch == 'v'))) {
                        sc.SetState(SCE_PL_NUMBER);
                        backFlag = BACK_NONE;
                        numState = PERLNUM_DECIMAL;
                        dotCount = 0;
                        if (sc.ch == '0') {           // hex,bin,octal
                              if (sc.chNext == 'x') {
                                    numState = PERLNUM_HEX;
                              } else if (sc.chNext == 'b') {
                                    numState = PERLNUM_BINARY;
                              } else if (IsADigit(sc.chNext)) {
                                    numState = PERLNUM_OCTAL;
                              }
                              if (numState != PERLNUM_DECIMAL) {
                                    sc.Forward();
                              }
                        } else if (sc.ch == 'v') {          // vector
                              numState = PERLNUM_V_VECTOR;
                        }
                  } else if (setWord.Contains(sc.ch)) {
                        // if immediately prefixed by '::', always a bareword
                        sc.SetState(SCE_PL_WORD);
                        if (sc.chPrev == ':' && sc.GetRelative(-2) == ':') {
                              sc.ChangeState(SCE_PL_IDENTIFIER);
                        }
                        unsigned int bk = sc.currentPos;
                        unsigned int fw = sc.currentPos + 1;
                        // first check for possible quote-like delimiter
                        if (sc.ch == 's' && !setWord.Contains(sc.chNext)) {
                              sc.ChangeState(SCE_PL_REGSUBST);
                              Quote.New(2);
                        } else if (sc.ch == 'm' && !setWord.Contains(sc.chNext)) {
                              sc.ChangeState(SCE_PL_REGEX);
                              Quote.New();
                        } else if (sc.ch == 'q' && !setWord.Contains(sc.chNext)) {
                              sc.ChangeState(SCE_PL_STRING_Q);
                              Quote.New();
                        } else if (sc.ch == 'y' && !setWord.Contains(sc.chNext)) {
                              sc.ChangeState(SCE_PL_REGSUBST);
                              Quote.New(2);
                        } else if (sc.Match('t', 'r') && !setWord.Contains(sc.GetRelative(2))) {
                              sc.ChangeState(SCE_PL_REGSUBST);
                              Quote.New(2);
                              sc.Forward();
                              fw++;
                        } else if (sc.ch == 'q' && setQDelim.Contains(sc.chNext)
                                       && !setWord.Contains(sc.GetRelative(2))) {
                              if      (sc.chNext == 'q') sc.ChangeState(SCE_PL_STRING_QQ);
                              else if (sc.chNext == 'x') sc.ChangeState(SCE_PL_STRING_QX);
                              else if (sc.chNext == 'r') sc.ChangeState(SCE_PL_STRING_QR);
                              else sc.ChangeState(SCE_PL_STRING_QW);    // sc.chNext == 'w'
                              Quote.New();
                              sc.Forward();
                              fw++;
                        } else if (sc.ch == 'x' && (sc.chNext == '=' || // repetition
                                       !setWord.Contains(sc.chNext) ||
                                       (IsADigit(sc.chPrev) && IsADigit(sc.chNext)))) {
                              sc.ChangeState(SCE_PL_OPERATOR);
                        }
                        // if potentially a keyword, scan forward and grab word, then check
                        // if it's really one; if yes, disambiguation test is performed
                        // otherwise it is always a bareword and we skip a lot of scanning
                        if (sc.state == SCE_PL_WORD) {
                              while (setWord.Contains(static_cast<unsigned char>(styler.SafeGetCharAt(fw))))
                                    fw++;
                              if (!isPerlKeyword(styler.GetStartSegment(), fw, keywords, styler)) {
                                    sc.ChangeState(SCE_PL_IDENTIFIER);
                              }
                        }
                        // if already SCE_PL_IDENTIFIER, then no ambiguity, skip this
                        // for quote-like delimiters/keywords, attempt to disambiguate
                        // to select for bareword, change state -> SCE_PL_IDENTIFIER
                        if (sc.state != SCE_PL_IDENTIFIER && bk > 0) {
                              if (disambiguateBareword(styler, bk, fw, backFlag, backPos, endPos))
                                    sc.ChangeState(SCE_PL_IDENTIFIER);
                        }
                        backFlag = BACK_NONE;
                  } else if (sc.ch == '#') {
                        sc.SetState(SCE_PL_COMMENTLINE);
                  } else if (sc.ch == '\"') {
                        sc.SetState(SCE_PL_STRING);
                        Quote.New();
                        Quote.Open(sc.ch);
                        backFlag = BACK_NONE;
                  } else if (sc.ch == '\'') {
                        if (sc.chPrev == '&' && setWordStart.Contains(sc.chNext)) {
                              // Archaic call
                              sc.SetState(SCE_PL_IDENTIFIER);
                        } else {
                              sc.SetState(SCE_PL_CHARACTER);
                              Quote.New();
                              Quote.Open(sc.ch);
                        }
                        backFlag = BACK_NONE;
                  } else if (sc.ch == '`') {
                        sc.SetState(SCE_PL_BACKTICKS);
                        Quote.New();
                        Quote.Open(sc.ch);
                        backFlag = BACK_NONE;
                  } else if (sc.ch == '$') {
                        sc.SetState(SCE_PL_SCALAR);
                        if (sc.chNext == '{') {
                              sc.ForwardSetState(SCE_PL_OPERATOR);
                        } else if (IsASpace(sc.chNext)) {
                              sc.ForwardSetState(SCE_PL_DEFAULT);
                        } else {
                              sc.Forward();
                              if (sc.Match('`', '`') || sc.Match(':', ':')) {
                                    sc.Forward();
                              }
                        }
                        backFlag = BACK_NONE;
                  } else if (sc.ch == '@') {
                        sc.SetState(SCE_PL_ARRAY);
                        if (setArray.Contains(sc.chNext)) {
                              // no special treatment
                        } else if (sc.chNext == ':' && sc.GetRelative(2) == ':') {
                              sc.Forward(2);
                        } else if (sc.chNext == '{' || sc.chNext == '[') {
                              sc.ForwardSetState(SCE_PL_OPERATOR);
                        } else {
                              sc.ChangeState(SCE_PL_OPERATOR);
                        }
                        backFlag = BACK_NONE;
                  } else if (setPreferRE.Contains(sc.ch)) {
                        // Explicit backward peeking to set a consistent preferRE for
                        // any slash found, so no longer need to track preferRE state.
                        // Find first previous significant lexed element and interpret.
                        // A few symbols shares this code for disambiguation.
                        bool preferRE = false;
                        bool isHereDoc = sc.Match('<', '<');
                        bool hereDocSpace = false;          // for: SCALAR [whitespace] '<<'
                        unsigned int bk = (sc.currentPos > 0) ? sc.currentPos - 1: 0;
                        unsigned int bkend;
                        sc.Complete();
                        styler.Flush();
                        if (styler.StyleAt(bk) == SCE_PL_DEFAULT)
                              hereDocSpace = true;
                        skipWhitespaceComment(styler, bk);
                        if (bk == 0) {
                              // avoid backward scanning breakage
                              preferRE = true;
                        } else {
                              int bkstyle = styler.StyleAt(bk);
                              int bkch = static_cast<unsigned char>(styler.SafeGetCharAt(bk));
                              switch(bkstyle) {
                              case SCE_PL_OPERATOR:
                                    preferRE = true;
                                    if (bkch == ')' || bkch == ']') {
                                          preferRE = false;
                                    } else if (bkch == '}') {
                                          // backtrack by counting balanced brace pairs
                                          // needed to test for variables like ${}, @{} etc.
                                          bkstyle = styleBeforeBracePair(styler, bk);
                                          if (bkstyle == SCE_PL_SCALAR
                                                || bkstyle == SCE_PL_ARRAY
                                                || bkstyle == SCE_PL_HASH
                                                || bkstyle == SCE_PL_SYMBOLTABLE
                                                || bkstyle == SCE_PL_OPERATOR) {
                                                preferRE = false;
                                          }
                                    } else if (bkch == '+' || bkch == '-') {
                                          if (bkch == static_cast<unsigned char>(styler.SafeGetCharAt(bk - 1))
                                                && bkch != static_cast<unsigned char>(styler.SafeGetCharAt(bk - 2)))
                                          // exceptions for operators: unary suffixes ++, --
                                          preferRE = false;
                                    }
                                    break;
                              case SCE_PL_IDENTIFIER:
                                    preferRE = true;
                                    bkstyle = styleCheckIdentifier(styler, bk);
                                    if ((bkstyle == 1) || (bkstyle == 2)) {
                                          // inputsymbol or var with "->" or "::" before identifier
                                          preferRE = false;
                                    } else if (bkstyle == 3) {
                                          // bare identifier, test cases follows:
                                          if (sc.ch == '/') {
                                                // if '/', /PATTERN/ unless digit/space immediately after '/'
                                                // if '//', always expect defined-or operator to follow identifier
                                                if (IsASpace(sc.chNext) || IsADigit(sc.chNext) || sc.chNext == '/')
                                                      preferRE = false;
                                          } else if (sc.ch == '*' || sc.ch == '%') {
                                                if (IsASpace(sc.chNext) || IsADigit(sc.chNext) || sc.Match('*', '*'))
                                                      preferRE = false;
                                          } else if (sc.ch == '<') {
                                                if (IsASpace(sc.chNext) || sc.chNext == '=')
                                                      preferRE = false;
                                          }
                                    }
                                    break;
                              case SCE_PL_SCALAR:           // for $var<< case:
                                    if (isHereDoc && hereDocSpace)      // if SCALAR whitespace '<<', *always* a HERE doc
                                          preferRE = true;
                                    break;
                              case SCE_PL_WORD:
                                    preferRE = true;
                                    // for HERE docs, always true
                                    if (sc.ch == '/') {
                                          // adopt heuristics similar to vim-style rules:
                                          // keywords always forced as /PATTERN/: split, if, elsif, while
                                          // everything else /PATTERN/ unless digit/space immediately after '/'
                                          // for '//', defined-or favoured unless special keywords
                                          bkend = bk + 1;
                                          while (bk > 0 && styler.StyleAt(bk - 1) == SCE_PL_WORD) {
                                                bk--;
                                          }
                                          if (isPerlKeyword(bk, bkend, reWords, styler))
                                                break;
                                          if (IsASpace(sc.chNext) || IsADigit(sc.chNext) || sc.chNext == '/')
                                                preferRE = false;
                                    } else if (sc.ch == '*' || sc.ch == '%') {
                                          if (IsASpace(sc.chNext) || IsADigit(sc.chNext) || sc.Match('*', '*'))
                                                preferRE = false;
                                    } else if (sc.ch == '<') {
                                          if (IsASpace(sc.chNext) || sc.chNext == '=')
                                                preferRE = false;
                                    }
                                    break;
                              // other styles uses the default, preferRE=false
                              case SCE_PL_POD:
                              case SCE_PL_HERE_Q:
                              case SCE_PL_HERE_QQ:
                              case SCE_PL_HERE_QX:
                                    preferRE = true;
                                    break;
                              }
                        }
                        backFlag = BACK_NONE;
                        if (isHereDoc) {  // handle '<<', HERE doc
                              if (preferRE) {
                                    sc.SetState(SCE_PL_HERE_DELIM);
                                    HereDoc.State = 0;
                              } else {          // << operator
                                    sc.SetState(SCE_PL_OPERATOR);
                                    sc.Forward();
                              }
                        } else if (sc.ch == '*') {    // handle '*', typeglob
                              if (preferRE) {
                                    sc.SetState(SCE_PL_SYMBOLTABLE);
                                    if (sc.chNext == ':' && sc.GetRelative(2) == ':') {
                                          sc.Forward(2);
                                    } else if (sc.chNext == '{') {
                                          sc.ForwardSetState(SCE_PL_OPERATOR);
                                    } else {
                                          sc.Forward();
                                    }
                              } else {
                                    sc.SetState(SCE_PL_OPERATOR);
                                    if (sc.chNext == '*')   // exponentiation
                                          sc.Forward();
                              }
                        } else if (sc.ch == '%') {    // handle '%', hash
                              if (preferRE) {
                                    sc.SetState(SCE_PL_HASH);
                                    if (setHash.Contains(sc.chNext)) {
                                          sc.Forward();
                                    } else if (sc.chNext == ':' && sc.GetRelative(2) == ':') {
                                          sc.Forward(2);
                                    } else if (sc.chNext == '{') {
                                          sc.ForwardSetState(SCE_PL_OPERATOR);
                                    } else {
                                          sc.ChangeState(SCE_PL_OPERATOR);
                                    }
                              } else {
                                    sc.SetState(SCE_PL_OPERATOR);
                              }
                        } else if (sc.ch == '<') {    // handle '<', inputsymbol
                              if (preferRE) {
                                    // forward scan
                                    int i = inputsymbolScan(styler, sc.currentPos, endPos);
                                    if (i > 0) {
                                          sc.SetState(SCE_PL_IDENTIFIER);
                                          sc.Forward(i);
                                    } else {
                                          sc.SetState(SCE_PL_OPERATOR);
                                    }
                              } else {
                                    sc.SetState(SCE_PL_OPERATOR);
                              }
                        } else {                // handle '/', regexp
                              if (preferRE) {
                                    sc.SetState(SCE_PL_REGEX);
                                    Quote.New();
                                    Quote.Open(sc.ch);
                              } else {          // / and // operators
                                    sc.SetState(SCE_PL_OPERATOR);
                                    if (sc.chNext == '/') {
                                          sc.Forward();
                                    }
                              }
                        }
                  } else if (sc.ch == '='       // POD
                                 && setPOD.Contains(sc.chNext)
                                 && sc.atLineStart) {
                        sc.SetState(SCE_PL_POD);
                        backFlag = BACK_NONE;
                  } else if (sc.ch == '-' && setWordStart.Contains(sc.chNext)) {    // extended '-' cases
                        unsigned int bk = sc.currentPos;
                        unsigned int fw = 2;
                        if (setSingleCharOp.Contains(sc.chNext) &&      // file test operators
                              !setWord.Contains(sc.GetRelative(2))) {
                              sc.SetState(SCE_PL_WORD);
                        } else {
                              // nominally a minus and bareword; find extent of bareword
                              while (setWord.Contains(sc.GetRelative(fw)))
                                    fw++;
                              sc.SetState(SCE_PL_OPERATOR);
                        }
                        // force to bareword for hash key => or {variable literal} cases
                        if (disambiguateBareword(styler, bk, bk + fw, backFlag, backPos, endPos) & 2) {
                              sc.ChangeState(SCE_PL_IDENTIFIER);
                        }
                        backFlag = BACK_NONE;
                  } else if (sc.ch == '(' && sc.currentPos > 0) { // '(' or subroutine prototype
                        sc.Complete();
                        if (styleCheckSubPrototype(styler, sc.currentPos - 1)) {
                              sc.SetState(SCE_PL_SUB_PROTOTYPE);
                              backFlag = BACK_NONE;
                        } else {
                              sc.SetState(SCE_PL_OPERATOR);
                        }
                  } else if (setPerlOperator.Contains(sc.ch)) {   // operators
                        sc.SetState(SCE_PL_OPERATOR);
                        if (sc.Match('.', '.')) {     // .. and ...
                              sc.Forward();
                              if (sc.chNext == '.') sc.Forward();
                        }
                  } else if (sc.ch == 4 || sc.ch == 26) {         // ^D and ^Z ends valid perl source
                        sc.SetState(SCE_PL_DATASECTION);
                  } else {
                        // keep colouring defaults
                        sc.Complete();
                  }
            }
      }
      sc.Complete();
}

static bool IsCommentLine(int line, Accessor &styler) {
      int pos = styler.LineStart(line);
      int eol_pos = styler.LineStart(line + 1) - 1;
      for (int i = pos; i < eol_pos; i++) {
            char ch = styler[i];
            int style = styler.StyleAt(i);
            if (ch == '#' && style == SCE_PL_COMMENTLINE)
                  return true;
            else if (!IsASpaceOrTab(ch))
                  return false;
      }
      return false;
}

static void FoldPerlDoc(unsigned int startPos, int length, int, WordList *[],
                        Accessor &styler) {
      bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
      bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
      // Custom folding of POD and packages

      // property fold.perl.pod 
      //    Enable folding Pod blocks when using the Perl lexer. 
      bool foldPOD = styler.GetPropertyInt("fold.perl.pod", 1) != 0;

      // property fold.perl.package 
      //    Enable folding packages when using the Perl lexer. 
      bool foldPackage = styler.GetPropertyInt("fold.perl.package", 1) != 0;

      unsigned int endPos = startPos + length;
      int visibleChars = 0;
      int lineCurrent = styler.GetLine(startPos);
      int levelPrev = SC_FOLDLEVELBASE;
      if (lineCurrent > 0)
            levelPrev = styler.LevelAt(lineCurrent - 1) >> 16;
      int levelCurrent = levelPrev;
      char chNext = styler[startPos];
      char chPrev = styler.SafeGetCharAt(startPos - 1);
      int styleNext = styler.StyleAt(startPos);
      // Used at end of line to determine if the line was a package definition
      bool isPackageLine = false;
      bool isPodHeading = false;
      for (unsigned int i = startPos; i < endPos; i++) {
            char ch = chNext;
            chNext = styler.SafeGetCharAt(i + 1);
            int style = styleNext;
            styleNext = styler.StyleAt(i + 1);
            bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
            bool atLineStart = ((chPrev == '\r') || (chPrev == '\n')) || i == 0;
            // Comment folding
            if (foldComment && atEOL && IsCommentLine(lineCurrent, styler))
            {
                  if (!IsCommentLine(lineCurrent - 1, styler)
                        && IsCommentLine(lineCurrent + 1, styler))
                        levelCurrent++;
                  else if (IsCommentLine(lineCurrent - 1, styler)
                               && !IsCommentLine(lineCurrent+1, styler))
                        levelCurrent--;
            }
            if (style == SCE_PL_OPERATOR) {
                  if (ch == '{') {
                        levelCurrent++;
                  } else if (ch == '}') {
                        levelCurrent--;
                  }
            }
            // Custom POD folding
            if (foldPOD && atLineStart) {
                  int stylePrevCh = (i) ? styler.StyleAt(i - 1):SCE_PL_DEFAULT;
                  if (style == SCE_PL_POD) {
                        if (stylePrevCh != SCE_PL_POD && stylePrevCh != SCE_PL_POD_VERB)
                              levelCurrent++;
                        else if (styler.Match(i, "=cut"))
                              levelCurrent--;
                        else if (styler.Match(i, "=head"))
                              isPodHeading = true;
                  } else if (style == SCE_PL_DATASECTION) {
                        if (ch == '=' && isalpha(chNext) && levelCurrent == SC_FOLDLEVELBASE)
                              levelCurrent++;
                        else if (styler.Match(i, "=cut") && levelCurrent > SC_FOLDLEVELBASE)
                              levelCurrent--;
                        else if (styler.Match(i, "=head"))
                              isPodHeading = true;
                        // if package used or unclosed brace, level > SC_FOLDLEVELBASE!
                        // reset needed as level test is vs. SC_FOLDLEVELBASE
                        else if (styler.Match(i, "__END__"))
                              levelCurrent = SC_FOLDLEVELBASE;
                  }
            }
            // Custom package folding
            if (foldPackage && atLineStart) {
                  if (style == SCE_PL_WORD && styler.Match(i, "package")) {
                        isPackageLine = true;
                  }
            }

            if (atEOL) {
                  int lev = levelPrev;
                  if (isPodHeading) {
                        lev = levelPrev - 1;
                        lev |= SC_FOLDLEVELHEADERFLAG;
                        isPodHeading = false;
                  }
                  // Check if line was a package declaration
                  // because packages need "special" treatment
                  if (isPackageLine) {
                        lev = SC_FOLDLEVELBASE | SC_FOLDLEVELHEADERFLAG;
                        levelCurrent = SC_FOLDLEVELBASE + 1;
                        isPackageLine = false;
                  }
                  lev |= levelCurrent << 16;
                  if (visibleChars == 0 && foldCompact)
                        lev |= SC_FOLDLEVELWHITEFLAG;
                  if ((levelCurrent > levelPrev) && (visibleChars > 0))
                        lev |= SC_FOLDLEVELHEADERFLAG;
                  if (lev != styler.LevelAt(lineCurrent)) {
                        styler.SetLevel(lineCurrent, lev);
                  }
                  lineCurrent++;
                  levelPrev = levelCurrent;
                  visibleChars = 0;
            }
            if (!isspacechar(ch))
                  visibleChars++;
            chPrev = ch;
      }
      // Fill in the real level of the next line, keeping the current flags as they will be filled in later
      int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
      styler.SetLevel(lineCurrent, levelPrev | flagsNext);
}

static const char * const perlWordListDesc[] = {
      "Keywords",
      0
};

LexerModule lmPerl(SCLEX_PERL, ColourisePerlDoc, "perl", FoldPerlDoc, perlWordListDesc, 8);

Generated by  Doxygen 1.6.0   Back to index