mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 17:47:32 +00:00
Added JS arrow function => and shorthand methods handling
This commit is contained in:
1 parent
959717fafd
commit
86c3277075
9 files changed
+318
-8
No files matched your search
+1
-1
@@ -346,6 +346,6 @@ public class JSAutocompleteProvider extends JavaAutocompleteProvider {
|
||||
}
|
||||
|
||||
public String[] getKeywords() {
|
||||
return TypeChecker.getJaveScriptKeywords();
|
||||
return TypeChecker.getJavaScriptKeywords();
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -15,7 +15,9 @@ public class InnerCallableScope {
|
||||
|
||||
public enum Kind {
|
||||
JAVA_LAMBDA,
|
||||
JS_FUNCTION_EXPR
|
||||
JS_FUNCTION_EXPR,
|
||||
JS_ARROW_FUNC,
|
||||
JS_SHORTHAND_METHOD
|
||||
}
|
||||
|
||||
private final Kind kind;
|
||||
|
||||
@@ -2282,6 +2282,8 @@ public class ScriptDocument {
|
||||
|
||||
if (isJavaScript()) {
|
||||
parseJSFunctionExpressions();
|
||||
parseJSArrowFunctions();
|
||||
parseJSShorthandMethods();
|
||||
} else {
|
||||
parseJavaLambdas();
|
||||
}
|
||||
@@ -2542,6 +2544,160 @@ public class ScriptDocument {
|
||||
}
|
||||
}
|
||||
|
||||
private void parseJSArrowFunctions() {
|
||||
String arrowToken = "=>";
|
||||
int pos = 0;
|
||||
while ((pos = text.indexOf(arrowToken, pos)) >= 0) {
|
||||
if (isExcluded(pos)) {
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
int arrowPos = pos;
|
||||
int headerStart = findArrowFunctionHeaderStart(arrowPos);
|
||||
if (headerStart < 0) {
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
int bodyStart = arrowPos + 2;
|
||||
while (bodyStart < text.length() && Character.isWhitespace(text.charAt(bodyStart))) {
|
||||
bodyStart++;
|
||||
}
|
||||
|
||||
if (bodyStart >= text.length()) {
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
int bodyEnd;
|
||||
if (text.charAt(bodyStart) == '{') {
|
||||
bodyEnd = findMatchingBrace(bodyStart);
|
||||
if (bodyEnd < 0) bodyEnd = text.length();
|
||||
else bodyEnd++;
|
||||
} else {
|
||||
bodyEnd = findLambdaExpressionEnd(bodyStart);
|
||||
}
|
||||
|
||||
InnerCallableScope scope = new InnerCallableScope(
|
||||
InnerCallableScope.Kind.JS_ARROW_FUNC,
|
||||
headerStart,
|
||||
arrowPos + 2,
|
||||
bodyStart,
|
||||
bodyEnd
|
||||
);
|
||||
|
||||
parseArrowFunctionParameters(scope, headerStart, arrowPos);
|
||||
innerScopes.add(scope);
|
||||
pos = bodyEnd;
|
||||
}
|
||||
}
|
||||
|
||||
private int findArrowFunctionHeaderStart(int arrowPos) {
|
||||
int pos = arrowPos - 1;
|
||||
while (pos >= 0 && Character.isWhitespace(text.charAt(pos))) {
|
||||
pos--;
|
||||
}
|
||||
if (pos < 0) return -1;
|
||||
|
||||
if (text.charAt(pos) == ')') {
|
||||
int depth = 1;
|
||||
pos--;
|
||||
while (pos >= 0 && depth > 0) {
|
||||
if (isExcluded(pos)) { pos--; continue; }
|
||||
char c = text.charAt(pos);
|
||||
if (c == ')') depth++;
|
||||
else if (c == '(') depth--;
|
||||
pos--;
|
||||
}
|
||||
return pos + 1;
|
||||
} else if (Character.isJavaIdentifierPart(text.charAt(pos))) {
|
||||
while (pos >= 0 && Character.isJavaIdentifierPart(text.charAt(pos))) {
|
||||
pos--;
|
||||
}
|
||||
return pos + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void parseArrowFunctionParameters(InnerCallableScope scope, int headerStart, int arrowPos) {
|
||||
String headerText = text.substring(headerStart, arrowPos).trim();
|
||||
|
||||
if (headerText.startsWith("(") && headerText.endsWith(")")) {
|
||||
String paramsText = headerText.substring(1, headerText.length() - 1).trim();
|
||||
if (!paramsText.isEmpty()) {
|
||||
String[] params = paramsText.split(",");
|
||||
int offset = headerStart + 1;
|
||||
for (String param : params) {
|
||||
param = param.trim();
|
||||
if (param.isEmpty()) continue;
|
||||
|
||||
int nameOffset = text.indexOf(param, offset);
|
||||
if (nameOffset < 0) nameOffset = offset;
|
||||
|
||||
FieldInfo paramInfo = FieldInfo.parameter(param, TypeInfo.ANY, nameOffset, null);
|
||||
scope.addParameter(paramInfo);
|
||||
offset = nameOffset + param.length();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ObjectLiteralParser.isSimpleIdentifier(headerText)) {
|
||||
int nameOffset = text.indexOf(headerText, headerStart);
|
||||
if (nameOffset < 0) nameOffset = headerStart;
|
||||
FieldInfo paramInfo = FieldInfo.parameter(headerText, TypeInfo.ANY, nameOffset, null);
|
||||
scope.addParameter(paramInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseJSShorthandMethods() {
|
||||
Pattern shorthandPattern = Pattern.compile("(\\w+)\\s*\\(([^)]*)\\)\\s*\\{");
|
||||
Matcher m = shorthandPattern.matcher(text);
|
||||
|
||||
while (m.find()) {
|
||||
int start = m.start();
|
||||
if (isExcluded(start)) continue;
|
||||
|
||||
if (!isInsideObjectLiteral(start)) continue;
|
||||
|
||||
String methodName = m.group(1);
|
||||
if (TypeChecker.isJavaScriptKeyword(methodName)) continue;
|
||||
|
||||
int headerStart = m.start(1);
|
||||
int bodyStart = m.end() - 1;
|
||||
int bodyEnd = findMatchingBrace(bodyStart);
|
||||
if (bodyEnd < 0) bodyEnd = text.length();
|
||||
else bodyEnd++;
|
||||
|
||||
InnerCallableScope scope = new InnerCallableScope(
|
||||
InnerCallableScope.Kind.JS_SHORTHAND_METHOD,
|
||||
headerStart,
|
||||
bodyStart,
|
||||
bodyStart,
|
||||
bodyEnd
|
||||
);
|
||||
|
||||
String paramsText = m.group(2).trim();
|
||||
if (!paramsText.isEmpty()) {
|
||||
String[] params = paramsText.split(",");
|
||||
int offset = m.start(2);
|
||||
for (String param : params) {
|
||||
param = param.trim();
|
||||
if (param.isEmpty()) continue;
|
||||
|
||||
int nameOffset = text.indexOf(param, offset);
|
||||
if (nameOffset < 0) nameOffset = offset;
|
||||
|
||||
FieldInfo paramInfo = FieldInfo.parameter(param, TypeInfo.ANY, nameOffset, null);
|
||||
scope.addParameter(paramInfo);
|
||||
offset = nameOffset + param.length();
|
||||
}
|
||||
}
|
||||
|
||||
innerScopes.add(scope);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFunctionDeclaration(int funcStart) {
|
||||
// A function declaration is a statement, check context:
|
||||
// - If preceded by = or ( or , or : or [ it's an expression
|
||||
@@ -3572,7 +3728,9 @@ public class ScriptDocument {
|
||||
private void validateLambdaReturnTypes(List<ScriptLine.Mark> marks) {
|
||||
for (InnerCallableScope scope : innerScopes) {
|
||||
if (scope.getKind() == InnerCallableScope.Kind.JAVA_LAMBDA ||
|
||||
scope.getKind() == InnerCallableScope.Kind.JS_FUNCTION_EXPR) {
|
||||
scope.getKind() == InnerCallableScope.Kind.JS_FUNCTION_EXPR ||
|
||||
scope.getKind() == InnerCallableScope.Kind.JS_ARROW_FUNC ||
|
||||
scope.getKind() == InnerCallableScope.Kind.JS_SHORTHAND_METHOD) {
|
||||
validateLambdaReturnType(scope, marks);
|
||||
}
|
||||
}
|
||||
@@ -5160,7 +5318,7 @@ public class ScriptDocument {
|
||||
// Check if expression contains operators - if so, use the full expression resolver
|
||||
// Handle cast expressions: (Type)expr, ((Type)expr).method(), etc
|
||||
// Also route JS function expressions and arrow lambdas through the parser
|
||||
if (containsOperators(expr) || expr.contains("::") || expr.contains("->") || expr.startsWith("(") || looksLikeFunctionOrLambda(expr)) {
|
||||
if (containsOperators(expr) || expr.contains("::") || expr.contains("->") || expr.contains("=>") || expr.startsWith("(") || looksLikeFunctionOrLambda(expr)) {
|
||||
return resolveExpressionWithParserAPI(expr, position);
|
||||
}
|
||||
|
||||
|
||||
+42
@@ -1,5 +1,6 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter.expression;
|
||||
|
||||
import noppes.npcs.client.gui.util.script.interpreter.InnerCallableScope;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -322,6 +323,47 @@ public abstract class ExpressionNode {
|
||||
}
|
||||
}
|
||||
|
||||
public static class JSArrowNode extends ExpressionNode {
|
||||
private final List<String> parameterNames;
|
||||
private final ExpressionNode body;
|
||||
private final boolean isBlock;
|
||||
private InnerCallableScope scopeRef;
|
||||
|
||||
public JSArrowNode(List<String> parameterNames, ExpressionNode body, boolean isBlock, int start, int end) {
|
||||
super(start, end);
|
||||
this.parameterNames = parameterNames != null ? new ArrayList<>(parameterNames) : new ArrayList<>();
|
||||
this.body = body;
|
||||
this.isBlock = isBlock;
|
||||
}
|
||||
|
||||
public List<String> getParameterNames() { return Collections.unmodifiableList(parameterNames); }
|
||||
public ExpressionNode getBody() { return body; }
|
||||
public boolean isBlock() { return isBlock; }
|
||||
public InnerCallableScope getScopeRef() { return scopeRef; }
|
||||
public void setScopeRef(InnerCallableScope scope) {
|
||||
this.scopeRef = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInfo resolveType(TypeResolverContext resolver) {
|
||||
return TypeInfo.fromClass(Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(");
|
||||
sb.append(String.join(", ", parameterNames));
|
||||
sb.append(") => ");
|
||||
if (isBlock) {
|
||||
sb.append("{ ... }");
|
||||
} else if (body != null) {
|
||||
sb.append(body);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MethodReferenceNode extends ExpressionNode {
|
||||
private final ExpressionNode target; // Object or class name
|
||||
private final String methodName;
|
||||
|
||||
+10
-1
@@ -24,6 +24,7 @@ public class ExpressionParser {
|
||||
int checkpoint = pos;
|
||||
List<String> paramNames = null;
|
||||
int lambdaStart = current().getStart();
|
||||
boolean isJSArrow = false;
|
||||
|
||||
// Try to parse parameter list
|
||||
if (check(ExpressionToken.TokenKind.IDENTIFIER)) {
|
||||
@@ -33,6 +34,10 @@ public class ExpressionParser {
|
||||
if (check(ExpressionToken.TokenKind.LAMBDA_ARROW)) {
|
||||
paramNames = new ArrayList<>();
|
||||
paramNames.add(paramName);
|
||||
} else if (check(ExpressionToken.TokenKind.JS_ARROW)) {
|
||||
paramNames = new ArrayList<>();
|
||||
paramNames.add(paramName);
|
||||
isJSArrow = true;
|
||||
} else {
|
||||
// Not a lambda, rewind
|
||||
pos = checkpoint;
|
||||
@@ -72,6 +77,8 @@ public class ExpressionParser {
|
||||
advance(); // consume ')'
|
||||
if (check(ExpressionToken.TokenKind.LAMBDA_ARROW)) {
|
||||
// Valid lambda!
|
||||
} else if (check(ExpressionToken.TokenKind.JS_ARROW)) {
|
||||
isJSArrow = true;
|
||||
} else {
|
||||
// Not a lambda, rewind
|
||||
pos = checkpoint;
|
||||
@@ -87,7 +94,7 @@ public class ExpressionParser {
|
||||
|
||||
if (paramNames != null) {
|
||||
// This is a lambda! Parse it
|
||||
advance(); // consume '->'
|
||||
advance(); // consume '->' or '=>'
|
||||
|
||||
// Parse body
|
||||
ExpressionNode body;
|
||||
@@ -122,6 +129,8 @@ public class ExpressionParser {
|
||||
}
|
||||
|
||||
// Create lambda node (scopeRef will be set during type resolution)
|
||||
if (isJSArrow)
|
||||
return new ExpressionNode.JSArrowNode(paramNames, body, isBlock, lambdaStart, bodyEnd);
|
||||
return new ExpressionNode.LambdaNode(paramNames, body, isBlock, lambdaStart, bodyEnd);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ public class ExpressionToken {
|
||||
LEFT_PAREN, RIGHT_PAREN, LEFT_BRACKET, RIGHT_BRACKET,
|
||||
LEFT_BRACE, RIGHT_BRACE,
|
||||
DOT, COMMA, QUESTION, COLON, SEMICOLON,
|
||||
LAMBDA_ARROW, METHOD_REFERENCE,
|
||||
LAMBDA_ARROW, JS_ARROW, METHOD_REFERENCE,
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -106,13 +106,20 @@ public class ExpressionTokenizer {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for lambda arrow
|
||||
// Check for lambda arrow (Java ->)
|
||||
if (pos + 1 < len && c == '-' && expr.charAt(pos + 1) == '>') {
|
||||
tokens.add(new ExpressionToken(ExpressionToken.TokenKind.LAMBDA_ARROW, "->", pos, pos + 2));
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for JS arrow (=>)
|
||||
if (pos + 1 < len && c == '=' && expr.charAt(pos + 1) == '>') {
|
||||
tokens.add(new ExpressionToken(ExpressionToken.TokenKind.JS_ARROW, "=>", pos, pos + 2));
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pos + 3 <= len) {
|
||||
String s3 = expr.substring(pos, pos + 3);
|
||||
if (">>>".equals(s3) || "<<=".equals(s3) || ">>=".equals(s3)) op = s3;
|
||||
|
||||
+83
@@ -182,6 +182,10 @@ public class ExpressionTypeResolver {
|
||||
return resolveJSFunctionType((ExpressionNode.JSFunctionNode) node);
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.JSArrowNode) {
|
||||
return resolveJSArrowType((ExpressionNode.JSArrowNode) node);
|
||||
}
|
||||
|
||||
if (node instanceof ExpressionNode.MethodReferenceNode) {
|
||||
return resolveMethodReferenceType((ExpressionNode.MethodReferenceNode) node);
|
||||
}
|
||||
@@ -395,6 +399,85 @@ public class ExpressionTypeResolver {
|
||||
return null;
|
||||
}
|
||||
|
||||
private TypeInfo resolveJSArrowType(ExpressionNode.JSArrowNode arrowNode) {
|
||||
TypeInfo expectedType = CURRENT_EXPECTED_TYPE;
|
||||
|
||||
if (expectedType == null) {
|
||||
TypeInfo functionType = context.resolveTypeName("Function");
|
||||
if (functionType != null && functionType.isResolved()) {
|
||||
return functionType;
|
||||
}
|
||||
return TypeInfo.fromClass(Object.class);
|
||||
}
|
||||
|
||||
MethodInfo sam = expectedType.getSingleAbstractMethod();
|
||||
if (sam != null && document != null) {
|
||||
InnerCallableScope scope = findJSArrowScopeByPosition(arrowNode.getStart(), arrowNode.getParameterNames());
|
||||
|
||||
if (scope != null) {
|
||||
scope.setExpectedType(expectedType);
|
||||
|
||||
List<FieldInfo> samParams = sam.getParameters();
|
||||
List<FieldInfo> arrowParams = scope.getParameters();
|
||||
|
||||
if (samParams.size() == arrowParams.size()) {
|
||||
for (int i = 0; i < arrowParams.size(); i++) {
|
||||
TypeInfo inferredType = samParams.get(i).getTypeInfo();
|
||||
if (inferredType != null) {
|
||||
arrowParams.get(i).setInferredType(inferredType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expectedType;
|
||||
}
|
||||
|
||||
String typeName = expectedType.getFullName();
|
||||
if (typeName != null && (typeName.contains("Function") || typeName.contains("Consumer") ||
|
||||
typeName.contains("Predicate") || typeName.contains("Supplier") ||
|
||||
typeName.equals("java.lang.Runnable"))) {
|
||||
if (arrowNode.getScopeRef() != null) {
|
||||
arrowNode.getScopeRef().setExpectedType(expectedType);
|
||||
}
|
||||
return expectedType;
|
||||
}
|
||||
|
||||
if (typeName != null && typeName.equals("Function")) {
|
||||
return expectedType;
|
||||
}
|
||||
|
||||
return TypeInfo.fromClass(Object.class);
|
||||
}
|
||||
|
||||
private InnerCallableScope findJSArrowScopeByPosition(int expressionRelativePos, List<String> paramNames) {
|
||||
if (document == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int absolutePos = basePosition + expressionRelativePos;
|
||||
|
||||
for (InnerCallableScope scope : document.getInnerScopes()) {
|
||||
if (scope.getKind() == InnerCallableScope.Kind.JS_ARROW_FUNC) {
|
||||
if (absolutePos >= scope.getHeaderStart() && absolutePos < scope.getFullEnd()) {
|
||||
if (scope.getParameters().size() == paramNames.size()) {
|
||||
boolean match = true;
|
||||
for (int i = 0; i < paramNames.size(); i++) {
|
||||
if (!scope.getParameters().get(i).getName().equals(paramNames.get(i))) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== Method Reference Resolution ====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -534,7 +534,7 @@ public final class TypeChecker {
|
||||
return keywords;
|
||||
}
|
||||
|
||||
public static String[] getJaveScriptKeywords() {
|
||||
public static String[] getJavaScriptKeywords() {
|
||||
String[] keywords = {
|
||||
"function", "var", "let", "const", "if", "else", "for", "while", "do",
|
||||
"switch", "case", "break", "continue", "return", "try", "catch", "finally",
|
||||
@@ -546,4 +546,13 @@ public final class TypeChecker {
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
public static boolean isJavaScriptKeyword(String keyword) {
|
||||
for (String k : getJavaScriptKeywords()) {
|
||||
if (k.equals(keyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user