diff --git a/Qrakhen.Qamp.Core/Collections/Table.cs b/Qrakhen.Qamp.Core/Collections/Table.cs index b18b371..d95d1fa 100644 --- a/Qrakhen.Qamp.Core/Collections/Table.cs +++ b/Qrakhen.Qamp.Core/Collections/Table.cs @@ -17,7 +17,7 @@ public class Table : IToArray, IEnumerable> { - 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; diff --git a/Qrakhen.Qamp.Core/Console.cs b/Qrakhen.Qamp.Core/Console.cs index 04b1bb3..c10d27a 100644 --- a/Qrakhen.Qamp.Core/Console.cs +++ b/Qrakhen.Qamp.Core/Console.cs @@ -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); \ No newline at end of file +public delegate string?[] MessageFormatter(params object[] parameters); \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Execution/ArithmeticResolver.cs b/Qrakhen.Qamp.Core/Execution/ArithmeticResolver.cs index fd9838b..6e0345f 100644 --- a/Qrakhen.Qamp.Core/Execution/ArithmeticResolver.cs +++ b/Qrakhen.Qamp.Core/Execution/ArithmeticResolver.cs @@ -14,15 +14,15 @@ public class ArithmeticResolver : IOperationResolver private readonly Register _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); diff --git a/Qrakhen.Qamp.Core/Execution/OpCode.cs b/Qrakhen.Qamp.Core/Execution/OpCode.cs index af47099..bee8f53 100644 --- a/Qrakhen.Qamp.Core/Execution/OpCode.cs +++ b/Qrakhen.Qamp.Core/Execution/OpCode.cs @@ -1,92 +1,131 @@ namespace Qrakhen.Qamp.Core.Execution; +public class SerializedEnumAttribute(Type serializedType) : Attribute +{ + public Type SerializedType { get; } = serializedType; +} + +/// +/// +/// +/// +/// 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 +/// +[SerializedEnum(typeof(byte))] public enum OpCode { - F_Mask = 0xF0, + /// + /// Mask bits, to be used like opCode & F_Mask == F_Operation, for example. + /// + 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})"; } \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Execution/Runner.cs b/Qrakhen.Qamp.Core/Execution/Runner.cs index 12428a3..b80fcdd 100644 --- a/Qrakhen.Qamp.Core/Execution/Runner.cs +++ b/Qrakhen.Qamp.Core/Execution/Runner.cs @@ -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()!.Get(index)); - else if (items.Is(T.List)) - Push(items.Ptr.As()!.Get(index)); - else if (items.Is(T.Structure)) - Push(items.Ptr.As()!.Get(index)); - else - return Error($"Unsupported native accessor for type {items.Type}!"); + if (items.Is(T.Array)) + Push(items.Ptr.As()!.Get(index)); + else if (items.Is(T.List)) + Push(items.Ptr.As()!.Get(index)); + else if (items.Is(T.Structure)) + Push(items.Ptr.As()!.Get(index)); + else if (items.Is(T.String)) + Push(items.Ptr.As()!.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 diff --git a/Qrakhen.Qamp.Core/Values/ExtensionMethod.cs b/Qrakhen.Qamp.Core/Values/ExtensionMethod.cs index a811a85..828a74f 100644 --- a/Qrakhen.Qamp.Core/Values/ExtensionMethod.cs +++ b/Qrakhen.Qamp.Core/Values/ExtensionMethod.cs @@ -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); diff --git a/Qrakhen.Qamp.Core/Values/Native/NativeMember.cs b/Qrakhen.Qamp.Core/Values/Native/NativeMember.cs index 27762e9..e26ff88 100644 --- a/Qrakhen.Qamp.Core/Values/Native/NativeMember.cs +++ b/Qrakhen.Qamp.Core/Values/Native/NativeMember.cs @@ -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> _linked = []; + + public static Value GetMember(TObj obj, string name) + { + return Value.Void; + } + + public static void SetMember(TObj obj, string name, Value value) + { + + } + + public static Value CallMember(TObj obj, string name, Value[] args) + { + + return Value.Void; + } + + private static void Compile(Type type) + { + var members = type.GetMembers(); + var table = new Dictionary(); + foreach (var member in members) + { + var attr = member.GetCustomAttribute(); + 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)); } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Values/Objects/Obj.cs b/Qrakhen.Qamp.Core/Values/Objects/Obj.cs index 8ad8d7b..f97664d 100644 --- a/Qrakhen.Qamp.Core/Values/Objects/Obj.cs +++ b/Qrakhen.Qamp.Core/Values/Objects/Obj.cs @@ -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 _table = new Table(size); + public IEnumerable> Entries => _table; + public Obj? Get(Address address) => _table[address]; public T? Get(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) diff --git a/Qrakhen.Qamp.Core/Values/Objects/String.cs b/Qrakhen.Qamp.Core/Values/Objects/String.cs index 5f56d9c..cf89807 100644 --- a/Qrakhen.Qamp.Core/Values/Objects/String.cs +++ b/Qrakhen.Qamp.Core/Values/Objects/String.cs @@ -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()!.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) diff --git a/Qrakhen.Qamp.Core/Values/Ptr.cs b/Qrakhen.Qamp.Core/Values/Ptr.cs index 820df21..acc76b5 100644 --- a/Qrakhen.Qamp.Core/Values/Ptr.cs +++ b/Qrakhen.Qamp.Core/Values/Ptr.cs @@ -9,13 +9,11 @@ namespace Qrakhen.Qamp.Core.Values; [StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))] public readonly struct Ptr { - private static readonly Table _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(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 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; diff --git a/Qrakhen.Qamp.Core/Values/Value.cs b/Qrakhen.Qamp.Core/Values/Value.cs index 2629059..e01cf10 100644 --- a/Qrakhen.Qamp.Core/Values/Value.cs +++ b/Qrakhen.Qamp.Core/Values/Value.cs @@ -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);