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 class ClassBuilder
{ {
public string Name;
public ClassBuilder? Outer; public ClassBuilder? Outer;
public bool IsDerived; 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 Local(string Name, int Depth = -1, bool IsCaptured = false);
public record struct Outer(long Index, bool IsLocal); 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? public readonly IReader<Token> Reader;
private readonly ILogger _logger = LoggerService.Get<Compiler>(); 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 public class DigesterProvider
@ -32,7 +39,7 @@ public class DigesterProvider
public class Digester : ISteppable<Token> public class Digester : ISteppable<Token>
{ {
private readonly Compiler _compiler; private readonly CompilerState _compiler;
public event Action<Token, string>? Error; public event Action<Token, string>? Error;
@ -44,8 +51,8 @@ public class Digester : ISteppable<Token>
internal Token Current; internal Token Current;
internal Token Previous; internal Token Previous;
public ClassBuilder? Class { get; private set; }
public Builder Builder { get; private set; } public Builder Builder { get; private set; }
public ClassBuilder? ClassBuilder { get; private set; }
public bool ThrowOnError { get; set; } = true; public bool ThrowOnError { get; set; } = true;
public bool HadError => !ErrorPosition.IsNone; public bool HadError => !ErrorPosition.IsNone;
@ -84,9 +91,7 @@ public class Digester : ISteppable<Token>
internal Builder CreateBuilder(FunctionType type) internal Builder CreateBuilder(FunctionType type)
{ {
Builder builder = new() { Builder builder = new() { Outer = Builder };
Outer = Builder
};
if (type != FunctionType.Script) if (type != FunctionType.Script)
builder.Function.Name = Previous.Value!; builder.Function.Name = Previous.Value!;
@ -126,7 +131,7 @@ public class Digester : ISteppable<Token>
else if (Match(T.Print)) Print(); else if (Match(T.Print)) Print();
else if (Match(T.Export)) Export(); else if (Match(T.Export)) Export();
else if (Match(T.Import)) Import(); else if (Match(T.Import)) Import();
else if (Match(T.ContextOpen)) CreateContext(); else if (Match(T.ContextOpen)) Context();
else StatementExpression(); else StatementExpression();
} }
@ -143,7 +148,7 @@ public class Digester : ISteppable<Token>
internal void Expression() => WeightedDigest(Weight.Assign); internal void Expression() => WeightedDigest(Weight.Assign);
internal void CreateContext() internal void Context()
{ {
_logger.Method(); _logger.Method();
BeginScope(); BeginScope();
@ -177,10 +182,9 @@ public class Digester : ISteppable<Token>
ExpressionParser.Get(Previous.Type).Infix?.Invoke(this, canAssign); ExpressionParser.Get(Previous.Type).Infix?.Invoke(this, canAssign);
} }
if (canAssign && Match(T.Equal)) { if (canAssign && Match(T.Equal))
ErrorAtCurrent("Invalid assignment target"); ErrorAtCurrent("Invalid assignment target");
} }
}
internal void DeclareClass() internal void DeclareClass()
{ {
@ -189,12 +193,25 @@ public class Digester : ISteppable<Token>
internal void DeclareFunction() internal void DeclareFunction()
{ {
_logger.Method();
long global = ParseVariable(); long global = ParseVariable();
InitializeLocal(); InitializeLocal();
CreateFunction(FunctionType.Function); CreateFunction(FunctionType.Function);
DefineVariable(global); 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() internal void DeclareVariable()
{ {
_logger.Method(); _logger.Method();
@ -477,7 +494,7 @@ public class Digester : ISteppable<Token>
InitializeLocal(); InitializeLocal();
} else { } else {
Emit(Op.DefineGlobal); Emit(Op.DefineGlobal);
Emit(index); EmitDynamic(index);
} }
} }
@ -496,16 +513,40 @@ public class Digester : ISteppable<Token>
return count; 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) internal void EmitConstant(Value constant)
{ {
Emit(Op.Constant); Emit(Op.Constant);
Emit(MakeConstant(constant)); EmitDynamic(MakeConstant(constant));
} }
internal long EmitJump(Instruction instruction) internal long EmitJump(Instruction instruction)
{ {
Emit(instruction); Emit(instruction);
Emit(0L); Emit(-1L);
return CurrentInstruction - sizeof(long); return CurrentInstruction - sizeof(long);
} }
@ -522,22 +563,27 @@ public class Digester : ISteppable<Token>
Emit(offset); Emit(offset);
} }
/// <summary>
/// Emits a dynamic range of bytes into the instruction set.
/// </summary>
internal void EmitDynamic(byte[] data) internal void EmitDynamic(byte[] data)
{ {
Emit(data.Length | 0x80); Emit((byte)(data.Length | 0x80));
Emit(data); 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) Emit(instruction);
_logger.Verbose($"Emitting {new Instruction(data[0])}"); EmitDynamic(data);
else if (data.Length > 1)
_logger.Verbose($"Emitting {string.Join(' ', data)}");
foreach (var value in data)
Function.Segment.Instructions.Add(value);
} }
internal void EmitDynamic(long value)
=> EmitDynamic(value.GetDynamicBytes());
internal void Emit(Instruction instruction) internal void Emit(Instruction instruction)
{ {
Emit(instruction.Code); Emit(instruction.Code);
@ -555,13 +601,6 @@ public class Digester : ISteppable<Token>
Emit(i); 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) internal void Emit(params Instruction[] instructions)
=> Emit(instructions.AsEnumerable()); => Emit(instructions.AsEnumerable());

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Qrakhen.Qamp.Core.Compilation.Digesters; 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)) { if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression(); digester.Expression();
digester.Emit(OpCode.SetProperty, name.GetBytes()); digester.EmitDynamic(OpCode.SetProperty, name);
} else if (digester.Match(TokenType.GroupOpen)) { } else if (digester.Match(TokenType.GroupOpen)) {
Byte argCount = 0; //argumentList(); Byte argCount = 0; //argumentList();
digester.Emit(OpCode.Invoke, name.GetBytes()); digester.Emit(OpCode.Invoke, name.GetBytes());
digester.Emit(argCount); digester.EmitDynamic(argCount);
} else { } else {
digester.Emit(OpCode.GetProperty, name.GetBytes()); digester.EmitDynamic(OpCode.GetProperty, name);
} }
} }
@ -108,12 +108,10 @@ public static class ExpressionParser
break; break;
} }
digester.Consume(TokenType.ArrayClose, "Expected ']' after array definition"); digester.Consume(TokenType.ArrayClose, "Expected ']' after array definition");
digester.Emit( digester.EmitDynamic(OpCode.Array, digester.MakeConstant(new Value((long)length)));
OpCode.Array,
digester.MakeConstant(new Value((long)length)).GetBytes());
} }
static void Add(Digester digester, bool canAssign) static void ArrayAdd(Digester digester, bool canAssign)
{ {
digester.Expression(); digester.Expression();
digester.Emit(OpCode.ArrayAdd); digester.Emit(OpCode.ArrayAdd);
@ -168,10 +166,9 @@ public static class ExpressionParser
digester.EmitConstant(Values.Objects.String.Make(digester.Previous.Value)); 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; OpCode Get, Set;
string name = digester.Previous.Value!;
long variable = digester.ResolveLocal(digester.Builder, name); long variable = digester.ResolveLocal(digester.Builder, name);
if (variable > -1) { if (variable > -1) {
Get = OpCode.GetLocal; Get = OpCode.GetLocal;
@ -188,21 +185,46 @@ public static class ExpressionParser
if (canAssign && digester.Match(TokenType.Equal)) { if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression(); digester.Expression();
digester.Emit(Set); digester.Emit(Set);
digester.Emit(variable); digester.EmitDynamic(variable);
} else { } else {
digester.Emit(Get); 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) 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) 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."); digester.ErrorAtPrevious("We're currently not in a class. 'this' only refers to the current instance of one.");
else else
Variable(digester, false); Variable(digester, false);

View File

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

View File

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

View File

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

View File

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

View File

@ -49,6 +49,27 @@ public static class Extensions
stream.SetLength(stream.Length - amount); 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 short value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this ushort value) => BitConverter.GetBytes(value); public static byte[] GetBytes(this ushort value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this int value) => BitConverter.GetBytes(value); public static byte[] GetBytes(this int value) => BitConverter.GetBytes(value);