From 3172e6673f1a332f0f498a606978f41e5592ab64 Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Thu, 5 Mar 2026 21:12:50 +0200 Subject: [PATCH] Implemented ES5.1 global functions --- dts-patches/ecmascript/GlobalFunctions.d.ts | 11 +++-- .../autocomplete/JSAutocompleteProvider.java | 5 ++ .../script/interpreter/ScriptDocument.java | 45 ++++++++++++++++++ .../interpreter/js_parser/JSTypeRegistry.java | 47 +++++++++++++++++++ 4 files changed, 104 insertions(+), 4 deletions(-) diff --git a/dts-patches/ecmascript/GlobalFunctions.d.ts b/dts-patches/ecmascript/GlobalFunctions.d.ts index d7933db53..f8b257a69 100644 --- a/dts-patches/ecmascript/GlobalFunctions.d.ts +++ b/dts-patches/ecmascript/GlobalFunctions.d.ts @@ -47,16 +47,19 @@ export interface GlobalFunctions { eval(x: any): any; /** Parses a string and returns an integer. Stops parsing at the first non-digit character */ + parseInt(string: string): number; + + /** Parses a string and returns an integer using the specified radix (base). Stops parsing at the first non-digit character */ parseInt(string: string, radix: number): number; /** Parses a string and returns a floating-point number. Stops parsing at the first invalid character */ parseFloat(string: string): number; - /** Determines whether a value is NaN (not-a-number). Returns true for NaN, false for numbers and non-numeric values */ - isNaN(number: number): boolean; + /** Determines whether a value is NaN (not-a-number). The value is first coerced to a number before testing */ + isNaN(value: any): boolean; - /** Determines whether a value is a finite number. Returns false for infinity, -infinity, and NaN */ - isFinite(number: number): boolean; + /** Determines whether a value is a finite number. The value is first coerced to a number. Returns false for Infinity, -Infinity, and NaN */ + isFinite(value: any): boolean; /** Decodes a URI by replacing encoded sequences like %20 with their original characters */ decodeURI(encodedURI: string): string; diff --git a/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JSAutocompleteProvider.java b/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JSAutocompleteProvider.java index c87e8dd5c..fb06144cf 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JSAutocompleteProvider.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JSAutocompleteProvider.java @@ -233,6 +233,11 @@ public class JSAutocompleteProvider extends JavaAutocompleteProvider { items.add(AutocompleteItem.fromType(TypeInfo.fromJSTypeInfo(jsTypeInfo))); } } + + // Add global engine functions (parseInt, parseFloat, isNaN, isFinite, etc.) + for (JSMethodInfo fn : registry.getGlobalEngineFunctions().values()) { + items.add(AutocompleteItem.fromJSMethod(fn, 0)); + } } protected UsageTracker getUsageTracker() { 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 db012cd85..b74652a0f 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 @@ -9,6 +9,7 @@ import noppes.npcs.client.gui.util.script.interpreter.field.EnumConstantInfo; import noppes.npcs.client.gui.util.script.interpreter.field.FieldAccessInfo; import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo; import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSScriptAnalyzer; +import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSMethodInfo; import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeRegistry; import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocInfo; import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocParamTag; @@ -4502,6 +4503,26 @@ public class ScriptDocument { methodCalls.add(callInfo); marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_CALL, callInfo)); } + } else if (isGlobalEngineFunction(methodName)) { + JSMethodInfo jsGlobalMethod = JSTypeRegistry.getInstance().getGlobalEngineFunction(methodName); + MethodInfo resolvedGlobal = MethodInfo.fromJSMethod(jsGlobalMethod, null); + + List globalArguments = arguments; + if (resolvedGlobal.getParameters().size() == arguments.size()) { + globalArguments = parseMethodArguments(openParen + 1, closeParen, resolvedGlobal); + } + + MethodCallInfo callInfo = new MethodCallInfo( + methodName, nameStart, nameEnd, openParen, closeParen, + globalArguments, null, resolvedGlobal + ); + if (!isFollowedByDot(closeParen)) { + TypeInfo expectedType = findExpectedTypeAtPosition(nameStart); + if (expectedType != null) callInfo.setExpectedType(expectedType); + } + callInfo.validate(); + methodCalls.add(callInfo); + marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_CALL, callInfo)); } else { marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.UNDEFINED_VAR)); } @@ -5027,6 +5048,9 @@ public class ScriptDocument { currentType = scriptMethod.getReturnType(); } } + if (currentType == null && isGlobalEngineFunction(first.name)) { + currentType = getGlobalEngineFunctionReturnType(first.name); + } } if (currentType != null && first.hasArrayAccess) { @@ -5658,6 +5682,9 @@ public class ScriptDocument { return scriptMethod.getReturnType(); } } + if (isGlobalEngineFunction(methodName)) { + return getGlobalEngineFunctionReturnType(methodName); + } return null; } @@ -5802,6 +5829,9 @@ public class ScriptDocument { currentType = scriptMethod.getReturnType(); } } + if (currentType == null && isGlobalEngineFunction(first.name)) { + currentType = getGlobalEngineFunctionReturnType(first.name); + } } if (currentType != null && first.hasArrayAccess) { @@ -6228,6 +6258,21 @@ public class ScriptDocument { ); return null; } + + /** + * True if in JS mode and name is a registered global engine function (parseInt, parseFloat, etc.). + */ + private boolean isGlobalEngineFunction(String name) { + return isJavaScript() && JSTypeRegistry.getInstance().isGlobalEngineFunction(name); + } + + /** + * Return the resolved return type of a global engine function call, or null if unknown. + */ + private TypeInfo getGlobalEngineFunctionReturnType(String name) { + JSMethodInfo m = JSTypeRegistry.getInstance().getGlobalEngineFunction(name); + return m != null ? m.getResolvedReturnType(null) : null; + } private void injectSamParameterTypes(MethodInfo scriptMethod, MethodInfo sam) { String methodName = scriptMethod.getName(); diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSTypeRegistry.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSTypeRegistry.java index 60dbc3e42..83e11d317 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSTypeRegistry.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSTypeRegistry.java @@ -54,6 +54,8 @@ public class JSTypeRegistry { private final Map globalEngineObjects = new LinkedHashMap<>(); // Math / Number / Json / etc. built-in globals from ECMAScript 5.1 private final Map globalEngineImports = new LinkedHashMap<>(); + // Top-level global functions from GlobalFunctions.d.ts (parseInt, parseFloat, isNaN, etc.) + private final Map globalEngineFunctions = new LinkedHashMap<>(); // Primitive types private static final Set PRIMITIVES = new HashSet<>(Arrays.asList( @@ -856,6 +858,8 @@ public class JSTypeRegistry { registerGlobalIfTypeExists("Boolean", "Boolean"); registerGlobalIfTypeExists("RegExp", "RegExp"); registerGlobalIfTypeExists("Error", "Error"); + + extractGlobalFunctions(); } /** @@ -869,6 +873,27 @@ public class JSTypeRegistry { } } + /** + * Extract top-level global functions from the GlobalFunctions JSTypeInfo. + * Moves all methods into globalEngineFunctions and removes the GlobalFunctions + * type from the registry (it is not a constructable type). + */ + private void extractGlobalFunctions() { + JSTypeInfo globalFns = types.get("GlobalFunctions"); + if (globalFns == null) return; + + for (Map.Entry entry : globalFns.getMethods().entrySet()) { + String key = entry.getKey(); + // Strip overload suffix ($1, $2, …) to get base name + String baseName = key.contains("$") ? key.substring(0, key.indexOf('$')) : key; + // Keep the first overload per base name + globalEngineFunctions.putIfAbsent(baseName, entry.getValue()); + } + + // Remove GlobalFunctions from the main type registry — it is not a usable type name + types.remove("GlobalFunctions"); + } + /** * Get the type name for a global object. * @param name The global variable name @@ -891,6 +916,27 @@ public class JSTypeRegistry { public Map getGlobalEngineImports() { return Collections.unmodifiableMap(globalEngineImports); } + + /** + * Get JSMethodInfo for a global engine function by name (e.g., "parseInt"). + */ + public JSMethodInfo getGlobalEngineFunction(String name) { + return globalEngineFunctions.get(name); + } + + /** + * Check if a name is a registered global engine function. + */ + public boolean isGlobalEngineFunction(String name) { + return globalEngineFunctions.containsKey(name); + } + + /** + * Get all registered global engine functions (parseInt, parseFloat, isNaN, etc.). + */ + public Map getGlobalEngineFunctions() { + return Collections.unmodifiableMap(globalEngineFunctions); + } /** * Check if initialized. */ @@ -911,6 +957,7 @@ public class JSTypeRegistry { contextHooks.clear(); globalEngineObjects.clear(); globalEngineImports.clear(); + globalEngineFunctions.clear(); DtsJavaBridge.clearCache(); initialized = false; initializationAttempted = false;