implement dynamic reading everywhere, add method rendering and super calls
This commit is contained in:
parent
da653b3ce5
commit
5d17059a7a
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
public class ClassBuilder
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public ClassBuilder? Outer;
|
||||
public bool IsDerived;
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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})>";
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue