From efd7d96ec7cc799814ca6b97443ce85bfaacf81a Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Mon, 29 Dec 2025 16:46:03 +0200 Subject: [PATCH] Casting completely functional --- .../script/interpreter/ScriptDocument.java | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java index fe9f8ca45..43e8db151 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java @@ -67,6 +67,11 @@ public class ScriptDocument { private static final Pattern FIELD_DECL_PATTERN = Pattern.compile( "\\b([A-Za-z_][a-zA-Z0-9_<>,[ \\t]\\[\\]]*)[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*(=|;)"); private static final Pattern NEW_TYPE_PATTERN = Pattern.compile("\\bnew\\s+([A-Za-z_][a-zA-Z0-9_]*)"); + + // Cast expression pattern: captures type name inside cast parentheses + // Matches: (Type), (Type[]), (pkg.Type), (Type) + private static final Pattern CAST_TYPE_PATTERN = Pattern.compile( + "\\(\\s*([A-Za-z_][a-zA-Z0-9_]*(?:\\s*\\.\\s*[A-Za-z_][a-zA-Z0-9_]*)*)\\s*(?:<[^>]*>)?\\s*(\\[\\s*\\])*\\s*\\)\\s*(?=[a-zA-Z_\"'(\\d!~+-])"); // Function parameters (for JS-style scripts) private static final Pattern FUNC_PARAMS_PATTERN = Pattern.compile( @@ -1452,6 +1457,9 @@ public class ScriptDocument { // Chained field accesses (e.g., mc.player.world, this.field) markChainedFieldAccesses(marks); + // Cast type expressions (e.g., (EntityPlayer) entity) + markCastTypes(marks); + // Imported class usages markImportedClassUsages(marks); @@ -2267,6 +2275,11 @@ public class ScriptDocument { return resolveExpressionWithOperators(expr, position); } + // Handle cast expressions: (Type)expr, ((Type)expr).method(), etc. + if (expr.startsWith("(")) { + return resolveCastOrParenthesizedExpression(expr, position); + } + // Invalid expressions starting with brackets if (expr.startsWith("[") || expr.startsWith("]")) { return null; // Invalid syntax @@ -2389,6 +2402,180 @@ public class ScriptDocument { return currentType; } + + /** + * Resolve a cast or parenthesized expression. + * Handles: + * - Simple casts: (Type) expr + * - Nested casts: ((Type) expr) + * - Cast chains: ((Type) expr).method() + * - Parenthesized expressions: (expr) + * + * @param expr The expression starting with '(' + * @param position The position in the source text + * @return The resolved type + */ + private TypeInfo resolveCastOrParenthesizedExpression(String expr, int position) { + if (!expr.startsWith("(")) return null; + + // Find the matching closing parenthesis for the first open paren + int depth = 0; + int closeParen = -1; + boolean inString = false; + char stringChar = 0; + + for (int i = 0; i < expr.length(); i++) { + char c = expr.charAt(i); + + // Handle string literals + if ((c == '"' || c == '\'') && (i == 0 || expr.charAt(i - 1) != '\\')) { + if (!inString) { + inString = true; + stringChar = c; + } else if (c == stringChar) { + inString = false; + } + continue; + } + if (inString) continue; + + if (c == '(') { + depth++; + } else if (c == ')') { + depth--; + if (depth == 0) { + closeParen = i; + break; + } + } + } + + if (closeParen < 0) return null; + + // Extract content inside parens + String insideParens = expr.substring(1, closeParen).trim(); + String afterParens = expr.substring(closeParen + 1).trim(); + + // Check if this is a cast expression + // A cast has a type name inside the parens (starts with uppercase or is primitive) + // and is followed by an expression (not an operator or nothing) + if (looksLikeCastType(insideParens) && looksLikeExpressionStart(afterParens)) { + // This is a cast: (Type) rest + TypeInfo castType = resolveType(insideParens); + if (castType != null && castType.isResolved()) { + // If there's more after the cast target, resolve the chain + // E.g., ((EntityPlayer) entity).worldObj + if (!afterParens.isEmpty()) { + // Check if afterParens starts with '.' for a method/field chain + if (afterParens.startsWith(".")) { + // Continue resolving the chain from the cast type + String chainExpr = afterParens.substring(1).trim(); + List segments = parseExpressionChain(chainExpr); + TypeInfo currentType = castType; + for (ChainSegment segment : segments) { + currentType = resolveChainSegment(currentType, segment); + if (currentType == null) return null; + } + return currentType; + } + } + return castType; + } + } + + // Not a cast - treat as parenthesized expression + // Could be ((expr).method()) or just (expr) + + // First, resolve what's inside the parens + TypeInfo innerType = resolveExpressionType(insideParens, position); + + // If there's a chain after the parens, continue resolving + if (innerType != null && !afterParens.isEmpty() && afterParens.startsWith(".")) { + String chainExpr = afterParens.substring(1).trim(); + List segments = parseExpressionChain(chainExpr); + TypeInfo currentType = innerType; + for (ChainSegment segment : segments) { + currentType = resolveChainSegment(currentType, segment); + if (currentType == null) return null; + } + return currentType; + } + + return innerType; + } + + /** + * Check if a string looks like a valid type name for a cast. + * Valid: EntityPlayer, int, String, java.lang.String, int[] + */ + private boolean looksLikeCastType(String s) { + if (s == null || s.isEmpty()) return false; + s = s.trim(); + + // Handle array types: Type[] or Type[][] + while (s.endsWith("[]")) { + s = s.substring(0, s.length() - 2).trim(); + } + + if (s.isEmpty()) return false; + + // Primitive types + switch (s) { + case "byte": case "short": case "int": case "long": + case "float": case "double": case "char": case "boolean": + return true; + } + + // Class type: starts with uppercase or is fully qualified + if (Character.isUpperCase(s.charAt(0))) return true; + + // Fully qualified name: contains dots and ends with uppercase segment + if (s.contains(".")) { + String[] parts = s.split("\\."); + String lastPart = parts[parts.length - 1]; + return !lastPart.isEmpty() && Character.isUpperCase(lastPart.charAt(0)); + } + + return false; + } + + /** + * Check if a string looks like the start of an expression (what follows a cast). + * Valid: identifier, (, new, literals, this, etc. + * Invalid: empty, operators like +, -, etc. + */ + private boolean looksLikeExpressionStart(String s) { + if (s == null || s.isEmpty()) return false; + s = s.trim(); + if (s.isEmpty()) return false; + + char first = s.charAt(0); + + // Parenthesis (nested cast or parenthesized expr) + if (first == '(') return true; + + // Identifier or keyword + if (Character.isJavaIdentifierStart(first)) return true; + + // String or char literal + if (first == '"' || first == '\'') return true; + + // Number literal (could start with digit or dot for .5) + if (Character.isDigit(first)) return true; + if (first == '.' && s.length() > 1 && Character.isDigit(s.charAt(1))) return true; + + // Unary operators followed by expression + if (first == '!' || first == '~') return true; + if ((first == '+' || first == '-') && s.length() > 1) { + // Could be unary +/- or ++/-- + char second = s.charAt(1); + if (second == first || Character.isDigit(second) || Character.isJavaIdentifierStart(second) || second == '(') { + return true; + } + } + + return false; + } /** * Parse an expression string into chain segments. @@ -4003,6 +4190,55 @@ public class ScriptDocument { // Attach as the declaration assignment targetField.setDeclarationAssignment(info); } + + /** + * Mark types in cast expressions with their appropriate type color. + * Handles: (Type), (Type[]), (pkg.Type), etc. + */ + private void markCastTypes(List marks) { + Matcher m = CAST_TYPE_PATTERN.matcher(text); + + while (m.find()) { + String typeName = m.group(1); + int typeStart = m.start(1); + int typeEnd = m.end(1); + + // Skip if inside string or comment + if (isExcluded(typeStart)) continue; + + // Skip in import/package statements + if (isInImportOrPackage(typeStart)) continue; + + // Resolve the type + TypeInfo info = resolveType(typeName); + if (info != null && info.isResolved()) { + // Mark the type with its appropriate color + marks.add(new ScriptLine.Mark(typeStart, typeEnd, info.getTokenType(), info)); + } else { + // Unknown type - check if it's a primitive + if (isPrimitiveType(typeName)) { + marks.add(new ScriptLine.Mark(typeStart, typeEnd, TokenType.KEYWORD)); + } else { + // Mark as undefined type + marks.add(new ScriptLine.Mark(typeStart, typeEnd, TokenType.UNDEFINED_VAR)); + } + } + } + } + + /** + * Check if a type name is a primitive type. + */ + private boolean isPrimitiveType(String typeName) { + switch (typeName) { + case "byte": case "short": case "int": case "long": + case "float": case "double": case "char": case "boolean": + case "void": + return true; + default: + return false; + } + } private void markImportedClassUsages(List marks) { // Find uppercase identifiers followed by dot (static method calls, field access)