I need to create a regular expression able to find and get valid identifiers in Java code like this:
int a, b, c;
float d, e;
a = b = 5;
c = 6;
if ( a > b)
{
c = a - b;
e = d - 2.0;
}
else
{
d = e + 6.0;
b = a + c;
}
I have tried to add multiple regexes in a single regex, but how can I build a pattern to exclude reserved words?
I tried this regex ^(((&&|<=|>=|<|>|!=|==|&|!)|([-+=]{1,2})|([.!?)}{;,(-]))|(else|if|float|int)|(d[d.]))
but it does not work as expected.
In the following picture, how should I match for identifiers?
Advertisement
Answer
A Java valid identifier is:
- having at least one character
- the first character MUST be a letter
[a-zA-Z]
, underscore_
, or dollar sign$
- the rest of the characters MAY be letters, digits, underscores, or dollar signs
- reserved words MUST not be used as identifiers
- Update: as single underscore
_
is a keyword since Java 9
A naive regexp to validate the first three conditions would be as follows: (b([A-Za-z_$][$w]*)b)
but it does not filter out the reserved words.
To exclude the reserved words, negative look-ahead (?!)
is needed to specify a group of tokens that cannot match:
b(?!(_b|if|else|for|float|int))([A-Za-z_$][$w]*)
:
- Group #1:
(?!(_b|if|else|for|float|int))
excludes the list of the specified words - Group #2:
([A-Za-z_$][$w]*)
matches identifiers.
However, word border b
consumes dollar sign $
, so this regular expression fails to match identifies starting with $
.
Also, we may want to exclude matching inside string and character literals (“not_a_variable”, ‘c’, ‘u65’).
This can be done using positive lookbehind (?<=)
to match a group before main expression without including it in the result instead of the word-border class b
:
(?<=[^$w'"\])(?!(_b|if|else|for|float|int))([A-Za-z_$][$w]*)
Online demo for a short list of reserved words
Next, the full list of the Java reserved words is as follows, which can be collected into a single String of tokens separated with |
.
A test class showing the final pattern for regular expression and its usage to detect the Java identifiers is provided below.
import java.util.Arrays;
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
public class IdFinder {
static final List<String> RESERVED = Arrays.asList(
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const",
"continue", "default", "double", "do", "else", "enum", "extends", "false", "final", "finally",
"float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long",
"native", "new", "null", "package", "private", "protected", "public", "return", "short", "static",
"strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try",
"void", "volatile", "while", "_\b"
);
static final String JAVA_KEYWORDS = String.join("|", RESERVED);
static final Pattern VALID_IDENTIFIERS = Pattern.compile(
"(?<=[^$\w'"\\])(?!(" + JAVA_KEYWORDS + "))([A-Za-z_$][$\w]*)");
public static void main(String[] args) {
System.out.println("ID pattern:n" + VALID_IDENTIFIERS.pattern());
String code = "public class Main {ntstatic int $1;ntprotected char _c0 = '\u65';ntprivate long c1__$$;n}";
System.out.println("nIdentifiers in the following code:n=====n" + code + "n=====");
VALID_IDENTIFIERS.matcher(code).results()
.map(MatchResult::group)
.forEach(System.out::println);
}
}
Output
ID pattern:
(?<=[^$w'"\])(?!(abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|double|do|else|enum|extends|false|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|transient|true|try|void|volatile|while|_b))([A-Za-z_$][$w]*)
Identifiers in the following code:
=====
public class Main {
static int $1;
protected char _c0 = 'u65';
private long c1__$$;
}
=====
Main
$1
_c0
c1__$$