few things, realized i should actually do the native methods like the extension ones

This commit is contained in:
Qrakhen 2026-01-08 13:53:48 +01:00
parent aaec6375eb
commit 44b8bb8900
11 changed files with 264 additions and 115 deletions

View File

@ -17,7 +17,7 @@ public class Table<TValue> :
IToArray<TValue>,
IEnumerable<KeyValuePair<Address, TValue>>
{
public const int BLOCK_SIZE = sizeof(int);
public const int BLOCK_SIZE = sizeof(int) * 8;
private const uint BLOCK_FULL = (uint)((1UL << 32) - 1);
private readonly TValue[] _data;

View File

@ -17,11 +17,7 @@ public static class Console
Formatter = (p) => {
if (p.Length == 0)
return [];
return p
.Select(v => $"{v}".Split('\n'))
.Select(v => string.Join($"\n :> ", v))
.Select(v => $" :> {v}")
.ToArray();
return p.Select(x => x.ToString()).ToArray() ?? [];
};
}
@ -58,4 +54,4 @@ public delegate string? InputStreamListenHandler();
public delegate void OutputStreamMessageHandler(string? message);
public delegate void ErrorStreamMessageHandler(string? message);
public delegate string[] MessageFormatter(params object[] parameters);
public delegate string?[] MessageFormatter(params object[] parameters);

View File

@ -14,15 +14,15 @@ public class ArithmeticResolver : IOperationResolver
private readonly Register<OpCode, OperationHandler> _operations;
public bool CanResolve(OpCode opCode) => (opCode & (OpCode.F_Operation | OpCode.F_Compare)) > 0;
public bool CanResolve(OpCode opCode) => opCode.IsHandledByAlu();
private Operation PopOperation(Runner runner, OpCode code)
{
#if LOG
_logger.Method(code);
#endif
if ((code & OpCode.F_Unary) == OpCode.F_Unary)
return new Operation(code, runner.Pop(), default);
if (code.IsUnary())
return new Operation(code, runner.Pop(), Value.Void);
Value right = runner.Pop();
Value left = runner.Pop();
return new Operation(code, left, right);

View File

@ -1,92 +1,131 @@
namespace Qrakhen.Qamp.Core.Execution;
public class SerializedEnumAttribute(Type serializedType) : Attribute
{
public Type SerializedType { get; } = serializedType;
}
/// <summary>
///
/// </summary>
/// <remarks>
/// In order to keep instruction files small, all op codes are crammed into 8 bits,
/// and operation flags aren't flags, but rather should be seen as IDs (the first 4 bits)
/// and the operation itself is a number made from the last 4 bits.
/// & 0b11110000 => Handler
/// & 0b00001111 => Operation
/// </remarks>
[SerializedEnum(typeof(byte))]
public enum OpCode
{
F_Mask = 0xF0,
/// <summary>
/// Mask bits, to be used like <c>opCode &amp; F_Mask == F_Operation</c>, for example.
/// </summary>
Mask_Handler = 0xF0,
Mask_Operation = 0x0F,
None = 0x00,
Constant = 0x01,
Null = 0x02,
Pop = 0x03,
Cast = 0x04,
Error = -1,
Void = 0,
False = 0x0e,
True = 0x0f,
F_Static = 0x10,
None = F_Static | 1,
True = F_Static | 2,
False = F_Static | 3,
Constant = F_Static | 4,
Null = F_Static | 5,
Cast = F_Static | 6,
Val = 0x10,
Ref = 0x11,
Addr = 0x12,
Pop = F_Static | 7,
SetGlobal = 0x20,
GetGlobal = 0x21,
GetLocal = 0x22,
SetLocal = 0x23,
GetOuter = 0x24,
SetOuter = 0x25,
GetMember = 0x26,
SetMember = 0x27,
F_Retrieve = 0x20,
Val = F_Retrieve | 1,
Ref = F_Retrieve | 2,
Addr = F_Retrieve | 3,
DefineGlobal = 0x30,
CloseOuter = 0x31,
F_Names = 0x30,
SetGlobal = F_Names | 1,
GetGlobal = F_Names | 2,
GetLocal = F_Names | 3,
SetLocal = F_Names | 4,
GetOuter = F_Names | 5,
SetOuter = F_Names | 6,
GetMember = F_Names | 7,
SetMember = F_Names | 8,
DefineGlobal = F_Names | 9,
CloseOuter = F_Names | 10,
AssignValue = 0x40, // unsure
AssignReference = 0x41, // unsure
//AssignValue = 0x0, // unsure
//AssignReference = 0x0, // unsure
F_Operation = 0x60,
Add = 0x60,
Subtract = 0x61,
Multiply = 0x62,
Divide = 0x63,
Modulo = 0x64,
BitwiseAnd = 0x65,
BitwiseOr = 0x66,
BitwiseXor = 0x67,
BitwiseLeft = 0x68,
BitwiseRight = 0x69,
F_Arithmetic = 0x40,
Add = F_Arithmetic | 1,
Subtract = F_Arithmetic | 2,
Multiply = F_Arithmetic | 3,
Divide = F_Arithmetic | 4,
Modulo = F_Arithmetic | 5,
Negate = F_Arithmetic | 7,
F_Unary = 0x0a,
Not = 0x6a,
Negate = 0x6b,
BitwiseNot = 0x6c,
F_Bitwise = 0x50,
BitwiseAnd = F_Bitwise | 1,
BitwiseOr = F_Bitwise | 2,
BitwiseXor = F_Bitwise | 3,
BitwiseLeft = F_Bitwise | 4,
BitwiseRight = F_Bitwise | 5,
BitwiseNot = F_Bitwise | 6,
F_Compare = 0x50,
Equal = 0x50,
Greater = 0x51,
Less = 0x52,
F_Boolean = 0x60,
Not = F_Boolean | 1,
Equal = F_Boolean | 2,
Greater = F_Boolean | 3,
Less = F_Boolean | 4,
//Increment = 0x70, // basically increment by, ++ simply 'makes up' the 1.
// = 0x71, // same here broren min
F_Collection = 0x70,
Array = F_Collection | 1,
List = F_Collection | 2,
Structure = F_Collection | 3,
GetItem = F_Collection | 4,
SetItem = F_Collection | 5,
AddItem = F_Collection | 6,
RemoveItem = F_Collection | 7,
Array = 0xc0,
List = 0xc1,
Structure = 0xc2,
GetItem = 0xc3,
SetItem = 0xc4,
AddItem = 0xc5,
RemoveItem = 0xc6,
F_Control = 0x80,
Return = F_Control | 1,
Jump = F_Control | 2,
JumpIfFalse = F_Control | 3,
Loop = F_Control | 4,
Invoke = F_Control | 5,
InvokeBase = F_Control | 6,
InvokeMember = F_Control | 7,
Context = F_Control | 8,
Return = 0x80,
Jump = 0x81,
JumpIfFalse = 0x82,
Loop = 0x83,
Invoke = 0x84,
InvokeBase = 0x85,
InvokeMember = 0x86,
Context = 0x87,
F_Class = 0x90,
Class = F_Class | 1,
Member = F_Class | 2,
Base = F_Class | 3,
Inherit = F_Class | 4,
Method = F_Class | 5,
Static = F_Class | 6,
Class = 0xa0,
Member = 0xa1,
Base = 0xa2,
Inherit = 0xa3,
Method = 0xa4,
Static = 0xa5,
F_Meta = 0xa0,
Print = F_Meta | 1,
PrintStack = F_Meta | 2,
PrintGlobals = F_Meta | 3,
PrintExpr = F_Meta | 4,
Typeof = F_Meta | 5,
Print = 0xe0,
PrintStack = 0xe1,
PrintGlobals = 0xe2,
PrintExpr = 0xe3,
Typeof = 0xef,
F_Module = 0xf0,
Export = F_Module | 1, // probably shouldnt be an op code but rather be done during digestion
Import = F_Module | 2, // probably shouldnt be an op code but rather be done during digestion
}
Export = 0xfe, // probably shouldnt be an op code but rather be done during digestion
Import = 0xff, // probably shouldnt be an op code but rather be done during digestion
public static class OpCodeExtensions
{
public static bool IsUnary(this OpCode op)
=> op is OpCode.Not or OpCode.Negate or OpCode.BitwiseNot;
public static bool IsHandledByAlu(this OpCode op)
=> (op & OpCode.Mask_Handler) is OpCode.F_Arithmetic or OpCode.F_Bitwise or OpCode.F_Boolean;
public static string ToString(this OpCode op)
=> $"{op & OpCode.Mask_Handler}: {op} ({(byte)op:x2})";
}

View File

@ -130,7 +130,7 @@ public class Runner : IDisposable
return result;
}
private bool TestFlag(Op code, Op flag) => (code & Op.F_Mask) == flag;
private bool TestHandler(Op code, Op handler) => (code & Op.Mask_Handler) == handler;
private ExecutionResult Execute()
{
@ -186,7 +186,7 @@ public class Runner : IDisposable
case Op.GetGlobal: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name))
return Error($"tried to set global variable with empty name");
return Error($"tried to get global variable with empty name");
if (!Globals.Has(name))
Push(Value.Void);
else
@ -306,14 +306,16 @@ public class Runner : IDisposable
if (!items.Is(T.ItemProvider, false))
return Error($"Can not access {index} of {items} - it is not an ItemProvider! (Array, List, etc.)");
if (items.Is(T.Array))
Push(items.Ptr.As<Values.Objects.Array>()!.Get(index));
else if (items.Is(T.List))
Push(items.Ptr.As<Values.Objects.List>()!.Get(index));
else if (items.Is(T.Structure))
Push(items.Ptr.As<Values.Objects.Structure>()!.Get(index));
else
return Error($"Unsupported native accessor for type {items.Type}!");
if (items.Is(T.Array))
Push(items.Ptr.As<Values.Objects.Array>()!.Get(index));
else if (items.Is(T.List))
Push(items.Ptr.As<List>()!.Get(index));
else if (items.Is(T.Structure))
Push(items.Ptr.As<Structure>()!.Get(index));
else if (items.Is(T.String))
Push(items.Ptr.As<String>()!.SubString(index, new Value(1L)));
else
return Error($"Unsupported native accessor for type {items.Type}!");
break;
}
@ -682,18 +684,6 @@ public class Runner : IDisposable
_stack.Push(value);
}
private Operation PopOperation(Op code)
{
#if LOG
_logger.Method(code);
#endif
if ((code & Op.F_Unary) == Op.F_Unary)
return new Operation(code, Pop(), default);
Value right = Pop();
Value left = Pop();
return new Operation(code, left, right);
}
private ExecutionResult Error(string message, object? context = null, bool @throw = true)
{
#if LOG

View File

@ -6,6 +6,8 @@ using System.Reflection;
namespace Qrakhen.Qamp.Core.Values;
// todo: do the same for native methods. make it more abstract. i like the approach.
public delegate Value ExtensionDelegate(Value target, Value[] parameters);
public class ExtensionException(string message, object? context = null) : QampException(message, context);

View File

@ -3,6 +3,86 @@ using System.Reflection;
namespace Qrakhen.Qamp.Core.Values.Native;
public class NativeMemberAttribute(string? name = null) : Attribute
{
public string? Name { get; } = name;
}
public class NativeMember(string name, MemberInfo info)
{
public string Name { get; } = name;
public MemberInfo MemberInfo { get; } = info;
}
public class NativeProperty(string name,
PropertyInfo info,
bool readOnly) : NativeMember(name, info)
{
public bool ReadOnly { get; } = readOnly;
public PropertyInfo PropertyInfo => (MemberInfo as PropertyInfo)!;
}
public class NativeMethod(string name,
MethodInfo info) : NativeMember(name, info)
{
public MethodInfo MethodInfo => (MemberInfo as MethodInfo)!;
}
public static class NativeLinker
{
private static readonly Dictionary<Type, Dictionary<string, NativeMember>> _linked = [];
public static Value GetMember<TObj>(TObj obj, string name)
{
return Value.Void;
}
public static void SetMember<TObj>(TObj obj, string name, Value value)
{
}
public static Value CallMember<TObj>(TObj obj, string name, Value[] args)
{
return Value.Void;
}
private static void Compile(Type type)
{
var members = type.GetMembers();
var table = new Dictionary<string, NativeMember>();
foreach (var member in members)
{
var attr = member.GetCustomAttribute<NativeMemberAttribute>();
if (attr == null)
continue;
NativeMember? native = null;
if (member is PropertyInfo propertyInfo)
{
native = new NativeProperty(attr.Name ?? member.Name,
propertyInfo,
propertyInfo.CanWrite);
}
if (member is MethodInfo methodInfo)
{
native = new NativeMethod(attr.Name ?? methodInfo.Name,
methodInfo);
}
if (native == null)
throw new Exception("meh.");
table[native.Name] = native;
}
_linked[type] = table;
}
}
/*
public delegate Value Getter(Obj? self);
public delegate void Setter(Obj? self, Value value);
public delegate Value Method(Obj? self, Value[] args);
@ -73,4 +153,4 @@ public static class Natives
{
Register(type, new NativeMethod(info.Name, info.IsStatic, method));
}
}
}*/

View File

@ -4,10 +4,12 @@ namespace Qrakhen.Qamp.Core.Values.Objects;
public class ObjTable(int size = 4096)
{
public static readonly ObjTable Shared = new ObjTable(4096);
public static readonly ObjTable Global = new ObjTable(4096);
private readonly Table<Obj> _table = new Table<Obj>(size);
public IEnumerable<KeyValuePair<Address, Obj>> Entries => _table;
public Obj? Get(Address address) => _table[address];
public T? Get<T>(Address address) where T : Obj => _table[address] as T;
public bool TryGet(Address address, out Obj? obj) => _table.TryGet(address, out obj);
@ -28,7 +30,7 @@ public class Obj : ITypedValue
public Obj(ValueType type)
{
Type = type;
__Address = ObjTable.Shared.Register(this);
__Address = ObjTable.Global.Register(this);
}
public static Value Create(Obj obj)

View File

@ -11,12 +11,52 @@ public class String(string? value) : Obj(ValueType.String)
public override string ToString() => Value ?? "null";
public Value SubString(Value start, Value length)
{
if (Value == null)
throw new RuntimeException($"Can not call substring on an empty string.");
int index = start.IsInteger ?
(int)start.Signed :
throw new RuntimeException($"{nameof(String)}.{nameof(SubString)} requires first parameter 'start' to be an integer.");
if (index < 0 || index >= Value.Length)
throw new RuntimeException($"Parameter 'start' {start} was out of bounds.");
if (length.IsVoid)
return String.Make(Value.Substring(index));
int _length = length.IsInteger ?
(int)length.Signed :
throw new RuntimeException($"{nameof(String)}.{nameof(SubString)} requires second parameter 'length' to be an integer.");
return String.Make(Value.Substring(index, _length));
}
public Value IndexOf(Value needle)
{
if (!needle.IsString)
throw new RuntimeException($"Parameter 'needle' is expected to be of type string, got {needle} instead.");
return new Value(Value?.IndexOf(needle.Ptr.As<String>()!.Value!) ?? -1L);
}
public Value Split(Value delimiter)
{
return Values.Value.Void;
}
public Value Length()
{
return new Value(Value?.Length ?? 0L);
}
public uint GetHash()
{
if (string.IsNullOrEmpty(value))
if (string.IsNullOrEmpty(Value))
return 0;
return Value.GetHash();
return Value?.GetHash() ?? 0;
}
public static Value Make(string? value)

View File

@ -9,13 +9,11 @@ namespace Qrakhen.Qamp.Core.Values;
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
public readonly struct Ptr
{
private static readonly Table<Obj> _register = []; // should not be needed that anymore tbh.
[Serialized] public readonly Address Address;
public Obj? Value {
get {
if (ObjTable.Shared.TryGet(Address, out Obj? obj))
if (ObjTable.Global.TryGet(Address, out Obj? obj))
return obj;
return null;
}
@ -28,7 +26,7 @@ public readonly struct Ptr
public static Ptr Create(Obj obj)
{
return new Ptr(_register.Add(obj));
return new Ptr(obj.__Address);
}
public T? As<T>(bool throwWhenNull = true) where T : Obj
@ -52,7 +50,7 @@ public readonly struct Ptr
public static async Task __GC_Prepare()
{
await Task.Run(() => {
foreach (var obj in _register) {
foreach (var obj in ObjTable.Global.Entries) {
obj.Value.__GC_Count = 0;
}
});
@ -61,7 +59,7 @@ public readonly struct Ptr
public static byte[] SerializeFunctions()
{
List<byte> result = new();
foreach (var element in _register) {
foreach (var element in ObjTable.Global.Entries) {
Obj obj = element.Value;
if (obj == null || obj.__GC_Marked)
continue;

View File

@ -87,10 +87,12 @@ public readonly struct Value :
public T ValueType => Type;
public TypeInfo TypeInfo => TypeInfo.FromValue(this);
public bool IsVoid => Type == T.Void;
public bool IsNumber => Type <= T.Decimal;
public bool IsSigned => Is(T.Signed);
public bool IsUnsigned => Is(T.Unsigned);
public bool IsDecimal => Is(T.Decimal);
public bool IsInteger => IsSigned || IsUnsigned;
public bool IsBool => Is(T.Bool);
public bool IsString => Is(T.String);
public bool IsRef => Is(T.Reference, false);