// Scintilla source code edit control /** @file LexCaml.cxx ** Lexer for Objective Caml. **/ // Copyright 2005 by Robert Roessler // 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. */ #include #include #include #include #include #include "Platform.h" #include "PropSet.h" #include "Accessor.h" #include "StyleContext.h" #include "KeyWords.h" #include "Scintilla.h" #include "SciLexer.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 == '_';} inline int iscamld(int c) {return isdigit(c) || c == '_';} #ifdef BUILD_AS_EXTERNAL_LEXER /* (actually seems to work!) */ #include "WindowAccessor.h" #include "ExternalLexer.h" #if PLAT_WIN #include #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(w), msg, wParam, lParam); } long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam) { return ::SendMessage(reinterpret_cast(w), msg, wParam, reinterpret_cast(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) PropSet 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 styler.StartAt(startPos); styler.StartSegment(startPos); // set up [initial] state info (terminating states that shouldn't "bleed") int state = initStyle, nesting = 0; if (state < SCE_CAML_STRING) state = SCE_CAML_DEFAULT; if (state >= SCE_CAML_COMMENT) nesting = state - SCE_CAML_COMMENT; int chLast = startPos? static_cast(styler[startPos - 1]): ' '; int chNext = static_cast(styler[startPos]); int chBase = 'd', chToken = 0, chLit = 0, chSkip; WordList& keywords = *keywordlists[0]; WordList& keywords2 = *keywordlists[1]; // foreach char in range... unsigned int i = startPos; const unsigned int endPos = startPos + length; for (; i < endPos; i += chSkip) { // set up [per-char] state info int ch = chNext; chNext = static_cast(styler.SafeGetCharAt(i + 1)); int state2 = -1; // (ASSUME no state change) int chColor = i - 1;// (ASSUME standard coloring range) chSkip = 1; // (ASSUME scanner "eats" 1 char) // this may be the correct thing to do... or not if (styler.IsLeadByte(static_cast(ch))) { chNext = static_cast(styler.SafeGetCharAt(i + 2)), chSkip++; continue; } // step state machine switch (state) { case SCE_CAML_DEFAULT: // it's wide open; what do we have? if (iscamlf(ch)) state2 = SCE_CAML_IDENTIFIER, chToken = i; else if (ch == '`') state2 = SCE_CAML_TAGNAME, chToken = i; else if (ch == '#' && isdigit(chNext)) state2 = SCE_CAML_LINENUM, chToken = i; else if (isdigit(ch)) { state2 = SCE_CAML_NUMBER, chBase = strchr("xXoObB", chNext)? chNext: 'd'; if (chBase != 'd') ch = chNext, chNext = static_cast(styler.SafeGetCharAt(i + 2)), chSkip++; } else if (ch == '\'') /* (char literal?) */ state2 = SCE_CAML_CHAR, chToken = i, chLit = 0; else if (ch == '\"') state2 = SCE_CAML_STRING; else if (ch == '(' && chNext == '*') state2 = SCE_CAML_COMMENT, ch = ' ', // (make SURE "(*)" isn't seen as a closed comment) chNext = static_cast(styler.SafeGetCharAt(i + 2)), chSkip++, nesting = 0; else if (strchr("!?~" /* Caml "prefix-symbol" */ "=<>@^|&+-*/$%" /* Caml "infix-symbol" */ "()[]{};,:.#", ch)) /* Caml "bracket" or ;,:.# */ state2 = SCE_CAML_OPERATOR, chToken = i; break; case SCE_CAML_IDENTIFIER: // [try to] interpret as [additional] identifier char if (!(iscaml(ch) || ch == '\'')) { const int n = i - chToken; if (n < 24) { // length is believable as keyword, [re-]construct token char t[24]; int p = 0; for (int q = chToken; p < n; p++, q++) t[p] = styler[q]; t[p] = '\0'; // special-case "_" token as KEYWORD if ((n == 1 && chLast == '_') || keywords.InList(t)) state = SCE_CAML_KEYWORD; else if (keywords2.InList(t)) state = SCE_CAML_KEYWORD2; } state2 = SCE_CAML_DEFAULT, chNext = ch, chSkip--; } break; case SCE_CAML_TAGNAME: // [try to] interpret as [additional] tagname char if (!(iscaml(ch) || ch == '\'')) state2 = SCE_CAML_DEFAULT, chNext = ch, chSkip--; break; /*case SCE_CAML_KEYWORD: case SCE_CAML_KEYWORD2: // [try to] interpret as [additional] keyword char if (!iscaml(ch)) state2 = SCE_CAML_DEFAULT, chNext = ch, chSkip--; break;*/ case SCE_CAML_LINENUM: // [try to] interpret as [additional] linenum directive char if (!isdigit(ch)) state2 = SCE_CAML_DEFAULT, chNext = ch, chSkip--; break; case SCE_CAML_OPERATOR: { // [try to] interpret as [additional] operator char char* o = 0; if (iscaml(ch) || isspace(ch) /* ident or whitespace */ || ((o = strchr(")]};,\'\"`#", ch)) != 0)/* "termination" chars */ || !strchr("!$%&*+-./:<=>?@^|~", ch)/* "operator" chars */) { // check for INCLUSIVE termination if (o && strchr(")]};,", ch)) { if ((ch == ')' && chLast == '(') || (ch == ']' && chLast == '[')) // special-case "()" and "[]" tokens as KEYWORDS state = SCE_CAML_KEYWORD; chColor++; } else chNext = ch, chSkip--; state2 = SCE_CAML_DEFAULT; } break; } case SCE_CAML_NUMBER: // [try to] interpret as [additional] numeric literal char // N.B. - improperly accepts "extra" digits in base 2 or 8 literals if (iscamld(ch) || ((chBase == 'x' || chBase == 'X') && isxdigit(ch))) break; // how about an integer suffix? if ((ch == 'l' || ch == 'L' || ch == 'n')&& (iscamld(chLast) || ((chBase == 'x' || chBase == 'X') && isxdigit(chLast)))) break; // or a floating-point literal? if (chBase == 'd') { // with a decimal point? if (ch == '.' && iscamld(chLast)) break; // with an exponent? (I) if ((ch == 'e' || ch == 'E') && (iscamld(chLast) || chLast == '.')) break; // with an exponent? (II) if ((ch == '+' || ch == '-') && (chLast == 'e' || chLast == 'E')) break; } // it looks like we have run out of number state2 = SCE_CAML_DEFAULT, chNext = ch, chSkip--; break; case SCE_CAML_CHAR: // [try to] interpret as [additional] char literal char if (ch == '\\') { chLit = 1; // (definitely IS a char literal) if (chLast == '\\') ch = ' '; // (so termination test isn't fooled) // should we be terminating - one way or another? } else if ((ch == '\'' && chLast != '\\') || ch == '\r' || ch == '\n') { state2 = SCE_CAML_DEFAULT; if (ch == '\'') chColor++; else state = SCE_CAML_IDENTIFIER; // ... maybe a char literal, maybe not } else if (chLit < 1 && i - chToken >= 2) state = SCE_CAML_IDENTIFIER, chNext = ch, chSkip--; break; case SCE_CAML_STRING: // [try to] interpret as [additional] string literal char if (ch == '\\' && chLast == '\\') ch = ' '; // (so '\\' doesn't cause us trouble) else if (ch == '\"' && chLast != '\\') state2 = SCE_CAML_DEFAULT, chColor++; break; case SCE_CAML_COMMENT: case SCE_CAML_COMMENT+1: case SCE_CAML_COMMENT+2: case SCE_CAML_COMMENT+3: // we're IN a comment - does this start a NESTED comment? if (ch == '(' && chNext == '*') state2 = state + 1, ch = ' ', // (make SURE "(*)" isn't seen as a closed comment) chNext = static_cast(styler.SafeGetCharAt(i + 2)), chSkip++, nesting++; // [try to] interpret as [additional] comment char else if (ch == ')' && chLast == '*') state2 = nesting? (state - 1): SCE_CAML_DEFAULT, chColor++, nesting--; break; } // handle state change and char coloring as required if (state2 >= 0) { // (1st char will NOT be colored until AT LEAST 2nd char) if (chColor >= 0) styler.ColourTo(chColor, state); state = state2; } chLast = ch; } // do any required terminal char coloring (JIC) if (i >= endPos) i = endPos - 1; styler.ColourTo(i, state); // styler.Flush(); // (is this always done by calling code?) } #ifdef BUILD_AS_EXTERNAL_LEXER static #endif /* BUILD_AS_EXTERNAL_LEXER */ void FoldCamlDoc( unsigned int startPos, int length, int initStyle, WordList *keywordlists[], Accessor &styler) { // below useless evaluation(s) to supress "not used" warnings startPos || length || initStyle || keywordlists[0] || styler.Length(); } static const char * const camlWordListDesc[] = { "Keywords", // primary Objective Caml keywords "Keywords2", // "optional" keywords (typically from Pervasives) 0 }; #ifndef BUILD_AS_EXTERNAL_LEXER LexerModule lmCaml(SCLEX_CAML, ColouriseCamlDoc, "caml", FoldCamlDoc, camlWordListDesc); #endif /* BUILD_AS_EXTERNAL_LEXER */