// Scintilla source code edit control
// Encoding: UTF-8
/** @file LexJulia.cxx
 ** Lexer for Julia.
 ** Reusing code from LexMatlab, LexPython and LexRust
 **
 ** Written by Bertrand Lacoste
 **
 **/
// Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#include <cstdlib>
#include <cassert>
#include <cstring>

#include <string>
#include <string_view>
#include <vector>
#include <map>
#include <algorithm>
#include <functional>

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

#include "StringCopy.h"
#include "PropSetSimple.h"
#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "CharacterCategory.h"
#include "LexerModule.h"
#include "OptionSet.h"
#include "DefaultLexer.h"

using namespace Scintilla;
using namespace Lexilla;

static const int MAX_JULIA_IDENT_CHARS = 1023;

// Options used for LexerJulia
struct OptionsJulia {
    bool fold;
    bool foldComment;
    bool foldCompact;
    bool foldDocstring;
    bool foldSyntaxBased;
    bool highlightTypeannotation;
    bool highlightLexerror;
	OptionsJulia() {
        fold = true;
        foldComment = true;
        foldCompact = false;
        foldDocstring = true;
        foldSyntaxBased = true;
        highlightTypeannotation = false;
        highlightLexerror = false;
	}
};

const char * const juliaWordLists[] = {
    "Primary keywords and identifiers",
    "Built in types",
    "Other keywords",
    "Built in functions",
    0,
};

struct OptionSetJulia : public OptionSet<OptionsJulia> {
	OptionSetJulia() {
		DefineProperty("fold", &OptionsJulia::fold);

		DefineProperty("fold.compact", &OptionsJulia::foldCompact);

		DefineProperty("fold.comment", &OptionsJulia::foldComment);

		DefineProperty("fold.julia.docstring", &OptionsJulia::foldDocstring,
			"Fold multiline triple-doublequote strings, usually used to document a function or type above the definition.");

		DefineProperty("fold.julia.syntax.based", &OptionsJulia::foldSyntaxBased,
			"Set this property to 0 to disable syntax based folding.");

		DefineProperty("lexer.julia.highlight.typeannotation", &OptionsJulia::highlightTypeannotation,
			"This option enables highlighting of the type identifier after `::`.");

		DefineProperty("lexer.julia.highlight.lexerror", &OptionsJulia::highlightLexerror,
			"This option enables highlighting of syntax error int character or number definition.");

		DefineWordListSets(juliaWordLists);
	}
};

LexicalClass juliaLexicalClasses[] = {
	// Lexer Julia SCLEX_JULIA SCE_JULIA_:
	0,  "SCE_JULIA_DEFAULT", "default", "White space",
	1,  "SCE_JULIA_COMMENT", "comment", "Comment",
	2,  "SCE_JULIA_NUMBER", "literal numeric", "Number",
	3,  "SCE_JULIA_KEYWORD1", "keyword", "Reserved keywords",
	4,  "SCE_JULIA_KEYWORD2", "identifier", "Builtin type names",
	5,  "SCE_JULIA_KEYWORD3", "identifier", "Constants",
	6,  "SCE_JULIA_CHAR", "literal string character", "Single quoted string",
	7,  "SCE_JULIA_OPERATOR", "operator", "Operator",
	8,  "SCE_JULIA_BRACKET", "bracket operator", "Bracket operator",
	9,  "SCE_JULIA_IDENTIFIER", "identifier", "Identifier",
	10, "SCE_JULIA_STRING", "literal string", "Double quoted String",
	11, "SCE_JULIA_SYMBOL", "literal string symbol", "Symbol",
	12, "SCE_JULIA_MACRO", "macro preprocessor", "Macro",
	13, "SCE_JULIA_STRINGINTERP", "literal string interpolated", "String interpolation",
	14, "SCE_JULIA_DOCSTRING", "literal string documentation", "Docstring",
	15, "SCE_JULIA_STRINGLITERAL", "literal string", "String literal prefix",
	16, "SCE_JULIA_COMMAND", "literal string command", "Command",
	17, "SCE_JULIA_COMMANDLITERAL", "literal string command", "Command literal prefix",
	18, "SCE_JULIA_TYPEANNOT", "identifier type", "Type annotation identifier",
	19, "SCE_JULIA_LEXERROR", "lexer error", "Lexing error",
	20, "SCE_JULIA_KEYWORD4", "identifier", "Builtin function names",
	21, "SCE_JULIA_TYPEOPERATOR", "operator type", "Type annotation operator",
};

class LexerJulia : public DefaultLexer {
	WordList keywords;
	WordList identifiers2;
	WordList identifiers3;
	WordList identifiers4;
	OptionsJulia options;
	OptionSetJulia osJulia;
public:
	explicit LexerJulia() :
		DefaultLexer("julia", SCLEX_JULIA, juliaLexicalClasses, ELEMENTS(juliaLexicalClasses)) {
	}
	virtual ~LexerJulia() {
	}
	void SCI_METHOD Release() override {
		delete this;
	}
	int SCI_METHOD Version() const override {
		return lvRelease5;
	}
	const char * SCI_METHOD PropertyNames() override {
		return osJulia.PropertyNames();
	}
	int SCI_METHOD PropertyType(const char *name) override {
		return osJulia.PropertyType(name);
	}
	const char * SCI_METHOD DescribeProperty(const char *name) override {
		return osJulia.DescribeProperty(name);
	}
	Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override;
	const char * SCI_METHOD PropertyGet(const char *key) override {
		return osJulia.PropertyGet(key);
	}
	const char * SCI_METHOD DescribeWordListSets() override {
		return osJulia.DescribeWordListSets();
	}
	Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override;
	void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
	void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
	void * SCI_METHOD PrivateCall(int, void *) override {
		return 0;
	}

	static ILexer5 *LexerFactoryJulia() {
		return new LexerJulia();
	}
};

Sci_Position SCI_METHOD LexerJulia::PropertySet(const char *key, const char *val) {
	if (osJulia.PropertySet(&options, key, val)) {
		return 0;
	}
	return -1;
}

Sci_Position SCI_METHOD LexerJulia::WordListSet(int n, const char *wl) {
	WordList *wordListN = nullptr;
	switch (n) {
	case 0:
		wordListN = &keywords;
		break;
	case 1:
		wordListN = &identifiers2;
		break;
	case 2:
		wordListN = &identifiers3;
		break;
	case 3:
		wordListN = &identifiers4;
		break;
	}
	Sci_Position firstModification = -1;
	if (wordListN) {
		if (wordListN->Set(wl)) {
			firstModification = 0;
		}
	}
	return firstModification;
}

static inline bool IsJuliaOperator(int ch) {
    if (ch == '%' || ch == '^' || ch == '&' || ch == '*' ||
        ch == '-' || ch == '+' || ch == '=' || ch == '|' ||
        ch == '<' || ch == '>' || ch == '/' || ch == '~' ||
        ch == '\\' ) {
        return true;
    }
    return false;
}

// The list contains non-ascii unary operators
static inline bool IsJuliaUnaryOperator (int ch) {
    if (ch == 0x00ac || ch == 0x221a || ch == 0x221b ||
        ch == 0x221c || ch == 0x22c6 || ch == 0x00b1 ||
        ch == 0x2213 ) {
        return true;
    }
    return false;
}

static inline bool IsJuliaParen (int ch) {
    if (ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
        ch == '[' || ch == ']' ) {
        return true;
    }
    return false;
}

// Unicode parsing from Julia source code:
// https://github.com/JuliaLang/julia/blob/master/src/flisp/julia_extensions.c
// keep the same function name to be easy to find again
static int is_wc_cat_id_start(uint32_t wc) {
    const CharacterCategory cat = CategoriseCharacter((int) wc);

    return (cat == ccLu || cat == ccLl ||
            cat == ccLt || cat == ccLm ||
            cat == ccLo || cat == ccNl ||
            cat == ccSc ||  // allow currency symbols
            // other symbols, but not arrows or replacement characters
            (cat == ccSo && !(wc >= 0x2190 && wc <= 0x21FF) &&
             wc != 0xfffc && wc != 0xfffd &&
             wc != 0x233f &&  // notslash
             wc != 0x00a6) || // broken bar

            // math symbol (category Sm) whitelist
            (wc >= 0x2140 && wc <= 0x2a1c &&
             ((wc >= 0x2140 && wc <= 0x2144) || // ⅀, ⅁, ⅂, ⅃, ⅄
              wc == 0x223f || wc == 0x22be || wc == 0x22bf || // ∿, ⊾, ⊿
              wc == 0x22a4 || wc == 0x22a5 ||   // ⊤ ⊥

              (wc >= 0x2200 && wc <= 0x2233 &&
               (wc == 0x2202 || wc == 0x2205 || wc == 0x2206 || // ∂, ∅, ∆
                wc == 0x2207 || wc == 0x220e || wc == 0x220f || // ∇, ∎, ∏
                wc == 0x2200 || wc == 0x2203 || wc == 0x2204 || // ∀, ∃, ∄
                wc == 0x2210 || wc == 0x2211 || // ∐, ∑
                wc == 0x221e || wc == 0x221f || // ∞, ∟
                wc >= 0x222b)) || // ∫, ∬, ∭, ∮, ∯, ∰, ∱, ∲, ∳

              (wc >= 0x22c0 && wc <= 0x22c3) ||  // N-ary big ops: ⋀, ⋁, ⋂, ⋃
              (wc >= 0x25F8 && wc <= 0x25ff) ||  // ◸, ◹, ◺, ◻, ◼, ◽, ◾, ◿

              (wc >= 0x266f &&
               (wc == 0x266f || wc == 0x27d8 || wc == 0x27d9 || // ♯, ⟘, ⟙
                (wc >= 0x27c0 && wc <= 0x27c1) ||  // ⟀, ⟁
                (wc >= 0x29b0 && wc <= 0x29b4) ||  // ⦰, ⦱, ⦲, ⦳, ⦴
                (wc >= 0x2a00 && wc <= 0x2a06) ||  // ⨀, ⨁, ⨂, ⨃, ⨄, ⨅, ⨆
                (wc >= 0x2a09 && wc <= 0x2a16) ||  // ⨉, ⨊, ⨋, ⨌, ⨍, ⨎, ⨏, ⨐, ⨑, ⨒, ⨓, ⨔, ⨕, ⨖
                wc == 0x2a1b || wc == 0x2a1c)))) || // ⨛, ⨜

            (wc >= 0x1d6c1 && // variants of \nabla and \partial
             (wc == 0x1d6c1 || wc == 0x1d6db ||
              wc == 0x1d6fb || wc == 0x1d715 ||
              wc == 0x1d735 || wc == 0x1d74f ||
              wc == 0x1d76f || wc == 0x1d789 ||
              wc == 0x1d7a9 || wc == 0x1d7c3)) ||

            // super- and subscript +-=()
            (wc >= 0x207a && wc <= 0x207e) ||
            (wc >= 0x208a && wc <= 0x208e) ||

            // angle symbols
            (wc >= 0x2220 && wc <= 0x2222) || // ∠, ∡, ∢
            (wc >= 0x299b && wc <= 0x29af) || // ⦛, ⦜, ⦝, ⦞, ⦟, ⦠, ⦡, ⦢, ⦣, ⦤, ⦥, ⦦, ⦧, ⦨, ⦩, ⦪, ⦫, ⦬, ⦭, ⦮, ⦯

            // Other_ID_Start
            wc == 0x2118 || wc == 0x212E || // ℘, ℮
            (wc >= 0x309B && wc <= 0x309C) || // katakana-hiragana sound marks

            // bold-digits and double-struck digits
            (wc >= 0x1D7CE && wc <= 0x1D7E1)); // 𝟎 through 𝟗 (inclusive), 𝟘 through 𝟡 (inclusive)
}

static inline bool IsIdentifierFirstCharacter (int ch) {
    if (IsASCII(ch)) {
        return (bool) (isalpha(ch) || ch == '_');
    }
    if (ch < 0xA1 || ch > 0x10ffff) {
        return false;
    }

    return is_wc_cat_id_start((uint32_t) ch);
}

static inline bool IsIdentifierCharacter (int ch) {
    if (IsASCII(ch)) {
        return (bool) (isalnum(ch) || ch == '_' || ch == '!');
    }
    if (ch < 0xA1 || ch > 0x10ffff) {
        return false;
    }

    if (is_wc_cat_id_start((uint32_t) ch)) {
        return true;
    }

    const CharacterCategory cat = CategoriseCharacter(ch);

    if (cat == ccMn || cat == ccMc ||
        cat == ccNd || cat == ccPc ||
        cat == ccSk || cat == ccMe ||
        cat == ccNo ||
        // primes (single, double, triple, their reverses, and quadruple)
        (ch >= 0x2032 && ch <= 0x2037) || (ch == 0x2057)) {
        return true;
    }
    return false;
}

// keep the same function name to be easy to find again
static const uint32_t opsuffs[] = {
   0x00b2, // ²
   0x00b3, // ³
   0x00b9, // ¹
   0x02b0, // ʰ
   0x02b2, // ʲ
   0x02b3, // ʳ
   0x02b7, // ʷ
   0x02b8, // ʸ
   0x02e1, // ˡ
   0x02e2, // ˢ
   0x02e3, // ˣ
   0x1d2c, // ᴬ
   0x1d2e, // ᴮ
   0x1d30, // ᴰ
   0x1d31, // ᴱ
   0x1d33, // ᴳ
   0x1d34, // ᴴ
   0x1d35, // ᴵ
   0x1d36, // ᴶ
   0x1d37, // ᴷ
   0x1d38, // ᴸ
   0x1d39, // ᴹ
   0x1d3a, // ᴺ
   0x1d3c, // ᴼ
   0x1d3e, // ᴾ
   0x1d3f, // ᴿ
   0x1d40, // ᵀ
   0x1d41, // ᵁ
   0x1d42, // ᵂ
   0x1d43, // ᵃ
   0x1d47, // ᵇ
   0x1d48, // ᵈ
   0x1d49, // ᵉ
   0x1d4d, // ᵍ
   0x1d4f, // ᵏ
   0x1d50, // ᵐ
   0x1d52, // ᵒ
   0x1d56, // ᵖ
   0x1d57, // ᵗ
   0x1d58, // ᵘ
   0x1d5b, // ᵛ
   0x1d5d, // ᵝ
   0x1d5e, // ᵞ
   0x1d5f, // ᵟ
   0x1d60, // ᵠ
   0x1d61, // ᵡ
   0x1d62, // ᵢ
   0x1d63, // ᵣ
   0x1d64, // ᵤ
   0x1d65, // ᵥ
   0x1d66, // ᵦ
   0x1d67, // ᵧ
   0x1d68, // ᵨ
   0x1d69, // ᵩ
   0x1d6a, // ᵪ
   0x1d9c, // ᶜ
   0x1da0, // ᶠ
   0x1da5, // ᶥ
   0x1da6, // ᶦ
   0x1dab, // ᶫ
   0x1db0, // ᶰ
   0x1db8, // ᶸ
   0x1dbb, // ᶻ
   0x1dbf, // ᶿ
   0x2032, // ′
   0x2033, // ″
   0x2034, // ‴
   0x2035, // ‵
   0x2036, // ‶
   0x2037, // ‷
   0x2057, // ⁗
   0x2070, // ⁰
   0x2071, // ⁱ
   0x2074, // ⁴
   0x2075, // ⁵
   0x2076, // ⁶
   0x2077, // ⁷
   0x2078, // ⁸
   0x2079, // ⁹
   0x207a, // ⁺
   0x207b, // ⁻
   0x207c, // ⁼
   0x207d, // ⁽
   0x207e, // ⁾
   0x207f, // ⁿ
   0x2080, // ₀
   0x2081, // ₁
   0x2082, // ₂
   0x2083, // ₃
   0x2084, // ₄
   0x2085, // ₅
   0x2086, // ₆
   0x2087, // ₇
   0x2088, // ₈
   0x2089, // ₉
   0x208a, // ₊
   0x208b, // ₋
   0x208c, // ₌
   0x208d, // ₍
   0x208e, // ₎
   0x2090, // ₐ
   0x2091, // ₑ
   0x2092, // ₒ
   0x2093, // ₓ
   0x2095, // ₕ
   0x2096, // ₖ
   0x2097, // ₗ
   0x2098, // ₘ
   0x2099, // ₙ
   0x209a, // ₚ
   0x209b, // ₛ
   0x209c, // ₜ
   0x2c7c, // ⱼ
   0x2c7d, // ⱽ
   0xa71b, // ꜛ
   0xa71c, // ꜜ
   0xa71d  // ꜝ
};
static const size_t opsuffs_len = sizeof(opsuffs) / (sizeof(uint32_t));

// keep the same function name to be easy to find again
static bool jl_op_suffix_char(uint32_t wc) {
    if (wc < 0xA1 || wc > 0x10ffff) {
        return false;
    }
    const CharacterCategory cat = CategoriseCharacter((int) wc);
    if (cat == ccMn || cat == ccMc ||
        cat == ccMe) {
        return true;
    }

    for (size_t i = 0; i < opsuffs_len; ++i) {
        if (wc == opsuffs[i]) {
            return true;
        }
    }
    return false;
}

// keep the same function name to be easy to find again
static bool never_id_char(uint32_t wc) {
     const CharacterCategory cat = CategoriseCharacter((int) wc);
     return (
          // spaces and control characters:
          (cat >= ccZs && cat <= ccCs) ||

          // ASCII and Latin1 non-connector punctuation
          (wc < 0xff &&
           cat >= ccPd && cat <= ccPo) ||

          wc == '`' ||

          // mathematical brackets
          (wc >= 0x27e6 && wc <= 0x27ef) ||
          // angle, corner, and lenticular brackets
          (wc >= 0x3008 && wc <= 0x3011) ||
          // tortoise shell, square, and more lenticular brackets
          (wc >= 0x3014 && wc <= 0x301b) ||
          // fullwidth parens
          (wc == 0xff08 || wc == 0xff09) ||
          // fullwidth square brackets
          (wc == 0xff3b || wc == 0xff3d));
}


static bool IsOperatorFirstCharacter (int ch) {
    if (IsASCII(ch)) {
        if (IsJuliaOperator(ch) ||
            ch == '!' || ch == '?' ||
            ch == ':' || ch == ';' ||
            ch == ',' || ch == '.' ) {
            return true;
        }else {
            return false;
        }
    } else if (is_wc_cat_id_start((uint32_t) ch)) {
        return false;
    } else if (IsJuliaUnaryOperator(ch) ||
               ! never_id_char((uint32_t) ch)) {
        return true;
    }
    return false;
}

static bool IsOperatorCharacter (int ch) {
    if (IsOperatorFirstCharacter(ch) ||
        (!IsASCII(ch) && jl_op_suffix_char((uint32_t) ch)) ) {
        return true;
    }
    return false;
}

static bool CheckBoundsIndexing(char *str) {
    if (strcmp("begin", str) == 0 || strcmp("end", str) == 0 ) {
        return true;
    }
    return false;
}

static int CheckKeywordFoldPoint(char *str) {
    if (strcmp ("if", str) == 0 ||
        strcmp ("for", str) == 0 ||
        strcmp ("while", str) == 0 ||
        strcmp ("try", str) == 0 ||
        strcmp ("do", str) == 0 ||
        strcmp ("begin", str) == 0 ||
        strcmp ("let", str) == 0 ||
        strcmp ("baremodule", str) == 0 ||
        strcmp ("quote", str) == 0 ||
        strcmp ("module", str) == 0 ||
        strcmp ("struct", str) == 0 ||
        strcmp ("type", str) == 0 ||
        strcmp ("macro", str) == 0 ||
        strcmp ("function", str) == 0) {
        return 1;
    }
    if (strcmp("end", str) == 0) {
        return -1;
    }
    return 0;
}

static bool IsNumberExpon(int ch, int base) {
    if ((base == 10 && (ch == 'e' || ch == 'E' || ch == 'f')) ||
        (base == 16 && (ch == 'p' || ch == 'P'))) {
        return true;
    }
    return false;
}

/* Scans a sequence of digits, returning true if it found any. */
static bool ScanDigits(StyleContext& sc, int base, bool allow_sep) {
	bool found = false;
    for (;;) {
		if (IsADigit(sc.chNext, base) || (allow_sep && sc.chNext == '_')) {
			found = true;
            sc.Forward();
		} else {
			break;
        }
	}
	return found;
}

static inline bool ScanNHexas(StyleContext &sc, int max) {
    int n = 0;
    bool error = false;

    sc.Forward();
    if (!IsADigit(sc.ch, 16)) {
        error = true;
    } else {
        while (IsADigit(sc.ch, 16) && n < max) {
            sc.Forward();
            n++;
        }
    }
    return error;
}

static void resumeCharacter(StyleContext &sc, bool lexerror) {
    bool error = false;

    //  ''' case
    if (sc.chPrev == '\'' && sc.ch == '\'' && sc.chNext == '\'') {
        sc.Forward();
        sc.ForwardSetState(SCE_JULIA_DEFAULT);
        return;
    } else if (lexerror && sc.chPrev == '\'' && sc.ch == '\'') {
        sc.ChangeState(SCE_JULIA_LEXERROR);
        sc.ForwardSetState(SCE_JULIA_DEFAULT);

    // Escape characters
    } else if (sc.ch == '\\') {
        sc.Forward();
        if (sc.ch == '\'' || sc.ch == '\\' ) {
            sc.Forward();
        } else if (sc.ch == 'n' || sc.ch == 't' || sc.ch == 'a' ||
                   sc.ch == 'b' || sc.ch == 'e' || sc.ch == 'f' ||
                   sc.ch == 'r' || sc.ch == 'v' ) {
            sc.Forward();
        } else if (sc.ch == 'x') {
            error |= ScanNHexas(sc, 2);
        } else if (sc.ch == 'u') {
            error |= ScanNHexas(sc, 4);
        } else if (sc.ch == 'U') {
            error |= ScanNHexas(sc, 8);
        } else if (IsADigit(sc.ch, 8)) {
            int n = 1;
            int max = 3;
            sc.Forward();
            while (IsADigit(sc.ch, 8) && n < max) {
                sc.Forward();
                n++;
            }
        }

        if (lexerror) {
            if (sc.ch != '\'') {
                error = true;
                while (sc.ch != '\'' &&
                       sc.ch != '\r' &&
                       sc.ch != '\n') {
                    sc.Forward();
                }
            }

            if (error) {
                sc.ChangeState(SCE_JULIA_LEXERROR);
                sc.ForwardSetState(SCE_JULIA_DEFAULT);
            }
        }
    } else if (lexerror) {
        if (sc.ch < 0x20 || sc.ch > 0x10ffff) {
            error = true;
        } else {
            // single character
            sc.Forward();

            if (sc.ch != '\'') {
                error = true;
                while (sc.ch != '\'' &&
                       sc.ch != '\r' &&
                       sc.ch != '\n') {
                    sc.Forward();
                }
            }
        }

        if (error) {
            sc.ChangeState(SCE_JULIA_LEXERROR);
            sc.ForwardSetState(SCE_JULIA_DEFAULT);
        }
    }

    // closing quote
    if (sc.ch == '\'') {
        if (sc.chNext == '\'') {
            sc.Forward();
        } else {
            sc.ForwardSetState(SCE_JULIA_DEFAULT);
        }
    }
}

static inline bool IsACharacter(StyleContext &sc) {
    return (sc.chPrev == '\'' && sc.chNext == '\'');
}

static void ScanParenInterpolation(StyleContext &sc) {
    // TODO: no syntax highlighting inside a string interpolation

    // Level of nested parenthesis
    int interp_level = 0;

    // If true, it is inside a string and parenthesis are not counted.
    bool allow_paren_string = false;


    // check for end of states
    for (; sc.More(); sc.Forward()) {
        // TODO: check corner cases for nested string interpolation
        // TODO: check corner cases with Command inside interpolation

        if ( sc.ch == '\"' && sc.chPrev != '\\') {
            // Toggle the string environment (parenthesis are not counted inside a string)
            allow_paren_string = !allow_paren_string;
        } else if ( !allow_paren_string ) {
            if ( sc.ch == '(' && !IsACharacter(sc) ) {
                interp_level ++;
            } else if ( sc.ch == ')' && !IsACharacter(sc) && interp_level > 0 ) {
                interp_level --;
                if (interp_level == 0) {
                    // Exit interpolation
                    return;
                }
            }
        }
    }
}
/*
 * Start parsing a number, parse the base.
 */
static void initNumber (StyleContext &sc, int &base, bool &with_dot) {
    base = 10;
    with_dot = false;
    sc.SetState(SCE_JULIA_NUMBER);
    if (sc.ch == '0') {
        if (sc.chNext == 'x') {
            sc.Forward();
            base = 16;
            if (sc.chNext == '.') {
                sc.Forward();
                with_dot = true;
            }
        } else if (sc.chNext == 'o') {
            sc.Forward();
            base = 8;
        } else if (sc.chNext == 'b') {
            sc.Forward();
            base = 2;
        }
    } else if (sc.ch == '.') {
        with_dot = true;
    }
}

/*
 * Resume parsing a String or Command, bounded by the `quote` character (\" or \`)
 * The `triple` argument specifies if it is a triple-quote String or Command.
 * Interpolation is detected (with `$`), and parsed if `allow_interp` is true.
 */
static void resumeStringLike(StyleContext &sc, int quote, bool triple, bool allow_interp, bool full_highlight) {
    int stylePrev = sc.state;
    bool checkcurrent = false;

    // Escape characters
    if (sc.ch == '\\') {
        if (sc.chNext == quote || sc.chNext == '\\' || sc.chNext == '$') {
            sc.Forward();
        }
    } else if (allow_interp && sc.ch == '$') {
        // If the interpolation is only of a variable, do not change state
        if (sc.chNext == '(') {
            if (full_highlight) {
                sc.SetState(SCE_JULIA_STRINGINTERP);
            } else {
                sc.ForwardSetState(SCE_JULIA_STRINGINTERP);
            }
            ScanParenInterpolation(sc);
            sc.ForwardSetState(stylePrev);

            checkcurrent = true;

        } else if (full_highlight && IsIdentifierFirstCharacter(sc.chNext)) {
            sc.SetState(SCE_JULIA_STRINGINTERP);
            sc.Forward();
            sc.Forward();
            for (; sc.More(); sc.Forward()) {
                if (! IsIdentifierCharacter(sc.ch)) {
                    break;
                }
            }
            sc.SetState(stylePrev);

            checkcurrent = true;
        }

        if (checkcurrent) {
            // Check that the current character is not a special char,
            // otherwise it will be skipped
            resumeStringLike(sc, quote, triple, allow_interp, full_highlight);
        }

    } else if (sc.ch == quote) {
        if (triple) {
            if (sc.chNext == quote && sc.GetRelativeCharacter(2) == quote) {
                // Move to the end of the triple quotes
                Sci_PositionU nextIndex = sc.currentPos + 2;
                while (nextIndex > sc.currentPos && sc.More()) {
                    sc.Forward();
                }
                sc.ForwardSetState(SCE_JULIA_DEFAULT);
            }
        } else {
            sc.ForwardSetState(SCE_JULIA_DEFAULT);
        }
    }
}

static void resumeCommand(StyleContext &sc, bool triple, bool allow_interp) {
    return resumeStringLike(sc, '`', triple, allow_interp, true);
}

static void resumeString(StyleContext &sc, bool triple, bool allow_interp) {
    return resumeStringLike(sc, '"', triple, allow_interp, true);
}

static void resumeNumber (StyleContext &sc, int base, bool &with_dot, bool lexerror) {
    if (IsNumberExpon(sc.ch, base)) {
        if (IsADigit(sc.chNext) || sc.chNext == '+' || sc.chNext == '-') {
            sc.Forward();
            // Capture all digits
            ScanDigits(sc, 10, false);
            sc.Forward();
        }
        sc.SetState(SCE_JULIA_DEFAULT);
    } else if (sc.ch == '.' && sc.chNext == '.') {
        // Interval operator `..`
        sc.SetState(SCE_JULIA_OPERATOR);
        sc.Forward();
        sc.ForwardSetState(SCE_JULIA_DEFAULT);
    } else if (sc.ch == '.' && !with_dot) {
        with_dot = true;
        ScanDigits(sc, base, true);
    } else if (IsADigit(sc.ch, base) || sc.ch == '_') {
        ScanDigits(sc, base, true);
    } else if (IsADigit(sc.ch) && !IsADigit(sc.ch, base)) {
        if (lexerror) {
            sc.ChangeState(SCE_JULIA_LEXERROR);
        }
        ScanDigits(sc, 10, false);
        sc.ForwardSetState(SCE_JULIA_DEFAULT);
    } else {
        sc.SetState(SCE_JULIA_DEFAULT);
    }
}

static void resumeOperator (StyleContext &sc) {
    if (sc.chNext == ':' && (sc.ch == ':' || sc.ch == '<' ||
                    (sc.ch == '>' && (sc.chPrev != '-' && sc.chPrev != '=')))) {
        // Case `:a=>:b`
        sc.Forward();
        sc.ForwardSetState(SCE_JULIA_DEFAULT);
    } else if (sc.ch == ':') {
        // Case `foo(:baz,:baz)` or `:one+:two`
        // Let the default case switch decide if it is a symbol
        sc.SetState(SCE_JULIA_DEFAULT);
    } else if (sc.ch == '\'') {
        sc.SetState(SCE_JULIA_DEFAULT);
    } else if ((sc.ch == '.' && sc.chPrev != '.') || IsIdentifierFirstCharacter(sc.ch) ||
               (! (sc.chPrev == '.' && IsOperatorFirstCharacter(sc.ch)) &&
                ! IsOperatorCharacter(sc.ch)) ) {
        sc.SetState(SCE_JULIA_DEFAULT);
    }
}

void SCI_METHOD LexerJulia::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
	PropSetSimple props;
	Accessor styler(pAccess, &props);

	Sci_Position pos = startPos;
	styler.StartAt(pos);
	styler.StartSegment(pos);

    // use the line state of each line to store block/multiline states
    Sci_Position curLine = styler.GetLine(startPos);
    // Default is false for everything and 0 counters.
	int lineState = (curLine > 0) ? styler.GetLineState(curLine-1) : 0;

    bool transpose = (lineState >> 0) & 0x01;                // 1 bit to know if ' is allowed to mean transpose
    bool istripledocstring = (lineState >> 1) & 0x01;        // 1 bit to know if we are in a triple doublequotes string
	bool triple_backtick = (lineState >> 2) & 0x01;          // 1 bit to know if we are in a triple backtick command
	bool israwstring = (lineState >> 3) & 0x01;              // 1 bit to know if we are in a raw string
    int indexing_level = (int)((lineState >> 4) & 0x0F);     // 4 bits of bracket nesting counter
    int list_comprehension = (int)((lineState >> 8) & 0x0F); // 4 bits of parenthesis nesting counter
    int commentDepth = (int)((lineState >> 12) & 0x0F);      // 4 bits of nested comment counter

    // base for parsing number
    int base = 10;
    // number has a float dot ?
    bool with_dot = false;

    StyleContext sc(startPos, length, initStyle, styler);

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

        //// check for end of states
        switch (sc.state) {
            case SCE_JULIA_BRACKET:
                sc.SetState(SCE_JULIA_DEFAULT);
                break;
            case SCE_JULIA_OPERATOR:
                resumeOperator(sc);
                break;
            case SCE_JULIA_TYPEOPERATOR:
                sc.SetState(SCE_JULIA_DEFAULT);
                break;
            case SCE_JULIA_TYPEANNOT:
                if (! IsIdentifierCharacter(sc.ch)) {
                    sc.SetState(SCE_JULIA_DEFAULT);
                }
                break;
            case SCE_JULIA_IDENTIFIER:
                // String literal
                if (sc.ch == '\"') {
                    // If the string literal has a prefix, interpolation is disabled
                    israwstring = true;
                    sc.ChangeState(SCE_JULIA_STRINGLITERAL);
                    sc.SetState(SCE_JULIA_DEFAULT);

                } else if (sc.ch == '`') {
                    // If the string literal has a prefix, interpolation is disabled
                    israwstring = true;
                    sc.ChangeState(SCE_JULIA_COMMANDLITERAL);
                    sc.SetState(SCE_JULIA_DEFAULT);

                // Continue if the character is an identifier character
                } else if (! IsIdentifierCharacter(sc.ch)) {
                    char s[MAX_JULIA_IDENT_CHARS + 1];
                    sc.GetCurrent(s, sizeof(s));

                    // Treat the keywords differently if we are indexing or not
                    if ( indexing_level > 0 && CheckBoundsIndexing(s)) {
                        // Inside [], (), `begin` and `end` are numbers not block keywords
                        sc.ChangeState(SCE_JULIA_NUMBER);
                        transpose = false;

                    } else {
                        if (keywords.InList(s)) {
                            sc.ChangeState(SCE_JULIA_KEYWORD1);
                            transpose = false;
                        } else if (identifiers2.InList(s)) {
                            sc.ChangeState(SCE_JULIA_KEYWORD2);
                            transpose = false;
                        } else if (identifiers3.InList(s)) {
                            sc.ChangeState(SCE_JULIA_KEYWORD3);
                            transpose = false;
                        } else if (identifiers4.InList(s)) {
                            sc.ChangeState(SCE_JULIA_KEYWORD4);
                            // These identifiers can be used for variable names also,
                            // so transpose is not forbidden.
                            //transpose = false;
                        }
                    }
                    sc.SetState(SCE_JULIA_DEFAULT);

                    // TODO: recognize begin-end blocks inside list comprehension
                    // b = [(begin n%2; n*2 end) for n in 1:10]
                    // TODO: recognize better comprehension for-if to avoid problem with code-folding
                    // c = [(if isempty(a); missing else first(b) end) for (a, b) in zip(l1, l2)]
                }
                break;
            case SCE_JULIA_NUMBER:
                resumeNumber(sc, base, with_dot, options.highlightLexerror);
                break;
            case SCE_JULIA_CHAR:
                resumeCharacter(sc, options.highlightLexerror);
                break;
            case SCE_JULIA_DOCSTRING:
                resumeString(sc, true, !israwstring);
                if (sc.state == SCE_JULIA_DEFAULT && israwstring) {
                    israwstring = false;
                }
                break;
            case SCE_JULIA_STRING:
                resumeString(sc, false, !israwstring);
                if (sc.state == SCE_JULIA_DEFAULT && israwstring) {
                    israwstring = false;
                }
                break;
            case SCE_JULIA_COMMAND:
                resumeCommand(sc, triple_backtick, !israwstring);
                break;
            case SCE_JULIA_MACRO:
                if (IsASpace(sc.ch) || ! IsIdentifierCharacter(sc.ch)) {
                    sc.SetState(SCE_JULIA_DEFAULT);
                }
                break;
            case SCE_JULIA_SYMBOL:
                if (! IsIdentifierCharacter(sc.ch)) {
                    sc.SetState(SCE_JULIA_DEFAULT);
                }
                break;
            case SCE_JULIA_COMMENT:
                if( commentDepth > 0 ) {
                    // end or start of a nested a block comment
                    if ( sc.ch == '=' && sc.chNext == '#') {
                        commentDepth --;
                        sc.Forward();

                        if (commentDepth == 0) {
                            sc.ForwardSetState(SCE_JULIA_DEFAULT);
                        }
                    } else if( sc.ch == '#' && sc.chNext == '=') {
                        commentDepth ++;
                        sc.Forward();
                    }
                } else {
                    // single line comment
                    if (sc.atLineEnd || sc.ch == '\r' || sc.ch == '\n') {
                        sc.SetState(SCE_JULIA_DEFAULT);
                        transpose = false;
                    }
                }
                break;
        }

        // check start of a new state
        if (sc.state == SCE_JULIA_DEFAULT) {
            if (sc.ch == '#') {
                sc.SetState(SCE_JULIA_COMMENT);
                // increment depth if we are a block comment
                if(sc.chNext == '=') {
                    commentDepth ++;
                    sc.Forward();
                }
            } else if (sc.ch == '!') {
                sc.SetState(SCE_JULIA_OPERATOR);
                transpose = false;
            } else if (sc.ch == '\'') {
                if (transpose) {
                    sc.SetState(SCE_JULIA_OPERATOR);
                } else {
                    sc.SetState(SCE_JULIA_CHAR);
                }
            } else if (sc.ch == '\"') {
                istripledocstring = (sc.chNext == '\"' && sc.GetRelativeCharacter(2) == '\"');
                if (istripledocstring) {
                    sc.SetState(SCE_JULIA_DOCSTRING);
                    // Move to the end of the triple quotes
                    Sci_PositionU nextIndex = sc.currentPos + 2;
                    while (nextIndex > sc.currentPos && sc.More()) {
                        sc.Forward();
                    }
                } else {
                    sc.SetState(SCE_JULIA_STRING);
                }
            } else if (sc.ch == '`') {
                triple_backtick = (sc.chNext == '`' && sc.GetRelativeCharacter(2) == '`');
                sc.SetState(SCE_JULIA_COMMAND);
                if (triple_backtick) {
                    // Move to the end of the triple backticks
                    Sci_PositionU nextIndex = sc.currentPos + 2;
                    while (nextIndex > sc.currentPos && sc.More()) {
                        sc.Forward();
                    }
                }
            } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
                initNumber(sc, base, with_dot);
            } else if (IsIdentifierFirstCharacter(sc.ch)) {
                sc.SetState(SCE_JULIA_IDENTIFIER);
                transpose = true;
            } else if (sc.ch == '@') {
                sc.SetState(SCE_JULIA_MACRO);
                transpose = false;

            // Several parsing of operators, should keep the order of `if` blocks
            } else if ((sc.ch == ':' || sc.ch == '<' || sc.ch == '>') && sc.chNext == ':') {
                sc.SetState(SCE_JULIA_TYPEOPERATOR);
                sc.Forward();
                // Highlight the next identifier, if option is set
                if (options.highlightTypeannotation &&
                    IsIdentifierFirstCharacter(sc.chNext)) {
                    sc.ForwardSetState(SCE_JULIA_TYPEANNOT);
                }
            } else if (sc.ch == ':') {
                // TODO: improve detection of range
                // should be solved with begin-end parsing
                // `push!(arr, s1 :s2)` and `a[begin :end]
                if (IsIdentifierFirstCharacter(sc.chNext) &&
                    ! IsIdentifierCharacter(sc.chPrev) &&
                    sc.chPrev != ')' && sc.chPrev != ']' ) {
                    sc.SetState(SCE_JULIA_SYMBOL);
                } else {
                    sc.SetState(SCE_JULIA_OPERATOR);
                }
            } else if (IsJuliaParen(sc.ch)) {
                if (sc.ch == '[') {
                    list_comprehension ++;
                    indexing_level ++;
                } else if (sc.ch == ']' && (indexing_level > 0)) {
                    list_comprehension --;
                    indexing_level --;
                } else if (sc.ch == '(') {
                    list_comprehension ++;
                } else if (sc.ch == ')' && (list_comprehension > 0)) {
                    list_comprehension --;
                }

                if (sc.ch == ')' || sc.ch == ']' || sc.ch == '}') {
                    transpose = true;
                } else {
                    transpose = false;
                }
                sc.SetState(SCE_JULIA_BRACKET);
            } else if (IsOperatorFirstCharacter(sc.ch)) {
                transpose = false;
                sc.SetState(SCE_JULIA_OPERATOR);
            } else {
                transpose = false;
            }
        }

        // update the line information (used for line-by-line lexing and folding)
        if (sc.atLineEnd) {
            // set the line state to the current state
            curLine = styler.GetLine(sc.currentPos);

            lineState = ((transpose ? 1 : 0) << 0) |
                        ((istripledocstring ? 1 : 0) << 1) |
                        ((triple_backtick ? 1 : 0) << 2) |
                        ((israwstring ? 1 : 0) << 3) |
                        ((indexing_level & 0x0F) << 4) |
                        ((list_comprehension & 0x0F) << 8) |
                        ((commentDepth & 0x0F) << 12);
            styler.SetLineState(curLine, lineState);
        }
    }
    sc.Complete();
}

void SCI_METHOD LexerJulia::Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {

	if (!options.fold)
		return;

	LexAccessor styler(pAccess);

	Sci_PositionU endPos = startPos + length;
	int visibleChars = 0;
	Sci_Position lineCurrent = styler.GetLine(startPos);
	int levelCurrent = SC_FOLDLEVELBASE;
    int lineState = 0;
	if (lineCurrent > 0) {
		levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
        lineState = styler.GetLineState(lineCurrent-1);
    }

    // level of nested brackets
    int indexing_level = (int)((lineState >> 4) & 0x0F);     // 4 bits of bracket nesting counter
    // level of nested parenthesis or brackets
    int list_comprehension = (int)((lineState >> 8) & 0x0F); // 4 bits of parenthesis nesting counter
    //int commentDepth = (int)((lineState >> 12) & 0x0F);      // 4 bits of nested comment counter
    
	Sci_PositionU lineStartNext = styler.LineStart(lineCurrent+1);
	int levelNext = levelCurrent;
	char chNext = styler[startPos];
	int stylePrev = styler.StyleAt(startPos - 1);
	int styleNext = styler.StyleAt(startPos);
	int style = initStyle;
    char word[100];
    int wordlen = 0;
    for (Sci_PositionU i = startPos; i < endPos; i++) {
		char ch = chNext;
		chNext = styler.SafeGetCharAt(i + 1);
		style = styleNext;
		styleNext = styler.StyleAt(i + 1);
		bool atEOL = i == (lineStartNext-1);

        // a start/end of comment block
        if (options.foldComment && style == SCE_JULIA_COMMENT) {
            // start of block comment
            if (ch == '#' && chNext == '=') {
                levelNext ++;
            }
            // end of block comment
            if (ch == '=' && chNext == '#' && levelNext > 0) {
                levelNext --;
            }
        }

        // Syntax based folding, accounts for list comprehension
        if (options.foldSyntaxBased) {
            // list comprehension allow `for`, `if` and `begin` without `end`
            if (style == SCE_JULIA_BRACKET) {
                if (ch == '[') {
                    list_comprehension ++;
                    indexing_level ++;
                    levelNext ++;
                } else if (ch == ']') {
                    list_comprehension --;
                    indexing_level --;
                    levelNext --;
                } else if (ch == '(') {
                    list_comprehension ++;
                    levelNext ++;
                } else if (ch == ')') {
                    list_comprehension --;
                    levelNext --;
                }
                // check non-negative
                if (indexing_level < 0) {
                    indexing_level = 0;
                }
                if (list_comprehension < 0) {
                    list_comprehension = 0;
                }
            }

            // keyword
            if (style == SCE_JULIA_KEYWORD1) {
                word[wordlen++] = static_cast<char>(ch);
                if (wordlen == 100) {  // prevent overflow
                    word[0] = '\0';
                    wordlen = 1;
                }
                if (styleNext != SCE_JULIA_KEYWORD1) {
                    word[wordlen] = '\0';
                    wordlen = 0;
                    if (list_comprehension <= 0 && indexing_level <= 0) {
                        levelNext += CheckKeywordFoldPoint(word);
                    }
                }
            }
        }

        // Docstring
        if (options.foldDocstring) {
            if (stylePrev != SCE_JULIA_DOCSTRING && style == SCE_JULIA_DOCSTRING) {
                levelNext ++;
            } else if (style == SCE_JULIA_DOCSTRING && styleNext != SCE_JULIA_DOCSTRING) {
                levelNext --;
            }
        }

        // check non-negative level
        if (levelNext < 0) {
            levelNext = 0;
        }

        if (!IsASpace(ch)) {
            visibleChars++;
        }
        stylePrev = style;

        if (atEOL || (i == endPos-1)) {
            int levelUse = levelCurrent;
            int lev = levelUse | levelNext << 16;
            if (visibleChars == 0 && options.foldCompact) {
                lev |= SC_FOLDLEVELWHITEFLAG;
            }
            if (levelUse < levelNext) {
                lev |= SC_FOLDLEVELHEADERFLAG;
            }
            if (lev != styler.LevelAt(lineCurrent)) {
                styler.SetLevel(lineCurrent, lev);
            }
            lineCurrent++;
            lineStartNext = styler.LineStart(lineCurrent+1);
            levelCurrent = levelNext;
            if (atEOL && (i == static_cast<Sci_PositionU>(styler.Length() - 1))) {
                // There is an empty line at end of file so give it same level and empty
                styler.SetLevel(lineCurrent, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG);
            }
            visibleChars = 0;
        }
    }
}

LexerModule lmJulia(SCLEX_JULIA, LexerJulia::LexerFactoryJulia, "julia", juliaWordLists);
