From 3013a338ed1d020778a4cb47a8026ee96ced4e79 Mon Sep 17 00:00:00 2001 From: Qrakhen Date: Sun, 9 Nov 2025 10:00:32 +0100 Subject: [PATCH] add native extensions --- Qrakhen.Qamp.Core/Execution/Runner.cs | 27 +++++++++- Qrakhen.Qamp.Core/Values/NativeExtension.cs | 52 +++++++++++++++++++ Qrakhen.Qamp.Core/Values/Objects/Extension.cs | 11 ++++ Qrakhen.Qamp.Core/Values/Objects/Native.cs | 9 ++++ Qrakhen.Qamp.Core/Values/ValueExtensions.cs | 5 +- 5 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 Qrakhen.Qamp.Core/Values/NativeExtension.cs create mode 100644 Qrakhen.Qamp.Core/Values/Objects/Extension.cs create mode 100644 Qrakhen.Qamp.Core/Values/Objects/Native.cs diff --git a/Qrakhen.Qamp.Core/Execution/Runner.cs b/Qrakhen.Qamp.Core/Execution/Runner.cs index e45982b..8ac9623 100644 --- a/Qrakhen.Qamp.Core/Execution/Runner.cs +++ b/Qrakhen.Qamp.Core/Execution/Runner.cs @@ -368,6 +368,25 @@ public class Runner : IDisposable } } + private bool InvokeNative(Value target, NativeExtension extension, int argumentCount) + { + _logger.Method(extension); + if (argumentCount != extension.Parameters.Length) { + Error($"{extension} expects {extension.Parameters.Length} arguments, received {argumentCount}"); + return false; + } + + var values = new Value[argumentCount]; + for (int i = 0; i < argumentCount; i++) { + values[i] = Peek(-i - 1); + } + + Stack.Decimate(Stack.Position - argumentCount - 1); + Value result = extension.Callback(target, values); + Push(result); + return true; + } + private bool Invoke(Context closure, int argumentCount) { _logger.Method(); @@ -390,8 +409,12 @@ public class Runner : IDisposable { Value value = Peek(-argumentCount - 1); if (!value.Is(T.Instance)) { - Error($"Can not call {methodName} on value of {value}"); - return false; + NativeExtension? extension = NativeExtension.Get(value.Type, methodName); + if (extension == null) { + Error($"Could not invoke {methodName} on {value}, no method or extension by that name found for this type."); + return false; + } + return InvokeNative(value, extension, argumentCount); } Instance instance = value.Ptr.As()!; diff --git a/Qrakhen.Qamp.Core/Values/NativeExtension.cs b/Qrakhen.Qamp.Core/Values/NativeExtension.cs new file mode 100644 index 0000000..d3b7132 --- /dev/null +++ b/Qrakhen.Qamp.Core/Values/NativeExtension.cs @@ -0,0 +1,52 @@ +using Qrakhen.Qamp.Core.Collections; +using Qrakhen.Qamp.Core.Values.Objects; + +namespace Qrakhen.Qamp.Core.Values; + +public delegate Value ExtensionDelegate(Value target, Value[] parameters); + +public class NativeExtension( + ValueType targetType, + string name, + ExtensionDelegate callback, + string[] parameters) +{ + private static readonly Register> _register = new(); + + public readonly string Name = name; + public readonly ValueType TargetType = targetType; + public readonly ExtensionDelegate Callback = callback; + public readonly string[] Parameters = parameters.ToArray(); + + public static NativeExtension? Get(ValueType targetType, string name) + { + if (!_register.Has(targetType)) + return null; + if (!_register[targetType].Has(name)) + return null; + return _register[targetType][name]; + } + + public static void Register(ValueType targetType, + string name, + ExtensionDelegate callback, + params string[] parameters) + { + if (!_register.Has(targetType)) + _register.Add(targetType, new()); + if (_register[targetType].Has(name)) + throw new QampException($"Extension {name} already exists for type {targetType}."); + _register[targetType].Add(name, new NativeExtension(targetType, name, callback, parameters)); + } + + static NativeExtension() + { + Register(ValueType.String, "Length", (v, p) => new Value((long)v.Ptr.As()!.Value!.Length), []); + Register(ValueType.String, "IndexOf", (v, p) => new Value((long)v.Ptr.As()!.Value!.IndexOf(p[0].Ptr.As()!.Value!)), [ "needle" ]); + } + + public override string ToString() + { + return $"{TargetType}.{Name}({string.Join(", ", Parameters)}) (NativeExtension)"; + } +} diff --git a/Qrakhen.Qamp.Core/Values/Objects/Extension.cs b/Qrakhen.Qamp.Core/Values/Objects/Extension.cs new file mode 100644 index 0000000..69db23d --- /dev/null +++ b/Qrakhen.Qamp.Core/Values/Objects/Extension.cs @@ -0,0 +1,11 @@ +namespace Qrakhen.Qamp.Core.Values.Objects; + +public class Extension(ValueType targetType, Function function) : Context(function) +{ + public ValueType TargetType = targetType; + + public override string ToString() + { + return $"{TargetType}.{Function.Name}() (Extension)"; + } +} diff --git a/Qrakhen.Qamp.Core/Values/Objects/Native.cs b/Qrakhen.Qamp.Core/Values/Objects/Native.cs new file mode 100644 index 0000000..2cdaf94 --- /dev/null +++ b/Qrakhen.Qamp.Core/Values/Objects/Native.cs @@ -0,0 +1,9 @@ +namespace Qrakhen.Qamp.Core.Values.Objects; + +public class Native(string name) : Class(name) +{ + public override string ToString() + { + return $"{Name} (Native)"; + } +} diff --git a/Qrakhen.Qamp.Core/Values/ValueExtensions.cs b/Qrakhen.Qamp.Core/Values/ValueExtensions.cs index d0c4a43..f4e4c6e 100644 --- a/Qrakhen.Qamp.Core/Values/ValueExtensions.cs +++ b/Qrakhen.Qamp.Core/Values/ValueExtensions.cs @@ -4,8 +4,5 @@ namespace Qrakhen.Qamp.Core.Values; internal static class ValueExtensions { - public static Value ExecuteOperation(this Value value, Value other, OpCode operation) - { - return new Value(); - } + } \ No newline at end of file