using Qrakhen.Qamp.Core.Collections; using Qrakhen.Qamp.Core.Compilation; using Qrakhen.Qamp.Core.Logging; using Qrakhen.Qamp.Core.Tokenization; using Qrakhen.Qamp.Core.Values; using Qrakhen.Qamp.Core.Values.Objects; using String = Qrakhen.Qamp.Core.Values.Objects.String; namespace Qrakhen.Qamp.Core.Execution; using Op = OpCode; /// /// Dynamic Pointer able to point to any object implementing the interface. /// public class Pointer { protected IGetSet Target; public long Ptr; public Pointer(IGetSet target, long pointer = 0) { Target = target; Ptr = pointer; } /// /// Returns the next item from and increases the pointer by 1. /// public T Next() => Target.Get(Ptr++); /// /// Sets the value of at . /// public void Set(long position, T value) => Target.Set(position, value); /// /// Sets the value of at the current location. /// public void Set(T value) => Set(Ptr, value); /// /// Gets the value of at . /// public T Get(long position) => Target.Get(position); /// /// Gets the value of at the current location. /// public T Get() => Get(Ptr); } // this is all a bit cheesy imho public class InstructionPtr : Pointer { public Segment Segment; public InstructionPtr(Segment segment, int position = 0) : base(segment.Instructions, position) { Segment = segment; } public long NextLong() { long value = Segment.ReadLong(Ptr); Ptr += sizeof(long); return value; } public Value NextConstant() { return Segment.Constants[NextDynamic()]; } public long NextDynamic() { int length = Segment.Read(Ptr) ^ 0x80; Ptr++; byte[] data = new byte[8]; for (int i = 0; i < length; i++) data[i] = Segment.Read(Ptr++); return data.ToInt64(); } public String? GetString(long offset) { return Segment.Constants[offset].Ptr.Value as String; } public Instruction Instruction => Segment.Instructions[Ptr]; public static Instruction operator +(InstructionPtr ptr, int value) => ptr.Segment.Instructions[ptr.Ptr + value]; public static Instruction operator -(InstructionPtr ptr, int value) => ptr.Segment.Instructions[ptr.Ptr - value]; public static InstructionPtr operator ++(InstructionPtr ptr) { ptr.Ptr++; return ptr; } public static InstructionPtr operator --(InstructionPtr ptr) { ptr.Ptr--; return ptr; } } public class Call { public Closure Closure; public InstructionPtr Instruction; public Pointer StackPtr; public Call(Closure closure, StackLike stack, long stackOffset) { Closure = closure; Instruction = new InstructionPtr(Closure.Function.Segment); StackPtr = new Pointer(stack, stackOffset); } } public unsafe class OuterWrapper { public Value* Target; public Value Stored; } public class Runner : IDisposable { private readonly ILogger _logger = LoggerService.Get(); public readonly Options Options; public StackLike Calls; // todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ? public StackLike Stack; public long Cursor; public Register Globals = new Register(); public Register Imports = new Register(); public Register Exports = new Register(); public Runner(Options options) { Options = options; Calls = new StackLike(options.MaxCalls); Stack = new StackLike(options.InitialStackSize); } public ExecutionResult Run(Stream stream) { _logger.Method(); using Reader reader = new Reader(stream); Digester digester = new Digester(reader); Function function = digester.Digest(); File.WriteAllBytes("func.sqi", function.Segment.Serialize()); Closure closure = new Closure(function); Push(Obj.Create(closure)); Call(closure, 0); return Interpret(); } private ExecutionResult Interpret() { _logger.Method(); Call call = Calls.Peek(-1); do { Op opCode = call.Instruction.Next(); _logger.Verbose($"OpCode: {opCode}"); switch (opCode) { case Op.Constant: Push(call.Instruction.NextConstant()); break; case Op.Pop: Pop(); break; case Op.Not: OpNot(); break; case Op.Add: OpAdd(); break; case Op.Subtract: OpSubtract(); break; case Op.Divide: OpDivide(); break; case Op.Modulo: OpModulo(); break; case Op.Multiply: OpMultiply(); break; case Op.Negate: OpNegate(); break; case Op.BitwiseOr: OpBitwiseOr(); break; case Op.BitwiseAnd: OpBitwiseAnd(); break; case Op.BitwiseXor: OpBitwiseXor(); break; case Op.BitwiseNot: OpBitwiseNot(); break; case Op.BitwiseLeft: OpBitwiseLeft(); break; case Op.BitwiseRight: OpBitwiseRight(); break; case Op.GetLocal: { long slot = call.Instruction.NextDynamic(); _logger.Verbose($"getting slot {slot} which is {call.StackPtr.Get(slot)}"); Push(call.StackPtr.Get(slot)); break; } case Op.SetLocal: { long slot = call.Instruction.NextDynamic(); _logger.Verbose($"setting stackptr {slot} to {Peek()}"); call.StackPtr.Set(slot, Peek()); break; } case Op.DefineGlobal: { string? name = call.Instruction.GetString(call.Instruction.NextDynamic())?.Value; if (string.IsNullOrEmpty(name)) throw new ExecutionException($"tried to define global variable with empty name"); Globals[name] = Pop(); _logger.Verbose($"defined global {name} as {Globals[name]}"); break; } case Op.SetGlobal: { string? name = call.Instruction.GetString(call.Instruction.NextDynamic())?.Value; if (string.IsNullOrEmpty(name)) throw new ExecutionException($"tried to set global variable with empty name"); if (!Globals.Has(name)) throw new ExecutionException($"tried to set a value of non-existing global variable"); Globals[name] = Pop(); _logger.Verbose($"set global {name} = {Globals[name]}"); break; } case Op.GetGlobal: { string? name = call.Instruction.GetString(call.Instruction.NextDynamic())?.Value; if (string.IsNullOrEmpty(name)) throw new ExecutionException($"tried to set global variable with empty name"); if (!Globals.Has(name)) Push(Value.Void); else Push(Globals[name]); _logger.Verbose($"got global {name} ({Peek()})"); break; } case Op.Typeof: { Value value = Pop(); Push(String.Make(value.Type.ToString())); break; } case Op.Print: { Value value = Pop(); IO.Console.Write(value); break; } case Op.Return: // todo: not done Value result = Pop(); Calls.Pop(); if (Calls.Count == 0) { Pop(); return ExecutionResult.OK; } Cursor = call.StackPtr.Ptr; // well find a better way... Push(result); call = Calls.Peek(); break; } } while (call.Instruction.Segment.Instructions.Length > call.Instruction.Ptr); return ExecutionResult.OK; } private bool Call(Closure closure, int argumentCount) { _logger.Method(); if (argumentCount != closure.Function.ArgumentCount) throw new Exception($"Expected {closure.Function.ArgumentCount} arguments but got {argumentCount}"); if (Calls.Count > Options.MaxCalls) throw new StackOverflowException($"Stack overflow {Calls.Count}"); Call call = new(closure, Stack, Stack.Position - argumentCount); Calls.Push(call); return true; } private Value Peek(int delta = -1) { _logger.Method(); return Stack.Peek(delta); } private Value Pop() { _logger.Method(); return Stack.Pop(); } private void Push(Value value) { _logger.Method($"{value}"); Stack.Push(value); } private void OpNot() { Value value = Pop(); Push(ValueOperation.Not(value)); } private void OpAdd() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.Add(left, right)); } private void OpSubtract() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.Subtract(left, right)); } private void OpDivide() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.Divide(left, right)); } private void OpModulo() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.Modulo(left, right)); } private void OpMultiply() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.Multiply(left, right)); } private void OpNegate() { Value value = Pop(); Push(ValueOperation.Negate(value)); } private void OpBitwiseOr() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.BitwiseOr(left, right)); } private void OpBitwiseAnd() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.BitwiseAnd(left, right)); } private void OpBitwiseXor() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.BitwiseXor(left, right)); } private void OpBitwiseLeft() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.BitwiseLeft(left, right)); } private void OpBitwiseRight() { Value right = Pop(); Value left = Pop(); Push(ValueOperation.BitwiseRight(left, right)); } private void OpBitwiseNot() { Value value = Pop(); Push(ValueOperation.BitwiseInvert(value)); } public void Dispose() { _logger.Method(); } } public enum ExecutionResult { OK = 0x0000, Error = 0x1000, Compilation = Error | 0x0001, Execution = Error | 0x0002 } public readonly struct Options( int maxCalls = 0x100, int stackSize = 0x200, int initialStackSize = 0x80) { public readonly int MaxCalls = maxCalls; public readonly int StackSize = stackSize; public readonly int InitialStackSize = initialStackSize; public Options() : this(0x100, 0x200, 0x80) { } }