869 lines
27 KiB
C#
869 lines
27 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 interface IOperationResolver
|
|
{
|
|
bool CanResolve(OpCode code);
|
|
OperationResult Resolve(Runner runner, OpCode code);
|
|
}
|
|
|
|
public struct OperationResult
|
|
{
|
|
public bool Success;
|
|
public Value Value;
|
|
public RuntimeException? Error;
|
|
|
|
public static OperationResult MakeSuccess(Value value)
|
|
{
|
|
return new OperationResult { Success = true, Value = value };
|
|
}
|
|
|
|
public static OperationResult MakeError(RuntimeException exception)
|
|
{
|
|
return new OperationResult { Success = false, Value = Value.Void, Error = exception };
|
|
}
|
|
}
|
|
|
|
public class OperationRouter
|
|
{
|
|
private readonly List<IOperationResolver> _handlers = [];
|
|
private readonly Dictionary<OpCode, IOperationResolver> _cache = [];
|
|
|
|
public void Register(IOperationResolver resolver)
|
|
{
|
|
_handlers.Add(resolver);
|
|
}
|
|
|
|
public IOperationResolver? Route(Runner runner, OpCode opCode)
|
|
{
|
|
if (!_cache.TryGetValue(opCode, out IOperationResolver? resolver)) {
|
|
resolver = _handlers.FirstOrDefault(h => h.CanResolve(opCode));
|
|
if (resolver == null)
|
|
return null;
|
|
|
|
_cache[opCode] = resolver;
|
|
}
|
|
|
|
return _cache[opCode];
|
|
}
|
|
}
|
|
|
|
public class Runner : IDisposable
|
|
{
|
|
private readonly ILogger _logger = LoggerService.Get<Runner>();
|
|
private readonly OperationRouter _router = new OperationRouter();
|
|
|
|
private Value _runnerReturned = Value.Void;
|
|
|
|
public readonly Options Options;
|
|
|
|
private List<IOperationResolver> _resolvers = [];
|
|
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;
|
|
IO.Console.StdIn ??= options.StdIn;
|
|
|
|
Globals["time"] = Obj.Create(new NativeFunction("time",
|
|
v => new Value(DateTime.Now.Ticks / 100)));
|
|
|
|
Globals["ftime"] = Obj.Create(new NativeFunction("ftime",
|
|
v =>
|
|
String.Make(
|
|
new DateTime(v[0].Signed * 10)
|
|
.ToString(
|
|
v[1].GetString() ??
|
|
Timestamp.DefaultFormat)),
|
|
"time",
|
|
"format"));
|
|
|
|
Globals["sin"] = Obj.Create(new NativeFunction("sin",
|
|
v => new Value(Math.Sin(v[0].GetDecimal())), "v"));
|
|
|
|
Globals["cos"] = Obj.Create(new NativeFunction("cos",
|
|
v => new Value(Math.Cos(v[0].GetDecimal())), "v"));
|
|
|
|
Globals["exit"] = Obj.Create(new NativeFunction("exit", v => {
|
|
Environment.Exit((int)(v.FirstOrDefault().Signed));
|
|
return Value.Void;
|
|
},
|
|
"code?"));
|
|
|
|
Globals["read"] = Obj.Create(new NativeFunction("read", v =>
|
|
String.Make(IO.Console.StdIn())));
|
|
|
|
Globals["write"] = Obj.Create(new NativeFunction("write", v => {
|
|
try {
|
|
if (v[0].GetString() is string content) {
|
|
IO.Console.StdOut(content);
|
|
}
|
|
} catch { }
|
|
return Value.Void;
|
|
},
|
|
"content"));
|
|
|
|
Globals["fread"] = Obj.Create(new NativeFunction("fread", v =>
|
|
(v[0].GetString() is string path && File.Exists(Path.GetFullPath(path))) ?
|
|
String.Make(
|
|
File.ReadAllText(
|
|
Path.GetFullPath(path))) :
|
|
Value.Void,
|
|
"path"));
|
|
|
|
Globals["fwrite"] = Obj.Create(new NativeFunction("fwrite", v => {
|
|
try {
|
|
if (v[0].GetString() is string path) {
|
|
File.WriteAllText(
|
|
path,
|
|
v[1].GetString());
|
|
} else {
|
|
return Value.False;
|
|
}
|
|
} catch {
|
|
return Value.False;
|
|
}
|
|
return Value.True;
|
|
},
|
|
"path",
|
|
"content?"));
|
|
|
|
_router.Register(new ArithmeticResolver());
|
|
}
|
|
|
|
public ExecutionResult Run(Stream stream, params string[] parameters)
|
|
{
|
|
_logger.Method();
|
|
|
|
if (_stack.Position != 0) {
|
|
_logger.Warn($"Something went terribly wrong, stack cursor is at {_stack.Position} before execution.\n --- Resetting Stack ---");
|
|
_stack = new StackLike<Value>(Options.InitialStackSize);
|
|
}
|
|
|
|
DateTime start = DateTime.Now;
|
|
Benchmark.Start($"Compile Start");
|
|
|
|
// cached value, used to directly return resulting values to the calling process
|
|
_runnerReturned = Value.Void;
|
|
|
|
using Reader reader = new Reader(stream);
|
|
Compilation.Digester digester = new(reader);
|
|
Function function = digester.Digest();
|
|
Benchmark.End($"Compile End");
|
|
|
|
// compilation result taste test
|
|
if (stream.Length > 64)
|
|
File.WriteAllBytes($"./func.{DateTime.Now:yyyyMMdd_HHmmss}.sqi", function.Serialize(true));
|
|
|
|
// run the code in a new context.
|
|
// global namespace bleeds into here anyway, executing directly in global namespace is a 'safety risk'.
|
|
Context closure = new Context(function);
|
|
Push(Obj.Create(closure));
|
|
Invoke(closure, 0);
|
|
|
|
Benchmark.Start($"Execution Start");
|
|
RunnerResult result = Execute();
|
|
Benchmark.End($"Execution End");
|
|
|
|
DateTime end = DateTime.Now;
|
|
|
|
return new ExecutionResult(result,
|
|
_runnerReturned,
|
|
start,
|
|
end,
|
|
parameters);
|
|
}
|
|
|
|
private bool TestHandler(Op code, Op handler) => (code & Op.Mask_Handler) == handler;
|
|
|
|
private RunnerResult Execute()
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
Call call = _calls.Peek(-1);
|
|
do {
|
|
Op opCode = call.Instruction.Next();
|
|
#if LOG
|
|
_logger.Verbose($"OpCode: {opCode}");
|
|
#endif
|
|
|
|
//IO.Console.Write($"{opCode}: \n - {string.Join("\n - ", Stack.ToArray().Subset(0, Stack.Count))}");
|
|
|
|
IOperationResolver? resolver = _router.Route(this, opCode);
|
|
|
|
if (resolver != null)
|
|
{
|
|
OperationResult result = resolver.Resolve(this, opCode);
|
|
if (!result.Success)
|
|
{
|
|
_logger.Error(result.Error?.Message);
|
|
}
|
|
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.Addr: {
|
|
Value value = Pop();
|
|
if (!value.IsObj)
|
|
return Error($"can not get address of a value on stack");
|
|
Push(new Value(value.Ptr.Address));
|
|
break;
|
|
}
|
|
|
|
case Op.GetGlobal: {
|
|
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
|
if (string.IsNullOrEmpty(name))
|
|
return Error($"tried to get 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] = Peek();
|
|
#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<List>()!.Get(index));
|
|
else if (items.Is(T.Structure))
|
|
Push(items.Ptr.As<Structure>()!.Get(index));
|
|
else if (items.Is(T.String))
|
|
Push(items.Ptr.As<String>()!.SubString(index, new Value(1L)));
|
|
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<List>()!.Set(index, value);
|
|
else if (items.Is(T.Structure))
|
|
items.Ptr.As<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<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) {
|
|
if (_stack.Count > 0) {
|
|
// todo: very bad fix for our stack problem
|
|
Pop();
|
|
} else {
|
|
_logger.Warn($"Critical issue at end of execution detected - stack was empty at return.");
|
|
}
|
|
return RunnerResult.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 RunnerResult.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 InvokeNative(NativeFunction native, int argumentCount)
|
|
{
|
|
#if LOG
|
|
_logger.Method(native);
|
|
#endif
|
|
if (argumentCount != native.Arguments.Length) {
|
|
Error($"{native} expects {native.Arguments.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 = native.Target.Invoke(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 invoke non-primitive values! Tried to invoke {value.ToString(true)}", value);
|
|
return false;
|
|
}
|
|
|
|
if (value.Is(T.Native)) {
|
|
return InvokeNative(value.Ptr.As<NativeFunction>(), argumentCount);
|
|
} else 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.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();
|
|
}
|
|
|
|
public Value Peek(int delta = -1)
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
return _stack.Peek(delta);
|
|
}
|
|
|
|
public Value Pop()
|
|
{
|
|
#if LOG
|
|
_logger.Method();
|
|
#endif
|
|
return _stack.Pop();
|
|
}
|
|
|
|
public void Push(Value value)
|
|
{
|
|
#if LOG
|
|
_logger.Method($"{value}");
|
|
#endif
|
|
_stack.Push(value);
|
|
}
|
|
|
|
private RunnerResult 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 RunnerResult.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 RunnerResult
|
|
{
|
|
OK = 0x0000,
|
|
Error = 0x1000,
|
|
Compilation = 0x0001,
|
|
Execution = 0x0002,
|
|
|
|
Unknown = -1
|
|
}
|
|
|
|
public readonly struct ExecutionResult(
|
|
RunnerResult code = RunnerResult.Unknown,
|
|
Value? returned = null,
|
|
DateTime? started = null,
|
|
DateTime? finished = null,
|
|
string[] parameters = null)
|
|
{
|
|
public RunnerResult RunnerResult { get; } = code;
|
|
public Value? Returned { get; } = returned;
|
|
|
|
public DateTime Started { get; } = started ?? DateTime.Now;
|
|
public DateTime Finished { get; } = finished ?? DateTime.Now;
|
|
|
|
public string[] Parameters { get; } = parameters ?? [];
|
|
|
|
public bool Success => RunnerResult == RunnerResult.OK;
|
|
public bool HasValue => Returned?.Type != T.Void;
|
|
|
|
public TimeSpan Elapsed => Finished - Started;
|
|
}
|
|
|
|
public readonly struct Options(
|
|
IO.OutputStreamMessageHandler stdOut = null,
|
|
IO.InputStreamListenHandler stdIn = null,
|
|
int maxCalls = 0x100,
|
|
int stackSize = 0x200,
|
|
int initialStackSize = 0x80)
|
|
{
|
|
public readonly IO.OutputStreamMessageHandler StdOut = stdOut;
|
|
public readonly IO.InputStreamListenHandler 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) { }
|
|
} |