mirror of
				https://github.com/notepad-plus-plus/notepad-plus-plus.git
				synced 2025-10-31 11:34:05 +01:00 
			
		
		
		
	git-svn-id: svn://svn.tuxfamily.org/svnroot/notepadplus/repository/trunk@653 f5eea248-9336-0410-98b8-ebc06183d4e3
		
			
				
	
	
		
			457 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			457 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Scintilla source code edit control
 | |
| /** @file LexCaml.cxx
 | |
|  ** Lexer for Objective Caml.
 | |
|  **/
 | |
| // Copyright 2005-2009 by Robert Roessler <robertr@rftp.com>
 | |
| // The License.txt file describes the conditions under which this software may be distributed.
 | |
| /*	Release History
 | |
| 	20050204 Initial release.
 | |
| 	20050205 Quick compiler standards/"cleanliness" adjustment.
 | |
| 	20050206 Added cast for IsLeadByte().
 | |
| 	20050209 Changes to "external" build support.
 | |
| 	20050306 Fix for 1st-char-in-doc "corner" case.
 | |
| 	20050502 Fix for [harmless] one-past-the-end coloring.
 | |
| 	20050515 Refined numeric token recognition logic.
 | |
| 	20051125 Added 2nd "optional" keywords class.
 | |
| 	20051129 Support "magic" (read-only) comments for RCaml.
 | |
| 	20051204 Swtich to using StyleContext infrastructure.
 | |
| 	20090629 Add full Standard ML '97 support.
 | |
| */
 | |
| 
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <stdio.h>
 | |
| #include <stdarg.h>
 | |
| #include <assert.h>
 | |
| #include <ctype.h>
 | |
| 
 | |
| #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"
 | |
| 
 | |
| //	Since the Microsoft __iscsym[f] funcs are not ANSI...
 | |
| inline int  iscaml(int c) {return isalnum(c) || c == '_';}
 | |
| inline int iscamlf(int c) {return isalpha(c) || c == '_';}
 | |
| 
 | |
| static const int baseT[24] = {
 | |
| 	0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* A - L */
 | |
| 	0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0,16	/* M - X */
 | |
| };
 | |
| 
 | |
| #ifdef SCI_NAMESPACE
 | |
| using namespace Scintilla;
 | |
| #endif
 | |
| 
 | |
| #ifdef BUILD_AS_EXTERNAL_LEXER
 | |
| /*
 | |
| 	(actually seems to work!)
 | |
| */
 | |
| #include <string>
 | |
| #include "WindowAccessor.h"
 | |
| #include "ExternalLexer.h"
 | |
| 
 | |
| #undef EXT_LEXER_DECL
 | |
| #define EXT_LEXER_DECL __declspec( dllexport ) __stdcall
 | |
| 
 | |
| #if PLAT_WIN
 | |
| #include <windows.h>
 | |
| #endif
 | |
| 
 | |
| static void ColouriseCamlDoc(
 | |
| 	unsigned int startPos, int length,
 | |
| 	int initStyle,
 | |
| 	WordList *keywordlists[],
 | |
| 	Accessor &styler);
 | |
| 
 | |
| static void FoldCamlDoc(
 | |
| 	unsigned int startPos, int length,
 | |
| 	int initStyle,
 | |
| 	WordList *keywordlists[],
 | |
| 	Accessor &styler);
 | |
| 
 | |
| static void InternalLexOrFold(int lexOrFold, unsigned int startPos, int length,
 | |
| 	int initStyle, char *words[], WindowID window, char *props);
 | |
| 
 | |
| static const char* LexerName = "caml";
 | |
| 
 | |
| #ifdef TRACE
 | |
| void Platform::DebugPrintf(const char *format, ...) {
 | |
| 	char buffer[2000];
 | |
| 	va_list pArguments;
 | |
| 	va_start(pArguments, format);
 | |
| 	vsprintf(buffer,format,pArguments);
 | |
| 	va_end(pArguments);
 | |
| 	Platform::DebugDisplay(buffer);
 | |
| }
 | |
| #else
 | |
| void Platform::DebugPrintf(const char *, ...) {
 | |
| }
 | |
| #endif
 | |
| 
 | |
| bool Platform::IsDBCSLeadByte(int codePage, char ch) {
 | |
| 	return ::IsDBCSLeadByteEx(codePage, ch) != 0;
 | |
| }
 | |
| 
 | |
| long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
 | |
| 	return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam, lParam);
 | |
| }
 | |
| 
 | |
| long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
 | |
| 	return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam,
 | |
| 		reinterpret_cast<LPARAM>(lParam));
 | |
| }
 | |
| 
 | |
| void EXT_LEXER_DECL Fold(unsigned int lexer, unsigned int startPos, int length,
 | |
| 	int initStyle, char *words[], WindowID window, char *props)
 | |
| {
 | |
| 	// below useless evaluation(s) to supress "not used" warnings
 | |
| 	lexer;
 | |
| 	// build expected data structures and do the Fold
 | |
| 	InternalLexOrFold(1, startPos, length, initStyle, words, window, props);
 | |
| 
 | |
| }
 | |
| 
 | |
| int EXT_LEXER_DECL GetLexerCount()
 | |
| {
 | |
| 	return 1;	// just us [Objective] Caml lexers here!
 | |
| }
 | |
| 
 | |
| void EXT_LEXER_DECL GetLexerName(unsigned int Index, char *name, int buflength)
 | |
| {
 | |
| 	// below useless evaluation(s) to supress "not used" warnings
 | |
| 	Index;
 | |
| 	// return as much of our lexer name as will fit (what's up with Index?)
 | |
| 	if (buflength > 0) {
 | |
| 		buflength--;
 | |
| 		int n = strlen(LexerName);
 | |
| 		if (n > buflength)
 | |
| 			n = buflength;
 | |
| 		memcpy(name, LexerName, n), name[n] = '\0';
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EXT_LEXER_DECL Lex(unsigned int lexer, unsigned int startPos, int length,
 | |
| 	int initStyle, char *words[], WindowID window, char *props)
 | |
| {
 | |
| 	// below useless evaluation(s) to supress "not used" warnings
 | |
| 	lexer;
 | |
| 	// build expected data structures and do the Lex
 | |
| 	InternalLexOrFold(0, startPos, length, initStyle, words, window, props);
 | |
| }
 | |
| 
 | |
| static void InternalLexOrFold(int foldOrLex, unsigned int startPos, int length,
 | |
| 	int initStyle, char *words[], WindowID window, char *props)
 | |
| {
 | |
| 	// create and initialize a WindowAccessor (including contained PropSet)
 | |
| 	PropSetSimple ps;
 | |
| 	ps.SetMultiple(props);
 | |
| 	WindowAccessor wa(window, ps);
 | |
| 	// create and initialize WordList(s)
 | |
| 	int nWL = 0;
 | |
| 	for (; words[nWL]; nWL++) ;	// count # of WordList PTRs needed
 | |
| 	WordList** wl = new WordList* [nWL + 1];// alloc WordList PTRs
 | |
| 	int i = 0;
 | |
| 	for (; i < nWL; i++) {
 | |
| 		wl[i] = new WordList();	// (works or THROWS bad_alloc EXCEPTION)
 | |
| 		wl[i]->Set(words[i]);
 | |
| 	}
 | |
| 	wl[i] = 0;
 | |
| 	// call our "internal" folder/lexer (... then do Flush!)
 | |
| 	if (foldOrLex)
 | |
| 		FoldCamlDoc(startPos, length, initStyle, wl, wa);
 | |
| 	else
 | |
| 		ColouriseCamlDoc(startPos, length, initStyle, wl, wa);
 | |
| 	wa.Flush();
 | |
| 	// clean up before leaving
 | |
| 	for (i = nWL - 1; i >= 0; i--)
 | |
| 		delete wl[i];
 | |
| 	delete [] wl;
 | |
| }
 | |
| 
 | |
| static
 | |
| #endif	/* BUILD_AS_EXTERNAL_LEXER */
 | |
| 
 | |
| void ColouriseCamlDoc(
 | |
| 	unsigned int startPos, int length,
 | |
| 	int initStyle,
 | |
| 	WordList *keywordlists[],
 | |
| 	Accessor &styler)
 | |
| {
 | |
| 	// initialize styler
 | |
| 	StyleContext sc(startPos, length, initStyle, styler);
 | |
| 
 | |
| 	int chBase = 0, chToken = 0, chLit = 0;
 | |
| 	WordList& keywords  = *keywordlists[0];
 | |
| 	WordList& keywords2 = *keywordlists[1];
 | |
| 	WordList& keywords3 = *keywordlists[2];
 | |
| 	const bool isSML = keywords.InList("andalso");
 | |
| 	const int useMagic = styler.GetPropertyInt("lexer.caml.magic", 0);
 | |
| 
 | |
| 	// set up [initial] state info (terminating states that shouldn't "bleed")
 | |
| 	const int state_ = sc.state & 0x0f;
 | |
| 	if (state_ <= SCE_CAML_CHAR
 | |
| 		|| (isSML && state_ == SCE_CAML_STRING))
 | |
| 		sc.state = SCE_CAML_DEFAULT;
 | |
| 	int nesting = (state_ >= SCE_CAML_COMMENT)? (state_ - SCE_CAML_COMMENT): 0;
 | |
| 
 | |
| 	// foreach char in range...
 | |
| 	while (sc.More()) {
 | |
| 		// set up [per-char] state info
 | |
| 		int state2 = -1;				// (ASSUME no state change)
 | |
| 		int chColor = sc.currentPos - 1;// (ASSUME standard coloring range)
 | |
| 		bool advance = true;			// (ASSUME scanner "eats" 1 char)
 | |
| 
 | |
| 		// step state machine
 | |
| 		switch (sc.state & 0x0f) {
 | |
| 		case SCE_CAML_DEFAULT:
 | |
| 			chToken = sc.currentPos;	// save [possible] token start (JIC)
 | |
| 			// it's wide open; what do we have?
 | |
| 			if (iscamlf(sc.ch))
 | |
| 				state2 = SCE_CAML_IDENTIFIER;
 | |
| 			else if (!isSML && sc.Match('`') && iscamlf(sc.chNext))
 | |
| 				state2 = SCE_CAML_TAGNAME;
 | |
| 			else if (!isSML && sc.Match('#') && isdigit(sc.chNext))
 | |
| 				state2 = SCE_CAML_LINENUM;
 | |
| 			else if (isdigit(sc.ch)) {
 | |
| 				// it's a number, assume base 10
 | |
| 				state2 = SCE_CAML_NUMBER, chBase = 10;
 | |
| 				if (sc.Match('0')) {
 | |
| 					// there MAY be a base specified...
 | |
| 					const char* baseC = "bBoOxX";
 | |
| 					if (isSML) {
 | |
| 						if (sc.chNext == 'w')
 | |
| 							sc.Forward();	// (consume SML "word" indicator)
 | |
| 						baseC = "x";
 | |
| 					}
 | |
| 					// ... change to specified base AS REQUIRED
 | |
| 					if (strchr(baseC, sc.chNext))
 | |
| 						chBase = baseT[tolower(sc.chNext) - 'a'], sc.Forward();
 | |
| 				}
 | |
| 			} else if (!isSML && sc.Match('\''))	// (Caml char literal?)
 | |
| 				state2 = SCE_CAML_CHAR, chLit = 0;
 | |
| 			else if (isSML && sc.Match('#', '"'))	// (SML char literal?)
 | |
| 				state2 = SCE_CAML_CHAR, sc.Forward();
 | |
| 			else if (sc.Match('"'))
 | |
| 				state2 = SCE_CAML_STRING;
 | |
| 			else if (sc.Match('(', '*'))
 | |
| 				state2 = SCE_CAML_COMMENT, sc.Forward(), sc.ch = ' '; // (*)...
 | |
| 			else if (strchr("!?~"			/* Caml "prefix-symbol" */
 | |
| 					"=<>@^|&+-*/$%"			/* Caml "infix-symbol" */
 | |
| 					"()[]{};,:.#", sc.ch)	// Caml "bracket" or ;,:.#
 | |
| 											// SML "extra" ident chars
 | |
| 				|| (isSML && (sc.Match('\\') || sc.Match('`'))))
 | |
| 				state2 = SCE_CAML_OPERATOR;
 | |
| 			break;
 | |
| 
 | |
| 		case SCE_CAML_IDENTIFIER:
 | |
| 			// [try to] interpret as [additional] identifier char
 | |
| 			if (!(iscaml(sc.ch) || sc.Match('\''))) {
 | |
| 				const int n = sc.currentPos - chToken;
 | |
| 				if (n < 24) {
 | |
| 					// length is believable as keyword, [re-]construct token
 | |
| 					char t[24];
 | |
| 					for (int i = -n; i < 0; i++)
 | |
| 						t[n + i] = static_cast<char>(sc.GetRelative(i));
 | |
| 					t[n] = '\0';
 | |
| 					// special-case "_" token as KEYWORD
 | |
| 					if ((n == 1 && sc.chPrev == '_') || keywords.InList(t))
 | |
| 						sc.ChangeState(SCE_CAML_KEYWORD);
 | |
| 					else if (keywords2.InList(t))
 | |
| 						sc.ChangeState(SCE_CAML_KEYWORD2);
 | |
| 					else if (keywords3.InList(t))
 | |
| 						sc.ChangeState(SCE_CAML_KEYWORD3);
 | |
| 				}
 | |
| 				state2 = SCE_CAML_DEFAULT, advance = false;
 | |
| 			}
 | |
| 			break;
 | |
| 
 | |
| 		case SCE_CAML_TAGNAME:
 | |
| 			// [try to] interpret as [additional] tagname char
 | |
| 			if (!(iscaml(sc.ch) || sc.Match('\'')))
 | |
| 				state2 = SCE_CAML_DEFAULT, advance = false;
 | |
| 			break;
 | |
| 
 | |
| 		/*case SCE_CAML_KEYWORD:
 | |
| 		case SCE_CAML_KEYWORD2:
 | |
| 		case SCE_CAML_KEYWORD3:
 | |
| 			// [try to] interpret as [additional] keyword char
 | |
| 			if (!iscaml(ch))
 | |
| 				state2 = SCE_CAML_DEFAULT, advance = false;
 | |
| 			break;*/
 | |
| 
 | |
| 		case SCE_CAML_LINENUM:
 | |
| 			// [try to] interpret as [additional] linenum directive char
 | |
| 			if (!isdigit(sc.ch))
 | |
| 				state2 = SCE_CAML_DEFAULT, advance = false;
 | |
| 			break;
 | |
| 
 | |
| 		case SCE_CAML_OPERATOR: {
 | |
| 			// [try to] interpret as [additional] operator char
 | |
| 			const char* o = 0;
 | |
| 			if (iscaml(sc.ch) || isspace(sc.ch)			// ident or whitespace
 | |
| 				|| (o = strchr(")]};,\'\"#", sc.ch),o)	// "termination" chars
 | |
| 				|| (!isSML && sc.Match('`'))			// Caml extra term char
 | |
| 				|| (!strchr("!$%&*+-./:<=>?@^|~", sc.ch)// "operator" chars
 | |
| 														// SML extra ident chars
 | |
| 					&& !(isSML && (sc.Match('\\') || sc.Match('`'))))) {
 | |
| 				// check for INCLUSIVE termination
 | |
| 				if (o && strchr(")]};,", sc.ch)) {
 | |
| 					if ((sc.Match(')') && sc.chPrev == '(')
 | |
| 						|| (sc.Match(']') && sc.chPrev == '['))
 | |
| 						// special-case "()" and "[]" tokens as KEYWORDS
 | |
| 						sc.ChangeState(SCE_CAML_KEYWORD);
 | |
| 					chColor++;
 | |
| 				} else
 | |
| 					advance = false;
 | |
| 				state2 = SCE_CAML_DEFAULT;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		case SCE_CAML_NUMBER:
 | |
| 			// [try to] interpret as [additional] numeric literal char
 | |
| 			if ((!isSML && sc.Match('_')) || IsADigit(sc.ch, chBase))
 | |
| 				break;
 | |
| 			// how about an integer suffix?
 | |
| 			if (!isSML && (sc.Match('l') || sc.Match('L') || sc.Match('n'))
 | |
| 				&& (sc.chPrev == '_' || IsADigit(sc.chPrev, chBase)))
 | |
| 				break;
 | |
| 			// or a floating-point literal?
 | |
| 			if (chBase == 10) {
 | |
| 				// with a decimal point?
 | |
| 				if (sc.Match('.')
 | |
| 					&& ((!isSML && sc.chPrev == '_')
 | |
| 						|| IsADigit(sc.chPrev, chBase)))
 | |
| 					break;
 | |
| 				// with an exponent? (I)
 | |
| 				if ((sc.Match('e') || sc.Match('E'))
 | |
| 					&& ((!isSML && (sc.chPrev == '.' || sc.chPrev == '_'))
 | |
| 						|| IsADigit(sc.chPrev, chBase)))
 | |
| 					break;
 | |
| 				// with an exponent? (II)
 | |
| 				if (((!isSML && (sc.Match('+') || sc.Match('-')))
 | |
| 						|| (isSML && sc.Match('~')))
 | |
| 					&& (sc.chPrev == 'e' || sc.chPrev == 'E'))
 | |
| 					break;
 | |
| 			}
 | |
| 			// it looks like we have run out of number
 | |
| 			state2 = SCE_CAML_DEFAULT, advance = false;
 | |
| 			break;
 | |
| 
 | |
| 		case SCE_CAML_CHAR:
 | |
| 			if (!isSML) {
 | |
| 				// [try to] interpret as [additional] char literal char
 | |
| 				if (sc.Match('\\')) {
 | |
| 					chLit = 1;	// (definitely IS a char literal)
 | |
| 					if (sc.chPrev == '\\')
 | |
| 						sc.ch = ' ';	// (...\\')
 | |
| 				// should we be terminating - one way or another?
 | |
| 				} else if ((sc.Match('\'') && sc.chPrev != '\\')
 | |
| 					|| sc.atLineEnd) {
 | |
| 					state2 = SCE_CAML_DEFAULT;
 | |
| 					if (sc.Match('\''))
 | |
| 						chColor++;
 | |
| 					else
 | |
| 						sc.ChangeState(SCE_CAML_IDENTIFIER);
 | |
| 				// ... maybe a char literal, maybe not
 | |
| 				} else if (chLit < 1 && sc.currentPos - chToken >= 2)
 | |
| 					sc.ChangeState(SCE_CAML_IDENTIFIER), advance = false;
 | |
| 				break;
 | |
| 			}/* else
 | |
| 				// fall through for SML char literal (handle like string) */
 | |
| 
 | |
| 		case SCE_CAML_STRING:
 | |
| 			// [try to] interpret as [additional] [SML char/] string literal char
 | |
| 			if (isSML && sc.Match('\\') && sc.chPrev != '\\' && isspace(sc.chNext))
 | |
| 				state2 = SCE_CAML_WHITE;
 | |
| 			else if (sc.Match('\\') && sc.chPrev == '\\')
 | |
| 				sc.ch = ' ';	// (...\\")
 | |
| 			// should we be terminating - one way or another?
 | |
| 			else if ((sc.Match('"') && sc.chPrev != '\\')
 | |
| 				|| (isSML && sc.atLineEnd)) {
 | |
| 				state2 = SCE_CAML_DEFAULT;
 | |
| 				if (sc.Match('"'))
 | |
| 					chColor++;
 | |
| 			}
 | |
| 			break;
 | |
| 
 | |
| 		case SCE_CAML_WHITE:
 | |
| 			// [try to] interpret as [additional] SML embedded whitespace char
 | |
| 			if (sc.Match('\\')) {
 | |
| 				// style this puppy NOW...
 | |
| 				state2 = SCE_CAML_STRING, sc.ch = ' ' /* (...\") */, chColor++,
 | |
| 					styler.ColourTo(chColor, SCE_CAML_WHITE), styler.Flush();
 | |
| 				// ... then backtrack to determine original SML literal type
 | |
| 				int p = chColor - 2;
 | |
| 				for (; p >= 0 && styler.StyleAt(p) == SCE_CAML_WHITE; p--) ;
 | |
| 				if (p >= 0)
 | |
| 					state2 = static_cast<int>(styler.StyleAt(p));
 | |
| 				// take care of state change NOW
 | |
| 				sc.ChangeState(state2), state2 = -1;
 | |
| 			}
 | |
| 			break;
 | |
| 
 | |
| 		case SCE_CAML_COMMENT:
 | |
| 		case SCE_CAML_COMMENT1:
 | |
| 		case SCE_CAML_COMMENT2:
 | |
| 		case SCE_CAML_COMMENT3:
 | |
| 			// we're IN a comment - does this start a NESTED comment?
 | |
| 			if (sc.Match('(', '*'))
 | |
| 				state2 = sc.state + 1, chToken = sc.currentPos,
 | |
| 					sc.Forward(), sc.ch = ' ' /* (*)... */, nesting++;
 | |
| 			// [try to] interpret as [additional] comment char
 | |
| 			else if (sc.Match(')') && sc.chPrev == '*') {
 | |
| 				if (nesting)
 | |
| 					state2 = (sc.state & 0x0f) - 1, chToken = 0, nesting--;
 | |
| 				else
 | |
| 					state2 = SCE_CAML_DEFAULT;
 | |
| 				chColor++;
 | |
| 			// enable "magic" (read-only) comment AS REQUIRED
 | |
| 			} else if (useMagic && sc.currentPos - chToken == 4
 | |
| 				&& sc.Match('c') && sc.chPrev == 'r' && sc.GetRelative(-2) == '@')
 | |
| 				sc.state |= 0x10;	// (switch to read-only comment style)
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		// handle state change and char coloring AS REQUIRED
 | |
| 		if (state2 >= 0)
 | |
| 			styler.ColourTo(chColor, sc.state), sc.ChangeState(state2);
 | |
| 		// move to next char UNLESS re-scanning current char
 | |
| 		if (advance)
 | |
| 			sc.Forward();
 | |
| 	}
 | |
| 
 | |
| 	// do any required terminal char coloring (JIC)
 | |
| 	sc.Complete();
 | |
| }
 | |
| 
 | |
| #ifdef BUILD_AS_EXTERNAL_LEXER
 | |
| static
 | |
| #endif	/* BUILD_AS_EXTERNAL_LEXER */
 | |
| void FoldCamlDoc(
 | |
| 	unsigned int, int,
 | |
| 	int,
 | |
| 	WordList *[],
 | |
| 	Accessor &)
 | |
| {
 | |
| }
 | |
| 
 | |
| static const char * const camlWordListDesc[] = {
 | |
| 	"Keywords",		// primary Objective Caml keywords
 | |
| 	"Keywords2",	// "optional" keywords (typically from Pervasives)
 | |
| 	"Keywords3",	// "optional" keywords (typically typenames)
 | |
| 	0
 | |
| };
 | |
| 
 | |
| #ifndef BUILD_AS_EXTERNAL_LEXER
 | |
| LexerModule lmCaml(SCLEX_CAML, ColouriseCamlDoc, "caml", FoldCamlDoc, camlWordListDesc);
 | |
| #endif	/* BUILD_AS_EXTERNAL_LEXER */
 |