Code logic improvements

This commit is contained in:
bigguy345 committed 2025-12-24 19:10:37 +02:00
1 parent 457a8aae37
commit 9cb4c96eb3
9 files changed
+1773 -106

No files matched your search

@@ -1,8 +1,12 @@
package noppes.npcs.client.gui.util.script.interpreter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Represents a single import statement with all its metadata.
* Tracks the full import path, resolution status, and source positions.
* Tracks the full import path, resolution status, source positions, and references.
*/
public final class ImportData {
@@ -17,6 +21,12 @@ public final class ImportData {
private TypeInfo resolvedType; // null if wildcard or unresolved
private boolean resolved; // whether resolution was attempted and succeeded
// Track usage count (simpler than tracking individual tokens)
private int usageCount = 0;
// Track all tokens that reference this import (optional, for detailed analysis)
private final List<Token> referencingTokens = new ArrayList<>();
public ImportData(String fullPath, String simpleName, boolean isWildcard, boolean isStatic,
int startOffset, int endOffset, int pathStartOffset, int pathEndOffset) {
@@ -57,6 +67,62 @@ public final class ImportData {
public void markResolved(boolean resolved) {
this.resolved = resolved;
}
// ==================== REFERENCE TRACKING ====================
/**
* Increment the usage count for this import.
* Called during type resolution when this import is used.
*/
public void incrementUsage() {
usageCount++;
}
/**
* Add a token that references this import.
* Called when a token is created that uses this import's type.
*/
public void addReference(Token token) {
if (token != null && !referencingTokens.contains(token)) {
referencingTokens.add(token);
}
}
/**
* Get all tokens that reference this import.
*/
public List<Token> getReferencingTokens() {
return Collections.unmodifiableList(referencingTokens);
}
/**
* Check if this import is used (has any references or usages).
*/
public boolean isUsed() {
return usageCount > 0 || !referencingTokens.isEmpty();
}
/**
* Get the usage count for this import.
*/
public int getUsageCount() {
return usageCount;
}
/**
* Get the number of references to this import.
*/
public int getReferenceCount() {
return referencingTokens.size();
}
/**
* Clear all references and reset usage count (used when re-tokenizing).
*/
public void clearReferences() {
referencingTokens.clear();
usageCount = 0;
}
/**
* Get the package portion of the import path.
@@ -0,0 +1,395 @@
package noppes.npcs.client.gui.util.script.interpreter;
import scala.annotation.meta.param;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Stores information about a method call for argument validation.
* This includes the method name, the arguments passed, and validation results.
*/
public class MethodCallInfo {
/**
* Represents a single argument in a method call.
*/
public static class Argument {
private final String text; // The text of the argument expression
private final int startOffset; // Start position in source
private final int endOffset; // End position in source
private final TypeInfo resolvedType; // The resolved type of the argument (null if unresolved)
private final boolean valid; // Whether this arg matches the expected parameter type
private final String errorMessage; // Error message if invalid
public Argument(String text, int startOffset, int endOffset, TypeInfo resolvedType,
boolean valid, String errorMessage) {
this.text = text;
this.startOffset = startOffset;
this.endOffset = endOffset;
this.resolvedType = resolvedType;
this.valid = valid;
this.errorMessage = errorMessage;
}
public String getText() {
return text;
}
public int getStartOffset() {
return startOffset;
}
public int getEndOffset() {
return endOffset;
}
public TypeInfo getResolvedType() {
return resolvedType;
}
public boolean isValid() {
return valid;
}
public String getErrorMessage() {
return errorMessage;
}
public boolean equals(Token t) {
return text.equals(t.getText()) &&
startOffset == t.getGlobalStart() &&
endOffset == t.getGlobalEnd();
}
@Override
public String toString() {
return "Arg{" + text + " [" + startOffset + "-" + endOffset + "]" +
(resolvedType != null ? " :" + resolvedType.getSimpleName() : "") +
(valid ? "" : " INVALID: " + errorMessage) + "}";
}
}
/**
* Validation error type.
*/
public enum ErrorType {
NONE,
WRONG_ARG_COUNT, // Number of args doesn't match any overload
WRONG_ARG_TYPE, // Specific argument has wrong type
STATIC_ACCESS_ERROR, // Trying to call instance method statically or vice versa
UNRESOLVED_METHOD, // Method doesn't exist
UNRESOLVED_RECEIVER // Can't resolve the receiver type
}
private final String methodName;
private final int methodNameStart; // Position of method name
private final int methodNameEnd;
private final int openParenOffset; // Position of '('
private final int closeParenOffset; // Position of ')'
private final List<Argument> arguments;
private final TypeInfo receiverType; // The type on which this method is called (null for standalone)
private final MethodInfo resolvedMethod; // The resolved method (null if unresolved)
private final boolean isStaticAccess; // True if this is Class.method() style access
private ErrorType errorType = ErrorType.NONE;
private String errorMessage;
private int errorArgIndex = -1; // Index of the problematic argument (for WRONG_ARG_TYPE)
private List<ArgumentTypeError> argumentTypeErrors = new ArrayList<>();
public MethodCallInfo(String methodName, int methodNameStart, int methodNameEnd,
int openParenOffset, int closeParenOffset,
List<Argument> arguments, TypeInfo receiverType,
MethodInfo resolvedMethod) {
this(methodName, methodNameStart, methodNameEnd, openParenOffset, closeParenOffset,
arguments, receiverType, resolvedMethod, false);
}
public MethodCallInfo(String methodName, int methodNameStart, int methodNameEnd,
int openParenOffset, int closeParenOffset,
List<Argument> arguments, TypeInfo receiverType,
MethodInfo resolvedMethod, boolean isStaticAccess) {
this.methodName = methodName;
this.methodNameStart = methodNameStart;
this.methodNameEnd = methodNameEnd;
this.openParenOffset = openParenOffset;
this.closeParenOffset = closeParenOffset;
this.arguments = arguments != null ? new ArrayList<>(arguments) : new ArrayList<>();
this.receiverType = receiverType;
this.resolvedMethod = resolvedMethod;
this.isStaticAccess = isStaticAccess;
}
// Getters
public String getMethodName() {
return methodName;
}
public int getMethodNameStart() {
return methodNameStart;
}
public int getMethodNameEnd() {
return methodNameEnd;
}
public int getOpenParenOffset() {
return openParenOffset;
}
public int getCloseParenOffset() {
return closeParenOffset;
}
public List<Argument> getArguments() {
return Collections.unmodifiableList(arguments);
}
public int getArgumentCount() {
return arguments.size();
}
public TypeInfo getReceiverType() {
return receiverType;
}
public MethodInfo getResolvedMethod() {
return resolvedMethod;
}
public boolean isStaticAccess() {
return isStaticAccess;
}
public ErrorType getErrorType() {
return errorType;
}
public String getErrorMessage() {
return errorMessage;
}
/**
* Get the full span of the method call including parentheses.
* Used for underlining the entire call on arg count errors.
*/
public int getFullCallStart() {
return methodNameStart;
}
public int getFullCallEnd() {
return closeParenOffset + 1;
}
/**
* Check if this method call has any validation errors.
*/
public boolean hasError() {
return errorType != ErrorType.NONE;
}
/**
* Check if this is an arg count error (underline whole call).
*/
public boolean hasArgCountError() {
return errorType == ErrorType.WRONG_ARG_COUNT;
}
/**
* Check if this is an arg type error (underline specific arg).
*/
public boolean hasArgTypeError() {
return !this.argumentTypeErrors.isEmpty();
}
public boolean hasArgTypeError(Token t) {
for (ArgumentTypeError ate : this.argumentTypeErrors) {
if (ate.getArg().equals(t)) {
return true;
}
}
return false;
}
/**
* Check if this is a static access error (underline method name).
*/
public boolean hasStaticAccessError() {
return errorType == ErrorType.STATIC_ACCESS_ERROR;
}
// Setters for validation results
public void setError(ErrorType type, String message) {
this.errorType = type;
this.errorMessage = message;
}
public void setArgTypeError(int argIndex, String message) {
this.argumentTypeErrors.add(new ArgumentTypeError(arguments.get(argIndex), argIndex, message));
}
public class ArgumentTypeError {
private ErrorType type = ErrorType.WRONG_ARG_TYPE;
private final Argument arg;
private final int argIndex;
private final String message;
public ArgumentTypeError(Argument arg, int argIndex, String message) {
this.arg = arg;
this.argIndex = argIndex;
this.message = message;
}
public int getArgIndex() {
return argIndex;
}
public String getMessage() {
return message;
}
public Argument getArg() {
return arg;
}
}
/**
* Validate this method call against the resolved method signature.
* Sets error information if validation fails.
*/
public void validate() {
if (resolvedMethod == null) {
setError(ErrorType.UNRESOLVED_METHOD, "Cannot resolve method '" + methodName + "'");
return;
}
// Check static/instance access
if (isStaticAccess && !resolvedMethod.isStatic()) {
setError(ErrorType.STATIC_ACCESS_ERROR,
"Cannot call instance method '" + methodName + "' on a class type");
return;
}
List<FieldInfo> params = resolvedMethod.getParameters();
int expectedCount = params.size();
int actualCount = arguments.size();
// Check arg count
if (actualCount != expectedCount) {
setError(ErrorType.WRONG_ARG_COUNT,
"Expected " + expectedCount + " argument(s) but got " + actualCount);
return;
}
// Check each argument type
for (int i = 0; i < actualCount; i++) {
Argument arg = arguments.get(i);
FieldInfo para = params.get(i);
TypeInfo argType = arg.getResolvedType();
TypeInfo paramType = para.getDeclaredType();
if (argType != null && paramType != null) {
if (!isTypeCompatible(argType, paramType)) {
setArgTypeError(i, "Expected " + paramType.getSimpleName() +
" but got " + argType.getSimpleName());
return;
}
} else if (paramType == null) {
setArgTypeError(i, "Parameter type of '" + para.getName() + "' is unresolved");
return;
} else if (argType == null) {
setArgTypeError(i, "Cannot resolve type of argument '" + arg.getText() + "'");
return;
}
}
}
/**
* Check if sourceType can be assigned to targetType.
* Handles primitive widening, inheritance, etc.
*/
private boolean isTypeCompatible(TypeInfo sourceType, TypeInfo targetType) {
if (sourceType == null || targetType == null) {
return true; // Can't verify, assume compatible
}
if (sourceType.equals(targetType)) {
return true;
}
Class<?> sourceClass = sourceType.getJavaClass();
Class<?> targetClass = targetType.getJavaClass();
if (sourceClass == null || targetClass == null) {
return true; // Can't verify, assume compatible
}
// Check inheritance
if (targetClass.isAssignableFrom(sourceClass)) {
return true;
}
// Check primitive widening conversions
if (isPrimitiveWidening(sourceClass, targetClass)) {
return true;
}
return false;
}
/**
* Check if widening conversion is valid between primitives.
*/
private boolean isPrimitiveWidening(Class<?> source, Class<?> target) {
// byte -> short -> int -> long -> float -> double
// char -> int -> long -> float -> double
if (target == double.class || target == Double.class) {
return source == float.class || source == Float.class ||
source == long.class || source == Long.class ||
source == int.class || source == Integer.class ||
source == short.class || source == Short.class ||
source == byte.class || source == Byte.class ||
source == char.class || source == Character.class;
}
if (target == float.class || target == Float.class) {
return source == long.class || source == Long.class ||
source == int.class || source == Integer.class ||
source == short.class || source == Short.class ||
source == byte.class || source == Byte.class ||
source == char.class || source == Character.class;
}
if (target == long.class || target == Long.class) {
return source == int.class || source == Integer.class ||
source == short.class || source == Short.class ||
source == byte.class || source == Byte.class ||
source == char.class || source == Character.class;
}
if (target == int.class || target == Integer.class) {
return source == short.class || source == Short.class ||
source == byte.class || source == Byte.class ||
source == char.class || source == Character.class;
}
if (target == short.class || target == Short.class) {
return source == byte.class || source == Byte.class;
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("MethodCallInfo{");
sb.append(methodName).append("(");
for (int i = 0; i < arguments.size(); i++) {
if (i > 0)
sb.append(", ");
sb.append(arguments.get(i).getText());
}
sb.append(")");
if (hasError()) {
sb.append(" ERROR: ").append(errorType).append(" - ").append(errorMessage);
}
sb.append("}");
return sb.toString();
}
}
@@ -1,5 +1,6 @@
package noppes.npcs.client.gui.util.script.interpreter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -19,10 +20,12 @@ public final class MethodInfo {
private final int bodyEnd; // End of method body (before })
private final boolean resolved;
private final boolean isDeclaration; // true if this is a declaration, false if it's a call
private final boolean isStatic; // true if this is a static method
private MethodInfo(String name, TypeInfo returnType, TypeInfo containingType,
List<FieldInfo> parameters, int declarationOffset,
int bodyStart, int bodyEnd, boolean resolved, boolean isDeclaration) {
int bodyStart, int bodyEnd, boolean resolved, boolean isDeclaration,
boolean isStatic) {
this.name = name;
this.returnType = returnType;
this.containingType = containingType;
@@ -32,12 +35,18 @@ public final class MethodInfo {
this.bodyEnd = bodyEnd;
this.resolved = resolved;
this.isDeclaration = isDeclaration;
this.isStatic = isStatic;
}
// Factory methods
public static MethodInfo declaration(String name, TypeInfo returnType, List<FieldInfo> params,
int declOffset, int bodyStart, int bodyEnd) {
return new MethodInfo(name, returnType, null, params, declOffset, bodyStart, bodyEnd, true, true);
return new MethodInfo(name, returnType, null, params, declOffset, bodyStart, bodyEnd, true, true, false);
}
public static MethodInfo declaration(String name, TypeInfo returnType, List<FieldInfo> params,
int declOffset, int bodyStart, int bodyEnd, boolean isStatic) {
return new MethodInfo(name, returnType, null, params, declOffset, bodyStart, bodyEnd, true, true, isStatic);
}
public static MethodInfo call(String name, TypeInfo containingType, int paramCount) {
@@ -47,7 +56,7 @@ public final class MethodInfo {
for (int i = 0; i < paramCount; i++) {
params.add(FieldInfo.unresolved("arg" + i, FieldInfo.Scope.PARAMETER));
}
return new MethodInfo(name, null, containingType, params, -1, -1, -1, resolved, false);
return new MethodInfo(name, null, containingType, params, -1, -1, -1, resolved, false, false);
}
public static MethodInfo unresolvedCall(String name, int paramCount) {
@@ -55,7 +64,7 @@ public final class MethodInfo {
for (int i = 0; i < paramCount; i++) {
params.add(FieldInfo.unresolved("arg" + i, FieldInfo.Scope.PARAMETER));
}
return new MethodInfo(name, null, null, params, -1, -1, -1, false, false);
return new MethodInfo(name, null, null, params, -1, -1, -1, false, false, false);
}
/**
@@ -65,6 +74,7 @@ public final class MethodInfo {
public static MethodInfo fromReflection(java.lang.reflect.Method method, TypeInfo containingType) {
String name = method.getName();
TypeInfo returnType = TypeInfo.fromClass(method.getReturnType());
boolean isStatic = Modifier.isStatic(method.getModifiers());
List<FieldInfo> params = new ArrayList<>();
Class<?>[] paramTypes = method.getParameterTypes();
@@ -73,7 +83,7 @@ public final class MethodInfo {
params.add(FieldInfo.reflectionParam("arg" + i, paramType));
}
return new MethodInfo(name, returnType, containingType, params, -1, -1, -1, true, false);
return new MethodInfo(name, returnType, containingType, params, -1, -1, -1, true, false, isStatic);
}
// Getters
@@ -88,6 +98,7 @@ public final class MethodInfo {
public boolean isResolved() { return resolved; }
public boolean isDeclaration() { return isDeclaration; }
public boolean isCall() { return !isDeclaration; }
public boolean isStatic() { return isStatic; }
/**
* Check if a position is inside this method's body.
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,8 @@
package noppes.npcs.client.gui.util.script.interpreter;
import net.minecraft.client.gui.Gui;
import noppes.npcs.client.ClientProxy;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.Collections;
@@ -146,6 +148,13 @@ public class ScriptLine {
token.setFieldInfo((FieldInfo) mark.metadata);
} else if (mark.metadata instanceof MethodInfo) {
token.setMethodInfo((MethodInfo) mark.metadata);
} else if (mark.metadata instanceof MethodCallInfo) {
MethodCallInfo callInfo = (MethodCallInfo) mark.metadata;
token.setMethodCallInfo(callInfo);
if (callInfo.getErrorType() != MethodCallInfo.ErrorType.NONE) {
// For other errors (arg count, static access), underline the method name
token.setUnderline(true, 0xFF5555); // Red wavy underline
}
} else if (mark.metadata instanceof ImportData) {
token.setImportData((ImportData) mark.metadata);
}
@@ -190,19 +199,41 @@ public class ScriptLine {
/**
* Draw this line with syntax highlighting using Minecraft color codes.
* Compatible with the existing rendering system.
* Also draws curly underlines for tokens with errors (method call validation failures).
*/
public void drawString(int x, int y, int defaultColor) {
StringBuilder builder = new StringBuilder();
int lastIndex = 0;
// Track positions for underlines
List<int[]> underlines = new ArrayList<>(); // [startX, endX, color]
int currentX = x;
for (Token t : tokens) {
int tokenStart = t.getGlobalStart() - globalStart; // relative position in line
// Append any text before this token (spaces, punctuation, etc.)
// Calculate gap width before this token
if (tokenStart > lastIndex && tokenStart <= text.length()) {
builder.append(text, lastIndex, tokenStart);
String gapText = text.substring(lastIndex, tokenStart);
builder.append(gapText);
currentX += ClientProxy.Font.width(gapText);
}
// Track underline position if token has one
if (t.hasUnderline()) {
int tokenWidth = ClientProxy.Font.width(t.getText());
underlines.add(new int[]{currentX, currentX + tokenWidth, t.getUnderlineColor()});
}
if (t.getMethodCallInfo() != null) {
MethodCallInfo callInfo = t.getMethodCallInfo();
if (callInfo.hasArgTypeError(t)) {
underlines.add(
new int[]{currentX, currentX + ClientProxy.Font.width(t.getText()), t.getUnderlineColor()});
}
}
// Append the colored token
builder.append(COLOR_CHAR)
.append(t.getColorCode())
@@ -210,6 +241,7 @@ public class ScriptLine {
.append(COLOR_CHAR)
.append('f'); // Reset to white
currentX += ClientProxy.Font.width(t.getText());
lastIndex = tokenStart + t.getText().length();
}
@@ -218,16 +250,67 @@ public class ScriptLine {
builder.append(text.substring(lastIndex));
}
// Draw the text
ClientProxy.Font.drawString(builder.toString(), x, y, defaultColor);
// Draw curly underlines for error tokens
for (int[] ul : underlines) {
drawCurlyUnderline(ul[0], y + ClientProxy.Font.height() - 1, ul[1] - ul[0], ul[2]);
}
}
/**
* Draw a curly/wavy underline (like IDE error highlighting).
* @param x Start X position
* @param y Y position (bottom of text)
* @param width Width of the underline
* @param color Color in ARGB format (e.g., 0xFFFF5555 for red)
*/
private void drawCurlyUnderline(int x, int y, int width, int color) {
if (width <= 0)
return;
float a = ((color >> 24) & 0xFF) / 255f;
float r = ((color >> 16) & 0xFF) / 255f;
float g = ((color >> 8) & 0xFF) / 255f;
float b = (color & 0xFF) / 255f;
// If alpha is 0, assume full opacity
if (a == 0)
a = 1.0f;
GL11.glPushMatrix();
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glColor4f(r, g, b, a);
GL11.glLineWidth(1.0f);
GL11.glBegin(GL11.GL_LINE_STRIP);
// Wave parameters: 2 pixels amplitude, 4 pixels wavelength
int waveHeight = 2;
int waveLength = 4;
for (int i = 0; i <= width; i++) {
// Create a sine-like wave pattern
double phase = (double) i / waveLength * Math.PI * 2;
int yOffset = (int) (Math.sin(phase) * waveHeight);
GL11.glVertex2f(x + i, y + yOffset);
}
GL11.glEnd();
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glDisable(GL11.GL_BLEND);
GL11.glPopMatrix();
}
/**
* Draw this line with hex colors instead of Minecraft color codes.
* More flexible but requires custom font rendering.
* Also draws wavy underlines for tokens with errors.
*
* @param x X position
* @param y Y position
* @param renderer A functional interface for drawing colored text segments
* @param renderer A renderer for drawing colored text and error underlines
*/
public void drawStringHex(int x, int y, HexColorRenderer renderer) {
int currentX = x;
@@ -239,31 +322,85 @@ public class ScriptLine {
// Draw any text before this token in default color
if (tokenStart > lastIndex && tokenStart <= text.length()) {
String gap = text.substring(lastIndex, tokenStart);
currentX = renderer.draw(gap, currentX, y, 0xFFFFFF);
currentX = renderer.draw(gap, currentX, y, 0xFFFFFF, false, 0);
}
// Draw the colored token
currentX = renderer.draw(t.getText(), currentX, y, t.getHexColor());
// Draw the colored token (with underline if flagged)
currentX = renderer.draw(t.getText(), currentX, y, t.getHexColor(), t.hasUnderline(),
t.getUnderlineColor());
lastIndex = tokenStart + t.getText().length();
}
// Draw any remaining text in default color
if (lastIndex < text.length()) {
renderer.draw(text.substring(lastIndex), currentX, y, 0xFFFFFF);
renderer.draw(text.substring(lastIndex), currentX, y, 0xFFFFFF, false, 0);
}
}
/**
* Functional interface for rendering text with hex colors.
* Functional interface for rendering text with hex colors and optional underlines.
*/
@FunctionalInterface
public interface HexColorRenderer {
/**
* Draw text at the specified position with the given color.
* @param text The text to draw
* @param x X position
* @param y Y position
* @param hexColor The text color in hex format
* @param underline Whether to draw an underline (wavy for errors)
* @param underlineColor The underline color (if underline is true)
* @return The X position after drawing (for continuation)
*/
int draw(String text, int x, int y, int hexColor);
int draw(String text, int x, int y, int hexColor, boolean underline, int underlineColor);
}
/**
* Creates a default HexColorRenderer implementation using ClientProxy.Font.
* This renderer draws text with hex colors and curly underlines for errors.
* @return A HexColorRenderer that can be passed to drawStringHex
*/
public static HexColorRenderer createDefaultHexRenderer() {
return (text, x, y, hexColor, underline, underlineColor) -> {
// Draw the text with hex color
// Minecraft's font renderer uses ARGB, so add full alpha if not present
int color = (hexColor & 0xFF000000) == 0 ? (0xFF000000 | hexColor) : hexColor;
ClientProxy.Font.drawString(text, x, y, color);
int textWidth = ClientProxy.Font.width(text);
// Draw curly underline if needed
if (underline && textWidth > 0) {
int ulColor = (underlineColor & 0xFF000000) == 0 ? (0xFF000000 | underlineColor) : underlineColor;
float a = ((ulColor >> 24) & 0xFF) / 255f;
float r = ((ulColor >> 16) & 0xFF) / 255f;
float g = ((ulColor >> 8) & 0xFF) / 255f;
float b = (ulColor & 0xFF) / 255f;
GL11.glPushMatrix();
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glColor4f(r, g, b, a);
GL11.glLineWidth(1.0f);
int underlineY = y + ClientProxy.Font.height() - 1;
GL11.glBegin(GL11.GL_LINE_STRIP);
for (int i = 0; i <= textWidth; i++) {
double phase = (double) i / 4 * Math.PI * 2;
int yOffset = (int) (Math.sin(phase) * 2);
GL11.glVertex2f(x + i, underlineY + yOffset);
}
GL11.glEnd();
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glDisable(GL11.GL_BLEND);
GL11.glPopMatrix();
}
return x + textWidth;
};
}
// ==================== UTILITIES ====================
@@ -0,0 +1,187 @@
package noppes.npcs.client.gui.util.script.interpreter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Represents a type defined within the script itself (not from Java classpath).
* This handles classes, interfaces, and enums declared in the user's script.
*
* Unlike TypeInfo which wraps a Java Class<?>, ScriptTypeInfo stores all metadata
* about fields, methods, and other type information parsed from the script.
*/
public class ScriptTypeInfo extends TypeInfo {
private final String scriptClassName;
private final int declarationOffset;
private final int bodyStart;
private final int bodyEnd;
// Script-defined members
private final Map<String, FieldInfo> fields = new HashMap<>();
private final Map<String, List<MethodInfo>> methods = new HashMap<>(); // name -> list of overloads
private final List<ScriptTypeInfo> innerClasses = new ArrayList<>();
// Parent class reference (for inner class resolution)
private ScriptTypeInfo outerClass;
private ScriptTypeInfo(String simpleName, String fullName, Kind kind,
int declarationOffset, int bodyStart, int bodyEnd) {
super(simpleName, fullName, "", kind, null, true, null, true);
this.scriptClassName = simpleName;
this.declarationOffset = declarationOffset;
this.bodyStart = bodyStart;
this.bodyEnd = bodyEnd;
}
// Factory method
public static ScriptTypeInfo create(String simpleName, Kind kind,
int declarationOffset, int bodyStart, int bodyEnd) {
return new ScriptTypeInfo(simpleName, simpleName, kind, declarationOffset, bodyStart, bodyEnd);
}
public static ScriptTypeInfo createInner(String simpleName, Kind kind, ScriptTypeInfo outer,
int declarationOffset, int bodyStart, int bodyEnd) {
String fullName = outer.getFullName() + "$" + simpleName;
ScriptTypeInfo inner = new ScriptTypeInfo(simpleName, fullName, kind, declarationOffset, bodyStart, bodyEnd);
inner.outerClass = outer;
outer.innerClasses.add(inner);
return inner;
}
// Getters
public String getScriptClassName() { return scriptClassName; }
public int getDeclarationOffset() { return declarationOffset; }
public int getBodyStart() { return bodyStart; }
public int getBodyEnd() { return bodyEnd; }
public ScriptTypeInfo getOuterClass() { return outerClass; }
public List<ScriptTypeInfo> getInnerClasses() { return innerClasses; }
/**
* Check if a position is inside this type's body.
*/
public boolean containsPosition(int position) {
return position >= bodyStart && position < bodyEnd;
}
// ==================== FIELD MANAGEMENT ====================
public void addField(FieldInfo field) {
fields.put(field.getName(), field);
}
@Override
public boolean hasField(String fieldName) {
return fields.containsKey(fieldName);
}
@Override
public FieldInfo getFieldInfo(String fieldName) {
return fields.get(fieldName);
}
public Map<String, FieldInfo> getFields() {
return new HashMap<>(fields);
}
// ==================== METHOD MANAGEMENT ====================
public void addMethod(MethodInfo method) {
methods.computeIfAbsent(method.getName(), k -> new ArrayList<>()).add(method);
}
@Override
public boolean hasMethod(String methodName) {
return methods.containsKey(methodName);
}
@Override
public boolean hasMethod(String methodName, int paramCount) {
List<MethodInfo> overloads = methods.get(methodName);
if (overloads == null) return false;
for (MethodInfo m : overloads) {
if (m.getParameterCount() == paramCount) {
return true;
}
}
return false;
}
@Override
public MethodInfo getMethodInfo(String methodName) {
List<MethodInfo> overloads = methods.get(methodName);
return (overloads != null && !overloads.isEmpty()) ? overloads.get(0) : null;
}
/**
* Get all method overloads with the given name.
*/
public List<MethodInfo> getMethodOverloads(String methodName) {
return methods.getOrDefault(methodName, new ArrayList<>());
}
/**
* Get a method with specific parameter count.
*/
public MethodInfo getMethodWithParamCount(String methodName, int paramCount) {
List<MethodInfo> overloads = methods.get(methodName);
if (overloads == null) return null;
for (MethodInfo m : overloads) {
if (m.getParameterCount() == paramCount) {
return m;
}
}
return null;
}
public Map<String, List<MethodInfo>> getMethods() {
return new HashMap<>(methods);
}
// ==================== INNER CLASS LOOKUP ====================
/**
* Find an inner class by name.
*/
public ScriptTypeInfo getInnerClass(String name) {
for (ScriptTypeInfo inner : innerClasses) {
if (inner.getSimpleName().equals(name)) {
return inner;
}
}
return null;
}
@Override
public TokenType getTokenType() {
switch (getKind()) {
case INTERFACE:
return TokenType.INTERFACE_DECL;
case ENUM:
return TokenType.ENUM_DECL;
case CLASS:
default:
return TokenType.CLASS_DECL;
}
}
// Script types are always considered resolved since they're defined in the script
@Override
public boolean isResolved() {
return true;
}
// Script types don't have a Java class
@Override
public Class<?> getJavaClass() {
return null;
}
@Override
public String toString() {
return "ScriptTypeInfo{" + scriptClassName + ", " + getKind() +
", fields=" + fields.size() + ", methods=" + methods.size() + "}";
}
}
@@ -21,7 +21,12 @@ public class Token {
private TypeInfo typeInfo; // For class references, type declarations
private FieldInfo fieldInfo; // For field references
private MethodInfo methodInfo; // For method calls/declarations
private MethodCallInfo methodCallInfo; // For method calls with argument info
private ImportData importData; // For import statements
// Rendering flags
private boolean hasUnderline; // True if this token should be underlined (for errors)
private int underlineColor; // Color of the underline (if any)
// Navigation - set by ScriptLine
private Token prev;
@@ -111,8 +116,11 @@ public class Token {
public TypeInfo getTypeInfo() { return typeInfo; }
public FieldInfo getFieldInfo() { return fieldInfo; }
public MethodInfo getMethodInfo() { return methodInfo; }
public MethodCallInfo getMethodCallInfo() { return methodCallInfo; }
public ImportData getImportData() { return importData; }
public ScriptLine getParentLine() { return parentLine; }
public boolean hasUnderline() { return hasUnderline; }
public int getUnderlineColor() { return underlineColor; }
// ==================== SETTERS ====================
@@ -120,7 +128,13 @@ public class Token {
public void setTypeInfo(TypeInfo info) { this.typeInfo = info; }
public void setFieldInfo(FieldInfo info) { this.fieldInfo = info; }
public void setMethodInfo(MethodInfo info) { this.methodInfo = info; }
public void setMethodCallInfo(MethodCallInfo info) { this.methodCallInfo = info; }
public void setImportData(ImportData data) { this.importData = data; }
public void setUnderline(boolean hasUnderline, int color) {
this.hasUnderline = hasUnderline;
this.underlineColor = color;
}
void setParentLine(ScriptLine line) { this.parentLine = line; }
void setPrev(Token prev) { this.prev = prev; }
@@ -23,10 +23,12 @@ public enum TokenType {
CLASS_DECL(0x00AAAA, 85), // class names in declarations
IMPORTED_CLASS(0x00AAAA, 75), // imported class usages
TYPE_DECL(0x00AAAA, 70), // package paths, type references
UNUSED_IMPORT(0x666666, 65), // unused import statements (gray)
// Methods
METHOD_DECL(0x00AA00, 60), // method declarations (green)
METHOD_CALL(0x55FF55, 50), // method calls (bright green)
METHOD_CALL_ERROR(0xFF5555, 51), // method call with validation error (red underline)
// Variables and fields
UNDEFINED_VAR(0xAA0000, 20), // unresolved variables (dark red) - high priority
@@ -65,11 +67,13 @@ public enum TokenType {
switch (this) {
case COMMENT:
case NUMBER:
case UNUSED_IMPORT:
return '7'; // gray
case STRING:
return '5'; // purple
case CLASS_KEYWORD:
case KEYWORD:
case METHOD_CALL_ERROR:
return 'c'; // red
case IMPORT_KEYWORD:
case MODIFIER:
@@ -2,9 +2,9 @@ package noppes.npcs.client.gui.util.script.interpreter;
/**
* Represents resolved type information for a class/interface/enum.
* Immutable data class holding all type metadata.
* Base class for type metadata. Extended by ScriptTypeInfo for script-defined types.
*/
public final class TypeInfo {
public class TypeInfo {
public enum Kind {
CLASS,
@@ -31,6 +31,19 @@ public final class TypeInfo {
this.resolved = resolved;
this.enclosingType = enclosingType;
}
// Protected constructor for subclasses (like ScriptTypeInfo)
protected TypeInfo(String simpleName, String fullName, String packageName,
Kind kind, Class<?> javaClass, boolean resolved, TypeInfo enclosingType,
@SuppressWarnings("unused") boolean subclass) {
this.simpleName = simpleName;
this.fullName = fullName;
this.packageName = packageName;
this.kind = kind;
this.javaClass = javaClass;
this.resolved = resolved;
this.enclosingType = enclosingType;
}
// Factory methods
public static TypeInfo resolved(String simpleName, String fullName, String packageName,
@@ -74,6 +87,25 @@ public final class TypeInfo {
return new TypeInfo(simpleName, fullName, packageName, kind, clazz, true, enclosing);
}
/**
* Create a TypeInfo for a primitive type.
*/
public static TypeInfo forPrimitive(String typeName) {
Class<?> primitiveClass = null;
switch (typeName) {
case "boolean": primitiveClass = boolean.class; break;
case "byte": primitiveClass = byte.class; break;
case "char": primitiveClass = char.class; break;
case "short": primitiveClass = short.class; break;
case "int": primitiveClass = int.class; break;
case "long": primitiveClass = long.class; break;
case "float": primitiveClass = float.class; break;
case "double": primitiveClass = double.class; break;
case "void": primitiveClass = void.class; break;
}
return new TypeInfo(typeName, typeName, "", Kind.CLASS, primitiveClass, true, null);
}
// Getters
public String getSimpleName() { return simpleName; }
public String getFullName() { return fullName; }