1 // Scintilla source code edit control
2 /** @file LexAVS.cxx
3 ** Lexer for AviSynth.
4 **/
5 // Copyright 2012 by Bruno Barbieri <brunorex@gmail.com>
6 // Heavily based on LexPOV by Neil Hodgson
7 // The License.txt file describes the conditions under which this software may be distributed.
8
9 #include <stdlib.h>
10 #include <string.h>
11 #include <stdio.h>
12 #include <stdarg.h>
13 #include <assert.h>
14 #include <ctype.h>
15
16 #include "ILexer.h"
17 #include "Scintilla.h"
18 #include "SciLexer.h"
19
20 #include "WordList.h"
21 #include "LexAccessor.h"
22 #include "Accessor.h"
23 #include "StyleContext.h"
24 #include "CharacterSet.h"
25 #include "LexerModule.h"
26
27 using namespace Scintilla;
28
IsAWordChar(const int ch)29 static inline bool IsAWordChar(const int ch) {
30 return (ch < 0x80) && (isalnum(ch) || ch == '_');
31 }
32
IsAWordStart(int ch)33 static inline bool IsAWordStart(int ch) {
34 return isalpha(ch) || (ch != ' ' && ch != '\n' && ch != '(' && ch != '.' && ch != ',');
35 }
36
IsANumberChar(int ch)37 static inline bool IsANumberChar(int ch) {
38 // Not exactly following number definition (several dots are seen as OK, etc.)
39 // but probably enough in most cases.
40 return (ch < 0x80) &&
41 (isdigit(ch) || ch == '.' || ch == '-' || ch == '+');
42 }
43
ColouriseAvsDoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * keywordlists[],Accessor & styler)44 static void ColouriseAvsDoc(
45 Sci_PositionU startPos,
46 Sci_Position length,
47 int initStyle,
48 WordList *keywordlists[],
49 Accessor &styler) {
50
51 WordList &keywords = *keywordlists[0];
52 WordList &filters = *keywordlists[1];
53 WordList &plugins = *keywordlists[2];
54 WordList &functions = *keywordlists[3];
55 WordList &clipProperties = *keywordlists[4];
56 WordList &userDefined = *keywordlists[5];
57
58 Sci_Position currentLine = styler.GetLine(startPos);
59 // Initialize the block comment nesting level, if we are inside such a comment.
60 int blockCommentLevel = 0;
61 if (initStyle == SCE_AVS_COMMENTBLOCK || initStyle == SCE_AVS_COMMENTBLOCKN) {
62 blockCommentLevel = styler.GetLineState(currentLine - 1);
63 }
64
65 // Do not leak onto next line
66 if (initStyle == SCE_AVS_COMMENTLINE) {
67 initStyle = SCE_AVS_DEFAULT;
68 }
69
70 StyleContext sc(startPos, length, initStyle, styler);
71
72 for (; sc.More(); sc.Forward()) {
73 if (sc.atLineEnd) {
74 // Update the line state, so it can be seen by next line
75 currentLine = styler.GetLine(sc.currentPos);
76 if (sc.state == SCE_AVS_COMMENTBLOCK || sc.state == SCE_AVS_COMMENTBLOCKN) {
77 // Inside a block comment, we set the line state
78 styler.SetLineState(currentLine, blockCommentLevel);
79 } else {
80 // Reset the line state
81 styler.SetLineState(currentLine, 0);
82 }
83 }
84
85 // Determine if the current state should terminate.
86 if (sc.state == SCE_AVS_OPERATOR) {
87 sc.SetState(SCE_AVS_DEFAULT);
88 } else if (sc.state == SCE_AVS_NUMBER) {
89 // We stop the number definition on non-numerical non-dot non-sign char
90 if (!IsANumberChar(sc.ch)) {
91 sc.SetState(SCE_AVS_DEFAULT);
92 }
93 } else if (sc.state == SCE_AVS_IDENTIFIER) {
94 if (!IsAWordChar(sc.ch)) {
95 char s[100];
96 sc.GetCurrentLowered(s, sizeof(s));
97
98 if (keywords.InList(s)) {
99 sc.ChangeState(SCE_AVS_KEYWORD);
100 } else if (filters.InList(s)) {
101 sc.ChangeState(SCE_AVS_FILTER);
102 } else if (plugins.InList(s)) {
103 sc.ChangeState(SCE_AVS_PLUGIN);
104 } else if (functions.InList(s)) {
105 sc.ChangeState(SCE_AVS_FUNCTION);
106 } else if (clipProperties.InList(s)) {
107 sc.ChangeState(SCE_AVS_CLIPPROP);
108 } else if (userDefined.InList(s)) {
109 sc.ChangeState(SCE_AVS_USERDFN);
110 }
111 sc.SetState(SCE_AVS_DEFAULT);
112 }
113 } else if (sc.state == SCE_AVS_COMMENTBLOCK) {
114 if (sc.Match('/', '*')) {
115 blockCommentLevel++;
116 sc.Forward();
117 } else if (sc.Match('*', '/') && blockCommentLevel > 0) {
118 blockCommentLevel--;
119 sc.Forward();
120 if (blockCommentLevel == 0) {
121 sc.ForwardSetState(SCE_AVS_DEFAULT);
122 }
123 }
124 } else if (sc.state == SCE_AVS_COMMENTBLOCKN) {
125 if (sc.Match('[', '*')) {
126 blockCommentLevel++;
127 sc.Forward();
128 } else if (sc.Match('*', ']') && blockCommentLevel > 0) {
129 blockCommentLevel--;
130 sc.Forward();
131 if (blockCommentLevel == 0) {
132 sc.ForwardSetState(SCE_AVS_DEFAULT);
133 }
134 }
135 } else if (sc.state == SCE_AVS_COMMENTLINE) {
136 if (sc.atLineEnd) {
137 sc.ForwardSetState(SCE_AVS_DEFAULT);
138 }
139 } else if (sc.state == SCE_AVS_STRING) {
140 if (sc.ch == '\"') {
141 sc.ForwardSetState(SCE_AVS_DEFAULT);
142 }
143 } else if (sc.state == SCE_AVS_TRIPLESTRING) {
144 if (sc.Match("\"\"\"")) {
145 sc.Forward();
146 sc.Forward();
147 sc.ForwardSetState(SCE_AVS_DEFAULT);
148 }
149 }
150
151 // Determine if a new state should be entered.
152 if (sc.state == SCE_AVS_DEFAULT) {
153 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
154 sc.SetState(SCE_AVS_NUMBER);
155 } else if (IsADigit(sc.ch) || (sc.ch == ',' && IsADigit(sc.chNext))) {
156 sc.Forward();
157 sc.SetState(SCE_AVS_NUMBER);
158 } else if (sc.Match('/', '*')) {
159 blockCommentLevel = 1;
160 sc.SetState(SCE_AVS_COMMENTBLOCK);
161 sc.Forward(); // Eat the * so it isn't used for the end of the comment
162 } else if (sc.Match('[', '*')) {
163 blockCommentLevel = 1;
164 sc.SetState(SCE_AVS_COMMENTBLOCKN);
165 sc.Forward(); // Eat the * so it isn't used for the end of the comment
166 } else if (sc.ch == '#') {
167 sc.SetState(SCE_AVS_COMMENTLINE);
168 } else if (sc.ch == '\"') {
169 if (sc.Match("\"\"\"")) {
170 sc.SetState(SCE_AVS_TRIPLESTRING);
171 } else {
172 sc.SetState(SCE_AVS_STRING);
173 }
174 } else if (isoperator(static_cast<char>(sc.ch))) {
175 sc.SetState(SCE_AVS_OPERATOR);
176 } else if (IsAWordStart(sc.ch)) {
177 sc.SetState(SCE_AVS_IDENTIFIER);
178 }
179 }
180 }
181
182 // End of file: complete any pending changeState
183 if (sc.state == SCE_AVS_IDENTIFIER) {
184 if (!IsAWordChar(sc.ch)) {
185 char s[100];
186 sc.GetCurrentLowered(s, sizeof(s));
187
188 if (keywords.InList(s)) {
189 sc.ChangeState(SCE_AVS_KEYWORD);
190 } else if (filters.InList(s)) {
191 sc.ChangeState(SCE_AVS_FILTER);
192 } else if (plugins.InList(s)) {
193 sc.ChangeState(SCE_AVS_PLUGIN);
194 } else if (functions.InList(s)) {
195 sc.ChangeState(SCE_AVS_FUNCTION);
196 } else if (clipProperties.InList(s)) {
197 sc.ChangeState(SCE_AVS_CLIPPROP);
198 } else if (userDefined.InList(s)) {
199 sc.ChangeState(SCE_AVS_USERDFN);
200 }
201 sc.SetState(SCE_AVS_DEFAULT);
202 }
203 }
204
205 sc.Complete();
206 }
207
FoldAvsDoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * [],Accessor & styler)208 static void FoldAvsDoc(
209 Sci_PositionU startPos,
210 Sci_Position length,
211 int initStyle,
212 WordList *[],
213 Accessor &styler) {
214
215 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
216 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
217 Sci_PositionU endPos = startPos + length;
218 int visibleChars = 0;
219 Sci_Position lineCurrent = styler.GetLine(startPos);
220 int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
221 int levelCurrent = levelPrev;
222 char chNext = styler[startPos];
223 int styleNext = styler.StyleAt(startPos);
224 int style = initStyle;
225
226 for (Sci_PositionU i = startPos; i < endPos; i++) {
227 char ch = chNext;
228 chNext = styler.SafeGetCharAt(i + 1);
229 int stylePrev = style;
230 style = styleNext;
231 styleNext = styler.StyleAt(i + 1);
232 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
233 if (foldComment && style == SCE_AVS_COMMENTBLOCK) {
234 if (stylePrev != SCE_AVS_COMMENTBLOCK) {
235 levelCurrent++;
236 } else if ((styleNext != SCE_AVS_COMMENTBLOCK) && !atEOL) {
237 // Comments don't end at end of line and the next character may be unstyled.
238 levelCurrent--;
239 }
240 }
241
242 if (foldComment && style == SCE_AVS_COMMENTBLOCKN) {
243 if (stylePrev != SCE_AVS_COMMENTBLOCKN) {
244 levelCurrent++;
245 } else if ((styleNext != SCE_AVS_COMMENTBLOCKN) && !atEOL) {
246 // Comments don't end at end of line and the next character may be unstyled.
247 levelCurrent--;
248 }
249 }
250
251 if (style == SCE_AVS_OPERATOR) {
252 if (ch == '{') {
253 levelCurrent++;
254 } else if (ch == '}') {
255 levelCurrent--;
256 }
257 }
258
259 if (atEOL) {
260 int lev = levelPrev;
261 if (visibleChars == 0 && foldCompact)
262 lev |= SC_FOLDLEVELWHITEFLAG;
263 if ((levelCurrent > levelPrev) && (visibleChars > 0))
264 lev |= SC_FOLDLEVELHEADERFLAG;
265 if (lev != styler.LevelAt(lineCurrent)) {
266 styler.SetLevel(lineCurrent, lev);
267 }
268 lineCurrent++;
269 levelPrev = levelCurrent;
270 visibleChars = 0;
271 }
272
273 if (!isspacechar(ch))
274 visibleChars++;
275 }
276 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
277 int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
278 styler.SetLevel(lineCurrent, levelPrev | flagsNext);
279 }
280
281 static const char * const avsWordLists[] = {
282 "Keywords",
283 "Filters",
284 "Plugins",
285 "Functions",
286 "Clip properties",
287 "User defined functions",
288 0,
289 };
290
291 LexerModule lmAVS(SCLEX_AVS, ColouriseAvsDoc, "avs", FoldAvsDoc, avsWordLists);
292