430 lines
13 KiB
C#
430 lines
13 KiB
C#
using Qrakhen.Qamp.Core.Collections;
|
|
using Qrakhen.Qamp.Core.Compilation;
|
|
using Qrakhen.Qamp.Core.Logging;
|
|
using Qrakhen.Qamp.Core.Tokenization;
|
|
using Qrakhen.Qamp.Core.Values;
|
|
using Qrakhen.Qamp.Core.Values.Objects;
|
|
using String = Qrakhen.Qamp.Core.Values.Objects.String;
|
|
|
|
namespace Qrakhen.Qamp.Core.Execution;
|
|
|
|
using Op = OpCode;
|
|
|
|
/*
|
|
* Todo:
|
|
* - Make Digester OOP-ish
|
|
* - inherit from a base digester that has the continouus stack / reader references as protected information
|
|
* - one digester for every job area (Loops, Functions, Variables, etc.)
|
|
* - time is not as essential here as I want to have the executables pre-compiled to OpCodes anyway
|
|
* - Think of a cleaner way to handle Obj (Pointers specifically)
|
|
* - Finish runner
|
|
* - add interpretation for all remaining op codes
|
|
* - make variable operations faster with optimized inline lambda expressions
|
|
* - better debugging for the instruction set
|
|
* - Add classic sqript dialect
|
|
* - Fix console "IDE"
|
|
* - Split Runner, Digester and Core into separate projects
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Dynamic Pointer able to point to any object implementing the <see cref="IGetSet{long, TValue}"/> interface.
|
|
/// </summary>
|
|
public class Pointer<T>
|
|
{
|
|
protected IGetSet<long, T> Target;
|
|
public long Ptr;
|
|
|
|
public Pointer(IGetSet<long, T> target, long pointer = 0)
|
|
{
|
|
Target = target;
|
|
Ptr = pointer;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the next item from <see cref="Target"/> and increases the pointer by 1.
|
|
/// </summary>
|
|
public T Next() => Target.Get(Ptr++);
|
|
|
|
/// <summary>
|
|
/// Sets the value of <see cref="Target"/> at <paramref name="position"/>.
|
|
/// </summary>
|
|
public void Set(long position, T value) => Target.Set(position, value);
|
|
|
|
/// <summary>
|
|
/// Sets the value of <see cref="Target"/> at the current <see cref="Ptr"/> location.
|
|
/// </summary>
|
|
public void Set(T value) => Set(Ptr, value);
|
|
|
|
/// <summary>
|
|
/// Gets the value of <see cref="Target"/> at <paramref name="position"/>.
|
|
/// </summary>
|
|
public T Get(long position) => Target.Get(position);
|
|
|
|
/// <summary>
|
|
/// Gets the value of <see cref="Target"/> at the current <see cref="Ptr"/> location.
|
|
/// </summary>
|
|
public T Get() => Get(Ptr);
|
|
}
|
|
|
|
// this is all a bit cheesy imho
|
|
public class InstructionPtr : Pointer<Instruction>
|
|
{
|
|
public Segment Segment;
|
|
|
|
public InstructionPtr(Segment segment, int position = 0) : base(segment.Instructions, position)
|
|
{
|
|
Segment = segment;
|
|
}
|
|
|
|
public long NextLong()
|
|
{
|
|
long value = Segment.ReadLong(Ptr);
|
|
Ptr += sizeof(long);
|
|
return value;
|
|
}
|
|
|
|
public Value NextConstant()
|
|
{
|
|
return Segment.Constants[NextDynamic()];
|
|
}
|
|
|
|
public long NextDynamic()
|
|
{
|
|
int length = Segment.Read(Ptr) ^ 0x80;
|
|
Ptr++;
|
|
byte[] data = new byte[8];
|
|
for (int i = 0; i < length; i++)
|
|
data[i] = Segment.Read(Ptr++);
|
|
return data.ToInt64();
|
|
}
|
|
|
|
|
|
public String? GetStringConstant(long offset)
|
|
{
|
|
return Segment.Constants[offset].Ptr.Value as String;
|
|
}
|
|
|
|
public Instruction Instruction => Segment.Instructions[Ptr];
|
|
|
|
public static Instruction operator +(InstructionPtr ptr, int value)
|
|
=> ptr.Segment.Instructions[ptr.Ptr + value];
|
|
|
|
public static Instruction operator -(InstructionPtr ptr, int value)
|
|
=> ptr.Segment.Instructions[ptr.Ptr - value];
|
|
|
|
public static InstructionPtr operator ++(InstructionPtr ptr)
|
|
{
|
|
ptr.Ptr++;
|
|
return ptr;
|
|
}
|
|
|
|
public static InstructionPtr operator --(InstructionPtr ptr)
|
|
{
|
|
ptr.Ptr--;
|
|
return ptr;
|
|
}
|
|
}
|
|
|
|
public class Call
|
|
{
|
|
public Closure Closure;
|
|
public InstructionPtr Instruction;
|
|
public Pointer<Value> StackPtr;
|
|
|
|
public Call(Closure closure, StackLike<Value> stack, long stackOffset)
|
|
{
|
|
Closure = closure;
|
|
Instruction = new InstructionPtr(Closure.Function.Segment);
|
|
StackPtr = new Pointer<Value>(stack, stackOffset);
|
|
}
|
|
}
|
|
|
|
public unsafe class OuterWrapper
|
|
{
|
|
public Value* Target;
|
|
public Value Stored;
|
|
}
|
|
|
|
public class Runner : IDisposable
|
|
{
|
|
private readonly ILogger _logger = LoggerService.Get<Runner>();
|
|
|
|
public readonly Options Options;
|
|
|
|
public StackLike<Call> Calls;
|
|
|
|
// todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ?
|
|
public StackLike<Value> Stack;
|
|
public long Cursor;
|
|
|
|
public Register<string, Value> Globals = new Register<string, Value>();
|
|
public Register<string, Value> Imports = new Register<string, Value>();
|
|
public Register<string, Value> Exports = new Register<string, Value>();
|
|
|
|
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);
|
|
Digester digester = new Digester(reader);
|
|
Function function = digester.Digest();
|
|
File.WriteAllBytes("func.sqi", function.Segment.Serialize());
|
|
Closure closure = new Closure(function);
|
|
Push(Obj.Create(closure));
|
|
Call(closure, 0);
|
|
return Interpret();
|
|
}
|
|
|
|
private ExecutionResult Interpret()
|
|
{
|
|
_logger.Method();
|
|
Call call = Calls.Peek(-1);
|
|
do {
|
|
Op opCode = call.Instruction.Next();
|
|
_logger.Verbose($"OpCode: {opCode}");
|
|
switch (opCode) {
|
|
case Op.Constant: Push(call.Instruction.NextConstant()); break;
|
|
case Op.Pop: Pop(); break;
|
|
|
|
case Op.Not: OpNot(); break;
|
|
case Op.Add: OpAdd(); break;
|
|
case Op.Subtract: OpSubtract(); break;
|
|
case Op.Divide: OpDivide(); break;
|
|
case Op.Modulo: OpModulo(); break;
|
|
case Op.Multiply: OpMultiply(); break;
|
|
case Op.Negate: OpNegate(); break;
|
|
case Op.BitwiseOr: OpBitwiseOr(); break;
|
|
case Op.BitwiseAnd: OpBitwiseAnd(); break;
|
|
case Op.BitwiseXor: OpBitwiseXor(); break;
|
|
case Op.BitwiseNot: OpBitwiseNot(); break;
|
|
case Op.BitwiseLeft: OpBitwiseLeft(); break;
|
|
case Op.BitwiseRight: OpBitwiseRight(); 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.DefineGlobal: {
|
|
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
|
if (string.IsNullOrEmpty(name))
|
|
throw new ExecutionException($"tried to define global variable with empty name");
|
|
Globals[name] = Pop();
|
|
_logger.Verbose($"defined global {name} as {Globals[name]}");
|
|
break;
|
|
}
|
|
|
|
case Op.SetGlobal: {
|
|
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
|
if (string.IsNullOrEmpty(name))
|
|
throw new ExecutionException($"tried to set global variable with empty name");
|
|
if (!Globals.Has(name))
|
|
throw new ExecutionException($"tried to set a value of non-existing global variable");
|
|
Globals[name] = Pop();
|
|
_logger.Verbose($"set global {name} = {Globals[name]}");
|
|
break;
|
|
}
|
|
|
|
case Op.GetGlobal: {
|
|
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
|
if (string.IsNullOrEmpty(name))
|
|
throw new ExecutionException($"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.Typeof: {
|
|
Value value = Pop();
|
|
Push(String.Make(value.Type.ToString()));
|
|
break;
|
|
}
|
|
|
|
case Op.Print: {
|
|
Value value = Pop();
|
|
IO.Console.Write(value);
|
|
break;
|
|
}
|
|
|
|
case Op.Return: // todo: not done
|
|
Value result = Pop();
|
|
Calls.Pop();
|
|
if (Calls.Count == 0) {
|
|
Pop();
|
|
return ExecutionResult.OK;
|
|
}
|
|
Cursor = call.StackPtr.Ptr; // well find a better way...
|
|
Push(result);
|
|
call = Calls.Peek();
|
|
break;
|
|
}
|
|
|
|
} while (call.Instruction.Segment.Instructions.Length > call.Instruction.Ptr);
|
|
|
|
return ExecutionResult.OK;
|
|
}
|
|
|
|
private bool Call(Closure 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, Stack.Position - argumentCount);
|
|
Calls.Push(call);
|
|
return true;
|
|
}
|
|
|
|
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 void OpNot()
|
|
{
|
|
Value value = Pop();
|
|
Push(ValueOperation.Not(value));
|
|
}
|
|
|
|
private void OpAdd()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.Add(left, right));
|
|
}
|
|
|
|
private void OpSubtract()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.Subtract(left, right));
|
|
}
|
|
|
|
private void OpDivide()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.Divide(left, right));
|
|
}
|
|
|
|
private void OpModulo()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.Modulo(left, right));
|
|
}
|
|
|
|
private void OpMultiply()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.Multiply(left, right));
|
|
}
|
|
|
|
private void OpNegate()
|
|
{
|
|
Value value = Pop();
|
|
Push(ValueOperation.Negate(value));
|
|
}
|
|
|
|
private void OpBitwiseOr()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.BitwiseOr(left, right));
|
|
}
|
|
|
|
private void OpBitwiseAnd()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.BitwiseAnd(left, right));
|
|
}
|
|
|
|
private void OpBitwiseXor()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.BitwiseXor(left, right));
|
|
}
|
|
|
|
private void OpBitwiseLeft()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.BitwiseLeft(left, right));
|
|
}
|
|
|
|
private void OpBitwiseRight()
|
|
{
|
|
Value right = Pop();
|
|
Value left = Pop();
|
|
Push(ValueOperation.BitwiseRight(left, right));
|
|
}
|
|
|
|
private void OpBitwiseNot()
|
|
{
|
|
Value value = Pop();
|
|
Push(ValueOperation.BitwiseInvert(value));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_logger.Method();
|
|
}
|
|
}
|
|
|
|
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) { }
|
|
} |