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

520 lines
18 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();
using Reader reader = new Reader(stream);
Compilation.Digester digester = new(reader);
Function function = digester.Digest();
File.WriteAllBytes("func.sqi", function.Segment.Serialize());
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))
throw new RuntimeException($"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))
throw new RuntimeException($"tried to set global variable with empty name");
if (!Globals.Has(name))
throw new RuntimeException($"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))
throw new RuntimeException($"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.Ptr += delta;
break;
}
case Op.JumpIfFalse: {
long delta = call.Instruction.NextLong();
if (Peek().IsFalsy)
call.Instruction.Ptr += delta;
break;
}
case Op.Loop: {
long delta = call.Instruction.NextLong();
call.Instruction.Ptr -= 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.Ptr);
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();
Push(String.Make(value.Type.ToString()));
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.Ptr);
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.Ptr <= target.Ptr)
break;
}
if (outer != null && outer.Target.Ptr == target.Ptr)
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.Ptr < last.Ptr)
return;
outer.Close();
}
}
private bool Invoke(Context 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, Cursor - argumentCount - 1);
Calls.Push(call);
return true;
}
private bool Invoke(string methodName, int argumentCount)
{
Value value = Peek(-argumentCount);
if (!value.Is(T.Instance)) {
Error($"Can not call {methodName} on value of {value}");
return false;
}
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<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);
if (@throw)
throw new QampException(message, context);
return ExecutionResult.Execution;
}
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) { }
}