xref: /OpenGrok/opengrok-indexer/src/main/jflex/analysis/powershell/PowershellXref.lex (revision d219b4cea555a12b602d2d5518daa22134ad4879)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
22  * Portions Copyright (c) 2017-2018, Chris Fraire <cfraire@me.com>.
23  */
24 
25 package org.opengrok.indexer.analysis.powershell;
26 
27 import java.io.IOException;
28 import java.util.Locale;
29 import java.util.Stack;
30 import java.util.regex.Matcher;
31 import org.opengrok.indexer.analysis.JFlexSymbolMatcher;
32 import org.opengrok.indexer.analysis.ScopeAction;
33 import org.opengrok.indexer.web.HtmlConsts;
34 %%
35 %public
36 %class PoshXref
37 %extends JFlexSymbolMatcher
38 %unicode
39 %ignorecase
40 %int
41 %char
42 %include ../CommonLexer.lexh
43 %include ../CommonXref.lexh
44 %{
45   private final Stack<String> styleStack = new Stack<String>();
46 
47   @Override
clearStack()48   protected void clearStack() {
49       super.clearStack();
50       styleStack.clear();
51   }
52 
emitComplexVariable()53   private void emitComplexVariable() throws IOException {
54     String id = yytext().substring(2, yylength() - 1);
55     onNonSymbolMatched("${", yychar);
56     onFilteredSymbolMatched(id, yychar, Consts.poshkwd, false);
57     onNonSymbolMatched("}", yychar);
58   }
59 
emitSimpleVariable()60   private void emitSimpleVariable() throws IOException {
61     String id = yytext().substring(1);
62     onNonSymbolMatched("$", yychar);
63     onFilteredSymbolMatched(id, yychar, Consts.poshkwd, false);
64   }
65 
pushSpan(int newState,String className)66   public void pushSpan(int newState, String className) throws IOException {
67       onDisjointSpanChanged(className, yychar);
68       yypush(newState);
69       styleStack.push(className);
70   }
71 
72   @Override
yypop()73   public void yypop() throws IOException {
74       onDisjointSpanChanged(null, yychar);
75       super.yypop();
76       styleStack.pop();
77 
78       if (!styleStack.empty()) {
79           String style = styleStack.peek();
80           onDisjointSpanChanged(style, yychar);
81       }
82   }
83 
chkLOC()84   protected void chkLOC() {
85       switch (yystate()) {
86           case COMMENT:
87           case SCOMMENT:
88               break;
89           default:
90               phLOC();
91               break;
92       }
93   }
94 %}
95 
96 File = {FNameChar}+ "." ([a-zA-Z0-9]+)
97 
98 /*
99  * Differs from {FPath} in that the path segments are only constrained to be
100  * {FNameChar}.
101  */
102 AnyFPath = "/"? {FNameChar}+ ("/" {FNameChar}+)+
103 
104 /*
105  * States:
106  * STRING   - double-quoted string, ex: "hello, world!"
107  * QSTRING  - single-quoted string, ex: 'hello, world!'
108  * COMMENT - multiple-line comment.
109  * SCOMMENT - single-line comment, ex: # this is a comment
110  * SUBSHELL - commands executed in a sub-shell,
111  *               example 1: (echo $header; cat file.txt)
112  * HERESTRING  - here-string, example: cat @" ... "@
113  * HEREQSTRING - here-string, example: cat @' ... '@
114  * DATATYPE - bracketed .NET datatype specification
115  * DOTSYNTAX - await possible dot syntax -- e.g. property or methods
116  */
117 %state STRING COMMENT SCOMMENT QSTRING SUBSHELL HERESTRING HEREQSTRING
118 %state DATATYPE DOTSYNTAX
119 
120 %include ../Common.lexh
121 %include ../CommonURI.lexh
122 %include ../CommonPath.lexh
123 %include Powershell.lexh
124 %%
125 
126 <STRING>{
127  {ComplexVariable}    {
128     chkLOC();
129     emitComplexVariable();
130  }
131  {SimpleVariable}    {
132     chkLOC();
133     emitSimpleVariable();
134  }
135 }
136 
137 <YYINITIAL>{
138  \{     { chkLOC(); onScopeChanged(ScopeAction.INC, yytext(), yychar); }
139  \}     { chkLOC(); onScopeChanged(ScopeAction.DEC, yytext(), yychar); }
140  \;     { chkLOC(); onScopeChanged(ScopeAction.END, yytext(), yychar); }
141 }
142 
143 <YYINITIAL, SUBSHELL> {
144  ^ {Label}    {
145     chkLOC();
146     String capture = yytext();
147     onLabelMatched(capture, yychar, capture.substring(1));
148  }
149  {Break} |
150  {Continue}    {
151     chkLOC();
152     String capture = yytext();
153     Matcher m = PoshUtils.GOTO_LABEL.matcher(capture);
154     if (!m.find()) {
155         onNonSymbolMatched(capture, yychar);
156     } else {
157         String control = m.group(1);
158         String space   = m.group(2);
159         String label   = m.group(3);
160         onFilteredSymbolMatched(control, yychar, Consts.poshkwd, false);
161         onNonSymbolMatched(space, yychar);
162         onLabelDefMatched(label, yychar);
163     }
164  }
165 
166  {DataType}    {
167     chkLOC();
168     yypushback(yylength());
169     pushSpan(DATATYPE, null);
170  }
171 }
172 
173 <YYINITIAL, SUBSHELL, DOTSYNTAX> {
174  {ComplexVariable}    {
175     chkLOC();
176     emitComplexVariable();
177     if (yystate() != DOTSYNTAX) pushSpan(DOTSYNTAX, null);
178  }
179  {SimpleVariable}    {
180     chkLOC();
181     emitSimpleVariable();
182     if (yystate() != DOTSYNTAX) pushSpan(DOTSYNTAX, null);
183  }
184 }
185 
186 <YYINITIAL, SUBSHELL> {
187  {Operator}    {
188     chkLOC();
189     String capture = yytext();
190     if (Consts.poshkwd.contains(capture.toLowerCase(Locale.ROOT))) {
191         onKeywordMatched(capture, yychar);
192     } else {
193         String sigil = capture.substring(0, 1);
194         String id = capture.substring(1);
195         onNonSymbolMatched(sigil, yychar);
196         onFilteredSymbolMatched(id, yychar, Consts.poshkwd, false);
197     }
198  }
199 
200  {Number}    {
201     chkLOC();
202     String lastClassName = getDisjointSpanClassName();
203     onDisjointSpanChanged(HtmlConsts.NUMBER_CLASS, yychar);
204     onNonSymbolMatched(yytext(), yychar);
205     onDisjointSpanChanged(lastClassName, yychar);
206  }
207 
208  \"    {
209     chkLOC();
210     pushSpan(STRING, HtmlConsts.STRING_CLASS);
211     onNonSymbolMatched(yytext(), yychar);
212  }
213  \'    {
214     chkLOC();
215     pushSpan(QSTRING, HtmlConsts.STRING_CLASS);
216     onNonSymbolMatched(yytext(), yychar);
217  }
218 
219  \#    {
220     pushSpan(SCOMMENT, HtmlConsts.COMMENT_CLASS);
221     onNonSymbolMatched(yytext(), yychar);
222  }
223  \<\#    {
224     pushSpan(COMMENT, HtmlConsts.COMMENT_CLASS);
225     onNonSymbolMatched(yytext(), yychar);
226  }
227 
228  \@\"    {
229     chkLOC();
230     pushSpan(HERESTRING, HtmlConsts.STRING_CLASS);
231     onNonSymbolMatched(yytext(), yychar);
232  }
233  \@\'    {
234     chkLOC();
235     pushSpan(HEREQSTRING, HtmlConsts.STRING_CLASS);
236     onNonSymbolMatched(yytext(), yychar);
237  }
238 }
239 
240 <DOTSYNTAX> {
241  "."    {
242     chkLOC();
243     onNonSymbolMatched(yytext(), yychar);
244  }
245 
246  [^]    {
247     yypushback(yylength());
248     yypop();
249  }
250 }
251 
252 <YYINITIAL, SUBSHELL, DATATYPE, DOTSYNTAX> {
253  {Identifier}    {
254     chkLOC();
255     String id = yytext();
256     onFilteredSymbolMatched(id, yychar, Consts.poshkwd, false);
257  }
258 }
259 
260 <DATATYPE> {
261  "]"    {
262     chkLOC();
263     yypushback(yylength());
264     yypop();
265  }
266 }
267 
268 <STRING> {
269  [`][\"\$`] |
270  \"\"    { chkLOC(); onNonSymbolMatched(yytext(), yychar); }
271 
272  \$? \"    {
273     chkLOC();
274     onNonSymbolMatched(yytext(), yychar);
275     yypop();
276  }
277 }
278 
279 <STRING, HERESTRING> {
280  "$("    {
281     chkLOC();
282     pushSpan(SUBSHELL, null);
283     onNonSymbolMatched(yytext(), yychar);
284  }
285 }
286 
287 <QSTRING> {
288  \'\'    { chkLOC(); onNonSymbolMatched(yytext(), yychar); }
289  \'    {
290     chkLOC();
291     onNonSymbolMatched(yytext(), yychar);
292     yypop();
293  }
294 }
295 
296 <COMMENT> {
297  \#\>    {
298     onNonSymbolMatched(yytext(), yychar);
299     yypop();
300  }
301 }
302 
303 <SCOMMENT> {
304  {WhspChar}*{EOL}    {
305     yypop();
306     onEndOfLineMatched(yytext(), yychar);
307  }
308 }
309 
310 <SUBSHELL> {
311   \)    {
312     chkLOC();
313     onNonSymbolMatched(yytext(), yychar);
314     yypop();
315   }
316 }
317 
318 <HERESTRING> {
319   // Match escaped dollar sign of variable
320   // (eg. `$var) so it does not turn into web-link.
321   "`$"    { chkLOC(); onNonSymbolMatched(yytext(), yychar); }
322 
323   {SimpleVariable}    {
324      chkLOC();
325      emitSimpleVariable();
326   }
327 
328   {ComplexVariable}    {
329      chkLOC();
330      emitComplexVariable();
331   }
332   ^ \"\@    {
333     chkLOC();
334     onNonSymbolMatched(yytext(), yychar);
335     yypop();
336   }
337 }
338 
339 <HEREQSTRING> {
340   ^ "'@"    {
341     chkLOC();
342     onNonSymbolMatched(yytext(), yychar);
343     yypop();
344   }
345 }
346 
347 <YYINITIAL, SUBSHELL> {
348   /* Don't enter new state if special character is escaped. */
349   [`][`\(\)\{\}\"\'\$\#\\]    {
350     chkLOC();
351     onNonSymbolMatched(yytext(), yychar);
352   }
353 
354   /* $# should not start a comment. */
355   "$#"    { chkLOC(); onNonSymbolMatched(yytext(), yychar); }
356 
357   \$ ? \(    {
358     chkLOC();
359     pushSpan(SUBSHELL, null);
360     onNonSymbolMatched(yytext(), yychar);
361   }
362 }
363 
364 <YYINITIAL, SUBSHELL, STRING, SCOMMENT, QSTRING> {
365     {File} {
366         chkLOC();
367         String path = yytext();
368         onFilelikeMatched(path, yychar);
369     }
370 
371     {AnyFPath}    {
372         chkLOC();
373         onPathlikeMatched(yytext(), '/', false, yychar);
374     }
375 }
376 
377 <YYINITIAL, DATATYPE, SUBSHELL, STRING, COMMENT, SCOMMENT, QSTRING, HERESTRING,
378     HEREQSTRING> {
379 
380     {WhspChar}*{EOL}    { onEndOfLineMatched(yytext(), yychar); }
381     [[\s]--[\n]]    { onNonSymbolMatched(yytext(), yychar); }
382     [^\n]    { chkLOC(); onNonSymbolMatched(yytext(), yychar); }
383 }
384 
385 <STRING, SCOMMENT, QSTRING> {
386 {FNameChar}+ "@" {FNameChar}+ "." {FNameChar}+
387         {
388           chkLOC();
389           onEmailAddressMatched(yytext(), yychar);
390         }
391 }
392 
393 <STRING, SCOMMENT> {
394     {BrowseableURI}    {
395         chkLOC();
396         onUriMatched(yytext(), yychar);
397     }
398 }
399 
400 <QSTRING> {
401     {BrowseableURI}    {
402         chkLOC();
403         onUriMatched(yytext(), yychar, PoshUtils.STRINGLITERAL_APOS_DELIMITER);
404     }
405 }
406 
407 <COMMENT> {
408     {BrowseableURI} \>?    {
409         onUriMatched(yytext(), yychar, PoshUtils.MAYBE_END_MULTILINE_COMMENT);
410     }
411 }
412