From 5d17059a7a73487fc263b291f4fc9c8deba393ee Mon Sep 17 00:00:00 2001 From: Qrakhen Date: Fri, 7 Nov 2025 14:06:56 +0100 Subject: [PATCH] implement dynamic reading everywhere, add method rendering and super calls --- .../Compilation/Builders/ClassBuilder.cs | 2 + Qrakhen.Qamp.Core/Compilation/Digester.cs | 113 ++++++++++++------ .../Compilation/Digesters/IDigester.cs | 4 +- .../Compilation/ExpressionParser.cs | 48 ++++++-- Qrakhen.Qamp.Core/Execution/Instruction.cs | 2 + Qrakhen.Qamp.Core/Execution/OpCode.cs | 4 +- Qrakhen.Qamp.Core/Execution/Runner.cs | 57 +++++---- Qrakhen.Qamp.Core/Execution/Segment.cs | 2 +- Qrakhen.Qamp.Core/Extensions.cs | 21 ++++ 9 files changed, 176 insertions(+), 77 deletions(-) diff --git a/Qrakhen.Qamp.Core/Compilation/Builders/ClassBuilder.cs b/Qrakhen.Qamp.Core/Compilation/Builders/ClassBuilder.cs index d568956..5a163d4 100644 --- a/Qrakhen.Qamp.Core/Compilation/Builders/ClassBuilder.cs +++ b/Qrakhen.Qamp.Core/Compilation/Builders/ClassBuilder.cs @@ -2,6 +2,8 @@ public class ClassBuilder { + public string Name; + public ClassBuilder? Outer; public bool IsDerived; } \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Compilation/Digester.cs b/Qrakhen.Qamp.Core/Compilation/Digester.cs index 7417539..cd0c76c 100644 --- a/Qrakhen.Qamp.Core/Compilation/Digester.cs +++ b/Qrakhen.Qamp.Core/Compilation/Digester.cs @@ -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(); + public readonly IReader 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 _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 { - private readonly Compiler _compiler; + private readonly CompilerState _compiler; public event Action? Error; @@ -44,8 +51,8 @@ public class Digester : ISteppable 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 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 _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 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 internal void Expression() => WeightedDigest(Weight.Assign); - internal void CreateContext() + internal void Context() { _logger.Method(); BeginScope(); @@ -177,9 +182,8 @@ public class Digester : ISteppable ExpressionParser.Get(Previous.Type).Infix?.Invoke(this, canAssign); } - if (canAssign && Match(T.Equal)) { - ErrorAtCurrent("Invalid assignment target"); - } + if (canAssign && Match(T.Equal)) + ErrorAtCurrent("Invalid assignment target"); } internal void DeclareClass() @@ -189,12 +193,25 @@ public class Digester : ISteppable 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 InitializeLocal(); } else { Emit(Op.DefineGlobal); - Emit(index); + EmitDynamic(index); } } @@ -496,16 +513,40 @@ public class Digester : ISteppable return count; } + /// + /// Primary Emit. + /// + 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); + } + + /// + /// Sets instruction bytes at + /// + 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,21 +563,26 @@ public class Digester : ISteppable Emit(offset); } + /// + /// Emits a dynamic range of bytes into the instruction set. + /// internal void EmitDynamic(byte[] data) { - Emit(data.Length | 0x80); + Emit((byte)(data.Length | 0x80)); Emit(data); } - internal void Emit(params byte[] data) + /// + /// Emits the given , and then adds using . + /// + 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) { @@ -555,13 +601,6 @@ public class Digester : ISteppable 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()); diff --git a/Qrakhen.Qamp.Core/Compilation/Digesters/IDigester.cs b/Qrakhen.Qamp.Core/Compilation/Digesters/IDigester.cs index 58df209..e059a9b 100644 --- a/Qrakhen.Qamp.Core/Compilation/Digesters/IDigester.cs +++ b/Qrakhen.Qamp.Core/Compilation/Digesters/IDigester.cs @@ -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); } diff --git a/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs b/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs index b0c5599..98227ec 100644 --- a/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs +++ b/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs @@ -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); diff --git a/Qrakhen.Qamp.Core/Execution/Instruction.cs b/Qrakhen.Qamp.Core/Execution/Instruction.cs index d88ffaa..66aefd6 100644 --- a/Qrakhen.Qamp.Core/Execution/Instruction.cs +++ b/Qrakhen.Qamp.Core/Execution/Instruction.cs @@ -11,6 +11,8 @@ public readonly record struct Instruction(byte Code) public override string ToString() { + if ((Code & 0x80) == 0x80) + return $""; return $"<{OpCode} ({Code})>"; } } \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Execution/OpCode.cs b/Qrakhen.Qamp.Core/Execution/OpCode.cs index f3bdb2a..73fad7f 100644 --- a/Qrakhen.Qamp.Core/Execution/OpCode.cs +++ b/Qrakhen.Qamp.Core/Execution/OpCode.cs @@ -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, diff --git a/Qrakhen.Qamp.Core/Execution/Runner.cs b/Qrakhen.Qamp.Core/Execution/Runner.cs index 95ba685..e99db78 100644 --- a/Qrakhen.Qamp.Core/Execution/Runner.cs +++ b/Qrakhen.Qamp.Core/Execution/Runner.cs @@ -10,18 +10,18 @@ namespace Qrakhen.Qamp.Core.Execution; using Op = OpCode; -public class GetPtr +public class Pointer { public IGetSet Target; - public long Pointer; + public long Ptr; - public GetPtr(IGetSet target, long pointer = 0) + public Pointer(IGetSet 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 } // this is all a bit cheesy imho -public class InstructionPtr : GetPtr +public class InstructionPtr : Pointer { public Segment Segment; @@ -46,36 +46,49 @@ public class InstructionPtr : GetPtr 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 StackPtr; + public Pointer StackPtr; public Call(Closure closure, StackLike stack, long stackOffset) { Closure = closure; Ptr = new InstructionPtr(Closure.Function.Segment); - StackPtr = new GetPtr(stack, stackOffset); + StackPtr = new Pointer(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; } diff --git a/Qrakhen.Qamp.Core/Execution/Segment.cs b/Qrakhen.Qamp.Core/Execution/Segment.cs index b122efd..91e9775 100644 --- a/Qrakhen.Qamp.Core/Execution/Segment.cs +++ b/Qrakhen.Qamp.Core/Execution/Segment.cs @@ -35,7 +35,7 @@ public class Segment( /// 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]; diff --git a/Qrakhen.Qamp.Core/Extensions.cs b/Qrakhen.Qamp.Core/Extensions.cs index 15683d4..a98e257 100644 --- a/Qrakhen.Qamp.Core/Extensions.cs +++ b/Qrakhen.Qamp.Core/Extensions.cs @@ -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);