Compare commits

...

10 Commits

Author SHA1 Message Date
Qrakhen 44b8bb8900 few things, realized i should actually do the native methods like the extension ones 2026-01-08 13:53:48 +01:00
Qrakhen aaec6375eb bit a dis, bit a dat 2026-01-08 09:29:20 +01:00
Qrakhen a66580e7fe like actually rewrite object tables and ptrs and stuff. much happier now 2026-01-08 08:59:27 +01:00
Qrakhen bdd1322a82 add handlers and routing 2026-01-07 18:39:31 +01:00
Qrakhen bb13c0ae2b dynval 2026-01-07 18:10:49 +01:00
Qrakhen f62c48396f add memory class isnt that cool 2026-01-07 14:57:03 +01:00
Qrakhen 880f3e60ce add some very basic foundation for static typing 2025-12-16 10:02:24 +01:00
Qrakhen c4de1dfb0a add stdoutinerr support 2025-12-11 20:29:44 +01:00
Qrakhen 3b50abebe4 add benchmark during digestion 2025-12-11 18:25:01 +01:00
Qrakhen d79cdf9c92 add addr of feature 2025-12-10 16:03:21 +01:00
18 changed files with 730 additions and 211 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}'\n"); IO.Console.Write($" ::: Registered new benchmark clock '{key}'");
} }
if (!string.IsNullOrEmpty(message)) if (!string.IsNullOrEmpty(message))
IO.Console.Write($" ::: {key}:{line} > {message}\n"); IO.Console.Write($" ::: {key}:{line} > {message}");
if (sw.IsRunning) if (sw.IsRunning)
IO.Console.Write($" ::: {key}:{line} > already running with an elapsed time of {sw.Elapsed}. clock was reset.\n"); IO.Console.Write($" ::: {key}:{line} > already running with an elapsed time of {sw.Elapsed}. clock was reset.");
sw.Reset(); sw.Reset();
sw.Start(); sw.Start();
@ -60,16 +60,12 @@ 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)) {
#if LOG
_logger.Debug($"No clock found for '{key}', start one first using Benchmark.Start()"); _logger.Debug($"No clock found for '{key}', start one first using Benchmark.Start()");
#endif
return 0; return 0;
} }
sw.Stop(); sw.Stop();
#if LOG IO.Console.Write($" ::: {key}:{line} > {message ?? "Elapsed"}: {sw.Elapsed}");
_logger.Debug($" ::: {key}:{line} > {message ?? "Elapsed"}: {sw.Elapsed}");
#endif
if (keep) if (keep)
sw.Start(); sw.Start();

View File

@ -0,0 +1,184 @@
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,6 +76,8 @@ 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
@ -101,6 +103,7 @@ 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,6 +299,9 @@ 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;
} }
} }
@ -357,6 +360,7 @@ 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,20 +1,57 @@
namespace Qrakhen.Qamp.Core.IO; using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Qrakhen.Qamp.Core.IO;
public static class Console public static class Console
{ {
// TEMP public static OutputStreamMessageHandler StdOut;
public static Action<string>? StdOut; public static ErrorStreamMessageHandler StdErr;
// TEMP public static InputStreamListenHandler StdIn;
public static MessageFormatter Formatter;
public static void Write(object? any) static Console()
{
StdOut = System.Console.Out.WriteLine;
StdErr = System.Console.Error.WriteLine;
StdIn = System.Console.In.ReadLine;
Formatter = (p) => {
if (p.Length == 0)
return [];
return p.Select(x => x.ToString()).ToArray() ?? [];
};
}
public static void Write(params object[] parameters)
{ {
string[] split = $"{any ?? "null"}".Split('\n');
for (int i = 0; i < split.Length; i++) {
string line = (i == 0 ? " :> " : " ") + $"{split[i]}\n";
if (StdOut == null) if (StdOut == null)
System.Console.Write(line); return;
else
var lines = Formatter(parameters);
foreach (var line in lines)
StdOut(line); 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

@ -8,19 +8,36 @@ public readonly record struct Operation(OpCode OpCode, Value Left, Value Right);
public delegate Value OperationHandler(Operation operation); public delegate Value OperationHandler(Operation operation);
public static class ValueOperation public class ArithmeticResolver : IOperationResolver
{ {
private static readonly ILogger _logger = LoggerService.Get(nameof(ValueOperation)); private readonly ILogger _logger = LoggerService.Get(nameof(ArithmeticResolver));
private static readonly Register<OpCode, OperationHandler> _operations; private readonly Register<OpCode, OperationHandler> _operations;
public static Value Resolve(Operation operation) public bool CanResolve(OpCode opCode) => opCode.IsHandledByAlu();
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)
return operation.OpCode switch { Value result = 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),
@ -39,9 +56,16 @@ public static class ValueOperation
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);
} }
static ValueOperation() public ArithmeticResolver()
{ {
_operations = new Register<OpCode, OperationHandler> { _operations = new Register<OpCode, OperationHandler> {
{ OpCode.Equal, Equal }, { OpCode.Equal, Equal },

View File

@ -1,90 +1,131 @@
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
{ {
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, Error = -1,
Constant = 0x01, Void = 0,
Null = 0x02,
Pop = 0x03,
Cast = 0x04,
False = 0x0e, F_Static = 0x10,
True = 0x0f, 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, Pop = F_Static | 7,
Ref = 0x11,
SetGlobal = 0x20, F_Retrieve = 0x20,
GetGlobal = 0x21, Val = F_Retrieve | 1,
GetLocal = 0x22, Ref = F_Retrieve | 2,
SetLocal = 0x23, Addr = F_Retrieve | 3,
GetOuter = 0x24,
SetOuter = 0x25,
GetMember = 0x26,
SetMember = 0x27,
DefineGlobal = 0x30, F_Names = 0x30,
CloseOuter = 0x31, 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 //AssignValue = 0x0, // unsure
AssignReference = 0x41, // unsure //AssignReference = 0x0, // unsure
F_Operation = 0x60, F_Arithmetic = 0x40,
Add = 0x60, Add = F_Arithmetic | 1,
Subtract = 0x61, Subtract = F_Arithmetic | 2,
Multiply = 0x62, Multiply = F_Arithmetic | 3,
Divide = 0x63, Divide = F_Arithmetic | 4,
Modulo = 0x64, Modulo = F_Arithmetic | 5,
BitwiseAnd = 0x65, Negate = F_Arithmetic | 7,
BitwiseOr = 0x66,
BitwiseXor = 0x67,
BitwiseLeft = 0x68,
BitwiseRight = 0x69,
F_Unary = 0x0a,
Not = 0x6a,
Negate = 0x6b,
BitwiseNot = 0x6c,
F_Compare = 0x50, F_Bitwise = 0x50,
Equal = 0x50, BitwiseAnd = F_Bitwise | 1,
Greater = 0x51, BitwiseOr = F_Bitwise | 2,
Less = 0x52, BitwiseXor = F_Bitwise | 3,
BitwiseLeft = F_Bitwise | 4,
BitwiseRight = F_Bitwise | 5,
BitwiseNot = F_Bitwise | 6,
//Increment = 0x70, // basically increment by, ++ simply 'makes up' the 1. F_Boolean = 0x60,
// = 0x71, // same here broren min Not = F_Boolean | 1,
Equal = F_Boolean | 2,
Greater = F_Boolean | 3,
Less = F_Boolean | 4,
Array = 0xc0, F_Collection = 0x70,
List = 0xc1, Array = F_Collection | 1,
Structure = 0xc2, List = F_Collection | 2,
GetItem = 0xc3, Structure = F_Collection | 3,
SetItem = 0xc4, GetItem = F_Collection | 4,
AddItem = 0xc5, SetItem = F_Collection | 5,
RemoveItem = 0xc6, AddItem = F_Collection | 6,
RemoveItem = F_Collection | 7,
Return = 0x80, F_Control = 0x80,
Jump = 0x81, Return = F_Control | 1,
JumpIfFalse = 0x82, Jump = F_Control | 2,
Loop = 0x83, JumpIfFalse = F_Control | 3,
Invoke = 0x84, Loop = F_Control | 4,
InvokeBase = 0x85, Invoke = F_Control | 5,
InvokeMember = 0x86, InvokeBase = F_Control | 6,
Context = 0x87, InvokeMember = F_Control | 7,
Context = F_Control | 8,
Class = 0xa0, F_Class = 0x90,
Member = 0xa1, Class = F_Class | 1,
Base = 0xa2, Member = F_Class | 2,
Inherit = 0xa3, Base = F_Class | 3,
Method = 0xa4, Inherit = F_Class | 4,
Static = 0xa5, Method = F_Class | 5,
Static = F_Class | 6,
Print = 0xe0, F_Meta = 0xa0,
PrintStack = 0xe1, Print = F_Meta | 1,
PrintGlobals = 0xe2, PrintStack = F_Meta | 2,
PrintExpr = 0xe3, PrintGlobals = F_Meta | 3,
Typeof = 0xef, PrintExpr = F_Meta | 4,
Typeof = F_Meta | 5,
Export = 0xfe, // probably shouldnt be an op code but rather be done during digestion F_Module = 0xf0,
Import = 0xff, // 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 = 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,17 +25,66 @@ 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 StackLike<Call> Calls; private List<IOperationResolver> _resolvers = [];
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 = [];
@ -46,9 +95,12 @@ 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)
@ -56,31 +108,36 @@ 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);
return Execute(); Benchmark.Start($"Execution Start");
ExecutionResult result = Execute();
Benchmark.End($"Execution End");
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() 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
@ -89,15 +146,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))}");
if (TestFlag(opCode, Op.F_Operation)) { IOperationResolver? resolver = _router.Route(this, opCode);
Value result = ValueOperation.Resolve(PopOperation(opCode));
Push(result);
continue;
}
if (TestFlag(opCode, Op.F_Compare)) { if (resolver != null)
Value result = ValueOperation.Resolve(PopOperation(opCode)); {
Push(result); OperationResult result = resolver.Resolve(this, opCode);
if (!result.Success)
{
_logger.Error(result.Error?.Message);
}
continue; continue;
} }
@ -118,10 +175,18 @@ 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 set global variable with empty name"); return Error($"tried to get global variable with empty name");
if (!Globals.Has(name)) if (!Globals.Has(name))
Push(Value.Void); Push(Value.Void);
else else
@ -244,9 +309,11 @@ public class Runner : IDisposable
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<Values.Objects.List>()!.Get(index)); Push(items.Ptr.As<List>()!.Get(index));
else if (items.Is(T.Structure)) else if (items.Is(T.Structure))
Push(items.Ptr.As<Values.Objects.Structure>()!.Get(index)); Push(items.Ptr.As<Structure>()!.Get(index));
else if (items.Is(T.String))
Push(items.Ptr.As<String>()!.SubString(index, new Value(1L)));
else else
return Error($"Unsupported native accessor for type {items.Type}!"); return Error($"Unsupported native accessor for type {items.Type}!");
break; break;
@ -303,7 +370,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;
} }
@ -312,7 +379,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;
} }
@ -322,7 +389,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;
} }
@ -343,7 +410,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;
} }
@ -351,9 +418,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 {
@ -361,9 +428,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;
} }
@ -414,8 +481,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;
} }
@ -478,7 +545,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;
@ -494,13 +561,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;
} }
@ -541,7 +608,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>()!;
@ -550,7 +617,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);
@ -593,40 +660,28 @@ public class Runner : IDisposable
Pop(); Pop();
} }
private Value Peek(int delta = -1) public Value Peek(int delta = -1)
{ {
#if LOG #if LOG
_logger.Method(); _logger.Method();
#endif #endif
return Stack.Peek(delta); return _stack.Peek(delta);
} }
private Value Pop() public Value Pop()
{ {
#if LOG #if LOG
_logger.Method(); _logger.Method();
#endif #endif
return Stack.Pop(); return _stack.Pop();
} }
private void Push(Value value) public 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)
@ -648,7 +703,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()
@ -673,14 +728,14 @@ public enum ExecutionResult
} }
public readonly struct Options( public readonly struct Options(
Action<string>? stdOut = null, IO.OutputStreamMessageHandler stdOut = null,
Action<string>? stdIn = null, IO.InputStreamListenHandler stdIn = null,
int maxCalls = 0x100, int maxCalls = 0x100,
int stackSize = 0x200, int stackSize = 0x200,
int initialStackSize = 0x80) int initialStackSize = 0x80)
{ {
public readonly Action<string>? StdOut = stdOut; public readonly IO.OutputStreamMessageHandler StdOut = stdOut;
public readonly Action<string>? StdIn = stdIn; public readonly IO.InputStreamListenHandler 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

@ -285,7 +285,7 @@ public class Reader : IReader<Token>, IDisposable
'&' => Check('&') ? '&' => Check('&') ?
MakeToken(And, buffer + Next()) : MakeToken(And, buffer + Next()) :
Check(':') ? Check(':') ?
MakeToken(Address, buffer + Next()) : MakeToken(AddressOf, buffer + Next()) :
MakeToken(BitwiseAnd, buffer), MakeToken(BitwiseAnd, buffer),
'^' => Check('~') ? '^' => Check('~') ?
MakeToken(Base, buffer + Next()) : MakeToken(Base, buffer + Next()) :
@ -320,7 +320,7 @@ public class Reader : IReader<Token>, IDisposable
'=' => Check('=') ? '=' => Check('=') ?
MakeToken(EqualEqual, buffer + Next()) : MakeToken(EqualEqual, buffer + Next()) :
MakeToken(Equal, buffer), MakeToken(Equal, buffer),
'<' => Peek(1) switch { '<' => Peek(0) 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,
Address = Operator | 12, // returns the address of a ptr/object as a value AddressOf = 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,7 +5,9 @@ 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 override string ToString() => $"0x{Value:x8}"; public static readonly Address Void = new Address(-1);
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,6 +6,8 @@ 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,6 +3,86 @@ 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);
@ -73,4 +153,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,23 +1,42 @@
namespace Qrakhen.Qamp.Core.Values.Objects; using Qrakhen.Qamp.Core.Collections;
public class Obj(ValueType type) : IValue namespace Qrakhen.Qamp.Core.Values.Objects;
public class ObjTable(int size = 4096)
{ {
public bool __gcMarked = false; public static readonly ObjTable Global = new ObjTable(4096);
public int __gcCount = 1;
public readonly ValueType Type = type; 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);
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,12 +11,52 @@ 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(); return Value?.GetHash() ?? 0;
} }
public static Value Make(string? value) public static Value Make(string? value)
@ -36,7 +76,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.__gcMarked) if (str == null || str.__GC_Marked)
continue; continue;
byte[] bytes; byte[] bytes;
bytes = Encoding.ASCII.GetBytes(str.Value!); bytes = Encoding.ASCII.GetBytes(str.Value!);

View File

@ -4,36 +4,29 @@ 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 (_register.TryGet(Address, out Obj obj)) if (ObjTable.Global.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(_register.Add(obj)); return new Ptr(obj.__Address);
} }
public T? As<T>(bool throwWhenNull = true) where T : Obj public T? As<T>(bool throwWhenNull = true) where T : Obj
@ -57,8 +50,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 _register) { foreach (var obj in ObjTable.Global.Entries) {
obj.__gcCount = 0; obj.Value.__GC_Count = 0;
} }
}); });
} }
@ -66,9 +59,9 @@ public readonly struct Ptr
public static byte[] SerializeFunctions() public static byte[] SerializeFunctions()
{ {
List<byte> result = new(); List<byte> result = new();
for (int i = 0; i < _register.Count; i++) { foreach (var element in ObjTable.Global.Entries) {
Obj obj = _register.Get(i); Obj obj = element.Value;
if (obj == null || obj.__gcMarked) if (obj == null || obj.__GC_Marked)
continue; continue;
byte[] bytes; byte[] bytes;
if (obj is Function function) { if (obj is Function function) {
@ -79,7 +72,7 @@ public readonly struct Ptr
continue; continue;
} }
result.AddRange(i.GetBytes()); result.AddRange(((long)element.Key).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,5 +1,6 @@
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;
@ -7,13 +8,47 @@ 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 : IValue, ISerialize<Value>, IDebug<string> public readonly struct Value :
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);
@ -28,6 +63,8 @@ public readonly struct Value : IValue, ISerialize<Value>, IDebug<string>
[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
@ -50,10 +87,12 @@ public readonly struct Value : IValue, ISerialize<Value>, IDebug<string>
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, // coded with : symbol Address = 0x0020,
// classes etc. here? // classes etc. here?