de.unkrig.commons.text.expression.Parser Maven / Gradle / Ivy
/*
* de.unkrig.commons - A general-purpose Java class library
*
* Copyright (c) 2011, Arno Unkrig
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.unkrig.commons.text.expression;
import static de.unkrig.commons.text.expression.Scanner.TokenType.CHARACTER_LITERAL;
import static de.unkrig.commons.text.expression.Scanner.TokenType.FLOATING_POINT_LITERAL;
import static de.unkrig.commons.text.expression.Scanner.TokenType.IDENTIFIER;
import static de.unkrig.commons.text.expression.Scanner.TokenType.INTEGER_LITERAL;
import static de.unkrig.commons.text.expression.Scanner.TokenType.STRING_LITERAL;
import java.io.Reader;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.regex.Pattern;
import de.unkrig.commons.lang.ExceptionUtil;
import de.unkrig.commons.lang.protocol.ProducerWhichThrows;
import de.unkrig.commons.nullanalysis.NotNull;
import de.unkrig.commons.nullanalysis.Nullable;
import de.unkrig.commons.text.expression.Scanner.TokenType;
import de.unkrig.commons.text.parser.AbstractParser;
import de.unkrig.commons.text.parser.ParseException;
import de.unkrig.commons.text.pattern.Glob;
import de.unkrig.commons.text.scanner.AbstractScanner.Token;
import de.unkrig.commons.text.scanner.ScanException;
import de.unkrig.commons.text.scanner.ScannerUtil;
/**
* Parses an expression like
*
* s == "abc" && (c == 'b' || !b == true)
*
*
* Supported operators (in ascending priority) are:
*
* - {@code a ? b : c}
* -
* {@code b} if {@code a}, {@link ExpressionEvaluator#toBoolean(Object) converted to boolean}, is true, otherwise
* {@code c}.
*
*
* - {@code a || b}
* -
* {@code a} if {@code a}, {@link ExpressionEvaluator#toBoolean(Object) converted to boolean}, is true, otherwise
* {@code b}. (Equivalent to "{@code a ? a : b}", except that {@code a} is evaluated only once.)
*
*
* - {@code a && b}
* -
* {@code b} if {@code a}, {@link ExpressionEvaluator#toBoolean(Object) converted to boolean}, is true, otherwise
* {@code false}. (Equivalent to "{@code a ? b : false}".)
*
*
* - {@code == != < <= > >=}
* -
* Arithmetic comparison (if both operands are numeric) or lexicographic comparison (otherwise).
*
*
* -
* {@code a =* "*.c"}
*
* {@code a =* "(*).c=$1.h"}
*
* -
* Wildcard matching/replacing (see {@link Glob}).
* The left-hand-side operand is the subject, the right-hand-side operand is the glob.
* Iff the right-hand-side operand contains an equal sign ("{@code =}"), as in the second example, then it
* specifies a glob and the replacement.
*
* Evaluates
*
* - to {@code null} iff the subject does not match the glob, otherwise,
* - to the replacement, iff one is specified, otherwise,
* - the subject
*
* The "{@code =*}" operator is only available if the {@link Parser.Extension#OPERATOR_GLOB} parsing extension is
* enabled (which is the default).
*
*
* -
* {@code a =~ "A.*"}
*
* {@code a =~ "(.*)abc(.*)=$1$2"}
*
* -
* Regex matching/replacing (see {@link Pattern}).
* The left-hand-side operand is the subject, the right-hand-side operand is the pattern.
* Iff the right-hand-side operand contains an equal sign ("{@code =}"), as in the second example, then it
* specifies a pattern and the replacement.
*
* Evaluates
*
* - to {@code null} iff the subject does not match the pattern, otherwise,
* - to the replacement, iff one is specified, otherwise,
* - the subject
*
* The "{@code =~}" operator is only available if the {@link Parser.Extension#OPERATOR_REGEX} parsing extension is
* enabled (which is the default).
*
*
* - {@code + -}
* -
* Addition (if both operands are numeric) or string concatenation; subtraction.
*
*
* - {@code * / %}
* -
* Multiplication; division; remainder (modulo).
*
*
* - {@code a instanceof reference-type}
* - Whether {@code a} is non-null and a subtype of {@code reference-type}
*
* - {@code (...) ! -}
* -
* Grouping; logical negation; arithmetic negation.
*
*
*
* Primaries are:
*
*
* - {@code "abc"}
* -
* String literal
*
*
* - {@code 123}
* -
* Integer literal
*
*
* - {@code 123L 123l}
* -
* Long literal
*
*
* - {@code 1F 1.2E+3f}
* -
* Float literal
*
*
* - {@code 1D 1d 1. 1.1 1e-3}
* -
* Double literal
*
*
* - {@code true false null}
* -
* Special constant values
*
*
* - {@code abc}
* -
* Variable reference, see {@link Expression#evaluate(de.unkrig.commons.lang.protocol.Mapping)}
*
*
* -
* {@code new pkg.Clazz(arg1, arg2)}
*
* {@code new RootPackageClazz(arg1, arg2)}
*
* {@code new ImportedClazz(arg1, arg2)}
*
* -
* Class instance creation
*
*
* -
* {@code new pkg.Clazz} (Only with {@link Parser.Extension#NEW_CLASS_WITHOUT_PARENTHESES})
*
* {@code pkg.Clazz()} (Only with {@link Parser.Extension#NEW_CLASS_WITHOUT_KEYWORD})
*
* {@code pkg.Clazz} (Only with {@link Parser.Extension#NEW_CLASS_WITHOUT_KEYWORD} and {@link
* Parser.Extension#NEW_CLASS_WITHOUT_PARENTHESES})
*
* -
* Abbreviated forms of "{@code new pkg.Clazz()}"
*
*
* -
* {@code new pkg.Clazz[x]}
*
* {@code new pkg.Clazz[x][y][][]}
*
* {@code new int[x]}
*
* {@code new int[x][y][][]}
*
* -
* Array creation (notice that there is no abbreviated notation, without the {@code new} keyword, as for class
* instance creation)
*
*
* -
* {@code pkg.Clazz[x]}
*
* {@code pkg.Clazz[x][y][][]}
*
* {@code int[x]}
*
* {@code int[x][y][][]}
*
* -
* Abbreviated array creation
*
*
* - {@code x[y]}
* -
* Array element access
*
*
* - {@code x.name}
* -
* Attribute reference; gets the value of the field "{@code x.name}", or invokes "{@code x.name()}" or "{@code
* x.getName()}" (it is not possible to set an attribute this way)
*
*
* - {@code x.meth(a, b, c)}
* -
* Method invocation
*
*
*
* {@code Null} operands are handled leniently, e.g. "7 == null" is false, "null == null" is true.
*
*
*
* To implement your parser, derive from this class and implement the abstract methods.
*
*
* @param The type returned by {@link #parse()}
* @param The exception thrown by the abstract handlers (e.g. {@link #conditional(Object, Object, Object)})
* @see #enableExtension(Extension)
* @see #disableExtension(Extension)
*/
public abstract
class Parser extends AbstractParser {
public
Parser(ProducerWhichThrows extends Token, ? extends ScanException> tokenProducer) {
super(tokenProducer);
}
public
Parser(Reader in) { this(ScannerUtil.toDocumentScanner(Scanner.stringScanner(), in)); }
public
Parser(String expression) { this(Scanner.stringScanner().setInput(expression)); }
/**
* @return The currently configured single imports (fully qualified class names)
*/
public String[]
getSingleImports() { return this.singleImports.toArray(new String[this.singleImports.size()]); }
/**
* @param singleImports Fully qualified class or interface names
*/
public Parser
addSingleImports(String... singleImports) {
for (String si : singleImports) this.singleImports.add(si);
return this;
}
/**
* @return The currently configured on-demand imports (fully qualified package names)
*/
public String[]
getOnDemandImports() { return this.onDemandImports.toArray(new String[this.onDemandImports.size()]); }
/**
* By default, there is only one on-demand import: "java.lang".
*
* @param onDemandImports Fully qualified package names
*/
public Parser
addOnDemandImports(String... onDemandImports) {
for (String si : onDemandImports) this.onDemandImports.add(si);
return this;
}
/**
* @return The currently configured {@link ClassLoader}
*/
public ClassLoader
getClassLoader() { return this.classLoader; }
/**
* @param classLoader Used to load classes named in the expression; by default the class loader which loaded
* the {@link Parser} class.
*/
public Parser
setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
return this;
}
/**
* Sets the given parsing extensions. By default, all extensions are enabled.
*
* @see Parser.Extension
*/
public void
setExtensions(Collection extensions) {
this.extensions = extensions.isEmpty() ? EnumSet.noneOf(Extension.class) : EnumSet.copyOf(extensions);
}
/**
* Sets the given parsing extensions. By default, all extensions are enabled.
*
* @see Parser.Extension
*/
public void
setExtensions(EnumSet extensions) { this.extensions = EnumSet.copyOf(extensions); }
/**
* Enables the given parsing extension. By default, all extensions are enabled.
*
* @see Parser.Extension
*/
public void
enableExtension(Extension extension) { this.extensions.add(extension); }
/**
* Disables the given parsing extension. By default, all extensions are enabled.
*
* @see Parser.Extension
*/
public void
disableExtension(Extension extension) { this.extensions.remove(extension); }
/**
* @return The parsed expression
*/
public T
parse() throws ParseException, EX {
try {
this.parseImports();
T result = this.parseExpression().toValue();
this.eoi();
return result;
} catch (ParseException pe) {
throw ExceptionUtil.wrap("At " + this.scanner.toString(), pe);
} catch (RuntimeException re) {
throw ExceptionUtil.wrap("At " + this.scanner.toString(), re);
} catch (Exception e) {
@SuppressWarnings("unchecked") EX ee = (EX) e;
throw ExceptionUtil.wrap("At " + this.scanner.toString(), ee);
}
}
/**
* Parses exactly one expression and leaves the following tokens in the input untouched.
*
* @return The parsed expression
*/
public T
parsePart() throws ParseException, EX {
try {
this.parseImports();
return this.parseExpression().toValue();
} catch (ParseException pe) {
throw ExceptionUtil.wrap("At " + this.scanner.toString(), pe);
} catch (RuntimeException re) {
throw ExceptionUtil.wrap("At " + this.scanner.toString(), re);
} catch (Exception e) {
@SuppressWarnings("unchecked") EX ee = (EX) e;
throw ExceptionUtil.wrap("At " + this.scanner.toString(), ee);
}
}
/**
* Representation of all unary operators.
*/
public
enum UnaryOperator {
MINUS("-"), // SUPPRESS CHECKSTYLE JavadocVariable:2
LOGICAL_COMPLEMENT("!"),
BITWISE_COMPLEMENT("~");
private final String text;
UnaryOperator(String text) { this.text = text; }
@Override public String
toString() { return this.text; }
}
/**
* Representation of all binary operators.
*/
public
enum BinaryOperator {
EQUAL("=="), // SUPPRESS CHECKSTYLE JavadocVariable:20
GLOB("=*"),
REGEX("=~"),
NOT_EQUAL("!="),
LESS("<"),
LESS_EQUAL("<="),
GREATER(">"),
GREATER_EQUAL(">="),
PLUS("+"),
MINUS("-"),
MULTIPLY("*"),
DIVIDE("/"),
MODULO("%"),
LOGICAL_OR("||"),
LOGICAL_AND("&&"),
BITWISE_OR("|"),
BITWISE_XOR("^"),
BITWISE_AND("&"),
LEFT_SHIFT("<<"),
RIGHT_SHIFT(">>"),
RIGHT_USHIFT(">>>"),
;
private final String text;
BinaryOperator(String text) { this.text = text; }
@Override public String
toString() { return this.text; }
}
/**
* Various extensions to the Java expression syntax.
*
* @see Parser#enableExtension(Extension)
* @see Parser#disableExtension(Extension)
*/
public
enum Extension {
/**
* The {@code new} keyword can be left out when a class object is instantiated. Notice that the {@code new}
* can never be left out when creating arrays.
*/
NEW_CLASS_WITHOUT_KEYWORD,
/**
* The "()" can be omitted when instantiating a class object with its zero-arg constructor.
*/
NEW_CLASS_WITHOUT_PARENTHESES,
/**
* The operator {@code =*} implements a wildcard pattern matching algorithm.
*
* Usage example:
*
*
* "foo.java" =* "*.java"
*
*/
OPERATOR_GLOB,
/**
* The operator {@code =~} implements a regex pattern matching algorithm.
*
* Usage example:
*
*
* ".*" =~ "abc"
*
*/
OPERATOR_REGEX,
}
/**
* The currently configured imports (fully qualified package names).
*/
private final List singleImports = new ArrayList();
private final List onDemandImports = new ArrayList(Collections.singleton("java.lang"));
private ClassLoader classLoader = this.getClass().getClassLoader();
private EnumSet extensions = EnumSet.allOf(Extension.class);
/**
* It is generally difficult to distinguish between values, types and packages, e.g. "a.b.c" could represent
*
* - Field c of field b of variable a (a value)
*
- Class or interface c in package a.b (a type)
*
- Package a.b.c
*
* This is why many of the productions (corresponding with the various {@code parse...()} methods) return an
* {@link Atom}.
*/
interface Atom {
/**
* @return This atom's value (where {@code null} is a perfectly legal value)
* @throws ParseException Iff this atom does not pose a value, e.g. "String[]"
*/
T
toValue() throws E, ParseException;
/**
* @return {@code null} iff this atom can impossibly denote a type, e.g. "1 + 2"
*/
@Nullable Class>
toType();
/**
* @return {@code null} iff this atom can impossibly denote a package, e.g. "1 + 2"
*/
@Nullable String
toPackage();
}
/**
* @return An {@link Atom} that poses a value
*/
private static Atom
value(final T t) {
return new Atom() {
@Override @NotNull public T
toValue() { return t; }
@Override @Nullable public Class>
toType() { return null; }
@Override @Nullable public String
toPackage() { return null; }
};
}
/**
* @return An {@link Atom} that poses a type
*/
private Atom
type(final Class> type) {
return new Atom() {
@Override @NotNull public T
toValue() throws EX, ParseException {
if (
Parser.this.extensions.contains(Extension.NEW_CLASS_WITHOUT_KEYWORD)
&& Parser.this.extensions.contains(Extension.NEW_CLASS_WITHOUT_PARENTHESES)
) {
return Parser.this.newClass(type, Collections.emptyList());
} else {
throw new ParseException("'" + type.getName() + "' is a type, not a value");
}
}
@Override public Class>
toType() { return type; }
@Override @Nullable public String
toPackage() { return null; }
};
}
/**
*
* imports := { import }
*
* import := 'import' identifier { '.' identifier } [ '.' '*' ] ';'
*
*/
private void
parseImports() throws ParseException {
while (this.peekRead("import")) {
String qn = this.read(TokenType.IDENTIFIER);
QN:
for (;;) {
switch (this.read(";", ".")) {
case 0: // ';'
this.singleImports.add(qn);
break QN;
case 1: // '.'
if (this.peekRead("*")) {
this.read(";");
this.onDemandImports.add(qn);
break QN;
}
qn += "." + this.read(TokenType.IDENTIFIER);
break;
}
}
}
}
/**
*
* expression := conditional
*
*/
private Atom
parseExpression() throws ParseException, EX { return this.parseConditional(); }
/**
*
* conditional := logical-or [ '?' logical-or ':' conditional ]
*
*/
private Atom
parseConditional() throws ParseException, EX {
Atom lhs = this.parseLogicalOr();
if (!this.peekRead("?")) return lhs;
T mhs = this.parseLogicalOr().toValue();
this.read(":");
T rhs = this.parseConditional().toValue();
return Parser.value(this.conditional(lhs.toValue(), mhs, rhs));
}
/**
*
* logical-or := logical-and { '||' logical-and }
*
*/
private Atom
parseLogicalOr() throws ParseException, EX {
Atom lhs = this.parseLogicalAnd();
while (this.peekRead("||")) {
lhs = Parser.value(this.binaryOperation(
lhs.toValue(),
BinaryOperator.LOGICAL_OR,
this.parseLogicalAnd().toValue()
));
}
return lhs;
}
/**
*
* logical-and := bitwise-or { '&&' bitwise-or }
*
*/
private Atom
parseLogicalAnd() throws ParseException, EX {
Atom lhs = this.parseBitwiseOr();
while (this.peekRead("&&")) {
lhs = Parser.value(this.binaryOperation(
lhs.toValue(),
BinaryOperator.LOGICAL_AND,
this.parseBitwiseOr().toValue()
));
}
return lhs;
}
/**
*
* bitwise-or := bitwise-xor { '|' bitwise-xor }
*
*/
private Atom
parseBitwiseOr() throws ParseException, EX {
Atom lhs = this.parseBitwiseXor();
while (this.peekRead("|")) {
lhs = Parser.value(this.binaryOperation(
lhs.toValue(),
BinaryOperator.BITWISE_OR,
this.parseBitwiseXor().toValue()
));
}
return lhs;
}
/**
*
* bitwise-xor := bitwise-and { '^' bitwise-and }
*
*/
private Atom
parseBitwiseXor() throws ParseException, EX {
Atom lhs = this.parseBitwiseAnd();
while (this.peekRead("^")) {
lhs = Parser.value(this.binaryOperation(
lhs.toValue(),
BinaryOperator.BITWISE_XOR,
this.parseBitwiseAnd().toValue()
));
}
return lhs;
}
/**
*
* bitwise-and := relational { '^' relational }
*
*/
private Atom
parseBitwiseAnd() throws ParseException, EX {
Atom lhs = this.parseRelational();
while (this.peekRead("&")) {
lhs = Parser.value(this.binaryOperation(
lhs.toValue(),
BinaryOperator.BITWISE_AND,
this.parseRelational().toValue()
));
}
return lhs;
}
/**
*
* relational :=
* shift {
* '==' shift
* | '=*' shift
* | '=~' shift
* | '!=' shift
* | '<' shift
* | '<=' shift
* | '>' shift
* | '>=' shift
* | 'instanceof' reference-type
* }
*
*/
private Atom
parseRelational() throws ParseException, EX {
Atom lhs = this.parseShift();
for (;;) {
BinaryOperator operator;
if (this.extensions.contains(Extension.OPERATOR_GLOB) && this.peekRead("=*")) {
lhs = Parser.value(this.binaryOperation(
lhs.toValue(),
BinaryOperator.GLOB,
this.parseRelational().toValue()
));
} else
if (this.extensions.contains(Extension.OPERATOR_REGEX) && this.peekRead("=~")) {
lhs = Parser.value(this.binaryOperation(
lhs.toValue(),
BinaryOperator.REGEX,
this.parseRelational().toValue()
));
} else
if ((operator = this.peekReadEnum(
BinaryOperator.EQUAL,
BinaryOperator.NOT_EQUAL,
BinaryOperator.LESS,
BinaryOperator.LESS_EQUAL,
BinaryOperator.GREATER,
BinaryOperator.GREATER_EQUAL
)) != null) {
lhs = Parser.value(this.binaryOperation(lhs.toValue(), operator, this.parseRelational().toValue()));
} else
if (this.peekRead("instanceof")) {
lhs = Parser.value(this.instanceoF(lhs.toValue(), this.parseReferenceType()));
} else
{
break;
}
}
return lhs;
}
/**
*
* shift :=
* additive { ( '<<' | '>>' | '>>>' ) additive }
*
*/
private Atom
parseShift() throws ParseException, EX {
Atom lhs = this.parseAdditive();
for (;;) {
final BinaryOperator op = this.peekReadEnum(
BinaryOperator.LEFT_SHIFT,
BinaryOperator.RIGHT_SHIFT,
BinaryOperator.RIGHT_USHIFT
);
if (op == null) return lhs;
lhs = Parser.value(this.binaryOperation(lhs.toValue(), op, this.parseAdditive().toValue()));
}
}
/**
*
* additive :=
* multiplicative { ( '+' | '-' ) multiplicative }
*
*/
private Atom
parseAdditive() throws ParseException, EX {
Atom mul = this.parseMultiplicative();
for (;;) {
final BinaryOperator op = this.peekReadEnum(BinaryOperator.PLUS, BinaryOperator.MINUS);
if (op == null) return mul;
mul = Parser.value(this.binaryOperation(mul.toValue(), op, this.parseMultiplicative().toValue()));
}
}
/**
*
* multiplicative :=
* selector { ( '*' | '/' | '%' ) selector }
*
*/
private Atom
parseMultiplicative() throws ParseException, EX {
Atom lhs = this.parseSelector();
for (;;) {
final BinaryOperator op = this.peekReadEnum(
BinaryOperator.MULTIPLY,
BinaryOperator.DIVIDE,
BinaryOperator.MODULO
);
if (op == null) return lhs;
lhs = Parser.value(this.binaryOperation(lhs.toValue(), op, this.parseSelector().toValue()));
}
}
/**
*
* reference-type :=
* ( 'boolean' | 'byte' | 'short' | 'int' | 'long' | 'char' | 'float' | 'double' ) '[' ']' { '[' ']' }
* | class-or-interface-type { '[' ']' }
*
*/
private Class>
parseReferenceType() throws ParseException {
Class> result;
if (this.peek(IDENTIFIER) != null) {
result = this.parseClassOrInterfaceType();
} else {
result = this.parsePrimitiveType();
this.read("[");
this.read("]");
}
while (this.peekRead("[")) {
this.read("]");
result = Array.newInstance(result, 0).getClass();
}
return result;
}
private Class>
parsePrimitiveType() throws ParseException {
switch (this.read("boolean", "byte", "short", "int", "long", "char", "float", "double")) {
case 0: return boolean.class;
case 1: return byte.class;
case 2: return short.class;
case 3: return int.class;
case 4: return long.class;
case 5: return char.class;
case 6: return float.class;
case 7: return double.class;
default: throw new IllegalStateException();
}
}
/**
*
* selector :=
* primary [ arguments ] { '.' identifier selector-rest }
* | primary [ arguments ] '[' expression ']'
*
*/
private Atom
parseSelector() throws ParseException, EX {
Atom result = this.parsePrimary();
if (this.extensions.contains(Extension.NEW_CLASS_WITHOUT_KEYWORD)) {
List arguments = this.parseOptionalArguments();
if (arguments != null) {
// MyClass()
Class> clasS = result.toType();
if (clasS != null) result = Parser.value(this.newClass(clasS, arguments));
}
}
for (;;) {
if (this.peekRead(".")) {
result = this.parseSelectorRest(result, this.read(IDENTIFIER));
} else
if (this.peekRead("[")) {
Atom index = this.parseExpression();
this.read("]");
result = Parser.value(this.arrayAccess(result.toValue(), index.toValue()));
} else
{
return result;
}
}
}
/**
*
* selector-rest :=
* arguments
* | (nothing)
*
*/
private Atom
parseSelectorRest(final Atom target, final String identifier) throws ParseException, EX {
{
final List arguments = this.parseOptionalArguments();
if (arguments != null) {
if (this.extensions.contains(Extension.NEW_CLASS_WITHOUT_KEYWORD)) {
String packagE = target.toPackage();
if (packagE != null) {
// pkg.MyClass(...)
final Class> clasS = this.loadClass(packagE + '.' + identifier);
if (clasS != null) return new Atom() {
@Override @NotNull public T
toValue() throws EX { return Parser.this.newClass(clasS, arguments); }
@Override public Class>
toType() { return clasS; }
@Override @Nullable public String
toPackage() { return null; }
};
}
}
// A method invocation.
Class> clasS = target.toType();
if (clasS != null) {
return Parser.value(this.staticMethodInvocation(clasS, identifier, arguments));
} else {
return Parser.value(this.methodInvocation(target.toValue(), identifier, arguments));
}
}
}
// A qualified class name?
{
String packagE = target.toPackage();
if (packagE != null) {
Class> clasS = this.loadClass(packagE + '.' + identifier);
if (clasS != null) return this.type(clasS);
}
}
// A nested type or a static field reference?
{
Class> type = target.toType();
if (type != null) {
{
Class> nestedType = this.loadClass(type.getName() + '$' + identifier);
if (nestedType != null) return this.type(nestedType);
}
return Parser.value(this.staticFieldReference(type, identifier));
}
}
// Must be a package or a nonstatic field reference.
return new Atom() {
@Override @NotNull public T
toValue() throws EX, ParseException {
return Parser.this.fieldReference(target.toValue(), identifier);
}
@Override @Nullable public Class>
toType() { return null; }
@Override @Nullable public String
toPackage() {
String packagE = target.toPackage();
return packagE == null ? null : packagE + '.' + identifier;
}
};
}
/**
*
* primary :=
* '(' expression ')'
* | '!' primary
* | '-' primary
* | string-literal
* | int-literal
* | long-literal
* | float-literal
* | double-literal
* | 'null' | 'true' | 'false'
* | 'new' class-or-interface-type { '[' expression ']' } { '[' ']' }
* | 'new' class-or-interface-type arguments
* | 'new' primitive-type '[' expression ']' { '[' expression ']' } { '[' ']' }
* | identifier // Variable reference
* | class-or-interface-type arguments // Class instantiation
* | class-or-interface-type // Class instantiation
* | class-or-interface-type // Class literal
*
*/
private Atom
parsePrimary() throws ParseException, EX {
if (this.peekRead("(")) {
final Atom result = this.parseExpression();
this.read(")");
if (this.peek(
"~", "!", "(", // SUPPRESS CHECKSTYLE WrapMethod:3
IDENTIFIER,
"this", "new",
INTEGER_LITERAL, CHARACTER_LITERAL, STRING_LITERAL, FLOATING_POINT_LITERAL
) != -1) {
Class> type = result.toType();
if (type == null) throw new ParseException("'" + type + "' does not pose a type");
return Parser.value(this.cast(type, this.parsePrimary().toValue()));
}
return Parser.value(this.parenthesized(result.toValue()));
}
{
UnaryOperator operator = this.peekReadEnum(
UnaryOperator.LOGICAL_COMPLEMENT,
UnaryOperator.MINUS,
UnaryOperator.BITWISE_COMPLEMENT
);
if (operator != null) {
if (operator == UnaryOperator.MINUS && this.peek(INTEGER_LITERAL) != null) {
try {
return Parser.value(this.literal(Scanner.decodeIntegerLiteral('-' + this.read().text)));
} catch (ScanException se) {
throw ExceptionUtil.wrap(null, se, ParseException.class);
}
}
return Parser.value(this.unaryOperation(operator, this.parseSelector().toValue()));
}
}
if (this.peek(CHARACTER_LITERAL) != null) {
try {
return Parser.value(this.literal(Scanner.decodeCharacterLiteral(this.read().text)));
} catch (ScanException se) {
throw ExceptionUtil.wrap(null, se, ParseException.class);
}
}
if (this.peek(STRING_LITERAL) != null) {
try {
return Parser.value(this.literal(Scanner.decodeStringLiteral(this.read().text)));
} catch (ScanException se) {
throw ExceptionUtil.wrap(null, se, ParseException.class);
}
}
if (this.peek(INTEGER_LITERAL) != null) {
try {
return Parser.value(this.literal(Scanner.decodeIntegerLiteral(this.read().text)));
} catch (ScanException se) {
throw ExceptionUtil.wrap(null, se, ParseException.class);
}
}
if (this.peek(FLOATING_POINT_LITERAL) != null) {
return Parser.value(this.literal(Scanner.decodeFloatingPointLiteral(this.read().text)));
}
if (this.peekRead("true")) return Parser.value(this.literal(true));
if (this.peekRead("false")) return Parser.value(this.literal(false));
if (this.peekRead("null")) return Parser.value(this.literal(null));
if (this.peekRead("new")) {
// Primitive array creation?
if (this.peek(IDENTIFIER) == null) {
return Parser.value(this.parseNewArrayRest(this.parsePrimitiveType()));
}
Class> clasS = this.parseClassOrInterfaceType();
// Class or interface array creation?
if (this.peek("[")) return Parser.value(this.parseNewArrayRest(clasS));
// Class instance creation.
List arguments = this.parseOptionalArguments();
if (arguments == null) {
if (this.extensions.contains(Extension.NEW_CLASS_WITHOUT_PARENTHESES)) {
arguments = Collections.emptyList();
} else {
throw new ParseException("Opening parenthesis expected");
}
}
return Parser.value(this.newClass(clasS, arguments));
}
final String identifier = this.peekRead(IDENTIFIER);
if (identifier != null) {
// An imported type?
{
Class> clasS = this.loadImportedClass(identifier);
if (clasS != null) return this.type(clasS);
}
// Must be a variable reference or a package.
return new Atom() {
@Override @NotNull public T
toValue() throws EX, ParseException { return Parser.this.variableReference(identifier); }
@Override @Nullable public Class>
toType() { return null; }
@Override public String
toPackage() { return identifier; }
};
}
throw new ParseException("Primary expected instead of \"" + this.peek() + "\"");
}
/**
*
* '[' expression ']' { '[' expression ']' } { '[' ']' }
*
*/
private T
parseNewArrayRest(Class> clasS) throws ParseException, EX {
final List dimensions = new ArrayList();
this.read("[");
dimensions.add(this.parseExpression().toValue());
this.read("]");
while (this.peekRead("[")) {
if (this.peekRead("]")) {
clasS = Array.newInstance(clasS, 0).getClass();
while (this.peekRead("[")) {
this.read("]");
clasS = Array.newInstance(clasS, 0).getClass();
}
break;
}
dimensions.add(this.parseExpression().toValue());
this.read("]");
}
return this.newArray(clasS, dimensions);
}
/**
*
* class-or-interface-type := identifier { '.' identifier }
*
*
* This method returns once it is able to load the class, i.e. it loads top-level types, but cannot be used to
* find nested types.
*
*/
private Class>
parseClassOrInterfaceType() throws ParseException {
final String identifier = this.read(IDENTIFIER);
// Imported class.
{
Class> importedClass = this.loadImportedClass(identifier);
if (importedClass != null) return importedClass;
}
for (String qualifiedClassName = identifier;; qualifiedClassName += '.' + this.read(TokenType.IDENTIFIER)) {
Class> clasS = this.loadClass(qualifiedClassName);
if (clasS != null) return clasS;
if (!this.peekRead(".")) throw new ParseException("Cannot load \"" + qualifiedClassName + "\"");
}
}
/**
*
* optional-arguments := [ arguments ]
*
* arguments := '(' [ expression { ',' expression } ] ')'
*
*
* @return {@code null} iff there is no opening parenthesis
*/
@Nullable private List
parseOptionalArguments() throws ParseException, EX {
if (!this.peekRead("(")) return null;
if (this.peekRead(")")) return Collections.emptyList();
List arguments = new ArrayList();
do {
arguments.add(this.parseExpression().toValue());
} while (this.peekRead(","));
this.read(")");
return arguments;
}
// ABSTRACT HANDLERS.
// SUPPRESS CHECKSTYLE JavadocMethod:15
protected abstract T conditional(T lhs, T mhs, T rhs) throws EX;
protected abstract T unaryOperation(UnaryOperator operator, T operand) throws EX;
protected abstract T binaryOperation(T lhs, BinaryOperator operator, T rhs) throws EX;
protected abstract T fieldReference(T target, String fieldName) throws EX;
protected abstract T staticFieldReference(Class> type, String fieldName) throws EX;
protected abstract T methodInvocation(T target, String methodName, List arguments) throws EX;
protected abstract T staticMethodInvocation(Class> target, String methodName, List arguments) throws EX;
protected abstract T variableReference(String variableName) throws EX, ParseException;
protected abstract T literal(@Nullable Object value) throws EX;
protected abstract T parenthesized(T value) throws EX;
protected abstract T instanceoF(T lhs, Class> rhs) throws EX;
protected abstract T newClass(Class> clasS, List arguments) throws EX;
protected abstract T newArray(Class> clasS, List dimensions) throws EX;
protected abstract T cast(Class> targetType, T operand) throws EX, ParseException;
protected abstract T arrayAccess(T lhs, T rhs) throws EX;
// HELPERS.
@Nullable private Class>
loadImportedClass(String simpleClassName) {
for (String si : this.singleImports) {
if (si.endsWith("." + simpleClassName)) {
Class> clasS = this.loadClass(si);
if (clasS != null) return clasS;
}
}
for (String iod : this.onDemandImports) {
Class> clasS = this.loadClass(iod + '.' + simpleClassName);
if (clasS != null) return clasS;
}
return null;
}
/**
* @return {@code null} iff a class with the given name cannot be loaded
*/
@Nullable private Class>
loadClass(String qualifiedClassName) {
try {
return this.classLoader.loadClass(qualifiedClassName);
} catch (ClassNotFoundException cnfe) {
return null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy