1// Scintilla source code edit control
2/** @file LexSAS.cxx
3 ** Lexer for SAS
4 **/
5// Author: Luke Rasmussen (luke.rasmussen@gmail.com)
6//
7// The License.txt file describes the conditions under which this software may
8// be distributed.
9//
10// Developed as part of the StatTag project at Northwestern University Feinberg
11// School of Medicine with funding from Northwestern University Clinical and
12// Translational Sciences Institute through CTSA grant UL1TR001422. This work
13// has not been reviewed or endorsed by NCATS or the NIH.
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 <string>
23#include <string_view>
24
25#include "ILexer.h"
26#include "Scintilla.h"
27#include "SciLexer.h"
28
29#include "WordList.h"
30#include "LexAccessor.h"
31#include "Accessor.h"
32#include "StyleContext.h"
33#include "CharacterSet.h"
34#include "LexerModule.h"
35
36using namespace Lexilla;
37
38static void ColouriseSASDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[],
39 Accessor &styler) {
40
41 WordList &keywords = *keywordlists[0];
42 WordList &blockKeywords = *keywordlists[1];
43 WordList &functionKeywords = *keywordlists[2];
44 WordList &statements = *keywordlists[3];
45
46 CharacterSet setCouldBePostOp(CharacterSet::setNone, "+-");
47 CharacterSet setMacroStart(CharacterSet::setNone, "%");
48 CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true);
49 CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true);
50
51 StyleContext sc(startPos, length, initStyle, styler);
52 bool lineHasNonCommentChar = false;
53 for (; sc.More(); sc.Forward()) {
54 if (sc.atLineStart) {
55 lineHasNonCommentChar = false;
56 }
57
58 // Determine if the current state should terminate.
59 switch (sc.state) {
60 case SCE_SAS_OPERATOR:
61 sc.SetState(SCE_SAS_DEFAULT);
62 break;
63 case SCE_SAS_NUMBER:
64 // We accept almost anything because of hex. and number suffixes
65 if (!setWord.Contains(sc.ch)) {
66 sc.SetState(SCE_SAS_DEFAULT);
67 }
68 break;
69 case SCE_SAS_MACRO:
70 if (!setWord.Contains(sc.ch) || (sc.ch == '.')) {
71 char s[1000];
72 sc.GetCurrentLowered(s, sizeof(s));
73 if (keywords.InList(s)) {
74 sc.ChangeState(SCE_SAS_MACRO_KEYWORD);
75 }
76 else if (blockKeywords.InList(s)) {
77 sc.ChangeState(SCE_SAS_BLOCK_KEYWORD);
78 }
79 else if (functionKeywords.InList(s)) {
80 sc.ChangeState(SCE_SAS_MACRO_FUNCTION);
81 }
82 sc.SetState(SCE_SAS_DEFAULT);
83 }
84 break;
85 case SCE_SAS_IDENTIFIER:
86 if (!setWord.Contains(sc.ch) || (sc.ch == '.')) {
87 char s[1000];
88 sc.GetCurrentLowered(s, sizeof(s));
89 if (statements.InList(s)) {
90 sc.ChangeState(SCE_SAS_STATEMENT);
91 }
92 else if(blockKeywords.InList(s)) {
93 sc.ChangeState(SCE_SAS_BLOCK_KEYWORD);
94 }
95 sc.SetState(SCE_SAS_DEFAULT);
96 }
97 break;
98 case SCE_SAS_COMMENTBLOCK:
99 if (sc.Match('*', '/')) {
100 sc.Forward();
101 sc.ForwardSetState(SCE_SAS_DEFAULT);
102 }
103 break;
104 case SCE_SAS_COMMENT:
105 case SCE_SAS_COMMENTLINE:
106 if (sc.Match(';')) {
107 sc.Forward();
108 sc.SetState(SCE_SAS_DEFAULT);
109 }
110 break;
111 case SCE_SAS_STRING:
112 if (sc.ch == '\"') {
113 sc.ForwardSetState(SCE_SAS_DEFAULT);
114 }
115 break;
116 }
117
118 // Determine if a new state should be entered.
119 if (sc.state == SCE_SAS_DEFAULT) {
120 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
121 lineHasNonCommentChar = true;
122 sc.SetState(SCE_SAS_NUMBER);
123 }
124 else if (setWordStart.Contains(sc.ch)) {
125 lineHasNonCommentChar = true;
126 sc.SetState(SCE_SAS_IDENTIFIER);
127 }
128 else if (sc.Match('*') && !lineHasNonCommentChar) {
129 sc.SetState(SCE_SAS_COMMENT);
130 }
131 else if (sc.Match('/', '*')) {
132 sc.SetState(SCE_SAS_COMMENTBLOCK);
133 sc.Forward(); // Eat the * so it isn't used for the end of the comment
134 }
135 else if (sc.Match('/', '/')) {
136 sc.SetState(SCE_SAS_COMMENTLINE);
137 }
138 else if (sc.ch == '\"') {
139 lineHasNonCommentChar = true;
140 sc.SetState(SCE_SAS_STRING);
141 }
142 else if (setMacroStart.Contains(sc.ch)) {
143 lineHasNonCommentChar = true;
144 sc.SetState(SCE_SAS_MACRO);
145 }
146 else if (isoperator(static_cast<char>(sc.ch))) {
147 lineHasNonCommentChar = true;
148 sc.SetState(SCE_SAS_OPERATOR);
149 }
150 }
151 }
152
153 sc.Complete();
154}
155
156// Store both the current line's fold level and the next lines in the
157// level store to make it easy to pick up with each increment
158// and to make it possible to fiddle the current level for "} else {".
159static void FoldSASDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[],
160 Accessor &styler) {
161 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
162 bool foldAtElse = styler.GetPropertyInt("fold.at.else", 0) != 0;
163 Sci_PositionU endPos = startPos + length;
164 int visibleChars = 0;
165 Sci_Position lineCurrent = styler.GetLine(startPos);
166 int levelCurrent = SC_FOLDLEVELBASE;
167 if (lineCurrent > 0)
168 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
169 int levelMinCurrent = levelCurrent;
170 int levelNext = levelCurrent;
171 char chNext = styler[startPos];
172 int styleNext = styler.StyleAt(startPos);
173 for (Sci_PositionU i = startPos; i < endPos; i++) {
174 char ch = chNext;
175 chNext = styler.SafeGetCharAt(i + 1);
176 int style = styleNext;
177 styleNext = styler.StyleAt(i + 1);
178 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
179 if (style == SCE_R_OPERATOR) {
180 if (ch == '{') {
181 // Measure the minimum before a '{' to allow
182 // folding on "} else {"
183 if (levelMinCurrent > levelNext) {
184 levelMinCurrent = levelNext;
185 }
186 levelNext++;
187 }
188 else if (ch == '}') {
189 levelNext--;
190 }
191 }
192 if (atEOL) {
193 int levelUse = levelCurrent;
194 if (foldAtElse) {
195 levelUse = levelMinCurrent;
196 }
197 int lev = levelUse | levelNext << 16;
198 if (visibleChars == 0 && foldCompact)
199 lev |= SC_FOLDLEVELWHITEFLAG;
200 if (levelUse < levelNext)
201 lev |= SC_FOLDLEVELHEADERFLAG;
202 if (lev != styler.LevelAt(lineCurrent)) {
203 styler.SetLevel(lineCurrent, lev);
204 }
205 lineCurrent++;
206 levelCurrent = levelNext;
207 levelMinCurrent = levelCurrent;
208 visibleChars = 0;
209 }
210 if (!isspacechar(ch))
211 visibleChars++;
212 }
213}
214
215
216static const char * const SASWordLists[] = {
217 "Language Keywords",
218 "Macro Keywords",
219 "Types",
220 0,
221};
222
223LexerModule lmSAS(SCLEX_SAS, ColouriseSASDoc, "sas", FoldSASDoc, SASWordLists);
224