/** @file LexRust.cxx
 ** Lexer for Rust.
 **
 ** Copyright (c) 2013 by SiegeLord <slabode@aim.com>
 ** Converted to lexer object and added further folding features/properties by "Udo Lechner" <dlchnr(at)gmx(dot)net>
 **/
// Copyright 1998-2005 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 <cctype>
#include <cstdio>
#include <cstdarg>

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

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

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

using namespace Scintilla;
using namespace Lexilla;

namespace {

constexpr int NUM_RUST_KEYWORD_LISTS = 7;
constexpr int MAX_RUST_IDENT_CHARS = 1023;


enum class StringType : int {
	STRING = SCE_RUST_STRING,
	BYTESTRING = SCE_RUST_BYTESTRING,
	CSTRING = SCE_RUST_CSTRING,

	RAW_STRING = SCE_RUST_STRINGR,
	RAW_BYTESTRING = SCE_RUST_BYTESTRINGR,
	RAW_CSTRING = SCE_RUST_CSTRINGR
};

static bool IsStreamCommentStyle(int style) noexcept {
	return style == SCE_RUST_COMMENTBLOCK ||
		   style == SCE_RUST_COMMENTBLOCKDOC;
}

// Options used for LexerRust
struct OptionsRust {
	bool fold;
	bool foldSyntaxBased;
	bool foldComment;
	bool foldCommentMultiline;
	bool foldCommentExplicit;
	std::string foldExplicitStart;
	std::string foldExplicitEnd;
	bool foldExplicitAnywhere;
	bool foldCompact;
	int  foldAtElseInt;	// This variable is not used
	bool foldAtElse;
	OptionsRust() {
		fold = false;
		foldSyntaxBased = true;
		foldComment = false;
		foldCommentMultiline = true;
		foldCommentExplicit = true;
		foldExplicitStart = "";
		foldExplicitEnd   = "";
		foldExplicitAnywhere = false;
		foldCompact = true;
		foldAtElseInt = -1;
		foldAtElse = false;
	}
};

static const char * const rustWordLists[NUM_RUST_KEYWORD_LISTS + 1] = {
	"Primary keywords and identifiers",
	"Built in types",
	"Other keywords",
	"Keywords 4",
	"Keywords 5",
	"Keywords 6",
	"Keywords 7",
	nullptr,
};

struct OptionSetRust : public OptionSet<OptionsRust> {
	OptionSetRust() {
		DefineProperty("fold", &OptionsRust::fold);

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

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

		DefineProperty("fold.at.else", &OptionsRust::foldAtElse);

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

		DefineProperty("fold.rust.comment.multiline", &OptionsRust::foldCommentMultiline,
			"Set this property to 0 to disable folding multi-line comments when fold.comment=1.");

		DefineProperty("fold.rust.comment.explicit", &OptionsRust::foldCommentExplicit,
			"Set this property to 0 to disable folding explicit fold points when fold.comment=1.");

		DefineProperty("fold.rust.explicit.start", &OptionsRust::foldExplicitStart,
			"The string to use for explicit fold start points, replacing the standard //{.");

		DefineProperty("fold.rust.explicit.end", &OptionsRust::foldExplicitEnd,
			"The string to use for explicit fold end points, replacing the standard //}.");

		DefineProperty("fold.rust.explicit.anywhere", &OptionsRust::foldExplicitAnywhere,
			"Set this property to 1 to enable explicit fold points anywhere, not just in line comments.");

		DefineProperty("lexer.rust.fold.at.else", &OptionsRust::foldAtElseInt,
			"This option enables Rust folding on a \"} else {\" line of an if statement.");

		DefineWordListSets(rustWordLists);
	}
};

const LexicalClass lexicalClasses[] = {
	// Lexer rust SCLEX_RUST SCE_RUST_:
	0, "SCE_RUST_DEFAULT", "default", "White space",
	1, "SCE_RUST_COMMENTBLOCK", "comment", "Comment",
	2, "SCE_RUST_COMMENTLINE", "comment line", "Line comment",
	3, "SCE_RUST_COMMENTBLOCKDOC", "comment documentation", "Doc comment",
	4, "SCE_RUST_COMMENTLINEDOC", "comment documentation line", "Doc comment line",
	5, "SCE_RUST_NUMBER", "literal numeric", "Number",
	6, "SCE_RUST_WORD", "keyword", "Keywords",
	7, "SCE_RUST_WORD2", "identifier", "Keywords 2",
	8, "SCE_RUST_WORD3", "identifier", "Keywords 3",
	9, "SCE_RUST_WORD4", "identifier", "Keywords 4",
	10, "SCE_RUST_WORD5", "identifier", "Keywords 5",
	11, "SCE_RUST_WORD6", "identifier", "Keywords 6",
	12, "SCE_RUST_WORD7", "identifier", "Keywords 7",
	13, "SCE_RUST_STRING", "literal string", "Regular string",
	14, "SCE_RUST_STRINGR", "literal string raw", "Raw string",
	15, "SCE_RUST_CHARACTER", "literal string character", "Character",
	16, "SCE_RUST_OPERATOR", "operator", "Operator",
	17, "SCE_RUST_IDENTIFIER", "identifier", "Identifier",
	18, "SCE_RUST_LIFETIME", "annotation", "Lifetime",
	19, "SCE_RUST_MACRO", "macro preprocessor", "Macro",
	20, "SCE_RUST_LEXERROR", "error", "Lexical error",
	21, "SCE_RUST_BYTESTRING", "literal string", "Byte string",
	22, "SCE_RUST_BYTESTRINGR", "literal string raw", "Raw byte string",
	23, "SCE_RUST_BYTECHARACTER", "literal string character", "Byte character",
	24, "SCE_RUST_CSTRING", "literal string", "C string",
	25, "SCE_RUST_CSTRINGR", "literal string raw", "Raw C string",
};

class LexerRust : public DefaultLexer {
	WordList keywords[NUM_RUST_KEYWORD_LISTS];
	OptionsRust options;
	OptionSetRust osRust;
public:
	LexerRust() : DefaultLexer("rust", SCLEX_RUST, lexicalClasses, std::size(lexicalClasses)) {
	}
	virtual ~LexerRust() {
	}
	void SCI_METHOD Release() override {
		delete this;
	}
	int SCI_METHOD Version() const override {
		return lvRelease5;
	}
	const char * SCI_METHOD PropertyNames() override {
		return osRust.PropertyNames();
	}
	int SCI_METHOD PropertyType(const char *name) override {
		return osRust.PropertyType(name);
	}
	const char * SCI_METHOD DescribeProperty(const char *name) override {
		return osRust.DescribeProperty(name);
	}
	Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override;
	const char * SCI_METHOD PropertyGet(const char *key) override {
		return osRust.PropertyGet(key);
	}
	const char * SCI_METHOD DescribeWordListSets() override {
		return osRust.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 nullptr;
	}
	static ILexer5 *LexerFactoryRust() {
		return new LexerRust();
	}
};

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

Sci_Position SCI_METHOD LexerRust::WordListSet(int n, const char *wl) {
	Sci_Position firstModification = -1;
	if (n < NUM_RUST_KEYWORD_LISTS) {
		WordList *wordListN = &keywords[n];
		if (wordListN->Set(wl)) {
			firstModification = 0;
		}
	}
	return firstModification;
}

static bool IsWhitespace(int c) {
    return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

/* This isn't quite right for Unicode identifiers */
static bool IsIdentifierStart(int ch) {
	return (IsASCII(ch) && (isalpha(ch) || ch == '_')) || !IsASCII(ch);
}

/* This isn't quite right for Unicode identifiers */
static bool IsIdentifierContinue(int ch) {
	return (IsASCII(ch) && (isalnum(ch) || ch == '_')) || !IsASCII(ch);
}

static void ScanWhitespace(Accessor& styler, Sci_Position& pos, Sci_Position max) {
	while (IsWhitespace(styler.SafeGetCharAt(pos, '\0')) && pos < max) {
		if (pos == styler.LineEnd(styler.GetLine(pos)))
			styler.SetLineState(styler.GetLine(pos), 0);
		pos++;
	}
	styler.ColourTo(pos-1, SCE_RUST_DEFAULT);
}

static void GrabString(char* s, Accessor& styler, Sci_Position start, Sci_Position len) {
	for (Sci_Position ii = 0; ii < len; ii++)
		s[ii] = styler[ii + start];
	s[len] = '\0';
}

static void ScanRawIdentifier(Accessor& styler, Sci_Position& pos) {
	const Sci_Position start = pos;
	while (IsIdentifierContinue(styler.SafeGetCharAt(pos, '\0')))
		pos++;

	char s[MAX_RUST_IDENT_CHARS + 1];
	Sci_Position len = pos - start;
	len = len > MAX_RUST_IDENT_CHARS ? MAX_RUST_IDENT_CHARS : len;
	GrabString(s, styler, start, len);
	// restricted values https://doc.rust-lang.org/reference/identifiers.html#raw-identifiers
	if (strcmp(s, "crate") != 0 && strcmp(s, "self") != 0 &&
		strcmp(s, "super") != 0 && strcmp(s, "Self") != 0) {
		styler.ColourTo(pos - 1, SCE_RUST_IDENTIFIER);
	} else {
		styler.ColourTo(pos - 1, SCE_RUST_LEXERROR);
	}
}

static void ScanIdentifier(Accessor& styler, Sci_Position& pos, WordList *keywords) {
	const Sci_Position start = pos;
	while (IsIdentifierContinue(styler.SafeGetCharAt(pos, '\0')))
		pos++;

	if (styler.SafeGetCharAt(pos, '\0') == '!') {
		pos++;
		styler.ColourTo(pos - 1, SCE_RUST_MACRO);
	} else {
		char s[MAX_RUST_IDENT_CHARS + 1];
		Sci_Position len = pos - start;
		len = len > MAX_RUST_IDENT_CHARS ? MAX_RUST_IDENT_CHARS : len;
		GrabString(s, styler, start, len);
		bool keyword = false;
		for (int ii = 0; ii < NUM_RUST_KEYWORD_LISTS; ii++) {
			if (keywords[ii].InList(s)) {
				styler.ColourTo(pos - 1, SCE_RUST_WORD + ii);
				keyword = true;
				break;
			}
		}
		if (!keyword) {
			styler.ColourTo(pos - 1, SCE_RUST_IDENTIFIER);
		}
	}
}

/* Scans a sequence of digits, returning true if it found any. */
static bool ScanDigits(Accessor& styler, Sci_Position& pos, int base) {
	const Sci_Position old_pos = pos;
	for (;;) {
		const int c = styler.SafeGetCharAt(pos, '\0');
		if (IsADigit(c, base) || c == '_')
			pos++;
		else
			break;
	}
	return old_pos != pos;
}

/* Scans an integer and floating point literals. */
static void ScanNumber(Accessor& styler, Sci_Position& pos) {
	int base = 10;
	int c = styler.SafeGetCharAt(pos, '\0');
	int n = styler.SafeGetCharAt(pos + 1, '\0');
	bool error = false;
	/* Scan the prefix, thus determining the base.
	 * 10 is default if there's no prefix. */
	if (c == '0' && n == 'x') {
		pos += 2;
		base = 16;
	} else if (c == '0' && n == 'b') {
		pos += 2;
		base = 2;
	} else if (c == '0' && n == 'o') {
		pos += 2;
		base = 8;
	}

	/* Scan initial digits. The literal is malformed if there are none. */
	error |= !ScanDigits(styler, pos, base);
	/* See if there's an integer suffix. We mimic the Rust's lexer
	 * and munch it even if there was an error above. */
	c = styler.SafeGetCharAt(pos, '\0');
	if (c == 'u' || c == 'i') {
		pos++;
		c = styler.SafeGetCharAt(pos, '\0');
		n = styler.SafeGetCharAt(pos + 1, '\0');
		if (c == '8') {
			pos++;
		} else if (c == '1' && n == '6') {
			pos += 2;
		} else if (c == '3' && n == '2') {
			pos += 2;
		} else if (c == '6' && n == '4') {
			pos += 2;
		} else if (styler.Match(pos, "128")) {
			pos += 3;
		} else if (styler.Match(pos, "size")) {
			pos += 4;
		} else {
			error = true;
		}
	/* See if it's a floating point literal. These literals have to be base 10.
	 */
	} else if (!error) {
		/* If there's a period, it's a floating point literal unless it's
		 * followed by an identifier (meaning this is a method call, e.g.
		 * `1.foo()`) or another period, in which case it's a range (e.g. 1..2)
		 */
		n = styler.SafeGetCharAt(pos + 1, '\0');
		if (c == '.' && !(IsIdentifierStart(n) || n == '.')) {
			error |= base != 10;
			pos++;
			/* It's ok to have no digits after the period. */
			ScanDigits(styler, pos, 10);
		}

		/* Look for the exponentiation. */
		c = styler.SafeGetCharAt(pos, '\0');
		if (c == 'e' || c == 'E') {
			error |= base != 10;
			pos++;
			c = styler.SafeGetCharAt(pos, '\0');
			if (c == '-' || c == '+')
				pos++;
			/* It is invalid to have no digits in the exponent. */
			error |= !ScanDigits(styler, pos, 10);
		}

		/* Scan the floating point suffix. */
		c = styler.SafeGetCharAt(pos, '\0');
		if (c == 'f') {
			error |= base != 10;
			pos++;
			c = styler.SafeGetCharAt(pos, '\0');
			n = styler.SafeGetCharAt(pos + 1, '\0');
			if (c == '3' && n == '2') {
				pos += 2;
			} else if (c == '6' && n == '4') {
				pos += 2;
			} else {
				error = true;
			}
		}
	}

	if (error)
		styler.ColourTo(pos - 1, SCE_RUST_LEXERROR);
	else
		styler.ColourTo(pos - 1, SCE_RUST_NUMBER);
}

static bool IsOneCharOperator(int c) {
	return c == ';' || c == ',' || c == '(' || c == ')'
	    || c == '{' || c == '}' || c == '[' || c == ']'
	    || c == '@' || c == '#' || c == '~' || c == '+'
	    || c == '*' || c == '/' || c == '^' || c == '%'
	    || c == '.' || c == ':' || c == '!' || c == '<'
	    || c == '>' || c == '=' || c == '-' || c == '&'
	    || c == '|' || c == '$' || c == '?';
}

static bool IsTwoCharOperator(int c, int n) {
	return (c == '.' && n == '.') || (c == ':' && n == ':')
	    || (c == '!' && n == '=') || (c == '<' && n == '<')
	    || (c == '<' && n == '=') || (c == '>' && n == '>')
	    || (c == '>' && n == '=') || (c == '=' && n == '=')
	    || (c == '=' && n == '>') || (c == '-' && n == '>')
	    || (c == '&' && n == '&') || (c == '|' && n == '|')
	    || (c == '-' && n == '=') || (c == '&' && n == '=')
	    || (c == '|' && n == '=') || (c == '+' && n == '=')
	    || (c == '*' && n == '=') || (c == '/' && n == '=')
	    || (c == '^' && n == '=') || (c == '%' && n == '=');
}

static bool IsThreeCharOperator(int c, int n, int n2) {
	return (c == '<' && n == '<' && n2 == '=')
	    || (c == '>' && n == '>' && n2 == '=');
}

static bool IsValidCharacterEscape(int c) {
	return c == 'n'  || c == 'r' || c == 't' || c == '\\'
	    || c == '\'' || c == '"' || c == '0';
}

static bool IsValidStringEscape(int c) {
	return IsValidCharacterEscape(c) || c == '\n' || c == '\r';
}

static bool ScanNumericEscape(Accessor &styler, Sci_Position& pos, Sci_Position num_digits, bool stop_asap) {
	for (;;) {
		const int c = styler.SafeGetCharAt(pos, '\0');
		if (!IsADigit(c, 16))
			break;
		num_digits--;
		pos++;
		if (num_digits == 0 && stop_asap)
			return true;
	}
	if (num_digits == 0) {
		return true;
	} else {
		return false;
	}
}

/* This is overly permissive for character literals in order to accept UTF-8 encoded
 * character literals. */
static void ScanCharacterLiteralOrLifetime(Accessor &styler, Sci_Position& pos, bool ascii_only) {
	pos++;
	int c = styler.SafeGetCharAt(pos, '\0');
	int n = styler.SafeGetCharAt(pos + 1, '\0');
	bool done = false;
	bool valid_lifetime = !ascii_only && IsIdentifierStart(c);
	bool valid_char = true;
	bool first = true;
	while (!done) {
		switch (c) {
			case '\\':
				done = true;
				if (IsValidCharacterEscape(n)) {
					pos += 2;
				} else if (n == 'x') {
					pos += 2;
					valid_char = ScanNumericEscape(styler, pos, 2, false);
				} else if (n == 'u' && !ascii_only) {
					pos += 2;
					if (styler.SafeGetCharAt(pos, '\0') != '{') {
						// old-style
						valid_char = ScanNumericEscape(styler, pos, 4, false);
					} else {
						int n_digits = 0;
						while (IsADigit(styler.SafeGetCharAt(++pos, '\0'), 16) && n_digits++ < 6) {
						}
						if (n_digits > 0 && styler.SafeGetCharAt(pos, '\0') == '}')
							pos++;
						else
							valid_char = false;
					}
				} else if (n == 'U' && !ascii_only) {
					pos += 2;
					valid_char = ScanNumericEscape(styler, pos, 8, false);
				} else {
					valid_char = false;
				}
				break;
			case '\'':
				valid_char = !first;
				done = true;
				break;
			case '\t':
			case '\n':
			case '\r':
			case '\0':
				valid_char = false;
				done = true;
				break;
			default:
				if (ascii_only && !IsASCII((char)c)) {
					done = true;
					valid_char = false;
				} else if (!IsIdentifierContinue(c) && !first) {
					done = true;
				} else {
					pos++;
				}
				break;
		}
		c = styler.SafeGetCharAt(pos, '\0');
		n = styler.SafeGetCharAt(pos + 1, '\0');

		first = false;
	}
	if (styler.SafeGetCharAt(pos, '\0') == '\'') {
		valid_lifetime = false;
	} else {
		valid_char = false;
	}
	if (valid_lifetime) {
		styler.ColourTo(pos - 1, SCE_RUST_LIFETIME);
	} else if (valid_char) {
		pos++;
		styler.ColourTo(pos - 1, ascii_only ? SCE_RUST_BYTECHARACTER : SCE_RUST_CHARACTER);
	} else {
		styler.ColourTo(pos - 1, SCE_RUST_LEXERROR);
	}
}

enum CommentState {
	UnknownComment,
	DocComment,
	NotDocComment
};

/*
 * The rule for block-doc comments is as follows: /xxN and /x! (where x is an asterisk, N is a non-asterisk) start doc comments.
 * Otherwise it's a regular comment.
 */
static void ResumeBlockComment(Accessor &styler, Sci_Position& pos, Sci_Position max, CommentState state, int level) {
	int c = styler.SafeGetCharAt(pos, '\0');
	bool maybe_doc_comment = false;
	if (c == '*') {
		const int n = styler.SafeGetCharAt(pos + 1, '\0');
		if (n != '*' && n != '/') {
			maybe_doc_comment = true;
		}
	} else if (c == '!') {
		maybe_doc_comment = true;
	}

	for (;;) {
		const int n = styler.SafeGetCharAt(pos + 1, '\0');
		if (pos == styler.LineEnd(styler.GetLine(pos)))
			styler.SetLineState(styler.GetLine(pos), level);
		if (c == '*') {
			pos++;
			if (n == '/') {
				pos++;
				level--;
				if (level == 0) {
					styler.SetLineState(styler.GetLine(pos), 0);
					if (state == DocComment || (state == UnknownComment && maybe_doc_comment))
						styler.ColourTo(pos - 1, SCE_RUST_COMMENTBLOCKDOC);
					else
						styler.ColourTo(pos - 1, SCE_RUST_COMMENTBLOCK);
					break;
				}
			}
		} else if (c == '/') {
			pos++;
			if (n == '*') {
				pos++;
				level++;
			}
		}
		else if (pos < max) {
			pos++;
		}
		if (pos >= max) {
			if (state == DocComment || (state == UnknownComment && maybe_doc_comment))
				styler.ColourTo(pos - 1, SCE_RUST_COMMENTBLOCKDOC);
			else
				styler.ColourTo(pos - 1, SCE_RUST_COMMENTBLOCK);
			break;
		}
		c = styler.SafeGetCharAt(pos, '\0');
	}
}

/*
 * The rule for line-doc comments is as follows... ///N and //! (where N is a non slash) start doc comments.
 * Otherwise it's a normal line comment.
 */
static void ResumeLineComment(Accessor &styler, Sci_Position& pos, Sci_Position max, CommentState state) {
	bool maybe_doc_comment = false;
	int c = styler.SafeGetCharAt(pos, '\0');
	if (c == '/') {
		if (pos < max) {
			pos++;
			c = styler.SafeGetCharAt(pos, '\0');
			if (c != '/') {
				maybe_doc_comment = true;
			}
		}
	} else if (c == '!') {
		maybe_doc_comment = true;
	}

	pos = styler.LineEnd(styler.GetLine(pos));
	styler.SetLineState(styler.GetLine(pos), SCE_RUST_DEFAULT);

	if (state == DocComment || (state == UnknownComment && maybe_doc_comment))
		styler.ColourTo(pos - 1, SCE_RUST_COMMENTLINEDOC);
	else
		styler.ColourTo(pos - 1, SCE_RUST_COMMENTLINE);
}

static void ScanComments(Accessor &styler, Sci_Position& pos, Sci_Position max) {
	pos++;
	const int c = styler.SafeGetCharAt(pos, '\0');
	pos++;
	if (c == '/')
		ResumeLineComment(styler, pos, max, UnknownComment);
	else if (c == '*')
		ResumeBlockComment(styler, pos, max, UnknownComment, 1);
}

static void ResumeString(Accessor &styler, Sci_Position& pos, Sci_Position max, StringType string_type) {
	int c = styler.SafeGetCharAt(pos, '\0');
	bool error = false;
	while (c != '"' && !error) {
		if (pos >= max) {
			error = true;
			break;
		}
		if (pos == styler.LineEnd(styler.GetLine(pos)))
			styler.SetLineState(styler.GetLine(pos), 0);
		if (c == '\\') {
			const int n = styler.SafeGetCharAt(pos + 1, '\0');
			if (IsValidStringEscape(n)) {
				pos += 2;
			} else if (n == 'x') {
				pos += 2;
				error = !ScanNumericEscape(styler, pos, 2, true);
			} else if (n == 'u' && (string_type != StringType::BYTESTRING)) {
				pos += 2;
				if (styler.SafeGetCharAt(pos, '\0') != '{') {
					// old-style
					error = !ScanNumericEscape(styler, pos, 4, true);
				} else {
					int n_digits = 0;
					while (IsADigit(styler.SafeGetCharAt(++pos, '\0'), 16) && n_digits++ < 6) {
					}
					if (n_digits > 0 && styler.SafeGetCharAt(pos, '\0') == '}')
						pos++;
					else
						error = true;
				}
			} else if (n == 'U' && (string_type != StringType::BYTESTRING)) {
				pos += 2;
				error = !ScanNumericEscape(styler, pos, 8, true);
			} else {
				pos += 1;
				error = true;
			}
		} else {
			if (string_type == StringType::BYTESTRING && !IsASCII((char)c))
				error = true;
			else
				pos++;
		}
		c = styler.SafeGetCharAt(pos, '\0');
	}
	if (!error)
		pos++;

	styler.ColourTo(pos - 1, static_cast<int>(string_type));
}

static void ResumeRawString(Accessor &styler, Sci_Position& pos, Sci_Position max, int num_hashes, StringType string_type) {
	for (;;) {
		if (pos == styler.LineEnd(styler.GetLine(pos)))
			styler.SetLineState(styler.GetLine(pos), num_hashes);

		const int c = styler.SafeGetCharAt(pos, '\0');
		if (c == '"') {
			pos++;
			int trailing_num_hashes = 0;
			while (styler.SafeGetCharAt(pos, '\0') == '#' && trailing_num_hashes < num_hashes) {
				trailing_num_hashes++;
				pos++;
			}
			if (trailing_num_hashes == num_hashes) {
				styler.SetLineState(styler.GetLine(pos), 0);
				break;
			}
		} else if (pos >= max) {
			break;
		} else {
			if ((string_type == StringType::RAW_BYTESTRING) && !IsASCII((char)c))
				break;
			pos++;
		}
	}

	styler.ColourTo(pos - 1, static_cast<int>(string_type));
}

static void ScanRawString(Accessor &styler, Sci_Position& pos, Sci_Position max, StringType string_type) {
	pos++;
	int num_hashes = 0;
	while (styler.SafeGetCharAt(pos, '\0') == '#') {
		num_hashes++;
		pos++;
	}
	if (styler.SafeGetCharAt(pos, '\0') != '"') {
		styler.ColourTo(pos - 1, SCE_RUST_LEXERROR);
	} else {
		pos++;
		ResumeRawString(styler, pos, max, num_hashes, string_type);
	}
}

void SCI_METHOD LexerRust::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
	PropSetSimple props;
	Accessor styler(pAccess, &props);
	Sci_Position pos = startPos;
	const Sci_Position max = pos + length;

	styler.StartAt(pos);
	styler.StartSegment(pos);

	if (initStyle == SCE_RUST_COMMENTBLOCK || initStyle == SCE_RUST_COMMENTBLOCKDOC) {
		ResumeBlockComment(styler, pos, max, initStyle == SCE_RUST_COMMENTBLOCKDOC ? DocComment : NotDocComment, styler.GetLineState(styler.GetLine(pos) - 1));
	} else if (initStyle == SCE_RUST_COMMENTLINE || initStyle == SCE_RUST_COMMENTLINEDOC) {
		ResumeLineComment(styler, pos, max, initStyle == SCE_RUST_COMMENTLINEDOC ? DocComment : NotDocComment);
	} else if (initStyle == SCE_RUST_STRING) {
		ResumeString(styler, pos, max, StringType::STRING);
	} else if (initStyle == SCE_RUST_BYTESTRING) {
		ResumeString(styler, pos, max, StringType::BYTESTRING);
	} else if (initStyle == SCE_RUST_STRINGR) {
		ResumeRawString(styler, pos, max, styler.GetLineState(styler.GetLine(pos) - 1), StringType::RAW_STRING);
	} else if (initStyle == SCE_RUST_BYTESTRINGR) {
		ResumeRawString(styler, pos, max, styler.GetLineState(styler.GetLine(pos) - 1), StringType::RAW_BYTESTRING);
	} else if (initStyle == SCE_RUST_CSTRING) {
		ResumeRawString(styler, pos, max, styler.GetLineState(styler.GetLine(pos) - 1), StringType::CSTRING);
	} else if (initStyle == SCE_RUST_CSTRINGR) {
		ResumeRawString(styler, pos, max, styler.GetLineState(styler.GetLine(pos) - 1), StringType::RAW_CSTRING);
	}
	while (pos < max) {
		const int c = styler.SafeGetCharAt(pos, '\0');
		const int n = styler.SafeGetCharAt(pos + 1, '\0');
		const int n2 = styler.SafeGetCharAt(pos + 2, '\0');

		if (pos == 0 && c == '#' && n == '!' && n2 != '[') {
			pos += 2;
			ResumeLineComment(styler, pos, max, NotDocComment);
		} else if (IsWhitespace(c)) {
			ScanWhitespace(styler, pos, max);
		} else if (c == '/' && (n == '/' || n == '*')) {
			ScanComments(styler, pos, max);
		} else if (c == 'r' && (n == '#' && IsIdentifierStart(n2))) {
			pos += 2;
			ScanRawIdentifier(styler, pos);
		} else if (c == 'r' && (n == '#' || n == '"')) {
			ScanRawString(styler, pos, max, StringType::RAW_STRING);
		} else if (c == 'b' && n == 'r' && (n2 == '#' || n2 == '"')) {
			pos++;
			ScanRawString(styler, pos, max, StringType::RAW_BYTESTRING);
		} else if (c == 'b' && n == '"') {
			pos += 2;
			ResumeString(styler, pos, max, StringType::BYTESTRING);
		} else if (c == 'c' && n == 'r' && (n2 == '#' || n2 == '"')) {
			pos++;
			ScanRawString(styler, pos, max, StringType::RAW_CSTRING);
		} else if (c == 'c' && n == '"') {
			pos += 2;
			ResumeString(styler, pos, max, StringType::CSTRING);
		} else if (c == 'b' && n == '\'') {
			pos++;
			ScanCharacterLiteralOrLifetime(styler, pos, true);
		} else if (IsIdentifierStart(c)) {
			ScanIdentifier(styler, pos, keywords);
		} else if (IsADigit(c)) {
			ScanNumber(styler, pos);
		} else if (IsThreeCharOperator(c, n, n2)) {
			pos += 3;
			styler.ColourTo(pos - 1, SCE_RUST_OPERATOR);
		} else if (IsTwoCharOperator(c, n)) {
			pos += 2;
			styler.ColourTo(pos - 1, SCE_RUST_OPERATOR);
		} else if (IsOneCharOperator(c)) {
			pos++;
			styler.ColourTo(pos - 1, SCE_RUST_OPERATOR);
		} else if (c == '\'') {
			ScanCharacterLiteralOrLifetime(styler, pos, false);
		} else if (c == '"') {
			pos++;
			ResumeString(styler, pos, max, StringType::STRING);
		} else {
			pos++;
			styler.ColourTo(pos - 1, SCE_RUST_LEXERROR);
		}
	}
	styler.ColourTo(pos - 1, SCE_RUST_DEFAULT);
	styler.Flush();
}

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

	if (!options.fold)
		return;

	LexAccessor styler(pAccess);

	const Sci_PositionU endPos = startPos + length;
	int visibleChars = 0;
	bool inLineComment = false;
	Sci_Position lineCurrent = styler.GetLine(startPos);
	int levelCurrent = SC_FOLDLEVELBASE;
	if (lineCurrent > 0)
		levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
	Sci_PositionU lineStartNext = styler.LineStart(lineCurrent+1);
	int levelMinCurrent = levelCurrent;
	int levelNext = levelCurrent;
	char chNext = styler[startPos];
	int styleNext = styler.StyleAt(startPos);
	int style = initStyle;
	const bool userDefinedFoldMarkers = !options.foldExplicitStart.empty() && !options.foldExplicitEnd.empty();
	for (Sci_PositionU i = startPos; i < endPos; i++) {
		const char ch = chNext;
		chNext = styler.SafeGetCharAt(i + 1);
		const int stylePrev = style;
		style = styleNext;
		styleNext = styler.StyleAt(i + 1);
		const bool atEOL = i == (lineStartNext-1);
		if ((style == SCE_RUST_COMMENTLINE) || (style == SCE_RUST_COMMENTLINEDOC))
			inLineComment = true;
		if (options.foldComment && options.foldCommentMultiline && IsStreamCommentStyle(style) && !inLineComment) {
			if (!IsStreamCommentStyle(stylePrev)) {
				levelNext++;
			} else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
				// Comments don't end at end of line and the next character may be unstyled.
				levelNext--;
			}
		}
		if (options.foldComment && options.foldCommentExplicit && ((style == SCE_RUST_COMMENTLINE) || options.foldExplicitAnywhere)) {
			if (userDefinedFoldMarkers) {
				if (styler.Match(i, options.foldExplicitStart.c_str())) {
					levelNext++;
				} else if (styler.Match(i, options.foldExplicitEnd.c_str())) {
					levelNext--;
				}
			} else {
				if ((ch == '/') && (chNext == '/')) {
					const char chNext2 = styler.SafeGetCharAt(i + 2);
					if (chNext2 == '{') {
						levelNext++;
					} else if (chNext2 == '}') {
						levelNext--;
					}
				}
			}
		}
		if (options.foldSyntaxBased && (style == SCE_RUST_OPERATOR)) {
			if (ch == '{') {
				// Measure the minimum before a '{' to allow
				// folding on "} else {"
				if (levelMinCurrent > levelNext) {
					levelMinCurrent = levelNext;
				}
				levelNext++;
			} else if (ch == '}') {
				levelNext--;
			}
		}
		if (!IsASpace(ch))
			visibleChars++;
		if (atEOL || (i == endPos-1)) {
			int levelUse = levelCurrent;
			if (options.foldSyntaxBased && options.foldAtElse) {
				levelUse = levelMinCurrent;
			}
			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;
			levelMinCurrent = levelCurrent;
			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;
			inLineComment = false;
		}
	}
}

}

extern const LexerModule lmRust(SCLEX_RUST, LexerRust::LexerFactoryRust, "rust", rustWordLists);
