mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 20:44:08 +00:00
Expanded scope of patterns/markers to handle nested generics more robustly
This commit is contained in:
1 parent
f53edb6e64
commit
102c4fc398
1 file changed
+96
-16
@@ -82,13 +82,13 @@ public class ScriptDocument {
|
||||
private static final Pattern CLASS_DECL_PATTERN = Pattern.compile(
|
||||
"\\b(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)");
|
||||
private static final Pattern METHOD_DECL_PATTERN = Pattern.compile(
|
||||
"\\b([A-Za-z_][a-zA-Z0-9_<>\\[\\]]*)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(");
|
||||
"\\b([A-Za-z_][a-zA-Z0-9_.<>,[ \\t]\\[\\]]*)[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(");
|
||||
private static final Pattern METHOD_CALL_PATTERN = Pattern.compile(
|
||||
"([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(");
|
||||
// Also matches ',' as a delimiter so that "int x, y, z;" is recognized
|
||||
// (the first declarator "int x," is captured; continuations are scanned manually).
|
||||
private static final Pattern FIELD_DECL_PATTERN = Pattern.compile(
|
||||
"\\b([A-Za-z_][a-zA-Z0-9_.<>,[ \\t\\n\\r]\\[\\]]*)[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*(=|;|,)");
|
||||
"\\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_]*)\\s*(?:<([^>]*)>)?");
|
||||
|
||||
// Cast expression pattern: captures type name inside cast parentheses
|
||||
@@ -524,6 +524,20 @@ public class ScriptDocument {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any position in [start, end) falls inside an excluded range.
|
||||
* Uses range-overlap logic (O(excludedRanges.size())) instead of per-character checks.
|
||||
* Two ranges [a,b) and [c,d) overlap iff a < d && c < b.
|
||||
*/
|
||||
public boolean hasExcludedRange(int start, int end) {
|
||||
for (int[] range : excludedRanges) {
|
||||
if (start < range[1] && range[0] < end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a position falls inside one of the type's inner class bodies.
|
||||
* Used to prevent field/constructor declarations inside inner classes
|
||||
@@ -1145,6 +1159,36 @@ public class ScriptDocument {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace newline characters ({@code \n}, {@code \r}) with spaces when they
|
||||
* occur inside angle brackets (generic type arguments). This lets patterns
|
||||
* like FIELD_DECL_PATTERN and METHOD_DECL_PATTERN match multi-line generics
|
||||
* (e.g. {@code HashMap<String,\n Integer>}) without allowing a match to
|
||||
* span across {@code //} comment lines.
|
||||
*
|
||||
* <p>The returned string has the <b>same length</b> as the input (characters
|
||||
* are replaced, never inserted or removed), so all positional indices remain
|
||||
* valid for the original text.
|
||||
*/
|
||||
private static String normalizeGenericNewlines(String src) {
|
||||
StringBuilder sb = new StringBuilder(src.length());
|
||||
int depth = 0;
|
||||
for (int i = 0; i < src.length(); i++) {
|
||||
char c = src.charAt(i);
|
||||
if (c == '<')
|
||||
depth++;
|
||||
else if (c == '>') {
|
||||
if (depth > 0)
|
||||
depth--;
|
||||
}
|
||||
if ((c == '\n' || c == '\r') && depth > 0)
|
||||
sb.append(' ');
|
||||
else
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of a keyword in a string, but only at angle-bracket depth 0.
|
||||
* The keyword must be preceded and followed by non-identifier characters (word boundary).
|
||||
@@ -1591,15 +1635,21 @@ public class ScriptDocument {
|
||||
} else {
|
||||
// Java: ReturnType methodName(params) { ... } or { ; }
|
||||
Pattern methodWithBody = Pattern.compile(
|
||||
"\\b([a-zA-Z_][a-zA-Z0-9_<>\\[\\]]*)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(([^)]*)\\)\\s*(\\{|;)");
|
||||
"\\b([a-zA-Z_][a-zA-Z0-9_.<>,[ \\t]\\[\\]]*)[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(([^)]*)\\)\\s*(\\{|;)");
|
||||
|
||||
Matcher m = methodWithBody.matcher(text);
|
||||
Matcher m = methodWithBody.matcher(normalizeGenericNewlines(text));
|
||||
while (m.find()) {
|
||||
String methodName = m.group(2);
|
||||
|
||||
if (isExcluded(m.start()) || isKeyword(methodName))
|
||||
continue;
|
||||
|
||||
if (isExcluded(m.start(2)))
|
||||
continue;
|
||||
|
||||
if (hasExcludedRange(m.start(1), m.end(1)))
|
||||
continue;
|
||||
|
||||
String returnType = m.group(1);
|
||||
|
||||
if (returnType.equals("class") || returnType.equals("interface") || returnType.equals("enum") || returnType.equals("new")
|
||||
@@ -2280,7 +2330,7 @@ public class ScriptDocument {
|
||||
}
|
||||
} else {
|
||||
// Java: Type varName = expr; or Type varName;
|
||||
Matcher m = FIELD_DECL_PATTERN.matcher(bodyText);
|
||||
Matcher m = FIELD_DECL_PATTERN.matcher(normalizeGenericNewlines(bodyText));
|
||||
while (m.find()) {
|
||||
String typeName = m.group(1);
|
||||
String varName = m.group(2);
|
||||
@@ -2294,6 +2344,8 @@ public class ScriptDocument {
|
||||
// where group(2) lands on the next line outside the comment.
|
||||
int absTypeStart = bodyStart + m.start(1);
|
||||
if (isExcluded(absTypeStart)) continue;
|
||||
if (hasExcludedRange(absTypeStart, bodyStart + m.end(1)))
|
||||
continue;
|
||||
|
||||
// --- FIX: Detect greedy regex consuming commas into the type name ---
|
||||
// FIELD_DECL_PATTERN allows commas in its type character class (for generics).
|
||||
@@ -2523,12 +2575,14 @@ public class ScriptDocument {
|
||||
*/
|
||||
private void parseJavaLocalsInRange(int start, int end, InnerCallableScope scope) {
|
||||
String rangeText = text.substring(start, Math.min(end, text.length()));
|
||||
|
||||
Matcher m = FIELD_DECL_PATTERN.matcher(rangeText);
|
||||
|
||||
Matcher m = FIELD_DECL_PATTERN.matcher(normalizeGenericNewlines(rangeText));
|
||||
while (m.find()) {
|
||||
int declPos = start + m.start();
|
||||
if (declPos < start || declPos >= end) continue;
|
||||
if (isExcluded(declPos)) continue;
|
||||
if (hasExcludedRange(start + m.start(1), start + m.end(1)))
|
||||
continue;
|
||||
|
||||
String typeNameRaw = m.group(1);
|
||||
String varName = m.group(2);
|
||||
@@ -3334,8 +3388,20 @@ public class ScriptDocument {
|
||||
if (param.isEmpty()) continue;
|
||||
|
||||
// Find position in original text
|
||||
// Search within a reasonable window to avoid matching shadowed names from outer scopes
|
||||
int paramOffset = text.indexOf(param, offset);
|
||||
if (paramOffset < 0) paramOffset = offset;
|
||||
// Validate that we found it within expected range (before next comma or closing paren)
|
||||
int nextComma = text.indexOf(',', offset);
|
||||
int nextClose = text.indexOf(')', offset);
|
||||
int endBound = Math.min(
|
||||
nextComma >= 0 ? nextComma : Integer.MAX_VALUE,
|
||||
nextClose >= 0 ? nextClose : Integer.MAX_VALUE
|
||||
);
|
||||
if (paramOffset >= endBound) {
|
||||
// Found match is outside the parameter list, use direct offset calculation instead
|
||||
paramOffset = offset;
|
||||
}
|
||||
|
||||
String[] parts = param.split("\\s+");
|
||||
String paramName;
|
||||
@@ -3348,28 +3414,33 @@ public class ScriptDocument {
|
||||
boolean isVarArg = typeName.endsWith("...");
|
||||
if (isVarArg) typeName = typeName.substring(0, typeName.length() - 3);
|
||||
paramType = resolveType(typeName);
|
||||
int nameOffset = text.indexOf(paramName, paramOffset);
|
||||
if (nameOffset < 0) nameOffset = paramOffset;
|
||||
// For typed params, the name is after the type, so calculate position more carefully
|
||||
int nameOffset = paramOffset + (param.lastIndexOf(paramName));
|
||||
if (nameOffset < paramOffset || nameOffset >= endBound)
|
||||
nameOffset = paramOffset + param.length() - paramName.length();
|
||||
FieldInfo paramInfo = FieldInfo.parameter(paramName, paramType, nameOffset, null);
|
||||
paramInfo.setVarArg(isVarArg);
|
||||
scope.addParameter(paramInfo);
|
||||
} else {
|
||||
// Untyped: just name (type will be inferred from expected FI)
|
||||
paramName = parts[0];
|
||||
int nameOffset = text.indexOf(paramName, paramOffset);
|
||||
if (nameOffset < 0) nameOffset = paramOffset;
|
||||
// For untyped params, the entire param string is the name, so use paramOffset directly
|
||||
int nameOffset = paramOffset;
|
||||
FieldInfo paramInfo = FieldInfo.parameter(paramName, paramType, nameOffset, null);
|
||||
scope.addParameter(paramInfo);
|
||||
}
|
||||
|
||||
offset = paramOffset + param.length();
|
||||
|
||||
offset = Math.max(paramOffset + param.length(), offset + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single identifier: x ->
|
||||
String paramName = headerText;
|
||||
int nameOffset = text.indexOf(paramName, headerStart);
|
||||
if (nameOffset < 0) nameOffset = headerStart;
|
||||
// Validate found position is before the arrow (not a shadowed name after)
|
||||
if (nameOffset < 0 || nameOffset >= arrowPos) {
|
||||
nameOffset = headerStart;
|
||||
}
|
||||
|
||||
FieldInfo paramInfo = FieldInfo.parameter(paramName, null, nameOffset, null);
|
||||
scope.addParameter(paramInfo);
|
||||
@@ -3783,7 +3854,7 @@ public class ScriptDocument {
|
||||
}
|
||||
} else {
|
||||
// Java: [modifiers] Type fieldName [= expr];
|
||||
Matcher m = FIELD_DECL_PATTERN.matcher(text);
|
||||
Matcher m = FIELD_DECL_PATTERN.matcher(normalizeGenericNewlines(text));
|
||||
while (m.find()) {
|
||||
String typeNameRaw = m.group(1);
|
||||
String fieldName = m.group(2);
|
||||
@@ -3797,6 +3868,9 @@ public class ScriptDocument {
|
||||
if (isExcluded(m.start(1)))
|
||||
continue;
|
||||
|
||||
if (hasExcludedRange(m.start(1), m.end(1)))
|
||||
continue;
|
||||
|
||||
// --- FIX: Detect greedy regex consuming commas into the type name ---
|
||||
// Same issue as parseLocalVariables: FIELD_DECL_PATTERN greedily consumes
|
||||
// multi-declarator commas into group(1). When a depth-0 comma exists in
|
||||
@@ -6074,11 +6148,17 @@ public class ScriptDocument {
|
||||
}
|
||||
|
||||
private void markMethodDeclarations(List<ScriptLine.Mark> marks) {
|
||||
Matcher m = METHOD_DECL_PATTERN.matcher(text);
|
||||
Matcher m = METHOD_DECL_PATTERN.matcher(normalizeGenericNewlines(text));
|
||||
while (m.find()) {
|
||||
if (isExcluded(m.start()))
|
||||
continue;
|
||||
|
||||
if (isExcluded(m.start(2)))
|
||||
continue;
|
||||
|
||||
if (hasExcludedRange(m.start(1), m.end(1)))
|
||||
continue;
|
||||
|
||||
// Skip class/interface/enum declarations - these look like method declarations
|
||||
// but "class Foo(" is not a method, it's a class declaration3
|
||||
String returnType = m.group(1);
|
||||
|
||||
Reference in new issue
Block a user