1 // Scintilla source code edit control
2 
3 // File: LexMetapost.cxx - general context conformant metapost coloring scheme
4 // Author: Hans Hagen - PRAGMA ADE - Hasselt NL - www.pragma-ade.com
5 // Version: September 28, 2003
6 // Modified by instanton: July 10, 2007
7 // Folding based on keywordlists[]
8 
9 // Copyright: 1998-2003 by Neil Hodgson <neilh@scintilla.org>
10 // The License.txt file describes the conditions under which this software may be distributed.
11 
12 // This lexer is derived from the one written for the texwork environment (1999++) which in
13 // turn is inspired on texedit (1991++) which finds its roots in wdt (1986).
14 
15 #include <stdlib.h>
16 #include <string.h>
17 #include <stdio.h>
18 #include <stdarg.h>
19 #include <assert.h>
20 #include <ctype.h>
21 
22 #include "ILexer.h"
23 #include "Scintilla.h"
24 #include "SciLexer.h"
25 
26 #include "WordList.h"
27 #include "LexAccessor.h"
28 #include "Accessor.h"
29 #include "StyleContext.h"
30 #include "CharacterSet.h"
31 #include "LexerModule.h"
32 
33 #ifdef SCI_NAMESPACE
34 using namespace Scintilla;
35 #endif
36 
37 // val SCE_METAPOST_DEFAULT = 0
38 // val SCE_METAPOST_SPECIAL = 1
39 // val SCE_METAPOST_GROUP = 2
40 // val SCE_METAPOST_SYMBOL = 3
41 // val SCE_METAPOST_COMMAND = 4
42 // val SCE_METAPOST_TEXT = 5
43 
44 // Definitions in SciTEGlobal.properties:
45 //
46 // Metapost Highlighting
47 //
48 // # Default
49 // style.metapost.0=fore:#7F7F00
50 // # Special
51 // style.metapost.1=fore:#007F7F
52 // # Group
53 // style.metapost.2=fore:#880000
54 // # Symbol
55 // style.metapost.3=fore:#7F7F00
56 // # Command
57 // style.metapost.4=fore:#008800
58 // # Text
59 // style.metapost.5=fore:#000000
60 
61 // lexer.tex.comment.process=0
62 
63 // Auxiliary functions:
64 
endOfLine(Accessor & styler,unsigned int i)65 static inline bool endOfLine(Accessor &styler, unsigned int i) {
66 	return
67       (styler[i] == '\n') || ((styler[i] == '\r') && (styler.SafeGetCharAt(i + 1) != '\n')) ;
68 }
69 
isMETAPOSTcomment(int ch)70 static inline bool isMETAPOSTcomment(int ch) {
71 	return
72       (ch == '%') ;
73 }
74 
isMETAPOSTone(int ch)75 static inline bool isMETAPOSTone(int ch) {
76 	return
77       (ch == '[') || (ch == ']') || (ch == '(') || (ch == ')') ||
78       (ch == ':') || (ch == '=') || (ch == '<') || (ch == '>') ||
79       (ch == '{') || (ch == '}') || (ch == '\'') || (ch == '\"') ;
80 }
81 
isMETAPOSTtwo(int ch)82 static inline bool isMETAPOSTtwo(int ch) {
83 	return
84       (ch == ';') || (ch == '$') || (ch == '@') || (ch == '#');
85 }
86 
isMETAPOSTthree(int ch)87 static inline bool isMETAPOSTthree(int ch) {
88 	return
89       (ch == '.') || (ch == '-') || (ch == '+') || (ch == '/') ||
90       (ch == '*') || (ch == ',') || (ch == '|') || (ch == '`') ||
91       (ch == '!') || (ch == '?') || (ch == '^') || (ch == '&') ||
92       (ch == '%') ;
93 }
94 
isMETAPOSTidentifier(int ch)95 static inline bool isMETAPOSTidentifier(int ch) {
96 	return
97       ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) ||
98       (ch == '_') ;
99 }
100 
isMETAPOSTnumber(int ch)101 static inline bool isMETAPOSTnumber(int ch) {
102 	return
103       (ch >= '0') && (ch <= '9') ;
104 }
105 
isMETAPOSTstring(int ch)106 static inline bool isMETAPOSTstring(int ch) {
107 	return
108       (ch == '\"') ;
109 }
110 
isMETAPOSTcolon(int ch)111 static inline bool isMETAPOSTcolon(int ch) {
112 	return
113 		(ch == ':') ;
114 }
115 
isMETAPOSTequal(int ch)116 static inline bool isMETAPOSTequal(int ch) {
117 	return
118 		(ch == '=') ;
119 }
120 
CheckMETAPOSTInterface(unsigned int startPos,int length,Accessor & styler,int defaultInterface)121 static int CheckMETAPOSTInterface(
122     unsigned int startPos,
123     int length,
124     Accessor &styler,
125 	int defaultInterface) {
126 
127     char lineBuffer[1024] ;
128 	unsigned int linePos = 0 ;
129 
130 	// some day we can make something lexer.metapost.mapping=(none,0)(metapost,1)(mp,1)(metafun,2)...
131 
132     if (styler.SafeGetCharAt(0) == '%') {
133         for (unsigned int i = 0; i < startPos + length; i++) {
134             lineBuffer[linePos++] = styler.SafeGetCharAt(i) ;
135             if (endOfLine(styler, i) || (linePos >= sizeof(lineBuffer) - 1)) {
136                 lineBuffer[linePos] = '\0';
137 				if (strstr(lineBuffer, "interface=none")) {
138                     return 0 ;
139 				} else if (strstr(lineBuffer, "interface=metapost") || strstr(lineBuffer, "interface=mp")) {
140                     return 1 ;
141 				} else if (strstr(lineBuffer, "interface=metafun")) {
142                     return 2 ;
143 				} else if (styler.SafeGetCharAt(1) == 'D' && strstr(lineBuffer, "%D \\module")) {
144 					// better would be to limit the search to just one line
145 					return 2 ;
146                 } else {
147                     return defaultInterface ;
148                 }
149             }
150 		}
151     }
152 
153     return defaultInterface ;
154 }
155 
ColouriseMETAPOSTDoc(unsigned int startPos,int length,int,WordList * keywordlists[],Accessor & styler)156 static void ColouriseMETAPOSTDoc(
157     unsigned int startPos,
158     int length,
159     int,
160     WordList *keywordlists[],
161     Accessor &styler) {
162 
163 	styler.StartAt(startPos) ;
164 	styler.StartSegment(startPos) ;
165 
166 	bool processComment   = styler.GetPropertyInt("lexer.metapost.comment.process",   0) == 1 ;
167     int  defaultInterface = styler.GetPropertyInt("lexer.metapost.interface.default", 1) ;
168 
169 	int currentInterface = CheckMETAPOSTInterface(startPos,length,styler,defaultInterface) ;
170 
171 	// 0  no keyword highlighting
172 	// 1  metapost keyword hightlighting
173 	// 2+ metafun keyword hightlighting
174 
175 	int extraInterface = 0 ;
176 
177 	if (currentInterface != 0) {
178 		extraInterface = currentInterface ;
179 	}
180 
181 	WordList &keywords  = *keywordlists[0] ;
182 	WordList &keywords2 = *keywordlists[extraInterface-1] ;
183 
184 	StyleContext sc(startPos, length, SCE_METAPOST_TEXT, styler) ;
185 
186 	char key[100] ;
187 
188     bool inTeX     = false ;
189 	bool inComment = false ;
190 	bool inString  = false ;
191 	bool inClause  = false ;
192 
193 	bool going = sc.More() ; // needed because of a fuzzy end of file state
194 
195 	for (; going; sc.Forward()) {
196 
197 		if (! sc.More()) { going = false ; } // we need to go one behind the end of text
198 
199 		if (inClause) {
200 			sc.SetState(SCE_METAPOST_TEXT) ;
201 			inClause = false ;
202 		}
203 
204 		if (inComment) {
205 			if (sc.atLineEnd) {
206 				sc.SetState(SCE_METAPOST_TEXT) ;
207 				inTeX = false ;
208 				inComment = false ;
209 				inClause = false ;
210 				inString = false ; // not correct but we want to stimulate one-lines
211 			}
212 		} else if (inString) {
213 			if (isMETAPOSTstring(sc.ch)) {
214 				sc.SetState(SCE_METAPOST_SPECIAL) ;
215 				sc.ForwardSetState(SCE_METAPOST_TEXT) ;
216 				inString = false ;
217 			} else if (sc.atLineEnd) {
218 				sc.SetState(SCE_METAPOST_TEXT) ;
219 				inTeX = false ;
220 				inComment = false ;
221 				inClause = false ;
222 				inString = false ; // not correct but we want to stimulate one-lines
223 			}
224 		} else {
225 			if ((! isMETAPOSTidentifier(sc.ch)) && (sc.LengthCurrent() > 0)) {
226 				if (sc.state == SCE_METAPOST_COMMAND) {
227 					sc.GetCurrent(key, sizeof(key)) ;
228 					if ((strcmp(key,"btex") == 0) || (strcmp(key,"verbatimtex") == 0)) {
229     					sc.ChangeState(SCE_METAPOST_GROUP) ;
230 						inTeX = true ;
231 					} else if (inTeX) {
232 						if (strcmp(key,"etex") == 0) {
233 	    					sc.ChangeState(SCE_METAPOST_GROUP) ;
234 							inTeX = false ;
235 						} else {
236 	    					sc.ChangeState(SCE_METAPOST_TEXT) ;
237 						}
238 					} else {
239 						if (keywords && keywords.InList(key)) {
240     						sc.ChangeState(SCE_METAPOST_COMMAND) ;
241 						} else if (keywords2 && keywords2.InList(key)) {
242 							sc.ChangeState(SCE_METAPOST_EXTRA) ;
243 						} else {
244 							sc.ChangeState(SCE_METAPOST_TEXT) ;
245 						}
246 					}
247 				}
248 			}
249 			if (isMETAPOSTcomment(sc.ch)) {
250 				if (! inTeX) {
251 					sc.SetState(SCE_METAPOST_SYMBOL) ;
252 					sc.ForwardSetState(SCE_METAPOST_DEFAULT) ;
253 					inComment = ! processComment ;
254 				} else {
255 					sc.SetState(SCE_METAPOST_TEXT) ;
256 				}
257 			} else if (isMETAPOSTstring(sc.ch)) {
258 				if (! inTeX) {
259 					sc.SetState(SCE_METAPOST_SPECIAL) ;
260 					if (! isMETAPOSTstring(sc.chNext)) {
261 						sc.ForwardSetState(SCE_METAPOST_TEXT) ;
262 					}
263 					inString = true ;
264 				} else {
265 					sc.SetState(SCE_METAPOST_TEXT) ;
266 				}
267 			} else if (isMETAPOSTcolon(sc.ch)) {
268 				if (! inTeX) {
269 					if (! isMETAPOSTequal(sc.chNext)) {
270 						sc.SetState(SCE_METAPOST_COMMAND) ;
271 						inClause = true ;
272 					} else {
273 						sc.SetState(SCE_METAPOST_SPECIAL) ;
274 					}
275 				} else {
276 					sc.SetState(SCE_METAPOST_TEXT) ;
277 				}
278 			} else if (isMETAPOSTone(sc.ch)) {
279 				if (! inTeX) {
280 					sc.SetState(SCE_METAPOST_SPECIAL) ;
281 				} else {
282 					sc.SetState(SCE_METAPOST_TEXT) ;
283 				}
284 			} else if (isMETAPOSTtwo(sc.ch)) {
285 				if (! inTeX) {
286 					sc.SetState(SCE_METAPOST_GROUP) ;
287 				} else {
288 					sc.SetState(SCE_METAPOST_TEXT) ;
289 				}
290 			} else if (isMETAPOSTthree(sc.ch)) {
291 				if (! inTeX) {
292 					sc.SetState(SCE_METAPOST_SYMBOL) ;
293 				} else {
294 					sc.SetState(SCE_METAPOST_TEXT) ;
295 				}
296 			} else if (isMETAPOSTidentifier(sc.ch)) {
297 				if (sc.state != SCE_METAPOST_COMMAND) {
298 					sc.SetState(SCE_METAPOST_TEXT) ;
299 					sc.ChangeState(SCE_METAPOST_COMMAND) ;
300 				}
301 			} else if (isMETAPOSTnumber(sc.ch)) {
302 				// rather redundant since for the moment we don't handle numbers
303 				sc.SetState(SCE_METAPOST_TEXT) ;
304 			} else if (sc.atLineEnd) {
305 				sc.SetState(SCE_METAPOST_TEXT) ;
306 				inTeX = false ;
307 				inComment = false ;
308 				inClause = false ;
309 				inString = false ;
310 			} else {
311 				sc.SetState(SCE_METAPOST_TEXT) ;
312 			}
313 		}
314 
315 	}
316 
317 	sc.Complete();
318 
319 }
320 
321 // Hooks info the system:
322 
323 static const char * const metapostWordListDesc[] = {
324 	"MetaPost",
325 	"MetaFun",
326 	0
327 } ;
328 
classifyFoldPointMetapost(const char * s,WordList * keywordlists[])329 static int classifyFoldPointMetapost(const char* s,WordList *keywordlists[]) {
330 	WordList& keywordsStart=*keywordlists[3];
331 	WordList& keywordsStop1=*keywordlists[4];
332 
333 	if (keywordsStart.InList(s)) {return 1;}
334 	else if (keywordsStop1.InList(s)) {return -1;}
335 	return 0;
336 
337 }
338 
ParseMetapostWord(unsigned int pos,Accessor & styler,char * word)339 static int ParseMetapostWord(unsigned int pos, Accessor &styler, char *word)
340 {
341   int length=0;
342   char ch=styler.SafeGetCharAt(pos);
343   *word=0;
344 
345   while(isMETAPOSTidentifier(ch) && isalpha(ch) && length<100){
346           word[length]=ch;
347           length++;
348           ch=styler.SafeGetCharAt(pos+length);
349   }
350   word[length]=0;
351   return length;
352 }
353 
FoldMetapostDoc(unsigned int startPos,int length,int,WordList * keywordlists[],Accessor & styler)354 static void FoldMetapostDoc(unsigned int startPos, int length, int, WordList *keywordlists[], Accessor &styler)
355 {
356 	bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
357 	unsigned int endPos = startPos+length;
358 	int visibleChars=0;
359 	int lineCurrent=styler.GetLine(startPos);
360 	int levelPrev=styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
361 	int levelCurrent=levelPrev;
362 	char chNext=styler[startPos];
363 
364 	char buffer[100]="";
365 
366 	for (unsigned int i=startPos; i < endPos; i++) {
367 		char ch=chNext;
368 		chNext=styler.SafeGetCharAt(i+1);
369 		char chPrev=styler.SafeGetCharAt(i-1);
370 		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
371 
372 		if(i==0 || chPrev == '\r' || chPrev=='\n'|| chPrev==' '|| chPrev=='(' || chPrev=='$')
373 		{
374             ParseMetapostWord(i, styler, buffer);
375 			levelCurrent += classifyFoldPointMetapost(buffer,keywordlists);
376 		}
377 
378 		if (atEOL) {
379 			int lev = levelPrev;
380 			if (visibleChars == 0 && foldCompact)
381 				lev |= SC_FOLDLEVELWHITEFLAG;
382 			if ((levelCurrent > levelPrev) && (visibleChars > 0))
383 				lev |= SC_FOLDLEVELHEADERFLAG;
384 			if (lev != styler.LevelAt(lineCurrent)) {
385 				styler.SetLevel(lineCurrent, lev);
386 			}
387 			lineCurrent++;
388 			levelPrev = levelCurrent;
389 			visibleChars = 0;
390 		}
391 
392 		if (!isspacechar(ch))
393 			visibleChars++;
394 	}
395 	// Fill in the real level of the next line, keeping the current flags as they will be filled in later
396 	int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
397 	styler.SetLevel(lineCurrent, levelPrev | flagsNext);
398 
399 }
400 
401 
402 LexerModule lmMETAPOST(SCLEX_METAPOST, ColouriseMETAPOSTDoc, "metapost", FoldMetapostDoc, metapostWordListDesc);
403