682 lines
21 KiB
C#
682 lines
21 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);
|
|
IO.Console.StdOut = options.StdOut;
|
|
}
|
|
|
|
public ExecutionResult Run(Stream stream)
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
if (Stack.Position < 0) {
|
|
#if LOG
|
|
_logger.Warn($"Something went wrong, stack cursor is at {Stack.Position}. Resetting Stack.");
|
|
#endif
|
|
Stack = new StackLike<Value>(Options.InitialStackSize);
|
|
}
|
|
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()
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
Call call = Calls.Peek(-1);
|
|
do {
|
|
Op opCode = call.Instruction.Next();
|
|
#if LOG
|
|
_logger.Verbose($"OpCode: {opCode}");
|
|
#endif
|
|
|
|
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]);
|
|
#if LOG
|
|
_logger.Verbose($"got global {name} ({Peek()})");
|
|
#endif
|
|
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();
|
|
#if LOG
|
|
_logger.Verbose($"set global {name} = {Globals[name]}");
|
|
#endif
|
|
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();
|
|
#if LOG
|
|
_logger.Verbose($"defined global {name} as {Globals[name]}");
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case Op.GetLocal: {
|
|
long slot = call.Instruction.NextDynamic();
|
|
#if LOG
|
|
_logger.Verbose($"getting slot {slot} which is {call.StackPtr.Get(slot)}");
|
|
#endif
|
|
Push(call.StackPtr.Get(slot));
|
|
break;
|
|
}
|
|
|
|
case Op.SetLocal: {
|
|
long slot = call.Instruction.NextDynamic();
|
|
#if LOG
|
|
_logger.Verbose($"setting stackptr {slot} to {Peek()}");
|
|
#endif
|
|
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.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<Values.Objects.Array>()!.Get(index));
|
|
else if (items.Is(T.List))
|
|
Push(items.Ptr.As<Values.Objects.List>()!.Get(index));
|
|
else if (items.Is(T.Structure))
|
|
Push(items.Ptr.As<Values.Objects.Structure>()!.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<Values.Objects.Array>()!.Set(index, value);
|
|
else if (items.Is(T.List))
|
|
items.Ptr.As<Values.Objects.List>()!.Set(index, value);
|
|
else if (items.Is(T.Structure))
|
|
items.Ptr.As<Values.Objects.Structure>()!.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<Values.Objects.List>()!.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<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: {
|
|
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++) {
|
|
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<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 InvokeExtension(Value target, ExtensionMethod extension, int argumentCount)
|
|
{
|
|
#if LOG
|
|
_logger.Method(extension);
|
|
#endif
|
|
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.Call(target, values);
|
|
Push(result);
|
|
return true;
|
|
}
|
|
|
|
private bool Invoke(Context closure, int argumentCount)
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
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);
|
|
TypeInfo typeInfo = value.TypeInfo;
|
|
if (!value.Is(T.Instance)) {
|
|
ExtensionMethod? extension = ExtensionMethod.Get(typeInfo, methodName, argumentCount);
|
|
if (extension == null) {
|
|
Error($"Could not invoke {methodName} on {value}, no method or extension by that name found for {typeInfo}.");
|
|
return false;
|
|
}
|
|
return InvokeExtension(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)
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
return Stack.Peek(delta);
|
|
}
|
|
|
|
private Value Pop()
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
return Stack.Pop();
|
|
}
|
|
|
|
private void Push(Value value)
|
|
{
|
|
#if LOG
|
|
_logger.Method($"{value}");
|
|
#endif
|
|
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)
|
|
{
|
|
#if LOG
|
|
_logger.Method(message);
|
|
#endif
|
|
#if LOG
|
|
_logger.Error(message, context);
|
|
#endif
|
|
Panic();
|
|
if (@throw)
|
|
throw new QampException(message, context);
|
|
return ExecutionResult.Execution;
|
|
}
|
|
|
|
private void Panic()
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
Stack.Decimate(0);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public class GarbageCollector
|
|
{
|
|
|
|
}
|
|
|
|
public enum ExecutionResult
|
|
{
|
|
OK = 0x0000,
|
|
Error = 0x1000,
|
|
Compilation = Error | 0x0001,
|
|
Execution = Error | 0x0002
|
|
}
|
|
|
|
public readonly struct Options(
|
|
Action<string>? stdOut = null,
|
|
Action<string>? stdIn = null,
|
|
int maxCalls = 0x100,
|
|
int stackSize = 0x200,
|
|
int initialStackSize = 0x80)
|
|
{
|
|
public readonly Action<string>? StdOut = stdOut;
|
|
public readonly Action<string>? StdIn = stdIn;
|
|
public readonly int MaxCalls = maxCalls;
|
|
public readonly int StackSize = stackSize;
|
|
public readonly int InitialStackSize = initialStackSize;
|
|
|
|
public Options() : this(null, null, 0x100, 0x200, 0x80) { }
|
|
} |