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