Expression resolving/type resolution framework with operator parsing

This commit is contained in:
bigguy345 committed 2025-12-28 22:36:46 +02:00
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.
*/
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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');
}
}
@@ -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);
}
}
};
}
}
@@ -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;
}
}
}
@@ -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;
}
}