using Qrakhen.Qamp.Core.Collections; using Qrakhen.Qamp.Core.Logging; using Qrakhen.Qamp.Core.Tokenization; using Qrakhen.Qamp.Core.Values; using Qrakhen.Qamp.Core.Values.Objects; using System.Xml.Linq; using String = Qrakhen.Qamp.Core.Values.Objects.String; namespace Qrakhen.Qamp.Core.Execution; using Op = OpCode; using T = Values.ValueType; public class Call { public Context Context; public InstructionPtr Instruction; public Pointer StackPtr; public Call(Context context, StackLike stack, long stackOffset) { Context = context; Instruction = new InstructionPtr(Context.Function.Segment); StackPtr = new Pointer(stack, stackOffset); } } public class Runner : IDisposable { private readonly ILogger _logger = LoggerService.Get(); public readonly Options Options; private StackLike Calls; // todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ? private StackLike Stack; private long Cursor => Stack.Position; private List Outers = []; private Register Globals = new(); private Register Imports = new(); private Register Exports = new(); public Runner(Options options) { Options = options; Calls = new StackLike(options.MaxCalls); Stack = new StackLike(options.InitialStackSize); } public ExecutionResult Run(Stream stream) { _logger.Method(); if (Stack.Position < 0) { _logger.Warn($"Something went wrong, stack cursor is at {Stack.Position}. Resetting Stack."); Stack.Decimate(0); } using Reader reader = new Reader(stream); Compilation.Digester digester = new(reader); Function function = digester.Digest(); if (stream.Length > 64) File.WriteAllBytes($"./func.{DateTime.Now:yyyyMMdd_HHmmss}.sqi", function.Serialize(true)); Context closure = new Context(function); Push(Obj.Create(closure)); Invoke(closure, 0); return Execute(); } private bool TestFlag(Op code, Op flag) => (code & Op.F_Mask) == flag; private ExecutionResult Execute() { _logger.Method(); Call call = Calls.Peek(-1); do { Op opCode = call.Instruction.Next(); _logger.Verbose($"OpCode: {opCode}"); if (TestFlag(opCode, Op.F_Operation)) { Value result = ValueOperation.Resolve(PopOperation(opCode)); Push(result); continue; } if (TestFlag(opCode, Op.F_Compare)) { Value result = ValueOperation.Resolve(PopOperation(opCode)); Push(result); continue; } switch (opCode) { case Op.Null: Push(Value.Void); break; case Op.Constant: Push(call.Instruction.NextConstant()); break; case Op.Pop: Pop(); break; case Op.True: Push(Value.True); break; case Op.False: Push(Value.False); break; case Op.GetGlobal: { string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value; if (string.IsNullOrEmpty(name)) return Error($"tried to set global variable with empty name"); if (!Globals.Has(name)) Push(Value.Void); else Push(Globals[name]); _logger.Verbose($"got global {name} ({Peek()})"); break; } case Op.SetGlobal: { string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value; if (string.IsNullOrEmpty(name)) return Error($"tried to set global variable with empty name"); if (!Globals.Has(name)) return Error($"tried to set a value of non-existing global variable"); Globals[name] = Pop(); _logger.Verbose($"set global {name} = {Globals[name]}"); break; } case Op.DefineGlobal: { string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value; if (string.IsNullOrEmpty(name)) return Error($"tried to define global variable with empty name"); Globals[name] = Pop(); _logger.Verbose($"defined global {name} as {Globals[name]}"); 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.GetOuter: { long slot = call.Instruction.NextDynamic(); Push(call.Context.Outers[slot].Value); break; } case Op.SetOuter: { long slot = call.Instruction.NextDynamic(); call.Context.Outers[slot].Value = Peek(); break; } case Op.GetMember: { Value value = Peek(); if (!value.Is(T.Instance)) return Error("Only instances have members."); Instance instance = value.Ptr.As()!; string name = call.Instruction.NextString().Value!; if (instance.Values.TryGet(name, out Value member)) { Pop(); // remove instance from stack Push(member); } else if (!BindMethod(instance.Class, name)) { return Error($"Could not bind member {name} to instance {instance} of class {instance.Class.Name}!"); } break; } case Op.SetMember: { Value value = Peek(-2); if (!value.Is(T.Instance)) return Error("Only instances have members."); Instance instance = value.Ptr.As()!; string name = call.Instruction.NextString().Value!; instance.Values.Set(name, Peek()); Value move = Pop(); Pop(); // remove instance from stack Push(move); break; } case Op.Base: { string name = call.Instruction.NextString().Value!; Class @class = Pop().Ptr.As()!; if (!BindMethod(@class, name)) return Error($"Could not bind member {name} to base class {@class.Name}"); break; } case Op.Array: { int length = (int)call.Instruction.NextDynamic(); Value[] values = new Value[length]; for (int i = length - 1; i >= 0; i--) { values[i] = Pop(); } var array = new Values.Objects.Array(values); Value value = Obj.Create(array); Push(value); break; } case Op.GetItem: { Value index = Pop(); Value items = Pop(); if (!items.Is(T.ItemProvider, false)) return Error($"Can not access {index} of {items} - it is not an ItemProvider! (Array, List, etc.)"); if (items.Is(T.Array)) Push(items.Ptr.As()!.Get(index)); else if (items.Is(T.List)) Push(items.Ptr.As()!.Get(index)); else if (items.Is(T.Structure)) Push(items.Ptr.As()!.Get(index)); else return Error($"Unsupported native accessor for type {items.Type}!"); break; } case Op.SetItem: { Value value = Pop(); Value index = Pop(); Value items = Pop(); if (!items.Is(T.ItemProvider, false)) return Error($"Can not access {index} of {items} - it is not an ItemProvider! (Array, List, etc.)"); if (items.Is(T.Array)) items.Ptr.As()!.Set(index, value); else if (items.Is(T.List)) items.Ptr.As()!.Set(index, value); else if (items.Is(T.Structure)) items.Ptr.As()!.Set(index, value); else return Error($"Unsupported native accessor for type {items.Type}!"); break; } case Op.AddItem: { Value value = Pop(); Value items = Pop(); if (!items.Is(T.List)) return Error($"Can not add {value} to {items} - only lists :[] support the native add <+ operator."); items.Ptr.As()!.Add(value); break; } case Op.Jump: { long delta = call.Instruction.NextLong(); call.Instruction.Cursor += delta; break; } case Op.JumpIfFalse: { long delta = call.Instruction.NextLong(); if (Peek().IsFalsy) call.Instruction.Cursor += delta; break; } case Op.Loop: { long delta = call.Instruction.NextLong(); call.Instruction.Cursor -= delta; break; } case Op.Invoke: { byte count = call.Instruction.Next(); if (!Invoke(Peek(-(count + 1)), count)) return Error($"Could not invoke function."); call = Calls.Peek(); break; } case Op.InvokeMember: { string name = call.Instruction.NextString().Value!; byte argumentCount = call.Instruction.Next(); if (!Invoke(name, argumentCount)) return Error($"Could not invoke member {name}."); call = Calls.Peek(); break; } case Op.InvokeBase: { string name = call.Instruction.NextString().Value!; byte argumentCount = call.Instruction.Next(); Class @class = Pop().Ptr.As()!; if (!Invoke(@class, name, argumentCount)) return Error($"Could not invoke member {name} of base class {@class.Name}!"); call = Calls.Peek(); break; } case Op.Context: { Function function = call.Instruction.NextConstant().Ptr.As()!; Context context = new Context(function); Push(Obj.Create(context)); for (int i = 0; i < context.Function.OuterCount; i++) { Outer outer = context.Outers[i]; bool isLocal = call.Instruction.Next() == 1; long index = call.Instruction.NextDynamic(); if (isLocal) context.Outers.Add(CaptureOuter(call.StackPtr.Branch(index))); else context.Outers.Add(call.Context.Outers[index]); } break; } case Op.CloseOuter: { CloseOuters(new Pointer(Stack, Cursor)); Pop(); break; } case Op.Return: { Value result = Pop(); CloseOuters(call.StackPtr.Branch()); Calls.Pop(); if (Calls.Count == 0) { Pop(); return ExecutionResult.OK; } Stack.Decimate(call.StackPtr.Cursor); Push(result); call = Calls.Peek(); break; } case Op.Class: { Push(Obj.Create(new Class(call.Instruction.NextString().Value!))); break; } case Op.Inherit: { Value @class = Peek(-2); if (!@class.Is(T.Class)) return Error($"Can not inherit from {@class}, value must be of type Class."); Class baseClass = @class.Ptr.As()!; Class inheritingClass = Peek().Ptr.As()!; foreach (var member in baseClass.Members) { if (!inheritingClass.Members.Has(member.Key)) inheritingClass.Members.Add(member.Key, member.Value); } Pop(); break; } case Op.Method: DefineMember(call.Instruction.NextString().Value!); break; case Op.Typeof: { Value value = Pop(); string type; if (value.Is(T.Class)) type = value.Ptr.As()!.Name; else if (value.Is(T.Instance)) type = $"instance of {value.Ptr.As()!.Class.Name}"; else if (value.Is(T.Function)) type = $"function {value.Ptr.As()!.Name}"; else if (value.Is(T.Method)) type = $"method {value.Ptr.As()!.Function.Name}"; else type = value.Type.ToString(); Push(String.Make(type)); break; } case Op.Print: { Value value = Pop(); IO.Console.Write(value); break; } case Op.PrintStack: { for (int i = 0; i < Cursor; i++) { IO.Console.Write($"{Stack[i].ToString(true)}"); } break; } case Op.PrintGlobals: { foreach (var value in Globals) { IO.Console.Write($"{value.Key}: {value.Value.ToString(true)}"); } break; } default: return Error($"Unexpected or not yet supported OpCode {opCode}!"); } } while (call.Instruction.Segment.Instructions.Length > call.Instruction.Cursor); return ExecutionResult.OK; } private Outer CaptureOuter(Pointer target) { Outer? outer = null; for (int i = Outers.Count; i-->0;) { outer = Outers[i]; if (outer.Target.Cursor <= target.Cursor) break; } if (outer != null && outer.Target.Cursor == target.Cursor) return outer; return new Outer(target); } private void CloseOuters(Pointer last) { for (int i = Outers.Count;i-->0;) { Outer outer = Outers[i]; if (outer.IsClosed) continue; if (outer.Target.Cursor < last.Cursor) return; outer.Close(); } } private bool InvokeNative(Value target, NativeExtension extension, int argumentCount) { _logger.Method(extension); if (argumentCount != extension.Parameters.Length) { Error($"{extension} expects {extension.Parameters.Length} arguments, received {argumentCount}"); return false; } var values = new Value[argumentCount]; for (int i = 0; i < argumentCount; i++) { values[i] = Peek(-i - 1); } Stack.Decimate(Stack.Position - argumentCount - 1); Value result = extension.Callback(target, values); Push(result); return true; } private bool Invoke(Context closure, int argumentCount) { _logger.Method(); if (argumentCount != closure.Function.ArgumentCount) { Error($"Expected {closure.Function.ArgumentCount} arguments but got {argumentCount}"); return false; } if (Calls.Count > Options.MaxCalls) { Error($"Stack overflow {Calls.Count}"); return false; } Call call = new(closure, Stack, Cursor - argumentCount - 1); Calls.Push(call); return true; } private bool Invoke(string methodName, int argumentCount) { Value value = Peek(-argumentCount - 1); if (!value.Is(T.Instance)) { NativeExtension? extension = NativeExtension.Get(value.Type, methodName); if (extension == null) { Error($"Could not invoke {methodName} on {value}, no method or extension by that name found for this type."); return false; } return InvokeNative(value, extension, argumentCount); } Instance instance = value.Ptr.As()!; if (instance.Values.TryGet(methodName, out Value method)) return Invoke(method, argumentCount); return Invoke(instance.Class, methodName, argumentCount); } private bool Invoke(Class @class, string methodName, int argumentCount) { if (@class.Members.TryGet(methodName, out Value method)) return Invoke(method, argumentCount); Error($"Could not call method {methodName} on class {@class.Name}: Member does not exist"); return false; } private bool Invoke(Value value, int argumentCount) { if (!value.IsObj) { Error($"Can only call values of object type! Tried to call {value.ToString(true)}", value); return false; } if (value.Is(T.Method)) { Method method = value.Ptr.As()!; Stack.Set(Cursor - method.Function.ArgumentCount - 1, method.Receiver); return Invoke(method, method.Function.ArgumentCount); } else if (value.Is(T.Context)) { Context? context = value.Ptr.As()!; return Invoke(context, context.Function.ArgumentCount); } else if (value.Is(T.Native)) { throw new NotImplementedException("i dont really want globals in this language so lets see"); } else if (value.Is(T.Class)) { Class? @class = value.Ptr.As()!; Stack.Set(Cursor - argumentCount - 1, Instantiate(@class)); if (@class.Members.TryGet(@class.Name, out Value ctor)) { Context? context = ctor.Ptr.As()!; return Invoke(context, argumentCount); } else if (argumentCount != 0) { Error($"{@class.Name}() expects 0 arguments but received {argumentCount}"); return false; } } else { return false; } return true; } private bool BindMethod(Class @class, string name) { if (!@class.Members.TryGet(name, out Value value)) { Error($"Undefined member '{name}' on class {@class.Name}"); return false; } Method method = new Method(value.Ptr.As()!.Function, Peek()); Pop(); // remove instance from stack Push(Obj.Create(method)); return true; } private Value Instantiate(Class @class) { Instance instance = new Instance(@class); // todo: init fields etc here too return Obj.Create(instance); } private void DefineMember(string name) { Value method = Peek(); Class @class = Peek(-2).Ptr.As()!; @class.Members.Set(name, method); Pop(); } 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 Operation PopOperation(Op code) { _logger.Method(code); 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) { _logger.Method(message); _logger.Error(message, context); Panic(); if (@throw) throw new QampException(message, context); return ExecutionResult.Execution; } private void Panic() { _logger.Method(); Stack.Decimate(0); } public void Dispose() { _logger.Method(); } } public class GarbageCollector { } 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) { } }