Compare commits

..

No commits in common. "44b8bb89002ca2bc68754aece8effcc57b42ad8b" and "c42a9a42e6386c7367ea80ef286a025002bec83c" have entirely different histories.

18 changed files with 211 additions and 730 deletions

View File

@ -33,14 +33,14 @@ public static class Benchmark
if (!_clocks.TryGetValue(key, out Stopwatch? sw)) { if (!_clocks.TryGetValue(key, out Stopwatch? sw)) {
sw = _clocks[key] = new Stopwatch(); sw = _clocks[key] = new Stopwatch();
IO.Console.Write($" ::: Registered new benchmark clock '{key}'"); IO.Console.Write($" ::: Registered new benchmark clock '{key}'\n");
} }
if (!string.IsNullOrEmpty(message)) if (!string.IsNullOrEmpty(message))
IO.Console.Write($" ::: {key}:{line} > {message}"); IO.Console.Write($" ::: {key}:{line} > {message}\n");
if (sw.IsRunning) if (sw.IsRunning)
IO.Console.Write($" ::: {key}:{line} > already running with an elapsed time of {sw.Elapsed}. clock was reset."); IO.Console.Write($" ::: {key}:{line} > already running with an elapsed time of {sw.Elapsed}. clock was reset.\n");
sw.Reset(); sw.Reset();
sw.Start(); sw.Start();
@ -60,12 +60,16 @@ public static class Benchmark
string key = MakeKey(id, member, file); string key = MakeKey(id, member, file);
if (!_clocks.TryGetValue(key, out Stopwatch sw)) { if (!_clocks.TryGetValue(key, out Stopwatch sw)) {
_logger.Debug($"No clock found for '{key}', start one first using Benchmark.Start()"); #if LOG
_logger.Debug($"No clock found for '{key}', start one first using Benchmark.Start()");
#endif
return 0; return 0;
} }
sw.Stop(); sw.Stop();
IO.Console.Write($" ::: {key}:{line} > {message ?? "Elapsed"}: {sw.Elapsed}"); #if LOG
_logger.Debug($" ::: {key}:{line} > {message ?? "Elapsed"}: {sw.Elapsed}");
#endif
if (keep) if (keep)
sw.Start(); sw.Start();

View File

@ -1,184 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
using Qrakhen.Qamp.Core.Values;
using System;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
namespace Qrakhen.Qamp.Core.Collections;
/// <summary>
/// Similar to a register with <see cref="Address"/> as key;
/// But with the caveat that the memory slots can be allocated or freed up,
/// automatically seeking for free addresses when adding data,
/// making it much easier to deal with addresses or pointers.
/// </summary>
public class Table<TValue> :
IGetSet<Address, TValue>,
IToArray<TValue>,
IEnumerable<KeyValuePair<Address, TValue>>
{
public const int BLOCK_SIZE = sizeof(int) * 8;
private const uint BLOCK_FULL = (uint)((1UL << 32) - 1);
private readonly TValue[] _data;
private readonly uint[] _alloc;
public int Size { get; }
public int Blocks => Size / BLOCK_SIZE;
public TValue this[Address index]
{
get => Get(index);
set => Set(index, value);
}
public Table(int size = 4096)
{
Size = size;
if (size % BLOCK_SIZE != 0)
throw new ArgumentException($"{nameof(Table<TValue>)} size parameter must be product (multiple) of its block size {BLOCK_SIZE}.");
_data = new TValue[size];
_alloc = new uint[size / BLOCK_SIZE];
}
private int GetBlockIndex(Address address)
{
return (int)(address / BLOCK_SIZE);
}
private int GetBitIndex(Address address)
{
return (int)(address % BLOCK_SIZE);
}
public bool IsAllocated(Address address)
{
return (_alloc[GetBlockIndex(address)] & (1 << GetBitIndex(address))) > 0;
}
private void SetAllocBit(Address address, bool value)
{
int block = GetBlockIndex(address);
int index = GetBitIndex(address);
if (value)
_alloc[block] |= 1U << index;
else
_alloc[block] &= ~(1U << index);
}
private Address SeekFree(Address from = default)
{
Address cur = from - (from % BLOCK_SIZE);
while (cur < Size)
{
int block = GetBlockIndex(cur);
uint mask = _alloc[block];
if (mask < BLOCK_FULL)
{
int index = 0;
while ((mask & (1 << index)) > 0)
index++;
return (Address)((long)block * BLOCK_SIZE + index);
}
cur += BLOCK_SIZE;
}
return Address.Void;
}
private bool Validate(Address address)
{
if (address < 0 || address >= Size)
throw new RuntimeException($"Tried to access address outside memory {address}");
return true;
}
public void Free(Address address, int size = 1)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(1, size); // only support single address freeing atm
SetAllocBit(address, false);
}
public Address Allocate(int size = 1)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(1, size); // only support single address allocation atm
Address address = SeekFree();
SetAllocBit(address, true);
return address;
}
public TValue[] ToArray()
{
return [.. _data];
}
public TValue Get(Address index)
{
Validate(index);
if (!IsAllocated(index))
return default;
return _data[index];
}
public bool TryGet(Address index, [MaybeNullWhen(false)] out TValue? value)
{
Validate(index);
value = default;
if (!IsAllocated(index))
return false;
value = _data[index];
return true;
}
public void Set(Address index, TValue value)
{
Validate(index);
SetAllocBit(index, true);
_data[index] = value;
}
/// <summary>
/// Calls <see cref="Allocate(int)"/> and then <see cref="Set(Address, TValue)"/>,
/// returning the address at which <paramref name="value"/> was stored at.
/// </summary>
public Address Add(TValue value)
{
Address free = SeekFree();
if (free == Address.Void)
throw new QampException($"Tried to add {value} to a memory block, but no free memory could be allocated.");
Set(free, value);
return free;
}
public string Print()
{
string r = $"Table [{nameof(TValue)}]:\n";
int c = 0;
for (int b = 0; b < Blocks; b++)
{
uint block = _alloc[b];
if (block == 0)
continue;
for (int i = 0; i < BLOCK_SIZE; i++)
{
Address address = MakeAddress(b, i);
if (!IsAllocated(address))
continue;
c++;
r += $" {address}: {Get(address)}\n";
}
}
r += $"{c}/{Size - c} slots allocated.";
return r;
}
public static Address MakeAddress(int block, int index) => new((long)block * BLOCK_SIZE + index);
public IEnumerator<KeyValuePair<Address, TValue>> GetEnumerator()
{
for (Address addr = 0; addr < Size; addr++)
yield return new KeyValuePair<Address, TValue>(addr, Get(addr));
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@ -76,8 +76,6 @@ public class Digester : ISteppable<Token>
public Function Digest() public Function Digest()
{ {
Benchmark.Active = true;
Benchmark.Start($"Digest begin");
#if LOG #if LOG
_logger.Method(); _logger.Method();
#endif #endif
@ -103,7 +101,6 @@ public class Digester : ISteppable<Token>
#if LOG #if LOG
_logger.Verbose(_debug.Debug()); _logger.Verbose(_debug.Debug());
#endif #endif
Benchmark.End($"Digest end");
return function; return function;
} }

View File

@ -299,9 +299,6 @@ public static class ExpressionParser
case TokenType.BitwiseNot: case TokenType.BitwiseNot:
digester.Emit(OpCode.BitwiseNot); digester.Emit(OpCode.BitwiseNot);
break; break;
case TokenType.AddressOf:
digester.Emit(OpCode.Addr);
break;
} }
} }
@ -360,7 +357,6 @@ public static class ExpressionParser
_rules[TokenType.Or] = new Rule(null, Or, Weight.Or); _rules[TokenType.Or] = new Rule(null, Or, Weight.Or);
_rules[TokenType.Print] = new Rule(null, null, Weight.None); _rules[TokenType.Print] = new Rule(null, null, Weight.None);
_rules[TokenType.TypeOf] = new Rule(TypeOf, null, Weight.None); _rules[TokenType.TypeOf] = new Rule(TypeOf, null, Weight.None);
_rules[TokenType.AddressOf] = new Rule(Modifier, null, Weight.Term);
_rules[TokenType.Export] = new Rule(null, null, Weight.None); _rules[TokenType.Export] = new Rule(null, null, Weight.None);
_rules[TokenType.Import] = new Rule(null, null, Weight.None); _rules[TokenType.Import] = new Rule(null, null, Weight.None);
_rules[TokenType.Return] = new Rule(null, null, Weight.None); _rules[TokenType.Return] = new Rule(null, null, Weight.None);

View File

@ -1,57 +1,20 @@
using static System.Runtime.InteropServices.JavaScript.JSType; namespace Qrakhen.Qamp.Core.IO;
namespace Qrakhen.Qamp.Core.IO;
public static class Console public static class Console
{ {
public static OutputStreamMessageHandler StdOut; // TEMP
public static ErrorStreamMessageHandler StdErr; public static Action<string>? StdOut;
public static InputStreamListenHandler StdIn; // TEMP
public static MessageFormatter Formatter;
static Console() public static void Write(object? any)
{ {
StdOut = System.Console.Out.WriteLine; string[] split = $"{any ?? "null"}".Split('\n');
StdErr = System.Console.Error.WriteLine; for (int i = 0; i < split.Length; i++) {
StdIn = System.Console.In.ReadLine; string line = (i == 0 ? " :> " : " ") + $"{split[i]}\n";
Formatter = (p) => { if (StdOut == null)
if (p.Length == 0) System.Console.Write(line);
return []; else
return p.Select(x => x.ToString()).ToArray() ?? []; StdOut(line);
}; }
} }
}
public static void Write(params object[] parameters)
{
if (StdOut == null)
return;
var lines = Formatter(parameters);
foreach (var line in lines)
StdOut(line);
}
public static string? Read()
{
if (StdIn == null)
throw new InvalidOperationException($"No stdin provided, can not read input stream.");
return StdIn();
}
public static void Error(params object[] parameters)
{
if (StdErr == null)
Write(parameters);
var lines = Formatter(parameters);
foreach (var line in lines)
StdErr(line);
}
}
public delegate string? InputStreamListenHandler();
public delegate void OutputStreamMessageHandler(string? message);
public delegate void ErrorStreamMessageHandler(string? message);
public delegate string?[] MessageFormatter(params object[] parameters);

View File

@ -1,131 +1,90 @@
namespace Qrakhen.Qamp.Core.Execution; 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 public enum OpCode
{ {
/// <summary> F_Mask = 0xF0,
/// Mask bits, to be used like <c>opCode &amp; F_Mask == F_Operation</c>, for example.
/// </summary>
Mask_Handler = 0xF0,
Mask_Operation = 0x0F,
Error = -1, None = 0x00,
Void = 0, Constant = 0x01,
Null = 0x02,
Pop = 0x03,
Cast = 0x04,
F_Static = 0x10, False = 0x0e,
None = F_Static | 1, True = 0x0f,
True = F_Static | 2,
False = F_Static | 3,
Constant = F_Static | 4,
Null = F_Static | 5,
Cast = F_Static | 6,
Pop = F_Static | 7, Val = 0x10,
Ref = 0x11,
F_Retrieve = 0x20, SetGlobal = 0x20,
Val = F_Retrieve | 1, GetGlobal = 0x21,
Ref = F_Retrieve | 2, GetLocal = 0x22,
Addr = F_Retrieve | 3, SetLocal = 0x23,
GetOuter = 0x24,
SetOuter = 0x25,
GetMember = 0x26,
SetMember = 0x27,
F_Names = 0x30, DefineGlobal = 0x30,
SetGlobal = F_Names | 1, CloseOuter = 0x31,
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 = 0x0, // unsure AssignValue = 0x40, // unsure
//AssignReference = 0x0, // unsure AssignReference = 0x41, // unsure
F_Arithmetic = 0x40, F_Operation = 0x60,
Add = F_Arithmetic | 1, Add = 0x60,
Subtract = F_Arithmetic | 2, Subtract = 0x61,
Multiply = F_Arithmetic | 3, Multiply = 0x62,
Divide = F_Arithmetic | 4, Divide = 0x63,
Modulo = F_Arithmetic | 5, Modulo = 0x64,
Negate = F_Arithmetic | 7, BitwiseAnd = 0x65,
BitwiseOr = 0x66,
BitwiseXor = 0x67,
BitwiseLeft = 0x68,
BitwiseRight = 0x69,
F_Unary = 0x0a,
Not = 0x6a,
Negate = 0x6b,
BitwiseNot = 0x6c,
F_Bitwise = 0x50, F_Compare = 0x50,
BitwiseAnd = F_Bitwise | 1, Equal = 0x50,
BitwiseOr = F_Bitwise | 2, Greater = 0x51,
BitwiseXor = F_Bitwise | 3, Less = 0x52,
BitwiseLeft = F_Bitwise | 4,
BitwiseRight = F_Bitwise | 5,
BitwiseNot = F_Bitwise | 6,
F_Boolean = 0x60, //Increment = 0x70, // basically increment by, ++ simply 'makes up' the 1.
Not = F_Boolean | 1, // = 0x71, // same here broren min
Equal = F_Boolean | 2,
Greater = F_Boolean | 3,
Less = F_Boolean | 4,
F_Collection = 0x70, Array = 0xc0,
Array = F_Collection | 1, List = 0xc1,
List = F_Collection | 2, Structure = 0xc2,
Structure = F_Collection | 3, GetItem = 0xc3,
GetItem = F_Collection | 4, SetItem = 0xc4,
SetItem = F_Collection | 5, AddItem = 0xc5,
AddItem = F_Collection | 6, RemoveItem = 0xc6,
RemoveItem = F_Collection | 7,
F_Control = 0x80, Return = 0x80,
Return = F_Control | 1, Jump = 0x81,
Jump = F_Control | 2, JumpIfFalse = 0x82,
JumpIfFalse = F_Control | 3, Loop = 0x83,
Loop = F_Control | 4, Invoke = 0x84,
Invoke = F_Control | 5, InvokeBase = 0x85,
InvokeBase = F_Control | 6, InvokeMember = 0x86,
InvokeMember = F_Control | 7, Context = 0x87,
Context = F_Control | 8,
F_Class = 0x90, Class = 0xa0,
Class = F_Class | 1, Member = 0xa1,
Member = F_Class | 2, Base = 0xa2,
Base = F_Class | 3, Inherit = 0xa3,
Inherit = F_Class | 4, Method = 0xa4,
Method = F_Class | 5, Static = 0xa5,
Static = F_Class | 6,
F_Meta = 0xa0, Print = 0xe0,
Print = F_Meta | 1, PrintStack = 0xe1,
PrintStack = F_Meta | 2, PrintGlobals = 0xe2,
PrintGlobals = F_Meta | 3, PrintExpr = 0xe3,
PrintExpr = F_Meta | 4, Typeof = 0xef,
Typeof = F_Meta | 5,
F_Module = 0xf0, Export = 0xfe, // probably shouldnt be an op code but rather be done during digestion
Export = F_Module | 1, // 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
Import = F_Module | 2, // 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

@ -25,66 +25,17 @@ public class Call
} }
} }
public interface IOperationResolver
{
bool CanResolve(OpCode code);
OperationResult Resolve(Runner runner, OpCode code);
}
public struct OperationResult
{
public bool Success;
public Value Value;
public RuntimeException? Error;
public static OperationResult MakeSuccess(Value value)
{
return new OperationResult { Success = true, Value = value };
}
public static OperationResult MakeError(RuntimeException exception)
{
return new OperationResult { Success = false, Value = Value.Void, Error = exception };
}
}
public class OperationRouter
{
private readonly List<IOperationResolver> _handlers = [];
private readonly Dictionary<OpCode, IOperationResolver> _cache = [];
public void Register(IOperationResolver resolver)
{
_handlers.Add(resolver);
}
public IOperationResolver? Route(Runner runner, OpCode opCode)
{
if (!_cache.TryGetValue(opCode, out IOperationResolver? resolver)) {
resolver = _handlers.FirstOrDefault(h => h.CanResolve(opCode));
if (resolver == null)
return null;
_cache[opCode] = resolver;
}
return _cache[opCode];
}
}
public class Runner : IDisposable public class Runner : IDisposable
{ {
private readonly ILogger _logger = LoggerService.Get<Runner>(); private readonly ILogger _logger = LoggerService.Get<Runner>();
private readonly OperationRouter _router = new OperationRouter();
public readonly Options Options; public readonly Options Options;
private List<IOperationResolver> _resolvers = []; private StackLike<Call> Calls;
private StackLike<Call> _calls;
// todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ? // todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ?
private StackLike<Value> _stack; private StackLike<Value> Stack;
private long _cursor => _stack.Position; private long Cursor => Stack.Position;
private List<Outer> Outers = []; private List<Outer> Outers = [];
@ -95,12 +46,9 @@ public class Runner : IDisposable
public Runner(Options options) public Runner(Options options)
{ {
Options = options; Options = options;
_calls = new StackLike<Call>(options.MaxCalls); Calls = new StackLike<Call>(options.MaxCalls);
_stack = new StackLike<Value>(options.InitialStackSize); Stack = new StackLike<Value>(options.InitialStackSize);
IO.Console.StdOut ??= options.StdOut; IO.Console.StdOut = options.StdOut;
IO.Console.StdIn ??= options.StdIn;
_router.Register(new ArithmeticResolver());
} }
public ExecutionResult Run(Stream stream) public ExecutionResult Run(Stream stream)
@ -108,36 +56,31 @@ public class Runner : IDisposable
#if LOG #if LOG
_logger.Method(); _logger.Method();
#endif #endif
if (_stack.Position != 0) { if (Stack.Position != 0) {
#if LOG #if LOG
_logger.Warn($"Something went wrong, stack cursor is at {_stack.Position}. Resetting Stack."); _logger.Warn($"Something went wrong, stack cursor is at {Stack.Position}. Resetting Stack.");
#endif #endif
_stack = new StackLike<Value>(Options.InitialStackSize); Stack = new StackLike<Value>(Options.InitialStackSize);
} }
Benchmark.Start($"Compile Start");
using Reader reader = new Reader(stream); using Reader reader = new Reader(stream);
Compilation.Digester digester = new(reader); Compilation.Digester digester = new(reader);
Function function = digester.Digest(); Function function = digester.Digest();
Benchmark.End($"Compile End");
if (stream.Length > 64) if (stream.Length > 64)
File.WriteAllBytes($"./func.{DateTime.Now:yyyyMMdd_HHmmss}.sqi", function.Serialize(true)); File.WriteAllBytes($"./func.{DateTime.Now:yyyyMMdd_HHmmss}.sqi", function.Serialize(true));
Context closure = new Context(function); Context closure = new Context(function);
Push(Obj.Create(closure)); Push(Obj.Create(closure));
Invoke(closure, 0); Invoke(closure, 0);
Benchmark.Start($"Execution Start"); return Execute();
ExecutionResult result = Execute();
Benchmark.End($"Execution End");
return result;
} }
private bool TestHandler(Op code, Op handler) => (code & Op.Mask_Handler) == handler; private bool TestFlag(Op code, Op flag) => (code & Op.F_Mask) == flag;
private ExecutionResult Execute() private ExecutionResult Execute()
{ {
#if LOG #if LOG
_logger.Method(); _logger.Method();
#endif #endif
Call call = _calls.Peek(-1); Call call = Calls.Peek(-1);
do { do {
Op opCode = call.Instruction.Next(); Op opCode = call.Instruction.Next();
#if LOG #if LOG
@ -146,15 +89,15 @@ public class Runner : IDisposable
//IO.Console.Write($"{opCode}: \n - {string.Join("\n - ", Stack.ToArray().Subset(0, Stack.Count))}"); //IO.Console.Write($"{opCode}: \n - {string.Join("\n - ", Stack.ToArray().Subset(0, Stack.Count))}");
IOperationResolver? resolver = _router.Route(this, opCode); if (TestFlag(opCode, Op.F_Operation)) {
Value result = ValueOperation.Resolve(PopOperation(opCode));
Push(result);
continue;
}
if (resolver != null) if (TestFlag(opCode, Op.F_Compare)) {
{ Value result = ValueOperation.Resolve(PopOperation(opCode));
OperationResult result = resolver.Resolve(this, opCode); Push(result);
if (!result.Success)
{
_logger.Error(result.Error?.Message);
}
continue; continue;
} }
@ -175,18 +118,10 @@ public class Runner : IDisposable
Push(Value.False); Push(Value.False);
break; break;
case Op.Addr: {
Value value = Pop();
if (!value.IsObj)
return Error($"can not get address of a value on stack");
Push(new Value(value.Ptr.Address));
break;
}
case Op.GetGlobal: { case Op.GetGlobal: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value; string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
return Error($"tried to get global variable with empty name"); return Error($"tried to set global variable with empty name");
if (!Globals.Has(name)) if (!Globals.Has(name))
Push(Value.Void); Push(Value.Void);
else else
@ -306,16 +241,14 @@ public class Runner : IDisposable
if (!items.Is(T.ItemProvider, false)) if (!items.Is(T.ItemProvider, false))
return Error($"Can not access {index} of {items} - it is not an ItemProvider! (Array, List, etc.)"); return Error($"Can not access {index} of {items} - it is not an ItemProvider! (Array, List, etc.)");
if (items.Is(T.Array)) if (items.Is(T.Array))
Push(items.Ptr.As<Values.Objects.Array>()!.Get(index)); Push(items.Ptr.As<Values.Objects.Array>()!.Get(index));
else if (items.Is(T.List)) else if (items.Is(T.List))
Push(items.Ptr.As<List>()!.Get(index)); Push(items.Ptr.As<Values.Objects.List>()!.Get(index));
else if (items.Is(T.Structure)) else if (items.Is(T.Structure))
Push(items.Ptr.As<Structure>()!.Get(index)); Push(items.Ptr.As<Values.Objects.Structure>()!.Get(index));
else if (items.Is(T.String)) else
Push(items.Ptr.As<String>()!.SubString(index, new Value(1L))); return Error($"Unsupported native accessor for type {items.Type}!");
else
return Error($"Unsupported native accessor for type {items.Type}!");
break; break;
} }
@ -370,7 +303,7 @@ public class Runner : IDisposable
byte count = call.Instruction.Next(); byte count = call.Instruction.Next();
if (!Invoke(Peek(-(count + 1)), count)) if (!Invoke(Peek(-(count + 1)), count))
return Error($"Could not invoke function."); return Error($"Could not invoke function.");
call = _calls.Peek(); call = Calls.Peek();
break; break;
} }
@ -379,7 +312,7 @@ public class Runner : IDisposable
byte argumentCount = call.Instruction.Next(); byte argumentCount = call.Instruction.Next();
if (!Invoke(name, argumentCount)) if (!Invoke(name, argumentCount))
return Error($"Could not invoke member {name}."); return Error($"Could not invoke member {name}.");
call = _calls.Peek(); call = Calls.Peek();
break; break;
} }
@ -389,7 +322,7 @@ public class Runner : IDisposable
Class @class = Pop().Ptr.As<Class>()!; Class @class = Pop().Ptr.As<Class>()!;
if (!Invoke(@class, name, argumentCount)) if (!Invoke(@class, name, argumentCount))
return Error($"Could not invoke member {name} of base class {@class.Name}!"); return Error($"Could not invoke member {name} of base class {@class.Name}!");
call = _calls.Peek(); call = Calls.Peek();
break; break;
} }
@ -410,7 +343,7 @@ public class Runner : IDisposable
} }
case Op.CloseOuter: { case Op.CloseOuter: {
CloseOuters(new Pointer<Value>(_stack, _cursor)); CloseOuters(new Pointer<Value>(Stack, Cursor));
Pop(); Pop();
break; break;
} }
@ -418,9 +351,9 @@ public class Runner : IDisposable
case Op.Return: { case Op.Return: {
Value result = Pop(); Value result = Pop();
CloseOuters(call.StackPtr.Branch()); CloseOuters(call.StackPtr.Branch());
_calls.Pop(); Calls.Pop();
if (_calls.Count == 0) { if (Calls.Count == 0) {
if (_stack.Count > 0) { if (Stack.Count > 0) {
// todo: very bad fix for our stack problem // todo: very bad fix for our stack problem
Pop(); Pop();
} else { } else {
@ -428,9 +361,9 @@ public class Runner : IDisposable
} }
return ExecutionResult.OK; return ExecutionResult.OK;
} }
_stack.Decimate(call.StackPtr.Cursor); Stack.Decimate(call.StackPtr.Cursor);
Push(result); Push(result);
call = _calls.Peek(); call = Calls.Peek();
break; break;
} }
@ -481,8 +414,8 @@ public class Runner : IDisposable
} }
case Op.PrintStack: { case Op.PrintStack: {
for (int i = 0; i < _cursor; i++) { for (int i = 0; i < Cursor; i++) {
IO.Console.Write($"{_stack[i].ToString(true)}"); IO.Console.Write($"{Stack[i].ToString(true)}");
} }
break; break;
} }
@ -545,7 +478,7 @@ public class Runner : IDisposable
values[i] = Peek(-i - 1); values[i] = Peek(-i - 1);
} }
_stack.Decimate(_stack.Position - argumentCount - 1); Stack.Decimate(Stack.Position - argumentCount - 1);
Value result = extension.Call(target, values); Value result = extension.Call(target, values);
Push(result); Push(result);
return true; return true;
@ -561,13 +494,13 @@ public class Runner : IDisposable
return false; return false;
} }
if (_calls.Count > Options.MaxCalls) { if (Calls.Count > Options.MaxCalls) {
Error($"Stack overflow {_calls.Count}"); Error($"Stack overflow {Calls.Count}");
return false; return false;
} }
Call call = new(closure, _stack, _cursor - argumentCount - 1); Call call = new(closure, Stack, Cursor - argumentCount - 1);
_calls.Push(call); Calls.Push(call);
return true; return true;
} }
@ -608,7 +541,7 @@ public class Runner : IDisposable
if (value.Is(T.Method)) { if (value.Is(T.Method)) {
Method method = value.Ptr.As<Method>()!; Method method = value.Ptr.As<Method>()!;
_stack.Set(_cursor - method.Function.ArgumentCount - 1, method.Receiver); Stack.Set(Cursor - method.Function.ArgumentCount - 1, method.Receiver);
return Invoke(method, method.Function.ArgumentCount); return Invoke(method, method.Function.ArgumentCount);
} else if (value.Is(T.Context)) { } else if (value.Is(T.Context)) {
Context? context = value.Ptr.As<Context>()!; Context? context = value.Ptr.As<Context>()!;
@ -617,7 +550,7 @@ public class Runner : IDisposable
throw new NotImplementedException("i dont really want globals in this language so lets see"); throw new NotImplementedException("i dont really want globals in this language so lets see");
} else if (value.Is(T.Class)) { } else if (value.Is(T.Class)) {
Class? @class = value.Ptr.As<Class>()!; Class? @class = value.Ptr.As<Class>()!;
_stack.Set(_cursor - argumentCount - 1, Instantiate(@class)); Stack.Set(Cursor - argumentCount - 1, Instantiate(@class));
if (@class.Members.TryGet(@class.Name, out Value ctor)) { if (@class.Members.TryGet(@class.Name, out Value ctor)) {
Context? context = ctor.Ptr.As<Context>()!; Context? context = ctor.Ptr.As<Context>()!;
return Invoke(context, argumentCount); return Invoke(context, argumentCount);
@ -660,28 +593,40 @@ public class Runner : IDisposable
Pop(); Pop();
} }
public Value Peek(int delta = -1) private Value Peek(int delta = -1)
{ {
#if LOG #if LOG
_logger.Method(); _logger.Method();
#endif #endif
return _stack.Peek(delta); return Stack.Peek(delta);
} }
public Value Pop() private Value Pop()
{ {
#if LOG #if LOG
_logger.Method(); _logger.Method();
#endif #endif
return _stack.Pop(); return Stack.Pop();
} }
public void Push(Value value) private void Push(Value value)
{ {
#if LOG #if LOG
_logger.Method($"{value}"); _logger.Method($"{value}");
#endif #endif
_stack.Push(value); 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) private ExecutionResult Error(string message, object? context = null, bool @throw = true)
@ -703,7 +648,7 @@ public class Runner : IDisposable
#if LOG #if LOG
_logger.Method(); _logger.Method();
#endif #endif
_stack.Decimate(0); Stack.Decimate(0);
} }
public void Dispose() public void Dispose()
@ -728,14 +673,14 @@ public enum ExecutionResult
} }
public readonly struct Options( public readonly struct Options(
IO.OutputStreamMessageHandler stdOut = null, Action<string>? stdOut = null,
IO.InputStreamListenHandler stdIn = null, Action<string>? stdIn = null,
int maxCalls = 0x100, int maxCalls = 0x100,
int stackSize = 0x200, int stackSize = 0x200,
int initialStackSize = 0x80) int initialStackSize = 0x80)
{ {
public readonly IO.OutputStreamMessageHandler StdOut = stdOut; public readonly Action<string>? StdOut = stdOut;
public readonly IO.InputStreamListenHandler StdIn = stdIn; public readonly Action<string>? StdIn = stdIn;
public readonly int MaxCalls = maxCalls; public readonly int MaxCalls = maxCalls;
public readonly int StackSize = stackSize; public readonly int StackSize = stackSize;
public readonly int InitialStackSize = initialStackSize; public readonly int InitialStackSize = initialStackSize;

View File

@ -8,36 +8,19 @@ public readonly record struct Operation(OpCode OpCode, Value Left, Value Right);
public delegate Value OperationHandler(Operation operation); public delegate Value OperationHandler(Operation operation);
public class ArithmeticResolver : IOperationResolver public static class ValueOperation
{ {
private readonly ILogger _logger = LoggerService.Get(nameof(ArithmeticResolver)); private static readonly ILogger _logger = LoggerService.Get(nameof(ValueOperation));
private readonly Register<OpCode, OperationHandler> _operations; private static readonly Register<OpCode, OperationHandler> _operations;
public bool CanResolve(OpCode opCode) => opCode.IsHandledByAlu(); public static Value Resolve(Operation operation)
private Operation PopOperation(Runner runner, OpCode code)
{ {
#if LOG
_logger.Method(code);
#endif
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);
}
public OperationResult Resolve(Runner runner, OpCode opCode)
{
Operation operation = PopOperation(runner, opCode);
#if LOG #if LOG
_logger.Method($"{operation.Left} {operation.OpCode} {operation.Right}"); _logger.Method($"{operation.Left} {operation.OpCode} {operation.Right}");
#endif #endif
// p sure switch is faster here (as opposed to dict lookup) // p sure switch is faster here (as opposed to dict lookup)
Value result = operation.OpCode switch { return operation.OpCode switch {
OpCode.Equal => Equal(operation), OpCode.Equal => Equal(operation),
OpCode.Greater => Greater(operation), OpCode.Greater => Greater(operation),
OpCode.Less => Less(operation), OpCode.Less => Less(operation),
@ -56,16 +39,9 @@ public class ArithmeticResolver : IOperationResolver
OpCode.BitwiseXor => BitwiseXor(operation), OpCode.BitwiseXor => BitwiseXor(operation),
_ => throw new NotImplementedException($"Unknown operator {operation.OpCode}.") _ => throw new NotImplementedException($"Unknown operator {operation.OpCode}.")
}; };
#if LOG
_logger.Verbose($"Result: {result}");
#endif
runner.Push(result);
return OperationResult.MakeSuccess(result);
} }
public ArithmeticResolver() static ValueOperation()
{ {
_operations = new Register<OpCode, OperationHandler> { _operations = new Register<OpCode, OperationHandler> {
{ OpCode.Equal, Equal }, { OpCode.Equal, Equal },

View File

@ -285,7 +285,7 @@ public class Reader : IReader<Token>, IDisposable
'&' => Check('&') ? '&' => Check('&') ?
MakeToken(And, buffer + Next()) : MakeToken(And, buffer + Next()) :
Check(':') ? Check(':') ?
MakeToken(AddressOf, buffer + Next()) : MakeToken(Address, buffer + Next()) :
MakeToken(BitwiseAnd, buffer), MakeToken(BitwiseAnd, buffer),
'^' => Check('~') ? '^' => Check('~') ?
MakeToken(Base, buffer + Next()) : MakeToken(Base, buffer + Next()) :
@ -318,9 +318,9 @@ public class Reader : IReader<Token>, IDisposable
MakeToken(Var, buffer + Next()) : MakeToken(Var, buffer + Next()) :
MakeToken(Star, buffer), MakeToken(Star, buffer),
'=' => Check('=') ? '=' => Check('=') ?
MakeToken(EqualEqual, buffer + Next()) : MakeToken(EqualEqual, buffer + Next()) :
MakeToken(Equal, buffer), MakeToken(Equal, buffer),
'<' => Peek(0) switch { '<' => Peek(1) switch {
'~' => MakeToken(Equal, buffer + Next()), '~' => MakeToken(Equal, buffer + Next()),
':' => MakeToken(Return, buffer + Next()), ':' => MakeToken(Return, buffer + Next()),
'+' => MakeToken(PlusEqual, buffer + Next()), '+' => MakeToken(PlusEqual, buffer + Next()),

View File

@ -47,7 +47,7 @@ public enum TokenType
BitwiseNot = Operator | 9, BitwiseNot = Operator | 9,
BitwiseLeft = Operator | 10, BitwiseLeft = Operator | 10,
BitwiseRight = Operator | 11, BitwiseRight = Operator | 11,
AddressOf = Operator | 12, // returns the address of a ptr/object as a value Address = Operator | 12, // returns the address of a ptr/object as a value
Assignment = 1 << 9, Assignment = 1 << 9,
Equal = Assignment | 1, Equal = Assignment | 1,

View File

@ -5,9 +5,7 @@ namespace Qrakhen.Qamp.Core.Values;
[StructLayout(LayoutKind.Sequential, Size = sizeof(long))] [StructLayout(LayoutKind.Sequential, Size = sizeof(long))]
public readonly record struct Address(long Value) public readonly record struct Address(long Value)
{ {
public static readonly Address Void = new Address(-1); public override string ToString() => $"0x{Value:x8}";
public override string ToString() => Value < 0 ? "&void" : $"&{Value:x8}";
public static implicit operator long(Address address) => address.Value; public static implicit operator long(Address address) => address.Value;
public static implicit operator Address(long value) => new(value); public static implicit operator Address(long value) => new(value);

View File

@ -6,8 +6,6 @@ using System.Reflection;
namespace Qrakhen.Qamp.Core.Values; 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 delegate Value ExtensionDelegate(Value target, Value[] parameters);
public class ExtensionException(string message, object? context = null) : QampException(message, context); public class ExtensionException(string message, object? context = null) : QampException(message, context);

View File

@ -3,86 +3,6 @@ using System.Reflection;
namespace Qrakhen.Qamp.Core.Values.Native; 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 Value Getter(Obj? self);
public delegate void Setter(Obj? self, Value value); public delegate void Setter(Obj? self, Value value);
public delegate Value Method(Obj? self, Value[] args); public delegate Value Method(Obj? self, Value[] args);
@ -153,4 +73,4 @@ public static class Natives
{ {
Register(type, new NativeMethod(info.Name, info.IsStatic, method)); Register(type, new NativeMethod(info.Name, info.IsStatic, method));
} }
}*/ }

View File

@ -1,42 +1,23 @@
using Qrakhen.Qamp.Core.Collections; namespace Qrakhen.Qamp.Core.Values.Objects;
namespace Qrakhen.Qamp.Core.Values.Objects; public class Obj(ValueType type) : IValue
public class ObjTable(int size = 4096)
{ {
public static readonly ObjTable Global = new ObjTable(4096); public bool __gcMarked = false;
public int __gcCount = 1;
private readonly Table<Obj> _table = new Table<Obj>(size); public readonly ValueType Type = type;
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);
public void Free(Address address) => _table.Free(address);
public Address Register(Obj obj) => _table.Add(obj);
}
public class Obj : ITypedValue
{
internal bool __GC_Marked = false;
internal int __GC_Count = 1;
internal readonly Address __Address; // unsure lol
public readonly ValueType Type;
public ValueType ValueType => Type; public ValueType ValueType => Type;
public Obj(ValueType type)
{
Type = type;
__Address = ObjTable.Global.Register(this);
}
public static Value Create(Obj obj) public static Value Create(Obj obj)
{ {
return new Value(Ptr.Create(obj)); return new Value(Ptr.Create(obj));
} }
public override string ToString() => "Obj<undefined>"; public override string ToString() => "Obj<undefined>";
static Obj()
{
}
} }

View File

@ -11,52 +11,12 @@ public class String(string? value) : Obj(ValueType.String)
public override string ToString() => Value ?? "null"; 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() public uint GetHash()
{ {
if (string.IsNullOrEmpty(Value)) if (string.IsNullOrEmpty(value))
return 0; return 0;
return Value?.GetHash() ?? 0; return Value.GetHash();
} }
public static Value Make(string? value) public static Value Make(string? value)
@ -76,7 +36,7 @@ public class String(string? value) : Obj(ValueType.String)
List<byte> result = new(); List<byte> result = new();
foreach (var pair in _strings) { foreach (var pair in _strings) {
String str = pair.Value; String str = pair.Value;
if (str == null || str.__GC_Marked) if (str == null || str.__gcMarked)
continue; continue;
byte[] bytes; byte[] bytes;
bytes = Encoding.ASCII.GetBytes(str.Value!); bytes = Encoding.ASCII.GetBytes(str.Value!);

View File

@ -4,29 +4,36 @@ using Qrakhen.Qamp.Core.Collections;
namespace Qrakhen.Qamp.Core.Values; namespace Qrakhen.Qamp.Core.Values;
// generally interesting: GCHandle.ToIntPtr(GCHandle.Alloc(obj, GCHandleType.Normal));
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))] [StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
public readonly struct Ptr public readonly struct Ptr
{ {
private static readonly PushStack<Obj> _register = new();
[Serialized] public readonly Address Address; [Serialized] public readonly Address Address;
//private readonly IntPtr _pointer;
//public IntPtr Pointer => _pointer;
public Obj? Value { public Obj? Value {
get { get {
if (ObjTable.Global.TryGet(Address, out Obj? obj)) if (_register.TryGet(Address, out Obj obj))
return obj; return obj;
return null; return null;
//if (_pointer == IntPtr.Zero)
// throw new ObjectDisposedException(nameof(Ptr));
//return (Obj)GCHandle.FromIntPtr(_pointer).Target!;
} }
} }
private Ptr(Address address) private Ptr(Address address)
{ {
//_pointer = GCHandle.ToIntPtr(GCHandle.Alloc(obj, GCHandleType.Normal));
Address = address; Address = address;
} }
public static Ptr Create(Obj obj) public static Ptr Create(Obj obj)
{ {
return new Ptr(obj.__Address); return new Ptr(_register.Add(obj));
} }
public T? As<T>(bool throwWhenNull = true) where T : Obj public T? As<T>(bool throwWhenNull = true) where T : Obj
@ -50,8 +57,8 @@ public readonly struct Ptr
public static async Task __GC_Prepare() public static async Task __GC_Prepare()
{ {
await Task.Run(() => { await Task.Run(() => {
foreach (var obj in ObjTable.Global.Entries) { foreach (var obj in _register) {
obj.Value.__GC_Count = 0; obj.__gcCount = 0;
} }
}); });
} }
@ -59,9 +66,9 @@ public readonly struct Ptr
public static byte[] SerializeFunctions() public static byte[] SerializeFunctions()
{ {
List<byte> result = new(); List<byte> result = new();
foreach (var element in ObjTable.Global.Entries) { for (int i = 0; i < _register.Count; i++) {
Obj obj = element.Value; Obj obj = _register.Get(i);
if (obj == null || obj.__GC_Marked) if (obj == null || obj.__gcMarked)
continue; continue;
byte[] bytes; byte[] bytes;
if (obj is Function function) { if (obj is Function function) {
@ -72,7 +79,7 @@ public readonly struct Ptr
continue; continue;
} }
result.AddRange(((long)element.Key).GetBytes()); result.AddRange(i.GetBytes());
result.AddRange(bytes.Length.GetBytes()); result.AddRange(bytes.Length.GetBytes());
result.AddRange(bytes); result.AddRange(bytes);
result.Add(0); result.Add(0);

View File

@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.AccessControl;
using Qrakhen.Qamp.Core.Abstractions; using Qrakhen.Qamp.Core.Abstractions;
using Qrakhen.Qamp.Core.Values.Objects; using Qrakhen.Qamp.Core.Values.Objects;
using String = Qrakhen.Qamp.Core.Values.Objects.String; using String = Qrakhen.Qamp.Core.Values.Objects.String;
@ -8,47 +7,13 @@ using T = Qrakhen.Qamp.Core.Values.ValueType;
namespace Qrakhen.Qamp.Core.Values; namespace Qrakhen.Qamp.Core.Values;
public interface IValue
// HEY. IDEA.
// have custom structs for types. this way we can handle many more thing and also more clean no????
public interface IPrimitive
{
public static abstract string Name { get; }
public static abstract int SizeOf { get; }
public ulong Raw { get; }
public string Print();
}
public readonly record struct Signed(long Value)// : IPrimitive
{
}
public interface IValue<T>
{
T Data { get; }
}
public interface ITypedValue
{ {
T ValueType { get; } T ValueType { get; }
} }
public interface IDynamicValue : IValue<ulong>
{
}
[StructLayout(LayoutKind.Explicit, Size = 0x10)] [StructLayout(LayoutKind.Explicit, Size = 0x10)]
public readonly struct Value : public readonly struct Value : IValue, ISerialize<Value>, IDebug<string>
IDynamicValue,
ISerialize<Value>,
IDebug<string>
{ {
public static readonly Value Void = new Value(); public static readonly Value Void = new Value();
public static readonly Value True = new Value(true); public static readonly Value True = new Value(true);
@ -63,8 +28,6 @@ public readonly struct Value :
[FieldOffset(0x00)] [Serialized] public readonly Ptr Ptr; [FieldOffset(0x00)] [Serialized] public readonly Ptr Ptr;
[FieldOffset(0x00)] [Serialized] public readonly Ref Ref; [FieldOffset(0x00)] [Serialized] public readonly Ref Ref;
public ulong Data => Unsigned;
[FieldOffset(0x08)] [Serialized] public readonly T Type; [FieldOffset(0x08)] [Serialized] public readonly T Type;
[FieldOffset(0x0c)] [Serialized] public readonly uint Meta; // like lengths of arrays or lists [FieldOffset(0x0c)] [Serialized] public readonly uint Meta; // like lengths of arrays or lists
@ -87,12 +50,10 @@ public readonly struct Value :
public T ValueType => Type; public T ValueType => Type;
public TypeInfo TypeInfo => TypeInfo.FromValue(this); public TypeInfo TypeInfo => TypeInfo.FromValue(this);
public bool IsVoid => Type == T.Void;
public bool IsNumber => Type <= T.Decimal; public bool IsNumber => Type <= T.Decimal;
public bool IsSigned => Is(T.Signed); public bool IsSigned => Is(T.Signed);
public bool IsUnsigned => Is(T.Unsigned); public bool IsUnsigned => Is(T.Unsigned);
public bool IsDecimal => Is(T.Decimal); public bool IsDecimal => Is(T.Decimal);
public bool IsInteger => IsSigned || IsUnsigned;
public bool IsBool => Is(T.Bool); public bool IsBool => Is(T.Bool);
public bool IsString => Is(T.String); public bool IsString => Is(T.String);
public bool IsRef => Is(T.Reference, false); public bool IsRef => Is(T.Reference, false);

View File

@ -19,7 +19,7 @@ public enum ValueType
Integer = Unsigned | Signed | Char, Integer = Unsigned | Signed | Char,
Primitive = Integer | Decimal | Bool, Primitive = Integer | Decimal | Bool,
Address = 0x0020, Address = 0x0020, // coded with : symbol
// classes etc. here? // classes etc. here?