implement dynamic reading everywhere, add method rendering and super calls

This commit is contained in:
Qrakhen 2025-11-07 14:06:56 +01:00
parent da653b3ce5
commit 5d17059a7a
9 changed files with 176 additions and 77 deletions

View File

@ -2,6 +2,8 @@
public class ClassBuilder
{
public string Name;
public ClassBuilder? Outer;
public bool IsDerived;
}

View File

@ -13,16 +13,23 @@ namespace Qrakhen.Qamp.Core.Compilation;
public record struct Local(string Name, int Depth = -1, bool IsCaptured = false);
public record struct Outer(long Index, bool IsLocal);
public enum FunctionType { Function, Init, Method, Script }
public enum FunctionType { Function, Constructor, Method, Script }
public class Compiler
internal class CompilerState
{
// could contain state like tokens, function builder, etc. and then IDigesters use that compiler state. smart, innit?
private readonly ILogger _logger = LoggerService.Get<Compiler>();
public readonly IReader<Token> Reader;
public Token Current;
public Token Previous;
public TokenPosition ErrorPosition = TokenPosition.None;
public TokenPosition CurrentPosition => Reader.CurrentPosition;
public bool HadError => !ErrorPosition.IsNone;
public bool Done => Reader.Done;
private readonly PopStack<Token> _stack;
public bool ThrowOnError { get; set; } = true;
internal Builder Builder { get; private set; }
public Builder Builder;
public FunctionBuilder Function => Builder.Function;
public long CurrentInstruction => Function.Segment.Instructions.Count;
}
public class DigesterProvider
@ -32,7 +39,7 @@ public class DigesterProvider
public class Digester : ISteppable<Token>
{
private readonly Compiler _compiler;
private readonly CompilerState _compiler;
public event Action<Token, string>? Error;
@ -44,8 +51,8 @@ public class Digester : ISteppable<Token>
internal Token Current;
internal Token Previous;
public ClassBuilder? Class { get; private set; }
public Builder Builder { get; private set; }
public ClassBuilder? ClassBuilder { get; private set; }
public bool ThrowOnError { get; set; } = true;
public bool HadError => !ErrorPosition.IsNone;
@ -84,9 +91,7 @@ public class Digester : ISteppable<Token>
internal Builder CreateBuilder(FunctionType type)
{
Builder builder = new() {
Outer = Builder
};
Builder builder = new() { Outer = Builder };
if (type != FunctionType.Script)
builder.Function.Name = Previous.Value!;
@ -109,8 +114,8 @@ public class Digester : ISteppable<Token>
_logger.Method();
if (Match(T.Class)) DeclareClass();
else if (Match(T.Function)) DeclareFunction();
else if (Match(T.Var)) DeclareVariable();
else Statement();
else if (Match(T.Var)) DeclareVariable();
else Statement();
}
internal void Statement()
@ -126,7 +131,7 @@ public class Digester : ISteppable<Token>
else if (Match(T.Print)) Print();
else if (Match(T.Export)) Export();
else if (Match(T.Import)) Import();
else if (Match(T.ContextOpen)) CreateContext();
else if (Match(T.ContextOpen)) Context();
else StatementExpression();
}
@ -143,7 +148,7 @@ public class Digester : ISteppable<Token>
internal void Expression() => WeightedDigest(Weight.Assign);
internal void CreateContext()
internal void Context()
{
_logger.Method();
BeginScope();
@ -177,9 +182,8 @@ public class Digester : ISteppable<Token>
ExpressionParser.Get(Previous.Type).Infix?.Invoke(this, canAssign);
}
if (canAssign && Match(T.Equal)) {
if (canAssign && Match(T.Equal))
ErrorAtCurrent("Invalid assignment target");
}
}
internal void DeclareClass()
@ -189,12 +193,25 @@ public class Digester : ISteppable<Token>
internal void DeclareFunction()
{
_logger.Method();
long global = ParseVariable();
InitializeLocal();
CreateFunction(FunctionType.Function);
DefineVariable(global);
}
internal void DeclareMethod()
{
_logger.Method();
Consume(T.Identifier, "Expected method name", false);
long name = IdentifierConstant(Previous.Value!);
FunctionType type = FunctionType.Method;
if (Previous.Value == ClassBuilder?.Name)
type = FunctionType.Constructor;
CreateFunction(type);
EmitDynamic(Op.Method, name);
}
internal void DeclareVariable()
{
_logger.Method();
@ -477,7 +494,7 @@ public class Digester : ISteppable<Token>
InitializeLocal();
} else {
Emit(Op.DefineGlobal);
Emit(index);
EmitDynamic(index);
}
}
@ -496,16 +513,40 @@ public class Digester : ISteppable<Token>
return count;
}
/// <summary>
/// Primary Emit.
/// </summary>
internal void Emit(params byte[] data)
{
if (data.Length == 1)
_logger.Verbose($"Emitting {new Instruction(data[0])}");
else if (data.Length > 1)
_logger.Verbose($"Emitting {string.Join(' ', data)}");
foreach (var value in data)
Function.Segment.Instructions.Add(value);
}
/// <summary>
/// Sets instruction bytes at <paramref name="position"/>
/// </summary>
internal void Patch(byte[] bytes, long position)
{
for (int i = 0; i < bytes.Length; i++)
{
Function.Segment.Instructions[position + i] = bytes[i];
}
}
internal void EmitConstant(Value constant)
{
Emit(Op.Constant);
Emit(MakeConstant(constant));
EmitDynamic(MakeConstant(constant));
}
internal long EmitJump(Instruction instruction)
{
Emit(instruction);
Emit(0L);
Emit(-1L);
return CurrentInstruction - sizeof(long);
}
@ -522,22 +563,27 @@ public class Digester : ISteppable<Token>
Emit(offset);
}
/// <summary>
/// Emits a dynamic range of bytes into the instruction set.
/// </summary>
internal void EmitDynamic(byte[] data)
{
Emit(data.Length | 0x80);
Emit((byte)(data.Length | 0x80));
Emit(data);
}
internal void Emit(params byte[] data)
/// <summary>
/// Emits the given <paramref name="instruction"/>, and then adds <paramref name="data"/> using <see cref="EmitDynamic(byte[])"/>.
/// </summary>
internal void EmitDynamic(Instruction instruction, long data)
{
if (data.Length == 1)
_logger.Verbose($"Emitting {new Instruction(data[0])}");
else if (data.Length > 1)
_logger.Verbose($"Emitting {string.Join(' ', data)}");
foreach (var value in data)
Function.Segment.Instructions.Add(value);
Emit(instruction);
EmitDynamic(data);
}
internal void EmitDynamic(long value)
=> EmitDynamic(value.GetDynamicBytes());
internal void Emit(Instruction instruction)
{
Emit(instruction.Code);
@ -555,13 +601,6 @@ public class Digester : ISteppable<Token>
Emit(i);
}
internal void Patch(byte[] bytes, long position)
{
for (int i = 0; i < bytes.Length; i++) {
Function.Segment.Instructions[position + i] = bytes[i];
}
}
internal void Emit(params Instruction[] instructions)
=> Emit(instructions.AsEnumerable());

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Qrakhen.Qamp.Core.Compilation.Digesters;
public interface IDigester
internal interface IDigester
{
void Digest(Compiler compiler);
void Digest(CompilerState state);
}

View File

@ -63,13 +63,13 @@ public static class ExpressionParser
if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression();
digester.Emit(OpCode.SetProperty, name.GetBytes());
digester.EmitDynamic(OpCode.SetProperty, name);
} else if (digester.Match(TokenType.GroupOpen)) {
Byte argCount = 0; //argumentList();
digester.Emit(OpCode.Invoke, name.GetBytes());
digester.Emit(argCount);
digester.EmitDynamic(argCount);
} else {
digester.Emit(OpCode.GetProperty, name.GetBytes());
digester.EmitDynamic(OpCode.GetProperty, name);
}
}
@ -108,12 +108,10 @@ public static class ExpressionParser
break;
}
digester.Consume(TokenType.ArrayClose, "Expected ']' after array definition");
digester.Emit(
OpCode.Array,
digester.MakeConstant(new Value((long)length)).GetBytes());
digester.EmitDynamic(OpCode.Array, digester.MakeConstant(new Value((long)length)));
}
static void Add(Digester digester, bool canAssign)
static void ArrayAdd(Digester digester, bool canAssign)
{
digester.Expression();
digester.Emit(OpCode.ArrayAdd);
@ -168,10 +166,9 @@ public static class ExpressionParser
digester.EmitConstant(Values.Objects.String.Make(digester.Previous.Value));
}
static void Variable(Digester digester, bool canAssign)
static void Variable(Digester digester, string name, bool canAssign)
{
OpCode Get, Set;
string name = digester.Previous.Value!;
long variable = digester.ResolveLocal(digester.Builder, name);
if (variable > -1) {
Get = OpCode.GetLocal;
@ -188,21 +185,46 @@ public static class ExpressionParser
if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression();
digester.Emit(Set);
digester.Emit(variable);
digester.EmitDynamic(variable);
} else {
digester.Emit(Get);
digester.Emit(variable);
digester.EmitDynamic(variable);
}
}
static void Variable(Digester digester, bool canAssign)
{
Variable(digester, digester.Previous.Value!, canAssign);
}
static void Base(Digester digester, bool canAssign)
{
if (digester.ClassBuilder == null)
digester.ErrorAtPrevious("We're currently not in a class. 'base' only works inside of a class context.");
else if (digester.ClassBuilder.Outer == null)
digester.ErrorAtPrevious($"{digester.ClassBuilder.Name} is not inherited from any class, so the 'base' keyword points to nothing.");
else
{
digester.Consume(TokenType.Dot, "Expected '.' after 'base' keyword.");
digester.Consume(TokenType.Identifier, "Expected an identifier after 'base' keyword.");
long name = digester.IdentifierConstant(digester.Previous.Value);
Variable(digester, "this", false);
if (digester.Match(TokenType.GroupOpen))
{
byte arguments = digester.ArgumentList();
Variable(digester, "base", false);
digester.Emit(OpCode.BaseCall);
digester.Emit(arguments);
} else {
Variable(digester, "base", false);
digester.Emit(OpCode.Base);
}
}
}
static void This(Digester digester, bool canAssign)
{
if (digester.Class == null)
if (digester.ClassBuilder == null)
digester.ErrorAtPrevious("We're currently not in a class. 'this' only refers to the current instance of one.");
else
Variable(digester, false);

View File

@ -11,6 +11,8 @@ public readonly record struct Instruction(byte Code)
public override string ToString()
{
if ((Code & 0x80) == 0x80)
return $"<Dynamic ({Code ^ 0x80})>";
return $"<{OpCode} ({Code})>";
}
}

View File

@ -20,7 +20,7 @@ public enum OpCode
GetProperty,
SetProperty,
Property,
GetSuper,
Base,
Equal,
Greater,
Less,
@ -53,7 +53,7 @@ public enum OpCode
Loop,
Call,
Invoke,
SuperInvoke,
BaseCall,
Closure,
CloseUpvalue,
Return,

View File

@ -10,18 +10,18 @@ namespace Qrakhen.Qamp.Core.Execution;
using Op = OpCode;
public class GetPtr<T>
public class Pointer<T>
{
public IGetSet<long, T> Target;
public long Pointer;
public long Ptr;
public GetPtr(IGetSet<long, T> target, long pointer = 0)
public Pointer(IGetSet<long, T> target, long pointer = 0)
{
Target = target;
Pointer = pointer;
Ptr = pointer;
}
public T Next() => Target.Get(Pointer++);
public T Next() => Target.Get(Ptr++);
public void Set(long position, T value)
{
@ -35,7 +35,7 @@ public class GetPtr<T>
}
// this is all a bit cheesy imho
public class InstructionPtr : GetPtr<Instruction>
public class InstructionPtr : Pointer<Instruction>
{
public Segment Segment;
@ -46,36 +46,49 @@ public class InstructionPtr : GetPtr<Instruction>
public long NextLong()
{
long value = Segment.ReadLong(Pointer);
Pointer += sizeof(long);
long value = Segment.ReadLong(Ptr);
Ptr += sizeof(long);
return value;
}
public Value NextConstant()
{
return Segment.Constants[NextLong()];
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? GetString(long offset)
{
return Segment.Constants[offset].Ptr.Value as String;
}
public Instruction Instruction => Segment.Instructions[Pointer];
public Instruction Instruction => Segment.Instructions[Ptr];
public static Instruction operator +(InstructionPtr ptr, int value) => ptr.Segment.Instructions[ptr.Pointer + value];
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.Pointer - value];
public static Instruction operator -(InstructionPtr ptr, int value) => ptr.Segment.Instructions[ptr.Ptr - value];
public static InstructionPtr operator ++(InstructionPtr ptr)
{
ptr.Pointer++;
ptr.Ptr++;
return ptr;
}
public static InstructionPtr operator --(InstructionPtr ptr)
{
ptr.Pointer--;
ptr.Ptr--;
return ptr;
}
}
@ -84,13 +97,13 @@ public class Call
{
public Closure Closure;
public InstructionPtr Ptr;
public GetPtr<Value> StackPtr;
public Pointer<Value> StackPtr;
public Call(Closure closure, StackLike<Value> stack, long stackOffset)
{
Closure = closure;
Ptr = new InstructionPtr(Closure.Function.Segment);
StackPtr = new GetPtr<Value>(stack, stackOffset);
StackPtr = new Pointer<Value>(stack, stackOffset);
}
}
@ -162,21 +175,21 @@ public class Runner : IDisposable
case Op.BitwiseRight: OpBitwiseRight(); break;
case Op.GetLocal: {
long slot = call.Ptr.NextLong();
long slot = call.Ptr.NextDynamic();
_logger.Verbose($"getting slot {slot} which is {call.StackPtr.Get(slot)}");
Push(call.StackPtr.Get(slot));
break;
}
case Op.SetLocal: {
long slot = call.Ptr.NextLong();
long slot = call.Ptr.NextDynamic();
_logger.Verbose($"setting stackptr {slot} to {Peek()}");
call.StackPtr.Set(slot, Peek());
break;
}
case Op.DefineGlobal: {
string? name = call.Ptr.GetString(call.Ptr.NextLong())?.Value;
string? name = call.Ptr.GetString(call.Ptr.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name))
throw new ExecutionException($"tried to define global variable with empty name");
Globals[name] = Pop();
@ -185,7 +198,7 @@ public class Runner : IDisposable
}
case Op.SetGlobal: {
string? name = call.Ptr.GetString(call.Ptr.NextLong())?.Value;
string? name = call.Ptr.GetString(call.Ptr.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name))
throw new ExecutionException($"tried to set global variable with empty name");
if (!Globals.Has(name))
@ -229,13 +242,13 @@ public class Runner : IDisposable
Pop();
return ExecutionResult.OK;
}
Cursor = call.StackPtr.Pointer; // well find a better way...
Cursor = call.StackPtr.Ptr; // well find a better way...
Push(result);
call = Calls.Peek();
break;
}
} while (call.Ptr.Segment.Instructions.Length > call.Ptr.Pointer);
} while (call.Ptr.Segment.Instructions.Length > call.Ptr.Ptr);
return ExecutionResult.OK;
}

View File

@ -35,7 +35,7 @@ public class Segment(
/// <returns></returns>
public long ReadDynamic(long offset, out byte length)
{
length = Read(offset, 1)[0];
length = (byte)(Read(offset) ^ 0x80);
byte[] bytes = new byte[8];
for (int i = 0; i < length; i++) {
bytes[i] = Instructions[offset + 1 + i];

View File

@ -49,6 +49,27 @@ public static class Extensions
stream.SetLength(stream.Length - amount);
}
public static byte[] GetDynamicBytes(this long value)
{
byte[] data = value.GetBytes();
return data.Subset(0, GetPrimitiveLength(value));
}
public static int GetPrimitiveLength(this long value)
{
int length = 8;
for (int i = 1; i < 8; i++)
{
if (value <= (1L << (i * 8)))
{
length = i;
break;
}
}
return length;
}
public static byte[] GetBytes(this short value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this ushort value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this int value) => BitConverter.GetBytes(value);