Compare commits
10 Commits
c42a9a42e6
...
44b8bb8900
| Author | SHA1 | Date |
|---|---|---|
|
|
44b8bb8900 | |
|
|
aaec6375eb | |
|
|
a66580e7fe | |
|
|
bdd1322a82 | |
|
|
bb13c0ae2b | |
|
|
f62c48396f | |
|
|
880f3e60ce | |
|
|
c4de1dfb0a | |
|
|
3b50abebe4 | |
|
|
d79cdf9c92 |
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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 },
|
||||||
|
|
@ -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 & 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})";
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
@ -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()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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!);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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?
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue