qamp/Qrakhen.Qamp.Core/Execution/Runner.cs

570 lines
20 KiB
C#

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<Value> StackPtr;
public Call(Context context, StackLike<Value> stack, long stackOffset)
{
Context = context;
Instruction = new InstructionPtr(Context.Function.Segment);
StackPtr = new Pointer<Value>(stack, stackOffset);
}
}
public class Runner : IDisposable
{
private readonly ILogger _logger = LoggerService.Get<Runner>();
public readonly Options Options;
private StackLike<Call> Calls;
// 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 long Cursor => Stack.Position;
private List<Outer> Outers = [];
private Register<string, Value> Globals = new();
private Register<string, Value> Imports = new();
private Register<string, Value> Exports = new();
public Runner(Options options)
{
Options = options;
Calls = new StackLike<Call>(options.MaxCalls);
Stack = new StackLike<Value>(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<Instance>()!;
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<Instance>()!;
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<Class>()!;
if (!BindMethod(@class, name))
return Error($"Could not bind member {name} to base class {@class.Name}");
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<Class>()!;
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<Function>()!;
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<Value>(Stack, Cursor));
Pop();
break;
}
case Op.Return: // todo: not done
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>()!;
Class inheritingClass = Peek().Ptr.As<Class>()!;
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<Class>()!.Name;
else if (value.Is(T.Instance))
type = $"instance of {value.Ptr.As<Instance>()!.Class.Name}";
else if (value.Is(T.Function))
type = $"function {value.Ptr.As<Function>()!.Name}";
else if (value.Is(T.Method))
type = $"method {value.Ptr.As<Method>()!.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++) {
Console.WriteLine($"{Stack[i].ToString(true)}");
}
break;
}
case Op.PrintGlobals: {
foreach (var value in Globals) {
Console.WriteLine($"{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<Value> 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<Value> 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<Instance>()!;
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<Method>()!;
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<Context>()!;
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<Class>()!;
Stack.Set(Cursor - argumentCount - 1, Instantiate(@class));
if (@class.Members.TryGet(@class.Name, out Value ctor)) {
Context? context = ctor.Ptr.As<Context>()!;
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<Context>()!.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>()!;
@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) { }
}