mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 20:07:32 +00:00
Casting completely functional
This commit is contained in:
1 parent
bc05164209
commit
efd7d96ec7
1 file changed
+236
@@ -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<Generic>)
|
||||
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<ChainSegment> 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<ChainSegment> 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<ScriptLine.Mark> 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<ScriptLine.Mark> marks) {
|
||||
// Find uppercase identifiers followed by dot (static method calls, field access)
|
||||
|
||||
Reference in new issue
Block a user