mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 20:07:32 +00:00
Expression resolving/type resolution framework with operator parsing
This commit is contained in:
1 parent
c2e0bbedae
commit
941cc5e3fd
8 files changed
+1598
No files matched your search
@@ -1,6 +1,8 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter;
|
||||
|
||||
import noppes.npcs.client.ClientProxy;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.expression.ExpressionNode;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.expression.ExpressionTypeResolver;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.field.AssignmentInfo;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.field.FieldAccessInfo;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
|
||||
@@ -2236,6 +2238,7 @@ public class ScriptDocument {
|
||||
* - Mixed chains (a.b().c.d())
|
||||
* - Static access (Class.field, Class.method())
|
||||
* - New expressions (new Type())
|
||||
* - All Java operators (arithmetic, logical, bitwise, etc.)
|
||||
*
|
||||
* This is THE method that should be used for all type resolution needs.
|
||||
*/
|
||||
@@ -2246,6 +2249,11 @@ public class ScriptDocument {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if expression contains operators - if so, use the full expression resolver
|
||||
if (containsOperators(expr)) {
|
||||
return resolveExpressionWithOperators(expr, position);
|
||||
}
|
||||
|
||||
// Invalid expressions starting with brackets
|
||||
if (expr.startsWith("[") || expr.startsWith("]")) {
|
||||
return null; // Invalid syntax
|
||||
@@ -2531,6 +2539,350 @@ public class ScriptDocument {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== OPERATOR EXPRESSION RESOLUTION ====================
|
||||
|
||||
/**
|
||||
* Check if an expression contains operators that need advanced resolution.
|
||||
* This is a quick heuristic check - it may have false positives for operators
|
||||
* inside strings, but those are handled by the full parser.
|
||||
*/
|
||||
private boolean containsOperators(String expr) {
|
||||
if (expr == null) return false;
|
||||
|
||||
boolean inString = false;
|
||||
boolean inChar = false;
|
||||
|
||||
for (int i = 0; i < expr.length(); i++) {
|
||||
char c = expr.charAt(i);
|
||||
char next = (i + 1 < expr.length()) ? expr.charAt(i + 1) : 0;
|
||||
|
||||
// Track string literals
|
||||
if (c == '"' && !inChar) {
|
||||
if (!inString) {
|
||||
inString = true;
|
||||
} else if (i > 0 && expr.charAt(i - 1) != '\\') {
|
||||
inString = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Track char literals
|
||||
if (c == '\'' && !inString) {
|
||||
if (!inChar) {
|
||||
inChar = true;
|
||||
} else if (i > 0 && expr.charAt(i - 1) != '\\') {
|
||||
inChar = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip content inside strings/chars
|
||||
if (inString || inChar) continue;
|
||||
|
||||
// Check for operators (excluding . which is used for member access)
|
||||
switch (c) {
|
||||
case '+':
|
||||
// + is arithmetic unless it's unary at start or after operator
|
||||
if (next == '+') return true; // ++
|
||||
if (next == '=') return true; // +=
|
||||
// Check if this is binary + by looking at previous non-whitespace
|
||||
int prevIdx = i - 1;
|
||||
while (prevIdx >= 0 && Character.isWhitespace(expr.charAt(prevIdx))) prevIdx--;
|
||||
if (prevIdx >= 0) {
|
||||
char prev = expr.charAt(prevIdx);
|
||||
// If previous char is identifier char or ), this is binary
|
||||
if (Character.isJavaIdentifierPart(prev) || prev == ')' || prev == ']' || Character.isDigit(prev)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '-':
|
||||
// - is arithmetic unless it's unary minus before a number
|
||||
if (next == '-') return true; // --
|
||||
if (next == '=') return true; // -=
|
||||
// Check if this is binary - by looking at previous non-whitespace
|
||||
prevIdx = i - 1;
|
||||
while (prevIdx >= 0 && Character.isWhitespace(expr.charAt(prevIdx))) prevIdx--;
|
||||
if (prevIdx >= 0) {
|
||||
char prev = expr.charAt(prevIdx);
|
||||
// If previous char is identifier char or ), this is binary
|
||||
if (Character.isJavaIdentifierPart(prev) || prev == ')' || prev == ']' || Character.isDigit(prev)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '*': case '/': case '%':
|
||||
return true;
|
||||
|
||||
case '&':
|
||||
if (next == '&' || next == '=') return true;
|
||||
// Single & is bitwise AND
|
||||
return true;
|
||||
|
||||
case '|':
|
||||
if (next == '|' || next == '=') return true;
|
||||
// Single | is bitwise OR
|
||||
return true;
|
||||
|
||||
case '^': case '~':
|
||||
return true;
|
||||
|
||||
case '<':
|
||||
// Could be < > <= >= << or generics
|
||||
if (next == '<' || next == '=') return true;
|
||||
// Check if this looks like relational (not generic type params)
|
||||
// Generics typically follow a type name directly with no space
|
||||
int nextNonSpace = i + 1;
|
||||
while (nextNonSpace < expr.length() && Character.isWhitespace(expr.charAt(nextNonSpace))) nextNonSpace++;
|
||||
if (nextNonSpace < expr.length() && !Character.isUpperCase(expr.charAt(nextNonSpace))) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case '>':
|
||||
if (next == '>' || next == '=') return true;
|
||||
// Similar check for generics
|
||||
break;
|
||||
|
||||
case '!':
|
||||
if (next == '=') return true; // !=
|
||||
// Standalone ! is logical NOT
|
||||
return true;
|
||||
|
||||
case '=':
|
||||
if (next == '=') return true; // ==
|
||||
// Single = is assignment
|
||||
return true;
|
||||
|
||||
case '?':
|
||||
// Ternary operator - but be careful of generics with ?
|
||||
// If followed by :, it's definitely ternary
|
||||
for (int j = i + 1; j < expr.length(); j++) {
|
||||
if (expr.charAt(j) == ':') return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for instanceof keyword
|
||||
if (expr.contains(" instanceof ")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an expression that contains operators using the full expression parser.
|
||||
* This handles all Java operators with proper precedence and type promotion rules.
|
||||
*/
|
||||
private TypeInfo resolveExpressionWithOperators(String expr, int position) {
|
||||
// Create a context that bridges to ScriptDocument's existing resolution methods
|
||||
ExpressionNode.TypeResolverContext context = createExpressionResolverContext(position);
|
||||
|
||||
// Use the expression resolver to parse and resolve the type
|
||||
ExpressionTypeResolver resolver = new ExpressionTypeResolver(context);
|
||||
TypeInfo result = resolver.resolve(expr);
|
||||
|
||||
// If the expression resolver couldn't resolve it, fall back to simple resolution
|
||||
if (result == null || !result.isResolved()) {
|
||||
// Try the simple path in case the operator detection was a false positive
|
||||
return resolveSimpleExpression(expr, position);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a type resolver context that connects the expression resolver
|
||||
* to ScriptDocument's existing type resolution infrastructure.
|
||||
*/
|
||||
private ExpressionNode.TypeResolverContext createExpressionResolverContext(int position) {
|
||||
return new ExpressionNode.TypeResolverContext() {
|
||||
@Override
|
||||
public TypeInfo resolveIdentifier(String name) {
|
||||
// Check for special keywords first
|
||||
if ("this".equals(name)) {
|
||||
return findEnclosingScriptType(position);
|
||||
}
|
||||
if ("true".equals(name) || "false".equals(name)) {
|
||||
return TypeInfo.fromPrimitive("boolean");
|
||||
}
|
||||
if ("null".equals(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to resolve as a variable
|
||||
FieldInfo varInfo = resolveVariable(name, position);
|
||||
if (varInfo != null) {
|
||||
return varInfo.getTypeInfo();
|
||||
}
|
||||
|
||||
// Try as a class name (for static access)
|
||||
if (name.length() > 0 && Character.isUpperCase(name.charAt(0))) {
|
||||
return resolveType(name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInfo resolveMemberAccess(TypeInfo targetType, String memberName) {
|
||||
if (targetType == null || !targetType.isResolved()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targetType.hasField(memberName)) {
|
||||
FieldInfo fieldInfo = targetType.getFieldInfo(memberName);
|
||||
return (fieldInfo != null) ? fieldInfo.getTypeInfo() : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInfo resolveMethodCall(TypeInfo targetType, String methodName, TypeInfo[] argTypes) {
|
||||
if (targetType == null || !targetType.isResolved()) {
|
||||
// Try as a script-defined method
|
||||
if (isScriptMethod(methodName)) {
|
||||
MethodInfo scriptMethod = getScriptMethodInfo(methodName);
|
||||
if (scriptMethod != null) {
|
||||
return scriptMethod.getReturnType();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targetType.hasMethod(methodName)) {
|
||||
MethodInfo methodInfo = targetType.getMethodInfo(methodName);
|
||||
return (methodInfo != null) ? methodInfo.getReturnType() : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInfo resolveArrayAccess(TypeInfo arrayType) {
|
||||
if (arrayType == null || !arrayType.isResolved()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String typeName = arrayType.getFullName();
|
||||
if (typeName.endsWith("[]")) {
|
||||
String elementTypeName = typeName.substring(0, typeName.length() - 2);
|
||||
// Try to resolve the element type properly
|
||||
return resolveType(elementTypeName);
|
||||
}
|
||||
|
||||
// For List<T> or similar, try to extract the element type
|
||||
// This is a simplified version - could be enhanced for full generic support
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInfo resolveTypeName(String typeName) {
|
||||
return resolveType(typeName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a simple expression without operators.
|
||||
* This is the fallback path when operator detection was a false positive.
|
||||
*/
|
||||
private TypeInfo resolveSimpleExpression(String expr, int position) {
|
||||
// String literals
|
||||
if (expr.startsWith("\"") && expr.endsWith("\"")) {
|
||||
return resolveType("String");
|
||||
}
|
||||
|
||||
// Character literals
|
||||
if (expr.startsWith("'") && expr.endsWith("'")) {
|
||||
return TypeInfo.fromPrimitive("char");
|
||||
}
|
||||
|
||||
// Boolean literals
|
||||
if (expr.equals("true") || expr.equals("false")) {
|
||||
return TypeInfo.fromPrimitive("boolean");
|
||||
}
|
||||
|
||||
// Null literal
|
||||
if (expr.equals("null")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Numeric literals
|
||||
if (expr.matches("-?\\d*\\.?\\d+[fF]")) {
|
||||
if (hasExcessivePrecision(expr, 7)) {
|
||||
return TypeInfo.fromPrimitive("double");
|
||||
}
|
||||
return TypeInfo.fromPrimitive("float");
|
||||
}
|
||||
if (expr.matches("-?\\d*\\.\\d+[dD]?") || expr.matches("-?\\d+\\.[dD]?") || expr.matches("-?\\d+[dD]")) {
|
||||
if (hasExcessivePrecision(expr, 15)) {
|
||||
return null;
|
||||
}
|
||||
return TypeInfo.fromPrimitive("double");
|
||||
}
|
||||
if (expr.matches("-?\\d+[lL]")) {
|
||||
return TypeInfo.fromPrimitive("long");
|
||||
}
|
||||
if (expr.matches("-?\\d+")) {
|
||||
return TypeInfo.fromPrimitive("int");
|
||||
}
|
||||
|
||||
// "this" keyword
|
||||
if (expr.equals("this")) {
|
||||
return findEnclosingScriptType(position);
|
||||
}
|
||||
|
||||
// "new Type()" expressions
|
||||
if (expr.startsWith("new ")) {
|
||||
Matcher newMatcher = NEW_TYPE_PATTERN.matcher(expr);
|
||||
if (newMatcher.find()) {
|
||||
return resolveType(newMatcher.group(1));
|
||||
}
|
||||
}
|
||||
|
||||
// Try as variable or field chain
|
||||
List<ChainSegment> segments = parseExpressionChain(expr);
|
||||
if (segments.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ChainSegment first = segments.get(0);
|
||||
TypeInfo currentType = null;
|
||||
|
||||
if (first.name.equals("this")) {
|
||||
currentType = findEnclosingScriptType(position);
|
||||
} else if (Character.isUpperCase(first.name.charAt(0))) {
|
||||
currentType = resolveType(first.name);
|
||||
} else if (!first.isMethodCall) {
|
||||
FieldInfo varInfo = resolveVariable(first.name, position);
|
||||
if (varInfo != null) {
|
||||
currentType = varInfo.getTypeInfo();
|
||||
}
|
||||
} else {
|
||||
if (isScriptMethod(first.name)) {
|
||||
MethodInfo scriptMethod = getScriptMethodInfo(first.name);
|
||||
if (scriptMethod != null) {
|
||||
currentType = scriptMethod.getReturnType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < segments.size(); i++) {
|
||||
currentType = resolveChainSegment(currentType, segments.get(i));
|
||||
if (currentType == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return currentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the matching opening parenthesis when given a closing paren position.
|
||||
*/
|
||||
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter.expression;
|
||||
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ExpressionNode {
|
||||
protected final int start;
|
||||
protected final int end;
|
||||
|
||||
protected ExpressionNode(int start, int end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public int getStart() { return start; }
|
||||
public int getEnd() { return end; }
|
||||
public abstract TypeInfo resolveType(TypeResolverContext resolver);
|
||||
|
||||
public static class IntLiteralNode extends ExpressionNode {
|
||||
private final String value;
|
||||
public IntLiteralNode(String value, int start, int end) { super(start, end); this.value = value; }
|
||||
public String getValue() { return value; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromPrimitive("int"); }
|
||||
}
|
||||
|
||||
public static class LongLiteralNode extends ExpressionNode {
|
||||
public LongLiteralNode(String value, int start, int end) { super(start, end); }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromPrimitive("long"); }
|
||||
}
|
||||
|
||||
public static class FloatLiteralNode extends ExpressionNode {
|
||||
public FloatLiteralNode(String value, int start, int end) { super(start, end); }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromPrimitive("float"); }
|
||||
}
|
||||
|
||||
public static class DoubleLiteralNode extends ExpressionNode {
|
||||
public DoubleLiteralNode(String value, int start, int end) { super(start, end); }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromPrimitive("double"); }
|
||||
}
|
||||
|
||||
public static class BooleanLiteralNode extends ExpressionNode {
|
||||
public BooleanLiteralNode(boolean value, int start, int end) { super(start, end); }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromPrimitive("boolean"); }
|
||||
}
|
||||
|
||||
public static class CharLiteralNode extends ExpressionNode {
|
||||
public CharLiteralNode(String value, int start, int end) { super(start, end); }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromPrimitive("char"); }
|
||||
}
|
||||
|
||||
public static class StringLiteralNode extends ExpressionNode {
|
||||
private final String value;
|
||||
public StringLiteralNode(String value, int start, int end) { super(start, end); this.value = value; }
|
||||
public String getValue() { return value; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromClass(String.class); }
|
||||
}
|
||||
|
||||
public static class NullLiteralNode extends ExpressionNode {
|
||||
public NullLiteralNode(int start, int end) { super(start, end); }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return null; }
|
||||
}
|
||||
|
||||
public static class IdentifierNode extends ExpressionNode {
|
||||
private final String name;
|
||||
public IdentifierNode(String name, int start, int end) { super(start, end); this.name = name; }
|
||||
public String getName() { return name; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return resolver.resolveIdentifier(name); }
|
||||
}
|
||||
|
||||
public static class MemberAccessNode extends ExpressionNode {
|
||||
private final ExpressionNode target;
|
||||
private final String memberName;
|
||||
public MemberAccessNode(ExpressionNode target, String memberName, int start, int end) {
|
||||
super(start, end); this.target = target; this.memberName = memberName;
|
||||
}
|
||||
public ExpressionNode getTarget() { return target; }
|
||||
public String getMemberName() { return memberName; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) {
|
||||
TypeInfo targetType = target.resolveType(resolver);
|
||||
if (targetType == null || !targetType.isResolved()) return null;
|
||||
return resolver.resolveMemberAccess(targetType, memberName);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MethodCallNode extends ExpressionNode {
|
||||
private final ExpressionNode target;
|
||||
private final String methodName;
|
||||
private final List<ExpressionNode> arguments;
|
||||
public MethodCallNode(ExpressionNode target, String methodName, List<ExpressionNode> arguments, int start, int end) {
|
||||
super(start, end); this.target = target; this.methodName = methodName;
|
||||
this.arguments = arguments != null ? new ArrayList<>(arguments) : new ArrayList<>();
|
||||
}
|
||||
public ExpressionNode getTarget() { return target; }
|
||||
public String getMethodName() { return methodName; }
|
||||
public List<ExpressionNode> getArguments() { return Collections.unmodifiableList(arguments); }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) {
|
||||
TypeInfo targetType = target != null ? target.resolveType(resolver) : null;
|
||||
TypeInfo[] argTypes = new TypeInfo[arguments.size()];
|
||||
for (int i = 0; i < arguments.size(); i++) argTypes[i] = arguments.get(i).resolveType(resolver);
|
||||
return resolver.resolveMethodCall(targetType, methodName, argTypes);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ArrayAccessNode extends ExpressionNode {
|
||||
private final ExpressionNode array;
|
||||
private final ExpressionNode index;
|
||||
public ArrayAccessNode(ExpressionNode array, ExpressionNode index, int start, int end) {
|
||||
super(start, end); this.array = array; this.index = index;
|
||||
}
|
||||
public ExpressionNode getArray() { return array; }
|
||||
public ExpressionNode getIndex() { return index; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) {
|
||||
TypeInfo arrayType = array.resolveType(resolver);
|
||||
if (arrayType == null || !arrayType.isResolved()) return null;
|
||||
return resolver.resolveArrayAccess(arrayType);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NewNode extends ExpressionNode {
|
||||
private final String typeName;
|
||||
private final List<ExpressionNode> arguments;
|
||||
public NewNode(String typeName, List<ExpressionNode> arguments, int start, int end) {
|
||||
super(start, end); this.typeName = typeName;
|
||||
this.arguments = arguments != null ? new ArrayList<>(arguments) : new ArrayList<>();
|
||||
}
|
||||
public String getTypeName() { return typeName; }
|
||||
public List<ExpressionNode> getArguments() { return Collections.unmodifiableList(arguments); }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return resolver.resolveTypeName(typeName); }
|
||||
}
|
||||
|
||||
public static class BinaryOpNode extends ExpressionNode {
|
||||
private final ExpressionNode left;
|
||||
private final OperatorType operator;
|
||||
private final ExpressionNode right;
|
||||
public BinaryOpNode(ExpressionNode left, OperatorType operator, ExpressionNode right, int start, int end) {
|
||||
super(start, end); this.left = left; this.operator = operator; this.right = right;
|
||||
}
|
||||
public ExpressionNode getLeft() { return left; }
|
||||
public OperatorType getOperator() { return operator; }
|
||||
public ExpressionNode getRight() { return right; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) {
|
||||
TypeInfo leftType = left.resolveType(resolver);
|
||||
TypeInfo rightType = right.resolveType(resolver);
|
||||
return TypeRules.resolveBinaryOperatorType(operator, leftType, rightType);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnaryOpNode extends ExpressionNode {
|
||||
private final OperatorType operator;
|
||||
private final ExpressionNode operand;
|
||||
private final boolean prefix;
|
||||
public UnaryOpNode(OperatorType operator, ExpressionNode operand, boolean prefix, int start, int end) {
|
||||
super(start, end); this.operator = operator; this.operand = operand; this.prefix = prefix;
|
||||
}
|
||||
public OperatorType getOperator() { return operator; }
|
||||
public ExpressionNode getOperand() { return operand; }
|
||||
public boolean isPrefix() { return prefix; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) {
|
||||
TypeInfo operandType = operand.resolveType(resolver);
|
||||
return TypeRules.resolveUnaryOperatorType(operator, operandType);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TernaryNode extends ExpressionNode {
|
||||
private final ExpressionNode condition;
|
||||
private final ExpressionNode thenExpr;
|
||||
private final ExpressionNode elseExpr;
|
||||
public TernaryNode(ExpressionNode condition, ExpressionNode thenExpr, ExpressionNode elseExpr, int start, int end) {
|
||||
super(start, end); this.condition = condition; this.thenExpr = thenExpr; this.elseExpr = elseExpr;
|
||||
}
|
||||
public ExpressionNode getCondition() { return condition; }
|
||||
public ExpressionNode getThenExpr() { return thenExpr; }
|
||||
public ExpressionNode getElseExpr() { return elseExpr; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) {
|
||||
TypeInfo thenType = thenExpr.resolveType(resolver);
|
||||
TypeInfo elseType = elseExpr.resolveType(resolver);
|
||||
return TypeRules.resolveTernaryType(thenType, elseType);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InstanceofNode extends ExpressionNode {
|
||||
private final ExpressionNode expression;
|
||||
private final String typeName;
|
||||
public InstanceofNode(ExpressionNode expression, String typeName, int start, int end) {
|
||||
super(start, end); this.expression = expression; this.typeName = typeName;
|
||||
}
|
||||
public ExpressionNode getExpression() { return expression; }
|
||||
public String getTypeName() { return typeName; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromPrimitive("boolean"); }
|
||||
}
|
||||
|
||||
public static class CastNode extends ExpressionNode {
|
||||
private final String typeName;
|
||||
private final ExpressionNode expression;
|
||||
public CastNode(String typeName, ExpressionNode expression, int start, int end) {
|
||||
super(start, end); this.typeName = typeName; this.expression = expression;
|
||||
}
|
||||
public String getTypeName() { return typeName; }
|
||||
public ExpressionNode getExpression() { return expression; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return resolver.resolveTypeName(typeName); }
|
||||
}
|
||||
|
||||
public static class AssignmentNode extends ExpressionNode {
|
||||
private final ExpressionNode target;
|
||||
private final OperatorType operator;
|
||||
private final ExpressionNode value;
|
||||
public AssignmentNode(ExpressionNode target, OperatorType operator, ExpressionNode value, int start, int end) {
|
||||
super(start, end); this.target = target; this.operator = operator; this.value = value;
|
||||
}
|
||||
public ExpressionNode getTarget() { return target; }
|
||||
public OperatorType getOperator() { return operator; }
|
||||
public ExpressionNode getValue() { return value; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return target.resolveType(resolver); }
|
||||
}
|
||||
|
||||
public static class ParenthesizedNode extends ExpressionNode {
|
||||
private final ExpressionNode inner;
|
||||
public ParenthesizedNode(ExpressionNode inner, int start, int end) { super(start, end); this.inner = inner; }
|
||||
public ExpressionNode getInner() { return inner; }
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) { return inner.resolveType(resolver); }
|
||||
}
|
||||
|
||||
public interface TypeResolverContext {
|
||||
TypeInfo resolveIdentifier(String name);
|
||||
TypeInfo resolveMemberAccess(TypeInfo targetType, String memberName);
|
||||
TypeInfo resolveMethodCall(TypeInfo targetType, String methodName, TypeInfo[] argTypes);
|
||||
TypeInfo resolveArrayAccess(TypeInfo arrayType);
|
||||
TypeInfo resolveTypeName(String typeName);
|
||||
}
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter.expression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ExpressionParser {
|
||||
private final List<ExpressionToken> tokens;
|
||||
private int pos;
|
||||
|
||||
public ExpressionParser(List<ExpressionToken> tokens) {
|
||||
this.tokens = tokens;
|
||||
this.pos = 0;
|
||||
}
|
||||
|
||||
public ExpressionNode parse() {
|
||||
if (tokens.isEmpty()) return null;
|
||||
return parseExpression(0);
|
||||
}
|
||||
|
||||
private ExpressionToken current() {
|
||||
if (pos >= tokens.size()) return tokens.get(tokens.size() - 1);
|
||||
return tokens.get(pos);
|
||||
}
|
||||
|
||||
private ExpressionToken advance() {
|
||||
ExpressionToken tok = current();
|
||||
if (pos < tokens.size()) pos++;
|
||||
return tok;
|
||||
}
|
||||
|
||||
private boolean check(ExpressionToken.TokenKind kind) { return current().getKind() == kind; }
|
||||
|
||||
private boolean match(ExpressionToken.TokenKind kind) {
|
||||
if (check(kind)) { advance(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
private ExpressionNode parseExpression(int minPrecedence) {
|
||||
ExpressionNode left = parsePrefixExpression();
|
||||
if (left == null) return null;
|
||||
|
||||
while (true) {
|
||||
if (check(ExpressionToken.TokenKind.QUESTION) && minPrecedence <= 2) {
|
||||
left = parseTernary(left);
|
||||
continue;
|
||||
}
|
||||
|
||||
OperatorType op = getCurrentBinaryOperator();
|
||||
if (op == null) break;
|
||||
|
||||
int precedence = op.getPrecedence();
|
||||
if (precedence < minPrecedence) break;
|
||||
|
||||
advance();
|
||||
|
||||
int nextMinPrecedence = (op.getAssociativity() == OperatorType.Associativity.LEFT)
|
||||
? precedence + 1 : precedence;
|
||||
|
||||
ExpressionNode right = parseExpression(nextMinPrecedence);
|
||||
if (right == null) break;
|
||||
|
||||
if (op == OperatorType.INSTANCEOF) {
|
||||
String typeName = extractTypeName(right);
|
||||
left = new ExpressionNode.InstanceofNode(left, typeName, left.getStart(), right.getEnd());
|
||||
} else if (op.isAssignment()) {
|
||||
left = new ExpressionNode.AssignmentNode(left, op, right, left.getStart(), right.getEnd());
|
||||
} else {
|
||||
left = new ExpressionNode.BinaryOpNode(left, op, right, left.getStart(), right.getEnd());
|
||||
}
|
||||
}
|
||||
|
||||
return parsePostfixExpression(left);
|
||||
}
|
||||
|
||||
private String extractTypeName(ExpressionNode node) {
|
||||
if (node instanceof ExpressionNode.IdentifierNode) {
|
||||
return ((ExpressionNode.IdentifierNode) node).getName();
|
||||
}
|
||||
if (node instanceof ExpressionNode.MemberAccessNode) {
|
||||
ExpressionNode.MemberAccessNode ma = (ExpressionNode.MemberAccessNode) node;
|
||||
return extractTypeName(ma.getTarget()) + "." + ma.getMemberName();
|
||||
}
|
||||
return "Object";
|
||||
}
|
||||
|
||||
private ExpressionNode parseTernary(ExpressionNode condition) {
|
||||
int start = condition.getStart();
|
||||
advance();
|
||||
ExpressionNode thenExpr = parseExpression(0);
|
||||
if (thenExpr == null) return condition;
|
||||
if (!match(ExpressionToken.TokenKind.COLON)) return condition;
|
||||
ExpressionNode elseExpr = parseExpression(2);
|
||||
if (elseExpr == null) return condition;
|
||||
return new ExpressionNode.TernaryNode(condition, thenExpr, elseExpr, start, elseExpr.getEnd());
|
||||
}
|
||||
|
||||
private OperatorType getCurrentBinaryOperator() {
|
||||
ExpressionToken tok = current();
|
||||
if (tok.getKind() == ExpressionToken.TokenKind.OPERATOR) {
|
||||
OperatorType op = tok.getOperatorType();
|
||||
if (op != null && (op.isBinary() || op.isAssignment())) return op;
|
||||
}
|
||||
if (tok.getKind() == ExpressionToken.TokenKind.INSTANCEOF) return OperatorType.INSTANCEOF;
|
||||
return null;
|
||||
}
|
||||
|
||||
private ExpressionNode parsePrefixExpression() {
|
||||
ExpressionToken tok = current();
|
||||
int start = tok.getStart();
|
||||
|
||||
if (tok.getKind() == ExpressionToken.TokenKind.OPERATOR) {
|
||||
OperatorType op = tok.getOperatorType();
|
||||
if (op != null && isUnaryOperator(op)) {
|
||||
advance();
|
||||
ExpressionNode operand = parsePrefixExpression();
|
||||
if (operand == null) return null;
|
||||
OperatorType unaryOp = toUnaryOperator(op);
|
||||
return new ExpressionNode.UnaryOpNode(unaryOp, operand, true, start, operand.getEnd());
|
||||
}
|
||||
}
|
||||
|
||||
if (tok.getKind() == ExpressionToken.TokenKind.LEFT_PAREN) {
|
||||
return parseCastOrParenthesized();
|
||||
}
|
||||
|
||||
return parsePrimaryExpression();
|
||||
}
|
||||
|
||||
private boolean isUnaryOperator(OperatorType op) {
|
||||
switch (op) {
|
||||
case ADD: case SUBTRACT: case LOGICAL_NOT: case BITWISE_NOT:
|
||||
case PRE_INCREMENT: case PRE_DECREMENT: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
private OperatorType toUnaryOperator(OperatorType op) {
|
||||
switch (op) {
|
||||
case ADD: return OperatorType.UNARY_PLUS;
|
||||
case SUBTRACT: return OperatorType.UNARY_MINUS;
|
||||
default: return op;
|
||||
}
|
||||
}
|
||||
|
||||
private ExpressionNode parseCastOrParenthesized() {
|
||||
int start = current().getStart();
|
||||
advance();
|
||||
|
||||
if (check(ExpressionToken.TokenKind.IDENTIFIER)) {
|
||||
String possibleType = current().getText();
|
||||
int savedPos = pos;
|
||||
advance();
|
||||
|
||||
while (check(ExpressionToken.TokenKind.DOT)) {
|
||||
advance();
|
||||
if (check(ExpressionToken.TokenKind.IDENTIFIER)) {
|
||||
possibleType += "." + current().getText();
|
||||
advance();
|
||||
}
|
||||
}
|
||||
|
||||
while (check(ExpressionToken.TokenKind.LEFT_BRACKET)) {
|
||||
advance();
|
||||
if (!match(ExpressionToken.TokenKind.RIGHT_BRACKET)) { pos = savedPos; break; }
|
||||
possibleType += "[]";
|
||||
}
|
||||
|
||||
if (check(ExpressionToken.TokenKind.RIGHT_PAREN)) {
|
||||
advance();
|
||||
if (canStartExpression()) {
|
||||
ExpressionNode expr = parsePrefixExpression();
|
||||
if (expr != null) {
|
||||
return new ExpressionNode.CastNode(possibleType, expr, start, expr.getEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
pos = savedPos;
|
||||
}
|
||||
|
||||
ExpressionNode inner = parseExpression(0);
|
||||
if (inner != null && match(ExpressionToken.TokenKind.RIGHT_PAREN)) {
|
||||
return new ExpressionNode.ParenthesizedNode(inner, start, current().getStart());
|
||||
}
|
||||
return inner;
|
||||
}
|
||||
|
||||
private boolean canStartExpression() {
|
||||
ExpressionToken.TokenKind kind = current().getKind();
|
||||
switch (kind) {
|
||||
case IDENTIFIER: case INT_LITERAL: case LONG_LITERAL: case FLOAT_LITERAL:
|
||||
case DOUBLE_LITERAL: case BOOLEAN_LITERAL: case CHAR_LITERAL: case STRING_LITERAL:
|
||||
case NULL_LITERAL: case NEW: case LEFT_PAREN: case OPERATOR: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ExpressionNode parsePrimaryExpression() {
|
||||
ExpressionToken tok = current();
|
||||
int start = tok.getStart();
|
||||
|
||||
switch (tok.getKind()) {
|
||||
case INT_LITERAL: advance(); return new ExpressionNode.IntLiteralNode(tok.getText(), start, tok.getEnd());
|
||||
case LONG_LITERAL: advance(); return new ExpressionNode.LongLiteralNode(tok.getText(), start, tok.getEnd());
|
||||
case FLOAT_LITERAL: advance(); return new ExpressionNode.FloatLiteralNode(tok.getText(), start, tok.getEnd());
|
||||
case DOUBLE_LITERAL: advance(); return new ExpressionNode.DoubleLiteralNode(tok.getText(), start, tok.getEnd());
|
||||
case BOOLEAN_LITERAL: advance(); return new ExpressionNode.BooleanLiteralNode("true".equals(tok.getText()), start, tok.getEnd());
|
||||
case CHAR_LITERAL: advance(); return new ExpressionNode.CharLiteralNode(tok.getText(), start, tok.getEnd());
|
||||
case STRING_LITERAL: advance(); return new ExpressionNode.StringLiteralNode(tok.getText(), start, tok.getEnd());
|
||||
case NULL_LITERAL: advance(); return new ExpressionNode.NullLiteralNode(start, tok.getEnd());
|
||||
case NEW: return parseNewExpression();
|
||||
case IDENTIFIER: return parseIdentifierOrMethodCall();
|
||||
case LEFT_PAREN: return parseCastOrParenthesized();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ExpressionNode parseNewExpression() {
|
||||
int start = current().getStart();
|
||||
advance();
|
||||
if (!check(ExpressionToken.TokenKind.IDENTIFIER)) return null;
|
||||
|
||||
StringBuilder typeName = new StringBuilder(current().getText());
|
||||
advance();
|
||||
|
||||
while (check(ExpressionToken.TokenKind.DOT)) {
|
||||
advance();
|
||||
if (check(ExpressionToken.TokenKind.IDENTIFIER)) {
|
||||
typeName.append(".").append(current().getText());
|
||||
advance();
|
||||
}
|
||||
}
|
||||
|
||||
List<ExpressionNode> args = new ArrayList<>();
|
||||
if (match(ExpressionToken.TokenKind.LEFT_PAREN)) {
|
||||
args = parseArgumentList();
|
||||
match(ExpressionToken.TokenKind.RIGHT_PAREN);
|
||||
}
|
||||
|
||||
return new ExpressionNode.NewNode(typeName.toString(), args, start, current().getStart());
|
||||
}
|
||||
|
||||
private ExpressionNode parseIdentifierOrMethodCall() {
|
||||
int start = current().getStart();
|
||||
String name = current().getText();
|
||||
advance();
|
||||
|
||||
ExpressionNode result = new ExpressionNode.IdentifierNode(name, start, current().getStart());
|
||||
return parseAccessChain(result);
|
||||
}
|
||||
|
||||
private ExpressionNode parseAccessChain(ExpressionNode base) {
|
||||
while (true) {
|
||||
if (check(ExpressionToken.TokenKind.LEFT_PAREN)) {
|
||||
advance();
|
||||
List<ExpressionNode> args = parseArgumentList();
|
||||
int end = current().getStart();
|
||||
match(ExpressionToken.TokenKind.RIGHT_PAREN);
|
||||
|
||||
String methodName;
|
||||
ExpressionNode target;
|
||||
|
||||
if (base instanceof ExpressionNode.IdentifierNode) {
|
||||
methodName = ((ExpressionNode.IdentifierNode) base).getName();
|
||||
target = null;
|
||||
} else if (base instanceof ExpressionNode.MemberAccessNode) {
|
||||
ExpressionNode.MemberAccessNode ma = (ExpressionNode.MemberAccessNode) base;
|
||||
methodName = ma.getMemberName();
|
||||
target = ma.getTarget();
|
||||
} else {
|
||||
methodName = "apply";
|
||||
target = base;
|
||||
}
|
||||
|
||||
base = new ExpressionNode.MethodCallNode(target, methodName, args, base.getStart(), end);
|
||||
} else if (check(ExpressionToken.TokenKind.DOT)) {
|
||||
advance();
|
||||
if (!check(ExpressionToken.TokenKind.IDENTIFIER)) break;
|
||||
String memberName = current().getText();
|
||||
int end = current().getEnd();
|
||||
advance();
|
||||
base = new ExpressionNode.MemberAccessNode(base, memberName, base.getStart(), end);
|
||||
} else if (check(ExpressionToken.TokenKind.LEFT_BRACKET)) {
|
||||
advance();
|
||||
ExpressionNode index = parseExpression(0);
|
||||
int end = current().getStart();
|
||||
match(ExpressionToken.TokenKind.RIGHT_BRACKET);
|
||||
if (index != null) {
|
||||
base = new ExpressionNode.ArrayAccessNode(base, index, base.getStart(), end);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
private List<ExpressionNode> parseArgumentList() {
|
||||
List<ExpressionNode> args = new ArrayList<>();
|
||||
if (check(ExpressionToken.TokenKind.RIGHT_PAREN)) return args;
|
||||
|
||||
ExpressionNode first = parseExpression(0);
|
||||
if (first != null) args.add(first);
|
||||
|
||||
while (match(ExpressionToken.TokenKind.COMMA)) {
|
||||
ExpressionNode arg = parseExpression(0);
|
||||
if (arg != null) args.add(arg);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private ExpressionNode parsePostfixExpression(ExpressionNode expr) {
|
||||
if (expr == null) return null;
|
||||
|
||||
while (true) {
|
||||
ExpressionToken tok = current();
|
||||
if (tok.getKind() != ExpressionToken.TokenKind.OPERATOR) break;
|
||||
|
||||
OperatorType op = tok.getOperatorType();
|
||||
if (op == OperatorType.PRE_INCREMENT) {
|
||||
advance();
|
||||
expr = new ExpressionNode.UnaryOpNode(OperatorType.POST_INCREMENT, expr, false, expr.getStart(), tok.getEnd());
|
||||
} else if (op == OperatorType.PRE_DECREMENT) {
|
||||
advance();
|
||||
expr = new ExpressionNode.UnaryOpNode(OperatorType.POST_DECREMENT, expr, false, expr.getStart(), tok.getEnd());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter.expression;
|
||||
|
||||
public class ExpressionToken {
|
||||
|
||||
public enum TokenKind {
|
||||
INT_LITERAL, LONG_LITERAL, FLOAT_LITERAL, DOUBLE_LITERAL,
|
||||
BOOLEAN_LITERAL, CHAR_LITERAL, STRING_LITERAL, NULL_LITERAL,
|
||||
IDENTIFIER, NEW, INSTANCEOF,
|
||||
OPERATOR,
|
||||
LEFT_PAREN, RIGHT_PAREN, LEFT_BRACKET, RIGHT_BRACKET,
|
||||
DOT, COMMA, QUESTION, COLON, SEMICOLON,
|
||||
EOF
|
||||
}
|
||||
|
||||
private final TokenKind kind;
|
||||
private final String text;
|
||||
private final int start;
|
||||
private final int end;
|
||||
private final OperatorType operatorType;
|
||||
|
||||
public ExpressionToken(TokenKind kind, String text, int start, int end) {
|
||||
this(kind, text, start, end, null);
|
||||
}
|
||||
|
||||
public ExpressionToken(TokenKind kind, String text, int start, int end, OperatorType operatorType) {
|
||||
this.kind = kind;
|
||||
this.text = text;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.operatorType = operatorType;
|
||||
}
|
||||
|
||||
public TokenKind getKind() { return kind; }
|
||||
public String getText() { return text; }
|
||||
public int getStart() { return start; }
|
||||
public int getEnd() { return end; }
|
||||
public OperatorType getOperatorType() { return operatorType; }
|
||||
|
||||
public static ExpressionToken operator(String symbol, int start, int end) {
|
||||
OperatorType op = OperatorType.fromBinarySymbol(symbol);
|
||||
if (op == null) op = OperatorType.fromSymbol(symbol);
|
||||
return new ExpressionToken(TokenKind.OPERATOR, symbol, start, end, op);
|
||||
}
|
||||
|
||||
public static ExpressionToken identifier(String name, int start, int end) {
|
||||
if ("true".equals(name) || "false".equals(name)) {
|
||||
return new ExpressionToken(TokenKind.BOOLEAN_LITERAL, name, start, end);
|
||||
}
|
||||
if ("null".equals(name)) {
|
||||
return new ExpressionToken(TokenKind.NULL_LITERAL, name, start, end);
|
||||
}
|
||||
if ("new".equals(name)) {
|
||||
return new ExpressionToken(TokenKind.NEW, name, start, end);
|
||||
}
|
||||
if ("instanceof".equals(name)) {
|
||||
return new ExpressionToken(TokenKind.INSTANCEOF, name, start, end);
|
||||
}
|
||||
return new ExpressionToken(TokenKind.IDENTIFIER, name, start, end);
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter.expression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ExpressionTokenizer {
|
||||
|
||||
public static List<ExpressionToken> tokenize(String expr) {
|
||||
List<ExpressionToken> tokens = new ArrayList<>();
|
||||
int pos = 0;
|
||||
int len = expr.length();
|
||||
|
||||
while (pos < len) {
|
||||
char c = expr.charAt(pos);
|
||||
|
||||
if (Character.isWhitespace(c)) { pos++; continue; }
|
||||
|
||||
if (c == '"') {
|
||||
int start = pos++;
|
||||
while (pos < len && expr.charAt(pos) != '"') {
|
||||
if (expr.charAt(pos) == '\\' && pos + 1 < len) pos++;
|
||||
pos++;
|
||||
}
|
||||
if (pos < len) pos++;
|
||||
tokens.add(new ExpressionToken(ExpressionToken.TokenKind.STRING_LITERAL, expr.substring(start, pos), start, pos));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\'') {
|
||||
int start = pos++;
|
||||
while (pos < len && expr.charAt(pos) != '\'') {
|
||||
if (expr.charAt(pos) == '\\' && pos + 1 < len) pos++;
|
||||
pos++;
|
||||
}
|
||||
if (pos < len) pos++;
|
||||
tokens.add(new ExpressionToken(ExpressionToken.TokenKind.CHAR_LITERAL, expr.substring(start, pos), start, pos));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Character.isDigit(c) || (c == '.' && pos + 1 < len && Character.isDigit(expr.charAt(pos + 1)))) {
|
||||
int start = pos;
|
||||
if (c == '0' && pos + 1 < len) {
|
||||
char next = expr.charAt(pos + 1);
|
||||
if (next == 'x' || next == 'X') {
|
||||
pos += 2;
|
||||
while (pos < len && isHexDigit(expr.charAt(pos))) pos++;
|
||||
if (pos < len && (expr.charAt(pos) == 'L' || expr.charAt(pos) == 'l')) {
|
||||
pos++;
|
||||
tokens.add(new ExpressionToken(ExpressionToken.TokenKind.LONG_LITERAL, expr.substring(start, pos), start, pos));
|
||||
} else {
|
||||
tokens.add(new ExpressionToken(ExpressionToken.TokenKind.INT_LITERAL, expr.substring(start, pos), start, pos));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (next == 'b' || next == 'B') {
|
||||
pos += 2;
|
||||
while (pos < len && (expr.charAt(pos) == '0' || expr.charAt(pos) == '1')) pos++;
|
||||
tokens.add(new ExpressionToken(ExpressionToken.TokenKind.INT_LITERAL, expr.substring(start, pos), start, pos));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
while (pos < len && Character.isDigit(expr.charAt(pos))) pos++;
|
||||
boolean hasDecimal = false;
|
||||
if (pos < len && expr.charAt(pos) == '.') {
|
||||
if (pos + 1 < len && Character.isDigit(expr.charAt(pos + 1))) {
|
||||
hasDecimal = true;
|
||||
pos++;
|
||||
while (pos < len && Character.isDigit(expr.charAt(pos))) pos++;
|
||||
}
|
||||
}
|
||||
if (pos < len && (expr.charAt(pos) == 'e' || expr.charAt(pos) == 'E')) {
|
||||
pos++;
|
||||
if (pos < len && (expr.charAt(pos) == '+' || expr.charAt(pos) == '-')) pos++;
|
||||
while (pos < len && Character.isDigit(expr.charAt(pos))) pos++;
|
||||
hasDecimal = true;
|
||||
}
|
||||
|
||||
ExpressionToken.TokenKind kind = ExpressionToken.TokenKind.INT_LITERAL;
|
||||
if (pos < len) {
|
||||
char suffix = expr.charAt(pos);
|
||||
if (suffix == 'f' || suffix == 'F') { kind = ExpressionToken.TokenKind.FLOAT_LITERAL; pos++; }
|
||||
else if (suffix == 'd' || suffix == 'D') { kind = ExpressionToken.TokenKind.DOUBLE_LITERAL; pos++; }
|
||||
else if (suffix == 'L' || suffix == 'l') { kind = ExpressionToken.TokenKind.LONG_LITERAL; pos++; }
|
||||
else if (hasDecimal) kind = ExpressionToken.TokenKind.DOUBLE_LITERAL;
|
||||
} else if (hasDecimal) kind = ExpressionToken.TokenKind.DOUBLE_LITERAL;
|
||||
|
||||
tokens.add(new ExpressionToken(kind, expr.substring(start, pos), start, pos));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Character.isJavaIdentifierStart(c)) {
|
||||
int start = pos;
|
||||
while (pos < len && Character.isJavaIdentifierPart(expr.charAt(pos))) pos++;
|
||||
tokens.add(ExpressionToken.identifier(expr.substring(start, pos), start, pos));
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = pos;
|
||||
String op = null;
|
||||
if (pos + 3 <= len) {
|
||||
String s3 = expr.substring(pos, pos + 3);
|
||||
if (">>>".equals(s3) || "<<=".equals(s3) || ">>=".equals(s3)) op = s3;
|
||||
}
|
||||
if (op == null && pos + 4 <= len) {
|
||||
String s4 = expr.substring(pos, pos + 4);
|
||||
if (">>>=".equals(s4)) op = s4;
|
||||
}
|
||||
if (op == null && pos + 2 <= len) {
|
||||
String s2 = expr.substring(pos, pos + 2);
|
||||
if ("++".equals(s2) || "--".equals(s2) || "+=".equals(s2) || "-=".equals(s2) ||
|
||||
"*=".equals(s2) || "/=".equals(s2) || "%=".equals(s2) || "&=".equals(s2) ||
|
||||
"|=".equals(s2) || "^=".equals(s2) || "==".equals(s2) || "!=".equals(s2) ||
|
||||
"<=".equals(s2) || ">=".equals(s2) || "&&".equals(s2) || "||".equals(s2) ||
|
||||
"<<".equals(s2) || ">>".equals(s2)) op = s2;
|
||||
}
|
||||
if (op == null) {
|
||||
switch (c) {
|
||||
case '+': case '-': case '*': case '/': case '%': case '&': case '|':
|
||||
case '^': case '~': case '!': case '<': case '>': case '=':
|
||||
op = String.valueOf(c); break;
|
||||
}
|
||||
}
|
||||
|
||||
if (op != null) {
|
||||
pos += op.length();
|
||||
tokens.add(ExpressionToken.operator(op, start, pos));
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case '(': tokens.add(new ExpressionToken(ExpressionToken.TokenKind.LEFT_PAREN, "(", pos, pos + 1)); break;
|
||||
case ')': tokens.add(new ExpressionToken(ExpressionToken.TokenKind.RIGHT_PAREN, ")", pos, pos + 1)); break;
|
||||
case '[': tokens.add(new ExpressionToken(ExpressionToken.TokenKind.LEFT_BRACKET, "[", pos, pos + 1)); break;
|
||||
case ']': tokens.add(new ExpressionToken(ExpressionToken.TokenKind.RIGHT_BRACKET, "]", pos, pos + 1)); break;
|
||||
case '.': tokens.add(new ExpressionToken(ExpressionToken.TokenKind.DOT, ".", pos, pos + 1)); break;
|
||||
case ',': tokens.add(new ExpressionToken(ExpressionToken.TokenKind.COMMA, ",", pos, pos + 1)); break;
|
||||
case '?': tokens.add(new ExpressionToken(ExpressionToken.TokenKind.QUESTION, "?", pos, pos + 1)); break;
|
||||
case ':': tokens.add(new ExpressionToken(ExpressionToken.TokenKind.COLON, ":", pos, pos + 1)); break;
|
||||
case ';': tokens.add(new ExpressionToken(ExpressionToken.TokenKind.SEMICOLON, ";", pos, pos + 1)); break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
tokens.add(new ExpressionToken(ExpressionToken.TokenKind.EOF, "", len, len));
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private static boolean isHexDigit(char c) {
|
||||
return Character.isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter.expression;
|
||||
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
|
||||
import java.util.List;
|
||||
|
||||
public class ExpressionTypeResolver {
|
||||
private final ExpressionNode.TypeResolverContext context;
|
||||
|
||||
public ExpressionTypeResolver(ExpressionNode.TypeResolverContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public TypeInfo resolve(String expression) {
|
||||
if (expression == null || expression.trim().isEmpty()) return null;
|
||||
|
||||
try {
|
||||
List<ExpressionToken> tokens = ExpressionTokenizer.tokenize(expression);
|
||||
if (tokens.isEmpty()) return null;
|
||||
|
||||
ExpressionParser parser = new ExpressionParser(tokens);
|
||||
ExpressionNode ast = parser.parse();
|
||||
if (ast == null) return null;
|
||||
|
||||
return resolveNodeType(ast);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean containsOperators(String expression) {
|
||||
if (expression == null) return false;
|
||||
for (int i = 0; i < expression.length(); i++) {
|
||||
char c = expression.charAt(i);
|
||||
switch (c) {
|
||||
case '+': case '-': case '*': case '/': case '%':
|
||||
case '&': case '|': case '^': case '~':
|
||||
case '<': case '>': case '=': case '!':
|
||||
case '?': case ':': return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private TypeInfo resolveNodeType(ExpressionNode node) {
|
||||
if (node == null) return null;
|
||||
|
||||
if (node instanceof ExpressionNode.IntLiteralNode) return TypeInfo.fromPrimitive("int");
|
||||
if (node instanceof ExpressionNode.LongLiteralNode) return TypeInfo.fromPrimitive("long");
|
||||
if (node instanceof ExpressionNode.FloatLiteralNode) return TypeInfo.fromPrimitive("float");
|
||||
if (node instanceof ExpressionNode.DoubleLiteralNode) return TypeInfo.fromPrimitive("double");
|
||||
if (node instanceof ExpressionNode.BooleanLiteralNode) return TypeInfo.fromPrimitive("boolean");
|
||||
if (node instanceof ExpressionNode.CharLiteralNode) return TypeInfo.fromPrimitive("char");
|
||||
if (node instanceof ExpressionNode.StringLiteralNode) return TypeInfo.fromClass(String.class);
|
||||
if (node instanceof ExpressionNode.NullLiteralNode) return null;
|
||||
|
||||
if (node instanceof ExpressionNode.IdentifierNode) {
|
||||
return context.resolveIdentifier(((ExpressionNode.IdentifierNode) node).getName());
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.MemberAccessNode) {
|
||||
ExpressionNode.MemberAccessNode ma = (ExpressionNode.MemberAccessNode) node;
|
||||
TypeInfo targetType = resolveNodeType(ma.getTarget());
|
||||
if (targetType == null || !targetType.isResolved()) return null;
|
||||
return context.resolveMemberAccess(targetType, ma.getMemberName());
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.MethodCallNode) {
|
||||
ExpressionNode.MethodCallNode mc = (ExpressionNode.MethodCallNode) node;
|
||||
TypeInfo targetType = mc.getTarget() == null ? context.resolveIdentifier("this") : resolveNodeType(mc.getTarget());
|
||||
if (targetType == null || !targetType.isResolved()) return null;
|
||||
TypeInfo[] argTypes = new TypeInfo[mc.getArguments().size()];
|
||||
for (int i = 0; i < argTypes.length; i++) argTypes[i] = resolveNodeType(mc.getArguments().get(i));
|
||||
return context.resolveMethodCall(targetType, mc.getMethodName(), argTypes);
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.ArrayAccessNode) {
|
||||
ExpressionNode.ArrayAccessNode aa = (ExpressionNode.ArrayAccessNode) node;
|
||||
TypeInfo arrayType = resolveNodeType(aa.getArray());
|
||||
if (arrayType == null || !arrayType.isResolved()) return null;
|
||||
String typeName = arrayType.getFullName();
|
||||
if (typeName.endsWith("[]")) {
|
||||
String elementTypeName = typeName.substring(0, typeName.length() - 2);
|
||||
return context.resolveTypeName(elementTypeName);
|
||||
}
|
||||
return context.resolveArrayAccess(arrayType);
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.NewNode) {
|
||||
return context.resolveTypeName(((ExpressionNode.NewNode) node).getTypeName());
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.BinaryOpNode) {
|
||||
ExpressionNode.BinaryOpNode bin = (ExpressionNode.BinaryOpNode) node;
|
||||
TypeInfo leftType = resolveNodeType(bin.getLeft());
|
||||
TypeInfo rightType = resolveNodeType(bin.getRight());
|
||||
return TypeRules.resolveBinaryOperatorType(bin.getOperator(), leftType, rightType);
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.UnaryOpNode) {
|
||||
ExpressionNode.UnaryOpNode un = (ExpressionNode.UnaryOpNode) node;
|
||||
TypeInfo operandType = resolveNodeType(un.getOperand());
|
||||
return TypeRules.resolveUnaryOperatorType(un.getOperator(), operandType);
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.TernaryNode) {
|
||||
ExpressionNode.TernaryNode tern = (ExpressionNode.TernaryNode) node;
|
||||
TypeInfo thenType = resolveNodeType(tern.getThenExpr());
|
||||
TypeInfo elseType = resolveNodeType(tern.getElseExpr());
|
||||
return TypeRules.resolveTernaryType(thenType, elseType);
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.CastNode) {
|
||||
return context.resolveTypeName(((ExpressionNode.CastNode) node).getTypeName());
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.InstanceofNode) {
|
||||
return TypeInfo.fromPrimitive("boolean");
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.AssignmentNode) {
|
||||
return resolveNodeType(((ExpressionNode.AssignmentNode) node).getTarget());
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.ParenthesizedNode) {
|
||||
return resolveNodeType(((ExpressionNode.ParenthesizedNode) node).getInner());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ExpressionNode.TypeResolverContext createBasicContext() {
|
||||
return new ExpressionNode.TypeResolverContext() {
|
||||
public TypeInfo resolveIdentifier(String name) {
|
||||
if ("true".equals(name) || "false".equals(name)) return TypeInfo.fromPrimitive("boolean");
|
||||
if ("null".equals(name)) return null;
|
||||
return null;
|
||||
}
|
||||
public TypeInfo resolveMemberAccess(TypeInfo targetType, String memberName) { return null; }
|
||||
public TypeInfo resolveMethodCall(TypeInfo targetType, String methodName, TypeInfo[] argTypes) { return null; }
|
||||
public TypeInfo resolveArrayAccess(TypeInfo arrayType) {
|
||||
String typeName = arrayType.getFullName();
|
||||
if (typeName.endsWith("[]")) {
|
||||
// For primitive arrays, we need to map back to primitive TypeInfo
|
||||
String elementType = typeName.substring(0, typeName.length() - 2);
|
||||
switch (elementType) {
|
||||
case "int": case "long": case "float": case "double":
|
||||
case "byte": case "short": case "char": case "boolean":
|
||||
return TypeInfo.fromPrimitive(elementType);
|
||||
default:
|
||||
return TypeInfo.unresolved(elementType, elementType);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public TypeInfo resolveTypeName(String typeName) {
|
||||
// Handle primitives
|
||||
switch (typeName) {
|
||||
case "int": case "long": case "float": case "double":
|
||||
case "byte": case "short": case "char": case "boolean": case "void":
|
||||
return TypeInfo.fromPrimitive(typeName);
|
||||
default:
|
||||
return TypeInfo.unresolved(typeName, typeName);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter.expression;
|
||||
|
||||
public enum OperatorType {
|
||||
|
||||
ADD("+", 11, Associativity.LEFT, Category.ARITHMETIC),
|
||||
SUBTRACT("-", 11, Associativity.LEFT, Category.ARITHMETIC),
|
||||
MULTIPLY("*", 12, Associativity.LEFT, Category.ARITHMETIC),
|
||||
DIVIDE("/", 12, Associativity.LEFT, Category.ARITHMETIC),
|
||||
MODULO("%", 12, Associativity.LEFT, Category.ARITHMETIC),
|
||||
|
||||
|
||||
ASSIGN("=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
ADD_ASSIGN("+=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
SUBTRACT_ASSIGN("-=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
MULTIPLY_ASSIGN("*=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
DIVIDE_ASSIGN("/=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
MODULO_ASSIGN("%=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
AND_ASSIGN("&=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
OR_ASSIGN("|=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
XOR_ASSIGN("^=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
LEFT_SHIFT_ASSIGN("<<=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
RIGHT_SHIFT_ASSIGN(">>=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
UNSIGNED_RIGHT_SHIFT_ASSIGN(">>>=", 1, Associativity.RIGHT, Category.ASSIGNMENT),
|
||||
|
||||
TERNARY_QUESTION("?", 2, Associativity.RIGHT, Category.TERNARY),
|
||||
TERNARY_COLON(":", 2, Associativity.RIGHT, Category.TERNARY),
|
||||
|
||||
LOGICAL_OR("||", 3, Associativity.LEFT, Category.LOGICAL),
|
||||
LOGICAL_AND("&&", 4, Associativity.LEFT, Category.LOGICAL),
|
||||
|
||||
BITWISE_OR("|", 5, Associativity.LEFT, Category.BITWISE),
|
||||
BITWISE_XOR("^", 6, Associativity.LEFT, Category.BITWISE),
|
||||
BITWISE_AND("&", 7, Associativity.LEFT, Category.BITWISE),
|
||||
|
||||
EQUALS("==", 8, Associativity.LEFT, Category.RELATIONAL),
|
||||
NOT_EQUALS("!=", 8, Associativity.LEFT, Category.RELATIONAL),
|
||||
LESS_THAN("<", 9, Associativity.LEFT, Category.RELATIONAL),
|
||||
GREATER_THAN(">", 9, Associativity.LEFT, Category.RELATIONAL),
|
||||
LESS_THAN_OR_EQUAL("<=", 9, Associativity.LEFT, Category.RELATIONAL),
|
||||
GREATER_THAN_OR_EQUAL(">=", 9, Associativity.LEFT, Category.RELATIONAL),
|
||||
|
||||
LEFT_SHIFT("<<", 10, Associativity.LEFT, Category.BITWISE),
|
||||
RIGHT_SHIFT(">>", 10, Associativity.LEFT, Category.BITWISE),
|
||||
UNSIGNED_RIGHT_SHIFT(">>>", 10, Associativity.LEFT, Category.BITWISE),
|
||||
|
||||
UNARY_PLUS("+", 14, Associativity.RIGHT, Category.UNARY),
|
||||
UNARY_MINUS("-", 14, Associativity.RIGHT, Category.UNARY),
|
||||
LOGICAL_NOT("!", 14, Associativity.RIGHT, Category.UNARY),
|
||||
BITWISE_NOT("~", 14, Associativity.RIGHT, Category.UNARY),
|
||||
PRE_INCREMENT("++", 14, Associativity.RIGHT, Category.UNARY),
|
||||
PRE_DECREMENT("--", 14, Associativity.RIGHT, Category.UNARY),
|
||||
POST_INCREMENT("++", 15, Associativity.LEFT, Category.UNARY_POSTFIX),
|
||||
POST_DECREMENT("--", 15, Associativity.LEFT, Category.UNARY_POSTFIX),
|
||||
|
||||
MEMBER_ACCESS(".", 16, Associativity.LEFT, Category.ACCESS),
|
||||
ARRAY_ACCESS("[]", 16, Associativity.LEFT, Category.ACCESS),
|
||||
METHOD_CALL("()", 16, Associativity.LEFT, Category.ACCESS),
|
||||
|
||||
CAST("(type)", 14, Associativity.RIGHT, Category.CAST),
|
||||
INSTANCEOF("instanceof", 9, Associativity.LEFT, Category.INSTANCEOF);
|
||||
|
||||
|
||||
private final String symbol;
|
||||
private final int precedence;
|
||||
private final Associativity associativity;
|
||||
private final Category category;
|
||||
|
||||
OperatorType(String symbol, int precedence, Associativity associativity, Category category) {
|
||||
this.symbol = symbol;
|
||||
this.precedence = precedence;
|
||||
this.associativity = associativity;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getSymbol() { return symbol; }
|
||||
public int getPrecedence() { return precedence; }
|
||||
public Associativity getAssociativity() { return associativity; }
|
||||
public Category getCategory() { return category; }
|
||||
|
||||
public boolean isBinary() {
|
||||
return category == Category.ARITHMETIC || category == Category.RELATIONAL ||
|
||||
category == Category.LOGICAL || category == Category.BITWISE;
|
||||
}
|
||||
|
||||
public boolean isUnary() {
|
||||
return category == Category.UNARY || category == Category.UNARY_POSTFIX;
|
||||
}
|
||||
|
||||
public boolean isAssignment() {
|
||||
return category == Category.ASSIGNMENT;
|
||||
}
|
||||
|
||||
public boolean isComparison() {
|
||||
return category == Category.RELATIONAL;
|
||||
}
|
||||
|
||||
public enum Associativity { LEFT, RIGHT }
|
||||
|
||||
public enum Category {
|
||||
ARITHMETIC, UNARY, UNARY_POSTFIX, RELATIONAL, LOGICAL, BITWISE,
|
||||
TERNARY, INSTANCEOF, ASSIGNMENT, CAST, ACCESS
|
||||
}
|
||||
|
||||
public static OperatorType fromSymbol(String symbol) {
|
||||
for (OperatorType op : values()) {
|
||||
if (op.symbol.equals(symbol)) return op;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static OperatorType fromBinarySymbol(String symbol) {
|
||||
switch (symbol) {
|
||||
case "+": return ADD;
|
||||
case "-": return SUBTRACT;
|
||||
case "*": return MULTIPLY;
|
||||
case "/": return DIVIDE;
|
||||
case "%": return MODULO;
|
||||
case "==": return EQUALS;
|
||||
case "!=": return NOT_EQUALS;
|
||||
case "<": return LESS_THAN;
|
||||
case ">": return GREATER_THAN;
|
||||
case "<=": return LESS_THAN_OR_EQUAL;
|
||||
case ">=": return GREATER_THAN_OR_EQUAL;
|
||||
case "&&": return LOGICAL_AND;
|
||||
case "||": return LOGICAL_OR;
|
||||
case "&": return BITWISE_AND;
|
||||
case "|": return BITWISE_OR;
|
||||
case "^": return BITWISE_XOR;
|
||||
case "<<": return LEFT_SHIFT;
|
||||
case ">>": return RIGHT_SHIFT;
|
||||
case ">>>": return UNSIGNED_RIGHT_SHIFT;
|
||||
case "=": return ASSIGN;
|
||||
case "+=": return ADD_ASSIGN;
|
||||
case "-=": return SUBTRACT_ASSIGN;
|
||||
case "*=": return MULTIPLY_ASSIGN;
|
||||
case "/=": return DIVIDE_ASSIGN;
|
||||
case "%=": return MODULO_ASSIGN;
|
||||
case "&=": return AND_ASSIGN;
|
||||
case "|=": return OR_ASSIGN;
|
||||
case "^=": return XOR_ASSIGN;
|
||||
case "<<=": return LEFT_SHIFT_ASSIGN;
|
||||
case ">>=": return RIGHT_SHIFT_ASSIGN;
|
||||
case ">>>=": return UNSIGNED_RIGHT_SHIFT_ASSIGN;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter.expression;
|
||||
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
|
||||
|
||||
public class TypeRules {
|
||||
|
||||
public static boolean isNumeric(TypeInfo type) {
|
||||
if (type == null || !type.isResolved()) return false;
|
||||
String name = type.getSimpleName();
|
||||
return "int".equals(name) || "long".equals(name) || "float".equals(name) ||
|
||||
"double".equals(name) || "byte".equals(name) || "short".equals(name) || "char".equals(name);
|
||||
}
|
||||
|
||||
public static boolean isIntegral(TypeInfo type) {
|
||||
if (type == null || !type.isResolved()) return false;
|
||||
String name = type.getSimpleName();
|
||||
return "int".equals(name) || "long".equals(name) || "byte".equals(name) ||
|
||||
"short".equals(name) || "char".equals(name);
|
||||
}
|
||||
|
||||
public static boolean isFloatingPoint(TypeInfo type) {
|
||||
if (type == null || !type.isResolved()) return false;
|
||||
String name = type.getSimpleName();
|
||||
return "float".equals(name) || "double".equals(name);
|
||||
}
|
||||
|
||||
public static boolean isBoolean(TypeInfo type) {
|
||||
if (type == null || !type.isResolved()) return false;
|
||||
return "boolean".equals(type.getSimpleName());
|
||||
}
|
||||
|
||||
public static boolean isString(TypeInfo type) {
|
||||
if (type == null || !type.isResolved()) return false;
|
||||
String name = type.getSimpleName();
|
||||
String fullName = type.getFullName();
|
||||
return "String".equals(name) || "java.lang.String".equals(fullName);
|
||||
}
|
||||
|
||||
public static TypeInfo binaryNumericPromotion(TypeInfo left, TypeInfo right) {
|
||||
if (left == null || right == null || !left.isResolved() || !right.isResolved()) {
|
||||
return null;
|
||||
}
|
||||
String l = left.getSimpleName();
|
||||
String r = right.getSimpleName();
|
||||
if ("double".equals(l) || "double".equals(r)) return TypeInfo.fromPrimitive("double");
|
||||
if ("float".equals(l) || "float".equals(r)) return TypeInfo.fromPrimitive("float");
|
||||
if ("long".equals(l) || "long".equals(r)) return TypeInfo.fromPrimitive("long");
|
||||
return TypeInfo.fromPrimitive("int");
|
||||
}
|
||||
|
||||
public static TypeInfo unaryNumericPromotion(TypeInfo type) {
|
||||
if (type == null || !type.isResolved()) return null;
|
||||
String name = type.getSimpleName();
|
||||
if ("double".equals(name) || "float".equals(name) || "long".equals(name)) return type;
|
||||
if ("byte".equals(name) || "short".equals(name) || "char".equals(name) || "int".equals(name)) {
|
||||
return TypeInfo.fromPrimitive("int");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static TypeInfo resolveBinaryOperatorType(OperatorType op, TypeInfo left, TypeInfo right) {
|
||||
if (op == null) return null;
|
||||
|
||||
switch (op.getCategory()) {
|
||||
case ARITHMETIC:
|
||||
if (op == OperatorType.ADD && (isString(left) || isString(right))) {
|
||||
return TypeInfo.fromClass(String.class);
|
||||
}
|
||||
if (isNumeric(left) && isNumeric(right)) {
|
||||
return binaryNumericPromotion(left, right);
|
||||
}
|
||||
return null;
|
||||
|
||||
case RELATIONAL:
|
||||
if (op == OperatorType.EQUALS || op == OperatorType.NOT_EQUALS) {
|
||||
return TypeInfo.fromPrimitive("boolean");
|
||||
}
|
||||
if (isNumeric(left) && isNumeric(right)) {
|
||||
return TypeInfo.fromPrimitive("boolean");
|
||||
}
|
||||
return null;
|
||||
|
||||
case LOGICAL:
|
||||
if (isBoolean(left) && isBoolean(right)) {
|
||||
return TypeInfo.fromPrimitive("boolean");
|
||||
}
|
||||
return null;
|
||||
|
||||
case BITWISE:
|
||||
if (op == OperatorType.LEFT_SHIFT || op == OperatorType.RIGHT_SHIFT ||
|
||||
op == OperatorType.UNSIGNED_RIGHT_SHIFT) {
|
||||
if (isIntegral(left)) {
|
||||
return unaryNumericPromotion(left);
|
||||
}
|
||||
}
|
||||
if (isIntegral(left) && isIntegral(right)) {
|
||||
return binaryNumericPromotion(left, right);
|
||||
}
|
||||
if (isBoolean(left) && isBoolean(right)) {
|
||||
return TypeInfo.fromPrimitive("boolean");
|
||||
}
|
||||
return null;
|
||||
|
||||
case ASSIGNMENT:
|
||||
return left;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static TypeInfo resolveUnaryOperatorType(OperatorType op, TypeInfo operand) {
|
||||
if (op == null || operand == null || !operand.isResolved()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case UNARY_PLUS:
|
||||
case UNARY_MINUS:
|
||||
if (isNumeric(operand)) return unaryNumericPromotion(operand);
|
||||
return null;
|
||||
|
||||
case BITWISE_NOT:
|
||||
if (isIntegral(operand)) return unaryNumericPromotion(operand);
|
||||
return null;
|
||||
|
||||
case LOGICAL_NOT:
|
||||
if (isBoolean(operand)) return TypeInfo.fromPrimitive("boolean");
|
||||
return null;
|
||||
|
||||
case PRE_INCREMENT:
|
||||
case PRE_DECREMENT:
|
||||
case POST_INCREMENT:
|
||||
case POST_DECREMENT:
|
||||
if (isNumeric(operand)) return operand;
|
||||
return null;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static TypeInfo resolveTernaryType(TypeInfo thenType, TypeInfo elseType) {
|
||||
if (thenType == null && elseType == null) return null;
|
||||
if (thenType == null) return elseType;
|
||||
if (elseType == null) return thenType;
|
||||
if (!thenType.isResolved()) return elseType.isResolved() ? elseType : null;
|
||||
if (!elseType.isResolved()) return thenType;
|
||||
|
||||
if (thenType.equals(elseType)) return thenType;
|
||||
if (isNumeric(thenType) && isNumeric(elseType)) {
|
||||
return binaryNumericPromotion(thenType, elseType);
|
||||
}
|
||||
|
||||
return thenType;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user