diff options
Diffstat (limited to 'lexers/LexPerl.cxx')
-rw-r--r-- | lexers/LexPerl.cxx | 1305 |
1 files changed, 1305 insertions, 0 deletions
diff --git a/lexers/LexPerl.cxx b/lexers/LexPerl.cxx new file mode 100644 index 000000000..bca78f57c --- /dev/null +++ b/lexers/LexPerl.cxx @@ -0,0 +1,1305 @@ +// 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 == '=' && isascii(chNext) && 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); |