• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Camelcade / Perl5-IDEA / #525521551

26 May 2025 11:24AM UTC coverage: 82.32% (+0.001%) from 82.319%
#525521551

push

github

hurricup
Migrated to the presentation property

1 of 1 new or added line in 1 file covered. (100.0%)

148 existing lines in 15 files now uncovered.

30897 of 37533 relevant lines covered (82.32%)

0.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.14
/plugin/core/src/main/java/com/perl5/lang/perl/parser/PerlParserUtil.java
1
/*
2
 * Copyright 2015-2025 Alexandr Evstigneev
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.perl5.lang.perl.parser;
18

19
import com.intellij.lang.PsiBuilder;
20
import com.intellij.lang.PsiParser;
21
import com.intellij.lang.WhitespacesAndCommentsBinder;
22
import com.intellij.lang.parser.GeneratedParserUtilBase;
23
import com.intellij.openapi.util.text.StringUtil;
24
import com.intellij.psi.TokenType;
25
import com.intellij.psi.tree.IElementType;
26
import com.intellij.psi.tree.TokenSet;
27
import com.perl5.PerlBundle;
28
import com.perl5.lang.perl.lexer.PerlElementTypes;
29
import com.perl5.lang.perl.lexer.PerlTokenSets;
30
import com.perl5.lang.perl.lexer.PerlTokenSetsEx;
31
import com.perl5.lang.perl.parser.builder.PerlBuilder;
32
import com.perl5.lang.perl.psi.stubs.PerlStubElementTypes;
33
import org.jetbrains.annotations.Contract;
34
import org.jetbrains.annotations.NotNull;
35
import org.jetbrains.annotations.Nullable;
36

37
import java.util.regex.Pattern;
38

39
import static com.intellij.lang.WhitespacesBinders.*;
40

41

42
public final class PerlParserUtil extends GeneratedParserUtilBase implements PerlElementTypes {
43
  private PerlParserUtil() {
44
  }
45
  public static final TokenSet VERSION_TOKENS = TokenSet.create(
1✔
46
    NUMBER,
47
    NUMBER_VERSION
48
  );
49
  private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("[_\\p{L}][_\\p{L}\\d]*");
1✔
50

51
  private static final Pattern VARIABLE_CHECK_PATTERN = Pattern.compile(
1✔
52
    "[$@%]" + IDENTIFIER_PATTERN
53
  );
54
  /**
55
   * something strange in Java with unicode props; Added digits to opener for package Encode::KR::2022_KR;
56
   **/
57
  private static final String BASIC_IDENTIFIER_PATTERN_TEXT = "[_\\p{L}\\d][_\\p{L}\\d]*";
58
  private static final String PACKAGE_SEPARATOR_PATTERN_TEXT = "(?:(?:::)++'?|')";
59
  private static final String OPTIONAL_PACKAGE_SEPARATOR_PATTERN_TEXT = PACKAGE_SEPARATOR_PATTERN_TEXT + "?";
60
  private static final Pattern AMBIGUOUS_PACKAGE_PATTERN = Pattern.compile(
1✔
61
    "(" +
62
    OPTIONAL_PACKAGE_SEPARATOR_PATTERN_TEXT +
63
    "(?:" + BASIC_IDENTIFIER_PATTERN_TEXT + PACKAGE_SEPARATOR_PATTERN_TEXT + ")*+" +
64
    ")" +
65
    "(" + BASIC_IDENTIFIER_PATTERN_TEXT + ")");
66
  public static final WhitespacesAndCommentsBinder PERL_LEADING_COMMENTS_BINDER = leadingCommentsBinder(PerlTokenSetsEx.getREAL_COMMENTS());
1✔
67

68
  /**
69
   * Wrapper for Builder class in order to implement additional per parser information in PerlBuilder
70
   *
71
   * @param root        root element
72
   * @param builder     psibuilder
73
   * @param parser      psiparser
74
   * @param extendsSets extends sets
75
   * @return PerlBuilder
76
   */
77
  public static PsiBuilder adapt_builder_(IElementType root, PsiBuilder builder, PsiParser parser, TokenSet[] extendsSets) {
78
    ErrorState state = new ErrorState();
1✔
79
    ErrorState.initState(state, builder, root, extendsSets);
1✔
80
    return new PerlBuilder(builder, state, parser);
1✔
81
  }
82

83

84
  /**
85
   * Smart parser for ->, makes }->[ optional
86
   *
87
   * @param b PerlBuilder
88
   * @param l parsing level
89
   * @return parsing result
90
   */
91
  public static boolean parseArrowSmart(PsiBuilder b, @SuppressWarnings("unused") int l) {
92
    IElementType tokenType = b.getTokenType();
1✔
93
    if (b.getTokenType() == OPERATOR_DEREFERENCE) {
1✔
94
      return consumeToken(b, OPERATOR_DEREFERENCE);
1✔
95
    }
96
    else {
97
      assert b instanceof PerlBuilder;
1✔
98
      IElementType prevTokenType = ((PerlBuilder)b).lookupToken(-1);
1✔
99

100
      /**
101
       * optional }->[ or ]->{
102
       **/
103
      return (prevTokenType == RIGHT_BRACE || prevTokenType == RIGHT_BRACKET)
1✔
104
             && (tokenType == LEFT_BRACE || tokenType == LEFT_BRACKET || tokenType == LEFT_PAREN);
105
    }
106
  }
107

108
  public static boolean parseExpressionLevel(PsiBuilder b, int l, int g) {
109
    return PerlParserGenerated.expr(b, l, g);
1✔
110
  }
111

112

113
  /**
114
   * Smart semi checker decides if we need semi here
115
   *
116
   * @param b Perl builder
117
   * @param l Parsing level
118
   * @return checking result
119
   */
120
  public static boolean statementSemi(PsiBuilder b, int l) {
121
    return ((PerlBuilder)b).getPerlParser().parseStatementSemi(b, l);
1✔
122
  }
123

124

125
  static boolean isOperatorToken(PsiBuilder b, @SuppressWarnings("unused") int l) {
126
    return PerlTokenSets.OPERATORS_TOKENSET.contains(b.getTokenType());
1✔
127
  }
128

129

130
  /**
131
   * @return true iff next token should be consumed by parser while recovering after statement parsing
132
   */
133
  public static boolean recoverStatement(PsiBuilder b, @SuppressWarnings("unused") int l) {
134
    assert b instanceof PerlBuilder;
1✔
135
    return ((PerlBuilder)b).getPerlParser().getStatementRecoveryConsumableTokenSet().contains(b.getTokenType());
1✔
136
  }
137

138
  /**
139
   * Parses statement modifier and rolls back if it looks like compound
140
   *
141
   * @param b PerlBuilder
142
   * @param l parsing level
143
   * @return check result
144
   */
145
  @SuppressWarnings("UnusedReturnValue")
146
  public static boolean parseStatementModifier(PsiBuilder b, int l) {
147
    assert b instanceof PerlBuilder;
1✔
148
    return ((PerlBuilder)b).getPerlParser().parseStatementModifier(b, l);
1✔
149
  }
150

151
  /**
152
   * Checks for version token and convert if necessary
153
   *
154
   * @param b PerlBuilder
155
   * @param l parsing level
156
   * @return parsing result
157
   */
158
  public static boolean parsePerlVersion(PsiBuilder b, @SuppressWarnings("unused") int l) {
159
    if (VERSION_TOKENS.contains(b.getTokenType())) {
1✔
160
      PsiBuilder.Marker m = b.mark();
1✔
161
      b.advanceLexer();
1✔
162
      m.collapse(VERSION_ELEMENT);
1✔
163
      return true;
1✔
164
    }
165
    return false;
1✔
166
  }
167

168
  /**
169
   * Parses SQ string with optional conversion to the use_vars lp string
170
   *
171
   * @param b PerlBuilder
172
   * @param l parsing level
173
   * @return parsing result
174
   */
175
  public static boolean mapUseVars(PsiBuilder b, int l, Parser parser) {
176
    PsiBuilder.Marker m = b.mark();
1✔
177

178
    boolean r;
179
    r = parser.parse(b, l);
1✔
180

181
    if (r) {
1✔
182
      m.setCustomEdgeTokenBinders(GREEDY_LEFT_BINDER, GREEDY_RIGHT_BINDER);
1✔
183
      m.collapse(PARSABLE_STRING_USE_VARS);
1✔
184
    }
185
    else {
186
      m.drop();
1✔
187
    }
188

189
    return r;
1✔
190
  }
191

192
  public static boolean isUseVars(PsiBuilder b, @SuppressWarnings("unused") int l) {
193
    return ((PerlBuilder)b).isUseVarsContent();
1✔
194
  }
195

196
  /**
197
   * Consuming unexpected token
198
   *
199
   * @param b perlbuilder
200
   * @param l parsing level
201
   * @return true
202
   **/
203
  public static boolean parseBadCharacters(PsiBuilder b, @SuppressWarnings("unused") int l) {
204
    IElementType tokenType = b.getTokenType();
1✔
205

206
    if (tokenType == null || ((PerlBuilder)b).getPerlParser().getBadCharacterForbiddenTokens().contains(tokenType)) {
1✔
207
      return false;
1✔
208
    }
209

210
    PsiBuilder.Marker m = b.mark();
1✔
211
    b.advanceLexer();
1✔
212

213
    if (tokenType == TokenType.BAD_CHARACTER) {
1✔
214
      while (b.getTokenType() == TokenType.BAD_CHARACTER) {
1✔
215
        b.advanceLexer();
1✔
216
      }
217
      m.error(PerlBundle.message("parsing.error.unexpected.token"));
1✔
218
    }
219
    else if (tokenType == RIGHT_PAREN) {
1✔
UNCOV
220
      m.error(PerlBundle.message("parsing.error.unopened.closing.parenthesis"));
×
221
    }
222
    else if (tokenType == RIGHT_BRACKET) {
1✔
UNCOV
223
      m.error(PerlBundle.message("parsing.error.unopened.closing.bracket"));
×
224
    }
225
    else {
226
      m.error(PerlBundle.message("parsing.error.unexpected.token"));
1✔
227
    }
228

229
    return true;
1✔
230
  }
231

232
  /**
233
   * Parses and wraps declaration of scalar variable; NB: special variable names suppressed
234
   *
235
   * @param b Perl builder
236
   * @param l parsing level
237
   * @return check result
238
   */
239
  public static boolean scalarDeclarationWrapper(PsiBuilder b, int l) {
240
    PsiBuilder.Marker m = b.mark();
1✔
241
    boolean r = false;
1✔
242
    assert b instanceof PerlBuilder;
1✔
243
    boolean flagBackup = ((PerlBuilder)b).setSpecialVariableNamesAllowed(false);
1✔
244

245
    if (PerlParserGenerated.scalar_variable(b, l)) {
1✔
246
      m.done(VARIABLE_DECLARATION_ELEMENT);
1✔
247
      r = true;
1✔
248
    }
249
    else {
250
      m.drop();
1✔
251
    }
252
    ((PerlBuilder)b).setSpecialVariableNamesAllowed(flagBackup);
1✔
253
    return r;
1✔
254
  }
255

256
  /**
257
   * attempting to parse statement using parserExtensions
258
   *
259
   * @param b PerlBuilder
260
   * @param l parsing level
261
   * @return parsing result
262
   */
263
  public static boolean parseParserExtensionStatement(PsiBuilder b, int l) {
264
    assert b instanceof PerlBuilder;
1✔
265
    return ((PerlBuilder)b).getPerlParser().parseStatement(b, l);
1✔
266
  }
267

268
  /**
269
   * attempting to parse term using parserExtensions
270
   *
271
   * @param b PerlBuilder
272
   * @param l parsing level
273
   * @return parsing result
274
   */
275
  public static boolean parseParserExtensionTerm(PsiBuilder b, int l) {
276
    assert b instanceof PerlBuilder;
1✔
277
    return ((PerlBuilder)b).getPerlParser().parseTerm(b, l);
1✔
278
  }
279

280
  public static boolean parseFileContent(PsiBuilder b, int l) {
281
    assert b instanceof PerlBuilder;
1✔
282
    return ((PerlBuilder)b).getPerlParser().parseFileContents(b, l);
1✔
283
  }
284

285
  public static boolean checkSemicolon(PsiBuilder b, @SuppressWarnings("unused") int l) {
286
    return ((PerlBuilder)b).getPerlParser().getConsumableSemicolonTokens().contains(b.getTokenType());
1✔
287
  }
288

289
  public static boolean parseSemicolon(PsiBuilder b, int l) {
290
    if (checkSemicolon(b, l)) {
1✔
291
      b.advanceLexer();
1✔
292
      return true;
1✔
293
    }
294
    return false;
1✔
295
  }
296

297
  public static boolean parseNamespaceContent(PsiBuilder b, int l) {
298
    PsiBuilder.Marker m = b.mark();
1✔
299
    if (PerlParserGenerated.real_namespace_content(b, l)) {
1✔
300
      m.done(NAMESPACE_CONTENT);
1✔
301
      m.setCustomEdgeTokenBinders(GREEDY_LEFT_BINDER, PERL_LEADING_COMMENTS_BINDER);
1✔
302
      return true;
1✔
303
    }
304
    m.rollbackTo();
×
UNCOV
305
    return false;
×
306
  }
307

308
  public static boolean parseLabelDeclaration(PsiBuilder b, @SuppressWarnings("unused") int l) {
309
    if (b.lookAhead(1) == COLON && b.getTokenType() != RESERVED_SUB) {
1✔
310
      String tokenText = b.getTokenText();
1✔
311
      if (tokenText != null && isIdentifier(tokenText)) {
1✔
312
        b.advanceLexer();
1✔
313
        b.advanceLexer();
1✔
314
        return true;
1✔
315
      }
316
    }
317
    return false;
1✔
318
  }
319

320
  /**
321
   * Parses leftward or rightward call with custom sub token
322
   *
323
   * @param b builder
324
   * @param l level
325
   * @return result
326
   */
327
  public static boolean parseCustomCallExpr(PsiBuilder b, int l, Parser tokenParser) {
328
    PsiBuilder.Marker m = b.mark();
1✔
329
    if (parseCustomMethod(b, l, tokenParser) && PerlParserGenerated.any_call_arguments(b, l)) {
1✔
330
      m.done(SUB_CALL);
1✔
331
      return true;
1✔
332
    }
333
    m.rollbackTo();
1✔
334
    return false;
1✔
335
  }
336

337
  public static boolean parseCustomMethod(PsiBuilder b, int l, Parser methodParser) {
338
    PsiBuilder.Marker marker = b.mark();
1✔
339
    if (methodParser.parse(b, l)) {
1✔
340
      marker.done(METHOD);
1✔
341
      return true;
1✔
342
    }
343
    marker.rollbackTo();
1✔
344
    return false;
1✔
345
  }
346

347
  /**
348
   * Parses use parameters with package processor if it's possible. If not, uses default parsing logic.
349
   */
350
  public static boolean parseUseParameters(@NotNull PsiBuilder b, int l, @NotNull Parser defaultParser) {
351
    if (b.getTokenType() == PACKAGE) {
1✔
352
      String packageName = b.getTokenText();
1✔
353
      if (StringUtil.isEmpty(packageName)) {
1✔
UNCOV
354
        return false;
×
355
      }
356
      if ("vars".equals(packageName)) {
1✔
357
        assert b instanceof PerlBuilder;
1✔
358
        PerlParserUtil.passPackageAndVersion((PerlBuilder)b, l);
1✔
359
        ((PerlBuilder)b).setUseVarsContent(true);
1✔
360
        PerlParserGenerated.expr(b, l, -1);
1✔
361
        ((PerlBuilder)b).setUseVarsContent(false);
1✔
362
        return true;
1✔
363
      }
364
    }
365
    return defaultParser.parse(b, l);
1✔
366
  }
367

368
  /**
369
   * Helper method to pass package [version] in use statement
370
   */
371
  @SuppressWarnings("UnusedReturnValue")
372
  public static boolean passPackageAndVersion(@NotNull PerlBuilder b, int l) {
373
    assert GeneratedParserUtilBase.consumeTokenFast(b, PACKAGE);
1✔
374
    PerlParserGenerated.perl_version(b, l);
1✔
375
    return true;
1✔
376
  }
377

378
  public static boolean parseUse(@NotNull PsiBuilder b, int l) {
379
    if (b.getTokenType() != RESERVED_USE) {
1✔
380
      return false;
1✔
381
    }
382
    PsiBuilder.Marker mark = b.mark();
1✔
383
    if (PerlParserGenerated.parse_use_statement(b, l)) {
1✔
384
      mark.done(PerlStubElementTypes.USE_STATEMENT);
1✔
385
      return true;
1✔
386
    }
387
    mark.rollbackTo();
×
UNCOV
388
    return false;
×
389
  }
390

391
  public static boolean parseNo(@NotNull PsiBuilder b, int l) {
392
    if (b.getTokenType() != RESERVED_NO) {
1✔
393
      return false;
1✔
394
    }
395
    PsiBuilder.Marker mark = b.mark();
1✔
396
    if (PerlParserGenerated.parse_no_statement(b, l)) {
1✔
397
      mark.done(PerlStubElementTypes.NO_STATEMENT);
1✔
398
      return true;
1✔
399
    }
400
    mark.rollbackTo();
×
UNCOV
401
    return false;
×
402
  }
403

404
  public static boolean parseBareString(@NotNull PsiBuilder b, int ignored) {
405
    IElementType type = b.getTokenType();
1✔
406
    if (type != STRING_CONTENT && type != STRING_SPECIAL_ESCAPE_CHAR) {
1✔
UNCOV
407
      return false;
×
408
    }
409
    while (true) {
410
      IElementType nextRawTokenType = b.rawLookup(1);
1✔
411
      boolean shouldContinue = nextRawTokenType == STRING_CONTENT || nextRawTokenType == STRING_SPECIAL_ESCAPE_CHAR;
1✔
412
      b.advanceLexer();
1✔
413
      if (!shouldContinue) {
1✔
414
        return true;
1✔
415
      }
416
    }
1✔
417
  }
418

419
  /**
420
   * @return true iff {@code text} is valid perl identifier
421
   */
422
  @Contract(value = "null -> false", pure = true)
423
  public static boolean isIdentifier(@Nullable String text) {
424
    return StringUtil.isNotEmpty(text) && IDENTIFIER_PATTERN.matcher(text).matches();
1✔
425
  }
426

427
  /**
428
   * @return true iff {@code text} looks like an ambiguous FQN, namespace name or method name, e.g. Foo::Bar
429
   */
430
  @Contract(value = "null -> false", pure = true)
431
  public static boolean isAmbiguousPackage(@Nullable String text) {
432
    return StringUtil.isNotEmpty(text) && AMBIGUOUS_PACKAGE_PATTERN.matcher(text).matches();
1✔
433
  }
434

435
  /**
436
   * @return true iff {@code value} looks like a scalar variable without braces, with optional namespace
437
   */
438
  @Contract(value = "null -> false", pure = true)
439
  public static boolean isVariableWithSigil(@Nullable String text) {
UNCOV
440
    return StringUtil.isNotEmpty(text) && VARIABLE_CHECK_PATTERN.matcher(text).matches();
×
441
  }
442
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc